From 9bb7ec99c07302ecc7c9657d57a17403c0e88ffc Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:09:12 +0200 Subject: [PATCH] survivors: adding health regen and simple particle system --- absurd-survivors/src/Player.ts | 29 ++++++++++++- absurd-survivors/src/World.ts | 30 ++++++++++++- absurd-survivors/src/drop.ts | 3 ++ absurd-survivors/src/interfaces.ts | 5 +++ absurd-survivors/src/main.ts | 3 +- absurd-survivors/src/particles.ts | 69 ++++++++++++++++++++++++++++++ absurd-survivors/src/stats.ts | 11 ++++- 7 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 absurd-survivors/src/particles.ts diff --git a/absurd-survivors/src/Player.ts b/absurd-survivors/src/Player.ts index 375caa5..7d53e00 100644 --- a/absurd-survivors/src/Player.ts +++ b/absurd-survivors/src/Player.ts @@ -3,6 +3,8 @@ import {Vector} from "./base.ts"; import {fillDot, getCoordinatesSplit} from "./utils.ts"; import {PlayerStats} from "./stats.ts"; import {PlayerStatus} from "./status.ts"; +import {World} from "./World.ts"; +import {HealingParticle} from "./particles.ts"; export class Player implements Drawable, Acting, Healthy { private _position: Vector; @@ -14,10 +16,13 @@ export class Player implements Drawable, Acting, Healthy { private _status: PlayerStatus; private _weapons: Weapon[] = [] private _items: Item[] = [] + private _healTick: number = 0; + private static readonly HEAL_TICK_INTERVAL = 20; + private _world: World; // temp private _speed: Vector; - + private _toHeal: number = 0; constructor(position: Vector) { this._position = position; @@ -61,6 +66,10 @@ export class Player implements Drawable, Acting, Healthy { this._items.push(item) } + set world(value: World) { + this._world = value; + } + move(direction: Vector) { this._position = this.position.add(direction) } @@ -145,5 +154,23 @@ export class Player implements Drawable, Acting, Healthy { level() { return this.status.level } + + isHurt() { + return this.health < this._effectiveStats.health + } + + tick(seconds: number, tick: number) { + this._healTick += 1; + if((this._healTick % Player.HEAL_TICK_INTERVAL) == 0 && this.isHurt()) { + let healed = this._effectiveStats.healthRegen / seconds + this._toHeal += healed; + if(this._toHeal >= 1) { + let toHealNow = this._toHeal - (this._toHeal % 1); + this._toHeal -= toHealNow; + this.heal(toHealNow); + HealingParticle.spawnHealingParticle(this._world, toHealNow, this.position) + } + } + } } diff --git a/absurd-survivors/src/World.ts b/absurd-survivors/src/World.ts index bf91768..413210e 100644 --- a/absurd-survivors/src/World.ts +++ b/absurd-survivors/src/World.ts @@ -2,20 +2,25 @@ import {Enemy} from "./Enemies.ts"; import {Player} from "./Player.ts"; import {Projectile } from "./projectile.ts"; import {Vector} from "./base.ts"; -import type {Drop, Placeable} from "./interfaces.ts"; +import type {Drop, Particle, Placeable} from "./interfaces.ts"; export class World { private _enemies: ObjectContainer = new ObjectContainer() private _projectiles: ObjectContainer = new ObjectContainer(); private _drops: ObjectContainer = new ObjectContainer(); + private _particles: ObjectContainer = new ObjectContainer(); private _player: Player; private readonly _ctx: CanvasRenderingContext2D; private _size: Vector; + private _tick: number = 0; + private static readonly TICK_INTERVAL = 10; + private timeStamp: Date; constructor(player: Player, ctx: CanvasRenderingContext2D, size: Vector) { this._player = player; this._ctx = ctx; this._size = size; + this.timeStamp = new Date(); } enemiesAct() { @@ -25,12 +30,15 @@ export class World { this._projectiles.clean() this._drops.items.forEach(drop => drop.act()) this._drops.clean() + this._particles.items.forEach(particle => particle.act()) + this._particles.clean() } draw() { this._enemies.items.forEach(enemy => enemy.draw(this._ctx)) this._drops.items.forEach(drop => drop.draw(this._ctx)) this._projectiles.items.forEach(projectile => projectile.draw(this._ctx)) + this._particles.items.forEach(particle => particle.draw(this._ctx)) this._player.draw(this._ctx); } @@ -38,6 +46,10 @@ export class World { this._projectiles.add(projectile) } + addParticle(particle: Particle) { + this._particles.add(particle) + } + addDrop(drop: Drop) { this._drops.add(drop) } @@ -46,6 +58,10 @@ export class World { this._drops.scheduleRemoval(drop) } + removeParticle(particle: Particle) { + this._particles.scheduleRemoval(particle) + } + removeEnemy(enemy: Enemy) { this._enemies.scheduleRemoval(enemy) } @@ -69,11 +85,21 @@ export class World { return Math.max(this.size.x, this.size.y) } - get size(): Vector { return this._size; } + tick() { + this._tick += 1; + if((this._tick % World.TICK_INTERVAL) == 0) { + let currentTimeStamp = new Date(); + let seconds = (currentTimeStamp.getTime() - this.timeStamp.getTime()) / 1000; + this._player.tick(seconds, this._tick); + this._particles.items.forEach(particle => particle.tick(seconds, this._tick)) + this.timeStamp = currentTimeStamp; + } + } + outside(position: Vector): boolean { return position.x > this.size.x || position.y > this.size.y || position.x < 0 || position.y < 0 } diff --git a/absurd-survivors/src/drop.ts b/absurd-survivors/src/drop.ts index c59c0fe..c4ccf92 100644 --- a/absurd-survivors/src/drop.ts +++ b/absurd-survivors/src/drop.ts @@ -42,6 +42,9 @@ export abstract class BasicDrop implements Drop { return this.size } + tick(seconds: number, tick: number) { + } + } export class MoneyDrop extends BasicDrop { diff --git a/absurd-survivors/src/interfaces.ts b/absurd-survivors/src/interfaces.ts index 6b06582..887df1c 100644 --- a/absurd-survivors/src/interfaces.ts +++ b/absurd-survivors/src/interfaces.ts @@ -5,6 +5,7 @@ import type {World} from "./World.ts"; export interface Acting { act() + tick(seconds: number, tick: number) } export interface Healthy { @@ -33,6 +34,10 @@ export interface Drop extends Drawable, Acting { pickup() } +export interface Particle extends Drawable, Placeable, Acting { + +} + export interface Placeable { move(any?: any) getSize(); diff --git a/absurd-survivors/src/main.ts b/absurd-survivors/src/main.ts index 6b633bb..1e874cf 100644 --- a/absurd-survivors/src/main.ts +++ b/absurd-survivors/src/main.ts @@ -9,7 +9,6 @@ import {HUD} from "./ui.ts"; import {Pistol} from "./weapons.ts"; import {ItemManagement} from "./items.ts"; - let hud: HUD; let world: World; let config: Config; @@ -39,6 +38,7 @@ function updateCanvas() { ctx.clearRect(0, 0, world.size.x, world.size.y); hud.draw(ctx) if(!state.ended) { + world.tick() world.enemiesAct() world.player.act() world.draw() @@ -108,6 +108,7 @@ docReady(function () { let player = Player.generatePlayer(new Vector(window.innerWidth /2, window.innerHeight / 2)); world = new World(player, ctx, new Vector(window.innerWidth, window.innerHeight)); + player.world = world; // not sure if this is great design state = new WorldState(); setInterval(() => { diff --git a/absurd-survivors/src/particles.ts b/absurd-survivors/src/particles.ts new file mode 100644 index 0000000..b74c9c0 --- /dev/null +++ b/absurd-survivors/src/particles.ts @@ -0,0 +1,69 @@ +import type {Particle} from "./interfaces.ts"; +import {Vector} from "./base.ts"; +import {World} from "./World.ts"; + +abstract class BaseParticle implements Particle { + protected _position: Vector; + protected world: World; + + + constructor(position: Vector, world: World) { + this._position = position.clone(); + this.world = world; + } + + getPosition(): Vector { + return this._position; + } + + getSize() { + } + + move(any?: any) { + this._position = this._position.add(new Vector(0, -0.5)) + } + + draw(ctx: CanvasRenderingContext2D) { + } + + act() { + this.move() + } + + tick(seconds: number, tick: number) { + } + +} + +export class HealingParticle extends BaseParticle { + private healthAmount: number; + private secondsToDisplay: number = 2; + private alreadyDisplayed: number = 0; + + constructor(position: Vector, world: World, healthAmount: number) { + super(position, world); + this.healthAmount = healthAmount; + } + + draw(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = 'green'; + ctx.fillText(this.healthAmount + '', this._position.x, this._position.y); + } + + static spawnHealingParticle(world: World, health: number, position: Vector) { + world.addParticle(this.createHealingParticle(world, health, position)) + } + + static createHealingParticle(world: World, health: number, position: Vector) { + let healingParticle = new HealingParticle(position, world, health) + return healingParticle + } + + + tick(seconds: number, tick: number) { + this.alreadyDisplayed += seconds; + if(this.alreadyDisplayed > this.secondsToDisplay) { + this.world.removeParticle(this) + } + } +} \ No newline at end of file diff --git a/absurd-survivors/src/stats.ts b/absurd-survivors/src/stats.ts index 68ba76b..a5b7aec 100644 --- a/absurd-survivors/src/stats.ts +++ b/absurd-survivors/src/stats.ts @@ -7,6 +7,7 @@ export class PlayerStats { private _pullRange: number; private _weaponRange: number; private _weaponRangeFactor: number; + private _healthRegen: number; constructor() { this._speed = 3; @@ -15,6 +16,7 @@ export class PlayerStats { this._pullRange = 150; this._weaponRange = 250; this._weaponRangeFactor = 1; + this._healthRegen = 0.001; } resetToBasic() { @@ -22,7 +24,8 @@ export class PlayerStats { this._health = 0; this._pullRange = 0; this._weaponRange = 0; - this._weaponRangeFactor = 1 + this._weaponRangeFactor = 1; + this._healthRegen = 0.1; } increaseLevel() { @@ -31,6 +34,7 @@ export class PlayerStats { this._pullRange *= 1.1; this._weaponRange *= 1.25 this._weaponRangeFactor += 0.1 + this._healthRegen += 0.1 } mergeStats(otherStats: PlayerStats) { @@ -39,6 +43,7 @@ export class PlayerStats { this._pullRange += otherStats._pullRange; this._weaponRange += otherStats._weaponRange this._weaponRangeFactor += otherStats._weaponRangeFactor; + this._healthRegen += otherStats._healthRegen; } clone() { @@ -91,6 +96,10 @@ export class PlayerStats { return this._weaponRange } + get healthRegen(): number { + return this._healthRegen; + } + get effectiveWeaponRange(): number { return this._weaponRange * this._weaponRangeFactor; }