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
24 changes: 13 additions & 11 deletions pomme-client/src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,8 +879,8 @@ pub struct AppCore {
/// A packed glyph's pixels changed (a head arrived), so the atlas needs
/// rebuilding even though its key set is the same.
inline_atlas_dirty: bool,
/// Whether the spectator menu's faces are in the atlas as packed.
spectator_faces_packed: bool,
/// Whether player faces are in the atlas as packed.
player_faces_packed: bool,
/// The inline-object keys currently packed.
game_dynamic_atlas_keys: HashSet<String>,
}
Expand Down Expand Up @@ -1033,7 +1033,7 @@ impl AppCore {
head_tx,
head_rx,
inline_atlas_dirty: false,
spectator_faces_packed: false,
player_faces_packed: false,
game_dynamic_atlas_keys: HashSet::new(),
}
}
Expand Down Expand Up @@ -1292,13 +1292,14 @@ impl AppCore {
}

/// Loads what the text drew last frame into the shared glyph atlas and
/// rebuilds it when its contents change. Spectator faces share the same
/// texture binding, so they are packed alongside.
/// rebuilds it when its contents change. Player faces (spectator menu,
/// tab list) share the same texture binding, so they are packed alongside
/// while `faces_shown`.
pub fn sync_game_dynamic_atlas(
&mut self,
game: &GameState,
renderer: &mut Renderer,
spectator_active: bool,
faces_shown: bool,
) {
let now = Instant::now();
self.drain_head_results();
Expand Down Expand Up @@ -1360,11 +1361,11 @@ impl AppCore {
.inline_objects
.iter()
.any(|(key, entry)| packable(entry) && !self.game_dynamic_atlas_keys.contains(key))
|| spectator_active != self.spectator_faces_packed
|| (spectator_active && self.player_faces_dirty);
|| faces_shown != self.player_faces_packed
|| (faces_shown && self.player_faces_dirty);
if rebuild {
let mut entries = Vec::<(String, Vec<u8>, u32)>::new();
if spectator_active {
if faces_shown {
entries.extend(
self.player_faces
.iter()
Expand Down Expand Up @@ -1410,8 +1411,8 @@ impl AppCore {
renderer.update_face_atlas(&entries);
}
self.inline_atlas_dirty = false;
self.spectator_faces_packed = spectator_active;
if spectator_active {
self.player_faces_packed = faces_shown;
if faces_shown {
self.player_faces_dirty = false;
}
}
Expand Down Expand Up @@ -2826,6 +2827,7 @@ impl AppCore {
game.recipe_book.reset_for_login();
game.player.entity_id = entity_id;
game.hardcore = hardcore;
game.online_mode = online_mode;
game.show_death_screen = show_death_screen;
// The fresh LocalPlayer starts from supplier defaults.
game.player.attributes = AttributeMap::player();
Expand Down
13 changes: 7 additions & 6 deletions pomme-client/src/app/phases/in_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub struct GameState {
pub death_screen_open: bool,
pub show_death_screen: bool,
pub hardcore: bool,
/// The login packet's `onlineMode`: the tab list shows player heads.
pub online_mode: bool,
pub death_message: String,
pub death_screen_ticks: u32,
pub death_confirm: bool,
Expand Down Expand Up @@ -448,6 +450,7 @@ impl GameState {
death_screen_open: false,
show_death_screen: true,
hardcore: false,
online_mode: false,
death_message: String::new(),
death_screen_ticks: 0,
death_confirm: false,
Expand Down Expand Up @@ -2542,11 +2545,9 @@ pub fn update_game(
core.input.text_capture = game.wants_text_input() || game.chat.is_open();
core.input.menu_capture = game.gui_open() || game.death_screen_open;
core.input.spectator = crate::player::is_spectator(game.player.game_mode);
core.sync_game_dynamic_atlas(
game,
&mut gfx.renderer,
core.input.spectator && game.spectator.is_menu_active(),
);
let faces_shown = (core.input.spectator && game.spectator.is_menu_active())
|| (game.online_mode && core.input.performing_action(input::Action::ViewPlayerList));
core.sync_game_dynamic_atlas(game, &mut gfx.renderer, faces_shown);

// The F3+F4 switcher shows the mouse cursor while open.
let switcher_open = game.game_mode_switcher.is_some();
Expand Down Expand Up @@ -2934,8 +2935,8 @@ pub fn update_game(
sw,
&game.tab_list,
&game.scoreboard,
game.online_mode,
gs,
&|t, s| r.menu_text_width(t, s),
&|spans, s| r.menu_spans_width(spans, s),
);
}
Expand Down
2 changes: 1 addition & 1 deletion pomme-client/src/net/scoreboard_wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ fn styled_color(nbt: &[u8]) -> Option<[f32; 4]> {
/// out-of-range ids fall back to BLACK (`ByIdMap` ZERO).
fn team_color(id: u32) -> u32 {
char::from_digit(id, 16)
.and_then(crate::ui::chat::legacy_rgb)
.and_then(crate::ui::text::legacy_rgb)
.unwrap_or(0x000000)
}

Expand Down
1 change: 0 additions & 1 deletion pomme-client/src/player/tab_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ pub struct PlayerInfoEntry {

#[derive(Clone, Debug)]
pub struct TabListPlayer {
#[allow(dead_code)]
pub uuid: Uuid,
pub name: String,
pub textures: Option<String>,
Expand Down
10 changes: 8 additions & 2 deletions pomme-client/src/renderer/pipelines/menu_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use crate::assets::{AssetIndex, resolve_asset_path};
use crate::renderer::{MAX_FRAMES_IN_FLIGHT, packing, rhi, shader, util};
use crate::ui::font::{FontSources, GLYPH_ATLAS_SIZE, GlyphAtlasPixels, GlyphInfo, GlyphMap};
use crate::ui::text::{InlineObject, TextSpan};
use crate::ui::text::{InlineObject, PREFIX_CODE, TextSpan, font_spans};

const FONT_BYTES: &[u8] = include_bytes!("../fonts/Montserrat-Medium.ttf");
const ICON_FONT_BYTES: &[u8] = include_bytes!("../fonts/fa-solid-900.ttf");
Expand Down Expand Up @@ -1001,6 +1001,11 @@ impl MenuGeometry {
let Some(ref gm) = self.mc_glyph_map else {
return plain_text_width(&self.atlas, text, scale);
};
if text.contains(PREFIX_CODE) {
let mut span = TextSpan::new(text.to_owned(), [1.0; 4]);
span.font = font.map(str::to_owned);
return self.spans_width(&[span], scale);
}
(run_advance(gm, text, font, false, false) * scale / gm.cell_h as f32).ceil()
}

Expand All @@ -1010,7 +1015,7 @@ impl MenuGeometry {
let Some(ref gm) = self.mc_glyph_map else {
return 0.0;
};
let raw: f32 = spans
let raw: f32 = font_spans(spans)
.iter()
.map(|s| {
run_advance(
Expand Down Expand Up @@ -1449,6 +1454,7 @@ impl MenuGeometry {
draw: McTextDraw,
obfuscation_rng: &mut ObfuscationRng,
) {
let spans = &*font_spans(spans);
if let Some(gm) = &self.mc_glyph_map {
let sources = McTextSources {
gm,
Expand Down
116 changes: 2 additions & 114 deletions pomme-client/src/ui/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use crate::net::sender::ChatMark;
use crate::renderer::pipelines::menu_overlay::{MenuElement, SpriteId, TooltipLine};
use crate::ui::text::{
TextSpan, format_component_spans, format_component_spans_with_bindings,
format_component_spans_with_parent_and_bindings, parse_uuid_value, with_alpha,
format_component_spans_with_parent_and_bindings, legacy_format_spans, parse_uuid_value,
with_alpha,
};
use crate::ui::text_edit::{
SystemClipboard, TextFieldState, TextInputEvent, truncate_to_utf16, utf16_len,
Expand Down Expand Up @@ -3146,93 +3147,6 @@ fn slice_spans(spans: &[TextSpan], start: usize, end: usize) -> Vec<TextSpan> {
out
}

fn legacy_color(code: char) -> Option<[f32; 4]> {
legacy_rgb(code).map(common::rgb)
}

/// The color of legacy formatting code `code` (`ChatFormatting`'s `0`-`f`).
pub(crate) fn legacy_rgb(code: char) -> Option<u32> {
Some(match code.to_ascii_lowercase() {
'0' => 0x000000,
'1' => 0x0000aa,
'2' => 0x00aa00,
'3' => 0x00aaaa,
'4' => 0xaa0000,
'5' => 0xaa00aa,
'6' => 0xffaa00,
'7' => 0xaaaaaa,
'8' => 0x555555,
'9' => 0x5555ff,
'a' => 0x55ff55,
'b' => 0x55ffff,
'c' => 0xff5555,
'd' => 0xff55ff,
'e' => 0xffff55,
'f' => 0xffffff,
_ => return None,
})
}

/// Vanilla `StringDecomposer.iterateFormatted` over each span: legacy codes
/// never carry into the next span, whose own style is both the current and
/// the reset style.
fn legacy_format_spans(spans: &[TextSpan], colors_enabled: bool) -> Vec<TextSpan> {
let mut out = Vec::new();
for base in spans {
let mut current = base.with_text(String::new());
let mut buffer = String::new();
let flush = |out: &mut Vec<TextSpan>, current: &TextSpan, buffer: &mut String| {
if !buffer.is_empty() {
out.push(current.with_text(std::mem::take(buffer)));
}
};

let mut chars = base.text.chars();
while let Some(ch) = chars.next() {
if ch != '\u{00a7}' {
buffer.push(ch);
continue;
}
let Some(code) = chars.next() else {
// A trailing section sign ends the iteration, unemitted.
break;
};
let lower = code.to_ascii_lowercase();
let recognized =
legacy_color(lower).is_some() || matches!(lower, 'k' | 'l' | 'm' | 'n' | 'o' | 'r');
if !recognized {
// StringDecomposer consumes even an unknown code pair.
continue;
}
flush(&mut out, &current, &mut buffer);
if !colors_enabled {
// Chat Colors off strips the code but keeps the base style.
continue;
}
if let Some(color) = legacy_color(lower) {
current.color = color;
current.bold = false;
current.italic = false;
current.strikethrough = false;
current.underline = false;
current.obfuscated = false;
continue;
}
match lower {
'k' => current.obfuscated = true,
'l' => current.bold = true,
'm' => current.strikethrough = true,
'n' => current.underline = true,
'o' => current.italic = true,
'r' => current = base.with_text(String::new()),
_ => unreachable!(),
}
}
flush(&mut out, &current, &mut buffer);
}
out
}

/// Word-wraps styled spans to `max_w` gui-space units like vanilla
/// `Font.split`: `width0` measures styled runs at gui-scale 1, so fonts, bold
/// and inline objects count as in `StringSplitter`. Returns one
Expand Down Expand Up @@ -4771,32 +4685,6 @@ mod tests {
assert_eq!(options.render_width_px(), 458.0);
}

#[test]
fn legacy_formatting_matches_vanilla_segment_reset_rules() {
let white = common::WHITE;
let spans = legacy_format_spans(&[span("a§cb§lc§rd", white)], true);
assert_eq!(line_text(&spans), "abcd");
assert_eq!(spans.len(), 4);
assert_eq!(spans[0].color, white);
assert_eq!(spans[1].color, common::rgb(0xff5555));
assert!(!spans[1].bold);
assert_eq!(spans[2].color, common::rgb(0xff5555));
assert!(spans[2].bold);
assert_eq!(spans[3].color, white);
assert!(!spans[3].bold);
}

#[test]
fn chat_colors_off_strips_only_legacy_codes() {
let mut base = span("a§cb§lc§rd", common::rgb(0x55ffff));
base.italic = true;
let spans = legacy_format_spans(&[base], false);
assert_eq!(line_text(&spans), "abcd");
assert!(spans.iter().all(|span| span.color == common::rgb(0x55ffff)));
assert!(spans.iter().all(|span| span.italic));
assert!(spans.iter().all(|span| !span.bold));
}

#[test]
fn visibility_matches_vanilla_chat_abilities() {
let mut chat = ChatState::new();
Expand Down
46 changes: 34 additions & 12 deletions pomme-client/src/ui/hud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use azalea_inventory::ItemStack;
use glam::DVec3;

use super::common::{FONT_SIZE, TextWidthFn, WHITE, push_item_icon};
use crate::entity::wrap_degrees;
use crate::mob_effect::ActiveMobEffects;
use crate::position::BlockPos;
use crate::renderer::pipelines::menu_overlay::{MenuElement, SpriteId};
Expand Down Expand Up @@ -1620,9 +1621,6 @@ pub fn build_debug_overlay(
let bz = pos.z.floor() as i32;
let cx = bx.div_euclid(16);
let cz = bz.div_euclid(16);
let facing = facing_name(info.y_rot_deg);
let y_rot_deg = info.y_rot_deg;
let x_rot_deg = info.x_rot_deg;

let mut left_lines: Vec<String> = vec![
format!("Pomme ({}fps)", info.fps),
Expand All @@ -1636,7 +1634,7 @@ pub fn build_debug_overlay(
cx,
cz
),
format!("Facing: {} ({:.1} / {:.1})", facing, y_rot_deg, x_rot_deg),
facing_line(info.y_rot_deg, info.x_rot_deg),
String::new(),
format!("Chunks: {} loaded", info.chunk_count),
format!(
Expand Down Expand Up @@ -1724,21 +1722,45 @@ fn push_debug_lines(
}
}

fn facing_name(y_rot_deg: f32) -> &'static str {
let deg = y_rot_deg.rem_euclid(360.0) as u32;
match deg {
315..=359 | 0..=44 => "South (+Z)",
45..=134 => "West (-X)",
135..=224 => "North (-Z)",
225..=314 => "East (+X)",
_ => "South (+Z)",
/// Vanilla `DebugEntryPosition`: the direction, which way it points, and
/// both angles wrapped to [-180, 180).
fn facing_line(y_rot_deg: f32, x_rot_deg: f32) -> String {
let (direction, towards) = facing(y_rot_deg);
format!(
"Facing: {direction} ({towards}) ({:.1} / {:.1})",
wrap_degrees(y_rot_deg),
wrap_degrees(x_rot_deg)
)
}

/// Vanilla `Direction.fromYRot`.
fn facing(y_rot_deg: f32) -> (&'static str, &'static str) {
match ((y_rot_deg / 90.0 + 0.5).floor() as i32) & 3 {
0 => ("south", "Towards positive Z"),
1 => ("west", "Towards negative X"),
2 => ("north", "Towards negative Z"),
_ => ("east", "Towards positive X"),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn facing_angles_wrap_like_vanilla() {
assert_eq!(
facing_line(400.0, 0.0),
"Facing: south (Towards positive Z) (40.0 / 0.0)"
);
assert_eq!(
facing_line(-190.0, 90.0),
"Facing: north (Towards negative Z) (170.0 / 90.0)"
);
assert_eq!(facing(44.9).0, "south");
assert_eq!(facing(45.0).0, "west");
}

#[test]
fn offhand_slot_sits_on_the_offhand_arms_side() {
let cx = 400.0;
Expand Down
Loading
Loading