Allow selling in stores, with Josephine special behaviour
Also added a staff invincible mode to help clean out NPCs with wrong inventory.
This commit is contained in:
parent
a2652e471d
commit
19cef2d9c4
@ -44,6 +44,7 @@ mod ignore;
|
||||
pub mod improvise;
|
||||
mod install;
|
||||
mod inventory;
|
||||
mod invincible;
|
||||
mod list;
|
||||
pub mod load;
|
||||
mod login;
|
||||
@ -69,6 +70,7 @@ pub mod say;
|
||||
mod scan;
|
||||
pub mod scavenge;
|
||||
mod score;
|
||||
mod sell;
|
||||
mod share;
|
||||
mod sign;
|
||||
pub mod sit;
|
||||
@ -248,6 +250,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"sc" => score::VERB,
|
||||
"score" => score::VERB,
|
||||
|
||||
"sell" => sell::VERB,
|
||||
|
||||
"share" => share::VERB,
|
||||
"serious" => share::VERB,
|
||||
"amicable" => share::VERB,
|
||||
@ -290,6 +294,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
};
|
||||
|
||||
static STAFF_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"staff_invincible" => invincible::VERB,
|
||||
"staff_reset_spawns" => reset_spawns::VERB,
|
||||
"staff_show" => staff_show::VERB,
|
||||
};
|
||||
|
@ -55,6 +55,9 @@ impl UserVerb for Verb {
|
||||
}
|
||||
|
||||
for stock in &room.stock_list {
|
||||
if !stock.can_buy {
|
||||
continue;
|
||||
}
|
||||
if let Some(possession_type) = possession_data().get(&stock.possession_type) {
|
||||
if possession_type
|
||||
.display
|
||||
|
@ -95,6 +95,7 @@ async fn reset_stats(ctx: &mut VerbContext<'_>) -> UResult<()> {
|
||||
user_dat.raw_skills = BTreeMap::new();
|
||||
user_dat.wristpad_hacks = vec![];
|
||||
user_dat.scan_codes = vec![];
|
||||
user_dat.quest_progress = None;
|
||||
calculate_total_stats_skills_for_user(&mut player_item, &user_dat);
|
||||
ctx.trans.save_user_model(&user_dat).await?;
|
||||
ctx.trans.save_item_model(&player_item).await?;
|
||||
|
@ -0,0 +1,43 @@
|
||||
use super::{get_player_item_or_fail, user_error, UResult, UserVerb, UserVerbRef, VerbContext};
|
||||
use crate::models::item::ItemFlag;
|
||||
use ansi::ansi;
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(
|
||||
self: &Self,
|
||||
ctx: &mut VerbContext,
|
||||
_verb: &str,
|
||||
remaining: &str,
|
||||
) -> UResult<()> {
|
||||
let requester = get_player_item_or_fail(ctx).await?;
|
||||
let remaining = remaining.trim();
|
||||
let state = if remaining == "on" {
|
||||
true
|
||||
} else if remaining == "off" {
|
||||
false
|
||||
} else {
|
||||
return user_error(
|
||||
ansi!("use <bold>staff_invincible on<reset> or <bold>staff_invincible off<reset>")
|
||||
.to_owned(),
|
||||
);
|
||||
};
|
||||
|
||||
let mut requester = (*requester).clone();
|
||||
requester.flags = requester
|
||||
.flags
|
||||
.into_iter()
|
||||
.filter(|f| f != &ItemFlag::Invincible)
|
||||
.collect();
|
||||
if state {
|
||||
requester.flags.push(ItemFlag::Invincible);
|
||||
}
|
||||
ctx.trans.save_item_model(&requester).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -46,6 +46,9 @@ impl UserVerb for Verb {
|
||||
));
|
||||
|
||||
for stock in &room.stock_list {
|
||||
if !stock.can_buy {
|
||||
continue;
|
||||
}
|
||||
if let Some(possession_type) = possession_data().get(&stock.possession_type) {
|
||||
let display = &possession_type.display;
|
||||
|
||||
|
@ -15,7 +15,9 @@ use crate::{
|
||||
models::{
|
||||
consent::ConsentType,
|
||||
effect::EffectType,
|
||||
item::{ActiveClimb, DoorState, Item, ItemSpecialData, LocationActionType, SkillType},
|
||||
item::{
|
||||
ActiveClimb, DoorState, Item, ItemFlag, ItemSpecialData, LocationActionType, SkillType,
|
||||
},
|
||||
},
|
||||
regular_tasks::queued_command::{
|
||||
queue_command, MovementSource, QueueCommand, QueueCommandHandler, QueuedCommandContext,
|
||||
@ -627,6 +629,7 @@ async fn attempt_move_immediate(
|
||||
)
|
||||
.await?
|
||||
>= 0.0
|
||||
|| ctx.item.flags.contains(&ItemFlag::Invincible)
|
||||
{
|
||||
if let Some((sess, _)) = session.as_ref() {
|
||||
ctx.trans
|
||||
|
135
blastmud_game/src/message_handler/user_commands/sell.rs
Normal file
135
blastmud_game/src/message_handler/user_commands/sell.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use super::{
|
||||
get_player_item_or_fail, search_item_for_user, user_error, UResult, UserVerb, UserVerbRef,
|
||||
VerbContext,
|
||||
};
|
||||
use crate::{
|
||||
db::ItemSearchParams,
|
||||
language::pluralise,
|
||||
models::item::Item,
|
||||
services::combat::max_health,
|
||||
static_content::{
|
||||
possession_type::possession_data,
|
||||
room::{self, Room},
|
||||
},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
async fn check_sell_trigger(
|
||||
ctx: &mut VerbContext<'_>,
|
||||
player_item: &Item,
|
||||
room: &Room,
|
||||
sell_item: &Item,
|
||||
) -> UResult<()> {
|
||||
let trigger = match room.sell_trigger.as_ref() {
|
||||
None => return Ok(()),
|
||||
Some(tr) => tr,
|
||||
};
|
||||
trigger.handle_sell(ctx, room, player_item, sell_item).await
|
||||
}
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
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.death_data.is_some() {
|
||||
user_error(
|
||||
"Nobody seems to listen when you try to sell... possibly because you're dead."
|
||||
.to_owned(),
|
||||
)?
|
||||
}
|
||||
let (heretype, herecode) = player_item
|
||||
.location
|
||||
.split_once("/")
|
||||
.unwrap_or(("room", "repro_xv_chargen"));
|
||||
if heretype != "room" {
|
||||
user_error("Can't sell anything because you're not in a shop.".to_owned())?;
|
||||
}
|
||||
let room = match room::room_map_by_code().get(herecode) {
|
||||
None => user_error("Can't find that shop.".to_owned())?,
|
||||
Some(r) => r,
|
||||
};
|
||||
if room.stock_list.is_empty() {
|
||||
user_error("Can't sell anything because you're not in a shop.".to_owned())?
|
||||
}
|
||||
|
||||
let sell_item = search_item_for_user(
|
||||
ctx,
|
||||
&ItemSearchParams {
|
||||
include_contents: true,
|
||||
item_type_only: Some("possession"),
|
||||
..ItemSearchParams::base(&player_item, remaining)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(charge_data) = sell_item
|
||||
.possession_type
|
||||
.as_ref()
|
||||
.and_then(|pt| possession_data().get(pt))
|
||||
.and_then(|pd| pd.charge_data.as_ref())
|
||||
{
|
||||
if charge_data.max_charges > sell_item.charges {
|
||||
user_error(format!(
|
||||
"No one will want to buy a used {}!",
|
||||
&sell_item.display
|
||||
))?
|
||||
}
|
||||
}
|
||||
if sell_item.health < max_health(&sell_item) {
|
||||
user_error(format!(
|
||||
"No one will want to buy a damaged {}!",
|
||||
&sell_item.display
|
||||
))?
|
||||
}
|
||||
|
||||
for stock in &room.stock_list {
|
||||
if Some(stock.possession_type.clone()) != sell_item.possession_type {
|
||||
continue;
|
||||
}
|
||||
let stats = ctx.trans.get_location_stats(&sell_item.refstr()).await?;
|
||||
if stats.total_count > 0 {
|
||||
user_error("Shouldn't you empty it first?".to_owned())?;
|
||||
}
|
||||
|
||||
let sell_discount = match stock.can_sell {
|
||||
None => continue,
|
||||
Some(d) => d,
|
||||
};
|
||||
|
||||
if let Some(user) = ctx.user_dat.as_mut() {
|
||||
let sell_price =
|
||||
((stock.list_price as f64) * (sell_discount as f64 / 10000.0)) as u64;
|
||||
user.credits += stock.list_price;
|
||||
ctx.trans
|
||||
.delete_item(&sell_item.item_type, &sell_item.item_code)
|
||||
.await?;
|
||||
|
||||
ctx.trans
|
||||
.queue_for_session(
|
||||
&ctx.session,
|
||||
Some(&format!(
|
||||
"Your wristpad beeps for a credit of {} credits.\n",
|
||||
sell_price
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
check_sell_trigger(ctx, &player_item, &room, &sell_item).await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
user_error(format!(
|
||||
"Sorry, this store doesn't buy {}!",
|
||||
pluralise(&sell_item.display)
|
||||
))?
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -340,6 +340,7 @@ pub enum ItemFlag {
|
||||
NoUrgesHere,
|
||||
DontListInLook,
|
||||
AllowShare,
|
||||
Invincible,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
@ -11,6 +11,7 @@ pub enum JournalType {
|
||||
// Misc
|
||||
Died,
|
||||
SharedWithPlayer,
|
||||
BribedJosephineForRedCode,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||
|
@ -27,6 +27,20 @@ pub struct UserExperienceData {
|
||||
pub crafted_items: BTreeMap<String, u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
||||
#[serde(default)]
|
||||
pub struct QuestProgress {
|
||||
pub daggers_sold_to_josephine: u8,
|
||||
}
|
||||
|
||||
impl Default for QuestProgress {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
daggers_sold_to_josephine: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum UserFlag {
|
||||
Staff,
|
||||
@ -89,6 +103,7 @@ pub struct User {
|
||||
pub credits: u64,
|
||||
pub danger_code: Option<String>,
|
||||
pub user_flags: Vec<UserFlag>,
|
||||
pub quest_progress: Option<QuestProgress>,
|
||||
// Reminder: Consider backwards compatibility when updating this.
|
||||
}
|
||||
|
||||
@ -222,6 +237,7 @@ impl Default for User {
|
||||
credits: 500,
|
||||
danger_code: None,
|
||||
user_flags: vec![],
|
||||
quest_progress: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ async fn process_attack(
|
||||
.map(|u| u.stress.value)
|
||||
.unwrap_or(0)
|
||||
> 8000
|
||||
&& !attacker_item.flags.contains(&ItemFlag::Invincible)
|
||||
{
|
||||
let msg = format!(
|
||||
"{} looks like {} wanted to attack {}, but was too tired and stressed to do it.\n",
|
||||
@ -218,7 +219,9 @@ async fn process_attack(
|
||||
|
||||
change_stress_considering_cool(&ctx.trans, attacker_item, 100).await?;
|
||||
|
||||
if dodge_result > attack_result {
|
||||
if (dodge_result > attack_result && !attacker_item.flags.contains(&ItemFlag::Invincible))
|
||||
|| victim_item.flags.contains(&ItemFlag::Invincible)
|
||||
{
|
||||
let msg = format!(
|
||||
"{} dodges out of the way of {}'s attack.\n",
|
||||
victim_item.display_for_sentence(1, true),
|
||||
@ -559,7 +562,7 @@ pub async fn consider_reward_for(
|
||||
by_item: &mut Item,
|
||||
for_item: &Item,
|
||||
) -> DResult<()> {
|
||||
if by_item.item_type != "player" {
|
||||
if by_item.item_type != "player" || by_item.flags.contains(&ItemFlag::Invincible) {
|
||||
return Ok(());
|
||||
}
|
||||
let (session, _) = match trans.find_session_for_player(&by_item.item_code).await? {
|
||||
@ -1045,7 +1048,7 @@ pub async fn switch_to_power_attack(ctx: &VerbContext<'_>, who: &Arc<Item>) -> U
|
||||
.map(|lp| (lp + Duration::seconds(pow_delay)) - Utc::now())
|
||||
{
|
||||
None => {}
|
||||
Some(d) if d < Duration::seconds(0) => {}
|
||||
Some(d) if d < Duration::seconds(0) || who.flags.contains(&ItemFlag::Invincible) => {}
|
||||
Some(d) => user_error(format!(
|
||||
"You can't powerattack again for another {} seconds.",
|
||||
d.num_seconds()
|
||||
|
@ -334,7 +334,7 @@ pub async fn change_stress_considering_cool(
|
||||
who: &mut Item,
|
||||
max_magnitude: i64,
|
||||
) -> DResult<()> {
|
||||
if !who.flags.contains(&ItemFlag::HasUrges) {
|
||||
if !who.flags.contains(&ItemFlag::HasUrges) || who.flags.contains(&ItemFlag::Invincible) {
|
||||
return Ok(());
|
||||
}
|
||||
let cool = who.total_stats.get(&StatType::Cool).unwrap_or(&8.0);
|
||||
|
@ -67,6 +67,11 @@ pub fn journal_types() -> &'static BTreeMap<JournalType, JournalData> {
|
||||
details: "sharing knowledge in a conversation [with another player]",
|
||||
xp: 200,
|
||||
}),
|
||||
(JournalType::BribedJosephineForRedCode, JournalData {
|
||||
name: "Bribed Josephine",
|
||||
details: "got the red code off Josephine by selling lots of blades",
|
||||
xp: 250,
|
||||
}),
|
||||
).into_iter().collect());
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ pub fn npc_list() -> Vec<NPC> {
|
||||
spawn_location: format!("room/melbs_sewers_{}", &spawn_loc),
|
||||
spawn_possessions: vec![
|
||||
NPCSpawnPossession {
|
||||
what: PossessionType::Dagger,
|
||||
what: PossessionType::RadiantPredatorDagger,
|
||||
action_type: LocationActionType::Wielded,
|
||||
wear_layer: 0,
|
||||
},
|
||||
@ -202,7 +202,7 @@ pub fn npc_list() -> Vec<NPC> {
|
||||
player_consents: vec!(ConsentType::Fight),
|
||||
spawn_possessions: vec![
|
||||
NPCSpawnPossession {
|
||||
what: PossessionType::Dagger,
|
||||
what: PossessionType::Sjambok,
|
||||
action_type: LocationActionType::Wielded,
|
||||
wear_layer: 0,
|
||||
},
|
||||
|
@ -415,6 +415,7 @@ pub enum PossessionType {
|
||||
// Weapons: Blades
|
||||
ButcherKnife,
|
||||
Dagger,
|
||||
RadiantPredatorDagger,
|
||||
RusticKatana,
|
||||
SawtoothMachete,
|
||||
Electroblade,
|
||||
|
@ -84,6 +84,46 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
(PossessionType::RadiantPredatorDagger,
|
||||
PossessionData {
|
||||
display: "radiant predator dagger",
|
||||
aliases: vec!["dagger"],
|
||||
details: "A 30 cm long stainless steel blade, sharp on two edges with a pointy tip, this weapon looks ideal for getting started with bladed close combat. Carved into the blade is the text \"Radiant Predators are the superior lifeform. All others must DIE\"",
|
||||
weight: 250,
|
||||
can_butcher: true,
|
||||
weapon_data: Some(WeaponData {
|
||||
uses_skill: SkillType::Blades,
|
||||
raw_min_to_learn: 1.0,
|
||||
raw_max_to_learn: 3.0,
|
||||
normal_attack: WeaponAttackData {
|
||||
start_messages: vec!(
|
||||
Box::new(|attacker, victim|
|
||||
format!("{} points {} dagger menancingly, preparing 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 dagger cuts into {}'s {}",
|
||||
&attacker.display_for_sentence(1, true),
|
||||
&victim.display_for_sentence(1, false),
|
||||
&part.display(victim.sex.clone())
|
||||
)
|
||||
)
|
||||
),
|
||||
mean_damage: 3.0,
|
||||
stdev_damage: 2.0,
|
||||
base_damage_type: DamageType::Slash,
|
||||
other_damage_types: vec!((0.33334, DamageType::Pierce)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
(PossessionType::SawtoothMachete,
|
||||
PossessionData {
|
||||
display: "sawtooth machete",
|
||||
|
@ -5,7 +5,7 @@ use super::{
|
||||
#[double]
|
||||
use crate::db::DBTrans;
|
||||
use crate::{
|
||||
message_handler::user_commands::{CommandHandlingError, UResult},
|
||||
message_handler::user_commands::{CommandHandlingError, UResult, VerbContext},
|
||||
models::{
|
||||
effect::SimpleEffect,
|
||||
item::{DoorState, Item, ItemFlag},
|
||||
@ -433,15 +433,19 @@ pub struct SecondaryZoneRecord {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(default)]
|
||||
pub struct RoomStock {
|
||||
pub possession_type: PossessionType,
|
||||
pub list_price: u64,
|
||||
pub poverty_discount: bool,
|
||||
pub can_buy: bool,
|
||||
pub can_sell: Option<u64>, // sell price in hundredths of a percent of the buy price, e.g. 8000 = 80% of buy price back.
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ScanCode {
|
||||
SewerAccess,
|
||||
RedImperialCode,
|
||||
}
|
||||
|
||||
impl Default for RoomStock {
|
||||
@ -450,6 +454,8 @@ impl Default for RoomStock {
|
||||
possession_type: PossessionType::AntennaWhip,
|
||||
list_price: 1000000000,
|
||||
poverty_discount: false,
|
||||
can_buy: true,
|
||||
can_sell: Some(8000),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,6 +492,16 @@ pub trait RoomEnterTrigger {
|
||||
pub trait RoomExitTrigger {
|
||||
async fn handle_exit(self: &Self, ctx: &mut QueuedCommandContext, room: &Room) -> UResult<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
pub trait RoomSellTrigger {
|
||||
async fn handle_sell(
|
||||
&self,
|
||||
ctx: &mut VerbContext,
|
||||
room: &Room,
|
||||
player_item: &Item,
|
||||
sell_item: &Item,
|
||||
) -> UResult<()>;
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
pub zone: String,
|
||||
@ -512,6 +528,7 @@ pub struct Room {
|
||||
pub journal: Option<JournalType>,
|
||||
pub enter_trigger: Option<Box<dyn RoomEnterTrigger + Sync + Send>>,
|
||||
pub exit_trigger: Option<Box<dyn RoomExitTrigger + Sync + Send>>,
|
||||
pub sell_trigger: Option<Box<dyn RoomSellTrigger + Sync + Send>>,
|
||||
pub scavtable: ScavtableType,
|
||||
}
|
||||
|
||||
@ -539,6 +556,7 @@ impl Default for Room {
|
||||
journal: None,
|
||||
enter_trigger: None,
|
||||
exit_trigger: None,
|
||||
sell_trigger: None,
|
||||
scavtable: ScavtableType::Nothing,
|
||||
}
|
||||
}
|
||||
@ -613,6 +631,7 @@ impl<T> Into<Room> for SimpleRoom<T> {
|
||||
Box::new(RoomEffectEntryTrigger { effects: fx })
|
||||
as Box<(dyn RoomEnterTrigger + std::marker::Send + Sync + 'static)>
|
||||
}),
|
||||
sell_trigger: None,
|
||||
scavtable: self.scavtable,
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,17 @@
|
||||
use super::{Room, SimpleRoom};
|
||||
use super::{Room, RoomSellTrigger, ScanCode, SimpleRoom};
|
||||
use crate::{
|
||||
models::item::Scavtype,
|
||||
static_content::{possession_type::PossessionType, scavtable::Scavinfo},
|
||||
message_handler::user_commands::{UResult, UserError, VerbContext},
|
||||
models::{
|
||||
item::{Item, Scavtype},
|
||||
journal::JournalType,
|
||||
user::QuestProgress,
|
||||
},
|
||||
static_content::{
|
||||
journals::award_journal_if_needed, possession_type::PossessionType, scavtable::Scavinfo,
|
||||
},
|
||||
};
|
||||
use ansi::ansi;
|
||||
use async_trait::async_trait;
|
||||
use serde_yaml::from_str as from_yaml_str;
|
||||
|
||||
pub fn sewer_scavtable() -> Vec<Scavinfo> {
|
||||
@ -15,10 +24,81 @@ pub fn sewer_scavtable() -> Vec<Scavinfo> {
|
||||
}]
|
||||
}
|
||||
|
||||
pub struct JosephineSellTrigger;
|
||||
|
||||
#[async_trait]
|
||||
impl RoomSellTrigger for JosephineSellTrigger {
|
||||
async fn handle_sell(
|
||||
&self,
|
||||
ctx: &mut VerbContext,
|
||||
_room: &Room,
|
||||
player_item: &Item,
|
||||
sell_item: &Item,
|
||||
) -> UResult<()> {
|
||||
if sell_item.possession_type == Some(PossessionType::RadiantPredatorDagger) {
|
||||
let user_dat = ctx
|
||||
.user_dat
|
||||
.as_mut()
|
||||
.ok_or_else(|| UserError("Selling while not logged in?".to_owned()))?;
|
||||
if user_dat.scan_codes.contains(&ScanCode::RedImperialCode) {
|
||||
return Ok(());
|
||||
}
|
||||
match user_dat.quest_progress.as_mut() {
|
||||
None => {
|
||||
user_dat.quest_progress = Some(QuestProgress {
|
||||
daggers_sold_to_josephine: 1,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Some(qd) => {
|
||||
qd.daggers_sold_to_josephine += 1;
|
||||
}
|
||||
}
|
||||
if user_dat
|
||||
.quest_progress
|
||||
.as_ref()
|
||||
.map(|qp| qp.daggers_sold_to_josephine)
|
||||
.unwrap_or(0)
|
||||
>= 5
|
||||
{
|
||||
user_dat.scan_codes.push(ScanCode::RedImperialCode);
|
||||
ctx.trans.queue_for_session(
|
||||
&ctx.session,
|
||||
Some(ansi!("<blue>Josephine whispers to you: \"Thank you - those daggers will really help me \
|
||||
build my defence system.\"<reset>\n\
|
||||
Josephine takes your hand and enters a code onto your wristpad. It beeps and flashes up \
|
||||
a message saying: \"Red Imperial Code saved\".\n"))
|
||||
).await?;
|
||||
let mut player_item = (*player_item).clone();
|
||||
award_journal_if_needed(
|
||||
&ctx.trans,
|
||||
user_dat,
|
||||
&mut player_item,
|
||||
JournalType::BribedJosephineForRedCode,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ctx.trans.save_user_model(user_dat).await?;
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room_list() -> Vec<Room> {
|
||||
from_yaml_str::<Vec<SimpleRoom<()>>>(include_str!("melbs_sewers.yaml"))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|r| r.into())
|
||||
.map(|r: Room| {
|
||||
if r.code == "melbs_sewers_subsewer_josephine" {
|
||||
Room {
|
||||
sell_trigger: Some(Box::new(JosephineSellTrigger)),
|
||||
..r
|
||||
}
|
||||
} else {
|
||||
r
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -1016,6 +1016,7 @@
|
||||
repel_npc: true
|
||||
description: Some kind of service tunnel that has been carved into the bedrock far beneath the sewers. Solid rock walls surround you in all directions except up and to the north. A dim light emanates from some kind of subterranean room to the north
|
||||
- zone: melbs_sewers
|
||||
# Caution: Code triggers special case sell trigger
|
||||
code: melbs_sewers_subsewer_josephine
|
||||
name: Josephine's cavern
|
||||
short: <bgblack><green>JO<reset>
|
||||
@ -1039,7 +1040,7 @@
|
||||
message: "<blue>Josephine whispers to you: \"I'm building a more advanced defence system that will fight off enemies all through the sewers. Right now I need lots of radiant predator blades to help me build it.\"<reset>"
|
||||
- !DirectMessage
|
||||
delay_secs: 20
|
||||
message: "<blue>Josephine whispers to you: \"I've got these special red key code cards that were apparently used by the emperor as part of some security system - my supplier apparently used to guard them for the emperor before the empire fell, but even he doesn't know what they are for except that it's one part of a key to some ultra-secure security system. I'll give you one for every five radiant predator blades you sell here.\"<reset>"
|
||||
message: "<blue>Josephine whispers to you: \"I've got this special red key code that was apparently used by the emperor as part of some security system - my supplier apparently used to guard it for the emperor before the empire fell, but even he doesn't know what they are for except that it's one part of a key to some ultra-secure security system. I'll let you load it to your wristpad if you sell me five radiant predator blades.\"<reset>"
|
||||
stock_list:
|
||||
- possession_type: !MediumTraumaKit
|
||||
list_price: 120
|
||||
@ -1047,6 +1048,10 @@
|
||||
- possession_type: !GreasyBurger
|
||||
list_price: 15
|
||||
poverty_discount: false
|
||||
- possession_type: !RadiantPredatorDagger
|
||||
list_price: 120
|
||||
can_buy: false
|
||||
can_sell: 10000
|
||||
- zone: melbs_sewers
|
||||
code: melbs_sewers_10h
|
||||
name: Vast sewer cavern
|
||||
|
Loading…
Reference in New Issue
Block a user