import dataclasses from map.quest_map import MapEncounter, QuestMap, EncounterType, QuestPath, BattleEncounter from typing import List, Optional from map.treasure_hoard import TreasureType, Treasure _ENCOUNTER_ICONS = { EncounterType.BATTLE_REGULAR: 'noun_Battle_3647629_cropped.png', EncounterType.BATTLE_ELITE: 'noun_Skull_3820295_cropped.png', EncounterType.BATTLE_BOSS: 'noun_dragon_3820621_cropped.png', EncounterType.TOWN_TAVERN: 'noun_Beer_3054928_cropped.png', EncounterType.TOWN_DOCKS: 'noun_Caravel_2421659_cropped.png', EncounterType.TOWN_MARKET: 'noun_Store_786819_cropped.png', EncounterType.TOWN_LIBRARY: 'noun_Library_3820590_cropped.png', EncounterType.TOWN_WORKSHOP: 'noun_Carving Tools_693475_cropped.png', EncounterType.TOWN_TOWN_HALL: 'noun_brussels town hall_3284349_cropped.png', EncounterType.TOWN_TEMPLE: 'noun_temple_2382601_cropped.png', EncounterType.TOWN_REST: 'noun_Tent_3814999_cropped.png', EncounterType.TOWN_ROAD: 'noun_Hiking Trail_2686687_cropped.png', } _PATH_ICONS = { QuestPath.FORWARD: 'arrow_down.png', QuestPath.FORWARD_LEFT: 'arrow_down_left.png', QuestPath.FORWARD_RIGHT: 'arrow_down_right.png', } _TREASURE_ICONS = { TreasureType.GOLD: 'noun_coin_48892_cropped.png', TreasureType.INK: 'noun_Ink_2509717_cropped.png', TreasureType.COG: 'noun_cogs_442166_cropped.png', TreasureType.DEED: 'noun_deed_3309438_cropped.png', TreasureType.ART_OBJECT: 'noun_Art_3410337_cropped.png', TreasureType.GEMS: 'noun_Diamond_3820185_cropped.png', TreasureType.CONSUMABLE: 'noun_potion_34882_cropped.png', TreasureType.NOBILITY_TITLE: 'noun_Crown_1936588_cropped.png', TreasureType.LIFE: 'noun_life_3553107_cropped.png', TreasureType.RELIC: 'noun_Ancient Egyptian religion_248077_cropped.png', } @dataclasses.dataclass(frozen=True) class RenderedElement: description: str src: str x: int y: int w: int h: int secondary: Optional['SecondaryRenderedElement'] = None @dataclasses.dataclass(frozen=True) class SecondaryRenderedElement(RenderedElement): text: str = None text_x: int = None text_y: int = None @dataclasses.dataclass(frozen=True) class RenderedEncounter(RenderedElement): pass @dataclasses.dataclass(frozen=True) class RenderedPath(RenderedElement): pass @dataclasses.dataclass(frozen=True) class RenderedMap: encounters: List[RenderedEncounter] paths: List[RenderedPath] def render_map(quest_map: QuestMap) -> RenderedMap: rendered_encounters = [] row: List[MapEncounter] for r, row in enumerate(quest_map.encounters): for c, encounter in enumerate(row): rendered_encounter = _render_encounter(r, c, encounter) if rendered_encounter is not None: rendered_encounters.append(rendered_encounter) rendered_paths = [] row: List[QuestPath] for r, row in enumerate(quest_map.paths): for c, path in enumerate(row): rendered_path = _render_path(r, c, path) if rendered_path is not None: rendered_paths.append(rendered_path) return RenderedMap(rendered_encounters, rendered_paths) _LEFT = 20 _TOP = 20 _ENCOUNTER_W = 40 _ENCOUNTER_H = 40 _ARROW_W = 40 _ARROW_H = 20 _PADDING_W = 15 _PADDING_V = 10 _ENCOUNTER_COLUMN_W = _ENCOUNTER_W + _PADDING_W _PATH_COLUMN_W = _ARROW_W + _PADDING_W _COLUMN_W = _ENCOUNTER_COLUMN_W + _PATH_COLUMN_W _ROW_H = _ENCOUNTER_H + _PADDING_V + _ARROW_H + _PADDING_V _SECONDARY_W = 24 _SECONDARY_H = 24 _SECONDARY_PADDING = 6 def _render_encounter(r: int, c: int, encounter: MapEncounter) -> Optional[RenderedEncounter]: if encounter.type == EncounterType.INACCESSIBLE: return None icon = f'map/{_ENCOUNTER_ICONS[encounter.type]}' x = _LEFT + _COLUMN_W * c y = _TOP + _ROW_H * r w = _ENCOUNTER_W h = _ENCOUNTER_H if isinstance(encounter, BattleEncounter): x -= (_SECONDARY_PADDING + _SECONDARY_W) / 2 secondary = _render_battle_encounter_secondary_info(encounter, x, y, w, h) else: secondary = None return RenderedEncounter( description=encounter.description, src=icon, x=x, y=y, w=w, h=h, secondary=secondary ) def _render_path(r: int, c: int, path: QuestPath) -> Optional[RenderedPath]: if path == QuestPath.NO_PATH: return None icon = f'map/{_PATH_ICONS[path]}' x = _LEFT + _ENCOUNTER_COLUMN_W * ((c + 1) // 2) + _PATH_COLUMN_W * (c // 2) y = _TOP + (_ENCOUNTER_H + _PADDING_V) + _ROW_H * r w = _ARROW_W h = _ARROW_H return RenderedPath( description=path.description, src=icon, x=x, y=y, w=w, h=h, ) def _render_battle_encounter_secondary_info(encounter: BattleEncounter, x, y, w, h) -> Optional[RenderedElement]: treasure: Treasure = encounter.treasure_hoard.revealed return SecondaryRenderedElement( description=encounter.treasure_hoard.description, src = f'map/{_TREASURE_ICONS[treasure.treasure_type]}', x=x + w + _SECONDARY_PADDING, y=y, w=_SECONDARY_W, h=_SECONDARY_H, secondary=None, text=treasure.amount, text_x=x + w + _SECONDARY_PADDING, text_y=y + _SECONDARY_H )