survivors: madding death split mechanic to projectiles

adding generic method for coordinate splitting around points
refactored projectile stats and weapon stats
This commit is contained in:
Sheldan
2025-08-22 15:29:23 +02:00
parent 8ecfbf499f
commit e714fc35f6
6 changed files with 154 additions and 37 deletions

View File

@@ -123,7 +123,11 @@ export class ShootingEnemy extends BasicEnemy implements Shooting {
}
createProjectile() {
let stats = new ProjectileStats(0, 1, 5, 2)
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

View File

@@ -1,6 +1,6 @@
import type {Acting, Drawable, Healthy, Weapon} from "./interfaces.ts";
import {Vector} from "./base.ts";
import {drawDot} from "./utils.ts";
import {drawDot, getCoordinatesSplit} from "./utils.ts";
export class Player implements Drawable, Acting, Healthy {
private _position: Vector;
@@ -37,17 +37,13 @@ export class Player implements Drawable, Acting, Healthy {
addWeapon(weapon: Weapon) {
let weaponCount = this._weapons.length + 1;
let angle = 2 * Math.PI / weaponCount;
for (let i = 0; i < this._weapons.length; i++) {
let points = getCoordinatesSplit(weaponCount)
for (let i = 0; i < points.length - 1; i++){
const value = points[i];
let affectedWeapon = this._weapons[i];
let x = Math.cos(angle * i)
let y = Math.sin(angle * i)
console.log(x + ' ' + y)
affectedWeapon.setOffset(new Vector(x, y).multiply(affectedWeapon.getSize()))
affectedWeapon.setOffset(value.multiply(affectedWeapon.getSize()))
}
let newPosition = new Vector(Math.cos(angle * (weaponCount - 1)), Math.sin(angle * (weaponCount - 1)));
newPosition = newPosition.multiply(weapon.getSize())
console.log(newPosition.x + ' ' + newPosition.y)
weapon.setOffset(newPosition)
weapon.setOffset(points[points.length - 1].multiply(weapon.getSize()))
this._weapons.push(weapon)
}

View File

@@ -60,6 +60,9 @@ export class World {
this._enemies = this._enemies.filter(item => item !== enemy)
}
maxValue() {
return Math.max(this.size.x, this.size.y)
}
get enemies(): [Enemy] {
return this._enemies;

View File

@@ -2,7 +2,15 @@ import type {Acting, Placeable, Healthy} from "./interfaces.ts";
import type {Vector} from "./base.ts";
import {World} from "./World.ts";
import {Cooldown, Point, Vector} from "./base.ts";
import {circleLineCollision, drawDot, moveInDirectionOf, pointOnLineWithinLine, straightMove, toRad} from "./utils.ts";
import {
circleLineCollision,
drawDot,
getCoordinatesSplit,
moveInDirectionOf,
pointOnLineWithinLine,
straightMove,
toRad
} from "./utils.ts";
import {InstanceOfUtils} from "./instance.ts";
export abstract class Projectile implements Acting, Placeable {
@@ -27,6 +35,10 @@ export abstract class Projectile implements Acting, Placeable {
this.status = new ProjectileStatus(stats.piercings)
}
die() {
this.world.removeProjectile(this)
}
act() {
this.move()
if(this.parent !== this.world.player) {
@@ -45,11 +57,11 @@ export abstract class Projectile implements Acting, Placeable {
let healthy = target as Healthy;
healthy.takeDamage(this.stats.damage)
if(!this.status.hasPiercingLeft()) {
this.world.removeProjectile(this)
this.die()
}
this.status.decreasePiercings()
} else {
this.world.removeProjectile(this)
this.die()
}
}
this.lastColliding = target;
@@ -66,15 +78,13 @@ export abstract class Projectile implements Acting, Placeable {
checkWorldBorder() {
if(this.world.outside(this.position)) {
this.world.removeProjectile(this)
this.die()
}
}
impactPlayer() {
this.world.player.takeDamage(this.stats.damage)
if(!this.status.hasPiercingLeft()) {
this.world.removeProjectile(this)
}
this.die()
};
draw(ctx: CanvasRenderingContext2D) {
@@ -152,7 +162,7 @@ export class HomingProjectile extends Projectile {
if(pointOnLineWithinLine(this.target.getPosition(), this.lastPosition, this.position)) {
justMovedDirection = olderMovedDirection
}
this.target = new Point(this.position.add(justMovedDirection.multiply(Math.max(this.world.size.x, this.world.size.y))))
this.target = new Point(this.position.add(justMovedDirection.multiply(this.world.maxValue())))
}
} else {
if(pointOnLineWithinLine(this.target.getPosition(), this.lastPosition, this.position)) {
@@ -173,6 +183,22 @@ export class HomingProjectile extends Projectile {
return projectile;
}
die() {
if(this.stats.deathSplit && this.stats.deathSplit > 0 && Math.random() > this.stats.deathSplitChance) {
let splits = this.stats.deathSplit;
let directionalVectors = getCoordinatesSplit(splits);
let stats = new ProjectileStats()
.withSize(this.stats.size / 2)
.withDamage(this.stats.damage / 2)
.withSpeed(this.stats.speed / 2)
directionalVectors.forEach(value => {
let target = this.position.add(value.multiply(this.world.maxValue()))
StraightProjectile.createStraightProjectile(this.world, this.position, target, this.parent, stats, 'white')
})
}
super.die();
}
}
export class ProjectileStatus {
@@ -204,16 +230,46 @@ export class ProjectileStatus {
}
export class ProjectileStats {
private _piercings: number;
private _size: number;
private _damage: number;
private _speed: number;
private _deathSplit: number;
private _deathSplitChance: number;
constructor(piercings: number, size: number, damage: number, _speed: number) {
this._piercings = piercings;
this._size = size;
this._damage = damage
this._speed = _speed;
constructor() {
this._size = 1
}
withPiercings(value: number) {
this._piercings = value;
return this;
}
withSize(value: number) {
this._size = Math.max(value, 1);
return this;
}
withDamage(value: number) {
this._damage = value;
return this;
}
withSpeed(value: number) {
this._speed = value;
return this;
}
withDeathSplit(value: number) {
this._deathSplit = value;
return this;
}
withDeathSplitChance(value: number) {
this._deathSplitChance = value;
return this;
}
get piercings(): number {
@@ -232,4 +288,13 @@ export class ProjectileStats {
get damage(): number {
return this._damage;
}
get deathSplitChance(): number {
return this._deathSplitChance;
}
get deathSplit(): number {
return this._deathSplit;
}
}

View File

@@ -66,4 +66,15 @@ export function circleLineCollision(circleCenter: Vector, radius: number, lineSt
}
return pointInsideCircle(circleCenter, radius, closestPoint);
}
export function getCoordinatesSplit(amount: number) {
let angle = 2 * Math.PI / amount;
let points: Vector[] = [];
for (let i = 0; i < amount; i++) {
let x = Math.cos(angle * i)
let y = Math.sin(angle * i)
points.push(new Vector(x, y))
}
return points;
}

View File

@@ -77,7 +77,11 @@ export class HomingPistol extends RangeWeapon {
let range = this.calculateRange()
let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position, range);
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined) {
let stats = new ProjectileStats(this.stats.projectilePiercings, 1, this.stats.damage, this.stats.projectileSpeed)
let stats = new ProjectileStats()
.withPiercings(this.stats.projectilePiercings)
.withSize(1)
.withDamage(this.stats.damage)
.withSpeed(this.stats.projectileSpeed);
let projectile = HomingProjectile.createHomingProjectile(this.world, this.getPosition(), this.player, closestTargetTo[1]!, stats, 'yellow')
this.projectiles.push(projectile)
return true
@@ -90,7 +94,9 @@ export class HomingPistol extends RangeWeapon {
if(!offset) {
offset = new Vector(5, 5)
}
let stats = new WeaponStats(0, 1, 3, 5, 5)
let stats = new WeaponStats()
.withProjectileSpeed(3)
.withDamage(5)
let pistol = new HomingPistol(world, stats)
pistol.offset = offset;
pistol.size = 5;
@@ -110,7 +116,11 @@ export class Pistol extends RangeWeapon {
let range = this.calculateRange()
let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position, range);
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined) {
let stats = new ProjectileStats(this.stats.projectilePiercings, 1, this.stats.damage, this.stats.projectileSpeed)
let stats = new ProjectileStats()
.withPiercings(this.stats.projectilePiercings)
.withSize(1)
.withDamage(this.stats.damage)
.withSpeed(this.stats.projectileSpeed);
let projectile = StraightProjectile.createStraightProjectile(this.world, this.getPosition(), closestTargetTo[1]!.getPosition(), this.player, stats, 'pink')
this.projectiles.push(projectile)
return true
@@ -123,11 +133,9 @@ export class Pistol extends RangeWeapon {
if(!offset) {
offset = new Vector(5, 5)
}
let stats = new WeaponStats(0,
1,
5,
2,
5)
let stats = new WeaponStats()
.withProjectileSpeed(5)
.withDamage(5)
let pistol = new Pistol(world, stats)
pistol.offset = offset;
pistol.size = 5;
@@ -139,13 +147,43 @@ export class Pistol extends RangeWeapon {
export class WeaponStats {
constructor(private _weaponRange: number,
private _weaponRangeFactor: number,
private _projectileSpeed: number,
private _projectilePiercings: number,
private _damage: number) {
private _weaponRange: number;
private _weaponRangeFactor: number;
private _projectileSpeed: number;
private _projectilePiercings: number;
private _damage: number;
constructor() {
this._weaponRangeFactor = 1
}
withWeaponRange(value: number) {
this._weaponRange = value;
return this;
}
withWeaponRangeFactor(value: number) {
this._weaponRangeFactor = value;
return this;
}
withProjectileSpeed(value: number) {
this._projectileSpeed = value;
return this;
}
withProjectilePiercings(value: number) {
this._projectilePiercings = value;
return this;
}
withDamage(value: number) {
this._damage = value;
return this;
}
get weaponRange(): number {
return this._weaponRange;