From 7ce4f0c31ffe27368d7a62298dcb45fd244904b1 Mon Sep 17 00:00:00 2001 From: zelgadis Date: Wed, 7 Jan 2009 12:03:36 +0000 Subject: Yet another fix to the arena win/loss/tie logic. During arena mode don't reserve any of mitm[] when creating new items, and when mitm[] fills up call arena_cull_items() instead of _cull_items(), since in arena mode we can cull via how boring/interesting the items are rather than having to consider game balance and fairness to the player. Allow the arena to veto monster placement, rather than culling them immediately after they're placed. New arena tags: * "no_bands" prevents band members from being placed. * "move_spawners" teleports test spawners every turn to spread their summons randomly over the arena. * "ban_glyphs:" lists a set of text glyphs of types of monsters which shouldn't be allowed in the arena. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@8300 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/arena.cc | 282 +++++++++++++++++++++++++++++++++++++++---- crawl-ref/source/arena.h | 6 + crawl-ref/source/items.cc | 16 ++- crawl-ref/source/monplace.cc | 9 ++ 4 files changed, 286 insertions(+), 27 deletions(-) diff --git a/crawl-ref/source/arena.cc b/crawl-ref/source/arena.cc index 530c421532..aca2daedd9 100644 --- a/crawl-ref/source/arena.cc +++ b/crawl-ref/source/arena.cc @@ -16,7 +16,9 @@ REVISION("$Rev$"); #include "mon-util.h" #include "monstuff.h" #include "monplace.h" +#include "mstuff2.h" #include "output.h" +#include "randart.h" #include "skills2.h" #include "spl-util.h" #include "state.h" @@ -69,11 +71,16 @@ namespace arena bool allow_chain_summons = true; bool allow_zero_xp = false; bool allow_immobile = true; + bool allow_bands = true; bool name_monsters = false; bool random_uniques = false; bool real_summons = false; + bool do_move_spawners = false; std::vector uniques_list; + std::vector spawner_list; + + bool banned_glyphs[256]; std::string arena_type = ""; faction faction_a(true); @@ -189,6 +196,8 @@ namespace arena void setup_level() { + spawner_list.clear(); + if (place.is_valid()) { you.level_type = place.level_type; @@ -271,8 +280,10 @@ namespace arena allow_summons = !strip_tag(spec, "no_summons"); allow_immobile = !strip_tag(spec, "no_immobile"); + allow_bands = !strip_tag(spec, "no_bands"); allow_zero_xp = strip_tag(spec, "allow_zero_xp"); real_summons = strip_tag(spec, "real_summons"); + do_move_spawners = strip_tag(spec, "move_spawners"); cycle_random = strip_tag(spec, "cycle_random"); name_monsters = strip_tag(spec, "names"); @@ -312,6 +323,10 @@ namespace arena "vault.")); } + std::string glyphs = strip_tag_prefix(spec, "ban_glyphs:"); + for (unsigned int i = 0; i < glyphs.size(); i++) + banned_glyphs[(int)glyphs[i]] = true; + std::vector factions = split_string(" v ", spec); if (factions.size() == 1) @@ -513,7 +528,7 @@ namespace arena faction_a.won = false; } } - } // count_foes() + } // Returns true as long as at least one member of each faction is alive. bool fight_is_on() @@ -593,6 +608,7 @@ namespace arena switch(chan) { case MSGCH_ERROR: prefix = "ERROR: "; break; + case MSGCH_WARN: prefix = "WARN: "; break; case MSGCH_DIAGNOSTICS: prefix = "DIAG: "; break; case MSGCH_SOUND: prefix = "SOUND: "; break; @@ -605,6 +621,24 @@ namespace arena } } + // Move test spawners to a new position every turn to scatter each + // faction all over the arena. + void move_spawners() + { + if (!do_move_spawners) + return; + + for (unsigned int i = 0; i < spawner_list.size(); i++) + { + monsters* mon = &menv[spawner_list[i]]; + + if (!mon->alive() || mon->type != MONS_TEST_SPAWNER) + continue; + + monster_teleport(mon, true, true); + } + } + void do_fight() { mesclr(true); @@ -625,6 +659,10 @@ namespace arena } } +#ifdef DEBUG_DIAGNOSTICS + mprf("---- Turn #%d ----", turns); +#endif + // Check the consistency of our book-keeping every 100 turns. if ((turns++ % 100) == 0) count_foes(); @@ -638,6 +676,7 @@ namespace arena you.hunger = 10999; //report_foes(); world_reacts(); + move_spawners(); delay(Options.arena_delay); mesclr(); dump_messages(); @@ -847,6 +886,7 @@ monster_type arena_pick_random_monster(const level_id &place, int power, monster_type type = (monster_type) uniques[random2(uniques.size())]; you.unique_creatures[type] = false; + return (type); } @@ -881,10 +921,20 @@ bool arena_veto_random_monster(monster_type type) return (true); if (!arena::allow_zero_xp && mons_class_flag(type, M_NO_EXP_GAIN)) return (true); + if (arena::banned_glyphs[mons_char(type)]) + return (true); return (false); } +bool arena_veto_place_monster(const mgen_data &mg, bool first_band_member, + const coord_def& pos) +{ + return (!arena::allow_bands && !first_band_member + || arena::banned_glyphs[mons_char(mg.cls)]); +} + + void arena_placed_monster(monsters *monster, const mgen_data &mg, bool first_band_member) { @@ -893,6 +943,13 @@ void arena_placed_monster(monsters *monster, const mgen_data &mg, else if (monster->attitude == ATT_HOSTILE) arena::faction_b.active_members++; + if (monster->type == MONS_TEST_SPAWNER) + arena::spawner_list.push_back(monster->mindex()); + +#ifdef DEBUG_DIAGNOSTICS + mprf("%s enters the arena!", monster->name(DESC_CAP_A).c_str()); +#endif + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) { short it = monster->inv[i]; @@ -939,39 +996,212 @@ void arena_monster_died(monsters *monster, killer_type killer, else if (monster->attitude == ATT_HOSTILE) arena::faction_b.active_members--; - const monsters* atk = - (invalid_monster_index(killer_index) || menv[killer_index].type == -1) - ? NULL : &menv[killer_index]; - - if (atk && atk->alive()) + if (arena::faction_a.active_members > 0 + && arena::faction_b.active_members <= 0) + { + arena::faction_a.won = true; + } + else if (arena::faction_b.active_members > 0 + && arena::faction_a.active_members <= 0) { - if (arena::faction_a.active_members > 0 - && arena::faction_b.active_members <= 0) + arena::faction_b.won = true; + } + // Everyone is dead. Is it a tie, or something else? + else if (arena::faction_a.active_members <= 0 + && arena::faction_b.active_members <= 0) + { + if (monster->flags & MF_HARD_RESET && !MON_KILL(killer)) { - arena::faction_a.won = true; + end(1, false, "Last arena monster was dismissed."); } - else if (arena::faction_b.active_members > 0 - && arena::faction_a.active_members <= 0) + // If all monsters are dead and the last one to die is a giant spore + // or ball lightning then that monster's faction is the winner, + // since self destruction is their purpose. But if a trap causes + // the spore to explode and that kills everything it's a tie since + // it counts as the trap killing everyone. + else if (mons_self_destructs(monster) && MON_KILL(killer)) { - arena::faction_b.won = true; + if (monster->attitude == ATT_FRIENDLY) + arena::faction_a.won = true; + else if (monster->attitude == ATT_HOSTILE) + arena::faction_b.won = true; } } - // If all monsters are dead and the last one to die is a giant spore - // or ball lightning then that monster's faction is the winner, - // since self destruction is their purpose. But if a trap causes - // the spore to explode and that kills everything it's a tie since - // it counts as the trap killing everyone. - else if (arena::faction_a.active_members <= 0 - && arena::faction_b.active_members <= 0 - && mons_self_destructs(monster) - && MON_KILL(killer)) +} + +static bool _sort_corpses(int a, int b) +{ + return (mitm[a].special < mitm[b].special); +} + +#define DESTROY_ITEM(i) \ +{ \ + destroy_item(i, true); \ + cull_count++; \ + if (first_avail == NON_ITEM) \ + first_avail = i; \ +} + +int arena_cull_items() +{ + // Try to cull 15% of items. + const int cull_target = MAX_ITEMS * 15 / 100; + + int cull_count = 0; + + std::vector artefacts; + std::vector egos; + std::vector ammo; + std::vector wands; + std::vector potions; + std::vector scrolls; + std::vector corpses; + + int first_avail = NON_ITEM; + +#ifdef DEBUG_DIAGNOSTICS + int non_floor_items = 0; +#endif + + for (int i = 0; i < MAX_ITEMS; i++) { - if (monster->attitude == ATT_FRIENDLY) - arena::faction_a.won = true; - else if (monster->attitude == ATT_HOSTILE) - arena::faction_b.won = true; + // All items in mitm[] are valid when we're called. + const item_def &item(mitm[i]); + +#ifdef DEBUG_DIAGNOSTICS + if (!is_valid_item(item)) + { + mprf("Invalid item in arena_cull_items()!!", MSGCH_ERROR); + non_floor_items++; + continue; + } +#endif + + // We want floor items. + if (!in_bounds(item.pos)) + { +#ifdef DEBUG_DIAGNOSTICS + if (item.pos == coord_def(-1, -1)) + mprf("Player item in arena_cull_items()!!", MSGCH_ERROR); + non_floor_items++; +#endif + continue; + } + + bool cull = false; + switch(item.base_type) + { + case OBJ_WEAPONS: + case OBJ_ARMOUR: + if (is_artefact(item)) + artefacts.push_back(i); + else if (item.special != 0) + egos.push_back(i); + else + // Always cull boring equipment. + cull = true; + break; + + case OBJ_MISSILES: + if (item.sub_type == MI_JAVELIN + || item.sub_type == MI_THROWING_NET) + { + ammo.push_back(i); + } + else + // Arrows/needles/etc on the floor are just clutter. + cull = true; + break; + + case OBJ_WANDS: + if (item.plus == 0) + // Get rid of empty wands. + cull = true; + else + wands.push_back(i); + break; + + case OBJ_SCROLLS: + scrolls.push_back(i); + break; + + case OBJ_POTIONS: + potions.push_back(i); + break; + + case OBJ_CORPSES: + if (item.sub_type == CORPSE_SKELETON) + // If it's rotted away into a skeleton then there's no one + // around who can uses corpses. + cull = true; + else + corpses.push_back(i); + break; + + default: + // Get rid of everything else. + cull = true; + } // switch(item.base_type) + + if (cull) + DESTROY_ITEM(i); + } // for (int i = 0; i < MAX_ITEMS; i++) + + if (cull_count >= cull_target) + { +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Arena culled %d misc items, done.", + cull_count); +#endif + return (first_avail); } -} + +#ifdef DEBUG_DIAGNOSTICS + if (cull_count > 0) + mprf(MSGCH_DIAGNOSTICS, "Arena culled %d misc items.", cull_count); +#endif + + // Get rid of oldest corpses first. + std::sort(corpses.begin(), corpses.end(), _sort_corpses); + + std::vector* lists[] = {&corpses, &ammo, &egos, &potions, + &scrolls, &wands, &artefacts, NULL}; + +#ifdef DEBUG_DIAGNOSTICS + const char* list_names[] = {"corpses", "ammo", "egos", "potions", + "scrolls", "wands", "artefacts"}; +#endif + + for (int i = 0; lists[i] != NULL; i++) + { + std::vector* vec = lists[i]; + + for (unsigned int j = 0; j < vec->size(); j++) + { + DESTROY_ITEM((*vec)[j]); + if (cull_count >= cull_target) + { +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Arena culled %d %s, done.", j + 1, + list_names[i]); +#endif + return (first_avail); + } + } // for (unsigned int j = 0; j < vec->size(); j++) +#ifdef DEBUG_DIAGNOSTICS + if (vec->size() > 0) + mprf(MSGCH_DIAGNOSTICS, "Arena culled %d %s.", vec->size(), + list_names[i]); +#endif + } // for (int i = 0; lists[i] != NULL; i++) + +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Arena only culled %d of a desired %d items.", + cull_count, cull_target); +#endif // DEBUG_DIAGNOSTICS + + return (first_avail); +} // arena_cull_items ///////////////////////////////////////////////////////////////////////////// diff --git a/crawl-ref/source/arena.h b/crawl-ref/source/arena.h index 8352860538..4547957b1a 100644 --- a/crawl-ref/source/arena.h +++ b/crawl-ref/source/arena.h @@ -8,6 +8,8 @@ class level_id; class monsters; class mgen_data; +struct coord_def; + void run_arena(); monster_type arena_pick_random_monster(const level_id &place, int power, @@ -15,9 +17,13 @@ monster_type arena_pick_random_monster(const level_id &place, int power, bool arena_veto_random_monster(monster_type type); +bool arena_veto_place_monster(const mgen_data &mg, bool first_band_member, + const coord_def& pos); void arena_placed_monster(monsters *monster, const mgen_data &mg, bool first_band_member); void arena_monster_died(monsters *monster, killer_type killer, int killer_index, bool silent); + +int arena_cull_items(); #endif diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index a05a5d8e9e..200656794c 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -26,6 +26,7 @@ REVISION("$Rev$"); #include "externs.h" +#include "arena.h" #include "beam.h" #include "branch.h" #include "cloud.h" @@ -60,6 +61,7 @@ REVISION("$Rev$"); #include "skills2.h" #include "spl-book.h" #include "spl-util.h" +#include "state.h" #include "stuff.h" #include "stash.h" #include "tiles.h" @@ -323,6 +325,9 @@ int get_item_slot( int reserve ) { ASSERT( reserve >= 0 ); + if (crawl_state.arena) + reserve = 0; + int item = NON_ITEM; for (item = 0; item < (MAX_ITEMS - reserve); item++) @@ -331,7 +336,16 @@ int get_item_slot( int reserve ) if (item >= MAX_ITEMS - reserve) { - item = (reserve <= 10) ? _cull_items() : NON_ITEM; + if (crawl_state.arena) + { + item = arena_cull_items(); + // If arena_cull_items() can't free up any space then + // _cull_items() won't be able to either, so give up. + if (item == NON_ITEM) + return (NON_ITEM); + } + else + item = (reserve <= 10) ? _cull_items() : NON_ITEM; if (item == NON_ITEM) return (NON_ITEM); diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index d1ace875bf..0a0574b523 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -1003,6 +1003,12 @@ static int _place_monster_aux(const mgen_data &mg, ASSERT(mgrd(fpos) == NON_MONSTER); + if (crawl_state.arena) + { + if (arena_veto_place_monster(mg, first_band_member, fpos)) + return (-1); + } + // Now, actually create the monster. (Wheeee!) menv[id].type = mg.cls; menv[id].base_monster = mg.base_type; @@ -2041,6 +2047,9 @@ static int _ood_limit() void mark_interesting_monst(struct monsters* monster, beh_type behaviour) { + if (crawl_state.arena) + return; + bool interesting = false; // Unique monsters are always intersting -- cgit v1.2.3-54-g00ecf