billr@saab.CNA.TEK.COM (Bill Randle) (02/20/91)
Submitted-by: jcg@tree.uucp (Chris Gonnerman) Posting-number: Volume 12, Issue 37 Archive-name: mdg/Part01 Environment: System V, SunOS 4.1, curses [I was able to compile this sucessfully on a Sun SS-2 running SunOS 4.1.1 using the System V environment. I've added a diff to patch the Makefile for compiling under SunOS. If you're running straight SysV, it should as is (with maybe the exception of the 'tinfo' lib. -br] [From the author:] [[Following is the source to MDG, the Multiuser Dungeon Game. As stated in the README it is only suitable for use on System V Release 3 and 4, and SunOS 4.1. It is a Hack/Larn/Rogue style game, but with a difference... it allows players true concurrent play. Up to 9 players at a time can simultaneously be in the game. They can ignore each other, or do battle, or team up to fight the monsters... anything that they want to do.]] #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 1 (of 6)." # Contents: README MANIFEST gmain.c mdg_dir mdg_dir/note.start # msghandler.c # Wrapped by billr@saab on Tue Feb 19 14:49:40 1991 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'README' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'README'\" else echo shar: Extracting \"'README'\" \(19787 characters\) sed "s/^X//" >'README' <<'END_OF_FILE' X@(#) MDG Multi-user Dungeon Game, Copyright 1990 John C. Gonnerman. X@(#) README Version 1.6, 1/11/91 X X XMDG is a multi-user Hack/Larn/Rogue type of game. It uses System V IPC Xcalls (message queues and shared memory) so it is only available for Xthose systems. It is not a "MUD" or any sort of a network game. XRather, it is intended to be played on a single Unix-based system, Xperhaps a BBS (such as the Tree in Sacramento... there's your plug Alan). X XNo reason why it could not be played over Telnet, though... on a LAN it Xcould really scream! X X XPORTING NOTES: X XAs stated, MDG is a SysV game. Getting it to run on any other system may Xbe impossible at this time. It was developed on a Xenix System V host, Xbut I only used the standard AT&T SysV Release 3.2 manual as a reference. XTherefore, I expect it to be fairly portable over most SysV systems. XRecent work on SunOS 4.1 leads me to believe that MDG will compile and Xrun there also. X XPlayers of MDG must be identified by number. The distributed version of XMDG includes a program, ident.c, which will return the uid number and Xreal name of a user for use by MDG. This program is automatically called Xby MDG to identify a player. For BBS systems, however, this may not be Xacceptible; if it is not, you will need to write your own ident.c to Xprovide this information. For instance, here on Tree the BBS password Xfile is consulted by MDG to get the BBS userid-number and the player's Xhandle. ident can also be a shell script, if desired, though it will be Xslower that way... remember that the slowdown caused by ident affects XALL current players, since the daemon task must run ident. It is a Xtradeoff to have a separate ident program, but the additional overhead Xof the current program seems minimal. X XSystem resources: MDG is tuned (as delivered) for a system with 40 Xmessage headers configured in the kernel. If you have fewer than this, do Xnot attempt to use MDG until you reconfigure as the daemon task can hang Xif insufficient message headers are available. If you want to use more X(to speed up MDG's response or allow for more users) tune the Xqueue-sizing parameters in struct.h. X XLikewise, MDG needs sufficient shared memory to store the current Xplayers, the map, and the item table. (The monster table is stored in X"conventional" memory by the daemon, as is the note table). This is Xharder to figure because of the variable size of these tables. X XFinally, MDG needs as many semaphores as there are to be concurrent Xplayers... for 9 players (the maximum, see below) a single semaphore set Xof 9 semaphores is created. X XThe game can handle up to 9 players if it is configured for that many. XThe shipped version is tuned and configured for only 6. For purposes of Xa clean screen display, set MAX_PLAYERS to 3, 6, or 9. The tuning of Xmessage queue usage is based on 6 players so you will need more messages Xto run 9 players. X X XADMINISTRATION: X XThe primary MDG game programs will need to be installed in /usr/games (or Xwherever you store your games.) These programs are: X X mdg user-side MDG game program. X X mdg_char player "editor" used to buy lives, and review X stats. X X mdg_daemon the real game (daemon task). X X mdg_stop, really one program. mdg_stop stops the game X mdg_save daemon (by sending a message). mdg_save causes X the game to write a save file immediately. X XThe MDG programs will need to be setuid to "games" (or some other userid Xwhich will own the files). Alternately it can be setgid if the files Xhave correct permission modes. At the minimum, mdg_char will need this Xso it can access player files. The daemon can safely be made setuid Xbecause it will protect itself using a lock file so that it can't be Xstarted twice; this would allow players to restart the daemon if the Xsystem admin forgets. If the daemon dumps core, it will not remove it's Xlock file, which is a "feature" that prevents a presumably buggy daemon Xfrom being restarted. The user-side game program, mdg, also needs to be Xsetuid so it can access the ident program. The control program, Xmdg_stop/mdg_save, should likely NOT be setuid so that players can't stop Xthe daemon in progress. X XMDG will need a directory (GAME_HOME in setup.h) to store the game save Xfiles and support programs (ident) in. The shar'ed version of MDG includes Xa public domain set of save files in a subdirectory (mdg_dir) under the Xsource directory. X XThe following files comprise the game save: X X map.start, the game map. X map.save, X map.old X X item.start, item list save files. X item.save, X item.old X X monster.start, monster table save file. X monster.save, X monster.old X X note.start, note table save file. X note.save, X note.old X XThese files contain data that is saved each time the game is stopped, Xand each time a player leaves the game. EXCEPTION: map saves are done Xonly when the map has been changed and are delayed until there are no Xplayers in the game. X XThe .start files are used only if the .save files do not exist. The X.old files are created by renaming old .save files before writing new Xones. X XAdditionally, in the GAME_HOME directory of a running system you will find: X X LOCK.daemon used to prevent more than one daemon from being X started. this is just a rudimentary protection. X X errorlog stderr output of mdg_daemon... check this if X the daemon core dumps. X X statlog when shut down, the daemon saves the peak X messages per second it experienced while running X in this file. X X bin/mdg_clock the daemon clock program; sends pulses to the X daemon task for timing. X X bin/ident described above. X X players/default standard beginning character. X X players/plNNNNN player game save. X X players/prNNNNN player "profile" (just the preferred name). X X XRUNNING THE GAME: X XThe daemon task must be started before you can play MDG. Just run Xmdg_daemon. It will find and run the mdg_clock task, automatically. X(When you shut down the daemon with mdg_stop, mdg_daemon will kill the Xclock before shutting itself down). X XPlayers can then enter the game by running mdg. Player stats can be Xviewed, names changed, and extra lives purchased by running mdg_char. X XFor players to leave MDG, they must go HOME. This is done by running off Xthe map onto a HOME sector. (See mdg.doc for more info.) The Quit spell Xcan also be used. X X XHOW IT WORKS: X X +--------------+ ---------- +-------------------+ X | Daemon Task |---->| Shared |----->| Viewer Task (mdg) | X | (mdg_daemon) | | Memory | |-------------------| X +--------------+ ---------- | Input Task (mdg) | X ^ ^ +-------------------+ X Message > | | | X Queue > | +---------------------------------+ X | X +--------------+ X | Clock Task | X | (mdg_clock) | X +--------------+ X Xmdg_daemon, when started, starts in turn mdg_clock. The clock accesses Xthe daemon's common input queue, and each second sends a "pulse" to Xcause the daemon to update it's timers and perform monster moves and Xother scheduled operations. X XWhen mdg is started on the user side, it fork()'s into two processes. XOne is responsible for parsing user input, and sends messages to Xmdg_daemon on the common input queue. The other, the Viewer, accesses Xthe shared memory of the daemon to read map sectors, player stats, and Xthe item list. X XNot shown are the Viewer semaphores. Each viewer task watches a Xsemaphore, doing screen updates when the semaphore is raised. The Xdaemon raises a player's semaphore to indicate that a screen update is Xneeded. X XThis is an efficient system in terms of CPU resources. Each process is Xsuspended much of the time (the daemon blocked on a message queue, the Xviewer(s) blocked on semaphores, the clock sleeping, and the input task Xblocked on the keyboard). X XThere is an additional shared segment private to each pair of mdg Xprocesses, used for the "help line" on the bottom of mdg's screen. X X XCREATING YOUR OWN DATA FILES: X XFour basic datasets comprise a game startup set: map.start, item.start, Xnote.start, and monster.start. The formats of these files are listed Xbelow: X X XMap File map.start: X XThe first line is a sector count. It is used to avoid a prescan, that Xis, so the map file need only be read once. The sector count is used in Xcreating a shared memory segment large enough for the map. X XFollowing this are 16-line long sector entries, consisting of 13 lines Xof actual sector layout and 3 lines of other data. A sample sector Xfollows: X X +-----------------------------------------------+ X |~ . 0 | X |~ #..#..#..#..#..#..# . | X |~~ . . . | X |~~ #..# #..# . | X |~ . . . | X | # # . | X | . . . | X | # # . | X | . . . | X | #..# #..#..#..#..# . | X | ~~~~ ~~~~ . | X | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~ . | X |~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~. | X |Start Sector... | X |26 -1 1 21 / -3 -3 / -3 -3 -3 -3 -3 -3 -3 | X |0 1 0 '~' G0 | X +-----------------------------------------------+ X X(A border has been added for clarity.) X XEach of the first 13 lines contains 38 columns of map codes, a single Xperiod (for ease of editing), and on the first line only, 2 spaces and a Xsector number. Note that the first number is 0, so that in a 10 sector Xmap the highest sector number is 9. X XThe 14th line is the sector name, which is allowed to be up to 39 Xcharacters long. X XThe 15th line is the link-list. Each sector connects in up to 11 X"directions" to other sectors. Negative link numbers refer to one of Xseveral special locations, as shown in mapstruct.h; the implemented Xlinks are as follows: X X LOC_HOME -1 (used to indicate where players may X exit the game.) X LOC_NONESUCH -3 (nowhere, used to "block" a direction.) X LOC_TRADER -4 (leads to the Trader's place of X business.) X X-2 is LOC_PLAYER, and is not used on the map. X XThe link-list is read as follows: X X N S E W / U D / G1 G2 G3 G4 G5 G6 G7 X Xwhere N is North, S is South, etc., U is Up, D is Down, and the Gn Xentries are teleport gates 1 to 7. X XNote that the link-list is used only to LEAVE a sector; entry to a Xsector depends entirely on the link-list of the sector you are leaving. X XLine 16 of the sector is a general data line, as follows: X X 0 1 0 '~' G0 X ^ ^ ^ ^ ^ Xlevel -----+ | | | | Xlight -------+ | | | Xjump inhibit --+ | | Xborder character -+ | Xgod limit -----------+ X XLevel is used to distribute monsters; see below, under monster.start. X XLight may be one of the following levels: X X -1 Tends to "cancel" player-provided light. X 0 "Normal" darkness. X 1 Lighted in the day only. X 2 Lighted full-time. X XJump inhibit is used to limit teleportation to or from sectors. It only Xlimits teleportation by spell use, that is, it does not affect gates. X XBorder Character is a single character to show on the borders of a sector Xwhich lead to LOC_NONESUCH. X XGod Limit is used to block or limit Godlike map changes in the sector. It Xmay be an integer from 0 up. 1 or higher is interpreted as a multiplier Xto apply to the cost in Creation Points spent modifying the sector. If Xthe God Limit is 0, modifications are not allowed. X X XItem File item.start: X XThis file, like map.start (and in fact like the other game save files) Xbegins with a count of items. Following the item count are the items, Xwhere each item consists of two lines. The first line of an item is Xit's name, and the second is a stat record in the following format: X X [0] 5, 6 <$$, 0, 0> 1:1 0 $600 (0) C0 X ^ ^ ^ ^^ ^ ^ ^ ^ ^ ^ ^ ^ Xsector --+ | | || | | | | | | | | Xx ----------+ | || | | | | | | | | Xy -------------+ || | | | | | | | | Xtype -------------+| | | | | | | | | Xsymbol ------------+ | | | | | | | | Xeffect or value ------+ | | | | | | | Xrange -------------------+ | | | | | | Xmax uses -------------------+ | | | | | Xremaining uses ---------------+ | | | | Xage ----------------------------+ | | | Xcost ------------------------------+ | | Xmodification count ---------------------+ | Xcurse flag ---------------------------------+ X XSector, x, and y indicate where the item is. If the sector is negative, Xthe meaning is as follows: X X -2 LOC_PLAYER... x is the player number who has it, y is 0. X -3 LOC_NONESUCH... the item is "lost," but may turn up when X a monster dies. X -4 LOC_TRADER... the item is in the Trader's. X X-1 (LOC_HOME) is meaningless here. X XType indicates what sort of item it is. Symbol usually is the same Xcode, but may be an underscore, which indicates an Obscured item. X XEffect (or value) is used as max damage for weapons, as protection for Xarmor or shields, as note index for notes, and for other objects it is Xused to indicate what effect or power the item confers. See the Xeffects.h file for more details. Note that any cursed object other than Xweapons and armor/shields acts as POISON! when activated by a player. X XThe handling of $ (treasure) and * (crystal) items is special... the Xsector number is never LOC_PLAYER. Instead, when such an item is picked Xup it is set to LOC_NONESUCH and the value of the item is added to a Xcounter in the player structure. Do not create an item file with no Xentries of these types! X XRange is normally only used by missile weapons, although I have plans in Xa future release to use it as an "effectiveness" value for generic Xobjects. X XMax Uses and Remaining Uses are normally used only on potions, scrolls, Xand "objects" (these three item types are the ones I refer to as X"generic objects"). X XAge is used to prevent an item from lying about on the map, in the XTrader's, or on an idle player for too long. We had problems in Xplaytest with players who would get ahold of all the good items, and Xthen leave for a month on vacation. So, item aging (or "decay") was Xadded using this field to put items in LOC_NONESUCH after a certain Xdelay (defined in setup.h as the DECAY_* parameters). X XCost is simply the base cost (value when sold) of the item. It is Xmultiplied by 1.5 when a player buys something. X XModification Count is used to limit changes to items made by Godlike Xcharacters. X XCurse flag may be C0 (not cursed) or C1 (cursed). Godlike characters Xcan remove or apply curses to items. A generic cursed item acts as XPOISON! to the character activating it. Cursed notes are unreadable. XCursed weapons do little or no damage, and cursed armor and shields Xprovide no protection. X X XMonster File monster.start: X XThis file starts with a count of monsters. Each monster is thereafter Xrepresented in two lines, the first being the monster's name, and the Xsecond being a stat record as follows: X X a [-3] 24, 11 15:15 2 0/60:10 L3 D2 IQ=50 X ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ Xcode ---+ | | | | | | | | | | | | Xsector ----+ | | | | | | | | | | | Xx -------------+ | | | | | | | | | | Xy -----------------+ | | | | | | | | | Xmax hit points -------+ | | | | | | | | Xcurrent hit points ------+ | | | | | | | Xmovement -------------------+ | | | | | | Xattack type ------------------+ | | | | | Xto hit -------------------------+ | | | | Xmax damage ------------------------+ | | | Xmap level ----------------------------+ | | Xdanger level ----------------------------+ | Xintelligence -------------------------------+ X XCode is the character (must be a letter or ampersand) to be used to Xdisplay the monster. Use of a character other than a letter or Xampersand will result in a monster immune to almost all attacks. X XSector, x, and y are used to indicate the monster's current location. XDead monsters have a -3 sector (LOC_NONESUCH). No other negative sector Xnumber is meaningful. X XMax Hit Points and Current Hit Points indicate the potential damage the Xmonster can take before death. Monsters do not heal. However, a dead Xmonster can be resurrected automatically... X XMovement is the number of moves per "pulse" that the monster may take. X XAttack type is used to select between normal attacks and several special Xmonster attacks... see the file "combat.h" for details. X XTo Hit and Damage are used (in most attack types) as the odds to hit a Xplayer and the maximum damage that may be done. The To Hit value is Xcompared with the player's Fighting score to generate an actual percent Xchance. X XMap Level is used to distribute the monster when resurrecting it. A Xmonster of level N may be resurrected in any sector of level N up to and Xincluding level N+4. Thus a "level band" is 5 levels wide. X XDanger Level is used to determine how much treasure the monster is Xworth. Danger Levels equal to or greater than DANGER_CUT (setup.h) will Xresult in a monster able to "cough up" Magic Crystals when it dies. X XIntelligence is used to determine if the monster uses the stupid or Xsmart movement routine. It is a simple percentage chance that the Xmonster will select a smart move. Note that smart moves are not all XTHAT smart. X X XNote File note.start: X XThis file consists of a record count, followed by the note text, one Xnote per line. Notice that more than one note item can reference the Xsame note in the note table... see the Mystic Slate(s) in the example Xitem.start file. X X XTIPS ON ADMINISTRATION: X X- Monsters are moved on the "pace and pulse with tick limit" method. X The original (beta) version of MDG used "pace and pulse"... monsters X each being moved once for each player move (if the monster had X movement left) with all remaining moves, if any, being made all at X once on the pulse. This however was shown to move monsters all at X once pretty much at the beginning of the pulse. Therefore, monsters X are moved in this version once per each N player moves. Exception: X if any monster is under attack, monsters move once per player attack. X "N" is set to the value TICKSPERPACE in setup.h. It may need to be X increased on fast machines or decreased on slow ones. Use the values X in the file "statlog" to help with the tuning... the value of X TICKSPERPACE should be around 1/4th or 1/5th of the Peak Messages per X Second of mdg_daemon. X X- Avoid giving out CP for free. Players with possibly excellent ideas X will beg the game administrator to give them a few extra CP so they X can finish the area they are building. Don't give in. CP are hard to X come by for a reason... there would be little challenge if they were X cheap. X X However YOU (the game admin) should have a large number of CP much of X the time, to allow you to patch up the map as needed. X X- By far the hardest part of administrating MDG is the God Powers. Once X you have a Godlike player in the game, the map will begin undergoing X strange metamorphoses. It is HIGHLY suggested, if you remodel the X sample map or build your own, that you LOCK (God Limit 0) all sectors X that have links to HOME in them. X X- The game limit values (in setup.h) can be changed to give a much X different feel to the game. You might wish to raise the limits of God X Powers to make only the VERY toughest players Godlike. Other limits X can be altered too... spell durations, limits for the levels of the X Detect spell, etc. X X- The Trader is located near HOME as a way of assisting newbie players. X You might make it harder to get to if your players require more of a X challenge... but don't remove it entirely. Note that regardless of X how many ways you can enter the Trader's, there is effectively only X one Trader in the game. One suggestion is to place the Traders at the X top or bottom of a set of stairs (what a surprise). X END_OF_FILE if test 19787 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'MANIFEST' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'MANIFEST'\" else echo shar: Extracting \"'MANIFEST'\" \(1911 characters\) sed "s/^X//" >'MANIFEST' <<'END_OF_FILE' X File Name Archive # Description X----------------------------------------------------------- X Copyright 3 X MANIFEST 1 This shipping list X Makefile 5 X Mk_sun.diff 6 X README 1 X clock.c 5 X cmain.c 4 X combat.c 3 X combat.h 6 X config.h 6 X dayclock.c 6 X defstruct.h 5 X dident.c 6 X dmain.c 5 X dmsg.c 6 X effect.c 4 X effect.h 6 X files.h 6 X findplayer.c 5 X genstruct.h 6 X gident.c 5 X gmain.c 1 X godpower.c 4 X help.c 5 X help.h 6 X ident.c 5 X improve.c 5 X input.c 3 X loadconfig.c 5 X magic.c 3 X mapstruct.h 6 X mdg.doc 4 X mdg_dir 1 X mdg_dir/NoCopyright 5 X mdg_dir/item.start 5 X mdg_dir/map.start 2 X mdg_dir/monster.start 5 X mdg_dir/note.start 1 X messages.h 5 X monsters.c 5 X monstruct.h 6 X msghandler.c 1 X msgstruct.h 6 X players.c 3 X plrstruct.h 5 X random.c 6 X ranged.c 5 X seg.c 4 X setup.h 5 X show.c 4 X show.h 6 X speak.c 5 X spells.c 6 X spells.h 6 X stopdaemon.c 6 X strstr.c 6 X struct.h 4 X traps.c 6 END_OF_FILE if test 1911 -ne `wc -c <'MANIFEST'`; then echo shar: \"'MANIFEST'\" unpacked with wrong size! fi # end of 'MANIFEST' fi if test -f 'gmain.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'gmain.c'\" else echo shar: Extracting \"'gmain.c'\" \(5884 characters\) sed "s/^X//" >'gmain.c' <<'END_OF_FILE' X/* X MDG Multiuser Dungeon Game -- mdg X X MDG is Copyright 1990 John C. Gonnerman X This program is subject to the general MDG X copyright statement (see enclosed file, Copyright). X*/ X X Xstatic char *sccsx = "@(#) MDG Copyright 1990 John C. Gonnerman"; Xstatic char *sccsvers = "@(#) gmain.c\t(1.4)\tcreated 1/3/91"; X X#include <curses.h> X#include <fcntl.h> X#include <signal.h> X#include <string.h> X#include <ctype.h> X X#include <sys/types.h> X#include <sys/stat.h> X#include <sys/ipc.h> X#include <sys/msg.h> X#include <sys/sem.h> X#include <sys/shm.h> X X#include "setup.h" X#include "show.h" X#include "files.h" X#include "struct.h" X#include "messages.h" X#include "effect.h" X Xchar *progname; X Xextern long dmsgkey, mapkey, playerkey; X Xstruct player_seg *pseg; Xstruct map_seg *mseg; X Xstruct termio term, save; X Xextern int errno; Xextern char *sys_errlist[]; X Xextern char *spell_names[]; X Xint pid, dqid, gseg_id, playernum, player_indx; Xchar handle[20]; X Xstruct game_msg *gseg; X X Xmain(argc, argv) Xint argc; Xchar **argv; X{ X int ch, m_sem, p_sem, i_sem; X void game_over(), shutdown(); X int eputc(); X struct dmessage dmsg_buf; X struct sembuf ops[1]; X X if((progname = strrchr(argv[0], '/')) != NULL) X progname++; X else X progname = argv[0]; X X if(argc != 1) { X fprintf(stderr, ARGCOUNT, progname, argc); X exit(1); X } X X setupterm(NULL, 2, NULL); X tputs(tigetstr("clear"), 1, eputc); X X fputs(BAR, stderr); X fputs(TITLE, stderr); X fputs(COPYRIGHT, stderr); X fputs(PRESSMSG, stderr); X fputs(INDENT, stderr); X X io_init(); X (void)getchar(); X io_reset(); X X fputs(WAITMSG, stderr); X X chdir(GAME_HOME); X X if((playernum = get_player(handle)) == -1) { X fputs(UNKPLAYER, stderr); X exit(1); X } X X /* load keys from config file */ X X loadconfig(); X X /* open message queue */ X X if((dqid = msgget(dmsgkey, 0)) == -1) { X fputs(NOTONLINE, stderr); X exit(1); X } X X /* set key interrupt traps */ X X signal(SIGINT, SIG_IGN); X signal(SIGQUIT, SIG_IGN); X X /* create our private segment */ X X gseg_id = shmget(IPC_PRIVATE, sizeof(struct game_msg), X IPC_CREAT | 0700); X X if((gseg = (struct game_msg *)shmat(gseg_id, 0, 0)) == X (struct game_msg *)-1) { X fprintf(CANTERR, progname, "attach private segment", X sys_errlist[errno]); X exit(1); X } X X gseg->trader_offset = -1; X gseg->clear_ok = 0; X X /* attach the daemon segments. */ X X seg_attach(); X X /* set other signal traps */ X X signal(SIGHUP, shutdown); X X /* log in to the daemon */ X X dmsg_buf.msg_type = 1L; X dmsg_buf.playernum = playernum; X dmsg_buf.cmd = 'J'; X tell_daemon(&dmsg_buf); X X player_indx = wait_connect(); X X fputs(READYMSG, stderr); X X /* start the subprocess. */ X X if((pid = fork()) == -1) { X fprintf(stderr, CANTERR, progname, X "fork()", sys_errlist[errno]); X exit(1); X } X X if(pid == 0) X input_process(); X X /* set up the terminal */ X X initscr(); X curs_set(0); X io_init(); X X /* we got it, let's do it! */ X X m_sem = mseg->map_sem; X show_map(); X X i_sem = pseg->item_sem; X show_items(); X X p_sem = pseg->player_sem; X show_player(); X X show_gmsg(); X show_modes(); X X while(1) { X X if(pseg->p[player_indx].loc.sector == LOC_NONESUCH X || pseg->p[player_indx].loc.sector == LOC_HOME) X game_over(); X X if(pseg->p[player_indx].loc.sector == LOC_TRADER) { X if(gseg->trader_offset < 0) X gseg->trader_offset = 0; X show_trader(); X } else if(m_sem != mseg->map_sem) { X gseg->trader_offset = -1; X m_sem = mseg->map_sem; X show_map(); X } X X if(i_sem != pseg->item_sem) { X i_sem = pseg->item_sem; X show_items(); X } X X if(p_sem != pseg->player_sem) { X p_sem = pseg->player_sem; X show_player(); X } X X show_gmsg(); X show_modes(); X X refresh(); X X ops[0].sem_num = player_indx; X ops[0].sem_op = -1; X ops[0].sem_flg = 0; X X semop(pseg->sid, ops, 1); X X switch(gseg->clear_ok) { X case 1 : X gseg->clear_ok = 0; X clearok(stdscr, TRUE); X break; X } X } X} X X Xeputc(ch) Xint ch; X{ X putc(ch, stderr); X} X X Xio_init() X{ X ioctl(0, TCGETA, &term); X save = term; X term.c_iflag = 0; X term.c_oflag = 0; X term.c_lflag = ISIG; X term.c_cc[VMIN] = 1; X term.c_cc[VTIME] = 7; X ioctl(0, TCSETA, &term); X} X X Xio_reset() X{ X ioctl(0, TCSETA, &save); X} X X Xseg_attach() X{ X int seg_id; X X seg_id = shmget(playerkey, 0, 0); X pseg = (struct player_seg *) shmat(seg_id, (char *)0, SHM_RDONLY); X X seg_id = shmget(mapkey, 0, 0); X mseg = (struct map_seg *) shmat(seg_id, (char *)0, SHM_RDONLY); X} X X Xvoid game_over() X{ X struct dmessage msg_buf; X int status; X X move(PARK_Y, PARK_X + 2); X clrtoeol(); X move(PARK_Y, PARK_X + 2); X X if(pseg->p[player_indx].loc.sector == LOC_NONESUCH) X addstr(DIED); X else X addstr(GAMEOVER); X X refresh(); X X msg_buf.msg_type = 1L; X msg_buf.playernum = playernum; X msg_buf.cmd = 'Q'; X tell_daemon(&msg_buf); X X kill(pid, SIGTERM); X wait(&status); X X io_reset(); X X noecho(); X while(getch() != '\n'); X X endwin(); X X tputs(tigetstr("clear"), 1, eputc); X X shmdt(mseg); X shmdt(pseg); X X shmdt(gseg); X shmctl(gseg_id, IPC_RMID, NULL); X X exit(0); X} X X Xvoid shutdown() X{ X struct dmessage msg_buf; X X msg_buf.msg_type = 1L; X msg_buf.playernum = playernum; X msg_buf.cmd = 'A'; X tell_daemon(&msg_buf); X X shmdt(mseg); X shmdt(pseg); X X shmdt(gseg); X shmctl(gseg_id, IPC_RMID, NULL); X X kill(pid, SIGTERM); X X endwin(); X X exit(0); X} X X Xint wait_connect() X{ X int i, indx; X struct dmessage dmsg_buf; X X for(i = 0; i < 20; i++) { X sleep(1); X X for(indx = 0; indx < MAX_PLAYERS; indx++) X if(pseg->p[indx].playernum == playernum) X return indx; X } X X fputs(NORESPONSE, stderr); X X dmsg_buf.msg_type = 1L; X dmsg_buf.playernum = playernum; X dmsg_buf.cmd = 'A'; X tell_daemon(&dmsg_buf); X X shmdt(mseg); X shmdt(pseg); X X shmdt(gseg); X shmctl(gseg_id, IPC_RMID, NULL); X X kill(pid, SIGTERM); X X exit(1); X} X X Xint trader_item(p_item_indx) Xint p_item_indx; X{ X int i, cnt; X X cnt = 0; X X for(i = 0; i < pseg->item_count; i++) { X if(pseg->itm[i].loc.sector == LOC_TRADER) { X if(cnt == (p_item_indx + gseg->trader_offset)) X return i; X cnt++; X } X } X X return -1; X} X X X/* end of file. */ END_OF_FILE if test 5884 -ne `wc -c <'gmain.c'`; then echo shar: \"'gmain.c'\" unpacked with wrong size! fi # end of 'gmain.c' fi if test ! -d 'mdg_dir' ; then echo shar: Creating directory \"'mdg_dir'\" mkdir 'mdg_dir' fi if test -f 'mdg_dir/note.start' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'mdg_dir/note.start'\" else echo shar: Extracting \"'mdg_dir/note.start'\" \(42 characters\) sed "s/^X//" >'mdg_dir/note.start' <<'END_OF_FILE' X5 Xhuh? XSure I will. XR U Sure? Xbah! Xwhat?] END_OF_FILE if test 42 -ne `wc -c <'mdg_dir/note.start'`; then echo shar: \"'mdg_dir/note.start'\" unpacked with wrong size! fi # end of 'mdg_dir/note.start' fi if test -f 'msghandler.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'msghandler.c'\" else echo shar: Extracting \"'msghandler.c'\" \(27418 characters\) sed "s/^X//" >'msghandler.c' <<'END_OF_FILE' X/* X MDG Multiuser Dungeon Game -- msghandler.c message code X X MDG is Copyright 1990 John C. Gonnerman X This program is subject to the general MDG X copyright statement (see enclosed file, Copyright). X X Unfortunately, most of the daemon is in this file X somewhere... X*/ X Xstatic char *sccsvers = "@(#) msghandler.c\t(1.7)\tcreated 1/7/91"; X X#include <stdio.h> X#include <sys/types.h> X#include <sys/ipc.h> X#include <sys/msg.h> X#include <sys/sem.h> X X#include "setup.h" X#include "struct.h" X Xextern char *progname; X Xextern int errno; Xextern char *sys_errlist[]; X Xextern struct map_seg *mseg; Xextern struct player_seg *pseg; X Xextern int dqid, map_size, player_size; X Xextern struct monster_tbl *m_table; Xextern int n_monster; X Xextern struct note_tbl *note_table; Xextern int n_notes; X Xint save_count = 0; Xint minutes = 0; X X Xmsg_handler(mbuf) Xstruct dmessage mbuf; X{ X int indx, symbol, act_val; X static int ticker = 0; X X if(mbuf.cmd == '@') /* stop daemon */ X shutdown(); X X if(mbuf.cmd == '$') { /* checkpoint game */ X savegame(); X save_count = 0; X return; X } X X if(mbuf.cmd == '{') { /* tell all */ X dmsg_all(mbuf.text); X return; X } X X if(mbuf.playernum == -1) { /* "heartbeat" */ X move_monsters(); X heal_players(); X update_timer(); X move_ranged(); X notify_all(); X minute_clock(); X savemap(); X return; X } X X switch(mbuf.cmd) { X case 'J' : /* Join game */ X if(find_player(mbuf.playernum) != -1) X break; X if((indx = getfree()) == -1) X break; X loadplayer(indx, mbuf.playernum); X break; X X case 'Q' : /* Quit game */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X saveplayer(indx); X savegame(); X save_count = 0; X break; X X case 'A' : /* Abort game */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X abortplayer(indx); X savegame(); X save_count = 0; X break; X X case 'e' : /* enter */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X enter_cmd(indx); X break; X X case 's' : /* speak */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X speak(indx, mbuf.text); X break; X X case 'S' : /* sell */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X X if(delete_item(indx, mbuf.arg) == -1) X break; X X deselect(indx, mbuf.arg); X X pseg->itm[mbuf.arg].loc.sector = LOC_TRADER; X pseg->item_sem++; X X pseg->p[indx].gold += pseg->itm[mbuf.arg].value; X pseg->player_sem++; X X tell_player(indx); X X break; X X case 'B' : /* buy */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X X if(item_cnt(indx) >= PMAX_ITEMS) { X dmsg_add(indx, "You are overloaded."); X break; X } X X act_val = pseg->itm[mbuf.arg].value * 3 / 2; X X if(act_val > pseg->p[indx].gold) { X dmsg_add(indx, "You can't afford it!"); X break; X } X X insert_item(indx, mbuf.arg); X X pseg->p[indx].gold -= act_val; X pseg->player_sem++; X X pseg->itm[mbuf.arg].decay_cnt = 0; X pseg->itm[mbuf.arg].loc.sector = LOC_PLAYER; X pseg->itm[mbuf.arg].loc.x = pseg->p[indx].playernum; X pseg->itm[mbuf.arg].symbol = pseg->itm[mbuf.arg].type; X pseg->itm[mbuf.arg].loc.y = ITEM_NEW; X X pseg->item_sem++; X X tell_player(indx); X X break; X X case 'L' : /* leave */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X if(pseg->p[indx].loc.sector != LOC_TRADER) X break; X X if(place(indx, pseg->p[indx].loc.x) == -1) { X saveplayer(indx); X pseg->p[indx].loc.sector = LOC_HOME; X pseg->player_sem++; X } X X move_sym(&(pseg->p[indx].loc), 1 + indx); X X pseg->in_trader = -1; X tell_player(indx); X break; X X case 'p' : /* partner */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X if(mbuf.subcmd < 0 || mbuf.subcmd >= MAX_PLAYERS) X break; X pseg->p[indx].partners[mbuf.subcmd] = X (pseg->p[indx].partners[mbuf.subcmd] ? 0 : 1); X break; X X case 't' : /* take */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X take_item(indx); X break; X X case 'm' : /* move */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X symbol = pseg->p[indx].invis ? indx + 11 : indx + 1; X move_cmd(&(pseg->p[indx].loc), mbuf.subcmd, symbol, indx); X break; X X case 'D' : /* drop cash */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X drop_cash(indx, mbuf.subcmd, mbuf.arg); X break; X X case 'd' : /* drop item */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X drop_cmd(indx, mbuf.subcmd, mbuf.arg); X break; X X case 'u' : /* use item */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X use_cmd(indx, &mbuf); X break; X X case 'v' : /* verbose */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X if(pseg->p[indx].brief) { X pseg->p[indx].brief = 0; X dmsg_add(indx, "Verbose ON"); X } else { X pseg->p[indx].brief = 1; X dmsg_add(indx, "Verbose OFF"); X } X break; X X case 'w' : /* write */ X write_cmd(mbuf.arg, mbuf.text); X break; X X case 'c' : /* cast spell */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X cast_spell(indx, &mbuf); X break; X X case 'g' : /* god power */ X if((indx = find_player(mbuf.playernum)) == -1) X break; X god_power(indx, &mbuf); X break; X X default: X break; X } X X if(++ticker == TICKSPERPACE) { X pace_monsters(); X ticker = 0; X } X X pace_ranged(); X} X X Xnotify_all() X{ X int i; X X mseg->map_sem++; X pseg->player_sem++; X pseg->item_sem++; X X for(i = 0; i < MAX_PLAYERS; i++) X if(pseg->p[i].playernum >= 0) X tell_player(i); X} X X Xenter_cmd(indx) Xint indx; X{ X struct location *l; X int symbol; X X l = &(pseg->p[indx].loc); X X symbol = pseg->p[indx].invis ? indx + 11 : indx + 1; X X if(pseg->p[indx].loc.under == '<') /* down */ X move_cmd(l, L_DOWN, symbol, indx); X else if(pseg->p[indx].loc.under == '>') /* up */ X move_cmd(l, L_UP, symbol, indx); X} X X Xint player_online(p_num) Xint p_num; X{ X int i; X X for(i = 0; i < MAX_PLAYERS; i++) X if(pseg->p[i].playernum == p_num) X return 1; X X return 0; X} X X Xminute_clock() X{ X if(minutes++ < MINUTE) X return; X X minutes = 0; X X if(++(mseg->dayclock) > GAMEDAY) X mseg->dayclock = 0; X X switch(mseg->dayclock) { X case 0 : X dmsg_all("It's morning."); X break; X case G_MORNING : X dmsg_all("It's noon."); X break; X case G_AFTERNOON : X dmsg_all("It's evening."); X break; X case G_EVENING : X dmsg_all("It's night."); X break; X } X X decay_items(); X} X X Xdecay_items() X{ X int i, sector, x; X X for(i = pseg->item_count - 1; i >= 0; i--) { X sector = pseg->itm[i].loc.sector; X x = pseg->itm[i].loc.x; X X if(sector > 0 X || (sector == LOC_PLAYER && !player_online(x)) X || (sector == LOC_TRADER)) X pseg->itm[i].decay_cnt++; X else X pseg->itm[i].decay_cnt = 0; X X if(pseg->itm[i].type == NOTE) X pseg->itm[i].decay_cnt = 0; X X if(pseg->itm[i].type == CRYSTAL X && pseg->itm[i].decay_cnt > DECAY_CRYSTAL) X recycle(i); X else if(pseg->itm[i].symbol == HIDDEN X && pseg->itm[i].decay_cnt > DECAY_OBSCURE) X recycle(i); X else if(sector > 0 X && pseg->itm[i].decay_cnt > DECAY_MAP) X recycle(i); X else if(sector == LOC_PLAYER X && pseg->itm[i].decay_cnt > DECAY_PLAYER) X recycle(i); X else if(sector == LOC_TRADER X && pseg->itm[i].decay_cnt > DECAY_TRADER X && pseg->in_trader < 0) X recycle(i); X } X} X X Xfreshen_item(indx) Xint indx; X{ X pseg->itm[indx].decay_cnt = 0; X} X X Xrecycle(indx) Xint indx; X{ X int sector, x, y; X X if(!top_item(indx)) { X pseg->itm[indx].decay_cnt /= 2; X return; X } X X sector = pseg->itm[indx].loc.sector; X x = pseg->itm[indx].loc.x; X y = pseg->itm[indx].loc.y; X X if(sector >= 0) { X mseg->m[sector].map[y][x] = pseg->itm[indx].loc.under; X mseg->map_sem++; X notify(sector); X } X X pseg->itm[indx].loc.sector = LOC_NONESUCH; X pseg->itm[indx].decay_cnt = 0; X X pseg->item_sem++; X} X X Xupdate_timer() X{ X int indx; X X if(++save_count > CHECKP_LEVEL) { X savegame(); X save_count = 0; X } X X for(indx = 0; indx < MAX_PLAYERS; indx++) { X X if(pseg->p[indx].blocked > 0) X pseg->p[indx].blocked--; X X if(pseg->p[indx].user_spell > 0) X pseg->p[indx].user_spell--; X X if(pseg->p[indx].ma_count > 0) X pseg->p[indx].ma_count--; X X if(pseg->p[indx].knight > 0) X pseg->p[indx].knight--; X X if(pseg->p[indx].invis > 0) { X pseg->p[indx].invis--; X X if(pseg->p[indx].invis == 0) X visible(indx); X } X X if(pseg->p[indx].detect > 0) X pseg->p[indx].detect--; X X if(pseg->p[indx].light > 0) { X pseg->p[indx].light--; X X if(pseg->p[indx].playernum >= 0 X && pseg->p[indx].light == 10) X dmsg_add(indx, "it's getting dark..."); X } X pseg->p[indx].attacks = P_ATTACKS; X } X} X X Xwrite_cmd(i_indx, text) Xint i_indx; Xchar *text; X{ X int n_indx; X X if((n_indx = pseg->itm[i_indx].effect) < 0 || n_indx >= n_notes) X return; X X strncpy(note_table[n_indx].text, text, 34); X note_table[n_indx].text[34] = '\0'; X} X X Xuse_cmd(p_indx, mptr) Xint p_indx; Xstruct dmessage *mptr; X{ X int playernum, item_indx, dir; X X playernum = mptr->playernum; X item_indx = mptr->subcmd; X X if(item_indx < 0 X || item_indx >= pseg->item_count) X return; X X if(pseg->itm[item_indx].loc.sector != LOC_PLAYER X || pseg->itm[item_indx].loc.x != playernum) X return; X X switch(pseg->itm[item_indx].type) { X case WEAPON : X pseg->weapon[p_indx] = item_indx; X pseg->item_sem++; X tell_player(p_indx); X break; X X case SHIELD : X pseg->shield[p_indx] = item_indx; X pseg->item_sem++; X pseg->player_sem++; X tell_player(p_indx); X break; X X case ARMOR : X pseg->armor[p_indx] = item_indx; X pseg->item_sem++; X pseg->player_sem++; X tell_player(p_indx); X break; X X case SCROLL : X case POTION : X case OBJECT : X case MISSILE : X case NOTE : X activate_item(p_indx, mptr); X break; X } X} X X Xtell_player(p_indx) Xint p_indx; X{ X struct sembuf ops[1]; X X ops[0].sem_num = p_indx; X ops[0].sem_op = 1; X ops[0].sem_flg = 0; X X semop(pseg->sid, ops, 1); X} X X Xnotify(sector) Xint sector; X{ X int i; X X for(i = 0; i < MAX_PLAYERS; i++) X if(pseg->p[i].loc.sector == sector) X tell_player(i); X} X X Xdrop_cash(p_indx, dir, amount) Xint p_indx, dir, amount; X{ X int i; X X if(amount < 1 || amount > pseg->p[p_indx].gold) X return; X X for(i = 0; i < pseg->item_count; i++) { X if(pseg->itm[i].loc.sector == LOC_NONESUCH X && pseg->itm[i].type == CASH) { X pseg->itm[i].value = amount; X pseg->itm[i].decay_cnt = 0; X pseg->itm[i].symbol = pseg->itm[i].type; X X if(pseg->p[p_indx].gold_hidden) { X pseg->p[p_indx].gold_hidden = 0; X pseg->itm[i].symbol = HIDDEN; X } X X if(drop_it(p_indx, dir, i) != -1) X pseg->p[p_indx].gold -= amount; X X pseg->player_sem++; X tell_player(p_indx); X return; X } X } X X dmsg_add(p_indx, "No treasure slots free..."); X} X X Xdrop_cmd(p_indx, dir, indx) Xint p_indx, dir, indx; X{ X pseg->itm[indx].decay_cnt = 0; X X if(drop_it(p_indx, dir, indx) != -1) { X delete_item(p_indx, indx); X deselect(p_indx, indx); X } X} X X Xint drop_it(p_indx, dir, indx) Xint p_indx, dir, indx; X{ X int dest_x, dest_y, dest_a; X X dest_a = pseg->p[p_indx].loc.sector; X dest_x = pseg->p[p_indx].loc.x; X dest_y = pseg->p[p_indx].loc.y; X X switch(dir) { X case L_NORTH : X dest_y--; X if(dest_y < 0) { X dest_y = MAPROWS - 1; X dest_a = mseg->m[dest_a].links[L_NORTH]; X } X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_SOUTH : X dest_y++; X if(dest_y >= MAPROWS) { X dest_y = 0; X dest_a = mseg->m[dest_a].links[L_SOUTH]; X } X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_EAST : X dest_x++; X if(dest_x >= MAPCOLS) { X dest_x = 0; X dest_a = mseg->m[dest_a].links[L_EAST]; X } X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_WEST : X dest_x--; X if(dest_x < 0) { X dest_x = MAPCOLS - 1; X dest_a = mseg->m[dest_a].links[L_WEST]; X } X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_NEAST : X dest_x++; X dest_y--; X X if(dest_y < 0 && dest_x >= MAPCOLS) X return -1; X X if(dest_x >= MAPCOLS) { X dest_x = 0; X dest_a = mseg->m[dest_a].links[L_EAST]; X } X X if(dest_y < 0) { X dest_y = MAPROWS - 1; X dest_a = mseg->m[dest_a].links[L_NORTH]; X } X X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_SEAST : X dest_x++; X dest_y++; X X if(dest_y >= MAPROWS && dest_x >= MAPCOLS) X return -1; X X if(dest_x >= MAPCOLS) { X dest_x = 0; X dest_a = mseg->m[dest_a].links[L_EAST]; X } X X if(dest_y >= MAPROWS) { X dest_y = 0; X dest_a = mseg->m[dest_a].links[L_SOUTH]; X } X X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_NWEST : X dest_x--; X dest_y--; X X if(dest_y < 0 && dest_x < 0) X return -1; X X if(dest_x < 0) { X dest_x = MAPCOLS - 1; X dest_a = mseg->m[dest_a].links[L_WEST]; X } X X if(dest_y < 0) { X dest_y = MAPROWS - 1; X dest_a = mseg->m[dest_a].links[L_NORTH]; X } X X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X X case L_SWEST : X dest_x--; X dest_y++; X X if(dest_y >= MAPROWS && dest_x < 0) X return -1; X X if(dest_x < 0) { X dest_x = MAPCOLS - 1; X dest_a = mseg->m[dest_a].links[L_WEST]; X } X X if(dest_y > MAPROWS) { X dest_y = 0; X dest_a = mseg->m[dest_a].links[L_SOUTH]; X } X X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x]) X || is_item(mseg->m[dest_a].map[dest_y][dest_x])) X return put_item(indx, dest_a, dest_x, dest_y); X X return -1; X break; X } X} X X Xint is_app_clear(ch) Xchar ch; X{ X if(is_clear(ch) || is_item(ch)) X return 1; X X switch(ch) { X case '~' : X case '%' : X return 1; X default : X return 0; X } X} X X Xint is_clear(ch) Xchar ch; X{ X switch(ch) { X X /* gates */ X case '=' : X case '{' : X case '}' : X case ':' : X case ';' : X X case ' ' : X case '`' : X case '.' : X case ',' : X case '+' : X case '^' : X case '0' : X case '1' : X case '2' : X case '3' : X case '4' : X case '5' : X case '6' : X case '7' : X case '8' : X case '9' : X return 1; X default : X return 0; X } X} X X Xint put_item(indx, dest_a, dest_x, dest_y) Xint indx, dest_a, dest_x, dest_y; X{ X int i, over_indx, code; X static int depth = 0; X X if(indx < 0 X || indx >= pseg->item_count) X return -1; X X if(dest_a < 0) X return -1; X X if(is_clear(mseg->m[dest_a].map[dest_y][dest_x])) { X if(isdrop(mseg->m[dest_a].map[dest_y][dest_x]) X && depth < 10) { X depth++; X code = put_item(indx, mseg->m[dest_a].links[L_DOWN], X dest_x, dest_y); X depth--; X return code; X } else { X /* simple case. */ X pseg->itm[indx].loc.under = X mseg->m[dest_a].map[dest_y][dest_x]; X mseg->m[dest_a].map[dest_y][dest_x] = X pseg->itm[indx].symbol; X } X } else { X /* find the item that will be over this one. */ X over_indx = -1; X X for(i = pseg->item_count - 1; i > indx; i--) { X if(pseg->itm[i].loc.sector == dest_a X && pseg->itm[i].loc.x == dest_x X && pseg->itm[i].loc.y == dest_y) X over_indx = i; X } X X if(over_indx == -1) { X /* new item is on top. */ X pseg->itm[indx].loc.under = X mseg->m[dest_a].map[dest_y][dest_x]; X mseg->m[dest_a].map[dest_y][dest_x] = X pseg->itm[indx].symbol; X } else { X /* new item is under over_indx */ X pseg->itm[indx].loc.under = X pseg->itm[over_indx].loc.under; X pseg->itm[over_indx].loc.under = X pseg->itm[indx].symbol; X } X } X X pseg->itm[indx].loc.sector = dest_a; X pseg->itm[indx].loc.x = dest_x; X pseg->itm[indx].loc.y = dest_y; X X mseg->map_sem++; X pseg->item_sem++; X X notify(dest_a); X X return 0; X} X X Xmove_cmd(locptr, dir, symbol, p_indx) Xstruct location *locptr; Xint dir; Xchar symbol; Xint p_indx; X{ X struct location loc; X int sector; X char buf[35]; X X if((sector = locptr->sector) < 0) X return; X X mseg->m[sector].map[locptr->y][locptr->x] = locptr->under; X X loc.x = locptr->x; X loc.y = locptr->y; X loc.sector = locptr->sector; X X if(get_dest(&loc, dir) != -1) X move_subcmd(locptr, loc.sector, loc.x, loc.y, p_indx); X X if(pseg->p[p_indx].brief == 0) X if(locptr->under == '>' || locptr->under == '<') { X sprintf(buf, "stairs going %s", X (locptr->under == '<' ? "down" : "up" )); X dmsg_add(p_indx, buf); X } X X move_sym(locptr, symbol); X} X X Xint get_dest(lptr, dir) Xstruct location *lptr; Xint dir; X{ X switch(dir) { X case L_UP : X lptr->sector = mseg->m[lptr->sector].links[L_UP]; X return 0; X X case L_DOWN : X lptr->sector = mseg->m[lptr->sector].links[L_DOWN]; X return 0; X X case L_NORTH : X lptr->y--; X X if(lptr->y < 0) { X lptr->y = MAPROWS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_NORTH]; X } X return 0; X X case L_SOUTH : X lptr->y++; X X if(lptr->y >= MAPROWS) { X lptr->y = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_SOUTH]; X } X return 0; X X case L_EAST : X lptr->x++; X X if(lptr->x >= MAPCOLS) { X lptr->x = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_EAST]; X } X return 0; X X case L_WEST : X lptr->x--; X X if(lptr->x < 0) { X lptr->x = MAPCOLS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_WEST]; X } X return 0; X X case L_NEAST : X lptr->x++; X lptr->y--; X X if(lptr->y < 0 && lptr->x >= MAPCOLS) X return -1; X X if(lptr->y < 0) { X lptr->y = MAPROWS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_NORTH]; X } X X if(lptr->x >= MAPCOLS) { X lptr->x = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_EAST]; X } X return 0; X X case L_NWEST : X lptr->x--; X lptr->y--; X X if(lptr->y < 0 && lptr->x < 0) X return -1; X X if(lptr->y < 0) { X lptr->y = MAPROWS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_NORTH]; X } X X if(lptr->x < 0) { X lptr->x = MAPCOLS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_WEST]; X } X return 0; X X case L_SEAST : X lptr->x++; X lptr->y++; X X if(lptr->y >= MAPROWS && lptr->x >= MAPCOLS) X return -1; X X if(lptr->y >= MAPROWS) { X lptr->y = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_SOUTH]; X } X X if(lptr->x >= MAPCOLS) { X lptr->x = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_EAST]; X } X return 0; X X case L_SWEST : X lptr->x--; X lptr->y++; X X if(lptr->y >= MAPROWS && lptr->x < 0) X return -1; X X if(lptr->y >= MAPROWS) { X lptr->y = 0; X lptr->sector = X mseg->m[lptr->sector].links[L_SOUTH]; X } X X if(lptr->x < 0) { X lptr->x = MAPCOLS - 1; X lptr->sector = X mseg->m[lptr->sector].links[L_WEST]; X } X return 0; X } X X return -1; X} X X Xmove_subcmd(locptr, dest_a, dest_x, dest_y, p_indx) Xstruct location *locptr; Xint dest_a, dest_x, dest_y, p_indx; X{ X int rc, newsector, oldsector; X X /* move_to does not always move a player to the sector */ X /* stored in dest_a due to pits, etc. */ X X oldsector = pseg->p[p_indx].loc.sector; X X rc = move_to(locptr, dest_a, dest_x, dest_y, p_indx); X X newsector = pseg->p[p_indx].loc.sector; X X if(rc == 0 X && newsector >= 0 X && oldsector != newsector X && mseg->m[newsector].light < 0) { X pseg->p[p_indx].light -= rnd(500); X if(pseg->p[p_indx].light < 0) X pseg->p[p_indx].light = 0; X dmsg_add(p_indx, "Your light dims..."); X } X X if(p_indx >= 0 && (is_critter(rc) || is_player(rc))) X attack(p_indx, dest_a, dest_x, dest_y); X X if(oldsector != newsector) X notify(oldsector); X} X X Xmove_sym(locptr, symbol) Xstruct location *locptr; Xchar symbol; X{ X if(locptr->sector >= 0) { X mseg->m[locptr->sector].map[locptr->y][locptr->x] = symbol; X mseg->map_sem++; X notify(locptr->sector); X } X} X X Xint move_to(locptr, dest_a, dest_x, dest_y, p_indx) Xstruct location *locptr; Xint dest_a, dest_x, dest_y, p_indx; X{ X char at_loc; X int prev_s, temp_a, code, gatenum; X static int depth = 0; X X if(dest_a == LOC_HOME && p_indx >= 0) { X mseg->map_sem++; X pseg->p[p_indx].home = locptr->sector; X locptr->x = locptr->sector; X locptr->sector = dest_a; X tell_player(p_indx); X return 0; X } X X if(dest_a == LOC_TRADER && p_indx >= 0) { X if(pseg->in_trader >= 0) { X dmsg_add(p_indx, "Trader is busy."); X return -1; X } X pseg->in_trader = p_indx; X mseg->map_sem++; X tell_player(p_indx); X locptr->x = locptr->sector; X locptr->sector = dest_a; X return 0; X } X X if(dest_a < 0) X return -1; X X at_loc = mseg->m[dest_a].map[dest_y][dest_x]; X X switch(at_loc) { X case '^' : /* air */ X case '0' : /* pit */ X X if(p_indx < 0 && at_loc == '^') X return '^'; X X if(p_indx >= 0 X && (temp_a = mseg->m[dest_a].links[L_DOWN]) >= 0 X && is_app_clear(mseg->m[temp_a].map[dest_y][dest_x])) { X dmsg_add(p_indx, "You fell!"); X pgetzapped(p_indx, rnd(10)); X if(locptr->sector == LOC_NONESUCH) X break; X if(isdrop(mseg->m[temp_a].map[dest_y][dest_x]) X && depth < 10) { X depth++; X code = move_to(locptr, temp_a, X dest_x, dest_y, p_indx); X depth--; X return code; X } X locptr->sector = mseg->m[dest_a].links[L_DOWN]; X } else X locptr->sector = dest_a; X X locptr->x = dest_x; X locptr->y = dest_y; X locptr->under = mseg->m[locptr->sector].map[dest_y][dest_x]; X mseg->map_sem++; X return 0; X X case '=' : /* gateway 1 */ X case '{' : /* gateway 2 */ X case '}' : /* gateway 3 */ X case ':' : /* gateway 4 */ X case ';' : /* gateway 5 */ X case '-' : /* gateway 6 */ X case '|' : /* gateway 7 */ X X prev_s = locptr->sector; X locptr->sector = dest_a; X X if(p_indx >= 0) { X gatenum = gate_id(at_loc); X X temp_a = mseg->m[dest_a].links[L_GATE+gatenum]; X X if(temp_a == LOC_HOME) { X locptr->x = prev_s; X locptr->sector = temp_a; X mseg->map_sem++; X return 0; X } else if(temp_a >= 0) { X at_loc = mseg->m[temp_a].map[dest_y][dest_x]; X if(is_clear(at_loc)) { X pseg->p[p_indx].prev_sect = prev_s; X locptr->sector = temp_a; X } else if(is_critter(at_loc)) X knockoffm(temp_a, dest_x, dest_y); X } X } X X locptr->x = dest_x; X locptr->y = dest_y; X locptr->under = X mseg->m[locptr->sector].map[dest_y][dest_x]; X mseg->map_sem++; X return 0; X X case '1' : /* 1-9 are traps */ X case '2' : X case '3' : X case '4' : X case '5' : X case '6' : X case '7' : X case '8' : X case '9' : X if(p_indx >= 0) { X do_trap(p_indx, at_loc); X if(locptr->sector == LOC_NONESUCH) X break; X } X locptr->sector = dest_a; X locptr->x = dest_x; X locptr->y = dest_y; X locptr->under = at_loc; X mseg->map_sem++; X return 0; X X case '.' : X if(p_indx < 0) X return (int)at_loc; X /* fall through here. */ X case ' ' : X case ',' : X case '`' : /* shallow water */ X case '+' : /* secret door */ X case '<' : /* down */ X case '>' : /* up */ X case POTION : X case SCROLL : X case ARMOR : X case SHIELD : X case WEAPON : X case MISSILE : X case OBJECT : X case CASH : X case CRYSTAL : X case NOTE : X case HIDDEN : X if(p_indx < 0 && is_item(at_loc)) X punt_item(dest_a, dest_x, dest_y); X X locptr->sector = dest_a; X locptr->x = dest_x; X locptr->y = dest_y; X locptr->under = mseg->m[dest_a].map[dest_y][dest_x]; X mseg->map_sem++; X return 0; X X case '%' : X case '~' : X if(locptr->under == at_loc) X return (int)at_loc; X else { X locptr->sector = dest_a; X locptr->x = dest_x; X locptr->y = dest_y; X locptr->under = at_loc; X mseg->map_sem++; X return 0; X } X X default : X return (int)at_loc; X } X X} X X Xknockoffm(dest_a, dest_x, dest_y) Xint dest_a, dest_x, dest_y; X{ X int i; X X for(i = 0; i < n_monster; i++) X if(m_table[i].loc.sector == dest_a X && m_table[i].loc.x == dest_x X && m_table[i].loc.y == dest_y) { X clearm(i, 0); X m_table[i].loc.sector = LOC_NONESUCH; X return; X } X} X X Xint is_player(val) Xint val; X{ X return (val >= 1 && val <= 19); X} X X Xint is_critter(val) Xint val; X{ X return (val == '&') X || (val >= 'a' && val <= 'z') X || (val >= 'A' && val <= 'Z'); X} X X Xint is_item(val) Xint val; X{ X switch(val) { X case POTION : X case SCROLL : X case ARMOR : X case SHIELD : X case WEAPON : X case MISSILE : X case OBJECT : X case CASH : X case CRYSTAL : X case NOTE : X case HIDDEN : X return 1; X default : X return 0; X } X} X X Xint top_item(indx) Xint indx; X{ X int i; X X for(i = pseg->item_count - 1; i > indx; i--) X if(pseg->itm[i].loc.sector == pseg->itm[indx].loc.sector X && pseg->itm[i].loc.x == pseg->itm[indx].loc.x X && pseg->itm[i].loc.y == pseg->itm[indx].loc.y) X return 0; X X return 1; X} X X Xtake_item(indx) Xint indx; X{ X int i; X X if(indx < 0 X || pseg->p[indx].playernum < 0) X return; X X for(i = pseg->item_count - 1; i >= 0; i--) X if(pseg->itm[i].loc.sector == pseg->p[indx].loc.sector X && pseg->itm[i].loc.x == pseg->p[indx].loc.x X && pseg->itm[i].loc.y == pseg->p[indx].loc.y) { X X if(pseg->itm[i].type == CASH) { X pseg->p[indx].gold += X pseg->itm[i].value; X pseg->itm[i].loc.sector = LOC_NONESUCH; X pseg->itm[i].symbol = pseg->itm[i].type; X pseg->player_sem++; X } else if(pseg->itm[i].type == CRYSTAL) { X if(pseg->p[indx].max_hp >= HP_CUTOFF X && pseg->p[indx].max_mp >= MP_CUTOFF) X pseg->p[indx].createpts += X pseg->itm[i].effect; X pseg->itm[i].loc.sector = LOC_NONESUCH; X pseg->itm[i].symbol = pseg->itm[i].type; X pseg->player_sem++; X } else { X if(insert_item(indx, i) == -1) X return; X X pseg->item_sem++; X X pseg->itm[i].decay_cnt = 0; X pseg->itm[i].loc.sector = LOC_PLAYER; X pseg->itm[i].loc.x = X pseg->p[indx].playernum; X pseg->itm[i].symbol = pseg->itm[i].type; X pseg->itm[i].loc.y = ITEM_NEW; X } X X pseg->p[indx].loc.under = pseg->itm[i].loc.under; X X tell_player(indx); X X return; X } X X /* if we got here, it is an item but we can't find it! */ X /* so, clear the space. */ X X if(is_item(pseg->p[indx].loc.under)) X pseg->p[indx].loc.under = ' '; X} X X Xpunt_item(sector, x, y) Xint sector, x, y; X{ X int i; X X for(i = pseg->item_count - 1; i >= 0; i--) X if(pseg->itm[i].loc.sector == sector X && pseg->itm[i].loc.x == x X && pseg->itm[i].loc.y == y) { X X pseg->itm[i].loc.sector = LOC_NONESUCH; X pseg->itm[i].symbol = pseg->itm[i].type; X X mseg->m[sector].map[y][x] = pseg->itm[i].loc.under; X mseg->map_sem++; X X notify(sector); X X return; X } X} X X Xint item_cnt(p_indx) Xint p_indx; X{ X int i, cnt; X X cnt = 0; X X for(i = 0; i < pseg->item_count; i++) { X if(pseg->itm[i].loc.sector == LOC_PLAYER X && pseg->itm[i].loc.x == pseg->p[p_indx].playernum) X cnt++; X } X X return cnt; X} X X Xint insert_item(p_indx, i_indx) Xint p_indx, i_indx; X{ X int i; X X for(i = 0; i < PMAX_ITEMS; i++) X if(pseg->p[p_indx].items[i] == -1) { X pseg->p[p_indx].items[i] = i_indx; X return i; X } X X return -1; X} X X Xdeselect(p_indx, i_indx) Xint p_indx, i_indx; X{ X if(pseg->armor[p_indx] == i_indx) X pseg->armor[p_indx] = -1; X X if(pseg->shield[p_indx] == i_indx) X pseg->shield[p_indx] = -1; X X if(pseg->weapon[p_indx] == i_indx) X pseg->weapon[p_indx] = -1; X} X X Xint delete_item(p_indx, i_indx) Xint p_indx, i_indx; X{ X int i; X X for(i = 0; i < PMAX_ITEMS; i++) X if(pseg->p[p_indx].items[i] == i_indx) { X pseg->p[p_indx].items[i] = -1; X return i; X } X X return -1; X} X X Xint gate_id(ch) Xint ch; X{ X switch(ch) { X case '=' : /* gateway 1 */ X return 0; X case '{' : /* gateway 2 */ X return 1; X case '}' : /* gateway 3 */ X return 2; X case ':' : /* gateway 4 */ X return 3; X case ';' : /* gateway 5 */ X return 4; X case '-' : /* gateway 6 */ X return 5; X case '|' : /* gateway 7 */ X return 6; X default : X return 0; X } X} X X Xint isdrop(ch) Xint ch; X{ X switch(ch) { X case '^' : X case '0' : X return 1; X } X X return 0; X} X X X/* end of file. */ END_OF_FILE if test 27418 -ne `wc -c <'msghandler.c'`; then echo shar: \"'msghandler.c'\" unpacked with wrong size! fi # end of 'msghandler.c' fi echo shar: End of archive 1 \(of 6\). cp /dev/null ark1isdone MISSING="" for I in 1 2 3 4 5 6 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 6 archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0