/* * Copyright (C) 2002-2007 The Warp Rogue Team * Part of the Warp Rogue Project * * This software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY. * * See the license.txt file for more details. */ /* * Module Name: Combat * Description: The combat system */ /* * Close combat */ #define Uses_Area #define Uses_Perks #define Uses_Random #define Uses_Round #define Uses_Object #define Uses_Faction #define Uses_Death #define Uses_DynamicMessage #define Uses_Perception #define Uses_Stats #include "mheader.h" #include "combat.h" #include "combat_c.h" static void hit_value_strike(ATTACK_DATA *); static bool strike_parried(const ATTACK_DATA *, const CHARACTER * ); static WEAPON_REACH effective_reach(const CHARACTER *, const OBJECT *); static REACH_MODIFIER reach_modifier(const CHARACTER *, const OBJECT *, const CHARACTER *, const OBJECT * ); /* * executes a strike / strikes */ void strike(CHARACTER *attacker, OBJECT *attacker_weapon, const AREA_POINT *target_point ) { ATTACK_DATA attack; N_STRIKES n_strikes; CHARACTER *target; attack_init(&attack); attack.type = AT_STRIKE; attack.attacker = attacker; attack.weapon = attacker_weapon; attack.target_point = *target_point; attack.distance = 1; if (attacker->perk[PK_SNEAK_ATTACK] && character_unnoticed(attacker)) { attack.sneak = true; } n_strikes = 1; if (attacker->perk[PK_RAPID_STRIKE] && rapid_strike_requirements_met(attacker, attacker_weapon)) { ++n_strikes; attack.rapid = true; } target = character_at(target_point); while (n_strikes-- > 0) { attack.hit_roll = d100(); hit_value_strike(&attack); if (!target_hit(&attack)) { miss(&attack); continue; } if (target != NULL && strike_parried(&attack, target)) { dynamic_message(MSG_PARRY, target, attacker, MOT_CHARACTER ); continue; } hit(&attack); } handle_destruction(target_point, attacker, DT_ATTACK); if (character_unnoticed(attacker)) { notice_check(attacker, false, true); } } /* * returns true if the passed character is involved in close combat */ bool involved_in_close_combat(const CHARACTER *character) { int y_mod, x_mod; for (y_mod = -1; y_mod <= 1; y_mod++) { for (x_mod = -1; x_mod <= 1; x_mod++) { const CHARACTER *adjacent_character; AREA_POINT p; if (x_mod == 0 && y_mod == 0) { continue; } p.y = character->location.y + y_mod; p.x = character->location.x + x_mod; if (out_of_area_bounds(&p)) { continue; } adjacent_character = character_at(&p); if (adjacent_character != NULL && hostility_between(character, adjacent_character)) { return true; } } } return false; } /* * determines the hit value of a strike */ static void hit_value_strike(ATTACK_DATA *attack) { SECTOR *sector; CHARACTER *target; sector = sector_at(&attack->target_point); target = sector->character; if (target == NULL) { if (sector->object != NULL) { attack->hit_value = OBJECT_HIT_VALUE; } else { attack->hit_value = 0; } return; } attack->hit_value = attack->attacker->stat[S_CC].current; attack->hit_value += reach_modifier( attack->attacker, attack->weapon, target, target->weapon ); if (target->perk[PK_UNCANNY_DODGE]) { attack->hit_value += uncanny_dodge_modifier(target); } } /* * returns true if a strike has been parried */ static bool strike_parried(const ATTACK_DATA *attack, const CHARACTER *target) { PARRY_VALUE parry_value; PARRY_PENALTY parry_penalty; const OBJECT_DATA *weapon_data; if (!character_can_act(target) || character_unnoticed(attack->attacker)) { return false; } if (target->weapon == NULL) { return false; } weapon_data = object_static_data(target->weapon); if (weapon_data->type != OTYPE_CLOSE_COMBAT_WEAPON || weapon_data->attribute[OA_NO_PARRY]) { return false; } parry_value = target->stat[S_CC].current / 2; parry_penalty = weapon_data->parry_penalty; if (target->perk[PK_ENHANCED_PARRY]) { parry_penalty += ENHANCED_PARRY_BONUS; if (parry_penalty > 0) { parry_penalty = 0; } } parry_value += parry_penalty; parry_value += reach_modifier(target, target->weapon, attack->attacker, attack->weapon ); if (d100_test_passed(parry_value)) { return true; } return false; } /* * returns the effective weapon reach */ static WEAPON_REACH effective_reach(const CHARACTER *character, const OBJECT *weapon ) { const OBJECT_DATA *weapon_data; if (weapon == NULL) { return 0; } weapon_data = object_static_data(weapon); if (weapon_data->type != OTYPE_CLOSE_COMBAT_WEAPON) { return 0; } if (character->perk[PK_BLADEMASTER] && weapon_data->subtype == OSTYPE_KNIFE) { return 4; } return weapon_data->reach; } /* * returns the reach modifier */ static REACH_MODIFIER reach_modifier( const CHARACTER *protagonist, const OBJECT *protagonist_weapon, const CHARACTER *antagonist, const OBJECT *antagonist_weapon ) { WEAPON_REACH protagonist_reach, antagonist_reach; REACH_MODIFIER modifier; protagonist_reach = effective_reach(protagonist, protagonist_weapon); antagonist_reach = effective_reach(antagonist, antagonist_weapon); modifier = (protagonist_reach * 10) - (antagonist_reach * 10); return modifier; }