From 1de5031d62f9160134c9a86aaa9912f78ebed37c Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 01:19:46 -0400 Subject: [PATCH 1/8] most of the logic for snowballs and potatos --- server/src/game/objects/player.ts | 57 +++++++++++++++++++++++++++ server/src/game/objects/projectile.ts | 12 ++++-- server/src/game/weaponManager.ts | 31 +++++++++------ shared/gameConfig.ts | 1 + 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/server/src/game/objects/player.ts b/server/src/game/objects/player.ts index c06b765f..74044e62 100644 --- a/server/src/game/objects/player.ts +++ b/server/src/game/objects/player.ts @@ -495,6 +495,9 @@ export class Player extends BaseGameObject { */ reloadAgain = false; + //if hit by snowball or potato, slowed down for "x" seconds + projectileSlowdownTicker = 0; + wearingPan = false; healEffect = false; frozen = false; @@ -998,6 +1001,13 @@ export class Player extends BaseGameObject { } } + // + // Projectile slowdown logic + // + if (this.projectileSlowdownTicker > 0) { + this.projectileSlowdownTicker -= dt; + } + // // Haste logic // @@ -2925,6 +2935,49 @@ export class Player extends BaseGameObject { }); } + // in original game, only called on snowball or potato collision + dropRandomLoot(): void { + // all possible droppable loot held by the player + //4 categories: inventory, weapons, armor, perks + const playerLootTypes: string[] = []; + + for (const [type, count] of Object.entries(this.inventory)) { + if (count == 0) continue; + if (type == "1xscope") continue; + playerLootTypes.push(type); + } + + for (let i = 0; i < this.weapons.length; i++) { + if (GameConfig.WeaponType[i] == "throwable") continue; + const weapon = this.weapons[i]; + if (!weapon.type) continue; + if (weapon.type == "fists") continue; + playerLootTypes.push(weapon.type); + } + + for (let i = 0; i < this.perks.length; i++) { + const perk = this.perks[i]; + if (!perk.droppable) continue; + playerLootTypes.push(perk.type); + } + + for (const armor of [this.helmet, this.chest] as const) { + if (!armor) continue; + if ((GameObjectDefs[armor] as ChestDef | HelmetDef).noDrop) continue; + playerLootTypes.push(armor); + } + + if (playerLootTypes.length == 0) return; + + const item = playerLootTypes[util.randomInt(0, playerLootTypes.length - 1)]; + const weapIdx = this.weapons.findIndex((w) => w.type == item); + + const dropMsg = new net.DropItemMsg(); + dropMsg.item = item; + dropMsg.weapIdx = weapIdx; + this.dropItem(dropMsg); + } + dropLoot(type: string, count = 1, useCountForAmmo?: boolean) { this.mobileDropTicker = 3; this.game.lootBarn.addLoot( @@ -3228,6 +3281,10 @@ export class Player extends BaseGameObject { this.speed += GameConfig.player.hasteSpeedBonus; } + if (this.projectileSlowdownTicker > 0) { + this.speed -= GameConfig.player.projectileHitPenalty; + } + // decrease speed if shooting or popping adren or heals // field_medic perk doesn't slow you down while you heal if ( diff --git a/server/src/game/objects/projectile.ts b/server/src/game/objects/projectile.ts index 4ea27331..1fc7391f 100644 --- a/server/src/game/objects/projectile.ts +++ b/server/src/game/objects/projectile.ts @@ -108,14 +108,16 @@ export class Projectile extends BaseGameObject { } update(dt: number) { + const def = GameObjectDefs[this.type] as ThrowableDef; // // Velocity // - this.vel = v2.mul(this.vel, 1 / (1 + dt * (this.posZ != 0 ? 1.2 : 2))); + if (!def.forceMaxThrowDistance) { + //velocity needs to stay constant to reach max throw dist + this.vel = v2.mul(this.vel, 1 / (1 + dt * (this.posZ != 0 ? 1.2 : 2))); + } this.pos = v2.add(this.pos, v2.mul(this.vel, dt)); - const def = GameObjectDefs[this.type] as ThrowableDef; - // // Height / posZ // @@ -181,6 +183,10 @@ export class Projectile extends BaseGameObject { obj.__id !== this.playerId ) { if (coldet.testCircleCircle(this.pos, this.rad, obj.pos, obj.rad)) { + if (this.type == "snowball" || this.type == "potato") { + obj.dropRandomLoot(); + obj.projectileSlowdownTicker = 0.5; + } this.explode(); } } diff --git a/server/src/game/weaponManager.ts b/server/src/game/weaponManager.ts index 06c0bc6e..5c24aaa5 100644 --- a/server/src/game/weaponManager.ts +++ b/server/src/game/weaponManager.ts @@ -929,15 +929,23 @@ export class WeaponManager { const throwableDef = GameObjectDefs[throwableType]; assert(throwableDef.type === "throwable"); - //if selected weapon slot is not throwable, that means player switched slots early and velocity needs to be 0 - const throwStr = - this.curWeapIdx == GameConfig.WeaponSlot.Throwable - ? math.clamp( - this.player.toMouseLen, - 0, - GameConfig.player.throwableMaxMouseDist * 1.8, - ) / 15 - : 0; + let multiplier: number; + if (throwableDef.forceMaxThrowDistance) { + multiplier = 1; + } else if (this.curWeapIdx != GameConfig.WeaponSlot.Throwable) { + //if selected weapon slot is not throwable, that means player switched slots early and velocity needs to be 0 + multiplier = 0; + } else { + //default throw strength algorithm, just based on mouse distance from player + multiplier = + math.clamp( + this.player.toMouseLen, + 0, + GameConfig.player.throwableMaxMouseDist * 1.8, + ) / 15; + } + + const throwStr = multiplier * throwableDef.throwPhysics.speed; const weapSlotId = GameConfig.WeaponSlot.Throwable; if (this.player.inventory[throwableType] > 0) { @@ -977,12 +985,11 @@ export class WeaponManager { dir = v2.normalizeSafe(v2.sub(aimTarget, pos), v2.create(1.0, 0.0)); } - const throwPhysicsSpeed = throwableDef.throwPhysics.speed; - // Incorporate some of the player motion into projectile velocity const vel = v2.add( v2.mul(this.player.moveVel, throwableDef.throwPhysics.playerVelMult), - v2.mul(dir, throwPhysicsSpeed * throwStr), + //player mouse position is irrelevant for max throwing distance + v2.mul(dir, throwStr), ); const fuseTime = math.max( diff --git a/shared/gameConfig.ts b/shared/gameConfig.ts index 6207dcc1..c14c0674 100644 --- a/shared/gameConfig.ts +++ b/shared/gameConfig.ts @@ -154,6 +154,7 @@ export const GameConfig = { moveSpeed: 12, waterSpeedPenalty: 3, cookSpeedPenalty: 3, + projectileHitPenalty: 3, hasteSpeedBonus: 4.8, bleedTickRate: 1, downedMoveSpeed: 4, From df28a02a8b267659ffb7c4e582fc38eca74001ed Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 01:38:57 -0400 Subject: [PATCH 2/8] fixed explosion logic from previous commit --- server/src/game/objects/explosion.ts | 24 +++++++++++++++++++----- server/src/game/objects/projectile.ts | 4 ---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/server/src/game/objects/explosion.ts b/server/src/game/objects/explosion.ts index b5d205b4..7d6aec4c 100644 --- a/server/src/game/objects/explosion.ts +++ b/server/src/game/objects/explosion.ts @@ -98,11 +98,25 @@ export class ExplosionBarn { obj.__type === ObjectType.Obstacle ) { let damage = def.damage; - if ( - obj.__type == ObjectType.Player && - obj.hasPerk("flak_jacket") - ) { - damage *= 0.1; + + if (obj.__type == ObjectType.Player) { + if (obj.hasPerk("flak_jacket")) { + damage *= 0.1; + } + + if ( + explosion.type == "explosion_snowball" || + explosion.type == "explosion_potato" + ) { + const isSourceTeammate = + explosion.source && + explosion.source.__type == ObjectType.Player && + explosion.source.teamId == obj.teamId; + if (!isSourceTeammate) { + obj.dropRandomLoot(); + obj.projectileSlowdownTicker = 0.5; + } + } } if (dist > def.rad.min) { diff --git a/server/src/game/objects/projectile.ts b/server/src/game/objects/projectile.ts index 1fc7391f..bc090bdc 100644 --- a/server/src/game/objects/projectile.ts +++ b/server/src/game/objects/projectile.ts @@ -183,10 +183,6 @@ export class Projectile extends BaseGameObject { obj.__id !== this.playerId ) { if (coldet.testCircleCircle(this.pos, this.rad, obj.pos, obj.rad)) { - if (this.type == "snowball" || this.type == "potato") { - obj.dropRandomLoot(); - obj.projectileSlowdownTicker = 0.5; - } this.explode(); } } From f088e5ca2066dbb2359cdea8087963df56dd0339 Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 01:45:01 -0400 Subject: [PATCH 3/8] add steelskin damage reduction --- server/src/game/objects/bullet.ts | 3 +++ server/src/game/objects/explosion.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/server/src/game/objects/bullet.ts b/server/src/game/objects/bullet.ts index 29d9578a..6191ee5a 100644 --- a/server/src/game/objects/bullet.ts +++ b/server/src/game/objects/bullet.ts @@ -541,6 +541,9 @@ export class Bullet { if (shouldFJReduction) { multiplier *= 0.1; } + if (col.player!.hasPerk("steelskin")) { + multiplier *= 0.5; + } this.bulletManager.damages.push({ obj: col.player!, diff --git a/server/src/game/objects/explosion.ts b/server/src/game/objects/explosion.ts index 7d6aec4c..6e980462 100644 --- a/server/src/game/objects/explosion.ts +++ b/server/src/game/objects/explosion.ts @@ -104,6 +104,10 @@ export class ExplosionBarn { damage *= 0.1; } + if (obj.hasPerk("steelskin")) { + damage *= 0.5; + } + if ( explosion.type == "explosion_snowball" || explosion.type == "explosion_potato" From 8a2359e8b40424e538dd8a368fb58f81fe566175 Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 14:18:18 -0400 Subject: [PATCH 4/8] fix steelskin damage calculation --- server/src/game/objects/bullet.ts | 3 --- server/src/game/objects/explosion.ts | 4 ---- server/src/game/objects/player.ts | 2 ++ 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/src/game/objects/bullet.ts b/server/src/game/objects/bullet.ts index 6191ee5a..29d9578a 100644 --- a/server/src/game/objects/bullet.ts +++ b/server/src/game/objects/bullet.ts @@ -541,9 +541,6 @@ export class Bullet { if (shouldFJReduction) { multiplier *= 0.1; } - if (col.player!.hasPerk("steelskin")) { - multiplier *= 0.5; - } this.bulletManager.damages.push({ obj: col.player!, diff --git a/server/src/game/objects/explosion.ts b/server/src/game/objects/explosion.ts index 6e980462..7d6aec4c 100644 --- a/server/src/game/objects/explosion.ts +++ b/server/src/game/objects/explosion.ts @@ -104,10 +104,6 @@ export class ExplosionBarn { damage *= 0.1; } - if (obj.hasPerk("steelskin")) { - damage *= 0.5; - } - if ( explosion.type == "explosion_snowball" || explosion.type == "explosion_potato" diff --git a/server/src/game/objects/player.ts b/server/src/game/objects/player.ts index 903898c9..5fb5e41b 100644 --- a/server/src/game/objects/player.ts +++ b/server/src/game/objects/player.ts @@ -1679,6 +1679,8 @@ export class Player extends BaseGameObject { ) { if (this.hasPerk("flak_jacket")) finalDamage *= 0.9; + if (this.hasPerk("steelskin")) finalDamage *= 0.5; + let isHeadShot = false; const gameSourceDef = GameObjectDefs[params.gameSourceType ?? ""]; From 90e277a3c936abc467808d4d544fd8a252842be6 Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 18:03:07 -0400 Subject: [PATCH 5/8] add "heavy" logic for snowballs and potatoes --- server/src/game/weaponManager.ts | 22 +++++++++++++++++----- shared/defs/gameObjects/throwableDefs.ts | 8 ++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/server/src/game/weaponManager.ts b/server/src/game/weaponManager.ts index 5a1fa521..15fc1618 100644 --- a/server/src/game/weaponManager.ts +++ b/server/src/game/weaponManager.ts @@ -933,8 +933,20 @@ export class WeaponManager { throwThrowable(): void { this.cookingThrowable = false; - const throwableType = this.weapons[GameConfig.WeaponSlot.Throwable].type; - const throwableDef = GameObjectDefs[throwableType]; + //need to store this incase throwableType gets replaced with its "heavy" variant like snowball => snowball_heavy + //used to manage inventory since snowball_heavy isnt stored in inventory, when it's thrown you decrement "snowball" from inv + const oldThrowableType = this.weapons[GameConfig.WeaponSlot.Throwable].type; + + let throwableType = this.weapons[GameConfig.WeaponSlot.Throwable].type; + let throwableDef = GameObjectDefs[throwableType]; + assert(throwableDef.type === "throwable"); + + if (throwableDef.heavyType && throwableDef.changeTime) { + if (this.cookTicker >= throwableDef.changeTime) { + throwableType = throwableDef.heavyType; + throwableDef = GameObjectDefs[throwableType] as ThrowableDef; + } + } assert(throwableDef.type === "throwable"); let multiplier: number; @@ -956,13 +968,13 @@ export class WeaponManager { const throwStr = multiplier * throwableDef.throwPhysics.speed; const weapSlotId = GameConfig.WeaponSlot.Throwable; - if (this.player.inventory[throwableType] > 0) { - this.player.inventory[throwableType] -= 1; + if (this.player.inventory[oldThrowableType] > 0) { + this.player.inventory[oldThrowableType] -= 1; // if throwable count drops below 0 // show the next throwable // if theres none switch to last weapon - if (this.player.inventory[throwableType] == 0) { + if (this.player.inventory[oldThrowableType] == 0) { this.showNextThrowable(); if (this.weapons[weapSlotId].type === "") { this.setCurWeapIndex(this.lastWeaponIdx); diff --git a/shared/defs/gameObjects/throwableDefs.ts b/shared/defs/gameObjects/throwableDefs.ts index 02d935db..de5e406d 100644 --- a/shared/defs/gameObjects/throwableDefs.ts +++ b/shared/defs/gameObjects/throwableDefs.ts @@ -50,6 +50,7 @@ export interface ThrowableDef { strikeDelay?: number; freezeOnImpact?: boolean; heavyType?: string; + changeTime?: number; //after changeTime has elapsed, throwable is changed to its "heavyType" variant forceMaxThrowDistance?: boolean; emoteId?: number; noPotatoSwap?: boolean; @@ -428,6 +429,8 @@ export const ThrowableDefs: Record = { type: "throwable", quality: 0, explosionType: "explosion_snowball", + heavyType: "snowball_heavy", + changeTime: 5, inventoryOrder: 0, cookable: true, noPotatoSwap: true, @@ -499,7 +502,7 @@ export const ThrowableDefs: Record = { forceMaxThrowDistance: true, explodeOnImpact: true, playerCollision: true, - fuseTime: 5, + fuseTime: 9999, aimDistance: 32, rad: 1.25, throwPhysics: { @@ -539,6 +542,7 @@ export const ThrowableDefs: Record = { explosionType: "explosion_potato", freezeOnImpact: true, heavyType: "potato_heavy", + changeTime: 5, inventoryOrder: 0, cookable: true, forceMaxThrowDistance: true, @@ -610,7 +614,7 @@ export const ThrowableDefs: Record = { forceMaxThrowDistance: true, explodeOnImpact: true, playerCollision: true, - fuseTime: 5, + fuseTime: 9999, aimDistance: 32, rad: 1.25, throwPhysics: { From f1dd66c9c2748a97ac290b6de1dfcb5ec1bbc1b8 Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 19:09:01 -0400 Subject: [PATCH 6/8] fixed frozen logic --- server/src/game/objects/explosion.ts | 15 ++++++++----- server/src/game/objects/player.ts | 26 +++++++++++++++++------ shared/defs/gameObjects/explosionsDefs.ts | 10 +++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/server/src/game/objects/explosion.ts b/server/src/game/objects/explosion.ts index 7d6aec4c..d6bf70e1 100644 --- a/server/src/game/objects/explosion.ts +++ b/server/src/game/objects/explosion.ts @@ -104,17 +104,22 @@ export class ExplosionBarn { damage *= 0.1; } - if ( - explosion.type == "explosion_snowball" || - explosion.type == "explosion_potato" - ) { + if (def.freezeAmount && def.freezeDuration) { const isSourceTeammate = explosion.source && explosion.source.__type == ObjectType.Player && explosion.source.teamId == obj.teamId; if (!isSourceTeammate) { obj.dropRandomLoot(); - obj.projectileSlowdownTicker = 0.5; + const colAngle = math.rad2degFromDirection( + -collision.dir.y, + collision.dir.x, + ); + + obj.freeze( + Math.floor(colAngle / 120), //TODO: make dividend dynamic based on num frozen sprites + def.freezeDuration, + ); } } } diff --git a/server/src/game/objects/player.ts b/server/src/game/objects/player.ts index 40ff43f6..5e777e22 100644 --- a/server/src/game/objects/player.ts +++ b/server/src/game/objects/player.ts @@ -495,11 +495,10 @@ export class Player extends BaseGameObject { */ reloadAgain = false; - //if hit by snowball or potato, slowed down for "x" seconds - projectileSlowdownTicker = 0; - wearingPan = false; healEffect = false; + //if hit by snowball or potato, slowed down for "x" seconds + frozenTicker = 0; frozen = false; frozenOri = 0; @@ -1015,8 +1014,14 @@ export class Player extends BaseGameObject { // // Projectile slowdown logic // - if (this.projectileSlowdownTicker > 0) { - this.projectileSlowdownTicker -= dt; + if (this.frozen) { + this.frozenTicker -= dt; + + if (this.frozenTicker <= 0) { + this.frozenTicker = 0; + this.frozen = false; + this.setDirty(); + } } // @@ -3192,6 +3197,13 @@ export class Player extends BaseGameObject { this.setDirty(); } + freeze(frozenOri: number, duration: number): void { + this.frozenTicker = duration; + this.frozen = true; + this.frozenOri = frozenOri; + this.setDirty(); + } + initLastBreath(): void { const affectedPlayers = this.game.contextManager.getNearbyAlivePlayersContext( this, @@ -3298,8 +3310,8 @@ export class Player extends BaseGameObject { this.speed += GameConfig.player.hasteSpeedBonus; } - if (this.projectileSlowdownTicker > 0) { - this.speed -= GameConfig.player.projectileHitPenalty; + if (this.frozen) { + this.speed -= GameConfig.player.frozenSpeedPenalty; } // decrease speed if shooting or popping adren or heals diff --git a/shared/defs/gameObjects/explosionsDefs.ts b/shared/defs/gameObjects/explosionsDefs.ts index 658b3cbe..fbc96481 100644 --- a/shared/defs/gameObjects/explosionsDefs.ts +++ b/shared/defs/gameObjects/explosionsDefs.ts @@ -11,6 +11,8 @@ export interface ExplosionDef { explosionEffectType: string; decalType: string; teamDamage?: boolean; + freezeAmount?: number; //unused atm in favor of gameconfig constant + freezeDuration?: number; //how long to slow down player on hit } export const ExplosionDefs: Record = { @@ -133,6 +135,8 @@ export const ExplosionDefs: Record = { shrapnelType: "", explosionEffectType: "snowball", decalType: "decal_snowball_explosion", + freezeAmount: 3, + freezeDuration: 0.5, }, explosion_snowball_heavy: { type: "explosion", @@ -143,6 +147,8 @@ export const ExplosionDefs: Record = { shrapnelType: "", explosionEffectType: "snowball_heavy", decalType: "decal_snowball_explosion", + freezeAmount: 3, + freezeDuration: 1, }, explosion_potato: { type: "explosion", @@ -154,6 +160,8 @@ export const ExplosionDefs: Record = { shrapnelType: "", explosionEffectType: "potato", decalType: "decal_potato_explosion", + freezeAmount: 3, + freezeDuration: 0.5, }, explosion_potato_heavy: { type: "explosion", @@ -165,6 +173,8 @@ export const ExplosionDefs: Record = { shrapnelType: "", explosionEffectType: "potato_heavy", decalType: "decal_potato_explosion", + freezeAmount: 3, + freezeDuration: 1, }, explosion_potato_cannonball: { type: "explosion", From e83455aa9690d1f43e53adac3e4d5e1d4ddac016 Mon Sep 17 00:00:00 2001 From: MamounKolovos Date: Tue, 3 Sep 2024 23:32:14 -0400 Subject: [PATCH 7/8] forgot to commit gameconfig --- shared/gameConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/gameConfig.ts b/shared/gameConfig.ts index c14c0674..943def50 100644 --- a/shared/gameConfig.ts +++ b/shared/gameConfig.ts @@ -154,7 +154,7 @@ export const GameConfig = { moveSpeed: 12, waterSpeedPenalty: 3, cookSpeedPenalty: 3, - projectileHitPenalty: 3, + frozenSpeedPenalty: 3, hasteSpeedBonus: 4.8, bleedTickRate: 1, downedMoveSpeed: 4, From d38d4a731dae578533742f674db342bb5e038b5b Mon Sep 17 00:00:00 2001 From: leia uwu Date: Thu, 26 Sep 2024 21:03:14 -0300 Subject: [PATCH 8/8] refactor: clean up and fix some explosion code --- server/src/game/objects/bullet.ts | 5 - server/src/game/objects/explosion.ts | 291 +++++++++++++------------- server/src/game/objects/player.ts | 12 +- server/src/game/objects/projectile.ts | 1 + 4 files changed, 152 insertions(+), 157 deletions(-) diff --git a/server/src/game/objects/bullet.ts b/server/src/game/objects/bullet.ts index 78563f86..6c8ed93f 100644 --- a/server/src/game/objects/bullet.ts +++ b/server/src/game/objects/bullet.ts @@ -532,16 +532,11 @@ export class Bullet { if (!shooterDead) { const isHighValueTarget = this.player?.hasPerk("targeting") && col.player!.perks.length; - const shouldFJReduction = - this.isShrapnel && col.player!.hasPerk("flak_jacket"); let multiplier = 1; if (isHighValueTarget) { multiplier *= 1.25; } - if (shouldFJReduction) { - multiplier *= 0.1; - } this.bulletManager.damages.push({ obj: col.player!, diff --git a/server/src/game/objects/explosion.ts b/server/src/game/objects/explosion.ts index 279be228..0754eb1c 100644 --- a/server/src/game/objects/explosion.ts +++ b/server/src/game/objects/explosion.ts @@ -17,170 +17,163 @@ export class ExplosionBarn { constructor(readonly game: Game) {} update() { - const game = this.game; for (let i = 0; i < this.explosions.length; i++) { - const explosion = this.explosions[i]; - const def = GameObjectDefs[explosion.type] as ExplosionDef; - - if (def.decalType) { - this.game.decalBarn.addDecal( - def.decalType, - explosion.pos, - explosion.layer, - 0, - 1, - ); - } + this.explode(this.explosions[i]); + } + this.explosions.length = 0; + } - if (explosion.type === "explosion_smoke") { - this.game.smokeBarn.addEmitter(explosion.pos, explosion.layer); - continue; - } + explode(explosion: Explosion) { + const def = GameObjectDefs[explosion.type] as ExplosionDef; + + if (def.decalType) { + this.game.decalBarn.addDecal( + def.decalType, + explosion.pos, + explosion.layer, + 0, + 1, + ); + } - const coll = collider.createCircle(explosion.pos, explosion.rad); - - // List of all near objects - const objects = game.grid.intersectCollider(coll); - const damagedObjects = new Map(); - - for (let angle = -Math.PI; angle < Math.PI; angle += 0.1) { - // All objects that collided with this line - const lineCollisions: Array<{ - obj: GameObject; - pos: Vec2; - distance: number; - dir: Vec2; - }> = []; - - const lineEnd = v2.add( - explosion.pos, - v2.rotate(v2.create(explosion.rad, 0), angle), - ); - - for (const obj of objects) { - if (!util.sameLayer(obj.layer, explosion.layer)) continue; - if ((obj as { dead?: boolean }).dead) continue; - if (obj.__type === ObjectType.Obstacle && obj.height <= 0.25) - continue; - if ( - obj.__type === ObjectType.Player || - obj.__type === ObjectType.Obstacle || - obj.__type === ObjectType.Loot - ) { - // check if the object hitbox collides with a line from the explosion center to the explosion max distance - const intersection = collider.intersectSegment( - obj.collider, - explosion.pos, - lineEnd, - ); - if (intersection) { - lineCollisions.push({ - pos: intersection.point, - obj, - distance: v2.distance(explosion.pos, intersection.point), - dir: v2.neg(v2.normalize(v2.sub(explosion.pos, obj.pos))), - }); - } + if (explosion.type === "explosion_smoke") { + this.game.smokeBarn.addEmitter(explosion.pos, explosion.layer); + return; + } + + const coll = collider.createCircle(explosion.pos, explosion.rad); + + // List of all near objects + const objects = this.game.grid.intersectCollider(coll); + const damagedObjects = new Map(); + + for (let angle = -Math.PI; angle < Math.PI; angle += 0.1) { + // All objects that collided with this line + const lineCollisions: Array<{ + obj: GameObject; + pos: Vec2; + distance: number; + dir: Vec2; + }> = []; + + const lineEnd = v2.add( + explosion.pos, + v2.rotate(v2.create(explosion.rad, 0), angle), + ); + + for (const obj of objects) { + if (!util.sameLayer(obj.layer, explosion.layer)) continue; + if ((obj as { dead?: boolean }).dead) continue; + if (obj.__type === ObjectType.Obstacle && obj.height <= 0.25) continue; + if ( + obj.__type === ObjectType.Player || + obj.__type === ObjectType.Obstacle || + obj.__type === ObjectType.Loot + ) { + // check if the object hitbox collides with a line from the explosion center to the explosion max distance + const intersection = collider.intersectSegment( + obj.collider, + explosion.pos, + lineEnd, + ); + if (intersection) { + lineCollisions.push({ + pos: intersection.point, + obj, + distance: v2.distance(explosion.pos, intersection.point), + dir: v2.neg(v2.normalize(v2.sub(explosion.pos, obj.pos))), + }); } } + } - // sort by closest to the explosion center to prevent damaging objects through walls - lineCollisions.sort((a, b) => a.distance - b.distance); - - for (const collision of lineCollisions) { - const obj = collision.obj; - - if (!damagedObjects.has(obj.__id)) { - damagedObjects.set(obj.__id, true); - const dist = collision.distance; - - if ( - obj.__type === ObjectType.Player || - obj.__type === ObjectType.Obstacle - ) { - let damage = def.damage; - - if (obj.__type == ObjectType.Player) { - if (obj.hasPerk("flak_jacket")) { - damage *= 0.1; - } - - if (def.freezeAmount && def.freezeDuration) { - const isSourceTeammate = - explosion.source && - explosion.source.__type == ObjectType.Player && - explosion.source.teamId == obj.teamId; - if (!isSourceTeammate) { - obj.dropRandomLoot(); - const colAngle = math.rad2degFromDirection( - -collision.dir.y, - collision.dir.x, - ); - - obj.freeze( - Math.floor(colAngle / 120), //TODO: make dividend dynamic based on num frozen sprites - def.freezeDuration, - ); - } - } - } - - if (dist > def.rad.min) { - damage = math.remap( - dist, - def.rad.min, - def.rad.max, - damage, - 0, - ); - } - - if (obj.__type === ObjectType.Obstacle) { - damage *= def.obstacleDamage; - } - - obj.damage({ - amount: damage, - gameSourceType: explosion.gameSourceType, - mapSourceType: explosion.mapSourceType, - source: explosion.source, - damageType: explosion.damageType, - dir: collision.dir, - }); - } + // sort by closest to the explosion center to prevent damaging objects through walls + lineCollisions.sort((a, b) => a.distance - b.distance); + + for (const collision of lineCollisions) { + const obj = collision.obj; + + if (damagedObjects.has(obj.__id)) continue; + damagedObjects.set(obj.__id, true); + const dist = collision.distance; + + if (obj.__type === ObjectType.Loot) { + obj.push( + v2.normalize(v2.sub(obj.pos, explosion.pos)), + (def.rad.max - dist) * EXPLOSION_LOOT_PUSH_FORCE, + ); + continue; + } - if (obj.__type === ObjectType.Loot) { - obj.push( - v2.normalize(v2.sub(obj.pos, explosion.pos)), - (def.rad.max - dist) * EXPLOSION_LOOT_PUSH_FORCE, - ); + if ( + obj.__type !== ObjectType.Player && + obj.__type !== ObjectType.Obstacle + ) { + continue; + } + + let damage = def.damage; + + if (dist > def.rad.min) { + damage = math.remap(dist, def.rad.min, def.rad.max, damage, 0); + } + + if (obj.__type == ObjectType.Player) { + if (def.freezeAmount && def.freezeDuration) { + const isSourceTeammate = + explosion.source && + explosion.source.__type == ObjectType.Player && + explosion.source.teamId == obj.teamId; + if (!isSourceTeammate) { + obj.dropRandomLoot(); + + const playerRot = Math.atan2(obj.dir.y, obj.dir.x); + const collRot = -Math.atan2(collision.dir.y, collision.dir.x); + + const ori = + (math.radToOri(playerRot) + math.radToOri(collRot) + 2) % + 4; + + obj.freeze(ori, def.freezeDuration); } } + } - if (obj.__type === ObjectType.Obstacle && obj.collidable) break; + if (obj.__type === ObjectType.Obstacle) { + damage *= def.obstacleDamage; } + + obj.damage({ + amount: damage, + gameSourceType: explosion.gameSourceType, + mapSourceType: explosion.mapSourceType, + source: explosion.source, + damageType: explosion.damageType, + dir: collision.dir, + }); + + if (obj.__type === ObjectType.Obstacle && obj.collidable) break; } + } - const bulletDef = GameObjectDefs[def.shrapnelType]; - if (bulletDef && bulletDef.type === "bullet") { - for (let i = 0, count = def.shrapnelCount ?? 0; i < count; i++) { - game.bulletBarn.fireBullet({ - bulletType: def.shrapnelType, - pos: explosion.pos, - layer: explosion.layer, - damageType: explosion.damageType, - playerId: explosion.source?.__id ?? 0, - shotFx: false, - damageMult: 1, - varianceT: Math.random(), - gameSourceType: explosion.gameSourceType, - mapSourceType: explosion.mapSourceType, - dir: v2.randomUnit(), - }); - } + const bulletDef = GameObjectDefs[def.shrapnelType]; + if (bulletDef && bulletDef.type === "bullet") { + for (let i = 0, count = def.shrapnelCount ?? 0; i < count; i++) { + this.game.bulletBarn.fireBullet({ + bulletType: def.shrapnelType, + pos: explosion.pos, + layer: explosion.layer, + damageType: explosion.damageType, + playerId: explosion.source?.__id ?? 0, + shotFx: false, + damageMult: 1, + varianceT: Math.random(), + gameSourceType: explosion.gameSourceType, + mapSourceType: explosion.mapSourceType, + dir: v2.randomUnit(), + }); } } - this.explosions.length = 0; } flush() { diff --git a/server/src/game/objects/player.ts b/server/src/game/objects/player.ts index e7563790..9e2c4c4b 100644 --- a/server/src/game/objects/player.ts +++ b/server/src/game/objects/player.ts @@ -1734,14 +1734,20 @@ export class Player extends BaseGameObject { params.damageType !== GameConfig.DamageType.Bleeding && params.damageType !== GameConfig.DamageType.Airdrop ) { - if (this.hasPerk("flak_jacket")) finalDamage *= 0.9; + const gameSourceDef = GameObjectDefs[params.gameSourceType ?? ""]; + + if (this.hasPerk("flak_jacket")) { + const isExplosion = + gameSourceDef && + (gameSourceDef.type === "explosion" || + (gameSourceDef.type === "bullet" && gameSourceDef.shrapnel)); + finalDamage *= isExplosion ? 0.1 : 0.9; + } if (this.hasPerk("steelskin")) finalDamage *= 0.5; let isHeadShot = false; - const gameSourceDef = GameObjectDefs[params.gameSourceType ?? ""]; - if (gameSourceDef && "headshotMult" in gameSourceDef) { isHeadShot = gameSourceDef.headshotMult > 1 && Math.random() < 0.15; diff --git a/server/src/game/objects/projectile.ts b/server/src/game/objects/projectile.ts index ec2645f8..6b0a29cf 100644 --- a/server/src/game/objects/projectile.ts +++ b/server/src/game/objects/projectile.ts @@ -181,6 +181,7 @@ export class Projectile extends BaseGameObject { } else if ( obj.__type === ObjectType.Player && def.playerCollision && + !obj.dead && obj.__id !== this.playerId ) { if (coldet.testCircleCircle(this.pos, this.rad, obj.pos, obj.rad)) {