1
0
Fork 0
taleweave-ai/taleweave/utils/effect.py

303 lines
8.4 KiB
Python
Raw Permalink Normal View History

from logging import getLogger
from typing import List, Literal
from taleweave.models.effect import (
BooleanEffectPattern,
BooleanEffectResult,
EffectPattern,
EffectResult,
FloatEffectPattern,
FloatEffectResult,
IntEffectPattern,
IntEffectResult,
StringEffectPattern,
StringEffectResult,
)
from taleweave.models.entity import Attributes, Character
from taleweave.utils.attribute import (
add_value,
append_value,
multiply_value,
prepend_value,
)
from .random import resolve_float_range, resolve_int_range, resolve_string_list
logger = getLogger(__name__)
def effective_boolean(attributes: Attributes, effect: BooleanEffectResult) -> bool:
"""
Apply a boolean effect to a value.
"""
if effect.set is not None:
return effect.set
value = attributes.get(effect.name, False)
if effect.toggle is not None:
return not value
return bool(value)
def effective_float(attributes: Attributes, effect: FloatEffectResult) -> float:
"""
Apply a float effect to a value.
"""
if effect.set is not None:
return effect.set
value = attributes.get(effect.name, 0.0)
if effect.offset is not None:
value = add_value(value, effect.offset)
if effect.multiply is not None:
value = multiply_value(value, effect.multiply)
return float(value)
def effective_int(attributes: Attributes, effect: IntEffectResult) -> int:
"""
Apply an integer effect to a value.
"""
if effect.set is not None:
return effect.set
value = attributes.get(effect.name, 0)
if effect.offset is not None:
value = add_value(value, effect.offset)
if effect.multiply is not None:
value = multiply_value(value, effect.multiply)
return int(value)
def effective_string(attributes: Attributes, effect: StringEffectResult) -> str:
"""
Apply a string effect to a value.
"""
if effect.set:
return effect.set
value = attributes.get(effect.name, "")
if effect.append:
value = append_value(value, effect.append)
if effect.prepend:
value = prepend_value(value, effect.prepend)
return str(value)
def effective_attributes(
effects: List[EffectResult], base_attributes: Attributes
) -> Attributes:
"""
Apply an effect to a set of attributes.
"""
attributes = base_attributes.copy()
for effect in effects:
for attribute in effect.attributes:
if isinstance(attribute, BooleanEffectResult):
attributes[attribute.name] = effective_boolean(attributes, attribute)
elif isinstance(attribute, FloatEffectResult):
attributes[attribute.name] = effective_float(attributes, attribute)
elif isinstance(attribute, IntEffectResult):
attributes[attribute.name] = effective_int(attributes, attribute)
elif isinstance(attribute, StringEffectResult):
attributes[attribute.name] = effective_string(attributes, attribute)
else:
raise ValueError(f"Invalid operation: {attribute.operation}")
return attributes
def resolve_boolean_effect(effect: BooleanEffectPattern) -> BooleanEffectResult:
"""
Apply a boolean effect pattern to a set of attributes.
"""
return BooleanEffectResult(
name=effect.name,
set=effect.set,
toggle=effect.toggle,
)
def resolve_float_effect(effect: FloatEffectPattern) -> FloatEffectResult:
"""
Apply a float effect pattern to a set of attributes.
"""
return FloatEffectResult(
name=effect.name,
set=resolve_float_range(effect.set),
offset=resolve_float_range(effect.offset),
multiply=resolve_float_range(effect.multiply),
)
def resolve_int_effect(effect: IntEffectPattern) -> IntEffectResult:
"""
Apply an integer effect pattern to a set of attributes.
"""
return IntEffectResult(
name=effect.name,
set=resolve_int_range(effect.set),
offset=resolve_int_range(effect.offset),
multiply=resolve_float_range(effect.multiply),
)
def resolve_string_effect(effect: StringEffectPattern) -> StringEffectResult:
"""
Apply a string effect pattern to a set of attributes.
"""
return StringEffectResult(
name=effect.name,
set=resolve_string_list(effect.set),
append=resolve_string_list(effect.append),
prepend=resolve_string_list(effect.prepend),
)
def resolve_effects(effects: List[EffectPattern]) -> List[EffectResult]:
"""
Generate results for a set of effect patterns, rolling all of the random values.
"""
results = []
for effect in effects:
attributes = []
for attribute in effect.attributes:
if isinstance(attribute, BooleanEffectPattern):
result = resolve_boolean_effect(attribute)
elif isinstance(attribute, FloatEffectPattern):
result = resolve_float_effect(attribute)
elif isinstance(attribute, IntEffectPattern):
result = resolve_int_effect(attribute)
elif isinstance(attribute, StringEffectPattern):
result = resolve_string_effect(attribute)
else:
raise ValueError(f"Invalid operation: {attribute.operation}")
attributes.append(result)
duration = resolve_int_range(effect.duration)
result = EffectResult(
name=effect.name,
description=effect.description,
duration=duration,
attributes=attributes,
)
results.append(result)
return results
def is_active_effect(effect: EffectResult) -> bool:
"""
Determine if an effect is active.
"""
return effect.duration is None or effect.duration > 0
def apply_permanent_results(
attributes: Attributes, effects: List[EffectResult]
) -> Attributes:
"""
Permanently apply a set of effects to a set of attributes.
"""
for effect in effects:
for attribute in effect.attributes:
if isinstance(attribute, BooleanEffectResult):
attributes[attribute.name] = effective_boolean(attributes, attribute)
elif isinstance(attribute, FloatEffectResult):
attributes[attribute.name] = effective_float(attributes, attribute)
elif isinstance(attribute, IntEffectResult):
attributes[attribute.name] = effective_int(attributes, attribute)
elif isinstance(attribute, StringEffectResult):
attributes[attribute.name] = effective_string(attributes, attribute)
else:
raise ValueError(f"Invalid operation: {attribute.operation}")
return attributes
def apply_permanent_effects(
attributes: Attributes, effects: List[EffectPattern]
) -> Attributes:
"""
Permanently apply a set of effects to a set of attributes.
"""
results = resolve_effects(effects)
return apply_permanent_results(attributes, results)
def apply_effects(target: Character, effects: List[EffectPattern]) -> None:
"""
Apply a set of effects to a character and their attributes.
"""
permanent_effects = [
effect for effect in effects if effect.application == "permanent"
]
permanent_effects = resolve_effects(permanent_effects)
target.attributes = apply_permanent_results(target.attributes, permanent_effects)
temporary_effects = [
effect for effect in effects if effect.application == "temporary"
]
temporary_effects = resolve_effects(temporary_effects)
target.active_effects.extend(temporary_effects)
def expire_effects(target: Character) -> None:
"""
Decrement the duration of effects on a character and remove any that have expired.
"""
for effect in target.active_effects:
if effect.duration is not None:
effect.duration -= 1
target.active_effects[:] = [
effect for effect in target.active_effects if is_active_effect(effect)
]
def is_effect_ready(
effect: EffectPattern, current_turn: int
) -> Literal["ready", "cooldown", "exhausted"]:
"""
Determine if an effect is ready to be used.
"""
if effect.cooldown is not None and effect.last_used is not None:
if (effect.last_used + effect.cooldown) >= current_turn:
return "cooldown"
if effect.uses is not None:
if effect.uses <= 0:
return "exhausted"
return "ready"