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