diff --git a/agent_docs/quests.md b/agent_docs/quests.md index 79cab853..dcf298fe 100644 --- a/agent_docs/quests.md +++ b/agent_docs/quests.md @@ -177,7 +177,7 @@ itself ("Etc item inventory is full."). `startscript`/ `endscript` are allowed only for scripts the server implements (`game_data::quests::SCRIPTS`: `q1021s`, `q1021e`, Bartos's secret `q4647e`, Garnox's pet evolutions `q8185e`, `q8189e` and `q4659e` (see -[Pets](./pets.md)), Meren's Maker lessons, Dyle's informants, the baby dragon and Captain Al's family lessons below); those steps go through +[Pets](./pets.md)), Meren's Maker lessons, Dyle's informants, Reef, the baby dragon and Captain Al's family lessons below); those steps go through QUEST_ACTION 4/5 and the server drives the dialogue (see [NPC dialogue](./npc-dialogue.md)). `money` pays (or, if negative, takes) mesos on completion; a quest that takes mesos needs them held, or the @@ -260,6 +260,12 @@ list. `game_data::quests::FINISHED_BY_START_SCRIPT` lets these four through without a completion NPC. The can's and the informants' lines are unsourced and stay placeholder (`dyle.*`). +"Reef's Gratitude" (2228) works the same way: once Faust is beaten (2227), +Reef (1032108, in 910100001) thanks the player through a start script +(`q2228s`) that completes the quest at once and gives the data's 8 fame +(`Act/1 pop`, through `Effect::GainFame`, since `FinishQuest` gives no +`Act/1` rewards). Its line is a placeholder (`reef.thanks`). + "The Remnants of Horned Tail" (3714) works the same way. Nine Spirit's Baby Dragon (2081011), in the nest past Leafre's `dragonNest` portal (240040612, open once 3706 is done), has a start script (`q3714s`) and no end. It diff --git a/game-data/placeholder_text.toml b/game-data/placeholder_text.toml index 8274b7e4..cb8c77ff 100644 --- a/game-data/placeholder_text.toml +++ b/game-data/placeholder_text.toml @@ -771,6 +771,7 @@ "nine_spirit.baby.hatched" = "[placeholder] The egg hatches on the nest. The baby dragon coughs up a Dragon Stone and asks you to take it away." "nine_spirit.baby.stone_held" = "[placeholder] The Dragon Stone in your bag glows brighter here, and the baby dragon glares at it." "nine_spirit.baby.full" = "[placeholder] You need a free USE slot for the Dragon Stone." +"reef.thanks" = "[placeholder] Reef thanks you for beating Faust. His spirit can rest at last." "family.captain_al.junior.done" = "[placeholder] Captain Al is pleased you have taken a Junior, and teaches you how a Senior earns Rep." "family.captain_al.junior.not_yet" = "[placeholder] Captain Al says you have no Junior yet. Add one from the Family window." "family.captain_al.rep.done" = "[placeholder] Captain Al sees you have earned 1,000 Rep, and praises how you look after your Juniors." diff --git a/game-data/server_content.toml b/game-data/server_content.toml index 0b9ac79d..49e44bef 100644 --- a/game-data/server_content.toml +++ b/game-data/server_content.toml @@ -1444,6 +1444,7 @@ unsourced = [ "nine_spirit.baby.hatched", "nine_spirit.baby.stone_held", "nine_spirit.baby.full", + "reef.thanks", "family.captain_al.junior.done", "family.captain_al.junior.not_yet", "family.captain_al.rep.done", diff --git a/game-data/src/quests.rs b/game-data/src/quests.rs index 22d02b11..753ec05f 100644 --- a/game-data/src/quests.rs +++ b/game-data/src/quests.rs @@ -64,15 +64,16 @@ pub const INERT_END_REQUIREMENTS: &[&str] = &["start", "end"]; /// Quest scripts (`startscript`/`endscript`) the server implements. A quest /// naming any other script is unsupported. pub const SCRIPTS: &[&str] = &[ - "q1021s", "q1021e", "q2214e", "q2215e", "q2232e", "q2233e", "q2234e", "q2216s", "q2217s", - "q2218s", "q2219s", "q3714s", "q4647e", "q4659e", "q6030e", "q6031e", "q6032e", "q6033e", - "q6036e", "q8185e", "q8189e", + "q1021s", "q1021e", "q2214e", "q2215e", "q2228s", "q2232e", "q2233e", "q2234e", "q2216s", + "q2217s", "q2218s", "q2219s", "q3714s", "q4647e", "q4659e", "q6030e", "q6031e", "q6032e", + "q6033e", "q6036e", "q8185e", "q8189e", ]; /// Quests with nothing to complete them by (an empty `Check/1`) whose start /// script completes them as it starts them (`Effect::FinishQuest`): JM's -/// informants (2216-2219), each heard from once, and the Remnants of Horned -/// Tail (3714), once for each Nine Spirit's Egg. -pub const FINISHED_BY_START_SCRIPT: &[i16] = &[2216, 2217, 2218, 2219, 3714]; +/// informants (2216-2219), each heard from once, Reef's thanks for Faust +/// (2228), and the Remnants of Horned Tail (3714), once for each Nine +/// Spirit's Egg. +pub const FINISHED_BY_START_SCRIPT: &[i16] = &[2216, 2217, 2218, 2219, 2228, 3714]; /// Meren's Maker classes (quests 6030-6032) and the quest they make up /// (6029) share 6029's quest-info string, one digit per class: `Act/0 info` /// starts it at "000", a class starts only while its own digit is 0 diff --git a/game-data/src/server_content.rs b/game-data/src/server_content.rs index ab22ccb8..5040b86d 100644 --- a/game-data/src/server_content.rs +++ b/game-data/src/server_content.rs @@ -782,6 +782,10 @@ pub mod keys { pub const NINE_SPIRIT_STONE_HELD: &str = "nine_spirit.baby.stone_held"; pub const NINE_SPIRIT_FULL: &str = "nine_spirit.baby.full"; + /// Reef's Gratitude (quest 2228, `q2228s`): Reef (NPC 1032108) thanks + /// the player for beating Faust. No licensed script has the text. + pub const REEF_THANKS: &str = "reef.thanks"; + /// Captain Al's lessons in leading a family (quests 2232-2234), each /// ended by his script (NPC 1002103, `q2232e`-`q2234e`): done, then not /// yet, for a junior taken, 1,000 Rep earned, and 2,000 earned with the @@ -3004,6 +3008,7 @@ mod tests { .chain(&keys::CAPTAIN_AL_REP) .chain(&keys::CAPTAIN_AL_ENTITLEMENT) .chain(&[ + keys::REEF_THANKS, keys::NINE_SPIRIT_HATCHED, keys::NINE_SPIRIT_STONE_HELD, keys::NINE_SPIRIT_FULL, diff --git a/game-data/supported_quests.txt b/game-data/supported_quests.txt index 8939c3b1..59b5ad13 100644 --- a/game-data/supported_quests.txt +++ b/game-data/supported_quests.txt @@ -256,6 +256,7 @@ 2225 2226 2227 +2228 2229 2231 2232 diff --git a/integration-harness/tests/reef_gratitude.rs b/integration-harness/tests/reef_gratitude.rs new file mode 100644 index 00000000..b463cf05 --- /dev/null +++ b/integration-harness/tests/reef_gratitude.rs @@ -0,0 +1,48 @@ +//! Reef's Gratitude (2228): once Faust is beaten (2227), Reef's start script +//! thanks the player, which completes the quest at once, with the data's 8 +//! fame. + +use integration_harness::database::{set_job, set_quest}; +use integration_harness::hunting::walk_to; +use integration_harness::preconditions::load_harness_config_or_fail; +use integration_harness::quests::{answer, build_quest_action, SCRIPT_START}; +use integration_harness::trace::{step, warp, Trace}; +use integration_harness::{login_to_world_session, relogin}; +use std::time::Duration; + +const QUEST: i16 = 2228; +const REEF: i32 = 1_032_108; +const REEFS_MAP: i32 = 910_100_001; + +#[tokio::test] +async fn reef_thanks_the_player_with_eight_fame() { + let config = load_harness_config_or_fail().await; + let m = config.character_name.clone(); + let id = login_to_world_session(&config) + .await + .expect("first login failed") + .character_id; + tokio::time::sleep(Duration::from_millis(500)).await; + set_job(id, 200, 45, 100) + .await + .expect("a level 45 magician"); + set_quest(id, 2227, 2, "").await.expect("Faust beaten"); + + let mut session = relogin(&config).await; + let c = &mut session.connection; + let mut trace = Trace::default(); + assert_eq!(warp(c, REEFS_MAP, "", &mut trace).await, REEFS_MAP); + step(c, &m, walk_to(33, 209)).await; + let t = step(c, &m, build_quest_action(SCRIPT_START, QUEST, REEF)).await; + assert_eq!(t.prompts, [(REEF, 0)], "Reef speaks"); + let t = step(c, &m, answer(0, 1, None)).await; + assert_eq!(t.progress, [(QUEST, 2)], "done at once"); + assert_eq!(t.fame_gains, [8]); + // Once. + let t = step(c, &m, build_quest_action(SCRIPT_START, QUEST, REEF)).await; + assert!(t.prompts.is_empty() && t.progress.is_empty()); + drop(session); + + let restored = relogin(&config).await; + assert_eq!(restored.quests.get(&QUEST), Some(&2)); +} diff --git a/net/src/npc_scripts.rs b/net/src/npc_scripts.rs index 8c18bdf6..52e820c2 100644 --- a/net/src/npc_scripts.rs +++ b/net/src/npc_scripts.rs @@ -2341,6 +2341,7 @@ impl NpcScript { "q2217s" => informant(player, 1), "q2218s" => informant(player, 2), "q2219s" => informant(player, 3), + "q2228s" => Some(reef()), "q3714s" => Some(baby_dragon(player)), "q6030e" => Some(maker_class(CARSON, keys::MAKER_ALCHEMY, 6030)), "q6031e" => Some(maker_class(HUGHES, keys::MAKER_SCIENCE, 6031)), @@ -2788,6 +2789,17 @@ fn trash_can(player: &Player, lost: bool, hour: u8) -> NpcScript { script } +/// `q2228s`, Reef's Gratitude: Reef's spirit, at rest once Faust is beaten +/// (2227), thanks the player, which completes the quest as it starts, with +/// the data's 8 fame (`Act/1 pop`, which a start does not give). +fn reef() -> NpcScript { + let mut script = NpcScript::new(REEF, vec![Page::Say(text(keys::REEF_THANKS))]); + script.on_end = vec![Effect::FinishQuest(2228), Effect::GainFame(8)]; + script +} + +/// Reef, in 910100001. +const REEF: i32 = 1032108; /// Captain Al, in Lith Harbor (104000000) and Orbis Park (200000200). const CAPTAIN_AL: i32 = 1002103; /// Captain Al's end scripts ([`NpcScript::for_family_quest`]). @@ -9995,6 +10007,15 @@ mod tests { assert_eq!(read_through(early), (1052006, heard(2219))); } + #[test] + fn reef_thanks_the_player_with_eight_fame() { + let script = NpcScript::for_quest("q2228s", &with_quests(&[(2227, true)])).unwrap(); + assert_eq!( + read_through(script), + (REEF, vec![Effect::FinishQuest(2228), Effect::GainFame(8)]) + ); + } + #[test] fn the_baby_dragon_trades_each_egg_for_a_dragon_stone() { let mut player = with_quests(&[(3706, true)]); diff --git a/net/src/scripts.rs b/net/src/scripts.rs index 4a503756..73733a9e 100644 --- a/net/src/scripts.rs +++ b/net/src/scripts.rs @@ -475,6 +475,8 @@ pub enum Effect { /// Up to the most a player may hold. GainMeso(i32), GainExp(i32), + /// Fame, shown in the chat log as a quest's is. + GainFame(i16), StartQuest(i16), CompleteQuest(i16), /// Start a quest not yet started and complete it at once: a start script diff --git a/runtime/src/actor/client/npc_script.rs b/runtime/src/actor/client/npc_script.rs index bcd3b99f..0df81e7d 100644 --- a/runtime/src/actor/client/npc_script.rs +++ b/runtime/src/actor/client/npc_script.rs @@ -379,6 +379,15 @@ impl ClientActor { gain_exp(&mut next, *amount, &mut packets)?; changed = true; } + Effect::GainFame(amount) => { + next.stats.fame = next.stats.fame.saturating_add(*amount); + changed = true; + packets.push(stat::build_stat_update( + false, + &[StatUpdate::Fame(next.stats.fame)], + )?); + packets.push(quest::build_fame_gain_in_chat(*amount)?); + } Effect::TakeFirstJob(job) | Effect::TakeSecondJob(job) | Effect::TakeThirdJob(job)