Npc::getPortraitPath() maps the name to image/npc-pictures/<NameNoSpaces>.png; the edit page renders it beside the heading (with family · occupation), and drops the <img> via onerror when a portrait is missing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
241 lines
5.5 KiB
PHP
241 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Entity;
|
|
|
|
use App\Enum\BloodCartInvolvement;
|
|
use App\Enum\MysteryImportance;
|
|
use App\Enum\Race;
|
|
use App\Enum\VillageStatus;
|
|
use App\Repository\NpcRepository;
|
|
use Doctrine\DBAL\Types\Types;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
|
|
#[ORM\Entity(repositoryClass: NpcRepository::class)]
|
|
#[ORM\UniqueConstraint(name: 'uniq_npc_family_name', columns: ['family', 'name'])]
|
|
#[ORM\HasLifecycleCallbacks]
|
|
class Npc
|
|
{
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(length: 120)]
|
|
#[Assert\NotBlank]
|
|
private string $family = '';
|
|
|
|
#[ORM\Column(length: 160)]
|
|
#[Assert\NotBlank]
|
|
private string $name = '';
|
|
|
|
#[ORM\Column(type: Types::SMALLINT, nullable: true)]
|
|
#[Assert\PositiveOrZero]
|
|
private ?int $age = null;
|
|
|
|
#[ORM\Column(enumType: Race::class)]
|
|
private Race $race = Race::Human;
|
|
|
|
#[ORM\Column(length: 160, nullable: true)]
|
|
private ?string $occupation = null;
|
|
|
|
/** Free text - the spreadsheet's "Relationships / Notes" column. */
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $relationships = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: self::class)]
|
|
#[ORM\JoinColumn(name: 'father_id', onDelete: 'SET NULL')]
|
|
private ?Npc $father = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: self::class)]
|
|
#[ORM\JoinColumn(name: 'mother_id', onDelete: 'SET NULL')]
|
|
private ?Npc $mother = null;
|
|
|
|
#[ORM\Column(enumType: VillageStatus::class)]
|
|
private VillageStatus $villageStatus = VillageStatus::Established;
|
|
|
|
#[ORM\Column(enumType: BloodCartInvolvement::class)]
|
|
private BloodCartInvolvement $bloodCartInvolvement = BloodCartInvolvement::None;
|
|
|
|
#[ORM\Column(enumType: MysteryImportance::class)]
|
|
private MysteryImportance $mysteryImportance = MysteryImportance::Low;
|
|
|
|
/** DM's live notes, written during play. Never touched by the importer. */
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $gameNotes = null;
|
|
|
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
|
private ?\DateTimeImmutable $updatedAt = null;
|
|
|
|
#[ORM\PrePersist]
|
|
#[ORM\PreUpdate]
|
|
public function touch(): void
|
|
{
|
|
$this->updatedAt = new \DateTimeImmutable();
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getFamily(): string
|
|
{
|
|
return $this->family;
|
|
}
|
|
|
|
public function setFamily(string $family): static
|
|
{
|
|
$this->family = $family;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function setName(string $name): static
|
|
{
|
|
$this->name = $name;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getAge(): ?int
|
|
{
|
|
return $this->age;
|
|
}
|
|
|
|
public function setAge(?int $age): static
|
|
{
|
|
$this->age = $age;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getRace(): Race
|
|
{
|
|
return $this->race;
|
|
}
|
|
|
|
public function setRace(Race $race): static
|
|
{
|
|
$this->race = $race;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getOccupation(): ?string
|
|
{
|
|
return $this->occupation;
|
|
}
|
|
|
|
public function setOccupation(?string $occupation): static
|
|
{
|
|
$this->occupation = $occupation;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getRelationships(): ?string
|
|
{
|
|
return $this->relationships;
|
|
}
|
|
|
|
public function setRelationships(?string $relationships): static
|
|
{
|
|
$this->relationships = $relationships;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getFather(): ?Npc
|
|
{
|
|
return $this->father;
|
|
}
|
|
|
|
public function setFather(?Npc $father): static
|
|
{
|
|
$this->father = $father;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getMother(): ?Npc
|
|
{
|
|
return $this->mother;
|
|
}
|
|
|
|
public function setMother(?Npc $mother): static
|
|
{
|
|
$this->mother = $mother;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getVillageStatus(): VillageStatus
|
|
{
|
|
return $this->villageStatus;
|
|
}
|
|
|
|
public function setVillageStatus(VillageStatus $villageStatus): static
|
|
{
|
|
$this->villageStatus = $villageStatus;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getBloodCartInvolvement(): BloodCartInvolvement
|
|
{
|
|
return $this->bloodCartInvolvement;
|
|
}
|
|
|
|
public function setBloodCartInvolvement(BloodCartInvolvement $bloodCartInvolvement): static
|
|
{
|
|
$this->bloodCartInvolvement = $bloodCartInvolvement;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getMysteryImportance(): MysteryImportance
|
|
{
|
|
return $this->mysteryImportance;
|
|
}
|
|
|
|
public function setMysteryImportance(MysteryImportance $mysteryImportance): static
|
|
{
|
|
$this->mysteryImportance = $mysteryImportance;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getGameNotes(): ?string
|
|
{
|
|
return $this->gameNotes;
|
|
}
|
|
|
|
public function setGameNotes(?string $gameNotes): static
|
|
{
|
|
$this->gameNotes = $gameNotes;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getUpdatedAt(): ?\DateTimeImmutable
|
|
{
|
|
return $this->updatedAt;
|
|
}
|
|
|
|
/**
|
|
* AssetMapper logical path of this NPC's portrait, e.g.
|
|
* "image/npc-pictures/GarrickVoss.png". The file may not exist for
|
|
* NPCs added after the portrait sets were generated.
|
|
*/
|
|
public function getPortraitPath(): string
|
|
{
|
|
return 'image/npc-pictures/'.str_replace(' ', '', $this->name).'.png';
|
|
}
|
|
}
|