dcs_liberation/qt_ui/widgets/map/QLiberationMap.py
2020-10-04 12:24:31 -07:00

384 lines
15 KiB
Python

from typing import Dict, List, Optional, Tuple
from PySide2.QtCore import Qt
from PySide2.QtGui import QBrush, QColor, QPen, QPixmap, QWheelEvent
from PySide2.QtWidgets import (
QFrame,
QGraphicsItem,
QGraphicsOpacityEffect,
QGraphicsScene,
QGraphicsView,
)
from dcs import Point
from dcs.mapping import point_from_heading
import qt_ui.uiconstants as CONST
from game import Game, db
from game.data.radar_db import UNITS_WITH_RADAR
from gen import Conflict
from gen.flights.flight import Flight
from qt_ui.models import GameModel
from qt_ui.widgets.map.QLiberationScene import QLiberationScene
from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
from qt_ui.widgets.map.QFrontLine import QFrontLine
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from theater import ControlPoint, FrontLine
class QLiberationMap(QGraphicsView):
WAYPOINT_SIZE = 4
instance = None
display_rules: Dict[str, bool] = {
"cp": True,
"go": True,
"lines": True,
"events": True,
"sam": True,
"flight_paths": False
}
def __init__(self, game_model: GameModel):
super(QLiberationMap, self).__init__()
QLiberationMap.instance = self
self.game_model = game_model
self.game: Optional[Game] = game_model.game
self.flight_path_items: List[QGraphicsItem] = []
self.setMinimumSize(800,600)
self.setMaximumHeight(2160)
self._zoom = 0
self.factor = 1
self.factorized = 1
self.init_scene()
self.connectSignals()
self.setGame(game_model.game)
GameUpdateSignal.get_instance().flight_paths_changed.connect(
lambda: self.draw_flight_plans(self.scene())
)
def init_scene(self):
scene = QLiberationScene(self)
self.setScene(scene)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setBackgroundBrush(QBrush(QColor(30, 30, 30)))
self.setFrameShape(QFrame.NoFrame)
self.setDragMode(QGraphicsView.ScrollHandDrag)
def connectSignals(self):
GameUpdateSignal.get_instance().gameupdated.connect(self.setGame)
def setGame(self, game: Optional[Game]):
self.game = game
print("Reloading Map Canvas")
if self.game is not None:
self.reload_scene()
"""
Uncomment to set up theather reference points
def keyPressEvent(self, event):
#super(QLiberationMap, self).keyPressEvent(event)
numpad_mod = int(event.modifiers()) & QtCore.Qt.KeypadModifier
i = 0
for k,v in self.game.theater.reference_points.items():
if i == 0:
point_0 = k
else:
point_1 = k
i = i + 1
if event.key() == QtCore.Qt.Key_Down:
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0] + 10, self.game.theater.reference_points[point_0][1]
if event.key() == QtCore.Qt.Key_Up:
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0] - 10, self.game.theater.reference_points[point_0][1]
if event.key() == QtCore.Qt.Key_Left:
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0], self.game.theater.reference_points[point_0][1] + 10
if event.key() == QtCore.Qt.Key_Right:
self.game.theater.reference_points[point_0] = self.game.theater.reference_points[point_0][0], self.game.theater.reference_points[point_0][1] - 10
if event.key() == QtCore.Qt.Key_2 and numpad_mod:
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0] + 10, self.game.theater.reference_points[point_1][1]
if event.key() == QtCore.Qt.Key_8 and numpad_mod:
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0] - 10, self.game.theater.reference_points[point_1][1]
if event.key() == QtCore.Qt.Key_4 and numpad_mod:
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0], self.game.theater.reference_points[point_1][1] + 10
if event.key() == QtCore.Qt.Key_6 and numpad_mod:
self.game.theater.reference_points[point_1] = self.game.theater.reference_points[point_1][0], self.game.theater.reference_points[point_1][1] - 10
print(self.game.theater.reference_points)
self.reload_scene()
"""
def reload_scene(self):
scene = self.scene()
scene.clear()
playerColor = self.game.get_player_color()
enemyColor = self.game.get_enemy_color()
self.addBackground()
# Uncomment below to help set up theater reference points
#for i, r in enumerate(self.game.theater.reference_points.items()):
# text = scene.addText(str(r), font=QFont("Trebuchet MS", 10, weight=5, italic=False))
# text.setPos(0, i * 24)
for cp in self.game.theater.controlpoints:
pos = self._transform_point(cp.position)
scene.addItem(QMapControlPoint(self, pos[0] - CONST.CP_SIZE / 2,
pos[1] - CONST.CP_SIZE / 2,
CONST.CP_SIZE,
CONST.CP_SIZE, cp, self.game_model))
if cp.captured:
pen = QPen(brush=CONST.COLORS[playerColor])
brush = CONST.COLORS[playerColor+"_transparent"]
else:
pen = QPen(brush=CONST.COLORS[enemyColor])
brush = CONST.COLORS[enemyColor+"_transparent"]
added_objects = []
for ground_object in cp.ground_objects:
if ground_object.obj_name in added_objects:
continue
go_pos = self._transform_point(ground_object.position)
if not ground_object.airbase_group:
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 14, 12, cp, ground_object, self.game, buildings))
if ground_object.category == "aa" and self.get_display_rule("sam"):
max_range = 0
has_radar = False
if ground_object.groups:
for g in ground_object.groups:
for u in g.units:
unit = db.unit_type_from_name(u.type)
if unit in UNITS_WITH_RADAR:
has_radar = True
if unit.threat_range > max_range:
max_range = unit.threat_range
if has_radar:
scene.addEllipse(go_pos[0] - max_range/300.0 + 8, go_pos[1] - max_range/300.0 + 8, max_range/150.0, max_range/150.0, CONST.COLORS["white_transparent"], CONST.COLORS["grey_transparent"])
added_objects.append(ground_object.obj_name)
for cp in self.game.theater.enemy_points():
if self.get_display_rule("lines"):
self.scene_create_lines_for_cp(cp, playerColor, enemyColor)
for cp in self.game.theater.player_points():
if self.get_display_rule("lines"):
self.scene_create_lines_for_cp(cp, playerColor, enemyColor)
self.draw_flight_plans(scene)
for cp in self.game.theater.controlpoints:
pos = self._transform_point(cp.position)
text = scene.addText(cp.name, font=CONST.FONT_MAP)
text.setPos(pos[0] + CONST.CP_SIZE, pos[1] - CONST.CP_SIZE / 2)
text = scene.addText(cp.name, font=CONST.FONT_MAP)
text.setDefaultTextColor(Qt.white)
text.setPos(pos[0] + CONST.CP_SIZE + 1, pos[1] - CONST.CP_SIZE / 2 + 1)
def draw_flight_plans(self, scene) -> None:
for item in self.flight_path_items:
try:
scene.removeItem(item)
except RuntimeError:
# Something may have caused those items to already be removed.
pass
self.flight_path_items.clear()
if not self.get_display_rule("flight_paths"):
return
for package in self.game_model.ato_model.packages:
for flight in package.flights:
self.draw_flight_plan(scene, flight)
def draw_flight_plan(self, scene: QGraphicsScene, flight: Flight) -> None:
is_player = flight.from_cp.captured
pos = self._transform_point(flight.from_cp.position)
self.draw_waypoint(scene, pos, is_player)
prev_pos = tuple(pos)
for point in flight.points:
new_pos = self._transform_point(Point(point.x, point.y))
self.draw_flight_path(scene, prev_pos, new_pos, is_player)
self.draw_waypoint(scene, new_pos, is_player)
prev_pos = tuple(new_pos)
self.draw_flight_path(scene, prev_pos, pos, is_player)
def draw_waypoint(self, scene: QGraphicsScene, position: Tuple[int, int],
player: bool) -> None:
waypoint_pen = self.waypoint_pen(player)
waypoint_brush = self.waypoint_brush(player)
self.flight_path_items.append(scene.addEllipse(
position[0], position[1], self.WAYPOINT_SIZE,
self.WAYPOINT_SIZE, waypoint_pen, waypoint_brush
))
def draw_flight_path(self, scene: QGraphicsScene, pos0: Tuple[int, int],
pos1: Tuple[int, int], player: bool):
flight_path_pen = self.flight_path_pen(player)
# Draw the line to the *middle* of the waypoint.
offset = self.WAYPOINT_SIZE // 2
self.flight_path_items.append(scene.addLine(
pos0[0] + offset, pos0[1] + offset,
pos1[0] + offset, pos1[1] + offset,
flight_path_pen
))
def scene_create_lines_for_cp(self, cp: ControlPoint, playerColor, enemyColor):
scene = self.scene()
pos = self._transform_point(cp.position)
for connected_cp in cp.connected_points:
pos2 = self._transform_point(connected_cp.position)
if not cp.captured:
color = CONST.COLORS["dark_"+enemyColor]
elif cp.captured:
color = CONST.COLORS["dark_"+playerColor]
else:
color = CONST.COLORS["dark_"+enemyColor]
pen = QPen(brush=color)
pen.setColor(color)
pen.setWidth(6)
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
if not cp.captured:
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
else:
posx, h = Conflict.frontline_position(self.game.theater, cp, connected_cp)
pos2 = self._transform_point(posx)
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
p1 = point_from_heading(pos2[0], pos2[1], h+180, 25)
p2 = point_from_heading(pos2[0], pos2[1], h, 25)
scene.addItem(QFrontLine(p1[0], p1[1], p2[0], p2[1],
FrontLine(cp, connected_cp)))
else:
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
def wheelEvent(self, event: QWheelEvent):
if event.angleDelta().y() > 0:
factor = 1.25
self._zoom += 1
if self._zoom < 10:
self.scale(factor, factor)
self.factorized *= factor
else:
self._zoom = 9
else:
factor = 0.8
self._zoom -= 1
if self._zoom > -5:
self.scale(factor, factor)
self.factorized *= factor
else:
self._zoom = -4
#print(self.factorized, factor, self._zoom)
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]
point_b = list(self.game.theater.reference_points.keys())[1]
point_b_img = self.game.theater.reference_points[point_b]
Y_dist = point_a_img[0] - point_b_img[0]
lon_dist = point_a[1] - point_b[1]
X_dist = point_a_img[1] - point_b_img[1]
lat_dist = point_b[0] - point_a[0]
Y_scale = float(Y_dist) / float(lon_dist)
X_scale = float(X_dist) / float(lat_dist)
# ---
Y_offset = p.x - point_a[0]
X_offset = p.y - point_a[1]
X = point_b_img[1] + X_offset * X_scale
Y = point_a_img[0] - Y_offset * Y_scale
#X += 5
#Y += 5
return X > treshold and X or treshold, Y > treshold and Y or treshold
def base_faction_color_name(self, player: bool) -> str:
if player:
return self.game.get_player_color()
else:
return self.game.get_enemy_color()
def waypoint_pen(self, player: bool) -> QPen:
name = self.base_faction_color_name(player)
return QPen(brush=CONST.COLORS[name])
def waypoint_brush(self, player: bool) -> QColor:
name = self.base_faction_color_name(player)
return CONST.COLORS[f"{name}_transparent"]
def flight_path_pen(self, player: bool) -> QPen:
name = self.base_faction_color_name(player)
color = CONST.COLORS[name]
pen = QPen(brush=color)
pen.setColor(color)
pen.setWidth(1)
pen.setStyle(Qt.DashDotLine)
return pen
def addBackground(self):
scene = self.scene()
bg = QPixmap("./resources/" + self.game.theater.overview_image)
scene.addPixmap(bg)
# Apply graphical effects to simulate current daytime
if self.game.current_turn_daytime == "day":
pass
elif self.game.current_turn_daytime == "night":
ov = QPixmap(bg.width(), bg.height())
ov.fill(CONST.COLORS["night_overlay"])
overlay = scene.addPixmap(ov)
effect = QGraphicsOpacityEffect()
effect.setOpacity(0.7)
overlay.setGraphicsEffect(effect)
else:
ov = QPixmap(bg.width(), bg.height())
ov.fill(CONST.COLORS["dawn_dust_overlay"])
overlay = scene.addPixmap(ov)
effect = QGraphicsOpacityEffect()
effect.setOpacity(0.3)
overlay.setGraphicsEffect(effect)
@staticmethod
def set_display_rule(rule: str, value: bool):
QLiberationMap.display_rules[rule] = value
QLiberationMap.instance.reload_scene()
QLiberationMap.instance.update()
@staticmethod
def get_display_rules() -> Dict[str, bool]:
return QLiberationMap.display_rules
@staticmethod
def get_display_rule(rule) -> bool:
return QLiberationMap.display_rules[rule]