6
.gitignore
vendored
@ -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
@ -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>
|
||||
46
__init__.py
@ -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
@ -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()
|
||||
|
||||
41
game/db.py
@ -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)
|
||||
|
||||
@ -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 *
|
||||
|
||||
@ -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)
|
||||
|
||||
83
game/event/convoystrike.py
Normal 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
|
||||
@ -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 = {}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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={})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
68
game/game.py
@ -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()
|
||||
|
||||
|
||||
49
game/operation/convoystrike.py
Normal 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()
|
||||
@ -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")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ from .triggergen import *
|
||||
from .environmentgen import *
|
||||
from .groundobjectsgen import *
|
||||
from .briefinggen import *
|
||||
from .forcedoptionsgen import *
|
||||
|
||||
from . import naming
|
||||
|
||||
|
||||
123
gen/aircraft.py
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
16
gen/armor.py
@ -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))
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
@ -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')
|
||||
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 260 KiB |
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
import dcs
|
||||
|
||||
from game import db
|
||||
|
||||
@ -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()
|
||||
BIN
resources/ui/events/air_intercept.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
resources/ui/events/attack.PNG
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
resources/ui/events/capture.PNG
Normal file
|
After Width: | Height: | Size: 697 B |
BIN
resources/ui/events/convoy.png
Normal file
|
After Width: | Height: | Size: 804 B |
BIN
resources/ui/events/delivery.PNG
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/ui/events/infantry.PNG
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
resources/ui/events/insurgent_attack.PNG
Normal file
|
After Width: | Height: | Size: 824 B |
BIN
resources/ui/events/naval_intercept.PNG
Normal file
|
After Width: | Height: | Size: 688 B |
BIN
resources/ui/events/strike.PNG
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/ui/ground_assets/aa.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
resources/ui/ground_assets/ammo.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
resources/ui/ground_assets/cleared.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
resources/ui/ground_assets/comms.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
resources/ui/ground_assets/factory.png
Normal file
|
After Width: | Height: | Size: 220 B |
BIN
resources/ui/ground_assets/farp.png
Normal file
|
After Width: | Height: | Size: 253 B |
BIN
resources/ui/ground_assets/fob.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
resources/ui/ground_assets/fuel.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
resources/ui/ground_assets/oil.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
resources/ui/ground_assets/power.png
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
resources/ui/ground_assets/target.png
Normal file
|
After Width: | Height: | Size: 230 B |
BIN
resources/ui/ground_assets/warehouse.png
Normal file
|
After Width: | Height: | Size: 232 B |
@ -1 +1 @@
|
||||
Subproject commit 54eab60f228847f2d92e344e6885eca855354f41
|
||||
Subproject commit 4fbb7ad3e0e2eecedc4e1dd14f2eb18025fef9f5
|
||||
0
tests/__init__.py
Normal file
10
tests/integration/__init__.py
Normal 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()
|
||||
46
tests/integration/baseattack.py
Normal 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()
|
||||
50
tests/integration/convoystrike.py
Normal 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()
|
||||
52
tests/integration/frontlineattack.py
Normal 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()
|
||||
48
tests/integration/insurgentattack.py
Normal 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()
|
||||
48
tests/integration/intercept.py
Normal 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()
|
||||
49
tests/integration/navalintercept.py
Normal 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()
|
||||
48
tests/integration/strike.py
Normal 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
@ -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)
|
||||
0
tests/userfolder/DCS/Missions/empty.txt
Normal 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]]
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
111
ui/window.py
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||