diff options
Diffstat (limited to 'crawl-ref/source/delay.cc')
-rw-r--r-- | crawl-ref/source/delay.cc | 1129 |
1 files changed, 787 insertions, 342 deletions
diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc index 0894207395..6998a3df50 100644 --- a/crawl-ref/source/delay.cc +++ b/crawl-ref/source/delay.cc @@ -2,6 +2,8 @@ * File: delay.cc * Summary: Functions for handling multi-turn actions. * + * Modified for Crawl Reference by $Author$ on $Date$ + * * Change History (most recent first): * * <1> Sept 09, 2001 BWR Created @@ -13,40 +15,111 @@ #include <stdio.h> #include <string.h> +#include "clua.h" +#include "command.h" #include "delay.h" #include "enum.h" #include "food.h" +#include "invent.h" #include "items.h" #include "itemname.h" +#include "itemprop.h" #include "item_use.h" #include "it_use2.h" #include "message.h" #include "misc.h" #include "monstuff.h" +#include "mstuff2.h" #include "ouch.h" #include "output.h" #include "player.h" #include "randart.h" #include "spl-util.h" #include "stuff.h" +#include "travel.h" + +extern std::vector<SelItem> items_for_multidrop; + +static void armour_wear_effects(const int item_inv_slot); +static void handle_run_delays(const delay_queue_item &delay); +static void handle_macro_delay(); + +// Returns true if this delay can act as a parent to other delays, i.e. if +// other delays can be spawned while this delay is running. If is_parent_delay +// returns true, new delays will be pushed immediately to the front of the +// delay in question, rather than at the end of the queue. +static bool is_parent_delay(int delay) +{ + // Interlevel travel can do upstairs/downstairs delays. + // Lua macros can in theory perform any of the other delays, + // including travel; in practise travel still assumes there can be + // no parent delay. + return (delay == DELAY_TRAVEL || delay == DELAY_MACRO); +} + +static void push_delay(const delay_queue_item &delay) +{ + for (delay_queue_type::iterator i = you.delay_queue.begin(); + i != you.delay_queue.end(); + ++i) + { + if (is_parent_delay( i->type )) + { + you.delay_queue.insert(i, delay); + return; + } + } + you.delay_queue.push_back( delay ); +} + +static void pop_delay() +{ + if (!you.delay_queue.empty()) + you.delay_queue.erase( you.delay_queue.begin() ); +} + +static void clear_pending_delays() +{ + while (you.delay_queue.size() > 1) + you.delay_queue.pop_back(); +} void start_delay( int type, int turns, int parm1, int parm2 ) /***********************************************************/ { - delay_queue_item delay; + delay_queue_item delay; delay.type = type; delay.duration = turns; delay.parm1 = parm1; delay.parm2 = parm2; - you.delay_queue.push( delay ); + switch ( delay.type ) { + case DELAY_ARMOUR_ON: + mpr("You start putting on your armour.", MSGCH_MULTITURN_ACTION); + break; + case DELAY_ARMOUR_OFF: + mpr("You start removing your armour.", MSGCH_MULTITURN_ACTION); + break; + case DELAY_MEMORISE: + mpr("You start memorising the spell.", MSGCH_MULTITURN_ACTION); + break; + case DELAY_PASSWALL: + mpr("You begin to meditate on the wall.", MSGCH_MULTITURN_ACTION); + break; + default: + break; + } + push_delay( delay ); } void stop_delay( void ) /*********************/ { - delay_queue_item delay = you.delay_queue.front(); + if ( you.delay_queue.empty() ) + return; + + delay_queue_item delay = you.delay_queue.front(); // At the very least we can remove any queued delays, right // now there is no problem with doing this... note that @@ -54,42 +127,60 @@ void stop_delay( void ) // as the effect of a delay doesn't normally allow interaction // until it is done... it merely chains up individual actions // into a single action. -- bwr - if (you.delay_queue.size() > 1) - { - while (you.delay_queue.size()) - you.delay_queue.pop(); - - you.delay_queue.push( delay ); - } + clear_pending_delays(); switch (delay.type) { case DELAY_BUTCHER: // Corpse keeps track of work in plus2 field, see handle_delay() -- bwr mpr( "You stop butchering the corpse." ); - you.delay_queue.pop(); + pop_delay(); break; - case DELAY_MEMORIZE: + case DELAY_MEMORISE: // Losing work here is okay... having to start from // scratch is a reasonable behaviour. -- bwr mpr( "Your memorization is interrupted." ); - you.delay_queue.pop(); + pop_delay(); break; case DELAY_PASSWALL: // The lost work here is okay since this spell requires - // the player to "attune to the rock". If changed, the + // the player to "attune to the rock". If changed, then // the delay should be increased to reduce the power of // this spell. -- bwr mpr( "Your meditation is interrupted." ); - you.delay_queue.pop(); + pop_delay(); + break; + + case DELAY_MULTIDROP: + // No work lost + mpr( "You stop dropping stuff." ); + pop_delay(); + break; + + case DELAY_RUN: + case DELAY_REST: + case DELAY_TRAVEL: + case DELAY_MACRO: + // Always interruptible. + pop_delay(); + + // Keep things consistent, otherwise disturbing phenomena can occur. + // Note that runrest::stop() will turn around and call stop_delay() + // again, but that's okay because the delay is already popped off + // the queue. + if (is_run_delay(delay.type) && you.running) + stop_running(); + + // There's no special action needed for macros - if we don't call out + // to the Lua function, it can't do damage. break; - case DELAY_INTERUPTABLE: - // always stopable by definition... + case DELAY_INTERRUPTIBLE: + // always stoppable by definition... // try using a more specific type anyways. -- bwr - you.delay_queue.pop(); + pop_delay(); break; case DELAY_EAT: @@ -101,7 +192,7 @@ void stop_delay( void ) case DELAY_ARMOUR_ON: case DELAY_ARMOUR_OFF: - // These two have the default action of not being interuptable, + // These two have the default action of not being interruptible, // although they will often be chained (remove cloak, remove // armour, wear new armour, replace cloak), all of which can // be stopped when complete. This is a fairly reasonable @@ -117,7 +208,8 @@ void stop_delay( void ) case DELAY_DROP_ITEM: // one turn... only used for easy armour drops case DELAY_ASCENDING_STAIRS: // short... and probably what people want case DELAY_DESCENDING_STAIRS: // short... and probably what people want - case DELAY_UNINTERUPTABLE: // never stopable + case DELAY_UNINTERRUPTIBLE: // never stoppable + case DELAY_JEWELLERY_ON: // one turn default: break; } @@ -136,414 +228,767 @@ int current_delay_action( void ) : DELAY_NOT_DELAYED); } +bool is_run_delay(int delay) +{ + return (delay == DELAY_RUN || delay == DELAY_REST + || delay == DELAY_TRAVEL); +} + void handle_delay( void ) /***********************/ { - int ego; char str_pass[ ITEMNAME_SIZE ]; - if (you_are_delayed()) + if (!you_are_delayed()) + return; + + delay_queue_item &delay = you.delay_queue.front(); + + // Run delays and Lua delays don't have a specific end time. + if (is_run_delay(delay.type)) { - delay_queue_item &delay = you.delay_queue.front(); + handle_run_delays(delay); + return; + } - // First check cases where delay may no longer be valid: - // XXX: need to handle passwall when monster digs -- bwr - if (delay.type == DELAY_BUTCHER) + if (delay.type == DELAY_MACRO) + { + handle_macro_delay(); + return; + } + + // First check cases where delay may no longer be valid: + // XXX: need to handle passwall when monster digs -- bwr + if (delay.type == DELAY_BUTCHER) + { + // A monster may have raised the corpse you're chopping up! -- bwr + // Note that a monster could have raised the corpse and another + // monster could die and create a corpse with the same ID number... + // However, it would not be at the player's square like the + // original and that's why we do it this way. Note that + // we ignore the conversion to skeleton possibility just to + // be nice. -- bwr + if (is_valid_item( mitm[ delay.parm1 ] ) + && mitm[ delay.parm1 ].base_type == OBJ_CORPSES + && mitm[ delay.parm1 ].x == you.x_pos + && mitm[ delay.parm1 ].y == you.y_pos ) { - // A monster may have raised the corpse you're chopping up! -- bwr - // Note that a monster could have raised the corpse and another - // monster could die and create a corpse with the same ID number... - // However, it would not be at the player's square like the - // original and that's why we do it this way. Note that - // we ignore the conversion to skeleton possiblity just to - // be nice. -- bwr - if (is_valid_item( mitm[ delay.parm1 ] ) - && mitm[ delay.parm1 ].base_type == OBJ_CORPSES - && mitm[ delay.parm1 ].x == you.x_pos - && mitm[ delay.parm1 ].y == you.y_pos) - { - // mark work done on the corpse in case we stop -- bwr - mitm[ delay.parm1 ].plus2++; - } - else - { - // corpse is no longer valid! - stop_delay(); - return; + // special < 100 is the rottenness check + if ( (mitm[delay.parm1].special < 100) && + (delay.parm2 >= 100) ) { + mpr("The corpse rots.", MSGCH_ROTTEN_MEAT); + delay.parm2 = 99; // don't give the message twice } + + // mark work done on the corpse in case we stop -- bwr + mitm[ delay.parm1 ].plus2++; + } + else + { + // corpse is no longer valid! + stop_delay(); + return; } + } + if ( delay.type == DELAY_MULTIDROP ) + { + + // Throw away invalid items; items usually go invalid because + // of chunks rotting away. + while (!items_for_multidrop.empty() + // Don't look for gold in inventory + && items_for_multidrop[0].slot != PROMPT_GOT_SPECIAL + && !is_valid_item(you.inv[ items_for_multidrop[0].slot ])) + items_for_multidrop.erase( items_for_multidrop.begin() ); - // Handle delay: - if (delay.duration > 0) + if ( items_for_multidrop.empty() ) { + // ran out of things to drop + pop_delay(); + return; + } + } + + // Handle delay: + if (delay.duration > 0) + { #if DEBUG_DIAGNOSTICS - snprintf( info, INFO_SIZE, "Delay type: %d duration: %d", - delay.type, delay.duration ); + snprintf( info, INFO_SIZE, "Delay type: %d (%s), duration: %d", + delay.type, delay_name(delay.type), delay.duration ); - mpr( info, MSGCH_DIAGNOSTICS ); + mpr( info, MSGCH_DIAGNOSTICS ); #endif - delay.duration--; + switch ( delay.type ) + { + case DELAY_ARMOUR_ON: + in_name( delay.parm1, DESC_NOCAP_YOUR, str_pass ); + snprintf( info, INFO_SIZE, + "You continue putting on %s.", str_pass ); + mpr(info, MSGCH_MULTITURN_ACTION); + break; + case DELAY_ARMOUR_OFF: + in_name( delay.parm1, DESC_NOCAP_YOUR, str_pass ); + snprintf( info, INFO_SIZE, + "You continue taking off %s.", str_pass ); + mpr(info, MSGCH_MULTITURN_ACTION); + break; + case DELAY_BUTCHER: + mpr("You continue butchering the corpse.", + MSGCH_MULTITURN_ACTION); + break; + case DELAY_MEMORISE: + mpr("You continue memorising.", MSGCH_MULTITURN_ACTION); + break; + case DELAY_PASSWALL: + mpr("You continue meditating on the rock.", + MSGCH_MULTITURN_ACTION); + break; + case DELAY_MULTIDROP: + drop_item( items_for_multidrop[0].slot, + items_for_multidrop[0].quantity ); + items_for_multidrop.erase( items_for_multidrop.begin() ); + break; + default: + break; } - else + delay.duration--; + } + else + { + switch (delay.type) { - switch (delay.type) - { - case DELAY_AUTOPICKUP: - break; + case DELAY_AUTOPICKUP: + break; - case DELAY_WEAPON_SWAP: - weapon_switch( delay.parm1 ); - break; + case DELAY_WEAPON_SWAP: + weapon_switch( delay.parm1 ); + break; - case DELAY_ARMOUR_ON: - set_ident_flags( you.inv[ delay.parm1 ], ISFLAG_EQ_ARMOUR_MASK ); + case DELAY_JEWELLERY_ON: + puton_ring( delay.parm1, false ); + break; - in_name( delay.parm1, DESC_NOCAP_YOUR, str_pass ); - snprintf( info, INFO_SIZE, "You finish putting on %s.", str_pass ); - mpr(info); + case DELAY_ARMOUR_ON: + armour_wear_effects( delay.parm1 ); + break; + + case DELAY_ARMOUR_OFF: + { + in_name( delay.parm1, DESC_NOCAP_YOUR, str_pass ); + snprintf( info, INFO_SIZE, "You finish taking off %s.", str_pass ); + mpr(info); - if (you.inv[ delay.parm1 ].sub_type < ARM_SHIELD - || you.inv[ delay.parm1 ].sub_type > ARM_LARGE_SHIELD) + const equipment_type slot = + get_armour_slot( you.inv[delay.parm1] ); + + if (slot == EQ_BODY_ARMOUR) + { + you.equip[EQ_BODY_ARMOUR] = -1; + } + else + { + switch (slot) { - you.equip[EQ_BODY_ARMOUR] = delay.parm1; + case EQ_SHIELD: + if (delay.parm1 == you.equip[EQ_SHIELD]) + you.equip[EQ_SHIELD] = -1; + break; - if (you.duration[DUR_ICY_ARMOUR] != 0) - { - mpr( "Your icy armour melts away.", MSGCH_DURATION ); - you.redraw_armour_class = 1; - you.duration[DUR_ICY_ARMOUR] = 0; - } + case EQ_CLOAK: + if (delay.parm1 == you.equip[EQ_CLOAK]) + you.equip[EQ_CLOAK] = -1; + break; + + case EQ_HELMET: + if (delay.parm1 == you.equip[EQ_HELMET]) + you.equip[EQ_HELMET] = -1; + break; + + + case EQ_GLOVES: + if (delay.parm1 == you.equip[EQ_GLOVES]) + you.equip[EQ_GLOVES] = -1; + break; + + case EQ_BOOTS: + if (delay.parm1 == you.equip[EQ_BOOTS]) + you.equip[EQ_BOOTS] = -1; + break; + + default: + break; } - else + } + + unwear_armour( delay.parm1 ); + + you.redraw_armour_class = 1; + you.redraw_evasion = 1; + break; + } + case DELAY_EAT: + mpr( "You finish eating." ); + // For chunks, warn the player if they're not getting much + // nutrition. + if (delay.parm1) + chunk_nutrition_message(delay.parm1); + break; + + case DELAY_MEMORISE: + mpr( "You finish memorising." ); + add_spell_to_memory( delay.parm1 ); + break; + + case DELAY_PASSWALL: + { + mpr( "You finish merging with the rock." ); + more(); // or the above message won't be seen + + const int pass_x = delay.parm1; + const int pass_y = delay.parm2; + + if (pass_x != 0 && pass_y != 0) { - switch (you.inv[ delay.parm1 ].sub_type) + + switch (grd[ pass_x ][ pass_y ]) { - case ARM_BUCKLER: - case ARM_LARGE_SHIELD: - case ARM_SHIELD: - if (you.duration[DUR_CONDENSATION_SHIELD]) - { - mpr( "Your icy shield evaporates.", MSGCH_DURATION ); - you.duration[DUR_CONDENSATION_SHIELD] = 0; - } - you.equip[EQ_SHIELD] = delay.parm1; - break; - case ARM_CLOAK: - you.equip[EQ_CLOAK] = delay.parm1; - break; - case ARM_HELMET: - you.equip[EQ_HELMET] = delay.parm1; + case DNGN_ROCK_WALL: + case DNGN_STONE_WALL: + case DNGN_METAL_WALL: + case DNGN_GREEN_CRYSTAL_WALL: + case DNGN_WAX_WALL: + case DNGN_SILVER_STATUE: + case DNGN_ORANGE_CRYSTAL_STATUE: + ouch(1 + you.hp, 0, KILLED_BY_PETRIFICATION); break; - case ARM_GLOVES: - you.equip[EQ_GLOVES] = delay.parm1; + + case DNGN_SECRET_DOOR: // oughtn't happen + case DNGN_CLOSED_DOOR: // open the door + grd[ pass_x ][ pass_y ] = DNGN_OPEN_DOOR; break; - case ARM_BOOTS: - you.equip[EQ_BOOTS] = delay.parm1; + + default: break; } - } - ego = get_armour_ego_type( you.inv[ delay.parm1 ] ); - if (ego != SPARM_NORMAL) - { - switch (ego) + // move any monsters out of the way: + int mon = mgrd[ pass_x ][ pass_y ]; + if (mon != NON_MONSTER) { - case SPARM_RUNNING: - strcpy(info, "You feel quick"); - strcat(info, (you.species == SP_NAGA - || you.species == SP_CENTAUR) ? "." : " on your feet."); - mpr(info); - break; + // one square, a few squares, anywhere... + if (!shift_monster(&menv[mon]) + && !monster_blink(&menv[mon])) + { + monster_teleport( &menv[mon], true, true ); + } + } - case SPARM_FIRE_RESISTANCE: - mpr("You feel resistant to fire."); - break; + move_player_to_grid(pass_x, pass_y, false, true, true); + redraw_screen(); + } + } + break; - case SPARM_COLD_RESISTANCE: - mpr("You feel resistant to cold."); - break; + case DELAY_BUTCHER: + strcpy( info, "You finish " ); + strcat( info, (you.species == SP_TROLL + || you.species == SP_GHOUL) ? "ripping" + : "chopping" ); - case SPARM_POISON_RESISTANCE: - mpr("You feel healthy."); - break; + strcat( info, " the corpse into pieces." ); + mpr( info ); - case SPARM_SEE_INVISIBLE: - mpr("You feel perceptive."); - break; + turn_corpse_into_chunks( mitm[ delay.parm1 ] ); - case SPARM_DARKNESS: - if (!you.invis) - mpr("You become transparent for a moment."); - break; + if (you.berserker && you.berserk_penalty != NO_BERSERK_PENALTY) + { + mpr("You enjoyed that."); + you.berserk_penalty = 0; + } + break; - case SPARM_STRENGTH: - modify_stat(STAT_STRENGTH, 3, false); - break; + case DELAY_DROP_ITEM: + // Note: checking if item is droppable is assumed to + // be done before setting up this delay... this includes + // quantity (delay.parm2). -- bwr - case SPARM_DEXTERITY: - modify_stat(STAT_DEXTERITY, 3, false); - break; + // Make sure item still exists. + if (!is_valid_item( you.inv[ delay.parm1 ] )) + break; - case SPARM_INTELLIGENCE: - modify_stat(STAT_INTELLIGENCE, 3, false); - break; + // Must handle unwield_item before we attempt to copy + // so that temporary brands and such are cleared. -- bwr + if (delay.parm1 == you.equip[EQ_WEAPON]) + { + unwield_item( delay.parm1 ); + you.equip[EQ_WEAPON] = -1; + canned_msg( MSG_EMPTY_HANDED ); + } - case SPARM_PONDEROUSNESS: - mpr("You feel rather ponderous."); - // you.speed += 2; - you.redraw_evasion = 1; - break; + if (!copy_item_to_grid( you.inv[ delay.parm1 ], + you.x_pos, you.y_pos, delay.parm2, + true )) + { + mpr("Too many items on this level, not dropping the item."); + } + else + { + quant_name( you.inv[ delay.parm1 ], delay.parm2, + DESC_NOCAP_A, str_pass ); - case SPARM_LEVITATION: - mpr("You feel rather light."); - break; + snprintf( info, INFO_SIZE, "You drop %s.", str_pass ); + mpr(info); - case SPARM_MAGIC_RESISTANCE: - mpr("You feel resistant to magic."); - break; + dec_inv_item_quantity( delay.parm1, delay.parm2 ); + } + break; + + case DELAY_ASCENDING_STAIRS: + up_stairs(); + untag_followers(); + break; + + case DELAY_DESCENDING_STAIRS: + down_stairs( false, delay.parm1 ); + untag_followers(); + break; + + case DELAY_INTERRUPTIBLE: + case DELAY_UNINTERRUPTIBLE: + // these are simple delays that have no effect when complete + break; + + default: + mpr( "You finish doing something." ); + break; + } - case SPARM_PROTECTION: - mpr("You feel protected."); - break; + you.wield_change = true; + print_stats(); // force redraw of the stats + you.turn_is_over = true; + pop_delay(); + } +} - case SPARM_STEALTH: - mpr("You feel stealthy."); - break; +static void armour_wear_effects(const int item_slot) +{ + item_def &arm = you.inv[item_slot]; - case SPARM_RESISTANCE: - mpr("You feel resistant to extremes of temperature."); - break; + set_ident_flags(arm, ISFLAG_EQ_ARMOUR_MASK ); + mprf("You finish putting on %s.", item_name(arm, DESC_NOCAP_YOUR)); - case SPARM_POSITIVE_ENERGY: - mpr("Your life-force is being protected."); - break; + const equipment_type eq_slot = get_armour_slot(arm); - case SPARM_ARCHMAGI: - if (!you.skills[SK_SPELLCASTING]) - mpr("You feel strangely numb."); - else - mpr("You feel extremely powerful."); - break; - } - } + if (eq_slot == EQ_BODY_ARMOUR) + { + you.equip[EQ_BODY_ARMOUR] = item_slot; - if (is_random_artefact( you.inv[ delay.parm1 ] )) - use_randart( delay.parm1 ); + if (you.duration[DUR_ICY_ARMOUR] != 0) + { + mpr( "Your icy armour melts away.", MSGCH_DURATION ); + you.redraw_armour_class = 1; + you.duration[DUR_ICY_ARMOUR] = 0; + } + } + else + { + switch (eq_slot) + { + case EQ_SHIELD: + if (you.duration[DUR_CONDENSATION_SHIELD]) + { + mpr( "Your icy shield evaporates.", MSGCH_DURATION ); + you.duration[DUR_CONDENSATION_SHIELD] = 0; + } + you.equip[EQ_SHIELD] = item_slot; + break; + case EQ_CLOAK: + you.equip[EQ_CLOAK] = item_slot; + break; + case EQ_HELMET: + you.equip[EQ_HELMET] = item_slot; + break; + case EQ_GLOVES: + you.equip[EQ_GLOVES] = item_slot; + break; + case EQ_BOOTS: + you.equip[EQ_BOOTS] = item_slot; + break; + default: + break; + } + } - if (item_cursed( you.inv[ delay.parm1 ] )) - mpr( "Oops, that feels deathly cold." ); + int ego = get_armour_ego_type( arm ); + if (ego != SPARM_NORMAL) + { + switch (ego) + { + case SPARM_RUNNING: + mprf("You feel quick%s.", + (you.species == SP_NAGA || you.species == SP_CENTAUR) + ? "" : " on your feet"); + break; + + case SPARM_FIRE_RESISTANCE: + mpr("You feel resistant to fire."); + break; + + case SPARM_COLD_RESISTANCE: + mpr("You feel resistant to cold."); + break; + + case SPARM_POISON_RESISTANCE: + mpr("You feel healthy."); + break; + + case SPARM_SEE_INVISIBLE: + mpr("You feel perceptive."); + break; + + case SPARM_DARKNESS: + if (!you.invis) + mpr("You become transparent for a moment."); + break; + + case SPARM_STRENGTH: + modify_stat(STAT_STRENGTH, 3, false); + break; + + case SPARM_DEXTERITY: + modify_stat(STAT_DEXTERITY, 3, false); + break; + + case SPARM_INTELLIGENCE: + modify_stat(STAT_INTELLIGENCE, 3, false); + break; + + case SPARM_PONDEROUSNESS: + mpr("You feel rather ponderous."); + // you.speed += 2; + you.redraw_evasion = 1; + break; + + case SPARM_LEVITATION: + mpr("You feel rather light."); + break; + + case SPARM_MAGIC_RESISTANCE: + mpr("You feel resistant to magic."); + break; + + case SPARM_PROTECTION: + mpr("You feel protected."); + break; + + case SPARM_STEALTH: + mpr("You feel stealthy."); + break; + + case SPARM_RESISTANCE: + mpr("You feel resistant to extremes of temperature."); + break; + + case SPARM_POSITIVE_ENERGY: + mpr("Your life-force is being protected."); + break; + + case SPARM_ARCHMAGI: + if (!you.skills[SK_SPELLCASTING]) + mpr("You feel strangely numb."); + else + mpr("You feel extremely powerful."); + break; + } + } - you.redraw_armour_class = 1; - you.redraw_evasion = 1; - break; + if (is_random_artefact( arm )) + use_randart( item_slot ); - case DELAY_ARMOUR_OFF: - in_name( delay.parm1, DESC_NOCAP_YOUR, str_pass ); - snprintf( info, INFO_SIZE, "You finish taking off %s.", str_pass ); - mpr(info); + if (item_cursed( arm )) + mpr( "Oops, that feels deathly cold." ); - if (you.inv[ delay.parm1 ].sub_type < ARM_SHIELD - || you.inv[ delay.parm1 ].sub_type > ARM_LARGE_SHIELD) - { - you.equip[EQ_BODY_ARMOUR] = -1; - } - else - { - switch (you.inv[ delay.parm1 ].sub_type) - { - case ARM_BUCKLER: - case ARM_LARGE_SHIELD: - case ARM_SHIELD: - if (delay.parm1 == you.equip[EQ_SHIELD]) - you.equip[EQ_SHIELD] = -1; - break; + warn_shield_penalties(); - case ARM_CLOAK: - if (delay.parm1 == you.equip[EQ_CLOAK]) - you.equip[EQ_CLOAK] = -1; - break; + you.redraw_armour_class = 1; + you.redraw_evasion = 1; +} - case ARM_HELMET: - if (delay.parm1 == you.equip[EQ_HELMET]) - you.equip[EQ_HELMET] = -1; - break; +static command_type get_running_command() { + if ( kbhit() ) { + stop_running(); + return CMD_NO_CMD; + } + if ( is_resting() ) { + you.running.rest(); + return CMD_MOVE_NOWHERE; + } + return direction_to_command( you.running.x, you.running.y ); +} +static void handle_run_delays(const delay_queue_item &delay) +{ + // Handle inconsistencies between the delay queue and you.running. + // We don't want to send the game into a deadlock. + if (!you.running) + { + pop_delay(); + return; + } - case ARM_GLOVES: - if (delay.parm1 == you.equip[EQ_GLOVES]) - you.equip[EQ_GLOVES] = -1; - break; + ASSERT( !you.turn_is_over ); - case ARM_BOOTS: - if (delay.parm1 == you.equip[EQ_BOOTS]) - you.equip[EQ_BOOTS] = -1; - break; - } - } + command_type cmd = CMD_NO_CMD; + switch (delay.type) + { + case DELAY_REST: + case DELAY_RUN: + cmd = get_running_command(); + break; + case DELAY_TRAVEL: + cmd = travel(); + break; + } - unwear_armour( delay.parm1 ); + if (cmd != CMD_NO_CMD) + { + mesclr(); + process_command(cmd); + } - you.redraw_armour_class = 1; - you.redraw_evasion = 1; - break; + // If you.running has gone to zero, and the run delay was not + // removed, remove it now. This is needed to clean up after + // find_travel_pos() function in travel.cc. + if (!you.running && is_run_delay(current_delay_action())) + pop_delay(); +} - case DELAY_EAT: - mpr( "You finish eating." ); - break; +static void handle_macro_delay() +{ + run_macro(); +} - case DELAY_MEMORIZE: - mpr( "You finish memorising." ); - add_spell_to_memory( delay.parm1 ); - break; +void run_macro(const char *macroname) +{ + const int currdelay = current_delay_action(); + if (currdelay != DELAY_NOT_DELAYED && currdelay != DELAY_MACRO) + return; - case DELAY_PASSWALL: - { - mpr( "You finish merging with the rock." ); - more(); // or the above message won't be seen +#ifdef CLUA_BINDINGS + if (!clua) + { + mpr("Lua not initialized", MSGCH_DIAGNOSTICS); + stop_delay(); + return; + } - const int pass_x = delay.parm1; - const int pass_y = delay.parm2; + if (!clua.callbooleanfn(false, "c_macro", "s", macroname)) + { + if (clua.error.length()) + mpr(clua.error.c_str()); - if (pass_x != 0 && pass_y != 0) - { + stop_delay(); + } + else + { + start_delay(DELAY_MACRO, 1); + } +#else + stop_delay(); +#endif +} - switch (grd[ pass_x ][ pass_y ]) - { - case DNGN_ROCK_WALL: - case DNGN_STONE_WALL: - case DNGN_METAL_WALL: - case DNGN_GREEN_CRYSTAL_WALL: - case DNGN_WAX_WALL: - case DNGN_SILVER_STATUE: - case DNGN_ORANGE_CRYSTAL_STATUE: - ouch(1 + you.hp, 0, KILLED_BY_PETRIFICATION); - break; - - case DNGN_SECRET_DOOR: // oughtn't happen - case DNGN_CLOSED_DOOR: // open the door - grd[ pass_x ][ pass_y ] = DNGN_OPEN_DOOR; - break; - - default: - break; - } +// Returns true if the delay should be interrupted, false if the user function +// had no opinion on the matter, -1 if the delay should not be interrupted. +static int userdef_interrupt_activity( const delay_queue_item &idelay, + activity_interrupt_type ai, + const activity_interrupt_data &at ) +{ +#ifdef CLUA_BINDINGS + const int delay = idelay.type; + lua_State *ls = clua.state(); + if (!ls || ai == AI_FORCE_INTERRUPT) + return (true); + + // Kludge: We have to continue to support ch_stop_run. :-( + if (is_run_delay(delay) && you.running && ai == AI_SEE_MONSTER) + { + bool stop_run = false; + if (clua.callfn("ch_stop_run", "M>b", + (const monsters *) at.data, &stop_run)) + { + if (stop_run) + return (true); - //jmf: hmm, what to do. kill the monster? (seems too powerful) - // displace the monster? randomly teleport the monster? - // This seems fair: try to move the monster, but if not - // able to, then kill it. - int mon = mgrd[ pass_x ][ pass_y ]; - if (mon != NON_MONSTER) - { - monster_blink( &menv[ mon ] ); + // No further processing. + return (-1); + } - // recheck square for monster - mon = mgrd[ pass_x ][ pass_y ]; - if (mon != NON_MONSTER) - monster_die( &menv[ mon ], KILL_YOU, 0 ); - } + // If we get here, ch_stop_run wasn't defined, fall through to the + // other handlers. + } + + const char *interrupt_name = activity_interrupt_name(ai); + const char *act_name = delay_name(delay); - you.x_pos = pass_x; - you.y_pos = pass_y; - redraw_screen(); + bool ran = clua.callfn("c_interrupt_activity", "1:ssA", + act_name, interrupt_name, &at); + if (ran) + { + // If the function returned nil, we want to cease processing. + if (lua_isnil(ls, -1)) + { + lua_pop(ls, 1); + return (-1); + } - const unsigned char grid = grd[ you.x_pos ][ you.y_pos ]; - if ((grid == DNGN_LAVA || grid == DNGN_DEEP_WATER) - && !player_is_levitating()) - { - if (you.species == SP_MERFOLK && grid == DNGN_DEEP_WATER) - { - mpr("You fall into the water and return " - "to your normal form."); - merfolk_start_swimming(); - } - else - { - fall_into_a_pool( true, grid ); - redraw_screen(); - } - } - } - } - break; + bool stopact = lua_toboolean(ls, -1); + lua_pop(ls, 1); + if (stopact) + return (true); + } - case DELAY_BUTCHER: - strcpy( info, "You finish " ); - strcat( info, (you.species == SP_TROLL - || you.species == SP_GHOUL) ? "ripping" - : "chopping" ); + if (delay == DELAY_MACRO && + clua.callbooleanfn(true, "c_interrupt_macro", + "sA", interrupt_name, &at)) + return (true); - strcat( info, " the corpse into pieces." ); - mpr( info ); +#endif + return (false); +} - turn_corpse_into_chunks( mitm[ delay.parm1 ] ); +// Returns true if the activity should be interrupted, false otherwise. +static bool should_stop_activity(const delay_queue_item &item, + activity_interrupt_type ai, + const activity_interrupt_data &at) +{ + int userd = userdef_interrupt_activity(item, ai, at); - if (you.berserker && you.berserk_penalty != NO_BERSERK_PENALTY) - { - mpr("You enjoyed that."); - you.berserk_penalty = 0; - } - break; + // If the user script wanted to stop the activity or cease processing, + // do so. + if (userd) + return (userd == 1); - case DELAY_DROP_ITEM: - // Note: checking if item is dropable is assumed to - // be done before setting up this delay... this includes - // quantity (delay.parm2). -- bwr + return (ai == AI_FORCE_INTERRUPT || + (Options.activity_interrupts[item.type][ai])); +} - // Make sure item still exists. - if (!is_valid_item( you.inv[ delay.parm1 ] )) - break; +void interrupt_activity( activity_interrupt_type ai, + const activity_interrupt_data &at ) +{ + const int delay = current_delay_action(); + if (delay == DELAY_NOT_DELAYED) + return; - // Must handle unwield_item before we attempt to copy - // so that temporary brands and such are cleared. -- bwr - if (delay.parm1 == you.equip[EQ_WEAPON]) - { - unwield_item( delay.parm1 ); - you.equip[EQ_WEAPON] = -1; - canned_msg( MSG_EMPTY_HANDED ); - } + // First try to stop the current delay. + const delay_queue_item &item = you.delay_queue.front(); + + if (should_stop_activity(item, ai, at)) + { + stop_delay(); + return; + } - if (!copy_item_to_grid( you.inv[ delay.parm1 ], - you.x_pos, you.y_pos, delay.parm2 )) + // Check the other queued delays; the first delay that is interruptible + // will kill itself and all subsequent delays. This is so that a travel + // delay stacked behind a delay such as stair/autopickup will be killed + // correctly by interrupts that the simple stair/autopickup delay ignores. + for (int i = 1, size = you.delay_queue.size(); i < size; ++i) + { + const delay_queue_item &it = you.delay_queue[i]; + if (should_stop_activity(it, ai, at)) + { + // Do we have a queued run delay? If we do, flush the delay queue + // so that stop running Lua notifications happen. + for (int j = i; j < size; ++j) + { + if (is_run_delay( you.delay_queue[j].type )) { - mpr("Too many items on this level, not dropping the item."); + stop_delay(); + return; } - else - { - quant_name( you.inv[ delay.parm1 ], delay.parm2, - DESC_NOCAP_A, str_pass ); + } - snprintf( info, INFO_SIZE, "You drop %s.", str_pass ); - mpr(info); + // Non-run queued delays can be discarded without any processing. + you.delay_queue.erase( you.delay_queue.begin() + i, + you.delay_queue.end() ); + break; + } + } +} - dec_inv_item_quantity( delay.parm1, delay.parm2 ); - } - break; +static const char *activity_interrupt_names[] = +{ + "force", "keypress", "full_hp", "full_mp", "statue", + "hungry", "message", "hp_loss", "burden", "stat", + "monster", "monster_attack", "teleport" +}; - case DELAY_ASCENDING_STAIRS: - up_stairs(); - untag_followers(); - break; +const char *activity_interrupt_name(activity_interrupt_type ai) +{ + ASSERT( sizeof(activity_interrupt_names) + / sizeof(*activity_interrupt_names) == NUM_AINTERRUPTS ); - case DELAY_DESCENDING_STAIRS: - down_stairs( false, delay.parm1 ); - untag_followers(); - break; + if (ai == NUM_AINTERRUPTS) + return (""); - case DELAY_INTERUPTABLE: - case DELAY_UNINTERUPTABLE: - // these are simple delays that have no effect when complete - break; + return activity_interrupt_names[ai]; +} - default: - mpr( "You finish doing something." ); - break; - } +activity_interrupt_type get_activity_interrupt(const std::string &name) +{ + ASSERT( sizeof(activity_interrupt_names) + / sizeof(*activity_interrupt_names) == NUM_AINTERRUPTS ); - you.wield_change = true; - print_stats(); // force redraw of the stats - you.turn_is_over = 1; - you.delay_queue.pop(); - } + for (int i = 0; i < NUM_AINTERRUPTS; ++i) + if (name == activity_interrupt_names[i]) + return activity_interrupt_type(i); + + return (NUM_AINTERRUPTS); +} + +static const char *delay_names[] = +{ + "not_delayed", "eat", "armour_on", "armour_off", "jewellery_on", + "memorise", "butcher", "autopickup", "weapon_swap", "passwall", + "drop_item", "multidrop", "ascending_stairs", "descending_stairs", "run", + "rest", "travel", "macro", "interruptible", "uninterruptible", +}; + +// Gets a delay given its name. +// name must be lowercased already! +delay_type get_delay(const std::string &name) +{ + ASSERT( sizeof(delay_names) / sizeof(*delay_names) == NUM_DELAYS ); + + for (int i = 0; i < NUM_DELAYS; ++i) + { + if (name == delay_names[i]) + return delay_type(i); } + + // Also check American spellings: + if (name == "armor_on") + return (DELAY_ARMOUR_ON); + + if (name == "armor_off") + return (DELAY_ARMOUR_OFF); + + if (name == "memorize") + return (DELAY_MEMORISE); + + if (name == "jewelry_on") + return (DELAY_JEWELLERY_ON); + + return (NUM_DELAYS); +} + +const char *delay_name(int delay) +{ + ASSERT( sizeof(delay_names) / sizeof(*delay_names) == NUM_DELAYS ); + + if (delay < 0 || delay >= NUM_DELAYS) + return (""); + + return delay_names[delay]; } |