From eef51d7a1ec7ce7e471d76f82154d2b147d6228b Mon Sep 17 00:00:00 2001 From: Charles Otto Date: Fri, 6 Nov 2009 20:46:30 -0500 Subject: Move Feawn's abilities to godabil.h/cc --- crawl-ref/source/godabil.cc | 870 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 870 insertions(+) (limited to 'crawl-ref/source/godabil.cc') diff --git a/crawl-ref/source/godabil.cc b/crawl-ref/source/godabil.cc index eaaa52e861..b176f06005 100644 --- a/crawl-ref/source/godabil.cc +++ b/crawl-ref/source/godabil.cc @@ -5,6 +5,9 @@ #include "AppHdr.h" +#include + +#include "beam.h" #include "cloud.h" #include "colour.h" #include "coord.h" @@ -20,6 +23,7 @@ #include "misc.h" #include "mon-act.h" #include "mon-behv.h" +#include "monplace.h" #include "monstuff.h" #include "mon-util.h" #include "mutation.h" @@ -319,6 +323,872 @@ bool feawn_passthrough(const monsters * target) || target->attitude != ATT_HOSTILE)); } + +// Turns corpses in LOS into skeletons and grows toadstools on them. +// Can also turn zombies into skeletons and destroy ghoul-type monsters. +// Returns the number of corpses consumed. +int fungal_bloom() +{ + int seen_mushrooms = 0; + int seen_corpses = 0; + + int processed_count = 0; + bool kills = false; + + for (radius_iterator i(you.pos(), LOS_RADIUS); i; ++i) + { + actor *target = actor_at(*i); + if (target && (target->atype() == ACT_PLAYER + || target->is_summoned())) + { + continue; + } + + monsters * mons = monster_at(*i); + + if (mons && mons->mons_species() != MONS_TOADSTOOL) + { + switch (mons_genus(mons->mons_species())) + { + case MONS_ZOMBIE_SMALL: + // Maybe turn a zombie into a skeleton. + if (mons_skeleton(mons_zombie_base(mons))) + { + processed_count++; + + monster_type skele_type = MONS_SKELETON_LARGE; + if (mons_zombie_size(mons_zombie_base(mons)) == Z_SMALL) + skele_type = MONS_SKELETON_SMALL; + + simple_monster_message(mons, "'s flesh rots away."); + + mons->upgrade_type(skele_type, true, true); + behaviour_event(mons, ME_ALERT, MHITYOU); + + continue; + } + // Else fall through and destroy the zombie. + // Ghoul-type monsters are always destroyed. + case MONS_GHOUL: + { + simple_monster_message(mons, " rots away and dies."); + + coord_def pos = mons->pos(); + int colour = mons->colour; + int corpse = monster_die(mons, KILL_MISC, NON_MONSTER, true); + kills = true; + + // If a corpse didn't drop, create a toadstool. + // If one did drop, we will create toadstools from it as usual + // later on. + if (corpse < 0) + { + const int mushroom = create_monster( + mgen_data(MONS_TOADSTOOL, + BEH_FRIENDLY, + 0, + 0, + pos, + MHITNOT, + MG_FORCE_PLACE, + GOD_NO_GOD, + MONS_NO_MONSTER, + 0, + colour), + false); + + if (mushroom != -1) + seen_mushrooms++; + + processed_count++; + + continue; + } + break; + } + + default: + continue; + } + } + + for (stack_iterator j(*i); j; ++j) + { + bool corpse_on_pos = false; + if (j->base_type == OBJ_CORPSES && j->sub_type == CORPSE_BODY) + { + corpse_on_pos = true; + int trial_prob = mushroom_prob(*j); + + processed_count++; + int target_count = 1 + binomial_generator(20, trial_prob); + + int seen_per; + spawn_corpse_mushrooms(*j, target_count, seen_per, + BEH_FRIENDLY, true); + + seen_mushrooms += seen_per; + + // Either turn this corpse into a skeleton or destroy it. + if (mons_skeleton(j->plus)) + turn_corpse_into_skeleton(*j); + else + destroy_item(j->index()); + } + + if (corpse_on_pos && observe_cell(*i)) + seen_corpses++; + } + } + + if (seen_mushrooms > 0) + { + mushroom_spawn_message(seen_mushrooms, seen_corpses); + } + + if (kills) + mprf("That felt like a moral victory."); + + return (processed_count); +} + +static int _create_plant(coord_def & target) +{ + if (actor_at(target) || !mons_class_can_pass(MONS_PLANT, grd(target))) + return (0); + + const int plant = create_monster(mgen_data + (MONS_PLANT, + BEH_FRIENDLY, + 0, + 0, + target, + MHITNOT, + MG_FORCE_PLACE, GOD_FEAWN)); + + + if (plant != -1) + { + env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT; + if (observe_cell(target)) + mpr("A plant grows up from the ground."); + } + + + return (plant != -1); +} + +bool sunlight() +{ + int c_size = 5; + int x_offset[] = {-1, 0, 0, 0, 1}; + int y_offset[] = { 0,-1, 0, 1, 0}; + + dist spelld; + + bolt temp_bolt; + + temp_bolt.colour = YELLOW; + direction(spelld, DIR_TARGET, TARG_HOSTILE, LOS_RADIUS, false, false, + false, true, "Select sunlight destination", NULL, + true); + + if (!spelld.isValid) + return (false); + + coord_def base = spelld.target; + + int evap_count = 0; + int plant_count = 0; + + // FIXME: Uncomfortable level of code duplication here but the explosion + // code in bolt subjects the input radius to r*(r+1) for the threshold and + // since r is an integer we can never get just the 4-connected neighbours. + // Anyway the bolt code doesn't seem to be well set up to handle the + // 'occasional plant' gimmick. + for (int i = 0;i < c_size; ++i) + { + coord_def target = base; + target.x += x_offset[i]; + target.y += y_offset[i]; + + if (!in_bounds(target) || feat_is_solid(grd(target))) + continue; + + temp_bolt.explosion_draw_cell(target); + + actor *victim = actor_at(target); + + // If this is a water square we will evaporate it. + dungeon_feature_type ftype = grd(target); + + switch (ftype) + { + case DNGN_SHALLOW_WATER: + ftype = DNGN_FLOOR; + break; + + case DNGN_DEEP_WATER: + ftype = DNGN_SHALLOW_WATER; + break; + + default: + break; + } + + if (grd(target) != ftype) + { + grd(target) = ftype; + if (observe_cell(target)) + evap_count++; + } + + monsters *mons = monster_at(target); + + // Pop submerged status. + if (mons && mons_habitat(mons) == HT_WATER) + { + mons->del_ench(ENCH_SUBMERGED); + if (ftype == DNGN_FLOOR) + mons->add_ench(mon_enchant(ENCH_AQUATIC_LAND, 0, KC_YOU)); + } + + if (victim) + { + if (!mons) + you.backlight(); + else + backlight_monsters(target, 1, 0); + } + else if (one_chance_in(100) + && ftype >= DNGN_FLOOR_MIN + && ftype <= DNGN_FLOOR_MAX) + { + // Create a plant. + const int plant = create_monster(mgen_data(MONS_PLANT, + BEH_HOSTILE, + 0, + 0, + target, + MHITNOT, + MG_FORCE_PLACE, + GOD_FEAWN)); + + if (plant != -1 && observe_cell(target)) + plant_count++; + } + } + // move the cursor out of the way (it looks weird ok) +#ifndef USE_TILE + cgotoxy(base.x, base.y, GOTO_DNGN); +#endif + delay(200); + + if (plant_count) + { + mprf("%s grow%s in the sunlight.", + (plant_count > 1 ? "Some plants": "A plant"), + (plant_count > 1 ? "": "s")); + } + + if (evap_count) + mprf("Some water evaporates in the bright sunlight."); + + return (true); +} + +template +bool less_second(const T & left, const T & right) +{ + return left.second < right.second; +} + +typedef std::pair point_distance; + +// dfs starting at origin, find the distance from the origin to the targets +// (not leaving LOS, not crossing monsters or solid walls) and store that in +// distances +static void _path_distance(coord_def & origin, + std::vector & targets, + std::vector & distances) +{ + std::set exclusion; + std::queue fringe; + fringe.push(point_distance(origin,0)); + + int idx = origin.x + origin.y * X_WIDTH; + exclusion.insert(idx); + + while (!fringe.empty()) + { + point_distance current = fringe.front(); + fringe.pop(); + + // did we hit a target? + for (unsigned i = 0; i < targets.size(); ++i) + { + if (current.first == targets[i]) + { + distances[i] = current.second; + break; + } + } + + for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it) + { + idx = adj_it->x + adj_it->y * X_WIDTH; + if (you.see_cell(*adj_it) + && !feat_is_solid(env.grid(*adj_it)) + && exclusion.insert(idx).second) + { + monsters * temp = monster_at(*adj_it); + if (!temp || (temp->attitude == ATT_HOSTILE + && temp->mons_species() != MONS_PLANT + && temp->mons_species() != MONS_TOADSTOOL + && temp->mons_species() != MONS_FUNGUS + && temp->mons_species() != MONS_BALLISTOMYCETE)) + { + fringe.push(point_distance(*adj_it, current.second+1)); + } + } + } + } +} + +// so we are basically going to compute point to point distance between +// the points of origin and the end points (origins and targets respecitvely) +// We will return a vector consisting of the minimum distances along one +// dimension of the distance matrix. +static void _point_point_distance(std::vector & origins, + std::vector & targets, + bool origin_to_target, + std::vector & distances) +{ + + distances.clear(); + // Consider a matrix where the points of origin form the rows and + // the target points form the column, we want to take the minimum along + // one of those dimensions. + if (origin_to_target) + distances.resize(origins.size(), INT_MAX); + else + distances.resize(targets.size(), INT_MAX); + + std::vector current_distances(targets.size(), 0); + for (unsigned i = 0; i < origins.size(); ++i) + { + for (unsigned j = 0; j < current_distances.size(); ++j) + current_distances[j] = INT_MAX; + + _path_distance(origins[i], targets, current_distances); + + // So we got the distance from a point of origin to one of the + // targets. What should we do with it? + if (origin_to_target) + { + // The minimum of current_distances is points(i) + int min_dist = current_distances[0]; + for (unsigned j = 1; i < current_distances.size(); ++i) + if (current_distances[j] < min_dist) + min_dist = current_distances[j]; + + distances[i] = min_dist; + } + else + { + for (unsigned j = 0; j < targets.size(); ++j) + { + if (i == 0) + distances[j] = current_distances[j]; + else if (current_distances[j] < distances[j]) + distances[j] = current_distances[j]; + } + } + } +} + +// So the idea is we want to decide which adjacent tiles are in the most 'danger' +// We claim danger is proportional to the minimum distances from the point to a +// (hostile) monster. This function carries out at most 8 depth-first searches +// to calculate the distances in question. In practice it should be called for +// at most 7 searches since 8 (all adjacent free, > 8 monsters in view) can be +// special cased easily. +bool prioritise_adjacent(const coord_def &target, std::vector & candidates) +{ + radius_iterator los_it(target, LOS_RADIUS, true, true, true); + + std::vector mons_positions; + // collect hostile monster positions in LOS + for ( ; los_it; ++los_it) + { + monsters *hostile = monster_at(*los_it); + + if (hostile && hostile->attitude == ATT_HOSTILE) + mons_positions.push_back(hostile->pos()); + } + + if (mons_positions.empty()) + { + std::random_shuffle(candidates.begin(), candidates.end()); + return (true); + } + + bool squares_to_monsters = mons_positions.size() > candidates.size(); + + std::vector distances; + + // So the idea is we will search from either possible plant locations to + // monsters or from monsters to possible plant locations, but honestly the + // implementation is unnecessarily tense and doing plants to monsters all + // the time would be fine. Yet I'm reluctant to change it because it does + // work. + if (squares_to_monsters) + { + _point_point_distance(candidates, mons_positions, + squares_to_monsters, distances); + } + else + { + _point_point_distance(mons_positions, candidates, + squares_to_monsters, distances); + } + + std::vector possible_moves(candidates.size()); + + for (unsigned i = 0; i < possible_moves.size(); ++i) + { + possible_moves[i].first = candidates[i]; + possible_moves[i].second = distances[i]; + } + + std::sort(possible_moves.begin(), possible_moves.end(), + less_second); + + for (unsigned i = 0; i < candidates.size(); ++i) + candidates[i] = possible_moves[i].first; + + return (true); +} + +// Prompt the user to select a stack of fruit from their inventory. The +// user can optionally select only a partial stack of fruit (the count +// variable will store the number of fruit the user wants). Return the +// index of the item selected in the user's inventory, or a negative +// number if the prompt failed (user cancelled or had no fruit). +int _prompt_for_fruit(int & count, const char * prompt_string) +{ + int rc = prompt_invent_item(prompt_string, + MT_INVLIST, + OSEL_FRUIT, + true, + true, + true, + '\0', + -1, + &count); + + if (prompt_failed(rc)) + return (rc); + + // Return PROMPT_INAPPROPRIATE if the 'object selected isn't + // actually fruit. + if (!is_fruit(you.inv[rc])) + return (PROMPT_INAPPROPRIATE); + + // Handle it if the user lies about the amount of fruit available. + if (count > you.inv[rc].quantity) + count = you.inv[rc].quantity; + + return (rc); +} + +// Create a ring or partial ring around the caster. The user is +// prompted to select a stack of fruit, and then plants are placed on open +// squares adjacent to the user. Of course, one piece of fruit is +// consumed per plant, so a complete ring may not be formed. +bool plant_ring_from_fruit() +{ + int possible_count; + int created_count = 0; + int rc = _prompt_for_fruit(possible_count, + "Use which fruit? [0-9] specify amount"); + + // Prompt failed? + if (rc < 0) + return (false); + + std::vector adjacent; + + for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it) + { + if (mons_class_can_pass(MONS_PLANT, env.grid(*adj_it)) + && !actor_at(*adj_it)) + { + adjacent.push_back(*adj_it); + } + } + + if ((int)adjacent.size() > possible_count) + { + prioritise_adjacent(you.pos(), adjacent); + } + + unsigned target_count = + (possible_count < (int)adjacent.size()) ? possible_count + : adjacent.size(); + + for (unsigned i = 0; i < target_count; ++i) + { + if (_create_plant(adjacent[i])) + created_count++; + } + + dec_inv_item_quantity(rc, created_count); + + return (created_count); +} + +// Create a circle of water around the target, with a radius of +// approximately 2. This turns normal floor tiles into shallow water +// and turns (unoccupied) shallow water into deep water. There is a +// chance of spawning plants or fungus on unoccupied dry floor tiles +// outside of the rainfall area. Return the number of plants/fungi +// created. +int rain(const coord_def &target) +{ + int spawned_count = 0; + for (radius_iterator rad(target, LOS_RADIUS, true, true, true); rad; ++rad) + { + // Adjust the shape of the rainfall slightly to make it look + // nicer. I want a threshold of 2.5 on the euclidean distance, + // so a threshold of 6 prior to the sqrt is close enough. + int rain_thresh = 6; + coord_def local = *rad - target; + + dungeon_feature_type ftype = grd(*rad); + + if (local.abs() > rain_thresh) + { + // Maybe spawn a plant on (dry, open) squares that are in + // LOS but outside the rainfall area. In open space, there + // are 213 squares in LOS, and we are going to drop water on + // (25-4) of those, so if we want x plants to spawn on + // average in open space, the trial probability should be + // x/192. + if (x_chance_in_y(5, 192) + && !actor_at(*rad) + && ftype >= DNGN_FLOOR_MIN + && ftype <= DNGN_FLOOR_MAX) + { + const int plant = create_monster(mgen_data + (coinflip() ? MONS_PLANT : MONS_FUNGUS, + BEH_GOOD_NEUTRAL, + 0, + 0, + *rad, + MHITNOT, + MG_FORCE_PLACE, GOD_FEAWN)); + + if (plant != -1) + spawned_count++; + } + + continue; + } + + // Turn regular floor squares only into shallow water. + if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX) + { + grd(*rad) = DNGN_SHALLOW_WATER; + // Remove blood stains as well. + env.map(*rad).property &= ~(FPROP_BLOODY); + + monsters *mon = monster_at(*rad); + if (mon && mon->has_ench(ENCH_AQUATIC_LAND)) + mon->del_ench(ENCH_AQUATIC_LAND); + } + // We can also turn shallow water into deep water, but we're + // just going to skip cases where there is something on the + // shallow water. Destroying items will probably be annoying, + // and insta-killing monsters is clearly out of the question. + else if (!actor_at(*rad) + && igrd(*rad) == NON_ITEM + && ftype == DNGN_SHALLOW_WATER) + { + grd(*rad) = DNGN_DEEP_WATER; + } + + if (ftype >= DNGN_MINMOVE) + { + // Maybe place a raincloud. + // + // The rainfall area is 20 (5*5 - 4 (corners) - 1 (center)); + // the expected number of clouds generated by a fixed chance + // per tile is 20 * p = expected. Say an Invocations skill + // of 27 gives expected 5 clouds. + int max_expected = 5; + int expected = div_rand_round(max_expected + * you.skills[SK_INVOCATIONS], 27); + + if (x_chance_in_y(expected, 20)) + place_cloud(CLOUD_RAIN, *rad, 10, KC_YOU); + } + + + } + + if (spawned_count > 0) + { + mprf("%s grow%s in the rain.", + (spawned_count > 1 ? "Some plants" : "A plant"), + (spawned_count > 1 ? "" : "s")); + } + + return (spawned_count); +} + +// Destroy corpses in the player's LOS (first corpse on a stack only) +// and make 1 giant spore per corpse. Spores are given the input as +// their starting behavior; the function returns the number of corpses +// processed. +int corpse_spores(beh_type behavior) +{ + int count = 0; + for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); rad; + ++rad) + { + for (stack_iterator stack_it(*rad); stack_it; ++stack_it) + { + if (stack_it->base_type == OBJ_CORPSES + && stack_it->sub_type == CORPSE_BODY) + { + count++; + + int rc = create_monster(mgen_data(MONS_GIANT_SPORE, + behavior, + 0, + 0, + *rad, + MHITNOT, + MG_FORCE_PLACE)); + + if (rc!=-1) + env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT; + + if (mons_skeleton(stack_it->plus)) + turn_corpse_into_skeleton(*stack_it); + else + destroy_item(stack_it->index()); + + break; + } + } + } + + return (count); +} + +struct monster_conversion +{ + monsters * base_monster; + int cost; + monster_type new_type; +}; + +bool operator<(const monster_conversion & left, + const monster_conversion & right) +{ + if (left.cost == right.cost) + return (coinflip()); + + return (left.cost < right.cost); +} + +// Given a monster (which should be a plant/fungus), see if +// evolve_flora() can upgrade it, and set up a monster_conversion +// structure for it. Return true (and fill in possible_monster) if the +// monster can be upgraded, and return false otherwise. +bool _possible_evolution(monsters * input, + monster_conversion & possible_monster) +{ + int plant_cost = 10; + int toadstool_cost = 1; + int fungus_cost = 5; + + possible_monster.base_monster = input; + switch (input->mons_species()) + { + case MONS_PLANT: + possible_monster.cost = plant_cost; + possible_monster.new_type = MONS_OKLOB_PLANT; + break; + + case MONS_FUNGUS: + case MONS_BALLISTOMYCETE: + possible_monster.cost = fungus_cost; + possible_monster.new_type = MONS_WANDERING_MUSHROOM; + break; + + case MONS_TOADSTOOL: + possible_monster.cost = toadstool_cost; + possible_monster.new_type = MONS_BALLISTOMYCETE; + break; + + default: + return (false); + } + + return (true); +} + +bool evolve_flora() +{ + int rc; + int available_count; + + int points_per_fruit = 8; + int oklob_cost = 10; + + float approx_oklob_rate = float(points_per_fruit)/float(oklob_cost); + + char prompt_string[100]; + memset(prompt_string,0,100); + sprintf(prompt_string, + "Use which fruit? %1.1f oklob plants per fruit, [0-9] specify amount", + approx_oklob_rate); + + rc = _prompt_for_fruit(available_count, prompt_string); + + // Prompt failed? + if (rc < 0) + return (false); + + int points = points_per_fruit * available_count; + int starting_points = points; + + std::priority_queue available_targets; + + monster_conversion temp_conversion; + + for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); + rad; ++rad) + { + monsters * target = monster_at(*rad); + + if (!target) + continue; + + if (_possible_evolution(target, temp_conversion)) + available_targets.push(temp_conversion); + } + + // Nothing available to upgrade. + if (available_targets.empty()) + { + mpr("No flora in sight can be evolved."); + return (false); + } + + int plants_evolved = 0; + int toadstools_evolved = 0; + int fungi_evolved = 0; + + while (!available_targets.empty() && points > 0) + { + monster_conversion current_target = available_targets.top(); + + monsters * current_plant = current_target.base_monster; + available_targets.pop(); + + // Can we afford this thing? + if (current_target.cost > points) + continue; + + points -= current_target.cost; + + switch (current_plant->mons_species()) + { + case MONS_PLANT: + plants_evolved++; + break; + + case MONS_FUNGUS: + case MONS_BALLISTOMYCETE: + fungi_evolved++; + break; + + case MONS_TOADSTOOL: + toadstools_evolved++; + break; + }; + + current_plant->upgrade_type(current_target.new_type, true, true); + current_plant->god = GOD_FEAWN; + current_plant->attitude = ATT_FRIENDLY; + current_plant->flags |= MF_CREATED_FRIENDLY; + current_plant->flags |= MF_ATT_CHANGE_ATTEMPT; + + // Try to remove slowly dying in case we are upgrading a + // toadstool, and spore production in case we are upgrading a + // fungus. + current_plant->del_ench(ENCH_SLOWLY_DYING); + current_plant->del_ench(ENCH_SPORE_PRODUCTION); + + if (current_plant->mons_species() == MONS_BALLISTOMYCETE) + current_plant->add_ench(ENCH_SPORE_PRODUCTION); + + // Maybe we can upgrade it again? + if (_possible_evolution(current_plant, temp_conversion) + && temp_conversion.cost <= points) + { + available_targets.push(temp_conversion); + } + } + + // How many pieces of fruit did we use up? + int points_used = starting_points - points; + int fruit_used = points_used / points_per_fruit; + if (points_used % points_per_fruit) + fruit_used++; + + // The player didn't have enough points to upgrade anything (probably + // supplied only one fruit). + if (!fruit_used) + { + mpr("Not enough fruit to cause evolution."); + return (false); + } + + dec_inv_item_quantity(rc, fruit_used); + + // Mention how many plants were used. + if (fruit_used > 1) + mprf("%d pieces of fruit are consumed!", fruit_used); + else + mpr("A piece of fruit is consumed!"); + + // Messaging for generated plants. + if (plants_evolved > 1) + mprf("%d plants can now spit acid.", plants_evolved); + else if (plants_evolved == 1) + mpr("A plant can now spit acid."); + + if (toadstools_evolved > 1) + mprf("%d toadstools gained stability.", toadstools_evolved); + else if (toadstools_evolved == 1) + mpr("A toadstool gained stability."); + + if (fungi_evolved > 1) + { + mprf("%d fungal colonies can now pick up their mycelia and move.", + fungi_evolved); + } + else if (fungi_evolved == 1) + mpr("A fungal colony can now pick up its mycelia and move."); + + return (true); +} + bool ponderousify_armour() { int item_slot = -1; -- cgit v1.2.3-54-g00ecf