mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'develop' into you_won
This commit is contained in:
commit
61ecdfc48c
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
|
||||
|
||||
@ -204,7 +204,10 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
tacan_callsign = self.tacan_callsign()
|
||||
icls = next(self.icls_alloc)
|
||||
|
||||
brc = self.steam_into_wind(ship_group)
|
||||
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)
|
||||
self._register_unit_group(group, ship_group)
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
self.scene().removeItem(self.movement_line)
|
||||
try:
|
||||
self.scene().removeItem(self.movement_line)
|
||||
except:
|
||||
pass
|
||||
self.selected_cp = None
|
||||
@ -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")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user