id = self::$nextId++; $this->hp = $this->maxHp = (float)$def['hp'] * $hpScale; $this->place(); } public function isFlying(): bool { return !empty($this->def['flying']); } public function armor(): string { return (string)($this->def['armor'] ?? 'light'); } public function currentSpeed(): float { if ($this->stunTime > 0) { return 0.0; } return (float)$this->def['speed'] * (1.0 - $this->slowFactor); } /** Pixels left to the bastion. */ public function remaining(): float { return $this->path->length - $this->distance; } public function isStealthy(): bool { return !empty($this->def['stealth']); } /** Between the two points of its tunnel (tunnel miners): out of reach, not drawn. */ public function isUnderground(): bool { if (empty($this->def['tunnel'])) { return false; } [$from, $to] = $this->def['tunnel']; $part = $this->distance / max(1.0, $this->path->length); return $part > (float)$from && $part < (float)$to; } /** Towers can aim at it. */ public function isTargetable(): bool { return !$this->dead && !$this->escaped && !$this->isUnderground() && (!$this->isStealthy() || $this->revealed); } public function dodge(): float { return (float)($this->def['dodge'] ?? 0.0); } /** Damage per second to the soldiers / walls that stop it. */ public function attack(): float { return (float)($this->def['attack'] ?? 0.0); } /** A soldier holds it, next to it. */ public function isHeld(): bool { return $this->blocker !== null && $this->blocker->isFighting() && $this->blocker->target === $this; } /** * Moves along the path (not past $stopAt: a wall) and ticks the effects. Returns the burn damage to apply this step. */ public function step(float $h, float $stopAt = INF): float { $this->age += $h; $this->lastDistance = $this->distance; $burn = 0.0; if ($this->burnTime > 0) { $burn = $this->burnDps * min($h, $this->burnTime); $this->burnTime -= $h; if ($this->burnTime <= 0) { $this->burnDps = 0.0; } } if (!$this->isHeld()) { $next = $this->distance + $this->currentSpeed() * $h; // a wall ahead stops it (one already past it walks on) $this->atBarrier = $this->distance <= $stopAt && $next >= $stopAt; $this->distance = $this->atBarrier ? max($this->distance, $stopAt) : $next; } else { $this->atBarrier = false; } $this->stunTime = max(0.0, $this->stunTime - $h); if ($this->slowTime > 0) { $this->slowTime -= $h; if ($this->slowTime <= 0) { $this->slowFactor = 0.0; } } if ($this->shredTime > 0) { $this->shredTime -= $h; if ($this->shredTime <= 0) { $this->shredAmount = 0.0; } } if ($this->distance >= $this->path->length) { $this->distance = $this->path->length; $this->escaped = true; } $this->place(); return $burn; } /** @param array $effects slow / stun / burn / fear / shred (towers.yaml) */ public function applyEffects(array $effects, callable $roll): void { if (isset($effects['slow']) && (float)$effects['slow']['factor'] >= $this->slowFactor) { $this->slowFactor = (float)$effects['slow']['factor']; $this->slowTime = max($this->slowTime, (float)$effects['slow']['time']); } // bosses shrug off half of the crowd control $hold = !empty($this->def['boss']) ? 0.5 : 1.0; if (isset($effects['stun']) && $roll() < (float)($effects['stun']['chance'] ?? 1.0)) { $this->stunTime = max($this->stunTime, (float)$effects['stun']['time'] * $hold); } if (isset($effects['burn'])) { $this->burnDps = max($this->burnDps, (float)$effects['burn']['dps']); $this->burnTime = max($this->burnTime, (float)$effects['burn']['time']); } if (isset($effects['fear']) && $roll() < (float)($effects['fear']['chance'] ?? 1.0)) { $this->distance = max(0.0, $this->distance - (float)$effects['fear']['distance'] * $hold); $this->place(); } if (isset($effects['shred'])) { $this->shredAmount = max($this->shredAmount, (float)$effects['shred']['amount']); $this->shredTime = max($this->shredTime, (float)$effects['shred']['time']); } } public function isSlowed(): bool { return $this->slowTime > 0; } public function isStunned(): bool { return $this->stunTime > 0; } public function isBurning(): bool { return $this->burnTime > 0; } private function place(): void { [$this->x, $this->y, $this->dirX, $this->dirY] = $this->path->at($this->distance); } }