File size: 2,373 Bytes
414dc55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""Visual provider abstraction.

``CompositeProvider`` renders procedural pixel art and caches PNGs to disk so each
portrait/scene/prop is drawn once - instant, offline, deterministic, no model.
"""

from __future__ import annotations

from pathlib import Path
from typing import Protocol

from ..config import Settings
from ..constants import SPRITES_DIR
from ..schemas.visual import VisualDescriptor
from . import compositor
from .cache import cached_png


class VisualProvider(Protocol):
    is_local: bool

    def portrait_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: ...
    def portrait_sheet_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: ...
    def scene_path(self, name: str, key: str, accent: str | None = None) -> Path: ...
    def prop_path(self, name: str, key: str, accent: str | None = None) -> Path: ...


class CompositeProvider:
    is_local = True

    def __init__(self, cache_dir: Path | None = None) -> None:
        self._cache = cache_dir or (SPRITES_DIR / "cache")

    def portrait_path(self, descriptor: VisualDescriptor | None, key: str) -> Path:
        accent = descriptor.accent_color if descriptor else ""
        mood = descriptor.mood if descriptor else ""
        return cached_png(
            self._cache, ("portrait", key, mood or "", accent or ""),
            lambda: compositor.render_portrait(descriptor, key),
        )

    def portrait_sheet_path(self, descriptor: VisualDescriptor | None, key: str) -> Path:
        accent = descriptor.accent_color if descriptor else ""
        mood = descriptor.mood if descriptor else ""
        return cached_png(
            self._cache, ("sheet", key, mood or "", accent or ""),
            lambda: compositor.render_portrait_sheet(descriptor, key),
        )

    def scene_path(self, name: str, key: str, accent: str | None = None) -> Path:
        return cached_png(
            self._cache, ("scene", key, name, accent or ""),
            lambda: compositor.render_scene(name, key, accent),
        )

    def prop_path(self, name: str, key: str, accent: str | None = None) -> Path:
        return cached_png(
            self._cache, ("prop", key, name, accent or ""),
            lambda: compositor.render_prop(name, key, accent),
        )


def make_visual_provider(settings: Settings) -> VisualProvider:
    return CompositeProvider()