setName('Darragh Oakwanderer') ->setCharacterClass('Ranger') ->setLevel(2) ->setRace('Halfling, stout') ->setProficiencyBonus(2) ->setStrength(12) ->setDexterity(17) ->setWisdom(13) ->setSavingThrowProficiencies(['Strength', 'Dexterity']) ->setSkillProficiencies(['Sleight of Hand', 'Stealth']); } public function testAbilityModifierRoundsDown(): void { $c = new Character(); self::assertSame(-1, $c->abilityModifier(8)); self::assertSame(0, $c->abilityModifier(10)); self::assertSame(0, $c->abilityModifier(11)); self::assertSame(3, $c->abilityModifier(17)); } public function testSkillModifierAddsProficiencyOnlyWhenProficient(): void { $c = $this->darragh(); // Dex 17 -> +3, proficient (+2 prof) -> +5 self::assertSame(5, $c->skillModifier('Stealth')); // Wis 13 -> +1, not proficient in Perception self::assertSame(1, $c->skillModifier('Perception')); } public function testExpertiseDoublesProficiencyBonus(): void { $c = $this->darragh()->setSkillExpertise(['Stealth']); // Dex 17 -> +3, expertise (2 x +2 prof) -> +7 self::assertSame(7, $c->skillModifier('Stealth')); // Sleight of Hand stays plain proficient -> +5 self::assertSame(5, $c->skillModifier('Sleight of Hand')); // Expertise alone implies proficiency $c->setSkillProficiencies([])->setSkillExpertise(['Perception']); self::assertSame(5, $c->skillModifier('Perception')); // Wis +1, 2 x +2 } public function testSaveModifierAddsProficiencyOnlyWhenProficient(): void { $c = $this->darragh(); self::assertSame(3, $c->saveModifier('Strength')); // Str +1, proficient +2 => +3 self::assertSame(5, $c->saveModifier('Dexterity')); // Dex +3, proficient +2 => +5 self::assertSame(1, $c->saveModifier('Wisdom')); // Wis +1, not proficient } public function testPassivePerceptionIsTenPlusPerceptionModifier(): void { $c = $this->darragh(); // Wis +1, not proficient in Perception self::assertSame(11, $c->passivePerception()); } public function testPortraitPathStripsSpacesFromName(): void { $c = (new Character())->setName('Darragh Oakwanderer'); self::assertSame('image/characters/DarraghOakwanderer.png', $c->getPortraitPath()); } public function testMetaLineCombinesRaceClassAndLevel(): void { self::assertSame('Halfling, stout Ranger 2', $this->darragh()->getMetaLine()); self::assertSame('', (new Character())->getMetaLine()); self::assertSame('Halfling', (new Character())->setRace('Halfling')->getMetaLine()); } }