view arogue7/effects.c @ 296:000b1c5b8d63

UltraRogue: fix inventory collision after save and restore. Inventory letters are based on "identifiers" stored in objects' o_ident field. Identifiers are allocated by get_ident(), which keeps a list of objects that have them, to avoid giving the same identifier to multiple objects. The list is not stored in the savefile, so after restore, get_ident() was not aware of existing identifiers. This resulted in picked-up objects having the same inventory letters as objects restored from the file. The restore code now adds all objects with identifiers to the list.
author John "Elwin" Edwards
date Mon, 15 Jan 2018 20:20:35 -0500
parents e1cd27c5464f
children
line wrap: on
line source

/*
 * effects.c  -  functions for dealing with appllying effects to monsters
 *
 * Advanced Rogue
 * Copyright (C) 1984, 1985, 1986 Michael Morgan, Ken Dalka and AT&T
 * All rights reserved.
 *
 * Based on "Rogue: Exploring the Dungeons of Doom"
 * Copyright (C) 1980, 1981 Michael Toy, Ken Arnold and Glenn Wichman
 * All rights reserved.
 *
 * See the file LICENSE.TXT for full copyright and licensing information.
 */

#include <string.h>
#include "curses.h"
#include "rogue.h"

/*
 * effect:
 *	Check for effects of one thing hitting another thing.  Return
 *	the reason code if the defender is killed.  Otherwise return 0.
 */
int
effect(struct thing *att, struct thing *def, struct object *weap, bool thrown, 
       bool see_att, bool see_def)
{
    register bool att_player, def_player;
    char attname[LINELEN+1], defname[LINELEN+1];

    /* See if the attacker or defender is the player */
    att_player = (att == &player);
    def_player = (def == &player);

    /*
     * If the player could see the attacker or defender, they can't
     * surprise anymore (don't bother checking if they could).
     */
    if (see_att) turn_off(*att, CANSURPRISE);
    if (see_def) turn_off(*def, CANSURPRISE);

    /* What are the attacker and defender names? */
    if (att_player) strcpy(attname, "you");
    else {
	if (see_att) strcpy(attname, monster_name(att));
	else strcpy(attname, "something");
    }

    if (def_player) strcpy(defname, "you");
    else {
	if (see_def) strcpy(defname, monster_name(def));
	else strcpy(defname, "something");
    }

    /*
     * See what happens to the attacker first.  We can skip this
     * whole section, however, if the defender is the player.
     * Nothing happens (yet) to anyone just for hitting the player.
     */
    if (!def_player) {
	if (!thrown) {	/* Some things require a direct hit. */
	    /*
	     * If the attacker hits a rusting monster, The weapon
	     * may be damaged
	     */
	    if (on(*def, CANRUST)	&& weap				&&
		weap->o_type != RELIC	&& (weap->o_flags & ISMETAL)	&&
		!(weap->o_flags & ISPROT)) {
		    if ((weap->o_hplus < 1 && weap->o_dplus < 1) ||
			roll(1,20) < weap->o_hplus+weap->o_dplus+10) {
			    if (rnd(100) < 50) weap->o_hplus--;
			    else               weap->o_dplus--;
			    if (att_player)
				msg(terse ? "Your %s weakens!"
					  : "Your %s appears to be weaker now!",
				    weaps[weap->o_which].w_name);
		    }
	    }
	}
		
	/* If the attacker hit something that shrieks, wake the dungeon */
	if (on(*def, CANSHRIEK)) {
	    turn_off(*def, CANSHRIEK);
	    if (see_def)
		msg("%s emits a piercing shriek.", prname(defname, TRUE));
	    else msg("You hear a piercing shriek.");
	    aggravate(TRUE, TRUE);
	}

	/*
	 * does the creature explode when hit?
	 */
	if (on(*def, CANEXPLODE)) {
	    if (see_def) msg("%s explodes!", prname(defname, TRUE));
	    else msg("You hear a tremendous explosion!");
	    explode(def);
	    if (pstats.s_hpt <= 0)
	        death(def->t_index);
	}
    }

    /*
     * Now let's see what happens to the defender.  Start out with
     * the things that everyone can do.  Then exit if the attacker
     * is the player.
     */
    if (!thrown) {
	/* 
	 * Can the player confuse? 
	 */
	if (on(*att, CANHUH) && att_player) {
	    msg("Your hands stop glowing red.");
	    if (off(*def, ISCLEAR) && 
	       (off(*def, ISUNIQUE) || !save(VS_MAGIC, def, 0))) {
		if (see_def) msg("%s appears confused.", prname(defname, TRUE));
		turn_on(*def, ISHUH);
	    }
	    turn_off(*att, CANHUH);
	}

	/* Return now if the attacker is the player. */
	if (att_player) return(0);

	/*
	 * Some monsters may take half your hit points
	 */
	if (on(*att, CANSUCK) && !save(VS_MAGIC, def, 0)) {
	    if (def->t_stats.s_hpt == 1) return(att->t_index); /* Killed! */
	    else {
	        def->t_stats.s_hpt /= 2;
	        if (def_player)
		    msg("You feel your life force being drawn from you.");
	    }
	}

	/*
	 * If a hugging monster hits, it may SQUEEEEEEEZE.
	 */
	if (on(*att, CANHUG)) {
	    if (roll(1,20) >= 18 || roll(1,20) >= 18) {
		if (def_player)
		    msg("%s squeezes you against itself.",
				prname(attname, TRUE));
		else if (see_att)
		    msg("%s squeezes hard.", prname(attname, TRUE));

		if ((def->t_stats.s_hpt -= roll(2,8)) <= 0)
		    return(att->t_index);
	    }
	}

	/*
	 * Some monsters have poisonous bites.
	 */
	if (on(*att, CANPOISON) && !save(VS_POISON, def, 0)) {
	    if (def_player) {
		if (ISWEARING(R_SUSABILITY))
		    msg(terse ? "Sting has no effect"
			      : "A sting momentarily weakens you");
		else {
		    chg_str(-1);
		    msg(terse ? "A sting has weakened you" :
		    "You feel a sting in your arm and now feel weaker");
		}
	    }
	    else {
		/* Subtract a strength point and see if it kills it */
		if (--def->t_stats.s_str <= 0) return(D_STRENGTH);
	    }
	}

	/*
	 * Turning to stone:
	 */
	if (on(*att, TOUCHSTONE)) {
	    if (def_player) turn_off(*att, TOUCHSTONE);
	    if (on(*def, CANINWALL)) {
		if (def_player)
		    msg("%s's touch has no effect.", prname(attname, TRUE));
	    }
	    else {
		if (!save(VS_PETRIFICATION, def, 0) && rnd(100) < 10) {
		    if (def_player) {
			msg("Your body begins to solidify.");
			msg("You are turned to stone !!! --More--");
			wait_for(' ');
			return(D_PETRIFY);
		    }
		    else {
			/* The monster got stoned! */
			turn_on(*def, ISSTONE);
			turn_off(*def, ISRUN);
			turn_off(*def, ISINVIS);
			turn_off(*def, ISDISGUISE);
			if (see_def)
			    msg("%s turns to stone.", prname(defname, TRUE));
			else if (cansee(unc(def->t_pos)))
			    msg("A new statue appears!");
		    }
		}
		else if (def->t_action != A_FREEZE) {
		    if (def_player)
			msg("%s's touch stiffens your limbs.",
					prname(attname, TRUE));
		    else if (see_def)
			msg("%s appears to freeze.", prname(defname, TRUE));

		    def->t_no_move += movement(def) * STONETIME;
		    def->t_action = A_FREEZE;
		}
	    }
	}

	/*
	 * Wraiths might drain energy levels
	 */
	if ((on(*att, CANDRAIN) || on(*att, DOUBLEDRAIN)) && 
	    !save(VS_POISON, def, 3-(att->t_stats.s_lvl/5))) {
	    if (def_player) {
		lower_level(att->t_index);
		if (on(*att, DOUBLEDRAIN)) lower_level(att->t_index);
		turn_on(*att, DIDDRAIN);  
	    }
	    else {
		def->t_stats.s_hpt -= roll(1, 8);
		def->t_stats.s_lvl--;
		if (on(*att, DOUBLEDRAIN)) {
		    def->t_stats.s_hpt -= roll(1, 8);
		    def->t_stats.s_lvl--;
		}
		if (see_def)
		    msg("%s appears less skillfull.", prname(defname, TRUE));

		/* Did it kill it? */
		if (def->t_stats.s_hpt <= 0 ||
		    def->t_stats.s_lvl <= 0)
		    return(att->t_index);
	    }
	}

	/*
	 * Paralyzation:
	 */
	if (on(*att, CANPARALYZE) && def->t_action != A_FREEZE) {
	    if (def_player) turn_off(*att, CANPARALYZE);
	    if (!save(VS_PARALYZATION, def, 0)) {
		if (on(*def, CANINWALL)) {
		    if (def_player)
			msg("%s's touch has no effect.", prname(attname, TRUE));
		}
		else {
		    if (def_player)
			msg("%s's touch paralyzes you.", prname(attname, TRUE));
		    else if (see_def)
			msg("%s appears to freeze.", prname(defname, TRUE));

		    def->t_no_move += movement(def) * FREEZETIME;
		    def->t_action = A_FREEZE;
		}
	    }
	}

	/*
	 * Painful wounds make the defendant faint
	 */
	 if (on(*att, CANPAIN) && def->t_action != A_FREEZE) {
	    if (def_player) turn_off(*att, CANPAIN);
	    if (!ISWEARING(R_ALERT) && !save(VS_POISON, def, 0)) {
		    if (def_player)
			msg("You faint from the painful wound");
		    else if (see_def)
			msg("%s appears to faint.", prname(defname, TRUE));

		    def->t_no_move += movement(def) * PAINTIME;
		    def->t_action = A_FREEZE;
	    }
	}

	/*
	 * Some things currently affect only the player.  Let's make
	 * a check here so we don't have to check for each thing.
	 */
	if (def_player) {
	/*
	 * Stinking monsters make the defender weaker (to hit).  For now
	 * this will only affect the player.  We may later add the HASSTINK
	 * effect to monsters, too.
	 */
	    if (on(*att, CANSTINK)) {
		turn_off(*att, CANSTINK);
		if (!save(VS_POISON, def, 0)) {
		    msg("The stench of %s sickens you.",
				prname(attname, FALSE));
		    if (on(player, HASSTINK)) lengthen(unstink, STINKTIME);
		    else {
			turn_on(player, HASSTINK);
			fuse(unstink, NULL, STINKTIME, AFTER);
		    }
		}
	    }

	    /*
	     * Chilling monster reduces strength each time.  This only
	     * affects the player for now because of its temporary nature.
	     */
	    if (on(*att, CANCHILL)) {
		if (!ISWEARING(R_SUSABILITY) && !save(VS_POISON, def, 0)) {
		    msg("You cringe at %s's chilling touch.",
				prname(attname, FALSE));
		    chg_str(-1);
		    if (lost_str++ == 0) {
			int fuse_arg = 0;
			fuse(res_strength, &fuse_arg, CHILLTIME, AFTER);
		    }
		    else lengthen(res_strength, CHILLTIME);
		}
	    }

	    /*
	     * Itching monsters reduce dexterity (temporarily).  This only
	     * affects the player for now because of its temporary nature.
	     */
	    if (on(*att, CANITCH) && !save(VS_POISON, def, 0)) {
		msg("The claws of %s scratch you.", prname(attname, FALSE));
		if (ISWEARING(R_SUSABILITY)) {
		    msg("The scratch has no effect");
		}
		else {
		    add_abil[A_DEXTERITY](-1);
		}
	    }


	    /*
	     * If a disease-carrying monster hits, there is a chance the
	     * defender will catch the disease.  This only applies to the
	     * player for now because of the temporary nature.
	     */
	    if (on(*att, CANDISEASE) &&
		(rnd(def->t_stats.s_const) < att->t_stats.s_lvl) &&
		off(*def, HASDISEASE)) {
		    if (ISWEARING(R_HEALTH)		||
			player.t_ctype == C_PALADIN	||
			player.t_ctype == C_MONK) {
			    msg("The wound heals quickly.");
		    }
		    else {
			turn_on(*def, HASDISEASE);
			fuse(cure_disease, NULL, roll(HEALTIME,SICKTIME), AFTER);
			msg(terse ? "You have been diseased."
			    : "You have contracted a disease!");
		    }
	    }

	    /*
	     * If a rusting monster hits, you lose armor.  This only applies to
	     * the player because monsters don't wear armor (for now).
	     */
	    if (on(*att, CANRUST)) { 
		if (cur_armor != NULL				&&
		    cur_armor->o_which != LEATHER		&&
		    cur_armor->o_which != STUDDED_LEATHER	&&
		    cur_armor->o_which != PADDED_ARMOR		&&
		    !(cur_armor->o_flags & ISPROT)		&&
		    cur_armor->o_ac < def->t_stats.s_arm+1) {
			msg(terse ? "Your armor weakens"
			    : "Your armor appears to be weaker now. Oh my!");
			cur_armor->o_ac++;
		}
		if (cur_misc[WEAR_BRACERS] != NULL		&&
		    cur_misc[WEAR_BRACERS]->o_ac > 0		&&
		    !(cur_misc[WEAR_BRACERS]->o_flags & ISPROT)) {
			cur_misc[WEAR_BRACERS]->o_ac--;
			if (cur_misc[WEAR_BRACERS]->o_ac == 0) {
			    register struct linked_list *item;

			    for (item=pack; item!=NULL; item=next(item)) {
				if (OBJPTR(item) == cur_misc[WEAR_BRACERS]) {
				    detach(pack, item);
				    o_discard(item);
				    break;
				}
			    }
			    msg ("Your bracers crumble and fall off!");
			    cur_misc[WEAR_BRACERS] = NULL;
			    inpack--;
			}
			else {
			    msg("Your bracers weaken!");
			}
		}
	    }

	    /*
	     * If can dissolve and hero has leather type armor.  This
	     * also only applies to the player for now because of the
	     * armor.
	     */
	    if (on(*att, CANDISSOLVE) && cur_armor != NULL &&
		(cur_armor->o_which == LEATHER		  ||
		 cur_armor->o_which == STUDDED_LEATHER	  ||
		 cur_armor->o_which == PADDED_ARMOR)	  &&
		!(cur_armor->o_flags & ISPROT) &&
		cur_armor->o_ac < def->t_stats.s_arm+1) {
		msg(terse ? "Your armor dissolves"
		    : "Your armor appears to dissolve. Oh my!");
		cur_armor->o_ac++;
	    }

	    /*
	     * If an infesting monster hits you, you get a parasite or rot.
	     * This will only affect the player until we figure out how to
	     * make it affect monsters.
	     */
	    if (on(*att, CANINFEST) &&
		rnd(def->t_stats.s_const) < att->t_stats.s_lvl) {
		if (ISWEARING(R_HEALTH)		||
		    player.t_ctype == C_PALADIN	||
		    player.t_ctype == C_MONK) {
			msg("The wound heals quickly.");
		}
		else {
		    turn_off(*att, CANINFEST);
		    msg(terse ? "You have been infested."
			: "You have contracted a parasitic infestation!");
		    infest_dam++;
		    turn_on(*def, HASINFEST);
		}
	    }

	    /*
	     * Does it take wisdom away?  This currently affects only
	     * the player because of its temporary nature.
	     */
	    if (on(*att, TAKEWISDOM)		&& 
		!save(VS_MAGIC, def, 0)	&&
		!ISWEARING(R_SUSABILITY)) {
			add_abil[A_WISDOM](-1);
	    }

	    /*
	     * Does it take intelligence away?  This currently affects
	     * only the player because of its temporary nature.
	     */
	    if (on(*att, TAKEINTEL)		&& 
		!save(VS_MAGIC, &player, 0)	&&
		!ISWEARING(R_SUSABILITY)) {
			add_abil[A_INTELLIGENCE](-1);
	    }

	    /*
	     * Cause fear by touching.  This currently affects only
	     * the player until we figure out how we want it to
	     * affect monsters.
	     */
	    if (on(*att, TOUCHFEAR)) {
		turn_off(*att, TOUCHFEAR);
		if (!ISWEARING(R_HEROISM)	&&
		    !save(VS_WAND, def, 0)	&&
		    !(on(*def, ISFLEE) && (def->t_dest == &att->t_pos))) {
			turn_on(*def, ISFLEE);
			def->t_dest = &att->t_pos;
			msg("%s's touch terrifies you.", prname(attname, TRUE));

			/* It is okay to turn tail */
			if (!def_player) def->t_oldpos = def->t_pos;
		}
	    }

	    /*
	     * Make the hero dance (as in otto's irresistable dance)
	     * This should be fairly easy to do to monsters, but
	     * we'll restrict it to players until we decide what to
	     * do about the temporary nature.
	     */
	    if (on(*att, CANDANCE) 		&& 
		!on(*def, ISDANCE)		&&
		def->t_action != A_FREEZE	&&
		!save(VS_MAGIC, def, -4)) {
		    turn_off(*att, CANDANCE);
		    turn_on(*def, ISDANCE);
		    msg("You begin to dance uncontrollably!");
		    fuse(undance, NULL, roll(2,4), AFTER);
	    }

	    /*
	     * Suffocating our hero.  Monsters don't get suffocated.
	     * That's too hard for now.
	     */
	    if (on(*att, CANSUFFOCATE)		&& 
		!ISWEARING(R_FREEDOM)		&& 
		rnd(100) < 25			&&
		(find_slot(suffocate) == 0)) {
		turn_on(*att, DIDSUFFOCATE);
		msg("%s is beginning to suffocate you.", prname(attname, TRUE));
		fuse(suffocate, NULL, roll(9,3), AFTER);
	    }

	    /*
	     * some creatures stops the poor guy from moving.
	     * How can we do this to a monster?
	     */
	    if (on(*att,CANHOLD) && off(*att,DIDHOLD) && !ISWEARING(R_FREEDOM)){
		turn_on(*def, ISHELD);
		turn_on(*att, DIDHOLD);
		hold_count++;
	    }

	    /*
	     * Sucker will suck blood and run.  This
	     * should be easy to have happen to a monster,
	     * but we have to decide how to handle the fleeing.
	     */
	    if (on(*att, CANDRAW)) {
		turn_off(*att, CANDRAW);
		turn_on(*att, ISFLEE);
		msg("%s sates itself with your blood.", prname(attname, TRUE));
		if ((def->t_stats.s_hpt -= 12) <= 0) return(att->t_index);

		/* It is okay to turn tail */
		att->t_oldpos = att->t_pos;
	    }

	    /*
	     * Bad smell will force a reduction in strength.
	     * This will happen only to the player because of
	     * the temporary nature.
	     */
	    if (on(*att, CANSMELL)) {
		turn_off(*att, CANSMELL);
		if (save(VS_MAGIC, def, 0) || ISWEARING(R_SUSABILITY))
		    msg("You smell an unpleasant odor.");
		else {
		    int odor_str = -(rnd(6)+1);
		    int fuse_arg2 = 0;
		    msg("You are overcome by a foul odor.");
		    if (lost_str == 0) {
			chg_str(odor_str);
			fuse(res_strength, &fuse_arg2, SMELLTIME, AFTER);
			lost_str -= odor_str;
		    }
		    else lengthen(res_strength, SMELLTIME);
		}
	    }

	    /*
	     * The monsters touch slows the defendant down.
	     */
	     if (on(*att, TOUCHSLOW)) {
		turn_off(*att, TOUCHSLOW);
		if (!save(VS_PARALYZATION, def, 0)) 
			add_slow();
	    }

	    /*
	     * Rotting only affects the player.
	     */
	    if (on(*att, CANROT)) {
		if (!ISWEARING(R_HEALTH)	&& 
		    player.t_ctype != C_PALADIN	&&
		    player.t_ctype != C_MONK	&&
		    !save(VS_POISON, def, 0)    && 
		    off(*def, DOROT)) {
		    turn_on(*def, DOROT);
		    msg("You feel your skin starting to rot away!");
		}
	    }

	    /*
	     * Monsters should be able to steal gold from anyone,
	     * but until this is rewritten, they will only steal
	     * from the player (tough break).
	     */
	    if (on(*att, STEALGOLD)) {
		/*
		 * steal some gold
		 */
		register long lastpurse;
		register struct linked_list *item;
		register struct object *obj;

		lastpurse = purse;
		purse -= GOLDCALC + GOLDCALC;
		if (!save(VS_MAGIC, def, att->t_stats.s_lvl/10)) {
		    if (on(*att, ISUNIQUE))
			purse -= GOLDCALC + GOLDCALC + GOLDCALC + GOLDCALC;
		    purse -= GOLDCALC + GOLDCALC + GOLDCALC + GOLDCALC;
		}
		if (purse < 0)
		    purse = 0;
		if (purse != lastpurse) {
		    msg("Your purse feels lighter");

		    /* Give the gold to the thief */
		    for (item=att->t_pack; item != NULL; item=next(item)) {
			obj = OBJPTR(item);
			if (obj->o_type == GOLD) {
			    obj->o_count += lastpurse - purse;
			    break;
			}
		    }

		    /* Did we do it? */
		    if (item == NULL) {	/* Then make some */
			item = new_item(sizeof *obj);
			obj = OBJPTR(item);
			obj->o_type = GOLD;
			obj->o_count = lastpurse - purse;
			obj->o_hplus = obj->o_dplus = 0;
			strncpy(obj->o_damage, "0d0", sizeof(obj->o_damage));
			strncpy(obj->o_hurldmg, "0d0", sizeof(obj->o_hurldmg));
			obj->o_ac = 11;
			obj->contents = NULL;
			obj->o_group = 0;
			obj->o_flags = 0;
			obj->o_mark[0] = '\0';
			obj->o_pos = att->t_pos;

			attach(att->t_pack, item);
		    }
		}

		turn_on(*att, ISFLEE);
		turn_on(*att, ISINVIS);

		/* It is okay to turn tail */
		att->t_oldpos = att->t_pos;
	    }
	}

	/*
	 * Stealing happens last since the monster disappears
	 * after the act.
	 */
	if (on(*att, STEALMAGIC)) {
	    register struct linked_list *list, *steal;
	    register struct object *obj;
	    register int nobj;

	    /*
	     * steal a magic item, look through the pack
	     * and pick out one we like.
	     */
	    steal = NULL;
	    for (nobj = 0, list = def->t_pack; list != NULL; list = next(list))
	    {
		obj = OBJPTR(list);
		if (!is_current(obj)	 &&
		    list != def->t_using &&
		    obj->o_type != RELIC &&
		    is_magic(obj)	 && 
		    rnd(++nobj) == 0)
			steal = list;
	    }
	    if (steal != NULL)
	    {
		register struct object *obj;
		struct linked_list *item;

		obj = OBJPTR(steal);
		if (on(*att, ISUNIQUE))
		    monsters[att->t_index].m_normal = TRUE;
		item = find_mons(att->t_pos.y, att->t_pos.x);

		killed(item, FALSE, FALSE, FALSE); /* Remove the attacker */

		if (obj->o_count > 1 && obj->o_group == 0) {
		    register int oc;

		    oc = --(obj->o_count);
		    obj->o_count = 1;
		    if (def_player)
			msg("%s stole %s!", prname(attname, TRUE),
					inv_name(obj, TRUE));
		    obj->o_count = oc;
		}
		else {
		    if (def_player) {
			msg("%s stole %s!", prname(attname, TRUE),
					inv_name(obj, TRUE));

			/* If this is a relic, clear its holding field */
			if (obj->o_type == RELIC)
			    cur_relic[obj->o_which] = 0;

			inpack--;
		    }

		    detach(def->t_pack, steal);
		    o_discard(steal);
		}

		updpack(FALSE, def);
	    }
	}
    }

    /* Didn't kill the defender */
    return(0);
}