Merge branch 'develop' into you_won

This commit is contained in:
walterroach 2020-12-09 18:12:18 -06:00
commit 61ecdfc48c
8 changed files with 203 additions and 48 deletions

View File

@ -1,11 +1,17 @@
# 2.3.0
# Features/Improvements
* **[Campaign Map]** Overhauled the campaign model
* **[Campaign Map]** Possible to add FOB as control points
* **[Campaign AI]** Overhauled AI recruiting behaviour
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
* **[Mission Generator]** Infantry squads on frontline can have manpads
* **[Flight Planner]** Added fighter sweep missions.
* **[Flight Planner]** Added BAI missions.
* **[Flight Planner]** Added anti-ship missions.
* **[Flight Planner]** Differentiated BARCAP and TARCAP. TARCAP is now for hostile areas and will arrive before the package.
* **[Culling]** Added possibility to include/exclude carriers from culling zones
* **[QOL]** On liberation startup, your latest save game is loaded automatically
## Fixes :
* **[Map]** Missiles sites now have a proper icon and will not re-use the SAM sites icon

View File

@ -7,45 +7,31 @@ from typing import Optional
_dcs_saved_game_folder: Optional[str] = None
_file_abs_path = None
def setup(user_folder: str):
global _dcs_saved_game_folder
_dcs_saved_game_folder = user_folder
_file_abs_path = os.path.join(base_path(), "default.liberation")
def base_path() -> str:
global _dcs_saved_game_folder
assert _dcs_saved_game_folder
return _dcs_saved_game_folder
def _save_file() -> str:
return os.path.join(base_path(), "default.liberation")
def _temporary_save_file() -> str:
return os.path.join(base_path(), "tmpsave.liberation")
def _autosave_path() -> str:
return os.path.join(base_path(), "autosave.liberation")
def _save_file_exists() -> bool:
return os.path.exists(_save_file())
def mission_path_for(name: str) -> str:
return os.path.join(base_path(), "Missions", "{}".format(name))
def restore_game():
if not _save_file_exists():
return None
with open(_save_file(), "rb") as f:
try:
save = pickle.load(f)
return save
except Exception:
logging.exception("Invalid Save game")
return None
def load_game(path):
with open(path, "rb") as f:
try:

View File

@ -438,6 +438,10 @@ class ConflictTheater:
if self.is_on_land(point):
return False
for exclusion_zone in self.landmap[1]:
if poly_contains(point.x, point.y, exclusion_zone):
return False
for sea in self.landmap[2]:
if poly_contains(point.x, point.y, sea):
return True

View File

@ -29,7 +29,7 @@ from .theatergroundobject import (
EwrGroundObject,
SamGroundObject,
TheaterGroundObject,
VehicleGroupGroundObject,
VehicleGroupGroundObject, GenericCarrierGroundObject,
)
from ..weather import Conditions
@ -443,6 +443,20 @@ class ControlPoint(MissionTarget, ABC):
if runway_status is not None:
runway_status.process_turn()
# Process movements for ships control points group
if self.target_position is not None:
delta = self.target_position - self.position
self.position = self.target_position
self.target_position = None
# Move the linked unit groups
for ground_object in self.ground_objects:
if isinstance(ground_object, GenericCarrierGroundObject):
for group in ground_object.groups:
for u in group.units:
u.position.x = u.position.x + delta.x
u.position.y = u.position.y + delta.y
class Airfield(ControlPoint):

View File

@ -204,6 +204,9 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
tacan_callsign = self.tacan_callsign()
icls = next(self.icls_alloc)
if self.control_point.target_position is not None:
brc = self.steam_to_target_position(ship_group)
else:
brc = self.steam_into_wind(ship_group)
self.activate_beacons(ship_group, tacan, tacan_callsign, icls)
self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls)
@ -248,6 +251,10 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
return brc
return None
def steam_to_target_position(self, group: ShipGroup) -> Optional[int]:
group.add_waypoint(self.control_point.target_position)
return group.position.heading_between_point(self.control_point.target_position)
def tacan_callsign(self) -> str:
raise NotImplementedError

View File

@ -8,6 +8,7 @@ from game import persistency
global __dcs_saved_game_directory
global __dcs_installation_directory
global __last_save_file
PREFERENCES_FILE_PATH = "liberation_preferences.json"
@ -15,6 +16,7 @@ PREFERENCES_FILE_PATH = "liberation_preferences.json"
def init():
global __dcs_saved_game_directory
global __dcs_installation_directory
global __last_save_file
if os.path.isfile(PREFERENCES_FILE_PATH):
try:
@ -22,10 +24,15 @@ def init():
pref_data = json.loads(prefs.read())
__dcs_saved_game_directory = pref_data["saved_game_dir"]
__dcs_installation_directory = pref_data["dcs_install_dir"]
if "last_save_file" in pref_data:
__last_save_file = pref_data["last_save_file"]
else:
__last_save_file = ""
is_first_start = False
except:
__dcs_saved_game_directory = ""
__dcs_installation_directory = ""
__last_save_file = ""
is_first_start = True
else:
try:
@ -52,11 +59,18 @@ def setup(saved_game_dir, install_dir):
persistency.setup(__dcs_saved_game_directory)
def setup_last_save_file(last_save_file):
global __last_save_file
__last_save_file = last_save_file
def save_config():
global __dcs_saved_game_directory
global __dcs_installation_directory
global __last_save_file
pref_data = {"saved_game_dir": __dcs_saved_game_directory,
"dcs_install_dir": __dcs_installation_directory}
"dcs_install_dir": __dcs_installation_directory,
"last_save_file": __last_save_file}
with(open(PREFERENCES_FILE_PATH, "w")) as prefs:
prefs.write(json.dumps(pref_data))
@ -71,6 +85,15 @@ def get_saved_game_dir():
return __dcs_saved_game_directory
def get_last_save_file():
global __last_save_file
print(__last_save_file)
if os.path.exists(__last_save_file):
return __last_save_file
else:
return None
def replace_mission_scripting_file():
install_dir = get_dcs_install_directory()
mission_scripting_path = os.path.join(install_dir, "Scripts", "MissionScripting.lua")

View File

@ -6,7 +6,7 @@ import math
from typing import Iterable, Iterator, List, Optional, Tuple
from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import QPointF, Qt, QLineF
from PySide2.QtCore import QPointF, Qt, QLineF, QRectF
from PySide2.QtGui import (
QBrush,
QColor,
@ -27,14 +27,12 @@ from dcs.mapping import point_from_heading
import qt_ui.uiconstants as CONST
from game import Game, db
from game.theater import ControlPoint, Enum, NavalControlPoint
from game.theater import ControlPoint, Enum
from game.theater.conflicttheater import FrontLine
from game.theater.theatergroundobject import (
EwrGroundObject,
MissileSiteGroundObject,
TheaterGroundObject,
)
from game.utils import meter_to_feet
from game.utils import meter_to_feet, nm_to_meter, meter_to_nm
from game.weather import TimeOfDay
from gen import Conflict
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
@ -47,6 +45,7 @@ from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
MAX_SHIP_DISTANCE = 80
def binomial(i: int, n: int) -> float:
"""Binomial coefficient"""
@ -155,6 +154,9 @@ class QLiberationMap(QGraphicsView):
update_flight_selection
)
self.nm_to_pixel_ratio: int = 0
def init_scene(self):
scene = QLiberationScene(self)
self.setScene(scene)
@ -168,6 +170,7 @@ class QLiberationMap(QGraphicsView):
self.game = game
if self.game is not None:
logging.debug("Reloading Map Canvas")
self.nm_to_pixel_ratio = self.km_to_pixel(float(nm_to_meter(1)) / 1000.0)
self.reload_scene()
"""
@ -322,11 +325,10 @@ class QLiberationMap(QGraphicsView):
self.draw_ground_objects(scene, cp)
if cp.target_position is not None:
tpos = cp.target_position
scene.addLine(QLineF(QPointF(pos[0], pos[1]), QPointF(tpos[0], tpos[1])),
proj = self._transform_point(cp.target_position)
scene.addLine(QLineF(QPointF(pos[0], pos[1]), QPointF(proj[0], proj[1])),
QPen(CONST.COLORS["green"], width=10, s=Qt.DashDotLine))
for cp in self.game.theater.enemy_points():
if DisplayOptions.lines:
self.scene_create_lines_for_cp(cp, playerColor, enemyColor)
@ -534,8 +536,38 @@ class QLiberationMap(QGraphicsView):
)
)
def wheelEvent(self, event: QWheelEvent):
def draw_scale(self, scale_distance_nm=20, number_of_points=4):
PADDING = 14
POS_X = 0
POS_Y = 10
BIG_LINE = 5
SMALL_LINE = 2
dist = self.km_to_pixel(nm_to_meter(scale_distance_nm)/1000.0)
self.scene().addRect(POS_X, POS_Y-PADDING, PADDING*2 + dist, BIG_LINE*2+3*PADDING, pen=CONST.COLORS["black"], brush=CONST.COLORS["black"])
l = self.scene().addLine(POS_X + PADDING, POS_Y + BIG_LINE*2, POS_X + PADDING + dist, POS_Y + BIG_LINE*2)
text = self.scene().addText("0nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False))
text.setPos(POS_X, POS_Y + BIG_LINE*2)
text.setDefaultTextColor(Qt.white)
text2 = self.scene().addText(str(scale_distance_nm) + "nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False))
text2.setPos(POS_X + dist, POS_Y + BIG_LINE * 2)
text2.setDefaultTextColor(Qt.white)
l.setPen(CONST.COLORS["white"])
for i in range(number_of_points+1):
d = float(i)/float(number_of_points)
if i == 0 or i == number_of_points:
h = BIG_LINE
else:
h = SMALL_LINE
l = self.scene().addLine(POS_X + PADDING + d * dist, POS_Y + BIG_LINE*2, POS_X + PADDING + d * dist, POS_Y + BIG_LINE - h)
l.setPen(CONST.COLORS["white"])
def wheelEvent(self, event: QWheelEvent):
if event.angleDelta().y() > 0:
factor = 1.25
self._zoom += 1
@ -553,9 +585,7 @@ class QLiberationMap(QGraphicsView):
else:
self._zoom = -4
#print(self.factorized, factor, self._zoom)
def _transform_point(self, p: Point, treshold=30) -> Tuple[int, int]:
def _transform_point(self, p: Point) -> Tuple[int, int]:
point_a = list(self.game.theater.reference_points.keys())[0]
point_a_img = self.game.theater.reference_points[point_a]
@ -578,14 +608,35 @@ class QLiberationMap(QGraphicsView):
X = point_b_img[1] + X_offset * X_scale
Y = point_a_img[0] - Y_offset * Y_scale
#X += 5
#Y += 5
return X, Y
return X > treshold and X or treshold, Y > treshold and Y or treshold
def _scene_to_dcs_coords(self, p: Point):
pa = list(self.game.theater.reference_points.keys())[0]
pa2 = self.game.theater.reference_points[pa]
pb = list(self.game.theater.reference_points.keys())[1]
pb2 = self.game.theater.reference_points[pb]
def _scene_to_dcs_coords(self, x, y):
pass
dy2 = pa2[0] - pb2[0]
dy = pa[1] - pb[1]
dx2 = pa2[1] - pb2[1]
dx = pb[0] - pa[0]
ys = float(dy2) / float(dy)
xs = float(dx2) / float(dx)
X = ((float(p.x - pb2[1])) / float(xs)) + pa[1]
Y = ((float(pa2[0] - p.y)) / float(ys)) + pa[0]
return Y, X
def km_to_pixel(self, km):
p1 = Point(0, 0)
p2 = Point(0, 1000*km)
p1a = Point(*self._transform_point(p1))
p2a = Point(*self._transform_point(p2))
return p1a.distance_to_point(p2a)
def highlight_color(self, transparent: Optional[bool] = False) -> QColor:
return QColor(255, 255, 0, 20 if transparent else 255)
@ -672,6 +723,32 @@ class QLiberationMap(QGraphicsView):
poly = QPolygonF([QPointF(*self._transform_point(Point(point[0], point[1]))) for point in exclusion_zone])
scene.addPolygon(poly, CONST.COLORS["grey"], CONST.COLORS["dark_dark_grey"])
# Uncomment to display plan projection test
#self.projection_test(scene)
self.draw_scale()
def projection_test(self, scene):
for i in range(100):
for j in range(100):
x = i * 100
y = j * 100
self.scene().addRect(QRectF(x, y, 4, 4), CONST.COLORS["green"])
proj = self._scene_to_dcs_coords(Point(x, y))
unproj = self._transform_point(Point(proj[0], proj[1]))
text = scene.addText(str(i) + ", " + str(j) + "\n" + str(unproj[0]) + ", " + str(unproj[1]),
font=QFont("Trebuchet MS", 6, weight=5, italic=False))
text.setPos(unproj[0] + 2, unproj[1] + 2)
text.setDefaultTextColor(Qt.red)
text2 = scene.addText(str(i) + ", " + str(j) + "\n" + str(x) + ", " + str(y),
font=QFont("Trebuchet MS", 6, weight=5, italic=False))
text2.setPos(x + 2, y + 10)
text2.setDefaultTextColor(Qt.green)
self.scene().addRect(QRectF(unproj[0] + 1, unproj[1] + 1, 4, 4), CONST.COLORS["red"])
if i % 2 == 0:
self.scene().addLine(QLineF(x + 1, y + 1, unproj[0], unproj[1]), CONST.COLORS["yellow"])
else:
self.scene().addLine(QLineF(x + 1, y + 1, unproj[0], unproj[1]), CONST.COLORS["purple"])
def setSelectedUnit(self, selected_cp: QMapControlPoint):
self.state = QLiberationMapState.MOVING_UNIT
self.selected_cp = selected_cp
@ -682,8 +759,19 @@ class QLiberationMap(QGraphicsView):
def sceneMouseMovedEvent(self, event: QGraphicsSceneMouseEvent):
if self.state == QLiberationMapState.MOVING_UNIT:
self.setCursor(Qt.PointingHandCursor)
pos = event.scenePos()
p1 = self.movement_line.line().p1()
self.movement_line.setLine(QLineF(p1, event.scenePos()))
distance = Point(p1.x(), p1.y()).distance_to_point(Point(pos.x(), pos.y()))
self.movement_line.setLine(QLineF(p1, pos))
if distance / self.nm_to_pixel_ratio < MAX_SHIP_DISTANCE:
self.movement_line.setPen(CONST.COLORS["green"])
else:
self.movement_line.setPen(CONST.COLORS["red"])
def sceneMousePressEvent(self, event: QGraphicsSceneMouseEvent):
if self.state == QLiberationMapState.MOVING_UNIT:
@ -694,10 +782,23 @@ class QLiberationMap(QGraphicsView):
# Set movement position for the cp
pos = event.scenePos()
point = Point(int(pos.x()), int(pos.y()))
self.selected_cp.control_point.target_position = point.x, point.y # TODO : convert to DCS coords !
proj = Point(*self._scene_to_dcs_coords(point))
# Check distance (max = 80 nm)
distance = meter_to_nm(proj.distance_to_point(self.selected_cp.control_point.position))
# Check if point is in sea
if self.game.theater.is_in_sea(proj) and distance < MAX_SHIP_DISTANCE:
self.selected_cp.control_point.target_position = proj
else:
self.selected_cp.control_point.target_position = None
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
else:
return
self.state = QLiberationMapState.NORMAL
try:
self.scene().removeItem(self.movement_line)
except:
pass
self.selected_cp = None

View File

@ -19,6 +19,7 @@ from PySide2.QtWidgets import (
import qt_ui.uiconstants as CONST
from game import Game, VERSION, persistency
from game.debriefing import Debriefing
from qt_ui import liberation_install
from qt_ui.dialogs import Dialog
from qt_ui.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
from qt_ui.models import GameModel
@ -62,7 +63,16 @@ class QLiberationWindow(QMainWindow):
self.setWindowState(Qt.WindowMaximized)
if self.game is None:
self.onGameGenerated(persistency.restore_game())
last_save_file = liberation_install.get_last_save_file()
if last_save_file:
try:
logging.info("Loading last saved game : " + str(last_save_file))
game = persistency.load_game(last_save_file)
self.onGameGenerated(game)
except:
logging.info("Error loading latest save game")
else:
logging.info("No existing save game")
else:
self.onGameGenerated(self.game)
@ -227,6 +237,8 @@ class QLiberationWindow(QMainWindow):
if self.game.savepath:
persistency.save_game(self.game)
GameUpdateSignal.get_instance().updateGame(self.game)
liberation_install.setup_last_save_file(self.game.savepath)
liberation_install.save_config()
else:
self.saveGameAs()
@ -235,6 +247,8 @@ class QLiberationWindow(QMainWindow):
if file is not None:
self.game.savepath = file[0]
persistency.save_game(self.game)
liberation_install.setup_last_save_file(self.game.savepath)
liberation_install.save_config()
def onGameGenerated(self, game: Game):
logging.info("On Game generated")