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,68 @@
|
|
|
1
|
+
import { direction, type collision } from "../../../../shared/types";
|
|
2
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
3
|
+
import type { StacheCannon } from "../../set-pieces/obstacles/blocks/stache-cannon";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import { Enemy } from "./enemy";
|
|
6
|
+
import { outOfBounds } from "../../../../shared/app-code";
|
|
7
|
+
import stacheShotLeft from "../../../../assets/stacheShotLeft.webp";
|
|
8
|
+
import stacheShotRight from "../../../../assets/stacheShotRight.webp";
|
|
9
|
+
import stacheShotUp from "../../../../assets/stacheShotUp.webp";
|
|
10
|
+
import stacheShotDown from "../../../../assets/stacheShotDown.webp";
|
|
11
|
+
|
|
12
|
+
export class StacheShot extends Enemy {
|
|
13
|
+
readonly pointValue: number = 250;
|
|
14
|
+
private readonly shotSpeed = 3;
|
|
15
|
+
|
|
16
|
+
constructor(gameContext: GameContext, parent: StacheCannon, dir: number) {
|
|
17
|
+
const width = BLOCK_SIZE * 0.75;
|
|
18
|
+
const height = BLOCK_SIZE * 0.75;
|
|
19
|
+
const x = parent.rect.x + parent.rect.width / 2 - width / 2;
|
|
20
|
+
const y = parent.rect.y + parent.rect.height / 2 - height / 2;
|
|
21
|
+
super(
|
|
22
|
+
gameContext,
|
|
23
|
+
{
|
|
24
|
+
x,
|
|
25
|
+
y,
|
|
26
|
+
width,
|
|
27
|
+
height,
|
|
28
|
+
},
|
|
29
|
+
false,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (dir === direction.LEFT) {
|
|
33
|
+
this.image.src = stacheShotLeft;
|
|
34
|
+
this.speedX = -this.shotSpeed;
|
|
35
|
+
} else if (dir === direction.RIGHT) {
|
|
36
|
+
this.image.src = stacheShotRight;
|
|
37
|
+
this.speedX = this.shotSpeed;
|
|
38
|
+
} else if (dir === direction.UP) {
|
|
39
|
+
this.image.src = stacheShotUp;
|
|
40
|
+
this.speedY = -this.shotSpeed;
|
|
41
|
+
} else if (dir === direction.DOWN) {
|
|
42
|
+
this.image.src = stacheShotDown;
|
|
43
|
+
this.speedY = this.shotSpeed;
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error("Invalid direction for StacheShot");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
50
|
+
update(_: collision[]) {
|
|
51
|
+
this.rect.x += this.speedX;
|
|
52
|
+
this.rect.y += this.speedY;
|
|
53
|
+
|
|
54
|
+
if (outOfBounds(this, this.gameContext)) {
|
|
55
|
+
this.gameContext.removeGameObject(this);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
60
|
+
ctx.drawImage(
|
|
61
|
+
this.image,
|
|
62
|
+
this.rect.x + this.gameContext.xOffset,
|
|
63
|
+
this.rect.y,
|
|
64
|
+
this.rect.width,
|
|
65
|
+
this.rect.height,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { collision } from "../../../../shared/types";
|
|
2
|
+
import { Enemy } from "./enemy";
|
|
3
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import { FireBall } from "../../projectiles/enemy-projectiles/fire-ball";
|
|
6
|
+
import stacheSlinger1 from "../../../../assets/stacheSlinger1.webp";
|
|
7
|
+
import stacheSlinger2 from "../../../../assets/stacheSlinger2.webp";
|
|
8
|
+
|
|
9
|
+
export class StacheSlinger extends Enemy {
|
|
10
|
+
readonly pointValue: number = 1250;
|
|
11
|
+
private readonly maxX: number;
|
|
12
|
+
private readonly minX: number;
|
|
13
|
+
private totalDistance: number = 0;
|
|
14
|
+
|
|
15
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
16
|
+
super(gameContext, {
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
width: BLOCK_SIZE * 1.5,
|
|
20
|
+
height: BLOCK_SIZE * 2,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.maxX = x + BLOCK_SIZE * 4;
|
|
24
|
+
this.minX = x - BLOCK_SIZE * 4;
|
|
25
|
+
|
|
26
|
+
this.imageSources.push(stacheSlinger1, stacheSlinger2);
|
|
27
|
+
|
|
28
|
+
this.image.src = this.imageSources[0];
|
|
29
|
+
this.speedX = 1.5;
|
|
30
|
+
gameContext.addGameObject(new FireBall(this.gameContext, this));
|
|
31
|
+
|
|
32
|
+
this.shotTimer = setInterval(() => {
|
|
33
|
+
gameContext.addGameObject(new FireBall(this.gameContext, this));
|
|
34
|
+
}, 5000);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// This enemy doesn't collide with anything
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
39
|
+
update(_: collision[]): void {
|
|
40
|
+
if (!this.isDead) {
|
|
41
|
+
this.rect.x += this.speedX;
|
|
42
|
+
this.totalDistance += this.speedX;
|
|
43
|
+
|
|
44
|
+
if (this.totalDistance >= this.maxX) {
|
|
45
|
+
this.speedX = -Math.abs(this.speedX);
|
|
46
|
+
this.image.src = this.imageSources[1];
|
|
47
|
+
} else if (this.totalDistance <= this.minX) {
|
|
48
|
+
this.speedX = Math.abs(this.speedX);
|
|
49
|
+
this.image.src = this.imageSources[0];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
draw(ctx: CanvasRenderingContext2D): void {
|
|
55
|
+
ctx.drawImage(
|
|
56
|
+
this.image,
|
|
57
|
+
this.rect.x + this.gameContext.xOffset,
|
|
58
|
+
this.rect.y,
|
|
59
|
+
this.rect.width,
|
|
60
|
+
this.rect.height,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { collision, rectangle } from "../../../../shared/types";
|
|
2
|
+
import { Enemy } from "./enemy";
|
|
3
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import stacheStalker from "../../../../assets/stacheStalker.webp";
|
|
6
|
+
import stacheStalkerReversed from "../../../../assets/stacheStalkerReversed.webp";
|
|
7
|
+
|
|
8
|
+
export class StacheStalker extends Enemy {
|
|
9
|
+
readonly pointValue: number = 100;
|
|
10
|
+
|
|
11
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
12
|
+
const rect: rectangle = {
|
|
13
|
+
x,
|
|
14
|
+
y,
|
|
15
|
+
width: BLOCK_SIZE * 0.75,
|
|
16
|
+
height: BLOCK_SIZE * 0.75,
|
|
17
|
+
};
|
|
18
|
+
super(gameContext, rect);
|
|
19
|
+
|
|
20
|
+
this.imageSources.push(stacheStalker, stacheStalkerReversed);
|
|
21
|
+
|
|
22
|
+
this.image.src = this.imageSources[0];
|
|
23
|
+
this.speedX = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
27
|
+
ctx.drawImage(
|
|
28
|
+
this.image,
|
|
29
|
+
this.rect.x + this.gameContext.xOffset,
|
|
30
|
+
this.rect.y,
|
|
31
|
+
this.rect.width,
|
|
32
|
+
this.rect.height,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
update(collisions: collision[]): void {
|
|
37
|
+
if (!this.isDead) {
|
|
38
|
+
this.leftRightMovement(collisions);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { collision } from "../../../../shared/types";
|
|
2
|
+
import { Enemy } from "./enemy";
|
|
3
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
import { Laser } from "../../projectiles/enemy-projectiles/laser";
|
|
6
|
+
import stacheStreaker1 from "../../../../assets/stacheStreaker1.webp";
|
|
7
|
+
import stacheStreaker2 from "../../../../assets/stacheStreaker2.webp";
|
|
8
|
+
|
|
9
|
+
export class StacheStreaker extends Enemy {
|
|
10
|
+
readonly pointValue: number = 1250;
|
|
11
|
+
private canMove: boolean = true;
|
|
12
|
+
private totalDistance: number = 0;
|
|
13
|
+
private readonly timeBetweenShots: number = 3500;
|
|
14
|
+
private readonly shotTime: number = 5000;
|
|
15
|
+
|
|
16
|
+
constructor(gameContext: GameContext, x: number, y: number) {
|
|
17
|
+
super(
|
|
18
|
+
gameContext,
|
|
19
|
+
{
|
|
20
|
+
x,
|
|
21
|
+
y,
|
|
22
|
+
width: BLOCK_SIZE * 2,
|
|
23
|
+
height: BLOCK_SIZE * 1.5,
|
|
24
|
+
},
|
|
25
|
+
false,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
this.imageSources.push(stacheStreaker1, stacheStreaker2);
|
|
29
|
+
|
|
30
|
+
this.image.src = this.imageSources[0];
|
|
31
|
+
this.speedX = 1;
|
|
32
|
+
this.shotTimer = setTimeout(() => {
|
|
33
|
+
this.fireLaser();
|
|
34
|
+
}, this.timeBetweenShots);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// This enemy doesn't collide with anything
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
39
|
+
update(_: collision[]): void {
|
|
40
|
+
if (!this.isDead && this.canMove) {
|
|
41
|
+
this.rect.x += this.speedX;
|
|
42
|
+
this.totalDistance += Math.abs(this.speedX);
|
|
43
|
+
|
|
44
|
+
if (this.totalDistance >= BLOCK_SIZE * 5) {
|
|
45
|
+
this.speedX *= -1;
|
|
46
|
+
this.totalDistance = 0;
|
|
47
|
+
//this.setNextImage()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
draw(ctx: CanvasRenderingContext2D): void {
|
|
53
|
+
ctx.drawImage(
|
|
54
|
+
this.image,
|
|
55
|
+
this.rect.x + this.gameContext.xOffset,
|
|
56
|
+
this.rect.y,
|
|
57
|
+
this.rect.width,
|
|
58
|
+
this.rect.height,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private fireLaser(): void {
|
|
63
|
+
this.gameContext.addGameObject(
|
|
64
|
+
new Laser(this.gameContext, this, this.shotTime),
|
|
65
|
+
);
|
|
66
|
+
this.canMove = false;
|
|
67
|
+
this.setNextImage();
|
|
68
|
+
|
|
69
|
+
this.shotTimer = setTimeout(() => {
|
|
70
|
+
this.canMove = true;
|
|
71
|
+
this.setNextImage();
|
|
72
|
+
|
|
73
|
+
this.shotTimer = setTimeout(() => {
|
|
74
|
+
this.fireLaser();
|
|
75
|
+
}, this.timeBetweenShots);
|
|
76
|
+
}, this.shotTime);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { collision, rectangle } from "../../../../shared/types";
|
|
3
|
+
import { Item } from "./item";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
|
|
6
|
+
export class Coin extends Item {
|
|
7
|
+
readonly pointValue: number = 100;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
gameContext: GameContext,
|
|
11
|
+
|
|
12
|
+
x: number,
|
|
13
|
+
y: number,
|
|
14
|
+
fromItemBlock: boolean = false,
|
|
15
|
+
) {
|
|
16
|
+
const rect: rectangle = {
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
width: 15,
|
|
20
|
+
height: 15,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (!fromItemBlock) {
|
|
24
|
+
rect.x += BLOCK_SIZE / 2 - rect.width / 2;
|
|
25
|
+
rect.y += BLOCK_SIZE / 2 - rect.height / 2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
super(gameContext, rect, fromItemBlock);
|
|
29
|
+
|
|
30
|
+
if (fromItemBlock) {
|
|
31
|
+
this.speedY = -2.5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The coin doesn't care about collisions
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
37
|
+
update(_collisions: collision[]): void {
|
|
38
|
+
// The animation only happens if the coin
|
|
39
|
+
// is triggered from an item block
|
|
40
|
+
if (!this.fromItemBlock) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.speedY < 0) {
|
|
45
|
+
this.rect.y += this.speedY;
|
|
46
|
+
this.speedY += 0.1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Once the animation is done, we remove the coin
|
|
51
|
+
this.collect();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
55
|
+
ctx.fillStyle = "gold";
|
|
56
|
+
|
|
57
|
+
ctx.beginPath();
|
|
58
|
+
ctx.arc(
|
|
59
|
+
this.rect.x + this.rect.width / 2 + this.gameContext.xOffset,
|
|
60
|
+
this.rect.y + this.rect.height / 2,
|
|
61
|
+
this.rect.width,
|
|
62
|
+
0,
|
|
63
|
+
Math.PI * 2,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
ctx.fill();
|
|
67
|
+
ctx.closePath();
|
|
68
|
+
ctx.strokeStyle = "black";
|
|
69
|
+
ctx.lineWidth = 1;
|
|
70
|
+
ctx.stroke();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { collision, rectangle } from "../../../../shared/types";
|
|
3
|
+
import { Item } from "./item";
|
|
4
|
+
|
|
5
|
+
export class FireStache extends Item {
|
|
6
|
+
readonly pointValue: number = 1000;
|
|
7
|
+
totalRaise: number = 20;
|
|
8
|
+
speedX = 2;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
gameContext: GameContext,
|
|
12
|
+
|
|
13
|
+
x: number,
|
|
14
|
+
y: number,
|
|
15
|
+
fromItemBlock: boolean = false,
|
|
16
|
+
) {
|
|
17
|
+
const rect: rectangle = {
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
width: 20,
|
|
21
|
+
height: 20,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
super(gameContext, rect, fromItemBlock);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
28
|
+
ctx.fillStyle = "red";
|
|
29
|
+
ctx.fillRect(
|
|
30
|
+
this.rect.x + this.gameContext.xOffset,
|
|
31
|
+
this.rect.y,
|
|
32
|
+
this.rect.width,
|
|
33
|
+
this.rect.height,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
update(collisions: collision[]): void {
|
|
38
|
+
// The animation only happens if the coin
|
|
39
|
+
// is triggered from an item block
|
|
40
|
+
if (this.fromItemBlock && this.totalRaise > 0) {
|
|
41
|
+
this.rect.y += this.speedY;
|
|
42
|
+
this.totalRaise += this.speedY;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.leftRightMovement(collisions);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { rectangle } from "../../../../shared/types";
|
|
3
|
+
import { PointObject } from "../point-item";
|
|
4
|
+
import { BLOCK_SIZE } from "../../../../shared/constants";
|
|
5
|
+
|
|
6
|
+
export abstract class Item extends PointObject {
|
|
7
|
+
protected readonly fromItemBlock: boolean;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
gameContext: GameContext,
|
|
11
|
+
|
|
12
|
+
rect: rectangle,
|
|
13
|
+
fromItemBlock: boolean = false,
|
|
14
|
+
) {
|
|
15
|
+
if (fromItemBlock) {
|
|
16
|
+
rect.x -= rect.width / 2;
|
|
17
|
+
rect.y += BLOCK_SIZE;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
super(gameContext, rect);
|
|
21
|
+
|
|
22
|
+
this.fromItemBlock = fromItemBlock;
|
|
23
|
+
if (fromItemBlock) {
|
|
24
|
+
this.speedY = -0.5;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { collision, rectangle } from "../../../../shared/types";
|
|
3
|
+
import { Item } from "./item";
|
|
4
|
+
|
|
5
|
+
export class Stacheroom extends Item {
|
|
6
|
+
readonly pointValue: number = 1000;
|
|
7
|
+
totalRaise: number = 20;
|
|
8
|
+
speedX = 2;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
gameContext: GameContext,
|
|
12
|
+
|
|
13
|
+
x: number,
|
|
14
|
+
y: number,
|
|
15
|
+
fromItemBlock: boolean = false,
|
|
16
|
+
) {
|
|
17
|
+
const rect: rectangle = {
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
width: 20,
|
|
21
|
+
height: 20,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
super(gameContext, rect, fromItemBlock);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
28
|
+
ctx.fillStyle = "blue";
|
|
29
|
+
ctx.fillRect(
|
|
30
|
+
this.rect.x + this.gameContext.xOffset,
|
|
31
|
+
this.rect.y,
|
|
32
|
+
this.rect.width,
|
|
33
|
+
this.rect.height,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
update(collisions: collision[]): void {
|
|
38
|
+
// The animation only happens if the coin
|
|
39
|
+
// is triggered from an item block
|
|
40
|
+
if (this.fromItemBlock && this.totalRaise > 0) {
|
|
41
|
+
this.rect.y += this.speedY;
|
|
42
|
+
this.totalRaise += this.speedY;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.leftRightMovement(collisions);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MovingGameObject } from "../../../shared/game-objects/moving-game-object";
|
|
2
|
+
|
|
3
|
+
export abstract class PointObject extends MovingGameObject {
|
|
4
|
+
abstract pointValue: number;
|
|
5
|
+
|
|
6
|
+
collect() {
|
|
7
|
+
this.gameContext.addScore(this.pointValue);
|
|
8
|
+
this.gameContext.removeGameObject(this);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { collision } from "../../../shared/types";
|
|
2
|
+
import type { Brick } from "../set-pieces/obstacles/blocks/punchable-blockS/brick";
|
|
3
|
+
import type { GameContext } from "../../../shared/game-context";
|
|
4
|
+
import { Projectile } from "./projectile";
|
|
5
|
+
|
|
6
|
+
export class BrickDebris extends Projectile {
|
|
7
|
+
readonly acceptsCollision = false;
|
|
8
|
+
|
|
9
|
+
constructor(gameContext: GameContext, parent: Brick) {
|
|
10
|
+
super(gameContext, {
|
|
11
|
+
x: parent.rect.x + parent.rect.width / 2,
|
|
12
|
+
y: parent.rect.y + parent.rect.height / 2,
|
|
13
|
+
width: 8,
|
|
14
|
+
height: 8,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
+
update(_: collision[]): void {
|
|
20
|
+
this.rect.x += this.speedX;
|
|
21
|
+
this.rect.y += this.speedY;
|
|
22
|
+
this.speedY += this.gameContext.gravity;
|
|
23
|
+
|
|
24
|
+
if (this.rect.y > this.gameContext.gameArea.height) {
|
|
25
|
+
this.gameContext.removeGameObject(this);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
draw(ctx: CanvasRenderingContext2D): void {
|
|
30
|
+
ctx.fillStyle = "brown";
|
|
31
|
+
|
|
32
|
+
ctx.beginPath();
|
|
33
|
+
ctx.arc(
|
|
34
|
+
this.rect.x + this.rect.width / 2 + this.gameContext.xOffset,
|
|
35
|
+
this.rect.y + this.rect.height / 2,
|
|
36
|
+
this.rect.width,
|
|
37
|
+
0,
|
|
38
|
+
Math.PI * 2,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
ctx.fill();
|
|
42
|
+
ctx.closePath();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { collision } from "../../../../shared/types";
|
|
2
|
+
import { EnemyProjectile } from "./enemy-projectile";
|
|
3
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
4
|
+
import type { Enemy } from "../../point-objects/enemies/enemy";
|
|
5
|
+
import { Player } from "../../../../shared/player";
|
|
6
|
+
|
|
7
|
+
export class FireBall extends EnemyProjectile {
|
|
8
|
+
private readonly target: Player;
|
|
9
|
+
private readonly tracking: boolean = Math.random() < 0.25;
|
|
10
|
+
|
|
11
|
+
constructor(gameContext: GameContext, parent: Enemy) {
|
|
12
|
+
super(gameContext, {
|
|
13
|
+
x: parent.rect.x + parent.rect.width / 2,
|
|
14
|
+
y: parent.rect.y + parent.rect.height + 10,
|
|
15
|
+
width: 8,
|
|
16
|
+
height: 8,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
this.gameContext.removeGameObject(this);
|
|
21
|
+
}, 5000);
|
|
22
|
+
|
|
23
|
+
const player = gameContext.getPlayer();
|
|
24
|
+
if (player) {
|
|
25
|
+
this.target = player;
|
|
26
|
+
this.setSpeed();
|
|
27
|
+
} else {
|
|
28
|
+
throw new Error("Player not found");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(collisions: collision[]): void {
|
|
33
|
+
for (const collision of collisions) {
|
|
34
|
+
if (collision.gameObject instanceof Player) {
|
|
35
|
+
collision.gameObject.playerHit();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.gameContext.removeGameObject(this);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (this.rect.x < 0 || this.rect.x > this.gameContext.gameArea.width) {
|
|
43
|
+
this.gameContext.removeGameObject(this);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.rect.y < 0 || this.rect.y > this.gameContext.gameArea.height) {
|
|
48
|
+
this.gameContext.removeGameObject(this);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.tracking) {
|
|
53
|
+
this.setSpeed();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.rect.x += this.speedX;
|
|
57
|
+
this.rect.y += this.speedY;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
draw(ctx: CanvasRenderingContext2D): void {
|
|
61
|
+
ctx.beginPath();
|
|
62
|
+
ctx.lineWidth = 5;
|
|
63
|
+
ctx.fillStyle = "red";
|
|
64
|
+
ctx.arc(
|
|
65
|
+
this.rect.x + this.gameContext.xOffset,
|
|
66
|
+
this.rect.y,
|
|
67
|
+
this.rect.height,
|
|
68
|
+
0,
|
|
69
|
+
2 * Math.PI,
|
|
70
|
+
);
|
|
71
|
+
ctx.fill();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private setSpeed() {
|
|
75
|
+
const dx = this.target.rect.x - this.rect.x;
|
|
76
|
+
const dy = this.target.rect.y - this.rect.y;
|
|
77
|
+
const angle = Math.atan2(dy, dx);
|
|
78
|
+
|
|
79
|
+
if (this.tracking) {
|
|
80
|
+
this.speedX = Math.cos(angle) * 1.25;
|
|
81
|
+
this.speedY = Math.sin(angle) * 1.25;
|
|
82
|
+
} else {
|
|
83
|
+
this.speedX = Math.cos(angle) * 2;
|
|
84
|
+
this.speedY = Math.sin(angle) * 2;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { GameContext } from "../../../../shared/game-context";
|
|
2
|
+
import type { collision, rectangle } from "../../../../shared/types";
|
|
3
|
+
import type { FireBarBlock } from "../../set-pieces/obstacles/blocks/fire-bar-block";
|
|
4
|
+
import { RotatingGameObject } from "../../../../shared/game-objects/rotating-game-object";
|
|
5
|
+
|
|
6
|
+
export class FireBar extends RotatingGameObject {
|
|
7
|
+
private readonly anchorBlock: FireBarBlock;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
gameContext: GameContext,
|
|
11
|
+
|
|
12
|
+
x: number,
|
|
13
|
+
y: number,
|
|
14
|
+
anchorBlock: FireBarBlock,
|
|
15
|
+
) {
|
|
16
|
+
const rect: rectangle = {
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
width: 10,
|
|
20
|
+
height: 250,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
super(gameContext, rect);
|
|
24
|
+
this.anchorBlock = anchorBlock;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
28
|
+
update(_: collision[]): void {
|
|
29
|
+
this.rotation += this.rotationSpeed;
|
|
30
|
+
this.rotation %= Math.PI * 2;
|
|
31
|
+
|
|
32
|
+
const anchorCenterX =
|
|
33
|
+
this.anchorBlock.rect.x + this.anchorBlock.rect.width / 2;
|
|
34
|
+
const anchorCenterY =
|
|
35
|
+
this.anchorBlock.rect.y + this.anchorBlock.rect.height / 2;
|
|
36
|
+
|
|
37
|
+
// Position the firebar so its BOTTOM CENTER is at the anchor center
|
|
38
|
+
this.rect.x = anchorCenterX - this.rect.width / 2;
|
|
39
|
+
this.rect.y = anchorCenterY - this.rect.height;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
draw(ctx: CanvasRenderingContext2D): void {
|
|
43
|
+
ctx.save();
|
|
44
|
+
|
|
45
|
+
// Move the origin to the bottom-center of the firebar
|
|
46
|
+
ctx.translate(
|
|
47
|
+
this.rect.x + this.rect.width / 2 + this.gameContext.xOffset,
|
|
48
|
+
this.rect.y + this.rect.height,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Rotate around the bottom edge
|
|
52
|
+
ctx.rotate(this.rotation);
|
|
53
|
+
|
|
54
|
+
// Draw the bar with its bottom edge at the origin
|
|
55
|
+
ctx.fillStyle = "red";
|
|
56
|
+
ctx.fillRect(
|
|
57
|
+
-this.rect.width / 2, // center horizontally
|
|
58
|
+
-this.rect.height, // draw upward from pivot
|
|
59
|
+
this.rect.width,
|
|
60
|
+
this.rect.height,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
ctx.restore();
|
|
64
|
+
}
|
|
65
|
+
}
|