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..4fe3b999 100644 --- a/src/Entity/Misc/ArenaCloser.ts +++ b/src/Entity/Misc/ArenaCloser.ts @@ -58,7 +58,7 @@ export default class ArenaCloser extends TankBody { def.maxHealth = 10000 - 598; // TODO(ABC): // Fix all the stats - def.speed = this.ai.movementSpeed = this.cameraEntity.cameraData.values.movementSpeed = 80; + def.speed = this.ai.movementSpeed = this.cameraEntity.cameraData.values.movementSpeed = 5; Object.defineProperty(this, "damagePerTick", { get() { @@ -72,7 +72,7 @@ export default class ArenaCloser extends TankBody { 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); @@ -91,6 +91,6 @@ export default class ArenaCloser extends TankBody { } super.tick(tick); - this.ai.movementSpeed = this.cameraEntity.cameraData.movementSpeed = 80; + this.ai.movementSpeed = this.cameraEntity.cameraData.movementSpeed = 5; } } diff --git a/src/Entity/Misc/Mothership.ts b/src/Entity/Misc/Mothership.ts index ecac3940..dd5b63ee 100644 --- a/src/Entity/Misc/Mothership.ts +++ b/src/Entity/Misc/Mothership.ts @@ -63,8 +63,8 @@ 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; + for (let i = Stat.MovementSpeed; i < Stat.HealthRegen; ++i) camera.setStat(i as Stat, 7); + camera.setStat(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 diff --git a/src/Entity/Tank/TankBody.ts b/src/Entity/Tank/TankBody.ts index a963c1bd..cce00aaa 100644 --- a/src/Entity/Tank/TankBody.ts +++ b/src/Entity/Tank/TankBody.ts @@ -181,10 +181,11 @@ export default class TankBody extends LivingEntity implements BarrelBase { camera.setFieldFactor(tank.fieldFactor); this.scale(1); // Update addons and etc + this.calculateStatData(); } /** 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 +245,30 @@ export default class TankBody extends LivingEntity implements BarrelBase { super.receiveDamage(source, amount); } + + public calculateStatData() { + // 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); + } /** See LivingEntity.onDeath */ public onDeath(killer: LivingEntity) { @@ -326,31 +351,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; }