Async imports support, aggressive uglifying
This commit is contained in:
parent
5e4af4d923
commit
576e191631
|
|
@ -14,9 +14,11 @@
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/html-minifier": "4.0.5",
|
"@types/html-minifier": "4.0.5",
|
||||||
"@types/inquirer": "9.0.7",
|
"@types/inquirer": "9.0.7",
|
||||||
|
"assemblyscript": "0.27.29",
|
||||||
"bun-lightningcss": "0.2.0",
|
"bun-lightningcss": "0.2.0",
|
||||||
"html-minifier": "4.0.0",
|
"html-minifier": "4.0.0",
|
||||||
"inquirer": "9.3.4",
|
"inquirer": "9.3.4",
|
||||||
"typescript": "5.5.2"
|
"typescript": "5.5.2",
|
||||||
|
"uglify-js": "3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { minify } from 'html-minifier';
|
import { minify } from 'html-minifier';
|
||||||
|
import UglifyJS from 'uglify-js';
|
||||||
|
|
||||||
|
import wasmPlugin from './wasmPlugin';
|
||||||
import imagePlugin from './imagePlugin';
|
import imagePlugin from './imagePlugin';
|
||||||
import fontPlugin from './fontPlugin';
|
import fontPlugin from './fontPlugin';
|
||||||
import lightningcss from 'bun-lightningcss';
|
import lightningcss from 'bun-lightningcss';
|
||||||
|
|
||||||
import { getGames } from './isGame';
|
import { getGames } from './isGame';
|
||||||
|
|
||||||
const transpiler = new Bun.Transpiler();
|
|
||||||
|
|
||||||
export async function buildHTML(game: string, production = false) {
|
export async function buildHTML(game: string, production = false) {
|
||||||
const html = await Bun.file(path.resolve(import.meta.dir, '..', 'assets', 'index.html')).text();
|
const html = await Bun.file(path.resolve(import.meta.dir, '..', 'assets', 'index.html')).text();
|
||||||
const bundle = await Bun.build({
|
const bundle = await Bun.build({
|
||||||
outdir: '/tmp',
|
outdir: '/tmp',
|
||||||
entrypoints: [path.resolve(import.meta.dir, '..', 'index.ts')],
|
entrypoints: [path.resolve(import.meta.dir, '..', 'index.ts')],
|
||||||
sourcemap: production ? 'none' : 'inline',
|
sourcemap: production ? 'none' : 'inline',
|
||||||
minify: production,
|
|
||||||
define: {
|
define: {
|
||||||
global: 'window',
|
global: 'window',
|
||||||
GAME: `"${game}"`,
|
GAME: `"${game}"`,
|
||||||
|
|
@ -24,6 +23,7 @@ export async function buildHTML(game: string, production = false) {
|
||||||
plugins: [
|
plugins: [
|
||||||
imagePlugin,
|
imagePlugin,
|
||||||
fontPlugin,
|
fontPlugin,
|
||||||
|
wasmPlugin,
|
||||||
lightningcss(),
|
lightningcss(),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -38,17 +38,39 @@ export async function buildHTML(game: string, production = false) {
|
||||||
if (await iconFile.exists()) {
|
if (await iconFile.exists()) {
|
||||||
icon = `<link rel="icon" href="data:;base64,${Buffer.from(await iconFile.arrayBuffer()).toString('base64')}" />`;
|
icon = `<link rel="icon" href="data:;base64,${Buffer.from(await iconFile.arrayBuffer()).toString('base64')}" />`;
|
||||||
}
|
}
|
||||||
const script = await bundle.outputs[0].text();
|
let script = await bundle.outputs[0].text();
|
||||||
|
const inits = new Set<string>();
|
||||||
|
script = script.replace(/var (init_[^ ]+) = __esm\(\(\)/g, (_, $1) => {
|
||||||
|
inits.add($1);
|
||||||
|
return `var ${$1} = __esm(async ()`;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const init of inits) {
|
||||||
|
script = script.replaceAll(`${init}()`, `await ${init}()`);
|
||||||
|
}
|
||||||
|
|
||||||
|
script = script.replaceAll('await Promise.resolve().then(() =>', '(');
|
||||||
|
if (production) {
|
||||||
|
const minifyResult = UglifyJS.minify(script, {
|
||||||
|
module: true,
|
||||||
|
});
|
||||||
|
if (minifyResult.error) {
|
||||||
|
console.warn(`Minify error: ${minifyResult.error}`);
|
||||||
|
} else {
|
||||||
|
script = minifyResult.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resultHTML = html
|
const resultHTML = html
|
||||||
.replace('$SCRIPT$', `<script>${script}</script>`)
|
.replace('$SCRIPT$', `<script type="module">${script}</script>`)
|
||||||
.replace('$TITLE$', game[0].toUpperCase() + game.slice(1).toLowerCase())
|
.replace('$TITLE$', game[0].toUpperCase() + game.slice(1).toLowerCase())
|
||||||
.replace('$ICON$', icon);
|
.replace('$ICON$', icon);
|
||||||
|
|
||||||
return minify(resultHTML, {
|
return minify(resultHTML, {
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: production,
|
||||||
decodeEntities: true,
|
decodeEntities: true,
|
||||||
minifyCSS: true,
|
minifyCSS: production,
|
||||||
|
minifyJS: production,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed: ', !bundle.success, bundle);
|
console.error('Failed: ', !bundle.success, bundle);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,14 @@ const imagePlugin: BunPlugin = {
|
||||||
return {
|
return {
|
||||||
contents: `
|
contents: `
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
img.onload = resolve;
|
||||||
|
img.onerror = reject;
|
||||||
|
});
|
||||||
img.src = (${JSON.stringify(src)});
|
img.src = (${JSON.stringify(src)});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
|
||||||
export default img;
|
export default img;
|
||||||
`,
|
`,
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { plugin, type BunPlugin } from "bun";
|
||||||
|
import path from 'path';
|
||||||
|
import asc from 'assemblyscript/asc';
|
||||||
|
|
||||||
|
const wasmPlugin: BunPlugin = {
|
||||||
|
name: "WASM loader",
|
||||||
|
async setup(build) {
|
||||||
|
build.onLoad({ filter: /\.wasm\.ts$/ }, async (args) => {
|
||||||
|
const wasmPath = path.resolve(import.meta.dir, '..', '..', 'dist', 'tmp.wasm');
|
||||||
|
const jsPath = wasmPath.replace(/\.wasm$/, '.js');
|
||||||
|
const { error, stderr } = await asc.main([
|
||||||
|
args.path,
|
||||||
|
'--outFile', wasmPath,
|
||||||
|
'--textFile', wasmPath.replace(/\.wasm$/, '.wat'),
|
||||||
|
'--optimize', '--bindings', 'esm',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(stderr.toString(), error.message);
|
||||||
|
throw error;
|
||||||
|
} else {
|
||||||
|
const jsContent = await Bun.file(jsPath).text();
|
||||||
|
const wasmContent = await Bun.file(wasmPath).arrayBuffer();
|
||||||
|
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
|
||||||
|
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
loader: 'js',
|
||||||
|
contents: jsContent
|
||||||
|
.replace(/new URL\([^)]*\)/, `new URL(${JSON.stringify(wasmURL)})`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin(wasmPlugin);
|
||||||
|
|
||||||
|
export default wasmPlugin;
|
||||||
|
|
@ -36,7 +36,7 @@ export class BrickDisplay implements Display {
|
||||||
}
|
}
|
||||||
|
|
||||||
set score(value) {
|
set score(value) {
|
||||||
this.#score = (value | 0) % 1000000000;
|
this.#score = Math.max(0, (value | 0) % 1000000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
get speed() {
|
get speed() {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export const nextFrame = async (): Promise<number> => new Promise((resolve) => r
|
||||||
|
|
||||||
export const randInt = (min: number, max: number) => Math.round(min + (max - min - 1) * Math.random());
|
export const randInt = (min: number, max: number) => Math.round(min + (max - min - 1) * Math.random());
|
||||||
export const randBool = () => Math.random() < 0.5;
|
export const randBool = () => Math.random() < 0.5;
|
||||||
export const choice = (array: any[]) => array[randInt(0, array.length)];
|
export const choice = <T>(array: T[]): T => array[randInt(0, array.length)];
|
||||||
export const weightedChoice = <T extends string | number>(options: [T, number][] | Partial<Record<T, number>>): T | null => {
|
export const weightedChoice = <T extends string | number>(options: [T, number][] | Partial<Record<T, number>>): T | null => {
|
||||||
if (!Array.isArray(options)) {
|
if (!Array.isArray(options)) {
|
||||||
options = Object.entries(options) as [T, number][];
|
options = Object.entries(options) as [T, number][];
|
||||||
|
|
@ -22,6 +22,16 @@ export const weightedChoice = <T extends string | number>(options: [T, number][]
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
export const shuffle = <T>(array: T[]): T[] => {
|
||||||
|
const shuffledArray = [...array];
|
||||||
|
|
||||||
|
for (let i = shuffledArray.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return shuffledArray;
|
||||||
|
}
|
||||||
|
|
||||||
export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k);
|
export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k);
|
||||||
export const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
export const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 115 B |
Binary file not shown.
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 241 B |
|
|
@ -1,4 +1,5 @@
|
||||||
import { BrickDisplay, type BrickDisplayImage } from "@common/display";
|
import { BrickDisplay, type BrickDisplayImage } from "@common/display";
|
||||||
|
import { choice, shuffle } from "@common/utils";
|
||||||
|
|
||||||
const emptySprite: BrickDisplayImage = { image: [], width: 0, height: 0 };
|
const emptySprite: BrickDisplayImage = { image: [], width: 0, height: 0 };
|
||||||
|
|
||||||
|
|
@ -54,26 +55,26 @@ export const ITEMS: Item[] = [
|
||||||
},
|
},
|
||||||
{ // SWORD
|
{ // SWORD
|
||||||
sprite: emptySprite,
|
sprite: emptySprite,
|
||||||
accuracy: 1,
|
accuracy: 0.8,
|
||||||
damage: 5,
|
damage: 5,
|
||||||
ranged: false,
|
ranged: false,
|
||||||
},
|
},
|
||||||
{ // BIG_SWORD
|
{ // BIG_SWORD
|
||||||
sprite: emptySprite,
|
sprite: emptySprite,
|
||||||
accuracy: 0.9,
|
accuracy: 0.7,
|
||||||
damage: 10,
|
damage: 10,
|
||||||
ranged: false,
|
ranged: false,
|
||||||
},
|
},
|
||||||
{ // BOW
|
{ // BOW
|
||||||
sprite: emptySprite,
|
sprite: emptySprite,
|
||||||
accuracy: 0.7,
|
accuracy: 0.6,
|
||||||
damage: 5,
|
damage: 4,
|
||||||
ranged: true,
|
ranged: true,
|
||||||
},
|
},
|
||||||
{ // GRENADE
|
{ // GRENADE
|
||||||
sprite: emptySprite,
|
sprite: emptySprite,
|
||||||
accuracy: 0.8,
|
accuracy: 0.8,
|
||||||
damage: 10,
|
damage: 8,
|
||||||
ranged: true,
|
ranged: true,
|
||||||
consumable: true,
|
consumable: true,
|
||||||
},
|
},
|
||||||
|
|
@ -155,6 +156,102 @@ export const MONSTERS: Monster[] = [
|
||||||
ranged: true,
|
ranged: true,
|
||||||
secondLootChance: 0,
|
secondLootChance: 0,
|
||||||
},
|
},
|
||||||
|
{ // SLIME
|
||||||
|
sprite: emptySprite,
|
||||||
|
health: 5,
|
||||||
|
maxHealth: 5,
|
||||||
|
damage: 3,
|
||||||
|
accuracy: 0.6,
|
||||||
|
lootTable: {
|
||||||
|
[Items.HEAL]: 0.5,
|
||||||
|
[Items.SWORD]: 0.3,
|
||||||
|
[Items.POTION]: 0.15,
|
||||||
|
[Items.BOW]: 0.05,
|
||||||
|
[Items.HEAVY_SWORD]: 0.01,
|
||||||
|
},
|
||||||
|
ranged: false,
|
||||||
|
secondLootChance: 0.3,
|
||||||
|
},
|
||||||
|
{ // DEMON
|
||||||
|
sprite: emptySprite,
|
||||||
|
health: 6,
|
||||||
|
maxHealth: 6,
|
||||||
|
damage: 3,
|
||||||
|
accuracy: 0.8,
|
||||||
|
lootTable: {
|
||||||
|
[Items.HEAL]: 0.5,
|
||||||
|
[Items.SWORD]: 0.3,
|
||||||
|
[Items.POTION]: 0.15,
|
||||||
|
[Items.BOW]: 0.1,
|
||||||
|
[Items.HEAVY_SWORD]: 0.05,
|
||||||
|
},
|
||||||
|
ranged: false,
|
||||||
|
secondLootChance: 0.3,
|
||||||
|
},
|
||||||
|
{ // ELEMENTAL
|
||||||
|
sprite: emptySprite,
|
||||||
|
health: 7,
|
||||||
|
maxHealth: 7,
|
||||||
|
damage: 4,
|
||||||
|
accuracy: 0.5,
|
||||||
|
lootTable: {
|
||||||
|
[Items.HEAL]: 0.2,
|
||||||
|
[Items.POTION]: 0.2,
|
||||||
|
[Items.BOW]: 0.3,
|
||||||
|
[Items.GRENADE]: 0.2,
|
||||||
|
},
|
||||||
|
ranged: true,
|
||||||
|
secondLootChance: 0.3,
|
||||||
|
},
|
||||||
|
{ // BIG_SLIME
|
||||||
|
sprite: emptySprite,
|
||||||
|
health: 10,
|
||||||
|
maxHealth: 10,
|
||||||
|
damage: 5,
|
||||||
|
accuracy: 0.6,
|
||||||
|
lootTable: {
|
||||||
|
[Items.HEAL]: 0.5,
|
||||||
|
[Items.SWORD]: 0.3,
|
||||||
|
[Items.POTION]: 0.2,
|
||||||
|
[Items.BOW]: 0.3,
|
||||||
|
[Items.GRENADE]: 0.2,
|
||||||
|
[Items.HEAVY_SWORD]: 0.1,
|
||||||
|
},
|
||||||
|
ranged: false,
|
||||||
|
secondLootChance: 0.7,
|
||||||
|
},
|
||||||
|
{ // BIG_ELEMENTAL
|
||||||
|
sprite: emptySprite,
|
||||||
|
health: 20,
|
||||||
|
maxHealth: 20,
|
||||||
|
damage: 6,
|
||||||
|
accuracy: 0.9,
|
||||||
|
lootTable: {},
|
||||||
|
ranged: true,
|
||||||
|
secondLootChance: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MONSTERS_ORDER = [
|
||||||
|
...shuffle([Monsters.SMALL_SLIME, Monsters.SMALL_DEMON, Monsters.SNAKE]),
|
||||||
|
Monsters.SMALL_ELEMENTAL,
|
||||||
|
choice([Monsters.SMALL_SLIME, Monsters.SMALL_DEMON, Monsters.SNAKE]),
|
||||||
|
|
||||||
|
...shuffle([Monsters.SLIME, Monsters.DEMON]),
|
||||||
|
Monsters.ELEMENTAL,
|
||||||
|
choice([Monsters.SLIME, Monsters.DEMON]),
|
||||||
|
|
||||||
|
Monsters.BIG_SLIME,
|
||||||
|
choice([
|
||||||
|
Monsters.SMALL_SLIME,
|
||||||
|
Monsters.SMALL_DEMON,
|
||||||
|
Monsters.SNAKE,
|
||||||
|
Monsters.SMALL_ELEMENTAL,
|
||||||
|
Monsters.SLIME,
|
||||||
|
Monsters.DEMON,
|
||||||
|
Monsters.ELEMENTAL,
|
||||||
|
]),
|
||||||
|
Monsters.BIG_ELEMENTAL,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function loadData(spritesheet: BrickDisplayImage) {
|
export function loadData(spritesheet: BrickDisplayImage) {
|
||||||
|
|
@ -164,7 +261,7 @@ export function loadData(spritesheet: BrickDisplayImage) {
|
||||||
|
|
||||||
for (let i = 0; i < MONSTERS.length; i++) {
|
for (let i = 0; i < MONSTERS.length; i++) {
|
||||||
const x = (i % 8) << 2;
|
const x = (i % 8) << 2;
|
||||||
const y = (3 + i / 8) << 2;
|
const y = (2 + i / 8) << 2;
|
||||||
MONSTERS[i].sprite = BrickDisplay.extractSprite(spritesheet, x, y, 4, 4);
|
MONSTERS[i].sprite = BrickDisplay.extractSprite(spritesheet, x, y, 4, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { BrickDisplay, type BrickDisplayImage } from "@common/display";
|
import { BrickDisplay, type BrickDisplayImage } from "@common/display";
|
||||||
import { isPressed, updateKeys } from "@common/input";
|
import { isPressed, updateKeys } from "@common/input";
|
||||||
|
|
||||||
import backgroundImage from './assets/background.png';
|
|
||||||
import spritesheetImage from './assets/spritesheet.png';
|
import spritesheetImage from './assets/spritesheet.png';
|
||||||
import { ITEMS, Items, MONSTERS, loadData, type Item, type Monster } from "./data";
|
import { ITEMS, Items, MONSTERS, MONSTERS_ORDER, loadData, type Item, type Monster } from "./data";
|
||||||
import { delay, randBool, weightedChoice } from "@common/utils";
|
import { randBool, weightedChoice } from "@common/utils";
|
||||||
|
|
||||||
let display: BrickDisplay;
|
let display: BrickDisplay;
|
||||||
let background: BrickDisplayImage;
|
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
|
||||||
let playerSprite: BrickDisplayImage;
|
const background = BrickDisplay.extractSprite(spritesheet, 12, 12, 10, 20);
|
||||||
|
const winScreen = BrickDisplay.extractSprite(spritesheet, 24, 12, 6, 20);
|
||||||
|
const playerSprite = BrickDisplay.extractSprite(spritesheet, 0, 4, 4, 4);
|
||||||
|
const playerDeadSprite = BrickDisplay.extractSprite(spritesheet, 4, 4, 4, 4);
|
||||||
|
|
||||||
const playerY = 2;
|
const playerY = 2;
|
||||||
|
|
||||||
|
|
@ -16,6 +18,9 @@ let y = 0;
|
||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
let playerHealth = 10;
|
let playerHealth = 10;
|
||||||
let playerBlink = 0;
|
let playerBlink = 0;
|
||||||
|
let missBlink = 0;
|
||||||
|
let win = false;
|
||||||
|
let winBlink = false;
|
||||||
|
|
||||||
let playerTurn = true;
|
let playerTurn = true;
|
||||||
let lootToConfirm: Items | null = null;
|
let lootToConfirm: Items | null = null;
|
||||||
|
|
@ -33,6 +38,7 @@ let monsterBlink = 0;
|
||||||
let bulletY = -1;
|
let bulletY = -1;
|
||||||
let bulletBlink = false;
|
let bulletBlink = false;
|
||||||
let bulletTargetY = -1;
|
let bulletTargetY = -1;
|
||||||
|
let bulletItem: Item | null = null;
|
||||||
|
|
||||||
let lootY = -1;
|
let lootY = -1;
|
||||||
let lootItem: Items | null = null;
|
let lootItem: Items | null = null;
|
||||||
|
|
@ -50,9 +56,19 @@ async function loop(time: number) {
|
||||||
lootBlink = !lootBlink;
|
lootBlink = !lootBlink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (win && frames % 16 == 0) {
|
||||||
|
winBlink = !winBlink;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerHealth <= 0) {
|
||||||
|
playerHealth = 0;
|
||||||
|
playerTurn = false;
|
||||||
|
}
|
||||||
|
|
||||||
let lootConfirmed = false;
|
let lootConfirmed = false;
|
||||||
const item = ITEMS[inventory[selectedSlot]];
|
const item = ITEMS[inventory[selectedSlot]];
|
||||||
const monster = MONSTERS[currentMonster];
|
const monster = MONSTERS[MONSTERS_ORDER[currentMonster]];
|
||||||
|
|
||||||
if (playerBlink) {
|
if (playerBlink) {
|
||||||
if (frames % 4 == 0) {
|
if (frames % 4 == 0) {
|
||||||
playerBlink--;
|
playerBlink--;
|
||||||
|
|
@ -61,6 +77,10 @@ async function loop(time: number) {
|
||||||
if (frames % 4 == 0) {
|
if (frames % 4 == 0) {
|
||||||
monsterBlink--;
|
monsterBlink--;
|
||||||
}
|
}
|
||||||
|
} else if (missBlink) {
|
||||||
|
if (frames % 4 == 0) {
|
||||||
|
missBlink--;
|
||||||
|
}
|
||||||
} else if (bulletTargetY !== bulletY) {
|
} else if (bulletTargetY !== bulletY) {
|
||||||
console.log('Bullet animation');
|
console.log('Bullet animation');
|
||||||
if (frames % 2 == 0) {
|
if (frames % 2 == 0) {
|
||||||
|
|
@ -77,7 +97,7 @@ async function loop(time: number) {
|
||||||
if (frames % 3 == 0) {
|
if (frames % 3 == 0) {
|
||||||
y += Math.sign(targetY - y);
|
y += Math.sign(targetY - y);
|
||||||
}
|
}
|
||||||
} else if (playerTurn && playerHealth > 0) {
|
} else if (playerTurn) {
|
||||||
lootToConfirm = null;
|
lootToConfirm = null;
|
||||||
if (y === lootY) {
|
if (y === lootY) {
|
||||||
lootToConfirm = lootItem;
|
lootToConfirm = lootItem;
|
||||||
|
|
@ -104,6 +124,7 @@ async function loop(time: number) {
|
||||||
console.log('Ranged attack')
|
console.log('Ranged attack')
|
||||||
bulletY = y + playerY + playerSprite.height;
|
bulletY = y + playerY + playerSprite.height;
|
||||||
bulletTargetY = monsterY + monster.sprite.height;
|
bulletTargetY = monsterY + monster.sprite.height;
|
||||||
|
bulletItem = item;
|
||||||
} else if (item.heal) {
|
} else if (item.heal) {
|
||||||
console.log('Heal' + item.heal);
|
console.log('Heal' + item.heal);
|
||||||
playerHealth += item.heal;
|
playerHealth += item.heal;
|
||||||
|
|
@ -123,6 +144,9 @@ async function loop(time: number) {
|
||||||
playerHealth += i.heal;
|
playerHealth += i.heal;
|
||||||
} else {
|
} else {
|
||||||
inventory.push(lootToConfirm);
|
inventory.push(lootToConfirm);
|
||||||
|
if (!i.heal) {
|
||||||
|
selectedSlot = inventory.length - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lootConfirmed = true;
|
lootConfirmed = true;
|
||||||
|
|
@ -147,7 +171,9 @@ async function loop(time: number) {
|
||||||
lootY = monsterY - 1;
|
lootY = monsterY - 1;
|
||||||
lootItem = drop;
|
lootItem = drop;
|
||||||
|
|
||||||
if (drop != null) {
|
if (drop == null) {
|
||||||
|
spawnNextMonster();
|
||||||
|
} else {
|
||||||
lootTable[drop] = 0;
|
lootTable[drop] = 0;
|
||||||
const rnd = Math.random();
|
const rnd = Math.random();
|
||||||
if (rnd < monster.secondLootChance) {
|
if (rnd < monster.secondLootChance) {
|
||||||
|
|
@ -158,11 +184,11 @@ async function loop(time: number) {
|
||||||
playerTurn = true;
|
playerTurn = true;
|
||||||
}
|
}
|
||||||
} else if (monster.ranged) {
|
} else if (monster.ranged) {
|
||||||
const retreat = randBool();
|
const retreat = randBool() && monsterY - y < 12;
|
||||||
if (retreat) {
|
if (retreat) {
|
||||||
monsterTargetY = monsterY + 4;
|
monsterTargetY = monsterY + 4;
|
||||||
playerTurn = true;
|
playerTurn = true;
|
||||||
} else {
|
} else {
|
||||||
bulletY = monsterY + monster.sprite.height;
|
bulletY = monsterY + monster.sprite.height;
|
||||||
bulletTargetY = y + playerY + playerSprite.height;
|
bulletTargetY = y + playerY + playerSprite.height;
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +219,7 @@ async function loop(time: number) {
|
||||||
bulletTargetY = bulletY = -1;
|
bulletTargetY = bulletY = -1;
|
||||||
bulletBlink = false;
|
bulletBlink = false;
|
||||||
if (playerTurn) {
|
if (playerTurn) {
|
||||||
damageMonster(item);
|
damageMonster(bulletItem ?? item);
|
||||||
playerTurn = false;
|
playerTurn = false;
|
||||||
} else {
|
} else {
|
||||||
damagePlayer(monster);
|
damagePlayer(monster);
|
||||||
|
|
@ -204,16 +230,23 @@ async function loop(time: number) {
|
||||||
display.clear();
|
display.clear();
|
||||||
display.clear(true);
|
display.clear(true);
|
||||||
|
|
||||||
display.speed = item.damage;
|
if (lootToConfirm) {
|
||||||
|
const i = ITEMS[lootToConfirm];
|
||||||
|
display.speed = i.heal ?? i.damage;
|
||||||
|
} else {
|
||||||
|
display.speed = item.heal ?? item.damage;
|
||||||
|
}
|
||||||
display.score = playerHealth;
|
display.score = playerHealth;
|
||||||
display.gameOver = playerHealth === 0;
|
display.gameOver = playerHealth <= 0;
|
||||||
|
|
||||||
const bgY = y % display.height;
|
const bgY = y % display.height;
|
||||||
display.drawImage(background, 0, bgY);
|
if (missBlink % 2 == 0 || playerHealth <= 0) {
|
||||||
display.drawImage(background, 0, bgY - display.height);
|
display.drawImage(background, 0, bgY);
|
||||||
|
display.drawImage(background, 0, bgY - display.height);
|
||||||
|
}
|
||||||
|
|
||||||
if (playerBlink % 2 == 0) {
|
if (playerBlink % 2 == 0) {
|
||||||
display.drawImage(playerSprite, 3, display.height - playerSprite.height - playerY);
|
display.drawImage(playerHealth > 0 ? playerSprite : playerDeadSprite, 3, display.height - playerSprite.height - playerY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monsterAlive && monsterBlink % 2 == 0 && monster) {
|
if (monsterAlive && monsterBlink % 2 == 0 && monster) {
|
||||||
|
|
@ -240,6 +273,11 @@ async function loop(time: number) {
|
||||||
display.drawImage(item.sprite, 0, 0, true);
|
display.drawImage(item.sprite, 0, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (winBlink) {
|
||||||
|
display.fillRect(2, 0, winScreen.width, winScreen.height, false);
|
||||||
|
display.drawImage(winScreen, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
display.update();
|
display.update();
|
||||||
|
|
||||||
updateKeys();
|
updateKeys();
|
||||||
|
|
@ -248,21 +286,26 @@ async function loop(time: number) {
|
||||||
|
|
||||||
function spawnNextMonster() {
|
function spawnNextMonster() {
|
||||||
currentMonster++;
|
currentMonster++;
|
||||||
monsterAlive = MONSTERS[currentMonster] != null;
|
monsterAlive = MONSTERS[MONSTERS_ORDER[currentMonster]] != null;
|
||||||
if (monsterAlive) {
|
if (monsterAlive) {
|
||||||
MONSTERS[currentMonster].health = MONSTERS[currentMonster].maxHealth;
|
MONSTERS[MONSTERS_ORDER[currentMonster]].health = MONSTERS[MONSTERS_ORDER[currentMonster]].maxHealth;
|
||||||
|
monsterY = y + 20;
|
||||||
|
monsterTargetY = y + 13;
|
||||||
|
} else {
|
||||||
|
win = true;
|
||||||
}
|
}
|
||||||
monsterY = y + 20;
|
|
||||||
monsterTargetY = y + 13;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function damageMonster(item: Item) {
|
function damageMonster(item: Item) {
|
||||||
|
if (!monsterAlive) return;
|
||||||
const rnd = Math.random();
|
const rnd = Math.random();
|
||||||
console.log(`Attack for ${item.damage}, success: ${rnd.toFixed(1)} < ${item.accuracy}`);
|
console.log(`Attack for ${item.damage}, success: ${rnd.toFixed(1)} < ${item.accuracy}`);
|
||||||
if (monsterAlive && rnd < item.accuracy) {
|
if (rnd < item.accuracy) {
|
||||||
MONSTERS[currentMonster].health -= item.damage;
|
MONSTERS[MONSTERS_ORDER[currentMonster]].health -= item.damage;
|
||||||
monsterBlink = 10;
|
monsterBlink = 6;
|
||||||
console.log(`Monster HP: ${MONSTERS[currentMonster].health}`);
|
console.log(`Monster HP: ${MONSTERS[MONSTERS_ORDER[currentMonster]].health}`);
|
||||||
|
} else {
|
||||||
|
missBlink = 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,10 +315,9 @@ function damagePlayer(monster: Monster) {
|
||||||
|
|
||||||
if (rnd < monster.accuracy) {
|
if (rnd < monster.accuracy) {
|
||||||
playerHealth -= monster.damage;
|
playerHealth -= monster.damage;
|
||||||
playerBlink = 10;
|
playerBlink = 6;
|
||||||
if (playerHealth <= 0) {
|
} else {
|
||||||
playerHealth = 0;
|
missBlink = 6;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,12 +325,7 @@ export default function main() {
|
||||||
display = new BrickDisplay();
|
display = new BrickDisplay();
|
||||||
display.init();
|
display.init();
|
||||||
|
|
||||||
background = BrickDisplay.convertImage(backgroundImage);
|
|
||||||
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
|
|
||||||
loadData(spritesheet);
|
loadData(spritesheet);
|
||||||
|
|
||||||
playerSprite = BrickDisplay.extractSprite(spritesheet, 0, 8, 4, 4);
|
|
||||||
|
|
||||||
spawnNextMonster();
|
spawnNextMonster();
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
}
|
}
|
||||||
|
|
@ -27,5 +27,6 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"@common/*": ["./src/common/*"]
|
"@common/*": ["./src/common/*"]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"include": ["./**/*.ts", "./**/*.tsx", "./node_modules/assemblyscript/std/portable/index.d.ts"],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue