diff --git a/__init__.py b/__init__.py index b63f4a41..b8b69e2e 100755 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 import theater.caucasus +import theater.persiangulf +import theater.nevada + import ui.window import ui.mainmenu import ui.newgamemenu @@ -19,10 +22,15 @@ game = persistency.restore_game() if not game: new_game_menu = None # type: NewGameMenu - def start_new_game(player_name: str, enemy_name: str): - conflicttheater = theater.caucasus.CaucasusTheater() - start_generator.generate_initial(conflicttheater, enemy_name) + def start_new_game(player_name: str, enemy_name: str, terrain: str): + if terrain == "persiangulf": + conflicttheater = theater.persiangulf.PersianGulfTheater() + elif terrain == "nevada": + conflicttheater = theater.nevada.NevadaTheater() + else: + conflicttheater = theater.caucasus.CaucasusTheater() + start_generator.generate_initial(conflicttheater, enemy_name) proceed_to_main_menu(Game(player_name=player_name, enemy_name=enemy_name, theater=conflicttheater)) diff --git a/game/db.py b/game/db.py index 3fb9475b..d2373691 100644 --- a/game/db.py +++ b/game/db.py @@ -8,33 +8,46 @@ from dcs.task import * from dcs.unittype import * PRICES = { - # planes - - Su_25T: 11, - Su_25: 11, - A_10A: 18, - A_10C: 20, - - FA_18C_hornet: 18, - AV8BNA: 15, - + # fighter + C_101CC: 10, + AJS37: 15, + F_5E: 12, + MiG_23MLD: 15, + MiG_25PD: 20, + MiG_31: 30, Su_27: 30, Su_33: 33, - F_15C: 30, - M_2000C: 11, - MiG_15bis: 8, MiG_21Bis: 13, MiG_29A: 23, + FA_18C_hornet: 18, + AV8BNA: 15, + F_15C: 30, + M_2000C: 15, + # bomber + Su_25T: 15, + Su_24M: 18, + Su_17M4: 13, + L_39ZA: 10, + MiG_29G: 18, + Su_34: 22, + + A_10A: 18, + A_10C: 20, + + # special IL_76MD: 13, + An_26B: 13, + An_30M: 13, + Yak_40: 13, S_3B_Tanker: 13, A_50: 8, E_3A: 8, + C_130: 8, # armor - Armor.MBT_T_55: 4, Armor.MBT_T_80U: 8, Armor.MBT_T_90: 10, @@ -45,49 +58,142 @@ PRICES = { Armor.ATGM_M1134_Stryker: 6, Armor.APC_BTR_80: 6, - AirDefence.AAA_ZU_23_on_Ural_375: 4, + AirDefence.AAA_Vulcan_M163: 5, AirDefence.SAM_Avenger_M1097: 10, + AirDefence.SAM_Patriot_ICC: 15, + + AirDefence.AAA_ZU_23_on_Ural_375: 5, + AirDefence.SAM_SA_18_Igla_MANPADS: 8, + AirDefence.SAM_SA_19_Tunguska_2S6: 10, + AirDefence.SAM_SA_8_Osa_9A33: 15, + + # ship + CV_1143_5_Admiral_Kuznetsov: 100, + CVN_74_John_C__Stennis: 100, } UNIT_BY_TASK = { - FighterSweep: [Su_27, Su_33, FA_18C_hornet, F_15C, MiG_21Bis, MiG_29A, F_A_18C, AV8BNA, ], - CAS: [Su_25T, A_10A, A_10C, ], - Transport: [IL_76MD, S_3B_Tanker, ], + FighterSweep: [ + C_101CC, + AJS37, + F_5E, + MiG_23MLD, + MiG_25PD, + MiG_31, + Su_27, + Su_33, + MiG_15bis, + MiG_21Bis, + MiG_29A, + FA_18C_hornet, + AV8BNA, + F_15C, + M_2000C, + ], + CAS: [ + A_10A, + A_10C, + Su_25T, + Su_24M, + Su_17M4, + L_39ZA, + MiG_29G, + Su_34, + ], + + Transport: [ + IL_76MD, + An_26B, + An_30M, + Yak_40, + + S_3B_Tanker, + C_130, + ], AWACS: [E_3A, A_50, ], CAP: [Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, Armor.APC_BTR_80, ], - AirDefence: [AirDefence.AAA_ZU_23_on_Ural_375, AirDefence.SAM_Avenger_M1097 ], + AirDefence: [ + AirDefence.AAA_Vulcan_M163, + AirDefence.SAM_Avenger_M1097, + AirDefence.SAM_Patriot_ICC, + + AirDefence.AAA_ZU_23_on_Ural_375, + AirDefence.SAM_SA_18_Igla_MANPADS, + AirDefence.SAM_SA_19_Tunguska_2S6, + AirDefence.SAM_SA_8_Osa_9A33, + ], Carriage: [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov, ], } UNIT_BY_COUNTRY = { "Russia": [ - Su_25T, + C_101CC, + AJS37, + F_5E, + MiG_23MLD, + MiG_25PD, + MiG_31, Su_27, Su_33, - Su_25, + MiG_15bis, MiG_21Bis, MiG_29A, + M_2000C, + + A_10A, + A_10C, + Su_25T, + Su_24M, + Su_17M4, + L_39ZA, + MiG_29G, + Su_34, + + IL_76MD, + An_26B, + An_30M, + Yak_40, A_50, + AirDefence.AAA_ZU_23_on_Ural_375, + AirDefence.SAM_SA_18_Igla_MANPADS, + AirDefence.SAM_SA_19_Tunguska_2S6, + AirDefence.SAM_SA_8_Osa_9A33, + Armor.APC_BTR_80, Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, - IL_76MD, CV_1143_5_Admiral_Kuznetsov], - "USA": [F_15C, - A_10C, - FA_18C_hornet, - AV8BNA, - E_3A, - Armor.MBT_M1A2_Abrams, - Armor.MBT_M60A3_Patton, - Armor.ATGM_M1134_Stryker, - S_3B_Tanker, - AirDefence.SAM_Avenger_M1097, - CVN_74_John_C__Stennis], + "USA": [ + F_15C, + FA_18C_hornet, + AV8BNA, + AJS37, + F_5E, + M_2000C, + MiG_21Bis, + MiG_15bis, + + A_10A, + A_10C, + + S_3B_Tanker, + C_130, + E_3A, + + Armor.MBT_M1A2_Abrams, + Armor.MBT_M60A3_Patton, + Armor.ATGM_M1134_Stryker, + + AirDefence.AAA_Vulcan_M163, + AirDefence.SAM_Avenger_M1097, + AirDefence.SAM_Patriot_ICC, + + CVN_74_John_C__Stennis, + ], } PLANE_PAYLOAD_OVERRIDES = { @@ -132,3 +238,38 @@ def task_name(task) -> str: return "AirDefence" else: return task.name + + +def choose_units(for_task: Task, factor: float, count: int, country: str) -> typing.Collection[UnitType]: + suitable_unittypes = find_unittype(for_task, country) + suitable_unittypes.sort(key=lambda x: PRICES[x]) + + idx = int(len(suitable_unittypes) * factor) + variety = int(count + count * factor / 2) + + index_start = min(idx, len(suitable_unittypes) - variety) + index_end = min(idx + variety, len(suitable_unittypes)) + return suitable_unittypes[index_start:index_end] + + +def _validate_db(): + # check unit by task uniquity + total_set = set() + for t, unit_collection in UNIT_BY_TASK.items(): + for unit_type in unit_collection: + assert unit_type not in total_set, "{} is duplicate".format(unit_type) + total_set.add(unit_type) + + # check country allegiance + for unit_type in total_set: + did_find = False + for country_units_list in UNIT_BY_COUNTRY.values(): + if unit_type in country_units_list: + did_find = True + assert did_find, "{} not in country list".format(unit_type) + + # check prices + for unit_type in total_set: + assert unit_type in PRICES, "{} not in prices".format(unit_type) + +_validate_db() \ No newline at end of file diff --git a/game/event.py b/game/event.py index d1ba4463..7b4f273b 100644 --- a/game/event.py +++ b/game/event.py @@ -26,13 +26,13 @@ class Event: def generate(self): self.operation.is_awacs_enabled = self.is_awacs_enabled - self.operation.prepare(is_quick=False) + self.operation.prepare(self.game.theater.terrain, is_quick=False) self.operation.generate() self.operation.mission.save("build/nextturn.miz") def generate_quick(self): self.operation.is_awacs_enabled = self.is_awacs_enabled - self.operation.prepare(is_quick=True) + self.operation.prepare(self.game.theater.terrain, is_quick=True) self.operation.generate() self.operation.mission.save('build/nextturn_quick.miz') @@ -88,7 +88,7 @@ class GroundInterceptEvent(Event): else: pass - def player_attacking(self, position: Point, strikegroup: db.PlaneDict, clients: db.PlaneDict): + def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict): suitable_unittypes = db.find_unittype(CAP, self.defender_name) random.shuffle(suitable_unittypes) unittypes = suitable_unittypes[:self.TARGET_VARIETY] @@ -100,9 +100,9 @@ class GroundInterceptEvent(Event): defender_name=self.defender_name, attacker_clients=clients, defender_clients={}, - from_cp=self.from_cp) - op.setup(position=position, - target=self.targets, + from_cp=self.from_cp, + to_cp=self.to_cp) + op.setup(target=self.targets, strikegroup=strikegroup) self.operation = op diff --git a/game/game.py b/game/game.py index 5ce191ab..8f25b577 100644 --- a/game/game.py +++ b/game/game.py @@ -9,6 +9,7 @@ COMMISION_LIMITS_FACTORS = { } COMMISION_AMOUNTS_SCALE = 2 +COMMISION_UNIT_VARIETY = 4 COMMISION_AMOUNTS_FACTORS = { CAP: 0.6, CAS: 0.3, @@ -85,7 +86,6 @@ class Game: enemy_interception = True break - if to_cp in self.theater.conflicts(False): continue @@ -120,8 +120,10 @@ class Game: break 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 = PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE * math.log(len(self.theater.player_points()) + 1, PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG) + 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, @@ -139,8 +141,9 @@ class Game: awarded_points = COMMISION_AMOUNTS_FACTORS[for_task] * math.pow(cp.importance, COMMISION_AMOUNTS_SCALE) points_to_spend = cp.base.append_commision_points(for_task, awarded_points) if points_to_spend > 0: - unit_type = random.choice(db.find_unittype(for_task, self.enemy)) - cp.base.commision_units({unit_type: points_to_spend}) + importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW) + unittypes = db.choose_units(for_task, importance_factor, COMMISION_UNIT_VARIETY, self.enemy) + cp.base.commision_units({random.choice(unittypes): points_to_spend}) @property def budget_reward_amount(self): diff --git a/game/operation.py b/game/operation.py index f22579c6..1fd53c0e 100644 --- a/game/operation.py +++ b/game/operation.py @@ -58,7 +58,8 @@ class Operation: enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name self.extra_aagen = ExtraAAConflictGenerator(mission, conflict, self.game, player_name, enemy_name) - def prepare(self, is_quick: bool): + def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): + self.mission = dcs.Mission(terrain) self.is_quick = is_quick if is_quick: @@ -66,14 +67,25 @@ class Operation: self.defenders_starting_position = None else: self.attackers_starting_position = self.from_cp.at - self.defenders_starting_position = self.to_cp and self.to_cp.at or None + self.defenders_starting_position = self.to_cp.at def generate(self): - self.extra_aagen.generate() - self.envgen.generate(self.is_quick) if self.is_awacs_enabled: self.awacsgen.generate() + self.extra_aagen.generate() + self.envgen.generate(self.is_quick) + + for global_cp in self.game.theater.controlpoints: + if not global_cp.is_global: + continue + + ship = self.shipgen.generate(type=db.find_unittype(Carriage, self.attacker_name)[0], + at=global_cp.at) + + if global_cp == self.from_cp and not self.is_quick: + self.attackers_starting_position = ship + def units_of(self, country_name: str) -> typing.Collection[UnitType]: return [] @@ -103,14 +115,12 @@ class CaptureOperation(Operation): self.defense = defense self.aa = aa - def prepare(self, is_quick: bool): - super(CaptureOperation, self).prepare(is_quick) - mission = dcs.Mission() - - self.initialize(mission=mission, + def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): + super(CaptureOperation, self).prepare(terrain, is_quick) + self.initialize(mission=self.mission, conflict=self.to_cp.conflict_attack(self.from_cp, - mission.country(self.attacker_name), - mission.country(self.defender_name))) + self.mission.country(self.attacker_name), + self.mission.country(self.defender_name))) def generate(self): self.armorgen.generate(self.attack, self.defense) @@ -140,18 +150,16 @@ class InterceptOperation(Operation): self.airdefense = airdefense self.interceptors = interceptors - def prepare(self, is_quick: bool): - super(InterceptOperation, self).prepare(is_quick) - mission = dcs.Mission() - + def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): + super(InterceptOperation, self).prepare(terrain, is_quick) conflict = Conflict.intercept_conflict( - attacker=mission.country(self.attacker_name), - defender=mission.country(self.defender_name), + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), from_cp=self.from_cp, to_cp=self.to_cp ) - self.initialize(mission=mission, + self.initialize(mission=self.mission, conflict=conflict) def generate(self): @@ -159,40 +167,30 @@ class InterceptOperation(Operation): self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) if self.from_cp.is_global: - starting_ship = self.shipgen.generate(type=db.find_unittype(Carriage, self.attacker_name)[0], - at=self.from_cp.at) - - if self.is_quick: - starting_ship = None - - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=starting_ship) + super(InterceptOperation, self).generate() + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) else: self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) - - super(InterceptOperation, self).generate() + super(InterceptOperation, self).generate() class GroundInterceptOperation(Operation): def setup(self, - position: Point, target: db.ArmorDict, strikegroup: db.PlaneDict): - self.position = position self.strikegroup = strikegroup self.target = target - def prepare(self, is_quick: bool): - super(GroundInterceptOperation, self).prepare(is_quick) - mission = dcs.Mission() + def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): + super(GroundInterceptOperation, self).prepare(terrain, is_quick) conflict = Conflict.ground_intercept_conflict( - attacker=mission.country(self.attacker_name), - defender=mission.country(self.defender_name), - position=self.position, - heading=randint(0, 360), - radials=ALL_RADIALS + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + heading=self.to_cp.position.heading_between_point(self.from_cp.position), + cp=self.to_cp ) - self.initialize(mission=mission, + self.initialize(mission=self.mission, conflict=conflict) def generate(self): diff --git a/gen/aaa.py b/gen/aaa.py index 6453d0c3..42fe6a55 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -7,8 +7,8 @@ from .naming import * from dcs.mission import * DISTANCE_FACTOR = 4, 5 -EXTRA_AA_MIN_DISTANCE = 70000 -EXTRA_AA_POSITION_FROM_CP = 10000 +EXTRA_AA_MIN_DISTANCE = 35000 +EXTRA_AA_POSITION_FROM_CP = 550 class AAConflictGenerator: def __init__(self, mission: Mission, conflict: Conflict): diff --git a/gen/aircraft.py b/gen/aircraft.py index d619cd72..84f7900c 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -72,6 +72,8 @@ class AircraftConflictGenerator: for unit_instance in group.units: unit_instance.livery_id = db.PLANE_LIVERY_OVERRIDES[unit_type] + print("AC: {} {}".format(unit_type, len(group.units))) + def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup: assert count > 0 assert unit is not None diff --git a/gen/awacsgen.py b/gen/awacsgen.py index 714a3184..1f7aeca3 100644 --- a/gen/awacsgen.py +++ b/gen/awacsgen.py @@ -8,6 +8,9 @@ from dcs.unittype import * from dcs.task import * from dcs.terrain.terrain import NoParkingSlotError +AWACS_DISTANCE = 150000 +AWACS_ALT = 10000 + class AWACSConflictGenerator: def __init__(self, mission: Mission, conflict: Conflict, game): @@ -22,5 +25,6 @@ class AWACSConflictGenerator: country=self.conflict.attackers_side, name=namegen.next_awacs_group_name(), plane_type=plane, + altitude=AWACS_ALT, airport=None, - position=self.conflict.position) + position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE)) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 83217068..34866ee5 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -14,10 +14,8 @@ from dcs.point import * from dcs.task import * from dcs.country import * -def _opposite_heading(h): - return h+180 - GROUND_DISTANCE_FACTOR = 2 +GROUNDINTERCEPT_DISTANCE_FACTOR = 6 AIR_DISTANCE = 32000 INTERCEPT_ATTACKERS_HEADING = -45, 45 @@ -28,6 +26,10 @@ INTERCEPT_MAX_DISTANCE = 80000 INTERCEPT_MIN_DISTANCE = 45000 +def _opposite_heading(h): + return h+180 + + class Conflict: attackers_side = None # type: Country defenders_side = None # type: Country @@ -81,18 +83,18 @@ class Conflict: return instance @classmethod - def ground_intercept_conflict(self, attacker: Country, defender: Country, position: Point, heading: int, radials: typing.List[int]): + def ground_intercept_conflict(self, attacker: Country, defender: Country, heading: int, cp): from theater.conflicttheater import SIZE_SMALL instance = self() instance.attackers_side = attacker instance.defenders_side = defender - instance.position = position - instance.size = SIZE_SMALL - instance.radials = radials + instance.position = cp.position + instance.size = cp.size + instance.radials = cp.radials instance.air_attackers_location = instance.position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE) - instance.ground_defenders_location = instance.position + instance.ground_defenders_location = instance.position.point_from_heading(random.choice(cp.radials), instance.size * GROUNDINTERCEPT_DISTANCE_FACTOR) return instance diff --git a/gen/envsettingsgen.py b/gen/envsettingsgen.py index 8d6c07c2..ce95877b 100644 --- a/gen/envsettingsgen.py +++ b/gen/envsettingsgen.py @@ -6,6 +6,7 @@ from dcs.mission import Mission from dcs.triggers import * from dcs.condition import * from dcs.action import * +from dcs.task import * from theater.weatherforecast import WeatherForecast from theater.conflicttheater import Conflict @@ -14,18 +15,17 @@ ACTIVATION_TRIGGER_SIZE = 80000 ACTIVATION_TRIGGER_MIN_DISTANCE = 5000 RANDOM_TIME = { - "night": 0, - "dusk": 5, - "dawn": 35, - "noon": 75, + "night": 5, + "dusk": 30, + "dawn": 30, "day": 100, } RANDOM_WEATHER = { - 0: 0, # thunderstorm - 1: 5, # heavy rain - 2: 20, # rain - 3: 40, # random dynamic + 0: 5, # thunderstorm + 1: 20, # heavy rain + 2: 30, # rain + 3: 100, # random dynamic } @@ -36,17 +36,7 @@ class EnvironmentSettingsGenerator: self.game = game def _gen_random_time(self): - time_roll = random.randint(0, 100) - time_period = None - for k, v in RANDOM_TIME.items(): - if v >= time_roll: - time_period = k - break - - self.mission.random_daytime(time_period) - - def _gen_random_time_2(self): - start_time = datetime.now(self.mission.start_time.tzinfo).date() + start_time = datetime.today() daytime_map = { "day": timedelta(hours=random.randrange(9, 18)), "night": timedelta(hours=random.randrange(-3, 6)), @@ -54,24 +44,24 @@ class EnvironmentSettingsGenerator: "dawn": timedelta(hours=random.randrange(6, 9)), } - time_roll = random.randint(0, 100) time_period = None for k, v in RANDOM_TIME.items(): - if v >= time_roll: + if random.randint(0, 100) <= v: time_period = k break + print("generated {}".format(time_period)) start_time += daytime_map[time_period] self.mission.start_time = start_time def _gen_random_weather(self): - weather_roll = random.randint(0, 100) weather_type = None - for k, v in RANDOM_TIME.items(): - if v >= weather_roll: + for k, v in RANDOM_WEATHER.items(): + if random.randint(0, 100) <= v: weather_type = k break + print("generated weather {}".format(weather_type)) if weather_type == 0: self.mission.weather.random_thunderstorm() elif weather_type == 1: @@ -116,9 +106,10 @@ class EnvironmentSettingsGenerator: player_coalition = self.game.player == "USA" and "blue" or "red" enemy_coalition = player_coalition == "blue" and "red" or "blue" - self.mission.coalition[player_coalition].bullseye = self.conflict.position + self.mission.coalition[player_coalition].bullseye = {"x": self.conflict.position.x, + "y": self.conflict.position.y} - self._gen_random_time_2() + self._gen_random_time() self._gen_random_weather() self._set_allegiances(player_coalition, enemy_coalition) diff --git a/theater/caucasus.py b/theater/caucasus.py index 81605c39..80e236b0 100644 --- a/theater/caucasus.py +++ b/theater/caucasus.py @@ -6,6 +6,8 @@ from .base import * class CaucasusTheater(ConflictTheater): + terrain = caucasus.Caucasus() + overview_image = "caumap.gif" reference_points = {(-317948.32727306, 635639.37385346): (282.5, 319), (-355692.3067714, 617269.96285781): (269, 352), } diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index 15c632f2..82b120cf 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -5,6 +5,7 @@ import dcs from .controlpoint import * +SIZE_TINY = 150 SIZE_SMALL = 600 SIZE_REGULAR = 1000 SIZE_BIG = 2000 @@ -29,6 +30,7 @@ class ConflictTheater: terrain = None # type: dcs.terrain.Terrain controlpoints = None # type: typing.Collection[ControlPoint] reference_points = None # type: typing.Dict + overview_image = None # type: str def __init__(self): self.controlpoints = [] diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 37f78c4e..d753b1fb 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -1,4 +1,5 @@ import typing +import re from dcs.mapping import * from dcs.country import * @@ -17,7 +18,8 @@ class ControlPoint: def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int): import theater.base - self.name = name.split("-")[0] + self.name = " ".join(re.split(r" |-", name)[:2]) + self.full_name = name self.position = position self.at = at @@ -30,6 +32,7 @@ class ControlPoint: @classmethod def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int): + assert airport return cls(airport.name, airport.position, airport, radials, size, importance) @classmethod diff --git a/theater/nevada.py b/theater/nevada.py index d52568ee..dc386977 100644 --- a/theater/nevada.py +++ b/theater/nevada.py @@ -6,6 +6,11 @@ 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), } + mina = ControlPoint.from_airport(nevada.Mina_Airport_3Q0, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) tonopah = ControlPoint.from_airport(nevada.Tonopah_Airport, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) tonopah_test_range = ControlPoint.from_airport(nevada.Tonopah_Test_Range_Airfield, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) diff --git a/theater/persiangulf.py b/theater/persiangulf.py index 1067aceb..2beaf1cb 100644 --- a/theater/persiangulf.py +++ b/theater/persiangulf.py @@ -6,46 +6,65 @@ from .base import * class PersianGulfTheater(ConflictTheater): + terrain = dcs.terrain.PersianGulf() + overview_image = "persiangulf.gif" + reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (351, 115), + (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (389, 22), } + al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, ALL_RADIALS, SIZE_BIG, IMPORTANCE_LOW) al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, ALL_RADIALS, SIZE_BIG, IMPORTANCE_LOW) al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_LOW) - sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) + sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [330], SIZE_SMALL, IMPORTANCE_LOW) dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_SWNE, SIZE_LARGE, IMPORTANCE_MEDIUM) sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, ALL_RADIALS, SIZE_BIG, IMPORTANCE_MEDIUM) - fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_NS_E, SIZE_REGULAR, IMPORTANCE_MEDIUM) + fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_NS_W, SIZE_REGULAR, IMPORTANCE_MEDIUM) khasab = ControlPoint.from_airport(persiangulf.Khasab, COAST_EW_S, SIZE_SMALL, IMPORTANCE_MEDIUM) - sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_MEDIUM) - abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_MEDIUM) - tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_HIGH) + sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, ALL_RADIALS, SIZE_TINY, IMPORTANCE_MEDIUM) + abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, ALL_RADIALS, SIZE_TINY, IMPORTANCE_MEDIUM) + tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, COAST_EW_N, SIZE_SMALL, IMPORTANCE_HIGH) + tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, COAST_EW_S, SIZE_TINY, IMPORTANCE_HIGH) bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, COAST_EW_N, SIZE_SMALL, IMPORTANCE_HIGH) qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, COAST_EW_N, SIZE_SMALL, IMPORTANCE_HIGH) - havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_SWNE, SIZE_REGULAR, IMPORTANCE_HIGH) + havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_EW_N, SIZE_REGULAR, IMPORTANCE_HIGH) bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, COAST_EW_N, SIZE_BIG, IMPORTANCE_HIGH) lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_HIGH) + east_carrier = ControlPoint.carrier("West carrier", Point(-91023.430176, -159467.078125)) + west_carrier = ControlPoint.carrier("East carrier", Point(-100531.972946, 60939.275818)) + north_carrier = ControlPoint.carrier("North carrier", Point(70531.972946, 60939.275818)) + def __init__(self): super(PersianGulfTheater, self).__init__() self.add_controlpoint(self.al_dhafra, connected_to=[self.sir_abu_nuayr, self.al_maktoum]) self.add_controlpoint(self.al_maktoum, connected_to=[self.al_dhafra, self.al_minhad, self.sir_abu_nuayr]) - self.add_controlpoint(self.dubai, connected_to=[self.sir_abu_nuayr, self.al_minhad, self.sharjah]) + self.add_controlpoint(self.al_minhad, connected_to=[self.al_maktoum, self.dubai]) + self.add_controlpoint(self.dubai, connected_to=[self.al_minhad, self.sharjah]) self.add_controlpoint(self.sharjah, connected_to=[self.dubai, self.khasab]) self.add_controlpoint(self.fujairah, connected_to=[self.dubai, self.khasab]) self.add_controlpoint(self.khasab, connected_to=[self.sharjah, self.fujairah, self.tunb_island]) - self.add_controlpoint(self.sir_abu_nuayr, connected_to=[self.al_dhafra, self.al_maktoum, self.dubai]) - self.add_controlpoint(self.sirri, connected_to=[self.sir_abu_nuayr, self.abu_musa, self.tunb_island]) - self.add_controlpoint(self.abu_musa, connected_to=[self.sirri, self.sir_abu_nuayr, self.tunb_island]) + self.add_controlpoint(self.sir_abu_nuayr, connected_to=[self.al_dhafra, self.al_maktoum, self.sirri]) + self.add_controlpoint(self.sirri, connected_to=[self.sir_abu_nuayr, self.abu_musa]) + self.add_controlpoint(self.abu_musa, connected_to=[self.sirri, self.sir_abu_nuayr]) + self.add_controlpoint(self.tunb_kochak, connected_to=[self.sirri, self.abu_musa, self.tunb_island]) - self.add_controlpoint(self.tunb_island, connected_to=[self.khasab, self.abu_musa, self.sirri, self.qeshm]) + self.add_controlpoint(self.tunb_island, connected_to=[self.khasab, self.qeshm, self.tunb_kochak]) self.add_controlpoint(self.bandar_lengeh, connected_to=[self.tunb_island, self.lar, self.qeshm]) self.add_controlpoint(self.qeshm, connected_to=[self.bandar_lengeh, self.havadarya, self.tunb_island]) self.add_controlpoint(self.havadarya, connected_to=[self.lar, self.qeshm, self.bandar_abbas]) self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya]) self.add_controlpoint(self.lar, connected_to=[self.bandar_lengeh, self.qeshm, self.havadarya]) + self.add_controlpoint(self.east_carrier) + self.add_controlpoint(self.west_carrier) + self.add_controlpoint(self.north_carrier) + + self.east_carrier.captured = True + self.west_carrier.captured = True + self.north_carrier.captured = True self.al_dhafra.captured = True diff --git a/theater/start_generator.py b/theater/start_generator.py index db07b3bb..b03cb739 100644 --- a/theater/start_generator.py +++ b/theater/start_generator.py @@ -1,8 +1,8 @@ from theater.base import * from theater.conflicttheater import * -UNIT_VARIETY = 2 -UNIT_AMOUNT_FACTOR = 0.25 +UNIT_VARIETY = 3 +UNIT_AMOUNT_FACTOR = 16 def generate_initial(theater: ConflictTheater, enemy: str): @@ -11,19 +11,14 @@ def generate_initial(theater: ConflictTheater, enemy: str): continue for task in [CAP, FighterSweep, CAS, AirDefence]: - suitable_unittypes = db.find_unittype(task, enemy) - suitable_unittypes.sort(key=lambda x: db.PRICES[x], reverse=True) + assert cp.importance <= IMPORTANCE_HIGH, "invalid importance {}".format(cp.importance) + assert cp.importance >= IMPORTANCE_LOW, "invalid importance {}".format(cp.importance) - importance = cp.importance * 10 - reversed_importance = IMPORTANCE_HIGH * 10 - cp.importance * 10 - units_idx_start = int(reversed_importance) - units_idx_end = units_idx_start + UNIT_VARIETY + importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW) + variety = int(UNIT_VARIETY + UNIT_VARIETY * importance_factor / 2) + unittypes = db.choose_units(task, importance_factor, variety, enemy) - range_start = min(len(suitable_unittypes)-1, units_idx_start) - range_end = min(len(suitable_unittypes), units_idx_end) - unittypes = suitable_unittypes[range_start:range_end] - - typecount = max(math.floor(importance * UNIT_AMOUNT_FACTOR), 1) - #print("{} - {}-{} {}, {}".format(cp.name, units_idx_start, units_idx_end, unittypes, typecount)) - units = {unittype: typecount for unittype in unittypes} - cp.base.commision_units(units) + count = max(int(importance_factor * UNIT_AMOUNT_FACTOR), 1) + count_per_type = max(int(float(count) / len(unittypes)), 1) + for unit_type in unittypes: + cp.base.commision_units({unit_type: count_per_type}) diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 9467089b..d19eb889 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -143,7 +143,7 @@ class EventMenu(Menu): clients=scrambled_clients) elif type(self.event) is GroundInterceptEvent: e = self.event # type: GroundInterceptEvent - e.player_attacking(e.to_cp.position.random_point_within(30000), strikegroup=scrambled_aircraft, clients=scrambled_clients) + e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) self.game.initiate_event(self.event) EventResultsMenu(self.window, self.parent, self.game, self.event).display() diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index 0a0f5af7..90434aa8 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -21,6 +21,9 @@ class EventResultsMenu(Menu): self.window.clear_right_pane() if not self.finished: + """ + For debugging purposes + Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1)).grid() Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1)).grid(row=1, column=1) @@ -29,6 +32,7 @@ class EventResultsMenu(Menu): Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0)).grid(row=3, ) Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0)).grid(row=3, column=1) + """ Label(self.frame, text="Play the mission and save debriefing to {}".format(debriefing_directory_location())).grid(row=0, column=0) else: diff --git a/ui/newgamemenu.py b/ui/newgamemenu.py index bcdedf96..8f9be887 100644 --- a/ui/newgamemenu.py +++ b/ui/newgamemenu.py @@ -6,6 +6,7 @@ from ui.window import * class NewGameMenu(Menu): selected_country = None # type: IntVar + selected_terrain = None # type: IntVar def __init__(self, window: Window, callback: typing.Callable): super(NewGameMenu, self).__init__(window, None, None) @@ -15,6 +16,9 @@ class NewGameMenu(Menu): self.selected_country = IntVar() self.selected_country.set(0) + self.selected_terrain = IntVar() + self.selected_terrain.set(0) + @property def player_country_name(self): if self.selected_country.get() == 0: @@ -29,13 +33,27 @@ class NewGameMenu(Menu): else: return "Russia" + @property + def terrain_name(self) -> str: + if self.selected_terrain.get() == 0: + return "caucasus" + elif self.selected_terrain.get() == 1: + return "nevada" + else: + return "persiangulf" + def display(self): self.window.clear_right_pane() Label(self.frame, text="Player country").grid(row=0, column=0) Radiobutton(self.frame, text="USA", variable=self.selected_country, value=0).grid(row=1, column=0) Radiobutton(self.frame, text="Russia", variable=self.selected_country, value=1).grid(row=2, column=0) - Button(self.frame, text="Proceed", command=self.proceed).grid(row=3, column=0) + + Label(self.frame, text="Terrain").grid(row=0, column=1) + Radiobutton(self.frame, text="Caucasus", variable=self.selected_terrain, value=0).grid(row=1, column=1) + Radiobutton(self.frame, text="Nevada", variable=self.selected_terrain, value=1).grid(row=2, column=1) + Radiobutton(self.frame, text="Persian Gulf", variable=self.selected_terrain, value=2).grid(row=3, column=1) + Button(self.frame, text="Proceed", command=self.proceed).grid(row=4, column=0, columnspan=2) def proceed(self): - self.callback(self.player_country_name, self.enemy_country_name) + self.callback(self.player_country_name, self.enemy_country_name, self.terrain_name) diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index d8a5d131..efef9c59 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -11,14 +11,14 @@ class OverviewCanvas: mainmenu = None # type: ui.mainmenu.MainMenu def __init__(self, frame: Frame, parent, game: Game): - self.canvas = Canvas(frame, width=616, height=350) - self.canvas.grid(column=0, row=0, sticky=NSEW) - self.image = PhotoImage(file="resources/caumap.gif") self.parent = parent - self.game = game - def cp_coordinates(self, cp: ControlPoint) -> (int, int): + 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) + + def transform_point(self, p: Point) -> (int, int): point_a = list(self.game.theater.reference_points.keys())[0] point_a_img = self.game.theater.reference_points[point_a] @@ -35,8 +35,8 @@ class OverviewCanvas: y_scale = float(y_dist) / float(lat_dist) # --- - x_offset = cp.position.x - point_a[0] - y_offset = cp.position.y - point_a[1] + x_offset = p.x - point_a[0] + y_offset = p.y - point_a[1] return point_b_img[1] + y_offset * y_scale, point_a_img[0] - x_offset * x_scale @@ -54,9 +54,9 @@ class OverviewCanvas: self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image) for cp in self.game.theater.controlpoints: - coords = self.cp_coordinates(cp) + coords = self.transform_point(cp.position) for connected_cp in cp.connected_points: - connected_coords = self.cp_coordinates(connected_cp) + connected_coords = self.transform_point(connected_cp.position) if connected_cp.captured != cp.captured: color = "red" elif connected_cp.captured and cp.captured: @@ -67,7 +67,7 @@ class OverviewCanvas: self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color) for cp in self.game.theater.controlpoints: - coords = self.cp_coordinates(cp) + coords = self.transform_point(cp.position) arc_size = 22 * math.pow(cp.importance, 1) extent = max(cp.base.strength * 180, 10) start = (180 - extent) / 2 @@ -79,6 +79,15 @@ class OverviewCanvas: 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)) + """ + self.canvas.tag_bind(cp_id, "", self.display(cp)) self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp)