From 1fdae9e45c1e87b05e7bf705145461a3c4c1c3c0 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 6 Mar 2026 18:10:52 -0500 Subject: [PATCH] feat: add shared types and constants for ECS, networking, and assets Co-Authored-By: Claude Opus 4.6 --- shared/src/constants.ts | 32 +++++++++++++ shared/src/index.ts | 2 + shared/src/types.ts | 100 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 shared/src/constants.ts create mode 100644 shared/src/index.ts create mode 100644 shared/src/types.ts diff --git a/shared/src/constants.ts b/shared/src/constants.ts new file mode 100644 index 0000000..30bb3b6 --- /dev/null +++ b/shared/src/constants.ts @@ -0,0 +1,32 @@ +export const TILE_SIZE = 48; +export const WORLD_WIDTH = 64; +export const WORLD_HEIGHT = 64; +export const TICK_RATE = 10; // server ticks per second +export const BROADCAST_EVERY_N_TICKS = 3; // state broadcast frequency +export const SPRITE_FRAME_WIDTH = 48; +export const SPRITE_FRAME_HEIGHT = 48; +export const SPRITE_COLS = 6; +export const SPRITE_ROWS = 4; + +// NPC needs +export const HUNGER_DECAY_PER_TICK = 0.05; +export const ENERGY_DECAY_PER_TICK = 0.03; +export const HUNGER_THRESHOLD = 30; +export const ENERGY_THRESHOLD = 20; +export const NEED_RECOVERY_RATE = 0.5; + +// Directions (row index in spritesheet) +export const Direction = { + DOWN: 0, + LEFT: 1, + RIGHT: 2, + UP: 3, +} as const; +export type Direction = (typeof Direction)[keyof typeof Direction]; + +// Accessory slot names in z-order (bottom to top rendering) +export const ACCESSORY_SLOTS = [ + 'bottom', 'feet', 'chest', 'arm', 'shoulder', + 'waist', 'back', 'facialHair', 'haircut', 'hat', +] as const; +export type AccessorySlot = (typeof ACCESSORY_SLOTS)[number]; diff --git a/shared/src/index.ts b/shared/src/index.ts new file mode 100644 index 0000000..c6eac14 --- /dev/null +++ b/shared/src/index.ts @@ -0,0 +1,2 @@ +export * from './constants.js'; +export * from './types.js'; diff --git a/shared/src/types.ts b/shared/src/types.ts new file mode 100644 index 0000000..9a48a57 --- /dev/null +++ b/shared/src/types.ts @@ -0,0 +1,100 @@ +import type { AccessorySlot } from './constants.js'; + +// Entity is just a numeric ID +export type EntityId = number; + +// Components +export interface Position { + x: number; + y: number; +} + +export interface Velocity { + dx: number; + dy: number; +} + +export interface Appearance { + skinId: string; // e.g. "shape00_skin02" + accessories: Partial>; // slot -> filename without extension +} + +export interface Needs { + hunger: number; // 0-100 + energy: number; // 0-100 +} + +export type MovementState = 'idle' | 'walking'; +export type GoalType = 'wander' | 'eat' | 'rest'; + +export interface Movement { + state: MovementState; + target: Position | null; + path: Position[]; + direction: number; // Direction enum value +} + +export interface PlayerControlled { + playerId: string; + mode: 'avatar' | 'camera'; +} + +export interface NPCBrain { + currentGoal: GoalType | null; + goalQueue: GoalType[]; +} + +// Network protocol messages +export interface EntityState { + id: EntityId; + position: Position; + movement: Movement; + appearance: Appearance; + needs?: Needs; + npcBrain?: NPCBrain; + playerControlled?: PlayerControlled; +} + +export interface WorldState { + entities: EntityState[]; + worldWidth: number; + worldHeight: number; + tileSize: number; + obstacles: Position[]; + pointsOfInterest: { type: 'food' | 'rest'; position: Position }[]; +} + +export interface StateUpdate { + entities: EntityState[]; + tick: number; +} + +export interface PlayerJoined { + playerId: string; + entityId: EntityId; +} + +export interface PlayerLeft { + playerId: string; +} + +export interface PlayerInput { + type: 'move' | 'toggle-mode' | 'follow' | 'interact'; + direction?: { dx: number; dy: number }; + targetPlayerId?: string; + targetEntityId?: EntityId; +} + +// Server -> Client events +export interface ServerEvents { + 'world-state': (data: WorldState) => void; + 'state-update': (data: StateUpdate) => void; + 'player-joined': (data: PlayerJoined) => void; + 'player-left': (data: PlayerLeft) => void; + 'npc-recomposed': (data: { entityId: EntityId; appearance: Appearance }) => void; +} + +// Client -> Server events +export interface ClientEvents { + 'player-input': (data: PlayerInput) => void; +}