Compare commits

...

47 Commits
1.4_rc3 ... 1.4

Author SHA1 Message Date
Vasyl Horbachenko
e9d7ee51f3 readme upd 2018-10-22 02:29:42 +03:00
Vasyl Horbachenko
9053408e13 Merge branch 'develop' 2018-10-22 02:18:26 +03:00
Vasyl Horbachenko
8f4094ee98 number of fixes 2018-10-22 02:13:38 +03:00
Vasyl Horbachenko
933e064079 fixed debriefing crash 2018-10-14 04:30:46 +03:00
Vasyl Horbachenko
274e08dd8b quick mission debriefing fixed; adjusted constants 2018-10-14 04:09:05 +03:00
Vasyl Horbachenko
05c968edc2 added Gazelle as CAS aircraft + minor fixes 2018-10-13 23:38:26 +03:00
Vasyl Horbachenko
270820de0b added Gazelle as CAS aircraft 2018-10-13 22:50:26 +03:00
Vasyl Horbachenko
e049a97bec minor text update 2018-10-13 22:30:43 +03:00
Vasyl Horbachenko
e2306ba0f3 new weather, strike objectives placement fixes & tarawa for av8b 2018-10-13 22:28:30 +03:00
Vasyl Horbachenko
6d0f488672 updated version compatibility check 2018-10-13 04:44:59 +03:00
Vasyl Horbachenko
397f9a58cb fixed naval intercept crash; fixed wrong targets order; fixed initial waypoint being WP #1; m2k a2g ccip; fixed time being time zone offset ahead; lowered rain weather chance 2018-10-13 04:41:18 +03:00
Vasyl Horbachenko
4fc766a524 trigger fixes; strike waypoint fixes; m2k strike payload update 2018-10-13 02:36:25 +03:00
Vasyl Horbachenko
3f8b5c6c00 minor UI fix 2018-10-12 23:33:10 +03:00
Vasyl Horbachenko
ce43be0d67 display strike objectives on map; minor fixes 2018-10-12 23:31:00 +03:00
Vasyl Horbachenko
ff08888385 minor adjustment in strike objectives generation 2018-10-12 22:38:26 +03:00
Vasyl Horbachenko
6b96410ea4 updated config menu 2018-10-12 22:29:37 +03:00
Vasyl Horbachenko
f21bd10f09 number of minor bugfixes and UI improvements 2018-10-12 21:32:43 +03:00
Vasyl Horbachenko
07b35f8ee1 specify tanker callsign in briefing 2018-10-12 05:45:28 +03:00
Vasyl Horbachenko
251435ae0b debriefing results based only on IDs; fixes in strike ops 2018-10-12 05:41:52 +03:00
Vasyl Horbachenko
b81bf90319 few minor fixes 2018-10-12 03:46:53 +03:00
Vasyl Horbachenko
d6b1b8665d minor fixes 2018-10-12 03:37:32 +03:00
Vasyl Horbachenko
64bd3e6a52 removed debugging code 2018-10-12 03:17:41 +03:00
Vasyl Horbachenko
520a0f91fd UI update; enemy vehicle difficulty settings; minor adjustments 2018-10-12 03:13:33 +03:00
Vasyl Horbachenko
0015667829 new frontline position finding method; AA for strikes; other minor fixes and adjustments 2018-10-12 00:12:25 +03:00
Vasyl Horbachenko
35a7da2816 oil strike objectives correct placement; updated starting point for gulf 2018-10-11 04:42:38 +03:00
Vasyl Horbachenko
5bbf3fc49f fixed land map for gulf; ability to get logs from settings; minor trigger updates 2018-10-11 04:12:02 +03:00
Vasyl Horbachenko
7a8dfeb819 Merge remote-tracking branch 'origin/develop' into develop 2018-10-11 03:45:31 +03:00
Vasyl Horbachenko
e28a24c875 randomized strike objects with templates; forbid ground objects and vehicles placement on mountains and in forests; updated push trigger so it include player group; adjacent CP missions could be initiated from carriers 2018-10-11 03:45:20 +03:00
Vasyl Horbachenko
823c6a6137 Merge pull request #27 from Khopa/combined_arms_slots
Combined arms slots
2018-10-03 02:17:21 +03:00
Khopa
2cbe63f162 Fixed potential exception in start if user enter invalid value inside the combined arms slots entry. 2018-09-29 11:55:42 +02:00
Khopa
93d0746d3e Replaced with player faction check by coalition check 2018-09-29 11:46:15 +02:00
Khopa
8cb7c7378f Type for ca_slot_entry 2018-09-29 11:34:22 +02:00
Khopa
3500c85e8d UI for CA slot selection (align with unit count rows) 2018-09-22 13:42:51 +02:00
Khopa
c699567c73 Only generate Combined Arms "Tactical Commander" slots for player side. 2018-09-22 13:33:31 +02:00
Khopa
056c397e68 [Cherry Picked] Added possibility to add 'DCS: Combined Arms' slots to the generated mission. 2018-09-22 11:46:40 +02:00
Vasyl Horbachenko
8431c7745d fixed base atttack op 2018-09-15 00:30:40 +03:00
Vasyl Horbachenko
8df4607e50 Update README.md 2018-09-14 23:49:50 +03:00
Vasyl Horbachenko
edf9efddf9 update to ground objects parser; waypoints in briefings & general briefings update; minor fixes 2018-09-13 05:09:57 +03:00
Vasyl Horbachenko
03fc17fae6 new ground objects format & parser; place dead objects instead of removing them completely 2018-09-12 05:19:21 +03:00
Vasyl Horbachenko
6fb342a42c updated location argument; updated ground units placement during attack operation 2018-09-12 00:20:35 +03:00
Vasyl Horbachenko
262347f8c8 tweaked caucasus start times 2018-09-11 22:46:26 +03:00
Vasyl Horbachenko
1176b92073 capture armor placement tweaks 2018-09-11 17:39:23 +03:00
Vasyl Horbachenko
49f2c00d76 Update README.md 2018-09-09 04:39:53 +03:00
Vasyl Horbachenko
d284305323 Update README.md 2018-09-09 04:39:03 +03:00
Vasyl Horbachenko
c32ac8577c Update README.md 2018-09-09 04:38:47 +03:00
Vasyl Horbachenko
0d5530f5ea Update README.md 2018-08-15 15:14:19 +03:00
Vasyl Horbachenko
a528249062 Update README.md 2018-08-15 15:13:49 +03:00
71 changed files with 1183 additions and 583 deletions

View File

@@ -1,8 +1,29 @@
[DCS World](https://www.digitalcombatsimulator.com/en/products/world/) single-player liberation dynamic campaign. ![Logo](https://i.imgur.com/c2k18E1.png)
[Installation instructions/Manual](https://github.com/shdwp/dcs_liberation/wiki) [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) single-player dynamic campaign.
Inspired by *ARMA Liberation* mission.
Uses [pydcs](http://github.com/pydcs/dcs) for mission generation. Uses [pydcs](http://github.com/pydcs/dcs) for mission generation.
## Installation
1. Download and install **Python 3.6.0** package from https://www.python.org/downloads/release/python-360/ (look at the bottom under *Files*; any option will do if it matches your architecture) with default set of options (you need to have *Install launcher for all users (recommended)* checked)
1. Download archived release (https://github.com/shdwp/dcs_liberation/releases; **not source code zip**, file should be named **dcs_liberation_xx.zip**)
1. Unzip the archive somewhere. Path does not matter. **Application will not work** if you start it without extracting
1. Run **start.bat**
1. If **"Windows protected your PC"** popup appears on your computer (windows blocks any application unknown to it), you can click on **"More info"** and **"Run anyway"**
## Tutorials
* [Manual](https://github.com/shdwp/dcs_liberation/wiki/Manual)
You should start with the manual, it covers everything you need to know before playing the campaign.
* [Troubleshooting](https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting)
You could also briefly check the troubleshooting page to get familiar with the known issues that you could probably fix by yourself.
* [Modding tutorial](https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial)
Modding tutorial will cover how to change default loadouts, configure which planes are present in the campaign (or add new altogether) and more. Check this out if you find that something is not going for your liking, there could be a tutorial for changing that. Although be aware that it would require changing source files and could easily result in non functioning application.
* [Development guide](https://github.com/shdwp/dcs_liberation/wiki/Development-guide)
If you want to contribute to the project, this will give you a brief overview and on how to actually run it from source files.

View File

@@ -43,7 +43,7 @@ def is_version_compatible(save_version):
if current_version_components == save_version_components: if current_version_components == save_version_components:
return True return True
if save_version == "1.4_rc1": if save_version in ["1.4_rc1", "1.4_rc2", "1.4_rc3", "1.4_rc4", "1.4_rc5", "1.4_rc6"]:
return False return False
if current_version_components[:2] == save_version_components[:2]: if current_version_components[:2] == save_version_components[:2]:
@@ -70,7 +70,8 @@ try:
for i in range(0, int(len(conflicttheater.controlpoints) / 2)): for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
conflicttheater.controlpoints[i].captured = True conflicttheater.controlpoints[i].captured = True
start_generator.generate_initial(conflicttheater, enemy_name, sams, multiplier) start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
start_generator.generate_groundobjects(conflicttheater)
game = Game(player_name=player_name, game = Game(player_name=player_name,
enemy_name=enemy_name, enemy_name=enemy_name,
theater=conflicttheater) theater=conflicttheater)

View File

@@ -2,12 +2,14 @@ import typing
import enum import enum
from dcs.vehicles import * from dcs.vehicles import *
from dcs.unitgroup import *
from dcs.ships import * from dcs.ships import *
from dcs.planes import * from dcs.planes import *
from dcs.helicopters import * from dcs.helicopters import *
from dcs.task import * from dcs.task import *
from dcs.unit import *
from dcs.unittype import * from dcs.unittype import *
from dcs.unitgroup import *
""" """
---------- BEGINNING OF CONFIGURATION SECTION ---------- BEGINNING OF CONFIGURATION SECTION
@@ -39,10 +41,10 @@ PRICES = {
# fighter # fighter
C_101CC: 8, C_101CC: 8,
MiG_23MLD: 18, MiG_23MLD: 18,
Su_27: 24, Su_27: 20,
Su_33: 25, Su_33: 22,
MiG_29A: 24, MiG_29A: 23,
MiG_29S: 26, MiG_29S: 25,
F_5E_3: 6, F_5E_3: 6,
MiG_15bis: 5, MiG_15bis: 5,
@@ -52,7 +54,7 @@ PRICES = {
AV8BNA: 13, AV8BNA: 13,
M_2000C: 13, M_2000C: 13,
FA_18C_hornet: 18, FA_18C_hornet: 18,
F_15C: 24, F_15C: 20,
# bomber # bomber
Su_25: 15, Su_25: 15,
@@ -65,7 +67,8 @@ PRICES = {
# heli # heli
Ka_50: 13, Ka_50: 13,
UH_1H: 5, SA342M: 8,
UH_1H: 4,
Mi_8MT: 5, Mi_8MT: 5,
# special # special
@@ -109,9 +112,10 @@ PRICES = {
# ship # ship
CV_1143_5_Admiral_Kuznetsov: 100, CV_1143_5_Admiral_Kuznetsov: 100,
CVN_74_John_C__Stennis: 100, CVN_74_John_C__Stennis: 100,
LHA_1_Tarawa: 50,
LHA_1_Tarawa: 30,
Bulk_cargo_ship_Yakushev: 10, Bulk_cargo_ship_Yakushev: 10,
Armed_speedboat: 10,
Dry_cargo_ship_Ivanov: 10, Dry_cargo_ship_Ivanov: 10,
Tanker_Elnya_160: 10, Tanker_Elnya_160: 10,
} }
@@ -157,6 +161,7 @@ UNIT_BY_TASK = {
Su_25T, Su_25T,
Su_34, Su_34,
Ka_50, Ka_50,
SA342M,
], ],
Transport: [ Transport: [
@@ -165,13 +170,13 @@ UNIT_BY_TASK = {
An_30M, An_30M,
Yak_40, Yak_40,
S_3B_Tanker,
C_130, C_130,
], ],
Refueling: [ Refueling: [
IL_78M, IL_78M,
KC_135, KC_135,
S_3B_Tanker,
], ],
AWACS: [E_3A, A_50, ], AWACS: [E_3A, A_50, ],
@@ -196,8 +201,8 @@ UNIT_BY_TASK = {
Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ], Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ],
Embarking: [UH_1H, Mi_8MT, ], Embarking: [UH_1H, Mi_8MT, ],
Carriage: [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov, ], Carriage: [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, ],
CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, LHA_1_Tarawa], CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ],
} }
""" """
@@ -263,6 +268,7 @@ UNIT_BY_COUNTRY = {
A_50, A_50,
Ka_50, Ka_50,
SA342M,
UH_1H, UH_1H,
Mi_8MT, Mi_8MT,
@@ -302,6 +308,7 @@ UNIT_BY_COUNTRY = {
E_3A, E_3A,
Ka_50, Ka_50,
SA342M,
UH_1H, UH_1H,
Mi_8MT, Mi_8MT,
@@ -316,9 +323,19 @@ UNIT_BY_COUNTRY = {
CVN_74_John_C__Stennis, CVN_74_John_C__Stennis,
LHA_1_Tarawa, LHA_1_Tarawa,
Armed_speedboat,
], ],
} }
CARRIER_TYPE_BY_PLANE = {
FA_18C_hornet: CVN_74_John_C__Stennis,
Ka_50: LHA_1_Tarawa,
SA342M: LHA_1_Tarawa,
UH_1H: LHA_1_Tarawa,
Mi_8MT: LHA_1_Tarawa,
AV8BNA: LHA_1_Tarawa,
}
""" """
Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation. Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation.
Syntax goes as follows: Syntax goes as follows:
@@ -338,6 +355,12 @@ Payload will be used for operation of following type, "*" category will be used
PLANE_PAYLOAD_OVERRIDES = { PLANE_PAYLOAD_OVERRIDES = {
FA_18C_hornet: { FA_18C_hornet: {
CAP: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel", CAP: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
PinpointStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
AntishipStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
},
Su_25T: {
CAS: "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410",
}, },
Su_33: { Su_33: {
@@ -364,6 +387,7 @@ PLANE_PAYLOAD_OVERRIDES = {
M_2000C: { M_2000C: {
CAP: "Combat Air Patrol", CAP: "Combat Air Patrol",
GroundAttack: "MK-82S Heavy Strike",
}, },
MiG_21Bis: { MiG_21Bis: {
@@ -427,6 +451,15 @@ def unit_type_from_name(name: str) -> UnitType:
return None return None
def unit_type_of(unit: Unit) -> UnitType:
if isinstance(unit, Vehicle):
return vehicle_map[unit.type]
elif isinstance(unit, Ship):
return ship_map[unit.type]
else:
return unit.unit_type
def task_name(task) -> str: def task_name(task) -> str:
if task == AirDefence: if task == AirDefence:
return "AirDefence" return "AirDefence"
@@ -452,6 +485,14 @@ def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int):
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1 unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
def unitdict_merge(a: UnitsDict, b: UnitsDict) -> UnitsDict:
b = b.copy()
for k, v in a.items():
b[k] = b.get(k, 0) + v
return b
def unitdict_split(unit_dict: UnitsDict, count: int): def unitdict_split(unit_dict: UnitsDict, count: int):
buffer_dict = {} buffer_dict = {}
for unit_type, unit_count in unit_dict.items(): for unit_type, unit_count in unit_dict.items():

View File

@@ -87,7 +87,7 @@ class BaseAttackEvent(Event):
op.setup(cas=flights[CAS], op.setup(cas=flights[CAS],
escort=flights[CAP], escort=flights[CAP],
attack=flights[PinpointStrike], attack=unitdict_from(flights[PinpointStrike]),
intercept=assigned_units_from(defenders), intercept=assigned_units_from(defenders),
defense=self.to_cp.base.armor, defense=self.to_cp.base.armor,
aa=self.to_cp.base.assemble_aa()) aa=self.to_cp.base.assemble_aa())

View File

@@ -21,6 +21,7 @@ class Event:
silent = False silent = False
informational = False informational = False
is_awacs_enabled = False is_awacs_enabled = False
ca_slots = 0
operation = None # type: Operation operation = None # type: Operation
difficulty = 1 # type: int difficulty = 1 # type: int
game = None # type: Game game = None # type: Game
@@ -74,10 +75,11 @@ class Event:
def generate(self): def generate(self):
self.operation.is_awacs_enabled = self.is_awacs_enabled self.operation.is_awacs_enabled = self.is_awacs_enabled
self.operation.ca_slots = self.ca_slots
self.operation.prepare(self.game.theater.terrain, is_quick=False) self.operation.prepare(self.game.theater.terrain, is_quick=False)
self.operation.generate() self.operation.generate()
self.operation.mission.save(persistency.mission_path_for("liberation_nextturn.miz")) self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
self.environment_settings = self.operation.environment_settings self.environment_settings = self.operation.environment_settings
def generate_quick(self): def generate_quick(self):
@@ -86,7 +88,7 @@ class Event:
self.operation.prepare(self.game.theater.terrain, is_quick=True) self.operation.prepare(self.game.theater.terrain, is_quick=True)
self.operation.generate() self.operation.generate()
self.operation.mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz")) self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz"))
def commit(self, debriefing: Debriefing): def commit(self, debriefing: Debriefing):
for country, losses in debriefing.destroyed_units.items(): for country, losses in debriefing.destroyed_units.items():
@@ -100,18 +102,16 @@ class Event:
for object_identifier in debriefing.destroyed_objects: for object_identifier in debriefing.destroyed_objects:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
remove_ids = []
if not cp.ground_objects: if not cp.ground_objects:
continue continue
for i, ground_object in enumerate(cp.ground_objects): for i, ground_object in enumerate(cp.ground_objects):
if ground_object.matches_string_identifier(object_identifier): if ground_object.is_dead:
logging.info("cp {} removing ground object {}".format(cp, ground_object.string_identifier)) continue
remove_ids.append(i)
remove_ids.reverse() if ground_object.matches_string_identifier(object_identifier):
for i in remove_ids: logging.info("cp {} killing ground object {}".format(cp, ground_object.string_identifier))
del cp.ground_objects[i] cp.ground_objects[i].is_dead = True
def skip(self): def skip(self):
pass pass

View File

@@ -8,7 +8,7 @@ class FrontlineAttackEvent(Event):
TARGET_AMOUNT_FACTOR = 0.5 TARGET_AMOUNT_FACTOR = 0.5
ATTACKER_AMOUNT_FACTOR = 0.4 ATTACKER_AMOUNT_FACTOR = 0.4
ATTACKER_DEFENDER_FACTOR = 0.7 ATTACKER_DEFENDER_FACTOR = 0.7
STRENGTH_INFLUENCE = 0.2 STRENGTH_INFLUENCE = 0.3
SUCCESS_FACTOR = 1.5 SUCCESS_FACTOR = 1.5
defenders = None # type: db.ArmorDict defenders = None # type: db.ArmorDict

View File

@@ -5,7 +5,7 @@ from userdata.debriefing import Debriefing
class FrontlinePatrolEvent(Event): class FrontlinePatrolEvent(Event):
ESCORT_FACTOR = 0.5 ESCORT_FACTOR = 0.5
STRENGTH_INFLUENCE = 0.2 STRENGTH_INFLUENCE = 0.3
SUCCESS_FACTOR = 0.8 SUCCESS_FACTOR = 0.8
cas = None # type: db.PlaneDict cas = None # type: db.PlaneDict

View File

@@ -15,6 +15,7 @@ class InsurgentAttackEvent(Event):
SUCCESS_FACTOR = 0.7 SUCCESS_FACTOR = 0.7
TARGET_VARIETY = 2 TARGET_VARIETY = 2
TARGET_AMOUNT_FACTOR = 0.5 TARGET_AMOUNT_FACTOR = 0.5
STRENGTH_INFLUENCE = 0.1
@property @property
def threat_description(self): def threat_description(self):
@@ -31,6 +32,9 @@ class InsurgentAttackEvent(Event):
def __str__(self): def __str__(self):
return "Destroy insurgents" return "Destroy insurgents"
def skip(self):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def is_successfull(self, debriefing: Debriefing): def is_successfull(self, debriefing: Debriefing):
killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
all_units = sum(self.targets.values()) all_units = sum(self.targets.values())

View File

@@ -11,7 +11,7 @@ class NavalInterceptEvent(Event):
def _targets_count(self) -> int: def _targets_count(self) -> int:
from gen.conflictgen import IMPORTANCE_LOW from gen.conflictgen import IMPORTANCE_LOW
factor = (self.to_cp.importance - IMPORTANCE_LOW) * 10 factor = (self.to_cp.importance - IMPORTANCE_LOW + 0.1) * 20
return max(int(factor), 1) return max(int(factor), 1)
def __str__(self) -> str: def __str__(self) -> str:

View File

@@ -5,7 +5,7 @@ from .event import *
class StrikeEvent(Event): class StrikeEvent(Event):
STRENGTH_INFLUENCE = 0.0 STRENGTH_INFLUENCE = 0.0
SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.03 SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.05
def __str__(self): def __str__(self):
return "Strike" return "Strike"
@@ -39,6 +39,7 @@ class StrikeEvent(Event):
def commit(self, debriefing: Debriefing): def commit(self, debriefing: Debriefing):
super(StrikeEvent, self).commit(debriefing) super(StrikeEvent, self).commit(debriefing)
self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects)) self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects))
def player_attacking(self, flights: db.TaskForceDict): def player_attacking(self, flights: db.TaskForceDict):

View File

@@ -25,7 +25,7 @@ COMMISION_LIMITS_FACTORS = {
COMMISION_AMOUNTS_SCALE = 1.5 COMMISION_AMOUNTS_SCALE = 1.5
COMMISION_AMOUNTS_FACTORS = { COMMISION_AMOUNTS_FACTORS = {
PinpointStrike: 6, PinpointStrike: 3,
CAS: 1, CAS: 1,
CAP: 2, CAP: 2,
AirDefence: 0.3, AirDefence: 0.3,
@@ -33,30 +33,39 @@ COMMISION_AMOUNTS_FACTORS = {
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2
PLAYER_BASEATTACK_THRESHOLD = 0.2 PLAYER_BASEATTACK_THRESHOLD = 0.4
""" """
Various events probabilities. First key is player probabilty, second is enemy probability. Various events probabilities. First key is player probabilty, second is enemy probability.
For the enemy events, only 1 event of each type could be generated for a turn. For the enemy events, only 1 event of each type could be generated for a turn.
Events: Events:
* CaptureEvent - capture base * BaseAttackEvent - capture base
* InterceptEvent - air intercept * InterceptEvent - air intercept
* FrontlineAttack - frontline attack * FrontlineAttackEvent - frontline attack
* GroundAttackEvent - destroy insurgents * FrontlineCAPEvent - frontline attack
* NavalInterceptEvent - naval intercept * NavalInterceptEvent - naval intercept
* AntiAAStrikeEvent - anti-AA strike * StrikeEvent - strike event
* InfantryTransportEvent - helicopter infantry transport * InfantryTransportEvent - helicopter infantry transport
""" """
EVENT_PROBABILITIES = { EVENT_PROBABILITIES = {
BaseAttackEvent: [100, 10], # events always present; only for the player
FrontlineAttackEvent: [100, 0], FrontlineAttackEvent: [100, 0],
FrontlinePatrolEvent: [100, 0], FrontlinePatrolEvent: [100, 0],
StrikeEvent: [100, 0], StrikeEvent: [100, 0],
InterceptEvent: [25, 10],
InsurgentAttackEvent: [0, 10], # events randomly present; only for the player
NavalInterceptEvent: [25, 10],
InfantryTransportEvent: [25, 0], InfantryTransportEvent: [25, 0],
# events conditionally present; for both enemy and player
BaseAttackEvent: [100, 9],
# events randomly present; for both enemy and player
InterceptEvent: [25, 9],
NavalInterceptEvent: [25, 9],
# events randomly present; only for the enemy
InsurgentAttackEvent: [0, 6],
} }
# amount of strength player bases recover for the turn # amount of strength player bases recover for the turn
@@ -71,7 +80,7 @@ AWACS_BUDGET_COST = 4
# Initial budget value # Initial budget value
PLAYER_BUDGET_INITIAL = 170 PLAYER_BUDGET_INITIAL = 170
# Base post-turn bonus value # Base post-turn bonus value
PLAYER_BUDGET_BASE = 17 PLAYER_BUDGET_BASE = 14
# Bonus multiplier logarithm base # Bonus multiplier logarithm base
PLAYER_BUDGET_IMPORTANCE_LOG = 2 PLAYER_BUDGET_IMPORTANCE_LOG = 2
@@ -107,55 +116,82 @@ class Game:
game=self)) game=self))
break break
def _generate_events(self): def _generate_player_event(self, event_class, player_cp, enemy_cp):
enemy_cap_generated = False if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
enemy_generated_types = [] # skip naval events for non-coastal CPs
return
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
# skip base attack events for CPs yet too strong
return
if event_class == StrikeEvent and not enemy_cp.ground_objects:
# skip strikes in case of no targets
return
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
def _generate_enemy_event(self, event_class, player_cp, enemy_cp):
if event_class in [type(x) for x in self.events if not self.is_player_attack(x)]:
# skip already generated enemy event types
return
if player_cp in self.ignored_cps:
# skip attacks against ignored CPs (for example just captured ones)
return
if enemy_cp.base.total_planes == 0:
# skip event if there's no planes on the base
return
if player_cp.is_global:
# skip carriers
return
if event_class == NavalInterceptEvent:
if player_cp.radials == LAND:
# skip naval events for non-coastal CPs
return
elif event_class == StrikeEvent:
if not player_cp.ground_objects:
# skip strikes if there's no ground objects
return
elif event_class == BaseAttackEvent:
if BaseAttackEvent in [type(x) for x in self.events]:
# skip base attack event if there's another one going on
return
if enemy_cp.base.total_armor == 0:
# skip base attack if there's no armor
return
if player_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
# skip base attack if strength is too high
return
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
def _generate_events(self):
for player_cp, enemy_cp in self.theater.conflicts(True): for player_cp, enemy_cp in self.theater.conflicts(True):
if player_cp.is_global or enemy_cp.is_global: if enemy_cp.is_global:
continue continue
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent: if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]:
# skip events requiring frontline
if not Conflict.has_frontline_between(player_cp, enemy_cp): if not Conflict.has_frontline_between(player_cp, enemy_cp):
continue continue
if player_cp.is_global:
# skip events requiring ground CP
if event_class not in [InterceptEvent, StrikeEvent, NavalInterceptEvent]:
continue
if player_probability == 100 or self._roll(player_probability, player_cp.base.strength): if player_probability == 100 or self._roll(player_probability, player_cp.base.strength):
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND: self._generate_player_event(event_class, player_cp, enemy_cp)
pass
else:
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
pass
else:
if event_class == StrikeEvent and not enemy_cp.ground_objects:
pass
else:
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
elif enemy_probability == 100 or self._roll(enemy_probability, enemy_cp.base.strength):
if event_class in enemy_generated_types:
continue
if player_cp in self.ignored_cps: if enemy_probability == 100 or self._roll(enemy_probability, enemy_cp.base.strength):
continue self._generate_enemy_event(event_class, player_cp, enemy_cp)
if enemy_cp.base.total_planes == 0:
continue
if event_class == NavalInterceptEvent:
if player_cp.radials == LAND:
continue
elif event_class == StrikeEvent:
if not player_cp.ground_objects:
continue
elif event_class == BaseAttackEvent:
if enemy_cap_generated:
continue
if enemy_cp.base.total_armor == 0:
continue
enemy_cap_generated = True
enemy_generated_types.append(event_class)
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]: def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW) importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)

View File

@@ -37,13 +37,13 @@ class BaseAttackOperation(Operation):
self.attackers_starting_position = None self.attackers_starting_position = None
conflict = Conflict.capture_conflict( conflict = Conflict.capture_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
@@ -59,5 +59,11 @@ class BaseAttackOperation(Operation):
self.briefinggen.title = "Base attack" self.briefinggen.title = "Base attack"
self.briefinggen.description = "The goal of an attacker is to lower defender presence by destroying their armor and aircraft. Base will be considered captured if attackers on the ground overrun the defenders. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." self.briefinggen.description = "The goal of an attacker is to lower defender presence by destroying their armor and aircraft. Base will be considered captured if attackers on the ground overrun the defenders. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
if self.game.player == self.attacker_name:
self.briefinggen.append_waypoint("TARGET")
else:
pass
super(BaseAttackOperation, self).generate() super(BaseAttackOperation, self).generate()

View File

@@ -26,14 +26,14 @@ class FrontlineAttackOperation(Operation):
self.defenders_starting_position = None self.defenders_starting_position = None
conflict = Conflict.frontline_cas_conflict( conflict = Conflict.frontline_cas_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
@@ -44,11 +44,15 @@ class FrontlineAttackOperation(Operation):
heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()} heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
if heli_flights: if heli_flights:
self.briefinggen.append_frequency("FARP", "127.5 MHz AM") self.briefinggen.append_frequency("FARP + Heli flights", "127.5 MHz AM")
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), at=farp, escort=False) self.airgen.generate_cas_strikegroup(*assigned_units_split(dict),
at=farp,
escort=len(planes_flights) == 0)
self.briefinggen.title = "Frontline CAS" self.briefinggen.title = "Frontline CAS"
self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
self.briefinggen.append_waypoint("CAS AREA IP")
self.briefinggen.append_waypoint("CAS AREA EGRESS")
super(FrontlineAttackOperation, self).generate() super(FrontlineAttackOperation, self).generate()

View File

@@ -32,14 +32,14 @@ class FrontlinePatrolOperation(Operation):
self.defenders_starting_position = None self.defenders_starting_position = None
conflict = Conflict.frontline_cap_conflict( conflict = Conflict.frontline_cap_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
@@ -51,4 +51,6 @@ class FrontlinePatrolOperation(Operation):
self.briefinggen.title = "Frontline CAP" self.briefinggen.title = "Frontline CAP"
self.briefinggen.description = "Providing CAP support for ground units attacking enemy lines. Enemy will scramble its CAS and your task is to intercept it. Operation will be considered successful if total number of friendly units will be lower than enemy by at least a factor of 0.8 (i.e. with 12 units from both sides, there should be at least 8 friendly units alive), lowering targets strength as a result." self.briefinggen.description = "Providing CAP support for ground units attacking enemy lines. Enemy will scramble its CAS and your task is to intercept it. Operation will be considered successful if total number of friendly units will be lower than enemy by at least a factor of 0.8 (i.e. with 12 units from both sides, there should be at least 8 friendly units alive), lowering targets strength as a result."
self.briefinggen.append_waypoint("CAP AREA IP")
self.briefinggen.append_waypoint("CAP AREA EGRESS")
super(FrontlinePatrolOperation, self).generate() super(FrontlinePatrolOperation, self).generate()

View File

@@ -15,14 +15,14 @@ class InfantryTransportOperation(Operation):
super(InfantryTransportOperation, self).prepare(terrain, is_quick) super(InfantryTransportOperation, self).prepare(terrain, is_quick)
conflict = Conflict.transport_conflict( conflict = Conflict.transport_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
@@ -36,6 +36,7 @@ class InfantryTransportOperation(Operation):
self.briefinggen.title = "Infantry transport" self.briefinggen.title = "Infantry transport"
self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength" self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength"
self.briefinggen.append_waypoint("DROP POINT")
# TODO: horrible, horrible hack # TODO: horrible, horrible hack
# this will disable vehicle activation triggers, # this will disable vehicle activation triggers,

View File

@@ -17,21 +17,22 @@ class InsurgentAttackOperation(Operation):
super(InsurgentAttackOperation, self).prepare(terrain, is_quick) super(InsurgentAttackOperation, self).prepare(terrain, is_quick)
conflict = Conflict.ground_attack_conflict( conflict = Conflict.ground_attack_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
self.airgen.generate_defense(*assigned_units_split(self.strikegroup), at=self.defenders_starting_position) self.airgen.generate_defenders_cas(*assigned_units_split(self.strikegroup), at=self.defenders_starting_position)
self.armorgen.generate(self.target, {}) self.armorgen.generate(self.target, {})
self.briefinggen.title = "Destroy insurgents" self.briefinggen.title = "Destroy insurgents"
self.briefinggen.description = "Destroy vehicles of insurgents in close proximity of the friendly base. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." self.briefinggen.description = "Destroy vehicles of insurgents in close proximity of the friendly base. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
self.briefinggen.append_waypoint("TARGET")
super(InsurgentAttackOperation, self).generate() super(InsurgentAttackOperation, self).generate()

View File

@@ -28,27 +28,18 @@ class InterceptOperation(Operation):
self.attackers_starting_position = None self.attackers_starting_position = None
conflict = Conflict.intercept_conflict( conflict = Conflict.intercept_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
for global_cp in self.game.theater.controlpoints: self.prepare_carriers(db.unitdict_from(self.interceptors))
if not global_cp.is_global:
continue
ship = self.shipgen.generate_carrier(type=db.find_unittype(Carriage, self.game.player)[0],
country=self.game.player,
at=global_cp.at)
if global_cp == self.from_cp and not self.is_quick:
self.attackers_starting_position = ship
self.airgen.generate_transport(self.transport, self.to_cp.at) self.airgen.generate_transport(self.transport, self.to_cp.at)
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position) self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)
@@ -56,7 +47,14 @@ class InterceptOperation(Operation):
self.airgen.generate_interception(*assigned_units_split(self.interceptors), at=self.attackers_starting_position) self.airgen.generate_interception(*assigned_units_split(self.interceptors), at=self.attackers_starting_position)
self.briefinggen.title = "Air Intercept" self.briefinggen.title = "Air Intercept"
self.briefinggen.description = "Intercept enemy supply transport aircraft. Escort will also be present if there are available planes on the base. Operation will be considered successful if most of the targets are destroyed, lowering targets strength as a result"
if self.game.player == self.attacker_name:
self.briefinggen.description = "Intercept enemy supply transport aircraft. Escort will also be present if there are available planes on the base. Operation will be considered successful if most of the targets are destroyed, lowering targets strength as a result"
self.briefinggen.append_waypoint("TARGET")
for unit_type, count in self.transport.items():
self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count))
else:
self.briefinggen.description = "Escort friendly supply transport aircraft. Operation will be considered failed if most of the targets are destroyed, lowering CP strength as a result"
super(InterceptOperation, self).generate() super(InterceptOperation, self).generate()

View File

@@ -23,16 +23,18 @@ class NavalInterceptionOperation(Operation):
self.attackers_starting_position = None self.attackers_starting_position = None
conflict = Conflict.naval_intercept_conflict( conflict = Conflict.naval_intercept_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(self.mission, conflict) self.initialize(self.current_mission, conflict)
def generate(self): def generate(self):
self.prepare_carriers(db.unitdict_from(self.strikegroup))
target_groups = self.shipgen.generate_cargo(units=self.targets) target_groups = self.shipgen.generate_cargo(units=self.targets)
self.airgen.generate_ship_strikegroup( self.airgen.generate_ship_strikegroup(
@@ -48,7 +50,13 @@ class NavalInterceptionOperation(Operation):
) )
self.briefinggen.title = "Naval Intercept" self.briefinggen.title = "Naval Intercept"
self.briefinggen.description = "Destroy supply transport ships. Lowers target strength. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." if self.game.player == self.attacker_name:
self.briefinggen.description = "Destroy supply transport ships. Lowers target strength. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
for unit_type, count in self.targets.items():
self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count))
else:
self.briefinggen.description = "Protect supply transport ships."
self.briefinggen.append_waypoint("TARGET")
super(NavalInterceptionOperation, self).generate() super(NavalInterceptionOperation, self).generate()

View File

@@ -4,12 +4,16 @@ from userdata.debriefing import *
from gen import * from gen import *
TANKER_CALLSIGNS = ["Texaco", "Arco", "Shell"]
class Operation: class Operation:
attackers_starting_position = None # type: db.StartingPosition attackers_starting_position = None # type: db.StartingPosition
defenders_starting_position = None # type: db.StartingPosition defenders_starting_position = None # type: db.StartingPosition
mission = None # type: dcs.Mission current_mission = None # type: dcs.Mission
regular_mission = None # type: dcs.Mission
quick_mission = None # type: dcs.Mission
conflict = None # type: Conflict conflict = None # type: Conflict
armorgen = None # type: ArmorConflictGenerator armorgen = None # type: ArmorConflictGenerator
airgen = None # type: AircraftConflictGenerator airgen = None # type: AircraftConflictGenerator
@@ -27,6 +31,7 @@ class Operation:
trigger_radius = TRIGGER_RADIUS_MEDIUM trigger_radius = TRIGGER_RADIUS_MEDIUM
is_quick = None is_quick = None
is_awacs_enabled = False is_awacs_enabled = False
ca_slots = 0
def __init__(self, def __init__(self,
game, game,
@@ -48,9 +53,8 @@ class Operation:
return True return True
def initialize(self, mission: Mission, conflict: Conflict): def initialize(self, mission: Mission, conflict: Conflict):
self.mission = mission self.current_mission = mission
self.conflict = conflict self.conflict = conflict
self.armorgen = ArmorConflictGenerator(mission, conflict) self.armorgen = ArmorConflictGenerator(mission, conflict)
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings) self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
self.aagen = AAConflictGenerator(mission, conflict) self.aagen = AAConflictGenerator(mission, conflict)
@@ -70,8 +74,13 @@ class Operation:
with open("resources/default_options.lua", "r") as f: with open("resources/default_options.lua", "r") as f:
options_dict = loads(f.read())["options"] options_dict = loads(f.read())["options"]
self.mission = dcs.Mission(terrain) self.current_mission = dcs.Mission(terrain)
self.mission.options.load_from_dict(options_dict) if is_quick:
self.quick_mission = self.current_mission
else:
self.regular_mission = self.current_mission
self.current_mission.options.load_from_dict(options_dict)
self.is_quick = is_quick self.is_quick = is_quick
if is_quick: if is_quick:
@@ -81,15 +90,36 @@ class Operation:
self.attackers_starting_position = self.from_cp.at self.attackers_starting_position = self.from_cp.at
self.defenders_starting_position = self.to_cp.at self.defenders_starting_position = self.to_cp.at
def prepare_carriers(self, for_units: db.UnitsDict):
for global_cp in self.game.theater.controlpoints:
if not global_cp.is_global:
continue
ship = self.shipgen.generate_carrier(for_units=[t for t, c in for_units.items() if c > 0],
country=self.game.player,
at=global_cp.at)
if global_cp == self.from_cp and not self.is_quick:
self.attackers_starting_position = ship
def generate(self): def generate(self):
self.visualgen.generate() self.visualgen.generate()
# air support # air support
self.airsupportgen.generate(self.is_awacs_enabled) self.airsupportgen.generate(self.is_awacs_enabled)
self.briefinggen.append_frequency("Tanker", "10X/131 MHz AM") for i, tanker_type in enumerate(self.airsupportgen.generated_tankers):
self.briefinggen.append_frequency("Tanker {} ({})".format(TANKER_CALLSIGNS[i], tanker_type), "{}X/{} MHz AM".format(97+i, 130+i))
if self.is_awacs_enabled: if self.is_awacs_enabled:
self.briefinggen.append_frequency("AWACS", "133 MHz AM") self.briefinggen.append_frequency("AWACS", "133 MHz AM")
# combined arms
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
if self.game.player in [country.name for country in self.current_mission.coalition["blue"].countries.values()]:
self.current_mission.groundControl.blue_tactical_commander = self.ca_slots
else:
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
# ground infrastructure # ground infrastructure
self.groundobjectgen.generate() self.groundobjectgen.generate()
self.extra_aagen.generate() self.extra_aagen.generate()
@@ -111,8 +141,6 @@ class Operation:
else: else:
self.envgen.load(self.environment_settings) self.envgen.load(self.environment_settings)
# @TODO: ADD WAYPOINT INFORMATION!
# main frequencies # main frequencies
self.briefinggen.append_frequency("Flight", "251 MHz AM") self.briefinggen.append_frequency("Flight", "251 MHz AM")
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global: if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:

View File

@@ -8,6 +8,8 @@ class StrikeOperation(Operation):
escort = None # type: db.AssignedUnitsDict escort = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict interceptors = None # type: db.AssignedUnitsDict
trigger_radius = TRIGGER_RADIUS_ALL_MAP
def setup(self, def setup(self,
strikegroup: db.AssignedUnitsDict, strikegroup: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict, escort: db.AssignedUnitsDict,
@@ -24,18 +26,20 @@ class StrikeOperation(Operation):
self.attackers_starting_position = None self.attackers_starting_position = None
conflict = Conflict.strike_conflict( conflict = Conflict.strike_conflict(
attacker=self.mission.country(self.attacker_name), attacker=self.current_mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name), defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp, from_cp=self.from_cp,
to_cp=self.to_cp, to_cp=self.to_cp,
theater=self.game.theater theater=self.game.theater
) )
self.initialize(mission=self.mission, self.initialize(mission=self.current_mission,
conflict=conflict) conflict=conflict)
def generate(self): def generate(self):
targets = [] # type: typing.List[typing.Tuple[str, Point]] self.prepare_carriers(db.unitdict_merge(db.unitdict_from(self.strikegroup), db.unitdict_from(self.escort)))
targets = [] # type: typing.List[typing.Tuple[str, str, Point]]
category_counters = {} # type: typing.Dict[str, int] category_counters = {} # type: typing.Dict[str, int]
processed_groups = [] processed_groups = []
for object in self.to_cp.ground_objects: for object in self.to_cp.ground_objects:
@@ -43,20 +47,31 @@ class StrikeOperation(Operation):
continue continue
processed_groups.append(object.group_identifier) processed_groups.append(object.group_identifier)
category_counters[object.category] = category_counters.get(object.category, 0) + 1 category_counters[object.category] = category_counters.get(object.category, 0) + 1
markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category]) markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category])
targets.append((markpoint_name, object.position)) targets.append((str(object), markpoint_name, object.position))
self.briefinggen.append_target(str(object), markpoint_name)
targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[1])) targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[2]))
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(self.strikegroup), for (name, markpoint_name, _) in targets:
targets=targets, self.briefinggen.append_waypoint("TARGET {} (TP {})".format(str(name), markpoint_name))
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights),
targets=[(mp, pos) for (n, mp, pos) in targets],
at=self.attackers_starting_position) at=self.attackers_starting_position)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
if heli_flights:
self.briefinggen.append_frequency("FARP", "127.5 MHz AM")
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(dict),
targets=[(mp, pos) for (n, mp, pos) in targets],
at=farp,
escort=len(planes_flights) == 0)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position) self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position)
self.briefinggen.title = "Strike" self.briefinggen.title = "Strike"

View File

@@ -2,8 +2,10 @@
class Settings: class Settings:
player_skill = "Good" player_skill = "Good"
enemy_skill = "Average" enemy_skill = "Average"
enemy_vehicle_skill = "Average"
only_player_takeoff = True only_player_takeoff = True
night_disabled = False night_disabled = False
multiplier = 1 multiplier = 1
sams = True sams = True
cold_start = False cold_start = False

View File

@@ -4,7 +4,7 @@ from .naming import *
from dcs.mission import * from dcs.mission import *
DISTANCE_FACTOR = 0.5, 1 DISTANCE_FACTOR = 0.5, 1
EXTRA_AA_MIN_DISTANCE = 35000 EXTRA_AA_MIN_DISTANCE = 50000
EXTRA_AA_MAX_DISTANCE = 150000 EXTRA_AA_MAX_DISTANCE = 150000
EXTRA_AA_POSITION_FROM_CP = 550 EXTRA_AA_POSITION_FROM_CP = 550
@@ -59,6 +59,9 @@ class ExtraAAConflictGenerator:
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE: if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
continue continue
if cp.position.distance_to_point(self.conflict.to_cp.position) < EXTRA_AA_MIN_DISTANCE:
continue
if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MAX_DISTANCE: if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MAX_DISTANCE:
continue continue

View File

@@ -112,7 +112,12 @@ class AircraftConflictGenerator:
group.units[idx].set_client() group.units[idx].set_client()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)) group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.set_frequency(251.0)
if unit_type in helicopters.helicopter_map.values():
print(unit_type)
group.set_frequency(127.5)
else:
group.set_frequency(251.0)
def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup: def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup:
assert count > 0 assert count > 0
@@ -449,10 +454,7 @@ class AircraftConflictGenerator:
at=at and at or self._group_point(self.conflict.air_attackers_location)) at=at and at or self._group_point(self.conflict.air_attackers_location))
group.task = CAP.name group.task = CAP.name
group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
heading = group.position.heading_between_point(self.conflict.position)
initial_wayp = group.add_waypoint(group.position.point_from_heading(heading, WORKAROUND_WAYP_DIST), INTERCEPTION_ALT, INTERCEPTION_AIRSPEED)
initial_wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED) wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))

View File

@@ -9,36 +9,46 @@ from dcs.task import *
from dcs.terrain.terrain import NoParkingSlotError from dcs.terrain.terrain import NoParkingSlotError
TANKER_DISTANCE = 15000 TANKER_DISTANCE = 15000
TANKER_ALT = 10000 TANKER_ALT = 4572
TANKER_HEADING_OFFSET = 45
AWACS_DISTANCE = 150000 AWACS_DISTANCE = 150000
AWACS_ALT = 10000 AWACS_ALT = 13000
class AirSupportConflictGenerator: class AirSupportConflictGenerator:
generated_tankers = None # type: typing.List[str]
def __init__(self, mission: Mission, conflict: Conflict, game): def __init__(self, mission: Mission, conflict: Conflict, game):
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict
self.game = game self.game = game
self.generated_tankers = []
@classmethod
def support_tasks(cls) -> typing.Collection[typing.Type[MainTask]]:
return [Refueling, AWACS]
def generate(self, is_awacs_enabled): def generate(self, is_awacs_enabled):
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
tanker_unit = db.find_unittype(Refueling, self.conflict.attackers_side.name)[0]
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position)
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
tanker_group = self.mission.refuel_flight(
country=self.mission.country(self.game.player),
name=namegen.next_tanker_name(self.mission.country(self.game.player)),
airport=None,
plane_type=tanker_unit,
position=tanker_position,
altitude=TANKER_ALT,
frequency=131,
start_type=StartType.Warm,
tacanchannel="99X",
)
tanker_group.points[0].tasks.append(ActivateBeaconCommand(channel=10, unit_id=tanker_group.id, aa=False)) for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side.name)):
self.generated_tankers.append(db.unit_type_name(tanker_unit_type))
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + TANKER_HEADING_OFFSET * i
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
tanker_group = self.mission.refuel_flight(
country=self.mission.country(self.game.player),
name=namegen.next_tanker_name(self.mission.country(self.game.player)),
airport=None,
plane_type=tanker_unit_type,
position=tanker_position,
altitude=TANKER_ALT,
frequency=130 + i,
start_type=StartType.Warm,
tacanchannel="{}X".format(97 + i),
)
tanker_group.points[0].tasks.append(ActivateBeaconCommand(channel=97 + i, unit_id=tanker_group.id, aa=False))
if is_awacs_enabled: if is_awacs_enabled:
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0] awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]

View File

@@ -20,6 +20,8 @@ FRONTLINE_CAS_FIGHTS_COUNT = 4, 8
FRONTLINE_CAS_GROUP_MIN = 1, 2 FRONTLINE_CAS_GROUP_MIN = 1, 2
FRONTLINE_CAS_PADDING = 12000 FRONTLINE_CAS_PADDING = 12000
FIGHT_DISTANCE = 1500
class ArmorConflictGenerator: class ArmorConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict): def __init__(self, mission: Mission, conflict: Conflict):
@@ -45,6 +47,9 @@ class ArmorConflictGenerator:
group_size=1, group_size=1,
move_formation=PointAction.OffRoad) move_formation=PointAction.OffRoad)
vehicle: Vehicle = group.units[0]
vehicle.player_can_drive = True
if not to: if not to:
to = self.conflict.position.point_from_heading(0, 500) to = self.conflict.position.point_from_heading(0, 500)
@@ -53,8 +58,8 @@ class ArmorConflictGenerator:
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point): def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
if attackers: if attackers:
attack_pos = position.point_from_heading(self.conflict.heading - 90, 8000) attack_pos = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE)
attack_dest = position.point_from_heading(self.conflict.heading + 90, 25000) attack_dest = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE * 2)
for type, count in attackers.items(): for type, count in attackers.items():
self._generate_group( self._generate_group(
side=self.conflict.attackers_side, side=self.conflict.attackers_side,
@@ -65,8 +70,8 @@ class ArmorConflictGenerator:
) )
if defenders: if defenders:
def_pos = position.point_from_heading(self.conflict.heading + 90, 4000) def_pos = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE)
def_dest = position.point_from_heading(self.conflict.heading - 90, 25000) def_dest = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE * 2)
for type, count in defenders.items(): for type, count in defenders.items():
self._generate_group( self._generate_group(
side=self.conflict.defenders_side, side=self.conflict.defenders_side,
@@ -100,10 +105,8 @@ class ArmorConflictGenerator:
attacker_groups = list(db.unitdict_split(attackers, single_fight_attackers_count)) attacker_groups = list(db.unitdict_split(attackers, single_fight_attackers_count))
for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups): for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups):
padding = FRONTLINE_CAS_PADDING if FRONTLINE_CAS_PADDING < self.conflict.distance else 0
position = self.conflict.position.point_from_heading(self.conflict.heading, position = self.conflict.position.point_from_heading(self.conflict.heading,
random.randint(padding, int(self.conflict.distance - padding))) random.randint(0, self.conflict.distance))
self._generate_fight_at(attacker_group_dict, target_group_dict, position) self._generate_fight_at(attacker_group_dict, target_group_dict, position)
def generate_passengers(self, count: int): def generate_passengers(self, count: int):

View File

@@ -12,6 +12,7 @@ class BriefingGenerator:
title = "" # type: str title = "" # type: str
description = "" # type: str description = "" # type: str
targets = None # type: typing.List[typing.Tuple[str, str]] targets = None # type: typing.List[typing.Tuple[str, str]]
waypoints = None # type: typing.List[str]
def __init__(self, mission: Mission, conflict: Conflict, game): def __init__(self, mission: Mission, conflict: Conflict, game):
self.m = mission self.m = mission
@@ -20,6 +21,7 @@ class BriefingGenerator:
self.freqs = [] self.freqs = []
self.targets = [] self.targets = []
self.waypoints = []
def append_frequency(self, name: str, frequency: str): def append_frequency(self, name: str, frequency: str):
self.freqs.append((name, frequency)) self.freqs.append((name, frequency))
@@ -27,7 +29,14 @@ class BriefingGenerator:
def append_target(self, description: str, markpoint: str = None): def append_target(self, description: str, markpoint: str = None):
self.targets.append((description, markpoint)) self.targets.append((description, markpoint))
def append_waypoint(self, description: str):
self.waypoints.append(description)
def generate(self): def generate(self):
self.waypoints.insert(0, "INITIAL")
self.waypoints.append("RTB")
self.waypoints.append("RTB Landing")
description = "" description = ""
if self.title: if self.title:
@@ -43,7 +52,12 @@ class BriefingGenerator:
if self.targets: if self.targets:
description += "\n\nTARGETS:" description += "\n\nTARGETS:"
for name, tp in self.targets: for i, (name, tp) in enumerate(self.targets):
description += "\n{} {}".format(name, "(TP {})".format(tp) if tp else "") description += "\n#{} {} {}".format(i+1, name, "(TP {})".format(tp) if tp else "")
if self.waypoints:
description += "\n\nWAYPOINTS:"
for i, descr in enumerate(self.waypoints):
description += "\n#{}: {}".format(i, descr)
self.m.set_description_text(description) self.m.set_description_text(description)

View File

@@ -28,6 +28,8 @@ CAP_CAS_DISTANCE = 10000, 120000
GROUND_INTERCEPT_SPREAD = 5000 GROUND_INTERCEPT_SPREAD = 5000
GROUND_DISTANCE_FACTOR = 1 GROUND_DISTANCE_FACTOR = 1
GROUND_DISTANCE = 4000
GROUND_ATTACK_DISTANCE = 25000, 13000 GROUND_ATTACK_DISTANCE = 25000, 13000
TRANSPORT_FRONTLINE_DIST = 1800 TRANSPORT_FRONTLINE_DIST = 1800
@@ -128,19 +130,22 @@ class Conflict:
return self.to_cp.size * GROUND_DISTANCE_FACTOR return self.to_cp.size * GROUND_DISTANCE_FACTOR
def find_insertion_point(self, other_point: Point) -> Point: def find_insertion_point(self, other_point: Point) -> Point:
dx = self.position.x - self.tail.x if self.is_vector:
dy = self.position.y - self.tail.y dx = self.position.x - self.tail.x
dr2 = float(dx ** 2 + dy ** 2) dy = self.position.y - self.tail.y
dr2 = float(dx ** 2 + dy ** 2)
lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2 lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2
if lerp < 0: if lerp < 0:
lerp = 0 lerp = 0
elif lerp > 1: elif lerp > 1:
lerp = 1 lerp = 1
x = lerp * dx + self.tail.x x = lerp * dx + self.tail.x
y = lerp * dy + self.tail.y y = lerp * dy + self.tail.y
return Point(x, y) return Point(x, y)
else:
return self.position
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]: def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]:
return Conflict._find_ground_position(at, max_distance, heading, self.theater) return Conflict._find_ground_position(at, max_distance, heading, self.theater)
@@ -150,14 +155,28 @@ class Conflict:
return from_cp.has_frontline and to_cp.has_frontline return from_cp.has_frontline and to_cp.has_frontline
@classmethod @classmethod
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]: def frontline_position(cls, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Optional[typing.Tuple[Point, int]]:
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE) attack_heading = from_cp.position.heading_between_point(to_cp.position)
heading = to_cp.position.heading_between_point(from_cp.position) attack_distance = from_cp.position.distance_to_point(to_cp.position)
return to_cp.position.point_from_heading(heading, distance), heading middle_point = from_cp.position.point_from_heading(attack_heading, attack_distance / 2)
strength_delta = (from_cp.base.strength - to_cp.base.strength) / 1.0
position = middle_point.point_from_heading(attack_heading, strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
ground_position = cls._find_ground_position(position, attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE, attack_heading, theater)
if ground_position:
return ground_position, _opposite_heading(attack_heading)
else:
logging.warning("Coudn't find frontline position between {} and {}!".format(from_cp, to_cp))
return position, _opposite_heading(attack_heading)
@classmethod @classmethod
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Tuple[Point, int, int]: def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Optional[typing.Tuple[Point, int, int]]:
center_position, heading = cls.frontline_position(from_cp, to_cp) frontline = cls.frontline_position(theater, from_cp, to_cp)
if not frontline:
return None
center_position, heading = frontline
left_position, right_position = None, None left_position, right_position = None, None
if not theater.is_on_land(center_position): if not theater.is_on_land(center_position):
@@ -170,7 +189,6 @@ class Conflict:
if pos: if pos:
left_position = pos left_position = pos
center_position = pos center_position = pos
print("{} - {} {}".format(from_cp, to_cp, center_position))
if left_position is None: if left_position is None:
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater) left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater)
@@ -211,12 +229,12 @@ class Conflict:
attack_heading = to_cp.find_radial(attack_raw_heading) attack_heading = to_cp.find_radial(attack_raw_heading)
defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading) defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading)
distance = to_cp.size * GROUND_DISTANCE_FACTOR distance = GROUND_DISTANCE
attackers_location = position.point_from_heading(attack_heading, distance) attackers_location = position.point_from_heading(attack_heading, distance)
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater) attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, attack_heading, theater)
defenders_location = position.point_from_heading(defense_heading, distance) defenders_location = position.point_from_heading(defense_heading, 0)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater) defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, defense_heading, theater)
return cls( return cls(
position=position, position=position,
@@ -394,7 +412,7 @@ class Conflict:
@classmethod @classmethod
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
frontline_position, heading = cls.frontline_position(from_cp, to_cp) frontline_position, heading = cls.frontline_position(theater, from_cp, to_cp)
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST) initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
dest = cls._find_ground_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater) dest = cls._find_ground_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
if not dest: if not dest:

View File

@@ -21,6 +21,10 @@ WEATHER_CLOUD_DENSITY = 1, 8
WEATHER_CLOUD_THICKNESS = 100, 400 WEATHER_CLOUD_THICKNESS = 100, 400
WEATHER_CLOUD_BASE_MIN = 1600 WEATHER_CLOUD_BASE_MIN = 1600
WEATHER_FOG_CHANCE = 20
WEATHER_FOG_VISIBILITY = 2500, 5000
WEATHER_FOG_THICKNESS = 100, 500
RANDOM_TIME = { RANDOM_TIME = {
"night": 5, "night": 5,
"dusk": 30, "dusk": 30,
@@ -29,11 +33,10 @@ RANDOM_TIME = {
} }
RANDOM_WEATHER = { RANDOM_WEATHER = {
1: 0, # heavy rain 1: 0, # thunderstorm
2: 10, # rain 2: 20, # rain
3: 20, # dynamic 3: 80, # clouds
4: 30, # clear 4: 100, # clear
5: 100, # random
} }
@@ -49,7 +52,8 @@ class EnviromentGenerator:
self.game = game self.game = game
def _gen_random_time(self): def _gen_random_time(self):
start_time = datetime.fromtimestamp(1527206400) start_time = datetime.strptime('May 25 2018 12:00AM', '%b %d %Y %I:%M%p')
time_range = None time_range = None
for k, v in RANDOM_TIME.items(): for k, v in RANDOM_TIME.items():
if self.game.settings.night_disabled and k == "night": if self.game.settings.night_disabled and k == "night":
@@ -60,8 +64,36 @@ class EnviromentGenerator:
break break
start_time += timedelta(hours=random.randint(*time_range)) start_time += timedelta(hours=random.randint(*time_range))
logging.info("time - {}, slot - {}, night skipped - {}".format(
str(start_time),
str(time_range),
self.game.settings.night_disabled))
self.mission.start_time = start_time self.mission.start_time = start_time
def _generate_wind(self, wind_speed, wind_direction=None):
# wind
if not wind_direction:
wind_direction = random.randint(0, 360)
self.mission.weather.wind_at_ground = Wind(wind_direction, wind_speed)
self.mission.weather.wind_at_2000 = Wind(wind_direction, wind_speed * 2)
self.mission.weather.wind_at_8000 = Wind(wind_direction, wind_speed * 3)
def _generate_base_weather(self):
# clouds
self.mission.weather.clouds_base = random.randint(*WEATHER_CLOUD_BASE)
self.mission.weather.clouds_density = random.randint(*WEATHER_CLOUD_DENSITY)
self.mission.weather.clouds_thickness = random.randint(*WEATHER_CLOUD_THICKNESS)
# wind
self._generate_wind(random.randint(0, 4))
# fog
if random.randint(0, 100) < WEATHER_FOG_CHANCE:
self.mission.weather.fog_visibility = random.randint(*WEATHER_FOG_VISIBILITY)
self.mission.weather.fog_thickness = random.randint(*WEATHER_FOG_THICKNESS)
def _gen_random_weather(self): def _gen_random_weather(self):
weather_type = None weather_type = None
for k, v in RANDOM_WEATHER.items(): for k, v in RANDOM_WEATHER.items():
@@ -71,32 +103,33 @@ class EnviromentGenerator:
logging.info("generated weather {}".format(weather_type)) logging.info("generated weather {}".format(weather_type))
if weather_type == 1: if weather_type == 1:
self.mission.weather.heavy_rain() # thunderstorm
elif weather_type == 2: self._generate_base_weather()
self.mission.weather.heavy_rain() self._generate_wind(random.randint(8, 12))
self.mission.weather.enable_fog = False
elif weather_type == 3:
self.mission.weather.random(self.mission.start_time, self.conflict.theater.terrain)
elif weather_type == 4:
pass
elif weather_type == 5:
self.mission.weather.clouds_base = random.randint(*WEATHER_CLOUD_BASE)
self.mission.weather.clouds_density = random.randint(*WEATHER_CLOUD_DENSITY)
self.mission.weather.clouds_thickness = random.randint(*WEATHER_CLOUD_THICKNESS)
wind_direction = random.randint(0, 360) self.mission.weather.clouds_density = random.randint(9, 10)
wind_speed = random.randint(0, 13) self.mission.weather.clouds_iprecptns = Weather.Preceptions.Thunderstorm
self.mission.weather.wind_at_ground = Wind(wind_direction, wind_speed) elif weather_type == 2:
self.mission.weather.wind_at_2000 = Wind(wind_direction, wind_speed * 2) # rain
self.mission.weather.wind_at_8000 = Wind(wind_direction, wind_speed * 3) self._generate_base_weather()
self.mission.weather.clouds_density = random.randint(5, 8)
self.mission.weather.clouds_iprecptns = Weather.Preceptions.Rain
self._generate_wind(random.randint(4, 8))
elif weather_type == 3:
# clouds
self._generate_base_weather()
elif weather_type == 4:
# clear
pass
if self.mission.weather.clouds_density > 0: if self.mission.weather.clouds_density > 0:
# sometimes clouds are randomized way too low and need to be fixed # sometimes clouds are randomized way too low and need to be fixed
self.mission.weather.clouds_base = max(self.mission.weather.clouds_base, WEATHER_CLOUD_BASE_MIN) self.mission.weather.clouds_base = max(self.mission.weather.clouds_base, WEATHER_CLOUD_BASE_MIN)
if self.mission.weather.wind_at_ground == 0: if self.mission.weather.wind_at_ground.speed == 0:
# frontline smokes look silly w/o any wind # frontline smokes look silly w/o any wind
self.mission.weather.wind_at_ground = random.randint(1, 2) self._generate_wind(1)
def generate(self) -> EnvironmentSettings: def generate(self) -> EnvironmentSettings:
self._gen_random_time() self._gen_random_time()

View File

@@ -8,17 +8,7 @@ from dcs.mission import *
from dcs.statics import * from dcs.statics import *
FARP_FRONTLINE_DISTANCE = 10000 FARP_FRONTLINE_DISTANCE = 10000
AA_CP_MIN_DISTANCE = 40000
CATEGORY_MAPPING = {
"power": [Fortification.Workshop_A],
"warehouse": [Warehouse.Warehouse],
"fuel": [Warehouse.Tank],
"ammo": [Warehouse.Ammunition_depot],
"farp": [Fortification.FARP_Tent],
"comms": [Fortification.TV_tower],
"oil": [Fortification.Oil_platform],
}
class GroundObjectsGenerator: class GroundObjectsGenerator:
@@ -30,11 +20,18 @@ class GroundObjectsGenerator:
self.game = game self.game = game
def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]: def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]:
assert self.conflict.is_vector, "FARP could be generated only on frontline conflicts!" if self.conflict.is_vector:
center = self.conflict.center
heading = self.conflict.heading - 90
else:
center, heading = self.conflict.frontline_position(self.conflict.theater, self.conflict.from_cp, self.conflict.to_cp)
heading -= 90
position = self.conflict.find_ground_position(center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading)
if not position:
return
for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)): for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)):
heading = self.conflict.heading - 90
position = self.conflict.find_ground_position(self.conflict.center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading)
position = position.point_from_heading(0, i * 275) position = position.point_from_heading(0, i * 275)
yield self.m.farp( yield self.m.farp(
@@ -53,7 +50,13 @@ class GroundObjectsGenerator:
cp = self.conflict.from_cp cp = self.conflict.from_cp
for ground_object in cp.ground_objects: for ground_object in cp.ground_objects:
if ground_object.category == "defense": if ground_object.dcs_identifier == "AA":
if ground_object.position.distance_to_point(self.conflict.from_cp.position) < AA_CP_MIN_DISTANCE:
continue
if ground_object.is_dead:
continue
unit_type = random.choice(self.game.commision_unit_types(cp, AirDefence)) unit_type = random.choice(self.game.commision_unit_types(cp, AirDefence))
assert unit_type is not None, "Cannot find unit type for GroundObject defense ({})!".format(cp) assert unit_type is not None, "Cannot find unit type for GroundObject defense ({})!".format(cp)
@@ -62,17 +65,27 @@ class GroundObjectsGenerator:
name=ground_object.string_identifier, name=ground_object.string_identifier,
_type=unit_type, _type=unit_type,
position=ground_object.position, position=ground_object.position,
heading=ground_object.heading heading=ground_object.heading,
) )
logging.info("generated defense object identifier {} with mission id {}".format(group.name, group.id)) logging.info("generated defense object identifier {} with mission id {}".format(group.name, group.id))
else: else:
if ground_object.dcs_identifier in warehouse_map:
static_type = warehouse_map[ground_object.dcs_identifier]
else:
static_type = fortification_map[ground_object.dcs_identifier]
if not static_type:
print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier))
continue
group = self.m.static_group( group = self.m.static_group(
country=side, country=side,
name=ground_object.string_identifier, name=ground_object.string_identifier,
_type=random.choice(CATEGORY_MAPPING[ground_object.category]), _type=static_type,
position=ground_object.position, position=ground_object.position,
heading=ground_object.heading heading=ground_object.heading,
dead=ground_object.is_dead,
) )
logging.info("generated object identifier {} with mission id {}".format(group.name, group.id)) logging.info("generated {}object identifier {} with mission id {}".format("dead " if ground_object.is_dead else "", group.name, group.id))

View File

@@ -1,5 +0,0 @@
from .aircraft import *
class HelicopterConflictGenerator(AircraftConflictGenerator):
pass

View File

@@ -16,7 +16,13 @@ class ShipGenerator:
self.m = mission self.m = mission
self.conflict = conflict self.conflict = conflict
def generate_carrier(self, type: ShipType, country: str, at: Point) -> ShipGroup: def generate_carrier(self, for_units: typing.Collection[UnitType], country: str, at: Point) -> ShipGroup:
type = db.find_unittype(Carriage, country)[0]
for unit_type in for_units:
if unit_type in db.CARRIER_TYPE_BY_PLANE:
type = db.CARRIER_TYPE_BY_PLANE[unit_type]
break
group = self.m.ship_group( group = self.m.ship_group(
country=self.m.country(country), country=self.m.country(country),
name=namegen.next_carrier_name(self.m.country(country)), name=namegen.next_carrier_name(self.m.country(country)),

View File

@@ -12,20 +12,23 @@ from dcs.action import *
from game import db from game import db
from theater import * from theater import *
from gen.airsupportgen import AirSupportConflictGenerator
from gen import * from gen import *
PUSH_TRIGGER_SIZE = 3000 PUSH_TRIGGER_SIZE = 3000
PUSH_TRIGGER_ACTIVATION_AGL = 25
REGROUP_ZONE_DISTANCE = 12000 REGROUP_ZONE_DISTANCE = 12000
REGROUP_ALT = 5000 REGROUP_ALT = 5000
TRIGGER_WAYPOINT_OFFSET = 2 TRIGGER_WAYPOINT_OFFSET = 2
TRIGGER_MIN_DISTANCE_FROM_START = 10000 TRIGGER_MIN_DISTANCE_FROM_START = 10000
TRIGGER_RADIUS_MINIMUM = 25000 TRIGGER_RADIUS_MINIMUM = 20000
TRIGGER_RADIUS_SMALL = 30000 TRIGGER_RADIUS_SMALL = 50000
TRIGGER_RADIUS_MEDIUM = 100000 TRIGGER_RADIUS_MEDIUM = 100000
TRIGGER_RADIUS_LARGE = 150000 TRIGGER_RADIUS_LARGE = 150000
TRIGGER_RADIUS_ALL_MAP = 3000000
class Silence(Option): class Silence(Option):
@@ -51,14 +54,16 @@ class TriggersGenerator:
vehicle_group.late_activation = True vehicle_group.late_activation = True
activate_by_trigger.append(vehicle_group) activate_by_trigger.append(vehicle_group)
"""
conflict_distance = player_cp.position.distance_to_point(self.conflict.position) conflict_distance = player_cp.position.distance_to_point(self.conflict.position)
minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM) minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM)
if minimum_radius < 0: if minimum_radius < 0:
minimum_radius = 0 minimum_radius = 0
result_radius = min(minimum_radius, radius) result_radius = min(minimum_radius, radius)
"""
activation_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.position, result_radius, name="Activation zone") activation_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.position, radius, name="Activation zone")
activation_trigger = TriggerOnce(Event.NoEvent, "Activation trigger") activation_trigger = TriggerOnce(Event.NoEvent, "Activation trigger")
activation_trigger.add_condition(PartOfCoalitionInZone(player_coalition, activation_trigger_zone.id)) activation_trigger.add_condition(PartOfCoalitionInZone(player_coalition, activation_trigger_zone.id))
activation_trigger.add_condition(FlagIsTrue()) activation_trigger.add_condition(FlagIsTrue())
@@ -79,40 +84,41 @@ class TriggersGenerator:
if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3: if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3:
continue continue
if group.units[0].is_human():
continue
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
pos1 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
pos2 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE+5000)
w1 = group.add_waypoint(pos1, REGROUP_ALT)
w2 = group.add_waypoint(pos2, REGROUP_ALT)
group.points.remove(w1)
group.points.remove(w2)
group.points.insert(1, w2)
group.points.insert(1, w1)
w1.tasks.append(Silence(True))
switch_waypoint_task = ControlledTask(SwitchWaypoint(from_waypoint=3, to_waypoint=2))
switch_waypoint_task.start_if_user_flag(1, False)
w2.tasks.append(switch_waypoint_task)
group.points[3].tasks.append(Silence(False))
group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
push_by_trigger.append(group) push_by_trigger.append(group)
push_trigger_zone = self.mission.triggers.add_triggerzone(player_cp.position, PUSH_TRIGGER_SIZE, name="Push zone") if not group.units[0].is_human():
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
pos1 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
pos2 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE+5000)
w1 = group.add_waypoint(pos1, REGROUP_ALT)
w2 = group.add_waypoint(pos2, REGROUP_ALT)
group.points.remove(w1)
group.points.remove(w2)
group.points.insert(1, w2)
group.points.insert(1, w1)
w1.tasks.append(Silence(True))
switch_waypoint_task = ControlledTask(SwitchWaypoint(from_waypoint=3, to_waypoint=2))
switch_waypoint_task.start_if_user_flag(1, False)
w2.tasks.append(switch_waypoint_task)
group.points[3].tasks.append(Silence(False))
group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
push_trigger = TriggerOnce(Event.NoEvent, "Push trigger") push_trigger = TriggerOnce(Event.NoEvent, "Push trigger")
for group in push_by_trigger: for group in push_by_trigger:
push_trigger.add_condition(AllOfGroupOutsideZone(group.id, push_trigger_zone.id)) for unit in group.units:
push_trigger.add_action(AITaskPush(group.id, 1)) push_trigger.add_condition(UnitAltitudeHigherAGL(unit.id, PUSH_TRIGGER_ACTIVATION_AGL))
message_string = self.mission.string("Task force is in the air, proceed with the objective (activate waypoint 3).") if not group.units[0].is_human():
push_trigger.add_action(AITaskPush(group.id, 1))
message_string = self.mission.string("Task force is in the air, proceed with the objective.")
push_trigger.add_action(MessageToAll(message_string, clearview=True)) push_trigger.add_action(MessageToAll(message_string, clearview=True))
push_trigger.add_action(SetFlagValue()) push_trigger.add_action(SetFlagValue())
@@ -127,9 +133,9 @@ class TriggersGenerator:
def _set_skill(self, player_coalition: str, enemy_coalition: str): def _set_skill(self, player_coalition: str, enemy_coalition: str):
for coalition_name, coalition in self.mission.coalition.items(): for coalition_name, coalition in self.mission.coalition.items():
if coalition_name == player_coalition: if coalition_name == player_coalition:
skill_level = self.game.settings.player_skill skill_level = self.game.settings.player_skill, self.game.settings.player_skill
elif coalition_name == enemy_coalition: elif coalition_name == enemy_coalition:
skill_level = self.game.settings.enemy_skill skill_level = self.game.settings.enemy_skill, self.game.settings.enemy_vehicle_skill
else: else:
continue continue
@@ -137,10 +143,10 @@ class TriggersGenerator:
for plane_group in country.plane_group: for plane_group in country.plane_group:
for plane_unit in plane_group.units: for plane_unit in plane_group.units:
if plane_unit.skill != Skill.Client and plane_unit.skill != Skill.Player: if plane_unit.skill != Skill.Client and plane_unit.skill != Skill.Player:
plane_unit.skill = Skill(skill_level) plane_unit.skill = Skill(skill_level[0])
for vehicle_group in country.vehicle_group: for vehicle_group in country.vehicle_group:
vehicle_group.set_skill(Skill(skill_level)) vehicle_group.set_skill(Skill(skill_level[1]))
def generate(self, player_cp: ControlPoint, is_quick: bool, activation_trigger_radius: int, awacs_enabled: bool): def generate(self, player_cp: ControlPoint, is_quick: bool, activation_trigger_radius: int, awacs_enabled: bool):
player_coalition = self.game.player == "USA" and "blue" or "red" player_coalition = self.game.player == "USA" and "blue" or "red"

View File

@@ -98,7 +98,14 @@ class VisualGenerator:
def _generate_frontline_smokes(self): def _generate_frontline_smokes(self):
for from_cp, to_cp in self.game.theater.conflicts(): for from_cp, to_cp in self.game.theater.conflicts():
point, heading = Conflict.frontline_position(from_cp, to_cp) if from_cp.is_global or to_cp.is_global:
continue
frontline = Conflict.frontline_position(self.game.theater, from_cp, to_cp)
if not frontline:
continue
point, heading = frontline
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2) plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2)
for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING): for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/nevlandmap.p Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,38 @@
import typing
from dcs.mission import *
from dcs.terrain import *
from theater.nevada import *
from theater.persiangulf import *
from theater.caucasus import *
from theater.controlpoint import *
def find_ground_location(near, theater, max, min) -> typing.Optional[Point]:
for _ in range(500):
p = near.random_point_within(max, min)
if theater.is_on_land(p):
return p
return None
mission = Mission(Nevada())
theater = NevadaTheater()
for cp in theater.enemy_points():
for _ in range(0, random.randrange(3, 6)):
p = find_ground_location(cp.position, theater, 120000, 5000)
if not p:
print("Didn't find ground location for {}".format(cp))
continue
mission.flight_group_inflight(
mission.country("USA"),
"",
A_10C,
p,
10000
)
mission.save("resources/tools/ground_objects_example.miz")

View File

@@ -0,0 +1,50 @@
import pickle
import typing
from dcs.mission import Mission
from dcs.mapping import Point
from dcs.unit import *
from dcs.statics import warehouse_map, fortification_map
def load_templates():
temp_mis = Mission()
temp_mis.load_file("resources/tools/groundobject_templates.miz")
groups = {} # type: typing.Dict[str, typing.Dict[int, typing.List[Static]]]
for static_group in temp_mis.country("USA").static_group:
for static in static_group.units:
static_name = str(static.name).split()[0]
tpl_name, tpl_idx = static_name[:-1], int(static_name[-1])
groups[tpl_name] = groups.get(tpl_name, {})
groups[tpl_name][tpl_idx] = groups[tpl_name].get(tpl_idx, [])
groups[tpl_name][tpl_idx].append(static)
tpls = {name: {idx: [] for idx in groups[name].keys()} for name in groups.keys()}
for category_name, category_groups in groups.items():
for idx, static_groups in category_groups.items():
dist = -1
a, b = None, None
for aa in static_groups:
for bb in static_groups:
if aa.position.distance_to_point(bb.position) > dist:
dist = aa.position.distance_to_point(bb.position)
a = aa
b = bb
center = a.position.point_from_heading(a.position.heading_between_point(b.position), dist / 2)
for static in static_groups:
tpls[category_name][idx].append({
"type": static.type,
"offset": Point(center.x - static.position.x, center.y - static.position.y),
"heading": static.heading,
})
tpls["aa"] = {0: [{"type": "AA", "offset": Point(0, 0), "heading": 0}]}
return tpls
with open("resources/groundobject_templates.p", "wb") as f:
pickle.dump(load_templates(), f)

View File

@@ -1,86 +1,108 @@
import pickle import pickle
import typing import typing
from game import db
from gen.groundobjectsgen import TheaterGroundObject
from dcs.mission import Mission from dcs.mission import Mission
from dcs.mapping import Point from dcs.mapping import Point
from dcs.terrain import *
from dcs.unitgroup import VehicleGroup, StaticGroup
from dcs import vehicles
from dcs.unit import *
from dcs.statics import warehouse_map, fortification_map
from game import db
from gen.groundobjectsgen import TheaterGroundObject
from theater.caucasus import CaucasusTheater
from theater.persiangulf import PersianGulfTheater
from theater.nevada import NevadaTheater
m = Mission() m = Mission()
m.load_file("./cau_groundobjects.miz") m.load_file("resources/tools/cau_groundobjects.miz")
result = {} if isinstance(m.terrain, Caucasus):
result_by_groups = {} # type: typing.Dict[int, TheaterGroundObject] theater = CaucasusTheater(load_ground_objects=False)
cp_counters = {} elif isinstance(m.terrain, PersianGulf):
ids_counters = {} theater = PersianGulfTheater(load_ground_objects=False)
group_id_counter = 0 elif isinstance(m.terrain, Nevada):
previous_group_id = None theater = NevadaTheater(load_ground_objects=False)
else:
assert False
def append_group(cp_id, category, group_id, object_id, position, heading): def closest_cp(location: Point) -> (int, float):
global result global theater
global result_by_groups min_distance, min_cp = None, None
ground_object = TheaterGroundObject(category, cp_id, group_id, object_id, position, heading) for cp in theater.controlpoints:
if not min_distance or location.distance_to_point(cp.position) < min_distance:
min_distance = location.distance_to_point(cp.position)
min_cp = cp.id
if cp_id not in result: assert min_cp is not None
result[cp_id] = [] return min_cp
result[cp_id].append(ground_object)
result_by_groups_key = "{}_{}_{}".format(cp_id, category, group_id)
if result_by_groups_key not in result_by_groups:
result_by_groups[result_by_groups_key] = []
result_by_groups[result_by_groups_key].append(ground_object)
def parse_name(name: str) -> typing.Tuple: if __name__ == "__main__":
args = str(name.split()[0]).split("|") theater_objects = []
if len(args) == 2: for group in m.country("Russia").static_group + m.country("Russia").vehicle_group:
global group_id_counter for unit in group.units:
group_id_counter += 1 theater_object = TheaterGroundObject()
args.append(str(group_id_counter)) theater_object.object_id = len(theater_objects) + 1
else:
global previous_group_id
if previous_group_id != args[2]:
group_id_counter += 1
previous_group_id = args[2]
return args[0], int(args[1]), int(args[2]) theater_object.position = unit.position
theater_object.heading = unit.heading
if isinstance(unit, Vehicle) and unit.type in vehicles.AirDefence.__dict__.values():
theater_object.dcs_identifier = "AA"
else:
theater_object.dcs_identifier = unit.type
for group in m.country("Russia").static_group + m.country("Russia").vehicle_group: assert theater_object.dcs_identifier
try: assert theater_object.object_id
category, cp_id, group_id = parse_name(str(group.name))
except:
print("Failed to parse {}".format(group.name))
continue
ids_counters_key = "{}_{}".format(cp_id, group_id) theater_objects.append(theater_object)
ids_counters[ids_counters_key] = ids_counters.get(ids_counters_key, 0) + 1
object_id = ids_counters[ids_counters_key]
cp_counters[cp_id] = cp_counters.get(cp_id, 0) + 1
append_group(cp_id, category, group_id, object_id, group.position, group.units[0].heading) group_ids = 1
for object_a in theater_objects:
for object_b in theater_objects:
if object_a.position.distance_to_point(object_b.position) < 2000:
if object_a.group_id and object_b.group_id:
continue
elif object_a.group_id:
object_b.group_id = object_a.group_id
object_b.cp_id = object_a.cp_id
elif object_b.group_id:
object_a.group_id = object_b.group_id
object_a.cp_id = object_b.cp_id
else:
object_a.group_id = group_ids
object_b.group_id = group_ids
object_a.cp_id = closest_cp(object_a.position)
object_b.cp_id = object_a.cp_id
group_ids += 1
GROUP_TRESHOLD = 2000 assert object_a.cp_id == object_b.cp_id, "Object {} and {} are placed in group with different airports!".format(object_a.string_identifier, object_b.string_identifier)
did_check_pairs = []
for group_id, objects_in_group in result_by_groups.items():
for a in objects_in_group:
for b in objects_in_group:
if (a, b) in did_check_pairs:
continue
did_check_pairs.append((a, b)) for a in theater_objects:
distance = a.position.distance_to_point(b.position) if not a.group_id:
if distance > GROUP_TRESHOLD: a.group_id = group_ids
print("Objects {} and {} in group {} are too far apart ({})!".format(a.string_identifier, b.string_identifier, group_id, distance)) a.cp_id = closest_cp(a.position)
group_ids += 1
print("Total {} objects".format(sum([len(x) for x in result.values()]))) with open("resources/cau_groundobjects.p", "wb") as f:
for cp_id, count in cp_counters.items(): result = {}
print("{} - {} objects".format(cp_id, count)) for theater_object in theater_objects:
assert theater_object.cp_id
assert theater_object.group_id
assert theater_object.object_id
if theater_object.cp_id not in result:
result[theater_object.cp_id] = []
result[theater_object.cp_id].append(theater_object)
with open("../cau_groundobjects.p", "wb") as f: print("Total {} objects".format(len(theater_objects)))
pickle.dump(result, f) for cp_id, objects in result.items():
print("{}: total {} objects".format(m.terrain.airport_by_id(cp_id), len(objects)))
pickle.dump(result, f)

View File

@@ -1,14 +1,26 @@
import pickle import pickle
from dcs.mission import Mission from dcs.mission import Mission
from dcs.planes import A_10C
for terrain in ["cau", "gulf"]: for terrain in ["cau", "gulf", "nev"]:
m = Mission() m = Mission()
m.load_file("./{}_terrain.miz".format(terrain)) m.load_file("./{}_terrain.miz".format(terrain))
landmap = [] inclusion_zones = []
exclusion_zones = []
for plane_group in m.country("USA").plane_group: for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points]) zone = [(x.position.x, x.position.y) for x in plane_group.points]
if terrain == "cau" and inclusion_zones:
# legacy
exclusion_zones.append(zone)
else:
if plane_group.units[0].type == "F-15C":
exclusion_zones.append(zone)
else:
inclusion_zones.append(zone)
with open("../{}landmap.p".format(terrain), "wb") as f: with open("../{}landmap.p".format(terrain), "wb") as f:
pickle.dump(landmap, f) print(len(inclusion_zones), len(exclusion_zones))
pickle.dump((inclusion_zones, exclusion_zones), f)

Binary file not shown.

Binary file not shown.

View File

@@ -43,7 +43,7 @@ def _mk_archieve():
return return
archieve = ZipFile(path, "w") archieve = ZipFile(path, "w")
archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\" \"{}\"".format(VERSION)) archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\\Saved Games\" \"{}\"".format(VERSION))
_zip_dir(archieve, ".") _zip_dir(archieve, ".")
os.chdir("submodules\\dcs") os.chdir("submodules\\dcs")
_zip_dir(archieve, "dcs") _zip_dir(archieve, "dcs")

Binary file not shown.

View File

@@ -1,3 +1,3 @@
from .controlpoint import * from .controlpoint import *
from .conflicttheater import * from .conflicttheater import *
from .base import * from .base import *

View File

@@ -3,17 +3,19 @@ import typing
import math import math
import itertools import itertools
from game import db
from theater.controlpoint import ControlPoint
from dcs.planes import * from dcs.planes import *
from dcs.vehicles import * from dcs.vehicles import *
from dcs.task import * from dcs.task import *
from game import db
STRENGTH_AA_ASSEMBLE_MIN = 0.2 STRENGTH_AA_ASSEMBLE_MIN = 0.2
PLANES_SCRAMBLE_MIN_BASE = 4 PLANES_SCRAMBLE_MIN_BASE = 2
PLANES_SCRAMBLE_MAX_BASE = 8 PLANES_SCRAMBLE_MAX_BASE = 8
PLANES_SCRAMBLE_FACTOR = 0.6 PLANES_SCRAMBLE_FACTOR = 0.3
BASE_MAX_STRENGTH = 1
BASE_MIN_STRENGTH = 0
class Base: class Base:
@@ -125,9 +127,11 @@ class Base:
elif unit_type in self.aa: elif unit_type in self.aa:
target_array = self.aa target_array = self.aa
else: else:
print("Base didn't find event type {}".format(unit_type))
continue continue
if unit_type not in target_array: if unit_type not in target_array:
print("Base didn't find event type {}".format(unit_type))
continue continue
target_array[unit_type] = max(target_array[unit_type] - count, 0) target_array[unit_type] = max(target_array[unit_type] - count, 0)
@@ -136,10 +140,10 @@ class Base:
def affect_strength(self, amount): def affect_strength(self, amount):
self.strength += amount self.strength += amount
if self.strength > 1: if self.strength > BASE_MAX_STRENGTH:
self.strength = 1 self.strength = BASE_MAX_STRENGTH
elif self.strength < 0: elif self.strength <= 0:
self.strength = 0.001 self.strength = BASE_MIN_STRENGTH
def scramble_count(self, multiplier: float, task: Task = None) -> int: def scramble_count(self, multiplier: float, task: Task = None) -> int:
if task: if task:

View File

@@ -11,13 +11,13 @@ from .base import *
class CaucasusTheater(ConflictTheater): class CaucasusTheater(ConflictTheater):
terrain = caucasus.Caucasus() terrain = caucasus.Caucasus()
overview_image = "caumap.gif" overview_image = "caumap.gif"
reference_points = {(-317948.32727306, 635639.37385346): (282.5, 319), reference_points = {(-317948.32727306, 635639.37385346): (278.5, 319),
(-355692.3067714, 617269.96285781): (269, 352), } (-355692.3067714, 617269.96285781): (263, 352), }
landmap = load_landmap("resources\\caulandmap.p") landmap = load_landmap("resources\\caulandmap.p")
daytime_map = { daytime_map = {
"dawn": (6, 9), "dawn": (6, 9),
"day": (9, 18), "day": (9, 18),
"dusk": (18, 21), "dusk": (18, 20),
"night": (0, 5), "night": (0, 5),
} }
@@ -33,7 +33,6 @@ class CaucasusTheater(ConflictTheater):
gelendzhik = ControlPoint.from_airport(caucasus.Gelendzhik, COAST_DR_E, SIZE_BIG, 1.1) gelendzhik = ControlPoint.from_airport(caucasus.Gelendzhik, COAST_DR_E, SIZE_BIG, 1.1)
maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH) maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
krasnodar = ControlPoint.from_airport(caucasus.Krasnodar_Center, LAND, SIZE_LARGE, IMPORTANCE_HIGH) krasnodar = ControlPoint.from_airport(caucasus.Krasnodar_Center, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
novorossiysk = ControlPoint.from_airport(caucasus.Novorossiysk, COAST_DR_E, SIZE_BIG, 1.2)
krymsk = ControlPoint.from_airport(caucasus.Krymsk, LAND, SIZE_LARGE, 1.2) krymsk = ControlPoint.from_airport(caucasus.Krymsk, LAND, SIZE_LARGE, 1.2)
anapa = ControlPoint.from_airport(caucasus.Anapa_Vityazevo, LAND, SIZE_LARGE, IMPORTANCE_HIGH) anapa = ControlPoint.from_airport(caucasus.Anapa_Vityazevo, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
@@ -44,9 +43,12 @@ class CaucasusTheater(ConflictTheater):
carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-305810.6875, 406399.1875)) carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-305810.6875, 406399.1875))
def __init__(self): def __init__(self, load_ground_objects=True):
super(CaucasusTheater, self).__init__() super(CaucasusTheater, self).__init__()
self.soganlug.frontline_offset = 0.5
self.soganlug.base.strength = 1
self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan]) self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan])
self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik]) self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik])
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye]) self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
@@ -73,10 +75,7 @@ class CaucasusTheater(ConflictTheater):
self.carrier_1.captured = True self.carrier_1.captured = True
self.soganlug.captured = True self.soganlug.captured = True
with open("resources/cau_groundobjects.p", "rb") as f:
self.set_groundobject(pickle.load(f))
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []): def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
point.name = " ".join(re.split(r"[ -]", point.name)[:1]) point.name = " ".join(re.split(r"[ -]", point.name)[:1])
super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to) super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to)

View File

@@ -18,6 +18,8 @@ IMPORTANCE_LOW = 1
IMPORTANCE_MEDIUM = 1.2 IMPORTANCE_MEDIUM = 1.2
IMPORTANCE_HIGH = 1.4 IMPORTANCE_HIGH = 1.4
GLOBAL_CP_CONFLICT_DISTANCE_MIN = 340000
""" """
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ] ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
COAST_NS_E = [45, 90, 135, ] COAST_NS_E = [45, 90, 135, ]
@@ -58,31 +60,36 @@ class ConflictTheater:
def __init__(self): def __init__(self):
self.controlpoints = [] self.controlpoints = []
def set_groundobject(self, dictionary: typing.Dict[int, typing.Collection[TheaterGroundObject]]):
for id, value in dictionary.items():
for cp in self.controlpoints:
if cp.id == id:
cp.ground_objects = value
break
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []): def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
for connected_point in connected_to: for connected_point in connected_to:
point.connect(to=connected_point) point.connect(to=connected_point)
self.controlpoints.append(point) self.controlpoints.append(point)
def is_in_sea(self, point: Point) -> bool:
if not self.landmap:
return False
for inclusion_zone in self.landmap[0]:
if poly_contains(point.x, point.y, inclusion_zone):
return False
return True
def is_on_land(self, point: Point) -> bool: def is_on_land(self, point: Point) -> bool:
if not self.landmap: if not self.landmap:
return True return True
# check first poly (main land poly) is_point_included = False
if not poly_contains(point.x, point.y, self.landmap[0]): for inclusion_zone in self.landmap[0]:
if poly_contains(point.x, point.y, inclusion_zone):
is_point_included = True
if not is_point_included:
return False return False
# check others polys (exclusion zones from main) for exclusion_zone in self.landmap[1]:
for poly in self.landmap[1:]: if poly_contains(point.x, point.y, exclusion_zone):
if poly_contains(point.x, point.y, poly):
# point is in one of the exclusion zones, meaning that it's in the lake or something
return False return False
return True return True
@@ -95,5 +102,9 @@ class ConflictTheater:
for connected_point in [x for x in cp.connected_points if x.captured != from_player]: for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
yield (cp, connected_point) yield (cp, connected_point)
for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]:
if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN:
yield (global_cp, connected_point)
def enemy_points(self) -> typing.Collection[ControlPoint]: def enemy_points(self) -> typing.Collection[ControlPoint]:
return [point for point in self.controlpoints if not point.captured] return [point for point in self.controlpoints if not point.captured]

View File

@@ -9,16 +9,21 @@ from .theatergroundobject import TheaterGroundObject
class ControlPoint: class ControlPoint:
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.Collection[TheaterGroundObject]
position = None # type: Point
captured = False
has_frontline = True
id = 0 id = 0
position = None # type: Point
name = None # type: str
full_name = None # type: str
base = None # type: theater.base.Base base = None # type: theater.base.Base
at = None # type: db.StartPosition at = None # type: db.StartPosition
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True): connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.List[TheaterGroundObject]
captured = False
has_frontline = True
frontline_offset = 0.0
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
import theater.base import theater.base
self.id = id self.id = id
@@ -37,14 +42,14 @@ class ControlPoint:
self.base = theater.base.Base() self.base = theater.base.Base()
@classmethod @classmethod
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True): def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
assert airport assert airport
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline) return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline)
@classmethod @classmethod
def carrier(cls, name: str, at: Point): def carrier(cls, name: str, at: Point):
import theater.conflicttheater import theater.conflicttheater
return cls(0, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1) return cls(0, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1, has_frontline=False)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@@ -1,7 +1,8 @@
import pickle import pickle
import typing import typing
Landmap = typing.Collection[typing.Collection[typing.Tuple[float, float]]] Zone = typing.Collection[typing.Tuple[float, float]]
Landmap = typing.Tuple[typing.Collection[Zone], typing.Collection[Zone]]
def load_landmap(filename: str) -> Landmap: def load_landmap(filename: str) -> Landmap:

View File

@@ -1,6 +1,7 @@
from dcs.terrain import nevada from dcs.terrain import nevada
from dcs import mapping from dcs import mapping
from .landmap import *
from .conflicttheater import * from .conflicttheater import *
from .base import * from .base import *
@@ -10,10 +11,11 @@ class NevadaTheater(ConflictTheater):
overview_image = "nevada.gif" overview_image = "nevada.gif"
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45, -360), 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), } (nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440, 80), }
landmap = load_landmap("resources\\nev_landmap.p")
daytime_map = { daytime_map = {
"dawn": (4, 6), "dawn": (4, 6),
"day": (6, 17), "day": (6, 17),
"dusk": (17, 19), "dusk": (17, 18),
"night": (0, 5), "night": (0, 5),
} }

View File

@@ -19,31 +19,32 @@ class PersianGulfTheater(ConflictTheater):
"night": (0, 5), "night": (0, 5),
} }
al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, LAND, SIZE_BIG, IMPORTANCE_HIGH) al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, LAND, SIZE_BIG, IMPORTANCE_LOW)
al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH) al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, LAND, SIZE_BIG, IMPORTANCE_LOW)
al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, IMPORTANCE_HIGH) al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, 1.1)
sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.3, has_frontline=False) sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.1, has_frontline=False)
dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, IMPORTANCE_HIGH) dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, IMPORTANCE_MEDIUM)
sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.3) sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.0)
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, IMPORTANCE_HIGH) fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.0)
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, IMPORTANCE_HIGH) khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM)
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_LOW, has_frontline=False) sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_LOW, has_frontline=False)
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False) abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False)
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], IMPORTANCE_LOW, 1.1, has_frontline=False) tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False)
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, 1.2, has_frontline=False) tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, 1.1, has_frontline=False)
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, 1.1) bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, IMPORTANCE_HIGH)
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.2, has_frontline=False) qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1, has_frontline=False)
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, 1.1) havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_HIGH)
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, 1.2) bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_LOW) lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_HIGH)
shiraz = ControlPoint.from_airport(persiangulf.Shiraz_International_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW) shiraz = ControlPoint.from_airport(persiangulf.Shiraz_International_Airport, LAND, SIZE_BIG, IMPORTANCE_HIGH)
kerman = ControlPoint.from_airport(persiangulf.Kerman_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW) kerman = ControlPoint.from_airport(persiangulf.Kerman_Airport, LAND, SIZE_BIG, IMPORTANCE_HIGH)
west_carrier = ControlPoint.carrier("East carrier", Point(-100531.972946, 60939.275818)) west_carrier = ControlPoint.carrier("West carrier", Point(-69043.813952358, -159916.65947136))
east_carrier = ControlPoint.carrier("East carrier", Point(59514.324335475, 28165.517980635))
def __init__(self): def __init__(self):
super(PersianGulfTheater, self).__init__() super(PersianGulfTheater, self).__init__()
@@ -72,17 +73,8 @@ class PersianGulfTheater(ConflictTheater):
self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya]) self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya])
self.add_controlpoint(self.west_carrier) self.add_controlpoint(self.west_carrier)
self.add_controlpoint(self.east_carrier)
self.west_carrier.captured = True self.west_carrier.captured = True
self.kerman.captured = True self.east_carrier.captured = True
self.al_dhafra.captured = True
"""
Mid game:
self.al_maktoum.captured = True
self.al_minhad.captured = True
self.dubai.captured = True
self.sharjah.captured = True
self.fujairah.captured = True
self.khasab.captured = True
self.sir_abu_nuayr.captured = True
"""

View File

@@ -1,4 +1,8 @@
import math import math
import pickle
import random
import typing
import logging
from theater.base import * from theater.base import *
from theater.conflicttheater import * from theater.conflicttheater import *
@@ -15,7 +19,7 @@ COUNT_BY_TASK = {
} }
def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float): def generate_inital_units(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float):
for cp in theater.enemy_points(): for cp in theater.enemy_points():
if cp.captured: if cp.captured:
continue continue
@@ -37,3 +41,71 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie
for unit_type in unittypes: for unit_type in unittypes:
logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type)) logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type))
cp.base.commision_units({unit_type: count_per_type}) cp.base.commision_units({unit_type: count_per_type})
def generate_groundobjects(theater: ConflictTheater):
with open("resources/groundobject_templates.p", "rb") as f:
tpls = pickle.load(f)
def find_location(on_ground, near, theater, min, max) -> typing.Optional[Point]:
point = None
for _ in range(1000):
p = near.random_point_within(max, min)
if on_ground and theater.is_on_land(p):
point = p
elif not on_ground and theater.is_in_sea(p):
point = p
if point:
for angle in range(0, 360, 45):
p = point.point_from_heading(angle, 2500)
if on_ground and not theater.is_on_land(p):
point = None
break
elif not on_ground and not theater.is_in_sea(p):
point = None
break
if point:
return point
return None
group_id = 0
for cp in theater.enemy_points():
for _ in range(0, random.randrange(2, 4)):
available_categories = list(tpls) + ["aa", "aa"]
tpl_category = random.choice(available_categories)
tpl = random.choice(list(tpls[tpl_category].values()))
point = find_location(tpl_category != "oil", cp.position, theater, 15000, 80000)
if point is None:
print("Couldn't find point for {}".format(cp))
continue
"""
dist = point.distance_to_point(cp.position) - 15000
for another_cp in theater.enemy_points():
if another_cp.position.distance_to_point(point) < dist:
cp = another_cp
"""
group_id += 1
object_id = 0
logging.info("generated {} for {}".format(tpl_category, cp))
for object in tpl:
object_id += 1
g = TheaterGroundObject()
g.group_id = group_id
g.object_id = object_id
g.cp_id = cp.id
g.dcs_identifier = object["type"]
g.heading = object["heading"]
g.position = Point(point.x + object["offset"].x, point.y + object["offset"].y)
cp.ground_objects.append(g)

View File

@@ -1,14 +1,17 @@
import typing import typing
from dcs.mapping import Point from dcs.mapping import Point
from dcs.statics import *
NAME_BY_CATEGORY = { NAME_BY_CATEGORY = {
"power": "Power plant", "power": "Power plant",
"ammo": "Ammo depot", "ammo": "Ammo depot",
"fuel": "Fuel depot", "fuel": "Fuel depot",
"defense": "AA Defense Site", "aa": "AA Defense Site",
"warehouse": "Warehouse", "warehouse": "Warehouse",
"farp": "FARP", "farp": "FARP",
"fob": "FOB",
"factory": "Factory",
"comms": "Comms. tower", "comms": "Comms. tower",
"oil": "Oil platform" "oil": "Oil platform"
} }
@@ -17,29 +20,47 @@ ABBREV_NAME = {
"power": "PLANT", "power": "PLANT",
"ammo": "AMMO", "ammo": "AMMO",
"fuel": "FUEL", "fuel": "FUEL",
"defense": "AA", "aa": "AA",
"warehouse": "WARE", "warehouse": "WARE",
"farp": "FARP", "farp": "FARP",
"fob": "FOB",
"factory": "FACTORY",
"comms": "COMMST", "comms": "COMMST",
"oil": "OILP" "oil": "OILP"
} }
CATEGORY_MAP = {
"aa": ["AA"],
"power": ["Workshop A", "Electric power box", "Garage small A"],
"warehouse": ["Warehouse", "Hangar A"],
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
"ammo": [".Ammunition depot", "Hangar B"],
"farp": ["FARP Tent", "FARP Ammo Dump Coating", "FARP Fuel Depot", "FARP Command Post", "FARP CP Blindage"],
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
"factory": ["Tech combine", "Tech hangar A"],
"comms": ["TV tower", "Comms tower M"],
"oil": ["Oil platform"],
}
class TheaterGroundObject: class TheaterGroundObject:
object_id = 0
cp_id = 0 cp_id = 0
group_id = 0 group_id = 0
object_id = 0
dcs_identifier = None # type: str
is_dead = False
heading = 0 heading = 0
position = None # type: Point position = None # type: Point
category = None # type: str
def __init__(self, category, cp_id, group_id, object_id, position, heading): @property
self.category = category def category(self) -> str:
self.cp_id = cp_id for k, v in CATEGORY_MAP.items():
self.group_id = group_id if self.dcs_identifier in v:
self.object_id = object_id return k
self.position = position assert False, "Identifier not found in mapping: {}".format(self.dcs_identifier)
self.heading = heading
@property @property
def string_identifier(self): def string_identifier(self):

View File

@@ -18,53 +18,60 @@ class BaseMenu(Menu):
def display(self): def display(self):
self.window.clear_right_pane() self.window.clear_right_pane()
row = 0
def purchase_row(unit_type, unit_price):
nonlocal row
existing_units = self.base.total_units_of_type(unit_type)
scheduled_units = self.event.units.get(unit_type, 0)
Label(self.frame, text="{}".format(db.unit_type_name(unit_type)), **STYLES["widget"]).grid(row=row, sticky=W)
label = Label(self.frame, text="({})".format(existing_units), **STYLES["widget"])
label.grid(column=1, row=row)
self.bought_amount_labels[unit_type] = label
Label(self.frame, text="{}m".format(unit_price), **STYLES["widget"]).grid(column=2, row=row, sticky=E)
Button(self.frame, text="+", command=self.buy(unit_type), **STYLES["btn-primary"]).grid(column=3, row=row, padx=(10,0))
Button(self.frame, text="-", command=self.sell(unit_type), **STYLES["btn-warning"]).grid(column=4, row=row, padx=(10,5))
row += 1
units = { units = {
PinpointStrike: db.find_unittype(PinpointStrike, self.game.player),
Embarking: db.find_unittype(Embarking, self.game.player),
CAS: db.find_unittype(CAS, self.game.player),
CAP: db.find_unittype(CAP, self.game.player), CAP: db.find_unittype(CAP, self.game.player),
Embarking: db.find_unittype(Embarking, self.game.player),
AirDefence: db.find_unittype(AirDefence, self.game.player), AirDefence: db.find_unittype(AirDefence, self.game.player),
CAS: db.find_unittype(CAS, self.game.player),
PinpointStrike: db.find_unittype(PinpointStrike, self.game.player),
} }
# Header # Header
head = Frame(self.frame, **STYLES["header"]) head = Frame(self.frame, **STYLES["header"])
head.grid(row=row, column=0, columnspan=5, sticky=NSEW, pady=5) head.grid(row=0, column=0, columnspan=99, sticky=NSEW, pady=5)
Label(head, text=self.cp.name, **STYLES["title"]).grid(row=0, column=0, sticky=NW+S) Label(head, text=self.cp.name, **STYLES["title"]).grid(row=0, column=0, sticky=NW+S)
units_title = "{}/{}/{}".format(self.cp.base.total_planes, self.cp.base.total_armor, self.cp.base.total_aa) units_title = "{}/{}/{}".format(self.cp.base.total_planes, self.cp.base.total_armor, self.cp.base.total_aa)
Label(head, text=units_title, **STYLES["strong-grey"]).grid(row=0, column=1, sticky=NE+S) Label(head, text=units_title, **STYLES["strong-grey"]).grid(row=0, column=1, sticky=NE+S)
row += 1
self.budget_label = Label(self.frame, text="Budget: {}m".format(self.game.budget), **STYLES["widget"]) self.budget_label = Label(self.frame, text="Budget: {}m".format(self.game.budget), **STYLES["widget"])
self.budget_label.grid(row=row, sticky=W) self.budget_label.grid(row=1, sticky=W)
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(column=4, row=row, padx=(0,15), pady=(0,5)) Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(column=9, row=1, padx=(0,15), pady=(0,5))
row += 1
for task_type, units in units.items(): tasks = list(units.keys())
Label(self.frame, text="{}".format(db.task_name(task_type)), **STYLES["strong"]).grid(row=row, columnspan=5, sticky=NSEW); row += 1 tasks_per_column = 3
units = list(set(units)) column = 0
units.sort(key=lambda x: db.PRICES[x]) for i, tasks_column in [(i, tasks[idx:idx+tasks_per_column]) for i, idx in enumerate(range(0, len(tasks), tasks_per_column))]:
for unit_type in units: row = 2
purchase_row(unit_type, db.PRICES[unit_type])
def purchase_row(unit_type, unit_price):
nonlocal row
nonlocal column
existing_units = self.base.total_units_of_type(unit_type)
scheduled_units = self.event.units.get(unit_type, 0)
Label(self.frame, text="{}".format(db.unit_type_name(unit_type)), **STYLES["widget"]).grid(row=row, column=column, sticky=W)
label = Label(self.frame, text="({}) ".format(existing_units), **STYLES["widget"])
label.grid(column=column + 1, row=row)
self.bought_amount_labels[unit_type] = label
Label(self.frame, text="{}m".format(unit_price), **STYLES["widget"]).grid(column=column + 2, row=row, sticky=E)
Button(self.frame, text="+", command=self.buy(unit_type), **STYLES["btn-primary"]).grid(column=column + 3, row=row, padx=(10,0))
Button(self.frame, text="-", command=self.sell(unit_type), **STYLES["btn-warning"]).grid(column=column + 4, row=row, padx=(10,5))
row += 1
for task_type in tasks_column:
Label(self.frame, text="{}".format(db.task_name(task_type)), **STYLES["strong"]).grid(row=row, column=column, columnspan=5, sticky=NSEW)
row += 1
units_column = list(set(units[task_type]))
units_column.sort(key=lambda x: db.PRICES[x])
for unit_type in units_column:
purchase_row(unit_type, db.PRICES[unit_type])
column += 5
def dismiss(self): def dismiss(self):
if sum([x for x in self.event.units.values()]) == 0: if sum([x for x in self.event.units.values()]) == 0:

View File

@@ -4,6 +4,7 @@ from tkinter import *
from tkinter.ttk import * from tkinter.ttk import *
from .styles import STYLES from .styles import STYLES
from userdata.logging import ShowLogsException
from ui.window import * from ui.window import *
@@ -17,6 +18,9 @@ class ConfigurationMenu(Menu):
self.enemy_skill_var = StringVar() self.enemy_skill_var = StringVar()
self.enemy_skill_var.set(self.game.settings.enemy_skill) self.enemy_skill_var.set(self.game.settings.enemy_skill)
self.enemy_vehicle_var = StringVar()
self.enemy_vehicle_var.set(self.game.settings.enemy_vehicle_skill)
self.takeoff_var = BooleanVar() self.takeoff_var = BooleanVar()
self.takeoff_var.set(self.game.settings.only_player_takeoff) self.takeoff_var.set(self.game.settings.only_player_takeoff)
@@ -29,6 +33,7 @@ class ConfigurationMenu(Menu):
def dismiss(self): def dismiss(self):
self.game.settings.player_skill = self.player_skill_var.get() self.game.settings.player_skill = self.player_skill_var.get()
self.game.settings.enemy_skill = self.enemy_skill_var.get() self.game.settings.enemy_skill = self.enemy_skill_var.get()
self.game.settings.enemy_vehicle_skill = self.enemy_vehicle_var.get()
self.game.settings.only_player_takeoff = self.takeoff_var.get() self.game.settings.only_player_takeoff = self.takeoff_var.get()
self.game.settings.night_disabled = self.night_var.get() self.game.settings.night_disabled = self.night_var.get()
self.game.settings.cold_start = self.cold_start_var.get() self.game.settings.cold_start = self.cold_start_var.get()
@@ -39,43 +44,64 @@ class ConfigurationMenu(Menu):
# Header # Header
head = Frame(self.frame, **STYLES["header"]) head = Frame(self.frame, **STYLES["header"])
head.grid(row=0, column=0, columnspan=2, sticky=NSEW) head.grid(row=0, column=0, sticky=NSEW)
Label(head, text="Configuration", **STYLES["title"]).grid() head.grid_columnconfigure(0, weight=100)
Label(head, text="Configuration", **STYLES["title"]).grid(row=0, sticky=W)
Button(head, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(row=0, column=1, sticky=E)
# Body # Body
body = Frame(self.frame, **STYLES["body"]) body = Frame(self.frame, **STYLES["body"])
body.grid(row=1, column=0, sticky=NSEW) body.grid(row=1, column=0, sticky=NSEW)
row = 0
Label(body, text="Player coalition skill", **STYLES["widget"]).grid(row=0, column=0, sticky=W) Label(body, text="Player coalition skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Label(body, text="Enemy coalition skill", **STYLES["widget"]).grid(row=1, column=0, sticky=W)
p_skill = OptionMenu(body, self.player_skill_var, "Average", "Good", "High", "Excellent") p_skill = OptionMenu(body, self.player_skill_var, "Average", "Good", "High", "Excellent")
p_skill.grid(row=0, column=1, sticky=E, pady=5) p_skill.grid(row=row, column=1, sticky=E, pady=5)
p_skill.configure(**STYLES["btn-primary"]) p_skill.configure(**STYLES["btn-primary"])
row += 1
Label(body, text="Enemy coalition skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
e_skill = OptionMenu(body, self.enemy_skill_var, "Average", "Good", "High", "Excellent") e_skill = OptionMenu(body, self.enemy_skill_var, "Average", "Good", "High", "Excellent")
e_skill.grid(row=1, column=1, sticky=E) e_skill.grid(row=row, column=1, sticky=E)
e_skill.configure(**STYLES["btn-primary"]) e_skill.configure(**STYLES["btn-primary"])
row += 1
Label(body, text="Aircraft cold start", **STYLES["widget"]).grid(row=2, column=0, sticky=W) Label(body, text="Enemy AA and vehicle skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Label(body, text="Takeoff only for player group", **STYLES["widget"]).grid(row=3, column=0, sticky=W) e_skill = OptionMenu(body, self.enemy_vehicle_var, "Average", "Good", "High", "Excellent")
Label(body, text="Disable night missions", **STYLES["widget"]).grid(row=4, column=0, sticky=W) e_skill.grid(row=row, column=1, sticky=E)
e_skill.configure(**STYLES["btn-primary"])
row += 1
Checkbutton(body, variable=self.cold_start_var, **STYLES["radiobutton"]).grid(row=2, column=1, sticky=E) Label(body, text="Aircraft cold start", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Checkbutton(body, variable=self.takeoff_var, **STYLES["radiobutton"]).grid(row=3, column=1, sticky=E) Checkbutton(body, variable=self.cold_start_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=4, column=1, sticky=E) row += 1
Button(body, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(row=5, column=1, sticky=E, pady=30) Label(body, text="Takeoff only for player group", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Checkbutton(body, variable=self.takeoff_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
row += 1
Label(body, text="Contributors: ", **STYLES["widget"]).grid(row=6, column=0, sticky=W) Label(body, text="Disable night missions", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
row += 1
Label(body, text="shdwp - author, maintainer", **STYLES["widget"]).grid(row=7, column=0, sticky=W) Button(body, text="Display logs", command=self.display_logs, **STYLES["btn-primary"]).grid(row=row, column=1, sticky=E, pady=30)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/shdwp"), **STYLES["widget"]).grid(row=7, column=1, sticky=E) row += 1
Label(body, text="Khopa - contributions", **STYLES["widget"]).grid(row=8, column=0, sticky=W) Label(body, text="Contributors: ", **STYLES["strong"]).grid(row=row, column=0, columnspan=2, sticky=EW)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=8, column=1, sticky=E) row += 1
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=10, column=1, pady=30) Label(body, text="shdwp - author, maintainer", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/shdwp"), **STYLES["widget"]).grid(row=row, column=1, sticky=E)
row += 1
Label(body, text="Khopa - contributions", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=row, column=1, sticky=E)
row += 1
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=row, column=1, pady=30)
def display_logs(self):
raise ShowLogsException()
def cheat_money(self): def cheat_money(self):
self.game.budget += 200 self.game.budget += 200

View File

@@ -9,7 +9,7 @@ from .styles import STYLES, RED
class EventMenu(Menu): class EventMenu(Menu):
scramble_entries = None # type: typing.Dict[typing.Type[Task], typing.Dict[typing.Type[UnitType], typing.Tuple[Entry, Entry]]] scramble_entries = None # type: typing.Dict[typing.Type[Task], typing.Dict[typing.Type[UnitType], typing.Tuple[Entry, Entry]]]
ca_slot_entry = None # type: Entry
error_label = None # type: Label error_label = None # type: Label
awacs = None # type: IntVar awacs = None # type: IntVar
@@ -69,15 +69,11 @@ class EventMenu(Menu):
row += 1 row += 1
threat_descr = self.event.threat_description
if threat_descr:
threat_descr = "Approx. {}".format(threat_descr)
# Header # Header
header("Mission Menu", "title") header("Mission Menu", "title")
# Mission Description # Mission Description
Label(self.frame, text="{}. {}".format(self.event, threat_descr), **STYLES["mission-preview"]).grid(row=row, column=0, columnspan=5, sticky=S+EW, padx=5, pady=5) Label(self.frame, text="{}".format(self.event), **STYLES["mission-preview"]).grid(row=row, column=0, columnspan=5, sticky=S+EW, padx=5, pady=5)
row += 1 row += 1
Label(self.frame, text="Amount", **STYLES["widget"]).grid(row=row, column=1, columnspan=2) Label(self.frame, text="Amount", **STYLES["widget"]).grid(row=row, column=1, columnspan=2)
@@ -100,8 +96,15 @@ class EventMenu(Menu):
header("Support:") header("Support:")
# Options # Options
awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED
Checkbutton(self.frame, var=self.awacs, state=awacs_enabled, **STYLES["radiobutton"]).grid(row=row, column=0, sticky=E) Label(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), **STYLES["widget"]).grid(row=row, column=0, sticky=W, pady=5)
Label(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), **STYLES["widget"]).grid(row=row, column=3, sticky=W, padx=5, pady=5) Checkbutton(self.frame, var=self.awacs, state=awacs_enabled, **STYLES["radiobutton"]).grid(row=row, column=4, sticky=E)
row += 1
Label(self.frame, text="Combined Arms Slots", **STYLES["widget"]).grid(row=row, sticky=W)
self.ca_slot_entry = Entry(self.frame, width=2)
self.ca_slot_entry.insert(0, "0")
self.ca_slot_entry.grid(column=3, row=row, sticky=E, padx=5)
Button(self.frame, text="+", command=self.add_ca_slot, **STYLES["btn-primary"]).grid(column=4, row=row, padx=5, sticky=W)
row += 1 row += 1
header("Ready?") header("Ready?")
@@ -124,6 +127,12 @@ class EventMenu(Menu):
return action return action
def add_ca_slot(self):
value = self.ca_slot_entry.get()
amount = int(value and value or "0")
self.ca_slot_entry.delete(0, END)
self.ca_slot_entry.insert(0, str(amount+1))
def client_one(self, task: typing.Type[Task], unit_type: UnitType) -> typing.Callable: def client_one(self, task: typing.Type[Task], unit_type: UnitType) -> typing.Callable:
def action(): def action():
entry = self.scramble_entries[task][unit_type][1] # type: Entry entry = self.scramble_entries[task][unit_type][1] # type: Entry
@@ -140,6 +149,13 @@ class EventMenu(Menu):
else: else:
self.event.is_awacs_enabled = False self.event.is_awacs_enabled = False
ca_slot_entry_value = self.ca_slot_entry.get()
try:
ca_slots = int(ca_slot_entry_value and ca_slot_entry_value or "0")
except:
ca_slots = 0
self.event.ca_slots = ca_slots
flights = {k: {} for k in self.event.tasks} # type: db.TaskForceDict flights = {k: {} for k in self.event.tasks} # type: db.TaskForceDict
units_scramble_counts = {} # type: typing.Dict[typing.Type[UnitType], int] units_scramble_counts = {} # type: typing.Dict[typing.Type[UnitType], int]
tasks_scramble_counts = {} # type: typing.Dict[typing.Type[Task], int] tasks_scramble_counts = {} # type: typing.Dict[typing.Type[Task], int]
@@ -178,6 +194,12 @@ class EventMenu(Menu):
self.error_label["text"] = "Need at least one player in flight {}".format(self.event.flight_name(task)) self.error_label["text"] = "Need at least one player in flight {}".format(self.event.flight_name(task))
return return
if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent):
if tasks_scramble_counts.get(PinpointStrike, 0) == 0:
self.error_label["text"] = "No ground vehicles assigned to attack!"
return
if self.game.is_player_attack(self.event): if self.game.is_player_attack(self.event):
self.event.player_attacking(flights) self.event.player_attacking(flights)
else: else:

View File

@@ -54,6 +54,7 @@ class EventResultsMenu(Menu):
pg.start(10) pg.start(10)
row += 1 row += 1
"""
Label(self.frame, text="Cheat operation results: ", **STYLES["strong"]).grid(column=0, row=row, Label(self.frame, text="Cheat operation results: ", **STYLES["strong"]).grid(column=0, row=row,
columnspan=2, sticky=NSEW, columnspan=2, sticky=NSEW,
pady=5) pady=5)
@@ -69,6 +70,7 @@ class EventResultsMenu(Menu):
Button(self.frame, text="some player losses", command=self.simulate_result(0.8, 0), Button(self.frame, text="some player losses", command=self.simulate_result(0.8, 0),
**STYLES["btn-warning"]).grid(column=1, row=row, padx=5, pady=5) **STYLES["btn-warning"]).grid(column=1, row=row, padx=5, pady=5)
row += 1 row += 1
"""
else: else:
row = 0 row = 0
@@ -99,12 +101,14 @@ class EventResultsMenu(Menu):
Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row) Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row)
row += 1 row += 1
Button(self.frame, text="Okay", command=self.dismiss, **STYLES["btn-primary"]).grid(columnspan=1, row=row); Button(self.frame, text="Okay", command=self.dismiss, **STYLES["btn-primary"]).grid(columnspan=1, row=row)
row += 1 row += 1
def process_debriefing(self, debriefing: Debriefing): def process_debriefing(self, debriefing: Debriefing):
self.debriefing = debriefing self.debriefing = debriefing
debriefing.calculate_units(mission=self.event.operation.mission,
debriefing.calculate_units(regular_mission=self.event.operation.regular_mission,
quick_mission=self.event.operation.quick_mission,
player_name=self.game.player, player_name=self.game.player,
enemy_name=self.game.enemy) enemy_name=self.game.enemy)
@@ -118,7 +122,7 @@ class EventResultsMenu(Menu):
def simulate_result(self, player_factor: float, enemy_factor: float): def simulate_result(self, player_factor: float, enemy_factor: float):
def action(): def action():
debriefing = Debriefing({}, []) debriefing = Debriefing({})
def count(country: Country) -> typing.Dict[UnitType, int]: def count(country: Country) -> typing.Dict[UnitType, int]:
result = {} result = {}

View File

@@ -20,69 +20,87 @@ class MainMenu(Menu):
self.upd.update() self.upd.update()
self.frame = self.window.right_pane self.frame = self.window.right_pane
self.frame.columnconfigure(0, weight=1) self.frame.rowconfigure(0, weight=0)
self.frame.rowconfigure(0, weight=1) self.frame.rowconfigure(1, weight=1)
def display(self): def display(self):
persistency.save_game(self.game) persistency.save_game(self.game)
self.window.clear_right_pane() self.window.clear_right_pane()
self.upd.update() self.upd.update()
row = 0
# Header : # Header :
header = Frame(self.frame, **STYLES["header"]) header = Frame(self.frame, **STYLES["header"])
Button(header, text="Configuration", command=self.configuration_menu, **STYLES["btn-primary"]).grid(column=0, row=0, sticky=NE) Button(header, text="Configuration", command=self.configuration_menu, **STYLES["btn-primary"]).grid(column=0, row=0, sticky=NW)
Label(header, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["strong"]).grid(column=1, row=0, sticky=NSEW, padx=50) Label(header, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["strong"]).grid(column=1, row=0, sticky=N+EW, padx=50)
Button(header, text="Pass turn", command=self.pass_turn, **STYLES["btn-primary"]).grid(column=2, row=0, sticky=NW) Button(header, text="Pass turn", command=self.pass_turn, **STYLES["btn-primary"]).grid(column=2, row=0, sticky=NE)
header.grid(column=0, row=0, sticky=N+EW) header.grid(column=0, row=0, sticky=N+EW)
body = LabelFrame(self.frame, **STYLES["body"]) content = Frame(self.frame, **STYLES["body"])
body.grid(column=0, row=1, sticky=NSEW) content.grid(column=0, row=1, sticky=NSEW)
column = 0
row = 0
def label(text): def label(text):
nonlocal row, body nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"]) frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=NSEW, columnspan=2) frame.grid(row=row, sticky=N+EW, columnspan=2)
Label(frame, text=text, **STYLES["widget"]).grid(row=row, sticky=NS) Label(frame, text=text, **STYLES["widget"]).grid(row=row, sticky=NS)
row += 1 row += 1
def event_button(event): def event_button(event):
nonlocal row, body nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"]) frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=NSEW) frame.grid(row=row, sticky=N+EW)
Message(frame, text="{}{}".format( Message(frame, text="{}".format(
event.defender_name == self.game.player and "Enemy attacking: " or "",
event event
), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=NSEW) ), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=N+EW)
Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E) Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E)
row += 1 row += 1
def destination_header(text, pady=0): def departure_header(text, style="strong"):
nonlocal row, body nonlocal row, body
Label(body, text=text, **STYLES["strong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(pady,0)); row += 1 Label(body, text=text, **STYLES[style]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(0, 5))
row += 1
def destination_header(text):
nonlocal row, body
Label(body, text=text, **STYLES["substrong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW)
row += 1
events = self.game.events events = self.game.events
events.sort(key=lambda x: x.to_cp.name) events.sort(key=lambda x: x.to_cp.name)
events.sort(key=lambda x: x.from_cp.name) events.sort(key=lambda x: x.from_cp.name)
events.sort(key=lambda x: x.informational and 2 or (self.game.is_player_attack(x) and 1 or 0)) events.sort(key=lambda x: x.informational and 1 or (self.game.is_player_attack(x) and 2 or 0))
destination = None destination = None
deliveries = False departure = None
for event in events: for event in events:
if event.informational:
new_departure = "Deliveries"
elif not self.game.is_player_attack(event):
new_departure = "Enemy attack"
else:
new_departure = event.from_cp.name
if new_departure != departure:
body = Frame(content, **STYLES["body"])
body.grid(column=column, row=1, sticky=N+EW)
row = 0
column += 1
departure = new_departure
departure_header(new_departure, style="strong" if self.game.is_player_attack(event) else "supstrong")
destination = None
if not event.informational: if not event.informational:
if self.game.is_player_attack(event): new_destination = "At {}".format(event.to_cp.name)
new_destination = "From {} to {}".format(event.from_cp.name, event.to_cp.name)
else:
new_destination = "Enemy attack"
if destination != new_destination: if destination != new_destination:
destination_header(new_destination) destination_header(new_destination)
destination = new_destination destination = new_destination
if event.informational: if event.informational:
if not deliveries:
deliveries = True
destination_header("Deliveries", 15)
label(str(event)) label(str(event))
else: else:
event_button(event) event_button(event)

View File

@@ -98,9 +98,6 @@ class NewGameMenu(Menu):
Label(terrain, text="Persian Gulf", **STYLES["widget"]).grid(row=2, column=1, sticky=W) Label(terrain, text="Persian Gulf", **STYLES["widget"]).grid(row=2, column=1, sticky=W)
self.create_label_image(terrain, "terrain_pg.gif").grid(row=2, column=2, padx=5) self.create_label_image(terrain, "terrain_pg.gif").grid(row=2, column=2, padx=5)
Label(terrain, text="Currently strike missions are only\navailable for a number of airports only in Caucasus", **STYLES["widget"]) \
.grid(row=3, column=0, columnspan=3, sticky=W)
# Misc Options # Misc Options
options = LabelFrame(body, text="Misc Options", **STYLES["label-frame"]) options = LabelFrame(body, text="Misc Options", **STYLES["label-frame"])
options.grid(row=0, column=2, sticky=NE, padx=5) options.grid(row=0, column=2, sticky=NE, padx=5)

View File

@@ -67,6 +67,14 @@ class OverviewCanvas:
self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image) self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image)
for cp in self.game.theater.controlpoints: 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) coords = self.transform_point(cp.position)
for connected_cp in cp.connected_points: for connected_cp in cp.connected_points:
connected_coords = self.transform_point(connected_cp.position) connected_coords = self.transform_point(connected_cp.position)
@@ -80,8 +88,15 @@ class OverviewCanvas:
self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color) 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): if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
frontline_pos, heading, distance = Conflict.frontline_vector(cp, connected_cp, self.game.theater) frontline = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
distance = max(distance, 1000) 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) start_coords = self.transform_point(frontline_pos, treshold=10)
end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60) end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60)

View File

@@ -12,6 +12,7 @@ GREEN = "#699245"
YELLOW = "#BF9A46" YELLOW = "#BF9A46"
RED = "#D0232E" RED = "#D0232E"
BG_TITLE_COLOR = "#2D3E50" BG_TITLE_COLOR = "#2D3E50"
BG_SUBTITLE_COLOR = "#3E4F61"
# Fonts # Fonts
FONT_FAMILY = "Trebuchet MS" FONT_FAMILY = "Trebuchet MS"
@@ -25,8 +26,10 @@ STYLES = {}
STYLES["label-frame"] = {"font": BOLD_FONT, "bg": BG_COLOR, "fg": FG_COLOR} STYLES["label-frame"] = {"font": BOLD_FONT, "bg": BG_COLOR, "fg": FG_COLOR}
STYLES["frame-wrapper"] = {"bg": BG_COLOR, "relief":"sunken"} STYLES["frame-wrapper"] = {"bg": BG_COLOR, "relief":"sunken"}
STYLES["body"] = {"bg": BG_COLOR, "padx": 25, "pady": 35} STYLES["body"] = {"bg": BG_COLOR, "padx": 10, "pady": 10}
STYLES["strong"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR} STYLES["strong"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR}
STYLES["substrong"] = {"font": BOLD_FONT, "bg": BG_SUBTITLE_COLOR, "fg": FG_COLOR}
STYLES["supstrong"] = {"font": BOLD_FONT, "bg": RED, "fg": FG_COLOR}
STYLES["strong-grey"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR_LIGHT} STYLES["strong-grey"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR_LIGHT}
STYLES["mission-preview"] = {"font": BOLD_FONT, "bg": YELLOW, "fg": FG_COLOR} STYLES["mission-preview"] = {"font": BOLD_FONT, "bg": YELLOW, "fg": FG_COLOR}
@@ -39,6 +42,7 @@ STYLES["title"] = {"bg": BG_TITLE_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pad
STYLES["title-green"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT} STYLES["title-green"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT}
STYLES["title-red"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT} STYLES["title-red"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT}
STYLES["header"] = {"bg": BG_TITLE_COLOR} STYLES["header"] = {"bg": BG_TITLE_COLOR}
STYLES["subheader"] = {"bg": BG_SUBTITLE_COLOR}
STYLES["btn-primary"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT} STYLES["btn-primary"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}
STYLES["btn-danger"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT} STYLES["btn-danger"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}

View File

@@ -11,7 +11,7 @@ class Window:
self.tk = Tk() self.tk = Tk()
self.tk.title("DCS Liberation") self.tk.title("DCS Liberation")
self.tk.iconbitmap("icon.ico") self.tk.iconbitmap("icon.ico")
self.tk.resizable(True, True) self.tk.resizable(False, False)
self.tk.grid_columnconfigure(0, weight=1) self.tk.grid_columnconfigure(0, weight=1)
self.tk.grid_rowconfigure(0, weight=1) self.tk.grid_rowconfigure(0, weight=1)
@@ -32,6 +32,10 @@ class Window:
self.tk.focus() self.tk.focus()
def clear_right_pane(self): def clear_right_pane(self):
for i in range(100):
self.right_pane.grid_columnconfigure(1, weight=0)
self.right_pane.grid_rowconfigure(1, weight=0)
for x in self.right_pane.winfo_children(): for x in self.right_pane.winfo_children():
x.grid_remove() x.grid_remove()

View File

@@ -17,6 +17,7 @@ from dcs.unit import UnitType
from game import db from game import db
from .persistency import base_path from .persistency import base_path
from theater.theatergroundobject import CATEGORY_MAP
DEBRIEFING_LOG_EXTENSION = "log" DEBRIEFING_LOG_EXTENSION = "log"
@@ -59,52 +60,26 @@ def parse_mutliplayer_debriefing(contents: str):
class Debriefing: class Debriefing:
def __init__(self, dead_units, dead_objects): def __init__(self, dead_units, trigger_state):
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]] self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
self.alive_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]] self.alive_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
self.destroyed_objects = [] # type: typing.List[str] self.destroyed_objects = [] # type: typing.List[str]
self._trigger_state = trigger_state
self._dead_units = dead_units self._dead_units = dead_units
self._dead_objects = dead_objects
@classmethod @classmethod
def parse(cls, path: str): def parse(cls, path: str):
dead_units = {} dead_units = []
dead_objects = []
def append_dead_unit(country_id, unit_type):
nonlocal dead_units
if country_id not in dead_units:
dead_units[country_id] = {}
if unit_type not in dead_units[country_id]:
dead_units[country_id][unit_type] = 0
dead_units[country_id][unit_type] += 1
def append_dead_object(object_mission_id_str): def append_dead_object(object_mission_id_str):
nonlocal dead_objects nonlocal dead_units
object_mission_id = int(object_mission_id_str) object_mission_id = int(object_mission_id_str)
if object_mission_id in dead_objects: if object_mission_id in dead_units:
logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id)) logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
return return
dead_objects.append(object_mission_id) dead_units.append(object_mission_id)
def parse_dead_unit(event):
try:
components = event["initiator"].split("|")
category, country_id, group_id, unit_type = components[0], int(components[1]), int(components[2]), db.unit_type_from_name(components[3])
if unit_type is None:
logging.info("Skipped {} due to no unit type".format(event))
return
if category == "unit":
append_dead_unit(country_id, unit_type)
else:
logging.info("Skipped {} due to category".format(event))
except Exception as e:
logging.error(e)
def parse_dead_object(event): def parse_dead_object(event):
try: try:
@@ -123,29 +98,18 @@ class Debriefing:
for event in events.values(): for event in events.values():
event_type = event.get("type", None) event_type = event.get("type", None)
if event_type in ["crash", "dead"]: if event_type in ["crash", "dead"]:
object_initiator = event["initiator"] in ["SKLAD_CRUSH", "SKLADCDESTR", "TEC_A_CRUSH", "BAK_CRUSH"] parse_dead_object(event)
defense_initiator = event["initiator"].startswith("defense|")
if object_initiator or defense_initiator: trigger_state = table.get("debriefing", {}).get("triggers_state", {})
parse_dead_object(event)
else:
parse_dead_unit(event)
return Debriefing(dead_units, dead_objects) return Debriefing(dead_units, trigger_state)
def calculate_units(self, mission: Mission, player_name: str, enemy_name: str): def calculate_units(self, regular_mission: Mission, quick_mission: Mission, player_name: str, enemy_name: str):
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]: def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
result = {} result = {}
for group in groups: for group in groups:
for unit in group.units: for unit in group.units:
unit_type = None unit_type = db.unit_type_of(unit)
if isinstance(unit, Vehicle):
unit_type = vehicle_map[unit.type]
elif isinstance(unit, Ship):
unit_type = ship_map[unit.type]
else:
unit_type = unit.unit_type
if unit_type in db.EXTRA_AA.values(): if unit_type in db.EXTRA_AA.values():
continue continue
@@ -153,6 +117,8 @@ class Debriefing:
return result return result
mission = regular_mission if len(self._trigger_state) else quick_mission
player = mission.country(player_name) player = mission.country(player_name)
enemy = mission.country(enemy_name) enemy = mission.country(enemy_name)
@@ -160,21 +126,45 @@ class Debriefing:
enemy_units = count_groups(enemy.plane_group + enemy.vehicle_group + enemy.ship_group) enemy_units = count_groups(enemy.plane_group + enemy.vehicle_group + enemy.ship_group)
self.destroyed_units = { self.destroyed_units = {
player.name: self._dead_units.get(player.id, {}), player.name: {},
enemy.name: self._dead_units.get(enemy.id, {}), enemy.name: {},
} }
all_groups = {
player.name: player.plane_group + player.helicopter_group + player.vehicle_group + player.ship_group,
enemy.name: enemy.plane_group + enemy.helicopter_group + enemy.vehicle_group + enemy.ship_group,
}
static_groups = enemy.static_group
for country_name, country_groups in all_groups.items():
for group in country_groups:
for unit in group.units:
if unit.id in self._dead_units:
unit_type = db.unit_type_of(unit)
logging.info("debriefing: found dead unit {} ({}, {})".format(str(unit.name), unit.id, unit_type))
assert country_name
assert unit_type
self.destroyed_units[country_name][unit_type] = self.destroyed_units[country_name].get(unit_type, 0) + 1
self._dead_units.remove(unit.id)
for group in static_groups:
identifier = group.units[0].id
if identifier in self._dead_units:
logging.info("debriefing: found dead static {} ({})".format(str(group.name), identifier))
assert str(group.name)
self.destroyed_objects.append(str(group.name))
self._dead_units.remove(identifier)
logging.info("debriefing: unsatistied ids: {}".format(self._dead_units))
self.alive_units = { self.alive_units = {
player.name: {k: v - self.destroyed_units[player.name].get(k, 0) for k, v in player_units.items()}, player.name: {k: v - self.destroyed_units[player.name].get(k, 0) for k, v in player_units.items()},
enemy.name: {k: v - self.destroyed_units[enemy.name].get(k, 0) for k, v in enemy_units.items()}, enemy.name: {k: v - self.destroyed_units[enemy.name].get(k, 0) for k, v in enemy_units.items()},
} }
for mission_id in self._dead_objects:
for group in mission.country(enemy.name).static_group + mission.country(enemy.name).vehicle_group:
if mission_id in [x.id for x in group.units]:
logging.info("debriefing: connected id {} to group {}".format(mission_id, str(group.name)))
self.destroyed_objects.append(str(group.name))
def debriefing_directory_location() -> str: def debriefing_directory_location() -> str:
return os.path.join(base_path(), "liberation_debriefings") return os.path.join(base_path(), "liberation_debriefings")

View File

@@ -9,9 +9,14 @@ from tkinter.scrolledtext import *
_version_string = None _version_string = None
def _error_prompt(): class ShowLogsException(Exception):
pass
def _error_prompt(oops=True):
tk = Tk() tk = Tk()
Label(tk, text="Oops, something went wrong.").grid(row=0) if oops:
Label(tk, text="Oops, something went wrong.").grid(row=0)
Label(tk, text="Please send following text to the developer:").grid(row=1) Label(tk, text="Please send following text to the developer:").grid(row=1)
text = ScrolledText(tk) text = ScrolledText(tk)
@@ -22,7 +27,7 @@ def _error_prompt():
def _handle_exception(self, exception: BaseException, *args): def _handle_exception(self, exception: BaseException, *args):
logging.exception(exception) logging.exception(exception)
_error_prompt() _error_prompt(isinstance(exception, ShowLogsException))
def setup_version_string(str): def setup_version_string(str):

View File

@@ -17,11 +17,11 @@ def base_path() -> str:
global _user_folder global _user_folder
assert _user_folder assert _user_folder
openbeta_path = os.path.join(_user_folder, "Saved Games", "DCS.openbeta") openbeta_path = os.path.join(_user_folder, "DCS.openbeta")
if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path): if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path):
return openbeta_path return openbeta_path
else: else:
return os.path.join(_user_folder, "Saved Games", "DCS") return os.path.join(_user_folder, "DCS")
def _save_file() -> str: def _save_file() -> str: