diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 3755058..896d111 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -1023,7 +1023,7 @@ impl DBTrans { WHERE \ (lower(details->>'display') LIKE $1) \ OR EXISTS (SELECT 1 FROM jsonb_array_elements(aliases) AS al(alias) WHERE \ - LOWER(alias#>>'{{}}') LIKE $1)) {} \ + LOWER(alias#>>'{{}}') LIKE $1) {} \ ORDER BY {} ABS(length(details->>'display')-$3) ASC \ LIMIT $4 OFFSET $2", &cte_str, &extra_where, &extra_order diff --git a/blastmud_game/src/message_handler/user_commands/wear.rs b/blastmud_game/src/message_handler/user_commands/wear.rs index f519206..6ac03c0 100644 --- a/blastmud_game/src/message_handler/user_commands/wear.rs +++ b/blastmud_game/src/message_handler/user_commands/wear.rs @@ -151,6 +151,7 @@ impl QueueCommandHandler for QueueHandler { if wear_data.dodge_penalty != 0.0 { ctx.item.temporary_buffs.push(Buff { description: "Dodge penalty".to_owned(), + code: "dodge".to_owned(), cause: BuffCause::ByItem { item_type: item_mut.item_type.clone(), item_code: item_mut.item_code.clone(), diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index eebe69b..25146e3 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -33,12 +33,28 @@ pub enum BuffImpact { } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] +#[serde(default)] pub struct Buff { pub description: String, + pub code: String, pub cause: BuffCause, pub impacts: Vec, } +impl Default for Buff { + fn default() -> Self { + Self { + description: "Default".to_owned(), + code: "default".to_owned(), + cause: BuffCause::WaitingTask { + task_code: "default".to_owned(), + task_type: "default".to_owned(), + }, + impacts: vec![], + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum SkillType { Appraise, diff --git a/blastmud_game/src/models/journal.rs b/blastmud_game/src/models/journal.rs index dc24838..29ff993 100644 --- a/blastmud_game/src/models/journal.rs +++ b/blastmud_game/src/models/journal.rs @@ -10,6 +10,7 @@ pub enum JournalType { JoinedHackerClub, // Misc Died, + SharedWithPlayer, } #[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] diff --git a/blastmud_game/src/models/task.rs b/blastmud_game/src/models/task.rs index b065882..30eea73 100644 --- a/blastmud_game/src/models/task.rs +++ b/blastmud_game/src/models/task.rs @@ -69,6 +69,10 @@ pub enum TaskDetails { target: String, effect_type: EffectType, }, + ExpireBuff { + item: String, + code: String, + }, } impl TaskDetails { pub fn name(self: &Self) -> &'static str { @@ -94,6 +98,7 @@ impl TaskDetails { ResetSpawns => "ResetSpawns", ResetHanoi => "ResetHanoi", HospitalERSeePatient { .. } => "HospitalERSeePatient", + ExpireBuff { .. } => "ExpireBuff", // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too. } } diff --git a/blastmud_game/src/regular_tasks.rs b/blastmud_game/src/regular_tasks.rs index 24ca3f7..c69c1c4 100644 --- a/blastmud_game/src/regular_tasks.rs +++ b/blastmud_game/src/regular_tasks.rs @@ -9,7 +9,7 @@ use crate::{ listener::{ListenerMap, ListenerSend}, message_handler::user_commands::{delete, drop, hire, open, rent}, models::task::Task, - services::{combat, effect, sharing, spawn, urges}, + services::{combat, effect, sharing, spawn, tempbuff, urges}, static_content::{ npc::{self, computer_museum_npcs}, room::general_hospital, @@ -67,6 +67,7 @@ fn task_handler_registry( ("ResetSpawns", spawn::RESET_SPAWNS_HANDLER), ("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER), ("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK), + ("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK), ] .into_iter() .collect() diff --git a/blastmud_game/src/services.rs b/blastmud_game/src/services.rs index ee3d8ef..a12a9ad 100644 --- a/blastmud_game/src/services.rs +++ b/blastmud_game/src/services.rs @@ -24,6 +24,7 @@ pub mod effect; pub mod sharing; pub mod skills; pub mod spawn; +pub mod tempbuff; pub mod urges; pub fn check_one_consent(consent: &Consent, action: &str, target: &Item) -> bool { diff --git a/blastmud_game/src/services/sharing.rs b/blastmud_game/src/services/sharing.rs index b8c4049..aa0559a 100644 --- a/blastmud_game/src/services/sharing.rs +++ b/blastmud_game/src/services/sharing.rs @@ -12,12 +12,15 @@ use crate::{ models::{ consent::ConsentType, item::{ - ActiveConversation, ConversationIntensity, ConversationTopic, - ConversationalInterestType, ConversationalStyle, Item, ItemFlag, SkillType, + ActiveConversation, Buff, BuffCause, BuffImpact, ConversationIntensity, + ConversationTopic, ConversationalInterestType, ConversationalStyle, Item, ItemFlag, + SkillType, StatType, }, + journal::JournalType, task::{Task, TaskDetails, TaskMeta}, }, regular_tasks::{TaskHandler, TaskRunContext}, + static_content::journals::award_journal_if_needed, DResult, }; use ansi::ansi; @@ -302,6 +305,7 @@ impl TaskHandler for ShareTaskHandler { .or_insert(growth.max(0) as u64) }); } + let res = compute_conversation_result(&p1, &p1_conv, &p2, &p2_conv); p1_mut.active_conversation.as_mut().map(|ac| { ac.peak_total_interest = res.my_total_interest.max(ac.peak_total_interest); @@ -408,7 +412,9 @@ async fn inform_player_convo_change_ready( } fn share_skill_to_base_transition_time(skill: f64) -> f64 { - ((13.6666666666666666666667 - skill) * 3.0).max(1.0) + ((13.6666666666666666666667 - skill) * 3.0) + .max(1.0) + .min(23.0) } fn compute_conversation_result( @@ -436,8 +442,8 @@ fn compute_conversation_result( let their_transition_time = share_skill_to_base_transition_time(their_theoretical_skill_level) as u64; - let my_total_interest = my_direct_interest * their_transition_time; - let their_total_interest = their_direct_interest * my_transition_time; + let my_total_interest = my_direct_interest + 1363 * (their_transition_time - 1); + let their_total_interest = their_direct_interest + 1363 * (my_transition_time - 1); ConversationResult { my_total_interest, @@ -551,7 +557,7 @@ pub async fn start_conversation( if initiator.item_type != "player" { user_error("Only players can initiate conversation with players".to_owned())?; } - let (_other_sess, _other_sessdat) = + let _ = trans .find_session_for_player(&acceptor.item_code) .await? @@ -670,6 +676,116 @@ pub async fn start_conversation( Ok(()) } +async fn apply_conversation_buff(trans: &DBTrans, to_player: &mut Item) -> DResult<()> { + let peak = to_player + .active_conversation + .as_ref() + .map(|ac| ac.peak_total_interest) + .unwrap_or(0); + let mut buffs: Vec = vec![]; + const BASE: u64 = 7691; + if peak >= BASE * 7 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Cool, + magnitude: 2.0, + }); + } else if peak >= BASE { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Cool, + magnitude: 1.0, + }); + } + if peak >= BASE * 8 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Brains, + magnitude: 2.0, + }); + } else if peak >= BASE * 2 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Brains, + magnitude: 1.0, + }); + } + if peak >= BASE * 9 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Senses, + magnitude: 2.0, + }); + } else if peak >= BASE * 3 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Senses, + magnitude: 1.0, + }); + } + if peak >= BASE * 10 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Endurance, + magnitude: 2.0, + }); + } else if peak >= BASE * 4 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Endurance, + magnitude: 1.0, + }); + } + if peak >= BASE * 11 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Brawn, + magnitude: 2.0, + }); + } else if peak >= BASE * 5 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Brawn, + magnitude: 1.0, + }); + } + if peak >= BASE * 12 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Reflexes, + magnitude: 2.0, + }); + } else if peak >= BASE * 6 { + buffs.push(BuffImpact::ChangeStat { + stat: StatType::Reflexes, + magnitude: 1.0, + }); + } + + let exp_task_code = format!("sharing/{}/{}", &to_player.item_type, &to_player.item_code); + + to_player.temporary_buffs.retain(|b| match b.cause { + BuffCause::WaitingTask { + ref task_type, + ref task_code, + } if task_type == "ExpireBuff" && task_code == &exp_task_code => true, + _ => false, + }); + to_player.temporary_buffs.push(Buff { + description: "Sharing knowledge improved you (for a while)".to_owned(), + code: "sharing".to_owned(), + cause: BuffCause::WaitingTask { + task_type: "ExpireBuff".to_owned(), + task_code: exp_task_code.clone(), + }, + impacts: buffs, + }); + trans + .upsert_task(&Task { + meta: TaskMeta { + task_code: exp_task_code.clone(), + next_scheduled: Utc::now() + chrono::Duration::seconds(600), + ..Default::default() + }, + details: TaskDetails::ExpireBuff { + item: to_player.refstr(), + code: "sharing".to_owned(), + }, + }) + .await?; + + Ok(()) +} + pub async fn stop_conversation_mut( trans: &DBTrans, participant: &mut Item, @@ -693,8 +809,39 @@ pub async fn stop_conversation_mut( }; let mut partner_mut = (*partner).clone(); + apply_conversation_buff(trans, &mut partner_mut).await?; + apply_conversation_buff(trans, participant).await?; + participant.active_conversation = None; partner_mut.active_conversation = None; + + if participant.item_type == "player" && partner_mut.item_type == "player" { + if let Some(mut participant_user) = trans.find_by_username(&participant.item_code).await? { + if award_journal_if_needed( + trans, + &mut participant_user, + participant, + JournalType::SharedWithPlayer, + ) + .await? + { + trans.save_user_model(&participant_user).await?; + } + } + if let Some(mut partner_user) = trans.find_by_username(&partner.item_code).await? { + if award_journal_if_needed( + trans, + &mut partner_user, + &mut partner_mut, + JournalType::SharedWithPlayer, + ) + .await? + { + trans.save_user_model(&partner_user).await?; + } + } + } + trans.save_item_model(&partner_mut).await?; broadcast_to_room( diff --git a/blastmud_game/src/services/tempbuff.rs b/blastmud_game/src/services/tempbuff.rs new file mode 100644 index 0000000..ee64e1a --- /dev/null +++ b/blastmud_game/src/services/tempbuff.rs @@ -0,0 +1,42 @@ +use crate::{ + models::task::TaskDetails, + regular_tasks::{TaskHandler, TaskRunContext}, + DResult, +}; +use async_trait::async_trait; +use std::time; + +pub struct ExpireBuffTask; +#[async_trait] +impl TaskHandler for ExpireBuffTask { + async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult> { + let (item_ref, code) = match ctx.task.details { + TaskDetails::ExpireBuff { ref item, ref code } => Ok((item, code)), + _ => Err("Bad task type for ExpireBuffTask"), + }?; + + let (item_type, item_code) = match item_ref.split_once("/") { + Some(v) => Ok(v), + None => Err("Invalid item for ExpireBuffTask"), + }?; + + let item = ctx + .trans + .find_item_by_type_code(item_type, item_code) + .await?; + let mut item = match item { + None => return Ok(None), + Some(i) => (*i).clone(), + }; + + item.temporary_buffs = item + .temporary_buffs + .into_iter() + .filter(|b| &b.code != code) + .collect(); + ctx.trans.save_item_model(&item).await?; + + Ok(None) + } +} +pub static EXPIRE_BUFF_TASK: &'static (dyn TaskHandler + Sync + Send) = &ExpireBuffTask; diff --git a/blastmud_game/src/static_content/journals.rs b/blastmud_game/src/static_content/journals.rs index 8eeb863..cd597f3 100644 --- a/blastmud_game/src/static_content/journals.rs +++ b/blastmud_game/src/static_content/journals.rs @@ -61,7 +61,12 @@ pub fn journal_types() -> &'static BTreeMap { name: "Carked it", details: "dying for the first time. Fortunately, you can come back by recloning in to a fresh body, just with fewer credits, a bit less experience, and a bruised ego! All your stuff is still on your body, so better go find it, or give up on it.", xp: 150 - }) + }), + (JournalType::SharedWithPlayer, JournalData { + name: "Learned to share", + details: "sharing knowledge in a conversation [with another player]", + xp: 200, + }), ).into_iter().collect()); } diff --git a/docs/dbfixes.md b/docs/dbfixes.md index cf373ff..03c964a 100644 --- a/docs/dbfixes.md +++ b/docs/dbfixes.md @@ -22,3 +22,9 @@ This is expected to be 0 - haven't seen any instances of it deviating, so any bu select i1.details->>'item_code' as i1_code, i1.details->>'active_combat' as i1_combat, i1.details->>'location' as i1_loc, i2.details->>'item_code' as i2_code, i2.details->>'active_combat' as i2_combat, i2.details->>'location' as i2_loc from items i1 join items i2 on i2.details->>'item_type' = (regexp_split_to_array(i1.details->'active_combat'->>'attacking', '/'))[1] and i2.details->>'item_code' = (regexp_split_to_array(i1.details->'active_combat'->>'attacking', '/'))[2] where not exists (select 1 from jsonb_array_elements_text(i2.details->'active_combat'->'attacked_by') e where e = ((i1.details->>'item_type') || '/' || (i1.details->>'item_code'))); This should be empty, but there has been a bug breaking this. + +# Other types of migrations +## Rename a skill + +Something like this: +with fexdet as (select item_id, jsonb_strip_nulls(jsonb_set(details, '{total_skills,Fuck}', 'null')) as details, details->'total_skills'->'Fuck' as f from items), amenddet as (select jsonb_set(details :: jsonb, '{total_skills,Share}', f :: jsonb) as details, item_id from fexdet where f is not null) update items i set details = a.details from amenddet a where i.item_id = a.item_id;