diff --git a/__init__.py b/__init__.py index e297774b..f85f9c74 100755 --- a/__init__.py +++ b/__init__.py @@ -1,21 +1,16 @@ #!/usr/bin/env python3 +import logging import os import re import sys + import dcs -import logging -import theater.caucasus -import theater.persiangulf -import theater.nevada - -import ui.window +import ui.corruptedsavemenu import ui.mainmenu import ui.newgamemenu -import ui.corruptedsavemenu - +import ui.window from game.game import Game -from theater import start_generator from userdata import persistency, logging as logging_module assert len(sys.argv) >= 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version" @@ -53,39 +48,12 @@ def is_version_compatible(save_version): w = ui.window.Window() + try: game = persistency.restore_game() if not game or not is_version_compatible(game.settings.version): new_game_menu = None # type: NewGameMenu - - def start_new_game(player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float): - if terrain == "persiangulf": - conflicttheater = theater.persiangulf.PersianGulfTheater() - elif terrain == "nevada": - conflicttheater = theater.nevada.NevadaTheater() - else: - conflicttheater = theater.caucasus.CaucasusTheater() - - if midgame: - for i in range(0, int(len(conflicttheater.controlpoints) / 2)): - conflicttheater.controlpoints[i].captured = True - - start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier) - start_generator.generate_groundobjects(conflicttheater) - game = Game(player_name=player_name, - enemy_name=enemy_name, - theater=conflicttheater) - game.budget = int(game.budget * multiplier) - game.settings.multiplier = multiplier - game.settings.sams = sams - game.settings.version = VERSION_STRING - - if midgame: - game.budget = game.budget * 4 * len(list(conflicttheater.conflicts())) - - proceed_to_main_menu(game) - - new_game_menu = ui.newgamemenu.NewGameMenu(w, start_new_game) + new_game_menu = ui.newgamemenu.NewGameMenu(w, w.start_new_game) new_game_menu.display() else: game.settings.version = VERSION_STRING diff --git a/resources/caumap.gif b/resources/caumap.gif index 2bfe9259..6c974b47 100644 Binary files a/resources/caumap.gif and b/resources/caumap.gif differ diff --git a/resources/nevada.gif b/resources/nevada.gif index c73532c5..d57b199e 100644 Binary files a/resources/nevada.gif and b/resources/nevada.gif differ diff --git a/resources/persiangulf.gif b/resources/persiangulf.gif index af08e704..88f74c1a 100644 Binary files a/resources/persiangulf.gif and b/resources/persiangulf.gif differ diff --git a/resources/ui/aa.png b/resources/ui/aa.png new file mode 100644 index 00000000..26ea05c7 Binary files /dev/null and b/resources/ui/aa.png differ diff --git a/resources/ui/ammo.png b/resources/ui/ammo.png new file mode 100644 index 00000000..c2e6ffc0 Binary files /dev/null and b/resources/ui/ammo.png differ diff --git a/resources/ui/cleared.png b/resources/ui/cleared.png new file mode 100644 index 00000000..2912f6de Binary files /dev/null and b/resources/ui/cleared.png differ diff --git a/resources/ui/comms.png b/resources/ui/comms.png new file mode 100644 index 00000000..bff1a9f9 Binary files /dev/null and b/resources/ui/comms.png differ diff --git a/resources/ui/factory.png b/resources/ui/factory.png new file mode 100644 index 00000000..0d9f3b2d Binary files /dev/null and b/resources/ui/factory.png differ diff --git a/resources/ui/farp.png b/resources/ui/farp.png new file mode 100644 index 00000000..62fef986 Binary files /dev/null and b/resources/ui/farp.png differ diff --git a/resources/ui/fob.png b/resources/ui/fob.png new file mode 100644 index 00000000..27b0ab33 Binary files /dev/null and b/resources/ui/fob.png differ diff --git a/resources/ui/fuel.png b/resources/ui/fuel.png new file mode 100644 index 00000000..1324adcb Binary files /dev/null and b/resources/ui/fuel.png differ diff --git a/resources/ui/oil.png b/resources/ui/oil.png new file mode 100644 index 00000000..548cdbab Binary files /dev/null and b/resources/ui/oil.png differ diff --git a/resources/ui/power.png b/resources/ui/power.png new file mode 100644 index 00000000..fd0260d1 Binary files /dev/null and b/resources/ui/power.png differ diff --git a/resources/ui/target.png b/resources/ui/target.png new file mode 100644 index 00000000..c93255c1 Binary files /dev/null and b/resources/ui/target.png differ diff --git a/resources/ui/warehouse.png b/resources/ui/warehouse.png new file mode 100644 index 00000000..fa7a38e1 Binary files /dev/null and b/resources/ui/warehouse.png differ diff --git a/submodules/dcs b/submodules/dcs index 54eab60f..fae12668 160000 --- a/submodules/dcs +++ b/submodules/dcs @@ -1 +1 @@ -Subproject commit 54eab60f228847f2d92e344e6885eca855354f41 +Subproject commit fae126689132d643d317252adfb03184042a0ded diff --git a/theater/caucasus.py b/theater/caucasus.py index b6506b0b..b4256a99 100644 --- a/theater/caucasus.py +++ b/theater/caucasus.py @@ -11,8 +11,8 @@ from .base import * class CaucasusTheater(ConflictTheater): terrain = caucasus.Caucasus() overview_image = "caumap.gif" - reference_points = {(-317948.32727306, 635639.37385346): (278.5, 319), - (-355692.3067714, 617269.96285781): (263, 352), } + reference_points = {(-317948.32727306, 635639.37385346): (278.5*2, 319*2), + (-355692.3067714, 617269.96285781): (263*2, 352*2), } landmap = load_landmap("resources\\caulandmap.p") daytime_map = { "dawn": (6, 9), diff --git a/theater/nevada.py b/theater/nevada.py index 66b8b350..c55b127d 100644 --- a/theater/nevada.py +++ b/theater/nevada.py @@ -9,8 +9,8 @@ from .base import * class NevadaTheater(ConflictTheater): terrain = dcs.terrain.Nevada() overview_image = "nevada.gif" - reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45, -360), - (nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440, 80), } + reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45*2, -360*2), + (nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440*2, 80*2), } landmap = load_landmap("resources\\nev_landmap.p") daytime_map = { "dawn": (4, 6), diff --git a/theater/persiangulf.py b/theater/persiangulf.py index ec55045f..6f4b0397 100644 --- a/theater/persiangulf.py +++ b/theater/persiangulf.py @@ -9,8 +9,8 @@ from .landmap import load_landmap class PersianGulfTheater(ConflictTheater): terrain = dcs.terrain.PersianGulf() overview_image = "persiangulf.gif" - reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321, 145), - (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), } + reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321*4, 145*4), + (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347*4, 82*4), } landmap = load_landmap("resources\\gulflandmap.p") daytime_map = { "dawn": (6, 8), diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index 23daec81..bd1476db 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -1,25 +1,427 @@ import os - -from tkinter import * +import platform +from threading import Thread from tkinter.ttk import * -from ui.window import * +import pygame -from game.game import * -from gen.conflictgen import Conflict -from theater.conflicttheater import * +from theater.theatergroundobject import CATEGORY_MAP +from ui.styles import STYLES +from ui.window import * class OverviewCanvas: mainmenu = None # type: ui.mainmenu.MainMenu + BRIGHT_RED = (200, 64, 64) + BRIGHT_GREEN = (64, 200, 64) + + RED = (255, 125, 125) + BLUE = (164, 164, 255) + DARK_BLUE = (45, 62, 80) + WHITE = (255, 255, 255) + GREEN = (128, 186, 128) + BLACK = (0, 0, 0) + BACKGROUND = pygame.Color(0, 64, 64) + ANTIALIASING = True + + WIDTH = 1066 + HEIGHT = 600 + + started = None + def __init__(self, frame: Frame, parent, game: Game): + self.parent = parent self.game = game - self.image = PhotoImage(file=os.path.join("resources", game.theater.overview_image)) - self.canvas = Canvas(frame, width=self.image.width(), height=self.image.height()) - self.canvas.grid(column=0, row=0, sticky=NSEW) + # Remove any previously existing pygame instance + pygame.quit() + + # Pygame objects + self.map = None + self.screen = None + self.surface: pygame.Surface = None + self.thread: Thread = None + self.clock = pygame.time.Clock() + 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.icons = {} + + # Frontline are too heavy on performance to compute in realtime, so keep them in a cache + self.frontline_vector_cache = {} + + # Map state + self.redraw_required = True + self.zoom = 1 + self.scroll = [0, 0] + self.exited = False + + # Display options + self.display_ground_targets = BooleanVar(value=True) + self.display_forces = BooleanVar(value=True) + self.display_bases = BooleanVar(value=True) + self.display_road = BooleanVar(value=True) + self.display_rules = self.compute_display_rules() + + parent.window.tk.protocol("", self.on_close) + + self.wrapper = Frame(frame, **STYLES["frame-wrapper"]) + self.wrapper.grid(column=0, row=0, sticky=NSEW) # Adds grid + self.wrapper.pack(side=LEFT) # packs window to the left + + self.embed = Frame(self.wrapper, width=self.WIDTH, height=self.HEIGHT, borderwidth=2, **STYLES["frame-wrapper"]) + self.embed.grid(column=0, row=0, sticky=NSEW) # Adds grid + + self.options = Frame(self.wrapper, borderwidth=2, **STYLES["frame-wrapper"]) + self.options.grid(column=0, row=1, sticky=NSEW) + self.build_map_options_panel() + + self.init_sdl_layer() + self.init_sdl_thread() + + def build_map_options_panel(self): + + def force_redraw(): + if self.screen: + self.redraw_required = True + self.draw() + + col = 0 + Label(self.options, text="Bases", **STYLES["widget"]).grid(row=0, column=col, sticky=W) + Checkbutton(self.options, variable=self.display_bases, **STYLES["radiobutton"]).grid(row=0, column=col + 1, + sticky=E) + Separator(self.options, orient=VERTICAL).grid(row=0, column=col + 2, sticky=NS) + col += 3 + Label(self.options, text="Roads", **STYLES["widget"]).grid(row=0, column=col, sticky=W) + Checkbutton(self.options, variable=self.display_road, **STYLES["radiobutton"]).grid(row=0, column=col + 1, + sticky=E) + Separator(self.options, orient=VERTICAL).grid(row=0, column=col + 2, sticky=NS) + col += 3 + Label(self.options, text="Strike targets", **STYLES["widget"]).grid(row=0, column=col, sticky=W) + Checkbutton(self.options, variable=self.display_ground_targets, **STYLES["radiobutton"]).grid(row=0, + column=col + 1, + sticky=E) + Separator(self.options, orient=VERTICAL).grid(row=0, column=col + 2, sticky=NS) + col += 3 + Label(self.options, text="Forces", **STYLES["widget"]).grid(row=0, column=col, sticky=W) + Checkbutton(self.options, variable=self.display_forces, **STYLES["radiobutton"]).grid(row=0, column=col + 1, + sticky=E) + Separator(self.options, orient=VERTICAL).grid(row=0, column=col + 2, sticky=NS) + col += 4 + Button(self.options, text="Toggle size", command=lambda: self.map_size_toggle(), **STYLES["btn-primary"]).grid(row=0, column=col, sticky=E, padx=(10,10)) + + def map_size_toggle(self): + if self.expanded: + self.embed.configure(width=0) + self.options.configure(width=0) + self.expanded = False + else: + self.embed.configure(width=self.WIDTH) + self.options.configure(width=self.WIDTH) + self.expanded = True + + def on_close(self): + self.exited = True + if self.thread is not None: + 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": + os.environ['SDL_VIDEODRIVER'] = 'windib' + + # Create pygame 'screen' + self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE) + self.screen.fill(pygame.Color(*self.BLACK)) + + # Load icons resources + self.icons = {} + self.icons["target"] = pygame.image.load(os.path.join("resources", "ui", "target.png")) + self.icons["cleared"] = pygame.image.load(os.path.join("resources", "ui", "cleared.png")) + for category in CATEGORY_MAP.keys(): + try: + self.icons[category] = pygame.image.load(os.path.join("resources", "ui", category + ".png")) + except: + print("Couldn't load icon for : " + category) + + # Load the map image + self.map = pygame.image.load(os.path.join("resources", self.game.theater.overview_image)).convert() + pygame.draw.rect(self.map, self.BLACK, (0, 0, self.map.get_width(), self.map.get_height()), 10) + pygame.draw.rect(self.map, self.WHITE, (0, 0, self.map.get_width(), self.map.get_height()), 5) + + # Create surfaces for drawing + self.surface = pygame.Surface((self.map.get_width(), self.map.get_height())) + self.surface.set_alpha(None) + self.overlay = pygame.Surface((self.WIDTH, self.HEIGHT), pygame.SRCALPHA) + + # Init pygame display + pygame.display.init() + pygame.display.update() + + def init_sdl_thread(self): + if OverviewCanvas.started is not None: + OverviewCanvas.started.exited = True + self.thread = Thread(target=self.sdl_thread) + self.thread.start() + OverviewCanvas.started = self + print("Started SDL app") + + def sdl_thread(self): + self.redraw_required = True + i = 0 + while not self.exited: + self.clock.tick(60) + self.draw() + i += 1 + if i == 300: + self.frontline_vector_cache = {} + i = 0 + print("Stopped SDL app") + + def draw(self): + + try: + #self.parent.window.tk.winfo_ismapped() + self.embed.winfo_ismapped() + self.embed.winfo_manager() + except: + self.exited = True + + right_down = False + left_down = False + + # Detect changes on display rules + r = self.compute_display_rules() + if r != self.display_rules: + self.display_rules = r + self.redraw_required = True + + for event in pygame.event.get(): + if event.type == pygame.MOUSEMOTION: + self.redraw_required = True + elif event.type == pygame.MOUSEBUTTONDOWN: + # Scroll wheel + if event.button == 4: + self.zoom += 0.25 + self.redraw_required = True + elif event.button == 5: + self.zoom -= 0.25 + self.redraw_required = True + + if event.button == 3: + right_down = True + pygame.mouse.get_rel() + if event.button == 1: + left_down = True + self.redraw_required = True + + # If Right click pressed + if pygame.mouse.get_pressed()[2] == 1 and not right_down: + scr = pygame.mouse.get_rel() + self.scroll[0] += scr[0] + self.scroll[1] += scr[1] + self.redraw_required = True + + if self.zoom <= 0.5: + self.zoom = 0.5 + elif self.zoom > 10: + self.zoom = 10 + + if self.redraw_required: + + # Fill + self.screen.fill(self.BACKGROUND) + self.overlay.fill(pygame.Color(0, 0, 0, 0)) + + # Surface + 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)) + + # Scaling + scaled = pygame.transform.scale(self.surface, ( + int(self.surface.get_width() * self.zoom), int(self.surface.get_height() * self.zoom))) + self.screen.blit(scaled, (self.scroll[0]*self.zoom, self.scroll[1]*self.zoom)) + self.screen.blit(self.overlay, (0, 0)) + + pygame.display.flip() + + self.redraw_required = False + + 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 + zoom_lvl = self.font.render(" x " + str(self.zoom) + " ", self.ANTIALIASING, self.WHITE, self.DARK_BLUE) + self.overlay.blit(zoom_lvl, (self.overlay.get_width()-zoom_lvl.get_width()-5, + self.overlay.get_height()-zoom_lvl.get_height()-5)) + + # Debug + # 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) + + if self.display_ground_targets.get(): + if cp.captured: + color = self._player_color() + else: + color = self._enemy_color() + for ground_object in cp.ground_objects: + 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) + if connected_cp.captured != cp.captured: + color = self._enemy_color() + elif connected_cp.captured and cp.captured: + color = self._player_color() + else: + color = self.BLACK + + 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 + + if not frontline: + continue + + frontline_pos, heading, distance = frontline + + if distance < 10000: + 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) + + 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 + + if cp.captured: + color = self._player_color() + else: + color = self._enemy_color() + + 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)) + + 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)) + + rect = pygame.Rect(coords[0] - label.get_width() / 2 + 1, coords[1] + 1, label.get_width(), + label.get_height()) + + 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)) + + 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) + hp = self.font.render("Strength : ", self.ANTIALIASING, (225, 225, 225), self.BLACK) + + armor_txt = "ARMOR > " + for key, value in controlPoint.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(): + 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(): + aa_txt += key.id + " x " + str(value) + " | " + aa = self.font.render(aa_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) + + lineheight = title.get_height() + w = max([max([a.get_width() for a in [title, armor, aircraft, aa]]), 150]) + h = 5 * lineheight + 4 * 5 + + # Draw frame + pygame.draw.rect(surface, self.GREEN, (pos[0], pos[1], w + 8, h + 8)) + pygame.draw.rect(surface, self.BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4)) + pygame.draw.rect(surface, self.GREEN, (pos[0] + 2, pos[1], w + 4, lineheight + 4)) + + # Title + surface.blit(title, (pos[0] + 4, 4 + pos[1])) + surface.blit(hp, (pos[0] + 4, 4 + pos[1] + lineheight + 5)) + + # Draw gauge + pygame.draw.rect(surface, self.WHITE, + (pos[0] + hp.get_width() + 3, 4 + pos[1] + lineheight + 5, 54, lineheight)) + 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)) + + # Text + surface.blit(armor, (pos[0] + 4, 4 + pos[1] + lineheight * 2 + 10)) + surface.blit(aircraft, (pos[0] + 4, 4 + pos[1] + lineheight * 3 + 15)) + 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) + rect = pygame.Rect(x, y, 16, 16) + + if ground_object.is_dead: + surface.blit(self.icons["cleared"], (x, y)) + else: + if ground_object.category in self.icons.keys(): + icon = self.icons[ground_object.category] + else: + icon = self.icons["target"] + surface.blit(icon, (x, y)) + + if rect.collidepoint(*mouse_pos): + self.draw_ground_object_info(ground_object, (x, y), color, surface) + + def draw_ground_object_info(self, ground_object: TheaterGroundObject, pos, color, surface: pygame.Surface): + 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): point_a = list(self.game.theater.reference_points.keys())[0] @@ -46,99 +448,20 @@ class OverviewCanvas: return X > treshold and X or treshold, Y > treshold and Y or treshold - def create_cp_title(self, coords, cp: ControlPoint): - title = cp.name - font = ("Helvetica", 10) - - id = self.canvas.create_text(coords[0], coords[1], text=title, font=font) - self.canvas.tag_bind(id, "", self.display(cp)) - - id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font) - self.canvas.tag_bind(id, "", self.display(cp)) - def _player_color(self): - return self.game.player == "USA" and "blue" or "red" + return self.game.player == "USA" and self.BLUE or self.RED def _enemy_color(self): - return self.game.player == "USA" and "red" or "blue" + return self.game.player == "USA" and self.RED or self.BLUE def update(self): - self.canvas.delete(ALL) - self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image) + self.draw() - for cp in self.game.theater.controlpoints: - for ground_object in cp.ground_objects: - x, y = self.transform_point(ground_object.position) - self.canvas.create_text(x, - y, - text=".", - fill="black" if ground_object.is_dead else self._enemy_color(), - font=("Helvetica", 18)) - - coords = self.transform_point(cp.position) - for connected_cp in cp.connected_points: - 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: - color = self._player_color() - else: - color = "black" - - self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color) - - if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp): - frontline = Conflict.frontline_vector(cp, connected_cp, self.game.theater) - if not frontline: - continue - - frontline_pos, heading, distance = frontline - if distance < 10000: - 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) - - self.canvas.create_line((*start_coords, *end_coords), width=2, fill=color) - - for cp in self.game.theater.controlpoints: - coords = self.transform_point(cp.position) - arc_size = 16 * math.pow(cp.importance, 1) - extent = max(cp.base.strength * 180, 10) - start = (180 - extent) / 2 - - if cp.captured: - color = self._player_color() - else: - color = self._enemy_color() - - cp_id = self.canvas.create_arc((coords[0] - arc_size/2, coords[1] - arc_size/2), - (coords[0] + arc_size/2, coords[1] + arc_size/2), - fill=color, - style=PIESLICE, - start=start, - extent=extent) - - """ - #For debugging purposes - - for r in cp.radials: - p = self.transform_point(cp.position.point_from_heading(r, 20000)) - self.canvas.create_text(p[0], p[1], text="{}".format(r)) - continue - """ - - self.canvas.tag_bind(cp_id, "", self.display(cp)) - self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp) - - units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) - self.canvas.create_text(coords[0]+1, coords[1] - arc_size / 1.5 +1, text=units_title, font=("Helvetica", 8), fill=color) - self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 8), fill="white") + def compute_display_rules(self): + return sum([1 if a.get() else 0 for a in [self.display_forces, self.display_road, self.display_bases, self.display_ground_targets]]) def display(self, cp: ControlPoint): def action(_): return self.parent.go_cp(cp) return action - diff --git a/ui/window.py b/ui/window.py index f7766013..841d23ef 100644 --- a/ui/window.py +++ b/ui/window.py @@ -1,8 +1,14 @@ from tkinter import * +from tkinter import Menu as TkMenu +from tkinter import messagebox from game.game import * +from theater import persiangulf, nevada, caucasus, start_generator from .styles import BG_COLOR,BG_TITLE_COLOR +import sys +import webbrowser class Window: + image = None left_pane = None # type: Frame right_pane = None # type: Frame @@ -15,6 +21,33 @@ class Window: self.tk.grid_columnconfigure(0, weight=1) self.tk.grid_rowconfigure(0, weight=1) + self.frame = None + self.right_pane = None + self.left_pane = None + self.build() + + menubar = TkMenu(self.tk) + filemenu = TkMenu(menubar, tearoff=0) + filemenu.add_command(label="New Game", command=lambda: self.new_game_confirm()) + filemenu.add_separator() + filemenu.add_command(label="Exit", command=lambda: self.exit()) + menubar.add_cascade(label="File", menu=filemenu) + + helpmenu = TkMenu(menubar, tearoff=0) + helpmenu.add_command(label="Online Manual", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Manual")) + helpmenu.add_command(label="Troubleshooting Guide", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting")) + helpmenu.add_command(label="Modding Guide", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial")) + helpmenu.add_separator() + helpmenu.add_command(label="Contribute", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation")) + helpmenu.add_command(label="Forum Thread", command=lambda: webbrowser.open_new_tab("https://forums.eagle.ru/showthread.php?t=214834")) + helpmenu.add_command(label="Report an issue", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/issues")) + menubar.add_cascade(label="Help", menu=helpmenu) + + self.tk.config(menu=menubar) + self.tk.focus() + + + def build(self): self.frame = Frame(self.tk, bg=BG_COLOR) self.frame.grid(column=0, row=0, sticky=NSEW) self.frame.grid_columnconfigure(0) @@ -29,8 +62,6 @@ class Window: self.right_pane = Frame(self.frame, bg=BG_COLOR) self.right_pane.grid(row=0, column=1, sticky=NSEW) - self.tk.focus() - def clear_right_pane(self): for i in range(100): self.right_pane.grid_columnconfigure(1, weight=0) @@ -40,10 +71,68 @@ class Window: x.grid_remove() def clear(self): - for x in self.left_pane.winfo_children(): - x.grid_remove() - for x in self.right_pane.winfo_children(): - x.grid_remove() + def clear_recursive(x, n=50): + if n < 0: + return + for y in x.winfo_children(): + clear_recursive(y, n-1) + x.grid_forget() + + clear_recursive(self.frame, 50) + self.left_pane.grid_remove() + self.right_pane.grid_remove() + 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": + conflicttheater = nevada.NevadaTheater() + else: + conflicttheater = caucasus.CaucasusTheater() + + if midgame: + for i in range(0, int(len(conflicttheater.controlpoints) / 2)): + conflicttheater.controlpoints[i].captured = True + + start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier) + start_generator.generate_groundobjects(conflicttheater) + game = Game(player_name=player_name, + enemy_name=enemy_name, + theater=conflicttheater) + game.budget = int(game.budget * multiplier) + game.settings.multiplier = multiplier + game.settings.sams = sams + game.settings.version = "1.4.0" + + if midgame: + game.budget = game.budget * 4 * len(list(conflicttheater.conflicts())) + + self.proceed_to_main_menu(game) + + def proceed_to_main_menu(self, game: Game): + from ui.mainmenu import MainMenu + self.clear() + m = MainMenu(self, None, game) + m.display() + + def proceed_to_new_game_menu(self): + from ui.newgamemenu import NewGameMenu + self.clear() + new_game_menu = NewGameMenu(self, self.start_new_game) + new_game_menu.display() + + def new_game_confirm(self): + result = messagebox.askquestion("Start a new game", "Are you sure you want to start a new game ? Your current campaign will be overriden and there is no going back !", icon='warning') + if result == 'yes': + self.proceed_to_new_game_menu() + else: + pass + + def exit(self): + self.tk.destroy() + sys.exit(0) def run(self): self.tk.mainloop()