/* * 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 */ /* * Common functionality */ #define Uses_Area #define Uses_Character #define Uses_Object #define Uses_Random #define Uses_Perception #define Uses_Equipment #define Uses_Util #define Uses_DynamicMessage #define Uses_Perks #define Uses_Effect #define Uses_Stats #include "mheader.h" #include "combat.h" #define CRITICAL_HIT_DAMAGE_MULTIPLIER 2 #define UNARMED_COMBAT_BASE_DAMAGE dice(1, 3) #define MORALE_BROKEN_EFFECT_DURATION 20 static void hit_character(const ATTACK_DATA *, CHARACTER *); static void hit_character_message(const ATTACK_DATA *, const CHARACTER * ); static void hit_object(const ATTACK_DATA *, OBJECT *); static DAMAGE base_damage(const ATTACK_DATA *); static ARMOUR_VALUE effective_armour_value(const ATTACK_DATA *, const CHARACTER * ); static void pre_armour_penetration_effects(const ATTACK_DATA *, CHARACTER *, DAMAGE * ); static void post_armour_penetration_effects(const ATTACK_DATA *, CHARACTER * ); static bool morale_test_passed(CHARACTER *); /* * attack data template (default values) */ static const ATTACK_DATA AttackDataTemplate = { /* ATTACK TYPE */ AT_NIL, /* ATTACKER */ NULL, /* WEAPON */ NULL, /* FIRING DATA */ NULL, /* TARGET POINT */ {AREA_COORD_NIL, AREA_COORD_NIL}, /* DISTANCE */ AREA_DISTANCE_NIL, /* SNEAK */ false, /* RAPID */ false, /* HIT VALUE */ 0, /* HIT ROLL */ 0, /* CRITICAL HIT */ false }; /* * initializes an attack with default values */ void attack_init(ATTACK_DATA *attack) { *attack = AttackDataTemplate; } /* * returns true if the target has been hit */ bool target_hit(ATTACK_DATA *attack) { if (attack->sneak && !attack->rapid) { return true; } if (d100_test(attack->hit_roll, attack->hit_value) < 0) { return false; } return true; } /* * determines whether a critical hit has been scored */ void critical_hit_scored(ATTACK_DATA *attack) { const CHARACTER *attacker; int critical_hit_value; attacker = attack->attacker; if (attack->sneak && !attack->rapid) { attack->critical_hit = true; return; } if (attack->type == AT_STRIKE && attacker->perk[PK_BLADEMASTER] && is_knife(attack->weapon)) { attack->critical_hit = true; return; } if (attack->hit_value <= 0) { return; } critical_hit_value = divide_and_round_up(attack->hit_value, 10); if (attack->type == AT_STRIKE && attacker->perk[PK_CRITICAL_STRIKE]) { critical_hit_value *= 2; } else if (attack->type == AT_SHOT && attacker->perk[PK_CRITICAL_SHOT] && !attack->rapid) { critical_hit_value *= 2; } if (d100_test(attack->hit_roll, critical_hit_value) >= 0) { attack->critical_hit = true; } } /* * handles what happens in the case of a hit */ void hit(ATTACK_DATA *attack) { SECTOR *sector; critical_hit_scored(attack); sector = sector_at(&(attack->target_point)); if (sector->character != NULL) { hit_character(attack, sector->character); } else if (sector->object != NULL) { hit_object(attack, sector->object); } } /* * handles what happens in the case of a miss */ void miss(ATTACK_DATA *attack) { SECTOR *sector; sector = sector_at(&(attack->target_point)); if (sector->character != NULL) { dynamic_message(MSG_MISS, attack->attacker, sector->character, MOT_CHARACTER ); } else if (sector->object != NULL) { dynamic_message(MSG_MISS, attack->attacker, sector->object, MOT_OBJECT ); } } /* * hit character */ static void hit_character(const ATTACK_DATA *attack, CHARACTER *target) { DAMAGE damage; hit_character_message(attack, target); damage = base_damage(attack); pre_armour_penetration_effects(attack, target, &damage); if (attack->critical_hit) { damage *= CRITICAL_HIT_DAMAGE_MULTIPLIER; } damage -= effective_armour_value(attack, target); if (damage <= 0) { return; } post_armour_penetration_effects(attack, target); target->injury += damage; if (morale_test_passed(target)) { return; } effect_activate(target, ET_BROKEN, MORALE_BROKEN_EFFECT_DURATION); } /* * hit character message */ static void hit_character_message(const ATTACK_DATA *attack, const CHARACTER *target ) { if (attack->critical_hit) { dynamic_message(MSG_CRITICAL_HIT, attack->attacker, target, MOT_CHARACTER ); } else { dynamic_message(MSG_HIT, attack->attacker, target, MOT_CHARACTER ); } } /* * hit object */ static void hit_object(const ATTACK_DATA *attack, OBJECT *target) { DAMAGE damage; dynamic_message(MSG_HIT, attack->attacker, target, MOT_OBJECT); damage = base_damage(attack); if (target->condition != CONDITION_INDESTRUCTABLE) { target->condition -= damage; } } /* * returns the base damage */ static DAMAGE base_damage(const ATTACK_DATA *attack) { DAMAGE damage; if (attack->weapon == NULL) { damage = UNARMED_COMBAT_BASE_DAMAGE; } else { const DICE_ROLL *damage_dice; damage_dice = &( object_static_data(attack->weapon)->damage ); damage = dice(damage_dice->n_dice, damage_dice->n_sides) + damage_dice->modifier; } if (attack->type == AT_STRIKE) { int strength_bonus; strength_bonus = stat_bonus(attack->attacker, S_ST); if (attack->attacker->perk[PK_CRUSHING_STRIKE]) { strength_bonus *= CRUSHING_STRIKE_STRENGTH_BONUS_MULTIPLIER; } damage += strength_bonus; } return damage; } /* * returns the effective armour value of the target */ static ARMOUR_VALUE effective_armour_value(const ATTACK_DATA *attack, const CHARACTER *target ) { ARMOUR_VALUE armour_value; armour_value = target->armour_rating.current; if (attack->attacker->perk[PK_SPOT_WEAKNESS]) { armour_value /= SPOT_WEAKNESS_ARMOUR_VALUE_DIVIDER; } if (attack->weapon == NULL) { return armour_value; } if (object_has_attribute(attack->weapon, OA_POWER)) { armour_value /= POWER_WEAPON_ARMOUR_VALUE_DIVIDER; } if (object_has_attribute(attack->weapon, OA_IGNORE_ARMOUR)) { armour_value = 0; } return armour_value; } /* * pre armour penetration effects */ static void pre_armour_penetration_effects(const ATTACK_DATA *attack, CHARACTER *target, DAMAGE *damage ) { const CHARACTER *attacker; const OBJECT *weapon; attacker = attack->attacker; weapon = attack->weapon; if (weapon == NULL) return; if (object_has_attribute(weapon, OA_FORCE) && attacker->perk[PK_PSYCHIC]) { if (d100_test_passed(attacker->stat[S_FR].current)) { int force_bonus; if (object_has_attribute(weapon, OA_FORCE_RUNE)) { force_bonus = FORCE_RUNE_DAMAGE_BONUS; } else { force_bonus = FORCE_DAMAGE_BONUS; } if (character_has_flag(target, CF_DAEMONIC)) { force_bonus *= FORCE_VS_DAEMON_MULTIPLIER; } *damage += force_bonus; } } } /* * post armour penetration effects */ static void post_armour_penetration_effects(const ATTACK_DATA *attack, CHARACTER *target ) { const CHARACTER *attacker; const OBJECT *weapon; attacker = attack->attacker; weapon = attack->weapon; if (weapon == NULL) { if (attack->type == AT_STRIKE && attacker->perk[PK_DISEASE_ATTACK]) { if (disease_resisted(target)) { dynamic_message(MSG_RESIST, target, NULL, MOT_NIL ); } else { effect_activate(target, ET_DISEASED, TIME_UNDETERMINED ); } } return; } if (object_has_attribute(weapon, OA_SHOCK)) { if (d100_test_passed(target->stat[S_TN].current)) { dynamic_message(MSG_RESIST, target, NULL, MOT_NIL); } else { effect_activate(target, ET_STUNNED, SHOCK_STUN_POWER ); } } if (object_has_attribute(weapon, OA_PAIN)) { if (pain_resisted(target)) { dynamic_message(MSG_RESIST, target, NULL, MOT_NIL ); } else { effect_activate(target, ET_BROKEN, MORALE_BROKEN_EFFECT_DURATION ); } } if (object_has_attribute(weapon, OA_PLAGUE)) { if (disease_resisted(target)) { dynamic_message(MSG_RESIST, target, NULL, MOT_NIL ); } else { effect_activate(target, ET_DISEASED, TIME_UNDETERMINED ); } } if (object_has_attribute(weapon, OA_POISON)) { if (poison_resisted(target)) { dynamic_message(MSG_RESIST, target, NULL, MOT_NIL ); } else { effect_activate(target, ET_POISONED, TIME_UNDETERMINED ); } } } /* * the morale test * * returns true if passed */ static bool morale_test_passed(CHARACTER *character) { int max_pain; max_pain = percent(character->stat[S_LD].current, injury_max(character) ); if (character->injury <= max_pain) { return true; } if (pain_resisted(character)) { return true; } return false; }