diff --git a/src/Client.ts b/src/Client.ts index 1ab9bfd7..d8b6a458 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -358,7 +358,7 @@ export default class Client { if (camera.cameraData.values.statLevels.values[statId] >= statLimit) return; - camera.cameraData.statLevels[statId] += 1; + camera.addStat(statId, 1); camera.cameraData.statsAvailable -= 1; return; diff --git a/src/Const/Commands.ts b/src/Const/Commands.ts index f4a780bd..586f397b 100644 --- a/src/Const/Commands.ts +++ b/src/Const/Commands.ts @@ -215,10 +215,11 @@ export const commandCallbacks = { }, game_set_score: (client: Client, scoreArg: string) => { const score = parseInt(scoreArg); - const camera = client.camera?.cameraData; - const player = client.camera?.cameraData.player; + const camera = client.camera; + const cameraData = camera?.cameraData; + const player = cameraData?.player; if (!isFinite(score) || score > Number.MAX_SAFE_INTEGER || score < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return; - camera.score = score; + camera.setScore(score); }, game_set_stat_max: (client: Client, statIdArg: string, statMaxArg: string) => { const statId = StatCount - parseInt(statIdArg); @@ -233,17 +234,19 @@ export const commandCallbacks = { game_set_stat: (client: Client, statIdArg: string, statPointsArg: string) => { const statId = StatCount - parseInt(statIdArg); const statPoints = parseInt(statPointsArg); - const camera = client.camera?.cameraData; - const player = client.camera?.cameraData.player; + const camera = client.camera; + const cameraData = camera?.cameraData; + const player = camera?.cameraData.player; if (statId < 0 || statId >= StatCount || !isFinite(statId) || !isFinite(statPoints) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return; - camera.statLevels[statId as Stat] = statPoints; + camera.setStat(statId as Stat, statPoints); }, game_add_upgrade_points: (client: Client, pointsArg: string) => { const points = parseInt(pointsArg); - const camera = client.camera?.cameraData; - const player = client.camera?.cameraData.player; - if (!isFinite(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return; - camera.statsAvailable += points; + const camera = client.camera; + const cameraData = camera?.cameraData; + const player = cameraData?.player; + if (!isFinite(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera || !cameraData) return; + cameraData.statsAvailable += points; }, game_teleport: (client: Client, xArg: string, yArg: string) => { const player = client.camera?.cameraData.player; diff --git a/src/Const/TankDefinitions.json b/src/Const/TankDefinitions.json index 45b18bc2..5350f193 100644 --- a/src/Const/TankDefinitions.json +++ b/src/Const/TankDefinitions.json @@ -6289,6 +6289,7 @@ "absorbtionFactor": 1, "speed": 1, "maxHealth": 50, + "bodyDamage": 2, "preAddon": null, "postAddon": "spike", "sides": 1, diff --git a/src/Const/TankDefinitions.ts b/src/Const/TankDefinitions.ts index 617182d4..884536d7 100644 --- a/src/Const/TankDefinitions.ts +++ b/src/Const/TankDefinitions.ts @@ -151,6 +151,8 @@ export interface TankDefinition { absorbtionFactor: number; /** The base max health of the tank. */ maxHealth: number; + /** Extra body damage addition, such as spike. */ + bodyDamage?: number; /** The addon, if not empty, which is built before the barrels. */ preAddon: addonId | null; /** The addon, if not empty, which is built after the barrels. */ diff --git a/src/Entity/Boss/Defender.ts b/src/Entity/Boss/Defender.ts index 5512c0da..ad0109f9 100644 --- a/src/Entity/Boss/Defender.ts +++ b/src/Entity/Boss/Defender.ts @@ -78,8 +78,6 @@ const DEFENDER_SIZE = 150; * Class which represents the boss "Defender" */ export default class Defender extends AbstractBoss { - /** Defender's trap launchers */ - private trappers: Barrel[] = []; /** See AbstractBoss.movementSpeed */ public movementSpeed = 0.2; @@ -99,7 +97,7 @@ export default class Defender extends AbstractBoss { const offset = 60 / (DEFENDER_SIZE * Math.SQRT1_2); for (let i = 0; i < count; ++i) { // Add trap launcher - this.trappers.push(new Barrel(this, { + this.barrels.push(new Barrel(this, { ...TrapperDefinition, angle: PI2 * ((i / count) + 1 / (count * 2)) })); diff --git a/src/Entity/Boss/Summoner.ts b/src/Entity/Boss/Summoner.ts index 56332f64..89d282dd 100644 --- a/src/Entity/Boss/Summoner.ts +++ b/src/Entity/Boss/Summoner.ts @@ -58,10 +58,6 @@ const SUMMONER_SIZE = 150; * Class which represents the boss "Summoner" */ export default class Summoner extends AbstractBoss { - - /** Summoner spawners */ - private spawners: Barrel[] = []; - public constructor(game: GameServer) { super(game); @@ -71,8 +67,9 @@ export default class Summoner extends AbstractBoss { this.physicsData.values.size = SUMMONER_SIZE * Math.SQRT1_2; this.physicsData.values.sides = 4; - for (let i = 0; i < 4; ++i) { - this.spawners.push(new Barrel(this, { + const count = this.physicsData.values.sides; + for (let i = 0; i < count; ++i) { + this.barrels.push(new Barrel(this, { ...SummonerSpawnerDefinition, angle: PI2 * ((i / 4)) })); diff --git a/src/Entity/Misc/ArenaCloser.ts b/src/Entity/Misc/ArenaCloser.ts index a30fbc03..c52edb42 100644 --- a/src/Entity/Misc/ArenaCloser.ts +++ b/src/Entity/Misc/ArenaCloser.ts @@ -53,29 +53,21 @@ export default class ArenaCloser extends TankBody { this.setTank(Tank.ArenaCloser); - const def = (this.definition = Object.assign({}, this.definition)); - // 598 is what the normal health increase for stat/level would be, so we just subtract it. - def.maxHealth = 10000 - 598; - // TODO(ABC): - // Fix all the stats - def.speed = this.ai.movementSpeed = this.cameraEntity.cameraData.values.movementSpeed = 80; - - Object.defineProperty(this, "damagePerTick", { - get() { - return 45; - }, - set() {} - }); - this.nameData.values.name = "Arena Closer"; this.styleData.values.color = Color.Neutral; this.positionData.values.flags |= PositionFlags.canMoveThroughWalls; this.physicsData.values.flags |= PhysicsFlags.canEscapeArena; - for (let i = Stat.MovementSpeed; i < Stat.BodyDamage; ++i) camera.cameraData.values.statLevels.values[i] = 7; + for (let i = Stat.MovementSpeed; i < Stat.BodyDamage; ++i) camera.setStat(i as Stat, 7); this.ai.aimSpeed = this.barrels[0].bulletAccel * 1.6; this.setInvulnerability(true); + + // TODO(ABC): + // Fix all the stats + this.ai.movementSpeed = this.cameraEntity.cameraData.values.movementSpeed = 5; + this.healthData.values.health = this.healthData.values.maxHealth = 10000; + this.damagePerTick = 45; } public tick(tick: number) { @@ -91,6 +83,5 @@ export default class ArenaCloser extends TankBody { } super.tick(tick); - this.ai.movementSpeed = this.cameraEntity.cameraData.movementSpeed = 80; } } diff --git a/src/Entity/Misc/Dominator.ts b/src/Entity/Misc/Dominator.ts index 6e164a93..9a1da95e 100644 --- a/src/Entity/Misc/Dominator.ts +++ b/src/Entity/Misc/Dominator.ts @@ -92,12 +92,7 @@ export default class Dominator extends TankBody { this.base = base; - Object.defineProperty(this, "damagePerTick", { - get() { - return 10; - }, - set() {} - }); + this.damagePerTick = 10; if (this.styleData.values.flags & StyleFlags.isFlashing) { // Remove spawn shield this.styleData.values.flags ^= StyleFlags.isFlashing; diff --git a/src/Entity/Misc/Mothership.ts b/src/Entity/Misc/Mothership.ts index ecac3940..09ac8c0d 100644 --- a/src/Entity/Misc/Mothership.ts +++ b/src/Entity/Misc/Mothership.ts @@ -63,12 +63,10 @@ export default class Mothership extends TankBody { camera.cameraData.values.player = this; - for (let i = Stat.MovementSpeed; i < Stat.HealthRegen; ++i) camera.cameraData.values.statLevels.values[i] = 7; - camera.cameraData.values.statLevels.values[Stat.HealthRegen] = 1; - - const def = (this.definition = Object.assign({}, this.definition)); - // 418 is what the normal health increase for stat/level would be, so we just subtract it and force it 7k - def.maxHealth = 7000 - 418; + for (let i = Stat.MovementSpeed; i < Stat.HealthRegen; ++i) camera.setStat(i as Stat, 7); + camera.setStat(Stat.HealthRegen, 1); + + this.healthData.values.health = this.healthData.values.maxHealth = 7000; } public onDeath(killer: Live): void { @@ -129,6 +127,7 @@ export default class Mothership extends TankBody { } } } + const team = this.relationsData.values.team; if (team?.teamData) { team.teamData.mothershipX = this.positionData.values.x; diff --git a/src/Entity/Tank/Barrel.ts b/src/Entity/Tank/Barrel.ts index 630576f7..62ee18fa 100644 --- a/src/Entity/Tank/Barrel.ts +++ b/src/Entity/Tank/Barrel.ts @@ -37,16 +37,17 @@ import CrocSkimmer from "./Projectile/CrocSkimmer"; import { BarrelAddon, BarrelAddonById } from "./BarrelAddons"; import { Swarm } from "./Projectile/Swarm"; import NecromancerSquare from "./Projectile/NecromancerSquare"; + /** * Class that determines when barrels can shoot, and when they can't. */ export class ShootCycle { /** The barrel this cycle is keeping track of. */ - private barrelEntity: Barrel; + public barrelEntity: Barrel; /** The current position in the cycle. */ - private pos: number; + public pos: number; /** The last known reload time of the barrel. */ - private reloadTime: number; + public reloadTime: number; public constructor(barrel: Barrel) { this.barrelEntity = barrel; @@ -56,11 +57,6 @@ export class ShootCycle { public tick() { const reloadTime = this.barrelEntity.tank.reloadTime * this.barrelEntity.definition.reload; - if (reloadTime !== this.reloadTime) { - this.pos *= reloadTime / this.reloadTime; - this.reloadTime = this.barrelEntity.barrelData.reloadTime = reloadTime; - } - const alwaysShoot = (this.barrelEntity.definition.forceFire) || (this.barrelEntity.definition.bullet.type === "drone") || (this.barrelEntity.definition.bullet.type === "minion"); if (this.pos >= reloadTime) { @@ -143,7 +139,7 @@ export default class Barrel extends ObjectEntity { this.barrelData.values.trapezoidDirection = barrelDefinition.trapezoidDirection; this.shootCycle = new ShootCycle(this); - this.bulletAccel = (20 + (owner.cameraEntity.cameraData?.values.statLevels.values[Stat.BulletSpeed] || 0) * 3) * barrelDefinition.bullet.speed; + this.calculateStatData(); } /** Shoots a bullet from the barrel. */ @@ -214,9 +210,19 @@ export default class Barrel extends ObjectEntity { if (this.definition.bullet.color) projectile.styleData.values.color = this.definition.bullet.color; } } + + public calculateStatData() { + const reloadTime = this.tank.reloadTime * this.definition.reload; + + if (reloadTime !== this.shootCycle.reloadTime) { + this.shootCycle.pos *= reloadTime / this.shootCycle.reloadTime; + this.shootCycle.reloadTime = this.barrelData.reloadTime = reloadTime; + } + + this.bulletAccel = (20 + (this.tank.cameraEntity.cameraData?.values.statLevels.values[Stat.BulletSpeed] || 0) * 3) * this.definition.bullet.speed; + } public tick(tick: number) { - this.bulletAccel = (20 + (this.tank.cameraEntity.cameraData?.values.statLevels.values[Stat.BulletSpeed] || 0) * 3) * this.definition.bullet.speed; this.relationsData.values.team = this.tank.relationsData.values.team; if (!this.tank.rootParent.deletionAnimation){ diff --git a/src/Entity/Tank/TankBody.ts b/src/Entity/Tank/TankBody.ts index a963c1bd..bdf2e4bf 100644 --- a/src/Entity/Tank/TankBody.ts +++ b/src/Entity/Tank/TankBody.ts @@ -121,6 +121,7 @@ export default class TankBody extends LivingEntity implements BarrelBase { this.children[i].isChild = false; this.children[i].delete(); } + this.children = []; this.barrels = []; this.addons = []; @@ -134,10 +135,10 @@ export default class TankBody extends LivingEntity implements BarrelBase { if (!Entity.exists(camera)) throw new Error("No camera"); this.physicsData.sides = tank.sides; - this.styleData.opacity = 1; + this.styleData.opacity = 1.0; for (let i: Stat = 0; i < StatCount; ++i) { - const {name, max} = tank.stats[i]; + const { name, max } = tank.stats[i]; camera.cameraData.statLimits[i] = max; camera.cameraData.statNames[i] = name; @@ -150,6 +151,7 @@ export default class TankBody extends LivingEntity implements BarrelBase { this.baseSize = tank.baseSizeOverride ?? tank.sides === 4 ? Math.SQRT2 * 32.5 : tank.sides === 16 ? Math.SQRT2 * 25 : 50; this.physicsData.size = this.baseSize * this.scaleFactor; this.physicsData.absorbtionFactor = this.isInvulnerable ? 0 : tank.absorbtionFactor; + if (tank.absorbtionFactor === 0) this.positionData.flags |= PositionFlags.canMoveThroughWalls; else if (this.positionData.flags & PositionFlags.canMoveThroughWalls) this.positionData.flags ^= PositionFlags.canMoveThroughWalls; @@ -181,10 +183,11 @@ export default class TankBody extends LivingEntity implements BarrelBase { camera.setFieldFactor(tank.fieldFactor); this.scale(1); // Update addons and etc + this.calculateStatData(); // Re-calculate everything once this is done } /** See LivingEntity.onKill */ public onKill(entity: LivingEntity) { - if (Entity.exists(this.cameraEntity.cameraData.values.player) && entity !== this) this.scoreData.score = this.cameraEntity.cameraData.score += entity.scoreReward; + if (Entity.exists(this.cameraEntity.cameraData.values.player) && entity !== this) this.cameraEntity.addScore(entity.scoreReward); if ((entity.nameData && !(entity.nameData.values.flags & NameFlags.hiddenName))) { const client = this.cameraEntity.getClient(); @@ -244,6 +247,31 @@ export default class TankBody extends LivingEntity implements BarrelBase { super.receiveDamage(source, amount); } + + public calculateStatData() { + // Body damage + this.damagePerTick = this.cameraEntity.cameraData.statLevels[Stat.BodyDamage] + 5 + (this.definition.bodyDamage ?? 0); + + // Max health + const maxHealthCache = this.healthData.values.maxHealth; + this.healthData.maxHealth = this.definition.maxHealth + 2 * (this.cameraEntity.cameraData.values.level - 1) + this.cameraEntity.cameraData.values.statLevels.values[Stat.MaxHealth] * 20; + if (this.healthData.values.health === maxHealthCache) this.healthData.health = this.healthData.maxHealth; // just in case + else if (this.healthData.values.maxHealth !== maxHealthCache) { + this.healthData.health *= this.healthData.values.maxHealth / maxHealthCache + } + + // Regen + this.regenPerTick = (this.healthData.values.maxHealth * 4 * this.cameraEntity.cameraData.values.statLevels.values[Stat.HealthRegen] + this.healthData.values.maxHealth) / 25000; + + // Reload + this.reloadTime = 15 * Math.pow(0.914, this.cameraEntity.cameraData.values.statLevels.values[Stat.Reload]); + + // Movement speed + this.cameraEntity.cameraData.movementSpeed = + this.definition.speed * 2.55 * Math.pow(1.07, this.cameraEntity.cameraData.values.statLevels.values[Stat.MovementSpeed]) / Math.pow(1.015, this.cameraEntity.cameraData.values.level - 1); + + for (const barrel of this.barrels) barrel.calculateStatData(); + } /** See LivingEntity.onDeath */ public onDeath(killer: LivingEntity) { @@ -326,31 +354,6 @@ export default class TankBody extends LivingEntity implements BarrelBase { this.styleData.opacity = util.constrain(this.styleData.values.opacity, 0, 1); } - - // Update stat related - updateStats: { - // Damage - this.damagePerTick = this.cameraEntity.cameraData.statLevels[Stat.BodyDamage] + 5; - if (this._currentTank === Tank.Spike) this.damagePerTick += 2; - - // Max Health - const maxHealthCache = this.healthData.values.maxHealth; - - this.healthData.maxHealth = this.definition.maxHealth + 2 * (this.cameraEntity.cameraData.values.level - 1) + this.cameraEntity.cameraData.values.statLevels.values[Stat.MaxHealth] * 20; - if (this.healthData.values.health === maxHealthCache) this.healthData.health = this.healthData.maxHealth; // just in case - else if (this.healthData.values.maxHealth !== maxHealthCache) { - this.healthData.health *= this.healthData.values.maxHealth / maxHealthCache - } - - // Regen - this.regenPerTick = (this.healthData.values.maxHealth * 4 * this.cameraEntity.cameraData.values.statLevels.values[Stat.HealthRegen] + this.healthData.values.maxHealth) / 25000; - - // Reload - this.reloadTime = 15 * Math.pow(0.914, this.cameraEntity.cameraData.values.statLevels.values[Stat.Reload]); - } - - this.scoreData.score = this.cameraEntity.cameraData.values.score; - if ((this.styleData.values.flags & StyleFlags.isFlashing) && (this.game.tick >= this.cameraEntity.cameraData.values.spawnTick + 374 || this.inputs.attemptingShot() || this.inputs.movement.magnitude > 0)) { this.styleData.flags ^= StyleFlags.isFlashing; // Dont worry about invulnerability here - not gonna be invulnerable while flashing ever (see setInvulnerability) diff --git a/src/Gamemodes/Survival.ts b/src/Gamemodes/Survival.ts index b96a672f..bdbdac48 100644 --- a/src/Gamemodes/Survival.ts +++ b/src/Gamemodes/Survival.ts @@ -27,6 +27,7 @@ import { ArenaFlags, ClientBound } from "../Const/Enums"; import { countdownDuration, scoreboardUpdateInterval } from "../config"; const MIN_PLAYERS = 4; // 6 in Diep.io +const SCORE_PER_TICK = 0.2; /** * Manage shape count @@ -121,7 +122,7 @@ export default class SurvivalArena extends ArenaEntity { public tick(tick: number) { for (const client of this.game.clients) { const camera = client.camera; - if (camera && Entity.exists(camera.cameraData.values.player)) camera.cameraData.score += 0.2; + if (camera && Entity.exists(camera.cameraData.values.player)) camera.addScore(SCORE_PER_TICK); } super.tick(tick); } diff --git a/src/Native/Camera.ts b/src/Native/Camera.ts index bedff207..887ec748 100644 --- a/src/Native/Camera.ts +++ b/src/Native/Camera.ts @@ -62,6 +62,7 @@ export class CameraEntity extends Entity { if (TankBody.isTank(player)) { const scaleFactor = Math.pow(1.01, level - previousLevel); player.scale(scaleFactor); + player.calculateStatData(); if (isMaxLevel) { player.scoreData.score = levelScore; @@ -73,7 +74,7 @@ export class CameraEntity extends Entity { const statIncrease = ClientCamera.calculateStatCount(level) - ClientCamera.calculateStatCount(previousLevel); this.cameraData.statsAvailable += statIncrease; - this.setFieldFactor(getTankById(this.cameraData.values.tank)?.fieldFactor || 1); + this.setFieldFactor(getTankById(this.cameraData.values.tank)?.fieldFactor ?? 1); } /** Returns the camera's client if it exists */ public getClient(): Client | null { @@ -84,6 +85,60 @@ export class CameraEntity extends Entity { public setFieldFactor(fieldFactor: number) { this.cameraData.FOV = (.55 * fieldFactor) / Math.pow(1.01, (this.cameraData.values.level - 1) / 2); } + + public addScore(score: number) { + this.cameraData.score += score; + + const player = this.cameraData.values.player; + if (player?.scoreData) player.scoreData.score += score; + + this.calculateLevelData(); + } + + public setScore(score: number) { + this.cameraData.score = score; + + const player = this.cameraData.values.player; + if (player?.scoreData) player.scoreData.score = score; + + this.calculateLevelData(); + } + + public addStat(statId: Stat, amount: number) { + this.cameraData.statLevels[statId] += amount; + + const player = this.cameraData.values.player; + + if (TankBody.isTank(player)) player.calculateStatData(); + } + + public setStat(statId: Stat, amount: number) { + this.cameraData.statLevels[statId] = amount; + + const player = this.cameraData.values.player; + + if (TankBody.isTank(player)) player.calculateStatData(); + } + + public calculateLevelData() { + const player = this.cameraData.values.player; + if (!TankBody.isTank(player)) return; + + const score = this.cameraData.values.score; + let newLevel = this.cameraData.values.level; + while (newLevel < levelToScoreTable.length && score - levelToScore(newLevel + 1) >= 0) newLevel += 1 + + if (newLevel !== this.cameraData.values.level) { + this.setLevel(newLevel); + this.cameraData.score = score; + } + + if (newLevel < levelToScoreTable.length) { + const levelScore = levelToScore(this.cameraData.values.level) + this.cameraData.levelbarMax = levelToScore(this.cameraData.values.level + 1) - levelScore; + this.cameraData.levelbarProgress = score - levelScore; + } + } public tick(tick: number) { if (Entity.exists(this.cameraData.values.player)) { @@ -92,28 +147,6 @@ export class CameraEntity extends Entity { this.cameraData.cameraX = focus.rootParent.positionData.values.x; this.cameraData.cameraY = focus.rootParent.positionData.values.y; } - - if (TankBody.isTank(this.cameraData.values.player)) { - // Update player related data - const player = this.cameraData.values.player as TankBody; - - const score = this.cameraData.values.score; - let newLevel = this.cameraData.values.level; - while (newLevel < levelToScoreTable.length && score - levelToScore(newLevel + 1) >= 0) newLevel += 1 - - if (newLevel !== this.cameraData.values.level) { - this.setLevel(newLevel); - this.cameraData.score = score; - } - - if (newLevel < levelToScoreTable.length) { - const levelScore = levelToScore(this.cameraData.values.level) - this.cameraData.levelbarMax = levelToScore(this.cameraData.values.level + 1) - levelScore; - this.cameraData.levelbarProgress = score - levelScore; - } - - this.cameraData.movementSpeed = player.definition.speed * 2.55 * Math.pow(1.07, this.cameraData.values.statLevels.values[Stat.MovementSpeed]) / Math.pow(1.015, this.cameraData.values.level - 1) - } } else { this.cameraData.flags |= CameraFlags.usesCameraCoords; }