Current section

Files

Jump to
ex_ttrpg_dev lib characters.ex
Raw

lib/characters.ex

defmodule ExTTRPGDev.Characters do
@moduledoc """
This module handles handles character operations
"""
alias ExTTRPGDev.Characters.Character
alias ExTTRPGDev.Characters.Metadata
alias ExTTRPGDev.Dice
alias ExTTRPGDev.Globals
alias ExTTRPGDev.RuleSystem.Evaluator
alias ExTTRPGDev.RuleSystems.LoadedSystem
@doc """
Get the file path for a character
## Examples
iex> Characters.character_file_path!(%Character{metadata: %Characters.Metadata{slug: "mr_whiskers"}})
"mr_whiskers.json"
"""
def character_file_path!(%Character{metadata: %Metadata{slug: slug}}) do
character_file_path!(slug)
end
def character_file_path!(character_slug) when is_bitstring(character_slug) do
Path.join(Globals.characters_path(), "#{character_slug}.json")
end
@doc """
Returns a boolean as to whether the character exists on disk
## Examples
iex> Characters.character_exists?(%Characters.Character{name: "This Character exists"})
true
iex> Characters.character_exists?("this_character_exists")
true
iex> Characters.Character_exists?(%Characters.Character{name: "This character doesn't exist})
false
iex> Characters.Character_exists?("this_character_doesnt_exist")
false
"""
def character_exists?(character) do
character
|> character_file_path!
|> File.exists?()
end
@doc """
Saves the given character to disk. Error is raised if character already exists unless `overwrite` is set to true
## Example
iex> Characters.save_character!(%Characters.Character{name: "doesn't exist yet"})
:ok
iex> Characters.save_character!(%Characters.Character{name: "exists already"})
:error, :character already exists
iex> Characters.save_character!(%Characters.Character{name: "exists already"}, true)
:ok
"""
def save_character!(
%Character{} = character,
overwrite \\ false
) do
if character_exists?(character) and not overwrite do
raise "Character named #{character.name} already exsts. To overwrite, pass `overwrite` as true"
else
File.mkdir_p!(Globals.characters_path())
File.write!(
character_file_path!(character),
Poison.encode!(Character.to_json_map(character))
)
end
end
@doc """
List saved characters
## Example
iex> Characters.list_characters!()
[%Character{}, %Character{}, ...]
"""
def list_characters!() do
if File.exists?(Globals.characters_path()) do
File.ls!(Globals.characters_path())
|> Enum.map(fn x -> String.trim_trailing(x, ".json") end)
else
[]
end
end
@doc """
Load a saved character
## Example
iex> Character.load_character!("misu_park_the_cat")
%Character{}
"""
def load_character!(character_slug) do
character_file_path!(character_slug)
|> File.read!()
|> Character.from_json!()
end
@doc """
Rolls for a concept using the roll definition attached to its type in the system config.
Looks up a roll definition (from the system's `roll` concept type) whose `target_type`
matches `type_id`, then rolls the specified dice and adds the resolved value of
`bonus_field` for the given concept.
Returns a map with `:type_id`, `:concept_id`, `:dice` (spec string), `:rolls` (list of
individual die results), `:bonus`, and `:total`.
Raises if no roll is defined for the given concept type, or if the bonus field cannot
be resolved for the concept.
"""
def concept_roll!(%LoadedSystem{} = system, %Character{} = character, type_id, concept_id) do
roll_def =
system.concept_metadata
|> Enum.find(fn {{type, _id}, meta} ->
type == "roll" and meta["target_type"] == type_id
end)
unless roll_def do
raise "No roll defined for concept type \"#{type_id}\" in system \"#{system.module.slug}\""
end
{_key, %{"dice" => dice_str, "bonus_field" => bonus_field}} = roll_def
effects = system.effects ++ character.effects
resolved = Evaluator.evaluate!(system, character.generated_values, effects)
bonus_key = {type_id, concept_id, bonus_field}
unless Map.has_key?(resolved, bonus_key) do
raise "Concept \"#{type_id}('#{concept_id}')\" not found in system \"#{system.module.slug}\""
end
bonus = resolved[bonus_key]
rolls = Dice.roll(dice_str)
%{
type_id: type_id,
concept_id: concept_id,
dice: dice_str,
rolls: rolls,
bonus: bonus,
total: Enum.sum(rolls) + bonus
}
end
end