Add buff reward + fix resolver bug

This commit is contained in:
Condorra 2024-01-05 23:46:02 +11:00
parent 657ec807e0
commit dbaf477f49
11 changed files with 234 additions and 9 deletions

View File

@ -1023,7 +1023,7 @@ impl DBTrans {
WHERE \ WHERE \
(lower(details->>'display') LIKE $1) \ (lower(details->>'display') LIKE $1) \
OR EXISTS (SELECT 1 FROM jsonb_array_elements(aliases) AS al(alias) WHERE \ 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 \ ORDER BY {} ABS(length(details->>'display')-$3) ASC \
LIMIT $4 OFFSET $2", LIMIT $4 OFFSET $2",
&cte_str, &extra_where, &extra_order &cte_str, &extra_where, &extra_order

View File

@ -151,6 +151,7 @@ impl QueueCommandHandler for QueueHandler {
if wear_data.dodge_penalty != 0.0 { if wear_data.dodge_penalty != 0.0 {
ctx.item.temporary_buffs.push(Buff { ctx.item.temporary_buffs.push(Buff {
description: "Dodge penalty".to_owned(), description: "Dodge penalty".to_owned(),
code: "dodge".to_owned(),
cause: BuffCause::ByItem { cause: BuffCause::ByItem {
item_type: item_mut.item_type.clone(), item_type: item_mut.item_type.clone(),
item_code: item_mut.item_code.clone(), item_code: item_mut.item_code.clone(),

View File

@ -33,12 +33,28 @@ pub enum BuffImpact {
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
#[serde(default)]
pub struct Buff { pub struct Buff {
pub description: String, pub description: String,
pub code: String,
pub cause: BuffCause, pub cause: BuffCause,
pub impacts: Vec<BuffImpact>, pub impacts: Vec<BuffImpact>,
} }
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)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SkillType { pub enum SkillType {
Appraise, Appraise,

View File

@ -10,6 +10,7 @@ pub enum JournalType {
JoinedHackerClub, JoinedHackerClub,
// Misc // Misc
Died, Died,
SharedWithPlayer,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]

View File

@ -69,6 +69,10 @@ pub enum TaskDetails {
target: String, target: String,
effect_type: EffectType, effect_type: EffectType,
}, },
ExpireBuff {
item: String,
code: String,
},
} }
impl TaskDetails { impl TaskDetails {
pub fn name(self: &Self) -> &'static str { pub fn name(self: &Self) -> &'static str {
@ -94,6 +98,7 @@ impl TaskDetails {
ResetSpawns => "ResetSpawns", ResetSpawns => "ResetSpawns",
ResetHanoi => "ResetHanoi", ResetHanoi => "ResetHanoi",
HospitalERSeePatient { .. } => "HospitalERSeePatient", HospitalERSeePatient { .. } => "HospitalERSeePatient",
ExpireBuff { .. } => "ExpireBuff",
// Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too. // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
} }
} }

View File

@ -9,7 +9,7 @@ use crate::{
listener::{ListenerMap, ListenerSend}, listener::{ListenerMap, ListenerSend},
message_handler::user_commands::{delete, drop, hire, open, rent}, message_handler::user_commands::{delete, drop, hire, open, rent},
models::task::Task, models::task::Task,
services::{combat, effect, sharing, spawn, urges}, services::{combat, effect, sharing, spawn, tempbuff, urges},
static_content::{ static_content::{
npc::{self, computer_museum_npcs}, npc::{self, computer_museum_npcs},
room::general_hospital, room::general_hospital,
@ -67,6 +67,7 @@ fn task_handler_registry(
("ResetSpawns", spawn::RESET_SPAWNS_HANDLER), ("ResetSpawns", spawn::RESET_SPAWNS_HANDLER),
("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER), ("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER),
("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK), ("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK),
("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK),
] ]
.into_iter() .into_iter()
.collect() .collect()

View File

@ -24,6 +24,7 @@ pub mod effect;
pub mod sharing; pub mod sharing;
pub mod skills; pub mod skills;
pub mod spawn; pub mod spawn;
pub mod tempbuff;
pub mod urges; pub mod urges;
pub fn check_one_consent(consent: &Consent, action: &str, target: &Item) -> bool { pub fn check_one_consent(consent: &Consent, action: &str, target: &Item) -> bool {

View File

@ -12,12 +12,15 @@ use crate::{
models::{ models::{
consent::ConsentType, consent::ConsentType,
item::{ item::{
ActiveConversation, ConversationIntensity, ConversationTopic, ActiveConversation, Buff, BuffCause, BuffImpact, ConversationIntensity,
ConversationalInterestType, ConversationalStyle, Item, ItemFlag, SkillType, ConversationTopic, ConversationalInterestType, ConversationalStyle, Item, ItemFlag,
SkillType, StatType,
}, },
journal::JournalType,
task::{Task, TaskDetails, TaskMeta}, task::{Task, TaskDetails, TaskMeta},
}, },
regular_tasks::{TaskHandler, TaskRunContext}, regular_tasks::{TaskHandler, TaskRunContext},
static_content::journals::award_journal_if_needed,
DResult, DResult,
}; };
use ansi::ansi; use ansi::ansi;
@ -302,6 +305,7 @@ impl TaskHandler for ShareTaskHandler {
.or_insert(growth.max(0) as u64) .or_insert(growth.max(0) as u64)
}); });
} }
let res = compute_conversation_result(&p1, &p1_conv, &p2, &p2_conv); let res = compute_conversation_result(&p1, &p1_conv, &p2, &p2_conv);
p1_mut.active_conversation.as_mut().map(|ac| { p1_mut.active_conversation.as_mut().map(|ac| {
ac.peak_total_interest = res.my_total_interest.max(ac.peak_total_interest); 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 { 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( fn compute_conversation_result(
@ -436,8 +442,8 @@ fn compute_conversation_result(
let their_transition_time = let their_transition_time =
share_skill_to_base_transition_time(their_theoretical_skill_level) as u64; share_skill_to_base_transition_time(their_theoretical_skill_level) as u64;
let my_total_interest = my_direct_interest * their_transition_time; let my_total_interest = my_direct_interest + 1363 * (their_transition_time - 1);
let their_total_interest = their_direct_interest * my_transition_time; let their_total_interest = their_direct_interest + 1363 * (my_transition_time - 1);
ConversationResult { ConversationResult {
my_total_interest, my_total_interest,
@ -551,7 +557,7 @@ pub async fn start_conversation(
if initiator.item_type != "player" { if initiator.item_type != "player" {
user_error("Only players can initiate conversation with players".to_owned())?; user_error("Only players can initiate conversation with players".to_owned())?;
} }
let (_other_sess, _other_sessdat) = let _ =
trans trans
.find_session_for_player(&acceptor.item_code) .find_session_for_player(&acceptor.item_code)
.await? .await?
@ -670,6 +676,116 @@ pub async fn start_conversation(
Ok(()) 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<BuffImpact> = 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( pub async fn stop_conversation_mut(
trans: &DBTrans, trans: &DBTrans,
participant: &mut Item, participant: &mut Item,
@ -693,8 +809,39 @@ pub async fn stop_conversation_mut(
}; };
let mut partner_mut = (*partner).clone(); let mut partner_mut = (*partner).clone();
apply_conversation_buff(trans, &mut partner_mut).await?;
apply_conversation_buff(trans, participant).await?;
participant.active_conversation = None; participant.active_conversation = None;
partner_mut.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?; trans.save_item_model(&partner_mut).await?;
broadcast_to_room( broadcast_to_room(

View File

@ -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<Option<time::Duration>> {
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;

View File

@ -61,7 +61,12 @@ pub fn journal_types() -> &'static BTreeMap<JournalType, JournalData> {
name: "Carked it", 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.", 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 xp: 150
}) }),
(JournalType::SharedWithPlayer, JournalData {
name: "Learned to share",
details: "sharing knowledge in a conversation [with another player]",
xp: 200,
}),
).into_iter().collect()); ).into_iter().collect());
} }

View File

@ -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'))); 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. 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;