/* * Learn about an "observed" resistance or other player state property, or * lack of it. */ public void update_smart_learn(Player.Player p, Object_Flag what) { Monster_Race r_ptr = Misc.r_info[r_idx]; /* Sanity check */ if (what != null) { return; } /* anything a monster might learn, the player should learn */ Object.Object.wieldeds_notice_flag(p, what.value); /* Not allowed to learn */ if (!Option.birth_ai_learn.value) { return; } /* Too stupid to learn anything */ if (r_ptr.flags.has(Monster_Flag.STUPID.value)) { return; } /* Not intelligent, only learn sometimes */ if (!r_ptr.flags.has(Monster_Flag.SMART.value) && Random.one_in_(2)) { return; } /* Analyze the knowledge; fail very rarely */ if (p.check_state(what, p.state.flags) && !Random.one_in_(100)) { known_pflags.on(what.value); } else { known_pflags.off(what.value); } }
/* * Build a string describing a monster in some way. * * We can correctly describe monsters based on their visibility. * We can force all monsters to be treated as visible or invisible. * We can build nominatives, objectives, possessives, or reflexives. * We can selectively pronominalize hidden, visible, or all monsters. * We can use definite or indefinite descriptions for hidden monsters. * We can use definite or indefinite descriptions for visible monsters. * * Pronominalization involves the gender whenever possible and allowed, * so that by cleverly requesting pronominalization / visibility, you * can get messages like "You hit someone. She screams in agony!". * * Reflexives are acquired by requesting Objective plus Possessive. * * I am assuming that no monster name is more than 65 characters long, * so that "char desc[80];" is sufficiently large for any result, even * when the "offscreen" notation is added. * * Note that the "possessive" for certain unique monsters will look * really silly, as in "Morgoth, King of Darkness's". We should * perhaps add a flag to "remove" any "descriptives" in the name. * * Note that "offscreen" monsters will get a special "(offscreen)" * notation in their name if they are visible but offscreen. This * may look silly with possessives, as in "the rat's (offscreen)". * Perhaps the "offscreen" descriptor should be abbreviated. * * Mode Flags: * 0x01 -. Objective (or Reflexive) * 0x02 -. Possessive (or Reflexive) * 0x04 -. Use indefinites for hidden monsters ("something") * 0x08 -. Use indefinites for visible monsters ("a kobold") * 0x10 -. Pronominalize hidden monsters * 0x20 -. Pronominalize visible monsters * 0x40 -. Assume the monster is hidden * 0x80 -. Assume the monster is visible * * Useful Modes: * 0x00 -. Full nominative name ("the kobold") or "it" * 0x04 -. Full nominative name ("the kobold") or "something" * 0x80 -. Banishment resistance name ("the kobold") * 0x88 -. Killing name ("a kobold") * 0x22 -. Possessive, genderized if visable ("his") or "its" * 0x23 -. Reflexive, genderized if visable ("himself") or "itself" */ public string monster_desc(Desc in_mode) { string res = "ERROR IN MONSTER_DESC"; int mode = (int)in_mode; Monster_Race r_ptr = Misc.r_info[r_idx]; string name = r_ptr.Name; /* Can we "see" it (forced, or not hidden + visible) */ bool seen = (((mode & (0x80)) != 0) || (((mode & (0x40)) == 0) && ml)); /* Sexed Pronouns (seen and forced, or unseen and allowed) */ bool pron = ((seen && ((mode & (0x20)) != 0)) || (!seen && ((mode & (0x10)) != 0))); /* First, try using pronouns, or describing hidden monsters */ if (!seen || pron) { /* an encoding of the monster "sex" */ int kind = 0x00; /* Extract the gender (if applicable) */ if (r_ptr.flags.has(Monster_Flag.FEMALE.value)) { kind = 0x20; } else if (r_ptr.flags.has(Monster_Flag.MALE.value)) { kind = 0x10; } /* Ignore the gender (if desired) */ if (!pron) { kind = 0x00; } /* Assume simple result */ res = "it"; /* Brute force: split on the possibilities */ switch (kind + (mode & 0x07)) { /* Neuter, or unknown */ case 0x00: res = "it"; break; case 0x01: res = "it"; break; case 0x02: res = "its"; break; case 0x03: res = "itself"; break; case 0x04: res = "something"; break; case 0x05: res = "something"; break; case 0x06: res = "something's"; break; case 0x07: res = "itself"; break; /* Male (assume human if vague) */ case 0x10: res = "he"; break; case 0x11: res = "him"; break; case 0x12: res = "his"; break; case 0x13: res = "himself"; break; case 0x14: res = "someone"; break; case 0x15: res = "someone"; break; case 0x16: res = "someone's"; break; case 0x17: res = "himself"; break; /* Female (assume human if vague) */ case 0x20: res = "she"; break; case 0x21: res = "her"; break; case 0x22: res = "her"; break; case 0x23: res = "herself"; break; case 0x24: res = "someone"; break; case 0x25: res = "someone"; break; case 0x26: res = "someone's"; break; case 0x27: res = "herself"; break; } } /* Handle visible monsters, "reflexive" request */ else if ((mode & 0x02) != 0 && (mode & 0x01) != 0) { /* The monster is visible, so use its gender */ if (r_ptr.flags.has(Monster_Flag.FEMALE.value)) { res = "herself"; } else if (r_ptr.flags.has(Monster_Flag.MALE.value)) { res = "himself"; } else { res = "itself"; } } /* Handle all other visible monster requests */ else { /* It could be a Unique */ if (r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { res = name; } /* It could be an indefinite monster */ else if ((mode & 0x08) != 0) { /* XXX Check plurality for "some" */ /* Indefinite monsters need an indefinite article */ res = "aeiouAEIOU".Contains(name[0]) ? "an" : "a"; res += name; } /* It could be a normal, definite, monster */ else { /* Definite monsters need a definite article */ res = "the " + name; } /* Handle the Possessive as a special afterthought */ if ((mode & 0x02) != 0) { /* XXX Check for trailing "s" */ /* Simply append "apostrophe" and "s" */ res += "'s"; } /* Mention "offscreen" monsters XXX XXX */ if (!Term.panel_contains(fy, fx)) { /* Append special notation */ res += " (offscreen)"; } } /* Return the result */ return(res); }
/** * Calculate hp for a monster. This function assumes that the Rand_normal * function has limits of +/- 4x std_dev. If that changes, this function * will become inaccurate. * * \param r_ptr is the race of the monster in question. * \param hp_aspect is the hp calc we want (min, max, avg, random). */ public static int mon_hp(Monster_Race r_ptr, aspect hp_aspect) { int std_dev = (((r_ptr.avg_hp * 10) / 8) + 5) / 10; if (r_ptr.avg_hp > 1) std_dev++; switch (hp_aspect) { case aspect.MINIMISE: return (r_ptr.avg_hp - (4 * std_dev)); case aspect.MAXIMISE: case aspect.EXTREMIFY: return (r_ptr.avg_hp + (4 * std_dev)); case aspect.AVERAGE: return r_ptr.avg_hp; default: return Random.Rand_normal(r_ptr.avg_hp, std_dev); } }
/** * Return the coin type of a monster race, based on the monster being * killed. */ static int get_coin_type(Monster_Race r_ptr) { string name = r_ptr.Name; if (!r_ptr.flags.has(Monster_Flag.METAL.value)) return (int)SVal.sval_gold.SV_GOLD_ANY; /* Look for textual clues */ if (name.Contains("copper ")) return (int)SVal.sval_gold.SV_COPPER; if (name.Contains("silver ")) return (int)SVal.sval_gold.SV_SILVER; if (name.Contains("gold ")) return (int)SVal.sval_gold.SV_GOLD; if (name.Contains("mithril ")) return (int)SVal.sval_gold.SV_MITHRIL; if (name.Contains("adamantite ")) return (int)SVal.sval_gold.SV_ADAMANTITE; /* Assume nothing */ return (int)SVal.sval_gold.SV_GOLD_ANY; }
/* * This function updates the monster record of the given monster * * This involves extracting the distance to the player (if requested), * and then checking for visibility (natural, infravision, see-invis, * telepathy), updating the monster visibility flag, redrawing (or * erasing) the monster when its visibility changes, and taking note * of any interesting monster flags (cold-blooded, invisible, etc). * * Note the new "mflag" field which encodes several monster state flags, * including "view" for when the monster is currently in line of sight, * and "mark" for when the monster is currently visible via detection. * * The only monster fields that are changed here are "cdis" (the * distance from the player), "ml" (visible to the player), and * "mflag" (to maintain the "MFLAG_VIEW" flag). * * Note the special "update_monsters()" function which can be used to * call this function once for every monster. * * Note the "full" flag which requests that the "cdis" field be updated, * this is only needed when the monster (or the player) has moved. * * Every time a monster moves, we must call this function for that * monster, and update the distance, and the visibility. Every time * the player moves, we must call this function for every monster, and * update the distance, and the visibility. Whenever the player "state" * changes in certain ways ("blindness", "infravision", "telepathy", * and "see invisible"), we must call this function for every monster, * and update the visibility. * * Routines that change the "illumination" of a grid must also call this * function for any monster in that grid, since the "visibility" of some * monsters may be based on the illumination of their grid. * * Note that this function is called once per monster every time the * player moves. When the player is running, this function is one * of the primary bottlenecks, along with "update_view()" and the * "process_monsters()" code, so efficiency is important. * * Note the optimized "inline" version of the "distance()" function. * * A monster is "visible" to the player if (1) it has been detected * by the player, (2) it is close to the player and the player has * telepathy, or (3) it is close to the player, and in line of sight * of the player, and it is "illuminated" by some combination of * infravision, torch light, or permanent light (invisible monsters * are only affected by "light" if the player can see invisible). * * Monsters which are not on the current panel may be "visible" to * the player, and their descriptions will include an "offscreen" * reference. Currently, offscreen monsters cannot be targeted * or viewed directly, but old targets will remain set. XXX XXX * * The player can choose to be disturbed by several things, including * "OPT(disturb_move)" (monster which is viewable moves in some way), and * "OPT(disturb_near)" (monster which is "easily" viewable moves in some * way). Note that "moves" includes "appears" and "disappears". */ public static void update_mon(int m_idx, bool full) { Monster m_ptr = Cave.cave_monster(Cave.cave, m_idx); Monster_Race r_ptr = Misc.r_info[m_ptr.r_idx]; Monster_Lore l_ptr = Misc.l_list[m_ptr.r_idx]; if (l_ptr == null) { l_ptr = Misc.l_list[m_ptr.r_idx] = new Monster_Lore(); } int d; /* Current location */ int fy = m_ptr.fy; int fx = m_ptr.fx; /* Seen at all */ bool flag = false; /* Seen by vision */ bool easy = false; /* Compute distance */ if (full) { int py = Misc.p_ptr.py; int px = Misc.p_ptr.px; /* Distance components */ int dy = (py > fy) ? (py - fy) : (fy - py); int dx = (px > fx) ? (px - fx) : (fx - px); /* Approximate distance */ d = (dy > dx) ? (dy + (dx >> 1)) : (dx + (dy >> 1)); /* Restrict distance */ if (d > 255) { d = 255; } /* Save the distance */ m_ptr.cdis = (byte)d; } /* Extract distance */ else { /* Extract the distance */ d = m_ptr.cdis; } /* Detected */ if ((m_ptr.mflag & (Monster_Flag.MFLAG_MARK)) != 0) { flag = true; } /* Nearby */ if (d <= Misc.MAX_SIGHT) { /* Basic telepathy */ if (Misc.p_ptr.check_state(Object_Flag.TELEPATHY, Misc.p_ptr.state.flags)) { /* Empty mind, no telepathy */ if (r_ptr.flags.has(Monster_Flag.EMPTY_MIND.value)) { /* Nothing! */ } /* Weird mind, occasional telepathy */ else if (r_ptr.flags.has(Monster_Flag.WEIRD_MIND.value)) { throw new NotImplementedException(); ///* One in ten individuals are detectable */ //if ((m_idx % 10) == 5) //{ // /* Detectable */ // flag = true; // /* Check for LOS so that MFLAG_VIEW is set later */ // if (player_has_los_bold(fy, fx)) easy = true; //} } /* Normal mind, allow telepathy */ else { throw new NotImplementedException(); ///* Detectable */ //flag = true; ///* Check for LOS to that MFLAG_VIEW is set later */ //if (player_has_los_bold(fy, fx)) easy = true; } } /* Normal line of sight and player is not blind */ if (Cave.player_has_los_bold(fy, fx) && Misc.p_ptr.timed[(int)Timed_Effect.BLIND] == 0) { /* Use "infravision" */ if (d <= Misc.p_ptr.state.see_infra) { /* Learn about warm/cold blood */ l_ptr.flags.on(Monster_Flag.COLD_BLOOD.value); /* Handle "warm blooded" monsters */ if (!r_ptr.flags.has(Monster_Flag.COLD_BLOOD.value)) { /* Easy to see */ easy = flag = true; } } /* See if the monster is emitting light */ /*if (rf_has(r_ptr.flags, RF_HAS_LIGHT)) easy = flag = true;*/ /* Use "illumination" */ if (Cave.player_can_see_bold(fy, fx)) { /* Learn it emits light */ l_ptr.flags.on(Monster_Flag.HAS_LIGHT.value); /* Learn about invisibility */ l_ptr.flags.on(Monster_Flag.INVISIBLE.value); /* Handle "invisible" monsters */ if (r_ptr.flags.has(Monster_Flag.INVISIBLE.value)) { /* See invisible */ if (Misc.p_ptr.check_state(Object_Flag.SEE_INVIS, Misc.p_ptr.state.flags)) { /* Easy to see */ easy = flag = true; } } /* Handle "normal" monsters */ else { /* Easy to see */ easy = flag = true; } } } } /* If a mimic looks like a squelched item, it's not seen */ if (is_mimicking(m_idx)) { throw new NotImplementedException(); //object_type *o_ptr = object_byid(m_ptr.mimicked_o_idx); //if (squelch_item_ok(o_ptr)) // easy = flag = false; } /* The monster is now visible */ if (flag) { /* Learn about the monster's mind */ if (Misc.p_ptr.check_state(Object_Flag.TELEPATHY, Misc.p_ptr.state.flags)) { l_ptr.flags.set(Monster_Flag.EMPTY_MIND.value, Monster_Flag.WEIRD_MIND.value, Monster_Flag.SMART.value, Monster_Flag.STUPID.value); } /* It was previously unseen */ if (!m_ptr.ml) { /* Mark as visible */ m_ptr.ml = true; /* Draw the monster */ Cave.cave_light_spot(Cave.cave, fy, fx); /* Update health bar as needed */ if (Misc.p_ptr.health_who == m_idx) { Misc.p_ptr.redraw |= (Misc.PR_HEALTH); } /* Hack -- Count "fresh" sightings */ if (l_ptr.sights < short.MaxValue) { l_ptr.sights++; } /* Disturb on appearance */ if (Option.disturb_move.value) { Cave.disturb(Misc.p_ptr, 1, 0); } /* Window stuff */ Misc.p_ptr.redraw |= Misc.PR_MONLIST; } } /* The monster is not visible */ else { /* It was previously seen */ if (m_ptr.ml) { /* Treat mimics differently */ if (m_ptr.mimicked_o_idx == 0 || Squelch.item_ok(Object.Object.byid(m_ptr.mimicked_o_idx))) { /* Mark as not visible */ m_ptr.ml = false; /* Erase the monster */ Cave.cave_light_spot(Cave.cave, fy, fx); /* Update health bar as needed */ if (Misc.p_ptr.health_who == m_idx) { Misc.p_ptr.redraw |= (Misc.PR_HEALTH); } /* Disturb on disappearance */ if (Option.disturb_move.value) { Cave.disturb(Misc.p_ptr, 1, 0); } /* Window stuff */ Misc.p_ptr.redraw |= Misc.PR_MONLIST; } } } /* The monster is now easily visible */ if (easy) { /* Change */ if ((m_ptr.mflag & (Monster_Flag.MFLAG_VIEW)) == 0) { /* Mark as easily visible */ m_ptr.mflag |= (Monster_Flag.MFLAG_VIEW); /* Disturb on appearance */ if (Option.disturb_near.value) { Cave.disturb(Misc.p_ptr, 1, 0); } /* Re-draw monster window */ Misc.p_ptr.redraw |= Misc.PR_MONLIST; } } /* The monster is not easily visible */ else { /* Change */ if ((m_ptr.mflag & (Monster_Flag.MFLAG_VIEW)) != 0) { /* Mark as not easily visible */ m_ptr.mflag &= ~(Monster_Flag.MFLAG_VIEW); /* Disturb on disappearance */ if (Option.disturb_near.value) { Cave.disturb(Misc.p_ptr, 1, 0); } /* Re-draw monster list window */ Misc.p_ptr.redraw |= Misc.PR_MONLIST; } } }
/* * Reset the "visual" lists * * This involves resetting various things to their "default" state. * * If the "prefs" flag is true, then we will also load the appropriate * "user pref file" based on the current setting of the "use_graphics" * flag. This is useful for switching "graphics" on/off. */ /* XXX this does not belong here */ //Todo: Figure out where it goes. At a glance, init seems nice. //Nick: Was previously in "obj-util.c"... lulz public static void reset_visuals(bool load_prefs) { int i; Flavor f; /* Extract default attr/char code for features */ for (i = 0; i < Misc.z_info.f_max; i++) { int j; Feature f_ptr = Misc.f_info[i]; /* Assume we will use the underlying values */ for (j = 0; j < (int)Grid_Data.grid_light_level.FEAT_LIGHTING_MAX; j++) { f_ptr.x_attr[j] = f_ptr.d_attr; f_ptr.x_char[j] = f_ptr.d_char; } } /* Extract default attr/char code for objects */ for (i = 0; i < Misc.z_info.k_max; i++) { Object.Object_Kind k_ptr = Misc.k_info[i]; if (k_ptr == null) { continue; } /* Default attr/char */ k_ptr.x_attr = k_ptr.d_attr; k_ptr.x_char = k_ptr.d_char; } /* Extract default attr/char code for monsters */ for (i = 0; i < Misc.z_info.r_max; i++) { Monster.Monster_Race r_ptr = Misc.r_info[i]; if (r_ptr == null) { continue; } /* Default attr/char */ r_ptr.x_attr = r_ptr.d_attr; r_ptr.x_char = r_ptr.d_char; } /* Extract default attr/char code for flavors */ for (f = Misc.flavors; f != null; f = f.next) { f.x_attr = f.d_attr; f.x_char = f.d_char; } /* Extract attr/chars for inventory objects (by tval) */ for (i = 0; i < Misc.tval_to_attr.Length; i++) { /* Default to white */ Misc.tval_to_attr[i] = ConsoleColor.White; } if (!load_prefs) { return; } /* Graphic symbols */ if (Misc.use_graphics != 0) { Prefs.process_pref_file("graf.prf", false, false); } /* Normal symbols */ else { Prefs.process_pref_file("font.prf", false, false); } //#ifdef ALLOW_BORG_GRAPHICS // /* Initialize the translation table for the borg */ // init_translate_visuals(); //#endif /* ALLOW_BORG_GRAPHICS */ }
/* * Attack the player via physical attacks. */ //TODO: Submit this function to the Guiness book of world records for "Longest f*****g function" //TODO: Refactor this. Refactor this so f*****g hard. public bool make_attack_normal(Player.Player p) { Monster_Race r_ptr = Misc.r_info[r_idx]; Monster_Lore l_ptr = Misc.l_list[r_idx]; int i, tmp; short gold; string o_name; // Not allowed to attack if (r_ptr.flags.has(Monster_Flag.NEVER_BLOW.value)) { return(false); } // Total armor int ac = p.state.ac + p.state.to_a; // Extract the effective monster level int rlev = ((r_ptr.level >= 1) ? r_ptr.level : 1); // Get the monster name (or "it") string m_name = monster_desc(0); // Get the "died from" information (i.e. "a kobold") string ddesc = monster_desc(Desc.SHOW | Desc.IND2); // Assume no blink bool blinked = false; // Scan through all blows for (int ap_cnt = 0; ap_cnt < Monster_Blow.MONSTER_BLOW_MAX; ap_cnt++) { bool visible = false; bool obvious = false; bool do_break = false; int power = 0; int damage = 0; string act = null; if (r_ptr.blow[ap_cnt] == null) { continue; } // Extract the attack infomation RBE effect = r_ptr.blow[ap_cnt].effect; RBM method = r_ptr.blow[ap_cnt].method; int d_dice = r_ptr.blow[ap_cnt].d_dice; int d_side = r_ptr.blow[ap_cnt].d_side; // Hack -- no more attacks if (method == RBM.NONE) { break; } // Handle "leaving" if (p.leaving) { break; } // Extract visibility (before blink) if (ml) { visible = true; } // Extract visibility from carrying light if (r_ptr.flags.has(Monster_Flag.HAS_LIGHT.value)) { visible = true; } power = effect.power; // Monster hits player if (effect == null || check_hit(p, power, rlev)) { // Always disturbing Cave.disturb(p, 1, 0); // Hack -- Apply "protection from evil" if (p.timed[(int)Timed_Effect.PROTEVIL] > 0) { // Learn about the evil flag if (ml) { l_ptr.flags.on(Monster_Flag.EVIL.value); } if (r_ptr.flags.has(Monster_Flag.EVIL.value) && p.lev >= rlev && Random.randint0(100) + p.lev > 50) { // Message Utilities.msg("%^s is repelled.", m_name); // Hack -- Next attack continue; } } // Assume no cut or stun bool do_cut = method.do_cut; bool do_stun = method.do_stun; // Assume no sound Message_Type sound_msg = method.sound_msg; act = method.action; // Describe the attack method if (method == RBM.INSULT) { throw new NotImplementedException(); //act = desc_insult[Random.randint0(Misc.MAX_DESC_INSULT)]; } else if (method == RBM.MOAN) { throw new NotImplementedException(); //act = desc_moan[Random.randint0(Misc.MAX_DESC_MOAN)]; } // Message if (act != null) { string name = Char.ToUpper(m_name[0]) + m_name.Substring(1); Utilities.msgt(sound_msg, "{0} {1}", name, act); } // Hack -- assume all attacks are obvious obvious = true; // Roll out the damage if (d_dice > 0 && d_side > 0) { damage = Random.damroll(d_dice, d_side); } else { damage = 0; } // Apply appropriate damage if (effect.value == 0) { // Hack -- Assume obvious obvious = true; // Hack -- No damage damage = 0; } else if (effect.value == RBE.HURT.value) { // Obvious obvious = true; // Hack -- Player armor reduces total damage damage -= (damage * ((ac < 240) ? ac : 240) / 400); // Take damage Spell.take_hit(p, damage, ddesc); } else if (effect.value == RBE.POISON.value) { damage = Spell.adjust_dam(p, GF.POIS, damage, aspect.RANDOMISE, Spell.check_for_resist(p, GF.POIS, p.state.flags, true)); // Take damage Spell.take_hit(p, damage, ddesc); // Take "poison" effect if (p.inc_timed(Timed_Effect.POISONED, Random.randint1(rlev) + 5, true, true)) { obvious = true; } // Learn about the player monster_learn_resists(p, GF.POIS); } else if (effect.value == RBE.UN_BONUS.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Allow complete resist //if (!check_state(p, OF_RES_DISEN, p.state.flags)) //{ // // Apply disenchantment // if (apply_disenchant(0)) obvious = true; //} //// Learn about the player //monster_learn_resists(m_ptr, p, GF_DISEN); } else if (effect.value == RBE.UN_POWER.value) { throw new NotImplementedException(); //int unpower = 0, newcharge; //// Take damage //take_hit(p, damage, ddesc); //// Find an item //for (k = 0; k < 10; k++) //{ // // Pick an item // i = randint0(INVEN_PACK); // // Obtain the item // o_ptr = &p.inventory[i]; // // Skip non-objects // if (!o_ptr.kind) continue; // // Drain charged wands/staves // if ((o_ptr.tval == TV_STAFF) || // (o_ptr.tval == TV_WAND)) // { // // Charged? // if (o_ptr.pval[DEFAULT_PVAL]) // { // // Get number of charge to drain // unpower = (rlev / (o_ptr.kind.level + 2)) + 1; // // Get new charge value, don't allow negative // newcharge = MAX((o_ptr.pval[DEFAULT_PVAL] // - unpower),0); // // Remove the charges // o_ptr.pval[DEFAULT_PVAL] = newcharge; // } // } // if (unpower) // { // int heal = rlev * unpower; // msg("Energy drains from your pack!"); // obvious = true; // // Don't heal more than max hp // heal = MIN(heal, maxhp - hp); // // Heal // hp += heal; // // Redraw (later) if needed // if (cave_monster(cave, p.health_who) == m_ptr) // p.redraw |= (PR_HEALTH); // // Combine / Reorder the pack // p.notice |= (PN_COMBINE | PN_REORDER); // // Redraw stuff // p.redraw |= (PR_INVEN); // // Affect only a single inventory slot // break; // } //} } else if (effect.value == RBE.EAT_GOLD.value) { // Take damage Spell.take_hit(p, damage, ddesc); // Obvious obvious = true; // Saving throw (unless paralyzed) based on dex and level if (p.timed[(int)Timed_Effect.PARALYZED] == 0 && (Random.randint0(100) < (Player.Player.adj_dex_safe[p.state.stat_ind[(int)Stat.Dex]] + p.lev))) { // Saving throw message Utilities.msg("You quickly protect your money pouch!"); // Occasional blink anyway if (Random.randint0(3) != 0) { blinked = true; } } // Eat gold else { gold = (short)((p.au / 10) + Random.randint1(25)); if (gold < 2) { gold = 2; } if (gold > 5000) { gold = (short)((p.au / 20) + Random.randint1(3000)); } if (gold > p.au) { gold = (short)p.au; } p.au -= gold; if (gold <= 0) { Utilities.msg("Nothing was stolen."); break; } // Let the player know they were robbed Utilities.msg("Your purse feels lighter."); if (p.au != 0) { Utilities.msg("{0} coins were stolen!", (long)gold); } else { Utilities.msg("All of your coins were stolen!"); } // While we have gold, put it in objects while (gold > 0) { int amt; // Create a new temporary object //object_type o; //object_wipe(&o); Object.Object o = new Object.Object(); o.prep(Object.Object_Kind.objkind_get(TVal.TV_GOLD, (int)SVal.sval_gold.SV_GOLD), 0, aspect.MINIMISE); // Amount of gold to put in this object amt = gold > Misc.MAX_PVAL ? Misc.MAX_PVAL : gold; o.pval[Misc.DEFAULT_PVAL] = (short)amt; gold -= (short)amt; // Set origin to stolen, so it is not confused with // dropped treasure in monster_death o.origin = Origin.STOLEN; // Give the gold to the monster carry(o); } // Redraw gold p.redraw |= (Misc.PR_GOLD); // Blink away blinked = true; } } else if (effect.value == RBE.EAT_ITEM.value) { // Take damage Spell.take_hit(p, damage, ddesc); // Saving throw (unless paralyzed) based on dex and level if (p.timed[(int)Timed_Effect.PARALYZED] == 0 && (Random.randint0(100) < (Player.Player.adj_dex_safe[p.state.stat_ind[(int)Stat.Dex]] + p.lev))) { // Saving throw message Utilities.msg("You grab hold of your backpack!"); // Occasional "blink" anyway blinked = true; // Obvious obvious = true; // Done break; } // Find an item for (int k = 0; k < 10; k++) { Object.Object i_ptr; //object_type object_type_body; // Pick an item i = Random.randint0(Misc.INVEN_PACK); // Obtain the item Object.Object o_ptr = p.inventory[i]; // Skip non-objects if (o_ptr.kind == null) { continue; } // Skip artifacts if (o_ptr.artifact != null) { continue; } // Get a description o_name = o_ptr.object_desc(Object.Object.Detail.FULL); // Message Utilities.msg("{0}our {1} ({2}) was stolen!", ((o_ptr.number > 1) ? "One of y" : "Y"), o_name, Object.Object.index_to_label(i)); // Get local object i_ptr = new Object.Object(); //&object_type_body; // Obtain local object i_ptr = o_ptr.copy(); // Modify number i_ptr.number = 1; // Hack -- If a rod, staff, or wand, allocate total // maximum timeouts or charges between those // stolen and those missed. -LM- o_ptr.distribute_charges(i_ptr, 1); // Carry the object carry(i_ptr); // Steal the items Object.Object.inven_item_increase(i, -1); Object.Object.inven_item_optimize(i); // Obvious obvious = true; // Blink away blinked = true; // Done break; } } else if (effect.value == RBE.EAT_FOOD.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Steal some food //for (k = 0; k < 10; k++) //{ // // Pick an item from the pack // i = randint0(INVEN_PACK); // // Get the item // o_ptr = &p.inventory[i]; // // Skip non-objects // if (!o_ptr.kind) continue; // // Skip non-food objects // if (o_ptr.tval != TV_FOOD) continue; // // Get a description // object_desc(o_name, sizeof(o_name), o_ptr, // ODESC_PREFIX | ODESC_BASE); // // Message // msg("%sour %s (%c) was eaten!", // ((o_ptr.number > 1) ? "One of y" : "Y"), // o_name, index_to_label(i)); // // Steal the items // inven_item_increase(i, -1); // inven_item_optimize(i); // // Obvious // obvious = true; // // Done // break; //} } else if (effect.value == RBE.EAT_LIGHT.value) { throw new NotImplementedException(); //bitflag f[OF_SIZE]; //// Take damage //take_hit(p, damage, ddesc); //// Get the light, and its flags //o_ptr = &p.inventory[INVEN_LIGHT]; //object_flags(o_ptr, f); //// Drain fuel where applicable //if (!of_has(f, OF_NO_FUEL) && (o_ptr.timeout > 0)) //{ // // Reduce fuel // o_ptr.timeout -= (250 + randint1(250)); // if (o_ptr.timeout < 1) o_ptr.timeout = 1; // // Notice // if (!p.timed[TMD_BLIND]) // { // msg("Your light dims."); // obvious = true; // } // // Redraw stuff // p.redraw |= (PR_EQUIP); //} } else if (effect.value == RBE.ACID.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Message //msg("You are covered in acid!"); //// Special damage //damage = adjust_dam(p, GF_ACID, damage, RANDOMISE, // check_for_resist(p, GF_ACID, p.state.flags, true)); //if (damage) { // take_hit(p, damage, ddesc); // inven_damage(p, GF_ACID, MIN(damage * 5, 300)); //} //// Learn about the player //monster_learn_resists(m_ptr, p, GF_ACID); } else if (effect.value == RBE.ELEC.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Message //msg("You are struck by electricity!"); //// Take damage (special) //damage = adjust_dam(p, GF_ELEC, damage, RANDOMISE, // check_for_resist(p, GF_ELEC, p.state.flags, true)); //if (damage) { // take_hit(p, damage, ddesc); // inven_damage(p, GF_ELEC, MIN(damage * 5, 300)); //} //// Learn about the player //monster_learn_resists(m_ptr, p, GF_ELEC); } else if (effect.value == RBE.FIRE.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Message //msg("You are enveloped in flames!"); //// Take damage (special) //damage = adjust_dam(p, GF_FIRE, damage, RANDOMISE, // check_for_resist(p, GF_FIRE, p.state.flags, true)); //if (damage) { // take_hit(p, damage, ddesc); // inven_damage(p, GF_FIRE, MIN(damage * 5, 300)); //} //// Learn about the player //monster_learn_resists(m_ptr, p, GF_FIRE); } else if (effect.value == RBE.COLD.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Message //msg("You are covered with frost!"); //// Take damage (special) //damage = adjust_dam(p, GF_COLD, damage, RANDOMISE, // check_for_resist(p, GF_COLD, p.state.flags, true)); //if (damage) { // take_hit(p, damage, ddesc); // inven_damage(p, GF_COLD, MIN(damage * 5, 300)); //} //// Learn about the player //monster_learn_resists(m_ptr, p, GF_COLD); } else if (effect.value == RBE.BLIND.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Increase "blind" //if (player_inc_timed(p, TMD_BLIND, 10 + randint1(rlev), true, true)) // obvious = true; //// Learn about the player //update_smart_learn(m_ptr, p, OF_RES_BLIND); } else if (effect.value == RBE.CONFUSE.value) { // Take damage Spell.take_hit(p, damage, ddesc); // Increase "confused" if (p.inc_timed(Timed_Effect.CONFUSED, 3 + Random.randint1(rlev), true, true)) { obvious = true; } // Learn about the player update_smart_learn(p, Object.Object_Flag.RES_CONFU); } else if (effect.value == RBE.TERRIFY.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Increase "afraid" //if (randint0(100) < p.state.skills[SKILL_SAVE]) //{ // msg("You stand your ground!"); // obvious = true; //} //else //{ // if (player_inc_timed(p, TMD_AFRAID, 3 + randint1(rlev), true, // true)) // obvious = true; //} //// Learn about the player //update_smart_learn(m_ptr, p, OF_RES_FEAR); } else if (effect.value == RBE.PARALYZE.value) { // Hack -- Prevent perma-paralysis via damage if (p.timed[(int)Timed_Effect.PARALYZED] != 0 && (damage < 1)) { damage = 1; } // Take damage Spell.take_hit(p, damage, ddesc); // Increase "paralyzed" if (Random.randint0(100) < p.state.skills[(int)Skill.SAVE]) { Utilities.msg("You resist the effects!"); obvious = true; } else { if (p.inc_timed(Timed_Effect.PARALYZED, 3 + Random.randint1(rlev), true, true)) { obvious = true; } } // Learn about the player update_smart_learn(p, Object_Flag.FREE_ACT); } else if (effect.value == RBE.LOSE_STR.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_STR, false)) obvious = true; } else if (effect.value == RBE.LOSE_INT.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_INT, false)) obvious = true; } else if (effect.value == RBE.LOSE_WIS.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_WIS, false)) obvious = true; } else if (effect.value == RBE.LOSE_DEX.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_DEX, false)) obvious = true; } else if (effect.value == RBE.LOSE_CON.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_CON, false)) obvious = true; } else if (effect.value == RBE.LOSE_CHR.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stat) //if (do_dec_stat(A_CHR, false)) obvious = true; } else if (effect.value == RBE.LOSE_ALL.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Damage (stats) //if (do_dec_stat(A_STR, false)) obvious = true; //if (do_dec_stat(A_DEX, false)) obvious = true; //if (do_dec_stat(A_CON, false)) obvious = true; //if (do_dec_stat(A_INT, false)) obvious = true; //if (do_dec_stat(A_WIS, false)) obvious = true; //if (do_dec_stat(A_CHR, false)) obvious = true; } else if (effect.value == RBE.SHATTER.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Hack -- Reduce damage based on the player armor class //damage -= (damage * ((ac < 240) ? ac : 240) / 400); //// Take damage //take_hit(p, damage, ddesc); //// Radius 8 earthquake centered at the monster //if (damage > 23) //{ // int px_old = p.px; // int py_old = p.py; // earthquake(fy, fx, 8); // // Stop the blows if the player is pushed away // if ((px_old != p.px) || // (py_old != p.py)) // do_break = true; //} } else if (effect.value == RBE.EXP_10.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Take damage //take_hit(p, damage, ddesc); //update_smart_learn(m_ptr, p_ptr, OF_HOLD_LIFE); //if (check_state(p, OF_HOLD_LIFE, p.state.flags) && (randint0(100) < 95)) //{ // msg("You keep hold of your life force!"); //} //else //{ // s32b d = damroll(10, 6) + (p.exp/100) * MON_DRAIN_LIFE; // if (check_state(p, OF_HOLD_LIFE, p.state.flags)) // { // msg("You feel your life slipping away!"); // player_exp_lose(p, d / 10, false); // } // else // { // msg("You feel your life draining away!"); // player_exp_lose(p, d, false); // } //} } else if (effect.value == RBE.EXP_20.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Take damage //take_hit(p, damage, ddesc); //update_smart_learn(m_ptr, p_ptr, OF_HOLD_LIFE); //if (check_state(p, OF_HOLD_LIFE, p.state.flags) && (randint0(100) < 90)) //{ // msg("You keep hold of your life force!"); //} //else //{ // s32b d = damroll(20, 6) + (p.exp / 100) * MON_DRAIN_LIFE; // if (check_state(p, OF_HOLD_LIFE, p.state.flags)) // { // msg("You feel your life slipping away!"); // player_exp_lose(p, d / 10, false); // } // else // { // msg("You feel your life draining away!"); // player_exp_lose(p, d, false); // } //} } else if (effect.value == RBE.EXP_40.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Take damage //take_hit(p, damage, ddesc); //update_smart_learn(m_ptr, p_ptr, OF_HOLD_LIFE); //if (check_state(p, OF_HOLD_LIFE, p.state.flags) && (randint0(100) < 75)) //{ // msg("You keep hold of your life force!"); //} //else //{ // s32b d = damroll(40, 6) + (p.exp / 100) * MON_DRAIN_LIFE; // if (check_state(p, OF_HOLD_LIFE, p.state.flags)) // { // msg("You feel your life slipping away!"); // player_exp_lose(p, d / 10, false); // } // else // { // msg("You feel your life draining away!"); // player_exp_lose(p, d, false); // } //} } else if (effect.value == RBE.EXP_80.value) { throw new NotImplementedException(); //// Obvious //obvious = true; //// Take damage //take_hit(p, damage, ddesc); //update_smart_learn(m_ptr, p_ptr, OF_HOLD_LIFE); //if (check_state(p, OF_HOLD_LIFE, p.state.flags) && (randint0(100) < 50)) //{ // msg("You keep hold of your life force!"); //} //else //{ // s32b d = damroll(80, 6) + (p.exp / 100) * MON_DRAIN_LIFE; // if (check_state(p, OF_HOLD_LIFE, p.state.flags)) // { // msg("You feel your life slipping away!"); // player_exp_lose(p, d / 10, false); // } // else // { // msg("You feel your life draining away!"); // player_exp_lose(p, d, false); // } //} } else if (effect.value == RBE.HALLU.value) { throw new NotImplementedException(); //// Take damage //take_hit(p, damage, ddesc); //// Increase "image" //if (player_inc_timed(p, TMD_IMAGE, 3 + randint1(rlev / 2), true, true)) // obvious = true; //// Learn about the player //monster_learn_resists(m_ptr, p, GF_CHAOS); } else { throw new NotImplementedException(); } // Hack -- only one of cut or stun if (do_cut && do_stun) { // Cancel cut if (Random.randint0(100) < 50) { do_cut = false; } // Cancel stun else { do_stun = false; } } // Handle cut if (do_cut) { int k = 0; // Critical hit (zero if non-critical) tmp = monster_critical(d_dice, d_side, damage); // Roll for damage switch (tmp) { case 0: k = 0; break; case 1: k = Random.randint1(5); break; case 2: k = Random.randint1(5) + 5; break; case 3: k = Random.randint1(20) + 20; break; case 4: k = Random.randint1(50) + 50; break; case 5: k = Random.randint1(100) + 100; break; case 6: k = 300; break; default: k = 500; break; } // Apply the cut if (k != 0) { p.inc_timed(Timed_Effect.CUT, k, true, true); } } // Handle stun if (do_stun) { int k; // Critical hit (zero if non-critical) tmp = monster_critical(d_dice, d_side, damage); // Roll for damage switch (tmp) { case 0: k = 0; break; case 1: k = Random.randint1(5); break; case 2: k = Random.randint1(10) + 10; break; case 3: k = Random.randint1(20) + 20; break; case 4: k = Random.randint1(30) + 30; break; case 5: k = Random.randint1(40) + 40; break; case 6: k = 100; break; default: k = 200; break; } // Apply the stun if (k != 0) { p.inc_timed(Timed_Effect.STUN, k, true, true); } } } // Monster missed player else { // Analyze failed attacks if (method == RBM.HIT || method == RBM.TOUCH || method == RBM.PUNCH || method == RBM.KICK || method == RBM.CLAW || method == RBM.BITE || method == RBM.STING || method == RBM.BUTT || method == RBM.CRUSH || method == RBM.ENGULF) { // Visible monsters if (ml) { // Disturbing Cave.disturb(p, 1, 0); // Message //Utilities.msg("%^s misses you.", m_name); Utilities.msg("{0} misses you.", m_name); } } } // Analyze "visible" monsters only if (visible) { // Count "obvious" attacks (and ones that cause damage) if (obvious || damage != 0 || (l_ptr.blows[ap_cnt] > 10)) { // Count attacks of this type if (l_ptr.blows[ap_cnt] < byte.MaxValue) { l_ptr.blows[ap_cnt]++; } } } // Skip the other blows if necessary if (do_break) { break; } } // Blink away if (blinked) { Utilities.msg("There is a puff of smoke!"); throw new NotImplementedException(); //teleport_away(m_ptr, MAX_SIGHT * 2 + 5); } // Always notice cause of death if (p.is_dead && (l_ptr.deaths < short.MaxValue)) { l_ptr.deaths++; } // Assume we attacked // Nick: Because, based on the length of this function, // literally a million other things could have happened return(true); }
static long max_dam(Monster_Race r_ptr) { int rlev, i; int melee_dam = 0, atk_dam = 0, spell_dam = 0; int dam = 1; /* Extract the monster level, force 1 for town monsters */ rlev = ((r_ptr.level >= 1) ? r_ptr.level : 1); /* Assume single resist for the elemental attacks */ spell_dam = Monster_Spell_Flag.best_spell_power(r_ptr, 1); /* Hack - Apply over 10 rounds */ spell_dam *= 10; /* Scale for frequency and availability of mana / ammo */ if (spell_dam != 0) { int freq = r_ptr.freq_spell; /* Hack -- always get 1 shot */ if (freq < 10) { freq = 10; } /* Adjust for frequency */ spell_dam = spell_dam * freq / 100; } /* Check attacks */ for (i = 0; i < 4; i++) { if (r_ptr.blow[i] == null) { continue; } /* Extract the attack infomation */ Monster_Blow.RBE effect = r_ptr.blow[i].effect; Monster_Blow.RBM method = r_ptr.blow[i].method; int d_dice = r_ptr.blow[i].d_dice; int d_side = r_ptr.blow[i].d_side; /* Hack -- no more attacks */ if (method == null) { continue; } /* Assume maximum damage*/ atk_dam = (int)blow_effect(effect, d_dice * d_side, r_ptr.level); /*stun definitely most dangerous*/ if (method == Monster_Blow.RBM.PUNCH || method == Monster_Blow.RBM.KICK || method == Monster_Blow.RBM.BUTT || method == Monster_Blow.RBM.CRUSH) { atk_dam *= 4; atk_dam /= 3; } else if (method == Monster_Blow.RBM.CLAW || method == Monster_Blow.RBM.BITE) { atk_dam *= 7; atk_dam /= 5; } /* Normal melee attack */ if (!r_ptr.flags.has(Monster_Flag.NEVER_BLOW.value)) { /* Keep a running total */ melee_dam += atk_dam; } } /* * Apply damage over 10 rounds. We assume that the monster has to make contact first. * Hack - speed has more impact on melee as has to stay in contact with player. * Hack - this is except for pass wall and kill wall monsters which can always get to the player. * Hack - use different values for huge monsters as they strike out to range 2. */ if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) { melee_dam *= 10; } else { melee_dam = melee_dam * 3 + melee_dam * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 7; } /* * Scale based on attack accuracy. We make a massive number of assumptions here and just use monster level. */ melee_dam = melee_dam * Math.Min(45 + rlev * 3, 95) / 100; /* Hack -- Monsters that multiply ignore the following reductions */ if (!r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { /*Reduce damamge potential for monsters that move randomly */ if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.RAND_25.value, Monster_Flag.RAND_50.value)) { int reduce = 100; if (r_ptr.flags.has(Monster_Flag.RAND_25.value)) { reduce -= 25; } if (r_ptr.flags.has(Monster_Flag.RAND_50.value)) { reduce -= 50; } /*even moving randomly one in 8 times will hit the player*/ reduce += (100 - reduce) / 8; /* adjust the melee damage*/ melee_dam = (melee_dam * reduce) / 100; } /*monsters who can't move aren't nearly as much of a combat threat*/ if (r_ptr.flags.has(Monster_Flag.NEVER_MOVE.value)) { if (r_ptr.spell_flags.has(Monster_Spell_Flag.TELE_TO.value) || r_ptr.spell_flags.has(Monster_Spell_Flag.BLINK.value)) { /* Scale for frequency */ melee_dam = melee_dam / 5 + 4 * melee_dam * r_ptr.freq_spell / 500; /* Incorporate spell failure chance */ if (!r_ptr.flags.has(Monster_Flag.STUPID.value)) { melee_dam = melee_dam / 5 + 4 * melee_dam * Math.Min(75 + (rlev + 3) / 4, 100) / 500; } } else if (r_ptr.flags.has(Monster_Flag.INVISIBLE.value)) { melee_dam /= 3; } else { melee_dam /= 5; } } } /* But keep at a minimum */ if (melee_dam < 1) { melee_dam = 1; } /* * Combine spell and melee damage */ dam = (spell_dam + melee_dam); r_ptr.highest_threat = (short)dam; r_ptr.spell_dam = spell_dam; /*AMF:DEBUG*/ r_ptr.melee_dam = melee_dam; /*AMF:DEBUG*/ /* * Adjust for speed. Monster at speed 120 will do double damage, * monster at speed 100 will do half, etc. Bonus for monsters who can haste self. */ dam = (dam * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) / 10; /* * Adjust threat for speed -- multipliers are more threatening. */ if (r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { r_ptr.highest_threat = (short)((r_ptr.highest_threat * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) / 5); } /* * Adjust threat for friends. */ if (r_ptr.flags.has(Monster_Flag.FRIENDS.value)) { r_ptr.highest_threat *= 2; } else if (r_ptr.flags.has(Monster_Flag.FRIEND.value)) { r_ptr.highest_threat = (short)(r_ptr.highest_threat * 3 / 2); } /*but deep in a minimum*/ if (dam < 1) { dam = 1; } /* We're done */ return(dam); }
/* Parsing functions for monster.txt */ public static Parser.Error parse_r_n(Parser p) { Monster_Race h = p.priv as Monster_Race; Monster_Race r = new Monster_Race(); r.Next = h; r.ridx = p.getuint("index"); r.Name = p.getstr("name"); p.priv = r; return Parser.Error.NONE; }
static long hp_adjust(Monster_Race r_ptr) { long hp; int resists = 1; int hide_bonus = 0; /* Get the monster base hitpoints */ hp = r_ptr.avg_hp; /* Never moves with no ranged attacks - high hit points count for less */ if (r_ptr.flags.has(Monster_Flag.NEVER_MOVE.value) && !(r_ptr.freq_innate != 0 || r_ptr.freq_spell != 0)) { hp /= 2; if (hp < 1) { hp = 1; } } /* Just assume healers have more staying power */ if (r_ptr.spell_flags.has(Monster_Spell_Flag.HEAL.value)) { hp = (hp * 6) / 5; } /* Miscellaneous improvements */ if (r_ptr.flags.has(Monster_Flag.REGENERATE.value)) { hp *= 10; hp /= 9; } if (r_ptr.flags.has(Monster_Flag.PASS_WALL.value)) { hp *= 3; hp /= 2; } /* Calculate hide bonus */ if (r_ptr.flags.has(Monster_Flag.EMPTY_MIND.value)) { hide_bonus += 2; } else { if (r_ptr.flags.has(Monster_Flag.COLD_BLOOD.value)) { hide_bonus += 1; } if (r_ptr.flags.has(Monster_Flag.WEIRD_MIND.value)) { hide_bonus += 1; } } /* Invisibility */ if (r_ptr.flags.has(Monster_Flag.INVISIBLE.value)) { hp = (hp * (r_ptr.level + hide_bonus + 1)) / Math.Max(1, (int)r_ptr.level); } /* Monsters that can teleport are a hassle, and can easily run away */ if (r_ptr.spell_flags.test(Monster_Spell_Flag.SIZE, Monster_Spell_Flag.TPORT.value, Monster_Spell_Flag.TELE_AWAY.value, Monster_Spell_Flag.TELE_LEVEL.value)) { hp = (hp * 6) / 5; } /* * Monsters that multiply are tougher to kill */ if (r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { hp *= 2; } /* Monsters with resistances are harder to kill. * Therefore effective slays / brands against them are worth more. */ if (r_ptr.flags.has(Monster_Flag.IM_ACID.value)) { resists += 2; } if (r_ptr.flags.has(Monster_Flag.IM_FIRE.value)) { resists += 2; } if (r_ptr.flags.has(Monster_Flag.IM_COLD.value)) { resists += 2; } if (r_ptr.flags.has(Monster_Flag.IM_ELEC.value)) { resists += 2; } if (r_ptr.flags.has(Monster_Flag.IM_POIS.value)) { resists += 2; } /* Bonus for multiple basic resists and weapon resists */ if (resists >= 12) { resists *= 6; } else if (resists >= 10) { resists *= 4; } else if (resists >= 8) { resists *= 3; } else if (resists >= 6) { resists *= 2; } /* If quite resistant, reduce resists by defense holes */ if (resists >= 6) { if (r_ptr.flags.has(Monster_Flag.HURT_ROCK.value)) { resists -= 1; } if (r_ptr.flags.has(Monster_Flag.HURT_LIGHT.value)) { resists -= 1; } if (!r_ptr.flags.has(Monster_Flag.NO_SLEEP.value)) { resists -= 3; } if (!r_ptr.flags.has(Monster_Flag.NO_FEAR.value)) { resists -= 2; } if (!r_ptr.flags.has(Monster_Flag.NO_CONF.value)) { resists -= 2; } if (!r_ptr.flags.has(Monster_Flag.NO_STUN.value)) { resists -= 1; } if (resists < 5) { resists = 5; } } /* If quite resistant, bonus for high resists */ if (resists >= 3) { if (r_ptr.flags.has(Monster_Flag.IM_WATER.value)) { resists += 1; } if (r_ptr.flags.has(Monster_Flag.RES_NETH.value)) { resists += 1; } if (r_ptr.flags.has(Monster_Flag.RES_NEXUS.value)) { resists += 1; } if (r_ptr.flags.has(Monster_Flag.RES_DISE.value)) { resists += 1; } } /* Scale resists */ resists = resists * 25; /* Monster resistances */ if (resists < (r_ptr.ac + resists) / 3) { hp += (hp * resists) / (150 + r_ptr.level); } else { hp += (hp * (r_ptr.ac + resists) / 3) / (150 + r_ptr.level); } /*boundry control*/ if (hp < 1) { hp = 1; } return(hp); }
public static int r_power(Monster_Race[] races) { //God damn it C... /* * ang_file *mon_fp; * char buf[1024];*/ bool dump = false; /* Allocate space for power */ long[] power = new long[Misc.z_info.r_max]; long[] tot_hp = new long[Misc.MAX_DEPTH]; long[] tot_dam = new long[Misc.MAX_DEPTH]; long[] mon_count = new long[Misc.MAX_DEPTH]; for (int iteration = 0; iteration < 3; iteration++) { /* Reset the sum of all monster power values */ tot_mon_power = 0; /* Make sure all arrays start at zero */ for (int i = 0; i < Misc.MAX_DEPTH; i++) { tot_hp[i] = 0; tot_dam[i] = 0; mon_count[i] = 0; } /* Go through r_info and evaluate power ratings & flows. */ for (int i = 0; i < Misc.z_info.r_max; i++) { /* Point at the "info" */ Monster_Race r_ptr = races[i]; if (r_ptr == null) { continue; } /* Set the current level */ byte lvl = r_ptr.level; /* Maximum damage this monster can do in 10 game turns */ long dam = max_dam(r_ptr); /* Adjust hit points based on resistances */ long hp = hp_adjust(r_ptr); /* Hack -- set exp */ if (lvl == 0) { r_ptr.mexp = 0; } else { /* Compute depths of non-unique monsters */ if (r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { long mexp = (hp * dam) / 25; long threat = r_ptr.highest_threat; /* Compute level algorithmically */ int j; for (j = 1; (mexp > j + 4) || (threat > j + 5); mexp -= j * j, threat -= (j + 4), j++) { ; } /* Set level */ lvl = (byte)Math.Min((j > 250 ? 90 + (j - 250) / 20 : /* Level 90+ */ (j > 130 ? 70 + (j - 130) / 6 : /* Level 70+ */ (j > 40 ? 40 + (j - 40) / 3 : /* Level 40+ */ j))), 99); /* Set level */ if (Misc.arg_rebalance) { r_ptr.level = lvl; } } if (Misc.arg_rebalance) { /* Hack -- for Ungoliant */ if (hp > 10000) { r_ptr.mexp = (int)((hp / 25) * (dam / lvl)); } else { r_ptr.mexp = (int)((hp * dam) / (lvl * 25)); } /* Round to 2 significant figures */ if (r_ptr.mexp > 100) { if (r_ptr.mexp < 1000) { r_ptr.mexp = (r_ptr.mexp + 5) / 10; r_ptr.mexp *= 10; } else if (r_ptr.mexp < 10000) { r_ptr.mexp = (r_ptr.mexp + 50) / 100; r_ptr.mexp *= 100; } else if (r_ptr.mexp < 100000) { r_ptr.mexp = (r_ptr.mexp + 500) / 1000; r_ptr.mexp *= 1000; } else if (r_ptr.mexp < 1000000) { r_ptr.mexp = (r_ptr.mexp + 5000) / 10000; r_ptr.mexp *= 10000; } else if (r_ptr.mexp < 10000000) { r_ptr.mexp = (r_ptr.mexp + 50000) / 100000; r_ptr.mexp *= 100000; } } } } /* If we're rebalancing, this is a nop, if not, we restore the orig value */ lvl = r_ptr.level; if ((lvl != 0) && (r_ptr.mexp < 1L)) { r_ptr.mexp = 1; } /* * Hack - We have to use an adjustment factor to prevent overflow. * Try to scale evenly across all levels instead of scaling by level. */ hp /= 2; if (hp < 1) { hp = 1; } r_ptr.hp = hp; /*AMF:DEBUG*/ /* Define the power rating */ power[i] = hp * dam; /* Adjust for group monsters. Average in-level group size is 5 */ if (!r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { if (r_ptr.flags.has(Monster_Flag.FRIEND.value)) { power[i] *= 2; } else if (r_ptr.flags.has(Monster_Flag.FRIENDS.value)) { power[i] *= 5; } } /* Adjust for escorts */ if (r_ptr.flags.has(Monster_Flag.ESCORTS.value)) { power[i] *= 3; } if (r_ptr.flags.has(Monster_Flag.ESCORT.value) && !r_ptr.flags.has(Monster_Flag.ESCORTS.value)) { power[i] *= 2; } /* Adjust for multiplying monsters. This is modified by the speed, * as fast multipliers are much worse than slow ones. We also adjust for * ability to bypass walls or doors. */ if (r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) { power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]); } else if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.OPEN_DOOR.value, Monster_Flag.BASH_DOOR.value)) { power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] * 3 / 2); } else { power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 2); } } /* * Update the running totals - these will be used as divisors later * Total HP / dam / count for everything up to the current level */ for (int j = lvl; j < (lvl == 0 ? lvl + 1 : Misc.MAX_DEPTH); j++) { int count = 10; /* Uniques don't count towards monster power on the level. */ if (r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { continue; } /* Specifically placed monsters don't count towards monster power * on the level. */ if (!(r_ptr.rarity != 0)) { continue; } /* Hack -- provide adjustment factor to prevent overflow */ if ((j == 90) && (r_ptr.level < 90)) { hp /= 10; dam /= 10; } if ((j == 65) && (r_ptr.level < 65)) { hp /= 10; dam /= 10; } if ((j == 40) && (r_ptr.level < 40)) { hp /= 10; dam /= 10; } /* * Hack - if it's a group monster or multiplying monster, add several to the count * so that the averages don't get thrown off */ if (r_ptr.flags.has(Monster_Flag.FRIEND.value)) { count = 20; } else if (r_ptr.flags.has(Monster_Flag.FRIENDS.value)) { count = 50; } if (r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) { count = Math.Max(1, (int)Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) * count; } else if (r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.OPEN_DOOR.value, Monster_Flag.BASH_DOOR.value)) { count = Math.Max(1, Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] * 3 / 2) * count; } else { count = Math.Max(1, Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 2) * count; } } /* Very rare monsters count less towards total monster power on the * level. */ if (r_ptr.rarity > count) { hp = hp * count / r_ptr.rarity; dam = dam * count / r_ptr.rarity; count = r_ptr.rarity; } tot_hp[j] += hp; tot_dam[j] += dam; mon_count[j] += count / r_ptr.rarity; } } /* Apply divisors now */ for (int i = 0; i < Misc.z_info.r_max; i++) { int new_power; /* Point at the "info" */ Monster_Race r_ptr = races[i]; if (r_ptr == null) { continue; } /* Extract level */ byte lvl = r_ptr.level; /* Paranoia */ if (tot_hp[lvl] != 0 && tot_dam[lvl] != 0) { /* Divide by average HP and av damage for all in-level monsters */ /* Note we have factored in the above 'adjustment factor' */ long av_hp = tot_hp[lvl] * 10 / mon_count[lvl]; long av_dam = tot_dam[lvl] * 10 / mon_count[lvl]; /* Assign monster power */ r_ptr.power = power[i]; /* Justifiable paranoia - avoid divide by zero errors */ if (av_hp > 0) { power[i] = power[i] / av_hp; } if (av_dam > 0) { power[i] = power[i] / av_dam; } /* Assign monster scaled power */ r_ptr.scaled_power = power[i]; /* Never less than 1 */ if (r_ptr.power < 1) { r_ptr.power = 1; } /* Get power */ new_power = (int)r_ptr.power; /* Compute rarity algorithmically */ int j; for (j = 1; new_power > j; new_power -= j * j, j++) { ; } /* Set rarity */ if (Misc.arg_rebalance) { r_ptr.rarity = (byte)j; } } } } /* Determine total monster power */ for (int i = 0; i < Misc.z_info.r_max; i++) { Monster_Race r = Misc.r_info[i]; if (r == null) { continue; } tot_mon_power += (int)r.scaled_power; } /* msg("Tot mon power is %d", tot_mon_power); */ if (dump) { /* dump the power details */ throw new NotImplementedException(); //path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "mon_power.txt"); //mon_fp = file_open(buf, MODE_WRITE, FTYPE_TEXT); //file_putf(mon_fp, "ridx|level|rarity|d_char|name|pwr|scaled|melee|spell|hp\n"); //for (i = 0; i < z_info.r_max; i++) { // r_ptr = &r_info[i]; // /* Don't print anything for nonexistent monsters */ // if (!r_ptr.name) continue; // file_putf(mon_fp, "%d|%d|%d|%c|%s|%d|%d|%d|%d|%d\n", r_ptr.ridx, // r_ptr.level, r_ptr.rarity, r_ptr.d_char, r_ptr.name, // r_ptr.power, r_ptr.scaled_power, r_ptr.melee_dam, // r_ptr.spell_dam, r_ptr.hp); //} //file_close(mon_fp); } /* Free power array */ //FREE(power); /* Success */ return(0); }
static long max_dam(Monster_Race r_ptr) { int rlev, i; int melee_dam = 0, atk_dam = 0, spell_dam = 0; int dam = 1; /* Extract the monster level, force 1 for town monsters */ rlev = ((r_ptr.level >= 1) ? r_ptr.level : 1); /* Assume single resist for the elemental attacks */ spell_dam = Monster_Spell_Flag.best_spell_power(r_ptr, 1); /* Hack - Apply over 10 rounds */ spell_dam *= 10; /* Scale for frequency and availability of mana / ammo */ if(spell_dam != 0) { int freq = r_ptr.freq_spell; /* Hack -- always get 1 shot */ if(freq < 10) freq = 10; /* Adjust for frequency */ spell_dam = spell_dam * freq / 100; } /* Check attacks */ for(i = 0; i < 4; i++) { if (r_ptr.blow[i] == null) continue; /* Extract the attack infomation */ Monster_Blow.RBE effect = r_ptr.blow[i].effect; Monster_Blow.RBM method = r_ptr.blow[i].method; int d_dice = r_ptr.blow[i].d_dice; int d_side = r_ptr.blow[i].d_side; /* Hack -- no more attacks */ if(method == null) continue; /* Assume maximum damage*/ atk_dam = (int)blow_effect(effect, d_dice * d_side, r_ptr.level); /*stun definitely most dangerous*/ if(method == Monster_Blow.RBM.PUNCH || method == Monster_Blow.RBM.KICK || method == Monster_Blow.RBM.BUTT || method == Monster_Blow.RBM.CRUSH) { atk_dam *= 4; atk_dam /= 3; } else if(method == Monster_Blow.RBM.CLAW || method == Monster_Blow.RBM.BITE) { atk_dam *= 7; atk_dam /= 5; } /* Normal melee attack */ if(!r_ptr.flags.has(Monster_Flag.NEVER_BLOW.value)) { /* Keep a running total */ melee_dam += atk_dam; } } /* * Apply damage over 10 rounds. We assume that the monster has to make contact first. * Hack - speed has more impact on melee as has to stay in contact with player. * Hack - this is except for pass wall and kill wall monsters which can always get to the player. * Hack - use different values for huge monsters as they strike out to range 2. */ if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) melee_dam *= 10; else { melee_dam = melee_dam * 3 + melee_dam * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 7; } /* * Scale based on attack accuracy. We make a massive number of assumptions here and just use monster level. */ melee_dam = melee_dam * Math.Min(45 + rlev * 3, 95) / 100; /* Hack -- Monsters that multiply ignore the following reductions */ if(!r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { /*Reduce damamge potential for monsters that move randomly */ if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.RAND_25.value, Monster_Flag.RAND_50.value)) { int reduce = 100; if(r_ptr.flags.has(Monster_Flag.RAND_25.value)) reduce -= 25; if(r_ptr.flags.has(Monster_Flag.RAND_50.value)) reduce -= 50; /*even moving randomly one in 8 times will hit the player*/ reduce += (100 - reduce) / 8; /* adjust the melee damage*/ melee_dam = (melee_dam * reduce) / 100; } /*monsters who can't move aren't nearly as much of a combat threat*/ if(r_ptr.flags.has(Monster_Flag.NEVER_MOVE.value)) { if(r_ptr.spell_flags.has(Monster_Spell_Flag.TELE_TO.value) || r_ptr.spell_flags.has(Monster_Spell_Flag.BLINK.value)) { /* Scale for frequency */ melee_dam = melee_dam / 5 + 4 * melee_dam * r_ptr.freq_spell / 500; /* Incorporate spell failure chance */ if(!r_ptr.flags.has(Monster_Flag.STUPID.value)) melee_dam = melee_dam / 5 + 4 * melee_dam * Math.Min(75 + (rlev + 3) / 4, 100) / 500; } else if(r_ptr.flags.has(Monster_Flag.INVISIBLE.value)) melee_dam /= 3; else melee_dam /= 5; } } /* But keep at a minimum */ if(melee_dam < 1) melee_dam = 1; /* * Combine spell and melee damage */ dam = (spell_dam + melee_dam); r_ptr.highest_threat = (short)dam; r_ptr.spell_dam = spell_dam; /*AMF:DEBUG*/ r_ptr.melee_dam = melee_dam; /*AMF:DEBUG*/ /* * Adjust for speed. Monster at speed 120 will do double damage, * monster at speed 100 will do half, etc. Bonus for monsters who can haste self. */ dam = (dam * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) / 10; /* * Adjust threat for speed -- multipliers are more threatening. */ if(r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) r_ptr.highest_threat = (short)((r_ptr.highest_threat * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) / 5); /* * Adjust threat for friends. */ if(r_ptr.flags.has(Monster_Flag.FRIENDS.value)) r_ptr.highest_threat *= 2; else if(r_ptr.flags.has(Monster_Flag.FRIEND.value)) r_ptr.highest_threat = (short)(r_ptr.highest_threat * 3 / 2); /*but deep in a minimum*/ if(dam < 1) dam = 1; /* We're done */ return (dam); }
static long hp_adjust(Monster_Race r_ptr) { long hp; int resists = 1; int hide_bonus = 0; /* Get the monster base hitpoints */ hp = r_ptr.avg_hp; /* Never moves with no ranged attacks - high hit points count for less */ if (r_ptr.flags.has(Monster_Flag.NEVER_MOVE.value) && !(r_ptr.freq_innate != 0 || r_ptr.freq_spell != 0)) { hp /= 2; if (hp < 1) hp = 1; } /* Just assume healers have more staying power */ if (r_ptr.spell_flags.has(Monster_Spell_Flag.HEAL.value)) hp = (hp * 6) / 5; /* Miscellaneous improvements */ if (r_ptr.flags.has(Monster_Flag.REGENERATE.value)) {hp *= 10; hp /= 9;} if (r_ptr.flags.has(Monster_Flag.PASS_WALL.value)) {hp *= 3; hp /= 2;} /* Calculate hide bonus */ if (r_ptr.flags.has(Monster_Flag.EMPTY_MIND.value)) hide_bonus += 2; else { if (r_ptr.flags.has(Monster_Flag.COLD_BLOOD.value)) hide_bonus += 1; if (r_ptr.flags.has(Monster_Flag.WEIRD_MIND.value)) hide_bonus += 1; } /* Invisibility */ if (r_ptr.flags.has(Monster_Flag.INVISIBLE.value)) { hp = (hp * (r_ptr.level + hide_bonus + 1)) / Math.Max(1, (int)r_ptr.level); } /* Monsters that can teleport are a hassle, and can easily run away */ if (r_ptr.spell_flags.test(Monster_Spell_Flag.SIZE, Monster_Spell_Flag.TPORT.value, Monster_Spell_Flag.TELE_AWAY.value, Monster_Spell_Flag.TELE_LEVEL.value)) hp = (hp * 6) / 5; /* * Monsters that multiply are tougher to kill */ if (r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) hp *= 2; /* Monsters with resistances are harder to kill. Therefore effective slays / brands against them are worth more. */ if (r_ptr.flags.has(Monster_Flag.IM_ACID.value)) resists += 2; if (r_ptr.flags.has(Monster_Flag.IM_FIRE.value)) resists += 2; if (r_ptr.flags.has(Monster_Flag.IM_COLD.value)) resists += 2; if (r_ptr.flags.has(Monster_Flag.IM_ELEC.value)) resists += 2; if (r_ptr.flags.has(Monster_Flag.IM_POIS.value)) resists += 2; /* Bonus for multiple basic resists and weapon resists */ if (resists >= 12) resists *= 6; else if (resists >= 10) resists *= 4; else if (resists >= 8) resists *= 3; else if (resists >= 6) resists *= 2; /* If quite resistant, reduce resists by defense holes */ if (resists >= 6) { if (r_ptr.flags.has(Monster_Flag.HURT_ROCK.value)) resists -= 1; if (r_ptr.flags.has(Monster_Flag.HURT_LIGHT.value)) resists -= 1; if (!r_ptr.flags.has(Monster_Flag.NO_SLEEP.value)) resists -= 3; if (!r_ptr.flags.has(Monster_Flag.NO_FEAR.value)) resists -= 2; if (!r_ptr.flags.has(Monster_Flag.NO_CONF.value)) resists -= 2; if (!r_ptr.flags.has(Monster_Flag.NO_STUN.value)) resists -= 1; if (resists < 5) resists = 5; } /* If quite resistant, bonus for high resists */ if (resists >= 3) { if (r_ptr.flags.has(Monster_Flag.IM_WATER.value)) resists += 1; if (r_ptr.flags.has(Monster_Flag.RES_NETH.value)) resists += 1; if (r_ptr.flags.has(Monster_Flag.RES_NEXUS.value)) resists += 1; if (r_ptr.flags.has(Monster_Flag.RES_DISE.value)) resists += 1; } /* Scale resists */ resists = resists * 25; /* Monster resistances */ if (resists < (r_ptr.ac + resists) / 3) { hp += (hp * resists) / (150 + r_ptr.level); } else { hp += (hp * (r_ptr.ac + resists) / 3) / (150 + r_ptr.level); } /*boundry control*/ if (hp < 1) hp = 1; return (hp); }
public static int r_power(Monster_Race[] races) { //God damn it C... /* ang_file *mon_fp; char buf[1024];*/ bool dump = false; /* Allocate space for power */ long[] power = new long[Misc.z_info.r_max]; long[] tot_hp = new long[Misc.MAX_DEPTH]; long[] tot_dam = new long[Misc.MAX_DEPTH]; long[] mon_count = new long[Misc.MAX_DEPTH]; for(int iteration = 0; iteration < 3; iteration++) { /* Reset the sum of all monster power values */ tot_mon_power = 0; /* Make sure all arrays start at zero */ for(int i = 0; i < Misc.MAX_DEPTH; i++) { tot_hp[i] = 0; tot_dam[i] = 0; mon_count[i] = 0; } /* Go through r_info and evaluate power ratings & flows. */ for(int i = 0; i < Misc.z_info.r_max; i++) { /* Point at the "info" */ Monster_Race r_ptr = races[i]; if(r_ptr == null) continue; /* Set the current level */ byte lvl = r_ptr.level; /* Maximum damage this monster can do in 10 game turns */ long dam = max_dam(r_ptr); /* Adjust hit points based on resistances */ long hp = hp_adjust(r_ptr); /* Hack -- set exp */ if(lvl == 0) r_ptr.mexp = 0; else { /* Compute depths of non-unique monsters */ if(r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { long mexp = (hp * dam) / 25; long threat = r_ptr.highest_threat; /* Compute level algorithmically */ int j; for(j = 1; (mexp > j + 4) || (threat > j + 5); mexp -= j * j, threat -= (j + 4), j++) ; /* Set level */ lvl = (byte)Math.Min((j > 250 ? 90 + (j - 250) / 20 : /* Level 90+ */ (j > 130 ? 70 + (j - 130) / 6 : /* Level 70+ */ (j > 40 ? 40 + (j - 40) / 3 : /* Level 40+ */ j))), 99); /* Set level */ if(Misc.arg_rebalance) r_ptr.level = lvl; } if(Misc.arg_rebalance) { /* Hack -- for Ungoliant */ if(hp > 10000) r_ptr.mexp = (int)((hp / 25) * (dam / lvl)); else r_ptr.mexp = (int)((hp * dam) / (lvl * 25)); /* Round to 2 significant figures */ if(r_ptr.mexp > 100) { if(r_ptr.mexp < 1000) { r_ptr.mexp = (r_ptr.mexp + 5) / 10; r_ptr.mexp *= 10; } else if(r_ptr.mexp < 10000) { r_ptr.mexp = (r_ptr.mexp + 50) / 100; r_ptr.mexp *= 100; } else if(r_ptr.mexp < 100000) { r_ptr.mexp = (r_ptr.mexp + 500) / 1000; r_ptr.mexp *= 1000; } else if(r_ptr.mexp < 1000000) { r_ptr.mexp = (r_ptr.mexp + 5000) / 10000; r_ptr.mexp *= 10000; } else if(r_ptr.mexp < 10000000) { r_ptr.mexp = (r_ptr.mexp + 50000) / 100000; r_ptr.mexp *= 100000; } } } } /* If we're rebalancing, this is a nop, if not, we restore the orig value */ lvl = r_ptr.level; if((lvl != 0) && (r_ptr.mexp < 1L)) r_ptr.mexp = 1; /* * Hack - We have to use an adjustment factor to prevent overflow. * Try to scale evenly across all levels instead of scaling by level. */ hp /= 2; if(hp < 1) hp = 1; r_ptr.hp = hp; /*AMF:DEBUG*/ /* Define the power rating */ power[i] = hp * dam; /* Adjust for group monsters. Average in-level group size is 5 */ if(!r_ptr.flags.has(Monster_Flag.UNIQUE.value)) { if(r_ptr.flags.has(Monster_Flag.FRIEND.value)) power[i] *= 2; else if(r_ptr.flags.has(Monster_Flag.FRIENDS.value)) power[i] *= 5; } /* Adjust for escorts */ if(r_ptr.flags.has(Monster_Flag.ESCORTS.value)) power[i] *= 3; if(r_ptr.flags.has(Monster_Flag.ESCORT.value) && !r_ptr.flags.has(Monster_Flag.ESCORTS.value)) power[i] *= 2; /* Adjust for multiplying monsters. This is modified by the speed, * as fast multipliers are much worse than slow ones. We also adjust for * ability to bypass walls or doors. */ if(r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]); else if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.OPEN_DOOR.value, Monster_Flag.BASH_DOOR.value)) power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] * 3 / 2); else power[i] = Math.Max(power[i], power[i] * Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 2); } /* * Update the running totals - these will be used as divisors later * Total HP / dam / count for everything up to the current level */ for(int j = lvl; j < (lvl == 0 ? lvl + 1 : Misc.MAX_DEPTH); j++) { int count = 10; /* Uniques don't count towards monster power on the level. */ if(r_ptr.flags.has(Monster_Flag.UNIQUE.value)) continue; /* Specifically placed monsters don't count towards monster power * on the level. */ if(!(r_ptr.rarity != 0)) continue; /* Hack -- provide adjustment factor to prevent overflow */ if((j == 90) && (r_ptr.level < 90)) { hp /= 10; dam /= 10; } if((j == 65) && (r_ptr.level < 65)) { hp /= 10; dam /= 10; } if((j == 40) && (r_ptr.level < 40)) { hp /= 10; dam /= 10; } /* * Hack - if it's a group monster or multiplying monster, add several to the count * so that the averages don't get thrown off */ if(r_ptr.flags.has(Monster_Flag.FRIEND.value)) count = 20; else if(r_ptr.flags.has(Monster_Flag.FRIENDS.value)) count = 50; if(r_ptr.flags.has(Monster_Flag.MULTIPLY.value)) { if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.KILL_WALL.value, Monster_Flag.PASS_WALL.value)) count = Math.Max(1, (int)Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)]) * count; else if(r_ptr.flags.test(Monster_Flag.SIZE, Monster_Flag.OPEN_DOOR.value, Monster_Flag.BASH_DOOR.value)) count = Math.Max(1, Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] * 3 / 2) * count; else count = Math.Max(1, Misc.extract_energy[r_ptr.speed + (r_ptr.spell_flags.has(Monster_Spell_Flag.HASTE.value) ? 5 : 0)] / 2) * count; } /* Very rare monsters count less towards total monster power on the * level. */ if(r_ptr.rarity > count) { hp = hp * count / r_ptr.rarity; dam = dam * count / r_ptr.rarity; count = r_ptr.rarity; } tot_hp[j] += hp; tot_dam[j] += dam; mon_count[j] += count / r_ptr.rarity; } } /* Apply divisors now */ for(int i = 0; i < Misc.z_info.r_max; i++) { int new_power; /* Point at the "info" */ Monster_Race r_ptr = races[i]; if(r_ptr == null) continue; /* Extract level */ byte lvl = r_ptr.level; /* Paranoia */ if(tot_hp[lvl] != 0 && tot_dam[lvl] != 0) { /* Divide by average HP and av damage for all in-level monsters */ /* Note we have factored in the above 'adjustment factor' */ long av_hp = tot_hp[lvl] * 10 / mon_count[lvl]; long av_dam = tot_dam[lvl] * 10 / mon_count[lvl]; /* Assign monster power */ r_ptr.power = power[i]; /* Justifiable paranoia - avoid divide by zero errors */ if(av_hp > 0) power[i] = power[i] / av_hp; if(av_dam > 0) power[i] = power[i] / av_dam; /* Assign monster scaled power */ r_ptr.scaled_power = power[i]; /* Never less than 1 */ if(r_ptr.power < 1) r_ptr.power = 1; /* Get power */ new_power = (int)r_ptr.power; /* Compute rarity algorithmically */ int j; for(j = 1; new_power > j; new_power -= j * j, j++) ; /* Set rarity */ if(Misc.arg_rebalance) r_ptr.rarity = (byte)j; } } } /* Determine total monster power */ for(int i = 0; i < Misc.z_info.r_max; i++) { Monster_Race r = Misc.r_info[i]; if(r == null) continue; tot_mon_power += (int)r.scaled_power; } /* msg("Tot mon power is %d", tot_mon_power); */ if(dump) { /* dump the power details */ throw new NotImplementedException(); //path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "mon_power.txt"); //mon_fp = file_open(buf, MODE_WRITE, FTYPE_TEXT); //file_putf(mon_fp, "ridx|level|rarity|d_char|name|pwr|scaled|melee|spell|hp\n"); //for (i = 0; i < z_info.r_max; i++) { // r_ptr = &r_info[i]; // /* Don't print anything for nonexistent monsters */ // if (!r_ptr.name) continue; // file_putf(mon_fp, "%d|%d|%d|%c|%s|%d|%d|%d|%d|%d\n", r_ptr.ridx, // r_ptr.level, r_ptr.rarity, r_ptr.d_char, r_ptr.name, // r_ptr.power, r_ptr.scaled_power, r_ptr.melee_dam, // r_ptr.spell_dam, r_ptr.hp); //} //file_close(mon_fp); } /* Free power array */ //FREE(power); /* Success */ return 0; }
public static void wr_monster_memory() { int i; int r_idx; wr_u16b(Misc.z_info.r_max); for (r_idx = 0; r_idx < Misc.z_info.r_max; r_idx++) { Monster_Race r_ptr = Misc.r_info[r_idx]; Monster_Lore l_ptr = Misc.l_list[r_idx]; if (r_ptr == null) { r_ptr = new Monster_Race(); } if (l_ptr == null) { l_ptr = new Monster_Lore(); } /* Count sights/deaths/kills */ wr_s16b(l_ptr.sights); wr_s16b(l_ptr.deaths); wr_s16b(l_ptr.pkills); wr_s16b(l_ptr.tkills); /* Count wakes and ignores */ wr_byte(l_ptr.wake); wr_byte(l_ptr.ignore); /* Count drops */ wr_byte(l_ptr.drop_gold); wr_byte(l_ptr.drop_item); /* Count spells */ wr_byte(l_ptr.cast_innate); wr_byte(l_ptr.cast_spell); /* Count blows of each type */ for (i = 0; i < Monster_Blow.MONSTER_BLOW_MAX; i++) wr_byte(l_ptr.blows[i]); /* Memorize flags */ for (i = 0; i < Monster_Flag.BYTES && i < Monster_Flag.SIZE; i++) wr_byte(l_ptr.flags.data[i]); if (i < Monster_Flag.BYTES) Savefile.pad_bytes(Monster_Flag.BYTES - i); for (i = 0; i < Monster_Flag.BYTES && i < Monster_Spell_Flag.SIZE; i++) wr_byte(l_ptr.spell_flags[i]); if (i < Monster_Flag.BYTES) Savefile.pad_bytes(Monster_Flag.BYTES - i); /* Monster limit per level */ wr_byte(r_ptr.max_num); /* XXX */ wr_byte(0); wr_byte(0); wr_byte(0); } }
/* * Returns a pointer to a statically allocatted string containing a formatted * message based on the given message code and the quantity flag. * The contents of the returned value will change with the next call * to this function */ static string get_mon_msg_action(MON_MSG msg_code, bool do_plural, Monster_Race race) { //static char buf[200]; string buf = ""; string action = msg_repository[(int)msg_code]; short n = 0; /* Regular text */ byte flag = 0; Misc.assert(race.Base != null && race.Base.pain != null); if(race.Base != null && race.Base.pain != null) { switch(msg_code) { case MON_MSG.MON_MSG_95: action = race.Base.pain.Messages[0]; break; case MON_MSG.MON_MSG_75: action = race.Base.pain.Messages[1]; break; case MON_MSG.MON_MSG_50: action = race.Base.pain.Messages[2]; break; case MON_MSG.MON_MSG_35: action = race.Base.pain.Messages[3]; break; case MON_MSG.MON_MSG_20: action = race.Base.pain.Messages[4]; break; case MON_MSG.MON_MSG_10: action = race.Base.pain.Messages[5]; break; case MON_MSG.MON_MSG_0: action = race.Base.pain.Messages[6]; break; } } /* Put the message characters in the buffer */ for(; action.Length > 0; action = action.Substring(1)) { /* Check available space */ if(n >= (buf.Length)) break; /* Are we parsing a quantity modifier? */ if(flag != 0) { /* Check the presence of the modifier's terminator */ if(action[0] == ']') { /* Go back to parsing regular text */ flag = 0; /* Skip the mark */ continue; } /* Check if we have to parse the plural modifier */ if(action[0] == '|') { /* Switch to plural modifier */ flag = PLURAL_MON; /* Skip the mark */ continue; } /* Ignore the character if we need the other part */ if((flag == PLURAL_MON) != do_plural) continue; } /* Do we need to parse a new quantity modifier? */ else if(action[0] == '[') { /* Switch to singular modifier */ flag = SINGULAR_MON; /* Skip the mark */ continue; } /* Append the character to the buffer */ buf += action[0]; n++; } /* Terminate the buffer */ //buf[n] = '\0'; /* Done */ return (buf); }
/* * Remove the "bad" spells from a spell list */ static void remove_bad_spells(int m_idx, Bitflag f) { Monster m_ptr = Cave.cave_monster(Cave.cave, m_idx); Monster_Race r_ptr = Misc.r_info[m_ptr.r_idx]; Bitflag f2 = new Bitflag(Monster_Spell_Flag.SIZE); Bitflag ai_flags = new Bitflag(Object_Flag.SIZE); int i; uint smart = 0; /* Stupid monsters act randomly */ if (r_ptr.flags.has(Monster_Flag.STUPID.value)) { return; } /* Take working copy of spell flags */ f2.copy(f); /* Don't heal if full */ if (m_ptr.hp >= m_ptr.maxhp) { f2.off(Monster_Spell_Flag.HEAL.value); } /* Don't haste if hasted with time remaining */ if (m_ptr.m_timed[(int)Misc.MON_TMD.FAST] > 10) { f2.off(Monster_Spell_Flag.HASTE.value); } /* Don't teleport to if the player is already next to us */ if (m_ptr.cdis == 1) { f2.off(Monster_Spell_Flag.TELE_TO.value); } /* Update acquired knowledge */ ai_flags.wipe(); if (Option.birth_ai_learn.value) { /* Occasionally forget player status */ if (Random.one_in_(100)) { m_ptr.known_pflags.wipe(); } /* Use the memorized flags */ smart = m_ptr.smart; ai_flags.copy(m_ptr.known_pflags); } /* Cheat if requested */ if (Option.birth_ai_cheat.value) { for (i = 0; i < Object_Flag.MAX.value; i++) { if (Misc.p_ptr.check_state(Object_Flag.list[i], Misc.p_ptr.state.flags)) { ai_flags.on(i); } } if (Misc.p_ptr.msp == 0) { smart |= Misc.SM_IMM_MANA; } } /* Cancel out certain flags based on knowledge */ if (!ai_flags.is_empty()) { throw new NotImplementedException(); //unset_spells(f2, ai_flags, r_ptr); } if ((smart & Misc.SM_IMM_MANA) != 0 && Random.randint0(100) < 50 * (r_ptr.flags.has(Monster_Flag.SMART.value) ? 2 : 1)) { f2.off(Monster_Spell_Flag.DRAIN_MANA.value); } /* use working copy of spell flags */ f.copy(f2); }
/* * Returns a pointer to a statically allocatted string containing a formatted * message based on the given message code and the quantity flag. * The contents of the returned value will change with the next call * to this function */ static string get_mon_msg_action(MON_MSG msg_code, bool do_plural, Monster_Race race) { //static char buf[200]; string buf = ""; string action = msg_repository[(int)msg_code]; short n = 0; /* Regular text */ byte flag = 0; Misc.assert(race.Base != null && race.Base.pain != null); if (race.Base != null && race.Base.pain != null) { switch (msg_code) { case MON_MSG.MON_MSG_95: action = race.Base.pain.Messages[0]; break; case MON_MSG.MON_MSG_75: action = race.Base.pain.Messages[1]; break; case MON_MSG.MON_MSG_50: action = race.Base.pain.Messages[2]; break; case MON_MSG.MON_MSG_35: action = race.Base.pain.Messages[3]; break; case MON_MSG.MON_MSG_20: action = race.Base.pain.Messages[4]; break; case MON_MSG.MON_MSG_10: action = race.Base.pain.Messages[5]; break; case MON_MSG.MON_MSG_0: action = race.Base.pain.Messages[6]; break; } } /* Put the message characters in the buffer */ for (; action.Length > 0; action = action.Substring(1)) { /* Check available space */ if (n >= (buf.Length)) { break; } /* Are we parsing a quantity modifier? */ if (flag != 0) { /* Check the presence of the modifier's terminator */ if (action[0] == ']') { /* Go back to parsing regular text */ flag = 0; /* Skip the mark */ continue; } /* Check if we have to parse the plural modifier */ if (action[0] == '|') { /* Switch to plural modifier */ flag = PLURAL_MON; /* Skip the mark */ continue; } /* Ignore the character if we need the other part */ if ((flag == PLURAL_MON) != do_plural) { continue; } } /* Do we need to parse a new quantity modifier? */ else if (action[0] == '[') { /* Switch to singular modifier */ flag = SINGULAR_MON; /* Skip the mark */ continue; } /* Append the character to the buffer */ buf += action[0]; n++; } /* Terminate the buffer */ //buf[n] = '\0'; /* Done */ return(buf); }