Combat tracker: multiple named encounters
- Encounter entity; Combatant now belongs to an encounter (ON DELETE
CASCADE). Migration adds encounter_id, rehoming any existing combatants.
- /tracker lists encounters (name, count, last updated) with a "New
encounter" form; /tracker/{id} is the HP tracker for one encounter.
- Encounters can be renamed, have their combatants cleared, or be deleted.
- The Enemies list gains an encounter picker next to "Add to tracker"
(existing encounter or "— New encounter —", which prompts for a name);
an encounter's "Add enemies" link pre-selects it via ?encounter=.
- Routes for combatant HP/remove moved under /tracker/combatant/ to keep
the numeric {id} for encounters.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ class Combatant
|
||||
#[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;
|
||||
@@ -73,6 +77,18 @@ class Combatant
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\EncounterRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* A named combat: a list of combatants with their HP, kept for prep and reuse.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: EncounterRepository::class)]
|
||||
#[ORM\Table(name: 'encounter')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class Encounter
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 120)]
|
||||
#[Assert\NotBlank]
|
||||
private string $name = '';
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
private \DateTimeImmutable $updatedAt;
|
||||
|
||||
/** @var Collection<int, Combatant> */
|
||||
#[ORM\OneToMany(mappedBy: 'encounter', targetEntity: Combatant::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['position' => 'ASC', 'id' => 'ASC'])]
|
||||
private Collection $combatants;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
$this->combatants = new ArrayCollection();
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function touch(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/** @return Collection<int, Combatant> */
|
||||
public function getCombatants(): Collection
|
||||
{
|
||||
return $this->combatants;
|
||||
}
|
||||
|
||||
public function addCombatant(Combatant $combatant): static
|
||||
{
|
||||
if (!$this->combatants->contains($combatant)) {
|
||||
$this->combatants->add($combatant);
|
||||
$combatant->setEncounter($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeCombatant(Combatant $combatant): static
|
||||
{
|
||||
$this->combatants->removeElement($combatant);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user