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
at main 75 lines 2.7 kB view raw
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}