/* -*- LPC -*- */ /* * $Locker: $ * $Id: good_fighter.c,v 1.16 2000/03/28 21:19:21 ceres Exp $ * $Log: good_fighter.c,v $ * Revision 1.16 2000/03/28 21:19:21 ceres * Varioius tweaks * * Revision 1.14 1999/02/16 02:23:04 sin * Fixed bug with giving him strike and crush * * Revision 1.13 1999/01/15 17:26:04 presto * Added '- ({ 0 })' to the assignment of 'holding' around line 451 * * Revision 1.12 1999/01/15 17:22:10 ceres * Forcibly unlocked by presto * * Revision 1.11 1999/01/05 03:03:24 sin * Turned off debugging. * * Revision 1.10 1998/12/31 20:02:48 ceres * Fixed error with command (had extraneous players name), added debug info, fixed inversion of query_special_manoeuvre() * * Revision 1.9 1998/12/31 01:01:19 ceres * Made disarm a little rarer since it shouldn't be used as often as say slash or crush. * * Revision 1.8 1998/09/10 20:20:27 ceres * Fixed riposte error. * * Revision 1.7 1998/04/12 21:33:14 sin * Added Ceres' new crush command to the good fighter effect. * * Revision 1.6 1998/04/12 00:09:05 sin * Hehhehe. forgot one piece of SUPPORT_PIERCE deadwood. * * Revision 1.5 1998/04/12 00:07:52 sin * Added support for USE_UNARMED and got rid of some dead wood from the * changeover. * * Revision 1.4 1998/02/08 21:36:20 sin * activated change for new skill tree. * * Revision 1.3 1998/02/06 22:18:29 sin * Change in preparation for the skills rearrange.c * * Revision 1.2 1998/01/30 10:50:45 sin * Had to rearrange fight_in_progress() slightly to make the chaining work. * * Revision 1.1 1998/01/06 04:39:52 ceres * Initial revision * */ /** * The Good Fighter shadow. The main docs are stored in the effect * header file, not here... Mostly for standardisation reasons. * * @see /std/effects/npc/good_fighter * * @author Sin * @created 12 November 1997 * @changed 13 November 1997 -- Sin * Converted it from a pure shadow to a shadow/effect pair * @changed 11 April 1998 -- Sin * Added support for USE_UNARMED. * God rid of some dead wood. * @changed 12 April 1998 -- Sin * Added support for crush */ #include <good_fighter.h> #define LOGFILE "good_fighter" #define CMDS "/cmds/guild-race/fighting/" #define DEBUG inherit "/std/effect_shadow"; string *specials; int bluntorsharp; int dodgeorparry; object victim; /** * @ignore * Used only to insure that the specials array is actually an array at * all times. */ void create() { specials = ({ }); } /** * This is a helper function used by the good_fighter_setup() function to * ensure that the specified skill is at least at a particular level. * * @param skill The skill to check * * @param level The minimum acceptable level for this skill */ protected void check_skill(string skill, int level) { int cur; cur = player->query_skill(skill); if (cur < level) player->add_skill_level(skill, level - cur); } /** * This helper function is used by good_fighter_setup() to add a known * command to the NPC's repertoire _only_ if the relevant skill is above * a certain level. If the command is added, then it is also stored in the * specials[] array for later use by fight_in_progress() * * @param command The command to be added * * @param skill The skill that controls the NPC's effectiveness with this * command. * * @param level The minimum level before the command can be added. */ protected void check_add_command(string command, string skill, int level) { int cur; cur = player->query_skill(skill); if (cur >= level) { player->add_known_command(command); specials += ({ command }); } } /** * This function is called from a callout() registered when setup_shadow() * is called. It is responsible for ensuring that all of the NPC's skills * are at a reasonable level, that the NPC has the commands appropriate * for its priorities and level, and that the tactics are set appropriately. */ void good_fighter_setup() { int adjust; int lvl; int *args = arg(); if (!args || !arrayp(args) || sizeof(args) != 2) return; specials = ({ }); bluntorsharp = args[0]; dodgeorparry = args[1]; lvl = player->query_level(); check_skill("other.perception", lvl / 2); check_skill("other.health", lvl); adjust = lvl * 3 / 4; if (bluntorsharp == USE_PIERCE) { check_skill("fighting.combat.melee.blunt", lvl - adjust); check_skill("fighting.combat.melee.sharp", lvl - adjust); check_skill("fighting.combat.melee.pierce", lvl + adjust); check_skill("fighting.combat.melee.unarmed", lvl - adjust); } else if (bluntorsharp == USE_BLUNT) { check_skill("fighting.combat.melee.blunt", lvl + adjust); check_skill("fighting.combat.melee.sharp", lvl - adjust); check_skill("fighting.combat.melee.pierce", lvl - adjust); check_skill("fighting.combat.melee.unarmed", lvl - adjust); } else if (bluntorsharp == USE_SHARP) { check_skill("fighting.combat.melee.blunt", lvl - adjust); check_skill("fighting.combat.melee.sharp", lvl + adjust); check_skill("fighting.combat.melee.pierce", lvl - adjust); check_skill("fighting.combat.melee.unarmed", lvl - adjust); } else if (bluntorsharp == USE_UNARMED) { check_skill("fighting.combat.melee.blunt", lvl - adjust); check_skill("fighting.combat.melee.sharp", lvl - adjust); check_skill("fighting.combat.melee.pierce", lvl - adjust); check_skill("fighting.combat.melee.unarmed", lvl + adjust); } else { check_skill("fighting.combat.melee.blunt", lvl); check_skill("fighting.combat.melee.sharp", lvl); check_skill("fighting.combat.melee.pierce", lvl); check_skill("fighting.combat.melee.unarmed", lvl); } adjust = lvl * 2 / 3; if (dodgeorparry == DEFEND_DODGE) { check_skill("fighting.combat.dodging", lvl + adjust); check_skill("fighting.combat.parry", lvl - adjust); player->init_command("tactics response dodge", 1); } else if (dodgeorparry == DEFEND_PARRY) { check_skill("fighting.combat.dodging", lvl - adjust); check_skill("fighting.combat.parry", lvl + adjust); player->init_command("tactics response parry", 1); } else { check_skill("fighting.combat.dodging", lvl); check_skill("fighting.combat.parry", lvl); player->init_command("tactics response neutral", 1); } check_skill("fighting.combat.special", lvl / 2); check_skill("fighting.points", lvl * 2); player->init_command("tactics attitude offensive", 1); if (bluntorsharp != USE_BLUNT) { if (dodgeorparry == DEFEND_PARRY) check_add_command("riposte", "fighting.combat.special", 15); check_add_command("hack", "fighting.combat.special", 15); check_add_command("slash", "fighting.combat.special", 15); if (bluntorsharp == USE_PIERCE) check_add_command("impale", "fighting.combat.special", 50); } if (bluntorsharp == USE_BLUNT || bluntorsharp == USE_BALANCED) { check_add_command("strike", "fighting.combat.special", 15); check_add_command("crush", "fighting.combat.special", 50); } check_add_command("disarm", "fighting.combat.parry.melee", 100); check_add_command("kick", "fighting.combat.special", 15); check_add_command("punch", "fighting.combat.special", 15); player->add_known_command("concentrate"); } /** * This helper function is used by fight_in_progress() to see if one of * the NPC's weapons are appropriate for use with the 'crush' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_crush(object weapon) { return (CMDS "crush")->check_blunt(weapon); } /** * This helper function is used by fight_in_progress() to see if one of * the NPC's weapons are appropriate for use with the 'riposte' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_riposte(object weapon) { return (CMDS "riposte")->check_pierce(weapon); } /** * This helper function is used by fight_in_progress() to see if one * of the NPC's weapons are appropriate for use with the 'strike' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_strike(object weapon) { return (CMDS "strike")->check_blunt(weapon); } /** * This helper function is used by fight_in_progress() to see if one * of the NPC's weapons are appropriate for use with the 'hack' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_hack(object weapon) { /* This code is copied out of the hack command, since it isn't * conveniently bundled in a separate function like the strike * and riposte commands are. */ if(!weapon) return 0; if (weapon->query_weapon_type() && member_array(weapon->query_weapon_type(), ({ "sword", "axe" })) != -1) return 1; if (weapon->id("sword")) return 1; if (weapon->id("blade")) return 1; if (weapon->id("cutlass")) return 1; if (weapon->id("sabre")) return 1; if (weapon->id("foil")) return 1; if (weapon->id("katana")) return 1; if (weapon->id("kring")) return 1; if (weapon->id("rapier")) return 1; if (weapon->id("sarilak")) return 1; if (weapon->id("ninjan-to")) return 1; if (weapon->id("tan-to")) return 1; if (weapon->id("waki-zashi")) return 1; if (weapon->id("scimitar")) return 1; if (weapon->id("axe")) return 1; return 0; } /** * This helper function is used by fight_in_progress() to see if one * of the NPC's weapons are appropriate for use with the 'impale' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_impale(object weapon) { return (CMDS "impale")->check_pierce(weapon); } /** * This helper function is used by fight_in_progress() to see if one * of the NPC's weapons are appropriate for use with the 'slash' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_slash(object weapon) { /* This code is copied out of the slash command, since it isn't * conveniently bundled in a separate function like the strike * and riposte commands are. */ if (member_array(weapon->query_weapon_type(), ({ "sword", "knife" })) != -1) return 1; if (weapon->id("knife")) return 1; if (weapon->id("dagger")) return 1; if (weapon->id("sword")) return 1; if (weapon->id("blade")) return 1; if (weapon->id("cutlass")) return 1; if (weapon->id("sabre")) return 1; if (weapon->id("foil")) return 1; if (weapon->id("katana")) return 1; if (weapon->id("kring")) return 1; if (weapon->id("rapier")) return 1; if (weapon->id("sarilak")) return 1; if (weapon->id("ninjan-to")) return 1; if (weapon->id("tan-to")) return 1; if (weapon->id("waki-zashi")) return 1; if (weapon->id("scimitar")) return 1; return 0; } /** * This function is called once per combat round by the combat shadow, * and does most of the work for this shadow. It controls on whom * the NPC is concentrating, chooses an appropriate attack, and chooses * an appropriate weapon for the attack. * * <p>Most of the intelligence is in the specific choice of attack, and * there isn't a whole lot in that. It restricts itself to simply going * through the attacks stored in the specials[] array (calculated in * the call to good_fighter_setup()), finding which attacks are valid (based * on whether the opponent is holding a weapon and which weapons are wielded * by the NPC). Once it has a list of valid commands, it choses from them * randomly. * * <p>Regardless, there is always the possibility that this function will * drop through and allow the combat handler to attack normally. That * chance is inversely proportional to the NPC's level. * * @param attacker The current person attacking the NPC. */ void fight_in_progress(object attacker) { object ts, temp; int chance; string cmd; /* I have to get to the top of the shadow stack, because the combat * shadow was probably added after this one. */ ts = player; while ((temp = shadow(ts, 0)) != 0 && temp != ts) ts = temp; if (victim && !objectp(victim)) victim = 0; if (victim && !interactive(victim)) victim = 0; if (victim && environment(victim) != environment(player)) victim = 0; if (victim && victim != attacker) return; if (!victim) { ts->concentrate(attacker->query_short()); victim = ts->query_concentrating(); if (!victim) return; } if (!ts->query_special_manoeuvre() || (sizeof((int *)ts->effects_matching("fighting.combat.special")))) { player->fight_in_progress(attacker); #ifdef DEBUG event(environment(player), "inform", "Already doing special", "combat"); #endif return; } /* * As the NPC gets higher level, there should be more possiblity of * it using a special command. But on the other hand, it mustn't * ever get to the point that it will _always_ use special commands. * I have fixed the upper limit at a 90% chance of using a special * command, and that is reached at level 300. * Right now the probability curve is linear. It should really * be a sigmoid. */ chance = ts->query_level(); if (chance > 300) chance = 90; else chance = (chance * 13) / 60; chance += 15; if (random(100) + 1 < chance) { object *holding; string *candidates; int i, j; candidates = ({ }); holding = player->query_holding() - ({ 0 }); for (i = 0; i < sizeof(specials); i++) { switch (specials[i]) { case "crush": /* syntax: crush <person> with <weapon> */ for (j = 0; j < sizeof(holding) && !check_crush(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "crush " + victim->query_name() + " with " + holding[j]->query_short() }); break; case "riposte": /* syntax: riposte <person> attack with <weapon>. */ for (j = 0; j < sizeof(holding) && !check_riposte(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "riposte " + victim->query_name() + " attack with " + holding[j]->query_short() }); break; case "strike": /* syntax: strike <person> with <weapon> */ for (j = 0; j < sizeof(holding) && !check_strike(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "strike " + victim->query_name() + " with " + holding[j]->query_short() }); break; case "disarm": /* syntax: disarm <person> */ if (sizeof(victim->query_weapons()) != 0 && !random(3)) candidates += ({ "disarm " + victim->query_name()}); break; case "hack": /* syntax: hack at <person> with <weapon> */ for (j = 0; j < sizeof(holding) && !check_hack(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "hack at " + victim->query_name() + " with " + holding[j]->query_short() }); break; case "impale": /* syntax: impale <person> with <weapon> */ for (j = 0; j < sizeof(holding) && !check_impale(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "impale " + victim->query_name() + " with " + holding[j]->query_short() }); break; case "kick": /* syntax: kick <person> */ if(!sizeof(holding - ({ 0 }))) // don't kick if we're holding a weapon. candidates += ({ "kick " + victim->query_name() }); break; case "punch": /* syntax: punch <person> */ if (sizeof(this_player()->query_holding() - ({ 0 })) != sizeof(this_player()->query_limbs())) candidates += ({ "punch " + victim->query_name() }); break; case "slash": /* syntax: slash at <person> with <weapon> */ for (j = 0; j < sizeof(holding) && !check_slash(holding[j]); j++); if (j < sizeof(holding)) candidates += ({ "slash at " + victim->query_name() + " with " + holding[j]->query_short() }); break; default: event(environment(victim), "inform", "BUG! The good fighter shadow " "encountered the command " + specials[i] + " in the NPC " + file_name(player), "combat"); log_file(LOGFILE, ctime(time()) + ": BUG. Don't know how to " + "use command " + specials[i] + " in object " + file_name(player) + ".\n"); break; } } if(sizeof(candidates)) { cmd = candidates[random(sizeof(candidates))]; #ifdef DEBUG event(environment(player), "inform", "Trying to perform: " + cmd, "combat"); #endif ts->init_command(cmd, 1); } else { #ifdef DEBUG event(environment(player), "inform", "No command to perform", "combat"); #endif } } else { #ifdef DEBUG event(environment(player), "inform", "Not doing special this time.", "combat"); #endif } player->fight_in_progress(attacker); } /** * When a creator uses the 'stat' command on an NPC that is shadowed * by this object, this function gets called. It returns an array * containing the normal stats for tho object that this is shadowing, ' * plus it tacks on some information regarding the configuration of * this shadow. * * @return A list of stats related to this shadow, augmented by * the stats of the NPC that this shadow is attached to (if any). */ mixed *stats() { if (!player || !objectp(player)) return ({ ({ "good fighter", "unattached" }) }); return player->stats() + ({ ({ "good fighter", ((bluntorsharp == USE_BLUNT) ? "blunt" : ((bluntorsharp == USE_PIERCE) ? "pierce" : ((bluntorsharp == USE_SHARP) ? "sharp" : "balanced"))) + ((dodgeorparry == DEFEND_DODGE) ? " dodger" : ((dodgeorparry == DEFEND_PARRY) ? " parrier" : " fighter")) }), ({ "managed commands", implode(specials, ", ") }) }); }