static void project_player_handler_NEXUS(project_player_handler_context_t *context) { struct monster *mon = NULL; if (context->origin.what == SRC_MONSTER) { mon = cave_monster(cave, context->origin.which.monster); } if (player_resists(player, ELEM_NEXUS)) { msg("You resist the effect!"); return; } /* Stat swap */ if (randint0(100) < player->state.skills[SKILL_SAVE]) { msg("You avoid the effect!"); } else { project_player_swap_stats(); } if (one_in_(3) && mon) { /* Teleport to */ effect_simple(EF_TELEPORT_TO, context->origin, "0", mon->fy, mon->fx, 0, NULL); } else if (one_in_(4)) { /* Teleport level */ if (randint0(100) < player->state.skills[SKILL_SAVE]) { msg("You avoid the effect!"); return; } effect_simple(EF_TELEPORT_LEVEL, context->origin, "0", 0, 0, 0, NULL); } else { /* Teleport */ const char *miles = "200"; effect_simple(EF_TELEPORT, context->origin, miles, 0, 1, 0, NULL); } }
static void adjust_level(struct player *p, bool verbose) { if (p->exp < 0) p->exp = 0; if (p->max_exp < 0) p->max_exp = 0; if (p->exp > PY_MAX_EXP) p->exp = PY_MAX_EXP; if (p->max_exp > PY_MAX_EXP) p->max_exp = PY_MAX_EXP; if (p->exp > p->max_exp) p->max_exp = p->exp; p->upkeep->redraw |= PR_EXP; handle_stuff(p); while ((p->lev > 1) && (p->exp < (player_exp[p->lev-2] * p->expfact / 100L))) p->lev--; while ((p->lev < PY_MAX_LEVEL) && (p->exp >= (player_exp[p->lev-1] * p->expfact / 100L))) { char buf[80]; p->lev++; /* Save the highest level */ if (p->lev > p->max_lev) p->max_lev = p->lev; if (verbose) { /* Log level updates */ strnfmt(buf, sizeof(buf), "Reached level %d", p->lev); history_add(p, buf, HIST_GAIN_LEVEL); /* Message */ msgt(MSG_LEVEL, "Welcome to level %d.", p->lev); } effect_simple(EF_RESTORE_STAT, "0", STAT_STR, 1, 0, NULL); effect_simple(EF_RESTORE_STAT, "0", STAT_INT, 1, 0, NULL); effect_simple(EF_RESTORE_STAT, "0", STAT_WIS, 1, 0, NULL); effect_simple(EF_RESTORE_STAT, "0", STAT_DEX, 1, 0, NULL); effect_simple(EF_RESTORE_STAT, "0", STAT_CON, 1, 0, NULL); } while ((p->max_lev < PY_MAX_LEVEL) && (p->max_exp >= (player_exp[p->max_lev-1] * p->expfact / 100L))) p->max_lev++; p->upkeep->update |= (PU_BONUS | PU_HP | PU_SPELLS); p->upkeep->redraw |= (PR_LEV | PR_TITLE | PR_EXP | PR_STATS); handle_stuff(p); }
/** * Melee effect handler: Drain all of the player's stats. */ static void melee_effect_handler_LOSE_ALL(melee_effect_handler_context_t *context) { /* Take damage */ take_hit(context->p, context->damage, context->ddesc); /* Damage (stats) */ effect_simple(EF_DRAIN_STAT, "0", STAT_STR, 0, 0, &context->obvious); effect_simple(EF_DRAIN_STAT, "0", STAT_DEX, 0, 0, &context->obvious); effect_simple(EF_DRAIN_STAT, "0", STAT_CON, 0, 0, &context->obvious); effect_simple(EF_DRAIN_STAT, "0", STAT_INT, 0, 0, &context->obvious); effect_simple(EF_DRAIN_STAT, "0", STAT_WIS, 0, 0, &context->obvious); }
/** * Chests have traps too. * * Exploding chest destroys contents (and traps). * Note that the chest itself is never destroyed. */ static void chest_trap(int y, int x, struct object *obj) { int trap; /* Ignore disarmed chests */ if (obj->pval <= 0) return; /* Obtain the traps */ trap = chest_traps[obj->pval]; /* Lose strength */ if (trap & (CHEST_LOSE_STR)) { msg("A small needle has pricked you!"); take_hit(player, damroll(1, 4), "a poison needle"); effect_simple(EF_DRAIN_STAT, "0", STAT_STR, 0, 0, NULL); } /* Lose constitution */ if (trap & (CHEST_LOSE_CON)) { msg("A small needle has pricked you!"); take_hit(player, damroll(1, 4), "a poison needle"); effect_simple(EF_DRAIN_STAT, "0", STAT_CON, 0, 0, NULL); } /* Poison */ if (trap & (CHEST_POISON)) { msg("A puff of green gas surrounds you!"); effect_simple(EF_TIMED_INC, "10+1d20", TMD_POISONED, 0, 0, NULL); } /* Paralyze */ if (trap & (CHEST_PARALYZE)) { msg("A puff of yellow gas surrounds you!"); effect_simple(EF_TIMED_INC, "10+1d20", TMD_PARALYZED, 0, 0, NULL); } /* Summon monsters */ if (trap & (CHEST_SUMMON)) { msg("You are enveloped in a cloud of smoke!"); effect_simple(EF_SUMMON, "2+1d3", 0, 0, 0, NULL); } /* Explode */ if (trap & (CHEST_EXPLODE)) { msg("There is a sudden explosion!"); msg("Everything inside the chest is destroyed!"); obj->pval = 0; take_hit(player, damroll(5, 8), "an exploding chest"); } }
/** * Melee effect handler: Cause an earthquake around the player. */ static void melee_effect_handler_SHATTER(melee_effect_handler_context_t *context) { /* Obvious */ context->obvious = TRUE; /* Hack -- Reduce damage based on the player armor class */ context->damage = adjust_dam_armor(context->damage, context->ac); /* Take damage */ take_hit(context->p, context->damage, context->ddesc); /* Player is dead */ if (context->p->is_dead) return; /* Radius 8 earthquake centered at the monster */ if (context->damage > 23) { int px_old = context->p->px; int py_old = context->p->py; effect_simple(EF_EARTHQUAKE, "0", 0, 8, 0, NULL); /* Stop the blows if the player is pushed away */ if ((px_old != context->p->px) || (py_old != context->p->py)) context->do_break = TRUE; } }
/** * Do damage as the result of a melee attack that drains a stat. * * \param context is the information for the current attack. * \param stat is the STAT_ constant for the desired stat. */ static void melee_effect_stat(melee_effect_handler_context_t *context, int stat) { /* Take damage */ take_hit(context->p, context->damage, context->ddesc); /* Damage (stat) */ effect_simple(EF_DRAIN_STAT, "0", stat, 0, 0, &context->obvious); }
static void project_player_handler_DISEN(project_player_handler_context_t *context) { if (player_resists(player, ELEM_DISEN)) { msg("You resist the effect!"); return; } /* Disenchant gear */ effect_simple(EF_DISENCHANT, context->origin, "0", 0, 0, 0, NULL); }
static void project_player_handler_FORCE(project_player_handler_context_t *context) { char grids_away[5]; /* Stun */ (void)player_inc_timed(player, TMD_STUN, randint1(20), true, true); /* Thrust player away. */ strnfmt(grids_away, sizeof(grids_away), "%d", 3 + context->dam / 20); effect_simple(EF_THRUST_AWAY, context->origin, grids_away, context->y, context->x, 0, NULL); }
/** * Melee effect handler: Disenchant the player. */ static void melee_effect_handler_DISENCHANT(melee_effect_handler_context_t *context) { /* Take damage */ take_hit(context->p, context->damage, context->ddesc); /* Apply disenchantmen if no resist */ if (!player_resists(context->p, ELEM_DISEN)) effect_simple(EF_DISENCHANT, "0", 0, 0, 0, &context->obvious); /* Learn about the player */ update_smart_learn(context->m_ptr, context->p, 0, 0, ELEM_DISEN); }
/** * Melee effect handler: Absorb the player's light. */ static void melee_effect_handler_EAT_LIGHT(melee_effect_handler_context_t *context) { /* Take damage */ take_hit(context->p, context->damage, context->ddesc); /* Player is dead */ if (context->p->is_dead) return; /* Drain the light source */ effect_simple(EF_DRAIN_LIGHT, "250+1d250", 0, 0, 0, &context->obvious); }
/** * Apply blow after effects */ static bool blow_after_effects(int y, int x, bool quake) { /* Apply earthquake brand */ if (quake) { effect_simple(EF_EARTHQUAKE, "0", 0, 10, 0, NULL); /* Monster may be dead or moved */ if (!square_monster(cave, y, x)) return TRUE; } return FALSE; }
static void project_player_handler_GRAVITY(project_player_handler_context_t *context) { msg("Gravity warps around you."); /* Blink */ if (randint1(127) > player->lev) { const char *five = "5"; effect_simple(EF_TELEPORT, five, 0, 1, 0, NULL); } /* Slow */ (void)player_inc_timed(player, TMD_SLOW, 4 + randint0(4), TRUE, FALSE); /* Stun */ if (!player_of_has(player, OF_PROT_STUN)) { int duration = 5 + randint1(context->dam / 3); if (duration > 35) duration = 35; (void)player_inc_timed(player, TMD_STUN, duration, TRUE, TRUE); } }
/** * Process player commands from the command queue, finishing when there is a * command using energy (any regular game command), or we run out of commands * and need another from the user, or the character changes level or dies, or * the game is stopped. * * Notice the annoying code to handle "pack overflow", which * must come first just in case somebody manages to corrupt * the savefiles by clever use of menu commands or something. (Can go? NRM) * * Notice the annoying code to handle "monster memory" changes, * which allows us to avoid having to update the window flags * every time we change any internal monster memory field, and * also reduces the number of times that the recall window must * be redrawn. */ void process_player(void) { /* Check for interrupts */ player_resting_complete_special(player); event_signal(EVENT_CHECK_INTERRUPT); /* Repeat until energy is reduced */ do { /* Refresh */ notice_stuff(player); handle_stuff(player); event_signal(EVENT_REFRESH); /* Hack -- Pack Overflow */ pack_overflow(NULL); /* Assume free turn */ player->upkeep->energy_use = 0; /* Dwarves detect treasure */ if (player_has(player, PF_SEE_ORE)) { /* Only if they are in good shape */ if (!player->timed[TMD_IMAGE] && !player->timed[TMD_CONFUSED] && !player->timed[TMD_AMNESIA] && !player->timed[TMD_STUN] && !player->timed[TMD_PARALYZED] && !player->timed[TMD_TERROR] && !player->timed[TMD_AFRAID]) effect_simple(EF_DETECT_GOLD, "3d3", 1, 0, 0, NULL); } /* Paralyzed or Knocked Out player gets no turn */ if ((player->timed[TMD_PARALYZED]) || (player->timed[TMD_STUN] >= 100)) cmdq_push(CMD_SLEEP); /* Prepare for the next command */ if (cmd_get_nrepeats() > 0) event_signal(EVENT_COMMAND_REPEAT); else { /* Check monster recall */ if (player->upkeep->monster_race) player->upkeep->redraw |= (PR_MONSTER); /* Place cursor on player/target */ event_signal(EVENT_REFRESH); } /* Get a command from the queue if there is one */ if (!cmdq_pop(CMD_GAME)) break; if (!player->upkeep->playing) break; process_player_cleanup(); } while (!player->upkeep->energy_use && !player->is_dead && !player->upkeep->generate_level); /* Notice stuff (if needed) */ notice_stuff(player); }
/** * 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); } } } }
/** * Attack the player via physical attacks. */ bool make_attack_normal(struct monster *mon, struct player *p) { struct monster_lore *lore = get_lore(mon->race); int ap_cnt; int k, tmp, ac, rlev; char m_name[80]; char ddesc[80]; bool blinked; /* Not allowed to attack */ if (rf_has(mon->race->flags, RF_NEVER_BLOW)) return (false); /* Total armor */ ac = p->state.ac + p->state.to_a; /* Extract the effective monster level */ rlev = ((mon->race->level >= 1) ? mon->race->level : 1); /* Get the monster name (or "it") */ monster_desc(m_name, sizeof(m_name), mon, MDESC_STANDARD); /* Get the "died from" information (i.e. "a kobold") */ monster_desc(ddesc, sizeof(ddesc), mon, MDESC_SHOW | MDESC_IND_VIS); /* Assume no blink */ blinked = false; /* Scan through all blows */ for (ap_cnt = 0; ap_cnt < z_info->mon_blows_max; ap_cnt++) { bool visible = false; bool obvious = false; bool do_break = false; int power = 0; int damage = 0; int do_cut = 0; int do_stun = 0; int sound_msg = MSG_GENERIC; const char *act = NULL; /* Extract the attack infomation */ int effect = mon->race->blow[ap_cnt].effect; int method = mon->race->blow[ap_cnt].method; random_value dice = mon->race->blow[ap_cnt].dice; /* Hack -- no more attacks */ if (!method) break; /* Handle "leaving" */ if (p->is_dead || p->upkeep->generate_level) break; /* Extract visibility (before blink) */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) visible = true; /* Extract visibility from carrying light */ if (rf_has(mon->race->flags, RF_HAS_LIGHT)) visible = true; /* Extract the attack "power" */ power = monster_blow_effect_power(effect); /* Monster hits player */ if (!effect || check_hit(p, power, rlev)) { melee_effect_handler_f effect_handler; /* Always disturbing */ disturb(p, 1); /* Hack -- Apply "protection from evil" */ if (p->timed[TMD_PROTEVIL] > 0) { /* Learn about the evil flag */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_EVIL); if (rf_has(mon->race->flags, RF_EVIL) && p->lev >= rlev && randint0(100) + p->lev > 50) { /* Message */ msg("%s is repelled.", m_name); /* Hack -- Next attack */ continue; } } /* Describe the attack method */ act = monster_blow_method_action(method); do_cut = monster_blow_method_cut(method); do_stun = monster_blow_method_stun(method); sound_msg = monster_blow_method_message(method); /* Message */ if (act) msgt(sound_msg, "%s %s", m_name, act); /* Hack -- assume all attacks are obvious */ obvious = true; /* Roll dice */ damage = randcalc(dice, rlev, RANDOMISE); /* Perform the actual effect. */ effect_handler = melee_handler_for_blow_effect(effect); if (effect_handler != NULL) { melee_effect_handler_context_t context = { p, mon, rlev, method, ac, ddesc, obvious, blinked, do_break, damage, }; effect_handler(&context); /* Save any changes made in the handler for later use. */ obvious = context.obvious; blinked = context.blinked; damage = context.damage; do_break = context.do_break; } else { msg("ERROR: Effect handler not found for %d.", effect); } /* Don't cut or stun if player is dead */ if (p->is_dead) { do_cut = false; do_stun = false; } /* Hack -- only one of cut or stun */ if (do_cut && do_stun) { /* Cancel cut */ if (randint0(100) < 50) do_cut = 0; /* Cancel stun */ else do_stun = 0; } /* Handle cut */ if (do_cut) { /* Critical hit (zero if non-critical) */ tmp = monster_critical(dice, rlev, damage); /* Roll for damage */ switch (tmp) { case 0: k = 0; break; case 1: k = randint1(5); break; case 2: k = randint1(5) + 5; break; case 3: k = randint1(20) + 20; break; case 4: k = randint1(50) + 50; break; case 5: k = randint1(100) + 100; break; case 6: k = 300; break; default: k = 500; break; } /* Apply the cut */ if (k) (void)player_inc_timed(p, TMD_CUT, k, true, true); } /* Handle stun */ if (do_stun) { /* Critical hit (zero if non-critical) */ tmp = monster_critical(dice, rlev, damage); /* Roll for damage */ switch (tmp) { case 0: k = 0; break; case 1: k = randint1(5); break; case 2: k = randint1(10) + 10; break; case 3: k = randint1(20) + 20; break; case 4: k = randint1(30) + 30; break; case 5: k = randint1(40) + 40; break; case 6: k = 100; break; default: k = 200; break; } /* Apply the stun */ if (k) (void)player_inc_timed(p, TMD_STUN, k, true, true); } } else { /* Visible monster missed player, so notify if appropriate. */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && monster_blow_method_miss(method)) { /* Disturbing */ disturb(p, 1); msg("%s misses you.", m_name); } } /* Analyze "visible" monsters only */ if (visible) { /* Count "obvious" attacks (and ones that cause damage) */ if (obvious || damage || (lore->blows[ap_cnt].times_seen > 10)) { /* Count attacks of this type */ if (lore->blows[ap_cnt].times_seen < UCHAR_MAX) lore->blows[ap_cnt].times_seen++; } } /* Skip the other blows if necessary */ if (do_break) break; } /* Blink away */ if (blinked) { char dice[5]; msg("There is a puff of smoke!"); strnfmt(dice, sizeof(dice), "%d", z_info->max_sight * 2 + 5); effect_simple(EF_TELEPORT, dice, 0, 0, 0, NULL); } /* Always notice cause of death */ if (p->is_dead && (lore->deaths < SHRT_MAX)) lore->deaths++; /* Learn lore */ lore_update(mon->race, lore); /* Assume we attacked */ return (true); }