Expand outdoor shopping district

Co-authored-by: Luke The Dev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-04-11 23:08:38 +00:00
co-authored by Luke The Dev
parent 94aff0f3da
commit 89a4e359bd
5 changed files with 135 additions and 52 deletions
+14 -12
View File
@@ -19,28 +19,30 @@ export const LOCAL_OFFICE_ZONE: DistrictZone = {
maxY: LOCAL_OFFICE_CANVAS_HEIGHT,
};
export const CITY_PATH_ZONE: DistrictZone = {
export const SHOPPING_ZONE: DistrictZone = {
minX: 0,
maxX: LOCAL_OFFICE_CANVAS_WIDTH,
minY: 760,
maxY: 980,
minY: 720,
maxY: 1080,
};
export const CITY_PATH_ZONE: DistrictZone = SHOPPING_ZONE;
export const REMOTE_OFFICE_ZONE: DistrictZone = {
minX: 0,
maxX: LOCAL_OFFICE_CANVAS_WIDTH,
minY: 1020,
maxY: 1020 + LOCAL_OFFICE_CANVAS_HEIGHT,
minY: SHOPPING_ZONE.maxY,
maxY: SHOPPING_ZONE.maxY + LOCAL_OFFICE_CANVAS_HEIGHT,
};
export const REMOTE_ROAM_POINTS = [
{ x: 800, y: 1220 },
{ x: 850, y: 1520 },
{ x: 820, y: 1600 },
{ x: 450, y: 1440 },
{ x: 250, y: 1440 },
{ x: 650, y: 1440 },
{ x: 150, y: 1640 },
{ x: 800, y: 1280 },
{ x: 850, y: 1580 },
{ x: 820, y: 1660 },
{ x: 450, y: 1500 },
{ x: 250, y: 1500 },
{ x: 650, y: 1500 },
{ x: 150, y: 1700 },
] as const;
export const DISTRICT_CAMERA_POSITION: [number, number, number] = [14, 16, 18];
@@ -62,8 +62,8 @@ const DEFAULT_JUKEBOX: FurnitureSeed = {
const DEFAULT_SHOP: FurnitureSeed = {
type: "shop",
x: 320,
y: 840,
x: 140,
y: 820,
facing: 0,
};
@@ -117,15 +117,28 @@ const DEFAULT_DINING_ITEMS: FurnitureSeed[] = [
];
const DEFAULT_SHOPPING_STRIP_ITEMS: FurnitureSeed[] = [
{ type: "table_rect", x: 230, y: 820, w: 110, h: 40, facing: 0 },
{ type: "vending", x: 470, y: 790, facing: 0 },
{ type: "table_rect", x: 670, y: 870, w: 120, h: 40, facing: 0 },
{ type: "plant", x: 210, y: 930 },
{ type: "plant", x: 520, y: 930 },
{ type: "plant", x: 860, y: 930 },
{ type: "couch", x: 1010, y: 820, w: 90, h: 30, facing: 0 },
{ type: "couch", x: 1200, y: 820, w: 90, h: 30, facing: 0 },
{ type: "couch", x: 1390, y: 820, w: 90, h: 30, facing: 0 },
{ type: "table_rect", x: 120, y: 780, w: 120, h: 42, facing: 0 },
{ type: "vending", x: 120, y: 930, facing: 0 },
{ type: "bookshelf", x: 420, y: 780, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 420, y: 930, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 650, y: 780, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 650, y: 930, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 880, y: 780, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 880, y: 930, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 1110, y: 780, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 1110, y: 930, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 1340, y: 780, w: 80, h: 120, facing: 0 },
{ type: "bookshelf", x: 1340, y: 930, w: 80, h: 120, facing: 0 },
{ type: "table_rect", x: 1560, y: 790, w: 120, h: 42, facing: 0 },
{ type: "table_rect", x: 1560, y: 930, w: 120, h: 42, facing: 0 },
{ type: "couch", x: 1490, y: 1010, w: 90, h: 30, facing: 0 },
{ type: "couch", x: 1640, y: 1010, w: 90, h: 30, facing: 0 },
{ type: "plant", x: 300, y: 1030 },
{ type: "plant", x: 560, y: 1030 },
{ type: "plant", x: 790, y: 1030 },
{ type: "plant", x: 1020, y: 1030 },
{ type: "plant", x: 1250, y: 1030 },
{ type: "plant", x: 1480, y: 1030 },
];
const DEFAULT_SERVER_ROOM_ITEMS: FurnitureSeed[] = [
@@ -1,3 +1,4 @@
import { SHOPPING_ZONE } from "@/features/retro-office/core/district";
import { getItemBaseSize } from "@/features/retro-office/core/geometry";
import type {
FacingPoint,
@@ -5,9 +6,21 @@ import type {
ShopRoute,
} from "@/features/retro-office/core/types";
export const SHOP_ENTRY_TARGET: FacingPoint = {
x: 900,
y: SHOPPING_ZONE.minY + 40,
facing: 0,
};
export const SHOP_AISLE_TARGET: FacingPoint = {
x: 980,
y: 910,
facing: 0,
};
export const SHOP_TARGET: FacingPoint = {
x: 280,
y: 840,
x: 180,
y: 920,
facing: 0,
};
@@ -18,40 +31,57 @@ export const resolveShopRoute = (
): ShopRoute => {
if (!item) {
return {
stage: "counter",
stage: "checkout",
targetX: SHOP_TARGET.x,
targetY: SHOP_TARGET.y,
facing: SHOP_TARGET.facing,
};
}
const { width, height } = getItemBaseSize(item);
const centerY = item.y + height / 2;
const approachTarget = {
const checkoutTarget = {
x: item.x + width / 2,
y: item.y + height + 50,
y: item.y + height + 44,
facing: 0,
};
const counterTarget = {
x: item.x + width / 2,
y: centerY,
facing: 0,
};
const atCounter =
y <= approachTarget.y + 6 || Math.hypot(x - approachTarget.x, y - approachTarget.y) < 18;
if (atCounter) {
const closeToCheckout =
Math.hypot(x - checkoutTarget.x, y - checkoutTarget.y) < 44;
if (closeToCheckout) {
return {
stage: "counter",
targetX: counterTarget.x,
targetY: counterTarget.y,
facing: counterTarget.facing,
stage: "checkout",
targetX: checkoutTarget.x,
targetY: checkoutTarget.y,
facing: checkoutTarget.facing,
};
}
const closeToAisle =
y >= SHOPPING_ZONE.minY + 70 &&
Math.hypot(x - SHOP_AISLE_TARGET.x, y - SHOP_AISLE_TARGET.y) < 90;
if (closeToAisle) {
return {
stage: "checkout",
targetX: checkoutTarget.x,
targetY: checkoutTarget.y,
facing: checkoutTarget.facing,
};
}
const enteredStore =
y >= SHOP_ENTRY_TARGET.y - 10 ||
Math.hypot(x - SHOP_ENTRY_TARGET.x, y - SHOP_ENTRY_TARGET.y) < 70;
if (enteredStore) {
return {
stage: "aisle",
targetX: SHOP_AISLE_TARGET.x,
targetY: SHOP_AISLE_TARGET.y,
facing: SHOP_AISLE_TARGET.facing,
};
}
return {
stage: "approach",
targetX: approachTarget.x,
targetY: approachTarget.y,
facing: approachTarget.facing,
stage: "entrance",
targetX: SHOP_ENTRY_TARGET.x,
targetY: SHOP_ENTRY_TARGET.y,
facing: SHOP_ENTRY_TARGET.facing,
};
};
+1 -1
View File
@@ -137,7 +137,7 @@ export type SmsBoothRoute = {
};
export type ShopRoute = {
stage: "approach" | "counter";
stage: "entrance" | "aisle" | "checkout";
targetX: number;
targetY: number;
facing: number;
@@ -277,11 +277,11 @@ export const FloorAndWalls = memo(function FloorAndWalls({
>
<planeGeometry
args={[
(CITY_PATH_ZONE.maxX - CITY_PATH_ZONE.minX) * SCALE * 0.72,
(CITY_PATH_ZONE.maxY - CITY_PATH_ZONE.minY) * SCALE * 0.26,
(CITY_PATH_ZONE.maxX - CITY_PATH_ZONE.minX) * SCALE * 0.92,
(CITY_PATH_ZONE.maxY - CITY_PATH_ZONE.minY) * SCALE * 0.74,
]}
/>
<meshStandardMaterial color="#c9ae8d" roughness={0.94} metalness={0.02} />
<meshStandardMaterial color="#c9ae8d" roughness={0.9} metalness={0.04} />
</mesh>
{Array.from({ length: 8 }).map((_, index) => {
@@ -355,8 +355,8 @@ export const FloorAndWalls = memo(function FloorAndWalls({
);
})}
{Array.from({ length: 5 }).map((_, index) => {
const [wx, , wz] = toWorld(220 + index * 280, 785);
{Array.from({ length: 6 }).map((_, index) => {
const [wx, , wz] = toWorld(190 + index * 270, 770);
return (
<group key={`shopping-awning-${index}`} position={[wx, 0, wz]}>
<mesh position={[0, 0.9, 0]} castShadow receiveShadow>
@@ -374,6 +374,44 @@ export const FloorAndWalls = memo(function FloorAndWalls({
</group>
);
})}
{Array.from({ length: 5 }).map((_, index) => {
const [wx, , wz] = toWorld(300 + index * 280, 1010);
return (
<group key={`shopping-section-${index}`} position={[wx, 0, wz]}>
<mesh position={[0, 0.52, 0]} castShadow receiveShadow>
<boxGeometry args={[1.55, 0.14, 0.22]} />
<meshStandardMaterial color="#3e2723" roughness={0.74} metalness={0.08} />
</mesh>
<mesh position={[0, 0.86, 0]} castShadow receiveShadow>
<boxGeometry args={[0.08, 0.68, 0.08]} />
<meshStandardMaterial color="#6d4c41" roughness={0.62} metalness={0.12} />
</mesh>
<mesh position={[0, 1.18, 0]} castShadow receiveShadow>
<boxGeometry args={[0.94, 0.18, 0.16]} />
<meshStandardMaterial
color={["#22c55e", "#f59e0b", "#38bdf8", "#a855f7", "#ef4444"][index]}
roughness={0.34}
metalness={0.06}
/>
</mesh>
</group>
);
})}
{Array.from({ length: 6 }).map((_, index) => {
const [wx, , wz] = toWorld(360 + index * 230, 905);
return (
<mesh
key={`shopping-aisle-mark-${index}`}
position={[wx, 0.008, wz]}
rotation={[-Math.PI / 2, 0, 0]}
>
<planeGeometry args={[0.05, (CITY_PATH_ZONE.maxY - CITY_PATH_ZONE.minY) * SCALE * 0.58]} />
<meshBasicMaterial color="#fff5d6" transparent opacity={0.16} />
</mesh>
);
})}
</>
) : null}