diff --git a/crates/horizon-client/src/game/text_displays.rs b/crates/horizon-client/src/game/text_displays.rs index a5545575fd..dcb8850f58 100644 --- a/crates/horizon-client/src/game/text_displays.rs +++ b/crates/horizon-client/src/game/text_displays.rs @@ -348,13 +348,14 @@ impl Game<'_> { let background = self.settings.text_background(0.25); let mut displays = std::mem::take(&mut self.text_displays); for display in displays.values_mut().filter(|d| d.shows == Shows::Text) { - let cached = display.lines.take(); display.last_progress = display.progress(partial as f32); let d = display.shown.blend(&display.from, display.last_progress); let position = display.previous.lerp(display.position, partial); if d.text.is_empty() || !self.display_in_view(camera, position, &d, scale) { + // Out of view: its glyphs stay cached for when it comes back. continue; } + let cached = display.lines.take(); let rotation = std::array::from_fn(|i| { display.old_rotation[i] + angle(display.rotation[i] - display.old_rotation[i]) * partial as f32 @@ -514,6 +515,11 @@ mod tests { d.text = std::sync::Arc::new(horizon_client::text::Text::legacy("§kSecret")); let cache = CachedQuads::new(&atlas, &d, 0); assert!(!cache.matches(&d, 0, atlas.font.epoch(), atlas.revision)); + // A plugin hologram sends `§k` inside a literal JSON string: the font + // resolves it, so its glyphs still change every frame. + d.text = std::sync::Arc::new(horizon_client::text::Text::literal("§kSecret")); + let cache = CachedQuads::new(&atlas, &d, 0); + assert!(!cache.matches(&d, 0, atlas.font.epoch(), atlas.revision)); } #[test] diff --git a/crates/horizon-client/src/text_display.rs b/crates/horizon-client/src/text_display.rs index c737a0e3c7..4408db6211 100644 --- a/crates/horizon-client/src/text_display.rs +++ b/crates/horizon-client/src/text_display.rs @@ -418,7 +418,10 @@ impl Lines { /// Local geometry, independent of the display's interpolated world pose. pub struct CachedQuads { - key: (Arc, i32, i32, i8, u8, u32, u64, u64), + key: (Arc, i32, i32, i8, u8, u32, u64, u64, bool), + /// Drawn differently every frame: obfuscated (`§k`) glyphs, legacy codes + /// included, or an inline object (a head whose skin arrives later). + animated: bool, pub lines: Lines, pub batch: crate::hud::HudBatch, bounds: (glam::Vec3, glam::Vec3), @@ -426,10 +429,7 @@ pub struct CachedQuads { impl CachedQuads { pub fn matches(&self, d: &TextDisplayData, background: u32, epoch: u64, revision: u64) -> bool { - !d.text - .spans - .iter() - .any(|s| s.style.obfuscated == Some(true)) + !self.animated && self.key == ( d.text.clone(), @@ -440,6 +440,7 @@ impl CachedQuads { background, epoch, revision, + crate::lang::bidirectional(), ) } @@ -454,6 +455,12 @@ impl CachedQuads { lo = lo.min(p); hi = hi.max(p); } + let animated = d + .text + .resolved() + .spans + .iter() + .any(|s| s.style.obfuscated == Some(true) || s.style.inline_object.is_some()); Self { key: ( d.text.clone(), @@ -464,7 +471,9 @@ impl CachedQuads { background, atlas.font.epoch(), atlas.revision, + crate::lang::bidirectional(), ), + animated, lines, batch, bounds: (lo, hi),