Combatant snapshots armor_class from the enemy when added (like maxHp), shown as an "AC n" pill next to the name for quick hit checks. Migration backfills existing combatants from their enemy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
172 lines
3.6 KiB
PHP
172 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Entity;
|
|
|
|
use App\Repository\CombatantRepository;
|
|
use Doctrine\DBAL\Types\Types;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
/**
|
|
* One line on the running combat HP tracker. Stats are copied from the Enemy
|
|
* when added, so editing the Enemy mid-fight doesn't change the tracker.
|
|
*/
|
|
#[ORM\Entity(repositoryClass: CombatantRepository::class)]
|
|
#[ORM\Table(name: 'combatant')]
|
|
class Combatant
|
|
{
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: Encounter::class, inversedBy: 'combatants')]
|
|
#[ORM\JoinColumn(name: 'encounter_id', nullable: false, onDelete: 'CASCADE')]
|
|
private ?Encounter $encounter = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: Enemy::class)]
|
|
#[ORM\JoinColumn(name: 'enemy_id', onDelete: 'SET NULL', nullable: true)]
|
|
private ?Enemy $enemy = null;
|
|
|
|
#[ORM\Column(length: 120)]
|
|
private string $label = '';
|
|
|
|
#[ORM\Column(type: Types::SMALLINT)]
|
|
private int $armorClass = 10;
|
|
|
|
#[ORM\Column(type: Types::SMALLINT)]
|
|
private int $maxHp = 1;
|
|
|
|
#[ORM\Column(type: Types::SMALLINT)]
|
|
private int $currentHp = 1;
|
|
|
|
#[ORM\Column(type: Types::SMALLINT)]
|
|
private int $position = 0;
|
|
|
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
|
private \DateTimeImmutable $addedAt;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->addedAt = new \DateTimeImmutable();
|
|
}
|
|
|
|
public function isDown(): bool
|
|
{
|
|
return $this->currentHp <= 0;
|
|
}
|
|
|
|
/** 0-100, for the HP bar. */
|
|
public function hpPercent(): int
|
|
{
|
|
if ($this->maxHp <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
return (int) round(max(0, min($this->currentHp, $this->maxHp)) / $this->maxHp * 100);
|
|
}
|
|
|
|
public function applyDamage(int $amount): void
|
|
{
|
|
$this->currentHp = max(0, $this->currentHp - abs($amount));
|
|
}
|
|
|
|
public function applyHealing(int $amount): void
|
|
{
|
|
$this->currentHp = min($this->maxHp, $this->currentHp + abs($amount));
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getEncounter(): ?Encounter
|
|
{
|
|
return $this->encounter;
|
|
}
|
|
|
|
public function setEncounter(?Encounter $encounter): static
|
|
{
|
|
$this->encounter = $encounter;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getEnemy(): ?Enemy
|
|
{
|
|
return $this->enemy;
|
|
}
|
|
|
|
public function setEnemy(?Enemy $enemy): static
|
|
{
|
|
$this->enemy = $enemy;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getLabel(): string
|
|
{
|
|
return $this->label;
|
|
}
|
|
|
|
public function setLabel(string $label): static
|
|
{
|
|
$this->label = $label;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getArmorClass(): int
|
|
{
|
|
return $this->armorClass;
|
|
}
|
|
|
|
public function setArmorClass(int $armorClass): static
|
|
{
|
|
$this->armorClass = $armorClass;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getMaxHp(): int
|
|
{
|
|
return $this->maxHp;
|
|
}
|
|
|
|
public function setMaxHp(int $maxHp): static
|
|
{
|
|
$this->maxHp = $maxHp;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getCurrentHp(): int
|
|
{
|
|
return $this->currentHp;
|
|
}
|
|
|
|
public function setCurrentHp(int $currentHp): static
|
|
{
|
|
$this->currentHp = $currentHp;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPosition(): int
|
|
{
|
|
return $this->position;
|
|
}
|
|
|
|
public function setPosition(int $position): static
|
|
{
|
|
$this->position = $position;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getAddedAt(): \DateTimeImmutable
|
|
{
|
|
return $this->addedAt;
|
|
}
|
|
}
|