/* * 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: Character * Description: - */ #define Uses_Ui #define Uses_CharacterGeneration #define Uses_CharacterAdvancement #define Uses_Inventory #define Uses_Util #define Uses_Object #define Uses_Career #define Uses_Stats #define Uses_Perks #define Uses_Psychic #define Uses_Party #define Uses_Random #define Uses_ProgramManager #define Uses_Event #define Uses_DataFile #define Uses_Effect #define Uses_DynamicMessage #define Uses_Area #define Uses_Npc #include "mheader.h" #include "charact.h" #define CHARACTER_FLAG_NAME_SIZE 32 /* * character flag data structure */ typedef struct { const char name[CHARACTER_FLAG_NAME_SIZE]; const COLOUR colour; } CHARACTER_FLAG_DATA; static void character_screen_setup_command_bar(const CHARACTER *); static void character_screen_basics(const CHARACTER *); /* * character flags data */ static const CHARACTER_FLAG_DATA CharacterFlag[MAX_CHARACTER_FLAGS] = { {"Broken", C_BROKEN}, {"Bull", C_DRUG}, {"Busy", C_TEXT}, {"Cannot speak", C_CANNOT_SPEAK}, {"Daemonic", C_DAEMONIC}, {"Diseased", C_DISEASED}, {"Flash", C_DRUG}, {"Machine", C_MACHINE}, {"Nomat", C_DRUG}, {"Poisoned", C_POISONED}, {"Psychic overload", C_PSYCHIC_OVERLOAD}, {"Psychic shield", C_PSY_POWER}, {"Stoic", C_DRUG}, {"Stunned", C_STUNNED}, {"Unique", C_UNIQUE}, {"Unnoticed", C_UNNOTICED}, {"Warp strength", C_PSY_POWER}, {"Zen", C_DRUG} }; /* * the player character pointer */ static CHARACTER * PlayerCharacter = NULL; /* * the player controlled character pointer */ static CHARACTER * PlayerControlledCharacter = NULL; /* * misc. character pointers for scripts */ static CHARACTER * CharacterSelf = NULL; static CHARACTER * CharacterActiveCharacter = NULL; /* * character template (default values) */ static const CHARACTER CharacterTemplate = { /* NAME */ "", /* NUMERUS */ NUMERUS_SINGULAR, /* SYMBOL */ 'E', /* COLOUR */ C_DEFAULT, /* GENDER */ GENDER_NEUTER, /* DESCRIPTION */ NULL, /* CHARACTER_TYPE */ CT_NPC, /* CONTROLLER */ CC_AI, /* LOCATION */ {AREA_COORD_NIL, AREA_COORD_NIL}, /* IS_HOSTILE */ true, /* INJURY */ 0, /* BLOODY_FEET */ 0, /* CONCENTRATION */ 0, /* ACTION_SPENT */ false, /* STATS */ {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, /* FLAGS */ { false,false,false,false,false,false,false,false,false, false,false,false,false,false,false,false,false,false}, /* PERKS */ {false,false,false,false,false, false,false,false,false,false,false,false,false,false,false, false,false,false,false,false,false,false,false,false,false, false,false,false,false,false,false,false,false,false,false, false,false,false,false,false,false,false,false,false,false}, /* PSY_POWERS */ {false,false,false,false,false, false,false,false,false,false,false,false,false,false,false, false,false,false,false,false,false,false,false,false,false}, /* CAREER */ CAREER_NIL, /* EP */ 0, /* EP_TOTAL */ 0, /* PARTY */ PARTY_NIL, /* INVENTORY */ NULL, /* EQUIPMENT */ NULL, NULL, NULL, NULL, {0, 0}, /* NOTICED_ENEMIES */ NULL, /* AI_DATA */ {AI_STATE_GUARD, AI_STATE_GUARD, AI_STATE_GUARD, TACTIC_PLAN_A, {AREA_COORD_NIL, AREA_COORD_NIL}}, /* CHARACTER_SCRIPT */ "", /* LIST_NODE */ NULL, /* LIST_NODE_INDEX */ LIST_NODE_INDEX_NIL, /* INDEX */ NPC_INDEX_NIL }; /* * allocs a character */ void * character_new(void) { CHARACTER *character; character = checked_malloc(sizeof *character); *character = CharacterTemplate; character->inventory = list_new(); character->noticed_enemies = list_new(); return character; } /* * frees a character */ void character_free(void *data) { CHARACTER *character; character = (CHARACTER *)data; list_free_with(character->inventory, object_free); list_free(character->noticed_enemies); free(character); } /* * destroys a character * i.e. free + handling the complex data management mess */ void character_destroy(void *data) { CHARACTER *character; character = (CHARACTER *)data; if (character->list_node != NULL) { LIST *character_events; character_events = list_new(); remove_character(character, character_events); list_free_with(character_events, event_destroy); } if (character == PlayerControlledCharacter) { PlayerControlledCharacter = NULL; } if (character == PlayerCharacter) { PlayerCharacter = NULL; } character_free(character); } /* * produces a copy of the passed character * * this function is used for NPC creation * * do NOT use this function to clone already created characters, * because this function clones everything, including AI data * */ CHARACTER * character_clone(const CHARACTER *character) { CHARACTER *clone; LIST_NODE *node; clone = checked_malloc(sizeof *clone); *clone = *character; clone->inventory = list_new(); clone->noticed_enemies = list_new(); for (node = character->inventory->head; node != NULL; node = node->next) { OBJECT *object; OBJECT *cloned_object; object = (OBJECT *)node->data; cloned_object = object_clone(object); inventory_add(clone, cloned_object); if (object == character->weapon) { clone->weapon = cloned_object; } else if (object == character->secondary_weapon) { clone->secondary_weapon = cloned_object; } else if (object == character->armour) { clone->armour = cloned_object; } else if (object == character->jump_pack) { clone->jump_pack = cloned_object; } } return clone; } /* * makes the passed character the new player character */ void set_player_character(CHARACTER *character) { PlayerCharacter = character; } /* * returns the player character */ CHARACTER * player_character(void) { return PlayerCharacter; } /* * makes the passed character the new player controlled character */ void set_player_controlled_character(CHARACTER *character) { if (PlayerControlledCharacter != NULL) { PlayerControlledCharacter->controller = CC_AI; PlayerControlledCharacter->symbol = 'H'; } PlayerControlledCharacter = character; if (PlayerControlledCharacter == NULL) { return; } PlayerControlledCharacter->controller = CC_PLAYER; PlayerControlledCharacter->symbol = '@'; } /* * returns the player controlled character */ CHARACTER * player_controlled_character(void) { return PlayerControlledCharacter; } /* * sets a character flag */ void character_set_flag(CHARACTER *character, CHARACTER_FLAG flag) { character->flag[flag] = true; } /* * removes a character flag */ void character_remove_flag(CHARACTER *character, CHARACTER_FLAG flag) { character->flag[flag] = false; } /* * returns true if the passed character has the passed flag */ bool character_has_flag(const CHARACTER *character, CHARACTER_FLAG flag) { return character->flag[flag]; } /* * returns the description of a character */ const char * character_description(const CHARACTER *character) { const char *description; if (character->index != NPC_INDEX_NIL) { description = npc_description(character->index); if (description != NULL && !is_empty_string(description)) { return description; } } if (character->career != CAREER_NIL) { CAREER *career; career = get_career_pointer(character->career); return career->description; } return NULL; } /* * returns the description of a character flag */ char * character_flag_description(char *description, CHARACTER_FLAG flag ) { return data_file_character_flag_description(description, flag ); } /* * the character screen */ void character_screen(CHARACTER *character) { COMMAND entered_command; do { character_screen_setup_command_bar(character); character_screen_basics(character); entered_command = command_bar_get_command(); if (entered_command == CM_PSY_POWERS) { psy_powers_screen(character); } else if (entered_command == CM_CAREER) { career_screen(get_career_pointer(character->career)); } else if (entered_command == CM_ADVANCEMENT) { character_advancement_screen(character); } else if (entered_command == CM_RENAME) { name_character(character); } } while (entered_command != CM_EXIT); } /* * the character flag screen */ void character_flag_screen(CHARACTER_FLAG flag) { command_bar_set(1, CM_EXIT); render_character_flag_screen(flag); update_screen(); command_bar_get_command(); } /* * the name character screen */ void name_character(CHARACTER *character) { GET_TEXT_DIALOGUE dialogue; sprintf(dialogue.prompt, "Name (limit: %d characters / default: random): ", CHARACTER_NAME_SIZE - 1 ); dialogue.prompt_colour = C_FIELD_NAME; dialogue.max_length = CHARACTER_NAME_SIZE - 1; command_bar_set(1, CM_OK); render_name_character_screen(); update_screen(); enter_name_dialogue(&dialogue); strcpy(character->name, dialogue.entered_text); if (strlen(character->name) < 2) { roll_name(character); } } /* * returns the power rating of a character */ POWER_RATING character_power_rating(const CHARACTER *character) { int i; POWER_RATING power_rating; LIST_NODE *node; power_rating = 0; power_rating += character->ep / 10; for (i = 0; i < MAX_STATS; i++) { if (i == S_SG) { continue; } if (i == S_FR && !character->perk[PK_PSYCHIC]) { continue; } if (i == S_LD && character_has_flag(character, CF_MACHINE)) { power_rating += 100; continue; } power_rating += character->stat[i].total; } for (i = 0; i < MAX_PERKS; i++) { if (character->perk[i]) { power_rating += 10; } } for (i = 0; i < MAX_PSY_POWERS; i++) { if (character->psy_power[i]) { power_rating += 10; } } for (node = character->inventory->head; node != NULL; node = node->next) { const OBJECT_DATA *object_data; object_data = object_static_data(node->data); if (object_data->type == OTYPE_MONEY || object_data->value == VALUE_NIL) { continue; } power_rating += object_data->value / 100; } return power_rating; } /* * effect of the recover action */ void character_recover(CHARACTER *character) { int test_value; dynamic_message(MSG_RECOVER, character, NULL, MOT_NIL); test_value = character->stat[S_TN].current; if (character->perk[PK_FIRST_AID]) { test_value += FIRST_AID_RECOVERY_BONUS; } if (character_has_flag(character, CF_DISEASED) && d100_test_passed(test_value + DISEASED_RECOVERY_MODIFIER)) { effect_terminate(character, ET_DISEASED); } if (character_has_flag(character, CF_PSYCHIC_OVERLOAD) && d100_test_passed(character->stat[S_FR].current + PSYCHIC_OVERLOAD_RECOVERY_MODIFIER)) { effect_terminate(character, ET_PSYCHIC_OVERLOAD); } if (character->injury > 0) { if (d100_test_passed(test_value)) { character_recover_injuries(character); } } } /* * recover injuries effect */ void character_recover_injuries(CHARACTER *character) { int bonus; bonus = stat_bonus(character, S_TN); character->injury -= dice(1, 3) + bonus; if (character->injury < 0) { character->injury = 0; } } /* * returns true if the character must recover to regain full strength */ bool character_must_recover(const CHARACTER *character) { if (character->injury > 0 || character_has_flag(character, CF_DISEASED) || character_has_flag(character, CF_PSYCHIC_OVERLOAD)) { return true; } return false; } /* * returns the maximal number of injuries the passed character can survive */ INJURY injury_max(const CHARACTER *character) { INJURY maximum; maximum = character->stat[S_TN].total; if (character->perk[PK_DIE_HARD]) { maximum += percent(DIE_HARD_MAX_INJURY_BONUS, maximum); } return maximum; } /* * returns true if the character has a ranged attack */ bool has_ranged_attack(const CHARACTER *character) { if (character->weapon != NULL && object_static_data(character->weapon)->type == OTYPE_RANGED_COMBAT_WEAPON) { return true; } if (character->secondary_weapon != NULL && object_static_data(character->secondary_weapon)->type == OTYPE_RANGED_COMBAT_WEAPON) { return true; } return false; } /* * returns the name of a character flag */ const char * character_flag_name(CHARACTER_FLAG flag) { return CharacterFlag[flag].name; } /* * returns the colour of a character flag */ COLOUR character_flag_colour(CHARACTER_FLAG flag) { return CharacterFlag[flag].colour; } /* * name -> character flag */ CHARACTER_FLAG name_to_character_flag(const char *name) { CHARACTER_FLAG i; for (i = 0; i < MAX_CHARACTER_FLAGS; i++) { if (strings_equal(name, CharacterFlag[i].name)) { return i; } } die("*** CORE ERROR *** invalid character flag: %s", name); /* NEVER REACHED */ return 0; } /* * sets up the character screen command bar */ static void character_screen_setup_command_bar(const CHARACTER *character) { CAREER *career; career = get_career_pointer(character->career); command_bar_clear(); if (n_psy_powers(character) > 0) { command_bar_add(1, CM_PSY_POWERS); } if (career != NULL) { command_bar_add(1, CM_CAREER); } if (character->party == PARTY_PLAYER) { command_bar_add(2, CM_ADVANCEMENT, CM_RENAME); } command_bar_add(1, CM_EXIT); } /* * the character screen basics */ static void character_screen_basics(const CHARACTER *character) { render_character_screen_basics(character); update_screen(); } /* * returns a pointer to the self character pointer */ CHARACTER ** character_self(void) { return &CharacterSelf; } /* * returns a pointer to the active character pointer */ CHARACTER ** character_active_character(void) { return &CharacterActiveCharacter; }