diff --git a/blastmud_game/src/models/effect.rs b/blastmud_game/src/models/effect.rs index a5df7cab..f27966ac 100644 --- a/blastmud_game/src/models/effect.rs +++ b/blastmud_game/src/models/effect.rs @@ -10,6 +10,8 @@ pub enum EffectType { Bleed, Stunned, CurrentRoom, + SnakePoisoned, + Stung, } pub struct EffectSet { diff --git a/blastmud_game/src/services/effect.rs b/blastmud_game/src/services/effect.rs index 9ee75036..1627caa8 100644 --- a/blastmud_game/src/services/effect.rs +++ b/blastmud_game/src/services/effect.rs @@ -76,6 +76,7 @@ impl TaskHandler for DelayedParameterTaskHandler { magnitude, message, parameter, + delay, .. }) => { let mut item_mut = (*item).clone(); @@ -92,7 +93,7 @@ impl TaskHandler for DelayedParameterTaskHandler { Ok(item_effect_series .1 .front() - .map(|it| time::Duration::from_secs(it.delay))) + .map(|it| time::Duration::from_secs(it.delay - delay))) } } } @@ -541,7 +542,302 @@ pub fn default_effects_for_type() -> &'static BTreeMap { ) }, ] - } + }, + EffectSet { + effect_type: EffectType::SnakePoisoned, + effects: vec![ + Effect::BroadcastMessage { + delay_secs: 0, + messagef: Box::new(|_player, _item, target| + format!(ansi!("You notice that {} has a fresh bite wound with some kind of yellow liquid on the bite site!\n"), + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 60, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -6, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks a bit unwell", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 120, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks pretty unwell", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 180, + base_effect: -18, + skill_multiplier: 0.0, + max_effect: -18, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks very sick", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 240, + base_effect: -18, + skill_multiplier: 0.0, + max_effect: -18, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks very sick", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 300, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks pretty unwell", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 360, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks pretty unwell", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 420, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -6, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} looks a bit unwell", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 480, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -6, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} feels the final effects of snake venom as it leaves {} body", + target.display_for_sentence(1, false), + target.pronouns.possessive, + ) + ) + }, + ], + }, + EffectSet { + effect_type: EffectType::Stung, + effects: vec![ + Effect::ChangeTargetParameter { + delay_secs: 0, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} howls out in pain from a sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 10, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} howls out in pain from a sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 20, + base_effect: -12, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} howls out in pain from a sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 30, + base_effect: -8, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} yelps in pain from the sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 40, + base_effect: -8, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} yelps in pain from the sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 50, + base_effect: -8, + skill_multiplier: 0.0, + max_effect: -12, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} yelps in pain from the sting", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 60, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -10, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{}'s cries as the sting really hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 70, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -10, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{}'s cries as the sting really hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 80, + base_effect: -6, + skill_multiplier: 0.0, + max_effect: -10, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{}'s cries as the sting really hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 90, + base_effect: -4, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} sobs as the sting still hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 100, + base_effect: -4, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} sobs as the sting still hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 110, + base_effect: -4, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} sobs as the sting still hurts", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 120, + base_effect: -2, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} whimpers as the sting starts to heal", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 130, + base_effect: -2, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} whimpers as the sting is almost healed", + target.display_for_sentence(1, false), + ) + ) + }, + Effect::ChangeTargetParameter { + delay_secs: 140, + base_effect: -2, + skill_multiplier: 0.0, + max_effect: -8, + parameter: EffectParameter::Health, + message: Box::new(|target| + format!("{} whimpers as the sting is finally healed", + target.display_for_sentence(1, false), + ) + ) + }, + ], + }, ] .into_iter() .map(|et| (et.effect_type.clone(), et)) diff --git a/blastmud_game/src/static_content/npc.rs b/blastmud_game/src/static_content/npc.rs index 13367b58..9a5e4f4e 100644 --- a/blastmud_game/src/static_content/npc.rs +++ b/blastmud_game/src/static_content/npc.rs @@ -39,6 +39,7 @@ use uuid::Uuid; pub mod computer_museum_npcs; mod melbs_npcs; +mod northrad_npcs; mod roboporter; mod sewer_npcs; pub mod statbot; @@ -192,6 +193,7 @@ pub fn npc_list() -> &'static Vec { ..Default::default() }]; npcs.append(&mut melbs_npcs::npc_list()); + npcs.append(&mut northrad_npcs::npc_list()); npcs.append(&mut roboporter::npc_list()); npcs.append(&mut computer_museum_npcs::npc_list()); npcs.append(&mut sewer_npcs::npc_list()); diff --git a/blastmud_game/src/static_content/npc/northrad_npcs.rs b/blastmud_game/src/static_content/npc/northrad_npcs.rs new file mode 100644 index 00000000..4c116a7a --- /dev/null +++ b/blastmud_game/src/static_content/npc/northrad_npcs.rs @@ -0,0 +1,102 @@ +use super::NPC; +use crate::{ + models::{ + consent::ConsentType, + item::{Pronouns, SkillType}, + }, + static_content::{npc::KillBonus, possession_type::PossessionType, species::SpeciesType}, +}; +use serde::Deserialize; +use serde_yaml::from_str as from_yaml_str; + +#[derive(Deserialize)] +enum NorthradNPC { + Scorpion { + code: String, + name: String, + spawn_loc: String, + }, + Snake { + code: String, + name: String, + spawn_loc: String, + }, +} + +pub fn npc_list() -> Vec { + from_yaml_str::>(include_str!("northrad_npcs.yaml")) + .unwrap() + .into_iter() + .map(|c| match c { + NorthradNPC::Scorpion { code, name, spawn_loc } => + NPC { + code: format!("northrad_{}", &code), + name, + pronouns: Pronouns::default_inanimate(), + aliases: vec!["scorpion".to_owned()], + aggression: 14, + description: "A large scorpion. Its eyes, black like beads of obsidian, give it a menacing look. Its curved tail writhes menacingly over its back. At the tip of its tail is a sharp stinger; a drop of some kind of liquid pools on the tip".to_owned(), + spawn_location: format!("room/northrad_{}", &spawn_loc), + intrinsic_weapon: Some(PossessionType::Stinger), + kill_bonus: Some(KillBonus { + msg: "On your wristpad: The Rangelands Service thanks you for your assistance! Please accept this small reward.", + payment: 250, + }), + aggro_pc_only: true, + total_xp: 15000, + total_skills: SkillType::values() + .into_iter() + .map(|sk| { + ( + sk.clone(), + match sk { + SkillType::Dodge => 14.0, + SkillType::Fists => 18.0, + _ => 8.0 + } + ) + }).collect(), + player_consents: vec!(ConsentType::Fight), + species: SpeciesType::Scorpion, + message_handler: None, + wander_zones: vec!["northern_radfields".to_owned()], + ..Default::default() + }, + NorthradNPC::Snake { code, name, spawn_loc } => + NPC { + code: format!("northrad_{}", &code), + name, + pronouns: Pronouns::default_inanimate(), + aliases: vec!["snake".to_owned()], + aggression: 14, + description: "A large snake, its scales glinting with a metallic sheen. Its eyes, cold and calculating, remain ever alert to potential threats. It has adopted an S-shaped posture, as if preparing to strike. It occasionally samples the air by flicking its forked tongue out of its mouth".to_owned(), + spawn_location: format!("room/northrad_{}", &spawn_loc), + intrinsic_weapon: Some(PossessionType::VenomousFangs), + species: SpeciesType::Snake, + kill_bonus: Some(KillBonus { + msg: "On your wristpad: The Rangelands Service thanks you for your assistance! Please accept this small reward.", + payment: 250, + }), + aggro_pc_only: true, + total_xp: 15000, + total_skills: SkillType::values() + .into_iter() + .map(|sk| { + ( + sk.clone(), + match sk { + SkillType::Dodge => 14.0, + SkillType::Fists => 19.0, + _ => 8.0 + } + ) + }) + .collect(), + player_consents: vec!(ConsentType::Fight), + message_handler: None, + wander_zones: vec!["northern_radfields".to_owned()], + ..Default::default() + }, + } + ).collect() +} diff --git a/blastmud_game/src/static_content/npc/northrad_npcs.yaml b/blastmud_game/src/static_content/npc/northrad_npcs.yaml new file mode 100644 index 00000000..9801b697 --- /dev/null +++ b/blastmud_game/src/static_content/npc/northrad_npcs.yaml @@ -0,0 +1,72 @@ +- !Scorpion + code: scorpion_0 + name: hyperactive scorpion + spawn_loc: k3 +- !Snake + code: snake_0 + name: malevolent serpant + spawn_loc: k4 +- !Scorpion + code: scorpion_1 + name: gleaming brown scorpion + spawn_loc: k6 +- !Scorpion + code: scorpion_2 + name: agile scorpion + spawn_loc: j3 +- !Snake + code: snake_1 + name: dangerous-looking snake + spawn_loc: j5 +- !Scorpion + code: scorpion_3 + name: crimson-banded scorpion + spawn_loc: j7 +- !Snake + code: snake_2 + name: sleek serpent + spawn_loc: i4 +- !Scorpion + code: scorpion_4 + name: stalking scorpion + spawn_loc: i6 +- !Snake + code: snake_3 + name: slithering serpent + spawn_loc: i7 +- !Scorpion + code: scorpion_5 + name: lurking scorpion + spawn_loc: h2 +- !Snake + code: snake_4 + name: aggressive stripey snake + spawn_loc: h3 +- !Scorpion + code: scorpion_6 + name: shadowy scorpion + spawn_loc: h6 +- !Snake + code: snake_5 + name: sinister serpent + spawn_loc: g4 +- !Scorpion + code: scorpion_7 + name: ebony scorpion + spawn_loc: g5 +- !Snake + code: snake_6 + name: red-bellied snake + spawn_loc: g7 +- !Scorpion + code: scorpion_8 + name: swift scorpion + spawn_loc: f4 +- !Snake + code: snake_7 + name: brown-scaled snake + spawn_loc: f5 +- !Scorpion + code: snake_8 + name: twitchy-tailed snake + spawn_loc: f6 diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index f2005892..84370f2c 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -410,6 +410,8 @@ impl WeaponAttackData { pub enum PossessionType { // Special values that substitute for possessions. Fangs, // Default weapon for certain animals + VenomousFangs, + Stinger, // Real possessions from here on: // Armour / Clothes RustyMetalPot, @@ -460,6 +462,8 @@ pub enum PossessionType { AnimalSkin, SeveredHead, RustySpike, + VenomSac, + SnakeSkin, // Craft benches KitchenStove, // Recipes @@ -553,9 +557,10 @@ pub fn fist() -> &'static WeaponData { pub fn possession_data() -> &'static BTreeMap { static POSSESSION_DATA: OnceCell> = OnceCell::new(); - use PossessionType::*; &POSSESSION_DATA.get_or_init(|| { - vec![(Fangs, fangs::data())] + fangs::data() + .iter() + .map(|v| ((*v).0.clone(), &(*v).1)) .into_iter() .chain(whip::data().iter().map(|v| ((*v).0.clone(), &(*v).1))) .chain(bags::data().iter().map(|v| ((*v).0.clone(), &(*v).1))) diff --git a/blastmud_game/src/static_content/possession_type/fangs.rs b/blastmud_game/src/static_content/possession_type/fangs.rs index 48f71d36..6abbea6f 100644 --- a/blastmud_game/src/static_content/possession_type/fangs.rs +++ b/blastmud_game/src/static_content/possession_type/fangs.rs @@ -1,35 +1,111 @@ use super::{PossessionData, WeaponAttackData, WeaponData}; -use crate::models::item::SkillType; +use crate::{ + models::{effect::EffectType, item::SkillType}, + static_content::possession_type::{DamageType, PossessionType}, +}; use once_cell::sync::OnceCell; -pub fn data() -> &'static PossessionData { - static D: OnceCell = OnceCell::new(); - D.get_or_init(|| PossessionData { - weapon_data: Some(WeaponData { - uses_skill: SkillType::Fists, - raw_min_to_learn: 0.0, - raw_max_to_learn: 2.0, - normal_attack: WeaponAttackData { - start_messages: vec![Box::new(|attacker, victim| { - format!( - "{} bares {} teeth and lunges at {}", - &attacker.display_for_sentence(1, true), - &attacker.pronouns.possessive, - &victim.display_for_sentence(1, false), - ) - })], - success_messages: vec![Box::new(|attacker, victim, part| { - format!( - "{}'s teeth connect and tear at the flesh of {}'s {}", - &attacker.display_for_sentence(1, true), - &victim.display_for_sentence(1, false), - &part.display(victim.sex.clone()) - ) - })], - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() +pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { + static D: OnceCell> = OnceCell::new(); + D.get_or_init(|| { + vec![ + ( + PossessionType::Fangs, + PossessionData { + weapon_data: Some(WeaponData { + uses_skill: SkillType::Fists, + raw_min_to_learn: 0.0, + raw_max_to_learn: 2.0, + normal_attack: WeaponAttackData { + start_messages: vec![Box::new(|attacker, victim| { + format!( + "{} bares {} teeth and lunges at {}", + &attacker.display_for_sentence(1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(1, false), + ) + })], + success_messages: vec![Box::new(|attacker, victim, part| { + format!( + "{}'s teeth connect and tear at the flesh of {}'s {}", + &attacker.display_for_sentence(1, true), + &victim.display_for_sentence(1, false), + &part.display(victim.sex.clone()) + ) + })], + ..Default::default() + }, + ..Default::default() + }), + ..Default::default() + }, + ), + ( + PossessionType::VenomousFangs, + PossessionData { + weapon_data: Some(WeaponData { + uses_skill: SkillType::Fists, + raw_min_to_learn: 0.0, + raw_max_to_learn: 2.0, + normal_attack: WeaponAttackData { + start_messages: vec![Box::new(|attacker, victim| { + format!( + "{} lines up {} venomous fang and strikes at {}", + &attacker.display_for_sentence(1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(1, false), + ) + })], + success_messages: vec![Box::new(|attacker, victim, part| { + format!( + "{}'s venomous fang pierces the flesh of {}'s {}", + &attacker.display_for_sentence(1, true), + &victim.display_for_sentence(1, false), + &part.display(victim.sex.clone()) + ) + })], + base_damage_type: DamageType::Pierce, + crit_effects: vec![(0.8, EffectType::SnakePoisoned)], + ..Default::default() + }, + ..Default::default() + }), + ..Default::default() + }, + ), + ( + PossessionType::Stinger, + PossessionData { + weapon_data: Some(WeaponData { + uses_skill: SkillType::Fists, + raw_min_to_learn: 0.0, + raw_max_to_learn: 2.0, + normal_attack: WeaponAttackData { + start_messages: vec![Box::new(|attacker, victim| { + format!( + "{} lines up {} stinger to attack {}", + &attacker.display_for_sentence(1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(1, false), + ) + })], + success_messages: vec![Box::new(|attacker, victim, part| { + format!( + "{}'s venomous stinger pierces the flesh of {}'s {}", + &attacker.display_for_sentence(1, true), + &victim.display_for_sentence(1, false), + &part.display(victim.sex.clone()) + ) + })], + base_damage_type: DamageType::Pierce, + crit_effects: vec![(0.8, EffectType::Stung)], + ..Default::default() + }, + ..Default::default() + }), + ..Default::default() + }, + ), + ] }) } diff --git a/blastmud_game/src/static_content/possession_type/meat.rs b/blastmud_game/src/static_content/possession_type/meat.rs index bc1e4ce4..950b060c 100644 --- a/blastmud_game/src/static_content/possession_type/meat.rs +++ b/blastmud_game/src/static_content/possession_type/meat.rs @@ -15,6 +15,16 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { ..Default::default() } ), + ( + PossessionType::SnakeSkin, + PossessionData { + display: "snake skin", + aliases: vec!("skin"), + details: "A scaly skin from some kind of snake", + weight: 20, + ..Default::default() + } + ), ( PossessionType::Steak, PossessionData { @@ -44,5 +54,15 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { ..Default::default() } ), + ( + PossessionType::VenomSac, + PossessionData { + display: "venom sac", + aliases: vec!("sac"), + details: "A little translucent bag with some kind of white fluid on the inside", + weight: 20, + ..Default::default() + } + ), )) } diff --git a/blastmud_game/src/static_content/room/northern_radfields.yaml b/blastmud_game/src/static_content/room/northern_radfields.yaml index 45c2f491..f164aa57 100644 --- a/blastmud_game/src/static_content/room/northern_radfields.yaml +++ b/blastmud_game/src/static_content/room/northern_radfields.yaml @@ -114,12 +114,13 @@ - direction: north exit_climb: height: 12 - difficulty: 12 + difficulty: 11 - direction: down - direction: south exit_climb: height: 10 difficulty: 8 + repel_npc: true environment: temperature: 1600 - zone: northern_radfields diff --git a/blastmud_game/src/static_content/species.rs b/blastmud_game/src/static_content/species.rs index ad3fa191..24c63df6 100644 --- a/blastmud_game/src/static_content/species.rs +++ b/blastmud_game/src/static_content/species.rs @@ -11,6 +11,8 @@ pub enum SpeciesType { Robot, Rat, Crocodile, + Scorpion, + Snake, } impl SpeciesType { @@ -35,6 +37,7 @@ pub enum BodyPart { Hands, Legs, Feet, + Tail, } impl BodyPart { @@ -50,24 +53,23 @@ impl BodyPart { Hands => "hands", Legs => "legs", Feet => "feet", + Tail => "tail", } } - pub fn copula(&self, sex: Option) -> &'static str { + pub fn copula(&self, _sex: Option) -> &'static str { use BodyPart::*; match self { Head => "is", Face => "is", - Chest => match sex { - Some(Sex::Female) => "are", - _ => "is", - }, + Chest => "is", Back => "is", Groin => "is", Arms => "are", Hands => "are", Legs => "are", Feet => "are", + Tail => "is", } } } @@ -160,6 +162,37 @@ pub fn species_info_map() -> &'static BTreeMap { can_open_door: false, }, ), + ( + SpeciesType::Scorpion, + SpeciesInfo { + body_parts: vec![ + BodyPart::Head, + BodyPart::Face, + BodyPart::Chest, + BodyPart::Back, + BodyPart::Groin, + BodyPart::Legs, + BodyPart::Feet, + ], + corpse_butchers_into: vec![PossessionType::VenomSac], + can_open_door: false, + }, + ), + ( + SpeciesType::Snake, + SpeciesInfo { + body_parts: vec![ + BodyPart::Head, + BodyPart::Face, + BodyPart::Chest, + BodyPart::Back, + BodyPart::Groin, + BodyPart::Tail, + ], + corpse_butchers_into: vec![PossessionType::Steak, PossessionType::SnakeSkin], + can_open_door: false, + }, + ), ] .into_iter() .collect()