Merge pull request #43 from Khopa/sdl_map
Scrollable & Zoomable Map view
44
__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
|
||||
|
||||
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 260 KiB |
BIN
resources/ui/aa.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
resources/ui/ammo.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
resources/ui/cleared.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
resources/ui/comms.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
resources/ui/factory.png
Normal file
|
After Width: | Height: | Size: 220 B |
BIN
resources/ui/farp.png
Normal file
|
After Width: | Height: | Size: 253 B |
BIN
resources/ui/fob.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
resources/ui/fuel.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
resources/ui/oil.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
resources/ui/power.png
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
resources/ui/target.png
Normal file
|
After Width: | Height: | Size: 230 B |
BIN
resources/ui/warehouse.png
Normal file
|
After Width: | Height: | Size: 232 B |
@ -1 +1 @@
|
||||
Subproject commit 54eab60f228847f2d92e344e6885eca855354f41
|
||||
Subproject commit fae126689132d643d317252adfb03184042a0ded
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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("<WM_DELETE_WINDOW>", 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, "<Button-1>", 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, "<Button-1>", 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, "<Button-1>", 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
|
||||
|
||||
|
||||
101
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()
|
||||
|
||||