Compare commits

..

56 Commits

Author SHA1 Message Date
Vasyl Horbachenko
afb084ebf8 minor bugfixes 2018-09-11 05:55:05 +03:00
Vasyl Horbachenko
12853feec3 updated version check 2018-09-11 03:35:52 +03:00
Vasyl Horbachenko
d31876b65e FARP spawn for helis on frontline cas; updated ground object placements 2018-09-11 03:12:33 +03:00
Vasyl Horbachenko
ca521e7e51 FARPs for heli flights WIP 2018-09-10 23:12:04 +03:00
Vasyl Horbachenko
f21c515d5c convert resource files to gif 2018-09-10 21:46:34 +03:00
Vasyl Horbachenko
fa5259d1f2 minor fixes before rc 2018-09-10 01:48:38 +03:00
Vasyl Horbachenko
7a5361c057 minor updates and strike mission objects expand 2018-09-10 01:38:50 +03:00
Vasyl Horbachenko
40bfb6fa88 strike operations fixes 2018-09-09 23:56:30 +03:00
Vasyl Horbachenko
61a237d1ae proper settings for barcap 2018-09-09 20:57:32 +03:00
Vasyl Horbachenko
cf7276b528 updated AA units 2018-09-09 20:39:49 +03:00
Vasyl Horbachenko
67f69d0a7f updated AA units 2018-09-09 20:27:53 +03:00
Vasyl Horbachenko
a918914431 dont limit aircraft to predefined role; better scrambling screen 2018-09-09 20:21:07 +03:00
Vasyl Horbachenko
4ba1dd87e8 updates to strike missions; frontline operations invalid units placement fixed; minor UI updates 2018-09-09 04:15:44 +03:00
Vasyl Horbachenko
e0d82da6cb save game version check; mission description in briefings; minor fixes and improvements; predefined ground objects (statics) support; strike mission WIP 2018-09-09 01:21:32 +03:00
Vasyl Horbachenko
2179e4af47 Merge pull request #12 from Khopa/ui_dev
Modified UI Style
2018-09-08 19:15:52 +03:00
Khopa
75d734d6e4 Typo in UI 2018-08-09 13:08:07 +02:00
Khopa
1d73affa08 New style for event result menu 2018-08-08 23:40:10 +02:00
Khopa
834ee30c86 Style for event result menu 2018-08-08 23:18:49 +02:00
Khopa
d236b8a94d UI alignement in base menu 2018-08-08 22:38:09 +02:00
Khopa
a132cba7ef Changed the color of text on map (easier to read this way for now imo) 2018-08-08 22:30:33 +02:00
Khopa
86706231e0 New style for event/mission menu 2018-08-08 22:30:12 +02:00
Khopa
5eb921948a Display the units x/x/x value of the base in the base menu. 2018-08-08 21:52:09 +02:00
Khopa
fd54a5f86c Display the name of the base in base menu 2018-08-08 21:41:16 +02:00
Khopa
1b2ad5b419 Improved style of main menu, configuration menu, and base menu 2018-08-08 21:22:00 +02:00
Khopa
14cd54668e Improved style of new game menu 2018-08-08 18:45:25 +02:00
Khopa
2047089b64 Added icon to ui windows. 2018-08-08 16:27:42 +02:00
Khopa
ee924ef36e Added title name to UI window 2018-08-08 16:24:20 +02:00
Vasyl Horbachenko
f9c1dd980b spamram for fa18; non-randomized groups vertical separation on spawn; argument to affect DCS folder selection 2018-08-04 04:35:27 +03:00
Vasyl Horbachenko
74c1861240 build archieve tool 2018-07-31 23:40:35 +03:00
Vasyl Horbachenko
e8226782c1 Merge pull request #8 from shdwp/develop
minor balance improvements
2018-07-31 04:17:48 +03:00
Vasyl Horbachenko
a592cf3a05 minor balance improvements 2018-07-31 04:16:17 +03:00
Vasyl Horbachenko
0b1cb0d770 Merge pull request #7 from shdwp/develop
Develop
2018-07-31 04:00:18 +03:00
Vasyl Horbachenko
a4aa1cff3a widened spawn windows for aircraft 2018-07-31 03:59:55 +03:00
Vasyl Horbachenko
6a02d2ffb6 few minor fixes; added F5 2018-07-31 03:47:50 +03:00
Vasyl Horbachenko
7458181e90 success rate calculation division by zero fix 2018-07-29 04:41:32 +03:00
Vasyl Horbachenko
ec28cdc936 version bump 2018-07-29 04:20:30 +03:00
Vasyl Horbachenko
9dbc9a8a56 prompt window with logs on raised exception; minor UI updates; minor fixes 2018-07-29 04:16:39 +03:00
Vasyl Horbachenko
73d4a2d414 moved viggen to CAS; tanker position fix on enemy attacks; more logs 2018-07-28 04:58:28 +03:00
Vasyl Horbachenko
e93ad8b800 adjusted budget amounts 2018-07-20 04:08:04 +03:00
Vasyl Horbachenko
d48985ca6d new airports for PG; TACANs and ICLS; list frequencies in mission briefing; carrier ops improvements; cold start option 2018-07-20 04:06:13 +03:00
Vasyl Horbachenko
5f7d717b63 fixed sir abu having a frontline 2018-07-19 01:07:24 +03:00
Vasyl Horbachenko
e266698e68 fixed start.bat for usernames w/ spaces; fixed triggers for player defending; minor fixes 2018-07-19 00:57:15 +03:00
Vasyl Horbachenko
e8098e795c added symlink to pydcs to resources/tools 2018-07-18 03:20:18 +03:00
Vasyl Horbachenko
8d69724272 env gen minor update 2018-07-18 03:14:25 +03:00
Vasyl Horbachenko
683114f916 updates to CAP op 2018-07-18 00:45:55 +03:00
Vasyl Horbachenko
3b454470f9 Merge branch 'improved_cas' of https://github.com/shdwp/dcs_pmcliberation into improved_cas 2018-07-17 23:31:58 +03:00
Vasyl Horbachenko
f40f83bb09 fixes and improvements for fronline CAP 2018-07-17 14:22:45 +03:00
Vasyl Horbachenko
932bec2f84 fixes to frontline attack; frontline CAP WIP 2018-07-17 14:22:45 +03:00
Vasyl Horbachenko
820820eb92 frontline attack ops 2018-07-17 14:22:45 +03:00
Vasyl Horbachenko
6f5835a2b8 Improved Frontline CAS 2018-07-17 14:21:50 +03:00
Vasyl Horbachenko
b302372d4c fixes and improvements for fronline CAP 2018-07-17 05:22:41 +03:00
Vasyl Horbachenko
cad7d2c735 fixes to frontline attack; frontline CAP WIP 2018-07-17 02:14:46 +03:00
Vasyl Horbachenko
e4c3f8bce2 frontline attack ops 2018-07-16 23:58:01 +03:00
Vasyl Horbachenko
1fbf4e292a Merge branch 'improved_cas' of https://github.com/shdwp/dcs_pmcliberation into improved_cas 2018-07-16 23:03:47 +03:00
Vasyl Horbachenko
62f5b2d06d Improved Frontline CAS 2018-07-16 23:02:08 +03:00
Vasyl Horbachenko
b2545e4de0 Improved Frontline CAS 2018-07-16 22:19:19 +03:00
86 changed files with 2537 additions and 1199 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
__pycache__
build/*
resources/payloads/*.lua
venv
logs.txt
.DS_Store
# User-specific stuff

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python3
import os
import re
import sys
import dcs
import logging
import theater.caucasus
import theater.persiangulf
@@ -14,50 +16,16 @@ import ui.corruptedsavemenu
from game.game import Game
from theater import start_generator
from userdata import persistency
from userdata import persistency, logging as logging_module
"""
from dcs.lua.parse import *
a = loads(open("build/mission", "r").read())
b = loads(open("build/mission_workin.lua", "r").read())
def get(a, k):
b = a
for x in k.strip().split(" "):
if isinstance(a, dict):
y = a
a = a.get(x, None)
if a is None:
try:
a = y.get(int(x), None)
except:
pass
else:
break
if a is None:
pass
return a
def cycle(kk, ref, v):
if isinstance(v, dict):
for k, v in v.items():
cycle(kk + " " + str(k), ref, v)
elif isinstance(v, list):
for i, v in enumerate(v):
cycle(kk + " " + str(i), ref, v)
else:
if get(ref, kk) != v:
print(kk, v)
print(get(ref, kk))
cycle("", a, b)
sys.exit(0)
"""
assert len(sys.argv) >= 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
persistency.setup(sys.argv[1])
dcs.planes.FlyingType.payload_dirs.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads"))
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")]
VERSION_STRING = sys.argv[2]
logging_module.setup_version_string(VERSION_STRING)
logging.info("Using {} as userdata folder".format(persistency.base_path()))
def proceed_to_main_menu(game: Game):
@@ -65,10 +33,29 @@ def proceed_to_main_menu(game: Game):
m.display()
def is_version_compatible(save_version):
current_version_components = re.split(r"[\._]", VERSION_STRING)
save_version_components = re.split(r"[\._]", save_version)
if "--ignore-save" in sys.argv:
return False
if current_version_components == save_version_components:
return True
if save_version == "1.4_rc1":
return False
if current_version_components[:2] == save_version_components[:2]:
return True
return False
w = ui.window.Window()
try:
game = persistency.restore_game()
if not game:
if not game or not is_version_compatible(game.settings.version):
new_game_menu = None # type: NewGameMenu
def start_new_game(player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float):
@@ -90,6 +77,7 @@ try:
game.budget = int(game.budget * multiplier)
game.settings.multiplier = multiplier
game.settings.sams = sams
game.settings.version = VERSION_STRING
if midgame:
game.budget = game.budget * 4 * len(list(conflicttheater.conflicts()))
@@ -99,8 +87,10 @@ try:
new_game_menu = ui.newgamemenu.NewGameMenu(w, start_new_game)
new_game_menu.display()
else:
game.settings.version = VERSION_STRING
proceed_to_main_menu(game)
except Exception as e:
logging.exception(e)
ui.corruptedsavemenu.CorruptedSaveMenu(w).display()
w.run()

View File

@@ -1,4 +1,5 @@
import typing
import enum
from dcs.vehicles import *
from dcs.unitgroup import *
@@ -37,10 +38,10 @@ and prioritization for the enemy (i.e. less important bases will receive units w
PRICES = {
# fighter
C_101CC: 8,
MiG_23MLD: 20,
MiG_23MLD: 18,
Su_27: 24,
Su_33: 25,
MiG_29A: 22,
MiG_29A: 24,
MiG_29S: 26,
F_5E_3: 6,
@@ -73,6 +74,8 @@ PRICES = {
An_30M: 13,
Yak_40: 13,
S_3B_Tanker: 13,
IL_78M: 13,
KC_135: 13,
A_50: 8,
E_3A: 8,
@@ -97,13 +100,11 @@ PRICES = {
Unarmed.Transport_M818: 3,
AirDefence.AAA_Vulcan_M163: 5,
AirDefence.SAM_Avenger_M1097: 10,
AirDefence.SAM_Patriot_ICC: 15,
AirDefence.SAM_Linebacker_M6: 10,
AirDefence.AAA_ZU_23_on_Ural_375: 5,
AirDefence.SAM_SA_18_Igla_S_MANPADS: 8,
AirDefence.SAM_SA_19_Tunguska_2S6: 15,
AirDefence.SAM_SA_8_Osa_9A33: 13,
AirDefence.SPAAA_ZSU_23_4_Shilka: 8,
AirDefence.SAM_SA_9_Strela_1_9P31: 13,
AirDefence.SAM_SA_8_Osa_9A33: 18,
# ship
CV_1143_5_Admiral_Kuznetsov: 100,
@@ -134,8 +135,8 @@ Following tasks are present:
UNIT_BY_TASK = {
CAP: [
C_101CC,
AJS37,
F_5E_3,
MiG_23MLD,
Su_27,
Su_33,
MiG_21Bis,
@@ -149,6 +150,7 @@ UNIT_BY_TASK = {
MiG_15bis,
L_39ZA,
AV8BNA,
AJS37,
A_10A,
A_10C,
Su_25,
@@ -167,6 +169,11 @@ UNIT_BY_TASK = {
C_130,
],
Refueling: [
IL_78M,
KC_135,
],
AWACS: [E_3A, A_50, ],
PinpointStrike: [Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, Armor.APC_BTR_80, ],
@@ -174,17 +181,15 @@ UNIT_BY_TASK = {
# those are listed multiple times here to balance prioritization more into lower tier AAs
AirDefence.AAA_Vulcan_M163,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_19_Tunguska_2S6,
],
Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469],
@@ -199,10 +204,9 @@ UNIT_BY_TASK = {
Units from AirDefense category of UNIT_BY_TASK that will be removed from use if "No SAM" option is checked at the start of the game
"""
SAM_BAN = [
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
AirDefence.SAM_Linebacker_M6,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_8_Osa_9A33,
]
@@ -224,8 +228,8 @@ CARRIER_TAKEOFF_BAN = [
AirDefense units that will be spawned at control points not related to the current operation
"""
EXTRA_AA = {
"Russia": AirDefence.SAM_SA_9_Strela_1_9P31,
"USA": AirDefence.SAM_Patriot_EPP_III,
"Russia": AirDefence.SAM_SA_19_Tunguska_2S6,
"USA": AirDefence.SAM_Linebacker_M6,
}
"""
@@ -236,6 +240,7 @@ UNIT_BY_COUNTRY = {
"Russia": [
C_101CC,
AJS37,
MiG_23MLD,
F_5E_3,
Su_25,
Su_27,
@@ -251,6 +256,7 @@ UNIT_BY_COUNTRY = {
L_39ZA,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
@@ -260,10 +266,9 @@ UNIT_BY_COUNTRY = {
UH_1H,
Mi_8MT,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.SAM_SA_18_Igla_S_MANPADS,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_19_Tunguska_2S6,
Armor.APC_BTR_80,
Armor.MBT_T_90,
@@ -279,6 +284,7 @@ UNIT_BY_COUNTRY = {
],
"USA": [
F_5E_3,
F_15C,
FA_18C_hornet,
AJS37,
@@ -290,6 +296,7 @@ UNIT_BY_COUNTRY = {
A_10C,
AV8BNA,
KC_135,
S_3B_Tanker,
C_130,
E_3A,
@@ -305,8 +312,7 @@ UNIT_BY_COUNTRY = {
Infantry.Infantry_M4,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
AirDefence.SAM_Linebacker_M6,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
@@ -331,11 +337,15 @@ Payload will be used for operation of following type, "*" category will be used
"""
PLANE_PAYLOAD_OVERRIDES = {
FA_18C_hornet: {
"*": "AIM-9M*6, AIM-7M*2, FUEL*3",
CAP: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
},
Su_33: {
"*": "R-73*4,R-27R*2,R-27ER*6",
CAP: "R-73*4,R-27R*2,R-27ER*6",
},
AJS37: {
CAS: "CAS (75 GUN): RB-75*2, AKAN",
},
AV8BNA: {
@@ -344,18 +354,20 @@ PLANE_PAYLOAD_OVERRIDES = {
A_10C: {
CAS: "AGM-65D*2,AGM-65H*2,GBU-12*2,GBU-38*2,AIM-9*2,TGP,ECM,MK151*7",
GroundAttack: "AGM-65K*2,GBU-12*8,AIM-9M*2.ECM,TGP",
},
Ka_50: {
"*": "12x9A4172, 40xS-8",
CAS: "12x9A4172, 40xS-8",
GroundAttack: "12x9A4172, 40xS-8",
},
M_2000C: {
"*": "Combat Air Patrol",
CAP: "Combat Air Patrol",
},
MiG_21Bis: {
"*": "Patrol, medium range",
CAP: "Patrol, medium range",
}
}
@@ -381,7 +393,11 @@ HeliDict = typing.Dict[HelicopterType, int]
ArmorDict = typing.Dict[VehicleType, int]
ShipDict = typing.Dict[ShipType, int]
AirDefenseDict = typing.Dict[AirDefence, int]
StartingPosition = typing.Optional[typing.Union[ShipGroup, Airport, Point]]
AssignedUnitsDict = typing.Dict[typing.Type[UnitType], typing.Tuple[int, int]]
TaskForceDict = typing.Dict[typing.Type[Task], AssignedUnitsDict]
StartingPosition = typing.Optional[typing.Union[ShipGroup, StaticGroup, Airport, Point]]
def unit_task(unit: UnitType) -> Task:
@@ -432,6 +448,67 @@ def choose_units(for_task: Task, factor: float, count: int, country: str) -> typ
return list(set(suitable_unittypes[index_start:index_end]))
def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int):
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
def unitdict_split(unit_dict: UnitsDict, count: int):
buffer_dict = {}
for unit_type, unit_count in unit_dict.items():
for _ in range(unit_count):
unitdict_append(buffer_dict, unit_type, 1)
if sum(buffer_dict.values()) >= count:
yield buffer_dict
buffer_dict = {}
if len(buffer_dict):
yield buffer_dict
def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict:
if total_count == 0:
return {}
groups = list(unitdict_split(unit_dict, total_count))
if len(groups) > 0:
return groups[0]
else:
return {}
def assigned_units_split(fd: AssignedUnitsDict) -> typing.Tuple[PlaneDict, PlaneDict]:
return {k: v1 for k, (v1, v2) in fd.items()}, {k: v2 for k, (v1, v2) in fd.items()},
def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
return {k: (v, 0) for k, v in d.items()}
def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int):
buffer_dict = {}
for unit_type, (unit_count, client_count) in dict.items():
for _ in range(unit_count):
new_count, new_client_count = buffer_dict.get(unit_type, (0, 0))
new_count += 1
if client_count > 0:
new_client_count += 1
client_count -= 1
buffer_dict[unit_type] = new_count, new_client_count
if new_count >= count:
yield buffer_dict
buffer_dict = {}
if len(buffer_dict):
yield buffer_dict
def unitdict_from(fd: AssignedUnitsDict) -> Dict:
return {k: v1 for k, (v1, v2) in fd.items()}
def _validate_db():
# check unit by task uniquity
total_set = set()

View File

@@ -1,8 +1,9 @@
from .event import *
from .groundintercept import *
from .frontlineattack import *
from .frontlinepatrol import *
from .intercept import *
from .capture import *
from .baseattack import *
from .navalintercept import *
from .antiaastrike import *
from .groundattack import *
from .insurgentattack import *
from .infantrytransport import *
from .strike import *

View File

@@ -1,86 +0,0 @@
import math
import random
from dcs.task import *
from game import *
from game.event import *
from game.operation.antiaastrike import AntiAAStrikeOperation
from userdata.debriefing import Debriefing
class AntiAAStrikeEvent(Event):
TARGET_AMOUNT_MAX = 2
STRENGTH_INFLUENCE = 0.3
SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5
targets = None # type: db.ArmorDict
def __str__(self):
return "Anti-AA strike from {} at {}".format(self.from_cp, self.to_cp)
def is_successfull(self, debriefing: Debriefing):
total_targets = sum(self.targets.values())
destroyed_targets = 0
for unit, count in debriefing.destroyed_units[self.defender_name].items():
if unit in self.targets:
destroyed_targets += count
if self.from_cp.captured:
return math.ceil(float(destroyed_targets) / total_targets) >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
else:
return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_TARGETS_HIT_PERCENTAGE
def commit(self, debriefing: Debriefing):
super(AntiAAStrikeEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def skip(self):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
self.targets = self.to_cp.base.assemble_aa(count=self.to_cp.base.total_aa)
op = AntiAAStrikeOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors={})
self.operation = op
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
self.targets = self.to_cp.base.assemble_aa()
op = AntiAAStrikeOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp
)
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors=interceptors)
self.operation = op

View File

@@ -1,22 +1,28 @@
import math
import random
from game.operation.baseattack import BaseAttackOperation
from dcs.task import *
from game import db
from game.operation.capture import CaptureOperation
from userdata.debriefing import Debriefing
from .event import Event
from .event import *
from game.db import assigned_units_from
class CaptureEvent(Event):
class BaseAttackEvent(Event):
silent = True
BONUS_BASE = 15
STRENGTH_RECOVERY = 0.55
def __str__(self):
return "Attack from {} to {}".format(self.from_cp, self.to_cp)
return "Base attack"
@property
def tasks(self):
return [CAP, CAS, PinpointStrike]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAP:
return "Escort flight"
elif for_task == CAS:
return "CAS flight"
elif for_task == PinpointStrike:
return "Ground attack"
def is_successfull(self, debriefing: Debriefing):
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
@@ -28,10 +34,11 @@ class CaptureEvent(Event):
return not attackers_success
def commit(self, debriefing: Debriefing):
super(CaptureEvent, self).commit(debriefing)
super(BaseAttackEvent, self).commit(debriefing)
if self.is_successfull(debriefing):
if self.from_cp.captured:
self.to_cp.captured = True
self.to_cp.ground_objects = []
self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name])
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
@@ -44,44 +51,44 @@ class CaptureEvent(Event):
if not self.is_player_attacking and self.to_cp.captured:
self.to_cp.captured = False
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
def player_defending(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid scrambled flights"
cas = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier)
attackers = self.from_cp.base.armor
op = CaptureOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp)
op = BaseAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(cas=cas,
escort=escort,
op.setup(cas=assigned_units_from(cas),
escort=assigned_units_from(escort),
intercept=flights[CAP],
attack=attackers,
intercept=interceptors,
defense=self.to_cp.base.armor,
aa=self.to_cp.base.aa)
self.operation = op
def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict):
op = CaptureOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp)
def player_attacking(self, flights: db.TaskForceDict):
assert CAP in flights and CAS in flights and PinpointStrike in flights and len(flights) == 3, "Invalid flights"
op = BaseAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp)
defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier)
defenders.update(self.to_cp.base.scramble_cas(self.game.settings.multiplier))
op.setup(cas=cas,
escort=escort,
attack=armor,
intercept=defenders,
op.setup(cas=flights[CAS],
escort=flights[CAP],
attack=flights[PinpointStrike],
intercept=assigned_units_from(defenders),
defense=self.to_cp.base.armor,
aa=self.to_cp.base.assemble_aa())

View File

@@ -1,8 +1,15 @@
import typing
import logging
from dcs.unittype import UnitType
from dcs.task import *
from dcs.vehicles import AirDefence
from dcs.unittype import UnitType
from game import *
from theater import *
from gen.environmentgen import EnvironmentSettings
from game.db import assigned_units_from, unitdict_from
from userdata.debriefing import Debriefing
from userdata import persistency
@@ -42,12 +49,29 @@ class Event:
def threat_description(self) -> str:
return ""
def flight_name(self, for_task: typing.Type[typing.Type[Task]]) -> str:
return "Flight"
@property
def tasks(self) -> typing.Collection[typing.Type[Task]]:
return []
@property
def ai_banned_tasks(self) -> typing.Collection[typing.Type[Task]]:
return []
def bonus(self) -> int:
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
def is_successfull(self, debriefing: Debriefing) -> bool:
return self.operation.is_successfull(debriefing)
def player_attacking(self, flights: db.TaskForceDict):
assert False
def player_defending(self, flights: db.TaskForceDict):
assert False
def generate(self):
self.operation.is_awacs_enabled = self.is_awacs_enabled
@@ -71,8 +95,24 @@ class Event:
else:
cp = self.to_cp
logging.info("base {} commit losses {}".format(cp.base, losses))
cp.base.commit_losses(losses)
for object_identifier in debriefing.destroyed_objects:
for cp in self.game.theater.controlpoints:
remove_ids = []
if not cp.ground_objects:
continue
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.matches_string_identifier(object_identifier):
logging.info("cp {} removing ground object {}".format(cp, ground_object.string_identifier))
remove_ids.append(i)
remove_ids.reverse()
for i in remove_ids:
del cp.ground_objects[i]
def skip(self):
pass

View File

@@ -0,0 +1,82 @@
from game.event import *
from game.operation.frontlineattack import FrontlineAttackOperation
from userdata.debriefing import Debriefing
class FrontlineAttackEvent(Event):
TARGET_VARIETY = 2
TARGET_AMOUNT_FACTOR = 0.5
ATTACKER_AMOUNT_FACTOR = 0.4
ATTACKER_DEFENDER_FACTOR = 0.7
STRENGTH_INFLUENCE = 0.2
SUCCESS_FACTOR = 1.5
defenders = None # type: db.ArmorDict
@property
def threat_description(self):
return "{} vehicles".format(self.to_cp.base.assemble_count())
@property
def tasks(self) -> typing.Collection[typing.Type[Task]]:
if self.is_player_attacking:
return [CAS, PinpointStrike]
else:
return [CAP, PinpointStrike]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAS:
return "CAS flight"
elif for_task == CAP:
return "CAP flight"
elif for_task == PinpointStrike:
return "Ground attack"
def __str__(self):
return "Frontline attack"
def is_successfull(self, debriefing: Debriefing):
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
attackers_success = (float(alive_attackers) / (alive_defenders + 0.01)) > self.SUCCESS_FACTOR
if self.from_cp.captured:
return attackers_success
else:
return not attackers_success
def commit(self, debriefing: Debriefing):
super(FrontlineAttackEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def skip(self):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, flights: db.TaskForceDict):
assert CAS in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights"
self.defenders = self.to_cp.base.assemble_attack()
op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp)
armor = unitdict_from(flights[PinpointStrike])
op.setup(target=self.defenders,
attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())),
strikegroup=flights[CAS])
self.operation = op

View File

@@ -0,0 +1,76 @@
from game.event import *
from game.operation.frontlinepatrol import FrontlinePatrolOperation
from userdata.debriefing import Debriefing
class FrontlinePatrolEvent(Event):
ESCORT_FACTOR = 0.5
STRENGTH_INFLUENCE = 0.2
SUCCESS_FACTOR = 0.8
cas = None # type: db.PlaneDict
escort = None # type: db.PlaneDict
@property
def threat_description(self):
return "{} aircraft + ? CAS".format(self.to_cp.base.scramble_count(self.game.settings.multiplier * self.ESCORT_FACTOR, CAP))
@property
def tasks(self):
return [CAP, PinpointStrike]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAP:
return "CAP flight"
elif for_task == PinpointStrike:
return "Ground attack"
def __str__(self):
return "Frontline CAP"
def is_successfull(self, debriefing: Debriefing):
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
attackers_success = (float(alive_attackers) / (alive_defenders + 0.01)) >= self.SUCCESS_FACTOR
if self.from_cp.captured:
return attackers_success
else:
return not attackers_success
def commit(self, debriefing: Debriefing):
super(FrontlinePatrolEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def skip(self):
pass
def player_attacking(self, flights: db.TaskForceDict):
assert CAP in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights"
self.cas = self.to_cp.base.scramble_cas(self.game.settings.multiplier)
self.escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier * self.ESCORT_FACTOR)
op = FrontlinePatrolOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp)
defenders = self.to_cp.base.assemble_attack()
op.setup(cas=assigned_units_from(self.cas),
escort=assigned_units_from(self.escort),
interceptors=flights[CAP],
armor_attackers=db.unitdict_restrict_count(db.unitdict_from(flights[PinpointStrike]), sum(defenders.values())),
armor_defenders=defenders)
self.operation = op

View File

@@ -1,40 +0,0 @@
import math
import random
from dcs.task import *
from game import *
from game.event import *
from game.event.groundintercept import GroundInterceptEvent
from game.operation.groundattack import GroundAttackOperation
class GroundAttackEvent(GroundInterceptEvent):
def __str__(self):
return "Destroy insurgents at {}".format(self.to_cp)
@property
def threat_description(self):
return ""
def player_defending(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
suitable_unittypes = db.find_unittype(Reconnaissance, self.attacker_name)
random.shuffle(suitable_unittypes)
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
self.targets = {unittype: typecount for unittype in unittypes}
op = GroundAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=strikegroup)
self.operation = op
def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
assert False

View File

@@ -1,106 +0,0 @@
import math
import random
from dcs.task import *
from dcs.vehicles import AirDefence
from game import *
from game.event import *
from game.operation.groundintercept import GroundInterceptOperation
from userdata.debriefing import Debriefing
class GroundInterceptEvent(Event):
TARGET_AMOUNT_FACTOR = 2
TARGET_VARIETY = 2
STRENGTH_INFLUENCE = 0.3
SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5
targets = None # type: db.ArmorDict
@property
def threat_description(self):
if not self.game.is_player_attack(self):
return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS))
else:
return super(GroundInterceptEvent, self).threat_description
def __str__(self):
return "Frontline CAS from {} at {}".format(self.from_cp, self.to_cp)
def is_successfull(self, debriefing: Debriefing):
total_targets = sum(self.targets.values())
destroyed_targets = 0
for unit, count in debriefing.destroyed_units[self.defender_name].items():
if unit in self.targets:
destroyed_targets += count
if self.from_cp.captured:
return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
else:
return float(destroyed_targets) / total_targets < self.SUCCESS_TARGETS_HIT_PERCENTAGE
def commit(self, debriefing: Debriefing):
super(GroundInterceptEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def skip(self):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_name)
random.shuffle(suitable_unittypes)
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
self.targets = {unittype: typecount for unittype in unittypes}
defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence))
self.targets[defense_aa_unit] = 1
op = GroundInterceptOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors={})
self.operation = op
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_name)
random.shuffle(suitable_unittypes)
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
self.targets = {unittype: typecount for unittype in unittypes}
op = GroundInterceptOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp
)
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors=interceptors)
self.operation = op

View File

@@ -9,14 +9,22 @@ from game.operation.infantrytransport import InfantryTransportOperation
from theater.conflicttheater import *
from userdata.debriefing import Debriefing
from .event import Event
from .event import *
class InfantryTransportEvent(Event):
STRENGTH_INFLUENCE = 0.3
def __str__(self):
return "Frontline transport troops to {}".format(self.to_cp)
return "Frontline transport troops"
@property
def tasks(self):
return [Embarking]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == Embarking:
return "Transport flight"
def is_successfull(self, debriefing: Debriefing):
return True
@@ -29,19 +37,19 @@ class InfantryTransportEvent(Event):
else:
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def player_attacking(self, transport: db.HeliDict, clients: db.HeliDict):
def player_attacking(self, flights: db.TaskForceDict):
assert Embarking in flights and len(flights) == 1, "Invalid flights"
op = InfantryTransportOperation(
game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp
)
air_defense = db.find_unittype(AirDefence, self.defender_name)[0]
op.setup(transport=transport,
op.setup(transport=flights[Embarking],
aa={air_defense: 2})
self.operation = op

View File

@@ -0,0 +1,60 @@
import math
import random
from dcs.task import *
from game import *
from game.event import *
from game.event.frontlineattack import FrontlineAttackEvent
from game.operation.insurgentattack import InsurgentAttackOperation
from .event import *
class InsurgentAttackEvent(Event):
SUCCESS_FACTOR = 0.7
TARGET_VARIETY = 2
TARGET_AMOUNT_FACTOR = 0.5
@property
def threat_description(self):
return ""
@property
def tasks(self):
return [CAS]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAS:
return "Ground intercept flight"
def __str__(self):
return "Destroy insurgents"
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])
all_units = sum(self.targets.values())
attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR
if self.from_cp.captured:
return attackers_success
else:
return not attackers_success
def player_defending(self, flights: db.TaskForceDict):
assert CAS in flights and len(flights) == 1, "Invalid flights"
suitable_unittypes = db.find_unittype(Reconnaissance, self.attacker_name)
random.shuffle(suitable_unittypes)
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
self.targets = {unittype: typecount for unittype in unittypes}
op = InsurgentAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=flights[CAS])
self.operation = op

View File

@@ -1,15 +1,6 @@
import math
import random
from dcs.task import *
from dcs.vehicles import *
from game import db
from game.operation.intercept import InterceptOperation
from theater.conflicttheater import *
from userdata.debriefing import Debriefing
from .event import Event
from .event import *
class InterceptEvent(Event):
@@ -20,11 +11,26 @@ class InterceptEvent(Event):
transport_unit = None # type: FlyingType
def __str__(self):
return "Intercept from {} at {}".format(self.from_cp, self.to_cp)
return "Air Intercept"
@property
def tasks(self):
return [CAP]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAP:
if self.is_player_attacking:
return "Intercept flight"
else:
return "Escort flight"
def _enemy_scramble_multiplier(self) -> float:
is_global = self.from_cp.is_global or self.to_cp.is_global
return self.game.settings.multiplier * is_global and 0.5 or 1
@property
def threat_description(self):
return "{} aircraft".format(self.enemy_cp.base.scramble_count(self.game.settings.multiplier, CAP))
return "{} aircraft".format(self.enemy_cp.base.scramble_count(self._enemy_scramble_multiplier(), CAP))
def is_successfull(self, debriefing: Debriefing):
units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0)
@@ -53,8 +59,10 @@ class InterceptEvent(Event):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier)
def player_attacking(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid flights"
escort = self.to_cp.base.scramble_sweep(self._enemy_scramble_multiplier())
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
assert self.transport_unit is not None
@@ -63,20 +71,19 @@ class InterceptEvent(Event):
op = InterceptOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(escort=escort,
op.setup(escort=assigned_units_from(escort),
transport={self.transport_unit: 1},
airdefense={airdefense_unit: self.AIRDEFENSE_COUNT},
interceptors=interceptors)
interceptors=flights[CAP])
self.operation = op
def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict):
# TODO: even not quick mission is too quick
def player_defending(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid flights"
interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier)
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
@@ -85,14 +92,12 @@ class InterceptEvent(Event):
op = InterceptOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(escort=escort,
op.setup(escort=flights[CAP],
transport={self.transport_unit: 1},
interceptors=interceptors,
interceptors=assigned_units_from(interceptors),
airdefense={})
self.operation = op

View File

@@ -1,15 +1,6 @@
import typing
import math
import random
from dcs.task import *
from dcs.vehicles import *
from game import db
from game.operation.navalintercept import NavalInterceptionOperation
from userdata.debriefing import Debriefing
from .event import Event
from .event import *
class NavalInterceptEvent(Event):
@@ -19,12 +10,25 @@ class NavalInterceptEvent(Event):
targets = None # type: db.ShipDict
def _targets_count(self) -> int:
from gen.conflictgen import IMPORTANCE_LOW, IMPORTANCE_HIGH
from gen.conflictgen import IMPORTANCE_LOW
factor = (self.to_cp.importance - IMPORTANCE_LOW) * 10
return max(int(factor), 1)
def __str__(self) -> str:
return "Naval intercept at {}".format(self.to_cp)
return "Naval intercept"
@property
def tasks(self):
if self.is_player_attacking:
return [CAS]
else:
return [CAP]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAS:
return "Naval intercept flight"
elif for_task == CAP:
return "CAP flight"
@property
def threat_description(self):
@@ -64,7 +68,9 @@ class NavalInterceptEvent(Event):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
def player_attacking(self, flights: db.TaskForceDict):
assert CAS in flights and len(flights) == 1, "Invalid flights"
self.targets = {
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
}
@@ -73,19 +79,19 @@ class NavalInterceptEvent(Event):
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp
)
op.setup(strikegroup=strikegroup,
op.setup(strikegroup=flights[CAS],
interceptors={},
targets=self.targets)
self.operation = op
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
def player_defending(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid flights"
self.targets = {
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
}
@@ -94,15 +100,13 @@ class NavalInterceptEvent(Event):
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp
)
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
op.setup(strikegroup=strikegroup,
interceptors=interceptors,
op.setup(strikegroup=assigned_units_from(strikegroup),
interceptors=flights[CAP],
targets=self.targets)
self.operation = op

60
game/event/strike.py Normal file
View File

@@ -0,0 +1,60 @@
from game.operation.strike import StrikeOperation
from .event import *
class StrikeEvent(Event):
STRENGTH_INFLUENCE = 0.0
SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.03
def __str__(self):
return "Strike"
def is_successfull(self, debriefing: Debriefing):
return True
@property
def threat_description(self):
return "{} aircraft + AA".format(self.to_cp.base.scramble_count(self.game.settings.multiplier, CAP))
@property
def tasks(self):
if self.is_player_attacking:
return [CAP, CAS]
else:
return [CAP]
@property
def ai_banned_tasks(self):
return [CAS]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAP:
if self.is_player_attacking:
return "Escort flight"
else:
return "CAP flight"
elif for_task == CAS:
return "Strike flight"
def commit(self, debriefing: Debriefing):
super(StrikeEvent, self).commit(debriefing)
self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects))
def player_attacking(self, flights: db.TaskForceDict):
assert CAP in flights and CAS in flights and len(flights) == 2, "Invalid flights"
op = StrikeOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
to_cp=self.to_cp
)
interceptors = self.to_cp.base.scramble_interceptors(self.game.settings.multiplier)
op.setup(strikegroup=flights[CAS],
escort=flights[CAP],
interceptors=assigned_units_from(interceptors))
self.operation = op

View File

@@ -1,3 +1,4 @@
import logging
import typing
import random
import math
@@ -5,6 +6,7 @@ import math
from dcs.task import *
from dcs.vehicles import *
from gen.conflictgen import Conflict
from userdata.debriefing import Debriefing
from theater import *
@@ -23,14 +25,15 @@ COMMISION_LIMITS_FACTORS = {
COMMISION_AMOUNTS_SCALE = 1.5
COMMISION_AMOUNTS_FACTORS = {
PinpointStrike: 2,
PinpointStrike: 6,
CAS: 1,
CAP: 2,
AirDefence: 0.3,
}
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 25
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2
PLAYER_BASEATTACK_THRESHOLD = 0.2
"""
Various events probabilities. First key is player probabilty, second is enemy probability.
@@ -39,19 +42,20 @@ For the enemy events, only 1 event of each type could be generated for a turn.
Events:
* CaptureEvent - capture base
* InterceptEvent - air intercept
* GroundInterceptEvent - frontline CAS
* FrontlineAttack - frontline attack
* GroundAttackEvent - destroy insurgents
* NavalInterceptEvent - naval intercept
* AntiAAStrikeEvent - anti-AA strike
* InfantryTransportEvent - helicopter infantry transport
"""
EVENT_PROBABILITIES = {
CaptureEvent: [100, 10],
BaseAttackEvent: [100, 10],
FrontlineAttackEvent: [100, 0],
FrontlinePatrolEvent: [100, 0],
StrikeEvent: [100, 0],
InterceptEvent: [25, 10],
GroundInterceptEvent: [25, 10],
GroundAttackEvent: [0, 10],
InsurgentAttackEvent: [0, 10],
NavalInterceptEvent: [25, 10],
AntiAAStrikeEvent: [25, 10],
InfantryTransportEvent: [25, 0],
}
@@ -65,9 +69,9 @@ ENEMY_BASE_STRENGTH_RECOVERY = 0.05
AWACS_BUDGET_COST = 4
# Initial budget value
PLAYER_BUDGET_INITIAL = 120
PLAYER_BUDGET_INITIAL = 170
# Base post-turn bonus value
PLAYER_BUDGET_BASE = 10
PLAYER_BUDGET_BASE = 17
# Bonus multiplier logarithm base
PLAYER_BUDGET_IMPORTANCE_LOG = 2
@@ -112,12 +116,22 @@ class Game:
continue
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
if self._roll(player_probability, player_cp.base.strength):
if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent:
if not Conflict.has_frontline_between(player_cp, enemy_cp):
continue
if player_probability == 100 or self._roll(player_probability, player_cp.base.strength):
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
pass
else:
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
elif self._roll(enemy_probability, enemy_cp.base.strength):
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
@@ -130,15 +144,15 @@ class Game:
if event_class == NavalInterceptEvent:
if player_cp.radials == LAND:
continue
elif event_class == CaptureEvent:
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
elif event_class == AntiAAStrikeEvent:
if player_cp.base.total_aa == 0:
continue
enemy_generated_types.append(event_class)
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
@@ -161,7 +175,7 @@ class Game:
if points_to_spend > 0:
unittypes = self.commision_unit_types(cp, for_task)
d = {random.choice(unittypes): points_to_spend}
print("Commision {}: {}".format(cp, d))
logging.info("Commision {}: {}".format(cp, d))
cp.base.commision_units(d)
@property
@@ -194,10 +208,13 @@ class Game:
def initiate_event(self, event: Event):
assert event in self.events
logging.info("Generating {} (regular)".format(event))
event.generate()
logging.info("Generating {} (quick)".format(event))
event.generate_quick()
def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event))
event.commit(debriefing)
if event.is_successfull(debriefing):
self.budget += event.bonus()
@@ -205,12 +222,16 @@ class Game:
if event in self.events:
self.events.remove(event)
else:
print("finish_event: event not in the events!")
logging.info("finish_event: event not in the events!")
def is_player_attack(self, event: Event):
return event.attacker_name == self.player
def is_player_attack(self, event):
if isinstance(event, Event):
return event.attacker_name == self.player
else:
return event.name == self.player
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None):
logging.info("Pass turn")
for event in self.events:
event.skip()

View File

@@ -1,53 +0,0 @@
from dcs.terrain import Terrain
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.awacsgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
class AntiAAStrikeOperation(Operation):
strikegroup = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
strikegroup: db.PlaneDict,
interceptors: db.PlaneDict):
self.strikegroup = strikegroup
self.interceptors = interceptors
self.target = target
def prepare(self, terrain: Terrain, is_quick: bool):
super(AntiAAStrikeOperation, self).prepare(terrain, is_quick)
if self.defender_name == self.game.player:
self.attackers_starting_position = None
self.defenders_starting_position = None
conflict = Conflict.ground_base_attack(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
if self.interceptors:
self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position)
self.armorgen.generate({}, self.target)
super(AntiAAStrikeOperation, self).generate()

View File

@@ -0,0 +1,63 @@
from game.db import assigned_units_split
from gen.triggergen import *
from .operation import *
class BaseAttackOperation(Operation):
cas = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
intercept = None # type: db.AssignedUnitsDict
attack = None # type: db.ArmorDict
defense = None # type: db.ArmorDict
aa = None # type: db.AirDefenseDict
trigger_radius = TRIGGER_RADIUS_SMALL
def setup(self,
cas: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
attack: db.AssignedUnitsDict,
intercept: db.AssignedUnitsDict,
defense: db.ArmorDict,
aa: db.AirDefenseDict):
self.cas = cas
self.escort = escort
self.intercept = intercept
self.attack = attack
self.defense = defense
self.aa = aa
def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool):
super(BaseAttackOperation, self).prepare(terrain, is_quick)
self.defenders_starting_position = None
if self.game.player == self.defender_name:
self.attackers_starting_position = None
conflict = Conflict.capture_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.armorgen.generate(self.attack, self.defense)
self.aagen.generate(self.aa)
self.airgen.generate_defense(*assigned_units_split(self.intercept), at=self.defenders_starting_position)
self.airgen.generate_cas_strikegroup(*assigned_units_split(self.cas), at=self.attackers_starting_position)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
self.visualgen.generate_target_smokes(self.to_cp)
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."
super(BaseAttackOperation, self).generate()

View File

@@ -1,67 +0,0 @@
from game import db
from gen.conflictgen import Conflict
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.awacsgen import *
from gen.visualgen import *
from .operation import Operation
class CaptureOperation(Operation):
cas = None # type: db.PlaneDict
escort = None # type: db.PlaneDict
intercept = None # type: db.PlaneDict
attack = None # type: db.ArmorDict
defense = None # type: db.ArmorDict
aa = None # type: db.AirDefenseDict
trigger_radius = TRIGGER_RADIUS_SMALL
def setup(self,
cas: db.PlaneDict,
escort: db.PlaneDict,
attack: db.ArmorDict,
intercept: db.PlaneDict,
defense: db.ArmorDict,
aa: db.AirDefenseDict):
self.cas = cas
self.escort = escort
self.intercept = intercept
self.attack = attack
self.defense = defense
self.aa = aa
def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool):
super(CaptureOperation, self).prepare(terrain, is_quick)
self.defenders_starting_position = None
if self.game.player == self.defender_name:
self.attackers_starting_position = None
conflict = Conflict.capture_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.armorgen.generate(self.attack, self.defense)
self.aagen.generate(self.aa)
self.airgen.generate_defense(self.intercept, clients=self.defender_clients, at=self.defenders_starting_position)
self.airgen.generate_cas_strikegroup(self.cas, clients=self.attacker_clients, at=self.attackers_starting_position)
self.airgen.generate_strikegroup_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position)
self.visualgen.generate_target_smokes(self.to_cp)
super(CaptureOperation, self).generate()

View File

@@ -0,0 +1,54 @@
from game.db import assigned_units_split
from .operation import *
MAX_DISTANCE_BETWEEN_GROUPS = 12000
class FrontlineAttackOperation(Operation):
strikegroup = None # type: db.AssignedUnitsDict
attackers = None # type: db.ArmorDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
attackers: db.ArmorDict,
strikegroup: db.AssignedUnitsDict):
self.strikegroup = strikegroup
self.target = target
self.attackers = attackers
def prepare(self, terrain: Terrain, is_quick: bool):
super(FrontlineAttackOperation, self).prepare(terrain, is_quick)
if self.defender_name == self.game.player:
self.attackers_starting_position = None
self.defenders_starting_position = None
conflict = Conflict.frontline_cas_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.armorgen.generate_vec(self.attackers, self.target)
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), 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_cas_strikegroup(*assigned_units_split(dict), at=farp, escort=False)
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."
super(FrontlineAttackOperation, self).generate()

View File

@@ -0,0 +1,54 @@
from game.db import assigned_units_split
from .operation import *
MAX_DISTANCE_BETWEEN_GROUPS = 12000
class FrontlinePatrolOperation(Operation):
cas = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict
armor_attackers = None # type: db.ArmorDict
armor_defenders = None # type: db.ArmorDict
def setup(self,
cas: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict,
armor_attackers: db.ArmorDict,
armor_defenders: db.ArmorDict):
self.cas = cas
self.escort = escort
self.interceptors = interceptors
self.armor_attackers = armor_attackers
self.armor_defenders = armor_defenders
def prepare(self, terrain: Terrain, is_quick: bool):
super(FrontlinePatrolOperation, self).prepare(terrain, is_quick)
self.defenders_starting_position = None
conflict = Conflict.frontline_cap_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_defenders_cas(*assigned_units_split(self.cas), at=self.defenders_starting_position)
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)
self.airgen.generate_migcap(*assigned_units_split(self.interceptors), at=self.attackers_starting_position)
self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders)
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."
super(FrontlinePatrolOperation, self).generate()

View File

@@ -1,44 +0,0 @@
from dcs.terrain import Terrain
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.awacsgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
class GroundAttackOperation(Operation):
strikegroup = None # type: db.PlaneDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
strikegroup: db.PlaneDict):
self.strikegroup = strikegroup
self.target = target
def prepare(self, terrain: Terrain, is_quick: bool):
super(GroundAttackOperation, self).prepare(terrain, is_quick)
conflict = Conflict.ground_attack_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_defense(self.strikegroup, self.defender_clients, self.defenders_starting_position)
self.armorgen.generate(self.target, {})
super(GroundAttackOperation, self).generate()

View File

@@ -1,53 +0,0 @@
from dcs.terrain import Terrain
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.awacsgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
class GroundInterceptOperation(Operation):
strikegroup = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
strikegroup: db.PlaneDict,
interceptors: db.PlaneDict):
self.strikegroup = strikegroup
self.interceptors = interceptors
self.target = target
def prepare(self, terrain: Terrain, is_quick: bool):
super(GroundInterceptOperation, self).prepare(terrain, is_quick)
if self.defender_name == self.game.player:
self.attackers_starting_position = None
self.defenders_starting_position = None
conflict = Conflict.ground_intercept_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
if self.interceptors:
self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position)
self.armorgen.generate({}, self.target)
super(GroundInterceptOperation, self).generate()

View File

@@ -1,23 +1,13 @@
from dcs.terrain import Terrain
from game.db import assigned_units_split
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.awacsgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
from .operation import *
class InfantryTransportOperation(Operation):
transport = None # type: db.HeliDict
transport = None # type: db.AssignedUnitsDict
aa = None # type: db.AirDefenseDict
def setup(self, transport: db.HeliDict, aa: db.AirDefenseDict):
def setup(self, transport: db.AssignedUnitsDict, aa: db.AirDefenseDict):
self.transport = transport
self.aa = aa
@@ -36,11 +26,7 @@ class InfantryTransportOperation(Operation):
conflict=conflict)
def generate(self):
self.airgen.generate_passenger_transport(
helis=self.transport,
clients=self.attacker_clients,
at=self.attackers_starting_position
)
self.airgen.generate_passenger_transport(*assigned_units_split(self.transport), at=self.attackers_starting_position)
self.armorgen.generate_passengers(count=6)
self.aagen.generate_at_defenders_location(self.aa)
@@ -48,6 +34,9 @@ class InfantryTransportOperation(Operation):
self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location)
self.visualgen.generate_transportation_destination(self.conflict.position)
self.briefinggen.title = "Infantry transport"
self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength"
# TODO: horrible, horrible hack
# this will disable vehicle activation triggers,
# which aren't needed on this type of missions

View File

@@ -0,0 +1,37 @@
from game.db import assigned_units_split
from .operation import *
class InsurgentAttackOperation(Operation):
strikegroup = None # type: db.AssignedUnitsDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
strikegroup: db.AssignedUnitsDict):
self.strikegroup = strikegroup
self.target = target
def prepare(self, terrain: Terrain, is_quick: bool):
super(InsurgentAttackOperation, self).prepare(terrain, is_quick)
conflict = Conflict.ground_attack_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_defense(*assigned_units_split(self.strikegroup), at=self.defenders_starting_position)
self.armorgen.generate(self.target, {})
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."
super(InsurgentAttackOperation, self).generate()

View File

@@ -1,22 +1,21 @@
from dcs.terrain import Terrain
from game.db import assigned_units_split
from gen import *
from .operation import Operation
from .operation import *
class InterceptOperation(Operation):
escort = None # type: db.PlaneDict
escort = None # type: db.AssignedUnitsDict
transport = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
interceptors = None # type: db.AssignedUnitsDict
airdefense = None # type: db.AirDefenseDict
trigger_radius = TRIGGER_RADIUS_LARGE
def setup(self,
escort: db.PlaneDict,
escort: db.AssignedUnitsDict,
transport: db.PlaneDict,
airdefense: db.AirDefenseDict,
interceptors: db.PlaneDict):
interceptors: db.AssignedUnitsDict):
self.escort = escort
self.transport = transport
self.airdefense = airdefense
@@ -52,8 +51,12 @@ class InterceptOperation(Operation):
self.attackers_starting_position = ship
self.airgen.generate_transport(self.transport, self.to_cp.at)
self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients)
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)
self.airgen.generate_interception(*assigned_units_split(self.interceptors), at=self.attackers_starting_position)
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"
self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position)
super(InterceptOperation, self).generate()

View File

@@ -1,18 +1,17 @@
from dcs.terrain import Terrain
from game.db import assigned_units_split
from gen import *
from .operation import Operation
from .operation import *
class NavalInterceptionOperation(Operation):
strikegroup = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
strikegroup = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict
targets = None # type: db.ShipDict
trigger_radius = TRIGGER_RADIUS_LARGE
def setup(self,
strikegroup: db.PlaneDict,
interceptors: db.PlaneDict,
strikegroup: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict,
targets: db.ShipDict):
self.strikegroup = strikegroup
self.interceptors = interceptors
@@ -34,20 +33,22 @@ class NavalInterceptionOperation(Operation):
self.initialize(self.mission, conflict)
def generate(self):
super(NavalInterceptionOperation, self).generate()
target_groups = self.shipgen.generate_cargo(units=self.targets)
self.airgen.generate_ship_strikegroup(
attackers=self.strikegroup,
clients=self.attacker_clients,
*assigned_units_split(self.strikegroup),
target_groups=target_groups,
at=self.attackers_starting_position
)
if self.interceptors:
self.airgen.generate_defense(
defenders=self.interceptors,
clients=self.defender_clients,
*assigned_units_split(self.interceptors),
at=self.defenders_starting_position
)
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."
super(NavalInterceptionOperation, self).generate()

View File

@@ -1,15 +1,14 @@
from dcs.terrain import Terrain
from dcs.lua.parse import loads
from userdata.debriefing import *
from theater import *
from gen import *
class Operation:
attackers_starting_position = None # type: db.StartingPosition
defenders_starting_position = None # type: db.StartingPosition
mission = None # type: dcs.Mission
conflict = None # type: Conflict
armorgen = None # type: ArmorConflictGenerator
@@ -18,9 +17,11 @@ class Operation:
extra_aagen = None # type: ExtraAAConflictGenerator
shipgen = None # type: ShipGenerator
triggersgen = None # type: TriggersGenerator
awacsgen = None # type: AWACSConflictGenerator
airsupportgen = None # type: AirSupportConflictGenerator
visualgen = None # type: VisualGenerator
envgen = None # type: EnvironmentGenerator
groundobjectgen = None # type: GroundObjectsGenerator
briefinggen = None # type: BriefingGenerator
environment_settings = None
trigger_radius = TRIGGER_RADIUS_MEDIUM
@@ -31,19 +32,21 @@ class Operation:
game,
attacker_name: str,
defender_name: str,
attacker_clients: db.PlaneDict,
defender_clients: db.PlaneDict,
from_cp: ControlPoint,
to_cp: ControlPoint = None):
self.game = game
self.attacker_name = attacker_name
self.defender_name = defender_name
self.attacker_clients = attacker_clients
self.defender_clients = defender_clients
self.from_cp = from_cp
self.to_cp = to_cp
self.is_quick = False
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
return []
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
def initialize(self, mission: Mission, conflict: Conflict):
self.mission = mission
self.conflict = conflict
@@ -52,10 +55,12 @@ class Operation:
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
self.aagen = AAConflictGenerator(mission, conflict)
self.shipgen = ShipGenerator(mission, conflict)
self.awacsgen = AWACSConflictGenerator(mission, conflict, self.game)
self.airsupportgen = AirSupportConflictGenerator(mission, conflict, self.game)
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
self.visualgen = VisualGenerator(mission, conflict, self.game)
self.envgen = EnviromentGenerator(mission, conflict, self.game)
self.groundobjectgen = GroundObjectsGenerator(mission, conflict, self.game)
self.briefinggen = BriefingGenerator(mission, conflict, self.game)
player_name = self.from_cp.captured and self.attacker_name or self.defender_name
enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name
@@ -79,19 +84,39 @@ class Operation:
def generate(self):
self.visualgen.generate()
# air support
self.airsupportgen.generate(self.is_awacs_enabled)
self.briefinggen.append_frequency("Tanker", "10X/131 MHz AM")
if self.is_awacs_enabled:
self.awacsgen.generate()
self.briefinggen.append_frequency("AWACS", "133 MHz AM")
# ground infrastructure
self.groundobjectgen.generate()
self.extra_aagen.generate()
self.triggersgen.generate(self.is_quick, self.trigger_radius)
# triggers
if self.game.is_player_attack(self.conflict.attackers_side):
cp = self.conflict.from_cp
else:
cp = self.conflict.to_cp
self.triggersgen.generate(player_cp=cp,
is_quick=self.is_quick,
activation_trigger_radius=self.trigger_radius,
awacs_enabled=self.is_awacs_enabled)
# env settings
if self.environment_settings is None:
self.environment_settings = self.envgen.generate()
else:
self.envgen.load(self.environment_settings)
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
return []
# @TODO: ADD WAYPOINT INFORMATION!
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
# main frequencies
self.briefinggen.append_frequency("Flight", "251 MHz AM")
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
# briefing
self.briefinggen.generate()

64
game/operation/strike.py Normal file
View File

@@ -0,0 +1,64 @@
from game.db import assigned_units_split
from .operation import *
class StrikeOperation(Operation):
strikegroup = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict
def setup(self,
strikegroup: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict):
self.strikegroup = strikegroup
self.escort = escort
self.interceptors = interceptors
def prepare(self, terrain: Terrain, is_quick: bool):
super(StrikeOperation, self).prepare(terrain, is_quick)
self.defenders_starting_position = None
if self.game.player == self.defender_name:
self.attackers_starting_position = None
conflict = Conflict.strike_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
targets = [] # type: typing.List[typing.Tuple[str, Point]]
category_counters = {} # type: typing.Dict[str, int]
processed_groups = []
for object in self.to_cp.ground_objects:
if object.group_identifier in processed_groups:
continue
processed_groups.append(object.group_identifier)
category_counters[object.category] = category_counters.get(object.category, 0) + 1
markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category])
targets.append((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]))
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(self.strikegroup),
targets=targets,
at=self.attackers_starting_position)
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.briefinggen.title = "Strike"
self.briefinggen.description = "Destroy infrastructure assets and military supplies in the region. Each building destroyed will lower targets strength."
super(StrikeOperation, self).generate()

View File

@@ -2,7 +2,9 @@
class Settings:
player_skill = "Good"
enemy_skill = "Average"
only_player_takeoff = False
only_player_takeoff = True
night_disabled = False
multiplier = 1
sams = True
cold_start = False
version = None

View File

@@ -1,12 +1,14 @@
from .aaa import *
from .aircraft import *
from .armor import *
from .awacsgen import *
from .airsupportgen import *
from .conflictgen import *
from .shipgen import *
from .visualgen import *
from .triggergen import *
from .environmentgen import *
from .groundobjectsgen import *
from .briefinggen import *
from . import naming

View File

@@ -1,6 +1,3 @@
from game import *
from theater.conflicttheater import ConflictTheater
from .conflictgen import *
from .naming import *
@@ -8,6 +5,7 @@ from dcs.mission import *
DISTANCE_FACTOR = 0.5, 1
EXTRA_AA_MIN_DISTANCE = 35000
EXTRA_AA_MAX_DISTANCE = 150000
EXTRA_AA_POSITION_FROM_CP = 550
@@ -61,6 +59,9 @@ class ExtraAAConflictGenerator:
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
continue
if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MAX_DISTANCE:
continue
country_name = cp.captured and self.player_name or self.enemy_name
position = cp.position.point_from_heading(0, EXTRA_AA_POSITION_FROM_CP)
@@ -69,6 +70,6 @@ class ExtraAAConflictGenerator:
name=namegen.next_basedefense_name(),
_type=db.EXTRA_AA[country_name],
position=position,
group_size=2
group_size=1
)

View File

@@ -1,3 +1,5 @@
import logging
from game import db
from game.settings import Settings
from .conflictgen import *
@@ -14,7 +16,7 @@ SPREAD_DISTANCE_FACTOR = 1, 2
ESCORT_ENGAGEMENT_MAX_DIST = 100000
WORKAROUND_WAYP_DIST = 1000
WARM_START_HELI_AIRSPEED = 200
WARM_START_HELI_AIRSPEED = 120
WARM_START_HELI_ALT = 1000
WARM_START_ALTITUDE = 3000
@@ -22,6 +24,7 @@ WARM_START_AIRSPEED = 550
INTERCEPTION_ALT = 3000
INTERCEPTION_AIRSPEED = 1000
BARCAP_RACETRACK_DISTANCE = 20000
ATTACK_CIRCLE_ALT = 5000
ATTACK_CIRCLE_DURATION = 15
@@ -35,16 +38,23 @@ TRANSPORT_LANDING_ALT = 1000
DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000
INTERCEPT_MAX_DISTANCE = 200000
GROUP_VERTICAL_OFFSET = 300
class AircraftConflictGenerator:
escort_targets = [] # type: typing.List[typing.Tuple[PlaneGroup, int]]
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]
vertical_offset = None # type: int
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings):
self.m = mission
self.settings = settings
self.conflict = conflict
self.vertical_offset = 0
self.escort_targets = []
def _start_type(self) -> StartType:
return self.settings.cold_start and StartType.Cold or StartType.Warm
def _group_point(self, point) -> Point:
distance = randint(
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
@@ -67,18 +77,22 @@ class AircraftConflictGenerator:
count -= group_size
client_count -= client_size
def _setup_group(self, group: FlyingGroup, for_task: Task, client_count: int):
def _setup_group(self, group: FlyingGroup, for_task: typing.Type[Task], client_count: int):
did_load_loadout = False
unit_type = group.units[0].unit_type
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
override_loadout = db.PLANE_PAYLOAD_OVERRIDES[unit_type]
if type(override_loadout) == dict:
if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
group.load_loadout(db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task])
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task]
group.load_loadout(payload_name)
did_load_loadout = True
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
elif "*" in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
group.load_loadout(db.PLANE_PAYLOAD_OVERRIDES[unit_type]["*"])
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type]["*"]
group.load_loadout(payload_name)
did_load_loadout = True
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
elif issubclass(override_loadout, MainTask):
group.load_task_default_loadout(override_loadout)
did_load_loadout = True
@@ -98,18 +112,20 @@ class AircraftConflictGenerator:
group.units[idx].set_client()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
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:
assert count > 0
assert unit is not None
logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport))
return self.m.flight_group_from_airport(
country=side,
name=name,
aircraft_type=unit_type,
airport=self.m.terrain.airport_by_id(airport.id),
maintask=None,
start_type=StartType.Warm,
start_type=self._start_type(),
group_size=count,
parking_slots=None)
@@ -117,15 +133,17 @@ class AircraftConflictGenerator:
assert count > 0
assert unit is not None
if unit_type in helicopters.helicopter_map:
alt = WARM_START_HELI_ALT + random.randint(50, 200)
self.vertical_offset += GROUP_VERTICAL_OFFSET
if unit_type in helicopters.helicopter_map.values():
alt = WARM_START_HELI_ALT + self.vertical_offset
speed = WARM_START_HELI_AIRSPEED
else:
alt = WARM_START_ALTITUDE + random.randint(50, 200)
alt = WARM_START_ALTITUDE + self.vertical_offset
speed = WARM_START_AIRSPEED
pos = Point(at.x + random.randint(100, 200), at.y + random.randint(100, 200))
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000))
logging.info("airgen: {} for {} at {} at {}".format(unit_type, side.id, alt, speed))
return self.m.flight_group(
country=side,
name=name,
@@ -135,29 +153,30 @@ class AircraftConflictGenerator:
altitude=alt,
speed=speed,
maintask=None,
start_type=StartType.Warm,
start_type=self._start_type(),
group_size=count)
def _generate_at_carrier(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: ShipGroup) -> FlyingGroup:
def _generate_at_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: typing.Union[ShipGroup, StaticGroup]) -> FlyingGroup:
assert count > 0
assert unit is not None
logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at))
return self.m.flight_group_from_unit(
country=side,
name=name,
aircraft_type=unit_type,
pad_group=at,
maintask=None,
start_type=StartType.Warm,
start_type=self._start_type(),
group_size=count)
def _generate_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: db.StartingPosition):
if isinstance(at, Point):
return self._generate_inflight(name, side, unit_type, count, client_count, at)
elif isinstance(at, ShipGroup):
elif isinstance(at, Group):
takeoff_ban = unit_type in db.CARRIER_TAKEOFF_BAN
if not takeoff_ban:
return self._generate_at_carrier(name, side, unit_type, count, client_count, at)
return self._generate_at_group(name, side, unit_type, count, client_count, at)
else:
return self._generate_inflight(name, side, unit_type, count, client_count, at.position)
elif issubclass(at, Airport):
@@ -174,14 +193,16 @@ class AircraftConflictGenerator:
assert False
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
group.add_waypoint(cp.position, RTB_ALTITUDE)
if not at:
at = cp.at
if isinstance(cp.at, Point):
pass
elif isinstance(cp.at, ShipGroup):
pass
elif issubclass(cp.at, Airport):
group.land_at(cp.at)
if isinstance(at, Point):
group.add_waypoint(at, RTB_ALTITUDE)
elif isinstance(at, Group):
group.add_waypoint(at.position, RTB_ALTITUDE)
elif issubclass(at, Airport):
group.add_waypoint(at.position, RTB_ALTITUDE)
group.land_at(at)
def _at_position(self, at) -> Point:
if isinstance(at, Point):
@@ -205,10 +226,6 @@ class AircraftConflictGenerator:
at=at)
group.task = Escort.name
heading = group.position.heading_between_point(self.conflict.position)
position = group.position # type: Point
wayp = group.add_waypoint(position.point_from_heading(heading, WORKAROUND_WAYP_DIST), CAS_ALTITUDE, WARM_START_AIRSPEED)
self._setup_group(group, CAP, client_count)
for escorted_group, waypoint_index in self.escort_targets:
@@ -216,7 +233,7 @@ class AircraftConflictGenerator:
if not is_quick:
waypoint_index += TRIGGER_WAYPOINT_OFFSET
wayp.tasks.append(EscortTaskAction(escorted_group.id, engagement_max_dist=ESCORT_ENGAGEMENT_MAX_DIST, lastwpt=waypoint_index))
group.points[0].tasks.append(EscortTaskAction(escorted_group.id, engagement_max_dist=ESCORT_ENGAGEMENT_MAX_DIST, lastwpt=waypoint_index))
if should_orbit:
orbit_task = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
@@ -229,8 +246,8 @@ class AircraftConflictGenerator:
groups.append(group)
return groups
def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
assert len(self.escort_targets) == 0
def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
group = self._generate_group(
@@ -242,13 +259,68 @@ class AircraftConflictGenerator:
at=at and at or self._group_point(self.conflict.air_attackers_location))
waypoint = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAS.name
self._setup_group(group, CAS, client_count)
self.escort_targets.append((group, group.points.index(waypoint)))
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None):
assert len(self.escort_targets) == 0
def generate_ground_attack_strikegroup(self, strikegroup: db.PlaneDict, clients: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(strikegroup, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
side=self.conflict.attackers_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
escort_until_waypoint = None
for name, pos in targets:
waypoint = group.add_waypoint(pos, 0, WARM_START_AIRSPEED, self.m.translation.create_string(name))
if escort_until_waypoint is None:
escort_until_waypoint = waypoint
group.task = GroundAttack.name
self._setup_group(group, GroundAttack, client_count)
if escort:
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(defenders, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.defenders_side, flying_type),
side=self.conflict.defenders_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
location = self._group_point(self.conflict.air_defenders_location)
insertion_point = self.conflict.find_insertion_point(location)
waypoint = group.add_waypoint(insertion_point, CAS_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
destination_tail = self.conflict.tail.distance_to_point(insertion_point) > self.conflict.position.distance_to_point(insertion_point)
group.add_waypoint(destination_tail and self.conflict.tail or self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAS.name
self._setup_group(group, CAS, client_count)
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._rtb_for(group, self.conflict.to_cp, at)
def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
group = self._generate_group(
@@ -265,10 +337,11 @@ class AircraftConflictGenerator:
group.task = AntishipStrike.name
self._setup_group(group, AntishipStrike, client_count)
self.escort_targets.append((group, group.points.index(wayp)))
if escort:
self.escort_targets.append((group, group.points.index(wayp)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_strikegroup_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
def generate_attackers_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for g in self._generate_escort(
side=self.conflict.attackers_side,
units=attackers,
@@ -278,7 +351,7 @@ class AircraftConflictGenerator:
should_orbit=True):
self._rtb_for(g, self.conflict.from_cp, at)
def generate_transport_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
def generate_defenders_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for g in self._generate_escort(
side=self.conflict.defenders_side,
units=escort,
@@ -305,8 +378,50 @@ class AircraftConflictGenerator:
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_transport(self, transport: db.PlaneDict, destination: Airport):
assert len(self.escort_targets) == 0
def generate_migcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
side=self.conflict.attackers_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAP.name
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)
def generate_barcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.defenders_side, flying_type),
side=self.conflict.defenders_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
else:
heading = group.position.heading_between_point(self.conflict.position)
waypoint = group.add_waypoint(self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE),
WARM_START_ALTITUDE,
WARM_START_AIRSPEED)
waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED))
group.task = CAP.name
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_transport(self, transport: db.PlaneDict, destination: Airport, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(transport):
group = self._generate_group(
@@ -318,8 +433,8 @@ class AircraftConflictGenerator:
at=self._group_point(self.conflict.air_defenders_location))
waypoint = group.add_waypoint(destination.position.random_point_within(0, 0), TRANSPORT_LANDING_ALT)
self.escort_targets.append((group, group.points.index(waypoint)))
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
group.task = Transport.name
group.land_at(destination)
@@ -341,6 +456,10 @@ class AircraftConflictGenerator:
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE)
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)

54
gen/airsupportgen.py Normal file
View File

@@ -0,0 +1,54 @@
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
from dcs.unitgroup import *
from dcs.unittype import *
from dcs.task import *
from dcs.terrain.terrain import NoParkingSlotError
TANKER_DISTANCE = 15000
TANKER_ALT = 10000
AWACS_DISTANCE = 150000
AWACS_ALT = 10000
class AirSupportConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game):
self.mission = mission
self.conflict = conflict
self.game = game
def generate(self, is_awacs_enabled):
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))
if is_awacs_enabled:
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]
self.mission.awacs_flight(
country=self.mission.country(self.game.player),
name=namegen.next_awacs_name(self.mission.country(self.game.player),),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
frequency=133,
start_type=StartType.Warm,
)

View File

@@ -1,3 +1,8 @@
import logging
from random import randint
from itertools import zip_longest
from game import db
from .conflictgen import *
from .naming import *
@@ -11,6 +16,10 @@ from dcs.country import *
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
SPREAD_DISTANCE_SIZE_FACTOR = 0.1
FRONTLINE_CAS_FIGHTS_COUNT = 4, 8
FRONTLINE_CAS_GROUP_MIN = 1, 2
FRONTLINE_CAS_PADDING = 12000
class ArmorConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict):
@@ -25,8 +34,9 @@ class ArmorConflictGenerator:
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR)
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point):
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None):
for c in range(count):
logging.info("armorgen: {} for {}".format(unit, side.id))
group = self.m.vehicle_group(
side,
namegen.next_unit_name(side, unit),
@@ -34,24 +44,67 @@ class ArmorConflictGenerator:
position=self._group_point(at),
group_size=1,
move_formation=PointAction.OffRoad)
initial_position = self.conflict.position.point_from_heading(0, 500)
wayp = group.add_waypoint(self._group_point(initial_position))
if not to:
to = self.conflict.position.point_from_heading(0, 500)
wayp = group.add_waypoint(self._group_point(to))
wayp.tasks = []
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
if attackers:
attack_pos = position.point_from_heading(self.conflict.heading - 90, 8000)
attack_dest = position.point_from_heading(self.conflict.heading + 90, 25000)
for type, count in attackers.items():
self._generate_group(
side=self.conflict.attackers_side,
unit=type,
count=count,
at=attack_pos,
to=attack_dest,
)
if defenders:
def_pos = position.point_from_heading(self.conflict.heading + 90, 4000)
def_dest = position.point_from_heading(self.conflict.heading - 90, 25000)
for type, count in defenders.items():
self._generate_group(
side=self.conflict.defenders_side,
unit=type,
count=count,
at=def_pos,
to=def_dest,
)
def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict):
for type, count in attackers.items():
self._generate_group(
side=self.conflict.attackers_side,
unit=type,
count=count,
at=self.conflict.ground_attackers_location)
side=self.conflict.attackers_side,
unit=type,
count=count,
at=self.conflict.ground_attackers_location)
for type, count in defenders.items():
self._generate_group(
side=self.conflict.defenders_side,
unit=type,
count=count,
at=self.conflict.ground_defenders_location)
side=self.conflict.defenders_side,
unit=type,
count=count,
at=self.conflict.ground_defenders_location)
def generate_vec(self, attackers: db.ArmorDict, defenders: db.ArmorDict):
fights_count = randint(*FRONTLINE_CAS_FIGHTS_COUNT)
single_fight_defenders_count = min(int(sum(defenders.values()) / fights_count), randint(*FRONTLINE_CAS_GROUP_MIN))
defender_groups = list(db.unitdict_split(defenders, single_fight_defenders_count))
single_fight_attackers_count = min(int(sum(attackers.values()) / len(defender_groups)), randint(*FRONTLINE_CAS_GROUP_MIN))
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):
padding = FRONTLINE_CAS_PADDING if FRONTLINE_CAS_PADDING < self.conflict.distance else 0
position = self.conflict.position.point_from_heading(self.conflict.heading,
random.randint(padding, int(self.conflict.distance - padding)))
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
def generate_passengers(self, count: int):
unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name))

View File

@@ -1,32 +0,0 @@
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
from dcs.unitgroup import *
from dcs.unittype import *
from dcs.task import *
from dcs.terrain.terrain import NoParkingSlotError
AWACS_DISTANCE = 150000
AWACS_ALT = 10000
class AWACSConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game):
self.mission = mission
self.conflict = conflict
self.game = game
def generate(self):
plane = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]
self.mission.awacs_flight(
country=self.mission.country(self.game.player),
name=namegen.next_awacs_name(self.mission.country(self.game.player),),
plane_type=plane,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
frequency=251
)

49
gen/briefinggen.py Normal file
View File

@@ -0,0 +1,49 @@
import logging
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
class BriefingGenerator:
freqs = None # type: typing.List[typing.Tuple[str, str]]
title = "" # type: str
description = "" # type: str
targets = None # type: typing.List[typing.Tuple[str, str]]
def __init__(self, mission: Mission, conflict: Conflict, game):
self.m = mission
self.conflict = conflict
self.game = game
self.freqs = []
self.targets = []
def append_frequency(self, name: str, frequency: str):
self.freqs.append((name, frequency))
def append_target(self, description: str, markpoint: str = None):
self.targets.append((description, markpoint))
def generate(self):
description = ""
if self.title:
description += self.title
if self.description:
description += "\n\n" + self.description
if self.freqs:
description += "\n\nCOMMS:"
for name, freq in self.freqs:
description += "\n{}: {}".format(name, freq)
if self.targets:
description += "\n\nTARGETS:"
for name, tp in self.targets:
description += "\n{} {}".format(name, "(TP {})".format(tp) if tp else "")
self.m.set_description_text(description)

View File

@@ -1,3 +1,4 @@
import logging
import typing
import pdb
import dcs
@@ -20,6 +21,10 @@ AIR_DISTANCE = 40000
CAPTURE_AIR_ATTACKERS_DISTANCE = 25000
CAPTURE_AIR_DEFENDERS_DISTANCE = 60000
STRIKE_AIR_ATTACKERS_DISTANCE = 45000
STRIKE_AIR_DEFENDERS_DISTANCE = 25000
CAP_CAS_DISTANCE = 10000, 120000
GROUND_INTERCEPT_SPREAD = 5000
GROUND_DISTANCE_FACTOR = 1
@@ -38,8 +43,9 @@ NAVAL_INTERCEPT_DISTANCE_FACTOR = 1
NAVAL_INTERCEPT_DISTANCE_MAX = 40000
NAVAL_INTERCEPT_STEP = 5000
FRONT_SMOKE_MIN_DISTANCE = 5000
FRONT_SMOKE_DISTANCE_FACTOR = 0.5
FRONTLINE_LENGTH = 80000
FRONTLINE_MIN_CP_DISTANCE = 5000
FRONTLINE_DISTANCE_STRENGTH_FACTOR = 0.7
def _opposite_heading(h):
@@ -65,28 +71,35 @@ class Conflict:
size = None # type: int
radials = None # type: typing.List[int]
heading = None # type: int
distance = None # type: int
ground_attackers_location = None # type: Point
ground_defenders_location = None # type: Point
air_attackers_location = None # type: Point
air_defenders_location = None # type: Point
def __init__(self,
position: Point,
theater: ConflictTheater,
from_cp: ControlPoint,
to_cp: ControlPoint,
attackers_side: Country,
defenders_side: Country,
ground_attackers_location: Point,
ground_defenders_location: Point,
air_attackers_location: Point,
air_defenders_location: Point):
position: Point,
heading=None,
distance=None,
ground_attackers_location: Point = None,
ground_defenders_location: Point = None,
air_attackers_location: Point = None,
air_defenders_location: Point = None):
self.attackers_side = attackers_side
self.defenders_side = defenders_side
self.from_cp = from_cp
self.to_cp = to_cp
self.theater = theater
self.position = position
self.heading = heading
self.distance = distance
self.size = to_cp.size
self.radials = to_cp.radials
self.ground_attackers_location = ground_attackers_location
@@ -94,28 +107,101 @@ class Conflict:
self.air_attackers_location = air_attackers_location
self.air_defenders_location = air_defenders_location
@property
def center(self) -> Point:
return self.position.point_from_heading(self.heading, self.distance / 2)
@property
def tail(self) -> Point:
return self.position.point_from_heading(self.heading, self.distance)
@property
def is_vector(self) -> bool:
return self.heading is not None
@property
def opposite_heading(self) -> int:
return _heading_sum(self.heading, 180)
@property
def to_size(self):
return self.to_cp.size * GROUND_DISTANCE_FACTOR
def find_insertion_point(self, other_point: Point) -> Point:
dx = self.position.x - self.tail.x
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
if lerp < 0:
lerp = 0
elif lerp > 1:
lerp = 1
x = lerp * dx + self.tail.x
y = lerp * dy + self.tail.y
return Point(x, y)
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)
@classmethod
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint):
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONT_SMOKE_DISTANCE_FACTOR * to_cp.base.strength, FRONT_SMOKE_MIN_DISTANCE)
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
return from_cp.has_frontline and to_cp.has_frontline
@classmethod
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> 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)
heading = to_cp.position.heading_between_point(from_cp.position)
return to_cp.position.point_from_heading(heading, distance)
return to_cp.position.point_from_heading(heading, distance), heading
@classmethod
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
for _ in range(0, int(max_distance), 800):
for _ in range(3):
if theater.is_on_land(initial):
return initial
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Tuple[Point, int, int]:
center_position, heading = cls.frontline_position(from_cp, to_cp)
left_position, right_position = None, None
initial = initial.random_point_within(1000, 1000)
if not theater.is_on_land(center_position):
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, -90), theater)
if pos:
right_position = pos
center_position = pos
else:
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, +90), theater)
if pos:
left_position = pos
center_position = pos
print("{} - {} {}".format(from_cp, to_cp, center_position))
initial = initial.point_from_heading(heading, 800)
if left_position is None:
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater)
print("Didn't find ground position!")
if right_position is None:
right_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, 90), theater)
return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position))
@classmethod
def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
pos = initial
for offset in range(0, int(max_distance), 500):
new_pos = initial.point_from_heading(heading, offset)
if theater.is_on_land(new_pos):
pos = new_pos
else:
return pos
return pos
@classmethod
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> typing.Optional[Point]:
pos = initial
for _ in range(0, int(max_distance), 500):
if theater.is_on_land(pos):
return pos
pos = pos.point_from_heading(heading, 500)
logging.info("Didn't find ground position!")
return None
@classmethod
@@ -127,10 +213,10 @@ class Conflict:
distance = to_cp.size * GROUND_DISTANCE_FACTOR
attackers_location = position.point_from_heading(attack_heading, distance)
attackers_location = Conflict._find_ground_location(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
defenders_location = position.point_from_heading(defense_heading, distance)
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
return cls(
position=position,
@@ -145,6 +231,33 @@ class Conflict:
air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), CAPTURE_AIR_DEFENDERS_DISTANCE)
)
@classmethod
def strike_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
position = to_cp.position
attack_raw_heading = to_cp.position.heading_between_point(from_cp.position)
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)
distance = to_cp.size * GROUND_DISTANCE_FACTOR
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)
defenders_location = position.point_from_heading(defense_heading, distance)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
return cls(
position=position,
theater=theater,
from_cp=from_cp,
to_cp=to_cp,
attackers_side=attacker,
defenders_side=defender,
ground_attackers_location=attackers_location,
ground_defenders_location=defenders_location,
air_attackers_location=position.point_from_heading(attack_raw_heading, STRIKE_AIR_ATTACKERS_DISTANCE),
air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), STRIKE_AIR_DEFENDERS_DISTANCE)
)
@classmethod
def intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
raw_distance = from_cp.position.distance_to_point(to_cp.position) * 1.5
@@ -170,7 +283,7 @@ class Conflict:
def ground_attack_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
heading = random.choice(to_cp.radials)
initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE)
position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
if not position:
heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
@@ -189,25 +302,45 @@ class Conflict:
)
@classmethod
def ground_intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
heading = to_cp.position.heading_between_point(from_cp.position)
initial_location = cls.frontline_position(from_cp, to_cp).random_point_within(GROUND_INTERCEPT_SPREAD)
position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, heading, theater)
if not position:
heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
def frontline_cas_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
assert cls.has_frontline_between(from_cp, to_cp)
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
return cls(
position=position,
heading=heading,
distance=distance,
theater=theater,
from_cp=from_cp,
to_cp=to_cp,
attackers_side=attacker,
defenders_side=defender,
ground_attackers_location=None,
ground_defenders_location=position,
ground_defenders_location=None,
air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE),
air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE)
air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE),
)
@classmethod
def frontline_cap_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
assert cls.has_frontline_between(from_cp, to_cp)
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
attack_position = position.point_from_heading(heading, randint(0, int(distance)))
attackers_position = attack_position.point_from_heading(heading - 90, AIR_DISTANCE)
defenders_position = attack_position.point_from_heading(heading + 90, random.randint(*CAP_CAS_DISTANCE))
return cls(
position=position,
heading=heading,
distance=distance,
theater=theater,
from_cp=from_cp,
to_cp=to_cp,
attackers_side=attacker,
defenders_side=defender,
air_attackers_location=attackers_position,
air_defenders_location=defenders_position,
)
@classmethod
@@ -218,7 +351,7 @@ class Conflict:
distance = to_cp.size * GROUND_DISTANCE_FACTOR
defenders_location = position.point_from_heading(defense_heading, distance)
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
return cls(
position=position,
@@ -261,10 +394,9 @@ class Conflict:
@classmethod
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
frontline_position = cls.frontline_position(from_cp, to_cp)
heading = to_cp.position.heading_between_point(from_cp.position)
frontline_position, heading = cls.frontline_position(from_cp, to_cp)
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
dest = cls._find_ground_location(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:
radial = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
dest = to_cp.position.point_from_heading(radial, to_cp.size * GROUND_DISTANCE_FACTOR)

View File

@@ -1,3 +1,4 @@
import logging
import typing
import random
from datetime import datetime, timedelta, time
@@ -18,7 +19,7 @@ from gen import *
WEATHER_CLOUD_BASE = 2000, 3000
WEATHER_CLOUD_DENSITY = 1, 8
WEATHER_CLOUD_THICKNESS = 100, 400
WEATHER_CLOUD_BASE_MIN = 1200
WEATHER_CLOUD_BASE_MIN = 1600
RANDOM_TIME = {
"night": 5,
@@ -28,10 +29,10 @@ RANDOM_TIME = {
}
RANDOM_WEATHER = {
1: 5, # heavy rain
2: 15, # rain
3: 25, # dynamic
4: 35, # clear
1: 0, # heavy rain
2: 10, # rain
3: 20, # dynamic
4: 30, # clear
5: 100, # random
}
@@ -48,7 +49,7 @@ class EnviromentGenerator:
self.game = game
def _gen_random_time(self):
start_time = datetime.combine(datetime.today(), time())
start_time = datetime.fromtimestamp(1527206400)
time_range = None
for k, v in RANDOM_TIME.items():
if self.game.settings.night_disabled and k == "night":
@@ -68,7 +69,7 @@ class EnviromentGenerator:
weather_type = k
break
print("generated weather {}".format(weather_type))
logging.info("generated weather {}".format(weather_type))
if weather_type == 1:
self.mission.weather.heavy_rain()
elif weather_type == 2:
@@ -90,8 +91,13 @@ class EnviromentGenerator:
self.mission.weather.wind_at_8000 = Wind(wind_direction, wind_speed * 3)
if self.mission.weather.clouds_density > 0:
# 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)
if self.mission.weather.wind_at_ground == 0:
# frontline smokes look silly w/o any wind
self.mission.weather.wind_at_ground = random.randint(1, 2)
def generate(self) -> EnvironmentSettings:
self._gen_random_time()
self._gen_random_weather()

78
gen/groundobjectsgen.py Normal file
View File

@@ -0,0 +1,78 @@
import logging
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
from dcs.statics import *
FARP_FRONTLINE_DISTANCE = 10000
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:
FARP_CAPACITY = 4
def __init__(self, mission: Mission, conflict: Conflict, game):
self.m = mission
self.conflict = conflict
self.game = game
def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]:
assert self.conflict.is_vector, "FARP could be generated only on frontline conflicts!"
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)
yield self.m.farp(
country=self.m.country(self.game.player),
name="FARP",
position=position,
)
def generate(self):
side = self.m.country(self.game.enemy)
cp = None # type: ControlPoint
if self.conflict.attackers_side.name == self.game.player:
cp = self.conflict.to_cp
else:
cp = self.conflict.from_cp
for ground_object in cp.ground_objects:
if ground_object.category == "defense":
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)
group = self.m.vehicle_group(
country=side,
name=ground_object.string_identifier,
_type=unit_type,
position=ground_object.position,
heading=ground_object.heading
)
logging.info("generated defense object identifier {} with mission id {}".format(group.name, group.id))
else:
group = self.m.static_group(
country=side,
name=ground_object.string_identifier,
_type=random.choice(CATEGORY_MAPPING[ground_object.category]),
position=ground_object.position,
heading=ground_object.heading
)
logging.info("generated object identifier {} with mission id {}".format(group.name, group.id))

5
gen/heli.py Normal file
View File

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

View File

@@ -15,6 +15,10 @@ class NameGenerator:
self.number += 1
return "awacs|{}|{}|0|".format(country.id, self.number)
def next_tanker_name(self, country):
self.number += 1
return "tanker|{}|{}|0|".format(country.id, self.number)
def next_carrier_name(self, country):
self.number += 1
return "carrier|{}|{}|0|".format(country.id, self.number)

View File

@@ -1,3 +1,5 @@
import logging
from game import db
from .conflictgen import *
from .naming import *
@@ -15,15 +17,20 @@ class ShipGenerator:
self.conflict = conflict
def generate_carrier(self, type: ShipType, country: str, at: Point) -> ShipGroup:
return self.m.ship_group(
group = self.m.ship_group(
country=self.m.country(country),
name=namegen.next_carrier_name(self.m.country(country)),
_type=type,
position=at)
group.points[0].tasks.append(ActivateBeaconCommand(unit_id=group.id, channel=20, callsign="SHDW", aa=False))
group.points[0].tasks.append(ActivateICLSCommand(unit_id=group.id, channel=1))
return group
def generate_cargo(self, units: db.ShipDict) -> typing.Collection[ShipGroup]:
groups = []
for unit_type, unit_count in units.items():
logging.info("shipgen: {} ({}) for {}".format(unit_type, unit_count, self.conflict.defenders_side))
group = self.m.ship_group(
country=self.conflict.defenders_side,
name=namegen.next_unit_name(self.conflict.defenders_side, unit_type),

View File

@@ -38,7 +38,7 @@ class TriggersGenerator:
self.conflict = conflict
self.game = game
def _gen_activation_trigger(self, radius: int, player_coalition: str, enemy_coalition: str):
def _gen_activation_trigger(self, radius: int, player_cp: ControlPoint, player_coalition: str, enemy_coalition: str):
activate_by_trigger = []
for coalition_name, coalition in self.mission.coalition.items():
for country in coalition.countries.values():
@@ -51,7 +51,7 @@ class TriggersGenerator:
vehicle_group.late_activation = True
activate_by_trigger.append(vehicle_group)
conflict_distance = self.conflict.from_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)
if minimum_radius < 0:
minimum_radius = 0
@@ -67,39 +67,45 @@ class TriggersGenerator:
self.mission.triggerrules.triggers.append(activation_trigger)
def _gen_push_trigger(self, player_coalition: str):
def _gen_push_trigger(self, player_cp: ControlPoint, player_coalition: str):
push_by_trigger = []
for coalition_name, coalition in self.mission.coalition.items():
for country in coalition.countries.values():
if coalition_name == player_coalition:
for plane_group in country.plane_group + country.helicopter_group:
if plane_group.task == AWACS.name:
for group in country.plane_group + country.helicopter_group:
if group.task == AWACS.name or group.task == Refueling.name:
continue
regroup_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position)
if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3:
continue
pos1 = plane_group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
pos2 = plane_group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE+5000)
w1 = plane_group.add_waypoint(pos1, REGROUP_ALT)
w2 = plane_group.add_waypoint(pos2, REGROUP_ALT)
if group.units[0].is_human():
continue
plane_group.points.remove(w1)
plane_group.points.remove(w2)
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
plane_group.points.insert(1, w2)
plane_group.points.insert(1, w1)
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)
plane_group.points[3].tasks.append(Silence(False))
group.points[3].tasks.append(Silence(False))
plane_group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
push_by_trigger.append(plane_group)
group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
push_by_trigger.append(group)
push_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.from_cp.position, PUSH_TRIGGER_SIZE, name="Push zone")
push_trigger_zone = self.mission.triggers.add_triggerzone(player_cp.position, PUSH_TRIGGER_SIZE, name="Push zone")
push_trigger = TriggerOnce(Event.NoEvent, "Push trigger")
for group in push_by_trigger:
@@ -136,7 +142,7 @@ class TriggersGenerator:
for vehicle_group in country.vehicle_group:
vehicle_group.set_skill(Skill(skill_level))
def generate(self, is_quick: bool, activation_trigger_radius: int):
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"
enemy_coalition = player_coalition == "blue" and "red" or "blue"
@@ -148,6 +154,6 @@ class TriggersGenerator:
if not is_quick:
# TODO: waypoint parts of this should not be post-hacked but added in airgen
self._gen_activation_trigger(activation_trigger_radius, player_coalition, enemy_coalition)
self._gen_push_trigger(player_coalition)
self._gen_activation_trigger(activation_trigger_radius, player_cp, player_coalition, enemy_coalition)
self._gen_push_trigger(player_cp, player_coalition)

View File

@@ -62,7 +62,6 @@ def __monkey_static_dict(self: Static):
__original_static_dict = Static.dict
Static.dict = __monkey_static_dict
FRONT_SMOKE_LENGTH = 80000
FRONT_SMOKE_SPACING = 800
FRONT_SMOKE_RANDOM_SPREAD = 4000
FRONT_SMOKE_TYPE_CHANCES = {
@@ -99,11 +98,10 @@ class VisualGenerator:
def _generate_frontline_smokes(self):
for from_cp, to_cp in self.game.theater.conflicts():
heading = to_cp.position.heading_between_point(from_cp.position)
point = Conflict.frontline_position(from_cp, to_cp)
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONT_SMOKE_LENGTH / 2)
point, heading = Conflict.frontline_position(from_cp, to_cp)
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2)
for offset in range(0, FRONT_SMOKE_LENGTH, FRONT_SMOKE_SPACING):
for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING):
position = plane_start.point_from_heading(turn_heading(heading, - 90), offset)
for k, v in FRONT_SMOKE_TYPE_CHANCES.items():

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

1
resources/tools/dcs Symbolic link
View File

@@ -0,0 +1 @@
../../submodules/dcs/dcs

View File

@@ -0,0 +1,86 @@
import pickle
import typing
from game import db
from gen.groundobjectsgen import TheaterGroundObject
from dcs.mission import Mission
from dcs.mapping import Point
m = Mission()
m.load_file("./cau_groundobjects.miz")
result = {}
result_by_groups = {} # type: typing.Dict[int, TheaterGroundObject]
cp_counters = {}
ids_counters = {}
group_id_counter = 0
previous_group_id = None
def append_group(cp_id, category, group_id, object_id, position, heading):
global result
global result_by_groups
ground_object = TheaterGroundObject(category, cp_id, group_id, object_id, position, heading)
if cp_id not in result:
result[cp_id] = []
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:
args = str(name.split()[0]).split("|")
if len(args) == 2:
global group_id_counter
group_id_counter += 1
args.append(str(group_id_counter))
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])
for group in m.country("Russia").static_group + m.country("Russia").vehicle_group:
try:
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)
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_TRESHOLD = 2000
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))
distance = a.position.distance_to_point(b.position)
if distance > GROUP_TRESHOLD:
print("Objects {} and {} in group {} are too far apart ({})!".format(a.string_identifier, b.string_identifier, group_id, distance))
print("Total {} objects".format(sum([len(x) for x in result.values()])))
for cp_id, count in cp_counters.items():
print("{} - {} objects".format(cp_id, count))
with open("../cau_groundobjects.p", "wb") as f:
pickle.dump(result, f)

View File

@@ -1,14 +1,14 @@
import pickle
from dcs.mission import Mission
from dcs.terrain import PersianGulf
m = Mission()
m.load_file("tools/cau_terrain.miz")
for terrain in ["cau", "gulf"]:
m = Mission()
m.load_file("./{}_terrain.miz".format(terrain))
landmap = []
for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
landmap = []
for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
with open("./caulandmap.p", "wb") as f:
pickle.dump(landmap, f)
with open("../{}landmap.p".format(terrain), "wb") as f:
pickle.dump(landmap, f)

View File

@@ -1,10 +1,14 @@
import os
import dcs
from gen.aircraft import AircraftConflictGenerator
from game import db
from gen.aircraft import AircraftConflictGenerator
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "..\\payloads")]
mis = dcs.Mission(dcs.terrain.PersianGulf())
pos = dcs.terrain.PersianGulf().khasab().position
airgen = AircraftConflictGenerator(mis, None)
airgen = AircraftConflictGenerator(mis, None, None)
for t, uts in db.UNIT_BY_TASK.items():
if t != dcs.task.CAP and t != dcs.task.CAS:
@@ -25,6 +29,6 @@ for t, uts in db.UNIT_BY_TASK.items():
altitude=10000
)
g.task = t.name
airgen._setup_group(g, t)
airgen._setup_group(g, t, 0)
mis.save("loadout_test.miz")

Binary file not shown.

View File

@@ -0,0 +1,42 @@
import sys
from dcs.lua.parse import *
from dcs.lua.serialize import *
a = loads(open(sys.argv[1], "r").read())
b = loads(open(sys.argv[2], "r").read())
def get(a, k):
b = a
for x in k.strip().split(" "):
if isinstance(a, dict):
y = a
a = a.get(x, None)
if a is None:
try:
a = y.get(int(x), None)
except:
pass
else:
break
if a is None:
pass
return a
def cycle(kk, ref, v):
if isinstance(v, dict):
for k, v in v.items():
cycle(kk + " " + str(k), ref, v)
elif isinstance(v, list):
for i, v in enumerate(v):
cycle(kk + " " + str(i), ref, v)
else:
if get(ref, kk) != v:
print(kk)
print(v)
print(get(ref, kk))
cycle("", a, b)

View File

@@ -0,0 +1,52 @@
import os
from zipfile import *
IGNORED_PATHS = [
"__pycache__",
".gitignore",
".gitmodules",
".git",
".idea",
".DS_Store",
"submodules",
"build",
"venv",
]
VERSION = input("version str:")
def _zip_dir(archieve, path):
for path, directories, files in os.walk(path):
is_ignored = False
for ignored_path in IGNORED_PATHS:
if ignored_path in path:
is_ignored = True
break
if is_ignored:
continue
for file in files:
if file in IGNORED_PATHS:
continue
archieve.write(os.path.join(path, file))
def _mk_archieve():
path = os.path.join("build", "dcs_liberation_{}.zip".format(VERSION))
if os.path.exists(path):
print("version already exists")
return
archieve = ZipFile(path, "w")
archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\" \"{}\"".format(VERSION))
_zip_dir(archieve, ".")
os.chdir("submodules\\dcs")
_zip_dir(archieve, "dcs")
_mk_archieve()

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/ui/terrain_pg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
py.exe __init__.py %UserProfile% > logs.txt 2>&1

View File

@@ -1,3 +1,4 @@
import logging
import typing
import math
import itertools
@@ -53,7 +54,7 @@ class Base:
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
if count <= 0:
print("{}: no units for {}".format(self, for_type))
logging.info("{}: no units for {}".format(self, for_type))
return {}
sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]]
@@ -74,7 +75,7 @@ class Base:
assert result_unit_count > 0
result[unit_type] = result.get(unit_type, 0) + result_unit_count
print("{} for {} ({}): {}".format(self, for_type, count, result))
logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
return result
def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[PlaneType, int]:
@@ -150,7 +151,7 @@ class Base:
return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count)
def assemble_count(self):
return int(self.total_armor * min(self.strength + 0.5, 1))
return int(self.total_armor * 0.5)
def assemble_aa_count(self) -> int:
if self.strength > STRENGTH_AA_ASSEMBLE_MIN:
@@ -167,11 +168,12 @@ class Base:
def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]:
return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def assemble_cap(self) -> typing.Dict[Armor, int]:
def assemble_attack(self) -> typing.Dict[Armor, int]:
return self._find_best_armor(PinpointStrike, self.assemble_count())
def assemble_defense(self) -> typing.Dict[Armor, int]:
return self._find_best_armor(PinpointStrike, self.assemble_count())
count = int(self.total_armor * min(self.strength + 0.5, 1))
return self._find_best_armor(PinpointStrike, count)
def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]:
return self._find_best_unit(self.aa, AirDefence, count and min(count, self.total_aa) or self.assemble_aa_count())

View File

@@ -13,7 +13,7 @@ class CaucasusTheater(ConflictTheater):
overview_image = "caumap.gif"
reference_points = {(-317948.32727306, 635639.37385346): (282.5, 319),
(-355692.3067714, 617269.96285781): (269, 352), }
landmap_poly = load_poly("resources\\caulandmap.p")
landmap = load_landmap("resources\\caulandmap.p")
daytime_map = {
"dawn": (6, 9),
"day": (9, 18),
@@ -73,7 +73,10 @@ class CaucasusTheater(ConflictTheater):
self.carrier_1.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] = []):
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)

View File

@@ -4,8 +4,9 @@ import itertools
import dcs
from dcs.mapping import Point
from .landmap import ray_tracing
from .landmap import Landmap, poly_contains
from .controlpoint import ControlPoint
from .theatergroundobject import TheaterGroundObject
SIZE_TINY = 150
SIZE_SMALL = 600
@@ -48,14 +49,22 @@ COAST_DR_W = [135, 180, 225, 315]
class ConflictTheater:
terrain = None # type: dcs.terrain.Terrain
controlpoints = None # type: typing.Collection[ControlPoint]
reference_points = None # type: typing.Dict
overview_image = None # type: str
landmap_poly = None
landmap = None # type: landmap.Landmap
daytime_map = None # type: typing.Dict[str, typing.Tuple[int, int]]
def __init__(self):
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] = []):
for connected_point in connected_to:
point.connect(to=connected_point)
@@ -63,14 +72,20 @@ class ConflictTheater:
self.controlpoints.append(point)
def is_on_land(self, point: Point) -> bool:
if not self.landmap_poly:
if not self.landmap:
return True
for poly in self.landmap_poly:
if ray_tracing(point.x, point.y, poly):
return True
# check first poly (main land poly)
if not poly_contains(point.x, point.y, self.landmap[0]):
return False
return False
# check others polys (exclusion zones from main)
for poly in self.landmap[1:]:
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 True
def player_points(self) -> typing.Collection[ControlPoint]:
return [point for point in self.controlpoints if point.captured]

View File

@@ -5,38 +5,46 @@ from dcs.mapping import *
from dcs.country import *
from dcs.terrain import Airport
from .theatergroundobject import TheaterGroundObject
class ControlPoint:
connected_points = [] # type: typing.List[ControlPoint]
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.Collection[TheaterGroundObject]
position = None # type: Point
captured = False
base: None # type: theater.base.Base
at: None # type: db.StartPosition
has_frontline = True
id = 0
base = None # type: theater.base.Base
at = None # type: db.StartPosition
def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int):
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
import theater.base
self.id = id
self.name = " ".join(re.split(r" |-", name)[:2])
self.full_name = name
self.position = position
self.at = at
self.ground_objects = []
self.size = size
self.importance = importance
self.captured = False
self.has_frontline = has_frontline
self.radials = radials
self.connected_points = []
self.base = theater.base.Base()
@classmethod
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int):
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
assert airport
return cls(airport.name, airport.position, airport, radials, size, importance)
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline)
@classmethod
def carrier(cls, name: str, at: Point):
import theater.conflicttheater
return cls(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)
def __str__(self):
return self.name

View File

@@ -1,7 +1,10 @@
import pickle
import typing
Landmap = typing.Collection[typing.Collection[typing.Tuple[float, float]]]
def load_poly(filename: str):
def load_landmap(filename: str) -> Landmap:
try:
with open(filename, "rb") as f:
return pickle.load(f)
@@ -9,7 +12,7 @@ def load_poly(filename: str):
return None
def ray_tracing(x, y, poly):
def poly_contains(x, y, poly):
n = len(poly)
inside = False
xints = 0.0
@@ -25,3 +28,11 @@ def ray_tracing(x, y, poly):
inside = not inside
p1x, p1y = p2x, p2y
return inside
def poly_centroid(poly) -> typing.Tuple[float, float]:
x_list = [vertex[0] for vertex in poly]
y_list = [vertex[1] for vertex in poly]
x = sum(x_list) / len(poly)
y = sum(y_list) / len(poly)
return (x, y)

View File

@@ -3,15 +3,15 @@ from dcs import mapping
from .conflicttheater import *
from .base import *
from .landmap import load_poly
from .landmap import load_landmap
class PersianGulfTheater(ConflictTheater):
terrain = dcs.terrain.PersianGulf()
overview_image = "persiangulf.gif"
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (351, 115),
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (389, 22), }
landmap_poly = load_poly("resources\\gulflandmap.p")
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321, 145),
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), }
landmap = load_landmap("resources\\gulflandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (8, 16),
@@ -19,33 +19,39 @@ class PersianGulfTheater(ConflictTheater):
"night": (0, 5),
}
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_LOW)
al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.1)
al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, LAND, SIZE_BIG, IMPORTANCE_HIGH)
al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, IMPORTANCE_HIGH)
sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.3, has_frontline=False)
dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, 1.3)
sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.2)
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.3)
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, 1.3)
dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, IMPORTANCE_HIGH)
sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.3)
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, IMPORTANCE_HIGH)
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, IMPORTANCE_HIGH)
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, 1.2)
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, 1.0)
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_REGULAR, 1.1)
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, IMPORTANCE_HIGH)
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)
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], IMPORTANCE_LOW, 1.1, has_frontline=False)
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, 1.2, has_frontline=False)
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.1)
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, 1.1)
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.2, has_frontline=False)
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, IMPORTANCE_HIGH)
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_HIGH)
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, 1.1)
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, 1.2)
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
shiraz = ControlPoint.from_airport(persiangulf.Shiraz_International_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW)
kerman = ControlPoint.from_airport(persiangulf.Kerman_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW)
west_carrier = ControlPoint.carrier("East carrier", Point(-100531.972946, 60939.275818))
def __init__(self):
super(PersianGulfTheater, self).__init__()
self.add_controlpoint(self.shiraz, connected_to=[self.lar, self.kerman])
self.add_controlpoint(self.kerman, connected_to=[self.lar, self.shiraz])
self.add_controlpoint(self.lar, connected_to=[self.bandar_lengeh, self.qeshm, self.havadarya, self.shiraz, self.kerman])
self.add_controlpoint(self.al_dhafra, connected_to=[self.sir_abu_nuayr, self.al_maktoum])
self.add_controlpoint(self.al_maktoum, connected_to=[self.al_dhafra, self.al_minhad, self.sir_abu_nuayr])
self.add_controlpoint(self.al_minhad, connected_to=[self.al_maktoum, self.dubai])
@@ -61,15 +67,14 @@ class PersianGulfTheater(ConflictTheater):
self.add_controlpoint(self.tunb_island, connected_to=[self.khasab, self.qeshm, self.tunb_kochak])
self.add_controlpoint(self.bandar_lengeh, connected_to=[self.tunb_island, self.lar, self.qeshm])
self.add_controlpoint(self.qeshm, connected_to=[self.bandar_lengeh, self.havadarya, self.tunb_island])
self.add_controlpoint(self.qeshm, connected_to=[self.bandar_lengeh, self.havadarya, self.tunb_island, self.lar])
self.add_controlpoint(self.havadarya, connected_to=[self.lar, self.qeshm, self.bandar_abbas])
self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya])
self.add_controlpoint(self.lar, connected_to=[self.bandar_lengeh, self.qeshm, self.havadarya])
self.add_controlpoint(self.west_carrier)
self.west_carrier.captured = True
self.al_dhafra.captured = True
self.kerman.captured = True
"""
Mid game:

View File

@@ -35,5 +35,5 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie
count = max(COUNT_BY_TASK[task] * multiplier * (1+count_log), 1)
count_per_type = max(int(float(count) / len(unittypes)), 1)
for unit_type in unittypes:
print("{} - {} {}".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})

View File

@@ -0,0 +1,60 @@
import typing
from dcs.mapping import Point
NAME_BY_CATEGORY = {
"power": "Power plant",
"ammo": "Ammo depot",
"fuel": "Fuel depot",
"defense": "AA Defense Site",
"warehouse": "Warehouse",
"farp": "FARP",
"comms": "Comms. tower",
"oil": "Oil platform"
}
ABBREV_NAME = {
"power": "PLANT",
"ammo": "AMMO",
"fuel": "FUEL",
"defense": "AA",
"warehouse": "WARE",
"farp": "FARP",
"comms": "COMMST",
"oil": "OILP"
}
class TheaterGroundObject:
object_id = 0
cp_id = 0
group_id = 0
heading = 0
position = None # type: Point
category = None # type: str
def __init__(self, category, cp_id, group_id, object_id, position, heading):
self.category = category
self.cp_id = cp_id
self.group_id = group_id
self.object_id = object_id
self.position = position
self.heading = heading
@property
def string_identifier(self):
return "{}|{}|{}|{}".format(self.category, self.cp_id, self.group_id, self.object_id)
@property
def group_identifier(self) -> str:
return "{}|{}".format(self.category, self.group_id)
@property
def name_abbrev(self) -> str:
return ABBREV_NAME[self.category]
def __str__(self):
return NAME_BY_CATEGORY[self.category]
def matches_string_identifier(self, id):
return self.string_identifier == id

View File

@@ -1,6 +1,7 @@
from ui.eventmenu import *
from game.game import *
from .styles import STYLES
class BaseMenu(Menu):
@@ -9,7 +10,6 @@ class BaseMenu(Menu):
def __init__(self, window: Window, parent, game: Game, cp: ControlPoint):
super(BaseMenu, self).__init__(window, parent, game)
self.cp = cp
self.base = cp.base
self.frame = window.right_pane
@@ -26,15 +26,15 @@ class BaseMenu(Menu):
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))).grid(row=row, sticky=W)
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))
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)).grid(column=2, row=row)
Button(self.frame, text="+", command=self.buy(unit_type)).grid(column=3, row=row)
Button(self.frame, text="-", command=self.sell(unit_type)).grid(column=4, row=row)
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 = {
@@ -45,13 +45,21 @@ class BaseMenu(Menu):
AirDefence: db.find_unittype(AirDefence, self.game.player),
}
self.budget_label = Label(self.frame, text="Budget: {}m".format(self.game.budget))
# Header
head = Frame(self.frame, **STYLES["header"])
head.grid(row=row, column=0, columnspan=5, sticky=NSEW, pady=5)
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)
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.grid(row=row, sticky=W)
Button(self.frame, text="Back", command=self.dismiss).grid(column=4, row=row)
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(column=4, row=row, padx=(0,15), pady=(0,5))
row += 1
for task_type, units in units.items():
Label(self.frame, text="{}".format(db.task_name(task_type))).grid(row=row, columnspan=5); row += 1
Label(self.frame, text="{}".format(db.task_name(task_type)), **STYLES["strong"]).grid(row=row, columnspan=5, sticky=NSEW); row += 1
units = list(set(units))
units.sort(key=lambda x: db.PRICES[x])

View File

@@ -1,5 +1,8 @@
import webbrowser
from tkinter import *
from tkinter.ttk import *
from .styles import STYLES
from ui.window import *
@@ -20,27 +23,59 @@ class ConfigurationMenu(Menu):
self.night_var = BooleanVar()
self.night_var.set(self.game.settings.night_disabled)
self.cold_start_var = BooleanVar()
self.cold_start_var.set(self.game.settings.cold_start)
def dismiss(self):
self.game.settings.player_skill = self.player_skill_var.get()
self.game.settings.enemy_skill = self.enemy_skill_var.get()
self.game.settings.only_player_takeoff = self.takeoff_var.get()
self.game.settings.night_disabled = self.night_var.get()
self.game.settings.cold_start = self.cold_start_var.get()
super(ConfigurationMenu, self).dismiss()
def display(self):
self.window.clear_right_pane()
Label(self.frame, text="Player coalition skill").grid(row=0, column=0)
Label(self.frame, text="Enemy coalition skill").grid(row=1, column=0)
# Header
head = Frame(self.frame, **STYLES["header"])
head.grid(row=0, column=0, columnspan=2, sticky=NSEW)
Label(head, text="Configuration", **STYLES["title"]).grid()
OptionMenu(self.frame, self.player_skill_var, "Average", "Good", "High", "Excellent").grid(row=0, column=1)
OptionMenu(self.frame, self.enemy_skill_var, "Average", "Good", "High", "Excellent").grid(row=1, column=1)
# Body
body = Frame(self.frame, **STYLES["body"])
body.grid(row=1, column=0, sticky=NSEW)
Checkbutton(self.frame, text="Takeoff only for player group", variable=self.takeoff_var).grid(row=2, column=0, columnspan=2)
Checkbutton(self.frame, text="Disable night missions", variable=self.night_var).grid(row=3, column=0, columnspan=2)
Label(body, text="Player coalition skill", **STYLES["widget"]).grid(row=0, column=0, sticky=W)
Label(body, text="Enemy coalition skill", **STYLES["widget"]).grid(row=1, column=0, sticky=W)
Button(self.frame, text="Back", command=self.dismiss).grid(row=4, column=0, columnspan=1)
Button(self.frame, text="Cheat +200m", command=self.cheat_money).grid(row=5, column=0)
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.configure(**STYLES["btn-primary"])
e_skill = OptionMenu(body, self.enemy_skill_var, "Average", "Good", "High", "Excellent")
e_skill.grid(row=1, column=1, sticky=E)
e_skill.configure(**STYLES["btn-primary"])
Label(body, text="Aircraft cold start", **STYLES["widget"]).grid(row=2, column=0, sticky=W)
Label(body, text="Takeoff only for player group", **STYLES["widget"]).grid(row=3, column=0, sticky=W)
Label(body, text="Disable night missions", **STYLES["widget"]).grid(row=4, column=0, sticky=W)
Checkbutton(body, variable=self.cold_start_var, **STYLES["radiobutton"]).grid(row=2, column=1, sticky=E)
Checkbutton(body, variable=self.takeoff_var, **STYLES["radiobutton"]).grid(row=3, column=1, sticky=E)
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=4, column=1, sticky=E)
Button(body, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(row=5, column=1, sticky=E, pady=30)
Label(body, text="Contributors: ", **STYLES["widget"]).grid(row=6, column=0, sticky=W)
Label(body, text="shdwp - author, maintainer", **STYLES["widget"]).grid(row=7, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/shdwp"), **STYLES["widget"]).grid(row=7, column=1, sticky=E)
Label(body, text="Khopa - contributions", **STYLES["widget"]).grid(row=8, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=8, column=1, sticky=E)
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=10, column=1, pady=30)
def cheat_money(self):
self.game.budget += 200

View File

@@ -1,5 +1,6 @@
from tkinter import *
from tkinter.ttk import *
from .styles import STYLES
from ui.window import *
@@ -12,6 +13,6 @@ class CorruptedSaveMenu(Menu):
def display(self):
self.window.clear_right_pane()
Label(text="Your save game was corrupted!").grid(row=0, column=0)
Label(text="Please restore it by replacing \"liberation_save\" file with \"liberation_save_tmp\" to restore last saved copy.").grid(row=1, column=0)
Label(text="You can find those files under user DCS directory.").grid(row=2, column=0)
Label(text="Your save game is either incompatible or was corrupted!", **STYLES["widget"]).grid(row=0, column=0)
Label(text="Please restore it by replacing \"liberation_save\" file with \"liberation_save_tmp\" to restore last saved copy.", **STYLES["widget"]).grid(row=1, column=0)
Label(text="You can find those files under user Saved Games\\DCS directory.", **STYLES["widget"]).grid(row=2, column=0)

View File

@@ -1,22 +1,23 @@
from dcs.helicopters import helicopter_map
from ui.eventresultsmenu import *
from game import *
from game.event import *
from .styles import STYLES, RED
class EventMenu(Menu):
aircraft_scramble_entries = None # type: typing.Dict[PlaneType , Entry]
aircraft_client_entries = None # type: typing.Dict[PlaneType, Entry]
armor_scramble_entries = None # type: typing.Dict[VehicleType, Entry]
scramble_entries = None # type: typing.Dict[typing.Type[Task], typing.Dict[typing.Type[UnitType], typing.Tuple[Entry, Entry]]]
error_label = None # type: Label
awacs = None # type: IntVar
def __init__(self, window: Window, parent, game: Game, event: event.Event):
super(EventMenu, self).__init__(window, parent, game)
self.event = event
self.aircraft_scramble_entries = {}
self.armor_scramble_entries = {}
self.aircraft_client_entries = {}
self.scramble_entries = {k: {} for k in self.event.tasks}
if self.event.attacker_name == self.game.player:
self.base = self.event.from_cp.base
@@ -30,39 +31,41 @@ class EventMenu(Menu):
self.window.clear_right_pane()
row = 0
def label(text, _row=None, _column=None, sticky=None):
def header(text, style="strong"):
nonlocal row
Label(self.frame, text=text).grid(row=_row and _row or row, column=_column and _column or 0, sticky=sticky)
head = Frame(self.frame, **STYLES["header"])
head.grid(row=row, column=0, sticky=N+EW, columnspan=5)
Label(head, text=text, **STYLES[style]).grid()
row += 1
def label(text, _row=None, _column=None, columnspan=None, sticky=None):
nonlocal row
new_label = Label(self.frame, text=text, **STYLES["widget"])
new_label.grid(row=_row and _row or row, column=_column and _column or 0, columnspan=columnspan, sticky=sticky)
if _row is None:
row += 1
def scrable_row(unit_type, unit_count):
return new_label
def scrable_row(task_type, unit_type, unit_count, client_slots: bool):
nonlocal row
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count), **STYLES["widget"]).grid(row=row, sticky=W)
scramble_entry = Entry(self.frame, width=2)
scramble_entry.grid(column=1, row=row, sticky=W)
scramble_entry.grid(column=1, row=row, sticky=E, padx=5)
scramble_entry.insert(0, "0")
self.aircraft_scramble_entries[unit_type] = scramble_entry
Button(self.frame, text="+", command=self.scramble_half(True, unit_type)).grid(column=2, row=row)
Button(self.frame, text="+", command=self.scramble_half(task_type, unit_type), **STYLES["btn-primary"]).grid(column=2, row=row)
client_entry = Entry(self.frame, width=2)
client_entry.grid(column=3, row=row, sticky=E)
client_entry.insert(0, "0")
self.aircraft_client_entries[unit_type] = client_entry
Button(self.frame, text="+", command=self.client_one(unit_type)).grid(column=4, row=row)
if client_slots:
client_entry = Entry(self.frame, width=2)
client_entry.grid(column=3, row=row, sticky=E, padx=5)
client_entry.insert(0, "0")
Button(self.frame, text="+", command=self.client_one(task_type, unit_type), **STYLES["btn-primary"]).grid(column=4, row=row)
else:
client_entry = None
row += 1
def scramble_armor_row(unit_type, unit_count):
nonlocal row
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
scramble_entry = Entry(self.frame, width=2)
scramble_entry.insert(0, "0")
scramble_entry.grid(column=1, row=row)
self.armor_scramble_entries[unit_type] = scramble_entry
Button(self.frame, text="+", command=self.scramble_half(False, unit_type)).grid(column=2, row=row)
self.scramble_entries[task_type][unit_type] = scramble_entry, client_entry
row += 1
@@ -70,68 +73,60 @@ class EventMenu(Menu):
if threat_descr:
threat_descr = "Approx. {}".format(threat_descr)
Label(self.frame, text="{}. {}".format(self.event, threat_descr)).grid(row=row, column=0, columnspan=5)
# Header
header("Mission Menu", "title")
# 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)
row += 1
Button(self.frame, text="Commit", command=self.start).grid(column=3, row=row, sticky=E)
Button(self.frame, text="Back", command=self.dismiss).grid(column=4, row=row, sticky=E)
awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED
Checkbutton(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), var=self.awacs, state=awacs_enabled).grid(row=row, column=0, sticky=W)
Label(self.frame, text="Amount", **STYLES["widget"]).grid(row=row, column=1, columnspan=2)
Label(self.frame, text="Client slots", **STYLES["widget"]).grid(row=row, column=3, columnspan=2)
row += 1
label("Aircraft")
if self.base.aircraft:
Label(self.frame, text="Amount").grid(row=row, column=1, columnspan=2)
Label(self.frame, text="Client slots").grid(row=row, column=3, columnspan=2)
row += 1
for unit_type, count in self.base.aircraft.items():
scrable_row(unit_type, count)
if not self.base.total_planes:
label("None", sticky=W)
label("Armor")
for unit_type, count in self.base.armor.items():
scramble_armor_row(unit_type, count)
if not self.base.total_armor:
label("None", sticky=W)
def _scrambled_aircraft_count(self, unit_type: UnitType) -> int:
value = self.aircraft_scramble_entries[unit_type].get()
if value and int(value) > 0:
return min(int(value), self.base.aircraft[unit_type])
return 0
def _scrambled_armor_count(self, unit_type: UnitType) -> int:
value = self.armor_scramble_entries[unit_type].get()
if value and int(value) > 0:
return min(int(value), self.base.armor[unit_type])
return 0
def scramble_half(self, aircraft: bool, unit_type: UnitType) -> typing.Callable:
def action():
entry = None # type: Entry
total_count = 0
if aircraft:
entry = self.aircraft_scramble_entries[unit_type]
total_count = self.base.aircraft[unit_type]
for flight_task in self.event.tasks:
header("{}:".format(self.event.flight_name(flight_task)))
if flight_task == PinpointStrike:
if not self.base.armor:
label("No units")
for t, c in self.base.armor.items():
scrable_row(flight_task, t, c, client_slots=False)
else:
entry = self.armor_scramble_entries[unit_type]
total_count = self.base.armor[unit_type]
if not self.base.aircraft:
label("No units")
for t, c in self.base.aircraft.items():
scrable_row(flight_task, t, c, client_slots=True)
existing_count = int(entry.get())
header("Support:")
# Options
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=3, sticky=W, padx=5, pady=5)
row += 1
header("Ready?")
self.error_label = label("", columnspan=4)
self.error_label["fg"] = RED
Button(self.frame, text="Commit", command=self.start, **STYLES["btn-primary"]).grid(column=0, row=row, sticky=E, padx=5, pady=(10,10))
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-warning"]).grid(column=3, row=row, sticky=E, padx=5, pady=(10,10))
row += 1
def scramble_half(self, task: typing.Type[UnitType], unit_type: UnitType) -> typing.Callable:
def action():
entry = self.scramble_entries[task][unit_type][0] # type: Entry
value = entry.get()
total_units = self.base.total_units_of_type(unit_type)
amount = int(value and value or "0")
entry.delete(0, END)
entry.insert(0, "{}".format(int(existing_count + math.ceil(total_count/2))))
entry.insert(0, str(amount + int(math.ceil(total_units/2))))
return action
def client_one(self, unit_type: UnitType) -> typing.Callable:
def client_one(self, task: typing.Type[Task], unit_type: UnitType) -> typing.Callable:
def action():
entry = self.aircraft_client_entries[unit_type] # type: Entry
entry = self.scramble_entries[task][unit_type][1] # type: Entry
value = entry.get()
amount = int(value and value or "0")
entry.delete(0, END)
@@ -145,82 +140,48 @@ class EventMenu(Menu):
else:
self.event.is_awacs_enabled = False
scrambled_aircraft = {}
scrambled_sweep = {}
scrambled_cas = {}
for unit_type, field in self.aircraft_scramble_entries.items():
amount = self._scrambled_aircraft_count(unit_type)
if amount > 0:
task = db.unit_task(unit_type)
flights = {k: {} for k in self.event.tasks} # type: db.TaskForceDict
units_scramble_counts = {} # type: typing.Dict[typing.Type[UnitType], int]
tasks_scramble_counts = {} # type: typing.Dict[typing.Type[Task], int]
tasks_clients_counts = {} # type: typing.Dict[typing.Type[Task], int]
scrambled_aircraft[unit_type] = amount
if task == CAS:
scrambled_cas[unit_type] = amount
elif task == CAP:
scrambled_sweep[unit_type] = amount
def dampen_count(for_task: typing.Type[Task], unit_type: typing.Type[UnitType], count: int) -> int:
nonlocal units_scramble_counts
total_count = self.base.total_units_of_type(unit_type)
scrambled_clients = {}
for unit_type, field in self.aircraft_client_entries.items():
value = field.get()
if value and int(value) > 0:
amount = int(value)
scrambled_clients[unit_type] = amount
total_scrambled = units_scramble_counts.get(unit_type, 0)
dampened_value = count if count + total_scrambled < total_count else total_count - total_scrambled
units_scramble_counts[unit_type] = units_scramble_counts.get(unit_type, 0) + dampened_value
scrambled_armor = {}
for unit_type, field in self.armor_scramble_entries.items():
amount = self._scrambled_armor_count(unit_type)
if amount > 0:
scrambled_armor[unit_type] = amount
return dampened_value
if type(self.event) is CaptureEvent:
e = self.event # type: CaptureEvent
if self.game.is_player_attack(self.event):
e.player_attacking(cas=scrambled_cas,
escort=scrambled_sweep,
armor=scrambled_armor,
clients=scrambled_clients)
else:
e.player_defending(interceptors=scrambled_aircraft,
clients=scrambled_clients)
elif type(self.event) is InterceptEvent:
e = self.event # type: InterceptEvent
if self.game.is_player_attack(self.event):
e.player_attacking(interceptors=scrambled_aircraft,
clients=scrambled_clients)
else:
e.player_defending(escort=scrambled_aircraft,
clients=scrambled_clients)
elif type(self.event) is GroundInterceptEvent:
e = self.event # type: GroundInterceptEvent
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
else:
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
elif type(self.event) is NavalInterceptEvent:
e = self.event # type: NavalInterceptEvent
for task_type, dict in self.scramble_entries.items():
for unit_type, (count_entry, clients_entry) in dict.items():
try:
count = int(count_entry.get())
except:
count = 0
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
else:
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
elif type(self.event) is AntiAAStrikeEvent:
e = self.event # type: AntiAAStrikeEvent
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
else:
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
elif type(self.event) is GroundAttackEvent:
e = self.event # type: GroundAttackEvent
if self.game.is_player_attack(self.event):
assert False
else:
e.player_defending(strikegroup=scrambled_aircraft, clients=scrambled_clients)
elif type(self.event) is InfantryTransportEvent:
e = self.event # type: InfantryTransportEvent
if self.game.is_player_attack(self.event):
e.player_attacking(transport=scrambled_aircraft, clients=scrambled_clients)
else:
assert False
try:
clients_count = int(clients_entry and clients_entry.get() or 0)
except:
clients_count = 0
dampened_count = dampen_count(task_type, unit_type, count)
tasks_clients_counts[task_type] = tasks_clients_counts.get(task_type, 0) + clients_count
tasks_scramble_counts[task_type] = tasks_scramble_counts.get(task_type, 0) + dampened_count
flights[task_type][unit_type] = dampened_count, clients_count
for task in self.event.ai_banned_tasks:
if tasks_clients_counts.get(task, 0) == 0 and tasks_scramble_counts.get(task, 0) > 0:
self.error_label["text"] = "Need at least one player in flight {}".format(self.event.flight_name(task))
return
if self.game.is_player_attack(self.event):
self.event.player_attacking(flights)
else:
self.event.player_defending(flights)
self.game.initiate_event(self.event)
EventResultsMenu(self.window, self.parent, self.game, self.event).display()

View File

@@ -3,6 +3,7 @@ from ui.window import *
from game.game import *
from userdata.debriefing import *
from .styles import STYLES
class EventResultsMenu(Menu):
@@ -13,6 +14,7 @@ class EventResultsMenu(Menu):
def __init__(self, window: Window, parent, game: Game, event: Event):
super(EventResultsMenu, self).__init__(window, parent, game)
self.frame = window.right_pane
self.frame.grid_rowconfigure(0, weight=0)
self.event = event
self.finished = False
@@ -21,47 +23,84 @@ class EventResultsMenu(Menu):
def display(self):
self.window.clear_right_pane()
row = 0
def header(text, style="strong"):
nonlocal row
head = Frame(self.frame, **STYLES["header"])
head.grid(row=row, column=0, sticky=N + EW, columnspan=2, pady=(0, 10))
Label(head, text=text, **STYLES[style]).grid()
row += 1
def label(text, style="widget"):
nonlocal row
Label(self.frame, text=text, **STYLES[style]).grid(row=row, column=0, sticky=NW, columnspan=2)
row += 1
if not self.finished:
"""
For debugging purposes
Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1)).grid()
Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1)).grid(row=1, column=1)
Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5)).grid(row=2, )
Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5)).grid(row=2, column=1)
header("You are clear for takeoff!")
Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0)).grid(row=3, )
Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0)).grid(row=3, column=1)
"""
label("In DCS, open and play the mission:")
label("liberation_nextturn", "italic")
label("or")
label("liberation_nextturn_quick", "italic")
header("Then save the debriefing to the folder:")
label(debriefing_directory_location(), "italic")
header("Waiting for results...")
pg = Progressbar(self.frame, orient="horizontal", length=200, mode="determinate")
pg.grid(row=row, column=0, columnspan=2, sticky=EW, pady=5, padx=5)
pg.start(10)
row += 1
Label(self.frame, text="Cheat operation results: ", **STYLES["strong"]).grid(column=0, row=row,
columnspan=2, sticky=NSEW,
pady=5)
row += 1
Button(self.frame, text="full enemy losses", command=self.simulate_result(0, 1),
**STYLES["btn-warning"]).grid(column=0, row=row, padx=5, pady=5)
Button(self.frame, text="full player losses", command=self.simulate_result(1, 0),
**STYLES["btn-warning"]).grid(column=1, row=row, padx=5, pady=5)
row += 1
Button(self.frame, text="some enemy losses", command=self.simulate_result(0, 0.8),
**STYLES["btn-warning"]).grid(column=0, row=row, padx=5, pady=5)
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)
row += 1
Label(self.frame, text="Play the mission and save debriefing to").grid(row=0, column=0)
Label(self.frame, text=debriefing_directory_location()).grid(row=1, column=0)
else:
row = 0
if self.event.is_successfull(self.debriefing):
Label(self.frame, text="Operation success").grid(row=row, columnspan=1); row += 1
header("Operation success", "title-green")
else:
Label(self.frame, text="Operation failed").grid(row=row, columnspan=1); row += 1
header("Operation failed", "title-red")
header("Player losses")
Separator(self.frame, orient='horizontal').grid(row=row, columnspan=1, sticky=NE); row += 1
Label(self.frame, text="Player losses").grid(row=row, columnspan=1); row += 1
for unit_type, count in self.player_losses.items():
Label(self.frame, text=db.unit_type_name(unit_type)).grid(row=row)
Label(self.frame, text="{}".format(count)).grid(column=1, row=row)
Label(self.frame, text=db.unit_type_name(unit_type), **STYLES["widget"]).grid(row=row)
Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row)
row += 1
header("Enemy losses")
if self.debriefing.destroyed_objects:
Label(self.frame, text="Ground assets", **STYLES["widget"]).grid(row=row)
Label(self.frame, text="{}".format(len(self.debriefing.destroyed_objects)), **STYLES["widget"]).grid(column=1, row=row)
row += 1
Separator(self.frame, orient='horizontal').grid(row=row, columnspan=1, sticky=NE); row += 1
Label(self.frame, text="Enemy losses").grid(columnspan=1, row=row); row += 1
for unit_type, count in self.enemy_losses.items():
if count == 0:
continue
Label(self.frame, text=db.unit_type_name(unit_type)).grid(row=row)
Label(self.frame, text="{}".format(count)).grid(column=1, row=row)
Label(self.frame, text=db.unit_type_name(unit_type), **STYLES["widget"]).grid(row=row)
Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row)
row += 1
Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1
Button(self.frame, text="Okay", command=self.dismiss, **STYLES["btn-primary"]).grid(columnspan=1, row=row);
row += 1
def process_debriefing(self, debriefing: Debriefing):
self.debriefing = debriefing
@@ -79,29 +118,57 @@ class EventResultsMenu(Menu):
def simulate_result(self, player_factor: float, enemy_factor: float):
def action():
debriefing = Debriefing({})
debriefing = Debriefing({}, [])
def count_planes(groups: typing.List[FlyingGroup], mult: float) -> typing.Dict[UnitType, int]:
def count(country: Country) -> typing.Dict[UnitType, int]:
result = {}
for group in groups:
for g in country.plane_group + country.vehicle_group + country.helicopter_group + country.ship_group:
group = g # type: Group
for unit in group.units:
result[unit.unit_type] = result.get(unit.unit_type, 0) + 1 * mult
unit_type = None
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
return {x: math.ceil(y) for x, y in result.items() if y >= 1}
if unit_type in db.EXTRA_AA.values():
continue
player_planes = self.event.operation.mission.country(self.game.player).plane_group
enemy_planes = self.event.operation.mission.country(self.game.enemy).plane_group
result[unit_type] = result.get(unit_type, 0) + 1
self.player_losses = count_planes(player_planes, player_factor)
self.enemy_losses = count_planes(enemy_planes, enemy_factor)
return result
player = self.event.operation.mission.country(self.game.player)
enemy = self.event.operation.mission.country(self.game.enemy)
alive_player_units = count(player)
alive_enemy_units = count(enemy)
destroyed_player_units = db.unitdict_restrict_count(alive_player_units, math.ceil(
sum(alive_player_units.values()) * player_factor))
destroyed_enemy_units = db.unitdict_restrict_count(alive_enemy_units, math.ceil(
sum(alive_enemy_units.values()) * enemy_factor))
alive_player_units = {k: v - destroyed_player_units.get(k, 0) for k, v in alive_player_units.items()}
alive_enemy_units = {k: v - destroyed_enemy_units.get(k, 0) for k, v in alive_enemy_units.items()}
debriefing.alive_units = {
enemy.name: alive_enemy_units,
player.name: alive_player_units,
}
debriefing.destroyed_units = {
self.game.player: self.player_losses,
self.game.enemy: self.enemy_losses,
player.name: destroyed_player_units,
enemy.name: destroyed_enemy_units,
}
self.finished = True
self.debriefing = debriefing
self.player_losses = debriefing.destroyed_units.get(self.game.player, {})
self.enemy_losses = debriefing.destroyed_units.get(self.game.enemy, {})
self.game.finish_event(self.event, debriefing)
self.display()
self.game.pass_turn()

View File

@@ -1,11 +1,13 @@
import pickle
from ui.basemenu import *
from ui.overviewcanvas import *
from ui.configurationmenu import *
from game.game import *
from ui.basemenu import *
from ui.configurationmenu import *
from ui.overviewcanvas import *
from userdata import persistency
from .styles import STYLES
import tkinter as tk
from tkinter import ttk
class MainMenu(Menu):
@@ -18,40 +20,69 @@ class MainMenu(Menu):
self.upd.update()
self.frame = self.window.right_pane
self.frame.grid_columnconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.frame.rowconfigure(0, weight=1)
def display(self):
persistency.save_game(self.game)
self.window.clear_right_pane()
self.upd.update()
row = 1
row = 0
# 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)
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)
Button(header, text="Pass turn", command=self.pass_turn, **STYLES["btn-primary"]).grid(column=2, row=0, sticky=NW)
header.grid(column=0, row=0, sticky=N+EW)
body = LabelFrame(self.frame, **STYLES["body"])
body.grid(column=0, row=1, sticky=NSEW)
def label(text):
nonlocal row
Label(self.frame, text=text).grid(row=row, sticky=NW)
nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=NSEW, columnspan=2)
Label(frame, text=text, **STYLES["widget"]).grid(row=row, sticky=NS)
row += 1
def event_button(event):
nonlocal row
Message(self.frame, text="{}{}".format(
nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=NSEW)
Message(frame, text="{}{}".format(
event.defender_name == self.game.player and "Enemy attacking: " or "",
event
), aspect=1600).grid(column=0, row=row, sticky=NW)
Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S)
), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=NSEW)
Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E)
row += 1
Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
Button(self.frame, text="Configuration", command=self.configuration_menu).grid(column=0, row=0, sticky=NE)
Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=0, row=0, sticky=NW)
Label(self.frame, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount)).grid(column=0, row=0, sticky=N)
Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
def destination_header(text, pady=0):
nonlocal row, body
Label(body, text=text, **STYLES["strong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(pady,0)); row += 1
events = self.game.events
events.sort(key=lambda x: x.to_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))
destination = None
deliveries = False
for event in events:
if not event.informational:
if self.game.is_player_attack(event):
new_destination = "From {} to {}".format(event.from_cp.name, event.to_cp.name)
else:
new_destination = "Enemy attack"
if destination != new_destination:
destination_header(new_destination)
destination = new_destination
if event.informational:
if not deliveries:
deliveries = True
destination_header("Deliveries", 15)
label(str(event))
else:
event_button(event)
@@ -76,3 +107,7 @@ class MainMenu(Menu):
self.basemenu = BaseMenu(self.window, self, self.game, cp)
self.basemenu.display()

View File

@@ -1,7 +1,9 @@
import os
from tkinter import *
from tkinter.ttk import *
from ui.window import *
from .styles import STYLES
class NewGameMenu(Menu):
@@ -14,6 +16,7 @@ class NewGameMenu(Menu):
def __init__(self, window: Window, callback: typing.Callable):
super(NewGameMenu, self).__init__(window, None, None)
self.frame = window.right_pane
window.left_pane.configure(background="black")
self.callback = callback
self.selected_country = IntVar()
@@ -57,23 +60,72 @@ class NewGameMenu(Menu):
def display(self):
self.window.clear_right_pane()
Label(self.frame, text="Player country").grid(row=0, column=0)
Radiobutton(self.frame, text="USA", variable=self.selected_country, value=0).grid(row=1, column=0)
Radiobutton(self.frame, text="Russia", variable=self.selected_country, value=1).grid(row=2, column=0)
# Header
head = Frame(self.frame, **STYLES["header"])
head.grid(row=0, column=0, sticky=NSEW)
Label(head, text="Start a new game", **STYLES["title"]).grid()
Label(self.frame, text="Terrain").grid(row=0, column=1)
Radiobutton(self.frame, text="Caucasus", variable=self.selected_terrain, value=0).grid(row=1, column=1)
Radiobutton(self.frame, text="Nevada", variable=self.selected_terrain, value=1).grid(row=2, column=1)
Radiobutton(self.frame, text="Persian Gulf", variable=self.selected_terrain, value=2).grid(row=3, column=1)
# Body
body = Frame(self.frame, **STYLES["body"])
body.grid(row=1, column=0, sticky=NSEW)
Label(self.frame, text="Options").grid(row=1, column=2)
Checkbutton(self.frame, text="SAMs", variable=self.sams).grid(row=1, column=2)
Checkbutton(self.frame, text="Mid game", variable=self.midgame).grid(row=2, column=2)
# Country Selection
country = LabelFrame(body, text="Player Side", **STYLES["label-frame"])
country.grid(row=0, column=0, sticky=NW, padx=5)
Radiobutton(country, variable=self.selected_country, value=0, **STYLES["radiobutton"]).grid(row=0, column=0,
sticky=W)
Label(country, text="USA", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
Radiobutton(country, variable=self.selected_country, value=1, **STYLES["radiobutton"]).grid(row=1, column=0,
sticky=W)
Label(country, text="Russia", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
Label(self.frame, text="Multiplier").grid(row=0, column=3)
Entry(self.frame, textvariable=self.multiplier).grid(row=1, column=3)
# Terrain Selection
terrain = LabelFrame(body, text="Terrain", **STYLES["label-frame"])
terrain.grid(row=0, column=1, sticky=N, padx=5)
Button(self.frame, text="Proceed", command=self.proceed).grid(row=5, column=0, columnspan=4)
Radiobutton(terrain, variable=self.selected_terrain, value=0, **STYLES["radiobutton"]) \
.grid(row=0, column=0, sticky=W)
Label(terrain, text="Caucasus", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
self.create_label_image(terrain, "terrain_caucasus.gif").grid(row=0, column=2, padx=5)
Radiobutton(terrain, variable=self.selected_terrain, value=1, **STYLES["radiobutton"]) \
.grid(row=1, column=0, sticky=W)
Label(terrain, text="Nevada", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
self.create_label_image(terrain, "terrain_nevada.gif").grid(row=1, column=2, padx=5)
Radiobutton(terrain, variable=self.selected_terrain, value=2, **STYLES["radiobutton"]) \
.grid(row=2, column=0, 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)
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
options = LabelFrame(body, text="Misc Options", **STYLES["label-frame"])
options.grid(row=0, column=2, sticky=NE, padx=5)
Checkbutton(options, variable=self.sams, **STYLES["radiobutton"]).grid(row=0, column=0, sticky=W)
Label(options, text="SAMs", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
Checkbutton(options, variable=self.midgame, **STYLES["radiobutton"]).grid(row=1, column=0, sticky=W)
Label(options, text="Mid Game", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
Label(options, text="Multiplier", **STYLES["widget"]).grid(row=2, column=0, sticky=W)
Entry(options, textvariable=self.multiplier).grid(row=2, column=1, sticky=W)
# Footer with Proceed Button
footer = Frame(self.frame, **STYLES["header"])
footer.grid(row=2, sticky=N + E + W)
Button(footer, text="Proceed", command=self.proceed, **STYLES["btn-primary"]).grid(row=0, column=0, sticky=SE,
padx=5, pady=5)
@staticmethod
def create_label_image(parent, image):
im = PhotoImage(file=os.path.join("resources", "ui", image))
label = Label(parent, image=im)
label.image = im
return label
def proceed(self):
self.callback(self.player_country_name,

View File

@@ -6,6 +6,7 @@ from tkinter.ttk import *
from ui.window import *
from game.game import *
from gen.conflictgen import Conflict
from theater.conflicttheater import *
@@ -20,36 +21,40 @@ class OverviewCanvas:
self.canvas = Canvas(frame, width=self.image.width(), height=self.image.height())
self.canvas.grid(column=0, row=0, sticky=NSEW)
def transform_point(self, p: Point) -> (int, int):
def transform_point(self, p: Point, treshold=30) -> (int, int):
point_a = list(self.game.theater.reference_points.keys())[0]
point_a_img = self.game.theater.reference_points[point_a]
point_b = list(self.game.theater.reference_points.keys())[1]
point_b_img = self.game.theater.reference_points[point_b]
x_dist = point_a_img[0] - point_b_img[0]
Y_dist = point_a_img[0] - point_b_img[0]
lon_dist = point_a[1] - point_b[1]
y_dist = point_a_img[1] - point_b_img[1]
X_dist = point_a_img[1] - point_b_img[1]
lat_dist = point_b[0] - point_a[0]
x_scale = float(x_dist) / float(lon_dist)
y_scale = float(y_dist) / float(lat_dist)
Y_scale = float(Y_dist) / float(lon_dist)
X_scale = float(X_dist) / float(lat_dist)
# ---
x_offset = p.x - point_a[0]
y_offset = p.y - point_a[1]
Y_offset = p.x - point_a[0]
X_offset = p.y - point_a[1]
return point_b_img[1] + y_offset * y_scale, point_a_img[0] - x_offset * x_scale
X = point_b_img[1] + X_offset * X_scale
Y = point_a_img[0] - Y_offset * Y_scale
return X > treshold and X or treshold, Y > treshold and Y or treshold
def create_cp_title(self, coords, cp: ControlPoint):
title = cp.name
font = ("Helvetica", 13)
font = ("Helvetica", 10)
id = self.canvas.create_text(coords[0], coords[1], text=title, font=font)
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font)
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
id = self.canvas.create_text(coords[0], coords[1], text=title, font=font)
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
def _player_color(self):
return self.game.player == "USA" and "blue" or "red"
@@ -74,9 +79,17 @@ class OverviewCanvas:
self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color)
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
frontline_pos, heading, distance = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
distance = max(distance, 1000)
start_coords = self.transform_point(frontline_pos, treshold=10)
end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60)
self.canvas.create_line((*start_coords, *end_coords), width=2, fill=color)
for cp in self.game.theater.controlpoints:
coords = self.transform_point(cp.position)
arc_size = 22 * math.pow(cp.importance, 1)
arc_size = 16 * math.pow(cp.importance, 1)
extent = max(cp.base.strength * 180, 10)
start = (180 - extent) / 2
@@ -86,7 +99,7 @@ class OverviewCanvas:
color = self._enemy_color()
cp_id = self.canvas.create_arc((coords[0] - arc_size/2, coords[1] - arc_size/2),
(coords[0]+arc_size/2, coords[1]+arc_size/2),
(coords[0] + arc_size/2, coords[1] + arc_size/2),
fill=color,
style=PIESLICE,
start=start,
@@ -105,7 +118,8 @@ class OverviewCanvas:
self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp)
units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa)
self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 10))
self.canvas.create_text(coords[0]+1, coords[1] - arc_size / 1.5 +1, text=units_title, font=("Helvetica", 8), fill=color)
self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 8), fill="white")
def display(self, cp: ControlPoint):
def action(_):

45
ui/styles.py Normal file
View File

@@ -0,0 +1,45 @@
# Style for UI
# Padding
PADDING_X = 5
PADDING_Y = 5
# Colors
FG_COLOR = "white"
FG_COLOR_LIGHT = "#dddddd"
BG_COLOR = "#4E5760"
GREEN = "#699245"
YELLOW = "#BF9A46"
RED = "#D0232E"
BG_TITLE_COLOR = "#2D3E50"
# Fonts
FONT_FAMILY = "Trebuchet MS"
DEFAULT_FONT = (FONT_FAMILY, 8)
ITALIC = (FONT_FAMILY, 8, "italic")
BOLD_FONT = (FONT_FAMILY, 10, "bold italic")
TITLE_FONT = (FONT_FAMILY, 16, "bold italic")
# List of styles
STYLES = {}
STYLES["label-frame"] = {"font": BOLD_FONT, "bg": BG_COLOR, "fg": FG_COLOR}
STYLES["frame-wrapper"] = {"bg": BG_COLOR, "relief":"sunken"}
STYLES["body"] = {"bg": BG_COLOR, "padx": 25, "pady": 35}
STYLES["strong"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR}
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["widget"] = {"bg": BG_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": DEFAULT_FONT}
STYLES["italic"] = {"bg": BG_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": ITALIC}
STYLES["radiobutton"] = {"bg": BG_COLOR, "fg": "black", "padx": PADDING_X, "pady": PADDING_Y, "font": DEFAULT_FONT,
"activebackground": BG_COLOR, "highlightbackground": BG_COLOR, "selectcolor": "white"}
STYLES["title"] = {"bg": BG_TITLE_COLOR, "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["header"] = {"bg": BG_TITLE_COLOR}
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-warning"] = {"bg": YELLOW, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}

View File

@@ -1,6 +1,6 @@
from tkinter import *
from game.game import *
from .styles import BG_COLOR,BG_TITLE_COLOR
class Window:
image = None
@@ -9,21 +9,24 @@ class Window:
def __init__(self):
self.tk = Tk()
self.tk.title("DCS Liberation")
self.tk.iconbitmap("icon.ico")
self.tk.resizable(True, True)
self.tk.grid_columnconfigure(0, weight=1)
self.tk.grid_rowconfigure(0, weight=1)
self.frame = Frame(self.tk)
self.frame = Frame(self.tk, bg=BG_COLOR)
self.frame.grid(column=0, row=0, sticky=NSEW)
self.frame.grid_columnconfigure(0, minsize=300)
self.frame.grid_columnconfigure(1, minsize=400)
self.frame.grid_columnconfigure(0)
self.frame.grid_columnconfigure(1)
self.frame.grid_columnconfigure(0, weight=0)
self.frame.grid_columnconfigure(1, weight=1)
self.frame.grid_rowconfigure(0, weight=1)
self.left_pane = Frame(self.frame)
self.left_pane = Frame(self.frame, bg=BG_TITLE_COLOR)
self.left_pane.grid(row=0, column=0, sticky=NSEW)
self.right_pane = Frame(self.frame)
self.right_pane = Frame(self.frame, bg=BG_COLOR)
self.right_pane.grid(row=0, column=1, sticky=NSEW)
self.tk.focus()

View File

@@ -1,3 +1,4 @@
import logging
import typing
import re
import threading
@@ -58,14 +59,59 @@ def parse_mutliplayer_debriefing(contents: str):
class Debriefing:
def __init__(self, dead_units):
def __init__(self, dead_units, dead_objects):
self.destroyed_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._dead_units = dead_units
self._dead_objects = dead_objects
@classmethod
def parse(cls, path: str):
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):
nonlocal dead_objects
object_mission_id = int(object_mission_id_str)
if object_mission_id in dead_objects:
logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
return
dead_objects.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):
try:
append_dead_object(event["initiatorMissionID"])
except Exception as e:
logging.error(e)
with open(path, "r") as f:
table_string = f.read()
try:
@@ -74,37 +120,18 @@ class Debriefing:
table = parse_mutliplayer_debriefing(table_string)
events = table.get("debriefing", {}).get("events", {})
dead_units = {}
for event in events.values():
event_type = event.get("type", None)
if event_type != "crash" and event_type != "dead":
continue
if event_type in ["crash", "dead"]:
object_initiator = event["initiator"] in ["SKLAD_CRUSH", "SKLADCDESTR", "TEC_A_CRUSH", "BAK_CRUSH"]
defense_initiator = event["initiator"].startswith("defense|")
try:
components = event["initiator"].split("|")
print(components)
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:
print("Skipped due to no unit type")
continue
if object_initiator or defense_initiator:
parse_dead_object(event)
else:
parse_dead_unit(event)
if category != "unit":
print("Skipped due to category")
continue
except Exception as e:
print(e)
continue
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
return Debriefing(dead_units)
return Debriefing(dead_units, dead_objects)
def calculate_units(self, mission: Mission, player_name: str, enemy_name: str):
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
@@ -142,6 +169,12 @@ class Debriefing:
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:
return os.path.join(base_path(), "liberation_debriefings")

40
userdata/logging.py Normal file
View File

@@ -0,0 +1,40 @@
import logging
import traceback
import sys
from io import StringIO
from tkinter import *
from tkinter.scrolledtext import *
_version_string = None
def _error_prompt():
tk = Tk()
Label(tk, text="Oops, something went wrong.").grid(row=0)
Label(tk, text="Please send following text to the developer:").grid(row=1)
text = ScrolledText(tk)
text.insert("0.0", log_stream.getvalue())
text.grid(row=2, sticky=NSEW)
tk.focus()
def _handle_exception(self, exception: BaseException, *args):
logging.exception(exception)
_error_prompt()
def setup_version_string(str):
global _version_string
_version_string = str
if "--stdout" in sys.argv:
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
else:
log_stream = StringIO()
logging.basicConfig(stream=log_stream, level=logging.INFO)
Tk.report_callback_exception = _handle_exception
logging.info("DCS Libration {}".format(_version_string))

View File

@@ -1,6 +1,8 @@
import logging
import typing
import pickle
import os
import sys
import shutil
_user_folder = None # type: str
@@ -16,10 +18,10 @@ def base_path() -> str:
assert _user_folder
openbeta_path = os.path.join(_user_folder, "Saved Games", "DCS.openbeta")
if os.path.exists(openbeta_path):
if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path):
return openbeta_path
else:
return os.path.join(_user_folder, "Saved Games" , "DCS")
return os.path.join(_user_folder, "Saved Games", "DCS")
def _save_file() -> str:
@@ -42,11 +44,8 @@ def restore_game():
if not _save_file_exists():
return None
try:
with open(_save_file(), "rb") as f:
return pickle.load(f)
except Exception as e:
raise e
with open(_save_file(), "rb") as f:
return pickle.load(f)
def save_game(game) -> bool:
@@ -56,5 +55,5 @@ def save_game(game) -> bool:
shutil.copy(_temporary_save_file(), _save_file())
return True
except Exception as e:
print(e)
logging.error(e)
return False