commit 6f3c9c80e418f35e28e1e64b73ae515131c8854b
parent 2e3361408c34945f3a8b0a3be9854f7716f87d52
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Mon, 14 Sep 2026 21:44:09 -0400
Draw metadata popups on top and limit player action radius
Diffstat:
4 files changed, 84 insertions(+), 61 deletions(-)
diff --git a/client/src/input.rs b/client/src/input.rs
@@ -36,23 +36,26 @@ pub(crate) fn handle_movement_input(local_player: &Player, dt: &Duration) -> Opt
pub(crate) fn handle_pick_up_book_input(
local_player: &Player,
+ camera: &Camera,
floor_books: &[FloorBook],
) -> Option<ActionKind> {
- if is_key_pressed(KeyCode::Space) {
- let local_player_collider = local_player
- .collider()
- .rect()
- .offset(local_player.position());
- if let Some(floor_book) = floor_books.iter().find(|fb| {
+ if is_mouse_button_pressed(MouseButton::Left)
+ && let Some(moused_floor_book) = floor_books.iter().find(|fb| {
fb.collider()
.rect()
.offset(fb.position())
- .overlaps(&local_player_collider)
- }) {
- return Some(ActionKind::PickUpBook {
- book_id: floor_book.id(),
- });
- }
+ .offset(-camera.position())
+ .contains({
+ let (x, y) = mouse_position();
+ Vec2::new(x, y)
+ })
+ && fb.position().distance(local_player.position())
+ <= GameState::MAX_PLAYER_ACTION_RADIUS
+ })
+ {
+ return Some(ActionKind::PickUpBook {
+ book_id: moused_floor_book.id(),
+ });
}
None
}
@@ -103,6 +106,11 @@ pub(crate) fn handle_put_book_on_shelf_input(
&& let Some(book_id) = local_player.books().get(selected_book_index)
&& let Some(shelf) = bookshelves.get(&slot_address.bookshelf_id())
&& let Some(Slot::Empty) = shelf.get_slot(&slot_address)
+ // TODO Base this on the position of the slot, not the click
+ && local_player.position().distance({
+ let (x, y) = mouse_position();
+ Vec2::new(x, y) + camera.position()
+ }) <= GameState::MAX_PLAYER_ACTION_RADIUS
{
return Some(ActionKind::PutBookOnShelf {
book_id: *book_id,
@@ -115,11 +123,17 @@ pub(crate) fn handle_put_book_on_shelf_input(
pub(crate) fn handle_take_book_off_shelf_input(
bookshelves: &HashMap<BookshelfId, Bookshelf>,
camera: &Camera,
+ local_player: &Player,
) -> Option<ActionKind> {
if is_mouse_button_pressed(MouseButton::Left)
&& let Some(slot_address) = get_bookshelf_mouse_address(camera, bookshelves)
&& let Some(shelf) = bookshelves.get(&slot_address.bookshelf_id())
&& let Some(Slot::Book { .. }) = shelf.get_slot(&slot_address)
+ // TODO Base this on the position of the slot, not the click
+ && local_player.position().distance({
+ let (x, y) = mouse_position();
+ Vec2::new(x, y) + camera.position()
+ }) <= GameState::MAX_PLAYER_ACTION_RADIUS
{
return Some(ActionKind::TakeBookOffShelf { slot_address });
}
diff --git a/client/src/render.rs b/client/src/render.rs
@@ -88,6 +88,7 @@ impl ClientState {
&& let Some(local_player) = self.interpolated_game.get_player(&local_player_id)
{
let mouse_slot = get_bookshelf_mouse_address(&self.camera, self.game.bookshelves());
+ let mut mouse_book_popup = None;
// Render bookshelves
for (bookshelf_id, bookshelf) in self.interpolated_game.bookshelves() {
const BOOKSHELF_BACKGROUND: Color = BROWN;
@@ -161,13 +162,7 @@ impl ClientState {
Bookshelf::BOOK_HEIGHT,
GOLD.with_alpha(0.5),
);
- self.draw_book_metadata_popup(
- Vec2::new(
- x - Bookshelf::BOOK_WIDTH / 2. + self.camera.position.x,
- y + self.camera.position.y,
- ),
- book_id,
- );
+ mouse_book_popup = Some((Vec2::new(x, y), book_id));
}
}
Slot::Empty => {
@@ -189,6 +184,9 @@ impl ClientState {
}
}
}
+ if let Some((position, book_id)) = mouse_book_popup {
+ self.draw_book_metadata_popup(position, book_id);
+ }
// Render books on the floor
let floor_book_sprite = SPRITESHEET.book.clone();
@@ -213,28 +211,25 @@ impl ClientState {
);
}
- // Render book metadata popup if local player is near a floor book
- if let Some(nearby_floor_book) =
- self.interpolated_game
- .floor_books()
- .iter()
- .find(|floor_book| {
- floor_book
- .collider()
- .rect()
- .offset(floor_book.position())
- .overlaps(
- &local_player
- .collider()
- .rect()
- .offset(local_player.position()),
- )
- })
+ // Render metadata popup for a floor book if the user mouses over it
+ if let Some(moused_floor_book) =
+ self.interpolated_game.floor_books().iter().find(|fb| {
+ fb.collider()
+ .rect()
+ .offset(fb.position())
+ .offset(-self.camera.position)
+ .contains({
+ let (x, y) = mouse_position();
+ Vec2::new(x, y)
+ })
+ })
{
- self.draw_book_metadata_popup(
- nearby_floor_book.position(),
- &nearby_floor_book.id(),
+ let popup_position = Vec2::new(
+ moused_floor_book.position().x - self.camera.position.x
+ + SPRITESHEET.book.size.x * 0.4,
+ moused_floor_book.position().y - self.camera.position.y - 5.,
);
+ self.draw_book_metadata_popup(popup_position, &moused_floor_book.id());
}
// Render list of books held by the local player
@@ -254,7 +249,7 @@ impl ClientState {
// Render debug view if enabled
if self.debug_view {
draw_rectangle(0., 0., 284., 56., BLACK);
- draw_text("Library Tech Demo 2026-09-09", 4., 16., 16., WHITE);
+ draw_text("Library Tech Demo 2026-09-14", 4., 16., 16., WHITE);
draw_text(format!("fps {}", get_fps()), 4., 32., 16., WHITE);
draw_text(format!("id {}", local_player_id), 4., 48., 16., WHITE);
draw_text(
@@ -290,22 +285,32 @@ impl ClientState {
let font_size = 24.;
let text_dimensions = measure_text(&text, None, font_size as u16, 1.);
let padding = 4.;
- let x = position.x - self.camera.position.x;
- let y = position.y - self.camera.position.y - 16.;
+ let Vec2 { x, y } = position;
+ let triangle_width = Bookshelf::BOOK_WIDTH * 2.;
+ let triangle_height = 10.;
+ let text_x = x - text_dimensions.width / 2.;
+ let text_y =
+ y - text_dimensions.height + text_dimensions.offset_y - triangle_height - padding;
let w = text_dimensions.width;
let h = text_dimensions.height;
draw_rectangle(
- x - padding,
- y - text_dimensions.offset_y - padding,
- w + padding,
- h + padding,
+ text_x - padding,
+ text_y - text_dimensions.offset_y - padding,
+ w + padding * 2.,
+ h + padding * 2.,
WHITE.with_alpha(0.5),
);
- draw_text(text, x, y, font_size, BLACK.with_alpha(0.5));
+ draw_text(text, text_x, text_y, font_size, BLACK.with_alpha(0.5));
draw_triangle(
- Vec2::new(x + 4., y + 5.),
- Vec2::new(x + 12., y + 13.),
- Vec2::new(x + 20., y + 5.),
+ Vec2::new(
+ x - triangle_width / 2. + Bookshelf::BOOK_WIDTH / 2.,
+ y - triangle_height,
+ ),
+ Vec2::new(x + Bookshelf::BOOK_WIDTH / 2., y),
+ Vec2::new(
+ x + triangle_width / 2. + Bookshelf::BOOK_WIDTH / 2.,
+ y - triangle_height,
+ ),
WHITE.with_alpha(0.5),
);
}
diff --git a/client/src/run.rs b/client/src/run.rs
@@ -163,7 +163,8 @@ impl ClientState {
let mut new_actions = [None, None, None, None, None];
new_actions[0] = handle_movement_input(&local_player, &self.dt);
- new_actions[1] = handle_pick_up_book_input(&local_player, self.game.floor_books());
+ new_actions[1] =
+ handle_pick_up_book_input(&local_player, &self.camera, self.game.floor_books());
new_actions[2] = handle_drop_book_input(&local_player, self.selected_inventory_book);
new_actions[3] = handle_put_book_on_shelf_input(
self.game.bookshelves(),
@@ -171,8 +172,11 @@ impl ClientState {
&local_player,
self.selected_inventory_book,
);
- new_actions[4] =
- handle_take_book_off_shelf_input(self.game.bookshelves(), &self.camera);
+ new_actions[4] = handle_take_book_off_shelf_input(
+ self.game.bookshelves(),
+ &self.camera,
+ &local_player,
+ );
for new_action in new_actions.iter().filter_map(|na| na.clone()) {
let action = Action::new(self.sequence_number, id, new_action);
diff --git a/common/src/game/state.rs b/common/src/game/state.rs
@@ -25,6 +25,7 @@ pub struct GameState {
impl GameState {
pub const MAX_PLAYER_SPEED_PX_S: f32 = 75.;
+ pub const MAX_PLAYER_ACTION_RADIUS: f32 = 100.;
pub fn apply_action(&mut self, action: &Action) {
match &action.kind {
@@ -52,6 +53,7 @@ impl GameState {
// - bookshelf exists
// - slot address is valid for bookshelf
// - slot is empty
+ // - TODO player is close enough
if BOOKS.get(book_id).is_some()
&& let Some(player) = self.players.get_mut(&action.player_id)
&& let Some(player_book_index) =
@@ -74,6 +76,7 @@ impl GameState {
// - bookshelf exists
// - slot address is valid for bookshelf
// - slot contains a book
+ // - TODO player is close enough
if let Some(player) = self.players.get_mut(&action.player_id)
&& player.books.len() < Player::MAX_BOOKS_COUNT
&& let Some(bookshelf) = self.bookshelves.get_mut(&slot_address.bookshelf_id())
@@ -214,15 +217,12 @@ impl GameState {
.iter()
.enumerate()
.find(|(_, fb)| fb.id() == *book_id)
+ && player.position().distance(floor_book.position())
+ <= GameState::MAX_PLAYER_ACTION_RADIUS
+ && player.books.len() < Player::MAX_BOOKS_COUNT
{
- let player_collider = player.collider.rect().offset(player.position);
- let floor_book_collider = floor_book.collider().rect().offset(floor_book.position());
- if player_collider.overlaps(&floor_book_collider)
- && player.books.len() < Player::MAX_BOOKS_COUNT
- {
- let book = self.floor_books.remove(floor_book_index);
- player.books.push(book.id());
- }
+ let book = self.floor_books.remove(floor_book_index);
+ player.books.push(book.id());
}
}
fn drop_book(&mut self, player_id: &Uuid, book_id: &BookId) {