/*************************************************************************** * Mud20 1.0 by Todd H. Johnson (Kregor) a derivative of the Open Gaming * * License by Wizards of the Coast. All comments referring to D20, OGL, * * and SRD refer to the System Reference Document for the Open Gaming * * system. Any inclusion of these derivatives must include credit to the * * Mud20 system, the full and complete Open Gaming LIcense, and credit to * * the respective authors. See ../doc/srd.txt for more information. * * * * Emud 2.2 by Igor van den Hoven, Michiel Lange, and Martin Bethlehem. * * * * MrMud 1.4 by David Bills, Dug Michael and Martin Gallwey * * * * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * * * Original Diku Mud copyright (C) 1990 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeld, Tom Madsen, and Katje Nyboe. * ***************************************************************************/ /*************************************************************************** * mob_ai.c * * Of note with these new AI functions: Much has yet to be factored into the * combat AI. Mobiles now have mana, and do not get to cast unlimited spells * as in other Merc's spec_funs. There are separate AI functions for idle * and combat, and all of them must work seamlessly with the mob prog engine. * For the most part, the old spec_fun system is deprecated, and is mainly * used more like an extra set of ACT flags for a mobile to determine its * roles and functions in an area. * * Extensive commenting and documentation should be made in each AI function * so there is no guesswork as to why a certain check or function is there. * * One basic premise to determine AI is this: while mobiles can be multiclass, * one class is considered its "favored" class. The class that is hard set * in the mobile's "class" field is considered that class. Its base AI, thus, * will be determined on that premise. ***************************************************************************/ #include "mud.h" /* * Old specials functions still in use */ struct spec_fun_type { char *name; SPEC_FUN *function; }; const struct spec_fun_type spec_fun[]= { {"spec_executioner", spec_executioner}, {"spec_fido", spec_fido}, {"spec_guard", spec_guard}, {"spec_janitor", spec_janitor}, {"spec_thief", spec_thief}, {"", NULL} }; /* Given a name, Return the appropriate spec fun. */ SPEC_FUN *spec_lookup( const char *name ) { int i; push_call("spec_lookup(%p)",name); for (i = 0 ; is_string(spec_fun[i].name) ; i++) { if (!strcasecmp(name, spec_fun[i].name)) { pop_call(); return spec_fun[i].function; } } pop_call(); return NULL; } /* Given a spec function, Return the appropriate name. */ char *spec_name_lookup( SPEC_FUN *fun ) { int i; push_call("spec_name(%p)",fun); for (i = 0 ; is_string(spec_fun[i].name) ; i++) { if (spec_fun[i].function == fun) { pop_call(); return spec_fun[i].name; } } pop_call(); return 0; } /* * Use for particularly nasty guard, or bounty hunters * who only aim to kill criminals - Kregor */ bool spec_executioner( CHAR_DATA *ch ) { char buf[MAX_STRING_LENGTH]; CHAR_DATA *victim; CHAR_DATA *v_next; char *crime; if ( !IS_AWAKE(ch) || in_combat(ch) || IS_SET( ch->in_room->room_flags, ROOM_SAFE)) { pop_call(); return FALSE; } crime = ""; for ( victim = ch->in_room->first_person; victim != NULL; victim = v_next ) { v_next = victim->next_in_room; if (IS_NPC(victim)) continue; if ( IS_PLR(victim, PLR_KILLER) ) { crime = "killer"; break; } if ( IS_PLR(victim, PLR_THIEF) ) { crime = "thief"; break; } else if (get_bounty(victim->pcdata->pvnum) != NULL) { crime = "criminal"; break; } } if ( victim == NULL ) { pop_call(); return FALSE; } sprintf( buf, "Your head is mine, %s", crime ); do_shout( ch, buf ); fight( ch, victim ); pop_call(); return TRUE; } bool spec_fido( CHAR_DATA *ch ) { OBJ_DATA *corpse; if (!IS_AWAKE(ch)) { pop_call(); return FALSE; } for (corpse = ch->in_room->first_content ; corpse ; corpse = corpse->next_content) { if (corpse->item_type != ITEM_CORPSE_NPC) { continue; } act( "$n savagely devours a corpse.", ch, NULL, NULL, TO_ROOM); while (corpse->first_content) { corpse->first_content->sac_timer = OBJ_SAC_TIME; obj_to_room(corpse->first_content, ch->in_room->vnum); } junk_obj( corpse ); pop_call(); return TRUE; } pop_call(); return FALSE; } /* * Heavily modified original spec fun to include area justice system. - Kregor */ bool spec_guard( CHAR_DATA *ch ) { CHAR_DATA *victim; CHAR_DATA *v_next; CHAR_DATA *ech; BOUNTY_DATA *bounty; bool align_diff; push_call("spec_guard(%p)",ch); if (!IS_AWAKE(ch) || in_combat(ch) || IS_SET(ch->in_room->room_flags, ROOM_SAFE)) { pop_call(); return FALSE; } if (IS_AFFECTED(ch, AFF_DOMINATE) || IS_AFFECTED(ch, AFF2_CHARMED)) { pop_call(); return FALSE; } align_diff = FALSE; ech = NULL; for (victim = ch->in_room->first_person ; victim ; victim = v_next) { v_next = victim->next_in_room; if (!can_see(ch, victim)) continue; // if (IS_PLR(victim, PLR_KILLER|PLR_THIEF)) // { // if (number_percent() < 20) // { // do_shout(ch, format("There is a %s at %s! Alarm!", IS_PLR(victim, PLR_KILLER) ? "killer" : "thief", ch->in_room->name)); // break; // } // } if ((bounty = get_record(victim, ch->pIndexData->area, -1)) != NULL) { if(number_percent() < 50) // guards aren't always perceptive - Kregor { if (!in_combat(victim)) // if not in comabat, execute punishments - Kregor { if (bounty->punishment == PUNISHMENT_DEATH) { do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name)); break; } if (bounty->punishment == PUNISHMENT_SEVER) { do_sayto(ch, format("%s I am carrying out your punishment for %s!", victim->name, crime_titles[bounty->crime])); sever_limb(ch, victim, CAN_WEAR_HANDS); remove_bounty(bounty); save_bounties(); pop_call(); return TRUE; } if (bounty->punishment == PUNISHMENT_CONFISCATE) { if (confiscate(ch, victim)) { remove_bounty(bounty); save_bounties(); pop_call(); return TRUE; } } if (bounty->punishment == PUNISHMENT_JAIL) { int jailroom; do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name)); if ((jailroom = ch->pIndexData->area->dungeon) == 0) { if ((jailroom = ch->pIndexData->area->courtroom) == 0) { log_build_printf(ch->pIndexData->vnum, "collect_bounty: dungeon and courtroom not set for area."); pop_call(); return TRUE; } } act("$n and $s contingent swarm and subdue you, dragging you off!", ch, NULL, victim, TO_VICT); act("$n and $s contingent swarm and subdue $N, dragging $M off!", ch, NULL, victim, TO_NOTVICT); char_from_room(victim); char_to_room(victim, jailroom, TRUE); remove_bounty(bounty); save_bounties(); pop_call(); return TRUE; } } else // otherwise, join against criminal - Kregor { do_shout(ch, format("There is a criminal at %s! Alarm!", ch->in_room->name)); break; } } } if ( in_combat(victim) ) { if (IS_CITIZEN(victim)) { continue; } if (IS_NPC(victim) && victim->pIndexData == ch->pIndexData) { continue; } if ((ech = who_fighting(victim)) != NULL) { if (IS_CITIZEN(ech)) { do_shout(ch, format("There is an attacker at %s! Alarm!", ch->in_room->name)); if (get_record(victim, ch->pIndexData->area, CRIME_ASSAULT)) set_bounty(ech, victim, CRIME_ASSAULT); break; } if (!IS_NPC(victim) && !IS_NPC(ech)) { victim = NULL; break; } } if ( number_percent() < OPPOSITE_ALIGN( ch, victim ) ) { align_diff = TRUE; ech = victim; } } } if ( victim != NULL ) { fight( ch, victim ); pop_call(); return TRUE; } if ( ech != NULL ) { if (in_combat(ech) && !align_diff && number_percent() < 75) { do_say(ch, "Alright you miscreants, break it up NOW!"); act("$n muscles $s way in to stop the fighting.", ch, NULL, NULL, TO_ROOM); for ( victim = ch->in_room->first_person ; victim != NULL ; victim = v_next ) { v_next = victim->next_in_room; if (in_combat(victim)) clean_combat(victim->in_battle); } pop_call(); return TRUE; } do_shout( ch, format("Alarm! There is an attacker at %s", ch->in_room->name) ); fight( ch, ech ); pop_call(); return TRUE; } pop_call(); return FALSE; } bool spec_janitor( CHAR_DATA *ch ) { OBJ_DATA *trash; OBJ_DATA *trash_next; push_call("spec_janitor(%p)",ch); if ( !IS_AWAKE(ch) ) { pop_call(); return FALSE; } for ( trash = ch->in_room->first_content; trash != NULL; trash = trash_next ) { trash_next = trash->next_content; if ( !IS_SET( trash->wear_flags, CAN_WEAR_TAKE ) ) { continue; } if ( trash->item_type == ITEM_DRINK_CON || trash->item_type == ITEM_TRASH || trash->cost < 11 ) { act( "$n picks up some trash.", ch, NULL, NULL, TO_ROOM ); obj_from_room( trash ); obj_to_char( trash, ch ); if (trash->cost < 1) junk_obj(trash); pop_call(); return TRUE; } } pop_call(); return FALSE; } bool spec_thief( CHAR_DATA *ch ) { CHAR_DATA *victim; CHAR_DATA *v_next; int gold, roll, chance; push_call("spec_thief(%p)",ch); if ( !in_combat(ch) ) { if (ch->hit < get_max_hit(ch) / 4) { act( "$n skulks off to nurse $s wounds.", ch, NULL, NULL, TO_ROOM); char_from_room(ch); char_to_room(ch, 3, TRUE); pop_call(); return FALSE; } else { if ( ch->position != POS_STANDING && ch->pIndexData->position == POS_STANDING ) { do_stand(ch, ""); } } } if ( ch->position != POS_STANDING ) { pop_call(); return FALSE; } if (IS_AWAKE(ch) && !IS_AFFECTED(ch, AFF_HIDE)) { act( "$n {108}slips quietly into the shadows.", ch, NULL, NULL, TO_ROOM); SET_BIT(ch->affected_by, AFF_HIDE); } if (!IS_AFFECTED(ch, AFF_SNEAK)) { SET_BIT(ch->affected_by, AFF_SNEAK); } chance = number_bits(6); for ( victim = ch->in_room->first_person; victim != NULL; victim = v_next ) { v_next = victim->next_in_room; if ( chance != 0 || IS_NPC(victim) || /* victim->level >= LEVEL_IMMORTAL || */!can_see( ch, victim ) ) { if ( chance == 1 && can_see( victim, ch ) ) { act( "$n glances around, flexing $s hands...", ch, NULL, victim, TO_VICT); } continue; } if ( victim->gold <= 0 || victim->level <= 3 ) { continue; } roll = sleight_of_hand_roll(ch); if ( sleight_of_hand_check(ch, victim, roll, 20) ) { gold = victim->gold * number_range( 1, 5 ) / 100; gold = UMIN( gold, 5000 ); ch->gold += gold; gold_transaction(victim, 0 - gold); if ( IS_AWAKE(victim) && perception_check(victim, ch, perception_roll(victim, SENSE_TOUCH), roll ) ) { act( "You discover $n's hands in your money purse!", ch, NULL, victim, TO_VICT ); act( "$N discovers $n's hands in $S money purse!", ch, NULL, victim, TO_NOTVICT ); } pop_call(); return TRUE; } else { if ( IS_AWAKE(victim) && perception_check(victim, ch, perception_roll(ch, SENSE_SIGHT), roll ) ) { act( "You discover $n reaching for your money purse!", ch, NULL, victim, TO_VICT ); act( "$n is caught reaching for $N's money purse!", ch, NULL, victim, TO_NOTVICT ); } pop_call(); return TRUE; } } pop_call(); return FALSE; } /* * Anti-tank by Cronel (supfly@geocities.com), greatly modified, * giving credit where credit is due :) * * This is called from mobile_turn, for mobs fighting PCs only; * ch is the mob, victim is the player. Returns TRUE if "victim" * has changed, FALSE otherwise. * The code simply checks if there's someone else fighting this * mob in the room, and if this person is under 5 levels from the * one the mob is currently fighting, then powerlevel tanking has * been detected. The mobile will focus on the lowbie instead. * While not disallowing unbalanced tanking, this will make it * more of a challenge worth the XP and require the tank to be * very vigilant! */ CHAR_DATA *check_tanking( CHAR_DATA *ch, CHAR_DATA *victim ) { CHAR_DATA *lowbie; push_call("check_tanking(%p,%p)",ch,victim); if (!who_fighting(ch) || who_fighting(ch) != victim) { pop_call(); return victim; } /* if the tank is a sneak attacker, don't turn away, that would be stupid */ if (learned(victim, gsn_backstab)) { pop_call(); return victim; } for (lowbie = ch->in_room->first_person ; lowbie ; lowbie = lowbie->next_in_room) { if (lowbie == ch || lowbie == victim || IS_NPC(lowbie)) continue; // if lowbie is fighting at range, can't attack him if (!IS_SET(lowbie->attack, ATTACK_MELEE)) continue; if (who_fighting(lowbie) == ch && lowbie->level < victim->level - 5) { act( "$n turns to fight $N.", ch, NULL, lowbie, TO_NOTVICT ); act( "$n turns to fight YOU!", ch, NULL, lowbie, TO_VICT ); pop_call(); return lowbie; } } pop_call(); return victim; } /* * Queries whether mobile has mana to cast * a spell in the class he casts it in - Kregor */ bool has_mana(CHAR_DATA *ch, int sn) { int class, circle; push_call("has_mana(%p,%p)",ch,sn); if (!is_spell(sn)) { bug("has_mana: not a spell!", 0); pop_call(); return FALSE; } if ((class = multi(ch, sn)) == -1) { if (IS_NPC(ch)) { bug("has_mana: mobile %d cannot cast spell.", ch->pIndexData->vnum); pop_call(); return FALSE; } else { pop_call(); return FALSE; } } if ((circle = get_spell_circle(ch, sn)) == -1) { if (IS_NPC(ch)) { bug("has_mana: mobile %d cannot cast spell.", ch->pIndexData->vnum); pop_call(); return FALSE; } else { pop_call(); return FALSE; } } if (get_mana(ch, sn, circle, class) > ch->mana[class]) { if (!IS_NPC(ch)) { send_to_char("You do not have enough mana.\n\r", ch); pop_call(); return FALSE; } else { pop_call(); return FALSE; } } pop_call(); return TRUE; } /* * does the char have a certain class of effect active? * The core purpose of this function is to keep a * caster from spam casting multiple buffs or defenses * when he should be casting offensive spells in combat. */ bool has_affect(CHAR_DATA *ch, int type) { AFFECT_DATA *paf, *paf_next; push_call("has_affect(%p,%p)",ch,type); if (!ch->first_affect) { pop_call(); return FALSE; } for (paf = ch->first_affect ; paf != NULL ; paf = paf_next) { paf_next = paf->next; if (skill_table[paf->type].spell_class == type) { pop_call(); return TRUE; } } pop_call(); return FALSE; } /* * get a random spell, of the right type, of the * 3 highest spell circles available - Kregor */ int get_spell(CHAR_DATA *ch, int class, int type) { int circle, sn, pick; push_call("get_spell(%p,%p,%p)",ch,class,type); //first get the highest circle abailable for class for (circle = max_spell_circle(ch, class) ; circle >= 0 ; circle--) { if (get_mana(ch, -1, circle, class) <= ch->mana[class]) break; } if (!circle) { pop_call(); return -1; } // limit the number of attempts to pick a good spell // range should be the gsn of the lowest number spell, // to the gsn of the highest. for (pick = 0 ; pick < 100 ; pick++) { sn = number_range(gsn_acid_arrow, gsn_wraith_form); if (multi(ch, sn) != class) continue; if (skill_table[sn].spell_class != type) continue; if (skill_table[sn].skill_level[class] > circle) continue; if (skill_table[sn].skill_level[class] < UMAX(0, circle - 3)) continue; break; } if (pick == 100) { pop_call(); return -1; } pop_call(); return sn; } /* * AI for spell casters during combat * controlled in mobile_turn in combat_handler - Kregor */ bool caster_AI_combat(CHAR_DATA *ch, CHAR_DATA *victim) { int cls, sn; char buf[MAX_INPUT_LENGTH]; CHAR_DATA *vch, *vch_next; push_call("caster_AI_combat(%p,%p)",ch,victim); /* if not a caster, this function is a waste of time */ if (!is_caster(ch)) { pop_call(); return FALSE; } /* can't cast without a standard action, so skip of you can't take one */ if (IS_SET(ch->action, ACTION_STANDARD)) { pop_call(); return FALSE; } //bard will sing first if needed and can if (learned(ch, gsn_bardic_song)) { if (!is_affected(ch, gsn_bardic_song) && CHECK_USES(ch, gsn_bardic_song)) { do_sing(ch, ""); pop_call(); return TRUE; } } /* default to chosen class (hard coded class) */ cls = ch->class; /* if primary caster class is out of mana, try another */ if (!ch->mana[ch->class]) { for (cls = 0 ; cls < MAX_CORE_CLASS ; cls++) { if (class_level(ch, cls) && ch->mana[cls] > 0) break; } } /* unless wiz/sorcerer, fall back to melee if no mana */ if (!ch->mana[cls] && ch->class != CLASS_WIZARD && ch->class != CLASS_SORCERER) { do_say(ch, "Awwwww, I'm outta mana!"); pop_call(); return FALSE; } /* if sorc/wiz, cast damage cantrips */ else if (!ch->mana[cls]) { switch (number_range(1,4)) { case 1: do_cast(ch, "'electric jolt'"); pop_call(); return TRUE; case 2: do_cast(ch, "'acid splash'"); pop_call(); return TRUE; case 3: do_cast(ch, "'flame tongue'"); pop_call(); return TRUE; case 4: do_cast(ch, "'ray of frost'"); pop_call(); return TRUE; } } /* will protect himself if unprotected at battle start */ if (!has_affect(ch, STYPE_DEFENSE)) { if ((sn = get_spell(ch, cls, STYPE_DEFENSE)) != -1) { sprintf(buf, "'%s'", skill_table[sn].name); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } /* healers' triage */ if (ch->class == CLASS_CLERIC || ch->class == CLASS_DRUID || ch->class == CLASS_PALADIN || ch->class == CLASS_RANGER) { /* If hurt, healer heal thyself */ if (ch->hit < get_max_hit(ch) / 2) { if ((sn = get_spell(ch, ch->class, STYPE_HEAL)) != -1) { sprintf(buf, "'%s'", skill_table[sn].name); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } /* divine casters have 50/50 chance of just going into combat */ if (number_bits(1) == 0) { pop_call(); return FALSE; } if (ch->class == CLASS_CLERIC || ch->class == CLASS_DRUID) { /* else, check for injured comrades */ for (vch = ch->in_room->first_person ; vch ; vch = vch_next) { vch_next = vch->next; if (is_same_group(vch, ch) && vch->hit < get_max_hit(vch) / 3) { if ((sn = get_spell(ch, ch->class, STYPE_HEAL)) != -1) { sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(vch), 1)); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } /* or maybe buff one if they need it */ if (is_same_group(vch, ch) && number_bits(2) == 0 && !has_affect(vch, STYPE_BUFF)) { if ((sn = get_spell(ch, cls, STYPE_BUFF)) != -1) { sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(vch), 1)); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } } } } /* functions only trigger if there is an opponent chosen in mobile_turn */ if (victim) { if (number_bits(2) == 0) /* debuff his opponent 1 in 4, otherwise go on offensive! */ { if (has_affect(victim, STYPE_BUFF)) { if ((sn = get_spell(ch, cls, STYPE_DEBUFF)) != -1) { sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1)); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } } if (number_bits(2) == 0) /* curse opponent 1 in 4 */ { if (!has_affect(victim, STYPE_CURSE)) { if ((sn = get_spell(ch, cls, STYPE_CURSE)) != -1) { sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1)); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } } if ((sn = get_spell(ch, cls, STYPE_ATTACK)) != -1) { sprintf(buf, "'%s' %s", skill_table[sn].name, short_to_name(get_name(victim), 1)); if (cast_spell(ch, buf, FALSE)) { pop_call(); return TRUE; } } } pop_call(); return FALSE; } char * const cm_flags [] = { "flurry", "manyshot", "rapidshot", "*", "*", "nonlethal", "power", "defensive", "called", "*", "arterial", "cripple", "*" }; /* * AI for warrior classes during combat * controlled in mobile_turn in combat_handler - Kregor */ bool warrior_AI_combat(CHAR_DATA *ch, CHAR_DATA *victim) { int chance; OBJ_DATA *wield; push_call("warrior_AI_combat(%p,%p)",ch,victim); if (victim == NULL) { pop_call(); return FALSE; } /* get weapons for checking later */ wield = get_wield(ch, FALSE); /* no weapon?? look for one! */ if (!wield) { // switch hands with offhand = free action if ((wield = get_wield(ch, TRUE)) != NULL) { wield->wear_loc = WEAR_WIELD; } // draw another weapon in inventory = move action else if ((wield = get_obj_carry_type(ch, ITEM_WEAPON)) != NULL) { act("$n draws $p.", ch, wield, NULL, TO_ROOM); equip_char(ch, wield, WEAR_WIELD); if (!learned(ch, gsn_quick_draw)) TAKE_ACTION(ch, ACTION_MOVE); pop_call(); return TRUE; } // scan the ground for disarmed weapon. Grab it! else if (ch->pIndexData->load_eq[WEAR_WIELD] || ch->pIndexData->load_eq[WEAR_BOTH_HANDS]) { for (wield = ch->in_room->first_content ; wield ; wield = wield->next_content) { if (IS_OBJ_TYPE(wield, ITEM_WEAPON)) { if ((ch->pIndexData->load_eq[WEAR_WIELD] && wield->pIndexData->vnum == ch->pIndexData->load_eq[WEAR_WIELD]) || (ch->pIndexData->load_eq[WEAR_BOTH_HANDS] && wield->pIndexData->vnum == ch->pIndexData->load_eq[WEAR_BOTH_HANDS])) { if (get_obj(ch, wield, NULL, TRUE, FALSE)) { act("$n recovers $p from the ground.", ch, wield, NULL, TO_ROOM); act("$n wields $p.", ch, wield, NULL, TO_ROOM); equip_char(ch, wield, WEAR_WIELD); TAKE_ACTION(ch, ACTION_MOVE); pop_call(); return TRUE; } } } } } } /* set combat modes for NPCs that have them. */ if (learned(ch, gsn_power_attack)) { if (GET_HITROLL(ch, victim, TYPE_UNDEFINED, wield) / 2 <= calc_ac(victim, ch, FALSE)) { SET_BIT(ch->combatmode, COMBAT_POWER); } } if (learned(ch, gsn_combat_expertise)) { if (GET_HITROLL(victim, ch, TYPE_UNDEFINED, wield) / 2 <= calc_ac(ch, victim, FALSE)) { SET_BIT(ch->combatmode, COMBAT_DEFENSIVE); } } if (learned(ch, gsn_flurry) && !wield) { SET_BIT(ch->combatmode, COMBAT_FLURRY); } if (learned(ch, gsn_arterial_strike) && number_percent() < 50) { SET_BIT(ch->combatmode, COMBAT_ARTERIAL); } else if (learned(ch, gsn_arterial_strike) && number_percent() < 50) { SET_BIT(ch->combatmode, COMBAT_CRIPPLE); } if (IS_ACT(ch, ACT_SUBDUAL)) { SET_BIT(ch->combatmode, COMBAT_NONLETHAL); } if (ch->combatmode) { wiz_printf_room(ch, "warrior_AI_combat: combatmodes: %s\n\r", flag_string(ch->combatmode, cm_flags)); } /* some special abilities here to start the battle with */ if (learned(ch, gsn_barbarian_rage) && !IS_AFFECTED(ch, AFF2_BERSERK) && CHECK_USES(ch, gsn_barbarian_rage)) { ability_rage(gsn_barbarian_rage, multi_skill_level(ch, gsn_barbarian_rage), ch, NULL, TAR_IGNORE); ch->uses[gsn_barbarian_rage]++; pop_call(); return TRUE; } if (race_skill(ch, gsn_blood_rage) && !IS_AFFECTED(ch, AFF2_BERSERK) && ch->hit < get_max_hit(ch)) { ability_rage(gsn_blood_rage, multi_skill_level(ch, gsn_blood_rage), ch, NULL, TAR_IGNORE); pop_call(); return TRUE; } if (learned(ch, gsn_smite) && CHECK_USES(ch, gsn_smite)) { if ((IS_GOOD(ch) && !IS_EVIL(victim)) || (IS_EVIL(ch) && !IS_GOOD(victim))) { if (!is_affected(victim, gsn_smite)) { ability_smite(gsn_smite, multi_skill_level(ch, gsn_smite), ch, victim, TAR_CHAR_OFFENSIVE); ch->uses[gsn_smite]++; pop_call(); return TRUE; } } } /* nasty rend attacks will be used half the time */ if (race_skill(ch, gsn_rend) && number_bits(1) == 0) { if (!IS_SET(ch->action, ACTION_FULL) && rend(ch, victim)) { pop_call(); return TRUE; } } /* if not rend, then how about a pounce? */ if (race_skill(ch, gsn_pounce) && number_bits(1) == 0) { if (!IS_SET(ch->action, ACTION_FULL) && pounce(ch, victim)) { pop_call(); return TRUE; } } /* * NPC will only attempt a combat maneuver * if they have the feat hard set - Kregor */ if (!IS_SET(ch->attack, ATTACK_MELEE)) { chance = number_range(1,10); // 50% of the time will just be a normal attack switch(chance) { case 1: if (disarm(ch, victim, 0)) { pop_call(); return TRUE; } break; case 2: if (trip(ch, victim, 0)) { pop_call(); return TRUE; } break; case 3: if (bash(ch, victim, 0)) { pop_call(); return TRUE; } break; case 4: if (stun(ch, victim, 0)) { pop_call(); return TRUE; } break; case 5: if (feint(ch, victim, 0)) { pop_call(); return TRUE; } break; } } if (attack(ch, victim)) { pop_call(); return TRUE; } pop_call(); return FALSE; } /* * Could use some better messages, figure out how to * make better, more race-appropriate messages - Kregor */ void found_hating( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("found_hating(%p,%p)",ch,victim); if (in_combat(ch)) { pop_call(); return; } if (!can_see(ch, victim)) { if (number_bits(2) != 0) { pop_call(); return; } if (CAN_TALK(ch)) { switch (number_bits(2)) { case 0: command(ch, do_say, "I can smell your blood %s!", victim->sex == SEX_FEMALE ? "bitch" : "bastard"); break; case 1: command(ch, do_say, "Where are you? Come out here and die!"); break; case 2: command(ch, do_say, "Just wait until I find you! Then you'll pay!"); break; case 3: command(ch, do_say, "You're dead! Just as soon as I get my hands on you!"); break; } } switch (number_bits(2)) { case 0: act( "$n staggers around blindly, attacking the air!", ch, NULL, NULL, TO_ROOM); break; case 1: act( "$n lurches around the area trying to find something to attack!", ch, NULL, NULL, TO_ROOM); break; case 2: act( "$n growls in frustration as $e tries to attack unseen enemies!", ch, NULL, NULL, TO_ROOM); break; case 3: act( "$n snarls quietly as $e attacks thin air!", ch, NULL, NULL, TO_ROOM); break; } pop_call(); return; } if (is_safe(ch, NULL)) { if (number_bits(2) != 0) { pop_call(); return; } if (CAN_TALK(ch)) { switch (number_bits(2)) { case 0: command(ch, do_say, "You're a coward! Fight me!"); break; case 1: command(ch, do_say, "That's right, try to hide from me you coward!"); break; case 2: command(ch, do_say, "C'mon, fight you craven dog!"); break; case 3: command(ch, do_say, "Only a coward would hide here!"); break; } } pop_call(); return; } if (CAN_TALK(ch)) { switch (number_bits(2)) { case 0: command(ch, do_say, "I'll get you now! Die!"); break; case 1: command(ch, do_say, "We were not done yet!"); break; case 2: command(ch, do_say, "Defend yourself!"); break; case 3: command(ch, do_say, "Prepare for your death!"); break; } } else { switch (number_bits(2)) { case 0: act( "$n contorts $s face and surges to the attack!", ch, NULL, NULL, TO_ROOM); break; case 1: act( "$n snarls and leaps to the attack!", ch, NULL, NULL, TO_ROOM); break; case 2: act( "$n growls and lunges forwards!", ch, NULL, NULL, TO_ROOM); break; case 3: act( "$n grinds $s teeth and leaps forwards!", ch, NULL, NULL, TO_ROOM); break; } } fight(ch, victim); pop_call(); return; } void stop_hate_fear( CHAR_DATA *ch ) { push_call("stop_hate_fear(%p)",ch); if (IS_NPC(ch) && ch->npcdata->hate_fear) { ch->npcdata->hate_fear = 0; } pop_call(); return; } /* * Mobile hunting code, by Mikko Kilpikoski E-Mail: turtle@modeemi.cs.tut.fi * Edited to use player vnum instead of crash happy char_data pointer - Kregor */ bool hunt_victim( CHAR_DATA *ch ) { int dir, range; bool found = FALSE; CHAR_DATA *tmp; CHAR_DATA *hunted; push_call("hunt_victim(%p)",ch); if (!IS_NPC(ch) || ch->in_room == NULL) { pop_call(); return FALSE; } if ((hunted = get_char_pvnum(ch->npcdata->hate_fear)) == NULL) { pop_call(); return FALSE; } if (in_combat(ch)) { pop_call(); return FALSE; } if (!IS_AWAKE(ch)) { pop_call(); return FALSE; } if (IS_ACT(ch, ACT_AGGRESSIVE) || ch->hit >= get_max_hit(ch) / 2) { pop_call(); return FALSE; } if (IS_AFFECTED(ch, AFF2_FEAR) || ch->fear_level) { pop_call(); return FALSE; } if (ch->position < POS_STANDING) { do_stand(ch, ""); pop_call(); return TRUE; } /* * Make sure the victim still exists, else clear out hate_fear */ for (found = FALSE, tmp = mud->f_char; tmp && !found; tmp = tmp->next) { if (hunted == tmp) { found = TRUE; } } if (!found) { if (CAN_TALK(ch)) { act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM); } stop_hate_fear(ch); pop_call(); return TRUE; } if (ch->in_room == hunted->in_room) { found_hating(ch, hunted); pop_call(); return TRUE; } // longer hunting distances based on tracking and scent ability - Kregor if (learned(ch, gsn_track)) range = 50; else range = 20; if (race_skill(ch, gsn_scent)) range *= 2; if ((dir = findpath_search_victim(ch, hunted, range)) == -1) { if (CAN_TALK(ch)) { act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM); } stop_hate_fear(ch); pop_call(); return TRUE; } if (can_see_dir(ch, hunted, dir)) { /* * AI missile/spell range attack goes here. * return to stop here if makes successful action * else let them persue - Kregor */ } // NPC will open doors to follow when it can if (IS_SET(ch->in_room->exit[dir]->exit_info, EX_CLOSED)) { if (is_handy(ch)) { do_open( ch, (char *) dir_name[dir] ); if (IS_SET(ch->in_room->exit[dir]->exit_info, EX_CLOSED)) { if (CAN_TALK(ch)) { act("$n curses losing $s prey.", ch, NULL, NULL, TO_ROOM); } stop_hate_fear(ch); pop_call(); return TRUE; } pop_call(); return TRUE; } } move_char( ch, dir, TRUE, NULL ); pop_call(); return TRUE; }