Current section
Files
Jump to
Current section
Files
lib/characters/character.ex
defmodule ExTTRPGDev.Characters.Character do
alias __MODULE__
alias ExTTRPGDev.Characters.Metadata
alias ExTTRPGDev.Dice
alias ExTTRPGDev.RuleSystems.LoadedSystem
@moduledoc """
Definition of an individual character.
- `generated_values` — map of `{type_id, concept_id, field_name} => integer` for leaf nodes
- `effects` — list of `%{target: {type_id, concept_id, field_name}, value: integer}`
for currently active items, feats, statuses etc. (defaults to [])
- `decisions` — list of `%{scope: nil | {type_id, concept_id}, choice: string, selection: string}`
recording each concept selection made during character creation (defaults to [])
"""
@type t :: %__MODULE__{}
defstruct [:name, :generated_values, :metadata, effects: [], decisions: []]
@doc """
Generates a character for the given loaded rule system.
Rolls dice for all generated nodes using the system's rolling methods.
Accepts a list of decisions representing concept selections made during character creation.
"""
def gen_character!(%LoadedSystem{} = system, decisions \\ []) do
character_name = Faker.Person.name()
generated_values =
system.nodes
|> Enum.filter(fn {_key, node} -> node.type == :generated end)
|> Map.new(fn {node_key, node} ->
{node_key, roll_generated_value(node, system.rolling_methods)}
end)
%Character{
name: character_name,
generated_values: generated_values,
effects: [],
decisions: decisions,
metadata: %Metadata{
slug: slugify(character_name),
rule_system: system.module.slug
}
}
end
@doc """
Encodes the character to a JSON-serializable map.
Tuple keys in `generated_values` are encoded as `"type:id:field"` strings.
Decision scopes are encoded as `null` (for nil) or `"type:id"` strings.
"""
def to_json_map(%Character{} = char) do
%{
"name" => char.name,
"generated_values" =>
Map.new(char.generated_values, fn {{type, id, field}, value} ->
{"#{type}:#{id}:#{field}", value}
end),
"effects" =>
Enum.map(char.effects, fn %{target: {type, id, field}, value: v} ->
%{"target" => "#{type}:#{id}:#{field}", "value" => v}
end),
"decisions" => Enum.map(char.decisions, &serialize_decision/1),
"metadata" => %{
"slug" => char.metadata.slug,
"rule_system" => char.metadata.rule_system
}
}
end
@doc """
Deserializes a character from its JSON string representation.
"""
def from_json!(json) when is_bitstring(json) do
map = Poison.decode!(json)
generated_values =
Map.new(map["generated_values"] || %{}, fn {key, value} ->
[type, id, field] = String.split(key, ":", parts: 3)
{{type, id, field}, value}
end)
effects =
Enum.map(map["effects"] || [], fn %{"target" => target, "value" => v} ->
[type, id, field] = String.split(target, ":", parts: 3)
%{target: {type, id, field}, value: v}
end)
decisions = Enum.map(map["decisions"] || [], &deserialize_decision/1)
%Character{
name: map["name"],
generated_values: generated_values,
effects: effects,
decisions: decisions,
metadata: %Metadata{
slug: map["metadata"]["slug"],
rule_system: map["metadata"]["rule_system"]
}
}
end
defp serialize_decision(%{scope: nil, choice: choice, selection: selection}) do
%{"scope" => nil, "choice" => choice, "selection" => selection}
end
defp serialize_decision(%{scope: {type, id}, choice: choice, selection: selection}) do
%{"scope" => "#{type}:#{id}", "choice" => choice, "selection" => selection}
end
defp deserialize_decision(%{"scope" => nil, "choice" => choice, "selection" => selection}) do
%{scope: nil, choice: choice, selection: selection}
end
defp deserialize_decision(%{"scope" => scope_str, "choice" => choice, "selection" => selection}) do
[type, id] = String.split(scope_str, ":", parts: 2)
%{scope: {type, id}, choice: choice, selection: selection}
end
defp roll_generated_value(%{method: method_id}, rolling_methods) do
method = Map.get(rolling_methods, method_id || "standard")
rolls = Dice.roll(method.dice)
rolls =
if method.drop == "lowest" do
rolls |> Enum.sort() |> tl()
else
rolls
end
Enum.sum(rolls)
end
defp slugify(name) do
name
|> String.downcase()
|> String.replace(~r/[!#$%&()*+,.:;<=>?@\^_`'{|}~-]/, "")
|> String.replace(" ", "_")
end
end