mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
39
__init__.py
39
__init__.py
@@ -17,45 +17,6 @@ from theater import start_generator
|
||||
from userdata import persistency
|
||||
|
||||
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
|
||||
persistency.setup(sys.argv[1])
|
||||
dcs.planes.FlyingType.payload_dirs.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads"))
|
||||
|
||||
|
||||
34
game/db.py
34
game/db.py
@@ -73,6 +73,8 @@ PRICES = {
|
||||
An_30M: 13,
|
||||
Yak_40: 13,
|
||||
S_3B_Tanker: 13,
|
||||
IL_78M: 13,
|
||||
KC_135: 13,
|
||||
|
||||
A_50: 8,
|
||||
E_3A: 8,
|
||||
@@ -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, ],
|
||||
@@ -251,6 +258,7 @@ UNIT_BY_COUNTRY = {
|
||||
L_39ZA,
|
||||
|
||||
IL_76MD,
|
||||
IL_78M,
|
||||
An_26B,
|
||||
An_30M,
|
||||
Yak_40,
|
||||
@@ -290,6 +298,7 @@ UNIT_BY_COUNTRY = {
|
||||
A_10C,
|
||||
AV8BNA,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
C_130,
|
||||
E_3A,
|
||||
@@ -432,6 +441,31 @@ 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:
|
||||
groups = list(unitdict_split(unit_dict, total_count))
|
||||
if len(groups) > 0:
|
||||
return groups[0]
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
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 *
|
||||
|
||||
@@ -4,19 +4,19 @@ import random
|
||||
from dcs.task import *
|
||||
|
||||
from game import db
|
||||
from game.operation.capture import CaptureOperation
|
||||
from game.operation.baseattack import BaseAttackOperation
|
||||
from userdata.debriefing import Debriefing
|
||||
|
||||
from .event import Event
|
||||
|
||||
|
||||
class CaptureEvent(Event):
|
||||
class BaseAttackEvent(Event):
|
||||
silent = True
|
||||
BONUS_BASE = 15
|
||||
STRENGTH_RECOVERY = 0.35
|
||||
STRENGTH_RECOVERY = 0.55
|
||||
|
||||
def __str__(self):
|
||||
return "Attack from {} to {}".format(self.from_cp, self.to_cp)
|
||||
return "Base 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])
|
||||
@@ -28,7 +28,7 @@ class CaptureEvent(Event):
|
||||
return not attackers_success
|
||||
|
||||
def commit(self, debriefing: Debriefing):
|
||||
super(CaptureEvent, self).commit(debriefing)
|
||||
super(BaseAttackEvent, self).commit(debriefing)
|
||||
if self.is_successfull(debriefing):
|
||||
if self.from_cp.captured:
|
||||
self.to_cp.captured = True
|
||||
@@ -41,7 +41,7 @@ class CaptureEvent(Event):
|
||||
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
||||
|
||||
def skip(self):
|
||||
if self.to_cp.captured:
|
||||
if not self.is_player_attacking and self.to_cp.captured:
|
||||
self.to_cp.captured = False
|
||||
|
||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
||||
@@ -49,13 +49,13 @@ class CaptureEvent(Event):
|
||||
escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier)
|
||||
attackers = self.from_cp.base.armor
|
||||
|
||||
op = CaptureOperation(game=self.game,
|
||||
attacker_name=self.attacker_name,
|
||||
defender_name=self.defender_name,
|
||||
attacker_clients={},
|
||||
defender_clients=clients,
|
||||
from_cp=self.from_cp,
|
||||
to_cp=self.to_cp)
|
||||
op = BaseAttackOperation(game=self.game,
|
||||
attacker_name=self.attacker_name,
|
||||
defender_name=self.defender_name,
|
||||
attacker_clients={},
|
||||
defender_clients=clients,
|
||||
from_cp=self.from_cp,
|
||||
to_cp=self.to_cp)
|
||||
|
||||
op.setup(cas=cas,
|
||||
escort=escort,
|
||||
@@ -67,13 +67,13 @@ class CaptureEvent(Event):
|
||||
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)
|
||||
op = BaseAttackOperation(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))
|
||||
73
game/event/frontlineattack.py
Normal file
73
game/event/frontlineattack.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
from dcs.task import *
|
||||
from dcs.vehicles import AirDefence
|
||||
|
||||
from game import *
|
||||
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())
|
||||
|
||||
def __str__(self):
|
||||
return "Frontline attack from {} at {}".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 = (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, armor: db.ArmorDict, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
||||
self.defenders = self.to_cp.base.assemble_attack()
|
||||
|
||||
op = FrontlineAttackOperation(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.defenders,
|
||||
attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())),
|
||||
strikegroup=strikegroup)
|
||||
|
||||
self.operation = op
|
||||
|
||||
87
game/event/frontlinepatrol.py
Normal file
87
game/event/frontlinepatrol.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
from dcs.task import *
|
||||
from dcs.vehicles import AirDefence
|
||||
|
||||
from game import *
|
||||
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))
|
||||
|
||||
def __str__(self):
|
||||
return "Frontline CAP from {} at {}".format(self.from_cp, self.to_cp)
|
||||
|
||||
"""
|
||||
def is_successfull(self, debriefing: Debriefing):
|
||||
total_targets = sum(self.cas.values())
|
||||
destroyed_targets = 0
|
||||
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
||||
if unit in self.cas:
|
||||
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 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, interceptors: db.PlaneDict, clients: db.PlaneDict, armor: db.ArmorDict):
|
||||
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,
|
||||
attacker_clients=clients,
|
||||
defender_clients={},
|
||||
from_cp=self.from_cp,
|
||||
to_cp=self.to_cp)
|
||||
|
||||
defenders = self.to_cp.base.assemble_attack()
|
||||
op.setup(cas=self.cas,
|
||||
escort=self.escort,
|
||||
interceptors=interceptors,
|
||||
armor_attackers=db.unitdict_restrict_count(armor, 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
|
||||
53
game/event/insurgentattack.py
Normal file
53
game/event/insurgentattack.py
Normal file
@@ -0,0 +1,53 @@
|
||||
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
|
||||
|
||||
|
||||
class InsurgentAttackEvent(Event):
|
||||
SUCCESS_FACTOR = 0.7
|
||||
TARGET_VARIETY = 2
|
||||
TARGET_AMOUNT_FACTOR = 0.5
|
||||
|
||||
@property
|
||||
def threat_description(self):
|
||||
return ""
|
||||
|
||||
def __str__(self):
|
||||
return "Destroy insurgents at {}".format(self.to_cp)
|
||||
|
||||
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, 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 = InsurgentAttackOperation(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
|
||||
33
game/game.py
33
game/game.py
@@ -5,6 +5,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,7 +24,7 @@ COMMISION_LIMITS_FACTORS = {
|
||||
|
||||
COMMISION_AMOUNTS_SCALE = 1.5
|
||||
COMMISION_AMOUNTS_FACTORS = {
|
||||
PinpointStrike: 2,
|
||||
PinpointStrike: 6,
|
||||
CAS: 1,
|
||||
CAP: 2,
|
||||
AirDefence: 0.3,
|
||||
@@ -31,6 +32,7 @@ COMMISION_AMOUNTS_FACTORS = {
|
||||
|
||||
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 25
|
||||
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2
|
||||
PLAYER_BASEATTACK_THRESHOLD = 0.2
|
||||
|
||||
"""
|
||||
Various events probabilities. First key is player probabilty, second is enemy probability.
|
||||
@@ -39,17 +41,18 @@ 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],
|
||||
InterceptEvent: [25, 10],
|
||||
GroundInterceptEvent: [25, 10],
|
||||
GroundAttackEvent: [0, 10],
|
||||
InsurgentAttackEvent: [0, 10],
|
||||
NavalInterceptEvent: [25, 10],
|
||||
AntiAAStrikeEvent: [25, 10],
|
||||
InfantryTransportEvent: [25, 0],
|
||||
@@ -65,7 +68,7 @@ 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
|
||||
# Bonus multiplier logarithm base
|
||||
@@ -112,11 +115,18 @@ class Game:
|
||||
continue
|
||||
|
||||
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
|
||||
if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent:
|
||||
if not Conflict.has_frontline_between(player_cp, enemy_cp):
|
||||
continue
|
||||
|
||||
if 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))
|
||||
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
|
||||
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 in enemy_generated_types:
|
||||
continue
|
||||
@@ -130,7 +140,7 @@ class Game:
|
||||
if event_class == NavalInterceptEvent:
|
||||
if player_cp.radials == LAND:
|
||||
continue
|
||||
elif event_class == CaptureEvent:
|
||||
elif event_class == BaseAttackEvent:
|
||||
if enemy_cap_generated:
|
||||
continue
|
||||
if enemy_cp.base.total_armor == 0:
|
||||
@@ -207,8 +217,11 @@ class Game:
|
||||
else:
|
||||
print("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):
|
||||
for event in self.events:
|
||||
|
||||
@@ -6,7 +6,7 @@ from gen.aircraft import *
|
||||
from gen.aaa import *
|
||||
from gen.shipgen import *
|
||||
from gen.triggergen import *
|
||||
from gen.awacsgen import *
|
||||
from gen.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
from gen.conflictgen import Conflict
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ from gen.aircraft import *
|
||||
from gen.aaa import *
|
||||
from gen.shipgen import *
|
||||
from gen.triggergen import *
|
||||
from gen.awacsgen import *
|
||||
from gen.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
|
||||
from .operation import Operation
|
||||
|
||||
|
||||
class CaptureOperation(Operation):
|
||||
class BaseAttackOperation(Operation):
|
||||
cas = None # type: db.PlaneDict
|
||||
escort = None # type: db.PlaneDict
|
||||
intercept = None # type: db.PlaneDict
|
||||
@@ -37,7 +37,7 @@ class CaptureOperation(Operation):
|
||||
self.aa = aa
|
||||
|
||||
def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool):
|
||||
super(CaptureOperation, self).prepare(terrain, is_quick)
|
||||
super(BaseAttackOperation, self).prepare(terrain, is_quick)
|
||||
|
||||
self.defenders_starting_position = None
|
||||
if self.game.player == self.defender_name:
|
||||
@@ -60,8 +60,8 @@ class CaptureOperation(Operation):
|
||||
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.airgen.generate_attackers_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position)
|
||||
|
||||
self.visualgen.generate_target_smokes(self.to_cp)
|
||||
super(CaptureOperation, self).generate()
|
||||
super(BaseAttackOperation, self).generate()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from itertools import zip_longest
|
||||
|
||||
from dcs.terrain import Terrain
|
||||
|
||||
from game import db
|
||||
@@ -6,33 +8,36 @@ from gen.aircraft import *
|
||||
from gen.aaa import *
|
||||
from gen.shipgen import *
|
||||
from gen.triggergen import *
|
||||
from gen.awacsgen import *
|
||||
from gen.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
from gen.conflictgen import Conflict
|
||||
|
||||
from .operation import Operation
|
||||
|
||||
|
||||
class GroundInterceptOperation(Operation):
|
||||
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||
|
||||
|
||||
class FrontlineAttackOperation(Operation):
|
||||
attackers = None # type: db.ArmorDict
|
||||
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):
|
||||
attackers: db.ArmorDict,
|
||||
strikegroup: db.PlaneDict):
|
||||
self.strikegroup = strikegroup
|
||||
self.interceptors = interceptors
|
||||
self.target = target
|
||||
self.attackers = attackers
|
||||
|
||||
def prepare(self, terrain: Terrain, is_quick: bool):
|
||||
super(GroundInterceptOperation, self).prepare(terrain, is_quick)
|
||||
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.ground_intercept_conflict(
|
||||
conflict = Conflict.frontline_cas_conflict(
|
||||
attacker=self.mission.country(self.attacker_name),
|
||||
defender=self.mission.country(self.defender_name),
|
||||
from_cp=self.from_cp,
|
||||
@@ -44,10 +49,6 @@ class GroundInterceptOperation(Operation):
|
||||
conflict=conflict)
|
||||
|
||||
def generate(self):
|
||||
self.armorgen.generate_vec(self.attackers, self.target)
|
||||
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()
|
||||
super(FrontlineAttackOperation, self).generate()
|
||||
58
game/operation/frontlinepatrol.py
Normal file
58
game/operation/frontlinepatrol.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from itertools import zip_longest
|
||||
|
||||
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.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
from gen.conflictgen import Conflict
|
||||
|
||||
from .operation import Operation
|
||||
|
||||
|
||||
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||
|
||||
|
||||
class FrontlinePatrolOperation(Operation):
|
||||
cas = None # type: db.PlaneDict
|
||||
escort = None # type: db.PlaneDict
|
||||
interceptors = None # type: db.PlaneDict
|
||||
|
||||
armor_attackers = None # type: db.ArmorDict
|
||||
armor_defenders = None # type: db.ArmorDict
|
||||
|
||||
def setup(self, cas: db.PlaneDict, escort: db.PlaneDict, interceptors: db.PlaneDict, 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(self.cas, {}, self.defenders_starting_position)
|
||||
self.airgen.generate_defenders_escort(self.escort, {}, self.defenders_starting_position)
|
||||
self.airgen.generate_patrol(self.interceptors, self.attacker_clients, self.attackers_starting_position)
|
||||
|
||||
self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders)
|
||||
super(FrontlinePatrolOperation, self).generate()
|
||||
@@ -6,7 +6,7 @@ from gen.aircraft import *
|
||||
from gen.aaa import *
|
||||
from gen.shipgen import *
|
||||
from gen.triggergen import *
|
||||
from gen.awacsgen import *
|
||||
from gen.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
from gen.conflictgen import Conflict
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ from gen.aircraft import *
|
||||
from gen.aaa import *
|
||||
from gen.shipgen import *
|
||||
from gen.triggergen import *
|
||||
from gen.awacsgen import *
|
||||
from gen.airsupportgen import *
|
||||
from gen.visualgen import *
|
||||
from gen.conflictgen import Conflict
|
||||
|
||||
from .operation import Operation
|
||||
|
||||
|
||||
class GroundAttackOperation(Operation):
|
||||
class InsurgentAttackOperation(Operation):
|
||||
strikegroup = None # type: db.PlaneDict
|
||||
target = None # type: db.ArmorDict
|
||||
|
||||
@@ -24,7 +24,7 @@ class GroundAttackOperation(Operation):
|
||||
self.target = target
|
||||
|
||||
def prepare(self, terrain: Terrain, is_quick: bool):
|
||||
super(GroundAttackOperation, self).prepare(terrain, is_quick)
|
||||
super(InsurgentAttackOperation, self).prepare(terrain, is_quick)
|
||||
|
||||
conflict = Conflict.ground_attack_conflict(
|
||||
attacker=self.mission.country(self.attacker_name),
|
||||
@@ -41,4 +41,4 @@ class GroundAttackOperation(Operation):
|
||||
self.airgen.generate_defense(self.strikegroup, self.defender_clients, self.defenders_starting_position)
|
||||
self.armorgen.generate(self.target, {})
|
||||
|
||||
super(GroundAttackOperation, self).generate()
|
||||
super(InsurgentAttackOperation, self).generate()
|
||||
@@ -52,7 +52,7 @@ 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(self.escort, clients=self.defender_clients)
|
||||
|
||||
self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position)
|
||||
super(InterceptOperation, self).generate()
|
||||
|
||||
@@ -18,7 +18,7 @@ class Operation:
|
||||
extra_aagen = None # type: ExtraAAConflictGenerator
|
||||
shipgen = None # type: ShipGenerator
|
||||
triggersgen = None # type: TriggersGenerator
|
||||
awacsgen = None # type: AWACSConflictGenerator
|
||||
awacsgen = None # type: AirSupportConflictGenerator
|
||||
visualgen = None # type: VisualGenerator
|
||||
envgen = None # type: EnvironmentGenerator
|
||||
|
||||
@@ -52,7 +52,7 @@ 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.awacsgen = 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)
|
||||
@@ -78,12 +78,16 @@ class Operation:
|
||||
|
||||
def generate(self):
|
||||
self.visualgen.generate()
|
||||
|
||||
if self.is_awacs_enabled:
|
||||
self.awacsgen.generate()
|
||||
self.awacsgen.generate(self.is_awacs_enabled)
|
||||
|
||||
self.extra_aagen.generate()
|
||||
self.triggersgen.generate(self.is_quick, self.trigger_radius)
|
||||
|
||||
if self.game.is_player_attack(self.conflict.attackers_side):
|
||||
cp = self.conflict.from_cp
|
||||
else:
|
||||
cp = self.conflict.to_cp
|
||||
|
||||
self.triggersgen.generate(cp, self.is_quick, self.trigger_radius)
|
||||
|
||||
if self.environment_settings is None:
|
||||
self.environment_settings = self.envgen.generate()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from .aaa import *
|
||||
from .aircraft import *
|
||||
from .armor import *
|
||||
from .awacsgen import *
|
||||
from .airsupportgen import *
|
||||
from .conflictgen import *
|
||||
from .shipgen import *
|
||||
from .visualgen import *
|
||||
|
||||
@@ -242,11 +242,39 @@ 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)))
|
||||
self._rtb_for(group, self.conflict.from_cp, at)
|
||||
|
||||
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
||||
assert 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)
|
||||
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):
|
||||
assert len(self.escort_targets) == 0
|
||||
|
||||
@@ -268,7 +296,7 @@ class AircraftConflictGenerator:
|
||||
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 +306,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,6 +333,24 @@ class AircraftConflictGenerator:
|
||||
self._setup_group(group, CAP, client_count)
|
||||
self._rtb_for(group, self.conflict.to_cp, at)
|
||||
|
||||
def generate_patrol(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_transport(self, transport: db.PlaneDict, destination: Airport):
|
||||
assert len(self.escort_targets) == 0
|
||||
|
||||
@@ -341,6 +387,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)
|
||||
|
||||
|
||||
50
gen/airsupportgen.py
Normal file
50
gen/airsupportgen.py
Normal file
@@ -0,0 +1,50 @@
|
||||
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):
|
||||
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 = self.conflict.from_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
|
||||
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=140,
|
||||
start_type=StartType.Warm,
|
||||
)
|
||||
|
||||
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=251,
|
||||
start_type=StartType.Warm,
|
||||
)
|
||||
70
gen/armor.py
70
gen/armor.py
@@ -1,3 +1,6 @@
|
||||
from random import randint
|
||||
from itertools import zip_longest
|
||||
|
||||
from game import db
|
||||
from .conflictgen import *
|
||||
from .naming import *
|
||||
@@ -11,6 +14,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,7 +32,7 @@ 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):
|
||||
group = self.m.vehicle_group(
|
||||
side,
|
||||
@@ -34,24 +41,65 @@ 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):
|
||||
position = self.conflict.position.point_from_heading(self.conflict.heading,
|
||||
random.randint(FRONTLINE_CAS_PADDING, int(self.conflict.distance - FRONTLINE_CAS_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
|
||||
)
|
||||
@@ -20,6 +20,7 @@ AIR_DISTANCE = 40000
|
||||
|
||||
CAPTURE_AIR_ATTACKERS_DISTANCE = 25000
|
||||
CAPTURE_AIR_DEFENDERS_DISTANCE = 60000
|
||||
CAP_CAS_DISTANCE = 10000, 120000
|
||||
|
||||
GROUND_INTERCEPT_SPREAD = 5000
|
||||
GROUND_DISTANCE_FACTOR = 1
|
||||
@@ -38,8 +39,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 +67,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,15 +103,73 @@ 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)
|
||||
|
||||
@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 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 = center_position
|
||||
|
||||
for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000):
|
||||
pos = center_position.point_from_heading(_heading_sum(heading, -90), offset)
|
||||
if not theater.is_on_land(pos):
|
||||
break
|
||||
else:
|
||||
left_position = pos
|
||||
|
||||
right_position = center_position
|
||||
for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000):
|
||||
pos = center_position.point_from_heading(_heading_sum(heading, 90), offset)
|
||||
if not theater.is_on_land(pos):
|
||||
break
|
||||
else:
|
||||
right_position = pos
|
||||
|
||||
return left_position, _heading_sum(heading, 90), right_position.distance_to_point(left_position)
|
||||
|
||||
@classmethod
|
||||
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||
@@ -189,25 +256,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
|
||||
@@ -261,8 +348,7 @@ 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)
|
||||
if not dest:
|
||||
|
||||
@@ -18,7 +18,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 +28,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
|
||||
}
|
||||
|
||||
@@ -90,8 +90,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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,16 +67,16 @@ 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:
|
||||
if plane_group.task == AWACS.name or plane_group.task == Refueling.name:
|
||||
continue
|
||||
|
||||
regroup_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position)
|
||||
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
|
||||
|
||||
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)
|
||||
@@ -99,7 +99,7 @@ class TriggersGenerator:
|
||||
plane_group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
|
||||
push_by_trigger.append(plane_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 +136,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):
|
||||
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 +148,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():
|
||||
|
||||
1
resources/tools/dcs
Symbolic link
1
resources/tools/dcs
Symbolic link
@@ -0,0 +1 @@
|
||||
../../submodules/dcs/dcs
|
||||
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)
|
||||
@@ -1 +1 @@
|
||||
py.exe __init__.py %UserProfile% > logs.txt 2>&1
|
||||
py.exe __init__.py "%UserProfile%" > logs.txt 2>&1
|
||||
|
||||
@@ -150,7 +150,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 +167,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())
|
||||
|
||||
@@ -10,10 +10,11 @@ class ControlPoint:
|
||||
connected_points = [] # type: typing.List[ControlPoint]
|
||||
position = None # type: Point
|
||||
captured = False
|
||||
base: None # type: theater.base.Base
|
||||
at: None # type: db.StartPosition
|
||||
has_frontline = True
|
||||
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, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
|
||||
import theater.base
|
||||
|
||||
self.name = " ".join(re.split(r" |-", name)[:2])
|
||||
@@ -24,14 +25,15 @@ class ControlPoint:
|
||||
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.name, airport.position, airport, radials, size, importance, has_frontline)
|
||||
|
||||
@classmethod
|
||||
def carrier(cls, name: str, at: Point):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -19,27 +19,27 @@ 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.1, 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, IMPORTANCE_LOW)
|
||||
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, 1.3)
|
||||
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
|
||||
|
||||
west_carrier = ControlPoint.carrier("East carrier", Point(-100531.972946, 60939.275818))
|
||||
|
||||
@@ -61,7 +61,7 @@ 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])
|
||||
@@ -69,8 +69,7 @@ class PersianGulfTheater(ConflictTheater):
|
||||
self.add_controlpoint(self.west_carrier)
|
||||
|
||||
self.west_carrier.captured = True
|
||||
|
||||
self.al_dhafra.captured = True
|
||||
self.lar.captured = True
|
||||
|
||||
"""
|
||||
Mid game:
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
from dcs.helicopters import helicopter_map
|
||||
|
||||
from ui.eventresultsmenu import *
|
||||
|
||||
from game import *
|
||||
from game.event import *
|
||||
|
||||
|
||||
UNITTYPES_FOR_EVENTS = {
|
||||
FrontlineAttackEvent: [CAS, PinpointStrike],
|
||||
FrontlinePatrolEvent: [CAP, PinpointStrike],
|
||||
InterceptEvent: [CAP],
|
||||
InsurgentAttackEvent: [CAS],
|
||||
NavalInterceptEvent: [CAS],
|
||||
AntiAAStrikeEvent: [CAS],
|
||||
InfantryTransportEvent: [Embarking],
|
||||
}
|
||||
|
||||
|
||||
class EventMenu(Menu):
|
||||
aircraft_scramble_entries = None # type: typing.Dict[PlaneType , Entry]
|
||||
aircraft_client_entries = None # type: typing.Dict[PlaneType, Entry]
|
||||
@@ -87,7 +100,14 @@ class EventMenu(Menu):
|
||||
Label(self.frame, text="Client slots").grid(row=row, column=3, columnspan=2)
|
||||
row += 1
|
||||
|
||||
filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__]
|
||||
for unit_type, count in self.base.aircraft.items():
|
||||
if filter_to and db.unit_task(unit_type) not in filter_to:
|
||||
continue
|
||||
|
||||
if unit_type in helicopter_map and self.event.__class__ != InsurgentAttackEvent:
|
||||
continue
|
||||
|
||||
scrable_row(unit_type, count)
|
||||
|
||||
if not self.base.total_planes:
|
||||
@@ -95,6 +115,9 @@ class EventMenu(Menu):
|
||||
|
||||
label("Armor")
|
||||
for unit_type, count in self.base.armor.items():
|
||||
if filter_to and db.unit_task(unit_type) not in filter_to:
|
||||
continue
|
||||
|
||||
scramble_armor_row(unit_type, count)
|
||||
|
||||
if not self.base.total_armor:
|
||||
@@ -172,8 +195,8 @@ class EventMenu(Menu):
|
||||
if amount > 0:
|
||||
scrambled_armor[unit_type] = amount
|
||||
|
||||
if type(self.event) is CaptureEvent:
|
||||
e = self.event # type: CaptureEvent
|
||||
if type(self.event) is BaseAttackEvent:
|
||||
e = self.event # type: BaseAttackEvent
|
||||
if self.game.is_player_attack(self.event):
|
||||
e.player_attacking(cas=scrambled_cas,
|
||||
escort=scrambled_sweep,
|
||||
@@ -190,12 +213,12 @@ class EventMenu(Menu):
|
||||
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 FrontlineAttackEvent:
|
||||
e = self.event # type: FrontlineAttackEvent
|
||||
e.player_attacking(armor=scrambled_armor, strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
||||
elif type(self.event) is FrontlinePatrolEvent:
|
||||
e = self.event # type: FrontlinePatrolEvent
|
||||
e.player_attacking(interceptors=scrambled_aircraft, clients=scrambled_clients, armor=scrambled_armor)
|
||||
elif type(self.event) is NavalInterceptEvent:
|
||||
e = self.event # type: NavalInterceptEvent
|
||||
|
||||
@@ -209,8 +232,8 @@ class EventMenu(Menu):
|
||||
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
|
||||
elif type(self.event) is InsurgentAttackEvent:
|
||||
e = self.event # type: InsurgentAttackEvent
|
||||
if self.game.is_player_attack(self.event):
|
||||
assert False
|
||||
else:
|
||||
|
||||
@@ -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 *
|
||||
|
||||
|
||||
@@ -44,7 +45,7 @@ class OverviewCanvas:
|
||||
|
||||
def create_cp_title(self, coords, cp: ControlPoint):
|
||||
title = cp.name
|
||||
font = ("Helvetica", 13)
|
||||
font = ("Helvetica", 10)
|
||||
|
||||
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))
|
||||
@@ -74,9 +75,14 @@ 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)
|
||||
start_coords, end_coords = self.transform_point(frontline_pos), self.transform_point(frontline_pos.point_from_heading(heading, distance))
|
||||
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
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ def base_path() -> str:
|
||||
global _user_folder
|
||||
assert _user_folder
|
||||
|
||||
openbeta_path = os.path.join(_user_folder, "Saved Games\DCS.openbeta")
|
||||
openbeta_path = os.path.join(_user_folder, "Saved Games", "DCS.openbeta")
|
||||
if 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,7 +35,7 @@ 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():
|
||||
|
||||
Reference in New Issue
Block a user