/* * 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: Death * Description: Death and destruction */ #define Uses_Ui #define Uses_Game #define Uses_Character #define Uses_CharacterAdvancement #define Uses_Npc #define Uses_ProgramManager #define Uses_Inventory #define Uses_Random #define Uses_Party #define Uses_Area #define Uses_LoadSave #define Uses_Terrain #define Uses_Util #define Uses_Object #define Uses_DynamicMessage #define Uses_Round #define Uses_Actions #define Uses_Honour #define Uses_Script #define Uses_DataFile #include "mheader.h" #include "death.h" #define DEATH_DROP_PROBABILITY 50 #define SPLATTER_MIN_N_BODY_PARTS 0 #define SPLATTER_MAX_N_BODY_PARTS 3 #define MAX_GORE_LEVELS 6 #define GORE_LEVEL_NAME_SIZE 20 static void character_death(CHARACTER *, CHARACTER *, DEATH_TYPE); static void player_character_death(CHARACTER *); static void execute_death_script(CHARACTER *); static void object_destruction(OBJECT *, const AREA_POINT *); static void splatter(CHARACTER *, DEATH_TYPE); static void splatter_corpse(CHARACTER *, DEATH_TYPE); static void splatter_body_parts(CHARACTER *); static void splatter_place_body_part(SECTOR *); static void death_drop(CHARACTER *); static void death_drop_all(CHARACTER *); static void give_ep_for_kill(CHARACTER *, CHARACTER *); static void restart_from_save_point(void); /* * gore level names */ static const char GoreLevelName[MAX_GORE_LEVELS][GORE_LEVEL_NAME_SIZE] = { "", "Blood", "Body part", "Machine parts", "Poisoned corpse", "Corpse" }; /* * checks the passed sector for destroyed characters and objects, * and calls the appropriate functions to handle the destructions * if there are any */ void handle_destruction(const AREA_POINT *location, CHARACTER *attacker, DEATH_TYPE death_type ) { AREA_POINT p; CHARACTER *character; OBJECT *object; p = *location; character = character_at(&p); if (character != NULL && character_killed(character)) { character_death(character, attacker, death_type); } object = object_at(&p); if (object != NULL && object_destroyed(object)) { object_destruction(object, &p); } } /* * returns true if the passed character has been killed */ bool character_killed(const CHARACTER *character) { if (character->injury > injury_max(character)) { return true; } return false; } /* * returns true if the passed object has been destroyed */ bool object_destroyed(const OBJECT *object) { if (object->condition <= 0) { return true; } return false; } /* * splatter (blood) */ void splatter_blood(SECTOR *sector) { OBJECT *object; TERRAIN *terrain; object = sector->object; if (object != NULL) { const OBJECT_DATA *object_data; object_data = object_static_data(object); if (object_data->type == OTYPE_ENVIRONMENT && object->gore_level < GORE_LEVEL_BLOOD) { object->gore_level = GORE_LEVEL_BLOOD; } } terrain = sector->terrain; if (!terrain_has_attribute(terrain, TA_DANGEROUS) && terrain->gore_level < GORE_LEVEL_BLOOD) { terrain->gore_level = GORE_LEVEL_BLOOD; } } /* * removes all splatter effects from the active area */ void remove_splatter(void) { AREA_POINT p; const AREA_SECTION *bounds; bounds = area_bounds(); for (p.y = bounds->top; p.y <= bounds->bottom; p.y++) { for (p.x = bounds->left; p.x <= bounds->right; p.x++) { SECTOR *sector; sector = sector_at(&p); if (sector->terrain != NULL) { sector->terrain->gore_level = GORE_LEVEL_ZERO; } if (sector->object != NULL) { sector->object->gore_level = GORE_LEVEL_ZERO; } } } } /* * returns the name of a gore level */ const char * gore_level_name(GORE_LEVEL gore_level) { return GoreLevelName[gore_level]; } /* * handles character deaths */ static void character_death(CHARACTER *character, CHARACTER *killer, DEATH_TYPE death_type ) { dynamic_message(MSG_DEATH, character, NULL, MOT_NIL); sector_at(&character->location)->character = NULL; if (character->index != NPC_INDEX_NIL) { NPC_COUNTER *counter; counter = npc_counter(character->index); counter->n_killed += 1; } if (death_type != DT_FALL) { death_drop(character); splatter(character, death_type); } if (killer != NULL) { give_ep_for_kill(killer, character); /* * hack - assumes that all deaths caused by poison are * indirectly caused by the player */ } else if (death_type == DT_POISON) { give_ep_for_kill(player_character(), character); } if (character == player_character()) { if (game_difficulty() == DIFFICULTY_EASY) { restart_from_save_point(); return; } player_character_death(character); } if (is_player_controlled_character(character)) { switch_character(CT_PC); } if (character->party == PARTY_PLAYER) { party_leave(character); } if (!is_empty_string(character->script)) { execute_death_script(character); } if (character != active_character()) { character_destroy(character); } } /* * handles the death of the player character */ static void player_character_death(CHARACTER *character) { message(SID_GAME, "Game over. Press Enter."); render_game_screen(); update_screen(); while (get_key() != KEY_ENTER) /* DO NOTHING */; certificate_screen(); character_destroy(character); world_erase(); program_shutdown(); } /* * executes the death script of the killed character */ static void execute_death_script(CHARACTER *character) { *character_self() = character; script_load(DIR_NPCS, character->script); script_set_data("KILLED", SCRIPT_TRUE); script_set_data("SELF", SCRIPT_SELF_PTR); script_execute(); } /* * handles object destruction */ static void object_destruction(OBJECT *object, const AREA_POINT *object_location ) { dynamic_message(MSG_DESTRUCTION, NULL, object, MOT_OBJECT); sector_at(object_location)->object = NULL; object_destroy(object); } /* * splatter */ static void splatter(CHARACTER *character, DEATH_TYPE death_type) { if (!character_has_flag(character, CF_MACHINE) && death_type != DT_POISON) { SECTOR *sector; sector = sector_at(&character->location); splatter_blood(sector); } splatter_corpse(character, death_type); if (!character_has_flag(character, CF_MACHINE) && death_type != DT_POISON) { splatter_body_parts(character); } } /* * splatter (corpse) */ static void splatter_corpse(CHARACTER *character, DEATH_TYPE death_type) { SECTOR *sector; OBJECT *object; TERRAIN *terrain; sector = sector_at(&character->location); object = sector->object; if (object != NULL && (object_has_attribute(object, OA_IMPASSABLE) || object_has_attribute(object, OA_DANGEROUS))) { return; } terrain = sector->terrain; if (terrain_has_attribute(terrain, TA_IMPASSABLE) || terrain_has_attribute(terrain, TA_DANGEROUS)) { return; } if (character_has_flag(character, CF_MACHINE)) { terrain->gore_level = GORE_LEVEL_MACHINE_PARTS; } else if (death_type == DT_POISON) { terrain->gore_level = GORE_LEVEL_POISONED_CORPSE; } else { terrain->gore_level = GORE_LEVEL_CORPSE; } } /* * splatter (body parts) */ static void splatter_body_parts(CHARACTER *character) { DIRECTION direction[MAX_REAL_DIRECTIONS]; int i, dir_i; int n_body_parts; n_body_parts = random_int( SPLATTER_MIN_N_BODY_PARTS, SPLATTER_MAX_N_BODY_PARTS ); randomised_directions(direction); for (i = dir_i = 0; i < n_body_parts; i++) { AREA_POINT target_point; SECTOR *sector; target_point = character->location; move_area_point(&target_point, direction[dir_i]); ++dir_i; if (dir_i == MAX_REAL_DIRECTIONS) { dir_i = 0; } sector = sector_at(&target_point); splatter_blood(sector); splatter_place_body_part(sector); } } /* * splatter (place body part) */ static void splatter_place_body_part(SECTOR *sector) { OBJECT *object; TERRAIN *terrain; object = sector->object; terrain = sector->terrain; if (terrain->gore_level >= GORE_LEVEL_BODY_PART) { return; } if (object != NULL && (object_has_attribute(object, OA_IMPASSABLE) || object_has_attribute(object, OA_DANGEROUS))) { return; } if (terrain_has_attribute(terrain, TA_IMPASSABLE) || terrain_has_attribute(terrain, TA_DANGEROUS)) { return; } terrain->gore_level = GORE_LEVEL_BODY_PART; } /* * at death a character may drop objects * this function handles this */ static void death_drop(CHARACTER *character) { if (character == player_character()) { return; } if (character_has_flag(character, CF_UNIQUE)) { death_drop_all(character); } else { OBJECT *object; if (!random_choice(DEATH_DROP_PROBABILITY)) { return; } object = inventory_random_object(character); if (object == NULL || object_has_attribute(object, OA_INHERENT)) { return; } action_drop_object(character, object, false); } } /* * drops all items */ static void death_drop_all(CHARACTER *character) { LIST_NODE *node; OBJECT *object; for (node = character->inventory->head; node != NULL;) { object = (OBJECT *)node->data; node = node->next; if (object_has_attribute(object, OA_INHERENT)) { continue; } action_drop_object(character, object, false); } } /* * calculates EP reward for a kill and hands out the reward to * the killer's party */ static void give_ep_for_kill(CHARACTER *killer, CHARACTER *victim) { POWER_RATING victim_power; LIST_NODE *node; if (killer->party != PARTY_PLAYER) { return; } victim_power = character_power_rating(victim); for (node = party_player()->head; node != NULL; node = node->next) { CHARACTER *character; POWER_RATING character_power; EXPERIENCE_POINTS ep_reward; character = (CHARACTER *)node->data; character_power = character_power_rating(character); if (victim_power < percent(50, character_power)) { continue; } ep_reward = victim_power - character_power_rating(character); if (ep_reward < 0) { ep_reward = 0; } ep_reward += 10; give_ep(character, ep_reward); } } /* * restart from the last save point */ static void restart_from_save_point(void) { message(SID_GAME, "Restart the game to continue from the last save point. "\ "Press Enter." ); render_game_screen(); update_screen(); while (get_key() != KEY_ENTER) /* DO NOTHING */; character_destroy(player_character()); program_shutdown(); }