rust-rl/src/effects/triggers.rs
Llywelwyn c5106a63b5 static inventory keys - items remember their slots
this is the biggest refactor of my entire life
2024-06-15 20:14:38 +01:00

465 lines
17 KiB
Rust

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<Entity>, item: Entity, target: &Targets, ecs: &mut World) {
// Check if the item has charges, etc.
if let Some(has_charges) = ecs.write_storage::<Charges>().get_mut(item) {
if has_charges.uses <= 0 {
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
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::<Consumable>()
.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::<Consumable>().get(item).is_some() {
let mut removekey = ecs.write_storage::<WantsToRemoveKey>();
removekey.insert(item, WantsToRemoveKey {}).expect("Unable to insert WantsToRemoveKey");
let mut delete = ecs.write_storage::<WantsToDelete>();
delete.insert(item, WantsToDelete {}).expect("Unable to insert WantsToDelete");
}
}
pub fn trigger(source: Option<Entity>, trigger: Entity, target: &Targets, ecs: &mut World) {
// Remove hidden from the trigger
ecs.write_storage::<Hidden>().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::<SingleActivation>().get(trigger).is_some() {
ecs.entities().delete(trigger).expect("Failed to delete entity with a SingleActivation");
}
}
struct EventInfo {
source: Option<Entity>,
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: Entity,
target: &Targets,
ecs: &mut World
) -> bool {
let buc = if let Some(beatitude) = ecs.read_storage::<Beatitude>().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::<ProvidesNutrition>().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::<MagicMapper>().get(event.entity).is_some() {
let mut runstate = ecs.fetch_mut::<RunState>();
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::<GrantsSpell>().get(event.entity) {
if
let Some(_known_spells) = ecs
.write_storage::<KnownSpells>()
.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::<ProvidesHealing>().get(event.entity) {
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
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::<Prop>().get(target).is_some() ||
ecs.read_storage::<Item>().get(target).is_some()
{
continue;
}
let renderables = ecs.read_storage::<Renderable>();
if ecs.read_storage::<Player>().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::<InflictsDamage>().get(event.entity) {
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
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::<Prop>().get(target).is_some() {
continue;
}
let renderables = ecs.read_storage::<Renderable>();
let positions = ecs.read_storage::<Position>();
let target_pos = positions.get(target).unwrap_or(&(Position { x: 0, y: 0 }));
let viewsheds = ecs.read_storage::<Viewshed>();
let player_viewshed = viewsheds.get(*ecs.fetch::<Entity>()).unwrap();
if ecs.read_storage::<Player>().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::<Item>().get(target).is_some() {
if ecs.read_storage::<Destructible>().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::<Confusion>().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::<RunState>();
*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::<ProvidesIdentify>().get(event.entity) {
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let mut dm = ecs.fetch_mut::<MasterDungeonMap>();
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::<Beatitude>();
let obfuscated = ecs.read_storage::<ObfuscatedName>();
for (e, _i, _bp, name) in (
&ecs.entities(),
&ecs.read_storage::<Item>(),
&ecs.read_storage::<InBackpack>(),
&ecs.read_storage::<Name>(),
)
.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::<ProvidesRemoveCurse>().get(event.entity) {
let mut to_decurse: Vec<Entity> = 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::<Item>(),
&ecs.read_storage::<InBackpack>(),
&ecs.read_storage::<Beatitude>(),
)
.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::<Item>(),
&ecs.read_storage::<Equipped>(),
&ecs.read_storage::<Beatitude>(),
)
.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::<Beatitude>();
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<Entity> {
let mut entities: Vec<Entity> = 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;
}