use super::{ gamelog, Attributes, Equipped, GrantsXP, InBackpack, Item, LootTable, Map, Name, ParticleBuilder, Player, Pools, Position, RunState, SufferDamage, }; use crate::gamesystem::{hp_per_level, mana_per_level}; use rltk::prelude::*; use specs::prelude::*; pub struct DamageSystem {} impl<'a> System<'a> for DamageSystem { #[allow(clippy::type_complexity)] type SystemData = ( WriteStorage<'a, Pools>, WriteStorage<'a, SufferDamage>, ReadStorage<'a, Position>, WriteExpect<'a, Map>, Entities<'a>, ReadExpect<'a, Entity>, ReadStorage<'a, Attributes>, WriteExpect<'a, rltk::RandomNumberGenerator>, ReadStorage<'a, GrantsXP>, WriteExpect<'a, ParticleBuilder>, ReadExpect<'a, rltk::Point>, ); fn run(&mut self, data: Self::SystemData) { let ( mut stats, mut damage, positions, mut map, entities, player, attributes, mut rng, xp_granters, mut particle_builder, player_pos, ) = data; let mut xp_gain = 0; for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() { for dmg in damage.amount.iter() { if !stats.god { stats.hit_points.current -= dmg.0; } let pos = positions.get(entity); if let Some(pos) = pos { let idx = map.xy_idx(pos.x, pos.y); map.bloodstains.insert(idx); } if stats.hit_points.current < 1 && dmg.1 { let gives_xp = xp_granters.get(entity); if let Some(xp_value) = gives_xp { xp_gain += xp_value.amount; } if let Some(pos) = pos { let idx = map.xy_idx(pos.x, pos.y); crate::spatial::remove_entity(entity, idx); } } } } if xp_gain != 0 { let mut player_stats = stats.get_mut(*player).unwrap(); let player_attributes = attributes.get(*player).unwrap(); player_stats.xp += xp_gain; let mut next_level_requirement = -1; if player_stats.level < 10 { next_level_requirement = 20 * 2_i32.pow(player_stats.level as u32 - 1); } else if player_stats.level < 20 { next_level_requirement = 10000 * 2_i32.pow(player_stats.level as u32 - 10); } else if player_stats.level < 30 { next_level_requirement = 10000000 * (player_stats.level - 19); } if next_level_requirement != -1 && player_stats.xp >= next_level_requirement { // We've gone up a level! player_stats.level += 1; gamelog::record_event("player_level", 1); gamelog::Logger::new() .append("Welcome to experience level") .append(player_stats.level) .append(".") .log(); for i in 0..5 { if player_pos.y - i > 1 { particle_builder.request( player_pos.x, player_pos.y - i, rltk::RGB::named(rltk::GOLD), rltk::RGB::named(rltk::BLACK), rltk::to_cp437('*'), 200.0, ); } } // Roll for HP gain this level let hp_gained = hp_per_level( &mut rng, player_attributes.constitution.base + player_attributes.constitution.modifiers, ); player_stats.hit_points.max += hp_gained; player_stats.hit_points.current += hp_gained; // Roll for MANA gain this level let mana_gained = mana_per_level( &mut rng, player_attributes.intelligence.base + player_attributes.intelligence.modifiers, ); player_stats.mana.max += mana_gained; player_stats.mana.current += mana_gained; } } // Clear the queue damage.clear(); } } pub fn delete_the_dead(ecs: &mut World) { let mut dead: Vec = Vec::new(); // Using scope to make borrow checker happy { let combat_stats = ecs.read_storage::(); let players = ecs.read_storage::(); let names = ecs.read_storage::(); let items = ecs.read_storage::(); let entities = ecs.entities(); for (entity, stats) in (&entities, &combat_stats).join() { if stats.hit_points.current < 1 { let player = players.get(entity); match player { None => { let victim_name = names.get(entity); if let Some(victim_name) = victim_name { let item = items.get(entity); if let Some(_item) = item { gamelog::Logger::new() .append("The") .npc_name(&victim_name.name) .colour(rltk::WHITE) .append("is destroyed!") .log(); } else { gamelog::Logger::new() .append("The") .npc_name(&victim_name.name) .colour(rltk::WHITE) .append("dies!") .log(); } } dead.push(entity) } // The player died, go to GameOver. Some(_) => { let mut runstate = ecs.write_resource::(); *runstate = RunState::GameOver; } } } } } let (items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead); for loot in loot_to_spawn { crate::raws::spawn_named_entity( &crate::raws::RAWS.lock().unwrap(), ecs, &loot.0, crate::raws::SpawnType::AtPosition { x: loot.1.x, y: loot.1.y }, 0, ); } for item in items_to_delete { ecs.delete_entity(item).expect("Unable to delete item."); } // For everything that died, increment the event log, and delete. for victim in dead { gamelog::record_event("death_count", 1); ecs.delete_entity(victim).expect("Unable to delete."); } } fn handle_dead_entity_items(ecs: &mut World, dead: &Vec) -> (Vec, Vec<(String, Position)>) { let mut to_drop: Vec<(Entity, Position)> = Vec::new(); let mut to_spawn: Vec<(String, Position)> = Vec::new(); let entities = ecs.entities(); let mut equipped = ecs.write_storage::(); let mut carried = ecs.write_storage::(); let mut positions = ecs.write_storage::(); let loot_tables = ecs.read_storage::(); let mut rng = ecs.write_resource::(); // Make list of every item in every dead thing's inv/equip for victim in dead.iter() { let pos = positions.get(*victim); for (entity, equipped) in (&entities, &equipped).join() { if equipped.owner == *victim { // Push equipped item entities and positions if let Some(pos) = pos { to_drop.push((entity, pos.clone())); } } } for (entity, backpack) in (&entities, &carried).join() { if backpack.owner == *victim { // Push backpack item entities and positions if let Some(pos) = pos { to_drop.push((entity, pos.clone())); } } } if let Some(table) = loot_tables.get(*victim) { let roll: f32 = rng.rand(); if roll < table.chance { let potential_drop = crate::raws::roll_on_loot_table(&crate::raws::RAWS.lock().unwrap(), &mut rng, &table.table); if let Some(id) = potential_drop { if let Some(pos) = pos { to_spawn.push((id, pos.clone())); } } } } } const DROP_ONE_IN_THIS_MANY_TIMES: i32 = 6; let mut to_return: Vec = Vec::new(); for drop in to_drop.iter() { if rng.roll_dice(1, DROP_ONE_IN_THIS_MANY_TIMES) == 1 { equipped.remove(drop.0); carried.remove(drop.0); positions.insert(drop.0, drop.1.clone()).expect("Unable to insert Position{}."); } else { to_return.push(drop.0); } } return (to_return, to_spawn); }