Make dead NPCs auto-respawn.
This commit is contained in:
parent
618d88bb06
commit
e6e712e255
@ -61,6 +61,6 @@ The latest schema is under `schema`.
|
||||
Create a user with a secret password, and username `blast`. Create a production database called `blast`.
|
||||
|
||||
To get to the latest schema:
|
||||
* Run `psql <schema/schema.sql` to create the temporary `blast_schemaonly` database.
|
||||
* Run `psql -d template1 <schema/schema.sql` to create the temporary `blast_schemaonly` database.
|
||||
* Run `migra "postgres:///blast" "postgres:///blast_schemaonly" > /tmp/update.sql`
|
||||
* Check `/tmp/update.sql` and if it looks good, apply it with `psql -u blast -d blast </tmp/update.sql`
|
||||
* Check `/tmp/update.sql` and if it looks good, apply it with `psql -U blast -d blast </tmp/update.sql`
|
||||
|
@ -440,6 +440,15 @@ impl DBTrans {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_item(self: &Self, item_type: &str, item_code: &str) -> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute("DELETE FROM items WHERE \
|
||||
details->>'item_type' = $2 AND \
|
||||
details->>'item_code' = $3",
|
||||
&[&item_type, &item_code]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_session_for_player(self: &Self, item_code: &str) -> DResult<Option<(ListenerSession, Session)>> {
|
||||
Ok(self.pg_trans()?
|
||||
.query_opt("SELECT u.current_listener, u.current_session, s.details \
|
||||
@ -563,6 +572,10 @@ impl DBTrans {
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn alloc_item_code(&self) -> DResult<i64> {
|
||||
Ok(self.pg_trans()?.query_one("SELECT NEXTVAL('item_seq')", &[]).await?.get(1))
|
||||
}
|
||||
|
||||
pub async fn commit(mut self: Self) -> DResult<()> {
|
||||
let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None));
|
||||
|
@ -363,7 +363,7 @@ impl Default for Item {
|
||||
is_dead: false,
|
||||
is_challenge_attack_only: true,
|
||||
species: SpeciesType::Human,
|
||||
health: 40,
|
||||
health: 24,
|
||||
total_xp: 0,
|
||||
total_stats: BTreeMap::new(),
|
||||
total_skills: BTreeMap::new(),
|
||||
|
@ -15,7 +15,13 @@ pub enum TaskDetails {
|
||||
npc_code: String,
|
||||
say_code: String
|
||||
},
|
||||
AttackTick
|
||||
AttackTick,
|
||||
RecloneNPC {
|
||||
npc_code: String
|
||||
},
|
||||
RotCorpse {
|
||||
corpse_code: String
|
||||
},
|
||||
}
|
||||
impl TaskDetails {
|
||||
pub fn name(self: &Self) -> &'static str {
|
||||
@ -23,7 +29,9 @@ impl TaskDetails {
|
||||
match self {
|
||||
RunQueuedCommand => "RunQueuedCommand",
|
||||
NPCSay { .. } => "NPCSay",
|
||||
AttackTick => "AttackTick"
|
||||
AttackTick => "AttackTick",
|
||||
RecloneNPC { .. } => "RecloneNPC",
|
||||
RotCorpse { .. } => "RotCorpse",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ fn task_handler_registry() -> &'static BTreeMap<&'static str, &'static (dyn Task
|
||||
|| vec!(
|
||||
("RunQueuedCommand", queued_command::HANDLER.clone()),
|
||||
("NPCSay", npc::SAY_HANDLER.clone()),
|
||||
("AttackTick", combat::TASK_HANDLER.clone())
|
||||
("AttackTick", combat::TASK_HANDLER.clone()),
|
||||
("RecloneNPC", npc::RECLONE_HANDLER.clone()),
|
||||
("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()),
|
||||
).into_iter().collect()
|
||||
)
|
||||
}
|
||||
|
@ -159,6 +159,18 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if whom.item_type == "npc" {
|
||||
trans.upsert_task(&Task {
|
||||
meta: TaskMeta {
|
||||
task_code: whom.item_code.clone(),
|
||||
next_scheduled: Utc::now() + chrono::Duration::seconds(120),
|
||||
..Default::default()
|
||||
},
|
||||
details: TaskDetails::RecloneNPC {
|
||||
npc_code: whom.item_code.clone()
|
||||
}
|
||||
}).await?;
|
||||
}
|
||||
|
||||
broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await
|
||||
}
|
||||
@ -289,3 +301,74 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn corpsify_item(trans: &DBTrans, base_item: &Item) -> DResult<()> {
|
||||
let mut new_item = base_item.clone();
|
||||
new_item.item_type = "corpse".to_owned();
|
||||
new_item.item_code = format!("{}", trans.alloc_item_code().await?);
|
||||
trans.save_item_model(&new_item).await?;
|
||||
trans.upsert_task(&Task {
|
||||
meta: TaskMeta {
|
||||
task_code: new_item.item_code.clone(),
|
||||
next_scheduled: Utc::now() + chrono::Duration::minutes(5),
|
||||
..Default::default()
|
||||
},
|
||||
details: TaskDetails::RotCorpse { corpse_code: new_item.item_code.clone() }
|
||||
}).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct NPCRecloneTaskHandler;
|
||||
#[async_trait]
|
||||
impl TaskHandler for NPCRecloneTaskHandler {
|
||||
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||
let npc_code = match &ctx.task.details {
|
||||
TaskDetails::RecloneNPC { npc_code } => npc_code.clone(),
|
||||
_ => Err("Expected RecloneNPC type")?
|
||||
};
|
||||
let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
||||
None => { return Ok(None) },
|
||||
Some(r) => (*r).clone()
|
||||
};
|
||||
|
||||
let npc = match npc_by_code().get(npc_code.as_str()) {
|
||||
None => { return Ok(None) },
|
||||
Some(r) => r
|
||||
};
|
||||
|
||||
if !npc_item.is_dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
corpsify_item(ctx.trans, &npc_item).await?;
|
||||
|
||||
npc_item.is_dead = false;
|
||||
npc_item.health = max_health(&npc_item);
|
||||
npc_item.location = npc.spawn_location.to_owned();
|
||||
ctx.trans.save_item_model(&npc_item).await?;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
pub struct RotCorpseTaskHandler;
|
||||
#[async_trait]
|
||||
impl TaskHandler for RotCorpseTaskHandler {
|
||||
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||
let corpse_code = match &ctx.task.details {
|
||||
TaskDetails::RotCorpse { corpse_code } => corpse_code.clone(),
|
||||
_ => Err("Expected RotCorpse type")?
|
||||
};
|
||||
let corpse = match ctx.trans.find_item_by_type_code("corpse", &corpse_code).await? {
|
||||
None => { return Ok(None) }
|
||||
Some(r) => r
|
||||
};
|
||||
ctx.trans.delete_item("corpse", &corpse_code).await?;
|
||||
let msg_exp = format!("{} rots away to nothing.\n",
|
||||
corpse.display_for_sentence(true, 1, true));
|
||||
let msg_nonexp = format!("{} rots away to nothing.\n",
|
||||
corpse.display_for_sentence(false, 1, true));
|
||||
broadcast_to_room(ctx.trans, &corpse.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
pub static ROT_CORPSE_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &RotCorpseTaskHandler;
|
||||
|
@ -8,6 +8,12 @@ use crate::models::{
|
||||
item::{Item, Pronouns, SkillType},
|
||||
task::{Task, TaskMeta, TaskRecurrence, TaskDetails}
|
||||
};
|
||||
use crate::services::{
|
||||
combat::{
|
||||
max_health,
|
||||
corpsify_item,
|
||||
}
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::collections::BTreeMap;
|
||||
use crate::message_handler::user_commands::{
|
||||
@ -218,3 +224,36 @@ impl TaskHandler for NPCSayTaskHandler {
|
||||
}
|
||||
}
|
||||
pub static SAY_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCSayTaskHandler;
|
||||
|
||||
pub struct NPCRecloneTaskHandler;
|
||||
#[async_trait]
|
||||
impl TaskHandler for NPCRecloneTaskHandler {
|
||||
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||
let npc_code = match &ctx.task.details {
|
||||
TaskDetails::RecloneNPC { npc_code } => npc_code.clone(),
|
||||
_ => Err("Expected RecloneNPC type")?
|
||||
};
|
||||
let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
||||
None => { return Ok(None) },
|
||||
Some(r) => (*r).clone()
|
||||
};
|
||||
|
||||
let npc = match npc_by_code().get(npc_code.as_str()) {
|
||||
None => { return Ok(None) },
|
||||
Some(r) => r
|
||||
};
|
||||
|
||||
if !npc_item.is_dead {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
corpsify_item(ctx.trans, &npc_item).await?;
|
||||
|
||||
npc_item.is_dead = false;
|
||||
npc_item.health = max_health(&npc_item);
|
||||
npc_item.location = npc.spawn_location.to_owned();
|
||||
ctx.trans.save_item_model(&npc_item).await?;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
pub static RECLONE_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCRecloneTaskHandler;
|
||||
|
@ -17,6 +17,8 @@ CREATE TABLE sessions (
|
||||
);
|
||||
CREATE INDEX session_by_listener ON sessions(listener);
|
||||
|
||||
CREATE SEQUENCE item_seq;
|
||||
|
||||
CREATE TABLE items (
|
||||
item_id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
details JSONB NOT NULL
|
||||
|
Loading…
Reference in New Issue
Block a user