survivors: adding health regen and simple particle system

This commit is contained in:
Sheldan
2025-09-14 18:09:12 +02:00
parent db2110c921
commit 9bb7ec99c0
7 changed files with 145 additions and 5 deletions

View File

@@ -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)
}
}
}
}

View File

@@ -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<Enemy> = new ObjectContainer<Enemy>()
private _projectiles: ObjectContainer<Projectile> = new ObjectContainer<Projectile>();
private _drops: ObjectContainer<Drop> = new ObjectContainer<Drop>();
private _particles: ObjectContainer<Particle> = 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
}

View File

@@ -42,6 +42,9 @@ export abstract class BasicDrop implements Drop {
return this.size
}
tick(seconds: number, tick: number) {
}
}
export class MoneyDrop extends BasicDrop {

View File

@@ -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();

View File

@@ -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(() => {

View File

@@ -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)
}
}
}

View File

@@ -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;
}