GENERATED_OBJS:=permobj.o permons.o
GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh
GENERATED_MAKES:=dirs.mk features.mk
-HANDWRITTEN_OBJS:=cave.o combat.o coord.o display-nc.o fov.o log.o main.o map.o monsters.o mon2.o notify-local-tty.o objects.o pmon2.o rng.o shrine.o sorcery.o u.o util.o
+HANDWRITTEN_OBJS:=cave.o combat.o combo.o coord.o deeds.o display-nc.o dungeon.o fov.o log.o main.o map.o monsters.o mon2.o mon3.o notify-local-tty.o objects.o obj2.o pmon2.o rng.o role.o shrine.o skills.o sorcery.o u.o util.o
OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS)
GAME:=victrix-abyssi
MAJVERS:=0
PATCHVERS:=1
COMMON_CFLAGS:=-Wall -Wwrite-strings -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS)-std=gnu11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
COMMON_CXXFLAGS:=-Wall -Wwrite-strings -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS) -std=gnu++11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
-PRODUCTION_CFLAGS:=$(COMMON_CFLAGS)
+PRODUCTION_CFLAGS:=$(COMMON_CFLAGS) -O2
DEVELOPMENT_CFLAGS:=$(COMMON_CFLAGS) -g -Werror
-PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS)
+PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS) -O2
DEVELOPMENT_CXXFLAGS:=$(COMMON_CXXFLAGS) -g -Werror
LIBS=-lpanelw -lncursesw -lxdg-basedir -lm
-ARCHIVEDIR:=victrix-abyssi-$(MAJVERS).$(MINVERS).$(PATCHVERS)
-ARCHIVENAME:=victrix-abyssi_$(MAJVERS).$(MINVERS).$(PATCHVERS)
+ARCHIVEDIR:=$(GAME)-$(MAJVERS).$(MINVERS).$(PATCHVERS)
+ARCHIVENAME:=$(GAME)_$(MAJVERS).$(MINVERS).$(PATCHVERS)
COMMON_LDFLAGS:=-Wl,-z,relro
DEVELOPMENT_LDFLAGS:=$(COMMON_LDFLAGS) -g
my-debworkflow: my-debclean debianize-archive
mkdir archive-test
- cp $(ARCHIVENAME).orig.tar.gz archive-test
+ mv $(ARCHIVENAME).orig.tar.gz archive-test
(cd archive-test && tar xzf $(ARCHIVENAME).orig.tar.gz)
cp -R debian archive-test/$(ARCHIVEDIR)/debian
(cd archive-test/$(ARCHIVEDIR) && debuild -uc -us)
permons.cc pmon_id.hh: $(srcdir)/default.permons $(srcdir)/pmon_comp
$(srcdir)/pmon_comp $<
## Dependencies for the build
-cave.o: $(srcdir)/cave.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
+cave.o: $(srcdir)/cave.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
-combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+combo.o: $(srcdir)/combo.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh
-display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+deeds.o: $(srcdir)/deeds.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/$(GAME).hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/permon.hh $(srcdir)/permobj.hh
log.o: $(srcdir)/log.cc $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/util.h
-main.o: $(srcdir)/main.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+main.o: $(srcdir)/main.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-map.o: $(srcdir)/map.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/map.hh $(srcdir)/core.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+map.o: $(srcdir)/map.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/core.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-monsters.o: $(srcdir)/monsters.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+monsters.o: $(srcdir)/monsters.cc $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-mon2.o: $(srcdir)/mon2.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/sorcery.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+mon2.o: $(srcdir)/mon2.cc $(srcdir)/$(GAME).hh $(srcdir)/sorcery.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-notify-local-tty.o: $(srcdir)/notify-local-tty.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/combat.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/sorcery.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+mon3.o: $(srcdir)/mon3.cc $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/map.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+notify-local-tty.o: $(srcdir)/notify-local-tty.cc $(srcdir)/$(GAME).hh $(srcdir)/combat.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/sorcery.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
permobj.o: ./permobj.cc $(srcdir)/core.hh $(srcdir)/permobj.hh
permons.o: ./permons.cc $(srcdir)/core.hh $(srcdir)/permon.hh
-pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+objects.o: $(srcdir)/objects.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+obj2.o: $(srcdir)/obj2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+rng.o: $(srcdir)/rng.cc $(srcdir)/rng.hh
+
+role.o: $(srcdir)/role.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+shrine.o: $(srcdir)/shrine.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
-objects.o: $(srcdir)/objects.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+skills.o: $(srcdir)/skills.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-shrine.o: $(srcdir)/shrine.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
-sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
-u.o: $(srcdir)/u.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+u.o: $(srcdir)/u.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
util.o: $(srcdir)/util.c $(srcdir)/util.h
#include "monsters.hh"
#include "mapgen.hh"
-void place_cave_stairs(void)
+void place_cave_stairs(Level *l)
{
Coord c;
Coord d;
int stair_tries = 0;
do
{
- c = lvl.random_point(1);
- } while (!(terrain_props[lvl.terrain_at(c)].flags & TFLAG_floor) ||
- (lvl.flags_at(c) & MAPFLAG_HARDWALL));
- lvl.add_stairs_at(c, STAIRS_DOWN, lvl.self.naive_next());
+ c = l->random_point(1);
+ } while (!(terrain_props[l->terrain_at(c)].flags & TFLAG_floor) ||
+ (l->flags_at(c) & MAPFLAG_HARDWALL));
+ l->add_stairs_at(c, PORTAL_ONWARD, l->self.naive_next());
do
{
- d = lvl.random_point(1);
+ d = l->random_point(1);
++stair_tries;
- } while (!(terrain_props[lvl.terrain_at(d)].flags & TFLAG_floor) ||
- (lvl.flags_at(d) & MAPFLAG_HARDWALL) ||
+ } while (!(terrain_props[l->terrain_at(d)].flags & TFLAG_floor) ||
+ (l->flags_at(d) & MAPFLAG_HARDWALL) ||
(d.dist_cheb(c) < (10 - (stair_tries / 40))));
- lvl.add_stairs_at(d, STAIRS_UP, lvl.self.naive_prev());
+ l->add_stairs_at(d, PORTAL_LANDING, l->self.naive_prev());
}
/*! \brief Excavate a cave level.
*
* This algorithm runs two random walks on the map.
*/
-void build_level_cave(void)
+void build_level_cave(Level *l)
{
Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
int num_pools;
int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
- initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
- run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
//run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
- if ((lvl.theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
+ if ((l->theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
{
num_pools = inc_flat(1, 4);
walk_data[0] = 2;
{
int pool_size = inc_flat(9, 36);
do {
- c = lvl.random_point(2);
- } while (lvl.terrain_at(c) != FLOOR);
- run_random_walk(c, excavation_write, walk_data, pool_size);
+ c = l->random_point(2);
+ } while (l->terrain_at(c) != FLOOR);
+ try
+ {
+ run_random_walk(l, c, excavation_write, walk_data, pool_size);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
--num_pools;
}
- place_cave_stairs();
+ place_cave_stairs(l);
}
/*! \brief Excavate a cave level with intrusions */
-void build_level_intrusions(void)
+void build_level_intrusions(Level *l)
{
Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
int i;
int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
- initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
for (i = 0; i < 6; ++i)
{
- place_random_intrusion(WALL);
+ place_random_intrusion(l, WALL);
+ }
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
}
- run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
/* and now the stairs */
- place_cave_stairs();
+ place_cave_stairs(l);
+}
+
+/*! \brief Excavate a "pits" level. */
+void build_level_pits(Level *l, Terrain pit_type)
+{
+ Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
+ int walk_data[4] = { 1, FLOOR, WALL, pit_type };
+
+ initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ walk_data[1] = pit_type;
+ c.y = GUIDE_EDGE_SIZE / 6;
+ c.x = GUIDE_EDGE_SIZE / 6;
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
+ c.x = GUIDE_EDGE_SIZE - (GUIDE_EDGE_SIZE / 6);
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
+ c.y = GUIDE_EDGE_SIZE - (GUIDE_EDGE_SIZE / 6);
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
+ c.x = (GUIDE_EDGE_SIZE / 6);
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
+ walk_data[0] = 2;
+ walk_data[1] = FLOOR;
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
+ place_cave_stairs(l);
}
/* cave.cc */
int damage;
if (u.weapon != NO_OBJ)
{
- dmgbase = permobjs[objects[u.weapon].obj_id].power + (u.body / 10);
+ dmgbase = permobjs[objects[u.weapon].po_ref].power + (u.body / 10);
damage = dmgbase / 3 + one_die(dmgbase - dmgbase / 3);
}
else
/*! \brief Calculate the effect of the player's damage amplifier ring
*/
-bool ring_effectiveness(int mon, int ring_pobj, int damage, int *bonus_damage, int *vamp_healing)
+bool damage_amplification(Mon_handle mon, int ring_pobj, int damage, int *bonus_damage, int *vamp_healing)
{
bool rv = false;
- int pm = monsters[mon].mon_id;
+ int pm = monsters[mon].pm_ref;
*vamp_healing = 0;
- switch (ring_pobj)
+ *bonus_damage = 0;
+ // Fire and frost mutually exclude.
+ if (u.damage_amp[DT_FIRE] && !pmon_resists_fire(pm))
{
- case PO_FIRE_RING:
- if (!pmon_resists_fire(pm))
- {
- *bonus_damage = dice(2, 4) + ((damage + 1) / 2);
- rv = true;
- }
- break;
- case PO_VAMPIRE_RING:
- if (!pmon_resists_necro(pm))
- {
- *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
- *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
- rv = true;
- }
- break;
- case PO_FROST_RING:
- if (!pmon_resists_cold(pm))
- {
- *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
- rv = true;
- }
- break;
- default:
- break;
+ *bonus_damage += dice(2, 4) + ((damage + 1) / 2);
+ rv = true;
+ }
+ else if (u.damage_amp[DT_COLD] && !pmon_resists_cold(pm))
+ {
+ *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
+ rv = true;
+ }
+ if (u.damage_amp[DT_NECRO] && !pmon_resists_necro(pm))
+ {
+ *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
+ *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
+ rv = true;
+ }
+ // only one "slay" applies
+ if (u.damage_amp[DT_SLAY_DEMON] && pmon_is_demonic(pm))
+ {
+ *bonus_damage += damage;
+ }
+ else if (u.damage_amp[DT_SLAY_UNDEAD] && pmon_is_undead(pm))
+ {
+ *bonus_damage += damage;
}
return rv;
}
* \param mon Monster hit
* \param damage Rolled damage before rings are applied
*/
-void resolve_player_melee(int mon, int damage)
+void resolve_player_melee(Mon_handle mon, int damage)
{
bool ring_eff;
int ring_bonus;
int healing = 0;
if (u.ring != NO_OBJ)
{
- ring_eff = ring_effectiveness(mon, objects[u.ring].obj_id, damage, &ring_bonus, &healing);
+ ring_eff = damage_amplification(mon, objects[u.ring].po_ref, damage, &ring_bonus, &healing);
if (ring_eff)
{
- notify_ring_boost(mon, objects[u.ring].obj_id);
+ notify_ring_boost(mon, objects[u.ring].po_ref);
damage += ring_bonus;
}
}
}
if (u.weapon != NO_OBJ)
{
- damage_obj(u.weapon);
+ if (permobjs[objects[u.weapon].po_ref].flags[0] & POF_DAMAGEABLE)
+ {
+ damage_obj(u.weapon);
+ }
}
}
{
Coord c = u.pos + delta;
int damage;
- int mon = lvl.mon_at(c);
+ Mon_handle mon = lvl.mon_at(c);
/* Power Attack: Always hit, do +75% damage. */
notify_player_combo_powatk(mon);
damage = (player_melee_base() * 7) / 4;
return Cost_std;
}
-int uhitm(int mon)
+Action_cost player_charge(Offset step, int power)
+{
+ Mon const *mp;
+ Coord c = u.pos + step;
+ int mon = lvl.mon_at(c);
+ int damage;
+ mp = mon_snap(mon);
+ notify_player_charge_mon(mon);
+ /* +25%, and a further +25% for each step in the charge */
+ damage = (player_melee_base() * (5 + power)) / 4;
+ resolve_player_melee(mon, damage);
+ return Cost_std; /* Hit. */
+}
+
+int uhitm(Mon_handle mon)
{
- Mon *mp;
+ Mon const *mp;
int tohit;
int damage;
int hitbase = u.agility + u.level;
- mp = monsters + mon;
+ mp = mon_snap(mon);
tohit = hitbase / 3 + zero_die(hitbase - hitbase / 3);
if (tohit < mp->defence)
{
int range;
Coord c = u.pos + step;
bool done = false;
- int mon;
+ Mon_handle mon;
Mon *mptr;
Obj *wep;
Permobj *pwep;
int damage;
- wep = objects + u.weapon;
- pwep = permobjs + wep->obj_id;
+ wep = obj_snapv(u.weapon);
+ pwep = permobjs + wep->po_ref;
damage = one_die(pwep->power);
for (range = 1; !done; ++range, (c += step))
{
if (mon != NO_MON)
{
done = true;
- mptr = monsters + mon;
+ mptr = mon_snapv(mon);
tohit = zero_die(u.agility + u.level - range);
if (range == 1)
{
notify_player_hurt_mon(mon, damage);
}
damage_mon(mon, damage, true);
- if ((mptr->used) && (wep->obj_id == PO_THUNDERBOW))
+ if ((mptr->flags & MF_ALIVE) && (wep->po_ref == PO_THUNDERBOW))
{
int kb = knockback_mon(mon, step, true, true);
switch (kb)
return 0;
}
-int mhitu(int mon, Damtyp dtype)
+int mhitu(Mon_handle mon, Damtyp dtype)
{
int tohit;
int damage;
int unaffected;
- Mon *mptr = monsters + mon;
+ Mon *mptr = mon_snapv(mon);
tohit = zero_die(mptr->mtohit + 5);
if (tohit < u.defence)
{
if ((u.armour != NO_OBJ) && (tohit > agility_modifier()))
{
/* Monster hit your armour. */
- damage_obj(u.armour);
+ if (permobjs[objects[u.armour].po_ref].flags[0] & POF_DAMAGEABLE)
+ {
+ damage_obj(u.armour);
+ }
notify_mon_hit_armour(mon);
}
else
else
{
notify_player_touch_effect(dtype);
- if ((mptr->mon_id == PM_VAMPIRE) && !u.resists(DT_NECRO))
+ if ((mptr->pm_ref == PM_VAMPIRE) && !u.resists(DT_NECRO))
{
heal_mon(mon, damage * 2 / 5, 1);
- } else if ((tohit - u.defence >= 5) && (mptr->mon_id == PM_SNAKE))
+ } else if ((tohit - u.defence >= 5) && (mptr->pm_ref == PM_SNAKE))
{
drain_body(1, "snake venom", 0);
}
- damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+ damage_u(damage, DEATH_KILLED_MON, permons[mptr->pm_ref].name);
}
return 1;
}
-int mshootu(int mon)
+int mshootu(Mon_handle mon)
{
Mon *mptr;
Mon *bystander;
int evasion;
int defence;
Damtyp dtype;
- mptr = monsters + mon;
+ mptr = mon_snapv(mon);
c = mptr->pos;
/* dy, dx == trajectory of missile */
delta = u.pos.delta(c);
step = mysign(delta);
/* Don't get the bonus that applies to melee attacks. */
tohit = zero_die(mptr->rtohit);
- dtype = permons[mptr->mon_id].rdtyp;
+ dtype = permons[mptr->pm_ref].rdtyp;
notify_mon_ranged_attack(mon);
if ((dtype == DT_NECRO) || (dtype == DT_ELEC))
{
/* Move projectile one square before looking for targets. */
for ((done = 0), (c = mptr->pos + step); !done; c += step)
{
- int victim;
+ Mon_handle victim;
if ((lvl.terrain_at(c) == WALL) || (lvl.terrain_at(c) == DOOR))
{
done = 1;
if (!unaffected)
{
damage = one_die(mptr->rdam);
- damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+ damage_u(damage, DEATH_KILLED_MON, permons[mptr->pm_ref].name);
}
return 1;
}
else if (victim != NO_MON)
{
done = 1;
- bystander = monsters + victim;
+ bystander = mon_snapv(victim);
switch (dtype)
{
case DT_COLD:
- if (pmon_resists_cold(bystander->mon_id))
+ if (pmon_resists_cold(bystander->pm_ref))
{
unaffected = 1;
}
}
break;
case DT_FIRE:
- if (pmon_resists_fire(bystander->mon_id))
+ if (pmon_resists_fire(bystander->pm_ref))
{
unaffected = 1;
}
}
break;
case DT_NECRO:
- if (pmon_is_undead(bystander->mon_id))
+ if (pmon_is_undead(bystander->pm_ref))
{
unaffected = 1;
}
#ifndef COMBAT_HH
#define COMBAT_HH
-#ifndef VICTRIX_ABYSSI_HH
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
#endif
#ifndef MONSTERS_HH
#define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5))
/* XXX combat.c data and funcs */
-extern Action_cost throw_flask(int obj, Offset step);
-extern Action_cost player_attack(Offset step);
-extern Action_cost player_power_attack(Offset step);
-extern void resolve_player_melee(int mon, int damage);
-extern int mhitu(int mon, Damtyp dtyp);
-extern int uhitm(int mon);
-extern int mshootu(int mon);
-extern int ushootm(Offset step);
+Action_cost player_attack(Offset step);
+Action_cost player_power_attack(Offset step);
+Action_cost player_charge(Offset step, int power);
+void resolve_player_melee(Mon_handle mon, int damage);
+int mhitu(Mon_handle mon, Damtyp dtyp);
+int uhitm(Mon_handle mon);
+int mshootu(Mon_handle mon);
+int ushootm(Offset step);
class Combo_spec
{
--- /dev/null
+/*! \file combo.cc
+ * \brief Combo detection for Obumbrata et Velata
+ */
+
+/*
+ * Copyright © 2014 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.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/* Design note: Completing a combo discards the moves that comprised the
+ * combo from the list, and pushes the combo's special action itself if the
+ * combo is marked as Combo_valid. */
+
+static bool rewrite_stand_still_1(Action *revisable_action)
+{
+ return false;
+}
+
+static bool rewrite_walk_1(Action *revisable_action)
+{
+ Mon_handle mon;
+ Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] };
+ Coord c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c);
+ revisable_action->cmd = REJECTED_ACTION;
+ return true;
+ }
+ else if (lvl.mon_at(c) != NO_MON)
+ {
+ mon = lvl.mon_at(c);
+ revisable_action->cmd = ATTACK;
+ return true;
+ }
+ return false;
+}
+
+static bool rewrite_attack_1(Action *revisable_action)
+{
+ Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] };
+ Coord c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c); // TODO notify_attack_oob()
+ revisable_action->cmd = REJECTED_ACTION;
+ return true;
+ }
+ return false;
+}
+
+Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS] =
+{
+ rewrite_walk_1,
+ rewrite_stand_still_1,
+ nullptr, // rewrite_ascend_1
+ nullptr, // rewrite_descend_1
+ rewrite_attack_1,
+ nullptr, // rewrite_get_1
+ nullptr, // rewrite_drop_1
+ nullptr, // rewrite_wield_1
+ nullptr, // rewrite_armour_on_1
+ nullptr, // rewrite_armour_off_1
+ nullptr, // rewrite ring_on_1
+ nullptr, // rewrite_ring_off_1
+ nullptr, // rewrite_quaff_1
+ nullptr, // rewrite_read_1
+ nullptr, // rewrite_throw_1
+ nullptr, // rewrite_eat_1
+ nullptr, // rewrite_emanate_1
+ nullptr, // rewrite_zap_1
+ nullptr, // rewrite_ringmagic_1
+ nullptr, // rewrite_use_skill_1
+ nullptr, // rewrite_allocate_skill_1
+ nullptr, // rewrite_save_1
+ nullptr, // rewrite_quit_1
+ nullptr, // rewrite_wizard_levup_1
+ nullptr, // rewrite_wizard_descend_1
+ nullptr, // rewrite_powatk_1
+};
+
+Combo_phase action_default_combophase[NUM_COMMANDS] =
+{
+ Combo_valid, // walk
+ Combo_valid, // stand still
+ Combo_invalid, // go up
+ Combo_invalid, // go down
+ Combo_finisher, // attack
+ Combo_invalid, // get
+ Combo_invalid, // drop
+ Combo_invalid, // wield
+ Combo_invalid, // wear
+ Combo_invalid, // take off
+ Combo_invalid, // put on
+ Combo_invalid, // remove
+ Combo_invalid, // quaff
+ Combo_invalid, // read
+ Combo_invalid, // throw
+ Combo_invalid, // eat
+ Combo_invalid, // emanate
+ Combo_invalid, // zap
+ Combo_invalid, // magic (ring)
+ Combo_invalid, // use skill
+ Combo_invalid, // allocate skill poiont
+ Combo_invalid, // save
+ Combo_invalid, // quit
+ Combo_invalid, // wizard mode levelup
+ Combo_invalid, // wizard mode descend
+ Combo_invalid, // combo: power attack
+};
+
+static bool powatk_avail(Player const *player)
+{
+ if (player->known_skill[Skill_power_attack])
+ {
+ Obj const *optr = obj_snap(player->weapon);
+ if (optr && (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON))
+ {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+static Combo_state powatk_detector(std::deque<Action>* action_list)
+{
+ if ((*action_list)[0].cmd == STAND_STILL)
+ {
+ if ((*action_list).size() > 1)
+ {
+ if ((*action_list)[1].cmd == ATTACK)
+ {
+ (*action_list)[0] = (*action_list)[1];
+ (*action_list)[0].cmd = COMBO_POWATK;
+ action_list->resize(1);
+ return Combo_state_complete;
+ }
+ return Combo_state_none;
+ }
+ else
+ {
+ return Combo_state_partial;
+ }
+ }
+ else
+ {
+ DEBUG_CONTROL_FLOW();
+ return Combo_state_none;
+ }
+}
+
+static bool blade_dance_avail(Player const *player)
+{
+ if (player->known_skill[Skill_blade_dance])
+ {
+ Obj const *optr = obj_snap(player->weapon);
+ if (optr)
+ {
+ if (po_is_sword(optr->po_ref) || po_is_dagger(optr->po_ref))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static Combo_state blade_dance_detector(std::deque<Action>* action_list)
+{
+ if ((*action_list)[0].cmd == WALK)
+ {
+ if (action_list->size() == 1)
+ {
+ Offset o = { (int32_t) (*action_list)[0].details[0], (int32_t) (*action_list)[0].details[1] };
+ if (myabs(o.y) == myabs(o.x))
+ {
+ Offset o1 = { o.y, 0 };
+ Offset o2 = { 0, o.x };
+ Coord c1 = u.pos + o1;
+ Coord c2 = u.pos + o2;
+ Mon_handle m1 = lvl.mon_at(c1);
+ Mon_handle m2 = lvl.mon_at(c2);
+ Coord target;
+ if ((m1 != NO_MON) && ((m2 == NO_MON) || (zero_die(2))))
+ {
+ target = c1;
+ }
+ else if (m2 != NO_MON)
+ {
+ target = c2;
+ }
+ else
+ {
+ return Combo_state_none;
+ }
+ action_list->resize(1);
+ (*action_list)[0].cmd = COMBO_BLADE_DANCE;
+ (*action_list)[0].details[0] = o.y;
+ (*action_list)[0].details[1] = o.x;
+ (*action_list)[0].details[2] = target.y;
+ (*action_list)[0].details[3] = target.x;
+ return Combo_state_complete;
+ }
+ }
+ return Combo_state_none;
+ }
+ else
+ {
+ DEBUG_CONTROL_FLOW();
+ return Combo_state_none;
+ }
+}
+
+static bool charge_avail(Player const *player)
+{
+ if (player->known_skill[Skill_charge])
+ {
+ Obj const *optr = obj_snap(player->weapon);
+ if (optr && (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON))
+ {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+static Combo_state charge_detector(std::deque<Action>* action_list)
+{
+ if ((*action_list)[0].cmd == WALK)
+ {
+ uint32_t size = action_list->size();
+ if (size > 1)
+ {
+ bool valid = true;
+ for (uint32_t i = 1; i < size; ++i)
+ {
+ if (i == (size - 1))
+ {
+ if (((*action_list)[size - 1].cmd == ATTACK) &&
+ ((*action_list)[size - 1].details[0] ==
+ (*action_list)[0].details[0]) &&
+ ((*action_list)[size - 1].details[1] ==
+ (*action_list)[0].details[1]))
+ {
+ (*action_list)[0].cmd = COMBO_CHARGE;
+ (*action_list)[0].details[2] = i;
+ action_list->resize(1);
+ return Combo_state_complete;
+ }
+ }
+ if ((*action_list)[i] != (*action_list)[0])
+ {
+ // Do we have a prefix consisting entirely of identical
+ // WALK actions?
+ valid = false;
+ }
+ }
+ if (!valid)
+ {
+ return Combo_state_none;
+ }
+ }
+ return Combo_state_partial;
+ }
+ else
+ {
+ DEBUG_CONTROL_FLOW();
+ return Combo_state_none;
+ }
+}
+
+static bool flying_leap_avail(Player const *player)
+{
+ if (player->known_skill[Skill_flying_leap])
+ {
+ if (u.armour == NO_OBJ)
+ {
+ return true;
+ }
+ Obj const *optr = obj_snap(u.armour);
+ if (po_is_dress(optr->po_ref) || po_is_leatherwear(optr->po_ref) ||
+ po_is_robe(optr->po_ref))
+ {
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+static Combo_state flying_leap_detector(std::deque<Action>* action_list)
+{
+ if ((*action_list)[0].cmd == WALK)
+ {
+ uint32_t size = action_list->size();
+ if (size == 1)
+ {
+ Offset o = { (int32_t) (*action_list)[0].details[0], (int32_t) (*action_list)[0].details[1] };
+ Coord c = u.pos + o;
+ Mon_handle mon = lvl.mon_at(c);
+ if (mon != NO_MON)
+ {
+ return Combo_state_none;
+ }
+ Terrain t = lvl.terrain_at(c);
+ if (terrain_gapes(t) || terrain_drowns(t) || terrain_is_hot(t))
+ {
+ Coord c2 = c + o;
+ Terrain t2 = lvl.terrain_at(c2);
+ Mon_handle mon = lvl.mon_at(c2);
+ if (terrain_is_floor(t2) && (mon == NO_MON))
+ {
+ (*action_list)[0].cmd = COMBO_FLYING_LEAP;
+ (*action_list)[0].details[0] = o.y*2;
+ (*action_list)[0].details[1] = o.x*2;
+ return Combo_state_complete;
+ }
+ }
+ }
+ return Combo_state_none;
+ }
+ else
+ {
+ DEBUG_CONTROL_FLOW();
+ return Combo_state_none;
+ }
+}
+
+Combo_entry combo_entries[] =
+{
+ { "charge", WALK, charge_avail, charge_detector },
+ { "flying leap", WALK, flying_leap_avail, flying_leap_detector },
+ { "blade dance", WALK, blade_dance_avail, blade_dance_detector },
+ { "power attack", STAND_STILL, powatk_avail, powatk_detector },
+ { "empty fencepost entry", REJECTED_ACTION, nullptr, nullptr }
+};
+
+/* combo.cc */
+// vim:cindent:expandtab
#define COORD_HH
#include <algorithm>
+#include <stdint.h>
template <typename T> T myabs(T val);
template <typename T> T mysign(T val);
class Offset
{
public:
- int y;
- int x;
+ int32_t y;
+ int32_t x;
bool ecardinal(void) const { return ((y && !x) || (x && !y)); }
bool rcardinal(void) const { return ecardinal() || (myabs(y) == myabs(x)); }
int len_cheb(void) const { return std::max(myabs(y), myabs(x)); }
bool operator <=(Offset const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
bool operator >(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
bool operator >=(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
- void clamp(int ymin, int xmin, int ymax, int xmax)
+ void clamp(int32_t ymin, int32_t xmin, int32_t ymax, int32_t xmax)
{
y = std::min(ymax, std::max(ymin, y));
x = std::min(xmax, std::max(xmin, x));
class Coord
{
public:
- int y;
- int x;
+ int32_t y;
+ int32_t x;
int dist_cheb(Coord const& right) const { return std::max(myabs(y - right.y), myabs(x - right.x)); }
int dist_taxi(Coord const& right) const { return myabs(y - right.y) + myabs(x - right.x); }
int distsq_eucl(Coord const& right) const { return (y - right.y) * (y - right.y) + (x - right.x) * (x - right.x); }
Offset delta(Coord const& right) const { Offset d = { y - right.y, x - right.x }; return d; }
Coord operator +(Offset const& right) const { Coord c = { y + right.y, x + right.x}; return c; }
Coord operator -(Offset const& right) const { Coord c = { y - right.y, x - right.x}; return c; }
+ Offset operator -(Coord const& right) const { Offset o = { y - right.y, x - right.x}; return o; }
Coord const& operator +=(Offset const& right) { y += right.y; x += right.x; return *this;}
Coord const& operator -=(Offset const& right) { y -= right.y; x -= right.x; return *this;}
bool operator !=(Coord const& right) const { return (y != right.y) || (x != right.x); }
bool operator <=(Coord const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
bool operator >(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
bool operator >=(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
- void clamp(int ymin, int xmin, int ymax, int xmax)
+ void clamp(int32_t ymin, int32_t xmin, int32_t ymax, int32_t xmax)
{
y = std::min(ymax, std::max(ymin, y));
x = std::min(xmax, std::max(xmin, x));
/*! \file core.hh
- * \brief Essential predefinitions header for Victrix Abyssi
+ * \brief Essential predefinitions header
*/
/* Copyright 2014 Martin Read
#include <stdbool.h>
#include <stdio.h>
#include <arpa/inet.h>
+#include <deque>
#ifndef COORD_HH
#include "coord.hh"
*/
enum Gamecolour
{
+ Gcol_nocolour = -1,
/* l_cyan is the last "core" colour. Colours after it may alias to one
* of the core colours depending on the nature of your display. */
- Gcol_l_grey, Gcol_d_grey, Gcol_red, Gcol_blue,
+ Gcol_l_grey = 0, Gcol_d_grey, Gcol_red, Gcol_blue,
Gcol_green, Gcol_purple, Gcol_brown, Gcol_cyan,
Gcol_white, Gcol_l_red, Gcol_l_blue, Gcol_l_green,
Gcol_l_purple, Gcol_yellow, Gcol_l_cyan,
/* Fancy colours now! */
- Gcol_iron, Gcol_gold, Gcol_silver,
+ Gcol_iron, Gcol_gold, Gcol_silver, Gcol_blood, Gcol_pus,
/* UI customizable colours */
Gcol_prio_low, Gcol_prio_normal, Gcol_prio_alert, Gcol_prio_warn, Gcol_prio_bug
};
/*! \brief Identification code for damage types */
enum Damtyp {
+ DT_NONE = -1,
DT_PHYS = 0, DT_COLD, DT_FIRE, DT_NECRO,
DT_ELEC, DT_HELLFIRE, DT_POISON,
- DT_KNOCKBACK, DT_DROWNING
+ DT_KNOCKBACK, DT_DROWNING,
+ // Now the special DTs for class bonuses
+ DT_SLAY_DEMON, DT_SLAY_UNDEAD
};
-#define DT_COUNT (1 + DT_DROWNING)
+#define LAST_DAMTYPE (DT_SLAY_UNDEAD)
+#define NUM_DAMTYPES (1 + LAST_DAMTYPE)
+extern char const *damtype_names[NUM_DAMTYPES];
/*! \brief Identification code for player actions */
enum Game_cmd {
ATTACK,
GET_ITEM, DROP_ITEM,
WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING,
- QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD,
+ QUAFF_POTION, READ_SCROLL, THROW_ITEM, EAT_FOOD,
EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING,
USE_ACTIVE_SKILL, ALLOCATE_SKILL_POINT,
SAVE_GAME, QUIT,
WIZARD_LEVELUP, WIZARD_DESCEND,
/* combos begin here */
- CMD_POWER_ATTACK
+ COMBO_POWATK, COMBO_BLADE_DANCE, COMBO_CHARGE, COMBO_FLYING_LEAP
+};
+#define LAST_COMMAND (COMBO_FLYING_LEAP)
+#define NUM_COMMANDS (1 + LAST_COMMAND)
+
+/*! \brief Is this action a valid combo step? */
+enum Combo_phase
+{
+ Combo_invalid, // No, it isn't.
+ Combo_valid, // Yes, it is.
+ Combo_finisher // Only at the end of a combo.
+};
+
+/*! \brief Did we detect a combo? */
+enum Combo_state
+{
+ Combo_state_none, // We did not detect the combo
+ Combo_state_partial, // We detected a leading subset of the combo
+ Combo_state_complete // We detected the whole combo
};
/*! \brief Identification code for ways to die
DEATH_LASH, DEATH_RIBBONS
};
-/*! \brief Fell powers that might influence the Princess's fate
+/*! \brief Fell powers from forbidden places
*/
enum Fell_powers {
FePo_iron,
#define TOTAL_FELL_POWERS (1 + FePo_flesh)
-#define RESIST_MASK_TEMPORARY 0x0000FFFFu
-#define RESIST_MASK_PERM_EQUIP 0xFFFF0000u
-#define RESIST_RING 0x00010000u
-#define RESIST_ARMOUR 0x00020000u
+#define BONUS_MASK_TEMPORARY 0x0000FFFFu
+#define BONUS_MASK_PERM_EQUIP 0x7FFF0000u
+#define BONUS_MASK_PERM_SKILL 0x80000000u
+#define BONUS_RING 0x00010000u
+#define BONUS_ARMOUR 0x00020000u
+#define BONUS_WEAPON 0x00040000u
/*! \brief Represent an in-game action by the player
*
{
Game_cmd cmd;
uint32_t details[8];
+ bool operator ==(Action const& right)
+ {
+ if (cmd != right.cmd) return false;
+ for (int i = 0; i < 8; ++i)
+ {
+ if (details[i] != right.details[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator !=(Action const& right)
+ {
+ if (cmd != right.cmd) return true;
+ for (int i = 0; i < 8; ++i)
+ {
+ if (details[i] != right.details[i])
+ {
+ return true;
+ }
+ }
+ return false;
+ }
};
-
class Level;
class Level_key;
class Permobj;
class Permon;
class Obj;
+typedef uint32_t Obj_handle;
class Mon;
+typedef uint32_t Mon_handle;
+
+enum Decal_tag
+{
+ NO_DECAL = -1,
+ Decal_blood,
+ Decal_ichor,
+ Decal_slime,
+ Decal_pus
+};
+#define MAX_DECAL (Decal_pus)
+#define NUM_DECALS (MAX_DECAL + 1)
#define NO_POBJ (-1)
#define NO_PMON (-1)
-#define NO_OBJ (-1)
-#define NO_MON (-1)
+extern const Obj_handle NO_OBJ;
+extern const Mon_handle NO_MON;
#define NO_REGION 0xffffffffu
+struct Inferno_detail
+{
+ bool by_you;
+ Mon_handle caster;
+ int dice_count;
+ int dice_sides;
+ int dice_bonus;
+};
+
+enum Role_id {
+ Role_none = -1,
+ Role_princess = 0,
+ Role_demon_hunter,
+ Role_thanatophile
+};
+#define LAST_ROLE (Role_thanatophile)
+#define NUM_ROLES (1 + LAST_ROLE)
+
+#define OBUMBRATA_MAGIC_NUMBER 0x0b756d62
+
+extern void game_cleanup(void);
+
+struct Obumb_sysexcep
+{
+ int err; // errno value
+ Obumb_sysexcep() = delete;
+ Obumb_sysexcep(int e) : err(e) { }
+};
+
+struct Obumb_dataexcep
+{
+ char const *str;
+ Obumb_dataexcep() = delete;
+ Obumb_dataexcep(char const *s) : str(s) { }
+};
+
+extern bool game_finished;
+extern int game_tick;
+extern bool wizard_mode;
+
#endif
-/* victrix-abyssi.hh */
+/* core.hh */
// vim:cindent:ts=8:sw=4:expandtab
--- /dev/null
+/*! \file deeds.cc
+ * \brief Player-character action implementations
+ */
+
+/*
+ * Copyright © 2014 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.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include "player.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+static Action_cost deed_walk(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ return move_player(step);
+}
+
+static Action_cost deed_stand_still(Action const *act)
+{
+ return Cost_std;
+}
+
+static Action_cost deed_ascend(Action const *act)
+{
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ if (terrain_props[t].flags & TFLAG_ascend)
+ {
+ notify_inert_portal();
+ }
+ else
+ {
+ leave_level();
+ make_new_level();
+ }
+ }
+ else
+ {
+ if (terrain_props[t].flags & TFLAG_ascend)
+ {
+ notify_ascent_blocked();
+ }
+ else
+ {
+ debug_ascend_non_stairs();
+ }
+ }
+ return Cost_none;
+}
+
+static Action_cost deed_descend(Action const *act)
+{
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ if (terrain_props[t].flags & TFLAG_ascend)
+ {
+ notify_inert_portal();
+ }
+ else
+ {
+ leave_level();
+ make_new_level();
+ }
+ }
+ else
+ {
+ if (terrain_props[t].flags & TFLAG_descend)
+ {
+ leave_level();
+ make_new_level();
+ }
+ else
+ {
+ debug_descend_non_stairs();
+ }
+ }
+ return Cost_none;
+}
+
+static Action_cost deed_attack(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ return player_attack(step);
+}
+
+static Action_cost deed_get(Action const *act)
+{
+ if (lvl.obj_at(u.pos) != NO_OBJ)
+ {
+ attempt_pickup();
+ return Cost_std;
+ }
+ else
+ {
+ notify_nothing_to_get();
+ return Cost_none;
+ }
+}
+
+static Action_cost deed_drop(Action const *act)
+{
+ int slot = (int) act->details[0];
+ return drop_obj(slot);
+}
+
+static Action_cost deed_wield(Action const *act)
+{
+ int slot = (int) act->details[0];
+ if (slot == SLOT_NOTHING)
+ {
+ return player_unwield(Noise_std);
+ }
+ else
+ {
+ return player_wield(u.inventory[slot], Noise_std);
+ }
+}
+
+static Action_cost deed_armour_on(Action const *act)
+{
+ int slot = (int) act->details[0];
+ return wear_armour(u.inventory[slot]);
+}
+
+static Action_cost deed_armour_off(Action const *act)
+{
+ if (u.armour != NO_OBJ)
+ {
+ /* this actually belongs in a sensible place. */
+ int saved_armour = u.armour;
+ if (unequip_safety_check(BONUS_ARMOUR, Noise_std) == You_pass)
+ {
+ u.armour = NO_OBJ;
+ recalc_defence();
+ notify_armour_unequip(saved_armour);
+ return Cost_std;
+ }
+ return Cost_none;
+ }
+ else
+ {
+ debug_take_off_no_armour();
+ return Cost_none;
+ }
+}
+
+static Action_cost deed_ring_on(Action const *act)
+{
+ int slot = act->details[0];
+ return put_on_ring(u.inventory[slot]);
+}
+
+static Action_cost deed_ring_off(Action const *act)
+{
+ return remove_ring();
+}
+
+static Action_cost deed_quaff(Action const *act)
+{
+ int slot = act->details[0];
+ return quaff_potion(u.inventory[slot]);
+}
+
+static Action_cost deed_read(Action const *act)
+{
+ int slot = act->details[0];
+ return read_scroll(u.inventory[slot]);
+}
+
+static Action_cost deed_ringmagic(Action const *act)
+{
+ if (u.ring == NO_OBJ)
+ {
+ notify_magic_no_ring();
+ return Cost_none;
+ }
+ return magic_ring();
+}
+
+static Action_cost deed_zap(Action const *act)
+{
+ if (u.weapon == NO_OBJ)
+ {
+ notify_zap_no_weapon();
+ return Cost_none;
+ }
+ return zap_weapon();
+}
+
+static Action_cost deed_emanate(Action const *act)
+{
+ if (u.armour == NO_OBJ)
+ {
+ notify_emanate_no_armour();
+ return Cost_none;
+ }
+ return emanate_armour();
+}
+
+static Action_cost deed_eat(Action const *act)
+{
+ int slot = act->details[0];
+ return eat_food(u.inventory[slot]);
+}
+
+static Action_cost deed_combo_powatk(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ return player_power_attack(step);
+}
+
+static Action_cost deed_combo_blade_dance(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ Mon_handle mon = (Mon_handle) act->details[2];
+ Action_cost ac = move_player(step);
+ if (ac == Cost_std)
+ {
+ Mon const *mptr = mon_snap(mon);
+ Offset atkstep = mptr->pos - u.pos;
+ player_attack(atkstep);
+ }
+ return ac;
+}
+
+static Action_cost deed_combo_charge(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ int power = std::min((int) act->details[2], 4);
+ return player_charge(step, power);
+}
+
+static Action_cost deed_combo_flying_leap(Action const *act)
+{
+ Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+ notify_flying_leap();
+ reloc_player(u.pos + step);
+ return Cost_std;
+}
+
+static Action_cost deed_wiz_descend(Action const *act)
+{
+ if (wizard_mode)
+ {
+ leave_level();
+ make_new_level();
+ }
+ else
+ {
+ debug_wizmode_violation();
+ }
+ return Cost_none;
+}
+
+static Action_cost deed_wiz_levup(Action const *act)
+{
+ if (wizard_mode)
+ {
+ if (lev_threshold(u.level) != INT_MAX)
+ {
+ gain_experience((lev_threshold(u.level) - u.experience) + 1);
+ }
+ }
+ else
+ {
+ debug_wizmode_violation();
+ }
+ return Cost_none;
+}
+
+static Action_cost deed_quit(Action const *act)
+{
+ game_finished = 1;
+ return Cost_none;
+}
+
+static Action_cost deed_save(Action const *act)
+{
+ game_finished = 1;
+ save_game();
+ return Cost_none;
+}
+
+Deed_func deed_funcs[NUM_COMMANDS] =
+{
+ deed_walk,
+ deed_stand_still,
+ deed_ascend,
+ deed_descend,
+ deed_attack,
+ deed_get,
+ deed_drop,
+ deed_wield,
+ deed_armour_on,
+ deed_armour_off,
+ deed_ring_on,
+ deed_ring_off,
+ deed_quaff,
+ deed_read,
+ nullptr, // deed_throw
+ deed_eat,
+ deed_emanate,
+ deed_zap,
+ deed_ringmagic,
+ nullptr, // deed_use_skill
+ nullptr, // deed_alloc_skill
+ deed_save,
+ deed_quit,
+ deed_wiz_levup,
+ deed_wiz_descend,
+ deed_combo_powatk,
+ deed_combo_blade_dance,
+ deed_combo_charge,
+ deed_combo_flying_leap,
+};
+
+/* deeds.cc */
+// vim:cindent:expandtab
#define DISP_NC_SIDE (DISP_NC_RADIUS * 2 + 1)
/* Prototypes for static funcs */
-static cchar_t const *object_char(int object_id);
-static cchar_t const *monster_char(int monster_id);
+static cchar_t const *object_char(int po_ref);
+static cchar_t const *monster_char(int pm_ref);
static cchar_t const *terrain_char(Terrain terrain_type);
static void draw_status_line(void);
static void draw_world(void);
{ 0, Gcol_cyan }, /* Iron = dark cyan */
{ A_BOLD, Gcol_brown }, /* Gold = yellow */
{ A_BOLD, Gcol_l_grey }, /* Silver = white */
+ { 0, Gcol_red }, /* Blood = red */
+ { A_BOLD, Gcol_yellow }, /* Pus = yellow */
/* UI customizable colours */
{ A_BOLD, Gcol_d_grey },
{ 0, Gcol_l_grey },
{ A_BOLD, Gcol_blue }
};
+/*! \brief Game colours for decals */
+Gamecolour decal_colours[NUM_DECALS] =
+{
+ Gcol_red,
+ Gcol_l_blue,
+ Gcol_yellow
+};
+
/*! \brief ncursesw objects for drawing terrain */
cchar_t terrain_tiles[NUM_TERRAINS];
/*! \brief ncursesw objects for drawing items */
-cchar_t permobj_tiles[NUM_OF_PERMOBJS];
+cchar_t *permobj_tiles;
#define DISP_HEIGHT (DISP_NC_SIDE)
#define DISP_WIDTH (DISP_NC_SIDE)
/*! \brief ncursesw objects for drawing monsters */
-cchar_t permon_tiles[NUM_OF_PERMONS];
+cchar_t *permon_tiles;
/*! \brief ncursesw object for drawing unexplored space */
cchar_t blank_tile;
cchar_t const *front_buffer[DISP_HEIGHT][DISP_WIDTH];
/*! \brief Printable English-language names for damage types */
-char const *damtype_names[DT_COUNT] = {
+char const *damtype_names[NUM_DAMTYPES] = {
"physical damage",
"cold",
"fire",
"drowning",
};
+static void announce_role(Role_id role)
+{
+ switch (role)
+ {
+ case Role_none:
+ print_msg(Msg_prio::Bug, "\nBUG: no role selected.\n\n");
+ break;
+ case Role_princess:
+ print_msg("\nYou are the rightful heir to your mother's throne,\n");
+ print_msg("and have been banished to this grim abyssal\n");
+ print_msg("realm by your father's treacherous bastard. The\n");
+ print_msg("power of the royal house's magic echoes in your\n");
+ print_msg("soul, though you have little hope of directing it\n");
+ print_msg("well without the proper talismans.\n\n");
+ break;
+ case Role_demon_hunter:
+ print_msg("\nWhile fighting the members of a devil-cult, you were\n");
+ print_msg("banished to this strange, shadowy realm by the foul\n");
+ print_msg("sorcery of the cult leader. You imagine your training\n");
+ print_msg("will serve you well here, for what could be a more\n");
+ print_msg("obvious home for demons than a realm to which a cultist\n");
+ print_msg("would banish you?\n\n");
+ break;
+ case Role_thanatophile:
+ print_msg("\nPursuing a notorious necromancer, you stumbled on a\n");
+ print_msg("magical trap in his lair and found yourself banished\n");
+ print_msg("to this strange, shadowy realm. The austere arts of\n");
+ print_msg("beloved Death will serve well should you encounter the\n");
+ print_msg("walking dead in this terrible place.\n\n");
+ break;
+ }
+}
+
+static char const *tips[] = {
+ "Pay attention to your hit points.",
+ "Magic rings never wear out; don't bother carrying duplicates.",
+ "Demon hunters cannot use demonic equipment; it is unclean.",
+ "Chasms, lava, and water can all be crossed using suitable gear.",
+ "Dying with a healing potion in your inventory is embarrassing.",
+ "Fire scrolls are a powerful weapon if you resist fire.",
+ "Corpses are not a food source.",
+ "Corpses blessed by Death do not rise again to trouble the living.",
+ "The PRNG is a dispassionate algorithm, incapable of love or hate.",
+ "Life is not fair, and neither is Obumbrata et Velata.",
+ "Most weapons and armour are consumable resources.",
+ "More tips wanted. Send suggestions to the author, please."
+};
+
+int tip_pool_size = (sizeof tips) / (sizeof tips[0]);
+
+static void draw_mainmenu_tip(void)
+{
+ char const *tip = tips[zero_die(tip_pool_size)];
+ mvwprintw(fullscreen_window, 19, 1, "\n");
+ mvwprintw(fullscreen_window, 19, 31, "Tip of the moment:\n");
+ mvwprintw(fullscreen_window, 21, 1, "\n");
+ mvwprintw(fullscreen_window, 21, (40 - strlen(tip) / 2), "%s\n", tip);
+}
+
/*! \brief Draw the main menu. */
static void draw_main_menu(void)
{
wclear(fullscreen_window);
- mvwprintw(fullscreen_window, 1, 23, "----====< Victrix Abyssi >====----\n");
- mvwprintw(fullscreen_window, 3, 25, "A roguelike game by Martin Read\n");
+ mvwprintw(fullscreen_window, 1, 20, "----====< Obumbrata et Velata >====----\n");
+ mvwprintw(fullscreen_window, 5, 24, "A roguelike game by Martin Read\n");
mvwprintw(fullscreen_window, 10, 25, "S)tart new game\n");
mvwprintw(fullscreen_window, 11, 25, "R)esume existing game\n");
mvwprintw(fullscreen_window, 12, 25, "Q)uit\n");
- mvwprintw(fullscreen_window, 23, 25, "Version %d.%d.%d\n", MAJVERS, MINVERS, PATCHVERS);
+ draw_mainmenu_tip();
+ mvwprintw(fullscreen_window, 3, 33, "Version %d.%d.%d\n", MAJVERS, MINVERS, PATCHVERS);
show_panel(fullscreen_panel);
update_panels();
doupdate();
/*! \brief Get pointer to cchar_t object for specified permon
*
- * \param monster_id Specified permon
+ * \param pm_ref Specified permon
* \return pointer to the specified permon's tile
*/
-static cchar_t const *monster_char(int monster_id)
+static cchar_t const *monster_char(int pm_ref)
{
- return permon_tiles + monster_id;
+ return permon_tiles + pm_ref;
}
/*! \brief Get pointer to cchar_t object for specified permobj
*
- * \param object_id Specified permobj
+ * \param po_ref Specified permobj
* \return pointer to the specified permobj's tile
*/
-static cchar_t const *object_char(int object_id)
+static cchar_t const *object_char(int po_ref)
{
- return permobj_tiles + object_id;
+ return permobj_tiles + po_ref;
}
/*! \brief Repopulate the back buffer and set the hard redraw flag */
}
else
{
- int obj = lvl.obj_at(c);
- int mon = lvl.mon_at(c);
+ Obj_handle obj = lvl.obj_at(c);
+ Mon_handle mon = lvl.mon_at(c);
Terrain terr = lvl.terrain_at(c);
uint32_t flags = lvl.flags_at(c);
if (c == u.pos)
}
else if ((!show_terrain) && (mon != NO_MON) && occupant_visible(c))
{
- back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].mon_id);
+ back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].pm_ref);
}
else if (flags & MAPFLAG_EXPLORED)
{
if ((!show_terrain) && (obj != NO_OBJ))
{
- back_buffer[camoff.y][camoff.x] = object_char(objects[obj].obj_id);
+ back_buffer[camoff.y][camoff.x] = object_char(objects[obj].po_ref);
}
else
{
{
mvwchgat(world_window, i, j, 1, A_BOLD, Gcol_d_grey, nullptr);
}
+ else if (c != u.pos)
+ {
+ Obj_handle obj = lvl.obj_at(c);
+ Mon_handle mon = lvl.mon_at(c);
+ Decal_tag decal_id = lvl.decal_at(c);
+ if (mon != NO_MON)
+ {
+ // FUTURE: handle colour-customized monsters
+ }
+ else if (obj != NO_OBJ)
+ {
+ Obj const *optr = obj_snap(obj);
+ if (optr->po_ref == PO_CORPSE)
+ {
+ mvwchgat(world_window, i, j, 1,
+ colour_data[permons[optr->meta[0]].colour].attr,
+ colour_data[permons[optr->meta[0]].colour].cpair,
+ nullptr);
+ }
+ }
+ else if (decal_id != NO_DECAL)
+ {
+ mvwchgat(world_window, i, j, 1,
+ colour_data[decal_colours[decal_id]].attr,
+ colour_data[decal_colours[decal_id]].cpair,
+ nullptr);
+ }
+ }
front_buffer[i][j] = back_buffer[i][j];
}
}
{
int i;
int j;
+ wchar_t wch[2];
{
- wchar_t wch[2];
wch[0] = L'@';
wch[1] = 0;
setcchar(&player_tile, wch, 0, 0, nullptr);
- wch[0] = L' ';
- setcchar(&blank_tile, wch, 0, 0, nullptr);
+ wch[0] = L'â–‘';
+ setcchar(&blank_tile, wch,
+ colour_data[Gcol_d_grey].attr,
+ colour_data[Gcol_d_grey].cpair, nullptr);
}
for (i = 0; i < NUM_OF_PERMONS; ++i)
{
- wchar_t wch[2];
wch[0] = permons[i].sym;
wch[1] = 0;
setcchar(permon_tiles + i, wch,
}
for (i = 0; i < NUM_TERRAINS; ++i)
{
- wchar_t wch[2];
/* policy decision: for now we don't support use of combining
* characters for terrain. */
j = mbtowc(wch, terrain_props[i].unicode, 4);
}
for (i = 0; i < NUM_OF_PERMOBJS; ++i)
{
- wchar_t wch[2];
/* policy decision: for now we don't support use of combining
* characters for items. */
j = mbtowc(wch, permobjs[i].unicode, 4);
static void load_ascii_tiles(void)
{
int i;
- {
- wchar_t wch[2];
- wch[0] = L'@';
- wch[1] = 0;
- setcchar(&player_tile, wch, 0, 0, nullptr);
- wch[0] = L' ';
- setcchar(&blank_tile, wch, 0, 0, nullptr);
- }
+ wchar_t wch[2];
+ wch[0] = L'@';
+ wch[1] = 0;
+ setcchar(&player_tile, wch, 0, 0, nullptr);
+ wch[0] = L' ';
+ setcchar(&blank_tile, wch,
+ colour_data[Gcol_d_grey].attr,
+ colour_data[Gcol_d_grey].cpair, nullptr);
for (i = 0; i < NUM_OF_PERMONS; ++i)
{
- wchar_t wch[2];
wch[0] = permons[i].sym;
wch[1] = 0;
setcchar(permon_tiles + i, wch,
}
for (i = 0; i < NUM_TERRAINS; ++i)
{
- wchar_t wch[2];
wch[0] = terrain_props[i].ascii;
wch[1] = 0;
setcchar(terrain_tiles + i, wch,
}
for (i = 0; i < NUM_OF_PERMOBJS; ++i)
{
- wchar_t wch[2];
wch[0] = permobjs[i].sym;
wch[1] = 0;
setcchar(permobj_tiles + i, wch,
char const *term = getenv("TERM");
if (term && (strstr(term, "xterm") || strstr (term, "rxvt")))
{
- fputs("\033]0;Victrix Abyssi 1.0 Alpha 1\007", stdout);
+ fputs("\033]0;Obumbrata et Velata\007", stdout);
fflush(stdout);
}
}
init_pair(Gcol_d_grey, COLOR_BLACK, COLOR_BLACK);
init_pair(Gcol_purple, COLOR_MAGENTA, COLOR_BLACK);
init_pair(Gcol_cyan, COLOR_CYAN, COLOR_BLACK);
+ permon_tiles = new cchar_t[NUM_OF_PERMONS];
+ permobj_tiles = new cchar_t[NUM_OF_PERMOBJS];
if (force_ascii)
{
load_ascii_tiles();
void print_msg(Msg_prio prio, char const *fmt, ...)
{
va_list ap;
+ char *s;
+ int i;
+ int j;
va_start(ap, fmt);
+ s = (char *) malloc(512);
+ i = vasprintf(&s, fmt, ap);
+ va_end(ap);
switch (prio)
{
case Msg_prio::Low:
wattr_set(message_window, colour_data[Gcol_prio_bug].attr, colour_data[Gcol_prio_bug].cpair, nullptr);
break;
}
- vw_printw(message_window, fmt, ap);
- wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
- va_end(ap);
+ if (j > 50)
+ {
+ /* TODO catch this */
+ }
+ waddstr(message_window, s);
#ifdef DEBUG_TO_STDERR
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
+ fwrite(s, 1, j, stderr);
#endif
+ wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
display_update();
+ free(s);
}
/*! \brief Set a message and whether 'nothing' should be listed in inventory
}
else
{
+ Obj const *optr = obj_snap(u.inventory[i]);
if ((filter == POCLASS_NONE) ||
- (permobjs[objects[u.inventory[i]].obj_id].poclass == filter))
+ (permobjs[optr->po_ref].poclass == filter))
{
wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
}
for (i = 0; i < 19; i++)
{
- if ((u.inventory[i] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)))
+ if ((u.inventory[i] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].po_ref].poclass == filter)))
{
items++;
}
* a strict superset of ASCII. IF we're not, the following code may
* break. */
selection = ch - 'a';
- if ((u.inventory[selection] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].obj_id].poclass == filter)))
+ if ((u.inventory[selection] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].po_ref].poclass == filter)))
{
hide_inv();
return selection;
break;
case '\x1b':
case ' ':
+ print_msg("Cancelled.\n");
return You_fail; /* cancelled. */
default:
print_msg("Bad direction (use movement keys).\n");
{
print_msg("You are not wearing a ring.\n");
}
- else if (ring_removal_unsafe(Noise_std) == You_pass)
+ else if (unequip_safety_check(BONUS_RING, Noise_std) == You_pass)
{
act->cmd = REMOVE_RING;
return;
break;
case '<':
{
- Terrain t = lvl.terrain_at(u.pos);
- if (terrain_props[t].flags & TFLAG_ascend)
- {
- act->cmd = GO_UP_STAIRS;
- return;
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_props[t].flags & (TFLAG_portal | TFLAG_ascend))
+ {
+ act->cmd = GO_UP_STAIRS;
+ return;
}
else
{
- print_msg("There are no stairs here.\n");
+ print_msg("There are no portals or upward stairs here.\n");
}
- // TODO accept this keystroke as "enter portal" too
}
break;
case '>':
{
Terrain t = lvl.terrain_at(u.pos);
- if (terrain_props[t].flags & TFLAG_descend)
+ if (terrain_props[t].flags & (TFLAG_descend | TFLAG_portal))
{
act->cmd = GO_DOWN_STAIRS;
return;
}
else
{
- print_msg("There are no stairs here.\n");
+ print_msg("There are no portals or downward stairs here.\n");
}
- // TODO accept this keystroke as "enter portal" too
}
break;
case '5':
return;
}
break;
+ case '\\':
+ print_msg("You have the following skills:\n");
+ for (int i = 0; i < NUM_SKILLS; ++i)
+ {
+ if (u.known_skill[i])
+ {
+ print_msg(" %s\n", skill_props[i].name);
+ }
+ }
+ break;
#ifdef DEBUG_MAPDUMP
case 'M':
{
*/
int display_shutdown(void)
{
+ delete[] permobj_tiles;
+ permobj_tiles = nullptr;
+ delete[] permon_tiles;
+ permon_tiles = nullptr;
+ hide_panel(status_panel);
+ hide_panel(world_panel);
+ hide_panel(message_panel);
+ hide_panel(inventory_panel);
+ hide_panel(fullscreen_panel);
+ update_panels();
+ doupdate();
clear();
refresh();
+ del_panel(status_panel);
+ del_panel(world_panel);
+ del_panel(message_panel);
+ del_panel(inventory_panel);
+ del_panel(fullscreen_panel);
+ delwin(status_window);
+ delwin(world_window);
+ delwin(message_window);
+ delwin(inventory_window);
+ delwin(fullscreen_window);
+ status_panel = world_panel = message_panel = inventory_panel = fullscreen_panel = nullptr;
+ status_window = world_window = message_window = inventory_window = fullscreen_window = nullptr;
endwin();
return 0;
}
ch = wgetch(message_window);
if (ch == 'Y')
{
+ print_msg("Confirmed.\n");
return 1;
}
+ print_msg("Cancelled.\n");
return 0;
}
return 0;
case '\x1b':
case ' ':
+ print_msg("Cancelled.\n");
return -1;
default:
print_msg("Invalid response. Press y or n (ESC or space to cancel)\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("< go down stairs or enter a magic portal\n");
+ print_msg("> go down stairs or enter a magic portal\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");
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("\n");
print_msg("OTHER COMMANDS\n");
print_msg("S save and exit\n");
print_msg("X quit without saving\n");
print_msg("i show 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("\\ list your character's known skills \n");
print_msg("@ dump your character's details to <name>.dump\n");
print_msg("? print this message\n");
print_msg("\nPress any key to continue...\n");
print_msg("SYMBOLS\n");
print_msg("@ you\n");
print_msg(". floor\n");
- print_msg("> stairs down\n");
+ print_msg("☊ magic portal\n");
print_msg("# wall\n");
print_msg("+ a door\n");
print_msg(") a weapon\n");
print_msg("? a scroll\n");
print_msg("! a potion\n");
print_msg("%% some food\n");
+ print_msg("& a corpse\n");
print_msg("1-4 demons (smaller numbers are stronger)\n");
print_msg("\nMost other monsters are shown as letters.\n");
print_msg("\nThis is all the help you get. Good luck!\n");
static void run_main_menu(void)
{
bool done = false;
+#if 0
+ bool role_selected = false;
+#endif
+ Role_id role = Role_princess;
char name[16];
do
{
mvwprintw(fullscreen_window, 10, 1, "\n");
mvwprintw(fullscreen_window, 11, 1, "\n");
mvwprintw(fullscreen_window, 12, 1, "\n");
- mvwprintw(fullscreen_window, 13, 19, "Welcome, Princess. Remind me of your name?\n");
+ mvwprintw(fullscreen_window, 13, 24, "Welcome. Remind me of thy name?\n");
mvwprintw(fullscreen_window, 14, 1, "\n");
+ mvwprintw(fullscreen_window, 15, 1, "\n");
+ mvwprintw(fullscreen_window, 16, 1, "\n");
+ mvwprintw(fullscreen_window, 17, 1, "\n");
+ mvwprintw(fullscreen_window, 18, 1, "\n");
wmove(fullscreen_window, 14, 30);
update_panels();
doupdate();
mvwgetnstr(fullscreen_window, 14, 30, name, 16);
curs_set(0);
noecho();
- new_game(name);
+ mvwprintw(fullscreen_window, 15, 19, "What path dost thou follow in thy quest?\n");
+ mvwprintw(fullscreen_window, 16, 25, "(P)rincess\n");
+ mvwprintw(fullscreen_window, 17, 25, "(D)emon Hunter\n");
+ mvwprintw(fullscreen_window, 18, 25, "(T)hanatophile\n");
+ mvwprintw(fullscreen_window, 19, 1, "\n");
+ mvwprintw(fullscreen_window, 21, 1, "\n");
+#if 0
+ role_selected = false;
+ do
+ {
+ int ch = wgetch(fullscreen_window);
+ switch (ch)
+ {
+ case 'p':
+ case 'P':
+ role_selected = true;
+ role = Role_princess;
+ break;
+ case 'd':
+ case 'D':
+ role_selected = true;
+ role = Role_demon_hunter;
+ break;
+ case 't':
+ case 'T':
+ role_selected = true;
+ role = Role_thanatophile;
+ break;
+ default:
+ break;
+ }
+ } while (!role_selected);
+#endif
+ new_game(name, role);
done = true;
wclear(message_window);
wmove(message_window, 0, 0);
show_game_view();
+ announce_role(role);
break;
case 'c':
case 'C':
case 'R':
// TODO implement loading properly.
{
- int i;
wclear(message_window);
wmove(message_window, 0, 0);
- i = load_game();
- if (i != -1)
- {
- done = true;
- }
- else
- {
- mvwprintw(fullscreen_window, 14, 30, "No saved game found.\n");
+ try
+ {
+ load_game();
+ done = true;
+ }
+ catch (Obumb_sysexcep e)
+ {
+ mvwprintw(fullscreen_window, 14, 20, "Error opening saved game:\n");
+ mvwprintw(fullscreen_window, 15, 1, "%s\n", strerror(e.err));
+ }
+ catch (Obumb_dataexcep e)
+ {
+ mvwprintw(fullscreen_window, 14, 20, "Error loading saved game:\n");
+ mvwprintw(fullscreen_window, 15, 1, "%s\n", e.str);
update_panels();
doupdate();
- }
+ }
}
break;
case 'q':
case 'Q':
endwin();
exit(0);
+ default:
+ break;
}
+ draw_mainmenu_tip();
} while (!done);
if (!game_finished)
{
{
Coord c = u.pos + o;
uint32_t flags;
- int mon;
- int obj;
+ Mon_handle mon;
+ Obj_handle obj;
Terrain terr;
- if ((c.y < 0) || (c.x < 0) || (c.y >= (lvl.chunks_high << CHUNK_SHIFT)) ||
- (c.x >= (lvl.chunks_wide << CHUNK_SHIFT)))
+ if ((c.y < 0) || (c.x < 0) ||
+ (c.y >= int32_t(lvl.chunks_high << CHUNK_SHIFT)) ||
+ (c.x >= int32_t(lvl.chunks_wide << CHUNK_SHIFT)))
{
print_msg("That square is beyond the bounds of the level.\n");
return;
mon = lvl.mon_at(c);
obj = lvl.obj_at(c);
terr = lvl.terrain_at(c);
- print_msg("%s\n", terrain_props[terr]);
+ print_msg("%s\n", terrain_props[terr].name);
if ((mon != NO_MON) && mon_visible(mon))
{
- print_msg("%s\n%s\n", permons[monsters[mon].mon_id].name, permons[monsters[mon].mon_id].description);
+ print_msg("%s\n%s\n", permons[monsters[mon].pm_ref].name, permons[monsters[mon].pm_ref].description);
}
if (obj != NO_OBJ)
{
- print_msg("%s\n%s\n", permobjs[objects[obj].obj_id].name, permobjs[objects[obj].obj_id].description);
+ print_msg("%s\n%s\n", permobjs[objects[obj].po_ref].name, permobjs[objects[obj].po_ref].description);
}
}
void welcome(void)
{
- print_msg("Welcome to the Abyss, Princess %s.\n", u.name);
+ print_msg("A welcome to you, Princess %s.\n", u.name);
print_msg("Press '?' for help.\n");
}
-void describe_object(int obj)
+void describe_object(Obj_handle obj)
{
- Obj *optr;
- Permobj *poptr;
+ Obj const *optr = obj_snap(obj);
+ Permobj const *poptr = permobjs + optr->po_ref;
print_obj_name(obj);
- optr = objects + obj;
- poptr = permobjs + optr->obj_id;
print_msg("\n%s\n", poptr->description);
}
-void print_obj_name(int obj)
+void print_obj_name(Obj_handle obj)
{
- Obj *optr;
- 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);
- }
+ char *s;
+ asprint_obj_name(&s, obj);
+ print_msg("%s", s);
+ free(s);
}
-void print_mon_name(int mon, int article)
+void print_mon_name(Mon_handle mon, int article)
{
- if (permons[monsters[mon].mon_id].name[0] == '\0')
- {
- print_msg("GROB THE VOID (%d)", monsters[mon].mon_id);
- return;
- }
- 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;
- }
+ char *s;
+ asprint_mon_name(&s, mon, article);
+ print_msg("%s", s);
+ free(s);
}
+
/* display-nc.cc */
// vim:cindent
/*! \file display.hh
- * \brief Display-related definitions for Victrix Abyssi
+ * \brief Display-related definitions
*/
/* Copyright 2005-2013 Martin Read
Bug
};
-extern void print_msg(char const *fmt, ...);
-extern void print_msg(Msg_prio prio, char const *fmt, ...);
-extern int read_input(char *buffer, int length);
-extern void print_help(void);
-extern int launch_user_interface(void);
-extern void display_update(void);
-extern int display_shutdown(void);
-extern void newsym(Coord c);
-extern void touch_back_buffer(void);
-extern void get_player_action(Action *act);
-extern Pass_fail select_dir(Offset *pstep);
-extern int getYN(char const *msg);
-extern int getyn(char const *msg);
-extern void press_enter(void);
-extern void pressanykey(void);
-extern void show_discoveries(void);
-extern void touch_one_screen(Coord c);
-extern void welcome(void);
+void print_msg(char const *fmt, ...);
+void print_msg(Msg_prio prio, char const *fmt, ...);
+int read_input(char *buffer, int length);
+void print_help(void);
+int launch_user_interface(void);
+void display_update(void);
+int display_shutdown(void);
+void newsym(Coord c);
+void touch_back_buffer(void);
+void get_player_action(Action *act);
+Pass_fail select_dir(Offset *pstep);
+int getYN(char const *msg);
+int getyn(char const *msg);
+void press_enter(void);
+void pressanykey(void);
+void show_discoveries(void);
+void touch_one_screen(Coord c);
+void welcome(void);
+void print_obj_name(Obj_handle obj);
+void print_mon_name(Mon_handle mon, int article);
/* "I've changed things that need to be redisplayed" flags. */
extern bool hard_redraw;
--- /dev/null
+/*! \file dungeon.cc
+ * \brief "dungeon"-like level generators i.e. made of rooms
+ */
+
+/* Copyright 2014 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.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#define DBASH_ROOMS_WIDE (3)
+#define DBASH_ROOMS_HIGH (3)
+/*! \brief Number of rooms on a "dungeonbash"-style level */
+#define DBASH_ROOMS (DBASH_ROOMS_WIDE * DBASH_ROOMS_HIGH)
+/*! \brief Maximum number of corridors on a dungeonbash-style level
+ *
+ * This value corresponds to the situation where every adjacent pair of
+ * rooms is connected. */
+#define DBASH_CORRIDORS ((DBASH_ROOMS_WIDE - 1) * DBASH_ROOMS_HIGH + \
+ (DBASH_ROOMS_HIGH - 1) * DBASH_ROOMS_WIDE)
+/*! \brief Room generated first */
+#define DBASH_ORIGIN_ROOM (4)
+
+struct Dbash_room
+{
+ Coord inner_tl;
+ Coord inner_br;
+ Coord exit_positions[4];
+};
+
+struct Dbash_corridor
+{
+ Coord endpoints[2];
+};
+
+struct Dbash_layout_data
+{
+ Level_layout id;
+ int32_t exit_room;
+ int32_t entry_room;
+ uint16_t connectivity[DBASH_ROOMS];
+ Dbash_room rooms[DBASH_ROOMS];
+ Dbash_corridor corridors[DBASH_CORRIDORS];
+ void *next_layout_data;
+};
+
+static void excavate_normal_room(Level *l, Coord inner_tl, Coord inner_br, uint32_t region)
+{
+ Coord pos;
+ // Initial pass: make the room interior all floor.
+ for (pos.y = inner_tl.y; pos.y <= inner_br.y; ++pos.y)
+ {
+ for (pos.x = inner_tl.x; pos.x <= inner_br.x; ++pos.x)
+ {
+ l->set_terrain_at(pos, FLOOR);
+ l->set_region_at(pos, region);
+ }
+ }
+}
+
+static void draw_pillar_row(Level *l, Coord end1, Coord end2, Terrain t)
+{
+ Offset delta = end2 - end1;
+ Offset step = mysign(delta) * 2;
+}
+
+#define PILLARS_WEST 0x00000001u
+#define PILLARS_EAST 0x00000002u
+#define PILLARS_NORTH 0x00000004u
+#define PILLARS_SOUTH 0x00000008u
+#define PILLARS_INNER_MASK 0x000000f0u
+#define PILLARS_EMPTY 0u
+#define PILLARS_ALL 1u
+#define PILLARS_EW_AVENUE 2u
+#define PILLARS_NS_AVENUE 3u
+#define PILLARS_CROSSED_AVENUE 4u
+#define PILLARS_INNER_SHIFT 4u
+
+static void excavate_pillar_room(Level *l, Coord inner_tl, Coord inner_br, uint32_t region, uint32_t pillar_pattern)
+{
+ Offset delta = inner_br - inner_tl;
+ if ((delta.y | delta.x) & 1)
+ {
+ throw Obumb_levgen_excep("Awkward dimensions for pillar room");
+ }
+ excavate_normal_room(l, inner_tl, inner_br, region);
+ /* Second pass: What pattern of pillars?
+ *
+ * Low 4 bits: Do we have the "ring" pillars?
+ * Next 4 bits: Choice of interior pattern from:
+ * 0 = no interior pillars
+ * 1 = all interior pillars
+ * 2 = east-west avenue of pillars
+ * 3 = north-south avenue of pillars
+ * 4 = crossed avenues of pillars
+ */
+ Coord end1;
+ Coord end2;
+ if (pillar_pattern & PILLARS_EAST)
+ {
+ end1.y = inner_tl.y + 1;
+ end1.x = end2.x = inner_br.x - 1;
+ end2.y = inner_br.y - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ if (pillar_pattern & PILLARS_WEST)
+ {
+ end1.y = inner_tl.y + 1;
+ end1.x = end2.x = inner_tl.x + 1;
+ end2.y = inner_br.y - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ if (pillar_pattern & PILLARS_NORTH)
+ {
+ end1.y = end2.y = inner_tl.y + 1;
+ end2.x = inner_tl.x + 1;
+ end2.x = inner_br.x - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ if (pillar_pattern & PILLARS_SOUTH)
+ {
+ end1.y = end2.y = inner_tl.y + 1;
+ end2.x = inner_tl.x + 1;
+ end2.x = inner_br.x - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ switch ((pillar_pattern >> 4) & 0xf)
+ {
+ default:
+ // TODO emit debug
+ case PILLARS_EMPTY:
+ break;
+ case PILLARS_ALL:
+ end1.x = inner_tl.x;
+ end2.x = inner_tl.x;
+ for (end1.y = end2.y = inner_tl.y + 1;
+ end1.y < inner_br.y; (end1.y += 2), (end2.y += 2))
+ {
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ break;
+ case PILLARS_EW_AVENUE:
+ if (delta.y > 2)
+ {
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 - 1;
+ end1.x = inner_tl.x + 1;
+ end2.x = inner_br.x - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ break;
+ case PILLARS_NS_AVENUE:
+ if (delta.x > 2)
+ {
+
+ end1.x = end2.x = (inner_tl.x + inner_br.x) / 2 - 1;
+ end1.y = inner_tl.y + 1;
+ end2.y = inner_br.y - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ }
+ break;
+ case PILLARS_CROSSED_AVENUE:
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 - 1;
+ end1.x = inner_tl.x + 1;
+ end2.x = inner_br.x - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ end1.x = end2.x = (inner_tl.x + inner_br.x) / 2 - 1;
+ end1.y = inner_tl.y + 1;
+ end2.y = inner_br.y - 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+ draw_pillar_row(l, end1, end2, MASONRY_WALL);
+ break;
+ }
+}
+
+/*! \brief Excavate a dungeonbash-style level
+ *
+ * Levels in Martin's Dungeon Bash had nine rooms in a 3x3 grid. This function
+ * reproduces that structure.
+ */
+void build_level_dungeonbash(Level *l)
+{
+ Coord tl;
+ Coord br;
+ int ysize; // internal y-size of room we're about to build
+ int xsize; // internal x-size of room we're about to build
+ Dbash_layout_data *layout_data = new Dbash_layout_data;
+ Dbash_room *rptr;
+ layout_data->id = LAYOUT_DUNGEONBASH;
+ l->layout_data = layout_data;
+ layout_data->entry_room = zero_die(9);
+ do
+ {
+ layout_data->exit_room = zero_die(9);
+ } while (layout_data->exit_room == layout_data->entry_room);
+ /* Go with the standard 3x3 chunk set. */
+ initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ /* Build the centre room. */
+ ysize = 3 + zero_die(6);
+ xsize = 3 + zero_die(6);
+ tl.y = (GUIDE_EDGE_CHUNKS / 2) - (ysize / 2);
+ br.y = tl.y + (ysize - 1);
+ tl.x = (GUIDE_EDGE_CHUNKS / 2) - (xsize / 2);
+ br.x = tl.x + (xsize - 1);
+ rptr = layout_data->rooms + DBASH_ORIGIN_ROOM;
+ rptr->inner_tl = tl;
+ rptr->inner_br = br;
+ /* Centre room is either plain, or ring pillars with crossed avenues. */
+ if (zero_die(2))
+ {
+ excavate_pillar_room(l, rptr->inner_tl, rptr->inner_br,
+ DBASH_ORIGIN_ROOM, 0x4f);
+ }
+ else
+ {
+ excavate_normal_room(l, rptr->inner_tl, rptr->inner_br,
+ DBASH_ORIGIN_ROOM);
+ }
+ /* Other rooms are plain for now. */
+ for (int i = 0; i < DBASH_ROOMS; ++i)
+ {
+ if (i == DBASH_ORIGIN_ROOM)
+ {
+ continue;
+ }
+ }
+}
+
+/*! \brief Excavate a "claustrophobia" level
+ *
+ * "Claustrophobia" levels
+ *
+ * \todo Implement.
+ */
+void build_level_claustrophobia(Level *l)
+{
+}
+
+/* dungeon.cc */
+// vim:cindent:ts=8:sw=4:expandtab
* This function returns true if the specified coordinates are out of bounds
* or the terrain at the specified position is opaque.
*/
-static bool dflt_blk(Coord c)
+bool dflt_blk(Coord c)
{
return ((c.y <= lvl.min_y()) || (c.x <= lvl.min_x()) || (c.y >= lvl.max_y()) || (c.x >= lvl.max_x()) || (terrain_is_opaque(lvl.terrain_at(c))));
}
return ((double) trn) / ((double) rdl);
}
-/*! \brief Reset the affected flags of a Radiance object
- */
-void clear_radiance(Radiance *rad)
-{
- memset(&(rad->affected), '\0', sizeof rad->affected);
-}
-
/*! \brief Start computing one octant of a Radiance
*/
static inline void compute_octant(Radiance *rad, int octant)
}
}
-void compute_radiance(Radiance *rad)
+void Radiance::compute(void)
{
int oct;
/* Compute the eight octants in order. */
for (oct = 0; oct < 8; ++oct)
{
- compute_octant(rad, oct);
+ compute_octant(this, oct);
}
/* Mark the centre as (un)affected according to the Radiance's setup. */
- if (rad->exclude_centre)
- {
- rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = false;
- }
- else
- {
- rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = true;
- }
+ affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = !exclude_centre;
}
-void resolve_radiance(Radiance *rad)
+void Radiance::resolve(void)
{
Coord c;
int i;
int j;
int k;
- switch (rad->order)
+ switch (order)
{
case Reo_ascending:
- for ((i = MAX_FOV_RADIUS - rad->radius), (c.y = rad->centre.y - rad->radius); c.y <= rad->centre.y + rad->radius; ++c.y, ++i)
+ for ((i = MAX_FOV_RADIUS - radius), (c.y = centre.y - radius); c.y <= centre.y + radius; ++c.y, ++i)
{
- for ((j = MAX_FOV_RADIUS - rad->radius), (c.x = rad->centre.x - rad->radius); c.x <= rad->centre.x + rad->radius; ++c.x, ++j)
+ for ((j = MAX_FOV_RADIUS - radius), (c.x = centre.x - radius); c.x <= centre.x + radius; ++c.x, ++j)
{
- if (rad->affected[i][j])
+ if (affected[i][j])
{
- rad->effect_fun(c, rad->pvt);
+ effect_fun(c, pvt);
}
}
}
break;
case Reo_spiral_out:
- for (i = 0, j = 0, k = 0; i >= -(rad->radius); )
+ for (i = 0, j = 0, k = 0; i >= -(radius); )
{
- c.y = rad->centre.y + i;
- c.x = rad->centre.x + j;
- if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+ c.y = centre.y + i;
+ c.x = centre.x + j;
+ if (affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
{
- rad->effect_fun(c, rad->pvt);
+ effect_fun(c, pvt);
}
if ((i == 0) && (j == 0))
{
}
break;
case Reo_spiral_in:
- for (i = -(rad->radius), j = -(rad->radius), k = rad->radius; k >= 0; )
+ for (i = -(radius), j = -(radius), k = radius; k >= 0; )
{
- c.y = rad->centre.y + i;
- c.x = rad->centre.x + j;
- if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+ c.y = centre.y + i;
+ c.x = centre.x + j;
+ if (affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
{
- rad->effect_fun(c, rad->pvt);
+ effect_fun(c, pvt);
}
if ((i == 0) && (j == 0))
{
break;
default:
- debug_unimplemented_radiance_order(rad->order);
+ debug_unimplemented_radiance_order(order);
abort();
}
}
void compute_fov(void)
{
- clear_radiance(&player_fov);
+ player_fov.clear();
player_fov.centre = u.pos;
player_fov.radius = MAX_FOV_RADIUS;
player_fov.order = Reo_ascending;
player_fov.opaque_fun = dflt_blk;
player_fov.effect_fun = mark_explored;
player_fov.pvt = nullptr;
- compute_radiance(&player_fov);
- resolve_radiance(&player_fov);
+ player_fov.compute();
+ player_fov.resolve();
}
bool tile_visible(Coord c)
* \brief field-of-view header
*/
-/* Copyright 2013 Martin Read
+/* Copyright © 2013-2014 Martin Read
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#ifndef FOV_HH
#define FOV_HH
+#include <string.h>
+
#define MAX_FOV_RADIUS 10
#define FOV_SIDE (2 * MAX_FOV_RADIUS + 1)
#define Visflag_central 0x02u
typedef uint8_t Vision_flags; /* 8 bits should do for now */
-class radiance_data
+class Radiance
{
public:
Vision_flags affected[FOV_SIDE][FOV_SIDE];
void *pvt;
bool (*opaque_fun)(Coord c);
bool (*effect_fun)(Coord c, void *pvt);
+ /* member functions only past this point please */
+ void clear(void) { memset(affected, '\0', sizeof affected); }
bool test(Coord c) const {
if ((c.y >= 0) && (c.x >= 0) && (c.y < FOV_SIDE) && (c.x < FOV_SIDE))
{
}
return false;
}
+ void compute(void);
+ void resolve(void);
};
-typedef struct radiance_data Radiance;
-
-extern void clear_radiance(Radiance *rad);
-extern void compute_radiance(Radiance *rad);
-extern void resolve_radiance(Radiance *rad);
-extern void compute_fov(void);
-extern bool tile_visible(Coord c);
-extern bool occupant_visible(Coord c);
extern Radiance player_fov;
+void compute_fov(void);
+bool tile_visible(Coord c);
+bool occupant_visible(Coord c);
+bool dflt_blk(Coord c);
+
#endif
-/* fov.h */
+/* fov.hh */
// vim:cindent
/* \file log.cc
- * \brief File I/O functions for Victrix Abyssi
+ * \brief File I/O functions for Obumbrata et Velata
*/
/* Copyright 2014 Martin Read
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "victrix-abyssi.hh"
+#include "core.hh"
#include "objects.hh"
#include "monsters.hh"
#include "player.hh"
#include "map.hh"
+#include "mapgen.hh"
+#include "notify.hh"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
int datadir_fd;
int confdir_fd;
-/*! \brief Read configuration files.
- *
- * \todo Implement configuration files.
- */
-void load_config(void)
+/*! \brief call system(cmd) and throw an exception on failure */
+void wrapped_system(char const *cmd)
{
- int i;
- i = setup_dirs("com.blackswordsonics", "victrix-abyssi", &datadir_fd, &confdir_fd);
- if (i < 0)
+ int i = system(cmd);
+ if (i != 0)
{
- perror("victrix-abyssi: couldn't access configuration directories");
- exit(1);
+ throw Obumb_sysexcep(errno);
}
- // TODO write this function
}
-/*! \brief Rewrite configuration files.
- *
- * \todo Implement configuration files.
- */
-int rewrite_config(void)
+/*! \brief call fread(args) and throw an exception on failure */
+void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
{
- // TODO write this function
- return 0;
+ size_t i = fread(buf, size, nmemb, fp);
+ if (i != nmemb)
+ {
+ if (ferror(fp))
+ {
+ throw Obumb_sysexcep(errno);
+ }
+ else if (feof(fp))
+ {
+ throw Obumb_dataexcep("Unexpected end of file");
+ }
+ else
+ {
+ throw Obumb_sysexcep(errno);
+ }
+ }
}
-/*! \brief Get a list of extant saved games and character names
- *
- * Fill in the map<string,string> container *dest using character names
- * as keys and filenames as values.
- *
- * \param dest Pointer to map<string,string> container to fill with data
- * \return Number of charname/filename pairs written to container
- * \todo Basic implementation
- * \todo Deal gracefully with a savefile going away before we open it
- */
-int get_savefile_list(std::map<std::string, std::string> *dest)
+/*! \brief Read an unsigned 32-bit integer in network byte order */
+static inline void deserialize_uint32(FILE *fp, uint32_t *i)
{
- // TODO write this function
- return 0;
+ wrapped_fread(i, 1, sizeof *i, fp);
+ *i = ntohl(*i);
}
-/*! \brief Record the details of the Princess's death to the log
- */
-void log_death(Death d, char const *what)
+/*! \brief Write an unsigned 32-bit integer in network byte order */
+static inline void serialize_uint32(FILE *fp, uint32_t i)
{
- FILE *fp;
- int fd;
- fd = openat(datadir_fd, "victrix-abyssi.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
- if (fd == -1)
+ i = htonl(i);
+ fwrite(&i, 1, sizeof i, fp);
+}
+
+/*! \brief Read an signed 32-bit integer in network byte order */
+static inline void deserialize_int32(FILE *fp, int32_t *i)
+{
+ int32_t tmp;
+ wrapped_fread(&tmp, 1, sizeof tmp, fp);
+ *i = (int32_t) ntohl(tmp);
+}
+
+/*! \brief Write a signed 32-bit integer in network byte order */
+static inline void serialize_int32(FILE *fp, int32_t i)
+{
+ int32_t tmp = htonl(i);
+ fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+/*! \brief Read an int in network byte order */
+static inline void deserialize_int(FILE *fp, int *i)
+{
+ int32_t tmp;
+ wrapped_fread(&tmp, 1, sizeof tmp, fp);
+ *i = (int32_t) ntohl(tmp);
+}
+
+/*! \brief Write an int in network byte order */
+static inline void serialize_int(FILE *fp, int i)
+{
+ int32_t tmp = htonl(i);
+ fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+static inline void deserialize_bool(FILE *fp, bool *b)
+{
+ char tmp;
+ wrapped_fread(&tmp, 1, 1, fp);
+ *b = !!tmp;
+}
+
+static inline void serialize_bool(FILE *fp, bool b)
+{
+ char tmp = b;
+ fwrite(&tmp, 1, 1, fp);
+}
+
+/*! \brief Read a Coord */
+static inline void deserialize_coord(FILE *fp, Coord * c)
+{
+ deserialize_int32(fp, &(c->y));
+ deserialize_int32(fp, &(c->x));
+}
+
+/*! \brief Write a Coord */
+static inline void serialize_coord(FILE *fp, Coord const& c)
+{
+ serialize_int32(fp, c.y);
+ serialize_int32(fp, c.x);
+}
+
+/*! \brief Read an Offset */
+static inline void deserialize_offset(FILE *fp, Offset * o)
+{
+ deserialize_int32(fp, &(o->y));
+ deserialize_int32(fp, &(o->x));
+}
+
+/*! \brief Write an Offset */
+static inline void serialize_offset(FILE *fp, Coord const& o)
+{
+ serialize_int32(fp, o.y);
+ serialize_int32(fp, o.x);
+}
+
+/*! \brief Read a C-style string */
+static inline void deserialize_cstring(FILE *fp, char *str, uint32_t buffer_size)
+{
+ uint32_t i;
+ deserialize_uint32(fp, &i);
+ if (i >= buffer_size)
{
- return;
+ // reading garbage
+ throw(-1);
}
- fp = fdopen(fd, "a");
- if (fp)
+ else
{
- switch (d)
+ memset(str, '\0', buffer_size);
+ wrapped_fread(str, 1, i, fp);
+ }
+}
+
+/*! \brief Write a C-style string */
+static inline void serialize_cstring(FILE *fp, char const *str, uint32_t expected_max_size)
+{
+ size_t i = strlen(str);
+ if (i >= expected_max_size)
+ {
+ // welp!
+ serialize_uint32(fp, 8);
+ fwrite("(badstr)", 1, 8, fp);
+ }
+ else
+ {
+ serialize_uint32(fp, (uint32_t) i);
+ fwrite(str, 1, i, fp);
+ }
+}
+
+/*! \brief Read a monster handle */
+static inline void deserialize_monhandle(FILE *fp, Mon_handle *m)
+{
+ uint32_t tmp;
+ wrapped_fread(&tmp, 1, sizeof tmp, fp);
+ *m = htonl(tmp);
+}
+
+/*! \brief Write a monster handle */
+static inline void serialize_monhandle(FILE *fp, Mon_handle m)
+{
+ uint32_t tmp = htonl(m);
+ fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+/*! \brief Throw exception if monster handle invalid */
+static void check_monhandle(Mon_handle mon)
+{
+ if (mon >= first_free_mon_handle)
+ {
+ throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt");
+ }
+}
+
+static void check_pmref(int pm)
+{
+ if ((pm < 0) || (pm >= NUM_OF_PERMONS))
+ {
+ throw Obumb_dataexcep("Monster permon ID out of bounds - save game probably corrupt");
+ }
+}
+
+/*! \brief Read an object handle */
+static inline void deserialize_objhandle(FILE *fp, Obj_handle *o)
+{
+ uint32_t tmp;
+ wrapped_fread(&tmp, 1, sizeof tmp, fp);
+ *o = htonl(tmp);
+}
+
+/*! \brief Write an object handle */
+static inline void serialize_objhandle(FILE *fp, Obj_handle o)
+{
+ uint32_t tmp = htonl(o);
+ fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+static void check_poref(int po)
+{
+ if ((po < 0) || (po >= NUM_OF_PERMOBJS))
+ {
+ throw Obumb_dataexcep("Object permobj ID out of bounds - save game probably corrupt");
+ }
+}
+
+/*! \brief Throw exception if object handle invalid */
+static void check_objhandle(Obj_handle obj)
+{
+ if (obj >= first_free_obj_handle)
+ {
+ throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt");
+ }
+}
+
+/*! \brief Read a monster */
+static void deserialize_monster(FILE *fp, Mon * mon)
+{
+ deserialize_monhandle(fp, &(mon->self));
+ check_monhandle(mon->self);
+ deserialize_int(fp, &(mon->pm_ref));
+ check_pmref(mon->pm_ref);
+ deserialize_uint32(fp, &(mon->flags));
+ deserialize_coord(fp, &(mon->pos));
+ deserialize_coord(fp, &(mon->ai_lastpos));
+ deserialize_int(fp, &(mon->hpmax));
+ deserialize_int(fp, &(mon->hpcur));
+ deserialize_int(fp, &(mon->mtohit));
+ deserialize_int(fp, &(mon->rtohit));
+ deserialize_int(fp, &(mon->defence));
+ deserialize_int(fp, &(mon->mdam));
+ deserialize_int(fp, &(mon->rdam));
+ deserialize_int(fp, &(mon->next_summon));
+}
+
+/*! \brief Write a monster */
+static void serialize_monster(FILE *fp, Mon const& mon)
+{
+ serialize_monhandle(fp, mon.self);
+ serialize_int(fp, mon.pm_ref);
+ serialize_uint32(fp, mon.flags);
+ serialize_coord(fp, mon.pos);
+ serialize_coord(fp, mon.ai_lastpos);
+ serialize_int(fp, mon.hpmax);
+ serialize_int(fp, mon.hpcur);
+ serialize_int(fp, mon.mtohit);
+ serialize_int(fp, mon.rtohit);
+ serialize_int(fp, mon.defence);
+ serialize_int(fp, mon.mdam);
+ serialize_int(fp, mon.rdam);
+ serialize_int(fp, mon.next_summon);
+}
+
+/*! \brief Read an object */
+static void deserialize_object(FILE *fp, Obj *obj)
+{
+ deserialize_objhandle(fp, &(obj->self));
+ check_objhandle(obj->self);
+ deserialize_int(fp, &(obj->po_ref));
+ check_poref(obj->po_ref);
+ deserialize_uint32(fp, &(obj->flags));
+ deserialize_coord(fp, &(obj->pos));
+ deserialize_int(fp, &(obj->quan));
+ for (int i = 0; i < 4; ++i)
+ {
+ deserialize_uint32(fp, &(obj->meta[i]));
+ }
+ deserialize_int(fp, &(obj->durability));
+}
+
+/*! \brief Write an object */
+static void serialize_object(FILE *fp, Obj const& obj)
+{
+ serialize_objhandle(fp, obj.self);
+ serialize_int(fp, obj.po_ref);
+ serialize_uint32(fp, obj.flags);
+ serialize_coord(fp, obj.pos);
+ serialize_int(fp, obj.quan);
+ for (int i = 0; i < 4; ++i)
+ {
+ serialize_uint32(fp, obj.meta[i]);
+ }
+ serialize_int(fp, obj.durability);
+}
+
+/*! \brief Read the player */
+static void deserialize_player(FILE *fp, Player *player)
+{
+ int32_t tmp;
+ deserialize_cstring(fp, player->name, 16);
+ deserialize_monhandle(fp, &(player->mh));
+ deserialize_coord(fp, &(player->pos));
+ deserialize_int(fp, &tmp);
+ player->role = (Role_id) tmp;
+ deserialize_int(fp, &(player->body));
+ deserialize_int(fp, &(player->bdam));
+ deserialize_int(fp, &(player->agility));
+ deserialize_int(fp, &(player->adam));
+ deserialize_int(fp, &(player->hpmax));
+ deserialize_int(fp, &(player->hpcur));
+ deserialize_int(fp, &(player->food));
+ deserialize_int(fp, &(player->experience));
+ deserialize_int(fp, &(player->level));
+ deserialize_int(fp, &(player->defence));
+ deserialize_int(fp, &(player->protection));
+ deserialize_int(fp, &(player->leadfoot));
+ deserialize_int(fp, &(player->withering));
+ deserialize_int(fp, &(player->armourmelt));
+ deserialize_int(fp, &(player->speed));
+ for (int i = 0; i < NUM_DAMTYPES; ++i)
+ {
+ deserialize_uint32(fp, &(player->resistances[i]));
+ deserialize_uint32(fp, &(player->damage_amp[i]));
+ }
+ deserialize_uint32(fp, &(player->passwater));
+ deserialize_uint32(fp, &(player->flight));
+ deserialize_uint32(fp, &(player->protective_gear));
+ for (int i = 0; i < NUM_SKILLS; ++i)
+ {
+ deserialize_bool(fp, &(player->known_skill[i]));
+ }
+ for (int i = 0; i < INVENTORY_SIZE; ++i)
+ {
+ deserialize_objhandle(fp, &(player->inventory[i]));
+ check_objhandle(player->inventory[i]);
+ }
+ deserialize_objhandle(fp, &(player->weapon));
+ check_objhandle(player->weapon);
+ deserialize_objhandle(fp, &(player->armour));
+ check_objhandle(player->armour);
+ deserialize_objhandle(fp, &(player->ring));
+ check_objhandle(player->ring);
+ for (int i = 0; i < TOTAL_FELL_POWERS; ++i)
+ {
+ deserialize_int(fp, &(player->sympathy[i]));
+ }
+}
+
+/*! \brief Write the player */
+static void serialize_player(FILE *fp, Player const& player)
+{
+ serialize_cstring(fp, player.name, 16);
+ serialize_monhandle(fp, player.mh);
+ serialize_coord(fp, player.pos);
+ serialize_int(fp, (int) player.role);
+ serialize_int(fp, player.body);
+ serialize_int(fp, player.bdam);
+ serialize_int(fp, player.agility);
+ serialize_int(fp, player.adam);
+ serialize_int(fp, player.hpmax);
+ serialize_int(fp, player.hpcur);
+ serialize_int(fp, player.food);
+ serialize_int(fp, player.experience);
+ serialize_int(fp, player.level);
+ serialize_int(fp, player.defence);
+ serialize_int(fp, player.protection);
+ serialize_int(fp, player.leadfoot);
+ serialize_int(fp, player.withering);
+ serialize_int(fp, player.armourmelt);
+ serialize_int(fp, player.speed);
+ for (int i = 0; i < NUM_DAMTYPES; ++i)
+ {
+ serialize_uint32(fp, player.resistances[i]);
+ serialize_uint32(fp, player.damage_amp[i]);
+ }
+ serialize_uint32(fp, player.passwater);
+ serialize_uint32(fp, player.flight);
+ serialize_uint32(fp, player.protective_gear);
+ for (int i = 0; i < NUM_SKILLS; ++i)
+ {
+ serialize_bool(fp, player.known_skill[i]);
+ }
+ for (int i = 0; i < INVENTORY_SIZE; ++i)
+ {
+ serialize_objhandle(fp, player.inventory[i]);
+ }
+ serialize_objhandle(fp, player.weapon);
+ serialize_objhandle(fp, player.armour);
+ serialize_objhandle(fp, player.ring);
+ for (int i = 0; i < TOTAL_FELL_POWERS; ++i)
+ {
+ serialize_int(fp, player.sympathy[i]);
+ }
+}
+
+/*! \brief Read a Chunk from a FILE. */
+void deserialize_chunk(FILE *fp, Chunk *c)
+{
+ static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t deserializable_decals[CHUNK_EDGE][CHUNK_EDGE];
+ int i;
+ int j;
+ wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ wrapped_fread(deserializable_decals, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ for (i = 0; i < CHUNK_EDGE; ++i)
+ {
+ for (j = 0; j < CHUNK_EDGE; ++j)
{
- case DEATH_KILLED:
- fprintf(fp, "%s was killed by %s.\n", u.name, what);
- break;
- case DEATH_KILLED_MON:
- fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what);
- break;
- case DEATH_BODY:
- fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what);
- break;
- case DEATH_AGILITY:
- fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what);
- break;
- case DEATH_LASH:
- fprintf(fp, "%s tasted the lash one time too many.\n", u.name);
- break;
- case DEATH_RIBBONS:
- fprintf(fp, "%s looked good in ribbons.\n", u.name);
- break;
+ c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
+ c->flags[i][j] = ntohl(deserializable_flags[i][j]);
+ c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
+ c->decals[i][j] = (Decal_tag) ntohl(deserializable_decals[i][j]);
+ /* objs and mons will get accurately set once we've loaded the
+ * objs and mons. */
+ c->objs[i][j] = NO_OBJ;
+ c->mons[i][j] = NO_MON;
}
- 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);
- fsync(fd);
- fclose(fp);
}
- else
+}
+
+/*! \brief Write a Chunk out to a FILE. */
+void serialize_chunk(FILE *fp, Chunk const *c)
+{
+ static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t serializable_decals[CHUNK_EDGE][CHUNK_EDGE];
+ int i;
+ int j;
+ for (i = 0; i < CHUNK_EDGE; ++i)
{
- close(fd);
+ for (j = 0; j < CHUNK_EDGE; ++j)
+ {
+ serializable_terrain[i][j] = htonl(c->terrain[i][j]);
+ serializable_flags[i][j] = htonl(c->flags[i][j]);
+ serializable_regions[i][j] = htonl(c->region_number[i][j]);
+ serializable_decals[i][j] = htonl(c->decals[i][j]);
+ }
}
+ fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ fwrite(serializable_decals, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
}
-void wrapped_system(char const *cmd)
+/*! \brief Deserialize a Level */
+void deserialize_level(FILE *fp, Level *l)
{
- int i = system(cmd);
- if (i != 0)
+ uint32_t tmp;
+ uint32_t tmp_pair[2];
+ uint16_t tmp_shorts[2];
+ uint32_t i;
+ uint32_t j;
+ wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+ l->self.dungeon = ntohs(tmp_shorts[0]);
+ l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
+ wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ l->origin_off.y = (int32_t) ntohl(tmp_pair[0]);
+ l->origin_off.x = (int32_t) ntohl(tmp_pair[1]);
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->dead_space = Terrain(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->theme = Level_theme(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->layout = Level_layout(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->chunks_high = ntohl(tmp);
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->chunks_wide = ntohl(tmp);
+ initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
+ do
{
- throw(i);
+ wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
+ i = ntohl(tmp_pair[0]);
+ j = ntohl(tmp_pair[1]);
+ if (i == ~0u)
+ {
+ break;
+ }
+ if ((i >= l->chunks_high) || (j >= l->chunks_wide))
+ {
+ throw Obumb_dataexcep("Out-of-bounds chunk indices while loading level - save probably corrupt");
+ }
+ l->chunks[i][j] = new Chunk(l->dead_space);
+ deserialize_chunk(fp, l->chunks[i][j]);
}
+ while (1);
}
-void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
+/*! \brief Serialize a Level */
+void serialize_level(FILE *fp, Level const *l)
{
- size_t i = fread(buf, size, nmemb, fp);
- if (i != nmemb)
+ uint32_t tmp;
+ uint32_t tmp_pair[2];
+ uint16_t tmp_shorts[2];
+ uint32_t i;
+ uint32_t j;
+ tmp_shorts[0] = htons(l->self.dungeon);
+ tmp_shorts[1] = htons(l->self.depth);
+ fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+ tmp_pair[0] = htonl(l->origin_off.y);
+ tmp_pair[1] = htonl(l->origin_off.x);
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ tmp = htonl(l->dead_space);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->theme);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->layout);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->chunks_high);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->chunks_wide);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ for (i = 0; i < l->chunks_high; ++i)
{
- throw(i);
+ tmp_pair[0] = htonl(i);
+ for (j = 0; j < l->chunks_wide; ++j)
+ {
+ if (l->chunks[i][j])
+ {
+ tmp_pair[1] = htonl(j);
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ serialize_chunk(fp, l->chunks[i][j]);
+ }
+ }
}
+ tmp_pair[0] = tmp_pair[1] = ~0u;
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
}
+/*! \brief Save the game; set game_finished flag if successful */
void save_game(void)
{
FILE *fp;
int fd;
- uint32_t tmp;
- fd = openat(datadir_fd, "victrix-abyssi.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR);
+ fd = openat(datadir_fd, "obumbrata.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1)
{
return;
close(fd);
return;
}
- /* Write out the player. */
- fwrite(&u, 1, sizeof u, fp);
- tmp = htonl(depth);
- fwrite(&tmp, 1, sizeof tmp, fp);
- tmp = htonl(game_tick);
- fwrite(&tmp, 1, sizeof tmp, fp);
+ serialize_uint32(fp, OBUMBRATA_MAGIC_NUMBER);
+ serialize_uint32(fp, MAJVERS);
+ serialize_uint32(fp, MINVERS);
+ serialize_int(fp, depth);
+ serialize_int(fp, game_tick);
+ serialize_objhandle(fp, first_free_obj_handle);
+ serialize_monhandle(fp, first_free_mon_handle);
+ serialize_player(fp, u);
serialize_level(fp, &lvl);
- fwrite(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
- fwrite(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
+ for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
+ {
+ serialize_monhandle(fp, iter->second.self);
+ serialize_monster(fp, iter->second);
+ }
+ serialize_monhandle(fp, NO_MON);
+ for (auto iter = objects.begin(); iter != objects.end(); ++iter)
+ {
+ serialize_objhandle(fp, iter->second.self);
+ serialize_object(fp, iter->second);
+ }
+ serialize_objhandle(fp, NO_OBJ);
/* Clean up */
fflush(fp);
fsync(fd);
return;
}
-/*! \brief Monster map reinitialization after reload
- */
+/*! \brief Reinitialize dungeon level monster handle arrays after reload */
static void rebuild_mapmons(void)
{
- int i;
- for (i = 0; i < 100; i++)
+ for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
{
- if (monsters[i].used)
+ if (iter->second.flags & MF_USED)
{
- lvl.set_mon_at(monsters[i].pos, i);
+ lvl.set_mon_at(iter->second.pos, iter->first);
}
}
}
-/*! \brief Object map reinitialization after reload
- */
+/*! \brief Reinitialize dungeon level object handle arrays after reload */
static void rebuild_mapobjs(void)
{
- int i;
- for (i = 0; i < 100; i++)
+ for (auto iter = objects.begin(); iter != objects.end(); ++iter)
{
- if (objects[i].used && !objects[i].with_you)
+ if ((iter->second.flags & OF_USED) && !(iter->second.flags & OF_WITH_YOU))
{
- lvl.set_obj_at(objects[i].pos, i);
+ lvl.set_obj_at(iter->second.pos, iter->first);
}
}
}
-int load_game(void)
+static void check_magic(uint32_t magic)
{
- int fd;
- FILE *fp;
- fd = openat(datadir_fd, "victrix-abyssi.sav", O_RDONLY, 0);
- if (fd == -1)
+ if (magic != OBUMBRATA_MAGIC_NUMBER)
{
- return -1;
+ throw Obumb_dataexcep("Invalid file ID code");
}
- fp = fdopen(fd, "rb");
- if (!fp)
+}
+
+static void check_minvers(uint32_t minvers)
+{
+ if (minvers != MINVERS)
{
- return -1;
- }
- wrapped_fread(&u, 1, sizeof u, fp);
- wrapped_fread(&depth, 1, sizeof depth, fp);
- depth = ntohl(depth);
- wrapped_fread(&game_tick, 1, sizeof game_tick, fp);
- game_tick = ntohl(game_tick);
- deserialize_level(fp, &lvl);
- wrapped_fread(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
- wrapped_fread(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
- rebuild_mapobjs();
- rebuild_mapmons();
- fclose(fp);
- game_finished = false;
- look_around_you();
- notify_load_complete();
- int i = unlinkat(datadir_fd, "victrix-abyssi.sav", 0);
- if (i == -1)
+ throw Obumb_dataexcep("Minor-version mismatch");
+ }
+}
+
+static void check_majvers(uint32_t majvers)
+{
+ if (majvers != MAJVERS)
{
- debug_save_unlink_failed();
+ throw Obumb_dataexcep("Major-version mismatch");
}
- return 0;
}
+/*! \brief Reload and unlink a saved game
+ *
+ * Any read errors or probable data corruption encountered during loading will
+ * be reported as an exception. */
+void load_game(void)
+{
+ int fd = -1;
+ FILE *fp = nullptr;
+ uint32_t tmp;
+ try
+ {
+ fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0);
+ if (fd == -1)
+ {
+ throw Obumb_sysexcep(errno);
+ }
+ fp = fdopen(fd, "rb");
+ if (!fp)
+ {
+ throw Obumb_sysexcep(errno);
+ }
+ deserialize_uint32(fp, &tmp);
+ check_magic(tmp);
+ deserialize_uint32(fp, &tmp);
+ check_majvers(tmp);
+ deserialize_uint32(fp, &tmp);
+ check_minvers(tmp);
+ deserialize_int(fp, &depth);
+ deserialize_int(fp, &game_tick);
+ deserialize_objhandle(fp, &first_free_obj_handle);
+ if (first_free_obj_handle == 0)
+ {
+ throw Obumb_dataexcep("Object pool exhausted - save file probably corrupt");
+ }
+ deserialize_monhandle(fp, &first_free_mon_handle);
+ if (first_free_mon_handle == 0)
+ {
+ throw Obumb_dataexcep("Monster pool exhausted - save file probably corrupt");
+ }
+ deserialize_player(fp, &u);
+ deserialize_level(fp, &lvl);
+ while (1)
+ {
+ Mon_handle mon;
+ deserialize_monhandle(fp, &mon);
+ check_monhandle(mon);
+ if (mon != NO_MON)
+ {
+ Mon m;
+ deserialize_monster(fp, &m);
+ monsters[mon] = m;
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (1)
+ {
+ Obj_handle obj;
+ deserialize_objhandle(fp, &obj);
+ check_objhandle(obj);
+ if (obj != NO_OBJ)
+ {
+ Obj o;
+ deserialize_object(fp, &o);
+ objects[obj] = o;
+ }
+ else
+ {
+ break;
+ }
+ }
+ rebuild_mapobjs();
+ rebuild_mapmons();
+ u.mptr = mon_snapv(u.mh);
+ fclose(fp);
+ game_finished = false;
+ look_around_you();
+ notify_load_complete();
+ int i = unlinkat(datadir_fd, "obumbrata.sav", 0);
+ if (i == -1)
+ {
+ debug_save_unlink_failed();
+ }
+ return;
+ }
+ catch (Obumb_dataexcep e)
+ {
+ /* NOTE: if we got a dataexcep, we've definitely opened fp, so no need
+ * to check for null pointer */
+ fclose(fp);
+ game_cleanup();
+ throw;
+ }
+ catch (Obumb_sysexcep e)
+ {
+ if (fp)
+ {
+ fclose(fp);
+ }
+ else if (fd != -1)
+ {
+ close(fd);
+ }
+ game_cleanup();
+ throw;
+ }
+}
+
+/*! \brief Write human-readable snapshot of the player character
+ *
+ * \todo Sanitize the player character's name, or the filename, somewhere */
void write_char_dump(void)
{
FILE *fp;
fclose(fp);
}
+/*! \brief Read configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+void load_config(void)
+{
+ int i;
+ i = setup_dirs("com.blackswordsonics", "obumbrata", &datadir_fd, &confdir_fd);
+ if (i < 0)
+ {
+ perror("obumbrata: couldn't access configuration directories");
+ exit(1);
+ }
+ // TODO write this function
+}
+
+/*! \brief Rewrite configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+int rewrite_config(void)
+{
+ // TODO write this function
+ return 0;
+}
+
+/*! \brief Get a list of extant saved games and character names
+ *
+ * Fill in the map<string,string> container *dest using character names
+ * as keys and filenames as values.
+ *
+ * \param dest Pointer to map<string,string> container to fill with data
+ * \return Number of charname/filename pairs written to container
+ * \todo Basic implementation
+ * \todo Deal gracefully with a savefile going away before we open it
+ */
+int get_savefile_list(std::map<std::string, std::string> *dest)
+{
+ // TODO write this function
+ return 0;
+}
+
+/*! \brief Record the details of the PC's death to the log
+ */
+void log_death(Death d, char const *what)
+{
+ FILE *fp;
+ int fd;
+ fd = openat(datadir_fd, "obumbrata.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ {
+ return;
+ }
+ fp = fdopen(fd, "a");
+ if (fp)
+ {
+ switch (d)
+ {
+ case DEATH_KILLED:
+ fprintf(fp, "%s was killed by %s.\n", u.name, what);
+ break;
+ case DEATH_KILLED_MON:
+ fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what);
+ break;
+ case DEATH_BODY:
+ fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what);
+ break;
+ case DEATH_AGILITY:
+ fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what);
+ break;
+ case DEATH_LASH:
+ fprintf(fp, "%s tasted the lash one time too many.\n", u.name);
+ break;
+ case DEATH_RIBBONS:
+ fprintf(fp, "%s looked good in ribbons.\n", u.name);
+ break;
+ }
+ 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);
+ fsync(fd);
+ fclose(fp);
+ }
+ else
+ {
+ close(fd);
+ }
+}
+
/* log.cc */
// vim:cindent:expandtab
/* \file main.cc
- * \brief main core of Victrix Abyssi
+ * \brief main core
*/
/* Copyright 2005-2014 Martin Read
{
depth = 0;
game_tick = 0;
+ first_free_obj_handle = 1u;
+ first_free_mon_handle = 1u;
level_cleanup();
player_cleanup();
}
-void new_game(char const *name)
+void new_game(char const *name, Role_id role)
{
game_finished = false;
depth = 1;
game_tick = 0;
- u_init(name);
+ u_init(name, role);
make_new_level();
}
void main_loop(void)
{
- int i;
int action_speed = 0;
welcome();
while (!game_finished)
}
} while (cost == Cost_none);
}
- for (i = 0; i < 100; i++)
+ for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
{
- if (!monsters[i].used)
+ if (!(iter->second.flags & MF_ALIVE))
{
- /* Unused monster */
continue;
}
/* Update the monster's status. */
- update_mon(i);
- if (action_speed <= permons[monsters[i].mon_id].speed)
+ update_mon(iter->first);
+ if (action_speed <= permons[iter->second.pm_ref].speed)
{
- mon_acts(i);
+ mon_acts(iter->first);
}
if (game_finished)
{
game_cleanup();
}
-/*! \brief main()
- */
+/*! \brief main() */
int main(void)
{
load_config();
Level lvl;
int depth;
+Decal_desc decal_props[NUM_DECALS] =
+{
+ { "blood", "Blood, presumably from a mortal creature.", Gcol_blood },
+ { "demonic ichor", "The uncanny, faintly glowing blue ichor of a demon.", Gcol_l_blue },
+ { "slime", "Unpleasant sticky green goo.", Gcol_green },
+ { "pus", "Sickly yellow gunk from an infected creature.", Gcol_blood },
+};
+
void drop_all_chunks(Level *l)
{
- int i;
- int j;
+ uint32_t i;
+ uint32_t j;
if (l->chunks)
{
for (i = 0; i < l->chunks_high; ++i)
terrain[k][m] = t;
flags[k][m] = 0;
region_number[k][m] = NO_REGION;
+ decals[k][m] = NO_DECAL;
}
}
}
int i;
int j;
drop_all_chunks(l);
- l->origin_off = Stationary;
l->chunks_high = height;
l->chunks_wide = width;
new_chunk_grid = new Chunk ** [height];
grow(North);
rc = c + origin_off;
}
- while (rc.y > (chunks_high << CHUNK_SHIFT))
+ while (rc.y > int32_t(chunks_high << CHUNK_SHIFT))
{
grow(South);
}
grow(West);
rc = c + origin_off;
}
- while (rc.x > (chunks_wide << CHUNK_SHIFT))
+ while (rc.x > int32_t(chunks_wide << CHUNK_SHIFT))
{
grow(East);
}
{
Chunk ***new_chunk_grid;
Chunk **new_chunk_row;
- int i;
- int j;
+ uint32_t i;
+ uint32_t j;
if (o.y < 0)
{
- int i;
origin_off.y += CHUNK_EDGE;
++chunks_high;
new_chunk_grid = new Chunk **[chunks_high];
*/
Coord Level::random_point(int margin) const
{
- Coord tl = { margin, margin };
- Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) };
+ Coord tl = { min_y() + margin, min_x() + margin };
+ Coord br = { max_y() - margin, max_x() - margin };
return inc_boxed(tl, br);
}
int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
{
int count = 0;
- for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter)
+ for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
{
if (iter->destination == from)
{
void leave_level(void)
{
- int i;
- for (i = 0; i < 100; i++)
+ for (auto iter = monsters.begin(); iter != monsters.end(); )
+ {
+ auto iter2 = iter;
+ ++iter2;
+ monsters.erase(iter);
+ iter = iter2;
+ }
+ for (auto iter = objects.begin(); iter != objects.end(); )
{
- /* Throw away each monster */
- monsters[i].used = false;
- /* and each object not carried by the player */
- if (!objects[i].with_you)
+ auto iter2 = iter;
+ ++iter2;
+ if (!(iter->second.flags & OF_WITH_YOU))
{
- objects[i].used = false;
+ objects.erase(iter);
}
+ iter = iter2;
}
+ lvl.origin_off = Stationary;
+ drop_all_chunks(&lvl);
depth++;
}
{
build_level();
populate_level();
- inject_player(lvl.self.naive_prev());
- look_around_you();
notify_change_of_depth();
+ inject_player(&lvl, lvl.self.naive_prev());
+ look_around_you();
}
/*! \brief Random walk which grows the level
* This version of run_random_walk will extend the level boundaries instead
* of rejecting out-of-bounds coordinates.
*/
-Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk(Level *l, Coord oc, rwalk_mod_funcptr func,
void const *priv_ptr, int cells)
{
int i;
Coord c = oc;
int bailout = 10000;
- func(c, priv_ptr);
+ func(l, c, priv_ptr);
for (i = 0; (i < cells) && (bailout > 0); --bailout)
{
oc = c;
- if (zero_die(2))
- {
- c.y += (zero_die(2) ? -1 : 1);
- c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2));
- }
- else
- {
- c.x += (zero_die(2) ? -1 : 1);
- c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2));
- }
- switch (func(c, priv_ptr))
+ c += zero_die(2) ?
+ (zero_die(2) ? North : South) :
+ (zero_die(2) ? East : West);
+ c.clamp(l->min_y() + 2, l->min_x() + 2, l->max_y() - 2, l->max_x() - 2);
+ switch (func(l, c, priv_ptr))
{
case 0:
/* nothing changed */
}
if (bailout < 1)
{
- debug_excavation_bailout();
+ throw Obumb_levgen_excep("run_random_walk exceeded bailout limit");
}
return c;
}
* This version of run_random_walk will extend the level boundaries instead
* of rejecting out-of-bounds coordinates.
*/
-Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk_unbounded(Level *l, Coord oc, rwalk_mod_funcptr func,
void const *priv_ptr, int cells)
{
int i;
Coord c = oc;
int bailout = 10000;
- func(c, priv_ptr);
+ func(l, c, priv_ptr);
for (i = 0; (i < cells) && (bailout > 0); --bailout)
{
oc = c;
- if (zero_die(2))
- {
- c.y += (zero_die(2) ? -1 : 1);
- }
- else
- {
- c.x += (zero_die(2) ? -1 : 1);
- }
- if (c.y <= lvl.min_y())
+ c += zero_die(2) ?
+ (zero_die(2) ? North : South) :
+ (zero_die(2) ? East : West);
+ if (c.y <= l->min_y())
{
- lvl.grow(North, true);
+ l->grow(North, true);
}
- if (c.y >= lvl.max_y())
+ if (c.y >= l->max_y())
{
- lvl.grow(South, true);
+ l->grow(South, true);
}
- if (c.x <= lvl.min_x())
+ if (c.x <= l->min_x())
{
- lvl.grow(West, true);
+ l->grow(West, true);
}
- if (c.x >= lvl.max_x())
+ if (c.x >= l->max_x())
{
- lvl.grow(East, true);
+ l->grow(East, true);
}
- switch (func(c, priv_ptr))
+ switch (func(l, c, priv_ptr))
{
case 0:
/* nothing changed */
}
if (bailout < 1)
{
- debug_excavation_bailout();
+ throw Obumb_levgen_excep("run_random_walk exceeded bailout limit");
}
return c;
}
lvl.stairs.clear();
rng.extract_serialization(saved_state_buffer, saved_state_size);
theme_roll = zero_die(depth + 50);
- if (!zero_die(4))
+ if (!zero_die(6))
+ {
+ lvl.layout = LAYOUT_CAVE_PITS;
+ }
+ else if (!zero_die(5))
{
lvl.layout = LAYOUT_CAVE_INTRUSIONS;
}
- else if ((depth > 1) && !zero_die(6))
+ else if ((depth > 8) && !zero_die(6))
{
lvl.layout = LAYOUT_CAVE_SHRINE;
}
switch (lvl.layout)
{
case LAYOUT_CAVE_SHRINE:
- build_level_shrine();
+ build_level_shrine(&lvl);
break;
case LAYOUT_CAVE_INTRUSIONS:
- build_level_intrusions();
+ build_level_intrusions(&lvl);
break;
case LAYOUT_DUNGEONBASH:
- build_level_dungeonbash();
+ build_level_dungeonbash(&lvl);
break;
case LAYOUT_CLASSIC_CAVE:
- build_level_cave();
+ build_level_cave(&lvl);
break;
- }
-}
-
-/*! \brief Build a dungeonbash-style rooms-and-corridors level */
-void build_level_dungeonbash()
-{
- int chy;
- int chx;
- /* We know we're going to use all nine chunks, so create them
- * immediately. */
- initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
- /* One room per chunk. */
- for (chy = 0; chy < lvl.chunks_high; ++chy)
- {
- for (chx = 0; chx < lvl.chunks_wide; ++ chx)
+ case LAYOUT_CAVE_PITS:
+ if (depth > 5)
{
- /* Smallest allowed room has a 2x2 interior. */
- Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) };
- /* Each dimension has a 1-in-3 chance to get another boost */
if (!zero_die(3))
{
- room_size.y += zero_die(5);
+ build_level_pits(&lvl, LAVA);
}
- if (!zero_die(3))
+ else if (!zero_die(2))
+ {
+ build_level_pits(&lvl, WATER);
+ }
+ else
{
- room_size.x += zero_die(5);
+ build_level_pits(&lvl, CHASM);
}
}
+ else
+ {
+ build_level_pits(&lvl, CHASM);
+ }
+ break;
+ case LAYOUT_CLAUSTROPHOBIA:
+ build_level_claustrophobia(&lvl);
+ break;
}
}
/*! \brief Excavation function for use with random walks */
-int excavation_write(Coord c, void const *data)
+int excavation_write(Level *l, Coord c, void const *data)
{
int const *data_as_ints = (int const *) data;
int const *overwrite = data_as_ints + 2;
int newterr = data_as_ints[1];
int j;
- if (lvl.flags_at(c) & MAPFLAG_HARDWALL)
+ if (l->flags_at(c) & MAPFLAG_HARDWALL)
{
/* Don't bite into hardened walls, but don't waste a step on
* them either. */
return 2;
}
+ if (l->terrain_at(c) == newterr)
+ {
+ return 0;
+ }
for (j = 0; j < data_as_ints[0]; ++j)
{
- if (lvl.terrain_at(c) == overwrite[j])
+ if (l->terrain_at(c) == overwrite[j])
{
- lvl.set_terrain_at(c, (Terrain) newterr);
+ l->set_terrain_at(c, (Terrain) newterr);
return 1;
}
}
- return 0;
+ return 2;
}
/*! \brief "Intrusion" function for use with random walks */
-int intrusion_write(Coord c, void const *data)
+int intrusion_write(Level *l, Coord c, void const *data)
{
Terrain const *tptr = (Terrain const *) data;
- if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL))
+ Terrain old_terr = l->terrain_at(c);
+ // Meander across MAPFLAG_HARDWALL and across whatever terrain we
+ // might be overwriting dead space with.
+ if ((l->flags_at(c) & MAPFLAG_HARDWALL) ||
+ (tptr && (*tptr == old_terr)))
{
return 0;
}
+ // Bounce off anything that isn't "dead space"
+ if (old_terr != l->dead_space)
+ {
+ return 2;
+ }
/* Don't intrude too closely on the centre of the level */
if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4)))
{
}
if (tptr)
{
- lvl.set_terrain_at(c, *tptr);
+ l->set_terrain_at(c, *tptr);
}
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
return 1;
}
/*! \brief Do a random walk laying down an exclusion area */
-void place_random_intrusion(Terrain new_wall)
+void place_random_intrusion(Level *l, Terrain new_wall)
{
Coord c;
int intrusion_size = inc_flat(27, 54);
{
c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
- } while (lvl.flags_at(c) & MAPFLAG_HARDWALL);
- run_random_walk(c, intrusion_write, &new_wall, intrusion_size);
+ } while (l->flags_at(c) & MAPFLAG_HARDWALL);
+ try
+ {
+ run_random_walk(l, c, intrusion_write, &new_wall, intrusion_size);
+ }
+ catch (...)
+ {
+ // undersize intrusions are not a problem
+ }
}
/*! \brief Get a valid square to generate a monster on */
-Pass_fail get_levgen_mon_floor(Coord *c)
+Pass_fail get_levgen_mon_floor(Level *l, Coord *c)
{
int cell_try;
Coord t;
for (cell_try = 0; cell_try < 200; cell_try++)
{
- t = lvl.random_point(1);
- if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON))
+ t = l->random_point(1);
+ if ((l->terrain_at(t) != FLOOR) || (l->mon_at(t) != NO_MON))
{
t = Nowhere;
continue;
Coord c;
int ic;
/* Generate some random monsters */
- for (i = 0; i < 10; i++)
+ for (i = 0; i < (10 + depth); i++)
{
- pf = get_levgen_mon_floor(&c);
+ pf = get_levgen_mon_floor(&lvl, &c);
if (pf == You_fail)
{
continue;
/* Generate some random treasure */
for (i = 0; i < ic; i++)
{
- pf = get_levgen_mon_floor(&c);
+ pf = get_levgen_mon_floor(&lvl, &c);
if (pf == You_fail)
{
continue;
}
/*! \brief Inject the player into the level based on where they arrived from */
-void inject_player(Level_key from)
+void inject_player(Level *l, Level_key from)
{
/* For now we are allowing only one linkage between levels */
Coord c;
std::vector<Stair_detail> stair_list;
- int i = lvl.find_stairs(from, &stair_list);
+ int i = l->find_stairs(from, &stair_list);
if (i != 0)
{
c = stair_list[0].pos;
/*! \brief Description of terrain types */
Terrain_props terrain_props[NUM_TERRAINS] =
{
- { "cave wall", '#', "â–ˆ", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "masonry wall", '#', "â–ˆ", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "amethyst wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "iron wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "skin wall", '#', "â–ˆ", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "bone wall", '#', "â–ˆ", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile },
+ { "cave wall", '#', "â–ˆ", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "masonry wall", '#', "â–ˆ", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "amethyst wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "iron wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "skin wall", '#', "â–ˆ", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "bone wall", '#', "â–ˆ", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile | TFLAG_block_items },
{ "floor", '.', "·", Gcol_l_grey, TFLAG_floor },
{ "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor },
{ "iron floor", '.', "·", Gcol_iron, TFLAG_floor },
{ "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor },
{ "bone floor", '.', "·", Gcol_white, TFLAG_floor },
- { "altar", '_', "_", Gcol_l_grey, 0 },
- { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend },
- { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend },
- { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard },
- { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard },
+ { "altar", '_', "_", Gcol_l_grey, 0 | TFLAG_block_items },
+ { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend | TFLAG_block_items },
+ { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend | TFLAG_block_items },
+ { "inert portal", '^', "☊", Gcol_d_grey, TFLAG_portal | TFLAG_ascend | TFLAG_block_items },
+ { "active portal", '^', "☊", Gcol_white, TFLAG_portal | TFLAG_descend | TFLAG_block_items },
+ { "chasm", ' ', " ", Gcol_d_grey, TFLAG_fall_hazard | TFLAG_block_items },
+ { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard | TFLAG_block_items },
+ { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard | TFLAG_block_items },
};
/*! \brief self-explanatory */
-bool terrain_is_opaque(Terrain terr)
+bool terrain_is_floor(Terrain terr)
{
- return terrain_props[terr].flags & TFLAG_opaque;
+ return terrain_props[terr].flags & TFLAG_floor;
}
/*! \brief self-explanatory */
-bool terrain_blocks_beings(Terrain terr)
+bool terrain_is_wall(Terrain terr)
{
- return terrain_props[terr].flags & TFLAG_block_beings;
+ return terrain_props[terr].flags & TFLAG_wall;
}
/*! \brief self-explanatory */
-bool terrain_blocks_missiles(Terrain terr)
+bool terrain_is_opaque(Terrain terr)
{
- return terrain_props[terr].flags & TFLAG_block_missile;
+ return terrain_props[terr].flags & TFLAG_opaque;
}
-/*! \brief Read a Chunk from a FILE.
- *
- * Yes, I know "throw(errno)" is tacky and stupid, but it achieves the
- * desired result: the save is obviously garbage, so there's no point
- * in finishing loading it.
- */
-void deserialize_chunk(FILE *fp, Chunk *c)
+bool terrain_gapes(Terrain terr)
{
- static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
- static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
- static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
- int i;
- int j;
- wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
- wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
- wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
- for (i = 0; i < CHUNK_EDGE; ++i)
- {
- for (j = 0; j < CHUNK_EDGE; ++j)
- {
- c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
- c->flags[i][j] = ntohl(deserializable_flags[i][j]);
- c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
- /* objs and mons will get accurately set once we've loaded the
- * objs and mons. */
- c->objs[i][j] = NO_OBJ;
- c->mons[i][j] = NO_MON;
- }
- }
+ return terrain_props[terr].flags & TFLAG_fall_hazard;
}
-/*! \brief Write a Chunk out to a FILE. */
-void serialize_chunk(FILE *fp, Chunk const *c)
+bool terrain_drowns(Terrain terr)
{
- static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
- static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
- static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
- int i;
- int j;
- for (i = 0; i < CHUNK_EDGE; ++i)
- {
- for (j = 0; j < CHUNK_EDGE; ++j)
- {
- serializable_terrain[i][j] = htonl(c->terrain[i][j]);
- serializable_flags[i][j] = htonl(c->flags[i][j]);
- serializable_regions[i][j] = htonl(c->region_number[i][j]);
- }
- }
- fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
- fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
- fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ return terrain_props[terr].flags & TFLAG_drown_hazard;
}
-/*! \brief Serialize a Level */
-void serialize_level(FILE *fp, Level const *l)
+bool terrain_is_hot(Terrain terr)
{
- uint32_t tmp;
- uint32_t tmp_pair[2];
- uint16_t tmp_shorts[2];
- int i;
- int j;
- tmp_shorts[0] = htons(l->self.dungeon);
- tmp_shorts[1] = htons(l->self.depth);
- fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
- tmp_pair[0] = htonl(l->origin_off.y);
- tmp_pair[1] = htonl(l->origin_off.x);
- fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
- tmp = htonl(l->dead_space);
- fwrite(&tmp, sizeof tmp, 1, fp);
- tmp = htonl(l->theme);
- fwrite(&tmp, sizeof tmp, 1, fp);
- tmp = htonl(l->layout);
- fwrite(&tmp, sizeof tmp, 1, fp);
- tmp = htonl(l->chunks_high);
- fwrite(&tmp, sizeof tmp, 1, fp);
- tmp = htonl(l->chunks_wide);
- fwrite(&tmp, sizeof tmp, 1, fp);
- for (i = 0; i < l->chunks_high; ++i)
- {
- tmp_pair[0] = htonl(i);
- for (j = 0; j < l->chunks_wide; ++j)
- {
- if (l->chunks[i][j])
- {
- tmp_pair[1] = htonl(j);
- fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
- serialize_chunk(fp, l->chunks[i][j]);
- }
- }
- }
- tmp_pair[0] = tmp_pair[1] = ~0u;
- fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ return terrain_props[terr].flags & TFLAG_fire_hazard;
}
-/*! \brief Deserialize a Level
- *
- * \todo Throw an exception if the level is malformed e.g. OOB chunk indices.
- */
-void deserialize_level(FILE *fp, Level *l)
+/*! \brief self-explanatory */
+bool terrain_blocks_items(Terrain terr)
{
- uint32_t tmp;
- uint32_t tmp_pair[2];
- uint16_t tmp_shorts[2];
- uint32_t i;
- uint32_t j;
- wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
- l->self.dungeon = ntohs(tmp_shorts[0]);
- l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
- wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
- l->origin_off.y = (int) ntohl(tmp_pair[0]);
- l->origin_off.x = (int) ntohl(tmp_pair[1]);
- wrapped_fread(&tmp, sizeof tmp, 1, fp);
- l->dead_space = Terrain(ntohl(tmp));
- wrapped_fread(&tmp, sizeof tmp, 1, fp);
- l->theme = level_theme(ntohl(tmp));
- wrapped_fread(&tmp, sizeof tmp, 1, fp);
- l->layout = level_layout(ntohl(tmp));
- wrapped_fread(&tmp, sizeof tmp, 1, fp);
- l->chunks_high = ntohl(tmp);
- wrapped_fread(&tmp, sizeof tmp, 1, fp);
- l->chunks_wide = ntohl(tmp);
- initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
- do
- {
- wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
- i = ntohl(tmp_pair[0]);
- j = ntohl(tmp_pair[1]);
- if (i == ~0u)
- {
- break;
- }
- l->chunks[i][j] = new Chunk(l->dead_space);
- deserialize_chunk(fp, l->chunks[i][j]);
- }
- while (1);
+ return terrain_props[terr].flags & TFLAG_block_items;
}
+/*! \brief self-explanatory */
+bool terrain_blocks_beings(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_block_beings;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_missiles(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_block_missile;
+}
+
+/*! \brief Rotate a connection bitmask */
uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
{
clockwise_steps &= 0x3;
return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf;
}
+/*! \brief Clean up everything level-related */
void level_cleanup(void)
{
- int i;
- for (i = 0; i < MONSTERS_IN_PLAY; ++i)
- {
- monsters[i].used = false;
- }
- for (i = 0; i < OBJECTS_IN_PLAY; ++i)
- {
- objects[i].used = false;
- objects[i].with_you = false;
- }
+ monsters.clear();
+ objects.clear();
drop_all_chunks(&lvl);
}
--- /dev/null
+/*! \file map.cc
+ * \brief Map generation and population
+ */
+
+/* Copyright 2005-2014 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.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#include <string.h>
+Level lvl;
+int depth;
+
+void drop_all_chunks(Level *l)
+{
+ int i;
+ int j;
+ if (l->chunks)
+ {
+ for (i = 0; i < l->chunks_high; ++i)
+ {
+ for (j = 0; j < l->chunks_wide; ++j)
+ {
+ if (l->chunks[i][j])
+ {
+ delete l->chunks[i][j];
+ }
+ l->chunks[i][j] = nullptr;
+ }
+ delete[] l->chunks[i];
+ l->chunks[i] = nullptr;
+ }
+ delete[] l->chunks;
+ l->chunks = nullptr;
+ }
+}
+
+Chunk::Chunk(Terrain t)
+{
+ int k, m;
+ for (k = 0; k < CHUNK_EDGE; ++k)
+ {
+ for (m = 0; m < CHUNK_EDGE; ++m)
+ {
+ objs[k][m] = NO_OBJ;
+ mons[k][m] = NO_MON;
+ terrain[k][m] = t;
+ flags[k][m] = 0;
+ region_number[k][m] = NO_REGION;
+ }
+ }
+}
+
+void initialize_chunks(Level *l, int height, int width, bool dense)
+{
+ Chunk ***new_chunk_grid;
+ Chunk **new_chunk_row;
+ Chunk *new_chunk;
+ int i;
+ int j;
+ drop_all_chunks(l);
+ l->origin_off = Stationary;
+ l->chunks_high = height;
+ l->chunks_wide = width;
+ new_chunk_grid = new Chunk ** [height];
+ for (i = 0; i < height; ++i)
+ {
+ new_chunk_row = new Chunk * [width];
+ for (j = 0; j < width; ++j)
+ {
+ new_chunk_row[j] = new_chunk = (dense ? new Chunk(l->dead_space) : nullptr);
+ }
+ new_chunk_grid[i] = new_chunk_row;
+ }
+ l->chunks = new_chunk_grid;
+}
+
+void Level::vivify(Coord c)
+{
+ Coord rc = c + origin_off;
+ while (rc.y < 0)
+ {
+ grow(North);
+ rc = c + origin_off;
+ }
+ while (rc.y > (chunks_high << CHUNK_SHIFT))
+ {
+ grow(South);
+ }
+ while (rc.x < 0)
+ {
+ grow(West);
+ rc = c + origin_off;
+ }
+ while (rc.x > (chunks_wide << CHUNK_SHIFT))
+ {
+ grow(East);
+ }
+ if (!chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT])
+ {
+ chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT] = new Chunk(dead_space);
+ }
+}
+
+void Level::grow(Offset o, bool dense)
+{
+ Chunk ***new_chunk_grid;
+ Chunk **new_chunk_row;
+ int i;
+ int j;
+ if (o.y < 0)
+ {
+ int i;
+ origin_off.y += CHUNK_EDGE;
+ ++chunks_high;
+ new_chunk_grid = new Chunk **[chunks_high];
+ for (i = 0; i < (chunks_high - 1); ++i)
+ {
+ new_chunk_grid[i + 1] = chunks[i];
+ }
+ new_chunk_grid[0] = new_chunk_row = new Chunk *[chunks_wide];
+ for (i = 0; i < chunks_wide; ++i)
+ {
+ new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+ }
+ delete[] chunks;
+ chunks = new_chunk_grid;
+ }
+ else if (o.y > 0)
+ {
+ ++chunks_high;
+ new_chunk_grid = new Chunk **[chunks_high];
+ for (i = 0; i < (chunks_high - 1); ++i)
+ {
+ new_chunk_grid[i] = chunks[i];
+ }
+ new_chunk_grid[chunks_high - 1] = new_chunk_row = new Chunk *[chunks_wide];
+ for (i = 0; i < chunks_wide; ++i)
+ {
+ new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+ }
+ delete[] chunks;
+ chunks = new_chunk_grid;
+ }
+ if (o.x < 0)
+ {
+ ++chunks_wide;
+ origin_off.x += CHUNK_EDGE;
+ for (i = 0; i < chunks_high; ++i)
+ {
+ new_chunk_row = new Chunk *[chunks_wide];
+ for (j = 0; j < chunks_wide - 1; ++j)
+ {
+ new_chunk_row[j + 1] = chunks[i][j];
+ }
+ new_chunk_row[0] = dense ? new Chunk(dead_space) : nullptr;
+ delete[] chunks[i];
+ chunks[i] = new_chunk_row;
+ }
+ }
+ else if (o.x > 0)
+ {
+ ++chunks_wide;
+ for (i = 0; i < chunks_high; ++i)
+ {
+ new_chunk_row = new Chunk *[chunks_wide];
+ for (j = 0; j < chunks_wide - 1; ++j)
+ {
+ new_chunk_row[j] = chunks[i][j];
+ }
+ new_chunk_row[chunks_wide - 1] = dense ? new Chunk(dead_space) : nullptr;
+ delete[] chunks[i];
+ chunks[i] = new_chunk_row;
+ }
+ }
+}
+
+/*! \brief Find a random point on the level
+ *
+ * \param margin Width of the "dead" space along each edge in which the point cannot appear
+ */
+Coord Level::random_point(int margin) const
+{
+ Coord tl = { margin, margin };
+ Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) };
+ return inc_boxed(tl, br);
+}
+
+int Level::add_stairs_at(Coord c, Terrain t, Level_key l)
+{
+ Stair_detail sd = { c, t, l };
+ if (!(terrain_props[t].flags &
+ (TFLAG_ascend | TFLAG_portal | TFLAG_descend)))
+ {
+ return NO_STAIRS;
+ }
+ set_terrain_at(c, t);
+ stairs.push_back(sd);
+ return stairs.size() - 1;
+}
+
+Level_key const No_level = { 65535, -32768 };
+
+Stair_detail const Bad_stairs = { Nowhere, FLOOR, No_level };
+Stair_detail Level::find_stairs(Coord pos) const
+{
+ for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+ {
+ if (iter->pos == pos)
+ {
+ return *iter;
+ }
+ }
+ return Bad_stairs;
+}
+
+int Level::find_stairs(Terrain t, std::vector<Stair_detail> *dest) const
+{
+ int count = 0;
+ for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+ {
+ if (iter->t == t)
+ {
+ dest->push_back(*iter);
+ ++count;
+ }
+ }
+ return count;
+}
+
+int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
+{
+ int count = 0;
+ for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter)
+ {
+ if (iter->destination == from)
+ {
+ dest->push_back(*iter);
+ ++count;
+ }
+ }
+ return count;
+}
+
+void leave_level(void)
+{
+ int i;
+ for (i = 0; i < 100; i++)
+ {
+ /* Throw away each monster */
+ monsters[i].used = false;
+ /* and each object not carried by the player */
+ if (!objects[i].with_you)
+ {
+ objects[i].used = false;
+ }
+ }
+ depth++;
+}
+
+void make_new_level(void)
+{
+ build_level();
+ populate_level();
+ inject_player(lvl.self.naive_prev());
+ look_around_you();
+ notify_change_of_depth();
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+ void const *priv_ptr, int cells)
+{
+ int i;
+ Coord c = oc;
+ int bailout = 10000;
+
+ func(c, priv_ptr);
+ for (i = 0; (i < cells) && (bailout > 0); --bailout)
+ {
+ oc = c;
+ if (zero_die(2))
+ {
+ c.y += (zero_die(2) ? -1 : 1);
+ c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2));
+ }
+ else
+ {
+ c.x += (zero_die(2) ? -1 : 1);
+ c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2));
+ }
+ switch (func(c, priv_ptr))
+ {
+ case 0:
+ /* nothing changed */
+ break;
+ case 1:
+ /* changed normally */
+ ++i;
+ break;
+ case 2:
+ /* reject! */
+ c = oc;
+ break;
+ }
+ }
+ if (bailout < 1)
+ {
+ debug_excavation_bailout();
+ }
+ return c;
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+ void const *priv_ptr, int cells)
+{
+ int i;
+ Coord c = oc;
+ int bailout = 10000;
+
+ func(c, priv_ptr);
+ for (i = 0; (i < cells) && (bailout > 0); --bailout)
+ {
+ oc = c;
+ if (zero_die(2))
+ {
+ c.y += (zero_die(2) ? -1 : 1);
+ }
+ else
+ {
+ c.x += (zero_die(2) ? -1 : 1);
+ }
+ if (c.y <= lvl.min_y())
+ {
+ lvl.grow(North, true);
+ }
+ if (c.y >= lvl.max_y())
+ {
+ lvl.grow(South, true);
+ }
+ if (c.x <= lvl.min_x())
+ {
+ lvl.grow(West, true);
+ }
+ if (c.x >= lvl.max_x())
+ {
+ lvl.grow(East, true);
+ }
+ switch (func(c, priv_ptr))
+ {
+ case 0:
+ /* nothing changed */
+ break;
+ case 1:
+ /* changed normally */
+ ++i;
+ break;
+ case 2:
+ /* reject! */
+ c = oc;
+ break;
+ }
+ }
+ if (bailout < 1)
+ {
+ debug_excavation_bailout();
+ }
+ return c;
+}
+
+/*! \brief Entry point for level generation.
+ *
+ * \todo Maybe implement support for Lua-based level generators.
+ */
+void build_level(void)
+{
+ int theme_roll;
+ lvl.self.depth = (int16_t) depth;
+ lvl.self.dungeon = 0;
+ lvl.stairs.clear();
+ rng.extract_serialization(saved_state_buffer, saved_state_size);
+ theme_roll = zero_die(depth + 50);
+ if (!zero_die(4))
+ {
+ lvl.layout = LAYOUT_CAVE_INTRUSIONS;
+ }
+ else if ((depth > 1) && !zero_die(6))
+ {
+ lvl.layout = LAYOUT_CAVE_SHRINE;
+ }
+ else
+ {
+ lvl.layout = LAYOUT_CLASSIC_CAVE;
+ }
+ if ((theme_roll < 50) || (depth < 10))
+ {
+ lvl.theme = THEME_NORMAL; /* no restrictions */
+ }
+ else if (theme_roll < 60)
+ {
+ lvl.theme = THEME_UNDEAD;
+ }
+ else if (theme_roll < 80)
+ {
+ lvl.theme = THEME_DRAGONS;
+ }
+ else if (theme_roll < 90)
+ {
+ lvl.theme = THEME_DEMONS;
+ }
+ switch (lvl.layout)
+ {
+ case LAYOUT_CAVE_SHRINE:
+ build_level_shrine();
+ break;
+ case LAYOUT_CAVE_INTRUSIONS:
+ build_level_intrusions();
+ break;
+ case LAYOUT_DUNGEONBASH:
+ build_level_dungeonbash();
+ break;
+ case LAYOUT_CLASSIC_CAVE:
+ build_level_cave();
+ break;
+ }
+}
+
+/*! \brief Build a dungeonbash-style rooms-and-corridors level */
+void build_level_dungeonbash()
+{
+ int chy;
+ int chx;
+ /* We know we're going to use all nine chunks, so create them
+ * immediately. */
+ initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ /* One room per chunk. */
+ for (chy = 0; chy < lvl.chunks_high; ++chy)
+ {
+ for (chx = 0; chx < lvl.chunks_wide; ++ chx)
+ {
+ /* Smallest allowed room has a 2x2 interior. */
+ Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) };
+ /* Each dimension has a 1-in-3 chance to get another boost */
+ if (!zero_die(3))
+ {
+ room_size.y += zero_die(5);
+ }
+ if (!zero_die(3))
+ {
+ room_size.x += zero_die(5);
+ }
+ }
+ }
+}
+
+/*! \brief Excavation function for use with random walks */
+int excavation_write(Coord c, void const *data)
+{
+ int const *data_as_ints = (int const *) data;
+ int const *overwrite = data_as_ints + 2;
+ int newterr = data_as_ints[1];
+ int j;
+ if (lvl.flags_at(c) & MAPFLAG_HARDWALL)
+ {
+ /* Don't bite into hardened walls, but don't waste a step on
+ * them either. */
+ return 2;
+ }
+ for (j = 0; j < data_as_ints[0]; ++j)
+ {
+ if (lvl.terrain_at(c) == overwrite[j])
+ {
+ lvl.set_terrain_at(c, (Terrain) newterr);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*! \brief "Intrusion" function for use with random walks */
+int intrusion_write(Coord c, void const *data)
+{
+ Terrain const *tptr = (Terrain const *) data;
+ if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL))
+ {
+ return 0;
+ }
+ /* Don't intrude too closely on the centre of the level */
+ if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4)))
+ {
+ return 2;
+ }
+ if ((c.x > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.x < ((GUIDE_EDGE_SIZE / 2) - 4)))
+ {
+ return 2;
+ }
+ if (tptr)
+ {
+ lvl.set_terrain_at(c, *tptr);
+ }
+ lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ return 1;
+}
+
+/*! \brief Do a random walk laying down an exclusion area */
+void place_random_intrusion(Terrain new_wall)
+{
+ Coord c;
+ int intrusion_size = inc_flat(27, 54);
+ do
+ {
+ c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+ c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+ } while (lvl.flags_at(c) & MAPFLAG_HARDWALL);
+ run_random_walk(c, intrusion_write, &new_wall, intrusion_size);
+}
+
+/*! \brief Get a valid square to generate a monster on */
+Pass_fail get_levgen_mon_floor(Coord *c)
+{
+ int cell_try;
+ Coord t;
+ for (cell_try = 0; cell_try < 200; cell_try++)
+ {
+ t = lvl.random_point(1);
+ if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON))
+ {
+ t = Nowhere;
+ continue;
+ }
+ break;
+ }
+ if (t == Nowhere)
+ {
+ return You_fail;
+ }
+ *c = t;
+ return You_pass;
+}
+
+/*! \brief Populate the level! */
+void populate_level(void)
+{
+ int i;
+ Pass_fail pf;
+ Coord c;
+ int ic;
+ /* Generate some random monsters */
+ for (i = 0; i < 10; i++)
+ {
+ pf = get_levgen_mon_floor(&c);
+ if (pf == You_fail)
+ {
+ continue;
+ }
+ create_mon(NO_PMON, c);
+ }
+ ic = 3 + depth;
+ if (ic > 40)
+ {
+ /* Never create more than 40 items. */
+ ic = 40;
+ }
+ /* Generate some random treasure */
+ for (i = 0; i < ic; i++)
+ {
+ pf = get_levgen_mon_floor(&c);
+ if (pf == You_fail)
+ {
+ continue;
+ }
+ create_obj(NO_POBJ, 1, 0, c);
+ }
+}
+
+/*! \brief Inject the player into the level based on where they arrived from */
+void inject_player(Level_key from)
+{
+ /* For now we are allowing only one linkage between levels */
+ Coord c;
+ std::vector<Stair_detail> stair_list;
+ int i = lvl.find_stairs(from, &stair_list);
+ if (i != 0)
+ {
+ c = stair_list[0].pos;
+ }
+ else
+ {
+ fprintf(stderr, "Couldn't find any stairs!\n");
+ abort();
+ }
+ u.pos = c;
+ reloc_player(u.pos);
+}
+
+/*! \brief Look around the player */
+void look_around_you(void)
+{
+ compute_fov();
+ notify_fov();
+}
+
+/*! \brief Description of terrain types */
+Terrain_props terrain_props[NUM_TERRAINS] =
+{
+ { "cave wall", '#', "â–ˆ", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "masonry wall", '#', "â–ˆ", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "amethyst wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "iron wall", '#', "â–ˆ", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "skin wall", '#', "â–ˆ", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "bone wall", '#', "â–ˆ", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
+ { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile },
+ { "floor", '.', "·", Gcol_l_grey, TFLAG_floor },
+ { "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor },
+ { "iron floor", '.', "·", Gcol_iron, TFLAG_floor },
+ { "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor },
+ { "bone floor", '.', "·", Gcol_white, TFLAG_floor },
+ { "altar", '_', "_", Gcol_l_grey, 0 },
+ { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend },
+ { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend },
+ { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard },
+ { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard },
+};
+
+/*! \brief self-explanatory */
+bool terrain_is_opaque(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_opaque;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_beings(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_block_beings;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_missiles(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_block_missile;
+}
+
+/*! \brief Read a Chunk from a FILE.
+ *
+ * Yes, I know "throw(errno)" is tacky and stupid, but it achieves the
+ * desired result: the save is obviously garbage, so there's no point
+ * in finishing loading it.
+ */
+void deserialize_chunk(FILE *fp, Chunk *c)
+{
+ static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+ int i;
+ int j;
+ wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ for (i = 0; i < CHUNK_EDGE; ++i)
+ {
+ for (j = 0; j < CHUNK_EDGE; ++j)
+ {
+ c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
+ c->flags[i][j] = ntohl(deserializable_flags[i][j]);
+ c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
+ /* objs and mons will get accurately set once we've loaded the
+ * objs and mons. */
+ c->objs[i][j] = NO_OBJ;
+ c->mons[i][j] = NO_MON;
+ }
+ }
+}
+
+/*! \brief Write a Chunk out to a FILE. */
+void serialize_chunk(FILE *fp, Chunk const *c)
+{
+ static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+ static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+ int i;
+ int j;
+ for (i = 0; i < CHUNK_EDGE; ++i)
+ {
+ for (j = 0; j < CHUNK_EDGE; ++j)
+ {
+ serializable_terrain[i][j] = htonl(c->terrain[i][j]);
+ serializable_flags[i][j] = htonl(c->flags[i][j]);
+ serializable_regions[i][j] = htonl(c->region_number[i][j]);
+ }
+ }
+ fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+ fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+}
+
+/*! \brief Serialize a Level */
+void serialize_level(FILE *fp, Level const *l)
+{
+ uint32_t tmp;
+ uint32_t tmp_pair[2];
+ uint16_t tmp_shorts[2];
+ int i;
+ int j;
+ tmp_shorts[0] = htons(l->self.dungeon);
+ tmp_shorts[1] = htons(l->self.depth);
+ fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+ tmp_pair[0] = htonl(l->origin_off.y);
+ tmp_pair[1] = htonl(l->origin_off.x);
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ tmp = htonl(l->dead_space);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->theme);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->layout);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->chunks_high);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ tmp = htonl(l->chunks_wide);
+ fwrite(&tmp, sizeof tmp, 1, fp);
+ for (i = 0; i < l->chunks_high; ++i)
+ {
+ tmp_pair[0] = htonl(i);
+ for (j = 0; j < l->chunks_wide; ++j)
+ {
+ if (l->chunks[i][j])
+ {
+ tmp_pair[1] = htonl(j);
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ serialize_chunk(fp, l->chunks[i][j]);
+ }
+ }
+ }
+ tmp_pair[0] = tmp_pair[1] = ~0u;
+ fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+}
+
+/*! \brief Deserialize a Level
+ *
+ * \todo Throw an exception if the level is malformed e.g. OOB chunk indices.
+ */
+void deserialize_level(FILE *fp, Level *l)
+{
+ uint32_t tmp;
+ uint32_t tmp_pair[2];
+ uint16_t tmp_shorts[2];
+ uint32_t i;
+ uint32_t j;
+ wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+ l->self.dungeon = ntohs(tmp_shorts[0]);
+ l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
+ wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
+ l->origin_off.y = (int) ntohl(tmp_pair[0]);
+ l->origin_off.x = (int) ntohl(tmp_pair[1]);
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->dead_space = Terrain(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->theme = level_theme(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->layout = level_layout(ntohl(tmp));
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->chunks_high = ntohl(tmp);
+ wrapped_fread(&tmp, sizeof tmp, 1, fp);
+ l->chunks_wide = ntohl(tmp);
+ initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
+ do
+ {
+ wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
+ i = ntohl(tmp_pair[0]);
+ j = ntohl(tmp_pair[1]);
+ if (i == ~0u)
+ {
+ break;
+ }
+ l->chunks[i][j] = new Chunk(l->dead_space);
+ deserialize_chunk(fp, l->chunks[i][j]);
+ }
+ while (1);
+}
+
+uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
+{
+ clockwise_steps &= 0x3;
+ return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf;
+}
+
+void level_cleanup(void)
+{
+ int i;
+ for (i = 0; i < MONSTERS_IN_PLAY; ++i)
+ {
+ monsters[i].used = false;
+ }
+ for (i = 0; i < OBJECTS_IN_PLAY; ++i)
+ {
+ objects[i].used = false;
+ objects[i].with_you = false;
+ }
+ drop_all_chunks(&lvl);
+}
+
+/* map.cc */
+// vim:cindent:ts=8:sw=4:expandtab
* \brief Map-related header
*/
-/* Copyright 2005-2013 Martin Read
+/* Copyright 2005-2014 Martin Read
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#ifndef MAP_HH
#define MAP_HH
-#ifndef VICTRIX_ABYSSI_HH
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#ifndef OBJECTS_HH
+#include "objects.hh"
#endif
#include <deque>
/* XXX enum terrain_num */
enum Terrain {
- WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, LAVA, WATER
+ WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, PORTAL_LANDING, PORTAL_ONWARD, CHASM, LAVA, WATER
};
#define MAX_TERRAIN (WATER)
#define NUM_TERRAINS (1 + MAX_TERRAIN)
#define MAPFLAG_EXPLORED 0x00000001
#define MAPFLAG_HARDWALL 0x00000002
-enum level_theme {
+enum Level_theme {
THEME_NORMAL = 0, THEME_DRAGONS, THEME_DEMONS, THEME_UNDEAD
};
-enum level_layout
+enum Level_layout
{
LAYOUT_CLASSIC_CAVE = 0,
LAYOUT_CAVE_INTRUSIONS, /* the cave has hardened intrusions */
LAYOUT_CAVE_SHRINE, /* the cave contains a shrine */
- LAYOUT_DUNGEONBASH /* maybe not for this version: dungeonbash-style room grid */
+ LAYOUT_CAVE_PITS, /* "pits"-style cave. */
+ LAYOUT_DUNGEONBASH, /* dungeonbash-style room grid */
+ LAYOUT_CLAUSTROPHOBIA
};
#define NO_REGION 0xffffffffu
//! Array of terrain properties
extern Terrain_props terrain_props[NUM_TERRAINS];
+struct Decal_desc
+{
+ char const *name;
+ char const *description;
+ Gamecolour colour;
+};
+
+extern Decal_desc decal_props[NUM_DECALS];
+
#define NO_STAIRS (-1)
#define TFLAG_opaque 0x00000001u
#define TFLAG_block_beings 0x00000002u
#define TFLAG_block_ether 0x00000004u
#define TFLAG_block_missile 0x00000008u
-#define TFLAG_portal 0x00000010u
-#define TFLAG_ascend 0x00000020u
-#define TFLAG_descend 0x00000040u
-#define TFLAG_floor 0x00000080u
+#define TFLAG_block_items 0x00000010u
+#define TFLAG_portal 0x00000020u
+#define TFLAG_ascend 0x00000040u
+#define TFLAG_descend 0x00000080u
+#define TFLAG_floor 0x00000100u
+#define TFLAG_wall 0x00000200u
#define TFLAG_fire_hazard 0x00010000u
#define TFLAG_fall_hazard 0x00020000u
#define TFLAG_drown_hazard 0x00040000u
class Chunk
{
public:
- int objs[CHUNK_EDGE][CHUNK_EDGE];
- int mons[CHUNK_EDGE][CHUNK_EDGE];
+ Obj_handle objs[CHUNK_EDGE][CHUNK_EDGE];
+ Mon_handle mons[CHUNK_EDGE][CHUNK_EDGE];
Terrain terrain[CHUNK_EDGE][CHUNK_EDGE];
uint32_t flags[CHUNK_EDGE][CHUNK_EDGE];
uint32_t region_number[CHUNK_EDGE][CHUNK_EDGE];
+ Decal_tag decals[CHUNK_EDGE][CHUNK_EDGE];
Terrain terrain_at(Coord c) const { return terrain[c.y][c.x]; }
uint32_t flags_at(Coord c) const { return flags[c.y][c.x]; }
uint32_t region_at(Coord c) const { return region_number[c.y][c.x]; }
- uint32_t obj_at(Coord c) const { return objs[c.y][c.x]; }
- uint32_t mon_at(Coord c) const { return mons[c.y][c.x]; }
+ Obj_handle obj_at(Coord c) const { return objs[c.y][c.x]; }
+ Mon_handle mon_at(Coord c) const { return mons[c.y][c.x]; }
+ Decal_tag decal_at(Coord c) const { return decals[c.y][c.x]; }
void set_terrain_at(Coord c, Terrain t) { terrain[c.y][c.x] = t; }
void overwrite_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] = f; }
void set_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] |= f; }
void clear_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] &= ~f; }
void set_region_at(Coord c, uint32_t r) { region_number[c.y][c.x] = r; }
- void set_obj_at(Coord c, int o) { objs[c.y][c.x] = o; }
- void set_mon_at(Coord c, int m) { mons[c.y][c.x] = m; }
+ void set_obj_at(Coord c, Obj_handle o) { objs[c.y][c.x] = o; }
+ void set_mon_at(Coord c, Mon_handle m) { mons[c.y][c.x] = m; }
+ void set_decal_at(Coord c, Decal_tag d) { decals[c.y][c.x] = d; }
Chunk(Terrain t = WALL);
};
class Level
{
public:
+ Level_key self;
Chunk ***chunks; //!< 16x16 subsections of the level, not necessarily dense
Offset origin_off; //!< Don't force a map size change to recalculate all Coords
- int chunks_high; //!< Chunkwise size of level in the y-direction
- int chunks_wide; //!< Chunkwise size of level in the x-direction
+ uint32_t chunks_high; //!< Chunkwise size of level in the y-direction
+ uint32_t chunks_wide; //!< Chunkwise size of level in the x-direction
Terrain dead_space; //!< Terrain to fill new chunks with and return for Coords in unpopulated chunks
- level_theme theme; //!< Will affect monster and maybe item generation
- level_layout layout; //!< Determines generation algorithm
- Level_key self;
+ Level_theme theme; //!< Will affect monster and maybe item generation
+ Level_layout layout; //!< Determines generation algorithm
std::deque<Stair_detail> stairs;
+ void *layout_data; //!< auxiliary data required by layout
+ void *theme_data; //!< auxiliary data required by layout
+ /* Member functions only past this point, please. */
+ Level() :
+ self(No_level), chunks(nullptr), origin_off(Stationary),
+ chunks_high(0), chunks_wide(0), dead_space(WALL), layout_data(nullptr),
+ theme_data(nullptr)
+ {
+ }
Terrain terrain_at(Coord c) const
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- return ch ? ch->terrain_at(c2) : dead_space;
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->terrain_at(c2) : dead_space;
+ }
+ else
+ {
+ return dead_space;
+ }
}
void set_terrain_at(Coord c, Terrain t)
{
- /* Algorithms that want to potentially stretch the level are
- * responsible for telling the level to stretch. */
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- if (ch)
+ if (in_bounds(c))
{
- ch->set_terrain_at(c2, t);
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_terrain_at(c2, t);
+ }
}
}
uint32_t flags_at(Coord c) const
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- return ch ? ch->flags_at(c2) : 0;
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->flags_at(c2) : 0u;
+ }
+ else
+ {
+ return 0u;
+ }
}
void set_flags_at(Coord c, uint32_t to_set)
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- if (ch)
+ if (in_bounds(c))
{
- ch->set_flags_at(c2, to_set);
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_flags_at(c2, to_set);
+ }
}
}
void clear_flags_at(Coord c, uint32_t to_clear)
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- if (ch)
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->clear_flags_at(c2, to_clear);
+ }
+ }
+ }
+ Mon_handle mon_at(Coord c) const
+ {
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->mon_at(c2) : NO_MON;
+ }
+ else
+ {
+ return NO_MON;
+ }
+ }
+ void set_mon_at(Coord c, Mon_handle mon)
+ {
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_mon_at(c2, mon);
+ }
+ }
+ }
+ Obj_handle obj_at(Coord c) const
+ {
+ if (in_bounds(c))
+ {
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->obj_at(c2) : NO_OBJ;
+ }
+ else
+ {
+ return NO_OBJ;
+ }
+ }
+ void set_obj_at(Coord c, Obj_handle obj)
+ {
+ if (in_bounds(c))
{
- ch->clear_flags_at(c2, to_clear);
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_obj_at(c2, obj);
+ }
}
}
- int mon_at(Coord c) const
+ Decal_tag decal_at(Coord c) const
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- return ch ? ch->mon_at(c2) : NO_MON;
+ if (in_bounds(c))
+ {
+
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->decal_at(c2) : NO_DECAL;
+ }
+ else
+ {
+ return NO_DECAL;
+ }
}
- void set_mon_at(Coord c, int mon)
+ void set_decal_at(Coord c, Decal_tag t)
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- if (ch)
+ if (in_bounds(c))
{
- ch->set_mon_at(c2, mon);
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_decal_at(c2, t);
+ }
}
}
- int obj_at(Coord c) const
+ uint32_t region_at(Coord c) const
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- return ch ? ch->obj_at(c2) : NO_OBJ;
+ if (in_bounds(c))
+ {
+
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ return ch ? ch->region_at(c2) : NO_REGION;
+ }
+ else
+ {
+ return NO_REGION;
+ }
}
- void set_obj_at(Coord c, int obj)
+ void set_region_at(Coord c, uint32_t r)
{
- Coord rc = c + origin_off;
- Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
- Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
- if (ch)
+ if (in_bounds(c))
{
- ch->set_obj_at(c2, obj);
+ Coord rc = c + origin_off;
+ Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+ Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+ if (ch)
+ {
+ ch->set_region_at(c2, r);
+ }
}
}
Coord random_point(int margin) const;
bool in_bounds(Coord c) const
{
c += origin_off;
- return !((c.y < 0) || (c.x < 0) || (c.y >= (chunks_high << CHUNK_SHIFT)) ||
- (c.x >= (chunks_wide << CHUNK_SHIFT)));
+ return !((c.y < 0) || (c.x < 0) || (c.y >= int32_t(chunks_high << CHUNK_SHIFT)) ||
+ (c.x >= int32_t(chunks_wide << CHUNK_SHIFT)));
}
int min_y() const
{
extern Level lvl;
extern int depth;
-extern enum level_theme current_theme;
-extern enum level_layout current_layout;
void leave_level(void);
void make_new_level(void);
void build_level(void);
void populate_level(void);
-void inject_player(Level_key from);
+void inject_player(Level *l, Level_key from);
void look_around_you(void);
+bool terrain_is_wall(Terrain terr);
+bool terrain_is_floor(Terrain terr);
bool terrain_is_opaque(Terrain terr);
+bool terrain_is_hot(Terrain terr);
+bool terrain_gapes(Terrain terr);
+bool terrain_drowns(Terrain terr);
bool terrain_blocks_beings(Terrain terr);
+bool terrain_blocks_items(Terrain terr);
bool terrain_blocks_missiles(Terrain terr);
void serialize_level(FILE *fp, Level const *l);
void deserialize_level(FILE *fp, Level *l);
void level_cleanup(void);
#endif
-/* map.h */
+/* map.hh */
// vim:cindent:expandtab
#define GUIDE_EDGE_CHUNKS 3
#define GUIDE_EDGE_SIZE (GUIDE_EDGE_CHUNKS << CHUNK_SHIFT)
-extern int depth;
-extern enum level_theme current_theme;
-extern enum level_layout current_layout;
-
-void build_level_shrine(void);
-void build_level_intrusions(void);
-void build_level_cave(void);
-void build_level_dungeonbash(void);
-Pass_fail get_levgen_mon_floor(Coord *c);
-int excavation_write(Coord c, void const *data);
-int intrusion_write(Coord c, void const *data);
+void build_level_shrine(Level *l);
+void build_level_intrusions(Level *l);
+void build_level_cave(Level *l);
+void build_level_pits(Level *l, Terrain t = CHASM);
+void build_level_dungeonbash(Level *l);
+void build_level_claustrophobia(Level *l);
+Pass_fail get_levgen_mon_floor(Level *l, Coord *c);
+int excavation_write(Level *l, Coord c, void const *data);
+int intrusion_write(Level *l, Coord c, void const *data);
void initialize_chunks(Level *l, int height, int width, bool dense);
void drop_all_chunks(Level *l);
-void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false);
-void place_random_intrusion(Terrain new_wall = WALL);
-void place_cave_stairs(void);
+void place_shrine(Level *l, Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false);
+void place_random_intrusion(Level *l, Terrain new_wall = WALL);
+void place_cave_stairs(Level *l);
#define ROOMCONN_NORTH 0x00000001u
#define ROOMCONN_EAST 0x00000002u
#define ROOMCONN_WEST 0x00000008u
uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps);
-typedef int (*rwalk_mod_funcptr)(Coord c, void const *data);
-Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+typedef int (*rwalk_mod_funcptr)(Level *l, Coord c, void const *data);
+Coord run_random_walk(Level *l, Coord oc, rwalk_mod_funcptr func,
void const *priv_ptr, int cells);
-Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk_unbounded(Level *l, Coord oc, rwalk_mod_funcptr func,
void const *priv_ptr, int cells);
+struct Obumb_levgen_excep
+{
+ char const *str;
+ Obumb_levgen_excep() = delete;
+ Obumb_levgen_excep(char const *s) : str(s) { }
+};
+
#endif
/* mapgen.hh */
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/* TODO: Convert missile AI to a new-style AI function. */
#define MON2_CC
#include "victrix-abyssi.hh"
#include "sorcery.hh"
static void build_ai_cells(struct ai_cell *cells, Coord loc);
static Comparison ai_cell_compare(struct ai_cell *cell, Offset delta);
static void get_dodger_prefs(Coord loc, Offset delta, Coord *pref_pos);
-static void get_chase_prefs(int mon, Coord *pref_pos);
+static void get_chase_prefs(Mon_handle mon, Coord *pref_pos);
/* get_drunk_prefs()
*
* details.
*/
-static void get_chase_prefs(int mon, Coord *pref_pos)
+static void get_chase_prefs(Mon_handle mon, Coord *pref_pos)
{
- Mon const *mptr = monsters + mon;
+ Mon const *mptr = mon_snapv(mon);
Offset delta = mptr->ai_lastpos.delta(mptr->pos);
Offset step = mysign(delta);
Offset absdelta = myabs(delta);
int i;
int highest_score = AI_REALLY_HATE;
int tryct;
- Mon *mptr = monsters + lvl.mon_at(loc);
+ Mon *mptr = mon_snapv(lvl.mon_at(loc));
*pref_pos = loc;
build_ai_cells(ai_cells, loc);
for (i = 0; i < 8; i++)
Offset absdelta = myabs(delta);
int highest_score = AI_REALLY_HATE;
int tryct;
- Mon *mptr = monsters + lvl.mon_at(loc);
+ Mon *mptr = mon_snapv(lvl.mon_at(loc));
*pref_pos = loc;
build_ai_cells(ai_cells, loc);
/* Build the local dx/dy arrays. */
Offset absdelta = myabs(delta);
Coord c;
Offset step = mysign(delta);
- Mon *mptr = monsters + lvl.mon_at(*pc);
+ Mon *mptr = mon_snapv(lvl.mon_at(*pc));
switch (selection_mode)
{
case AI_charger:
break;
case AI_chaser:
/* "chase" AI i.e. pursue your last known position. */
- get_chase_prefs(mptr - monsters, ai_pos);
+ get_chase_prefs(mptr->self, ai_pos);
c = ai_pos[0];
break;
}
*pc = c;
}
-void mon_acts(int mon)
+void mon_acts(Mon_handle mon)
{
Mon *mptr;
Offset delta;
bool meleerange;
bool oncardinal;
bool special_used = false;
- mptr = monsters + mon;
+ mptr = mon_snapv(mon);
/* dy,dx == direction monster must go to reach you. */
c = mptr->pos;
delta = u.pos.delta(c);
if (delta.len_cheb() == 0)
{
debug_misplaced_monster();
- mptr->used = false;
+ mptr->flags &= ~MF_USED;
lvl.set_mon_at(c, NO_MON);
return;
}
if (lvl.mon_at(c) != mon)
{
debug_misplaced_monster();
- mptr->used = false;
+ mptr->flags &= ~MF_USED;
if (lvl.mon_at(c) != NO_MON)
{
- monsters[lvl.mon_at(c)].used = false;
+ monsters[lvl.mon_at(c)].flags &= ~MF_USED;
lvl.set_mon_at(c, NO_MON);
}
return;
/* Adjacent! Attack you. Demons have a 1 in 10 chance of attempting to
* summon another demon instead of attacking you, if that individual
* demon has not summoned in the last 100 ticks. */
- if ((mptr->mon_id == PM_DEMON) && (mptr->next_summon < game_tick) &&
+ if ((mptr->pm_ref == PM_DEMON) && (mptr->next_summon < game_tick) &&
!zero_die(10))
{
summon_demon_near(c);
mptr->next_summon = game_tick + 100;
special_used = true;
}
- else if (pmon_is_magician(mptr->mon_id))
+ else if (pmon_is_magician(mptr->pm_ref))
{
special_used = mon_use_sorcery(mon);
}
else if (mon_visible(mon))
{
/* In sight. */
- if (pmon_is_magician(mptr->mon_id))
+ if (pmon_is_magician(mptr->pm_ref))
{
/* Two-thirds of the time, try to use sorcery. */
if (zero_die(6) < 4)
* as if an archer. */
select_space(&c, delta, AI_archer);
}
- else if (pmon_is_archer(mptr->mon_id))
+ else if (pmon_is_archer(mptr->pm_ref))
{
if (oncardinal && (zero_die(6) < 3))
{
}
select_space(&c, delta, AI_archer);
}
- else if (pmon_is_smart(mptr->mon_id))
+ else if (pmon_is_smart(mptr->pm_ref))
{
select_space(&c, delta, AI_dodger);
}
{
/* Out of LOS, but awake. Stupid monsters move "drunkenly"; smart
* monsters (may) seek you out. */
- if (pmon_is_magician(mptr->mon_id))
+ if (pmon_is_magician(mptr->pm_ref))
{
/* Magicians may have spells that are used when
* you are out of sight. For example, some magicians
{
return;
}
- if (pmon_is_smart(mptr->mon_id))
+ if (pmon_is_smart(mptr->pm_ref))
{
select_space(&c, delta, AI_seeker);
}
- else if (pmon_is_stupid(mptr->mon_id) || (mptr->ai_lastpos == Nowhere))
+ else if (pmon_is_stupid(mptr->pm_ref) || (mptr->ai_lastpos == Nowhere))
{
select_space(&c, delta, AI_drunk);
}
--- /dev/null
+/*! \file mon3.cc
+ * \brief Death drop tables and similar things
+ */
+
+/* Copyright © 2014 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 MON3_CC
+#include "core.hh"
+#include "monsters.hh"
+#include "objects.hh"
+
+static void goblin_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_near(PO_DAGGER, 1, c);
+ }
+}
+
+static void thug_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_near(PO_MACE, 1, c);
+ }
+ else if (!zero_die(3))
+ {
+ create_obj_near(PO_LEATHER_ARMOUR, 1, c);
+ }
+}
+
+static void hunter_death_drop(Coord c)
+{
+ if (!zero_die(6))
+ {
+ create_obj_near(PO_BOW, 1, c);
+ }
+}
+
+static void duellist_death_drop(Coord c)
+{
+ if (!zero_die(6))
+ {
+ create_obj_near(PO_LONG_SWORD, 1, c);
+ }
+}
+
+static void wizard_death_drop(Coord c)
+{
+ if (!zero_die(4))
+ {
+ create_obj_class_near(POCLASS_SCROLL, 1, false, c);
+ }
+ else if (!zero_die(3))
+ {
+ create_obj_class_near(POCLASS_POTION, 1, false, c);
+ }
+}
+
+static void warlord_death_drop(Coord c)
+{
+ if (!zero_die(3))
+ {
+ create_obj_near(PO_RUNESWORD, 1, c);
+ }
+}
+
+static void demon_death_drop(Coord c)
+{
+ if (!zero_die(100))
+ {
+ create_obj_near(PO_DEVIL_SPLEEN, 1, c);
+ }
+}
+
+Death_drop_entry death_drops[] =
+{
+ { PM_GOBLIN, goblin_death_drop },
+ { PM_THUG, thug_death_drop },
+ { PM_GOON, thug_death_drop },
+ { PM_HUNTER, hunter_death_drop },
+ { PM_DUELLIST, duellist_death_drop },
+ { PM_WIZARD, wizard_death_drop },
+ { PM_WARLORD, warlord_death_drop },
+ { PM_DEMON, demon_death_drop },
+ { NO_PMON, nullptr }
+};
+
+/* mon3.cc */
+// vim:cindent
#include "monsters.hh"
#include "objects.hh"
-Mon monsters[100];
-static int reject_mon(int pm);
+const Mon_handle NO_MON = 0u;
+
+std::map<Mon_handle, Mon> monsters;
+static bool reject_mon(int pm);
/*! \brief Summon some monsters
*
Offset delta;
Coord testpos;
int tryct;
- int mon;
+ Mon_handle mon;
int created = 0;
int pmon;
for (i = 0; i < how_many; i++)
return pm;
}
-int create_mon(int pm_idx, Coord c)
+Mon_handle first_free_mon_handle = 1u;
+static Mon_handle get_first_free_mon(void)
+{
+ if (first_free_mon_handle == NO_MON)
+ {
+ return NO_MON;
+ }
+ return first_free_mon_handle++;
+}
+
+Mon_handle create_mon(int pm_idx, Coord c)
{
- int mon;
+ Mon_handle mon;
if (lvl.mon_at(c) != NO_MON)
{
debug_create_mon_occupied(c);
return NO_MON;
}
}
- for (mon = 0; mon < 100; mon++)
- {
- if (!monsters[mon].used)
+ mon = get_first_free_mon();
+ if (mon != NO_MON)
+ {
+ Mon m;
+ m.self = mon;
+ m.pm_ref = pm_idx;
+ m.flags = MF_USED | MF_ALIVE;
+ m.pos = c;
+ m.ai_lastpos = Nowhere;
+ m.hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
+ m.hpcur = m.hpmax;
+ m.mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
+ m.defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
+ m.mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
+ if (permons[pm_idx].rdam != NO_ATK)
{
- monsters[mon].mon_id = pm_idx;
- monsters[mon].used = true;
- monsters[mon].pos = c;
- monsters[mon].ai_lastpos = Nowhere;
- 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 != NO_ATK)
- {
- 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 = NO_ATK;
- monsters[mon].rdam = NO_ATK;
- }
- monsters[mon].awake = false;
- lvl.set_mon_at(c, mon);
- if (mon_visible(mon))
- {
- notify_new_mon_at(c, mon);
- }
- return mon;
+ m.rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
+ m.rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
+ }
+ else
+ {
+ m.rtohit = NO_ATK;
+ m.rdam = NO_ATK;
}
+ monsters[mon] = m;
+ lvl.set_mon_at(c, mon);
+ if (mon_visible(mon))
+ {
+ notify_new_mon_at(c, mon);
+ }
+ return mon;
}
return NO_MON;
}
-void death_drop(int mon)
+/*! \brief Handle a monster's death drop. */
+void death_drop(Mon_handle mon)
{
- Mon *mptr = monsters + mon;
- int pm = mptr->mon_id;
- Coord c = mptr->pos;
- Offset delta;
- int tryct = 0;
- while (((lvl.obj_at(c) != NO_OBJ) || (lvl.terrain_at(c) != FLOOR)) &&
- (tryct < 100))
+ Mon const *mptr = mon_snap(mon);
+ for (int i = 0; death_drops[i].func != nullptr; ++i)
{
- delta = random_step();
- tryct++;
- c += delta;
- }
- if (tryct >= 100)
- {
- return;
- }
- switch (pm)
- {
- case PM_GOBLIN:
- if (!zero_die(4))
+ if (death_drops[i].pm_ref == mptr->pm_ref)
{
- create_obj(PO_DAGGER, 1, 0, c);
+ death_drops[i].func(mptr->pos);
}
- break;
- case PM_THUG:
- case PM_GOON:
- if (!zero_die(4))
- {
- create_obj(PO_MACE, 1, 0, c);
- }
- else if (!zero_die(3))
- {
- create_obj(PO_LEATHER_ARMOUR, 1, 0, c);
- }
- break;
- case PM_HUNTER:
- if (!zero_die(6))
- {
- create_obj(PO_BOW, 1, 0, c);
- }
- break;
- case PM_DUELLIST:
- if (!zero_die(6))
- {
- create_obj(PO_LONG_SWORD, 1, 0, c);
- }
- break;
- case PM_WIZARD:
- if (!zero_die(4))
- {
- create_obj_class(POCLASS_SCROLL, 1, 0, c);
- }
- else if (!zero_die(3))
- {
- create_obj_class(POCLASS_POTION, 1, 0, c);
- }
- break;
- case PM_WARLORD:
- if (!zero_die(3))
- {
- create_obj(PO_RUNESWORD, 1, 0, c);
- }
- break;
- case PM_DEMON:
- if (!zero_die(100))
- {
- create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
- }
- break;
- case PM_DEFILER:
- if (!zero_die(50))
- {
- create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
- }
- break;
- default:
- break;
}
}
/* Let's *not* stuff all the wall types into a switch, eh? */
return false;
}
- /* Keep the switch, so that we can maintain a convenient distinction
- * between floor hazards and volumetric hazards. */
- switch (terr)
+ if (terrain_is_hot(terr) && !can_fly() && !resists(DT_FIRE))
{
- case LAVA:
- if (!can_fly() && !resists(DT_FIRE))
- {
- return false;
- }
- break;
- case WATER:
- if (!can_fly() && !resists(DT_DROWNING))
- {
- return false;
- }
- default:
- break;
+ return false;
+ }
+ if (terrain_drowns(terr) && !can_fly() && !resists(DT_DROWNING))
+ {
+ return false;
+ }
+ if (terrain_gapes(terr) && !can_fly())
+ {
+ return false;
}
return true;
}
-void heal_mon(int mon, int amount, int cansee)
+void heal_mon(Mon_handle mon, int amount, int cansee)
{
if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
{
}
}
-void unplace_mon(int mon)
+void unplace_mon(Mon_handle mon)
{
lvl.set_mon_at(monsters[mon].pos, NO_MON);
- monsters[mon].used = false;
+ monsters[mon].flags &= ~MF_USED;
+}
+
+void apply_liquid(int pm, Coord c)
+{
+ Decal_tag decal = pmon_fluid_decal(pm);
+ if (pm != NO_DECAL)
+ {
+ lvl.set_decal_at(c, decal);
+ }
}
/*! \brief Handle the death of a monster
*
* \todo Support special effects on monster death
*/
-void kill_mon(int mon, bool by_you)
+void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse)
{
+ Mon *mptr = mon_snapv(mon);
+ Terrain t = lvl.terrain_at(mptr->pos);
+ int pm = mptr->pm_ref;
+ bool detonate =
+ (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse));
+ if (!terrain_blocks_items(t))
+ {
+ apply_liquid(pm, mptr->pos);
+ }
+ if (detonate)
+ {
+ detonate_mon(mon);
+ }
+ else if (pmon_leaves_corpse(pm))
+ {
+ create_corpse(pm, mptr->pos);
+ }
death_drop(mon); // phat lewt!
- monsters[mon].hpcur = -1; // Set HP to -1 so nothing will think it's alive
+ mptr->flags &= ~MF_ALIVE;
+ monsters[mon].hpcur = -1;
unplace_mon(mon); // cleanup
if (by_you)
{
notify_player_killed_mon(mon);
- gain_experience(permons[monsters[mon].mon_id].exp);
+ gain_experience(permons[monsters[mon].pm_ref].exp);
}
- else if (mon_visible(mon))
+ else if (mon_visible(mon) && !detonate)
{
notify_mon_dies(mon);
}
}
-void damage_mon(int mon, int amount, bool by_you)
+void damage_mon(Mon_handle mon, int amount, bool by_you)
{
Mon *mptr;
- mptr = monsters + mon;
+ mptr = mon_snapv(mon);
if (amount >= mptr->hpcur)
{
kill_mon(mon, by_you);
}
}
-int reject_mon(int pm)
+bool reject_mon(int pm)
{
if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
{
- return 1;
+ return true;
}
- return 0;
+ return false;
}
-Pass_fail teleport_mon_to_you(int mon)
+Pass_fail teleport_mon_to_you(Mon_handle mon)
{
int tryct;
Offset delta;
Coord c;
int success = 0;
- Mon *mptr = monsters + mon;
+ Mon *mptr = mon_snapv(mon);
Coord oldpos = mptr->pos;
for (tryct = 0; tryct < 40; tryct++)
{
return You_fail;
}
-Pass_fail teleport_mon(int mon)
+Pass_fail teleport_mon(Mon_handle mon)
{
Pass_fail rval = You_fail;
int cell_try;
return rval;
}
-int knockback_mon(int mon, Offset step, bool cansee, bool by_you)
+int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you)
{
/* 0 = blocked, 1 = knocked, 2 = killed */
- Mon *mptr = monsters + mon;
+ Mon *mptr = mon_snapv(mon);
Coord c = mptr->pos + step;
Coord savedpos = mptr->pos;
Terrain terr = lvl.terrain_at(c);
return 1;
}
-void reloc_mon(int mon, Coord newpos)
+void reloc_mon(Mon_handle mon, Coord newpos)
{
- Mon *mptr = monsters + mon;
+ Mon *mptr = mon_snapv(mon);
lvl.set_mon_at(mptr->pos, NO_MON);
notify_new_mon_at(mptr->pos, NO_MON);
mptr->pos = newpos;
notify_new_mon_at(mptr->pos, mon);
}
-void move_mon(int mon, Coord c)
+void move_mon(Mon_handle mon, Coord c)
{
Mon *mptr;
- mptr = monsters + mon;
+ mptr = mon_snapv(mon);
if (!mptr->can_pass(c))
{
debug_mon_invalid_move(mon, c);
void summon_demon_near(Coord c)
{
Coord c2 = c + random_step();
- int mon;
+ Mon_handle mon;
if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
(c2 != u.pos))
{
}
}
-bool mon_visible(int mon)
+void detonate_mon(Mon_handle mon)
+{
+ Mon *mptr = mon_snapv(mon);
+ Coord c;
+ int pm = mptr->pm_ref;
+ Decal_tag decal = pmon_fluid_decal(pm);
+ notify_mon_detonates(mon, decal);
+ if (decal != NO_DECAL)
+ {
+ for (c.y = mptr->pos.y - 1; c.y <= mptr->pos.y + 1; ++c.y)
+ {
+ for (c.x = mptr->pos.x - 1; c.x <= mptr->pos.x + 1; ++c.x)
+ {
+ if (lvl.in_bounds(c))
+ {
+ lvl.set_decal_at(c, decal);
+ }
+ }
+ }
+ }
+ return;
+}
+
+bool mon_visible(Mon_handle mon)
{
Offset delta;
- if (monsters[mon].used == 0)
+ Mon const *mptr = mon_snap(mon);
+ if (!(mptr->flags & MF_USED))
{
return false;
}
- delta = monsters[mon].pos.delta(u.pos);
+ delta = mptr->pos.delta(u.pos);
if (delta.len_cheb() <= MAX_FOV_RADIUS)
{
return (player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x]) & Visflag_central;
}
}
-void update_mon(int mon)
+void update_mon(Mon_handle mon)
{
int cansee;
- if (monsters[mon].hpcur < monsters[mon].hpmax)
+ Mon *mptr = mon_snapv(mon);
+ if (mptr->hpcur < mptr->hpmax)
{
cansee = mon_visible(mon);
- // TODO modify regen handling to use flags/fields instead of switching on mon_id
- switch (monsters[mon].mon_id)
+ // TODO modify regen handling to use flags/fields instead of switching on pm_ref
+ switch (mptr->pm_ref)
{
case PM_TROLL:
if (!(game_tick % 10))
switch (dt)
{
case DT_COLD:
- return pmon_resists_cold(mon_id);
+ return pmon_resists_cold(pm_ref);
case DT_FIRE:
- return pmon_resists_fire(mon_id);
+ return pmon_resists_fire(pm_ref);
case DT_POISON:
- return pmon_resists_poison(mon_id);
+ return pmon_resists_poison(pm_ref);
case DT_NECRO:
- return pmon_resists_necro(mon_id);
+ return pmon_resists_necro(pm_ref);
case DT_ELEC:
- return pmon_resists_elec(mon_id);
+ return pmon_resists_elec(pm_ref);
case DT_KNOCKBACK:
- return pmon_resists_knockback(mon_id);
+ return pmon_resists_knockback(pm_ref);
case DT_DROWNING:
- return pmon_resists_drowning(mon_id);
+ return pmon_resists_drowning(pm_ref);
default:
return false;
}
bool Mon::is_ethereal(void) const
{
- return pmon_is_ethereal(mon_id);
+ return pmon_is_ethereal(pm_ref);
}
bool Mon::can_fly(void) const
{
- return pmon_can_fly(mon_id);
+ return pmon_can_fly(pm_ref);
+}
+
+void asprint_mon_name(char **buf, Mon_handle mon, int article)
+{
+ Mon const *mptr = mon_snap(mon);
+ int i;
+ switch (article)
+ {
+ case 0:
+ i = asprintf(buf, "a%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+ break;
+ case 1:
+ i = asprintf(buf, "the %s", permons[mptr->pm_ref].name);
+ break;
+ case 2:
+ i = asprintf(buf, "A%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+ break;
+ case 3:
+ i = asprintf(buf, "The %s", permons[mptr->pm_ref].name);
+ break;
+ default:
+ i = -1;
+ }
+ if (i == -1)
+ {
+ // TODO wibble
+ }
+ return;
}
-/* monsters.c */
-// vim:cindent
+/* monsters.cc */
+// vim:cindent:expandtab
#endif
/* XXX struct mon */
-#define MONSTERS_IN_PLAY 100
+
+#define MF_AWAKE 0x00000001u // gets turns
+#define MF_ALIVE 0x00000002u // gets displayed
+#define MF_USED 0x00000004u // legacy cruft
+
class Mon {
public:
- int mon_id;
+ Mon_handle self;
+ int pm_ref;
Coord pos;
Coord ai_lastpos;
- bool used;
+ uint32_t flags;
int hpmax; /* Improved by OOD rating at 1:1. */
int hpcur; /* <= 0 is dead. */
int mtohit; /* Improved by OOD rating at 1:3. */
int rdam; /* Improved by OOD rating at 1:5. */
bool awake;
int next_summon;
+ /* member functions only past this point please */
bool resists(Damtyp dt) const;
bool can_pass(Coord c) const;
bool can_fly(void) const;
bool is_ethereal(void) const;
};
-extern Mon monsters[MONSTERS_IN_PLAY];
+#include <map>
+extern std::map<Mon_handle, Mon> monsters;
+extern Mon_handle first_free_mon_handle;
+
+inline Mon *mon_snapv(Mon_handle mon)
+{
+ auto iter = monsters.find(mon);
+ return ((iter == monsters.end()) ? nullptr : &(iter->second));
+}
+
+inline Mon const *mon_snap(Mon_handle mon)
+{
+ auto iter = monsters.find(mon);
+ return ((iter == monsters.end()) ? nullptr : &(iter->second));
+}
+
+struct Death_drop_entry
+{
+ int pm_ref;
+ void (*func)(Coord c);
+};
+
+extern Death_drop_entry death_drops[];
-#define NO_MON (-1)
#define PLAYER_MON (-2)
-/* 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(Coord c);
-extern int create_mon(int pm_idx, Coord c);
-extern int summoning(Coord c, int how_many);
-extern int ood(int power, int ratio);
-extern int get_random_pmon(void);
-extern void damage_mon(int mon, int amount, bool by_you);
-extern bool mon_visible(int mon);
-extern int knockback_mon(int mon, Offset step, bool cansee, bool by_you);
-extern void move_mon(int mon, Coord c);
-extern void reloc_mon(int mon, Coord c);
-extern Pass_fail teleport_mon(int mon); /* Randomly relocate monster. */
-extern Pass_fail teleport_mon_to_you(int mon); /* Relocate monster to your vicinity. */
-extern void heal_mon(int mon, int amount, int cansee);
-extern void kill_mon(int mon, bool by_you);
-extern void unplace_mon(int mon);
+void update_mon(Mon_handle mon);
+void mon_acts(Mon_handle mon);
+void death_drop(Mon_handle mon);
+void asprint_mon_name(char **buf, Mon_handle mon, int article);
+void summon_demon_near(Coord c);
+Mon_handle create_mon(int pm_idx, Coord c);
+int summoning(Coord c, int how_many);
+int ood(int power, int ratio);
+int get_random_pmon(void);
+void damage_mon(Mon_handle mon, int amount, bool by_you);
+void detonate_mon(Mon_handle mon);
+bool mon_visible(Mon_handle mon);
+int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you);
+void move_mon(Mon_handle mon, Coord c);
+void reloc_mon(Mon_handle mon, Coord c);
+Pass_fail teleport_mon(Mon_handle mon); /* Randomly relocate monster. */
+Pass_fail teleport_mon_to_you(Mon_handle mon); /* Relocate monster to your vicinity. */
+void heal_mon(Mon_handle mon, int amount, int cansee);
+void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse = false);
+void unplace_mon(Mon_handle mon);
/* XXX mon2.c data and funcs */
-extern void select_space(int *py, int *px, int dy, int dx, int selection_mode);
+void select_space(Coord *pc, Offset delta, int selection_mode);
#endif
}
}
+void notify_learned_skill(Skill_id skill)
+{
+ print_msg("You learned a new skill: %s\n", skill_props[skill].name);
+ print_msg("%s\n", skill_props[skill].desc);
+}
+
void notify_leadfoot_recovered(void)
{
print_msg("You shed your feet of lead.\n");
print_msg("You feel like you are no longer being helped.\n");
}
-void notify_start_lavawalk(void)
+void notify_start_hotwalk(Terrain t)
{
- print_msg("You walk on the lava.\n");
+ print_msg("You stride fearlessly across the %s.\n", terrain_props[t].name);
}
-void notify_blocked_lava(void)
+void notify_blocked_hot(Terrain t)
{
- print_msg("The fierce heat of the molten rock repels you.\n");
+ print_msg("The fierce heat of the %s repels you.\n", terrain_props[t].name);
}
-void notify_start_waterwalk(void)
+void notify_start_waterwalk(Terrain t)
{
- print_msg("You walk on the water.\n");
+ print_msg("You stride fearlessly across the surface of the %s.\n", terrain_props[t].name);
}
-void notify_blocked_water(void)
+void notify_blocked_water(Terrain t)
{
- print_msg("The idiot who raised you never taught you to swim.\n");
+ print_msg("You never learned to swim.\n");
+}
+
+void notify_portal_underfoot(void)
+{
+ print_msg("There is a stone arch here%s.\n", (lvl.terrain_at(u.pos) == PORTAL_ONWARD) ? ", glowing with arcane power" : "");
+}
+
+void notify_new_obj_at(Coord c, Obj_handle obj)
+{
+ newsym(c);
+ display_update();
}
void notify_obj_at(Coord c)
{
- print_msg("You see here ");
- print_obj_name(lvl.obj_at(c));
- print_msg(".\n");
+ char *s;
+ asprint_obj_name(&s, lvl.obj_at(c));
+ print_msg("You see here %s.\n", s);
+ free(s);
}
void debug_move_oob(Coord c)
print_msg("A mysterious force prevents you climbing the stairs.\n");
}
+void notify_inert_portal(void)
+{
+ print_msg("This side of the portal is completely inert.\n");
+}
+
void notify_wasted_gain(void)
{
print_msg("You feel disappointed.\n");
}
-void notify_summon_help(int mon, bool success)
+void notify_summon_help(Mon_handle mon, bool success)
{
/* Do the summoning... */
- print_mon_name(mon, 3);
- print_msg(" calls for help...\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s calls for help...\n", s);
if (success)
{
print_msg("... and gets it.\n");
{
print_msg("... luckily for you, help wasn't listening.\n");
}
+ free(s);
}
-void notify_summon_demon(int mon)
+void notify_summon_demon(Mon_handle mon)
{
if (mon != NO_MON)
{
}
}
-void notify_mon_disappear(int mon)
+void notify_mon_disappear(Mon_handle mon)
{
- print_mon_name(mon, 3);
+ char *s;
+ asprint_mon_name(&s, mon, 3);
print_msg(" vanishes in a puff of smoke.\n");
}
print_msg("The fires of hell %s\n", resisted ? "lightly singe you." : "burn you!");
}
-void notify_monster_cursing(int mon)
+void notify_monster_cursing(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" points at you and curses horribly.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s points at you and curses horribly.\n", s);
+ free(s);
}
void notify_no_attackee(void)
print_msg("Your foe is knocked backwards by the force of the shot.\n");
}
-void notify_player_miss(int mon)
+void notify_player_miss(Mon_handle mon)
+{
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("You miss %s.\n", s);
+ free(s);
+}
+
+void notify_player_charge_mon(Mon_handle mon)
{
- print_msg("You miss ");
- print_mon_name(mon, 1);
- print_msg(".\n");
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("You charge %s.\n", s);
+ free(s);
}
-void notify_player_hit_mon(int mon)
+void notify_player_hit_mon(Mon_handle mon)
{
- print_msg("You hit ");
- print_mon_name(mon, 1);
- print_msg(".\n");
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("You hit %s.\n", s);
+ free(s);
}
-void notify_player_combo_powatk(int mon)
+void notify_player_combo_powatk(Mon_handle mon)
{
- print_msg("You deal a powerful blow to ");
- print_mon_name(mon, 1);
- print_msg(".\n");
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("You deal a powerful blow to %s.\n", s);
}
void notify_point_blank_warning(void)
print_msg(Msg_prio::Alert, "Using a bow at such close quarters is awkward, and you are unlikely to hit your target.\n");
}
-void notify_player_shot_terrain(int obj, Coord c)
+void notify_player_shot_terrain(Obj_handle obj, Coord c)
{
- print_msg("Your %s hits the %s.\n", (objects[obj].obj_id == PO_CROSSBOW) ? "bolt" : "arrow", terrain_props[lvl.terrain_at(c)].name);
+ print_msg("Your %s hits the %s.\n", (objects[obj].po_ref == PO_CROSSBOW) ? "bolt" : "arrow", terrain_props[lvl.terrain_at(c)].name);
}
-void notify_ring_boost(int mon, int pobj)
+void notify_ring_boost(Mon_handle mon, int pobj)
{
+ char *s;
+ asprint_mon_name(&s, mon, 1);
switch (pobj)
{
case PO_FIRE_RING:
- print_msg("Your ring burns ");
- print_mon_name(mon, 1);
- print_msg("!\n");
+ print_msg("Your ring burns %s!\n", s);
break;
case PO_VAMPIRE_RING:
- print_msg("Your ring drains ");
- print_mon_name(mon, 1);
- print_msg("!\n");
+ print_msg("Your ring drains %s!\n", s);
break;
case PO_FROST_RING:
- print_msg("Your ring freezes ");
- print_mon_name(mon, 1);
- print_msg("!\n");
+ print_msg("Your ring freezes %s!\n", s);
break;
}
+ free(s);
+}
+
+void notify_flying_leap(void)
+{
+ print_msg("You vault through the air.\n");
}
-void notify_player_hurt_mon(int mon, int damage)
+
+void notify_player_hurt_mon(Mon_handle mon, int damage)
{
print_msg("You do %d damage.\n", damage);
}
-void notify_player_killed_mon(int mon)
+void notify_player_killed_mon(Mon_handle mon)
{
newsym(monsters[mon].pos);
- print_msg("You kill ");
if (occupant_visible(monsters[mon].pos))
{
- print_mon_name(mon, 1);
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("You kill %s!\n", s);
+ free(s);
}
else
{
- print_msg("something");
+ print_msg("You kill something...\n");
+ }
+}
+
+void notify_mon_dies(Mon_handle mon)
+{
+ newsym(monsters[mon].pos);
+ if (occupant_visible(monsters[mon].pos))
+ {
+ char *s;
+ asprint_mon_name(&s, mon, 2);
+ print_msg("%s dies.\n", &s);
+ free(s);
}
- print_msg("!\n");
}
-void notify_mon_dies(int mon)
+void notify_mon_detonates(Mon_handle mon, Decal_tag tag)
{
newsym(monsters[mon].pos);
if (occupant_visible(monsters[mon].pos))
{
- print_mon_name(mon, 2);
- print_msg(" dies.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 2);
+ if (tag != NO_DECAL)
+ {
+ print_msg("%s explodes in a welter of %s!\n", s, decal_props[tag]);
+
+ }
+ else
+ {
+ print_msg("%s explodes!\n", s);
+ }
+ free(s);
}
}
-void notify_knockback_mon_resisted(int mon)
+void notify_knockback_mon_resisted(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" wobbles slightly.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s wobbles slightly.\n", s);
+ free(s);
}
-void notify_knockback_mon_blocked(int mon)
+void notify_knockback_mon_blocked(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" is slammed against the wall.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s is slammed against the wall.\n", s);
+ free(s);
}
-void notify_knockback_mon_hover_lava(int mon)
+void notify_knockback_mon_hover_lava(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" is hurled out over the lava.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s is hurled out over the lava.\n", s);
+ free(s);
}
-void notify_knockback_mon_hover_water(int mon)
+void notify_knockback_mon_hover_water(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" is hurled out over the water.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s is hurled out over the water.\n", s);
+ free(s);
}
-void notify_knockback_mon_immersed_lava(int mon)
+void notify_knockback_mon_immersed_lava(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" tumbles into a pool of molten rock.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s tumbles into a pool of molten rock.\n", s);
+ free(s);
}
-void notify_knockback_mon_immersed_water(int mon)
+void notify_knockback_mon_immersed_water(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" tumbles into the water.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s tumbles into the water.\n", s);
+ free(s);
}
void notify_knockback_mon_lava_offscreen(void)
print_msg("Splash!\n");
}
-void notify_mon_disappears(int mon, Coord oldpos)
+void notify_mon_disappears(Mon_handle mon, Coord oldpos)
{
}
-void notify_mon_appears(int mon)
+void notify_mon_appears(Mon_handle mon)
{
- print_mon_name(mon, 2);
- print_msg(" appears in a puff of smoke.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 2);
+ print_msg("%s appears in a puff of smoke.\n", s);
+ free(s);
}
-void notify_mon_hit_armour(int mon)
+void notify_mon_hit_armour(Mon_handle mon)
{
- print_msg("Your armour deflects ");
- print_mon_name(mon, 1);
- print_msg("'s blow.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 1);
+ print_msg("Your armour deflects %s's blow.\n", s);
+ free(s);
}
-void notify_mon_missed_player(int mon)
+void notify_mon_missed_player(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" misses you.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s misses you.\n", s);
+ free(s);
}
-void notify_mon_hit_player(int mon)
+void notify_mon_hit_player(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" hits you.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s hits you.\n", s);
+ free(s);
}
-void notify_mon_ranged_attack(int mon)
+void notify_mon_ranged_attack(Mon_handle mon)
{
- int pm = monsters[mon].mon_id;
+ int pm = monsters[mon].pm_ref;
Damtyp dt = permons[pm].rdtyp;
- print_mon_name(mon, 3);
+ char *s;
+ asprint_mon_name(&s, mon, 3);
if (dt == DT_PHYS)
{
- print_msg(" %s at you!\n", permons[pm].shootverb);
+ print_msg("%s %s at you!\n", s, permons[pm].shootverb);
}
else
{
- print_msg(" %s %s at you!\n", permons[pm].shootverb, damtype_names[dt]);
+ print_msg("%s %s %s at you!\n", s, permons[pm].shootverb, damtype_names[dt]);
}
+ free(s);
}
void notify_mon_ranged_hit_mon(int er, int ee)
print_msg("It hits a bystander.\n");
}
-void notify_mon_ranged_missed_player(int mon)
+void notify_mon_ranged_missed_player(Mon_handle mon)
{
print_msg("It misses you.\n");
}
-void notify_mon_ranged_hit_player(int mon)
+void notify_mon_ranged_hit_player(Mon_handle mon)
{
print_msg("It hits you!\n");
}
print_msg("Your current weapon seems to have no magic powers to activate.\n");
}
-void notify_armour_equip(int obj)
+void notify_armour_equip(Obj_handle obj)
{
- Permobj *pobj = permobjs + objects[obj].obj_id;
+ Permobj *pobj = permobjs + objects[obj].po_ref;
if (pobj->flags[0] & POF_NOTIFY_EQUIP)
{
- switch (objects[u.armour].obj_id)
+ switch (objects[u.armour].po_ref)
{
case PO_SET_OF_RIBBONS:
if (u.sybaritic())
}
else
{
- print_msg("Wearing ");
- print_obj_name(u.armour);
- print_msg(".\n");
+ char *s;
+ asprint_obj_name(&s, u.armour);
+ print_msg("Wearing %s.\n", s);
+ free(s);
}
}
-void notify_armour_unequip(int obj)
+void notify_armour_unequip(Obj_handle obj)
{
- Permobj *pobj = permobjs + objects[obj].obj_id;
- // TODO add unequip messages for NOTIFY_EQUIP armours.
+ Permobj *pobj = permobjs + objects[obj].po_ref;
if (pobj->flags[0] & POF_NOTIFY_EQUIP)
{
- switch (objects[obj].obj_id)
+ switch (objects[obj].po_ref)
{
- case PO_SET_OF_RIBBONS:
- if (u.sybaritic())
- {
- print_msg("Reluctantly, you peel off the web of ribbons.\n");
- }
- else
- {
- print_msg("With a sigh of relief you peel off the uncanny web of ribbons.\n");
- }
- break;
case PO_LICHS_ROBE:
print_msg("The supernatural chill of the robes lingers in your bones even after you put them aside.\n");
break;
}
}
-void notify_ring_equip(int obj)
+void notify_ring_equip(Obj_handle obj)
{
- print_msg("You put on ");
- print_obj_name(obj);
- print_msg(".\n");
+ char *s;
+ asprint_obj_name(&s, obj);
+ print_msg("You put on %s.\n", s);
+ free(s);
}
-void notify_ring_unequip(int obj)
+void notify_ring_unequip(Obj_handle obj)
{
print_msg("You remove your ring.\n");
}
-void notify_lava_blocks_unequip(void)
+void notify_hot_blocks_unequip(void)
{
- print_msg(Msg_prio::Warn, "That item is your only current source of fire resistance; setting it aside here would incinerate you.\n");
+ print_msg(Msg_prio::Warn, "Setting that item aside here would incinerate you.\n");
}
-void notify_water_blocks_unequip(void)
+void notify_wet_blocks_unequip(void)
{
print_msg(Msg_prio::Warn, "Setting that item aside here would cause your death by drowning.\n");
}
+void notify_pit_blocks_unequip(void)
+{
+ print_msg(Msg_prio::Warn, "Setting that item aside here would send you plummetting to your death.\n");
+}
+
+void notify_role_blocks_equip(void)
+{
+ switch (u.role)
+ {
+ case Role_demon_hunter:
+ print_msg(Msg_prio::Alert, "Unclean! Unclean! Cast this filth aside!\n");
+ break;
+ default:
+ print_msg(Msg_prio::Alert, "Your chosen path in life precludes the use of that item.\n");
+ break;
+ }
+}
+
void notify_player_touch_effect(Damtyp dt)
{
switch (dt)
switch (dt)
{
case DT_FIRE:
- print_msg("The feel a pleasant warmth.\n");
+ print_msg("You feel a pleasant warmth.\n");
break;
case DT_COLD:
print_msg("You feel a pleasant chill.\n");
}
}
-void notify_item_explodes_flames(int obj)
+void notify_item_explodes_flames(Obj_handle obj)
{
- print_msg("The %s explodes in flames!\n", permobjs[objects[obj].obj_id].name);
+ print_msg("The %s explodes in flames!\n", permobjs[objects[obj].po_ref].name);
}
void notify_read_scroll_protection(void)
}
}
-void notify_fireitem_hit(int mon)
+void notify_inferno_hit(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" is engulfed in roaring flames.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s is engulfed in roaring flames.\n", s);
+ free(s);
}
void notify_lash_activation(int specific)
print_msg("Nothing to get.\n");
}
-void notify_mon_healed(int mon)
+void notify_mon_healed(Mon_handle mon)
{
- print_mon_name(mon, 3);
- print_msg(" looks healthier.\n");
+ char *s;
+ asprint_mon_name(&s, mon, 3);
+ print_msg("%s looks healthier.\n", s);
+ free(s);
}
-void notify_mon_regenerates(int mon)
+void notify_mon_regenerates(Mon_handle mon)
{
// TODO allow things that aren't trolls to be regenerators
print_msg("The troll regenerates.\n");
}
-void notify_unwield(int obj, Noisiness noisy)
+void notify_unwield(Obj_handle obj, Noisiness noisy)
{
if (noisy == Noise_std)
{
}
}
-void notify_wield_weapon(int obj)
+void notify_wield_weapon(Obj_handle obj)
{
- print_msg("Wielding ");
- print_obj_name(obj);
- print_msg(".\n");
+ Obj const *optr = obj_snap(obj);
+ int po = optr->po_ref;
+ if (permobjs[po].flags[0] & POF_NOTIFY_EQUIP)
+ {
+ switch (po)
+ {
+#if 0
+ case PO_WINDSWORD:
+ print_msg("Your stride lightens as you ready the magic blade.\n");
+ break;
+#endif
+ case PO_HELLGLAIVE:
+ print_msg("Infernal power suffuses you as you heft the cruel pole-arm.\n");
+ break;
+ case PO_PLAGUE_SCYTHE:
+ print_msg("The scent of pestilence and decay fills your nostrils as you ready the scythe.\n");
+ break;
+ case PO_DEATH_STAFF:
+ print_msg("The chill of the grave suffuses you as you ready the black staff.\n");
+ break;
+ case PO_TORMENTORS_LASH:
+ print_msg("A%s tingle of power runs up your arm as you ready the magic whip.\n",
+ u.sybaritic() ? " delightful" : "n uncanny");
+ break;
+ }
+ }
+ else
+ {
+ char *s;
+ asprint_obj_name(&s, obj);
+ print_msg("Wielding %s.\n", s);
+ free(s);
+ }
}
-void notify_weapon_broke(int obj)
+void notify_weapon_broke(Obj_handle obj)
{
print_msg(Msg_prio::Warn, "Your weapon breaks!\n");
}
-void notify_armour_broke(int obj)
+void notify_armour_broke(Obj_handle obj)
{
print_msg(Msg_prio::Warn, "Your armour is ruined!\n");
}
-void notify_drop_item(int obj)
+void notify_drop_item(Obj_handle obj)
{
- print_msg("You drop ");
- print_obj_name(obj);
- print_msg(".\n");
+ char *s;
+ asprint_obj_name(&s, obj);
+ print_msg("You drop %s.\n", s);
+ free(s);
}
void notify_drop_blocked(void)
print_msg(Msg_prio::Alert, "There is no room to drop that here.\n");
}
-void notify_get_item(int from, int slot)
+void notify_get_item(Obj_handle from, int slot)
{
if (from != NO_OBJ)
{
* \param c Affected location
* \param mon Monster at that location.
*/
-void notify_new_mon_at(Coord c, int mon)
+void notify_new_mon_at(Coord c, Mon_handle mon)
{
newsym(c);
display_update();
void notify_change_of_depth(void)
{
status_updated = true;
- print_msg("Welcome to level %d of the Abyss.\n", depth);
+ print_msg("Welcome to level %d.\n", depth);
}
void notify_load_complete(void)
print_msg(Msg_prio::Bug, "BUG: reading non-scroll\n");
}
-void debug_eat_non_food(int obj)
+void debug_eat_non_food(Obj_handle obj)
{
- print_msg(Msg_prio::Bug, "BUG: attempt to eat non-food (%d)!\n", objects[obj].obj_id);
+ print_msg(Msg_prio::Bug, "BUG: attempt to eat non-food (%d)!\n", objects[obj].po_ref);
}
void debug_quaff_non_potion(void)
print_msg(Msg_prio::Bug, "BUG: permobj %d should react to hitting durability 0 but has no break reaction handler.\n");
}
-void debug_mon_invalid_move(int mon, Coord c)
+void debug_mon_invalid_move(Mon_handle mon, Coord c)
{
print_msg(Msg_prio::Bug, "BUG: monster %d attempted move to impassable location y=%d x=%d\n", mon, c.y, c.x);
}
/*! \file notify.hh
- * \brief Notification-related definitions for Victrix Abyssi
+ * \brief Notification-related definitions
*/
/* Copyright 2014 Martin Read
void notify_wasted_gain(void);
void notify_defence_recalc(void);
void notify_food_use(int food_use, int hunger_severity);
+void notify_learned_skill(Skill_id skill);
// Resistance notifications
// Player movement notifications
-void notify_start_lavawalk(void);
-void notify_blocked_lava(void);
-void notify_start_waterwalk(void);
-void notify_blocked_water(void);
+void notify_start_hotwalk(Terrain t);
+void notify_blocked_hot(Terrain t);
+void notify_start_waterwalk(Terrain t);
+void notify_blocked_water(Terrain t);
void notify_cant_go(void);
+void notify_portal_underfoot(void);
+void notify_flying_leap(void);
// Unsorted notifications
+void notify_new_obj_at(Coord c, Obj_handle obj);
void notify_obj_at(Coord c);
void notify_dress_shredded(void);
-void notify_mon_healed(int mon);
-void notify_mon_regenerates(int mon);
-void notify_mon_disappears(int mon, Coord oldpos);
-void notify_mon_appears(int mon);
+void notify_mon_healed(Mon_handle mon);
+void notify_mon_regenerates(Mon_handle mon);
+void notify_mon_disappears(Mon_handle mon, Coord oldpos);
+void notify_mon_appears(Mon_handle mon);
// Item manipulation notifications
void notify_magic_no_ring(void);
void notify_magic_powerless_ring(void);
void notify_emanate_powerless_armour(void);
void notify_zap_powerless_weapon(void);
-void notify_ring_equip(int obj);
-void notify_ring_unequip(int obj);
-void notify_armour_equip(int obj);
-void notify_armour_unequip(int obj);
-void notify_lava_blocks_unequip(void);
-void notify_water_blocks_unequip(void);
+void notify_ring_equip(Obj_handle obj);
+void notify_ring_unequip(Obj_handle obj);
+void notify_armour_equip(Obj_handle obj);
+void notify_armour_unequip(Obj_handle obj);
+void notify_hot_blocks_unequip(void);
+void notify_wet_blocks_unequip(void);
+void notify_pit_blocks_unequip(void);
void notify_nothing_to_get(void);
-void notify_unwield(int obj, Noisiness noisy);
-void notify_wield_weapon(int obj);
-void notify_weapon_broke(int obj);
-void notify_armour_broke(int obj);
-void notify_drop_item(int obj);
+void notify_unwield(Obj_handle obj, Noisiness noisy);
+void notify_wield_weapon(Obj_handle obj);
+void notify_role_blocks_equip(void);
+void notify_weapon_broke(Obj_handle obj);
+void notify_armour_broke(Obj_handle obj);
+void notify_drop_item(Obj_handle obj);
void notify_drop_blocked(void);
-void notify_get_item(int from, int slot);
+void notify_drop_blocked_portal(void);
+void notify_get_item(Obj_handle from, int slot);
void notify_pack_full(void);
// Magic item use releated notifications
void notify_ribbon_activation(int specific);
void notify_lash_activation(int specific);
void notify_firestaff_activation(int specific);
-void notify_fireitem_hit(int mon);
+void notify_inferno_hit(Mon_handle mon);
void notify_telering_activation(int specific);
-void notify_item_explodes_flames(int obj);
+void notify_item_explodes_flames(Obj_handle obj);
void notify_eat_food(bool ravenous);
void notify_ingest_spleen(void);
void notify_swing_bow(void);
void notify_no_attackee(void);
void notify_no_flask_target(void);
-void notify_player_miss(int mon);
-void notify_ring_boost(int mon, int pobj);
-void notify_player_hit_mon(int mon);
-void notify_player_hurt_mon(int mon, int damage);
-void notify_player_killed_mon(int mon);
-void notify_player_combo_powatk(int mon);
-void notify_knockback_mon_resisted(int mon);
-void notify_knockback_mon_blocked(int mon);
-void notify_knockback_mon_hover_lava(int mon);
-void notify_knockback_mon_hover_water(int mon);
-void notify_knockback_mon_immersed_lava(int mon);
-void notify_knockback_mon_immersed_water(int mon);
+void notify_player_miss(Mon_handle mon);
+void notify_ring_boost(Mon_handle mon, int pobj);
+void notify_player_hit_mon(Mon_handle mon);
+void notify_player_charge_mon(Mon_handle mon);
+void notify_player_hurt_mon(Mon_handle mon, int damage);
+void notify_player_killed_mon(Mon_handle mon);
+void notify_player_combo_powatk(Mon_handle mon);
+void notify_knockback_mon_resisted(Mon_handle mon);
+void notify_knockback_mon_blocked(Mon_handle mon);
+void notify_knockback_mon_hover_lava(Mon_handle mon);
+void notify_knockback_mon_hover_water(Mon_handle mon);
+void notify_knockback_mon_immersed_lava(Mon_handle mon);
+void notify_knockback_mon_immersed_water(Mon_handle mon);
void notify_knockback_mon_water_offscreen(void);
void notify_knockback_mon_lava_offscreen(void);
void notify_point_blank_warning(void);
-void notify_player_shot_terrain(int obj, Coord c);
-
-void notify_mon_hit_armour(int mon);
-void notify_mon_missed_player(int mon);
-void notify_mon_hit_player(int mon);
-void notify_mon_ranged_attack(int mon);
-void notify_mon_ranged_hit_player(int mon);
-void notify_mon_ranged_missed_player(int mon);
+void notify_player_shot_terrain(Obj_handle obj, Coord c);
+
+void notify_mon_hit_armour(Mon_handle mon);
+void notify_mon_missed_player(Mon_handle mon);
+void notify_mon_hit_player(Mon_handle mon);
+void notify_mon_ranged_attack(Mon_handle mon);
+void notify_mon_ranged_hit_player(Mon_handle mon);
+void notify_mon_ranged_missed_player(Mon_handle mon);
void notify_mon_ranged_hit_mon(int er, int ee);
-void notify_mon_dies(int mon);
-void notify_new_mon_at(Coord c, int mon);
+void notify_mon_dies(Mon_handle mon);
+void notify_mon_detonates(Mon_handle mon, Decal_tag decal);
+void notify_new_mon_at(Coord c, Mon_handle mon);
void notify_player_damage_taken(int amount);
void notify_player_touch_effect(Damtyp dt);
void notify_player_ignore_damage(Damtyp dt);
// Sorcery notifications
-void notify_summon_help(int mon, bool success);
-void notify_summon_demon(int mon);
-void notify_monster_cursing(int mon);
-void notify_mon_disappear(int mon);
+void notify_summon_help(Mon_handle mon, bool success);
+void notify_summon_demon(Mon_handle mon);
+void notify_monster_cursing(Mon_handle mon);
+void notify_mon_disappear(Mon_handle mon);
void notify_moncurse_fail(void);
void notify_start_armourmelt(void);
void notify_start_withering(void);
void notify_change_of_depth(void);
void notify_load_complete(void);
void notify_ascent_blocked(void);
+void notify_inert_portal(void);
// Debugging notifications
void debug_move_oob(Coord c);
void debug_remove_no_ring(void);
void debug_put_on_second_ring(void);
void debug_put_on_uncarried_ring(void);
-void debug_eat_non_food(int obj);
+void debug_eat_non_food(Obj_handle obj);
void debug_read_non_scroll(void);
void debug_quaff_non_potion(void);
void debug_ascend_non_stairs(void);
void debug_misplaced_monster(void);
void debug_create_mon_occupied(Coord c);
void debug_monster_pool_exhausted(void);
-void debug_mon_invalid_move(int mon, Coord c);
+void debug_mon_invalid_move(Mon_handle mon, Coord c);
void debug_pmon_select_failed(void);
void debug_dump_write_failed(void);
void debug_unimplemented_activation(int po);
class Notify_uattkm
{
public:
- int mon;
+ Mon_handle mon;
bool hit;
int dmg;
bool ringflag;
--- /dev/null
+/* \file obj2.cc
+ * \brief Per-PO object use functions for Obumbrata et Velata
+ */
+
+/* Copyright © 2014 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 OBJ2_CC
+#include "victrix-abyssi.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "fov.hh"
+
+#include <string.h>
+
+/*! \brief Effect of quaffing a body potion */
+static void body_potion_quaff(Obj_handle obj)
+{
+ gain_body(1);
+}
+
+/*! \brief Effect of quaffing a agility potion */
+static void agility_potion_quaff(Obj_handle obj)
+{
+ gain_agility(1);
+}
+
+/*! \brief Effect of quaffing a healing potion */
+static void healing_potion_quaff(Obj_handle obj)
+{
+ int healpercent = inc_flat(30, 50);
+ int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100;
+ heal_u(healamount, 1, 1);
+}
+
+/*! \brief Effect of quaffing a restoration potion */
+static void restoration_potion_quaff(Obj_handle obj)
+{
+ notify_quaff_potion_restoration();
+ if (u.bdam && ((!u.adam) || zero_die(2)))
+ {
+ u.bdam = 0;
+ notify_body_restore();
+ }
+ else if (u.adam)
+ {
+ u.adam = 0;
+ notify_agility_restore();
+ }
+}
+
+/*! \brief Lookup table for effects of operations involving potions */
+Potion_table_entry potion_table[] =
+{
+ { PO_BODY_POTION, body_potion_quaff },
+ { PO_AGILITY_POTION, agility_potion_quaff },
+ { PO_HEALING_POTION, healing_potion_quaff },
+ { PO_RESTORATION_POTION, restoration_potion_quaff },
+ { NO_POBJ, nullptr }
+};
+
+static Inferno_detail fire_scroll_inferno = { true, NO_MON, 4, 10, 0 };
+
+bool inferno_effector(Coord c, void *pvt)
+{
+ Inferno_detail *inf = (Inferno_detail *) pvt;
+ Mon_handle mon = lvl.mon_at(c);
+ if (mon != NO_MON)
+ {
+ Mon const *mptr = mon_snap(mon);
+ if (!pmon_resists_fire(mptr->pm_ref))
+ {
+ notify_inferno_hit(mon);
+ damage_mon(mon, inf->dice_bonus + dice(inf->dice_count, inf->dice_sides), inf->by_you);
+ }
+ }
+ return true;
+}
+
+static Radiance fire_scroll_explosion =
+{
+ {},
+ Nowhere,
+ MAX_FOV_RADIUS,
+ Reo_spiral_out,
+ true,
+ &fire_scroll_inferno,
+ dflt_blk,
+ inferno_effector
+};
+
+static void fire_scroll_read(Obj_handle obj)
+{
+ notify_item_explodes_flames(obj);
+ if (u.resistances[DT_FIRE])
+ {
+ notify_player_ignore_damage(DT_FIRE);
+ }
+ else
+ {
+ damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
+ }
+ fire_scroll_explosion.clear();
+ fire_scroll_explosion.centre = u.pos;
+ fire_scroll_explosion.compute();
+ fire_scroll_explosion.resolve();
+}
+
+static void teleport_scroll_read(Obj_handle obj)
+{
+ teleport_u();
+}
+
+static void protection_scroll_read(Obj_handle obj)
+{
+ notify_read_scroll_protection();
+ if (!u.protection)
+ {
+ /* Do not prolong existing protection, only grant
+ * protection to the unprotected. */
+ u.protection = 100;
+ }
+ if (u.withering)
+ {
+ u.withering = 0;
+ notify_wither_recovered();
+ }
+ if (u.armourmelt)
+ {
+ u.armourmelt = 0;
+ notify_armourmelt_recovered();
+ }
+ if (u.leadfoot)
+ {
+ u.leadfoot = 0;
+ notify_leadfoot_recovered();
+ }
+ recalc_defence();
+}
+
+/*! \brief Lookup table for effects of reading scrolls */
+Scroll_table_entry scroll_table[] =
+{
+ { PO_FIRE_SCROLL, fire_scroll_read },
+ { PO_TELEPORT_SCROLL, teleport_scroll_read },
+ { PO_PROTECTION_SCROLL, protection_scroll_read },
+ { NO_POBJ, nullptr }
+};
+
+static Inferno_detail fire_staff_inferno = { true, NO_MON, 4, 10, 0 };
+
+static Radiance fire_staff_explosion =
+{
+ {},
+ Nowhere,
+ MAX_FOV_RADIUS,
+ Reo_spiral_out,
+ true,
+ &fire_staff_inferno,
+ dflt_blk,
+ inferno_effector
+};
+
+static void fire_staff_zap(Obj_handle obj)
+{
+ if (u.food > 150)
+ {
+ u.food -= 150;
+ notify_firestaff_activation(1);
+ fire_staff_explosion.clear();
+ fire_staff_explosion.centre = u.pos;
+ fire_staff_explosion.compute();
+ fire_staff_explosion.resolve();
+ damage_obj(obj);
+ }
+ else
+ {
+ notify_firestaff_activation(0);
+ }
+}
+
+Weapon_table_entry weapon_table[] =
+{
+ { PO_STAFF_OF_FIRE, fire_staff_zap, nullptr, nullptr },
+ { NO_POBJ, nullptr, nullptr, nullptr },
+};
+
+/* obj2.cc */
+// vim:cindent
/* \file objects.cc
- * \brief Object handling for Victrix Abyssi
+ * \brief Object handling
*/
/* Copyright 2005-2013 Martin Read
#include "objects.hh"
#include "monsters.hh"
-Obj objects[100];
+#include <string.h>
+
+std::map<Obj_handle, Obj> objects;
+const Obj_handle NO_OBJ = 0u;
+
int get_random_pobj(void);
-char const 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"
-};
-
-char const 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"
-};
-
-char const 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"
-};
-
-Action_cost read_scroll(int obj)
-{
- Obj *optr = objects + obj;
+/*! \brief Read a magic scroll */
+Action_cost read_scroll(Obj_handle obj)
+{
+ Obj *optr = obj_snapv(obj);
int i;
- switch (optr->obj_id)
+ for (i = 0; scroll_table[i].read_func != nullptr; ++i)
{
- case PO_TELEPORT_SCROLL:
- teleport_u();
- break;
- case PO_FIRE_SCROLL:
- notify_item_explodes_flames(obj);
- if (u.resistances[DT_FIRE])
- {
- notify_player_ignore_damage(DT_FIRE);
- }
- else
- {
- damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
- }
- for (i = 0; i < MONSTERS_IN_PLAY; ++i)
- {
- if (mon_visible(i))
- {
- if (!pmon_resists_fire(monsters[i].mon_id))
- {
- notify_fireitem_hit(i);
- damage_mon(i, dice(4, 10), true);
- }
- }
- }
- break;
- case PO_PROTECTION_SCROLL:
- notify_read_scroll_protection();
- if (!u.protection)
- {
- /* Do not prolong existing protection, only grant
- * protection to the unprotected. */
- u.protection = 100;
- }
- if (u.withering)
- {
- u.withering = 0;
- notify_wither_recovered();
- }
- if (u.armourmelt)
- {
- u.armourmelt = 0;
- notify_armourmelt_recovered();
- }
- if (u.leadfoot)
+ if (scroll_table[i].pobj == optr->po_ref)
{
- u.leadfoot = 0;
- notify_leadfoot_recovered();
+ scroll_table[i].read_func(obj);
+ consume_obj(obj);
+ return Cost_std;
}
- recalc_defence();
- break;
- default:
- debug_read_non_scroll();
- return Cost_none;
}
- consume_obj(obj);
- return Cost_std;
+ debug_read_non_scroll();
+ return Cost_none;
}
-bool consume_obj(int obj)
+bool consume_obj(Obj_handle obj)
{
int i;
- objects[obj].quan--;
- if (objects[obj].quan == 0)
+ Obj *optr = obj_snapv(obj);
+ optr->quan--;
+ if (optr->quan == 0)
{
- objects[obj].used = false;
- if (objects[obj].with_you)
+ optr->flags &= ~OF_USED;
+ if (optr->flags & OF_WITH_YOU)
{
if (obj == u.armour)
{
u.ring = NO_OBJ;
recalc_defence();
}
- if (!objects[obj].used)
+ for (i = 0; i < 19; i++)
{
- for (i = 0; i < 19; i++)
+ if (u.inventory[i] == obj)
{
- if (u.inventory[i] == obj)
- {
- u.inventory[i] = NO_OBJ;
- break;
- }
+ u.inventory[i] = NO_OBJ;
+ break;
}
}
}
return false;
}
-Action_cost eat_food(int obj)
+/*! \brief Consume a food */
+Action_cost eat_food(Obj_handle obj)
{
- Obj *optr = objects + obj;
+ Obj *optr = obj_snapv(obj);
bool ravenous = (u.food < 0);
- u.food += 1500;
- if (permobjs[optr->obj_id].poclass != POCLASS_FOOD)
+ if (permobjs[optr->po_ref].poclass != POCLASS_FOOD)
{
debug_eat_non_food(obj);
return Cost_none;
}
- if (optr->obj_id == PO_DEVIL_SPLEEN)
+ u.food += permobjs[optr->po_ref].power;
+ if (optr->po_ref == PO_DEVIL_SPLEEN)
{
notify_ingest_spleen();
if (zero_die(2))
return Cost_std;
}
-Action_cost quaff_potion(int obj)
+/*! \brief Consume a potion */
+Action_cost quaff_potion(Obj_handle obj)
{
- Obj *optr = objects + obj;
- switch (optr->obj_id)
- {
- case PO_BODY_POTION:
- gain_body(1);
- break;
- case PO_AGILITY_POTION:
- gain_agility(1);
- break;
- case PO_HEALING_POTION:
- {
- int healpercent = inc_flat(30, 50);
- int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100;
- heal_u(healamount, 1, 1);
- }
- break;
- case PO_RESTORATION_POTION:
- notify_quaff_potion_restoration();
- if (u.bdam && ((!u.adam) || zero_die(2)))
- {
- u.bdam = 0;
- notify_body_restore();
- }
- else if (u.adam)
- {
- u.adam = 0;
- notify_agility_restore();
- }
- break;
- default:
- debug_quaff_non_potion();
- return Cost_none;
- }
- consume_obj(obj);
- return Cost_std;
-
-}
-
-void flavours_init(void)
-{
- int colour_choices[10];
+ Obj *optr = obj_snapv(obj);
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_REGENERATION_RING].power = colour_choices[0];
- permobjs[PO_FIRE_RING].power = colour_choices[1];
- permobjs[PO_VAMPIRE_RING].power = colour_choices[2];
- permobjs[PO_FROST_RING].power = colour_choices[3];
- permobjs[PO_TELEPORT_RING].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_FIRE_SCROLL].power = colour_choices[0];
- permobjs[PO_TELEPORT_SCROLL].power = colour_choices[1];
- permobjs[PO_PROTECTION_SCROLL].power = colour_choices[2];
- /* Potions */
- for (i = 0; i < 10;)
+ for (i = 0; potion_table[i].pobj != NO_POBJ; ++i)
{
- colour_choices[i] = zero_die(20);
- done = 1;
- for (j = 0; j < i; j++)
+ if (potion_table[i].pobj == optr->po_ref)
{
- if (colour_choices[i] == colour_choices[j])
- {
- done = 0;
- }
- }
- if (done)
- {
- i++;
+ potion_table[i].quaff_func(obj);
+ consume_obj(obj);
+ return Cost_std;
}
}
- permobjs[PO_HEALING_POTION].power = colour_choices[0];
- permobjs[PO_BODY_POTION].power = colour_choices[1];
- permobjs[PO_AGILITY_POTION].power = colour_choices[2];
- permobjs[PO_RESTORATION_POTION].power = colour_choices[3];
+ debug_quaff_non_potion();
+ return Cost_none;
}
-int get_first_free_obj(void)
+Obj_handle first_free_obj_handle = 1u;
+
+Obj_handle get_first_free_obj(void)
{
- int obj;
- for (obj = 0; obj < 100; obj++)
+ if (first_free_obj_handle == 0u)
{
- if (!objects[obj].used)
- {
- break;
- }
+ return NO_OBJ;
}
- return (obj == 100) ? NO_OBJ : obj;
+ return first_free_obj_handle++;
}
-int create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coord c)
+Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool with_you, Coord c)
{
int po_idx;
int tryct;
- int obj = get_first_free_obj();
- if (obj == 100)
- {
- debug_object_pool_exhausted();
- return NO_OBJ;
- }
for (tryct = 0; tryct < 200; tryct++)
{
switch (po_class)
}
break;
}
- objects[obj].obj_id = po_idx;
- objects[obj].quan = quantity;
- return obj;
+ return (with_you ? create_obj(po_idx, quantity, true, c) : create_obj_near(po_idx, quantity, c));
}
int get_random_pobj(void)
po_idx = NO_POBJ;
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 = NO_POBJ;
return po_idx;
}
-int create_obj(int po_idx, int quantity, bool with_you, Coord c)
+Obj_handle create_corpse(int pm_idx, Coord c)
+{
+ Obj_handle obj = create_obj_near(PO_CORPSE, 1, c);
+ if (obj != NO_OBJ)
+ {
+ Obj *o = obj_snapv(obj);
+ o->meta[0] = pm_idx;
+ o->meta[1] = 0; // reserved
+ o->meta[2] = 0; // reserved
+ o->meta[3] = 0; // reserved
+ }
+ return obj;
+}
+
+Obj_handle create_obj_near(int po_idx, int quantity, Coord c)
{
- int obj = get_first_free_obj();
- if (obj == 100)
+ Offset delta;
+ Coord nc = c;
+ int panic_count = 200;
+ while ((terrain_blocks_items(lvl.terrain_at(nc)) || (lvl.obj_at(c) != NO_OBJ)) && (panic_count > 0))
+ {
+ do
+ {
+ delta = random_step();
+ nc = c + delta;
+ }
+ while (terrain_blocks_items(lvl.terrain_at(nc)));
+ c = nc;
+ --panic_count;
+ }
+ if (panic_count < 1)
+ {
+ return NO_OBJ;
+ }
+ Obj_handle obj = create_obj(po_idx, quantity, false, c);
+ return obj;
+}
+
+Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c)
+{
+ Obj_handle obj = get_first_free_obj();
+ if (obj == NO_OBJ)
{
debug_object_pool_exhausted();
return NO_OBJ;
return NO_OBJ;
}
}
- objects[obj].obj_id = po_idx;
- objects[obj].with_you = with_you;
- objects[obj].used = true;
- objects[obj].pos = c;
- objects[obj].quan = quantity;
+ Obj o;
+ memset(o.meta, '\0', sizeof o.meta);
+ o.self = obj;
+ o.po_ref = po_idx;
+ o.flags = OF_USED | (with_you ? OF_WITH_YOU : 0);
+ o.pos = c;
+ o.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[obj].durability = OBJ_MAX_DUR;
+ o.durability = OBJ_MAX_DUR;
break;
default:
break;
}
- if (!objects[obj].with_you)
+ objects[obj] = o;
+ if (!(o.flags & OF_WITH_YOU))
{
lvl.set_obj_at(c, obj);
+ notify_new_obj_at(c, obj);
}
return obj;
}
-void sprint_obj_name(char *buf, int obj, int len)
+void asprint_obj_name(char **buf, Obj_handle obj)
{
Obj *optr;
Permobj *poptr;
- optr = objects + obj;
- poptr = permobjs + optr->obj_id;
+ int i;
+ optr = obj_snapv(obj);
+ poptr = permobjs + optr->po_ref;
if (optr->quan > 1)
{
- snprintf(buf, len, "%d %s", optr->quan, poptr->plural);
+ i = asprintf(buf, "%d %s", optr->quan, poptr->plural);
}
- else if (po_is_stackable(optr->obj_id))
+ else if (po_is_stackable(optr->po_ref))
{
- snprintf(buf, len, "1 %s", poptr->name);
+ i = asprintf(buf, "1 %s", poptr->name);
}
- else if ((poptr->poclass == POCLASS_WEAPON) ||
- (poptr->poclass == POCLASS_ARMOUR))
+ else if (poptr->flags[0] & POF_DAMAGEABLE)
{
- snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+ i = asprintf(buf, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+ }
+ else if (optr->po_ref == PO_CORPSE)
+ {
+ Permon *pmptr = permons + optr->meta[0];
+ i = asprintf(buf, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name);
}
else
{
- snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+ i = asprintf(buf, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+ }
+ if (i == -1)
+ {
+ // TODO wibble
}
+ return;
}
-void fprint_obj_name(FILE *fp, int obj)
+void sprint_obj_name(char *buf, Obj_handle obj, int len)
{
Obj *optr;
Permobj *poptr;
- optr = objects + obj;
- poptr = permobjs + optr->obj_id;
+ optr = obj_snapv(obj);
+ poptr = permobjs + optr->po_ref;
if (optr->quan > 1)
{
- fprintf(fp, "%d %s", optr->quan, poptr->plural);
+ snprintf(buf, len, "%d %s", optr->quan, poptr->plural);
+ }
+ else if (po_is_stackable(optr->po_ref))
+ {
+ snprintf(buf, len, "1 %s", poptr->name);
}
- else if (po_is_stackable(optr->obj_id))
+ else if (poptr->flags[0] & POF_DAMAGEABLE)
{
- fprintf(fp, "1 %s", poptr->name);
+ snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
}
- else if ((poptr->poclass == POCLASS_WEAPON) ||
- (poptr->poclass == POCLASS_ARMOUR))
+ else if (optr->po_ref == PO_CORPSE)
{
- fprintf(fp, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+ Permon *pmptr = permons + optr->meta[0];
+ snprintf(buf, len, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name);
}
else
{
- fprintf(fp, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+ snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
}
}
+void fprint_obj_name(FILE *fp, Obj_handle obj)
+{
+ char *s;
+ asprint_obj_name(&s, obj);
+ fputs(s, fp);
+ free(s);
+}
+
Action_cost drop_obj(int inv_idx)
{
Obj *optr;
- optr = objects + u.inventory[inv_idx];
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ notify_drop_blocked();
+ return Cost_none;
+ }
+ optr = obj_snapv(u.inventory[inv_idx]);
if (lvl.obj_at(u.pos) == NO_OBJ)
{
optr->pos = u.pos;
u.weapon = NO_OBJ;
}
u.inventory[inv_idx] = NO_OBJ;
- optr->with_you = false;
+ optr->flags &= ~OF_WITH_YOU;
notify_drop_item(lvl.obj_at(u.pos));
return Cost_std;
}
}
}
-bool po_is_stackable(int po)
+Damtyp po_resistance(int po)
{
- switch (permobjs[po].poclass)
+ if ((po < 0) || (po >= NUM_OF_PERMOBJS))
{
- default:
- return false;
- case POCLASS_POTION:
- case POCLASS_SCROLL:
- case POCLASS_FOOD:
- return true;
+ return DT_NONE;
+ }
+ uint32_t res = permobjs[po].flags[1] & POF_RES_MASK;
+ // Items never grant physical damage resistance
+ if ((res > 0) && (res <= LAST_DAMTYPE))
+ {
+ return Damtyp(res);
+ }
+ return DT_NONE;
+}
+
+Damtyp po_damage_amp(int po)
+{
+ if ((po < 0) || (po >= NUM_OF_PERMOBJS))
+ {
+ return DT_NONE;
+ }
+ uint32_t dt = (permobjs[po].flags[1] & POF_DMG_MASK) >> POF_DMG_SHIFT;
+ // Items never grant physical damage amplification
+ if ((dt > 0) && (dt <= LAST_DAMTYPE))
+ {
+ return Damtyp(dt);
}
+ return DT_NONE;
+}
+
+bool po_is_sword(int po)
+{
+ return (permobjs[po].flags[0] & POF_SWORD);
+}
+
+bool po_is_dagger(int po)
+{
+ return (permobjs[po].flags[0] & POF_DAGGER);
+}
+
+bool po_is_polearm(int po)
+{
+ return (permobjs[po].flags[0] & POF_POLEARM);
+}
+
+bool po_is_bludgeon(int po)
+{
+ return (permobjs[po].flags[0] & POF_BLUDGEON);
+}
+
+bool po_is_demonic(int po)
+{
+ return (permobjs[po].flags[0] & POF_DEMONIC);
+}
+
+bool po_is_dress(int po)
+{
+ return (permobjs[po].flags[0] & POF_DRESS);
+}
+
+bool po_is_leatherwear(int po)
+{
+ return (permobjs[po].flags[0] & POF_LEATHERY);
+}
+
+bool po_is_robe(int po)
+{
+ return (permobjs[po].flags[0] & POF_ROBE);
+}
+
+bool po_is_stackable(int po)
+{
+ return (permobjs[po].flags[0] & POF_STACKABLE);
+}
+
+bool po_grants_speed(int po)
+{
+ return (permobjs[po].flags[1] & POF_SPEED);
+}
+
+bool po_grants_flight(int po)
+{
+ return (permobjs[po].flags[1] & POF_FLIGHT);
+}
+
+bool po_grants_protective(int po)
+{
+ return (permobjs[po].flags[1] & POF_PROTECTIVE);
+}
+
+bool po_grants_passwater(int po)
+{
+ return (permobjs[po].flags[1] & POF_PASS_WATER);
}
void attempt_pickup(void)
{
int i;
int stackable;
- stackable = po_is_stackable(objects[lvl.obj_at(u.pos)].obj_id);
+ Obj_handle oh = lvl.obj_at(u.pos);
+ Obj *optr = obj_snapv(oh);
+ Obj *invptr;
+ stackable = po_is_stackable(optr->po_ref);
if (stackable)
{
for (i = 0; i < 19; i++)
{
- if ((objects[u.inventory[i]].obj_id == objects[lvl.obj_at(u.pos)].obj_id))
+ invptr = obj_snapv(u.inventory[i]);
+ if (invptr && (invptr->po_ref == optr->po_ref))
{
- int stale_obj = lvl.obj_at(u.pos);
- objects[u.inventory[i]].quan += objects[lvl.obj_at(u.pos)].quan;
- objects[stale_obj].used = false;
+ invptr->quan += optr->quan;
+ optr->flags &= ~OF_USED;
lvl.set_obj_at(u.pos, NO_OBJ);
- notify_get_item(stale_obj, i);
+ notify_get_item(oh, i);
return;
}
}
}
u.inventory[i] = lvl.obj_at(u.pos);
lvl.set_obj_at(u.pos, NO_OBJ);
- objects[u.inventory[i]].with_you = true;
- objects[u.inventory[i]].pos = Nowhere;
+ optr->flags |= OF_WITH_YOU;
+ optr->pos = Nowhere;
notify_get_item(NO_OBJ, i);
}
-void break_reaction(int obj)
+void break_reaction(Obj_handle obj)
{
- switch (objects[obj].obj_id)
+ switch (objects[obj].po_ref)
{
case PO_SET_OF_RIBBONS:
if (u.food < 500)
break;
default:
- if (permobjs[objects[obj].obj_id].flags[0] & POF_DRESS)
+ if (permobjs[objects[obj].po_ref].flags[0] & POF_DRESS)
{
objects[obj].durability = 50 + zero_die(51);
- objects[obj].obj_id = PO_RAGGED_SHIFT;
+ objects[obj].po_ref = PO_RAGGED_SHIFT;
notify_dress_shredded();
recalc_defence();
}
else
{
- debug_unimplemented_break_reaction(objects[obj].obj_id);
+ debug_unimplemented_break_reaction(objects[obj].po_ref);
}
break;
}
}
-void damage_obj(int obj)
+void damage_obj(Obj_handle obj)
{
/* Only weapons and armour have non-zero durability. */
if (objects[obj].durability == 0)
else
{
objects[obj].durability--;
- if ((objects[obj].durability == 0) && (permobjs[objects[obj].obj_id].flags[0] & POF_BREAK_REACT))
+ if ((objects[obj].durability == 0) && (permobjs[objects[obj].po_ref].flags[0] & POF_BREAK_REACT))
{
break_reaction(obj);
}
}
}
-int evasion_penalty(int obj)
+int evasion_penalty(Obj_handle obj)
{
- if (permobjs[objects[obj].obj_id].poclass == POCLASS_ARMOUR)
+ if (permobjs[objects[obj].po_ref].poclass == POCLASS_ARMOUR)
{
- return permobjs[objects[obj].obj_id].power2;
+ return permobjs[objects[obj].po_ref].power2;
}
return 100;
}
Action_cost magic_ring(void)
{
- Obj *optr = objects + u.ring;
- switch (optr->obj_id)
+ Obj *optr = obj_snapv(u.ring);
+ switch (optr->po_ref)
{
case PO_TELEPORT_RING:
if (u.food >= 50)
}
return Cost_none;
default:
- if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+ if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
{
- debug_unimplemented_activation(optr->obj_id);
+ debug_unimplemented_activation(optr->po_ref);
}
else
{
Action_cost emanate_armour(void)
{
- Obj *optr = objects + u.armour;
+ Obj *optr = obj_snapv(u.armour);
Action_cost cost = Cost_none;
- switch (optr->obj_id)
+ switch (optr->po_ref)
{
case PO_SET_OF_RIBBONS:
if (optr->durability < OBJ_MAX_DUR)
}
break;
default:
- if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+ if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
{
- debug_unimplemented_activation(optr->obj_id);
+ debug_unimplemented_activation(optr->po_ref);
}
else
{
}
break;
}
- return Cost_none;
+ return cost;
}
Action_cost zap_weapon(void)
{
- Obj *optr = objects + u.weapon;
- switch (optr->obj_id)
+ Obj *optr = obj_snapv(u.weapon);
+ int i;
+ for (i = 0; weapon_table[i].pobj != NO_POBJ; ++i)
{
- case PO_STAFF_OF_FIRE:
- if (u.food > 150)
+ if (weapon_table[i].pobj == optr->po_ref)
{
- Coord c;
- u.food -= 150;
- notify_firestaff_activation(1);
- for (c.y = u.pos.y - 1; c.y <= u.pos.y + 1; ++c.y)
+ if (weapon_table[i].zap_func != nullptr)
{
- if ((c.y < lvl.min_y()) || (c.y >= lvl.max_y()))
- {
- continue;
- }
- for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x)
- {
- int mon;
- if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x()))
- {
- continue;
- }
- mon = lvl.mon_at(c);
- if (mon != NO_MON)
- {
- Mon *mptr = monsters + mon;
- if (!pmon_resists_fire(mptr->mon_id))
- {
- notify_fireitem_hit(mon);
- damage_mon(mon, dice(4, 10), true);
- }
- }
- }
+ weapon_table[i].zap_func(u.weapon);
+ return Cost_std;
}
- damage_obj(u.weapon);
- }
- else
- {
- notify_firestaff_activation(0);
- return Cost_none;
- }
- return Cost_std;
- default:
- if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
- {
- debug_unimplemented_activation(optr->obj_id);
- }
- else
- {
- notify_zap_powerless_weapon();
+ else if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
+ {
+ debug_unimplemented_activation(optr->po_ref);
+ return Cost_none;
+ }
+ break;
}
- return Cost_none;
}
+ notify_zap_powerless_weapon();
+ return Cost_none;
}
/*! \brief Unwield the player's currently equipped weapon
return Cost_std;
}
-Action_cost player_wield(int slot, Noisiness noisy)
+Pass_fail role_equip_check(Obj_handle obj, Noisiness noisy)
{
- if (u.weapon != NO_OBJ)
+ Obj const *optr = obj_snap(obj);
+ if ((u.role == Role_demon_hunter) && po_is_demonic(optr->po_ref))
{
- player_unwield(Noise_low);
+ notify_role_blocks_equip();
+ return You_fail;
}
- u.weapon = u.inventory[slot];
- notify_wield_weapon(u.weapon);
- recalc_defence();
- return Cost_std;
+ return You_pass;
}
-Action_cost wear_armour(int slot)
+Action_cost player_wield(Obj_handle obj, Noisiness noisy)
+{
+ Pass_fail rej = role_equip_check(obj, noisy);
+ if (rej == You_pass)
+ {
+ if (u.weapon != NO_OBJ)
+ {
+ player_unwield(Noise_low);
+ }
+ u.weapon = obj;
+ notify_wield_weapon(u.weapon);
+ recalc_defence();
+ return Cost_std;
+ }
+ else
+ {
+ return Cost_none;
+ }
+}
+
+Action_cost wear_armour(Obj_handle obj)
{
- int obj;
if (u.armour != NO_OBJ)
{
debug_wear_while_wearing();
return Cost_none;
}
- obj = u.inventory[slot];
- if (!objects[obj].with_you)
+ if (!(objects[obj].flags & OF_WITH_YOU))
{
debug_wear_uncarried_armour();
return Cost_none;
return Cost_std;
}
-Action_cost put_on_ring(int obj)
+Action_cost put_on_ring(Obj_handle obj)
{
if (u.ring != NO_OBJ)
{
debug_put_on_second_ring();
return Cost_none;
}
- if (!objects[obj].with_you)
+ if (!(objects[obj].flags & OF_WITH_YOU))
{
debug_put_on_uncarried_ring();
return Cost_none;
return Cost_std;
}
-Pass_fail ring_removal_unsafe(Noisiness noisy)
+Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy)
{
- if ((lvl.terrain_at(u.pos) == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING))
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_is_hot(t) &&
+ (!(u.resistances[DT_FIRE] & ~mask)) &&
+ (!(u.flight & ~mask)))
{
if (noisy != Noise_silent)
{
- notify_lava_blocks_unequip();
+ notify_hot_blocks_unequip();
}
return You_fail;
}
- else if ((objects[u.ring].obj_id == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER))
+ else if (terrain_drowns(t) &&
+ (!(u.passwater & ~mask)) &&
+ (!(u.flight & ~mask)))
{
if (noisy != Noise_silent)
{
- notify_water_blocks_unequip();
+ notify_wet_blocks_unequip();
+ }
+ return You_fail;
+ }
+ else if (terrain_gapes(t) &&
+ (!(u.flight & ~mask)))
+ {
+ if (noisy != Noise_silent)
+ {
+ notify_pit_blocks_unequip();
}
return You_fail;
}
return You_pass;
}
+bool corpse_is_blessed(Obj const *optr)
+{
+ return (optr && (optr->po_ref == PO_CORPSE) && (optr->meta[1] == 1));
+}
+
/* objects.cc */
-// vim:cindent
+// vim:cindent:expandtab
/*! \file objects.hh
- * \brief object-related header for Victrix Abyssi
+ * \brief object-related header
*/
-/* Copyright 2005-2013 Martin Read
+/* Copyright 2005-2014 Martin Read
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#include "permobj.hh"
#endif
+#include <map>
/* XXX Obj */
#define OBJ_MAX_DUR 100
#define OBJECTS_IN_PLAY 100
+#define OF_USED 0x00000001u
+#define OF_WITH_YOU 0x00000002u
class Obj {
public:
- int obj_id;
- bool used; /* Entry is occupied. */
- bool with_you; /* Preserved when item DB is reaped on level change. */
+ Obj_handle self;
+ int po_ref;
+ uint32_t flags;
int quan;
Coord pos;
+ uint32_t meta[4]; // Things like what pmon a corpse is
int durability; /* Weapons and armour degrade with use. */
};
-extern Obj objects[OBJECTS_IN_PLAY];
-
-#define NO_OBJ (-1)
-
-/* 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, bool with_you, Coord c);
-extern bool consume_obj(int obj);
-extern int create_obj_class(enum poclass_num pocl, int quantity, bool with_you, Coord c);
-extern void damage_obj(int obj);
-extern int evasion_penalty(int obj);
-
-extern Action_cost drop_obj(int inv_idx);
-extern Action_cost put_on_ring(int obj);
-extern Action_cost remove_ring(void);
-extern Action_cost wear_armour(int obj);
-extern Action_cost take_off_armour(void);
-extern Action_cost read_scroll(int obj);
-extern Action_cost quaff_potion(int obj);
-extern Action_cost eat_food(int obj);
-extern Action_cost magic_ring(void);
-extern Action_cost emanate_armour(void);
-extern Action_cost zap_weapon(void);
-extern Action_cost player_unwield(Noisiness noisy = Noise_std);
-extern Action_cost player_wield(int slot, Noisiness noisy = Noise_std);
-extern void attempt_pickup(void);
-
-extern Pass_fail ring_removal_unsafe(Noisiness noisy = Noise_std);
-extern Pass_fail armour_removal_unsafe(Noisiness noisy = Noise_std);
+extern std::map<Obj_handle, Obj> objects;
+extern Obj_handle first_free_obj_handle;
+
+struct Potion_table_entry
+{
+ int pobj;
+ void (*quaff_func)(Obj_handle obj);
+};
+extern Potion_table_entry potion_table[];
+
+struct Scroll_table_entry
+{
+ int pobj;
+ void (*read_func)(Obj_handle obj);
+};
+
+extern Scroll_table_entry scroll_table[];
+
+struct Weapon_table_entry
+{
+ int pobj;
+ void (*zap_func)(Obj_handle obj);
+ void (*on_wield)(Obj_handle obj);
+ void (*on_unwield)(Obj_handle obj);
+};
+
+extern Weapon_table_entry weapon_table[];
+
+/* XXX objects.cc data and funcs */
+void asprint_obj_name(char **s, Obj_handle obj);
+void sprint_obj_name(char *s, Obj_handle obj, int len);
+void fprint_obj_name(FILE *fp, Obj_handle obj);
+void describe_object(Obj_handle obj);
+Obj_handle create_corpse(int pm_idx, Coord c);
+Obj_handle create_obj_near(int po_idx, int quantity, Coord c);
+Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c);
+bool consume_obj(Obj_handle obj);
+Obj_handle create_obj_class_near(enum poclass_num pocl, int quantity, bool with_you, Coord c);
+void damage_obj(Obj_handle obj);
+int evasion_penalty(Obj_handle obj);
+bool corpse_is_blessed(Obj const *optr);
+
+inline Obj *obj_snapv(Obj_handle obj)
+{
+ auto iter = objects.find(obj);
+ return ((iter == objects.end()) ? nullptr : &(iter->second));
+}
+
+inline Obj const *obj_snap(Obj_handle obj)
+{
+ auto iter = objects.find(obj);
+ return ((iter == objects.end()) ? nullptr : &(iter->second));
+}
+
+Action_cost drop_obj(int inv_idx);
+Action_cost put_on_ring(Obj_handle obj);
+Action_cost remove_ring(void);
+Action_cost wear_armour(Obj_handle obj);
+Action_cost take_off_armour(void);
+Action_cost read_scroll(Obj_handle obj);
+Action_cost quaff_potion(Obj_handle obj);
+Action_cost eat_food(Obj_handle obj);
+Action_cost magic_ring(void);
+Action_cost emanate_armour(void);
+Action_cost zap_weapon(void);
+Action_cost player_unwield(Noisiness noisy = Noise_std);
+Action_cost player_wield(Obj_handle obj, Noisiness noisy = Noise_std);
+void attempt_pickup(void);
+Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy = Noise_std);
#endif
/*! \file permobj.hh
- * \brief permobj-related header for Victrix Abyssi
+ * \brief permobj-related header
*/
/* Copyright 2014 Martin Read
#include "pobj_id.hh"
-#define POBJ_FLAG_WORDS 1
+#define POBJ_FLAG_WORDS 2
// POF field 0
#define POF_NOTIFY_EQUIP 0x00000001u // item has special messages when changing equip status
#define POF_STACKABLE 0x00000004u // item can stack
#define POF_DAMAGEABLE 0x00000008u // track durability
#define POF_BREAK_REACT 0x00000010u // item reacts to breakage attempts
-/* Yes, DRESS and MELEE_WEAPON have the same value. This is OK because
- * only an ARMOUR can possibly be a DRESS, and only a WEAPON can possibly be
- * a MELEE_WEAPON. */
-#define POF_DRESS 0x00010000u
-#define POF_MELEE_WEAPON 0x00010000u
-#define POF_RANGED_WEAPON 0x00020000u
+#define POF_DRESS 0x00010000u
+#define POF_ROBE 0x00020000u
+#define POF_LEATHERY 0x00040000u
+#define POF_MELEE_WEAPON 0x00010000u
+#define POF_RANGED_WEAPON 0x00020000u
+#define POF_SWORD 0x00040000u
+#define POF_POLEARM 0x00080000u
+#define POF_BLUDGEON 0x00100000u
+#define POF_DAGGER 0x00200000u
+#define POF_WHIP 0x00400000u
+#define POF_DEMONIC 0x10000000u
+// POF field 1
+#define POF_RES_FIRE ((uint32_t) DT_FIRE)
+#define POF_RES_COLD ((uint32_t) DT_COLD)
+#define POF_RES_ELEC ((uint32_t) DT_NECRO)
+#define POF_RES_NECRO ((uint32_t) DT_ELEC)
+#define POF_RES_POISON ((uint32_t) DT_POISON)
+#define POF_RES_MASK 0x000000ffu
+#define POF_DMG_FIRE (((uint32_t) DT_FIRE) << POF_DMG_SHIFT)
+#define POF_DMG_COLD (((uint32_t) DT_COLD) << POF_DMG_SHIFT)
+#define POF_DMG_ELEC (((uint32_t) DT_ELEC) << POF_DMG_SHIFT)
+#define POF_DMG_NECRO (((uint32_t) DT_NECRO) << POF_DMG_SHIFT)
+#define POF_DMG_POISON (((uint32_t) DT_POISON) << POF_DMG_SHIFT)
+#define POF_DMG_SHIFT 8
+#define POF_DMG_MASK (0xffu << POF_DMG_SHIFT)
+#define POF_PASS_WATER 0x00010000u
+#define POF_FLIGHT 0x00020000u
+#define POF_PROTECTIVE 0x00100000u // all damage from enemy attacks halved
+#define POF_SPEED 0x01000000u // bumps you up one speed band
/*! \brief The 'permanent object' database */
class Permobj {
};
#define NO_POBJ (-1)
-extern Permobj permobjs[NUM_OF_PERMOBJS];
-extern bool po_is_stackable(int po);
+extern const int NUM_OF_PERMOBJS;
+extern Permobj permobjs[];
+
+bool po_is_stackable(int po);
+bool po_is_dress(int po);
+bool po_is_leatherwear(int po);
+bool po_is_robe(int po);
+bool po_is_sword(int po);
+bool po_is_dagger(int po);
+bool po_is_polearm(int po);
+bool po_is_bludgeon(int po);
+bool po_is_demonic(int po);
+Damtyp po_resistance(int po);
+Damtyp po_damage_amp(int po);
+bool po_grants_flight(int po);
+bool po_grants_passwater(int po);
+bool po_grants_protective(int po);
+bool po_grants_speed(int po);
#endif
/* permobj.hh */
-// vim:cindent
-
+// vim:cindent:expandtab
#define PMF_QUADRUPED 0x10000000
#define PMF_SKITTERISH 0x20000000
#define PMF_HUGE 0x40000000
-#define PMF_SMALL 0x80000000
+#define PMF_SMALL 0x80000000u
+
+#define PMF_MADE_OF_MEAT 0x00000001u
+#define PMF_MADE_OF_GOO 0x00000002u
+#define PMF_MADE_OF_BONE 0x00000004u
+#define PMF_MADE_OF_METAL 0x00000008u
+#define PMF_MADE_OF_ICE 0x00000010u
+#define PMF_MADE_OF_FIRE 0x00000020u
+#define PMF_CONTAINS_BLOOD 0x00010000u
+#define PMF_CONTAINS_PUS 0x00020000u
+#define PMF_BURSTS_ON_DEATH 0x40000000u
+#define PMF_LEAVES_CORPSE 0x80000000u
#define PERMON_FLAG_FIELDS 2
/*! \brief Describes the baseline stats of a specific kind of monster */
int speed; /* 0 = slow; 1 = normal; 2 = quick */
uint32_t flags[PERMON_FLAG_FIELDS]; /* resistances, AI settings, etc. */
};
-extern struct Permon permons[NUM_OF_PERMONS];
+extern struct Permon permons[];
+extern int const NUM_OF_PERMONS;
#define NO_ATK (-1)
#define NO_PMON (-1)
AI_charger, AI_archer, AI_dodger, AI_drunk, AI_seeker, AI_chaser
};
-/* 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_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);
-extern bool pmon_resists_drowning(int pm);
-extern bool pmon_resists_knockback(int pm);
-
-extern bool pmon_can_fly(int pm);
-
-extern bool pmon_is_ethereal(int pm);
-extern bool pmon_is_undead(int pm);
-extern bool pmon_is_demonic(int pm);
-
-extern bool pmon_has_hands(int pm);
-extern bool pmon_is_humanoid(int pm);
-extern bool pmon_is_quadruped(int pm);
-extern bool pmon_is_skitterish(int pm);
-extern bool pmon_is_amorphous(int pm);
-extern bool pmon_is_huge(int pm);
-extern bool pmon_is_small(int pm);
+bool pmon_is_archer(int pm);
+bool pmon_is_magician(int pm);
+bool pmon_is_smart(int pm);
+bool pmon_is_stupid(int pm);
+
+bool pmon_resists_cold(int pm);
+bool pmon_resists_fire(int pm);
+bool pmon_resists_poison(int pm);
+bool pmon_resists_necro(int pm);
+bool pmon_resists_elec(int pm);
+bool pmon_resists_drowning(int pm);
+bool pmon_resists_knockback(int pm);
+
+bool pmon_can_fly(int pm);
+
+bool pmon_is_ethereal(int pm);
+bool pmon_is_undead(int pm);
+bool pmon_is_demonic(int pm);
+
+bool pmon_has_hands(int pm);
+bool pmon_is_humanoid(int pm);
+bool pmon_is_quadruped(int pm);
+bool pmon_is_skitterish(int pm);
+bool pmon_is_amorphous(int pm);
+bool pmon_is_huge(int pm);
+bool pmon_is_small(int pm);
+
+inline bool pmon_bleeds(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_BLOOD; }
+inline bool pmon_suppurates(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_PUS; }
+inline bool pmon_explodes(int pm) { return permons[pm].flags[1] & PMF_BURSTS_ON_DEATH; }
+inline bool pmon_leaves_corpse(int pm) { return permons[pm].flags[1] & PMF_LEAVES_CORPSE; }
+Decal_tag pmon_fluid_decal(int pm);
#endif
/*! \file player.hh
- * \brief Player character header for Victrix Abyssi
+ * \brief Player character header
*/
/* Copyright 2014 Martin Read
#ifndef PLAYER_HH
#define PLAYER_HH
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+enum Skill_id
+{
+ NO_SKILL = -1,
+ Skill_power_attack = 0,
+ /* Princess skills */
+ Skill_blood_royal,
+ Skill_flying_leap,
+ Skill_regal_radiance,
+ /* Demon Hunter skills */
+ Skill_charge,
+ Skill_blade_dance,
+ Skill_demon_slayer,
+ /* Thanatophile skills */
+ Skill_deaths_grace,
+ Skill_deaths_vengeance,
+ Skill_sanctified_by_death
+};
+
+#define LAST_SKILL (Skill_sanctified_by_death)
+#define NUM_SKILLS (1 + LAST_SKILL)
+
+/*! \brief Skill descriptor */
+struct Skill_desc
+{
+ char const *name;
+ char const *desc;
+};
+
+extern Skill_desc skill_props[NUM_SKILLS];
+
+struct Skill_gain_table
+{
+ int level;
+ Skill_id skill;
+};
+
+extern Skill_gain_table universal_skills[];
+extern Skill_gain_table *skill_tables[NUM_ROLES];
/*! \brief Internal representation of the player character
*/
+#define INVENTORY_SIZE 19
class Player {
public:
char name[17]; //!< Allows 16 actual characters, plus 0 terminator
+ Mon_handle mh; //!< Handle for monster representing the player.
+ Mon *mptr; //!< Pointer for monster representing the player.
Coord pos; //!< Position within current dungeon level.
+ Role_id role; //!< Starting "job"; hypothetically affects gains
int body; //!< Combined stamina and strength stat.
int bdam; //!< Current level of temporary Body drain
int agility; //!< Combined accuracy and avoidance stat.
int withering; //!< Vile withering curse.
int armourmelt; //!< Armour-like-dust curse.
int speed; //!< Controls how often you act.
- uint32_t resistances[DT_COUNT]; //!< Resistance masks per damage type
+ uint32_t resistances[NUM_DAMTYPES]; //!< Resistance masks per damage type
+ uint32_t damage_amp[NUM_DAMTYPES]; //!< Damage amplification masks per damage type
+ uint32_t passwater; //!< Can currently walk on water tiles
+ uint32_t flight; //!< Ignore all hazardfloors and pits
+ uint32_t protective_gear; //!< Reduce all damage taken
int level; //!< Current experience level.
- int inventory[19]; //!< Object handles of currently carried items.
- int weapon; //!< Object handle of currently equipped weapon.
- int armour; //!< Object handle of currently equipped armour.
- int ring; //!< Object handle of currently equipped ring.
+ Obj_handle inventory[INVENTORY_SIZE]; //!< currently carried items.
+ Obj_handle weapon; //!< currently equipped weapon.
+ Obj_handle armour; //!< currently equipped armour.
+ Obj_handle ring; //!< currently equipped ring.
int sympathy[TOTAL_FELL_POWERS]; //!< Level of alignment with fell powers
+ bool known_skill[NUM_SKILLS]; //!<
/* Methods only after here plzkthx */
bool resists(Damtyp dtype) const; //!< Does player resist this Damtyp?
bool martial(void) const //!< Is player significantly influenced by iron?
#define SLOT_CANCEL (-1)
#define SLOT_NOTHING (-2)
-/* XXX u.c data and funcs */
extern Player u;
-void u_init(char const *name);
+typedef Action_cost (*Deed_func)(Action const *act);
+
+extern Deed_func deed_funcs[NUM_COMMANDS];
+
+typedef void (*Role_inventory_func)(Player *p);
+extern Role_inventory_func role_inventories[NUM_ROLES];
+
+void u_init(char const *name, Role_id role);
void write_char_dump(void);
int do_death(Death d, char const *what);
void heal_u(int amount, int boost, int loud);
inline bool empty_handed(void) { return u.weapon == NO_OBJ; }
bool wielding_melee_weapon(void);
bool wielding_ranged_weapon(void);
+Action_cost do_player_action(Action *act);
void player_cleanup(void);
+
+typedef bool (*Action_rewriter_phase1_func)(Action *revisable_action);
+extern Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS];
+extern Combo_phase action_default_combophase[NUM_COMMANDS];
+
+typedef Combo_state (*Combo_detector)(std::deque<Action>* action_list);
+typedef bool (*Combo_avail_func)(Player const *player);
+
+struct Combo_entry
+{
+ char const *name;
+ Game_cmd first_step; //!< if this move isn't present, don't call detector()
+ Combo_avail_func avail;
+ Combo_detector detector;
+};
+
+extern Combo_entry combo_entries[];
+
+/*! \brief ID numbers for skills */
#endif
/* player.hh */
*/
#define PMON2_CC
-#include "victrix-abyssi.hh"
+#include "core.hh"
#include "monsters.hh"
bool pmon_resists_fire(int pm)
return !!(permons[pm].flags[0] & PMF_UNDEAD);
}
+bool pmon_is_demonic(int pm)
+{
+ return !!(permons[pm].flags[0] & PMF_DEMONIC);
+}
+
bool pmon_is_stupid(int pm)
{
return !!(permons[pm].flags[0] & PMF_STUPID);
return !!(permons[pm].flags[0] & PMF_ETHEREAL);
}
+Decal_tag pmon_fluid_decal(int pm)
+{
+ if (permons[pm].flags[1] & PMF_CONTAINS_BLOOD)
+ {
+ return Decal_blood;
+ }
+ if (permons[pm].flags[1] & PMF_CONTAINS_PUS)
+ {
+ return Decal_pus;
+ }
+ return NO_DECAL;
+}
+
/* pmon2.c */
// vim:cindent
our @monsters;
+our $max_flag_index = 1;
+
our %flag_indices =
(
'RESIST_FIRE' => 0,
'SKITTERISH' => 0,
'HUGE' => 0,
'SMALL' => 0,
+ 'MADE_OF_MEAT' => 1,
+ 'MADE_OF_GOO' => 1,
+ 'MADE_OF_BONE' => 1,
+ 'MADE_OF_METAL' => 1,
+ 'MADE_OF_ICE' => 1,
+ 'MADE_OF_FIRE' => 1,
+ 'CONTAINS_BLOOD' => 1,
+ 'CONTAINS_PUS' => 1,
+ 'BURSTS_ON_DEATH' => 1,
+ 'LEAVES_CORPSE' => 1,
);
else
{
my $name;
+ my $i;
+ for ($i = 0; $i <= $max_flag_index; ++$i)
+ {
+ $#flag_fields = $i if ($i > $#flag_fields);
+ if (!defined($flag_fields[$i]))
+ {
+ $flag_fields[$i] = "0 ";
+ }
+ }
for $name (@$aref)
{
die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name});
my $idx = $flag_indices{$name};
- $#flag_fields = $idx if ($idx > $#flag_fields);
- if (!defined($flag_fields[$idx]))
- {
- $flag_fields[$idx] = "0 ";
- }
$flag_fields[$idx] .= "| PMF_$name ";
}
}
open(HEADERFILE, ">", "pmon_id.hh") or die "pmon_comp: could not open pmon_id.hh for write: $!";
open(SOURCEFILE, ">", "permons.cc") or die "pmon_comp: could not open permons.cc for write: $!";
print HEADERFILE "// pmon_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pmon_comp to regenerate this file and permons.cc\n#pragma once\nenum Pmon_id {\n";
-print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[NUM_OF_PERMONS] = {\n";
+print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[] = {\n";
my $phref;
my $i;
my $total_mons = 0;
-my @firsts;
my $tagname;
for ($i = 0; $i <= $#monsters; ++$i, ++$total_mons)
printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", %s, %s, Gcol_%s, %d, %d, %d, %d, %d, %d, %d, DT_%s, \"%s\", %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{rarity}, $phref->{power}, $phref->{hp}, $phref->{mtohit}, $phref->{rtohit}, $phref->{mdam}, $phref->{rdam}, $phref->{rdtyp}, $phref->{shootverb}, $phref->{defence}, $phref->{exp}, $phref->{speed}, flag_string($phref->{flags});
}
-print SOURCEFILE "};\n// permons.cc\n";
-printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMONS %d\n\n// pmon_id.hh\n", $total_mons;
+printf SOURCEFILE "};\nconst int NUM_OF_PERMONS = %d;\n// permons.cc\n", $total_mons;
+printf HEADERFILE "};\n\n// pmon_id.hh\n";
close(SOURCEFILE);
close(HEADERFILE);
our @food;
our @carrion;
+our $max_flag_index = 1;
our %flag_indices =
(
'NOTIFY_EQUIP' => 0,
'DRESS' => 0,
'RANGED_WEAPON' => 0,
'MELEE_WEAPON' => 0,
+ 'SWORD' => 0,
+ 'DAGGER' => 0,
+ 'BLUDGEON' => 0,
+ 'POLEARM' => 0,
+ 'WHIP' => 0,
+ 'DEMONIC' => 0,
+ 'ROBE' => 0,
+ 'LEATHERY' => 0,
+ 'RES_FIRE' => 1,
+ 'RES_COLD' => 1,
+ 'RES_ELEC' => 1,
+ 'RES_NECRO' => 1,
+ 'RES_POISON' => 1,
+ 'DMG_FIRE' => 1,
+ 'DMG_COLD' => 1,
+ 'DMG_ELEC' => 1,
+ 'DMG_NECRO' => 1,
+ 'DMG_POISON' => 1,
+ 'PASS_WATER' => 1,
+ 'FLIGHT' => 1,
+ 'PROTECTIVE' => 1,
+ 'SPEED' => 1
);
else
{
my $name;
+ $#flag_fields = $max_flag_index;
+ for (my $i = 0; $i <= $max_flag_index; ++$i)
+ {
+ $flag_fields[$i] = "0 ";
+ }
for $name (@$aref)
{
die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name});
my $idx = $flag_indices{$name};
- $#flag_fields = $idx if ($idx > $#flag_fields);
- if (!defined($flag_fields[$idx]))
- {
- $flag_fields[$idx] = "0 ";
- }
$flag_fields[$idx] .= "| POF_$name ";
}
}
open(HEADERFILE, ">", "pobj_id.hh") or die "pobj_comp: could not open pobj_id.hh for write: $!";
open(SOURCEFILE, ">", "permobj.cc") or die "pobj_comp: could not open permobj.cc for write: $!";
print HEADERFILE "// pobj_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pobj_comp to regenerate this file and permobj.cc\n#pragma once\nenum Pobj_id {\n";
-print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[NUM_OF_PERMOBJS] = {\n";
+print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[] = {\n";
my $phref;
my $i;
my $total_objs = 0;
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
undef $tagname;
}
-print SOURCEFILE "};\n// permobj.cc\n";
-printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMOBJS %d\n\n// pobj_id.hh\n", $total_objs;
+printf SOURCEFILE "};\nconst int NUM_OF_PERMOBJS = %d;\n// permobj.cc\n", ${total_objs};
+print HEADERFILE "};\n".join('', @firsts)."\n\n// pobj_id.hh\n";
close(SOURCEFILE);
close(HEADERFILE);
/*! \file rng.hh
- * \brief RNG-related header for Victrix Abyssi
+ * \brief RNG-related header
*/
/* Copyright 2014 Martin Read
--- /dev/null
+/*! \file role.cc
+ * \brief Per-role layer-character initialization/advancement
+ */
+
+/*
+ * Copyright 2014 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 "core.hh"
+#include "player.hh"
+#include "monsters.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+static void princess_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+static void demonhunter_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+static void thanatophile_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+Role_inventory_func role_inventories[NUM_ROLES] =
+{
+ princess_inventory,
+ demonhunter_inventory,
+ thanatophile_inventory
+};
+
+/* role.cc */
+// vim:cindent:expandtab
};
/*! \brief Construction helper for shrines */
-void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall)
+void place_shrine(Level *l, Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall)
{
Coord c;
Coord start;
switch (sh->grid[shy][shx])
{
case '0':
- lvl.set_terrain_at(c, shfloor);
+ l->set_terrain_at(c, shfloor);
/* We want to be able to slap this down as a room, which means
* that at least one square on the connectable edges must not
* be HARDWALL. Those squares are marked '0'. */
break;
case '1':
- lvl.set_terrain_at(c, margin_is_wall ? shwall : shfloor);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, margin_is_wall ? shwall : shfloor);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case '.':
- lvl.set_terrain_at(c, shfloor);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, shfloor);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case '#':
- lvl.set_terrain_at(c, shwall);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, shwall);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case '+':
- lvl.set_terrain_at(c, DOOR);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, DOOR);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case '_':
- lvl.set_terrain_at(c, ALTAR);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, ALTAR);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case 'L':
- lvl.set_terrain_at(c, LAVA);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, LAVA);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
case 'W':
- lvl.set_terrain_at(c, WATER);
- lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+ l->set_terrain_at(c, WATER);
+ l->set_flags_at(c, MAPFLAG_HARDWALL);
break;
}
}
}
/*! \brief Excavate a cave-with-shrine level. */
-void build_level_shrine(void)
+void build_level_shrine(Level *l)
{
Coord shrinepos = { inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2), inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2) };
Coord c;
int intrusions;
int i;
- initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+ initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
intrusions = dice(2, 4) - 1;
for (i = 0; i < 6; ++i)
{
- place_random_intrusion(WALL);
+ place_random_intrusion(l, WALL);
}
switch (zero_die(4))
{
c3 = c + North;
mask = ROOMCONN_EAST;
}
- place_shrine(shrinepos, shrines + shrine_num, mask);
- lvl.set_terrain_at(c, FLOOR);
- lvl.set_terrain_at(c2, FLOOR);
- lvl.set_terrain_at(c3, FLOOR);
- run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
- place_cave_stairs();
+ place_shrine(l, shrinepos, shrines + shrine_num, mask);
+ l->set_terrain_at(c, FLOOR);
+ l->set_terrain_at(c2, FLOOR);
+ l->set_terrain_at(c3, FLOOR);
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ place_cave_stairs(l);
}
/* shrine.cc */
--- /dev/null
+/*! \file skills.cc
+ * \brief Player-character skill data
+ */
+
+/*
+ * Copyright © 2014 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.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include "player.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/*! \brief Descriptions of the player character's possible skills */
+Skill_desc skill_props[NUM_SKILLS] =
+{
+ {
+ "Power Attack",
+ ("On the turn after you use the 'wait' command, any melee attack you "
+ "make will hit automatically and do 75 percent more damage than "
+ "normal."),
+ },
+ {
+ "Blood Royal",
+ ("The stress of your banishment has partially unlocked the power of "
+ "your bloodline, allowing you to make full use of certain magical "
+ "items that would be ineffective, or less effective, for others."),
+ },
+ {
+ "Flying Leap",
+ ("While wearing a dress, robe, or leather armour, you can jump across "
+ "any one-square environmental hazard (lava, chasms, etc.) that has "
+ "clear space on the other side by attempting to move onto it. This "
+ "ability has no effect if you have equipped an item allowing you to "
+ "fly."),
+ },
+ {
+ "Regal Radiance",
+ ("If you are wearing an imperatrix gown, you can use the talismans "
+ "embroidered into it to stun nearby enemies for five turns using "
+ "the (E)manate command, at a cost of 100 points of food. Stunned "
+ "enemies cannot take any action against you."),
+ },
+ {
+ "Charge",
+ ("If you move in the same direction on two (or more) consecutive "
+ "turns, any melee attack you make in that direction on the "
+ "immediately following turn will hit automatically and do more "
+ "damage than usual."),
+ },
+ {
+ "Blade Dance",
+ ("While wielding a sword or dagger, if you move diagonally past an "
+ "enemy, you will make a free melee attack against that enemy; this "
+ "attack will have normal accuracy and damage. Moving diagonally "
+ "between two enemies will randomly select one of them to be the "
+ "victim of this attack."),
+ },
+ {
+ "Demon Slayer",
+ ("Your instincts for dealing with demonkind have developed to the "
+ "point that your melee attacks do double damage against demons."),
+ },
+ {
+ "Death's Grace",
+ ("Beloved Death has granted you protection from the malign arts of "
+ "necromancers, making you immune to the necromantic damage type. "
+ "This does not make you immune to effects such as the 'leadfoot' "
+ "curse."),
+ },
+ {
+ "Death's Vengeance",
+ ("Your melee attacks are imbued with the blessing of beloved Death, "
+ "causing them to do double damage against the undead."),
+ },
+ {
+ "Sanctified by Death",
+ ("Each turn, all corpses adjacent to you are purified by beloved "
+ "Death's holy touch, making them impossible to reanimate as zombies. "
+ "This ability has no effect on existing zombies."),
+ },
+};
+
+Skill_gain_table universal_skills[] = {
+ { 2, Skill_power_attack },
+ { 0, NO_SKILL }
+};
+
+static Skill_gain_table princess_skills[] = {
+ { 2, Skill_flying_leap },
+ //{ 4, Skill_blood_royal },
+ //{ 10, Skill_regal_radiance },
+ { 0, NO_SKILL }
+};
+
+static Skill_gain_table demon_hunter_skills[] = {
+ { 2, Skill_charge },
+ { 4, Skill_demon_slayer },
+ { 6, Skill_blade_dance },
+ { 0, NO_SKILL }
+};
+
+static Skill_gain_table thanatophile_skills[] = {
+ { 2, Skill_deaths_grace },
+ { 4, Skill_deaths_vengeance },
+ { 6, Skill_sanctified_by_death },
+ { 0, NO_SKILL }
+};
+
+Skill_gain_table *skill_tables[NUM_ROLES] = {
+ princess_skills,
+ demon_hunter_skills,
+ thanatophile_skills
+};
+
+/* skills.cc */
+// vim:cindent:expandtab
* \brief evil magic used by monsters
*/
-/* Copyright 2005-2013 Martin Read
+/* Copyright © 2005-2014 Martin Read
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#include "victrix-abyssi.hh"
#include "sorcery.hh"
#include "monsters.hh"
+#include "objects.hh"
#include "combat.hh"
/* SORCERY
* with high enough agility.
*/
-int mon_use_sorcery(int mon)
+typedef void (*Monspell_cast_func)(Mon_handle mon);
+
+static void cast_strike_staff(Mon_handle mon)
{
- /* Returns zero for no spell selected, -1 for unsupported spell
- * selected, 1 for supported spell selected. */
- Mon *mptr = monsters + mon;
- Offset delta = u.pos.delta(mptr->pos);
- Offset step = mysign(delta);
- enum monspell to_cast = MS_REJECT;
- int rval = 1; /* Default to success; failure paths will force this
- * to an appropriate value. */
- int range = delta.len_cheb();
- bool meleerange = (range < 2);
- bool oncardinal = delta.rcardinal();
- int dieroll;
- bool cansee = mon_visible(mon);
- int i;
+ mhitu(mon, DT_PHYS);
+}
+
+static void cast_necro_staff(Mon_handle mon)
+{
+ mhitu(mon, DT_NECRO);
+}
+
+static void cast_chilling_touch(Mon_handle mon)
+{
+ mhitu(mon, DT_COLD);
+}
+
+static void cast_lightning_bolt(Mon_handle mon)
+{
+ mshootu(mon);
+}
- switch (monsters[mon].mon_id)
+static void cast_necro_bolt(Mon_handle mon)
+{
+ mshootu(mon);
+}
+
+static void cast_necro_smite(Mon_handle mon)
+{
+ notify_monster_cursing(mon);
+ if (u.resists(DT_NECRO))
+ {
+ notify_necrosmite_fail();
+ }
+ else
{
- 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
- * sorcery to relocate us to the player's location.
- */
- to_cast = MS_TELEPORT_ASSAULT;
- }
- break;
+ damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+ }
+}
- 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 sorcery. */
- to_cast = MS_TELEPORT_ASSAULT;
- }
- break;
+static void cast_fire_column(Mon_handle mon)
+{
+ notify_monster_cursing(mon);
+ notify_hellfire_hit(u.resists(DT_FIRE));
+ if (u.resists(DT_FIRE))
+ {
+ damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+ }
+ else
+ {
+ damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+ }
+}
+
+static void cast_teleport_escape(Mon_handle mon)
+{
+ teleport_mon(mon);
+ notify_mon_disappear(mon);
+}
+
+static void cast_teleport_summon(Mon_handle mon)
+{
+ Mon *mptr = mon_snapv(mon);
+ int i = summoning(mptr->pos, dice(2, 3));
+ mptr->next_summon = game_tick + 1000;
+ notify_summon_help(mon, (i > 0));
+ teleport_mon(mon);
+ notify_mon_disappear(mon);
+}
- 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 5:
- if (!u.leadfoot)
- {
- to_cast = MS_CURSE_LEADFOOT;
- break;
- }
- /* fall through */
- case 4:
- 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 sorcery. */
- 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 || !zero_die(3))
- {
- // 1-in-7 chance of cursing you when in melee range
- // 3-in-7 chance when out of melee range
- 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_REJECT;
- break;
- }
- }
- }
- break;
+static void cast_teleport_assault(Mon_handle mon)
+{
+ teleport_mon_to_you(mon);
+}
- default:
- break;
+static void cast_withering(Mon_handle mon)
+{
+ notify_monster_cursing(mon);
+ if (u.protection)
+ {
+ notify_moncurse_fail();
}
- switch (to_cast)
+ else
{
- default:
- /* If this happens, we're trying to cast an unimplemented
- * spell. */
- debug_bad_monspell(to_cast);
- rval = -1;
- break;
+ u.withering = 10 + one_die(10);
+ recalc_defence();
+ notify_start_withering();
+ }
+}
+
+static void cast_leadfoot(Mon_handle mon)
+{
+ notify_monster_cursing(mon);
+ if (u.protection)
+ {
+ notify_moncurse_fail();
+ }
+ else
+ {
+ u.leadfoot = 10 + one_die(10);
+ recalc_defence();
+ notify_start_leadfoot();
+ }
+}
+
+static void cast_armourmelt(Mon_handle mon)
+{
+ notify_monster_cursing(mon);
+ if (u.protection)
+ {
+ notify_moncurse_fail();
+ }
+ else
+ {
+ u.armourmelt = 10 + one_die(10);
+ recalc_defence();
+ notify_start_armourmelt();
+ }
+}
- case MS_REJECT:
- /* No usable spell available. */
- rval = 0;
- break;
+static bool reanimation_effector(Coord c, void *pvt)
+{
+ Obj_handle o = lvl.obj_at(c);
+ Mon_handle m = lvl.mon_at(c);
+ if (m == NO_MON)
+ {
+ Obj const *optr = obj_snap(o);
+ if (!corpse_is_blessed(optr))
+ {
+ m = create_mon(PM_ZOMBIE, c);
+ if (m != NO_MON)
+ {
+ consume_obj(o);
+ return true;
+ }
+ }
+ }
+ return false;
+}
- case MS_STRIKE_STAFF:
- mhitu(mon, DT_PHYS);
- break;
+static Radiance reanimate_radiance =
+{
+ {},
+ Nowhere,
+ MAX_FOV_RADIUS,
+ Reo_spiral_out,
+ false,
+ nullptr,
+ dflt_blk,
+ reanimation_effector
+};
- case MS_NECRO_STAFF:
- mhitu(mon, DT_NECRO);
- break;
+static void cast_animate_dead(Mon_handle mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ reanimate_radiance.clear();
+ reanimate_radiance.centre = mptr->pos;
+ reanimate_radiance.compute();
+ reanimate_radiance.resolve();
+}
- case MS_CHILLING_TOUCH:
- mhitu(mon, DT_COLD);
- break;
+Monspell_cast_func sorcery_funcs[NUM_MONSPELLS] =
+{
+ cast_strike_staff,
+ cast_necro_staff,
+ cast_chilling_touch,
+ cast_lightning_bolt,
+ cast_necro_bolt,
+ cast_necro_smite,
+ cast_fire_column,
+ cast_armourmelt,
+ cast_leadfoot,
+ cast_withering,
+ cast_teleport_escape,
+ cast_teleport_summon,
+ cast_teleport_assault,
+ cast_animate_dead
+};
- case MS_LIGHTNING:
- case MS_NECRO_BOLT:
- mshootu(mon);
- break;
+typedef Monspell (*Monspell_select_func)(int mon);
+struct Monspell_selector
+{
+ int pm;
+ Monspell_select_func func;
+};
- case MS_TELEPORT_AND_SUMMON:
- mptr->next_summon = game_tick + 1000;
- /* (Try to) summon 2-6 monsters. */
- i = summoning(mptr->pos, dice(2, 3));
- notify_summon_help(mon, (i > 0));
- /* ... and fall through. */
- case MS_TELEPORT_ESCAPE:
- teleport_mon(mon);
- notify_mon_disappear(mon);
- break;
+Monspell wizard_spell_select(int mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ Offset delta = u.pos.delta(mptr->pos);
+ if (mon_visible(mon))
+ {
+ if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+ {
+ return MS_TELEPORT_ESCAPE;
+ }
+ else if ((delta.len_cheb() == 1) && (zero_die(10) > 2))
+ {
+ return MS_STRIKE_STAFF;
+ }
+ else if (delta.rcardinal())
+ {
+ return 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 sorcery. */
+ return MS_TELEPORT_ASSAULT;
+ }
+ return MS_REJECT;
+}
- case MS_TELEPORT_ASSAULT:
- /* It is rare that a monster will cast this spell, but not
- * unheard of. */
- teleport_mon_to_you(mon);
- break;
+Monspell archmage_spell_select(int mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ Offset delta = u.pos.delta(mptr->pos);
+ if (mon_visible(mon))
+ {
+ /* We have LOS; choose a spell on that basis. */
+ if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+ {
+ return zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+ }
+ else if ((delta.len_cheb() == 1) && (zero_die(10) > 3))
+ {
+ return MS_STRIKE_STAFF;
+ }
+ else if (delta.rcardinal())
+ {
+ return MS_LIGHTNING;
+ }
+ }
+ else if (!zero_die(40))
+ {
+ /*
+ * We lack LOS, but pass the 1-in-40 chance; use
+ * sorcery to relocate us to the player's location.
+ */
+ return MS_TELEPORT_ASSAULT;
+ }
+ return MS_REJECT;
+}
- case MS_CURSE_ARMOURMELT:
- notify_monster_cursing(mon);
- if (u.protection)
- {
- notify_moncurse_fail();
- }
- else
- {
- u.armourmelt = 10 + one_die(10);
- recalc_defence();
- notify_start_armourmelt();
- }
- break;
+Monspell lich_spell_select(int mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ Offset delta = u.pos.delta(mptr->pos);
+ int range = delta.len_cheb();
+ if (mon_visible(mon))
+ {
+ if (range == 1)
+ {
+ switch (zero_die(6))
+ {
+ case 3:
+ return MS_ANIMATE_DEAD;
+ case 4:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 5:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ return MS_NECRO_STAFF;
+ }
+ }
+ else if (delta.rcardinal())
+ {
+ if (range < 3)
+ {
+ switch (zero_die(6))
+ {
+ case 3:
+ return MS_ANIMATE_DEAD;
+ case 4:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 5:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ return MS_NECRO_BOLT;
+ }
+ }
+ else
+ {
+ return MS_NECRO_BOLT;
+ }
+ }
+ }
+ return MS_REJECT;
+}
- case MS_CURSE_LEADFOOT:
- notify_monster_cursing(mon);
- if (u.protection)
- {
- notify_moncurse_fail();
- }
- else
- {
- u.leadfoot = 10 + one_die(10);
- recalc_defence();
- notify_start_leadfoot();
- }
- break;
+Monspell master_lich_spell_select(int mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ Offset delta = u.pos.delta(mptr->pos);
+ int range = delta.len_cheb();
+ if (mon_visible(mon))
+ {
+ if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 4))
+ {
+ return ((mptr->next_summon < game_tick) || !zero_die(3)) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+ }
+ else if (range == 1)
+ {
+ switch (zero_die(7))
+ {
+ case 3:
+ return MS_ANIMATE_DEAD;
+ case 6:
+ if (!u.withering)
+ {
+ return MS_CURSE_WITHERING;
+ }
+ case 5:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 4:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ return zero_die(2) ? MS_CHILLING_TOUCH : MS_STRIKE_STAFF;
+ }
+ }
+ else if (range < 3)
+ {
+ switch (zero_die(10))
+ {
+ case 6:
+ return MS_ANIMATE_DEAD;
+ case 9:
+ if (!u.withering)
+ {
+ return MS_CURSE_WITHERING;
+ }
+ case 8:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 7:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ return MS_NECRO_SMITE;
+ }
+ }
+ else if (range < 8)
+ {
+ switch (zero_die(7))
+ {
+ case 3:
+ return MS_ANIMATE_DEAD;
+ case 6:
+ if (!u.withering)
+ {
+ return MS_CURSE_WITHERING;
+ }
+ /* fall through */
+ case 4:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 5:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ return MS_NECRO_SMITE;
+ }
+ }
+ }
+ else if (!zero_die(40))
+ {
+ /* we lack LOS, but passed the 1-in-40 chance to
+ * close with the player by means of sorcery. */
+ return MS_TELEPORT_ASSAULT;
+ }
+ return MS_REJECT;
+}
- case MS_CURSE_WITHERING:
- notify_monster_cursing(mon);
- if (u.protection)
- {
- notify_moncurse_fail();
- }
- else
- {
- u.withering = 10 + one_die(10);
- recalc_defence();
- notify_start_withering();
- }
- break;
+Monspell defiler_spell_select(int mon)
+{
+ Mon const *mptr = mon_snap(mon);
+ Offset delta = u.pos.delta(mptr->pos);
+ if (mon_visible(mon))
+ {
+ if (!(delta.len_cheb() != 1) || !zero_die(3))
+ {
+ // 1-in-7 chance of cursing you when in melee range
+ // 3-in-7 chance when out of melee range
+ switch (zero_die(7))
+ {
+ case 6:
+ if (!u.withering)
+ {
+ return MS_CURSE_WITHERING;
+ }
+ case 4:
+ if (!u.leadfoot)
+ {
+ return MS_CURSE_LEADFOOT;
+ }
+ /* fall through */
+ case 5:
+ if (!u.armourmelt)
+ {
+ return MS_CURSE_ARMOURMELT;
+ }
+ /* fall through */
+ default:
+ break;
+ }
+ }
+ }
+ return MS_REJECT;
+}
- case MS_NECRO_SMITE:
- notify_monster_cursing(mon);
- if (u.resists(DT_NECRO))
- {
- notify_necrosmite_fail();
- }
- else
- {
- damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
- }
- break;
+Monspell_selector monspell_selectors[] =
+{
+ { PM_WIZARD, wizard_spell_select },
+ { PM_ARCHMAGE, archmage_spell_select },
+ { PM_LICH, lich_spell_select },
+ { PM_MASTER_LICH, master_lich_spell_select },
+ { PM_DEFILER, defiler_spell_select },
+ { NO_PMON, nullptr }
+};
- case MS_FIRE_COLUMN:
- notify_monster_cursing(mon);
- notify_hellfire_hit(u.resists(DT_FIRE));
- if (u.resists(DT_FIRE))
- {
- damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
- }
- else
- {
- damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
- }
- break;
+int mon_use_sorcery(int mon)
+{
+ /* Returns zero for no spell selected, -1 for unsupported spell
+ * selected, 1 for supported spell selected. */
+ Mon const *mptr = mon_snap(mon);
+ Monspell to_cast = MS_REJECT;
+ int rval;
+ for (int i = 0; monspell_selectors[i].func; ++i)
+ {
+ if (monspell_selectors[i].pm == mptr->pm_ref)
+ {
+ to_cast = monspell_selectors[i].func(mon);
+ }
+ }
+ if (to_cast == MS_REJECT)
+ {
+ rval = 0;
+ }
+ else if (!sorcery_funcs[to_cast])
+ {
+ debug_bad_monspell(to_cast);
+ rval = -1;
+ }
+ else
+ {
+ sorcery_funcs[to_cast](mon);
+ rval = 1;
}
return rval;
}
/* XXX DATA TYPES XXX */
-enum monspell {
+enum Monspell {
MS_REJECT = -1, /* Rejection tag. */
/* "Melee" attacks */
MS_STRIKE_STAFF, /* Wizard */
MS_TELEPORT_ESCAPE, /* Wizard, Archmage, Master Lich */
MS_TELEPORT_AND_SUMMON, /* Archmage */
MS_TELEPORT_ASSAULT, /* Wizard, Archmage, Master Lich */
+ MS_ANIMATE_DEAD, /* Lich, Master Lich */
};
+#define LAST_MONSPELL (MS_ANIMATE_DEAD)
+#define NUM_MONSPELLS (LAST_MONSPELL + 1)
+
enum Monspell_mode
{
Msmode_melee,
#include <stdio.h>
#include <deque>
-bool action_rewrite(Action const *act, Action *revised_act);
struct Player u;
bool wielding_ranged_weapon(void)
{
- return permobjs[objects[u.weapon].obj_id].flags[0] & POF_RANGED_WEAPON;
+ return (u.weapon != NO_OBJ) && (permobjs[objects[u.weapon].po_ref].flags[0] & POF_RANGED_WEAPON);
}
bool wielding_melee_weapon(void)
{
- return permobjs[objects[u.weapon].obj_id].flags[0] & POF_MELEE_WEAPON;
+ return (u.weapon != NO_OBJ) && (permobjs[objects[u.weapon].po_ref].flags[0] & POF_MELEE_WEAPON);
+}
+
+static void apply_bonuses(Obj_handle obj, uint32_t bonusmask)
+{
+ if (obj != NO_OBJ)
+ {
+ Obj const *optr = obj_snap(obj);
+ int resistance = po_resistance(optr->po_ref);
+ int dmg_amp = po_damage_amp(optr->po_ref);
+ if (resistance != DT_NONE)
+ {
+ u.resistances[resistance] |= BONUS_ARMOUR;
+ }
+ if (dmg_amp != DT_NONE)
+ {
+ u.damage_amp[dmg_amp] |= BONUS_ARMOUR;
+ }
+ if (po_grants_speed(optr->po_ref))
+ {
+ ++u.speed;
+ }
+ if (po_grants_passwater(optr->po_ref))
+ {
+ u.passwater |= bonusmask;
+ }
+ if (po_grants_flight(optr->po_ref))
+ {
+ u.flight |= bonusmask;
+ }
+ if (po_grants_protective(optr->po_ref))
+ {
+ u.protective_gear |= bonusmask;
+ }
+ }
}
void recalc_defence(void)
{
int i;
- for (i = 0; i < DT_COUNT; i++)
+ for (i = 0; i < NUM_DAMTYPES; i++)
{
- u.resistances[i] &= RESIST_MASK_TEMPORARY;
+ u.resistances[i] &= BONUS_MASK_TEMPORARY;
+ u.damage_amp[i] &= BONUS_MASK_TEMPORARY;
}
+ u.passwater &= BONUS_MASK_TEMPORARY;
+ u.flight &= BONUS_MASK_TEMPORARY;
+ u.protective_gear &= BONUS_MASK_TEMPORARY;
u.speed = (u.leadfoot ? 0 : 1);
+ u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
if (u.armour != NO_OBJ)
{
- 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_DRAGONHIDE_ARMOUR:
- case PO_METEORIC_PLATE_ARMOUR:
- u.resistances[DT_FIRE] |= RESIST_ARMOUR;
- break;
- case PO_ROBE_OF_SWIFTNESS:
- u.speed++;
- break;
- default:
- break;
- }
+ Obj const *aptr = obj_snap(u.armour);
+ u.defence += u.armourmelt ? 0 : permobjs[aptr->po_ref].power;
}
- else
+ apply_bonuses(u.armour, BONUS_ARMOUR);
+ apply_bonuses(u.ring, BONUS_RING);
+ apply_bonuses(u.weapon, BONUS_WEAPON);
+ if (u.known_skill[Skill_deaths_grace])
{
- u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
+ u.resistances[DT_NECRO] |= BONUS_MASK_PERM_SKILL;
}
- if (u.ring != NO_OBJ)
+ if (u.known_skill[Skill_deaths_vengeance])
{
- switch (objects[u.ring].obj_id)
- {
- case PO_FIRE_RING:
- u.resistances[DT_FIRE] |= RESIST_RING;
- break;
- case PO_FROST_RING:
- u.resistances[DT_COLD] |= RESIST_RING;
- break;
- case PO_VAMPIRE_RING:
- u.resistances[DT_NECRO] |= RESIST_RING;
- break;
- }
+ u.damage_amp[DT_SLAY_UNDEAD] |= BONUS_MASK_PERM_SKILL;
+ }
+ if (u.known_skill[Skill_demon_slayer])
+ {
+ u.damage_amp[DT_SLAY_DEMON] |= BONUS_MASK_PERM_SKILL;
}
notify_defence_recalc();
}
{
if (u.weapon != NO_OBJ)
{
- if ((objects[u.weapon].obj_id == PO_BOW) ||
- (objects[u.weapon].obj_id == PO_CROSSBOW) ||
- (objects[u.weapon].obj_id == PO_THUNDERBOW))
+ Obj const *wep = obj_snap(u.weapon);
+ if ((wep->po_ref == PO_BOW) ||
+ (wep->po_ref == PO_CROSSBOW) ||
+ (wep->po_ref == PO_THUNDERBOW))
{
notify_swing_bow();
return Cost_none;
}
return player_attack(delta);
}
- switch (lvl.terrain_at(c))
+ Terrain t = lvl.terrain_at(c);
+ if (terrain_blocks_beings(t))
{
- case WALL:
- case MASONRY_WALL:
- case AMETHYST_WALL:
- case IRON_WALL:
- case SKIN_WALL:
- case BONE_WALL:
notify_cant_go();
return Cost_none;
- case FLOOR:
- case AMETHYST_FLOOR:
- case IRON_FLOOR:
- case SKIN_FLOOR:
- case BONE_FLOOR:
- case DOOR:
- case STAIRS_UP:
- case STAIRS_DOWN:
- case ALTAR:
- reloc_player(c);
- return Cost_std;
- case LAVA:
+ }
+ else if ((!u.flight) && terrain_is_hot(t))
+ {
if (u.resistances[DT_FIRE])
{
- if (lvl.terrain_at(u.pos) != LAVA)
+ if (!terrain_is_hot(lvl.terrain_at(u.pos)))
{
- notify_start_lavawalk();
+ notify_start_hotwalk(t);
}
- reloc_player(c);
- return Cost_std;
}
else
{
- notify_blocked_lava();
+ notify_blocked_hot(t);
return Cost_none;
}
- case WATER:
- if ((u.ring != NO_OBJ) && (objects[u.ring].obj_id == PO_FROST_RING))
+ }
+ else if ((!u.flight) && terrain_drowns(t))
+ {
+ if (u.passwater)
{
- if (lvl.terrain_at(u.pos) != WATER)
+ if (!terrain_drowns(lvl.terrain_at(u.pos)))
{
- notify_start_waterwalk();
+ notify_start_waterwalk(t);
}
- reloc_player(c);
- return Cost_std;
}
else
{
- notify_blocked_water();
+ notify_blocked_water(t);
return Cost_none;
}
}
- return Cost_none;
+ else if ((!u.flight) && terrain_gapes(t))
+ {
+ notify_cant_go();
+ return Cost_none;
+ }
+ reloc_player(c);
+ return Cost_std;
}
void reloc_player(Coord c)
{
+ Terrain t = lvl.terrain_at(c);
Coord oc = u.pos;
u.pos = c;
look_around_you();
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ notify_portal_underfoot();
+ }
if (lvl.obj_at(c) != NO_OBJ)
{
notify_obj_at(c);
else
{
boost = 0;
- u.hpcur += amount;
}
+ u.hpcur += amount;
notify_player_heal(amount, boost, loud);
return;
}
return really;
}
-void u_init(char const *name)
+void u_init(char const *name, Role_id role)
{
if (!name || !*name)
{
{
strncpy(u.name, name, 16);
}
+ u.role = role;
u.body = 10;
u.bdam = 0;
u.agility = 10;
u.experience = 0;
u.level = 1;
u.food = 2000;
- memset(u.inventory, -1, sizeof u.inventory);
- u.inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
- u.inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
- u.inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere);
- u.weapon = u.inventory[0];
- u.ring = NO_OBJ;
- u.armour = u.inventory[2];
+ for (int i = 0; i < INVENTORY_SIZE; ++i)
+ {
+ u.inventory[i] = NO_OBJ;
+ }
+ for (int i = 0; i < NUM_SKILLS; ++i)
+ {
+ u.known_skill[i] = false;
+ }
+ role_inventories[role](&u);
recalc_defence();
}
{
u.level++;
notify_level_gain();
+ for (int i = 0; universal_skills[i].skill != NO_SKILL; ++i)
+ {
+ if (u.level == universal_skills[i].level)
+ {
+ u.known_skill[universal_skills[i].skill] = true;
+ recalc_defence();
+ notify_learned_skill(universal_skills[i].skill);
+ }
+ }
+ for (int i = 0; skill_tables[u.role][i].skill != NO_SKILL; ++i)
+ {
+ if (u.level == skill_tables[u.role][i].level)
+ {
+ u.known_skill[skill_tables[u.role][i].skill] = true;
+ recalc_defence();
+ notify_learned_skill(skill_tables[u.role][i].skill);
+ }
+ }
if (!zero_die(2))
{
bodygain = gain_body(2);
}
if (hpgain > 0)
{
- /* v1.3: Policy change - gaining a level effectively
- * heals you. */
u.hpcur += hpgain;
u.hpmax += hpgain;
notify_hp_gain(hpgain);
void update_player(void)
{
+ Obj const *ring = obj_snap(u.ring);
+ //Obj const *weapon = obj_snap(u.weapon);
+ //Obj const *armour = obj_snap(u.armour);
if (!(game_tick % 5) && (u.food >= 0) && (u.hpcur < u.hpmax))
{
/* Heal player for one hit point; do not allow HP gain,
/* 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_REGENERATION_RING) &&
+ if (((game_tick % 10) == 5) && (ring) &&
+ (ring->po_ref == PO_REGENERATION_RING) &&
(u.hpcur < ((u.food >= 0) ? u.hpmax : ((u.hpmax * 3) / 4))) &&
(u.food > MIN_FOOD))
{
{
int food_use = 1;
int squeal = 0;
- if ((objects[u.ring].obj_id == PO_REGENERATION_RING) && !(game_tick % 2) && (u.food > MIN_FOOD))
+ if (ring && (ring->po_ref == PO_REGENERATION_RING) &&
+ !(game_tick % 2) && (u.food > MIN_FOOD))
{
/* If you are still less hungry than MIN_FOOD nutrition,
* use one more food every second tick if you are
u.food -= food_use;
notify_food_use(food_use, squeal);
}
+ if (u.known_skill[Skill_sanctified_by_death])
+ {
+ for (int i = 0; i < INVENTORY_SIZE; ++i)
+ {
+ Obj *optr = obj_snapv(u.inventory[i]);
+ if (optr && (optr->po_ref == PO_CORPSE))
+ {
+ optr->meta[1] = 1; // bless this corpse
+ }
+ }
+ Coord c;
+ for (c.y = u.pos.y - 1; c.y <= u.pos.x + 1; ++c.y)
+ {
+ for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x)
+ {
+ Obj_handle o = lvl.obj_at(c);
+ Obj *optr = obj_snapv(o);
+ if (optr && (optr->po_ref == PO_CORPSE))
+ {
+ optr->meta[1] = 1;
+ }
+ }
+ }
+ }
if (u.leadfoot > 0)
{
u.leadfoot--;
/*! \brief Action history for combo detection */
std::deque<Action> past_actions;
-/*! \brief Constants used in combo detection logic */
-enum Combo_phase
-{
- Combo_invalid,
- Combo_valid,
- Combo_finisher
-};
-
/* \brief Process action combos
*
* This function is responsible for detecting whether the most recently
* \return true if revised_act was written to; false otherwise
*/
-bool action_rewrite(Action const *act, Action *revised_act)
+static bool action_rewrite(Action const *act, Action *revised_act)
{
Coord c = u.pos;
- int mon = NO_MON;
- Offset o;
Action tmp_act = *act;
Combo_phase p;
bool rewrite_flag = false;
- switch (tmp_act.cmd)
+ if (tmp_act.cmd == REJECTED_ACTION)
{
- case STAND_STILL:
- p = Combo_valid;
- break;
- case ATTACK:
- o.y = tmp_act.details[0];
- o.x = tmp_act.details[1];
- c = u.pos + o;
- if (!lvl.in_bounds(c))
- {
- debug_move_oob(c); // TODO notify_attack_oob()
- tmp_act.cmd = REJECTED_ACTION;
- rewrite_flag = true;
- p = Combo_invalid;
- break;
- }
- mon = lvl.mon_at(c);
- p = Combo_finisher;
- break;
- case WALK:
- o.y = tmp_act.details[0];
- o.x = tmp_act.details[1];
- c = u.pos + o;
- if (!lvl.in_bounds(c))
- {
- debug_move_oob(c);
- tmp_act.cmd = REJECTED_ACTION;
- rewrite_flag = true;
- p = Combo_invalid;
- }
- else if (lvl.mon_at(c) != NO_MON)
- {
- mon = lvl.mon_at(c);
- tmp_act.cmd = ATTACK;
- rewrite_flag = true;
- p = Combo_finisher;
- }
- else
- {
- p = Combo_valid;
- }
- break;
- default:
- rewrite_flag = false;
- p = Combo_invalid;
- break;
+ return false;
}
+ if (action_rewrite_phase1[tmp_act.cmd] != nullptr)
+ {
+ rewrite_flag = action_rewrite_phase1[tmp_act.cmd](&tmp_act);
+ }
+ p = action_default_combophase[tmp_act.cmd];
switch (p)
{
case Combo_invalid:
+ past_actions.clear();
break;
case Combo_valid:
- if (past_actions.empty())
- {
- past_actions.push_front(tmp_act);
- }
- else
+ case Combo_finisher:
{
- switch (tmp_act.cmd)
+ Combo_state cs = Combo_state_none;
+ Combo_state tmp_cs = Combo_state_none;
+ past_actions.push_back(tmp_act);
+ do
{
- case STAND_STILL:
- /* For now, no combo opens with more than one stand still,
- * and no combo starts with something else then chains through
- * a stand still */
- if (past_actions.front().cmd != STAND_STILL)
+ for (int i = 0; combo_entries[i].first_step != REJECTED_ACTION; ++i)
{
- past_actions.clear();
- past_actions.push_front(tmp_act);
+ if (past_actions[0].cmd == combo_entries[i].first_step)
+ {
+ if (combo_entries[i].avail(&u))
+ {
+ tmp_cs = combo_entries[i].detector(&past_actions);
+ if (tmp_cs == Combo_state_complete)
+ {
+ tmp_act = past_actions[0];
+ rewrite_flag = true;
+ cs = Combo_state_complete;
+ break;
+ }
+ if (tmp_cs == Combo_state_partial)
+ {
+ cs = Combo_state_partial;
+ }
+ }
+ }
}
- break;
- case WALK:
- /* For now, no combo chains through a WALK, but the charge
- * combo will when I have the mental effort to write it. */
- past_actions.clear();
- break;
- default:
- DEBUG_CONTROL_FLOW();
- break;
- }
- }
- break;
- case Combo_finisher:
- if (!past_actions.empty())
- {
- switch (past_actions.front().cmd)
- {
- case STAND_STILL:
- if ((empty_handed() || wielding_melee_weapon()) &&
- (mon != NO_MON))
+ /* If we didn't even get a partial match, drop the oldest
+ * action and try again. */
+ if (cs == Combo_state_none)
{
- rewrite_flag = true;
- tmp_act.cmd = CMD_POWER_ATTACK;
+ past_actions.pop_front();
}
- break;
- case WALK:
- break;
- default:
- DEBUG_CONTROL_FLOW();
- break;
+ } while ((cs == Combo_state_none) && (past_actions.size() >= 1));
+ if ((p == Combo_finisher) && (cs != Combo_state_complete))
+ {
+ past_actions.clear();
}
}
- past_actions.clear();
break;
}
if (rewrite_flag)
return rewrite_flag;
}
-/*! \brief Process a player action.
- *
- * \todo Make CMD_POWER_ATTACK do something special
- */
+/*! \brief Process a player action. */
Action_cost do_player_action(Action *act)
{
- int slot;
- Offset step;
Action redact;
bool rewritten = action_rewrite(act, &redact);
if (rewritten)
{
act = &redact;
}
- switch (act->cmd)
+ if (act->cmd == REJECTED_ACTION)
{
- case REJECTED_ACTION:
return Cost_none;
-
- case CMD_POWER_ATTACK:
- step.y = act->details[0];
- step.x = act->details[1];
- return player_power_attack(step);
-
- case WALK:
- step.y = act->details[0];
- step.x = act->details[1];
- return move_player(step);
-
- case ATTACK:
- step.y = act->details[0];
- step.x = act->details[1];
- return player_attack(step);
-
- case USE_ACTIVE_SKILL:
- debug_unimplemented();
+ }
+ if (act->cmd > LAST_COMMAND)
+ {
return Cost_none;
-
- case ALLOCATE_SKILL_POINT:
+ }
+ if (deed_funcs[act->cmd] == nullptr)
+ {
debug_unimplemented();
return Cost_none;
-
- case GET_ITEM:
- if (lvl.obj_at(u.pos) != NO_OBJ)
- {
- attempt_pickup();
- return Cost_std;
- }
- else
- {
- notify_nothing_to_get();
- return Cost_none;
- }
-
- case WIELD_WEAPON:
- slot = act->details[0];
- if (slot == SLOT_NOTHING)
- {
- return player_unwield(Noise_std);
- }
- else
- {
- return player_wield(slot, Noise_std);
- }
- break;
-
- case WEAR_ARMOUR:
- slot = act->details[0];
- return wear_armour(slot);
-
- case EMANATE_ARMOUR:
- if (u.armour == NO_OBJ)
- {
- notify_emanate_no_armour();
- return Cost_none;
- }
- return emanate_armour();
-
- case ZAP_WEAPON:
- if (u.weapon == NO_OBJ)
- {
- notify_zap_no_weapon();
- return Cost_none;
- }
- return zap_weapon();
-
- case MAGIC_RING:
- if (u.weapon == NO_OBJ)
- {
- notify_magic_no_ring();
- return Cost_none;
- }
- return magic_ring();
-
- case TAKE_OFF_ARMOUR:
- if (u.armour != NO_OBJ)
- {
- int saved_armour = u.armour;
- if ((u.resistances[DT_FIRE] == RESIST_ARMOUR) &&
- (lvl.terrain_at(u.pos) == LAVA))
- {
- notify_lava_blocks_unequip();
- return Cost_none;
- }
- u.armour = NO_OBJ;
- recalc_defence();
- notify_armour_unequip(saved_armour);
- return Cost_std;
- }
- else
- {
- debug_take_off_no_armour();
- return Cost_none;
- }
-
- case GO_UP_STAIRS:
- if (lvl.terrain_at(u.pos) == STAIRS_UP)
- {
- notify_ascent_blocked();
- }
- else
- {
- debug_ascend_non_stairs();
- }
- return Cost_none;
-
- case GO_DOWN_STAIRS:
- if (lvl.terrain_at(u.pos) == STAIRS_DOWN)
- {
- leave_level();
- make_new_level();
- }
- else
- {
- debug_descend_non_stairs();
- }
- return Cost_none;
-
- case STAND_STILL:
- return Cost_std;
-
- case READ_SCROLL:
- slot = act->details[0];
- return read_scroll(u.inventory[slot]);
-
- case EAT_FOOD:
- slot = act->details[0];
- return eat_food(u.inventory[slot]);
-
- case QUAFF_POTION:
- slot = act->details[0];
- return quaff_potion(u.inventory[slot]);
-
- case THROW_FLASK:
- return Cost_none;
-
- case REMOVE_RING:
- return remove_ring();
-
- case PUT_ON_RING:
- slot = act->details[0];
- return put_on_ring(u.inventory[slot]);
-
- case DROP_ITEM:
- slot = act->details[0];
- return drop_obj(slot);
-
- case SAVE_GAME:
- game_finished = 1;
- save_game();
- return Cost_none;
-
- case QUIT:
- game_finished = 1;
- return Cost_none;
-
- case WIZARD_DESCEND:
- if (wizard_mode)
- {
- leave_level();
- make_new_level();
- }
- else
- {
- debug_wizmode_violation();
- }
- return Cost_none;
-
- case WIZARD_LEVELUP:
- if (wizard_mode)
- {
- if (lev_threshold(u.level) != INT_MAX)
- {
- gain_experience((lev_threshold(u.level) - u.experience) + 1);
- }
- }
- else
- {
- debug_wizmode_violation();
- }
- return Cost_none;
}
- return Cost_none;
+ return deed_funcs[act->cmd](act);
}
void player_cleanup(void)
int i;
memset(u.sympathy, '\0', sizeof u.sympathy);
memset(u.resistances, '\0', sizeof u.resistances);
+ memset(u.damage_amp, '\0', sizeof u.damage_amp);
+ u.mptr = nullptr;
+ u.mh = NO_MON;
u.experience = u.level = 0;
u.body = u.agility = u.bdam = u.adam = 0;
memset(u.name, '\0', sizeof u.name);
u.food = 0;
u.leadfoot = u.protection = u.withering = u.armourmelt = 0;
u.level = 0;
+ u.flight = u.passwater = u.protective_gear = 0;
u.weapon = u.armour = u.ring = NO_OBJ;
- for (i = 0; i < 19; ++i)
+ for (i = 0; i < INVENTORY_SIZE; ++i)
{
u.inventory[i] = NO_OBJ;
}
+ for (i = 0; i < NUM_SKILLS; ++i)
+ {
+ u.known_skill[i] = false;
+ }
}
/* u.cc */
#include "display.hh"
#endif
-#ifndef NOTIFY_HH
-#include "notify.hh"
-#endif
-
#ifndef MAP_HH
#include "map.hh"
#endif
#include "fov.hh"
#endif
-/* XXX main.c data and funcs */
-extern bool game_finished;
-extern int game_tick;
-extern bool wizard_mode;
-Action_cost do_player_action(Action *act);
-void new_game(char const *name);
+void new_game(char const *name, Role_id role);
void main_loop(void);
/* XXX misc.c data and funcs */
-extern char const *damtype_names[DT_COUNT];
/* XXX log.cc */
extern void log_death(Death d, char const *what);
extern void load_config(void);
extern void wrapped_system(char const *cmd);
extern void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *stream);
-extern int load_game(void);
+extern void load_game(void);
extern void save_game(void);
+#ifndef NOTIFY_HH
+#include "notify.hh"
+#endif
+
#endif
/* victrix-abyssi.hh */