Files
canvas/absurd-survivors/src/Enemies.ts
Sheldan 7214a64b77 survivors: adding items and item drops
adding rarities to items
adding drops to enemies
2025-09-05 22:33:09 +02:00

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]
}
}