Battler-Generator/index.ts

659 lines
19 KiB
TypeScript
Raw Normal View History

2024-04-30 11:05:57 +00:00
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
2024-04-10 21:47:46 +00:00
import {
GenerateBattlerOptions,
GenerateBattleImageOptions,
PlayerActionOptions,
Opponents,
2024-08-13 11:57:47 +00:00
CustomActionOptions,
CommunityOpponents
2024-04-10 21:47:46 +00:00
} from "./types";
import {
generateBattler, drawText, applyText,
validOpponents, validPlayerActions
} from "./utils";
import { createServer } from "node:http";
import GenerateParty from "./party"
GlobalFonts
.registerFromPath(import.meta.dirname + "/LilitaOne.ttf", "Lilita One");
const opponents = {
[Opponents.BattleBeginner]: {
text: "Battle Beginner",
colour: ["#007500", "#FFFFFF"]
},
[Opponents.BattleBeginner2024]: {
text: "Battle Beginner",
colour: ["#007500", "#FFFFFF"]
},
[Opponents.BattleCasual]: {
text: "Battle Casual",
colour: ["#757500", "#FFFFFF"]
2024-04-10 21:47:46 +00:00
},
[Opponents.BattleMaster]: {
text: "Battle Master",
colour: ["#750000", "#FFFFFF"]
2024-04-10 21:47:46 +00:00
},
[Opponents.BattlePro]: {
text: "Battle Pro",
colour: ["#753000", "#FFFFFF"]
2024-04-10 21:47:46 +00:00
},
[Opponents.Battler]: {
text: "Battler",
colour: ["#FFFFFF", "#000000"]
},
[Opponents.BattlerElite]: {
text: "Battler Elite",
colour: ["#FFFFFF", "#FF0000"]
},
[Opponents.None]: {
text: "",
colour: ["#FFFFFF", "#FFFFFF"]
},
[Opponents.RapStar]: {
text: "Rap Star",
colour: ["#FF0000", "#FFFFFF"]
},
[Opponents.ArtMaster]: {
text: "Art Master",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.TheInterrogator]: {
text: "The Interrogator",
colour: ["#FFFFFF", "#000000"]
},
[Opponents.CommonOverseer]: {
text: "Common Overseer",
colour: ["#000000", "#32CD32"]
},
[Opponents.Moonlite]: {
text: "Moonlite",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.Dicey]: {
text: "Dicey",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.Kacey]: {
text: "Kacey",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.Lexa]: {
text: "Lexa",
colour: ["#FFFFFF", "#FF00FF"]
},
[Opponents.Delta]: {
text: "Delta",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.DoodlePro]: {
text: "Doodle Pro",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.GuitarHero]: {
text: "Guitar Hero",
colour: ["#FFFFFF", "#FF8000"]
},
[Opponents.SmallKeyMaster]: {
text: "Small KeyMaster",
colour: ["#FFFFFF", "#32CD32"]
},
[Opponents.RareOverseer]: {
text: "Rare Overseer",
colour: ["#000000", "#00FFFF"]
},
[Opponents.DEMUL]: {
text: "DEMUL",
colour: ["#FFFFFF", "#000000"]
},
[Opponents.AgentZ]: {
text: "Agent Z",
colour: ["#FFFFFF", "#000000"]
},
[Opponents.MediumKeyMaster]: {
text: "Medium KeyMaster",
colour: ["#000000", "#00FFFF"]
},
[Opponents.IncomingSword]: {
text: "Incoming Sword",
colour: ["#FF0000", "#FFFFFF"]
},
[Opponents.Eda]: {
text: "Eda",
colour: ["#9000FF", "#FFFFFF"]
},
[Opponents.TheFirewall]: {
text: "The Firewall",
colour: ["#FFFFFF", "#FFAA00"]
2024-04-22 15:39:06 +00:00
},
2024-04-22 15:51:00 +00:00
[Opponents.HouseannorSupport]: {
2024-04-22 15:39:06 +00:00
text: "Houseannor Support",
colour: ["#FF00FF", "#FFFFFF"]
2024-06-17 18:25:55 +00:00
},
[Opponents.Dicey2024]: {
text: "Dicey",
colour: ["#000000", "#FFFFFF"]
},
[Opponents.CakeDay]: {
text: "Cake Day",
colour: ["#FA61FF", "#FFFFFF"]
},
[Opponents.MasterOG]: {
text: "Master OG",
colour: ["#FF0000", "#FFFFFF"]
2024-08-13 13:42:06 +00:00
},
2024-08-13 14:00:01 +00:00
}
const communityOpponents = {
[Opponents.None]: {
text: "",
colour: ["#FFFFFF", "#FFFFFF"]
},
[CommunityOpponents.Stik]: {
2024-08-13 11:57:47 +00:00
text: "Stik",
colour: ["#FF0000", "#000000"]
},
[CommunityOpponents.ThisMessage]: {
text: "This Message",
colour: ["#000000", "#FFFFFF"]
2024-08-30 12:13:40 +00:00
},
[CommunityOpponents.ProjectIX]: {
text: "Project IX",
colour: ["#FFFFFF", "#000000"]
2024-08-13 11:57:47 +00:00
}
}
2024-04-10 21:47:46 +00:00
const server = createServer(async (req, res) => {
if (req.method === "OPTIONS") {
res.setHeader("access-control-allow-origin", "*")
return res.end();
}
const url = new URL(req.url!, "https://loc.al");
2024-07-13 17:45:53 +00:00
if (req.method == "GET" && url.pathname == "/status") {
return res.end("OK")
}
2024-04-10 21:47:46 +00:00
if (req.method == "GET" && url.pathname == "/battler.png") {
2024-07-10 12:41:18 +00:00
console.log(`Generating battler`)
2024-04-10 21:47:46 +00:00
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions;
const battler = await generateBattler(opts);
res.setHeader('content-type', 'image/png');
return res.end(battler.toBuffer("image/png"), 'binary')
}
if (req.method == "GET" && url.pathname == "/profilebattler.png") {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions & { username: string };
2024-07-10 12:41:18 +00:00
console.log(`Generating profile battler for ${opts.username}`)
2024-04-10 21:47:46 +00:00
const canvas = createCanvas(1280, 1280);
const ctx = canvas.getContext("2d");
const battler = await generateBattler(opts)
ctx.drawImage(battler, 0, 0);
ctx.textAlign = "center"
ctx.font = applyText(canvas, opts.username, canvas.width - 20)
drawText([640, 1100], opts.username, opts.username == "homeannor" ? "#AA00FF" : "#000000", { colour: "#FFFFFF", width: 20 }, ctx)
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
if (
req.method == "GET" &&
(url.pathname == "/battlesquadfight.png" ||
url.pathname == "/battlestart.png")
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions & GenerateBattleImageOptions;
if (!opts.opponent) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error: '"opponent" query parameter not specified',
})
);
}
2024-07-10 12:41:18 +00:00
console.log(`Generating battle start for ${opts.username} and ${opts.opponent}`)
2024-04-10 21:47:46 +00:00
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
if (!validOpponents.includes(opts.opponent)) opts.opponent = Opponents.None
const SquadBackground = await loadImage(
`./assets/battlebgs/${opts.opponent}.png`,
);
context.drawImage(SquadBackground, 0, 0, canvas.width, canvas.height);
const battler = await generateBattler(opts);
context.drawImage(battler, -140, -100, battler.width, battler.height);
context.textAlign = "center"
context.font = applyText(canvas, opponents[opts.opponent].text, 875)
drawText([1450, 1000], opponents[opts.opponent].text, opponents[opts.opponent].colour[1], { colour: opponents[opts.opponent].colour[0], width: 20 }, context);
context.font = applyText(canvas, opts.username, 675)
drawText([470, 1000], opts.username, opts.username == "homeannor" ? "#AA00FF" : "#000000", { colour: "#FFFFFF", width: 20 }, context)
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
2024-08-13 11:57:47 +00:00
if (
req.method == "GET" &&
2024-08-13 12:13:43 +00:00
url.pathname == "/communityvs.png"
2024-08-13 11:57:47 +00:00
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions & GenerateBattleImageOptions;
if (!opts.opponent) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error: '"opponent" query parameter not specified',
})
);
}
console.log(`Generating battle start for ${opts.username} and ${opts.opponent}`)
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
2024-08-13 14:00:01 +00:00
if (!validOpponents.includes(opts.opponent)) opts.opponent = Opponents.None
2024-08-13 11:57:47 +00:00
const SquadBackground = await loadImage(
`./assets/communityvs/${opts.opponent}.png`,
);
context.drawImage(SquadBackground, 0, 0, canvas.width, canvas.height);
const battler = await generateBattler(opts);
context.drawImage(battler, -140, -100, battler.width, battler.height);
context.textAlign = "center"
2024-08-13 14:00:01 +00:00
// @ts-ignore shut up
context.font = applyText(canvas, communityOpponents[opts.opponent].text, 875)
// @ts-ignore shut up
drawText([1450, 1000], communityOpponents[opts.opponent].text, communityOpponents[opts.opponent].colour[1], { colour: communityOpponents[opts.opponent].colour[0], width: 20 }, context);
2024-08-13 11:57:47 +00:00
context.font = applyText(canvas, opts.username, 675)
drawText([470, 1000], opts.username, opts.username == "homeannor" ? "#AA00FF" : "#000000", { colour: "#FFFFFF", width: 20 }, context)
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
2024-04-10 21:47:46 +00:00
if (
req.method == "GET" &&
url.pathname == "/playeraction.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as PlayerActionOptions;
if (!opts.action) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error: '"action" query parameter not specified',
})
);
}
if (!validPlayerActions.includes(opts.action)) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'"action" query parameter not formatted correctly. Got "' +
opts.action +
'".',
})
);
}
2024-08-13 14:00:01 +00:00
console.log(`Generating ${opts.action}`)
2024-07-10 12:41:18 +00:00
2024-04-10 21:47:46 +00:00
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const actionBackground = await loadImage(
`./assets/actionbgs/${opts.action}.png`,
);
context.drawImage(actionBackground, 0, 0, canvas.width, canvas.height);
const pUrl = new URL(encodeURI(opts.player))
const player = Object.fromEntries(
Array.from(pUrl.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
if (opts.action.endsWith("Left")) {
const battler = await generateBattler(player);
context.drawImage(battler, -50, -100, battler.width, battler.height);
} else if (opts.action.endsWith("Right")) {
const battler = await generateBattler({ ...player, direction: "left" });
context.drawImage(battler, (canvas.width / 2) - 300, -100, battler.width, battler.height);
}
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
if (
req.method == "GET" &&
url.pathname == "/customaction.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as CustomActionOptions;
if (!opts.background) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'"background" query parameter not found',
})
);
}
2024-07-10 12:41:18 +00:00
console.log(`Generating custom action (${opts.background})`)
2024-04-10 21:47:46 +00:00
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const actionBackground = await loadImage(
opts.background,
);
context.drawImage(actionBackground, 0, 0, canvas.width, canvas.height);
const pUrl = new URL(encodeURI(opts.player))
const player = Object.fromEntries(
Array.from(pUrl.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const battler = await generateBattler(player);
if (opts.position == "middle") {
context.drawImage(battler, (1920 / 2) - 640, -100, battler.width, battler.height);
} else {
context.drawImage(battler, -50, -100, battler.width, battler.height);
}
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
if (
req.method == "GET" &&
url.pathname == "/playermap.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as { player1: string, player2: string, map: string };
if (!opts.player1 || !opts.player2 || !opts.map) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'Did not pass all parameters',
})
);
}
2024-07-10 12:41:18 +00:00
console.log(`Generating player map (${opts.map})`)
2024-04-10 21:47:46 +00:00
const p1URL = new URL(encodeURI(opts.player1))
const p2URL = new URL(encodeURI(opts.player2))
const p1Opts = Object.fromEntries(
Array.from(p1URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p2Opts = Object.fromEntries(
Array.from(p2URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p1 = await generateBattler(p1Opts);
const p2 = await generateBattler({ ...p2Opts, direction: "left" })
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const Background = await loadImage(
2024-07-09 14:58:17 +00:00
`./assets/playerlocations/${opts.map}.png`,
2024-04-10 21:47:46 +00:00
);
context.drawImage(Background, 0, 0, canvas.width, canvas.height);
context.drawImage(p1, 140, 313, p1.width / 2, p1.height / 2);
context.drawImage(p2, (canvas.width / 2) + 177, 313, p2.width / 2, p2.height / 2);
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
if (
req.method == "GET" &&
url.pathname == "/map.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as { player: string, map: string };
if (!opts.player || !opts.map) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'Did not pass all parameters',
})
);
}
2024-07-10 12:41:18 +00:00
console.log(`Generating map (${opts.map})`)
2024-04-10 21:47:46 +00:00
const p1URL = new URL(encodeURI(opts.player))
const p1Opts = Object.fromEntries(
Array.from(p1URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p1 = await generateBattler(p1Opts);
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const Background = await loadImage(
`./assets/locations/${opts.map}.png`,
);
context.drawImage(Background, 0, 0, canvas.width, canvas.height);
context.drawImage(p1, 140, 313, p1.width / 2, p1.height / 2);
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
2024-08-13 11:57:47 +00:00
if (
req.method == "GET" &&
url.pathname == "/communitymap.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as { player: string, map: string };
if (!opts.player || !opts.map) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'Did not pass all parameters',
})
);
}
console.log(`Generating map (${opts.map})`)
const p1URL = new URL(encodeURI(opts.player))
const p1Opts = Object.fromEntries(
Array.from(p1URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p1 = await generateBattler(p1Opts);
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const Background = await loadImage(
`./assets/communitymaps/${opts.map}.png`,
);
context.drawImage(Background, 0, 0, canvas.width, canvas.height);
context.drawImage(p1, 140, 313, p1.width / 2, p1.height / 2);
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
2024-04-10 21:47:46 +00:00
if (
req.method == "GET" &&
url.pathname == "/pvp.png"
) {
const opts = Object.fromEntries(
Array.from(url.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as { player1: string, player2: string, username1: string, username2: string };
if (!opts.player1 || !opts.player2 || !opts.username1 || !opts.username2) {
res.writeHead(400, { 'content-type': "application/json" })
return res.end(
JSON.stringify({
error:
'Did not pass all parameters',
})
);
}
2024-07-10 12:41:18 +00:00
console.log(`Generating PvP image for ${opts.username1} and ${opts.username2}`)
2024-04-10 21:47:46 +00:00
const p1URL = new URL(encodeURI(opts.player1))
const p2URL = new URL(encodeURI(opts.player2))
const p1Opts = Object.fromEntries(
Array.from(p1URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p2Opts = Object.fromEntries(
Array.from(p2URL.searchParams.entries())
.map(([key, value]) => [key, value.replaceAll('+', ' ')]),
) as GenerateBattlerOptions
const p1 = await generateBattler(p1Opts);
const p2 = await generateBattler({ ...p2Opts, direction: "left" })
// Image dimensions
const canvas = createCanvas(1920, 1080);
const context = canvas.getContext("2d");
const Background = await loadImage(
`./assets/battlebgs/PvP.png`,
);
context.drawImage(Background, 0, 0, canvas.width, canvas.height);
context.drawImage(p1, -140, -100, p1.width, p1.height);
context.drawImage(p2, (canvas.width / 2) - 180, -100, p2.width, p2.height);
context.textAlign = "center"
context.font = applyText(canvas, opts.username2, 675)
drawText([1450, 1000], opts.username2, opts.username2 == "homeannor" ? "#AA00FF" : "#000000", { colour: "#FFFFFF", width: 20 }, context)
context.font = applyText(canvas, opts.username1, 675)
drawText([470, 1000], opts.username1, opts.username1 == "homeannor" ? "#AA00FF" : "#000000", { colour: "#FFFFFF", width: 20 }, context)
res.setHeader('content-type', 'image/png');
return res.end(canvas.toBuffer("image/png"), 'binary')
}
2024-07-10 12:41:18 +00:00
console.log(`Failed request (${req.method}, ${url.pathname})`)
2024-04-10 21:47:46 +00:00
res.writeHead(404)
return res.end("404 Not Found")
})
server.listen(60125, () => {
console.log(`Listening on localhost:60125`)
2024-07-10 12:41:18 +00:00
})