/** * This is an effect that is added to all objects in a water room unless they * have a race object that returns non-zero to lives_in_water(). It handles * making them drown, drift with the current, float and sink, and also has two * functions to find the buoyancy and swimming difficulty for an object, which * may be used by other files. The argument of this effect is an array of two * integers. The first indicates if the effectee is passively drifting (when * it is 0), actively swimming (when it is 1), trying deperately to get to the * surface (when it is -1, which should only happen briefly as they panic), or * being moved by something else such as a current (when it is -2, which should * only happen briefly as they move through the exit). The default value for * this should be 1. The second member of the argument array indicates what * stage of drowning they have progressed to. * @author Bakhtosh * @see /std/room/basic/water.c * @classification "body.immersed" */ #include <effect.h> #include <tasks.h> #define ANCHOR_PROP "anchor" #define BUOYANT_PROP "buoyancy" #define GILLS_PROP "gills" #define FLOATING_PROP "floating" #define TROLL_RACE "troll" #define LIVES_IN_WATER_PROP "lives in water" #define LUNGS_PROP "lung capacity" #define STAMINA_SKILL "other.points" #define SWIMMING_SKILL "other.movement.swimming" #define WITCH_GUILD "/std/guilds/witch" int calc_buoyancy(object); int swim_difficulty(object); void set_arg(object, int*); void flee_drowning(object, int*); /** * @ignore yes */ string query_classification() { return "body.immersed"; } /** * @ignore yes */ int *beginning(object player, int swimming, int id) { if (living(player) && !(player->query_property(GILLS_PROP) && player->query_property(LIVES_IN_WATER_PROP))) { player->submit_ee("drown", 15, EE_CONTINUOUS); } if (!player->query_property(LIVES_IN_WATER_PROP)) { player->submit_ee("drift", 1, EE_ONCE); player->submit_ee("drift", 20, EE_CONTINUOUS); } return ({swimming ? 1 : 0, 0}); } /** * @ignore yes */ int *merge_effect(object player, int *old_arg, int swimming, int id) { if (swimming == 2) { old_arg[0] = 1; } player->submit_ee(0, -1, EE_REMOVE); return old_arg; } /** * @ignore yes */ void end(object player, int *arg, int id) { // Currently, nothing is done when this effect is removed. } /** * This function, submitted as a continuous event in the swimming/immersion * effect, advances the stage of drowning which the effectee has attained (if * they are underwater). As it grows higher, they receive more urgent warning * messages and eventually suffer penalties including, but not limited to, * death. Past a certain stage, they will automatically attempt to flee. * @see flee_drowning */ void drown(object player, int *arg, int id) { int damage, gp_left, extra_drown, fleed = 0, drown_stage = arg[1]; object new_env, env = environment(player); if (!env || !env->query_water()) { player->submit_ee(0, 1, EE_REMOVE); set_arg(player, ({arg[0], 0})); return; } // The dead or the netdead cannot drown. if (player->query_property("dead") || (userp(player) && !interactive(player))) { return; } if (player->query_property(GILLS_PROP) || load_object(player->query_race_ob())->lives_in_water()) { set_arg(player, ({arg[0], 0})); return; } if (env->query_surface()) { if (drown_stage < 1) { return; } set_arg(player, ({arg[0], 0})); tell_object(player, "You take a deep breath of air.\n"); return; } // If they're just floating, they don't drown as fast. if (arg[0]) { extra_drown = 75000; } else { extra_drown = 50000; } extra_drown /= 1000 + player->query_skill_bonus(SWIMMING_SKILL) + player->query_skill_bonus(STAMINA_SKILL); // A living creature may be given the "lung capacity" property to represent // a percentage of normal human lung capacity, in order to alter the rate at // which they drown. For example, a "lung capacity" property of 50 will make // them drown twice as fast, which a property of 300 will allow them to last // three times as long underwater. if (player->query_property(LUNGS_PROP)) { extra_drown *= 100; extra_drown /= player->query_property(LUNGS_PROP); } drown_stage += extra_drown; set_arg(player, ({arg[0], drown_stage})); if (drown_stage < 120) { return; } switch (drown_stage) { case 120..149: tell_object(player, "Your lungs feel a bit heavy.\n"); if (!sizeof(player->query_hide_invis())) { tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ a bit " "uncomfortable.\n", player); } return; case 150..179: tell_object(player, "Your lungs feel quite heavy.\n"); if (!sizeof(player->query_hide_invis())) { tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ somewhat " "uncomfortable.\n", player); } return; case 180..239: tell_object(player, "Your lungs feel very heavy.\n"); if (!sizeof(player->query_hide_invis())) { tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ quite " "uncomfortable.\n", player); } return; case 240..299: tell_object(player, "Your lungs feel close to bursting.\n"); if (!sizeof(player->query_hide_invis())) { tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ slightly " "blue.\n", player); } return; case 300..359: tell_object(player, "You feel a burning sensation in your lungs.\n"); if (!sizeof(player->query_hide_invis())) { tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ quite " "blue.\n", player); } return; case 360..479: // From now on, they may try to save themselves automatically, depending on // their wimpy. Npcs flee automatically once they risk losing hp. if (player->query_wimpy() >= 100) { flee_drowning(player, arg); new_env = environment(player); if (new_env->query_surface() || !new_env->query_water()) { return; } fleed = 1; } // If they were fully rested before going in (represented by having gp), // their gp can shield them from some of the damage in this stage. damage = drown_stage - 300; // Leave them with a minimum of 50 other guildpoints so that they can try // to swim up. gp_left = player->query_specific_gp("other") - 50; if (gp_left > 0) { if (gp_left >= damage) { tell_object(player, "Your lungs feel as though they are on fire.\n"); player->adjust_gp(-damage); tell_room(env, "$C$"+player->one_short()+" $V$0=looks,look$V$ " "pained.\n", player); if (player->query_monitor()) { player->monitor_points(); } return; } damage -= gp_left; player->adjust_gp(-gp_left); } damage *= 5; if (!fleed && (!userp(player) || 100*(player->query_hp() - damage) < player->query_max_hp()*player->query_wimpy())) { flee_drowning(player, arg); new_env = environment(player); if (new_env->query_surface() || !new_env->query_water()) { return; } } tell_object(player, "Agonising pain radiates from your lungs.\n"); if (damage >= player->query_hp()) { player->attack_by(env); } player->adjust_hp(-damage); if (player->query_hp() > 0) { tell_room(env, "$C$"+player->one_short()+" $V$0=thrashes,thrash$V$ " "about.\n", player); } if (player->query_monitor()) { player->monitor_points(); } return; case 480..599: if (!userp(player) || player->query_wimpy() >= 100) { flee_drowning(player, arg); new_env = environment(player); if (new_env->query_surface() || !new_env->query_water()) { return; } fleed = 1; } // They're stubborn, or they're using something to boost their gp. But // magical means can only go so far - their body needs real oxygen. gp_left = player->query_specific_gp("other") - 50; if (gp_left > 0) { player->adjust_gp(-gp_left); } damage = drown_stage - 300; damage *= 5; if (!fleed && 100*(player->query_hp() - damage) < player->query_max_hp()*player->query_wimpy()) { flee_drowning(player, arg); new_env = environment(player); if (new_env->query_surface() || !new_env->query_water()) { return; } } tell_object(player, "You feel everything going black.\n"); if (damage >= player->query_hp()) { player->attack_by(env); } player->adjust_hp(-damage); if (player->query_hp() > 0) { tell_room(env, "$C$"+player->one_short()+" $V$0=thrashes,thrash$V$ " "weakly.\n", player); } if (player->query_monitor()) { player->monitor_points(); } return; default: flee_drowning(player, arg); new_env = environment(player); if (new_env->query_surface() || !new_env->query_water()) { return; } tell_object(player, "You feel everything going black.\n"); // Stop mucking about. Just kill them. player->attack_by(env); player->do_death(); return; } } /** * This function makes the subject try to flee to the surface. The first * member of their effect argument array for the swimming/immersion effect is * set to -1 while they do so to give them a little extra chance. * @see drown * @see swim_exit */ void flee_drowning(object player, int *arg) { int which, *effnums, *new_arg; string up, *exits; object env = environment(player); up = env->query_up_dir(); exits = env->query_dest_dir(); if (member_array(up, exits) == -1) { which = random(sizeof(exits)); which -= which%2; up = exits[which]; tell_object(player, "You panic and try to flee "+up+".\n"); tell_room(env, "$C$"+player->one_short()+" $V$0=panics,panic$V$ and " "$V$0=tries,try$V$ to flee "+up+".\n", player); } else { tell_object(player, "You panic and try to flee for the surface.\n"); tell_room(env, "$C$"+player->one_short()+" $V$0=panics,panic$V$ and " "$V$0=tries,try$V$ to flee for the surface.\n", player); } set_arg(player, ({-1, arg[1]})); player->exit_command(up); effnums = player->effects_matching(query_classification()); if (!effnums || !sizeof(effnums)) { return; } new_arg = player->arg_of(effnums[0]); if (new_arg[0] == -1) { set_arg(player, ({arg[0], new_arg[1]})); } } /** * This function, submitted as a continuous event in the swimming/immersion * effect, checks to see if the effectee will have any trouble staying in their * water room. If they will, it performs a skillcheck to see if they are swept * away, and moves them if they fail. It handles moving due to currents, * floating and sinking. * @see add_flow */ void drift(object player, int *args, int id) { int buoyancy, difficulty, traction, *effnums, *new_args; string up_dir, down_dir, move_dir, msgout, msgin, *exits, *tm_messes; object env = environment(player); mapping flows; mixed next; if (!env || !env->query_water()) { player->submit_ee(0, 1, EE_REMOVE); set_arg(player, ({args[0], 0})); return; } if (player->query_property("dead")) { return; } if (player->query_property(FLOATING_PROP) && env->query_surface()) { return; } flows = env->query_flows(); up_dir = env->query_up_dir(); down_dir = env->query_down_dir(); buoyancy = calc_buoyancy(player); if (player->query_weight()) { buoyancy /= player->query_weight(); } flows[up_dir] += buoyancy; flows[down_dir] -= buoyancy; if (env->query_bottom()) { // If it's sitting on the bottom it is less likely to be swept away. Also, // it is in no danger of being swept downwards. traction = env->query_water_traction_bonus(player, buoyancy); flows = map(flows, (: $2 - $(traction) :)); map_delete(flows, down_dir); } if (!args[0] || !living(player)) { exits = env->query_exits(); flows = filter(flows, (: member_array($1, $(exits)) != -1 :)); exits = sort_array(keys(flows), (: $(flows)[$1] > $(flows)[$2] ? -1 : 1 :)); if (!sizeof(exits)) { return; } move_dir = exits[0]; if (flows[move_dir] <= 0) { return; } if (!living(player) && flows[move_dir] <= player->query_property(ANCHOR_PROP)) { tell_object(player, "You resist the "+move_dir+" current.\n"); return; } exits = env->query_dest_dir(); next = exits[member_array(move_dir, exits) + 1]; next = load_object(next); if (!next) { return; } if (move_dir == up_dir && buoyancy > 0) { tell_object(player, "You float "+up_dir+".\n"); msgout = env->query_float_out_mess(); msgin = replace(next->query_float_in_mess(), "$F", env->query_origin(move_dir)); } else if (move_dir == down_dir && buoyancy < 0) { tell_object(player, "You sink "+down_dir+".\n"); msgout = env->query_sink_out_mess(); msgin = replace(next->query_sink_in_mess(), "$F", env->query_origin(move_dir)); } else { tell_object(player, "You drift "+move_dir+" with the current.\n"); msgout = replace(env->query_sweep_out_mess(), ({"$T", move_dir})); msgin = replace(next->query_sweep_in_mess(), "$F", env->query_origin(move_dir)); } if (!living(player)) { player->move(next, msgin, msgout); return; } env->add_exit_mess(player, msgout); env->add_enter_mess(player, msgin); set_arg(player, ({-2, args[1]})); player->exit_command(move_dir); effnums = player->effects_matching(query_classification()); if (!effnums || !sizeof(effnums)) { return; } new_args = player->arg_of(effnums[0]); if (new_args[0] == -2) { set_arg(player, ({args[0], new_args[1]})); } return; } flows[up_dir] -= 50; flows[down_dir] -= 50; exits = env->query_exits(); flows = filter(flows, (: member_array($1, $(exits)) != -1 :)); exits = sort_array(keys(flows), (: $(flows)[$1] > $(flows)[$2] ? -1 : 1 :)); if (!sizeof(exits)) { return; } move_dir = exits[0]; if (flows[move_dir] <= 0) { return; } difficulty = flows[move_dir]; difficulty *= swim_difficulty(player); difficulty /= 100; if (difficulty <= 0) { return; } switch (TASKER->perform_task(player, SWIMMING_SKILL, difficulty, TM_CONTINUOUS)) { case AWARD: tm_messes = ({"You discover an easier swimming technique.", "You manage to paddle where you are when you thought you'd be " "swept away."}); tell_object(player, "%^YELLOW%^"+tm_messes[random(sizeof(tm_messes))]+ "%^RESET%^\n"); case SUCCEED: return; } exits = env->query_dest_dir(); next = exits[member_array(move_dir, exits) + 1]; next = load_object(next); if (!next) { return; } if (move_dir == up_dir && buoyancy > 50) { tell_object(player, "Despite your best efforts, your buoyancy drags you " +up_dir+".\n"); msgout = env->query_float_out_mess(); msgin = replace(next->query_float_in_mess(), "$F", env->query_origin(move_dir)); } else if (move_dir == down_dir && buoyancy < -50) { tell_object(player, "Despite your best efforts, your weight drags you " +down_dir+".\n"); msgout = env->query_sink_out_mess(); msgin = replace(next->query_sink_in_mess(), "$F", env->query_origin(move_dir)); } else { tell_object(player, "Despite your best efforts, the current drags you " +move_dir+".\n"); msgout = replace(env->query_sweep_out_mess(), "$T", move_dir); msgin = replace(next->query_sweep_in_mess(), "$F", env->query_origin(move_dir)); } env->add_exit_mess(player, msgout); env->add_enter_mess(player, msgin); set_arg(player, ({-2, args[1]})); player->exit_command(move_dir); effnums = player->effects_matching(query_classification()); if (!effnums || !sizeof(effnums)) { return; } new_args = player->arg_of(effnums[0]); if (new_args[0] == -2) { set_arg(player, ({args[0], new_args[1]})); } } /** * @ignore yes */ void set_arg(object player, int *arg) { int *effects = player->effects_matching(query_classification()); if (!effects || !sizeof(effects)) { return; } player->set_arg_of(effects[0], arg); } /** * This function calculates the total buoyancy of the specified object. It is * an absolute value for the buoyancy force acting on them, so it should be * divided by their complete weight to find the relative buoyancy. It is * adjusted according to their race (trolls are denser), guild (witches float), * the salinity of the water (brine is denser), their buoyancy property and the * amount that they are carrying. * @param player the object for which the buoyancy should be found * @return the buoyancy force acting on the object * @see query_complete_weight */ int calc_buoyancy(object player) { int weight, buoyancy, personal, baggage; if (!player) { return 0; } if (!living(player)) { weight = player->query_complete_weight(); buoyancy = player->query_property(BUOYANT_PROP); if (weight == 0) { return 1000*buoyancy - 1; } return 1000*buoyancy - 500*weight; } if (player->query_property("dead")) { return 0; } personal = 10; if (player->query_race() == TROLL_RACE) { personal -= 2000; } if (player->query_guild_ob() == WITCH_GUILD) { personal += to_int(20.0*sqrt(to_float(player->query_level()))); } if (environment(player)) { personal += environment(player)->query_salinity()/2; } weight = player->query_weight(); buoyancy = player->query_property(BUOYANT_PROP); baggage = player->query_loc_weight(); return 1000*buoyancy + personal*weight - 500*baggage; } /** * This function is used to adjust the difficulty of any attempt at swimming by * a living object. It returns an integer greater than or equal to 100. The * difficulty of any skillcheck involves their swimming skill should be * multiplied by this value and then divided by 100. This handles adjustments * to account for burdening and held or worn items. * @param player the object to take the skillcheck * @return the adjustment factor for the difficulty of the skillcheck * @see perform_task */ int swim_difficulty(object player) { int handiness, difficulty; object *tmp; if (!player || !living(player)) { return 0; } if (player->query_property("dead")) { return 0; } // Give them a base value for being able to move their body. handiness = 20; // Give them a bonus for having feet, reduced according to the weight of any // boots they're wearing. tmp = player->query_wearing(); if (tmp && sizeof(tmp)) { tmp = filter(uniq_array(tmp), (: member_array($1->query_type(), ({"shoe", "boot", "chausse", "overshoe"})) != -1 :)); } if (!sizeof(tmp)) { handiness += 40; } else { handiness += 600/(15 + implode(map(tmp, (: $1->query_weight() :)), (: $1 + $2 :))); } // Give them a bonus for each arm with something in it, depending on the // weight of the item they're holding. Yes, this setup will penalise them a // bit extra for holding something two-handed. tmp = player->query_holding(); if(tmp) { tmp = filter(tmp, (: $1 :)); if(tmp) handiness += implode(map(uniq_array(tmp), (: 180/(10+$1->query_weight()) :)), (: $1 + $2 :)); } // Give them a bonus for any free limbs, better than they could get with // something in their hand... handiness += 20*player->query_free_limbs(); // Don't let them get past the maximum value for an unencumbered human. if (handiness > 100) { handiness = 100; } difficulty = 10000/handiness; difficulty *= 200 + player->query_burden(); difficulty /= 200; return difficulty; } /** * @ignore yes */ int survive_death() { return 1; }