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.
185 lines
6.6 KiB
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()
|
|
}
|
|
|