use super::{ add_effect, particles, spatial, EffectType, Entity, Targets, World }; use crate::{ gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Beatitude, Charges, Confusion, Consumable, Destructible, Equipped, Hidden, InBackpack, InflictsDamage, Item, MagicMapper, MasterDungeonMap, Name, ObfuscatedName, Player, Prop, ProvidesHealing, ProvidesIdentify, ProvidesNutrition, ProvidesRemoveCurse, RandomNumberGenerator, Renderable, RunState, SingleActivation, BUC, GrantsSpell, KnownSpells, Position, Viewshed, WantsToRemoveKey, WantsToDelete, }; use crate::data::messages::*; use bracket_lib::prelude::*; use specs::prelude::*; pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: &mut World) { // Check if the item has charges, etc. if let Some(has_charges) = ecs.write_storage::().get_mut(item) { if has_charges.uses <= 0 { let mut rng = ecs.write_resource::(); if rng.roll_dice(1, 121) != 1 { gamelog::Logger::new().append(NOCHARGES_DIDNOTHING).log(); return; } gamelog::Logger::new().colour(YELLOW).append(NOCHARGES_WREST); ecs.write_storage::() .insert(item, Consumable {}) .expect("Could not insert consumable"); } has_charges.uses -= 1; } // Use the item via the generic system let did_something = event_trigger(source, item, target, ecs); // If it's a consumable, delete it if did_something && ecs.read_storage::().get(item).is_some() { let mut removekey = ecs.write_storage::(); removekey.insert(item, WantsToRemoveKey {}).expect("Unable to insert WantsToRemoveKey"); let mut delete = ecs.write_storage::(); delete.insert(item, WantsToDelete {}).expect("Unable to insert WantsToDelete"); } } pub fn trigger(source: Option, trigger: Entity, target: &Targets, ecs: &mut World) { // Remove hidden from the trigger ecs.write_storage::().remove(trigger); // Use the trigger via the generic system let did_something = event_trigger(source, trigger, target, ecs); // If it was single-activation, delete it if did_something && ecs.read_storage::().get(trigger).is_some() { ecs.entities().delete(trigger).expect("Failed to delete entity with a SingleActivation"); } } struct EventInfo { source: Option, entity: Entity, target: Targets, buc: BUC, log: bool, } // TODO: Currently, items can only be used by the player, and so this system is only built for that. // It does almost no sanity-checking to make sure the logs only appear if the effect is taking // place on the player -- once monsters can use an item, their item usage will make logs for // the player saying they were the one who used the item. This will need refactoring then. fn event_trigger( source: Option, entity: Entity, target: &Targets, ecs: &mut World ) -> bool { let buc = if let Some(beatitude) = ecs.read_storage::().get(entity) { beatitude.buc.clone() } else { BUC::Uncursed }; let mut event = EventInfo { source, entity, target: target.clone(), buc, log: false }; let logger = gamelog::Logger::new(); let mut did_something = false; particles::handle_simple_particles(ecs, entity, target); particles::handle_burst_particles(ecs, entity, &target); particles::handle_line_particles(ecs, entity, &target); let (logger, restored_nutrition) = handle_restore_nutrition(ecs, &mut event, logger); let (logger, magic_mapped) = handle_magic_mapper(ecs, &mut event, logger); let (logger, granted_spell) = handle_grant_spell(ecs, &mut event, logger); let (logger, removed_curse) = handle_remove_curse(ecs, &mut event, logger); let (logger, identified) = handle_identify(ecs, &mut event, logger); let (logger, healed) = handle_healing(ecs, &mut event, logger); let (logger, damaged) = handle_damage(ecs, &mut event, logger); let (logger, confused) = handle_confusion(ecs, &mut event, logger); //let (logger, dug) = handle_dig(ecs, &mut event, logger); -- NYI i.e. Wand of Digging did_something |= restored_nutrition || magic_mapped || granted_spell || healed || damaged || confused || removed_curse || identified; if event.log { logger.log(); } return did_something; } fn handle_restore_nutrition( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if ecs.read_storage::().get(event.entity).is_some() { let amount = match event.buc { BUC::Blessed => 600, BUC::Uncursed => 400, BUC::Cursed => 200, }; add_effect(event.source, EffectType::ModifyNutrition { amount }, event.target.clone()); logger = logger .append(NUTRITION) .colour(item_colour_ecs(ecs, event.entity)) .append_n(obfuscate_name_ecs(ecs, event.entity).0) .colour(WHITE) .period() .buc(event.buc.clone(), Some(NUTRITION_CURSED), Some(NUTRITION_BLESSED)); event.log = true; return (logger, true); } return (logger, false); } fn handle_magic_mapper( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if ecs.read_storage::().get(event.entity).is_some() { let mut runstate = ecs.fetch_mut::(); let cursed = if event.buc == BUC::Cursed { true } else { false }; *runstate = RunState::MagicMapReveal { row: 0, cursed: cursed }; logger = logger.append(MAGICMAP).buc(event.buc.clone(), Some(MAGICMAP_CURSED), None); event.log = true; return (logger, true); } return (logger, false); } fn handle_grant_spell( ecs: &mut World, event: &mut EventInfo, logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(_granted_spell) = ecs.read_storage::().get(event.entity) { if let Some(_known_spells) = ecs .write_storage::() .get_mut(event.source.unwrap()) { // TODO: Check if the player knows *this* spell, and add it if not. } else { // TODO: Grant the KnownSpells component, and then add the spell. } } return (logger, false); } fn handle_healing( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(healing_item) = ecs.read_storage::().get(event.entity) { let mut rng = ecs.write_resource::(); let buc_mod = match event.buc { BUC::Blessed => 2, BUC::Cursed => -1, _ => 0, }; let roll = rng.roll_dice(healing_item.n_dice + buc_mod, healing_item.sides) + healing_item.modifier; add_effect( event.source, EffectType::Healing { amount: roll, increment_max: event.buc.noncursed() }, event.target.clone() ); for target in get_entity_targets(&event.target) { if ecs.read_storage::().get(target).is_some() || ecs.read_storage::().get(target).is_some() { continue; } let renderables = ecs.read_storage::(); if ecs.read_storage::().get(target).is_some() { logger = logger .colour(renderable_colour(&renderables, target)) .append("You") .colour(WHITE) .append(HEAL_PLAYER_HIT) .buc(event.buc.clone(), None, Some(HEAL_PLAYER_HIT_BLESSED)); } else { logger = logger .append("The") .colour(renderable_colour(&renderables, target)) .append(obfuscate_name_ecs(ecs, target).0) .colour(WHITE) .append(HEAL_OTHER_HIT); } event.log = true; } return (logger, true); } return (logger, false); } fn handle_damage( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(damage_item) = ecs.read_storage::().get(event.entity) { let mut rng = ecs.write_resource::(); let roll = rng.roll_dice(damage_item.n_dice, damage_item.sides) + damage_item.modifier; add_effect( event.source, EffectType::Damage { amount: roll, damage_type: damage_item.damage_type }, event.target.clone() ); for target in get_entity_targets(&event.target) { if ecs.read_storage::().get(target).is_some() { continue; } let renderables = ecs.read_storage::(); let positions = ecs.read_storage::(); let target_pos = positions.get(target).unwrap_or(&(Position { x: 0, y: 0 })); let viewsheds = ecs.read_storage::(); let player_viewshed = viewsheds.get(*ecs.fetch::()).unwrap(); if ecs.read_storage::().get(target).is_some() { logger = logger .colour(renderable_colour(&renderables, target)) .append("You") .colour(WHITE) .append(DAMAGE_PLAYER_HIT); event.log = true; } else if player_viewshed.visible_tiles.contains(&Point::new(target_pos.x, target_pos.y)) { if ecs.read_storage::().get(target).is_some() { if ecs.read_storage::().get(target).is_some() { logger = logger .append("The") .colour(renderable_colour(&renderables, target)) .append(obfuscate_name_ecs(ecs, target).0) .colour(WHITE) .append(DAMAGE_ITEM_HIT); } } else { logger = logger .append("The") .colour(renderable_colour(&renderables, target)) .append(obfuscate_name_ecs(ecs, target).0) .colour(WHITE) .append(DAMAGE_OTHER_HIT); } event.log = true; } } return (logger, true); } return (logger, false); } #[allow(unused_mut)] fn handle_confusion( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(confusion) = ecs.read_storage::().get(event.entity) { add_effect( event.source, EffectType::Confusion { turns: confusion.turns }, event.target.clone() ); return (logger, true); } return (logger, false); } fn select_single(ecs: &World, runstate: RunState) { let mut new_runstate = ecs.fetch_mut::(); *new_runstate = runstate; } fn handle_identify( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(_i) = ecs.read_storage::().get(event.entity) { let mut rng = ecs.write_resource::(); let mut dm = ecs.fetch_mut::(); let identify_all = match event.buc { BUC::Blessed => rng.roll_dice(1, 5) == 1, BUC::Uncursed => rng.roll_dice(1, 25) == 1, _ => false, }; if !identify_all { select_single(ecs, RunState::ShowIdentify); return (logger, true); } let mut to_identify: Vec<(Entity, String)> = Vec::new(); let mut beatitudes = ecs.write_storage::(); let obfuscated = ecs.read_storage::(); for (e, _i, _bp, name) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() .filter(|(e, _i, bp, name)| { let in_this_backpack = bp.owner == event.source.unwrap(); let has_obfuscated_name = obfuscated.get(*e).is_some(); let already_identified = dm.identified_items.contains(&name.name.clone()); let known_beatitude = beatitudes .get(event.source.unwrap()) .map(|b| b.known) .unwrap_or(true); let result = in_this_backpack && (has_obfuscated_name || !already_identified || !known_beatitude); return result; }) { to_identify.push((e, name.name.clone())); } for item in to_identify { dm.identified_items.insert(item.1); if let Some(beatitude) = beatitudes.get_mut(item.0) { beatitude.known = true; } } logger = logger .append(IDENTIFY_ALL) .buc(event.buc.clone(), None, Some(IDENTIFY_ALL_BLESSED)); event.log = true; return (logger, true); } return (logger, false); } fn handle_remove_curse( ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger ) -> (gamelog::Logger, bool) { if let Some(_r) = ecs.read_storage::().get(event.entity) { let mut to_decurse: Vec = Vec::new(); match event.buc { // If cursed, show the prompt to select one item. BUC::Cursed => { select_single(ecs, RunState::ShowRemoveCurse); return (logger, true); } // If blessed, decurse everything in our backpack. BUC::Blessed => { for (entity, _i, _bp, _b) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() .filter( |(_e, _i, bp, b)| bp.owner == event.source.unwrap() && b.buc == BUC::Cursed ) { to_decurse.push(entity); } } _ => {} } // If noncursed, decurse everything we have equipped. for (entity, _i, _e, _b) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() .filter(|(_e, _i, e, b)| e.owner == event.source.unwrap() && b.buc == BUC::Cursed) { to_decurse.push(entity); } if to_decurse.len() == 0 { match event.buc { BUC::Uncursed => select_single(ecs, RunState::ShowRemoveCurse), BUC::Blessed => { logger = logger.append(REMOVECURSE_BLESSED_FAILED); } _ => {} } return (logger, true); } let mut beatitudes = ecs.write_storage::(); for e in to_decurse { beatitudes .insert(e, Beatitude { buc: BUC::Uncursed, known: true }) .expect("Unable to insert beatitude"); } logger = logger.append(REMOVECURSE).buc(event.buc.clone(), None, Some(REMOVECURSE_BLESSED)); event.log = true; return (logger, true); } return (logger, false); } fn get_entity_targets(target: &Targets) -> Vec { let mut entities: Vec = Vec::new(); match target { Targets::Entity { target } => entities.push(*target), Targets::EntityList { targets } => targets.iter().for_each(|target| entities.push(*target)), Targets::Tile { target } => { spatial::for_each_tile_content(*target, |entity| { entities.push(entity); }); } Targets::TileList { targets } => { targets.iter().for_each(|target| { spatial::for_each_tile_content(*target, |entity| { entities.push(entity); }); }); } } return entities; }