static enum parser_error parse_summon_base(struct parser *p) { struct summon *s = parser_priv(p); struct monster_base *base; struct monster_base_list *b = mem_zalloc(sizeof(*b)); assert(s); base = lookup_monster_base(parser_getsym(p, "base")); if (base == NULL) { mem_free(b); return PARSE_ERROR_INVALID_MONSTER_BASE; } b->base = base; b->next = s->bases; s->bases = b; return PARSE_ERROR_NONE; }
static enum parser_error parse_r_t(struct parser *p) { struct monster_race *r = parser_priv(p); r->base = lookup_monster_base(parser_getsym(p, "base")); if (r->base == NULL) /* Todo: make new error for this */ return PARSE_ERROR_UNRECOGNISED_TVAL; /* The template sets the default display character */ r->d_char = r->base->d_char; /* Give the monster its default flags */ rf_union(r->flags, r->base->flags); return PARSE_ERROR_NONE; }
int test_defaults(void *state) { size_t i; struct monster_base *mb = lookup_monster_base("giant"); int tval = tval_find_idx("sword"); /* Monster bases */ eq(process_pref_file_command("monster-base:giant:3:3"), 0); for (i = 0; i < z_info->r_max; i++) { monster_race *race = &r_info[i]; if (race->base != mb) continue; eq(monster_x_attr[race->ridx], 3); eq(monster_x_char[race->ridx], 3); } /* Object tvals */ eq(process_pref_file_command("object:sword:*:3:3"), 0); for (i = 0; i < z_info->k_max; i++) { struct object_kind *kind = &k_info[i]; if (kind->tval != tval) continue; eq(kind_x_attr[kind->kidx], 3); eq(kind_x_char[kind->kidx], 3); } /* Traps */ eq(process_pref_file_command("trap:*:*:3:3"), 0); for (i = 0; i < z_info->trap_max; i++) { int light_idx; for (light_idx = 0; light_idx < LIGHTING_MAX; light_idx++) { eq(trap_x_attr[light_idx][i], 3); eq(trap_x_attr[light_idx][i], 3); } } ok; }
static enum parser_error parse_r_friends_base(struct parser *p) { struct monster_race *r = parser_priv(p); struct monster_friends_base *f; struct random number; if (!r) return PARSE_ERROR_MISSING_RECORD_HEADER; f = mem_zalloc(sizeof *f); number = parser_getrand(p, "number"); f->number_dice = number.dice; f->number_side = number.sides; f->percent_chance = parser_getuint(p, "chance"); f->base = lookup_monster_base(parser_getstr(p, "name")); if (!f->base) return PARSE_ERROR_UNRECOGNISED_TVAL; f->next = r->friends_base; r->friends_base = f; return PARSE_ERROR_NONE; }
/** * Decreases a monster's hit points by `dam` and handle monster death. * * Hack -- we "delay" fear messages by passing around a "fear" flag. * * We announce monster death (using an optional "death message" (`note`) * if given, and a otherwise a generic killed/destroyed message). * * Returns TRUE if the monster has been killed (and deleted). * * TODO: Consider decreasing monster experience over time, say, by using * "(m_exp * m_lev * (m_lev)) / (p_lev * (m_lev + n_killed))" instead * of simply "(m_exp * m_lev) / (p_lev)", to make the first monster * worth more than subsequent monsters. This would also need to * induce changes in the monster recall code. XXX XXX XXX **/ bool mon_take_hit(struct monster *m_ptr, int dam, bool *fear, const char *note) { s32b div, new_exp, new_exp_frac; monster_lore *l_ptr = get_lore(m_ptr->race); /* Redraw (later) if needed */ if (p_ptr->health_who == m_ptr) p_ptr->redraw |= (PR_HEALTH); /* Wake it up */ mon_clear_timed(m_ptr, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, FALSE); /* Become aware of its presence */ if (m_ptr->unaware) become_aware(m_ptr); /* Hurt it */ m_ptr->hp -= dam; /* It is dead now */ if (m_ptr->hp < 0) { char m_name[80]; char buf[80]; /* Assume normal death sound */ int soundfx = MSG_KILL; /* Play a special sound if the monster was unique */ if (rf_has(m_ptr->race->flags, RF_UNIQUE)) { if (m_ptr->race->base == lookup_monster_base("Morgoth")) soundfx = MSG_KILL_KING; else soundfx = MSG_KILL_UNIQUE; } /* Extract monster name */ monster_desc(m_name, sizeof(m_name), m_ptr, 0); /* Death by Missile/Spell attack */ if (note) { /* Hack -- allow message suppression */ if (strlen(note) <= 1) { /* Be silent */ } else { char *str = format("%s%s", m_name, note); my_strcap(str); msgt(soundfx, "%s", str); } } /* Death by physical attack -- invisible monster */ else if (!m_ptr->ml) msgt(soundfx, "You have killed %s.", m_name); /* Death by Physical attack -- non-living monster */ else if (monster_is_unusual(m_ptr->race)) msgt(soundfx, "You have destroyed %s.", m_name); /* Death by Physical attack -- living monster */ else msgt(soundfx, "You have slain %s.", m_name); /* Player level */ div = p_ptr->lev; /* Give some experience for the kill */ new_exp = ((long)m_ptr->race->mexp * m_ptr->race->level) / div; /* Handle fractional experience */ new_exp_frac = ((((long)m_ptr->race->mexp * m_ptr->race->level) % div) * 0x10000L / div) + p_ptr->exp_frac; /* Keep track of experience */ if (new_exp_frac >= 0x10000L) { new_exp++; p_ptr->exp_frac = (u16b)(new_exp_frac - 0x10000L); } else p_ptr->exp_frac = (u16b)new_exp_frac; /* When the player kills a Unique, it stays dead */ if (rf_has(m_ptr->race->flags, RF_UNIQUE)) { char unique_name[80]; m_ptr->race->max_num = 0; /* * This gets the correct name if we slay an invisible * unique and don't have See Invisible. */ monster_desc(unique_name, sizeof(unique_name), m_ptr, MDESC_SHOW | MDESC_IND2); /* Log the slaying of a unique */ strnfmt(buf, sizeof(buf), "Killed %s", unique_name); history_add(buf, HISTORY_SLAY_UNIQUE, 0); } /* Gain experience */ player_exp_gain(p_ptr, new_exp); /* Generate treasure */ monster_death(m_ptr, FALSE); /* Recall even invisible uniques or winners */ if (m_ptr->ml || rf_has(m_ptr->race->flags, RF_UNIQUE)) { /* Count kills this life */ if (l_ptr->pkills < MAX_SHORT) l_ptr->pkills++; /* Count kills in all lives */ if (l_ptr->tkills < MAX_SHORT) l_ptr->tkills++; /* Hack -- Auto-recall */ monster_race_track(m_ptr->race); } /* Delete the monster */ delete_monster_idx(m_ptr->midx); /* Not afraid */ (*fear) = FALSE; /* Monster is dead */ return (TRUE); } /* Mega-Hack -- Pain cancels fear */ if (!(*fear) && m_ptr->m_timed[MON_TMD_FEAR] && (dam > 0)) { int tmp = randint1(dam); /* Cure a little fear */ if (tmp < m_ptr->m_timed[MON_TMD_FEAR]) { /* Reduce fear */ mon_dec_timed(m_ptr, MON_TMD_FEAR, tmp, MON_TMD_FLG_NOMESSAGE, FALSE); } /* Cure all the fear */ else { /* Cure fear */ mon_clear_timed(m_ptr, MON_TMD_FEAR, MON_TMD_FLG_NOMESSAGE, FALSE); /* No more fear */ (*fear) = FALSE; } } /* Sometimes a monster gets scared by damage */ if (!m_ptr->m_timed[MON_TMD_FEAR] && !rf_has(m_ptr->race->flags, RF_NO_FEAR) && dam > 0) { int percentage; /* Percentage of fully healthy */ percentage = (100L * m_ptr->hp) / m_ptr->maxhp; /* * Run (sometimes) if at 10% or less of max hit points, * or (usually) when hit for half its current hit points */ if ((randint1(10) >= percentage) || ((dam >= m_ptr->hp) && (randint0(100) < 80))) { int timer = randint1(10) + (((dam >= m_ptr->hp) && (percentage > 7)) ? 20 : ((11 - percentage) * 5)); /* Hack -- note fear */ (*fear) = TRUE; mon_inc_timed(m_ptr, MON_TMD_FEAR, timer, MON_TMD_FLG_NOMESSAGE | MON_TMD_FLG_NOFAIL, FALSE); } } /* Not dead yet */ return (FALSE); }
/** * Show and delete the stacked monster messages. * Some messages are delayed so that they show up after everything else. * This is to avoid things like "The snaga dies. The snaga runs in fear!" * So we only flush messages matching the delay parameter. */ static void flush_monster_messages(bool delay, byte delay_tag) { const monster_race *r_ptr; int i, count; char buf[512]; char *action; bool action_only; /* Show every message */ for (i = 0; i < size_mon_msg; i++) { int type = MSG_GENERIC; if (mon_msg[i].delay != delay) continue; /* Skip if we are delaying and the tags don't match */ if (mon_msg[i].delay && mon_msg[i].delay_tag != delay_tag) continue; /* Cache the monster count */ count = mon_msg[i].mon_count; /* Paranoia */ if (count < 1) continue; /* Start with an empty string */ buf[0] = '\0'; /* Cache the race index */ r_ptr = mon_msg[i].race; /* Get the proper message action */ action = get_mon_msg_action(mon_msg[i].msg_code, (count > 1), r_ptr); /* Monster is marked as invisible */ if (mon_msg[i].mon_flags & MON_MSG_FLAG_INVISIBLE) r_ptr = NULL; /* Special message? */ action_only = (*action == '~'); /* Format the proper message depending on type, number and visibility */ if (r_ptr && !action_only) { char race_name[80]; /* Get the race name */ my_strcpy(race_name, r_ptr->name, sizeof(race_name)); /* Uniques, multiple monsters, or just one */ if (rf_has(r_ptr->flags, RF_UNIQUE)) { /* Just copy the race name */ my_strcpy(buf, (r_ptr->name), sizeof(buf)); } else if (count > 1) { /* Get the plural of the race name */ if (r_ptr->plural != NULL) { my_strcpy(race_name, r_ptr->plural, sizeof(race_name)); } else { plural_aux(race_name, sizeof(race_name)); } /* Put the count and the race name together */ strnfmt(buf, sizeof(buf), "%d %s", count, race_name); } else { /* Just add a slight flavor */ strnfmt(buf, sizeof(buf), "the %s", race_name); } } else if (!r_ptr && !action_only) { if (count > 1) { /* Show the counter */ strnfmt(buf, sizeof(buf), "%d monsters", count); } else { /* Just one non-visible monster */ my_strcpy(buf, "it", sizeof(buf)); } } /* Special message. Nuke the mark */ if (action_only) ++action; /* Regular message */ else { /* Add special mark. Monster is offscreen */ if (mon_msg[i].mon_flags & MON_MSG_FLAG_OFFSCREEN) my_strcat(buf, " (offscreen)", sizeof(buf)); /* Add the separator */ my_strcat(buf, " ", sizeof(buf)); } /* Append the action to the message */ my_strcat(buf, action, sizeof(buf)); /* Capitalize the message */ *buf = toupper((unsigned char)*buf); switch (mon_msg[i].msg_code) { case MON_MSG_FLEE_IN_TERROR: type = MSG_FLEE; break; case MON_MSG_MORIA_DEATH: case MON_MSG_DESTROYED: case MON_MSG_DIE: case MON_MSG_SHRIVEL_LIGHT: case MON_MSG_DISENTEGRATES: case MON_MSG_FREEZE_SHATTER: case MON_MSG_DISSOLVE: { /* Assume normal death sound */ type = MSG_KILL; /* Play a special sound if the monster was unique */ if (r_ptr != NULL && rf_has(r_ptr->flags, RF_UNIQUE)) { if (r_ptr->base == lookup_monster_base("Morgoth")) type = MSG_KILL_KING; else type = MSG_KILL_UNIQUE; } break; } } /* Show the message */ msgt(type, "%s", buf); } }