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 pomme-client/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ fn main() {
("item_entity.vert", shaderc::ShaderKind::Vertex),
("item_entity_world.vert", shaderc::ShaderKind::Vertex),
("item_entity.frag", shaderc::ShaderKind::Fragment),
("item_outline.vert", shaderc::ShaderKind::Vertex),
("weather.vert", shaderc::ShaderKind::Vertex),
("weather.frag", shaderc::ShaderKind::Fragment),
("particle.vert", shaderc::ShaderKind::Vertex),
Expand Down
5 changes: 4 additions & 1 deletion pomme-client/src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2228,6 +2228,7 @@ impl AppCore {
color,
fill_color,
collision_rule,
see_friendly_invisibles,
members,
} => {
game.scoreboard.set_team(
Expand All @@ -2238,6 +2239,7 @@ impl AppCore {
color,
fill_color,
collision_rule,
see_friendly_invisibles,
members,
);
}
Expand Down Expand Up @@ -2517,7 +2519,8 @@ impl AppCore {
}
}
if entity_type == azalea_registry::builtin::EntityKind::Item {
game.item_entity_store.spawn_item(id, position, velocity);
game.item_entity_store
.spawn_item(id, uuid, position, velocity);
}
}
NetworkEvent::EntityMoved {
Expand Down
52 changes: 38 additions & 14 deletions pomme-client/src/app/phases/in_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3858,12 +3858,26 @@ pub fn update_game(
(pos, stage, state)
});

let spectator_outlines = crate::player::is_spectator(game.player.game_mode)
&& core.input.performing_action(Action::SpectatorOutlines);
let outline = |glowing: bool, is_player: bool, uuid, player_uuid| {
use crate::entity::glowing::{appears_glowing, outline_color};
use crate::entity::glowing::{appears_glowing, outline_color, scoreboard_name};
use crate::renderer::pipelines::entity_renderer::Visibility;
let spectating = crate::player::is_spectator(game.player.game_mode);
let spectator_outlines = spectating && core.input.performing_action(Action::SpectatorOutlines);
let viewer = scoreboard_name(&game.tab_list, core.user.uuid, Some(core.user.uuid));
// Both take the entity's scoreboard name lazily: it's only needed when the
// entity glows or is invisible.
let outline = |glowing: bool, is_player: bool, name: &dyn Fn() -> String| {
appears_glowing(glowing, is_player, spectator_outlines)
.then(|| outline_color(&game.scoreboard, &game.tab_list, uuid, player_uuid))
.then(|| outline_color(&game.scoreboard, &name()))
};
// Vanilla `Entity.isInvisibleTo`, against the local player.
let visibility = |invisible: bool, name: &dyn Fn() -> String| {
if !invisible {
Visibility::Visible
} else if spectating || game.scoreboard.sees_friendly_invisible(&viewer, &name()) {
Visibility::Seen
} else {
Visibility::Hidden
}
};

let mut entity_renders: Vec<EntityRenderInfo> = if benchmark_running {
Expand All @@ -3883,14 +3897,15 @@ pub fn update_game(
let is_sleeping = e.sleeping_pos.is_some();
let (radius, strength) = crate::entity::shadow::of_living(e);

let name = || scoreboard_name(&game.tab_list, e.uuid, e.player_uuid);
EntityRenderInfo {
position: interp_pos + extras.render_offset,
outline_color: outline(
e.is_glowing,
e.entity_type == EntityKind::Player,
e.uuid,
e.player_uuid,
&name,
),
visibility: visibility(e.is_invisible, &name),
head_y_rot_deg: lerp_angle(
e.prev_head_y_rot_deg,
e.head_y_rot_deg,
Expand Down Expand Up @@ -3988,7 +4003,8 @@ pub fn update_game(
partial_tick,
);

let (radius, strength) = crate::entity::shadow::of_player(&game.player.attributes);
let (radius, strength) =
crate::entity::shadow::of_player(&game.player.attributes, game.player.invisible);
entity_renders.push(EntityRenderInfo {
position: interp_pos,
head_y_rot_deg: interp_y_rot_deg,
Expand All @@ -4007,12 +4023,8 @@ pub fn update_game(
.min(1.0),
entity_kind: EntityKind::Player,
player_uuid: Some(core.user.uuid),
outline_color: outline(
game.player.glowing,
true,
core.user.uuid,
Some(core.user.uuid),
),
outline_color: outline(game.player.glowing, true, &|| viewer.clone()),
visibility: visibility(game.player.invisible, &|| viewer.clone()),
armor: armor_render_infos(game.player.inventory.armor_slots()),
has_red_overlay: has_red_overlay(game.player.hurt_time, game.player.death_time),
death_time: render_death_time(game.player.death_time, partial_tick),
Expand Down Expand Up @@ -4054,6 +4066,11 @@ pub fn update_game(
*gfx.renderer.camera_pivot_position(),
gfx.renderer.camera_anchor(),
partial_tick,
|item| {
outline(item.is_glowing, false, &|| {
scoreboard_name(&game.tab_list, item.uuid, None)
})
},
)
};

Expand Down Expand Up @@ -4554,6 +4571,7 @@ fn emit_item_copies(
z_size: f32,
light: f32,
nether_lighting: bool,
outline_color: Option<u32>,
) {
use crate::renderer::pipelines::item_entity::ItemRenderInfo;
use crate::util::JavaRandom;
Expand All @@ -4575,6 +4593,7 @@ fn emit_item_copies(
let mut push = |copy_offset: glam::Mat4| {
infos.push(ItemRenderInfo {
item_name: name.clone(),
outline_color,
tints: tints.clone(),
model_matrix: base * copy_offset * ground_transform,
light,
Expand Down Expand Up @@ -4685,6 +4704,7 @@ fn shadow_casters(
entities.chain(items).collect()
}

#[allow(clippy::too_many_arguments)]
fn build_item_render_infos(
entity_store: &crate::entity::ItemEntityStore,
chunk_store: &ChunkStore,
Expand All @@ -4693,6 +4713,7 @@ fn build_item_render_infos(
camera_pos: glam::DVec3,
anchor: glam::DVec3,
partial_tick: f32,
outline: impl Fn(&crate::entity::ItemEntity) -> Option<u32>,
) -> Vec<crate::renderer::pipelines::item_entity::ItemRenderInfo> {
let mut infos = Vec::new();
let nether_lighting = cardinal_light == CardinalLightType::Nether;
Expand All @@ -4717,6 +4738,7 @@ fn build_item_render_infos(
z_size,
light,
nether_lighting,
outline(item),
);
}

Expand All @@ -4742,6 +4764,8 @@ fn build_item_render_infos(
z_size,
light,
nether_lighting,
// Vanilla's `ItemPickupParticle` clears its outline.
None,
);
}

Expand Down
37 changes: 19 additions & 18 deletions pomme-client/src/entity/glowing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,18 @@ pub fn appears_glowing(glowing: bool, is_player: bool, spectator_outlines: bool)
glowing || spectator_outlines && is_player
}

/// Vanilla `Entity.getTeamColor`: the colour of the team the entity's
/// scoreboard name belongs to (a player's name, anyone else's UUID string,
/// `getScoreboardName`), white without one.
pub fn outline_color(
scoreboard: &Scoreboard,
tab_list: &TabList,
uuid: Uuid,
player_uuid: Option<Uuid>,
) -> u32 {
let name = match player_uuid.and_then(|id| tab_list.players.get(&id)) {
/// Vanilla `getScoreboardName`: a player's name, anyone else's UUID string.
pub fn scoreboard_name(tab_list: &TabList, uuid: Uuid, player_uuid: Option<Uuid>) -> String {
match player_uuid.and_then(|id| tab_list.players.get(&id)) {
Some(player) => player.name.clone(),
None => uuid.to_string(),
};
scoreboard.member_team_color(&name).unwrap_or(0xFFFFFF)
}
}

/// Vanilla `Entity.getTeamColor`: the colour of the team the entity's
/// scoreboard name belongs to, white without one.
pub fn outline_color(scoreboard: &Scoreboard, name: &str) -> u32 {
scoreboard.member_team_color(name).unwrap_or(0xFFFFFF)
}

#[cfg(test)]
Expand All @@ -41,12 +39,15 @@ mod tests {

#[test]
fn outline_color_falls_back_to_white() {
let color = outline_color(
&Scoreboard::default(),
&TabList::default(),
Uuid::nil(),
None,
assert_eq!(outline_color(&Scoreboard::default(), "anyone"), 0xFFFFFF);
}

#[test]
fn non_players_score_by_uuid() {
let uuid = Uuid::from_u128(7);
assert_eq!(
scoreboard_name(&TabList::default(), uuid, None),
uuid.to_string()
);
assert_eq!(color, 0xFFFFFF);
}
}
19 changes: 15 additions & 4 deletions pomme-client/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ use crate::world::chunk::ChunkStore;

/// `Entity.FLAG_GLOWING` (bit 6) of the shared entity flags byte (index 0).
const SHARED_FLAG_GLOWING: u8 = 0x40;
/// `Entity.FLAG_INVISIBLE` (bit 5).
const SHARED_FLAG_INVISIBLE: u8 = 0x20;

/// Whether a shared entity flags byte is glowing: the server's
/// `isCurrentlyGlowing`, from the `Glowing` tag or the glowing effect.
pub fn shared_flags_glowing(flags: u8) -> bool {
flags & SHARED_FLAG_GLOWING != 0
}

/// Whether a shared entity flags byte is invisible (vanilla `isInvisible`).
pub fn shared_flags_invisible(flags: u8) -> bool {
flags & SHARED_FLAG_INVISIBLE != 0
}

/// A scalar synched-entity-data value, forwarded raw from the wire;
/// [`EntityStore::apply_entity_data`] gives it meaning per (kind, index).
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -866,6 +873,9 @@ impl LivingEntity {
}

pub struct ItemEntity {
/// As a string, its scoreboard name (vanilla `getScoreboardName`), which
/// its team colour is keyed by.
pub uuid: uuid::Uuid,
pub position: Position,
pub prev_position: Position,
pub item_name: String,
Expand Down Expand Up @@ -928,12 +938,13 @@ impl ItemEntityStore {
}
}

pub fn spawn_item(&mut self, id: i32, position: Position, velocity: DVec3) {
pub fn spawn_item(&mut self, id: i32, uuid: uuid::Uuid, position: Position, velocity: DVec3) {
let bob_offset =
((id as u32).wrapping_mul(2654435761)) as f32 / u32::MAX as f32 * std::f32::consts::TAU;
self.items.insert(
id,
ItemEntity {
uuid,
position,
prev_position: position,
item_name: String::new(),
Expand Down Expand Up @@ -1260,10 +1271,10 @@ impl EntityStore {
let kind = entity.entity_type;
let index = normalize_player_index(kind, normalize_ageable_index(kind, index));
match (kind, index, value) {
// Shared entity flags byte: bit 0x08 = sprinting, 0x20 = invisible.
// Shared entity flags byte: bit 0x08 = sprinting.
(_, 0, Byte(f)) => {
entity.is_sprinting = f & 0x08 != 0;
entity.is_invisible = f & 0x20 != 0;
entity.is_invisible = shared_flags_invisible(f);
entity.is_glowing = shared_flags_glowing(f);
}
(_, 9, Float(h)) => entity.health = h,
Expand Down Expand Up @@ -1937,7 +1948,7 @@ mod tests {
assert!(!store.living[&1].is_glowing);

let mut items = ItemEntityStore::new();
items.spawn_item(2, Position::default(), DVec3::ZERO);
items.spawn_item(2, uuid::Uuid::nil(), Position::default(), DVec3::ZERO);
items.set_shared_flags(2, SHARED_FLAG_GLOWING);
assert!(items.items[&2].is_glowing);
}
Expand Down
20 changes: 13 additions & 7 deletions pomme-client/src/entity/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,35 @@ pub struct ShadowInputs {
pub puff_state: u8,
}

/// A tracked mob's or player's shadow; none while it's invisible.
/// A tracked mob's or player's shadow.
pub fn of_living(entity: &LivingEntity) -> (f32, f32) {
if entity.is_invisible {
return (0.0, 0.0);
}
let inputs = ShadowInputs {
scale: scale(&entity.attributes),
is_baby: entity.is_baby,
slime_size: entity.slime_size,
puff_state: entity.puff_state,
};
shadow(entity.entity_type, &inputs)
unless_invisible(entity.is_invisible, entity.entity_type, &inputs)
}

/// The local player's shadow, from its own attributes.
pub fn of_player(attributes: &AttributeMap) -> (f32, f32) {
pub fn of_player(attributes: &AttributeMap, invisible: bool) -> (f32, f32) {
let inputs = ShadowInputs {
scale: scale(attributes),
is_baby: false,
slime_size: 0,
puff_state: 0,
};
shadow(EntityKind::Player, &inputs)
unless_invisible(invisible, EntityKind::Player, &inputs)
}

/// Vanilla `extractShadow`: no shadow while the entity is invisible.
fn unless_invisible(invisible: bool, kind: EntityKind, inputs: &ShadowInputs) -> (f32, f32) {
if invisible {
(0.0, 0.0)
} else {
shadow(kind, inputs)
}
}

/// Vanilla `getScale`: the `scale` attribute.
Expand Down
1 change: 1 addition & 0 deletions pomme-client/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ pub enum NetworkEvent {
color: [f32; 4],
fill_color: Option<u32>,
collision_rule: crate::ui::hud::CollisionRule,
see_friendly_invisibles: bool,
members: Option<Vec<String>>,
},
ScoreboardTeamMembers {
Expand Down
2 changes: 2 additions & 0 deletions pomme-client/src/net/scoreboard_wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ fn team(
color,
fill_color,
collision_rule: collision_rule(parameters.collision_rule),
// `PlayerTeam.unpackOptions`: bit 2.
see_friendly_invisibles: parameters.options & 2 != 0,
members,
})
}
Expand Down
8 changes: 6 additions & 2 deletions pomme-client/src/player/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ pub struct LocalPlayer {
pub swimming: bool,
/// The shared glowing flag.
pub glowing: bool,
/// The shared invisible flag.
pub invisible: bool,
/// The physical SWIMMING pose, chosen by `Player.updatePlayerPose`.
pub swimming_pose: bool,
pub air_supply: i32,
Expand Down Expand Up @@ -329,6 +331,7 @@ impl LocalPlayer {
under_water: false,
swimming: false,
glowing: false,
invisible: false,
swimming_pose: false,
air_supply: MAX_AIR_SUPPLY,
portal_effect_intensity: 0.0,
Expand Down Expand Up @@ -438,14 +441,15 @@ impl LocalPlayer {
}
}

/// The shared entity flags (bit 3 sprinting, bit 4 swimming, bit 6
/// glowing) the server sets on the local player.
/// The shared entity flags (bit 3 sprinting, bit 4 swimming, bit 5
/// invisible, bit 6 glowing) the server sets on the local player.
// TODO: vanilla's sprint speed modifier only follows a local
// `setSprinting`; this server flag also drives pomme's movement speed.
pub fn sync_shared_flags(&mut self, flags: u8) {
self.sprinting = flags & 0x08 != 0;
self.swimming = flags & 0x10 != 0;
self.glowing = crate::entity::shared_flags_glowing(flags);
self.invisible = crate::entity::shared_flags_invisible(flags);
}

pub fn snapshot_render_state(&mut self) {
Expand Down
3 changes: 2 additions & 1 deletion pomme-client/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1798,10 +1798,11 @@ impl Renderer {
}

// Vanilla blits the outline after the hand, before the GUI.
if self.entity_renderer.has_outlines() {
if self.entity_renderer.has_outlines() || self.item_entity_pipeline.has_outlines() {
pass.end();
let mut outline = self.entity_outline.begin(&self.ctx.device, &encoder);
self.entity_renderer.draw_outlines(&mut outline, frame);
self.item_entity_pipeline.draw_outlines(&mut outline, frame);
self.entity_outline
.finish(&self.ctx.device, &encoder, outline);
pass = self.resume_frame_pass(&encoder, image_index);
Expand Down
Loading
Loading