Skip to content

Commit 20f917b

Browse files
committed
smoother player animations
1 parent efe513f commit 20f917b

File tree

6 files changed

+121
-36
lines changed

6 files changed

+121
-36
lines changed

simulator/.dockerignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ helm-charts
1212
.env
1313
.editorconfig
1414
.idea
15-
coverage*
15+
coverage*
16+
.cache

simulator/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,5 @@ dist
173173

174174
# Finder (MacOS) folder config
175175
.DS_Store
176+
177+
.cache

simulator/Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ RUN cd /temp && bun install --frozen-lockfile --production
1313

1414
# copy production dependencies and source code into final image
1515
FROM base AS release
16+
ENV NODE_ENV=production
17+
1618
COPY --from=install /temp/node_modules node_modules
1719
COPY . .
1820

simulator/app.ts

+91-35
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { schedule } from "node-cron";
21
import { v4 as createUUID } from "uuid";
2+
import { loadCache, saveCache } from "./cache";
33
import type { Point } from "./messages";
44
import { publish, subscribe } from "./rabbitmq";
55

6-
const players = new Map<string, Point>();
6+
const players = loadCache();
7+
const goals = new Map<string, Point>();
8+
const speeds = new Map<string, number>();
79

810
const MAX_PLAYERS = 20;
911

@@ -12,56 +14,110 @@ console.log("simulator started");
1214
function addPlayer() {
1315
const uuid = createUUID();
1416
console.log(`${uuid} joined the game (${players.size}/${MAX_PLAYERS})`);
15-
players.set(uuid, {
16-
x: Math.floor(Math.random() * 200 - 100),
17-
y: Math.floor(Math.random() * 40 + 100),
18-
z: Math.floor(Math.random() * 200 - 100),
19-
world: "overworld",
20-
});
17+
const pos = randomPos();
18+
players.set(uuid, pos);
19+
publish("PlayerMoveMessage", { pos, player: uuid });
2120
}
2221

23-
function removePlayer() {
24-
const index = Math.floor(Math.random() * players.size);
25-
const uuid = [...players.keys()][index];
22+
function removePlayer(uuid: string) {
2623
console.log(`${uuid} left the game (${players.size}/${MAX_PLAYERS})`);
2724
players.delete(uuid);
2825
publish("PlayerDisconnectMessage", { player: uuid });
2926
}
3027

31-
for (let i = 0; i < MAX_PLAYERS / 2; i++) {
32-
addPlayer();
28+
if (players.size === 0) {
29+
for (let i = 0; i < MAX_PLAYERS / 2; i++) {
30+
addPlayer();
31+
}
3332
}
3433

35-
const interval = process.env.INTERVAL || "*/2 * * * * *";
34+
let interval = Number.parseInt(process.env.INTERVAL ?? "");
35+
if (isNaN(interval)) interval = 100;
36+
37+
function randomPos(): Point {
38+
const x = Math.random() * 1000 - 500;
39+
const z = Math.random() * 1000 - 500;
40+
const y = 80;
41+
const world = "overworld";
42+
return { x, y, z, world };
43+
}
44+
45+
function step(pos: Point, uuid: string) {
46+
let goal = goals.get(uuid);
47+
48+
if (!goal) {
49+
if (Math.random() < 0.1) return removePlayer(uuid);
50+
goal = randomPos();
51+
goals.set(uuid, goal);
52+
}
53+
54+
if (Math.random() > 0.2) return;
55+
56+
const diff = {
57+
x: goal.x - pos.x,
58+
z: goal.z - pos.z,
59+
};
60+
61+
const dist = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.x, 2));
62+
63+
const speed = speeds.get(uuid) ?? 0.1;
64+
65+
if (Math.random() < 0.2) {
66+
speeds.set(uuid, Math.max(0, speed - 0.1));
67+
} else if (Math.random() > 0.5) {
68+
speeds.set(uuid, Math.min(3, speed + 0.1));
69+
}
3670

37-
schedule(
38-
interval,
39-
() => {
40-
if (players.size > 0 && Math.random() < 0.5) {
41-
removePlayer();
42-
}
71+
const vec = {
72+
x: (diff.x / dist) * speed,
73+
z: (diff.z / dist) * speed,
74+
};
4375

44-
players.forEach((pos, key) => {
45-
if (Math.random() > 0.2) return;
76+
function add(a: number, b: number) {
77+
if (b < 0) return Math.floor(a + b);
78+
return Math.ceil(a + b);
79+
}
4680

47-
const dx = Math.floor(Math.random() * 24 - 12);
48-
const dz = Math.floor(Math.random() * 24 - 12);
49-
players.set(key, { ...pos, x: pos.x + dx, z: pos.z + dz });
50-
});
81+
const newPos: Point = {
82+
...pos,
83+
x: add(pos.x, vec.x),
84+
z: add(pos.z, vec.z),
85+
};
5186

52-
if (players.size < MAX_PLAYERS && Math.random() < 0.5) {
53-
addPlayer();
54-
}
87+
publish("PlayerMoveMessage", { pos: newPos, player: uuid });
5588

56-
players.forEach((pos, player) => {
57-
publish("PlayerMoveMessage", { pos, player });
58-
});
59-
},
60-
{ runOnInit: true }
61-
);
89+
players.set(uuid, newPos);
90+
}
91+
92+
setInterval(() => {
93+
players.forEach(step);
94+
95+
if (players.size < MAX_PLAYERS && Math.random() < 0.5) {
96+
addPlayer();
97+
}
98+
99+
saveCache(players);
100+
}, interval);
62101

63102
publish("ServerStatusMessage", { status: "STARTED" });
64103

65104
subscribe("RestartMessage", () => {
66105
publish("ServerStatusMessage", { status: "RECOVERED" });
67106
});
107+
108+
function shutdown() {
109+
console.log("shutting down simulator");
110+
111+
players.forEach((_, player) => {
112+
publish("PlayerDisconnectMessage", { player });
113+
});
114+
115+
publish("ServerStatusMessage", { status: "STOPPED" });
116+
}
117+
118+
process.on("exit", shutdown);
119+
process.on("SIGINT", shutdown);
120+
process.on("SIGUSR1", shutdown);
121+
process.on("SIGUSR2", shutdown);
122+
process.on("SIGTERM", shutdown);
123+
process.on("SIGBREAK", shutdown);

simulator/cache.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { existsSync, readFileSync, writeFileSync } from "fs";
2+
import type { Point } from "./messages";
3+
4+
const cacheFile = ".cache";
5+
6+
const isProduction = process.env.NODE_ENV === "production";
7+
8+
export function loadCache(): Map<string, Point> {
9+
if (isProduction || !existsSync(cacheFile)) return new Map();
10+
const json = JSON.parse(readFileSync(cacheFile).toString());
11+
return new Map(json);
12+
}
13+
14+
export function saveCache(state: Map<string, Point>) {
15+
if (isProduction) return;
16+
const json = JSON.stringify([...state.entries()]);
17+
writeFileSync(cacheFile, json);
18+
}

simulator/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
"name": "simulator",
33
"module": "app.ts",
44
"type": "module",
5+
"scripts": {
6+
"start": "bun src/app.ts",
7+
"dev": "bun --watch app.ts",
8+
"clear": "rm .cache",
9+
"type-check": "tsc"
10+
},
511
"devDependencies": {
612
"@types/amqplib": "^0.10.5",
713
"@types/bun": "latest",

0 commit comments

Comments
 (0)