diff --git a/src/assets/audio/building/electro.mp3 b/src/assets/audio/building/electro.mp3 new file mode 100644 index 00000000..93bb9c4e Binary files /dev/null and b/src/assets/audio/building/electro.mp3 differ diff --git a/src/game/scenes/world/builder.ts b/src/game/scenes/world/builder.ts index c8f7d9ee..8ef797a9 100644 --- a/src/game/scenes/world/builder.ts +++ b/src/game/scenes/world/builder.ts @@ -5,6 +5,7 @@ import { DIFFICULTY } from '~const/world/difficulty'; import { BUILDING_TILE } from '~const/world/entities/building'; import { BUILDINGS } from '~const/world/entities/buildings'; import { LEVEL_MAP_PERSPECTIVE, LEVEL_MAP_TILE } from '~const/world/level'; +import { Analytics } from '~lib/analytics'; import { isPositionsEqual } from '~lib/dimension'; import { phrase } from '~lib/lang'; import { progressionLinear } from '~lib/progression'; @@ -74,15 +75,19 @@ export class Builder extends Phaser.Events.EventEmitter implements IBuilder { } public update() { - if (this.isCanBuild()) { - if (this.isBuild) { - this.updateSupposedPosition(); - this.updateBuildInstance(); - } else { - this.open(); + try { + if (this.isCanBuild()) { + if (this.isBuild) { + this.updateSupposedPosition(); + this.updateBuildInstance(); + } else { + this.open(); + } + } else if (this.isBuild) { + this.close(); } - } else if (this.isBuild) { - this.close(); + } catch (error) { + Analytics.TrackWarn('Failed builder update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/building/building.ts b/src/game/scenes/world/entities/building/building.ts index e0cd2c73..1369c141 100644 --- a/src/game/scenes/world/entities/building/building.ts +++ b/src/game/scenes/world/entities/building/building.ts @@ -7,6 +7,7 @@ import { BUILDING_TILE } from '~const/world/entities/building'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; import { Indicator } from '~entity/addons/indicator'; import { Live } from '~entity/addons/live'; +import { Analytics } from '~lib/analytics'; import { Assets } from '~lib/assets'; import { progressionQuadratic, progressionLinear } from '~lib/progression'; import { Tutorial } from '~lib/tutorial'; @@ -153,12 +154,16 @@ export class Building extends Phaser.GameObjects.Image implements IBuilding, ITi } public update() { - this.updateOutline(); - this.updateIndicators(); + try { + this.updateOutline(); + this.updateIndicators(); - // Catch focus by camera moving - if (this.toFocus) { - this.focus(); + // Catch focus by camera moving + if (this.toFocus) { + this.focus(); + } + } catch (error) { + Analytics.TrackWarn('Failed building update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/building/variants/electro.ts b/src/game/scenes/world/entities/building/variants/electro.ts index 184f2823..a9a96ee5 100644 --- a/src/game/scenes/world/entities/building/variants/electro.ts +++ b/src/game/scenes/world/entities/building/variants/electro.ts @@ -1,10 +1,12 @@ import { DIFFICULTY } from '~const/world/difficulty'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; +import { Analytics } from '~lib/analytics'; import { progressionLinear } from '~lib/progression'; import { Building } from '~scene/world/entities/building'; import { IWorld } from '~type/world'; import { EntityType } from '~type/world/entities'; import { + BuildingAudio, BuildingCategory, BuildingEvents, BuildingIcon, @@ -63,12 +65,14 @@ export class BuildingElectro extends Building implements IBuilding { public update() { super.update(); - if (!this.isActionAllowed()) { - return; + try { + if (this.isActionAllowed()) { + this.attack(); + this.pauseActions(); + } + } catch (error) { + Analytics.TrackWarn('Failed electro building update', error as TypeError); } - - this.attack(); - this.pauseActions(); } public getInfo() { @@ -149,6 +153,10 @@ export class BuildingElectro extends Building implements IBuilding { } }); + if (this.scene.game.sound.getAll(BuildingAudio.ELECTRO).length === 0) { + this.scene.game.sound.play(BuildingAudio.ELECTRO); + } + if (this.area) { this.area.setActive(true); this.area.setVisible(true); diff --git a/src/game/scenes/world/entities/building/variants/generator.ts b/src/game/scenes/world/entities/building/variants/generator.ts index 35a67b24..acfd2c35 100644 --- a/src/game/scenes/world/entities/building/variants/generator.ts +++ b/src/game/scenes/world/entities/building/variants/generator.ts @@ -1,5 +1,6 @@ import { DIFFICULTY } from '~const/world/difficulty'; import { Building } from '~entity/building'; +import { Analytics } from '~lib/analytics'; import { Tutorial } from '~lib/tutorial'; import { TutorialStep } from '~type/tutorial'; import { IWorld } from '~type/world'; @@ -47,12 +48,14 @@ export class BuildingGenerator extends Building { public update() { super.update(); - if (!this.isActionAllowed()) { - return; + try { + if (this.isActionAllowed()) { + this.generateResource(); + this.pauseActions(); + } + } catch (error) { + Analytics.TrackWarn('Failed generator building update', error as TypeError); } - - this.generateResource(); - this.pauseActions(); } public getTopFace() { diff --git a/src/game/scenes/world/entities/building/variants/tower/tower.ts b/src/game/scenes/world/entities/building/variants/tower/tower.ts index 432b120b..afa5b0c1 100644 --- a/src/game/scenes/world/entities/building/variants/tower/tower.ts +++ b/src/game/scenes/world/entities/building/variants/tower/tower.ts @@ -1,5 +1,6 @@ import { DIFFICULTY } from '~const/world/difficulty'; import { Building } from '~entity/building'; +import { Analytics } from '~lib/analytics'; import { getClosestByIsometricDistance } from '~lib/dimension'; import { progressionLinear } from '~lib/progression'; import { Tutorial } from '~lib/tutorial'; @@ -55,8 +56,12 @@ export class BuildingTower extends Building implements IBuildingTower { public update() { super.update(); - if (this.isCanAttack()) { - this.attack(); + try { + if (this.isCanAttack()) { + this.attack(); + } + } catch (error) { + Analytics.TrackWarn('Failed tower building update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/npc/npc.ts b/src/game/scenes/world/entities/npc/npc.ts index 1c35452e..93eaeffb 100644 --- a/src/game/scenes/world/entities/npc/npc.ts +++ b/src/game/scenes/world/entities/npc/npc.ts @@ -5,6 +5,7 @@ import { WORLD_DEPTH_GRAPHIC } from '~const/world'; import { NPC_PATH_FIND_RATE } from '~const/world/entities/npc'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; import { Sprite } from '~entity/sprite'; +import { Analytics } from '~lib/analytics'; import { isPositionsEqual, getIsometricAngle, getIsometricDistance } from '~lib/dimension'; import { Level } from '~scene/world/level'; import { IWorld } from '~type/world'; @@ -66,18 +67,22 @@ export class NPC extends Sprite implements INPC { public update() { super.update(); - if (this.isCanPursuit()) { - if (this.getDistanceToTarget() <= this.pathFindTriggerDistance) { - this.resetPath(); - this.isPathPassed = true; + try { + if (this.isCanPursuit()) { + if (this.getDistanceToTarget() <= this.pathFindTriggerDistance) { + this.resetPath(); + this.isPathPassed = true; + } else { + this.findPathToTarget(); + this.moveByPath(); + this.isPathPassed = false; + } } else { - this.findPathToTarget(); - this.moveByPath(); + this.setVelocity(0, 0); this.isPathPassed = false; } - } else { - this.setVelocity(0, 0); - this.isPathPassed = false; + } catch (error) { + Analytics.TrackWarn('Failed NPC update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/npc/variants/assistant.ts b/src/game/scenes/world/entities/npc/variants/assistant.ts index 8f9a1372..4902a3c3 100644 --- a/src/game/scenes/world/entities/npc/variants/assistant.ts +++ b/src/game/scenes/world/entities/npc/variants/assistant.ts @@ -3,6 +3,7 @@ import { ASSISTANT_PATH_BREAKPOINT, ASSISTANT_TILE_SIZE, ASSISTANT_WEAPON } from import { NPC } from '~entity/npc'; import { ShotBallFire } from '~entity/shot/ball/variants/fire'; import { ShotLazer } from '~entity/shot/lazer'; +import { Analytics } from '~lib/analytics'; import { Assets } from '~lib/assets'; import { getClosestByIsometricDistance, getIsometricDistance } from '~lib/dimension'; import { progressionQuadratic } from '~lib/progression'; @@ -63,12 +64,16 @@ export class Assistant extends NPC implements IAssistant { public update() { super.update(); - if (this.isPathPassed) { - this.setVelocity(0, 0); - } + try { + if (this.isPathPassed) { + this.setVelocity(0, 0); + } - if (this.isCanAttack()) { - this.attack(); + if (this.isCanAttack()) { + this.attack(); + } + } catch (error) { + Analytics.TrackWarn('Failed assistant update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts b/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts index 65106abb..4232891d 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts @@ -7,6 +7,7 @@ import { } from '~const/world/entities/enemy'; import { Building } from '~entity/building'; import { NPC } from '~entity/npc'; +import { Analytics } from '~lib/analytics'; import { Assets } from '~lib/assets'; import { progressionLinear, progressionQuadratic } from '~lib/progression'; import { GameSettings } from '~type/game'; @@ -124,13 +125,17 @@ export class Enemy extends NPC implements IEnemy { public update() { super.update(); - if (this.isOverlapTarget) { - this.setVelocity(0, 0); - } else if (this.isPathPassed) { - this.moveTo(this.scene.player.getBottomFace()); - } + try { + if (this.isOverlapTarget) { + this.setVelocity(0, 0); + } else if (this.isPathPassed) { + this.moveTo(this.scene.player.getBottomFace()); + } - this.isOverlapTarget = false; + this.isOverlapTarget = false; + } catch (error) { + Analytics.TrackWarn('Failed enemy update', error as TypeError); + } } public overlapTarget() { diff --git a/src/game/scenes/world/entities/npc/variants/enemy/variants/berserk.ts b/src/game/scenes/world/entities/npc/variants/enemy/variants/berserk.ts index f7fc2f51..dee2808e 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/variants/berserk.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/variants/berserk.ts @@ -1,4 +1,5 @@ import { ENEMY_HEAL_MULTIPLIER, ENEMY_HEAL_TIMESTAMP_PAUSE } from '~const/world/entities/enemy'; +import { Analytics } from '~lib/analytics'; import { IWorld } from '~type/world'; import { EnemyVariantData, EnemyTexture } from '~type/world/entities/npc/enemy'; @@ -23,8 +24,13 @@ export class EnemyBerserk extends Enemy { } public update() { - this.heal(); super.update(); + + try { + this.heal(); + } catch (error) { + Analytics.TrackWarn('Failed berserk enemy update', error as TypeError); + } } private heal() { diff --git a/src/game/scenes/world/entities/npc/variants/enemy/variants/ghost.ts b/src/game/scenes/world/entities/npc/variants/enemy/variants/ghost.ts index f594b98b..55674e4c 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/variants/ghost.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/variants/ghost.ts @@ -1,3 +1,4 @@ +import { Analytics } from '~lib/analytics'; import { IWorld } from '~type/world'; import { BuildingVariant } from '~type/world/entities/building'; import { EnemyVariantData, EnemyTexture } from '~type/world/entities/npc/enemy'; @@ -25,6 +26,14 @@ export class EnemyGhost extends Enemy { public update() { super.update(); + try { + this.updateVisible(); + } catch (error) { + Analytics.TrackWarn('Failed ghost enemy update', error as TypeError); + } + } + + private updateVisible() { const isVisible = this.scene.builder .getBuildingsByVariant(BuildingVariant.RADAR) .some((building) => building.actionsAreaContains(this)); diff --git a/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts b/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts index d42f6aee..8f679149 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts @@ -2,6 +2,7 @@ import { ENEMY_REGENERATION_EFFECT_COLOR, ENEMY_REGENERATION_EFFECT_DURATION, ENEMY_REGENERATION_RADIUS, } from '~const/world/entities/enemy'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; +import { Analytics } from '~lib/analytics'; import { getIsometricDistance } from '~lib/dimension'; import { IWorld } from '~type/world'; import { EntityType } from '~type/world/entities'; @@ -41,11 +42,21 @@ export class EnemyTelepath extends Enemy { public update() { super.update(); - if (this.regenerateArea.visible) { - const position = this.getBottomFace(); + try { + this.updateArea(); + } catch (error) { + Analytics.TrackWarn('Failed telepth enemy update', error as TypeError); + } + } - this.regenerateArea.setPosition(position.x, position.y); + private updateArea() { + if (!this.regenerateArea.visible) { + return; } + + const position = this.getBottomFace(); + + this.regenerateArea.setPosition(position.x, position.y); } public onDamage(amount: number) { diff --git a/src/game/scenes/world/entities/player.ts b/src/game/scenes/world/entities/player.ts index b3559097..24b03e65 100644 --- a/src/game/scenes/world/entities/player.ts +++ b/src/game/scenes/world/entities/player.ts @@ -11,6 +11,7 @@ import { import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; import { Crystal } from '~entity/crystal'; import { Sprite } from '~entity/sprite'; +import { Analytics } from '~lib/analytics'; import { Assets } from '~lib/assets'; import { getClosestByIsometricDistance, isPositionsEqual } from '~lib/dimension'; import { progressionLinear, progressionQuadratic } from '~lib/progression'; @@ -184,16 +185,20 @@ export class Player extends Sprite implements IPlayer { public update() { super.update(); - this.findPathToCrystal(); - this.drawPathToCrystal(); + try { + this.findPathToCrystal(); + this.drawPathToCrystal(); - if (!this.live.isDead()) { - this.dustEffect?.emitter.setDepth(this.depth - 1); + if (!this.live.isDead()) { + this.dustEffect?.emitter.setDepth(this.depth - 1); - this.updateMovement(); - this.updateVelocity(); - this.updateVisible(); - this.updateStamina(); + this.updateMovement(); + this.updateVelocity(); + this.updateVisible(); + this.updateStamina(); + } + } catch (error) { + Analytics.TrackWarn('Failed player update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/shot/ball/ball.ts b/src/game/scenes/world/entities/shot/ball/ball.ts index f71ff7a1..2cd746c5 100644 --- a/src/game/scenes/world/entities/shot/ball/ball.ts +++ b/src/game/scenes/world/entities/shot/ball/ball.ts @@ -82,7 +82,17 @@ export class ShotBall extends Phaser.Physics.Arcade.Image implements IShotBall { } public update() { + try { + this.updateFlyDistance(); + } catch (error) { + Analytics.TrackWarn('Failed ball shot update', error as TypeError); + } + } + + private updateFlyDistance() { if (!this.params.maxDistance || !this.startPosition) { + this.stop(); + return; } diff --git a/src/game/scenes/world/entities/shot/lazer.ts b/src/game/scenes/world/entities/shot/lazer.ts index 7f716138..76ee191f 100644 --- a/src/game/scenes/world/entities/shot/lazer.ts +++ b/src/game/scenes/world/entities/shot/lazer.ts @@ -61,7 +61,7 @@ export class ShotLazer extends Phaser.GameObjects.Line implements IShotLazer { this.updateLine(); this.processing(); } catch (error) { - Analytics.TrackWarn('Failed lazer update', error as TypeError); + Analytics.TrackWarn('Failed lazer shot update', error as TypeError); } } diff --git a/src/game/scenes/world/entities/sprite.ts b/src/game/scenes/world/entities/sprite.ts index 0435e676..bb9ce357 100644 --- a/src/game/scenes/world/entities/sprite.ts +++ b/src/game/scenes/world/entities/sprite.ts @@ -4,6 +4,7 @@ import { DEBUG_MODS } from '~const/game'; import { WORLD_COLLIDE_SPEED_FACTOR, WORLD_DEPTH_GRAPHIC } from '~const/world'; import { Indicator } from '~entity/addons/indicator'; import { Live } from '~entity/addons/live'; +import { Analytics } from '~lib/analytics'; import { isPositionsEqual } from '~lib/dimension'; import { Level } from '~scene/world/level'; import { ILive, LiveEvents } from '~type/live'; @@ -98,17 +99,24 @@ export class Sprite extends Phaser.Physics.Arcade.Sprite implements ISprite { } public update() { + try { + this.updateDimension(); + this.updateContainer(); + this.updateIndicators(); + + this.drawDebugGroundPosition(); + } catch (error) { + Analytics.TrackWarn('Failed sprite update', error as TypeError); + } + } + + private updateDimension() { const positionOnGround = this.getBottomFace(); this.positionAtMatrix = Level.ToMatrixPosition(positionOnGround); this.currentBiome = this.scene.level.map.getAt(this.positionAtMatrix); this.setDepth(positionOnGround.y); - - this.updateContainer(); - this.updateIndicators(); - - this.drawDebugGroundPosition(); } private addContainer() { diff --git a/src/game/scenes/world/wave.ts b/src/game/scenes/world/wave.ts index 8608f60c..48965585 100644 --- a/src/game/scenes/world/wave.ts +++ b/src/game/scenes/world/wave.ts @@ -71,28 +71,45 @@ export class Wave extends Phaser.Events.EventEmitter implements IWave { } public update() { + try { + this.handleTimeleft(); + this.handleProcessing(); + } catch (error) { + Analytics.TrackWarn('Failed wave update', error as TypeError); + } + } + + private handleProcessing() { + if (!this.isGoing) { + return; + } + const now = this.scene.getTime(); - if (this.isGoing) { - if (this.spawnedEnemiesCount < this.enemiesMaxCount) { - if (this.nextSpawnTimestamp <= now) { - this.spawnEnemy(); - } - } else if (this.scene.getEntitiesGroup(EntityType.ENEMY).getTotalUsed() === 0) { - this.complete(); - } - } else if (!this.isPeaceMode) { - const left = this.nextWaveTimestamp - now; - - if (left <= 0) { - this.start(); - } else if ( - left <= WAVE_TIMELEFT_ALARM - && !this.scene.isTimePaused() - && !this.alarmInterval - ) { - this.runAlarmCountdown(); + if (this.spawnedEnemiesCount < this.enemiesMaxCount) { + if (this.nextSpawnTimestamp <= now) { + this.spawnEnemy(); } + } else if (this.scene.getEntitiesGroup(EntityType.ENEMY).getTotalUsed() === 0) { + this.complete(); + } + } + + private handleTimeleft() { + if (this.isGoing || this.isPeaceMode) { + return; + } + + const left = this.nextWaveTimestamp - this.scene.getTime(); + + if (left <= 0) { + this.start(); + } else if ( + left <= WAVE_TIMELEFT_ALARM + && !this.scene.isTimePaused() + && !this.alarmInterval + ) { + this.runAlarmCountdown(); } } diff --git a/src/types/world/entities/building.ts b/src/types/world/entities/building.ts index 44d64135..8f8754ba 100644 --- a/src/types/world/entities/building.ts +++ b/src/types/world/entities/building.ts @@ -246,6 +246,7 @@ export enum BuildingAudio { REPAIR = 'building/repair', DAMAGE_1 = 'building/damage_1', DAMAGE_2 = 'building/damage_2', + ELECTRO = 'building/electro', } export enum BuildingOutlineState {