/*************************************************************************** * 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. * ***************************************************************************/ /************************************************************************** * File: combat_handler.c * * Author: Midboss * * Purpose: Core functionality for turn-based combat on ROM MUDs. * * Modified by Kregor for the Mud20 System, Jan 2008 * **************************************************************************/ #include "mud.h" /* * Guards come to the rescue of NPC citizens in distress. * Note that putting this in combat_update does in fact mean that the more ACT_CITIZENs * there are involved in the battle (this should include guards) * The more often another guard will be summoned. This is the desired * effect and should aid in area razing defense - Kregor */ void check_guard_assist( CHAR_DATA *ch, CHAR_DATA *attacker ) { MOB_INDEX_DATA *mobIndex; CHAR_DATA *guard; push_call("check_guard_assist(%p)", ch); if (IS_ACT(ch, ACT_CITIZEN) && ch->in_room->area == ch->pIndexData->area) { if (number_bits(3) == 0) { if ((mobIndex = get_mob_index(ch->pIndexData->area->guard)) == NULL) log_build_printf(ch->pIndexData->vnum, "mobile_turn (%d): guard data for area invalid."); else { guard = create_mobile(mobIndex); char_to_room( guard, ch->in_room->vnum, TRUE ); guard->npcdata->sac_timer = 60; RESTRING(guard->npcdata->sac_string, "$n departs the vicinity as the turmoil calms."); act("$n comes to the aid of $N.", guard, NULL, ch, TO_ROOM); if (attacker != NULL) { fight(guard, attacker); } } } } pop_call(); return; } /* * Updater for what few autonomous functions used in this combat system. * Mostly just triggers mobile's turns and updates battlefield conditions. * Also controls the automatic turn skip when someone tries to stall. */ void combat_update (void) { COMBAT_DATA * battle, * battle_next; CHAR_DATA * ch; push_call("combat_update()"); for (battle = battle_list; battle != NULL; battle = battle_next) { battle_next = battle->next; if (battle->turn_list->roundtime >= 2000000000) nuke_roundtime (battle); ch = battle->turn_list->unit; if (ch == NULL) { bug ("combat_update (): Turn with NULL character!", 0); clean_combat (battle); pop_call(); return; } if (!IS_NPC(ch) && ch->desc == NULL) linkdead_turn (ch); else if (!IS_AWAKE(ch)) // unconscious/mortal stay in combat, but only to lose affects and bleed turn_end (battle->turn_list, 200 - ch->initiative); else if (!mounted_combat_check(ch)) // failure to control mount in combat forfeits turn turn_end (battle->turn_list, 200 - ch->initiative); else if (IS_AFFECTED(ch, AFF2_DAZED)) { act("You are too dazed to fight.", ch, NULL, NULL, TO_CHAR); act("$n simply stands around in a daze.", ch, NULL, NULL, TO_ROOM); turn_end (battle->turn_list, 200 - ch->initiative); } else if (is_affected(ch, gsn_irresistible_dance)) irresistible_dance(ch); else if (IS_AFFECTED(ch, AFF2_CONFUSION)) // confusion can override attacks confused_turn(ch); else if (IS_NPC (ch) && !ch->desc) mobile_turn (ch); else { battle->turn_list->timer++; } if (battle->turn_list->timer >= 20) //two minutes to end a turn seems reasonable? Kregor turn_end (battle->turn_list, 200 - ch->initiative); if (IS_PLR(ch, PLR_WIZTIME)) { if (ch->last_attacked != NULL) ch_printf_color(ch, "{058}DEBUG: you last attacked %s.\n\r", get_name(ch->last_attacked)); } check_victory (battle); } pop_call(); return; } /* * Checks to see if a char has taken all the * actions they can take in a turn, if so, then * advance to the next turn - Kregor */ bool turn_complete( CHAR_DATA *ch ) { TURN_DATA *turn; push_call("turn_complete(%p)",ch); if (!in_combat(ch)) { pop_call(); return FALSE; } if (!is_active(ch)) { pop_call(); return FALSE; } if ((turn = get_turn_char(ch)) == NULL) { pop_call(); return FALSE; } if (ch->casting || ch->concentrating) { pop_call(); return FALSE; } if (IS_SET(ch->action, ACTION_STANDARD) && IS_SET(ch->action, ACTION_MOVE)) { turn_end (turn, 200 - ch->initiative); pop_call(); return TRUE; } pop_call(); return FALSE; } /* * Lets a mobile take its turn. * All the AI functions happen here. All functions should control their * own action space, and should all pass both ch and victim. The loop * for deciding a target may not yet be the best. - Kregor */ void mobile_turn (CHAR_DATA * ch) { CHAR_DATA *victim, *vch = NULL, *list; TURN_DATA *turn; push_call("mobile_turn(%p)",ch); if (!IS_NPC(ch)) { pop_call(); return; } if ((turn = get_turn_char(ch)) == NULL) { pop_call(); return; } if (!IS_AWAKE(ch)) { turn_end (turn, 200 - ch->initiative); pop_call(); return; } if (!in_combat(ch)) { pop_call(); return; } // hit percent prog triggers before aggression, overrides AI - Kregor if ((victim = who_fighting(ch)) != NULL && mprog_hitprcnt_trigger(ch, victim)) { TAKE_ACTION(ch, ACTION_FULL); pop_call(); return; } // Select the target of aggression if not already focused on someone if (!victim) { for (vch = ch, list = ch->in_battle->unit_list; list != NULL; list = list->next_in_battle) { if (!is_same_group(ch, list)) { if (vch == ch) vch = list; else if (who_fighting(list) && who_fighting(list) == ch) { vch = list; break; } else if (vch != ch && number_percent () < 45) vch = list; else continue; } else if ((vch = who_fighting(list)) != NULL) { vch = list; break; } } } if (vch == ch) { clean_combat (ch->in_battle); pop_call(); return; } else if (victim) { vch = check_tanking(ch, victim); } if (IS_AWAKE(ch) && ch->position < POS_FIGHTING) { do_stand(ch, ""); } if (!is_safe(ch, vch)) // if safe, end turn { // fight_prog takes over turn, overrides regular AI attack - Kregor if (mprog_fight_trigger(ch, vch)) { TAKE_ACTION(ch, ACTION_FULL); } else if (!IS_SET(ch->act, ACT_NOFIGHT)) // the AI functions for combat need to go here! { if (breath_weapon(ch, vch)) { pop_call(); return; } if (gaze_attack(ch, vch)) { pop_call(); return; } if (caster_AI_combat(ch, vch)) { pop_call(); return; } if (warrior_AI_combat(ch, vch)) { pop_call(); return; } else { do_say(ch, "Awwwwww! I'm outta options!"); } } } TAKE_ACTION(ch, ACTION_FULL); pop_call(); return; } /* * Hacking mobile_turn to handle confused characters - Kregor */ void confused_turn(CHAR_DATA * ch) { CHAR_DATA *victim, *vch = NULL; TURN_DATA *turn; push_call("confused_turn(%p)",ch); if ((turn = get_turn_char(ch)) == NULL) { pop_call(); return; } if (!IS_AWAKE(ch)) { turn_end (turn, 200 - ch->initiative); pop_call(); return; } if (!in_combat(ch)) { pop_call(); return; } // Select the target of aggression if not already focused on someone if ((victim = who_fighting(ch)) == NULL) { for (vch = ch->in_battle->unit_list ; vch ; vch = ch->next_in_battle) { if (is_master(vch, ch)) // because I'm nice, skip over pets - Kregor continue; if (vch->last_attacked && vch->last_attacked == ch) { break; } if (number_bits(1) == 0) { break; } } } // select collateral damage if (vch == NULL) { for (vch = ch->in_room->first_person ; vch ; vch = vch->next) { if (is_master(vch, ch)) continue; if (vch->last_attacked && vch->last_attacked == ch) { break; } if (number_bits(1) == 0) { break; } } } if (!victim) victim = vch; switch (number_range(1,4)) { case 1: attack(ch, vch); attack(ch, vch); break; case 2: act("$n stands around, babbling nonsense.", ch, NULL, NULL, TO_ROOM); act("You stand around, babbling nonsense.", ch, NULL, NULL, TO_CHAR); break; case 3: act("You swing wildly, hitting yourself in the process!", ch, NULL, NULL, TO_CHAR); act("$n swings wildly, only hitting himself as a result!", ch, NULL, NULL, TO_ROOM); damage(ch, ch, dice(1,8) + stat_bonus(TRUE, ch, APPLY_STR), TYPE_NOFIGHT, NULL); break; case 4: attack(ch, vch); attack(ch, vch); break; } TAKE_ACTION(ch, ACTION_FULL); pop_call(); return; } /* * If link dead, remove from combat - Kregor */ void linkdead_turn (CHAR_DATA * ch) { push_call("linkdead_turn(%p)",ch); char_from_combat (ch); pop_call(); return; } /* * This will end a combat, and notify the victorious party. */ void check_victory (COMBAT_DATA * battle) { CHAR_DATA *leader; CHAR_DATA *unit; bool victory = TRUE; bool found = FALSE; push_call("check_victory(%p)",battle); //First look for PCs. Since mobs don't gain, if only mobs are left, we abort. for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) if (!IS_NPC (unit)) found = TRUE; if (!found) { clean_combat (battle); return; } for (leader = battle->unit_list; leader != NULL; leader = leader->next_in_battle) { if (!IS_AWAKE(leader)) continue; //Once we find the first guy that isn't out, we look for another guy that //isn't out, and isn't on his team. If one isn't found, battle's over. for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) { if ((!IS_AWAKE(unit) && !is_same_group (leader, unit)) || is_same_group (leader, unit) || leader == unit) continue; else { victory = FALSE; break; } } if (!victory) continue; else { for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) { if (is_same_group (leader, unit)) act ("{138}You have won the battle!", unit, NULL, NULL, TO_CHAR); else act ("{138}You have lost the battle!", unit, NULL, NULL, TO_CHAR); } clean_combat (battle); pop_call(); return; } } pop_call(); return; } /* * True if ch is part of any combat. */ bool in_combat (CHAR_DATA * ch) { push_call("in_combat(%p)",ch); if (ch->in_battle) { pop_call(); return TRUE; } pop_call(); return FALSE; } /* * True if ch and victim are both in same combat. */ bool in_same_combat (CHAR_DATA * ch, CHAR_DATA *victim) { push_call("in_combat(%p)",ch); if (ch == NULL || victim == NULL) { pop_call(); return FALSE; } if (!ch->in_battle || victim->in_battle) { pop_call(); return FALSE; } if (ch->in_battle != victim->in_battle) { pop_call(); return FALSE; } pop_call(); return TRUE; } /* * True if ch may take a turn right now. */ bool is_active (CHAR_DATA * ch) { push_call("is_active(%p)",ch); if (!in_combat (ch)) { pop_call(); return FALSE; } if (ch->turn == ch->in_battle->turn_list) { pop_call(); return TRUE; } pop_call(); return FALSE; } /* * initiative per D20 SRD - Kregor */ void roll_initiative( CHAR_DATA *ch ) { int init; push_call("roll_initiative(%p)", ch); if (arcane_mastery(ch, SCHOOL_DIVINATION)) init = 20 + stat_bonus(TRUE, ch, APPLY_DEX); else init = dex_roll(ch); if (learned(ch, gsn_imp_initiative)) init += 4; if (learned(ch, gsn_alertness)) init += 2; if (learned(ch, gsn_first_strike) && armor_type_worn(ch) == ARMOR_NONE && get_eq_char(ch, WEAR_SHIELD) == NULL) { init += multi_class_level(ch, gsn_first_strike) / 2; } if (get_monk_style(ch) == STYLE_INVISIBLE_EYE && class_level(ch, CLASS_MONK) >= 12) init += 2; if (IS_AFFECTED(ch, AFF2_HALLUCINATE)) init -= 4; ch->initiative = UMAX(1, init); if (IS_PLR(ch, PLR_WIZTIME)) ch_printf_color(ch, "DEBUG: You score %d for initiative.\n\r", ch->initiative); pop_call(); return; } /* * Inserts a character into combat and adds their turn to the list. */ void char_to_combat (CHAR_DATA * ch, COMBAT_DATA * combat) { TURN_DATA * turn; AFFECT_DATA af; push_call("char_to_combat(%p,%p)",ch,combat); if (combat->unit_list == NULL) combat->unit_list = ch; else { ch->next_in_battle = combat->unit_list; combat->unit_list = ch; } ch->in_battle = combat; roll_initiative(ch); turn = new_turn (); turn->unit = ch; turn->in_battle = combat; //So people don't end up getting a dozen turns by joining in. if (combat->turn_count > 0) turn->roundtime = (combat->turn_list->roundtime + 200) - ch->initiative; else turn->roundtime = 500 - ch->initiative; ch->turn = turn; ch->action = 0; if (IS_STAGGERED(ch)) ch->action = ACTION_MOVE; if (IS_FLYING(ch)) TAKE_ACTION(ch, ACTION_MOVE); ch->attack = 0; ch->attack_part = -1; insert_turn (combat, turn); // check racial abilities that only happen once in a fight frightful_presence(ch); check_aura(ch, gsn_aura_of_menace); check_aura(ch, gsn_babble); if (learned(ch, gsn_daring_charge)) { af.type = gsn_daring_charge; af.level = multi_class_level(ch, gsn_daring_charge); af.duration = 1; af.bittype = AFFECT_TO_NONE; af.bitvector = AFF_NONE; af.location = APPLY_MOR_TOHIT; af.modifier = af.level; affect_join( ch, ch, &af); af.bitvector = AFF_NONE; af.location = APPLY_MOR_DAMG; af.modifier = af.level; affect_join( ch, ch, &af); ch->uses[gsn_daring_charge]++; } pop_call(); return; } /* * Removes a ch and their turn entry from combat. */ void char_from_combat (CHAR_DATA * ch) { CHAR_DATA * prev; push_call("char_from_combat(%p)",ch); if (ch == NULL) { bug ("char_from_combat() -- null ch!?", 0); pop_call(); return; } stop_fighting(ch, TRUE); if (!in_combat(ch)) { pop_call(); return; } if (ch->in_battle->unit_list == ch) { ch->in_battle->unit_list = ch->next_in_battle; ch->next_in_battle = NULL; } else { for (prev = ch->in_battle->unit_list; prev != NULL; prev = prev->next_in_battle) { if (prev->next_in_battle == ch) { prev->next_in_battle = ch->next_in_battle; ch->next_in_battle = NULL; break; } } if (prev == NULL) { bug ("char_from_combat() -- ch not found.", 0); pop_call(); return; } } remove_turn (ch->turn); ch->turn = NULL; ch->in_battle = NULL; // strip all effects that only last until end of fight if (is_affected(ch, gsn_barbarian_rage)) { affect_strip(ch, gsn_barbarian_rage); if (!learned(ch, gsn_tireless_rage)) { int cost = get_max_move(ch) / 2; move_loss(ch, NULL, cost); } } if (is_affected(ch, gsn_blood_rage)) affect_strip(ch, gsn_blood_rage); if (is_affected(ch, gsn_smite)) affect_strip(ch, gsn_smite); if (is_affected(ch, gsn_retributive_strike)) affect_strip(ch, gsn_retributive_strike); stop_fighting(ch, TRUE); pop_call(); return; } /* * Returns the data for ch's next/current turn. */ TURN_DATA * get_turn_char (CHAR_DATA * ch) { TURN_DATA * turn; push_call("get_turn_char(%p)",ch); if (!in_combat (ch)) { pop_call(); return NULL; } for (turn = ch->in_battle->turn_list; turn != NULL; turn = turn->next) { if (turn->unit == ch) { pop_call(); return turn; } } pop_call(); return NULL; } /* * Inserts a turn into its proper position in the specified list. */ void insert_turn (COMBAT_DATA * combat, TURN_DATA * nTurn) { push_call("insert_turn(%p,%p)",combat,nTurn); if (combat->turn_list == NULL) { combat->turn_list = nTurn; nTurn->in_battle = combat; } else { TURN_DATA * prev; if (combat->turn_list->roundtime > nTurn->roundtime) { nTurn->next = combat->turn_list; combat->turn_list = nTurn; nTurn->in_battle = combat; } else { for (prev = combat->turn_list; prev != NULL; prev = prev->next) { if (prev->next != NULL && prev->next->roundtime > nTurn->roundtime) { nTurn->next = prev->next; prev->next = nTurn; nTurn->in_battle = combat; break; } else if (prev->next == NULL) { prev->next = nTurn; nTurn->in_battle = combat; break; } } } } pop_call(); return; } /* * Removes a turn from the list without freeing it. * It isn't freed here because you have to use this every time you move a turn * to a new position in the list. */ void remove_turn (TURN_DATA * turn) { TURN_DATA * prev; push_call("remove_turn(%p)",turn); if (turn == NULL) { bug ("remove_turn() -- null turn!?", 0); pop_call(); return; } if (turn == turn->in_battle->turn_list) { turn->in_battle->turn_list = turn->next; turn->next = NULL; } else { for (prev = turn->in_battle->turn_list; prev != NULL; prev = prev->next) { if (prev->next == turn) { prev->next = turn->next; turn->next = NULL; break; } } } pop_call(); return; } /* * Similar to roundtime (), but used specifically at the end of each action. */ void turn_end (TURN_DATA * turn, int roundtime) { CHAR_DATA *grappled; CHAR_DATA *caster; CHAR_DATA *ch, *ch_next; int lvl; push_call("turn_end(%p,%p)",turn,roundtime); turn->timer = 0; turn->roundtime += roundtime; ch = turn->unit; ch_next = turn->next->unit; if (!IS_NPC(ch)) ch->pcdata->last_combat = mud->current_time; if (IS_SET(ch->in_room->progtypes, FIGHT_PROG)) rprog_fight_trigger(ch); // If grappling at end of affected char's turn, do damage - Kregor if ((grappled = who_grappling(ch)) != NULL) { if ((lvl = get_affect_level(ch, gsn_fire_shield)) > 0 && !save_resist(ch, grappled, gsn_fire_shield, lvl)) { damage(ch, grappled, dice(1,6) + UMIN(lvl,15), gsn_fire_shield, NULL); } else if ((lvl = get_affect_level(ch, gsn_thorn_body)) > 0 && !save_resist(ch, grappled, gsn_thorn_body, lvl)) { damage(ch, grappled, dice(1,6) + UMIN(lvl,15), gsn_thorn_body, NULL); } else if ((lvl = race_skill(ch, gsn_barbed_touch)) > 0) { damage(ch, grappled, dice(1,lvl), gsn_barbed_touch, NULL); } else if ((lvl = race_skill(ch, gsn_fire_touch)) > 0) { damage(ch, grappled, dice(1,lvl), gsn_fire_touch, NULL); } else if ((lvl = race_skill(ch, gsn_frost_touch)) > 0) { damage(ch, grappled, dice(1,lvl), gsn_frost_touch, NULL); } else if ((lvl = race_skill(ch, gsn_shock_touch)) > 0) { damage(ch, grappled, dice(1,lvl), gsn_shock_touch, NULL); } else if ((lvl = race_skill(ch, gsn_acid_touch)) > 0) { damage(ch, grappled, dice(1,lvl), gsn_acid_touch, NULL); } } act( "You have ended your turn.", ch, NULL, NULL, TO_CHAR); act( "$n has ended $s turn.", ch, NULL, NULL, TO_ROOM); damage_update(ch); affect_update(ch); obj_char_update(ch); act( "It is now your turn.", ch_next, NULL, NULL, TO_CHAR); // update cleric with status of his comrades. if (is_affected(ch_next, gsn_status)) { show_party_line(ch_next); } check_guard_assist(ch, NULL); ch_next->action = 0; if (IS_STAGGERED(ch_next)) { act("You are staggered and can only take a partial action.", ch_next, NULL, NULL, TO_CHAR); ch_next->action = ACTION_MOVE; } if (is_mounting(ch_next) && !learned(ch_next->mounting, gsn_combat_rearing)) { act("You take a move action to control your mount.", ch_next, NULL, NULL, TO_CHAR); TAKE_ACTION(ch_next, ACTION_MOVE); } if (IS_FLYING(ch_next)) { act("You take a move action to remain airborne.", ch_next, NULL, NULL, TO_CHAR); TAKE_ACTION(ch_next, ACTION_MOVE); } ch_next->attack = 0; ch_next->attack_part = -1; if (is_affected(ch_next, gsn_crushing_hand)) { if (IS_AFFECTED(ch_next, AFF_FREEDOM) || (caster = get_caster(ch_next, gsn_crushing_hand)) == NULL || combat_maneuver_check(caster, ch_next, gsn_crushing_hand) < 0) { act("{038}You break free from the hand's grasp!", ch, NULL, NULL, TO_CHAR); act("{038}$n breaks free from the hand's grasp!", ch, NULL, NULL, TO_ROOM); affect_strip(ch_next, gsn_crushing_hand); } else { damage(caster, ch_next, dice(2,6)+12, gsn_crushing_hand, NULL); } TAKE_ACTION(ch_next, ACTION_MOVE); } else if (is_affected(ch_next, gsn_grasping_hand)) { if (IS_AFFECTED(ch_next, AFF_FREEDOM) || (caster = get_caster(ch_next, gsn_grasping_hand)) == NULL || combat_maneuver_check(caster, ch_next, gsn_grasping_hand) < 0) { act("{038}You break free from the hand's grasp!", ch, NULL, NULL, TO_CHAR); act("{038}$n breaks free from the hand's grasp!", ch, NULL, NULL, TO_ROOM); affect_strip(ch_next, gsn_grasping_hand); } else { act("{118}The giant hand of force holds you fast!", ch, NULL, NULL, TO_CHAR); act("{118}The giant hand of force holds $n fast!", ch, NULL, NULL, TO_ROOM); } TAKE_ACTION(ch_next, ACTION_MOVE); } else if (IS_ENTANGLED(ch_next)) { int diceroll; if (learned(ch_next, gsn_escape_artist)) diceroll = escape_artist_roll(ch_next); else diceroll = str_roll(ch_next); if (diceroll < 15 && domain_apotheosis(ch_next, DOMAIN_STRENGTH)) diceroll = UMAX(diceroll, str_roll(ch_next)); if (diceroll >= 15) { act("$tYou break free from your entanglement!", ch_next, get_color_string(ch, COLOR_YOU_HIT, VT102_BOLD), NULL, TO_CHAR); act("$t$n breaks free from $s entanglement!", ch_next, get_color_string(ch, COLOR_YOU_HIT, VT102_BOLD), NULL, TO_ROOM); AFFECT_STRIP(ch_next, AFF2_ENTANGLED); } else { act("$tYou fail to escape your entanglement!", ch_next, get_color_string(ch, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR); act("$t$n fails to escape $s entanglement!", ch_next, get_color_string(ch, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_ROOM); } TAKE_ACTION(ch_next, ACTION_MOVE); } else if ((grappled = ch_next->grappled_by) != NULL) { if (combat_maneuver_check(ch_next, grappled, gsn_grapple) >= 0) { stop_grapple(grappled); } else { act("$tYou try to break $N's hold, but fail.", ch_next, get_color_string(ch_next, COLOR_YOU_ARE_HIT, VT102_BOLD), grappled, TO_CHAR); act("$t$n tries to break $N's hold, but fails.", ch_next, "{178}", grappled, TO_NOTVICT); act("$t$n tries to break your hold, but fails.", ch_next, get_color_string(grappled, COLOR_YOU_HIT, VT102_BOLD), grappled, TO_VICT); } TAKE_ACTION(ch_next, ACTION_MOVE); } else if ((grappled = ch_next->grappling) != NULL) { if (combat_maneuver_check(ch_next, grappled, gsn_grapple) < 0) { stop_grapple(grappled); } else { act("$tYou maintain your hold on $N.", ch_next, get_color_string(ch_next, COLOR_YOU_HIT, VT102_BOLD), grappled, TO_CHAR); act("$t$n maintains $s grapple on $N.", ch_next, "{178}", grappled, TO_NOTVICT); act("$t$n maintains $s grapple on you.", ch_next, get_color_string(grappled, COLOR_YOU_ARE_HIT, VT102_BOLD), grappled, TO_VICT); } TAKE_ACTION(ch_next, ACTION_MOVE); } check_aura(ch_next, gsn_aura_of_menace); check_aura(ch_next, gsn_babble); remove_turn(turn); insert_turn(turn->in_battle, turn); turn->in_battle->turn_count++; pop_call(); return; } /* * Moves the turn down the list a bit. */ void skip_turn (TURN_DATA * turn, bool fWilling) { push_call("skip_turn(%p,%p)",turn,fWilling); turn->roundtime = turn->next->roundtime; remove_turn (turn); insert_turn (turn->in_battle, turn); if (fWilling) turn->in_battle->turn_count++; pop_call(); return; } /* * Adds to the roundtime of a turn, then replaces it in the order. */ void roundtime (TURN_DATA * turn, int roundtime) { push_call("roundtime(%p,%p)",turn,roundtime); turn->roundtime += roundtime; remove_turn (turn); insert_turn (turn->in_battle, turn); pop_call(); return; } /* * Swaps the placement of two turns, by switching roundtimes and reordering. */ void swap_turn (TURN_DATA * aTurn, TURN_DATA * bTurn) { COMBAT_DATA * battle = aTurn->in_battle; int rt = aTurn->roundtime; push_call("swap_turn(%p,%p)",aTurn,bTurn); aTurn->roundtime = bTurn->roundtime; bTurn->roundtime = rt; remove_turn (aTurn); remove_turn (bTurn); insert_turn (battle, aTurn); insert_turn (battle, bTurn); pop_call(); return; } /* * Just in case some newb with a trigger leaves a battle against a training * dummy running for weeks on end with trigs, or something. You can never * have too much protection, y'know. -- Midboss */ void nuke_roundtime (COMBAT_DATA * battle) { TURN_DATA * list; push_call("nuke_rountime(%p)",battle); for (list = battle->turn_list; list != NULL; list = list->next) list->roundtime -= 2000000000; pop_call(); return; } /* * Creates a new battle between two parties. * Recycles the data if there somehow aren't enough people. */ void initiate_combat (CHAR_DATA * ch, CHAR_DATA * victim) { CHAR_DATA * och; TURN_DATA * turn; COMBAT_DATA * battle; int acount = 0, bcount = 0; push_call("initiate_combat(%p,%p)",ch,victim); ALLOCMEM(battle, COMBAT_DATA, 1); if (battle_list == NULL) battle_list = battle; else { battle->next = battle_list; battle_list = battle; } set_fighting(ch, victim); set_fighting(victim, ch); for (och = ch->in_room->first_person; och != NULL; och = och->next_in_room) { if (is_same_group (ch, och) && !IS_ACT(och, ACT_NOASSIST)) { char_to_combat (och, battle); set_fighting(och, victim); } if (is_same_group (victim, och) && !IS_ACT(och, ACT_NOASSIST)) { char_to_combat (och, battle); set_fighting(och, ch); } } for (turn = battle->turn_list; turn != NULL; turn = turn->next) { if (is_same_group (ch, turn->unit)) acount++; else bcount++; } if (acount < 1 || bcount < 1) clean_combat (battle); pop_call(); return; } /* * is ch dead or nonexistent? */ bool valid_victim( CHAR_DATA *ch ) { push_call("valid_victim(%p)",ch); if (ch == NULL) { pop_call(); return FALSE; } if (ch->position == POS_DEAD || IS_PLR(ch, PLR_DEAD)) { pop_call(); return FALSE; } if (ch->in_room && ch->in_room->vnum == ROOM_VNUM_DEATH) { pop_call(); return FALSE; } pop_call(); return TRUE; } /* * is ch REALLY fighting victim? */ bool valid_fight( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("valid_fight(%p,%p)",ch,victim); /* Keep from fighting thin air... */ if (ch == NULL) { pop_call(); return FALSE; } if (victim == NULL) { pop_call(); return FALSE; } /* keep SuperMob from fighting */ if (victim == supermob || ch == supermob) { pop_call(); return FALSE; } // if either party died, it's not valid fight if (victim->position == POS_DEAD) { pop_call(); return FALSE; } if (victim->in_room) { switch (victim->in_room->vnum) { case ROOM_VNUM_TEMPLE: case ROOM_VNUM_SCHOOL: case ROOM_VNUM_DEATH: pop_call(); return FALSE; default: break; } } if (ch->in_room) { switch (ch->in_room->vnum) { case ROOM_VNUM_TEMPLE: case ROOM_VNUM_SCHOOL: case ROOM_VNUM_DEATH: pop_call(); return FALSE; default: break; } } // ch can't attack if he's out cold. if (ch->position <= POS_SLEEPING) { pop_call(); return FALSE; } // /* prevent melee in separate rooms */ // if (ch->in_room != victim->in_room) // { // pop_call(); // return FALSE; // } pop_call(); return TRUE; } /* * What person is ch actually fighting */ CHAR_DATA *who_fighting( CHAR_DATA *ch ) { push_call("who_fighting(%p)",ch); // // pop_call(); // return NULL; // if (ch == NULL || ch->last_attacked == NULL) { pop_call(); return NULL; } if (!is_string(ch->last_attacked->name)) { log_printf("who_fighting: who->name == NULL for %s", ch->name); ch->last_attacked = NULL; dump_stack(); pop_call(); return NULL; } pop_call(); return ch->last_attacked; } /* * Is CH grappling, or grappled by someone */ CHAR_DATA *who_grappling( CHAR_DATA *ch ) { CHAR_DATA *grappled; push_call("who_grappling(%p)",ch); if ((grappled = ch->grappling) == NULL) { if ((grappled = ch->grappled_by) == NULL) { pop_call(); return NULL; } } if (!is_string(grappled->name)) { log_printf("who_grappling: who->name == NULL for %s", ch->name); if (ch->grappling) stop_grapple(ch); if (ch->grappled_by) stop_grapple(ch->grappled_by); dump_stack(); pop_call(); return NULL; } pop_call(); return grappled; } /* * Choose a new target for aggression * choose new victim fighting CH first, * else pick a member of victim's group - Kregor 1/5/08 */ CHAR_DATA *get_next_fighting(CHAR_DATA *ch, CHAR_DATA *victim) { CHAR_DATA *rch, *rch_next; push_call("get_next_fighting(%p)",ch); for (rch = ch->in_room->first_person ; rch ; rch = rch_next) { rch_next = rch->next_in_room; if (rch == victim) continue; if (!IS_AWAKE(rch)) continue; if (who_fighting(rch) == NULL) continue; if (who_fighting(rch) == ch) { pop_call(); return rch; } if (is_same_group(who_fighting(rch),ch)) { pop_call(); return rch; } } pop_call(); return NULL; } /* Start ch fighting against victim. */ void set_fighting( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("set_fighting(%p,%p)",ch,victim); if (ch == victim) { pop_call(); return; } if (ch->in_room != victim->in_room) { pop_call(); return; } if (IS_AFFECTED(ch, AFF_SLEEP)) { affect_strip(ch, gsn_sleep); } if (ch->position == POS_STANDING) { ch->position = POS_FIGHTING; } ch->last_attacked = victim; if (!IS_NPC(ch) && is_string(ch->pcdata->pose)) STRFREE(ch->pcdata->pose); if (!IS_NPC(victim) && is_string(victim->pcdata->pose)) STRFREE(victim->pcdata->pose); if (!IS_NPC(ch)) { ch->pcdata->last_combat = mud->current_time; } if (!IS_NPC(victim)) { victim->pcdata->last_combat = mud->current_time; } update_pos(ch,-1); pop_call(); return; } /* And stop ch from fighting, and alternately those fighting ch. */ void stop_fighting( CHAR_DATA *ch, bool fBoth ) { CHAR_DATA *victim, *och; push_call("stop_fighting(%p,%p)",ch,fBoth); free_unit( ch ); if (!fBoth) { pop_call(); return; } for (och = mud->f_char ; och ; och = mud->update_wch) { mud->update_wch = och->next; if (och->last_attacked && och->last_attacked == ch) { if ((victim = get_next_fighting(och, ch)) == NULL) { free_unit (och); continue; } set_fighting(och, victim); } } pop_call(); return; } void free_unit( CHAR_DATA *ch ) { push_call("free_unit(%p)",ch); ch->last_attacked = NULL; ch->last_attacker = NULL; ch->position = POS_STANDING; ch->attack_part = -1; ch->initiative = 0; ch->attack = 0; ch->action = 0; if (ch->grappled_by) { stop_grapple(ch->grappled_by); } if (ch->grappling) { stop_grapple(ch); } update_pos(ch,-1); pop_call(); return; } /* * Frees combat data at the end of battle. */ void clean_combat (COMBAT_DATA * battle) { COMBAT_DATA *list; CHAR_DATA *unit, *unit_next; push_call("clean_combat(%p)",battle); for (unit = battle->unit_list; unit != NULL; unit = unit_next) { unit_next = unit->next_in_battle; char_from_combat (unit); //Perhaps call raw_kill() here on KOd mobiles? if (unit->position == POS_DEAD) raw_kill(unit, -1); } if (battle == battle_list) battle_list = battle->next; else for (list = battle_list; list != NULL; list = list->next) { if (list->next == battle) list->next = battle->next; } battle->next = NULL; free_combat (battle); pop_call(); return; } TURN_DATA *new_turn (void) { TURN_DATA *turn; push_call("new_turn()"); ALLOCMEM(turn, TURN_DATA, 1); turn->next = NULL; pop_call(); return turn; } void free_turn (TURN_DATA * turn) { push_call("free_turn(%p)",turn); turn = turn->next; FREEMEM(turn); pop_call(); return; } void free_combat (COMBAT_DATA * battle) { push_call("free_combat(%p)",battle); battle = battle->next; FREEMEM(battle); pop_call(); return; } /* * Move action, only here for testing purposes. */ void do_move (CHAR_DATA *ch, char *argument) { push_call("do_move(%p)",ch); if (!in_combat (ch)) { send_to_char ("You're not fighting!\n\r", ch); pop_call(); return; } act( "You execute a move action.", ch, NULL, NULL, TO_CHAR); act( "$n executes a move action.", ch, NULL, NULL, TO_ROOM); TAKE_ACTION(ch, ACTION_MOVE); pop_call(); return; } /* * Withdraws a character from combat with check for victory. */ void withdraw_combat(CHAR_DATA *ch) { COMBAT_DATA * battle; push_call("withdraw_combat(%p)",ch); if (!in_combat (ch)) { pop_call(); return; } battle = ch->in_battle; char_from_combat (ch); check_victory (battle); pop_call(); return; } /* * This is no longer supported in code, but left * the old function deprecated here - Kregor */ void add_to_victory_list( CHAR_DATA *ch, CHAR_DATA *victim) { int i; char buf[MAX_STRING_LENGTH]; push_call("add_to_victory_list(%p,%p)",ch,victim); for (i = 1 ; i < VICTORY_LIST_SIZE ; i++) { STRFREE(victory_list[i-1]); victory_list[i-1] = STRALLOC(victory_list[i]); } sprintf(buf, "Z%12s (Level %2d) %12s (Level %2d) %s", ch->name, ch->level, victim->name, victim->level, get_time_string(mud->current_time)); RESTRING(victory_list[i-1], buf); save_victors(); pop_call(); return; } /* * Qualifiers in the check_murder function - Kregor */ bool can_attack( CHAR_DATA *ch, CHAR_DATA *victim) { push_call("can_attack(%p,%p)",ch,victim); if (victim == NULL) { pop_call(); return TRUE; } // I am god, and I can kill you! if (IS_GOD(ch)) { pop_call(); return TRUE; } // This means it's not murder. if (IS_NPC(ch) || IS_NPC(victim)) { pop_call(); return TRUE; } // Anyone in the arena is fair game. if (in_area(ch, ROOM_VNUM_ARENA) && in_area(victim, ROOM_VNUM_ARENA)) { pop_call(); return TRUE; } // PKill is restricted by character level if ((!IS_NPC(ch) && ch->level < 3) || (!IS_NPC(victim) && victim->level < 3)) { pop_call(); return FALSE; } pop_call(); return TRUE; } /* * New function to check for pkill allowed, * control murder restrictions. - Kregor 1/5/08 */ bool check_murder(CHAR_DATA *ch, CHAR_DATA *victim) { bool fMurder = FALSE; push_call("check_murder(%p,%p)",ch,victim); if (victim == NULL) { pop_call(); return TRUE; } if (IS_NPC(victim) && IS_SET(victim->act, ACT_PET) && victim->master) { if (victim->master == ch) { act( "Why not just DISCHARGE $M instead?", ch, NULL, victim, TO_CHAR); pop_call(); return FALSE; } if (!in_area(ch, ROOM_VNUM_ARENA)) fMurder = TRUE; } if (!IS_NPC(ch) || IS_SET(ch->act, ACT_PET) || ch->desc != NULL) { if (!IS_NPC(victim) && !in_area(ch, ROOM_VNUM_ARENA)) fMurder = TRUE; } if (!can_attack(ch, victim)) { pop_call(); return FALSE; } if (fMurder) { if (ch == victim) { pop_call(); return TRUE; } if (victim->pcdata->just_died_ctr > 0) { send_to_char("That person is currently protected by their god.\n\r", ch); pop_call(); return FALSE; } if (!IS_SET(victim->act, PLR_KILLER) && !IS_SET(victim->act, PLR_THIEF)) { if (ch->pcdata->last_connect + 120 > mud->current_time) { ch_printf_color(ch, "You must wait %d seconds before you can murder anyone.\n\r", ch->pcdata->last_connect + 120 - mud->current_time); pop_call(); return FALSE; } } if (!IS_NPC(victim) && victim->pcdata->corpse != NULL) { act("OOC: $N has been killed quite recently.", ch, NULL, victim, TO_CHAR); pop_call(); return FALSE; } if (!check_add_attack(ch, victim)) { act("OOC: $N has been attacked too many times recently.", ch, NULL, victim, TO_CHAR); pop_call(); return FALSE; } check_killer( ch, victim ); ch->pcdata->just_died_ctr = 0; clear_attack_list(ch); log_printf("ROOM [%u] MURDER %s attacking %s", ch->in_room->vnum, ch->name, victim->name); } pop_call(); return TRUE; } /* true if victim is a legal target for a mass attack - Scandum 27/05/2002 */ bool can_mass_attack( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("can_mass_attack(%p,%p)",ch,victim); if (victim == ch) { pop_call(); return FALSE; } if (victim == supermob) { pop_call(); return FALSE; } // anyone fighting CH is fair game! if (who_fighting(victim) && who_fighting(victim) == ch) { pop_call(); return TRUE; } // anyone attacking someone in your group is also fair game! if (who_fighting(victim) && is_same_group(ch, who_fighting(victim))) { pop_call(); return TRUE; } if (IS_ACT(victim, ACT_MOBINVIS)) { pop_call(); return FALSE; } if (is_same_group(ch, victim)) { pop_call(); return FALSE; } if (is_master(victim, ch)) { pop_call(); return FALSE; } if (in_area(ch, ROOM_VNUM_ARENA)) { pop_call(); return TRUE; } // Should prevent some nasty AOE kills from mobiles against friendlies if (IS_NPC(ch) && who_fighting(ch) && !is_same_group(ch, who_fighting(ch))) { pop_call(); return FALSE; } pop_call(); return TRUE; } /* Rewrote this function to put all the no fighting crap in one neat function, charmed and possessed mobs attack like players. Scandum 18/02/02 Revised - Kregor Rewrote santuary affect per d20 rules. This is separate from can_melee_attack in that this checks for ability to do ANY hostile action, not just a physical attack. */ bool is_safe( CHAR_DATA *ch, CHAR_DATA *victim) { push_call("is_safe(%p,%p)",ch,victim); if (IS_SET(ch->in_room->room_flags, ROOM_SAFE)) { send_to_char( "Wards of peace prevent you from doing that.\n\r", ch); pop_call(); return TRUE; } // should assure no auto actions happen from people who are down if (ch->position < POS_FIGHTING) { act("You are in no position to do anything aggressive.", ch, NULL, NULL, TO_CHAR); pop_call(); return TRUE; } // These are only needed for auto actions, commands are snagged in interp.c if (IS_AFFECTED(ch, AFF2_PETRIFICATION) || (IS_AFFECTED(ch, AFF2_PARALYSIS) && !IS_AFFECTED(ch, AFF_FREEDOM))) { pop_call(); return TRUE; } if (IS_AFFECTED(ch, AFF2_DAZED)) { act( "You are too dazed to act.", ch, NULL, NULL, TO_CHAR); act( "$n simply stands in a daze.", ch, NULL, NULL, TO_ROOM); pop_call(); return TRUE; } if (IS_AFFECTED(ch, AFF2_NAUSEATED)) { act( "You are too nauseous to act.", ch, NULL, NULL, TO_CHAR); act( "$n struggles against wretching.", ch, NULL, NULL, TO_ROOM); pop_call(); return TRUE; } if (IS_AFFECTED(ch, AFF2_STUNNED)) { act( "You are too stunned to act.", ch, NULL, NULL, TO_CHAR); pop_call(); return TRUE; } if (victim == NULL) { pop_call(); return FALSE; } if (!IS_NPC(ch) && !IS_NPC(victim) && IS_SET(ch->in_room->area->flags, AFLAG_NO_PVP)) { send_to_char( "You are currently in a no-pvp area.\n\r", ch); pop_call(); return TRUE; } if (victim == supermob) { send_to_char( "You cannot attack the SuperMob!\n\r", ch); pop_call(); return TRUE; } if (is_affected(victim, gsn_time_stop)) { act("You cannot affect $N while $E is frozen in time.", ch, NULL, victim, TO_CHAR); pop_call(); return TRUE; } if (!can_attack( ch, victim )) { act( "You may not do that against $N.", ch, NULL, victim, TO_CHAR); pop_call(); return TRUE; } //hostile action removes fascination. if (IS_AFFECTED(victim, AFF2_FASCINATED)) { AFFECT_STRIP(victim, AFF2_FASCINATED); act("Your fascination is broken due to $N's actions.", victim, NULL, ch, TO_CHAR); } if (IS_AFFECTED(ch, AFF2_CHARMED)) { if ((is_affected(ch, gsn_charm_monster) && get_caster(ch, gsn_charm_monster) == victim) || (is_affected(ch, gsn_charm_person) && get_caster(ch, gsn_charm_person) == victim) || (is_affected(ch, gsn_charm_plant) && get_caster(ch, gsn_charm_plant) == victim) || (is_affected(ch, gsn_charm_animal) && get_caster(ch, gsn_charm_animal) == victim)) { act( "You like $N too much to attack $M.", ch, NULL, victim, TO_CHAR); pop_call(); return TRUE; } } // already fighting, sanctuary doesn't matter - Kregor if (who_fighting(ch) == victim) { pop_call(); return FALSE; } if (who_fighting(victim) == ch) { pop_call(); return FALSE; } if (IS_AFFECTED(victim, AFF_SANCTUARY)) { if (!save_resist(ch, victim, gsn_sanctuary, get_affect_level(victim, gsn_sanctuary))) { send_to_char( "A magical force stays your hand.\n\r", ch); pop_call(); return TRUE; } } if (IS_AFFECTED(victim, gsn_undeath_ward) && IS_UNDEAD(ch)) { if (!save_resist(ch, victim, gsn_undeath_ward, get_affect_level(victim, gsn_undeath_ward))) { send_to_char( "A powerful ward stays your hand.\n\r", ch); pop_call(); return TRUE; } } if (IS_AFFECTED(ch, AFF_DOMINATE) && (is_master(ch, victim) || is_same_group(ch, victim->master))) { act( "You may not attack your master.", ch, NULL, victim, TO_CHAR); pop_call(); return TRUE; } if (in_area(ch, ROOM_VNUM_ARENA)) { pop_call(); return FALSE; } if (IS_NPC(ch) && !in_combat(ch) && ch->hit < get_max_hit(ch) / 2 && IS_SET(ch->act, ACT_WIMPY)) { if (!IS_AFFECTED(ch, AFF_DOMINATE) && !IS_SET(ch->act, ACT_PET) && ch->desc == NULL) { pop_call(); return TRUE; } } if (IS_AFFECTED(ch, AFF_DOMINATE) && ch->master == victim ) { act( "$N is your beloved master.", ch, NULL, victim, TO_CHAR); pop_call(); return TRUE; } pop_call(); return FALSE; } /* * New function to determine whether an aggressive action * can be performed outside of combat or out of turn - Kregor 01/2008 * Make sure this is the LAST check before the action, so * that AoO isn't wasted by other conditions! */ bool can_surprise(CHAR_DATA *ch, CHAR_DATA *victim) { push_call("can_surprize(%p,%p)",ch,victim); if (ch == NULL || victim == NULL) { pop_call(); return FALSE; } if (ch == victim) { act("Attack yourself?", ch, NULL, NULL, TO_CHAR); pop_call(); return FALSE; } if (in_combat(ch)) { if (!is_active(ch)) { if (!has_opportunity(ch, victim)) { send_to_char ("Just wait your turn.\n\r", ch); pop_call(); return FALSE; } else if (check_murder(ch, victim)) { take_opportunity(ch, victim); pop_call(); return TRUE; } } else { pop_call(); return TRUE; } } else if (has_opportunity(ch, victim)) { if (check_murder(ch, victim)) { take_opportunity(ch, victim); pop_call(); return TRUE; } } else if (!can_see(victim, ch) || !IS_AWAKE(victim) || multi(ch, gsn_backstab) != -1) { if (check_murder(ch, victim)) { if (IS_AFFECTED(ch, AFF_HIDE)) { AFFECT_STRIP(ch, AFF_HIDE); act( "$n steps out from $s hiding spot.", ch, NULL, NULL, TO_ROOM ); act( "You step out from your hiding spot.", ch, NULL, NULL, TO_CHAR ); } if (IS_AFFECTED(ch, AFF_INVISIBLE)) { affect_strip(ch, gsn_invis); REMOVE_BIT(ch->affected_by, AFF_INVISIBLE); } act( "$n takes you by surprise!", ch, NULL, victim, TO_VICT); act( "You takes $N by surprise!", ch, NULL, victim, TO_CHAR); act( "$n takes $N by surprise!", ch, NULL, victim, TO_NOTVICT); pop_call(); return TRUE; } } act ("$N is too wary to be taken by surprise. Try starting a FIGHT first.", ch, NULL, victim, TO_CHAR); pop_call(); return FALSE; } bool check_recently_fought( CHAR_DATA *ch, CHAR_DATA *victim ) { CHAR_DATA *ch_ld; int pvnum_ld; push_call("check_recently_fought(%p,%p)",ch,victim); if (!IS_NPC(victim)) { pop_call(); return FALSE; } if (victim->npcdata->pvnum_last_hit == 0) { pop_call(); return FALSE; } if (MP_VALID_MOB(ch)) { pop_call(); return FALSE; } ch_ld = ch; if (IS_NPC(ch) && ch->master) { ch_ld = ch_ld->master; } if (ch_ld->leader != NULL) { ch_ld = ch_ld->leader; } if (!IS_NPC(ch_ld)) { pvnum_ld = ch_ld->pcdata->pvnum; } else { pvnum_ld = 0; } if (ch_ld == NULL || IS_NPC(ch_ld)) { pop_call(); return TRUE; } if (pvnum_ld != victim->npcdata->pvnum_last_hit) { pop_call(); return TRUE; } pop_call(); return FALSE; } /* See if an attack justifies a KILLER flag. */ void check_killer( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("check_killer(%p,%p)",ch,victim); if (IS_PLR(victim, PLR_KILLER)) { pop_call(); return; } if (!IS_PLR(ch, PLR_KILLER)) { send_to_char("*** PLAYER KILLER status recorded. ***\n\r", ch); } SET_BIT(ch->act, PLR_KILLER); ch->pcdata->killer_played = ch->pcdata->played; pop_call(); return; } /* Code for total and max pvnum attacks - Chaos 4/20/99 */ bool check_add_attack( CHAR_DATA *ch, CHAR_DATA *victim ) { push_call("check_add_attack(%p,%p)",ch,victim); /* Killers and Thieves can always be attacked - Scandum 07/02/02 */ if (!IS_NPC(ch) && !IS_NPC(victim)) { int oldest_attack, total_pvnum_attacks, attacks_today, cnt; attacks_today = 0; oldest_attack = 0; total_pvnum_attacks = 0; for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++) { if (victim->pcdata->last_pk_attack_time[cnt] == 0) { oldest_attack = cnt; } else if (victim->pcdata->last_pk_attack_time[oldest_attack] > victim->pcdata->last_pk_attack_time[cnt]) { oldest_attack = cnt; } if (victim->pcdata->last_pk_attack_time[cnt] > mud->current_time) { attacks_today++; } if (ch->pcdata->pvnum == victim->pcdata->last_pk_attack_pvnum[cnt] && victim->pcdata->last_pk_attack_time[cnt] > mud->current_time) { total_pvnum_attacks++; } } if (!IS_SET(victim->act, PLR_KILLER) && !IS_SET(victim->act, PLR_THIEF)) { if (attacks_today >= MAX_PK_ATTACKS) { pop_call(); return( FALSE ); } if (total_pvnum_attacks >= 5) { pop_call(); return( FALSE ); } victim->pcdata->last_pk_attack_time[oldest_attack] = mud->current_time + 60 * 60 * 6; victim->pcdata->last_pk_attack_pvnum[oldest_attack] = ch->pcdata->pvnum; STRFREE(victim->pcdata->last_pk_attack_name[oldest_attack]); victim->pcdata->last_pk_attack_name[oldest_attack] = STRALLOC(ch->name); } } pop_call(); return( TRUE ); } /* Scandum 14-01-2003 */ void clear_attack_list( CHAR_DATA *ch ) { int cnt; push_call("clear_attack_list(%p)",ch); for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++) { ch->pcdata->last_pk_attack_time[cnt] = 0; STRFREE(ch->pcdata->last_pk_attack_name[cnt]); ch->pcdata->last_pk_attack_name[cnt] = STRDUPE(str_empty); } pop_call(); return; } void spam_attack_list( CHAR_DATA *ch ) { int cnt; push_call("spam_attack_list(%p)",ch); for (cnt = 0 ; cnt < MAX_PK_ATTACKS ; cnt++) { ch->pcdata->last_pk_attack_time[cnt] = mud->current_time + 60 * 60 * 24; ch->pcdata->last_pk_attack_pvnum[cnt] = 0; STRFREE(ch->pcdata->last_pk_attack_name[cnt]); ch->pcdata->last_pk_attack_name[cnt] = STRDUPE(str_empty); } pop_call(); return; } /* * Set position of a victim. * Updated to include D20 guidelines * for stunning and unconsciousness - Kregor 10/07 */ void update_pos( CHAR_DATA *victim, int pos ) { CHAR_DATA *gch; push_call("update_pos(%p)",victim); // really, can't get any deader. if (victim->position == POS_DEAD) { free_cast(victim); free_skill(victim); pop_call(); return; } if (in_combat(victim) && in_camp(victim)) { for (gch = victim->in_room->first_person ; gch ; gch = gch->next_in_room) { if (is_same_group(victim, gch) && IS_AFFECTED(gch, AFF2_CAMPING)) { send_to_char( "Your camp is destroyed!\n\r", victim ); REMOVE_AFFECT(gch, AFF2_CAMPING); break; } } } if (in_combat(victim) && victim->rest > 0) { send_to_char( "Your rest is spoiled!\n\r", victim ); victim->rest = 0; } if ((!CAN_CRITICAL(victim) && victim->hit <= 0) || victim->hit <= -10) { victim->position = POS_DEAD; free_cast(victim); free_skill(victim); pop_call(); return; } if (victim->level + get_apply(victim, APPLY_LEVEL) <= 0) { act( "$tYou succumb to energy drain!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR); act( "$t$n succumbs to energy drain!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_ROOM); victim->position = POS_DEAD; free_cast(victim); free_skill(victim); pop_call(); return; } if (get_curr_con(victim) == 0) { act( "$tYou succumb to consitution loss!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR); victim->position = POS_DEAD; free_cast(victim); free_skill(victim); pop_call(); return; } if (pos != -1) { victim->position = URANGE(0, pos, POS_STANDING); } if (victim->position < POS_FIGHTING) { if (is_affected(victim, gsn_defensive_stance)) { affect_strip(victim, gsn_defensive_stance); } } if (victim->hit > 0 && victim->nonlethal <= victim->hit) { if (get_curr_wis(victim) == 0 || get_curr_int(victim) == 0 || get_curr_cha(victim) == 0) { if (victim->position > POS_INCAP) { act( "$tYou collapse into a catatonic state!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR); } victim->position = POS_INCAP; } else if (get_curr_str(victim) == 0 || get_curr_dex(victim) == 0) { if (victim->position > POS_INCAP) { act( "$tYou fall paralyzed to the ground!", victim, get_color_code(victim, COLOR_YOU_ARE_HIT, VT102_BOLD), NULL, TO_CHAR); } victim->position = POS_INCAP; } else if (IS_AFFECTED(victim, AFF2_UNCONSCIOUS)) { victim->position = POS_STUNNED; } else if (drunk_level(victim) >= DRUNK_TOTALLED) { victim->position = POS_STUNNED; } else if (victim->position <= POS_STUNNED) { act( "You recover consciousness.", victim, NULL, NULL, TO_CHAR); act( "$n recovers consciousness.", victim, NULL, NULL, TO_ROOM); victim->position = POS_RESTING; } // stunned chars drop their wielded weapons - Kregor if (IS_AFFECTED(victim, AFF2_STUNNED) || victim->position <= POS_STUNNED) { OBJ_DATA *obj; if ((obj = get_wield(victim, FALSE)) != NULL) { act( "$p slips out of your grasp.", victim, obj, NULL, TO_CHAR ); act( "$p slips out of $n's grasp.", victim, obj, NULL, TO_ROOM ); unequip_char(victim, obj, FALSE); } if ((obj = get_wield(victim, TRUE)) != NULL) { act( "$p slips out of your grasp.", victim, obj, NULL, TO_CHAR ); act( "$p slips out of $n's grasp.", victim, obj, NULL, TO_ROOM ); unequip_char(victim, obj, FALSE); } } if (victim->position <= POS_SLEEPING) { free_cast(victim); free_skill(victim); } pop_call(); return; } if (victim->position != POS_INCAP) { if (victim->hit <= -1) { if (!race_skill(victim, gsn_ferocity) || victim->uses[gsn_ferocity]) { if (!learned(victim, gsn_diehard) || !will_save(victim, NULL, 10 - (victim->hit * 2), -1)) { victim->position = POS_MORTAL; } } } else if (victim->hit == 0 || !domain_apotheosis(victim, DOMAIN_SUFFERING)) { victim->position = POS_STUNNED; } } if (victim->position <= POS_SLEEPING) { free_cast(victim); free_skill(victim); } pop_call(); return; } void group_gain( CHAR_DATA *ch, CHAR_DATA *victim ) { CHAR_DATA *gch; int xp, group_bonus, group_count, bonus; push_call("group_gain(%p,%p)",ch,victim); if (IS_AFFECTED(victim, AFF_DOMINATE) || IS_ACT(victim, ACT_PET)) { pop_call(); return; } if (IS_NPC(victim) && victim->pIndexData->vnum == 9901) /* No Demons */ { pop_call(); return; } group_bonus = group_count = 0; for (gch = ch->in_room->first_person ; gch ; gch = gch->next_in_room) { if (is_same_group(gch, ch)) { group_count += 1; if (IS_NPC(gch)) continue; group_bonus += 1; gch->alignment -= victim->alignment / 50; gch->ethos -= victim->ethos / 50; gch->alignment = URANGE(-1000, gch->alignment, 1000); gch->ethos = URANGE(-1000, gch->ethos, 1000); } } bonus = group_bonus * 10; bonus += 100; for (gch = ch->in_room->first_person ; gch ; gch = gch->next_in_room) { if (!is_same_group(gch, ch) || IS_NPC(gch)) { continue; } xp = xp_compute(gch, victim); if (group_count > 1) xp = xp * bonus / 100; wiz_printf("group_gain(%s) - bonus xp: %d", gch->name, xp); xp /= group_count; wiz_printf("group_gain(%s) - share: %d", gch->name, xp); ch_printf_color(gch, "You receive %d experience points.", xp); ch_printf_color(gch, "\n\r"); gain_exp(gch, xp); if (!gain_favor(gch, DOMAIN_WAR, 1)) if (!gain_favor(gch, DOMAIN_DESTRUCTION, 1)) if (!gain_favor(gch, DOMAIN_RETRIBUTION, 1)) if (!gain_favor(gch, DOMAIN_SUFFERING, 1)) gain_favor(gch, DOMAIN_WRATH, 1); if (faith_enemies(gch, victim)) gain_favor(gch, -1, 1); if (which_god(gch) == which_god(victim)) gain_favor(gch, -1, -5); check_zap(gch, TRUE); } pop_call(); return; } /* * Calculate how much experience a character is worth. * Formula derived from the OSRIC ruleset, and thus does not * infringe upon non-Open Gaming content from WotC - Kregor */ int get_exp_worth( CHAR_DATA *ch ) { int exp; int level = UMIN(ch->level, 21); int sn; push_call("get_exp_worth(%p)",ch); //base XP is based on hit dice exp = (level + level - 1) * 10; wiz_printf("get_exp_worth(%s) - base xp: %d", ch->name, exp); //add 1 xp per hit point, per level of victim exp += get_max_hit(ch) * level; // each exceptional race attack grants +50% of base xp for (sn = 0 ; *skill_table[sn].name != '\0' ; sn++) { switch (skill_table[sn].skilltype) { default: break; case FSKILL_RACEATTACK: if (race_skill(ch, sn)) exp += (level + level - 1) * 5; break; case FSKILL_SPELL: if (race_skill(ch, sn)) { if (skill_table[sn].native_level <= 3) exp += (level + level - 1) * 5 / 2; // spell-like abilities of 3rd or below only +25% else exp += (level + level - 1) * 5; } break; } } wiz_printf("get_exp_worth(%s) - final xp: %d", ch->name, exp); pop_call(); return exp; } /* * Adjust XP awarded to a char based on * level difference with victim - Kregor */ int xp_compute( CHAR_DATA *gch, CHAR_DATA *victim ) { int xp; int spread; push_call("xp_compute(%p,%p)",gch,victim); if (gch->in_room == NULL) { pop_call(); return(0); } if (IS_SET(gch->in_room->room_flags, ROOM_RIP)) { pop_call(); return(0); } xp = get_exp_worth(victim); if (!IS_NPC(victim)) { xp = 0; } spread = 10 + gch->level / 10; if (gch->level > victim->level) { xp = xp * spread / (gch->level - victim->level + spread); } pop_call(); return UMAX(1, xp); } /* * Calc the experience worth based on a stated * Challenge Rating per OSRIC calculations - Kregor */ int xp_compute_cr( CHAR_DATA *gch, int challenge ) { int xp, spread; push_call("xp_compute(%p,%p)",gch,challenge); xp = (challenge + challenge - 1) * 10; xp += 5 * challenge; spread = 10 + gch->level / 10; if (gch->level > challenge) { xp = xp * spread / (gch->level - challenge + spread); } pop_call(); return UMAX(1, xp); } /* * The SPAM stat line * could use for the Status spell */ void show_party_line( CHAR_DATA *ch ) { CHAR_DATA *fch; char buf[MAX_STRING_LENGTH], buf2[200], buf3[200]; push_call("show_party_line(%p)",ch); buf[0]='\0'; for (fch = ch->in_room->first_person; fch != NULL; fch = fch->next_in_room) { if (fch != ch && is_same_group(ch, fch)) { strcpy(buf3, !IS_NPC(fch) ? capitalize(fch->name) : capitalize(fch->short_descr)); buf3[6]='\0'; sprintf(buf2, "%s:%d/%d ", buf3, fch->hit, get_max_hit(fch)); strcat(buf, buf2); } } if (buf[0] != '\0') { ch_printf_color(ch, "%s%s\n\r", get_color_string(ch,COLOR_PROMPT,VT102_DIM), buf); } pop_call(); return; } bool is_light_weapon(CHAR_DATA *ch, OBJ_DATA *obj) { push_call("is_light_weapon(%p)",ch,obj); if (obj == NULL || !IS_OBJ_TYPE(obj, ITEM_WEAPON)) { pop_call(); return FALSE; } if (get_size(ch) > obj->size) { pop_call(); return TRUE; } else if (get_size(ch) == obj->size && WSPEC(obj, WSPEC_LIGHT)) { pop_call(); return TRUE; } else { pop_call(); return FALSE; } } /* * Find the range between ch and victim for ranged attack */ int get_range(CHAR_DATA *ch, CHAR_DATA *victim) { int range; push_call("get_range(%p,%p)",ch,victim); if (ch == NULL || victim == NULL) { bug("get_range: NULL char!",0); pop_call(); return 0; } for (range = 0 ; range < MAX_VNUM ; range++) { if (findpath_search_victim(ch, victim, range) == -1) continue; wiz_printf_room(ch, "get_range(%s,%s): range: %d\n\r", get_name(ch), get_name(victim), range); wiz_printf_room(victim, "get_range(%s,%s): range: %d\n\r", get_name(ch), get_name(victim), range); pop_call(); return range; } pop_call(); return -1; } /* returns weapon proficiency for a wielded weapon - Kregor 2/18/07 */ int weapon_skill( CHAR_DATA *ch, OBJ_DATA *wield ) { int prof; push_call("weapon_skill(%p,%p)",ch,wield); /* needs a safety in here */ if (wield != NULL && !IS_WEAPON(wield) && !IS_AMMO(wield)) { pop_call(); return 0; } // treat NULL as unarmed strike - Kregor if (wield == NULL) { prof = learned(ch, gsn_unarmed_strike); } else { switch (wield->value[0]) { default: bug("weapon_skill: bad weapon type", 0); prof = 0; break; case WEAPON_TYPE_AXE_DOUBLE: prof = learned(ch,gsn_weapon_double_axe); if ((ch->race == RACE_ORC || ch->race == RACE_HALFORC) && learned(ch, gsn_weapon_prof_martial)) { SET_BIT(prof, WSKILL_PROFICIENT); } break; case WEAPON_TYPE_HANDAXE: prof = (learned(ch,gsn_weapon_handaxe)); break; case WEAPON_TYPE_AXE_THROWING: prof = (learned(ch,gsn_weapon_throwing_axe)); break; case WEAPON_TYPE_BATTLEAXE: prof = (learned(ch,gsn_weapon_battleaxe)); break; case WEAPON_TYPE_BOLA: prof = (learned(ch,gsn_weapon_bola)); break; case WEAPON_TYPE_CHAIN_SPIKED: prof = (learned(ch,gsn_weapon_spiked_chain)); break; case WEAPON_TYPE_CLUB: prof = (learned(ch,gsn_weapon_club)); break; case WEAPON_TYPE_CROSSBOW_HAND: prof = (learned(ch,gsn_weapon_crossbow_hand)); break; case WEAPON_TYPE_CROSSBOW_HEAVY: prof = (learned(ch,gsn_weapon_crossbow_heavy)); break; case WEAPON_TYPE_CROSSBOW_LIGHT: prof = (learned(ch,gsn_weapon_crossbow_light)); break; case WEAPON_TYPE_DAGGER: case WEAPON_TYPE_KNIFE: prof = (learned(ch,gsn_weapon_short_blade)); break; case WEAPON_TYPE_DART: prof = (learned(ch,gsn_weapon_dart)); break; case WEAPON_TYPE_FALCHION: prof = (learned(ch,gsn_weapon_falchion)); break; case WEAPON_TYPE_FLAIL: case WEAPON_TYPE_FLAIL_HEAVY: prof = (learned(ch,gsn_weapon_flail)); break; case WEAPON_TYPE_FLAIL_DIRE: prof = (learned(ch,gsn_weapon_flail_dire)); break; case WEAPON_TYPE_GLAIVE: prof = (learned(ch,gsn_weapon_glaive)); break; case WEAPON_TYPE_GREATAXE: prof = (learned(ch,gsn_weapon_greataxe)); break; case WEAPON_TYPE_GREATCLUB: prof = (learned(ch,gsn_weapon_greatclub)); break; case WEAPON_TYPE_GREATSWORD: prof = (learned(ch,gsn_weapon_greatsword)); break; case WEAPON_TYPE_GUISARME: prof = (learned(ch,gsn_weapon_guisarme)); break; case WEAPON_TYPE_HALBERD: prof = (learned(ch,gsn_weapon_halberd)); break; case WEAPON_TYPE_HAMMER_LIGHT: case WEAPON_TYPE_WARHAMMER: prof = (learned(ch,gsn_weapon_hammer)); break; case WEAPON_TYPE_JAVELIN: prof = (learned(ch,gsn_weapon_javelin)); break; case WEAPON_TYPE_KAMA: prof = (learned(ch,gsn_weapon_kama)); break; case WEAPON_TYPE_KUKRI: prof = (learned(ch,gsn_weapon_kukri)); break; case WEAPON_TYPE_LANCE: prof = (learned(ch,gsn_weapon_lance)); break; case WEAPON_TYPE_LONGBOW: prof = (learned(ch,gsn_weapon_longbow)); break; case WEAPON_TYPE_LONGBOW_COMPOSITE: prof = (learned(ch,gsn_weapon_longbow_composite)); break; case WEAPON_TYPE_LONGSPEAR: case WEAPON_TYPE_SHORTSPEAR: case WEAPON_TYPE_SPEAR: prof = (learned(ch,gsn_weapon_spear)); break; case WEAPON_TYPE_MACE_HEAVY: case WEAPON_TYPE_MACE_LIGHT: prof = (learned(ch,gsn_weapon_mace)); break; case WEAPON_TYPE_MORNINGSTAR: prof = (learned(ch,gsn_weapon_morningstar)); break; case WEAPON_TYPE_NUNCHAKU: prof = (learned(ch,gsn_weapon_nunchaku)); break; case WEAPON_TYPE_PICK_HEAVY: case WEAPON_TYPE_PICK_LIGHT: prof = (learned(ch,gsn_weapon_pick)); break; case WEAPON_TYPE_QUARTERSTAFF: prof = (learned(ch,gsn_weapon_quarterstaff)); break; case WEAPON_TYPE_RANSEUR: prof = (learned(ch,gsn_weapon_ranseur)); break; case WEAPON_TYPE_RAPIER: prof = (learned(ch,gsn_weapon_rapier)); break; case WEAPON_TYPE_SAI: prof = (learned(ch,gsn_weapon_sai)); break; case WEAPON_TYPE_SAP: prof = (learned(ch,gsn_weapon_sap)); break; case WEAPON_TYPE_SCIMITAR: prof = (learned(ch,gsn_weapon_scimitar)); break; case WEAPON_TYPE_SCYTHE: prof = (learned(ch,gsn_weapon_scythe)); break; case WEAPON_TYPE_SHORTBOW: prof = (learned(ch,gsn_weapon_shortbow)); break; case WEAPON_TYPE_SHORTBOW_COMPOSITE: prof = (learned(ch,gsn_weapon_shortbow_composite)); break; case WEAPON_TYPE_SHURIKEN: prof = (learned(ch,gsn_weapon_shuriken)); break; case WEAPON_TYPE_CHAKRAM: prof = (learned(ch,gsn_weapon_chakram)); break; case WEAPON_TYPE_SIANGHAM: prof = (learned(ch,gsn_weapon_siangham)); break; case WEAPON_TYPE_SICKLE: prof = (learned(ch,gsn_weapon_sickle)); break; case WEAPON_TYPE_SLING: prof = (learned(ch,gsn_weapon_sling)); break; case WEAPON_TYPE_SWORD_BASTARD: prof = (learned(ch,gsn_weapon_sword_bastard)); break; case WEAPON_TYPE_SWORD_LONG: prof = (learned(ch,gsn_weapon_sword_long)); break; case WEAPON_TYPE_SWORD_SHORT: prof = (learned(ch,gsn_weapon_sword_short)); break; case WEAPON_TYPE_SWORD_TWO_BLADED: prof = (learned(ch,gsn_weapon_sword_double)); break; case WEAPON_TYPE_TRIDENT: prof = (learned(ch,gsn_weapon_trident)); break; case WEAPON_TYPE_URGROSH_DWARVEN: prof = (learned(ch,gsn_weapon_urgrosh)); if (IS_RACE(ch, RACE_DWARF) && learned(ch, gsn_weapon_prof_martial)) { SET_BIT(prof, WSKILL_PROFICIENT); } break; case WEAPON_TYPE_ELVEN_THINBLADE: prof = (learned(ch,gsn_weapon_thinblade)); if (IS_RACE(ch, RACE_ELF) && learned(ch, gsn_weapon_prof_martial)) { SET_BIT(prof, WSKILL_PROFICIENT); } break; case WEAPON_TYPE_WARAXE_DWARVEN: prof = (learned(ch,gsn_weapon_waraxe_dwarven)); if (IS_RACE(ch, RACE_DWARF) && learned(ch, gsn_weapon_prof_martial)) { SET_BIT(prof, WSKILL_PROFICIENT); } break; case WEAPON_TYPE_WHIP: prof = (learned(ch,gsn_weapon_whip)); break; } /* * cleric gains weapon proficiency in deity's weapon of choice * cleric of War domain gets specialization in weapon - Kregor */ if (class_level(ch, CLASS_CLERIC) && ch->god && god_table[ch->god].favored_weapon == wield->value[0]) { SET_BIT(prof, WSKILL_PROFICIENT); if (has_domain(ch, DOMAIN_WAR)) { SET_BIT(prof, WSKILL_SPECIALIZED); } } } pop_call(); return prof; } /* Find the ac value of armor worn. Magic plusses are added by affects - Kregor */ int apply_ac( CHAR_DATA *ch ) { OBJ_DATA *obj; int ac; push_call("apply_ac(%p,%p)",ch); // added automatic armor for NPCs if (IS_NPC(ch) && ch->npcdata->armor_type) { pop_call(); return armor_table[ch->npcdata->armor_type].ac_bonus; } for (ac = 0, obj = ch->first_carrying ; obj ; obj = obj->next_content) { if (!IS_WORN(obj)) continue; if ( obj->item_type != ITEM_ARMOR ) continue; if ( obj->value[0] == ARMOR_TYPE_CLOTH ) continue; // if (get_layer(obj) != LAYER_ARMOR) // continue; // switch ( obj->wear_loc ) { case WEAR_BODY: ac += 30 * armor_table[obj->value[0]].ac_bonus; break; case WEAR_HEAD: ac += 20 * armor_table[obj->value[0]].ac_bonus; break; case WEAR_LEGS: ac += 20 * armor_table[obj->value[0]].ac_bonus; break; case WEAR_FEET: ac += 5 * armor_table[obj->value[0]].ac_bonus; break; case WEAR_ARMS: ac += 20 * armor_table[obj->value[0]].ac_bonus; break; case WEAR_HANDS: ac += 5 * armor_table[obj->value[0]].ac_bonus; break; default: ac += 0; break; } } ac /= 100; pop_call(); return ac; } /* * Calculate base AC of ch * Added argument for touch attacks */ int GET_AC(CHAR_DATA *ch, bool touch) { int AC, armor, deflect, natural; push_call("GET_AC(%p)",ch); AC = armor = deflect = natural = 0; if (!touch) { armor = UMAX(get_apply(ch, APPLY_ARMOR), ch->armor); if (!IS_NPC(ch) || (IS_NPC(ch) && !ch->race)) natural = race_table[get_race(ch)].nat_armor; else if (IS_NPC(ch)) natural = ch->pIndexData->nat_armor; natural += ch->nat_armor; natural = UMAX(get_apply(ch, APPLY_NATURAL_AC), natural); } deflect = UMAX(get_apply(ch, APPLY_DEFLECT), race_table[get_race(ch)].deflection); if (domain_apotheosis(ch, DOMAIN_PROTECTION)) deflect = stat_bonus(TRUE, ch, APPLY_WIS); // incorporeals gain deflection AC == their CHA bonus - Kregor if (IS_INCORPOREAL(ch)) deflect = UMAX(deflect, stat_bonus(TRUE, ch, APPLY_CHA)); AC = armor; AC += deflect; AC += natural; AC += get_apply(ch, APPLY_ENHANCE_AC); // takes greater of magic armor vs. Magic Vestment affect - Kregor if (class_level(ch, CLASS_MONK) && armor_type_worn(ch) == ARMOR_NONE && get_eq_char(ch, WEAR_SHIELD) == NULL && !IS_ENCUMBERED(ch) && !IS_HELPLESS(ch)) { AC += class_level(ch, CLASS_MONK) / 5; AC += stat_bonus(TRUE, ch, APPLY_WIS); } if (is_affected(ch, gsn_defensive_stance) && IS_AWAKE(ch)) { AC += stat_bonus(TRUE, ch, APPLY_CON); } switch (get_size(ch)) { case SIZE_FINE: AC += 8; break; case SIZE_DIMINUTIVE: AC += 4; break; case SIZE_TINY: AC += 2; break; case SIZE_SMALL: AC += 1; break; case SIZE_LARGE: AC -= 1; break; case SIZE_HUGE: AC -= 2; break; case SIZE_GARGANTUAN: AC -= 4; break; case SIZE_COLOSSAL: AC -= 8; break; } if (IS_AFFECTED(ch, AFF2_STUNNED)) AC -= 2; // AC penalty for a charge if (IS_SET(ch->attack, ATTACK_CHARGE)) AC -= 2; pop_call(); return(AC+10); } /* * adds shield bonus against immediate opponent */ int shield_bonus( CHAR_DATA *ch ) { int shield; OBJ_DATA *obj; push_call("shield_bonus(%p)",ch); if (IS_HELPLESS(ch)) { pop_call(); return get_apply(ch, APPLY_SHIELD); } if (is_affected(ch, gsn_irresistible_dance)) { pop_call(); return get_apply(ch, APPLY_SHIELD); } shield = 0; if ((obj = get_eq_char(ch, WEAR_SHIELD)) != NULL && IS_OBJ_TYPE(obj, ITEM_ARMOR)) { shield = armor_table[obj->value[0]].ac_bonus; shield += obj->apply[APPLY_ENHANCE_AC]; if (learned(ch, gsn_shield_specialization)) { if (learned(ch, gsn_active_shield_defense) && COMBATMODE(ch, COMBAT_DEFENSIVE) && IS_SET(ch->attack, ATTACK_MELEE)) shield += 4; else shield += 1; } if (IS_SET(ch->attack, ATTACK_SHIELD_BASH) && !learned(ch, gsn_imp_shield_bash)) shield = 0; } else if (get_wield(ch, TRUE) != NULL && learned(ch, gsn_two_weapon_defense)) { if (IS_SET(ch->combatmode, COMBAT_DEFENSIVE) && IS_SET(ch->attack, ATTACK_MELEE)) { shield = 2; } else { shield = 1; } } shield = UMAX(get_apply(ch, APPLY_SHIELD), shield); pop_call(); return(shield); } /* * Dodge bonuses applied if can_dodge */ int dodge_bonus( CHAR_DATA *ch ) { int dodge = 0; int dex_max = dex_bonus_max(ch); push_call("dodge_bonus(%p)",ch); dodge = stat_bonus(TRUE, ch, APPLY_DEX); if (dex_max != -1) dodge = UMIN(dodge, dex_max); dodge += get_apply(ch, APPLY_DODGE); dodge += get_apply(ch, APPLY_INS_AC); if (learned(ch, gsn_dodge)) { dodge += 1; if (get_monk_style(ch) == STYLE_COBRA_STRIKE && class_level(ch, CLASS_MONK) >= 12) dodge += 1; } if (learned(ch, gsn_canny_defense) && armor_type_worn(ch) == ARMOR_NONE && get_eq_char(ch, WEAR_SHIELD) == NULL) { dodge += UMIN(stat_bonus(TRUE, ch, APPLY_INT), multi_class_level(ch, gsn_canny_defense)); } if (is_mounted(ch) && learned(ch->master, gsn_mounted_defense)) dodge += URANGE(0, learned(ch->master, gsn_mount), stat_bonus(TRUE, ch->master, APPLY_DEX)); if (IS_SET(ch->combatmode, COMBAT_DEFENSIVE) && !MISSILE_WIELD(ch) && IS_SET(ch->attack, ATTACK_MELEE)) { dodge += (learned(ch, gsn_combat_expertise) ? UMIN(stat_bonus(TRUE, ch, APPLY_DEX), base_attack(ch)) : 2); if (learned(ch, gsn_tumble) >= 5) { dodge += 1; if (learned(ch, gsn_combat_expertise)) dodge += 1; } if (learned(ch, gsn_elaborate_defense)) dodge += multi_class_level(ch, gsn_elaborate_defense) / 2; } pop_call(); return dodge; } /* * Dodge penalties still apply if !can_dodge */ int dodge_penalty( CHAR_DATA *ch ) { int dodge = 0; push_call("dodge_penalty(%p)",ch); dodge += stat_bonus(TRUE, ch, APPLY_DEX); dodge += get_apply(ch, APPLY_DODGE); pop_call(); return UMIN(dodge, 0); } /* * Does the character have reach weapon? - Kregor */ bool has_reach( CHAR_DATA *ch, OBJ_DATA *wield ) { push_call("has_reach(%p,%p)",ch,wield); if (!wield) { pop_call(); return FALSE; } if (WSPEC(wield, WSPEC_REACH)) { pop_call(); return TRUE; } pop_call(); return FALSE; } /* * Qualifiers for evasion or imp. evasion feat */ bool can_evade( CHAR_DATA *ch ) { push_call("can_evade(%p)",ch); if (!IS_HELPLESS(ch)) { pop_call(); return FALSE; } if (!learned(ch, gsn_evasion)) { if (!learned(ch, gsn_unstoppable_rage) || !IS_AFFECTED(ch, AFF2_BERSERK)) { pop_call(); return FALSE; } } if (armor_type_worn(ch) >= ARMOR_MEDIUM) { pop_call(); return FALSE; } if (IS_AFFECTED(ch, AFF2_STUNNED)) { pop_call(); return FALSE; } if (IS_FLATFOOTED(ch) && !learned(ch,gsn_uncanny_dodge)) { pop_call(); return FALSE; } if (!IS_AWAKE(ch)) { pop_call(); return FALSE; } if (IS_OVERLOADED(ch)) { pop_call(); return FALSE; } pop_call(); return TRUE; } /* * Returns the total Base Attack bonus for all classes - Kregor 11/26/06 */ int base_attack( CHAR_DATA *ch ) { int bab, lvl, cnt, type; push_call("base_attack(%p)",ch); lvl = ch->level; bab = 0; if (is_affected(ch, gsn_transformation) || is_affected(ch, gsn_divine_power)) { pop_call(); return lvl; } if (!IS_CLASSED(ch)) { type = race_type(ch); if (race_type_table[type].base_attack == BAB_BEST) bab += lvl; else if (race_type_table[type].base_attack == BAB_POOR) bab += lvl / 2; else bab += lvl * 3 / 4; } else { float bab_hi, bab_md, bab_lo; for (cnt = bab_lo = bab_md = bab_hi = 0 ; cnt < MAX_CLASS ; cnt++) { if (lvl <= 0) break; if (class_level(ch, cnt) > 0) { if (cnt == CLASS_MONSTER) { type = race_type(ch); if (race_type_table[type].base_attack == BAB_BEST) bab_hi += UMIN(class_level(ch, cnt), lvl); else if (race_type_table[type].base_attack == BAB_POOR) bab_lo += UMIN(class_level(ch, cnt), lvl); else bab_md += UMIN(class_level(ch, cnt), lvl); } else if (cnt == CLASS_CLERIC && domain_apotheosis(ch, DOMAIN_WAR)) bab_hi += UMIN(class_level(ch, cnt), lvl); else if (class_table[cnt].base_attack == BAB_POOR) bab_lo += UMIN(class_level(ch, cnt), lvl); else if (class_table[cnt].base_attack == BAB_GOOD) bab_md += UMIN(class_level(ch, cnt), lvl); else if (class_table[cnt].base_attack == BAB_BEST) bab_hi += UMIN(class_level(ch, cnt), lvl); lvl -= UMIN(class_level(ch, cnt), lvl); } } bab = 0; if (bab_hi > 0) bab += bab_hi; if (bab_md > 0) bab += bab_md * 3 / 4; if (bab_lo > 0) bab += bab_lo / 2; } pop_call(); return(bab); }