diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..90a3258f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/dcs"] + path = submodules/dcs + url = https://github.com/pydcs/dcs diff --git a/.idea/dcs_pmcliberation.iml b/.idea/dcs_pmcliberation.iml index 9eedabcf..1f377c84 100644 --- a/.idea/dcs_pmcliberation.iml +++ b/.idea/dcs_pmcliberation.iml @@ -4,7 +4,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index e524f659..65531ca9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/dcs b/dcs new file mode 120000 index 00000000..f77acded --- /dev/null +++ b/dcs @@ -0,0 +1 @@ +submodules/dcs/dcs \ No newline at end of file diff --git a/game/event.py b/game/event.py index cb23bbf0..e90ae8af 100644 --- a/game/event.py +++ b/game/event.py @@ -83,6 +83,7 @@ class GroundInterceptEvent(Event): defender=self.defender, attacker_clients=clients, defender_clients={}, + attacker_airport=self.from_cp.at, position=position, target=targets, strikegroup=strikegroup) @@ -92,6 +93,7 @@ class InterceptEvent(Event): ESCORT_AMOUNT_FACTOR = 2 BONUS_BASE = 5 STRENGTH_INFLUENCE = 0.25 + AIRDEFENSE_COUNT = 3 def __str__(self): return "Intercept at {} ({})".format(self.to_cp, "*" * self.difficulty) @@ -112,15 +114,19 @@ class InterceptEvent(Event): transport_unit = random.choice(db.find_unittype(Transport, self.defender.name)) assert transport_unit is not None + airdefense_unit = db.find_unittype(AirDefence, self.defender.name)[0] + self.operation = InterceptOperation(mission=self.mission, attacker=self.attacker, defender=self.defender, attacker_clients=clients, defender_clients={}, + attacker_airport=self.from_cp.at, destination=self.to_cp, - destination_port=self.to_cp.airport, + destination_port=self.to_cp.at, escort=escort, transport={transport_unit: 1}, + airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, interceptors=interceptors) def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict): @@ -133,11 +139,13 @@ class InterceptEvent(Event): defender=self.defender, attacker_clients={}, defender_clients=clients, + attacker_airport=None, destination=self.to_cp, - destination_port=self.to_cp.airport, + destination_port=self.to_cp.at, escort=escort, transport={transport_unit: 1}, - interceptors=interceptors) + interceptors=interceptors, + airdefense={}) class CaptureEvent(Event): @@ -172,6 +180,7 @@ class CaptureEvent(Event): defender=self.defender, attacker_clients={}, defender_clients=clients, + attacker_airport=None, from_cp=self.from_cp, to_cp=self.to_cp, cas=cas, @@ -189,6 +198,7 @@ class CaptureEvent(Event): defender=self.defender, attacker_clients=clients, defender_clients={}, + attacker_airport=self.from_cp.at, from_cp=self.from_cp, to_cp=self.to_cp, cas=cas, diff --git a/game/game.py b/game/game.py index f4e24584..1cfcebe9 100644 --- a/game/game.py +++ b/game/game.py @@ -23,8 +23,8 @@ COMMISION_AMOUNTS_FACTORS = { } -ENEMY_INTERCEPT_PROBABILITY_BASE = 25 -ENEMY_CAPTURE_PROBABILITY_BASE = 15 +ENEMY_INTERCEPT_PROBABILITY_BASE = 15 +ENEMY_CAPTURE_PROBABILITY_BASE = 5 PLAYER_INTERCEPT_PROBABILITY_BASE = 30 PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 30 @@ -49,10 +49,11 @@ class Game: def _fill_cap_events(self): for from_cp, to_cp in self.theater.conflicts(True): - self.events.append(CaptureEvent(attacker_name=self.player, - defender_name=self.enemy, - from_cp=from_cp, - to_cp=to_cp)) + if to_cp not in [x.to_cp for x in self.events]: + self.events.append(CaptureEvent(attacker_name=self.player, + defender_name=self.enemy, + from_cp=from_cp, + to_cp=to_cp)) def _generate_enemy_caps(self): for from_cp, to_cp in self.theater.conflicts(False): @@ -106,12 +107,17 @@ class Game: unit_type = random.choice(db.find_unittype(for_task, self.enemy)) cp.base.commision_units({unit_type: points_to_spend}) - def _budget_player(self): + @property + def budget_reward_amount(self): if len(self.theater.player_points()) > 0: total_importance = sum([x.importance for x in self.theater.player_points()]) total_strength = sum([x.base.strength for x in self.theater.player_points()]) / len(self.theater.player_points()) + return math.ceil(math.log(total_importance * total_strength + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE) + else: + return 0 - self.budget += math.ceil(math.log(total_importance * total_strength + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE) + def _budget_player(self): + self.budget += self.budget_reward_amount def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent: event = UnitsDeliveryEvent(attacker_name=self.player, @@ -121,6 +127,9 @@ class Game: self.events.append(event) return event + def units_delivery_remove(self, event: Event): + self.events.remove(event) + def initiate_event(self, event: Event): event.operation.generate() event.mission.save("build/next_mission.miz") diff --git a/game/operation.py b/game/operation.py index 23232449..b2593ada 100644 --- a/game/operation.py +++ b/game/operation.py @@ -13,6 +13,7 @@ from shop import * from gen.armor import * from gen.aircraft import * from gen.aaa import * +from gen.shipgen import * from gen.conflictgen import * @@ -23,6 +24,7 @@ class Operation: self.armorgen = ArmorConflictGenerator(self.mission, self.conflict) self.airgen = AircraftConflictGenerator(self.mission, self.conflict) self.aagen = AAConflictGenerator(self.mission, self.conflict) + self.shipgen = ShipGenerator(self.mission, self.conflict) def units_of(self, country_name: str) -> typing.Collection[UnitType]: return [] @@ -41,6 +43,7 @@ class CaptureOperation(Operation): defender: Country, attacker_clients: db.PlaneDict, defender_clients: db.PlaneDict, + attacker_airport: typing.Optional[Airport], from_cp: ControlPoint, to_cp: ControlPoint, cas: db.PlaneDict, @@ -48,7 +51,7 @@ class CaptureOperation(Operation): attack: db.ArmorDict, intercept: db.PlaneDict, defense: db.ArmorDict, - aa: db.AADict): + aa: db.AirDefenseDict): conflict = to_cp.conflict_attack(from_cp, attacker, defender) super(CaptureOperation, self).__init__(mission, conflict) @@ -56,6 +59,7 @@ class CaptureOperation(Operation): self.to_cp = to_cp self.attacker_clients = attacker_clients self.defender_clients = defender_clients + self.attacker_airport = attacker_airport self.cas = cas self.escort = escort self.intercept = intercept @@ -67,10 +71,11 @@ class CaptureOperation(Operation): def generate(self): self.armorgen.generate(self.attack, self.defense) - self.airgen.generate_cas(self.cas, clients=self.attacker_clients) - self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients) - self.airgen.generate_defense(self.intercept, clients=self.defender_clients) self.aagen.generate(self.aa) + self.airgen.generate_defense(self.intercept, clients=self.defender_clients) + + self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.attacker_airport) + self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.attacker_airport) class InterceptOperation(Operation): @@ -80,10 +85,12 @@ class InterceptOperation(Operation): defender: Country, attacker_clients: db.PlaneDict, defender_clients: db.PlaneDict, + attacker_airport: typing.Optional[Airport], destination: ControlPoint, destination_port: Airport, escort: db.PlaneDict, transport: db.PlaneDict, + airdefense: db.AirDefenseDict, interceptors: db.PlaneDict): conflict = Conflict.intercept_conflict( attacker=attacker, @@ -95,16 +102,20 @@ class InterceptOperation(Operation): super(InterceptOperation, self).__init__(mission, conflict) self.destination_port = destination_port + self.attacker_airport = attacker_airport self.attacker_clients = attacker_clients self.defender_clients = defender_clients self.escort = escort self.transport = transport + self.airdefense = airdefense self.interceptors = interceptors def generate(self): self.airgen.generate_transport(self.transport, self.destination_port) self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients) + self.aagen.generate(self.airdefense) + + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, airport=self.attacker_airport) class GroundInterceptOperation(Operation): @@ -114,6 +125,7 @@ class GroundInterceptOperation(Operation): defender: Country, attacker_clients: db.PlaneDict, defender_clients: db.PlaneDict, + attacker_airport: typing.Optional[Airport], position: Point, target: db.ArmorDict, strikegroup: db.PlaneDict): @@ -128,10 +140,10 @@ class GroundInterceptOperation(Operation): super(GroundInterceptOperation, self).__init__(mission, conflict) self.attacker_clients = attacker_clients self.defender_clients = defender_clients + self.attacker_airport = attacker_airport self.strikegroup = strikegroup self.target = target def generate(self): - self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients) + self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.attacker_airport) self.armorgen.generate({}, self.target) - diff --git a/gen/aircraft.py b/gen/aircraft.py index fa0f5b95..8c645b5e 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -45,28 +45,39 @@ class AircraftConflictGenerator: ) return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_FACTOR[0]) - def _generate_group( - self, - name: str, - side: Country, - unit: PlaneType, - count: int, - client_count: int, - at: Point = None, - airport: Airport = None) -> FlyingGroup: - starttype = airport is None and StartType.Warm or StartType.Cold + 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 + + group = self.m.flight_group_from_airport( + country=side, + name=name, + aircraft_type=unit_type, + airport=airport, + maintask=None, + start_type=StartType.Cold, + group_size=count, + parking_slots=None) + + for idx in range(client_count): + group.units[idx].set_client() + + return group + + def _generate_inflight(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: Point) -> FlyingGroup: + assert count > 0 + assert unit is not None group = self.m.flight_group( country=side, name=name, - aircraft_type=unit, - airport=airport, + aircraft_type=unit_type, + airport=None, position=at, altitude=WARM_START_ALTITUDE, speed=WARM_START_AIRSPEED, maintask=None, - start_type=starttype, + start_type=StartType.Warm, group_size=count) for idx in range(client_count): @@ -74,7 +85,35 @@ class AircraftConflictGenerator: return group - def _generate_escort(self, units: db.PlaneDict, clients: db.PlaneDict, airport: Airport, side: Country, location: Point): + def _generate_at_carrier(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: ShipGroup) -> FlyingGroup: + assert count > 0 + assert unit is not None + + group = self.m.flight_group_from_unit( + country=side, + name=name, + aircraft_type=unit_type, + pad_group=at, + maintask=None, + start_type=StartType.Warm, + group_size=count) + + for idx in range(client_count): + group.units[idx].set_client() + + return group + + def _generate_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: db.StartingPosition): + if type(at) == Point: + return self._generate_inflight(name, side, unit_type, count, client_count, at) + elif type(at) == Airport: + return self._generate_at_airport(name, side, unit_type, count, client_count, at) + elif type(at) == ShipGroup: + return self._generate_at_carrier(name, side, unit_type, count, client_count, at) + else: + assert False + + def _generate_escort(self, side: Country, units: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition): if len(self.escort_targets) == 0: return @@ -82,11 +121,10 @@ class AircraftConflictGenerator: group = self._generate_group( name=namegen.next_escort_group_name(), side=side, - unit=type, + unit_type=type, count=count, client_count=clients.get(type, 0), - at=location, - airport=airport) + at=at) group.task = Escort.name group.load_task_default_loadout(dcs.task.Escort) @@ -98,52 +136,46 @@ class AircraftConflictGenerator: for group in self.escort_targets: wayp.tasks.append(EscortTaskAction(group.id, engagement_max_dist=ESCORT_MAX_DIST)) - def generate_cas(self, attackers: db.PlaneDict, clients: db.PlaneDict, airport: Airport = None): + def generate_cas(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): assert len(self.escort_targets) == 0 for type, count in attackers.items(): group = self._generate_group( name=namegen.next_cas_group_name(), side=self.conflict.attackers_side, - unit=type, + unit_type=type, count=count, client_count=clients.get(type, 0), - at=airport is None and self._group_point(self.conflict.air_attackers_location) or None, - airport=airport) + at=at and at or self._group_point(self.conflict.air_attackers_location)) self.escort_targets.append(group) group.add_waypoint(self.conflict.position, CAS_ALTITUDE) group.task = CAS.name group.load_task_default_loadout(CAS) - def generate_cas_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, airport: Airport = None): + def generate_cas_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): self._generate_escort( + side=self.conflict.attackers_side, units=attackers, clients=clients, - airport=airport, - side=self.conflict.attackers_side, - location=airport is None and self._group_point(self.conflict.air_attackers_location) or None - ) + at=at and at or self._group_point(self.conflict.air_attackers_location)) - def generate_transport_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, airport: Airport = None): + def generate_transport_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): self._generate_escort( + side=self.conflict.defenders_side, units=escort, clients=clients, - airport=airport, - side=self.conflict.defenders_side, - location=airport is None and self._group_point(self.conflict.air_defenders_location) or None - ) + at=at and at or self._group_point(self.conflict.air_defenders_location)) - def generate_defense(self, defenders: db.PlaneDict, clients: db.PlaneDict, airport: Airport = None): + def generate_defense(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): for type, count in defenders.items(): group = self._generate_group( - name=namegen.next_intercept_group_name(), - side=self.conflict.defenders_side, - unit=type, - count=count, - client_count=clients.get(type, 0), - at=airport is None and self._group_point(self.conflict.air_defenders_location) or None, - airport=airport) + name=namegen.next_intercept_group_name(), + side=self.conflict.defenders_side, + unit_type=type, + count=count, + client_count=clients.get(type, 0), + at=at and at or self._group_point(self.conflict.air_defenders_location)) group.task = FighterSweep.name group.load_task_default_loadout(FighterSweep) @@ -158,29 +190,25 @@ class AircraftConflictGenerator: group = self._generate_group( name=namegen.next_transport_group_name(), side=self.conflict.defenders_side, - unit=type, + unit_type=type, count=count, client_count=0, - at=self._group_point(self.conflict.air_defenders_location), - airport=None - ) + at=self._group_point(self.conflict.air_defenders_location)) group.task = Transport.name self.escort_targets.append(group) group.land_at(destination) - def generate_interception(self, interceptors: db.PlaneDict, clients: db.PlaneDict, airport: Airport = None): + def generate_interception(self, interceptors: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): for type, count in interceptors.items(): group = self._generate_group( name=namegen.next_intercept_group_name(), side=self.conflict.attackers_side, - unit=type, + unit_type=type, count=count, client_count=clients.get(type, 0), - at=airport is None and self._group_point(self.conflict.air_attackers_location) or None, - airport=airport - ) + at=at and at or self._group_point(self.conflict.air_attackers_location)) group.task = FighterSweep.name group.load_task_default_loadout(FighterSweep) diff --git a/gen/shipgen.py b/gen/shipgen.py new file mode 100644 index 00000000..5268df0e --- /dev/null +++ b/gen/shipgen.py @@ -0,0 +1,32 @@ +import typing +import pdb +import dcs + +from random import randint + +import globals + +from .conflictgen import * +from .naming import * + +from dcs.mission import * +from dcs.vehicles import * +from dcs.unitgroup import * +from dcs.unittype import * +from dcs.mapping import * +from dcs.point import * +from dcs.task import * + + +class ShipGenerator: + def __init__(self, mission: Mission, conflict: Conflict, position: Point): + self.m = mission + self.conflict = conflict + self.position = position + + def generate(self): + self.m.ship_group( + country=self.conflict.attackers_side, + name=namegen.next_transport_group_name(), + _type=dcs.ships.CVN_74_John_C__Stennis, + position=self.position) diff --git a/resources/caumap.gif b/resources/caumap.gif index f3d388f1..2bfe9259 100644 Binary files a/resources/caumap.gif and b/resources/caumap.gif differ diff --git a/shop/db.py b/shop/db.py index f1fc416a..6f3c351f 100644 --- a/shop/db.py +++ b/shop/db.py @@ -17,6 +17,9 @@ PRICES = { A_10A: 18, A_10C: 20, + F_A_18C: 18, + AV8BNA: 15, + Su_27: 30, Su_33: 33, F_15C: 30, @@ -45,7 +48,7 @@ PRICES = { } UNIT_BY_TASK = { - FighterSweep: [Su_27, Su_33, Su_25, F_15C, MiG_15bis, MiG_21Bis, MiG_29A, ], + FighterSweep: [Su_27, Su_33, Su_25, F_15C, MiG_15bis, MiG_21Bis, MiG_29A, F_A_18C, AV8BNA], CAS: [Su_25T, A_10A, A_10C, ], 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, ], @@ -53,14 +56,15 @@ UNIT_BY_TASK = { } UNIT_BY_COUNTRY = { -"Russia": [Su_25T, Su_27, Su_33, Su_25, MiG_15bis, MiG_21Bis, MiG_29A, AirDefence.AAA_ZU_23_on_Ural_375, Armor.APC_BTR_80, Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, IL_76MD, ], - "USA": [F_15C, A_10C, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, S_3B_Tanker], + "Russia": [Su_25T, Su_27, Su_33, Su_25, MiG_15bis, MiG_21Bis, MiG_29A, AirDefence.AAA_ZU_23_on_Ural_375, Armor.APC_BTR_80, Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, IL_76MD, ], + "USA": [F_15C, A_10C, F_A_18C, AV8BNA, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, S_3B_Tanker], } UnitsDict = typing.Dict[UnitType, int] PlaneDict = typing.Dict[PlaneType, int] ArmorDict = typing.Dict[VehicleType, int] -AADict = typing.Dict[AirDefence, int] +AirDefenseDict = typing.Dict[AirDefence, int] +StartingPosition = typing.Optional[typing.Union[ShipGroup, Airport, Point]] def unit_task(unit: UnitType) -> Task: diff --git a/submodules/dcs b/submodules/dcs new file mode 160000 index 00000000..d982dfe5 --- /dev/null +++ b/submodules/dcs @@ -0,0 +1 @@ +Subproject commit d982dfe594e16a45851050e8f63cec03c91aeed9 diff --git a/theater/base.py b/theater/base.py index 7b9f06e7..5964ed72 100644 --- a/theater/base.py +++ b/theater/base.py @@ -1,8 +1,10 @@ import typing -import dcs import math +import random import itertools +import dcs + from shop import db from theater.controlpoint import ControlPoint diff --git a/theater/caucasus.py b/theater/caucasus.py index aaae9885..36c4e32b 100644 --- a/theater/caucasus.py +++ b/theater/caucasus.py @@ -3,24 +3,50 @@ from dcs.terrain import caucasus from .conflicttheater import * from .base import * - class CaucasusTheater(ConflictTheater): - kutaisi = ControlPoint(caucasus.Kutaisi, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) - senaki = ControlPoint(caucasus.Senaki, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_LOW) - kobuleti = ControlPoint(caucasus.Kobuleti, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_LOW) - batumi = ControlPoint(caucasus.Batumi, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_MEDIUM) - sukhumi = ControlPoint(caucasus.Sukhumi, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM) - gudauta = ControlPoint(caucasus.Gudauta, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM) - sochi = ControlPoint(caucasus.Sochi, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH) + soganlug = ControlPoint.from_airport(caucasus.Soganlug, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) + kutaisi = ControlPoint.from_airport(caucasus.Kutaisi, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW) + senaki = ControlPoint.from_airport(caucasus.Senaki_Kolkhi, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_LOW) + kobuleti = ControlPoint.from_airport(caucasus.Kobuleti, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_LOW) + batumi = ControlPoint.from_airport(caucasus.Batumi, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_MEDIUM) + sukhumi = ControlPoint.from_airport(caucasus.Sukhumi_Babushara, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM) + gudauta = ControlPoint.from_airport(caucasus.Gudauta, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM) + sochi = ControlPoint.from_airport(caucasus.Sochi_Adler, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH) + + maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, ALL_RADIALS, SIZE_LARGE, IMPORTANCE_HIGH) + krasnodar = ControlPoint.from_airport(caucasus.Krasnodar_Center, ALL_RADIALS, SIZE_LARGE, IMPORTANCE_HIGH) + novorossiysk = ControlPoint.from_airport(caucasus.Novorossiysk, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH) + gelendzhik = ControlPoint.from_airport(caucasus.Gelendzhik, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH) + krymsk = ControlPoint.from_airport(caucasus.Krymsk, ALL_RADIALS, SIZE_LARGE, IMPORTANCE_HIGH) + anapa = ControlPoint.from_airport(caucasus.Anapa_Vityazevo, ALL_RADIALS, SIZE_LARGE, IMPORTANCE_HIGH) + + beslan = ControlPoint.from_airport(caucasus.Beslan, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_MEDIUM) + nalchik = ControlPoint.from_airport(caucasus.Nalchik, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_MEDIUM) + mineralnye = ControlPoint.from_airport(caucasus.Mineralnye_Vody, ALL_RADIALS, SIZE_BIG, IMPORTANCE_HIGH) + mozdok = ControlPoint.from_airport(caucasus.Mozdok, ALL_RADIALS, SIZE_BIG, IMPORTANCE_HIGH) + + #carrier_1 = ControlPoint() def __init__(self): - self.kutaisi.captured = True + self.soganlug.captured = True - self.add_controlpoint(self.kutaisi, connected_to=[self.senaki]) + self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan]) + self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik]) + self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye]) + self.add_controlpoint(self.mozdok, connected_to=[self.nalchik, self.beslan, self.mineralnye]) + self.add_controlpoint(self.mineralnye, connected_to=[self.nalchik, self.mozdok, self.maykop]) + self.add_controlpoint(self.maykop, connected_to=[self.mineralnye, self.krasnodar]) + + self.add_controlpoint(self.kutaisi, connected_to=[self.soganlug, self.senaki]) self.add_controlpoint(self.senaki, connected_to=[self.kobuleti, self.sukhumi, self.kutaisi]) self.add_controlpoint(self.kobuleti, connected_to=[self.batumi, self.senaki]) self.add_controlpoint(self.batumi, connected_to=[self.kobuleti]) - self.add_controlpoint(self.sukhumi, connected_to=[self.gudauta, self.senaki]) self.add_controlpoint(self.gudauta, connected_to=[self.sochi, self.sukhumi]) - self.add_controlpoint(self.sochi, connected_to=[self.gudauta]) + self.add_controlpoint(self.sochi, connected_to=[self.gudauta, self.gelendzhik]) + + self.add_controlpoint(self.gelendzhik, connected_to=[self.sochi, self.novorossiysk]) + self.add_controlpoint(self.novorossiysk, connected_to=[self.gelendzhik, self.anapa]) + self.add_controlpoint(self.krymsk, connected_to=[self.novorossiysk, self.anapa, self.krasnodar]) + self.add_controlpoint(self.anapa, connected_to=[self.novorossiysk, self.krymsk]) + self.add_controlpoint(self.krasnodar, connected_to=[self.krymsk, self.maykop]) diff --git a/theater/controlpoint.py b/theater/controlpoint.py index e5446497..34fa2248 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -13,14 +13,15 @@ class ControlPoint: position = None # type: Point captured = False base: None # type: theater.base.Base - airport: None # type: Airport + at: None # type: db.StartPosition - def __init__(self, airport: Airport, radials: typing.Collection[int], size: int, importance: int): + def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int): import theater.base - self.name = airport.name - self.position = airport.position - self.airport = airport + self.name = name + self.position = position + self.at = at + self.size = size self.importance = importance self.captured = False @@ -28,6 +29,10 @@ class ControlPoint: self.connected_points = [] self.base = theater.base.Base() + @classmethod + def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int): + return cls(airport.name, airport.position, airport, radials, size, importance) + def __str__(self): return self.name @@ -61,5 +66,3 @@ class ControlPoint: position=self.position, size=self.size, radials=self.radials) - - diff --git a/ui/basemenu.py b/ui/basemenu.py index 678eed3e..39cd5a14 100644 --- a/ui/basemenu.py +++ b/ui/basemenu.py @@ -49,6 +49,12 @@ class BaseMenu(Menu): for unit_type in units: purchase_row(unit_type, db.PRICES[unit_type]) + def dismiss(self): + if sum([x for x in self.event.units.values()]) == 0: + self.game.units_delivery_remove(self.event) + + super(BaseMenu, self).dismiss() + def buy(self, unit_type): def action(): price = db.PRICES[unit_type] diff --git a/ui/mainmenu.py b/ui/mainmenu.py index be03dd02..0a9dddfd 100644 --- a/ui/mainmenu.py +++ b/ui/mainmenu.py @@ -4,23 +4,26 @@ from tkinter.ttk import * from ui.window import * from ui.eventmenu import * from ui.basemenu import * +from ui.overviewcanvas import * from game.game import * class MainMenu(Menu): + basemenu = None # type: BaseMenu + def __init__(self, window: Window, parent, game: Game): super(MainMenu, self).__init__(window, parent, game) - self.image = PhotoImage(file="resources/caumap.gif") - map = Label(window.left_pane, image=self.image) - map.grid() + self.upd = OverviewCanvas(self.window.left_pane, self, game) + self.upd.update() self.frame = self.window.right_pane self.frame.grid_columnconfigure(0, weight=1) def display(self): self.window.clear_right_pane() + self.upd.update() row = 1 @@ -34,23 +37,14 @@ class MainMenu(Menu): Button(self.frame, text=text, command=self.start_event(event)).grid(row=row, sticky=N) row += 1 - def cp_button(cp): - nonlocal row - title = "{}{}{}{}".format( - cp.name, - "^" * cp.base.total_planes, - "." * cp.base.total_armor, - "*" * cp.base.total_aa) - Button(self.frame, text=title, command=self.go_cp(cp)).grid(row=row, sticky=NW) - row += 1 - - Label(self.frame, text="Budget: {}m".format(self.game.budget)).grid(column=0, row=0, sticky=NW) - Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=1, row=0, sticky=NE) - row += 1 + Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=0, row=0, sticky=NE) + Label(self.frame, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount)).grid(column=0, row=0, sticky=NW) + Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1 for event in self.game.events: if not event.informational: continue + label(str(event)) for event in self.game.events: @@ -59,21 +53,6 @@ class MainMenu(Menu): event_button(event, "{} {}".format(event.attacker.name != self.game.player and "!" or " ", event)) - Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1 - for cp in self.game.theater.player_points(): - cp_button(cp) - - Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1 - for cp in self.game.theater.enemy_bases(): - title = "[{}] {}{}{}{}".format( - int(cp.base.strength * 10), - cp.name, - "^" * cp.base.total_planes, - "." * cp.base.total_armor, - "*" * cp.base.total_aa) - Label(self.frame, text=title).grid(row=row, sticky=NE) - row += 1 - def pass_turn(self): self.game.pass_turn(no_action=True) self.display() @@ -81,5 +60,10 @@ class MainMenu(Menu): def start_event(self, event) -> typing.Callable: return lambda: EventMenu(self.window, self, self.game, event).display() - def go_cp(self, cp: ControlPoint) -> typing.Callable: - return lambda: BaseMenu(self.window, self, self.game, cp).display() + def go_cp(self, cp: ControlPoint): + if self.basemenu: + self.basemenu.dismiss() + self.basemenu = None + + self.basemenu = BaseMenu(self.window, self, self.game, cp) + self.basemenu.display() diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py new file mode 100644 index 00000000..b124f046 --- /dev/null +++ b/ui/overviewcanvas.py @@ -0,0 +1,88 @@ +from tkinter import * +from tkinter.ttk import * + +from ui.window import * + +from game.game import * + + +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): + point_a = (-317948.32727306, 635639.37385346) + point_a_img = 282.5, 319 + + point_b = (-355692.3067714, 617269.96285781) + point_b_img = 269, 352 + + x_dist = point_a_img[0] - point_b_img[0] + lon_dist = point_a[1] - point_b[1] + + y_dist = point_a_img[1] - point_b_img[1] + lat_dist = point_b[0] - point_a[0] + + x_scale = float(x_dist) / float(lon_dist) + y_scale = float(y_dist) / float(lat_dist) + + # --- + x_offset = cp.position.x - point_a[0] + y_offset = cp.position.y - point_a[1] + + return point_b_img[1] + y_offset * y_scale, point_a_img[0] - x_offset * x_scale + + def create_cp_title(self, coords, cp: ControlPoint): + title = cp.name + font = ("Helvetica", 13) + + self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font) + self.canvas.create_text(coords[0], coords[1], text=title, font=font) + + def update(self): + self.canvas.delete(ALL) + 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) + for connected_cp in cp.connected_points: + connected_coords = self.cp_coordinates(connected_cp) + if connected_cp.captured != cp.captured: + color = "red" + elif connected_cp.captured and cp.captured: + color = "blue" + else: + color = "black" + + 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) + arc_size = 18 * math.pow(cp.importance, 1) + extent = max(cp.base.strength * 180, 10) + start = (180 - extent) / 2 + color = cp.captured and 'blue' or 'red' + + 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) + self.canvas.tag_bind(cp_id, "", self.display(cp)) + self.create_cp_title((coords[0] + arc_size/2, coords[1] + arc_size/2), cp) + self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text="8/4/2", font=("Helvetica", 10)) + + def display(self, cp: ControlPoint): + def action(_): + return self.parent.go_cp(cp) + + return action +