From d992fae7b21b9db728a60475cbdc837b352d127c Mon Sep 17 00:00:00 2001 From: Martin Read Date: Fri, 20 Sep 2013 21:26:27 +0100 Subject: [PATCH] Creating depot --- .gitignore | 9 + MANIFEST | 21 ++ Makefile | 57 ++++ bmagic.c | 487 ++++++++++++++++++++++++++++++ bmagic.h | 60 ++++ combat.c | 508 ++++++++++++++++++++++++++++++++ combat.h | 48 +++ configure | 128 ++++++++ display-nc.c | 801 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 639 ++++++++++++++++++++++++++++++++++++++++ map.c | 272 +++++++++++++++++ misc.c | 39 +++ mon2.c | 877 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ monsters.c | 501 +++++++++++++++++++++++++++++++ monsters.h | 70 +++++ notes.txt | 77 +++++ objects.c | 805 ++++++++++++++++++++++++++++++++++++++++++++++++++ permobj.c | 191 ++++++++++++ permons.c | 153 ++++++++++ pmon2.c | 82 ++++++ rng.c | 74 +++++ u.c | 684 +++++++++++++++++++++++++++++++++++++++++++ vector.c | 65 +++++ victrix-abyssi.h | 381 ++++++++++++++++++++++++ 24 files changed, 7029 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST create mode 100644 Makefile create mode 100644 bmagic.c create mode 100644 bmagic.h create mode 100644 combat.c create mode 100644 combat.h create mode 100755 configure create mode 100644 display-nc.c create mode 100644 main.c create mode 100644 map.c create mode 100644 misc.c create mode 100644 mon2.c create mode 100644 monsters.c create mode 100644 monsters.h create mode 100644 notes.txt create mode 100644 objects.c create mode 100644 permobj.c create mode 100644 permons.c create mode 100644 pmon2.c create mode 100644 rng.c create mode 100644 u.c create mode 100644 vector.c create mode 100644 victrix-abyssi.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd8f08a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.o +*.sw? +victrix-abyssi +*.tgz +*.tar.gz +*.mk +victrix-abyssi.log +victrix-abyssi.sav +victrix-abyssi.sav.gz diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..af9dcda --- /dev/null +++ b/MANIFEST @@ -0,0 +1,21 @@ +MANIFEST +Makefile +bmagic.c +bmagic.h +combat.c +combat.h +display-nc.c +main.c +map.c +misc.c +mon2.c +monsters.c +monsters.h +objects.c +permobj.c +permons.c +pmon2.c +rng.c +u.c +vector.c +victrix-abyssi.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e4fcc3b --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +# Makefile for Victrix Abyssi + +OBJS=bmagic.o combat.o display-nc.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 + +include dirs.mk +include features.mk + +GAME=victrix-abyssi +MAJVERS=1 +MINVERS=0 +PRODUCTION_CFLAGS=-c -Wall -Wstrict-prototypes -Wwrite-strings -Wmissing-prototypes -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) +DEVELOPMENT_CFLAGS=-c -g -Wall -Wstrict-prototypes -Wwrite-strings -Wmissing-prototypes -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -Werror -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) +CFLAGS=$(PRODUCTION_CFLAGS) +LINKFLAGS=-lpanelw -lncursesw -g +ARCHIVEDIR=victrix-abyssi + +all: $(GAME) + +$(GAME): $(OBJS) + $(CC) $(OBJS) $(LINKFLAGS) -o $(GAME) + +archive: clean + mkdir $(ARCHIVEDIR) + cp `cat MANIFEST` $(ARCHIVEDIR) + tar czf $(ARCHIVEDIR).tgz $(ARCHIVEDIR) + rm -r $(ARCHIVEDIR) + +clean: + -rm -f *.o $(GAME) victrix-abyssi.log victrix-abyssi.sav.gz *.tgz + +display-nc.o: display-nc.c victrix-abyssi.h + +main.o: main.c combat.h victrix-abyssi.h monsters.h + +combat.o: combat.c combat.h victrix-abyssi.h + +u.o: u.c combat.h victrix-abyssi.h + +permobj.o: permobj.c victrix-abyssi.h + +map.o: map.c victrix-abyssi.h + +permons.o: permons.c victrix-abyssi.h + +pmon2.o: pmon2.c victrix-abyssi.h monsters.h + +objects.o: objects.c victrix-abyssi.h + +monsters.o: monsters.c victrix-abyssi.h monsters.h + +mon2.o: mon2.c victrix-abyssi.h bmagic.h monsters.h + +vector.o: vector.c victrix-abyssi.h + +bmagic.o: bmagic.c victrix-abyssi.h bmagic.h + +# vim:ts=8:sw=8:noexpandtab diff --git a/bmagic.c b/bmagic.c new file mode 100644 index 0000000..235c5ec --- /dev/null +++ b/bmagic.c @@ -0,0 +1,487 @@ +/*! \file bmagic.c + * \brief evil magic used by monsters + */ + +/* Copyright 2005-2012 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 "victrix-abyssi.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 = mon_visible(mon); + 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 = ((mptr->next_summon < game_tick) || !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: + mptr->next_summon = game_tick + 1000; + /* 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 */ +// vim:cindent diff --git a/bmagic.h b/bmagic.h new file mode 100644 index 0000000..c45c321 --- /dev/null +++ b/bmagic.h @@ -0,0 +1,60 @@ +/* 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 + +/* bmagic.h */ +// vim:cindent diff --git a/combat.c b/combat.c new file mode 100644 index 0000000..8016bc3 --- /dev/null +++ b/combat.c @@ -0,0 +1,508 @@ +/*! \file combat.c + * \brief combat mechanics + */ + +/* Copyright 2005-2013 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 "victrix-abyssi.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; + 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; + switch (ring->obj_id) + { + case PO_RING_FIRE: + if (!pmon_resists_fire(mp->mon_id)) + { + 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)) + { + 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)) + { + 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"); + } + break; + case DT_COLD: + print_msg("Its touch seems pleasantly cool.\n"); + if (unaffected & RESIST_RING) + { + print_msg("Your ring flashes blue.\n"); + } + break; + case DT_NECRO: + print_msg("Its touch makes you feel no deader.\n"); + if (objects[u.ring].obj_id == PO_RING_VAMPIRE) + { + 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) + { + if (unaffected & RESIST_RING) + { + switch (dtype) + { + case DT_COLD: + if (objects[u.ring].obj_id == PO_RING_FROST) + { + print_msg("Your ring flashes blue.\n"); + } + break; + case DT_FIRE: + if (objects[u.ring].obj_id == PO_RING_FIRE) + { + print_msg("Your ring flashes red.\n"); + } + break; + case DT_NECRO: + if (objects[u.ring].obj_id == PO_RING_VAMPIRE) + { + 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; +} + +int throw_flask(int obj, int sy, int sx) +{ + int i; + int y, x; + for (i = 0, y = u.y, x = u.x; i < 10; ++i) + { + y += sy; + x += sx; + if ((mapmonster[y][x] != -1) && + (monsters[mapmonster[y][x]].used)) + { + struct mon *mptr = monsters + mapmonster[y][x]; + switch (objects[obj].obj_id) + { + case PO_FLASK_WEAKNESS: + if (!pmon_is_undead(mptr->mon_id)) + { + print_msg("Your foe shrivels and twists horribly.\n"); + mptr->hpmax = (mptr->hpmax + 1) / 2; + mptr->hpcur = (mptr->hpcur + 1) / 2; + } + else + { + print_msg("What does not live cannot degenerate.\n"); + } + break; + case PO_FLASK_POISON: + print_msg("Your foe is drenched in contact poison.\n"); + if (!pmon_resists_poison(mptr->mon_id)) + { + damage_mon(mptr - monsters, inclusive_flat((mptr->hpmax + 5) / 6, (mptr->hpmax + 2) / 3), 1); + } + else + { + print_msg("... to little effect.\n"); + } + break; + case PO_FLASK_FIRE: + print_msg("Your foe is drenched in burning oil.\n"); + if (!pmon_resists_fire(mptr->mon_id)) + { + damage_mon(mptr - monsters, 15 + dice(5, 4), 1); + } + else + { + print_msg("... to little effect.\n"); + } + break; + default: + print_msg("internal error: attempt to throw non-flask.\n"); + return 0; + } + consume_obj(obj); + return 1; + } + } + return 1; +} + +/* combat.c */ +// vim:cindent diff --git a/combat.h b/combat.h new file mode 100644 index 0000000..51a34d0 --- /dev/null +++ b/combat.h @@ -0,0 +1,48 @@ +/* combat.h + * + * Copyright 2005-2012 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 VICTRIX_ABYSSI_H +#include "victrix-abyssi.h" +#endif + +#include "monsters.h" + +#define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5)) +/* XXX combat.c data and funcs */ +extern int throw_flask(int obj, int sy, int sx); +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 */ +// vim:cindent diff --git a/configure b/configure new file mode 100755 index 0000000..3cbb65e --- /dev/null +++ b/configure @@ -0,0 +1,128 @@ +#! /usr/bin/perl -w + +use strict; +use Getopt::Long; +use English; +# Prefixes +my $prefix='/usr/local'; +my $exec_prefix='$(prefix)'; + +# Bin and lib dirs +my $bindir='$(exec_prefix)/bin'; +my $sbindir='$(exec_prefix)/sbin'; +my $libdir='$(exec_prefix)/lib'; +my $libexecdir='$(exec_prefix)/libexec'; + +my $datarootdir='$(prefix)/share'; +my $datadir='$(datarootdir)/victrix-abyssi'; +my $sysconfdir='$(prefix)/etc'; +my $sharedstatedir='$(prefix)/com'; +my $localstatedir='$(prefix)/var'; + +my $includedir='$(prefix)/include'; +my $oldincludedir='/usr/include'; + +my $docdir='$(datarootdir)/doc/victrix-abyssi'; +my $htmldir='$(docdir)'; +my $dvidir='$(docdir)'; +my $pdfdir='$(docdir)'; +my $psdir='$(docdir)'; + +my $localedir='$(datarootdir)/locale'; + +my $infodir='$(datarootdir)/info'; + +my $mandir='$(datarootdir)/man'; +my $man1dir='$(mandir)/man1'; +my $man3dir='$(mandir)/man3'; +my $man5dir='$(mandir)/man5'; +my $man6dir='$(mandir)/man6'; +my $man7dir='$(mandir)/man7'; +my $man1ext='.1'; +my $man3ext='.3'; +my $man5ext='.5'; +my $man6ext='.6'; +my $man7ext='.7'; + +# feature control +my $disable_statics='false'; + +my $verbose=1; +GetOptions( +'prefix=s' => \$prefix, 'exec_prefix=s' => \$exec_prefix, +'bindir=s' => \$bindir, 'libdir=s' => \$libdir, +'includedir=s' => \$includedir, 'oldincludedir=s' => \$oldincludedir, +'datarootdir=s' => \$datarootdir, 'datadir=s' => \$datadir, +'sysconfdir=s' => \$sysconfdir, 'sharedstatedir=s' => \$sharedstatedir, +'localstatedir=s' => \$localstatedir, 'localedir=s' => \$localedir, +'mandir=s' => \$mandir, +'man1dir=s' => \$man1dir, 'man3dir=s' => \$man3dir, +'man5dir=s' => \$man5dir, 'man6dir=s' => \$man6dir, +'man7dir=s' => \$man7ext, +'man1ext=s' => \$man1ext, 'man3ext=s' => \$man3ext, +'man5ext=s' => \$man5ext, 'man6ext=s' => \$man6ext, +'man7ext=s' => \$man7ext, +'docdir=s' => \$docdir, 'htmldir=s' => \$htmldir, +'dvidir=s' => \$dvidir, 'pdfdir=s' => \$pdfdir, +'psdir=s' => \$psdir, 'infodir=s' => \$infodir, +'verbose' => \$verbose, +'disable-statics' => sub { $disable_statics='true' }, +'quiet' => sub { $verbose = 0; } ); + +print STDOUT "Configuring Victrix Abyssi build/install process...\n" if $verbose; + +my $cmdresults; + +print STDOUT "Testing 'mkdir -p' ...\n" if $verbose; +rmdir "billy/bob"; +rmdir "billy"; +$cmdresults=`mkdir -p billy/bob 2>&1`; +if ($CHILD_ERROR != 0) { + print STDERR "mkdir -p billy/bob failed\n"; + rmdir "billy"; + die "Please install a POSIX-compliant mkdir.\n"; +} +rmdir "billy/bob"; +rmdir "billy"; + +if ($includedir eq $oldincludedir) { $oldincludedir = ''; } + +print STDOUT "Writing dirs.mk...\n" if $verbose; +open(DIRS_MK, '>', "dirs.mk") or die $!; +print DIRS_MK "prefix=${prefix}\n"; +print DIRS_MK "exec_prefix=${exec_prefix}\n"; +print DIRS_MK "bindir=${bindir}\n"; +print DIRS_MK "sbindir=${sbindir}\n"; +print DIRS_MK "libdir=${libdir}\n"; +print DIRS_MK "libexecdir=${libexecdir}\n"; +print DIRS_MK "datarootdir=${datarootdir}\n"; +print DIRS_MK "datadir=${datadir}\n"; +print DIRS_MK "sysconfdir=${sysconfdir}\n"; +print DIRS_MK "sharedstatedir=${sharedstatedir}\n"; +print DIRS_MK "localstatedir=${localstatedir}\n"; +print DIRS_MK "localedir=${localedir}\n"; +print DIRS_MK "includedir=${includedir}\n"; +print DIRS_MK "oldincludedir=${oldincludedir}\n"; +print DIRS_MK "mandir=${mandir}\n"; +print DIRS_MK "man1dir=${man1dir}\n"; +print DIRS_MK "man3dir=${man3dir}\n"; +print DIRS_MK "man5dir=${man5dir}\n"; +print DIRS_MK "man6dir=${man6dir}\n"; +print DIRS_MK "man7dir=${man7dir}\n"; +print DIRS_MK "man1ext=${man1ext}\n"; +print DIRS_MK "man3ext=${man3ext}\n"; +print DIRS_MK "man5ext=${man5ext}\n"; +print DIRS_MK "man6ext=${man6ext}\n"; +print DIRS_MK "man7ext=${man7ext}\n"; +print DIRS_MK "docdir=${docdir}\n"; +print DIRS_MK "htmldir=${htmldir}\n"; +print DIRS_MK "dvidir=${dvidir}\n"; +print DIRS_MK "pdfdir=${pdfdir}\n"; +print DIRS_MK "psdir=${psdir}\n"; +print DIRS_MK "infodir=${infodir}\n"; +close(DIRS_MK); + +print STDOUT "Writing features.mk...\n" if $verbose; +open(FEATURES_MK, '>', "features.mk") or die $!; +print FEATURES_MK "DISABLE_STATICS=${disable_statics}"; +close(FEATURES_MK); diff --git a/display-nc.c b/display-nc.c new file mode 100644 index 0000000..c6daf60 --- /dev/null +++ b/display-nc.c @@ -0,0 +1,801 @@ +/*! \file display-nc.c + * \brief ncurses display support + */ + +/* Copyright 2005-2013 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_NC_C +#include "victrix-abyssi.h" +#include "monsters.h" +#include +#include +#include +// Not that we actually *use* ncursesw yet, but I want the capability there +// when I do :) +#include +#include + +void set_inventory_message(const char *s, bool allow_nil); +void reset_inventory_message(void); +void hide_inv(void); +void show_inv(void); +void update_inv(enum poclass_num filter); +WINDOW *status_window; +WINDOW *world_window; +WINDOW *message_window; +WINDOW *inventory_window; +PANEL *status_panel; +PANEL *world_panel; +PANEL *message_panel; +PANEL *inventory_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, 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 '+'; + case WATER: + return '{' | colour_attrs[DBCLR_BLUE]; + case LAVA: + return '{' | colour_attrs[DBCLR_RED]; + 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); + inventory_window = newwin(22, 58, 0, 22); + inventory_panel = new_panel(inventory_window); + wattrset(inventory_window, colour_attrs[DBCLR_L_GREY]); + hide_panel(inventory_panel); + clear(); + wclear(status_window); + wclear(world_window); + wclear(message_window); + wclear(inventory_window); + scrollok(status_window, FALSE); + scrollok(world_window, FALSE); + scrollok(message_window, TRUE); + scrollok(inventory_window, FALSE); + idcok(status_window, FALSE); + idcok(world_window, FALSE); + idcok(message_window, FALSE); + idcok(inventory_window, FALSE); + mvwprintw(world_window, 7, 4, "Victrix Abyssi"); + //mvwprintw(world_window, 9, 2, "Seven-Day Roguelike"); + mvwprintw(world_window, 11, 4, "By Martin Read"); + 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 set_inventory_message(const char *s, bool allow_nil) +{ + mvwprintw(inventory_window, 0, 0, "%-57s\n", s); + if (!allow_nil) + { + mvwprintw(inventory_window, 20, 0, " "); + } + else + { + mvwprintw(inventory_window, 20, 0, "-) nothing"); + } +} + +void reset_inventory_message(void) +{ + wattrset(inventory_window, colour_attrs[DBCLR_L_GREY]); + mvwprintw(inventory_window, 0, 21, "=== INVENTORY ==="); + mvwprintw(inventory_window, 20, 0, " "); +} + +void show_inv(void) +{ + show_panel(inventory_panel); + update_panels(); + doupdate(); +} + +void hide_inv(void) +{ + hide_panel(inventory_panel); + update_panels(); + doupdate(); +} + +void update_inv(enum poclass_num filter) +{ + int i; + char inv_line[60]; + wattrset(inventory_window, colour_attrs[DBCLR_D_GREY]); + for (i = 0; i < 19; i++) + { + if (u.inventory[i] == -1) + { + wattrset(inventory_window, colour_attrs[DBCLR_D_GREY]); + mvwprintw(inventory_window, i + 1, 0, "%c) -----\n", 'a' + i); + } + else + { + if ((filter == POCLASS_NONE) || + (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)) + { + wattrset(inventory_window, colour_attrs[DBCLR_L_GREY]); + } + else + { + wattrset(inventory_window, colour_attrs[DBCLR_D_GREY]); + } + sprintf(inv_line, "%c) ", 'a' + i); + sprint_obj_name(inv_line + 3, u.inventory[i], 45); + if ((u.ring == u.inventory[i]) || (u.armour == u.inventory[i])) + { + strcat(inv_line, " (worn)"); + } + else if (u.weapon == u.inventory[i]) + { + strcat(inv_line, " (held)"); + } + mvwprintw(inventory_window, i + 1, 0, "%s\n", inv_line); + } + } +} + +int inv_select(enum poclass_num filter, const char *action, int accept_blank) +{ + int selection; + int ch; + int i; + int items = 0; + char msg[60]; + + 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; + } + wattrset(inventory_window, colour_attrs[DBCLR_L_GREY]); + snprintf(msg, 58, "What do you want to %s?\n", action); + set_inventory_message(msg, accept_blank); + update_inv(filter); + wattrset(inventory_window, colour_attrs[DBCLR_L_GREY]); + mvwprintw(inventory_window, 21, 17, "[ESC or SPACE to cancel]"); + show_inv(); +tryagain: + ch = wgetch(inventory_window); + switch (ch) + { + case '-': + if (accept_blank) + { + mvwprintw(inventory_window, 21, 0, " "); + reset_inventory_message(); + update_inv(POCLASS_NONE); + hide_inv(); + return -2; + } + case 'x': + case '\x1b': + case ' ': + hide_inv(); + 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. */ + 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: + 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); + if (!panel_hidden(inventory_panel)) + { + hide_inv(); + } + switch (ch) + { + case 'a': + return ATTACK; + case '0': + case ',': + case 'g': + return GET_ITEM; + case 'd': + return DROP_ITEM; + case '@': + return DUMP_CHARA; + case 'S': + return SAVE_GAME; + case 'X': + return QUIT; + case 'i': + if (panel_hidden(inventory_panel)) + { + reset_inventory_message(); + update_inv(POCLASS_NONE); + show_inv(); + } + else + { + hide_inv(); + } + continue; + case 'I': + return INSPECT_ITEM; + case ';': + return EXAMINE_MONSTER; + case '#': + return SHOW_TERRAIN; + 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 'e': + return EAT_FOOD; + case 't': + return THROW_FLASK; + case 'w': + return WIELD_WEAPON; + case 'z': + return ZAP_WEAPON; + case 'W': + return WEAR_ARMOUR; + case 'T': + return TAKE_OFF_ARMOUR; + case 'E': + return EMANATE_ARMOUR; + case 'P': + return PUT_ON_RING; + case 'R': + return REMOVE_RING; + case 'm': + return MAGIC_RING; + case '?': + return GIVE_HELP; + case '>': + return GO_DOWN_STAIRS; + 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("w wield a weapon\n"); + print_msg("t throw a flask\n"); + print_msg("r read a scroll\n"); + print_msg("q quaff a potion\n"); + print_msg("e eat something edible\n"); + print_msg("g pick up an item (also 0 or comma)\n"); + print_msg("d drop an item\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("ACTIONS (continued)\n"); + print_msg("z activate your weapon's magical power (if any)\n"); + print_msg("m activate your ring's magical power (if any)\n"); + print_msg("E activate your armour's magical power (if any)\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("@ 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"); +} + +void touch_one_screen(int y, int x) +{ + int y2, x2; + for (y2 = y - 10; y2 <= y + 10; y2++) + { + if ((y2 < 0) || (y2 >= DUN_HEIGHT)) + { + continue; + } + for (x2 = x - 10; x2 <= x + 10; x2++) + { + if ((x2 < 0) || (x2 >= DUN_WIDTH)) + { + continue; + } + newsym(y2, x2); + } + } +} + +/* display.c */ +// vim:cindent diff --git a/main.c b/main.c new file mode 100644 index 0000000..561b0b7 --- /dev/null +++ b/main.c @@ -0,0 +1,639 @@ +/* main.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" +#include "combat.h" +#include +#include +#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("cavechop.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... */ + 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 cavechop.sav"); + game_finished = 1; + return; +} + +void load_game(void) +{ + FILE *fp; + system("gunzip cavechop.sav"); + fp = fopen("cavechop.sav", "rb"); + fread(rng_state, sizeof rng_state, 1, fp); + 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("cavechop.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]; + recalc_defence(); + if (objects[u.armour].obj_id == PO_RIBBONS) + { + print_msg("You grit your teeth, trying to get used to the tingle of\nthe ribbons' magic against your skin.\n"); + } + else + { + print_msg("Wearing "); + print_obj_name(u.armour); + print_msg(".\n"); + } + return 1; + } + return 0; + + case EMANATE_ARMOUR: + if (u.armour == -1) + { + print_msg("You are not wearing any armour.\n"); + return 0; + } + return emanate_armour(); + + case ZAP_WEAPON: + if (u.weapon == -1) + { + print_msg("You have no weapon in hand.\n"); + return 0; + } + return zap_weapon(); + + case MAGIC_RING: + if (u.weapon == -1) + { + print_msg("You are not wearing a ring.\n"); + return 0; + } + return magic_ring(); + + case TAKE_OFF_ARMOUR: + if (u.armour != -1) + { + if ((u.resistances[DT_FIRE] == RESIST_ARMOUR) && + (terrain[u.y][u.x] == LAVA)) + { + print_msg("Your armour is your only current source of fire\nresistance; removing it here would incinerate you.\n"); + return 0; + } + 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 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 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 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 THROW_FLASK: + i = inv_select(POCLASS_FLASK, "throw", 0); + if (i >= 0) + { + j = select_dir(&sy, &sx); + if (j != -1) + { + return throw_flask(i, sy, sx); + } + } + return 0; + + case REMOVE_RING: + if (u.ring == -1) + { + print_msg("You have no ring to remove!\n"); + return 0; + } + else if ((terrain[u.y][u.x] == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING)) + { + print_msg("That ring is your only current source of fire resistance. Removing\nit here would incinerate you.\n"); + return 0; + } + else if ((objects[u.ring].obj_id == PO_RING_FROST) && (terrain[u.y][u.x] == WATER)) + { + print_msg("Since nobody ever taught you to swim, removing that ring\nhere would result in your death by drowning.\n"); + return 0; + } + 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_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 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 Cave Chop, Princess %s.\n", u.name); + 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; + } + } + for (i = 0; i < 100; i++) + { + if (!monsters[i].used) + { + /* 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(); + memset(mapobject, -1, sizeof mapobject); + memset(mapmonster, -1, sizeof mapmonster); + /* Do we have a saved game? */ + i = stat("cavechop.sav.gz", &s); + if (!i) + { + /* Yes! */ + print_msg("Loading...\n"); + load_game(); + } + else + { + /* No! */ + new_game(); + } + main_loop(); + display_shutdown(); + return 0; +} + +/* main.c */ +// vim:cindent diff --git a/map.c b/map.c new file mode 100644 index 0000000..dc2156f --- /dev/null +++ b/map.c @@ -0,0 +1,272 @@ +/* map.c + * + * Copyright 2005-2012 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 "victrix-abyssi.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 depth = 1; + +static int get_levgen_mon_floor(int *y, int *x); +static void put_stairs(void); + +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); + 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) +{ + build_level(); + populate_level(); + inject_player(); + touch_back_buffer(); + display_update(); +} + +void put_stairs(void) +{ + int y; + int x; + do + { + y = exclusive_flat(0, DUN_HEIGHT - 1); + x = exclusive_flat(0, DUN_WIDTH - 1); + } while (terrain[y][x] != FLOOR); + terrain[y][x] = STAIRS; +} + +static void run_random_walk(int oy, int ox, enum terrain_num write, enum terrain_num *overwrite, int overwrite_length, int cells) +{ + int i; + int j; + int y = oy; + int x = ox; + for (i = 0; i < cells; ) + { + if (zero_die(2)) + { + y += (zero_die(2) ? -1 : 1); + if ((y == 0) || (y == DUN_HEIGHT - 1)) + { + y = DUN_HEIGHT / 2; + x = DUN_WIDTH / 2; + } + } + else + { + x += (zero_die(2) ? -1 : 1); + if (x == 0) + { + x = 2; + } + else if (x == DUN_WIDTH - 1) + { + x = DUN_WIDTH - 3; + } + } + for (j = 0; j < overwrite_length; ++j) + { + if (terrain[y][x] == overwrite[j]) + { + ++i; + terrain[y][x] = write; + break; + } + } + } +} + +void build_level(void) +{ + int y = DUN_HEIGHT / 2; + int x = DUN_WIDTH / 2; + enum terrain_num pool_flavour = FLOOR; + int num_pools; + enum terrain_num overwrite_array[2] = { WALL, FLOOR }; + /* 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); + run_random_walk(y, x, FLOOR, overwrite_array, 1, LEVGEN_WALK_CELLS); + if ((depth > 20) && !zero_die(4)) + { + num_pools = inclusive_flat(1, 4); + pool_flavour = LAVA; + } + else if ((depth > 10) && !zero_die(3)) + { + num_pools = inclusive_flat(1, 4); + pool_flavour = WATER; + } + else + { + num_pools = 0; + } + while (num_pools > 0) + { + int pool_size = inclusive_flat(9, 36); + + do + { + y = exclusive_flat(1, DUN_HEIGHT - 2); + x = exclusive_flat(1, DUN_WIDTH - 2); + } while (terrain[y][x] != FLOOR); + run_random_walk(y, x, pool_flavour, overwrite_array, 2, pool_size); + --num_pools; + } + /* Add the stairs */ + put_stairs(); +} + +int get_levgen_mon_floor(int *y, int *x) +{ + /* Get a vacant floor cell that isn't in the treasure zoo. */ + int cell_try; + int ty, tx; + for (cell_try = 0; cell_try < 200; cell_try++) + { + ty = exclusive_flat(0, DUN_HEIGHT - 1); + tx = exclusive_flat(0, DUN_WIDTH - 1); + if ((terrain[ty][tx] != FLOOR) || + (mapmonster[ty][tx] != -1)) + { + ty = -1; + tx = -1; + continue; + } + 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" */ + /* 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 cell_try; + for (cell_try = 0; cell_try < 200; cell_try++) + { + u.y = exclusive_flat(0, DUN_HEIGHT - 1); + u.x = exclusive_flat(0, DUN_WIDTH - 1); + if (terrain[u.y][u.x] != FLOOR) + { + continue; + } + if (mapmonster[u.y][u.x] != -1) + { + continue; + } + break; + } + reloc_player(u.y, u.x); +} + +void explore_around(int y, int x) +{ + int y2, x2; + for (y2 = y - 10; y2 <= y + 10; y2++) + { + if ((y2 < 0) || (y2 >= DUN_HEIGHT)) + { + continue; + } + for (x2 = x - 10; x2 <= x + 10; x2++) + { + if ((x2 < 0) || (x2 >= DUN_WIDTH)) + { + continue; + } + if (!(mapflags[y2][x2] & MAPFLAG_EXPLORED)) + { + mapflags[y2][x2] |= MAPFLAG_EXPLORED; + } + newsym(y2, x2); + } + } +} + +/* map.c */ +// vim:cindent diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..b77ec25 --- /dev/null +++ b/misc.c @@ -0,0 +1,39 @@ +/* misc.c + * + * Copyright 2005-2012 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 "victrix-abyssi.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 */ +// vim:cindent diff --git a/mon2.c b/mon2.c new file mode 100644 index 0000000..4a86054 --- /dev/null +++ b/mon2.c @@ -0,0 +1,877 @@ +/* mon2.c + * + * Copyright 2005-2012 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 "victrix-abyssi.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; + *pref_y = y; + *pref_x = x; + ady = dy > 0 ? dy : -dy; + adx = dx > 0 ? dx : -dx; + build_ai_cells(ai_cells, y, x); + 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 closer to player: +1 + * Square further from player: -1 + */ + 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 ((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 (mon_visible(mon)) + { + mptr->awake = 1; + } + if (meleerange) + { + /* Adjacent! Attack you. Demons have a 1 in 10 chance of + * attempting to summon another demon instead of attacking + * you. */ + 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 (mon_visible(mon)) + { + /* In sight. */ + 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 ((dy >= -10) && (dy <= 10) && (dx >= -10) && (dx >= 10)) + { + 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 */ +// vim:cindent diff --git a/monsters.c b/monsters.c new file mode 100644 index 0000000..79b35fd --- /dev/null +++ b/monsters.c @@ -0,0 +1,501 @@ +/* monsters.c + * + * Copyright 2005-2012 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 "victrix-abyssi.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(NUM_OF_PERMONS); + 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; + case PM_DEMON: + if (!zero_die(100)) + { + create_obj(PO_DEVIL_SPLEEN, 1, 0, y, x); + } + break; + case PM_DEFILER: + if (!zero_die(50)) + { + create_obj(PO_DEVIL_SPLEEN, 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; + } + if ((terrain[y][x] == LAVA) && + !pmon_resists_fire(monsters[mon].mon_id)) + { + return 0; + } + if (terrain[y][x] == WATER) + { + 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; + 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; + } + } + 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 cell_try; + int y, x; + for (cell_try = 0; cell_try < 200; cell_try++) + { + y = exclusive_flat(0, DUN_HEIGHT - 1); + x = exclusive_flat(0, DUN_WIDTH - 1); + 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; + /* Cave Chop FoV: The screen. */ + if (((dy > -11) && (dy < 11) && (dx > -11) && (dx < 11))) + { + 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 */ +// vim:cindent diff --git a/monsters.h b/monsters.h new file mode 100644 index 0000000..8c6b3c9 --- /dev/null +++ b/monsters.h @@ -0,0 +1,70 @@ +/* monsters.h + * + * Copyright 2005-2012 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 VICTRIX_ABYSSI_H +#include "victrix-abyssi.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 bool pmon_is_archer(int pm); +extern bool pmon_is_magician(int pm); +extern bool pmon_is_smart(int pm); +extern bool pmon_is_stupid(int pm); +extern bool pmon_is_undead(int pm); +extern bool pmon_resists_cold(int pm); +extern bool pmon_resists_fire(int pm); +extern bool pmon_resists_poison(int pm); +extern bool pmon_resists_necro(int pm); +extern bool pmon_resists_elec(int pm); + +#endif + +/* monsters.h */ +// vim:cindent diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..08f2dac --- /dev/null +++ b/notes.txt @@ -0,0 +1,77 @@ +CAVE CHOP SEVEN-DAY ROGUELIKE +================================== +Copyright 2012 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. + +Cave Chop (aka "cavechop") is copyright 2012 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. + +INSTALLATION INSTRUCTIONS +------------------------- +On Linux (and other Unix-like systems, though I don't guarantee it working +on any given Unix): + tar xzf cavechop7drl.tar.gz + cd cavechop7drl + make all + cp cavechop /somewhere/in/your/PATH + +The cavechop 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 Cave Chop contemporaneous with the +source release. + +ABOUT THE GAME +-------------- +This is the seven-day roguelike Cave Chop. + +Like its progenitor, Martin's Dungeon Bash v1.7, Cave Chop has a very simple +concept: Kill as many monsters as you can, diving ever deeper into the +infinite hellish caverns in which you are trapped, until you die. There is no +"victory" condition; the only objective provided by the design is "see how far +you get and how many monsters you kill". + +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. + +Discussion of Cave Chop is on-topic for the newsgroup +rec.games.roguelike.misc ; please put -cavechop- in the Subject: header of any +post to RGRM regarding Cave Chop. + +REPORTING BUGS +-------------- +Report bugs by e-mail to martin (at) blackswordsonics dot com. + +THANKS TO +--------- +* Jeff Lait, Jon Amery, Adam White, Gero Kunter, David Damerell, and Richard + Kettlewell for advice, inspiration, suggestions, and bug reports that helped + make Martin's Dungeon Bash 1.7 the game it was and is. + diff --git a/objects.c b/objects.c new file mode 100644 index 0000000..17bd443 --- /dev/null +++ b/objects.c @@ -0,0 +1,805 @@ +/* objects.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" +#include "monsters.h" + +struct obj objects[100]; +int get_random_pobj(void); + +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_TELEPORT: + teleport_u(); + break; + case PO_SCR_FIRE: + print_msg("The scroll explodes in flames!\n"); + if (u.resistances[DT_FIRE]) + { + if (objects[u.ring].obj_id == PO_RING_FIRE) + { + print_msg("Your ring glows, and the flames seem cool.\n"); + break; + } + } + else + { + i = damage_u(dice(4, 10), DEATH_KILLED, "searing flames"); + if (!i) + { + print_msg("That hurt!\n"); + } + } + for (i = 0; i < MONSTERS_IN_PLAY; ++i) + { + if (mon_visible(i)) + { + if (!pmon_resists_fire(monsters[i].mon_id)) + { + print_mon_name(i, 3); + print_msg(" is burned.\n"); + damage_mon(i, dice(4, 10), 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; + } + return consume_obj(obj); +} + +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 (optr->obj_id == PO_DEVIL_SPLEEN) + { + print_msg("Vile power suffuses your body as you devour the devil\nspleen.\n"); + if (zero_die(2)) + { + gain_body(1, 1); + } + else + { + gain_agility(1, 1); + } + } + else 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_HEAL: + { + int healpercent = inclusive_flat(30, 50); + int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100; + heal_u(healamount, 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; + } + 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_VAMPIRE].power = colour_choices[2]; + permobjs[PO_RING_FROST].power = colour_choices[3]; + permobjs[PO_RING_TELEPORT].power = colour_choices[4]; + /* 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_PROTECTION].power = colour_choices[2]; + /* 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_AGILITY].power = colour_choices[2]; + permobjs[PO_POT_RESTORATION].power = colour_choices[3]; +} + +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(NUM_OF_PERMOBJS); + 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; + 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 sprint_obj_name(char *buf, int obj, int len) +{ + struct obj *optr; + struct permobj *poptr; + optr = objects + obj; + poptr = permobjs + optr->obj_id; + if (optr->quan > 1) + { + snprintf(buf, len, "%d %s", optr->quan, poptr->plural); + } + else if (po_is_stackable(optr->obj_id)) + { + snprintf(buf, len, "1 %s", poptr->name); + } + else if ((poptr->poclass == POCLASS_WEAPON) || + (poptr->poclass == POCLASS_ARMOUR)) + { + snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); + } + else + { + snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); + } +} + +void fprint_obj_name(FILE *fp, int obj) +{ + struct obj *optr; + struct permobj *poptr; + optr = objects + obj; + poptr = permobjs + optr->obj_id; + 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); + } +} + +void print_obj_name(int obj) +{ + struct obj *optr; + struct permobj *poptr; + optr = objects + obj; + poptr = permobjs + optr->obj_id; + 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); + } +} + +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; + 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--; + if (objects[obj].durability == 0) + { + switch (objects[obj].obj_id) + { + case PO_RIBBONS: + if (u.food < 500) + { + int shortfall = (u.food < 0) ? 500 : 500 - u.food; + int damage = (shortfall + 24) / 25; + u.food = (u.food < 0) ? u.food : 0; + print_msg("Blood trickles from your nose as the ribbons drain your\nstrength repair themselves.\n"); + damage_u(damage, DEATH_RIBBONS, ""); + } + else + { + print_msg("You gasp as the ribbons drain your strength to repair\nthemselves.\n"); + u.food -= 500; + } + break; + + case PO_TORMENTORS_LASH: + if (u.food < 500) + { + int shortfall = (u.food < 0) ? 500 : 500 - u.food; + int damage = (shortfall + 24) / 25; + u.food = (u.food < 0) ? u.food : 0; + print_msg("Pain explodes through your arm as the lash resotres\nitself!\n"); + damage_u(damage, DEATH_LASH, ""); + } + else + { + print_msg("A pins-and-needles sensation washes over you as the lash\nrestores itself.\n"); + u.food -= 500; + } + break; + } + } + } +} + +void describe_object(int obj) +{ + struct obj *optr; + struct permobj *poptr; + print_obj_name(obj); + optr = objects + obj; + poptr = permobjs + optr->obj_id; + print_msg("\n%s\n", poptr->description); +} + +int evasion_penalty(int obj) +{ + switch (objects[obj].obj_id) + { + case PO_ROBE: + return 5; + + case PO_LEATHER_ARMOUR: + case PO_DRAGON_ARMOUR: + case PO_BATTLE_BALLGOWN: + 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: + case PO_RIBBONS: + 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; + } +} + +int magic_ring(void) +{ + struct obj *optr = objects + u.ring; + switch (optr->obj_id) + { + case PO_RING_TELEPORT: + if (u.food >= 50) + { + u.food -= 50; + print_msg("You activate your ring's power of teleportation.\n"); + teleport_u(); + return 1; + } + return 0; + default: + print_msg("Your current ring has no activatable power.\n"); + return 0; + } +} + +int emanate_armour(void) +{ + struct obj *optr = objects + u.armour; + switch (optr->obj_id) + { + case PO_RIBBONS: + if (optr->durability < OBJ_MAX_DUR) + { + if (u.food < 5 * (OBJ_MAX_DUR - optr->durability)) + { + print_msg("You are too hungry to willingly let the ribbons draw on\nyour strength.\n"); + return 0; + } + else + { + print_msg("You gasp as the ribbons draw on your strength to\nrepair themselves.\n"); + u.food -= 5 * (OBJ_MAX_DUR - optr->durability); + optr->durability = OBJ_MAX_DUR; + return 1; + } + } + return 1; + default: + print_msg("Your current attire has no activatable powers.\n"); + break; + } + return 0; +} + +int zap_weapon(void) +{ + struct obj *optr = objects + u.weapon; + switch (optr->obj_id) + { + case PO_STAFF_OF_FIRE: + if (u.food > 150) + { + int y, x; + u.food -= 150; + print_msg("You unleash the fiery powers of your staff!\n"); + for (y = u.y - 1; y <= u.y + 1; ++y) + { + if ((y < 0) || (y >= DUN_HEIGHT)) + { + continue; + } + for (x = u.x - 1; x <= u.x + 1; ++x) + { + if ((x < 0) || (x >= DUN_WIDTH)) + { + continue; + } + if (mapmonster[y][x] != -1) + { + struct mon *mptr = monsters + mapmonster[y][x]; + if (!pmon_resists_fire(mptr->mon_id)) + { + print_mon_name(mapmonster[y][x], 3); + print_msg(" is engulfed in roaring flames.\n"); + damage_mon(mapmonster[y][x], dice(4, 10), 1); + } + } + } + } + damage_obj(u.weapon); + } + default: + print_msg("Your current weapon has no activatable powers.\n"); + return 0; + } +} + +/* objects.c */ +// vim:cindent diff --git a/permobj.c b/permobj.c new file mode 100644 index 0000000..d872586 --- /dev/null +++ b/permobj.c @@ -0,0 +1,191 @@ +/* permobj.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" + +struct permobj permobjs[NUM_OF_PERMOBJS] = { + [PO_DAGGER] = + { + "dagger", "daggers", "A long knife, designed for stabbing.", POCLASS_WEAPON, 25, ')', 4, 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, 4 + }, + [PO_MACE] = + { + "mace", "maces", "A flanged lump of iron on an iron haft.", POCLASS_WEAPON, 30, ')', 7, 1, 2 + }, + [PO_RUNESWORD] = + { + "runesword", "runeswords", "An eerily glowing sword engraved with many strange\nrunes.", POCLASS_WEAPON, 80, ')', 20, 1, 12 + }, + [PO_BOW] = + { + "bow", "bows", "A recurve composite bow.", POCLASS_WEAPON, 45, '(', 8, 1, 1 + }, + [PO_CROSSBOW] = + { + "crossbow", "crossbows", "A crossbow.", POCLASS_WEAPON, 70, '(', 16, 1, 6 + }, + [PO_TORMENTORS_LASH] = + { + "tormentor's lash", "tormentor's lash", "A bone-handled whip that crackles with malefic energies.", POCLASS_WEAPON, 80, ')', 20, 1, 30 + }, + [PO_STAFF_OF_FIRE] = + { + "staff of fire", "staff of fire", "A jet-black staff with a glowing ruby in its headpiece.", POCLASS_WEAPON, 80, ')', 10, 1, 20 + }, + [PO_POT_HEAL] = + { + "healing potion", "healing potions", "This magic elixir restores some lost hit points.", POCLASS_POTION, 10, '!', 0, 1, 1 + }, + [PO_POT_BODY] = + { + "body potion", "body potions", "This magic elixir will improve your physique.", POCLASS_POTION, 70, '!', 0, 1, 5 + }, + [PO_POT_AGILITY] = + { + "agility potion", "agility potions", "This magic elixir will sharpen your reflexes.", POCLASS_POTION, 70, '!', 0, 1, 5 + }, + [PO_POT_RESTORATION] = + { + "restoration potion", "restoration potions", "This magic elixir cures temporary damage to one's\nabilities.", POCLASS_POTION, 70, '!', 0, 1, 1 + }, + [PO_FLASK_POISON] = + { + "poison flask", "poison flask", "This fragile bottle is full of contact poison.", POCLASS_FLASK, 10, '~', 0, 1, 1 + }, + [PO_FLASK_FIRE] = + { + "fire flask", "fire flasks", "The volatile, phosphorus-laced liquid in this sealed\nflask will ignite spontaneously when exposed to the air.", POCLASS_FLASK, 40, '~', 0, 1, 20 + }, + [PO_FLASK_WEAKNESS] = + { + "weakness flask", "weakness flasks", "Dousing the living in this vile liquid causes immediate\nand severe physical degeneration.", POCLASS_FLASK, 40, '~', 0, 1, 20 + }, + [PO_SCR_TELEPORT] = + { + "teleport scroll", "teleport scrolls", "Reading this scroll will teleport you to a random\nlocation.", POCLASS_SCROLL, 40, '?', 0, 1, 1 + }, + [PO_SCR_FIRE] = + { + "fire scroll", "fire scrolls", "Reading this scroll will engulf all nearby creatures\n(including you) in flames.", POCLASS_SCROLL, 30, '?', 0, 1, 1 + }, + [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, 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 + }, + [PO_CHAINMAIL] = + { + "chainmail", "suits of chainmail", "A suit of interlocking metal rings, providing better\nprotection than leather.", POCLASS_ARMOUR, 30, '[', 6, 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, 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, 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, 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, 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, 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, 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, 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, 24 + }, + [PO_BATTLE_BALLGOWN] = + { + "battle ballgown", "battle ballgowns", "This lightly armoured dress is a beloved heirloom of\nyour house.", POCLASS_ARMOUR, 100, '[', 3, 1, 1 + }, + [PO_RIBBONS] = + { + /* inspired by DoomRL's Necroarmor and my own creepiness. */ + "set of ribbons", "sets of ribbons", "These ribbons, arranged as if to form an alleged\ngarment, make your fingers tingle with magic.", POCLASS_ARMOUR, 90, '[', 15, 1, 27 + }, + [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, 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, 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, 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. Rumour suggests it might also allow walking\non water.\n", POCLASS_RING, 40, '=', 0, 1, 1 + }, + [PO_RING_TELEPORT] = + { + "teleport ring", "teleport rings", "This magical ring allows the wearer to teleport for a\nmodest cost in nutrition.", POCLASS_RING, 70, '=', 0, 1, 1 + }, + [PO_IRON_RATION] = + { + "iron ration", "iron rations", "A parcel of hardtack and beef jerky. Dull but nutritious.", POCLASS_FOOD, 75, '%', 0, 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 + }, + [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 + }, + [PO_DEVIL_SPLEEN] = + { + "devil spleen", "devil spleens", "A weirdly pulsing organ ripped from the torso of a devil.", POCLASS_FOOD, 100, '%', 0, 1, 1 + } +}; + +/* permobj.c */ +// vim:cindent diff --git a/permons.c b/permons.c new file mode 100644 index 0000000..12abeba --- /dev/null +++ b/permons.c @@ -0,0 +1,153 @@ +/* permons.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" + +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, 15, 6, -1, 7, -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 | PMF_RESIST_ELEC + }, + [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 | PMF_RESIST_POIS | PMF_RESIST_NECR + }, + [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 | PMF_RESIST_POIS | PMF_RESIST_NECR + }, + [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 | PMF_RESIST_POIS | PMF_RESIST_NECR + }, + [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 | PMF_RESIST_POIS | PMF_RESIST_NECR + }, + [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 | PMF_RESIST_POIS | PMF_RESIST_NECR + }, + [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 | PMF_RESIST_POIS + }, + [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 */ +// vim:cindent diff --git a/pmon2.c b/pmon2.c new file mode 100644 index 0000000..d4fb4c9 --- /dev/null +++ b/pmon2.c @@ -0,0 +1,82 @@ +/* pmon2.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" +#include "monsters.h" + +bool pmon_resists_fire(int pm) +{ + return !!(permons[pm].flags & PMF_RESIST_FIRE); +} + +bool pmon_resists_cold(int pm) +{ + return !!(permons[pm].flags & PMF_RESIST_COLD); +} + +bool pmon_resists_poison(int pm) +{ + return !!(permons[pm].flags & PMF_RESIST_POIS); +} + +bool pmon_resists_necro(int pm) +{ + return !!(permons[pm].flags & PMF_RESIST_NECR); +} + +bool pmon_resists_elec(int pm) +{ + return !!(permons[pm].flags & PMF_RESIST_ELEC); +} + +bool pmon_is_undead(int pm) +{ + return !!(permons[pm].flags & PMF_UNDEAD); +} + +bool pmon_is_stupid(int pm) +{ + return !!(permons[pm].flags & PMF_STUPID); +} + +bool pmon_is_smart(int pm) +{ + return !!(permons[pm].flags & PMF_SMART); +} + +bool pmon_is_magician(int pm) +{ + return !!(permons[pm].flags & PMF_MAGICIAN); +} + +bool pmon_is_archer(int pm) +{ + return !!(permons[pm].flags & PMF_ARCHER); +} + +/* pmon2.c */ +// vim:cindent diff --git a/rng.c b/rng.c new file mode 100644 index 0000000..97fff9c --- /dev/null +++ b/rng.c @@ -0,0 +1,74 @@ +/* rng.c + * + * Copyright 2005-2012 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 "victrix-abyssi.h" +#include +#include +#include + +uint32_t rng_state[5]; +uint32_t saved_state[5]; + +uint32_t rng(void) +{ + uint32_t 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 */ +// vim:cindent diff --git a/u.c b/u.c new file mode 100644 index 0000000..f66fd6e --- /dev/null +++ b/u.c @@ -0,0 +1,684 @@ +/*! \file u.c + * \brief player-character + */ +/* + * Copyright 2005-2012 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 "victrix-abyssi.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: + print_msg("gaining fire resistance from ring\n"); + 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; + case LAVA: + if (u.resistances[DT_FIRE]) + { + if (terrain[u.y][u.x] != LAVA) + { + print_msg("You walk on the lava.\n"); + } + reloc_player(u.y + dy, u.x + dx); + return 1; + } + else + { + print_msg("The fierce heat of the molten rock repels you.\n"); + return 0; + } + case WATER: + if ((u.ring != -1) && (objects[u.ring].obj_id == PO_RING_FROST)) + { + if (terrain[u.y][u.x] != WATER) + { + print_msg("You walk on the water.\n"); + } + reloc_player(u.y + dy, u.x + dx); + return 1; + } + else + { + print_msg("The idiot who raised you never taught you to swim.\n"); + return 0; + } + } + return 0; +} + +int reloc_player(int y, int x) +{ + int oy, ox; + + oy = u.y; + ox = u.x; + u.y = y; + u.x = x; + + touch_one_screen(oy, ox); + explore_around(u.y, u.x); + 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 they feel. */ + 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("cavechop.log", "a"); + } + 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, "%s was killed by %s.\n", u.name, what); + } + break; + case DEATH_KILLED_MON: + print_msg("You were killed by a nasty %s.\n", what); + if (!wizard_mode) + { + fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what); + } + break; + case DEATH_BODY: + print_msg("Your heart was stopped by %s.\n", what); + if (!wizard_mode) + { + fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what); + } + break; + case DEATH_AGILITY: + print_msg("Your nerves were destroyed by %s.\n", what); + if (!wizard_mode) + { + fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); + } + break; + case DEATH_LASH: + print_msg("You tasted the lash one time too many.\n"); + if (!wizard_mode) + { + fprintf(fp, "%s tasted the lash one time too many.\n", u.name); + } + break; + case DEATH_RIBBONS: + print_msg("You looked good in ribbons.\n"); + if (!wizard_mode) + { + fprintf(fp, "%s looked good in ribbons.\n", u.name); + } + break; + } + if (!wizard_mode) + { + fprintf(fp, " %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, 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); + + 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 princess (%d XP)\n", u.name, u.level, u.experience); + 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; + int i; + u.name[16] = '\0'; + do { + print_msg("What is your name, stranger?\n"); + i = read_input(u.name, 16); + if (i == 0) + { + /* Default name is Matilda, in honour of Matilda of England + * (aka Empress Matilda) and of Matilda di Canossa, Margravine + * regnant of Tuscany. */ + strcpy(u.name, "Matilda"); + } + else + { + 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.inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, 1, -1, -1); + u.weapon = u.inventory[0]; + u.ring = -1; + u.armour = u.inventory[2]; + 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 cell_try; + int y, x; + for (cell_try = 0; cell_try < 200; cell_try++) + { + y = exclusive_flat(0, DUN_HEIGHT - 1); + x = exclusive_flat(0, DUN_WIDTH - 1); + 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); + } + 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]; +} + +// vim:cindent +/* u.c */ +// vim:cindent diff --git a/vector.c b/vector.c new file mode 100644 index 0000000..df990bc --- /dev/null +++ b/vector.c @@ -0,0 +1,65 @@ +/*! \file vector.c + * \brief direction-handling code + */ + +/* Copyright 2005-2012 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 "victrix-abyssi.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 */ +// vim:cindent diff --git a/victrix-abyssi.h b/victrix-abyssi.h new file mode 100644 index 0000000..c8e3eda --- /dev/null +++ b/victrix-abyssi.h @@ -0,0 +1,381 @@ +/* victrix-abyssi.h + * + * Copyright 2005-2013 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 VICTRIX_ABYSSI_H +#define VICTRIX_ABYSSI_H + +#include +#include +#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, + MOVE_WEST, MOVE_SOUTH, MOVE_NORTH, MOVE_EAST, + MOVE_NW, MOVE_NE, MOVE_SW, MOVE_SE, + WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING, + QUAFF_POTION, READ_SCROLL, THROW_FLASK, + EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING, ATTACK, + GET_ITEM, QUIT, GO_DOWN_STAIRS, STAND_STILL, EAT_FOOD, DUMP_CHARA, + GIVE_HELP, INSPECT_ITEM, EXAMINE_MONSTER, RNG_TEST, SHOW_TERRAIN, + WIZARD_LEVELUP, WIZARD_DESCEND +}; + +/* XXX enum terrain_num */ +enum terrain_num { + WALL = 0, FLOOR, DOOR, STAIRS, LAVA, WATER +}; + +/* XXX enum death */ +/* Sadly, there are not 52 kinds of way to die. */ +enum death { + DEATH_KILLED, DEATH_KILLED_MON, DEATH_BODY, DEATH_AGILITY, + DEATH_LASH, DEATH_RIBBONS +}; + +/* XXX enum poclass_num */ +/* Categories of permanent object. */ +enum poclass_num { + POCLASS_NONE = 0, POCLASS_WEAPON, POCLASS_POTION, + POCLASS_SCROLL, POCLASS_FLASK, POCLASS_ARMOUR, POCLASS_RING, + POCLASS_FOOD +}; + +#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; + uint32_t 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 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 */ +enum Permon_num +{ + PM_NEWT = 0, PM_RAT, PM_WOLF, PM_SNAKE, PM_THUG, PM_GOON, PM_HUNTER, + PM_DUELLIST, PM_WARLORD, PM_WIZARD, PM_ARCHMAGE, PM_GOBLIN, PM_BAD_ELF, + PM_TROLL, PM_GIANT, PM_GIANT_JARL, PM_ZOMBIE, PM_WRAITH, PM_LICH, + PM_VAMPIRE, PM_MASTER_LICH, PM_DEMON, PM_DEFILER, PM_ICE_MONSTER, + PM_CENTAUR, PM_DRAGON +}; +#define NUM_OF_PERMONS (1 + PM_DRAGON) + +#define PMF_RESIST_FIRE 0x00000001 +#define PMF_RESIST_COLD 0x00000002 +#define PMF_RESIST_ELEC 0x00000004 +#define PMF_RESIST_POIS 0x00000008 +#define PMF_RESIST_NECR 0x00000010 +#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. */ +}; +extern struct permon permons[NUM_OF_PERMONS]; + +/* XXX struct permobj */ +enum Permobj_nums +{ + // weapons + PO_DAGGER=0, PO_LONG_SWORD, PO_MACE, PO_RUNESWORD, PO_BOW, PO_CROSSBOW, + PO_TORMENTORS_LASH, PO_STAFF_OF_FIRE, + // potions + PO_POT_HEAL, PO_POT_BODY, PO_POT_AGILITY, PO_POT_RESTORATION, + // flasks + PO_FLASK_POISON, PO_FLASK_FIRE, PO_FLASK_WEAKNESS, + // scrolls + PO_SCR_TELEPORT, PO_SCR_FIRE, PO_SCR_PROTECTION, + // armour + PO_LEATHER_ARMOUR, PO_CHAINMAIL, PO_PLATE_ARMOUR, PO_MAGE_ARMOUR, PO_ROBE, + PO_ROBE_SWIFTNESS, PO_ROBE_SHADOWS, PO_DRAGON_ARMOUR, PO_METEOR_ARMOUR, + PO_SACRED_MAIL, PO_BATTLE_BALLGOWN, PO_RIBBONS, + // rings + PO_RING_REGEN, PO_RING_FIRE, PO_RING_VAMPIRE, PO_RING_FROST, + PO_RING_TELEPORT, + // food + PO_IRON_RATION, PO_DRIED_FRUIT, PO_ELVEN_BREAD, PO_DEVIL_SPLEEN +}; +#define PO_FIRST_WEAPON PO_DAGGER +#define PO_LAST_WEAPON PO_STAFF_OF_FIRE +#define PO_FIRST_POTION PO_POT_HEAL +#define PO_LAST_POTION PO_POT_RESTORATION +#define PO_FIRST_FLASK PO_FLASK_POISON +#define PO_LAST_FLASK PO_FLASK_WEAKNESS +#define PO_FIRST_SCROLL PO_SCR_TELEPORT +#define PO_LAST_SCROLL PO_SCR_PROTECTION +#define PO_FIRST_ARMOUR PO_LEATHER_ARMOUR +#define PO_LAST_ARMOUR PO_RIBBONS +#define PO_FIRST_RING PO_RING_REGEN +#define PO_LAST_RING PO_RING_TELEPORT +#define PO_FIRST_FOOD PO_IRON_RATION +#define PO_LAST_FOOD PO_DEVIL_SPLEEN +#define NUM_OF_PERMOBJS ((PO_LAST_FOOD) + 1) + +struct permobj { + const char name[48]; + const char plural[48]; + const char *description; + 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 depth; /* If greater than 1, this item cannot be given out + * by get_random_pobj() before the specified depth. */ +}; +extern struct permobj permobjs[NUM_OF_PERMOBJS]; + +/* XXX struct mon */ +#define MONSTERS_IN_PLAY 100 +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; + int next_summon; +}; +extern struct mon monsters[MONSTERS_IN_PLAY]; + +/* XXX struct obj */ +#define OBJ_MAX_DUR 100 +#define OBJECTS_IN_PLAY 100 +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. */ +}; +extern struct obj objects[OBJECTS_IN_PLAY]; + +/* 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 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); +extern void touch_one_screen(int y, int x); + +/* "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 uint32_t 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 void explore_around(int y, int x); + +#define LEVGEN_WALK_CELLS 600 +#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 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 sprint_obj_name(char *s, int obj, int len); +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 consume_obj(int obj); +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 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); +extern int magic_ring(void); +extern int emanate_armour(void); +extern int zap_weapon(void); + +/* XXX rng.c data and funcs */ +#define RNG_MAX 0xFFFFFFFFu +extern uint32_t rng_state[5]; +extern uint32_t saved_state[5]; +extern uint32_t 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; +#endif + +/* victrix-abyssi.h */ +// vim:cindent -- 2.11.0