default.permons
display.hh
display-nc.cc
+dungeon.cc
fov.cc
fov.hh
log.cc
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 combo.o coord.o deeds.o display-nc.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o role.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 mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o role.o shrine.o sorcery.o u.o util.o
OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS)
GAME:=obumbrata
MAJVERS:=1
int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
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 ((l->theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
{
do {
c = l->random_point(2);
} while (l->terrain_at(c) != FLOOR);
- run_random_walk(l, c, excavation_write, walk_data, pool_size);
+ 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(l);
{
place_random_intrusion(l, WALL);
}
- run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ try
+ {
+ run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+ }
+ catch (Obumb_levgen_excep p)
+ {
+ DEBUG_ARBITRARY(p.str);
+ }
/* and now the 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 */
// vim:cindent:ts=8:sw=4:expandtab
bool rv = false;
int pm = monsters[mon].pm_ref;
*vamp_healing = 0;
- switch (ring_pobj)
+ // Testing order is fire, vampire, frost.
+ 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;
+ }
+ else 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;
+ }
+ else
+ {
+ *bonus_damage = 0;
+ *vamp_healing = 0;
}
return rv;
}
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); }
/*! \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
};
-#define DT_COUNT (1 + DT_DROWNING)
+#define LAST_DAMTYPE (DT_DROWNING)
+#define NUM_DAMTYPES (1 + LAST_DAMTYPE)
/*! \brief Identification code for player actions */
enum Game_cmd {
*/
enum Death {
DEATH_KILLED, DEATH_KILLED_MON, DEATH_BODY, DEATH_AGILITY,
- DEATH_LASH, DEATH_RIBBONS
+ DEATH_LASH
};
/*! \brief Fell powers from forbidden places
#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 0xFFFF0000u
+#define BONUS_RING 0x00010000u
+#define BONUS_ARMOUR 0x00020000u
+#define BONUS_WEAPON 0x00040000u
/*! \brief Represent an in-game action by the player
*
{
/* this actually belongs in a sensible place. */
int saved_armour = u.armour;
- if ((u.resistances[DT_FIRE] == RESIST_ARMOUR) &&
- (lvl.terrain_at(u.pos) == LAVA))
+ if (unequip_safety_check(BONUS_ARMOUR, Noise_std) == You_pass)
{
- notify_lava_blocks_unequip();
- return Cost_none;
+ u.armour = NO_OBJ;
+ recalc_defence();
+ notify_armour_unequip(saved_armour);
+ return Cost_std;
}
- u.armour = NO_OBJ;
- recalc_defence();
- notify_armour_unequip(saved_armour);
- return Cost_std;
+ return Cost_none;
}
else
{
DAMAGEABLE
BREAK_REACT
MELEE_WEAPON
+DEMONIC
WEAPON plague scythe
PLURAL plague sycthes
DEPTH 30
NOTIFY_EQUIP
MELEE_WEAPON
+DEMONIC
WEAPON tormentor's lash
PLURAL tormentor's lashes
DAMAGEABLE
BREAK_REACT
MELEE_WEAPON
+DEMONIC
WEAPON death staff
PLURAL death staves
POWER2 0
DEPTH 8
DAMAGEABLE
+SPEED
ARMOUR robe of shadows
PLURAL robes of shadows
DAMAGEABLE
BREAK_REACT
RES_POISON
+DEMONIC
ARMOUR lich's robe
PLURAL lich's robes
DEPTH 30
NOTIFY_EQUIP
RES_FIRE
+DEMONIC
RING regeneration ring
PLURAL regeneration rings
POWER2 0
DEPTH 15
RES_POISON
+DEMONIC
RING protection ring
PLURAL protection rings
RING imperial seal
PLURAL imperial seals
-DESC This rarest and most ancient of magical rings was forged in the days before the Archon Inferni was imprisoned in this dismal place. Of the nature of its power, there are not even rumours.
+DESC This rarest and most ancient of magical rings was forged in the days before the Archon Inferni was banished to the Abyss. Of the nature of its power, there are not even rumours.
RARITY 100
ASCII '='
UTF8 "="
POWER2 0
DEPTH 1
STACKABLE
+DEMONIC
CARRION corpse
PLURAL corpses
CONTAINS_BLOOD
LEAVES_CORPSE
+monster sybarite
+desc A thrill-seeking devotee of fell powers.
+ascii 'f'
+utf8 "f"
+colour l_purple
+rarity 30
+power 18
+hp 120
+mhit 30
+mdam 18
+defence 20
+experience 600
+speed 1
+SMART
+HUMANOID
+MADE_OF_MEAT
+CONTAINS_BLOOD
+LEAVES_CORPSE
+
# goblins
monster goblin
desc A short, scrawny humanoid with no love for surface folk.
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",
{
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);
wch[1] = 0;
setcchar(&player_tile, wch, 0, 0, nullptr);
wch[0] = L' ';
- setcchar(&blank_tile, wch, 0, 0, nullptr);
+ 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)
{
wch[0] = permons[i].sym;
{
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;
mvwprintw(fullscreen_window, 12, 1, "\n");
mvwprintw(fullscreen_window, 13, 19, "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();
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;
#include "monsters.hh"
#include "player.hh"
#include "map.hh"
+#include "mapgen.hh"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
deserialize_int(fp, &(player->withering));
deserialize_int(fp, &(player->armourmelt));
deserialize_int(fp, &(player->speed));
- for (int i = 0; i < DT_COUNT; ++i)
+ 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 < INVENTORY_SIZE; ++i)
{
deserialize_objhandle(fp, &(player->inventory[i]));
serialize_int(fp, player.withering);
serialize_int(fp, player.armourmelt);
serialize_int(fp, player.speed);
- for (int i = 0; i < DT_COUNT; ++i)
+ 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 < INVENTORY_SIZE; ++i)
{
serialize_objhandle(fp, player.inventory[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)
+ {
+ 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;
+ }
+ }
+}
+
+/*! \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)
+ {
+ 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);
+}
+
+/*! \brief Deserialize a Level */
+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 = (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
+ {
+ 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);
+}
+
+/*! \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];
+ 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)
+ {
+ 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)
{
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);
}
for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
{
- if (!(iter->second.flags & MF_USED))
+ if (!(iter->second.flags & MF_ALIVE))
{
continue;
}
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)
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);
}
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(l->max_y() - 2, std::max(c.y, l->min_y() + 2));
- }
- else
- {
- c.x += (zero_die(2) ? -1 : 1);
- c.x = std::min(l->max_x() - 2, std::max(c.x, l->min_x() + 2));
- }
+ 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:
}
if (bailout < 1)
{
- debug_excavation_bailout();
+ throw Obumb_levgen_excep("run_random_walk exceeded bailout limit");
}
return c;
}
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);
- }
+ c += zero_die(2) ?
+ (zero_die(2) ? North : South) :
+ (zero_die(2) ? East : West);
if (c.y <= l->min_y())
{
l->grow(North, true);
}
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;
}
case LAYOUT_CLASSIC_CAVE:
build_level_cave(&lvl);
break;
- }
-}
-
-/*! \brief Build a dungeonbash-style rooms-and-corridors level */
-void build_level_dungeonbash(Level *l)
-{
- 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 < l->chunks_high; ++chy)
- {
- for (chx = 0; chx < l->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;
}
}
* them either. */
return 2;
}
+ if (l->terrain_at(c) == newterr)
+ {
+ return 0;
+ }
for (j = 0; j < data_as_ints[0]; ++j)
{
if (l->terrain_at(c) == overwrite[j])
return 1;
}
}
- return 0;
+ return 2;
}
/*! \brief "Intrusion" function for use with random walks */
int intrusion_write(Level *l, Coord c, void const *data)
{
Terrain const *tptr = (Terrain const *) data;
- if ((l->terrain_at(c) != WALL) || (l->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)))
{
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 (l->flags_at(c) & MAPFLAG_HARDWALL);
- run_random_walk(l, c, intrusion_write, &new_wall, intrusion_size);
+ 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 */
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(&lvl, &c);
if (pf == You_fail)
{ "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 },
+ { "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_floor(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_floor;
+}
+
+/*! \brief self-explanatory */
+bool terrain_is_wall(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_wall;
+}
+
+/*! \brief self-explanatory */
bool terrain_is_opaque(Terrain terr)
{
return terrain_props[terr].flags & TFLAG_opaque;
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];
- 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)
- {
- 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;
- }
- }
-}
-
-/*! \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)
- {
- 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);
-}
-
-/*! \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 = (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
- {
- 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);
-}
-
/*! \brief Rotate a connection bitmask */
uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
{
#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
#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 0x00000100u
+#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 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
{
if (in_bounds(c))
}
}
}
+ uint32_t region_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->region_at(c2) : NO_REGION;
+ }
+ else
+ {
+ return NO_REGION;
+ }
+ }
+ void set_region_at(Coord c, uint32_t r)
+ {
+ 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_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 populate_level(void);
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);
#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(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);
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 */
/* 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;
}
ABOUT THE GAME
--------------
-Vana salus.
-
Obumbrata et Velata is Martin Read's entry in the 2014 Seven Day Roguelike
Challenge.
+It is, however you look at it, not even remotely fair and balanced.
+
REPORTING BUGS
--------------
Report bugs by e-mail to martin (at) blackswordsonics dot com.
case DEATH_LASH:
print_msg("You tasted the lash one time too many.\n");
break;
- case DEATH_RIBBONS:
- print_msg("You looked good in ribbons.\n");
- break;
}
print_msg("Your game lasted %d ticks.\n", game_tick);
print_msg("You killed monsters worth %d experience.\n", u.experience);
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("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_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_ring_unequip(Obj_handle obj);
void notify_armour_equip(Obj_handle obj);
void notify_armour_unequip(Obj_handle obj);
-void notify_lava_blocks_unequip(void);
-void notify_water_blocks_unequip(void);
+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(Obj_handle obj, Noisiness noisy);
void notify_wield_weapon(Obj_handle obj);
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"
-};
-
/*! \brief Read a magic scroll */
Action_cost read_scroll(Obj_handle obj)
{
return Cost_none;
}
-void flavours_init(void)
-{
- int colour_choices[10];
- int i;
- int j;
- int done;
- /* Flavoured items use "power" to track their flavour. This is a
- * gross and unforgiveable hack. */
- /* Rings */
- for (i = 0; i < 10;)
- {
- colour_choices[i] = zero_die(20);
- done = 1;
- for (j = 0; j < i; j++)
- {
- if (colour_choices[i] == colour_choices[j])
- {
- done = 0;
- }
- }
- if (done)
- {
- i++;
- }
- }
- permobjs[PO_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;)
- {
- 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_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];
-}
-
Obj_handle first_free_obj_handle = 1u;
Obj_handle get_first_free_obj(void)
}
}
-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_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)
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_hot_blocks_unequip();
+ }
+ return You_fail;
+ }
+ else if (terrain_drowns(t) &&
+ (!(u.passwater & ~mask)) &&
+ (!(u.flight & ~mask)))
{
if (noisy != Noise_silent)
{
- notify_lava_blocks_unequip();
+ notify_wet_blocks_unequip();
}
return You_fail;
}
- else if ((objects[u.ring].po_ref == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER))
+ else if (terrain_gapes(t) &&
+ (!(u.flight & ~mask)))
{
if (noisy != Noise_silent)
{
- notify_water_blocks_unequip();
+ notify_pit_blocks_unequip();
}
return You_fail;
}
Action_cost player_wield(Obj_handle obj, Noisiness noisy = Noise_std);
void attempt_pickup(void);
-Pass_fail ring_removal_unsafe(Noisiness noisy = Noise_std);
-Pass_fail armour_removal_unsafe(Noisiness noisy = Noise_std);
+Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy = Noise_std);
#endif
#include "core.hh"
/* change WIZARD_MODE to 1 if you want the wizard mode commands. */
-#define WIZARD_MODE 0
+#define WIZARD_MODE 1
#ifndef PLAYER_HH
#include "player.hh"
void main_loop(void);
/* XXX misc.c data and funcs */
-extern char const *damtype_names[DT_COUNT];
+extern char const *damtype_names[NUM_DAMTYPES];
/* XXX log.cc */
extern void log_death(Death d, char const *what);
#define POF_DRESS 0x00010000u
#define POF_MELEE_WEAPON 0x00010000u
#define POF_RANGED_WEAPON 0x00020000u
+#define POF_DEMONIC 0x10000000u
// POF field 1
-#define POF_RES_FIRE 0x00000001u
-#define POF_RES_COLD 0x00000002u
-#define POF_RES_ELEC 0x00000003u
-#define POF_RES_NECRO 0x00000004u
-#define POF_RES_POISON 0x00000005u
+#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 0x00000100u
-#define POF_DMG_COLD 0x00000200u
-#define POF_DMG_ELEC 0x00000300u
-#define POF_DMG_NECRO 0x00000400u
-#define POF_DMG_POISON 0x00000500u
-#define POF_DMG_MASK 0x0000ff00u
+#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 {
extern const int NUM_OF_PERMOBJS;
extern Permobj permobjs[];
-extern bool po_is_stackable(int po);
+
+bool po_is_stackable(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
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.
Obj_handle inventory[INVENTORY_SIZE]; //!< currently carried items.
Obj_handle weapon; //!< currently equipped weapon.
'DRESS' => 0,
'RANGED_WEAPON' => 0,
'MELEE_WEAPON' => 0,
+ 'DEMONIC' => 0,
'RES_FIRE' => 1,
'RES_COLD' => 1,
'RES_ELEC' => 1,
'DMG_POISON' => 1,
'PASS_WATER' => 1,
'FLIGHT' => 1,
- 'PROTECTIVE' => 1
+ 'PROTECTIVE' => 1,
+ 'SPEED' => 1
);
* \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
* 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 = mon_snapv(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);
+}
- switch (monsters[mon].pm_ref)
+static void cast_lightning_bolt(Mon_handle mon)
+{
+ mshootu(mon);
+}
+
+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))
{
- 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;
+ notify_necrosmite_fail();
+ }
+ else
+ {
+ 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);
+}
- 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_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);
+}
+
+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();
+ }
+}
- case MS_REJECT:
- /* No usable spell available. */
- rval = 0;
- break;
+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();
+ }
+}
- case MS_STRIKE_STAFF:
- mhitu(mon, DT_PHYS);
- break;
+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_NECRO_STAFF:
- mhitu(mon, DT_NECRO);
- break;
+static void cast_animate_dead(Mon_handle mon)
+{
+}
- 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 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 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 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 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 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:
+ 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].pm_ref].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].pm_ref].name);
- }
- else
- {
- damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].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 */
+ /* Other */
+ MS_ANIMATE_DEAD, /* Lich, Master Lich */
};
+#define LAST_MONSPELL (MS_ANIMATE_DEAD)
+#define NUM_MONSPELLS (LAST_MONSPELL + 1)
+
enum Monspell_mode
{
Msmode_melee,
return (u.weapon != NO_OBJ) && (permobjs[objects[u.weapon].po_ref].flags[0] & POF_MELEE_WEAPON);
}
-void recalc_defence(void)
+static void apply_bonuses(Obj_handle obj, uint32_t bonusmask)
{
- int i;
- for (i = 0; i < DT_COUNT; i++)
+ if (obj != NO_OBJ)
{
- u.resistances[i] &= RESIST_MASK_TEMPORARY;
- }
- u.speed = (u.leadfoot ? 0 : 1);
- if (u.armour != NO_OBJ)
- {
- Obj const *armour = obj_snap(u.armour);
- u.defence = u.armourmelt ? 0 : permobjs[armour->po_ref].power;
- u.defence += u.withering ? (u.agility / 10) : (u.agility / 5);
- switch (armour->po_ref)
+ 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))
{
- 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;
+ u.flight |= bonusmask;
+ }
+ if (po_grants_protective(optr->po_ref))
+ {
+ u.protective_gear |= bonusmask;
}
}
- else
+}
+
+void recalc_defence(void)
+{
+ int i;
+ for (i = 0; i < NUM_DAMTYPES; i++)
{
- u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
+ u.resistances[i] &= BONUS_MASK_TEMPORARY;
+ u.damage_amp[i] &= BONUS_MASK_TEMPORARY;
}
- if (u.ring != NO_OBJ)
+ 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)
{
- Obj const *ring = obj_snap(u.ring);
- switch (ring->po_ref)
- {
- 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;
- }
+ Obj const *aptr = obj_snap(u.armour);
+ u.defence += u.armourmelt ? 0 : permobjs[aptr->po_ref].power;
}
+ apply_bonuses(u.armour, BONUS_ARMOUR);
+ apply_bonuses(u.ring, BONUS_RING);
+ apply_bonuses(u.weapon, BONUS_WEAPON);
notify_defence_recalc();
}
notify_cant_go();
return Cost_none;
}
- else if (terrain_is_hot(t))
+ else if ((!u.flight) && terrain_is_hot(t))
{
if (u.resistances[DT_FIRE])
{
return Cost_none;
}
}
- else if (terrain_drowns(t))
+ else if ((!u.flight) && terrain_drowns(t))
{
- if (u.ring != NO_OBJ)
+ if (u.passwater)
{
- Obj const *ring = obj_snap(u.ring);
- if (ring->po_ref == PO_FROST_RING)
+ if (!terrain_drowns(lvl.terrain_at(u.pos)))
{
- if (!terrain_drowns(lvl.terrain_at(u.pos)))
- {
- notify_start_waterwalk(t);
- }
+ notify_start_waterwalk(t);
}
}
else
return Cost_none;
}
}
- else if (terrain_gapes(t))
+ else if ((!u.flight) && terrain_gapes(t))
{
notify_cant_go();
return Cost_none;
else
{
boost = 0;
- u.hpcur += amount;
}
+ u.hpcur += amount;
notify_player_heal(amount, boost, loud);
return;
}
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.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)
{