mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12853feec3 | ||
|
|
d31876b65e | ||
|
|
ca521e7e51 | ||
|
|
f21c515d5c | ||
|
|
fa5259d1f2 | ||
|
|
7a5361c057 | ||
|
|
40bfb6fa88 | ||
|
|
61a237d1ae | ||
|
|
cf7276b528 | ||
|
|
67f69d0a7f | ||
|
|
a918914431 | ||
|
|
4ba1dd87e8 | ||
|
|
e0d82da6cb | ||
|
|
2179e4af47 | ||
|
|
75d734d6e4 | ||
|
|
1d73affa08 | ||
|
|
834ee30c86 | ||
|
|
d236b8a94d | ||
|
|
a132cba7ef | ||
|
|
86706231e0 | ||
|
|
5eb921948a | ||
|
|
fd54a5f86c | ||
|
|
1b2ad5b419 | ||
|
|
14cd54668e | ||
|
|
2047089b64 | ||
|
|
ee924ef36e | ||
|
|
f9c1dd980b | ||
|
|
74c1861240 | ||
|
|
e8226782c1 | ||
|
|
a592cf3a05 | ||
|
|
0b1cb0d770 | ||
|
|
a4aa1cff3a | ||
|
|
6a02d2ffb6 | ||
|
|
7458181e90 | ||
|
|
ec28cdc936 | ||
|
|
9dbc9a8a56 | ||
|
|
73d4a2d414 | ||
|
|
e93ad8b800 | ||
|
|
d48985ca6d | ||
|
|
5f7d717b63 | ||
|
|
e266698e68 | ||
|
|
e8098e795c | ||
|
|
8d69724272 | ||
|
|
683114f916 | ||
|
|
3b454470f9 | ||
|
|
f40f83bb09 | ||
|
|
932bec2f84 | ||
|
|
820820eb92 | ||
|
|
6f5835a2b8 | ||
|
|
07128bb5e6 | ||
|
|
b302372d4c | ||
|
|
cad7d2c735 | ||
|
|
e4c3f8bce2 | ||
|
|
1fbf4e292a | ||
|
|
62f5b2d06d | ||
|
|
3831658162 | ||
|
|
b2545e4de0 | ||
|
|
26e43f5e54 | ||
|
|
44ba5c32c6 | ||
|
|
ce7d3a89c0 | ||
|
|
c74b1205df | ||
|
|
58dd16219b | ||
|
|
e0e1d0238f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
__pycache__
|
||||
build/*
|
||||
resources/payloads/*.lua
|
||||
venv
|
||||
logs.txt
|
||||
.DS_Store
|
||||
# User-specific stuff
|
||||
|
||||
74
__init__.py
74
__init__.py
@@ -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()
|
||||
|
||||
145
game/db.py
145
game/db.py
@@ -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()
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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
|
||||
96
game/event/baseattack.py
Normal file
96
game/event/baseattack.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from game.operation.baseattack import BaseAttackOperation
|
||||
|
||||
from .event import *
|
||||
from game.db import assigned_units_from
|
||||
|
||||
|
||||
class BaseAttackEvent(Event):
|
||||
silent = True
|
||||
BONUS_BASE = 15
|
||||
STRENGTH_RECOVERY = 0.55
|
||||
|
||||
def __str__(self):
|
||||
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])
|
||||
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
|
||||
attackers_success = alive_attackers >= alive_defenders
|
||||
if self.from_cp.captured:
|
||||
return attackers_success
|
||||
else:
|
||||
return not attackers_success
|
||||
|
||||
def commit(self, debriefing: 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)
|
||||
else:
|
||||
if not self.from_cp.captured:
|
||||
self.to_cp.captured = False
|
||||
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
||||
|
||||
def skip(self):
|
||||
if not self.is_player_attacking and self.to_cp.captured:
|
||||
self.to_cp.captured = False
|
||||
|
||||
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 = 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=assigned_units_from(cas),
|
||||
escort=assigned_units_from(escort),
|
||||
intercept=flights[CAP],
|
||||
attack=attackers,
|
||||
defense=self.to_cp.base.armor,
|
||||
aa=self.to_cp.base.aa)
|
||||
|
||||
self.operation = op
|
||||
|
||||
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=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())
|
||||
|
||||
self.operation = op
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
from dcs.task import *
|
||||
|
||||
from game import db
|
||||
from game.operation.capture import CaptureOperation
|
||||
from userdata.debriefing import Debriefing
|
||||
|
||||
from .event import Event
|
||||
|
||||
|
||||
class CaptureEvent(Event):
|
||||
silent = True
|
||||
BONUS_BASE = 15
|
||||
STRENGTH_RECOVERY = 0.35
|
||||
|
||||
def __str__(self):
|
||||
return "Attack from {} to {}".format(self.from_cp, self.to_cp)
|
||||
|
||||
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 = alive_attackers >= alive_defenders
|
||||
if self.from_cp.captured:
|
||||
return attackers_success
|
||||
else:
|
||||
return not attackers_success
|
||||
|
||||
def commit(self, debriefing: Debriefing):
|
||||
super(CaptureEvent, self).commit(debriefing)
|
||||
if self.is_successfull(debriefing):
|
||||
if self.from_cp.captured:
|
||||
self.to_cp.captured = True
|
||||
self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name])
|
||||
|
||||
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
||||
else:
|
||||
if not self.from_cp.captured:
|
||||
self.to_cp.captured = False
|
||||
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
||||
|
||||
def skip(self):
|
||||
if self.to_cp.captured:
|
||||
self.to_cp.captured = False
|
||||
|
||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
||||
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.setup(cas=cas,
|
||||
escort=escort,
|
||||
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)
|
||||
|
||||
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,
|
||||
defense=self.to_cp.base.armor,
|
||||
aa=self.to_cp.base.assemble_aa())
|
||||
|
||||
self.operation = op
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from dcs.unittype import UnitType
|
||||
from dcs.task import *
|
||||
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 +48,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 +94,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
|
||||
|
||||
|
||||
82
game/event/frontlineattack.py
Normal file
82
game/event/frontlineattack.py
Normal 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
|
||||
|
||||
76
game/event/frontlinepatrol.py
Normal file
76
game/event/frontlinepatrol.py
Normal 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(dict_from_flight(flights[PinpointStrike]), sum(defenders.values())),
|
||||
armor_defenders=defenders)
|
||||
|
||||
self.operation = op
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
60
game/event/insurgentattack.py
Normal file
60
game/event/insurgentattack.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
60
game/event/strike.py
Normal 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
|
||||
61
game/game.py
61
game/game.py
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
63
game/operation/baseattack.py
Normal file
63
game/operation/baseattack.py
Normal 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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
54
game/operation/frontlineattack.py
Normal file
54
game/operation/frontlineattack.py
Normal 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()
|
||||
54
game/operation/frontlinepatrol.py
Normal file
54
game/operation/frontlinepatrol.py
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
37
game/operation/insurgentattack.py
Normal file
37
game/operation/insurgentattack.py
Normal 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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,37 @@ class Operation:
|
||||
def generate(self):
|
||||
self.visualgen.generate()
|
||||
|
||||
# air support
|
||||
self.airsupportgen.generate(self.is_awacs_enabled)
|
||||
self.briefinggen.append_frequency("Tanker", "10X/240 MHz FM")
|
||||
if self.is_awacs_enabled:
|
||||
self.awacsgen.generate()
|
||||
self.briefinggen.append_frequency("AWACS", "244 MHz FM")
|
||||
|
||||
# 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 []
|
||||
# main frequencies
|
||||
self.briefinggen.append_frequency("Flight", "251 MHz FM")
|
||||
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
|
||||
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
|
||||
|
||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||
return True
|
||||
# briefing
|
||||
self.briefinggen.generate()
|
||||
|
||||
64
game/operation/strike.py
Normal file
64
game/operation/strike.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
197
gen/aircraft.py
197
gen/aircraft.py
@@ -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
54
gen/airsupportgen.py
Normal 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=240,
|
||||
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=244,
|
||||
start_type=StartType.Warm,
|
||||
)
|
||||
75
gen/armor.py
75
gen/armor.py
@@ -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))
|
||||
|
||||
@@ -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
49
gen/briefinggen.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
78
gen/groundobjectsgen.py
Normal 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
5
gen/heli.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .aircraft import *
|
||||
|
||||
|
||||
class HelicopterConflictGenerator(AircraftConflictGenerator):
|
||||
pass
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,42 @@ 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)
|
||||
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
|
||||
|
||||
plane_group.points.remove(w1)
|
||||
plane_group.points.remove(w2)
|
||||
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)
|
||||
|
||||
plane_group.points.insert(1, w2)
|
||||
plane_group.points.insert(1, w1)
|
||||
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,14 +139,10 @@ 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"
|
||||
|
||||
# dcs require at least some slots on both sides for the mission to start
|
||||
self.mission.groundControl.red_observer = 1
|
||||
self.mission.groundControl.blue_observer = 1
|
||||
|
||||
self.mission.coalition[player_coalition].bullseye = {"x": self.conflict.position.x,
|
||||
"y": self.conflict.position.y}
|
||||
|
||||
@@ -152,6 +151,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)
|
||||
|
||||
|
||||
@@ -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
resources/cau_groundobjects.p
Normal file
BIN
resources/cau_groundobjects.p
Normal file
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 |
BIN
resources/tools/cau_groundobjects.miz
Normal file
BIN
resources/tools/cau_groundobjects.miz
Normal file
Binary file not shown.
Binary file not shown.
1
resources/tools/dcs
Symbolic link
1
resources/tools/dcs
Symbolic link
@@ -0,0 +1 @@
|
||||
../../submodules/dcs/dcs
|
||||
86
resources/tools/generate_groundobjectsmap.py
Normal file
86
resources/tools/generate_groundobjectsmap.py
Normal 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
42
resources/tools/miz_diff.py
Normal file
42
resources/tools/miz_diff.py
Normal 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)
|
||||
52
resources/tools/mkrelease.py
Normal file
52
resources/tools/mkrelease.py
Normal 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()
|
||||
BIN
resources/ui/terrain_caucasus.gif
Normal file
BIN
resources/ui/terrain_caucasus.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 852 B |
BIN
resources/ui/terrain_nevada.gif
Normal file
BIN
resources/ui/terrain_nevada.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/ui/terrain_pg.gif
Normal file
BIN
resources/ui/terrain_pg.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Submodule submodules/dcs updated: c913af9c76...e2f8478c4e
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -51,5 +51,4 @@ class NevadaTheater(ConflictTheater):
|
||||
self.add_controlpoint(self.laughlin, connected_to=[self.jean, self.las_vegas])
|
||||
|
||||
self.mina.captured = True
|
||||
self.pahute_mesa.captured = True
|
||||
self.groom_lake.captured = True
|
||||
|
||||
|
||||
@@ -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,16 +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:
|
||||
|
||||
@@ -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})
|
||||
|
||||
60
theater/theatergroundobject.py
Normal file
60
theater/theatergroundobject.py
Normal 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
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
261
ui/eventmenu.py
261
ui/eventmenu.py
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
45
ui/styles.py
Normal 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}
|
||||
15
ui/window.py
15
ui/window.py
@@ -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()
|
||||
|
||||
@@ -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
40
userdata/logging.py
Normal 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))
|
||||
@@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import typing
|
||||
import pickle
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
_user_folder = None # type: str
|
||||
@@ -15,11 +17,11 @@ def base_path() -> str:
|
||||
global _user_folder
|
||||
assert _user_folder
|
||||
|
||||
openbeta_path = os.path.join(_user_folder, "Saved Games\DCS.openbeta")
|
||||
if os.path.exists(openbeta_path):
|
||||
openbeta_path = os.path.join(_user_folder, "Saved Games", "DCS.openbeta")
|
||||
if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path):
|
||||
return openbeta_path
|
||||
else:
|
||||
return os.path.expanduser("~\Saved Games\DCS")
|
||||
return os.path.join(_user_folder, "Saved Games", "DCS")
|
||||
|
||||
|
||||
def _save_file() -> str:
|
||||
@@ -35,18 +37,15 @@ def _save_file_exists() -> bool:
|
||||
|
||||
|
||||
def mission_path_for(name: str) -> str:
|
||||
return os.path.join(base_path(), "Missions\{}".format(name))
|
||||
return os.path.join(base_path(), "Missions", "{}".format(name))
|
||||
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user