survivors: adding enemy health scaling

adding dmg bonus items and dmg scaling stat for player
This commit is contained in:
Sheldan
2025-09-15 01:14:43 +02:00
parent 26ad150b59
commit ef162a7dc2
7 changed files with 164 additions and 25 deletions

View File

@@ -6,7 +6,7 @@ import type {Projectile} from "./projectile.ts";
import {StraightProjectile} from "./projectile.ts"; import {StraightProjectile} from "./projectile.ts";
import {HealthPack, ItemDrop, LevelDrop, MoneyDrop} from "./drop.ts"; import {HealthPack, ItemDrop, LevelDrop, MoneyDrop} from "./drop.ts";
import {ItemManagement} from "./items.ts"; import {ItemManagement} from "./items.ts";
import {ProjectileStats} from "./stats.ts"; import {EnemyStats, ProjectileStats} from "./stats.ts";
import {EnemyStatus} from "./status.ts"; import {EnemyStatus} from "./status.ts";
import {NumberDisplayParticle} from "./particles.ts"; import {NumberDisplayParticle} from "./particles.ts";
@@ -15,13 +15,15 @@ export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
protected speed: number; protected speed: number;
protected world: World; protected world: World;
protected size: number protected size: number
protected status: EnemyStatus = new EnemyStatus(10); protected status: EnemyStatus;
protected drops: KillChanceTable; protected drops: KillChanceTable;
constructor(position: Vector) { constructor(position: Vector, enemyStats: EnemyStats) {
this.drops = new KillChanceTable(); this.drops = new KillChanceTable();
this.drops.addDrop( {chance: 10, creationMethod: this.spawnMoney}) this.drops.addDrop( {chance: 10, creationMethod: this.spawnMoney})
this._position = position.clone(); this._position = position.clone();
let health = enemyStats.getEffectiveHealth()
this.status = new EnemyStatus(health);
} }
draw(ctx: CanvasRenderingContext2D) { draw(ctx: CanvasRenderingContext2D) {
@@ -74,8 +76,8 @@ export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
export class BasicEnemy extends Enemy { export class BasicEnemy extends Enemy {
constructor(position: Vector) { constructor(position: Vector, enemyStats: EnemyStats) {
super(position); super(position, enemyStats);
} }
protected color: string; protected color: string;
@@ -109,7 +111,8 @@ export class BasicEnemy extends Enemy {
if(position === undefined) { if(position === undefined) {
position = world.randomPlace() position = world.randomPlace()
} }
let basicEnemy = new BasicEnemy(position); let enemyStats = new EnemyStats().withHealthFactor(world.getEnemyHealthFactor());
let basicEnemy = new BasicEnemy(position, enemyStats);
basicEnemy.size = 5; basicEnemy.size = 5;
basicEnemy.world = world; basicEnemy.world = world;
basicEnemy.speed = 0.5; basicEnemy.speed = 0.5;
@@ -125,8 +128,8 @@ export class ShootingEnemy extends BasicEnemy implements Shooting {
private shootInterval: number; private shootInterval: number;
private projectiles: Projectile[] = [] private projectiles: Projectile[] = []
constructor(position: Vector) { constructor(position: Vector, enemyStats: EnemyStats) {
super(position); super(position, enemyStats);
} }
removeProjectile(projectile: Projectile) { removeProjectile(projectile: Projectile) {
@@ -161,7 +164,8 @@ export class ShootingEnemy extends BasicEnemy implements Shooting {
if(position === undefined) { if(position === undefined) {
position = world.randomPlace() position = world.randomPlace()
} }
let shootingEnemy = new ShootingEnemy(position); let enemyStats = new EnemyStats().withHealthFactor(world.getEnemyHealthFactor());;
let shootingEnemy = new ShootingEnemy(position, enemyStats);
shootingEnemy.size = 5; shootingEnemy.size = 5;
shootingEnemy.world = world; shootingEnemy.world = world;
shootingEnemy.speed = 0.5; shootingEnemy.speed = 0.5;
@@ -174,8 +178,8 @@ export class ShootingEnemy extends BasicEnemy implements Shooting {
export class HealthEnemy extends Enemy { export class HealthEnemy extends Enemy {
constructor(position: Vector) { constructor(position: Vector, enemyStats: EnemyStats) {
super(position); super(position, enemyStats);
} }
protected color: string; protected color: string;
@@ -204,7 +208,8 @@ export class HealthEnemy extends Enemy {
if(position === undefined) { if(position === undefined) {
position = world.randomPlace() position = world.randomPlace()
} }
let basicEnemy = new HealthEnemy(position); let enemyStats = new EnemyStats().withHealthFactor(world.getEnemyHealthFactor());;
let basicEnemy = new HealthEnemy(position, enemyStats);
basicEnemy.size = 5; basicEnemy.size = 5;
basicEnemy.world = world; basicEnemy.world = world;
basicEnemy.speed = 0; basicEnemy.speed = 0;
@@ -221,8 +226,8 @@ export class HealthEnemy extends Enemy {
export class ContainerEnemy extends Enemy { export class ContainerEnemy extends Enemy {
private drops: KillChanceTable; private drops: KillChanceTable;
constructor(position: Vector) { constructor(position: Vector, enemyStats: EnemyStats) {
super(position); super(position, enemyStats);
this.status.health = 5; this.status.health = 5;
this.drops = new KillChanceTable(); this.drops = new KillChanceTable();
ItemManagement.getItemsWithRarityFactor().forEach(drop => { ItemManagement.getItemsWithRarityFactor().forEach(drop => {
@@ -272,7 +277,8 @@ export class ContainerEnemy extends Enemy {
if(position === undefined) { if(position === undefined) {
position = world.randomPlace() position = world.randomPlace()
} }
let basicEnemy = new ContainerEnemy(position); let enemyStats = new EnemyStats().withHealthFactor(world.getEnemyHealthFactor());;
let basicEnemy = new ContainerEnemy(position, enemyStats);
basicEnemy.size = 5; basicEnemy.size = 5;
basicEnemy.world = world; basicEnemy.world = world;
basicEnemy.speed = 0; basicEnemy.speed = 0;

View File

@@ -15,12 +15,14 @@ export class World {
private _tick: number = 0; private _tick: number = 0;
private static readonly TICK_INTERVAL = 10; private static readonly TICK_INTERVAL = 10;
private timeStamp: Date; private timeStamp: Date;
private startTimeStamp: Date;
constructor(player: Player, ctx: CanvasRenderingContext2D, size: Vector) { constructor(player: Player, ctx: CanvasRenderingContext2D, size: Vector) {
this._player = player; this._player = player;
this._ctx = ctx; this._ctx = ctx;
this._size = size; this._size = size;
this.timeStamp = new Date(); this.timeStamp = new Date();
this.startTimeStamp = new Date();
} }
enemiesAct() { enemiesAct() {
@@ -80,7 +82,6 @@ export class World {
this._player.position.y = Math.max(this._player.getSize(), this._player.position.y) this._player.position.y = Math.max(this._player.getSize(), this._player.position.y)
} }
maxValue() { maxValue() {
return Math.max(this.size.x, this.size.y) return Math.max(this.size.x, this.size.y)
} }
@@ -89,7 +90,19 @@ export class World {
return this._size; return this._size;
} }
tick() { get tick(): number {
return this._tick;
}
getSecondsPassed() {
return (this.timeStamp.getTime() - this.startTimeStamp.getTime()) / 1000
}
getEnemyHealthFactor() {
return this.getSecondsPassed() / 30;
}
triggerTick() {
this._tick += 1; this._tick += 1;
if((this._tick % World.TICK_INTERVAL) == 0) { if((this._tick % World.TICK_INTERVAL) == 0) {
let currentTimeStamp = new Date(); let currentTimeStamp = new Date();

View File

@@ -44,6 +44,10 @@ export class ItemManagement {
this.ITEMS.push(new ChainBallWeaponItem()) this.ITEMS.push(new ChainBallWeaponItem())
this.ITEMS.push(new PullRangeUp()) this.ITEMS.push(new PullRangeUp())
this.ITEMS.push(new SpearWeaponItem()) this.ITEMS.push(new SpearWeaponItem())
this.ITEMS.push(new DamageUpFactor())
this.ITEMS.push(new DamageFactorUp())
this.ITEMS.push(new DamageFactorUpFactor())
this.ITEMS.push(new DamageUp())
} }
} }
@@ -206,5 +210,62 @@ export class SpearWeaponItem extends BaseItem {
} }
} }
export class DamageFactorUpFactor extends BaseItem {
pickup(player: Player, world: World) {
player.changeBaseStat(1, PlayerStats.factorDamageFactor)
super.pickup(player, world)
}
name() {
return 'damageFactorUpFactor'
}
getRarity(): Rarity {
return Rarity.LEGENDARY;
}
}
export class DamageUp extends BaseItem {
pickup(player: Player, world: World) {
player.changeBaseStat(1, PlayerStats.increaseDamage)
super.pickup(player, world)
}
name() {
return 'damageUp'
}
getRarity(): Rarity {
return Rarity.COMMON;
}
}
export class DamageUpFactor extends BaseItem {
pickup(player: Player, world: World) {
player.changeBaseStat(1, PlayerStats.factorDamage)
super.pickup(player, world)
}
name() {
return 'damageUpFactor'
}
getRarity(): Rarity {
return Rarity.LEGENDARY;
}
}
export class DamageFactorUp extends BaseItem {
pickup(player: Player, world: World) {
player.changeBaseStat(1, PlayerStats.increaseDamageFactor)
super.pickup(player, world)
}
name() {
return 'damageFactorUp'
}
getRarity(): Rarity {
return Rarity.UNCOMMON;
}
}

View File

@@ -38,7 +38,7 @@ function updateCanvas() {
ctx.clearRect(0, 0, world.size.x, world.size.y); ctx.clearRect(0, 0, world.size.x, world.size.y);
hud.draw(ctx) hud.draw(ctx)
if(!state.ended) { if(!state.ended) {
world.tick() world.triggerTick()
world.enemiesAct() world.enemiesAct()
world.player.act() world.player.act()
world.draw() world.draw()

View File

@@ -8,6 +8,8 @@ export class PlayerStats {
private _weaponRange: number; private _weaponRange: number;
private _weaponRangeFactor: number; private _weaponRangeFactor: number;
private _healthRegen: number; private _healthRegen: number;
private _damage: number;
private _damageFactor: number;
constructor() { constructor() {
this._speed = 3; this._speed = 3;
@@ -17,6 +19,8 @@ export class PlayerStats {
this._weaponRange = 250; this._weaponRange = 250;
this._weaponRangeFactor = 1; this._weaponRangeFactor = 1;
this._healthRegen = 0.001; this._healthRegen = 0.001;
this._damage = 0;
this._damageFactor = 0;
} }
resetToBasic() { resetToBasic() {
@@ -25,7 +29,9 @@ export class PlayerStats {
this._pullRange = 0; this._pullRange = 0;
this._weaponRange = 0; this._weaponRange = 0;
this._weaponRangeFactor = 1; this._weaponRangeFactor = 1;
this._healthRegen = 0.1; this._healthRegen = 0.001;
this._damage = 0;
this._damageFactor = 0;
} }
increaseLevel() { increaseLevel() {
@@ -35,6 +41,8 @@ export class PlayerStats {
this._weaponRange *= 1.25 this._weaponRange *= 1.25
this._weaponRangeFactor += 0.1 this._weaponRangeFactor += 0.1
this._healthRegen += 0.1 this._healthRegen += 0.1
this._damage += 1;
this._damageFactor += 0.1;
} }
mergeStats(otherStats: PlayerStats) { mergeStats(otherStats: PlayerStats) {
@@ -44,6 +52,8 @@ export class PlayerStats {
this._weaponRange += otherStats._weaponRange this._weaponRange += otherStats._weaponRange
this._weaponRangeFactor += otherStats._weaponRangeFactor; this._weaponRangeFactor += otherStats._weaponRangeFactor;
this._healthRegen += otherStats._healthRegen; this._healthRegen += otherStats._healthRegen;
this._damage += otherStats._damage;
this._damageFactor += otherStats._damageFactor
} }
clone() { clone() {
@@ -64,6 +74,22 @@ export class PlayerStats {
stats._speed *= value stats._speed *= value
} }
static factorDamage(stats: PlayerStats, value: number) {
stats._damage *= value
}
static increaseDamage(stats: PlayerStats, value: number) {
stats._damage += value
}
static factorDamageFactor(stats: PlayerStats, value: number) {
stats._damageFactor *= value
}
static increaseDamageFactor(stats: PlayerStats, value: number) {
stats._damageFactor += value
}
static increasePullRange(stats: PlayerStats, value: number) { static increasePullRange(stats: PlayerStats, value: number) {
stats._pullRange += value stats._pullRange += value
} }
@@ -104,6 +130,10 @@ export class PlayerStats {
return this._weaponRange * this._weaponRangeFactor; return this._weaponRange * this._weaponRangeFactor;
} }
get effectiveDamage(): number {
return this._damage * this._damageFactor;
}
public static defaultPlayerStats(): PlayerStats { public static defaultPlayerStats(): PlayerStats {
return new PlayerStats(); return new PlayerStats();
} }
@@ -178,4 +208,28 @@ export class ProjectileStats {
get deathSplit(): number { get deathSplit(): number {
return this._deathSplit; return this._deathSplit;
} }
}
export class EnemyStats {
private _healthFactor: number;
private _baseHealth: number;
constructor() {
this._healthFactor = 1;
this._baseHealth = 10
}
withHealthFactor(healthFactor: number) {
this._healthFactor = healthFactor;
return this
}
withBaseHealth(baseHealth: number) {
this._baseHealth = baseHealth;
return this
}
getEffectiveHealth() {
return this._baseHealth * this._healthFactor
}
} }

View File

@@ -127,7 +127,8 @@ export class PlayerInfo implements DrawContainer {
new StatLabel(() => 'Level', () => this.world.player.status.level), new StatLabel(() => 'Level', () => this.world.player.status.level),
new StatLabel(() => 'Speed', () => Math.floor(this.world.player.effectiveStats.speed)), new StatLabel(() => 'Speed', () => Math.floor(this.world.player.effectiveStats.speed)),
new StatLabel(() => 'Pull range', () => Math.floor(this.world.player.effectiveStats.pullRange)), new StatLabel(() => 'Pull range', () => Math.floor(this.world.player.effectiveStats.pullRange)),
new StatLabel(() => 'Weapon range', () => Math.floor(this.world.player.effectiveStats.effectiveWeaponRange)) new StatLabel(() => 'Weapon range', () => Math.floor(this.world.player.effectiveStats.effectiveWeaponRange)),
new StatLabel(() => 'Damage', () => Math.floor(this.world.player.effectiveStats.effectiveDamage))
] ]
} }

View File

@@ -50,6 +50,10 @@ export abstract class BasicWeapon implements Weapon {
return this.size; return this.size;
} }
getDamage() {
return this.stats.damage + this.player.effectiveStats.effectiveDamage;
}
getOffset(): Vector { getOffset(): Vector {
return this.offset; return this.offset;
} }
@@ -120,7 +124,7 @@ export class Spear extends MeleeWeapon {
let stats = new ProjectileStats() let stats = new ProjectileStats()
.withPiercings(1000) .withPiercings(1000)
.withSize(3) .withSize(3)
.withDamage(this.stats.damage) .withDamage(this.getDamage())
.withSpeed(this.stats.projectileSpeed); .withSpeed(this.stats.projectileSpeed);
let offsetVector = Vector.createVector(closestTargetTo[1]!.getPosition(), this.player.position).multiply(1.1); let offsetVector = Vector.createVector(closestTargetTo[1]!.getPosition(), this.player.position).multiply(1.1);
if(offsetVector.vecLength() < 15) { if(offsetVector.vecLength() < 15) {
@@ -161,7 +165,7 @@ export class ChainBall extends MeleeWeapon {
let stats = new ProjectileStats() let stats = new ProjectileStats()
.withPiercings(1000) .withPiercings(1000)
.withSize(3) .withSize(3)
.withDamage(this.stats.damage) .withDamage(this.getDamage())
.withSpeed(this.stats.projectileSpeed); .withSpeed(this.stats.projectileSpeed);
let projectile = ChainBallProjectile.createChainBallProjectile(this.world, this.getPosition(), closestTargetTo[1]!.getPosition(), this.player, stats, this) let projectile = ChainBallProjectile.createChainBallProjectile(this.world, this.getPosition(), closestTargetTo[1]!.getPosition(), this.player, stats, this)
this.projectiles.push(projectile) this.projectiles.push(projectile)
@@ -201,7 +205,7 @@ export class HomingPistol extends RangeWeapon {
let stats = new ProjectileStats() let stats = new ProjectileStats()
.withPiercings(this.stats.projectilePiercings) .withPiercings(this.stats.projectilePiercings)
.withSize(1) .withSize(1)
.withDamage(this.stats.damage) .withDamage(this.getDamage())
.withSpeed(this.stats.projectileSpeed); .withSpeed(this.stats.projectileSpeed);
let projectile = HomingProjectile.createHomingProjectile(this.world, this.getPosition(), this.player, closestTargetTo[1]!, stats, 'yellow') let projectile = HomingProjectile.createHomingProjectile(this.world, this.getPosition(), this.player, closestTargetTo[1]!, stats, 'yellow')
this.projectiles.push(projectile) this.projectiles.push(projectile)
@@ -240,7 +244,7 @@ export class Pistol extends RangeWeapon {
let stats = new ProjectileStats() let stats = new ProjectileStats()
.withPiercings(this.stats.projectilePiercings) .withPiercings(this.stats.projectilePiercings)
.withSize(1) .withSize(1)
.withDamage(this.stats.damage) .withDamage(this.getDamage())
.withSpeed(this.stats.projectileSpeed); .withSpeed(this.stats.projectileSpeed);
let projectile = StraightProjectile.createStraightProjectile(this.world, this.getPosition(), closestTargetTo[1]!.getPosition(), this.player, stats, 'pink') let projectile = StraightProjectile.createStraightProjectile(this.world, this.getPosition(), closestTargetTo[1]!.getPosition(), this.player, stats, 'pink')
this.projectiles.push(projectile) this.projectiles.push(projectile)
@@ -278,7 +282,7 @@ export class SpreadWeapon extends RangeWeapon {
let stats = new ProjectileStats() let stats = new ProjectileStats()
.withPiercings(this.stats.projectilePiercings) .withPiercings(this.stats.projectilePiercings)
.withSize(1) .withSize(1)
.withDamage(this.stats.damage) .withDamage(this.getDamage())
.withSpeed(this.stats.projectileSpeed); .withSpeed(this.stats.projectileSpeed);
let targetPosition = closestTargetTo[1]!.getPosition(); let targetPosition = closestTargetTo[1]!.getPosition();
let weaponPosition = this.getPosition(); let weaponPosition = this.getPosition();