From 8f85101cec0ecad4eb42402f187f8bf3dd9c3e26 Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Sun, 4 Nov 2018 02:38:14 +0200 Subject: [PATCH] WIP: display events on the map; start events from adjacent CPs --- game/event/baseattack.py | 16 +-- game/event/event.py | 45 +++++--- game/event/frontlineattack.py | 16 ++- game/event/frontlinepatrol.py | 7 +- game/event/infantrytransport.py | 4 +- game/event/insurgentattack.py | 4 +- game/event/intercept.py | 14 +-- game/event/navalintercept.py | 16 +-- game/event/strike.py | 2 +- game/game.py | 13 ++- theater/conflicttheater.py | 4 - ui/eventmenu.py | 7 +- ui/mainmenu.py | 4 +- ui/overviewcanvas.py | 193 +++++++++++++++++++++----------- ui/window.py | 9 +- userdata/logging.py | 4 + 16 files changed, 221 insertions(+), 137 deletions(-) diff --git a/game/event/baseattack.py b/game/event/baseattack.py index 6a3b8601..2517a769 100644 --- a/game/event/baseattack.py +++ b/game/event/baseattack.py @@ -28,7 +28,7 @@ class BaseAttackEvent(Event): alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike]) attackers_success = alive_attackers >= alive_defenders - if self.from_cp.captured: + if self.departure_cp.captured: return attackers_success else: return not attackers_success @@ -36,14 +36,14 @@ class BaseAttackEvent(Event): def commit(self, debriefing: Debriefing): super(BaseAttackEvent, self).commit(debriefing) if self.is_successfull(debriefing): - if self.from_cp.captured: + if self.departure_cp.captured: self.to_cp.captured = True self.to_cp.ground_objects = [] self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name]) self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) else: - if not self.from_cp.captured: + if not self.departure_cp.captured: self.to_cp.captured = False self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) @@ -54,14 +54,14 @@ class BaseAttackEvent(Event): def player_defending(self, flights: db.TaskForceDict): assert CAP in flights and len(flights) == 1, "Invalid scrambled flights" - cas = self.from_cp.base.scramble_cas(self.game.settings.multiplier) - escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier) - attackers = self.from_cp.base.armor + cas = self.departure_cp.base.scramble_cas(self.game.settings.multiplier) + escort = self.departure_cp.base.scramble_sweep(self.game.settings.multiplier) + attackers = self.departure_cp.base.armor op = BaseAttackOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp) op.setup(cas=assigned_units_from(cas), @@ -79,7 +79,7 @@ class BaseAttackEvent(Event): op = BaseAttackOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp) defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier) diff --git a/game/event/event.py b/game/event/event.py index 39692148..21130a9f 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -22,18 +22,26 @@ class Event: informational = False is_awacs_enabled = False ca_slots = 0 + + game = None # type: Game + location = None # type: Point + from_cp = None # type: ControlPoint + departure_cp = None # type: ControlPoint + to_cp = None # type: ControlPoint + operation = None # type: Operation difficulty = 1 # type: int - game = None # type: Game environment_settings = None # type: EnvironmentSettings BONUS_BASE = 5 - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): + def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str): + self.game = game + self.departure_cp = None + self.from_cp = from_cp + self.to_cp = target_cp + self.location = location self.attacker_name = attacker_name self.defender_name = defender_name - self.to_cp = to_cp - self.from_cp = from_cp - self.game = game @property def is_player_attacking(self) -> bool: @@ -44,7 +52,7 @@ class Event: if self.attacker_name == self.game.player: return self.to_cp else: - return self.from_cp + return self.departure_cp @property def threat_description(self) -> str: @@ -67,11 +75,17 @@ class Event: def is_successfull(self, debriefing: Debriefing) -> bool: return self.operation.is_successfull(debriefing) - def player_attacking(self, flights: db.TaskForceDict): - assert False + def player_attacking(self, cp: ControlPoint, flights: db.TaskForceDict): + if self.is_player_attacking: + self.departure_cp = cp + else: + self.to_cp = cp - def player_defending(self, flights: db.TaskForceDict): - assert False + def player_defending(self, cp: ControlPoint, flights: db.TaskForceDict): + if self.is_player_attacking: + self.departure_cp = cp + else: + self.to_cp = cp def generate(self): self.operation.is_awacs_enabled = self.is_awacs_enabled @@ -93,7 +107,7 @@ class Event: def commit(self, debriefing: Debriefing): for country, losses in debriefing.destroyed_units.items(): if country == self.attacker_name: - cp = self.from_cp + cp = self.departure_cp else: cp = self.to_cp @@ -122,11 +136,12 @@ class UnitsDeliveryEvent(Event): units = None # type: typing.Dict[UnitType, int] def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): - super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, - defender_name=defender_name, + super(UnitsDeliveryEvent, self).__init__(game=game, + location=to_cp.position, from_cp=from_cp, - to_cp=to_cp, - game=game) + target_cp=to_cp, + attacker_name=attacker_name, + defender_name=defender_name) self.units = {} diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index a4a923b0..3201ca47 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -11,8 +11,6 @@ class FrontlineAttackEvent(Event): STRENGTH_INFLUENCE = 0.3 SUCCESS_FACTOR = 1.5 - defenders = None # type: db.ArmorDict - @property def threat_description(self): return "{} vehicles".format(self.to_cp.base.assemble_count()) @@ -20,9 +18,9 @@ class FrontlineAttackEvent(Event): @property def tasks(self) -> typing.Collection[typing.Type[Task]]: if self.is_player_attacking: - return [CAS, PinpointStrike] + return [CAS] else: - return [CAP, PinpointStrike] + return [CAP] def flight_name(self, for_task: typing.Type[Task]) -> str: if for_task == CAS: @@ -63,9 +61,8 @@ class FrontlineAttackEvent(Event): self.to_cp.base.affect_strength(-0.1) def player_attacking(self, flights: db.TaskForceDict): - assert CAS in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights" + assert CAS in flights and len(flights) == 1, "Invalid flights" - self.defenders = self.to_cp.base.assemble_attack() op = FrontlineAttackOperation(game=self.game, attacker_name=self.attacker_name, @@ -73,9 +70,10 @@ class FrontlineAttackEvent(Event): from_cp=self.from_cp, to_cp=self.to_cp) - armor = unitdict_from(flights[PinpointStrike]) - op.setup(target=self.defenders, - attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())), + defenders = self.to_cp.base.assemble_attack() + attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values())) + op.setup(target=defenders, + attackers=attackers, strikegroup=flights[CAS]) self.operation = op diff --git a/game/event/frontlinepatrol.py b/game/event/frontlinepatrol.py index ef891c3e..6f0b51d7 100644 --- a/game/event/frontlinepatrol.py +++ b/game/event/frontlinepatrol.py @@ -17,7 +17,7 @@ class FrontlinePatrolEvent(Event): @property def tasks(self): - return [CAP, PinpointStrike] + return [CAP] def flight_name(self, for_task: typing.Type[Task]) -> str: if for_task == CAP: @@ -55,7 +55,7 @@ class FrontlinePatrolEvent(Event): pass def player_attacking(self, flights: db.TaskForceDict): - assert CAP in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights" + assert CAP in flights and len(flights) == 1, "Invalid flights" self.cas = self.to_cp.base.scramble_cas(self.game.settings.multiplier) self.escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier * self.ESCORT_FACTOR) @@ -67,10 +67,11 @@ class FrontlinePatrolEvent(Event): to_cp=self.to_cp) defenders = self.to_cp.base.assemble_attack() + attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values())) op.setup(cas=assigned_units_from(self.cas), escort=assigned_units_from(self.escort), interceptors=flights[CAP], - armor_attackers=db.unitdict_restrict_count(db.unitdict_from(flights[PinpointStrike]), sum(defenders.values())), + armor_attackers=attackers, armor_defenders=defenders) self.operation = op diff --git a/game/event/infantrytransport.py b/game/event/infantrytransport.py index 59d85abe..3be47800 100644 --- a/game/event/infantrytransport.py +++ b/game/event/infantrytransport.py @@ -35,7 +35,7 @@ class InfantryTransportEvent(Event): if self.is_successfull(debriefing): self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) def player_attacking(self, flights: db.TaskForceDict): assert Embarking in flights and len(flights) == 1, "Invalid flights" @@ -44,7 +44,7 @@ class InfantryTransportEvent(Event): game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp ) diff --git a/game/event/insurgentattack.py b/game/event/insurgentattack.py index a04d2168..607d9d43 100644 --- a/game/event/insurgentattack.py +++ b/game/event/insurgentattack.py @@ -39,7 +39,7 @@ class InsurgentAttackEvent(Event): killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) all_units = sum(self.targets.values()) attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR - if self.from_cp.captured: + if self.departure_cp.captured: return attackers_success else: return not attackers_success @@ -56,7 +56,7 @@ class InsurgentAttackEvent(Event): op = InsurgentAttackOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp) op.setup(target=self.targets, strikegroup=flights[CAS]) diff --git a/game/event/intercept.py b/game/event/intercept.py index a137d9b2..c9a62ae3 100644 --- a/game/event/intercept.py +++ b/game/event/intercept.py @@ -25,7 +25,7 @@ class InterceptEvent(Event): return "Escort flight" def _enemy_scramble_multiplier(self) -> float: - is_global = self.from_cp.is_global or self.to_cp.is_global + is_global = self.departure_cp.is_global or self.to_cp.is_global return self.game.settings.multiplier * is_global and 0.5 or 1 @property @@ -34,7 +34,7 @@ class InterceptEvent(Event): def is_successfull(self, debriefing: Debriefing): units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0) - if self.from_cp.captured: + if self.departure_cp.captured: return units_destroyed > 0 else: return units_destroyed == 0 @@ -47,11 +47,11 @@ class InterceptEvent(Event): for _, cp in self.game.theater.conflicts(True): cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: # enemy attacking if self.is_successfull(debriefing): - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) @@ -71,7 +71,7 @@ class InterceptEvent(Event): op = InterceptOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp) op.setup(escort=assigned_units_from(escort), @@ -84,7 +84,7 @@ class InterceptEvent(Event): def player_defending(self, flights: db.TaskForceDict): assert CAP in flights and len(flights) == 1, "Invalid flights" - interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier) + interceptors = self.departure_cp.base.scramble_interceptors(self.game.settings.multiplier) self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) assert self.transport_unit is not None @@ -92,7 +92,7 @@ class InterceptEvent(Event): op = InterceptOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp) op.setup(escort=flights[CAP], diff --git a/game/event/navalintercept.py b/game/event/navalintercept.py index df425415..c9d5ff7e 100644 --- a/game/event/navalintercept.py +++ b/game/event/navalintercept.py @@ -33,8 +33,8 @@ class NavalInterceptEvent(Event): @property def threat_description(self): s = "{} ship(s)".format(self._targets_count()) - if not self.from_cp.captured: - s += ", {} aircraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier)) + if not self.departure_cp.captured: + s += ", {} aircraft".format(self.departure_cp.base.scramble_count(self.game.settings.multiplier)) return s def is_successfull(self, debriefing: Debriefing): @@ -44,7 +44,7 @@ class NavalInterceptEvent(Event): if unit in self.targets: destroyed_targets += count - if self.from_cp.captured: + if self.departure_cp.captured: return math.ceil(float(destroyed_targets) / total_targets) > self.SUCCESS_RATE else: return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_RATE @@ -56,11 +56,11 @@ class NavalInterceptEvent(Event): if self.is_successfull(debriefing): self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: # enemy attacking if self.is_successfull(debriefing): - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) @@ -79,7 +79,7 @@ class NavalInterceptEvent(Event): self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp ) @@ -100,11 +100,11 @@ class NavalInterceptEvent(Event): self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp ) - strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier) + strikegroup = self.departure_cp.base.scramble_cas(self.game.settings.multiplier) op.setup(strikegroup=assigned_units_from(strikegroup), interceptors=flights[CAP], targets=self.targets) diff --git a/game/event/strike.py b/game/event/strike.py index e8bea076..d538541a 100644 --- a/game/event/strike.py +++ b/game/event/strike.py @@ -49,7 +49,7 @@ class StrikeEvent(Event): self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - from_cp=self.from_cp, + from_cp=self.departure_cp, to_cp=self.to_cp ) diff --git a/game/game.py b/game/game.py index f3f003a8..20b2c004 100644 --- a/game/game.py +++ b/game/game.py @@ -129,7 +129,7 @@ class Game: # skip strikes in case of no targets return - self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self)) + self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player, self.enemy)) def _generate_enemy_event(self, event_class, player_cp, enemy_cp): if event_class in [type(x) for x in self.events if not self.is_player_attack(x)]: @@ -169,7 +169,7 @@ class Game: # skip base attack if strength is too high return - self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self)) + self.events.append(event_class(self, enemy_cp, player_cp, player_cp.position, self.enemy, self.player)) def _generate_events(self): for player_cp, enemy_cp in self.theater.conflicts(True): @@ -269,7 +269,12 @@ class Game: def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None): logging.info("Pass turn") for event in self.events: - event.skip() + if self.settings.version == "dev": + # don't damage player CPs in by skipping in dev mode + if isinstance(event, UnitsDeliveryEvent): + event.skip() + else: + event.skip() if not no_action: self._budget_player() @@ -286,5 +291,5 @@ class Game: self.events = [] # type: typing.List[Event] self._generate_events() - self._generate_globalinterceptions() + #self._generate_globalinterceptions() diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 9d7a634a..d44caf4e 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -102,9 +102,5 @@ class ConflictTheater: for connected_point in [x for x in cp.connected_points if x.captured != from_player]: yield (cp, connected_point) - for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]: - if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN: - yield (global_cp, connected_point) - def enemy_points(self) -> typing.Collection[ControlPoint]: return [point for point in self.controlpoints if not point.captured] diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 64b9c580..e8f7b530 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -20,7 +20,7 @@ class EventMenu(Menu): self.scramble_entries = {k: {} for k in self.event.tasks} if self.event.attacker_name == self.game.player: - self.base = self.event.from_cp.base + self.base = self.event.departure_cp.base else: self.base = self.event.to_cp.base @@ -195,11 +195,10 @@ class EventMenu(Menu): return if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent): - if tasks_scramble_counts.get(PinpointStrike, 0) == 0: - self.error_label["text"] = "No ground vehicles assigned to attack!" + if self.base.total_armor == 0: + self.error_label["text"] = "No ground vehicles available to attack!" return - if self.game.is_player_attack(self.event): self.event.player_attacking(flights) else: diff --git a/ui/mainmenu.py b/ui/mainmenu.py index 9117e9b6..e3527793 100644 --- a/ui/mainmenu.py +++ b/ui/mainmenu.py @@ -41,6 +41,7 @@ class MainMenu(Menu): column = 0 row = 0 + """ def label(text): nonlocal row, body frame = LabelFrame(body, **STYLES["label-frame"]) @@ -104,6 +105,7 @@ class MainMenu(Menu): label(str(event)) else: event_button(event) + """ def pass_turn(self): self.game.pass_turn(no_action=True) @@ -113,7 +115,7 @@ class MainMenu(Menu): ConfigurationMenu(self.window, self, self.game).display() def start_event(self, event) -> typing.Callable: - return lambda: EventMenu(self.window, self, self.game, event).display() + EventMenu(self.window, self, self.game, event).display() def go_cp(self, cp: ControlPoint): if not cp.captured: diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index bd1476db..98bc781c 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -10,6 +10,9 @@ from ui.styles import STYLES from ui.window import * +EVENT_DEPARTURE_MAX_DISTANCE = 250000 + + class OverviewCanvas: mainmenu = None # type: ui.mainmenu.MainMenu @@ -29,6 +32,8 @@ class OverviewCanvas: HEIGHT = 600 started = None + selected_event_info = None # type: typing.Tuple[Event, typing.Tuple[int, int]] + frontline_vector_cache = None # type: typing.Dict[str, typing.Tuple[Point, int, int]] def __init__(self, frame: Frame, parent, game: Game): @@ -47,8 +52,8 @@ class OverviewCanvas: self.expanded = True pygame.font.init() - self.font:pygame.font.SysFont = pygame.font.SysFont("arial", 15) - self.fontsmall:pygame.font.SysFont = pygame.font.SysFont("arial", 10) + self.font: pygame.font.SysFont = pygame.font.SysFont("arial", 15) + self.fontsmall: pygame.font.SysFont = pygame.font.SysFont("arial", 10) self.icons = {} # Frontline are too heavy on performance to compute in realtime, so keep them in a cache @@ -84,7 +89,6 @@ class OverviewCanvas: self.init_sdl_thread() def build_map_options_panel(self): - def force_redraw(): if self.screen: self.redraw_required = True @@ -130,7 +134,6 @@ class OverviewCanvas: self.thread.join() def init_sdl_layer(self): - # Setup pygame to run in tk frame os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id()) if platform.system == "Windows": @@ -185,9 +188,7 @@ class OverviewCanvas: print("Stopped SDL app") def draw(self): - try: - #self.parent.window.tk.winfo_ismapped() self.embed.winfo_ismapped() self.embed.winfo_manager() except: @@ -234,7 +235,6 @@ class OverviewCanvas: self.zoom = 10 if self.redraw_required: - # Fill self.screen.fill(self.BACKGROUND) self.overlay.fill(pygame.Color(0, 0, 0, 0)) @@ -243,7 +243,7 @@ class OverviewCanvas: cursor_pos = pygame.mouse.get_pos() cursor_pos = ( cursor_pos[0] / self.zoom - self.scroll[0], cursor_pos[1] / self.zoom - self.scroll[1]) - self.draw_map(self.surface, self.overlay, cursor_pos, (left_down, right_down)) + self.draw_map(self.surface, self.overlay, cursor_pos, [left_down, right_down]) # Scaling scaled = pygame.transform.scale(self.surface, ( @@ -255,9 +255,7 @@ class OverviewCanvas: self.redraw_required = False - def draw_map(self, surface: pygame.Surface, overlay: pygame.Surface, mouse_pos: (int, int), - mouse_down: (bool, bool)): - + def draw_map(self, surface: pygame.Surface, overlay: pygame.Surface, mouse_pos: (int, int), mouse_down: [bool, bool]): self.surface.blit(self.map, (0, 0)) # Display zoom level on overlay @@ -269,8 +267,7 @@ class OverviewCanvas: # pygame.draw.rect(surface, (255, 0, 255), (mouse_pos[0], mouse_pos[1], 5, 5), 2) for cp in self.game.theater.controlpoints: - - coords = self.transform_point(cp.position) + coords = self._transform_point(cp.position) if self.display_ground_targets.get(): if cp.captured: @@ -278,14 +275,13 @@ class OverviewCanvas: else: color = self._enemy_color() for ground_object in cp.ground_objects: - x, y = self.transform_point(ground_object.position) + x, y = self._transform_point(ground_object.position) pygame.draw.line(surface, color, coords, (x + 8, y + 8), 1) self.draw_ground_object(ground_object, surface, color, mouse_pos) if self.display_road.get(): - for connected_cp in cp.connected_points: - connected_coords = self.transform_point(connected_cp.position) + connected_coords = self._transform_point(connected_cp.position) if connected_cp.captured != cp.captured: color = self._enemy_color() elif connected_cp.captured and cp.captured: @@ -296,15 +292,7 @@ class OverviewCanvas: pygame.draw.line(surface, color, coords, connected_coords, 2) if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp): - - # Cache mechanism to avoid performing frontline vector computation on every frame - key = str(cp.id) + "_" + str(connected_cp.id) - if key in self.frontline_vector_cache: - frontline = self.frontline_vector_cache[key] - else: - frontline = Conflict.frontline_vector(cp, connected_cp, self.game.theater) - self.frontline_vector_cache[key] = frontline - + frontline = self._frontline_vector(cp, connected_cp) if not frontline: continue @@ -314,66 +302,79 @@ class OverviewCanvas: frontline_pos = frontline_pos.point_from_heading(heading + 180, 5000) distance = 10000 - start_coords = self.transform_point(frontline_pos, treshold=10) - end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), - treshold=60) + start_coords = self._transform_point(frontline_pos, treshold=10) + end_coords = self._transform_point(frontline_pos.point_from_heading(heading, distance), + treshold=60) pygame.draw.line(surface, color, start_coords, end_coords, 4) if self.display_bases.get(): - for cp in self.game.theater.controlpoints: - coords = self.transform_point(cp.position) - radius = 12 * math.pow(cp.importance, 1) - radius_m = radius * cp.base.strength - 2 + mouse_down = self.draw_bases(mouse_pos, mouse_down) - if cp.captured: - color = self._player_color() + mouse_down = self.draw_events(self.surface, mouse_pos, mouse_down) + + if mouse_down[0]: + self.selected_event_info = None + + def draw_bases(self, mouse_pos, mouse_down): + for cp in self.game.theater.controlpoints: + coords = self._transform_point(cp.position) + radius = 12 * math.pow(cp.importance, 1) + radius_m = radius * cp.base.strength - 2 + + if cp.captured: + color = self._player_color() + else: + color = self._enemy_color() + + pygame.draw.circle(self.surface, self.BLACK, (int(coords[0]), int(coords[1])), int(radius)) + pygame.draw.circle(self.surface, color, (int(coords[0]), int(coords[1])), int(radius_m)) + + label = self.font.render(cp.name, self.ANTIALIASING, (225, 225, 225), self.BLACK) + labelHover = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (128, 186, 128)) + labelClick = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (122, 122, 255)) + + point = coords[0] - label.get_width() / 2 + 1, coords[1] + 1 + rect = pygame.Rect(*point, label.get_width(), label.get_height()) + + if rect.collidepoint(*mouse_pos): + if mouse_down[0]: + self.surface.blit(labelClick, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) + self._selected_cp(cp) + mouse_down[0] = False else: - color = self._enemy_color() + self.surface.blit(labelHover, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) - pygame.draw.circle(surface, self.BLACK, (int(coords[0]), int(coords[1])), int(radius)) - pygame.draw.circle(surface, color, (int(coords[0]), int(coords[1])), int(radius_m)) + self.draw_base_info(self.overlay, cp, (0, 0)) + if self.selected_event_info and cp.captured and self.selected_event_info[0].location.distance_to_point(cp.position) < EVENT_DEPARTURE_MAX_DISTANCE: + pygame.draw.line(self.surface, self.WHITE, point, self.selected_event_info[1]) - label = self.font.render(cp.name, self.ANTIALIASING, (225, 225, 225), self.BLACK) - labelHover = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (128, 186, 128)) - labelClick = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (122, 122, 255)) + else: + self.surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) - rect = pygame.Rect(coords[0] - label.get_width() / 2 + 1, coords[1] + 1, label.get_width(), - label.get_height()) + if self.display_forces.get(): + units_title = " {} / {} / {} ".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) + label2 = self.fontsmall.render(units_title, self.ANTIALIASING, color, (30, 30, 30)) + self.surface.blit(label2, (coords[0] - label2.get_width() / 2, coords[1] + label.get_height() + 1)) - if rect.collidepoint(mouse_pos): - if (mouse_down[0]): - surface.blit(labelClick, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) - self.parent.go_cp(cp) - else: - surface.blit(labelHover, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) + return mouse_down - self.draw_base_info(overlay, cp, (0, 0)) - - else: - surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) - - if self.display_forces.get(): - units_title = " {} / {} / {} ".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) - label2 = self.fontsmall.render(units_title, self.ANTIALIASING, color, (30, 30, 30)) - surface.blit(label2, (coords[0] - label2.get_width() / 2, coords[1] + label.get_height() + 1)) - - def draw_base_info(self, surface: pygame.Surface, controlPoint: ControlPoint, pos): - title = self.font.render(controlPoint.name, self.ANTIALIASING, self.BLACK, self.GREEN) + def draw_base_info(self, surface: pygame.Surface, control_point: ControlPoint, pos): + title = self.font.render(control_point.name, self.ANTIALIASING, self.BLACK, self.GREEN) hp = self.font.render("Strength : ", self.ANTIALIASING, (225, 225, 225), self.BLACK) armor_txt = "ARMOR > " - for key, value in controlPoint.base.armor.items(): + for key, value in control_point.base.armor.items(): armor_txt += key.id + " x " + str(value) + " | " armor = self.font.render(armor_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) aircraft_txt = "AIRCRAFT > " - for key, value in controlPoint.base.aircraft.items(): + for key, value in control_point.base.aircraft.items(): aircraft_txt += key.id + " x " + str(value) + " | " aircraft = self.font.render(aircraft_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) aa_txt = "AA/SAM > " - for key, value in controlPoint.base.aa.items(): + for key, value in control_point.base.aa.items(): aa_txt += key.id + " x " + str(value) + " | " aa = self.font.render(aa_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) @@ -396,7 +397,7 @@ class OverviewCanvas: pygame.draw.rect(surface, self.BRIGHT_RED, (pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50, lineheight - 4)) pygame.draw.rect(surface, self.BRIGHT_GREEN, ( - pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50 * controlPoint.base.strength, lineheight - 4)) + pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50 * control_point.base.strength, lineheight - 4)) # Text surface.blit(armor, (pos[0] + 4, 4 + pos[1] + lineheight * 2 + 10)) @@ -404,7 +405,7 @@ class OverviewCanvas: surface.blit(aa, (pos[0] + 4, 4 + pos[1] + lineheight * 4 + 20)) def draw_ground_object(self, ground_object: TheaterGroundObject, surface: pygame.Surface, color, mouse_pos): - x, y = self.transform_point(ground_object.position) + x, y = self._transform_point(ground_object.position) rect = pygame.Rect(x, y, 16, 16) if ground_object.is_dead: @@ -423,7 +424,50 @@ class OverviewCanvas: lb = self.font.render(str(ground_object), self.ANTIALIASING, color, self.BLACK) surface.blit(lb, (pos[0] + 18, pos[1])) - def transform_point(self, p: Point, treshold=30) -> (int, int): + def draw_events(self, surface: pygame.Surface, mouse_pos, mouse_down): + location_point_counters = {} + + def _location_to_point(location: Point) -> typing.Tuple[int, int]: + nonlocal location_point_counters + key = str(location.x) + str(location.y) + + point = self._transform_point(location) + point = point[0], point[1] + location_point_counters.get(key, 0) * 40 + + location_point_counters[key] = location_point_counters.get(key, 0) + 1 + return point + + for event in self.game.events: + location = event.location + if isinstance(event, FrontlinePatrolEvent) or isinstance(event, FrontlineAttackEvent): + location = self._frontline_center(event.from_cp, event.to_cp) + + point = _location_to_point(location) + rect = pygame.Rect(*point, 30, 30) + pygame.draw.rect(surface, self.BLACK, rect) + + if rect.collidepoint(*mouse_pos) or self.selected_event_info == (event, point): + line = self.font.render(str(event), self.ANTIALIASING, self.WHITE, self.BLACK) + surface.blit(line, rect.center) + + if rect.collidepoint(*mouse_pos): + if mouse_down[0]: + self.selected_event_info = event, point + mouse_down[0] = False + + return mouse_down + + def _selected_cp(self, cp): + if self.selected_event_info: + event = self.selected_event_info[0] + event.departure_cp = cp + + self.selected_event_info = None + self.parent.start_event(event) + else: + self.parent.go_cp(cp) + + def _transform_point(self, p: Point, treshold=30) -> (int, int): point_a = list(self.game.theater.reference_points.keys())[0] point_a_img = self.game.theater.reference_points[point_a] @@ -448,6 +492,23 @@ class OverviewCanvas: return X > treshold and X or treshold, Y > treshold and Y or treshold + def _frontline_vector(self, from_cp: ControlPoint, to_cp: ControlPoint): + # Cache mechanism to avoid performing frontline vector computation on every frame + key = str(from_cp.id) + "_" + str(to_cp.id) + if key in self.frontline_vector_cache: + return self.frontline_vector_cache[key] + else: + frontline = Conflict.frontline_vector(from_cp, to_cp, self.game.theater) + self.frontline_vector_cache[key] = frontline + return frontline + + def _frontline_center(self, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Optional[Point]: + frontline_vector = self._frontline_vector(from_cp, to_cp) + if frontline_vector: + return frontline_vector[0].point_from_heading(frontline_vector[1], frontline_vector[2]/2) + else: + return None + def _player_color(self): return self.game.player == "USA" and self.BLUE or self.RED diff --git a/ui/window.py b/ui/window.py index 841d23ef..0b1df41f 100644 --- a/ui/window.py +++ b/ui/window.py @@ -1,12 +1,16 @@ from tkinter import * from tkinter import Menu as TkMenu from tkinter import messagebox + +from .styles import BG_COLOR,BG_TITLE_COLOR from game.game import * from theater import persiangulf, nevada, caucasus, start_generator -from .styles import BG_COLOR,BG_TITLE_COLOR +from userdata import logging as logging_module + import sys import webbrowser + class Window: image = None @@ -84,7 +88,6 @@ class Window: self.build() def start_new_game(self, player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float): - if terrain == "persiangulf": conflicttheater = persiangulf.PersianGulfTheater() elif terrain == "nevada": @@ -104,7 +107,7 @@ class Window: game.budget = int(game.budget * multiplier) game.settings.multiplier = multiplier game.settings.sams = sams - game.settings.version = "1.4.0" + game.settings.version = logging_module.version_string() if midgame: game.budget = game.budget * 4 * len(list(conflicttheater.conflicts())) diff --git a/userdata/logging.py b/userdata/logging.py index 8fd944aa..a0c75d7f 100644 --- a/userdata/logging.py +++ b/userdata/logging.py @@ -35,6 +35,10 @@ def setup_version_string(str): _version_string = str +def version_string(): + return _version_string + + if "--stdout" in sys.argv: logging.basicConfig(stream=sys.stdout, level=logging.INFO) else: