obfuscates names of unidentified items

This commit is contained in:
Llywelwyn 2023-08-13 20:16:24 +01:00
parent 7795044d36
commit 9e768c5f73
9 changed files with 166 additions and 29 deletions

View file

@ -24,7 +24,8 @@
"weight": 0.5,
"value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
"effects": { "ranged": "12", "damage": "10" }
"effects": { "ranged": "12", "damage": "10" },
"magic": { "class": "common", "naming": "scroll" }
},
{
"id": "scroll_fireball",
@ -33,7 +34,8 @@
"weight": 0.5,
"value": 150,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
"effects": { "ranged": "10", "damage": "15", "aoe": "3" }
"effects": { "ranged": "10", "damage": "15", "aoe": "3" },
"magic": { "class": "rare", "naming": "scroll" }
},
{
"id": "scroll_fireball_c",
@ -42,7 +44,8 @@
"weight": 0.5,
"value": 150,
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "CURSED"],
"effects": { "ranged": "10", "damage": "15", "aoe": "3" }
"effects": { "ranged": "10", "damage": "15", "aoe": "3" },
"magic": { "class": "rare", "naming": "scroll" }
},
{
"id": "scroll_confusion",
@ -51,7 +54,8 @@
"weight": 0.5,
"value": 100,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
"effects": { "ranged": "10", "confusion": "4" }
"effects": { "ranged": "10", "confusion": "4" },
"magic": { "class": "uncommon", "naming": "scroll" }
},
{
"id": "scroll_magicmap",
@ -60,7 +64,8 @@
"weight": 0.5,
"value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
"effects": { "magicmapper": "" }
"effects": { "magicmapper": "" },
"magic": { "class": "common", "naming": "scroll" }
},
{
"id": "scroll_magicmap_c",
@ -69,7 +74,8 @@
"weight": 0.5,
"value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "CURSED"],
"effects": { "magicmapper": "" }
"effects": { "magicmapper": "" },
"magic": { "class": "common", "naming": "scroll" }
},
{
"id": "equip_dagger",

View file

@ -222,6 +222,12 @@ pub struct MagicItem {
pub class: MagicItemClass,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ObfuscatedName {
pub name: String,
pub plural: String,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EquipmentChanged {}

View file

@ -1,7 +1,7 @@
use super::{
ai::CARRY_CAPACITY_PER_STRENGTH, camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes,
Burden, Equipped, Hidden, HungerClock, HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, Prop,
Renderable, RunState, Skill, Skills, State, Viewshed,
Burden, Equipped, Hidden, HungerClock, HungerState, InBackpack, MagicItem, Map, Name, ObfuscatedName, Player,
Point, Pools, Position, Prop, Renderable, RunState, Skill, Skills, State, Viewshed,
};
use rltk::{Rltk, VirtualKeyCode, RGB};
use specs::prelude::*;
@ -143,13 +143,13 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.print_color(20, 20, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "--- GODMODE: ON ---");
}
// Draw equipment
let names = ecs.read_storage::<Name>();
let renderables = ecs.read_storage::<Renderable>();
let mut equipment: Vec<(String, RGB)> = Vec::new();
for (_equipped, name, renderable) in
(&equipped, &names, &renderables).join().filter(|item| item.0.owner == *player_entity)
let entities = ecs.entities();
for (entity, _equipped, renderable) in
(&entities, &equipped, &renderables).join().filter(|item| item.1.owner == *player_entity)
{
equipment.push((name.name.to_string(), renderable.fg));
equipment.push((get_item_display_name(ecs, entity).0, renderable.fg));
}
let mut y = 1;
if !equipment.is_empty() {
@ -205,12 +205,13 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
if entity == &*player_entity {
draw = false;
}
let name = &names.get(*entity);
if let Some(name) = name {
if draw {
let fg = renderables.get(*entity).unwrap().fg;
seen_entities.push((name.name.to_string(), fg));
}
if draw {
let fg = if let Some(renderable) = renderables.get(*entity) {
renderable.fg
} else {
RGB::named(rltk::WHITE)
};
seen_entities.push((get_item_display_name(ecs, *entity).0, fg));
}
}
}
@ -360,6 +361,25 @@ pub fn get_max_inventory_width(inventory: &BTreeMap<(String, String, (u8, u8, u8
return width;
}
pub fn get_item_display_name(ecs: &World, item: Entity) -> (String, String) {
if let Some(name) = ecs.read_storage::<Name>().get(item) {
if ecs.read_storage::<MagicItem>().get(item).is_some() {
let dm = ecs.fetch::<crate::map::MasterDungeonMap>();
if dm.identified_items.contains(&name.name) {
return (name.name.clone(), name.plural.clone());
} else if let Some(obfuscated) = ecs.read_storage::<ObfuscatedName>().get(item) {
return (obfuscated.name.clone(), obfuscated.plural.clone());
} else {
return ("unid magic item".to_string(), "unid magic items".to_string());
}
} else {
return (name.name.clone(), name.plural.clone());
}
} else {
return ("nameless item (bug)".to_string(), "nameless items (bug)".to_string());
}
}
pub fn show_help(ctx: &mut Rltk) -> YesNoResult {
let mut x = 3;
let mut y = 12;
@ -423,10 +443,8 @@ pub fn get_player_inventory(ecs: &World) -> (BTreeMap<(String, String, (u8, u8,
} else {
(255, 255, 255)
};
player_inventory
.entry((name.name.to_string(), name.plural.to_string(), (r, g, b)))
.and_modify(|count| *count += 1)
.or_insert(1);
let (singular, plural) = get_item_display_name(ecs, entity);
player_inventory.entry((singular, plural, (r, g, b))).and_modify(|count| *count += 1).or_insert(1);
inventory_ids.entry(name.name.to_string()).or_insert(entity);
}
@ -505,10 +523,9 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option
pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
let player_entity = gs.ecs.fetch::<Entity>();
let names = gs.ecs.read_storage::<Name>();
let backpack = gs.ecs.read_storage::<Equipped>();
let entities = gs.ecs.entities();
let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity);
let inventory = (&backpack).join().filter(|item| item.owner == *player_entity);
let count = inventory.count();
let (x_offset, y_offset) = (1, 10);
@ -523,8 +540,8 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti
let mut equippable: Vec<(Entity, String)> = Vec::new();
let mut width = 3;
for (entity, _pack, name) in (&entities, &backpack, &names).join().filter(|item| item.1.owner == *player_entity) {
let this_name = &name.name;
for (entity, _pack) in (&entities, &backpack).join().filter(|item| item.1.owner == *player_entity) {
let this_name = &get_item_display_name(&gs.ecs, entity).0;
let this_width = 3 + this_name.len();
width = if width > this_width { width } else { this_width };
equippable.push((entity, this_name.to_string()));

View file

@ -526,6 +526,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<Viewshed>();
gs.ecs.register::<Telepath>();
gs.ecs.register::<Name>();
gs.ecs.register::<ObfuscatedName>();
gs.ecs.register::<BlocksTile>();
gs.ecs.register::<BlocksVisibility>();
gs.ecs.register::<Door>();

View file

@ -3,17 +3,28 @@ use crate::{gamelog, map_builders, OtherLevelPosition, Position, Telepath, Views
use rltk::prelude::*;
use serde::{Deserialize, Serialize};
use specs::prelude::*;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
#[derive(Default, Serialize, Deserialize, Clone)]
pub struct MasterDungeonMap {
maps: HashMap<i32, Map>,
pub identified_items: HashSet<String>,
pub scroll_map: HashMap<String, (String, String)>,
}
impl MasterDungeonMap {
/// Initialises a blank MasterDungeonMap
pub fn new() -> MasterDungeonMap {
return MasterDungeonMap { maps: HashMap::new() };
let mut dm =
MasterDungeonMap { maps: HashMap::new(), identified_items: HashSet::new(), scroll_map: HashMap::new() };
// TODO: Use stored RNG
let mut rng = RandomNumberGenerator::new();
for scroll_tag in crate::raws::get_scroll_tags().iter() {
let (unid_singular, unid_plural) = make_scroll_name(&mut rng);
dm.scroll_map.insert(scroll_tag.to_string(), (unid_singular, unid_plural));
}
return dm;
}
/// Stores the given map in the MasterDungeonMap
pub fn store_map(&mut self, map: &Map) {
@ -31,6 +42,52 @@ impl MasterDungeonMap {
}
}
fn make_scroll_name(rng: &mut RandomNumberGenerator) -> (String, String) {
let len = 4 + rng.roll_dice(1, 4);
let mut singular = "scroll of ".to_string();
let mut plural = "scrolls of ".to_string();
for i in 0..len {
if i % 2 == 0 {
let char = match rng.roll_dice(1, 5) {
1 => "a",
2 => "e",
3 => "i",
4 => "o",
_ => "u",
};
singular += char;
plural += char;
} else {
let char = match rng.roll_dice(1, 21) {
1 => "b",
2 => "c",
3 => "d",
4 => "f",
5 => "g",
6 => "h",
7 => "j",
8 => "k",
9 => "l",
10 => "m",
11 => "n",
12 => "p",
13 => "q",
14 => "r",
15 => "s",
16 => "t",
17 => "v",
18 => "w",
19 => "x",
20 => "y",
_ => "z",
};
singular += char;
plural += char;
}
}
return (singular, plural);
}
pub fn level_transition(ecs: &mut World, new_id: i32, offset: i32) -> Option<Vec<Map>> {
// Obtain master
let dungeon_master = ecs.read_resource::<MasterDungeonMap>();

View file

@ -10,6 +10,7 @@ pub struct Item {
pub value: Option<f32>,
pub flags: Option<Vec<String>>,
pub effects: Option<HashMap<String, String>>,
pub magic: Option<MagicItem>,
}
#[derive(Deserialize, Debug)]
@ -25,3 +26,9 @@ pub struct Renderable {
pub bg: String,
pub order: i32,
}
#[derive(Deserialize, Debug)]
pub struct MagicItem {
pub class: String,
pub naming: String,
}

View file

@ -111,6 +111,7 @@ pub fn spawn_named_entity(
pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
if raws.item_index.contains_key(key) {
let item_template = &raws.raws.items[raws.item_index[key]];
let scroll_names = ecs.fetch::<crate::map::MasterDungeonMap>().scroll_map.clone();
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
eb = eb.with(Name { name: item_template.name.name.clone(), plural: item_template.name.plural.clone() });
@ -146,7 +147,6 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn
}
}
}
let mut base_damage = "1d4";
let mut hit_bonus = 0;
@ -168,6 +168,27 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn
}
}
}
if let Some(magic_item) = &item_template.magic {
let item_class = match magic_item.class.as_str() {
"common" => MagicItemClass::Common,
"uncommon" => MagicItemClass::Uncommon,
"rare" => MagicItemClass::Rare,
"veryrare" => MagicItemClass::VeryRare,
_ => MagicItemClass::Legendary,
};
eb = eb.with(MagicItem { class: item_class });
#[allow(clippy::single_match)]
match magic_item.naming.as_str() {
"scroll" => {
eb = eb.with(ObfuscatedName {
name: scroll_names[&item_template.name.name].0.clone(),
plural: scroll_names[&item_template.name.name].1.clone(),
})
}
_ => {}
}
}
if weapon_type != -1 {
let (n_dice, die_type, bonus) = parse_dice_string(base_damage);
@ -621,3 +642,16 @@ pub fn get_mob_spawn_amount(rng: &mut RandomNumberGenerator, spawn_type: &Spawns
_ => return roll,
};
}
pub fn get_scroll_tags() -> Vec<String> {
let raws = &super::RAWS.lock().unwrap();
let mut result = Vec::new();
for item in raws.raws.items.iter() {
if let Some(magic) = &item.magic {
if &magic.naming == "scroll" {
result.push(item.name.name.clone());
}
}
}
return result;
}

View file

@ -85,6 +85,7 @@ pub fn save_game(ecs: &mut World) {
MultiAttack,
NaturalAttacks,
Name,
ObfuscatedName,
OtherLevelPosition,
ParticleLifetime,
Player,
@ -198,6 +199,7 @@ pub fn load_game(ecs: &mut World) {
MultiAttack,
NaturalAttacks,
Name,
ObfuscatedName,
OtherLevelPosition,
ParticleLifetime,
Player,

View file

@ -69,6 +69,13 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
raws::SpawnType::Equipped { by: player },
0,
);
raws::spawn_named_entity(
&raws::RAWS.lock().unwrap(),
ecs,
"equip_dagger",
raws::SpawnType::Equipped { by: player },
0,
);
raws::spawn_named_entity(
&raws::RAWS.lock().unwrap(),
ecs,