/* -*- LPC -*- */ /** * 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 * @changed 3 August 2002 -- Rhinehold * Converted to work with new non-effect combat system * Added support for behead, pierce, bash, smash, chop * Removed strike */ #include <good_fighter.h> #define LOGFILE "good_fighter" #define CMDS "/cmds/guild-race/fighting/" #define DEBUG inherit "/std/effect_shadow"; #define W_ATTACKS ([ "crush" : "crush $N with $W", \ "behead" : "behead $N with $W", \ "impale" : "impale $N with $W", \ "riposte" : "riposte $N with $W", \ "stab" : "stab $N with $W", \ "pierce" : "pierce $N with $W", \ "bash" : "bash $N with $W", \ "smash" : "smash $N with $W", \ "slash" : "slash $N with $W", \ "slice" : "slice $N with $W", \ "hack" : "hack $N with $W", \ "chop" : "chop $N with $W", \ "feint" : "feint at $N with $W" ]) #define U_ATTACKS ([ "trip" : "trip $N", \ "shove" : "shove $N" ]) string *specials; mapping weapons; 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 = ({ }); weapons = ([ ]); } /** * 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); if (bluntorsharp == USE_SHARP) { check_add_command("hack", "fighting.combat.special", 15); check_add_command("slash", "fighting.combat.special", 15); check_add_command("slice", "fighting.combat.special", 75); check_add_command("chop", "fighting.combat.special", 75); if (player->query_guild_ob() == "/std/guilds/warrior") check_add_command("behead", "fighting.combat.special", 150); } if (bluntorsharp == USE_PIERCE) { check_add_command("stab", "fighting.combat.special", 15); check_add_command("pierce", "fighting.combat.special", 75); if (player->query_guild_ob() == "/std/guilds/warrior") check_add_command("impale", "fighting.combat.special", 150); } } if (bluntorsharp == USE_BLUNT || bluntorsharp == USE_BALANCED) { check_add_command("bash", "fighting.combat.special", 15); check_add_command("smash", "fighting.combat.special", 75); if (player->query_guild_ob() == "/std/guilds/warrior") check_add_command("crush", "fighting.combat.special", 150); } check_add_command("feint", "fighting.combat.special", 15); check_add_command("trip", "fighting.combat.special", 15); check_add_command("shove", "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 command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_weapon(object weapon, string command) { if (member_array(weapon->query_command_names(), ({ command })) != -1) 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 'crush' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_crush(object weapon) { if (member_array(weapon->query_commands_names(), ({ "smash" })) != -1) 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 'behead' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_behead(object weapon) { if (member_array(weapon->query_commands_names(), ({ "slice" })) != -1) 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) { if (member_array(weapon->query_commands_names(), ({ "pierce" })) != -1) 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 'riposte' command. * * @param weapon The object to check. * * @return 1 if the object is appropriate, 0 otherwise */ protected int check_riposte(object weapon) { if (member_array(weapon->query_commands_names(), ({ "slash" })) != -1) 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 event_fight_in_progress(object attacker, object opponent) { object ts, temp, weapon; int chance; string cmd, str; /* 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; ts->event_fight_in_progress(attacker, opponent); 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) { // player->do_command("concentrate " + (attacker->query_short())); // victim = player->query_concentrating(); victim = attacker; if (!victim) return; } /* if (!ts->query_special_manoeuvre() || (sizeof((int *)ts->effects_matching("fighting.combat.special")))) { player->fight_in_progress(attacker); 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 25% 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 = (player->query_level() / 12) + 5; if(chance > 25) chance = 25; if (random(100) + 1 < chance) { object *holding; string *candidates; int i, j; candidates = ({ }); holding = player->query_holding() - ({ 0 }); foreach(weapon in holding) { // Setup an array of the attack possibilities for each weapon so // we don't have to recalc them everytime. if(!weapons[weapon]) { weapons[weapon] = ({ }); for (i = 0; i < sizeof(specials); i++) if(W_ATTACKS[specials[i]]) { weapons[weapon] += ({ replace(W_ATTACKS[specials[i]], "$W", weapon->query_short()) }); } } // Add held weapons to the candidates list candidates += weapons[weapon]; } // Setup unarmed attacks. if(weapons["unarmed"]) { for(i=0; i<sizeof(specials); i++) weapons["unarmed"] = ({ }); if(U_ATTACKS[specials[i]]) weapons["unarmed"] += ({ U_ATTACKS[specials[i]] }); } // add unarmed attacks to the candidates list candidates += weapons["unarmed"]; #ifdef DEBUG // debug_printf("Commands available: %O", candidates); #endif if(sizeof(candidates)) { cmd = replace(candidates[random(sizeof(candidates))], "$N", opponent->query_name()); #ifdef DEBUG debug_printf("Trying to perform: %O", cmd); #endif player->do_command(cmd, 1); } else { #ifdef DEBUG debug_printf("No command to perform"); #endif } } else { #ifdef DEBUG debug_printf("Not doing a special this time"); #endif } } /** * 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, ", ") }) }); }