From e2b19e815bb4fd5fd9231ddc85f9df90b6aecce8 Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Sun, 22 Jun 2025 19:57:13 +0000 Subject: [PATCH 1/6] Initial stab at final validation step for SR5E chargen --- .../Http/Controllers/CharactersController.php | 2 + .../app/Models/PartialCharacter.php | 188 ++++++++++++++- Modules/Shadowrun5e/data/qualities.php | 7 + .../Feature/Models/PartialCharacterTest.php | 214 ++++++++++++++++++ 4 files changed, 410 insertions(+), 1 deletion(-) diff --git a/Modules/Shadowrun5e/app/Http/Controllers/CharactersController.php b/Modules/Shadowrun5e/app/Http/Controllers/CharactersController.php index 7fb422082..443ea2e14 100644 --- a/Modules/Shadowrun5e/app/Http/Controllers/CharactersController.php +++ b/Modules/Shadowrun5e/app/Http/Controllers/CharactersController.php @@ -844,11 +844,13 @@ function (Rulebook $_value, string $key) use ($selectedBooks): bool { ] ); case 'review': + $character->validate(); return view( 'shadowrun5e::character', [ 'character' => $character, 'currentStep' => 'review', + // @phpstan-ignore argument.type 'errors' => new MessageBag($character->errors ?? []), 'nextStep' => $this->nextStep('review', $character), 'previousStep' => $this->previousStep('review', $character), diff --git a/Modules/Shadowrun5e/app/Models/PartialCharacter.php b/Modules/Shadowrun5e/app/Models/PartialCharacter.php index 0e4e3dd38..a4ac21ae0 100644 --- a/Modules/Shadowrun5e/app/Models/PartialCharacter.php +++ b/Modules/Shadowrun5e/app/Models/PartialCharacter.php @@ -15,6 +15,10 @@ */ class PartialCharacter extends Character implements Stringable { + protected const PRIORITY_STANDARD = 'standard'; + protected const PRIORITY_SUM_TO_TEN = 'sum-to-ten'; + protected const PRIORITY_KARMA = 'karma'; + protected const int DEFAULT_MAX_ATTRIBUTE = 6; /** @var string */ @@ -22,9 +26,15 @@ class PartialCharacter extends Character implements Stringable /** @var string */ protected $table = 'characters-partial'; - /** @var array> */ + + /** @var array|string> */ public array $errors = []; + protected string $priority_method; + + /** @var array */ + protected array $priorities = []; + /** * Return the starting maximum for a character based on their metatype and * qualities. @@ -109,4 +119,180 @@ public function newFromBuilder( // @phpstan-ignore return.type return $character; } + + /** + * Validate the character against Shadowrun 5E's rules. + * + * Stores any errors or warnings in the errors property, similar to how + * HeroLab or Chummer import does. + */ + public function validate(): void + { + $this->errors = array_merge( + $this->errors ?? [], + $this->validatePriorities(), + $this->validateNativeLanguage(), + ); + } + + /** + * @return array + */ + protected function validatePriorities(): array + { + $errors = []; + if (!isset($this->priorities['a']) && !isset($this->priorities['metatypePriority'])) { + $errors[] = 'You must choose priorities.'; + return $errors; + } + if (!isset($this->priorities['metatype'])) { + $errors[] = 'You must choose a metatype.'; + } + + if (isset($this->priorities['a'])) { + $this->priority_method = self::PRIORITY_STANDARD; + if ( + !isset($this->priorities['b']) + || !isset($this->priorities['c']) + || !isset($this->priorities['d']) + || !isset($this->priorities['e']) + ) { + $errors[] = 'You must allocate all priorities.'; + } + } else { + $this->priority_method = self::PRIORITY_SUM_TO_TEN; + $sumToTen = 10; + $priorities = [ + 'metatypePriority', + 'magicPriority', + 'attributePriority', + 'skillPriority', + 'resourcePriority', + ]; + foreach ($priorities as $priority) { + if (!isset($this->priorities[$priority])) { + $errors[] = 'You must allocate the ' + . str_replace('P', ' p', $priority) + . ' on the ' + . 'priorities page.'; + continue; + } + switch ($this->priorities[$priority]) { + case 'E': + // E priority is worth zero. + break; + case 'D': + $sumToTen -= 1; + break; + case 'C': + $sumToTen -= 2; + break; + case 'B': + $sumToTen -= 3; + break; + case 'A': + $sumToTen -= 4; + break; + } + } + if ($sumToTen > 0) { + $errors[] = 'You haven\'t allocated all sum-to-ten priority points.'; + } elseif ($sumToTen < 0) { + $errors[] = 'You have allocated too many sum-to-ten priority points.'; + } + } + return $errors; + } + + /** + * @return array + */ + protected function validateNativeLanguage(): array + { + $nativeLanguages = 0; + /** @var KnowledgeSkill $knowledge */ + foreach ($this->getKnowledgeSkills() as $knowledge) { + if ('language' !== $knowledge->category) { + continue; + } + if ('N' !== $knowledge->level) { + continue; + } + $nativeLanguages++; + } + $bilingual = false; + foreach ($this->getQualities() as $quality) { + if ('bilingual' === $quality->id) { + $bilingual = true; + break; + } + } + + if ($bilingual && 2 !== $nativeLanguages) { + return ['You haven\'t chosen two native languages for your bilingual quality']; + } + + if (0 === $nativeLanguages) { + return ['You must choose a native language']; + } + + if (!$bilingual && 1 !== $nativeLanguages) { + return ['You can only have one native language']; + } + return []; + } + + /** + * @return array + */ + protected function validateAttributes(): array + { + $errors = []; + $attributePoints = 0; + if (self::PRIORITY_STANDARD === $this->priority_method) { + switch (array_search('attributes', $this->priorities, true)) { + case 'a': + $attributePoints = 24; + break; + case 'b': + $attributePoints = 20; + break; + case 'c': + $attributePoints = 16; + break; + case 'd': + $attributePoints = 14; + break; + case 'e': + $attributePoints = 12; + break; + } + } else { + switch ($this->priorities['attributePriority']) { + case 'A': + $attributePoints = 24; + break; + case 'B': + $attributePoints = 20; + break; + case 'C': + $attributePoints = 16; + break; + case 'D': + $attributePoints = 14; + break; + case 'E': + $attributePoints = 12; + break; + } + } + + $attributePoints = $attributePoints - $this->body - $this->agility + - $this->reaction - $this->strength - $this->willpower + - $this->logic - $this->intuition - $this->charisma; + if (0 < $attributePoints) { + $errors[] = 'You have unspent attribute points'; + } + return $errors; + } } diff --git a/Modules/Shadowrun5e/data/qualities.php b/Modules/Shadowrun5e/data/qualities.php index 1d8498a75..cd26ebf77 100644 --- a/Modules/Shadowrun5e/data/qualities.php +++ b/Modules/Shadowrun5e/data/qualities.php @@ -129,6 +129,13 @@ 'ruleset' => 'chrome-flesh', 'severity' => 'Cyberware', ], + 'bilingual' => [ + 'id' => 'bilingual', + 'description' => 'Bilingual description.', + 'incompatible-with' => ['bilingual'], + 'karma' => -5, + 'name' => 'Bilingual', + ], 'exceptional-attribute-body' => [ 'id' => 'exceptional-attribute-body', 'attribute' => 'Body', diff --git a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php index d58287917..80f672afe 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php @@ -157,4 +157,218 @@ public function testGetMaximumAttributes( $character->getStartingMaximumAttribute($attribute) ); } + + /** + * Test validate on an empty partial character. + * @test + */ + public function testValidateEmpty(): void + { + $character = new PartialCharacter(); + $character->validate(); + self::assertSame( + [ + 'You must choose priorities.', + 'You must choose a native language', + ], + $character->errors + ); + } + + /** + * Test validate on a partial character with no metatype. + * @test + */ + public function testValidateNoMetatype(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'a' => 'metatype', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertSame( + [ + 'You must choose a metatype.', + 'You must allocate all priorities.', + ], + $character->errors + ); + } + + /** + * Test a normal priority character with too many native languages. + * @test + */ + public function testValidateTooManyNativeLanguages(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'a' => 'metatype', + 'b' => 'resources', + 'c' => 'magic', + 'd' => 'attributes', + 'e' => 'skills', + 'metatype' => 'human', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ['name' => 'Spanish', 'category' => 'language', 'level' => 'N'], + ['name' => 'Orkish', 'category' => 'language', 'level' => 2], + ], + ]); + $character->validate(); + self::assertSame( + [ + 'You can only have one native language', + ], + $character->errors + ); + } + + /** + * Test validating native languages with the bilingual quality. + * @test + */ + public function testValidateBilingual(): void + { + $character = new PartialCharacter([ + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ['name' => 'Spanish', 'category' => 'language', 'level' => 'N'], + ['name' => 'Orkish', 'category' => 'language', 'level' => 2], + ], + 'priorities' => [ + 'a' => 'metatype', + 'b' => 'resources', + 'c' => 'magic', + 'd' => 'attributes', + 'e' => 'skills', + 'metatype' => 'human', + ], + 'qualities' => [ + ['id' => 'bilingual'], + ], + ]); + $character->validate(); + self::assertEmpty($character->errors); + } + + /** + * Test validating the bilingual quality without enough native languages. + * @test + */ + public function testValidateBilingualNotEnoughLanguages(): void + { + $character = new PartialCharacter([ + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ['name' => 'Bars', 'category' => 'street', 'level' => 2], + ], + 'priorities' => [ + 'a' => 'metatype', + 'b' => 'resources', + 'c' => 'magic', + 'd' => 'attributes', + 'e' => 'skills', + 'metatype' => 'human', + ], + 'qualities' => [ + ['id' => 'bilingual'], + ], + ]); + $character->validate(); + self::assertSame( + ['You haven\'t chosen two native languages for your bilingual quality'], + $character->errors + ); + } + + /** + * Test validating a sum-to-ten character that hasn't assigned all + * priorities. + * @test + */ + public function testValidateSumToTenMissing(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'metatypePriority' => 'frank', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertSame( + [ + 'You must choose a metatype.', + 'You must allocate the magic priority on the priorities page.', + 'You must allocate the attribute priority on the priorities page.', + 'You must allocate the skill priority on the priorities page.', + 'You must allocate the resource priority on the priorities page.', + 'You haven\'t allocated all sum-to-ten priority points.', + ], + $character->errors + ); + } + + /** + * Test validating a sum-to-ten character that has overspent. + * @test + */ + public function testValidateSumToTenOverspent(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'metatypePriority' => 'A', + 'magicPriority' => 'A', + 'attributePriority' => 'A', + 'skillPriority' => 'A', + 'resourcePriority' => 'A', + 'metatype' => 'elf', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertSame( + [ + 'You have allocated too many sum-to-ten priority points.', + ], + $character->errors + ); + } + + /** + * Test validating a sum-to-ten character that chose attributes correctly. + * @test + */ + public function testValidateSumToTen(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'metatypePriority' => 'A', + 'magicPriority' => 'B', + 'attributePriority' => 'C', + 'skillPriority' => 'D', + 'resourcePriority' => 'E', + 'metatype' => 'elf', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertEmpty($character->errors); + } } From 3fefdac1f82ada93978e16e281e84ffaf796b008 Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Mon, 30 Oct 2023 20:16:37 +0000 Subject: [PATCH 2/6] Add some additional tests for validation --- .../app/Models/PartialCharacter.php | 9 ++- .../Feature/Models/PartialCharacterTest.php | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Modules/Shadowrun5e/app/Models/PartialCharacter.php b/Modules/Shadowrun5e/app/Models/PartialCharacter.php index a4ac21ae0..a3403e05e 100644 --- a/Modules/Shadowrun5e/app/Models/PartialCharacter.php +++ b/Modules/Shadowrun5e/app/Models/PartialCharacter.php @@ -132,6 +132,7 @@ public function validate(): void $this->errors ?? [], $this->validatePriorities(), $this->validateNativeLanguage(), + $this->validateAttributes(), ); } @@ -268,6 +269,9 @@ protected function validateAttributes(): array break; } } else { + if (!isset($this->priorities['attributePriority'])) { + return []; + } switch ($this->priorities['attributePriority']) { case 'A': $attributePoints = 24; @@ -291,7 +295,10 @@ protected function validateAttributes(): array - $this->reaction - $this->strength - $this->willpower - $this->logic - $this->intuition - $this->charisma; if (0 < $attributePoints) { - $errors[] = 'You have unspent attribute points'; + $errors[] = sprintf( + 'You have %d unspent attribute points', + $attributePoints, + ); } return $errors; } diff --git a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php index 80f672afe..cbf82d034 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php @@ -214,6 +214,14 @@ public function testValidateTooManyNativeLanguages(): void 'e' => 'skills', 'metatype' => 'human', ], + 'agility' => 6, + 'body' => 6, + 'charisma' => 6, + 'reaction' => 1, + 'strength' => 1, + 'willpower' => 1, + 'logic' => 1, + 'intuition' => 2, 'knowledgeSkills' => [ ['name' => 'English', 'category' => 'language', 'level' => 'N'], ['name' => 'Spanish', 'category' => 'language', 'level' => 'N'], @@ -249,6 +257,14 @@ public function testValidateBilingual(): void 'e' => 'skills', 'metatype' => 'human', ], + 'agility' => 6, + 'body' => 6, + 'charisma' => 6, + 'reaction' => 1, + 'strength' => 1, + 'willpower' => 1, + 'logic' => 1, + 'intuition' => 2, 'qualities' => [ ['id' => 'bilingual'], ], @@ -276,6 +292,14 @@ public function testValidateBilingualNotEnoughLanguages(): void 'e' => 'skills', 'metatype' => 'human', ], + 'agility' => 6, + 'body' => 6, + 'charisma' => 6, + 'reaction' => 1, + 'strength' => 1, + 'willpower' => 1, + 'logic' => 1, + 'intuition' => 2, 'qualities' => [ ['id' => 'bilingual'], ], @@ -336,6 +360,14 @@ public function testValidateSumToTenOverspent(): void 'resourcePriority' => 'A', 'metatype' => 'elf', ], + 'agility' => 6, + 'body' => 6, + 'charisma' => 6, + 'reaction' => 1, + 'strength' => 1, + 'willpower' => 1, + 'logic' => 1, + 'intuition' => 2, 'knowledgeSkills' => [ ['name' => 'English', 'category' => 'language', 'level' => 'N'], ], @@ -364,6 +396,14 @@ public function testValidateSumToTen(): void 'resourcePriority' => 'E', 'metatype' => 'elf', ], + 'agility' => 6, + 'body' => 6, + 'charisma' => 6, + 'reaction' => 1, + 'strength' => 1, + 'willpower' => 1, + 'logic' => 1, + 'intuition' => 2, 'knowledgeSkills' => [ ['name' => 'English', 'category' => 'language', 'level' => 'N'], ], @@ -371,4 +411,30 @@ public function testValidateSumToTen(): void $character->validate(); self::assertEmpty($character->errors); } + + /** + * Test validating a character with unspent attribute points. + * @test + */ + public function testValidateAttributesUnspent(): void + { + $character = new PartialCharacter([ + 'priorities' => [ + 'metatypePriority' => 'A', + 'magicPriority' => 'B', + 'attributePriority' => 'C', + 'skillPriority' => 'D', + 'resourcePriority' => 'E', + 'metatype' => 'elf', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertSame( + ['You have 16 unspent attribute points'], + $character->errors, + ); + } } From df6e9558a220ac22c988811c21e55165e88cf40c Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Sun, 22 Jun 2025 20:05:15 +0000 Subject: [PATCH 3/6] Import sprintf --- Modules/Shadowrun5e/app/Models/PartialCharacter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/Shadowrun5e/app/Models/PartialCharacter.php b/Modules/Shadowrun5e/app/Models/PartialCharacter.php index a3403e05e..35e9f66e6 100644 --- a/Modules/Shadowrun5e/app/Models/PartialCharacter.php +++ b/Modules/Shadowrun5e/app/Models/PartialCharacter.php @@ -9,6 +9,8 @@ use Override; use Stringable; +use function sprintf; + /** * Representation of a character currently being built. * @method static self create(array $attributes) From 184f77c5440be7389b45a453b7cfdd39fb1aeff7 Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Mon, 23 Jun 2025 21:08:55 -0500 Subject: [PATCH 4/6] Remove redundant @test annotations --- .../tests/Feature/Models/PartialCharacterTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php index cbf82d034..dbdc607d6 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php @@ -160,7 +160,6 @@ public function testGetMaximumAttributes( /** * Test validate on an empty partial character. - * @test */ public function testValidateEmpty(): void { @@ -177,7 +176,6 @@ public function testValidateEmpty(): void /** * Test validate on a partial character with no metatype. - * @test */ public function testValidateNoMetatype(): void { @@ -201,7 +199,6 @@ public function testValidateNoMetatype(): void /** * Test a normal priority character with too many native languages. - * @test */ public function testValidateTooManyNativeLanguages(): void { @@ -239,7 +236,6 @@ public function testValidateTooManyNativeLanguages(): void /** * Test validating native languages with the bilingual quality. - * @test */ public function testValidateBilingual(): void { @@ -275,7 +271,6 @@ public function testValidateBilingual(): void /** * Test validating the bilingual quality without enough native languages. - * @test */ public function testValidateBilingualNotEnoughLanguages(): void { @@ -314,7 +309,6 @@ public function testValidateBilingualNotEnoughLanguages(): void /** * Test validating a sum-to-ten character that hasn't assigned all * priorities. - * @test */ public function testValidateSumToTenMissing(): void { @@ -347,7 +341,6 @@ public function testValidateSumToTenMissing(): void /** * Test validating a sum-to-ten character that has overspent. - * @test */ public function testValidateSumToTenOverspent(): void { @@ -383,7 +376,6 @@ public function testValidateSumToTenOverspent(): void /** * Test validating a sum-to-ten character that chose attributes correctly. - * @test */ public function testValidateSumToTen(): void { @@ -414,7 +406,6 @@ public function testValidateSumToTen(): void /** * Test validating a character with unspent attribute points. - * @test */ public function testValidateAttributesUnspent(): void { From a5f7a3db57ece144f0830aac81a5a770084232a7 Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Fri, 27 Jun 2025 20:17:26 +0000 Subject: [PATCH 5/6] Minor code cleanups, fix tests, add tests --- .../app/Models/AdeptPowerArray.php | 8 +- Modules/Shadowrun5e/app/Models/Armor.php | 18 ++++- Modules/Shadowrun5e/app/Models/Character.php | 14 ++-- .../app/Models/PartialCharacter.php | 24 +++--- Modules/Shadowrun5e/data/armor.php | 1 + .../Controllers/CharactersControllerTest.php | 1 - .../Feature/Models/AdeptPowerArrayTest.php | 2 +- .../Feature/Models/PartialCharacterTest.php | 79 +++++++++++++++++-- 8 files changed, 111 insertions(+), 36 deletions(-) diff --git a/Modules/Shadowrun5e/app/Models/AdeptPowerArray.php b/Modules/Shadowrun5e/app/Models/AdeptPowerArray.php index 860b1a711..69957df0b 100644 --- a/Modules/Shadowrun5e/app/Models/AdeptPowerArray.php +++ b/Modules/Shadowrun5e/app/Models/AdeptPowerArray.php @@ -16,14 +16,14 @@ final class AdeptPowerArray extends ArrayObject { /** * Add a power to the array. - * @param AdeptPower $power + * @param AdeptPower $value * @throws TypeError */ #[Override] - public function offsetSet(mixed $index = null, $power = null): void + public function offsetSet(mixed $key = null, $value = null): void { - if ($power instanceof AdeptPower) { - parent::offsetSet($index, $power); + if ($value instanceof AdeptPower) { + parent::offsetSet($key, $value); return; } throw new TypeError('AdeptPowerArray only accepts AdeptPower objects'); diff --git a/Modules/Shadowrun5e/app/Models/Armor.php b/Modules/Shadowrun5e/app/Models/Armor.php index 66772753e..36327b743 100644 --- a/Modules/Shadowrun5e/app/Models/Armor.php +++ b/Modules/Shadowrun5e/app/Models/Armor.php @@ -73,7 +73,23 @@ final class Armor implements Stringable /** * List of all armor. - * @var ?array + * @var ?array, + * cost: int, + * description: string, + * effects?: array, + * features: array, + * id: string, + * name: string, + * page: int, + * rating?: int, + * ruleset: string, + * stack-rating: int, + * wireless-effects?: array + * }> */ public static ?array $armor; diff --git a/Modules/Shadowrun5e/app/Models/Character.php b/Modules/Shadowrun5e/app/Models/Character.php index 8d6c1781d..0f1979e36 100644 --- a/Modules/Shadowrun5e/app/Models/Character.php +++ b/Modules/Shadowrun5e/app/Models/Character.php @@ -188,7 +188,7 @@ protected static function booted(): void { static::addGlobalScope( 'shadowrun5e', - function (Builder $builder): void { + static function (Builder $builder): void { $builder->where('system', 'shadowrun5e'); } ); @@ -527,7 +527,7 @@ public function liftCarry(): Attribute public function getMartialArtsStyles(): MartialArtsStyleArray { $styles = new MartialArtsStyleArray(); - if (!isset($this->martialArts, $this->martialArts['styles'])) { + if (!isset($this->martialArts['styles'])) { return $styles; } foreach ($this->martialArts['styles'] as $style) { @@ -550,7 +550,7 @@ public function getMartialArtsStyles(): MartialArtsStyleArray public function getMartialArtsTechniques(): MartialArtsTechniqueArray { $techniques = new MartialArtsTechniqueArray(); - if (!isset($this->martialArts, $this->martialArts['techniques'])) { + if (!isset($this->martialArts['techniques'])) { return $techniques; } foreach ($this->martialArts['techniques'] as $technique) { @@ -837,7 +837,7 @@ public function socialLimit(): Attribute public function getSpells(): SpellArray { $spells = new SpellArray(); - if (!isset($this->magics, $this->magics['spells'])) { + if (!isset($this->magics['spells'])) { return $spells; } foreach ($this->magics['spells'] as $spell) { @@ -860,7 +860,7 @@ public function getSpells(): SpellArray public function getSpirits(): SpiritArray { $spirits = new SpiritArray(); - if (!isset($this->magics, $this->magics['spirits'])) { + if (!isset($this->magics['spirits'])) { return $spirits; } foreach ($this->magics['spirits'] as $spirit) { @@ -886,7 +886,7 @@ public function getSpirits(): SpiritArray public function getSprites(): SpriteArray { $sprites = new SpriteArray(); - if (!isset($this->technomancer, $this->technomancer['sprites'])) { + if (!isset($this->technomancer['sprites'])) { return $sprites; } foreach ($this->technomancer['sprites'] as $sprite) { @@ -911,7 +911,7 @@ public function getSprites(): SpriteArray */ public function getTradition(): ?Tradition { - if (!isset($this->magics, $this->magics['tradition'])) { + if (!isset($this->magics['tradition'])) { return null; } try { diff --git a/Modules/Shadowrun5e/app/Models/PartialCharacter.php b/Modules/Shadowrun5e/app/Models/PartialCharacter.php index 35e9f66e6..e76e869c1 100644 --- a/Modules/Shadowrun5e/app/Models/PartialCharacter.php +++ b/Modules/Shadowrun5e/app/Models/PartialCharacter.php @@ -5,6 +5,7 @@ namespace Modules\Shadowrun5e\Models; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Database\Eloquent\Model; use Modules\Shadowrun5e\Database\Factories\PartialCharacterFactory; use Override; use Stringable; @@ -14,18 +15,16 @@ /** * Representation of a character currently being built. * @method static self create(array $attributes) + * @mixin Model */ class PartialCharacter extends Character implements Stringable { - protected const PRIORITY_STANDARD = 'standard'; - protected const PRIORITY_SUM_TO_TEN = 'sum-to-ten'; - protected const PRIORITY_KARMA = 'karma'; + protected const string PRIORITY_STANDARD = 'standard'; + protected const string PRIORITY_SUM_TO_TEN = 'sum-to-ten'; + protected const string PRIORITY_KARMA = 'karma'; protected const int DEFAULT_MAX_ATTRIBUTE = 6; - /** @var string */ - protected $connection = 'mongodb'; - /** @var string */ protected $table = 'characters-partial'; @@ -34,9 +33,6 @@ class PartialCharacter extends Character implements Stringable protected string $priority_method; - /** @var array */ - protected array $priorities = []; - /** * Return the starting maximum for a character based on their metatype and * qualities. @@ -87,7 +83,7 @@ public function getStartingMaximumAttribute(string $attribute): int */ public function isMagicallyActive(): bool { - return isset($this->priorities, $this->priorities['magic']) + return isset($this->priorities['magic']) && 'technomancer' !== $this->priorities['magic']; } @@ -96,7 +92,7 @@ public function isMagicallyActive(): bool */ public function isTechnomancer(): bool { - return isset($this->priorities, $this->priorities['magic']) + return isset($this->priorities['magic']) && 'technomancer' === $this->priorities['magic']; } @@ -185,7 +181,7 @@ protected function validatePriorities(): array // E priority is worth zero. break; case 'D': - $sumToTen -= 1; + $sumToTen--; break; case 'C': $sumToTen -= 2; @@ -252,8 +248,8 @@ protected function validateAttributes(): array { $errors = []; $attributePoints = 0; - if (self::PRIORITY_STANDARD === $this->priority_method) { - switch (array_search('attributes', $this->priorities, true)) { + if (self::PRIORITY_STANDARD === ($this->priority_method ?? 'unknown')) { + switch (array_search('attributes', $this->priorities ?? [], true)) { case 'a': $attributePoints = 24; break; diff --git a/Modules/Shadowrun5e/data/armor.php b/Modules/Shadowrun5e/data/armor.php index d5d28c734..4912df98e 100644 --- a/Modules/Shadowrun5e/data/armor.php +++ b/Modules/Shadowrun5e/data/armor.php @@ -11,6 +11,7 @@ 'availability' => '', 'capacity' => , 'chummer-id' => '', + 'container-type' => ['armor', 'audio', 'vision'], 'cost' => , 'description' => '', 'effects' => [], diff --git a/Modules/Shadowrun5e/tests/Feature/Http/Controllers/CharactersControllerTest.php b/Modules/Shadowrun5e/tests/Feature/Http/Controllers/CharactersControllerTest.php index 2ca860592..9e32018a6 100644 --- a/Modules/Shadowrun5e/tests/Feature/Http/Controllers/CharactersControllerTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Http/Controllers/CharactersControllerTest.php @@ -1484,7 +1484,6 @@ public function testLoadKnowledgeSkillsMagical(): void public function testLoadKnowledgeSkillsResonance(): void { $user = User::factory()->create(); - $character = PartialCharacter::factory()->create([ 'priorities' => [ 'magic' => 'technomancer', diff --git a/Modules/Shadowrun5e/tests/Feature/Models/AdeptPowerArrayTest.php b/Modules/Shadowrun5e/tests/Feature/Models/AdeptPowerArrayTest.php index 2b4d2a8f9..920926350 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/AdeptPowerArrayTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/AdeptPowerArrayTest.php @@ -68,7 +68,7 @@ public function testAddWrongTypeDoesntAdd(): void { try { // @phpstan-ignore argument.type - $this->powers->offsetSet(power: new stdClass()); + $this->powers->offsetSet(value: new stdClass()); } catch (TypeError) { // Ignored } diff --git a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php index dbdc607d6..9002f006b 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php @@ -10,6 +10,8 @@ use PHPUnit\Framework\Attributes\Small; use Tests\TestCase; +use function sprintf; + #[Group('shadowrun')] #[Group('shadowrun5e')] #[Small] @@ -405,17 +407,78 @@ public function testValidateSumToTen(): void } /** - * Test validating a character with unspent attribute points. + * @return array> */ - public function testValidateAttributesUnspent(): void + public static function unspentSumToTenAttributesProvider(): array { + return [ + ['A', 'B', 'C', 'D', 'E', 16], + ['B', 'C', 'D', 'E', 'A', 14], + ['C', 'D', 'E', 'A', 'B', 12], + ['D', 'E', 'A', 'B', 'C', 24], + ['E', 'A', 'B', 'C', 'D', 20], + ]; + } + + #[DataProvider('unspentSumToTenAttributesProvider')] + public function testValidateAttributesUnspentSumToTen( + string $metatype, + string $magic, + string $attribute, + string $skill, + string $resource, + int $remaining, + ): void { $character = new PartialCharacter([ 'priorities' => [ - 'metatypePriority' => 'A', - 'magicPriority' => 'B', - 'attributePriority' => 'C', - 'skillPriority' => 'D', - 'resourcePriority' => 'E', + 'metatypePriority' => $metatype, + 'magicPriority' => $magic, + 'attributePriority' => $attribute, + 'skillPriority' => $skill, + 'resourcePriority' => $resource, + 'metatype' => 'elf', + ], + 'knowledgeSkills' => [ + ['name' => 'English', 'category' => 'language', 'level' => 'N'], + ], + ]); + $character->validate(); + self::assertSame( + [sprintf('You have %d unspent attribute points', $remaining)], + $character->errors, + ); + } + + /** + * @return array> + */ + public static function unspentStandardAttributesProvider(): array + { + return [ + ['attributes', 'skills', 'magic', 'metatype', 'resources', 24], + ['skills', 'magic', 'metatype', 'resources', 'attributes', 12], + ['magic', 'metatype', 'resources', 'attributes', 'skills', 14], + ['metatype', 'resources', 'attributes', 'skills', 'magic', 16], + ['resources', 'attributes', 'skills', 'magic', 'metatype', 20], + ]; + } + + #[DataProvider('unspentStandardAttributesProvider')] + public function testValidateAttributesUnspentStandard( + string $a, + string $b, + string $c, + string $d, + string $e, + int $remaining, + ): void { + $character = new PartialCharacter([ + 'priorities' => [ + 'a' => $a, + 'b' => $b, + 'c' => $c, + 'd' => $d, + 'e' => $e, 'metatype' => 'elf', ], 'knowledgeSkills' => [ @@ -424,7 +487,7 @@ public function testValidateAttributesUnspent(): void ]); $character->validate(); self::assertSame( - ['You have 16 unspent attribute points'], + [sprintf('You have %d unspent attribute points', $remaining)], $character->errors, ); } From ed64aa190307dee3da313c8b6ef0f255b8ccdddc Mon Sep 17 00:00:00 2001 From: Omni Adams Date: Mon, 8 Sep 2025 16:29:53 +0000 Subject: [PATCH 6/6] Fix style and static issues --- .../app/Models/PartialCharacter.php | 4 +-- .../Feature/Models/PartialCharacterTest.php | 32 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/Modules/Shadowrun5e/app/Models/PartialCharacter.php b/Modules/Shadowrun5e/app/Models/PartialCharacter.php index e76e869c1..4bfde1d2f 100644 --- a/Modules/Shadowrun5e/app/Models/PartialCharacter.php +++ b/Modules/Shadowrun5e/app/Models/PartialCharacter.php @@ -181,7 +181,7 @@ protected function validatePriorities(): array // E priority is worth zero. break; case 'D': - $sumToTen--; + --$sumToTen; break; case 'C': $sumToTen -= 2; @@ -217,7 +217,7 @@ protected function validateNativeLanguage(): array if ('N' !== $knowledge->level) { continue; } - $nativeLanguages++; + ++$nativeLanguages; } $bilingual = false; foreach ($this->getQualities() as $quality) { diff --git a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php index 664699750..e8366d003 100644 --- a/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php +++ b/Modules/Shadowrun5e/tests/Feature/Models/PartialCharacterTest.php @@ -406,17 +406,15 @@ public function testValidateSumToTen(): void } /** - * @return array> + * @return Iterator> */ - public static function unspentSumToTenAttributesProvider(): array + public static function unspentSumToTenAttributesProvider(): Iterator { - return [ - ['A', 'B', 'C', 'D', 'E', 16], - ['B', 'C', 'D', 'E', 'A', 14], - ['C', 'D', 'E', 'A', 'B', 12], - ['D', 'E', 'A', 'B', 'C', 24], - ['E', 'A', 'B', 'C', 'D', 20], - ]; + yield ['A', 'B', 'C', 'D', 'E', 16]; + yield ['B', 'C', 'D', 'E', 'A', 14]; + yield ['C', 'D', 'E', 'A', 'B', 12]; + yield ['D', 'E', 'A', 'B', 'C', 24]; + yield ['E', 'A', 'B', 'C', 'D', 20]; } #[DataProvider('unspentSumToTenAttributesProvider')] @@ -449,17 +447,15 @@ public function testValidateAttributesUnspentSumToTen( } /** - * @return array> + * @return Iterator> */ - public static function unspentStandardAttributesProvider(): array + public static function unspentStandardAttributesProvider(): Iterator { - return [ - ['attributes', 'skills', 'magic', 'metatype', 'resources', 24], - ['skills', 'magic', 'metatype', 'resources', 'attributes', 12], - ['magic', 'metatype', 'resources', 'attributes', 'skills', 14], - ['metatype', 'resources', 'attributes', 'skills', 'magic', 16], - ['resources', 'attributes', 'skills', 'magic', 'metatype', 20], - ]; + yield ['attributes', 'skills', 'magic', 'metatype', 'resources', 24]; + yield ['skills', 'magic', 'metatype', 'resources', 'attributes', 12]; + yield ['magic', 'metatype', 'resources', 'attributes', 'skills', 14]; + yield ['metatype', 'resources', 'attributes', 'skills', 'magic', 16]; + yield ['resources', 'attributes', 'skills', 'magic', 'metatype', 20]; } #[DataProvider('unspentStandardAttributesProvider')]