A Deno-powered backend service for Plants vs. Zombies: MODDED. [Read-only GitHub mirror]
docs.pvzm.net
express
typescript
expressjs
plant
deno
jspvz
pvzm
game
online
backend
plants-vs-zombies
zombie
javascript
plants
modded
vs
plantsvszombies
openapi
pvz
noads
1import type { ServerConfig } from "./config.ts";
2import { createCanvas, loadImage, type Image } from "@napi-rs/canvas";
3import { izombiePlantsMap } from "./levels_io.ts";
4import type { PlantData } from "./plantImages.ts";
5
6const PUMPKIN_HEAD_INDEX = izombiePlantsMap.indexOf("oPumpkinHead");
7
8const imageCache = new Map<string, Image>();
9
10async function cachedLoadImage(url: string): Promise<Image> {
11 const cached = imageCache.get(url);
12 if (cached) return cached;
13 const img = await loadImage(url);
14 imageCache.set(url, img);
15 return img;
16}
17
18export async function renderThumbnailCanvas(
19 thumb: number[][],
20 isWater: boolean,
21 plantImages: { [key: string]: PlantData },
22 config: ServerConfig
23): Promise<Uint8Array> {
24 const gameUrl = config.gameUrl.endsWith("/") ? config.gameUrl.slice(0, -1) : config.gameUrl;
25 const secret = config.gameUrlSecret;
26 const suffix = secret ? `?secret=${encodeURIComponent(secret)}` : "";
27 const baseUrl = `${gameUrl}/game/`;
28
29 const canvas = createCanvas(900, 600);
30 const ctx = canvas.getContext("2d");
31
32 // draw background
33 const bgPath = isWater ? "images/interface/background4.jpg" : "images/interface/background2.jpg";
34 const bgImg = await cachedLoadImage(`${baseUrl}${bgPath}${suffix}`);
35 ctx.drawImage(bgImg, -115, 0, 1400, 600);
36
37 // sort by zindex (plant[5])
38 thumb.sort((a, b) => a[5] - b[5]);
39
40 // preload all plant images + shadow
41 const shadowImg = await cachedLoadImage(`${baseUrl}images/interface/plantshadow32.png${suffix}`);
42 const images = await Promise.all(
43 thumb.map((plant) => {
44 const plantName = izombiePlantsMap[plant[0]];
45 const data = plantImages[plantName];
46 const src = plant[0] !== PUMPKIN_HEAD_INDEX ? data.PicArr[1] : data.PicArr[8];
47 return cachedLoadImage(`${baseUrl}${src}${suffix}`);
48 })
49 );
50
51 // draw plants (game renders at 0.9 scale, so scale from center)
52 const gameScale = 0.9;
53 thumb.forEach((plant, i) => {
54 const w = plant[3] / gameScale;
55 const h = plant[4] / gameScale;
56 const x = plant[1] + (plant[3] - w) / 2;
57 const y = plant[2] + (plant[4] - h) / 2;
58
59 // draw shadow using per-plant getShadow CSS style
60 const plantName = izombiePlantsMap[plant[0]];
61 const data = plantImages[plantName];
62 const style = data.shadowStyle;
63 if (!style.includes("display:none") && !style.includes("display: none")) {
64 const leftMatch = style.match(/left:\s*(-?[\d.]+)px/);
65 const topMatch = style.match(/top:\s*(-?[\d.]+)px/);
66 const shadowLeft = leftMatch ? parseFloat(leftMatch[1]) : 0;
67 const shadowTop = topMatch ? parseFloat(topMatch[1]) : 0;
68 ctx.drawImage(shadowImg, x + shadowLeft / gameScale, y + shadowTop / gameScale);
69 }
70
71 ctx.drawImage(images[i], x, y, w, h);
72 });
73
74 return new Uint8Array(await canvas.encode("png"));
75}