From e66c77b50df3c0c91837b52b9e61c8235a96c685 Mon Sep 17 00:00:00 2001 From: j-p-e-g Date: Mon, 29 Jun 2009 19:44:39 +0000 Subject: Apply caotto's neat patch to make toadstools grow on or around corpses. Obviously, this might affect food balance and Necromancy. Tweaks may be necessary, but overall this looks good. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@10077 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/dat/descript/monsters.txt | 4 + crawl-ref/source/effects.cc | 202 +++++++++++++++++++++++++++++ crawl-ref/source/enum.h | 4 +- crawl-ref/source/mon-data.h | 11 ++ crawl-ref/source/mon-util.cc | 26 +++- crawl-ref/source/monplace.cc | 10 ++ crawl-ref/source/monstuff.cc | 3 +- crawl-ref/source/stuff.cc | 15 +++ crawl-ref/source/stuff.h | 2 + crawl-ref/source/tilepick.cc | 2 + 10 files changed, 276 insertions(+), 3 deletions(-) (limited to 'crawl-ref') diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt index a5f0684ac6..987b029e7b 100644 --- a/crawl-ref/source/dat/descript/monsters.txt +++ b/crawl-ref/source/dat/descript/monsters.txt @@ -1369,6 +1369,10 @@ toenail golem A huge animated statue made entirely from toenail clippings. Some people just have too much time on their hands. %%%% +toadstool + +A short-lived species of fungus typically found on or around decaying organic matter. +%%%% tormentor This malign devil is covered in all manner of claws, spines and cruel hooks. diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc index 2244402d48..7ef9ec7a2a 100644 --- a/crawl-ref/source/effects.cc +++ b/crawl-ref/source/effects.cc @@ -15,6 +15,9 @@ REVISION("$Rev$"); #include #include #include +#include +#include +#include #include "externs.h" @@ -3733,6 +3736,202 @@ static void _maybe_restart_fountain_flow(const coord_def& where, } } +// Spawn a ring of mushrooms around the input corpse (radius=1). +// Could try different radii/check for largest available but doesn't right now. +// Maybe there should be a message for this. +static int _mushroom_ring(item_def &corpse) +{ + ::adjacent_iterator adj(corpse.pos); + + int spawned_count=0; + for ( ; adj; ++adj) + { + if (mons_class_can_pass(MONS_TOADSTOOL, grd(*adj)) + && !actor_at(*adj)) + { + const int mushroom = create_monster( + mgen_data(MONS_TOADSTOOL, + BEH_HOSTILE, + 0, + 0, + *adj, + MHITNOT, + MG_FORCE_PLACE, + GOD_NO_GOD, + MONS_PROGRAM_BUG, + 0, + corpse.colour), + false); + + if (mushroom != -1) + spawned_count++; + } + } + return spawned_count; +} + +// Try to spawn 'target_count' mushrooms around the position of 'corpse'. +// Returns the number of mushrooms actually spawned. +// Mushrooms radiate outwards from the corpse following bfs with 8-connectivity. +// Could change the expansion pattern by using a priority queue for +// sequencing (priority = distance from origin under some metric). +int spawn_corpse_mushrooms(item_def &corpse, int target_count = 1) +{ + if (target_count == 0) + return 0; + + int x_offset[] = {-1,-1,-1, 0, 0, 1, 1, 1}; + int y_offset[] = {-1, 0, 1,-1, 1,-1, 0, 1}; + + + int placed_targets = 0; + + std::queue fringe; + std::set visited_indices; + + // Slight chance of spawning a ring of mushrooms around the corpse (and + // skeletonizing it) if the corpse square is unoccupied. + if (!actor_at(corpse.pos) && one_chance_in(100)) + { + if (see_grid(corpse.pos)) + mpr("A ring of toadstools grow before your very eyes."); + + corpse.special = 0; + return _mushroom_ring(corpse); + } + + // Can't figure out how to query the size of the xdim of the grid but who + // cares. + visited_indices.insert(10000*corpse.pos.y + corpse.pos.x); + fringe.push(corpse.pos); + + while (!fringe.empty()) + { + coord_def current = fringe.front(); + + fringe.pop(); + + actor * occupant = NULL; + // is this square occupied by a non mushroom? + if((occupant = actor_at(current)) + && occupant->mons_species() != MONS_TOADSTOOL) + { + continue; + } + + if (!occupant) + { + const int mushroom = create_monster( + mgen_data(MONS_TOADSTOOL, + BEH_HOSTILE, + 0, + 0, + current, + MHITNOT, + MG_FORCE_PLACE, + GOD_NO_GOD, + MONS_PROGRAM_BUG, + 0, + corpse.colour), + false); + + if (mushroom != -1) + { + placed_targets++; + if (see_grid(current)) + { + if (see_grid(corpse.pos)) + mpr("A toadstool grows from a nearby corpse."); + else + mpr("A toadstool springs up from the ground."); + } + } + else + continue; + } + + // We're done here if we place the desired number of mushrooms. + if (placed_targets == target_count) + break; + + // Randomize the order in which children are processed to a certain + // extent. + int idx = rand() % 8; + + for (int count = 0; count < 8; ++count) + { + idx= (idx + 1) % 8; + coord_def temp(current.x + x_offset[idx], current.y + y_offset[idx]); + + int index = temp.x + temp.y * 10000; + + if (visited_indices.find(index) == visited_indices.end() + && in_bounds(temp) + && mons_class_can_pass(MONS_TOADSTOOL, grd(temp))) + { + + visited_indices.insert(index); + fringe.push(temp); + } + } + } + + return placed_targets; +} + + +// Randomly decide whether or not to spawn a mushroom over the given corpse +// Assumption: this is called before the rotting away logic in update_corpses. +// Some conditions in this function may set the corpse timer to 0, assuming +// that the corpse will be turned into a skeleton/destroyed on this update. +static void _maybe_spawn_mushroom(item_def & corpse, int rot_time) +{ + // We won't spawn a mushroom within 10 turns of the corpse being created + // or rotting away. + int low_threshold = 5; + int high_threshold = FRESHEST_CORPSE - 5; + + if (corpse.special < low_threshold) + return; + + int spawn_time = (rot_time > corpse.special ? corpse.special : rot_time); + + if (spawn_time > high_threshold) + spawn_time = high_threshold; + + // So we're going to spawn one or more mushrooms over the lifetime of a + // corpse here. For convenience we follow a binomial distribution. I think + // the most useful analysis is in terms of the probability of a corpse + // producing no mushrooms, although trial probability should just be hard + // coded once a value is agreed on. + + + // Expect this many trials over a corpse's lifetime since this function + // is called once for every 10 units of rot_time. + int step_size = 10; + float total_trials = (high_threshold - low_threshold) / step_size; + + // chance of producing no mushrooms + float p_failure = .5; + + float trial_prob_f = 1 - powf(p_failure,1.0f/total_trials); + + // The chance of producing mushrooms depends on the weight of the + // corpse involved. Humans weigh 550 so we will take that as the + // base factor here. + float weight_factor = item_mass(corpse)/550.0f; + + trial_prob_f *= weight_factor; + + int trial_prob = static_cast(100*trial_prob_f); + + int current_trials = spawn_time/step_size; + + int success_count = binomial_generator(current_trials, trial_prob); + + spawn_corpse_mushrooms(corpse, success_count); +} + //--------------------------------------------------------------- // // update_corpses @@ -3762,6 +3961,9 @@ void update_corpses(double elapsedTime) continue; } + if (it.sub_type == CORPSE_BODY) + _maybe_spawn_mushroom(it, rot_time); + if (rot_time >= it.special && !is_being_butchered(it)) { if (it.base_type == OBJ_FOOD) diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 378e695cbb..9905fb912f 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -1239,6 +1239,7 @@ enum enchant_type ENCH_PETRIFYING, ENCH_PETRIFIED, ENCH_LOWERED_MR, + ENCH_SLOWLY_DYING, // Update enchantment names in mon-util.cc when adding or removing // enchantments. @@ -1795,7 +1796,8 @@ enum monster_type // (int) menv[].type MONS_MERMAID, MONS_SIREN, // 195 MONS_FLAMING_CORPSE, - MONS_HARPY, // 197 + MONS_HARPY, + MONS_TOADSTOOL, // 198 //jmf: end new monsters MONS_WHITE_IMP = 220, // 220 MONS_LEMURE, diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index bb43d11f3e..ef3c9e90c7 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -666,6 +666,17 @@ static monsterentry mondata[] = { HT_LAND, 10, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_TINY }, +{ + MONS_TOADSTOOL, 'f', BROWN, "toadstool", + M_NO_EXP_GAIN | M_STATIONARY, + MR_RES_POISON, + 0, 10, MONS_PLANT, MONS_TOADSTOOL, MH_PLANT, MAG_IMMUNE, + { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, + { 1, 2,2, 0 }, + 1, 0, MST_NO_SPELLS, CE_NOCORPSE, Z_NOZOMBIE, S_SILENT, I_PLANT, + HT_LAND, 10, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_TINY +}, + // goblins ('g') { MONS_GOBLIN, 'g', LIGHTGREY, "goblin", diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 2ee32ba969..bdcdbd4646 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -7105,6 +7105,13 @@ void monsters::timeout_enchantments(int levels) del_ench(i->first); break; + case ENCH_SLOWLY_DYING: + { + const int actdur = speed_to_duration(speed)*levels; + if (lose_ench_duration(i->first, actdur)) + monster_die(this,KILL_MISC,-1,true); + break; + } default: break; } @@ -7481,6 +7488,18 @@ void monsters::apply_enchantment(const mon_enchant &me) hit_points = -1; break; + case ENCH_SLOWLY_DYING: + + // If you are no longer dying you must be dead + if (decay_enchantment(me)) + { + if (see_grid(this->position)) + mprf("A nearby %s withers and dies.", this->name(DESC_PLAIN, false).c_str()); + + monster_die(this,KILL_MISC,-1,true); + } + break; + case ENCH_GLOWING_SHAPESHIFTER: // This ench never runs out! // Number of actions is fine for shapeshifters. if (type == MONS_GLOWING_SHAPESHIFTER || one_chance_in(4)) @@ -8211,7 +8230,7 @@ static const char *enchant_names[] = "gloshifter", "shifter", "tp", "wary", "submerged", "short-lived", "paralysis", "sick", "sleep", "fatigue", "held", "blood-lust", "neutral", "petrifying", "petrified", "magic-vulnerable", - "bug" + "decay", "bug" }; static const char *_mons_enchantment_name(enchant_type ench) @@ -8357,6 +8376,11 @@ int mon_enchant::calc_duration(const monsters *mons, case ENCH_SHORT_LIVED: cturn = 1000 / _mod_speed(200, mons->speed); break; + case ENCH_SLOWLY_DYING: + // This may be a little too direct but the randomization at the end + // of this function is excessive for corpse mold. -cao + return (2*FRESHEST_CORPSE + random2(10)) * speed_to_duration(mons->speed) + * mons->speed/10; case ENCH_ABJ: if (deg >= 6) cturn = 1000 / _mod_speed(10, mons->speed); diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index 4e56354e58..93d59b44a8 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -1101,6 +1101,16 @@ static int _place_monster_aux(const mgen_data &mg, if (mg.cls == MONS_GLOWING_SHAPESHIFTER) menv[id].add_ench(ENCH_GLOWING_SHAPESHIFTER); + if (mg.cls == MONS_TOADSTOOL) + { + // This enchantment is a timer that counts down until death. + // These mushrooms should last longer than the lifespan of a corpse + // (to avoid spawning mushrooms in the same place over and over), aside + // from that the value is slightly randomized to avoid simultaneous + // die-offs of mushroom rings. + menv[id].add_ench(ENCH_SLOWLY_DYING); + } + if (monster_can_submerge(&menv[id], grd(fpos)) && !one_chance_in(5)) menv[id].add_ench(ENCH_SUBMERGED); diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index ccc1526130..d0fee93312 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -2704,7 +2704,8 @@ void behaviour_event(monsters *mon, mon_event_type event, int src, && !mons_is_fleeing(mon) && !mons_is_panicking(mon))) { // (Plain) plants and fungi cannot fight back. - if (mon->type == MONS_FUNGUS || mon->type == MONS_PLANT) + if (mon->type == MONS_FUNGUS || mon->type == MONS_PLANT + || mon->type == MONS_TOADSTOOL) return; mon->foe = src; diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index 43bcdb7a77..6d3d0ec719 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -689,6 +689,21 @@ int random2limit(int max, int limit) return (sum); } +// Generate samples from a binomial distribution with n_trials and trial_prob +// probability of success per trial. trial_prob is a integer less than 100 +// representing the % chancee of success. +// This just evaluates all n trials, there is probably an efficient way of +// doing this but I'm not much of a statistician. -CAO +int binomial_generator(unsigned n_trials, unsigned trial_prob) +{ + int count = 0; + for (unsigned i = 0; i < n_trials; ++i) + if (::x_chance_in_y(trial_prob, 100)) + count++; + + return count; +} + void cio_init() { crawl_state.io_inited = true; diff --git a/crawl-ref/source/stuff.h b/crawl-ref/source/stuff.h index 96d54af6e6..11a6cff013 100644 --- a/crawl-ref/source/stuff.h +++ b/crawl-ref/source/stuff.h @@ -43,6 +43,8 @@ int bestroll(int max, int rolls); int roll_dice(int num, int size); void scale_dice(dice_def &dice, int threshold = 24); +int binomial_generator(unsigned n_trials, unsigned trial_prob); + // Various ways to iterate over things. // stack_iterator guarantees validity so long as you don't manually diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 1d413ac86a..21b085a08b 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -198,6 +198,8 @@ int tileidx_monster_base(const monsters *mon, bool detected) return TILEP_MONS_FUNGUS; case MONS_WANDERING_MUSHROOM: return TILEP_MONS_WANDERING_MUSHROOM; + case MONS_TOADSTOOL: + return TILEP_MONS_FUNGUS; // goblins ('g') case MONS_GOBLIN: -- cgit v1.2.3-54-g00ecf