Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Modules/Dnd5e/app/Casts/AsAbilityValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Modules\Dnd5e\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use Modules\Dnd5e\ValueObjects\AbilityValue;

class AsAbilityValue implements CastsAttributes
{
/**
* Cast the given value.
* @param array<string, mixed> $attributes
*/
public function get(
Model $model,
string $key,
mixed $value,
array $attributes,
): AbilityValue {
return new AbilityValue($value);
}

/**
* Prepare the given value for storage.
* @param array<string, mixed> $attributes
*/
public function set(
Model $model,
string $key,
mixed $value,
array $attributes,
): int {
if ($value instanceof AbilityValue) {
return $value->value;
}
return (int)$value;
}
}
15 changes: 15 additions & 0 deletions Modules/Dnd5e/app/Enums/Ability.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Modules\Dnd5e\Enums;

enum Ability: string
{
case Charisma = 'charisma';
case Constitution = 'constitution';
case Dexterity = 'dexterity';
case Intelligence = 'intelligence';
case Strength = 'strength';
case Wisdom = 'wisdom';
}
30 changes: 30 additions & 0 deletions Modules/Dnd5e/app/Enums/CreatureSize.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Modules\Dnd5e\Enums;

enum CreatureSize: string
{
case Tiny = 'tiny';
case Small = 'small';
case Medium = 'medium';
case Large = 'large';
case Huge = 'huge';
case Gargantuan = 'gargantuan';

/**
* Get a side of the square amount of space a character of this size
* occupies.
*/
public function space(): float
{
return match ($this) {
self::Tiny => 2.5,
self::Small, self::Medium => 5.0,
self::Large => 10.0,
self::Huge => 15.0,
self::Gargantuan => 20.0,
};
}
}
97 changes: 59 additions & 38 deletions Modules/Dnd5e/app/Models/Character.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,57 @@

namespace Modules\Dnd5e\Models;

use App\Casts\AsEmail;
use App\Models\Character as BaseCharacter;
use App\ValueObjects\Email;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Dnd5e\Casts\AsAbilityValue;
use Modules\Dnd5e\Database\Factories\CharacterFactory;
use OutOfRangeException;
use Modules\Dnd5e\ValueObjects\AbilityValue;
use Modules\Dnd5e\ValueObjects\CharacterLevel;
use Modules\Dnd5e\ValueObjects\Wallet;
use Override;
use RuntimeException;
use Stringable;

use function floor;
use function json_encode;

use const JSON_THROW_ON_ERROR;

/**
* Representation of a D&D 5E character sheet.
* @property int $charisma
* @property int $constitution
* @property int $dexterity
* @property int $intelligence
* @property-read int $armor_class
* @property AbilityValue $charisma
* @property AbilityValue $constitution
* @property AbilityValue $dexterity
* @property int $experience_points
* @property AbilityValue $intelligence
* @property-read CharacterLevel $level
* @property string $name
* @property Email $owner
* @property int $strength
* @property AbilityValue $strength
* @property string $system
* @property int $wisdom
* @property Wallet $wallet
* @property AbilityValue $wisdom
*/
class Character extends BaseCharacter implements Stringable
{
use HasFactory;

public const int ATTRIBUTE_MIN = 1;
public const int ATTRIBUTE_MAX = 30;

/** @var array<string, mixed> */
protected $attributes = [
'system' => 'dnd5e',
];

/** @var array<string, string> */
/** @var array<string, class-string> */
protected $casts = [
'owner' => AsEmail::class,
'charisma' => AsAbilityValue::class,
'constitution' => AsAbilityValue::class,
'dexterity' => AsAbilityValue::class,
'intelligence' => AsAbilityValue::class,
'strength' => AsAbilityValue::class,
'wisdom' => AsAbilityValue::class,
];

/** @var list<string> */
Expand All @@ -55,7 +65,7 @@ class Character extends BaseCharacter implements Stringable
'constitution',
'dexterity',
'equipment',
'experience',
'experience_points',
'languages',
'hitDice',
'hitDieType',
Expand All @@ -66,6 +76,7 @@ class Character extends BaseCharacter implements Stringable
'name',
'race',
'strength',
'wallet',
'wisdom',
];

Expand All @@ -88,44 +99,54 @@ protected static function booted(): void
{
static::addGlobalScope(
'dnd5e',
function (Builder $builder): void {
static function (Builder $builder): void {
$builder->where('system', 'dnd5e');
}
);
}

/**
* Return the ability modifier for the given attribute.
* @throws OutOfRangeException If the attribute is < 1 or > 30
* @throws RuntimeException If the attribute isn't set
* Return the character's armor class.
*/
public function getAbilityModifier(string $attribute): int
protected function armorClass(): Attribute
{
if (!isset($this->attributes[$attribute])) {
throw new RuntimeException('Invalid attribute');
}

$value = $this->attributes[$attribute];
if (self::ATTRIBUTE_MIN > $value || self::ATTRIBUTE_MAX < $value) {
throw new OutOfRangeException('Attribute value is out of range');
}

return -5 + (int)floor($value / 2);
return Attribute::make(
get: function (): int {
return 10 + $this->dexterity->modifier;
},
);
}

/**
* Return the character's armor class.
* @throws OutOfRangeException If the character's dexterity is invalid
* @throws RuntimeException If the character's dexterity isn't set
*/
public function getArmorClass(): int
protected function level(): Attribute
{
return 10 + $this->getAbilityModifier('dexterity');
return Attribute::make(
get: function (): CharacterLevel {
return new CharacterLevel($this->attributes['experience_points'] ?? 0);
},
);
}

#[Override]
protected static function newFactory(): Factory
{
return CharacterFactory::new();
}

protected function wallet(): Attribute
{
return Attribute::make(
get: function (string|null $wallet): Wallet {
if (null === $wallet) {
return Wallet::make();
}
return Wallet::fromJson($wallet);
},
set: function (Wallet|string $wallet): string {
if ($wallet instanceof Wallet) {
return json_encode($wallet, JSON_THROW_ON_ERROR);
}
return $wallet;
},
);
}
}
91 changes: 91 additions & 0 deletions Modules/Dnd5e/app/Models/Race.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Modules\Dnd5e\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Modules\Dnd5e\Enums\Ability;
use Modules\Dnd5e\Enums\CreatureSize;
use Override;
use Stringable;
use Sushi\Sushi;

/**
* @property-read array<Ability, int> $ability_increases
* @property-read string $id
* @property-read string $name
* @property-read int $page
* @property-read string $parent_race
* @property-read string $ruleset
* @property-read CreatureSize $size
*/
class Race extends Model implements Stringable
{
use Sushi;

public $incrementing = false;
protected $keyType = 'string';

/** @var list<string> */
protected $fillable = [
'ability_increases',
'id',
'name',
'page',
'ruleset',
'size',
'tool_proficiencies',
'weapon_proficiencies',
];

#[Override]
public function __toString(): string
{
return $this->name;
}

/**
* @return array<string, class-string|string>
*/
#[Override]
protected function casts(): array
{
return [
'size' => CreatureSize::class,
];
}

/**
* @return array{
* ability_increases: string,
* id: string,
* name: string,
* page: int,
* parent_race: string,
* ruleset: string,
* size: string,
* tool_proficiencies: string,
* weapon_proficiencies: string
* }
*/
public function getRows(): array
{
$filename = config('dnd5e.data_path') . 'races.php';
return require $filename;
}

protected function abilityIncreases(): Attribute
{
return Attribute::make(
get: function (string $increases): array {
$increases = json_decode($increases, true);
foreach (array_keys($increases) as $ability) {
Ability::from($ability);
}
return $increases;
},
);
}
}
Loading
Loading