use std::time::SystemTime;
use pixtuoid_core::layout::{SEAT_RENDER_Y_OFF, WALKING_Y_OFF};
use pixtuoid_core::AgentSlot;
use super::epoch_ms;
use crate::tui::layout::{Point, WaypointKind, DESK_W};
use crate::tui::pose::{self, Pose};
pub(super) const CHARACTER_SPRITE_W: u16 = 8;
pub(super) fn seated_anchor(desk: Point, sprite_w: u16) -> Point {
Point {
x: desk.x + DESK_W.saturating_sub(sprite_w) / 2,
y: desk.y.saturating_sub(8),
}
}
pub(super) fn standing_at_desk_anchor(desk: Point, sprite_w: u16) -> Point {
Point {
x: desk.x + DESK_W.saturating_sub(sprite_w) / 2,
y: desk.y.saturating_sub(12),
}
}
pub(super) fn walking_anchor(p: Point, sprite_w: u16) -> Point {
Point {
x: p.x.saturating_sub(sprite_w / 2),
y: p.y.saturating_sub(WALKING_Y_OFF),
}
}
pub(super) fn waypoint_anchor(wp: Point, sprite_w: u16) -> Point {
Point {
x: wp.x.saturating_sub(sprite_w / 2),
y: wp.y.saturating_sub(WALKING_Y_OFF),
}
}
fn breath_offset_y(agent_id: pixtuoid_core::AgentId, now: SystemTime) -> u16 {
let elapsed_ms = epoch_ms(now);
const CYCLE_MS: u64 = 4500;
let offset_ms = agent_id.raw() % CYCLE_MS;
let phase = elapsed_ms.wrapping_add(offset_ms) % CYCLE_MS;
if phase < CYCLE_MS / 2 {
0
} else {
1
}
}
pub(super) fn with_breath(
anchor: Point,
agent_id: pixtuoid_core::AgentId,
now: SystemTime,
) -> Point {
Point {
x: anchor.x,
y: anchor.y.saturating_sub(breath_offset_y(agent_id, now)),
}
}
pub(super) fn back_couch_anchor(wp: Point, sprite_w: u16) -> Point {
Point {
x: wp.x.saturating_sub(sprite_w / 2),
y: wp.y.saturating_sub(SEAT_RENDER_Y_OFF),
}
}
pub(super) fn waypoint_rank_offset_x(kind: WaypointKind, rank: usize) -> i16 {
match (kind, rank) {
(_, 0) => 0,
(WaypointKind::Couch, 1) => 6,
(WaypointKind::Couch, 2) => -6,
(WaypointKind::Couch, _) => 0,
(_, 1) => 9,
(_, 2) => -9,
(_, _) => 0,
}
}
pub(in crate::tui) fn walking_position(from: Point, to: Point, t_x1000: u16) -> Point {
let t = t_x1000 as i32;
let dx = to.x as i32 - from.x as i32;
let dy = to.y as i32 - from.y as i32;
Point {
x: (from.x as i32 + dx * t / 1000).max(0).min(u16::MAX as i32) as u16,
y: (from.y as i32 + dy * t / 1000).max(0).min(u16::MAX as i32) as u16,
}
}
pub(in crate::tui) fn character_anchor(
agent: &AgentSlot,
layout: &crate::tui::layout::Layout,
now: SystemTime,
rctx: &mut pose::RouteCtx<'_>,
) -> Option<Point> {
let desk = layout.home_desk(agent.desk_index.single_floor_local())?;
let pose = pose::derive_with_routing(agent, now, layout, rctx)?;
let w = CHARACTER_SPRITE_W;
let anchor = match pose {
Pose::SeatedIdle | Pose::SeatedThinking | Pose::SeatedTyping { .. } => {
seated_anchor(desk, w)
}
Pose::StandingAtDesk => standing_at_desk_anchor(desk, w),
Pose::AtWaypoint { wp, kind } => {
let wp_obj = layout.waypoints.get(wp)?;
let stand = pixtuoid_core::layout::stand_point(
wp_obj.kind,
wp_obj.pos,
layout.pantry_counter_size,
&layout.walkable,
desk,
wp_obj.facing,
);
match kind {
WaypointKind::Couch | WaypointKind::MeetingSofa => back_couch_anchor(stand, w),
_ => waypoint_anchor(stand, w),
}
}
Pose::AimlessAt { dest } => waypoint_anchor(dest, w),
Pose::Walking {
from, to, t_x1000, ..
} => walking_anchor(walking_position(from, to, t_x1000), w),
};
Some(anchor)
}
const DOOR_TRANSITION_MS: u64 = 200;
pub(super) fn compute_door_frame_idx(
agents: &[AgentSlot],
now: SystemTime,
door_anim_max_ms: u64,
) -> usize {
fn frame_for_progress(elapsed_ms: u64, total_ms: u64) -> usize {
if elapsed_ms < DOOR_TRANSITION_MS {
if elapsed_ms < DOOR_TRANSITION_MS / 2 {
1
} else {
2
}
} else if elapsed_ms + DOOR_TRANSITION_MS > total_ms {
let remaining = total_ms.saturating_sub(elapsed_ms);
if remaining < DOOR_TRANSITION_MS / 2 {
0
} else {
1
}
} else {
2
}
}
let entry_window_ms = if door_anim_max_ms > 0 {
door_anim_max_ms
} else {
pose::ENTRY_ANIMATION_MS
};
let mut max_frame: usize = 0;
for a in agents {
if a.exiting_at.is_none() {
if let Ok(d) = now.duration_since(a.created_at) {
let ms = d.as_millis() as u64;
if ms < entry_window_ms {
max_frame = max_frame.max(frame_for_progress(ms, entry_window_ms));
}
}
}
if let Some(exit_at) = a.exiting_at {
if let Ok(d) = now.duration_since(exit_at) {
let ms = d.as_millis() as u64;
let exit_window_ms =
pixtuoid_core::state::reducer::EXIT_GRACE_WINDOW.as_millis() as u64;
if ms < exit_window_ms {
max_frame = max_frame.max(frame_for_progress(ms, exit_window_ms));
}
}
}
}
max_frame
}