mirror of
https://github.com/Sheldan/canvas.git
synced 2026-04-13 11:31:37 +00:00
326 lines
8.0 KiB
TypeScript
326 lines
8.0 KiB
TypeScript
import type {Acting, ChanceEntry, Drawable, Healthy, Placeable, Shooting} from "./interfaces.ts";
|
|
import {drawDot, moveInDirectionOf} from "./utils.ts";
|
|
import {Vector} from "./base.ts";
|
|
import {World} from "./World.ts";
|
|
import type {Projectile} from "./projectile.ts";
|
|
import {ProjectileStats, StraightProjectile} from "./projectile.ts";
|
|
import {HealthPack, ItemDrop, LevelDrop, MoneyDrop} from "./drop.ts";
|
|
import {ItemManagement} from "./items.ts";
|
|
|
|
export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
|
|
protected _position: Vector;
|
|
protected speed: number;
|
|
protected world: World;
|
|
protected size: number
|
|
protected status: EnemyStatus = new EnemyStatus(10);
|
|
protected drops: KillChanceTable;
|
|
|
|
constructor(position: Vector) {
|
|
this.drops = new KillChanceTable();
|
|
this.drops.addDrop( {chance: 10, creationMethod: this.spawnMoney})
|
|
this._position = position.clone();
|
|
}
|
|
|
|
draw(ctx: CanvasRenderingContext2D) {
|
|
}
|
|
|
|
act() {
|
|
this.move()
|
|
}
|
|
|
|
move() {
|
|
}
|
|
|
|
|
|
getPosition(): Vector {
|
|
return this._position;
|
|
}
|
|
|
|
takeDamage(damage: number) {
|
|
this.status.health -= damage;
|
|
if(this.status.dead) {
|
|
this.die()
|
|
this.world.removeEnemy(this)
|
|
}
|
|
}
|
|
|
|
die() {
|
|
let draw = this.drops.draw();
|
|
if(draw) {
|
|
draw.creationMethod(this)
|
|
}
|
|
}
|
|
|
|
spawnMoney(enemy: Enemy) {
|
|
MoneyDrop.spawnMoneyDrop(enemy.world, enemy._position);
|
|
}
|
|
|
|
getSize() {
|
|
return this.size;
|
|
}
|
|
|
|
dead() {
|
|
return this.status.dead
|
|
}
|
|
|
|
}
|
|
|
|
export class BasicEnemy extends Enemy {
|
|
|
|
constructor(position: Vector) {
|
|
super(position);
|
|
}
|
|
|
|
protected color: string;
|
|
protected impactDamage: number;
|
|
protected impactCooldown: number = 0;
|
|
protected impactInterval: number = 60;
|
|
|
|
draw(ctx: CanvasRenderingContext2D) {
|
|
drawDot(this._position, this.getSize(), this.color, ctx)
|
|
}
|
|
|
|
|
|
move() {
|
|
this._position = moveInDirectionOf(this._position, this.world.player.position, this.speed)
|
|
}
|
|
|
|
act() {
|
|
super.act();
|
|
if(this._position.distanceTo(this.world.player.position) < this.getSize() && this.impactCooldown <= 0) {
|
|
this.world.player.takeDamage(this.impactDamage)
|
|
this.impactCooldown = this.impactInterval;
|
|
}
|
|
this.impactCooldown -= 1;
|
|
}
|
|
|
|
static spawnBasicEnemy(world: World, position?: Vector) {
|
|
world.addEnemy(this.generateBasicEnemy(world, position))
|
|
}
|
|
|
|
static generateBasicEnemy(world: World, position?: Vector): BasicEnemy {
|
|
if(position === undefined) {
|
|
position = world.randomPlace()
|
|
}
|
|
let basicEnemy = new BasicEnemy(position);
|
|
basicEnemy.size = 5;
|
|
basicEnemy.world = world;
|
|
basicEnemy.speed = 0.5;
|
|
basicEnemy.color = 'orange'
|
|
basicEnemy.impactDamage = 2;
|
|
return basicEnemy;
|
|
}
|
|
|
|
}
|
|
|
|
export class ShootingEnemy extends BasicEnemy implements Shooting {
|
|
private shootCooldown: number = 0;
|
|
private shootInterval: number;
|
|
private projectiles: Projectile[] = []
|
|
|
|
constructor(position: Vector) {
|
|
super(position);
|
|
}
|
|
|
|
removeProjectile(projectile: Projectile) {
|
|
this.projectiles = this.projectiles.filter(item => item !== projectile)
|
|
}
|
|
|
|
act() {
|
|
super.act();
|
|
if(this.shootCooldown <= 0) {
|
|
this.createProjectile()
|
|
this.shootCooldown = this.shootInterval;
|
|
}
|
|
this.shootCooldown -= 1;
|
|
}
|
|
|
|
createProjectile() {
|
|
let stats = new ProjectileStats()
|
|
.withPiercings(0)
|
|
.withSize(1)
|
|
.withDamage(5)
|
|
.withSpeed(2)
|
|
let projectile = StraightProjectile.createStraightProjectile(this.world, this._position, this.world.player.position, this, stats)
|
|
this.projectiles.push(projectile)
|
|
return projectile
|
|
}
|
|
|
|
static spawnShootingEnemy(world: World, position?: Vector) {
|
|
world.addEnemy(this.generateShootingEnemy(world, position))
|
|
}
|
|
|
|
static generateShootingEnemy(world: World, position?: Vector) {
|
|
if(position === undefined) {
|
|
position = world.randomPlace()
|
|
}
|
|
let shootingEnemy = new ShootingEnemy(position);
|
|
shootingEnemy.size = 5;
|
|
shootingEnemy.world = world;
|
|
shootingEnemy.speed = 0.5;
|
|
shootingEnemy.color = 'green'
|
|
shootingEnemy.impactDamage = 2;
|
|
shootingEnemy.shootInterval = 100
|
|
return shootingEnemy
|
|
}
|
|
}
|
|
|
|
export class EnemyStatus {
|
|
constructor(private _health: number) {
|
|
}
|
|
|
|
|
|
get health(): number {
|
|
return this._health;
|
|
}
|
|
|
|
get dead(): boolean {
|
|
return this._health <= 0;
|
|
}
|
|
|
|
set health(value: number) {
|
|
this._health = value;
|
|
}
|
|
}
|
|
|
|
export class HealthEnemy extends Enemy {
|
|
|
|
constructor(position: Vector) {
|
|
super(position);
|
|
}
|
|
|
|
protected color: string;
|
|
|
|
draw(ctx: CanvasRenderingContext2D) {
|
|
drawDot(this._position, this.size, this.color, ctx)
|
|
}
|
|
|
|
|
|
move() {
|
|
}
|
|
|
|
act() {
|
|
super.act();
|
|
}
|
|
|
|
die() {
|
|
HealthPack.spawnHealthPack(this.world, this._position)
|
|
}
|
|
|
|
static spawnHealthEnemy(world: World, position?: Vector) {
|
|
world.addEnemy(this.createHealthEnemy(world, position))
|
|
}
|
|
|
|
static createHealthEnemy(world: World, position?: Vector) {
|
|
if(position === undefined) {
|
|
position = world.randomPlace()
|
|
}
|
|
let basicEnemy = new HealthEnemy(position);
|
|
basicEnemy.size = 5;
|
|
basicEnemy.world = world;
|
|
basicEnemy.speed = 0;
|
|
basicEnemy.color = 'purple'
|
|
return basicEnemy;
|
|
}
|
|
|
|
|
|
getSize() {
|
|
return this.size
|
|
}
|
|
}
|
|
|
|
export class ContainerEnemy extends Enemy {
|
|
|
|
private drops: KillChanceTable;
|
|
constructor(position: Vector) {
|
|
super(position);
|
|
this.status.health = 5;
|
|
this.drops = new KillChanceTable();
|
|
ItemManagement.getItemsWithRarityFactor().forEach(drop => {
|
|
this.drops.addDrop(drop)
|
|
})
|
|
this.drops.calculateProbs()
|
|
}
|
|
|
|
protected color: string;
|
|
|
|
draw(ctx: CanvasRenderingContext2D) {
|
|
drawDot(this._position, this.size, this.color, ctx)
|
|
}
|
|
|
|
move() {
|
|
}
|
|
|
|
act() {
|
|
super.act();
|
|
}
|
|
|
|
die() {
|
|
let draw = this.drops.draw();
|
|
if(draw) {
|
|
let item = draw.creationMethod(this);
|
|
ItemDrop.spawnItemDrop(this.world, item, this._position)
|
|
}
|
|
}
|
|
|
|
spawnHealthPack(enemy: ContainerEnemy) {
|
|
HealthPack.spawnHealthPack(enemy.world, enemy._position)
|
|
}
|
|
|
|
spawnLevelUp(enemy: ContainerEnemy) {
|
|
LevelDrop.spawnLevelDrop(enemy.world, enemy._position)
|
|
}
|
|
|
|
spawnEnemy(enemy: ContainerEnemy) {
|
|
ShootingEnemy.spawnShootingEnemy(enemy.world, enemy._position)
|
|
}
|
|
|
|
static spawnContainerEnemy(world: World, position?: Vector) {
|
|
world.addEnemy(this.createContainerEnemy(world, position))
|
|
}
|
|
|
|
static createContainerEnemy(world: World, position?: Vector) {
|
|
if(position === undefined) {
|
|
position = world.randomPlace()
|
|
}
|
|
let basicEnemy = new ContainerEnemy(position);
|
|
basicEnemy.size = 5;
|
|
basicEnemy.world = world;
|
|
basicEnemy.speed = 0;
|
|
basicEnemy.color = 'brown'
|
|
return basicEnemy;
|
|
}
|
|
|
|
|
|
getSize() {
|
|
return this.size
|
|
}
|
|
}
|
|
|
|
|
|
export class KillChanceTable {
|
|
private chances: ChanceEntry[] = []
|
|
|
|
addDrop(entry: ChanceEntry) {
|
|
this.chances.push(entry)
|
|
}
|
|
|
|
calculateProbs() {
|
|
let sum = this.chances.reduce((sum, entry) => sum + entry.chance, 0)
|
|
this.chances.forEach(value => value.chance /= sum)
|
|
}
|
|
|
|
draw() {
|
|
if(this.chances.length === 0) {
|
|
return undefined;
|
|
}
|
|
let change = Math.random();
|
|
for (const value of this.chances) {
|
|
change -= value.chance;
|
|
if(change <= 0) {
|
|
return value;
|
|
}
|
|
}
|
|
return this.chances[this.chances.length - 1]
|
|
}
|
|
} |