diff --git a/src/Command/SeedEnemiesCommand.php b/src/Command/SeedEnemiesCommand.php new file mode 100644 index 0000000..1ad22b1 --- /dev/null +++ b/src/Command/SeedEnemiesCommand.php @@ -0,0 +1,180 @@ +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> + */ + 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', + ], + ]; + } +}