remove curse - with full BUC impl

This commit is contained in:
Llywelwyn 2023-08-22 10:50:44 +01:00
parent 5a42ee283d
commit e76a03633b
6 changed files with 219 additions and 13 deletions

View file

@ -33,8 +33,8 @@
"id": "villager_equipment",
"table": [
{ "id": "equip_pitchfork", "weight": 1, "difficulty": 1},
{ "id": "equip_sickle", "weight": 1, "difficulty": 1},
{ "id": "equip_handaxe", "weight": 1, "difficulty": 1}
{ "id": "equip_sickle", "weight": 1, "difficulty": 1},
{ "id": "equip_handaxe", "weight": 1, "difficulty": 1}
]
},
{

View file

@ -1,8 +1,9 @@
use super::{add_effect, get_noncursed, particles, spatial, targeting, EffectType, Entity, Targets, World};
use super::{add_effect, get_noncursed, 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, Hidden, InflictsDamage, Item, MagicMapper, Player, Prop, ProvidesHealing,
ProvidesNutrition, RandomNumberGenerator, Renderable, RunState, SingleActivation, BUC,
Consumable, Destructible, Equipped, Hidden, InBackpack, InflictsDamage, Item, MagicMapper, Player, Prop,
ProvidesHealing, ProvidesNutrition, RandomNumberGenerator, RemovesCurse, Renderable, RunState, SingleActivation,
BUC,
};
use rltk::prelude::*;
use specs::prelude::*;
@ -61,14 +62,17 @@ fn event_trigger(source: Option<Entity>, entity: Entity, target: &Targets, ecs:
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, removed_curse) = handle_remove_curse(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);
did_something |= restored_nutrition || magic_mapped || healed || damaged || confused;
//let (logger, dug) = handle_dig(ecs, &mut event, logger); -- NYI i.e. Wand of Digging
did_something |= restored_nutrition || magic_mapped || healed || damaged || confused || removed_curse;
if event.log {
logger.log();
@ -208,6 +212,72 @@ fn handle_confusion(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog:
return (logger, false);
}
fn select_single_remove_curse(ecs: &World) {
let mut runstate = ecs.fetch_mut::<RunState>();
*runstate = RunState::ShowRemoveCurse;
}
fn handle_remove_curse(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> (gamelog::Logger, bool) {
if let Some(_r) = ecs.read_storage::<RemovesCurse>().get(event.entity) {
let mut to_decurse: Vec<Entity> = Vec::new();
let mut cursed_in_backpack = false;
let mut cursed_in_inventory = false;
match event.buc {
// If cursed, show the prompt to select one item.
BUC::Cursed => {
select_single_remove_curse(ecs);
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)
{
cursed_in_backpack = true;
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)
{
cursed_in_inventory = true;
to_decurse.push(entity);
}
if to_decurse.len() == 0 {
match event.buc {
BUC::Uncursed => select_single_remove_curse(ecs),
BUC::Blessed => logger = logger.append("You feel righteous! ... but nothing happens."),
_ => {}
}
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("You feel a reassuring presence.").buc(event.buc.clone(), None, Some("You feel righteous!"));
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 {

View file

@ -11,6 +11,8 @@ mod character_creation;
mod cheat_menu;
mod letter_to_option;
pub use character_creation::*;
mod remove_curse_menu;
pub use remove_curse_menu::*;
mod tooltip;
pub use cheat_menu::*;
@ -391,15 +393,18 @@ pub fn get_max_inventory_width(inventory: &BTreeMap<UniqueInventoryItem, i32>) -
let mut width: i32 = 0;
for (item, count) in inventory {
let mut this_width = item.display_name.singular.len() as i32;
if count < &1 {
this_width += 6;
if item.display_name.singular.ends_with("s") {
this_width += 5;
// Clean this up. It should use consts.
this_width += 4; // The spaces before and after the character to select this item, etc.
if count <= &1 {
if item.display_name.singular == item.display_name.plural {
this_width += 4; // "some".len
} else if ['a', 'e', 'i', 'o', 'u'].iter().any(|&v| item.display_name.singular.starts_with(v)) {
this_width += 3;
this_width += 2; // "an".len
} else {
this_width += 1; // "a".len
}
} else {
this_width += 6;
this_width += count.to_string().len() as i32; // i.e. "12".len
}
width = if width > this_width { width } else { this_width };
}

View file

@ -0,0 +1,113 @@
use super::{
get_max_inventory_width, item_colour_ecs, obfuscate_name_ecs, print_options, renderable_colour, ItemMenuResult,
UniqueInventoryItem,
};
use crate::{gamelog, Beatitude, Entity, Equipped, InBackpack, Item, Name, Renderable, State, BUC};
use rltk::prelude::*;
use specs::prelude::*;
use std::collections::BTreeMap;
/// Handles the Remove Curse menu.
pub fn remove_curse(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
let player_entity = gs.ecs.fetch::<Entity>();
let equipped = gs.ecs.read_storage::<Equipped>();
let backpack = gs.ecs.read_storage::<InBackpack>();
let entities = gs.ecs.entities();
let items = gs.ecs.read_storage::<Item>();
let beatitudes = gs.ecs.read_storage::<Beatitude>();
let names = gs.ecs.read_storage::<Name>();
let renderables = gs.ecs.read_storage::<Renderable>();
let build_cursed_iterator = || {
(&entities, &items, &beatitudes, &renderables, &names).join().filter(|(item_entity, _i, b, _r, _n)| {
// Set all items to FALSE initially.
let mut keep = false;
// If found in the player's backpack, set to TRUE
if let Some(bp) = backpack.get(*item_entity) {
if bp.owner == *player_entity {
keep = true;
}
}
// If found in the player's equipslot, set to TRUE
if let Some(equip) = equipped.get(*item_entity) {
if equip.owner == *player_entity {
keep = true;
}
}
// If it's not OUR item, RETURN FALSE.
if !keep {
return false;
}
// If it's identified as noncursed, RETURN FALSE.
if b.known && b.buc != BUC::Cursed {
return false;
}
// Otherwise, return: returns any items that are unidentified,
// or identified as being cursed.
return true;
})
};
// Build list of items to display
let count = build_cursed_iterator().count();
// If no items, return nothing, wasting the scroll.
if count == 0 {
return (ItemMenuResult::Cancel, None);
}
// If only one item, return it.
if count == 1 {
return (ItemMenuResult::Selected, Some(build_cursed_iterator().nth(0).unwrap().0));
}
let mut player_inventory: BTreeMap<UniqueInventoryItem, i32> = BTreeMap::new();
let mut inventory_ids: BTreeMap<String, Entity> = BTreeMap::new();
for (entity, _i, _b, renderable, name) in build_cursed_iterator() {
let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity);
player_inventory
.entry(UniqueInventoryItem {
display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() },
rgb: item_colour_ecs(&gs.ecs, entity),
renderables: renderable_colour(&renderables, entity),
glyph: renderable.glyph,
name: name.name.clone(),
})
.and_modify(|count| *count += 1)
.or_insert(1);
inventory_ids.entry(singular).or_insert(entity);
}
// Get display args
let width = get_max_inventory_width(&player_inventory);
let (_, _, _, _, x_offset, y_offset) = crate::camera::get_screen_bounds(&gs.ecs, ctx);
let (x, y) = (x_offset + 1, y_offset + 3);
// Draw menu
ctx.print_color(
1 + x_offset,
1 + y_offset,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"Decurse which item? [aA-zZ][Esc.]",
);
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
print_options(player_inventory, x + 1, y + 1, ctx);
// Input
match ctx.key {
None => (ItemMenuResult::NoResponse, None),
Some(key) => match key {
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
_ => {
let selection = rltk::letter_to_option(key);
if selection > -1 && selection < count as i32 {
let item = inventory_ids.iter().nth(selection as usize).unwrap().1;
gamelog::Logger::new()
.append("You decurse the")
.colour(item_colour_ecs(&gs.ecs, *item))
.append_n(obfuscate_name_ecs(&gs.ecs, *item).0)
.colour(WHITE)
.append("!")
.log();
return (ItemMenuResult::Selected, Some(*item));
}
(ItemMenuResult::NoResponse, None)
}
},
}
}

View file

@ -89,11 +89,12 @@ impl<'a> System<'a> for ItemEquipSystem {
if target == *player_entity {
logger = logger
.append("You remove your")
.colour(item_colour(*item, &beatitudes, &dm))
.append_n(
obfuscate_name(*item, &names, &magic_items, &obfuscated_names, &beatitudes, &dm, None)
.0,
)
.colour(item_colour(*item, &beatitudes, &dm))
.colour(rltk::WHITE)
.period();
}
}

View file

@ -53,6 +53,7 @@ pub enum RunState {
ShowDropItem,
ShowRemoveItem,
ShowTargeting { range: i32, item: Entity, aoe: i32 },
ShowRemoveCurse,
ActionWithDirection { function: fn(i: i32, j: i32, ecs: &mut World) -> RunState },
MainMenu { menu_selection: gui::MainMenuSelection },
CharacterCreation { ancestry: gui::Ancestry, class: gui::Class },
@ -261,6 +262,7 @@ impl GameState for State {
RunState::MagicMapReveal { row, cursed } => {
new_runstate = RunState::MagicMapReveal { row: row, cursed: cursed }
}
RunState::ShowRemoveCurse => new_runstate = RunState::ShowRemoveCurse,
_ => new_runstate = RunState::Ticking,
}
}
@ -380,6 +382,21 @@ impl GameState for State {
}
}
}
RunState::ShowRemoveCurse => {
let result = gui::remove_curse(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_runstate = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
self.ecs
.write_storage::<Beatitude>()
.insert(item_entity, Beatitude { buc: BUC::Uncursed, known: true })
.expect("Unable to insert beatitude");
new_runstate = RunState::Ticking;
}
}
}
RunState::ActionWithDirection { function } => {
new_runstate = gui::get_input_direction(&mut self.ecs, ctx, function);
}