public function Update($SecondsPassed = false) { $this->Time = time(); if (!$this->IsRunning()) { Server::GetLogger()->debug('Game #' . $this->GameId . ' is not running, canceling update'); return; } Server::GetLogger()->debug('Updating game #' . $this->GameId); $SecondPassed = $SecondsPassed !== false && $SecondsPassed > 0; $LaneDps = [0 => 0, 1 => 0, 2 => 0]; foreach ($this->Players as $Player) { $Player->ClearLoot($this->Time); $Player->CheckActiveAbilities($this); if ($SecondPassed && !$Player->IsDead()) { // Deal DPS damage to current target $Lane = $this->Lanes[$Player->GetCurrentLane()]; $Enemy = $Lane->GetEnemy($Player->GetTarget()); if ($Enemy === null || $Enemy->IsDead()) { $Enemy = $Lane->GetAliveEnemy(); if ($Enemy !== null) { $Player->SetTarget($Enemy->GetPosition()); } } if ($Enemy !== null) { $DealtDpsDamage = $Player->GetTechTree()->GetDps() * $Player->GetTechTree()->GetExtraDamageMultipliers($this->Lanes[$Player->GetCurrentLane()]->GetElement()) * $this->Lanes[$Player->GetCurrentLane()]->GetDamageMultiplier() * $SecondsPassed; if ($this->Lanes[$Player->GetCurrentLane()]->HasActivePlayerAbilityMaxElementalDamage()) { $DealtDpsDamage *= $Player->GetTechTree()->GetHighestElementalMultiplier(); } if ($Player->GetCritDamage() > 0) { $DealtDpsDamage *= $Player->GetTechTree()->GetDamageMultiplierCrit(); $Player->Stats->CritDamageDealt += $DealtDpsDamage; $Player->CritDamage = 0; } $Player->Stats->DpsDamageDealt += $DealtDpsDamage; $Enemy->DpsDamageTaken += $DealtDpsDamage; if ($DealtDpsDamage > 0) { Server::GetLogger()->debug('Player ' . $Player->PlayerName . ' (#' . $Player->GetAccountId() . ')' . ' did ' . $DealtDpsDamage . ' DPS damage to enemy #' . $Enemy->GetId()); } foreach ($Player->LaneDamageBuffer as $LaneId => $LaneDamage) { $LaneDps[$LaneId] += $LaneDamage / $SecondsPassed; // TODO: This is damage done by clicks, not per second, remove or keep? $Player->LaneDamageBuffer[$LaneId] = 0; } $LaneDps[$Player->GetCurrentLane()] += $Player->GetTechTree()->GetDps() * $SecondsPassed; #TODO: $DealtDpsDamage? } } if ($Player->IsDead() && $Player->CanRespawn($this->Time, true)) { // Respawn player $Player->Respawn(); } } // Loop through lanes and deal damage etc $DeadLanes = 0; foreach ($this->Lanes as $LaneId => $Lane) { if ($SecondPassed) { # TODO: Apply this in Lane::CheckActivePlayerAbilities instead? $ReflectDamageMultiplier = $Lane->GetReflectDamageMultiplier(); $NapalmDamageMultiplier = $Lane->GetNapalmDamageMultiplier(); } $DeadEnemies = 0; $EnemyCount = count($Lane->Enemies); $EnemyDpsDamage = 0; foreach ($Lane->Enemies as $Enemy) { if ($Enemy->IsDead()) { if ($Enemy->GetDpsHpDifference() > 0) { // Find next enemy to deal the rest of the DPS damage to $NextEnemy = $Lane->GetAliveEnemy(); if ($NextEnemy !== null) { Server::GetLogger()->debug('Enemy #' . $Enemy->GetId() . ' is dead. Passing on ' . $Enemy->GetDpsHpDifference() . ' DPS damage to enemy #' . $NextEnemy->GetId()); $NextEnemy->DpsDamageTaken += $Enemy->GetDpsHpDifference(); } } $DeadEnemies++; } else { if ($SecondPassed) { # TODO: Apply this in Lane::CheckActivePlayerAbilities instead? # TODO: Check if $ReflectDamageMultiplier is 0.5% or 50%, 0.5% would make more sense if it stacks.. if ($ReflectDamageMultiplier > 0) { $Enemy->AbilityDamageTaken += $Enemy->GetHp() * $ReflectDamageMultiplier * $SecondsPassed; } if ($NapalmDamageMultiplier > 0) { $Damage = $Enemy->GetMaxHp() * $NapalmDamageMultiplier * $SecondsPassed; $Enemy->AbilityDamageTaken += $Damage; } } $Enemy->Hp -= $Enemy->DpsDamageTaken; if ($Enemy->GetDpsHpDifference() > 0) { // Find next enemy to deal the rest of the DPS damage to $NextEnemy = $Lane->GetAliveEnemy(); if ($NextEnemy !== null) { Server::GetLogger()->debug('Enemy #' . $Enemy->GetId() . ' is dead. Passing on ' . $Enemy->GetDpsHpDifference() . ' DPS damage to enemy #' . $NextEnemy->GetId()); $NextEnemy->DpsDamageTaken += $Enemy->GetDpsHpDifference(); } } $Enemy->Hp -= $Enemy->ClickDamageTaken; $Enemy->Hp -= $Enemy->AbilityDamageTaken; if ($Enemy->IsDead()) { $this->IncreaseEnemiesKilled($Enemy); $Enemy->SetHp(0); $DeadEnemies++; $EnemyGold = $Enemy->GetGold() * $Lane->GetEnemyGoldMultiplier(); Server::GetLogger()->debug('Enemy #' . $Enemy->GetId() . ' is dead. Giving ' . $EnemyGold . ' gold (Multiplier: ' . $Lane->GetEnemyGoldMultiplier() . ') to players in lane #' . $Lane->GetLaneId()); $Lane->GiveGoldToPlayers($this, $EnemyGold); } else { $EnemyDpsDamage += $Enemy->GetDps(); } } $Enemy->DpsDamageTaken = 0; $Enemy->ClickDamageTaken = 0; $Enemy->AbilityDamageTaken = 0; if ($Enemy->HasTimer() && $Enemy->IsTimerEnabled() && $Enemy->HasTimerRanOut($SecondsPassed)) { switch ($Enemy->GetType()) { case Enums\EEnemyType::Tower: if ($Enemy->IsDead()) { continue; } // Revive dead mobs in the lane if the tower timer ran out Server::GetLogger()->debug('Respawn timer has ran out in lane #' . $Lane->GetLaneId() . ', reviving dead mobs in the lane'); foreach ($Lane->GetDeadEnemies(Enums\EEnemyType::Mob) as $DeadEnemy) { $DeadEnemy->ResetHp(); } break; case Enums\EEnemyType::MiniBoss: if (!$Enemy->IsDead()) { continue; } // Revive dead miniboss if he's dead and the timer ran out Server::GetLogger()->debug('Respawn timer has ran out for miniboss #' . $Enemy->GetId() . ' in lane #' . $Lane->GetLaneId() . ', reviving dead miniboss'); $Enemy->ResetHp(); break; case Enums\EEnemyType::TreasureMob: if ($Enemy->IsDead()) { continue; } // Kill the treasure mob and set gold to 0 if the timer (lifetime) ran out Server::GetLogger()->debug('Treasure mob #' . $Enemy->GetId() . ' timer has ran out in lane #' . $Lane->GetLaneId() . ', killing treasure mob'); $Enemy->SetHp(0); $Enemy->SetGold(0); $Enemy->DisableTimer(); $this->AddChatEntry('server', '', 'You were too slow to kill the treasure, it has despawned'); break; } $Enemy->ResetTimer(); } } $DeadLanes += $DeadEnemies === count($Lane->Enemies) ? 1 : 0; // Deal damage to players in lane $PlayersInLane = []; foreach ($this->Players as $Player) { if ($Player->GetCurrentLane() === $LaneId) { $PlayersInLane[] = $Player; if ($Player->IsInvulnerable()) { continue; } if ($SecondPassed && !$Player->IsDead()) { $EnemyDamage = $EnemyDpsDamage * $SecondsPassed; $PlayerHp = $Player->Hp - $EnemyDamage; if ($PlayerHp > 0) { $Player->Stats->DamageTaken += $EnemyDamage; $Player->Hp = $PlayerHp; } else { $Player->Stats->DamageTaken += $Player->Hp; $Player->Hp = 0; $Player->Kill($this->Time); } } } } $Lane->Dps = $LaneDps[$LaneId]; $Lane->CheckActivePlayerAbilities($this, $SecondsPassed); $Lane->UpdateHpBuckets($this->Time, $PlayersInLane); } if ($DeadLanes === 3) { if ($this->WormholeCount > 0) { if ($this->IsGoldHelmBossLevel()) { $this->WormholeCount *= 10; } $this->Level += $this->WormholeCount; $BadgePoints = Util::FloorToMultipleOf(10, $this->WormholeCount * 0.1); $AbilityGold = AbilityItem::GetGoldMultiplier(Enums\EAbility::Item_SkipLevels); $Players = $this->GetPlayers(); foreach ($Players as $Player) { $Player->IncreaseGold($AbilityGold * $this->WormholeCount); # TODO: Is gold stackable as well? if ($BadgePoints > 0) { $Player->GetTechTree()->IncreaseBadgePoints($BadgePoints); } } $this->AddChatEntry('server', '', 'Skipped ' . number_format($this->WormholeCount) . ' level' . ($this->WormholeCount === 1 ? '' : 's')); $this->WormholeCount = 0; foreach ($this->Lanes as $Lane) { $Lane->RemoveActiveWormholes(); } } $this->GenerateNewLevel(); } }
public function HandleAbilityUsage($Game, $RequestedAbilities) { foreach ($RequestedAbilities as $RequestedAbility) { $RequestedAbility['ability'] = (int) $RequestedAbility['ability']; if ($RequestedAbility['ability'] <= Enums\EAbility::Invalid || $RequestedAbility['ability'] >= Enums\EAbility::MaxAbilities) { Server::GetLogger()->debug($this->AccountId . ' tried to use an invalid ability: ' . $RequestedAbility['ability']); // Ignore invalid abilities continue; } if (isset($this->AbilityLastUsed[$RequestedAbility['ability']]) && $this->AbilityLastUsed[$RequestedAbility['ability']] >= $Game->Time || $this->IsDead() && $RequestedAbility['ability'] !== Enums\EAbility::ChangeLane && $RequestedAbility['ability'] !== Enums\EAbility::Respawn && $RequestedAbility['ability'] !== Enums\EAbility::ChatMessage) { // Rate limit continue; } $this->AbilityLastUsed[$RequestedAbility['ability']] = $Game->Time + 1; $AllowedAbilityTypes = [Enums\EAbilityType::Support => true, Enums\EAbilityType::Offensive => true, Enums\EAbilityType::Item => true]; if (isset($AllowedAbilityTypes[AbilityItem::GetType($RequestedAbility['ability'])])) { $this->UseAbility($Game, $RequestedAbility['ability']); continue; } # Handle rest of the abilities below // TODO: @Contex: move this to AbilityItem as well? switch ($RequestedAbility['ability']) { case Enums\EAbility::ChatMessage: if (!isset($RequestedAbility['message'])) { break; } $Message = mb_substr($RequestedAbility['message'], 0, 500); $Game->AddChatEntry('chat', $this->PlayerName, $Message); // TODO: This is for debugging only, remove later if (function_exists('SendToIRC')) { SendToIRC("[GAME] 12" . $this->PlayerName . ": " . $Message); } break; case Enums\EAbility::Attack: if (!isset($RequestedAbility['num_clicks'])) { break; } $NumClicks = (int) $RequestedAbility['num_clicks']; if ($NumClicks > self::MAX_CLICKS) { $NumClicks = self::MAX_CLICKS; } else { if ($NumClicks < 1) { $NumClicks = 1; } } $Damage = $NumClicks * $this->GetTechTree()->GetDamagePerClick(); $Lane = $Game->GetLane($this->GetCurrentLane()); // Abilities $Damage *= $Lane->GetDamageMultiplier(); if ($Lane->HasActivePlayerAbilityMaxElementalDamage()) { $Damage *= $this->GetTechTree()->GetHighestElementalMultiplier(); } // Elementals $Damage *= $this->GetTechTree()->GetExtraDamageMultipliers($Lane->GetElement()); $this->CritDamage = 0; if ($this->IsCriticalHit($Lane)) { $Damage *= $this->GetTechTree()->GetDamageMultiplierCrit(); $this->CritDamage = $Damage; $this->Stats->CritDamageDealt += $Damage; } $this->Stats->NumClicks += $NumClicks; $this->Stats->ClickDamageDealt += $Damage; $Game->NumClicks += $NumClicks; $this->LaneDamageBuffer[$this->GetCurrentLane()] += $Damage; # TODO: this logic isn't correct.. it shouldn't buffer the whole lane, FIX! $Enemy = $Lane->GetEnemy($this->GetTarget()); if ($Enemy === null || $Enemy->IsDead()) { break; } $Enemy->ClickDamageTaken += $Damage; $GoldMultiplier = $Lane->GetGoldPerClickMultiplier(); if ($GoldMultiplier > 0) { $this->IncreaseGold($GoldMultiplier * $NumClicks * $Enemy->GetGold()); } $StealHealthMultiplier = $Lane->GetStealHealthMultiplier(); if ($StealHealthMultiplier > 0) { $this->IncreaseHp($StealHealthMultiplier * $NumClicks * $Damage); } break; case Enums\EAbility::ChangeLane: if (!isset($RequestedAbility['new_lane'])) { break; } $NewLane = (int) $RequestedAbility['new_lane']; if ($NewLane < 0 || $NewLane > 2) { break; } $Lane = $Game->GetLane($this->GetCurrentLane()); $Lane->RemovePlayer($this); $this->SetLane($NewLane); $NewLane = $Game->GetLane($this->GetCurrentLane()); $NewLane->AddPlayer($this); break; case Enums\EAbility::Respawn: if ($this->IsDead() && $this->CanRespawn($Game->Time)) { $this->Respawn(); } break; case Enums\EAbility::ChangeTarget: if (!isset($RequestedAbility['new_target'])) { break; } $NewTarget = (int) $RequestedAbility['new_target']; if ($NewTarget < 0 || $NewTarget > 3) { break; } $this->SetTarget($NewTarget); break; } } }