You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
motw-tracker/src/renderStatus.ts

185 lines
6.6 KiB

import {Resvg} from "@resvg/resvg-js";
import {JSDOM} from "jsdom";
export interface GameStatus {
health: number
unstable: boolean
healthDelta: number
experience: number
experienceDelta: number
luck: number
luckDelta: number
}
export interface GameStatusWithPortrait extends GameStatus {
portrait: Buffer
}
function removeElement(dom: Element): void {
dom.remove()
}
function healthBelow(health: number): (status: GameStatus) => boolean {
return (s) => s.health < health
}
function luckBelow(luck: number): (status: GameStatus) => boolean {
return (s) => s.luck < luck
}
function experienceBelow(exp: number): (status: GameStatus) => boolean {
return (s) => s.experience < exp
}
function healthNotEqual(health: number): (status: GameStatus) => boolean {
return (s) => s.health !== health
}
function experienceDeltaNotIncludes(index: number): (status: GameStatus) => boolean {
return (s) => {
if (s.experienceDelta <= 0) {
return true
}
const effectiveMax = Math.min(5, s.experience)
return !(index <= effectiveMax && index > effectiveMax - s.experienceDelta)
}
}
function luckDeltaNotIncludes(index: number): (status: GameStatus) => boolean {
return (s) => {
if (s.luckDelta === 0) {
return true
} else if (s.luckDelta > 0) {
return !((index <= s.luck) && (index > s.luck - s.luckDelta))
} else {
return !((index > s.luck) && (index <= s.luck - s.luckDelta))
}
}
}
function healthDamageNotIncludes(index: number): (status: GameStatus) => boolean {
return (s) => {
if (s.healthDelta >= 0) {
return true
}
return !((index > s.health) && (index <= s.health - s.healthDelta))
}
}
function healthRecoveredNotIncludes(index: number): (status: GameStatus) => boolean {
return (s) => {
if (s.healthDelta <= 0) {
return true
}
return !((index <= s.health) && (index > s.health - s.healthDelta))
}
}
function notStableAlive(): (status: GameStatus) => boolean {
return (status) => status.unstable || status.health <= 0
}
function notUnstableAlive(): (status: GameStatus) => boolean {
return (status) => !status.unstable || status.health <= 0
}
function notDead(): (status: GameStatus) => boolean {
return (status) => status.health > 0
}
function dead(): (status: GameStatus) => boolean {
return (status) => status.health <= 0
}
const mappings: [filter: (status: GameStatus) => boolean, selector: string, hit: (dom: Element) => void][] = [
[healthBelow(8), "#health8", removeElement],
[healthBelow(7), "#health7", removeElement],
[healthBelow(6), "#health6", removeElement],
[healthBelow(5), "#health5", removeElement],
[healthBelow(4), "#health4", removeElement],
[healthBelow(3), "#health3", removeElement],
[healthBelow(2), "#health2", removeElement],
[healthBelow(1), "#health1", removeElement],
[luckBelow(7), "#luck7", removeElement],
[luckBelow(6), "#luck6", removeElement],
[luckBelow(5), "#luck5", removeElement],
[luckBelow(4), "#luck4", removeElement],
[luckBelow(3), "#luck3", removeElement],
[luckBelow(2), "#luck2", removeElement],
[luckBelow(1), "#luck1", removeElement],
[experienceBelow(5), "#experienceLevelUpReady", removeElement],
[experienceBelow(5), "#experience5", removeElement],
[experienceBelow(4), "#experience4", removeElement],
[experienceBelow(3), "#experience3", removeElement],
[experienceBelow(2), "#experience2", removeElement],
[experienceBelow(1), "#experience1", removeElement],
[healthDamageNotIncludes(8), "#healthDamage8", removeElement],
[healthDamageNotIncludes(7), "#healthDamage7", removeElement],
[healthDamageNotIncludes(6), "#healthDamage6", removeElement],
[healthDamageNotIncludes(5), "#healthDamage5", removeElement],
[healthDamageNotIncludes(4), "#healthDamage4", removeElement],
[healthDamageNotIncludes(3), "#healthDamage3", removeElement],
[healthDamageNotIncludes(2), "#healthDamage2", removeElement],
[healthDamageNotIncludes(1), "#healthDamage1", removeElement],
[healthRecoveredNotIncludes(8), "#healthRecovery8", removeElement],
[healthRecoveredNotIncludes(7), "#healthRecovery7", removeElement],
[healthRecoveredNotIncludes(6), "#healthRecovery6", removeElement],
[healthRecoveredNotIncludes(5), "#healthRecovery5", removeElement],
[healthRecoveredNotIncludes(4), "#healthRecovery4", removeElement],
[healthRecoveredNotIncludes(3), "#healthRecovery3", removeElement],
[healthRecoveredNotIncludes(2), "#healthRecovery2", removeElement],
[healthRecoveredNotIncludes(1), "#healthRecovery1", removeElement],
[healthNotEqual(8), "#healthCounter8", removeElement],
[healthNotEqual(7), "#healthCounter7", removeElement],
[healthNotEqual(6), "#healthCounter6", removeElement],
[healthNotEqual(5), "#healthCounter5", removeElement],
[healthNotEqual(4), "#healthCounter4", removeElement],
[healthNotEqual(3), "#healthCounter3", removeElement],
[healthNotEqual(2), "#healthCounter2", removeElement],
[healthNotEqual(1), "#healthCounter1", removeElement],
[healthNotEqual(0), "#healthCounter0", removeElement],
[luckDeltaNotIncludes(7), "#luckShine7", removeElement],
[luckDeltaNotIncludes(6), "#luckShine6", removeElement],
[luckDeltaNotIncludes(5), "#luckShine5", removeElement],
[luckDeltaNotIncludes(4), "#luckShine4", removeElement],
[luckDeltaNotIncludes(3), "#luckShine3", removeElement],
[luckDeltaNotIncludes(2), "#luckShine2", removeElement],
[luckDeltaNotIncludes(1), "#luckShine1", removeElement],
[experienceDeltaNotIncludes(5), "#experienceUp5", removeElement],
[experienceDeltaNotIncludes(4), "#experienceUp4", removeElement],
[experienceDeltaNotIncludes(3), "#experienceUp3", removeElement],
[experienceDeltaNotIncludes(2), "#experienceUp2", removeElement],
[experienceDeltaNotIncludes(1), "#experienceUp1", removeElement],
[notStableAlive(), "#healthIcon", removeElement],
[notUnstableAlive(), "#healthLowIcon", removeElement],
[notDead(), "#healthEmptyIcon", removeElement],
[notDead(), "#characterDyingFace", removeElement],
[dead(), "#characterFaceImage", removeElement]
]
export async function renderStatus(svgTemplate: string, status: GameStatusWithPortrait): Promise<Buffer> {
const dom = new JSDOM(svgTemplate, {
contentType: "image/svg+xml",
pretendToBeVisual: false,
includeNodeLocations: false,
url: "https://localhost/status.xml"
})
for (const [filter, selector, action] of mappings) {
if (filter(status)) {
for (const el of dom.window.document.querySelectorAll(selector)) {
action(el)
}
}
}
const resvg = new Resvg(dom.window.document.documentElement.outerHTML, {})
resvg.resolveImage("https://invalid.invalid/face.png", status.portrait)
return resvg.render().asPng()
}