--- /dev/null
+*.o
+cavechop
--- /dev/null
+# 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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
+
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+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
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */