Add app:enemy:seed - 8 SRD monsters for testing the Enemies section
Goblin, Wolf, Zombie, Bandit Captain, Ogre, Owlbear, Wight and Adult Red Dragon (CR 1/4 to 17; humanoid, beast, undead, giant, monstrosity, dragon). Idempotent by name; --purge removes them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Enemy;
|
||||
use App\Enum\CreatureSize;
|
||||
use App\Enum\CreatureType;
|
||||
use App\Repository\EnemyRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:enemy:seed',
|
||||
description: 'Add a handful of SRD monsters to the enemies table for testing.',
|
||||
)]
|
||||
class SeedEnemiesCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly EnemyRepository $enemies,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('purge', null, InputOption::VALUE_NONE, 'Remove the seeded monsters instead of adding them');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$data = $this->monsters();
|
||||
|
||||
if ($input->getOption('purge')) {
|
||||
$removed = 0;
|
||||
foreach ($data as $row) {
|
||||
if ($e = $this->enemies->findOneBy(['name' => $row['name']])) {
|
||||
$this->em->remove($e);
|
||||
++$removed;
|
||||
}
|
||||
}
|
||||
$this->em->flush();
|
||||
$io->success(sprintf('Removed %d seeded monster(s).', $removed));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$added = $skipped = 0;
|
||||
foreach ($data as $row) {
|
||||
if ($this->enemies->findOneBy(['name' => $row['name']])) {
|
||||
++$skipped;
|
||||
continue;
|
||||
}
|
||||
$e = new Enemy();
|
||||
foreach ($row as $prop => $value) {
|
||||
$e->{'set'.ucfirst($prop)}(
|
||||
'size' === $prop ? CreatureSize::from($value)
|
||||
: ('type' === $prop ? CreatureType::from($value) : $value)
|
||||
);
|
||||
}
|
||||
$this->em->persist($e);
|
||||
++$added;
|
||||
}
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(sprintf('%d monster(s) added, %d already present.', $added, $skipped));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* SRD 5.1 stat blocks (open content). Trimmed to what the app stores.
|
||||
*
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function monsters(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Goblin', 'size' => 'Small', 'type' => 'Humanoid', 'subtype' => 'goblinoid',
|
||||
'alignment' => 'neutral evil', 'armorClass' => 15, 'armorClassNote' => 'leather armor, shield',
|
||||
'hitPoints' => 7, 'hitDice' => '2d6', 'speed' => '30 ft.',
|
||||
'strength' => 8, 'dexterity' => 14, 'constitution' => 10, 'intelligence' => 10, 'wisdom' => 8, 'charisma' => 8,
|
||||
'challengeRating' => '1/4', 'experiencePoints' => 50, 'proficiencyBonus' => 2,
|
||||
'skills' => 'Stealth +6', 'senses' => 'darkvision 60 ft., passive Perception 9',
|
||||
'languages' => 'Common, Goblin',
|
||||
'traits' => "Nimble Escape. The goblin can take the Disengage or Hide action as a bonus action on each of its turns.",
|
||||
'actions' => "Scimitar. Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 5 (1d6 + 2) slashing damage.\nShortbow. Ranged Weapon Attack: +4 to hit, range 80/320 ft., one target. Hit: 5 (1d6 + 2) piercing damage.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Wolf', 'size' => 'Medium', 'type' => 'Beast', 'alignment' => 'unaligned',
|
||||
'armorClass' => 13, 'armorClassNote' => 'natural armor', 'hitPoints' => 11, 'hitDice' => '2d8 + 2', 'speed' => '40 ft.',
|
||||
'strength' => 12, 'dexterity' => 15, 'constitution' => 12, 'intelligence' => 3, 'wisdom' => 12, 'charisma' => 6,
|
||||
'challengeRating' => '1/4', 'experiencePoints' => 50, 'proficiencyBonus' => 2,
|
||||
'skills' => 'Perception +3, Stealth +4', 'senses' => 'passive Perception 13',
|
||||
'traits' => "Keen Hearing and Smell. The wolf has advantage on Wisdom (Perception) checks that rely on hearing or smell.\nPack Tactics. The wolf has advantage on an attack roll against a creature if at least one of the wolf's allies is within 5 ft. of the creature and the ally isn't incapacitated.",
|
||||
'actions' => "Bite. Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 7 (2d4 + 2) piercing damage. If the target is a creature, it must succeed on a DC 11 Strength saving throw or be knocked prone.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Zombie', 'size' => 'Medium', 'type' => 'Undead', 'alignment' => 'neutral evil',
|
||||
'armorClass' => 8, 'hitPoints' => 22, 'hitDice' => '3d8 + 9', 'speed' => '20 ft.',
|
||||
'strength' => 13, 'dexterity' => 6, 'constitution' => 16, 'intelligence' => 3, 'wisdom' => 6, 'charisma' => 5,
|
||||
'challengeRating' => '1/4', 'experiencePoints' => 50, 'proficiencyBonus' => 2,
|
||||
'savingThrows' => 'Wis +0', 'damageImmunities' => 'poison', 'conditionImmunities' => 'poisoned',
|
||||
'senses' => 'darkvision 60 ft., passive Perception 8',
|
||||
'languages' => "understands the languages it knew in life but can't speak",
|
||||
'traits' => "Undead Fortitude. If damage reduces the zombie to 0 hit points, it must make a Constitution saving throw with a DC of 5 + the damage taken, unless the damage is radiant or from a critical hit. On a success, the zombie drops to 1 hit point instead.",
|
||||
'actions' => "Slam. Melee Weapon Attack: +3 to hit, reach 5 ft., one target. Hit: 4 (1d6 + 1) bludgeoning damage.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Bandit Captain', 'size' => 'Medium', 'type' => 'Humanoid', 'alignment' => 'any non-lawful alignment',
|
||||
'armorClass' => 15, 'armorClassNote' => 'studded leather', 'hitPoints' => 65, 'hitDice' => '10d8 + 20', 'speed' => '30 ft.',
|
||||
'strength' => 15, 'dexterity' => 16, 'constitution' => 14, 'intelligence' => 14, 'wisdom' => 11, 'charisma' => 14,
|
||||
'challengeRating' => '2', 'experiencePoints' => 450, 'proficiencyBonus' => 2,
|
||||
'savingThrows' => 'Str +4, Dex +5, Wis +2', 'skills' => 'Athletics +4, Deception +4',
|
||||
'senses' => 'passive Perception 10', 'languages' => 'any two languages',
|
||||
'actions' => "Multiattack. The captain makes three melee attacks: two with its scimitar and one with its dagger. Or the captain makes two ranged attacks with its daggers.\nScimitar. Melee Weapon Attack: +5 to hit, reach 5 ft., one target. Hit: 6 (1d6 + 3) slashing damage.\nDagger. Melee or Ranged Weapon Attack: +5 to hit, reach 5 ft. or range 20/60 ft., one target. Hit: 5 (1d4 + 3) piercing damage.",
|
||||
'reactions' => "Parry. The captain adds 2 to its AC against one melee attack that would hit it. To do so, the captain must see the attacker and be wielding a melee weapon.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Ogre', 'size' => 'Large', 'type' => 'Giant', 'alignment' => 'chaotic evil',
|
||||
'armorClass' => 11, 'armorClassNote' => 'hide armor', 'hitPoints' => 59, 'hitDice' => '7d10 + 21', 'speed' => '40 ft.',
|
||||
'strength' => 19, 'dexterity' => 8, 'constitution' => 16, 'intelligence' => 5, 'wisdom' => 7, 'charisma' => 7,
|
||||
'challengeRating' => '2', 'experiencePoints' => 450, 'proficiencyBonus' => 2,
|
||||
'senses' => 'darkvision 60 ft., passive Perception 8', 'languages' => 'Common, Giant',
|
||||
'actions' => "Greatclub. Melee Weapon Attack: +6 to hit, reach 5 ft., one target. Hit: 13 (2d8 + 4) bludgeoning damage.\nJavelin. Melee or Ranged Weapon Attack: +6 to hit, reach 5 ft. or range 30/120 ft., one target. Hit: 11 (2d6 + 4) piercing damage.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Owlbear', 'size' => 'Large', 'type' => 'Monstrosity', 'alignment' => 'unaligned',
|
||||
'armorClass' => 13, 'armorClassNote' => 'natural armor', 'hitPoints' => 59, 'hitDice' => '7d10 + 21', 'speed' => '40 ft.',
|
||||
'strength' => 20, 'dexterity' => 12, 'constitution' => 17, 'intelligence' => 3, 'wisdom' => 12, 'charisma' => 7,
|
||||
'challengeRating' => '3', 'experiencePoints' => 700, 'proficiencyBonus' => 2,
|
||||
'skills' => 'Perception +3', 'senses' => 'darkvision 60 ft., passive Perception 13',
|
||||
'traits' => "Keen Sight and Smell. The owlbear has advantage on Wisdom (Perception) checks that rely on sight or smell.",
|
||||
'actions' => "Multiattack. The owlbear makes two attacks: one with its beak and one with its claws.\nBeak. Melee Weapon Attack: +7 to hit, reach 5 ft., one creature. Hit: 10 (1d10 + 5) piercing damage.\nClaws. Melee Weapon Attack: +7 to hit, reach 5 ft., one target. Hit: 14 (2d8 + 5) slashing damage.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Wight', 'size' => 'Medium', 'type' => 'Undead', 'alignment' => 'neutral evil',
|
||||
'armorClass' => 14, 'armorClassNote' => 'studded leather', 'hitPoints' => 45, 'hitDice' => '6d8 + 18', 'speed' => '30 ft.',
|
||||
'strength' => 15, 'dexterity' => 14, 'constitution' => 16, 'intelligence' => 10, 'wisdom' => 13, 'charisma' => 15,
|
||||
'challengeRating' => '3', 'experiencePoints' => 700, 'proficiencyBonus' => 2,
|
||||
'skills' => 'Perception +3, Stealth +4',
|
||||
'damageResistances' => "necrotic; bludgeoning, piercing, and slashing from nonmagical attacks that aren't silvered",
|
||||
'damageImmunities' => 'poison', 'conditionImmunities' => 'exhaustion, poisoned',
|
||||
'senses' => 'darkvision 60 ft., passive Perception 13',
|
||||
'languages' => 'the languages it knew in life',
|
||||
'traits' => "Sunlight Sensitivity. While in sunlight, the wight has disadvantage on attack rolls, as well as on Wisdom (Perception) checks that rely on sight.",
|
||||
'actions' => "Multiattack. The wight makes two longsword attacks or two longbow attacks. It can use its Life Drain in place of one longsword attack.\nLife Drain. Melee Weapon Attack: +4 to hit, reach 5 ft., one creature. Hit: 5 (1d6 + 2) necrotic damage. The target must succeed on a DC 13 Constitution saving throw or its hit point maximum is reduced by an amount equal to the damage taken. This reduction lasts until the target finishes a long rest. The target dies if this effect reduces its hit point maximum to 0.\nLongsword. Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 6 (1d8 + 2) slashing damage, or 7 (1d10 + 2) slashing damage if used with two hands.\nLongbow. Ranged Weapon Attack: +4 to hit, range 150/600 ft., one target. Hit: 6 (1d8 + 2) piercing damage.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
[
|
||||
'name' => 'Adult Red Dragon', 'size' => 'Huge', 'type' => 'Dragon', 'alignment' => 'chaotic evil',
|
||||
'armorClass' => 19, 'armorClassNote' => 'natural armor', 'hitPoints' => 256, 'hitDice' => '19d12 + 133',
|
||||
'speed' => '40 ft., climb 40 ft., fly 80 ft.',
|
||||
'strength' => 27, 'dexterity' => 10, 'constitution' => 25, 'intelligence' => 16, 'wisdom' => 13, 'charisma' => 21,
|
||||
'challengeRating' => '17', 'experiencePoints' => 18000, 'proficiencyBonus' => 6,
|
||||
'savingThrows' => 'Dex +6, Con +13, Wis +7, Cha +11', 'skills' => 'Perception +13, Stealth +6',
|
||||
'damageImmunities' => 'fire',
|
||||
'senses' => 'blindsight 60 ft., darkvision 120 ft., passive Perception 23',
|
||||
'languages' => 'Common, Draconic',
|
||||
'traits' => "Legendary Resistance (3/Day). If the dragon fails a saving throw, it can choose to succeed instead.",
|
||||
'actions' => "Multiattack. The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws.\nBite. Melee Weapon Attack: +14 to hit, reach 10 ft., one target. Hit: 19 (2d10 + 8) piercing damage plus 7 (2d6) fire damage.\nClaw. Melee Weapon Attack: +14 to hit, reach 5 ft., one target. Hit: 15 (2d6 + 8) slashing damage.\nTail. Melee Weapon Attack: +14 to hit, reach 15 ft., one target. Hit: 17 (2d8 + 8) bludgeoning damage.\nFrightful Presence. Each creature of the dragon's choice within 120 ft. and aware of it must succeed on a DC 19 Wisdom saving throw or become frightened for 1 minute. A creature can repeat the save at the end of each of its turns; on a success it is immune to this dragon's Frightful Presence for 24 hours.\nFire Breath (Recharge 5-6). The dragon exhales fire in a 60-foot cone. Each creature in that area must make a DC 21 Dexterity saving throw, taking 63 (18d6) fire damage on a failed save, or half as much on a success.",
|
||||
'legendaryActions' => "The dragon can take 3 legendary actions, choosing from the options below. Only one legendary action can be used at a time and only at the end of another creature's turn. The dragon regains spent legendary actions at the start of its turn.\nDetect. The dragon makes a Wisdom (Perception) check.\nTail Attack. The dragon makes a tail attack.\nWing Attack (Costs 2 Actions). The dragon beats its wings. Each creature within 10 ft. must succeed on a DC 22 Dexterity saving throw or take 15 (2d6 + 8) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed.",
|
||||
'source' => 'SRD 5.1',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user