mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Improved Frontline CAS
This commit is contained in:
parent
3831658162
commit
62f5b2d06d
16
game/db.py
16
game/db.py
@ -432,6 +432,22 @@ def choose_units(for_task: Task, factor: float, count: int, country: str) -> typ
|
|||||||
return list(set(suitable_unittypes[index_start:index_end]))
|
return list(set(suitable_unittypes[index_start:index_end]))
|
||||||
|
|
||||||
|
|
||||||
|
def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int):
|
||||||
|
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def unitdict_split(unit_dict: UnitsDict, count: int):
|
||||||
|
buffer_dict = {}
|
||||||
|
for unit_type, unit_count in unit_dict.items():
|
||||||
|
for _ in range(unit_count):
|
||||||
|
unitdict_append(buffer_dict, unit_type, 1)
|
||||||
|
if sum(buffer_dict.values()) >= count:
|
||||||
|
yield buffer_dict
|
||||||
|
buffer_dict = {}
|
||||||
|
|
||||||
|
if len(buffer_dict):
|
||||||
|
yield buffer_dict
|
||||||
|
|
||||||
def _validate_db():
|
def _validate_db():
|
||||||
# check unit by task uniquity
|
# check unit by task uniquity
|
||||||
total_set = set()
|
total_set = set()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from .event import *
|
from .event import *
|
||||||
from .groundintercept import *
|
from .frontlinecas import *
|
||||||
from .intercept import *
|
from .intercept import *
|
||||||
from .capture import *
|
from .capture import *
|
||||||
from .navalintercept import *
|
from .navalintercept import *
|
||||||
|
|||||||
91
game/event/frontlinecas.py
Normal file
91
game/event/frontlinecas.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
|
from dcs.task import *
|
||||||
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
|
from game import *
|
||||||
|
from game.event import *
|
||||||
|
from game.operation.frontlinecas import FrontlineCASOperation
|
||||||
|
from userdata.debriefing import Debriefing
|
||||||
|
|
||||||
|
|
||||||
|
class FrontlineCASEvent(Event):
|
||||||
|
TARGET_VARIETY = 2
|
||||||
|
TARGET_AMOUNT_FACTOR = 0.5
|
||||||
|
ATTACKER_AMOUNT_FACTOR = 0.4
|
||||||
|
STRENGTH_INFLUENCE = 0.3
|
||||||
|
SUCCESS_MIN_TARGETS = 3
|
||||||
|
|
||||||
|
targets = None # type: db.ArmorDict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threat_description(self):
|
||||||
|
if not self.game.is_player_attack(self):
|
||||||
|
return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS))
|
||||||
|
else:
|
||||||
|
return super(FrontlineCASEvent, self).threat_description
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Frontline CAS from {} at {}".format(self.from_cp, self.to_cp)
|
||||||
|
|
||||||
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
|
total_targets = sum(self.targets.values())
|
||||||
|
destroyed_targets = 0
|
||||||
|
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
||||||
|
if unit in self.targets:
|
||||||
|
destroyed_targets += count
|
||||||
|
|
||||||
|
if self.from_cp.captured:
|
||||||
|
return float(destroyed_targets) >= min(self.SUCCESS_MIN_TARGETS, total_targets)
|
||||||
|
else:
|
||||||
|
return float(destroyed_targets) < min(self.SUCCESS_MIN_TARGETS, total_targets)
|
||||||
|
|
||||||
|
def commit(self, debriefing: Debriefing):
|
||||||
|
super(FrontlineCASEvent, self).commit(debriefing)
|
||||||
|
|
||||||
|
if self.from_cp.captured:
|
||||||
|
if self.is_successfull(debriefing):
|
||||||
|
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
else:
|
||||||
|
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
|
||||||
|
else:
|
||||||
|
if self.is_successfull(debriefing):
|
||||||
|
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
else:
|
||||||
|
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
|
if self.to_cp.captured:
|
||||||
|
self.to_cp.base.affect_strength(-0.1)
|
||||||
|
|
||||||
|
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
||||||
|
suitable_armor_targets = db.find_unittype(PinpointStrike, self.defender_name)
|
||||||
|
random.shuffle(suitable_armor_targets)
|
||||||
|
|
||||||
|
target_types = suitable_armor_targets[:self.TARGET_VARIETY]
|
||||||
|
typecount = max(math.floor(self.to_cp.base.assemble_count() * self.TARGET_AMOUNT_FACTOR), 1)
|
||||||
|
self.targets = {unittype: typecount for unittype in target_types}
|
||||||
|
|
||||||
|
defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence))
|
||||||
|
self.targets[defense_aa_unit] = 1
|
||||||
|
|
||||||
|
suitable_armor_attackers = db.find_unittype(PinpointStrike, self.attacker_name)
|
||||||
|
random.shuffle(suitable_armor_attackers)
|
||||||
|
attacker_types = suitable_armor_attackers[:self.TARGET_VARIETY]
|
||||||
|
typecount = max(math.floor(self.from_cp.base.assemble_count() * self.ATTACKER_AMOUNT_FACTOR), 1)
|
||||||
|
attackers = {unittype: typecount for unittype in attacker_types}
|
||||||
|
|
||||||
|
op = FrontlineCASOperation(game=self.game,
|
||||||
|
attacker_name=self.attacker_name,
|
||||||
|
defender_name=self.defender_name,
|
||||||
|
attacker_clients=clients,
|
||||||
|
defender_clients={},
|
||||||
|
from_cp=self.from_cp,
|
||||||
|
to_cp=self.to_cp)
|
||||||
|
op.setup(target=self.targets,
|
||||||
|
attackers=attackers,
|
||||||
|
strikegroup=strikegroup)
|
||||||
|
|
||||||
|
self.operation = op
|
||||||
|
|
||||||
@ -5,11 +5,11 @@ from dcs.task import *
|
|||||||
|
|
||||||
from game import *
|
from game import *
|
||||||
from game.event import *
|
from game.event import *
|
||||||
from game.event.groundintercept import GroundInterceptEvent
|
from game.event.frontlinecas import FrontlineCASEvent
|
||||||
from game.operation.groundattack import GroundAttackOperation
|
from game.operation.groundattack import GroundAttackOperation
|
||||||
|
|
||||||
|
|
||||||
class GroundAttackEvent(GroundInterceptEvent):
|
class GroundAttackEvent(FrontlineCASEvent):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Destroy insurgents at {}".format(self.to_cp)
|
return "Destroy insurgents at {}".format(self.to_cp)
|
||||||
|
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import AirDefence
|
|
||||||
|
|
||||||
from game import *
|
|
||||||
from game.event import *
|
|
||||||
from game.operation.groundintercept import GroundInterceptOperation
|
|
||||||
from userdata.debriefing import Debriefing
|
|
||||||
|
|
||||||
|
|
||||||
class GroundInterceptEvent(Event):
|
|
||||||
TARGET_AMOUNT_FACTOR = 2
|
|
||||||
TARGET_VARIETY = 2
|
|
||||||
STRENGTH_INFLUENCE = 0.3
|
|
||||||
SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5
|
|
||||||
|
|
||||||
targets = None # type: db.ArmorDict
|
|
||||||
|
|
||||||
@property
|
|
||||||
def threat_description(self):
|
|
||||||
if not self.game.is_player_attack(self):
|
|
||||||
return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS))
|
|
||||||
else:
|
|
||||||
return super(GroundInterceptEvent, self).threat_description
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Frontline CAS from {} at {}".format(self.from_cp, self.to_cp)
|
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
|
||||||
total_targets = sum(self.targets.values())
|
|
||||||
destroyed_targets = 0
|
|
||||||
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
|
||||||
if unit in self.targets:
|
|
||||||
destroyed_targets += count
|
|
||||||
|
|
||||||
if self.from_cp.captured:
|
|
||||||
return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
else:
|
|
||||||
return float(destroyed_targets) / total_targets < self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
|
|
||||||
def commit(self, debriefing: Debriefing):
|
|
||||||
super(GroundInterceptEvent, self).commit(debriefing)
|
|
||||||
|
|
||||||
if self.from_cp.captured:
|
|
||||||
if self.is_successfull(debriefing):
|
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
if self.is_successfull(debriefing):
|
|
||||||
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
if self.to_cp.captured:
|
|
||||||
self.to_cp.base.affect_strength(-0.1)
|
|
||||||
|
|
||||||
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
|
||||||
suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_name)
|
|
||||||
random.shuffle(suitable_unittypes)
|
|
||||||
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
|
|
||||||
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
|
|
||||||
self.targets = {unittype: typecount for unittype in unittypes}
|
|
||||||
|
|
||||||
defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence))
|
|
||||||
self.targets[defense_aa_unit] = 1
|
|
||||||
|
|
||||||
op = GroundInterceptOperation(game=self.game,
|
|
||||||
attacker_name=self.attacker_name,
|
|
||||||
defender_name=self.defender_name,
|
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
to_cp=self.to_cp)
|
|
||||||
op.setup(target=self.targets,
|
|
||||||
strikegroup=strikegroup,
|
|
||||||
interceptors={})
|
|
||||||
|
|
||||||
self.operation = op
|
|
||||||
|
|
||||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
|
||||||
suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_name)
|
|
||||||
random.shuffle(suitable_unittypes)
|
|
||||||
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
|
|
||||||
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
|
|
||||||
self.targets = {unittype: typecount for unittype in unittypes}
|
|
||||||
|
|
||||||
op = GroundInterceptOperation(
|
|
||||||
self.game,
|
|
||||||
attacker_name=self.attacker_name,
|
|
||||||
defender_name=self.defender_name,
|
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
to_cp=self.to_cp
|
|
||||||
)
|
|
||||||
|
|
||||||
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
|
||||||
op.setup(target=self.targets,
|
|
||||||
strikegroup=strikegroup,
|
|
||||||
interceptors=interceptors)
|
|
||||||
|
|
||||||
self.operation = op
|
|
||||||
@ -39,7 +39,7 @@ For the enemy events, only 1 event of each type could be generated for a turn.
|
|||||||
Events:
|
Events:
|
||||||
* CaptureEvent - capture base
|
* CaptureEvent - capture base
|
||||||
* InterceptEvent - air intercept
|
* InterceptEvent - air intercept
|
||||||
* GroundInterceptEvent - frontline CAS
|
* FrontlineCASEvent - frontline CAS
|
||||||
* GroundAttackEvent - destroy insurgents
|
* GroundAttackEvent - destroy insurgents
|
||||||
* NavalInterceptEvent - naval intercept
|
* NavalInterceptEvent - naval intercept
|
||||||
* AntiAAStrikeEvent - anti-AA strike
|
* AntiAAStrikeEvent - anti-AA strike
|
||||||
@ -48,7 +48,7 @@ Events:
|
|||||||
EVENT_PROBABILITIES = {
|
EVENT_PROBABILITIES = {
|
||||||
CaptureEvent: [100, 10],
|
CaptureEvent: [100, 10],
|
||||||
InterceptEvent: [25, 10],
|
InterceptEvent: [25, 10],
|
||||||
GroundInterceptEvent: [25, 10],
|
FrontlineCASEvent: [250, 0],
|
||||||
GroundAttackEvent: [0, 10],
|
GroundAttackEvent: [0, 10],
|
||||||
NavalInterceptEvent: [25, 10],
|
NavalInterceptEvent: [25, 10],
|
||||||
AntiAAStrikeEvent: [25, 10],
|
AntiAAStrikeEvent: [25, 10],
|
||||||
@ -212,7 +212,8 @@ class Game:
|
|||||||
|
|
||||||
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None):
|
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None):
|
||||||
for event in self.events:
|
for event in self.events:
|
||||||
event.skip()
|
if isinstance(event, UnitsDeliveryEvent):
|
||||||
|
event.skip()
|
||||||
|
|
||||||
if not no_action:
|
if not no_action:
|
||||||
self._budget_player()
|
self._budget_player()
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from itertools import zip_longest
|
||||||
|
|
||||||
from dcs.terrain import Terrain
|
from dcs.terrain import Terrain
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
@ -13,26 +15,29 @@ from gen.conflictgen import Conflict
|
|||||||
from .operation import Operation
|
from .operation import Operation
|
||||||
|
|
||||||
|
|
||||||
class GroundInterceptOperation(Operation):
|
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||||
|
|
||||||
|
|
||||||
|
class FrontlineCASOperation(Operation):
|
||||||
|
attackers = None # type: db.ArmorDict
|
||||||
strikegroup = None # type: db.PlaneDict
|
strikegroup = None # type: db.PlaneDict
|
||||||
interceptors = None # type: db.PlaneDict
|
|
||||||
target = None # type: db.ArmorDict
|
target = None # type: db.ArmorDict
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
target: db.ArmorDict,
|
target: db.ArmorDict,
|
||||||
strikegroup: db.PlaneDict,
|
attackers: db.ArmorDict,
|
||||||
interceptors: db.PlaneDict):
|
strikegroup: db.PlaneDict):
|
||||||
self.strikegroup = strikegroup
|
self.strikegroup = strikegroup
|
||||||
self.interceptors = interceptors
|
|
||||||
self.target = target
|
self.target = target
|
||||||
|
self.attackers = attackers
|
||||||
|
|
||||||
def prepare(self, terrain: Terrain, is_quick: bool):
|
def prepare(self, terrain: Terrain, is_quick: bool):
|
||||||
super(GroundInterceptOperation, self).prepare(terrain, is_quick)
|
super(FrontlineCASOperation, self).prepare(terrain, is_quick)
|
||||||
if self.defender_name == self.game.player:
|
if self.defender_name == self.game.player:
|
||||||
self.attackers_starting_position = None
|
self.attackers_starting_position = None
|
||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.ground_intercept_conflict(
|
conflict = Conflict.frontline_cas_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
@ -44,10 +49,6 @@ class GroundInterceptOperation(Operation):
|
|||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
self.armorgen.generate_vec(self.attackers, self.target)
|
||||||
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
|
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
|
||||||
|
super(FrontlineCASOperation, self).generate()
|
||||||
if self.interceptors:
|
|
||||||
self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position)
|
|
||||||
|
|
||||||
self.armorgen.generate({}, self.target)
|
|
||||||
super(GroundInterceptOperation, self).generate()
|
|
||||||
@ -242,6 +242,9 @@ class AircraftConflictGenerator:
|
|||||||
at=at and at or self._group_point(self.conflict.air_attackers_location))
|
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 = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
|
||||||
|
if self.conflict.is_vector:
|
||||||
|
group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE)
|
||||||
|
|
||||||
group.task = CAS.name
|
group.task = CAS.name
|
||||||
self._setup_group(group, CAS, client_count)
|
self._setup_group(group, CAS, client_count)
|
||||||
self.escort_targets.append((group, group.points.index(waypoint)))
|
self.escort_targets.append((group, group.points.index(waypoint)))
|
||||||
@ -341,6 +344,10 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
|
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
|
||||||
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
||||||
|
|
||||||
|
if self.conflict.is_vector:
|
||||||
|
group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE)
|
||||||
|
|
||||||
self._setup_group(group, CAP, client_count)
|
self._setup_group(group, CAP, client_count)
|
||||||
self._rtb_for(group, self.conflict.from_cp, at)
|
self._rtb_for(group, self.conflict.from_cp, at)
|
||||||
|
|
||||||
|
|||||||
59
gen/armor.py
59
gen/armor.py
@ -1,3 +1,5 @@
|
|||||||
|
from itertools import zip_longest
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from .conflictgen import *
|
from .conflictgen import *
|
||||||
from .naming import *
|
from .naming import *
|
||||||
@ -25,7 +27,7 @@ class ArmorConflictGenerator:
|
|||||||
|
|
||||||
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR)
|
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR)
|
||||||
|
|
||||||
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point):
|
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None):
|
||||||
for c in range(count):
|
for c in range(count):
|
||||||
group = self.m.vehicle_group(
|
group = self.m.vehicle_group(
|
||||||
side,
|
side,
|
||||||
@ -34,24 +36,59 @@ class ArmorConflictGenerator:
|
|||||||
position=self._group_point(at),
|
position=self._group_point(at),
|
||||||
group_size=1,
|
group_size=1,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad)
|
||||||
initial_position = self.conflict.position.point_from_heading(0, 500)
|
|
||||||
wayp = group.add_waypoint(self._group_point(initial_position))
|
if not to:
|
||||||
|
to = self.conflict.position.point_from_heading(0, 500)
|
||||||
|
|
||||||
|
wayp = group.add_waypoint(self._group_point(to))
|
||||||
wayp.tasks = []
|
wayp.tasks = []
|
||||||
|
|
||||||
|
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
|
||||||
|
if attackers:
|
||||||
|
for type, count in attackers.items():
|
||||||
|
self._generate_group(
|
||||||
|
side=self.conflict.attackers_side,
|
||||||
|
unit=type,
|
||||||
|
count=count,
|
||||||
|
at=position.point_from_heading(self.conflict.heading - 90, 600),
|
||||||
|
to=position)
|
||||||
|
|
||||||
|
if defenders:
|
||||||
|
for type, count in defenders.items():
|
||||||
|
self._generate_group(
|
||||||
|
side=self.conflict.defenders_side,
|
||||||
|
unit=type,
|
||||||
|
count=count,
|
||||||
|
at=position.point_from_heading(self.conflict.heading + 90, 600),
|
||||||
|
to=position)
|
||||||
|
|
||||||
def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict):
|
def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict):
|
||||||
for type, count in attackers.items():
|
for type, count in attackers.items():
|
||||||
self._generate_group(
|
self._generate_group(
|
||||||
side=self.conflict.attackers_side,
|
side=self.conflict.attackers_side,
|
||||||
unit=type,
|
unit=type,
|
||||||
count=count,
|
count=count,
|
||||||
at=self.conflict.ground_attackers_location)
|
at=self.conflict.ground_attackers_location)
|
||||||
|
|
||||||
for type, count in defenders.items():
|
for type, count in defenders.items():
|
||||||
self._generate_group(
|
self._generate_group(
|
||||||
side=self.conflict.defenders_side,
|
side=self.conflict.defenders_side,
|
||||||
unit=type,
|
unit=type,
|
||||||
count=count,
|
count=count,
|
||||||
at=self.conflict.ground_defenders_location)
|
at=self.conflict.ground_defenders_location)
|
||||||
|
|
||||||
|
def generate_vec(self, attackers: db.ArmorDict, defenders: db.ArmorDict):
|
||||||
|
defender_groups = list(db.unitdict_split(defenders, 6))
|
||||||
|
distance_between_groups = min(self.conflict.distance / len(defender_groups), 12000)
|
||||||
|
total_distance = distance_between_groups * len(defender_groups)
|
||||||
|
|
||||||
|
attacker_groups = list(db.unitdict_split(attackers,
|
||||||
|
int(sum(attackers.values()) / len(defender_groups))))
|
||||||
|
|
||||||
|
position = self.conflict.center.point_from_heading(self.conflict.opposite_heading, total_distance / 2)
|
||||||
|
for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups):
|
||||||
|
position = position.point_from_heading(self.conflict.heading, distance_between_groups)
|
||||||
|
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
|
||||||
|
|
||||||
def generate_passengers(self, count: int):
|
def generate_passengers(self, count: int):
|
||||||
unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name))
|
unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name))
|
||||||
|
|||||||
@ -65,28 +65,35 @@ class Conflict:
|
|||||||
size = None # type: int
|
size = None # type: int
|
||||||
radials = None # type: typing.List[int]
|
radials = None # type: typing.List[int]
|
||||||
|
|
||||||
|
heading = None # type: int
|
||||||
|
distance = None # type: int
|
||||||
|
|
||||||
ground_attackers_location = None # type: Point
|
ground_attackers_location = None # type: Point
|
||||||
ground_defenders_location = None # type: Point
|
ground_defenders_location = None # type: Point
|
||||||
air_attackers_location = None # type: Point
|
air_attackers_location = None # type: Point
|
||||||
air_defenders_location = None # type: Point
|
air_defenders_location = None # type: Point
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
position: Point,
|
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
to_cp: ControlPoint,
|
to_cp: ControlPoint,
|
||||||
attackers_side: Country,
|
attackers_side: Country,
|
||||||
defenders_side: Country,
|
defenders_side: Country,
|
||||||
ground_attackers_location: Point,
|
position: Point,
|
||||||
ground_defenders_location: Point,
|
heading=None,
|
||||||
air_attackers_location: Point,
|
distance=None,
|
||||||
air_defenders_location: Point):
|
ground_attackers_location: Point = None,
|
||||||
|
ground_defenders_location: Point = None,
|
||||||
|
air_attackers_location: Point = None,
|
||||||
|
air_defenders_location: Point = None):
|
||||||
self.attackers_side = attackers_side
|
self.attackers_side = attackers_side
|
||||||
self.defenders_side = defenders_side
|
self.defenders_side = defenders_side
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.position = position
|
self.position = position
|
||||||
|
self.heading = heading
|
||||||
|
self.distance = distance
|
||||||
self.size = to_cp.size
|
self.size = to_cp.size
|
||||||
self.radials = to_cp.radials
|
self.radials = to_cp.radials
|
||||||
self.ground_attackers_location = ground_attackers_location
|
self.ground_attackers_location = ground_attackers_location
|
||||||
@ -94,15 +101,57 @@ class Conflict:
|
|||||||
self.air_attackers_location = air_attackers_location
|
self.air_attackers_location = air_attackers_location
|
||||||
self.air_defenders_location = air_defenders_location
|
self.air_defenders_location = air_defenders_location
|
||||||
|
|
||||||
|
@property
|
||||||
|
def center(self) -> Point:
|
||||||
|
return self.position.point_from_heading(self.heading, self.distance / 2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tail(self) -> Point:
|
||||||
|
return self.position.point_from_heading(self.heading, self.distance)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_vector(self) -> bool:
|
||||||
|
return self.heading is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opposite_heading(self) -> int:
|
||||||
|
return _heading_sum(self.heading, 180)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def to_size(self):
|
def to_size(self):
|
||||||
return self.to_cp.size * GROUND_DISTANCE_FACTOR
|
return self.to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint):
|
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
||||||
|
return from_cp.has_frontline and to_cp.has_frontline
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]:
|
||||||
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONT_SMOKE_DISTANCE_FACTOR * to_cp.base.strength, FRONT_SMOKE_MIN_DISTANCE)
|
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONT_SMOKE_DISTANCE_FACTOR * to_cp.base.strength, FRONT_SMOKE_MIN_DISTANCE)
|
||||||
heading = to_cp.position.heading_between_point(from_cp.position)
|
heading = to_cp.position.heading_between_point(from_cp.position)
|
||||||
return to_cp.position.point_from_heading(heading, distance)
|
return to_cp.position.point_from_heading(heading, distance), heading
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Tuple[Point, int, int]:
|
||||||
|
center_position, heading = cls.frontline_position(from_cp, to_cp)
|
||||||
|
|
||||||
|
left_position = center_position
|
||||||
|
for offset in range(0, 80000, 1000):
|
||||||
|
pos = center_position.point_from_heading(_heading_sum(heading, -90), offset)
|
||||||
|
if not theater.is_on_land(pos):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
left_position = pos
|
||||||
|
|
||||||
|
right_position = center_position
|
||||||
|
for offset in range(0, 80000, 1000):
|
||||||
|
pos = center_position.point_from_heading(_heading_sum(heading, 90), offset)
|
||||||
|
if not theater.is_on_land(pos):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
right_position = pos
|
||||||
|
|
||||||
|
return left_position, _heading_sum(heading, 90), right_position.distance_to_point(left_position)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||||
@ -189,25 +238,44 @@ class Conflict:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ground_intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def frontline_cas_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
heading = to_cp.position.heading_between_point(from_cp.position)
|
assert cls.has_frontline_between(from_cp, to_cp)
|
||||||
initial_location = cls.frontline_position(from_cp, to_cp).random_point_within(GROUND_INTERCEPT_SPREAD)
|
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||||
position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, heading, theater)
|
|
||||||
if not position:
|
|
||||||
heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
|
|
||||||
position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
|
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
position=position,
|
position=position,
|
||||||
|
heading=heading,
|
||||||
|
distance=distance,
|
||||||
theater=theater,
|
theater=theater,
|
||||||
from_cp=from_cp,
|
from_cp=from_cp,
|
||||||
to_cp=to_cp,
|
to_cp=to_cp,
|
||||||
attackers_side=attacker,
|
attackers_side=attacker,
|
||||||
defenders_side=defender,
|
defenders_side=defender,
|
||||||
ground_attackers_location=None,
|
ground_attackers_location=None,
|
||||||
ground_defenders_location=position,
|
ground_defenders_location=None,
|
||||||
air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE),
|
air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE),
|
||||||
air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE)
|
air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def frontline_cap_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
|
assert cls.has_frontline_between(from_cp, to_cp)
|
||||||
|
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||||
|
defenders_distance = random.randint(distance/3, distance)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
position=position,
|
||||||
|
heading=heading,
|
||||||
|
distance=distance,
|
||||||
|
theater=theater,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp,
|
||||||
|
attackers_side=attacker,
|
||||||
|
defenders_side=defender,
|
||||||
|
ground_attackers_location=None,
|
||||||
|
ground_defenders_location=None,
|
||||||
|
air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE),
|
||||||
|
air_defenders_location=position.point_from_heading(heading, max(AIR_DISTANCE, defenders_distance)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -261,8 +329,7 @@ class Conflict:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
frontline_position = cls.frontline_position(from_cp, to_cp)
|
frontline_position, heading = cls.frontline_position(from_cp, to_cp)
|
||||||
heading = to_cp.position.heading_between_point(from_cp.position)
|
|
||||||
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
|
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
|
||||||
dest = cls._find_ground_location(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
|
dest = cls._find_ground_location(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
|
||||||
if not dest:
|
if not dest:
|
||||||
|
|||||||
@ -99,8 +99,7 @@ class VisualGenerator:
|
|||||||
|
|
||||||
def _generate_frontline_smokes(self):
|
def _generate_frontline_smokes(self):
|
||||||
for from_cp, to_cp in self.game.theater.conflicts():
|
for from_cp, to_cp in self.game.theater.conflicts():
|
||||||
heading = to_cp.position.heading_between_point(from_cp.position)
|
point, heading = Conflict.frontline_position(from_cp, to_cp)
|
||||||
point = Conflict.frontline_position(from_cp, to_cp)
|
|
||||||
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONT_SMOKE_LENGTH / 2)
|
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONT_SMOKE_LENGTH / 2)
|
||||||
|
|
||||||
for offset in range(0, FRONT_SMOKE_LENGTH, FRONT_SMOKE_SPACING):
|
for offset in range(0, FRONT_SMOKE_LENGTH, FRONT_SMOKE_SPACING):
|
||||||
|
|||||||
@ -10,10 +10,11 @@ class ControlPoint:
|
|||||||
connected_points = [] # type: typing.List[ControlPoint]
|
connected_points = [] # type: typing.List[ControlPoint]
|
||||||
position = None # type: Point
|
position = None # type: Point
|
||||||
captured = False
|
captured = False
|
||||||
base: None # type: theater.base.Base
|
has_frontline = True
|
||||||
at: None # type: db.StartPosition
|
base = None # type: theater.base.Base
|
||||||
|
at = None # type: db.StartPosition
|
||||||
|
|
||||||
def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int):
|
def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
|
||||||
import theater.base
|
import theater.base
|
||||||
|
|
||||||
self.name = " ".join(re.split(r" |-", name)[:2])
|
self.name = " ".join(re.split(r" |-", name)[:2])
|
||||||
@ -24,14 +25,15 @@ class ControlPoint:
|
|||||||
self.size = size
|
self.size = size
|
||||||
self.importance = importance
|
self.importance = importance
|
||||||
self.captured = False
|
self.captured = False
|
||||||
|
self.has_frontline = has_frontline
|
||||||
self.radials = radials
|
self.radials = radials
|
||||||
self.connected_points = []
|
self.connected_points = []
|
||||||
self.base = theater.base.Base()
|
self.base = theater.base.Base()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int):
|
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
|
||||||
assert airport
|
assert airport
|
||||||
return cls(airport.name, airport.position, airport, radials, size, importance)
|
return cls(airport.name, airport.position, airport, radials, size, importance, has_frontline)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def carrier(cls, name: str, at: Point):
|
def carrier(cls, name: str, at: Point):
|
||||||
|
|||||||
@ -29,13 +29,13 @@ class PersianGulfTheater(ConflictTheater):
|
|||||||
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.3)
|
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.3)
|
||||||
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, 1.3)
|
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, 1.3)
|
||||||
|
|
||||||
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, 1.2)
|
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, 1.2, has_frontline=False)
|
||||||
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, 1.0)
|
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, 1.0, has_frontline=False)
|
||||||
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_REGULAR, 1.1)
|
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_REGULAR, 1.1, has_frontline=False)
|
||||||
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, IMPORTANCE_HIGH)
|
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, IMPORTANCE_HIGH, has_frontline=False)
|
||||||
|
|
||||||
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, IMPORTANCE_HIGH)
|
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, IMPORTANCE_HIGH)
|
||||||
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1)
|
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1, has_frontline=False)
|
||||||
|
|
||||||
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_HIGH)
|
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_HIGH)
|
||||||
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
||||||
|
|||||||
@ -190,8 +190,8 @@ class EventMenu(Menu):
|
|||||||
else:
|
else:
|
||||||
e.player_defending(escort=scrambled_aircraft,
|
e.player_defending(escort=scrambled_aircraft,
|
||||||
clients=scrambled_clients)
|
clients=scrambled_clients)
|
||||||
elif type(self.event) is GroundInterceptEvent:
|
elif type(self.event) is FrontlineCASEvent:
|
||||||
e = self.event # type: GroundInterceptEvent
|
e = self.event # type: FrontlineCASEvent
|
||||||
if self.game.is_player_attack(self.event):
|
if self.game.is_player_attack(self.event):
|
||||||
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user