From d25f6cc5df2db93489d7477a792f6123f3f0f539 Mon Sep 17 00:00:00 2001 From: David Goldstein Date: Mon, 26 Oct 2026 16:23:45 -0400 Subject: [PATCH] Row 202: a mob's HP bar (SHOW_MONSTER_HP 0xFA) goes to the hitter's party on the map as well (Cosmic's broadcastMobHpBar), so a party Dojo's monster gauge follows every member's hits --- agent_docs/dojo.md | 5 ++-- integration-harness/tests/party_hunting.rs | 12 +++++++--- net/src/packet/build/world/mob.rs | 2 +- runtime/src/actor/field.rs | 27 ++++++++++++++-------- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/agent_docs/dojo.md b/agent_docs/dojo.md index 69d381f87..a1a2ec8e2 100644 --- a/agent_docs/dojo.md +++ b/agent_docs/dojo.md @@ -375,8 +375,9 @@ points: field clock down. STOP_CLOCK leaves the HUD's clock be (v83 0x53184a only closes the field clock's window). -The server sends SHOW_MOB_HP only to the player who hit the mob, so in -the party Dojo a member's monster gauge moves only with their own hits. +The server sends SHOW_MOB_HP to the player who hit the mob and to their +party on the map (Cosmic's rule), so in the party Dojo every member's +monster gauge moves with anyone's hits. The energy bar sits at the left: `muruengRaid/energy`, its frame at (20, 130) with the dark `Gage` strip stretched over the unfilled part of diff --git a/integration-harness/tests/party_hunting.rs b/integration-harness/tests/party_hunting.rs index 82587ccb9..456e0b264 100644 --- a/integration-harness/tests/party_hunting.rs +++ b/integration-harness/tests/party_hunting.rs @@ -1,6 +1,6 @@ -//! Parties: inviting, joining, party HP bars, sharing a kill's EXP on -//! Snail Hunting Ground I (40000), and its drops, party chat, and -//! disbanding. +//! Parties: inviting, joining, party HP bars, a mob's HP bar from a +//! member's hit, sharing a kill's EXP on Snail Hunting Ground I (40000), +//! and its drops, party chat, and disbanding. use integration_harness::hunting::{attack, pickup}; use integration_harness::login_to_world_session; @@ -103,6 +103,12 @@ async fn a_party_shares_hp_bars_exp_and_chat() { until(&mut leader.connection, &mut l, |t| !t.gains.is_empty()).await; until(&mut member.connection, &mut m, |t| !t.gains.is_empty()).await; assert_eq!((l.gains[0], m.gains[0]), (2, 1)); + // The member saw the snail's HP bar from the leader's first hit. + assert!( + m.field.hp_bars.iter().any(|&(oid, _)| oid == snail), + "{:?}", + m.field.hp_bars + ); // Its drops (in the harness, at least a card) are the party's for now: // the member may take one at once. diff --git a/net/src/packet/build/world/mob.rs b/net/src/packet/build/world/mob.rs index f70360e6a..6b59cb7e8 100644 --- a/net/src/packet/build/world/mob.rs +++ b/net/src/packet/build/world/mob.rs @@ -232,7 +232,7 @@ pub fn build_kill_mob(object_id: i32, animation: u8) -> Result Result { let mut p = Packet::new_empty(); p.write_short(SendOpcode::ShowMonsterHp as i16)?; diff --git a/runtime/src/actor/field.rs b/runtime/src/actor/field.rs index adbfb7307..7f131ef02 100644 --- a/runtime/src/actor/field.rs +++ b/runtime/src/actor/field.rs @@ -536,12 +536,7 @@ impl FieldActor { self.apply_cast(&cast, now); } for hurt in self.mobs.hurt_by_statuses(now) { - if let (Some(occupant), Ok(packet)) = ( - self.occupants.get(&hurt.from), - build_mob_hp(hurt.object_id, hurt.percent), - ) { - occupant.handle.send_packet(packet); - } + self.show_mob_hp(hurt.from, hurt.object_id, hurt.percent); self.show_boss_bar(self.mobs.bar_for(hurt.object_id)); } self.mobs.recover(now); @@ -1948,9 +1943,7 @@ impl FieldActor { massacre.misses += 1; } Some(Ok(percent)) => { - if let Ok(packet) = build_mob_hp(object_id, percent) { - attacker.send_packet(packet); - } + self.show_mob_hp(from, object_id, percent); if damage > 0 { self.show_boss_bar(self.mobs.bar_for(object_id)); } @@ -5162,6 +5155,22 @@ impl FieldActor { } } + /// The HP bar of a mob `from` hurt, for them and their party here + /// (Cosmic's rule), so a party's Dojo gauge follows every member's hits. + fn show_mob_hp(&self, from: ClientId, object_id: i32, percent: u8) { + let Some(party) = self.occupants.get(&from).map(|o| o.party) else { + return; + }; + let Ok(packet) = build_mob_hp(object_id, percent) else { + return; + }; + for (&client_id, occupant) in &self.occupants { + if client_id == from || (party.is_some() && occupant.party == party) { + occupant.handle.send_packet(packet.clone()); + } + } + } + /// Send what `from` did to the others here who see them. fn broadcast_by(&self, from: ClientId, packet: Packet) { self.broadcast_seen_by(from, Some(from), packet);