mustachio-game 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pr.yaml +21 -0
- package/.github/workflows/push.yaml +26 -0
- package/eslint.config.ts +17 -0
- package/index.html +22 -0
- package/package.json +35 -0
- package/src/assets/Mustachio.webp +0 -0
- package/src/assets/Mustachio_FacingLeft.webp +0 -0
- package/src/assets/Mustachio_FacingLeft_Fire.webp +0 -0
- package/src/assets/Mustachio_FacingRight.webp +0 -0
- package/src/assets/Mustachio_FacingRight_Fire.webp +0 -0
- package/src/assets/Mustachio_Fire.webp +0 -0
- package/src/assets/brick.webp +0 -0
- package/src/assets/cannonDown.webp +0 -0
- package/src/assets/cannonLeft.webp +0 -0
- package/src/assets/cannonRight.webp +0 -0
- package/src/assets/cannonUp.webp +0 -0
- package/src/assets/fallingFloor.webp +0 -0
- package/src/assets/homestead.webp +0 -0
- package/src/assets/homesteadClosed.webp +0 -0
- package/src/assets/itemBlock.webp +0 -0
- package/src/assets/obstacleBrick.webp +0 -0
- package/src/assets/punchedBlock.webp +0 -0
- package/src/assets/stacheSeed1.webp +0 -0
- package/src/assets/stacheSeed2.webp +0 -0
- package/src/assets/stacheSeedReversed1.webp +0 -0
- package/src/assets/stacheSeedReversed2.webp +0 -0
- package/src/assets/stacheShotDown.webp +0 -0
- package/src/assets/stacheShotLeft.webp +0 -0
- package/src/assets/stacheShotRight.webp +0 -0
- package/src/assets/stacheShotUp.webp +0 -0
- package/src/assets/stacheSlinger1.webp +0 -0
- package/src/assets/stacheSlinger2.webp +0 -0
- package/src/assets/stacheStalker.webp +0 -0
- package/src/assets/stacheStalkerReversed.webp +0 -0
- package/src/assets/stacheStreaker1.webp +0 -0
- package/src/assets/stacheStreaker2.webp +0 -0
- package/src/classes/game-objects/bg-objects/background.ts +18 -0
- package/src/classes/game-objects/bg-objects/cloud.ts +37 -0
- package/src/classes/game-objects/mustachio.ts +482 -0
- package/src/classes/game-objects/point-objects/enemies/enemy.ts +64 -0
- package/src/classes/game-objects/point-objects/enemies/stache-seed.ts +124 -0
- package/src/classes/game-objects/point-objects/enemies/stache-shot.ts +68 -0
- package/src/classes/game-objects/point-objects/enemies/stache-slinger.ts +63 -0
- package/src/classes/game-objects/point-objects/enemies/stache-stalker.ts +41 -0
- package/src/classes/game-objects/point-objects/enemies/stache-streaker.ts +78 -0
- package/src/classes/game-objects/point-objects/items/coin.ts +72 -0
- package/src/classes/game-objects/point-objects/items/fire-stache.ts +48 -0
- package/src/classes/game-objects/point-objects/items/item.ts +27 -0
- package/src/classes/game-objects/point-objects/items/stacheroom.ts +48 -0
- package/src/classes/game-objects/point-objects/point-item.ts +10 -0
- package/src/classes/game-objects/projectiles/brick-debris.ts +44 -0
- package/src/classes/game-objects/projectiles/enemy-projectiles/enemy-projectile.ts +3 -0
- package/src/classes/game-objects/projectiles/enemy-projectiles/fire-ball.ts +87 -0
- package/src/classes/game-objects/projectiles/enemy-projectiles/fire-bar.ts +65 -0
- package/src/classes/game-objects/projectiles/enemy-projectiles/fire-cross.ts +67 -0
- package/src/classes/game-objects/projectiles/enemy-projectiles/laser.ts +41 -0
- package/src/classes/game-objects/projectiles/projectile.ts +3 -0
- package/src/classes/game-objects/projectiles/stache-ball.ts +57 -0
- package/src/classes/game-objects/set-pieces/flag.ts +34 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/block.ts +17 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/cave-wall.ts +21 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/falling-floor.ts +65 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/fire-bar-block.ts +31 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/fire-cross-block.ts +28 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/punchable-blockS/brick.ts +44 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/punchable-blockS/item-block.ts +82 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/punchable-blockS/punchable-block.ts +6 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/stache-cannon.ts +54 -0
- package/src/classes/game-objects/set-pieces/obstacles/blocks/wall.ts +22 -0
- package/src/classes/game-objects/set-pieces/obstacles/floor.ts +27 -0
- package/src/classes/game-objects/set-pieces/obstacles/obstacle-types.ts +14 -0
- package/src/classes/game-objects/set-pieces/obstacles/obstacle.ts +3 -0
- package/src/classes/game-objects/set-pieces/obstacles/pipe.ts +35 -0
- package/src/classes/game-objects/set-pieces/obstacles/warp-pipe.ts +17 -0
- package/src/classes/game-objects/set-pieces/set-piece.ts +3 -0
- package/src/classes/game-objects/ui-objects/score-display.ts +10 -0
- package/src/classes/game-objects/ui-objects/timer-display.ts +15 -0
- package/src/classes/game-objects/ui-objects/ui-object.ts +16 -0
- package/src/classes/game-objects/ui-objects/win-display.ts +25 -0
- package/src/dev.ts +5 -0
- package/src/index.ts +3 -0
- package/src/levels/caves/cave-one.ts +90 -0
- package/src/levels/level-helpers.ts +101 -0
- package/src/levels/level-one.ts +379 -0
- package/src/levels/test-levels/blocks-and-items.ts +77 -0
- package/src/levels/test-levels/cannon-and-cross.ts +75 -0
- package/src/levels/test-levels/caves-and-enemies.ts +73 -0
- package/src/levels/test-levels/win-game.ts +24 -0
- package/src/main.ts +6 -0
- package/src/mustachi-game-context.ts +35 -0
- package/src/shared/app-code.ts +106 -0
- package/src/shared/constants.ts +1 -0
- package/src/shared/game-context.ts +547 -0
- package/src/shared/game-objects/game-object.ts +28 -0
- package/src/shared/game-objects/moving-game-object.ts +46 -0
- package/src/shared/game-objects/rotating-game-object.ts +58 -0
- package/src/shared/game-objects/updating-game-object.ts +7 -0
- package/src/shared/player.ts +73 -0
- package/src/shared/types.ts +21 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +13 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { FireCrossBlock } from "../../set-pieces/obstacles/blocks/fire-cross-block";
|
|
3
|
+
import { EnemyProjectile } from "./enemy-projectile";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import { direction, type collision } from "../../../../shared/types";
|
|
6
|
+
|
|
7
|
+
export class FireCross extends EnemyProjectile {
|
|
8
|
+
private readonly direction: number;
|
|
9
|
+
private readonly maxLength = BLOCK_SIZE * 4;
|
|
10
|
+
private readonly minLength = BLOCK_SIZE * 0.5;
|
|
11
|
+
|
|
12
|
+
constructor(gameContext: GameContext, parent: FireCrossBlock, dir: number) {
|
|
13
|
+
const offset = BLOCK_SIZE * 0.25;
|
|
14
|
+
const width = BLOCK_SIZE * 0.5;
|
|
15
|
+
const height = BLOCK_SIZE * 0.5;
|
|
16
|
+
const x = parent.rect.x + parent.rect.width / 2 - offset;
|
|
17
|
+
const y = parent.rect.y + parent.rect.height / 2 - offset;
|
|
18
|
+
|
|
19
|
+
super(gameContext, {
|
|
20
|
+
x,
|
|
21
|
+
y,
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.speedY = 0.75;
|
|
27
|
+
this.speedX = 0.75;
|
|
28
|
+
|
|
29
|
+
this.direction = dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
33
|
+
update(_: collision[]): void {
|
|
34
|
+
if (this.direction === direction.LEFT) {
|
|
35
|
+
this.rect.x -= this.speedX;
|
|
36
|
+
this.rect.width += this.speedX;
|
|
37
|
+
} else if (this.direction === direction.RIGHT) {
|
|
38
|
+
this.rect.width += this.speedX;
|
|
39
|
+
} else if (this.direction === direction.UP) {
|
|
40
|
+
this.rect.y -= this.speedY;
|
|
41
|
+
this.rect.height += this.speedY;
|
|
42
|
+
} else if (this.direction === direction.DOWN) {
|
|
43
|
+
this.rect.height += this.speedY;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.rect.width > this.maxLength || this.rect.height > this.maxLength) {
|
|
47
|
+
this.speedX = -Math.abs(this.speedX);
|
|
48
|
+
this.speedY = -Math.abs(this.speedY);
|
|
49
|
+
} else if (
|
|
50
|
+
this.rect.width < this.minLength ||
|
|
51
|
+
this.rect.height < this.minLength
|
|
52
|
+
) {
|
|
53
|
+
this.speedX = Math.abs(this.speedX);
|
|
54
|
+
this.speedY = Math.abs(this.speedY);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
59
|
+
ctx.fillStyle = "red";
|
|
60
|
+
ctx.fillRect(
|
|
61
|
+
this.rect.x + this.gameContext.xOffset,
|
|
62
|
+
this.rect.y,
|
|
63
|
+
this.rect.width,
|
|
64
|
+
this.rect.height,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { collision } from "../../../../shared/types";
|
|
3
|
+
import { EnemyProjectile } from "./enemy-projectile";
|
|
4
|
+
import type { Enemy } from "../../point-objects/enemies/enemy";
|
|
5
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
6
|
+
|
|
7
|
+
export class Laser extends EnemyProjectile {
|
|
8
|
+
constructor(gameContext: GameContext, parent: Enemy, shotTime: number) {
|
|
9
|
+
const width = BLOCK_SIZE * 0.5;
|
|
10
|
+
const height = gameContext.gameArea.height;
|
|
11
|
+
const x = parent.rect.x + parent.rect.width / 2 - width / 2;
|
|
12
|
+
const y = parent.rect.y + parent.rect.height;
|
|
13
|
+
|
|
14
|
+
super(gameContext, {
|
|
15
|
+
x,
|
|
16
|
+
y,
|
|
17
|
+
width,
|
|
18
|
+
height,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
this.gameContext.removeGameObject(this);
|
|
23
|
+
}, shotTime);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
update(_: collision[]): void {}
|
|
28
|
+
|
|
29
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
30
|
+
ctx.fillStyle = "blue";
|
|
31
|
+
ctx.fillRect(
|
|
32
|
+
this.rect.x + this.gameContext.xOffset,
|
|
33
|
+
this.rect.y,
|
|
34
|
+
this.rect.width,
|
|
35
|
+
this.rect.height,
|
|
36
|
+
);
|
|
37
|
+
ctx.strokeStyle = "black";
|
|
38
|
+
ctx.lineWidth = 2;
|
|
39
|
+
ctx.strokeRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Enemy } from "../point-objects/enemies/enemy";
|
|
2
|
+
import { Projectile } from "./projectile";
|
|
3
|
+
import type { GameContext } from "../../../shared/game-context";
|
|
4
|
+
import type { collision, rectangle } from "../../../shared/types";
|
|
5
|
+
|
|
6
|
+
export class StacheBall extends Projectile {
|
|
7
|
+
constructor(
|
|
8
|
+
gameContext: GameContext,
|
|
9
|
+
|
|
10
|
+
x: number,
|
|
11
|
+
y: number,
|
|
12
|
+
) {
|
|
13
|
+
const rect: rectangle = {
|
|
14
|
+
x,
|
|
15
|
+
y,
|
|
16
|
+
width: 8,
|
|
17
|
+
height: 8,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
super(gameContext, rect);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
update(collisions: collision[]): void {
|
|
24
|
+
this.leftRightMovement(collisions);
|
|
25
|
+
|
|
26
|
+
if (this.onGround) {
|
|
27
|
+
this.speedY = -5.5;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (this.rect.x < 0 || this.rect.x > this.gameContext.gameArea.width) {
|
|
31
|
+
this.gameContext.removeGameObject(this);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const collision of collisions) {
|
|
36
|
+
if (collision.gameObject instanceof Enemy) {
|
|
37
|
+
collision.gameObject.enemyHit();
|
|
38
|
+
this.gameContext.removeGameObject(this);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
45
|
+
ctx.beginPath();
|
|
46
|
+
ctx.lineWidth = 5;
|
|
47
|
+
ctx.fillStyle = "red";
|
|
48
|
+
ctx.arc(
|
|
49
|
+
this.rect.x + this.gameContext.xOffset,
|
|
50
|
+
this.rect.y,
|
|
51
|
+
this.rect.height,
|
|
52
|
+
0,
|
|
53
|
+
2 * Math.PI,
|
|
54
|
+
);
|
|
55
|
+
ctx.fill();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { GameContext } from "../../../shared/game-context";
|
|
2
|
+
import { SetPiece } from "./set-piece";
|
|
3
|
+
import { BLOCK_SIZE } from "../../../shared/constants";
|
|
4
|
+
import homestead from "../../../assets/homestead.webp";
|
|
5
|
+
import homesteadClosed from "../../../assets/homesteadClosed.webp";
|
|
6
|
+
|
|
7
|
+
export class Flag extends SetPiece {
|
|
8
|
+
private readonly image = new Image();
|
|
9
|
+
|
|
10
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
11
|
+
super(gameContext, {
|
|
12
|
+
x,
|
|
13
|
+
y,
|
|
14
|
+
width: BLOCK_SIZE * 8,
|
|
15
|
+
height: BLOCK_SIZE * 8,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
this.image.src = homestead;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
closeDoor() {
|
|
22
|
+
this.image.src = homesteadClosed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
26
|
+
ctx.drawImage(
|
|
27
|
+
this.image,
|
|
28
|
+
this.rect.x + this.gameContext.xOffset,
|
|
29
|
+
this.rect.y,
|
|
30
|
+
this.rect.width,
|
|
31
|
+
this.rect.height,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BLOCK_SIZE } from "../../../../../shared/constants";
|
|
2
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
3
|
+
import type { rectangle } from "../../../../../shared/types";
|
|
4
|
+
import { Obstacle } from "../obstacle";
|
|
5
|
+
|
|
6
|
+
export abstract class Block extends Obstacle {
|
|
7
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
8
|
+
const rect: rectangle = {
|
|
9
|
+
x,
|
|
10
|
+
y,
|
|
11
|
+
width: BLOCK_SIZE,
|
|
12
|
+
height: BLOCK_SIZE,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
super(gameContext, rect);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
2
|
+
import { Block } from "./block";
|
|
3
|
+
import type { rectangle } from "../../../../../shared/types";
|
|
4
|
+
|
|
5
|
+
export class CaveWall extends Block {
|
|
6
|
+
constructor(gameContext: GameContext, rect: rectangle) {
|
|
7
|
+
super(gameContext, rect.x, rect.y);
|
|
8
|
+
this.rect.width = rect.width;
|
|
9
|
+
this.rect.height = rect.height;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
13
|
+
ctx.fillStyle = "DarkSlateGray";
|
|
14
|
+
ctx.fillRect(
|
|
15
|
+
this.rect.x + this.gameContext.xOffset,
|
|
16
|
+
this.rect.y,
|
|
17
|
+
this.rect.width,
|
|
18
|
+
this.rect.height,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { BLOCK_SIZE } from "../../../../../shared/constants";
|
|
2
|
+
import type { rectangle } from "../../../../../shared/types";
|
|
3
|
+
import { Block } from "./block";
|
|
4
|
+
|
|
5
|
+
export class FallingFloor extends Block {
|
|
6
|
+
private isFalling: boolean = false;
|
|
7
|
+
private fallStarted: boolean = false;
|
|
8
|
+
|
|
9
|
+
startFall() {
|
|
10
|
+
if (this.fallStarted) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.fallStarted = true;
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
this.acceptsCollision = false;
|
|
17
|
+
this.isFalling = true;
|
|
18
|
+
}, 250);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
22
|
+
// This logic SHOULD be in the update method,
|
|
23
|
+
// but I decided to extend block rather than UpdatingGameObject
|
|
24
|
+
if (this.isFalling) {
|
|
25
|
+
this.rect.y += 5;
|
|
26
|
+
|
|
27
|
+
if (this.rect.y > this.gameContext.gameArea.height) {
|
|
28
|
+
this.gameContext.removeGameObject(this);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ctx.fillStyle = "Bisque";
|
|
33
|
+
ctx.fillRect(
|
|
34
|
+
this.rect.x + this.gameContext.xOffset,
|
|
35
|
+
this.rect.y,
|
|
36
|
+
this.rect.width,
|
|
37
|
+
this.rect.height,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const seeThroughRect: rectangle = {
|
|
41
|
+
x: this.rect.x + BLOCK_SIZE / 3 + this.gameContext.xOffset,
|
|
42
|
+
y: this.rect.y + BLOCK_SIZE / 3,
|
|
43
|
+
width: this.rect.width - (BLOCK_SIZE / 3) * 2,
|
|
44
|
+
height: this.rect.height - (BLOCK_SIZE / 3) * 2,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ctx.clearRect(
|
|
48
|
+
seeThroughRect.x,
|
|
49
|
+
seeThroughRect.y,
|
|
50
|
+
seeThroughRect.width,
|
|
51
|
+
seeThroughRect.height,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
ctx.strokeStyle = "black";
|
|
55
|
+
ctx.lineWidth = 2;
|
|
56
|
+
ctx.strokeRect(
|
|
57
|
+
seeThroughRect.x,
|
|
58
|
+
seeThroughRect.y,
|
|
59
|
+
seeThroughRect.width,
|
|
60
|
+
seeThroughRect.height,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
ctx.strokeRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
2
|
+
import { Block } from "./block";
|
|
3
|
+
import { FireBar } from "../../../projectiles/enemy-projectiles/fire-bar";
|
|
4
|
+
import obstacleBrick from "../../../../../assets/obstacleBrick.webp";
|
|
5
|
+
|
|
6
|
+
export class FireBarBlock extends Block {
|
|
7
|
+
private readonly image: HTMLImageElement = new Image();
|
|
8
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
9
|
+
super(gameContext, x, y);
|
|
10
|
+
|
|
11
|
+
this.image.src = obstacleBrick;
|
|
12
|
+
const fireBar = new FireBar(
|
|
13
|
+
gameContext,
|
|
14
|
+
this.rect.x + this.rect.width / 2 - 5,
|
|
15
|
+
this.rect.y + this.rect.height / 2 - 50,
|
|
16
|
+
this,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
this.gameContext.addGameObject(fireBar);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
23
|
+
ctx.drawImage(
|
|
24
|
+
this.image,
|
|
25
|
+
this.rect.x + this.gameContext.xOffset,
|
|
26
|
+
this.rect.y,
|
|
27
|
+
this.rect.width,
|
|
28
|
+
this.rect.height,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
2
|
+
import { Block } from "./block";
|
|
3
|
+
import { FireCross } from "../../../projectiles/enemy-projectiles/fire-cross";
|
|
4
|
+
import obstacleBrick from "../../../../../assets/obstacleBrick.webp";
|
|
5
|
+
|
|
6
|
+
export class FireCrossBlock extends Block {
|
|
7
|
+
private readonly image: HTMLImageElement = new Image();
|
|
8
|
+
|
|
9
|
+
constructor(gameContext: GameContext, x: number, y: number, dirs: number[]) {
|
|
10
|
+
super(gameContext, x, y);
|
|
11
|
+
|
|
12
|
+
this.image.src = obstacleBrick;
|
|
13
|
+
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
this.gameContext.addGameObject(new FireCross(gameContext, this, dir));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
20
|
+
ctx.drawImage(
|
|
21
|
+
this.image,
|
|
22
|
+
this.rect.x + this.gameContext.xOffset,
|
|
23
|
+
this.rect.y,
|
|
24
|
+
this.rect.width,
|
|
25
|
+
this.rect.height,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../../shared/game-context";
|
|
2
|
+
import { PunchableBlock } from "./punchable-block";
|
|
3
|
+
import { BrickDebris } from "../../../../projectiles/brick-debris";
|
|
4
|
+
import brickImage from "../../../../../../assets/brick.webp";
|
|
5
|
+
|
|
6
|
+
export class Brick extends PunchableBlock {
|
|
7
|
+
protected punched = false;
|
|
8
|
+
private readonly image: HTMLImageElement = new Image();
|
|
9
|
+
|
|
10
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
11
|
+
super(gameContext, x, y);
|
|
12
|
+
this.image.src = brickImage;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
punch() {
|
|
16
|
+
if (this.punched) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.punched = true;
|
|
21
|
+
this.gameContext.addScore(100);
|
|
22
|
+
|
|
23
|
+
const speedXs = [-1, -2.5, 1, 2.5];
|
|
24
|
+
const speedYs = [-2, -3.5, -2, -3.5];
|
|
25
|
+
for (let i = 0; i < 4; i++) {
|
|
26
|
+
const debris = new BrickDebris(this.gameContext, this);
|
|
27
|
+
debris.speedX = speedXs[i];
|
|
28
|
+
debris.speedY = speedYs[i];
|
|
29
|
+
this.gameContext.addGameObject(debris);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.gameContext.removeGameObject(this);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
36
|
+
ctx.drawImage(
|
|
37
|
+
this.image,
|
|
38
|
+
this.rect.x + this.gameContext.xOffset,
|
|
39
|
+
this.rect.y,
|
|
40
|
+
this.rect.width,
|
|
41
|
+
this.rect.height,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../../shared/game-context";
|
|
2
|
+
import type { Item } from "../../../../point-objects/items/item";
|
|
3
|
+
import { Coin } from "../../../../point-objects/items/coin";
|
|
4
|
+
import { Stacheroom } from "../../../../point-objects/items/stacheroom";
|
|
5
|
+
import { FireStache } from "../../../../point-objects/items/fire-stache";
|
|
6
|
+
import { PunchableBlock } from "./punchable-block";
|
|
7
|
+
import itemBlockImage from "../../../../../../assets/itemBlock.webp";
|
|
8
|
+
import punchedBlockImage from "../../../../../../assets/punchedBlock.webp";
|
|
9
|
+
|
|
10
|
+
export class ItemBlock extends PunchableBlock {
|
|
11
|
+
protected punched = false;
|
|
12
|
+
hidden: boolean;
|
|
13
|
+
|
|
14
|
+
private readonly image: HTMLImageElement = new Image();
|
|
15
|
+
private readonly imageSource: string = itemBlockImage;
|
|
16
|
+
private readonly imageSourcePunched: string = punchedBlockImage;
|
|
17
|
+
|
|
18
|
+
protected item: new (
|
|
19
|
+
gameContext: GameContext,
|
|
20
|
+
x: number,
|
|
21
|
+
y: number,
|
|
22
|
+
fromItemBlock?: boolean,
|
|
23
|
+
) => Item;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
gameContext: GameContext,
|
|
27
|
+
x: number,
|
|
28
|
+
y: number,
|
|
29
|
+
hidden: boolean,
|
|
30
|
+
itemType: "coin" | "stacheroom" | "fire-stache",
|
|
31
|
+
) {
|
|
32
|
+
super(gameContext, x, y);
|
|
33
|
+
this.image.src = this.imageSource;
|
|
34
|
+
this.hidden = hidden;
|
|
35
|
+
|
|
36
|
+
switch (itemType) {
|
|
37
|
+
case "coin":
|
|
38
|
+
this.item = Coin;
|
|
39
|
+
break;
|
|
40
|
+
case "stacheroom":
|
|
41
|
+
this.item = Stacheroom;
|
|
42
|
+
break;
|
|
43
|
+
case "fire-stache":
|
|
44
|
+
this.item = FireStache;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`Unknown item type: ${itemType}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
punch() {
|
|
52
|
+
if (this.punched) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.punched = true;
|
|
57
|
+
this.hidden = false;
|
|
58
|
+
this.image.src = this.imageSourcePunched;
|
|
59
|
+
const newItem = new this.item(
|
|
60
|
+
this.gameContext,
|
|
61
|
+
this.rect.x + this.rect.width / 2,
|
|
62
|
+
this.rect.y - this.rect.height,
|
|
63
|
+
true,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
this.gameContext.addGameObject(newItem, true);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
70
|
+
if (this.hidden) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ctx.drawImage(
|
|
75
|
+
this.image,
|
|
76
|
+
this.rect.x + this.gameContext.xOffset,
|
|
77
|
+
this.rect.y,
|
|
78
|
+
this.rect.width,
|
|
79
|
+
this.rect.height,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
2
|
+
import { Block } from "./block";
|
|
3
|
+
import { direction } from "../../../../../shared/types";
|
|
4
|
+
import { StacheShot } from "../../../point-objects/enemies/stache-shot";
|
|
5
|
+
import { outOfBounds } from "../../../../../shared/app-code";
|
|
6
|
+
import stacheCannonUp from "../../../../../assets/cannonUp.webp";
|
|
7
|
+
import stacheCannonDown from "../../../../../assets/cannonDown.webp";
|
|
8
|
+
import stacheCannonLeft from "../../../../../assets/cannonLeft.webp";
|
|
9
|
+
import stacheCannonRight from "../../../../../assets/cannonRight.webp";
|
|
10
|
+
|
|
11
|
+
export class StacheCannon extends Block {
|
|
12
|
+
private readonly image: HTMLImageElement = new Image();
|
|
13
|
+
private readonly shotTimer: number;
|
|
14
|
+
|
|
15
|
+
constructor(gameContext: GameContext, x: number, y: number, dir: number) {
|
|
16
|
+
super(gameContext, x, y);
|
|
17
|
+
if (dir === direction.UP) {
|
|
18
|
+
this.image.src = stacheCannonUp;
|
|
19
|
+
} else if (dir === direction.DOWN) {
|
|
20
|
+
this.image.src = stacheCannonDown;
|
|
21
|
+
} else if (dir === direction.LEFT) {
|
|
22
|
+
this.image.src = stacheCannonLeft;
|
|
23
|
+
} else if (dir === direction.RIGHT) {
|
|
24
|
+
this.image.src = stacheCannonRight;
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error("Invalid direction for StacheCannon");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.shotTimer = setInterval(() => {
|
|
30
|
+
if (outOfBounds(this, this.gameContext)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.gameContext.addGameObject(
|
|
35
|
+
new StacheShot(this.gameContext, this, dir),
|
|
36
|
+
true,
|
|
37
|
+
);
|
|
38
|
+
}, 6000);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
42
|
+
ctx.drawImage(
|
|
43
|
+
this.image,
|
|
44
|
+
this.rect.x + this.gameContext.xOffset,
|
|
45
|
+
this.rect.y,
|
|
46
|
+
this.rect.width,
|
|
47
|
+
this.rect.height,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
dispose() {
|
|
52
|
+
clearInterval(this.shotTimer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../../shared/game-context";
|
|
2
|
+
import { Block } from "./block";
|
|
3
|
+
import obstacleBrick from "../../../../../assets/obstacleBrick.webp";
|
|
4
|
+
|
|
5
|
+
export class Wall extends Block {
|
|
6
|
+
private readonly image: HTMLImageElement = new Image();
|
|
7
|
+
|
|
8
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
9
|
+
super(gameContext, x, y);
|
|
10
|
+
this.image.src = obstacleBrick;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
14
|
+
ctx.drawImage(
|
|
15
|
+
this.image,
|
|
16
|
+
this.rect.x + this.gameContext.xOffset,
|
|
17
|
+
this.rect.y,
|
|
18
|
+
this.rect.width,
|
|
19
|
+
this.rect.height,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
2
|
+
import { Obstacle } from "./obstacle";
|
|
3
|
+
|
|
4
|
+
export class Floor extends Obstacle {
|
|
5
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
6
|
+
ctx.fillStyle = "YellowGreen";
|
|
7
|
+
|
|
8
|
+
const grassRect = {
|
|
9
|
+
x: this.rect.x + this.gameContext.xOffset,
|
|
10
|
+
y: this.rect.y,
|
|
11
|
+
width: this.rect.width,
|
|
12
|
+
height: BLOCK_SIZE / 3,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const dirtRect = {
|
|
16
|
+
x: this.rect.x + this.gameContext.xOffset,
|
|
17
|
+
y: this.rect.y + BLOCK_SIZE / 3,
|
|
18
|
+
width: this.rect.width,
|
|
19
|
+
height: BLOCK_SIZE - BLOCK_SIZE / 3,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
ctx.fillRect(grassRect.x, grassRect.y, grassRect.width, grassRect.height);
|
|
23
|
+
|
|
24
|
+
ctx.fillStyle = "SaddleBrown";
|
|
25
|
+
ctx.fillRect(dirtRect.x, dirtRect.y, dirtRect.width, dirtRect.height);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
|
|
3
|
+
export type PipeOptions = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
hasStacheSeed?: boolean;
|
|
9
|
+
reversed?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type WarpPipeOptions = PipeOptions & {
|
|
13
|
+
setNewLevel: (gameContext: GameContext) => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Obstacle } from "./obstacle";
|
|
2
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
3
|
+
import { StacheSeed } from "../../point-objects/enemies/stache-seed";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import type { PipeOptions } from "./obstacle-types";
|
|
6
|
+
|
|
7
|
+
export class Pipe extends Obstacle {
|
|
8
|
+
constructor(
|
|
9
|
+
gameContext: GameContext,
|
|
10
|
+
{ x, y, width, height, hasStacheSeed, reversed }: PipeOptions,
|
|
11
|
+
) {
|
|
12
|
+
super(gameContext, {
|
|
13
|
+
x,
|
|
14
|
+
y,
|
|
15
|
+
width: width ?? BLOCK_SIZE * 2,
|
|
16
|
+
height: height ?? BLOCK_SIZE * 2,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (hasStacheSeed) {
|
|
20
|
+
gameContext.addGameObject(
|
|
21
|
+
new StacheSeed(gameContext, this, reversed ?? false),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
27
|
+
ctx.fillStyle = "green";
|
|
28
|
+
ctx.fillRect(
|
|
29
|
+
this.rect.x + this.gameContext.xOffset,
|
|
30
|
+
this.rect.y,
|
|
31
|
+
this.rect.width,
|
|
32
|
+
this.rect.height,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|