Refactor to make death more than a boolean (what organs left)

This commit is contained in:
Condorra 2023-04-23 22:31:31 +10:00
parent 8102f2f7b0
commit 936fcc6dde
28 changed files with 193 additions and 52 deletions

View File

@ -20,7 +20,7 @@ impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("It doesn't really seem fair, but you realise you won't be able to attack anyone while you're dead!".to_string())?; user_error("It doesn't really seem fair, but you realise you won't be able to attack anyone while you're dead!".to_string())?;
} }
@ -56,7 +56,7 @@ impl UserVerb for Verb {
user_error(ansi!("<blue>Your wristpad vibrates and blocks you from doing that.<reset> You get a feeling that while the empire might be gone, the system to stop subjects with working wristpads from fighting each unless they have an consented is very much functional. [Try <bold>help allow<reset>]").to_string())? user_error(ansi!("<blue>Your wristpad vibrates and blocks you from doing that.<reset> You get a feeling that while the empire might be gone, the system to stop subjects with working wristpads from fighting each unless they have an consented is very much functional. [Try <bold>help allow<reset>]").to_string())?
} }
if attack_whom.is_dead { if attack_whom.death_data.is_some() {
user_error("There's no point attacking the dead!".to_string())? user_error("There's no point attacking the dead!".to_string())?
} }

View File

@ -18,7 +18,7 @@ impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Nobody seems to listen when you try to buy... possibly because you're dead.".to_owned())? user_error("Nobody seems to listen when you try to buy... possibly because you're dead.".to_owned())?
} }
let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));

View File

@ -119,7 +119,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> { -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -151,7 +151,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> { -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -219,7 +219,7 @@ impl UserVerb for Verb {
..ItemSearchParams::base(&player_item, &remaining) ..ItemSearchParams::base(&player_item, &remaining)
}).await?; }).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to drop it, but your ghostly hands slip through it uselessly".to_owned())?;
} }

View File

@ -34,7 +34,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> { -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -66,7 +66,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> { -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -133,7 +133,7 @@ impl UserVerb for Verb {
limit: get_limit.unwrap_or(100), limit: get_limit.unwrap_or(100),
..ItemSearchParams::base(&player_item, &remaining) ..ItemSearchParams::base(&player_item, &remaining)
}).await?; }).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
} }

View File

@ -21,7 +21,7 @@ impl UserVerb for Verb {
Some(v) => v Some(v) => v
}; };
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Apparently, you have to be alive to work as an installer.\ user_error("Apparently, you have to be alive to work as an installer.\
So discriminatory!".to_owned())?; So discriminatory!".to_owned())?;
} }

View File

@ -19,7 +19,7 @@ pub struct Verb;
impl UserVerb for Verb { impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
ctx.trans.queue_for_session(ctx.session, Some("The dead don't really have an inventory.\n")).await?; ctx.trans.queue_for_session(ctx.session, Some("The dead don't really have an inventory.\n")).await?;
} }
let inv = ctx.trans.find_items_by_location(&format!("{}/{}", let inv = ctx.trans.find_items_by_location(&format!("{}/{}",

View File

@ -14,7 +14,7 @@ impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Nobody seems to offer you any prices... possibly because you're dead.".to_owned())? user_error("Nobody seems to offer you any prices... possibly because you're dead.".to_owned())?
} }
let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));

View File

@ -233,7 +233,7 @@ async fn list_room_contents<'l>(ctx: &'l VerbContext<'_>, item: &'l Item) -> URe
LocationActionType::Sitting => buf.push_str("sitting "), LocationActionType::Sitting => buf.push_str("sitting "),
LocationActionType::Reclining => buf.push_str("reclining "), LocationActionType::Reclining => buf.push_str("reclining "),
LocationActionType::Normal | LocationActionType::Attacking(_) if is_creature => { LocationActionType::Normal | LocationActionType::Attacking(_) if is_creature => {
if head.is_dead { if head.death_data.is_some() {
buf.push_str("lying "); buf.push_str("lying ");
} else { } else {
buf.push_str("standing "); buf.push_str("standing ");
@ -343,7 +343,7 @@ impl UserVerb for Verb {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
let rem_trim = remaining.trim().to_lowercase(); let rem_trim = remaining.trim().to_lowercase();
let use_location = if player_item.is_dead { "room/repro_xv_respawn" } else { let use_location = if player_item.death_data.is_some() { "room/repro_xv_respawn" } else {
&player_item.location &player_item.location
}; };
let (heretype, herecode) = use_location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); let (heretype, herecode) = use_location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));

View File

@ -393,7 +393,7 @@ impl MapType for GmapType {
room: &room::Room) -> UResult<()> { room: &room::Room) -> UResult<()> {
ctx.trans.queue_for_session( ctx.trans.queue_for_session(
ctx.session, ctx.session,
Some(&render_map(room, 16, 9)) Some(&render_map(room, 32, 18))
).await?; ).await?;
Ok(()) Ok(())
} }

View File

@ -181,7 +181,7 @@ pub async fn attempt_move_immediate(
// for the orig_mover's queue, because might re-queue a move command. // for the orig_mover's queue, because might re-queue a move command.
mut player_ctx: &mut Option<&mut VerbContext<'_>> mut player_ctx: &mut Option<&mut VerbContext<'_>>
) -> UResult<()> { ) -> UResult<()> {
let use_location = if orig_mover.is_dead { let use_location = if orig_mover.death_data.is_some() {
if orig_mover.item_type != "player" { if orig_mover.item_type != "player" {
user_error("Dead players don't move".to_owned())?; user_error("Dead players don't move".to_owned())?;
} }
@ -268,7 +268,7 @@ pub async fn attempt_move_immediate(
} }
} }
if mover.is_dead { if mover.death_data.is_some() {
if !handle_resurrect(trans, &mut mover).await? { if !handle_resurrect(trans, &mut mover).await? {
user_error("You couldn't be resurrected.".to_string())?; user_error("You couldn't be resurrected.".to_string())?;
} }

View File

@ -75,7 +75,7 @@ impl TaskHandler for SwingShutHandler {
pub async fn attempt_open_immediate(trans: &DBTrans, ctx_opt: &mut Option<&mut VerbContext<'_>>, pub async fn attempt_open_immediate(trans: &DBTrans, ctx_opt: &mut Option<&mut VerbContext<'_>>,
who: &Item, direction: &Direction) -> UResult<()> { who: &Item, direction: &Direction) -> UResult<()> {
if who.is_dead { if who.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?; user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?;
} }
let (room_1, dir_in_room, room_2) = match is_door_in_direction(trans, &direction, &who).await? { let (room_1, dir_in_room, room_2) = match is_door_in_direction(trans, &direction, &who).await? {

View File

@ -25,7 +25,7 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?; user_error("You need to provide a message to send.".to_owned())?;
} }
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Shush, the dead can't talk!".to_string())?; user_error("Shush, the dead can't talk!".to_string())?;
} }
let to_whom = search_item_for_user(ctx, &ItemSearchParams { let to_whom = search_item_for_user(ctx, &ItemSearchParams {

View File

@ -55,7 +55,7 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?; user_error("You need to provide a message to send.".to_owned())?;
} }
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Shush, the dead can't talk!".to_string())?; user_error("Shush, the dead can't talk!".to_string())?;
} }
say_to_room(ctx.trans, &player_item, &player_item.location, say_to_room(ctx.trans, &player_item, &player_item.location,

View File

@ -21,7 +21,7 @@ impl UserVerb for Verb {
Some(v) => v Some(v) => v
}; };
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Apparently, you have to be alive to work as an uninstaller. \ user_error("Apparently, you have to be alive to work as an uninstaller. \
So discriminatory!".to_owned())?; So discriminatory!".to_owned())?;
} }

View File

@ -38,7 +38,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> { -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let (item_id, target_type_code) = match command { let (item_id, target_type_code) = match command {
@ -122,7 +122,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> { -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
@ -260,7 +260,7 @@ pub struct Verb;
impl UserVerb for Verb { impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to use it, but your ghostly hands slip through it uselessly".to_owned())?;
} }

View File

@ -17,7 +17,7 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?; user_error("You need to provide a message to send.".to_owned())?;
} }
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("Shush, the dead can't talk!".to_string())?; user_error("Shush, the dead can't talk!".to_string())?;
} }
let to_whom = search_item_for_user(ctx, &ItemSearchParams { let to_whom = search_item_for_user(ctx, &ItemSearchParams {

View File

@ -33,7 +33,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> { -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -80,7 +80,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand) async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> { -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
let item_id = match command { let item_id = match command {
@ -118,7 +118,7 @@ impl UserVerb for Verb {
limit: 1, limit: 1,
..ItemSearchParams::base(&player_item, &remaining) ..ItemSearchParams::base(&player_item, &remaining)
}).await?; }).await?;
if player_item.is_dead { if player_item.death_data.is_some() {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?; user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
} }
if weapon.action_type == LocationActionType::Wielded { if weapon.action_type == LocationActionType::Wielded {

View File

@ -342,6 +342,20 @@ impl Default for DoorState {
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
#[serde(default)]
pub struct DeathData {
pub parts_remaining: Vec<PossessionType>
}
impl Default for DeathData {
fn default() -> Self {
Self {
parts_remaining: vec!()
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
#[serde(default)] #[serde(default)]
pub struct Item { pub struct Item {
@ -357,7 +371,7 @@ pub struct Item {
pub action_type: LocationActionType, pub action_type: LocationActionType,
pub presence_target: Option<String>, // e.g. what are they sitting on. pub presence_target: Option<String>, // e.g. what are they sitting on.
pub is_static: bool, pub is_static: bool,
pub is_dead: bool, pub death_data: Option<DeathData>,
pub species: SpeciesType, pub species: SpeciesType,
pub health: u64, pub health: u64,
pub total_xp: u64, pub total_xp: u64,
@ -373,13 +387,13 @@ pub struct Item {
pub special_data: Option<ItemSpecialData>, pub special_data: Option<ItemSpecialData>,
pub dynamic_entrance: Option<DynamicEntrance>, pub dynamic_entrance: Option<DynamicEntrance>,
pub owner: Option<String>, pub owner: Option<String>,
pub door_states: Option<BTreeMap<Direction, DoorState>> pub door_states: Option<BTreeMap<Direction, DoorState>>,
} }
impl Item { impl Item {
pub fn display_for_sentence(&self, explicit_ok: bool, pluralise: usize, caps: bool) -> String { pub fn display_for_sentence(&self, explicit_ok: bool, pluralise: usize, caps: bool) -> String {
let mut buf = String::new(); let mut buf = String::new();
if self.is_dead { if self.death_data.is_some() {
if pluralise > 1 { if pluralise > 1 {
buf.push_str("the bodies of "); buf.push_str("the bodies of ");
} else { } else {
@ -441,7 +455,7 @@ impl Default for Item {
action_type: LocationActionType::Normal, action_type: LocationActionType::Normal,
presence_target: None, presence_target: None,
is_static: false, is_static: false,
is_dead: false, death_data: None,
species: SpeciesType::Human, species: SpeciesType::Human,
health: 24, health: 24,
total_xp: 0, total_xp: 0,

View File

@ -5,12 +5,13 @@ use crate::{
skills::skill_check_only, skills::skill_check_only,
}, },
models::{ models::{
item::{Item, LocationActionType, Subattack, SkillType}, item::{Item, LocationActionType, Subattack, SkillType, DeathData},
task::{Task, TaskMeta, TaskDetails} task::{Task, TaskMeta, TaskDetails},
}, },
static_content::{ static_content::{
possession_type::{WeaponData, possession_data, fist}, possession_type::{WeaponData, possession_data, fist},
npc::npc_by_code, npc::npc_by_code,
species::species_info_map,
}, },
message_handler::user_commands::{user_error, UResult, drop::consider_expire_job_for_item}, message_handler::user_commands::{user_error, UResult, drop::consider_expire_job_for_item},
regular_tasks::{TaskRunContext, TaskHandler}, regular_tasks::{TaskRunContext, TaskHandler},
@ -48,7 +49,7 @@ impl TaskHandler for AttackTaskHandler {
Some(item) => (*item).clone() Some(item) => (*item).clone()
}; };
if attacker_item.is_dead || victim_item.is_dead { if attacker_item.death_data.is_some() || victim_item.death_data.is_some() {
return Ok(None) return Ok(None)
} }
@ -210,7 +211,11 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
); );
broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await?; broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await?;
whom.is_dead = true; whom.death_data = Some(DeathData {
parts_remaining: species_info_map().get(&whom.species)
.map(|sp| sp.corpse_butchers_into.clone()).unwrap_or_else(|| vec!()),
..Default::default()
});
if let Some(ac) = &whom.active_combat { if let Some(ac) = &whom.active_combat {
let at_str = ac.attacking.clone(); let at_str = ac.attacking.clone();
for attacker in ac.attacked_by.clone().iter() { for attacker in ac.attacked_by.clone().iter() {
@ -250,7 +255,7 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<bool> { pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<bool> {
corpsify_item(trans, &player).await?; corpsify_item(trans, &player).await?;
player.is_dead = false; player.death_data = None;
let lost_xp = (player.total_xp / 200).max(10).min(player.total_xp); let lost_xp = (player.total_xp / 200).max(10).min(player.total_xp);
let (session, _) = match trans.find_session_for_player(&player.item_code).await? { let (session, _) = match trans.find_session_for_player(&player.item_code).await? {
None => return Ok(false), None => return Ok(false),
@ -476,13 +481,13 @@ impl TaskHandler for NPCRecloneTaskHandler {
Some(r) => r Some(r) => r
}; };
if !npc_item.is_dead { if npc_item.death_data.is_none() {
return Ok(None); return Ok(None);
} }
corpsify_item(ctx.trans, &npc_item).await?; corpsify_item(ctx.trans, &npc_item).await?;
npc_item.is_dead = false; npc_item.death_data = None;
npc_item.health = max_health(&npc_item); npc_item.health = max_health(&npc_item);
npc_item.location = npc.spawn_location.to_owned(); npc_item.location = npc.spawn_location.to_owned();
ctx.trans.save_item_model(&npc_item).await?; ctx.trans.save_item_model(&npc_item).await?;

View File

@ -8,7 +8,7 @@ use mockall_double::double;
pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>, pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>,
message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> { message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> {
for item in trans.find_items_by_location(location).await? { for item in trans.find_items_by_location(location).await? {
if item.item_type != "player" || item.is_dead { if item.item_type != "player" || item.death_data.is_some() {
continue; continue;
} }
if let Some((session, session_dat)) = trans.find_session_for_player(&item.item_code).await? { if let Some((session, session_dat)) = trans.find_session_for_player(&item.item_code).await? {

View File

@ -59,7 +59,7 @@ impl TaskHandler for DelayedHealthTaskHandler {
} }
Some(it) => it Some(it) => it
}; };
if item.is_dead { if item.death_data.is_some() {
return Ok(None); return Ok(None);
} }
match item_effect_series.1.pop_front() { match item_effect_series.1.pop_front() {

View File

@ -270,7 +270,7 @@ impl TaskHandler for NPCSayTaskHandler {
Some(r) => r Some(r) => r
}; };
if npc_item.is_dead { if npc_item.death_data.is_none() {
return Ok(None); return Ok(None);
} }
@ -323,7 +323,7 @@ impl TaskHandler for NPCWanderTaskHandler {
}, },
Some(r) => r Some(r) => r
}; };
if item.is_dead { if item.death_data.is_some() {
return Ok(None) return Ok(None)
} }
let (ltype, lcode) = match item.location.split_once("/") { let (ltype, lcode) = match item.location.split_once("/") {
@ -379,14 +379,14 @@ impl TaskHandler for NPCAggroTaskHandler {
}, },
Some(r) => r Some(r) => r
}; };
if item.is_dead || item.active_combat.as_ref().map(|ac| ac.attacking.is_some()).unwrap_or(false) { if item.death_data.is_some() || item.active_combat.as_ref().map(|ac| ac.attacking.is_some()).unwrap_or(false) {
return Ok(None); return Ok(None);
} }
let items_loc = ctx.trans.find_items_by_location(&item.location).await?; let items_loc = ctx.trans.find_items_by_location(&item.location).await?;
let vic_opt = items_loc let vic_opt = items_loc
.iter() .iter()
.filter(|it| (it.item_type == "player" || it.item_type == "npc") && .filter(|it| (it.item_type == "player" || it.item_type == "npc") &&
!it.is_dead && (it.item_type != item.item_type || it.item_code != item.item_code)) it.death_data.is_none() && (it.item_type != item.item_type || it.item_code != item.item_code))
.choose(&mut thread_rng()); .choose(&mut thread_rng());
if let Some(victim) = vic_opt { if let Some(victim) = vic_opt {
match start_attack(ctx.trans, &item, victim).await { match start_attack(ctx.trans, &item, victim).await {
@ -418,13 +418,13 @@ impl TaskHandler for NPCRecloneTaskHandler {
Some(r) => r Some(r) => r
}; };
if !npc_item.is_dead { if npc_item.death_data.is_none() {
return Ok(None); return Ok(None);
} }
corpsify_item(ctx.trans, &npc_item).await?; corpsify_item(ctx.trans, &npc_item).await?;
npc_item.is_dead = false; npc_item.death_data = None;
npc_item.health = npc.max_health; npc_item.health = npc.max_health;
npc_item.location = npc.spawn_location.to_owned(); npc_item.location = npc.spawn_location.to_owned();
ctx.trans.save_item_model(&npc_item).await?; ctx.trans.save_item_model(&npc_item).await?;

View File

@ -13,9 +13,11 @@ use async_trait::async_trait;
mod fangs; mod fangs;
mod antenna_whip; mod antenna_whip;
mod blade;
mod trauma_kit; mod trauma_kit;
mod corp_licence; mod corp_licence;
mod lock; mod lock;
mod meat;
pub type AttackMessageChoice = Vec<Box<dyn Fn(&Item, &Item, bool) -> String + 'static + Sync + Send>>; pub type AttackMessageChoice = Vec<Box<dyn Fn(&Item, &Item, bool) -> String + 'static + Sync + Send>>;
pub type AttackMessageChoicePart = Vec<Box<dyn Fn(&Item, &Item, &BodyPart, bool) -> String + 'static + Sync + Send>>; pub type AttackMessageChoicePart = Vec<Box<dyn Fn(&Item, &Item, &BodyPart, bool) -> String + 'static + Sync + Send>>;
@ -145,6 +147,7 @@ pub struct PossessionData {
pub lockcheck_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, pub lockcheck_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>, pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
pub can_butcher: bool,
} }
impl Default for PossessionData { impl Default for PossessionData {
@ -165,6 +168,7 @@ impl Default for PossessionData {
lockcheck_handler: None, lockcheck_handler: None,
sign_handler: None, sign_handler: None,
write_handler: None, write_handler: None,
can_butcher: false,
} }
} }
} }
@ -203,6 +207,10 @@ pub enum PossessionType {
NewCorpLicence, NewCorpLicence,
CertificateOfIncorporation, CertificateOfIncorporation,
Scanlock, Scanlock,
ButcherKnife,
Steak,
AnimalSkin,
SeveredHead,
} }
impl Into<Item> for PossessionType { impl Into<Item> for PossessionType {
@ -266,11 +274,15 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
vec!( vec!(
(Fangs, fangs::data()), (Fangs, fangs::data()),
(AntennaWhip, antenna_whip::data()), (AntennaWhip, antenna_whip::data()),
(ButcherKnife, blade::butcher_data()),
(MediumTraumaKit, trauma_kit::medium_data()), (MediumTraumaKit, trauma_kit::medium_data()),
(EmptyMedicalBox, trauma_kit::empty_data()), (EmptyMedicalBox, trauma_kit::empty_data()),
(NewCorpLicence, corp_licence::data()), (NewCorpLicence, corp_licence::data()),
(CertificateOfIncorporation, corp_licence::cert_data()), (CertificateOfIncorporation, corp_licence::cert_data()),
(Scanlock, lock::scan()), (Scanlock, lock::scan()),
(Steak, meat::steak_data()),
(AnimalSkin, meat::skin_data()),
(SeveredHead, meat::severed_head_data()),
).into_iter().collect() ).into_iter().collect()
}) })
} }

View File

@ -0,0 +1,38 @@
use super::{PossessionData, WeaponData};
use crate::models::item::SkillType;
pub fn butcher_data() -> PossessionData {
PossessionData {
display: "butcher knife",
details: "A 30 cm long stainless steel blade, sharp on one edge with a pointy tip. It looks perfect for butchering things, and in a pinch you could probably fight with it too.",
aliases: vec!("butcher", "knife"),
weight: 250,
weapon_data: Some(WeaponData {
uses_skill: SkillType::Blades,
raw_min_to_learn: 0.0,
raw_max_to_learn: 2.0,
normal_attack_start_messages: vec!(
Box::new(|attacker, victim, exp|
format!("{} raises {} butcher knife menancingly, preparing to attack {}",
&attacker.display_for_sentence(exp, 1, true),
&attacker.pronouns.possessive,
&victim.display_for_sentence(exp, 1, false),
)
)
),
normal_attack_success_messages: vec!(
Box::new(|attacker, victim, part, exp|
format!("{}'s butcher knife cuts into {}'s {}",
&attacker.display_for_sentence(exp, 1, true),
&victim.display_for_sentence(exp, 1, false),
&part.display(victim.sex.clone())
)
)
),
normal_attack_mean_damage: 2.0,
normal_attack_stdev_damage: 2.0,
..Default::default()
}),
..Default::default()
}
}

View File

@ -0,0 +1,28 @@
use super::PossessionData;
pub fn skin_data() -> PossessionData {
PossessionData {
display: "animal skin",
details: "The skin of an animal of some kind. It looks like you could make something out of this",
weight: 100,
..Default::default()
}
}
pub fn steak_data() -> PossessionData {
PossessionData {
display: "steak",
details: "A hunk of raw red meat, dripping with blood",
weight: 100,
..Default::default()
}
}
pub fn severed_head_data() -> PossessionData {
PossessionData {
display: "steak",
details: "A head that has been chopped clean from the body",
weight: 250,
..Default::default()
}
}

View File

@ -212,7 +212,7 @@ pub fn medium_data() -> PossessionData {
task_ref: "bandage", task_ref: "bandage",
errorf: Box::new( errorf: Box::new(
|_item, target| |_item, target|
if target.is_dead { if target.death_data.is_some() {
Some(format!("It is too late, {}'s dead", target.pronouns.subject)) Some(format!("It is too late, {}'s dead", target.pronouns.subject))
} else if target.item_type != "player" && target.item_type != "npc" { } else if target.item_type != "player" && target.item_type != "npc" {
Some("It only works on animals.".to_owned()) Some("It only works on animals.".to_owned())

View File

@ -2527,6 +2527,11 @@ pub fn room_list() -> Vec<Room> {
target: ExitTarget::UseGPS, target: ExitTarget::UseGPS,
exit_type: ExitType::Free exit_type: ExitType::Free
}, },
Exit {
direction: Direction::SOUTH,
target: ExitTarget::UseGPS,
exit_type: ExitType::Free
},
), ),
should_caption: false, should_caption: false,
..Default::default() ..Default::default()
@ -2557,6 +2562,32 @@ pub fn room_list() -> Vec<Room> {
should_caption: true, should_caption: true,
..Default::default() ..Default::default()
}, },
Room {
zone: "melbs",
secondary_zones: vec!(),
code: "melbs_grande_outdoors",
name: "Grande Outdoors",
short: ansi!("<yellow>GO<reset>"),
description: "A shop that looks like it once helped people camp for fun, its clientele now days are probably more focused on just surviving! A weary looking woman with calloused, grease covered hands paces the shop floor, prepared to spring on you and hype up whatever merchandise you look at as the best thing ever",
description_less_explicit: None,
grid_coords: GridCoords { x: 3, y: 4, z: 0 },
exits: vec!(
Exit {
direction: Direction::NORTH,
target: ExitTarget::UseGPS,
exit_type: ExitType::Free
},
),
stock_list: vec!(
RoomStock {
possession_type: PossessionType::ButcherKnife,
list_price: 120,
..Default::default()
}
),
should_caption: true,
..Default::default()
},
Room { Room {
zone: "melbs", zone: "melbs",
secondary_zones: vec!(), secondary_zones: vec!(),

View File

@ -2,7 +2,8 @@ use once_cell::sync::OnceCell;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::{ use crate::{
models::item::Sex models::item::Sex,
static_content::possession_type::PossessionType,
}; };
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
@ -57,7 +58,8 @@ impl BodyPart {
} }
pub struct SpeciesInfo { pub struct SpeciesInfo {
body_parts: Vec<BodyPart>, pub body_parts: Vec<BodyPart>,
pub corpse_butchers_into: Vec<PossessionType>,
} }
@ -76,7 +78,13 @@ pub fn species_info_map() -> &'static BTreeMap<SpeciesType, SpeciesInfo> {
BodyPart::Arms, BodyPart::Arms,
BodyPart::Legs, BodyPart::Legs,
BodyPart::Feet BodyPart::Feet
) ),
corpse_butchers_into: vec!(
PossessionType::Steak,
PossessionType::Steak,
PossessionType::AnimalSkin,
PossessionType::SeveredHead,
),
} }
), ),
(SpeciesType::Dog, (SpeciesType::Dog,
@ -89,7 +97,12 @@ pub fn species_info_map() -> &'static BTreeMap<SpeciesType, SpeciesInfo> {
BodyPart::Groin, BodyPart::Groin,
BodyPart::Legs, BodyPart::Legs,
BodyPart::Feet BodyPart::Feet
) ),
corpse_butchers_into: vec!(
PossessionType::Steak,
PossessionType::AnimalSkin,
PossessionType::SeveredHead,
),
} }
), ),
).into_iter().collect() ).into_iter().collect()