diff --git a/blastmud_game/src/message_handler/user_commands/use_cmd.rs b/blastmud_game/src/message_handler/user_commands/use_cmd.rs index dc2b6531..7e7a312d 100644 --- a/blastmud_game/src/message_handler/user_commands/use_cmd.rs +++ b/blastmud_game/src/message_handler/user_commands/use_cmd.rs @@ -213,13 +213,35 @@ impl QueueCommandHandler for QueueHandler { } ctx.trans.save_item_model(&player_mut).await?; + let mut item_mut = (*item).clone(); + let mut save_item = false; if item.possession_type.as_ref() .and_then(|poss_type| possession_data().get(&poss_type)) .and_then(|poss_data| poss_data.charge_data.as_ref()).is_some() { - let mut item_mut = (*item).clone(); item_mut.charges -= 1; - ctx.trans.save_item_model(&item_mut).await?; + save_item = true; } + if item_mut.charges == 0 { + if let Some((new_poss, new_poss_dat)) = item.possession_type.as_ref() + .and_then(|pt| possession_data().get(pt)) + .and_then(|poss_data| poss_data.becomes_on_spent.as_ref()) + .and_then(|poss_type| possession_data().get(&poss_type) + .map(|poss_dat| (poss_type, poss_dat))) + { + item_mut.possession_type = Some(new_poss.clone()); + item_mut.display = new_poss_dat.display.to_owned(); + item_mut.display_less_explicit = new_poss_dat.display_less_explicit.map(|d| d.to_owned()); + item_mut.details = Some(new_poss_dat.details.to_owned()); + item_mut.details_less_explicit = new_poss_dat.details_less_explicit.map(|d| d.to_owned()); + item_mut.aliases = new_poss_dat.aliases.iter().map(|al| (*al).to_owned()).collect(); + item_mut.health = new_poss_dat.max_health; + item_mut.weight = new_poss_dat.weight; + save_item = true; + } + } + if save_item { + ctx.trans.save_item_model(&item_mut).await?; + } Ok(()) } } diff --git a/blastmud_game/src/models.rs b/blastmud_game/src/models.rs index 542ecf0d..9d50dbe9 100644 --- a/blastmud_game/src/models.rs +++ b/blastmud_game/src/models.rs @@ -2,3 +2,4 @@ pub mod session; pub mod user; pub mod item; pub mod task; +pub mod consent; diff --git a/blastmud_game/src/models/consent.rs b/blastmud_game/src/models/consent.rs new file mode 100644 index 00000000..e0e92c28 --- /dev/null +++ b/blastmud_game/src/models/consent.rs @@ -0,0 +1,36 @@ +use serde::{Serialize, Deserialize}; +use chrono::{DateTime, Utc}; + +#[derive(Serialize, Deserialize)] +pub enum ConsentType { + Fight, + Medicine, + Gifts, + Visit, + Sex +} + +#[derive(Serialize, Deserialize)] +pub enum ConsentStatus { + PendingAdd, // Added but awaiting other party to ratify by giving matching consent. + Active, // Consent in force, no delete pending. + PendingDelete, // Pending cancellation but other party has to also disallow to ratify. +} + +#[derive(Serialize, Deserialize)] +pub struct FightConsent { + status: ConsentStatus, + pending_change: Option>, + allow_pick: bool, + freely_revoke: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct Consent { + consent_type: ConsentType, + fight_consent: Option, + expires: Option>, + only_in: Vec, + allow_private: bool, + until_death: bool, +} diff --git a/blastmud_game/src/models/corp.rs b/blastmud_game/src/models/corp.rs new file mode 100644 index 00000000..f8b7b36b --- /dev/null +++ b/blastmud_game/src/models/corp.rs @@ -0,0 +1,10 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +pub struct Corp { + pub name: String, + // If true, new members get allow_combat on, and members cannot turn + // allow_combat off. This will allow duly authorised corp members to + // consent to combat with other corps, and have it apply to members. + pub allow_combat_required: bool, +} diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index df18a78d..ac83a26e 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -286,6 +286,11 @@ impl Default for ActiveCombat { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] +pub enum ItemSpecialData { + ItemWriting { text: String } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct Item { @@ -314,7 +319,8 @@ pub struct Item { pub sex: Option, pub active_combat: Option, pub weight: u64, - pub charges: u8 + pub charges: u8, + pub special_data: Option, } impl Item { @@ -392,6 +398,7 @@ impl Default for Item { active_combat: Some(Default::default()), weight: 0, charges: 0, + special_data: None, } } } diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index ae934c9c..72b38908 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -1,11 +1,12 @@ use serde::{Serialize, Deserialize}; use crate::{ - models::item::{SkillType, Item, Pronouns} + models::item::{SkillType, Item, ItemSpecialData, Pronouns} }; use once_cell::sync::OnceCell; use std::collections::BTreeMap; use rand::seq::SliceRandom; use super::species::BodyPart; +use ansi::ansi; pub type AttackMessageChoice = Vec String + 'static + Sync + Send>>; pub type AttackMessageChoicePart = Vec String + 'static + Sync + Send>>; @@ -111,6 +112,7 @@ pub struct PossessionData { pub max_health: u64, pub charge_data: Option, pub use_data: Option, + pub becomes_on_spent: Option, pub weight: u64, } @@ -126,6 +128,7 @@ impl Default for PossessionData { max_health: 10, weight: 100, charge_data: None, + becomes_on_spent: None, use_data: None, } } @@ -161,6 +164,9 @@ pub enum PossessionType { // Real possessions from here on: AntennaWhip, MediumTraumaKit, + EmptyMedicalBox, + NewCorpLicence, + CertificateOfIncorporation, } impl Into for PossessionType { @@ -496,6 +502,52 @@ pub fn possession_data() -> &'static BTreeMap { }), ..Default::default() }), + becomes_on_spent: Some(EmptyMedicalBox), + ..Default::default() + }), + (EmptyMedicalBox, PossessionData { + display: "empty medical box", + details: "An empty box that looks like it once had something medical in it.", + aliases: vec!("box"), + ..Default::default() + }), + (NewCorpLicence, PossessionData { + display: "new corp licence", + details: ansi!("A blank form that you can use to establish a new corp. It rests on a clipboard with a pencil attached by a chain. There is a space to write on it [try write Blah on licence followed by use licence to create a corp named Blah]"), + aliases: vec!("form", "license", "licence", "new"), + use_data: Some(UseData { + uses_skill: SkillType::Persuade, + diff_level: 4.0, + crit_fail_effects: vec!(), + fail_effects: vec!(), + success_effects: vec!( + UseEffect::BroadcastMessage { + messagef: Box::new(|player, _item, _target| ( + format!( + "{} signs a contract establishing Blah as a corp\n", + &player.display_for_sentence(true, 1, true), + ), + format!("{} signs a contract establishing Blah as a corp\n", + &player.display_for_sentence(false, 1, true), + ))) + }, + ), + errorf: Box::new( + |item, _target| + match item.special_data { + Some(ItemSpecialData::ItemWriting { .. }) => None, + _ => Some("You have to your corp's name on it first!".to_owned()) + }), + ..Default::default() + }), + weight: 10, + becomes_on_spent: Some(CertificateOfIncorporation), + ..Default::default() + }), + (CertificateOfIncorporation, PossessionData { + display: "certificate of incorporation", + details: "A certificate recording the formation of a corp.", + weight: 10, ..Default::default() }), ).into_iter().collect() diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index 5f15bf8f..e16bf02b 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -2312,10 +2312,41 @@ pub fn room_list() -> Vec { target: ExitTarget::UseGPS, exit_type: ExitType::Free }, + Exit { + direction: Direction::EAST, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free + }, ), should_caption: false, ..Default::default() }, + Room { + zone: "melbs", + secondary_zones: vec!(), + code: "kings_office", + name: "Kings Office", + short: ansi!("KO"), + description: ansi!("A dilapidated office that clearly was once a grand governor's office under the empire. It is now barely maintained. The self-styled king slouches behind a desk in the corner, shuffling paperwork around his desk. [Use list to see contracts for sale here]"), + description_less_explicit: None, + grid_coords: GridCoords { x: 6, y: 5, z: 0 }, + exits: vec!( + Exit { + direction: Direction::WEST, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free + }, + ), + should_caption: true, + stock_list: vec!( + RoomStock { + possession_type: PossessionType::NewCorpLicence, + list_price: 5000, + ..Default::default() + } + ), + ..Default::default() + }, Room { zone: "melbs", secondary_zones: vec!(), diff --git a/schema/schema.sql b/schema/schema.sql index 5670a8f7..b5409b42 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -53,3 +53,31 @@ CREATE TABLE tasks ( CREATE UNIQUE INDEX tasks_by_code_type ON tasks((details->>'task_code'), (details->>'task_type')); CREATE INDEX tasks_by_static ON tasks((cast(details->>'is_static' as boolean))); CREATE INDEX tasks_by_scheduled ON tasks((details->>'next_scheduled')); + +CREATE TABLE corps ( + corp_id BIGSERIAL NOT NULL PRIMARY KEY, + details JSONB NOT NULL +); +CREATE INDEX corp_by_name ON corps((details->>'name')); +CREATE TABLE corp_membership ( + corp_id BIGSERIAL NOT NULL REFERENCES corps(corp_id), + member_username TEXT NOT NULL REFERENCES users(username), + details JSONB NOT NULL, + PRIMARY KEY (corp_id, member_username) +); + +CREATE TABLE user_consent ( + consenting_user TEXT NOT NULL REFERENCES users(username), + consented_user TEXT NOT NULL REFERENCES users(username), + details JSONB NOT NULL, + PRIMARY KEY (consenting_user, consented_user) +); +CREATE INDEX user_consent_by_consented ON user_consent (consented_user); + +CREATE TABLE corp_consent ( + consenting_corp BIGINT NOT NULL REFERENCES corps(corp_id), + consented_corp BIGINT NOT NULL REFERENCES corps(corp_id), + details JSONB NOT NULL, + PRIMARY KEY (consenting_corp, consented_corp) +); +CREATE INDEX corp_consent_by_consented ON corp_consent (consented_corp);