Esempio n. 1
0
/**
 * Allow monsters on a frozen persistent level to recover
 */
void restore_monsters(void)
{
	int i;
	struct monster *mon;

	/* Get the number of turns that have passed */
	int num_turns = turn - cave->turn;

	/* Process the monsters (backwards) */
	for (i = cave_monster_max(cave) - 1; i >= 1; i--) {
		int status, status_red;

		/* Access the monster */
		mon = cave_monster(cave, i);

		/* Regenerate */
		regen_monster(mon, num_turns / 100);

		/* Handle timed effects */
		status_red = num_turns * turn_energy(mon->mspeed) / z_info->move_energy;
		if (status_red > 0) {
			for (status = 0; status < MON_TMD_MAX; status++) {
				if (mon->m_timed[status]) {
					mon_dec_timed(mon, status, status_red, 0, false);
				}
			}
		}
	}
}
Esempio n. 2
0
static byte adj_energy(struct monster_race *race)
{
	unsigned i = race->speed + (rsf_has(race->spell_flags,RSF_HASTE) ? 5 : 0);

	/* Fastest monster in the game is currently +30, but bounds check anyway */
	return turn_energy(MIN(i, N_ELEMENTS(extract_energy) - 1));
}
Esempio n. 3
0
/**
 * Describes a food item
 */
static bool describe_food(textblock *tb, const struct object *obj,
		bool subjective)
{
	int nourishment = obj_known_food(obj);

	if (nourishment) {
		/* Sometimes adjust for player speed */
		int multiplier = turn_energy(player->state.speed);
		if (!subjective) multiplier = 10;

		if (nourishment == OBJ_KNOWN_PRESENT) {
			textblock_append(tb, "Provides some nourishment.\n");
		} else {
			textblock_append(tb, "Nourishes for around ");
			textblock_append_c(tb, COLOUR_L_GREEN, "%d", nourishment *
				multiplier / 10);
			textblock_append(tb, " turns.\n");
		}

		return TRUE;
	}

	return FALSE;
}
Esempio n. 4
0
/**
 * Process all the "live" monsters, once per game turn.
 *
 * During each game turn, we scan through the list of all the "live" monsters,
 * (backwards, so we can excise any "freshly dead" monsters), energizing each
 * monster, and allowing fully energized monsters to move, attack, pass, etc.
 *
 * This function and its children are responsible for a considerable fraction
 * of the processor time in normal situations, greater if the character is
 * resting.
 */
void process_monsters(struct chunk *c, int minimum_energy)
{
	int i;
	int mspeed;

	/* Only process some things every so often */
	bool regen = false;

	/* Regenerate hitpoints and mana every 100 game turns */
	if (turn % 100 == 0)
		regen = true;

	/* Process the monsters (backwards) */
	for (i = cave_monster_max(c) - 1; i >= 1; i--) {
		struct monster *mon;
		bool moving;

		/* Handle "leaving" */
		if (player->is_dead || player->upkeep->generate_level) break;

		/* Get a 'live' monster */
		mon = cave_monster(c, i);
		if (!mon->race) continue;

		/* Ignore monsters that have already been handled */
		if (mflag_has(mon->mflag, MFLAG_HANDLED))
			continue;

		/* Not enough energy to move yet */
		if (mon->energy < minimum_energy) continue;

		/* Does this monster have enough energy to move? */
		moving = mon->energy >= z_info->move_energy ? true : false;

		/* Prevent reprocessing */
		mflag_on(mon->mflag, MFLAG_HANDLED);

		/* Handle monster regeneration if requested */
		if (regen)
			regen_monster(mon);

		/* Calculate the net speed */
		mspeed = mon->mspeed;
		if (mon->m_timed[MON_TMD_FAST])
			mspeed += 10;
		if (mon->m_timed[MON_TMD_SLOW])
			mspeed -= 10;

		/* Give this monster some energy */
		mon->energy += turn_energy(mspeed);

		/* End the turn of monsters without enough energy to move */
		if (!moving)
			continue;

		/* Use up "some" energy */
		mon->energy -= z_info->move_energy;

		/* Mimics lie in wait */
		if (is_mimicking(mon)) continue;

		/* Check if the monster is active */
		if (monster_check_active(c, mon)) {
			/* Process timed effects - skip turn if necessary */
			if (process_monster_timed(c, mon))
				continue;

			/* Set this monster to be the current actor */
			c->mon_current = i;

			/* Process the monster */
			process_monster(c, mon);

			/* Monster is no longer current */
			c->mon_current = -1;
		}
	}

	/* Update monster visibility after this */
	/* XXX This may not be necessary */
	player->upkeep->update |= PU_MONSTERS;
}
Esempio n. 5
0
/**
 * The main game loop.
 *
 * This function will run until the player needs to enter a command, or closes
 * the game, or the character dies.
 */
void run_game_loop(void)
{
	/* Tidy up after the player's command */
	process_player_cleanup();

	/* Keep processing the player until they use some energy or
	 * another command is needed */
	while (player->upkeep->playing) {
		process_player();
		if (player->upkeep->energy_use)
			break;
		else
			return;
	}

	/* The player may still have enough energy to move, so we run another
	 * player turn before processing the rest of the world */
	while (player->energy >= z_info->move_energy) {
		/* Do any necessary animations */
		event_signal(EVENT_ANIMATE);
		
		/* Process monster with even more energy first */
		process_monsters(cave, player->energy + 1);
		if (player->is_dead || !player->upkeep->playing ||
			player->upkeep->generate_level)
			break;

		/* Process the player until they use some energy */
		while (player->upkeep->playing) {
			process_player();
			if (player->upkeep->energy_use)
				break;
			else
				return;
		}
	}

	/* Now that the player's turn is fully complete, we run the main loop 
	 * until player input is needed again */
	while (true) {
		notice_stuff(player);
		handle_stuff(player);
		event_signal(EVENT_REFRESH);

		/* Process the rest of the world, give the player energy and 
		 * increment the turn counter unless we need to stop playing or
		 * generate a new level */
		if (player->is_dead || !player->upkeep->playing)
			return;
		else if (!player->upkeep->generate_level) {
			/* Process the rest of the monsters */
			process_monsters(cave, 0);

			/* Mark all monsters as ready to act when they have the energy */
			reset_monsters();

			/* Refresh */
			notice_stuff(player);
			handle_stuff(player);
			event_signal(EVENT_REFRESH);
			if (player->is_dead || !player->upkeep->playing)
				return;

			/* Process the world every ten turns */
			if (!(turn % 10) && !player->upkeep->generate_level) {
				process_world(cave);

				/* Refresh */
				notice_stuff(player);
				handle_stuff(player);
				event_signal(EVENT_REFRESH);
				if (player->is_dead || !player->upkeep->playing)
					return;
			}

			/* Give the player some energy */
			player->energy += turn_energy(player->state.speed);

			/* Count game turns */
			turn++;
		}

		/* Make a new level if requested */
		if (player->upkeep->generate_level) {
			if (character_dungeon)
				on_leave_level();

			cave_generate(&cave, player);
			on_new_level();

			player->upkeep->generate_level = false;
		}

		/* If the player has enough energy to move they now do so, after
		 * any monsters with more energy take their turns */
		while (player->energy >= z_info->move_energy) {
			/* Do any necessary animations */
			event_signal(EVENT_ANIMATE);

			/* Process monster with even more energy first */
			process_monsters(cave, player->energy + 1);
			if (player->is_dead || !player->upkeep->playing ||
				player->upkeep->generate_level)
				break;

			/* Process the player until they use some energy */
			while (player->upkeep->playing) {
				process_player();
				if (player->upkeep->energy_use)
					break;
				else
					return;
			}
		}
	}
}
Esempio n. 6
0
/**
 * Handle things that need updating once every 10 game turns
 */
void process_world(struct chunk *c)
{
	int i, y, x;

	/* Compact the monster list if we're approaching the limit */
	if (cave_monster_count(cave) + 32 > z_info->level_monster_max)
		compact_monsters(64);

	/* Too many holes in the monster list - compress */
	if (cave_monster_count(cave) + 32 < cave_monster_max(cave))
		compact_monsters(0);

	/*** Check the Time ***/

	/* Play an ambient sound at regular intervals. */
	if (!(turn % ((10L * z_info->day_length) / 4)))
		play_ambient_sound();

	/*** Handle stores and sunshine ***/

	if (!player->depth) {
		/* Daybreak/Nighfall in town */
		if (!(turn % ((10L * z_info->day_length) / 2))) {
			bool dawn;

			/* Check for dawn */
			dawn = (!(turn % (10L * z_info->day_length)));

			/* Day breaks */
			if (dawn)
				msg("The sun has risen.");

			/* Night falls */
			else
				msg("The sun has fallen.");

			/* Illuminate */
			cave_illuminate(c, dawn);
		}
	} else {
		/* Update the stores once a day (while in the dungeon).
		   The changes are not actually made until return to town,
		   to avoid giving details away in the knowledge menu. */
		if (!(turn % (10L * z_info->store_turns))) daycount++;
	}


	/* Check for creature generation */
	if (one_in_(z_info->alloc_monster_chance))
		(void)pick_and_place_distant_monster(cave, player,
											 z_info->max_sight + 5, true,
											 player->depth);

	/*** Damage over Time ***/

	/* Take damage from poison */
	if (player->timed[TMD_POISONED])
		take_hit(player, 1, "poison");

	/* Take damage from cuts */
	if (player->timed[TMD_CUT]) {
		/* Mortal wound or Deep Gash */
		if (player->timed[TMD_CUT] > TMD_CUT_SEVERE)
			i = 3;

		/* Severe cut */
		else if (player->timed[TMD_CUT] > TMD_CUT_NASTY)
			i = 2;

		/* Other cuts */
		else
			i = 1;

		/* Take damage */
		take_hit(player, i, "a fatal wound");
	}


	/*** Check the Food, and Regenerate ***/

	/* Digest normally */
	if (!(turn % 100)) {
		/* Basic digestion rate based on speed */
		i = turn_energy(player->state.speed) * 2;

		/* Regeneration takes more food */
		if (player_of_has(player, OF_REGEN)) i += 30;

		/* Slow digestion takes less food */
		if (player_of_has(player, OF_SLOW_DIGEST)) i /= 5;

		/* Minimal digestion */
		if (i < 1) i = 1;

		/* Digest some food */
		player_set_food(player, player->food - i);
	}

	/* Getting Faint */
	if (player->food < PY_FOOD_FAINT) {
		/* Faint occasionally */
		if (!player->timed[TMD_PARALYZED] && one_in_(10)) {
			/* Message */
			msg("You faint from the lack of food.");
			disturb(player, 1);

			/* Faint (bypass free action) */
			(void)player_inc_timed(player, TMD_PARALYZED, 1 + randint0(5),
								   true, false);
		}
	}

	/* Starve to death (slowly) */
	if (player->food < PY_FOOD_STARVE) {
		/* Calculate damage */
		i = (PY_FOOD_STARVE - player->food) / 10;

		/* Take damage */
		take_hit(player, i, "starvation");
	}

	/* Regenerate Hit Points if needed */
	if (player->chp < player->mhp)
		player_regen_hp(player);

	/* Regenerate mana if needed */
	if (player->csp < player->msp)
		player_regen_mana(player);

	/* Timeout various things */
	decrease_timeouts();

	/* Process light */
	player_update_light(player);


	/*** Process Inventory ***/

	/* Handle experience draining */
	if (player_of_has(player, OF_DRAIN_EXP)) {
		if ((player->exp > 0) && one_in_(10)) {
			s32b d = damroll(10, 6) +
				(player->exp / 100) * z_info->life_drain_percent;
			player_exp_lose(player, d / 10, false);
		}

		equip_learn_flag(player, OF_DRAIN_EXP);
	}

	/* Recharge activatable objects and rods */
	recharge_objects();

	/* Notice things after time */
	if (!(turn % 100))
		equip_learn_after_time(player);

	/* Decrease trap timeouts */
	for (y = 0; y < cave->height; y++) {
		for (x = 0; x < cave->width; x++) {
			struct trap *trap = cave->squares[y][x].trap;
			while (trap) {
				if (trap->timeout) {
					trap->timeout--;
					if (!trap->timeout)
						square_light_spot(cave, y, x);
				}
				trap = trap->next;
			}
		}
	}


	/*** Involuntary Movement ***/

	/* Delayed Word-of-Recall */
	if (player->word_recall) {
		/* Count down towards recall */
		player->word_recall--;

		/* Activate the recall */
		if (!player->word_recall) {
			/* Disturbing! */
			disturb(player, 0);

			/* Determine the level */
			if (player->depth) {
				msgt(MSG_TPLEVEL, "You feel yourself yanked upwards!");
				dungeon_change_level(player, 0);
			} else {
				msgt(MSG_TPLEVEL, "You feel yourself yanked downwards!");
                
                /* Force descent to a lower level if allowed */
                if (OPT(player, birth_force_descend) &&
					player->max_depth < z_info->max_depth - 1 &&
					!is_quest(player->max_depth)) {
                    player->max_depth = dungeon_get_next_level(player->max_depth, 1);
                }

				/* New depth - back to max depth or 1, whichever is deeper */
				dungeon_change_level(player, player->max_depth < 1 ? 1: player->max_depth);
			}
		}
	}

	/* Delayed Deep Descent */
	if (player->deep_descent) {
		/* Count down towards recall */
		player->deep_descent--;

		/* Activate the recall */
		if (player->deep_descent == 0) {
			int target_increment;
			int target_depth = player->max_depth;

			/* Calculate target depth */
			target_increment = (4 / z_info->stair_skip) + 1;
			target_depth = dungeon_get_next_level(player->max_depth, target_increment);

			disturb(player, 0);

			/* Determine the level */
			if (target_depth > player->depth) {
				msgt(MSG_TPLEVEL, "The floor opens beneath you!");
				dungeon_change_level(player, target_depth);
			} else {
				/* Otherwise do something disastrous */
				msgt(MSG_TPLEVEL, "You are thrown back in an explosion!");
				effect_simple(EF_DESTRUCTION, "0", 0, 5, 0, NULL);
			}		
		}
	}
}
Esempio n. 7
0
/**
 * Describe an object's effect, if any.
 */
static bool describe_effect(textblock *tb, const struct object *obj,
		bool only_artifacts, bool subjective)
{
	char desc[200];
	struct effect *effect = NULL;
	bool aimed = false;
	int min_time, max_time, failure_chance;

	/* Sometimes we only print artifact activation info */
	if (only_artifacts && !obj->artifact)
		return false;

	if (obj_known_effect(obj, &effect, &aimed, &min_time, &max_time,
							 &failure_chance) == false)
		return false;

	/* Effect not known, mouth platitudes */
	if (!effect && object_effect(obj)) {
		if (aimed)
			textblock_append(tb, "It can be aimed.\n");
		else if (tval_is_edible(obj))
			textblock_append(tb, "It can be eaten.\n");
		else if (tval_is_potion(obj))
			textblock_append(tb, "It can be drunk.\n");
		else if (tval_is_scroll(obj))
			textblock_append(tb, "It can be read.\n");
		else textblock_append(tb, "It can be activated.\n");

		return true;
	}

	/* Activations get a special message */
	if (obj->activation && obj->activation->desc) {
		textblock_append(tb, "When activated, it ");
		textblock_append(tb, obj->activation->desc);
	} else {
		int random_choices = 0;

		/* Get descriptions for all the effects */
		effect = object_effect(obj);
		if (!effect_desc(effect)) return false;

		if (aimed)
			textblock_append(tb, "When aimed, it ");
		else if (tval_is_edible(obj))
			textblock_append(tb, "When eaten, it ");
		else if (tval_is_potion(obj))
			textblock_append(tb, "When quaffed, it ");
		else if (tval_is_scroll(obj))
			textblock_append(tb, "When read, it ");
		else
			textblock_append(tb, "When activated, it ");

		/* Print a colourised description */
		while (effect) {
			char *next_char = desc;
			int roll = 0;
			random_value value = { 0, 0, 0, 0 };
			char dice_string[20];
			int boost, level = obj->kind->level;

			/* Get the level */
			if (obj->artifact)
				level = obj->artifact->level;
			else if (obj->ego)
				level = obj->ego->level;

			/* Get the boost */
			boost = MAX(player->state.skills[SKILL_DEVICE] - level, 0);			

			if (effect->dice != NULL)
				roll = dice_roll(effect->dice, &value);

			/* Deal with special random effect */
			if (effect->index == EF_RANDOM)
				random_choices = roll + 1;

			/* Get the possible dice strings */
			if (value.dice && value.base)
				strnfmt(dice_string, sizeof(dice_string), "%d+%dd%d",
						value.base, value.dice, value.sides);
			else if (value.dice)
				strnfmt(dice_string, sizeof(dice_string), "%dd%d",
						value.dice, value.sides);
			else
				strnfmt(dice_string, sizeof(dice_string), "%d", value.base);

			/* Check all the possible types of description format */
			switch (base_descs[effect->index].efinfo_flag) {
				/* Healing sometimes has a minimum percentage */
			case EFINFO_HEAL: {
				char min_string[50];
				if (value.m_bonus)
					strnfmt(min_string, sizeof(min_string),
							" (or %d%%, whichever is greater)", value.m_bonus);
				else
					strnfmt(min_string, sizeof(min_string), "");
				strnfmt(desc, sizeof(desc), effect_desc(effect), dice_string,
						min_string);
				break;
			}

			/* Nourishment is just a flat amount */
			case EFINFO_CONST: {
				strnfmt(desc, sizeof(desc), effect_desc(effect), value.base/2);
				break;
			}
			case EFINFO_CURE: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
							timed_idx_to_desc(effect->params[0]));
				break;
			}
			case EFINFO_TIMED: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
							timed_idx_to_desc(effect->params[0]), dice_string);
				break;
			}
			case EFINFO_STAT: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
							mod_flags[effect->params[0]].name);
				break;
			}
			case EFINFO_SEEN: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						gf_desc(effect->params[0]));
				break;
			}
			case EFINFO_SUMM: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						summon_desc(effect->params[0]));
				break;
			}

			/* Only currently used for the player, but can handle monsters */
			case EFINFO_TELE: {
				if (effect->params[0])
					strnfmt(desc, sizeof(desc), effect_desc(effect),
							"a monster", value.base);
				else
					strnfmt(desc, sizeof(desc), effect_desc(effect), "you",
							value.base);
				break;
			}
			case EFINFO_QUAKE: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						effect->params[1]);
				break;
			}
			case EFINFO_LIGHT: {
				strnfmt(desc, sizeof(desc), effect_desc(effect), dice_string,
						effect->params[1]);
				break;
			}

			/* Object generated balls are elemental */
			case EFINFO_BALL: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						elements[effect->params[0]].name, effect->params[1],
						dice_string);
				if (boost)
					my_strcat(desc, format(", which your device skill increases by %d per cent", boost),
							  sizeof(desc));
				break;
			}

			/* Bolts that inflict status */
			case EFINFO_BOLT: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						gf_desc(effect->params[0]));
				break;
			}
			/* Bolts and beams that damage */
			case EFINFO_BOLTD: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						gf_desc(effect->params[0]), dice_string);
				if (boost)
					my_strcat(desc, format(", which your device skill increases by %d per cent", boost),
							  sizeof(desc));
				break;
			}
			case EFINFO_TOUCH: {
				strnfmt(desc, sizeof(desc), effect_desc(effect),
						gf_desc(effect->params[0]));
				break;
			}
			case EFINFO_NONE: {
				strnfmt(desc, sizeof(desc), effect_desc(effect));
				break;
			}
			default: {
				msg("Bad effect description passed to describe_effect(). Please report this bug.");
				return false;
			}
			}

			do {
				if (isdigit((unsigned char) *next_char))
					textblock_append_c(tb, COLOUR_L_GREEN, "%c", *next_char);
				else
					textblock_append(tb, "%c", *next_char);
			} while (*next_char++);

			/* Random choices need special treatment - note that this code
			 * assumes that RANDOM and the random choices will be the last
			 * effect in the object/activation description */
			if (random_choices >= 1) {
				if (effect->index == EF_RANDOM)
					;
				else if (random_choices > 2)
					textblock_append(tb, ", ");
				else if (random_choices == 2)
					textblock_append(tb, " or ");
				random_choices--;
			} else if (effect->next) {
				if (effect->next->next && (effect->next->index != EF_RANDOM))
					textblock_append(tb, ", ");
				else
					textblock_append(tb, " and ");
			}
			effect = effect->next;
		}
	}

	textblock_append(tb, ".\n");

	if (min_time || max_time) {
		/* Sometimes adjust for player speed */
		int multiplier = turn_energy(player->state.speed);
		if (!subjective) multiplier = 10;

		textblock_append(tb, "Takes ");

		/* Correct for player speed */
		min_time = (min_time * multiplier) / 10;
		max_time = (max_time * multiplier) / 10;

		textblock_append_c(tb, COLOUR_L_GREEN, "%d", min_time);

		if (min_time != max_time) {
			textblock_append(tb, " to ");
			textblock_append_c(tb, COLOUR_L_GREEN, "%d", max_time);
		}

		textblock_append(tb, " turns to recharge");
		if (subjective && player->state.speed != 110)
			textblock_append(tb, " at your current speed");

		textblock_append(tb, ".\n");
	}

	if (failure_chance > 0) {
		textblock_append(tb, "Your chance of success is %d.%d%%\n", 
			(1000 - failure_chance) / 10, (1000 - failure_chance) % 10);
	}

	return true;
}
Esempio n. 8
0
/**
 * Attempts to place a monster of the given race at the given location.
 *
 * If `sleep` is true, the monster is placed with its default sleep value,
 * which is given in monster.txt.
 *
 * `origin` is the item origin to use for any monster drops (e.g. ORIGIN_DROP,
 * ORIGIN_DROP_PIT, etc.)
 *
 * To give the player a sporting chance, some especially dangerous
 * monsters are marked as "FORCE_SLEEP" in monster.txt, which will
 * cause them to be placed with low energy. This helps ensure that
 * if such a monster suddenly appears in line-of-sight (due to a
 * summon, for instance), the player gets a chance to move before
 * they do.
 *
 * This routine refuses to place out-of-depth "FORCE_DEPTH" monsters.
 *
 * This is the only function which may place a monster in the dungeon,
 * except for the savefile loading code, which calls place_monster()
 * directly.
 */
static bool place_new_monster_one(struct chunk *c, int y, int x,
								  struct monster_race *race, bool sleep,
								  byte origin)
{
	int i;

	struct monster *mon;
	struct monster monster_body;

	assert(square_in_bounds(c, y, x));
	assert(race && race->name);

	/* Not where monsters already are */
	if (square_monster(c, y, x))
		return false;

	/* Not where the player already is */
	if ((player->py == y) && (player->px == x))
		return false;

	/* Prevent monsters from being placed where they cannot walk, but allow other feature types */
	if (!square_is_monster_walkable(c, y, x))
		return false;

	/* No creation on glyph of warding */
	if (square_iswarded(c, y, x)) return false;

	/* "unique" monsters must be "unique" */
	if (rf_has(race->flags, RF_UNIQUE) && race->cur_num >= race->max_num)
		return (false);

	/* Depth monsters may NOT be created out of depth */
	if (rf_has(race->flags, RF_FORCE_DEPTH) && player->depth < race->level)
		return (false);

	/* Add to level feeling, note uniques for cheaters */
	c->mon_rating += race->level * race->level;

	/* Check out-of-depth-ness */
	if (race->level > c->depth) {
		if (rf_has(race->flags, RF_UNIQUE)) { /* OOD unique */
			if (OPT(player, cheat_hear))
				msg("Deep unique (%s).", race->name);
		} else { /* Normal monsters but OOD */
			if (OPT(player, cheat_hear))
				msg("Deep monster (%s).", race->name);
		}
		/* Boost rating by power per 10 levels OOD */
		c->mon_rating += (race->level - c->depth) * race->level * race->level;
	} else if (rf_has(race->flags, RF_UNIQUE) && OPT(player, cheat_hear))
		msg("Unique (%s).", race->name);

	/* Get local monster */
	mon = &monster_body;

	/* Clean out the monster */
	memset(mon, 0, sizeof(struct monster));

	/* Save the race */
	mon->race = race;

	/* Enforce sleeping if needed */
	if (sleep && race->sleep) {
		int val = race->sleep;
		mon->m_timed[MON_TMD_SLEEP] = ((val * 2) + randint1(val * 10));
	}

	/* Uniques get a fixed amount of HP */
	if (rf_has(race->flags, RF_UNIQUE))
		mon->maxhp = race->avg_hp;
	else {
		mon->maxhp = mon_hp(race, RANDOMISE);
		mon->maxhp = MAX(mon->maxhp, 1);
	}

	/* And start out fully healthy */
	mon->hp = mon->maxhp;

	/* Extract the monster base speed */
	mon->mspeed = race->speed;

	/* Hack -- small racial variety */
	if (!rf_has(race->flags, RF_UNIQUE)) {
		/* Allow some small variation per monster */
		i = turn_energy(race->speed) / 10;
		if (i) mon->mspeed += rand_spread(0, i);
	}

	/* Give a random starting energy */
	mon->energy = (byte)randint0(50);

	/* Force monster to wait for player */
	if (rf_has(race->flags, RF_FORCE_SLEEP))
		mflag_on(mon->mflag, MFLAG_NICE);

	/* Radiate light? */
	if (rf_has(race->flags, RF_HAS_LIGHT))
		player->upkeep->update |= PU_UPDATE_VIEW;

	/* Is this obviously a monster? (Mimics etc. aren't) */
	if (rf_has(race->flags, RF_UNAWARE))
		mflag_on(mon->mflag, MFLAG_CAMOUFLAGE);
	else
		mflag_off(mon->mflag, MFLAG_CAMOUFLAGE);

	/* Set the color if necessary */
	if (rf_has(race->flags, RF_ATTR_RAND))
		mon->attr = randint1(BASIC_COLORS - 1);

	/* Place the monster in the dungeon */
	if (!place_monster(c, y, x, mon, origin))
		return (false);

	/* Success */
	return (true);
}
Esempio n. 9
0
/**
 * Describe an object's effect, if any.
 */
static bool describe_effect(textblock *tb, const struct object *obj,
		bool only_artifacts, bool subjective)
{
	char desc[200];
	struct effect *e;

	int effect = 0;
	bool aimed = FALSE;
	int min_time, max_time, failure_chance;

	/* Sometimes we only print artifact activation info */
	if (only_artifacts && !obj->artifact)
		return FALSE;

	if (obj_known_effect(obj, &effect, &aimed, &min_time, &max_time, &failure_chance) == FALSE)
		return FALSE;

	/* We don't know much */
	if (effect == OBJ_KNOWN_PRESENT) {
		if (aimed)
			textblock_append(tb, "It can be aimed.\n");
		else if (tval_is_edible(obj))
			textblock_append(tb, "It can be eaten.\n");
		else if (tval_is_potion(obj))
			textblock_append(tb, "It can be drunk.\n");
		else if (tval_is_scroll(obj))
			textblock_append(tb, "It can be read.\n");
		else textblock_append(tb, "It can be activated.\n");

		return TRUE;
	}

	/* Obtain the description */
	e = obj->effect;
	if (!effect_desc(e)) return FALSE;

	if (aimed)
		textblock_append(tb, "When aimed, it ");
	else if (tval_is_edible(obj))
		textblock_append(tb, "When eaten, it ");
	else if (tval_is_potion(obj))
		textblock_append(tb, "When quaffed, it ");
	else if (tval_is_scroll(obj))
	    textblock_append(tb, "When read, it ");
	else
	    textblock_append(tb, "When activated, it ");

	/* Print a colourised description */
	while (e) {
		char *next_char = desc;
		random_value value = { 0, 0, 0, 0 };
		char dice_string[20];
		if (e->dice != NULL)
			(void) dice_roll(e->dice, &value);

		/* Get the possible dice strings */
		if (value.dice && value.base)
			strnfmt(dice_string, sizeof(dice_string), "%d+%dd%d",
					value.base, value.dice, value.sides);
		else if (value.dice)
			strnfmt(dice_string, sizeof(dice_string), "%dd%d",
					value.dice, value.sides);
		else
			strnfmt(dice_string, sizeof(dice_string), "%d", value.base);

		/* Check all the possible types of description format */
		switch (base_descs[e->index].efinfo_flag) {
			/* Healing sometimes has a minimum percentage */
			case EFINFO_HEAL: {
				char min_string[50];
				if (value.m_bonus)
					strnfmt(min_string, sizeof(min_string),
							" (or %d%%, whichever is greater)", value.m_bonus);
				else
					strnfmt(min_string, sizeof(min_string), "");
				strnfmt(desc, sizeof(desc), effect_desc(e), dice_string,
						min_string);
				break;
			}

			/* Nourishment is just a flat amount */
			case EFINFO_FEED: {
				strnfmt(desc, sizeof(desc), effect_desc(e), value.base);
				break;
			}
			case EFINFO_CURE: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
							timed_idx_to_desc(e->params[0]));
				break;
			}
			case EFINFO_TIMED: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
							timed_idx_to_desc(e->params[0]), dice_string);
				break;
			}
			case EFINFO_STAT: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
							mod_flags[e->params[0]].name);
				break;
			}
			case EFINFO_SEEN: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						gf_desc(e->params[0]));
				break;
			}
			case EFINFO_SUMM: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						summon_desc(e->params[0]));
				break;
			}

			/* Only currently used for the player, but can handle monsters */
			case EFINFO_TELE: {
				if (e->params[0])
					strnfmt(desc, sizeof(desc), effect_desc(e), "a monster", 
						value.base);
				else
					strnfmt(desc, sizeof(desc), effect_desc(e), "you",
							value.base);
				break;
			}
			case EFINFO_QUAKE: {
				strnfmt(desc, sizeof(desc), effect_desc(e), e->params[1]);
				break;
			}
			case EFINFO_LIGHT: {
				strnfmt(desc, sizeof(desc), effect_desc(e), dice_string,
						e->params[1]);
				break;
			}

			/* Object generated balls are elemental */
			case EFINFO_BALL: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						elements[e->params[0]].name, e->params[1], dice_string);
				break;
			}

			/* Bolts that inflict status */
			case EFINFO_BOLT: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						gf_desc(e->params[0]));
				break;
			}
			/* Bolts and beams that damage */
			case EFINFO_BOLTD: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						gf_desc(e->params[0]), dice_string);
				break;
			}
			case EFINFO_TOUCH: {
				strnfmt(desc, sizeof(desc), effect_desc(e),
						gf_desc(e->params[0]));
				break;
			}
			default:strnfmt(desc, sizeof(desc), effect_desc(e)); break;
		}
		do {
			if (isdigit((unsigned char) *next_char))
				textblock_append_c(tb, COLOUR_L_GREEN, "%c", *next_char);
			else
				textblock_append(tb, "%c", *next_char);
		} while (*next_char++);
		if (e->next) {
			if (e->next->next)
				textblock_append(tb, ", ");
			else
				textblock_append(tb, " and ");
		}
		e = e->next;
	}

	textblock_append(tb, ".\n");

	if (min_time || max_time) {
		/* Sometimes adjust for player speed */
		int multiplier = turn_energy(player->state.speed);
		if (!subjective) multiplier = 10;

		textblock_append(tb, "Takes ");

		/* Correct for player speed */
		min_time *= multiplier / 10;
		max_time *= multiplier / 10;

		textblock_append_c(tb, COLOUR_L_GREEN, "%d", min_time);

		if (min_time != max_time) {
			textblock_append(tb, " to ");
			textblock_append_c(tb, COLOUR_L_GREEN, "%d", max_time);
		}

		textblock_append(tb, " turns to recharge");
		if (subjective && player->state.speed != 110)
			textblock_append(tb, " at your current speed");

		textblock_append(tb, ".\n");
	}

	if (failure_chance > 0) {
		textblock_append(tb, "Your chance of success is %d.%d%%\n", 
			(1000 - failure_chance) / 10, (1000 - failure_chance) % 10);
	}

	return TRUE;
}