all the things
25
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#af666c",
|
||||
"activityBar.background": "#af666c",
|
||||
"activityBar.foreground": "#e7e7e7",
|
||||
"activityBar.inactiveForeground": "#e7e7e799",
|
||||
"activityBarBadge.background": "#2f542c",
|
||||
"activityBarBadge.foreground": "#e7e7e7",
|
||||
"commandCenter.border": "#e7e7e799",
|
||||
"sash.hoverBorder": "#af666c",
|
||||
"statusBar.background": "#944e53",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#af666c",
|
||||
"statusBarItem.remoteBackground": "#944e53",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#944e53",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#944e5399",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
},
|
||||
"peacock.remoteColor": "#de088c",
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"peacock.color": "#944e53"
|
||||
}
|
14
attackDefs.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const attackRanges = {
|
||||
airToAir: {
|
||||
min: 0,
|
||||
max: 2
|
||||
},
|
||||
airToGround: {
|
||||
min: 0,
|
||||
max: 2
|
||||
},
|
||||
groundToAir: {
|
||||
min: 0,
|
||||
max: 2
|
||||
}
|
||||
}
|
1
camelToSpace.ts
Normal file
@ -0,0 +1 @@
|
||||
export const camelToSpace = (value: string) => value.replace(/[A-Z0-9]/g, e => ` ${e.toLowerCase()}`);
|
BIN
card-images/series1-1.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-10.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-11.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-12.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-13.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-14.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-15.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-16.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-17.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-18.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-19.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-2.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-20.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-21.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-22.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-23.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-24.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-25.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-26.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-27.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-28.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-29.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-3.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-30.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-31.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-32.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-33.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-34.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-35.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-36.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-37.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-38.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-39.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-4.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-40.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-41.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-42.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-43.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-44.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-45.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-46.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-47.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-48.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-49.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-5.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-50.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-51.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-52.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-53.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-54.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-55.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-56.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-57.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-58.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-59.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-6.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-60.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-7.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-8.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
card-images/series1-9.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
1
cardGen/consts.ts
Normal file
@ -0,0 +1 @@
|
||||
export const textSmall = '24px'
|
62
cardGen/rows.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { CanvasRenderingContext2D } from "https://deno.land/x/skia_canvas@0.4.1/mod.ts";
|
||||
import { styleTypeAccessor, TextStyle } from "./textStyle.ts";
|
||||
import { wordWrap } from "./wordWrap.ts";
|
||||
|
||||
type line = { value: string, style: TextStyle, type: styleTypeAccessor, newLine?: boolean, combine?: boolean }
|
||||
|
||||
export const flexRow = (ctx: CanvasRenderingContext2D, sections: string[], maxWidth: number) => {
|
||||
const totalWidth = sections.map(s => ctx.measureText(s).width).reduce((a, b) => a + b, 0);
|
||||
const gap = (maxWidth - totalWidth) / (sections.length - 1);
|
||||
|
||||
ctx.save()
|
||||
for (const section of sections) {
|
||||
ctx.fillText(section, 0, 0);
|
||||
ctx.translate(ctx.measureText(section).width + gap, 0);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export const inlineRow = (ctx: CanvasRenderingContext2D, sections: line[], maxWidth: number) => {
|
||||
const lines: line[] = [];
|
||||
for (const [i, section] of sections.entries()) {
|
||||
if (!section.newLine) {
|
||||
const last = lines[i - 1];
|
||||
if (last) {
|
||||
ctx.font = last.style[last.type];
|
||||
const lastWidth = ctx.measureText(last.value).width;
|
||||
ctx.font = section.style[section.type];
|
||||
lines.push(...wordWrap(ctx, section.value, maxWidth, maxWidth - lastWidth).map((e, i): line => ({
|
||||
...section,
|
||||
value: e,
|
||||
combine: i === 0
|
||||
})));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ctx.font = section.style[section.type];
|
||||
lines.push(...wordWrap(ctx, section.value, maxWidth).map((e): line => ({
|
||||
...section,
|
||||
value: e
|
||||
})));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
export const drawRow = (ctx: CanvasRenderingContext2D, lines: line[]) => {
|
||||
for (const [i, line] of lines.entries()) {
|
||||
const last = lines[i - 1];
|
||||
if (line.combine && last) {
|
||||
ctx.save();
|
||||
ctx.font = last.style[last.type];
|
||||
ctx.translate(ctx.measureText(last.value).width, 0);
|
||||
ctx.font = line.style[line.type];
|
||||
ctx.fillText(line.value, 0, 0);
|
||||
ctx.restore();
|
||||
} else {
|
||||
ctx.translate(0, line.style.lineHeight);
|
||||
ctx.font = line.style[line.type];
|
||||
ctx.fillText(line.value, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
22
cardGen/textStyle.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export type styleTypeAccessor = 'default' | 'bold' | 'italic' | 'boldItalic';
|
||||
|
||||
export class TextStyle {
|
||||
fontSize: number;
|
||||
lineHeight: number;
|
||||
constructor(fontSize: number, lineHeight?: number) {
|
||||
this.fontSize = fontSize;
|
||||
this.lineHeight = lineHeight || fontSize;
|
||||
}
|
||||
get default() {
|
||||
return `${this.fontSize}px Chakra Petch`
|
||||
}
|
||||
get bold() {
|
||||
return `bold ${this.fontSize}px Chakra Petch`
|
||||
}
|
||||
get italic() {
|
||||
return `italic ${this.fontSize}px Chakra Petch`
|
||||
}
|
||||
get boldItalic() {
|
||||
return `italic bold ${this.fontSize}px Chakra Petch`
|
||||
}
|
||||
}
|
24
cardGen/wordWrap.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { CanvasRenderingContext2D } from "https://deno.land/x/skia_canvas@0.4.1/mod.ts";
|
||||
|
||||
export const wordWrap = (ctx: CanvasRenderingContext2D, text: string, maxWidth: number, lineWidth?: number) => {
|
||||
const words = text.split(' ');
|
||||
let testLine = '';
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const word of words) {
|
||||
const previous = testLine;
|
||||
testLine += ` ${word}`;
|
||||
testLine = testLine.replace(/^\s/, '');
|
||||
const { width } = ctx.measureText(testLine);
|
||||
|
||||
const calcedWidth = lineWidth && lines.length === 0 ? lineWidth : maxWidth;
|
||||
|
||||
if (width > calcedWidth) {
|
||||
lines.push(previous);
|
||||
testLine = word;
|
||||
}
|
||||
}
|
||||
if (testLine) lines.push(testLine);
|
||||
|
||||
return lines;
|
||||
}
|
BIN
fonts/ChakraPetch-Bold.ttf
Normal file
BIN
fonts/ChakraPetch-BoldItalic.ttf
Normal file
BIN
fonts/ChakraPetch-Italic.ttf
Normal file
BIN
fonts/ChakraPetch-Light.ttf
Normal file
BIN
fonts/ChakraPetch-LightItalic.ttf
Normal file
BIN
fonts/ChakraPetch-Medium.ttf
Normal file
BIN
fonts/ChakraPetch-MediumItalic.ttf
Normal file
BIN
fonts/ChakraPetch-Regular.ttf
Normal file
BIN
fonts/ChakraPetch-SemiBold.ttf
Normal file
BIN
fonts/ChakraPetch-SemiBoldItalic.ttf
Normal file
BIN
fonts/Rajdhani-Bold.ttf
Normal file
BIN
fonts/Rajdhani-Light.ttf
Normal file
BIN
fonts/Rajdhani-Medium.ttf
Normal file
BIN
fonts/Rajdhani-Regular.ttf
Normal file
BIN
fonts/Rajdhani-SemiBold.ttf
Normal file
176
generateCardImages.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import { createCanvas, Fonts, Image } from "https://deno.land/x/skia_canvas@0.4.1/mod.ts";
|
||||
import { drawRow, flexRow, inlineRow } from "./cardGen/rows.ts";
|
||||
import { TextStyle } from "./cardGen/textStyle.ts";
|
||||
import { wordWrap } from "./cardGen/wordWrap.ts";
|
||||
import { card } from './types.ts';
|
||||
import { camelToSpace } from './camelToSpace.ts';
|
||||
|
||||
const bg = Image.loadSync('./bg.jpg');
|
||||
|
||||
Fonts.registerDir('./fonts');
|
||||
|
||||
// Fonts.setAlias('Petch', 'Chakra Petch')
|
||||
|
||||
const xsText = new TextStyle(32, 36);
|
||||
const mdText = new TextStyle(40, 46);
|
||||
const smText = new TextStyle(34, 40);
|
||||
const lgText = new TextStyle(60, 66)
|
||||
|
||||
const width = Math.floor(2.74 * 300);
|
||||
const height = Math.floor(3.74 * 300);
|
||||
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const cutAreaWidth = 750;
|
||||
const cutAreaHeight = 1050;
|
||||
const _cutArea = {
|
||||
wStart: (canvas.width - cutAreaWidth) / 2,
|
||||
wEnd: canvas.width - ((canvas.width - cutAreaWidth) / 2),
|
||||
hStart: (canvas.height - cutAreaHeight) / 2,
|
||||
hEnd: canvas.height - ((canvas.height - cutAreaHeight) / 2),
|
||||
}
|
||||
const safeAreaWidth = 690;
|
||||
const safeAreaHeight = 990;
|
||||
const safeArea = {
|
||||
wStart: (canvas.width - safeAreaWidth) / 2,
|
||||
wEnd: canvas.width - ((canvas.width - safeAreaWidth) / 2),
|
||||
hStart: (canvas.height - safeAreaHeight) / 2,
|
||||
hEnd: canvas.height - ((canvas.height - safeAreaHeight) / 2),
|
||||
}
|
||||
|
||||
const cards: card[] = JSON.parse(Deno.readTextFileSync('./series1.json'));
|
||||
// const card = cards[12];
|
||||
// Safe Area
|
||||
// ctx.strokeStyle = 'green'
|
||||
// ctx.beginPath()
|
||||
// ctx.roundRect(safeArea.wStart, safeArea.hStart, safeAreaWidth, safeAreaHeight, 24)
|
||||
// ctx.stroke()
|
||||
|
||||
// Cut Area
|
||||
// ctx.strokeStyle = 'red'
|
||||
// ctx.beginPath()
|
||||
// ctx.roundRect(cutArea.wStart, cutArea.hStart, cutAreaWidth, cutAreaHeight, 40)
|
||||
// ctx.stroke()
|
||||
|
||||
for (const [i, card] of cards.entries()) {
|
||||
ctx.drawImage(bg, 0, 0)
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
|
||||
ctx.save();
|
||||
ctx.textBaseline = "top"
|
||||
ctx.fillStyle = 'black'
|
||||
ctx.translate(safeArea.wStart, safeArea.hStart)
|
||||
ctx.font = lgText.bold;
|
||||
ctx.fillText(card.type.toLocaleUpperCase(), 0, 0)
|
||||
ctx.translate(0, lgText.lineHeight + 40);
|
||||
|
||||
const blurbs = {
|
||||
bad: `Bad luck! You must take one of the following penalties`,
|
||||
good: `Select either one maneuver or one attack to make. If you can't complete any action from this card, you must take one penalty listed. If there are no penalties listed, discard this card.`
|
||||
}
|
||||
const blurb = card.attacks.length || card.maneuvers.length ? blurbs.good : blurbs.bad;
|
||||
ctx.font = xsText.italic
|
||||
const blurbLines = wordWrap(ctx, blurb, safeAreaWidth);
|
||||
for (const line of blurbLines) {
|
||||
ctx.fillText(line, 0, 0);
|
||||
ctx.translate(0, smText.lineHeight);
|
||||
}
|
||||
|
||||
ctx.font = mdText.default
|
||||
if (card.attacks.length) {
|
||||
ctx.translate(0, -20)
|
||||
const attackLines = inlineRow(
|
||||
ctx,
|
||||
[
|
||||
{
|
||||
value: 'Attacks: ',
|
||||
style: mdText,
|
||||
type: 'bold',
|
||||
},
|
||||
{
|
||||
value: card.attacks.map(a => camelToSpace(a)).join(', '),
|
||||
style: mdText,
|
||||
type: 'default'
|
||||
}
|
||||
],
|
||||
safeAreaWidth
|
||||
);
|
||||
drawRow(ctx, attackLines);
|
||||
}
|
||||
if (card.maneuvers.length) {
|
||||
ctx.translate(0, 20)
|
||||
const maneuverLines = inlineRow(
|
||||
ctx,
|
||||
[
|
||||
{
|
||||
value: 'Maneuvers: ',
|
||||
style: mdText,
|
||||
type: 'bold',
|
||||
},
|
||||
{
|
||||
value: card.maneuvers.map(m => camelToSpace(m.name)).join(', '),
|
||||
style: mdText,
|
||||
type: 'default'
|
||||
}
|
||||
],
|
||||
safeAreaWidth
|
||||
);
|
||||
drawRow(ctx, maneuverLines);
|
||||
}
|
||||
|
||||
if (card.penalties.length) {
|
||||
ctx.translate(0, 80);
|
||||
ctx.strokeStyle = 'purple';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(safeAreaWidth, 0);
|
||||
ctx.stroke();
|
||||
ctx.translate(0, -20);
|
||||
const penaltyLines = inlineRow(
|
||||
ctx,
|
||||
[
|
||||
{
|
||||
value: card.penalties.length === 1 ? 'Penalty: ' : 'Penalties: ',
|
||||
style: mdText,
|
||||
type: 'bold',
|
||||
},
|
||||
{
|
||||
value: card.penalties.join(', '),
|
||||
style: mdText,
|
||||
type: 'default'
|
||||
}
|
||||
],
|
||||
safeAreaWidth
|
||||
);
|
||||
drawRow(ctx, penaltyLines);
|
||||
}
|
||||
|
||||
|
||||
ctx.restore();
|
||||
ctx.fillStyle = 'grey'
|
||||
ctx.font = xsText.default;
|
||||
ctx.save();
|
||||
ctx.translate(safeArea.wStart, safeArea.hEnd);
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -xsText.lineHeight);
|
||||
ctx.lineTo(safeAreaWidth, -xsText.lineHeight);
|
||||
ctx.stroke();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Nightfall: Blood Red Skies', safeAreaWidth / 2, -xsText.lineHeight - 12);
|
||||
ctx.textAlign = 'left'
|
||||
flexRow(ctx, ['Series 1', `${i + 1}/60`, `© ${new Date().getFullYear()} Cyborg Grizzly Games`], safeAreaWidth);
|
||||
ctx.restore();
|
||||
|
||||
try {
|
||||
canvas.save(`./card-images/series1-${i + 1}.png`, "png")
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log(i);
|
||||
}
|
||||
// canvas.save(`./testImg.png`, "png")
|
||||
}
|
94
generateHtml.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { camelToSpace } from "./camelToSpace.ts";
|
||||
import { card } from "./types.ts";
|
||||
|
||||
const series = 1;
|
||||
|
||||
const template = (cards: string[]) => `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blood Red Skies Cards Series ${series}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Chakra Petch'
|
||||
]
|
||||
},
|
||||
extend: {
|
||||
aspectRatio: {
|
||||
card: '2.5 / 3.5'
|
||||
},
|
||||
container: {
|
||||
center: true
|
||||
},
|
||||
colors: {
|
||||
gunmetal: '#0a2e36',
|
||||
green: {
|
||||
10: '#09a129',
|
||||
100: '#036d19'
|
||||
},
|
||||
violet: {
|
||||
10: '#8a7090',
|
||||
50: '#8d3b72'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer base {
|
||||
* {
|
||||
@apply dark:text-white text-black
|
||||
}
|
||||
hr {
|
||||
@apply !border-violet-10
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&family=Rajdhani:wght@300;400;500;600;700&display=swap');
|
||||
</style>
|
||||
</head>
|
||||
<body class="dark:bg-gunmetal">
|
||||
<div class="container p-8 grid grid-cols-4 gap-8 bg-purple">
|
||||
${cards.join('\n')}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const cards: card[] = JSON.parse(decoder.decode(Deno.readFileSync(`./series${series}.json`)));
|
||||
|
||||
const html = template(cards.map((c, i) => `
|
||||
<div class="rounded-lg aspect-card p-8 shadow-lg flex flex-wrap dark:bg-green-700">
|
||||
<div class="basis-full">
|
||||
<p class="mb-4 font-extrabold uppercase text-xl">${c.type}</p>
|
||||
|
||||
${c.attacks.length || c.maneuvers.length ?
|
||||
`<p class="mb-8 text-sm">Select either one maneuver or one attack to make. If you can't complete one action from this card, you must take one penalty listed. If there are no penalties listed, discard this card.</p>` :
|
||||
`<p class="mb-8 text-sm">Bad luck! You must take one of the following penalties</p>`
|
||||
}
|
||||
|
||||
${c.maneuvers.length > 0 ? `<p class="capitalize my-8"><span class="font-bold">Maneuvers</span>: ${c.maneuvers.map(a => camelToSpace(a.name)).join(', ')}</p>` : ''}
|
||||
${c.attacks.length > 0 ? `<p class="capitalize my-8"><span class="font-bold">Attacks</span>: ${c.attacks.map(a => camelToSpace(a)).join(', ')}</p>` : ''}
|
||||
${c.penalties.length > 0 ? `<hr/><p class="capitalize my-8"><span class="font-bold">Penalties</span>: ${c.penalties.join(', ')}</p>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="text-xs self-end flex justify-between w-full text-white/50">
|
||||
<p class="dark:text-white/50 text-black/50">Series ${series}</p>
|
||||
<p class="dark:text-white/50 text-black/50">${i + 1}/${cards.length}</p>
|
||||
<p class="dark:text-white/50 text-black/50">© ${new Date().getFullYear()} Cyborg Grizzly Games</p>
|
||||
</div>
|
||||
</div>
|
||||
`))
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
Deno.writeFile(`./series${series}.html`, encoder.encode(html));
|
15
index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blood Red Skies Cards</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container grid grid-cols-4">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
67
index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { attackRanges } from "./attackDefs.ts";
|
||||
import { move, moves } from "./moveDefs.ts";
|
||||
import { penaltyDefs } from "./penaltyDefs.ts";
|
||||
import { card } from "./types.ts";
|
||||
|
||||
const series = Deno.args.find(a => a.includes('series'))?.split('=')[1];
|
||||
console.log(`Generating cards for series ${series}`);
|
||||
|
||||
const cardCount = 60;
|
||||
|
||||
const cards: card[] = [];
|
||||
|
||||
while (cards.length < cardCount) {
|
||||
const maneuvers: move[] = [];
|
||||
const maneuverCount = Math.floor(Math.random() * 3);
|
||||
while (maneuvers.length < maneuverCount) {
|
||||
const move = moves[Math.floor(Math.random() * moves.length)];
|
||||
if (!maneuvers.includes(move)) maneuvers.push(move);
|
||||
}
|
||||
|
||||
const attacks: string[] = [];
|
||||
|
||||
for (const [name, attack] of Object.entries(attackRanges)) {
|
||||
const rand = Math.random() * 10;
|
||||
if (rand < attack.max) attacks.push(name);
|
||||
}
|
||||
|
||||
const penalties = penaltyDefs.map(value => ({ value, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ value }) => value).splice(0, Math.min(3, Math.floor(Math.random() * (penaltyDefs.length + 1))))
|
||||
.sort((a,b) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const card: card = {
|
||||
maneuvers,
|
||||
attacks,
|
||||
penalties,
|
||||
type: 'action'
|
||||
};
|
||||
|
||||
if (card.penalties.length === 0 && card.attacks.includes('groundToAir')) card.penalties.push('Land Battle');
|
||||
|
||||
// TODO: optimize this. Cards should be stored in a map with the key being the stringification of a normalized concatenation of all properties
|
||||
for (const existing of cards) {
|
||||
const everyAttack = existing.attacks.every(a => card.attacks.includes(a)) && card.attacks.every(a => existing.attacks.includes(a))
|
||||
const everyManeuver = existing.maneuvers.every(a => card.maneuvers.find(e => e.name === a.name)) && card.maneuvers.every(a => existing.maneuvers.find(e => e.name === a.name));
|
||||
const everyPenalty = existing.penalties.every(a => card.penalties.includes(a)) && card.penalties.every(a => existing.penalties.includes(a))
|
||||
|
||||
if (everyAttack && everyManeuver && everyPenalty) {
|
||||
card.needsEffect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(card.maneuvers.length || card.attacks.length || card.penalties.length) && cards.push(card);
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
Deno.writeFile(`./series${series}.json`, encoder.encode(JSON.stringify(cards, null, 2)));
|
||||
console.log('Done');
|
46
moveDefs.ts
Normal file
@ -0,0 +1,46 @@
|
||||
export interface move {
|
||||
complexity: 'simple' | 'complex';
|
||||
length?: number;
|
||||
facing?: number;
|
||||
horizontalOffset?: number;
|
||||
evasiveness?: number;
|
||||
name: string
|
||||
}
|
||||
|
||||
export const moves: move[] = [
|
||||
{
|
||||
name: 'immelmann',
|
||||
complexity: 'complex',
|
||||
length: 10
|
||||
},
|
||||
{
|
||||
name: 'bankedTurn',
|
||||
complexity: 'simple',
|
||||
length: 6
|
||||
},
|
||||
{
|
||||
name: 'barrelRoll',
|
||||
complexity: 'complex',
|
||||
length: 8
|
||||
},
|
||||
{
|
||||
name: 'straight',
|
||||
complexity: 'simple',
|
||||
length: 4
|
||||
},
|
||||
{
|
||||
name: 'tightBank',
|
||||
complexity: 'complex',
|
||||
length: 4
|
||||
},
|
||||
{
|
||||
name: 'turn',
|
||||
complexity: 'simple',
|
||||
length: 4
|
||||
},
|
||||
{
|
||||
name: 'sBend',
|
||||
complexity: 'simple',
|
||||
length: 4
|
||||
},
|
||||
]
|
5
penaltyDefs.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const penaltyDefs = [
|
||||
'Stall',
|
||||
'Damage',
|
||||
'Ground Battle'
|
||||
]
|
1198
series1.html
Normal file
934
series1.json
Normal file
@ -0,0 +1,934 @@
|
||||
[
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "turn",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir",
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround",
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "turn",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround",
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToAir",
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
},
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "straight",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "turn",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
},
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "bankedTurn",
|
||||
"complexity": "simple",
|
||||
"length": 6
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToAir"
|
||||
],
|
||||
"penalties": [],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action",
|
||||
"needsEffect": true
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"name": "barrelRoll",
|
||||
"complexity": "complex",
|
||||
"length": 8
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"groundToAir"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "immelmann",
|
||||
"complexity": "complex",
|
||||
"length": 10
|
||||
}
|
||||
],
|
||||
"attacks": [
|
||||
"airToGround"
|
||||
],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle"
|
||||
],
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"name": "sBend",
|
||||
"complexity": "simple",
|
||||
"length": 4
|
||||
},
|
||||
{
|
||||
"name": "tightBank",
|
||||
"complexity": "complex",
|
||||
"length": 4
|
||||
}
|
||||
],
|
||||
"attacks": [],
|
||||
"penalties": [
|
||||
"Damage",
|
||||
"Land Battle",
|
||||
"Stall"
|
||||
],
|
||||
"type": "action"
|
||||
}
|
||||
]
|
BIN
testImg.png
Normal file
After Width: | Height: | Size: 1.2 MiB |