From 3f5f7922b90578239ea949aaafffa97530be65a9 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 10 Sep 2008 10:08:42 -0600 Subject: [PATCH] initial commit --- LICENSE | 5 + Makefile | 42 + README | 24 + data/dungeon.dat | 180 ++ doc/dungeon_design.html | 139 ++ include/jbdungeon.h | 332 +++ include/jbdungeondata.h | 165 ++ include/jbdungeonpainter.h | 174 ++ include/jbdungeonpaintergd.h | 55 + include/jbmaze.h | 208 ++ include/jbmazemask.h | 87 + include/treasureEngine.h | 198 ++ src/dungeoncgi.cpp | 571 +++++ src/jbdungeon.cpp | 685 ++++++ src/jbdungeondata.cpp | 1948 +++++++++++++++++ src/jbdungeonpainter.cpp | 209 ++ src/jbdungeonpaintergd.cpp | 99 + src/jbmaze.cpp | 669 ++++++ src/jbmazemask.cpp | 110 + src/main.cpp | 537 +++++ src/treasureEngine.c | 3799 ++++++++++++++++++++++++++++++++++ tem/dungeon_desc.tem | 39 + tem/dungeon_main.tem | 423 ++++ tem/generators.css | 123 ++ 24 files changed, 10821 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 data/dungeon.dat create mode 100644 doc/dungeon_design.html create mode 100644 include/jbdungeon.h create mode 100644 include/jbdungeondata.h create mode 100644 include/jbdungeonpainter.h create mode 100644 include/jbdungeonpaintergd.h create mode 100644 include/jbmaze.h create mode 100644 include/jbmazemask.h create mode 100644 include/treasureEngine.h create mode 100644 src/dungeoncgi.cpp create mode 100644 src/jbdungeon.cpp create mode 100644 src/jbdungeondata.cpp create mode 100644 src/jbdungeonpainter.cpp create mode 100644 src/jbdungeonpaintergd.cpp create mode 100644 src/jbmaze.cpp create mode 100644 src/jbmazemask.cpp create mode 100644 src/main.cpp create mode 100644 src/treasureEngine.c create mode 100755 tem/dungeon_desc.tem create mode 100755 tem/dungeon_main.tem create mode 100644 tem/generators.css diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6191ef9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +This code is released in the PUBLIC DOMAIN by the author, Jamis Buck +(jamis@jamisbuck.org). + +Permission is hereby granted to anyone, anywhere, to use this code in any +way they please, with or without attribution, and without restriction. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..865a562 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +all: cgi console + +cgi: dungeon.cgi + +console: dungeon + +CC=gcc +CPP=g++ + +LIBS=$(LDFLAGS) -lm -lqDecoder -lgd -lpng -lz -ldndutil -lnpcEngine -lwritetem + +OPTS=$(CFLAGS) -Iinclude -O3 -Wall -DUSE_COUNTER -DCTRLOCATION="\"/tmp/dungeon.cnt\"" + +.SUFFIXES: + +.SUFFIXES: .cpp .c .o + +.cpp.o: + $(CPP) $(OPTS) -c -o $@ $< + +.c.o: + $(CC) $(OPTS) -c -o $@ $< + +OBJS=\ + src/jbdungeon.o \ + src/jbdungeondata.o \ + src/jbdungeonpainter.o \ + src/jbdungeonpaintergd.o \ + src/jbmaze.o \ + src/jbmazemask.o \ + src/treasureEngine.o + +dungeon.cgi: src/dungeoncgi.o $(OBJS) + $(CPP) $(OPTS) -o dungeon.cgi src/dungeoncgi.o $(OBJS) $(LIBS) + +dungeon: src/main.o $(OBJS) + $(CPP) $(OPTS) -o dungeon src/main.o $(OBJS) $(LIBS) + +clean: + rm -f src/*.o + rm -f dungeon.cgi + rm -f dungeon diff --git a/README b/README new file mode 100644 index 0000000..de144d2 --- /dev/null +++ b/README @@ -0,0 +1,24 @@ +This application is a generator, capable of randomly producing full-fledged +dungeons and mazes for D&D, 3rd edition. It includes a CGI +front-end, as well as a command-line front-end that is suitable only for +generating mazes (though is quite powerful for all of that). + +BUILDING +-------- + +This library was written well before I knew anything about autoconf and +friends. Thus, the makefile should be considered to be only a guideline, and +you're expected to actually MUCK WITH IT. Yes, I know. How deliciously +primitive. + +You should set the CFLAGS environment variable to the locations of the +following header files: + +* dnd-writetem (a library hosted at this site) +* dnd-util (a library hosted at this site) +* dnd-npc (a library hosted at this site) +* qDecoder (http://www.qDecoder.org) +* gd2 (http://www.boutell.com/gd/) + +You should also set the LDFLAGS environment variable to the locations of the +library (*.a) files for each of the above as well. \ No newline at end of file diff --git a/data/dungeon.dat b/data/dungeon.dat new file mode 100644 index 0000000..aca3f05 --- /dev/null +++ b/data/dungeon.dat @@ -0,0 +1,180 @@ +# This is the default dungeon data file. + +# -------------------------------------------------------------------------- +# These are the user-defined door types. Note that there are a few "special" +# values that are predefined: +# +# - TRAPPED - indicates there is a trap of some sort on the door +# - REROLL1 - add the indicated value and reroll ONCE on the same table +# -------------------------------------------------------------------------- + +[doortypes] +WOODEN wooden +STONE stone +IRON iron +SIMPLE simple +GOOD good +STRONG strong +FREE free +STUCK stuck +LOCKED locked +SIDESLIDE side-sliding +DOWNSLIDE down-sliding +UPSLIDE up-sliding +MAGIC magically-reinforced +ROTATINGWALL rotating wall +PASSWALL Passwall (as the spell) +PUSHBRICK push brick to open +MAGICWORD magic word to open +GESTURE gesture to open +PRESSUREPLATE pressure-plate to open +ILLUSORYWALL illusory wall +BEHINDTAPESTRY behind tapestry +BEHINDRUBBISH behind rubbish pile +FALSEWALL false wall + +[doors] +008 WOODEN,SIMPLE,FREE +009 WOODEN,SIMPLE,FREE,TRAPPED +023 WOODEN,SIMPLE,STUCK +024 WOODEN,SIMPLE,STUCK,TRAPPED +029 WOODEN,SIMPLE,LOCKED +030 WOODEN,SIMPLE,LOCKED,TRAPPED +035 WOODEN,GOOD,FREE +036 WOODEN,GOOD,FREE,TRAPPED +044 WOODEN,GOOD,STUCK +045 WOODEN,GOOD,STUCK,TRAPPED +049 WOODEN,GOOD,LOCKED +050 WOODEN,GOOD,LOCKED,TRAPPED +055 WOODEN,STRONG,FREE +056 WOODEN,STRONG,FREE,TRAPPED +064 WOODEN,STRONG,STUCK +065 WOODEN,STRONG,STUCK,TRAPPED +069 WOODEN,STRONG,LOCKED +070 WOODEN,STRONG,LOCKED,TRAPPED +071 STONE,FREE +072 STONE,FREE,TRAPPED +075 STONE,STUCK +076 STONE,STUCK,TRAPPED +079 STONE,LOCKED +080 STONE,LOCKED,TRAPPED +081 IRON,FREE +082 IRON,FREE,TRAPPED +085 IRON,STUCK +089 IRON,LOCKED +090 IRON,LOCKED,TRAPPED +093 SIDESLIDE,REROLL1 +096 DOWNSLIDE,REROLL1 +099 UPSLIDE,REROLL1 +100 MAGIC,REROLL1 +000 + +[secretdoors] +010 ROTATINGWALL,PUSHBRICK,FREE +011 ROTATINGWALL,PUSHBRICK,FREE,TRAPPED +015 ROTATINGWALL,MAGICWORD,FREE +016 ROTATINGWALL,MAGICWORD,FREE,TRAPPED +020 ROTATINGWALL,GESTURE,FREE +021 ROTATINGWALL,GESTURE,FREE,TRAPPED +030 ROTATINGWALL,PRESSUREPLATE,FREE +031 ROTATINGWALL,PRESSUREPLATE,FREE,TRAPPED +035 ROTATINGWALL,PUSHBRICK,STUCK +036 ROTATINGWALL,PUSHBRICK,STUCK,TRAPPED +038 ROTATINGWALL,MAGICWORD,STUCK +039 ROTATINGWALL,MAGICWORD,STUCK,TRAPPED +041 ROTATINGWALL,GESTURE,STUCK +042 ROTATINGWALL,GESTURE,STUCK,TRAPPED +046 ROTATINGWALL,PRESSUREPLATE,STUCK +047 ROTATINGWALL,PRESSUREPLATE,STUCK,TRAPPED +048 PASSWALL,PUSHBRICK +049 PASSWALL,PUSHBRICK,TRAPPED +050 PASSWALL,MAGICWORD +051 PASSWALL,GESTURE +052 PASSWALL,GESTURE,TRAPPED +053 PASSWALL,PRESSUREPLATE +054 PASSWALL,PRESSUREPLATE,TRAPPED +064 SIDESLIDE,PUSHBRICK,FREE +065 SIDESLIDE,PUSHBRICK,FREE,TRAPPED +069 SIDESLIDE,MAGICWORD,FREE +070 SIDESLIDE,MAGICWORD,FREE,TRAPPED +074 SIDESLIDE,GESTURE,FREE +075 SIDESLIDE,GESTURE,FREE,TRAPPED +084 SIDESLIDE,PRESSUREPLATE,FREE +085 SIDESLIDE,PRESSUREPLATE,FREE,TRAPPED +089 SIDESLIDE,PUSHBRICK,STUCK +090 SIDESLIDE,PUSHBRICK,STUCK,TRAPPED +092 SIDESLIDE,MAGICWORD,STUCK +093 SIDESLIDE,MAGICWORD,STUCK,TRAPPED +094 SIDESLIDE,GESTURE,STUCK +095 SIDESLIDE,GESTURE,STUCK,TRAPPED +099 SIDESLIDE,PRESSUREPLATE,STUCK +100 SIDESLIDE,PRESSUREPLATE,STUCK,TRAPPED +000 + +[concealeddoors] +ILLUSORYWALL +ILLUSORYWALL,TRAPPED +WOODEN,SIMPLE,FREE,BEHINDTAPESTRY +WOODEN,SIMPLE,FREE,TRAPPED,BEHINDTAPESTRY +WOODEN,SIMPLE,STUCK,BEHINDTAPESTRY +WOODEN,SIMPLE,STUCK,TRAPPED,BEHINDTAPESTRY +WOODEN,SIMPLE,LOCKED,BEHINDTAPESTRY +WOODEN,SIMPLE,LOCKED,TRAPPED,BEHINDTAPESTRY +WOODEN,GOOD,FREE,BEHINDTAPESTRY +WOODEN,GOOD,FREE,TRAPPED,BEHINDTAPESTRY +WOODEN,GOOD,STUCK,BEHINDTAPESTRY +WOODEN,GOOD,STUCK,TRAPPED,BEHINDTAPESTRY +WOODEN,GOOD,LOCKED,BEHINDTAPESTRY +WOODEN,GOOD,LOCKED,TRAPPED,BEHINDTAPESTRY +WOODEN,STRONG,FREE,BEHINDTAPESTRY +WOODEN,STRONG,FREE,TRAPPED,BEHINDTAPESTRY +WOODEN,STRONG,STUCK,BEHINDTAPESTRY +WOODEN,STRONG,STUCK,TRAPPED,BEHINDTAPESTRY +WOODEN,STRONG,LOCKED,BEHINDTAPESTRY +WOODEN,STRONG,LOCKED,TRAPPED,BEHINDTAPESTRY +STONE,FREE,BEHINDTAPESTRY +STONE,FREE,TRAPPED,BEHINDTAPESTRY +STONE,STUCK,BEHINDTAPESTRY +STONE,STUCK,TRAPPED,BEHINDTAPESTRY +STONE,LOCKED,BEHINDTAPESTRY +STONE,LOCKED,TRAPPED,BEHINDTAPESTRY +IRON,FREE,BEHINDTAPESTRY +IRON,FREE,TRAPPED,BEHINDTAPESTRY +IRON,STUCK,BEHINDTAPESTRY +IRON,LOCKED,BEHINDTAPESTRY +IRON,LOCKED,TRAPPED,BEHINDTAPESTRY +WOODEN,SIMPLE,FREE,BEHINDRUBBISH +WOODEN,SIMPLE,FREE,TRAPPED,BEHINDRUBBISH +WOODEN,SIMPLE,STUCK,BEHINDRUBBISH +WOODEN,SIMPLE,STUCK,TRAPPED,BEHINDRUBBISH +WOODEN,SIMPLE,LOCKED,BEHINDRUBBISH +WOODEN,SIMPLE,LOCKED,TRAPPED,BEHINDRUBBISH +WOODEN,GOOD,FREE,BEHINDRUBBISH +WOODEN,GOOD,FREE,TRAPPED,BEHINDRUBBISH +WOODEN,GOOD,STUCK,BEHINDRUBBISH +WOODEN,GOOD,STUCK,TRAPPED,BEHINDRUBBISH +WOODEN,GOOD,LOCKED,BEHINDRUBBISH +WOODEN,GOOD,LOCKED,TRAPPED,BEHINDRUBBISH +WOODEN,STRONG,FREE,BEHINDRUBBISH +WOODEN,STRONG,FREE,TRAPPED,BEHINDRUBBISH +WOODEN,STRONG,STUCK,BEHINDRUBBISH +WOODEN,STRONG,STUCK,TRAPPED,BEHINDRUBBISH +WOODEN,STRONG,LOCKED,BEHINDRUBBISH +WOODEN,STRONG,LOCKED,TRAPPED,BEHINDRUBBISH +STONE,FREE,BEHINDRUBBISH +STONE,FREE,TRAPPED,BEHINDRUBBISH +STONE,STUCK,BEHINDRUBBISH +STONE,STUCK,TRAPPED,BEHINDRUBBISH +STONE,LOCKED,BEHINDRUBBISH +STONE,LOCKED,TRAPPED,BEHINDRUBBISH +IRON,FREE,BEHINDRUBBISH +IRON,FREE,TRAPPED,BEHINDRUBBISH +IRON,STUCK,BEHINDRUBBISH +IRON,LOCKED,BEHINDRUBBISH +IRON,LOCKED,TRAPPED,BEHINDRUBBISH +FALSEWALL,FREE +FALSEWALL,FREE,TRAPPED +FALSEWALL,STUCK +FALSEWALL,STUCK,TRAPPED +FALSEWALL,LOCKED +FALSEWALL,LOCKED,TRAPPED diff --git a/doc/dungeon_design.html b/doc/dungeon_design.html new file mode 100644 index 0000000..83f4573 --- /dev/null +++ b/doc/dungeon_design.html @@ -0,0 +1,139 @@ + + + + Random Dungeon Design: The Secret Workings of Jamis Buck's Dungeon Generator + + + + +

Random Dungeon Design

+

The Secret Workings
of
Jamis Buck's Dungeon Generator

+
+ +

So you've tried my Dungeon Generator once or twice, and it's got you thinking. Perhaps you're a programmer and would like to incorporate similar features in a program of your own. Or maybe you're not a programmer, but would be interested in an overview of how this program works.

+ +

Either way, I've been asked how this random dungeon generator works many, many times, and I finally decided that, to save myself time, I'd just put up the description on a web page.

+ +

If you find this explanation useful, please let me know. Likewise, if you feel that I was too technical, or not techinical enough, or too ambiguous, let me know that, too, and I can try and improve it.

+ +

Please send all comments, questions, suggestions, and flames to: jamis at jamisbuck dot org.

+ +
+ + + +
I. A Dungeon is a Maze
+ +

First of all, it is helpful to think of any dungeon as simply a maze—a collection of corridors that turn every which way. The first part of generating any dungeon, then, is to create a random maze.

+ +

Now, there are lots of different ways to generate mazes (for some idea of how many different types of mazes and algorithms there are, check out the Maze Algorithms page at Think Labyrinth). For the dungeon generator, I just picked a straightforward algorithm that I'm pretty familiar with—it's a variation on the "Hunt-and-Kill" algorithm. The algorithm creates a 2D, normal, orthoganol, perfect maze, which simply means that the maze is rectangular, with all passages intersecting at right angles, and that there are no loops or inaccessible areas in the maze.

+ +

Here's how the algorithm I picked works. Feel free to substitute this one with any other algorithm.

+ +
    +
  1. Start with a rectangular grid, x units wide and y units tall. Mark each cell in the grid unvisited.
  2. +
  3. Pick a random cell in the grid and mark it visited. This is the current cell.
  4. +
  5. From the current cell, pick a random direction (north, south, east, or west). If (1) there is no cell adjacent to the current cell in that direction, or (2) if the adjacent cell in that direction has been visited, then that direction is invalid, and you must pick a different random direction. If all directions are invalid, pick a different random visited cell in the grid and start this step over again.
  6. +
  7. Let's call the cell in the chosen direction C. Create a corridor between the current cell and C, and then make C the current cell. Mark C visited.
  8. +
  9. Repeat steps 3 and 4 until all cells in the grid have been visited.
  10. +
+ +

Once that process finishes, you'll have your maze! There are a few variations you can do to make the maze more interesting; for example, my dungeon generator has a parameter called "randomness". This is a percentage value (0–100) that determines how often the direction of a corridor changes. If the value of randomness is 0, the corridors go straight until they run into a wall or another corridor—you wind up with a maze with lots of long, straight halls. If the randomness is 100, you get the algorithm given above—corridors that twist and turn unpredictably from cell to cell.

+ + +
II. Mazes vs. Dungeons
+ +

It is important to note that the algorithm given above results in no loops in the maze. It is also important to note that the algorithm results in a dense maze—that is, every cell contains a corridor.

+ +

This "pure" maze is probably not what you had in mind when you asked for a dungeon. For example, sometimes a dungeon passage intersects with another passage, or with itself, forming a loop. Also, an underground dungeon may cover a lot of territory, but not fill every square meter of rock—it is probably sparse, as opposed to dense.

+ +

There are two steps I used to convert the maze into something more like a dungeon (though still lacking rooms).

+ +

The first step involves a parameter I called sparseness. It is an integer value; you may want to experiment with it to arrive at a value (or set of values) that work best for you. It is used as follows:

+ +
    +
  1. Look at every cell in the maze grid. If the given cell contains a corridor that exits the cell in only one direction (in otherwords, if the cell is the end of a dead-end hallway), "erase" that cell by removing the corridor.
  2. +
  3. Repeat step #1 sparseness times (ie, if sparseness is 5, repeat step #1 five times).
  4. +
+ +

After sparsifying the maze, you should wind up with large "blank" gaps, where no passages go. The maze, however, is still perfect, meaning that it has no loops, and that no corridor is inaccessible from any other corridor.

+ +

The next step is to remove dead-ends by adding loops to the maze. The "deadends removed" parameter of my generator is a percentage value that represents the chance a given dead-end in the maze has of being removed. It is used as follows:

+ +
    +
  1. Look at every cell in the maze grid. If the given cell is a dead-end cell (meaning that a corridor enters but does not exit the cell), it is a candidate for "dead-end removal."
  2. +
  3. Roll d% (ie, pick a number between 1 and 100, inclusive). If the result is less than or equal to the "deadends removed" parameter, this deadend should be removed. Otherwise, proceed to the next candidate cell.
  4. +
  5. Remove the dead-end by performing step #3 of the maze generation algorithm, above, except that a cell is not considered invalid if it has been visited. Stop when you intersect an existing corridor.
  6. +
+ +

So, now you have something looking more like a dungeon. All it lacks, now, are rooms…

+ + +
III. Room Generation and Placement
+ +

This was perhaps the trickiest step. Looking at my generator, you'll see three parameters: "room count" (Rn), "room width", (Rw), and "room height" (Rh).

+ +

Generating rooms is actually easy: Rw is just a random number between the minimum and maximum widths. Rh is generated similarly.

+ +

Placing the rooms was trickier. The idea is to find a place in the maze where the given room overlaps a minimum of corridors and other rooms, but where the room touches a corridor in at least on place. To this end, I implemented a weighting system.

+ +

The program tries to put the room at every possible place in the dungeon. The algorithm works as follows:

+ +
    +
  1. Set the "best" score to infinity (or some arbitrarily huge number).
  2. +
  3. Generate a room such that Wmin <= Rw <= Wmax and Hmin <= Rh <= Hmax.
  4. +
  5. For each cell C in the dungeon, do the following: +
      +
    1. Put the upper-left corner of the room at C. Set the "current" score to 0.
    2. +
    3. For each cell of the room that is adjacent to a corridor, add 1 to the current score.
    4. +
    5. For each cell of the room that overlaps a corridor, add 3 to the current score.
    6. +
    7. For each cell of the room that overlaps a room, add 100 to the current score.
    8. +
    9. If the current score is less than the best score, set the best score to the current score and note C as the best position seen yet.
    10. +
    +
  6. +
  7. Place the room at the best position (where the best score was found).
  8. +
  9. For every place where the room is adjacent to a corridor or a room, add a door. (If you don't want doors everywhere, add another parameter that determines when a door should be placed, and when an empty doorway [ie, archway, etc.] should be placed).
  10. +
  11. Repeat steps 2 through 6 until all rooms have been placed.
  12. +
+ + + +
IV. Populating the Dungeon
+ +

I won't go into any great detail here, since I took the algorithms for this part straight from the Dungeon Master's Guide. The idea is simply to put something in each room of the dungeon: hidden treasure, a monster, some description, etc. At this stage, you also determine whether any given door is secret or concealed, and also what the door's properties are (wooden, locked, trapped, etc, etc.). Random tables work quite well to determine all of this.

+ + + +
V. Finis
+ +

And that, as they say, is the proverbial that. All that remains is to display the dungeon, and that has nothing to do with dungeon generation algorithms. :)

+ +

Feel free to download the source code for my dungeon generator—that's where you'll find the real technical explanation. The sources are a bastardized mix of C and C++, but fairly readable for all of that. The "jbmaze.cpp" file contains the maze generation algorithms, and the "jbdungeon.cpp" file contains the dungeon generation stuff. The "jbdungeondata.cpp" file populates the dungeon.

+ +

The sources can be obtained on GitHub: http://github.com/jamis/dnd-dungeon. (Note, though, that building the thing will require tinkering with Makefiles, and rationing your patience.)

+ +

Enjoy!

+ +
+ +

This document last updated on 10 September 2008 by Jamis Buck.

+ + diff --git a/include/jbdungeon.h b/include/jbdungeon.h new file mode 100644 index 0000000..d833d7e --- /dev/null +++ b/include/jbdungeon.h @@ -0,0 +1,332 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeon + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * JBDungeon represents a "complete" dungeon -- map, monsters, treasures, + * rooms, walls, and everything. To accomplish this, this file also + * includes the auxiliary objects used by JBDungeon: + * + * - JBDungeonOptions + * represents the set of options that will determine the configuration + * of the maze. + * - JBDungeonRoom + * represents a single room (and it's contents) in the dungeon. + * - JBDungeonWall + * represents a single wall (and it's features) in the dungeon. + * - JBDungeonDatum + * an abstract object that is used by both JBDungeonRoom and + * JBDungeonWall to generalize the contents and features of these + * objects. See JBDungeonData for more information. + * + * ---------------------------------------------------------------------- */ + +#ifndef __JBDUNGEON_H__ +#define __JBDUNGEON_H__ + + +#include "jbmaze.h" +#include "jbmazemask.h" + +#define REROLL_ONCE ( 0x10000000 ) +#define REROLL_ANY ( 0x20000000 ) + + +/* forward declarations */ + +class JBDungeonOptions; +class JBDungeonRoom; +class JBDungeonWall; +class JBDungeon; +class JBDungeonDatum; + + +/* --------------------------------------------------------------------- * + * JBDungeonDatum + * + * An abstract class used to encapsulate data associated with the various + * attributes of a dungeon room or wall. + * + * See JBDungeonData for more information on how this object is used. + * --------------------------------------------------------------------- */ +class JBDungeonDatum { + public: + + JBDungeonDatum() {} + virtual ~JBDungeonDatum() {} + + virtual void getDatumDescription( char* desc ) = 0; +}; + + +/* --------------------------------------------------------------------- * + * JBDungeonOptions + * + * Contains the various parameters, settings, and attributes of the + * dungeon to be created. This object is used solely as a parameter to + * the JBDungeon constructor. + * --------------------------------------------------------------------- */ +class JBDungeonOptions { + public: + JBDungeonOptions(); + ~JBDungeonOptions(); + + JBMazePt size; /* dimensions of the dungeon, in the absense of a mask */ + JBMazePt start; /* the starting point of the dungeon (for mazes) */ + JBMazePt end; /* the ending point of the dungeon (for mazes) */ + + JBMazeMask* mask; /* the mask to use on the maze and dungeon (or NULL if no mask is desired) */ + + int seed; /* the random seed to use to generate the dungeon */ + + int randomness; /* (0-100) how random the dungeon's passages are */ + int sparseness; /* (0+) how sparse the dungeon is */ + int clearDeadends; /* (0-100) what pct of deadends to remove */ + + int minRoomCount; /* minimum number of rooms to place */ + int maxRoomCount; /* maximum number of rooms to place */ + + int minRoomX; /* minimum x-dimension of a room */ + int maxRoomX; /* maximum x-dimension of a room */ + int minRoomY; /* minimum y-dimension of a room */ + int maxRoomY; /* maximum y-dimension of a room */ + + int secretDoors; /* percentage of doors to make "secret" doors */ + int concealedDoors; /* percentage of doors to make "concealed" doors */ +}; + + +/* --------------------------------------------------------------------- * + * JBDungeonRoom + * + * One element in a linked list of all the rooms in a dungeon. + * --------------------------------------------------------------------- */ +class JBDungeonRoom { + public: + + JBDungeonRoom(); + JBDungeonRoom( JBDungeonRoom* nextRoom ); + ~JBDungeonRoom(); + + JBMazePt topLeft; /* top-left coordinate of the room */ + JBMazePt size; /* x/y dimensions of the room */ + + JBDungeonRoom* next; /* the next room in the sequence */ + + JBDungeonWall** walls; /* an array of the walls referenced by the room */ + int wallCount; /* the number of walls contained in the above array */ + + JBDungeonDatum* data; /* description of the room */ +}; + + +/* --------------------------------------------------------------------- * + * JBDungeonWall + * + * One element in a linked list of all the explicit walls in a dungeon. + * --------------------------------------------------------------------- */ +class JBDungeonWall { + public: + + static const int c_NONE; /* no wall */ + static const int c_WALL; /* wall */ + static const int c_DOOR; /* wall is actually a door */ + static const int c_SECRETDOOR; /* wall is actually a secret door */ + static const int c_CONCEALEDDOOR; /* wall is actually a concealed door */ + + public: + + JBDungeonWall( const JBMazePt& p1, const JBMazePt& p2, int wallType = c_NONE ); + ~JBDungeonWall(); + + JBMazePt pt1; /* coordinate of point on one side of the wall */ + JBMazePt pt2; /* coordinate of point on other side of the wall */ + int type; /* one of the c__XXX constants (above) */ + + JBDungeonDatum* data; /* an object describing the wall's attributes */ + + JBDungeonWall* next; /* the next wall in the sequence */ +}; + + +/* --------------------------------------------------------------------- * + * JBDungeon + * + * The dungeon object. + * --------------------------------------------------------------------- */ +class JBDungeon { + public: + + static const int c_WALL; /* point is in a wall */ + static const int c_PASSAGE; /* point is in a passage */ + static const int c_ROOM; /* point is in a room */ + + public: + + /* ----------------------------------------------------------------- * + * JBDungeon( JBDungeonOptions& options ) + * + * Construct a new dungeon with the given options. + * ----------------------------------------------------------------- */ + JBDungeon( JBDungeonOptions& options ); + + /* ----------------------------------------------------------------- * + * ~JBDungeon() + * + * Destroy and deallocate the dungeon object and all it's referenced + * resources. + * ----------------------------------------------------------------- */ + ~JBDungeon(); + + /* ----------------------------------------------------------------- * + * int getX() + * + * Retrieve the x-dimension of the dungeon + * ----------------------------------------------------------------- */ + int getX() { return m_x; } + + /* ----------------------------------------------------------------- * + * int getY() + * + * Retrieve the y-dimension of the dungeon + * ----------------------------------------------------------------- */ + int getY() { return m_y; } + + /* ----------------------------------------------------------------- * + * int getZ() + * + * Retrieve the z-dimension of the dungeon + * ----------------------------------------------------------------- */ + int getZ() { return m_z; } + + /* ----------------------------------------------------------------- * + * int getDungeonAt( int x, int y, int z ) + * + * Retrieves the rough character of the dungeon at the given point + * (one of the JBDungeon::c_XXXX constants, above). + * ----------------------------------------------------------------- */ + int getDungeonAt( int x, int y, int z ); + + /* ----------------------------------------------------------------- * + * int getSolutionLength() + * + * Retrieves the number of steps in the solution of the maze. + * ----------------------------------------------------------------- */ + int getSolutionLength() { return m_solutionLength; } + + /* ----------------------------------------------------------------- * + * const JBMazePt& getSolutionStep( int i ) + * + * Retrieves the solution point at the given index. + * ----------------------------------------------------------------- */ + const JBMazePt& getSolutionStep( int i ) { return m_solution[ i ]; } + + /* ----------------------------------------------------------------- * + * int getWallBetween( const JBMazePt& p1, const JBMazePt& p2 ) + * + * Returns the wall type that exists between the two given points. + * The points given must be vertically or horizontally adjacent. + * ----------------------------------------------------------------- */ + int getWallBetween( const JBMazePt& p1, const JBMazePt& p2 ); + + /* ----------------------------------------------------------------- * + * int getRoomCount() + * + * Returns the number of rooms in the dungeon. + * ----------------------------------------------------------------- */ + int getRoomCount(); + + /* ----------------------------------------------------------------- * + * JBDungeonRoom* getRoom( int idx ) + * + * Returns the room at the given index. + * ----------------------------------------------------------------- */ + JBDungeonRoom* getRoom( int idx ); + + /* ----------------------------------------------------------------- * + * void setDataPath( const char* path ) + * + * Sets the data path that the dungeon will use. + * ----------------------------------------------------------------- */ + void setDataPath( const char* path ); + + /* ----------------------------------------------------------------- * + * char* getDataPath() + * + * Retrieves the data path that the dungeon will use. + * ----------------------------------------------------------------- */ + char* getDataPath() { return m_dataPath; } + + private: + + /* ----------------------------------------------------------------- * + * void m_generate( JBDungeonOptions& options ) + * + * Generates the dungeon with the given options. + * ----------------------------------------------------------------- */ + void m_generate( JBDungeonOptions& options ); + + /* ----------------------------------------------------------------- * + * void m_computeRooms( JBDungeonOptions& options ) + * + * Computes the rooms in the dungeon using the given options. + * ----------------------------------------------------------------- */ + void m_computeRooms( JBDungeonOptions& options ); + + /* ----------------------------------------------------------------- * + * void m_computeWalls( JBDungeonOptions& options ) + * + * Computes the walls in the dungeon using the given options. + * ----------------------------------------------------------------- */ + void m_computeWalls( JBDungeonOptions& options ); + + /* ----------------------------------------------------------------- * + * int m_findOptimalRoomPlacement( int& rx, int& ry, int z, int& cx, int& cy ) + * + * Finds the optimal placement for a room of cx/cy dimenions, at depth + * z in the dungeon. The resulting x/y coordinates are returned in cx + * and cy. NOTE: the rx and ry parameters may change as well, if there + * is not room for a room of the requested size. + * ----------------------------------------------------------------- */ + int m_findOptimalRoomPlacement( int& rx, int& ry, int z, int& cx, int& cy ); + + /* ----------------------------------------------------------------- * + * void m_addRoom( int cx, int cy, int z, int rx, int ry ) + * + * Adds a room of the given dimensions at the given point in the + * dungeon. + * ----------------------------------------------------------------- */ + void m_addRoom( int cx, int cy, int z, int rx, int ry ); + + /* ----------------------------------------------------------------- * + * void m_addWall( const JBMazePt& p1, const JBMazePt& p2, int type ) + * + * Adds a wall of the given type between the two indicated horizontally + * or vertically adjacent points. + * ----------------------------------------------------------------- */ + void m_addWall( const JBMazePt& p1, const JBMazePt& p2, int type ); + + private: + + int*** m_dungeon; /* the three dimensional array of dungeon points */ + + JBMazePt* m_solution; /* the list of points in the solution of the maze */ + int m_solutionLength; /* the number of steps in the solution */ + + int m_x; /* the x-dimension of the dungeon */ + int m_y; /* the y-dimension of the dungeon */ + int m_z; /* the z-dimension of the dungeon */ + + JBDungeonRoom* m_rooms; /* the list of rooms in the dungeon */ + JBDungeonWall* m_walls; /* the list of walls in the dungeon */ + + JBMazeMask* m_mask; /* the mask to use for creating the dungeon */ + + char* m_dataPath; /* the path that the generator looks in to find data */ +}; + +#endif /* __JBDUNGEON_H__ */ diff --git a/include/jbdungeondata.h b/include/jbdungeondata.h new file mode 100644 index 0000000..e2ee603 --- /dev/null +++ b/include/jbdungeondata.h @@ -0,0 +1,165 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonDescription + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * The JBDungeonDescription object is auxiliary to the JBDungeon object. It + * encapsulates the logic that populates the dungeon and it's rooms. + * + * This file also defines the JBDungeonWallDatum and JBDungeonRoomDatum + * classes, which derive from JBDungeonDatum. They describe, respectively, + * the walls and rooms of the dungeon. + * ---------------------------------------------------------------------- */ + +#ifndef __JBDUNGEONDATA_H__ +#define __JBDUNGEONDATA_H__ + +#include +#include "jbdungeon.h" +#include "treasureEngine.h" + +/* door types */ + +#define dtWOODEN ( 0x00000001 ) +#define dtSTONE ( 0x00000002 ) +#define dtIRON ( 0x00000004 ) +#define dtSIDESLIDE ( 0x00000008 ) +#define dtDOWNSLIDE ( 0x00000010 ) +#define dtUPSLIDE ( 0x00000020 ) +#define dtMAGIC ( 0x00000040 ) +#define dtSIMPLE ( 0x00000080 ) +#define dtGOOD ( 0x00000100 ) +#define dtSTRONG ( 0x00000200 ) + +#define dtFREE ( 0x00000400 ) +#define dtSTUCK ( 0x00000800 ) +#define dtLOCKED ( 0x00001000 ) +#define dtTRAPPED ( 0x00002000 ) + +/* secret door types */ + +#define sdtROTATINGWALL ( 0x00004000 ) +#define sdtPASSWALL ( 0x00008000 ) + +#define sdtPUSHBRICK ( 0x00010000 ) +#define sdtMAGICWORD ( 0x00020000 ) +#define sdtGESTURE ( 0x00040000 ) +#define sdtPRESSUREPLATE ( 0x00080000 ) + +/* concealed door types */ + +#define cdtILLUSORYWALL ( 0x00100000 ) +#define cdtBEHINDTAPESTRY ( 0x00200000 ) +#define cdtBEHINDRUBBISH ( 0x00400000 ) +#define cdtFALSEWALL ( 0x00800000 ) + +#define DUNGEON_DATA_DELIM "|" + +class JBDungeonWallDatum : public JBDungeonDatum { + public: + + JBDungeonWallDatum( long wtype, char* wtrap, char* wdescriptor ) { + type = wtype; + + trap = 0; + if( wtrap != 0 ) { + trap = new char[ strlen( wtrap ) + 1 ]; + strcpy( trap, wtrap ); + } + + descriptor = 0; + if( wdescriptor != 0 ) { + descriptor = new char[ strlen( wdescriptor ) + 1 ]; + strcpy( descriptor, wdescriptor ); + } + } + + ~JBDungeonWallDatum() { delete[] descriptor; delete[] trap; } + + virtual void getDatumDescription( char* desc ); + + long type; /* wall type (see dt---, sdt---, and cdt--- constants, above */ + char* trap; /* textual description of trap, or NULL if there is not trap */ + char* descriptor; /* descriptor (ie, "secret", "concealed", etc) */ +}; + + +class JBDungeonRoomDatum : public JBDungeonDatum { + public: + + JBDungeonRoomDatum() { + monsters = features = 0; + monsterCount = featureCount = 0; + trap = 0; + dataPath = ""; + memset( &treasure, 0, sizeof( treasure ) ); + memset( &monsterTreasure, 0, sizeof( monsterTreasure ) ); + } + + ~JBDungeonRoomDatum(); + + virtual void getDatumDescription( char* desc ); + + void addMonster( char* desc ); + void addFeature( char* desc ); + + char** monsters; /* array of monsters in room */ + int monsterCount; /* number of monster entries in room */ + + TREASUREOPTS monsterTreasure; /* the treasure that the monster has (if any) */ + + char** features; /* array of features in room */ + int featureCount; /* number of feature entries in room */ + + TREASUREOPTS treasure; /* hidden treasure in room (if any) */ + char* trap; /* description of trap in room (if any) */ + + int dungeonLevel; /* the dungeon level of the room */ + + char* dataPath; /* the file path that the generator looks in for data */ +}; + + +class JBDungeonDescription { + public: + + /* ------------------------------------------------------------------ * + * JBDungeonDescription( JBDungeon* dungeon, int level ) + * + * Describe the indicated dungeon as if it were at the indicated + * level. + * ------------------------------------------------------------------ */ + JBDungeonDescription( JBDungeon* dungeon, int level ); + + /* ------------------------------------------------------------------ * + * ~JBDungeonDescription() + * + * Deallocates and destroys the JBDungeonDescription object. + * ------------------------------------------------------------------ */ + ~JBDungeonDescription(); + + private: + + /* ------------------------------------------------------------------ * + * void m_describeRooms( JBDungeon* dungeon, int level ) + * + * Describes all the rooms in the dungeon. + * ------------------------------------------------------------------ */ + void m_describeRooms( JBDungeon* dungeon, int level ); + + /* ------------------------------------------------------------------ * + * void m_describeRoom( JBDungeon* dungeon, JBDungeonRoom* room, int level ) + * + * Describes the given room as if it were at the given dungeon level. + * This includes describing the walls in the room, if they have not + * already been described. + * ------------------------------------------------------------------ */ + void m_describeRoom( JBDungeon* dungeon, JBDungeonRoom* room, int level ); + +}; + +#endif /* __JBDUNGEONDATA_H__ */ diff --git a/include/jbdungeonpainter.h b/include/jbdungeonpainter.h new file mode 100644 index 0000000..16c118e --- /dev/null +++ b/include/jbdungeonpainter.h @@ -0,0 +1,174 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonPainter + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * JBDungeonPainter is an abstract class to standardizes how dungeons are + * drawn. To implement a dungeon painter, you need merely implement the + * following primitive draw methods of JBDungeonPainter: + * + * - m_rectangle + * - m_line + * - m_string + * - m_char + * - m_charUp + * - m_selectFontToFit + * - m_allocateColor + * - m_getFontWidth + * - m_getFontHeight + * + * See JBDungeonPainterGD for an example of an implementation of + * JBDungeonPainter. + * + * ---------------------------------------------------------------------- */ + +#ifndef __JBDUNGEONPAINTER_H__ +#define __JBDUNGEONPAINTER_H__ + +#include "jbdungeon.h" + +class JBDungeonPainter { + public: + + JBDungeonPainter( JBDungeon* dungeon, int gridSize, int border ) { + m_dungeon = dungeon; + m_gridSize = gridSize; + m_border = border; + } + + virtual ~JBDungeonPainter() { + } + + /* ------------------------------------------------------------------ * + * void paint() + * + * Paints the dungeon. How this is done depends on how the primitives + * have been implemented. + * ------------------------------------------------------------------ */ + void paint(); + + /* ------------------------------------------------------------------ * + * int getCanvasWidth() + * + * Returns the width (in pixels) of the canvas. + * ------------------------------------------------------------------ */ + int getCanvasWidth(); + + /* ------------------------------------------------------------------ * + * int getCanvasHeight() + * + * Returns the height (in pixels) of the canvas. + * ------------------------------------------------------------------ */ + int getCanvasHeight(); + + /* ------------------------------------------------------------------ * + * JBDungeon* getDungeon() + * + * Returns the dungeon this painter is painting. + * ------------------------------------------------------------------ */ + JBDungeon* getDungeon() { return m_dungeon; } + + protected: + + /* ------------------------------------------------------------------ * + * void m_centerNumberAt( int x, int y, int num, int maxWidth, long color ) + * + * Centers the given number around the given coordinates, and tries to + * ensure that the resulting text is no longer than maxWidth. It is + * drawn in the specified color. + * ------------------------------------------------------------------ */ + void m_centerNumberAt( int x, int y, int num, int maxWidth, long color ); + + /* ------------------------------------------------------------------ * + * void m_drawDoor( int x, int y, int doorType, int horiz, long color ) + * + * Draws a door of the specified type, orientation and color at the + * indicated position. + * ------------------------------------------------------------------ */ + void m_drawDoor( int x, int y, int doorType, int horiz, long color ); + + /* ------------------------------------------------------------------ * + * virtual void m_rectangle( int x1, int y1, int x2, int y2, long color, bool filled = false ) + * + * Should draw a rectangle (filled if the 'filled' parameter is true, of + * the given color, where one corner is at x1,y1 and the opposite corner + * is at x2,y2. + * ------------------------------------------------------------------ */ + virtual void m_rectangle( int x1, int y1, int x2, int y2, long color, bool filled = false ) = 0; + + /* ------------------------------------------------------------------ * + * virtual void m_line( int x1, int y1, int x2, int y2, long color ) + * + * Should draw a line of the specified color between the points x1,y1 + * and x2,y2. + * ------------------------------------------------------------------ */ + virtual void m_line( int x1, int y1, int x2, int y2, long color ) = 0; + + /* ------------------------------------------------------------------ * + * virtual void m_string( int x, int y, char* text, long color, void* font ) + * + * Draws the given string in the given color with the given font at the + * given point (the lower-left coordinate of the text should begin at + * the given point). + * ------------------------------------------------------------------ */ + virtual void m_string( int x, int y, char* text, long color, void* font ) = 0; + + /* ------------------------------------------------------------------ * + * virtual void m_char( int x, int y, char c, long color, void* font ) + * + * As m_string, but should only draw the specified character. + * ------------------------------------------------------------------ */ + virtual void m_char( int x, int y, char c, long color, void* font ) = 0; + + /* ------------------------------------------------------------------ * + * virtual void m_charUp( int x, int y, char c, long color, void* font ) + * + * As m_char, but should draw the specified character turned 90 degrees. + * ------------------------------------------------------------------ */ + virtual void m_charUp( int x, int y, char c, long color, void* font ) = 0; + + /* ------------------------------------------------------------------ * + * virtual void* m_selectFontToFit( char* text, int width ) + * + * Return a font for which the indicated text is less than or equal to + * the requested width. If this is not possible, return the smallest + * font it can. + * ------------------------------------------------------------------ */ + virtual void* m_selectFontToFit( char* text, int width ) = 0; + + /* ------------------------------------------------------------------ * + * virtual long m_allocateColor( int red, int green, int blue ) + * + * Should ensures that the given color exists in the palette and return + * a number that will be displayed as a color with the requested amount + * of red, green, and blue. + * ------------------------------------------------------------------ */ + virtual long m_allocateColor( int red, int green, int blue ) = 0; + + /* ------------------------------------------------------------------ * + * virtual int getFontWidth( void* font ) + * + * Returns the average width of a single character of the given font. + * ------------------------------------------------------------------ */ + virtual int getFontWidth( void* font ) = 0; + + /* ------------------------------------------------------------------ * + * virtual int getFontHeight( void* font ) + * + * Returns the maximum height of the given font. + * ------------------------------------------------------------------ */ + virtual int getFontHeight( void* font ) = 0; + + protected: + + int m_gridSize; /* the size of a single square of the grid in pixels */ + int m_border; /* how many pixels are reserved for the border of the dungeon */ + + JBDungeon* m_dungeon; /* the dungeon object to draw */ +}; + +#endif /* __JBDUNGEONPAINTER_H__ */ diff --git a/include/jbdungeonpaintergd.h b/include/jbdungeonpaintergd.h new file mode 100644 index 0000000..fa10218 --- /dev/null +++ b/include/jbdungeonpaintergd.h @@ -0,0 +1,55 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonPainterGD + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * JBDungeonPainterGD implements the interface specified by JBDungeonPainter + * and uses the GD graphics library (from http://www.boutell.com/gd/) to + * implement the primitives. + * ---------------------------------------------------------------------- */ + +#ifndef __JBDUNGEONPAINTERGD_H__ +#define __JBDUNGEONPAINTERGD_H__ + +#include "jbdungeon.h" +#include "jbdungeonpainter.h" +#include "gd.h" + +class JBDungeonPainterGD : public JBDungeonPainter { + public: + + JBDungeonPainterGD( JBDungeon* dungeon, int gridSize, int border ); + + virtual ~JBDungeonPainterGD(); + + /* ------------------------------------------------------------------ * + * gdImagePtr getImage() + * + * Returns the image data created by the painter routines. + * ------------------------------------------------------------------ */ + gdImagePtr getImage() { return m_image; } + + protected: + + virtual void m_rectangle( int x1, int y1, int x2, int y2, long color, bool filled = false ); + virtual void m_line( int x1, int y1, int x2, int y2, long color ); + virtual void m_string( int x, int y, char* text, long color, void* font ); + virtual void m_char( int x, int y, char c, long color, void* font ); + virtual void m_charUp( int x, int y, char c, long color, void* font ); + + virtual void* m_selectFontToFit( char* text, int width ); + virtual long m_allocateColor( int red, int green, int blue ); + + virtual int getFontWidth( void* font ); + virtual int getFontHeight( void* font ); + + protected: + + gdImagePtr m_image; +}; + +#endif /* __JBDUNGEONPAINTERGD_H__ */ diff --git a/include/jbmaze.h b/include/jbmaze.h new file mode 100644 index 0000000..760c466 --- /dev/null +++ b/include/jbmaze.h @@ -0,0 +1,208 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBMaze + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * JBMaze implements a rather simple algorithm for generating random + * mazes. The algorithm is as follows: + * + * (1) initialize each element of an n-dimensional grid to 0. + * (2) pick a point at random from the grid. ensure that the point lies + * within the mask. + * (3) pick a random direction and move that way, as long as the new + * point is unvisited and within the mask. + * (4) perform (3) until there is no valid direction to move in. Then + * pick a new point that HAS been visited before, and perform (3) + * and (4) until all the points have been visited. + * + * This code currently only supports up to 3-dimensional mazes, but could + * be extended fairly easily to support mazes of arbitrary dimension + * (which would be almost the same as having n mazes with teleports + * between them). Could be a fun project . . . + * ---------------------------------------------------------------------- */ + +#ifndef __JBMAZE_H__ +#define __JBMAZE_H__ + +#include "jbmazemask.h" + +/* ---------------------------------------------------------------------- * + * JBMazePt + * + * An auxiliary class that represents a single three-dimensional point. + * ---------------------------------------------------------------------- */ +class JBMazePt { + public: + + JBMazePt() { } + JBMazePt( int sx, int sy, int sz ) { x = sx; y = sy; z = sz; } + + bool operator ==( const JBMazePt& p ) { + return ( ( x == p.x ) && ( y == p.y ) && ( z == p.z ) ); + } + + int x; + int y; + int z; +}; + + +class JBMaze { + public: + + static const int c_NORTH; + static const int c_SOUTH; + static const int c_WEST; + static const int c_EAST; + static const int c_UP; + static const int c_DOWN; + + public: + + /* ------------------------------------------------------------------ * + * JBMaze( ... ) + * Prepare a new maze with the given parameters. + * ------------------------------------------------------------------ */ + JBMaze( int x, int y, int z, + long seed = 0, + int randomness = 100, + int sx = 0, int sy = 0, int sz = 0, + int ex = -1, int ey = -1, int ez = -1 ); + + /* ------------------------------------------------------------------ * + * ~JBMaze() + * Destroy the maze and release it's resources. + * ------------------------------------------------------------------ */ + ~JBMaze(); + + /* ------------------------------------------------------------------ * + * Get the x-, y-, and z-dimensions of the maze. + * ------------------------------------------------------------------ */ + int getX() { return m_x; } + int getY() { return m_y; } + int getZ() { return m_z; } + + /* ------------------------------------------------------------------ * + * Get the start- and end-points of the maze. + * ------------------------------------------------------------------ */ + const JBMazePt& getStart() { return m_start; } + const JBMazePt& getEnd() { return m_end; } + + /* ------------------------------------------------------------------ * + * Get the random seed that generated the maze. + * ------------------------------------------------------------------ */ + long getSeed() { return m_seed; } + + /* ------------------------------------------------------------------ * + * Get the randomness of the maze (a value from 0-100 representing + * how often a passage will bend). + * ------------------------------------------------------------------ */ + int getRandomness() { return m_randomness; } + + /* ------------------------------------------------------------------ * + * Returns the exits at the given point in the maze. This will be + * a bitwise combination of the JBMaze::c_XXXX constants (above). + * ------------------------------------------------------------------ */ + int getExitsAt( int x, int y, int z ); + + /* ------------------------------------------------------------------ * + * Solve the maze, and return the solution as an array of points. This + * will fail and return NO solution if the maze has previously had any + * number of it's deadends removed (see clearDeadends(), below). + * ------------------------------------------------------------------ */ + void solve( JBMazePt** path, int* pathLen ); + + /* ------------------------------------------------------------------ * + * Sparsify the maze by the given amount. The amount represents the + * number of times to sparsify the maze. A smaller maze will sparsify + * faster than a larger maze, so the smaller the maze, the smaller the + * number should be to accomplish the same relative amount of + * "sparsification". Also, a maze will sparsify much better if + * clearDeadends() has not yet been called. + * ------------------------------------------------------------------ */ + void sparsify( int amount ); + + /* ------------------------------------------------------------------ * + * Clears the given percentage of deadends from the maze by causing + * the deadends to extend until they hit another passage. As this + * causes cycles in the maze, it can result in multiple possible solutions + * to the maze. Thus, solve() must be called BEFORE clearDeadends() + * is called. + * ------------------------------------------------------------------ */ + void clearDeadends( int percentage ); + + /* ------------------------------------------------------------------ * + * Generates the maze. This must be called BEFORE solve(), sparsify(), + * or clearDeadends(). + * ------------------------------------------------------------------ */ + void generate(); + + /* ------------------------------------------------------------------ * + * Sets the mask to be used when generating the maze. As such, it + * must be called BEFORE generate to have any effect. + * ------------------------------------------------------------------ */ + void setMask( JBMazeMask* mask ); + + /* ------------------------------------------------------------------ * + * Retrieves the mask that the maze is using to generate itself. + * ------------------------------------------------------------------ */ + JBMazeMask* getMask() { return m_mask; } + + private: + + /* ------------------------------------------------------------------ * + * Used internally to verify that a given point has been visited during + * "sparsification". + * ------------------------------------------------------------------ */ + static const int c_MARK; + + struct JBMAZE_SOLUTION { + int x; + int y; + int z; + int directions; + JBMAZE_SOLUTION* previous; + }; + + /* ------------------------------------------------------------------ * + * Used internally by solve() to keep track of the current solution + * state. + * ------------------------------------------------------------------ */ + JBMAZE_SOLUTION* m_push( int x, int y, int z, JBMAZE_SOLUTION* stack ); + JBMAZE_SOLUTION* m_pop( JBMAZE_SOLUTION* stack ); + + /* ------------------------------------------------------------------ * + * Used internally by sparsify() to clear all marks of visitation. + * ------------------------------------------------------------------ */ + void m_clearMarks(); + + /* ------------------------------------------------------------------ * + * Used internally for allocation and reallocation of the maze. + * ------------------------------------------------------------------ */ + void m_allocateMaze(); + void m_deallocateMaze(); + + private: + + int m_x; /* x-dimension */ + int m_y; /* y-dimension */ + int m_z; /* z-dimension */ + + JBMazePt m_start; /* starting point */ + JBMazePt m_end; /* ending point */ + + int*** m_maze; /* the maze itself (three-dimensional) */ + + int m_randomness; /* (0-100) how often the passages bend */ + long m_seed; /* the random seed value */ + + int m_deadendsClosed; /* (bool) has clearDeadends() been called? */ + + JBMazeMask* m_mask; /* the mask to use for generating the maze */ +}; + +#endif /* __JBMAZE_H__ */ diff --git a/include/jbmazemask.h b/include/jbmazemask.h new file mode 100644 index 0000000..7325dd3 --- /dev/null +++ b/include/jbmazemask.h @@ -0,0 +1,87 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBMazeMask + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * JBMazeMask represents a two-dimensional mask that may be applied to a + * maze object (see JBMaze). The mask then determines what areas of the + * map are valid for generating the maze. + * ---------------------------------------------------------------------- */ + +#ifndef __JBMAZEMASK_H__ +#define __JBMAZEMASK_H__ + +class JBMazeMask { + public: + + /* ------------------------------------------------------------------ * + * JBMazeMask( int width, int height ) + * + * Creates a new JBMazeMask object with the given width and height. + * The mask is initialized to be empty. + * ------------------------------------------------------------------ */ + JBMazeMask( int width, int height ); + + /* ------------------------------------------------------------------ * + * JBMazeMask( char* filename ) + * + * Creates a new JBMazeMask object from the data in the indicated + * file. The first line of the file must be the width and the height + * of the mask (comma delimited). Subsequent lines represent the rows + * in the mask, where each character in the line must be a '0' or a '1' + * and represents whether the mask at that point is 'on' (1) or 'off + * (0). + * ------------------------------------------------------------------ */ + JBMazeMask( char* filename ); + + /* ------------------------------------------------------------------ * + * JBMazeMask( JBMazeMask& master ) + * + * Copy constructor -- creates a new JBMazeMask that is an exact + * duplicate of the indicated mask object. + * ------------------------------------------------------------------ */ + JBMazeMask( JBMazeMask& master ); + + /* ------------------------------------------------------------------ * + * ~JBMazeMask() + * + * Destroys the mask object and deallocates any resources it used. + * ------------------------------------------------------------------ */ + virtual ~JBMazeMask(); + + /* ------------------------------------------------------------------ * + * int getWidth() + * + * Returns the width of the mask. + * ------------------------------------------------------------------ */ + int getWidth() { return m_width; } + + /* ------------------------------------------------------------------ * + * int getHeight() + * + * Returns the height of the mask. + * ------------------------------------------------------------------ */ + int getHeight() { return m_height; } + + /* ------------------------------------------------------------------ * + * int getMaskAt( int x, int y ) + * + * Returns the value of the mask at the indicated point. A 1 means + * that the mask is "valid" at that point (ie, the maze may be drawn + * there). A 0 means that the maze must consider this point a wall. + * ------------------------------------------------------------------ */ + int getMaskAt( int x, int y ) { return m_mask[ x ][ y ]; } + + private: + + int m_width; + int m_height; + + char** m_mask; +}; + +#endif /* __JBMAZEMASK_H__ */ diff --git a/include/treasureEngine.h b/include/treasureEngine.h new file mode 100644 index 0000000..420afff --- /dev/null +++ b/include/treasureEngine.h @@ -0,0 +1,198 @@ +#ifndef __TREASUREENGINE_H__ +#define __TREASUREENGINE_H__ + +/* ---------------------------------------------------------------------- * + * Constant definitions + * ---------------------------------------------------------------------- */ + +#define NONE ( 0 ) +#define CP ( 1 ) +#define SP ( 2 ) +#define GP ( 3 ) +#define PP ( 4 ) +#define GEMS ( 5 ) +#define ART ( 6 ) +#define MINOR ( 7 ) +#define MEDIUM ( 8 ) +#define MAJOR ( 9 ) +#define MUNDANE ( 10 ) +#define ARMOR ( 11 ) +#define WEAPONS ( 12 ) +#define POTIONS ( 13 ) +#define RINGS ( 14 ) +#define RODS ( 15 ) +#define SCROLLS ( 16 ) +#define STAFFS ( 17 ) +#define WANDS ( 18 ) +#define WONDROUS ( 19 ) +#define ENHANCED ( 20 ) +#define SPECIAL ( 21 ) +#define SPECIFIC ( 22 ) +#define COMMONMW ( 23 ) +#define UNCOMMNW ( 24 ) +#define COMMONRW ( 25 ) +#define MELEE ( 26 ) +#define RANGED ( 27 ) +#define BANE ( 28 ) +#define ARCANE ( 29 ) +#define DIVINE ( 30 ) + +/* maximum number of "levels" for random treasure generation */ + +#define LEVEL_COUNT ( 21 ) + +/* ---------------------------------------------------------------------- * + * Type definitions + * ---------------------------------------------------------------------- */ + +typedef struct __treasureitem__ TREASUREITEM; +typedef struct __treasureoptions__ TREASUREOPTS; + +struct __treasureitem__ { + char desc[1024]; + int value; /* this is the value of the item in cp */ + TREASUREITEM* next; +}; + +struct __treasureoptions__ { + TREASUREITEM* treasureList; + TREASUREITEM* treasureTail; + int forceIntelligent; +}; + +/* ---------------------------------------------------------------------- * + * Function definitions + * ---------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------------- * + * Adds the given description and value to the treasure list for the given + * options. + * ---------------------------------------------------------------------- */ +void addNewTreasure( TREASUREOPTS *opts, char* desc, int value ); + +/* ---------------------------------------------------------------------- * + * Cleans up (deallocates) the treasure list for the given options. + * ---------------------------------------------------------------------- */ +void cleanupTreasure( TREASUREOPTS *opts ); + +/* ---------------------------------------------------------------------- * + * Generates a random armor from the DMG lists -- this will be either + * armor, a shield, a "specific" armor, or a "specific" shield. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateArmor( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random potion from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generatePotion( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a complete random treasure from the DMG lists. + * "level" is an integer from 0 to 20 (inclusive), and represents the + * 0-based encounter level that the treasure is generated for. Level 20 + * is a special case and contains random magical and mundane treasures from + * the MUNDANE, MINOR, MEDIUM, and MAJOR lists. + * ---------------------------------------------------------------------- */ +void generateRandomTreasure( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a complete random treasure from the DMG lists. + * "level" is an integer from 0 to 20 (inclusive), and represents the + * 0-based encounter level that the treasure is generated for. Level 20 + * is a special case and contains random magical and mundane treasures from + * the MUNDANE, MINOR, MEDIUM, and MAJOR lists. + * ---------------------------------------------------------------------- */ +void generateRandomTreasureEx( TREASUREOPTS *opts, int level, float* mods ); + +/* ---------------------------------------------------------------------- * + * Generates a random ring from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateRing( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random rod from the DMG lists. + * "level" is one of MEDIUM or MAJOR. + * ---------------------------------------------------------------------- */ +void generateRod( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random scroll from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateScroll( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a single specific set of armor from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateSpecificArmor( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a single specific shield from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateSpecificShield( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a single specific weapon from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateSpecificWeapon( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a single staff from the DMG lists. + * "level" is one of MEDIUM or MAJOR. + * ---------------------------------------------------------------------- */ +void generateStaff( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random treasure for a given encounter level and column from + * the table. Column '0' is coins, column '1' is gems and art objects, + * and column '2' is mundane and magical items. + * "level" is from 0 to 20, inclusive, representing the 0-based encounter + * level that the treasure is being rolled for. + * "rollHigher" is a boolean flag indicating whether or not to roll on a + * higher treasure table on a d% roll of 96-100. (See DMG treasure lists). + * ---------------------------------------------------------------------- */ +int generateTreasure( TREASUREOPTS *opts, int level, int column, + int rollHigher, float mod ); + +/* ---------------------------------------------------------------------- * + * Generates a random wand from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateWand( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random weapon from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateWeapon( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random wondrous item from the DMG lists. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * ---------------------------------------------------------------------- */ +void generateWondrousItem( TREASUREOPTS *opts, int level ); + +/* ---------------------------------------------------------------------- * + * Generates a random spell from the DMG scroll lists. + * "type" is one of ARCANE or DIVINE. + * "level" is one of MINOR, MEDIUM, or MAJOR. + * "value" is a pointer to an integer that will contain the gold piece + * value for the returned spell. + * ---------------------------------------------------------------------- */ +char* randomSpell( int type, int level, int *value ); + +#ifdef __cplusplus +} +#endif + +#endif /* __TREASUREENGINE_H__ */ diff --git a/src/dungeoncgi.cpp b/src/dungeoncgi.cpp new file mode 100644 index 0000000..1c462eb --- /dev/null +++ b/src/dungeoncgi.cpp @@ -0,0 +1,571 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * Dungeon CGI Front-end + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * This CGI front end employs two other libraries that are freely available + * online: + * + * - qDecoder (available from http://www.qDecoder.org) is a CGI library + * for use by CGI applications. + * - gd (available from http://www.boutell.com/gd/) is a graphics library + * used for creating and modifying images. In the case of this program, + * gd is the tool that creates the dungeon maps in .PNG format. + * ---------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +#include "gameutil.h" +#include "jbdungeon.h" +#include "jbdungeondata.h" +#include "jbdungeonpaintergd.h" + +#include "gd.h" + +#include "gdfontt.h" +#include "gdfonts.h" +#include "gdfontmb.h" +#include "gdfontl.h" +#include "gdfontg.h" + +#include "writetem.h" +#include "qDecoder.h" + +#define TEMPATH "tem/" +#define WEBIMGPATH "/temp/img/" + +#ifdef WIN32 +#define CGINAME "dungeon.exe" +#else +#define CGINAME "dungeon.cgi" +#endif + +static char url[512]; +static time_t seedn; + +/* logs access to the CGI */ +void logAccess( char* url ) { +#ifdef USE_LOG + FILE *f; + char buffer[512]; + time_t t; + char *host; + + f = fopen( LOGLOCATION, "at" ); + if( f == NULL ) { + printf( "content-type: text/html\r\n\r\n" ); + printf( "could not open log file: %d (%s)\n", errno, strerror( errno ) ); + return; + } + + t = time( NULL ); + strftime( buffer, sizeof( buffer ), "[%d %b %Y %H:%M:%S]", localtime( &t ) ); + + host = getenv( "REMOTE_ADDR" ); + fprintf( f, "%s %s %s\n", buffer, host, url ); + fclose( f ); +#endif +} + + +int getCounter() { +#ifdef USE_COUNTER + FILE *f; + char buffer[32]; + int count; + + f = fopen( CTRLOCATION, "r+t" ); + if( f == NULL ) { + f = fopen( CTRLOCATION, "w+t" ); + if( f == NULL ) { + return 0; + } + } + + fgets( buffer, sizeof( buffer ), f ); + count = atoi( buffer ) + 1; + rewind( f ); + fprintf( f, "%d", count ); + fclose( f ); + + return count; +#else + return -1; +#endif +} + + +static void strrepl( char* buf, char* srch, char* repl ) { + char* p; + int slen; + int rlen; + + slen = strlen( srch ); + rlen = strlen( repl ); + + p = strstr( buf, srch ); + while( p != NULL ) { + memmove( p+rlen, p+slen, strlen( p + slen ) + 1 ); + strncpy( p, repl, rlen ); + p = strstr( p+rlen, srch ); + } +} + + +#define NORTH ( 1 ) +#define SOUTH ( 2 ) +#define EAST ( 3 ) +#define WEST ( 4 ) + +int getWallOrientation( JBDungeonRoom* room, JBDungeonWall* wall, int *ofs ) { + int minx; + int maxx; + int miny; + int maxy; + + minx = ( wall->pt1.x < wall->pt2.x ? wall->pt1.x : wall->pt2.x ); + maxx = ( wall->pt1.x > wall->pt2.x ? wall->pt1.x : wall->pt2.x ); + miny = ( wall->pt1.y < wall->pt2.y ? wall->pt1.y : wall->pt2.y ); + maxy = ( wall->pt1.y > wall->pt2.y ? wall->pt1.y : wall->pt2.y ); + + if( minx < room->topLeft.x ) { + *ofs = miny - room->topLeft.y + 1; + return WEST; + } + + if( maxx >= room->topLeft.x + room->size.x ) { + *ofs = miny - room->topLeft.y + 1; + return EAST; + } + + if( miny < room->topLeft.y ) { + *ofs = minx - room->topLeft.x + 1; + return NORTH; + } + + if( maxy >= room->topLeft.y + room->size.y ) { + *ofs = minx - room->topLeft.x + 1; + return SOUTH; + } + + *ofs = 0; + return 0; +} + + +int displayDungeonDescription( wtSTREAM_t* stream, wtTAG_t** tags, wtGENERIC_t data, char* other ){ + JBDungeon* dungeon; + int level; + int roomCount; + int i; + int j; + JBDungeonRoom* room; + char hugeBuffer[32 * 1024]; + int ofs; + char* p; + char* tmp; + int blockIsOpen; + + logAccess( url ); + + dungeon = (JBDungeon*)data; + level = qiValue( "level" ); + if( level < 1 ) { + level = 1; + } + + JBDungeonDescription( dungeon, qiValue( "level" ) ); + + wtPrint( stream, "

\n" ); + + roomCount = dungeon->getRoomCount(); + for( i = 0; i < roomCount; i++ ) { + room = dungeon->getRoom( i ); + + wtPrintf( stream, "Room #%d:\n

    \n", i+1 ); + + for( j = 0; j < room->wallCount; j++ ) { + if( room->walls[ j ]->type == JBDungeonWall::c_WALL ) { + continue; + } + + wtPrintf( stream, "
  • Door" ); + + switch( getWallOrientation( room, room->walls[ j ], &ofs ) ) { + case NORTH: + wtPrintf( stream, " (north, %d from west)", ofs ); + break; + case SOUTH: + wtPrintf( stream, " (south, %d from west)", ofs ); + break; + case EAST: + wtPrintf( stream, " (east, %d from north)", ofs ); + break; + case WEST: + wtPrintf( stream, " (west, %d from north)", ofs ); + break; + } + + wtPrint( stream, ": " ); + + if( room->walls[ j ]->data != 0 ) { + room->walls[ j ]->data->getDatumDescription( hugeBuffer ); + strrepl( hugeBuffer, "\n", "
    \n" ); + wtPrint( stream, hugeBuffer ); + } else if( room->walls[ j ]->type == JBDungeonWall::c_DOOR ) { + wtPrint( stream, "(normal)" ); + } else if( room->walls[ j ]->type == JBDungeonWall::c_SECRETDOOR ) { + wtPrint( stream, "(secret)" ); + } else if( room->walls[ j ]->type == JBDungeonWall::c_CONCEALEDDOOR ) { + wtPrint( stream, "(concealed)" ); + } + + wtPrint( stream, "\n" ); + } + + room->data->getDatumDescription( hugeBuffer ); + strrepl( hugeBuffer, "\n", "
    \n" ); + strrepl( hugeBuffer, "~B", "" ); + strrepl( hugeBuffer, "~b", "" ); + strrepl( hugeBuffer, "~I", "" ); + strrepl( hugeBuffer, "~i", "" ); + strrepl( hugeBuffer, "~n", "\n" ); + + tmp = hugeBuffer; + blockIsOpen = 0; + while( ( p = strtok( tmp, DUNGEON_DATA_DELIM ) ) != 0 ) { + tmp = 0; + + if( *p == '$' ) { + if( blockIsOpen ) { + wtPrintf( stream, "
" ); + } + blockIsOpen = 0; + } else if( *p == '!' ) { + p++; + wtPrintf( stream, "
  • %s\n", p ); + wtPrintf( stream, "
      \n" ); + blockIsOpen = 1; + } else { + wtPrintf( stream, "
    • %s\n", p ); + } + } + + if( blockIsOpen ) { + wtPrintf( stream, "
    \n" ); + } + + wtPrintf( stream, "\n" ); + } + + return 0; +} + + +JBDungeon* prepareDungeon() { + char* width; + char* roomcnt; + char* height; + char* minroomw; + char* maxroomw; + char* minroomh; + char* maxroomh; + char* sparse; + char* secret; + char* random; + char* concealed; + char* deadends; + char* resolution; + + float minMod = 0; + float maxMod = 0; + + JBDungeonOptions dungeonOpts; + JBDungeon* dungeon; + + int area; + int aveRoomArea; + int lw; + + width = qValue( "width" ); + roomcnt = qValue( "roomcnt" ); + height = qValue( "height" ); + minroomw = qValue( "minroomw" ); + maxroomw = qValue( "maxroomw" ); + minroomh = qValue( "minroomh" ); + maxroomh = qValue( "maxroomh" ); + sparse = qValue( "sparse" ); + secret = qValue( "secret" ); + random = qValue( "random" ); + concealed = qValue( "concealed" ); + deadends = qValue( "deadends" ); + resolution = qValue( "resolution" ); + + if( atoi(width) > 30 || atoi(height) > 30 ) { + printf( "Nice try! Attempting to bypass the limits set in the program can get you and me both " + "in trouble!\n" ); + printf( "

    \n" ); + printf( "In the past, large dungeons like the one you just tried to build put heavy loads on the " + "server and caused lots of problems. I have had to disable the larger dungeons for the " + "online version. If this frustrates you, then I encourage you to grab the downloadable version " + "-- you can build much larger dungeons with it!\n" ); + printf( "

    \n" ); + printf( "Thanks!\n" ); + printf( "

    \n" ); + printf( "Jamis Buck (jgb3@email.byu.edu)" ); + exit(0); + } + + + dungeonOpts.size.x = atoi( width ); + dungeonOpts.size.y = atoi( height ); + dungeonOpts.seed = seedn; + dungeonOpts.randomness = atoi( random ); + dungeonOpts.clearDeadends = atoi( deadends ); + +// dungeonOpts.mask = new JBMazeMask( "d:\\dev\\roger.txt" ); + + if( dungeonOpts.mask != 0 ) { + dungeonOpts.size.x = dungeonOpts.mask->getWidth(); + dungeonOpts.size.y = dungeonOpts.mask->getHeight(); + } + + area = dungeonOpts.size.x * dungeonOpts.size.y; + lw = (int)( ( dungeonOpts.size.x + dungeonOpts.size.y ) * 0.5 ); + + if( strcmp( sparse, "very" ) == 0 ) { + dungeonOpts.sparseness = lw; + } else if( strcmp( sparse, "quite" ) == 0 ) { + dungeonOpts.sparseness = (int)( lw * 0.75 ); + } else if( strcmp( sparse, "some" ) == 0 ) { + dungeonOpts.sparseness = (int)( lw * 0.5 ); + } else if( strcmp( sparse, "crowd" ) == 0 ) { + dungeonOpts.sparseness = (int)( lw * 0.25 ); + } else if( strcmp( sparse, "dense" ) == 0 ) { + dungeonOpts.sparseness = 0; + } + + dungeonOpts.minRoomX = atoi( minroomw ); + dungeonOpts.maxRoomX = atoi( maxroomw ); + dungeonOpts.minRoomY = atoi( minroomh ); + dungeonOpts.maxRoomY = atoi( maxroomh ); + + aveRoomArea = (int) + ( ( ( dungeonOpts.maxRoomX + dungeonOpts.minRoomX ) / 2.0 ) + + ( ( dungeonOpts.maxRoomY + dungeonOpts.minRoomY ) / 2.0 ) ); + + if( strcmp( roomcnt, "none" ) == 0 ) { + minMod = 0; + maxMod = 0; + } else if( strcmp( roomcnt, "few" ) == 0 ) { + minMod = 0.5; + maxMod = 1; + } else if( strcmp( roomcnt, "some" ) == 0 ) { + minMod = 1; + maxMod = 2; + } else if( strcmp( roomcnt, "many" ) == 0 ) { + minMod = 2; + maxMod = 4; + } else if( strcmp( roomcnt, "lots" ) == 0 ) { + minMod = 4; + maxMod = 8; + } + + if( aveRoomArea == 0 ) { + dungeonOpts.minRoomCount = dungeonOpts.maxRoomCount = 0; + } else { + dungeonOpts.minRoomCount = (int)( ( lw * 4.0 / aveRoomArea ) * minMod ); + dungeonOpts.maxRoomCount = (int)( ( lw * 4.0 / aveRoomArea ) * maxMod ); + } + + dungeonOpts.secretDoors = atoi( secret ); + dungeonOpts.concealedDoors = atoi( concealed ); + + dungeon = new JBDungeon( dungeonOpts ); + dungeon->setDataPath( TEMPATH ); + + return dungeon; +} + + +int displayDungeon( void ) { + wtTAG_t *tags[5]; + + char fname[1024]; + char buffer[20]; + + wtDELEGATE_t roomDesc; + + JBDungeon* dungeon; + + dungeon = prepareDungeon(); + + sprintf( fname, "/cgi-bin/%s&imageonly=1", url ); + + roomDesc.handler = displayDungeonDescription; + roomDesc.userData = dungeon; + + tags[0] = wtTagReplace( "MAPIMG", fname ); + tags[1] = wtTagDelegate( "ROOMDESC", &roomDesc ); + tags[2] = wtTagReplaceI( "SEED", seedn ); + + commify( buffer, getCounter() ); + tags[3] = wtTagReplace( "COUNTER", buffer ); + + tags[4] = 0; + + wtWriteTemplateToFile( stdout, TEMPATH "dungeon_desc.tem", tags ); + wtFreeTagList( tags ); + + delete dungeon; + + return 0; +} + + +void asciiOnly() { + JBDungeon* dungeon; + char** map; + int i; + int j; + + printf( "content-type: text/plain\r\n" ); + printf( "Pragma: no-cache\r\n" ); + printf( "Expires: Thu, 1 Jan 1970 00:00:01 GMT\r\n\r\n" ); + + dungeon = prepareDungeon(); + map = new char*[ dungeon->getX() ]; + for( i = 0; i < dungeon->getX(); i++ ) { + map[ i ] = new char[ dungeon->getY() ]; + for( j = 0; j < dungeon->getY(); j++ ) { + int cell = dungeon->getDungeonAt( i, j, 0 ); + + if( cell == JBDungeon::c_WALL ) { + map[ i ][ j ] = '#'; + } else if( cell == JBDungeon::c_PASSAGE || cell == JBDungeon::c_ROOM ) { + map[ i ][ j ] = '.'; + } + + if( cell == JBDungeon::c_ROOM ) { + JBMazePt p1( i, j, 0 ); + if( i > 0 ) { + JBMazePt p2( i-1, j, 0 ); + int wall = dungeon->getWallBetween( p1, p2 ); + if( wall != JBDungeonWall::c_NONE && wall != JBDungeonWall::c_WALL ) { + map[ i ][ j ] = 'w'; + } + } + if( i+1 < dungeon->getX() ) { + JBMazePt p2( i+1, j, 0 ); + int wall = dungeon->getWallBetween( p1, p2 ); + if( wall != JBDungeonWall::c_NONE && wall != JBDungeonWall::c_WALL ) { + map[ i ][ j ] = 'e'; + } + } + if( j > 0 ) { + JBMazePt p2( i, j-1, 0 ); + int wall = dungeon->getWallBetween( p1, p2 ); + if( wall != JBDungeonWall::c_NONE && wall != JBDungeonWall::c_WALL ) { + map[ i ][ j ] = 'n'; + } + } + if( j < dungeon->getY() ) { + JBMazePt p2( i, j+1, 0 ); + int wall = dungeon->getWallBetween( p1, p2 ); + if( wall != JBDungeonWall::c_NONE && wall != JBDungeonWall::c_WALL ) { + map[ i ][ j ] = 's'; + } + } + } + } + } + + for( i = 0; i < dungeon->getX(); i++ ) { + delete map[ i ]; + } + delete map; + + delete dungeon; +} + + +void imageOnly() { + gdImagePtr image; + JBDungeon* dungeon; + JBDungeonPainterGD* painter; + + printf( "content-type: image/png\r\n" ); + printf( "Pragma: no-cache\r\n" ); + printf( "Expires: Thu, 1 Jan 1970 00:00:01 GMT\r\n\r\n" ); + + dungeon = prepareDungeon(); + painter = new JBDungeonPainterGD( dungeon, qiValue( "resolution" ), 5 ); + + painter->paint(); + image = painter->getImage(); + +#ifdef WIN32 + _setmode( _fileno( stdout ), _O_BINARY ); +#endif + + gdImagePng( image, stdout ); + + delete painter; +} + + +int main( int argc, char* argv[] ) { + time_t t; + wtTAG_t *tags[3]; + char buffer[12]; + +// putenv( "REQUEST_METHOD=GET" ); +// putenv( "QUERY_STRING=width=16&roomcnt=some&height=16&minroomw=2&maxroomw=5&sparse=some&minroomh=2&maxroomh=5&random=40&secret=5&deadends=100&concealed=10&level=1&resolution=20&seed=973963837&imageonly=1" ); + + qDecoder(); + + t = time( NULL ); + seedn = qiValue( "seed" ); + if( seedn < 1 ) { + seedn = t; + sprintf( url, "%s?%s%d", CGINAME, getenv( "QUERY_STRING" ), (int)seedn ); + } else { + sprintf( url, "%s?%s", CGINAME, getenv( "QUERY_STRING" ) ); + } + + if( qiValue( "imageonly" ) ) { + imageOnly(); + qFree(); + return 0; + } + + printf( "content-type: text/html\r\n" ); + printf( "Pragma: no-cache\r\n" ); + printf( "Expires: Thu, 1 Jan 1970 00:00:01 GMT\r\n\r\n" ); + + if( qValueDefault( 0, "width" ) != 0 ) { + displayDungeon(); + } else { + commify( buffer, getCounter() ); + tags[0] = wtTagReplace( "COUNTER", buffer ); + tags[1] = wtTagReplace( "CGINAME", CGINAME ); + + tags[2] = 0; + + wtWriteTemplateToFile( stdout, TEMPATH "dungeon_main.tem", tags ); + wtFreeTagList( tags ); + } + + qFree(); + + return 0; +} diff --git a/src/jbdungeon.cpp b/src/jbdungeon.cpp new file mode 100644 index 0000000..d7e6c28 --- /dev/null +++ b/src/jbdungeon.cpp @@ -0,0 +1,685 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeon + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * ---------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include + +#include "gameutil.h" +#include "jbdungeon.h" + + +JBDungeonOptions::JBDungeonOptions() { + size.x = 10; + size.y = 10; + size.z = 1; + + start.x = 0; + start.y = 0; + start.z = 0; + + end.x = -1; + end.y = -1; + end.z = -1; + + seed = time(NULL); + randomness = 100; + sparseness = 0; + clearDeadends = 0; + + minRoomCount = 0; + maxRoomCount = 0; + minRoomX = 0; + maxRoomX = 0; + minRoomY = 0; + maxRoomY = 0; + + secretDoors = 5; + concealedDoors = 5; + + mask = 0; +} + + +JBDungeonOptions::~JBDungeonOptions() { + if( mask != 0 ) { + delete mask; + } +} + + +JBDungeonRoom::JBDungeonRoom() { + next = 0; + walls = 0; + data = 0; +} + +JBDungeonRoom::JBDungeonRoom( JBDungeonRoom* nextRoom ) { + next = nextRoom; + walls = 0; + data = 0; +} + +JBDungeonRoom::~JBDungeonRoom() { + if( next != 0 ) { + delete next; + } + if( walls != 0 ) { + delete[] walls; + } + if( data != 0 ) { + delete data; + } +} + + +const int JBDungeonWall::c_NONE = 0; +const int JBDungeonWall::c_WALL = 1; +const int JBDungeonWall::c_DOOR = 2; +const int JBDungeonWall::c_SECRETDOOR = 3; +const int JBDungeonWall::c_CONCEALEDDOOR = 4; + + +JBDungeonWall::JBDungeonWall( const JBMazePt& p1, const JBMazePt& p2, int wallType ) { + pt1 = p1; + pt2 = p2; + type = wallType; + next = 0; + data = 0; +} + + +JBDungeonWall::~JBDungeonWall() { + delete data; +} + + +const int JBDungeon::c_WALL = 0x0001; +const int JBDungeon::c_PASSAGE = 0x0002; +const int JBDungeon::c_ROOM = 0x0004; + + +JBDungeon::JBDungeon( JBDungeonOptions& options ) { + m_dungeon = 0; + m_rooms = 0; + m_walls = 0; + m_dataPath = 0; + + m_x = m_y = m_z = 0; + + if( options.mask != 0 ) { + m_mask = new JBMazeMask( *options.mask ); + } else { + m_mask = new JBMazeMask( options.size.x, options.size.y ); + } + + setDataPath( "" ); + + m_generate( options ); + m_computeRooms( options ); + m_computeWalls( options ); +} + + +JBDungeon::~JBDungeon() { + int x; + int y; + + for( x = 0; x < m_x; x++ ) { + for( y = 0; y < m_y; y++ ) { + free( m_dungeon[x][y] ); + } + free( m_dungeon[x] ); + } + free( m_dungeon ); + + free( m_solution ); + + if( m_rooms != 0 ) { + delete m_rooms; + } + if( m_walls != 0 ) { + delete m_walls; + } + + delete m_mask; + delete m_dataPath; +} + + +void JBDungeon::m_generate( JBDungeonOptions& options ) { + JBMaze* maze; + int x; + int y; + int z; + int dir; + + /* create the maze */ + maze = new JBMaze( options.size.x, options.size.y, options.size.z, + options.seed, options.randomness, + options.start.x, options.start.y, options.start.z, + options.end.x, options.end.y, options.end.z ); + + /* set the mask to use for the maze (and dungeon) */ + maze->setMask( new JBMazeMask( *m_mask ) ); + + /* generate, solve, sparsify, and clear the deadends */ + maze->generate(); + maze->solve( &m_solution, &m_solutionLength ); + maze->sparsify( options.sparseness ); + maze->clearDeadends( options.clearDeadends ); + + /* the dimension of the dungeon is twice (plus 1) the dimension of the + * maze on which it was based. This is to allow the walls of the dungeon + * to be considered full-blocks. Here, we are converting the solution + * of the maze to the new dimensions. */ + + for( x = 0; x < m_solutionLength; x++ ) { + m_solution[ x ].x = m_solution[ x ].x * 2 + 1; + m_solution[ x ].y = m_solution[ x ].y * 2 + 1; + } + + /* set the dimensions of the dungeon to be the dimensions of the mask, + * times 2 plus 1 (to account for walls) */ + + m_x = m_mask->getWidth() * 2 + 1; + m_y = m_mask->getHeight() * 2 + 1; + m_z = options.size.z; + + /* allocate and initialize the dungeon */ + + m_dungeon = (int***)malloc( m_x * sizeof( int** ) ); + + for( x = 0; x < m_mask->getWidth(); x++ ) { + m_dungeon[ x*2 ] = (int**)malloc( m_y * sizeof( int* ) ); + m_dungeon[ x*2+1 ] = (int**)malloc( m_y * sizeof( int* ) ); + for( y = 0; y < m_mask->getHeight(); y++ ) { + m_dungeon[ x*2 ][ y*2 ] = (int*)malloc( m_z * sizeof( int ) ); + m_dungeon[ x*2 ][ y*2+1 ] = (int*)malloc( m_z * sizeof( int ) ); + m_dungeon[ x*2+1 ][ y*2 ] = (int*)malloc( m_z * sizeof( int ) ); + m_dungeon[ x*2+1 ][ y*2+1 ] = (int*)malloc( m_z * sizeof( int ) ); + for( z = 0; z < m_z; z++ ) { + m_dungeon[ x*2 ][ y*2 ][ z ] = c_WALL; + m_dungeon[ x*2 ][ y*2+1 ][ z ] = c_WALL; + m_dungeon[ x*2+1 ][ y*2 ][ z ] = c_WALL; + m_dungeon[ x*2+1 ][ y*2+1 ][ z ] = c_WALL; + dir = maze->getExitsAt( x, y, z ); + if( dir != 0 ) { + m_dungeon[ x*2+1 ][ y*2+1 ][ z ] = c_PASSAGE; + } + if( ( dir & JBMaze::c_NORTH ) != 0 ) { + m_dungeon[ x*2+1 ][ y*2 ][ z ] = c_PASSAGE; + } + if( ( dir & JBMaze::c_WEST ) != 0 ) { + m_dungeon[ x*2 ][ y*2+1 ][ z ] = c_PASSAGE; + } + } + } + m_dungeon[ x*2 ][ m_y-1 ] = (int*)malloc( m_z * sizeof( int ) ); + m_dungeon[ x*2+1 ][ m_y-1 ] = (int*)malloc( m_z * sizeof( int ) ); + for( z = 0; z < m_z; z++ ) { + m_dungeon[ x*2 ][ m_y-1 ][ z ] = c_WALL; + m_dungeon[ x*2+1 ][ m_y-1 ][ z ] = c_WALL; + } + } + + m_dungeon[m_x-1] = (int**)malloc( m_y * sizeof( int* ) ); + for( y = 0; y < m_y; y++ ) { + m_dungeon[ m_x-1 ][ y ] = (int*)malloc( m_z * sizeof( int ) ); + for( z = 0; z < m_z; z++ ) { + m_dungeon[ m_x-1 ][ y ][ z ] = c_WALL; + } + } + + delete maze; +} + + +JBDungeonRoom* JBDungeon::getRoom( int idx ) { + JBDungeonRoom* room; + int i; + + for( i = 0, room = m_rooms; room != 0; i++, room = room->next ) { + if( i == idx ) { + return room; + } + } + + return room; +} + + +int JBDungeon::getDungeonAt( int x, int y, int z ) { + if( ( x < 0 ) || ( y < 0 ) || ( z < 0 ) ) { + return 0; + } + if( ( x >= m_x ) || ( y >= m_y ) || ( z >= m_z ) ) { + return 0; + } + + return m_dungeon[ x ][ y ][ z ]; +} + + +void JBDungeon::m_computeRooms( JBDungeonOptions& options ) { + int roomCount; + int rx; + int ry; + int i; + int j; + int k; + int z; + int cx; + int cy; + + for( z = 0; z < m_z; z++ ) { + if( options.maxRoomCount == options.minRoomCount ) { + roomCount = options.minRoomCount; + } else { + roomCount = rand() % ( options.maxRoomCount - options.minRoomCount + 1 ) + options.minRoomCount; + } + + for( i = 0; i < roomCount; i++ ) { + if( options.maxRoomX == options.minRoomX ) { + rx = options.maxRoomX; + } else { + rx = rand() % ( options.maxRoomX - options.minRoomX + 1 ) + options.minRoomX; + } + + if( options.maxRoomY == options.minRoomY ) { + ry = options.minRoomY; + } else { + ry = rand() % ( options.maxRoomY - options.minRoomY + 1 ) + options.minRoomY; + } + + /* disallow extremely narrow rooms by requiring that a room never be + * thinner than half it's longest dimension. */ + + if( rx > ( ry << 1 ) ) { + ry = ( rx >> 1 ) + 1; + } + if( ry > ( rx << 1 ) ) { + rx = ( ry >> 1 ) + 1; + } + + if( m_findOptimalRoomPlacement( rx, ry, z, cx, cy ) != 0 ) { + break; + } + + m_addRoom( cx, cy, z, rx, ry ); + + for( j = 0; j < rx; j++ ) { + for( k = 0; k < ry; k++ ) { + if( m_mask->getMaskAt( (cx+j)>>1, (cy+k)>>1 ) ) { + m_dungeon[ cx+j ][ cy+k ][ z ] = c_ROOM; + } + } + } + } + } +} + + +int determineRandomDoorType( JBDungeonOptions& options ) { + int d; + + d = rollDice( 1, 100 ); + if( d <= options.secretDoors ) { + return JBDungeonWall::c_SECRETDOOR; + } + + d -= options.secretDoors; + if( d <= options.concealedDoors ) { + return JBDungeonWall::c_CONCEALEDDOOR; + } + + return JBDungeonWall::c_DOOR; +} + + +void JBDungeon::m_computeWalls( JBDungeonOptions& options ) { + JBDungeonRoom* room; + int walls; + WEIGHTEDLIST* one; + WEIGHTEDLIST* two; + int oneTotal; + int twoTotal; + int lastOne; + int lastTwo; + int i; + int j; + JBDungeonWall* wall; + + /* -------------------------------------------------------------------- * + * What's happening here is the following: we need to add walls to + * separate the rooms from the passageways. Some of these walls are + * going to be doors. However, there are stretches of wall that + * abut a passageway, and we only want ONE door for this section of wall, + * rather than one door for each square of that section. So we use + * the trusty weighted list approach, adding each adjacent section of + * wall to the list and choosing one of the sections at random. + * + * And if that made no sense to you at all -- reread it. It's a + * simple procedure that's difficult to describe simply. + * -------------------------------------------------------------------- */ + + for( room = m_rooms; room != 0; room = room->next ) { + walls = 0; + + oneTotal = twoTotal = 0; + one = two = 0; + lastOne = lastTwo = -1; + + /* check for walls and doors on the north and south */ + + for( j = 0; j < room->size.x; j++ ) { + if( ( one != 0 ) && ( lastOne != j-1 ) ) { + wall = (JBDungeonWall*)getWeightedItem( &one, rollDice( 1, oneTotal ), &oneTotal ); + wall->type = determineRandomDoorType( options ); + + destroyWeightedList( &one ); + oneTotal = 0; + } + + if( m_dungeon[ room->topLeft.x+j ][ room->topLeft.y-1 ][ room->topLeft.z ] != c_WALL ) { + JBMazePt p1( room->topLeft.x+j, room->topLeft.y-1, room->topLeft.z ); + JBMazePt p2( room->topLeft.x+j, room->topLeft.y, room->topLeft.z ); + + m_addWall( p1, p2, JBDungeonWall::c_WALL ); + walls++; + + lastOne = j; + oneTotal += addToWeightedList( &one, (long)m_walls, 1 ); + } + + if( ( two != 0 ) && ( lastTwo != j-1 ) ) { + wall = (JBDungeonWall*)getWeightedItem( &two, rollDice( 1, twoTotal ), &twoTotal ); + wall->type = determineRandomDoorType( options ); + + destroyWeightedList( &two ); + twoTotal = 0; + } + + if( m_dungeon[ room->topLeft.x+j ][ room->topLeft.y+room->size.y ][ room->topLeft.z ] != c_WALL ) { + JBMazePt p1( room->topLeft.x+j, room->topLeft.y+room->size.y-1, room->topLeft.z ); + JBMazePt p2( room->topLeft.x+j, room->topLeft.y+room->size.y, room->topLeft.z ); + + m_addWall( p1, p2, JBDungeonWall::c_WALL ); + walls++; + + lastTwo = j; + twoTotal += addToWeightedList( &two, (long)m_walls, 1 ); + } + } + + if( one != 0 ) { + wall = (JBDungeonWall*)getWeightedItem( &one, rollDice( 1, oneTotal ), &oneTotal ); + wall->type = determineRandomDoorType( options ); + } + if( two != 0 ) { + wall = (JBDungeonWall*)getWeightedItem( &two, rollDice( 1, twoTotal ), &twoTotal ); + wall->type = determineRandomDoorType( options ); + } + + destroyWeightedList( &one ); + destroyWeightedList( &two ); + + /* check for walls and doors on the east and west */ + + oneTotal = twoTotal = 0; + one = two = 0; + lastOne = lastTwo = -1; + + for( j = 0; j < room->size.y; j++ ) { + if( ( one != 0 ) && ( lastOne != j-1 ) ) { + wall = (JBDungeonWall*)getWeightedItem( &one, rollDice( 1, oneTotal ), &oneTotal ); + wall->type = determineRandomDoorType( options ); + + destroyWeightedList( &one ); + oneTotal = 0; + } + + if( m_dungeon[ room->topLeft.x-1 ][ room->topLeft.y+j ][ room->topLeft.z ] != c_WALL ) { + JBMazePt p1( room->topLeft.x-1, room->topLeft.y+j, room->topLeft.z ); + JBMazePt p2( room->topLeft.x, room->topLeft.y+j, room->topLeft.z ); + + m_addWall( p1, p2, JBDungeonWall::c_WALL ); + walls++; + + lastOne = j; + oneTotal += addToWeightedList( &one, (long)m_walls, 1 ); + } + + if( ( two != 0 ) && ( lastTwo != j-1 ) ) { + wall = (JBDungeonWall*)getWeightedItem( &two, rollDice( 1, twoTotal ), &twoTotal ); + wall->type = determineRandomDoorType( options ); + + destroyWeightedList( &two ); + twoTotal = 0; + } + + if( m_dungeon[ room->topLeft.x+room->size.x ][ room->topLeft.y+j ][ room->topLeft.z ] != c_WALL ) { + JBMazePt p1( room->topLeft.x+room->size.x-1, room->topLeft.y+j, room->topLeft.z ); + JBMazePt p2( room->topLeft.x+room->size.x, room->topLeft.y+j, room->topLeft.z ); + + m_addWall( p1, p2, JBDungeonWall::c_WALL ); + walls++; + + lastTwo = j; + twoTotal += addToWeightedList( &two, (long)m_walls, 1 ); + } + } + + if( one != 0 ) { + wall = (JBDungeonWall*)getWeightedItem( &one, rollDice( 1, oneTotal ), &oneTotal ); + wall->type = determineRandomDoorType( options ); + } + if( two != 0 ) { + wall = (JBDungeonWall*)getWeightedItem( &two, rollDice( 1, twoTotal ), &twoTotal ); + wall->type = determineRandomDoorType( options ); + } + + destroyWeightedList( &one ); + destroyWeightedList( &two ); + + room->wallCount = walls; + room->walls = new JBDungeonWall*[ walls ]; + for( wall = m_walls, i = 0; i < walls; i++, wall = wall->next ) { + room->walls[ i ] = wall; + } + } +} + + +int JBDungeon::m_findOptimalRoomPlacement( int& rx, int& ry, int z, int& cx, int& cy ) { + int x; + int y; + int i; + int j; + int tally; + int spaceX; + int spaceY; + int d; + int minimumTally; + int xForMin; + int yForMin; + int isDiagonal; + WEIGHTEDLIST* wlist; + int total; + int overlapsRoom; + int lowestOverlapsRoom; + + if( rx > m_x - 2 ) { + rx = m_x - 2; + } + if( ry > m_y - 2 ) { + ry = m_y - 2; + } + + spaceX = ( m_x - rx ); + spaceY = ( m_y - ry ); + + minimumTally = 100000; + xForMin = -1; + yForMin = -1; + + wlist = 0; + total = 0; + overlapsRoom = 0; + + lowestOverlapsRoom = 0; + for( x = 1; x < spaceX; x++ ) { + for( y = 1; y < spaceY; y++ ) { + + if( !m_mask->getMaskAt( x>>1, y>>1 ) ) { + continue; + } + + tally = 0; + for( i = -1; i < rx+1; i++ ) { + for( j = -1; j < ry+1; j++ ) { + d = m_dungeon[ x+i ][ y+j ][ z ]; + if( ( ( i == -1 ) && ( j == -1 ) ) || + ( ( i == -1 ) && ( j == ry ) ) || + ( ( i == rx ) && ( j == -1 ) ) || + ( ( i == rx ) && ( j == ry ) ) ) + { + isDiagonal = 1; + } else { + if( ( d & c_PASSAGE ) != 0 ) { + if( ( j == -1 ) || ( i == -1 ) || ( j == ry ) || ( i == rx ) ) { + tally++; + } else { + tally += 3; + } + } + if( ( d & c_ROOM ) != 0 ) { + /* we REALLY don't want rooms to overlap unless they have to */ + tally += 100; + overlapsRoom = 1; + } + if( ( i >= 0 ) && ( j >= 0 ) && ( i < rx ) && ( j < ry ) ) { + if( !m_mask->getMaskAt( (x+i)/2, (y+j)/2 ) ) { + tally += 10; + } + } + } + } + } + + if( ( tally > 0 ) && ( tally <= minimumTally ) ) { + if( tally != minimumTally ) { + destroyWeightedList( &wlist ); + wlist = 0; + total = 0; + lowestOverlapsRoom = overlapsRoom; + } + + minimumTally = tally; + xForMin = x; + yForMin = y; + total += addToWeightedList( &wlist, ( ( xForMin << 16 ) + yForMin ), 1 ); + } + + overlapsRoom = 0; + } + } + + if( lowestOverlapsRoom ) { + if( ( rx == 1 ) && ( ry == 1 ) ) { + return 1; + } + if( rx > ry ) { + rx--; + } else { + ry--; + } + return m_findOptimalRoomPlacement( rx, ry, z, cx, cy ); + } + + if( wlist == 0 ) { + cx = 1 + rand() % ( spaceX - 1 ); + cy = 1 + rand() % ( spaceY - 1 ); + } else { + total = getWeightedItem( &wlist, rollDice( 1, total ), &total ); + cx = (unsigned int)( total >> 16 ); + cy = (unsigned int)( total & 0xFFFF ); + } + + return 0; +} + + +void JBDungeon::m_addRoom( int cx, int cy, int z, int rx, int ry ) { + JBDungeonRoom* room; + + room = new JBDungeonRoom( m_rooms ); + + room->size.x = rx; + room->size.y = ry; + room->size.z = 0; + room->topLeft.x = cx; + room->topLeft.y = cy; + room->topLeft.z = z; + + m_rooms = room; +} + + +void JBDungeon::m_addWall( const JBMazePt& p1, const JBMazePt& p2, int type ) { + JBDungeonWall* wall; + + wall = new JBDungeonWall( p1, p2, type ); + wall->next = m_walls; + m_walls = wall; +} + + +int JBDungeon::getWallBetween( const JBMazePt& p1, const JBMazePt& p2 ) { + JBDungeonWall* wall; + + for( wall = m_walls; wall != 0; wall = wall->next ) { + if( ( wall->pt1 == p1 ) && ( wall->pt2 == p2 ) ) { + return wall->type; + } + } + + if( ( ( m_dungeon[ p1.x ][ p1.y ][ p1.z ] == c_WALL ) || + ( m_dungeon[ p2.x ][ p2.y ][ p2.z ] == c_WALL ) ) && + ( ( m_dungeon[ p1.x ][ p1.y ][ p1.z ] != c_WALL ) || + ( m_dungeon[ p2.x ][ p2.y ][ p2.z ] != c_WALL ) ) ) + { + return JBDungeonWall::c_WALL; + } + + return JBDungeonWall::c_NONE; +} + + +int JBDungeon::getRoomCount() { + int i; + JBDungeonRoom* room; + + for( i = 0, room = m_rooms; room != 0; i++, room = room->next ); + + return i; +} + + +void JBDungeon::setDataPath( const char* path ) { + if( m_dataPath != 0 ) { + delete[] m_dataPath; + } + + m_dataPath = new char[ strlen( path ) + 1 ]; + strcpy( m_dataPath, path ); +} diff --git a/src/jbdungeondata.cpp b/src/jbdungeondata.cpp new file mode 100644 index 0000000..119aa4b --- /dev/null +++ b/src/jbdungeondata.cpp @@ -0,0 +1,1948 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonData + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * ---------------------------------------------------------------------- */ + +#include +#include +#include + +#include "jbdungeon.h" +#include "jbdungeondata.h" + +#include "dndconst.h" +#include "dndutil.h" +#include "gameutil.h" + +#include "npcEngine.h" +#include "treasureEngine.h" + +#define SPELLTRAP ((char*)14) +#define DRAGON ((char*)11) + +#define MONSTER ( 0x0001 ) +#define FEATURE ( 0x0002 ) +#define TREASURE ( 0x0004 ) +#define TRAP ( 0x0008 ) + +#define REROLL_ONCE ( 0x10000000 ) +#define REROLL_ANY ( 0x20000000 ) + +/* door and wall attribute descriptions */ + +static struct { + long flag; + char* desc; +} s_flagDescriptions[] = { + { dtWOODEN, "wooden" }, + { dtSTONE, "stone" }, + { dtIRON, "iron" }, + { cdtILLUSORYWALL, "illusory wall" }, + { cdtFALSEWALL, "false wall" }, + { dtSIMPLE, "simple" }, + { dtGOOD, "good" }, + { dtSTRONG, "strong" }, + { dtFREE, "free" }, + { dtSTUCK, "stuck" }, + { dtLOCKED, "locked" }, + { dtSIDESLIDE, "side-sliding" }, + { dtDOWNSLIDE, "down-sliding" }, + { dtUPSLIDE, "up-sliding" }, + { dtMAGIC, "magically reinforced" }, + { sdtROTATINGWALL, "rotating wall" }, + { sdtPASSWALL, "passwall" }, + { sdtPUSHBRICK, "push-brick trigger" }, + { sdtMAGICWORD, "magic word trigger" }, + { sdtGESTURE, "gesture trigger" }, + { sdtPRESSUREPLATE, "pressure-plate trigger" }, + { cdtBEHINDTAPESTRY, "behind tapestry" }, + { cdtBEHINDRUBBISH, "behind rubbish" }, + { dtTRAPPED, "trapped" }, + { 0, 0 } +}; + + +typedef struct __doortype__ DOORTYPE; +struct __doortype__ { + int dtop; + long data; +}; + +/* "normal" door types, by percentage chance of occuring */ + +static DOORTYPE s_doorTypes[] = { + { 8, dtWOODEN|dtSIMPLE|dtFREE }, + { 9, dtWOODEN|dtSIMPLE|dtFREE|dtTRAPPED }, + { 23, dtWOODEN|dtSIMPLE|dtSTUCK }, + { 24, dtWOODEN|dtSIMPLE|dtSTUCK|dtTRAPPED }, + { 29, dtWOODEN|dtSIMPLE|dtLOCKED }, + { 30, dtWOODEN|dtSIMPLE|dtLOCKED|dtTRAPPED }, + { 35, dtWOODEN|dtGOOD|dtFREE }, + { 36, dtWOODEN|dtGOOD|dtFREE|dtTRAPPED }, + { 44, dtWOODEN|dtGOOD|dtSTUCK }, + { 45, dtWOODEN|dtGOOD|dtSTUCK|dtTRAPPED }, + { 49, dtWOODEN|dtGOOD|dtLOCKED }, + { 50, dtWOODEN|dtGOOD|dtLOCKED|dtTRAPPED }, + { 55, dtWOODEN|dtSTRONG|dtFREE }, + { 56, dtWOODEN|dtSTRONG|dtFREE|dtTRAPPED }, + { 64, dtWOODEN|dtSTRONG|dtSTUCK }, + { 65, dtWOODEN|dtSTRONG|dtSTUCK|dtTRAPPED }, + { 69, dtWOODEN|dtSTRONG|dtLOCKED }, + { 70, dtWOODEN|dtSTRONG|dtLOCKED|dtTRAPPED }, + { 71, dtSTONE|dtFREE }, + { 72, dtSTONE|dtFREE|dtTRAPPED }, + { 75, dtSTONE|dtSTUCK }, + { 76, dtSTONE|dtSTUCK|dtTRAPPED }, + { 79, dtSTONE|dtLOCKED }, + { 80, dtSTONE|dtLOCKED|dtTRAPPED }, + { 81, dtIRON|dtFREE }, + { 82, dtIRON|dtFREE|dtTRAPPED }, + { 85, dtIRON|dtSTUCK }, + { 89, dtIRON|dtLOCKED }, + { 90, dtIRON|dtLOCKED|dtTRAPPED }, + { 93, dtSIDESLIDE | REROLL_ONCE }, + { 96, dtDOWNSLIDE | REROLL_ONCE }, + { 99, dtUPSLIDE | REROLL_ONCE }, + { 100, dtMAGIC | REROLL_ONCE }, + { 0, 0 } +}; + + +/* secret door types, by percentage chance of occuring */ + +static DOORTYPE s_secretDoorTypes[] = { + { 4, sdtROTATINGWALL|sdtPUSHBRICK }, + { 5, sdtROTATINGWALL|sdtPUSHBRICK|dtTRAPPED }, + { 9, sdtROTATINGWALL|sdtMAGICWORD }, + { 10, sdtROTATINGWALL|sdtMAGICWORD|dtTRAPPED }, + { 14, sdtROTATINGWALL|sdtGESTURE }, + { 15, sdtROTATINGWALL|sdtGESTURE|dtTRAPPED }, + { 19, sdtROTATINGWALL|sdtPRESSUREPLATE }, + { 20, sdtROTATINGWALL|sdtPRESSUREPLATE|dtTRAPPED }, + { 24, sdtPASSWALL|sdtPUSHBRICK }, + { 25, sdtPASSWALL|sdtPUSHBRICK|dtTRAPPED }, + { 29, sdtPASSWALL|sdtMAGICWORD }, + { 30, sdtPASSWALL|sdtMAGICWORD|dtTRAPPED }, + { 34, sdtPASSWALL|sdtGESTURE }, + { 35, sdtPASSWALL|sdtGESTURE|dtTRAPPED }, + { 39, sdtPASSWALL|sdtPRESSUREPLATE }, + { 40, sdtPASSWALL|sdtPRESSUREPLATE|dtTRAPPED }, + { 44, dtSIDESLIDE|sdtPUSHBRICK }, + { 45, dtSIDESLIDE|sdtPUSHBRICK|dtTRAPPED }, + { 49, dtSIDESLIDE|sdtMAGICWORD }, + { 50, dtSIDESLIDE|sdtMAGICWORD|dtTRAPPED }, + { 54, dtSIDESLIDE|sdtGESTURE }, + { 55, dtSIDESLIDE|sdtGESTURE|dtTRAPPED }, + { 59, dtSIDESLIDE|sdtPRESSUREPLATE }, + { 60, dtSIDESLIDE|sdtPRESSUREPLATE|dtTRAPPED }, + { 64, dtUPSLIDE|sdtPUSHBRICK }, + { 65, dtUPSLIDE|sdtPUSHBRICK|dtTRAPPED }, + { 69, dtUPSLIDE|sdtMAGICWORD }, + { 70, dtUPSLIDE|sdtMAGICWORD|dtTRAPPED }, + { 74, dtUPSLIDE|sdtGESTURE }, + { 75, dtUPSLIDE|sdtGESTURE|dtTRAPPED }, + { 79, dtUPSLIDE|sdtPRESSUREPLATE }, + { 80, dtUPSLIDE|sdtPRESSUREPLATE|dtTRAPPED }, + { 84, dtDOWNSLIDE|sdtPUSHBRICK }, + { 85, dtDOWNSLIDE|sdtPUSHBRICK|dtTRAPPED }, + { 89, dtDOWNSLIDE|sdtMAGICWORD }, + { 90, dtDOWNSLIDE|sdtMAGICWORD|dtTRAPPED }, + { 94, dtDOWNSLIDE|sdtGESTURE }, + { 95, dtDOWNSLIDE|sdtGESTURE|dtTRAPPED }, + { 99, dtDOWNSLIDE|sdtPRESSUREPLATE }, + { 100, dtDOWNSLIDE|sdtPRESSUREPLATE|dtTRAPPED }, + { 0, 0 } +}; + +/* concealed door types, by percentage chance of occuring */ + +static DOORTYPE s_concealedDoorTypes[] = { + { 35, cdtILLUSORYWALL }, + { 36, cdtILLUSORYWALL|dtTRAPPED }, + { 70, cdtFALSEWALL|dtFREE }, + { 71, cdtFALSEWALL|dtFREE|dtTRAPPED }, + { 99, cdtFALSEWALL|dtSTUCK }, + { 100, cdtFALSEWALL|dtSTUCK|dtTRAPPED }, + { 0, 0 } +}; + + +/* room contents, by percentage chance of occuring. Note that is directly + * from the table in the DMG. */ + +static struct { + int dtop; + int contents; +} s_roomContents[] = { + { 18, MONSTER }, + { 44, MONSTER | FEATURE }, + { 45, MONSTER | TREASURE }, + { 46, MONSTER | TRAP }, + { 47, MONSTER | FEATURE | TREASURE }, + { 48, MONSTER | FEATURE | TRAP }, + { 49, MONSTER | TREASURE | TRAP }, + { 50, MONSTER | FEATURE | TREASURE | TRAP }, + { 76, FEATURE }, + { 77, FEATURE | TREASURE }, + { 78, FEATURE | TRAP }, + { 79, FEATURE | TREASURE | TRAP }, + { 80, TREASURE }, + { 81, TREASURE | TRAP }, + { 82, TRAP }, + { 100, 0 }, + { 0, 0 } +}; + + +typedef struct __traplist__ TRAPLIST; +struct __traplist__ { + int dtop; + int forDoor; + char* trap; +}; + + +typedef struct __featurelist__ FEATURELIST; +struct __featurelist__ { + int dtop; + char* feature; +}; + + +/* trap types (CR 1-3), by percentage chance of occuring. Note that is + * directly from the table in the DMG. */ + +TRAPLIST traps1[] = { + { 10, 1, "arrow trap (CR1)" }, + { 15, 1, "spear trap (CR2)" }, + { 25, 1, "pit trap (20 ft. deep) (CR1)" }, + { 35, 1, "spiked pit trap (20 ft. deep) (CR2)" }, + { 45, 1, "pit trap (40 ft. deep) (CR2)" }, + { 50, 1, "spiked pit trap (40 ft. deep) (CR3)" }, + { 55, 1, "pit trap (30 ft. deep) (CR3)" }, + { 65, 1, "poison needle trap (CR2)" }, + { 70, 1, "hail of needles (CR1)" }, + { 75, 1, "scything blade trap (CR1)" }, + { 80, 1, "large net trap (CR1)" }, + { 85, 1, "portculis trap (CR2)" }, + { 90, 1, "flame jet (CR2)" }, + { 95, 1, "lightning blast (CR3)" }, + { 100, 0, "illusion over spiked pit (CR3)" }, + { 0, 0, 0 } +}; + +/* trap types (CR 4+), by percentage chance of occuring. Note that is + * directly from the table in the DMG. */ + +TRAPLIST traps2[] = { + { 10, 1, "spiked pit trap (60 ft. deep) (CR4)" }, + { 20, 1, "pit trap (80 ft. deep) (CR4)" }, + { 25, 1, "spiked pit trap (80 ft. deep) (CR5)" }, + { 30, 1, "pit trap (100 ft. deep) (CR5)" }, + { 35, 1, "spiked pit trap (100 ft. deep) (CR6)" }, + { 38, 1, "crushing wall trap (CR10)" }, + { 43, 1, "falling block trap (CR5)" }, + { 45, 1, "poison gas trap (CR10)" }, + { 50, 1, "flooding room trap (CR5)" }, + { 55, 1, "globe of cold (CR4)" }, + { 60, 1, "electrified floor (CR4)" }, + { 65, 1, "air sucked out of room (CR5)" }, + { 70, 1, "floor transforms into acid (CR6)" }, + { 100, 1, SPELLTRAP }, + { 0, 0, 0 } +}; + + +/* possible spells for use in traps, by percentage chance of occuring. Note + * that is directly from the table in the DMG. */ + +static struct { + int dtop; + int spell; +} s_spellsForTraps[] = { + { 1, spACIDFOG }, + { 2, spALARM }, + { 3, spANIMATEOBJECTS }, + { 4, spANTIMAGICFIELD }, + { 5, spBIGBYSCLENCHEDFIST }, + { 6, spBIGBYSFORCEFULHAND }, + { 7, spBIGBYSGRASPINGHAND }, + { 8, spBINDING }, + { 9, spBLADEBARRIER }, + { 10, spBLINDNESSDEAFNESS }, + { 11, spCIRCLEOFDEATH }, + { 12, spCOLORSPRAY }, + { 13, spCONFUSION }, + { 14, spCONTAGION }, + { 15, spDARKNESS }, + { 16, spDISINTEGRATE }, + { 17, spDISPELGOOD }, + { 18, spDISPELMAGIC }, + { 19, spDOMINATEPERSON }, + { 20, spDOOM }, + { 21, spENERGYDRAIN }, + { 22, spENERVATION }, + { 23, spENLARGE }, + { 24, spEXPLOSIVERUNES }, + { 25, spEYEBITE }, + { 26, spFALSEVISION }, + { 27, spFEAR }, + { 28, spFEEBLEMIND }, + { 29, spFIREBALL }, + { 30, spFIRETRAP }, + { 31, spFLAMINGSPHERE }, + { 32, spFLESHTOSTONE }, + { 33, spFORBIDDANCE }, + { 34, spFORCECAGE }, + { 35, spGATE }, + { 36, spGEASQUEST }, + { 37, spGIANTVERMIN }, + { 38, spGLYPHOFWARDING }, + { 39, spGREASE }, + { 40, spHARM }, + { 41, spHOLDMONSTER }, + { 42, spHOLDPERSON }, + { 43, spIMPRISONMENT }, + { 44, spINFLICTCRITICALWOUNDS }, + { 45, spINFLICTLIGHTWOUNDS }, + { 46, spINFLICTMODERATEWOUNDS }, + { 47, spINFLICTSERIOUSWOUNDS }, + { 48, spINVISIBILITY }, + { 49, spLEVITATE }, + { 50, spLIGHTNINGBOLT }, + { 51, spMAGICJAR }, + { 52, spMAGICMISSILE }, + { 53, spMASSSUGGESTION }, + { 54, spMELFSACIDARROW }, + { 55, spMINDFOG }, + { 56, spMORDENKAINENSDISJUNCTION }, + { 57, spNIGHTMARE }, + { 58, spOTILUKESTELEKINETICSPHERE }, + { 59, spPERMANENCY }, + { 60, spPERMANENTIMAGE }, + { 61, spPLANESHIFT }, + { 62, spPOLYMORPHOTHER }, + { 63, spPOWERWORDKILL }, + { 64, spPRISMATICSPRAY }, + { 65, spPROGRAMMEDIMAGE }, + { 66, spREDUCE }, + { 67, spREPULSION }, + { 68, spREVERSEGRAVITY }, + { 69, spSCREEN }, + { 70, spSEPIASNAKESIGIL }, + { 71, spSHATTER }, + { 72, spSILENCE }, + { 73, spSLAYLIVING }, + { 74, spSLOW }, + { 75, spSPELLTURNING }, + { 76, spSUGGESTION }, + { 77, spSUMMONMONSTERI }, + { 78, spSUMMONMONSTERII }, + { 79, spSUMMONMONSTERIII }, + { 80, spSUMMONMONSTERIV }, + { 81, spSUMMONMONSTERIX }, + { 82, spSUMMONMONSTERV }, + { 83, spSUMMONMONSTERVI }, + { 84, spSUMMONMONSTERVII }, + { 85, spSUMMONMONSTERVIII }, + { 86, spSUMMONMONSTERIX }, + { 87, spSYMBOL }, + { 88, spTASHASHIDEOUSLAUGHTER }, + { 89, spTELEKINESIS }, + { 90, spTELEPORT }, + { 91, spTEMPORALSTASIS }, + { 92, spTRAPTHESOUL }, + { 93, spVANISH }, + { 94, spWALLOFFIRE }, + { 95, spWALLOFFORCE }, + { 96, spWALLOFIRON }, + { 97, spWALLOFSTONE }, + { 98, spWEB }, + { 99, spWEIRD }, + { 100, spWORDOFCHAOS }, + { 0, 0 } +}; + + +/* major features of rooms, by percentage chance of occuring. Note + * that is directly from the table in the DMG. */ + +static FEATURELIST s_majorFeatures[] = { + { 1, "alcove" }, + { 2, "altar" }, + { 3, "arch" }, + { 4, "arrow slit (wall)/murder hole (ceiling)" }, + { 5, "balcony" }, + { 6, "barrel" }, + { 7, "bed" }, + { 8, "bench" }, + { 9, "bookcase" }, + { 10, "brazier" }, + { 11, "cage" }, + { 12, "caldron" }, + { 13, "carpet" }, + { 14, "carving" }, + { 15, "casket" }, + { 16, "catwalk" }, + { 17, "chair" }, + { 18, "chandelier" }, + { 19, "charcoal bin" }, + { 20, "chasm" }, + { 21, "chest" }, + { 22, "chest of drawers" }, + { 23, "chute" }, + { 24, "coat rack" }, + { 25, "collapsed wall" }, + { 26, "crate" }, + { 27, "cupboard" }, + { 28, "curtain" }, + { 29, "divan" }, + { 30, "dome" }, + { 31, "door (broken)" }, + { 32, "dung heap" }, + { 33, "evil symbol" }, + { 34, "fallen stones" }, + { 35, "firepit" }, + { 36, "fireplace" }, + { 37, "font" }, + { 38, "forge" }, + { 39, "fountain" }, + { 40, "furniture (broken)" }, + { 41, "gong" }, + { 42, "hay (pile)" }, + { 43, "hole" }, + { 44, "hole (blasted)" }, + { 45, "idol" }, + { 46, "iron bars" }, + { 47, "iron maiden" }, + { 48, "kiln" }, + { 49, "ladder" }, + { 50, "ledge" }, + { 51, "loom" }, + { 52, "loose masonry" }, + { 53, "manacles" }, + { 54, "manger" }, + { 55, "mirror" }, + { 56, "mosaic" }, + { 57, "mound of rubble" }, + { 58, "oven" }, + { 59, "overhang" }, + { 60, "painting" }, + { 61, "partially collapsed ceiling" }, + { 62, "pedestal" }, + { 63, "peephole" }, + { 64, "pillar" }, + { 65, "pillory" }, + { 66, "pit (shallow)" }, + { 67, "platform" }, + { 68, "pool" }, + { 69, "portcullis" }, + { 70, "rack" }, + { 71, "ramp" }, + { 72, "recess" }, + { 73, "relief" }, + { 74, "sconce" }, + { 75, "screen" }, + { 76, "shaft" }, + { 77, "shelf" }, + { 78, "shrine" }, + { 79, "spinning wheel" }, + { 80, "stall or pen" }, + { 81, "statue" }, + { 82, "statue (toppled)" }, + { 83, "steps" }, + { 84, "stool" }, + { 85, "stuffed beast" }, + { 86, "sunken area" }, + { 87, "table (large)" }, + { 88, "table (small)" }, + { 89, "tapestry" }, + { 90, "throne" }, + { 91, "trash (pile)" }, + { 92, "tripod" }, + { 93, "trough" }, + { 94, "tub" }, + { 95, "wall basin" }, + { 96, "wardrobe" }, + { 97, "weapon rack" }, + { 98, "well" }, + { 99, "winch and pulley" }, + { 100, "workbench" }, + { 0, 0 } +}; + + +/* minor features of rooms, by percentage chance of occuring. Note + * that is directly from the table in the DMG. */ + +static FEATURELIST s_minorFeatures[] = { + { 1, "anvil" }, + { 2, "ash" }, + { 3, "backpack" }, + { 4, "bale (straw)" }, + { 5, "bellows" }, + { 6, "belt" }, + { 7, "bits of fur" }, + { 8, "blanket" }, + { 9, "bloodstain" }, + { 10, "bones (humanoid)" }, + { 11, "bones (nonhumanoid)" }, + { 12, "books" }, + { 13, "boots" }, + { 14, "bottle" }, + { 15, "box" }, + { 16, "branding iron" }, + { 17, "broken glass" }, + { 18, "bucket" }, + { 19, "candle" }, + { 20, "candelabra" }, + { 21, "cards (playing cards)" }, + { 22, "chains" }, + { 23, "claw marks" }, + { 24, "cleaver" }, + { 25, "clothing" }, + { 26, "cobwebs" }, + { 27, "cold spot" }, + { 28, "corpse (adventurer)" }, + { 29, "corpse (monster)" }, + { 30, "cracks" }, + { 31, "dice" }, + { 32, "discarded weapons" }, + { 33, "dishes" }, + { 34, "dipping water" }, + { 35, "drum" }, + { 36, "dust" }, + { 37, "engraving" }, + { 38, "equipment (broken)" }, + { 39, "equipment (usable)" }, + { 40, "flask" }, + { 41, "flint and tinder" }, + { 42, "foodstuffs (spoiled)" }, + { 43, "foodstuffs (edible)" }, + { 44, "fungus" }, + { 45, "grinder" }, + { 46, "hook" }, + { 47, "horn" }, + { 48, "hourglass" }, + { 49, "insects" }, + { 50, "jar" }, + { 51, "keg" }, + { 52, "key" }, + { 53, "lamp" }, + { 54, "lantern" }, + { 55, "markings" }, + { 56, "mold" }, + { 57, "mud" }, + { 58, "mug" }, + { 59, "musical instrument" }, + { 60, "mysterious stain" }, + { 61, "nest (animal)" }, + { 62, "odor (unidentifiable)" }, + { 63, "oil (fuel)" }, + { 64, "oil (scented)" }, + { 65, "paint" }, + { 66, "paper" }, + { 67, "pillows" }, + { 68, "pipe (smoking pipe)" }, + { 69, "pole" }, + { 70, "pot" }, + { 71, "pottery shard" }, + { 72, "pouch" }, + { 73, "puddle (water)" }, + { 74, "rags" }, + { 75, "razor" }, + { 76, "rivulet" }, + { 77, "ropes" }, + { 78, "runes" }, + { 79, "sack" }, + { 80, "scattered stones" }, + { 81, "scorch marks" }, + { 82, "scroll (nonmagical)" }, + { 83, "scroll case (empty)" }, + { 84, "skull" }, + { 85, "slime" }, + { 86, "sound (unexplained)" }, + { 87, "spices" }, + { 88, "spike" }, + { 89, "teeth" }, + { 90, "tongs" }, + { 91, "tools" }, + { 92, "torch (stub)" }, + { 93, "tray" }, + { 94, "trophy" }, + { 95, "twine" }, + { 96, "urn" }, + { 97, "utensils" }, + { 98, "whetstone" }, + { 99, "wood (scraps)" }, + { 100, "words (scrawled)" }, + { 0, 0 } +}; + + +typedef struct __dungeonlevel__ DUNGEONLEVEL; +struct __dungeonlevel__ { + int dtop; + int level; + int mul; + int div; +}; + +/* encounter modifiers by dungeon level, by percentage chance. See the + * DMG for a fuller description of these tables */ + +static DUNGEONLEVEL one[] = { + { 70, 1, 1, 1 }, + { 90, 2, 1, 2 }, + { 100, 3, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL two[] = { + { 20, 1, 2, 1 }, + { 70, 2, 1, 1 }, + { 80, 3, 2, 3 }, + { 90, 4, 1, 2 }, + { 100, 5, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL three[] = { + { 10, 1, 3, 1 }, + { 30, 2, 3, 2 }, + { 70, 3, 1, 1 }, + { 80, 4, 2, 3 }, + { 90, 5, 1, 2 }, + { 100, 6, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL four[] = { + { 10, 1, 4, 1 }, + { 20, 2, 2, 1 }, + { 30, 3, 3, 2 }, + { 70, 4, 1, 1 }, + { 80, 5, 2, 3 }, + { 90, 6, 1, 2 }, + { 100, 7, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL five[] = { + { 5, 2, 4, 1 }, + { 10, 2, 3, 1 }, + { 20, 3, 2, 1 }, + { 30, 4, 3, 2 }, + { 70, 5, 1, 1 }, + { 80, 6, 2, 3 }, + { 90, 7, 1, 2 }, + { 100, 8, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL six[] = { + { 5, 2, 4, 1 }, + { 10, 3, 3, 1 }, + { 20, 4, 2, 1 }, + { 30, 5, 3, 2 }, + { 70, 6, 1, 1 }, + { 80, 7, 2, 3 }, + { 90, 8, 1, 2 }, + { 100, 9, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL seven[] = { + { 5, 3, 4, 1 }, + { 10, 4, 3, 1 }, + { 20, 5, 2, 1 }, + { 30, 6, 3, 2 }, + { 70, 7, 1, 1 }, + { 80, 8, 2, 3 }, + { 90, 9, 1, 2 }, + { 100, 10, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL eight[] = { + { 5, 4, 4, 1 }, + { 10, 5, 3, 1 }, + { 20, 6, 2, 1 }, + { 30, 7, 3, 2 }, + { 70, 8, 1, 1 }, + { 80, 9, 2, 3 }, + { 90, 10, 1, 2 }, + { 100, 11, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL nine[] = { + { 5, 5, 4, 1 }, + { 10, 6, 3, 1 }, + { 20, 7, 2, 1 }, + { 30, 8, 3, 2 }, + { 70, 9, 1, 1 }, + { 80, 10, 2, 3 }, + { 90, 11, 1, 2 }, + { 100, 12, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL ten[] = { + { 5, 6, 4, 1 }, + { 10, 7, 3, 1 }, + { 20, 8, 2, 1 }, + { 30, 9, 3, 2 }, + { 70, 10, 1, 1 }, + { 80, 11, 2, 3 }, + { 90, 12, 1, 2 }, + { 100, 13, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL eleven[] = { + { 5, 7, 4, 1 }, + { 10, 8, 3, 1 }, + { 20, 9, 2, 1 }, + { 30, 10, 3, 2 }, + { 70, 11, 1, 1 }, + { 80, 12, 2, 3 }, + { 90, 13, 1, 2 }, + { 100, 14, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL twelve[] = { + { 5, 8, 4, 1 }, + { 10, 9, 3, 1 }, + { 20, 10, 2, 1 }, + { 30, 11, 3, 2 }, + { 70, 12, 1, 1 }, + { 80, 13, 2, 3 }, + { 90, 14, 1, 2 }, + { 100, 15, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL thirteen[] = { + { 5, 9, 4, 1 }, + { 10, 10, 3, 1 }, + { 20, 11, 2, 1 }, + { 30, 12, 3, 2 }, + { 70, 13, 1, 1 }, + { 80, 14, 2, 3 }, + { 90, 15, 1, 2 }, + { 100, 16, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL fourteen[] = { + { 5, 10, 4, 1 }, + { 10, 11, 3, 1 }, + { 20, 12, 2, 1 }, + { 30, 13, 3, 2 }, + { 70, 14, 1, 1 }, + { 80, 15, 2, 3 }, + { 90, 16, 1, 2 }, + { 100, 17, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL fifteen[] = { + { 5, 11, 4, 1 }, + { 10, 12, 3, 1 }, + { 20, 13, 2, 1 }, + { 30, 14, 3, 2 }, + { 70, 15, 1, 1 }, + { 80, 16, 2, 3 }, + { 90, 17, 1, 2 }, + { 100, 18, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL sixteen[] = { + { 5, 12, 4, 1 }, + { 10, 13, 3, 1 }, + { 20, 14, 2, 1 }, + { 30, 15, 3, 2 }, + { 70, 16, 1, 1 }, + { 80, 17, 2, 3 }, + { 90, 18, 1, 2 }, + { 100, 19, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL seventeen[] = { + { 5, 13, 4, 1 }, + { 10, 14, 3, 1 }, + { 20, 15, 2, 1 }, + { 30, 16, 3, 2 }, + { 70, 17, 1, 1 }, + { 80, 18, 2, 3 }, + { 90, 19, 1, 2 }, + { 100, 20, 1, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL eighteen[] = { + { 5, 14, 4, 1 }, + { 10, 15, 3, 1 }, + { 20, 16, 2, 1 }, + { 30, 17, 3, 2 }, + { 70, 18, 1, 1 }, + { 80, 19, 2, 3 }, + { 100, 20, 1, 2 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL nineteen[] = { + { 5, 15, 4, 1 }, + { 10, 16, 3, 1 }, + { 20, 17, 2, 1 }, + { 30, 18, 3, 2 }, + { 80, 19, 1, 1 }, + { 100, 20, 2, 3 }, + { 0, 0, 0, 0 } +}; + +static DUNGEONLEVEL twenty[] = { + { 5, 16, 4, 1 }, + { 10, 17, 3, 1 }, + { 20, 18, 2, 1 }, + { 30, 19, 3, 2 }, + { 100, 20, 1, 1 }, + { 0, 0, 0, 0 } +}; + +DUNGEONLEVEL* s_masterMonsterTable[] = + { 0, one, two, three, four, five, six, seven, eight, nine, ten, eleven, + twelve, thirteen, fourteen, fifteen, sixteen, seventeen, eighteen, + nineteen, twenty }; + +typedef struct __dungeonmonsters__ DUNGEONMONSTERS; +struct __dungeonmonsters__ { + int dtop; + int dcount; + int dtype; + int dmod; + double treasureMod; + int levelMod; + int npcRace; + int npcClass; + int npcMinLevel; + int npcMaxLevel; + char* monster; +}; + +/* encounter table by dungeon level, by percentage chance of encountering + * that type of monster. See the DMG for a fuller description of these + * tables */ + +DUNGEONMONSTERS mOne[] = { + { 4, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "centipede, medium-size monstrous (vermin)" }, + { 9, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dire rat" }, + { 14, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "fire beetle, giant (vermin)" }, + { 17, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "scorpion, small monstrous (vermin)" }, + { 20, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "spider, small monstrous (vermin)" }, + { 25, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, DRAGON }, + { 30, 1, 3, 0, 0.8, 1, rcDWARF_HILL, npcWARRIOR, 1, 1, 0 }, + { 35, 1, 3, 0, 0.8, 1, rcELF_HIGH, npcWARRIOR, 1, 1, 0 }, + { 40, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 1, 1, 0 }, + { 45, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "darkmantle" }, + { 55, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "krenshar" }, + { 60, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "lemure (devil)" }, + { 65, 1, 4, 2, 0.8, 1, 0, 0, 0, 0, "goblin" }, + { 70, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, "hobgoblin" }, + { 70, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "goblin" }, + { 80, 1, 6, 3, 0.8, 1, 0, 0, 0, 0, "kobold" }, + { 90, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "skeleton, medium-size [human]" }, + { 100, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "zombie, medium-size [human]" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mTwo[] = { + { 5, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "centipede, large monstrous (vermin)" }, + { 10, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "giant ant (vermin)" }, + { 15, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "scorpion, medium-size monstrous (vermin)" }, + { 20, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "spider, medium-size monstrous (vermin)" }, + { 25, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 30, 1, 4, 2, 0.8, 2, rcELF_HIGH, npcWARRIOR, 1, 2, 0 }, + { 35, 1, 3, 0, 0.0, 0, raceANY_DMG, classANY_NPC, 1, 1, 0 }, + { 37, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "choker" }, + { 42, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "ethereal marauder" }, + { 45, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "shrieker" }, + { 50, 1, 4, 2, 0.8, 2, 0, 0, 0, 0, "formian workers" }, + { 55, 1, 4, 2, 0.8, 2, 0, 0, 0, 0, "hobgoblin" }, + { 60, 1, 3, 0, 0.8, 2, 0, 0, 0, 0, "hobgoblin" }, + { 60, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "goblin" }, + { 70, 1, 3, 0, 0.8, 2, 0, 0, 0, 0, "lizardfolk" }, + { 80, 1, 4, 2, 0.8, 2, 0, 0, 0, 0, "orc" }, + { 90, 1, 4, 2, 0.5, 0, 0, 0, 0, 0, "zombie, medium-size [human]" }, + { 100, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "ghoul" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mThree[] = { + { 2, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "bombardier beetle, giant (vermin)" }, + { 4, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "centipede, huge monstrous (vermin)" }, + { 6, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "dire badger" }, + { 8, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "dire bat" }, + { 11, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "gelatinous cube (ooze)" }, + { 13, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "praying mantis, giant (vermin)" }, + { 14, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "scorpion, large monstrous (vermin)" }, + { 15, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "spider, large monstrous (vermin)" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 25, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "imp (devil)" }, + { 30, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "wererat (lycanthrope)" }, + { 30, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "dire rat" }, + { 35, 1, 6, 3, 0.8, 3, rcDWARF_HILL, npcWARRIOR, 1, 2, 0 }, + { 40, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 1, 1, 0 }, + { 44, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "dretch (demon)" }, + { 48, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "ethereal filcher" }, + { 52, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "phantom fungus" }, + { 56, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "thoqquas" }, + { 60, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "vargouilles" }, + { 62, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "bugbear" }, + { 62, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "goblin" }, + { 67, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "gnoll" }, + { 69, 1, 4, 2, 0.8, 3, 0, 0, 0, 0, "goblin" }, + { 69, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "wolf" }, + { 71, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "hobgoblin" }, + { 71, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "wolf" }, + { 75, 1, 6, 3, 0.8, 3, 0, 0, 0, 0, "kobold" }, + { 75, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "dire weasel" }, + { 80, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "troglodyte" }, + { 90, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "shadow" }, + { 100, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "skeleton, large [ogre]" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + + +DUNGEONMONSTERS mFour[] = { + { 4, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "ankheg" }, + { 8, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "dire weasel" }, + { 12, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "ooze, gray" }, + { 15, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "snake, huge viper (animal)" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 23, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "formian warrior" }, + { 23, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "formian worker" }, + { 26, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "imp (devil)" }, + { 26, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "lemure (devil)" }, + { 30, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "quasit (devil)" }, + { 35, 1, 3, 0, 0.5, 2, 0, 0, 0, 0, "lantern archon (celestial)" }, + { 40, 1, 3, 0, 0.0, 0, raceANY_DMG, classANY_PC, 2, 2, 0 }, + { 45, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "carrion crawler" }, + { 50, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "mimic" }, + { 55, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "rust monster" }, + { 60, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "violet fungi" }, + { 62, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "bugbear" }, + { 62, 1, 6, 3, 0.0, 0, 0, 0, 0, 0, "hobgoblin" }, + { 65, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ettercap" }, + { 67, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "gnoll" }, + { 67, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "hyena [treat as wolf (animal)]" }, + { 70, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "lizardfolk" }, + { 70, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "giant lizard (animal)" }, + { 73, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "magmin" }, + { 76, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ogre" }, + { 76, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "orc" }, + { 78, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "orc" }, + { 78, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "dire boar" }, + { 80, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "worg" }, + { 80, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "goblin" }, + { 85, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "allip" }, + { 90, 1, 1, 0, 0.5, 0, raceANY_DMG, classANY_PC, 1, 3, "ghost" }, + { 95, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "vampire spawn" }, + { 100, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "wight" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mFive[] = { + { 2, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "ant, giant soldier (vermin)" }, + { 2, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "ant, giant worker (vermin)" }, + { 5, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "dire wolverine" }, + { 9, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "ochre jelly" }, + { 11, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "snake, giant constrictor (animal)" }, + { 12, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "spiders, huge monstrous (vermin)" }, + { 15, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "spider eater" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 23, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "doppleganger" }, + { 25, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "green hag (hag)" }, + { 27, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "mephits" }, + { 30, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "wererat (lycanthrope)" }, + { 35, 1, 3, 1, 0.5, 2, 0, 0, 0, 0, "blink dog" }, + { 40, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 2, 2, 0 }, + { 43, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "cockatrice" }, + { 47, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "gibbering mouther" }, + { 50, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "grick" }, + { 52, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra, 1d3+4 heads" }, + { 55, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "nightmare" }, + { 58, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "shocker lizard" }, + { 60, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "violet fungus" }, + { 60, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "shrieker" }, + { 64, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "azer" }, + { 67, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "bugbear" }, + { 69, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ettercap" }, + { 69, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "spider, medium-size monstous (vermin)" }, + { 72, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "orge" }, + { 75, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "salamanders, small" }, + { 77, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "troglodyte" }, + { 77, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "giant lizard (animal) [immune to stench]" }, + { 80, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "worg" }, + { 85, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "ghast" }, + { 85, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "ghoul" }, + { 90, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "mummy" }, + { 95, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "skeleton, huge [giant]" }, + { 100, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "wraith" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mSix[] = { + { 2, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "digester" }, + { 4, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dire ape" }, + { 6, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dire wolf" }, + { 7, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "giant stag beetle (vermin)" }, + { 9, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "giant wasp (vermin)" }, + { 12, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "owlbear" }, + { 15, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "shambling mound" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 22, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "annis (hag)" }, + { 25, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "harpy" }, + { 26, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "quasit (demon)" }, + { 26, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "dretch (demon)" }, + { 28, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "wereboar (lycanthrope)" }, + { 30, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "werewolf (lycanthrope)" }, + { 35, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "werebear (lycanthrope)" }, + { 40, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 3, 3, 0 }, + { 43, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "arrowhawk, small" }, + { 46, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "basilisk" }, + { 50, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "displacer beast" }, + { 53, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "gargoyle" }, + { 56, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "hell hound" }, + { 59, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "howler" }, + { 62, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "otyughs" }, + { 65, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "ravid" }, + { 65, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "animated object, large" }, + { 67, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "xorn, small" }, + { 70, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "yeth hounds" }, + { 77, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ettin" }, + { 77, 1, 6, 3, 0.0, 0, 0, 0, 0, 0, "orc" }, + { 82, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "ogre" }, + { 82, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "boar (animal)" }, + { 90, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "weretiger (lycanthrope)" }, + { 100, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "zombie, huge [giant]" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mSeven[] = { + { 4, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "black pudding (ooze)" }, + { 5, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "centipede, gargantuan monstrous (vermin)" }, + { 8, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "criosphinx (sphinx)" }, + { 10, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dire boar" }, + { 14, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "remorhaz" }, + { 15, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "scorpion, huge monstrous (vermin)" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 22, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "aranea" }, + { 24, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "barghest, medium-size" }, + { 26, 1, 3, 0, 0.8, 3, 0, 0, 0, 0, "djinn" }, + { 28, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "formian taskmaster" }, + { 28, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "minotaur" }, + { 30, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "jann (genie)" }, + { 35, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "hound archon (celestial)" }, + { 40, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 4, 4, 0 }, + { 45, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "cloaker" }, + { 48, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "cryohydra, 1d3+4 heads (hydra)" }, + { 52, 1, 4, 2, 0.8, 3, 0, 0, 0, 0, "formian warrior" }, + { 57, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "invisible stalker" }, + { 60, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "pyrohydra, 1d3+4 heads (hydra)" }, + { 65, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "bugbear" }, + { 65, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "wolf" }, + { 70, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ettin" }, + { 70, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "brown bear (animal)" }, + { 75, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "minotaur" }, + { 80, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "salamander, medium-size" }, + { 80, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "salamander, small" }, + { 90, 1, 1, 0, 0.5, 0, raceANY_DMG, classANY_PC, 5, 8, "ghost" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 5, 6, "vampire" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mEight[] = { + { 3, 1, 6, 5, 0.2, 0, 0, 0, 0, 0, "ant, giant soldier (vermin)" }, + { 8, 1, 6, 5, 0.2, 0, 0, 0, 0, 0, "dire bat" }, + { 10, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "spider, gargantuan monstrous (vermin)" }, + { 20, 1, 1, 0, 0.8, 4, 0, 0, 0, 0, DRAGON }, + { 22, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "aboleth" }, + { 22, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "skum" }, + { 24, 1, 3, 1, 0.8, 3, 0, 0, 0, 0, "barghest, large" }, + { 26, 1, 2, 0, 0.8, 3, 0, 0, 0, 0, "erinyes (devil)" }, + { 28, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "medusa" }, + { 28, 1, 6, 3, 0.0, 0, 0, 0, 0, 0, "grimlock" }, + { 30, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "mind flayer" }, + { 33, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "ogre mage" }, + { 35, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "yuan-ti halfblood" }, + { 35, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "yuan-ti pureblood" }, + { 40, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, "lammasu" }, + { 45, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 5, 5, 0 }, + { 47, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "achaierai" }, + { 48, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "arrowhawk, medium-size" }, + { 50, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "girallon" }, + { 52, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "golem, flesh" }, + { 54, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "render" }, + { 56, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "hieracosphinx (sphinx)" }, + { 59, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra (1d3+7 heads)" }, + { 60, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra, lernaean (1d3+4 heads)" }, + { 62, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "phase spider" }, + { 64, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "rast" }, + { 66, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "shadow mastiff" }, + { 70, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "xorn, medium-size" }, + { 74, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "drider" }, + { 74, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "spider, large monstrous (vermin)" }, + { 78, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "ettin" }, + { 82, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "manticore" }, + { 86, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "salamander, medium-size" }, + { 90, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "troll" }, + { 100, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "spectre" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mNine[] = { + { 5, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "bulette" }, + { 10, 1, 4, 2, 0.2, 0, 0, 0, 0, 0, "dire lion" }, + { 20, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, DRAGON }, + { 21, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "bebilith (demon)" }, + { 22, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "lamia" }, + { 24, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "mind flayer" }, + { 24, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 3, 3, 0 }, + { 26, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "night hag" }, + { 28, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "ogre mage" }, + { 28, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "ogre" }, + { 30, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "rakshasa" }, + { 32, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "succubus" }, + { 33, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "xill, barbaric" }, + { 34, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "xill, civilized" }, + { 35, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "yuan-ti, abomination" }, + { 35, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "yuan-ti, halfblood" }, + { 40, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "androsphinx (sphinx)" }, + { 45, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 6, 6, 0 }, + { 47, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "behir" }, + { 49, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "belker" }, + { 50, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "cryohydra, 1d3+6 heads (hydra)" }, + { 52, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "delver" }, + { 54, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "dragon turtle" }, + { 55, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "pyrohydra, 1d3+6 heads (hydra)" }, + { 57, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "will-o'-wisp" }, + { 60, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "wyvern" }, + { 64, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "barbazu (devil)" }, + { 64, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "osyluth (devil)" }, + { 68, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "giant, hill" }, + { 68, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "dire wolf" }, + { 72, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "kyton (devil)" }, + { 76, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "osyluth (devil)" }, + { 80, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "troll" }, + { 80, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "dire boar" }, + { 90, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "bodak" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 7, 8, "vampire" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mTen[] = { + { 5, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dire bear" }, + { 15, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, DRAGON }, + { 17, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "aboleth" }, + { 19, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "athach" }, + { 21, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "formian myrmarch" }, + { 24, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "medusa" }, + { 26, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "naga, water" }, + { 28, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "night hag" }, + { 28, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "nightmare" }, + { 30, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "salamander, large" }, + { 30, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "salamander, medium-size" }, + { 32, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "yuan-ti abomination" }, + { 37, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "lillend" }, + { 47, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 7, 7, 0 }, + { 49, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "chaos beast" }, + { 51, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "chimera" }, + { 53, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "chuul" }, + { 54, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "cryohydra, lernaean, 1d4+4 heads (hydra)" }, + { 56, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "dragonne" }, + { 58, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "hellcat (devil)" }, + { 59, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra, 1d3+9 heads" }, + { 60, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "phasm" }, + { 61, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "pyrohydra, lernaean, 1d4+4 heads (hydra)" }, + { 63, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "retriever (demon)" }, + { 65, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "slaad, red" }, + { 67, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "umber hulk" }, + { 71, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "barbazu (devil)" }, + { 75, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "drider" }, + { 79, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "giant, frost" }, + { 79, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "winter wolf" }, + { 83, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "giant, stone" }, + { 83, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "dire bear" }, + { 87, 1, 3, 1, 0.8, 2, 0, 0, 0, 0, "giant, hill" }, + { 90, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, "hamatula (devil)" }, + { 90, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "barbazu (devil)" }, + { 100, 1, 1, 0, 0.5, 0, raceANY_DMG, classANY_PC, 7, 9, "ghost" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mEleven[] = { + { 5, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "dire tiger" }, + { 15, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, DRAGON }, + { 18, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, "green hag" }, + { 18, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "annis (hag)" }, + { 18, 1, 1, 0, 0.0, 0, 0, 0, 0, 0, "sea hag" }, + { 18, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "ogre" }, + { 18, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "giant, hill" }, + { 21, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "efreet" }, + { 24, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, "formian myrmarch" }, + { 24, 1, 6, 3, 0.0, 0, 0, 0, 0, 0, "formian warrior" }, + { 27, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "gynosphinx (sphinx)" }, + { 30, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "naga, dark" }, + { 35, 1, 3, 0, 0.8, 1, 0, 0, 0, 0, "avoral guardian (celestial)" }, + { 45, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 8, 8, 0 }, + { 48, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "arrowhawk, large" }, + { 51, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "destrachan" }, + { 54, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "golem, clay" }, + { 57, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "gorgon" }, + { 59, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra, lernaean, 1d3+7 heads" }, + { 62, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "slaad, blue" }, + { 65, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "xorn, large" }, + { 70, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, "giant, fire" }, + { 70, 1, 6, 3, 0.0, 0, 0, 0, 0, 0, "hell hound" }, + { 75, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "giant, stone" }, + { 80, 1, 3, 1, 0.8, 1, 0, 0, 0, 0, "hamatula (devil)" }, + { 90, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "devourer" }, + { 100, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "mohrg" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mTwelve[] = { + { 4, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "purple worm" }, + { 5, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "scorpion, colossal monstrous (vermin)" }, + { 15, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, DRAGON }, + { 20, 1, 4, 2, 0.5, 1, 0, 0, 0, 0, "mind flayer (inquisition)" }, + { 25, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "naga, spirit" }, + { 30, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "slaad, green" }, + { 35, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "giant, cloud [good]" }, + { 35, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "dire lion" }, + { 50, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 9, 9, 0 }, + { 55, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "cryohydra, 1d3+9 heads (hydra)" }, + { 60, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "golem, stone" }, + { 65, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "pyrohydra, 1d3+9 heads (hydra)" }, + { 70, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "yrthak" }, + { 75, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "cornugon (devil)" }, + { 75, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "hamatula (devil)" }, + { 80, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "giant, cloud [evil]" }, + { 80, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "dire lion" }, + { 85, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "giant, frost" }, + { 90, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "salamander, large" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 9, 11, "vampire" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mThirteen[] = { + { 15, 1, 1, 0, 0.8, 3, 0, 0, 0, 0, DRAGON }, + { 20, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "beholder" }, + { 30, 3, 1, 0, 0.5, 1, 0, 0, 0, 0, "night hag" }, + { 30, 3, 1, 0, 0.0, 0, 0, 0, 0, 0, "nightmare" }, + { 35, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "slaad, gray" }, + { 40, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "couatl" }, + { 45, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "naga, guardian" }, + { 60, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 10, 10, 0 }, + { 67, 1, 2, 0, 0.2, 0, 0, 0, 0, 0, "frost worm" }, + { 73, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "hydra, lernaean, 1d3+9 heads" }, + { 80, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "roper" }, + { 90, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "cornugon (devil)" }, + { 100, 1, 1, 0, 0.5, 0, raceANY_DMG, classANY_PC, 10, 13, "ghost" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mFourteen[] = { + { 15, 1, 1, 0, 0.8, 2, 0, 0, 0, 0, DRAGON }, + { 25, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "beholder" }, + { 25, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 8, 8, "charmed" }, + { 35, 1, 2, 0, 0.5, 1, 0, 0, 0, 0, "slaad, death" }, + { 40, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "giant, cloud [good]" }, + { 55, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 11, 11, 0 }, + { 60, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "cryohydra, lernaean, 1d4+8 heads (hydra)" }, + { 65, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "golem, iron" }, + { 70, 1, 1, 0, 0.2, 0, 0, 0, 0, 0, "pyrohydra, lernaean, 1d4+8 heads (hydra)" }, + { 80, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "giant, cloud [evil]" }, + { 90, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "giant, storm" }, + { 90, 1, 4, 2, 0.0, 0, 0, 0, 0, 0, "griffon" }, + { 91, 1, 1, 0, 0.0, 0, raceANY_DMG, pcCLERIC, 11, 13, "lich" }, + { 94, 1, 1, 0, 0.0, 0, raceANY_DMG, pcSORCERER, 11, 13, "lich" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, pcWIZARD, 11, 13, "lich" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mFifteen[] = { + { 20, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, DRAGON }, + { 30, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "beholder" }, + { 40, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "slaad, death" }, + { 40, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "slaad, green" }, + { 45, 1, 3, 0, 0.8, 0, 0, 0, 0, 0, "ghaele (celestial)" }, + { 70, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 12, 12, 0 }, + { 80, 1, 2, 0, 0.5, 0, 0, 0, 0, 0, "hezrous" }, + { 90, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "gelugon (devil)" }, + { 90, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "cornugon (devil)" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 12, 14, "vampire" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mSixteen[] = { + { 20, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, DRAGON }, + { 30, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "pit fiend (devil)" }, + { 35, 1, 3, 0, 0.5, 1, 0, 0, 0, 0, "astral deva (celestial)" }, + { 60, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 13, 13, 0 }, + { 70, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "gelugon (devil)" }, + { 80, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "giant, storm" }, + { 90, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "vrock (demon)" }, + { 100, 1, 1, 0, 0.2, 0, raceANY_DMG, classANY_PC, 13, 15, "ghost" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mSeventeen[] = { + { 20, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, DRAGON }, + { 30, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "marilith (demon)" }, + { 35, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "trumpet archon (celestial)" }, + { 60, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 14, 14, 0 }, + { 70, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "glabrezu (demon)" }, + { 80, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "hezrou (demon)" }, + { 81, 1, 1, 0, 0.0, 0, raceANY_DMG, pcCLERIC, 14, 16, "lich" }, + { 84, 1, 1, 0, 0.0, 0, raceANY_DMG, pcSORCERER, 14, 16, "lich" }, + { 90, 1, 1, 0, 0.0, 0, raceANY_DMG, pcWIZARD, 14, 16, "lich" }, + { 100, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "nightwing (nightshade)" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mEighteen[] = { + { 20, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, DRAGON }, + { 30, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "balor (demon)" }, + { 40, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "pit fiend (devil)" }, + { 40, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "gelugon (devil)" }, + { 45, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "planetar (celestial)" }, + { 70, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 15, 15, 0 }, + { 80, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "glabrezu (demon)" }, + { 90, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 15, 17, "vampire" }, + { 100, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "nightwalker (nightshade)" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mNineteen[] = { + { 20, 1, 1, 0, 0.8, 1, 0, 0, 0, 0, DRAGON }, + { 30, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "marilith (demon)" }, + { 30, 1, 3, 0, 0.0, 0, 0, 0, 0, 0, "glabrezu (demon)" }, + { 40, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "pit fiend (devil)" }, + { 45, 1, 1, 0, 0.5, 1, 0, 0, 0, 0, "solar (celestial)" }, + { 70, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 16, 16, 0 }, + { 80, 1, 3, 1, 0.5, 1, 0, 0, 0, 0, "nalfeshnee (demon)" }, + { 90, 1, 1, 0, 0.2, 0, raceANY_DMG, classANY_PC, 16, 18, "ghost" }, + { 100, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "nightcrawler (nightshade)" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + +DUNGEONMONSTERS mTwenty[] = { + { 20, 1, 1, 0, 0.8, 0, 0, 0, 0, 0, DRAGON }, + { 30, 1, 3, 0, 0.5, 0, 0, 0, 0, 0, "balor (demon)" }, + { 40, 1, 3, 1, 0.5, 0, 0, 0, 0, 0, "marilith (demon)" }, + { 45, 1, 1, 0, 0.5, 0, 0, 0, 0, 0, "solar (celestial)" }, + { 45, 1, 2, 0, 0.0, 0, 0, 0, 0, 0, "planetar (celestial)" }, + { 55, 1, 3, 1, 0.0, 0, raceANY_DMG, classANY_PC, 17, 17, 0 }, + { 60, 1, 3, 0, 0.0, 0, raceANY_DMG, classANY_PC, 18, 18, 0 }, + { 65, 1, 2, 0, 0.0, 0, raceANY_DMG, classANY_PC, 19, 19, 0 }, + { 70, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 20, 20, 0 }, + { 80, 1, 3, 1, 0.2, 0, 0, 0, 0, 0, "nalfeshnee (demon)" }, + { 80, 1, 3, 1, 0.0, 0, 0, 0, 0, 0, "hezrou (demon)" }, + { 85, 1, 1, 0, 0.2, 0, raceANY_DMG, classANY_PC, 19, 20, "ghost" }, + { 86, 1, 1, 0, 0.0, 0, raceANY_DMG, pcCLERIC, 17, 20, "lich" }, + { 88, 1, 1, 0, 0.0, 0, raceANY_DMG, pcSORCERER, 17, 20, "lich" }, + { 90, 1, 1, 0, 0.0, 0, raceANY_DMG, pcWIZARD, 17, 20, "lich" }, + { 95, 1, 3, 0, 0.2, 0, 0, 0, 0, 0, "nightcrawler (nightshade)" }, + { 100, 1, 1, 0, 0.0, 0, raceANY_DMG, classANY_PC, 18, 20, "lich" }, + { 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0, 0 } +}; + + +DUNGEONMONSTERS* s_monsterTable[] = { + 0, mOne, mTwo, mThree, mFour, mFive, mSix, mSeven, mEight, mNine, mTen, + mEleven, mTwelve, mThirteen, mFourteen, mFifteen, mSixteen, mSeventeen, + mEighteen, mNineteen, mTwenty +}; + +/* dragon ages */ + +#define daWYRMLING "wyrmling" +#define daVERYYOUNG "very young" +#define daYOUNG "young" +#define daJUVENILE "juvenile" +#define daYOUNGADULT "young adult" +#define daADULT "adult" +#define daMATUREADULT "mature adult" +#define daOLD "old" +#define daVERYOLD "very old" +#define daANCIENT "ancient" +#define daWYRM "wyrm" +#define daGREATWYRM "great wyrm" + +/* random dragon table, by percentage chance of that dragon occuring. See + * the DMG for a fuller description of this table */ + +static struct { + int dtop; + char* dragonType; + char* age[21]; +} s_dragonTable[] = { + { 16, "white", { 0, daWYRMLING, daVERYYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daOLD, daVERYOLD, daVERYOLD, daANCIENT, daWYRM, daGREATWYRM, daGREATWYRM } }, + { 32, "black", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD, daVERYOLD, daANCIENT, daWYRM, daGREATWYRM } }, + { 48, "green", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD, daANCIENT, daANCIENT } }, + { 64, "blue", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD, daANCIENT, daANCIENT } }, + { 80, "red", { 0, daWYRMLING, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD } }, + { 84, "brass", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD, daVERYOLD, daANCIENT, daWYRM } }, + { 88, "copper", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daOLD, daVERYOLD, daANCIENT } }, + { 91, "bronze", { 0, daWYRMLING, daWYRMLING, daVERYYOUNG, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD, daANCIENT } }, + { 96, "silver", { 0, daWYRMLING, daWYRMLING, daWYRMLING, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD, daVERYOLD } }, + { 100, "gold", { 0, daWYRMLING, daWYRMLING, daWYRMLING, daWYRMLING, daVERYYOUNG, daVERYYOUNG, daYOUNG, daYOUNG, daJUVENILE, daJUVENILE, daYOUNGADULT, daYOUNGADULT, daYOUNGADULT, daADULT, daADULT, daMATUREADULT, daMATUREADULT, daMATUREADULT, daOLD, daOLD } }, + { 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } +}; + +typedef int (*CONTENTHANDLER)( JBDungeonRoomDatum*, int ); + +/* "handlers" to be used in generating room descriptions */ + +int monsterHandler( JBDungeonRoomDatum* room, int level ); +int featureHandler( JBDungeonRoomDatum* room, int level ); +int trapHandler( JBDungeonRoomDatum* room, int level ); +int treasureHandler( JBDungeonRoomDatum* room, int level ); + +/* handlers to use to determine the contents of a given room */ + +static struct { + int type; + CONTENTHANDLER handler; +} s_contentHandlers[] = { + { MONSTER, monsterHandler }, + { FEATURE, featureHandler }, + { TREASURE, treasureHandler }, + { TRAP, trapHandler }, + { 0, 0 } +}; + + +void JBDungeonWallDatum::getDatumDescription( char* desc ) { + int comma; + int i; + + comma = 0; + *desc = 0; + + if( descriptor != 0 ) { + strcat( desc, descriptor ); + strcat( desc, " " ); + } + + for( i = 0; s_flagDescriptions[ i ].desc != 0; i++ ) { + if( ( type & s_flagDescriptions[ i ].flag ) != 0 ) { + if( comma ) { + strcat( desc, ", " ); + } + strcat( desc, s_flagDescriptions[ i ].desc ); + comma = 1; + } + } + + if( trap != 0 ) { + strcat( desc, " [trap: " ); + strcat( desc, trap ); + strcat( desc, "]" ); + } +} + + +JBDungeonRoomDatum::~JBDungeonRoomDatum() { + int i; + + if( monsterCount > 0 ) { + for( i = 0; i < monsterCount; i++ ) { + delete[] monsters[ i ]; + } + delete[] monsters; + } + + if( featureCount > 0 ) { + for( i = 0; i < featureCount; i++ ) { + delete[] features[ i ]; + } + delete[] features; + } + + cleanupTreasure( &treasure ); + cleanupTreasure( &monsterTreasure ); + + if( trap != 0 ) { + delete[] trap; + } +} + + +void JBDungeonRoomDatum::addMonster( char* desc ) { + char** temp; + int len; + + temp = new char*[ monsterCount + 1 ]; + if( monsterCount > 0 ) { + memcpy( temp, monsters, sizeof( char* ) * monsterCount ); + delete[] monsters; + } + monsters = temp; + + len = strlen( desc ) + 1; + monsters[ monsterCount ] = new char[ len ]; + strcpy( monsters[ monsterCount ], desc ); + + monsterCount++; +} + + +void JBDungeonRoomDatum::addFeature( char* desc ) { + char** temp; + int len; + + temp = new char*[ featureCount + 1 ]; + if( featureCount > 0 ) { + memcpy( temp, features, sizeof( char* ) * featureCount ); + delete[] features; + } + features = temp; + + len = strlen( desc ) + 1; + features[ featureCount ] = new char[ len ]; + strcpy( features[ featureCount ], desc ); + + featureCount++; +} + + +void JBDungeonRoomDatum::getDatumDescription( char* desc ) { + int i; + char temp[4096]; + TREASUREITEM* n; + int output; + + output = 0; + + *desc = 0; + if( monsterCount > 0 ) { + output = 1; + strcat( desc, "$" DUNGEON_DATA_DELIM ); + strcat( desc, "!Monsters" DUNGEON_DATA_DELIM ); + + for( i = 0; i < monsterCount; i++ ) { + strcat( desc, monsters[ i ] ); + strcat( desc, DUNGEON_DATA_DELIM ); + } + + if( monsterTreasure.treasureList != 0 ) { + strcat( desc, "$" DUNGEON_DATA_DELIM ); + strcat( desc, "!Treasure" DUNGEON_DATA_DELIM ); + for( n = monsterTreasure.treasureList; n != 0; n = n->next ) { + strcat( desc, n->desc ); + sprintf( temp, " (%d gp)", n->value/100 ); + strcat( desc, temp ); + strcat( desc, DUNGEON_DATA_DELIM ); + } + } + } + + if( featureCount > 0 ) { + output = 1; + strcat( desc, "$" DUNGEON_DATA_DELIM ); + strcat( desc, "!Features" DUNGEON_DATA_DELIM ); + + for( i = 0; i < featureCount; i++ ) { + strcat( desc, features[ i ] ); + strcat( desc, DUNGEON_DATA_DELIM ); + } + } + + if( treasure.treasureList != 0 ) { + output = 1; + strcat( desc, "$" DUNGEON_DATA_DELIM ); + sprintf( temp, "!Hidden Treasure (Search DC %d)" DUNGEON_DATA_DELIM, 20 + dungeonLevel ); + strcat( desc, temp ); + + for( n = treasure.treasureList; n != 0; n = n->next ) { + sprintf( temp, "%s (%d gp)" DUNGEON_DATA_DELIM, n->desc, n->value/100 ); + strcat( desc, temp ); + } + } + + if( trap != 0 ) { + output = 1; + strcat( desc, "$" DUNGEON_DATA_DELIM ); + strcat( desc, "!Trap" DUNGEON_DATA_DELIM ); + strcat( desc, trap ); + strcat( desc, DUNGEON_DATA_DELIM ); + } + + if( !output ) { + strcat( desc, "$" DUNGEON_DATA_DELIM ); + strcat( desc, "Empty" DUNGEON_DATA_DELIM ); + } +} + + +JBDungeonDescription::JBDungeonDescription( JBDungeon* dungeon, int level ) { + m_describeRooms( dungeon, level ); +} + + +JBDungeonDescription::~JBDungeonDescription() { +} + + +void JBDungeonDescription::m_describeRooms( JBDungeon* dungeon, int level ) { + int count; + int i; + + count = dungeon->getRoomCount(); + for( i = 0; i < count; i++ ) { + m_describeRoom( dungeon, dungeon->getRoom( i ), level ); + } +} + + +char* getRandomTrap( int level, int forDoor ) { + TRAPLIST* list; + int d; + int i; + int valid; + + static char buffer[ 256 ]; + + buffer[0] = 0; + + if( level <= 3 ) { + list = traps1; + } else { + list = traps2; + } + + do { + valid = 1; + d = rollDice( 1, 100 ); + for( i = 0; list[ i ].dtop != 0; i++ ) { + if( d <= list[ i ].dtop ) { + if( !(list[ i ].forDoor) && ( forDoor ) ) { + valid = 0; + break; + } + + if( list[ i ].trap == SPELLTRAP ) { + d = rollDice( 1, 100 ); + for( i = 0; s_spellsForTraps[ i ].dtop != 0; i++ ) { + if( d <= s_spellsForTraps[ i ].dtop ) { + char* name; + + name = dndGetSpellName( s_spellsForTraps[ i ].spell ); + + sprintf( buffer, "spell (%s)", name ); + break; + } + } + } else { + strcpy( buffer, list[ i ].trap ); + } + break; + } + } + } while( !valid ); + + sprintf( &(buffer[strlen(buffer)]), " (Find/Disable DC %d)", 20+level ); + + return buffer; +} + + +long getDoorType( int allowReroll, DOORTYPE* doors ) { + int d; + int i; + int valid; + long value = 0; + + do { + d = rollDice( 1, 100 ); + valid = 0; + for( i = 0; doors[ i ].dtop != 0; i++ ) { + if( d <= doors[ i ].dtop ) { + value = doors[ i ].data; + if( ( value & REROLL_ONCE ) != 0 ) { + if( allowReroll ) { + value = ( value | getDoorType( 0, doors ) ) & ~REROLL_ONCE; + valid = 1; + } + } else { + valid = 1; + } + break; + } + } + } while( !valid ); + + return value; +} + + +void JBDungeonDescription::m_describeRoom( JBDungeon* dungeon, JBDungeonRoom* room, int level ) { + int i; + int d; + int j; + JBDungeonRoomDatum *rdatum; + char* trap; + long data; + + rdatum = new JBDungeonRoomDatum(); + rdatum->dungeonLevel = level; + rdatum->dataPath = dungeon->getDataPath(); + + room->data = rdatum; + + for( i = 0; i < room->wallCount; i++ ) { + if( room->walls[ i ]->type == JBDungeonWall::c_WALL ) { + } else if( room->walls[ i ]->type == JBDungeonWall::c_DOOR ) { + if( room->walls[ i ]->data == 0 ) { + data = getDoorType( 1, s_doorTypes ); + trap = ( ( data & dtTRAPPED ) != 0 ? getRandomTrap( level, 1 ) : 0 ); + room->walls[ i ]->data = new JBDungeonWallDatum( data, trap, 0 ); + } + } else if( room->walls[ i ]->type == JBDungeonWall::c_SECRETDOOR ) { + if( room->walls[ i ]->data == 0 ) { + data = getDoorType( 1, s_secretDoorTypes ); + trap = ( ( data & dtTRAPPED ) != 0 ? getRandomTrap( level, 1 ) : 0 ); + room->walls[ i ]->data = new JBDungeonWallDatum( data, trap, "(secret)" ); + } + } else if( room->walls[ i ]->type == JBDungeonWall::c_CONCEALEDDOOR ) { + if( room->walls[ i ]->data == 0 ) { + if( rand() % 3 != 0 ) { + data = getDoorType( 1, s_doorTypes ); + if( rand() % 2 == 0 ) { + data |= cdtBEHINDRUBBISH; + } else { + data |= cdtBEHINDTAPESTRY; + } + } else { + data = getDoorType( 1, s_concealedDoorTypes ); + } + + trap = ( ( data & dtTRAPPED ) != 0 ? getRandomTrap( level, 1 ) : 0 ); + room->walls[ i ]->data = new JBDungeonWallDatum( data, trap, "(concealed)" ); + } + } + } + + d = rollDice( 1, 100 ); + for( i = 0; s_roomContents[ i ].dtop != 0; i++ ) { + if( d <= s_roomContents[ i ].dtop ) { + for( j = 0; s_contentHandlers[ j ].type != 0; j++ ) { + if( ( s_contentHandlers[ j ].type & s_roomContents[ i ].contents ) != 0 ) { + s_contentHandlers[ j ].handler( rdatum, level ); + } + } + break; + } + } +} + + +char* getRandomDragon( int level ) { + int i; + int d; + static char buffer[128]; + + d = rollDice( 1, 100 ); + for( i = 0; s_dragonTable[ i ].dtop != 0; i++ ) { + if( d <= s_dragonTable[ i ].dtop ) { + strcpy( buffer, "~B" ); + strcat( buffer, s_dragonTable[ i ].age[ level ] ); + strcat( buffer, " " ); + strcat( buffer, s_dragonTable[ i ].dragonType ); + strcat( buffer, " " ); + strcat( buffer, "dragon" ); + strcat( buffer, "~b" ); + return buffer; + } + } + + return ""; +} + + +int monsterHandler( JBDungeonRoomDatum* room, int level ) { + DUNGEONLEVEL* masterData; + DUNGEONMONSTERS* monsters; + int i; + int d; + int lvl; + int mul; + int div; + int k; + int x; + int y; + char buffer[512]; + + masterData = s_masterMonsterTable[ level ]; + d = rollDice( 1, 100 ); + for( i = 0; masterData[i].dtop != 0; i++ ) { + if( d <= masterData[ i ].dtop ) { + lvl = masterData[ i ].level; + mul = masterData[ i ].mul; + div = masterData[ i ].div; + + monsters = s_monsterTable[ lvl ]; + d = rollDice( 1, 100 ); + for( i = 0; monsters[ i ].dtop != 0; i++ ) { + if( d <= monsters[ i ].dtop ) { + k = monsters[ i ].dtop; + while( k == monsters[ i ].dtop ) { + x = rollDice( monsters[ i ].dcount, monsters[ i ].dtype ) + monsters[ i ].dmod; + x = ( x * mul ) / div; + if( x < 1 ) { + x = 1; + } + + if( monsters[ i ].monster == DRAGON ) { + /* if the monster is a dragon, add a random dragon based on the + * modified level of the monster to be generated. */ + + room->addMonster( getRandomDragon( lvl ) ); + + } else if( monsters[ i ].npcMinLevel > 0 ) { + /* if the monster is an NPC, use the NPC generator engine to + * calculate the NPC's stats. */ + + NPCGENERATOROPTS opts; + NPCGENERATOROPTS opts2; + NPCSTATBLOCKOPTS statOpts; + NPC* npc; + char buffer[4096]; + + /* set the parameters for stat-block output */ + memset( &statOpts, 0, sizeof( statOpts ) ); + statOpts.acBreakdown = 1; + statOpts.initBreakdown = 1; + statOpts.languages = 1; + statOpts.skillsAndFeats = 1; + statOpts.possessions = 1; + statOpts.spells = 1; + statOpts.richFormatting = 1; + + /* set the parameters for NPC generation */ + memset( &opts, 0, sizeof( opts ) ); + opts.raceType = npcGetRandomRace( monsters[ i ].npcRace ); + opts.classType[0] = monsters[ i ].npcClass; + opts.level[0] = selectBetween( monsters[ i ].npcMinLevel, monsters[ i ].npcMaxLevel ); + opts.filePath = room->dataPath; + opts.abilityScoreStrategy = npcAbScoreStrategy_BestOf4d6; + + for( y = 0; y < x; y++ ) { + memcpy( &opts2, &opts, sizeof( opts2 ) ); + + npc = npcGenerateNPC( &opts2 ); + + /* if there is a further description indicated (ghost, lich, etc.), + * prepend it to the NPC description. */ + + buffer[0] = 0; + if( monsters[ i ].monster != 0 ) { + strcpy( buffer, "(" ); + strcat( buffer, monsters[ i ].monster ); + strcat( buffer, ") " ); + } + + npcBuildStatBlock( npc, &statOpts, &(buffer[strlen(buffer)]), sizeof( buffer ) ); + strcat( buffer, "~n" ); + + room->addMonster( buffer ); + + npcDestroyNPC( npc ); + } + } else { + /* otherwise, it's exactly what the description says it is */ + + sprintf( buffer, "~B%s~b (%d)", monsters[ i ].monster, x ); + room->addMonster( buffer ); + } + + /* if the monster has treasure, compute it using the treasure + * generator engine. */ + if( monsters[ i ].treasureMod > 0 ) { + float mods[3]; + + mods[0] = mods[1] = mods[2] = (float)monsters[ i ].treasureMod; + generateRandomTreasureEx( &(room->monsterTreasure), lvl+monsters[i].levelMod-1, mods ); + } + + i++; + } + break; + } + } + break; + } + } + + return 0; +} + + +int featureHandler( JBDungeonRoomDatum* room, int level ) { + FEATURELIST* lists[2]; + int i; + int d; + int j; + int k; + + lists[0] = s_minorFeatures; + lists[1] = s_majorFeatures; + for( i = 0; i < 2; i++ ) { + for( j = rollDice( 1, 4 ); j > 0; j-- ) { + d = rollDice( 1, 100 ); + for( k = 0; lists[ i ][ k ].dtop != 0; k++ ) { + if( d <= lists[ i ][ k ].dtop ) { + room->addFeature( lists[ i ][ k ].feature ); + break; + } + } + } + } + + return 0; +} + + +int trapHandler( JBDungeonRoomDatum* room, int level ) { + char* trap; + + trap = getRandomTrap( level, 0 ); + + room->trap = new char[ strlen(trap) + 1 ]; + strcpy( room->trap, trap ); + + return 0; +} + + +int treasureHandler( JBDungeonRoomDatum* room, int level ) { + do { + /* if there is hidden treasure, use the treasure generator engine to + * compute the actual contents of the treasure. */ + + generateRandomTreasure( &(room->treasure), level-1 ); + } while( room->treasure.treasureList == 0 ); + return 0; +} diff --git a/src/jbdungeonpainter.cpp b/src/jbdungeonpainter.cpp new file mode 100644 index 0000000..f7a9b10 --- /dev/null +++ b/src/jbdungeonpainter.cpp @@ -0,0 +1,209 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonPainter + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * ---------------------------------------------------------------------- */ + +#include +#include + +#include "jbdungeon.h" +#include "jbdungeonpainter.h" + +void JBDungeonPainter::paint() { + long wallClr; + long bgColor; + long pathClr; + long roomClr; + long gridClr; + + int i; + int j; + int ofs; + int dir; + int xSize; + int ySize; + int wall; + + JBDungeonRoom* room; + + ofs = m_border; + + /* compute the size of the canvas we have to work with */ + + xSize = getCanvasWidth(); + ySize = getCanvasHeight(); + + /* allocate the colors we need to use */ + /* FIXME: this should be customizable! */ + + bgColor = m_allocateColor( 255, 255, 255 ); + wallClr = m_allocateColor( 128, 128, 128 ); + pathClr = m_allocateColor( 255, 0, 0 ); + roomClr = m_allocateColor( 255, 255, 255 ); + gridClr = m_allocateColor( 0, 0, 0 ); + + /* set the background of the maze */ + + m_rectangle( 0, 0, xSize - 1, ySize - 1, bgColor, true ); + + /* draw the basic walls and floors */ + + ofs++; + for( j = 0; j < m_dungeon->getY(); j++ ) { + for( i = 0; i < m_dungeon->getX(); i++ ) { + dir = m_dungeon->getDungeonAt( i, j, 0 ); + if( dir == JBDungeon::c_WALL ) { + m_rectangle( ofs + i * m_gridSize, ofs + j * m_gridSize, + ofs + (i+1) * m_gridSize - 1, ofs + (j+1) * m_gridSize - 1, + wallClr, true ); + } else if( dir == JBDungeon::c_ROOM ) { + m_rectangle( ofs + i * m_gridSize, ofs + j * m_gridSize, + ofs + (i+1) * m_gridSize - 1, ofs + (j+1) * m_gridSize - 1, + roomClr, true ); + } + } + } + + /* draw specific walls and doors, by checking each point and the points + * to the left and below it to see if there is a wall between them. */ + + for( i = 0; i < m_dungeon->getX() - 1; i++ ) { + for( j = 0; j < m_dungeon->getY() - 1; j++ ) { + JBMazePt p1( i, j, 0 ); + JBMazePt p2( i, j+1, 0 ); + JBMazePt p3( i+1, j, 0 ); + + wall = m_dungeon->getWallBetween( p1, p2 ); /* south wall */ + if( wall != JBDungeonWall::c_NONE ) { + m_rectangle( ofs + i * m_gridSize, ofs + (j+1) * m_gridSize - 1, + ofs + (i+1)*m_gridSize, ofs + (j+1) * m_gridSize + 1, + gridClr, true ); + m_drawDoor( ofs + i * m_gridSize, ofs + j*m_gridSize, wall, 1, gridClr ); + } + wall = m_dungeon->getWallBetween( p1, p3 ); /* east wall */ + if( wall != JBDungeonWall::c_NONE ) { + m_rectangle( ofs + (i+1) * m_gridSize - 1, ofs + j * m_gridSize, + ofs + (i+1) * m_gridSize + 1, ofs + (j+1) * m_gridSize, + gridClr, true ); + m_drawDoor( ofs + i * m_gridSize, ofs + j*m_gridSize, wall, 0, gridClr ); + } + } + } + + /* overlay a grid on top */ + + for( i = 0; i < m_dungeon->getX() + 1; i++ ) { + j = ofs + i * m_gridSize; + m_line( j, ofs, j, ySize-ofs, gridClr ); + } + for( i = 0; i < m_dungeon->getY() + 1; i++ ) { + j = ofs + i * m_gridSize; + m_line( ofs, j, xSize-ofs, j, gridClr ); + } + + /* lastly, number the rooms */ + + for( i = 0; ( room = m_dungeon->getRoom( i ) ) != 0; i++ ) { + int x; + int y; + + x = (int)( ofs + m_gridSize * ( room->topLeft.x + room->size.x / 2.0 ) ); + y = (int)( ofs + m_gridSize * ( room->topLeft.y + room->size.y / 2.0 ) ); + + m_centerNumberAt( x, y, i+1, (int)( m_gridSize * 0.8 ), gridClr ); + } +} + + +int JBDungeonPainter::getCanvasWidth() { + return m_border*2 + m_gridSize * m_dungeon->getX() + 1; +} + + +int JBDungeonPainter::getCanvasHeight() { + return m_border*2 + m_gridSize * m_dungeon->getY() + 1; +} + + +void JBDungeonPainter::m_centerNumberAt( int x, int y, int num, int maxWidth, long color ) { + char buffer[10]; + int length; + int cx; + int cy; + void* font; + + sprintf( buffer, "%d", num ); + length = strlen( buffer ); + + font = m_selectFontToFit( buffer, maxWidth ); + if( font == 0 ) { + return; + } + + length = getFontWidth( font ) * length; + cx = x - length / 2; + cy = y - getFontHeight( font ) / 2; + + m_string( cx, cy, buffer, color, font ); +} + + +void JBDungeonPainter::m_drawDoor( int x, int y, int doorType, int horiz, long color ) { + char c[2]; + void* font; + + if( horiz ) { + if( doorType == JBDungeonWall::c_DOOR ) { + m_rectangle( x + m_gridSize/4, y + m_gridSize - m_gridSize/5, + x + m_gridSize - m_gridSize/4, y + m_gridSize + m_gridSize/5, + color ); + } else { + if( doorType == JBDungeonWall::c_SECRETDOOR ) { + c[0] = 'S'; + } else if( doorType == JBDungeonWall::c_CONCEALEDDOOR ) { + c[0] = 'C'; + } else { + return; + } + c[1] = 0; + font = m_selectFontToFit( c, m_gridSize ); + if( font == 0 ) { + return; + } + + x = x + m_gridSize/2 - getFontHeight( font ) / 2; + y = y + m_gridSize + getFontWidth( font) / 2; + + m_charUp( x, y, c[0], color, font ); + } + } else { + if( doorType == JBDungeonWall::c_DOOR ) { + m_rectangle( x + m_gridSize - m_gridSize/5, y + m_gridSize/4, + x + m_gridSize + m_gridSize/5, y + m_gridSize - m_gridSize/4, + color ); + } else { + if( doorType == JBDungeonWall::c_SECRETDOOR ) { + c[0] = 'S'; + } else if( doorType == JBDungeonWall::c_CONCEALEDDOOR ) { + c[0] = 'C'; + } else { + return; + } + c[1] = 0; + font = m_selectFontToFit( c, m_gridSize ); + if( font == 0 ) { + return; + } + + x = x + m_gridSize - getFontWidth( font ) / 2; + y = y + m_gridSize/2 - getFontHeight( font ) / 2; + + m_char( x, y, c[0], color, font ); + } + } +} diff --git a/src/jbdungeonpaintergd.cpp b/src/jbdungeonpaintergd.cpp new file mode 100644 index 0000000..7eb97ec --- /dev/null +++ b/src/jbdungeonpaintergd.cpp @@ -0,0 +1,99 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBDungeonPainterGD + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * + * These routines require the existance of the GD library (available from + * http://www.boutell.com/gd/), as well as any other libraries the GD + * library requires (most notably libpng, libjpeg, and libz). + * ---------------------------------------------------------------------- */ + +#include + +#include "jbdungeon.h" +#include "jbdungeonpaintergd.h" + +#include "gdfontt.h" +#include "gdfonts.h" +#include "gdfontmb.h" +#include "gdfontl.h" +#include "gdfontg.h" + + +JBDungeonPainterGD::JBDungeonPainterGD( JBDungeon* dungeon, int gridSize, int border ) + : JBDungeonPainter( dungeon, gridSize, border ) +{ + m_image = gdImageCreate( getCanvasWidth(), getCanvasHeight() ); +} + + +JBDungeonPainterGD::~JBDungeonPainterGD() { + gdImageDestroy( m_image ); +} + + +void JBDungeonPainterGD::m_rectangle( int x1, int y1, int x2, int y2, long color, bool filled ) { + if( filled ) { + gdImageFilledRectangle( m_image, x1, y1, x2, y2, color ); + } else { + gdImageRectangle( m_image, x1, y1, x2, y2, color ); + } +} + + +void JBDungeonPainterGD::m_line( int x1, int y1, int x2, int y2, long color ) { + gdImageLine( m_image, x1, y1, x2, y2, color ); +} + + +void JBDungeonPainterGD::m_string( int x, int y, char* text, long color, void* font ) { + gdImageString( m_image, (gdFontPtr)font, x, y, (unsigned char*)text, color ); +} + + +void JBDungeonPainterGD::m_char( int x, int y, char c, long color, void* font ) { + gdImageChar( m_image, (gdFontPtr)font, x, y, c, color ); +} + + +void JBDungeonPainterGD::m_charUp( int x, int y, char c, long color, void* font ) { + gdImageCharUp( m_image, (gdFontPtr)font, x, y, c, color ); +} + + +void* JBDungeonPainterGD::m_selectFontToFit( char* text, int width ) { + gdFontPtr fonts[] = { gdFontGiant, gdFontLarge, gdFontMediumBold, gdFontSmall, gdFontTiny, 0 }; + int i; + int length; + + length = strlen( text ); + + for( i = 0; fonts[ i ] != 0; i++ ) { + if( length * fonts[ i ]->w <= width ) { + return (void*)( fonts[ i ] ); + } + } + + return (void*)( gdFontTiny ); +} + + +long JBDungeonPainterGD::m_allocateColor( int red, int green, int blue ) { + return gdImageColorAllocate( m_image, red, green, blue ); +} + + +int JBDungeonPainterGD::getFontWidth( void* font ) { + gdFontPtr f = (gdFontPtr)font; + return f->w; +} + + +int JBDungeonPainterGD::getFontHeight( void* font ) { + gdFontPtr f = (gdFontPtr)font; + return f->h; +} diff --git a/src/jbmaze.cpp b/src/jbmaze.cpp new file mode 100644 index 0000000..9b35b2a --- /dev/null +++ b/src/jbmaze.cpp @@ -0,0 +1,669 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBMaze + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * ---------------------------------------------------------------------- */ + +#include +#include +#include + +#include "jbmaze.h" + +const int JBMaze::c_NORTH = 0x0001; +const int JBMaze::c_SOUTH = 0x0002; +const int JBMaze::c_WEST = 0x0004; +const int JBMaze::c_EAST = 0x0008; +const int JBMaze::c_UP = 0x0010; +const int JBMaze::c_DOWN = 0x0020; + +const int JBMaze::c_MARK = 0x8000; + + +JBMaze::JBMaze( int x, int y, int z, long seed, int randomness, + int sx, int sy, int sz, + int ex, int ey, int ez ) +{ + int i; + int j; + + m_deadendsClosed = 1; + + m_maze = 0; + m_x = m_y = m_z = 0; + m_seed = 0; + m_randomness = 0; + + if( ( x < 1 ) || ( y < 1 ) || ( z < 1 ) ) { + return; + } + + m_x = x; + m_y = y; + m_z = z; + + m_mask = new JBMazeMask( m_x, m_y ); + + m_start.x = sx; + m_start.y = sy; + m_start.z = sz; + + m_end.x = ( ex < 0 ? m_x - 1 : ex ); + m_end.y = ( ey < 0 ? m_y - 1 : ey ); + m_end.z = ( ez < 0 ? m_z - 1 : ez ); + + if( seed > 0 ) { + m_seed = seed; + srand( m_seed ); + } + + m_randomness = randomness; + + m_maze = (int***)malloc( m_x * sizeof( int** ) ); + for( i = 0; i < x; i++ ) { + m_maze[ i ] = (int**)malloc( m_y * sizeof( int* ) ); + for( j = 0; j < y; j++ ) { + m_maze[ i ][ j ] = (int*)malloc( m_z * sizeof( int ) ); + memset( m_maze[ i ][ j ], 0, m_z * sizeof( int ) ); + } + } +} + + +JBMaze::~JBMaze() { + m_deallocateMaze(); + + m_maze = 0; + m_x = m_y = m_z = 0; + m_seed = 0; + + delete m_mask; +} + + +int JBMaze::getExitsAt( int x, int y, int z ) { + if( m_maze == 0 ) { + return 0; + } + if( ( x < 0 ) || ( y < 0 ) || ( z < 0 ) ) { + return 0; + } + if( ( x >= m_x ) || ( y >= m_y ) || ( z >= m_z ) ) { + return 0; + } + return m_maze[ x ][ y ][ z ]; +} + + +void JBMaze::solve( JBMazePt** path, + int* pathLen ) +{ + JBMaze::JBMAZE_SOLUTION* stack; + JBMaze::JBMAZE_SOLUTION* iter; + + int dirs; + int cx; + int cy; + int cz; + + *path = 0; + *pathLen = 0; + + if( ( m_maze == 0 ) || ( !m_deadendsClosed ) ) { + return; + } + + /* put our starting position on the stack */ + + cx = m_start.x; + cy = m_start.y; + cz = m_start.z; + + stack = m_push( cx, cy, cz, 0 ); + + /* loop while we have not reached the end point */ + + while( !( ( cx == m_end.x ) && ( cy == m_end.y ) && ( cz == m_end.z ) ) ) { + if( stack == 0 ) { + /* if we've run out of stack, then there is no solution to the maze. + * we have to just exit. */ + return; + } + + /* test all possible directions from the given point, recursively. + * First, try north, then south, then east, etc, until all options + * have been tried, and if none of them worked, pop the stack and try + * with the previous position. */ + + dirs = m_maze[ cx ][ cy ][ cz ] & ~( stack->directions ); + if( ( dirs & c_NORTH ) != 0 ) { + stack->directions |= c_NORTH; + if( cy > 0 ) { + cy--; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_SOUTH; + } + } else if( ( dirs & c_SOUTH ) != 0 ) { + stack->directions |= c_SOUTH; + if( cy+1 < m_y ) { + cy++; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_NORTH; + } + } else if( ( dirs & c_EAST ) != 0 ) { + stack->directions |= c_EAST; + if( cx+1 < m_x ) { + cx++; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_WEST; + } + } else if( ( dirs & c_WEST ) != 0 ) { + stack->directions |= c_WEST; + if( cx > 0 ) { + cx--; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_EAST; + } + } else if( ( dirs & c_UP ) != 0 ) { + stack->directions |= c_UP; + if( cz > 0 ) { + cz--; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_DOWN; + } + } else if( ( dirs & c_DOWN ) != 0 ) { + stack->directions |= c_DOWN; + if( cz+1 < m_z ) { + cz++; + stack = m_push( cx, cy, cz, stack ); + stack->directions |= c_UP; + } + } else { + stack = m_pop( stack ); + if( stack == 0 ) { + /* if the stack is empty, there is no solution! */ + break; + } + cx = stack->x; + cy = stack->y; + cz = stack->z; + } + } + + /* determine the number of items in the solution */ + for( iter = stack, *pathLen = 0; iter != 0; (*pathLen)++, iter = iter->previous ); + + /* allocate the solution array */ + *path = (JBMazePt*)malloc( *pathLen * sizeof( JBMazePt ) ); + + /* work backwards from the ends, populating the solution array */ + for( cx = *pathLen-1; cx >= 0; cx-- ) { + (*path)[cx].x = stack->x; + (*path)[cx].y = stack->y; + (*path)[cx].z = stack->z; + stack = m_pop( stack ); + } +} + + +void JBMaze::sparsify( int amount ) { + int i; + int x; + int y; + int z; + int dir; + + if( m_maze == 0 ) { + return; + } + + for( i = 0; i < amount; i++ ) { + for( x = 0; x < m_x; x++ ) { + for( y = 0; y < m_y; y++ ) { + for( z = 0; z < m_z; z++ ) { + + /* don't sparsify from the beginning and end points -- this guarantees + * that the solution to the maze (from solve()) will still be valid + * after calling sparsify() */ + + if( ( x == m_start.x ) && ( y == m_start.y ) && ( z == m_end.z ) ) { + continue; + } + if( ( x == m_end.x ) && ( y == m_end.y ) && ( z == m_end.z ) ) { + continue; + } + + /* if the indicated position (x,y,z) is a deadend (ie, there is + * only one direction out of it), then we "erase" the passage + * here and mark it as visited. */ + + dir = m_maze[ x ][ y ][ z ]; + switch( dir ) { + case c_NORTH: + case c_SOUTH: + case c_WEST: + case c_EAST: + case c_UP: + case c_DOWN: + break; + default: + continue; + } + + m_maze[ x ][ y ][ z ] = 0; + if( ( dir & c_NORTH ) != 0 ) { + m_maze[ x ][ y - 1 ][ z ] &= ~c_SOUTH; + m_maze[ x ][ y - 1 ][ z ] |= c_MARK; + } else if( ( dir & c_SOUTH ) != 0 ) { + m_maze[ x ][ y + 1 ][ z ] &= ~c_NORTH; + m_maze[ x ][ y + 1 ][ z ] |= c_MARK; + } else if( ( dir & c_WEST ) != 0 ) { + m_maze[ x - 1 ][ y ][ z ] &= ~c_EAST; + m_maze[ x - 1 ][ y ][ z ] |= c_MARK; + } else if( ( dir & c_EAST ) != 0 ) { + m_maze[ x + 1 ][ y ][ z ] &= ~c_WEST; + m_maze[ x + 1 ][ y ][ z ] |= c_MARK; + } else if( ( dir & c_UP ) != 0 ) { + m_maze[ x ][ y ][ z - 1 ] &= ~c_DOWN; + m_maze[ x ][ y ][ z - 1 ] |= c_MARK; + } else if( ( dir & c_DOWN ) != 0 ) { + m_maze[ x ][ y ][ z + 1 ] &= ~c_UP; + m_maze[ x ][ y ][ z + 1 ] |= c_MARK; + } + } + } + } + + /* clear the marks so we're ready to go for another pass! */ + m_clearMarks(); + } +} + + +void JBMaze::clearDeadends( int percentage ) { + int x; + int y; + int z; + int tx; + int ty; + int tz; + int cx; + int cy; + int cz; + int dir; + int rdir = 0; + int dirsTested; + + if( m_maze == 0 ) { + return; + } + + m_deadendsClosed = 0; + + for( x = 0; x < m_x; x++ ) { + for( y = 0; y < m_y; y++ ) { + for( z = 0; z < m_z; z++ ) { + dir = m_maze[ x ][ y ][ z ]; + switch( dir ) { + case c_NORTH: + case c_SOUTH: + case c_WEST: + case c_EAST: + case c_UP: + case c_DOWN: + break; + default: + continue; + } + + /* do we close this deadend, or not? */ + + if( rand() % 100 + 1 > percentage ) { + continue; + } + + /* if so, start at the dead end and randomly meander our way to + * another point, to eliminate the dead end. */ + + cx = x; + cy = y; + cz = z; + + do { + dir = 0; + dirsTested = 0; + + do { + tx = cx; + ty = cy; + tz = cz; + switch( rand() % 6 ) { + case 0: if( cy > 0 ) { dir = c_NORTH; rdir = c_SOUTH; ty--; } + else { dirsTested |= c_NORTH; } + break; + case 1: if( cy+1 < m_y ) { dir = c_SOUTH; rdir = c_NORTH; ty++; } + else { dirsTested |= c_SOUTH; } + break; + case 2: if( cx > 0 ) { dir = c_WEST; rdir = c_EAST; tx--; } + else { dirsTested |= c_WEST; } + break; + case 3: if( cx+1 < m_x ) { dir = c_EAST; rdir = c_WEST; tx++; } + else { dirsTested |= c_EAST; } + break; + case 4: if( cz > 0 ) { dir = c_UP; rdir = c_DOWN; tz--; } + else { dirsTested |= c_UP; } + break; + case 5: if( cz+1 < m_z ) { dir = c_DOWN; rdir = c_UP; tz++; } + else { dirsTested |= c_DOWN; } + break; + } + if( m_maze[ cx ][ cy ][ cz ] == dir ) { + dirsTested |= dir; + dir = 0; + } + if( !m_mask->getMaskAt( tx, ty ) ) { + dirsTested |= dir; + dir = 0; + } + if( dirsTested == ( c_NORTH | c_SOUTH | c_WEST | c_EAST | c_UP | c_DOWN ) ) { + break; + } + } while( dir == 0 ); + + if( dirsTested == ( c_NORTH | c_SOUTH | c_WEST | c_EAST | c_UP | c_DOWN ) ) { + break; + } + + m_maze[ cx ][ cy ][ cz ] |= dir; + m_maze[ tx ][ ty ][ tz ] |= rdir; + + cx = tx; + cy = ty; + cz = tz; + } while( m_maze[ tx ][ ty ][ tz ] == rdir ); + } + } + } +} + + +void JBMaze::generate() { + unsigned long remaining; + int x; + int y; + int z; + int tx; + int ty; + int tz; + int directions; + int direction; + int allDirections; + int doRandomSelection; + int lastDirection = 0; + int straightStretch; + + if( m_maze == 0 ) { + return; + } + + allDirections = c_NORTH | c_SOUTH | c_WEST | c_EAST | c_UP | c_DOWN; + straightStretch = 0; + + /* compute how many valid points there are in the maze */ + + remaining = 0; + for( x = 0; x < m_x; x++ ) { + for( y = 0; y < m_y; y++ ) { + remaining += m_mask->getMaskAt( x, y ); + } + } + remaining *= m_z; + remaining--; + + /* find the point at which we want to start -- make sure the point we + * pick is within the mask. */ + + directions = 0; + do { + x = rand() % m_x; + y = rand() % m_y; + z = rand() % m_z; + } while( !m_mask->getMaskAt( x, y ) ); + + /* now, for each point remaining in the maze, we loop! */ + + while( remaining > 0 ) { + if( directions == allDirections ) { + + /* if we're stuck (boxed in or otherwise), choose another point, this + * time choosing one that has already been visited. */ + + do { + x = rand() % m_x; + y = rand() % m_y; + z = rand() % m_z; + } while( m_maze[ x ][ y ][ z ] == 0 ); + directions = m_maze[ x ][ y ][ z ]; + } + + /* eliminate obviously impossible directions */ + + if( x < 1 ) directions |= c_WEST; + if( x+1 >= m_x ) directions |= c_EAST; + if( y < 1 ) directions |= c_NORTH; + if( y+1 >= m_y ) directions |= c_SOUTH; + if( z < 1 ) directions |= c_UP; + if( z+1 >= m_z ) directions |= c_DOWN; + + doRandomSelection = 0; + + if( rand() % 100 < m_randomness ) { + /* choose a direction at random */ + doRandomSelection = 1; + } else { + /* otherwise, move, based on the direction last chosen. Note that we're + * only allowing a straight stretch that is less than half as long as the + * relevant dimension of the maze. */ + + switch( lastDirection ) { + case c_NORTH: + if( ( straightStretch < ( m_y >> 1 ) ) && ( y > 0 ) && ( m_maze[ x ][ y-1 ][ z ] == 0 ) && ( m_mask->getMaskAt( x, y-1 ) ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + case c_SOUTH: + if( ( straightStretch < ( m_y >> 1 ) ) && ( y+1 < m_y ) && ( m_maze[ x ][ y+1 ][ z ] == 0 ) && ( m_mask->getMaskAt( x, y+1 ) ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + case c_WEST: + if( ( straightStretch < ( m_x >> 1 ) ) && ( x > 0 ) && ( m_maze[ x-1 ][ y ][ z ] == 0 ) && ( m_mask->getMaskAt( x-1, y ) ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + case c_EAST: + if( ( straightStretch < ( m_x >> 1 ) ) && ( x+1 < m_x ) && ( m_maze[ x+1 ][ y ][ z ] == 0 ) && ( m_mask->getMaskAt( x+1, y ) ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + case c_UP: + if( ( straightStretch < ( m_z >> 1 ) ) && ( z > 0 ) && ( m_maze[ x ][ y ][ z-1 ] == 0 ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + case c_DOWN: + if( ( straightStretch < ( m_z >> 1 ) ) && ( z+1 < m_z ) && ( m_maze[ x ][ y ][ z+1 ] == 0 ) ) { + direction = lastDirection; + } else { + doRandomSelection = 1; + } + break; + default: + doRandomSelection = 1; + } + } + + if( doRandomSelection ) { + /* reset the straight stretch count */ + + straightStretch = 0; + direction = 0; + + /* pick a random direction */ + + while( ( direction == 0 ) || ( ( directions & direction ) != 0 ) ) { + tx = x; + ty = y; + tz = z; + switch( rand() % 6 ) { + case 0: if( y > 0 ) { direction = c_NORTH; ty--; } else { directions |= c_NORTH; } break; + case 1: if( y+1 < m_y ) { direction = c_SOUTH; ty++; } else { directions |= c_SOUTH; } break; + case 2: if( x > 0 ) { direction = c_WEST; tx--; } else { directions |= c_WEST; } break; + case 3: if( x+1 < m_x ) { direction = c_EAST; tx++; } else { directions |= c_EAST; } break; + case 4: if( z > 0 ) { direction = c_UP; tz--; } else { directions |= c_UP; } break; + case 5: if( z+1 < m_z ) { direction = c_DOWN; tz++; } else { directions |= c_DOWN; } break; + } + if( ( !m_mask->getMaskAt( tx, ty ) ) || ( m_maze[ tx ][ ty ][ tz ] != 0 ) ) { + directions |= direction; + if( directions == allDirections ) { + break; + } + direction = 0; + } + } + } else { + straightStretch++; + } + + if( directions == allDirections ) { + /* if we've tested all directions, then we are stuck. Continue to the + * top of the loop, where we will select a new point to search from. */ + continue; + } + + /* set the given direction in the maze, both at the point of origin and + * the point of destination. */ + + lastDirection = direction; + m_maze[ x ][ y ][ z ] |= direction; + switch( direction ) { + case c_NORTH: y--; direction = c_SOUTH; break; + case c_SOUTH: y++; direction = c_NORTH; break; + case c_WEST: x--; direction = c_EAST; break; + case c_EAST: x++; direction = c_WEST; break; + case c_UP: z--; direction = c_DOWN; break; + case c_DOWN: z++; direction = c_UP; break; + } + m_maze[ x ][ y ][ z ] |= direction; + directions = m_maze[ x ][ y ][ z ]; + + /* decrement the number of points remaining */ + + remaining--; + } +} + + +JBMaze::JBMAZE_SOLUTION* JBMaze::m_push( int x, int y, int z, JBMAZE_SOLUTION* stack ) { + JBMaze::JBMAZE_SOLUTION* newItem; + + newItem = new JBMaze::JBMAZE_SOLUTION; + newItem->x = x; + newItem->y = y; + newItem->z = z; + newItem->directions = 0; + newItem->previous = stack; + + return newItem; +} + + +JBMaze::JBMAZE_SOLUTION* JBMaze::m_pop( JBMAZE_SOLUTION* stack ) { + JBMaze::JBMAZE_SOLUTION* next; + + next = stack->previous; + delete stack; + + return next; +} + + +void JBMaze::m_clearMarks() { + int i; + int j; + int k; + + if( m_maze == 0 ) { + return; + } + + for( i = 0; i < m_x; i++ ) { + for( j = 0; j < m_y; j++ ) { + for( k = 0; k < m_z; k++ ) { + m_maze[ i ][ j ][ k ] &= ~c_MARK; + } + } + } +} + + +void JBMaze::setMask( JBMazeMask* mask ) { + delete m_mask; + m_mask = mask; + + m_deallocateMaze(); + m_x = m_mask->getWidth(); + m_y = m_mask->getHeight(); + m_allocateMaze(); +} + + +void JBMaze::m_deallocateMaze() { + int i; + int j; + + if( m_maze != 0 ) { + for( i = 0; i < m_x; i++ ) { + for( j = 0; j < m_y; j++ ) { + delete[] m_maze[ i ][ j ]; + } + delete[] m_maze[ i ]; + } + delete[] m_maze; + } + + m_maze = 0; +} + + +void JBMaze::m_allocateMaze() { + int i; + int j; + + if( m_maze != 0 ) { + m_deallocateMaze(); + } + + m_maze = new int**[ m_x ]; + for( i = 0; i < m_x; i++ ) { + m_maze[ i ] = new int*[ m_y ]; + for( j = 0; j < m_y; j++ ) { + m_maze[ i ][ j ] = new int[ m_z ]; + memset( m_maze[ i ][ j ], 0, m_z * sizeof( int ) ); + } + } +} diff --git a/src/jbmazemask.cpp b/src/jbmazemask.cpp new file mode 100644 index 0000000..32eba67 --- /dev/null +++ b/src/jbmazemask.cpp @@ -0,0 +1,110 @@ +/* ---------------------------------------------------------------------- * + * This file is in the public domain, and may be used, modified, and + * distributed without restriction. + * ---------------------------------------------------------------------- * + * JBMazeMask + * + * Author: Jamis Buck + * Homepage: http://github.com/jamis/dnd-dungeon + * ---------------------------------------------------------------------- */ + +#include +#include +#include + +#include "jbmazemask.h" + +JBMazeMask::JBMazeMask( int width, int height ) { + int i; + + m_width = width; + m_height = height; + + m_mask = new char*[ m_width ]; + for( i = 0; i < m_width; i++ ) { + m_mask[ i ] = new char[ m_height ]; + memset( m_mask[ i ], 1, m_height ); + } +} + + +JBMazeMask::JBMazeMask( char* filename ) { + std::ifstream in( filename ); + char line[ 1024 ]; + int i; + int j; + + if( !in ) { + m_width = 0; + m_height = 0; + m_mask = 0; + } + + /* read the first line to get the width and height */ + + in >> line; + sscanf( line, "%d,%d", &m_width, &m_height ); + + /* allocate and initialize the mask array */ + + m_mask = new char*[ m_width ]; + for( i = 0; i < m_width; i++ ) { + m_mask[ i ] = new char[ m_height ]; + memset( m_mask[ i ], 0, m_height ); + } + + /* loop until we hit the end of the file, or until we have read as many + * lines as the mask is high. */ + + j = 0; + while( !in.eof() ) { + if( j >= m_height ) { + break; + } + + /* read the next line and parse it */ + in >> line; + + for( i = 0; i < m_width; i++ ) { + if( line[ i ] == 0 ) { + break; + } + + /* if the current char is a '1' then the mask is valid at that point */ + m_mask[ i ][ j ] = ( line[i] == '1' ? 1 : 0 ); + } + + j++; + } +} + + +JBMazeMask::JBMazeMask( JBMazeMask& master ) { + int i; + + m_width = master.m_width; + m_height = master.m_height; + + m_mask = new char*[ m_width ]; + for( i = 0; i < m_width; i++ ) { + m_mask[ i ] = new char[ m_height ]; + memcpy( m_mask[ i ], master.m_mask[ i ], m_height ); + } +} + + +JBMazeMask::~JBMazeMask() { + int i; + + if( m_mask == 0 ) { + return; + } + + for( i = 0; i < m_width; i++ ) { + delete[] m_mask[i]; + } + delete m_mask; + + m_mask = 0; + m_width = m_height = 0; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..566f9c3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,537 @@ +#include +#include +#include +#include +#include +#include + +#include "jbmaze.h" +#include "gd.h" + + +typedef struct { + int width; + int height; + int depth; + int sparseness; + int deadends; + int randomness; + int startx; + int starty; + int startz; + int endx; + int endy; + int endz; + long seed; + int ofs; + int wallWid; + int pathWid; + long bgColor; + long wallClr; + long pathClr; + long boundClr; + long startClr; + long endClr; + int showSolution; + int showMarkers; + char maskFile[256]; +} PARMOPTS; + + +int verticalBoundary( JBMazeMask* mask, int x, int y ) { + int one; + int two; + + if( ( y <= 0 ) || ( y >= mask->getHeight() ) ) { + return 1; + } + + if( x >= mask->getWidth() ) { + return 0; + } + + one = mask->getMaskAt( x, y ); + two = mask->getMaskAt( x, y-1 ); + + return ( one != two ); +} + +int horizontalBoundary( JBMazeMask* mask, int x, int y ) { + int one; + int two; + + if( ( x <= 0 ) || ( x >= mask->getWidth() ) ) { + return 1; + } + + if( y >= mask->getHeight() ) { + return 0; + } + + one = mask->getMaskAt( x, y ); + two = mask->getMaskAt( x-1, y ); + + return ( one != two ); +} + + +#define RED(x) ( x >> 16 ) +#define GREEN(x) ( ( x & 0x00FF00 ) >> 8 ) +#define BLUE(x) ( x & 0x0000FF ) + +int drawAsStructure( gdImagePtr* image, JBMaze* maze, JBMazePt* path, int len, PARMOPTS* opts ) { + int wallClr; + int bgColor; + int boundClr; + int i; + int j; + int ofs; + int inc; + int dir; + int wallWid; + int xSize; + int ySize; + int gridSize; + + JBMazeMask* mask; + + ofs = opts->ofs; + inc = opts->pathWid; + wallWid = opts->wallWid; + + gridSize = inc + wallWid + 1; + mask = maze->getMask(); + + xSize = ofs*2 + gridSize * maze->getX() + wallWid+2; + ySize = ofs*2 + gridSize * maze->getY() + wallWid+2; + + *image = gdImageCreate( xSize, ySize ); + + bgColor = gdImageColorAllocate( *image, RED(opts->bgColor), GREEN(opts->bgColor), BLUE(opts->bgColor) ); + wallClr = gdImageColorAllocate( *image, RED(opts->wallClr), GREEN(opts->wallClr), BLUE(opts->wallClr) ); + boundClr = gdImageColorAllocate( *image, RED(opts->boundClr), GREEN(opts->boundClr), BLUE(opts->boundClr) ); + + gdImageFilledRectangle( *image, 0, 0, xSize - 1, ySize - 1, bgColor ); + + ofs++; + for( j = 0; j < maze->getY(); j++ ) { + for( i = 0; i < maze->getX(); i++ ) { + dir = maze->getExitsAt( i, j, 0 ); + if( dir == 0 ) { + gdImageFilledRectangle( *image, + ofs + i * gridSize, ofs + j * gridSize, + ofs + (i+1) * gridSize, ofs + (j+1) * gridSize, + wallClr ); + } else { + if( ( dir & JBMaze::c_NORTH ) == 0 ) { + gdImageFilledRectangle( *image, + ofs + i * gridSize, ofs + j * gridSize, + ofs + (i+1) * gridSize + wallWid, ofs + j * gridSize + wallWid, + wallClr ); + } + if( ( dir & JBMaze::c_WEST ) == 0 ) { + gdImageFilledRectangle( *image, + ofs + i * gridSize, ofs + j * gridSize, + ofs + i * gridSize + wallWid, ofs + (j+1) * gridSize + wallWid, + wallClr ); + } + } + } + } + + /* right wall */ + gdImageFilledRectangle( *image, + ofs + maze->getX() * gridSize, + ofs, + ofs + maze->getX() * gridSize + wallWid, + ofs + maze->getY() * gridSize + wallWid, + wallClr ); + + /* bottom wall */ + gdImageFilledRectangle( *image, + ofs, + ofs + maze->getY() * gridSize, + ofs + maze->getX() * gridSize + wallWid, + ofs + maze->getY() * gridSize + wallWid, + wallClr ); + + + /* trace the mask */ + + for( j = 0; j < maze->getY(); j++ ) { + for( i = 0; i < maze->getX(); i++ ) { + if( verticalBoundary( mask, i, j ) ) { + gdImageLine( *image, + ofs + i * gridSize, + ofs + j * gridSize, + ofs + (i+1) * gridSize, + ofs + j * gridSize, + boundClr ); + } + if( horizontalBoundary( mask, i, j ) ) { + gdImageLine( *image, + ofs + i * gridSize, + ofs + j * gridSize, + ofs + i * gridSize, + ofs + (j+1) * gridSize, + boundClr ); + } + } + } + + gdImageLine( *image, ofs, + ofs + maze->getY() * gridSize + 1, + ofs + maze->getX() * gridSize + 1, + ofs + maze->getY() * gridSize + 1, + boundClr ); + + gdImageLine( *image, ofs + maze->getX() * gridSize + 1, + ofs, + ofs + maze->getX() * gridSize + 1, + ofs + maze->getY() * gridSize + 1, + boundClr ); + + /* display the beginning and ending markers */ + + if( opts->showMarkers ) { + int startClr; + int endClr; + + startClr = gdImageColorAllocate( *image, RED(opts->startClr), GREEN(opts->startClr), BLUE(opts->startClr) ); + endClr = gdImageColorAllocate( *image, RED(opts->endClr), GREEN(opts->endClr), BLUE(opts->endClr) ); + + gdImageFilledRectangle( *image, + ofs + maze->getStart().x * gridSize + wallWid + 1, + ofs + maze->getStart().y * gridSize + wallWid + 1, + ofs + (maze->getStart().x+1) * gridSize - wallWid, + ofs + (maze->getStart().y+1) * gridSize - wallWid, + startClr ); + gdImageFilledRectangle( *image, + ofs + maze->getEnd().x * gridSize + wallWid + 1, + ofs + maze->getEnd().y * gridSize + wallWid + 1, + ofs + (maze->getEnd().x+1) * gridSize - wallWid, + ofs + (maze->getEnd().y+1) * gridSize - wallWid, + endClr ); + } + + /* display the solution */ + + if( opts->showSolution ) { + int pathClr; + + pathClr = gdImageColorAllocate( *image, RED(opts->pathClr), GREEN(opts->pathClr), BLUE(opts->pathClr) ); + + for( j = 0; j < len-1; j++ ) { + if( path[j].z == 0 ) { + int x1 = ofs + path[j].x * gridSize + wallWid + inc/2+1; + int y1 = ofs + path[j].y * gridSize + wallWid + inc/2+1; + int x2 = ofs + path[j+1].x * gridSize + wallWid + inc/2+1; + int y2 = ofs + path[j+1].y * gridSize + wallWid + inc/2+1; + + gdImageLine( *image, + x1, y1, x2, y2, + pathClr ); + } + } + } + + return 0; +} + + +char* trimleft( char* s ) { + char* i; + + i = s + strlen( s ) - 1; + while( i >= i && isspace( *i ) ) { + *i = 0; + i--; + } + + return s; +} + + +char* getParm( char* line, char** parm ) { + char* p; + char* s; + + p = line; + while( isspace( *p ) ) p++; + if( ( *p == 0 ) || ( *p == '#' ) ) { + return 0; + } + + s = strchr( p, '=' ); + if( s == 0 ) { + return 0; + } + + *s = 0; + s++; + + trimleft( s ); + *parm = s; + + return p; +} + + +long makeColor( char* line ) { + char* s; + long clr; + + s = line; + clr = atoi( s ) << 16; + + while(*s && *s != ',') s++; + if(*s == 0) return clr; + s++; + + clr |= atoi( s ) << 8; + + while(*s && *s != ',') s++; + if(*s == 0) return clr; + s++; + + clr |= atoi( s ); + return clr; +} + + +int readParameters( char* file, PARMOPTS* opts ) { + FILE* f; + char line[100]; + char* parm; + char* value; + + f = fopen( file, "rt" ); + if( !f ) { + return 0; + } + + opts->seed = time(NULL); + + while( fgets( line, sizeof( line ), f ) != NULL ) { + parm = getParm( line, &value ); + + if( parm == 0 ) { + continue; + } + + if( strcmp( parm, "x" ) == 0 ) { + opts->width = atoi( value ); + } else if( strcmp( parm, "y" ) == 0 ) { + opts->height = atoi( value ); + } else if( strcmp( parm, "z" ) == 0 ) { + opts->depth = atoi( value ); + } else if( strcmp( parm, "sparse" ) == 0 ) { + opts->sparseness = atoi( value ); + } else if( strcmp( parm, "deadends" ) == 0 ) { + opts->deadends = atoi( value ); + } else if( strcmp( parm, "randomness" ) == 0 ) { + opts->randomness = atoi( value ); + } else if( strcmp( parm, "startx" ) == 0 ) { + opts->startx = atoi( value ); + } else if( strcmp( parm, "starty" ) == 0 ) { + opts->starty = atoi( value ); + } else if( strcmp( parm, "startz" ) == 0 ) { + opts->startz = atoi( value ); + } else if( strcmp( parm, "endx" ) == 0 ) { + opts->endx = atoi( value ); + } else if( strcmp( parm, "endy" ) == 0 ) { + opts->endy = atoi( value ); + } else if( strcmp( parm, "endz" ) == 0 ) { + opts->endz = atoi( value ); + } else if( strcmp( parm, "mask" ) == 0 ) { + strcpy( opts->maskFile, value ); + } else if( strcmp( parm, "seed" ) == 0 ) { + opts->seed = atol( value ); + } else if( strcmp( parm, "border" ) == 0 ) { + opts->ofs = atoi( value ); + } else if( strcmp( parm, "wall" ) == 0 ) { + opts->wallWid = atoi( value ); + } else if( strcmp( parm, "path" ) == 0 ) { + opts->pathWid = atoi( value ); + } else if( strcmp( parm, "bgclr" ) == 0 ) { + opts->bgColor = makeColor( value ); + } else if( strcmp( parm, "wallclr" ) == 0 ) { + opts->wallClr = makeColor( value ); + } else if( strcmp( parm, "pathclr" ) == 0 ) { + opts->pathClr = makeColor( value ); + } else if( strcmp( parm, "boundclr" ) == 0 ) { + opts->boundClr = makeColor( value ); + } else if( strcmp( parm, "startclr" ) == 0 ) { + opts->startClr = makeColor( value ); + } else if( strcmp( parm, "endclr" ) == 0 ) { + opts->endClr = makeColor( value ); + } else if( strcmp( parm, "solution" ) == 0 ) { + opts->showSolution = ( atoi( value ) != 0 ); + } else if( strcmp( parm, "markers" ) == 0 ) { + opts->showMarkers = ( atoi( value ) != 0 ); + } else if( strcmp( parm, "include" ) == 0 ) { + readParameters( value, opts ); + } + } + + return 1; +} + +void printHelp(void) { + fprintf(stderr, + "usage: maze \n" + "\n" + "options:\n" + " -H : this help\n" + " -w n : set maze width to n\n" + " -h n : set maze height to n\n" + " -d n : set maze depth to n\n" + " -s n : set maze sparseness to n\n" + " -e n : set maze deadend percentage to n\n" + " -r n : set maze randomness percentage to n\n" + " -x n : set maze starting x coordinate to n\n" + " -y n : set maze starting y coordinate to n\n" + " -z n : set maze starting z coordinate to n\n" + " -X n : set maze ending x coordinate to n\n" + " -Y n : set maze ending y coordinate to n\n" + " -Z n : set maze ending z coordinate to n\n" + " -m file : use file to define the maze mask\n" + " -S n : use n as the random seed for the maze\n" + " -b n : set the outer margin to n pixels\n" + " -W n : set the wall width to n pixels\n" + " -p n : set the path width to n pixels\n" + " -B r,g,b : set background color to r,g,b\n" + " -A r,g,b : set wall color to r,g,b\n" + " -P r,g,b : set path color to r,g,b\n" + " -O r,g,b : set boundary color to r,g,b\n" + " -T r,g,b : set start position color to r,g,b\n" + " -E r,g,b : set end position color to r,g,b\n" + " -L n : set n to non-zero to show maze solution\n" + " -M n : set n to non-zero to show start/end positions\n" + " -f file : read configuration options from file\n" + ); + + exit(-1); +} + +int parseArgs(int argc, char* argv[], PARMOPTS* opts) { + int i; + + opts->width = 10; + opts->height = 10; + opts->depth = 1; + opts->randomness = 50; + opts->wallWid = 1; + opts->pathWid = 9; + opts->bgColor = makeColor("255,255,255"); + opts->wallClr = makeColor("0,0,0"); + opts->pathClr = makeColor("255,0,0"); + opts->startClr = makeColor("0,128,0"); + opts->endClr = makeColor("255,0,0"); + opts->endx = opts->endy = opts->endz = -1; + + for(i = 1; i < argc; i++) { + if(strcmp(argv[i], "-H") == 0) printHelp(); + + if(argv[i][0] != '-') { + fprintf(stderr, "bad argument: %s\n\n", argv[i]); + printHelp(); + } + + if(i == argc) { + fprintf(stderr, "argument is missing a value: %s\n\n", argv[i]); + printHelp(); + } + + switch(argv[i][1]) { + case 'w': opts->width = atoi(argv[++i]); break; + case 'h': opts->height = atoi(argv[++i]); break; + case 'd': opts->depth = atoi(argv[++i]); break; + case 's': opts->sparseness = atoi(argv[++i]); break; + case 'e': opts->deadends = atoi(argv[++i]); break; + case 'r': opts->randomness = atoi(argv[++i]); break; + case 'x': opts->startx = atoi(argv[++i]); break; + case 'y': opts->starty = atoi(argv[++i]); break; + case 'z': opts->startz = atoi(argv[++i]); break; + case 'X': opts->endx = atoi(argv[++i]); break; + case 'Y': opts->endy = atoi(argv[++i]); break; + case 'Z': opts->endz = atoi(argv[++i]); break; + case 'm': strcpy( opts->maskFile, argv[++i] ); break; + case 'S': opts->seed = atol(argv[++i]); break; + case 'b': opts->ofs = atoi(argv[++i]); break; + case 'W': opts->wallWid = atoi(argv[++i]); break; + case 'p': opts->pathWid = atoi(argv[++i]); break; + case 'B': opts->bgColor = makeColor(argv[++i]); break; + case 'A': opts->wallClr = makeColor(argv[++i]); break; + case 'P': opts->pathClr = makeColor(argv[++i]); break; + case 'O': opts->boundClr = makeColor(argv[++i]); break; + case 'T': opts->startClr = makeColor(argv[++i]); break; + case 'E': opts->endClr = makeColor(argv[++i]); break; + case 'L': opts->showSolution = atoi(argv[++i]); break; + case 'M': opts->showMarkers = atoi(argv[++i]); break; + case 'f': readParameters( argv[++i], opts ); break; + default: + fprintf(stderr, "unsupported argument: %s\n\n", argv[i]); + printHelp(); + } + } + + if(opts->endx < 0) opts->endx = opts->width-1; + if(opts->endy < 0) opts->endy = opts->height-1; + if(opts->endz < 0) opts->endz = opts->depth-1; + + return 1; +} + +int main( int argc, char* argv[] ) { + JBMaze* maze; + gdImagePtr image; + JBMazePt* path; + int len; + PARMOPTS opts; + + memset( &opts, 0, sizeof( opts ) ); + parseArgs( argc, argv, &opts); + + fprintf( stderr, "current seed: %ld\n", opts.seed ); + + /* construct the maze */ + + maze = new JBMaze( opts.width, opts.height, opts.depth, opts.seed, opts.randomness, + opts.startx, opts.starty, opts.startz, opts.endx, opts.endy, opts.endz ); + + /* load the mask */ + + if( opts.maskFile[0] != 0 ) { + maze->setMask( new JBMazeMask( opts.maskFile ) ); + } + + /* generate it */ + + maze->generate(); + + /* solve it */ + + maze->solve( &path, &len ); + + /* sparsify it */ + + maze->sparsify( opts.sparseness ); + + /* clear up the deadends by reconnecting them into an existing passage */ + + maze->clearDeadends( opts.deadends ); + + /* draw it */ + + drawAsStructure( &image, maze, path, len, &opts ); + + free( path ); + + gdImagePng( image, stdout ); + + gdImageDestroy( image ); + delete maze; + + return 0; +} diff --git a/src/treasureEngine.c b/src/treasureEngine.c new file mode 100644 index 0000000..13da10a --- /dev/null +++ b/src/treasureEngine.c @@ -0,0 +1,3799 @@ +/* ---------------------------------------------------------------------- * + * D&D Treasure Generator Engine + * + * by Jamis Buck (jgb3@email.byu.edu) + * online version at http://rover.byu.edu/cgi-bin/treasure.cgi + * + * This file is in the public domain, but it should be noted that the + * contents of the tables is from the D&D Dungeon Master's Guide, and + * there may be copyright issues involved there. + * + * data taken straight from the D&D Dungeon Master's Guide, with a few + * modifications (in particular, entries that read "DM's choice" were + * replaced with my own preferences). + * + * The generator engine places all items generated in a linked list of + * TREASUREITEM types (see the two global variables "treasureList" and + * "treasureTail", as well as the "addNewTreasure" routine). The treasures + * are then displayed by looping over the list. To make the engine + * really spiffy, all global variables should be eliminated, and the + * values should be passed from routine to routine . . . I didn't have the + * luxury of time, so this will have to do for now. + * + * TODO: spell-containing magic items have a chance to already contain a + * spell (like a ring of spell-storing or counterspells). + * ---------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include + +#include "gameutil.h" +#include "treasureEngine.h" + +/* weapon types, used to determine what types of abilities a given weapon + * can have. */ + +#define SLASHING ( 0x01 ) +#define BLUDGEON ( 0x02 ) +#define PIERCING ( 0x04 ) +#define ANY ( SLASHING | BLUDGEON | PIERCING ) + +#define opLAWFUL ( 0x0001 ) +#define opCHAOTIC ( 0x0002 ) +#define opGOOD ( 0x0004 ) +#define opEVIL ( 0x0008 ) + + +/* here is the real meat of the program, and the part that took the longest + * to write -- all the relevant tables from the DMG!!! + */ + +typedef struct { + int pcap; /* upper percentage cap for treasure */ + int ttype; /* type of treasure */ + int tdc; /* die count */ + int tdt; /* die type */ + int mul; /* sum multiplier */ +} treasureEntry; + + +treasureEntry treasureTable[ LEVEL_COUNT * 15 ] = { +/* 1 */ + /* coins */ { 14, NONE, 0, 0, 0 }, + { 29, CP, 1, 6, 1000 }, + { 52, SP, 1, 8, 100 }, + { 95, GP, 2, 8, 10 }, + { 100, PP, 1, 4, 10 }, + /* gems */ { 90, NONE, 0, 0, 0 }, + { 95, GEMS, 1, 1, 1 }, + { 100, ART, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + /* magic */ { 71, NONE, 0, 0, 0 }, + { 95, MUNDANE, 1, 1, 1 }, + { 100, MINOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 2 */ + { 13, NONE, 0, 0, 0 }, + { 23, CP, 1, 10, 1000 }, + { 43, SP, 2, 10, 100 }, + { 95, GP, 4, 10, 10 }, + { 100, PP, 2, 8, 10 }, + { 81, NONE, 0, 0, 0 }, + { 95, GEMS, 1, 3, 1 }, + { 100, ART, 1, 3, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 49, NONE, 0, 0, 0 }, + { 85, MUNDANE, 1, 1, 1 }, + { 100, MINOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 3 */ + { 11, NONE, 0, 0, 0 }, + { 21, CP, 2, 10, 1000 }, + { 41, SP, 4, 8, 100 }, + { 95, GP, 1, 4, 100 }, + { 100, PP, 1, 10, 10 }, + { 70, NONE, 0, 0, 0 }, + { 95, GEMS, 1, 4, 1 }, + { 100, ART, 1, 3, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 49, NONE, 0, 0, 0 }, + { 79, MUNDANE, 1, 3, 1 }, + { 100, MINOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 4 */ + { 11, NONE, 0, 0, 0 }, + { 21, CP, 3, 10, 1000 }, + { 41, SP, 4, 12, 1000 }, + { 95, GP, 1, 6, 100 }, + { 100, PP, 1, 8, 10 }, + { 70, NONE, 0, 0, 0 }, + { 95, GEMS, 1, 4, 1 }, + { 100, ART, 1, 3, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 42, NONE, 0, 0, 0 }, + { 62, MUNDANE, 1, 4, 1 }, + { 100, MINOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 5 */ + { 10, NONE, 0, 0, 0 }, + { 19, CP, 1, 4, 10000 }, + { 38, SP, 1, 6, 1000 }, + { 95, GP, 1, 8, 100 }, + { 100, PP, 1, 10, 10 }, + { 60, NONE, 0, 0, 0 }, + { 95, GEMS, 1, 4, 1 }, + { 100, ART, 1, 4, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 57, NONE, 0, 0, 0 }, + { 67, MUNDANE, 1, 4, 1 }, + { 100, MINOR, 1, 3, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 6 */ + { 10, NONE, 0, 0, 0 }, + { 18, CP, 1, 6, 10000 }, + { 37, SP, 1, 8, 1000 }, + { 95, GP, 1, 10, 100 }, + { 100, PP, 1, 12, 10 }, + { 56, NONE, 0, 0, 0 }, + { 92, GEMS, 1, 4, 1 }, + { 100, ART, 1, 4, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 54, NONE, 0, 0, 0 }, + { 59, MUNDANE, 1, 4, 1 }, + { 99, MINOR, 1, 3, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 7 */ + { 11, NONE, 0, 0, 0 }, + { 18, CP, 1, 10, 10000 }, + { 35, SP, 1, 12, 1000 }, + { 93, GP, 2, 6, 100 }, + { 100, PP, 3, 4, 10 }, + { 48, NONE, 0, 0, 0 }, + { 88, GEMS, 1, 4, 1 }, + { 100, ART, 1, 4, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 51, NONE, 0, 0, 0 }, + { 97, MINOR, 1, 3, 1 }, + { 100, MEDIUM, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 8 */ + { 10, NONE, 0, 0, 0 }, + { 15, CP, 1, 12, 10000 }, + { 29, SP, 2, 6, 1000 }, + { 87, GP, 2, 8, 100 }, + { 100, PP, 3, 6, 10 }, + { 45, NONE, 0, 0, 0 }, + { 85, GEMS, 1, 6, 1 }, + { 100, ART, 1, 4, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 48, NONE, 0, 0, 0 }, + { 96, MINOR, 1, 4, 1 }, + { 100, MEDIUM, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 9 */ + { 10, NONE, 0, 0, 0 }, + { 15, CP, 2, 6, 10000 }, + { 29, SP, 2, 8, 1000 }, + { 85, GP, 5, 4, 100 }, + { 100, PP, 2, 12, 10 }, + { 40, NONE, 0, 0, 0 }, + { 80, GEMS, 1, 8, 1 }, + { 100, ART, 1, 4, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 43, NONE, 0, 0, 0 }, + { 91, MINOR, 1, 4, 1 }, + { 100, MEDIUM, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 10 */ + { 10, NONE, 0, 0, 0 }, + { 24, SP, 2, 10, 1000 }, + { 79, GP, 6, 4, 100 }, + { 100, PP, 5, 6, 10 }, + { 0, NONE, 0, 0, 0 }, + { 35, NONE, 0, 0, 0 }, + { 79, GEMS, 1, 8, 1 }, + { 100, ART, 1, 6, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 40, NONE, 0, 0, 0 }, + { 88, MINOR, 1, 4, 1 }, + { 99, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 11 */ + { 8, NONE, 0, 0, 0 }, + { 14, SP, 3, 10, 1000 }, + { 75, GP, 4, 8, 1000 }, + { 100, PP, 4, 10, 10 }, + { 0, NONE, 0, 0, 0 }, + { 24, NONE, 0, 0, 0 }, + { 74, GEMS, 1, 10, 1 }, + { 100, ART, 1, 6, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 31, NONE, 0, 0, 0 }, + { 84, MINOR, 1, 4, 1 }, + { 98, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 12 */ + { 8, NONE, 0, 0, 0 }, + { 14, SP, 3, 12, 1000 }, + { 75, GP, 1, 4, 1000 }, + { 100, PP, 1, 4, 100 }, + { 0, NONE, 0, 0, 0 }, + { 17, NONE, 0, 0, 0 }, + { 70, GEMS, 1, 10, 1 }, + { 100, ART, 1, 8, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 27, NONE, 0, 0, 0 }, + { 82, MINOR, 1, 6, 1 }, + { 97, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 13 */ + { 8, NONE, 0, 0, 0 }, + { 75, GP, 1, 4, 1000 }, + { 100, PP, 1, 10, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 11, NONE, 0, 0, 0 }, + { 66, GEMS, 1, 12, 1 }, + { 100, ART, 1, 10, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 19, NONE, 0, 0, 0 }, + { 73, MINOR, 1, 6, 1 }, + { 95, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 14 */ + { 8, NONE, 0, 0, 0 }, + { 75, GP, 1, 6, 1000 }, + { 100, PP, 1, 12, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 11, NONE, 0, 0, 0 }, + { 66, GEMS, 2, 8, 1 }, + { 100, ART, 2, 6, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 19, NONE, 0, 0, 0 }, + { 58, MINOR, 1, 6, 1 }, + { 92, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 15 */ + { 3, NONE, 0, 0, 0 }, + { 74, GP, 1, 8, 1000 }, + { 100, PP, 3, 4, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 9, NONE, 0, 0, 0 }, + { 65, GEMS, 2, 10, 1 }, + { 100, ART, 2, 8, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 11, NONE, 0, 0, 0 }, + { 46, MINOR, 1, 10, 1 }, + { 90, MEDIUM, 1, 1, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 16 */ + { 3, NONE, 0, 0, 0 }, + { 74, GP, 1, 12, 1000 }, + { 100, PP, 3, 4, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 7, NONE, 0, 0, 0 }, + { 64, GEMS, 4, 6, 1 }, + { 100, ART, 2, 10, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 40, NONE, 0, 0, 0 }, + { 46, MINOR, 1, 10, 1 }, + { 90, MEDIUM, 1, 3, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, +/* 17 */ + { 3, NONE, 0, 0, 0 }, + { 68, GP, 3, 4, 1000 }, + { 100, PP, 2, 10, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 4, NONE, 0, 0, 0 }, + { 63, GEMS, 4, 8, 1 }, + { 100, ART, 3, 8, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 33, NONE, 0, 0, 0 }, + { 83, MEDIUM, 1, 3, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 18 */ + { 2, NONE, 0, 0, 0 }, + { 65, GP, 3, 6, 1000 }, + { 100, PP, 5, 4, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 4, NONE, 0, 0, 0 }, + { 54, GEMS, 3, 12, 1 }, + { 100, ART, 3, 10, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 24, NONE, 0, 0, 0 }, + { 80, MEDIUM, 1, 4, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 19 */ + { 2, NONE, 0, 0, 0 }, + { 65, GP, 3, 8, 1000 }, + { 100, PP, 3, 10, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 3, NONE, 0, 0, 0 }, + { 50, GEMS, 6, 6, 1 }, + { 100, ART, 6, 6, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 4, NONE, 0, 0, 0 }, + { 70, MEDIUM, 1, 4, 1 }, + { 100, MAJOR, 1, 1, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* 20 */ + { 2, NONE, 0, 0, 0 }, + { 65, GP, 4, 8, 1000 }, + { 100, PP, 4, 10, 100 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 2, NONE, 0, 0, 0 }, + { 38, GEMS, 4, 10, 1 }, + { 100, ART, 7, 6, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, + { 25, NONE, 0, 0, 0 }, + { 65, MEDIUM, 1, 4, 1 }, + { 100, MAJOR, 1, 3, 1 }, + { 0, NONE, 0, 0, 0 }, + { 0, NONE, 0, 0, 0 }, +/* mundane/magic items only */ + { 25, MUNDANE, 1, 10, 1 }, + { 50, MINOR, 1, 10, 1 }, + { 75, MEDIUM, 1, 10, 1 }, + { 100, MAJOR, 1, 10, 1 }, + { 0, NONE, 0, 0, 0 }, + { 25, MUNDANE, 1, 10, 1 }, + { 50, MINOR, 1, 10, 1 }, + { 75, MEDIUM, 1, 10, 1 }, + { 100, MAJOR, 1, 10, 1 }, + { 0, NONE, 0, 0, 0 }, + { 25, MUNDANE, 1, 10, 1 }, + { 50, MINOR, 1, 10, 1 }, + { 75, MEDIUM, 1, 10, 1 }, + { 100, MAJOR, 1, 10, 1 }, + { 0, NONE, 0, 0, 0 } +}; + + +char* gemNames1[] = { /* 4-16 gp */ + (char*) 10, + "banded eye agate", + "moss agate", + "azurite", + "blue quartz", + "hematite", + "lapis lazuli", + "malachite", + "obsidian", + "rhodochrosite", + "tiger eye turquoise" +}; + + +char* gemNames2[] = { /* 20-40 gp */ + (char*) 16, + "bloodstone", + "carnelian", + "chalcedony", + "chysoprase", + "citrine", + "jasper iolite", + "moonstone", + "onyx", + "peridot", + "rock crystal", + "sard", + "sardonyx", + "rose quartz", + "smoky quartz", + "star rose quartz", + "zircon" +}; + + +char* gemNames3[] = { /* 40-160 gp */ + (char*) 16, + "amber", + "amethyst", + "chrysoberyl", + "coral", + "red garnet", + "brown-green garnet", + "jade", + "jet", + "white pearl", + "golden pearl", + "pink pearl", + "silver pearl", + "red spinel", + "red-brown spinel", + "deep green spinel", + "tourmaline" +}; + + +char* gemNames4[] = { /* 200-800 gp */ + (char*) 6, + "alexandrite", + "aquamarine", + "violet garnet", + "black pearl", + "deep blue spinel", + "golden yellow topaz" +}; + + +char* gemNames5[] = { /* 400-1600 gp */ + (char*) 10, + "emerald", + "white opal", + "black opal", + "fire opal", + "blue sapphire", + "fiery yellow corundum", + "rich purple corundum", + "blue star sapphire", + "black star sapphire", + "star ruby" +}; + + +char* gemNames6[] = { /* 2000-8000 gp */ + (char*) 7, + "clearest bright green emerald", + "blue-white diamond", + "canary diamond", + "pink diamond", + "brown diamond", + "blue diamond", + "jacinth" +}; + + +struct { + int dcap; + int dcount; + int dtype; + int dmul; + char** names; +} gemTable[] = { + { 25, 4, 4, 1, gemNames1 }, + { 50, 2, 4, 10, gemNames2 }, + { 70, 4, 4, 10, gemNames3 }, + { 90, 2, 4, 100, gemNames4 }, + { 99, 4, 4, 100, gemNames5 }, + { 100, 2, 4, 1000, gemNames6 }, + { 0, 0, 0, 0, 0 } +}; + + +char* artNames1[] = { /* 1-100 gp */ + (char*) 17, + "silver ewer", + "carved bone statuette", + "carved ivory statuette", + "finely wrought small gold bracelet", + "a scroll tube, carved in ivory with gold-plated metal end caps|a large bowl made of chased and pierced gold, worked in the design of leaping dragons and spear-armed warriors", + "a 6-sided die, 1\" cube, of beaten gold stamped with holes for the pips", + "a death mask of a noble, bearded male visage, made of beaten gold", + "a monocle made from a polished glass lens in a gold frame, with hooked and pierced side-handle, but without ribbon or cord", + "a quill pen case made of gold, held shut by a clasp, carved into the case is a scene of a scribe sitting on a stool amid stacks of parchment and writing in a tome", + "a chain, 6' long, made of ornamental, gold-plated, triple-interlaced links which are both heavy and strong", + "a jewel coffer of chased silver, depicting wooded scenes with birds in branches on back and sides, the top is graced by an engraving of a maiden combing her hair while looking into a pool at her reflection", + "an ornamental skullcap of beaten gold cut in the shape of floral vines meeting, curling away and meeting again", + "a statuette in solid gold of a flowing-haired maiden riding a rearing unicorn", + "a statuette of carved ivory of an armored warrior leaning on a great broadsword", + "a salt cellar made of ornately carved gold with a cork stopper in the bottom, the cellar is shaped like a slumbering gold dragon curled around a pile of gold", + "a sword hilt made of intricately carved gold with an enameled painting of a hawk in flight in the center of the grip, the sword's pommel is fashioned into a hawk's head, the hilt is ornamental in nature, for it is too soft (solid gold, not plating on a stronger metal) for battle use", + "a drinking jack of polished black-and-white horn with silver cap and base" +}; + + +char* artNames2[] = { /* 30-180 gp */ + (char*) 8, + "cloth of gold vestments", + "black velvet mask with numerous citrines", + "silver chalice with lapis lazuli gems", + "a single electrum bracer worked in mock scales with four circular bosses about it, the center of each boss being a claw holding a small bloodstone", + "a coffer, 6' x 1' x 2', with gold hinges and catch, made of carved ivory worked into a beveled top, with a battle scene covering the sides and top", + "gold-plated corkscrew with a bloodstone set into each tip of the handle|a golden ball, dimpled from use but still brightly polished, 3-inch-diameter sphere of solid gold", + "a tulip-shaped flagon with a heavy, bulbous base, carved of clear rock crystal polished glass-smooth, holding about one pint", + "a gaming piece in the shape of a halfling, carved of ivory with two amber beads" +}; + + +char* artNames3[] = { /* 100-600 gp */ + (char*) 7, + "large well-done wool tapestry", + "brass mug with jade inlays", + "a book with steel-edged, beaten-gold covers, embossed and painted in fine, intricate repeating pattern borders, having as a central scene a warrior with a long sword battling a dragon, which he is grasping by the throat", + "a golden flute, of delicate workmanship and mirror-smooth finish", + "a peg-leg made of gilded wood and set with three large, cabochon-cut ovals of amber", + "a platter of chased and pierced gold, delicate and easily damaged, but in good condition, oval-shaped, 2 feet long by 1 foot wide at widest point", + "a ring of red gold, beaten into a long knuckle-coil to resemble a miniature snake coiling about the wearer's finger, with two tiny rubies set into its head as eyes" +}; + + +char* artNames4[] = { /* 100-1000 gp */ + (char*) 5, + "silver comb with moonstones", + "silver-plated steel longsword with jet jewel in hilt", + "a cork bottle stopper, fastened by an ornate wire twisting to a large, brilliant-cut topaz", + "a sarcophagus/casket of bronze sheathed with electrum, worked in an effigy-shape of sleeping form, the face of which is fashioned of gold inlay and its eye sockets once held gems (which are now missing)", + "a long sword of steel plated with silver, with a simple cross-hilted blade and a cabochon-cut piece of jet set into the center of the tang where the quillons meet it" +}; + + +char* artNames5[] = { /* 200-1200 gp */ + (char*) 6, + "carved harp of exotic wood with ivory inlay and zircon gems", + "solid gold idol (10 lb.)", + "a golden comb, its handle carved into a dragon's head with a ruby set as an eye", + "a silver cloak pin, fashioned in the shape of a griffon's head (side view, facing right) with a ruby as the eye", + "a fire-blackened oak staff shod with meteoritic steel at its base, the head of the staff is carved in the shape of a fanged serpent with two rubies as eyes", + "a ring of carved and beaten gold in curlicue designs, showing a mock beast claw holding a large spherical aquamarine" +}; + + +char* artNames6[] = { /* 300-1800 gp */ + (char*) 6, + "gold dragon comb with red garnet eye", + "gold and topaz bottle stopper cork", + "ceremonial electrum dagger with a star ruby in the pommel", + "an eye patch, sans chain or thong ties, shaped as a rhomboid of beaten gold set with a mock eye made of a sapphire, which is in turn surrounded by two crescents of polished moonstone, the eye patch is pierced in all four corners for ties", + "a clappered bell made of carved, polished rose crystal, the bell and clapper are joined by fine gold wire.", + "a half-mask of black velvet backed by leather, its lower edge trimmed with tiny teardrop citrines. There are 16 small citrines and six 6 slightly larger gems" +}; + + +char* artNames7[] = { /* 400-2400 gp */ + (char*) 5, + "eyepatch with mock eye of sapphire and moonstone", + "fire opal pendant on a fine gold chain", + "old masterpiece painting", + "a pendant consisting of a fire opal with a gilded, fine, twisted-link neck chain", + "a mantle with a black silk lining and a black velvet outer face that is adorned with beaded stars and geometric shapes, with one moonstone set into the center of each of the 36 stars; the mantle was created for a tall human and needs a pin to be worn correctly" +}; + + +char* artNames8[] = { /* 500-3000 gp */ + (char*) 5, + "embroidered silk and velvet mantle with numerous moonstones", + "sapphire pendant on gold chain", + "scroll tube made of carved ebony with silver plated end caps, each cap is inset with a large faceted emerald", + "a crown of yellow gold with six slim spires, with a large zircon set at the base of five of the spires, and a gigantic (2 inches high) amethyst set at the base of the tallest (front) spire", + "copper chamber pot, chased and embossed in a relief design of rampant, stylized dragon, with two emeralds as eyes" +}; + + +char* artNames9[] = { /* 1000-4000 gp */ + (char*) 5, + "emroidered and bejeweled glove", + "jeweled anklet", + "gold music box", + "a bracelet made of 46 tiny white pearls strung together on gilded wire, fastened with a clever hook and loop clasp", + "A leather glove for the right hand of a large human with embroidery along the back, making a curling tendril design utilizing beads and a few gemstones as flower buds, as follows: 8 white pearls, 1 peridot, 9 rock crystal \"tears\" (teardrop-cut, glassy polished), 1 opal" +}; + + +char* artNames10[] = { /* 1000-6000 gp */ + (char*) 4, + "goldet circlet with four aquamarines", + "a string of small pink pearls (necklace)", + "an anklet made of 12 tiny plates of gold linked with gilded wire and fastened by a hook and eye, from each wire loop save the fastening depends a wire-mounted gem, 11 in all, as follows: 4 white pearls, 6 violet garnets, 1 deep blue spinel", + "a crown made of a thick, soft band of beaten gold, set with 4 large (2-inch-diameter, half-relief cabochon-cut) aquamarines" +}; + + +char* artNames11[] = { /* 2000-8000 gp */ + (char*) 4, + "jeweled gold crown", + "jeweled electrum ring", + "a cup of the thinnest beaten gold set with a lip-ring of 12 tiny emeralds, the whole item chased and embossed in rings of an abstract pattern (interlocked rings, vertical and horizontal bars interwoven with them)", + "a single bracelet made of heavy gold and set with six small blue-white diamonds, the bracelet's edges cut in curlicues" +}; + + +char* artNames12[] = { /* 2000-12000 gp */ + (char*) 4, + "gold and ruby ring", + "gold cup set with emeralds", + "a garter consisting of nine gold coins linked with gold wire, from which hangs an electrum mesh fringe extending down in six triangles, each triangle ends in a claw-mounted, smoothly polished jacinth, the whole garter is backed with a (rotting) black leather band", + "a 5-inch-diameter sphere of solid gold cut with a relief design of four sylphs amid clouds, holding up a mirror (a polished area on the sphere), the eyes of the sylphs are tiny cabochon-cut rubies" +}; + + +struct { + int dcap; + int dcount; + int dtype; + int dmul; + char** names; +} artTable[] = { + { 10, 1, 10, 10, artNames1 }, + { 25, 3, 6, 10, artNames2 }, + { 40, 1, 6, 100, artNames3 }, + { 50, 1, 10, 100, artNames4 }, + { 60, 2, 6, 100, artNames5 }, + { 70, 3, 6, 100, artNames6 }, + { 80, 4, 6, 100, artNames7 }, + { 85, 5, 6, 100, artNames8 }, + { 90, 1, 4, 1000, artNames9 }, + { 95, 1, 6, 1000, artNames10 }, + { 99, 2, 4, 1000, artNames11 }, + { 100, 2, 6, 1000, artNames12 }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int dcap; + int dcount; + int dtype; + int dmul; + int other; + int value; + char* name; +} mundaneTable[] = { + { 5, 1, 4, 1, NONE, 20, "Alchemist's fire" }, + { 10, 2, 4, 1, NONE, 10, "Acid" }, + { 12, 1, 4, 1, NONE, 20, "Smokesticks" }, + { 18, 1, 4, 1, NONE, 25, "Holy water" }, + { 20, 1, 4, 1, NONE, 30, "Thunderstones" }, + { 22, 1, 1, 1, NONE, 100, "Chain shirt" }, + { 27, 1, 4, 1, NONE, 50, "Antitoxin" }, + { 29, 1, 4, 1, NONE, 50, "Tanglefoot bag" }, + { 34, 1, 1, 1, NONE, 175, "Masterwork studded leather" }, + { 39, 1, 1, 1, COMMONRW, -300, "" }, + { 43, 1, 1, 1, NONE, 200, "Breastplate" }, + { 48, 1, 1, 1, NONE, 250, "Banded mail" }, + { 66, 1, 1, 1, COMMONMW, 0, "Masterwork " }, + { 68, 1, 1, 1, UNCOMMNW, 0, "Masterwork " }, + { 73, 1, 1, 1, COMMONRW, 0, "Masterwork " }, + { 83, 1, 1, 1, COMMONRW, -300, "" }, + { 93, 1, 1, 1, NONE, 600, "Half-plate" }, + { 100, 1, 1, 1, NONE, 1500, "Full-plate" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int type; +} magicTable[] = { + { 4, 10, 10, ARMOR }, + { 9, 20, 20, WEAPONS }, + { 44, 30, 25, POTIONS }, + { 46, 40, 35, RINGS }, + { 0, 50, 45, RODS }, + { 81, 65, 55, SCROLLS }, + { 0, 68, 75, STAFFS }, + { 91, 83, 80, WANDS }, + { 100, 100, 100, WONDROUS }, + { 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int type; + int enhancement; + int armor; +} armorTable[] = { + { 60, 5, 0, ENHANCED, 1, 0 }, + { 80, 10, 0, ENHANCED, 1, 1 }, + { 85, 20, 0, ENHANCED, 2, 0 }, + { 87, 30, 0, ENHANCED, 2, 1 }, + { 0, 40, 8, ENHANCED, 3, 0 }, + { 0, 50, 16, ENHANCED, 3, 1 }, + { 0, 55, 27, ENHANCED, 4, 0 }, + { 0, 57, 38, ENHANCED, 4, 1 }, + { 0, 0, 49, ENHANCED, 5, 0 }, + { 0, 0, 57, ENHANCED, 5, 1 }, + { 0, 60, 60, SPECIFIC, 0, 1 }, + { 0, 63, 63, SPECIFIC, 0, 0 }, + { 100, 100, 100, SPECIAL, 0, 0 }, + { 0, 0, 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int value; + char* name; +} armorTypes[] = { + { 1, 155, "padded armor" }, + { 2, 160, "leather armor" }, + { 12, 165, "hide armor" }, + { 27, 175, "studded leather armor" }, + { 42, 250, "chain shirt" }, + { 43, 200, "scale mail" }, + { 44, 300, "chain mail" }, + { 57, 350, "breastplate" }, + { 58, 350, "splint mail" }, + { 59, 400, "banded mail" }, + { 60, 750, "half-plate" }, + { 100, 1650, "full-plate" }, + { 0, 0 } +}; + + +struct { + int dtop; + int value; + char* name; +} shieldTypes[] = { + { 10, 165, "buckler" }, + { 15, 153, "small wooden shield" }, + { 20, 159, "small steel shield" }, + { 30, 157, "large wooden shield" }, + { 95, 170, "large steel shield" }, + { 100, 180, "tower shield" }, + { 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int plus; + char* ability; +} armorSpecials[] = { + { 0, 2, 2, 1, "light fortification" }, + { 30, 7, 8, 1, "glamers" }, + { 52, 19, 9, 1, "slickness" }, + { 74, 30, 11, 1, "shadow" }, + { 96, 49, 14, 1, "silent moves" }, + { 0, 50, 16, 2, "spell resistance (13)" }, + { 0, 60, 21, 3, "ghost touch" }, + { 0, 0, 23, 3, "invulnerability" }, + { 98, 65, 27, 3, "moderate fortification" }, + { 0, 66, 29, 3, "spell resistance (15)" }, + { 0, 71, 31, 3, "acid resistance" }, + { 0, 76, 41, 3, "cold resistance" }, + { 0, 81, 51, 3, "fire resistance" }, + { 0, 86, 61, 3, "lightning resistance" }, + { 0, 91, 64, 3, "sonic resistance" }, + { 0, 94, 67, 4, "spell resistance (17)" }, + { 0, 95, 69, 5, "etherealness" }, + { 0, 98, 72, 5, "heavy fortification" }, + { 0, 0, 74, 5, "spell resistance (19)" }, + { 100, 100, 100, 0, "-" }, + { 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int plus; + char* ability; +} shieldSpecials[] = { + { 30, 0, 0, 1, "bashing" }, + { 50, 0, 0, 1, "blinding" }, + { 60, 0, 0, 1, "light fortification" }, + { 99, 10, 0, 2, "arrow deflection" }, + { 0, 16, 15, 2, "animation" }, + { 0, 20, 20, 2, "spell resistance (13)" }, + { 0, 25, 25, 3, "ghost touch" }, + { 0, 30, 35, 3, "moderate fortification" }, + { 0, 40, 38, 3, "acid resistance" }, + { 0, 50, 41, 3, "cold resistance" }, + { 0, 60, 44, 3, "fire resistance" }, + { 0, 70, 47, 3, "lightning resistance" }, + { 0, 80, 50, 3, "sonic resistance" }, + { 0, 0, 55, 3, "spell resistance (15)" }, + { 0, 0, 60, 4, "spell resistance (17)" }, + { 0, 0, 65, 5, "heavy fortification" }, + { 0, 90, 70, 5, "reflecting" }, + { 0, 0, 80, 5, "spell resistance (19)" }, + { 100, 100, 100, 0, "-" }, + { 0, 0, 0, 0 } +}; + + +struct { + int mediumDtop; + int majorDtop; + int value; + char *armor; +} specificArmors[] = { + { 10, 0, 1100, "mithril shirt" }, + { 25, 0, 4150, "elven chain armor" }, + { 35, 0, 5165, "rhino hide armor" }, + { 45, 0, 5350, "adamantine breastplate" }, + { 70, 0, 10500, "dwarven plate" }, + { 80, 10, 16650, "plate armor of the deep" }, + { 90, 40, 18900, "banded mail of luck" }, + { 100, 60, 21600, "breastplate of command" }, + { 0, 80, 25300, "celestial armor" }, + { 0, 100, 41650, "demon armor" }, + { 0, 0, 0, 0 } +}; + + +struct { + int mediumDtop; + int majorDtop; + int value; + char *shield; +} specificShields[] = { + { 10, 0, 257, "darkwood shield" }, + { 18, 0, 1020, "mithril large shield" }, + { 25, 0, 2170, "adamantine shield" }, + { 45, 20, 2670, "spined shield" }, + { 65, 40, 3153, "caster's shield" }, + { 90, 60, 9170, "lion's shield" }, + { 100, 80, 15159, "winged shield" }, + { 0, 100, 50170, "absorbing shield" }, + { 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int type; + int enhancement; +} weaponTable[] = { + { 70, 10, 0, ENHANCED, 1 }, + { 85, 20, 0, ENHANCED, 2 }, + { 0, 58, 20, ENHANCED, 3 }, + { 0, 62, 38, ENHANCED, 4 }, + { 0, 0, 49, ENHANCED, 5 }, + { 0, 68, 63, SPECIFIC, 0 }, + { 100, 100, 100, SPECIAL, 0 }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int type; +} weaponTypes[] = { + { 70, COMMONMW }, + { 80, UNCOMMNW }, + { 100, COMMONRW }, + { 0, 0 } +}; + + +struct { + int dtop; + int type; + int value; + char* name; +} commonMeleeWeapons[] = { + { 4, PIERCING, 302, "dagger" }, + { 14, SLASHING, 320, "greataxe" }, + { 24, SLASHING, 350, "greatsword" }, + { 28, SLASHING, 302, "kama" }, + { 41, SLASHING, 315, "longsword" }, + { 45, BLUDGEON, 305, "light mace" }, + { 50, BLUDGEON, 312, "heavy mace" }, + { 54, BLUDGEON, 302, "nunchaku" }, + { 57, BLUDGEON, 600, "quarterstaff" }, + { 61, PIERCING, 320, "rapier" }, + { 66, SLASHING, 315, "scimitar" }, + { 70, PIERCING, 302, "shortspear" }, + { 74, PIERCING, 303, "siangham" }, + { 84, SLASHING, 335, "bastard sword" }, + { 89, PIERCING, 310, "short sword" }, + { 100, SLASHING, 330, "dwarven waraxe" }, + { 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int value; + int type; + int dtype; + char* name; +} uncommonWeapons[] = { + { 3, 660, MELEE, SLASHING, "orc double axe" }, + { 7, 310, MELEE, SLASHING, "battleaxe" }, + { 10, 325, MELEE, PIERCING, "spiked chain" }, + { 12, 300, MELEE, BLUDGEON, "club" }, + { 16, 400, RANGED, PIERCING, "hand crossbow" }, + { 19, 550, RANGED, PIERCING, "repeating crossbow" }, + { 21, 302, MELEE, PIERCING, "punching dagger" }, + { 23, 375, MELEE, SLASHING, "falchion" }, + { 26, 690, MELEE, BLUDGEON, "dire flail" }, + { 31, 315, MELEE, BLUDGEON, "heavy flail" }, + { 35, 308, MELEE, BLUDGEON, "light flail" }, + { 37, 302, MELEE, BLUDGEON, "gauntlet" }, + { 39, 305, MELEE, PIERCING, "spiked gauntlet" }, + { 41, 308, MELEE, SLASHING, "glaive" }, + { 43, 305, MELEE, BLUDGEON, "greatclub" }, + { 45, 309, MELEE, SLASHING, "guisarme" }, + { 48, 310, MELEE, PIERCING | SLASHING, "halberd" }, + { 51, 301, MELEE, PIERCING, "halfspear" }, + { 54, 620, MELEE, PIERCING | BLUDGEON, "gnome hooked hammer" }, + { 56, 301, MELEE, BLUDGEON, "light hammer" }, + { 58, 306, MELEE, SLASHING, "handaxe" }, + { 61, 308, MELEE, SLASHING, "kukri" }, + { 63, 310, MELEE, PIERCING, "heavy lance" }, + { 65, 306, MELEE, PIERCING, "light lance" }, + { 67, 305, MELEE, PIERCING, "longspear" }, + { 70, 308, MELEE, PIERCING | BLUDGEON, "morningstar" }, + { 72, 320, RANGED, BLUDGEON, "net" }, + { 74, 308, MELEE, PIERCING, "heavy pick" }, + { 76, 304, MELEE, PIERCING, "light pick" }, + { 78, 310, MELEE, PIERCING, "ranseur" }, + { 80, 301, MELEE, BLUDGEON, "sap" }, + { 82, 318, MELEE, SLASHING | PIERCING, "scythe" }, + { 84, 301, RANGED, PIERCING, "shuriken" }, + { 86, 306, MELEE, SLASHING, "sickle" }, + { 89, 700, MELEE, SLASHING, "two-bladed sword" }, + { 91, 315, MELEE, PIERCING, "trident" }, + { 94, 650, MELEE, SLASHING | PIERCING, "dwarven urgosh" }, + { 97, 312, MELEE, BLUDGEON, "warhammer" }, + { 100, 301, MELEE, SLASHING, "whip" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int value; + int type; + char* name; +} commonRangedWeapons[] = { + { 10, 0, BLUDGEON, "ammunition" }, + { 50, 350, PIERCING, "arrows (50)" }, + { 80, 350, PIERCING, "crossbow bolts (50)" }, + { 100, 350, BLUDGEON, "sling bullets (50)" }, + { 115, 308, SLASHING, "throwing axe" }, + { 125, 350, PIERCING, "heavy crossbow" }, + { 135, 335, PIERCING, "light crossbow" }, + { 139, 300, PIERCING, "dart" }, + { 141, 301, PIERCING, "javelin" }, + { 146, 330, PIERCING, "shortbow" }, + { 151, 375, PIERCING, "composite shortbow" }, + { 156, 450, PIERCING, "mighty composite shortbow (+1 Str bonus)" }, + { 161, 525, PIERCING, "mighty composite shortbow (+2 Str bonus)" }, + { 165, 300, PIERCING, "sling" }, + { 175, 375, PIERCING, "longbow" }, + { 180, 400, PIERCING, "composite longbow" }, + { 185, 500, PIERCING, "mighty composite longbow (+1 Str bonus)" }, + { 190, 600, PIERCING, "mighty composite longbow (+2 Str bonus)" }, + { 195, 700, PIERCING, "mighty composite longbow (+3 Str bonus)" }, + { 200, 800, PIERCING, "mighty composite longbow (+4 Str bonus)" }, + { 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int other; + int plus; + int restrict; + int standing; + char* name; +} meleeSpecials[] = { + { 15, 10, 0, NONE, 1, ANY, NONE, "defending" }, + { 25, 15, 3, NONE, 1, ANY, NONE, "flaming" }, + { 35, 20, 6, NONE, 1, ANY, NONE, "frost" }, + { 45, 25, 9, NONE, 1, ANY, NONE, "shocking" }, + { 55, 30, 12, NONE, 1, ANY, NONE, "ghost touch" }, + { 70, 40, 0, NONE, 1, SLASHING | PIERCING, NONE, "keen" }, + { 80, 50, 17, NONE, 1, ANY, NONE, "mighty cleaving" }, + { 89, 51, 19, NONE, 1, ANY, NONE, "spell storing" }, + { 99, 56, 21, NONE, 1, ANY, NONE, "throwing" }, + { 0, 59, 26, BANE, 2, ANY, NONE, "bane of " }, + { 0, 62, 29, NONE, 2, BLUDGEON, NONE, "disruption" }, + { 0, 65, 33, NONE, 2, ANY, NONE, "flaming burst" }, + { 0, 68, 37, NONE, 2, ANY, NONE, "icy burst" }, + { 0, 71, 41, NONE, 2, ANY, NONE, "shocking burst" }, + { 0, 76, 44, NONE, 2, ANY, NONE, "thundering" }, + { 0, 79, 47, NONE, 2, ANY, NONE, "wounding" }, + { 0, 82, 52, NONE, 2, ANY, opGOOD, "holiness" }, + { 0, 85, 57, NONE, 2, ANY, opEVIL, "unholiness" }, + { 0, 88, 62, NONE, 2, ANY, opLAWFUL, "lawfulness" }, + { 0, 91, 67, NONE, 2, ANY, opCHAOTIC, "chaos" }, + { 0, 92, 71, NONE, 4, ANY, NONE, "brilliant energy" }, + { 0, 93, 73, NONE, 4, ANY, NONE, "dancing" }, + { 0, 95, 76, NONE, 4, ANY, NONE, "speed" }, + { 0, 0, 80, NONE, 5, SLASHING, NONE, "vorpal" }, + { 100, 100, 100, NONE, 0, ANY, NONE, "-" }, + { 0, 0, 0, 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int other; + int plus; + int standing; + char* name; +} rangedSpecials[] = { + { 20, 15, 0, NONE, 1, NONE, "returning" }, + { 40, 30, 0, NONE, 1, NONE, "distance" }, + { 60, 35, 10, NONE, 1, NONE, "flaming" }, + { 80, 40, 20, NONE, 1, NONE, "shocking" }, + { 100, 45, 30, NONE, 1, NONE, "frost" }, + { 0, 50, 40, NONE, 2, NONE, "flaming burst" }, + { 0, 55, 50, NONE, 2, NONE, "icy burst" }, + { 0, 60, 60, NONE, 2, NONE, "shocking burst" }, + { 0, 66, 65, BANE, 2, NONE, "bane of " }, + { 0, 74, 70, NONE, 2, opGOOD, "holiness" }, + { 0, 82, 75, NONE, 2, opEVIL, "unholiness" }, + { 0, 90, 80, NONE, 2, opLAWFUL, "lawfulness" }, + { 0, 98, 85, NONE, 2, opCHAOTIC, "chaos" }, + { 0, 0, 90, NONE, 4, NONE, "speed" }, + { 0, 0, 97, NONE, 4, NONE, "brilliant energy" }, + { 0, 100, 100, NONE, 0, NONE, "-" }, + { 0, 0, 0, 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int standing; + char* name; +} baneTable[] = { + { 5, NONE, "aberrations" }, + { 8, NONE, "animals" }, + { 13, NONE, "beasts" }, + { 20, NONE, "constructs" }, + { 25, NONE, "dragons" }, + { 30, NONE, "elementals" }, + { 35, NONE, "fey" }, + { 40, NONE, "giants" }, + { 45, NONE, "magical beasts" }, + { 50, NONE, "monstrous humanoids" }, + { 53, NONE, "oozes" }, + { 58, opLAWFUL, "chaotic outsiders" }, + { 65, opGOOD, "evil outsiders" }, + { 70, opEVIL, "good outsiders" }, + { 75, opCHAOTIC, "lawful outsiders" }, + { 77, NONE, "plants" }, + { 85, NONE, "shapechangers" }, + { 92, NONE, "undead" }, + { 94, NONE, "vermin" }, + { 100, NONE, "one humanoid subtype" }, + { 0, NONE, 0 } +}; + + +struct { + int mediumDtop; + int majorDtop; + int value; + int other; + char* name; +} specificWeapons[] = { + { 20, 0, 132, NONE, "sleep arrow" }, + { 40, 0, 257, NONE, "screaming bolt" }, + { 55, 4, 751, NONE, "javelin of lightning" }, + { 65, 9, 2282, BANE, "slaying arrow" }, + { 70, 0, 3302, NONE, "adamantine dagger" }, + { 72, 11, 3815, NONE, "trident of fish command" }, + { 0, 13, 4057, BANE, "greater slaying arrow" }, + { 74, 17, 9302, NONE, "dagger of venom" }, + { 76, 20, 9310, NONE, "adamantine battleaxe" }, + { 79, 25, 9815, NONE, "trident of warning" }, + { 82, 30, 10302, NONE, "assassin's dagger" }, + { 85, 35, 15310, NONE, "sword of subtlety" }, + { 88, 40, 17812, NONE, "mace of terror" }, + { 91, 45, 25315, NONE, "nine lives stealer" }, + { 94, 50, 27875, NONE, "oathbow" }, + { 96, 55, 30315, NONE, "sword of life stealing" }, + { 98, 60, 32315, NONE, "flame tongue" }, + { 100, 66, 40320, NONE, "life-drinker" }, + { 0, 72, 49350, NONE, "frost brand" }, + { 0, 78, 50320, NONE, "rapier of puncturing" }, + { 0, 81, 50335, NONE, "sun blade" }, + { 0, 83, 52315, NONE, "sword of the planes" }, + { 0, 85, 55815, NONE, "sylvan scimitar" }, + { 0, 87, 60312, NONE, "dwarven thrower" }, + { 0, 90, 75312, NONE, "mace of smiting" }, + { 0, 96, 120315, NONE, "holy avenger" }, + { 0, 100, 170560, NONE, "luck blade" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int value; + char* name; +} potionTable[] = { + { 5, 0, 0, 50, "jump" }, + { 10, 0, 0, 50, "spider climb" }, + { 19, 0, 0, 50, "cure light wounds" }, + { 20, 1, 0, 150, "love" }, + { 24, 2, 0, 150, "vision" }, + { 28, 3, 0, 150, "swimming" }, + { 32, 4, 0, 150, "hiding" }, + { 36, 5, 0, 150, "sneaking" }, + { 37, 6, 0, 150, "oil of timelessness" }, + { 42, 7, 0, 250, "reduce (at 5th level)" }, + { 47, 8, 0, 250, "enlarge (at 5th level)" }, + { 50, 9, 0, 300, "speak with animals" }, + { 53, 10, 1, 300, "clairaudience/clairvoyance" }, + { 56, 12, 2, 300, "charisma" }, + { 59, 14, 3, 300, "intelligence" }, + { 62, 16, 4, 300, "wisdom" }, + { 65, 18, 5, 300, "alter self" }, + { 68, 21, 7, 300, "blur" }, + { 71, 24, 8, 300, "darkvision" }, + { 74, 26, 9, 300, "ghoul touch" }, + { 77, 29, 10, 300, "delay poison" }, + { 80, 32, 13, 300, "endurance" }, + { 83, 40, 16, 300, "cure moderate wounds" }, + { 86, 45, 19, 300, "detect thoughts" }, + { 89, 50, 22, 300, "levitate" }, + { 91, 55, 25, 300, "aid" }, + { 93, 60, 30, 300, "invisibility" }, + { 94, 65, 35, 300, "lesser restoration" }, + { 95, 70, 40, 300, "cat's grace" }, + { 96, 75, 45, 300, "bull's strength" }, + { 97, 77, 46, 500, "truth" }, + { 98, 79, 47, 500, "glibness" }, + { 99, 84, 49, 750, "nondetection" }, + { 100, 87, 51, 750, "tongues" }, + { 0, 91, 53, 750, "water breathing" }, + { 0, 92, 55, 750, "remove paralysis" }, + { 0, 93, 57, 750, "remove blindness/deafness" }, + { 0, 94, 59, 750, "remove disease" }, + { 0, 96, 69, 750, "neutralize poison" }, + { 0, 97, 73, 750, "cure serious wounds" }, + { 0, 98, 75, 750, "fly" }, + { 0, 0, 77, 750, "protection from elements (cold)" }, + { 0, 0, 79, 750, "protection from elements (electricity)" }, + { 0, 0, 83, 750, "protection from elements (fire)" }, + { 0, 0, 85, 750, "protection from elements (acid)" }, + { 0, 0, 87, 750, "protection from elements (sonic)" }, + { 0, 0, 90, 750, "haste" }, + { 0, 0, 93, 750, "gaseous form" }, + { 0, 0, 95, 900, "oil of slipperiness" }, + { 0, 100, 98, 900, "heroism" }, + { 0, 0, 100, 900, "fire breath" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int dcount; + int dtype; + int value; + char* name; +} ringTable[] = { + { 5, 0, 0, 0, 0, 2000, "climbing" }, + { 10, 0, 0, 0, 0, 2000, "jumping" }, + { 25, 0, 0, 0, 0, 2000, "protection +1" }, + { 30, 0, 0, 0, 0, 2100, "warmth" }, + { 40, 0, 0, 0, 0, 2200, "feather falling" }, + { 45, 0, 0, 0, 0, 2300, "swimming" }, + { 50, 0, 0, 0, 0, 2500, "sustenance" }, + { 55, 5, 0, 0, 0, 4000, "counterspells" }, + { 60, 10, 0, 0, 0, 8000, "mind shielding" }, + { 70, 20, 0, 0, 0, 8000, "protection +2" }, + { 75, 25, 0, 0, 0, 8500, "force shield" }, + { 80, 30, 1, 1, 50, 8600, "the ram" }, + { 85, 35, 2, 0, 0, 9500, "animal friendship" }, + { 90, 40, 3, 0, 0, 12000, "chameleon power" }, + { 95, 45, 4, 0, 0, 15000, "water walking" }, + { 100, 50, 6, 0, 0, 16000, "minor elemental resistance" }, + { 0, 60, 10, 0, 0, 18000, "protection +3" }, + { 0, 70, 15, 0, 0, 20000, "invisibility" }, + { 0, 75, 20, 0, 0, 20000, "wizardry (I)" }, + { 0, 80, 25, 0, 0, 24000, "major elemental resistance" }, + { 0, 82, 30, 0, 0, 25000, "x-ray vision" }, + { 0, 84, 35, 0, 0, 25000, "evasion" }, + { 0, 86, 40, 0, 0, 30000, "blinking" }, + { 0, 88, 45, 0, 0, 32000, "protection +4" }, + { 0, 90, 50, 0, 0, 40000, "wizardry (II)" }, + { 0, 92, 55, 0, 0, 40000, "freedom of movement" }, + { 0, 94, 60, 0, 0, 50000, "friend shield" }, + { 0, 96, 65, 0, 0, 50000, "protection +5" }, + { 0, 98, 70, 0, 0, 50000, "shooting stars" }, + { 0, 99, 75, 0, 0, 75000, "telekinesis" }, + { 0, 100, 80, 0, 0, 80000, "wizardry (III)" }, + { 0, 0, 84, 0, 0, 90000, "spell storing" }, + { 0, 0, 87, 0, 0, 90000, "regeneration" }, + { 0, 0, 89, 1, 3, 97950, "three wishes" }, + { 0, 0, 92, 0, 0, 100000, "wizardry (IV)" }, + { 0, 0, 94, 0, 0, 125000, "djinni calling" }, + { 0, 0, 96, 0, 0, 150000, "spell turning" }, + { 0, 0, 97, 0, 0, 200000, "air elemental command" }, + { 0, 0, 98, 0, 0, 200000, "earth elemental command" }, + { 0, 0, 99, 0, 0, 200000, "fire elemental command" }, + { 0, 0, 100, 0, 0, 200000, "water elemental command" }, + { 0, 0, 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int value; + char* name; +} rodTable[] = { + { 0, 6, 0, 7500, "immovable" }, + { 0, 12, 0, 10500, "metal and mineral detection" }, + { 0, 20, 5, 11000, "cancellation" }, + { 0, 25, 10, 12000, "wonder" }, + { 0, 29, 15, 13000, "python" }, + { 0, 34, 20, 15000, "flame extinguishing" }, + { 0, 40, 27, 17000, "withering" }, + { 0, 45, 33, 19000, "viper" }, + { 0, 52, 40, 23000, "thunder and lightning" }, + { 0, 60, 50, 23500, "enemy detection" }, + { 0, 68, 55, 25000, "splendor" }, + { 0, 78, 65, 35000, "negation" }, + { 0, 90, 80, 40000, "flailing" }, + { 0, 96, 85, 50000, "absorption" }, + { 0, 99, 90, 60000, "rulership" }, + { 0, 100, 94, 61000, "security" }, + { 0, 0, 98, 70000, "lordly might" }, + { 0, 0, 100, 72000, "alertness" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int type; +} scrollTypes[] = { + { 70, ARCANE }, + { 100, DIVINE }, + { 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int level; + int caster; +} spellLevels[] = { + { 50, 0, 0, 1, 1 }, + { 95, 5, 0, 2, 3 }, + { 100, 65, 0, 3, 5 }, + { 0, 95, 5, 4, 7 }, + { 0, 100, 50, 5, 9 }, + { 0, 0, 70, 6, 11 }, + { 0, 0, 85, 7, 13 }, + { 0, 0, 95, 8, 15 }, + { 0, 0, 100, 9, 17 }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int value; + char* name; +} staffTable[] = { + { 0, 10, 0, 6500, "size alteration" }, + { 0, 20, 5, 12000, "charming" }, + { 0, 30, 15, 33000, "healing" }, + { 0, 40, 30, 29000, "fire" }, + { 0, 50, 40, 20000, "swarming insects" }, + { 0, 60, 50, 70000, "frost" }, + { 0, 70, 60, 85000, "earth and stone" }, + { 0, 80, 70, 80000, "defense" }, + { 0, 89, 80, 90000, "woodlands" }, + { 0, 95, 90, 130000, "life" }, + { 0, 100, 96, 180000, "passage" }, + { 0, 0, 100, 200000, "power" }, + { 0, 0, 0, 0, 0 } +}; + + +struct { + int minorDtop; + int mediumDtop; + int majorDtop; + int value; + char* name; +} wandTable[] = { + { 5, 0, 0, 375, "detect magic" }, + { 10, 0, 0, 375, "light" }, + { 15, 0, 0, 750, "detect secret doors" }, + { 20, 0, 0, 750, "color spray" }, + { 25, 0, 0, 750, "burning hands" }, + { 30, 3, 0, 750, "charm person" }, + { 35, 6, 0, 750, "enlarge" }, + { 40, 9, 0, 750, "magic missile (1st-level caster)" }, + { 45, 12, 0, 750, "shocking grasp" }, + { 50, 15, 0, 750, "summon monster I" }, + { 55, 18, 0, 750, "cure light wounds" }, + { 58, 21, 0, 2250, "magic missile (3rd-level caster)" }, + { 59, 23, 2, 3750, "magic missile (5th-level caster)" }, + { 63, 26, 3, 4500, "levitate" }, + { 66, 29, 4, 4500, "summon monster II" }, + { 69, 32, 5, 4500, "silence" }, + { 72, 35, 6, 4500, "knock" }, + { 75, 38, 7, 4500, "daylight" }, + { 78, 41, 10, 4500, "invisibility" }, + { 81, 44, 12, 4500, "shatter" }, + { 84, 48, 15, 4500, "bull's strength" }, + { 87, 50, 17, 4500, "mirror image" }, + { 90, 53, 19, 4500, "ghoul touch" }, + { 93, 60, 21, 4500, "cure moderate wounds" }, + { 96, 63, 23, 4500, "hold person" }, + { 98, 66, 25, 4500, "Melf's acid arrow" }, + { 99, 69, 27, 4500, "web" }, + { 100, 71, 30, 4500, "darkness" }, + { 0, 72, 33, 5250, "magic missile (7th-level caster)" }, + { 0, 0, 36, 6750, "magic missile (9th-level caster)" }, + { 0, 75, 39, 11250, "fireball (3rd-level caster)" }, + { 0, 80, 41, 11250, "lightning bolt (3rd-level caster)" }, + { 0, 82, 43, 11250, "summon monster III" }, + { 0, 84, 45, 11250, "keen edge" }, + { 0, 86, 47, 11250, "major image" }, + { 0, 88, 49, 11250, "slow" }, + { 0, 90, 51, 11250, "suggestion" }, + { 0, 92, 53, 11250, "dispel magic" }, + { 0, 94, 55, 11250, "cure serious wounds" }, + { 0, 95, 57, 11250, "contagion" }, + { 0, 96, 58, 11250, "charm person" }, + { 0, 97, 59, 13500, "fireball (6th-level caster)" }, + { 0, 99, 61, 13500, "searing light (6th-level caster)" }, + { 0, 100, 63, 13500, "lightning bolt (6th-level caster)" }, + { 0, 0, 65, 18000, "fireball (8th-level caster)" }, + { 0, 0, 67, 18000, "lightning bolt (8th-level caster)" }, + { 0, 0, 69, 21000, "charm monster" }, + { 0, 0, 71, 21000, "fear" }, + { 0, 0, 73, 21000, "improved invisibility" }, + { 0, 0, 75, 21000, "polymorph self" }, + { 0, 0, 77, 21000, "polymorph other" }, + { 0, 0, 79, 21000, "ice storm" }, + { 0, 0, 81, 21000, "summon monster IV" }, + { 0, 0, 83, 21000, "wall of ice" }, + { 0, 0, 84, 21000, "wall of fire" }, + { 0, 0, 85, 21000, "ray of enfeeblement (heightened to 4th-level spell)" }, + { 0, 0, 86, 21000, "poison" }, + { 0, 0, 87, 21000, "suggestion (heightened to 4th-level spell)" }, + { 0, 0, 89, 21000, "neutralize poison" }, + { 0, 0, 90, 21000, "inflict critical wounds" }, + { 0, 0, 92, 21000, "cure critical wounds" }, + { 0, 0, 93, 21100, "restoration" }, + { 0, 0, 94, 22500, "fireball (10th-level caster)" }, + { 0, 0, 95, 22500, "lightning bolt (10th-level caster)" }, + { 0, 0, 96, 24000, "holy smite (8th-level caster)" }, + { 0, 0, 97, 24000, "chaos hammer (8th-level caster)" }, + { 0, 0, 98, 24000, "unholy blight (8th-level caster)" }, + { 0, 0, 99, 24000, "order's wrath (8th-level caster)" }, + { 0, 0, 100, 37700, "stoneskin" }, + { 0, 0, 0, 0, 0 } +}; + + +typedef struct { + int dtop; + int value; + char* name; +} WONDROUSITEM; + +WONDROUSITEM minorItems[] = { + { 1, 25, "dull gray Ioun stone" }, + { 2, 50, "Quaal's feather token (anchor)" }, + { 3, 90, "everburning torch" }, + { 4, 100, "Quaal's feather token (tree)" }, + { 5, 200, "Quaal's feather token (fan)" }, + { 6, 250, "dust of tracelessness" }, + { 7, 300, "Quaal's feather token (bird)" }, + { 8, 450, "Quaal's feather token (swan boat)" }, + { 9, 500, "dust of illusion" }, + { 10, 500, "necklace of prayer beads (blessing)" }, + { 11, 500, "Quaal's feather token (whip)" }, + { 12, 800, "golembane scarab (flesh)" }, + { 13, 900, "gray bag of tricks" }, + { 14, 900, "dust of dryness" }, + { 15, 1000, "bracers of armor (+1)" }, + { 16, 1000, "cloak of resistance (+1)" }, + { 17, 1000, "eyes of the eagle" }, + { 18, 1000, "goggles of minute seeing" }, + { 19, 1000, "hand of the mage" }, + { 20, 1000, "pearl of power (1st-level spell)" }, + { 21, 1000, "phylactery of faithfulness" }, + { 22, 1000, "golembane scarab (clay)" }, + { 23, 1000, "stone of alarm" }, + { 24, 1150, "pipes of the sewers" }, + { 25, 1200, "golembane scarab (stone)" }, + { 26, 1500, "brooch of shielding" }, + { 27, 1600, "golembane scarab (iron)" }, + { 28, 1650, "necklace of fireballs (Type I)" }, + { 29, 1800, "pipes of sounding" }, + { 30, 1800, "quiver of Ehlonna" }, + { 31, 1800, "golembane scarab (flesh and clay)" }, + { 32, 1900, "horseshoes of speed" }, + { 33, 2000, "amulet of natural armor (+1)" }, + { 34, 2000, "bead of force" }, + { 35, 2000, "boots of elvenkind" }, + { 36, 2000, "cloak of elvenkind" }, + { 37, 2000, "hat of disguise" }, + { 38, 2000, "Heward's handy haversack" }, + { 39, 2000, "horn of fog" }, + { 40, 2000, "slippers of spider climbing" }, + { 41, 2000, "universal solvent" }, + { 42, 2000, "vest of escape" }, + { 43, 2100, "dust of appearance" }, + { 44, 2200, "glove of storing" }, + { 45, 2400, "sovereign glue" }, + { 46, 2500, "candle of truth" }, + { 47, 2500, "bag of holding (bag 1)" }, + { 48, 2500, "boots of the winterlands" }, + { 49, 2500, "boots of striding and springing" }, + { 50, 2500, "golembane scarab (any golem)" }, + { 51, 2600, "helm of comprehending languages and reading magic" }, + { 52, 2700, "necklace of fireballs (Type II)" }, + { 53, 3000, "rust bag of tricks" }, + { 54, 3000, "chime of opening" }, + { 55, 3000, "rope of climbing" }, + { 56, 3000, "horseshoes of a zephyr" }, + { 57, 3500, "dust of disappearance" }, + { 58, 3500, "lens of detection" }, + { 59, 3800, "figurine of wondrous power (silver raven)" }, + { 60, 4000, "bracers of armor (+2)" }, + { 61, 4000, "cloak of resistance (+2)" }, + { 62, 4000, "gloves of arrow snaring" }, + { 63, 4000, "dusty rose prism Ioun stone" }, + { 64, 4000, "Keoghtom's ointment" }, + { 65, 4000, "pearl of power (2nd-level spell)" }, + { 66, 4000, "periapt of proof against poison" }, + { 67, 4000, "stone salve" }, + { 68, 4000, "gauntlets of ogre power" }, + { 69, 4000, "bracers of health (+2)" }, + { 70, 4000, "gloves of Dexterity (+2)" }, + { 71, 4000, "headband of intellect (+2)" }, + { 72, 4000, "periapt of Wisdom (+2)" }, + { 73, 4000, "cloak of Charisma (+2)" }, + { 74, 4350, "necklace of fireballs (Type III)" }, + { 75, 4500, "circlet of persuasion" }, + { 76, 4550, "bracelet of friends" }, + { 77, 4900, "incense of meditation" }, + { 78, 5000, "bag of holding (bag 2)" }, + { 79, 5000, "clear spindle Ioun stone" }, + { 80, 5000, "necklace of prayer beads (karma)" }, + { 81, 5100, "bracers of archery" }, + { 82, 5200, "eversmoking bottle" }, + { 83, 5400, "necklace of fireballs (Type IV)" }, + { 84, 5500, "Murlynd's spoon" }, + { 85, 5500, "Nolzur's marvelous pigments" }, + { 86, 5500, "wind fan" }, + { 87, 5500, "wings of flying" }, + { 88, 5800, "druid's vestment" }, + { 89, 6000, "cloak of arachnida" }, + { 90, 6000, "gloves of swimming and climbing" }, + { 91, 6000, "horn of goodness/evil" }, + { 92, 6150, "necklace of fireballs (Type V)" }, + { 93, 6300, "tan bag of tricks" }, + { 94, 6480, "minor circlet of blasting" }, + { 95, 6500, "pipes of haunting" }, + { 96, 7000, "robe of useful items" }, + { 97, 7200, "hand of glory" }, + { 98, 7400, "bag of holding (bag 3)" }, + { 99, 2000, "boots of elvenkind" }, + { 100, 900, "gray bag of tricks" }, + { 0, 0, 0 } +}; + + +WONDROUSITEM mediumItems[] = { + { 1, 7500, "boots of levitation" }, + { 2, 7500, "harp of charming" }, + { 3, 7500, "periapt of health" }, + { 4, 7800, "candle of invocation" }, + { 5, 8000, "amulet of natural armor (+2)" }, + { 6, 8000, "boots of speed" }, + { 7, 8000, "dark blue rhomboid Ioun stone" }, + { 8, 8000, "deep red sphere Ioun stone" }, + { 9, 8000, "incandescent blue sphere Ioun stone" }, + { 10, 8000, "pale blue rhomboid Ioun stone" }, + { 11, 8000, "pink rhomboid Ioun stone" }, + { 12, 8000, "pink and green sphere Ioun stone" }, + { 13, 8000, "scarlet and blue sphere Ioun stone" }, + { 14, 8000, "goggles of night" }, + { 15, 8100, "necklace of fireballs (Type VI)" }, + { 16, 9000, "monk's belt" }, + { 17, 9000, "bracers or armor (+3)" }, + { 18, 9000, "cloak of resistance (+3)" }, + { 19, 9000, "decanter of endless water" }, + { 20, 9000, "pearl of power (3rd-level spell)" }, + { 21, 9000, "talisman of the sphere" }, + { 22, 9100, "figurine of wondrous power (serpentine owl)" }, + { 23, 9150, "necklace of fireball (Type VII)" }, + { 24, 9200, "deck of illusions" }, + { 25, 9500, "Boccob's blessed book" }, + { 26, 10000, "bag of holding (bag 4)" }, + { 27, 10000, "figurine of wondrous power (bronze griffon)" }, + { 28, 10000, "figurine of wondrous power (ebony fly)" }, + { 29, 10000, "necklace of prayer beads (healing)" }, + { 30, 10000, "robe of blending" }, + { 31, 10000, "stone of good luck (luckstone)" }, + { 32, 10000, "stone horse (courser)" }, + { 33, 10500, "folding boat" }, + { 34, 11000, "amulet of undead turning" }, + { 35, 11500, "gauntlet of rust" }, + { 36, 12000, "winged boots" }, + { 37, 12000, "horn of blasting" }, + { 38, 12000, "vibrant purple prism Ioun stone" }, + { 39, 12000, "medallion of thoughts" }, + { 40, 12000, "pipes of pain" }, + { 41, 12960, "cape of mountebank" }, + { 42, 13000, "lyre of building" }, + { 43, 14000, "portable hole" }, + { 44, 14500, "bottle of air" }, + { 45, 14800, "stone horse (destrier)" }, + { 46, 14900, "belt of dwarvenkind" }, + { 47, 15000, "iridescent spindle Ioun stone" }, + { 48, 15000, "necklace of prayer beads (smiting)" }, + { 49, 15000, "periapt of wound closure" }, + { 50, 15000, "scabbard of keen edges" }, + { 51, 15100, "broom of flying" }, + { 52, 15100, "horn of the tritons" }, + { 53, 15200, "gem of brightness" }, + { 54, 15300, "pearl of the sirines" }, + { 55, 15500, "figurine of wondrous power (onyx dog)" }, + { 56, 15800, "chime of interruption" }, + { 57, 16000, "bracers of armor (+4)" }, + { 58, 16000, "cloak of resistance (+4)" }, + { 59, 16000, "pearl of power (4th-level spell)" }, + { 60, 16000, "belt of giant strength (+4)" }, + { 61, 16000, "gloves of Dexterity (+4)" }, + { 62, 16000, "bracers of health (+4)" }, + { 63, 16000, "headband of intellect (+4)" }, + { 64, 16000, "periapt of Wisdom (+4)" }, + { 65, 16000, "cloak of Charisma (+4)" }, + { 66, 16500, "figurine of wondrous power (golden lions)" }, + { 67, 17000, "figurine of wondrous power (marble elephant)" }, + { 68, 18000, "amulet of natural armor (+3)" }, + { 69, 18000, "carpet of flying (3 ft. by 5 ft.)" }, + { 70, 19000, "necklace of adaptation" }, + { 71, 20000, "cloak of the manta ray" }, + { 72, 20000, "pale green prism Ioun stone" }, + { 73, 20000, "pale lavender ellipsoid Ioun stone" }, + { 74, 20000, "pearly white spindle Ioun stone" }, + { 75, 21000, "figurine of wondrous power (ivory goats)" }, + { 76, 21000, "rope of entanglement" }, + { 77, 22000, "cube of frost resistance" }, + { 78, 23000, "mattock of the titans" }, + { 79, 23760, "major circlet of blasting" }, + { 80, 24000, "cloak of the bat" }, + { 81, 24000, "helm of underwater action" }, + { 82, 24500, "eyes of doom" }, + { 83, 25000, "minor cloak of displacement (20% miss chance)" }, + { 84, 25000, "cloak of resistance (+5)" }, + { 85, 25000, "mask of the skull" }, + { 86, 25000, "maul of the titans" }, + { 87, 25000, "pearl of power (5th-level spell)" }, + { 88, 25000, "bracers of armor (+5)" }, + { 89, 26000, "dimensional shackles" }, + { 90, 26000, "iron bands of Bilarro" }, + { 91, 27000, "robe of scintillating colors" }, + { 92, 27500, "manual of bodily health +1" }, + { 93, 27500, "manual of gainful exercise +1" }, + { 94, 27500, "manual of quickness in action +1" }, + { 95, 27500, "tome of clear thought +1" }, + { 96, 27500, "tome of leadership and influence +1" }, + { 97, 27500, "tome of understanding +1" }, + { 98, 28500, "figurine of wondrous power (obsidian steed)" }, + { 99, 29000, "carpet of flying (4 ft. by 6 ft.)" }, + { 100, 7500, "boots of levitation" }, + { 0, 0, 0 } +}; + + +WONDROUSITEM majorItems[] = { + { 2, 30000, "lantern of revealing" }, + { 4, 30000, "necklace of prayer beads (wind walking)" }, + { 6, 30000, "drums of panic" }, + { 8, 31000, "helm of telepathy" }, + { 10, 32000, "amulet of natural armor (+4)" }, + { 12, 35000, "amulet of proof against detection of location" }, + { 14, 36000, "bracers of armor (+6)" }, + { 15, 36000, "belt of giant strength (+6)" }, + { 16, 36000, "gloves of Dexterity (+6)" }, + { 17, 36000, "bracers of health (+6)" }, + { 18, 36000, "headband of intellect (+6)" }, + { 19, 36000, "periapt of Wisdom (+6)" }, + { 20, 36000, "cloak of Charisma (+6)" }, + { 22, 36000, "pearl of power (6th-level spell)" }, + { 24, 38000, "orb of storms" }, + { 26, 38000, "scarab of protection" }, + { 28, 40000, "lavender and green ellipsoid Ioun stone" }, + { 30, 40000, "ring gates" }, + { 31, 41000, "carpet of flying (5 ft. by 7 ft.)" }, + { 32, 42000, "crystal ball" }, + { 33, 48600, "helm of teleportation" }, + { 34, 49000, "bracers of armor (+7)" }, + { 35, 50000, "pearl of power (7th-level spell)" }, + { 36, 50000, "amulet of natural armor (+5)" }, + { 37, 50000, "major cloak of displacement (50% miss chance)" }, + { 38, 50000, "crystal ball with detect invisibility" }, + { 39, 50000, "horn of Valhalla" }, + { 40, 50000, "necklace of prayer beads (summons)" }, + { 41, 51000, "crystal ball with detect thoughts" }, + { 42, 52000, "cloak of etherealness" }, + { 43, 53000, "carpet of flying (6 ft. by 9 ft.)" }, + { 44, 55000, "Daern's instant fortress" }, + { 45, 55000, "manual of bodily health +2" }, + { 46, 55000, "manual of gainful exercise +2" }, + { 47, 55000, "manual of quickness in action +2" }, + { 48, 55000, "tome of clear thought +2" }, + { 49, 55000, "tome of leadership and influence +2" }, + { 50, 55000, "tome of understanding +2" }, + { 51, 56000, "eyes of charming" }, + { 52, 58000, "robe of stars" }, + { 53, 60000, "darkskull" }, + { 54, 62000, "cube of force" }, + { 55, 64000, "bracers of armor (+8)" }, + { 56, 64000, "pearl of power (8th-level spell)" }, + { 57, 70000, "crystal ball with telepathy" }, + { 58, 70000, "pearl of power (two spells)" }, + { 59, 75000, "gem of seeing" }, + { 60, 75000, "robe of the archmagi" }, + { 61, 76000, "vestments of faith" }, + { 62, 80000, "amulet of the planes" }, + { 63, 80000, "crystal ball with true seeing" }, + { 64, 81000, "pearl of power (9th-level spell)" }, + { 65, 82000, "well of many worlds" }, + { 66, 82500, "manual of bodily health +3" }, + { 67, 82500, "manual of gainful exercise +3" }, + { 68, 82500, "manual of quickness in action +3" }, + { 69, 82500, "tome of clear thought +3" }, + { 70, 82500, "tome of leadership and influence +3" }, + { 71, 82500, "tome of understanding +3" }, + { 72, 90000, "mantle of spell resistance" }, + { 73, 90000, "robe of eyes" }, + { 74, 92000, "mirror of opposition" }, + { 75, 93000, "chaos diamond" }, + { 76, 98000, "eyes of petrification" }, + { 77, 100000, "bowl of commanding water elementals" }, + { 78, 100000, "brazier of commanding fire elementals" }, + { 79, 100000, "censer of controlling air elementals" }, + { 80, 100000, "stone of controlling earth elementals" }, + { 81, 110000, "manual of bodily health +4" }, + { 82, 110000, "manual of gainful exercise +4" }, + { 83, 110000, "manual of quickness in action +4" }, + { 84, 110000, "tome of clear thought +4" }, + { 85, 110000, "tome of leadership and influence +4" }, + { 86, 110000, "tome of understanding +4" }, + { 87, 130000, "apparatus of Kwalish" }, + { 88, 137500, "manual of bodily health +5" }, + { 89, 137500, "manual of gainful exercise +5" }, + { 90, 137500, "manual of quickness in action +5" }, + { 91, 137500, "tome of clear thought +5" }, + { 92, 137500, "tome of leadership and influence +5" }, + { 93, 137500, "tome of understanding +5" }, + { 94, 150000, "efreeti bottle" }, + { 95, 152000, "mirror of life trapping" }, + { 96, 156000, "cubic gate" }, + { 97, 157000, "helm of brilliance" }, + { 98, 170000, "iron flask" }, + { 99, 175000, "mirror of mental prowess" }, + { 100, 36000, "belt of giant strength (+6)" }, + { 0, 0, 0 } +}; + + +typedef struct { + int dtop; + int targeted; + int value; + char* name; +} SPELL; + + +SPELL arcane1[] = { + { 5, 0, 25, "burning hands" }, + { 10, 0, 25, "change self" }, + { 15, 1, 25, "charm person" }, + { 18, 0, 25, "color spray" }, + { 22, 0, 25, "detect secret doors" }, + { 25, 0, 25, "detect undead" }, + { 28, 1, 25, "enlarge" }, + { 31, 0, 25, "erase" }, + { 36, 0, 25, "feather fall" }, + { 39, 0, 25, "grease" }, + { 44, 0, 125, "identify" }, + { 47, 0, 25, "jump" }, + { 51, 0, 25, "mage armor" }, + { 54, 0, 25, "magic weapon" }, + { 57, 0, 25, "mount" }, + { 60, 1, 25, "ray of enfeeblement" }, + { 63, 1, 25, "reduce" }, + { 66, 0, 25, "shield" }, + { 69, 1, 25, "shocking grasp" }, + { 73, 0, 25, "silent image" }, + { 78, 1, 25, "sleep" }, + { 81, 0, 25, "spider climb" }, + { 84, 0, 25, "summon monster I" }, + { 87, 0, 25, "Tenser's floating disk" }, + { 92, 0, 25, "unseen servant" }, + { 95, 0, 25, "ventriloquism" }, + { 100, 0, 25, "magic missile" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane2[] = { + { 3, 0, 175, "arcane lock" }, + { 8, 1, 150, "blindness/deafness" }, + { 13, 0, 150, "blur" }, + { 18, 0, 150, "bull's strength" }, + { 22, 0, 150, "cat's grace" }, + { 25, 0, 150, "darkvision" }, + { 30, 0, 150, "detect thoughts" }, + { 33, 0, 150, "flaming sphere" }, + { 38, 0, 150, "invisibility" }, + { 41, 0, 150, "knock" }, + { 46, 0, 150, "levitate" }, + { 51, 0, 150, "locate object" }, + { 54, 1, 150, "Melf's acid arrow" }, + { 59, 0, 150, "minor image" }, + { 64, 0, 150, "mirror image" }, + { 69, 0, 150, "misdirection" }, + { 72, 0, 150, "protection from arrows" }, + { 77, 0, 150, "see invisibility" }, + { 80, 0, 150, "spectral hand" }, + { 83, 0, 150, "stinking cloud" }, + { 87, 0, 150, "summon monster II" }, + { 92, 0, 150, "summon swarm" }, + { 95, 1, 150, "web" }, + { 100, 1, 150, "ghoul touch" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane3[] = { + { 5, 0, 375, "blink" }, + { 10, 0, 375, "clairaudience/clairvoyance" }, + { 15, 1, 375, "dispel magic" }, + { 20, 0, 375, "displacement" }, + { 25, 1, 375, "fireball" }, + { 28, 1, 375, "flame arrow" }, + { 31, 0, 375, "fly" }, + { 33, 1, 375, "gaseous form" }, + { 36, 0, 375, "greater magic weapon" }, + { 39, 0, 375, "halt undead" }, + { 42, 0, 375, "haste" }, + { 45, 1, 375, "hold person" }, + { 47, 0, 375, "invisibility sphere" }, + { 53, 1, 375, "lightning bolt" }, + { 54, 0, 375, "magic circle against chaos" }, + { 55, 0, 375, "magic circle against evil" }, + { 56, 0, 375, "magic circle against good" }, + { 57, 0, 375, "magic circle against law" }, + { 60, 0, 425, "nondetection" }, + { 65, 0, 375, "slow" }, + { 70, 0, 375, "sepia snake sigil" }, + { 75, 0, 375, "suggestion" }, + { 79, 0, 375, "tongues" }, + { 87, 1, 375, "vampiric touch" }, + { 90, 0, 375, "water breathing" }, + { 100, 0, 375, "keen edge" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane4[] = { + { 5, 1, 700, "charm monster" }, + { 10, 1, 700, "confusion" }, + { 15, 1, 700, "contagion" }, + { 20, 0, 700, "detect scrying" }, + { 23, 1, 700, "dimensional anchor" }, + { 28, 1, 700, "dimension door" }, + { 33, 1, 700, "emotion" }, + { 36, 1, 700, "enervation" }, + { 39, 0, 700, "Evard's black tentacles" }, + { 44, 1, 700, "fear" }, + { 47, 0, 700, "fire shield" }, + { 50, 0, 700, "ice storm" }, + { 55, 0, 700, "improved invisibility" }, + { 58, 0, 700, "lesser geas" }, + { 61, 0, 700, "minor globe of invulnerability" }, + { 67, 0, 700, "phantasmal killer" }, + { 70, 1, 700, "polymorph other" }, + { 73, 0, 700, "polymorph self" }, + { 76, 0, 700, "remove curse" }, + { 79, 0, 700, "shadow conjuration" }, + { 82, 0, 950, "stoneskin" }, + { 84, 0, 700, "summon monster IV" }, + { 87, 0, 700, "wall of fire" }, + { 90, 0, 700, "wall of ice" }, + { 95, 0, 700, "shout" }, + { 100, 0, 700, "bestow curse" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane5[] = { + { 4, 0, 1125, "Bigby's interposing hand" }, + { 8, 0, 1125, "cloudkill" }, + { 13, 0, 1125, "cone of cold" }, + { 17, 1, 1125, "dismissal" }, + { 21, 1, 1125, "dominate person" }, + { 24, 1, 1125, "feeblemind" }, + { 27, 0, 1125, "greater shadow conjuration" }, + { 31, 1, 1125, "hold monster" }, + { 35, 0, 1125, "major creation" }, + { 40, 0, 1125, "mind fog" }, + { 44, 0, 1125, "passwall" }, + { 49, 0, 1125, "persistant image" }, + { 53, 0, 1125, "shadow evocation" }, + { 56, 0, 1125, "stone shape" }, + { 60, 0, 1125, "summon monster V" }, + { 64, 0, 1125, "telekinesis" }, + { 69, 0, 1125, "teleport" }, + { 73, 0, 1125, "transmute mud to rock" }, + { 77, 0, 1125, "transmute rock to mud" }, + { 81, 0, 1125, "wall of force" }, + { 86, 0, 1175, "wall of iron" }, + { 90, 0, 1125, "wall of stone" }, + { 95, 0, 1125, "permanency" }, + { 100, 0, 1125, "Mordenkainen's faithful hound" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane6[] = { + { 4, 0, 1650, "acid fog" }, + { 7, 0, 1650, "analyze dweomer" }, + { 11, 0, 1650, "antimagic field" }, + { 15, 0, 1650, "Bigby's forceful hand" }, + { 19, 1, 1650, "chain lightning" }, + { 23, 0, 2150, "circle of death" }, + { 26, 0, 1650, "control water" }, + { 30, 1, 1650, "disintegrate" }, + { 33, 0, 1650, "eyebite" }, + { 37, 1, 1650, "flesh to stone" }, + { 41, 0, 1650, "globe of invulnerability" }, + { 45, 0, 1650, "greater shadow evocation" }, + { 49, 0, 1650, "mass suggestion" }, + { 52, 0, 1650, "mislead" }, + { 57, 0, 1650, "move earth" }, + { 61, 0, 1650, "Otiluke's freezing sphere" }, + { 65, 0, 1650, "programmed image" }, + { 70, 0, 1650, "project image" }, + { 75, 1, 1650, "repulsion" }, + { 78, 0, 1650, "shades" }, + { 82, 0, 1650, "stone to flesh" }, + { 86, 0, 1650, "summon monster VI" }, + { 90, 0, 1650, "true seeing" }, + { 95, 0, 1900, "contingency" }, + { 100, 0, 2000, "legend lore" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane7[] = { + { 5, 0, 2275, "Bigby's grasping hand" }, + { 10, 0, 2275, "control undead" }, + { 15, 1, 2275, "delayed blast fireball" }, + { 20, 0, 2275, "ethereal jaunt" }, + { 25, 1, 2275, "finger of death" }, + { 30, 0, 3775, "forcecage" }, + { 35, 0, 3775, "limited wish" }, + { 40, 0, 2275, "mass invisibility" }, + { 45, 0, 2275, "Mordenkainen's sword" }, + { 50, 1, 2275, "Power word, stun" }, + { 55, 0, 2275, "prismatic spray" }, + { 60, 0, 2275, "reverse gravity" }, + { 65, 0, 2275, "sequester" }, + { 70, 0, 2275, "spell turning" }, + { 75, 0, 2275, "summon monster VII" }, + { 80, 0, 2275, "teleport without error" }, + { 85, 0, 3025, "vanish" }, + { 90, 0, 2275, "vision" }, + { 95, 0, 2275, "Mordenkainen's magnificent mansion" }, + { 100, 0, 2275, "plane shift" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane8[] = { + { 3, 0, 3000, "antipathy" }, + { 8, 0, 3000, "Bigby's clenched fist" }, + { 13, 0, 4000, "clone" }, + { 18, 0, 3000, "demand" }, + { 23, 1, 3000, "horrid wilting" }, + { 28, 0, 3000, "incendiary cloud" }, + { 33, 0, 3000, "mass charm" }, + { 38, 1, 3000, "maze" }, + { 43, 0, 3000, "mind blank" }, + { 48, 0, 3000, "Otiluke's telekinetic sphere" }, + { 53, 1, 3000, "Otto's irresistible dance" }, + { 58, 1, 3000, "polymorph any object" }, + { 63, 1, 3000, "power world, blind" }, + { 68, 0, 3000, "prismatic wall" }, + { 73, 0, 3000, "protection from spells" }, + { 78, 0, 3000, "screen" }, + { 83, 0, 3000, "summon monster VII" }, + { 88, 0, 3000, "sunburst" }, + { 90, 0, 4500, "sympathy" }, + { 95, 0, 13000, "symbol" }, + { 100, 0, 5000, "binding" }, + { 0, 0, 0, 0 } +}; + + +SPELL arcane9[] = { + { 7, 0, 3825, "Bigby's crushing hand" }, + { 14, 1, 3825, "energy drain" }, + { 21, 1, 3825, "imprisonment" }, + { 28, 1, 3825, "meteor swarm" }, + { 35, 0, 3825, "Mordenkainen's disjunction" }, + { 42, 0, 3825, "teleportation circle" }, + { 49, 0, 3825, "prismatic sphere" }, + { 56, 0, 3825, "shapechange" }, + { 63, 0, 3825, "summon monster IX" }, + { 69, 0, 3825, "time stop" }, + { 76, 0, 3825, "wail of the banshee" }, + { 83, 0, 3825, "weird" }, + { 90, 0, 28825, "wish" }, + { 95, 0, 3825, "gate" }, + { 100, 0, 3825, "foresight" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine1[] = { + { 5, 0, 25, "bless" }, + { 10, 0, 25, "calm animals" }, + { 14, 0, 25, "command" }, + { 19, 0, 25, "cure light wounds" }, + { 22, 0, 25, "detect chaos" }, + { 25, 0, 25, "detect evil" }, + { 28, 0, 25, "detect good" }, + { 31, 0, 25, "detect law" }, + { 34, 0, 25, "detect snares and pits" }, + { 39, 0, 25, "doom" }, + { 44, 0, 25, "entangle" }, + { 49, 0, 25, "faerie fire" }, + { 54, 1, 25, "inflict light wounds" }, + { 59, 0, 25, "invisibility to animals" }, + { 64, 0, 25, "invisibility to undead" }, + { 67, 0, 25, "magic fang" }, + { 70, 0, 25, "magic stone" }, + { 73, 0, 25, "magic weapon" }, + { 77, 0, 25, "sanctuary" }, + { 82, 0, 25, "shillelagh" }, + { 86, 0, 25, "summon monster I" }, + { 90, 0, 25, "summon nature's ally I" }, + { 95, 0, 25, "pass without trace" }, + { 100, 0, 25, "endure elements" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine2[] = { + { 5, 0, 150, "aid" }, + { 10, 0, 150, "augury" }, + { 15, 0, 150, "barkskin" }, + { 20, 0, 150, "bull's strength" }, + { 25, 0, 150, "charm person or animal" }, + { 28, 0, 150, "chill metal" }, + { 31, 0, 150, "cure moderate wounds" }, + { 36, 0, 150, "delay poison" }, + { 39, 0, 150, "flame blade" }, + { 42, 0, 150, "flaming sphere" }, + { 47, 0, 150, "heat metal" }, + { 50, 0, 150, "hold animal" }, + { 55, 1, 150, "hold person" }, + { 58, 1, 150, "inflict moderate wounds" }, + { 63, 0, 150, "lesser restoration" }, + { 67, 1, 150, "silence" }, + { 70, 0, 150, "speak with animals" }, + { 75, 0, 150, "spiritual weapon" }, + { 79, 0, 150, "summon monster II" }, + { 83, 0, 150, "summon nature's ally II" }, + { 85, 0, 150, "summon swarm" }, + { 90, 0, 150, "undetectable alignment" }, + { 95, 0, 150, "find traps" }, + { 100, 0, 150, "zone of truth" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine3[] = { + { 2, 0, 375, "call lightning" }, + { 9, 0, 375, "cure serious wounds" }, + { 13, 1, 375, "dispel magic" }, + { 15, 1, 375, "dominate animal" }, + { 17, 0, 375, "greater magic fang" }, + { 19, 1, 375, "inflict serious wounds" }, + { 22, 0, 375, "invisibility purge" }, + { 26, 0, 375, "locate object" }, + { 28, 0, 375, "magic circle against chaos" }, + { 30, 0, 375, "magic circle against evil" }, + { 32, 0, 375, "magic circle against good" }, + { 34, 0, 375, "magic circle against law" }, + { 38, 0, 375, "negative energy protection" }, + { 41, 0, 375, "neutralize poison" }, + { 43, 0, 375, "plant growth" }, + { 46, 0, 375, "prayer" }, + { 51, 0, 375, "protection from elements" }, + { 53, 0, 375, "remove blindness/deafness" }, + { 56, 0, 375, "remove curse" }, + { 59, 0, 375, "remove disease" }, + { 62, 0, 375, "searing light" }, + { 65, 0, 375, "speak with dead" }, + { 67, 0, 375, "spike growth" }, + { 72, 0, 375, "stone shape" }, + { 75, 0, 375, "summon monster III" }, + { 78, 0, 375, "summon nature's ally III" }, + { 80, 0, 375, "water breathing" }, + { 90, 0, 375, "water walk" }, + { 95, 0, 375, "meld into stone" }, + { 100, 0, 375, "deeper darkness" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine4[] = { + { 2, 0, 700, "antiplant shell" }, + { 5, 0, 700, "control water" }, + { 12, 0, 700, "cure critical wounds" }, + { 19, 0, 700, "discern lies" }, + { 24, 1, 700, "dispel magic" }, + { 27, 0, 700, "divine power" }, + { 34, 0, 700, "flame strike" }, + { 41, 0, 700, "freedom of movement" }, + { 47, 0, 700, "giant vermin" }, + { 50, 0, 700, "greater magic weapon" }, + { 53, 1, 700, "inflict critical wounds" }, + { 55, 0, 700, "lesser planar ally" }, + { 62, 0, 700, "neutralize poison" }, + { 66, 0, 700, "quench" }, + { 68, 0, 800, "restoration" }, + { 71, 0, 700, "rusting grasp" }, + { 74, 0, 700, "spell immunity" }, + { 76, 0, 700, "spike stones" }, + { 80, 0, 700, "summon monster IV" }, + { 82, 0, 700, "summon nature's ally IV" }, + { 90, 0, 700, "tongues" }, + { 95, 0, 700, "death ward" }, + { 100, 0, 700, "sleet storm" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine5[] = { + { 7, 0, 1125, "break enchantment" }, + { 13, 0, 1625, "commune" }, + { 15, 0, 1125, "control winds" }, + { 22, 0, 1125, "cure critical wounds" }, + { 26, 0, 1125, "dispel evil" }, + { 29, 0, 1125, "dispel good" }, + { 35, 0, 1125, "flame strike" }, + { 38, 0, 1125, "greater command" }, + { 40, 0, 6125, "hallow" }, + { 43, 0, 1125, "healing circle" }, + { 45, 0, 1125, "ice storm" }, + { 50, 0, 1125, "insect plague" }, + { 57, 0, 1625, "raise dead" }, + { 60, 0, 1125, "righteous might" }, + { 63, 0, 1125, "slay living" }, + { 65, 0, 1125, "spell resistance" }, + { 67, 0, 1125, "summon monster V" }, + { 69, 0, 1125, "summon nature's ally V" }, + { 72, 0, 1125, "transmute rock to mud" }, + { 74, 0, 1325, "true seeing" }, + { 75, 0, 6125, "unhallow" }, + { 78, 0, 1125, "wall of fire" }, + { 80, 0, 1125, "wall of stone" }, + { 90, 0, 1125, "wall of thorns" }, + { 95, 0, 1125, "plane shift" }, + { 100, 0, 1125, "tree stride" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine6[] = { + { 8, 0, 1650, "antilife shell" }, + { 14, 0, 1650, "blade barrier" }, + { 19, 0, 1650, "find the path" }, + { 23, 0, 1650, "fire seeds" }, + { 28, 0, 1650, "geas/quest" }, + { 34, 1, 1650, "harm" }, + { 41, 1, 1650, "heal" }, + { 47, 0, 1650, "heroes' feast" }, + { 55, 0, 1650, "planar ally" }, + { 57, 0, 1650, "repel wood" }, + { 60, 0, 1650, "stone tell" }, + { 68, 0, 1650, "summon monster VI" }, + { 71, 0, 1650, "transport via plants" }, + { 77, 0, 1650, "wall of stone" }, + { 80, 0, 1650, "wind walk" }, + { 90, 0, 1650, "word of recall" }, + { 100, 0, 1800, "create undead" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine7[] = { + { 11, 0, 2275, "control weather" }, + { 18, 0, 2275, "creeping doom" }, + { 25, 0, 2275, "destruction" }, + { 32, 0, 2275, "dictum" }, + { 36, 0, 2275, "fire storm" }, + { 40, 0, 4775, "greater restoration" }, + { 47, 0, 2275, "holy word" }, + { 54, 0, 2275, "regenerate" }, + { 61, 0, 2275, "repulsion" }, + { 68, 0, 2275, "resurrection" }, + { 72, 0, 2275, "summon monster VII" }, + { 76, 0, 2275, "transmute metal to wood" }, + { 80, 0, 2575, "true seeing" }, + { 90, 0, 2275, "word of chaos" }, + { 95, 0, 3775, "refuge" }, + { 100, 0, 2275, "blasphemy" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine8[] = { + { 6, 0, 3000, "antimagic field" }, + { 12, 0, 3000, "creeping doom" }, + { 18, 0, 3000, "discern location" }, + { 25, 0, 3000, "earthquake" }, + { 30, 1, 3000, "finger of death" }, + { 35, 0, 3000, "fire storm" }, + { 44, 0, 3000, "holy aura" }, + { 50, 0, 3000, "mass heal" }, + { 56, 0, 3000, "repel metal or stone" }, + { 62, 0, 3000, "reverse gravity" }, + { 68, 0, 3000, "summon monster VIII" }, + { 74, 0, 3000, "sunburst" }, + { 80, 0, 3000, "unholy aura" }, + { 90, 0, 3000, "whirlwind" }, + { 100, 0, 3000, "animal shapes" }, + { 0, 0, 0, 0 } +}; + + +SPELL divine9[] = { + { 7, 0, 3825, "earthquake" }, + { 14, 0, 3825, "elemental swarm" }, + { 26, 1, 3825, "energy drain" }, + { 38, 1, 3825, "implosion" }, + { 50, 0, 28825, "miracle" }, + { 57, 0, 3825, "shapechange" }, + { 68, 0, 3825, "storm of vengeance" }, + { 80, 0, 3825, "summon monster IX" }, + { 90, 0, 8825, "true resurrection" }, + { 93, 0, 5825, "soul bind" }, + { 95, 0, 3825, "gate" }, + { 100, 0, 3825, "antipathy" }, + { 0, 0, 0, 0 } +}; + + +struct { + SPELL *arcane[9]; + SPELL *divine[9]; +} spellTable = { + { arcane1, arcane2, arcane3, arcane4, arcane5, arcane6, arcane7, arcane8, arcane9 }, + { divine1, divine2, divine3, divine4, divine5, divine6, divine7, divine8, divine9 } +}; + + +struct { + int dtop; + int twoBonus; + int primary; + int extraordinary; + int egoBonus; + int value; + char* communication; + char* other; +} intTable[] = { + { 34, 5, 1, 0, 0, 10000, "Semiempathy", "" }, + { 59, 6, 2, 0, 0, 15000, "Empathy", "" }, + { 79, 7, 2, 0, 0, 17500, "Speech", "" }, + { 91, 8, 3, 0, 0, 25000, "Speech", "" }, + { 97, 9, 3, 0, 1, 32000, "Speech", "reads all languages spoken" }, + { 98, 10, 3, 1, 2, 55000, "Speech, telepathy", "reads all languages spoken" }, + { 99, 11, 3, 2, 3, 78000, "Speech, telepathy", "reads all languages and magic" }, + { 100, 12, 4, 2, 3, 90000, "Speech, telepathy", "reads all languages and magic" }, + { 0, 0, 0, 0, 0, 0, 0 } +}; + + +struct { + int die; + char* hi; + char* med; + char* lo; +} intAbilities[] = { + { 1, "Int", "Cha", "Wis" }, + { 2, "Int", "Wis", "Cha" }, + { 3, "Wis", "Int", "Cha" }, + { 4, "Cha", "Int", "Wis" }, + { 0, 0, 0, 0 } +}; + + +struct { + int dtop; + int standing; + char* alignment; +} alignments[] = { + { 5, opCHAOTIC | opGOOD, "Chaotic good" }, + { 15, opCHAOTIC, "Chaotic neutral" }, + { 20, opCHAOTIC | opEVIL, "Chaotic evil" }, + { 25, opEVIL, "Neutral evil" }, + { 30, opLAWFUL | opEVIL, "Lawful evil" }, + { 55, opLAWFUL | opGOOD, "Lawful good" }, + { 60, opLAWFUL, "Lawful neutral" }, + { 80, opGOOD, "Neutral good" }, + { 100, NONE, "Neutral" }, + { 0, 0 } +}; + + +struct { + int dtop; + int allowDup; + char* name; +} primaryAbilities[] = { + { 4, 1, "Item can Intuit Direction (10 ranks)" }, + { 8, 1, "Item can Sense Motive (10 ranks)" }, + { 12, 0, "Wielder has free use of Combat Reflexes" }, + { 16, 0, "Wielder has free use of Blind-Fight" }, + { 20, 0, "Wielder has free use of Improved Initiative" }, + { 24, 0, "Wielder has free use of Mobility" }, + { 28, 0, "Wielder has free use of Sunder" }, + { 32, 0, "Wielder has free use of Expertise" }, + { 39, 0, "Detect opposing alignment at will" }, + { 42, 0, "Find traps at will" }, + { 47, 0, "Detect secret doors at will" }, + { 54, 0, "Detect magic at will" }, + { 57, 0, "Wielder has free use of uncanny dodge (as a 5th-level barbarian)" }, + { 60, 0, "Wielder has free use of evasion" }, + { 65, 0, "Wielder can see invisible at will" }, + { 70, 1, "Cure light wounds (1d8+5) on wielder 1/day" }, + { 75, 1, "Feather fall on wielder 1/day" }, + { 76, 1, "Locate object in a 120-ft. radius" }, + { 77, 0, "Wielder does not need to sleep" }, + { 78, 0, "Wielder does not need to breathe" }, + { 79, 1, "Jump for 20 minutes on wielder 1/day" }, + { 80, 1, "Spider climb for 20 minutes on wielder 1/day" }, + { 90, 1, "-" }, /* roll twice more */ + { 100, 1, "*" }, /* roll on extraordinary abilities table */ + { 0, 0 } +}; + + +struct { + int dtop; + int allowDup; + char* name; +} extraAbilities[] = { + { 5, 1, "Charm person (DC 11) on contact, 3/day" }, + { 10, 1, "Clairaudience/clairvoyance (100-ft. range, 1 minute per use), 3/day" }, + { 15, 1, "Magic missile (200-ft. range, 3 missiles), 3/day" }, + { 20, 1, "Shield on wielder, 3/day" }, + { 25, 1, "Detect thoughts (100-ft. range, 1 minute per use), 3/day" }, + { 30, 1, "Levitation (wielder only, 10 minute duration), 3/day" }, + { 35, 1, "Invisibility (wielder only, up to 30 minutes per use), 3/day" }, + { 40, 1, "Fly (30 minutes per use), 2/day" }, + { 45, 1, "Lightning bolt (8d6 points of damage, 200-ft. range, DC 13), 1/day" }, + { 50, 1, "Summon monster III, 1/day" }, + { 55, 1, "Telepathy (100 ft. range), 2/day" }, + { 60, 1, "Cat's grace (wielder only), 1/day" }, + { 65, 1, "Bull's strength (wielder only), 1/day" }, + { 70, 1, "Haste (wielder only, 10 rounds), 1/day" }, + { 73, 1, "Telekinesis (250 lb. maximum, 1 minute each use), 2/day" }, + { 76, 1, "Heal, 1/day" }, + { 77, 1, "Teleport, 600 lb. maximum, 1/day" }, + { 78, 1, "Globe of invulnerability, 1/day" }, + { 79, 1, "Stoneskin (wielder only, 10 minutes per use), 2/day" }, + { 80, 1, "Feeblemind by touch, 2/day" }, + { 81, 0, "True seeing, at will" }, + { 82, 1, "Wall of force, 1/day" }, + { 83, 1, "Summon monster VI, 1/day" }, + { 84, 1, "Finger of death (100 ft. range, DC 17), 1/day" }, + { 85, 0, "Passwall, at will" }, + { 90, 1, "-" }, /* roll twice again */ + { 100, 0, "*" }, /* roll again, plus roll on item purpose table */ + { 0, 0 } +}; + + +struct { + int dtop; + int other; + int standing; + char* purpose; +} itemPurposes[] = { + { 20, NONE, NONE, "Defeat/slay diametrically opposed alignment" }, + { 30, NONE, NONE, "Defeat/slay arcane spellcasters (including spellcasting monsters and those that use spell-like abilities" }, + { 40, NONE, NONE, "Defeat/slay divine spellcasters (including divine entities and servitors)" }, + { 50, NONE, NONE, "Defeat/slay nonspellcasters" }, + { 55, NONE, NONE, "Defeat/slay a type of creature (see MM)" }, + { 60, NONE, NONE, "Defeat/slay a particular kind of creature" }, + { 70, NONE, NONE, "Defend a particular race or kind of creature" }, + { 80, NONE, NONE, "Defeat/slay the servants of a specific deity" }, + { 90, NONE, NONE, "Defend the servants and interests of a specific deity" }, + { 95, NONE, opEVIL | opCHAOTIC, "Defeat/slay all (other than the item and the wielder)" }, + { 100, NONE, opGOOD, "Defeat/slay evil creatures and items" }, + { 0, 0, 0 } +}; + + +struct { + int dtop; + char* name; +} itemPurposeAbilities[] = { + { 10, "Blindness (DC 12) for 2d6 rounds" }, + { 20, "Confusion (DC 14) for 2d6 rounds" }, + { 25, "Fear (DC 14) for 1d4 rounds" }, + { 55, "Hold monster (DC 14) for 1d4 rounds" }, + { 65, "Slay living (DC 15)" }, + { 75, "Disintegrate (DC 16)" }, + { 80, "True resurrection on wielder, one time only" }, + { 100, "+2 luck bonus to all saving throws, +2 deflection AC bonus, spell resistance 15" }, + { 0, 0 } +}; + + +static int opposes( int standing1, int standing2 ) { + if( ( standing1 & opGOOD ) != 0 && ( standing2 & opEVIL ) != 0 ) { + return 1; + } + if( ( standing1 & opEVIL ) != 0 && ( standing2 & opGOOD ) != 0 ) { + return 1; + } + if( ( standing1 & opLAWFUL ) != 0 && ( standing2 & opCHAOTIC ) != 0 ) { + return 1; + } + if( ( standing1 & opCHAOTIC ) != 0 && ( standing2 & opLAWFUL ) != 0 ) { + return 1; + } + + return 0; +} + + +void addNewTreasure( TREASUREOPTS *opts, char* desc, int value ) { + TREASUREITEM *t; + + t = (TREASUREITEM*)malloc( sizeof( TREASUREITEM ) ); + strcpy( t->desc, desc ); + t->value = value; + t->next = 0; + + if( opts->treasureTail == NULL ) { + opts->treasureList = opts->treasureTail = t; + } else { + opts->treasureTail->next = t; + opts->treasureTail = t; + } +} + + +void cleanupTreasure( TREASUREOPTS *opts ) { + TREASUREITEM* t; + TREASUREITEM* n; + + t = opts->treasureList; + while( t != NULL ) { + n = t->next; + free( t ); + t = n; + } +} + + +/* swap two integer values, used by determineIntelligence */ +static void swap( int* i1, int* i2 ) { + int t; + + t = *i1; + *i1 = *i2; + *i2 = t; +} + + +/* determines the characteristics of an intelligent item. the 'enh' + * value is the enhancement value of the item (including special abilities) + * and 'value' is a pointer to an integer that holds the current gp value + * of the item. The routine returns a static pointer to the text description + * of the intelligent item. + */ + +static char* determineIntelligence( int enh, int* standing, int* value ) { + int hi, med, lo; + int d; + int i; + int j; + int k; + int valid; + int ilevl = 0; + int primary; + int extra; + int hasPurpose; + int ego; + static char buffer[1024]; + char *slist[30]; + + ego = enh; + + /* ego, according to the DMG, is an amalgamation of the properties of the + * item: enhancement bonuses, special abilities, ability score bonuses, + * purpose, primary abilities, extraordinary abilities, etc. */ + + buffer[0] = 0; + + d = rollDice( 1, 100 ); + for( i = 0; intTable[i].communication != 0; i++ ) { + if( d <= intTable[i].dtop ) { + ilevl = i; + break; + } + } + + ego += intTable[i].egoBonus; + *value += intTable[i].value; + + hi = rollDice( 2, 6 ) + intTable[i].twoBonus; + med = rollDice( 2, 6 ) + intTable[i].twoBonus; + lo = rollDice( 3, 6 ); + + if( hi < lo ) { + swap( &hi, &lo ); + } + if( hi < med ) { + swap( &hi, &med ); + } + if( med < lo ) { + swap( &med, &lo ); + } + + d = ( hi - 10 ) / 2; + d = ( d < 0 ? 0 : d ); + ego += d; + d = ( med - 10 ) / 2; + d = ( d < 0 ? 0 : d ); + ego += d; + d = ( lo - 10 ) / 2; + d = ( d < 0 ? 0 : d ); + ego += d; + + d = rollDice( 1, 4 ) - 1; + sprintf( buffer, " - %s %d, %s %d, %s %d ", + intAbilities[d].hi, hi, + intAbilities[d].med, med, + intAbilities[d].lo, lo ); + strcat( buffer, "communicates by " ); + strcat( buffer, intTable[ilevl].communication ); + if( intTable[ilevl].other[0] != '\0' ) { + strcat( buffer, " and " ); + strcat( buffer, intTable[ilevl].other ); + } + strcat( buffer, ", " ); + + do { + j = 0; + d = rollDice( 1, 100 ); + for( i = 0; alignments[i].alignment != 0; i++ ) { + if( d <= alignments[i].dtop ) { + if( opposes( *standing, alignments[i].standing ) ) { + j = 1; + break; + } + strcat( buffer, alignments[i].alignment ); + (*standing) |= alignments[i].standing; + break; + } + } + } while( j ); + + primary = intTable[ilevl].primary; + extra = intTable[ilevl].extraordinary; + hasPurpose = 0; + + for( i = 0; i < primary; i++ ) { + do { + valid = 1; + d = rollDice( 1, 100 ); + for( j = 0; primaryAbilities[j].name != 0; j++ ) { + if( d <= primaryAbilities[j].dtop ) { + if( primaryAbilities[j].name[0] == '-' ) { + slist[i] = strdup( primaryAbilities[j].name ); + primary += 2; + } else if( primaryAbilities[j].name[0] == '*' ) { + slist[i] = strdup( primaryAbilities[j].name ); + extra++; + } else { + for( k = 0; k < i; k++ ) { + if( !primaryAbilities[j].allowDup && ( strcmp( slist[k], primaryAbilities[j].name ) == 0 ) ) { + valid = 0; + break; + } + } + if( !valid ) { + break; + } + ego++; + strcat( buffer, "\n - " ); + strcat( buffer, primaryAbilities[j].name ); + slist[i] = strdup( primaryAbilities[j].name ); + } + break; + } + } + } while( !valid ); + } + + for( i = 0; i < primary; i++ ) { + free( slist[i] ); + } + + for( i = 0; i < extra; i++ ) { + do { + valid = 1; + d = rollDice( 1, 100 ); + for( j = 0; extraAbilities[j].name != 0; j++ ) { + if( d <= extraAbilities[j].dtop ) { + if( extraAbilities[j].name[0] == '-' ) { + extra += 2; + slist[i] = strdup( extraAbilities[j].name ); + } else if( extraAbilities[j].name[0] == '*' ) { + extra++; + hasPurpose = 1; + slist[i] = strdup( extraAbilities[j].name ); + } else { + for( k = 0; k < i; k++ ) { + if( !extraAbilities[j].allowDup && ( strcmp( slist[k], extraAbilities[j].name ) == 0 ) ) { + valid = 0; + break; + } + } + if( !valid ) { + break; + } + ego += 2; + strcat( buffer, "\n - " ); + strcat( buffer, extraAbilities[j].name ); + slist[i] = strdup( extraAbilities[j].name ); + } + break; + } + } + } while( !valid ); + } + + for( i = 0; i < extra; i++ ) { + free( slist[i] ); + } + + if( hasPurpose ) { + ego += 4; + + strcat( buffer, "\n - Purpose: " ); + do { + j = 0; + d = rollDice( 1, 100 ); + for( i = 0; itemPurposes[i].purpose != 0; i++ ) { + if( d <= itemPurposes[i].dtop ) { + if( opposes( *standing, itemPurposes[i].standing ) ) { + j = 1; + break; + } + strcat( buffer, itemPurposes[i].purpose ); + strcat( buffer, "\n" ); + break; + } + } + } while( j ); + + d = rollDice( 1, 100 ); + strcat( buffer, " " ); + for( i = 0; itemPurposeAbilities[i].name != 0; i++ ) { + if( d <= itemPurposeAbilities[i].dtop ) { + strcat( buffer, itemPurposeAbilities[i].name ); + break; + } + } + } + + sprintf( &(buffer[strlen(buffer)]), "\n - Ego: %d", ego ); + + return buffer; +} + + +char* randomSpell( int type, int level, int *value ) { + int d; + int i; + SPELL **table; + + if( ( level < 1 ) || ( level > 9 ) ) { + return ""; + } + + d = rollDice( 1, 100 ); + if( type == ARCANE ) { + table = spellTable.arcane; + } else { + table = spellTable.divine; + } + + for( i = 0; table[level-1][i].name != 0; i++ ) { + if( d <= table[level-1][i].dtop ) { + *value += table[level-1][i].value; + return table[level-1][i].name; + } + } + + return ""; +} + + +static char* randomItem( char** names ) { + int len; + + len = (int)names[0]; + + if( len < 1 ) { + return ""; + } + + return names[ rand() % len + 1 ]; +} + + +void generateSpecificArmor( TREASUREOPTS *opts, int level ) { + int d; + int i; + int *top; + + d = rollDice( 1, 100 ); + for( i = 0; specificArmors[i].armor != 0; i++ ) { + if( level == MEDIUM ) { + top = &(specificArmors[i].mediumDtop); + } else { + top = &(specificArmors[i].majorDtop); + } + + if( d <= *top ) { + addNewTreasure( opts, specificArmors[i].armor, specificArmors[i].value*100 ); + return; + } + } +} + + +void generateSpecificShield( TREASUREOPTS *opts, int level ) { + int d; + int i; + int *top; + + d = rollDice( 1, 100 ); + for( i = 0; specificShields[i].shield != 0; i++ ) { + if( level == MEDIUM ) { + top = &(specificShields[i].mediumDtop); + } else { + top = &(specificShields[i].majorDtop); + } + + if( d <= *top ) { + addNewTreasure( opts, specificShields[i].shield, specificShields[i].value*100 ); + return; + } + } +} + + +static char* getShieldSpecial( int level, int* plusses ) { + int d; + int i; + int *top; + + do { + d = rollDice( 1, 100 ); + for( i = 0; shieldSpecials[ i ].ability != 0; i++) { + if( level == MINOR ) { + top = &(shieldSpecials[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(shieldSpecials[i].mediumDtop); + } else { + top = &(shieldSpecials[i].majorDtop); + } + + if( d <= *top ) { + if( *plusses + shieldSpecials[i].plus > 10 ) { + break; + } + (*plusses) += shieldSpecials[i].plus; + return shieldSpecials[i].ability; + } + } + } while( 1 ); + + return "[unknown!]"; +} + + +static char* getArmorSpecial( int level, int* plusses ) { + int d; + int i; + int *top; + + do { + d = rollDice( 1, 100 ); + for( i = 0; armorSpecials[ i ].ability != 0; i++) { + if( level == MINOR ) { + top = &(armorSpecials[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(armorSpecials[i].mediumDtop); + } else { + top = &(armorSpecials[i].majorDtop); + } + + if( d <= *top ) { + if( *plusses + armorSpecials[i].plus > 10 ) { + break; + } + (*plusses) += armorSpecials[i].plus; + return armorSpecials[i].ability; + } + } + } while( 1 ); + + return "[unknown!]"; +} + + +void generateArmor( TREASUREOPTS *opts, int level ) { + int i; + int d; + int j; + int *top; + int enh; + int specCnt; + int armor; + int tries; + char *p; + char desc[1024]; + char *slist[30]; + int value; + int standing; + + enh = 0; + specCnt = 0; + armor = 0; + desc[0] = 0; + value = 0; + standing = NONE; + + do { + d = rollDice( 1, 100 ); + for( i = 0; armorTable[i].type != 0; i++ ) { + if( level == MINOR ) { + top = &(armorTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(armorTable[i].mediumDtop); + } else { + top = &(armorTable[i].majorDtop); + } + + if( d <= *top ) { + break; + } + } + + switch( armorTable[i].type ) { + case ENHANCED: + enh = armorTable[i].enhancement; + armor = armorTable[i].armor; + break; + case SPECIFIC: + if( armorTable[i].armor ) { + generateSpecificArmor( opts, level ); + } else { + generateSpecificShield( opts, level ); + } + return; + case SPECIAL: + specCnt++; + break; + } + } while( enh < 1 ); + + sprintf( desc, "+%d ", enh ); + + d = rollDice( 1, 100 ); + if( armor ) { + for( i = 0; armorTypes[i].name != 0; i++ ) { + if( d <= armorTypes[i].dtop ) { + value = armorTypes[i].value; + strcat( desc, armorTypes[i].name ); + strcat( desc, " " ); + break; + } + } + } else { + for( i = 0; shieldTypes[i].name != 0; i++ ) { + if( d <= shieldTypes[i].dtop ) { + value = shieldTypes[i].value; + strcat( desc, shieldTypes[i].name ); + strcat( desc, " " ); + break; + } + } + } + + if( specCnt > 0 ) { + strcat( desc, "of " ); + + for( i = 0; i < specCnt; i++ ) { + if( enh >= 10 ) { + specCnt = i; + break; + } + + tries = 0; + do { + tries++; + if( armor ) { + p = getArmorSpecial( level, &enh ); + } else { + p = getShieldSpecial( level, &enh ); + } + + d = 0; + for( j = 0; j < i; j++ ) { + if( strcmp( slist[j], p ) == 0 ) { + d = 1; + break; + } + } + } while( ( d ) && ( tries < 10 ) ); + + if( tries >= 10 ) { + desc[strlen(desc)-2] = 0; + specCnt = i; + break; + } + + slist[i] = strdup( p ); + + if( *p == '-' ) { + specCnt += 2; + } else { + strcat( desc, p ); + if( ( i < specCnt-1 ) && ( enh < 10 ) ) { + strcat( desc, ", " ); + } + } + } + } + + value += ( enh * enh * 1000 ); + + if( opts->forceIntelligent || ( rollDice( 1, 100 ) == 1 ) ) { + strcat( desc, "\n" ); + strcat( desc, determineIntelligence( enh, &standing, &value ) ); + } + + for( i = 0; i < specCnt; i++ ) { + free( slist[i] ); + } + + addNewTreasure( opts, desc, value*100 ); +} + + +static char* getBane( int* standing ) { + int d; + int i; + + do { + d = rollDice( 1, 100 ); + for( i = 0; baneTable[i].name != 0; i++ ) { + if( d <= baneTable[i].dtop ) { + if( opposes( *standing, baneTable[i].standing ) ) { + break; + } + return baneTable[i].name; + } + } + } while( 1 ); + + return "unknown"; +} + + +void generateSpecificWeapon( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[ 1024 ]; + int standing = 0; + + d = rollDice( 1, 100 ); + for( i = 0; specificWeapons[i].name != 0; i++ ) { + if( level == MEDIUM ) { + top = &(specificWeapons[i].mediumDtop); + } else { + top = &(specificWeapons[i].majorDtop); + } + + if( d <= *top ) { + strcpy( desc, specificWeapons[i].name ); + switch( specificWeapons[i].other ) { + case BANE: + strcat( desc, " - " ); + strcat( desc, getBane( &standing ) ); + break; + } + addNewTreasure( opts, desc, specificWeapons[i].value*100 ); + return; + } + } +} + + +static char* getMeleeSpecial( int level, int restrict, int* standing, int* plusses ) { + int d; + int i; + int* top; + static char buffer[256]; + + do { + d = rollDice( 1, 100 ); + for( i = 0; meleeSpecials[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(meleeSpecials[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(meleeSpecials[i].mediumDtop); + } else { + top = &(meleeSpecials[i].majorDtop); + } + + if( d <= *top ) { + if( ( restrict & meleeSpecials[i].restrict ) != 0 ) { + if( *plusses + meleeSpecials[i].plus > 10 ) { + break; + } + if( opposes( *standing, meleeSpecials[i].standing ) ) { + break; + } + (*plusses) += meleeSpecials[i].plus; + (*standing) |= meleeSpecials[i].standing; + strcpy( buffer, meleeSpecials[i].name ); + switch( meleeSpecials[i].other ) { + case BANE: + strcat( buffer, getBane( standing ) ); + break; + } + return buffer; + } else { + break; + } + } + } + } while( 1 ); + + return "[!unknown]"; +} + + +static char* getRangedSpecial( int level, int* standing, int* plusses ) { + int d; + int i; + int* top; + static char buffer[256]; + + do { + d = rollDice( 1, 100 ); + for( i = 0; rangedSpecials[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(rangedSpecials[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(rangedSpecials[i].mediumDtop); + } else { + top = &(rangedSpecials[i].majorDtop); + } + + if( d <= *top ) { + if( *plusses + rangedSpecials[i].plus > 10 ) { + break; + } + if( opposes( *standing, rangedSpecials[i].standing ) ) { + break; + } + (*plusses) += rangedSpecials[i].plus; + (*standing) |= rangedSpecials[i].standing; + strcpy( buffer, rangedSpecials[i].name ); + switch( rangedSpecials[i].other ) { + case BANE: + strcat( buffer, getBane( standing ) ); + break; + } + return buffer; + } + } + } while( 1 ); + + return "[!unknown]"; +} + + +void generateWeapon( TREASUREOPTS *opts, int level ) { + int i; + int d; + int j; + int* top; + int enh; + int spcCnt; + int melee; + int restrict = 0; + int intelligent; + int tries; + char* p; + char desc[1024]; + char* slist[30]; + int value; + int standing; + + enh = 0; + spcCnt = 0; + melee = 0; + intelligent = 0; + value = 0; + standing = NONE; + + do { + d = rollDice( 1, 100 ); + for( i = 0; weaponTable[i].type != 0; i++ ) { + if( level == MINOR ) { + top = &(weaponTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(weaponTable[i].mediumDtop); + } else { + top = &(weaponTable[i].majorDtop); + } + + if( d <= *top ) { + break; + } + } + + switch( weaponTable[i].type ) { + case ENHANCED: + enh = weaponTable[i].enhancement; + break; + case SPECIFIC: + generateSpecificWeapon( opts, level ); + return; + case SPECIAL: + spcCnt++; + break; + } + } while( enh < 1 ); + + d = rollDice( 1, 100 ); + for( i = 0; weaponTypes[ i ].type != 0; i++ ) { + if( d <= weaponTypes[ i ].dtop ) { + break; + } + } + + sprintf( desc, "+%d", enh ); + + d = rollDice( 1, 100 ); + if( d <= 30 ) { + strcat( desc, " light-emitting" ); + } + + switch( weaponTypes[ i ].type ) { + case COMMONMW: + melee = 1; + if( rollDice( 1, 100 ) <= 15 ) { + intelligent = 1; + } + + d = rollDice( 1, 100 ); + for( i = 0; commonMeleeWeapons[i].name != 0; i++ ) { + if( d <= commonMeleeWeapons[i].dtop ) { + strcat( desc, " " ); + strcat( desc, commonMeleeWeapons[i].name ); + restrict = commonMeleeWeapons[i].type; + value += commonMeleeWeapons[i].value; + break; + } + } + break; + case UNCOMMNW: + d = rollDice( 1, 100 ); + for( i = 0; uncommonWeapons[i].name != 0; i++ ) { + if( d <= uncommonWeapons[i].dtop ) { + melee = ( uncommonWeapons[i].type == MELEE ); + if( melee ) { + if( rollDice( 1, 100 ) <= 15 ) { + intelligent = 1; + } + } else { + if( rollDice( 1, 100 ) <= 5 ) { + intelligent = 1; + } + } + + strcat( desc, " " ); + strcat( desc, uncommonWeapons[i].name ); + restrict = uncommonWeapons[i].dtype; + value += uncommonWeapons[i].value; + break; + } + } + break; + case COMMONRW: + melee = 0; + d = rollDice( 1, 200 ); + + if( ( d > 100 ) && ( rollDice( 1, 100 ) <= 5 ) ) { + intelligent = 1; + } + + for( i = 0; commonRangedWeapons[i].name != 0; i++ ) { + if( d <= commonRangedWeapons[i].dtop ) { + strcat( desc, " " ); + strcat( desc, commonRangedWeapons[i].name ); + value += commonRangedWeapons[i].value; + break; + } + } + break; + } + + if( spcCnt > 0 ) { + strcat( desc, ", " ); + for( i = 0; i < spcCnt; i++ ) { + if( enh >= 10 ) { + spcCnt = i; + break; + } + + tries = 0; + do { + tries++; + if( melee ) { + p = getMeleeSpecial( level, restrict, &standing, &enh ); + } else { + p = getRangedSpecial( level, &standing, &enh ); + } + + d = 0; + for( j = 0; j < i; j++ ) { + if( strcmp( slist[j], p ) == 0 ) { + d = 1; + break; + } + } + } while( ( d ) && ( tries < 10 ) ); + + if( tries >= 10 ) { + spcCnt = i; + desc[strlen(desc)-2] = 0; + break; + } + + slist[i] = strdup( p ); + + if( *p == '-' ) { + spcCnt += 2; + } else { + strcat( desc, p ); + if( ( i < spcCnt-1 ) && ( enh < 10 ) ) { + strcat( desc, ", " ); + } + } + } + } + + value += ( enh * enh * 2000 ); + + for( i = 0; i < spcCnt; i++ ) { + free( slist[i] ); + } + + if( opts->forceIntelligent || intelligent ) { + strcat( desc, "\n" ); + strcat( desc, determineIntelligence( enh, &standing, &value ) ); + } + + addNewTreasure( opts, desc, value*100 ); +} + + +void generatePotion( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[1024]; + + strcpy( desc, "potion: " ); + + d = rollDice( 1, 100 ); + for( i = 0; potionTable[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(potionTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(potionTable[i].mediumDtop); + } else { + top = &(potionTable[i].majorDtop); + } + + if( d <= *top ) { + strcat( desc, potionTable[i].name ); + addNewTreasure( opts, desc, potionTable[i].value*100 ); + return; + } + } +} + + +void generateRing( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[1024]; + int value; + int standing; + + desc[0] = 0; + standing = NONE; + + d = rollDice( 1, 100 ); + for( i = 0; ringTable[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(ringTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(ringTable[i].mediumDtop); + } else { + top = &(ringTable[i].majorDtop); + } + + if( d <= *top ) { + strcat( desc, "ring of " ); + strcat( desc, ringTable[i].name ); + if( ringTable[i].dcount > 0 ) { + sprintf( &desc[strlen(desc)], " (%d charges)", rollDice( ringTable[i].dcount, ringTable[i].dtype ) ); + } + + value = ringTable[i].value; + + if( opts->forceIntelligent || ( rollDice( 1, 100 ) == 1 ) ) { + strcat( desc, "\n" ); + strcat( desc, determineIntelligence( 1, &standing, &value ) ); + } + + addNewTreasure( opts, desc, value*100 ); + return; + } + } +} + + +void generateRod( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[1024]; + int value; + int standing; + + desc[0] = 0; + standing = 0; + + d = rollDice( 1, 100 ); + for( i = 0; rodTable[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(rodTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(rodTable[i].mediumDtop); + } else { + top = &(rodTable[i].majorDtop); + } + + if( d <= *top ) { + strcat( desc, "rod: " ); + strcat( desc, rodTable[i].name ); + + value = rodTable[i].value; + + if( opts->forceIntelligent || ( rollDice( 1, 100 ) == 1 ) ) { + strcat( desc, "\n" ); + strcat( desc, determineIntelligence( 1, &standing, &value ) ); + } + + addNewTreasure( opts, desc, value*100 ); + return; + } + } +} + + +void generateScroll( TREASUREOPTS *opts, int level ) { + int i; + int j; + int d; + int type; + int min; + int max; + int count; + int *top; + char desc[1024]; + int value; + + type = 0; + d = rollDice( 1, 100 ); + for( i = 0; scrollTypes[i].type != 0; i++ ) { + if( d <= scrollTypes[i].dtop ) { + type = scrollTypes[i].type; + break; + } + } + + min = 1; + if( level == MINOR ) { + max = 3; + } else if( level == MEDIUM ) { + max = 4; + } else { + max = 6; + } + + value = 0; + + sprintf( desc, "scroll (%s)\n", ( type == ARCANE ? "arcane" : "divine" ) ); + count = rollDice( min, max/min ); + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; spellLevels[j].level != 0; j++ ) { + if( level == MINOR ) { + top = &(spellLevels[j].minorDtop); + } else if( level == MEDIUM ) { + top = &(spellLevels[j].mediumDtop); + } else { + top = &(spellLevels[j].majorDtop); + } + + if( d <= *top ) { + sprintf( &desc[strlen(desc)], " - %s (l%d, cl%d)", randomSpell( type, spellLevels[j].level, &value ), spellLevels[j].level, spellLevels[j].caster ); + if( i < count-1 ) { + strcat( desc, "\n" ); + } + break; + } + } + } + addNewTreasure( opts, desc, value*100 ); +} + + +void generateStaff( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[1024]; + + d = rollDice( 1, 100 ); + for( i = 0; staffTable[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(staffTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(staffTable[i].mediumDtop); + } else { + top = &(staffTable[i].majorDtop); + } + + if( d <= *top ) { + sprintf( desc, "staff of %s (%d charges)", staffTable[i].name, rollDice( 1, 50 ) ); + addNewTreasure( opts, desc, staffTable[i].value*100 ); + return; + } + } +} + + +void generateWand( TREASUREOPTS *opts, int level ) { + int d; + int i; + int* top; + char desc[1024]; + + d = rollDice( 1, 100 ); + for( i = 0; wandTable[i].name != 0; i++ ) { + if( level == MINOR ) { + top = &(wandTable[i].minorDtop); + } else if( level == MEDIUM ) { + top = &(wandTable[i].mediumDtop); + } else { + top = &(wandTable[i].majorDtop); + } + + if( d <= *top ) { + sprintf( desc, "wand of %s (%d charges)", wandTable[i].name, rollDice( 1, 50 ) ); + addNewTreasure( opts, desc, wandTable[i].value*100 ); + return; + } + } +} + + +void generateWondrousItem( TREASUREOPTS *opts, int level ) { + WONDROUSITEM* list; + int d; + int i; + + if( level == MINOR ) { + list = minorItems; + } else if( level == MEDIUM ) { + list = mediumItems; + } else { + list = majorItems; + } + + d = rollDice( 1, 100 ); + for( i = 0; list[ i ].name != 0; i++ ) { + if( d <= list[ i ].dtop ) { + addNewTreasure( opts, list[i].name, list[i].value*100 ); + break; + } + } +} + + +int generateTreasure( TREASUREOPTS *opts, int level, int column, int rollHigher, float mod ) { + int d; + int count; + int i; + int j; + int k; + int level_column; + int value; + treasureEntry *t; + char desc[1024]; + float f; + + if( (int)(mod*1000) == 0 ) { + return 0; + } + + level_column = ( level * 15 ) + ( column * 5 ); + + d = rollDice( 1, 100 ); + t = NULL; + for( i = 0; i < 5; i++ ) { + if( d <= treasureTable[ level_column + i ].pcap ) { + t = &( treasureTable[ level_column + i ] ); + break; + } + } + + count = rollDice( t->tdc, t->tdt ) * t->mul; + f = mod * count; + + if( ( f < 1 ) && ( f >= 0.5 ) ) { + count = 1; + } else if( f < 0.5 ) { + return 0; + } else { + count = (int)f; + } + + switch( t->ttype ) { + case NONE: + break; + case CP: + commify( desc, count ); + strcat( desc, " copper coins" ); + addNewTreasure( opts, desc, count ); + break; + case SP: + commify( desc, count ); + strcat( desc, " silver coins" ); + addNewTreasure( opts, desc, count*10 ); + break; + case GP: + commify( desc, count ); + strcat( desc, " gold coins" ); + addNewTreasure( opts, desc, count*100 ); + break; + case PP: + commify( desc, count ); + strcat( desc, " platinum coins" ); + addNewTreasure( opts, desc, count*1000 ); + break; + case GEMS: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; gemTable[ j ].dcap > 0; j++ ) { + if( d <= gemTable[ j ].dcap ) { + d = rollDice( gemTable[ j ].dcount, gemTable[ j ].dtype ) * gemTable[ j ].dmul; + strcpy( desc, "gemstone - " ); + strcat( desc, randomItem( gemTable[ j ].names ) ); + addNewTreasure( opts, desc, d*100 ); + break; + } + } + } + break; + case ART: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; artTable[ j ].dcap > 0; j++ ) { + if( d <= artTable[ j ].dcap ) { + d = rollDice( artTable[ j ].dcount, artTable[ j ].dtype ) * artTable[ j ].dmul; + addNewTreasure( opts, randomItem( artTable[ j ].names ), d*100 ); + break; + } + } + } + break; + case MUNDANE: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; mundaneTable[ j ].name != 0; j++ ) { + if( d <= mundaneTable[ j ].dcap ) { + strcpy( desc, mundaneTable[ j ].name ); + + value = mundaneTable[ j ].value; + switch( mundaneTable[ j ].other ) { + case COMMONMW: + d = rollDice( 1, 100 ); + for( k = 0; commonMeleeWeapons[k].name != 0; k++ ) { + if( d <= commonMeleeWeapons[k].dtop ) { + strcat( desc, commonMeleeWeapons[k].name ); + value += commonMeleeWeapons[k].value; + break; + } + } + break; + case UNCOMMNW: + d = rollDice( 1, 100 ); + for( k = 0; uncommonWeapons[k].name != 0; k++ ) { + if( d <= uncommonWeapons[k].dtop ) { + strcat( desc, uncommonWeapons[k].name ); + value += uncommonWeapons[k].value; + break; + } + } + break; + case COMMONRW: + d = rollDice( 1, 200 ); + for( k = 0; commonRangedWeapons[k].name != 0; k++ ) { + if( d <= commonRangedWeapons[k].dtop ) { + strcat( desc, commonRangedWeapons[k].name ); + value += commonRangedWeapons[k].value; + break; + } + } + break; + } + + if( mundaneTable[ j ].dtype > 1 ) { + d = rollDice( mundaneTable[ j ].dcount, mundaneTable[ j ].dtype ) * mundaneTable[ j ].dmul; + sprintf( &desc[strlen(desc)], " (%d)", d ); + } else { + d = 1; + } + + value *= d; + addNewTreasure( opts, desc, value*100 ); + break; + } + } + } + break; + case MINOR: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; magicTable[ j ].type != 0; j++ ) { + if( d <= magicTable[ j ].minorDtop ) { + switch( magicTable[ j ].type ) { + case ARMOR: + generateArmor( opts, MINOR ); + break; + case WEAPONS: + generateWeapon( opts, MINOR ); + break; + case POTIONS: + generatePotion( opts, MINOR ); + break; + case RINGS: + generateRing( opts, MINOR ); + break; + case RODS: + generateRod( opts, MINOR ); + break; + case SCROLLS: + generateScroll( opts, MINOR ); + break; + case STAFFS: + generateStaff( opts, MINOR ); + break; + case WANDS: + generateWand( opts, MINOR ); + break; + case WONDROUS: + generateWondrousItem( opts, MINOR ); + break; + } + break; + } + } + } + break; + case MEDIUM: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; magicTable[ j ].type != 0; j++ ) { + if( d <= magicTable[ j ].mediumDtop ) { + switch( magicTable[ j ].type ) { + case ARMOR: + generateArmor( opts, MEDIUM ); + break; + case WEAPONS: + generateWeapon( opts, MEDIUM ); + break; + case POTIONS: + generatePotion( opts, MEDIUM ); + break; + case RINGS: + generateRing( opts, MEDIUM ); + break; + case RODS: + generateRod( opts, MEDIUM ); + break; + case SCROLLS: + generateScroll( opts, MEDIUM ); + break; + case STAFFS: + generateStaff( opts, MEDIUM ); + break; + case WANDS: + generateWand( opts, MEDIUM ); + break; + case WONDROUS: + generateWondrousItem( opts, MEDIUM ); + break; + } + break; + } + } + } + break; + case MAJOR: + for( i = 0; i < count; i++ ) { + d = rollDice( 1, 100 ); + for( j = 0; magicTable[ j ].type != 0; j++ ) { + if( d <= magicTable[ j ].majorDtop ) { + switch( magicTable[ j ].type ) { + case ARMOR: + generateArmor( opts, MAJOR ); + break; + case WEAPONS: + generateWeapon( opts, MAJOR ); + break; + case POTIONS: + generatePotion( opts, MAJOR ); + break; + case RINGS: + generateRing( opts, MAJOR ); + break; + case RODS: + generateRod( opts, MAJOR ); + break; + case SCROLLS: + generateScroll( opts, MAJOR ); + break; + case STAFFS: + generateStaff( opts, MAJOR ); + break; + case WANDS: + generateWand( opts, MAJOR ); + break; + case WONDROUS: + generateWondrousItem( opts, MAJOR ); + break; + } + break; + } + } + } + break; + } + + if( rollHigher && ( d >= 96 ) ) { + level++; + if( level >= LEVEL_COUNT ) { + level = LEVEL_COUNT-1; + } + count += generateTreasure( opts, level, column, 0, mod ); + } + + return count; +} + + +void generateRandomTreasureEx( TREASUREOPTS *opts, int level, float* mods ) { + generateTreasure( opts, level, 0, 1, mods[0] ); + generateTreasure( opts, level, 1, 1, mods[1] ); + generateTreasure( opts, level, 2, 1, mods[2] ); +} + + +void generateRandomTreasure( TREASUREOPTS* opts, int level ) { + float mods[3]; + + mods[0] = mods[1] = mods[2] = 1.0; + generateRandomTreasureEx( opts, level, mods ); +} diff --git a/tem/dungeon_desc.tem b/tem/dungeon_desc.tem new file mode 100755 index 0000000..5ba56f3 --- /dev/null +++ b/tem/dungeon_desc.tem @@ -0,0 +1,39 @@ + + + Dungeon Generator + + + + +

    Curious how this generator works? Check out Random Dungeon Design: The Secret Workings of Jamis Buck's Dungeon Generator

    + +
    + +

    +

    + Current Random Seed:
    +
    + +

    + +

    + +
    + +
    +

    + +

    + This dungeon generator has received hits since 10 Nov 2000!
    +

    + + This dungeon generator incorporates data and information from the + Open Gaming License (OGL). + The algorithms to populate the rooms and the monster and room content data are from the + System Reference Document. All + other aspects of this program are Copyright 2000 by Jamis Buck. + +

    + + + diff --git a/tem/dungeon_main.tem b/tem/dungeon_main.tem new file mode 100755 index 0000000..9786993 --- /dev/null +++ b/tem/dungeon_main.tem @@ -0,0 +1,423 @@ + + + Dungeon Generator + + + + + +

    Dungeon Generator

    + +

    Welcome to the Dungeons & Dragons © random dungeon generator! Configure the dungeon by selecting from the options below, and then click the "Generate Dungeon" button to see the result.

    + +

    Not sure what the different options mean? Click on their names to see a window describing what each one does, and how they modify the dungeons that are generated.

    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Width: + + + + Room Count: + + +
    + Height: + + + + Room Width: + + Between + + and + + squares +
    + Sparseness: + + + + Room Height: + + Between + + and + + squares +
    + Randomness: + + + + Secret Doors: + + +
    + Deadends Removed: + + + + Concealed Doors: + + +
    + Level: + + + + Grid Resolution: + + +
    + Random Seed: +
    + +
    +
    +
    +
    + Restart the Dungeon generator. +

    + +
    + +

    Dungeon Generator-Related Links/Downloads

    + +

    Curious as to how this generator works? Check it out here: Random Dungeon Design: The Secret Workings of Jamis Buck's Dungeon Generator. By popular demand, I've put together a document describing it in relatively general terms, so that (hopefully) non-programmers can come away from it with an idea of what the generator does.

    + +

    Are you a programmer? Would you like to download and tinker with the source code for this generator? The C and C++ source code may be obtained here: http://github.com/jamis/dnd-dungeon

    +
    + + + diff --git a/tem/generators.css b/tem/generators.css new file mode 100644 index 0000000..4e4f26c --- /dev/null +++ b/tem/generators.css @@ -0,0 +1,123 @@ +BODY, .NORMAL + { COLOR: #000000; + BACKGROUND: #FFFFFF; + FONT: 90% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: normal; + TEXT-DECORATION: none; } + +.DATA + { COLOR: #000000; + BACKGROUND: #FFFFFF; + FONT: 90% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: bold; + TEXT-DECORATION: none; } + +.HEADER + { COLOR: #FFFFFF; + BACKGROUND: #00009F; + FONT: 90% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: bold; + TEXT-DECORATION: none; } + +.SUBLIMINAL + { COLOR: #000000; + BACKGROUND: #FFFFFF; + FONT: 70% "Arial", sans-serif; + FONT-STYLE: italic; + FONT-WEIGHT: normal; + TEXT-DECORATION: none; } + +.NAV + { BACKGROUND: #000060; } + +.NAVHDR + { COLOR: #FFFFFF; + BACKGROUND: #00009F; + FONT: 70% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: bold; + TEXT-DECORATION: none; } + +.NAVNORM + { COLOR: #FFFFFF; + FONT: 70% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: normal; + TEXT-DECORATION: none; } + +.NAVHDR A:link, .NAVHDR A:visited, .HEADER A:visited, .HEADER A:link + { COLOR: #FFFF7F; + FONT-WEIGHT: bold; + TEXT-DECORATION: underline; } + +.NAVNORM A:link, .NAVNORM A:visited + { COLOR: #FFFF7F; + TEXT-DECORATION: underline; } + +A:link, A:visited + { COLOR: #0000FF; + FONT-WEIGHT: normal; + TEXT-DECORATION: underline; } + +H1 + { COLOR: #000000; + FONT-WEIGHT: bold; + FONT: 200% "Arial", sans-serif; } + +H2 + { COLOR: #000000; + FONT-WEIGHT: bold; + FONT: 165% "Arial", sans-serif; } + +H3 + { COLOR: #000000; + FONT-WEIGHT: bold; + FONT: 110% "Arial", sans-serif; } + +B + { FONT-WEIGHT: bold; } + +PRE + { FONT-FAMILY: monospace; } + +CODE + { FONT-FAMILY: monospace; } + +.DIVIDER + { COLOR: #C8B078; + BACKGROUND: #C8B078; } + +.BORDER + { BACKGROUND: black; } + +.ASIDE + { COLOR: #000000; + BACKGROUND: #FFE0FF; + FONT: 90% "Arial", sans-serif; + FONT-STYLE: normal; + FONT-WEIGHT: normal; + TEXT-DECORATION: none; } + +BLOCKQUOTE, .DESC + { FONT: 90% "Arial", sans-serif; + COLOR: #00007F; + FONT-STYLE: italic; } + +.JROW + { BACKGROUND: #CFCFFF; + FONT: 90% "Arial", sans-serif; } + +.SAROW + { BACKGROUND: #FFFFFF; + FONT: 90% "Arial", sans-serif; } + +.SSROW + { BACKGROUND: #FFCFCF; + FONT: 90% "Arial", sans-serif; } + +.DBROW + { BACKGROUND: #AFFFAF; + FONT: 90% "Arial", sans-serif; }