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.
194 lines
6.8 KiB
194 lines
6.8 KiB
import { Resvg } from '@resvg/resvg-js';
|
|
import { JSDOM } from 'jsdom';
|
|
|
|
export interface Element {
|
|
remove(): void;
|
|
}
|
|
|
|
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, fontDir: string): 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, {
|
|
font: {
|
|
fontDirs: [fontDir],
|
|
loadSystemFonts: false,
|
|
}
|
|
});
|
|
resvg.resolveImage('https://invalid.invalid/face.png', status.portrait);
|
|
return resvg.render().asPng();
|
|
}
|
|
|