NotQuiteParadise2

Source code for nqp.world.controllers.event_controller

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

import pygame

from nqp.base_classes.animation import Animation
from nqp.base_classes.controller import Controller
from nqp.base_classes.image import Image
from nqp.command.troupe import Troupe
from nqp.command.unit import Unit
from nqp.core.constants import DEFAULT_IMAGE_SIZE, EventState
from nqp.core.debug import Timer

if TYPE_CHECKING:
    from typing import Any, Dict, Tuple, Union

    from nqp.core.game import Game
    from nqp.scenes.world.scene import WorldScene

__all__ = ["EventController"]


[docs]class EventController(Controller): """ Event game functionality and event-only data. * Modify game state in accordance with game rules * Do not draw anything """
[docs] def __init__(self, game: Game, parent_scene: WorldScene): with Timer("EventController: initialised"): super().__init__(game, parent_scene) self.state: EventState = EventState.IDLE self.active_event: Dict = {} self.event_resources: Dict = {} # resources needed for the event self.current_index: int = 0 # the event option chosen
[docs] def update(self, delta_time: float): pass
[docs] def reset(self): self.state = EventState.IDLE self.active_event = {} self.event_resources = {} self.current_index = 0 self.load_random_event()
[docs] def load_random_event(self): self.active_event = self.get_random_event() logging.info(f"Event {self.active_event['type']} loaded.") self._load_event_resources()
[docs] def load_event(self, event_id: str, remove_from_pool: bool = False): """ Load a specific event. """ event = self.get_event(event_id, remove_from_pool) self.active_event = event logging.info(f"Event {self.active_event['type']} loaded.") self._load_event_resources()
def _load_event_resources(self): self.event_resources = {} for resource_str in self.active_event["resources"]: key, value, target = self.parse_event_string(resource_str) resource = self._generate_event_resource(key, target) if resource is not None: self.event_resources[value] = resource else: event_type = self.active_event["type"] logging.critical(f"Event resources ({event_type}:{key}) returned none and was ignored.") def _generate_event_resource(self, resource_key: str, resource_target: str) -> Any: """ Create a resource based on the given key. """ resource = None if resource_key == "existing_unit": unit = None units = list(self._parent_scene.model.player_troupe.units.values()) # shuffle list self._game.rng.shuffle(units) # ensure a unique resource for unit in units: if unit not in self.event_resources: break resource = unit elif resource_key == "new_specific_unit": if resource_target in self._game.data.units: troupe = Troupe(self._game, "player", self._parent_scene.model.player_troupe.allies) unit_id = troupe.generate_specific_units([resource_target])[0] resource = troupe.units[unit_id] else: logging.warning( f"Unit type ({resource_target}) specified does not exist. No resource was " f"created." ) elif resource_key == "new_random_unit": if resource_target is None: tiers = None else: tiers = [int(resource_target)] troupe = Troupe(self._game, "player", self._parent_scene.model.player_troupe.allies) unit_id = troupe.generate_units(1, tiers)[0] resource = troupe.units[unit_id] return resource
[docs] def trigger_result(self): """ Trigger the result for the option at the select current_index. Results are a list of key value target strings. "result_action : result_value @ target" Example: ["Gold:10","Gold:10"] - would add 10 gold twice. """ logging.info( f"Choose option {self.current_index}, " f"{self.active_event['options'][self.current_index]['text']}." ) for result in self.active_event["options"][self.current_index]["result"]: key, value, target = self.parse_event_string(result) self._action_result(key, value, target)
[docs] @staticmethod def parse_event_string(result: str) -> Tuple[str, str, str]: """ Break event string into component parts. Applicable for Conditions, Resources and Results. (or anything that has a syntax of key:value@target. Returns a tuple of key, value, target. """ key, result_remainder = result.split(":", 1) if "@" in result_remainder: value, target = result_remainder.split("@", 1) else: value = result_remainder target = None return key, value, target
def _action_result(self, result_key: str, result_value: str, target: str): """ Resolve the action from the result. Not all actions need a target and in those cases the target is ignored. """ if result_key == "gold": original_value = self._parent_scene.model.gold self._parent_scene.model.amend_gold(int(result_value)) logging.info(f"Gold changed by {result_value}; {original_value} -> {self._parent_scene.model.gold}.") elif result_key == "rations": original_value = self._parent_scene.model.rations self._parent_scene.model.amend_rations(int(result_value)) logging.info(f"Rations changed by {result_value}; {original_value} -> {self._parent_scene.model.rations}.") elif result_key == "morale": original_value = self._parent_scene.model.morale self._parent_scene.model.amend_morale(int(result_value)) logging.info(f"Morale changed by {result_value}; {original_value} -> {self._parent_scene.model.morale}.") elif result_key == "charisma": original_value = self._parent_scene.model.charisma self._parent_scene.model.amend_charisma(int(result_value)) logging.info( f"Charisma changed by {result_value}; {original_value} -> " f"{self._parent_scene.model.charisma}. " ) elif result_key == "leadership": original_value = self._parent_scene.model.leadership self._parent_scene.model.amend_leadership(int(result_value)) logging.info( f"Leadership changed by {result_value}; {original_value} -> " f"{self._parent_scene.model.leadership}. " ) elif result_key == "injury": try: resource = self.event_resources[target] assert isinstance(resource, Unit) for count in range(0, int(result_value)): # TODO - add injury allocation pass logging.warning( f"Injuries don't yet exist, but {resource.type}:{resource.id} should have been " f"given {result_value} of them just now." ) except KeyError: logging.warning( f"Target specified ({target}) is not found in resources ({self.event_resources})" f" and was ignored." ) elif result_key == "unlock_event": # add flag to show unlocked self._game.memory.flags.append(result_value + "_unlocked") self.prioritise_event(result_value) elif result_key == "add_unit_resource": try: unit = self.event_resources[result_value] # check doesnt already exist if unit not in self._parent_scene.model.player_troupe.units: self._parent_scene.model.player_troupe.add_unit(unit) else: logging.warning(f"Target specified ({target}) is an existing unit and was therefore ignored.") except KeyError: logging.warning( f"Target specified ({target}) is not found in resources ({self.event_resources})" f" and was ignored." ) elif result_key == "add_specific_unit": if result_value in self._game.data.units: self._parent_scene.model.player_troupe.generate_specific_units([result_value]) else: logging.warning(f"Unit type ({result_value}) specified does not exist. No unit was added.") else: logging.warning(f"Result key specified ({result_key}) is not known and was ignored.")
[docs] def get_result_image(self, result_key: str, result_value: str, result_target: str) -> Union[Image, Animation]: """ Get an image for the result key given. """ icon_size = pygame.Vector2(DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE) if result_key == "gold": image = self._game.visual.get_image("gold", icon_size) elif result_key == "rations": image = self._game.visual.get_image("rations", icon_size) elif result_key == "morale": image = self._game.visual.get_image("morale", icon_size) elif result_key == "charisma": image = self._game.visual.get_image("charisma", icon_size) elif result_key == "leadership": image = self._game.visual.get_image("leadership", icon_size) elif result_key == "injury": image = self._game.visual.get_image("injury", icon_size) elif result_key == "add_unit_resource": unit = self.event_resources[result_value] image = self._game.visual.create_animation(unit.type, "icon") elif result_key == "add_specific_unit": image = self._game.visual.create_animation(result_value, "icon") else: logging.warning(f"Result key not recognised. Image not found used.") image = self._game.visual.get_image("not_found", icon_size) return image
[docs] def get_random_event(self) -> Dict: """ Get a random event from those available. This event is then removed from the list of possible events. """ possible_events = [] possible_events_occur_rates = [] # priority or non-priority if len(self._parent_scene.model.priority_events) >= 1: chance_of_priority = 33 * self._parent_scene.model.turns_since_priority_event if self._game.rng.roll() < chance_of_priority: events = self._parent_scene.model.priority_events self._parent_scene.model.turns_since_priority_event = 0 # reset count else: events = self._parent_scene.model.event_deck self._parent_scene.model.turns_since_priority_event += 1 # increment count else: events = self._parent_scene.model.event_deck # grab events and occur rate for event in events.values(): if self._check_event_conditions(event): possible_events.append(event) occur_rate = self._game.data.get_event_occur_rate(event["type"]) possible_events_occur_rates.append(occur_rate) # choose an event event_ = self._game.rng.choices(possible_events, possible_events_occur_rates)[0] events.pop(event_["type"]) return event_
[docs] def get_event(self, event_id: str, remove_from_pool: bool): """ Get a specific event. remove_from_pool determines whether the event is removed from the list of possible events. """ try: if remove_from_pool: event = self._parent_scene.model.event_deck.pop(event_id) else: event = self._parent_scene.model.event_deck[event_id] return event except KeyError: logging.error(f"Event ID ({event_id}) not found.") raise Exception
def _check_event_conditions(self, event: Dict) -> bool: """ Return true if all an event's conditions are met. """ conditions = event["conditions"] results = [] for condition in conditions: key, condition_remainder = condition.split(":", 1) if "@" in condition_remainder: value, target = condition_remainder.split("@", 1) else: value = condition_remainder target = None results.append(self._check_event_condition(key, value, target)) if all(results): outcome = True else: outcome = False return outcome def _check_event_condition(self, condition_key: str, condition_value: str, target: str) -> bool: """ Check the condition given. Not all conditions use a target and in those cases the target is ignored. """ outcome = False if condition_key == "flag": if self._game.memory.check_for_flag(condition_value): outcome = True else: outcome = False else: logging.critical(f"Condition key specified ({condition_key}) is not known and was ignored.") return outcome
[docs] def prioritise_event(self, event_type: str): """ Move an event from the event_deck to the priority events. """ try: event = self._parent_scene.model.event_deck.pop(event_type) self._parent_scene.model.priority_events[event_type] = event except KeyError: logging.critical(f"Event ({event_type}) specified not found in event_deck and was ignored.")