Merge pull request #66 from shdwp/develop

Develop
This commit is contained in:
Vasyl Horbachenko 2019-05-31 16:49:03 +03:00 committed by GitHub
commit ba8112280d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 1956 additions and 449 deletions

6
.gitignore vendored
View File

@ -1,10 +1,14 @@
*.pyc
__pycache__
build/*
build/**
resources/payloads/*.lua
venv
logs.txt
.DS_Store
dist/**
a.py
resources/tools/a.miz
tests/**
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml

1
.idea/modules.xml generated
View File

@ -3,7 +3,6 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/dcs_pmcliberation.iml" filepath="$PROJECT_DIR$/.idea/dcs_pmcliberation.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/dcs_pmcliberation.iml" filepath="$PROJECT_DIR$/.idea/dcs_pmcliberation.iml" />
</modules>
</component>
</project>

View File

@ -1,21 +1,16 @@
#!/usr/bin/env python3
import logging
import os
import re
import sys
import dcs
import logging
import theater.caucasus
import theater.persiangulf
import theater.nevada
import ui.window
import ui.corruptedsavemenu
import ui.mainmenu
import ui.newgamemenu
import ui.corruptedsavemenu
import ui.window
from game.game import Game
from theater import start_generator
from userdata import persistency, logging as logging_module
assert len(sys.argv) >= 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
@ -53,40 +48,11 @@ def is_version_compatible(save_version):
w = ui.window.Window()
try:
game = persistency.restore_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):
if terrain == "persiangulf":
conflicttheater = theater.persiangulf.PersianGulfTheater()
elif terrain == "nevada":
conflicttheater = theater.nevada.NevadaTheater()
else:
conflicttheater = theater.caucasus.CaucasusTheater()
if midgame:
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
conflicttheater.controlpoints[i].captured = True
start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
start_generator.generate_groundobjects(conflicttheater)
game = Game(player_name=player_name,
enemy_name=enemy_name,
theater=conflicttheater)
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()))
proceed_to_main_menu(game)
new_game_menu = ui.newgamemenu.NewGameMenu(w, start_new_game)
new_game_menu.display()
ui.newgamemenu.NewGameMenu(w, w.start_new_game).display()
else:
game.settings.version = VERSION_STRING
proceed_to_main_menu(game)

38
a.py Normal file
View File

@ -0,0 +1,38 @@
from theater.caucasus import *
from gen.conflictgen import Conflict
from matplotlib import pyplot
from matplotlib import lines
from shapely import geometry
from shapely.geometry import Polygon
from descartes.patch import PolygonPatch
def put_lines(ls, ax):
for g in ls.geoms:
ax.plot([g.xy[0][0], g.xy[0][1]], [g.xy[1][0], g.xy[1][1]])
cau = CaucasusTheater()
#left, heading, dist = Conflict.frontline_vector(cau.soganlug, cau.kutaisi, cau)
#right = left.point_from_heading(heading, dist)
left, heading = Conflict.frontline_position(cau, cau.soganlug, cau.kutaisi)
right = left.point_from_heading(heading+90, 80000)
left = left.point_from_heading(heading-90, 80000)
line = geometry.LineString([(left.x, left.y), (right.x, right.y)])
line = line.intersection(cau.land_poly)
fig = pyplot.figure(1, figsize=(20, 20), dpi=90)
ax = fig.add_subplot(121)
ax.set_ylim([0, 1500000])
ax.set_xlim([-600000, 400000])
patch = PolygonPatch(cau.land_poly, facecolor=(0, 0, 0), edgecolor=(0, 0, 0), alpha=0.5, zorder=2)
ax.add_patch(patch)
ax.plot([left.x, right.x], [left.y, right.y], 'k-', lw=2)
ax.plot([cau.soganlug.position.x, cau.soganlug.position.x+1000], [cau.soganlug.position.y, cau.soganlug.position.y+1000], lw=5)
ax.plot([cau.kutaisi.position.x, cau.kutaisi.position.x+1000], [cau.kutaisi.position.y, cau.kutaisi.position.y+1000], lw=5)
put_lines(line, ax)
pyplot.show()

View File

@ -39,12 +39,11 @@ and prioritization for the enemy (i.e. less important bases will receive units w
"""
PRICES = {
# fighter
C_101CC: 8,
MiG_23MLD: 18,
Su_27: 20,
MiG_23MLD: 13,
Su_27: 18,
Su_33: 22,
MiG_29A: 23,
MiG_29S: 25,
MiG_29A: 18,
MiG_29S: 20,
F_5E_3: 6,
MiG_15bis: 5,
@ -55,6 +54,7 @@ PRICES = {
M_2000C: 13,
FA_18C_hornet: 18,
F_15C: 20,
F_14B: 14,
# bomber
Su_25: 15,
@ -85,14 +85,14 @@ PRICES = {
C_130: 8,
# armor
Armor.APC_BTR_80: 12,
Armor.MBT_T_55: 14,
Armor.MBT_T_80U: 18,
Armor.MBT_T_90: 20,
Armor.APC_BTR_80: 16,
Armor.MBT_T_55: 22,
Armor.MBT_T_80U: 28,
Armor.MBT_T_90: 35,
Armor.ATGM_M1134_Stryker: 12,
Armor.MBT_M60A3_Patton: 14,
Armor.MBT_M1A2_Abrams: 18,
Armor.ATGM_M1134_Stryker: 18,
Armor.MBT_M60A3_Patton: 24,
Armor.MBT_M1A2_Abrams: 35,
Unarmed.Transport_UAZ_469: 3,
Unarmed.Transport_Ural_375: 3,
@ -137,7 +137,6 @@ Following tasks are present:
"""
UNIT_BY_TASK = {
CAP: [
C_101CC,
F_5E_3,
MiG_23MLD,
Su_27,
@ -147,6 +146,7 @@ UNIT_BY_TASK = {
MiG_29S,
FA_18C_hornet,
F_15C,
F_14B,
M_2000C,
],
CAS: [
@ -235,7 +235,6 @@ SAM_BAN = [
Units that will always be spawned in the air
"""
TAKEOFF_BAN = [
AV8BNA, # AI takeoff currently bugged attempting VTOL with no regards for the total weight
]
"""
@ -259,7 +258,6 @@ Be advised that putting unit to the country that have not access to the unit in
"""
UNIT_BY_COUNTRY = {
"Russia": [
C_101CC,
AJS37,
MiG_23MLD,
F_5E_3,
@ -308,6 +306,7 @@ UNIT_BY_COUNTRY = {
"USA": [
F_5E_3,
F_15C,
F_14B,
FA_18C_hornet,
AJS37,
M_2000C,
@ -345,6 +344,7 @@ UNIT_BY_COUNTRY = {
CARRIER_TYPE_BY_PLANE = {
FA_18C_hornet: CVN_74_John_C__Stennis,
F_14B: CVN_74_John_C__Stennis,
Ka_50: LHA_1_Tarawa,
SA342M: LHA_1_Tarawa,
UH_1H: LHA_1_Tarawa,
@ -371,16 +371,25 @@ Payload will be used for operation of following type, "*" category will be used
PLANE_PAYLOAD_OVERRIDES = {
FA_18C_hornet: {
CAP: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
Escort: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
PinpointStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
AntishipStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
},
F_14B: {
CAP: "AIM-54A-MK47*4, AIM-7M*2, AIM-9M*2, XT*2",
Escort: "AIM-54A-MK47*4, AIM-7M*2, AIM-9M*2, XT*2",
CAS: "AIM-54A-MK60*1, AIM-7M*1, AIM-9M*2, XT*2, Mk-82*2, LANTIRN",
GroundAttack: "AIM54, AIM-9M*2, XT*2, GBU-12*4, LANTIRN",
},
Su_25T: {
CAS: "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410",
},
Su_33: {
CAP: "R-73*4,R-27R*2,R-27ER*6",
Escort: "R-73*4,R-27R*2,R-27ER*6",
},
AJS37: {
@ -403,6 +412,7 @@ PLANE_PAYLOAD_OVERRIDES = {
M_2000C: {
CAP: "Combat Air Patrol",
Escort: "Combat Air Patrol",
GroundAttack: "MK-82S Heavy Strike",
},
@ -487,6 +497,7 @@ def task_name(task) -> str:
def choose_units(for_task: Task, factor: float, count: int, country: str) -> typing.Collection[UnitType]:
suitable_unittypes = find_unittype(for_task, country)
suitable_unittypes = [x for x in suitable_unittypes if x not in helicopter_map.values()]
suitable_unittypes.sort(key=lambda x: PRICES[x])
idx = int(len(suitable_unittypes) * factor)

View File

@ -5,5 +5,6 @@ from .intercept import *
from .baseattack import *
from .navalintercept import *
from .insurgentattack import *
from .convoystrike import *
from .infantrytransport import *
from .strike import *

View File

@ -25,10 +25,10 @@ class BaseAttackEvent(Event):
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])
alive_attackers = sum([v for k, v in debriefing.alive_units.get(self.attacker_name, {}).items() if db.unit_task(k) == PinpointStrike])
alive_defenders = sum([v for k, v in debriefing.alive_units.get(self.defender_name, {}).items() if db.unit_task(k) == PinpointStrike])
attackers_success = alive_attackers >= alive_defenders
if self.from_cp.captured:
if self.departure_cp.captured:
return attackers_success
else:
return not attackers_success
@ -36,14 +36,14 @@ class BaseAttackEvent(Event):
def commit(self, debriefing: Debriefing):
super(BaseAttackEvent, self).commit(debriefing)
if self.is_successfull(debriefing):
if self.from_cp.captured:
if self.departure_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:
if not self.departure_cp.captured:
self.to_cp.captured = False
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
@ -54,14 +54,15 @@ class BaseAttackEvent(Event):
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
cas = self.departure_cp.base.scramble_cas(self.game.settings.multiplier)
escort = self.departure_cp.base.scramble_sweep(self.game.settings.multiplier)
attackers = self.departure_cp.base.armor
op = BaseAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
op.setup(cas=assigned_units_from(cas),
@ -80,6 +81,7 @@ class BaseAttackEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier)

View File

@ -0,0 +1,83 @@
import math
import random
from dcs.task import *
from game import *
from game.event import *
from game.event.frontlineattack import FrontlineAttackEvent
from .event import *
from game.operation.convoystrike import ConvoyStrikeOperation
TRANSPORT_COUNT = 4, 6
DEFENDERS_AMOUNT_FACTOR = 4
class ConvoyStrikeEvent(Event):
SUCCESS_FACTOR = 0.6
STRENGTH_INFLUENCE = 0.25
targets = None # type: db.ArmorDict
@property
def threat_description(self):
return ""
@property
def tasks(self):
return [CAS]
@property
def global_cp_available(self) -> bool:
return True
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAS:
return "Strike flight"
def __str__(self):
return "Convoy Strike"
def skip(self):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def commit(self, debriefing: Debriefing):
super(ConvoyStrikeEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def is_successfull(self, debriefing: Debriefing):
killed_units = sum([v for k, v in debriefing.destroyed_units.get(self.defender_name, {}).items() if db.unit_task(k) in [PinpointStrike, Reconnaissance]])
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_attacking(self, flights: db.TaskForceDict):
assert CAS in flights and len(flights) == 1, "Invalid flights"
convoy_unittype = db.find_unittype(Reconnaissance, self.defender_name)[0]
defense_unittype = db.find_unittype(PinpointStrike, self.defender_name)[0]
defenders_count = int(math.ceil(self.from_cp.base.strength * self.from_cp.importance * DEFENDERS_AMOUNT_FACTOR))
self.targets = {convoy_unittype: random.randrange(*TRANSPORT_COUNT),
defense_unittype: defenders_count, }
op = ConvoyStrikeOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=flights[CAS])
self.operation = op

View File

@ -9,12 +9,14 @@ from dcs.unittype import UnitType
from game import *
from theater import *
from gen.environmentgen import EnvironmentSettings
from gen.conflictgen import Conflict
from game.db import assigned_units_from, unitdict_from
from userdata.debriefing import Debriefing
from userdata import persistency
DIFFICULTY_LOG_BASE = 1.1
EVENT_DEPARTURE_MAX_DISTANCE = 340000
class Event:
@ -22,18 +24,26 @@ class Event:
informational = False
is_awacs_enabled = False
ca_slots = 0
game = None # type: Game
location = None # type: Point
from_cp = None # type: ControlPoint
departure_cp = None # type: ControlPoint
to_cp = None # type: ControlPoint
operation = None # type: Operation
difficulty = 1 # type: int
game = None # type: Game
environment_settings = None # type: EnvironmentSettings
BONUS_BASE = 5
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game):
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
self.game = game
self.departure_cp = None
self.from_cp = from_cp
self.to_cp = target_cp
self.location = location
self.attacker_name = attacker_name
self.defender_name = defender_name
self.to_cp = to_cp
self.from_cp = from_cp
self.game = game
@property
def is_player_attacking(self) -> bool:
@ -44,7 +54,7 @@ class Event:
if self.attacker_name == self.game.player:
return self.to_cp
else:
return self.from_cp
return self.departure_cp
@property
def threat_description(self) -> str:
@ -61,17 +71,43 @@ class Event:
def ai_banned_tasks(self) -> typing.Collection[typing.Type[Task]]:
return []
@property
def player_banned_tasks(self) -> typing.Collection[typing.Type[Task]]:
return []
@property
def global_cp_available(self) -> bool:
return False
def is_departure_available_from(self, cp: ControlPoint) -> bool:
if not cp.captured:
return False
if self.location.distance_to_point(cp.position) > EVENT_DEPARTURE_MAX_DISTANCE:
return False
if cp.is_global and not self.global_cp_available:
return False
return True
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_attacking(self, cp: ControlPoint, flights: db.TaskForceDict):
if self.is_player_attacking:
self.departure_cp = cp
else:
self.to_cp = cp
def player_defending(self, flights: db.TaskForceDict):
assert False
def player_defending(self, cp: ControlPoint, flights: db.TaskForceDict):
if self.is_player_attacking:
self.departure_cp = cp
else:
self.to_cp = cp
def generate(self):
self.operation.is_awacs_enabled = self.is_awacs_enabled
@ -93,7 +129,7 @@ class Event:
def commit(self, debriefing: Debriefing):
for country, losses in debriefing.destroyed_units.items():
if country == self.attacker_name:
cp = self.from_cp
cp = self.departure_cp
else:
cp = self.to_cp
@ -122,11 +158,12 @@ class UnitsDeliveryEvent(Event):
units = None # type: typing.Dict[UnitType, int]
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game):
super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name,
defender_name=defender_name,
super(UnitsDeliveryEvent, self).__init__(game=game,
location=to_cp.position,
from_cp=from_cp,
to_cp=to_cp,
game=game)
target_cp=to_cp,
attacker_name=attacker_name,
defender_name=defender_name)
self.units = {}

View File

@ -11,8 +11,6 @@ class FrontlineAttackEvent(Event):
STRENGTH_INFLUENCE = 0.3
SUCCESS_FACTOR = 1.5
defenders = None # type: db.ArmorDict
@property
def threat_description(self):
return "{} vehicles".format(self.to_cp.base.assemble_count())
@ -20,9 +18,13 @@ class FrontlineAttackEvent(Event):
@property
def tasks(self) -> typing.Collection[typing.Type[Task]]:
if self.is_player_attacking:
return [CAS, PinpointStrike]
return [CAS, CAP]
else:
return [CAP, PinpointStrike]
return [CAP]
@property
def global_cp_available(self) -> bool:
return True
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAS:
@ -36,8 +38,8 @@ class FrontlineAttackEvent(Event):
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])
alive_attackers = sum([v for k, v in debriefing.alive_units.get(self.attacker_name, {}).items() if db.unit_task(k) == PinpointStrike])
alive_defenders = sum([v for k, v in debriefing.alive_units.get(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
@ -63,20 +65,46 @@ class FrontlineAttackEvent(Event):
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()
assert CAS in flights and CAP in flights and len(flights) == 2, "Invalid flights"
op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_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])
defenders = self.to_cp.base.assemble_attack()
max_attackers = int(math.ceil(sum(defenders.values()) * self.ATTACKER_DEFENDER_FACTOR))
attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), max_attackers)
op.setup(defenders=defenders,
attackers=attackers,
strikegroup=flights[CAS],
escort=flights[CAP],
interceptors=assigned_units_from(self.to_cp.base.scramble_interceptors(1)))
self.operation = op
def player_defending(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid flights"
op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
defenders = self.to_cp.base.assemble_attack()
max_attackers = int(math.ceil(sum(defenders.values())))
attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), max_attackers)
op.setup(defenders=defenders,
attackers=attackers,
strikegroup=assigned_units_from(self.from_cp.base.scramble_cas(1)),
escort=assigned_units_from(self.from_cp.base.scramble_sweep(1)),
interceptors=flights[CAP])
self.operation = op

View File

@ -17,7 +17,7 @@ class FrontlinePatrolEvent(Event):
@property
def tasks(self):
return [CAP, PinpointStrike]
return [CAP]
def flight_name(self, for_task: typing.Type[Task]) -> str:
if for_task == CAP:
@ -55,7 +55,7 @@ class FrontlinePatrolEvent(Event):
pass
def player_attacking(self, flights: db.TaskForceDict):
assert CAP in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights"
assert CAP in flights and len(flights) == 1, "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)
@ -64,13 +64,15 @@ class FrontlinePatrolEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
defenders = self.to_cp.base.assemble_attack()
attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values()))
op.setup(cas=assigned_units_from(self.cas),
escort=assigned_units_from(self.escort),
interceptors=flights[CAP],
armor_attackers=db.unitdict_restrict_count(db.unitdict_from(flights[PinpointStrike]), sum(defenders.values())),
armor_attackers=attackers,
armor_defenders=defenders)
self.operation = op

View File

@ -35,7 +35,7 @@ class InfantryTransportEvent(Event):
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def player_attacking(self, flights: db.TaskForceDict):
assert Embarking in flights and len(flights) == 1, "Invalid flights"
@ -45,6 +45,7 @@ class InfantryTransportEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp
)

View File

@ -57,6 +57,7 @@ class InsurgentAttackEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=flights[CAS])

View File

@ -10,6 +10,11 @@ class InterceptEvent(Event):
transport_unit = None # type: FlyingType
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str,
defender_name: str):
super().__init__(game, from_cp, target_cp, location, attacker_name, defender_name)
self.location = Conflict.intercept_position(self.from_cp, self.to_cp)
def __str__(self):
return "Air Intercept"
@ -25,15 +30,19 @@ class InterceptEvent(Event):
return "Escort flight"
def _enemy_scramble_multiplier(self) -> float:
is_global = self.from_cp.is_global or self.to_cp.is_global
is_global = self.departure_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._enemy_scramble_multiplier(), CAP))
@property
def global_cp_available(self) -> bool:
return True
def is_successfull(self, debriefing: Debriefing):
units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0)
units_destroyed = debriefing.destroyed_units.get(self.defender_name, {}).get(self.transport_unit, 0)
if self.from_cp.captured:
return units_destroyed > 0
else:
@ -72,9 +81,11 @@ class InterceptEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
op.setup(escort=assigned_units_from(escort),
op.setup(location=self.location,
escort=assigned_units_from(escort),
transport={self.transport_unit: 1},
airdefense={airdefense_unit: self.AIRDEFENSE_COUNT},
interceptors=flights[CAP])
@ -93,9 +104,11 @@ class InterceptEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
op.setup(escort=flights[CAP],
op.setup(location=self.location,
escort=flights[CAP],
transport={self.transport_unit: 1},
interceptors=assigned_units_from(interceptors),
airdefense={})

View File

@ -9,6 +9,11 @@ class NavalInterceptEvent(Event):
targets = None # type: db.ShipDict
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str,
defender_name: str):
super().__init__(game, from_cp, target_cp, location, attacker_name, defender_name)
self.location = Conflict.naval_intercept_position(from_cp, target_cp, game.theater)
def _targets_count(self) -> int:
from gen.conflictgen import IMPORTANCE_LOW
factor = (self.to_cp.importance - IMPORTANCE_LOW + 0.1) * 20
@ -33,18 +38,22 @@ class NavalInterceptEvent(Event):
@property
def threat_description(self):
s = "{} ship(s)".format(self._targets_count())
if not self.from_cp.captured:
s += ", {} aircraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier))
if not self.departure_cp.captured:
s += ", {} aircraft".format(self.departure_cp.base.scramble_count(self.game.settings.multiplier))
return s
@property
def global_cp_available(self) -> bool:
return True
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():
for unit, count in debriefing.destroyed_units.get(self.defender_name, {}).items():
if unit in self.targets:
destroyed_targets += count
if self.from_cp.captured:
if self.departure_cp.captured:
return math.ceil(float(destroyed_targets) / total_targets) > self.SUCCESS_RATE
else:
return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_RATE
@ -56,11 +65,11 @@ class NavalInterceptEvent(Event):
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
# enemy attacking
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
@ -80,10 +89,12 @@ class NavalInterceptEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp
)
op.setup(strikegroup=flights[CAS],
op.setup(location=self.location,
strikegroup=flights[CAS],
interceptors={},
targets=self.targets)
@ -101,10 +112,11 @@ class NavalInterceptEvent(Event):
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp
)
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
strikegroup = self.departure_cp.base.scramble_cas(self.game.settings.multiplier)
op.setup(strikegroup=assigned_units_from(strikegroup),
interceptors=flights[CAP],
targets=self.targets)

View File

@ -8,7 +8,7 @@ class StrikeEvent(Event):
SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.05
def __str__(self):
return "Strike"
return "Strike / SEAD"
def is_successfull(self, debriefing: Debriefing):
return True
@ -20,7 +20,7 @@ class StrikeEvent(Event):
@property
def tasks(self):
if self.is_player_attacking:
return [CAP, CAS]
return [CAP, CAS, SEAD]
else:
return [CAP]
@ -28,12 +28,22 @@ class StrikeEvent(Event):
def ai_banned_tasks(self):
return [CAS]
@property
def player_banned_tasks(self):
return [SEAD]
@property
def global_cp_available(self) -> bool:
return True
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 == SEAD:
return "SEAD flight"
elif for_task == CAS:
return "Strike flight"
@ -43,18 +53,20 @@ class StrikeEvent(Event):
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"
assert CAP in flights and CAS in flights and SEAD in flights and len(flights) == 3, "Invalid flights"
op = StrikeOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp
)
interceptors = self.to_cp.base.scramble_interceptors(self.game.settings.multiplier)
op.setup(strikegroup=flights[CAS],
sead=flights[SEAD],
escort=flights[CAP],
interceptors=assigned_units_from(interceptors))

View File

@ -43,19 +43,19 @@ Events:
* BaseAttackEvent - capture base
* InterceptEvent - air intercept
* FrontlineAttackEvent - frontline attack
* FrontlineCAPEvent - frontline attack
* NavalInterceptEvent - naval intercept
* StrikeEvent - strike event
* InfantryTransportEvent - helicopter infantry transport
"""
EVENT_PROBABILITIES = {
# events always present; only for the player
FrontlineAttackEvent: [100, 0],
FrontlinePatrolEvent: [100, 0],
FrontlineAttackEvent: [100, 9],
#FrontlinePatrolEvent: [100, 0],
StrikeEvent: [100, 0],
# events randomly present; only for the player
InfantryTransportEvent: [25, 0],
#InfantryTransportEvent: [25, 0],
ConvoyStrikeEvent: [25, 0],
# events conditionally present; for both enemy and player
BaseAttackEvent: [100, 9],
@ -100,28 +100,18 @@ class Game:
self.enemy = enemy_name
def _roll(self, prob, mult):
return random.randint(1, 100) <= prob * mult
def _generate_globalinterceptions(self):
global_count = len([x for x in self.theater.player_points() if x.is_global])
for from_cp in [x for x in self.theater.player_points() if x.is_global]:
probability_base = max(PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE / global_count, 1)
probability = probability_base * math.log(len(self.theater.player_points()) + 1, PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG)
if self._roll(probability, from_cp.base.strength):
to_cp = random.choice([x for x in self.theater.enemy_points() if x not in self.theater.conflicts()])
self.events.append(InterceptEvent(attacker_name=self.player,
defender_name=self.enemy,
from_cp=from_cp,
to_cp=to_cp,
game=self))
break
if self.settings.version == "dev":
# always generate all events for dev
return 100
else:
return random.randint(1, 100) <= prob * mult
def _generate_player_event(self, event_class, player_cp, enemy_cp):
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
# skip naval events for non-coastal CPs
return
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD and self.settings.version != "dev":
# skip base attack events for CPs yet too strong
return
@ -129,7 +119,7 @@ class Game:
# skip strikes in case of no targets
return
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player, self.enemy))
def _generate_enemy_event(self, event_class, player_cp, enemy_cp):
if event_class in [type(x) for x in self.events if not self.is_player_attack(x)]:
@ -169,28 +159,35 @@ class Game:
# skip base attack if strength is too high
return
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
self.events.append(event_class(self, enemy_cp, player_cp, player_cp.position, self.enemy, self.player))
def _generate_events(self):
for player_cp, enemy_cp in self.theater.conflicts(True):
if enemy_cp.is_global:
continue
strikes_generated_for = set()
base_attack_generated_for = set()
for player_cp, enemy_cp in self.theater.conflicts(True):
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]:
if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]:
# skip events requiring frontline
if not Conflict.has_frontline_between(player_cp, enemy_cp):
continue
if player_cp.is_global:
# skip events requiring ground CP
if event_class not in [InterceptEvent, StrikeEvent, NavalInterceptEvent]:
# don't generate multiple 100% events from each attack direction
if event_class is StrikeEvent:
if enemy_cp in strikes_generated_for:
continue
if event_class is BaseAttackEvent:
if enemy_cp in base_attack_generated_for:
continue
if player_probability == 100 or self._roll(player_probability, player_cp.base.strength):
if player_probability == 100 or player_probability > 0 and self._roll(player_probability, player_cp.base.strength):
self._generate_player_event(event_class, player_cp, enemy_cp)
if event_class is StrikeEvent:
strikes_generated_for.add(enemy_cp)
if event_class is BaseAttackEvent:
base_attack_generated_for.add(enemy_cp)
if enemy_probability == 100 or self._roll(enemy_probability, enemy_cp.base.strength):
if enemy_probability == 100 or enemy_probability > 0 and self._roll(enemy_probability, enemy_cp.base.strength):
self._generate_enemy_event(event_class, player_cp, enemy_cp)
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
@ -269,7 +266,12 @@ class Game:
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None):
logging.info("Pass turn")
for event in self.events:
event.skip()
if self.settings.version == "dev":
# don't damage player CPs in by skipping in dev mode
if isinstance(event, UnitsDeliveryEvent):
event.skip()
else:
event.skip()
if not no_action:
self._budget_player()
@ -286,5 +288,5 @@ class Game:
self.events = [] # type: typing.List[Event]
self._generate_events()
self._generate_globalinterceptions()
#self._generate_globalinterceptions()

View File

@ -0,0 +1,49 @@
from game.db import assigned_units_split
from .operation import *
class ConvoyStrikeOperation(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(ConvoyStrikeOperation, self).prepare(terrain, is_quick)
conflict = Conflict.convoy_strike_conflict(
attacker=self.current_mission.country(self.attacker_name),
defender=self.current_mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.current_mission,
conflict=conflict)
def generate(self):
if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.strikegroup))
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 + Heli flights", "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=len(planes_flights) == 0)
self.armorgen.generate_convoy(self.target)
self.briefinggen.append_waypoint("TARGET")
super(ConvoyStrikeOperation, self).generate()

View File

@ -7,16 +7,24 @@ MAX_DISTANCE_BETWEEN_GROUPS = 12000
class FrontlineAttackOperation(Operation):
interceptors = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
strikegroup = None # type: db.AssignedUnitsDict
attackers = None # type: db.ArmorDict
target = None # type: db.ArmorDict
defenders = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
defenders: db.ArmorDict,
attackers: db.ArmorDict,
strikegroup: db.AssignedUnitsDict):
strikegroup: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict):
self.strikegroup = strikegroup
self.target = target
self.escort = escort
self.interceptors = interceptors
self.defenders = defenders
self.attackers = attackers
def prepare(self, terrain: Terrain, is_quick: bool):
@ -37,8 +45,13 @@ class FrontlineAttackOperation(Operation):
conflict=conflict)
def generate(self):
self.armorgen.generate_vec(self.attackers, self.target)
if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.strikegroup))
# ground units
self.armorgen.generate_vec(self.attackers, self.defenders)
# strike group w/ heli support
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)
@ -51,6 +64,10 @@ class FrontlineAttackOperation(Operation):
at=farp,
escort=len(planes_flights) == 0)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
self.airgen.generate_defense(*assigned_units_split(self.interceptors), at=self.defenders_starting_position)
self.briefinggen.title = "Frontline CAS"
self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
self.briefinggen.append_waypoint("CAS AREA IP")

View File

@ -43,6 +43,9 @@ class FrontlinePatrolOperation(Operation):
conflict=conflict)
def generate(self):
if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.interceptors))
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)

View File

@ -4,6 +4,7 @@ from .operation import *
class InterceptOperation(Operation):
location = None # type: Point
escort = None # type: db.AssignedUnitsDict
transport = None # type: db.PlaneDict
interceptors = None # type: db.AssignedUnitsDict
@ -12,10 +13,12 @@ class InterceptOperation(Operation):
trigger_radius = TRIGGER_RADIUS_LARGE
def setup(self,
location: Point,
escort: db.AssignedUnitsDict,
transport: db.PlaneDict,
airdefense: db.AirDefenseDict,
interceptors: db.AssignedUnitsDict):
self.location = location
self.escort = escort
self.transport = transport
self.airdefense = airdefense
@ -30,6 +33,7 @@ class InterceptOperation(Operation):
conflict = Conflict.intercept_conflict(
attacker=self.current_mission.country(self.attacker_name),
defender=self.current_mission.country(self.defender_name),
position=self.location,
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
@ -39,7 +43,8 @@ class InterceptOperation(Operation):
conflict=conflict)
def generate(self):
self.prepare_carriers(db.unitdict_from(self.interceptors))
if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.interceptors))
self.airgen.generate_transport(self.transport, self.to_cp.at)
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)

View File

@ -4,15 +4,18 @@ from .operation import *
class NavalInterceptionOperation(Operation):
location = None # type: Point
strikegroup = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict
targets = None # type: db.ShipDict
trigger_radius = TRIGGER_RADIUS_LARGE
def setup(self,
location: Point,
strikegroup: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict,
targets: db.ShipDict):
self.location = location
self.strikegroup = strikegroup
self.interceptors = interceptors
self.targets = targets
@ -25,6 +28,7 @@ class NavalInterceptionOperation(Operation):
conflict = Conflict.naval_intercept_conflict(
attacker=self.current_mission.country(self.attacker_name),
defender=self.current_mission.country(self.defender_name),
position=self.location,
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
@ -33,7 +37,8 @@ class NavalInterceptionOperation(Operation):
self.initialize(self.current_mission, conflict)
def generate(self):
self.prepare_carriers(db.unitdict_from(self.strikegroup))
if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.strikegroup))
target_groups = self.shipgen.generate_cargo(units=self.targets)

View File

@ -26,6 +26,7 @@ class Operation:
envgen = None # type: EnvironmentGenerator
groundobjectgen = None # type: GroundObjectsGenerator
briefinggen = None # type: BriefingGenerator
forcedoptionsgen = None # type: ForcedOptionsGenerator
environment_settings = None
trigger_radius = TRIGGER_RADIUS_MEDIUM
@ -38,11 +39,13 @@ class Operation:
attacker_name: str,
defender_name: str,
from_cp: ControlPoint,
departure_cp: ControlPoint,
to_cp: ControlPoint = None):
self.game = game
self.attacker_name = attacker_name
self.defender_name = defender_name
self.from_cp = from_cp
self.departure_cp = departure_cp
self.to_cp = to_cp
self.is_quick = False
@ -52,6 +55,10 @@ class Operation:
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
@property
def is_player_attack(self) -> bool:
return self.from_cp.captured
def initialize(self, mission: Mission, conflict: Conflict):
self.current_mission = mission
self.conflict = conflict
@ -63,6 +70,7 @@ class Operation:
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
self.visualgen = VisualGenerator(mission, conflict, self.game)
self.envgen = EnviromentGenerator(mission, conflict, self.game)
self.forcedoptionsgen = ForcedOptionsGenerator(mission, conflict, self.game)
self.groundobjectgen = GroundObjectsGenerator(mission, conflict, self.game)
self.briefinggen = BriefingGenerator(mission, conflict, self.game)
@ -87,24 +95,24 @@ class Operation:
self.attackers_starting_position = None
self.defenders_starting_position = None
else:
self.attackers_starting_position = self.from_cp.at
self.attackers_starting_position = self.departure_cp.at
self.defenders_starting_position = self.to_cp.at
def prepare_carriers(self, for_units: db.UnitsDict):
for global_cp in self.game.theater.controlpoints:
if not global_cp.is_global:
continue
if not self.departure_cp.is_global:
return
ship = self.shipgen.generate_carrier(for_units=[t for t, c in for_units.items() if c > 0],
country=self.game.player,
at=global_cp.at)
ship = self.shipgen.generate_carrier(for_units=[t for t, c in for_units.items() if c > 0],
country=self.game.player,
at=self.departure_cp.at)
if global_cp == self.from_cp and not self.is_quick:
if not self.is_quick:
if not self.to_cp.captured:
self.attackers_starting_position = ship
else:
self.defenders_starting_position = ship
def generate(self):
self.visualgen.generate()
# air support
self.airsupportgen.generate(self.is_awacs_enabled)
for i, tanker_type in enumerate(self.airsupportgen.generated_tankers):
@ -141,10 +149,17 @@ class Operation:
else:
self.envgen.load(self.environment_settings)
# options
self.forcedoptionsgen.generate()
# main frequencies
self.briefinggen.append_frequency("Flight", "251 MHz AM")
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
if self.departure_cp.is_global or self.conflict.to_cp.is_global:
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
# briefing
self.briefinggen.generate()
# visuals
self.visualgen.generate()

View File

@ -5,6 +5,7 @@ from .operation import *
class StrikeOperation(Operation):
strikegroup = None # type: db.AssignedUnitsDict
sead = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
interceptors = None # type: db.AssignedUnitsDict
@ -12,9 +13,11 @@ class StrikeOperation(Operation):
def setup(self,
strikegroup: db.AssignedUnitsDict,
sead: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict):
self.strikegroup = strikegroup
self.sead = sead
self.escort = escort
self.interceptors = interceptors
@ -40,8 +43,10 @@ class StrikeOperation(Operation):
self.prepare_carriers(db.unitdict_merge(db.unitdict_from(self.strikegroup), db.unitdict_from(self.escort)))
targets = [] # type: typing.List[typing.Tuple[str, str, Point]]
sead_targets = [] # type: typing.List[typing.Tuple[str, 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
@ -49,6 +54,10 @@ class StrikeOperation(Operation):
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])
if object.category == "aa":
sead_targets.append((str(object), markpoint_name, object.position))
targets.append((str(object), markpoint_name, object.position))
targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[2]))
@ -59,7 +68,13 @@ class StrikeOperation(Operation):
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights),
targets=[(mp, pos) for (n, mp, pos) in targets],
at=self.attackers_starting_position)
at=self.attackers_starting_position,
escort=len(self.sead) == 0)
self.airgen.generate_sead_strikegroup(*assigned_units_split(self.sead),
targets=[(mp, pos) for (n, mp, pos) in sead_targets],
at=self.attackers_starting_position,
escort=len(self.sead) > 0)
heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
if heli_flights:

View File

@ -3,6 +3,8 @@ class Settings:
player_skill = "Good"
enemy_skill = "Average"
enemy_vehicle_skill = "Average"
map_coalition_visibility = "All Units"
labels = "Full"
only_player_takeoff = True
night_disabled = False

View File

@ -9,6 +9,7 @@ from .triggergen import *
from .environmentgen import *
from .groundobjectsgen import *
from .briefinggen import *
from .forcedoptionsgen import *
from . import naming

View File

@ -17,23 +17,23 @@ ESCORT_ENGAGEMENT_MAX_DIST = 100000
WORKAROUND_WAYP_DIST = 1000
WARM_START_HELI_AIRSPEED = 120
WARM_START_HELI_ALT = 1000
WARM_START_HELI_ALT = 500
WARM_START_ALTITUDE = 3000
WARM_START_AIRSPEED = 550
INTERCEPTION_ALT = 3000
INTERCEPTION_AIRSPEED = 1000
BARCAP_RACETRACK_DISTANCE = 20000
ATTACK_CIRCLE_ALT = 5000
ATTACK_CIRCLE_ALT = 1000
ATTACK_CIRCLE_DURATION = 15
CAS_ALTITUDE = 1000
RTB_ALTITUDE = 1000
HELI_ALT = 900
CAS_ALTITUDE = 800
RTB_ALTITUDE = 800
RTB_DISTANCE = 5000
HELI_ALT = 500
TRANSPORT_LANDING_ALT = 1000
TRANSPORT_LANDING_ALT = 2000
DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000
INTERCEPT_MAX_DISTANCE = 200000
@ -69,9 +69,15 @@ class AircraftConflictGenerator:
else:
client_count = 0
if flying_type == F_14B:
# workaround since 2 and 3 tomcat collide on carrier
group_size = 2
else:
group_size = 4
while count > 0:
group_size = min(count, 4)
client_size = max(min(client_count, 4), 0)
group_size = min(count, group_size)
client_size = max(min(client_count, group_size), 0)
yield (flying_type, group_size, client_size)
count -= group_size
@ -149,7 +155,7 @@ class AircraftConflictGenerator:
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(
group = self.m.flight_group(
country=side,
name=name,
aircraft_type=unit_type,
@ -161,6 +167,9 @@ class AircraftConflictGenerator:
start_type=self._start_type(),
group_size=count)
group.points[0].alt_type = "RADIO"
return group
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
@ -180,7 +189,9 @@ class AircraftConflictGenerator:
return self._generate_inflight(name, side, unit_type, count, client_count, at)
elif isinstance(at, Group):
takeoff_ban = unit_type in db.CARRIER_TAKEOFF_BAN
if not takeoff_ban:
ai_ban = client_count == 0 and self.settings.only_player_takeoff
if not takeoff_ban and not ai_ban:
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)
@ -197,17 +208,26 @@ class AircraftConflictGenerator:
else:
assert False
def _add_radio_waypoint(self, group: FlyingGroup, position, altitude: int, airspeed: int = 600):
point = group.add_waypoint(position, altitude, airspeed)
point.alt_type = "RADIO"
return point
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
if not at:
at = cp.at
position = at if isinstance(at, Point) else at.position
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)
last_waypoint = group.points[-1]
if last_waypoint is not None:
heading = position.heading_between_point(last_waypoint.position)
tod_location = position.point_from_heading(heading, RTB_DISTANCE)
self._add_radio_waypoint(group, tod_location, last_waypoint.alt)
destination_waypoint = self._add_radio_waypoint(group, position, RTB_ALTITUDE)
if isinstance(at, Airport):
group.land_at(at)
return destination_waypoint
def _at_position(self, at) -> Point:
if isinstance(at, Point):
@ -244,7 +264,7 @@ class AircraftConflictGenerator:
orbit_task = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
orbit_task.stop_after_duration(ATTACK_CIRCLE_DURATION * 60)
orbit_waypoint = group.add_waypoint(self.conflict.position, CAS_ALTITUDE)
orbit_waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE)
orbit_waypoint.tasks.append(orbit_task)
orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
@ -263,9 +283,9 @@ class AircraftConflictGenerator:
client_count=client_count,
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)
waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED)
self._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAS.name
self._setup_group(group, CAS, client_count)
@ -289,6 +309,7 @@ class AircraftConflictGenerator:
for name, pos in targets:
waypoint = group.add_waypoint(pos, 0, WARM_START_AIRSPEED, self.m.translation.create_string(name))
waypoint.tasks.append(Bombing(pos, attack_qty=2))
if escort_until_waypoint is None:
escort_until_waypoint = waypoint
@ -298,6 +319,32 @@ class AircraftConflictGenerator:
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_sead_strikegroup(self, strikegroup: db.PlaneDict, clients: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], at: db.StartingPosition, 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 = SEAD.name
self._setup_group(group, SEAD, 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
@ -312,11 +359,11 @@ class AircraftConflictGenerator:
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)
waypoint = self._add_radio_waypoint(group, 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)
self._add_radio_waypoint(group, 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)
@ -336,7 +383,7 @@ class AircraftConflictGenerator:
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
for target_group in target_groups:
wayp.tasks.append(AttackGroup(target_group.id))
@ -377,7 +424,7 @@ class AircraftConflictGenerator:
at=at and at or self._group_point(self.conflict.air_defenders_location))
group.task = CAP.name
wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
wayp.tasks.append(dcs.task.EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
wayp.tasks.append(dcs.task.OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
self._setup_group(group, CAP, client_count)
@ -393,9 +440,9 @@ class AircraftConflictGenerator:
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)
waypoint = self._add_radio_waypoint(group, 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)
self._add_radio_waypoint(group, self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAP.name
self._setup_group(group, CAP, client_count)
@ -411,14 +458,14 @@ class AircraftConflictGenerator:
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)
waypoint = self._add_radio_waypoint(group, 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)
self._add_radio_waypoint(group, 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 = self._add_radio_waypoint(group, 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
@ -437,9 +484,11 @@ class AircraftConflictGenerator:
client_count=client_count,
at=self._group_point(self.conflict.air_defenders_location))
waypoint = group.add_waypoint(destination.position.random_point_within(0, 0), TRANSPORT_LANDING_ALT)
waypoint = self._rtb_for(group, self.conflict.to_cp)
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._add_radio_waypoint(group, destination.position, RTB_ALTITUDE)
group.task = Transport.name
group.land_at(destination)
@ -456,11 +505,11 @@ class AircraftConflictGenerator:
group.task = CAP.name
group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
wayp = self._add_radio_waypoint(group, 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._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE)
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)
@ -476,9 +525,5 @@ class AircraftConflictGenerator:
at=at and at or self._group_point(self.conflict.air_attackers_location)
)
group.add_waypoint(
pos=self.conflict.position,
altitude=HELI_ALT,
)
self._add_radio_waypoint(group, self.conflict.position, HELI_ALT)
self._setup_group(group, Transport, client_count)

View File

@ -49,12 +49,14 @@ class AirSupportConflictGenerator:
)
tanker_group.points[0].tasks.append(ActivateBeaconCommand(channel=97 + i, unit_id=tanker_group.id, aa=False))
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
if is_awacs_enabled:
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]
self.mission.awacs_flight(
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player),
name=namegen.next_awacs_name(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,
@ -62,3 +64,6 @@ class AirSupportConflictGenerator:
frequency=133,
start_type=StartType.Warm,
)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))

View File

@ -36,7 +36,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, to: Point = None):
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None, move_formation: PointAction = PointAction.OffRoad):
for c in range(count):
logging.info("armorgen: {} for {}".format(unit, side.id))
group = self.m.vehicle_group(
@ -45,7 +45,7 @@ class ArmorConflictGenerator:
unit,
position=self._group_point(at),
group_size=1,
move_formation=PointAction.OffRoad)
move_formation=move_formation)
vehicle: Vehicle = group.units[0]
vehicle.player_can_drive = True
@ -53,7 +53,7 @@ class ArmorConflictGenerator:
if not to:
to = self.conflict.position.point_from_heading(0, 500)
wayp = group.add_waypoint(self._group_point(to))
wayp = group.add_waypoint(self._group_point(to), move_formation=move_formation)
wayp.tasks = []
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
@ -109,6 +109,16 @@ class ArmorConflictGenerator:
random.randint(0, self.conflict.distance))
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
def generate_convoy(self, units: db.ArmorDict):
for type, count in units.items():
self._generate_group(
side=self.conflict.defenders_side,
unit=type,
count=count,
at=self.conflict.ground_defenders_location,
to=self.conflict.position,
move_formation=PointAction.OnRoad)
def generate_passengers(self, count: int):
unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name))

View File

@ -28,7 +28,7 @@ CAP_CAS_DISTANCE = 10000, 120000
GROUND_INTERCEPT_SPREAD = 5000
GROUND_DISTANCE_FACTOR = 1
GROUND_DISTANCE = 4000
GROUND_DISTANCE = 2000
GROUND_ATTACK_DISTANCE = 25000, 13000
@ -162,6 +162,8 @@ class Conflict:
strength_delta = (from_cp.base.strength - to_cp.base.strength) / 1.0
position = middle_point.point_from_heading(attack_heading, strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
return position, _opposite_heading(attack_heading)
ground_position = cls._find_ground_position(position, attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE, attack_heading, theater)
if ground_position:
return ground_position, _opposite_heading(attack_heading)
@ -172,6 +174,23 @@ class Conflict:
@classmethod
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Optional[typing.Tuple[Point, int, int]]:
initial, heading = cls.frontline_position(theater, from_cp, to_cp)
"""
probe_end_point = initial.point_from_heading(heading, FRONTLINE_LENGTH)
probe = geometry.LineString([(initial.x, initial.y), (probe_end_point.x, probe_end_point.y) ])
intersection = probe.intersection(theater.land_poly)
if isinstance(intersection, geometry.LineString):
intersection = intersection
elif isinstance(intersection, geometry.MultiLineString):
intersection = intersection.geoms[0]
else:
print(intersection)
return None
return Point(*intersection.xy[0]), _heading_sum(heading, 90), intersection.length
"""
frontline = cls.frontline_position(theater, from_cp, to_cp)
if not frontline:
return None
@ -207,9 +226,21 @@ class Conflict:
pos = new_pos
else:
return pos
return pos
"""
probe_end_point = initial.point_from_heading(heading, max_distance)
probe = geometry.LineString([(initial.x, initial.y), (probe_end_point.x, probe_end_point.y)])
intersection = probe.intersection(theater.land_poly)
if intersection is geometry.LineString:
return Point(*intersection.xy[1])
elif intersection is geometry.MultiLineString:
return Point(*intersection.geoms[0].xy[1])
return None
"""
@classmethod
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> typing.Optional[Point]:
pos = initial
@ -218,9 +249,19 @@ class Conflict:
return pos
pos = pos.point_from_heading(heading, 500)
"""
probe_end_point = initial.point_from_heading(heading, max_distance)
probe = geometry.LineString([(initial.x, initial.y), (probe_end_point.x, probe_end_point.y) ])
logging.info("Didn't find ground position!")
return None
intersection = probe.intersection(theater.land_poly)
if isinstance(intersection, geometry.LineString):
return Point(*intersection.xy[1])
elif isinstance(intersection, geometry.MultiLineString):
return Point(*intersection.geoms[0].xy[1])
"""
logging.error("Didn't find ground position ({})!".format(initial))
return initial
@classmethod
def capture_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
@ -277,13 +318,15 @@ class Conflict:
)
@classmethod
def intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
def intercept_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> Point:
raw_distance = from_cp.position.distance_to_point(to_cp.position) * 1.5
distance = max(min(raw_distance, INTERCEPT_MAX_DISTANCE), INTERCEPT_MIN_DISTANCE)
heading = _heading_sum(from_cp.position.heading_between_point(to_cp.position), random.choice([-1, 1]) * random.randint(60, 100))
position = from_cp.position.point_from_heading(heading, distance)
return from_cp.position.point_from_heading(heading, distance)
@classmethod
def intercept_conflict(cls, attacker: Country, defender: Country, position: Point, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
heading = from_cp.position.heading_between_point(position)
return cls(
position=position.point_from_heading(position.heading_between_point(to_cp.position), INTERCEPT_CONFLICT_DISTANCE),
theater=theater,
@ -319,6 +362,35 @@ class Conflict:
air_defenders_location=position.point_from_heading(heading, AIR_DISTANCE),
)
@classmethod
def convoy_strike_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
frontline_position, frontline_heading, frontline_length = Conflict.frontline_vector(from_cp, to_cp, theater)
if not frontline_position:
assert False
heading = frontline_heading
starting_position = Conflict._find_ground_position(frontline_position.point_from_heading(heading, 7000),
GROUND_INTERCEPT_SPREAD,
_opposite_heading(heading), theater)
if not starting_position:
starting_position = frontline_position
destination_position = frontline_position
else:
destination_position = frontline_position
return cls(
position=destination_position,
theater=theater,
from_cp=from_cp,
to_cp=to_cp,
attackers_side=attacker,
defenders_side=defender,
ground_attackers_location=None,
ground_defenders_location=starting_position,
air_attackers_location=starting_position.point_from_heading(_opposite_heading(heading), AIR_DISTANCE),
air_defenders_location=starting_position.point_from_heading(heading, AIR_DISTANCE),
)
@classmethod
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)
@ -385,7 +457,7 @@ class Conflict:
)
@classmethod
def naval_intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
def naval_intercept_position(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
radial = random.choice(to_cp.sea_radials)
initial_distance = min(int(from_cp.position.distance_to_point(to_cp.position) * NAVAL_INTERCEPT_DISTANCE_FACTOR), NAVAL_INTERCEPT_DISTANCE_MAX)
@ -395,7 +467,10 @@ class Conflict:
if not theater.is_on_land(position):
break
return position
@classmethod
def naval_intercept_conflict(cls, attacker: Country, defender: Country, position: Point, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
attacker_heading = from_cp.position.heading_between_point(to_cp.position)
return cls(
position=position,

View File

@ -26,9 +26,9 @@ WEATHER_FOG_VISIBILITY = 2500, 5000
WEATHER_FOG_THICKNESS = 100, 500
RANDOM_TIME = {
"night": 5,
"dusk": 30,
"dawn": 30,
"night": 7,
"dusk": 40,
"dawn": 40,
"day": 100,
}

45
gen/forcedoptionsgen.py Normal file
View File

@ -0,0 +1,45 @@
import logging
import typing
from enum import IntEnum
from dcs.mission import Mission
from dcs.forcedoptions import ForcedOptions
from .conflictgen import *
class Labels(IntEnum):
Off = 0
Full = 1
Abbreviated = 2
Dot = 3
class ForcedOptionsGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game):
self.mission = mission
self.conflict = conflict
self.game = game
def _set_options_view(self):
if self.game.settings.map_coalition_visibility == "All Units":
self.mission.forced_options.options_view = ForcedOptions.Views.All
elif self.game.settings.map_coalition_visibility == "Allied Units":
self.mission.forced_options.options_view = ForcedOptions.Views.Allies
elif self.game.settings.map_coalition_visibility == "Own Aircraft":
self.mission.forced_options.options_view = ForcedOptions.Views.MyAircraft
elif self.game.settings.map_coalition_visibility == "None":
self.mission.forced_options.options_view = ForcedOptions.Views.OnlyMap
def _set_labels(self):
if self.game.settings.labels == "Abbreviated":
self.mission.forced_options.labels = int(Labels.Abbreviated)
elif self.game.settings.labels == "Dot Only":
self.mission.forced_options.labels = int(Labels.Dot)
elif self.game.settings.labels == "Off":
self.mission.forced_options.labels = int(Labels.Off)
def generate(self):
self._set_options_view()
self._set_labels()

View File

@ -35,17 +35,19 @@ class ShipGenerator:
def generate_cargo(self, units: db.ShipDict) -> typing.Collection[ShipGroup]:
groups = []
offset = 0
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),
_type=unit_type,
position=self.conflict.ground_defenders_location.random_point_within(SHIP_RANDOM_SPREAD, SHIP_RANDOM_SPREAD),
group_size=unit_count,
)
for _ in range(unit_count):
offset += 1
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),
_type=unit_type,
position=self.conflict.ground_defenders_location.random_point_within(SHIP_RANDOM_SPREAD, SHIP_RANDOM_SPREAD).point_from_heading(0, offset * SHIP_RANDOM_SPREAD)
)
group.add_waypoint(self.conflict.to_cp.position)
groups.append(group)
group.add_waypoint(self.conflict.to_cp.position)
groups.append(group)
return groups

View File

@ -54,14 +54,12 @@ class TriggersGenerator:
vehicle_group.late_activation = True
activate_by_trigger.append(vehicle_group)
"""
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
result_radius = min(minimum_radius, radius)
"""
radius = min(minimum_radius, radius)
activation_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.position, radius, name="Activation zone")
activation_trigger = TriggerOnce(Event.NoEvent, "Activation trigger")

View File

@ -9,6 +9,7 @@ from dcs.unit import Static
from theater import *
from .conflictgen import *
#from game.game import Game
from game import db
class MarkerSmoke(unittype.StaticType):
@ -124,6 +125,17 @@ class VisualGenerator:
position=pos)
break
def _generate_stub_planes(self):
mission_units = set()
for coalition_name, coalition in self.mission.coalition.items():
for country in coalition.countries.values():
for group in country.plane_group + country.helicopter_group + country.vehicle_group:
for unit in group.units:
mission_units.add(db.unit_type_of(unit))
for unit_type in mission_units:
self.mission.static_group(self.mission.country("USA"), "a", unit_type, Point(0, 300000), hidden=True)
def generate_target_smokes(self, target):
spread = target.size * DESTINATION_SMOKE_DISTANCE_FACTOR
for _ in range(0, int(target.size * DESTINATION_SMOKE_AMOUNT_FACTOR * (1.1 - target.base.strength))):
@ -159,3 +171,4 @@ class VisualGenerator:
def generate(self):
self._generate_frontline_smokes()
self._generate_stub_planes()

40
pyinstaller.spec Normal file
View File

@ -0,0 +1,40 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['__init__.py'],
pathex=['C:\\Users\\shdwp\\PycharmProjects\\dcs_liberation'],
binaries=[],
datas=[
('resources', 'resources'),
('submodules/dcs/dcs/terrain/caucasus.p', 'dcs/terrain/'),
('submodules/dcs/dcs/terrain/nevada.p', 'dcs/terrain/'),
],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
icon="resources/icon.ico",
exclude_binaries=True,
name='liberation_main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='dcs_liberation')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

@ -1,4 +1,5 @@
import os
import sys
import dcs
from game import db

View File

@ -1,4 +1,5 @@
import os
import shutil
from zipfile import *
@ -42,11 +43,13 @@ def _mk_archieve():
print("version already exists")
return
shutil.rmtree("./dist")
os.system("pyinstaller.exe pyinstaller.spec")
archieve = ZipFile(path, "w")
archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\\Saved Games\" \"{}\"".format(VERSION))
_zip_dir(archieve, ".")
os.chdir("submodules\\dcs")
_zip_dir(archieve, "dcs")
archieve.writestr("dcs_liberation.bat", "cd dist\\dcs_liberation\r\nliberation_main \"%UserProfile%\\Saved Games\" \"{}\"".format(VERSION))
_zip_dir(archieve, "./dist/dcs_liberation")
_mk_archieve()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

@ -1 +1 @@
Subproject commit 54eab60f228847f2d92e344e6885eca855354f41
Subproject commit 4fbb7ad3e0e2eecedc4e1dd14f2eb18025fef9f5

0
tests/__init__.py Normal file
View File

View File

@ -0,0 +1,10 @@
from tests.integration import baseattack, convoystrike, frontlineattack, insurgentattack, intercept, navalintercept, strike
if __name__ == "__main__":
baseattack.execute_all()
convoystrike.execute_all()
frontlineattack.execute_all()
insurgentattack.execute_all()
intercept.execute_all()
navalintercept.execute_all()
strike.execute_all()

View File

@ -0,0 +1,46 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = BaseAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
print("{} for {} ({}) - {}".format(e, player_cp, departure_cp, enemy_cp))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,50 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = ConvoyStrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
enemy_cp.base.strength = 1
for _ in range(10):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
enemy_cp.base.affect_strength(-0.1)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,52 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from game.event.frontlineattack import FrontlineAttackEvent
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = FrontlineAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
enemy_cp.base.strength = 1
for _ in range(10):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
enemy_cp.base.affect_strength(-0.1)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,48 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from game.event.insurgentattack import InsurgentAttackEvent
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = InsurgentAttackEvent(game, enemy_cp, player_cp, player_cp.position, ENEMY_COUNTRY, PLAYER_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_defending(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,48 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from game.event.intercept import InterceptEvent
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = InterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,49 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from game.event.intercept import InterceptEvent
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = NavalInterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
if enemy_cp.radials != LAND:
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

View File

@ -0,0 +1,48 @@
from theater.caucasus import CaucasusTheater
from theater.nevada import NevadaTheater
from game.event.intercept import InterceptEvent
from tests.integration.util import *
PLAYER_COUNTRY = "USA"
ENEMY_COUNTRY = "Russia"
def execute(game, player_cp, enemy_cp, departure_cp = None):
e = StrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY)
departures = [departure_cp] if departure_cp else game.theater.player_points()
for departure_cp in departures:
if e.is_departure_available_from(departure_cp):
print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength))
e.departure_cp = departure_cp
e.player_attacking(autoflights_for(e, PLAYER_COUNTRY))
e.generate()
execute_autocommit(e)
e.generate_quick()
execute_autocommit(e)
def execute_theater(theater_klass):
print("Theater: {}".format(theater_klass))
game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass)
total_events = 0
while len(theater.enemy_points()) > 0:
for player_cp, enemy_cp in theater.conflicts():
execute(game, player_cp, enemy_cp)
enemy_cp.captured = True
print("Total: {}".format(total_events))
def execute_all():
for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]:
execute_theater(theater_klass)
if __name__ == "__main__":
execute_all()

80
tests/integration/util.py Normal file
View File

@ -0,0 +1,80 @@
from dcs.mission import Mission
from game import *
from game.event import *
from game.db import *
from theater.persiangulf import *
from theater import start_generator
PLAYER_COUNTRY = None
ENEMY_COUNTRY = None
def init(player_country: str, enemy_country: str, theater_klass: typing.Type[ConflictTheater]) -> typing.Tuple[Game, ConflictTheater]:
global PLAYER_COUNTRY
global ENEMY_COUNTRY
PLAYER_COUNTRY = player_country
ENEMY_COUNTRY = enemy_country
# prerequisites
persistency.setup("./tests/userfolder/")
theater = theater_klass()
start_generator.generate_inital_units(theater, ENEMY_COUNTRY, True, 1)
start_generator.generate_groundobjects(theater)
return Game(PLAYER_COUNTRY, ENEMY_COUNTRY, theater), theater
def autoflights_for(event: Event, country: str) -> TaskForceDict:
result = {}
for task in event.tasks:
result[task] = {find_unittype(task, country)[0]: (1, 1)}
return result
class AutodebriefType(Enum):
EVERYONE_DEAD = 0
PLAYER_DEAD = 1
ENEMY_DEAD = 2
def autodebrief_for(event: Event, type: AutodebriefType) -> Debriefing:
mission = event.operation.current_mission # type: Mission
countries = []
if type == AutodebriefType.PLAYER_DEAD or type == AutodebriefType.EVERYONE_DEAD:
countries.append(mission.country(PLAYER_COUNTRY))
if type == AutodebriefType.ENEMY_DEAD or type == AutodebriefType.EVERYONE_DEAD:
countries.append(mission.country(ENEMY_COUNTRY))
dead_units = []
for country in countries:
for group in country.plane_group + country.vehicle_group + country.helicopter_group:
for unit in group.units:
dead_units.append(str(unit.name))
return Debriefing(dead_units, [])
def event_state_save(e: Event) -> typing.Tuple[Base, Base]:
return (copy.deepcopy(e.from_cp.base), copy.deepcopy(e.to_cp.base))
def event_state_restore(e: Event, state: typing.Tuple[Base, Base]):
e.from_cp.base, e.to_cp.base = state[0], state[1]
def execute_autocommit(e: Event):
state = event_state_save(e)
e.commit(autodebrief_for(e, AutodebriefType.EVERYONE_DEAD))
event_state_restore(e, state)
state = event_state_save(e)
e.commit(autodebrief_for(e, AutodebriefType.PLAYER_DEAD))
event_state_restore(e, state)
state = event_state_save(e)
e.commit(autodebrief_for(e, AutodebriefType.ENEMY_DEAD))
event_state_restore(e, state)

View File

View File

@ -56,7 +56,7 @@ class Base:
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
if count <= 0:
logging.info("{}: no units for {}".format(self, for_type))
logging.warning("{}: 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]]

View File

@ -11,8 +11,8 @@ from .base import *
class CaucasusTheater(ConflictTheater):
terrain = caucasus.Caucasus()
overview_image = "caumap.gif"
reference_points = {(-317948.32727306, 635639.37385346): (278.5, 319),
(-355692.3067714, 617269.96285781): (263, 352), }
reference_points = {(-317948.32727306, 635639.37385346): (278.5*2, 319*2),
(-355692.3067714, 617269.96285781): (263*2, 352*2), }
landmap = load_landmap("resources\\caulandmap.p")
daytime_map = {
"dawn": (6, 9),

View File

@ -18,8 +18,6 @@ IMPORTANCE_LOW = 1
IMPORTANCE_MEDIUM = 1.2
IMPORTANCE_HIGH = 1.4
GLOBAL_CP_CONFLICT_DISTANCE_MIN = 340000
"""
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
COAST_NS_E = [45, 90, 135, ]
@ -55,10 +53,18 @@ class ConflictTheater:
reference_points = None # type: typing.Dict
overview_image = None # type: str
landmap = None # type: landmap.Landmap
"""
land_poly = None # type: Polygon
"""
daytime_map = None # type: typing.Dict[str, typing.Tuple[int, int]]
def __init__(self):
self.controlpoints = []
"""
self.land_poly = geometry.Polygon(self.landmap[0][0])
for x in self.landmap[1]:
self.land_poly = self.land_poly.difference(geometry.Polygon(x))
"""
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
for connected_point in connected_to:
@ -102,9 +108,5 @@ class ConflictTheater:
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
yield (cp, connected_point)
for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]:
if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN:
yield (global_cp, connected_point)
def enemy_points(self) -> typing.Collection[ControlPoint]:
return [point for point in self.controlpoints if not point.captured]

View File

@ -9,8 +9,8 @@ from .base import *
class NevadaTheater(ConflictTheater):
terrain = dcs.terrain.Nevada()
overview_image = "nevada.gif"
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45, -360),
(nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440, 80), }
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45*2, -360*2),
(nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440*2, 80*2), }
landmap = load_landmap("resources\\nev_landmap.p")
daytime_map = {
"dawn": (4, 6),
@ -19,7 +19,6 @@ class NevadaTheater(ConflictTheater):
"night": (0, 5),
}
mina = ControlPoint.from_airport(nevada.Mina_Airport_3Q0, LAND, SIZE_SMALL, IMPORTANCE_LOW)
tonopah = ControlPoint.from_airport(nevada.Tonopah_Airport, LAND, SIZE_SMALL, IMPORTANCE_LOW)
tonopah_test_range = ControlPoint.from_airport(nevada.Tonopah_Test_Range_Airfield, LAND, SIZE_SMALL, IMPORTANCE_LOW)
lincoln_conty = ControlPoint.from_airport(nevada.Lincoln_County, LAND, SIZE_SMALL, 1.2)
@ -37,8 +36,7 @@ class NevadaTheater(ConflictTheater):
def __init__(self):
super(NevadaTheater, self).__init__()
self.add_controlpoint(self.mina, connected_to=[self.tonopah])
self.add_controlpoint(self.tonopah, connected_to=[self.mina, self.tonopah_test_range, self.lincoln_conty])
self.add_controlpoint(self.tonopah, connected_to=[self.tonopah_test_range, self.lincoln_conty])
self.add_controlpoint(self.tonopah_test_range, connected_to=[self.tonopah, self.lincoln_conty, self.groom_lake, self.pahute_mesa])
self.add_controlpoint(self.lincoln_conty, connected_to=[self.tonopah_test_range, self.mesquite])
@ -52,5 +50,5 @@ class NevadaTheater(ConflictTheater):
self.add_controlpoint(self.jean, connected_to=[self.laughlin, self.las_vegas])
self.add_controlpoint(self.laughlin, connected_to=[self.jean, self.las_vegas])
self.mina.captured = True
self.tonopah.captured = True

View File

@ -9,8 +9,8 @@ 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): (321, 145),
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), }
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321*4, 145*4),
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347*4, 82*4), }
landmap = load_landmap("resources\\gulflandmap.p")
daytime_map = {
"dawn": (6, 8),

View File

@ -72,10 +72,20 @@ def generate_groundobjects(theater: ConflictTheater):
return None
group_id = 0
for cp in theater.enemy_points():
for _ in range(0, random.randrange(2, 4)):
available_categories = list(tpls) + ["aa", "aa"]
tpl_category = random.choice(available_categories)
for cp in theater.controlpoints:
if cp.is_global:
continue
if not cp.has_frontline:
continue
amount = random.randrange(5, 7)
for i in range(0, amount):
available_categories = list(tpls)
if i >= amount - 1:
tpl_category = "aa"
else:
tpl_category = random.choice(available_categories)
tpl = random.choice(list(tpls[tpl_category].values()))
@ -85,13 +95,6 @@ def generate_groundobjects(theater: ConflictTheater):
print("Couldn't find point for {}".format(cp))
continue
"""
dist = point.distance_to_point(cp.position) - 15000
for another_cp in theater.enemy_points():
if another_cp.position.distance_to_point(point) < dist:
cp = another_cp
"""
group_id += 1
object_id = 0

View File

@ -21,6 +21,12 @@ class ConfigurationMenu(Menu):
self.enemy_vehicle_var = StringVar()
self.enemy_vehicle_var.set(self.game.settings.enemy_vehicle_skill)
self.map_coalition_visibility_var = StringVar()
self.map_coalition_visibility_var.set(self.game.settings.map_coalition_visibility)
self.labels_var = StringVar()
self.labels_var.set(self.game.settings.labels)
self.takeoff_var = BooleanVar()
self.takeoff_var.set(self.game.settings.only_player_takeoff)
@ -34,6 +40,8 @@ class ConfigurationMenu(Menu):
self.game.settings.player_skill = self.player_skill_var.get()
self.game.settings.enemy_skill = self.enemy_skill_var.get()
self.game.settings.enemy_vehicle_skill = self.enemy_vehicle_var.get()
self.game.settings.map_coalition_visibility = self.map_coalition_visibility_var.get()
self.game.settings.labels = self.labels_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()
@ -72,6 +80,18 @@ class ConfigurationMenu(Menu):
e_skill.configure(**STYLES["btn-primary"])
row += 1
Label(body, text="F10 Map Coalition Visibility", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
map_vis = OptionMenu(body, self.map_coalition_visibility_var, "All Units", "Allied Units", "Own Aircraft", "None")
map_vis.grid(row=row, column=1, sticky=E)
map_vis.configure(**STYLES["btn-primary"])
row += 1
Label(body, text="In Game Labels", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
g_labels = OptionMenu(body, self.labels_var, "Full", "Abbreviated", "Dot Only", "Off")
g_labels.grid(row=row, column=1, sticky=E)
g_labels.configure(**STYLES["btn-primary"])
row += 1
Label(body, text="Aircraft cold start", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
Checkbutton(body, variable=self.cold_start_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
row += 1
@ -84,9 +104,6 @@ class ConfigurationMenu(Menu):
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
row += 1
Button(body, text="Display logs", command=self.display_logs, **STYLES["btn-primary"]).grid(row=row, column=1, sticky=E, pady=30)
row += 1
Label(body, text="Contributors: ", **STYLES["strong"]).grid(row=row, column=0, columnspan=2, sticky=EW)
row += 1
@ -98,7 +115,8 @@ class ConfigurationMenu(Menu):
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=row, column=1, sticky=E)
row += 1
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=row, column=1, pady=30)
Button(body, text="Display logs", command=self.display_logs, **STYLES["btn-primary"]).grid(row=row, column=0, pady=5)
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=row, column=1)
def display_logs(self):
raise ShowLogsException()

View File

@ -20,7 +20,7 @@ class EventMenu(Menu):
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
self.base = self.event.departure_cp.base
else:
self.base = self.event.to_cp.base
@ -194,15 +194,24 @@ class EventMenu(Menu):
self.error_label["text"] = "Need at least one player in flight {}".format(self.event.flight_name(task))
return
if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent):
if tasks_scramble_counts.get(PinpointStrike, 0) == 0:
self.error_label["text"] = "No ground vehicles assigned to attack!"
for task in self.event.player_banned_tasks:
if tasks_clients_counts.get(task, 0) != 0:
self.error_label["text"] = "Players are not allowed on flight {}".format(self.event.flight_name(task))
return
if self.game.is_player_attack(self.event):
if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent):
if self.event.from_cp.base.total_armor == 0:
self.error_label["text"] = "No ground vehicles available to attack!"
return
self.event.player_attacking(flights)
else:
if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent):
if self.event.to_cp.base.total_armor == 0:
self.error_label["text"] = "No ground vehicles available to defend!"
return
self.event.player_defending(flights)
self.game.initiate_event(self.event)

View File

@ -25,85 +25,11 @@ class MainMenu(Menu):
def display(self):
persistency.save_game(self.game)
self.window.clear_right_pane()
self.upd.update()
# Header :
header = Frame(self.frame, **STYLES["header"])
Button(header, text="Configuration", command=self.configuration_menu, **STYLES["btn-primary"]).grid(column=0, row=0, sticky=NW)
Label(header, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["strong"]).grid(column=1, row=0, sticky=N+EW, padx=50)
Button(header, text="Pass turn", command=self.pass_turn, **STYLES["btn-primary"]).grid(column=2, row=0, sticky=NE)
header.grid(column=0, row=0, sticky=N+EW)
content = Frame(self.frame, **STYLES["body"])
content.grid(column=0, row=1, sticky=NSEW)
column = 0
row = 0
def label(text):
nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=N+EW, columnspan=2)
Label(frame, text=text, **STYLES["widget"]).grid(row=row, sticky=NS)
row += 1
def event_button(event):
nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=N+EW)
Message(frame, text="{}".format(
event
), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=N+EW)
Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E)
row += 1
def departure_header(text, style="strong"):
nonlocal row, body
Label(body, text=text, **STYLES[style]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(0, 5))
row += 1
def destination_header(text):
nonlocal row, body
Label(body, text=text, **STYLES["substrong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW)
row += 1
events = self.game.events
events.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 1 or (self.game.is_player_attack(x) and 2 or 0))
destination = None
departure = None
for event in events:
if event.informational:
new_departure = "Deliveries"
elif not self.game.is_player_attack(event):
new_departure = "Enemy attack"
else:
new_departure = event.from_cp.name
if new_departure != departure:
body = Frame(content, **STYLES["body"])
body.grid(column=column, row=1, sticky=N+EW)
row = 0
column += 1
departure = new_departure
departure_header(new_departure, style="strong" if self.game.is_player_attack(event) else "supstrong")
destination = None
if not event.informational:
new_destination = "At {}".format(event.to_cp.name)
if destination != new_destination:
destination_header(new_destination)
destination = new_destination
if event.informational:
label(str(event))
else:
event_button(event)
header.grid(column=0, row=0, sticky=NSEW)
def pass_turn(self):
self.game.pass_turn(no_action=True)
@ -113,7 +39,7 @@ class MainMenu(Menu):
ConfigurationMenu(self.window, self, self.game).display()
def start_event(self, event) -> typing.Callable:
return lambda: EventMenu(self.window, self, self.game, event).display()
EventMenu(self.window, self, self.game, event).display()
def go_cp(self, cp: ControlPoint):
if not cp.captured:

View File

@ -1,27 +1,536 @@
import os
from tkinter import *
import platform
from threading import Thread
from tkinter.ttk import *
import pygame
from theater.theatergroundobject import CATEGORY_MAP
from ui.styles import STYLES
from ui.window import *
from game.game import *
from gen.conflictgen import Conflict
from theater.conflicttheater import *
EVENT_COLOR_ATTACK = (100, 100, 255)
EVENT_COLOR_DEFENSE = (255, 100, 100)
RED = (255, 125, 125)
BRIGHT_RED = (200, 64, 64)
BLUE = (164, 164, 255)
DARK_BLUE = (45, 62, 80)
WHITE = (255, 255, 255)
GREEN = (128, 186, 128)
BRIGHT_GREEN = (64, 200, 64)
BLACK = (0, 0, 0)
BACKGROUND = pygame.Color(0, 64, 64)
ANTIALIASING = True
WIDTH = 800
HEIGHT = 600
MAP_PADDING = 100
class OverviewCanvas:
mainmenu = None # type: ui.mainmenu.MainMenu
budget_label = None # type: Label
started = None
ground_assets_icons = None # type: typing.Dict[str, pygame.Surface]
event_icons = None # type: typing.Dict[typing.Type, pygame.Surface]
selected_event_info = None # type: typing.Tuple[Event, typing.Tuple[int, int]]
frontline_vector_cache = None # type: typing.Dict[str, typing.Tuple[Point, int, int]]
def __init__(self, frame: Frame, parent, game: Game):
self.parent = parent
self.game = game
self.image = PhotoImage(file=os.path.join("resources", game.theater.overview_image))
self.canvas = Canvas(frame, width=self.image.width(), height=self.image.height())
self.canvas.grid(column=0, row=0, sticky=NSEW)
# Remove any previously existing pygame instance
pygame.quit()
def transform_point(self, p: Point, treshold=30) -> (int, int):
# Pygame objects
self.map = None
self.screen = None
self.surface: pygame.Surface = None
self.thread: Thread = None
self.clock = pygame.time.Clock()
self.expanded = True
pygame.font.init()
self.font: pygame.font.SysFont = pygame.font.SysFont("arial", 15)
self.fontsmall: pygame.font.SysFont = pygame.font.SysFont("arial", 10)
self.ground_assets_icons = {}
# Frontline are too heavy on performance to compute in realtime, so keep them in a cache
self.frontline_vector_cache = {}
# Map state
self.redraw_required = True
self.zoom = 1
self.scroll = [0, 0]
self.exited = False
# Display options
self.display_ground_targets = BooleanVar(value=True)
self.display_forces = BooleanVar(value=True)
self.display_bases = BooleanVar(value=True)
self.display_road = BooleanVar(value=True)
self.display_rules = self.compute_display_rules()
parent.window.tk.protocol("<WM_DELETE_WINDOW>", self.on_close)
self.wrapper = Frame(frame, **STYLES["frame-wrapper"])
self.wrapper.grid(column=0, row=0, sticky=NSEW) # Adds grid
self.wrapper.pack(side=LEFT) # packs window to the left
self.embed = Frame(self.wrapper, width=WIDTH, height=HEIGHT, borderwidth=2, **STYLES["frame-wrapper"])
self.embed.grid(column=0, row=1, sticky=NSEW) # Adds grid
self.options = Frame(self.wrapper, borderwidth=2, **STYLES["frame-wrapper"])
self.options.grid(column=0, row=0, sticky=NSEW)
self.options.grid_columnconfigure(1, weight=1)
self.build_map_options_panel()
self.init_sdl_layer()
self.init_sdl_thread()
def build_map_options_panel(self):
col = 0
Button(self.options, text="Configuration", command=self.parent.configuration_menu, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NE)
col += 1
self.budget_label = Label(self.options, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["widget"])
self.budget_label.grid(column=col, row=0, sticky=N+EW)
col += 1
Button(self.options, text="Pass turn", command=self.parent.pass_turn, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NW)
col += 1
def map_size_toggle(self):
if self.expanded:
self.embed.configure(width=0)
self.options.configure(width=0)
self.expanded = False
else:
self.embed.configure(width=WIDTH)
self.options.configure(width=WIDTH)
self.expanded = True
def on_close(self):
self.exited = True
if self.thread is not None:
self.thread.join()
def init_sdl_layer(self):
# Setup pygame to run in tk frame
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if platform.system == "Windows":
os.environ['SDL_VIDEODRIVER'] = 'windib'
# Create pygame 'screen'
self.screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE)
self.screen.fill(pygame.Color(*BLACK))
# Load icons resources
self.ground_assets_icons = {}
self.ground_assets_icons["target"] = pygame.image.load(os.path.join("resources", "ui", "ground_assets", "target.png"))
self.ground_assets_icons["cleared"] = pygame.image.load(os.path.join("resources", "ui", "ground_assets", "cleared.png"))
for category in CATEGORY_MAP.keys():
self.ground_assets_icons[category] = pygame.image.load(os.path.join("resources", "ui", "ground_assets", category + ".png"))
self.event_icons = {}
for category, image in {BaseAttackEvent: "capture",
FrontlinePatrolEvent: "attack",
FrontlineAttackEvent: "attack",
InfantryTransportEvent: "infantry",
InsurgentAttackEvent: "insurgent_attack",
ConvoyStrikeEvent: "convoy",
InterceptEvent: "air_intercept",
NavalInterceptEvent: "naval_intercept",
StrikeEvent: "strike",
UnitsDeliveryEvent: "delivery"}.items():
self.event_icons[category] = pygame.image.load(os.path.join("resources", "ui", "events", image + ".png"))
# Load the map image
self.map = pygame.image.load(os.path.join("resources", self.game.theater.overview_image)).convert()
pygame.draw.rect(self.map, BLACK, (0, 0, self.map.get_width(), self.map.get_height()), 10)
pygame.draw.rect(self.map, WHITE, (0, 0, self.map.get_width(), self.map.get_height()), 5)
# Create surfaces for drawing
self.surface = pygame.Surface((self.map.get_width() + MAP_PADDING * 2,
self.map.get_height() + MAP_PADDING * 2))
self.surface.set_alpha(None)
self.overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
# Init pygame display
pygame.display.init()
pygame.display.update()
def init_sdl_thread(self):
if OverviewCanvas.started is not None:
OverviewCanvas.started.exited = True
self.thread = Thread(target=self.sdl_thread)
self.thread.start()
OverviewCanvas.started = self
print("Started SDL app")
def sdl_thread(self):
self.redraw_required = True
i = 0
while not self.exited:
self.clock.tick(30)
self.draw()
i += 1
if i == 600:
self.frontline_vector_cache = {}
i = 0
print("Stopped SDL app")
def draw(self):
try:
self.embed.winfo_ismapped()
self.embed.winfo_manager()
except:
self.exited = True
right_down = False
left_down = False
# Detect changes on display rules
r = self.compute_display_rules()
if r != self.display_rules:
self.display_rules = r
self.redraw_required = True
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
self.redraw_required = True
elif event.type == pygame.MOUSEBUTTONDOWN:
"""
Due to rendering not really supporting the zoom this is currently disabled.
@TODO: improve rendering so zoom would actually make sense
# Scroll wheel
if event.button == 4:
self.zoom += 0.25
self.redraw_required = True
elif event.button == 5:
self.zoom -= 0.25
self.redraw_required = True
"""
if event.button == 3:
right_down = True
pygame.mouse.get_rel()
if event.button == 1:
left_down = True
self.redraw_required = True
# If Right click pressed
if pygame.mouse.get_pressed()[2] == 1 and not right_down:
scr = pygame.mouse.get_rel()
self.scroll[0] += scr[0]
self.scroll[1] += scr[1]
self.redraw_required = True
if self.zoom <= 0.5:
self.zoom = 0.5
elif self.zoom > 3:
self.zoom = 3
if self.redraw_required:
# Fill
self.screen.fill(BACKGROUND)
self.surface.fill(BACKGROUND)
self.overlay.fill(pygame.Color(0, 0, 0, 0))
# Surface
cursor_pos = pygame.mouse.get_pos()
cursor_pos = (
cursor_pos[0] / self.zoom - self.scroll[0], cursor_pos[1] / self.zoom - self.scroll[1])
self.draw_map(self.surface, self.overlay, cursor_pos, [left_down, right_down])
# Scaling
scaled = pygame.transform.scale(self.surface, (
int(self.surface.get_width() * self.zoom), int(self.surface.get_height() * self.zoom)))
self.screen.blit(scaled, (self.scroll[0]*self.zoom, self.scroll[1]*self.zoom))
self.screen.blit(self.overlay, (0, 0))
pygame.display.flip()
self.redraw_required = False
def draw_map(self, surface: pygame.Surface, overlay: pygame.Surface, mouse_pos: (int, int), mouse_down: [bool, bool]):
self.surface.blit(self.map, (MAP_PADDING, MAP_PADDING))
# Display zoom level on overlay
zoom_lvl = self.font.render(" x " + str(self.zoom) + " ", ANTIALIASING, WHITE, DARK_BLUE)
self.overlay.blit(zoom_lvl, (self.overlay.get_width()-zoom_lvl.get_width()-5,
self.overlay.get_height()-zoom_lvl.get_height()-5))
# Debug
# pygame.draw.rect(surface, (255, 0, 255), (mouse_pos[0], mouse_pos[1], 5, 5), 2)
for cp in self.game.theater.controlpoints:
coords = self._transform_point(cp.position)
if self.display_road.get():
for connected_cp in cp.connected_points:
connected_coords = self._transform_point(connected_cp.position)
if connected_cp.captured != cp.captured:
color = self._enemy_color()
elif connected_cp.captured and cp.captured:
color = self._player_color()
else:
color = BLACK
pygame.draw.line(surface, color, coords, connected_coords, 2)
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
frontline = self._frontline_vector(cp, connected_cp)
if not frontline:
continue
frontline_pos, heading, distance = frontline
if distance < 10000:
frontline_pos = frontline_pos.point_from_heading(heading + 180, 5000)
distance = 10000
start_coords = self._transform_point(frontline_pos, treshold=10)
end_coords = self._transform_point(frontline_pos.point_from_heading(heading, distance),
treshold=60)
pygame.draw.line(surface, color, start_coords, end_coords, 4)
if self.display_ground_targets.get():
for ground_object in cp.ground_objects:
self.draw_ground_object(ground_object, surface, cp.captured, mouse_pos)
if self.display_bases.get():
mouse_down = self.draw_bases(mouse_pos, mouse_down)
mouse_down = self.draw_events(self.surface, mouse_pos, mouse_down)
if mouse_down[0]:
self.selected_event_info = None
def draw_bases(self, mouse_pos, mouse_down):
for cp in self.game.theater.controlpoints:
coords = self._transform_point(cp.position)
radius = 12 * math.pow(cp.importance, 1)
radius_m = max(radius * cp.base.strength - 2, 0)
if cp.captured:
color = self._player_color()
else:
color = self._enemy_color()
pygame.draw.circle(self.surface, BLACK, (int(coords[0]), int(coords[1])), int(radius))
pygame.draw.circle(self.surface, color, (int(coords[0]), int(coords[1])), int(radius_m))
label = self.font.render(cp.name, ANTIALIASING, (225, 225, 225), BLACK)
labelHover = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (128, 186, 128))
labelClick = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (122, 122, 255))
point = coords[0] - label.get_width() / 2 + 1, coords[1] + 1
rect = pygame.Rect(*point, label.get_width(), label.get_height())
if rect.collidepoint(*mouse_pos):
if mouse_down[0]:
self.surface.blit(labelClick, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1))
self._selected_cp(cp)
mouse_down[0] = False
else:
self.surface.blit(labelHover, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1))
self.draw_base_info(self.overlay, cp, (0, 0))
if self.selected_event_info:
if self._cp_available_for_selected_event(cp):
pygame.draw.line(self.surface, WHITE, rect.center, self.selected_event_info[1])
else:
self.surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1))
if self.display_forces.get():
units_title = " {} / {} / {} ".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa)
label2 = self.fontsmall.render(units_title, ANTIALIASING, color, (30, 30, 30))
self.surface.blit(label2, (coords[0] - label2.get_width() / 2, coords[1] + label.get_height() + 1))
return mouse_down
def draw_base_info(self, surface: pygame.Surface, control_point: ControlPoint, pos):
title = self.font.render(control_point.name, ANTIALIASING, BLACK, GREEN)
hp = self.font.render("Strength : ", ANTIALIASING, (225, 225, 225), BLACK)
armor_txt = "ARMOR > "
for key, value in control_point.base.armor.items():
armor_txt += key.id + " x " + str(value) + " | "
armor = self.font.render(armor_txt, ANTIALIASING, (225, 225, 225), BLACK)
aircraft_txt = "AIRCRAFT > "
for key, value in control_point.base.aircraft.items():
aircraft_txt += key.id + " x " + str(value) + " | "
aircraft = self.font.render(aircraft_txt, ANTIALIASING, (225, 225, 225), BLACK)
aa_txt = "AA/SAM > "
for key, value in control_point.base.aa.items():
aa_txt += key.id + " x " + str(value) + " | "
aa = self.font.render(aa_txt, ANTIALIASING, (225, 225, 225), BLACK)
lineheight = title.get_height()
w = max([max([a.get_width() for a in [title, armor, aircraft, aa]]), 150])
h = 5 * lineheight + 4 * 5
# Draw frame
pygame.draw.rect(surface, GREEN, (pos[0], pos[1], w + 8, h + 8))
pygame.draw.rect(surface, BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4))
pygame.draw.rect(surface, GREEN, (pos[0] + 2, pos[1], w + 4, lineheight + 4))
# Title
surface.blit(title, (pos[0] + 4, 4 + pos[1]))
surface.blit(hp, (pos[0] + 4, 4 + pos[1] + lineheight + 5))
# Draw gauge
pygame.draw.rect(surface, WHITE,
(pos[0] + hp.get_width() + 3, 4 + pos[1] + lineheight + 5, 54, lineheight))
pygame.draw.rect(surface, BRIGHT_RED,
(pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50, lineheight - 4))
pygame.draw.rect(surface, BRIGHT_GREEN, (
pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50 * control_point.base.strength, lineheight - 4))
# Text
surface.blit(armor, (pos[0] + 4, 4 + pos[1] + lineheight * 2 + 10))
surface.blit(aircraft, (pos[0] + 4, 4 + pos[1] + lineheight * 3 + 15))
surface.blit(aa, (pos[0] + 4, 4 + pos[1] + lineheight * 4 + 20))
def draw_selected_event_info(self):
event = self.selected_event_info[0]
title = self.font.render(str(event), ANTIALIASING, BLACK, GREEN)
hint = self.font.render("Select CP to depart from.", ANTIALIASING, (225, 225, 225), BLACK)
w = hint.get_width()
h = title.get_height() + hint.get_height() + 20
pos = self.overlay.get_width() / 2 - w / 2, self.overlay.get_height() - h
# Draw frame
pygame.draw.rect(self.overlay, GREEN, (pos[0], pos[1], w + 8, h + 8))
pygame.draw.rect(self.overlay, BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4))
pygame.draw.rect(self.overlay, GREEN, (pos[0] + 2, pos[1], w + 4, title.get_height() + 4))
# Title
self.overlay.blit(title, (pos[0] + 4, 4 + pos[1]))
self.overlay.blit(hint, (pos[0] + 4, 4 + pos[1] + title.get_height() + 5))
def draw_ground_object(self, ground_object: TheaterGroundObject, surface: pygame.Surface, captured: bool, mouse_pos):
if captured:
color = self._player_color()
else:
color = self._enemy_color()
x, y = self._transform_point(ground_object.position)
rect = pygame.Rect(x, y, 16, 16)
if ground_object.is_dead or captured:
surface.blit(self.ground_assets_icons["cleared"], (x, y))
else:
if ground_object.category in self.ground_assets_icons.keys():
icon = self.ground_assets_icons[ground_object.category]
else:
icon = self.ground_assets_icons["target"]
surface.blit(icon, (x, y))
if rect.collidepoint(*mouse_pos):
self.draw_ground_object_info(ground_object, (x, y), color, surface)
def draw_ground_object_info(self, ground_object: TheaterGroundObject, pos, color, surface: pygame.Surface):
lb = self.font.render(str(ground_object), ANTIALIASING, color, BLACK)
surface.blit(lb, (pos[0] + 18, pos[1]))
def draw_events(self, surface: pygame.Surface, mouse_pos, mouse_down):
occupied_rects = []
for cp in self.game.theater.controlpoints:
point = self._transform_point(cp.position)
occupied_rects.append(pygame.Rect(point[0] - 16, point[1] - 16, 32, 48))
def _location_to_rect(location: Point) -> pygame.Rect:
nonlocal occupied_rects
point = self._transform_point(location)
rect = pygame.Rect(point[0] - 16, point[1] - 16, 32, 32)
i = 0
while True:
result = True
for occupied_rect in occupied_rects:
if rect.colliderect(occupied_rect):
i += 1
if i % 2:
rect.y += occupied_rect.height
else:
rect.x += occupied_rect.width
result = False
break
if result:
break
occupied_rects.append(rect)
return rect
def _events_priority_key(event: Event) -> int:
priority_list = [InfantryTransportEvent, StrikeEvent, BaseAttackEvent, UnitsDeliveryEvent]
if type(event) not in priority_list:
return 0
else:
return priority_list.index(type(event)) + 1
events = self.game.events
events.sort(key=_events_priority_key, reverse=True)
label_to_draw = None
for event in self.game.events:
location = event.location
if type(event) in [FrontlineAttackEvent, FrontlinePatrolEvent, ConvoyStrikeEvent]:
location = self._frontline_center(event.from_cp, event.to_cp)
rect = _location_to_rect(location)
pygame.draw.rect(surface, EVENT_COLOR_ATTACK if event.is_player_attacking else EVENT_COLOR_DEFENSE, rect)
self.surface.blit(self.event_icons[event.__class__], rect.topleft)
if rect.collidepoint(*mouse_pos) or self.selected_event_info == (event, rect.center):
if not label_to_draw:
label_to_draw = self.font.render(str(event), ANTIALIASING, WHITE, BLACK), rect.center
if rect.collidepoint(*mouse_pos):
if mouse_down[0]:
self.selected_event_info = event, rect.center
mouse_down[0] = False
if label_to_draw:
surface.blit(*label_to_draw)
if self.selected_event_info:
self.draw_selected_event_info()
return mouse_down
def _selected_cp(self, cp):
if self.selected_event_info:
if self. _cp_available_for_selected_event(cp):
event = self.selected_event_info[0]
event.departure_cp = cp
self.selected_event_info = None
self.parent.start_event(event)
else:
return
else:
self.parent.go_cp(cp)
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]
@ -44,101 +553,48 @@ class OverviewCanvas:
X = point_b_img[1] + X_offset * X_scale
Y = point_a_img[0] - Y_offset * Y_scale
X += MAP_PADDING
Y += MAP_PADDING
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", 10)
def _frontline_vector(self, from_cp: ControlPoint, to_cp: ControlPoint):
# Cache mechanism to avoid performing frontline vector computation on every frame
key = str(from_cp.id) + "_" + str(to_cp.id)
if key in self.frontline_vector_cache:
return self.frontline_vector_cache[key]
else:
frontline = Conflict.frontline_vector(from_cp, to_cp, self.game.theater)
self.frontline_vector_cache[key] = frontline
return frontline
id = self.canvas.create_text(coords[0], coords[1], text=title, font=font)
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
def _frontline_center(self, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Optional[Point]:
frontline_vector = self._frontline_vector(from_cp, to_cp)
if frontline_vector:
return frontline_vector[0].point_from_heading(frontline_vector[1], frontline_vector[2]/2)
else:
return None
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))
def _cp_available_for_selected_event(self, cp: ControlPoint) -> bool:
event = self.selected_event_info[0]
return event.is_departure_available_from(cp)
def _player_color(self):
return self.game.player == "USA" and "blue" or "red"
return self.game.player == "USA" and BLUE or RED
def _enemy_color(self):
return self.game.player == "USA" and "red" or "blue"
return self.game.player == "USA" and RED or BLUE
def update(self):
self.canvas.delete(ALL)
self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image)
self.redraw_required = True
self.draw()
self.budget_label.text = "Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount)
for cp in self.game.theater.controlpoints:
for ground_object in cp.ground_objects:
x, y = self.transform_point(ground_object.position)
self.canvas.create_text(x,
y,
text=".",
fill="black" if ground_object.is_dead else self._enemy_color(),
font=("Helvetica", 18))
coords = self.transform_point(cp.position)
for connected_cp in cp.connected_points:
connected_coords = self.transform_point(connected_cp.position)
if connected_cp.captured != cp.captured:
color = self._enemy_color()
elif connected_cp.captured and cp.captured:
color = self._player_color()
else:
color = "black"
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 = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
if not frontline:
continue
frontline_pos, heading, distance = frontline
if distance < 10000:
frontline_pos = frontline_pos.point_from_heading(heading + 180, 5000)
distance = 10000
start_coords = self.transform_point(frontline_pos, treshold=10)
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 = 16 * math.pow(cp.importance, 1)
extent = max(cp.base.strength * 180, 10)
start = (180 - extent) / 2
if cp.captured:
color = self._player_color()
else:
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),
fill=color,
style=PIESLICE,
start=start,
extent=extent)
"""
#For debugging purposes
for r in cp.radials:
p = self.transform_point(cp.position.point_from_heading(r, 20000))
self.canvas.create_text(p[0], p[1], text="{}".format(r))
continue
"""
self.canvas.tag_bind(cp_id, "<Button-1>", self.display(cp))
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]+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 compute_display_rules(self):
return sum([1 if a.get() else 0 for a in [self.display_forces, self.display_road, self.display_bases, self.display_ground_targets]])
def display(self, cp: ControlPoint):
def action(_):
return self.parent.go_cp(cp)
return action

View File

@ -1,8 +1,18 @@
from tkinter import *
from game.game import *
from tkinter import Menu as TkMenu
from tkinter import messagebox
from .styles import BG_COLOR,BG_TITLE_COLOR
from game.game import *
from theater import persiangulf, nevada, caucasus, start_generator
from userdata import logging as logging_module
import sys
import webbrowser
class Window:
image = None
left_pane = None # type: Frame
right_pane = None # type: Frame
@ -10,11 +20,38 @@ class Window:
def __init__(self):
self.tk = Tk()
self.tk.title("DCS Liberation")
self.tk.iconbitmap("icon.ico")
self.tk.iconbitmap("resources/icon.ico")
self.tk.resizable(False, False)
self.tk.grid_columnconfigure(0, weight=1)
self.tk.grid_rowconfigure(0, weight=1)
self.frame = None
self.right_pane = None
self.left_pane = None
self.build()
menubar = TkMenu(self.tk)
filemenu = TkMenu(menubar, tearoff=0)
filemenu.add_command(label="New Game", command=lambda: self.new_game_confirm())
filemenu.add_separator()
filemenu.add_command(label="Exit", command=lambda: self.exit())
menubar.add_cascade(label="File", menu=filemenu)
helpmenu = TkMenu(menubar, tearoff=0)
helpmenu.add_command(label="Online Manual", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Manual"))
helpmenu.add_command(label="Troubleshooting Guide", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting"))
helpmenu.add_command(label="Modding Guide", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial"))
helpmenu.add_separator()
helpmenu.add_command(label="Contribute", command=lambda: webbrowser.open_new_tab("https://github.com/shdwp/dcs_liberation"))
helpmenu.add_command(label="Forum Thread", command=lambda: webbrowser.open_new_tab("https://forums.eagle.ru/showthread.php?t=214834"))
helpmenu.add_command(label="Report an issue", command=self.report_issue)
menubar.add_cascade(label="Help", menu=helpmenu)
self.tk.config(menu=menubar)
self.tk.focus()
def build(self):
self.frame = Frame(self.tk, bg=BG_COLOR)
self.frame.grid(column=0, row=0, sticky=NSEW)
self.frame.grid_columnconfigure(0)
@ -29,8 +66,6 @@ class Window:
self.right_pane = Frame(self.frame, bg=BG_COLOR)
self.right_pane.grid(row=0, column=1, sticky=NSEW)
self.tk.focus()
def clear_right_pane(self):
for i in range(100):
self.right_pane.grid_columnconfigure(1, weight=0)
@ -40,10 +75,70 @@ class Window:
x.grid_remove()
def clear(self):
for x in self.left_pane.winfo_children():
x.grid_remove()
for x in self.right_pane.winfo_children():
x.grid_remove()
def clear_recursive(x, n=50):
if n < 0:
return
for y in x.winfo_children():
clear_recursive(y, n-1)
x.grid_forget()
clear_recursive(self.frame, 50)
self.left_pane.grid_remove()
self.right_pane.grid_remove()
self.build()
def start_new_game(self, player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float):
if terrain == "persiangulf":
conflicttheater = persiangulf.PersianGulfTheater()
elif terrain == "nevada":
conflicttheater = nevada.NevadaTheater()
else:
conflicttheater = caucasus.CaucasusTheater()
if midgame:
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
conflicttheater.controlpoints[i].captured = True
start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
start_generator.generate_groundobjects(conflicttheater)
game = Game(player_name=player_name,
enemy_name=enemy_name,
theater=conflicttheater)
game.budget = int(game.budget * multiplier)
game.settings.multiplier = multiplier
game.settings.sams = sams
game.settings.version = logging_module.version_string()
if midgame:
game.budget = game.budget * 4 * len(list(conflicttheater.conflicts()))
self.proceed_to_main_menu(game)
def proceed_to_main_menu(self, game: Game):
from ui.mainmenu import MainMenu
self.clear()
m = MainMenu(self, None, game)
m.display()
def proceed_to_new_game_menu(self):
from ui.newgamemenu import NewGameMenu
self.clear()
new_game_menu = NewGameMenu(self, self.start_new_game)
new_game_menu.display()
def new_game_confirm(self):
result = messagebox.askquestion("Start a new game", "Are you sure you want to start a new game ? Your current campaign will be overriden and there is no going back !", icon='warning')
if result == 'yes':
self.proceed_to_new_game_menu()
else:
pass
def report_issue(self):
raise logging_module.ShowLogsException()
def exit(self):
self.tk.destroy()
sys.exit(0)
def run(self):
self.tk.mainloop()

View File

@ -42,6 +42,10 @@ def parse_mutliplayer_debriefing(contents: str):
key = "initiator"
if element is None:
element = {}
elif line.startswith("initiatorMissionID\t"):
key = "initiatorMissionID"
if element is None:
element = {}
elif line.startswith("type\t"):
key = "type"
if element is None:
@ -76,7 +80,7 @@ class Debriefing:
nonlocal dead_units
object_mission_id = int(object_mission_id_str)
if object_mission_id in dead_units:
logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
logging.error("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
return
dead_units.append(object_mission_id)

View File

@ -35,6 +35,10 @@ def setup_version_string(str):
_version_string = str
def version_string():
return _version_string
if "--stdout" in sys.argv:
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
else: