/* * 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: Psychic * Description: - */ #define Uses_Character #define Uses_Util #define Uses_Stats #define Uses_Combat #define Uses_Random #define Uses_Effect #define Uses_Area #define Uses_ProgramManager #define Uses_Faction #define Uses_Object #define Uses_DynamicMessage #define Uses_DataFile #define Uses_Ui #include "mheader.h" #include "psychic.h" #include "psypower.h" #define PSYCHIC_RESISTANCE_MODIFIER -20 #define get_psy_discipline(p) ((p) / MAX_PSY_POWERS_PER_DISCIPLINE) typedef int PSY_TEST_VALUE; typedef int PSY_RANGE_MODIFIER; /* * psychic power target types */ typedef enum { PTT_NIL = -1, PTT_NONE, PTT_CHARACTER_BIO, PTT_CHARACTER_ALL, PTT_OBJECT, PTT_ANY, PTT_OUT_OF_BOUNDS } PSY_TARGET_TYPE; /* * psychic power data structure */ typedef struct { /* the name of the psychic power */ const char name[PSY_POWER_NAME_SIZE]; /* the difficulty modifier */ const PSY_DIFFICULTY_MODIFIER difficulty; /* true if the psychic power is considered hostile */ const bool is_hostile; /* determines which targets are valid */ const PSY_TARGET_TYPE target_type; /* true if the psychic power is a psychic bolt */ const bool is_bolt; /* the associated character flag */ const CHARACTER_FLAG flag; /* called when the psychic power is evoked */ void (* const evoke)\ (const EVOCATION_DATA *); /* called when the psychic power is terminated */ void (* const terminate)(EVENT *); } PSY_POWER_DATA; /* * psychic discipline data structure */ typedef struct { /* the name of the psychic discipline */ const char name[PSY_DISCIPLINE_NAME_SIZE]; /* the associated 'expert' perk (e.g. Telepathy/Telepath) */ PERK expert_perk; } PSY_DISCIPLINE_DATA; static bool psychic_test(const EVOCATION_DATA *); static PSY_RANGE_MODIFIER psy_range_modifier(const EVOCATION_DATA *); static void psychic_overload(CHARACTER *); /* * psychic discipline data */ static const PSY_DISCIPLINE_DATA PsychicDiscipline[MAX_PSY_DISCIPLINES] = { {"Biomancy", PK_BIOMANCER}, {"Telepathy", PK_TELEPATH}, {"Telekineses", PK_TELEKINETIC}, {"Pyromancy", PK_PYROMANCER}, {"Daemonology", PK_DAEMONOLOGIST} }; /* * psychic power data */ static const PSY_POWER_DATA PsychicPower[MAX_PSY_POWERS] = { /* * PSY_DISCIPLINE_BIOMANCY */ {"Regenerate", 0, false, PTT_CHARACTER_BIO, false, CF_NIL, psy_regenerate, NULL }, {"Reactivate", 0, false, PTT_CHARACTER_BIO, false, CF_NIL, psy_reactivate, NULL }, {"Warp strength", -5, false, PTT_CHARACTER_BIO, false, CF_WARP_STRENGTH, psy_warp_strength, psy_warp_strength_terminate }, {"Purify blood", -10, false, PTT_CHARACTER_BIO, false, CF_NIL, psy_purify_blood, NULL }, {"Purge plague", -20, false, PTT_CHARACTER_BIO, false, CF_NIL, psy_purge_plague, NULL }, /* * PSY_DISCIPLINE_TELEPATHY */ {"Mind view", 0, false, PTT_NONE, false, CF_NIL, psy_mind_view, NULL }, {"Psychic shriek", -5, true, PTT_CHARACTER_BIO, false, CF_NIL, psy_psychic_shriek, NULL }, {"Terrify", -10, true, PTT_CHARACTER_BIO, false, CF_NIL, psy_terrify, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, /* * PSY_DISCIPLINE_TELEKINESES */ {"Psychic shield", -15, false, PTT_CHARACTER_ALL, false, CF_PSYCHIC_SHIELD, psy_psychic_shield, psy_psychic_shield_terminate }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, /* * PSY_DISCIPLINE_PYROMANCY */ {"Fireball", 0, true, PTT_ANY, true, CF_NIL, psy_fireball, NULL }, {"Firestorm", -10, true, PTT_ANY, true, CF_NIL, psy_firestorm, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, /* * PSY_DISCIPLINE_DAEMONOLOGY */ {"Banishment", -20, true, PTT_CHARACTER_BIO, false, CF_NIL, psy_banishment, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, {"", 0, false, PTT_NIL, false, CF_NIL, NULL, NULL }, }; /* * the psychic power screen */ void psy_power_screen(PSY_POWER power) { command_bar_set(1, CM_EXIT); render_psy_power_screen(power); update_screen(); command_bar_get_command(); } /* * returns the description of a psychic power */ const char * psy_power_description(char *description, PSY_POWER power ) { return data_file_psy_power_description(description, power ); } /* * concentrate */ void concentrate(CHARACTER *character) { if (character->concentration >= MAX_CONCENTRATION_BONUS) { return; } if (character->party == PARTY_PLAYER || character->is_hostile) { dynamic_message(MSG_CONCENTRATE, character, NULL, MOT_NIL ); } character->concentration += 10; } /* * cancels concentration */ void cancel_concentration(CHARACTER *character) { character->concentration = 0; } /* * makes a character attempt to evoke a psychic power */ bool evoke_psy_power(CHARACTER *character, PSY_POWER evoked_power, const AREA_POINT *target_point ) { const PSY_POWER_DATA *power; EVOCATION_DATA evocation; evocation.evoker = character; evocation.power = evoked_power; evocation.target_point = target_point; power = &PsychicPower[evoked_power]; if (!psychic_test(&evocation)) { return false; } dynamic_message(MSG_EVOKE_PSY_POWER, character, power->name, MOT_STRING ); (*power->evoke)(&evocation); return true; } /* * terminates a psychic power */ void psy_power_terminate(EVENT *event) { const PSY_POWER_DATA *power; power = &PsychicPower[event->psy_power]; dynamic_message(MSG_PSY_POWER_TERMINATION, event->character, power->name, MOT_STRING ); if (power->terminate != NULL) { (*power->terminate)(event); } } /* * returns true if the passed character is able to evoke psychic powers */ bool is_able_to_evoke_psy_powers(const CHARACTER *character) { if (!character->perk[PK_PSYCHIC]) { return false; } if (character_has_flag(character, CF_BROKEN)) { return false; } return true; } /* * returns the number of psychic powers the passed character has */ N_PSY_POWERS n_psy_powers(const CHARACTER *character) { N_PSY_POWERS n_powers; int i; for (i = 0, n_powers = 0; i < MAX_PSY_POWERS; i++) { if (character->psy_power[i]) { ++n_powers; } } return n_powers; } /* * returns true if the passed psychic power is implemented */ bool psy_power_implemented(PSY_POWER power) { if (is_empty_string(PsychicPower[power].name)) { return false; } return true; } /* * returns true if the passed psychic power is hostile */ bool psy_power_is_hostile(PSY_POWER power) { return PsychicPower[power].is_hostile; } /* * returns true if the passed psychic power requires a target */ bool psy_power_requires_target(PSY_POWER power) { if (PsychicPower[power].target_type == PTT_NONE) { return false; } return true; } /* * returns true if the passed psychic power is a psychic bolt */ bool is_psychic_bolt(PSY_POWER power) { return PsychicPower[power].is_bolt; } /* * returns true if the passed perk is a psyker perk */ bool is_psyker_perk(PERK perk) { PSY_DISCIPLINE i; if (perk == PK_PSYCHIC || perk == PK_PSYCHIC_STABILITY) { return true; } for (i = 0; i < MAX_PSY_DISCIPLINES; i++) { if (PsychicDiscipline[i].expert_perk == perk) { return true; } } return false; } /* * returns true if the passed psychic power is tricky i.e. * the AI is not smart enough to use it in a sensible way */ bool psy_power_is_tricky(PSY_POWER power) { bool is_tricky; switch (power) { case PSY_WARP_STRENGTH: case PSY_MIND_VIEW: is_tricky = true; break; default: is_tricky = false; } return is_tricky; } /* * returns true if the passed point is a valid target for * the passed psychic power */ bool psy_power_valid_target(PSY_POWER power, const AREA_POINT *target_point) { CHARACTER *target; PSY_TARGET_TYPE target_type; target = character_at(target_point); target_type = PsychicPower[power].target_type; if (target == NULL) { if (target_type == PTT_CHARACTER_BIO || target_type == PTT_CHARACTER_ALL) { return false; } return true; } if (character_has_flag(target, CF_MACHINE) && PsychicPower[power].target_type == PTT_CHARACTER_BIO) { return false; } if (power == PSY_REGENERATE) { if (target->injury == 0) { return false; } } else if (power == PSY_REACTIVATE) { if (!character_has_flag(target, CF_STUNNED)) { return false; } } else if (power == PSY_PURIFY_BLOOD) { if (!character_has_flag(target, CF_POISONED)) { return false; } } else if (power == PSY_PURGE_PLAGUE) { if (!character_has_flag(target, CF_DISEASED)) { return false; } } else if (power == PSY_TERRIFY) { if (character_has_flag(target, CF_BROKEN)) { return false; } } else if (power == PSY_PSYCHIC_SHRIEK) { if (character_has_flag(target, CF_STUNNED)) { return false; } } else if (power == PSY_BANISHMENT) { if (!character_has_flag(target, CF_DAEMONIC)) { return false; } } return true; } /* * returns true if passed psychic power is already in effect * at the passed point */ bool psy_power_in_effect(PSY_POWER power, const AREA_POINT *target_point) { CHARACTER *target; if (PsychicPower[power].flag == CF_NIL) { return false; } target = character_at(target_point); if (target == NULL) { return false; } if (character_has_flag(target, PsychicPower[power].flag)) { return true; } return false; } /* * returns true if the target is immune to the passed psychic power */ bool psy_power_target_is_immune(PSY_POWER power, const AREA_POINT *target_point) { CHARACTER *target; target = character_at(target_point); if (target == NULL) { return false; } if (character_has_flag(target, CF_MACHINE) && PsychicPower[power].target_type == PTT_CHARACTER_BIO) { return true; } if (power == PSY_TERRIFY && (target->perk[PK_IMMUNITY_TO_FEAR] || character_has_flag(target, CF_STOIC))) { return true; } if (power == PSY_BANISHMENT && target->stat[S_TN].current >= 80) { return true; } return false; } /* * returns the name of a psychic power */ const char * psy_power_name(PSY_POWER power) { return PsychicPower[power].name; } /* * returns the discipline of a psychic power */ PSY_DISCIPLINE psy_power_discipline(PSY_POWER power) { return get_psy_discipline(power); } /* * returns the name of a psychic discipline */ const char * psy_discipline_name(PSY_DISCIPLINE discipline_index) { return PsychicDiscipline[discipline_index].name; } /* * returns the difficulty modifier of a psychic power */ PSY_DIFFICULTY_MODIFIER psy_power_difficulty(const CHARACTER *character, PSY_POWER power ) { PSY_DIFFICULTY_MODIFIER modifier; PSY_DISCIPLINE discipline; PERK expert_perk; modifier = PsychicPower[power].difficulty; if (character == NULL) { return modifier; } discipline = get_psy_discipline(power); expert_perk = PsychicDiscipline[discipline].expert_perk; if (expert_perk != PK_NIL && character->perk[expert_perk]) { modifier /= 2; } return modifier; } /* * the "Psychic Powers" screen */ void psy_powers_screen(const CHARACTER *character) { command_bar_set(1, CM_EXIT); render_psy_powers_screen(character); update_screen(); command_bar_get_command(); } /* * name -> psy power index */ PSY_POWER name_to_psy_power(const char *str) { PSY_POWER i; for (i = 0; i < MAX_PSY_POWERS; i++) { if (!psy_power_implemented(i)) { continue; } if (strings_equal(str, PsychicPower[i].name)) { return i; } } die("*** CORE ERROR *** invalid psychic power: %s", str); return PSY_NIL; } /* * the psychic test */ static bool psychic_test(const EVOCATION_DATA *evocation) { const PSY_POWER_DATA *power; CHARACTER *evoker; CHARACTER *target; PSY_TEST_VALUE psy_test_value; int dice_roll; evoker = evocation->evoker; dice_roll = d100(); if (dice_roll >= D100_AUTOMATIC_FAILURE) { if (evoker->perk[PK_PSYCHIC_STABILITY] && dice_roll < 100) { return false; } psychic_overload(evoker); return false; } psy_test_value = evoker->stat[S_FR].current + psy_power_difficulty(evoker, evocation->power) + evoker->concentration; if (!area_points_equal(evocation->target_point, area_point_nil())) { psy_test_value += psy_range_modifier(evocation); } power = &PsychicPower[evocation->power]; if (!area_points_equal(evocation->target_point, area_point_nil())) { target = character_at(evocation->target_point); if (target != NULL && power->is_hostile && !power->is_bolt && target->perk[PK_PSYCHIC_RESISTANCE]) { psy_test_value += PSYCHIC_RESISTANCE_MODIFIER; } } if (d100_test(dice_roll, psy_test_value) >= 0) { return true; } dynamic_message(MSG_EVOKE_PSY_POWER_FAILS, evoker, power->name, MOT_STRING ); return false; } /* * returns the range-based modifier applied when evoking psychic powers */ static PSY_RANGE_MODIFIER psy_range_modifier(const EVOCATION_DATA *evocation) { PSY_RANGE_MODIFIER mod; AREA_DISTANCE target_distance; const CHARACTER *evoker; evoker = evocation->evoker; target_distance = area_distance(&evoker->location, evocation->target_point ); if (target_distance > MAX_LONG_RANGE) { mod = -40; } else if (target_distance > MAX_MEDIUM_RANGE) { mod = -20; } else if (target_distance > MAX_SHORT_RANGE) { mod = -10; } else if (target_distance > MAX_CLOSE_COMBAT_RANGE) { mod = 0; } else { if (evoker->perk[PK_COMBAT_EVOCATION]) { mod = 0; } else { mod = -20; } } if (evoker->weapon != NULL) { if (object_has_attribute(evoker->weapon, OA_FORCE_CHANNEL)) { mod /= 2; } } return mod; } /* * psychic overload */ static void psychic_overload(CHARACTER *character) { effect_activate(character, ET_PSYCHIC_OVERLOAD, TIME_UNDETERMINED ); }