diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2007-03-15 20:10:20 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2007-03-15 20:10:20 +0000 |
commit | 621bd9ce58cc45ce9cfcc3cf1f576882b40a426d (patch) | |
tree | adc2b3b4faed06c535b2bec690cf362af40c7fa5 | |
parent | 83559fff8232481cbc68731b7527dd2154c0bd88 (diff) | |
download | crawl-ref-621bd9ce58cc45ce9cfcc3cf1f576882b40a426d.tar.gz crawl-ref-621bd9ce58cc45ce9cfcc3cf1f576882b40a426d.zip |
Cleaned up ghost and Pandemonium demon handling.
Can now have multiple ghosts or Pandemonium demons on a level. Ghosts and Pan
demons can coexist. (D:9 and later are eligible for >1 ghost.) Enabled loading
ghosts in Pandemonium.
Pandemonium demons can now be created outside Pan. Not that you'd want to do
it, but you can.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1043 c06c8d41-db1a-0410-9941-cceddc491573
-rw-r--r-- | crawl-ref/source/Kills.cc | 6 | ||||
-rw-r--r-- | crawl-ref/source/debug.cc | 16 | ||||
-rw-r--r-- | crawl-ref/source/defines.h | 3 | ||||
-rw-r--r-- | crawl-ref/source/describe.cc | 78 | ||||
-rw-r--r-- | crawl-ref/source/describe.h | 2 | ||||
-rw-r--r-- | crawl-ref/source/dungeon.cc | 8 | ||||
-rw-r--r-- | crawl-ref/source/enum.h | 1 | ||||
-rw-r--r-- | crawl-ref/source/externs.h | 44 | ||||
-rw-r--r-- | crawl-ref/source/files.cc | 745 | ||||
-rw-r--r-- | crawl-ref/source/files.h | 3 | ||||
-rw-r--r-- | crawl-ref/source/ghost.cc | 592 | ||||
-rw-r--r-- | crawl-ref/source/hiscores.cc | 128 | ||||
-rw-r--r-- | crawl-ref/source/makefile.obj | 1 | ||||
-rw-r--r-- | crawl-ref/source/mon-util.cc | 243 | ||||
-rw-r--r-- | crawl-ref/source/mon-util.h | 4 | ||||
-rw-r--r-- | crawl-ref/source/monplace.cc | 10 | ||||
-rw-r--r-- | crawl-ref/source/monstuff.cc | 48 | ||||
-rw-r--r-- | crawl-ref/source/player.cc | 27 | ||||
-rw-r--r-- | crawl-ref/source/player.h | 2 | ||||
-rw-r--r-- | crawl-ref/source/tags.cc | 121 | ||||
-rw-r--r-- | crawl-ref/source/tags.h | 1 |
21 files changed, 1202 insertions, 881 deletions
diff --git a/crawl-ref/source/Kills.cc b/crawl-ref/source/Kills.cc index 5dcb4ba495..7bf7e86388 100644 --- a/crawl-ref/source/Kills.cc +++ b/crawl-ref/source/Kills.cc @@ -612,16 +612,16 @@ void kill_def::load(FILE *file) } } -kill_ghost::kill_ghost(const struct monsters *mon) +kill_ghost::kill_ghost(const monsters *mon) { exp = exper_value(mon); place = get_packed_place(); - ghost_name = ghost.name; + ghost_name = mon->ghost->name; // Check whether this is really a ghost, since we also have to handle // the Pandemonic demons. if (mon->type == MONS_PLAYER_GHOST) - ghost_name = "The ghost of " + ghost_description(true); + ghost_name = "The ghost of " + ghost_description(*mon, true); } std::string kill_ghost::info() const diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index aaef55481c..bca8987829 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -1192,7 +1192,8 @@ void stethoscope(int mwh) // print type of monster snprintf( info, INFO_SIZE, "%s (id #%d; type=%d loc=(%d,%d) align=%s)", - monam( menv[i].number, menv[i].type, true, DESC_CAP_THE ), + monam( &menv[i], menv[i].number, menv[i].type, true, + DESC_CAP_THE ), i, menv[i].type, menv[i].x, menv[i].y, ((menv[i].attitude == ATT_FRIENDLY) ? "friendly" : @@ -1233,8 +1234,9 @@ void stethoscope(int mwh) ((menv[i].foe == MHITYOU) ? "you" : (menv[i].foe == MHITNOT) ? "none" : (menv[menv[i].foe].type == -1) ? "unassigned monster" - : monam( menv[menv[i].foe].number, menv[menv[i].foe].type, - true, DESC_PLAIN )), + : monam( &menv[menv[i].foe], + menv[menv[i].foe].number, menv[menv[i].foe].type, + true, DESC_PLAIN )), menv[i].foe, menv[i].foe_memory, @@ -1276,9 +1278,11 @@ void stethoscope(int mwh) if (menv[i].type == MONS_PLAYER_GHOST || menv[i].type == MONS_PANDEMONIUM_DEMON) { - snprintf( info, INFO_SIZE, "Ghost damage: %d; brand: %d", - ghost.values[ GVAL_DAMAGE ], ghost.values[ GVAL_BRAND ] ); - mpr( info, MSGCH_DIAGNOSTICS ); + ASSERT(menv[i].ghost.get()); + const ghost_demon &ghost = *menv[i].ghost; + mprf( MSGCH_DIAGNOSTICS, + "Ghost damage: %d; brand: %d", + ghost.values[ GVAL_DAMAGE ], ghost.values[ GVAL_BRAND ] ); } } // end stethoscope() #endif diff --git a/crawl-ref/source/defines.h b/crawl-ref/source/defines.h index f143a0ec51..d71743bc2e 100644 --- a/crawl-ref/source/defines.h +++ b/crawl-ref/source/defines.h @@ -47,6 +47,9 @@ // minimum value for strength required on armour and weapons #define STR_REQ_THRESHOLD 10 +// Max ghosts on a level. +#define MAX_GHOSTS 10 + // max size of monter array {dlb}: #define MAX_MONSTERS 200 // number of monster enchantments diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 20a66edbce..acb2f2b39f 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -364,14 +364,17 @@ int str_to_trap(const std::string &s) // Describes the random demons you find in Pandemonium. // //--------------------------------------------------------------- -static std::string describe_demon(void) +static std::string describe_demon(const monsters &mons) { + ASSERT(mons.ghost.get()); + long globby = 0; - - for (unsigned int i = 0; i < strlen( ghost.name ); i++) + const ghost_demon &ghost = *mons.ghost; + + for (unsigned i = 0; i < ghost.name.length(); i++) globby += ghost.name[i]; - globby *= strlen( ghost.name ); + globby *= ghost.name.length(); push_rng_state(); seed_rng( globby ); @@ -4581,17 +4584,18 @@ static std::string describe_draconian(const monsters *mon) void describe_monsters(int class_described, unsigned char which_mons) { std::string description; + monsters &mons = menv[which_mons]; description.reserve(200); clrscr(); - description = ptr_monam( &(menv[ which_mons ]), DESC_CAP_A ); + description = ptr_monam( &mons, DESC_CAP_A ); description += "$$"; // Now that the player has examined it, he knows it's a mimic. - if (mons_is_mimic(menv[which_mons].type)) - menv[which_mons].flags |= MF_KNOWN_MIMIC; + if (mons_is_mimic(mons.type)) + mons.flags |= MF_KNOWN_MIMIC; switch (class_described) { @@ -5974,12 +5978,12 @@ void describe_monsters(int class_described, unsigned char which_mons) } case MONS_PLAYER_GHOST: description += "The apparition of "; - description += ghost_description(); + description += ghost_description(mons); description += ".$"; break; case MONS_PANDEMONIUM_DEMON: - description += describe_demon(); + description += describe_demon(mons); break; // mimics -- I'm not considering these descriptions a bug. -- bwr @@ -6253,8 +6257,9 @@ void describe_monsters(int class_described, unsigned char which_mons) found_spell = true; } - snprintf( info, INFO_SIZE, " %d: %s$", i, - mons_spell_name( hspell_pass[i] ) ); + snprintf( info, INFO_SIZE, " %d: %s (%d)$", i, + mons_spell_name( hspell_pass[i] ), + hspell_pass[i] ); description += info; } @@ -6300,10 +6305,13 @@ void describe_monsters(int class_described, unsigned char which_mons) // punctuation that's wanted. // //--------------------------------------------------------------- -std::string ghost_description(bool concise) +std::string ghost_description(const monsters &mons, bool concise) { + ASSERT(mons.ghost.get()); char tmp_buff[ INFO_SIZE ]; + const ghost_demon &ghost = *mons.ghost; + // We're fudging stats so that unarmed combat gets based off // of the ghost's species, not the player's stats... exact // stats are required anyways, all that matters is whether @@ -6312,34 +6320,34 @@ std::string ghost_description(bool concise) int str; switch (ghost.values[GVAL_SPECIES]) { - case SP_HILL_DWARF: - case SP_MOUNTAIN_DWARF: - case SP_TROLL: - case SP_OGRE: - case SP_OGRE_MAGE: - case SP_MINOTAUR: - case SP_HILL_ORC: - case SP_CENTAUR: - case SP_NAGA: - case SP_MUMMY: - case SP_GHOUL: - str = 15; - break; - - case SP_HUMAN: - case SP_DEMIGOD: - case SP_DEMONSPAWN: - str = 10; - break; + case SP_HILL_DWARF: + case SP_MOUNTAIN_DWARF: + case SP_TROLL: + case SP_OGRE: + case SP_OGRE_MAGE: + case SP_MINOTAUR: + case SP_HILL_ORC: + case SP_CENTAUR: + case SP_NAGA: + case SP_MUMMY: + case SP_GHOUL: + str = 15; + break; + + case SP_HUMAN: + case SP_DEMIGOD: + case SP_DEMONSPAWN: + str = 10; + break; - default: - str = 5; - break; + default: + str = 5; + break; } snprintf( tmp_buff, sizeof(tmp_buff), "%s the %s, a%s %s %s", - ghost.name, + ghost.name.c_str(), skill_title( ghost.values[GVAL_BEST_SKILL], ghost.values[GVAL_SKILL_LEVEL], diff --git a/crawl-ref/source/describe.h b/crawl-ref/source/describe.h index 32868ddaf7..97ad8848f3 100644 --- a/crawl-ref/source/describe.h +++ b/crawl-ref/source/describe.h @@ -62,7 +62,7 @@ void describe_spell(int spelled); /* *********************************************************************** * called from: describe_monsters - describe, kill_ghost - Kills * *********************************************************************** */ -std::string ghost_description(bool concise = false); +std::string ghost_description(const monsters &mons, bool concise = false); const char *trap_name(trap_type trap); int str_to_trap(const std::string &s); diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index 04df22f0ef..9a45191132 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -6010,7 +6010,7 @@ static void dngn_place_item_explicit(int index, int x, int y, dngn_place_item_explicit(spec, x, y, level); } -static void dngn_make_monster( +static void dngn_place_monster( const mons_spec &monster_type_thing, int monster_level, int vx, int vy) @@ -6080,7 +6080,7 @@ static int vault_grid( vault_placement &place, grd[vx][vy] = DNGN_FLOOR; mons_spec mons = mapsp->get_mons(); - dngn_make_monster(mons, level_number, vx, vy); + dngn_place_monster(mons, level_number, vx, vy); item_spec item = mapsp->get_item(); dngn_place_item_explicit(item, vx, vy, level_number); @@ -6278,8 +6278,8 @@ static int vault_grid( vault_placement &place, if (vgrid != '8' && vgrid != '9' && vgrid != '0') monster_type_thing = place.map.mons.get_monster(vgrid - '1'); - dngn_make_monster(monster_type_thing, monster_level, - vx, vy); + dngn_place_monster(monster_type_thing, monster_level, + vx, vy); } // again, this seems odd, given that this is just one of many diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 8ea5a59621..db03db6da9 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -1362,6 +1362,7 @@ enum ghost_value_type GVAL_SPELL_4, GVAL_SPELL_5, GVAL_SPELL_6, // 19 + GVAL_SPEED, NUM_GHOST_VALUES, // should always be last value // these values are for demonlords, which override the above: diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index f4e4626c87..b5e419792f 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -814,9 +814,17 @@ struct mon_attack_def } }; +class ghost_demon; + class monsters : public actor { public: + monsters(); + monsters(const monsters &other); + + monsters &operator = (const monsters &other); + +public: int type; int hit_points; int max_hit_points; @@ -845,7 +853,15 @@ public: god_type god; // Usually GOD_NO_GOD. + std::auto_ptr<ghost_demon> ghost; // Ghost information. + public: + void set_ghost(const ghost_demon &ghost); + void ghost_init(); + void pandemon_init(); + void reset(); + void load_spells(int spellbook); + // actor interface int id() const; bool alive() const; @@ -912,6 +928,9 @@ public: int shield_bypass_ability(int tohit) const; actor_type atype() const { return ACT_MONSTER; } + +private: + void init_with(const monsters &mons); }; struct cloud_struct @@ -998,14 +1017,31 @@ struct game_state }; extern game_state crawl_state; -struct ghost_struct +struct ghost_demon { - char name[20]; +public: + std::string name; FixedVector< short, NUM_GHOST_VALUES > values; -}; +public: + ghost_demon(); + void reset(); + void init_random_demon(); + void init_player_ghost(); + +public: + static std::vector<ghost_demon> find_ghosts(); + +private: + static int n_extra_ghosts(); + static void find_extra_ghosts(std::vector<ghost_demon> &ghosts, int n); + +private: + void add_spells(); + int translate_spell(int playerspell) const; +}; -extern struct ghost_struct ghost; +extern std::vector<ghost_demon> ghosts; struct system_environment { diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc index 463cc14603..a45f76a32c 100644 --- a/crawl-ref/source/files.cc +++ b/crawl-ref/source/files.cc @@ -84,79 +84,16 @@ #define DO_CHMOD_PRIVATE(x) // empty command #endif -void save_level(int level_saved, bool was_a_labyrinth, char where_were_you); - -/* - Order for looking for conjurations for the 1st & 2nd spell slots, - when finding spells to be remembered by a player's ghost: - */ -unsigned char search_order_conj[] = { - SPELL_LEHUDIBS_CRYSTAL_SPEAR, - SPELL_BOLT_OF_DRAINING, - SPELL_AGONY, - SPELL_DISINTEGRATE, - SPELL_LIGHTNING_BOLT, - SPELL_STICKY_FLAME, - SPELL_ISKENDERUNS_MYSTIC_BLAST, - SPELL_BOLT_OF_MAGMA, - SPELL_ICE_BOLT, - SPELL_BOLT_OF_FIRE, - SPELL_BOLT_OF_COLD, - SPELL_FIREBALL, - SPELL_DELAYED_FIREBALL, - SPELL_VENOM_BOLT, - SPELL_BOLT_OF_IRON, - SPELL_STONE_ARROW, - SPELL_THROW_FLAME, - SPELL_THROW_FROST, - SPELL_PAIN, - SPELL_STING, - SPELL_SHOCK, - SPELL_MAGIC_DART, - SPELL_NO_SPELL, // end search -}; - -/* - Order for looking for summonings and self-enchants for the 3rd spell slot: - */ -unsigned char search_order_third[] = { -/* 0 */ - SPELL_SYMBOL_OF_TORMENT, - SPELL_SUMMON_GREATER_DEMON, - SPELL_SUMMON_WRAITHS, - SPELL_SUMMON_HORRIBLE_THINGS, - SPELL_SUMMON_DEMON, - SPELL_DEMONIC_HORDE, - SPELL_HASTE, - SPELL_ANIMATE_DEAD, - SPELL_INVISIBILITY, - SPELL_CALL_IMP, - SPELL_SUMMON_SMALL_MAMMAL, -/* 10 */ - SPELL_CONTROLLED_BLINK, - SPELL_BLINK, - SPELL_NO_SPELL, // end search -}; +// file locking stuff +#ifdef USE_FILE_LOCKING +static bool lock_file_handle( FILE *handle, int type ); +static bool unlock_file_handle( FILE *handle ); +#endif // USE_FILE_LOCKING -/* - Order for looking for enchants for the 4th + 5th spell slot. If fails, will - go through conjs. - Note: Dig must be in misc2 (5th) position to work. - */ -unsigned char search_order_misc[] = { -/* 0 */ - SPELL_AGONY, - SPELL_BANISHMENT, - SPELL_PARALYZE, - SPELL_CONFUSE, - SPELL_SLOW, - SPELL_POLYMORPH_OTHER, - SPELL_TELEPORT_OTHER, - SPELL_DIG, - SPELL_NO_SPELL, // end search -}; +void save_level(int level_saved, bool was_a_labyrinth, char where_were_you); -/* Last slot (emergency) can only be teleport self or blink. */ +#define GHOST_MINOR_VERSION 1 +#define LEVEL_MINOR_VERSION 1 static void redraw_all(void) { @@ -173,16 +110,6 @@ static void redraw_all(void) you.redraw_status_flags = REDRAW_LINE_1_MASK | REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK; } -struct ghost_struct ghost; - -unsigned char translate_spell(unsigned char spel); -unsigned char search_third_list(unsigned char ignore_spell); -unsigned char search_second_list(unsigned char ignore_spell); -unsigned char search_first_list(unsigned char ignore_spell); - -void add_spells( struct ghost_struct &gs ); -void generate_random_demon(); - static bool determine_version( FILE *restoreFile, char &majorVersion, char &minorVersion ); @@ -720,31 +647,21 @@ void load( unsigned char stair_taken, int load_mode, bool was_a_labyrinth, was_a_labyrinth = false; } - // clear out ghost/demon lord information: - strcpy( ghost.name, "" ); - for (int ic = 0; ic < NUM_GHOST_VALUES; ++ic) - ghost.values[ic] = 0; - // Try to open level savefile. FILE *levelFile = fopen(cha_fil.c_str(), "rb"); // GENERATE new level when the file can't be opened: if (levelFile == NULL) - { - strcpy(ghost.name, ""); - - for (int imn = 0; imn < NUM_GHOST_VALUES; ++imn) - ghost.values[imn] = 0; - + { builder( you.your_level, you.level_type ); just_created_level = true; - if (you.level_type == LEVEL_PANDEMONIUM) - generate_random_demon(); - else if (you.your_level > 1 - && one_chance_in(3) - && you.level_type != LEVEL_LABYRINTH) - load_ghost(); // no ghosts in Pan or Labyrinth + if (you.your_level > 1 + && one_chance_in(3) + && you.level_type != LEVEL_LABYRINTH) + { + load_ghost(); // no ghosts in Labyrinth + } } else { @@ -1149,7 +1066,8 @@ void save_level(int level_saved, bool was_a_labyrinth, char where_were_you) // nail all items to the ground fix_item_coordinates(); - write_tagged_file( saveFile, SAVE_MAJOR_VERSION, 0, TAGTYPE_LEVEL ); + write_tagged_file( saveFile, SAVE_MAJOR_VERSION, + LEVEL_MINOR_VERSION, TAGTYPE_LEVEL ); fclose(saveFile); @@ -1264,8 +1182,6 @@ void load_ghost(void) { char majorVersion; char minorVersion; - int imn; - int i; std::string cha_fil = make_filename("bones", you.your_level, you.where_are_you, @@ -1289,6 +1205,16 @@ void load_ghost(void) return; } + if (majorVersion != SAVE_MAJOR_VERSION + || minorVersion != GHOST_MINOR_VERSION) + { + + fclose(gfile); + unlink(cha_fil.c_str()); + return; + } + + ghosts.clear(); restore_ghost_version(gfile, majorVersion, minorVersion); // sanity check - EOF @@ -1313,44 +1239,15 @@ void load_ghost(void) unlink(cha_fil.c_str()); // translate ghost to monster and place. - for (imn = 0; imn < MAX_MONSTERS - 10; imn++) + for (int imn = 0; imn < MAX_MONSTERS - 10 && !ghosts.empty(); imn++) { if (menv[imn].type != -1) continue; - menv[imn].type = MONS_PLAYER_GHOST; - menv[imn].hit_dice = ghost.values[ GVAL_EXP_LEVEL ]; - menv[imn].hit_points = ghost.values[ GVAL_MAX_HP ]; - menv[imn].max_hit_points = ghost.values[ GVAL_MAX_HP ]; - menv[imn].ac = ghost.values[ GVAL_AC]; - menv[imn].ev = ghost.values[ GVAL_EV ]; - menv[imn].speed = 10; - menv[imn].speed_increment = 70; - menv[imn].attitude = ATT_HOSTILE; - menv[imn].behaviour = BEH_WANDER; - menv[imn].flags = 0; - menv[imn].foe = MHITNOT; - menv[imn].foe_memory = 0; - menv[imn].colour = mons_class_colour(MONS_PLAYER_GHOST); - menv[imn].number = 250; - mons_load_spells(&menv[imn], MST_GHOST); - - for (i = 0; i < NUM_MONSTER_SLOTS; i++) - menv[imn].inv[i] = NON_ITEM; - - for (i = 0; i < NUM_MON_ENCHANTS; i++) - menv[imn].enchantment[i] = ENCH_NONE; - - do - { - menv[imn].x = random2(GXM - 20) + 10; - menv[imn].y = random2(GYM - 20) + 10; - } - while ((grd[menv[imn].x][menv[imn].y] != DNGN_FLOOR) - || (mgrd[menv[imn].x][menv[imn].y] != NON_MONSTER)); - - mgrd[menv[imn].x][menv[imn].y] = imn; - break; + menv[imn].set_ghost(ghosts[0]); + menv[imn].ghost_init(); + + ghosts.erase(ghosts.begin()); } } @@ -1561,18 +1458,16 @@ static void restore_ghost_version( FILE *ghostFile, { switch(majorVersion) { - case SAVE_MAJOR_VERSION: - restore_tagged_file(ghostFile, TAGTYPE_GHOST, minorVersion); - break; - default: - break; + case SAVE_MAJOR_VERSION: + restore_tagged_file(ghostFile, TAGTYPE_GHOST, minorVersion); + break; + default: + break; } } void save_ghost( bool force ) { - const int wpn = you.equip[EQ_WEAPON]; - if (!force && (you.your_level < 2 || you.is_undead)) return; @@ -1590,82 +1485,21 @@ void save_ghost( bool force ) return; } - snprintf(ghost.name, sizeof ghost.name, "%s", you.your_name); - - ghost.values[ GVAL_MAX_HP ] = ((you.hp_max >= 150) ? 150 : you.hp_max); - ghost.values[ GVAL_EV ] = player_evasion(); - ghost.values[ GVAL_AC ] = player_AC(); - ghost.values[ GVAL_SEE_INVIS ] = player_see_invis(); - ghost.values[ GVAL_RES_FIRE ] = player_res_fire(); - ghost.values[ GVAL_RES_COLD ] = player_res_cold(); - ghost.values[ GVAL_RES_ELEC ] = player_res_electricity(); - - /* note - as ghosts, automatically get res poison + prot_life */ - - int d = 4; - int e = 0; - - if (wpn != -1) - { - if (you.inv[wpn].base_type == OBJ_WEAPONS - || you.inv[wpn].base_type == OBJ_STAVES) - { - d = property( you.inv[wpn], PWPN_DAMAGE ); - - d *= 25 + you.skills[weapon_skill( you.inv[wpn].base_type, - you.inv[wpn].sub_type )]; - d /= 25; - - if (you.inv[wpn].base_type == OBJ_WEAPONS) - { - if (is_random_artefact( you.inv[wpn] )) - e = randart_wpn_property( you.inv[wpn], RAP_BRAND ); - else - e = you.inv[wpn].special; - } - } - } - else - { - /* Unarmed combat */ - if (you.species == SP_TROLL) - d += you.experience_level; - - d += you.skills[SK_UNARMED_COMBAT]; - } - - d *= 30 + you.skills[SK_FIGHTING]; - d /= 30; - - d += you.strength / 4; - - if (d > 50) - d = 50; - - ghost.values[ GVAL_DAMAGE ] = d; - ghost.values[ GVAL_BRAND ] = e; - ghost.values[ GVAL_SPECIES ] = you.species; - ghost.values[ GVAL_BEST_SKILL ] = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99); - ghost.values[ GVAL_SKILL_LEVEL ] = you.skills[best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99)]; - ghost.values[ GVAL_EXP_LEVEL ] = you.experience_level; - ghost.values[ GVAL_CLASS ] = you.char_class; - - add_spells(ghost); - - gfile = fopen(cha_fil.c_str(), "wb"); + ghosts = ghost_demon::find_ghosts(); + + gfile = lk_open("wb", cha_fil); if (gfile == NULL) { - snprintf(info, INFO_SIZE, "Error creating ghost file: %s", - cha_fil.c_str()); - mpr(info); + mprf("Error creating ghost file: %s", cha_fil.c_str()); more(); return; } - write_tagged_file( gfile, SAVE_MAJOR_VERSION, 0, TAGTYPE_GHOST ); + write_tagged_file( gfile, SAVE_MAJOR_VERSION, + GHOST_MINOR_VERSION, TAGTYPE_GHOST ); - fclose(gfile); + lk_close(gfile, "wb", cha_fil); #if DEBUG_DIAGNOSTICS mpr( "Saved ghost.", MSGCH_DIAGNOSTICS ); @@ -1674,211 +1508,10 @@ void save_ghost( bool force ) DO_CHMOD_PRIVATE(cha_fil.c_str()); } // end save_ghost() -/* - Used when creating ghosts: goes through and finds spells for the ghost to - cast. Death is a traumatic experience, so ghosts only remember a few spells. - */ -void add_spells( struct ghost_struct &gs ) -{ - int i = 0; - - for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) - gs.values[i] = SPELL_NO_SPELL; - - gs.values[ GVAL_SPELL_1 ] = search_first_list(SPELL_NO_SPELL); - gs.values[ GVAL_SPELL_2 ] = search_first_list(gs.values[GVAL_SPELL_1]); - gs.values[ GVAL_SPELL_3 ] = search_second_list(SPELL_NO_SPELL); - gs.values[ GVAL_SPELL_4 ] = search_third_list(SPELL_NO_SPELL); - - if (gs.values[ GVAL_SPELL_4 ] == SPELL_NO_SPELL) - gs.values[ GVAL_SPELL_4 ] = search_first_list(SPELL_NO_SPELL); - - gs.values[ GVAL_SPELL_5 ] = search_first_list(gs.values[GVAL_SPELL_4]); - - if (gs.values[ GVAL_SPELL_5 ] == SPELL_NO_SPELL) - gs.values[ GVAL_SPELL_5 ] = search_first_list(gs.values[GVAL_SPELL_4]); - - if (player_has_spell( SPELL_DIG )) - gs.values[ GVAL_SPELL_5 ] = SPELL_DIG; - - /* Looks for blink/tport for emergency slot */ - if (player_has_spell( SPELL_CONTROLLED_BLINK ) - || player_has_spell( SPELL_BLINK )) - { - gs.values[ GVAL_SPELL_6 ] = SPELL_CONTROLLED_BLINK; - } - - if (player_has_spell( SPELL_TELEPORT_SELF )) - gs.values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF; - - for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) - gs.values[i] = translate_spell( gs.values[i] ); -} // end add_spells() - -unsigned char search_first_list(unsigned char ignore_spell) -{ - for (int i = 0; i < 20; i++) - { - if (search_order_conj[i] == SPELL_NO_SPELL) - return SPELL_NO_SPELL; - - if (search_order_conj[i] == ignore_spell) - continue; - - if (player_has_spell(search_order_conj[i])) - return search_order_conj[i]; - } - - return SPELL_NO_SPELL; -} // end search_first_list() - -unsigned char search_second_list(unsigned char ignore_spell) -{ - for (int i = 0; i < 20; i++) - { - if (search_order_third[i] == SPELL_NO_SPELL) - return SPELL_NO_SPELL; - - if (search_order_third[i] == ignore_spell) - continue; - - if (player_has_spell(search_order_third[i])) - return search_order_third[i]; - } - - return SPELL_NO_SPELL; -} // end search_second_list() -unsigned char search_third_list(unsigned char ignore_spell) -{ - for (int i = 0; i < 20; i++) - { - if (search_order_misc[i] == SPELL_NO_SPELL) - return SPELL_NO_SPELL; - - if (search_order_misc[i] == ignore_spell) - continue; - - if (player_has_spell(search_order_misc[i])) - return search_order_misc[i]; - } - - return SPELL_NO_SPELL; -} // end search_third_list() - - -/* - When passed the number for a player spell, returns the equivalent monster - spell. Returns SPELL_NO_SPELL on failure (no equiv). - */ -unsigned char translate_spell(unsigned char spel) -{ - switch (spel) - { - case SPELL_TELEPORT_SELF: - return (MS_TELEPORT); - case SPELL_ICE_BOLT: - return (MS_ICE_BOLT); - case SPELL_SHOCK: - return (MS_SHOCK); - case SPELL_BOLT_OF_MAGMA: - return (MS_MAGMA); - case SPELL_MAGIC_DART: - return (MS_MMISSILE); - case SPELL_FIREBALL: - case SPELL_DELAYED_FIREBALL: - return (MS_FIREBALL); - case SPELL_DIG: - return (MS_DIG); - case SPELL_BOLT_OF_FIRE: - return (MS_FIRE_BOLT); - case SPELL_BOLT_OF_COLD: - return (MS_COLD_BOLT); - case SPELL_LIGHTNING_BOLT: - return (MS_LIGHTNING_BOLT); - case SPELL_POLYMORPH_OTHER: - return (MS_MUTATION); - case SPELL_SLOW: - return (MS_SLOW); - case SPELL_HASTE: - return (MS_HASTE); - case SPELL_PARALYZE: - return (MS_PARALYSIS); - case SPELL_CONFUSE: - return (MS_CONFUSE); - case SPELL_INVISIBILITY: - return (MS_INVIS); - case SPELL_THROW_FLAME: - return (MS_FLAME); - case SPELL_THROW_FROST: - return (MS_FROST); - case SPELL_CONTROLLED_BLINK: - return (MS_BLINK); /* approximate */ -/* case FREEZING_CLOUD: return ; no freezing/mephitic cloud yet - case MEPHITIC_CLOUD: return ; */ - case SPELL_VENOM_BOLT: - return (MS_VENOM_BOLT); - case SPELL_POISON_ARROW: - return (MS_POISON_ARROW); - case SPELL_TELEPORT_OTHER: - return (MS_TELEPORT_OTHER); - case SPELL_SUMMON_SMALL_MAMMAL: - return (MS_SUMMON_SMALL_MAMMALS); - case SPELL_BOLT_OF_DRAINING: - return (MS_NEGATIVE_BOLT); - case SPELL_LEHUDIBS_CRYSTAL_SPEAR: - return (MS_CRYSTAL_SPEAR); - case SPELL_BLINK: - return (MS_BLINK); - case SPELL_ISKENDERUNS_MYSTIC_BLAST: - return (MS_ORB_ENERGY); - case SPELL_SUMMON_HORRIBLE_THINGS: - return (MS_LEVEL_SUMMON); /* approximate */ - case SPELL_SHADOW_CREATURES: - return (MS_LEVEL_SUMMON); /* approximate */ - case SPELL_ANIMATE_DEAD: - return (MS_ANIMATE_DEAD); - case SPELL_PAIN: - return (MS_PAIN); - case SPELL_SUMMON_WRAITHS: - return (MS_SUMMON_UNDEAD); /* approximate */ - case SPELL_STICKY_FLAME: - return (MS_STICKY_FLAME); - case SPELL_CALL_IMP: - return (MS_SUMMON_DEMON_LESSER); - case SPELL_BANISHMENT: - return (MS_BANISHMENT); - case SPELL_STING: - return (MS_STING); - case SPELL_SUMMON_DEMON: - return (MS_SUMMON_DEMON); - case SPELL_DEMONIC_HORDE: - return (MS_SUMMON_DEMON_LESSER); - case SPELL_SUMMON_GREATER_DEMON: - return (MS_SUMMON_DEMON_GREATER); - case SPELL_BOLT_OF_IRON: - return (MS_IRON_BOLT); - case SPELL_STONE_ARROW: - return (MS_STONE_ARROW); - case SPELL_DISINTEGRATE: - return (MS_DISINTEGRATE); - case SPELL_AGONY: - /* Too powerful to give ghosts Torment for Agony? Nah. */ - return (MS_TORMENT); - case SPELL_SYMBOL_OF_TORMENT: - return (MS_TORMENT); - default: - break; - } - - return (MS_NO_SPELL); -} - -void generate_random_demon(void) +void generate_random_demon() { int rdem = 0; - int i = 0; - for (rdem = 0; rdem < MAX_MONSTERS + 1; rdem++) { if (rdem == MAX_MONSTERS) @@ -1888,166 +1521,10 @@ void generate_random_demon(void) break; } - char st_p[ITEMNAME_SIZE]; - - make_name(random_int(), false, st_p); - strcpy(ghost.name, st_p); - - // hp - could be defined below (as could ev, AC etc). Oh well, too late: - ghost.values[ GVAL_MAX_HP ] = 100 + roll_dice( 3, 50 ); - - ghost.values[ GVAL_EV ] = 5 + random2(20); - ghost.values[ GVAL_AC ] = 5 + random2(20); - - ghost.values[ GVAL_SEE_INVIS ] = (one_chance_in(10) ? 0 : 1); - - if (!one_chance_in(3)) - ghost.values[ GVAL_RES_FIRE ] = (coinflip() ? 2 : 3); - else - { - ghost.values[ GVAL_RES_FIRE ] = 0; /* res_fire */ - - if (one_chance_in(10)) - ghost.values[ GVAL_RES_FIRE ] = -1; - } - - if (!one_chance_in(3)) - ghost.values[ GVAL_RES_COLD ] = 2; - else - { - ghost.values[ GVAL_RES_COLD ] = 0; /* res_cold */ - - if (one_chance_in(10)) - ghost.values[ GVAL_RES_COLD ] = -1; - } - - // demons, like ghosts, automatically get poison res. and life prot. - - // resist electricity: - ghost.values[ GVAL_RES_ELEC ] = (!one_chance_in(3) ? 1 : 0); - - // HTH damage: - ghost.values[ GVAL_DAMAGE ] = 20 + roll_dice( 2, 20 ); - - // special attack type (uses weapon brand code): - ghost.values[ GVAL_BRAND ] = SPWPN_NORMAL; - - if (!one_chance_in(3)) - { - do { - ghost.values[ GVAL_BRAND ] = random2(17); - /* some brands inappropriate (eg holy wrath) */ - } while (ghost.values[ GVAL_BRAND ] == SPWPN_HOLY_WRATH - || ghost.values[ GVAL_BRAND ] == SPWPN_ORC_SLAYING - || ghost.values[ GVAL_BRAND ] == SPWPN_PROTECTION - || ghost.values[ GVAL_BRAND ] == SPWPN_FLAME - || ghost.values[ GVAL_BRAND ] == SPWPN_FROST - || ghost.values[ GVAL_BRAND ] == SPWPN_DISRUPTION); - } - - // is demon a spellcaster? - // upped from one_chance_in(3)... spellcasters are more interesting - // and I expect named demons to typically have a trick or two -- bwr - ghost.values[GVAL_DEMONLORD_SPELLCASTER] = (one_chance_in(10) ? 0 : 1); - - // does demon fly? (0 = no, 1 = fly, 2 = levitate) - ghost.values[GVAL_DEMONLORD_FLY] = (one_chance_in(3) ? 0 : - one_chance_in(5) ? 2 : 1); - - // vacant <ghost best skill level>: - ghost.values[GVAL_DEMONLORD_UNUSED] = 0; - - // hit dice: - ghost.values[GVAL_DEMONLORD_HIT_DICE] = 10 + roll_dice(2, 10); - - // does demon cycle colours? - ghost.values[GVAL_DEMONLORD_CYCLE_COLOUR] = (one_chance_in(10) ? 1 : 0); - - menv[rdem].hit_dice = ghost.values[ GVAL_DEMONLORD_HIT_DICE ]; - menv[rdem].hit_points = ghost.values[ GVAL_MAX_HP ]; - menv[rdem].max_hit_points = ghost.values[ GVAL_MAX_HP ]; - menv[rdem].ac = ghost.values[ GVAL_AC ]; - menv[rdem].ev = ghost.values[ GVAL_EV ]; - menv[rdem].speed = (one_chance_in(3) ? 10 : 6 + roll_dice(2, 9)); - menv[rdem].speed_increment = 70; - menv[rdem].colour = random_colour(); // demon's colour - - for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) - ghost.values[i] = SPELL_NO_SPELL; - - /* This bit uses the list of player spells to find appropriate spells - for the demon, then converts those spells to the monster spell indices. - Some special monster-only spells are at the end. */ - if (ghost.values[ GVAL_DEMONLORD_SPELLCASTER ] == 1) - { -#define RANDOM_ARRAY_ELEMENT(x) x[random2(sizeof(x) / sizeof(x[0]))] - - if (coinflip()) - ghost.values[GVAL_SPELL_1]=RANDOM_ARRAY_ELEMENT(search_order_conj); - - // Might duplicate the first spell, but that isn't a problem. - if (coinflip()) - ghost.values[GVAL_SPELL_2]=RANDOM_ARRAY_ELEMENT(search_order_conj); - - if (!one_chance_in(4)) - ghost.values[GVAL_SPELL_3]=RANDOM_ARRAY_ELEMENT(search_order_third); - - if (coinflip()) - ghost.values[GVAL_SPELL_4]=RANDOM_ARRAY_ELEMENT(search_order_misc); - - if (coinflip()) - ghost.values[GVAL_SPELL_5]=RANDOM_ARRAY_ELEMENT(search_order_misc); - -#undef RANDOM_ARRAY_ELEMENT - - if (coinflip()) - ghost.values[ GVAL_SPELL_6 ] = SPELL_BLINK; - if (coinflip()) - ghost.values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF; - - /* Converts the player spell indices to monster spell ones */ - for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) - ghost.values[i] = translate_spell( ghost.values[i] ); - - /* give demon a chance for some monster-only spells: */ - /* and demon-summoning should be fairly common: */ - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_1] = MS_HELLFIRE_BURST; - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_1] = MS_METAL_SPLINTERS; - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_1] = MS_ENERGY_BOLT; /* eye of devas */ - - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_2] = MS_STEAM_BALL; - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_2] = MS_PURPLE_BLAST; - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_2] = MS_HELLFIRE; - - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_3] = MS_SMITE; - if (one_chance_in(25)) - ghost.values[GVAL_SPELL_3] = MS_HELLFIRE_BURST; - if (one_chance_in(12)) - ghost.values[GVAL_SPELL_3] = MS_SUMMON_DEMON_GREATER; - if (one_chance_in(12)) - ghost.values[GVAL_SPELL_3] = MS_SUMMON_DEMON; - - if (one_chance_in(20)) - ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON_GREATER; - if (one_chance_in(20)) - ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON; - - /* at least they can summon demons */ - if (ghost.values[GVAL_SPELL_4] == SPELL_NO_SPELL) - ghost.values[GVAL_SPELL_4] = MS_SUMMON_DEMON; - - if (one_chance_in(15)) - ghost.values[GVAL_SPELL_5] = MS_DIG; - - mons_load_spells(&menv[rdem], MST_GHOST); - } + ghost_demon pandemon; + pandemon.init_random_demon(); + menv[rdem].set_ghost(pandemon); + menv[rdem].pandemon_init(); } // end generate_random_demon() // Largest string we'll save @@ -2127,3 +1604,127 @@ long readLong(FILE *file) return ((long) (unsigned short) readShort(file)) << 16 | (long) (unsigned short) readShort(file); } + +//////////////////////////////////////////////////////////////////////////// +// Locking for multiuser systems + +// first, some file locking stuff for multiuser crawl +#ifdef USE_FILE_LOCKING + +static bool lock_file_handle( FILE *handle, int type ) +{ + struct flock lock; + int status; + + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = type; + +#ifdef USE_BLOCKING_LOCK + + status = fcntl( fileno( handle ), F_SETLKW, &lock ); + +#else + + for (int i = 0; i < 30; i++) + { + status = fcntl( fileno( handle ), F_SETLK, &lock ); + + // success + if (status == 0) + break; + + // known failure + if (status == -1 && (errno != EACCES && errno != EAGAIN)) + break; + + perror( "Problems locking file... retrying..." ); + delay( 1000 ); + } + +#endif + + return (status == 0); +} + +static bool unlock_file_handle( FILE *handle ) +{ + struct flock lock; + int status; + + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = F_UNLCK; + +#ifdef USE_BLOCKING_LOCK + + status = fcntl( fileno( handle ), F_SETLKW, &lock ); + +#else + + for (int i = 0; i < 30; i++) + { + status = fcntl( fileno( handle ), F_SETLK, &lock ); + + // success + if (status == 0) + break; + + // known failure + if (status == -1 && (errno != EACCES && errno != EAGAIN)) + break; + + perror( "Problems unlocking file... retrying..." ); + delay( 1000 ); + } + +#endif + + return (status == 0); +} + +#endif + +FILE *lk_open(const char *mode, const std::string &file) +{ + FILE *handle = fopen(file.c_str(), mode); +#ifdef SHARED_FILES_CHMOD_PUBLIC + chmod(file.c_str(), SHARED_FILES_CHMOD_PUBLIC); +#endif + +#ifdef USE_FILE_LOCKING + int locktype = F_RDLCK; + if (mode && mode[0] != 'r') + locktype = F_WRLCK; + + if (handle && !lock_file_handle( handle, locktype )) + { + perror( "Could not lock file... " ); + fclose( handle ); + handle = NULL; + } +#endif + return handle; +} + +void lk_close(FILE *handle, const char *mode, const std::string &file) +{ + UNUSED( mode ); + + if (handle == NULL || handle == stdin) + return; + +#ifdef USE_FILE_LOCKING + unlock_file_handle( handle ); +#endif + + // actually close + fclose(handle); + +#ifdef SHARED_FILES_CHMOD_PUBLIC + if (mode && mode[0] == 'w') + chmod(file.c_str(), SHARED_FILES_CHMOD_PUBLIC); +#endif +} diff --git a/crawl-ref/source/files.h b/crawl-ref/source/files.h index 577e443333..c2563cd479 100644 --- a/crawl-ref/source/files.h +++ b/crawl-ref/source/files.h @@ -91,4 +91,7 @@ void writeLong(FILE* file, long num); long readLong(FILE *file); +FILE *lk_open(const char *mode, const std::string &file); +void lk_close(FILE *handle, const char *mode, const std::string &file); + #endif diff --git a/crawl-ref/source/ghost.cc b/crawl-ref/source/ghost.cc new file mode 100644 index 0000000000..52077c4e77 --- /dev/null +++ b/crawl-ref/source/ghost.cc @@ -0,0 +1,592 @@ +/* + * File: ghost.cc + * Summary: Player ghost and random Pandemonium demon handling. + * + * Created for Dungeon Crawl Reference by $Author:dshaligram $ on + * $Date: 2007-03-15 $. + */ + +#include "AppHdr.h" + +#include "externs.h" +#include "itemname.h" +#include "itemprop.h" +#include "randart.h" +#include "skills2.h" +#include "stuff.h" +#include "misc.h" +#include "player.h" +#include <vector> + +std::vector<ghost_demon> ghosts; + +/* + Order for looking for conjurations for the 1st & 2nd spell slots, + when finding spells to be remembered by a player's ghost: + */ +static int search_order_conj[] = { + SPELL_LEHUDIBS_CRYSTAL_SPEAR, + SPELL_BOLT_OF_DRAINING, + SPELL_AGONY, + SPELL_DISINTEGRATE, + SPELL_LIGHTNING_BOLT, + SPELL_STICKY_FLAME, + SPELL_ISKENDERUNS_MYSTIC_BLAST, + SPELL_BOLT_OF_MAGMA, + SPELL_ICE_BOLT, + SPELL_BOLT_OF_FIRE, + SPELL_BOLT_OF_COLD, + SPELL_FIREBALL, + SPELL_DELAYED_FIREBALL, + SPELL_VENOM_BOLT, + SPELL_BOLT_OF_IRON, + SPELL_STONE_ARROW, + SPELL_THROW_FLAME, + SPELL_THROW_FROST, + SPELL_PAIN, + SPELL_STING, + SPELL_SHOCK, + SPELL_MAGIC_DART, + SPELL_NO_SPELL, // end search +}; + +/* + Order for looking for summonings and self-enchants for the 3rd spell slot: + */ +static int search_order_third[] = { +/* 0 */ + SPELL_SYMBOL_OF_TORMENT, + SPELL_SUMMON_GREATER_DEMON, + SPELL_SUMMON_WRAITHS, + SPELL_SUMMON_HORRIBLE_THINGS, + SPELL_SUMMON_DEMON, + SPELL_DEMONIC_HORDE, + SPELL_HASTE, + SPELL_ANIMATE_DEAD, + SPELL_INVISIBILITY, + SPELL_CALL_IMP, + SPELL_SUMMON_SMALL_MAMMAL, +/* 10 */ + SPELL_CONTROLLED_BLINK, + SPELL_BLINK, + SPELL_NO_SPELL, // end search +}; + +/* + Order for looking for enchants for the 4th + 5th spell slot. If fails, will + go through conjs. + Note: Dig must be in misc2 (5th) position to work. + */ +static int search_order_misc[] = { +/* 0 */ + SPELL_AGONY, + SPELL_BANISHMENT, + SPELL_PARALYZE, + SPELL_CONFUSE, + SPELL_SLOW, + SPELL_POLYMORPH_OTHER, + SPELL_TELEPORT_OTHER, + SPELL_DIG, + SPELL_NO_SPELL, // end search +}; + +/* Last slot (emergency) can only be teleport self or blink. */ + +ghost_demon::ghost_demon() : name(), values() +{ + reset(); +} + +void ghost_demon::reset() +{ + name.clear(); + values.init(0); + values[GVAL_SPEED] = 10; +} + +void ghost_demon::init_random_demon() +{ + char st_p[ITEMNAME_SIZE]; + + make_name(random_int(), false, st_p); + name = st_p; + + // hp - could be defined below (as could ev, AC etc). Oh well, too late: + values[ GVAL_MAX_HP ] = 100 + roll_dice( 3, 50 ); + + values[ GVAL_EV ] = 5 + random2(20); + values[ GVAL_AC ] = 5 + random2(20); + + values[ GVAL_SEE_INVIS ] = (one_chance_in(10) ? 0 : 1); + + if (!one_chance_in(3)) + values[ GVAL_RES_FIRE ] = (coinflip() ? 2 : 3); + else + { + values[ GVAL_RES_FIRE ] = 0; /* res_fire */ + + if (one_chance_in(10)) + values[ GVAL_RES_FIRE ] = -1; + } + + if (!one_chance_in(3)) + values[ GVAL_RES_COLD ] = 2; + else + { + values[ GVAL_RES_COLD ] = 0; /* res_cold */ + + if (one_chance_in(10)) + values[ GVAL_RES_COLD ] = -1; + } + + // demons, like ghosts, automatically get poison res. and life prot. + + // resist electricity: + values[ GVAL_RES_ELEC ] = (!one_chance_in(3) ? 1 : 0); + + // HTH damage: + values[ GVAL_DAMAGE ] = 20 + roll_dice( 2, 20 ); + + // special attack type (uses weapon brand code): + values[ GVAL_BRAND ] = SPWPN_NORMAL; + + if (!one_chance_in(3)) + { + do { + values[ GVAL_BRAND ] = random2(17); + /* some brands inappropriate (eg holy wrath) */ + } while (values[ GVAL_BRAND ] == SPWPN_HOLY_WRATH + || values[ GVAL_BRAND ] == SPWPN_ORC_SLAYING + || values[ GVAL_BRAND ] == SPWPN_PROTECTION + || values[ GVAL_BRAND ] == SPWPN_FLAME + || values[ GVAL_BRAND ] == SPWPN_FROST + || values[ GVAL_BRAND ] == SPWPN_DISRUPTION); + } + + // is demon a spellcaster? + // upped from one_chance_in(3)... spellcasters are more interesting + // and I expect named demons to typically have a trick or two -- bwr + values[GVAL_DEMONLORD_SPELLCASTER] = (one_chance_in(10) ? 0 : 1); + + // does demon fly? (0 = no, 1 = fly, 2 = levitate) + values[GVAL_DEMONLORD_FLY] = (one_chance_in(3) ? 0 : + one_chance_in(5) ? 2 : 1); + + // vacant <ghost best skill level>: + values[GVAL_DEMONLORD_UNUSED] = 0; + + // hit dice: + values[GVAL_DEMONLORD_HIT_DICE] = 10 + roll_dice(2, 10); + + // does demon cycle colours? + values[GVAL_DEMONLORD_CYCLE_COLOUR] = (one_chance_in(10) ? 1 : 0); + + for (int i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) + values[i] = SPELL_NO_SPELL; + + /* This bit uses the list of player spells to find appropriate spells + for the demon, then converts those spells to the monster spell indices. + Some special monster-only spells are at the end. */ + if (values[ GVAL_DEMONLORD_SPELLCASTER ] == 1) + { +#define RANDOM_ARRAY_ELEMENT(x) x[random2(sizeof(x) / sizeof(x[0]))] + + if (coinflip()) + values[GVAL_SPELL_1]=RANDOM_ARRAY_ELEMENT(search_order_conj); + + // Might duplicate the first spell, but that isn't a problem. + if (coinflip()) + values[GVAL_SPELL_2]=RANDOM_ARRAY_ELEMENT(search_order_conj); + + if (!one_chance_in(4)) + values[GVAL_SPELL_3]=RANDOM_ARRAY_ELEMENT(search_order_third); + + if (coinflip()) + values[GVAL_SPELL_4]=RANDOM_ARRAY_ELEMENT(search_order_misc); + + if (coinflip()) + values[GVAL_SPELL_5]=RANDOM_ARRAY_ELEMENT(search_order_misc); + +#undef RANDOM_ARRAY_ELEMENT + + if (coinflip()) + values[ GVAL_SPELL_6 ] = SPELL_BLINK; + if (coinflip()) + values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF; + + /* Converts the player spell indices to monster spell ones */ + for (int i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) + values[i] = translate_spell( values[i] ); + + /* give demon a chance for some monster-only spells: */ + /* and demon-summoning should be fairly common: */ + if (one_chance_in(25)) + values[GVAL_SPELL_1] = MS_HELLFIRE_BURST; + if (one_chance_in(25)) + values[GVAL_SPELL_1] = MS_METAL_SPLINTERS; + if (one_chance_in(25)) + values[GVAL_SPELL_1] = MS_ENERGY_BOLT; /* eye of devas */ + + if (one_chance_in(25)) + values[GVAL_SPELL_2] = MS_STEAM_BALL; + if (one_chance_in(25)) + values[GVAL_SPELL_2] = MS_PURPLE_BLAST; + if (one_chance_in(25)) + values[GVAL_SPELL_2] = MS_HELLFIRE; + + if (one_chance_in(25)) + values[GVAL_SPELL_3] = MS_SMITE; + if (one_chance_in(25)) + values[GVAL_SPELL_3] = MS_HELLFIRE_BURST; + if (one_chance_in(12)) + values[GVAL_SPELL_3] = MS_SUMMON_DEMON_GREATER; + if (one_chance_in(12)) + values[GVAL_SPELL_3] = MS_SUMMON_DEMON; + + if (one_chance_in(20)) + values[GVAL_SPELL_4] = MS_SUMMON_DEMON_GREATER; + if (one_chance_in(20)) + values[GVAL_SPELL_4] = MS_SUMMON_DEMON; + + /* at least they can summon demons */ + if (values[GVAL_SPELL_4] == SPELL_NO_SPELL) + values[GVAL_SPELL_4] = MS_SUMMON_DEMON; + + if (one_chance_in(15)) + values[GVAL_SPELL_5] = MS_DIG; + } +} + +void ghost_demon::init_player_ghost() +{ + name = you.your_name; + values[ GVAL_MAX_HP ] = ((you.hp_max >= 400) ? 400 : you.hp_max); + values[ GVAL_EV ] = player_evasion(); + values[ GVAL_AC ] = player_AC(); + + if (values[GVAL_EV] > 40) + values[GVAL_EV] = 40; + + values[ GVAL_SEE_INVIS ] = player_see_invis(); + values[ GVAL_RES_FIRE ] = player_res_fire(); + values[ GVAL_RES_COLD ] = player_res_cold(); + values[ GVAL_RES_ELEC ] = player_res_electricity(); + values[ GVAL_SPEED ] = player_ghost_base_movement_speed(); + + int d = 4; + int e = 0; + const int wpn = you.equip[EQ_WEAPON]; + + if (wpn != -1) + { + if (you.inv[wpn].base_type == OBJ_WEAPONS + || you.inv[wpn].base_type == OBJ_STAVES) + { + d = property( you.inv[wpn], PWPN_DAMAGE ); + + d *= 25 + you.skills[weapon_skill( you.inv[wpn].base_type, + you.inv[wpn].sub_type )]; + d /= 25; + + if (you.inv[wpn].base_type == OBJ_WEAPONS) + { + if (is_random_artefact( you.inv[wpn] )) + e = randart_wpn_property( you.inv[wpn], RAP_BRAND ); + else + e = you.inv[wpn].special; + } + } + } + else + { + /* Unarmed combat */ + if (you.species == SP_TROLL) + d += you.experience_level; + + d += you.skills[SK_UNARMED_COMBAT]; + } + + d *= 30 + you.skills[SK_FIGHTING]; + d /= 30; + + d += you.strength / 4; + + if (d > 50) + d = 50; + + values[ GVAL_DAMAGE ] = d; + values[ GVAL_BRAND ] = e; + values[ GVAL_SPECIES ] = you.species; + values[ GVAL_BEST_SKILL ] = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99); + values[ GVAL_SKILL_LEVEL ] = + you.skills[best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99)]; + values[ GVAL_EXP_LEVEL ] = you.experience_level; + values[ GVAL_CLASS ] = you.char_class; + + add_spells(); +} + +static int search_first_list(int ignore_spell) +{ + for (unsigned i = 0; + i < sizeof(search_order_conj) / sizeof(*search_order_conj); i++) + { + if (search_order_conj[i] == SPELL_NO_SPELL) + return SPELL_NO_SPELL; + + if (search_order_conj[i] == ignore_spell) + continue; + + if (player_has_spell(search_order_conj[i])) + return search_order_conj[i]; + } + + return SPELL_NO_SPELL; +} // end search_first_list() + +static int search_second_list(int ignore_spell) +{ + for (unsigned i = 0; + i < sizeof(search_order_third) / sizeof(*search_order_third); i++) + { + if (search_order_third[i] == SPELL_NO_SPELL) + return SPELL_NO_SPELL; + + if (search_order_third[i] == ignore_spell) + continue; + + if (player_has_spell(search_order_third[i])) + return search_order_third[i]; + } + + return SPELL_NO_SPELL; +} // end search_second_list() + +static int search_third_list(int ignore_spell) +{ + for (unsigned i = 0; + i < sizeof(search_order_misc) / sizeof(*search_order_misc); i++) + { + if (search_order_misc[i] == SPELL_NO_SPELL) + return SPELL_NO_SPELL; + + if (search_order_misc[i] == ignore_spell) + continue; + + if (player_has_spell(search_order_misc[i])) + return search_order_misc[i]; + } + + return SPELL_NO_SPELL; +} // end search_third_list() + +/* + Used when creating ghosts: goes through and finds spells for the ghost to + cast. Death is a traumatic experience, so ghosts only remember a few spells. + */ +void ghost_demon::add_spells( ) +{ + int i = 0; + + for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) + values[i] = SPELL_NO_SPELL; + + values[ GVAL_SPELL_1 ] = search_first_list(SPELL_NO_SPELL); + values[ GVAL_SPELL_2 ] = search_first_list(values[GVAL_SPELL_1]); + values[ GVAL_SPELL_3 ] = search_second_list(SPELL_NO_SPELL); + values[ GVAL_SPELL_4 ] = search_third_list(SPELL_NO_SPELL); + + if (values[ GVAL_SPELL_4 ] == SPELL_NO_SPELL) + values[ GVAL_SPELL_4 ] = search_first_list(SPELL_NO_SPELL); + + values[ GVAL_SPELL_5 ] = search_first_list(values[GVAL_SPELL_4]); + + if (values[ GVAL_SPELL_5 ] == SPELL_NO_SPELL) + values[ GVAL_SPELL_5 ] = search_first_list(values[GVAL_SPELL_4]); + + if (player_has_spell( SPELL_DIG )) + values[ GVAL_SPELL_5 ] = SPELL_DIG; + + /* Looks for blink/tport for emergency slot */ + if (player_has_spell( SPELL_CONTROLLED_BLINK ) + || player_has_spell( SPELL_BLINK )) + { + values[ GVAL_SPELL_6 ] = SPELL_CONTROLLED_BLINK; + } + + if (player_has_spell( SPELL_TELEPORT_SELF )) + values[ GVAL_SPELL_6 ] = SPELL_TELEPORT_SELF; + + for (i = GVAL_SPELL_1; i <= GVAL_SPELL_6; i++) + values[i] = translate_spell( values[i] ); +} // end add_spells() + +/* + When passed the number for a player spell, returns the equivalent monster + spell. Returns SPELL_NO_SPELL on failure (no equiv). + */ +int ghost_demon::translate_spell(int spel) const +{ + switch (spel) + { + case SPELL_TELEPORT_SELF: + return (MS_TELEPORT); + case SPELL_ICE_BOLT: + return (MS_ICE_BOLT); + case SPELL_SHOCK: + return (MS_SHOCK); + case SPELL_BOLT_OF_MAGMA: + return (MS_MAGMA); + case SPELL_MAGIC_DART: + return (MS_MMISSILE); + case SPELL_FIREBALL: + case SPELL_DELAYED_FIREBALL: + return (MS_FIREBALL); + case SPELL_DIG: + return (MS_DIG); + case SPELL_BOLT_OF_FIRE: + return (MS_FIRE_BOLT); + case SPELL_BOLT_OF_COLD: + return (MS_COLD_BOLT); + case SPELL_LIGHTNING_BOLT: + return (MS_LIGHTNING_BOLT); + case SPELL_POLYMORPH_OTHER: + return (MS_MUTATION); + case SPELL_SLOW: + return (MS_SLOW); + case SPELL_HASTE: + return (MS_HASTE); + case SPELL_PARALYZE: + return (MS_PARALYSIS); + case SPELL_CONFUSE: + return (MS_CONFUSE); + case SPELL_INVISIBILITY: + return (MS_INVIS); + case SPELL_THROW_FLAME: + return (MS_FLAME); + case SPELL_THROW_FROST: + return (MS_FROST); + case SPELL_CONTROLLED_BLINK: + return (MS_BLINK); /* approximate */ +/* case FREEZING_CLOUD: return ; no freezing/mephitic cloud yet + case MEPHITIC_CLOUD: return ; */ + case SPELL_VENOM_BOLT: + return (MS_VENOM_BOLT); + case SPELL_POISON_ARROW: + return (MS_POISON_ARROW); + case SPELL_TELEPORT_OTHER: + return (MS_TELEPORT_OTHER); + case SPELL_SUMMON_SMALL_MAMMAL: + return (MS_SUMMON_SMALL_MAMMALS); + case SPELL_BOLT_OF_DRAINING: + return (MS_NEGATIVE_BOLT); + case SPELL_LEHUDIBS_CRYSTAL_SPEAR: + return (MS_CRYSTAL_SPEAR); + case SPELL_BLINK: + return (MS_BLINK); + case SPELL_ISKENDERUNS_MYSTIC_BLAST: + return (MS_ORB_ENERGY); + case SPELL_SUMMON_HORRIBLE_THINGS: + return (MS_LEVEL_SUMMON); /* approximate */ + case SPELL_SHADOW_CREATURES: + return (MS_LEVEL_SUMMON); /* approximate */ + case SPELL_ANIMATE_DEAD: + return (MS_ANIMATE_DEAD); + case SPELL_PAIN: + return (MS_PAIN); + case SPELL_SUMMON_WRAITHS: + return (MS_SUMMON_UNDEAD); /* approximate */ + case SPELL_STICKY_FLAME: + return (MS_STICKY_FLAME); + case SPELL_CALL_IMP: + return (MS_SUMMON_DEMON_LESSER); + case SPELL_BANISHMENT: + return (MS_BANISHMENT); + case SPELL_STING: + return (MS_STING); + case SPELL_SUMMON_DEMON: + return (MS_SUMMON_DEMON); + case SPELL_DEMONIC_HORDE: + return (MS_SUMMON_DEMON_LESSER); + case SPELL_SUMMON_GREATER_DEMON: + return (MS_SUMMON_DEMON_GREATER); + case SPELL_BOLT_OF_IRON: + return (MS_IRON_BOLT); + case SPELL_STONE_ARROW: + return (MS_STONE_ARROW); + case SPELL_DISINTEGRATE: + return (MS_DISINTEGRATE); + case SPELL_AGONY: + /* Too powerful to give ghosts Torment for Agony? Nah. */ + return (MS_TORMENT); + case SPELL_SYMBOL_OF_TORMENT: + return (MS_TORMENT); + default: + break; + } + + return (MS_NO_SPELL); +} + +std::vector<ghost_demon> ghost_demon::find_ghosts() +{ + std::vector<ghost_demon> gs; + + ghost_demon player; + player.init_player_ghost(); + gs.push_back(player); + + find_extra_ghosts( gs, n_extra_ghosts() ); + + return (gs); +} + +void ghost_demon::find_extra_ghosts( std::vector<ghost_demon> &gs, int n ) +{ + for (int i = 0; n > 0 && i < MAX_MONSTERS; ++i) + { + if (!menv[i].alive()) + continue; + + if (menv[i].type == MONS_PLAYER_GHOST && menv[i].ghost.get()) + { + // Bingo! + gs.push_back( *menv[i].ghost ); + --n; + } + } +} + +int ghost_demon::n_extra_ghosts() +{ + const int lev = you.your_level + 1; + const int subdepth = subdungeon_depth(you.where_are_you, you.your_level); + + if (you.level_type == LEVEL_PANDEMONIUM + || you.level_type == LEVEL_ABYSS + || (you.level_type == LEVEL_DUNGEON + && (you.where_are_you == BRANCH_CRYPT + || you.where_are_you == BRANCH_TOMB + || you.where_are_you == BRANCH_HALL_OF_ZOT + || player_in_hell())) + || lev > 22) + { + return (MAX_GHOSTS - 1); + } + + if (you.where_are_you == BRANCH_ECUMENICAL_TEMPLE) + return (0); + + // No multiple ghosts until level 14 of the Main Dungeon. + if ((lev < 9 && you.where_are_you == BRANCH_MAIN_DUNGEON) + || (subdepth < 2 && you.where_are_you == BRANCH_LAIR) + || (subdepth < 2 && you.where_are_you == BRANCH_ORCISH_MINES)) + return (0); + + if (you.where_are_you == BRANCH_LAIR + || you.where_are_you == BRANCH_ORCISH_MINES + || (you.where_are_you == BRANCH_MAIN_DUNGEON && lev < 15)) + return (1); + + return 1 + (random2(20) < lev) + (random2(40) < lev); +} diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc index 2155e359aa..65db7627a0 100644 --- a/crawl-ref/source/hiscores.cc +++ b/crawl-ref/source/hiscores.cc @@ -36,6 +36,7 @@ #include "externs.h" #include "branch.h" +#include "files.h" #include "hiscores.h" #include "itemname.h" #include "itemprop.h" @@ -78,12 +79,6 @@ static int hs_nextint(const char *&inbuf); static long hs_nextlong(const char *&inbuf); static time_t parse_time(const std::string &st); -// file locking stuff -#ifdef USE_FILE_LOCKING -static bool lock_file_handle( FILE *handle, int type ); -static bool unlock_file_handle( FILE *handle ); -#endif // USE_FILE_LOCKING - std::string score_file_name() { if (!SysEnv.scorefile.empty()) @@ -363,129 +358,18 @@ std::string hiscores_format_single_long( const scorefile_entry &se, // BEGIN private functions // -------------------------------------------------------------------------- -// first, some file locking stuff for multiuser crawl -#ifdef USE_FILE_LOCKING - -static bool lock_file_handle( FILE *handle, int type ) -{ - struct flock lock; - int status; - - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - lock.l_type = type; - -#ifdef USE_BLOCKING_LOCK - - status = fcntl( fileno( handle ), F_SETLKW, &lock ); - -#else - - for (int i = 0; i < 30; i++) - { - status = fcntl( fileno( handle ), F_SETLK, &lock ); - - // success - if (status == 0) - break; - - // known failure - if (status == -1 && (errno != EACCES && errno != EAGAIN)) - break; - - perror( "Problems locking file... retrying..." ); - delay( 1000 ); - } - -#endif - - return (status == 0); -} - -static bool unlock_file_handle( FILE *handle ) -{ - struct flock lock; - int status; - - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - lock.l_type = F_UNLCK; - -#ifdef USE_BLOCKING_LOCK - - status = fcntl( fileno( handle ), F_SETLKW, &lock ); - -#else - - for (int i = 0; i < 30; i++) - { - status = fcntl( fileno( handle ), F_SETLK, &lock ); - - // success - if (status == 0) - break; - - // known failure - if (status == -1 && (errno != EACCES && errno != EAGAIN)) - break; - - perror( "Problems unlocking file... retrying..." ); - delay( 1000 ); - } - -#endif - - return (status == 0); -} - -#endif - FILE *hs_open( const char *mode, const std::string &scores ) { // allow reading from standard input if ( scores == "-" ) return stdin; - FILE *handle = fopen(scores.c_str(), mode); -#ifdef SHARED_FILES_CHMOD_PUBLIC - chmod(scores.c_str(), SHARED_FILES_CHMOD_PUBLIC); -#endif - -#ifdef USE_FILE_LOCKING - int locktype = F_RDLCK; - if (stricmp(mode, "r")) - locktype = F_WRLCK; - - if (handle && !lock_file_handle( handle, locktype )) - { - perror( "Could not lock scorefile... " ); - fclose( handle ); - handle = NULL; - } -#endif - return handle; + return lk_open( mode, scores ); } void hs_close( FILE *handle, const char *mode, const std::string &scores ) { - UNUSED( mode ); - - if (handle == NULL || handle == stdin) - return; - -#ifdef USE_FILE_LOCKING - unlock_file_handle( handle ); -#endif - - // actually close - fclose(handle); - -#ifdef SHARED_FILES_CHMOD_PUBLIC - if (stricmp(mode, "w") == 0) - chmod(scores.c_str(), SHARED_FILES_CHMOD_PUBLIC); -#endif + lk_close(handle, mode, scores); } bool hs_read( FILE *scores, scorefile_entry &dest ) @@ -1247,8 +1131,8 @@ void scorefile_entry::init_death_cause(int dam, int dsrc, } death_source_name = - monam( monster->number, monster->type, true, DESC_NOCAP_A, - monster->inv[MSLOT_WEAPON] ); + monam( monster, monster->number, monster->type, true, + DESC_NOCAP_A, monster->inv[MSLOT_WEAPON] ); } } else @@ -1484,7 +1368,7 @@ const char *scorefile_entry::death_source_desc() const return (!death_source_name.empty()? death_source_name.c_str() - : monam( mon_num, death_source, true, DESC_NOCAP_A ) ); + : monam( NULL, mon_num, death_source, true, DESC_NOCAP_A ) ); } std::string scorefile_entry::damage_string(bool terse) const diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj index 594e7792a7..c192363222 100644 --- a/crawl-ref/source/makefile.obj +++ b/crawl-ref/source/makefile.obj @@ -20,6 +20,7 @@ fight.o \ files.o \ food.o \ format.o \ +ghost.o \ hiscores.o \ initfile.o \ insult.o \ diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 49065dc41b..7ea248d7e3 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -136,6 +136,11 @@ static const char *monster_spell_name[] = { "Poison Arrow", "Summon Small Mammals", "Summon Mushrooms", + "Ice Bolt", + "Magma", + "Shock", + "Berserk Rage", + "Might" }; #endif @@ -495,7 +500,7 @@ bool mons_is_unique( int mc ) char mons_see_invis( struct monsters *mon ) { if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON) - return (ghost.values[ GVAL_SEE_INVIS ]); + return (mon->ghost->values[ GVAL_SEE_INVIS ]); else if (((seekmonster(mon->type))->bitfields & M_SEE_INVIS) != 0) return (1); else if (scan_mon_inv_randarts( mon, RAP_EYESIGHT ) > 0) @@ -635,7 +640,7 @@ mon_attack_def mons_attack_spec(const monsters *mons, int attk_number) if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) { if (attk_number == 0) - return mon_attack_def::attk(ghost.values[GVAL_DAMAGE]); + return mon_attack_def::attk(mons->ghost->values[GVAL_DAMAGE]); else return mon_attack_def::attk(0, AT_NONE); } @@ -734,7 +739,7 @@ int mons_res_elec( const monsters *mon ) int mc = mon->type; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) - return (ghost.values[ GVAL_RES_ELEC ]); + return (mon->ghost->values[ GVAL_RES_ELEC ]); /* this is a variable, not a player_xx() function, so can be above 1 */ int u = 0; @@ -810,7 +815,7 @@ int mons_res_fire( const monsters *mon ) int mc = mon->type; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) - return (ghost.values[ GVAL_RES_FIRE ]); + return (mon->ghost->values[ GVAL_RES_FIRE ]); int u = 0, f = get_mons_resists(mon); @@ -858,7 +863,7 @@ int mons_res_cold( const monsters *mon ) int mc = mon->type; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) - return (ghost.values[ GVAL_RES_COLD ]); + return (mon->ghost->values[ GVAL_RES_COLD ]); int u = 0, f = get_mons_resists(mon); @@ -961,9 +966,6 @@ int mons_skeleton(int mc) int mons_class_flies(int mc) { - if (mc == MONS_PANDEMONIUM_DEMON) - return (ghost.values[ GVAL_DEMONLORD_FLY ]); - int f = smc->bitfields; if (f & M_FLIES) @@ -977,6 +979,12 @@ int mons_class_flies(int mc) int mons_flies( const monsters *mon ) { + if (mon->type == MONS_PANDEMONIUM_DEMON + && mon->ghost->values[ GVAL_DEMONLORD_FLY ]) + { + return (1); + } + int ret = mons_class_flies( mon->type ); return (ret ? ret : (scan_mon_inv_randarts(mon, RAP_LEVITATE) > 0) ? 2 : 0); } // end mons_flies() @@ -1165,48 +1173,7 @@ int exper_value( const struct monsters *monster ) void mons_load_spells( monsters *mon, int book ) { - int x, y; - - if (book == MST_NO_SPELLS) - { - for (y = 0; y < NUM_MONSTER_SPELL_SLOTS; y++) - mon->spells[y] = MS_NO_SPELL; - return; - } - -#if DEBUG_DIAGNOSTICS - mprf( MSGCH_DIAGNOSTICS, "%s: loading spellbook #%d", - ptr_monam( mon, DESC_PLAIN ), book ); -#endif - - for (x = 0; x < 6; x++) - mon->spells[x] = MS_NO_SPELL; - - if (book == MST_GHOST) - { - for (y = 0; y < NUM_MONSTER_SPELL_SLOTS; y++) - { - mon->spells[y] = ghost.values[ GVAL_SPELL_1 + y ]; -#if DEBUG_DIAGNOSTICS - mprf( MSGCH_DIAGNOSTICS, "spell #%d: %d", y, mon->spells[y] ); -#endif - } - } - else - { - // this needs to be rewritten a la the monsterseek rewrite {dlb}: - for (x = 0; x < NUM_MSTYPES; x++) - { - if (mspell_list[x][0] == book) - break; - } - - if (x < NUM_MSTYPES) - { - for (y = 0; y < 6; y++) - mon->spells[y] = mspell_list[x][y + 1]; - } - } + mon->load_spells(book); } #if DEBUG_DIAGNOSTICS @@ -1441,12 +1408,13 @@ const char *ptr_monam( const monsters *mon, char desc, bool force_seen ) return (mimic_name_buff); } - return (monam( mon->number, mon->type, + return (monam( mon, mon->number, mon->type, force_seen || player_monster_visible( mon ), desc, mon->inv[MSLOT_WEAPON] )); } -const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) +const char *monam( const monsters *mon, + int mons_num, int mons, bool vis, char desc, int mons_wpn ) { static char gmo_n[ ITEMNAME_SIZE ]; char gmo_n2[ ITEMNAME_SIZE ] = ""; @@ -1550,12 +1518,12 @@ const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) break; case MONS_PLAYER_GHOST: - strcpy(gmo_n, ghost.name); + strcpy(gmo_n, mon->ghost->name.c_str()); strcat(gmo_n, "'s ghost"); break; case MONS_PANDEMONIUM_DEMON: - strcpy(gmo_n, ghost.name); + strcpy(gmo_n, mon->ghost->name.c_str()); break; default: @@ -1759,7 +1727,7 @@ int mons_offhand_weapon_index(const monsters *m) int mons_base_damage_brand(const monsters *m) { if (m->type == MONS_PLAYER_GHOST || m->type == MONS_PANDEMONIUM_DEMON) - return ghost.values[ GVAL_BRAND ]; + return m->ghost->values[ GVAL_BRAND ]; return (SPWPN_NORMAL); } @@ -2602,6 +2570,57 @@ bool monster_senior(const monsters *m1, const monsters *m2) /////////////////////////////////////////////////////////////////////////////// // monsters methods +monsters::monsters() + : type(-1), hit_points(0), max_hit_points(0), hit_dice(0), + ac(0), ev(0), speed(0), speed_increment(0), x(0), y(0), + target_x(0), target_y(0), inv(), spells(), attitude(ATT_NEUTRAL), + behaviour(BEH_WANDER), foe(MHITYOU), enchantment(), flags(0L), + number(0), colour(BLACK), foe_memory(0), god(GOD_NO_GOD), + ghost() +{ +} + +monsters::monsters(const monsters &mon) +{ + init_with(mon); +} + +monsters &monsters::operator = (const monsters &mon) +{ + init_with(mon); + return (*this); +} + +void monsters::init_with(const monsters &mon) +{ + type = mon.type; + hit_points = mon.hit_points; + max_hit_points = mon.max_hit_points; + hit_dice = mon.hit_dice; + ac = mon.ac; + ev = mon.ev; + speed = mon.speed; + speed_increment = mon.speed_increment; + x = mon.x; + y = mon.y; + target_x = mon.target_x; + target_y = mon.target_y; + inv = mon.inv; + spells = mon.spells; + attitude = mon.attitude; + behaviour = mon.behaviour; + foe = mon.foe; + enchantment = mon.enchantment; + flags = mon.flags; + number = mon.number; + colour = mon.colour; + foe_memory = mon.foe_memory; + god = mon.god; + + if (mon.ghost.get()) + ghost.reset( new ghost_demon( *mon.ghost ) ); +} + coord_def monsters::pos() const { return coord_def(x, y); @@ -2976,3 +2995,117 @@ void monsters::slow_down(int strength) slow.flavour = BEAM_SLOW; mons_ench_f2(this, slow); } + +void monsters::set_ghost(const ghost_demon &g) +{ + ghost.reset( new ghost_demon(g) ); +} + +void monsters::pandemon_init() +{ + hit_dice = ghost->values[ GVAL_DEMONLORD_HIT_DICE ]; + hit_points = ghost->values[ GVAL_MAX_HP ]; + max_hit_points = ghost->values[ GVAL_MAX_HP ]; + ac = ghost->values[ GVAL_AC ]; + ev = ghost->values[ GVAL_EV ]; + speed = (one_chance_in(3) ? 10 : 6 + roll_dice(2, 9)); + speed_increment = 70; + colour = random_colour(); // demon's colour + load_spells(MST_GHOST); +} + +void monsters::ghost_init() +{ + type = MONS_PLAYER_GHOST; + hit_dice = ghost->values[ GVAL_EXP_LEVEL ]; + hit_points = ghost->values[ GVAL_MAX_HP ]; + max_hit_points = ghost->values[ GVAL_MAX_HP ]; + ac = ghost->values[ GVAL_AC]; + ev = ghost->values[ GVAL_EV ]; + speed = ghost->values[ GVAL_SPEED ]; + speed_increment = 70; + attitude = ATT_HOSTILE; + behaviour = BEH_WANDER; + flags = 0; + foe = MHITNOT; + foe_memory = 0; + colour = mons_class_colour(MONS_PLAYER_GHOST); + number = 250; + load_spells(MST_GHOST); + + inv.init(NON_ITEM); + enchantment.init(ENCH_NONE); + + do + { + x = random2(GXM - 20) + 10; + y = random2(GYM - 20) + 10; + } + while ((grd[x][y] != DNGN_FLOOR) + || (mgrd[x][y] != NON_MONSTER)); + + mgrd[x][y] = monster_index(this); +} + +void monsters::reset() +{ + enchantment.init(ENCH_NONE); + inv.init(NON_ITEM); + + flags = 0; + type = -1; + hit_points = 0; + max_hit_points = 0; + hit_dice = 0; + ac = 0; + ev = 0; + speed_increment = 0; + attitude = ATT_HOSTILE; + behaviour = BEH_SLEEP; + foe = MHITNOT; + + if (in_bounds(x, y)) + mgrd[x][y] = NON_MONSTER; + + x = y = 0; + ghost.reset(NULL); +} + +void monsters::load_spells(int book) +{ + spells.init(MS_NO_SPELL); + if (book == MST_NO_SPELLS || (book == MST_GHOST && !ghost.get())) + return; + +#if DEBUG_DIAGNOSTICS + mprf( MSGCH_DIAGNOSTICS, "%s: loading spellbook #%d", + name(DESC_PLAIN).c_str(), book ); +#endif + + if (book == MST_GHOST) + { + for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++) + { + spells[i] = ghost->values[ GVAL_SPELL_1 + i ]; +#if DEBUG_DIAGNOSTICS + mprf( MSGCH_DIAGNOSTICS, "spell #%d: %d", i, spells[i] ); +#endif + } + } + else + { + int i = 0; + // this needs to be rewritten a la the monsterseek rewrite {dlb}: + for (; i < NUM_MSTYPES; i++) + { + if (mspell_list[i][0] == book) + break; + } + + if (i < NUM_MSTYPES) + { + for (int z = 0; z < NUM_MONSTER_SPELL_SLOTS; z++) + spells[z] = mspell_list[i][z + 1]; + } + } +} diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h index 454a80710f..20774df6d8 100644 --- a/crawl-ref/source/mon-util.h +++ b/crawl-ref/source/mon-util.h @@ -128,7 +128,9 @@ void init_monsters( FixedVector<unsigned short, 1000>& colour ); * spells4 * *********************************************************************** */ // mons_wpn only important for dancing weapons -- bwr -const char *monam(int mons_num, int mons, bool vis, char desc, int mons_wpn = NON_ITEM); +const char *monam(const monsters *mon, + int mons_num, int mons, bool vis, char desc, + int mons_wpn = NON_ITEM); // these front for monam const char *ptr_monam(const monsters *mon, char desc, bool force_seen = false); diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index 9c70d802a2..0057b36025 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -571,7 +571,6 @@ static int place_monster_aux( int mon_type, char behaviour, int target, } } - // give manticores 8 to 16 spike volleys. // they're not spellcasters so this doesn't screw anything up. if (mon_type == MONS_MANTICORE) @@ -604,6 +603,15 @@ static int place_monster_aux( int mon_type, char behaviour, int target, menv[id].foe = target; + // Initialise pandemonium demons + if (menv[id].type == MONS_PANDEMONIUM_DEMON) + { + ghost_demon ghost; + ghost.init_random_demon(); + menv[id].set_ghost(ghost); + menv[id].pandemon_init(); + } + mark_interesting_monst(&menv[id], behaviour); if (player_monster_visible(&menv[id]) && mons_near(&menv[id])) diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index 0b3f48b85c..0b1fc60250 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -772,39 +772,15 @@ void monster_die(monsters *monster, char killer, int i, bool silent) monster_cleanup(monster); if ( xom_will_act ) - Xom_acts(true, xom_will_act, false); - + Xom_acts(true, xom_will_act, false); } // end monster_die void monster_cleanup(monsters *monster) { unsigned int monster_killed = monster_index(monster); - int dmi = 0; - - for (unsigned char j = 0; j < NUM_MON_ENCHANTS; j++) - monster->enchantment[j] = ENCH_NONE; - - monster->flags = 0; - monster->type = -1; - monster->hit_points = 0; - monster->max_hit_points = 0; - monster->hit_dice = 0; - monster->ac = 0; - monster->ev = 0; - monster->speed_increment = 0; - monster->attitude = ATT_HOSTILE; - monster->behaviour = BEH_SLEEP; - monster->foe = MHITNOT; - - if (in_bounds(monster->x, monster->y)) - mgrd[monster->x][monster->y] = NON_MONSTER; - - for (dmi = MSLOT_GOLD; dmi >= MSLOT_WEAPON; dmi--) - { - monster->inv[dmi] = NON_ITEM; - } + monster->reset(); - for (dmi = 0; dmi < MAX_MONSTERS; dmi++) + for (int dmi = 0; dmi < MAX_MONSTERS; dmi++) { if (menv[dmi].foe == monster_killed) menv[dmi].foe = MHITNOT; @@ -812,7 +788,6 @@ void monster_cleanup(monsters *monster) if (you.pet_target == monster_killed) you.pet_target = MHITNOT; - } // end monster_cleanup() static bool jelly_divide(struct monsters * parent) @@ -1037,7 +1012,7 @@ bool monster_polymorph( struct monsters *monster, int targetc, int power ) strcat( str_polymon, "something you cannot see!" ); else { - strcat( str_polymon, monam( 250, targetc, true, DESC_NOCAP_A ) ); + strcat( str_polymon, monam( NULL, 250, targetc, true, DESC_NOCAP_A ) ); if (targetc == MONS_PULSATING_LUMP) strcat( str_polymon, " of flesh" ); @@ -2371,7 +2346,7 @@ static void handle_nearby_ability(struct monsters *monster) break; case MONS_PANDEMONIUM_DEMON: - if (ghost.values[ GVAL_DEMONLORD_CYCLE_COLOUR ]) + if (monster->ghost->values[ GVAL_DEMONLORD_CYCLE_COLOUR ]) monster->colour = random_colour(); break; } @@ -3164,7 +3139,7 @@ static bool handle_spell( monsters *monster, bolt & beem ) return (false); } else if (monster->type == MONS_PANDEMONIUM_DEMON - && !ghost.values[ GVAL_DEMONLORD_SPELLCASTER ]) + && !monster->ghost->values[ GVAL_DEMONLORD_SPELLCASTER ]) { return (false); } @@ -4313,13 +4288,10 @@ static bool handle_pickup(struct monsters *monster) case OBJ_GOLD: //mv - monsters now pick up gold (19 May 2001) if (monsterNearby) { - - strcpy(info, monam( monster->number, monster->type, - player_monster_visible( monster ), - DESC_CAP_THE )); - - strcat(info, " picks up some gold."); - mpr(info); + mprf("%s picks up some gold.", + monam( monster, monster->number, monster->type, + player_monster_visible( monster ), + DESC_CAP_THE )); } if (monster->inv[MSLOT_GOLD] != NON_ITEM) diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 7cccd7b502..611ca70bc5 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -1331,6 +1331,28 @@ int player_prot_life(bool calc_unid) return (pl); } +// Returns the movement speed for a player ghost. Note that this is a real +// speed, not a movement cost, so higher is better. +int player_ghost_base_movement_speed() +{ + int speed = you.species == SP_NAGA? 8 : 10; + + if (you.mutation[MUT_FAST]) + speed += you.mutation[MUT_FAST] + 1; + + if (player_equip_ego_type( EQ_BOOTS, SPARM_RUNNING )) + speed += 2; + + // Cap speeds. + if (speed < 6) + speed = 6; + + if (speed > 13) + speed = 13; + + return (speed); +} + // New player movement speed system... allows for a bit more that // "player runs fast" and "player walks slow" in that the speed is // actually calculated (allowing for centaurs to get a bonus from @@ -4553,11 +4575,6 @@ void player::init() num_gifts[i] = 0; } - ghost.name[0] = 0; - - for (int i = 0; i < NUM_GHOST_VALUES; i++) - ghost.values[i] = 0; - for (int i = EQ_WEAPON; i < NUM_EQUIP; i++) equip[i] = -1; diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index 4dd31f58f2..2d68ade989 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -474,4 +474,6 @@ bool is_grid_dangerous(int grid); void run_macro(const char *macroname = NULL); +int player_ghost_base_movement_speed(); + #endif diff --git a/crawl-ref/source/tags.cc b/crawl-ref/source/tags.cc index a95aad09d4..351aff12ea 100644 --- a/crawl-ref/source/tags.cc +++ b/crawl-ref/source/tags.cc @@ -116,6 +116,9 @@ static void tag_missing_level_attitude(); static void tag_construct_ghost(struct tagHeader &th); static void tag_read_ghost(struct tagHeader &th, char minorVersion); +static void marshallGhost(tagHeader &th, const ghost_demon &ghost); +static ghost_demon unmarshallGhost( tagHeader &th ); + // provide a wrapper for file writing, just in case. int write2(FILE * file, const char *buffer, unsigned int count) { @@ -324,6 +327,21 @@ void unmarshallString(struct tagHeader &th, char *data, int maxSize) th.offset += len; } +std::string unmarshallString(tagHeader &th, int maxSize) +{ + if (maxSize <= 0) + return (""); + char *buffer = new char [maxSize]; + if (!buffer) + return (""); + *buffer = 0; + unmarshallString(th, buffer, maxSize); + const std::string res = buffer; + delete [] buffer; + + return (res); +} + // boolean (to avoid system-dependant bool implementations) void marshallBoolean(struct tagHeader &th, bool data) { @@ -592,7 +610,7 @@ void tag_set_expected(char tags[], int fileType) tags[i] = 1; break; case TAGTYPE_LEVEL: - if (i >= TAG_LEVEL && i <= TAG_LEVEL_ATTITUDE) + if (i >= TAG_LEVEL && i <= TAG_LEVEL_ATTITUDE && i != TAG_GHOST) tags[i] = 1; break; case TAGTYPE_GHOST: @@ -1330,6 +1348,13 @@ static void tag_construct_level_monsters(struct tagHeader &th) marshallShort(th, m.spells[j]); marshallByte(th, m.god); + + if (m.type == MONS_PLAYER_GHOST || m.type == MONS_PANDEMONIUM_DEMON) + { + // *Must* have ghost field set. + ASSERT(m.ghost.get()); + marshallGhost(th, *m.ghost); + } } } @@ -1474,40 +1499,45 @@ static void tag_read_level_monsters(struct tagHeader &th, char minorVersion) for (i = 0; i < count; i++) { - menv[i].ac = unmarshallByte(th); - menv[i].ev = unmarshallByte(th); - menv[i].hit_dice = unmarshallByte(th); - menv[i].speed = unmarshallByte(th); + monsters &m = menv[i]; + + m.ac = unmarshallByte(th); + m.ev = unmarshallByte(th); + m.hit_dice = unmarshallByte(th); + m.speed = unmarshallByte(th); // Avoid sign extension when loading files (Elethiomel's hang) - menv[i].speed_increment = (unsigned char) unmarshallByte(th); - menv[i].behaviour = unmarshallByte(th); - menv[i].x = unmarshallByte(th); - menv[i].y = unmarshallByte(th); - menv[i].target_x = unmarshallByte(th); - menv[i].target_y = unmarshallByte(th); - menv[i].flags = unmarshallLong(th); + m.speed_increment = (unsigned char) unmarshallByte(th); + m.behaviour = unmarshallByte(th); + m.x = unmarshallByte(th); + m.y = unmarshallByte(th); + m.target_x = unmarshallByte(th); + m.target_y = unmarshallByte(th); + m.flags = unmarshallLong(th); for (j = 0; j < ecount; j++) - menv[i].enchantment[j] = unmarshallByte(th); + m.enchantment[j] = unmarshallByte(th); - menv[i].type = unmarshallShort(th); - menv[i].hit_points = unmarshallShort(th); - menv[i].max_hit_points = unmarshallShort(th); - menv[i].number = unmarshallShort(th); + m.type = unmarshallShort(th); + m.hit_points = unmarshallShort(th); + m.max_hit_points = unmarshallShort(th); + m.number = unmarshallShort(th); - menv[i].colour = unmarshallShort(th); + m.colour = unmarshallShort(th); for (j = 0; j < icount; j++) - menv[i].inv[j] = unmarshallShort(th); + m.inv[j] = unmarshallShort(th); for (j = 0; j < NUM_MONSTER_SPELL_SLOTS; ++j) - menv[i].spells[j] = unmarshallShort(th); + m.spells[j] = unmarshallShort(th); - menv[i].god = (god_type) unmarshallByte(th); + m.god = (god_type) unmarshallByte(th); + + if (m.type == MONS_PLAYER_GHOST || m.type == MONS_PANDEMONIUM_DEMON) + m.set_ghost( unmarshallGhost(th) ); // place monster - if (menv[i].type != -1) - mgrd[menv[i].x][menv[i].y] = i; + if (m.type != -1) + mgrd[m.x][m.y] = i; } } @@ -1579,28 +1609,51 @@ void tag_missing_level_attitude() // ------------------------------- ghost tags ---------------------------- // -static void tag_construct_ghost(struct tagHeader &th) +static void marshallGhost(tagHeader &th, const ghost_demon &ghost) { - int i; - - marshallString(th, ghost.name, 20); + marshallString(th, ghost.name.c_str(), 20); // how many ghost values? - marshallByte(th, 20); + marshallByte(th, NUM_GHOST_VALUES); - for (i = 0; i < 20; i++) + for (int i = 0; i < NUM_GHOST_VALUES; i++) marshallShort( th, ghost.values[i] ); } -static void tag_read_ghost(struct tagHeader &th, char minorVersion) +static void tag_construct_ghost(struct tagHeader &th) { - int i, count_c; + // How many ghosts? + marshallShort(th, ghosts.size()); - unmarshallString(th, ghost.name, 20); + for (int i = 0, size = ghosts.size(); i < size; ++i) + marshallGhost(th, ghosts[i]); +} + +static ghost_demon unmarshallGhost( tagHeader &th ) +{ + ghost_demon ghost; + + ghost.name = unmarshallString(th, 20); // how many ghost values? - count_c = unmarshallByte(th); + int count_c = unmarshallByte(th); - for (i = 0; i < count_c; i++) + if (count_c > NUM_GHOST_VALUES) + count_c = NUM_GHOST_VALUES; + + for (int i = 0; i < count_c; i++) ghost.values[i] = unmarshallShort(th); + + return (ghost); +} + +static void tag_read_ghost(struct tagHeader &th, char minorVersion) +{ + int nghosts = unmarshallShort(th); + + if (nghosts < 1 || nghosts > MAX_GHOSTS) + return; + + for (int i = 0; i < nghosts; ++i) + ghosts.push_back( unmarshallGhost(th) ); } diff --git a/crawl-ref/source/tags.h b/crawl-ref/source/tags.h index 553cdaee6d..3f98a0d216 100644 --- a/crawl-ref/source/tags.h +++ b/crawl-ref/source/tags.h @@ -51,6 +51,7 @@ long unmarshallLong(struct tagHeader &th); float unmarshallFloat(struct tagHeader &th); bool unmarshallBoolean(struct tagHeader &th); void unmarshallString(struct tagHeader &th, char *data, int maxSize); +std::string unmarshallString(tagHeader &th, int maxSize); std::string make_date_string( time_t in_date ); time_t parse_date_string( char[20] ); |