Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent_docs/map-travel.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ nothing. A script returns effects that the client actor sends:
| `enterWitch` | Leafre's nest into the Cave of Black Witches once 20404 is completed: `924010200` after 20407, `924010100` after 20406, else `924010000`, portal `out00`; else a pink line |
| `TD_MC_enterboss1` | The castle tower's way to the Wedding Hall entrance: portal sound, warp to `106021401` `out00`. Cosmic starts a fight with the Yeti brothers instead while 2330 is taken and they aren't all beaten; that event isn't in, so the way is open to everyone |
| `secretDoor` | Magatia's lab doors (`261010000`, `261020200` `secret00`) once 3360 is taken or done: portal sound, warp to `261030000` `sp_jenu` (from Zenumist's) or `sp_alca`; else a pink line. Cosmic's password prompt while 3360 is taken is left out: the quest's start script that sets the password isn't run |
| `magatia_dark0` | The dark pipe on `261020600` (`up00`) into Yulete's lab: portal sound, warp to `926130000` `out00`, once Yulete's traces in the Romeo and Juliet party quest have shown the player the way (marker 32600, `party-quests.md`; Cosmic completes quest 7770, which isn't in `Quest.nx`); else a pink line. The lab's quest rooms (`q3366in`-`q3368in` on `926130100`) aren't in |
| `curseforest` | The Forest of Evil I's hidden portal (`100040105` `secret00`) into the Cursed Forest, for players with 2224 or 2226 taken or 2227 done: portal sound, warp to `910100001` (after 2227) or `910100000`, portal `out00`, from 17:00 until 7:00 by the stations' clock (route time, UTC); by day, or off those quests, a pink line |
| `elevator` | The Helios Tower's floors (`222020100`, `222020200`, `in00`): portal sound and into the floor's waiting room (`222020110`, `222020210`, portal 0) while the elevator's car is docked there, else a pink line; the car takes the waiting room up or down (`ships.md`) |
| `rienTutor8` | Rien's way out (`140000000` `west00`): portal sound, warp to `140010000` `east00`; an Aran beginner (job 2000) on Basic Fitness Training 1-3 (21015-21017) stays, with a chat line (SHOW_STATUS_INFO 9, `Effect::InfoText`) saying which way the training lies |
Expand Down
5 changes: 3 additions & 2 deletions agent_docs/party-quests.md
Original file line number Diff line number Diff line change
Expand Up @@ -1356,8 +1356,9 @@ Yulete's traces (2112001, 2112011) in x00500, who send them on in turn.
In the last room Romeo & Juliet (2112018) thank each member and give
one of 23 rewards (`events::MAGATIA_REWARDS`, Cosmic's
`setEventRewards`), which needs a free slot in each of the Equip and Use
inventories, on the way back to the side's room. Cosmic's traces also complete quest 7770; ours don't
yet.
inventories, on the way back to the side's room. The traces also open
the dark pipe into Yulete's lab to the member (marker 32600, for Cosmic's
quest 7770, which `Quest.nx` lacks; see `map-travel.md`, `magatia_dark0`).

Inside, Romeo or Juliet asks whether a member would rather retreat
(`magatia.romeo.retreat`, `magatia.juliet.retreat`) and sends them to the
Expand Down
1 change: 1 addition & 0 deletions game-data/placeholder_text.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,7 @@
"magatia.thanks.rehab" = "[placeholder] Thanks to you we are together again, and Yulete will be helped back onto the right path."
"magatia.thanks.jail" = "[placeholder] Thanks to you we are together again, and Yulete will be jailed."
"magatia.thanks.full" = "[placeholder] Free a slot in each of your inventories first."
"magatia.pipe_dark" = "[placeholder] It is too dark in the pipe to go in."
"pirate.menu" = "[placeholder] The Lord Pirate has taken Wu Yang, my son, onto his ship. Will your party board it and bring him back?"
"pirate.enter" = "[placeholder] Board the Pirate Ship."
"pirate.about" = "[placeholder] What waits on the ship?"
Expand Down
1 change: 1 addition & 0 deletions game-data/server_content.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,7 @@ unsourced = [
"magatia.thanks.rehab",
"magatia.thanks.jail",
"magatia.thanks.full",
"magatia.pipe_dark",
"pirate.menu",
"pirate.enter",
"pirate.about",
Expand Down
2 changes: 2 additions & 0 deletions game-data/src/server_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,7 @@ pub mod keys {
pub const MAGATIA_THANKS_REHAB: &str = "magatia.thanks.rehab";
pub const MAGATIA_THANKS_JAIL: &str = "magatia.thanks.jail";
pub const MAGATIA_THANKS_FULL: &str = "magatia.thanks.full";
pub const MAGATIA_PIPE_DARK: &str = "magatia.pipe_dark";
pub const PIRATE_MENU: &str = "pirate.menu";
pub const PIRATE_ENTER: &str = "pirate.enter";
pub const PIRATE_ABOUT: &str = "pirate.about";
Expand Down Expand Up @@ -3117,6 +3118,7 @@ mod tests {
keys::MAGATIA_THANKS_REHAB,
keys::MAGATIA_THANKS_JAIL,
keys::MAGATIA_THANKS_FULL,
keys::MAGATIA_PIPE_DARK,
keys::PIRATE_MENU,
keys::PIRATE_ENTER,
keys::PIRATE_ABOUT,
Expand Down
87 changes: 79 additions & 8 deletions integration-harness/tests/magatia_boss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
//! run, paying each member by whether the escort fainted, sends the guards
//! away, and brings out Romeo or Juliet, who give the side's gift and send
//! each member on; in the last room each is thanked and rewarded on the
//! way home.
//! way home. Yulete's traces open the dark pipe off Magatia into his lab
//! to whoever they met.

use game_data::server_content::keys;
use integration_harness::events::talk_to;
Expand All @@ -14,7 +15,7 @@ use integration_harness::quests::answer;
use integration_harness::trace::{read_for, send, until, warp, Trace};
use integration_harness::WorldSession;
use net::events::{EventKind, MagatiaSide, MAGATIA_REWARDS};
use net::npc_scripts::{magatia_hub_host, yulete, JULIET, MAGATIA_THANKS, ROMEO};
use net::npc_scripts::{magatia_hub_host, traces, yulete, JULIET, MAGATIA_THANKS, ROMEO};
use net::packet::op::RecvOpcode;
use packet::io::write::PktWrite;
use packet::Packet;
Expand All @@ -23,6 +24,10 @@ use tokio::time::Duration;
/// The boss room's guards.
const GUARD: i32 = 9300150;
const PINK: u8 = 5;
/// Where the dark pipe (`up00`, running `magatia_dark0`) is, and where it
/// leads: Yulete's lab, by its way out (`out00`).
const PIPE_MAP: i32 = 261020600;
const YULETE_LAB: i32 = 926130000;

fn text(key: &str) -> String {
net::content::text().get(key, &[])
Expand Down Expand Up @@ -69,6 +74,37 @@ fn mob_damage_friendly(attacker: i32, character_id: i32, victim: i32) -> Packet
p
}

/// CHANGE_MAP_SPECIAL: press Up on the scripted portal `name`.
fn enter(name: &str) -> Packet {
let mut p = Packet::new_empty();
p.write_short(RecvOpcode::ChangeMapSpecial as i16).unwrap();
p.write_byte(0).unwrap();
p.write_str_with_length(name).unwrap();
p.write_short(0).unwrap();
p
}

/// `player` goes to the dark pipe and presses Up on it; what came of it.
async fn into_the_pipe(player: &mut WorldSession) -> Trace {
let mut seen = Trace::default();
let c = &mut player.connection;
assert_eq!(warp(c, PIPE_MAP, "", &mut seen).await, PIPE_MAP);
let portal = maps()
.field(PIPE_MAP)
.unwrap()
.portal_by_name("up00")
.unwrap();
send(c, walk_to(portal.x as i16, portal.y as i16)).await;
let mut seen = Trace::default();
send(c, enter("up00")).await;
until(c, &mut seen, |t| {
!t.warps.is_empty() || !t.broadcasts.is_empty()
})
.await;
read_for(c, &mut seen, 300).await;
seen
}

/// The leader goes from the corridor into `side`'s boss room and waits
/// for Yulete, the escort and the guards; what was seen.
async fn into_the_lab(leader: &mut WorldSession, side: MagatiaSide) -> Trace {
Expand All @@ -85,13 +121,8 @@ async fn into_the_lab(leader: &mut WorldSession, side: MagatiaSide) -> Trace {
.portal_by_name(name)
.unwrap();
send(c, walk_to(portal.x as i16, portal.y as i16)).await;
let mut p = Packet::new_empty();
p.write_short(RecvOpcode::ChangeMapSpecial as i16).unwrap();
p.write_byte(0).unwrap();
p.write_str_with_length(name).unwrap();
p.write_short(0).unwrap();
let mut seen = Trace::default();
send(c, p).await;
send(c, enter(name)).await;
until(c, &mut seen, |t| {
t.warps.contains(&lab)
&& t.field.npcs.iter().any(|&(_, id)| id == yulete(side))
Expand Down Expand Up @@ -227,6 +258,35 @@ async fn the_frankenroid_falls_and_romeo_sends_the_party_home_with_gifts() {
);
send(&mut team[0].connection, answer(0, 1, None)).await;

// Yulete's traces, met in their room, send a member on to the last
// room, where they are thanked on the way home; the dark pipe then
// leads them into his lab.
let member = &mut team[1];
let mut met = Trace::default();
let c = &mut member.connection;
let room = side.traces_room();
assert_eq!(warp(c, room, "", &mut met).await, room);
until(c, &mut met, |t| {
t.field.npcs.iter().any(|&(_, id)| id == traces(side))
})
.await;
let asked = told(member, talk_to(npc(&met, traces(side)))).await;
assert_eq!(asked.prompts, [(traces(side), 4)], "{asked:?}");
let sorry = told(member, answer(4, 1, Some(0))).await;
assert_eq!(
sorry.prompt_texts,
[text(keys::MAGATIA_TRACES_SORRY)],
"{sorry:?}"
);
let mut on = Trace::default();
let c = &mut member.connection;
send(c, answer(0, 1, None)).await;
until(c, &mut on, |t| t.warps.contains(&side.thanks_room())).await;
thanked(member, side, on, keys::MAGATIA_THANKS_REHAB).await;
let piped = into_the_pipe(member).await;
assert_eq!(piped.warps, [YULETE_LAB], "{piped:?}");
assert_eq!(piped.portal_sounds, 1, "{piped:?}");

// Romeo gives the gift and, Yulete not having been caught, sends the
// leader straight to the last room.
let host = npc(&seen, magatia_hub_host(side));
Expand All @@ -249,6 +309,17 @@ async fn the_frankenroid_falls_and_romeo_sends_the_party_home_with_gifts() {
thanked(&mut team[0], side, on, keys::MAGATIA_THANKS_REHAB).await;
}

#[tokio::test]
async fn the_dark_pipe_is_too_dark_for_those_the_traces_never_met() {
let mut player = armed_in(PIPE_MAP, 75).await;
let dark = into_the_pipe(&mut player).await;
let line = (PINK, text(keys::MAGATIA_PIPE_DARK));
assert!(
dark.warps.is_empty() && dark.broadcasts.contains(&line),
"{dark:?}"
);
}

#[tokio::test]
async fn juliet_fainting_costs_the_run_its_gifts_and_some_exp() {
let side = MagatiaSide::Alcadno;
Expand Down
20 changes: 17 additions & 3 deletions net/src/npc_scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,10 @@ pub(crate) const ZAKUM_APPROVED: i16 = 32400;
/// Adobis forged the player's Eyes of Fire: the trials are done (upstream
/// quest 100201).
pub(crate) const ZAKUM_TRIALS_DONE: i16 = 32401;
/// Yulete's traces have shown the player the way into his lab: the pipe
/// beyond Magatia's Alcadno side leads there (upstream quest 7770, which
/// Quest.nx lacks).
pub(crate) const YULETE_LAB_OPENED: i16 = 32600;
const DOOR_OF_DIMENSION: i32 = 1061009;
const LORD_JONATHAN: i32 = 1092019;
/// How long a Corsair has to find the real Bart (Cosmic's `4jaerial`).
Expand Down Expand Up @@ -7538,11 +7542,15 @@ fn magatia_saved(side: MagatiaSide, escort_failed: bool, caught: bool) -> NpcScr
}

/// Yulete's traces (Cosmic `2112001`, `2112011`): beaten, he is cheered
/// up, says he is sorry, and sends the member on to the last room.
/// up, says he is sorry, and sends the member on to the last room, the
/// way into his lab open to them from now on (`YULETE_LAB_OPENED`).
fn yulete_traces(side: MagatiaSide) -> NpcScript {
let on = vec![
Page::Say(text(keys::MAGATIA_TRACES_SORRY)),
Page::Done(vec![Effect::WarpToPortal(side.thanks_room(), 0)]),
Page::Done(vec![
Effect::Mark(YULETE_LAB_OPENED),
Effect::WarpToPortal(side.thanks_room(), 0),
]),
];
NpcScript::new(
traces(side),
Expand Down Expand Up @@ -15910,7 +15918,13 @@ mod tests {
let sorry = traced.respond(ScriptInput::Select(0)).unwrap();
assert_eq!(page_text(&sorry), text(keys::MAGATIA_TRACES_SORRY));
let on = traced.respond(ScriptInput::Next).unwrap();
assert_eq!(on.effects, [Effect::WarpToPortal(thanks_room, 0)]);
assert_eq!(
on.effects,
[
Effect::Mark(YULETE_LAB_OPENED),
Effect::WarpToPortal(thanks_room, 0)
]
);

// The thanks, then one of the rewards and the way home.
let lobby = EventKind::Magatia(side).event().lobby;
Expand Down
30 changes: 30 additions & 0 deletions net/src/scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const THORN_REMOVER_QUEST: i16 = 2324;
/// Set once a Killer Mushroom Spore opened the `obstacle` portal (Cosmic
/// starts the unknown quest 100202).
const OBSTACLE_OPENED: i16 = 32500;
/// Yulete's lab, the dark pipe's end, and its way out to Magatia
/// (`out00`), where the pipe leads.
const YULETE_LAB: i32 = 926130000;
const YULETE_LAB_WAY_OUT: u8 = 2;
/// How near a scripted item's portal the player must stand.
const ITEM_REACH: f64 = 210.0;
/// Kerning City's subway: Line 1's first area, and its second, where the
Expand Down Expand Up @@ -1343,6 +1347,12 @@ pub fn portal(script: &str, player: &Player) -> Option<ScriptOutcome> {
let side = crate::events::MagatiaSide::of(player.stats.map_id)?;
through(Effect::WarpToPortal(side.stages()[6], 0))
}
// The dark pipe off Magatia's Alcadno side, into Yulete's lab once
// his traces have shown the way (Cosmic `magatia_dark0`, quest 7770).
"magatia_dark0" if quest(npc_scripts::YULETE_LAB_OPENED) == QuestState::Started => {
through(Effect::WarpToPortal(YULETE_LAB, YULETE_LAB_WAY_OUT))
}
"magatia_dark0" => refused(keys::MAGATIA_PIPE_DARK),
// The hub's doors into the rooms beside it, once a Card Key opened
// them, those rooms' ways back, once a door opened them too, and
// the hub's way on, once Romeo or Juliet opened it for the files
Expand Down Expand Up @@ -3255,6 +3265,26 @@ mod tests {
assert!(portal("rnj1_pt00", &player).is_none());
}

#[test]
fn the_dark_pipe_leads_into_yuletes_lab_once_his_traces_showed_the_way() {
let mut player = player_with(&[]);
player.stats.map_id = 261020600;
let dark = portal("magatia_dark0", &player).unwrap();
let line = content::text().get(keys::MAGATIA_PIPE_DARK, &[]);
assert_eq!(dark.effects, [Effect::Pink(line)]);
assert!(!dark.block);

player.quests.mark(npc_scripts::YULETE_LAB_OPENED);
let open = portal("magatia_dark0", &player).unwrap();
assert_eq!(
open.effects,
[Effect::PortalSound, Effect::WarpToPortal(926130000, 2)]
);
// `out00`, its way out to Magatia.
let lab = crate::game_data::get().unwrap().field(926130000).unwrap();
assert_eq!(lab.portal_by_name("out00").unwrap().id, 2);
}

#[test]
fn a_magatia_hubs_ways_wait_for_their_doors() {
use crate::events::MagatiaSide;
Expand Down
Loading