Create venomous NPCs in Northrad (snakes & scorpions)

This commit is contained in:
Condorra 2024-05-15 22:58:30 +10:00
parent 90cb258455
commit e238abd832
10 changed files with 649 additions and 40 deletions

View File

@ -10,6 +10,8 @@ pub enum EffectType {
Bleed,
Stunned,
CurrentRoom,
SnakePoisoned,
Stung,
}
pub struct EffectSet {

View File

@ -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<EffectType, EffectSet> {
)
},
]
}
},
EffectSet {
effect_type: EffectType::SnakePoisoned,
effects: vec![
Effect::BroadcastMessage {
delay_secs: 0,
messagef: Box::new(|_player, _item, target|
format!(ansi!("<red>You notice that {} has a fresh bite wound with some kind of yellow liquid on the bite site!<reset>\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))

View File

@ -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<NPC> {
..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());

View File

@ -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<NPC> {
from_yaml_str::<Vec<NorthradNPC>>(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()
}

View File

@ -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

View File

@ -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<PossessionType, &'static PossessionData> {
static POSSESSION_DATA: OnceCell<BTreeMap<PossessionType, &'static PossessionData>> =
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)))

View File

@ -1,10 +1,17 @@
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<PossessionData> = OnceCell::new();
D.get_or_init(|| PossessionData {
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
static D: OnceCell<Vec<(PossessionType, PossessionData)>> = OnceCell::new();
D.get_or_init(|| {
vec![
(
PossessionType::Fangs,
PossessionData {
weapon_data: Some(WeaponData {
uses_skill: SkillType::Fists,
raw_min_to_learn: 0.0,
@ -31,5 +38,74 @@ pub fn data() -> &'static PossessionData {
..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()
},
),
]
})
}

View File

@ -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()
}
),
))
}

View File

@ -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

View File

@ -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<Sex>) -> &'static str {
pub fn copula(&self, _sex: Option<Sex>) -> &'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<SpeciesType, SpeciesInfo> {
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()