#include <tasks.h>
inherit OUTSIDE_OBJ;
/**
* This is a standard rooftop inheritable. It allows you to setup a rooftop
* location including such things as slope, strength, jumping locations etc.
*
* @example
* inherit "/std/rooftop"
*
* void setup() {
* set_fall_damage( "all", 500 );
* set_slope( 60, "ledge", PATH + "womble01" );
* set_weak_messages(
* "The ledge crumbles under your weight.",
* "$short$ falls from the crumbling ledge.",
* "$short$ comes crashing to the ground.",
* "The paving stones shift beneath your feet.",
* );
* set_weak_roof( 2000, PATH + "womble01" );
* set_jump( ({ "north", "n" }), PATH + "ledge01", PATH + "womble01", 6 );
* }
*
* @see set_wall
* @author shrike
*/
#undef DEBUG
#define ROCK "general.climbing.rock"
#define ROPE "general.climbing.rope"
/* function prototypes */
void set_death_reason( string );
void set_fall_damage( string, int );
mixed query_fall_damage( string );
int calc_fall_damage( string );
void set_weak_roof( int, string );
void set_slope( int, string, string );
string process_string( string, mapping );
string process_mess( string, object, string );
/* object variables */
mapping damages; // damages for all the various types of falling.
string * damage_types;
int roof_max_weight; // in units (20 units == 1 kg)
int gradient; // percentage grade of the roof.
string weak_roof_dest; // where you fall if the roof collapses.
string slope_dest; // where you fall if you slip off the roof.
string place; // where you fall off of. (ie "edge of the roof
// bordering Filigree Street" )
string death_reason; // used when you die.
/*
* Message arrays. most are of the form
* ({ player_mess, exit_mess, enter_mess })
* Where player_mess is what the player who's jumping/falling sees.
* exit mess is the message sent to the other objects in the starting room,
* and enter_mess is what the objects in the target room see.
* weak_messages has an additional 4th parameter, which is the message to
* the room when the roof is about to collapse, and jump_tm_messages will
* be randomly selected from if the player gets a TM while jumping.
*/
string * weak_messages; // for when a roof collapses.
string * slope_messages; // falling off
string * step_messages; // stepping off when you should have jumped
string * jump_tm_messages; // tm messages. it will pick a rnadom one
string * jump_success_messages; // jumping successfully
string * jump_failure_messages; // jumping unsuccessfully
string * ghost_fall_messages; // a ghost drifting to the ground
string * item_slope_messages; // a non-corpse item sliding off the edge
string * corpse_slope_messages; // a corpse sliding off the edge
/*
* Info for jumping from roof to roof. each element is of the form
* <string:key>: ({ Name of the exit
* <string:destination>, Filename of the destination room
* <string:fall_dest>, filename of the room where you fall on failure
* <int:distance> Distance. (streets in AM are ~5-20 wide
* })
*/
mapping jump_info;
/*
* Translates from direction aliases to jump_info keys. Each element looks
* like <string:name> : <string: jump_info key>
* In order to pull up the jump info for a particular location, you'd
* use jump_info[ translations["name"] ].
*/
mapping translations;
/** @ignore yes */
void init() {
::init();
add_command( "jump", "<word'direction'>",
(: TO->do_roofjump( $4[0] ) :) );
} /* init() */
/** @ignore yes */
void create() {
do_setup++;
::create();
do_setup--;
weak_messages = ({
"The roof collapses! This is going to be painful...\n",
"$short$ crashes down through a weak spot in the roof.",
"$short$ comes crashing to the ground, landing in a rain of debris.",
"You hear an ominous creak.\n"
});
slope_messages = ({
"The roof is to steep for you to stand on! It's time to make friends "
"with the floor...\n",
"$short$ gets in touch with gravity and slides over the edge of the "
"roof.",
"$short$ comes crashing to the ground, landing in a heap."
});
step_messages = ({
"You step off the edge of the roof into midair.\n",
"$short$ steps off the edge of the roof and plummets earthwards.",
"$short$ comes crashing to the ground, landing in a heap."
});
jump_tm_messages = ({
"You leap more gracefully through the air.",
"You feel more able to leap tall buildings in a single bound.",
"You jump like a mountain goat."
});
jump_success_messages = ({
"You launch yourself off the edge of the roof and land gracefully on "
"the other side.\n",
"$short$ jumps gracefully across the gap to the $dir$.",
"$short$ jumps in from across the gap to the $opp_dir$."
});
jump_failure_messages = ({
"You launch yourself off the edge of the roof!\nUnfortunately you "
"misjudge the distance and plummet earthwards. This is going to "
"hurt...\n",
"$short$ jumps off to the $dir$, but misjudges and plummets "
"earthwards.",
"$short$ plummets to the ground, landing in a heap."
});
ghost_fall_messages = ({
"You find your consciousness drifting earthwards.\n",
"$the_short$ drifts earthwards.",
"$the_short$ drifts in from above, looking somewhat dazed."
});
item_slope_messages = ({
"$the_short$ tumbles over the edge and plummets to the ground.\n",
"Hearing a noise, you look up just as $a_short$ falls off the edge "
"of the roof and hits the ground.\n"
});
corpse_slope_messages = ({
"$the_short$ tumbles over the edge and plummets to the ground with "
"a sickening thud.\n",
"Hearing a noise, you look up just as $the_short$ tumbles over the "
"edge of the roof and hits the ground with a sickening thud.\n"
});
damages = ([ ]);
damage_types = ({ "weak", "slope", "step", "jump" });
translations = ([ ]);
jump_info = ([ ]);
TO->setup();
} /* create() */
/* Customize the message sets */
/**
* Set the messages for when a player falls through a weak roof.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
* @param warn the message shown to the room just before the roof collapses.
*
* @see set_weak_roof
*/
void set_weak_messages( string player, string from, string to, string warn ) {
weak_messages = ({ player, from, to, warn });
} /* set_weak_messages() */
/**
* Set the messages for when a player slips off a sloping roof.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
*
* @see set_slope
*/
void set_slope_messages( string player, string from, string to ) {
slope_messages = ({ player, from, to });
} /* set_slope_messages() */
/**
* Set the messages for when a player falls off by moving in the wrong
* direction.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
*
* @see set_jump
*/
void set_step_messages( string player, string from, string to ) {
step_messages = ({ player, from, to });
} /* set_step_messages() */
/**
* Set the messages for when a player gets a TM by jumping. These messages
* will be selected at random, and then coloured appropriately.
*
* @param messages the array of messages from which to choose.
*
* @see set_jump
*/
void set_jump_tm_messages( string * messages ) {
jump_success_messages = messages;
} /* set_jump_tm_messages() */
/**
* Set the messages for when a player successfully jumps a gap.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
*
* @see set_jump
*/
void set_jump_success_messages( string player, string from, string to ) {
jump_success_messages = ({ player, from, to });
} /* set_jump_success_messages() */
/**
* Set the messages for when a player falls off by failing to jump across
* a gap.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
*
* @see set_jump
*/
void set_jump_failure_messages( string player, string from, string to ) {
jump_failure_messages = ({ player, from, to });
} /* set_jump_failure_messages() */
/**
* Set the messages for when a ghost falls off a roof.
*
* @param player the message shown to the player.
* @param from the message shown in the room the player is leaving.
* @param to the message shown in the room the player is entering.
*
* @see set_slope
*/
void set_ghost_fall_messages( string player, string from, string to ) {
ghost_fall_messages = ({ player, from, to });
} /* set_ghost_fall_messages() */
/**
* Set the messages for when a corpse slides off a sloping roof.
*
* @param from the message shown in the room the corpse is leaving.
* @param to the message shown in the room the corpse is entering.
*
* @see set_slope
*/
void set_corpse_slope_messages( string from, string to ) {
corpse_slope_messages = ({ from, to });
} /* set_item_slope_messages() */
/**
* Set the messages for when a non-corpse item slides off a sloping roof.
*
* @param from the message shown in the room the object is leaving.
* @param to the message shown in the room the object is entering.
*
* @see set_slope
*/
void set_item_slope_messages( string from, string to ) {
item_slope_messages = ({ from, to });
} /* set_item_slope_messages() */
/**
* Set the amount of damage the player will take if they fall off this
* roof. This amount is somewhat randomised.
*
* @param type The type of fall (weak, slope, step or jump; all to set all damages the same)
* @param damage The amount of damage.
*
* @see set_slope, set_jump, set_weak_roof
*/
void set_fall_damage( string type, int damage ) {
if( damage < 0 )
damage = -damage;
if( type == "all" ) {
foreach( type in damage_types )
damages[type] = damage;
} else if( member_array( type, damage_types ) >= 0 ) {
damages[type] = damage;
} else
return;
} /* set_fall_famage() */
/**
* Find out the damage for falling from this location.
*
* @param type The type ( weak, slope, step, jump, or all )
* @return An int, or an integer array of the damage(s)
*
* @see set_fall_damage
*/
mixed query_fall_damage( string type ) {
if( type == "all" )
return damages;
else
return damages[type];
} /* query_fall_famage() */
/** @ignore yes */
int calc_fall_damage( string type ) {
if( type == "all" )
return 0;
return query_fall_damage( type ) + random( query_fall_damage( type ) );
} /* calc_fall_famage() */
/**
* Set the maximum weight this location can support.
*
* @param maxweight the maximum weight capacity of the roof.
* @param dest the room you go to if the roof collapses.
*/
void set_weak_roof( int maxweight, string dest ) {
roof_max_weight = maxweight;
weak_roof_dest = dest;
} /* set_weak_roof() */
/**
* Set the gradient of this location.
*
* @param angle the angle (in degrees).
* @param loc a short description of the area to show to players.
* @param dest the fall destination.
*/
void set_slope( int angle, string loc, string dest ) {
gradient = ( angle * 100 ) / 90;
place = loc;
slope_dest = dest;
} /* set_slope() */
/**
* Setup a jumping exit. The player will be able to "jump <dir>" and a skill
* test will be performed and they'll succeed or fail.
*
* Streets in AM are usually 5-20 wide.
* if you want to do something other than the standard fall function,
* you'll need to mask do_fall() in the child room.
*
* @param dir direction names
* @param dest the destination
* @param fall_dest the destination if they fall
* @param distance the distance of the jump
* @return -1 if any of the keys in dir already exists, 0 if dir is
* malformed (string and string * are the only acceptable types), or
* 1 on success.
*
* @example
* set_jump( ({ "north", "n" }), RUN + "run12", FILIGREE + "filigree10", 6 );
*/
int set_jump( mixed dir, string dest, string fall_dest, int distance ) {
string * dirs;
mixed foo;
string bar;
if( stringp( dir ) ) {
dirs = ({ dir });
} else if( arrayp(dir) ) {
foreach( foo in dir )
if( !stringp( foo ) )
return 0;
dirs = sort_array( dir, 1 );
}
foreach( bar in dir )
if( !undefinedp( translations[bar] ) )
return -1;
// setup the mappings
jump_info[ dir[0] ] = ({ dest, fall_dest, distance });
foreach( bar in dir )
translations[bar] = dir[0];
if( !query_exit( dir[0] ) )
add_exit( dir[0], dest, "roof" );
modify_exit( dir[0], ({
"closed", 1,
"function", (: TO->silly_move(
jump_info[translations[$1]][1], "step", step_messages ) :),
"look", "You'll have to jump across to see what's on the other side."
}) );
return 1;
} /* set_jump() */
/**
* @ignore yes
* Internal function used if a player steps off the edge of a roof.
*/
int silly_move( mixed dest, string dam_type, string * messages ) {
TO->do_fall( TP, dest, dam_type, messages, 0 );
return notify_fail( "" );
} /* silly_move() */
/**
* @ignore yes
* Internal function to handle falling. You can overload this if you so
* desire.
*/
void do_fall( object obj, mixed dest, string dam_type, string * messages,
string dir ) {
int damage, i;
object destob;
for( i = 0; i < sizeof( messages ); i++ )
messages[i] = process_mess( messages[i], obj, dir );
if( stringp( dest ) ) {
if( !(destob = load_object( dest ) ) ) {
tell_object( obj, "Cannot find " + dest + ".\n"
"Moving you to the void - Please contact a creator.\n" );
obj->move( "/room/void", "Poof. $N appears.\n",
"$N plummets earthwards.\n" );
}
} else if( objectp( dest ) ) {
destob = dest;
} else
return;
tell_object( obj, messages[0] );
obj->move_with_look( destob, messages[2], messages[1] );
if( obj->query_property( "dead" ) )
return;
damage = calc_fall_damage( dam_type );
if( damage >= obj->query_hp() ) {
set_death_reason( "plummeting from the rooftops" );
obj->attack_by( TO );
obj->do_death();
} else
obj->adjust_hp( -damage );
if(obj->query_monitor())
HEALTH_H->monitor_points(obj, 0);
} /* do_fall() */
/**
* @ignore yes
* Deals with falling through or off roofs. also takes care of dropped
* items/corpses on sloped roofs.
*/
void event_enter( object obj, object from ) {
int totalweight, objectweight, i;
float encum, diff;
int fall;
object * contents;
object destination;
if( !obj ) return;
if( obj->query_property( "demon" )
|| obj->query_property( "floating" ) ) {
return;
}
// deal with weak roofs. Calculate the total weight of the objects
// here, including carried items.
if( roof_max_weight ) {
contents = all_inventory( TO );
for( i = 0; i < sizeof(contents); i++ ) {
objectweight = contents[i]->query_weight();
objectweight += contents[i]->query_loc_weight();
totalweight += objectweight;
#ifdef DEBUG
debug_printf( "event_enter: Total weight of %s is %d units.\n",
obj->query_name(), objectweight );
#endif
}
#ifdef DEBUG
debug_printf( "event_enter: The current weight on this roof is %d "
"units. Max weight is set to %d.\n", totalweight, roof_max_weight );
#endif
// Have we exceeded the maxweight?
if( totalweight > roof_max_weight ) {
fall = 1;
tell_room( environment( obj ), weak_messages[3] );
if( !(destination = load_object( weak_roof_dest ) ) ) {
tell_room( TO, "Error loading room "
+ weak_roof_dest + ", moving you to the void.\n"
"Please contact a creator.\n" );
for( i = 0; i < sizeof(contents); i++ )
contents[i]->move_with_look( "/room/void" );
return;
}
for( i = 0; i < sizeof(contents); i++ )
call_out( "do_fall", 1, contents[i], destination, "weak",
weak_messages, 0 );
}
}
if( gradient && !fall ) {
if( living( obj ) ) {
if( ( obj->query_property( "dead" ) )
|| !( obj->query_max_weight() ) ) {
call_out( "do_fall", 1, obj, slope_dest, "step",
ghost_fall_messages );
return;
}
encum = ( 100 * to_float( obj->query_loc_weight() ) )
/ to_float( obj->query_max_weight() );
diff = to_int( sin( ( 3.1415926536 / 180.0 ) * gradient )
* ( encum * 10 ) );
call_out( "gradient_check", 1, obj, slope_dest,
to_int( diff + ( gradient * 2 ) ) );
} else {
if( member_array( obj->query_name(),
({ "death", "binky" }) ) >= 0 )
return;
if( gradient > 3 ) {
if( obj->query_corpse() ) {
obj->move( slope_dest,
process_mess( corpse_slope_messages[1], obj, 0 ),
process_mess( corpse_slope_messages[0], obj, 0 ) );
} else {
obj->move( slope_dest,
process_mess( item_slope_messages[1], obj, 0 ),
process_mess( item_slope_messages[0], obj, 0 ) );
}
}
}
}
} /* event_enter() */
/** @ignore yes
* Internal function to perform the skillcheck for sloping roofs.
*/
void gradient_check( object obj, string destination, int diff ) {
object destob;
#ifdef DEBUG
debug_printf( "gradient_check( %s, \"%s\", %d )",
obj->query_name(), destination, diff );
#endif
switch( TASKER->perform_task( obj, ROCK, diff + 1, TM_FIXED ) ) {
case AWARD:
tell_object( obj, "%^YELLOW%^" + ({
"You balance more confidently on the " + place + ".",
"Climbing becomes easier."
})[ random(2) ] + "%^RESET%^\n" );
case SUCCEED:
tell_object( obj, "The " + place + " is steep, but you manage "
"not to fall.\n" );
break;
case FAIL:
if( !( destob = load_object( destination ) ) ) {
tell_object( obj, "Error loading room " + destination +
", moving you to the void.\nPlease contact a creator.\n" );
obj->move_with_look( "/room/void" );
} else
do_fall( obj, destob, "slope", slope_messages, 0 );
break;
default:
write( "Gnaaaaaaaaaaaah! You should not be getting this message. "
"Please contact a creator.\n" );
break;
}
} /* gradient_check() */
/** @ignore yes */
int do_roofjump( string dir ){
mixed * info;
int distance;
float weight, max_weight;
object destination, fall_destination;
string key;
if( !( key = translations[dir] ) || !( info = jump_info[key] ) ) {
add_failed_mess( "You can't jump there!\n" );
return 0;
}
if( !(destination = load_object( info[0] ) ) ) {
add_failed_mess( "Error! The file " + info[0] + " does not exist "
"or does not load. Please contact a creator.\n" );
return 0;
}
if( !(fall_destination = load_object( info[1] ) ) ) {
add_failed_mess( "Error! The file " + info[1] + " does not exist "
"or does not load. Please contact a creator.\n" );
return 0;
}
distance = info[2];
if( distance ) {
weight = TP->query_loc_weight();
max_weight = TP->query_max_weight();
// at this rate, a 300-bonus, unencumbered
// player can handle a 20-foot gap.
distance *= to_float( ( to_float(weight) * 7) /
to_float(max_weight) ) + 15;
switch( TASKER->perform_task( TP, ROCK, distance, TM_FIXED ) ) {
case AWARD:
write( "%^YELLOW%^"
+ jump_tm_messages[ random( sizeof( jump_tm_messages ) ) ]
+ "%^RESET%^\n" );
case SUCCEED:
write( process_mess( jump_success_messages[0], TP, dir ) );
TP->move_with_look( destination,
process_mess( jump_success_messages[2], TP, dir ),
process_mess( jump_success_messages[1], TP, dir ), );
break;
case FAIL:
do_fall( TP, fall_destination, "jump",
jump_failure_messages, dir );
break;
default:
write( "Oh dear. Something is broken. Please inform a "
"creator.\n" );
}
return 1;
}
} /* do_roofjump() */
/** @ignore yes */
string query_death_reason() {
call_out( (: death_reason = 0 :), 2 );
if( death_reason )
return PLAYER_OBJ->convert_message( death_reason );
return "a rooftop (" + file_name( TO )
+ ") with an incorrectly set death message";
} /* query_death_reason() */
/** @ignore yes */
void set_death_reason( string str ) {
death_reason = str;
} /* set_death_reason() */
/** @ignore yes */
string process_string( string str, mapping transforms ) {
string foo, bar;
foreach( foo, bar in transforms )
if( bar ) str = replace_string( str, foo, bar );
return PLAYER_OBJ->convert_message( str );
} /* process_string() */
/** @ignore yes */
string process_mess( string str, object ob, string direction ) {
string opp_dir;
string * directions = ({ "north", "northeast", "east", "southeast",
"south", "southwest", "west", "northwest" });
if( !str || ( str == "" ) )
return "";
if( stringp( direction ) ) {
if( member_array( direction, directions ) < 0 ) {
opp_dir = 0;
} else {
opp_dir =
directions[ ( member_array( direction, directions ) + 4 ) % 8 ];
}
}
str = process_string( str, ([
"$dir$" : direction,
"$opp_dir$" : opp_dir,
"$short$" : ob->query_short(),
"$poss$" : ob->query_possessive(),
"$pronoun$" : ob->query_pronoun(),
"$obj$" : ob->query_objective(),
"$a_short$" : ob->a_short(),
"$the_short$" : ob->the_short(),
"$one_short$" : ob->one_short()
]) );
return implode( explode( str, ". " ),
(: "$C$" + $1 + ". " + "$C$" + $2 :) );
} /* process_mess() */