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<()> {
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())?;
}
@ -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())?
}
if attack_whom.is_dead {
if attack_whom.death_data.is_some() {
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<()> {
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())?
}
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)
-> UResult<time::Duration> {
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())?;
}
let item_id = match command {
@ -151,7 +151,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
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())?;
}
let item_id = match command {
@ -219,7 +219,7 @@ impl UserVerb for Verb {
..ItemSearchParams::base(&player_item, &remaining)
}).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())?;
}

View File

@ -34,7 +34,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> {
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())?;
}
let item_id = match command {
@ -66,7 +66,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
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())?;
}
let item_id = match command {
@ -133,7 +133,7 @@ impl UserVerb for Verb {
limit: get_limit.unwrap_or(100),
..ItemSearchParams::base(&player_item, &remaining)
}).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())?;
}

View File

@ -21,7 +21,7 @@ impl UserVerb for Verb {
Some(v) => v
};
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.\
So discriminatory!".to_owned())?;
}

View File

@ -19,7 +19,7 @@ pub struct Verb;
impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
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?;
}
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<()> {
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())?
}
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::Reclining => buf.push_str("reclining "),
LocationActionType::Normal | LocationActionType::Attacking(_) if is_creature => {
if head.is_dead {
if head.death_data.is_some() {
buf.push_str("lying ");
} else {
buf.push_str("standing ");
@ -343,7 +343,7 @@ impl UserVerb for Verb {
let player_item = get_player_item_or_fail(ctx).await?;
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
};
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<()> {
ctx.trans.queue_for_session(
ctx.session,
Some(&render_map(room, 16, 9))
Some(&render_map(room, 32, 18))
).await?;
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.
mut player_ctx: &mut Option<&mut VerbContext<'_>>
) -> 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" {
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? {
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<'_>>,
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())?;
}
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())?;
}
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())?;
}
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())?;
}
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())?;
}
say_to_room(ctx.trans, &player_item, &player_item.location,

View File

@ -21,7 +21,7 @@ impl UserVerb for Verb {
Some(v) => v
};
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. \
So discriminatory!".to_owned())?;
}

View File

@ -38,7 +38,7 @@ impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> {
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())?;
}
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)
-> UResult<()> {
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())?;
}
@ -260,7 +260,7 @@ pub struct Verb;
impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
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())?;
}

View File

@ -17,7 +17,7 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?;
}
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())?;
}
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)
-> UResult<time::Duration> {
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())?;
}
let item_id = match command {
@ -80,7 +80,7 @@ impl QueueCommandHandler for QueueHandler {
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
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())?;
}
let item_id = match command {
@ -118,7 +118,7 @@ impl UserVerb for Verb {
limit: 1,
..ItemSearchParams::base(&player_item, &remaining)
}).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())?;
}
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)]
#[serde(default)]
pub struct Item {
@ -357,7 +371,7 @@ pub struct Item {
pub action_type: LocationActionType,
pub presence_target: Option<String>, // e.g. what are they sitting on.
pub is_static: bool,
pub is_dead: bool,
pub death_data: Option<DeathData>,
pub species: SpeciesType,
pub health: u64,
pub total_xp: u64,
@ -373,13 +387,13 @@ pub struct Item {
pub special_data: Option<ItemSpecialData>,
pub dynamic_entrance: Option<DynamicEntrance>,
pub owner: Option<String>,
pub door_states: Option<BTreeMap<Direction, DoorState>>
pub door_states: Option<BTreeMap<Direction, DoorState>>,
}
impl Item {
pub fn display_for_sentence(&self, explicit_ok: bool, pluralise: usize, caps: bool) -> String {
let mut buf = String::new();
if self.is_dead {
if self.death_data.is_some() {
if pluralise > 1 {
buf.push_str("the bodies of ");
} else {
@ -441,7 +455,7 @@ impl Default for Item {
action_type: LocationActionType::Normal,
presence_target: None,
is_static: false,
is_dead: false,
death_data: None,
species: SpeciesType::Human,
health: 24,
total_xp: 0,

View File

@ -5,12 +5,13 @@ use crate::{
skills::skill_check_only,
},
models::{
item::{Item, LocationActionType, Subattack, SkillType},
task::{Task, TaskMeta, TaskDetails}
item::{Item, LocationActionType, Subattack, SkillType, DeathData},
task::{Task, TaskMeta, TaskDetails},
},
static_content::{
possession_type::{WeaponData, possession_data, fist},
npc::npc_by_code,
species::species_info_map,
},
message_handler::user_commands::{user_error, UResult, drop::consider_expire_job_for_item},
regular_tasks::{TaskRunContext, TaskHandler},
@ -48,7 +49,7 @@ impl TaskHandler for AttackTaskHandler {
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)
}
@ -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?;
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 {
let at_str = ac.attacking.clone();
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> {
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 (session, _) = match trans.find_session_for_player(&player.item_code).await? {
None => return Ok(false),
@ -476,13 +481,13 @@ impl TaskHandler for NPCRecloneTaskHandler {
Some(r) => r
};
if !npc_item.is_dead {
if npc_item.death_data.is_none() {
return Ok(None);
}
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.location = npc.spawn_location.to_owned();
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>,
message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> {
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;
}
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
};
if item.is_dead {
if item.death_data.is_some() {
return Ok(None);
}
match item_effect_series.1.pop_front() {

View File

@ -270,7 +270,7 @@ impl TaskHandler for NPCSayTaskHandler {
Some(r) => r
};
if npc_item.is_dead {
if npc_item.death_data.is_none() {
return Ok(None);
}
@ -323,7 +323,7 @@ impl TaskHandler for NPCWanderTaskHandler {
},
Some(r) => r
};
if item.is_dead {
if item.death_data.is_some() {
return Ok(None)
}
let (ltype, lcode) = match item.location.split_once("/") {
@ -379,14 +379,14 @@ impl TaskHandler for NPCAggroTaskHandler {
},
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);
}
let items_loc = ctx.trans.find_items_by_location(&item.location).await?;
let vic_opt = items_loc
.iter()
.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());
if let Some(victim) = vic_opt {
match start_attack(ctx.trans, &item, victim).await {
@ -418,13 +418,13 @@ impl TaskHandler for NPCRecloneTaskHandler {
Some(r) => r
};
if !npc_item.is_dead {
if npc_item.death_data.is_none() {
return Ok(None);
}
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.location = npc.spawn_location.to_owned();
ctx.trans.save_item_model(&npc_item).await?;

View File

@ -13,9 +13,11 @@ use async_trait::async_trait;
mod fangs;
mod antenna_whip;
mod blade;
mod trauma_kit;
mod corp_licence;
mod lock;
mod meat;
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>>;
@ -145,6 +147,7 @@ pub struct PossessionData {
pub lockcheck_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 can_butcher: bool,
}
impl Default for PossessionData {
@ -165,6 +168,7 @@ impl Default for PossessionData {
lockcheck_handler: None,
sign_handler: None,
write_handler: None,
can_butcher: false,
}
}
}
@ -203,6 +207,10 @@ pub enum PossessionType {
NewCorpLicence,
CertificateOfIncorporation,
Scanlock,
ButcherKnife,
Steak,
AnimalSkin,
SeveredHead,
}
impl Into<Item> for PossessionType {
@ -266,11 +274,15 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
vec!(
(Fangs, fangs::data()),
(AntennaWhip, antenna_whip::data()),
(ButcherKnife, blade::butcher_data()),
(MediumTraumaKit, trauma_kit::medium_data()),
(EmptyMedicalBox, trauma_kit::empty_data()),
(NewCorpLicence, corp_licence::data()),
(CertificateOfIncorporation, corp_licence::cert_data()),
(Scanlock, lock::scan()),
(Steak, meat::steak_data()),
(AnimalSkin, meat::skin_data()),
(SeveredHead, meat::severed_head_data()),
).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",
errorf: Box::new(
|_item, target|
if target.is_dead {
if target.death_data.is_some() {
Some(format!("It is too late, {}'s dead", target.pronouns.subject))
} else if target.item_type != "player" && target.item_type != "npc" {
Some("It only works on animals.".to_owned())

View File

@ -2527,6 +2527,11 @@ pub fn room_list() -> Vec<Room> {
target: ExitTarget::UseGPS,
exit_type: ExitType::Free
},
Exit {
direction: Direction::SOUTH,
target: ExitTarget::UseGPS,
exit_type: ExitType::Free
},
),
should_caption: false,
..Default::default()
@ -2557,6 +2562,32 @@ pub fn room_list() -> Vec<Room> {
should_caption: true,
..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 {
zone: "melbs",
secondary_zones: vec!(),

View File

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