Initial drop
authorfluffymormegil <mpread@chiark.greenend.org.uk>
Fri, 16 Mar 2012 16:55:27 +0000 (16:55 +0000)
committerfluffymormegil <mpread@chiark.greenend.org.uk>
Fri, 16 Mar 2012 16:55:27 +0000 (16:55 +0000)
22 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
bmagic.c [new file with mode: 0644]
bmagic.h [new file with mode: 0644]
combat.c [new file with mode: 0644]
combat.h [new file with mode: 0644]
display.c [new file with mode: 0644]
dunbash.h [new file with mode: 0644]
main.c [new file with mode: 0644]
map.c [new file with mode: 0644]
misc.c [new file with mode: 0644]
mon2.c [new file with mode: 0644]
monsters.c [new file with mode: 0644]
monsters.h [new file with mode: 0644]
notes.txt [new file with mode: 0644]
objects.c [new file with mode: 0644]
permobj.c [new file with mode: 0644]
permons.c [new file with mode: 0644]
pmon2.c [new file with mode: 0644]
rng.c [new file with mode: 0644]
u.c [new file with mode: 0644]
vector.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..77d5e63
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+cavechop
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1d251ce
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+# Makefile for Dungeon Bash
+#
+# Linux/gcc only; I'm writing this in riceboy-hacker mode.  Deal.
+
+OBJS=bmagic.o combat.o display.o main.o map.o misc.o monsters.o mon2.o objects.o permobj.o permons.o pmon2.o rng.o u.o vector.o
+
+GAME=dungeonbash
+# MPR: per <lj user="ewx">, users are advised to remove -Werror when
+# building on Darwin due to a bug in Darwin's version of "ncurses.h".
+#
+# It is apparently possible to build this code for Windows XP, although I have
+# no details.
+MAJVERS=1
+MINVERS=7
+CFLAGS=-c -g -Wall -Wstrict-prototypes -Wwrite-strings -Wmissing-prototypes -Werror -Wredundant-decls -Wunreachable-code -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS)
+LINKFLAGS=-lpanel -lncurses -g
+
+all: $(GAME)
+
+$(GAME): $(OBJS)
+       $(CC) $(OBJS) $(LINKFLAGS) -o $(GAME)
+
+archive: clean
+       (cd .. && tar cvzf dungeonbash-$(MAJVERS).$(MINVERS).tar.gz dungeonbash-$(MAJVERS).$(MINVERS))
+
+clean:
+       -rm -f *.o $(GAME) dunbash.log dunbash.sav.gz
+
+display.o: display.c dunbash.h
+
+main.o: main.c combat.h dunbash.h monsters.h
+
+combat.o: combat.c combat.h dunbash.h
+
+u.o: u.c combat.h dunbash.h
+
+permobj.o: permobj.c dunbash.h
+
+map.o: map.c dunbash.h
+
+permons.o: permons.c dunbash.h
+
+pmon2.o: pmon2.c dunbash.h monsters.h
+
+objects.o: objects.c dunbash.h
+
+monsters.o: monsters.c dunbash.h monsters.h
+
+mon2.o: mon2.c dunbash.h bmagic.h monsters.h
+
+vector.o: vector.c dunbash.h
+
+bmagic.o: bmagic.c dunbash.h bmagic.h
diff --git a/bmagic.c b/bmagic.c
new file mode 100644 (file)
index 0000000..9c2e482
--- /dev/null
+++ b/bmagic.c
@@ -0,0 +1,483 @@
+/* bmagic.c 
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "dunbash.h"
+#include "bmagic.h"
+#include "monsters.h"
+#include "combat.h"
+
+/* BLACK MAGIC
+ *
+ * Certain of the denizens of the dungeon have the power to use black magic
+ * against the player.
+ * 
+ * The "ordinary" lich may unleash bolts of necromantic force against
+ * the player, or smite him at close quarters with their staves of necromancy,
+ * or invoke grim curses against him.
+ *
+ * The dreaded master liches can smite the player from a distance with
+ * their necromantic powers without lying on a cardinal direction from
+ * him, and can steal the player's vitality with a touch, as well as having
+ * the spells of their lesser brethren.  Furthermore, they may attempt to
+ * evade the player in the same manner as wizards.
+ *
+ * Itinerant wizards roaming the dungeon cast bolts of lightning, and strike
+ * in hand-to-hand combat with staves wreathed with enchantments of shattering
+ * force; if sorely pressed, they may invoke their powers to teleport across
+ * the dungeon level, cheating the player of his victory.
+ * 
+ * Archmages, learned scholars of the Black Arts and veterans of many a
+ * confrontation, have the powers of wizards. In addition, an archmage who
+ * teleports away from the player to evade death may well leave him with a
+ * group of summoned monsters.
+ * 
+ * The more potent order of demons known as defilers may cast curses against
+ * the player, or call down a column of fire to smite him.
+ *
+ * Some forms of black magic may be defended against by wearing the proper
+ * armour or putting on a suitable ring; others bypass all such defences to
+ * strike the player directly, although some of these can be evaded by those
+ * with high enough agility.
+ */
+
+int use_black_magic(int mon)
+{
+    /* Returns zero for no spell selected, -1 for unsupported spell
+     * selected, 1 for supported spell selected. */
+    struct mon *mptr = monsters + mon;
+    int dy, dx;
+    int sy, sx;
+    enum monspell to_cast = MS_REJECT;
+    int rval = 1;      /* Default to success; failure paths will force this
+                        * to an appropriate value. */
+    int dieroll;
+    int meleerange;
+    int oncardinal;
+    int i;
+    int cansee;
+    int range;
+    compute_directions(u.y, u.x, mptr->y, mptr->x, &dy, &dx, &sy, &sx, &meleerange, &oncardinal);
+    cansee = inyourroom(mptr->y, mptr->x) || meleerange;
+    if ((dy * sy) >= (dx * sx))
+    {
+       range = dy * sy;
+    }
+    else
+    {
+       range = dx * sx;
+    }
+    switch (monsters[mon].mon_id)
+    {
+    case PM_ARCHMAGE:
+       if (cansee)
+       {
+           /* We have LOS; choose a spell on that basis. */
+           if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+           {
+               to_cast = zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+           }
+           else if (meleerange && (zero_die(10) > 3))
+           {
+               to_cast = MS_STRIKE_STAFF;
+           }
+           else if (oncardinal)
+           {
+               to_cast = MS_LIGHTNING;
+           }
+       }
+       else if (!zero_die(40))
+       {
+           /* 
+            * We lack LOS, but pass the 1-in-40 chance; use
+            * black magic to relocate us to the player's location.
+            */
+           to_cast = MS_TELEPORT_ASSAULT;
+       }
+       break;
+
+    case PM_WIZARD:
+       if (cansee)
+       {
+           if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+           {
+               to_cast = MS_TELEPORT_ESCAPE;
+           }
+           else if (meleerange && (zero_die(10) > 2))
+           {
+               to_cast = MS_STRIKE_STAFF;
+           }
+           else if (oncardinal)
+           {
+               to_cast = MS_LIGHTNING;
+           }
+       }
+       else if (!zero_die(80))
+       {
+           /* we lack LOS, but passed the 1-in-80 chance to
+            * close with the player by means of black magic. */
+           to_cast = MS_TELEPORT_ASSAULT;
+       }
+       break;
+
+    case PM_MASTER_LICH:
+       if (cansee)
+       {
+           if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 4))
+           {
+               to_cast = !zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+           }
+           else if (meleerange)
+           {
+               switch (zero_die(7))
+               {
+               case 6:
+                   if (!u.withering)
+                   {
+                       to_cast = MS_CURSE_WITHERING;
+                       break;
+                   }
+               case 4:
+                   if (!u.leadfoot)
+                   {
+                       to_cast = MS_CURSE_LEADFOOT;
+                       break;
+                   }
+                   /* fall through */
+               case 5:
+                   if (!u.armourmelt)
+                   {
+                       to_cast = MS_CURSE_ARMOURMELT;
+                       break;
+                   }
+                   /* fall through */
+               default:
+                   to_cast = zero_die(2) ? MS_CHILLING_TOUCH : MS_STRIKE_STAFF;
+                   break;
+               }
+           }
+           else if (range < 3)
+           {
+               switch (zero_die(10))
+               {
+               case 9:
+                   if (!u.withering)
+                   {
+                       to_cast = MS_CURSE_WITHERING;
+                       break;
+                   }
+               case 8:
+                   if (!u.leadfoot)
+                   {
+                       to_cast = MS_CURSE_LEADFOOT;
+                       break;
+                   }
+                   /* fall through */
+               case 7:
+                   if (!u.armourmelt)
+                   {
+                       to_cast = MS_CURSE_ARMOURMELT;
+                       break;
+                   }
+                   /* fall through */
+               default:
+                   to_cast = MS_NECRO_SMITE;
+                   break;
+               }
+           }
+           else if (range < 8)
+           {
+               switch (zero_die(7))
+               {
+               case 6:
+                   if (!u.withering)
+                   {
+                       to_cast = MS_CURSE_WITHERING;
+                       break;
+                   }
+               case 4:
+                   if (!u.leadfoot)
+                   {
+                       to_cast = MS_CURSE_LEADFOOT;
+                       break;
+                   }
+                   /* fall through */
+               case 5:
+                   if (!u.armourmelt)
+                   {
+                       to_cast = MS_CURSE_ARMOURMELT;
+                       break;
+                   }
+                   /* fall through */
+               default:
+                   to_cast = MS_NECRO_SMITE;
+                   break;
+               }
+           }
+       }
+       else if (!zero_die(40))
+       {
+           /* we lack LOS, but passed the 1-in-40 chance to
+            * close with the player by means of black magic. */
+           to_cast = MS_TELEPORT_ASSAULT;
+       }
+       break;
+    case PM_LICH:
+       if (cansee)
+       {
+           if (meleerange)
+           {
+               dieroll = zero_die(6);
+               switch (dieroll)
+               {
+               case 4:
+                   if (!u.leadfoot)
+                   {
+                       to_cast = MS_CURSE_LEADFOOT;
+                       break;
+                   }
+                   /* fall through */
+               case 5:
+                   if (!u.armourmelt)
+                   {
+                       to_cast = MS_CURSE_ARMOURMELT;
+                       break;
+                   }
+                   /* fall through */
+               default:
+                   to_cast = MS_NECRO_STAFF;
+                   break;
+               }
+           }
+           else if (oncardinal)
+           {
+               if (range < 3)
+               {
+                   switch (zero_die(6))
+                   {
+                   case 4:
+                       if (!u.leadfoot)
+                       {
+                           to_cast = MS_CURSE_LEADFOOT;
+                           break;
+                       }
+                       /* fall through */
+                   case 5:
+                       if (!u.armourmelt)
+                       {
+                           to_cast = MS_CURSE_ARMOURMELT;
+                           break;
+                       }
+                       /* fall through */
+                   default:
+                       to_cast = MS_NECRO_BOLT;
+                       break;
+                   }
+               }
+               else
+               {
+                   to_cast = MS_NECRO_BOLT;
+               }
+           }
+           break;
+       }
+       break;
+    case PM_DEFILER:
+       if (cansee)
+       {
+           if (!meleerange)
+           {
+               switch (zero_die(7))
+               {
+               case 6:
+                   if (!u.withering)
+                   {
+                       to_cast = MS_CURSE_WITHERING;
+                       break;
+                   }
+               case 4:
+                   if (!u.leadfoot)
+                   {
+                       to_cast = MS_CURSE_LEADFOOT;
+                       break;
+                   }
+                   /* fall through */
+               case 5:
+                   if (!u.armourmelt)
+                   {
+                       to_cast = MS_CURSE_ARMOURMELT;
+                       break;
+                   }
+                   /* fall through */
+               default:
+                   to_cast = MS_FIRE_COLUMN;
+                   break;
+               }
+           }
+       }
+       break;
+
+    default:
+       break;
+    }
+    switch (to_cast)
+    {
+    default:
+       /* If this happens, we're trying to cast an unimplemented
+        * spell. */
+       print_msg("Can't happen: Bogus spell %d!\n", to_cast);
+       rval = -1;
+       break;
+
+    case MS_REJECT:
+       /* No usable spell available. */
+       rval = 0;
+       break;
+
+    case MS_STRIKE_STAFF:
+       mhitu(mon, DT_PHYS);
+       break;
+
+    case MS_NECRO_STAFF:
+       mhitu(mon, DT_NECRO);
+       break;
+
+    case MS_CHILLING_TOUCH:
+       mhitu(mon, DT_COLD);
+       break;
+
+    case MS_LIGHTNING:
+    case MS_NECRO_BOLT:
+       mshootu(mon);
+       break;
+
+    case MS_TELEPORT_AND_SUMMON:
+       /* Do the summoning... */
+       print_mon_name(mon, 3);
+       print_msg(" calls for help...\n");
+       /* (Try to) summon 2-6 monsters. */
+       i = summoning(mptr->y, mptr->x, dice(2, 3));
+       if (i == 0)
+       {
+           print_msg("... luckily for you, help wasn't listening.\n");
+       }
+       else
+       {
+           print_msg("... and gets it.\n");
+       }
+       /* ... and fall through. */
+    case MS_TELEPORT_ESCAPE:
+       print_mon_name(mon, 3);
+       print_msg(" vanishes in a puff of smoke.\n");
+       teleport_mon(mon);
+       break;
+
+    case MS_TELEPORT_ASSAULT:
+       /* It is rare that a monster will cast this spell, but not
+        * unheard of. */
+       teleport_mon_to_you(mon);
+       break;
+
+    case MS_CURSE_ARMOURMELT:
+       mon_curses(mon);
+       if (u.protection)
+       {
+           malignant_aura();
+       }
+       else
+       {
+           u.armourmelt = 10 + one_die(10);
+           print_msg("Your armour seems suddenly no stronger than dust!\n");
+       }
+       break;
+
+    case MS_CURSE_LEADFOOT:
+       mon_curses(mon);
+       if (u.protection)
+       {
+           malignant_aura();
+       }
+       else
+       {
+           u.leadfoot = 10 + one_die(10);
+           print_msg("Your feet feel like lead!\n");
+       }
+       break;
+
+    case MS_CURSE_WITHERING:
+       mon_curses(mon);
+       if (u.protection)
+       {
+           malignant_aura();
+       }
+       else
+       {
+           u.withering = 10 + one_die(10);
+           print_msg("Your limbs twist and wither!\n");
+       }
+       break;
+
+    case MS_NECRO_SMITE:
+       mon_curses(mon);
+       if (player_resists_dtype(DT_NECRO))
+       {
+           print_msg("Darkness reaches towards you, but dissolves.\n");
+       }
+       else
+       {
+           print_msg("Soul-chilling darkness engulfs you!\n");
+           damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+       }
+       break;
+
+    case MS_FIRE_COLUMN:
+       mon_curses(mon);
+       print_msg("The fires of hell ");
+       if (player_resists_dtype(DT_FIRE))
+       {
+           print_msg("lightly singe you.\n");
+           damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+       }
+       else
+       {
+           print_msg("burn you!\n");
+           damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
+       }
+       break;
+    }
+    return rval;
+}
+
+void malignant_aura()
+{
+    print_msg("A malignant aura surrounds you briefly.\n");
+}
+
+void mon_curses(int mon)
+{
+    print_mon_name(mon, 3);
+    print_msg(" points at you and curses horribly.\n");
+}
+
+/* bmagic.c */
diff --git a/bmagic.h b/bmagic.h
new file mode 100644 (file)
index 0000000..7006046
--- /dev/null
+++ b/bmagic.h
@@ -0,0 +1,59 @@
+/* bmagic.h
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BMAGIC_H
+#define BMAGIC_H
+
+/* XXX DATA TYPES XXX */
+
+enum monspell {
+       MS_REJECT = -1,         /* Rejection tag. */
+       /* "Melee" attacks */
+       MS_STRIKE_STAFF,        /* Wizard */
+       MS_NECRO_STAFF,         /* Lich */
+       MS_CHILLING_TOUCH,      /* Master Lich */
+       /* Ranged Attacks */
+       MS_LIGHTNING,   /* Wizard */
+       MS_NECRO_BOLT,  /* Lich */
+       MS_NECRO_SMITE, /* Master Lich - no cardinal alignment needed */
+       MS_FIRE_COLUMN, /* Defiler */
+       /* Curses */
+       MS_CURSE_ARMOURMELT,    /* All cursers */
+       MS_CURSE_LEADFOOT,      /* All cursers */
+       MS_CURSE_WITHERING,     /* Master Lich and Defiler only */
+       /* Evasion */
+       MS_TELEPORT_ESCAPE,     /* Wizard, Archmage, Master Lich */
+       MS_TELEPORT_AND_SUMMON, /* Archmage */
+       MS_TELEPORT_ASSAULT,    /* Wizard, Archmage, Master Lich */
+};
+
+extern int use_black_magic(int mon);
+extern void mon_curses(int mon);
+extern void malignant_aura(void);
+
+#endif
+
+/* dunbash.h */
diff --git a/combat.c b/combat.c
new file mode 100644 (file)
index 0000000..3d7e8a4
--- /dev/null
+++ b/combat.c
@@ -0,0 +1,468 @@
+/* combat.c 
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+#include "combat.h"
+#include "monsters.h"
+
+int player_attack(int dy, int dx)
+{
+    if ((objects[u.weapon].obj_id == PO_BOW) || (objects[u.weapon].obj_id == PO_CROSSBOW))
+    {
+       ushootm(dy, dx);
+    }
+    else if (mapmonster[u.y + dy][u.x + dx] != -1)
+    {
+       uhitm(mapmonster[u.y + dy][u.x + dx]);
+    }
+    else
+    {
+       print_msg("Nothing to attack.\n");
+       return 0;
+    }
+    return 1;
+}
+
+int uhitm(int mon)
+{
+    struct mon *mp;
+    struct obj *wep;
+    struct permobj *pwep;
+    struct obj *ring;
+    struct permobj *pring;
+    int tohit;
+    int damage;
+    int healing;
+    mp = monsters + mon;
+    tohit = zero_die(u.agility + u.level);
+    if (tohit < mp->defence)
+    {
+       print_msg("You miss.\n");
+       return 0;       /* Missed. */
+    }
+    print_msg("You hit ");
+    print_mon_name(mon, 1);
+    print_msg(".\n");
+    if (u.weapon != -1)
+    {
+       wep = objects + u.weapon;
+       pwep = permobjs + wep->obj_id;
+       damage = one_die(pwep->power) + (u.body / 10);
+    }
+    else
+    {
+       damage = u.body / 10;
+    }
+    if (u.ring != -1)
+    {
+       ring = objects + u.ring;
+       pring = permobjs + ring->obj_id;
+       switch (ring->obj_id)
+       {
+       case PO_RING_FIRE:
+           if (!pmon_resists_fire(mp->mon_id))
+           {
+               if (!pring->known)
+               {
+                   pring->known = 1;
+               }
+               print_msg("Your ring burns ");
+               print_mon_name(mon, 1);
+               print_msg("!\n");
+               damage += (damage + 1) / 2 + dice(2, 4);
+           }
+           break;
+       case PO_RING_VAMPIRE:
+           if (!pmon_is_undead(mp->mon_id))
+           {
+               if (!pring->known)
+               {
+                   pring->known = 1;
+               }
+               print_msg("Your ring drains ");
+               print_mon_name(mon, 1);
+               print_msg("!\n");
+               damage += (damage + 3) / 4 + dice(2, 4);
+               healing = (damage + 5) / 6;
+               heal_u(healing, 0, 1);
+           }
+           break;
+       case PO_RING_FROST:
+           if (!pmon_resists_cold(mp->mon_id))
+           {
+               if (!pring->known)
+               {
+                   pring->known = 1;
+               }
+               print_msg("Your ring freezes ");
+               print_mon_name(mon, 1);
+               print_msg("!\n");
+               damage += (damage + 2) / 3 + dice(1, 6);
+           }
+       }
+    }
+    print_msg("You do %d damage.\n", damage);
+    damage_mon(mon, damage, 1);
+    if (u.weapon != -1)
+    {
+       damage_obj(u.weapon);
+    }
+    return 1;  /* Hit. */
+}
+
+int ushootm(int sy, int sx)
+{
+    /* Propagate a missile in direction (sy,sx). Attack first target in
+     * LOF. */
+    int tohit;
+    int range;
+    int y, x;
+    int done = 0;
+    struct mon *mptr;
+    struct obj *wep;
+    struct permobj *pwep;
+    int damage;
+    wep = objects + u.weapon;
+    pwep = permobjs + wep->obj_id;
+    damage = one_die(pwep->power);
+    y = u.y + sy;
+    x = u.x + sx;
+    range = 1;
+    for ( ; !done; (y += sy), (x += sx))
+    {
+       if (mapmonster[y][x] != -1)
+       {
+           done = 1;
+           mptr = monsters + mapmonster[y][x];
+           tohit = zero_die(u.agility + u.level - range);
+           if (range == 1)
+           {
+               /* Shooting at point-blank is tricky */
+               tohit = (tohit + 1) / 2;
+           }
+           if (tohit >= mptr->defence)
+           {
+               if (mon_visible(mapmonster[y][x]))
+               {
+                   print_msg("You hit ");
+                   print_mon_name(mapmonster[y][x], 1);
+                   print_msg(".\n");
+                   print_msg("You do %d damage.\n", damage);
+               }
+               damage_mon(mapmonster[y][x], damage, 1);
+               return 1;
+           }
+           else
+           {
+               print_msg("You miss ");
+               print_mon_name(mapmonster[y][x], 1);
+               print_msg(".\n");
+               return 0;
+           }
+       }
+       else if ((terrain[y][x] == WALL) || (terrain[y][x] == DOOR))
+       {
+           print_msg("Your %s hits the %s.\n", (wep->obj_id == PO_BOW) ? "arrow" : "bolt", (terrain[y][x] == WALL) ? "wall" : "door");
+           return 0;
+       }
+    }
+    return 0;
+}
+
+int mhitu(int mon, enum damtyp dtype)
+{
+    int tohit;
+    int damage;
+    int unaffected;
+    struct mon *mptr = monsters + mon;
+    tohit = zero_die(mptr->mtohit + 5);
+    if (tohit < u.defence)
+    {
+       /* Note: Yes, all attacks can damage your armour. Deal. */
+       if ((u.armour != -1) && (tohit > agility_modifier()))
+       {
+           /* Monster hit your armour. */
+           print_msg("Your armour deflects ");
+           print_mon_name(mon, 1);
+           print_msg("'s blow.\n");
+           damage_obj(u.armour);
+       }
+       else
+       {
+           print_mon_name(mon, 3);
+           print_msg(" misses you.\n");
+       }
+       return 0;
+    }
+    damage = one_die(mptr->mdam);
+    unaffected = player_resists_dtype(dtype);
+    print_mon_name(mon, 3);
+    print_msg(" hits you.\n");
+    if (u.armourmelt && (!zero_die(3)))
+    {
+       /* If you're subject to armourmelt, it is decreed that one
+        * blow in three hits your dust-weak armour and rips a chunk
+        * out of it. */
+       damage_obj(u.armour);
+    }
+test_unaffected:
+    if (unaffected)
+    {
+       switch (dtype)
+       {
+       case DT_PHYS:
+           print_msg("Can't happen: player resisting physical damage\n");
+           unaffected = 0;
+           /* Turn off the player's resistance, because they're
+            * not supposed to have it! */
+           u.resistances[DT_PHYS] = 0;
+           goto test_unaffected;
+       case DT_FIRE:
+           print_msg("The flames seem pleasantly warm.\n");
+           if (unaffected & RESIST_RING)
+           {
+               print_msg("Your ring flashes red.\n");
+               permobjs[PO_RING_FIRE].known = 1;
+           }
+           break;
+       case DT_COLD:
+           print_msg("Its touch seems pleasantly cool.\n");
+           if (unaffected & RESIST_RING)
+           {
+               print_msg("Your ring flashes blue.\n");
+               permobjs[PO_RING_FROST].known = 1;
+           }
+           break;
+       case DT_NECRO:
+           print_msg("Its touch makes you feel no deader.\n");
+           if (objects[u.ring].obj_id == PO_RING_VAMPIRE)
+           {
+               permobjs[PO_RING_VAMPIRE].known = 1;
+               print_msg("Your ring shrieks.\n");
+           }
+           break;
+       default:
+           print_msg("Can't happen: bogus damage type.\n");
+           break;
+       }
+    }
+    else
+    {
+       switch (dtype)
+       {
+       default:
+       case DT_PHYS:
+           break;
+       case DT_FIRE:
+           print_msg("You are engulfed in flames.\n");
+           break;
+       case DT_COLD:
+           print_msg("You are covered in frost.\n");
+           break;
+       case DT_NECRO:
+           print_msg("You feel your life force slipping away.\n");
+           break;
+       }
+       print_msg("You take %d damage.\n", damage);
+       if ((mptr->mon_id == PM_VAMPIRE) && !player_resists_dtype(DT_NECRO))
+       {
+           heal_mon(mon, damage * 2 / 5, 1);
+       } else if ((tohit - u.defence >= 5) && (mptr->mon_id == PM_SNAKE))
+       {
+           drain_body(1, "snake venom", 0);
+       }
+       damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+       display_update();
+    }
+    return 1;
+}
+
+int mshootu(int mon)
+{
+    struct mon *mptr;
+    struct mon *bystander;
+    int y;
+    int x;
+    int dy;
+    int dx;
+    int sy, sx;
+    int done;
+    int unaffected = 0;
+    int tohit;
+    int damage;
+    int evasion;
+    int defence;
+    enum damtyp dtype;
+    mptr = monsters + mon;
+    y = mptr->y;
+    x = mptr->x;
+    /* dy, dx == trajectory of missile */
+    dy = u.y - y;
+    dx = u.x - x;
+    sy = (dy > 0) ? 1 : ((dy < 0) ? -1 : 0);
+    sx = (dx > 0) ? 1 : ((dx < 0) ? -1 : 0);
+    /* Don't get the bonus that applies to melee attacks. */
+    tohit = zero_die(mptr->rtohit);
+    print_mon_name(mon, 3);
+    dtype = permons[mptr->mon_id].rdtyp;
+    if (dtype == DT_PHYS)
+    {
+       print_msg(" %s at you!\n", permons[mptr->mon_id].shootverb);
+    }
+    else
+    {
+       print_msg(" %s %s at you!\n", permons[mptr->mon_id].shootverb, damtype_names[dtype]);
+    }
+    if ((dtype == DT_NECRO) || (dtype == DT_ELEC))
+    {
+       /* Use agility-based defence for necromantic blasts and lightning
+        * bolts */
+       evasion = u.agility * 100;
+       if (u.armour != -1)
+       {
+           evasion -= (u.agility * evasion_penalty(u.armour));
+       }
+       defence = evasion / 200;
+    }
+    else
+    {
+       defence = u.defence;
+    }
+    /* Move projectile one square before looking for targets. */
+    for ((done = 0), (y = mptr->y + sy), (x = mptr->x + sx);
+        !done;
+        (y += sy), (x += sx))
+    {
+       if ((terrain[y][x] == WALL) || (terrain[y][x] == DOOR))
+       {
+           done = 1;
+       }
+       if ((y == u.y) && (x == u.x))
+       {
+           if (tohit >= defence)
+           {
+               done = 1;
+               print_msg("It hits you!\n");
+               unaffected = player_resists_dtype(dtype);
+               if (unaffected)
+               {
+                   /* For now, resistant armours are always known, so
+                    * we only need to check for identification of rings. */
+                   if (unaffected & RESIST_RING)
+                   {
+                       switch (dtype)
+                       {
+                       case DT_COLD:
+                           if (objects[u.ring].obj_id == PO_RING_FROST)
+                           {
+                               permobjs[PO_RING_FROST].known = 1;
+                               print_msg("Your ring flashes blue.\n");
+                           }
+                           break;
+                       case DT_FIRE:
+                           if (objects[u.ring].obj_id == PO_RING_FIRE)
+                           {
+                               permobjs[PO_RING_FIRE].known = 1;
+                               print_msg("Your ring flashes red.\n");
+                           }
+                           break;
+                       case DT_NECRO:
+                           if (objects[u.ring].obj_id == PO_RING_VAMPIRE)
+                           {
+                               permobjs[PO_RING_VAMPIRE].known = 1;
+                               print_msg("Your ring shrieks.\n");
+                           }
+                           break;
+                       default:
+                           break;
+                       }
+                   }
+               }
+               if (!unaffected)
+               {
+                   damage = one_die(mptr->rdam);
+                   print_msg("You take %d damage.\n", damage);
+                   damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+               }
+               display_update();
+               return 1;
+           }
+           else
+           {
+               print_msg("It misses you.\n");
+           }
+       }
+       else if (mapmonster[y][x] != -1)
+       {
+           done = 1;
+           bystander = monsters + mapmonster[y][x];
+           switch (dtype)
+           {
+           case DT_COLD:
+               if (pmon_resists_cold(bystander->mon_id))
+               {
+                   unaffected = 1;
+               }
+               else
+               {
+                   unaffected = 0;
+               }
+               break;
+           case DT_FIRE:
+               if (pmon_resists_fire(bystander->mon_id))
+               {
+                   unaffected = 1;
+               }
+               else
+               {
+                   unaffected = 0;
+               }
+               break;
+           case DT_NECRO:
+               if (pmon_is_undead(bystander->mon_id))
+               {
+                   unaffected = 1;
+               }
+               else
+               {
+                   unaffected = 0;
+               }
+               break;
+           default:
+               unaffected = 0;
+               break;
+           }
+           if (tohit >= bystander->defence)
+           {
+               damage = one_die(mptr->rdam);
+               damage_mon(mapmonster[y][x], dtype, 0);
+           }
+       }
+    }
+    return 0;
+}
+
+/* combat.c */
diff --git a/combat.h b/combat.h
new file mode 100644 (file)
index 0000000..45e41f9
--- /dev/null
+++ b/combat.h
@@ -0,0 +1,47 @@
+/* combat.h
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef COMBAT_H
+#define COMBAT_H
+
+#ifndef DUNBASH_H
+#include "dunbash.h"
+#endif
+
+#include "monsters.h"
+
+#define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5))
+/* XXX combat.c data and funcs */
+extern int player_attack(int dy, int dx);
+extern int mhitu(int mon, enum damtyp dtyp);
+extern int uhitm(int mon);
+extern int mshootu(int mon);
+extern int ushootm(int sy, int sx);
+
+#endif
+
+/* combat.h */
+
diff --git a/display.c b/display.c
new file mode 100644 (file)
index 0000000..188d5e3
--- /dev/null
+++ b/display.c
@@ -0,0 +1,701 @@
+/* display.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define DISPLAY_C
+#include "dunbash.h"
+#include "monsters.h"
+#include <curses.h>
+#include <stdio.h>
+#include <panel.h>
+#include <stdarg.h>
+#include <string.h>
+
+WINDOW *status_window;
+WINDOW *world_window;
+WINDOW *message_window;
+PANEL *status_panel;
+PANEL *world_panel;
+PANEL *message_panel;
+
+int wall_colour;
+int you_colour;
+int status_updated;
+int map_updated;
+int show_terrain;
+int hard_redraw;
+
+chtype colour_attrs[15] =
+{
+    0,
+    COLOR_PAIR(DBCLR_D_GREY) | A_BOLD,
+    COLOR_PAIR(DBCLR_RED),
+    COLOR_PAIR(DBCLR_BLUE),
+    COLOR_PAIR(DBCLR_GREEN),
+    COLOR_PAIR(DBCLR_PURPLE),
+    COLOR_PAIR(DBCLR_BROWN),
+    COLOR_PAIR(DBCLR_CYAN),
+    A_BOLD,
+    COLOR_PAIR(DBCLR_RED) | A_BOLD,
+    COLOR_PAIR(DBCLR_BLUE) | A_BOLD,
+    COLOR_PAIR(DBCLR_GREEN) | A_BOLD,
+    COLOR_PAIR(DBCLR_PURPLE) | A_BOLD,
+    COLOR_PAIR(DBCLR_BROWN) | A_BOLD,
+    COLOR_PAIR(DBCLR_CYAN) | A_BOLD
+};
+
+#define DISP_HEIGHT 21
+#define DISP_WIDTH 21
+
+chtype back_buffer[DUN_HEIGHT][DUN_WIDTH];
+chtype front_buffer[DISP_HEIGHT][DISP_WIDTH];
+
+/* Prototypes for static funcs */
+static chtype object_char(int object_id);
+static chtype monster_char(int monster_id);
+static chtype terrain_char(enum terrain_num terrain_type);
+static void draw_status_line(void);
+static void draw_world(void);
+
+/* Static funcs */
+static void draw_status_line(void)
+{
+    mvwprintw(status_window, 0, 0, "%-16.16s", u.name);
+    mvwprintw(status_window, 0, 17, "HP: %03d/%03d", u.hpcur, u.hpmax);
+    mvwprintw(status_window, 0, 30, "XL: %d", u.level);
+    mvwprintw(status_window, 0, 47, "Body: %02d/%02d", u.body - u.bdam, u.body);
+    mvwprintw(status_window, 0, 62, "Gold: %d", u.gold);
+    mvwprintw(status_window, 1, 0, "Defence: %02d", u.defence);
+    mvwprintw(status_window, 1, 15, "Food: %06d", u.food);
+    mvwprintw(status_window, 1, 30, "Depth: %d", depth);
+    mvwprintw(status_window, 1, 62, "XP: %d", u.experience);
+    mvwprintw(status_window, 1, 44, "Agility: %02d/%02d", u.agility - u.adam, u.agility);
+}
+
+static chtype terrain_char(enum terrain_num terrain_type)
+{
+    switch (terrain_type)
+    {
+    case STAIRS:
+       return '>';
+    case FLOOR:
+       return '.';
+    case WALL:
+       return '#' | colour_attrs[wall_colour];
+    case DOOR:
+       return '+';
+    default:
+       return '*';
+    }
+}
+
+static chtype monster_char(int monster_id)
+{
+    return (permons[monster_id].sym) | colour_attrs[permons[monster_id].colour];
+}
+
+static chtype object_char(int object_id)
+{
+    return permobjs[object_id].sym;
+}
+
+void touch_back_buffer(void)
+{
+    int y;
+    int x;
+    for (y = 0; y < DUN_HEIGHT; y++)
+    {
+       for (x = 0; x < DUN_WIDTH; x++)
+       {
+           newsym(y, x);
+       }
+    }
+    map_updated = 1;
+    hard_redraw = 1;
+}
+
+void newsym(int y, int x)
+{
+    chtype ch;
+
+    ch = back_buffer[y][x];
+    if ((y == u.y) && (x == u.x))
+    {
+       back_buffer[y][x] = '@' | colour_attrs[you_colour];
+    }
+    else if (!show_terrain && (mapmonster[y][x] != -1) && mon_visible(mapmonster[y][x]))
+    {
+       back_buffer[y][x] = monster_char(monsters[mapmonster[y][x]].mon_id);
+    }
+    else if (mapflags[y][x] & MAPFLAG_EXPLORED)
+    {
+       if (!show_terrain && (mapobject[y][x] != -1))
+       {
+           back_buffer[y][x] = object_char(objects[mapobject[y][x]].obj_id);
+       }
+       else
+       {
+           back_buffer[y][x] = terrain_char(terrain[y][x]);
+       }
+    }
+    else
+    {
+       back_buffer[y][x] = ' ';
+    }
+    if (ch != back_buffer[y][x])
+    {
+       map_updated = 1;
+    }
+}
+
+static void draw_world(void)
+{
+    int i;
+    int j;
+    int x;
+    int y;
+
+    for (i = 0; i < 21; i++)
+    {
+       y = u.y + i - 10;
+       for (j = 0; j < 21; j++)
+       {
+           x = u.x + j - 10;
+           if ((y < 0) || (x < 0) ||
+               (y >= DUN_HEIGHT) || (x >= DUN_WIDTH))
+           {
+               if ((front_buffer[i][j] != ' ') || hard_redraw)
+               {
+                   mvwaddch(world_window, i, j, ' ');
+               }
+               front_buffer[i][j] = ' ';
+           }
+           else if (hard_redraw || (front_buffer[i][j] != back_buffer[y][x]))
+           {
+               mvwaddch(world_window, i, j, back_buffer[y][x]);
+               front_buffer[i][j] = back_buffer[y][x];
+           }
+       }
+    }
+}
+
+/* extern funcs */
+
+void press_enter(void)
+{
+    int ch;
+    print_msg("Press RETURN or SPACE to continue\n");
+    while (1)
+    {
+       ch = wgetch(message_window);
+       if ((ch == ' ') || (ch == '\n') || (ch == '\r'))
+       {
+           break;
+       }
+    }
+}
+
+void display_update(void)
+{
+    if (status_updated)
+    {
+       status_updated = 0;
+       draw_status_line();
+    }
+    if (map_updated)
+    {
+       map_updated = 0;
+       draw_world();
+    }
+    update_panels();
+    doupdate();
+}
+
+int display_init(void)
+{
+    initscr();
+    noecho();
+    cbreak();
+    start_color();
+    init_pair(DBCLR_BROWN, COLOR_YELLOW, COLOR_BLACK);
+    init_pair(DBCLR_RED, COLOR_RED, COLOR_BLACK);
+    init_pair(DBCLR_GREEN, COLOR_GREEN, COLOR_BLACK);
+    init_pair(DBCLR_BLUE, COLOR_BLUE, COLOR_BLACK);
+    init_pair(DBCLR_D_GREY, COLOR_BLACK, COLOR_BLACK);
+    init_pair(DBCLR_PURPLE, COLOR_MAGENTA, COLOR_BLACK);
+    init_pair(DBCLR_CYAN, COLOR_CYAN, COLOR_BLACK);
+    wall_colour = DBCLR_BROWN;
+    you_colour = DBCLR_WHITE;
+    /* OK. We want a 21x21 viewport (player at centre), a 21x58 message
+     * window, and a 2x80 status line. */
+    status_window = newwin(2, 80, 22, 0);
+    status_panel = new_panel(status_window);
+    world_window = newwin(21, 21, 0, 0);
+    world_panel = new_panel(world_window);
+    message_window = newwin(21, 58, 0, 22);
+    message_panel = new_panel(message_window);
+    wclear(status_window);
+    wclear(world_window);
+    wclear(message_window);
+    scrollok(status_window, FALSE);
+    scrollok(world_window, FALSE);
+    scrollok(message_window, TRUE);
+    idcok(status_window, FALSE);
+    idcok(world_window, FALSE);
+    idcok(message_window, FALSE);
+    mvwprintw(world_window, 6, 5, "  Martin's");
+    mvwprintw(world_window, 7, 5, "Dungeon Bash");
+    mvwprintw(world_window, 9, 5, "Version %d.%d", MAJVERS, MINVERS);
+    wmove(message_window, 0, 0);
+    map_updated = FALSE;
+    status_updated = FALSE;
+    update_panels();
+    doupdate();
+    return 0;
+}
+
+int read_input(char *buffer, int length)
+{
+    echo();
+    display_update();
+    buffer[0] = '\0';
+    wgetnstr(message_window, buffer, length);
+    noecho();
+    return strlen(buffer);
+}
+
+void print_msg(const char *fmt, ...)
+{
+    va_list ap;
+    /* For now, assume (1) that the player will never be so inundated
+     * with messages that it's dangerous to let them just fly past (2)
+     * that messages will be of sane length and nicely formatted. THIS
+     * IS VERY BAD CODING PRACTICE! */
+    /* Note that every message forces a call to display_update().
+     * Events that cause changes to the map or the player should flag
+     * the change before calling printmsg. */
+    va_start(ap, fmt);
+    vw_printw(message_window, fmt, ap);
+    va_end(ap);
+    display_update();
+}
+
+void show_discoveries(void)
+{
+    int i, j;
+    print_msg("You recognise the following items:\n");
+    for (i = 0, j = 1; i < PO_REAL_COUNT; i++)
+    {
+       if (permobjs[i].known)
+       {
+           print_msg("%s\n", permobjs[i].name);
+           j++;
+       }
+       if (j == 19)
+       {
+           press_enter();
+           j = 0;
+       }
+    }
+}
+
+void print_inv(enum poclass_num filter)
+{
+    int i;
+    for (i = 0; i < 19; i++)
+    {
+       if ((u.inventory[i] != -1) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)))
+       {
+           print_msg("%c) ", 'a' + i);
+           print_obj_name(u.inventory[i]);
+           if (u.ring == u.inventory[i])
+           {
+               print_msg(" (on finger)");
+           }
+           else if (u.weapon == u.inventory[i])
+           {
+               print_msg(" (in hand)");
+           }
+           else if (u.armour == u.inventory[i])
+           {
+               print_msg(" (being worn)");
+           }
+           print_msg("\n");
+       }
+    }
+}
+
+int inv_select(enum poclass_num filter, const char *action, int accept_blank)
+{
+    int selection;
+    int ch;
+    int i;
+    int items = 0;
+    for (i = 0; i < 19; i++)
+    {
+       if ((u.inventory[i] != -1) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)))
+       {
+           items++;
+       }
+    }
+    if (items == 0)
+    {
+       print_msg("You have nothing to %s.\n", action);
+       return -1;
+    }
+    print_msg("Items available to %s\n", action);
+    print_inv(filter);
+    if (accept_blank)
+    {
+       print_msg("-) no item\n");
+    }
+    print_msg("[ESC or SPACE to cancel]\n");
+tryagain:
+    print_msg("What do you want to %s? ", action);
+    ch = wgetch(message_window);
+    switch (ch)
+    {
+    case '-':
+       if (accept_blank)
+       {
+           print_msg("\n");
+           return -2;
+       }
+    case 'x':
+    case '\x1b':
+    case ' ':
+       print_msg("\nNever mind.\n");
+       return -1;
+    case 'a':
+    case 'b':
+    case 'c':
+    case 'd':
+    case 'e':
+    case 'f':
+    case 'g':
+    case 'h':
+    case 'i':
+    case 'j':
+    case 'k':
+    case 'l':
+    case 'm':
+    case 'n':
+    case 'o':
+    case 'p':
+    case 'q':
+    case 'r':
+    case 's':
+       /* I am assuming that we're in a place where the character
+        * set is a strict superset of ASCII. IF we're not, the
+        * following code may break. */
+       print_msg("\n");
+       selection = ch - 'a';
+       if ((u.inventory[selection] != -1) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].obj_id].poclass == filter)))
+       {
+           return selection;
+       }
+       /* Fall through */
+    default:
+       print_msg("\nBad selection\n");
+       goto tryagain;
+    }
+}
+
+int select_dir(int *psy, int *psx)
+{
+    int ch;
+    int done = 0;
+    print_msg("Select a direction with movement keys.\n[ESC or space to cancel].\n");
+    while (!done)
+    {
+       ch = wgetch(message_window);
+       switch (ch)
+       {
+       case 'h':
+       case '4':
+           *psx = -1;
+           *psy = 0;
+           done = 1;
+           break;
+       case 'j':
+       case '2':
+           *psx = 0;
+           *psy = 1;
+           done = 1;
+           break;
+       case 'k':
+       case '8':
+           *psx = 0;
+           *psy = -1;
+           done = 1;
+           break;
+       case 'l':
+       case '6':
+           *psx = 1;
+           *psy = 0;
+           done = 1;
+           break;
+       case 'y':
+       case '7':
+           *psx = -1;
+           *psy = -1;
+           done = 1;
+           break;
+       case 'u':
+       case '9':
+           *psx = 1;
+           *psy = -1;
+           done = 1;
+           break;
+       case 'b':
+       case '1':
+           *psx = -1;
+           *psy = 1;
+           done = 1;
+           break;
+       case 'n':
+       case '3':
+           *psx = 1;
+           *psy = 1;
+           done = 1;
+           break;
+       case '\x1b':
+       case ' ':
+           return -1;  /* cancelled. */
+       default:
+           print_msg("Bad direction (use movement keys).\n");
+           print_msg("[Press ESC or space to cancel.]\n");
+           break;
+       }
+    }
+    return 0;
+}
+
+enum game_cmd get_command(void)
+{
+    int ch;
+    int done = 0;
+    while (!done)
+    {
+       ch = wgetch(message_window);
+       switch (ch)
+       {
+       case 'a':
+           return ATTACK;
+       case '0':
+       case ',':
+       case 'g':
+           return GET_ITEM;
+       case 'd':
+           return DROP_ITEM;
+       case 'D':
+           return DUMP_CHARA;
+       case 'S':
+           return SAVE_GAME;
+       case 'X':
+           return QUIT;
+       case 'i':
+           return SHOW_INVENTORY;
+       case 'I':
+           return INSPECT_ITEM;
+       case ';':
+           return EXAMINE_MONSTER;
+       case '#':
+           return SHOW_TERRAIN;
+       case '\\':
+           return SHOW_DISCOVERIES;
+       case '\x12':
+           return RNG_TEST;
+       case '4':
+       case 'h':
+           return MOVE_WEST;
+       case '2':
+       case 'j':
+           return MOVE_SOUTH;
+       case '8':
+       case 'k':
+           return MOVE_NORTH;
+       case '6':
+       case 'l':
+           return MOVE_EAST;
+       case '7':
+       case 'y':
+           return MOVE_NW;
+       case '9':
+       case 'u':
+           return MOVE_NE;
+       case '1':
+       case 'b':
+           return MOVE_SW;
+       case '3':
+       case 'n':
+           return MOVE_SE;
+       case 'q':
+           return QUAFF_POTION;
+       case 'r':
+           return READ_SCROLL;
+       case 'w':
+           return WIELD_WEAPON;
+       case 'W':
+           return WEAR_ARMOUR;
+       case 'T':
+           return TAKE_OFF_ARMOUR;
+       case 'P':
+           return PUT_ON_RING;
+       case 'R':
+           return REMOVE_RING;
+       case '?':
+           return GIVE_HELP;
+       case '>':
+           return GO_DOWN_STAIRS;
+       case 'e':
+           return EAT_FOOD;
+       case '5':
+       case '.':
+           return STAND_STILL;
+       case '\x04':
+           return WIZARD_DESCEND;
+       case '\x05':
+           return WIZARD_LEVELUP;
+       }
+    }
+    return 0;
+}
+
+int display_shutdown(void)
+{
+    display_update();
+    press_enter();
+    clear();
+    refresh();
+    endwin();
+    return 0;
+}
+
+void pressanykey(void)
+{
+    print_msg("Press any key to continue.\n");
+    wgetch(message_window);
+}
+
+int getYN(const char *msg)
+{
+    int ch;
+    print_msg("%s", msg);
+    print_msg("Press capital Y to confirm, any other key to cancel\n");
+    ch = wgetch(message_window);
+    if (ch == 'Y')
+    {
+       return 1;
+    }
+    return 0;
+}
+
+int getyn(const char *msg)
+{
+    int ch;
+    print_msg("%s", msg);
+    while (1)
+    {
+       ch = wgetch(message_window);
+       switch (ch)
+       {
+       case 'y':
+       case 'Y':
+           return 1;
+       case 'n':
+       case 'N':
+           return 0;
+       case '\x1b':
+       case ' ':
+           return -1;
+       default:
+           print_msg("Invalid response. Press y or n (ESC or space to cancel)\n");
+       }
+    }
+}
+
+void print_help(void)
+{
+    print_msg("MOVEMENT\n");
+    print_msg("y  k  u  7  8  9\n");
+    print_msg(" \\ | /    \\ | / \n");
+    print_msg("  \\|/      \\|/  \n");
+    print_msg("h--*--l  4--*--6\n");
+    print_msg("  /|\\      /|\\  \n");
+    print_msg(" / | \\    / | \\ \n");
+    print_msg("b  j  n  1  2  3\n");
+    print_msg("Attack monsters in melee by bumping into them.\n");
+    print_msg("Doors do not have to be opened before you go through.\n");
+    print_msg("Turn on NUM LOCK to use the numeric keypad for movement.\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("ACTIONS\n");
+    print_msg("a   make an attack (used to fire bows)\n");
+    print_msg("P   put on a ring\n");
+    print_msg("R   remove a ring\n");
+    print_msg("W   wear armour\n");
+    print_msg("T   take off armour\n");
+    print_msg("r   read a scroll\n");
+    print_msg("w   wield a weapon\n");
+    print_msg("q   quaff a potion\n");
+    print_msg("g   pick up an item (also 0 or comma)\n");
+    print_msg("d   drop an item\n");
+    print_msg("e   eat something edible\n");
+    print_msg(">   go down stairs\n");
+    print_msg("5   do nothing (wait until next action)\n");
+    print_msg(".   do nothing (wait until next action)\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("OTHER COMMANDS\n");
+    print_msg("S   save and exit\n");
+    print_msg("X   quit without saving\n");
+    print_msg("i   print your inventory\n");
+    print_msg("I   examine an item you are carrying\n");
+    print_msg("#   show underlying terrain of occupied squares\n");
+    print_msg("\\   list all recognised items\n");
+    print_msg("D   dump your character's details to <name>.dump\n");
+    print_msg("?   print this message\n");
+    print_msg("\nPress any key to continue...\n");
+    wgetch(message_window);
+    print_msg("SYMBOLS\n");
+    print_msg("@   you\n");
+    print_msg(".   floor\n");
+    print_msg(">   stairs down\n");
+    print_msg("#   wall\n");
+    print_msg("+   a door\n");
+    print_msg(")   a weapon\n");
+    print_msg("(   a missile weapon\n");
+    print_msg("[   a suit of armour\n");
+    print_msg("=   a ring\n");
+    print_msg("?   a scroll\n");
+    print_msg("!   a potion\n");
+    print_msg("%%   some food\n");
+    print_msg("&   a demon\n");
+    print_msg("\nMost other monsters are shown as letters.\n");
+    print_msg("\nThis is all the help you get. Good luck!\n");
+}
+
+/* display.c */
diff --git a/dunbash.h b/dunbash.h
new file mode 100644 (file)
index 0000000..6663bcb
--- /dev/null
+++ b/dunbash.h
@@ -0,0 +1,424 @@
+/* dunbash.h
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DUNBASH_H
+#define DUNBASH_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+/* change WIZARD_MODE to 1 if you want the wizard mode commands. */
+#define WIZARD_MODE 0
+
+#define is_vowel(ch) (((ch) == 'a') || ((ch) == 'e') || ((ch) == 'i') || ((ch) == 'o') || ((ch) == 'u'))
+
+/* XXX ENUMERATED TYPES XXX */
+
+/* XXX enum damtyp - types of damage. */
+enum damtyp {
+    DT_PHYS = 0, DT_COLD, DT_FIRE, DT_NECRO, DT_ELEC, DT_POISON, DT_COUNT
+};
+
+/* XXX enum game_cmd - player actions. */
+enum game_cmd {
+    DROP_ITEM, SAVE_GAME, SHOW_INVENTORY, MOVE_WEST, MOVE_SOUTH,
+    MOVE_NORTH, MOVE_EAST, MOVE_NW, MOVE_NE, MOVE_SW, MOVE_SE,
+    QUAFF_POTION, READ_SCROLL, WIELD_WEAPON, WEAR_ARMOUR,
+    TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING, GIVE_HELP,
+    ATTACK, GET_ITEM, QUIT, GO_DOWN_STAIRS, STAND_STILL, EAT_FOOD,
+    DUMP_CHARA, INSPECT_ITEM, EXAMINE_MONSTER, RNG_TEST, SHOW_TERRAIN,
+    SHOW_DISCOVERIES, WIZARD_LEVELUP, WIZARD_DESCEND
+};
+
+/* XXX enum terrain_num */
+/* To start with, only four types of terrain: Wall, Floor, Door, Stairs. */
+enum terrain_num {
+    WALL = 0, FLOOR = 1, DOOR = 2, STAIRS = 3
+};
+
+/* XXX enum death */
+/* Sadly, there are not yet 52 kinds of way to die, only four: killed by
+ * an arbitrary string, killed by a monster, drained of body by an arbitrary
+ * string, and drained of agility by an arbitrary string. */
+enum death {
+    DEATH_KILLED, DEATH_KILLED_MON, DEATH_BODY, DEATH_AGILITY
+};
+
+/* XXX enum poclass_num */
+/* Categories of permanent object. */
+enum poclass_num {
+    POCLASS_NONE = 0, POCLASS_WEAPON = 1, POCLASS_POTION = 2,
+    POCLASS_SCROLL = 3, POCLASS_ARMOUR = 4, POCLASS_RING = 5,
+    POCLASS_FOOD = 6
+};
+
+#define RESIST_MASK_TEMPORARY  0x0000FFFFu
+#define RESIST_MASK_PERM_EQUIP 0xFFFF0000u
+#define RESIST_RING    0x00010000u
+#define RESIST_ARMOUR  0x00020000u
+/* XXX STRUCTURES XXX */
+
+/* XXX struct player */
+struct player {
+    char name[17];     /* including '\0' the fencepost. */
+    int y;             /* y-coord */
+    int x;             /* x-coord */
+    int body;          /* determines mace to-hit, melee damage, max 99 */
+    int bdam;          /* current level of temporary body drain. */
+    int agility;       /* determines sword, dagger, missile to-hit, max 99 */
+    int adam;          /* current level of temporary agility drain. */
+    int hpmax;         /* Max hit points; max of 999. */
+    int hpcur;         /* Current hit points; <= 0 is dead. */
+    int food;          /* Current nutrition in body; < 0 is hungry. */
+    int experience;    /* Experience points earned. */
+    int defence;       /* To-hit target number for monsters */
+    int protection;    /* Temporary protection from cursing */
+    int leadfoot;      /* Feet-of-lead curse */
+    int withering;     /* Vile withering curse */
+    int armourmelt;    /* Armour-like-dust curse */
+    int speed;
+    unsigned int resistances[DT_COUNT];        /* Resistances to damage types. */
+    int level; /* Each level gets you +1 body, +1 agility, +1 random
+                  point, and +(10+body/10) hit points */
+    int gold;
+    int inventory[19]; /* 19 inventory slots, leaving room for a prompt */
+    int weapon;                /* For now, you can only wield one weapon. */
+    int armour;                /* For now, you can only wear one item of armour. */
+    int ring;          /* For now, you can only wear one magic ring. */
+};
+
+#define DBCLR_L_GREY   0
+#define DBCLR_D_GREY   1
+#define DBCLR_RED      2
+#define DBCLR_BLUE     3
+#define DBCLR_GREEN    4
+#define DBCLR_PURPLE   5
+#define DBCLR_BROWN    6
+#define DBCLR_CYAN     7
+#define DBCLR_WHITE    8
+#define DBCLR_L_RED    9
+#define DBCLR_L_BLUE   10
+#define DBCLR_L_GREEN  11
+#define DBCLR_L_PURPLE 12
+#define DBCLR_YELLOW   13
+#define DBCLR_L_CYAN   14
+
+/* XXX struct permon */
+#define PM_NEWT 0
+#define PM_RAT (PM_NEWT + 1)
+#define PM_WOLF (PM_RAT + 1)
+#define PM_SNAKE (PM_WOLF + 1)
+#define PM_THUG (PM_SNAKE + 1)
+#define PM_GOON (PM_THUG + 1)
+#define PM_HUNTER (PM_GOON + 1)
+#define PM_DUELLIST (PM_HUNTER + 1)
+#define PM_WARLORD (PM_DUELLIST + 1)
+#define PM_WIZARD (PM_WARLORD + 1)
+#define PM_ARCHMAGE (PM_WIZARD + 1)
+#define PM_GOBLIN (PM_ARCHMAGE + 1)
+#define PM_BAD_ELF (PM_GOBLIN + 1)
+#define PM_TROLL (PM_BAD_ELF + 1)
+#define PM_GIANT (PM_TROLL + 1)
+#define PM_GIANT_JARL (PM_GIANT + 1)
+#define PM_ZOMBIE (PM_GIANT_JARL + 1)
+#define PM_WRAITH (PM_ZOMBIE + 1)
+#define PM_LICH (PM_WRAITH + 1)
+#define PM_VAMPIRE (PM_LICH + 1)
+#define PM_MASTER_LICH (PM_VAMPIRE + 1)
+#define PM_DEMON (PM_MASTER_LICH + 1)
+#define PM_DEFILER (PM_DEMON + 1)
+#define PM_ICE_MONSTER (PM_DEFILER + 1)
+#define PM_CENTAUR (PM_ICE_MONSTER + 1)
+#define PM_DRAGON (PM_CENTAUR + 1)
+#define PM_REAL_COUNT ((PM_DRAGON) + 1)
+
+#define PMF_RESIST_FIRE        0x00000001
+#define PMF_RESIST_COLD        0x00000002
+#define PMF_RESIST_ELEC        0x00000004
+#define PMF_UNDEAD     0x00010000
+#define PMF_DEMONIC    0x00020000
+#define PMF_MAGICIAN   0x00040000
+#define PMF_ARCHER     0x00080000
+#define PMF_SMART      0x00100000
+#define PMF_STUPID     0x00200000
+
+struct permon {
+    const char name[48];
+    const char plural[48];
+    char sym;
+    int colour;
+    int rarity;                /* Chance in 100 of being thrown back and regen'd. */
+    int power;         /* Used to determine OOD rating. */
+    /* All OOD-improved stats cap out at base + (power * base) */
+    int hp;            /* Improved by OOD rating at 1:1. */
+    int mtohit;                /* Improved by OOD rating at 1:3. */
+    int rtohit;                /* Improved by OOD rating at 1:3. */
+    int mdam;          /* Improved by OOD rating at 1:5. */
+    int rdam;          /* Improved by OOD rating at 1:5. */
+    enum damtyp rdtyp; /* type of damage used by ranged attack. */
+    const char shootverb[48];  /* shooting verb e.g. "fires an arrow", "breathes". */
+    int defence;       /* Improved by OOD rating at 1:3. */
+    int exp;           /* Unaffected by OOD rating. */
+    int speed;         /* 0 = slow; 1 = normal; 2 = quick */
+    int flags;         /* resistances, AI settings, etc. */
+};
+
+/* XXX struct permobj */
+#define PO_DAGGER 0
+#define PO_FIRST_WEAPON PO_DAGGER
+#define PO_LONG_SWORD (PO_FIRST_WEAPON + 1)
+#define PO_MACE (PO_FIRST_WEAPON + 2)
+#define PO_RUNESWORD (PO_FIRST_WEAPON + 3)
+#define PO_BOW (PO_FIRST_WEAPON + 4)
+#define PO_CROSSBOW (PO_FIRST_WEAPON + 5)
+#define PO_LAST_WEAPON PO_CROSSBOW
+#define PO_POT_HEAL (PO_LAST_WEAPON + 1)
+#define PO_FIRST_POTION PO_POT_HEAL
+#define PO_POT_POISON (PO_FIRST_POTION + 1)
+#define PO_POT_BODY (PO_FIRST_POTION + 2)
+#define PO_POT_AGILITY (PO_FIRST_POTION + 3)
+#define PO_POT_WEAKNESS (PO_FIRST_POTION + 4)
+#define PO_POT_RESTORATION (PO_FIRST_POTION + 5)
+#define PO_LAST_POTION PO_POT_RESTORATION
+#define PO_SCR_TELEPORT (PO_LAST_POTION + 1)
+#define PO_FIRST_SCROLL PO_SCR_TELEPORT
+#define PO_SCR_FIRE (PO_FIRST_SCROLL + 1)
+#define PO_SCR_MONSTERS (PO_FIRST_SCROLL + 2)
+#define PO_SCR_IDENTIFY (PO_FIRST_SCROLL + 3)
+#define PO_SCR_AGGRAVATE (PO_FIRST_SCROLL + 4)
+#define PO_SCR_PROTECTION (PO_FIRST_SCROLL + 5)
+#define PO_LAST_SCROLL PO_SCR_PROTECTION
+#define PO_LEATHER_ARMOUR (PO_LAST_SCROLL + 1)
+#define PO_FIRST_ARMOUR PO_LEATHER_ARMOUR
+#define PO_CHAINMAIL (PO_FIRST_ARMOUR + 1)
+#define PO_PLATE_ARMOUR (PO_FIRST_ARMOUR + 2)
+#define PO_MAGE_ARMOUR (PO_FIRST_ARMOUR + 3)
+#define PO_ROBE (PO_FIRST_ARMOUR + 4)
+#define PO_ROBE_SWIFTNESS (PO_FIRST_ARMOUR + 5)
+#define PO_ROBE_SHADOWS (PO_FIRST_ARMOUR + 6)
+#define PO_DRAGON_ARMOUR (PO_FIRST_ARMOUR + 7)
+#define PO_METEOR_ARMOUR (PO_FIRST_ARMOUR + 8)
+#define PO_SACRED_MAIL (PO_FIRST_ARMOUR + 9)
+#define PO_LAST_ARMOUR PO_SACRED_MAIL
+#define PO_RING_REGEN (PO_LAST_ARMOUR + 1)
+#define PO_FIRST_RING PO_RING_REGEN
+#define PO_RING_FIRE (PO_FIRST_RING + 1)
+#define PO_RING_WEDDING (PO_FIRST_RING + 2)
+#define PO_RING_VAMPIRE (PO_FIRST_RING + 3)
+#define PO_RING_FROST (PO_FIRST_RING + 4)
+#define PO_RING_DOOM (PO_FIRST_RING + 5)
+#define PO_RING_TELEPORT (PO_FIRST_RING + 6)
+#define PO_LAST_RING PO_RING_TELEPORT
+#define PO_IRON_RATION (PO_LAST_RING + 1)
+#define PO_FIRST_FOOD PO_IRON_RATION
+#define PO_DRIED_FRUIT (PO_FIRST_FOOD + 1)
+#define PO_ELVEN_BREAD (PO_FIRST_FOOD + 2)
+#define PO_LAST_FOOD PO_ELVEN_BREAD
+#define PO_GOLD (PO_LAST_FOOD + 1)
+#define PO_REAL_COUNT ((PO_GOLD) + 1)
+
+struct permobj {
+    const char name[48];
+    const char plural[48];
+    const char description[160];
+    enum poclass_num poclass;
+    int rarity;        /* Chance in 100 of being thrown away and regen'd. */
+    int sym;
+    int power; /* AC for armour; damage for weapons; colour/title for
+                * scrolls and potions and rings and such. */
+    int used;  /* Set to 1 for valid entries. */
+    int known; /* Set to 1 for items recognised at startup. Updated
+                * during play when items identified. */
+    int depth; /* If greater than 1, this item cannot be given out
+                * by get_random_pobj() before the specified depth. */
+};
+
+/* XXX struct mon */
+struct mon {
+    int mon_id;
+    int y;
+    int x;
+    int ai_lasty;      /* AI's belief about your last position. -1 == lost you. */
+    int ai_lastx;      /* AI's belief about your last position. -1 == lost you. */
+    int used;
+    int hpmax; /* Improved by OOD rating at 1:1. */
+    int hpcur; /* <= 0 is dead. */
+    int mtohit;        /* Improved by OOD rating at 1:3. */
+    int rtohit;        /* Improved by OOD rating at 1:3. */
+    int defence;       /* Improved by OOD rating at 1:3. */
+    int mdam;  /* Improved by OOD rating at 1:5. */
+    int rdam;  /* Improved by OOD rating at 1:5. */
+    int awake;
+};
+
+/* XXX struct obj */
+struct obj {
+    int obj_id;
+    int quan;
+    int with_you;      /* Preserved when item DB is reaped on level change. */
+    int y;
+    int x;
+    int used;  /* Entry is occupied. */
+    int durability;    /* Weapons and armour degrade with use. */
+};
+
+#define OBJ_MAX_DUR 100
+/* XXX Object/monster arrays */
+#define NUM_OF_PERMOBJS 300
+extern struct permobj permobjs[NUM_OF_PERMOBJS];
+
+/* Treasure generation limit plus inventory limit means this should be
+ * enough.  When I evolve to the "fully-fledged" version, I'll use a Tatham
+ * tree instead. */
+extern struct mon monsters[100];
+
+/* XXX display.c data and funcs */
+extern int read_input(char *buffer, int length);
+extern void print_msg(const char *fmt, ...);
+extern void print_help(void);
+extern int display_init(void);
+extern void display_update(void);
+extern int display_shutdown(void);
+extern void newsym(int y, int x);
+extern void touch_back_buffer(void);
+extern void print_inv(enum poclass_num filter);
+extern int inv_select(enum poclass_num filter, const char *action, int accept_blank);
+extern enum game_cmd get_command(void);
+extern int select_dir(int *psy, int *psx);
+extern int getYN(const char *msg);
+extern int getyn(const char *msg);
+extern void press_enter(void);
+extern void pressanykey(void);
+extern void show_discoveries(void);
+
+/* "I've changed things that need to be redisplayed" flags. */
+extern int hard_redraw;
+extern int status_updated;
+extern int map_updated;
+/* "Show the player the terrain only" flag. */
+extern int show_terrain;
+
+/* XXX main.c data and funcs */
+extern int exclusive_flat(int lower, int upper); /* l+1 ... u-1 */
+extern int inclusive_flat(int lower, int upper); /* l ... u */
+extern int one_die(int sides); /* 1..n */
+extern int dice(int count, int sides);
+extern int zero_die(int sides);        /* 0..n-1 */
+extern int do_command(enum game_cmd command);
+extern unsigned int convert_range(int dy, int dx);
+extern int game_finished;
+extern int game_tick;
+extern int wizard_mode;
+
+/* XXX map.c data and funcs*/
+extern void leave_level(void);
+extern void make_new_level(void);
+extern void build_level(void);
+extern void populate_level(void);
+extern void inject_player(void);
+extern int get_room_x(int room);
+extern int get_room_y(int room);
+extern void room_reset(void);
+
+/* For now, I can't be arsed with a mapcell structure */
+#define DUN_WIDTH 42
+#define DUN_HEIGHT 42
+#define ROOM_HT_DELTA 4
+#define ROOM_WD_DELTA 4
+#define MAX_ROOMS 9
+
+extern int mapobject[DUN_HEIGHT][DUN_WIDTH];
+extern int mapmonster[DUN_HEIGHT][DUN_WIDTH];
+extern enum terrain_num terrain[DUN_HEIGHT][DUN_WIDTH];
+#define MAPFLAG_EXPLORED 0x00000001
+extern int mapflags[DUN_HEIGHT][DUN_WIDTH];
+extern int roomnums[DUN_HEIGHT][DUN_WIDTH];
+extern int roombounds[MAX_ROOMS][4];
+extern int roomlinkage[MAX_ROOMS][MAX_ROOMS];
+extern int depth;
+
+/* XXX misc.c data and funcs */
+extern const char *damtype_names[DT_COUNT];
+
+/* XXX objects.c data and funcs */
+extern void flavours_init(void);
+extern void fprint_obj_name(FILE *fp, int obj);
+extern void print_obj_name(int obj);
+extern void describe_object(int obj);
+extern int create_obj(int po_idx, int quantity, int with_you, int y, int x);
+extern int drop_obj(int inv_idx);
+extern int create_obj_class(enum poclass_num pocl, int quantity, int with_you, int y, int x);
+extern int create_obj_random(int y, int x);
+extern struct obj objects[100];        /* SHould be enough. */
+extern int read_scroll(int obj);
+extern int quaff_potion(int obj);
+extern int eat_food(int obj);
+extern void attempt_pickup(void);
+extern int po_is_stackable(int po);
+extern void damage_obj(int obj);
+extern int evasion_penalty(int obj);
+
+/* XXX permons.c data */
+#define NUM_OF_PERMONS 300
+/* I don't intend to start with anything like this many monsters, but,
+ * it gives me headroom. */
+extern struct permon permons[NUM_OF_PERMONS];
+
+/* XXX rng.c data and funcs */
+#define RNG_MAX 0xFFFFFFFFu
+extern unsigned int rng_state[5];
+extern unsigned int saved_state[5];
+extern unsigned int rng(void);
+extern void rng_init(void);
+
+/* XXX vector.c data and funcs */
+extern void compute_directions(int y1, int x1, int y2, int x2, int *pdy, int *pdx, int *psy, int *psx, int *pmelee, int *pcardinal);
+
+/* XXX u.c data and funcs */
+extern void u_init(void);
+extern void write_char_dump(void);
+extern int do_death(enum death d, const char *what);
+extern void heal_u(int amount, int boost, int loud);
+extern int damage_u(int amount, enum death d, const char *what);
+extern int gain_body(int amount, int loud);
+extern int gain_agility(int amount, int loud);
+extern int drain_body(int amount, const char *what, int permanent);
+extern int drain_agility(int amount, const char *what, int permanent);
+extern void gain_experience(int amount);
+extern int lev_threshold(int level);
+extern int move_player(int dy, int dx);
+extern int reloc_player(int y, int x);
+extern void recalc_defence(void);
+extern int teleport_u(void);
+extern void update_player(void);
+extern int player_resists_dtype(enum damtyp dtype);
+
+extern struct player u;
+
+#define inyourroom(my, mx) ((roomnums[u.y][u.x] != -1) && (roomnums[u.y][u.x] == roomnums[(my)][(mx)]))
+#endif
+
+/* dunbash.h */
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..298960f
--- /dev/null
+++ b/main.c
@@ -0,0 +1,629 @@
+/* main.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+#include "combat.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+
+void save_game(void);
+static void load_game(void);
+static void new_game(void);
+static void rebuild_mapobjs(void);
+static void rebuild_mapmons(void);
+static void main_loop(void);
+int game_finished;
+int game_tick;
+int wizard_mode = WIZARD_MODE;
+
+static void rebuild_mapmons(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+       if (monsters[i].used)
+       {
+           mapmonster[monsters[i].y][monsters[i].x] = i;
+       }
+    }
+}
+
+static void rebuild_mapobjs(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+       if (objects[i].used && !objects[i].with_you)
+       {
+           mapobject[objects[i].y][objects[i].x] = i;
+       }
+    }
+}
+
+unsigned int convert_range(int dy, int dx)
+{
+    int ady, adx;
+    ady = dy > 0 ? dy : -dy;
+    adx = dx > 0 ? dx : -dx;
+    if (ady > adx)
+    {
+       return ady;
+    }
+    return adx;
+}
+
+void save_game(void)
+{
+    FILE *fp;
+    fp = fopen("dunbash.sav", "wb");
+    /* Write out the snapshot we took of the RNG before generating the
+     * current level. */
+    fwrite(saved_state, sizeof saved_state, 1, fp);
+    /* Write out the map flags; if we decide to have at-generation
+     * flags as well as at-exploration flags, the at-generation flags
+     * will be stored in a separate array. */
+    fwrite(mapflags, DUN_HEIGHT, DUN_WIDTH * sizeof (int), fp);
+    /* Write out the permanent object data. This is hideously
+     * wasteful (and guarantees savefile breakage at version-up), but
+     * it makes handling flavours much easier... */
+    /* FIX: We no longer write out the permons data, since none of it
+     * changes at the moment. */
+    fwrite(permobjs, 100, sizeof (struct permobj), fp);
+    /* Write out the dynamic monster/object arrays. */
+    fwrite(monsters, 100, sizeof (struct mon), fp);
+    fwrite(objects, 100, sizeof (struct obj), fp);
+    /* Write out the depth */
+    fwrite(&depth, 1, sizeof depth, fp);
+    /* Write out the player. */
+    fwrite(&u, 1, sizeof u, fp);
+    /* Write out the tick number */
+    fwrite(&game_tick, 1, sizeof game_tick, fp);
+    /* Clean up */
+    fflush(fp);
+    fclose(fp);
+    /* Compress! */
+    system("gzip dunbash.sav");
+    game_finished = 1;
+    return;
+}
+
+void load_game(void)
+{
+    FILE *fp;
+    system("gunzip dunbash.sav");
+    fp = fopen("dunbash.sav", "rb");
+    fread(rng_state, sizeof rng_state, 1, fp);
+    room_reset();
+    build_level();
+    fread(mapflags, DUN_HEIGHT, DUN_WIDTH * sizeof (int), fp);
+    fread(permobjs, 100, sizeof (struct permobj), fp);
+    fread(monsters, 100, sizeof (struct mon), fp);
+    fread(objects, 100, sizeof (struct obj), fp);
+    rebuild_mapobjs();
+    rebuild_mapmons();
+    fread(&depth, 1, sizeof depth, fp);
+    fread(&u, 1, sizeof u, fp);
+    fread(&game_tick, 1, sizeof game_tick, fp);
+    fclose(fp);
+    unlink("dunbash.sav");
+    touch_back_buffer();
+    status_updated = 1;
+    map_updated = 1;
+    hard_redraw = 1;
+    print_msg("Game loaded.\n");
+}
+
+int exclusive_flat(int lower, int upper)
+{
+    return lower + one_die(upper - lower - 1);
+}
+
+int inclusive_flat(int lower, int upper)
+{
+    return lower + zero_die(upper - lower + 1);
+}
+
+int one_die(int sides)
+{
+    int rval;
+    if (sides < 2)
+    {
+       return 1;
+    }
+    rval = 1 + (rng() / ((RNG_MAX / sides) + 1));
+    return rval;
+}
+
+int zero_die(int sides)
+{
+    int rval;
+    if (sides < 2)
+    {
+       return 0;
+    }
+    rval = rng() / ((RNG_MAX / sides) + 1);
+    return rval;
+}
+
+int dice(int count, int sides)
+{
+    int total = 0;
+    for ( ; count > 0; count--)
+    {
+       total += one_die(sides);
+    }
+    return total;
+}
+
+void new_game(void)
+{
+    rng_init();
+    u_init();
+    flavours_init();
+    make_new_level();
+    status_updated = 1;
+    map_updated = 1;
+    hard_redraw = 1;
+    print_msg("Initialisation complete.\n");
+}
+
+int do_command(enum game_cmd cmd)
+{
+    int i;
+    int j;
+    int sy, sx;
+    switch (cmd)
+    {
+    case MOVE_NORTH:
+       return move_player(-1, 0);
+    case MOVE_SOUTH:
+       return move_player(1, 0);
+    case MOVE_EAST:
+       return move_player(0, 1);
+    case MOVE_WEST:
+       return move_player(0, -1);
+    case MOVE_NW:
+       return move_player(-1, -1);
+    case MOVE_NE:
+       return move_player(-1, 1);
+    case MOVE_SE:
+       return move_player(1, 1);
+    case MOVE_SW:
+       return move_player(1, -1);
+
+    case ATTACK:
+       i = select_dir(&sy, &sx);
+       if (i != -1)
+       {
+           return player_attack(sy, sx);
+       }
+       return 0;
+
+    case GET_ITEM:
+       if (mapobject[u.y][u.x] != -1)
+       {
+           attempt_pickup();
+           return 1;
+       }
+       else
+       {
+           print_msg("Nothing to get.\n");
+           return 0;
+       }
+
+    case WIELD_WEAPON:
+       j = 0;
+       i = inv_select(POCLASS_WEAPON, "wield", 1);
+       if (i == -2)
+       {
+           u.weapon = -1;
+           print_msg("Weapon unwielded.\n");
+       }
+       else if (i >= 0)
+       {
+           u.weapon = u.inventory[i];
+           j = 1;
+           print_msg("Wielding ");
+           print_obj_name(u.weapon);
+           print_msg(".\n");
+       }
+       return j;
+
+    case WEAR_ARMOUR:
+       if (u.armour >= 0)
+       {
+           print_msg("You are already wearing armour.\n");
+           return 0;
+       }
+       i = inv_select(POCLASS_ARMOUR, "wear", 0);
+       if (i >= 0)
+       {
+           u.armour = u.inventory[i];
+           permobjs[objects[u.armour].obj_id].known = 1;
+           recalc_defence();
+           print_msg("Wearing ");
+           print_obj_name(u.armour);
+           print_msg(".\n");
+           return 1;
+       }
+       return 0;
+    case TAKE_OFF_ARMOUR:
+       if (u.armour != -1)
+       {
+           u.armour = -1;
+           recalc_defence();
+           print_msg("You take off your armour.\n");
+           return 1;
+       }
+       else
+       {
+           print_msg("YOu aren't wearing any armour.\n");
+           return 0;
+       }
+
+    case GIVE_HELP:
+       print_help();
+       return 0;
+
+    case READ_SCROLL:
+       i = inv_select(POCLASS_SCROLL, "read", 0);
+       if (i >= 0)
+       {
+           j = read_scroll(u.inventory[i]);
+           if (j)
+           {
+               u.inventory[i] = -1;
+           }
+           return 1;
+       }
+       return 0;
+
+    case GO_DOWN_STAIRS:
+       if (terrain[u.y][u.x] == STAIRS)
+       {
+           leave_level();
+           make_new_level();
+       }
+       else
+       {
+           print_msg("There are no stairs here.\n");
+       }
+       return 0;
+
+    case STAND_STILL:
+       return 1;
+
+    case EAT_FOOD:
+       i = inv_select(POCLASS_FOOD, "eat", 0);
+       if (i >= 0)
+       {
+           j = eat_food(u.inventory[i]);
+           if (j == -1)
+           {
+               u.inventory[i] = -1;
+           }
+            return 1;
+       }
+       return 0;
+
+    case QUAFF_POTION:
+       i = inv_select(POCLASS_POTION, "quaff", 0);
+       if (i >= 0)
+       {
+           j = quaff_potion(u.inventory[i]);
+           if (j)
+           {
+               u.inventory[i] = -1;
+           }
+           return 1;
+       }
+       return 0;
+
+    case REMOVE_RING:
+       if (u.ring == -1)
+       {
+           print_msg("You have no ring to remove!\n");
+           return 0;
+       }
+       if (objects[u.ring].obj_id == PO_RING_DOOM)
+       {
+           int dmg;
+           print_msg("You remove your ring.\n");
+           print_msg("It exacts vengeance!\n");
+           drain_body(one_die(4), "a ring of doom", 1);
+           drain_agility(one_die(4), "a ring of doom", 1);
+           dmg = one_die(20);
+           damage_u(dmg, DEATH_KILLED, "a ring of doom");
+           u.hpmax -= dmg;
+           status_updated = 1;
+           u.ring = -1;
+           display_update();
+       }
+       else if (objects[u.ring].obj_id == PO_RING_TELEPORT)
+       {
+           i = zero_die(u.level);
+           if (i < 4)
+           {
+               print_msg("You lack the willpower to remove it.\n");
+           }
+           else
+           {
+               print_msg("You manage to pull the ring off.\n");
+               u.ring = -1;
+           }
+       }
+       else
+       {
+           print_msg("You remove your ring.\n");
+           u.ring = -1;
+       }
+       return 1;
+    case PUT_ON_RING:
+       if (u.ring != -1)
+       {
+           print_msg("You are already wearing a ring.\n");
+           return 0;
+       }
+       i = inv_select(POCLASS_RING, "put on", 0);
+       if (i >= 0)
+       {
+           u.ring = u.inventory[i];
+           print_msg("You put on ");
+           print_obj_name(u.ring);
+           print_msg(".\n");
+           return 1;
+       }
+       return 0;
+    case INSPECT_ITEM:
+       i = inv_select(POCLASS_NONE, "inspect", 0);
+       if ((i >= 0) && (u.inventory[i] != -1))
+       {
+           describe_object(u.inventory[i]);
+       }
+       return 0;
+    case EXAMINE_MONSTER:
+       print_msg("Monster examination not implemented yet.\n");
+       return 0;
+    case SHOW_DISCOVERIES:
+       show_discoveries();
+       return 0;
+    case SHOW_TERRAIN:
+       show_terrain = 1;
+       map_updated = 1;
+       display_update();
+       print_msg("Display of monsters and objects suppressed.\n");
+       press_enter();
+       show_terrain = 0;
+       map_updated = 1;
+       display_update();
+       return 0;
+    case RNG_TEST:
+       {
+           int odds = 0;
+           int evens = 0;
+           for (i = 0; i < 100000; i++)
+           {
+               if (zero_die(2))
+               {
+                   odds++;
+               }
+               else
+               {
+                   evens++;
+               }
+           }
+           print_msg("100k rolls: 0 %d, 1 %d\n", odds, evens);
+       }
+       print_msg("1d2-1: %d %d %d %d %d %d %d %d\n", zero_die(2), zero_die(2), zero_die(2), zero_die(2), zero_die(2), zero_die(2), zero_die(2), zero_die(2));
+       print_msg("1d8-1: %d %d %d %d %d %d %d %d\n", zero_die(8), zero_die(8), zero_die(8), zero_die(8), zero_die(8), zero_die(8), zero_die(8), zero_die(8));
+       print_msg("1d32-1: %d %d %d %d %d %d %d %d\n", zero_die(32), zero_die(32), zero_die(32), zero_die(32), zero_die(32), zero_die(32), zero_die(32), zero_die(32));
+       return 0;
+    case DROP_ITEM:
+       if (mapobject[u.y][u.x] != -1)
+       {
+           print_msg("There is already an item here.\n");
+           return 0;
+       }
+       i = inv_select(POCLASS_NONE, "drop", 0);
+       if (i >= 0)
+       {
+           if ((u.inventory[i] != -1) &&
+               ((u.inventory[i] == u.ring) ||
+                (u.inventory[i] == u.armour)))
+           {
+               print_msg("You cannot drop something you are wearing.\n");
+               return 0;
+           }
+           j = drop_obj(i);
+           if (j == -1)
+           {
+               return 0;
+           }
+           return 1;
+       }
+       return 0;
+    case DUMP_CHARA:
+       write_char_dump();
+       return 0;
+    case SAVE_GAME:
+       game_finished = 1;
+       save_game();
+       return 0;
+    case QUIT:
+       j = getYN("Really quit?\n");
+       if (j > 0)
+       {
+           game_finished = 1;
+       }
+       else
+       {
+           print_msg("Never mind.\n");
+       }
+       return 0;
+    case SHOW_INVENTORY:
+       print_msg("You are carrying:\n");
+       print_inv(POCLASS_NONE);
+       return 0;
+
+    case WIZARD_DESCEND:
+       if (wizard_mode)
+       {
+           leave_level();
+           make_new_level();
+       }
+       else
+       {
+           print_msg("You aren't a wizard.\n");
+       }
+       return 0;
+
+    case WIZARD_LEVELUP:
+       if (wizard_mode)
+       {
+           gain_experience((lev_threshold(u.level) - u.experience) + 1);
+       }
+       else
+       {
+           print_msg("You aren't a wizard.\n");
+       }
+       return 0;
+    }
+    return 0;
+}
+
+void main_loop(void)
+{
+    enum game_cmd cmd;
+    int i;
+    int action_speed;
+    print_msg("Welcome to Martin's Infinite Dungeon.\n");
+    print_msg("Press '?' for help.\n");
+    while (!game_finished)
+    {
+       switch (game_tick % 4)
+       {
+       case 0:
+       case 2:
+           action_speed = 0;
+           break;
+       case 1:
+           action_speed = 1;
+           break;
+       case 3:
+           action_speed = 2;
+           break;
+       }
+       /* Player is always speed 1, for now. */
+       if (action_speed <= u.speed)
+       {
+           i = 0;
+           while (!i)
+           {
+               /* Take commands until the player does
+                * something that uses an action. */
+               cmd = get_command();
+               i = do_command(cmd);
+               if (game_finished)
+               {
+                   break;
+               }
+           }
+           if (game_finished)
+           {
+               break;
+           }
+           /* If you're wearing a ring of doom, zap you. */
+           if (objects[u.ring].obj_id == PO_RING_DOOM)
+           {
+               print_msg("Your ring pulses uncleanly.\n");
+               damage_u(1, DEATH_KILLED, "a ring of doom");
+               display_update();
+               permobjs[PO_RING_DOOM].known = 1;
+           }
+           else if (objects[u.ring].obj_id == PO_RING_TELEPORT)
+           {
+               if (!zero_die(75))
+               {
+                   print_msg("Your ring flares white!\n");
+                   permobjs[PO_RING_TELEPORT].known = 1;
+                   teleport_u();
+               }
+           }
+       }
+       for (i = 0; i < 100; i++)
+       {
+           if (monsters[i].used == 0)
+           {
+               /* Unused monster */
+               continue;
+           }
+           /* Update the monster's status. */
+           update_mon(i);
+           if (action_speed <= permons[monsters[i].mon_id].speed)
+           {
+               mon_acts(i);
+           }
+           if (game_finished)
+           {
+               break;
+           }
+       }
+       if (game_finished)
+       {
+           break;
+       }
+       update_player();
+       game_tick++;
+    }
+}
+
+int main(void)
+{
+    struct stat s;
+    int i;
+    display_init();
+    /* Do we have a saved game? */
+    i = stat("dunbash.sav.gz", &s);
+    if (!i)
+    {
+       /* Yes! */
+       print_msg("Loading...\n");
+       load_game();
+    }
+    else
+    {
+       /* No! */
+       new_game();
+    }
+    main_loop();
+    display_shutdown();
+    return 0;
+}
+
+/* main.c */
diff --git a/map.c b/map.c
new file mode 100644 (file)
index 0000000..8ec4905
--- /dev/null
+++ b/map.c
@@ -0,0 +1,506 @@
+/* map.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+#include "monsters.h"
+
+#include <string.h>
+int mapobject[DUN_HEIGHT][DUN_WIDTH];
+int mapmonster[DUN_HEIGHT][DUN_WIDTH];
+enum terrain_num terrain[DUN_HEIGHT][DUN_WIDTH];
+int mapflags[DUN_HEIGHT][DUN_WIDTH];
+int roomnums[DUN_HEIGHT][DUN_WIDTH];
+int depth = 1;
+int roomlinkage[MAX_ROOMS][MAX_ROOMS];
+int roombounds[MAX_ROOMS][4];
+int stairs_room = -1;
+int zoo_room = -1;
+static int segsused[MAX_ROOMS];
+
+static int get_levgen_mon_floor(int *y, int *x);
+static void add_random_room(int yseg, int xseg);
+static void link_rooms(int r1, int r2);
+static void put_stairs(void);
+static void generate_zoo(void);
+
+void room_reset(void)
+{
+    int i;
+    memset((void *) &(roombounds[0][0]), 0, sizeof roombounds);
+    memset((void *) &(roomlinkage[0][0]), 0, sizeof roomlinkage);
+    memset((void *) segsused, 0, sizeof segsused);
+    zoo_room = -1;
+    stairs_room = -1;
+    for (i = 0; i < MAX_ROOMS; i++)
+    {
+       roomlinkage[i][i] = 3;
+    }
+    memset(mapobject, -1, sizeof mapobject);
+    memset(mapmonster, -1, sizeof mapmonster);
+    memset(terrain, 0, sizeof terrain);
+    memset(mapflags, 0, sizeof mapflags);
+    memset(roomnums, -1, sizeof roomnums);
+}
+
+static void add_random_room(int yseg, int xseg)
+{
+    int roomidx = (yseg * 3) + xseg;
+    int ycen, xcen;
+    int y1, y2, x1, x2;
+    int y, x;
+    ycen = (DUN_HEIGHT - 2) / 6 + yseg * ((DUN_HEIGHT - 2) / 3);
+    xcen = (DUN_WIDTH - 2) / 6 + xseg * ((DUN_WIDTH - 2) / 3);
+    y1 = ycen - one_die(ROOM_HT_DELTA) - 1;
+    x1 = xcen - one_die(ROOM_WD_DELTA) - 1;
+    y2 = ycen + one_die(ROOM_HT_DELTA) + 1;
+    x2 = xcen + one_die(ROOM_WD_DELTA) + 1;
+    for (y = y1 + 1; y < y2; y++)
+    {
+       for (x = x1 + 1; x < x2; x++)
+       {
+           terrain[y][x] = FLOOR;
+           roomnums[y][x] = roomidx;
+       }
+    }
+    for (y = y1; y <= y2; y++)
+    {
+       roomnums[y][x1] = roomidx;
+       roomnums[y][x2] = roomidx;
+    }
+    for (x = x1; x <= x2; x++)
+    {
+       roomnums[y1][x] = roomidx;
+       roomnums[y2][x] = roomidx;
+    }
+    roombounds[roomidx][0] = y1;
+    roombounds[roomidx][1] = y2;
+    roombounds[roomidx][2] = x1;
+    roombounds[roomidx][3] = x2;
+    segsused[yseg * 3 + xseg] = 1;
+}
+
+static void link_rooms(int r1, int r2)
+{
+    int i;
+    int y, x;
+    int y1, y2, y3, y4;
+    int x1, x2, x3, x4;
+    /* First rule: CORRIDORS ARE STRAIGHT! This makes the AI easier. */
+    /* Second rule: Don't pass in bogus numbers to this function; it
+     * has no error-checking of its own. */
+    /* Update the linkage matrix. */
+    roomlinkage[r1][r2] = 1;
+    roomlinkage[r2][r1] = 1;
+    for (i = 0; i < MAX_ROOMS; i++)
+    {
+       if ((i == r1) || (i == r2))
+       {
+           continue;
+       }
+       if ((roomlinkage[r1][i] > 0) && !roomlinkage[r2][i])
+       {
+           roomlinkage[r2][i] = 2;
+           roomlinkage[i][r2] = 2;
+       }
+       if ((roomlinkage[r2][i] > 0) && !roomlinkage[r1][i])
+       {
+           roomlinkage[r1][i] = 2;
+           roomlinkage[i][r1] = 2;
+       }
+    }
+    y1 = roombounds[r1][0];
+    y2 = roombounds[r2][0];
+    y3 = roombounds[r1][1];
+    y4 = roombounds[r2][1];
+    x1 = roombounds[r1][2];
+    x2 = roombounds[r2][2];
+    x3 = roombounds[r1][3];
+    x4 = roombounds[r2][3];
+    /* Now generate the corridor. */
+    if ((r1 % 3) == (r2 % 3))
+    {
+       /* same xseg; north-south linkage */
+       if (x4 < x3)
+       {
+           x3 = x4;
+       }
+       if (x2 > x1)
+       {
+           x1 = x2;
+       }
+       x = exclusive_flat(x1, x3);
+       if (y3 < y2)
+       {
+           /* go south from r1 */
+           terrain[y3][x] = DOOR;
+           terrain[y2][x] = DOOR;
+           for (y = y3 + 1; y < y2; y++)
+           {
+               terrain[y][x] = FLOOR;
+           }
+       }
+       else if (y4 < y1)
+       {
+           /* go south from r2 */
+           terrain[y4][x] = DOOR;
+           terrain[y1][x] = DOOR;
+           for (y = y4 + 1; y < y1; y++)
+           {
+               terrain[y][x] = FLOOR;
+           }
+       }
+    }
+    else
+    {
+       /* same yseg; east-west linkage */
+       if (y4 < y3)
+       {
+           y3 = y4;
+       }
+       if (y2 > y1)
+       {
+           y1 = y2;
+       }
+       y = exclusive_flat(y1, y3);
+       if (x3 < x2)
+       {
+           /* go south from r1 */
+           terrain[y][x3] = DOOR;
+           terrain[y][x2] = DOOR;
+           for (x = x3 + 1; x < x2; x++)
+           {
+               terrain[y][x] = FLOOR;
+           }
+       }
+       else if (x4 < x1)
+       {
+           /* go south from r2 */
+           terrain[y][x4] = DOOR;
+           terrain[y][x1] = DOOR;
+           for (x = x4 + 1; x < x1; x++)
+           {
+               terrain[y][x] = FLOOR;
+           }
+       }
+    }
+}
+
+void leave_level(void)
+{
+    int i;
+    memset(mapobject, -1, sizeof mapobject);
+    memset(mapmonster, -1, sizeof mapmonster);
+    memset(terrain, 0, sizeof terrain);
+    memset(mapflags, 0, sizeof mapflags);
+    memset(roomnums, -1, sizeof roomnums);
+    for (i = 0; i < 100; i++)
+    {
+       /* Throw away each monster */
+       monsters[i].used = 0;
+       /* and each object not carried by the player */
+       if (!objects[i].with_you)
+       {
+           objects[i].used = 0;
+       }
+    }
+    depth++;
+    status_updated = 1;
+    map_updated = 1;
+}
+
+void make_new_level(void)
+{
+    room_reset();
+    build_level();
+    populate_level();
+    inject_player();
+    touch_back_buffer();
+    display_update();
+}
+
+void put_stairs(void)
+{
+    int y;
+    int x;
+    stairs_room = zero_die(MAX_ROOMS);
+    y = exclusive_flat(roombounds[stairs_room][0], roombounds[stairs_room][1]);
+    x = exclusive_flat(roombounds[stairs_room][2], roombounds[stairs_room][3]);
+    terrain[y][x] = STAIRS;
+}
+
+int edge_rooms[4] = { 1, 3, 5, 7 };
+int corners[4][2] = { { 0, 2 }, { 0, 6 }, { 2, 8 }, { 6, 8 } };
+void build_level(void)
+{
+    int i;
+    /* Snapshot the running RNG state, so that we can rebuild the map from
+     * the saved RNG state at game reload. */
+    memcpy(saved_state, rng_state, sizeof saved_state);
+    /* Add rooms */
+    for (i = 0; i < MAX_ROOMS; i++)
+    {
+       add_random_room(i / 3, i % 3);
+    }
+    /* Add corridors */
+    /* Link the centre room to an edge room. */
+    link_rooms(4, edge_rooms[zero_die(4)]);
+    /* And to another; if we're already linked, don't bother. */
+    i = zero_die(4);
+    if (roomlinkage[4][edge_rooms[i]] == 0)
+    {
+       link_rooms(4, edge_rooms[i]);
+    }
+    /* Link each edge room to one of its corner rooms. */
+    for (i = 0; i < 4; i++)
+    {
+       link_rooms(edge_rooms[i], corners[i][zero_die(2)]);
+    }
+    /* At this point, 1-2 edge rooms and their attached corner rooms
+     * have linkage to the centre. */
+    /* Link each edge room to its unlinked corner if it is not 2-linked
+     * to the centre. */
+    for (i = 0; i < 4; i++)
+    {
+       if (!roomlinkage[4][edge_rooms[i]])
+       {
+           if (roomlinkage[edge_rooms[i]][corners[i][0]])
+           {
+               link_rooms(edge_rooms[i], corners[i][1]);
+           }
+           else
+           {
+               link_rooms(edge_rooms[i], corners[i][0]);
+           }
+       }
+
+    }
+    /* Link each corner room to its unlinked edge if that edge is not
+     * 2-linked to the centre.  If we still haven't got centre
+     * connectivity for the edge room, connect the edge to the centre. */
+    for (i = 0; i < 4; i++)
+    {
+       if (!roomlinkage[4][edge_rooms[i]])
+       {
+           if (!roomlinkage[edge_rooms[i]][corners[i][0]])
+           {
+               link_rooms(edge_rooms[i], corners[i][0]);
+           }
+           if (!roomlinkage[edge_rooms[i]][corners[i][1]])
+           {
+               link_rooms(edge_rooms[i], corners[i][1]);
+           }
+       }
+       if (!roomlinkage[4][edge_rooms[i]])
+       {
+           link_rooms(edge_rooms[i], 4);
+       }
+    }
+    /* Just for safety's sake: Now we know all edges are attached,
+     * make sure all the corners are. (Previously, it was possible
+     * for them not to be. I know, because I met such a level :) */
+    for (i = 3; i > -1; i--)
+    {
+       if (!roomlinkage[4][corners[i][0]])
+       {
+           link_rooms(edge_rooms[i], corners[i][0]);
+       }
+       if (!roomlinkage[4][corners[i][1]])
+       {
+           link_rooms(edge_rooms[i], corners[i][1]);
+       }
+    }
+    /* Add the stairs */
+    put_stairs();
+}
+
+static void generate_zoo(void)
+{
+    int mons;
+    int items;
+    int tries;
+    int index;
+    int y, x;
+    zoo_room = zero_die(MAX_ROOMS);
+    if (zoo_room == stairs_room)
+    {
+       zoo_room = -1;
+       return;
+    }
+    /* A treasure zoo should get nine monsters and nine items. */
+    for (mons = 0; mons < 9; mons++)
+    {
+       for (tries = 0; tries < 200; tries++)
+       {
+           y = exclusive_flat(roombounds[zoo_room][0], roombounds[zoo_room][1]);
+           x = exclusive_flat(roombounds[zoo_room][2], roombounds[zoo_room][3]);
+           if (mapmonster[y][x] == -1)
+           {
+               index = create_mon(-1, y, x);
+               if (index != -1)
+               {
+                   break;
+               }
+           }
+       }
+    }
+    for (items = 0; items < 9; items++)
+    {
+       for (tries = 0; tries < 200; tries++)
+       {
+           y = exclusive_flat(roombounds[zoo_room][0], roombounds[zoo_room][1]);
+           x = exclusive_flat(roombounds[zoo_room][2], roombounds[zoo_room][3]);
+           if (mapobject[y][x] == -1)
+           {
+               index = create_obj(-1, 1, 0, y, x);
+               if (index != -1)
+               {
+                   break;
+               }
+           }
+       }
+    }
+}
+
+
+int get_room_y(int room)
+{
+    return exclusive_flat(roombounds[room][0], roombounds[room][1]);
+}
+
+int get_room_x(int room)
+{
+    return exclusive_flat(roombounds[room][2], roombounds[room][3]);
+}
+
+int get_levgen_mon_floor(int *y, int *x)
+{
+    /* Get a vacant floor cell that isn't in the treasure zoo. */
+    int room_try;
+    int cell_try;
+    int ty, tx;
+    int room;
+    for (room_try = 0; room_try < (MAX_ROOMS * 2); room_try++)
+    {
+       room = zero_die(MAX_ROOMS);
+       if (room == zoo_room)
+       {
+           continue;
+       }
+       for (cell_try = 0; cell_try < 200; cell_try++)
+       {
+           ty = get_room_y(room);
+           tx = get_room_x(room);
+           if ((terrain[ty][tx] != FLOOR) ||
+               (mapmonster[ty][tx] != -1))
+           {
+               ty = -1;
+               tx = -1;
+               continue;
+           }
+           break;
+       }
+       break;
+    }
+    if (ty == -1)
+    {
+       return -1;
+    }
+    *y = ty;
+    *x = tx;
+    return 0;
+}
+
+void populate_level(void)
+{
+    int i;
+    int j;
+    int y, x;
+    int ic;
+    /* Check for a "treasure zoo" */
+    if (!zero_die(10) && (depth > 2))
+    {
+       generate_zoo();
+    }
+    /* Generate some random monsters */
+    for (i = 0; i < 10; i++)
+    {
+       j = get_levgen_mon_floor(&y, &x);
+       if (j == -1)
+       {
+           continue;
+       }
+       create_mon(-1, y, x);
+    }
+    ic = 3 + depth;
+    if (ic > 40)
+    {
+       /* Never create more than 40 items. */
+       ic = 40;
+    }
+    /* Generate some random treasure */
+    for (i = 0; i < ic; i++)
+    {
+       j = get_levgen_mon_floor(&y, &x);
+       if (j == -1)
+       {
+           continue;
+       }
+       create_obj(-1, 1, 0, y, x);
+    }
+}
+
+void inject_player(void)
+{
+    int i;
+    int room_try;
+    int cell_try;
+    for (room_try = 0; room_try < (MAX_ROOMS * 2); room_try++)
+    {
+       i = zero_die(MAX_ROOMS);
+       if (i == zoo_room)
+       {
+           continue;
+       }
+       if (i == stairs_room)
+       {
+           continue;
+       }
+       for (cell_try = 0; cell_try < 200; cell_try++)
+       {
+           u.y = exclusive_flat(roombounds[i][0], roombounds[i][1]);
+           u.x = exclusive_flat(roombounds[i][2], roombounds[i][3]);
+           if (mapmonster[u.y][u.x] != -1)
+           {
+               continue;
+           }
+           break;
+       }
+       break;
+    }
+    reloc_player(u.y, u.x);
+}
+
+/* map.c */
diff --git a/misc.c b/misc.c
new file mode 100644 (file)
index 0000000..710475f
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,38 @@
+/* misc.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+
+const char *damtype_names[DT_COUNT] = {
+       [DT_PHYS] = "physical damage",
+       [DT_FIRE] = "fire",
+       [DT_COLD] = "cold",
+       [DT_ELEC] = "electricity",
+       [DT_NECRO] = "necromantic force",
+       [DT_POISON] = "poison",
+};
+
+/* main.c */
diff --git a/mon2.c b/mon2.c
new file mode 100644 (file)
index 0000000..22eedbf
--- /dev/null
+++ b/mon2.c
@@ -0,0 +1,931 @@
+/* mon2.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* TODO: Convert missile AI to a new-style AI function. */
+#define MON2_C
+#include "dunbash.h"
+#include "bmagic.h"
+#include "monsters.h"
+#include "combat.h"
+
+/* AI map cell descriptor. */
+struct ai_cell {
+    int y, x;
+    int dy, dx;
+    int score;
+};
+
+/* prototypes for AI preference functions. */
+static void get_naive_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x);
+static void get_seeking_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x);
+static void get_drunk_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x);
+static void build_ai_cells(struct ai_cell *cells, int y, int x);
+static int ai_cell_compare(struct ai_cell *cell, int dy, int dx);
+static void get_dodger_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x);
+static void get_chase_prefs(int mon, int *pref_y, int *pref_x);
+
+/* get_drunk_prefs()
+ *
+ * Fills the three-entry preference arrays with three randomly-selected
+ * adjacent squares.
+ */
+
+static void get_drunk_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x)
+{
+    int sy, sx;
+    int tryct;
+    int pref_idx;
+    int idx2;
+    int retry;
+    pref_y[0] = y;
+    pref_y[1] = y;
+    pref_y[2] = y;
+    pref_x[0] = x;
+    pref_x[1] = x;
+    pref_x[2] = x;
+    for (pref_idx = 0; pref_idx < 3; pref_idx++)
+    {
+       for (tryct = 0; tryct < 40; tryct++)
+       {
+           retry = 0;
+           sy = zero_die(3) - 1;
+           sx = zero_die(3) - 1;
+           if (!sy && !sx)
+           {
+               continue;
+           }
+           for (idx2 = 0; idx2 < pref_idx; idx2++)
+           {
+               if ((pref_y[idx2] == y + sy) &&
+                   (pref_x[idx2] == x + sx))
+               {
+                   retry = 1;
+                   break;
+               }
+           }
+           if (retry) 
+           {
+               continue;
+           }
+           pref_y[pref_idx] = y + sy;
+           pref_x[pref_idx] = x + sx;
+           break;
+       }
+    }
+}
+
+/* get_chase_prefs()
+ *
+ * The naive "chase" AI is used by non-stupid non-smart monsters to chase your
+ * last known position. If after moving towards it once they can't see you,
+ * they will give up and revert to "drunk" AI. (Contrast stupid monsters,
+ * who always use "drunk" AI if they can't see you, and smart monsters, who
+ * always use "seeking" AI if they can't see you.)
+ *
+ * This function takes different parameters to the other AI preference
+ * functions because it has to have access to the monster's lasty/lastx
+ * details.
+ */
+
+static void get_chase_prefs(int mon, int *pref_y, int *pref_x)
+{
+    int sy, sx;
+    int ady, adx;
+    int dy, dx;
+    int y, x;
+    y = monsters[mon].y;
+    x = monsters[mon].x;
+    dy = monsters[mon].ai_lasty - y;
+    dx = monsters[mon].ai_lastx - x;
+    if (dy == 0)
+    {
+       sy = 0;
+       ady = 0;
+    }
+    else
+    {
+       sy = dy < 0 ? -1 : 1;
+       ady = dy < 0 ? -dy : dy;
+    }
+    if (dx == 0)
+    {
+       sx = 0;
+       adx = 0;
+    }
+    else
+    {
+       sx = dx < 0 ? -1 : 1;
+       adx = dx < 0 ? -dx : dx;
+    }
+    if (mon_can_pass(mon, y + sy, x + sx))
+    {
+       *pref_y = y + sy;
+       *pref_x = x + sx;
+    }
+    else if (!sy)
+    {
+       /* We're on the horizontal; check the horizontally adjacent
+        * square, then the squares one square north or south in a
+        * random order. */
+       if (zero_die(2))
+       {
+           pref_y[1] = y - 1;
+           pref_y[2] = y + 1;
+       }
+       else
+       {
+           pref_y[1] = y + 1;
+           pref_y[2] = y - 1;
+       }
+       pref_x[1] = x + sx;
+       pref_x[2] = x + sx;
+       if (mon_can_pass(mon, pref_y[1], pref_x[1]))
+       {
+           pref_y[0] = pref_y[1];
+           pref_x[0] = pref_x[1];
+       }
+       else if (mon_can_pass(mon, pref_y[2], pref_x[2]))
+       {
+           pref_y[0] = pref_y[2];
+           pref_x[0] = pref_x[2];
+       }
+       else
+       {
+           pref_y[0] = monsters[mon].y;
+           pref_x[0] = monsters[mon].x;
+       }
+    }
+    else if (!sx)
+    {
+       /* We're on the horizontal; check the horizontally adjacent
+        * square, then the squares one square north or south in a
+        * random order. */
+       if (zero_die(2))
+       {
+           pref_x[1] = x - 1;
+           pref_x[2] = x + 1;
+       }
+       else
+       {
+           pref_x[1] = x + 1;
+           pref_x[2] = x - 1;
+       }
+       pref_y[1] = y + sy;
+       pref_y[2] = y + sy;
+       if (mon_can_pass(mon, pref_y[1], pref_x[1]))
+       {
+           pref_y[0] = pref_y[1];
+           pref_x[0] = pref_x[1];
+       }
+       else if (mon_can_pass(mon, pref_y[2], pref_x[2]))
+       {
+           pref_y[0] = pref_y[2];
+           pref_x[0] = pref_x[2];
+       }
+       else
+       {
+           pref_y[0] = monsters[mon].y;
+           pref_x[0] = monsters[mon].x;
+       }
+    }
+    else
+    {
+       if (zero_die(2))
+       {
+           pref_x[1] = x;
+           pref_y[1] = y + sy;
+           pref_x[2] = x + sx;
+           pref_y[2] = y;
+       }
+       else
+       {
+           pref_x[2] = x;
+           pref_y[2] = y + sy;
+           pref_x[1] = x + sx;
+           pref_y[1] = y;
+       }
+       if (mon_can_pass(mon, pref_y[1], pref_x[1]))
+       {
+           pref_y[0] = pref_y[1];
+           pref_x[0] = pref_x[1];
+       }
+       else if (mon_can_pass(mon, pref_y[2], pref_x[2]))
+       {
+           pref_y[0] = pref_y[2];
+           pref_x[0] = pref_x[2];
+       }
+       else
+       {
+           pref_y[0] = monsters[mon].y;
+           pref_x[0] = monsters[mon].x;
+       }
+    }
+}
+
+/* get_seeking_prefs()
+ *
+ * Does all the work of finding the best (or least-bad) square for a seeking
+ * AI monster to move to.
+ */
+
+static void get_seeking_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x)
+{
+    struct ai_cell ai_cells[8];
+    int i;
+    int ady, adx;
+    int j;
+    int highest_score = -10000;
+    int tryct;
+    int uroom;
+    int mroom;
+    int troom;
+    *pref_y = y;
+    *pref_x = x;
+    ady = dy > 0 ? dy : -dy;
+    adx = dx > 0 ? dx : -dx;
+    build_ai_cells(ai_cells, y, x);
+    uroom = roomnums[u.y][u.x];
+    mroom = roomnums[y][x];
+    for (i = 0; i < 8; i++)
+    {
+       troom = roomnums[ai_cells[i].y][ai_cells[i].x];
+       ai_cells[i].dy = u.y - ai_cells[i].y;
+       ai_cells[i].dx = u.x - ai_cells[i].x;
+       /* Scoring factors:
+        * If player in a room:
+        *   Square in player's room: +10
+        *   Square closer to player: +1
+        *   Square further from player: -1
+        *   Square in room with linkage 1 to player's room: +5
+        * If player in corridor:
+        *   Square closer to player: +1
+        *   Square matches one of player's coords: +1
+        *   Square further from player: -1
+        * Note that now that wraiths are smart and can walk through
+        * walls, the player can only finally evade a wraith by
+        * killing it or leaving the level; fortunately, wraiths 
+        * are slow. (Danenth will feature ethereal beings, in the
+        * form of chthonic spirits, which are *not* slow.)
+        */
+       if (!mon_can_pass(mapmonster[y][x], ai_cells[i].y, ai_cells[i].x))
+       {
+           /* Square impassable to this monster. Set score WAY
+            * out of bounds and continue. */
+           ai_cells[i].score = -10000;
+           continue;
+       }
+       if (uroom != -1)
+       {
+           /* Player in a room */
+           if (troom == uroom)
+           {
+               ai_cells[i].score += 10;
+           }
+           else if ((troom != -1) && (roomlinkage[troom][uroom] == 1))
+           {
+               ai_cells[i].score += 5;
+           }
+           j = ai_cell_compare(ai_cells + i, dy, dx);
+           if (j > 0)
+           {
+               ai_cells[i].score -= 1;
+           }
+           else if (j < 0)
+           {
+               ai_cells[i].score += 1;
+           }
+           if (ai_cells[i].score > highest_score)
+           {
+               highest_score = ai_cells[i].score;
+           }
+       }
+       else
+       {
+           /* Player in a corridor */
+           if ((ai_cells[i].y == u.y) || (ai_cells[i].x == u.x))
+           {
+               ai_cells[i].score += 1;
+           }
+           j = ai_cell_compare(ai_cells + i, dy, dx);
+           if (j > 0)
+           {
+               ai_cells[i].score -= 1;
+           }
+           else if (j < 0)
+           {
+               ai_cells[i].score += 1;
+           }
+           if (ai_cells[i].score > highest_score)
+           {
+               highest_score = ai_cells[i].score;
+           }
+       }
+    }
+    if (highest_score == -10000)
+    {
+       /* No good targets. */
+       return;
+    }
+    for (tryct = 0; tryct < 32; tryct++)
+    {
+       i = zero_die(8);
+       if (ai_cells[i].score == highest_score)
+       {
+           *pref_y = ai_cells[i].y;
+           *pref_x = ai_cells[i].x;
+           break;
+       }
+    }
+    return;
+}
+
+/* get_naive_prefs()
+ *
+ * Fills the three-entry preference arrays with three best choices for closing
+ * with the player - optimal first, then secondaries in random order as #2 and
+ * #3.
+ */
+
+static void get_naive_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x)
+{
+    int sy, sx;
+    int ady, adx;
+    if (dy == 0)
+    {
+       sy = 0;
+       ady = 0;
+    }
+    else
+    {
+       sy = dy < 0 ? -1 : 1;
+       ady = dy < 0 ? -dy : dy;
+    }
+    if (dx == 0)
+    {
+       sx = 0;
+       adx = dx < 0 ? -dx : dx;
+    }
+    else
+    {
+       sx = dx < 0 ? -1 : 1;
+       adx = dx < 0 ? -dx : dx;
+    }
+    if (!sy)
+    {
+       /* We're on the horizontal; check the horizontally adjacent
+        * square, then the squares one square north or south in a
+        * random order. */
+       pref_y[0] = y;
+       if (zero_die(2))
+       {
+           pref_y[1] = y - 1;
+           pref_y[2] = y + 1;
+       }
+       else
+       {
+           pref_y[1] = y + 1;
+           pref_y[2] = y - 1;
+       }
+       pref_x[0] = x + sx;
+       pref_x[1] = x + sx;
+       pref_x[2] = x + sx;
+    }
+    else if (!sx)
+    {
+       pref_x[0] = x;
+       if (zero_die(2))
+       {
+           pref_x[1] = x - 1;
+           pref_x[2] = x + 1;
+       }
+       else
+       {
+           pref_x[1] = x + 1;
+           pref_x[2] = x - 1;
+       }
+       pref_y[0] = y + sy;
+       pref_y[1] = y + sy;
+       pref_y[2] = y + sy;
+    }
+    else
+    {
+       pref_x[0] = x + sx;
+       pref_y[0] = y + sy;
+       if (zero_die(2))
+       {
+           pref_x[1] = x;
+           pref_y[1] = y + sy;
+           pref_x[2] = x + sx;
+           pref_y[2] = y;
+       }
+       else
+       {
+           pref_x[2] = x;
+           pref_y[2] = y + sy;
+           pref_x[1] = x + sx;
+           pref_y[1] = y;
+       }
+    }
+}
+
+/* XXX build_ai_cells()
+ *
+ * Populate array of eight AI cell descriptors.
+ */
+
+static void build_ai_cells(struct ai_cell *cells, int y, int x)
+{
+    cells[0].score = 0;
+    cells[1].score = 0;
+    cells[2].score = 0;
+    cells[3].score = 0;
+    cells[4].score = 0;
+    cells[5].score = 0;
+    cells[6].score = 0;
+    cells[7].score = 0;
+    cells[0].y = y - 1;
+    cells[1].y = y - 1;
+    cells[2].y = y - 1;
+    cells[3].y = y;
+    cells[4].y = y;
+    cells[5].y = y + 1;
+    cells[6].y = y + 1;
+    cells[7].y = y + 1;
+    cells[0].x = x - 1;
+    cells[1].x = x;
+    cells[2].x = x + 1;
+    cells[3].x = x - 1;
+    cells[4].x = x + 1;
+    cells[5].x = x - 1;
+    cells[6].x = x;
+    cells[7].x = x + 1;
+}
+
+/* XXX ai_cell_compare()
+ *
+ * Find relative range of cell compared to monster's current range.
+ */
+static int ai_cell_compare(struct ai_cell *cell, int dy, int dx)
+{
+    /* returns -1 for closer, 0 for same range, +1 for further. */
+    int pointrange = convert_range(dy, dx);
+    int cellrange = convert_range(cell->dy, cell->dx);
+    if (cellrange < pointrange)
+    {
+       return -1;
+    }
+    else if (cellrange > pointrange)
+    {
+       return 1;
+    }
+    return 0;
+}
+
+/* XXX get_dodger_prefs()
+ *
+ * Get preferences for "smart" monsters without ranged attacks.
+ */
+static void get_dodger_prefs(int y, int x, int dy, int dx, int *pref_y, int *pref_x)
+{
+    /* "Dodgers" are smart melee-only monsters. They will try to avoid
+     * the cardinals as they close, and will even flow around other
+     * monsters to try to get to the player. 
+     *
+     * This function does *all* the work of selecting a destination square
+     * for a smart melee-only monster; accordingly, only pref_y[0] and
+     * pref_x[0] get set.
+     */
+    struct ai_cell ai_cells[8];
+    int i;
+    int ady, adx;
+    int j;
+    int highest_score = -10000;
+    int tryct;
+    *pref_y = y;
+    *pref_x = x;
+    ady = dy > 0 ? dy : -dy;
+    adx = dx > 0 ? dx : -dx;
+    build_ai_cells(ai_cells, y, x);
+    /* Build the local dx/dy arrays. */
+    for (i = 0; i < 8; i++)
+    {
+       ai_cells[i].dy = u.y - ai_cells[i].y;
+       ai_cells[i].dx = u.x - ai_cells[i].x;
+       /* Scoring factors:
+        * Square on cardinal: -2.
+        * Square closer to player: +1.
+        * Square further from player: -3.
+        * Square next to player: +10.
+        *
+        * Yes, this monster prizes not opening the range more than
+        * it prizes staying off the cardinal; this is intentional.
+        * It also prizes staying off the cardinal more than actually
+        * closing. When I add more AI state to the monster structure,
+        * this will change.
+        */
+       if (!mon_can_pass(mapmonster[y][x], ai_cells[i].y, ai_cells[i].x))
+       {
+           /* Square impassable. Set score WAY out of bounds
+            * and continue. */
+           ai_cells[i].score = -10000;
+           continue;
+       }
+       /* Cardinality */
+       if ((ai_cells[i].dy == ai_cells[i].dx) || (ai_cells[i].dy == -ai_cells[i].dx) || (ai_cells[i].dy == 0) || (ai_cells[i].dx == 0))
+       {
+           /* Score this square down for being on a cardinal. */
+           ai_cells[i].score -= 2;
+       }
+       j = ai_cell_compare(ai_cells + i, dy, dx);
+       /* Range */
+       if ((ai_cells[i].dy < 2) && (ai_cells[i].dy > -2) && (ai_cells[i].dx < 2) && (ai_cells[i].dx > -2))
+       {
+           /* Score upward a *lot* for being adjacent to player */
+           ai_cells[i].score += 10;
+       }
+       else if (j > 0)
+       {
+           ai_cells[i].score -= 3;
+       }
+       else if (j < 0)
+       {
+           ai_cells[i].score += 1;
+       }
+       if (ai_cells[i].score > highest_score)
+       {
+           highest_score = ai_cells[i].score;
+       }
+    }
+    if (highest_score == -10000)
+    {
+       /* No good targets. */
+       return;
+    }
+    for (tryct = 0; tryct < 32; tryct++)
+    {
+       i = zero_die(8);
+       if (ai_cells[i].score == highest_score)
+       {
+           *pref_y = ai_cells[i].y;
+           *pref_x = ai_cells[i].x;
+           break;
+       }
+    }
+    return;
+}
+
+void select_space(int *py, int *px, int dy, int dx, int selection_mode)
+{
+    int ai_y[3];
+    int ai_x[3];
+    int ady, adx;
+    int y, x;
+    int sy, sx;
+    if (dy == 0)
+    {
+       sy = 0;
+       ady = 0;
+    }
+    else
+    {
+       sy = dy < 0 ? -1 : 1;
+       ady = dy < 0 ? -dy : dy;
+    }
+    if (dx == 0)
+    {
+       sx = 0;
+       adx = dx < 0 ? -dx : dx;
+    }
+    else
+    {
+       sx = dx < 0 ? -1 : 1;
+       adx = dx < 0 ? -dx : dx;
+    }
+    switch (selection_mode)
+    {
+    case 0:
+       /* Simple convergence */
+       get_naive_prefs(*py, *px, dy, dx, ai_y, ai_x);
+       if (mon_can_pass(mapmonster[*py][*px], ai_y[0], ai_x[0]))
+       {
+           y = ai_y[0];
+           x = ai_x[0];
+       }
+       else if (mon_can_pass(mapmonster[*py][*px], ai_y[1], ai_x[1]))
+       {
+           y = ai_y[1];
+           x = ai_x[1];
+       }
+       else if (mon_can_pass(mapmonster[*py][*px], ai_y[2], ai_x[2]))
+       {
+           y = ai_y[2];
+           x = ai_x[2];
+       }
+       else
+       {
+           y = *py;
+           x = *px;
+       }
+       break;
+    case 1:
+       /* Converge to cardinal */
+       if ((dy == dx) || (dy == -dx) || (!dy) || (!dx))
+       {
+           /* On cardinal. Stay there if we can. But close anyway. */
+           x = *px + sx;
+           y = *py + sy;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           x = *px;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           y = *py;
+           x = *px + sx;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+       }
+       else if ((ady == 1) || ((adx > 1) && (ady > adx)))
+       {
+           /* One step in ydir off a NSEW cardinal, or further
+            * off cardinal in y than in x */
+           y = *py + sy;
+           x = *px;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           x = *px + sx;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           y = *py;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+       }
+       else if ((adx == 1) || ((ady > 1) && (adx > ady)))
+       {
+           /* One step off a diagonal cardinal, with adx > ady */
+           x = *px + sx;
+           y = *py;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           y = *py + sy;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+           x = *px;
+           if (mon_can_pass(mapmonster[*py][*px], y, x))
+           {
+               break;
+           }
+       }
+       y = *py;
+       x = *px;
+       break;
+    case 2:
+       get_dodger_prefs(*py, *px, dy, dx, ai_y, ai_x);
+       y = ai_y[0];
+       x = ai_x[0];
+       break;
+    case 3:
+       /* "Drunk" monster i.e. monster moving while it doesn't know
+        * how to find you. */
+       get_drunk_prefs(*py, *px, dy, dx, ai_y, ai_x);
+       if (mon_can_pass(mapmonster[*py][*px], ai_y[0], ai_x[0]))
+       {
+           y = ai_y[0];
+           x = ai_x[0];
+       }
+       else if (mon_can_pass(mapmonster[*py][*px], ai_y[1], ai_x[1]))
+       {
+           y = ai_y[1];
+           x = ai_x[1];
+       }
+       else if (mon_can_pass(mapmonster[*py][*px], ai_y[2], ai_x[2]))
+       {
+           y = ai_y[2];
+           x = ai_x[2];
+       }
+       else
+       {
+           y = *py;
+           x = *px;
+       }
+       break;
+    case 4:
+       /* "Seeking" monster i.e. monster moving while it can't see
+        * you, but thinks it knows where you are. This AI isn't
+        * great, but it'll do for now. */
+       get_seeking_prefs(*py, *px, dy, dx, ai_y, ai_x);
+       y = ai_y[0];
+       x = ai_x[0];
+       break;
+    case 5:
+       /* "chase" AI i.e. pursue your last known position. */
+       get_chase_prefs(mapmonster[*py][*px], ai_y, ai_x);
+       y = ai_y[0];
+       x = ai_x[0];
+       break;
+    }
+    *py = y;
+    *px = x;
+}
+
+void mon_acts(int mon)
+{
+    struct mon *mptr;
+    int dy, dx;
+    int y, x;
+    int sy, sx;
+    int meleerange;
+    int oncardinal;
+    int special_used = 0;
+    mptr = monsters + mon;
+    /* dy,dx == direction monster must go to reach you. */
+    y = mptr->y;
+    x = mptr->x;
+    compute_directions(u.y, u.x, y, x, &dy, &dx, &sy, &sx, &meleerange, &oncardinal);
+    dy = u.y - mptr->y;
+    dx = u.x - mptr->x;
+    if ((dy == 0) && (dx == 0))
+    {
+       print_msg("Program disordered: monster in player's square.\n");
+       print_msg("Discarding misplaced monster.\n");
+       mptr->used = 0;
+       mapmonster[y][x] = -1;
+       return;
+    }
+    if (mapmonster[y][x] != mon)
+    {
+       print_msg("Program disordered: monster(s) misplaced.\n");
+       mptr->used = 0;
+       if (mapmonster[y][x] != -1)
+       {
+           monsters[mapmonster[y][x]].used = 0;
+           mapmonster[y][x] = -1;
+       }
+       return;
+    }
+    if (meleerange)
+    {
+       /* Adjacent! Attack you.  Demons have a 1 in 10 chance of
+        * attempting to summon another demon instead of attacking
+        * you. */
+       if (!mptr->awake)
+       {
+           print_mon_name(mon, 2);
+           print_msg(" notices you.\n");
+           mptr->awake = 1;
+       }
+       if ((mptr->mon_id == PM_DEMON) && !zero_die(10))
+       {
+           summon_demon_near(y, x);
+           special_used = 1;
+       }
+       else if (pmon_is_magician(mptr->mon_id))
+       {
+           special_used = use_black_magic(mon);
+       }
+       if (!special_used)
+       {
+           mhitu(mon, DT_PHYS);
+       }
+    }
+    else if (inyourroom(y, x))
+    {
+       /* In same room. */
+       if (!mptr->awake)
+       {
+           print_mon_name(mon, 2);
+           print_msg(" notices you.\n");
+           mptr->awake = 1;
+       }
+       if (pmon_is_magician(mptr->mon_id))
+       {
+           /* Two-thirds of the time, try to use black magic. */
+           if (zero_die(6) < 4)
+           {
+               special_used = use_black_magic(mon);
+           }
+           if (special_used)
+           {
+               return;
+           }
+           /* Didn't, or couldn't, use black magic; converge
+            * as if an archer. */
+           select_space(&y, &x, dy, dx, 1);
+       }
+       else if (pmon_is_archer(mptr->mon_id))
+       {
+           if (oncardinal && (zero_die(6) < 3))
+           {
+               special_used = 1;
+               mshootu(mon);
+           }
+           if (special_used)
+           {
+               return;
+           }
+           select_space(&y, &x, dy, dx, 1);
+       }
+       else if (pmon_is_smart(mptr->mon_id))
+       {
+           select_space(&y, &x, dy, dx, 2);
+       }
+       else /* pmon_is_stupid() */
+       {
+           select_space(&y, &x, dy, dx, 0);
+       }
+       if ((y != mptr->y) || (x != mptr->x))
+       {
+           /* We decided to move; move! */
+           move_mon(mon, y, x);
+       }
+    }
+    else if (!mptr->awake)
+    {
+       return;
+    }
+    else
+    {
+       /* Out of LOS, but awake. Stupid monsters move "drunkenly"; smart
+        * monsters (may) seek you out. */
+       if (pmon_is_magician(mptr->mon_id))
+       {
+           /* Magicians may have spells that are used when
+            * you are out of sight.  For example, some magicians
+            * may teleport themselves to your vicinity. */
+           special_used = use_black_magic(mon);
+       }
+       if (special_used)
+       {
+           return;
+       }
+       if (pmon_is_smart(mptr->mon_id))
+       {
+           select_space(&y, &x, dy, dx, 4);
+       }
+       else if (pmon_is_stupid(mptr->mon_id) || (mptr->ai_lasty == -1))
+       {
+           select_space(&y, &x, dy, dx, 3);
+       }
+       else
+       {
+           select_space(&y, &x, dy, dx, 5);
+       }
+       if ((y != mptr->y) || (x != mptr->x))
+       {
+           /* We decided to move; move! */
+           move_mon(mon, y, x);
+       }
+    }
+    /* Let's get the data again. */
+    compute_directions(u.y, u.x, y, x, &dy, &dx, &sy, &sx, &meleerange, &oncardinal);
+    if (meleerange || ((roomnums[u.y][u.x] != -1) && (roomnums[u.y][u.x] == roomnums[y][x])))
+    {
+       mptr->ai_lasty = u.y;
+       mptr->ai_lastx = u.x;
+    }
+    else if (mptr->ai_lasty != -1)
+    {
+       mptr->ai_lasty = -1;
+       mptr->ai_lastx = -1;
+    }
+}
+
+/* mon2.c */
diff --git a/monsters.c b/monsters.c
new file mode 100644 (file)
index 0000000..9d93360
--- /dev/null
@@ -0,0 +1,505 @@
+/* monsters.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define MONSTERS_C
+#include "dunbash.h"
+#include "monsters.h"
+
+struct mon monsters[100];
+static int reject_mon(int pm);
+
+int summoning(int y, int x, int how_many)
+{
+    int i;
+    int dy;
+    int dx;
+    int tryct;
+    int mon;
+    int created = 0;
+    int pmon;
+    for (i = 0; i < how_many; i++)
+    {
+       for (tryct = 0; tryct < 20; tryct++)
+       {
+           dy = zero_die(3) - 1;
+           dx = zero_die(3) - 1;
+           if ((terrain[y + dy][x + dx] == FLOOR) &&
+               (mapmonster[y + dy][x + dx] == -1) &&
+               ((y + dy != u.y) || (x + dx != u.x)))
+           {
+               pmon = get_random_pmon();
+               if (pmon_is_magician(pmon))
+               {
+                   /* Never summon magicians! */
+                   continue;
+               }
+               mon = create_mon(-1, y + dy, x + dx);
+               if (mon != -1)
+               {
+                   created++;
+                   break;
+               }
+           }
+       }
+    }
+    if (created > 0)
+    {
+       map_updated = 1;
+       display_update();
+    }
+    return created;
+}
+
+int ood(int power, int ratio)
+{
+    return (depth - power + ratio - 1) / ratio;
+}
+
+int get_random_pmon(void)
+{
+    int tryct;
+    int pm;
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+       pm = zero_die(PM_REAL_COUNT);
+       if (reject_mon(pm))
+       {
+           pm = -1;
+           continue;
+       }
+       break;
+    }
+    return pm;
+}
+
+int create_mon(int pm_idx, int y, int x)
+{
+    int mon;
+    if (mapmonster[y][x] != -1)
+    {
+       print_msg("Attempt to create monster at occupied space %d %d\n", y, x);
+       return -1;
+    }
+    if (pm_idx == -1)
+    {
+       pm_idx = get_random_pmon();
+       if (pm_idx == -1)
+       {
+           return -1;
+       }
+    }
+    for (mon = 0; mon < 100; mon++)
+    {
+       if (monsters[mon].used == 0)
+       {
+           monsters[mon].mon_id = pm_idx;
+           monsters[mon].used = 1;
+           monsters[mon].y = y;
+           monsters[mon].x = x;
+           monsters[mon].ai_lasty = -1;
+           monsters[mon].ai_lastx = -1;
+           monsters[mon].hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
+           monsters[mon].hpcur = monsters[mon].hpmax;
+           monsters[mon].mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
+           monsters[mon].defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
+           monsters[mon].mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
+           if (permons[pm_idx].rdam != -1)
+           {
+               monsters[mon].rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
+               monsters[mon].rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
+           }
+           else
+           {
+               monsters[mon].rtohit = -1;
+               monsters[mon].rdam = -1;
+           }
+           monsters[mon].awake = 0;
+           mapmonster[y][x] = mon;
+           newsym(y, x);
+           return mon;
+       }
+    }
+    return -1;
+}
+
+void death_drop(int mon)
+{
+    int pm = monsters[mon].mon_id;
+    int y = monsters[mon].y;
+    int x = monsters[mon].x;
+    int dy, dx;
+    int tryct;
+    while (((mapobject[y][x] != -1) || (terrain[y][x] != FLOOR)) && tryct < 100)
+    {
+       dy = zero_die(3) - 1;
+       dx = zero_die(3) - 1;
+       tryct++;
+       y += dy;
+       x += dx;
+    }
+    if (tryct >= 100)
+    {
+       return;
+    }
+    switch (pm)
+    {
+    case PM_GOBLIN:
+       if (!zero_die(4))
+       {
+           create_obj(PO_DAGGER, 1, 0, y, x);
+       }
+       break;
+    case PM_THUG:
+    case PM_GOON:
+       if (!zero_die(4))
+       {
+           create_obj(PO_MACE, 1, 0, y, x);
+       }
+       else if (!zero_die(3))
+       {
+           create_obj(PO_LEATHER_ARMOUR, 1, 0, y, x);
+       }
+       break;
+    case PM_HUNTER:
+       if (!zero_die(6))
+       {
+           create_obj(PO_BOW, 1, 0, y, x);
+       }
+       break;
+    case PM_DUELLIST:
+       if (!zero_die(6))
+       {
+           create_obj(PO_LONG_SWORD, 1, 0, y, x);
+       }
+       break;
+    case PM_WIZARD:
+       if (!zero_die(4))
+       {
+           create_obj_class(POCLASS_SCROLL, 1, 0, y, x);
+       }
+       else if (!zero_die(3))
+       {
+           create_obj_class(POCLASS_POTION, 1, 0, y, x);
+       }
+        break;
+    case PM_WARLORD:
+       if (!zero_die(3))
+       {
+           create_obj(PO_RUNESWORD, 1, 0, y, x);
+       }
+        break;
+    default:
+       break;
+    }
+    map_updated = 1;
+}
+
+int mon_can_pass(int mon, int y, int x)
+{
+    if ((y < 0) || (x < 0) || (y >= DUN_HEIGHT) || (x >= DUN_WIDTH))
+    {
+       return 0;
+    }
+    if (mapmonster[y][x] != -1)
+    {
+       return 0;
+    }
+    if ((y == u.y) && (x == u.x))
+    {
+       /* Sanity check! */
+       return 0;
+    }
+    if (monsters[mon].mon_id == PM_WRAITH)
+    {
+       /* Wraiths can walk through walls. */
+       return 1;
+    }
+    if (terrain[y][x] == WALL)
+    {
+       return 0;
+    }
+    return 1;
+}
+
+void print_mon_name(int mon, int article)
+{
+    if (permons[monsters[mon].mon_id].name[0] == '\0')
+    {
+       print_msg("GROB THE VOID (%d)", monsters[mon].mon_id);
+    }
+    switch (article)
+    {
+    case 0:    /* a */
+       print_msg("a%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
+       break;
+    case 1: /* the */
+       print_msg("the %s", permons[monsters[mon].mon_id].name);
+       break;
+    case 2: /* A */
+       print_msg("A%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
+       break;
+    case 3: /* The */
+       print_msg("The %s", permons[monsters[mon].mon_id].name);
+       break;
+    }
+}
+
+void heal_mon(int mon, int amount, int cansee)
+{
+    if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
+    {
+       amount = monsters[mon].hpmax - monsters[mon].hpcur;
+    }
+    if (amount > 0)
+    {
+       if (cansee)
+       {
+           print_mon_name(mon, 3);
+           print_msg(" looks healthier.\n");
+       }
+       monsters[mon].hpcur += amount;
+    }
+}
+
+void damage_mon(int mon, int amount, int by_you)
+{
+    struct mon *mptr;
+    mptr = monsters + mon;
+    if (amount >= mptr->hpcur)
+    {
+       if (by_you)
+       {
+           print_msg("You kill ");
+           if (mon_visible(mon))
+           {
+               print_mon_name(mon, 1);
+           }
+           else
+           {
+               print_msg("something");
+           }
+           print_msg("!\n");
+           gain_experience(permons[mptr->mon_id].exp);
+       }
+       else if (mon_visible(mon))
+       {
+           print_mon_name(mon, 2);
+           print_msg(" dies.\n");
+       }
+       death_drop(mon);
+       mapmonster[mptr->y][mptr->x] = -1;
+       newsym(mptr->y, mptr->x);
+       mptr->used = 0;
+       map_updated = 1;
+       display_update();
+    }
+    else
+    {
+       mptr->hpcur -= amount;
+    }
+}
+
+int reject_mon(int pm)
+{
+    if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
+    {
+       return 1;
+    }
+    return 0;
+}
+
+int teleport_mon_to_you(int mon)
+{
+    int tryct;
+    int dy, dx;
+    int y, x;
+    int success = 0;
+    int roomnum;
+    if (roomnums[u.y][u.x] == -1)
+    {
+       /* Player in corridor.  Try to teleport next to him. */
+       for (tryct = 0; tryct < 40; tryct++)
+       {
+           dy = zero_die(3) - 1;
+           dx = zero_die(3) - 1;
+           y = u.y + dy;
+           x = u.x + dx;
+           if (mon_can_pass(mon, y, x))
+           {
+               success = 1;
+               break;
+           }
+       }
+    }
+    else
+    {
+       /* Player is in a room.  Try to teleport into same room. */
+       roomnum = roomnums[u.y][u.x];
+       for (tryct = 0; tryct < 200; tryct++)
+       {
+           y = exclusive_flat(roombounds[roomnum][0], roombounds[roomnum][1]);
+           x = exclusive_flat(roombounds[roomnum][2], roombounds[roomnum][3]);
+           if (mon_can_pass(mon, y, x))
+           {
+               success = 1;
+               break;
+           }
+       }
+    }
+    if (success)
+    {
+       move_mon(mon, y, x);
+       print_mon_name(mon, 2);
+       print_msg(" appears in a puff of smoke.\n");
+       return 1;
+    }
+    return 0;
+}
+
+int teleport_mon(int mon)
+{
+    int rval = -1;
+    int room_try;
+    int cell_try;
+    int room;
+    int y, x;
+    for (room_try = 0; room_try < MAX_ROOMS * 4; room_try++)
+    {
+       room = zero_die(MAX_ROOMS);
+       for (cell_try = 0; cell_try < 200; cell_try++)
+       {
+           y = exclusive_flat(roombounds[room][0], roombounds[room][1]);
+           x = exclusive_flat(roombounds[room][2], roombounds[room][3]);
+           if ((mapmonster[y][x] == -1) && (terrain[y][x] == FLOOR) && ((y != u.y) || (x != u.x)))
+           {
+               move_mon(mon, y, x);
+               rval = 0;
+               break;
+           }
+       }
+    }
+    return rval;
+}
+
+void move_mon(int mon, int y, int x)
+{
+    struct mon *mptr;
+    if (!mon_can_pass(mon, y, x))
+    {
+       print_msg("Warning: monster attempted an invalid move.\n");
+       return;
+    }
+    mptr = monsters + mon;
+    if (mapmonster[mptr->y][mptr->x] != mon)
+    {
+       print_msg("Monster map array in disorder.\n");
+       press_enter();
+       return;
+    }
+    mapmonster[mptr->y][mptr->x] = -1;
+    newsym(mptr->y, mptr->x);
+    mptr->y = y;
+    mptr->x = x;
+    mapmonster[mptr->y][mptr->x] = mon;
+    newsym(mptr->y, mptr->x);
+    display_update();
+}
+
+void summon_demon_near(int y, int x)
+{
+    int y2, x2;
+    int i;
+    y2 = y - 1 + zero_die(3);
+    x2 = x - 1 + zero_die(3);
+    if ((terrain[y2][x2] == FLOOR) && (mapmonster[y2][x2] == -1) &&
+       ((y2 != u.y) || (x2 != u.x)))
+    {
+       i = create_mon(PM_DEMON, y2, x2);
+       if (i != -1)
+       {
+           print_msg("Another demon appears!\n");
+       }
+       else
+       {
+           print_msg("You smell sulphurous fumes.\n");
+       }
+    }
+}
+
+int mon_visible(int mon)
+{
+    int dy, dx;
+    if (monsters[mon].used == 0)
+    {
+       return 0;
+    }
+    dy = u.y - monsters[mon].y;
+    dx = u.x - monsters[mon].x;
+    if (((dy > -2) && (dy < 2) && (dx > -2) && (dx < 2)) ||
+       ((roomnums[u.y][u.x] != -1) && (roomnums[u.y][u.x] == roomnums[monsters[mon].y][monsters[mon].x])))
+    {
+       return 1;
+    }
+    else
+    {
+       return 0;
+    }
+}
+
+void update_mon(int mon)
+{
+    int cansee;
+    if (monsters[mon].hpcur < monsters[mon].hpmax)
+    {
+       cansee = mon_visible(mon);
+       switch (monsters[mon].mon_id)
+       {
+       case PM_TROLL:
+           if (!(game_tick % 10))
+           {
+               if (cansee)
+               {
+                   print_msg("The troll regenerates.\n");
+               }
+               heal_mon(mon, one_die(3) + 3, 0);
+           }
+           break;
+
+       case PM_ZOMBIE:
+           /* Zombies don't recover from their injuries. */
+           break;
+
+       default:
+           if (!(game_tick % 20))
+           {
+               heal_mon(mon, 1, cansee);
+           }
+           break;
+       }
+    }
+}
+
+/* monsters.c */
diff --git a/monsters.h b/monsters.h
new file mode 100644 (file)
index 0000000..8d9c202
--- /dev/null
@@ -0,0 +1,65 @@
+/* monsters.h
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MONSTERS_H
+#define MONSTERS_H
+
+#ifndef DUNBASH_H
+#include "dunbash.h"
+#endif
+
+/* XXX monsters.c data and funcs */
+extern void update_mon(int mon);
+extern void mon_acts(int mon);
+extern void death_drop(int mon);
+extern void print_mon_name(int mon, int article);
+extern void summon_demon_near(int y, int x);
+extern int create_mon(int pm_idx, int y, int x);
+extern int summoning(int y, int x, int how_many);
+extern int ood(int power, int ratio);
+extern int get_random_pmon(void);
+extern void damage_mon(int mon, int amount, int by_you);
+extern int mon_can_pass(int mon, int y, int x);
+extern int mon_visible(int mon);
+extern void move_mon(int mon, int y, int x);
+extern int teleport_mon(int mon);      /* Randomly relocate monster. */
+extern int teleport_mon_to_you(int mon);       /* Relocate monster to your vicinity. */
+extern void heal_mon(int mon, int amount, int cansee);
+
+/* XXX mon2.c data and funcs */
+extern void select_space(int *py, int *px, int dy, int dx, int selection_mode);
+
+/* XXX pmon2.c data and funcs */
+extern int pmon_is_archer(int pm);
+extern int pmon_is_magician(int pm);
+extern int pmon_is_smart(int pm);
+extern int pmon_is_stupid(int pm);
+extern int pmon_is_undead(int pm);
+extern int pmon_resists_cold(int pm);
+extern int pmon_resists_fire(int pm);
+#endif
+
+/* monsters.h */
diff --git a/notes.txt b/notes.txt
new file mode 100644 (file)
index 0000000..1872a7c
--- /dev/null
+++ b/notes.txt
@@ -0,0 +1,405 @@
+MARTIN'S DUNGEON BASH, VERSION 1.7
+==================================
+Copyright 2009 Martin Read.
+
+This software is released under the terms of the BSD-style licence as provided
+in the file /usr/share/doc/apps/LICENSES/BSD on a Debian GNU/Linux system.
+
+Martin's Dungeon Bash (aka "dungeonbash") is copyright 2005-2009 Martin Read.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ABOUT THE GAME
+--------------
+This is Martin's Dungeon Bash, aka "dungeonbash", version 1.7.  This release
+of Martin's Dungeon Bash fixes several bugs in version 1.6.
+
+Martin's Dungeon Bash v1.7 has a very simple concept: Kill as many monsters
+as you can and amass as much gold as you can, diving ever deeper into Martin's
+Infinite Dungeon, until you die.  There is no "victory" condition; the only
+objective provided by the design is "see how far you get, how much gold you
+gather, and how many monsters you kill".
+
+There are no new gameplay features currently planned.
+
+The game has a website:
+  http://www.chiark.greenend.org.uk/~mpread/dungeonbash/
+
+Discussion of Martin's Dungeon Bash is on-topic for the newsgroup
+rec.games.roguelike.misc ; please put -MPRDB- in the Subject: header of any
+post to RGRM regarding Martin's Dungeon Bash.
+
+INSTALLATION INSTRUCTIONS
+-------------------------
+On Linux (and other Unix-like systems, though I don't guarantee it working
+on any given Unix):
+   tar xzf dungeonbash-1.7.tar.gz
+   cd dungeonbash-1.7
+   make all
+   cp dungeonbash /somewhere/in/your/PATH
+
+Richard Kettlewell reports that Darwin users should edit the Makefile
+before compiling to remove '-Werror' from CFLAGS, due to a bug in Darwin's
+copy of 'ncurses.h'. This may also apply to other platforms' implementations
+of curses. (The version of ncurses currently distributed with Debian
+GNU/Linux takes 'const char *' where it wants strings that it isn't
+going to modify; some implementations of curses declare these functions to
+take 'char *' in those locations.)
+
+David Damerell reports that if you are using a particularly old version
+of GCC, you may need to remove the '-Wunreachable-code' flag from CFLAGS.
+
+(No, I will not consider removing any of the -W flags from the default
+distribution; they are all there for a reason, with -Werror being there to
+stop me getting away with having warnings.)
+
+I am also informed that with suitable minor modifications, my code will 
+build on Windows XP (I believe using a GCC-based environment).
+
+The dungeonbash binary does not require any setgid or setuid privileges, and
+does not require root privilege to install unless you want to put it in a
+system directory (e.g. /usr/local/games), as it has no shared data and no
+shared playground.  Players are trusted to not savescum, because savescumming
+is cheating, and cheating sucks.
+
+There are not official binary releases of Martin's Dungeon Bash v1.7
+contemporaneous with the source release.
+
+ABOUT THE GAME
+--------------
+Martin's Dungeon Bash v1.7 is conceptually trivial: You are trapped in an
+(approximately) infinitely deep dungeon complex from which there is said to
+be no possible escape.  Not being ready to give up on life just yet, you
+decide to kill as many of the denizens as possible before you yourself
+succumb.
+
+As you dive deeper into the dungeon, you will meet more fearsome foes, and
+familiar foes will increase in power.  It is said that eventually, the
+beasts of the dungeon grow so mighty that they can slay even the boldest of
+heroes with a single blow.
+
+REPORTING BUGS
+--------------
+Report bugs by e-mail to mpread+dbash+1@chiark.greenend.org.uk.
+
+THANKS TO
+---------
+* Jeff Lait, for the concept of the Seven-Day Roguelike Challenge (and
+  POWDER's speed system, which I have approximately ripped off), and for the
+  suggestion I've implemented whereby armour and weapons are slowly ground
+  away through use and so the player must carry spares.
+* Jon Amery, for reporting a bug in v1.0 which led to me finding that (1)
+  I hadn't put an entry for identify scrolls in permobjs[] (2) magic robes
+  weren't being identified on wearing (3) mundane robes weren't being listed
+  properly in the inventory.  Jon also spotted a bug in 1.0 which I failed to
+  fix in 1.1 (regarding summoned monsters), which I have now put right.
+* Adam White, for alerting me to the fact that I was making a now-unsafe
+  assumption about the size of long int, which caused the RNG to do Bad
+  Things on x86-64.
+* Gero Kunter, for reporting a big bunch of bugs in v1.2 and v1.3, and
+  coming up with some very good suggestions for improving play balance.
+* David Damerell, for a host of bug reports.
+* Richard Kettlewell, for the pointers on building the game on Darwin.
+* Programmers too numerous to mention, down the years, who have written the
+  assortment of roguelike games I've played.
+* You, for downloading my game.
+
+NEW FEATURES IN RELEASE 1.7
+---------------------------
+* None.
+
+BUG FIXES IN RELEASE 1.7
+------------------------
+* 'e'ating now consumes a turn as well as an item of food.
+* Wizards no longer drop runeswords.
+* The HP and XP of wraiths have been corrected to be more appropriate for their
+  power rating.
+* The armour spoiler now correctly proclaims itself to be the armour spoiler.
+
+NEW FEATURES IN RELEASE 1.6
+---------------------------
+* The curses-based display module should do much less I/O now.
+* There is now a "wizard" mode for debugging purposes. Enable it by changing
+  the WIZARD_MODE macro in dunbash.h from 0 to 1.
+* Certain monsters are now (much) easier to hit. (In particular, a first
+  level character is now capable of hitting a bad elf, and it's virtually
+  impossible to miss a zombie.)
+* Necromantic bolts and lightning bolts now use pure-agility defence.
+* Once your food counter hits the lower end cap, rings of regeneration
+  cease to function.
+
+BUG FIXES IN RELEASE 1.6
+------------------------
+* 'T'aking off armour no longer consumes a turn if you are already naked.
+* Treasure zoos no longer have absurdly large numbers of monsters.
+* Flags in struct permons are now being initialised to sensible values;
+  this resolves several issues in version 1.5, including the absence of
+  black magic, the failure of many monsters to use their ranged attacks,
+  and the failure of many monsters to resist elemental damage correctly.
+
+NEW FEATURES IN RELEASE 1.5
+---------------------------
+* Zombies no longer regain hit points over time.
+* The player is alerted when his food counter drops below 100, and again when
+  it drops below zero.
+* The 'I'nspect command permits the player to examine his belongings.
+* The '\' command permits the player to review what types of item he has
+  knowledge of.
+* The '#' command permits the player to inspect the underlying terrain,
+  without the obstructions caused by monsters, the player character,
+  and items on the floor.
+* Body and agility are now capped at 99.
+* There are more kinds of food.  Each individual kind of food has non-zero
+  rarity.  As a result, the player may well end up needing to carry
+  several kinds of food.
+
+BUG FIXES IN RELEASE 1.5
+------------------------
+* A conceptual clash in symbol space has been resolved by categorising
+  duellists and warlords as 'f'ighters.
+* The status window now shows body and agility in a "current/max" format so
+  that the player can distinguish between temporary and permanent ability
+  loss.
+* Demon summoning no longer attempts to make the summoned demon materialise
+  on top of the summoner.
+* The first item in the inventory no longer scrolls off the message box when
+  attempting to 'd'rop while the pack is full.
+* Body drains now correctly set the status window update flag and update the
+  display.
+* If you can't see a monster involved in the resolution of a ranged attack,
+  you don't get told what it is.
+* Black magicians' names are correctly displayed when spells are cast.
+* The game will no longer attempt to generate more items than the arrays can
+  accommodate.
+* Hellfire messages are changed to be less irritating on repetition.
+* Treasure zoos now work.
+* Restoration potions now trigger a display update.
+* Body and agility damage are displayed in a more useful format.
+* The player's first action now occurs before the first call to
+  update_player(), so the player really does get 2000 ticks worth of food.
+
+NEW FEATURES IN RELEASE 1.4
+---------------------------
+* Ability score damage may be temporary or permanent; temporary ability score
+  damage may be cured using a restoration potion.
+* Armour and weapons are degraded through use, and cannot be repaired.
+* The mechanics of poison damage (e.g. snakebite) have been revamped.
+* Some minor "flavour" has been added to the eating-food message for when the
+  player's "food" score is below zero.
+* Command 'D' writes a character dump.
+* The player's name cannot contain any colons, slashes, or backslashes.
+
+BUG FIXES IN RELEASE 1.4
+------------------------
+* Various pieces of black magic now print the correct monster name.
+* The armourmelt curse now has a meaningful effect.
+* The check for whether the player is wearing a ring in combat.c now correctly
+  checks against -1, not against 0.
+* Monster healing messages no longer appear when the player is out of sight.
+* The inventory-select function now behaves more intelligently in the face of
+  an absence of valid items, and rejects attempts to select nonexistent
+  objects.
+* Aggravation scrolls now do something.
+* Eating now updates the status window.
+* Protection and aggravation scrolls now have randomised titles.
+* The level threshold function now returns the correct value for levels
+  greater than 19.
+* Defilers now cast spells.
+* Demons can no longer get summoned on top of each other.
+* Daggers found in the dungeon now have non-zero utility.
+
+KNOWN BUGS IN RELEASE 1.4
+-------------------------
+* Monster drops which scatter to an explored square that is not currently
+  visible are nevertheless visible. (This is a minor bug, IMO, and I'm not
+  keen on the added state-maintenance required to fix it.)
+
+NEW FEATURES IN RELEASE 1.3
+---------------------------
+* The use of colour has been extended to monsters, and the colour handling
+  in the display code has been much improved as a necessary part of this.
+* Some monsters have had their symbols changed; closely related monsters (for
+  example, thugs and goons) now share a symbol (but have different colours).
+* Several new monsters have been added.
+* Several new magic items have been added.
+* Dungeon rooms are larger than in v1.2; this should improve the usefulness
+  of missile weapons and increase the threat posed by missile-capable monsters.
+* Certain powerful items (most importantly, mage armour, runeswords, and robes
+  of shadows) cannot be found until the deeper levels of the dungeon.  This
+  prevents an early find of such an item from making the first few levels of
+  the dungeon a cakewalk. [Suggestion by Gero Kunter]
+* Some monsters can now use "black magic", giving them several ways to attack
+  (or evade!) the player, not all of which do pure hit point damage.  This
+  includes one or two monsters who can attack from a distance, albeit with
+  limited power, without having to line the player up on a cardinal.
+* Melee-only monsters using the simplest AI mode will no longer "form an
+  orderly queue" if you string them out along a cardinal direction.
+  [Suggestion by Gero Kunter]
+* All monsters now slowly recover hit points; trolls simply do so much more
+  quickly than other kinds of monsters.
+* Hunger and food have been added.  A "hungry" player character regains hit
+  points even more slowly than the v1.2-and-earlier healing rate, and
+  cannot regain hit points above 75% of his normal total.  A "fed" player
+  character regains hit points rather faster than the v1.2-and-earlier healing
+  rate.  Rings of regeneration increase the rate at which the player character
+  becomes hungry.  The player character now starts the game well-fed, with a
+  ration of food in addition to his basic dagger. [Basic suggestion by Gero
+  Kunter; effects of hunger inspired by Jeff Lait's _POWDER_; impact of 
+  regeneration items on metabolic rate is a classic behaviour of roguelikes.]
+* Non-"stupid" monsters who were next to you when you fled a room down a
+  corridor will pursue you down the corridor until you can outrun them.
+* The game now waits for a <CR> or <SPACE> before exiting after save, quit,
+  or player death, and before continuing play after the player gains a level.
+  [Suggestion by Gero Kunter]
+* Increases to maximum hit points are accompanied by an equal increase in
+  current hit points. (This makes the early game a little bit more survivable.)
+* The prompt presented to confirm the 'X' quit command is now much more
+  paranoid; only a capital 'Y' in response to the query will cause the game
+  to be abandoned, with any other response cancelling the quit command.
+* The game now maintains a log file recording every character to die in
+  Martin's Infinite Dungeon.  [Part of a suggestion by Gero Kunter]
+
+BUG FIXES IN RELEASE 1.3
+------------------------
+* Object name printing is more internally consistent, less potentially
+  misleading, and produces a better approximation to "correct English", though
+  if I ever introduce "honourable" or suchlike as a description of an item,
+  I shall have to refine the algorithm further. [Bug reported by Gero Kunter]
+* Monster name printing produces a better approximation to "correct English";
+  the same caveat regarding words like "honourable" applies here.
+* The select_dir() function (used by the 'a'ttack command) now accepts number
+  keys as well as the hjklyubn-keys. [Bug reported by Gero Kunter]
+* Player hit points are correctly displayed after a poison potion has been
+  consumed. [Bug reported by Gero Kunter]
+* The save file is no longer called "7drl.sav.gz"; the new name for the save
+  file is "dunbash.sav.gz". [Bug reported by Gero Kunter]
+* It is now only possible to descend to a new level when standing on a
+  staircase; previously, you could descend from any square on the level.
+  [Bug reported by Gero Kunter]
+* I believe I have fixed the mysterious "ghost monster" bugs, but I can't
+  swear to that.
+
+KNOWN BUGS IN RELEASE 1.3
+-------------------------
+* The log file cannot be shared between users except by playing the game in
+  a shared world- or group-writable directory. I refuse to add any feature
+  requiring setgid or setuid privileges until I have satisfied myself that
+  I know what the necessary steps are to ensure the game does not represent
+  a security hazard to the system on which it is installed set[ug]id.
+* The AI remains not as smart as I wanted it to be.
+* There *still* isn't any sanity checking when loading a saved game.
+
+NEW FEATURES IN RELEASE 1.2
+---------------------------
+* None.
+
+BUG FIXES IN RELEASE 1.2
+------------------------
+* Monsters no longer get summoned in spaces that are not unoccupied floor.
+  [Bug reported by Jon Amery, although the bug turned out to not quite be
+  what he thought it was.]
+* Saved games can once more be reloaded. [Bug detected locally.]
+
+NEW FEATURES IN RELEASE 1.1
+---------------------------
+* Magic robes now auto-ID when worn.
+
+BUG FIXES IN RELEASE 1.1
+------------------------
+* permons[] is no longer stored in the save file, as it has no features that
+  are changed at runtime
+* Magic robes are now correctly printed in the inventory when identified.
+  [Bug reported by Jon Amery]
+* Scrolls of identify now exist and function. [Bug found due to bug report
+  by Jon Amery]
+* The internal RNG now uses "unsigned int" instead of "unsigned long" to store
+  its state. As a result, the game can now be played on x86-64. [Bug reported
+  by Adam White]
+
+NEW FEATURES IN RELEASE 1.0
+---------------------------
+* The game now uses an internal pseudo-random number generator with a much
+  larger period than Berkeley random().
+* Save files are marginally smaller, as the new internal PRNG uses much
+  less state than Berkeley random().
+* The dungeon walls are now "dark yellow" and the player is now "bright
+  white".
+* New monsters.
+* New capabilities for some existing monsters.
+* Several new items.
+
+BUG FIXES IN RELEASE 1.0
+------------------------
+* ood() now works correctly; it makes weak monsters stronger, not weaker,
+  and does not cause related divide-by-zero errors in the dice-rolling code.
+* The dice-rolling code now checks for being passed values less than two,
+  and simply returns 1 or 0 (as appropriate) in such circumstances.
+* Save/reload now works correctly, without corrupting the dungeon map.
+* The status display correctly depicts the player's Agility.
+* Body potions no longer improve the player's Agility.
+* Monsters created by summoning scrolls are no longer hidden from view
+  until the next map update.
+* The 'awake' flag is set (with accompanying message) in the AI branch
+  handling the case where the monster is directly adjacent to the player.
+* Mid-level monsters now have some chance of hitting characters wearing
+  mage armour.
+* Teleport scrolls now actually work.
+* The archive unpacks into a subdirectory.
+
+KNOWN BUGS AND INFELICITIES
+---------------------------
+* There is little incentive for the player to use ranged weapons, as rings
+  do not enhance them and dungeon rooms are small.
+* The game does not actually check that the data loaded from the saved game
+  file even vaguely conforms to what is expected.
+* Monster ranged attacks no longer get silently absorbed by a non-existent
+  monster on their first square of movement.
+* The AI remains less intelligent than it should be.
+
+WRINKLES
+--------
+* Due to a change I made at one point for other reasons, monster melee attacks
+  are more accurate than monster missile attacks.  I like this state of
+  affairs; it prevents dragons from being shockingly lethal.
+
+THE ORIGINAL RELEASE NOTES FOR MPR7DRL NOW FOLLOW
+=================================================
+
+ABOUT THE GAME
+This is Martin's Seven-Day Roguelike, aka "mpr7drl", written by me (Martin
+Read) in less than 168 hours total wall time (total of the time I spent
+eating, sleeping, working, coding mpr7drl, playing Nethack, playing SFB,
+playing Angband variants, talking on the phone, arranging to rent a new
+flat, going to the pub to talk about Nethack, reading Livejournal, reading
+Usenet, ...).
+
+KNOWN BUGS AND INFELICITIES:
+* Reloading can corrupt some of the map data; I can't work out quite what's
+  going wrong here.
+* print_obj_name() and print_mon_name() are insufficiently sophisticated.
+* The game balance blows goats.
+* The AI sucks rocks through a straw.
+
+But hey.  It's a roguelike written in 3600 lines of C in 168 hours, so what
+are you expecting?  The next Nethack?
+
+-- MPR, 19:39 Sunday 2005.01.30 GMT 
diff --git a/objects.c b/objects.c
new file mode 100644 (file)
index 0000000..1aa9200
--- /dev/null
+++ b/objects.c
@@ -0,0 +1,826 @@
+/* objects.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define OBJECTS_C
+#include "dunbash.h"
+#include "monsters.h"
+
+struct obj objects[100];
+int get_random_pobj(void);
+static int consume_obj(int obj);
+
+const char ring_colours[20][16] = {
+    "gold", "ruby", "sapphire", "ivory", "coral",
+    "amethyst", "silver", "iron", "copper", "jade",
+    "haematite", "bone", "crystal", "platinum", "lead",
+    "diamond", "topaz", "emerald", "electrum", "smoky quartz"
+};
+
+const char scroll_titles[20][16] = {
+    "grem pho", "terra terrax", "phong", "ateh malkuth", "xixaxa",
+    "aku ryo tai san", "qoph shin tau", "ythek shri", "ia ia", "cthulhu fhtagn",
+    "arifech malex", "DOOM", "leme athem", "hail smkznrf", "rorrim foo",
+    "ad aerarium", "ligemrom", "asher ehiyeh", "YELLOW SIGN", "ELDER SIGN"
+};
+
+const char potion_colours[20][16] = {
+    "purple", "red", "blue", "green", "yellow",
+    "orange", "white", "black", "brown", "fizzy",
+    "grey", "silver", "gold", "shimmering", "glowing",
+    "navy blue", "bottle green", "amber", "lilac", "ivory"
+};
+
+int read_scroll(int obj)
+{
+    struct obj *optr = objects + obj;
+    int i;
+    switch (optr->obj_id)
+    {
+    case PO_SCR_IDENTIFY:
+       print_msg("This is an identify scroll!\n");
+       for (i = 0; i < 19; i++)
+       {
+           if (u.inventory[i] != -1)
+           {
+               permobjs[objects[u.inventory[i]].obj_id].known = 1;
+           }
+       }
+       break;
+    case PO_SCR_TELEPORT:
+       teleport_u();
+       break;
+    case PO_SCR_FIRE:
+       print_msg("The scroll explodes in flames!\n");
+       if (u.ring != -1)
+       {
+           if (objects[u.ring].obj_id == PO_RING_FIRE)
+           {
+               print_msg("Your ring glows, and the flames seem cool.\n");
+               permobjs[objects[u.ring].obj_id].known = 1;
+               break;
+           }
+       }
+       i = damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
+       if (!i)
+       {
+           print_msg("That hurt!\n");
+       }
+       break;
+    case PO_SCR_MONSTERS:
+       i = summoning(u.y, u.x, one_die(3) + 1);
+       if (i > 0)
+       {
+           print_msg("Monsters appear!\n");
+       }
+       else
+       {
+           print_msg("You hear a snarl of frustration.\n");
+       }
+       break;
+    case PO_SCR_AGGRAVATE:
+       print_msg("You hear a high-pitched humming noise.\n");
+       for (i = 0; i < 100; i++)
+       {
+           if (monsters[i].used)
+           {
+               monsters[i].awake = 1;
+           }
+       }
+       break;
+    case PO_SCR_PROTECTION:
+       print_msg("You feel like something is helping you.\n");
+       if (!u.protection)
+       {
+           /* Do not prolong existing protection, only grant
+            * protection to the unprotected. */
+           u.protection = 100;
+       }
+       if (u.withering)
+       {
+           print_msg("Your limbs straighten.\n");
+           u.withering = 0;
+       }
+       if (u.armourmelt)
+       {
+           print_msg("Your armour regains its strength.\n");
+           u.armourmelt = 0;
+       }
+       if (u.leadfoot)
+       {
+           print_msg("You shed your feet of lead.\n");
+           u.leadfoot = 0;
+       }
+       break;
+    default:
+       print_msg("Impossible: reading non-scroll\n");
+       return 0;
+    }
+    permobjs[optr->obj_id].known = 1;
+    return consume_obj(obj);
+}
+
+static int consume_obj(int obj)
+{
+    int i;
+    objects[obj].quan--;
+    if (objects[obj].quan == 0)
+    {
+       objects[obj].used = 0;
+       if (objects[obj].with_you)
+       {
+           if (obj == u.armour)
+           {
+               u.armour = -1;
+               recalc_defence();
+           }
+           else if (obj == u.weapon)
+           {
+               u.weapon = -1;
+               recalc_defence();
+           }
+           else if (obj == u.ring)
+           {
+               u.ring = -1;
+               recalc_defence();
+           }
+           for (i = 0; i < 19; i++)
+           {
+               if (u.inventory[i] == obj)
+               {
+                   u.inventory[i] = -1;
+                   break;
+               }
+           }
+       }
+       return 1;
+    }
+    return 0;
+}
+
+int eat_food(int obj)
+{
+    struct obj *optr = objects + obj;
+    if (permobjs[optr->obj_id].poclass != POCLASS_FOOD)
+    {
+       print_msg("Error: attempt to eat non-food (%d)!\n", optr->obj_id);
+       return -1;
+    }
+    if (u.food < 0)
+    {
+       print_msg("You ravenously devour your food!\n");
+    }
+    else
+    {
+       print_msg("You eat some food.\n");
+    }
+    u.food += 1500;
+    status_updated = 1;
+    display_update();
+    return consume_obj(obj);
+}
+
+int quaff_potion(int obj)
+{
+    struct obj *optr = objects + obj;
+    switch (optr->obj_id)
+    {
+    case PO_POT_BODY:
+       gain_body(1, 1);
+       break;
+    case PO_POT_AGILITY:
+       gain_agility(1, 1);
+       break;
+    case PO_POT_WEAKNESS:
+       print_msg("You feel that was a bad idea!\n");
+       drain_body(one_die(4), "a potion of weakness", 1);
+       drain_agility(one_die(4), "a potion of weakness", 1);
+       break;
+    case PO_POT_POISON:
+       print_msg("This is poison!\n");
+       damage_u(dice(3, 12), DEATH_KILLED, "drinking poison");
+       display_update();
+       break;
+    case PO_POT_HEAL:
+       /* Heal player; if hit points brought to max, gain one
+        * hit point. */
+       heal_u(dice(3, 12), 1, 1);
+       break;
+    case PO_POT_RESTORATION:
+       print_msg("This potion makes you feel warm all over.\n");
+       status_updated = 1;
+       if (!zero_die(2))
+       {
+           if (u.adam)
+           {
+               u.adam = 0;
+               print_msg("You feel less clumsy.\n");
+           }
+           else if (u.bdam)
+           {
+               u.bdam = 0;
+               print_msg("You feel less feeble.\n");
+           }
+       }
+       else
+       {
+           if (u.bdam)
+           {
+               u.bdam = 0;
+               print_msg("You feel less feeble.\n");
+           }
+           else if (u.adam)
+           {
+               u.adam = 0;
+               print_msg("You feel less clumsy.\n");
+           }
+       }
+       break;
+    default:
+       print_msg("Impossible: quaffing non-potion\n");
+       return 0;
+    }
+    permobjs[optr->obj_id].known = 1;
+    return consume_obj(obj);
+}
+
+void flavours_init(void)
+{
+    int colour_choices[10];
+    int i;
+    int j;
+    int done;
+    /* Flavoured items use "power" to track their flavour.  This is a
+     * gross and unforgiveable hack. */
+    /* Rings */
+    for (i = 0; i < 10;)
+    {
+       colour_choices[i] = zero_die(20);
+       done = 1;
+       for (j = 0; j < i; j++)
+       {
+           if (colour_choices[i] == colour_choices[j])
+           {
+               done = 0;
+           }
+       }
+       if (done)
+       {
+           i++;
+       }
+    }
+    permobjs[PO_RING_REGEN].power = colour_choices[0];
+    permobjs[PO_RING_FIRE].power = colour_choices[1];
+    permobjs[PO_RING_WEDDING].power = colour_choices[2];
+    permobjs[PO_RING_VAMPIRE].power = colour_choices[3];
+    permobjs[PO_RING_FROST].power = colour_choices[4];
+    permobjs[PO_RING_DOOM].power = colour_choices[5];
+    permobjs[PO_RING_TELEPORT].power = colour_choices[6];
+    /* Scrolls */
+    for (i = 0; i < 10;)
+    {
+       colour_choices[i] = zero_die(20);
+       done = 1;
+       for (j = 0; j < i; j++)
+       {
+           if (colour_choices[i] == colour_choices[j])
+           {
+               done = 0;
+           }
+       }
+       if (done)
+       {
+           i++;
+       }
+    }
+    permobjs[PO_SCR_FIRE].power = colour_choices[0];
+    permobjs[PO_SCR_TELEPORT].power = colour_choices[1];
+    permobjs[PO_SCR_MONSTERS].power = colour_choices[2];
+    permobjs[PO_SCR_IDENTIFY].power = colour_choices[3];
+    permobjs[PO_SCR_PROTECTION].power = colour_choices[4];
+    permobjs[PO_SCR_AGGRAVATE].power = colour_choices[5];
+    /* Potions */
+    for (i = 0; i < 10;)
+    {
+       colour_choices[i] = zero_die(20);
+       done = 1;
+       for (j = 0; j < i; j++)
+       {
+           if (colour_choices[i] == colour_choices[j])
+           {
+               done = 0;
+           }
+       }
+       if (done)
+       {
+           i++;
+       }
+    }
+    permobjs[PO_POT_HEAL].power = colour_choices[0];
+    permobjs[PO_POT_BODY].power = colour_choices[1];
+    permobjs[PO_POT_POISON].power = colour_choices[2];
+    permobjs[PO_POT_AGILITY].power = colour_choices[3];
+    permobjs[PO_POT_WEAKNESS].power = colour_choices[4];
+    permobjs[PO_POT_RESTORATION].power = colour_choices[5];
+}
+
+int create_obj_class(enum poclass_num po_class, int quantity, int with_you, int y, int x)
+{
+    int obj;
+    int po_idx;
+    int tryct;
+    for (obj = 0; obj < 100; obj++)
+    {
+       if (!objects[obj].used)
+       {
+           break;
+       }
+    }
+    if (obj == 100)
+    {
+       print_msg("ERROR: Ran out of objects[].\n");
+       return -1;
+    }
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+       switch (po_class)
+       {
+       case POCLASS_POTION:
+           po_idx = inclusive_flat(PO_FIRST_POTION, PO_LAST_POTION);
+           break;
+       case POCLASS_SCROLL:
+           po_idx = inclusive_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL);
+           break;
+       case POCLASS_RING:
+           po_idx = inclusive_flat(PO_FIRST_RING, PO_LAST_RING);
+           break;
+       default:
+           /* No getting armour/weapons by class... yet. */
+           return -1;
+       }
+       if (zero_die(100) < permobjs[po_idx].rarity)
+       {
+           continue;
+       }
+       break;
+    }
+    objects[obj].obj_id = po_idx;
+    objects[obj].quan = quantity;
+    return obj;
+}
+
+int get_random_pobj(void)
+{
+    int tryct;
+    int po_idx;
+    for (tryct = 0; tryct < 200; tryct++)
+    {
+       po_idx = zero_die(PO_REAL_COUNT);
+       if (zero_die(100) < permobjs[po_idx].rarity)
+       {
+           po_idx = -1;
+           continue;
+       }
+       /* v1.3: Do not permit generation of particularly powerful
+        * items (runeswords, mage armour, etc.) at shallow depths.
+        * (game balance fix) */
+       if (depth < permobjs[po_idx].depth)
+       {
+           po_idx = -1;
+           continue;
+       }
+       break;
+    }
+    return po_idx;
+}
+
+int create_obj(int po_idx, int quantity, int with_you, int y, int x)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+       if (!objects[i].used)
+       {
+           break;
+       }
+    }
+    if (i == 100)
+    {
+       print_msg("ERROR: Ran out of objects[].\n");
+       return -1;
+    }
+    if (po_idx == -1)
+    {
+       po_idx = get_random_pobj();
+       if (po_idx == -1)
+       {
+           return -1;
+       }
+    }
+    objects[i].obj_id = po_idx;
+    objects[i].with_you = with_you;
+    objects[i].used = 1;
+    objects[i].y = y;
+    objects[i].x = x;
+    if (po_idx == PO_GOLD)
+    {
+       objects[i].quan = dice(depth + 1, 20);
+    }
+    else
+    {
+       objects[i].quan = quantity;
+    }
+    switch (permobjs[po_idx].poclass)
+    {
+    case POCLASS_WEAPON:
+    case POCLASS_ARMOUR:
+       /* Set durability of weapons and armour to a suitable value.
+        * 100 has been chosen for the moment, but this may need
+        * tuning. */
+       objects[i].durability = OBJ_MAX_DUR;
+       break;
+    default:
+       break;
+    }
+    if (!objects[i].with_you)
+    {
+       mapobject[y][x] = i;
+    }
+    return i;
+}
+
+void fprint_obj_name(FILE *fp, int obj)
+{
+    struct obj *optr;
+    struct permobj *poptr;
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (poptr->known)
+    {
+       if (optr->quan > 1)
+       {
+           fprintf(fp, "%d %s", optr->quan, poptr->plural);
+       }
+       else if (po_is_stackable(optr->obj_id))
+       {
+           fprintf(fp, "1 %s", poptr->name);
+       }
+       else if ((poptr->poclass == POCLASS_WEAPON) ||
+                (poptr->poclass == POCLASS_ARMOUR))
+       {
+           fprintf(fp, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+       }
+       else
+       {
+           fprintf(fp, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+       }
+    }
+    else
+    {
+       switch (poptr->poclass)
+       {
+       case POCLASS_NONE:
+           fprintf(fp, "a non-thing (%d)", optr->obj_id);
+           break;
+       case POCLASS_FOOD:
+           fprintf(fp, "a mysterious food (%d)", optr->obj_id);
+           break;
+       case POCLASS_WEAPON:
+           fprintf(fp, "a mysterious weapon (%d)", optr->obj_id);
+           break;
+       case POCLASS_ARMOUR:
+           if ((optr->obj_id == PO_ROBE) ||
+               (optr->obj_id == PO_ROBE_SHADOWS) ||
+               (optr->obj_id == PO_ROBE_SWIFTNESS))
+           {
+               fprintf(fp, "a robe");
+           }
+           else
+           {
+               fprintf(fp, "some mysterious armour (%d)", optr->obj_id);
+           }
+           break;
+       case POCLASS_SCROLL:
+           if (optr->quan > 1)
+           {
+               fprintf(fp, "%d scrolls '%s'", optr->quan, scroll_titles[poptr->power]);
+           }
+           else
+           {
+               fprintf(fp, "1 scroll '%s'", scroll_titles[poptr->power]);
+           }
+           break;
+       case POCLASS_POTION:
+           if (optr->quan > 1)
+           {
+               fprintf(fp, "%d %s potions", optr->quan, potion_colours[poptr->power]);
+           }
+           else
+           {
+               fprintf(fp, "1 %s potion", potion_colours[poptr->power]);
+           }
+           break;
+       case POCLASS_RING:
+           fprintf(fp, "a%s %s ring", is_vowel(ring_colours[poptr->power][0]) ? "n" : "", ring_colours[poptr->power]);
+           break;
+       }
+    }
+}
+
+void print_obj_name(int obj)
+{
+    struct obj *optr;
+    struct permobj *poptr;
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (poptr->known)
+    {
+       if (optr->quan > 1)
+       {
+           print_msg("%d %s", optr->quan, poptr->plural);
+       }
+       else if (po_is_stackable(optr->obj_id))
+       {
+           print_msg("1 %s", poptr->name);
+       }
+       else if ((poptr->poclass == POCLASS_WEAPON) ||
+                (poptr->poclass == POCLASS_ARMOUR))
+       {
+           print_msg("a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+       }
+       else
+       {
+           print_msg("a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+       }
+    }
+    else
+    {
+       switch (poptr->poclass)
+       {
+       case POCLASS_NONE:
+           print_msg("a non-thing (%d)", optr->obj_id);
+           break;
+       case POCLASS_FOOD:
+           print_msg("a mysterious food (%d)", optr->obj_id);
+           break;
+       case POCLASS_WEAPON:
+           print_msg("a mysterious weapon (%d)", optr->obj_id);
+           break;
+       case POCLASS_ARMOUR:
+           if ((optr->obj_id == PO_ROBE) ||
+               (optr->obj_id == PO_ROBE_SHADOWS) ||
+               (optr->obj_id == PO_ROBE_SWIFTNESS))
+           {
+               print_msg("a robe");
+           }
+           else
+           {
+               print_msg("some mysterious armour (%d)", optr->obj_id);
+           }
+           break;
+       case POCLASS_SCROLL:
+           if (optr->quan > 1)
+           {
+               print_msg("%d scrolls '%s'", optr->quan, scroll_titles[poptr->power]);
+           }
+           else
+           {
+               print_msg("1 scroll '%s'", scroll_titles[poptr->power]);
+           }
+           break;
+       case POCLASS_POTION:
+           if (optr->quan > 1)
+           {
+               print_msg("%d %s potions", optr->quan, potion_colours[poptr->power]);
+           }
+           else
+           {
+               print_msg("1 %s potion", potion_colours[poptr->power]);
+           }
+           break;
+       case POCLASS_RING:
+           print_msg("a%s %s ring", is_vowel(ring_colours[poptr->power][0]) ? "n" : "", ring_colours[poptr->power]);
+           break;
+       }
+    }
+}
+
+int drop_obj(int inv_idx)
+{
+    struct obj *optr;
+    optr = objects + u.inventory[inv_idx];
+    if (mapobject[u.y][u.x] == -1)
+    {
+       optr->y = u.y;
+       optr->x = u.x;
+       mapobject[u.y][u.x] = u.inventory[inv_idx];
+       if (u.weapon == u.inventory[inv_idx])
+       {
+           u.weapon = -1;
+       }
+       u.inventory[inv_idx] = -1;
+       optr->with_you = 0;
+       print_msg("You drop ");
+       print_obj_name(mapobject[u.y][u.x]);
+       print_msg(".\n");
+       return 0;
+    }
+    else
+    {
+       print_msg("There is already an item here.\n");
+    }
+    return -1;
+}
+
+int po_is_stackable(int po)
+{
+    switch (permobjs[po].poclass)
+    {
+    default:
+       return 0;
+    case POCLASS_POTION:
+    case POCLASS_SCROLL:
+    case POCLASS_FOOD:
+       return 1;
+    }
+}
+
+void attempt_pickup(void)
+{
+    int i;
+    int stackable;
+    if (objects[mapobject[u.y][u.x]].obj_id == PO_GOLD)
+    {
+       print_msg("You get %d gold.\n", objects[mapobject[u.y][u.x]].quan);
+       u.gold += objects[mapobject[u.y][u.x]].quan;
+       objects[mapobject[u.y][u.x]].used = 0;
+       mapobject[u.y][u.x] = -1;
+       return;
+    }
+    stackable = po_is_stackable(objects[mapobject[u.y][u.x]].obj_id);
+    if (stackable)
+    {
+       for (i = 0; i < 19; i++)
+       {
+           if ((objects[u.inventory[i]].obj_id == objects[mapobject[u.y][u.x]].obj_id))
+           {
+               print_msg("You get ");
+               print_obj_name(mapobject[u.y][u.x]);
+               print_msg(".\nYou now have\n");
+               objects[u.inventory[i]].quan += objects[mapobject[u.y][u.x]].quan;
+               objects[mapobject[u.y][u.x]].used = 0;
+               mapobject[u.y][u.x] = -1;
+               print_msg("%c) ", 'a' + i);
+               print_obj_name(u.inventory[i]);
+               print_msg("\n");
+               return;
+           }
+       }
+    }
+    for (i = 0; i < 19; i++)
+    {
+       if (u.inventory[i] == -1)
+       {
+           break;
+       }
+    }
+    if (i == 19)
+    {
+       print_msg("Your pack is full.\n");
+       return;
+    }
+    u.inventory[i] = mapobject[u.y][u.x];
+    mapobject[u.y][u.x] = -1;
+    objects[u.inventory[i]].with_you = 1;
+    objects[u.inventory[i]].x = -1;
+    objects[u.inventory[i]].y = -1;
+    print_msg("You now have\n");
+    print_msg("%c) ", 'a' + i);
+    print_obj_name(u.inventory[i]);
+    print_msg("\n");
+}
+
+void damage_obj(int obj)
+{
+    /* Only weapons and armour have non-zero durability. */
+    if (objects[obj].durability == 0)
+    {
+       /* Break the object. Weapons and armour don't stack. */
+       if (obj == u.weapon)
+       {
+           print_msg("Your weapon breaks!\n");
+       }
+       else if (obj == u.armour)
+       {
+           print_msg("Your armour is ruined!\n");
+       }
+       consume_obj(obj);
+       recalc_defence();
+    }
+    else
+    {
+       objects[obj].durability--;
+    }
+} 
+
+void describe_object(int obj)
+{
+    struct obj *optr;
+    struct permobj *poptr;
+    print_obj_name(obj);
+    optr = objects + obj;
+    poptr = permobjs + optr->obj_id;
+    if (poptr->known)
+    {
+       print_msg("\n%s\n", poptr->description);
+    }
+    else
+    {
+       switch (poptr->poclass)
+       {
+       case POCLASS_NONE:
+       default:
+           print_msg("This unidentified permobj (%d) is indescribable.\n", optr->obj_id);
+           break;
+       case POCLASS_ARMOUR:
+           if ((optr->obj_id == PO_ROBE) ||
+               (optr->obj_id == PO_ROBE_SHADOWS) ||
+               (optr->obj_id == PO_ROBE_SWIFTNESS))
+           {
+               print_msg("\nA simple woolen robe.\n");
+           }
+           else
+           {
+               print_msg("\nAn unidentified and indescribable piece of armour (%d)\n", optr->obj_id);
+           }
+           break;
+       case POCLASS_SCROLL:
+           print_msg("\nA mysterious scroll.\nReading it will unleash its enchantment.\n");
+           break;
+       case POCLASS_POTION:
+           print_msg("\nA rather dubious-looking liquid.\nQuaffing it may be baleful or beneficial.\n");
+           break;
+       case POCLASS_RING:
+           print_msg("\nSome rings are baneful, some are beneficial, and\nsome are junk.\n");
+           break;
+       }
+    }
+}
+
+int evasion_penalty(int obj)
+{
+    switch (objects[obj].obj_id)
+    {
+    case PO_ROBE:
+       return 5;
+
+    case PO_LEATHER_ARMOUR:
+    case PO_DRAGON_ARMOUR:
+       return 10;
+
+    case PO_CHAINMAIL:
+    case PO_SACRED_MAIL:
+       return 25;
+
+    case PO_PLATE_ARMOUR:
+    case PO_MAGE_ARMOUR:
+    case PO_METEOR_ARMOUR:
+       return 40;
+
+    case PO_ROBE_SWIFTNESS:
+       return 0;
+
+    case PO_ROBE_SHADOWS:
+       return -15;     /* This is a bonus. */
+
+    default:
+       /* If you've somehow managed to wear a non-armour, you're abusing
+        * a bug; get a 100% penalty to evasion. */
+       print_msg("Trying to find evasion penalty of non-armour!\n");
+       return 100;
+    }
+}
+
+/* objects.c */
diff --git a/permobj.c b/permobj.c
new file mode 100644 (file)
index 0000000..3dc763c
--- /dev/null
+++ b/permobj.c
@@ -0,0 +1,189 @@
+/* permobj.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define PERMOBJ_C
+#include "dunbash.h"
+
+struct permobj permobjs[NUM_OF_PERMOBJS] = {
+       [PO_DAGGER] =
+       {
+               "dagger", "daggers", "A long knife, designed for stabbing.", POCLASS_WEAPON, 25, ')', 4, 1, 1, 1
+       },
+       [PO_LONG_SWORD] =
+       {
+               "long sword", "long swords", "A steel sword of simple but sturdy construction; the\nblade is three feet long.", POCLASS_WEAPON, 30, ')', 10, 1, 1, 4
+       },
+       [PO_MACE] =
+       {
+               "mace", "maces", "A flanged lump of iron on an iron haft.", POCLASS_WEAPON, 30, ')', 7, 1, 1, 2
+       },
+       [PO_RUNESWORD] =
+       {
+               "runesword", "runeswords", "An eerily glowing sword engraved with many strange\nrunes.", POCLASS_WEAPON, 80, ')', 20, 1, 1, 12
+       },
+       [PO_BOW] =
+       {
+               "bow", "bows", "A recurve composite bow.", POCLASS_WEAPON, 45, '(', 8, 1, 1, 1
+       },
+       [PO_CROSSBOW] =
+       {
+               "crossbow", "crossbows", "A crossbow.", POCLASS_WEAPON, 70, '(', 16, 1, 1, 6
+       },
+       [PO_POT_HEAL] =
+       {
+               "healing potion", "healing potions", "This magic elixir restores some lost hit points.", POCLASS_POTION, 10, '!', 0, 1, 0, 1
+       },
+       [PO_POT_POISON] =
+       {
+               "poison potion", "poison potions", "This liquid is poisonous.", POCLASS_POTION, 10, '!', 0, 1, 0, 1
+       },
+       [PO_POT_BODY] =
+       {
+               "body potion", "body potions", "This magic elixir will improve your physique.", POCLASS_POTION, 70, '!', 0, 1, 0, 5
+       },
+       [PO_POT_AGILITY] =
+       {
+               "agility potion", "agility potions", "This magic elixir will sharpen your reflexes.", POCLASS_POTION, 70, '!', 0, 1, 0, 5
+       },
+       [PO_POT_WEAKNESS] =
+       {
+               "weakness potion", "weakness potions", "This magic elixir causes physical degeneration in\nwhoever drinks it.", POCLASS_POTION, 40, '!', 0, 1, 0, 1
+       },
+       [PO_POT_RESTORATION] =
+       {
+               "restoration potion", "restoration potions", "This magic elixir cures temporary damage to one's\nabilities.", POCLASS_POTION, 70, '!', 0, 1, 0, 1
+       },
+       [PO_SCR_TELEPORT] =
+       {
+               "teleport scroll", "teleport scrolls", "Reading this scroll will teleport you to a random\nlocation.", POCLASS_SCROLL, 40, '?', 0, 1, 0, 1
+       },
+       [PO_SCR_FIRE] =
+       {
+               "fire scroll", "fire scrolls", "Reading this scroll will engulf you in flames.", POCLASS_SCROLL, 30, '?', 0, 1, 0, 1
+       },
+       [PO_SCR_MONSTERS] =
+       {
+               "summoning scroll", "summoning scrolls", "Reading this scroll will summon hostile monsters to\nyour side.", POCLASS_SCROLL, 30, '?', 0, 1, 0, 1
+       },
+       [PO_SCR_IDENTIFY] =
+       {
+               "identify scroll", "identify scrolls", "Reading this scroll will reveal the nature of your\npossessions.", POCLASS_SCROLL, 70, '?', 0, 1, 0, 3
+       },
+       [PO_SCR_AGGRAVATE] =
+       {
+               "aggravating scroll", "aggravating scrolls", "Reading this scroll will awaken every monster on the\nlevel.", POCLASS_SCROLL, 50, '?', 0, 1, 0, 3
+       },
+       [PO_SCR_PROTECTION] =
+       {
+               "protection scroll", "protection scrolls", "Reading this scroll will dispel any curses afflicting\nyou and protect you from curses for a time.", POCLASS_SCROLL, 80, '?', 0, 1, 0, 8
+       },
+       [PO_LEATHER_ARMOUR] =
+       {
+               "leather armour", "suits of leather armour", "A heavy leather jerkin and breeches, providing some\nprotection.", POCLASS_ARMOUR, 25, '[', 3, 1, 1, 1
+       },
+       [PO_CHAINMAIL] =
+       {
+               "chainmail", "suits of chainmail", "A suit of interlocking metal rings, providing better\nprotection than leather.", POCLASS_ARMOUR, 30, '[', 6, 1, 1, 3
+       },
+       [PO_PLATE_ARMOUR] =
+       {
+               "plate armour", "suits of plate armour", "A suit of steel plates, providing better protection than\nchainmail.", POCLASS_ARMOUR, 40, '[', 10, 1, 1, 6
+       },
+       [PO_MAGE_ARMOUR] =
+       {
+               "mage armour", "suits of mage armour", "A suit of glowing steel plates bearing enchantments of\ndefence.", POCLASS_ARMOUR, 70, '[', 15, 1, 1, 12
+       },
+       [PO_ROBE] =
+       {
+               "mundane robe", "mundane robes", "A simple woolen robe. It's better protection than your\nskin, but not by much.", POCLASS_ARMOUR, 50, '[', 2, 1, 0, 1
+       },
+       [PO_ROBE_SWIFTNESS] =
+       {
+               "robe of swiftness", "robes of swiftness", "A simple woolen robe that bears a potent enchantment,\nprotecting the wearer and making him unnaturally swift.", POCLASS_ARMOUR, 70, '[', 8, 1, 0, 8
+       },
+       [PO_ROBE_SHADOWS] =
+       {
+               "robe of shadows", "robes of shadows", "A simple woolen robe that bears an awesome enchantment,\nprotecting the wearer better than steel plate.", POCLASS_ARMOUR, 90, '[', 14, 1, 0, 18
+       },
+       [PO_DRAGON_ARMOUR] =
+       {
+               "dragonhide armour", "suits of dragonhide armour", "The skin of a dragon, formed into a jerkin and breeches;\nit turns blows like steel plate and turns away\nflames.", POCLASS_ARMOUR, 90, '[', 12, 1, 1, 21
+       },
+       [PO_METEOR_ARMOUR] =
+       {
+               "meteoric plate armour", "suits of meteoric plate armour", "This plate armour has been forged out of metal taken from\na fallen star.", POCLASS_ARMOUR, 90, '[', 18, 1, 1, 27
+       },
+       [PO_SACRED_MAIL] =
+       {
+               "sacred chainmail", "suits of sacred chainmail", "This suit of interlocking rings has been consecrated to\nthe gods of the Light.", POCLASS_ARMOUR, 90, '[', 15, 1, 1, 24
+       },
+       [PO_RING_REGEN] =
+       {
+               "regeneration ring", "regeneration rings", "This magical ring increases the wearer's healing rate,\nbut also increases the rate at which they must consume\nfood.", POCLASS_RING, 70, '=', 0, 1, 0, 1
+       },
+       [PO_RING_FIRE] =
+       {
+               "fire ring", "fire rings", "This magical ring protects the wearer from mundane and\nmagical fire, and imbues their blows in combat with the\npower of fire.", POCLASS_RING, 50, '=', 0, 1, 0, 1
+       },
+       [PO_RING_WEDDING] =
+       {
+               "wedding ring", "wedding rings", "This ring is but a simple love-token.", POCLASS_RING, 20, '=', 0, 1, 0, 1
+       },
+       [PO_RING_VAMPIRE] =
+       {
+               "vampire ring", "vampire rings", "This magical ring protects the wearer from necromantic\nenergies, and imbues their blows in combat with such\nenergies as well.", POCLASS_RING, 90, '=', 0, 1, 0, 12
+       },
+       [PO_RING_FROST] =
+       {
+               "frost ring", "frost rings", "This magical ring protects the wearer from mundane and\nmagical cold, and imbues their blows in combat with the\npower of cold.", POCLASS_RING, 40, '=', 0, 1, 0, 1
+       },
+       [PO_RING_DOOM] =
+       {
+               "doom ring", "doom rings", "This accursed ring inflicts great misery on its wearer.\nIt is said that even its removal is painful.", POCLASS_RING, 80, '=', 0, 1, 0, 1
+       },
+       [PO_RING_TELEPORT] =
+       {
+               "teleport ring", "teleport rings", "This magical ring causes the wearer to teleport at\nrandom.", POCLASS_RING, 70, '=', 0, 1, 0, 1
+       },
+       [PO_IRON_RATION] =
+       {
+               "iron ration", "iron rations", "A parcel of hardtack and beef jerky. Dull but nutritious.", POCLASS_FOOD, 75, '%', 0, 1, 1, 1
+       },
+       [PO_DRIED_FRUIT] =
+       {
+               "parcel of dried fruit", "parcels of dried fruit", "A parcel of mixed dried fruit. It sure beats hardtack\nand beef jerky.", POCLASS_FOOD, 75, '%', 0, 1, 1, 1
+       },
+       [PO_ELVEN_BREAD] =
+       {
+               "round of elven waybread", "rounds of elven waybread", "A tasty, filling, nutritious piece of elven waybread.", POCLASS_FOOD, 85, '%', 0, 1, 1, 1
+       },
+       [PO_GOLD] =
+       {
+               "gold piece", "gold pieces", "The wealth of ages. Not that it will do you much good.", POCLASS_NONE, 1, '$', 0, 1, 1, 1
+       },
+};
+
+/* permobj.c */
diff --git a/permons.c b/permons.c
new file mode 100644 (file)
index 0000000..3090416
--- /dev/null
+++ b/permons.c
@@ -0,0 +1,162 @@
+/* permons.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define PERMONS_C
+#include "dunbash.h"
+
+/* This array is no longer necessarily defined in sequential order. Instead,
+ * it has been defined in order of increasing power within "monster groups",
+ * as follows:
+ *   Wild Animals
+ *   Evil Men
+ *   Evil Humanoids
+ *   Undead
+ *   Demonkind
+ *   Supernatural Beasts
+ */
+struct permon permons[NUM_OF_PERMONS] = {
+       [PM_NEWT] =
+       {
+               "newt", "newts", 'n', DBCLR_RED, 20, 1, 3, 0, -1, 2, -1, DT_PHYS, "", 1, 1, 0, PMF_STUPID
+       },
+       [PM_RAT] =
+       {
+               "rat", "rats", 'r', DBCLR_BROWN, 15, 1, 4, 0, -1, 2, -1, DT_PHYS, "", 4, 2, 2, PMF_STUPID
+       },
+       [PM_WOLF] =
+       {
+               "wolf", "wolves", 'c', DBCLR_BROWN, 30, 6, 20, 8, -1, 10, -1, DT_PHYS, "", 6, 15, 2, 0
+       },
+       [PM_SNAKE] =
+       {
+               /* Saps one Body on a really good hit */
+               "snake", "snakes", 's', DBCLR_RED, 20, 6, 15, 10, -1, 3, -1, DT_PHYS, "", 9, 40, 2, PMF_STUPID
+       },
+       [PM_THUG] =
+       {
+               /* may drop a mace or leather armour */
+               "thug", "thugs", 't', DBCLR_BROWN, 30, 1, 8, 5, -1, 5, -1, DT_PHYS, "", 4, 5, 1, 0
+       },
+       [PM_GOON] =
+       {
+               "goon", "goons", 't', DBCLR_YELLOW, 20, 3, 20, 6, -1, 10, -1, DT_PHYS, "", 8, 10, 1, 0
+       },
+       [PM_HUNTER] =
+       {
+               /* Has a ranged attack - arrows */
+               "hunter", "hunters", 'h', DBCLR_GREEN, 30, 9, 40, 6, 20, 6, 10, DT_PHYS, "shoots an arrow", 10, 50, 1, PMF_ARCHER
+       },
+       [PM_DUELLIST] =
+       {
+               "duellist", "duellists", 'f', DBCLR_RED, 40, 12, 60, 30, -1, 15, -1, DT_PHYS, "", 15, 130, 1, PMF_SMART
+       },
+       [PM_WARLORD] =
+       {
+               "warlord", "warlords", 'f', DBCLR_L_RED, 30, 15, 80, 25, -1, 20, -1, DT_PHYS, "", 20, 400, 2, PMF_SMART
+       },
+       [PM_GOBLIN] =
+       {
+               /* may drop a dagger */
+               "goblin", "goblins", 'g', DBCLR_BROWN, 20, 1, 6, 1, -1, 3, -1, DT_PHYS, "", 3, 3, 1, 0
+       },
+       [PM_BAD_ELF] =
+       {
+               "bad elf", "bad elves", 'e', DBCLR_L_GREY, 40, 3, 15, 10, -1, 6, -1, DT_PHYS, "", 8, 15, 2, PMF_SMART
+       },
+       [PM_TROLL] =
+       {
+               "troll", "trolls", 'T', DBCLR_GREEN, 20, 12, 80, 15, -1, 15, -1, DT_PHYS, "", 13, 150, 1, PMF_STUPID
+       },
+       [PM_GIANT] =
+       {
+               "giant", "giants", 'H', DBCLR_BROWN, 20, 21, 80, 15, -1, 25, -1, DT_PHYS, "", 20, 500, 1, PMF_STUPID
+       },
+       [PM_GIANT_JARL] =
+       {
+               "giant jarl", "giant jarls", 'H', DBCLR_L_GREY, 80, 25, 160, 20, -1, 30, -1, DT_PHYS, "", 22, 1000, 1, 0
+       },
+       [PM_WIZARD] =
+       {
+               /* Uses black magic against you; see bmagic.c for details. */
+               "wizard", "wizards", 'w', DBCLR_BLUE, 80, 12, 40, 10, 20, 10, 10, DT_ELEC, "casts", 15, 200, 1, PMF_SMART | PMF_MAGICIAN
+       },
+       [PM_ARCHMAGE] =
+       {
+               /* Uses black magic against you; see bmagic.c for details. */
+               "archmage", "archmagi", 'w', DBCLR_L_BLUE, 80, 24, 80, 15, 30, 15, 15, DT_ELEC, "casts", 15, 1500, 1, PMF_SMART | PMF_MAGICIAN
+       },
+       [PM_ZOMBIE] =
+       {
+               "zombie", "zombies", 'z', DBCLR_L_GREY, 25, 3, 30, 2, -1, 20, -1, DT_PHYS, "", 1, 7, 0, PMF_STUPID | PMF_UNDEAD | PMF_RESIST_COLD
+       },
+       [PM_WRAITH] =
+       {
+               "wraith", "wraiths", 'W', DBCLR_WHITE, 25, 12, 40, 25, -1, 5, -1, DT_PHYS, "", 5, 100, 0, PMF_SMART | PMF_UNDEAD | PMF_RESIST_COLD
+       },
+       [PM_LICH] =
+       {
+               /* Uses black magic against you; see bmagic.c for details. */
+               "lich", "liches", 'L', DBCLR_L_GREY, 70, 15, 70, 15, 25, 15, 15, DT_NECRO, "casts", 15, 250, 1, PMF_SMART | PMF_UNDEAD | PMF_RESIST_COLD | PMF_MAGICIAN
+       },
+       [PM_VAMPIRE] =
+       {
+               /* Vampires heal by hitting you. */
+               "vampire", "vampires", 'V', DBCLR_RED, 55, 18, 70, 25, -1, 15, -1, DT_PHYS, "", 22, 750, 1, PMF_SMART | PMF_UNDEAD | PMF_RESIST_COLD
+       },
+       [PM_MASTER_LICH] =
+       {
+               /* Master liches use black magic against you, more powerfully
+                * than lesser practitioners. */
+               "master lich", "master liches", 'L', DBCLR_PURPLE, 60, 30, 150, 30, 30, 20, 30, DT_NECRO, "", 30, 3000, 1, PMF_SMART | PMF_UNDEAD | PMF_RESIST_COLD | PMF_MAGICIAN
+       },
+       [PM_DEMON] =
+       {
+               /* Demons summon more demons if you don't kill them
+                * quickly. */
+               "demon", "demons", '&', DBCLR_RED, 60, 18, 40, 25, -1, 20, -1, DT_PHYS, "", 15, 500, 1, PMF_SMART | PMF_DEMONIC | PMF_RESIST_FIRE
+       },
+       [PM_DEFILER] =
+       {
+               /* Defilers use black magic against you. */
+               "defiler", "defilers", '&', DBCLR_L_GREEN, 65, 27, 120, 30, 30, 20, 30, DT_FIRE, "", 25, 2000, 1, PMF_SMART | PMF_DEMONIC | PMF_RESIST_FIRE | PMF_MAGICIAN
+       },
+       [PM_CENTAUR] =
+       {
+               "centaur", "centaurs", 'C', DBCLR_BROWN, 30, 9, 40, 15, -1, 10, -1, DT_PHYS, "", 10, 50, 2, 0
+       },
+       [PM_ICE_MONSTER] =
+       {
+               /* Fires ice blasts. */
+               "ice monster", "ice monsters", 'I', DBCLR_WHITE, 50, 6, 40, 10, 20, 15, 15, DT_COLD, "launches a blast of", 10, 35, 0, PMF_RESIST_COLD | PMF_ARCHER
+       },
+       [PM_DRAGON] =
+       {
+               /* Breathes fire. */
+               "dragon", "dragons", 'D', DBCLR_RED, 50, 15, 80, 20, 20, 20, 20, DT_FIRE, "breathes", 18, 300, 1, PMF_RESIST_FIRE | PMF_ARCHER
+       },
+};
+
+/* permons.c */
diff --git a/pmon2.c b/pmon2.c
new file mode 100644 (file)
index 0000000..8aa8938
--- /dev/null
+++ b/pmon2.c
@@ -0,0 +1,66 @@
+/* pmon2.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define PMON2_C
+#include "dunbash.h"
+#include "monsters.h"
+
+int pmon_resists_fire(int pm)
+{
+       return !!(permons[pm].flags & PMF_RESIST_FIRE);
+}
+
+int pmon_resists_cold(int pm)
+{
+       return !!(permons[pm].flags & PMF_RESIST_COLD);
+}
+
+int pmon_is_undead(int pm)
+{
+       return !!(permons[pm].flags & PMF_UNDEAD);
+}
+
+int pmon_is_stupid(int pm)
+{
+       return !!(permons[pm].flags & PMF_STUPID);
+}
+
+int pmon_is_smart(int pm)
+{
+       return !!(permons[pm].flags & PMF_SMART);
+}
+
+int pmon_is_magician(int pm)
+{
+       return !!(permons[pm].flags & PMF_MAGICIAN);
+}
+
+int pmon_is_archer(int pm)
+{
+       return !!(permons[pm].flags & PMF_ARCHER);
+}
+
+/* pmon2.c */
diff --git a/rng.c b/rng.c
new file mode 100644 (file)
index 0000000..9fc0dcf
--- /dev/null
+++ b/rng.c
@@ -0,0 +1,74 @@
+/* rng.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "dunbash.h"
+#include <time.h>
+#include <unistd.h>
+
+/* FIX mpr v1.1 2005.02.06: Changed type of RNG stuff to "unsigned int" to
+ * prevent malformed behaviour on x86-64 (where long int is 64 bits). */
+unsigned int rng_state[5];
+unsigned int saved_state[5];
+
+unsigned int rng(void)
+{
+       unsigned int tmp;
+       tmp = rng_state[0] ^ (rng_state[0] >> 7);
+       rng_state[0] = rng_state[1];
+       rng_state[1] = rng_state[2];
+       rng_state[2] = rng_state[3];
+       rng_state[3] = rng_state[4];
+       rng_state[4] = (rng_state[4] ^ (rng_state[4] << 6)) ^ (tmp ^ (tmp << 13));
+       return (rng_state[2] + rng_state[2] + 1) * rng_state[4];
+}
+
+void rng_init(void)
+{
+       int i;
+       /* To make manipulating the RNG by monitoring the system time
+        * harder, we use PID and UID to perturb the return value of time()
+        * used to initialise the libc RNG.
+        *
+        * Yes, I am aware that the libc RNG on many platforms is a steaming
+        * pile of shite. However, I need *something* with which to populate
+        * my RNG's state array.
+        */
+       srand(time(NULL) ^ getpid() ^ (getuid() << 16));
+       rng_state[0] = rand();
+       rng_state[1] = rand();
+       rng_state[2] = rand();
+       rng_state[3] = rand();
+       rng_state[4] = rand();
+       /* Flush through the first 100 numbers just in case some bastard
+        * tries to run us with a 16-bit rand(). */
+       for (i = 0; i < 100; i++)
+       {
+               rng();
+       }
+}
+
+/* rng.c */
diff --git a/u.c b/u.c
new file mode 100644 (file)
index 0000000..f249e7b
--- /dev/null
+++ b/u.c
@@ -0,0 +1,677 @@
+/* u.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+#include "combat.h"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+
+struct player u;
+
+void recalc_defence(void)
+{
+    int i;
+    for (i = 0; i < DT_COUNT; i++)
+    {
+       u.resistances[i] &= RESIST_MASK_TEMPORARY;
+    }
+    u.speed = (u.leadfoot ? 0 : 1);
+    if (u.armour != -1)
+    {
+       u.defence = u.armourmelt ? 0 : permobjs[objects[u.armour].obj_id].power;
+       u.defence += u.withering ? (u.agility / 10) : (u.agility / 5);
+       switch (objects[u.armour].obj_id)
+       {
+       case PO_DRAGON_ARMOUR:
+       case PO_METEOR_ARMOUR:
+           u.resistances[DT_FIRE] |= RESIST_ARMOUR;
+           break;
+       case PO_ROBE_SWIFTNESS:
+           u.speed++;
+           break;
+       default:
+           break;
+       }
+    }
+    else
+    {
+       u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
+    }
+    if (u.ring != -1)
+    {
+       switch (objects[u.ring].obj_id)
+       {
+       case PO_RING_FIRE:
+           u.resistances[DT_FIRE] |= RESIST_RING;
+           break;
+       case PO_RING_FROST:
+           u.resistances[DT_COLD] |= RESIST_RING;
+           break;
+       case PO_RING_VAMPIRE:
+           u.resistances[DT_NECRO] |= RESIST_RING;
+           break;
+       }
+    }
+    status_updated = 1;
+    display_update();
+}
+
+int move_player(int dy, int dx)
+{
+    if ((u.y + dy < 0) || (u.y + dy >= DUN_HEIGHT) ||
+       (u.x + dx < 0) || (u.x + dx >= DUN_WIDTH))
+    {
+       print_msg("Attempted move out of bounds.\n");
+       return 0;       /* No movement. */
+    }
+    if (mapmonster[u.y + dy][u.x + dx] != -1)
+    {
+       if (u.weapon != -1)
+       {
+           if ((objects[u.weapon].obj_id == PO_BOW) ||
+               (objects[u.weapon].obj_id == PO_CROSSBOW))
+           {
+               print_msg("You can't use that weapon in melee!\n");
+               return 0;
+           }
+       }
+       return player_attack(dy, dx);
+    }
+    switch (terrain[u.y + dy][u.x + dx])
+    {
+    case WALL:
+       print_msg("You cannot go there.\n");
+       return 0;
+    case FLOOR:
+    case DOOR:
+    case STAIRS:
+       reloc_player(u.y + dy, u.x + dx);
+       return 1;
+    }
+    return 0;
+}
+
+int reloc_player(int y, int x)
+{
+    int y2, x2;
+    int oy, ox;
+
+    oy = u.y;
+    ox = u.x;
+    u.y = y;
+    u.x = x;
+    newsym(oy, ox);
+    newsym(u.y, u.x);
+    if ((roomnums[oy][ox] != roomnums[y][x]) &&
+       (roomnums[oy][ox] != -1))
+    {
+       for (y2 = roombounds[roomnums[oy][ox]][0]; y2 <= roombounds[roomnums[oy][ox]][1]; y2++)
+       {
+           for (x2 = roombounds[roomnums[oy][ox]][2]; x2 <= roombounds[roomnums[oy][ox]][3]; x2++)
+           {
+               newsym(y2, x2);
+           }
+       }
+    }
+    if (roomnums[y][x] != -1)
+    {
+       for (y2 = roombounds[roomnums[y][x]][0]; y2 <= roombounds[roomnums[y][x]][1]; y2++)
+       {
+           for (x2 = roombounds[roomnums[y][x]][2]; x2 <= roombounds[roomnums[y][x]][3]; x2++)
+           {
+               if (!(mapflags[y2][x2] & MAPFLAG_EXPLORED))
+               {
+                   mapflags[y2][x2] |= MAPFLAG_EXPLORED;
+               }
+               newsym(y2, x2);
+           }
+       }
+    }
+    for (y2 = y - 1; y2 <= y + 1; y2++)
+    {
+       if ((y2 < 0) || (y2 >= DUN_HEIGHT))
+       {
+           continue;
+       }
+       for (x2 = x - 1; x2 <= x + 1; x2++)
+       {
+           if ((x2 < 0) || (x2 >= DUN_WIDTH))
+           {
+               continue;
+           }
+           if (!(mapflags[y2][x2] & MAPFLAG_EXPLORED))
+           {
+               mapflags[y2][x2] |= MAPFLAG_EXPLORED;
+           }
+           newsym(y2, x2);
+       }
+    }
+    map_updated = 1;
+    status_updated = 1;
+    if (mapobject[y][x] != -1)
+    {
+       print_msg("You see here ");
+       print_obj_name(mapobject[y][x]);
+       print_msg(".\n");
+    }
+    display_update();
+    return 0;
+}
+
+int gain_body(int amount, int loud)
+{
+    if (amount < 1)
+    {
+       print_msg("Absurd body gain %d\n", amount);
+    }
+    if (u.body < 99)
+    {
+       if (u.body + amount > 99)
+       {
+           amount = 99 - u.body;
+       }
+       u.body += amount;
+       status_updated = 1;
+       if (loud)
+       {
+           print_msg("You feel stronger!\n");
+       }
+       else
+       {
+           display_update();
+       }
+       return amount;
+    }
+    else
+    {
+       print_msg("You feel disappointed.\n");
+       return 0;
+    }
+}
+
+int drain_body(int amount, const char *what, int permanent)
+{
+    print_msg("You feel weaker!\n");
+    if (permanent)
+    {
+       u.body -= amount;
+    }
+    else
+    {
+       u.bdam += amount;
+    }
+    status_updated = 1;
+    if ((u.body - u.bdam) < 0)
+    {
+       print_msg("Your heart is too weak to beat.\n");
+       return do_death(DEATH_BODY, what);
+    }
+    display_update();
+    return 0;
+}
+
+int gain_agility(int amount, int loud)
+{
+    if (amount < 1)
+    {
+       print_msg("Absurd agility gain %d\n", amount);
+    }
+    if (u.agility < 99)
+    {
+       if (u.agility + amount > 99)
+       {
+           amount = 99 - u.agility;
+       }
+       u.agility += amount;
+       status_updated = 1;
+       recalc_defence();
+       if (loud)
+       {
+           print_msg("You feel more agile!\n");
+       }
+       else
+       {
+           display_update();
+       }
+       return amount;
+    }
+    else
+    {
+       print_msg("You feel disappointed.\n");
+       return 0;
+    }
+}
+
+int drain_agility(int amount, const char *what, int permanent)
+{
+    print_msg("You feel clumsy!\n");
+    if (permanent)
+    {
+       u.agility -= amount;
+    }
+    else
+    {
+       u.adam += amount;
+    }
+    status_updated = 1;
+    if ((u.agility - u.adam) < 0)
+    {
+       print_msg("You forget how to breathe.\n");
+       return do_death(DEATH_AGILITY, what);
+    }
+    recalc_defence();
+    return 0;
+}
+
+int damage_u(int amount, enum death d, const char *what)
+{
+    u.hpcur -= amount;
+    status_updated = 1;
+    if (u.hpcur < 0)
+    {
+       u.hpcur = 0;
+       return do_death(d, what);
+    }
+    return 0;
+}
+
+void heal_u(int amount, int boost, int loud)
+{
+    if (u.hpcur + amount > u.hpmax)
+    {
+       if (boost)
+       {
+           u.hpmax++;
+       }
+       amount = u.hpmax - u.hpcur;
+    }
+    u.hpcur += amount;
+    /* Touch the status line */
+    status_updated = 1;
+    if (loud)
+    {
+       /* Tell the player how much better he feels. */
+       if (u.hpcur == u.hpmax)
+       {
+           print_msg("You feel great.\n");
+       }
+       else
+       {
+           print_msg("You feel %sbetter.\n", amount > 10 ? "much " : "");
+       }
+    }
+    else
+    {
+       /* Update the display. */
+       display_update();
+    }
+    return;
+}
+
+int do_death(enum death d, const char *what)
+{
+    FILE *fp;
+    int really = 0;
+
+    if (wizard_mode)
+    {
+       really = getyn("Really die? ");
+       if (really != 1)
+       {
+           u.hpcur = u.hpmax;
+           u.adam = 0;
+           u.bdam = 0;
+           status_updated = 1;
+           print_msg("You survived that attempt on your life.");
+           return 0;
+       }
+    }
+    if (!wizard_mode)
+    {
+       fp = fopen("dunbash.log", "a");
+    }
+    fprintf(fp, "%s, ", u.name);
+    print_msg("THOU ART SLAIN!\n");
+    game_finished = 1;
+    switch (d)
+    {
+    case DEATH_KILLED:
+       print_msg("You were killed by %s.\n", what);
+       if (!wizard_mode)
+       {
+           fprintf(fp, "you were killed by %s.\n", what);
+       }
+       break;
+    case DEATH_KILLED_MON:
+       print_msg("You were killed by a nasty %s.\n", what);
+       if (!wizard_mode)
+       {
+           fprintf(fp, "you were killed by a nasty %s.\n", what);
+       }
+       break;
+    case DEATH_BODY:
+       print_msg("Your heart was stopped by %s.\n", what);
+       if (!wizard_mode)
+       {
+           fprintf(fp, "your heart was stopped by %s.\n", what);
+       }
+       break;
+    case DEATH_AGILITY:
+       print_msg("Your nerves were destroyed by %s.\n", what); 
+       if (!wizard_mode)
+       {
+           fprintf(fp, "your nerves were destroyed by %s.\n", what); 
+       }
+       break;
+    }
+    if (!wizard_mode)
+    {
+       fprintf(fp, "You died after %d ticks, with %d XP and %d gold, on dungeon level %d.\n\n", game_tick, u.experience, u.gold, depth);
+       fflush(fp);
+       fclose(fp);
+    }
+    print_msg("Your game lasted %d ticks.\n", game_tick);
+    print_msg("You killed monsters worth %d experience.\n", u.experience);
+    print_msg("You found %d pieces of gold.\n", u.gold);
+
+    return 1;
+}
+
+void write_char_dump(void)
+{
+    FILE *fp;
+    char filename[32];
+    int i;
+    snprintf(filename, 31, "%s.dump", u.name);
+    fp = fopen(filename, "w");
+    if (fp == NULL)
+    {
+       print_msg("Couldn't create dump file. Dump failed.\n");
+       return;
+    }
+    fprintf(fp, "%s, level %d adventurer (%d XP)\n", u.name, u.level, u.experience);
+    fprintf(fp, "%d gold pieces collected.\n", u.gold);
+    fprintf(fp, "%d of %d hit points.\n", u.hpcur, u.hpmax);
+    fprintf(fp, "Body %d (%d damage).\n", u.body, u.bdam);
+    fprintf(fp, "Agility %d (%d damage).\n", u.agility, u.adam);
+    fprintf(fp, "Defence %d.\n", u.defence);
+    fprintf(fp, "Inventory:\n");
+    for (i = 0; i < 19; i++)
+    {
+       if (u.inventory[i] != -1)
+       {
+           fprint_obj_name(fp, u.inventory[i]);
+           fputc('\n', fp);
+       }
+    }
+    fflush(fp);
+    fclose(fp);
+}
+
+void u_init(void)
+{
+    char * hasslash = NULL;
+    u.name[16] = '\0';
+    do {
+       print_msg("What is your name, stranger?\n");
+       read_input(u.name, 16);
+       hasslash = strchr(u.name, '/');
+       /* Now that we create a named dump file, we must not
+        * permit the player's name to contain a slash, colon,
+        * or backslash. */
+       if (hasslash)
+       {
+           print_msg("No slashes permitted.\n");
+           continue;
+       }
+       hasslash = strchr(u.name, '\\');
+       if (hasslash)
+       {
+           print_msg("No backslashes permitted.\n");
+           continue;
+       }
+       hasslash = strchr(u.name, ':');
+       if (hasslash)
+       {
+           print_msg("No colons permitted.\n");
+           continue;
+       }
+    } while (hasslash != NULL);
+    u.body = 10;
+    u.bdam = 0;
+    u.agility = 10;
+    u.adam = 0;
+    u.hpmax = 20;
+    u.hpcur = 20;
+    u.experience = 0;
+    u.level = 1;
+    u.food = 2000;
+    memset(u.inventory, -1, sizeof u.inventory);
+    u.inventory[0] = create_obj(PO_DAGGER, 1, 1, -1, -1);
+    if (u.inventory[0] == -1)
+    {
+       print_msg("Couldn't create dagger!\n");
+    }
+    u.inventory[1] = create_obj(PO_IRON_RATION, 1, 1, -1, -1);
+    u.weapon = u.inventory[0];
+    u.ring = -1;
+    u.armour = -1;
+    recalc_defence();
+}
+
+int lev_threshold(int level)
+{
+    if (level < 10)
+    {
+       return 20 * (1 << (level - 1));
+    }
+    if (level < 20)
+    {
+       return 10000 * (level - 9);
+    }
+    if (level < 30)
+    {
+       return 100000 * (level - 18);
+    }
+    return INT_MAX;
+}
+
+void gain_experience(int amount)
+{
+    int hpgain;
+    int bodygain;
+    int agilgain;
+    u.experience += amount;
+    status_updated = 1;
+    if (u.experience > lev_threshold(u.level))
+    {
+       u.level++;
+       print_msg("You gained a level!\n");
+       if (!zero_die(2))
+       {
+           bodygain = gain_body(2, 0);
+           agilgain = gain_agility(1, 0);
+       }
+       else
+       {
+           bodygain = gain_body(1, 0);
+           agilgain = gain_agility(2, 0);
+       }
+       print_msg("You gained %d body and %d agility.\n", bodygain, agilgain);
+       hpgain = u.body / 10 + 10;
+       if (u.hpmax + hpgain > 999)
+       {
+           hpgain = 999 - u.hpmax;
+       }
+       if (hpgain > 0)
+       {
+           /* v1.3: Policy change - gaining a level effectively
+            * heals you. */
+           u.hpcur += hpgain;
+           u.hpmax += hpgain;
+           status_updated = 1;
+           print_msg("You gained %d hit points.\n", hpgain);
+       }
+       press_enter();
+    }
+    else
+    {
+       display_update();
+    }
+}
+
+int teleport_u(void)
+{
+    int room_try;
+    int cell_try;
+    int room;
+    int y, x;
+    for (room_try = 0; room_try < MAX_ROOMS * 4; room_try++)
+    {
+       room = zero_die(MAX_ROOMS);
+       for (cell_try = 0; cell_try < 200; cell_try++)
+       {
+           y = exclusive_flat(roombounds[room][0], roombounds[room][1]);
+           x = exclusive_flat(roombounds[room][2], roombounds[room][3]);
+           if ((mapmonster[y][x] == -1) && (terrain[y][x] == FLOOR) && ((y != u.y) || (x != u.x)))
+           {
+               print_msg("You are whisked away!\n");
+               reloc_player(y, x);
+               return 0;
+           }
+       }
+    }
+    print_msg("You feel briefly dislocated.\n");
+    return -1;
+}
+
+void update_player(void)
+{
+    if (!(game_tick % 5) && (u.food >= 0) && (u.hpcur < u.hpmax))
+    {
+       /* Heal player for one hit point; do not allow HP gain,
+        * and don't say anything. */
+       heal_u(1, 0, 0);
+    }
+    else if (!(game_tick % 60) && (u.hpcur < u.hpmax * 3 / 4))
+    {
+       /* Hungry player heals much, much slower, and cannot regain
+        * all their hit points. */
+       heal_u(1, 0, 0);
+    }
+    /* Once you hit the nutrition endstop, your ring of regeneration stops
+     * working, and like normal regen, it won't raise you above 75% HP if
+     * your food counter is negative. */
+    if (((game_tick % 10) == 5) &&
+       (objects[u.ring].obj_id == PO_RING_REGEN) &&
+       (u.hpcur < ((u.food >= 0) ? u.hpmax : ((u.hpmax * 3) / 4))) &&
+       (u.food >= -1950))
+    {
+       /* Heal player for 1d3 hit points; do not allow HP gain,
+        * and don't say anything apart from the regen ring message. */
+       print_msg("Your ring pulses soothingly.\n");
+       heal_u(one_die(3), 0, 0);
+       permobjs[PO_RING_REGEN].known = 1;
+    }
+    if (u.food >= -1950)
+    {
+       int food_use = 1;
+       int squeal = 0;
+       if ((objects[u.ring].obj_id == PO_RING_REGEN) && !(game_tick % 2) && (u.food >= -1950))
+       {
+           /* If you are still less hungry than -1950 nutrition,
+            * use one more food every second tick if you are
+            * wearing a ring of regeneration. */
+           food_use++;
+       }
+       if ((u.food >= 100) && (u.food - food_use < 100))
+       {
+           squeal = 1;
+       }
+       if ((u.food >= 0) && (u.food < food_use))
+       {
+           squeal = 2;
+       }
+       u.food -= food_use;
+       status_updated = 1;
+       switch (squeal)
+       {
+       case 0:
+       default:
+           break;
+       case 1:
+           print_msg("You are getting quite hungry.\n");
+           break;
+       case 2:
+           print_msg("You are feeling hunger pangs, and will recover\nmore slowly from your injuries.\n");
+           break;
+       }
+    }
+    if (u.leadfoot > 0)
+    {
+       u.leadfoot--;
+       if (!u.leadfoot)
+       {
+           print_msg("You shed your feet of lead.\n");
+           recalc_defence();
+       }
+    }
+    if (u.armourmelt > 0)
+    {
+       u.armourmelt--;
+       if (!u.armourmelt)
+       {
+           print_msg("Your armour seems solid once more.\n");
+           recalc_defence();
+       }
+    }
+    if (u.withering > 0)
+    {
+       u.withering--;
+       if (!u.withering)
+       {
+           print_msg("Your limbs straighten.\n");
+           recalc_defence();
+       }
+    }
+    if (u.protection > 0)
+    {
+       u.protection--;
+       if (!u.protection)
+       {
+           print_msg("You feel like you are no longer being helped.\n");
+       }
+    }
+    display_update();
+}
+
+int player_resists_dtype(enum damtyp dtype)
+{
+    return u.resistances[dtype];
+}
+
+/* u.c */
diff --git a/vector.c b/vector.c
new file mode 100644 (file)
index 0000000..32b0f65
--- /dev/null
+++ b/vector.c
@@ -0,0 +1,62 @@
+/* vector.c
+ * 
+ * Copyright 2005 Martin Read
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dunbash.h"
+
+void compute_directions(int y1, int x1, int y2, int x2, int *pdy, int *pdx, int *psy, int *psx, int *pmelee, int *pcardinal)
+{
+       int dy, dx, sy, sx;
+       dy = y1 - y2;
+       dx = x1 - x2;
+       sy = dy > 0 ? 1 : (dy < 0 ? -1 : 0);
+       sx = dx > 0 ? 1 : (dx < 0 ? -1 : 0);
+       if (pdy)
+       {
+               *pdy = dy;
+       }
+       if (pdx)
+       {
+               *pdx = dx;
+       }
+       if (psy)
+       {
+               *psy = sy;
+       }
+       if (psx)
+       {
+               *psx = sx;
+       }
+       if (pmelee)
+       {
+               *pmelee = (dy < 2) && (dy > -2) && (dx < 2) && (dx > -2);
+       }
+       if (pcardinal)
+       {
+               *pcardinal = (dy == dx) || (dy == -dx) || (dx == 0) || (dy == 0);
+       }
+}
+
+/* vector.c */