mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
game loop; player budget; enemy progression; GUI WIP
This commit is contained in:
parent
4cd3c24b49
commit
ad4d183972
2
.idea/dcs_pmcliberation.iml
generated
2
.idea/dcs_pmcliberation.iml
generated
@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.6 (venv)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.6" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="TestRunnerService">
|
<component name="TestRunnerService">
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (venv)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
110
__init__.py
110
__init__.py
@ -5,71 +5,87 @@ import os
|
|||||||
|
|
||||||
import gen
|
import gen
|
||||||
import theater.caucasus
|
import theater.caucasus
|
||||||
import game.mission
|
import game.operation
|
||||||
|
import ui.window
|
||||||
|
import ui.mainmenu
|
||||||
|
|
||||||
|
from game.game import Game
|
||||||
|
from theater.controlpoint import *
|
||||||
|
|
||||||
from dcs.planes import *
|
from dcs.planes import *
|
||||||
from dcs.vehicles import *
|
from dcs.vehicles import *
|
||||||
|
|
||||||
m = dcs.Mission()
|
m = dcs.Mission()
|
||||||
|
|
||||||
theater = theater.caucasus.CaucasusTheater()
|
theater = theater.caucasus.CaucasusTheater()
|
||||||
|
|
||||||
theater.kutaisi.base.aircraft = {
|
theater.kutaisi.base.aircraft = {
|
||||||
A_10C: 4,
|
|
||||||
F_15C: 4,
|
F_15C: 4,
|
||||||
|
A_10C: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
theater.kutaisi.base.armor = {
|
g = Game(theater=theater)
|
||||||
Armor.MBT_M1A2_Abrams: 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
theater.senaki.base.aircraft = {
|
w = ui.window.Window()
|
||||||
MiG_21Bis: 8,
|
m = ui.mainmenu.MainMenu(g, w)
|
||||||
}
|
|
||||||
|
|
||||||
theater.senaki.base.armor = {
|
w.run()
|
||||||
Armor.MBT_T_55: 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
theater.senaki.base.aa = {
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
op = game.mission.InterceptOperation(
|
"""
|
||||||
mission=m,
|
selected_cp = None # type: ControlPoint
|
||||||
attacker=m.country("USA"),
|
while True:
|
||||||
defender=m.country("Russia"),
|
ptr = 0
|
||||||
destination=theater.batumi,
|
|
||||||
destination_port=m.terrain.batumi(),
|
|
||||||
escort={Su_27: 2},
|
|
||||||
transport={An_26B: 2},
|
|
||||||
interceptors={M_2000C: 2}
|
|
||||||
)
|
|
||||||
|
|
||||||
op = game.mission.GroundInterceptOperation(
|
print("Budget: {}m".format(g.budget))
|
||||||
mission=m,
|
|
||||||
attacker=m.country("USA"),
|
|
||||||
defender=m.country("Russia"),
|
|
||||||
position=m.terrain.batumi().position,
|
|
||||||
target={Unarmed.Transport_ZIL_4331: 10},
|
|
||||||
strikegroup={A_10C: 2}
|
|
||||||
)
|
|
||||||
|
|
||||||
op = game.mission.CaptureOperation(
|
if selected_cp is None:
|
||||||
mission=m,
|
print("Events:")
|
||||||
attacker=m.country("USA"),
|
for event in g.events:
|
||||||
defender=m.country("Russia"),
|
ptr += 1
|
||||||
from_cp=theater.senaki,
|
print("{}. {} {}".format(ptr, event.attacker != g.side and "!" or " ", event))
|
||||||
to_cp=theater.batumi,
|
|
||||||
cas={A_10C: 2},
|
print("Control Points:")
|
||||||
escort={F_15C: 2},
|
controlpoints = g.theater.controlpoints
|
||||||
attack={Armor.MBT_M1A2_Abrams: 4},
|
controlpoints.sort(key=lambda x: x.captured)
|
||||||
intercept={Su_27: 4},
|
for cp in g.theater.controlpoints:
|
||||||
defense={Armor.MBT_T_55: 4},
|
ptr += 1
|
||||||
aa={AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 3})
|
print("{}. [{}{}] {}{}{}{}".format(
|
||||||
op.generate()
|
ptr,
|
||||||
|
cp.captured and "x" or " ",
|
||||||
|
int(cp.base.readiness * 10),
|
||||||
|
cp.name,
|
||||||
|
"^" * cp.base.total_planes,
|
||||||
|
"." * cp.base.total_armor,
|
||||||
|
"*" * cp.base.total_aa))
|
||||||
|
|
||||||
|
events_boundary = len(g.events)
|
||||||
|
try:
|
||||||
|
selected_idx = int(input(">").strip()) - 1
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if selected_idx == -1:
|
||||||
|
g.pass_turn()
|
||||||
|
continue
|
||||||
|
if selected_idx < events_boundary:
|
||||||
|
event = g.events[selected_idx]
|
||||||
|
else:
|
||||||
|
selected_cp = controlpoints[selected_idx - events_boundary]
|
||||||
|
else:
|
||||||
|
print("Units on the base: ")
|
||||||
|
for unit, count in selected_cp.base.all_units:
|
||||||
|
print("{} ({}) ".format(unit.name and unit.name or unit.id, count), end="")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
try:
|
||||||
|
selected_idx = int(input(">").strip()) - 1
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if selected_idx == -1:
|
||||||
|
selected_cp = None
|
||||||
|
|
||||||
if not os.path.exists("./build"):
|
if not os.path.exists("./build"):
|
||||||
os.mkdir("./build")
|
os.mkdir("./build")
|
||||||
|
|
||||||
m.save("build/output.miz")
|
m.save("build/output.miz")
|
||||||
|
"""
|
||||||
|
|
||||||
|
|||||||
180
game/event.py
180
game/event.py
@ -1,55 +1,185 @@
|
|||||||
import typing
|
import typing
|
||||||
|
import random
|
||||||
|
import math
|
||||||
|
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
from theater.controlpoint import *
|
from theater.controlpoint import *
|
||||||
from .mission import *
|
from userdata.debriefing_parser import *
|
||||||
|
from game.operation import *
|
||||||
|
|
||||||
|
DIFFICULTY_LOG_BASE = 1.5
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
silent = False
|
silent = False
|
||||||
operation = None # type: Operation
|
operation = None # type: Operation
|
||||||
|
difficulty = 1 # type: int
|
||||||
|
BONUS_BASE = 0
|
||||||
|
|
||||||
def failure(self):
|
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint):
|
||||||
|
self.mission = dcs.mission.Mission()
|
||||||
|
self.attacker = self.mission.country(attacker_name)
|
||||||
|
self.defender = self.mission.country(defender_name)
|
||||||
|
self.to_cp = to_cp
|
||||||
|
self.from_cp = from_cp
|
||||||
|
|
||||||
|
def bonus(self) -> int:
|
||||||
|
return math.ceil(math.log(self.difficulty, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
||||||
|
|
||||||
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
|
return self.operation.is_successfull(debriefing)
|
||||||
|
|
||||||
|
def commit(self, debriefing: Debriefing):
|
||||||
|
for country, losses in debriefing.destroyed_units.items():
|
||||||
|
cp = None # type: ControlPoint
|
||||||
|
if country == self.attacker.name:
|
||||||
|
cp = self.from_cp
|
||||||
|
else:
|
||||||
|
cp = self.to_cp
|
||||||
|
|
||||||
|
cp.base.commit_losses(losses)
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def success(self):
|
|
||||||
pass
|
class GroundInterceptEvent(Event):
|
||||||
|
BONUS_BASE = 3
|
||||||
|
TARGET_AMOUNT_FACTOR = 3
|
||||||
|
TARGET_VARIETY = 3
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Ground intercept at {} ({})".format(self.to_cp, "*" * self.difficulty)
|
||||||
|
|
||||||
|
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(-0.1)
|
||||||
|
else:
|
||||||
|
self.to_cp.base.affect_strength(+0.1)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
|
if not self.to_cp.captured:
|
||||||
|
self.to_cp.base.affect_strength(+0.1)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def player_attacking(self, position: Point, strikegroup: typing.Dict[PlaneType, int]):
|
||||||
|
suitable_unittypes = db.find_unittype(CAP, self.defender.name)
|
||||||
|
random.shuffle(suitable_unittypes)
|
||||||
|
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
|
||||||
|
typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1)
|
||||||
|
targets = {unittype: typecount for unittype in unittypes}
|
||||||
|
|
||||||
|
self.operation = GroundInterceptOperation(mission=self.mission,
|
||||||
|
attacker=self.attacker,
|
||||||
|
defender=self.defender,
|
||||||
|
position=position,
|
||||||
|
target=targets,
|
||||||
|
strikegroup=strikegroup)
|
||||||
|
|
||||||
|
|
||||||
class InterceptEvent(Event):
|
class InterceptEvent(Event):
|
||||||
pass
|
ESCORT_AMOUNT_FACTOR = 2
|
||||||
|
BONUS_BASE = 5
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Intercept at {} ({})".format(self.to_cp, "*" * self.difficulty)
|
||||||
|
|
||||||
|
def commit(self, debriefing: Debriefing):
|
||||||
|
super(InterceptEvent, self).commit(debriefing)
|
||||||
|
if self.is_successfull(debriefing):
|
||||||
|
self.to_cp.base.affect_strength(0.1 * self.from_cp.captured and -1 or 1)
|
||||||
|
else:
|
||||||
|
self.to_cp.base.affect_strength(0.1 * self.from_cp.captured and 1 or -1)
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
|
if self.to_cp.captured:
|
||||||
|
self.to_cp.base.affect_strength(-0.2)
|
||||||
|
|
||||||
|
def player_attacking(self, interceptors: typing.Dict[PlaneType, int]):
|
||||||
|
escort = self.to_cp.base.scramble_sweep(self.to_cp)
|
||||||
|
transport_unit = random.choice(db.find_unittype(Transport, self.defender.name))
|
||||||
|
assert transport_unit is not None
|
||||||
|
|
||||||
|
self.operation = InterceptOperation(mission=self.mission,
|
||||||
|
attacker=self.attacker,
|
||||||
|
defender=self.defender,
|
||||||
|
destination=self.to_cp,
|
||||||
|
destination_port=self.to_cp.airport,
|
||||||
|
escort=escort,
|
||||||
|
transport={transport_unit: 1},
|
||||||
|
interceptors=interceptors)
|
||||||
|
|
||||||
|
def player_defending(self, escort: typing.Dict[PlaneType, int]):
|
||||||
|
interceptors = self.from_cp.base.scramble_interceptors_count(self.difficulty * self.ESCORT_AMOUNT_FACTOR)
|
||||||
|
transport_unit = random.choice(db.find_unittype(Transport, self.defender.name))
|
||||||
|
assert transport_unit is not None
|
||||||
|
|
||||||
|
self.operation = InterceptOperation(mission=self.mission,
|
||||||
|
attacker=self.attacker,
|
||||||
|
defender=self.defender,
|
||||||
|
destination=self.to_cp,
|
||||||
|
destination_port=self.to_cp.airport,
|
||||||
|
escort=escort,
|
||||||
|
transport={transport_unit: 1},
|
||||||
|
interceptors=interceptors)
|
||||||
|
|
||||||
|
|
||||||
class CaptureEvent(Event):
|
class CaptureEvent(Event):
|
||||||
silent = True
|
silent = True
|
||||||
|
BONUS_BASE = 7
|
||||||
|
|
||||||
def __init__(self, from_cp: ControlPoint, to_cp: ControlPoint):
|
def __str__(self):
|
||||||
pass
|
return "Capture {} ({})".format(self.to_cp, "*" * self.difficulty)
|
||||||
|
|
||||||
def player_defending(self, from_cp: ControlPoint, to_cp: ControlPoint, interceptors: typing.Dict[PlaneType, int]):
|
def commit(self, debriefing: Debriefing):
|
||||||
assert not self.operation
|
super(CaptureEvent, self).commit(debriefing)
|
||||||
|
if self.is_successfull(debriefing):
|
||||||
|
if self.from_cp.captured:
|
||||||
|
self.to_cp.captured = True
|
||||||
|
else:
|
||||||
|
if not self.from_cp.captured:
|
||||||
|
self.to_cp.captured = False
|
||||||
|
self.to_cp.base.affect_strength(+0.5)
|
||||||
|
|
||||||
cas = from_cp.base.scramble_cas(to_cp)
|
def skip(self):
|
||||||
escort = from_cp.base.scramble_sweep(to_cp)
|
if self.to_cp.captured:
|
||||||
attackers = from_cp.base.assemble_cap(to_cp)
|
self.to_cp.captured = False
|
||||||
|
|
||||||
self.operation = CaptureOperation(from_cp=from_cp,
|
def player_defending(self, interceptors: typing.Dict[PlaneType, int]):
|
||||||
to_cp=to_cp,
|
cas = self.from_cp.base.scramble_cas(self.to_cp)
|
||||||
|
escort = self.from_cp.base.scramble_sweep(self.to_cp)
|
||||||
|
attackers = self.from_cp.base.assemble_cap(self.to_cp)
|
||||||
|
|
||||||
|
self.operation = CaptureOperation(mission=self.mission,
|
||||||
|
attacker=self.attacker,
|
||||||
|
defender=self.defender,
|
||||||
|
from_cp=self.from_cp,
|
||||||
|
to_cp=self.to_cp,
|
||||||
cas=cas,
|
cas=cas,
|
||||||
escort=escort,
|
escort=escort,
|
||||||
attack=attackers,
|
attack=attackers,
|
||||||
intercept=interceptors,
|
intercept=interceptors,
|
||||||
defense=to_cp.base.armor,
|
defense=self.to_cp.base.armor,
|
||||||
aa=to_cp.base.aa)
|
aa=self.to_cp.base.aa)
|
||||||
|
|
||||||
def player_attacking(self, from_cp: ControlPoint, to_cp: ControlPoint, cas: typing.Dict[PlaneType, int], escort: typing.Dict[PlaneType, int], armor: typing.Dict[Armor, int]):
|
def player_attacking(self, cas: typing.Dict[PlaneType, int], escort: typing.Dict[PlaneType, int], armor: typing.Dict[Armor, int]):
|
||||||
assert not self.operation
|
interceptors = self.to_cp.base.scramble_sweep(for_target=self.to_cp)
|
||||||
|
|
||||||
interceptors = to_cp.base.scramble_sweep()
|
self.operation = CaptureOperation(mission=self.mission,
|
||||||
|
attacker=self.attacker,
|
||||||
self.operation = CaptureOperation(from_cp=from_cp,
|
defender=self.defender,
|
||||||
to_cp=to_cp,
|
from_cp=self.from_cp,
|
||||||
|
to_cp=self.to_cp,
|
||||||
cas=cas,
|
cas=cas,
|
||||||
escort=escort,
|
escort=escort,
|
||||||
attack=armor,
|
attack=armor,
|
||||||
intercept=interceptors,
|
intercept=interceptors,
|
||||||
defense=to_cp.base.armor,
|
defense=self.to_cp.base.armor,
|
||||||
aa=to_cp.base.aa)
|
aa=self.to_cp.base.aa)
|
||||||
8
game/event_results.py
Normal file
8
game/event_results.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import typing
|
||||||
|
import dcs
|
||||||
|
|
||||||
|
from game.event import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
128
game/game.py
128
game/game.py
@ -1,21 +1,135 @@
|
|||||||
import typing
|
import typing
|
||||||
|
import random
|
||||||
|
|
||||||
from theater.conflicttheater import *
|
from theater.conflicttheater import *
|
||||||
from theater.controlpoint import *
|
from theater.controlpoint import *
|
||||||
from .event import *
|
from userdata.debriefing_parser import *
|
||||||
|
from game.event import *
|
||||||
|
|
||||||
|
COMMISION_LIMITS_SCALE = 2
|
||||||
|
COMMISION_LIMITS_FACTORS = {
|
||||||
|
CAP: 2,
|
||||||
|
CAS: 1,
|
||||||
|
FighterSweep: 3,
|
||||||
|
AirDefence: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
COMMISION_AMOUNTS_SCALE = 2
|
||||||
|
COMMISION_AMOUNTS_FACTORS = {
|
||||||
|
CAP: 0.6,
|
||||||
|
CAS: 0.3,
|
||||||
|
FighterSweep: 0.5,
|
||||||
|
AirDefence: 0.3,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ENEMY_INTERCEPT_PROBABILITY_BASE = 25
|
||||||
|
ENEMY_CAPTURE_PROBABILITY_BASE = 15
|
||||||
|
|
||||||
|
PLAYER_INTERCEPT_PROBABILITY_BASE = 30
|
||||||
|
PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE = 30
|
||||||
|
|
||||||
|
PLAYER_BUDGET_BASE = 25
|
||||||
|
PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
events = [] # type: typing.List[Event]
|
budget = 45
|
||||||
|
events = None # type: typing.List[Event]
|
||||||
|
|
||||||
def __init__(self, theater: ConflictTheater):
|
def __init__(self, theater: ConflictTheater):
|
||||||
|
self.events = []
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
|
self.player = "USA"
|
||||||
|
self.enemy = "Russia"
|
||||||
|
|
||||||
|
def _roll(self, prob, mult):
|
||||||
|
return random.randint(0, 100) <= prob * mult
|
||||||
|
|
||||||
def _fill_cap_events(self):
|
def _fill_cap_events(self):
|
||||||
for cp in [x for x in self.theater.controlpoints if x.captured]:
|
for from_cp, to_cp in self.theater.conflicts(True):
|
||||||
for connected_cp in [x for x in cp.connected_points if not x.captured]:
|
self.events.append(CaptureEvent(attacker_name=self.player,
|
||||||
self.events.append(CaptureEvent(cp, connected_cp))
|
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):
|
||||||
|
if self._roll(ENEMY_CAPTURE_PROBABILITY_BASE, from_cp.base.strength):
|
||||||
|
self.events.append(CaptureEvent(attacker_name=self.enemy,
|
||||||
|
defender_name=self.player,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp))
|
||||||
|
break
|
||||||
|
|
||||||
|
def _generate_interceptions(self):
|
||||||
|
for from_cp, to_cp in self.theater.conflicts(False):
|
||||||
|
if self._roll(ENEMY_INTERCEPT_PROBABILITY_BASE, from_cp.base.strength):
|
||||||
|
self.events.append(InterceptEvent(attacker_name=self.enemy,
|
||||||
|
defender_name=self.player,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp))
|
||||||
|
break
|
||||||
|
|
||||||
|
for from_cp, to_cp in self.theater.conflicts(True):
|
||||||
|
if self._roll(PLAYER_INTERCEPT_PROBABILITY_BASE, from_cp.base.strength):
|
||||||
|
self.events.append(InterceptEvent(attacker_name=self.player,
|
||||||
|
defender_name=self.enemy,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp))
|
||||||
|
break
|
||||||
|
|
||||||
|
def _generate_groundinterceptions(self):
|
||||||
|
for from_cp, to_cp in self.theater.conflicts(True):
|
||||||
|
if self._roll(PLAYER_GROUNDINTERCEPT_PROBABILITY_BASE, from_cp.base.strength):
|
||||||
|
self.events.append(GroundInterceptEvent(attacker_name=self.player,
|
||||||
|
defender_name=self.enemy,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp))
|
||||||
|
break
|
||||||
|
|
||||||
|
def _commision_units(self, cp: ControlPoint):
|
||||||
|
for for_task in [CAP, CAS, FighterSweep, AirDefence]:
|
||||||
|
limit = COMMISION_LIMITS_FACTORS[for_task] * math.pow(cp.importance, COMMISION_LIMITS_SCALE)
|
||||||
|
missing_units = limit - cp.base.total_units(for_task)
|
||||||
|
if missing_units > 0:
|
||||||
|
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})
|
||||||
|
|
||||||
|
def _budget_player(self):
|
||||||
|
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())
|
||||||
|
|
||||||
|
self.budget += math.ceil(math.log(total_importance * total_strength + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE)
|
||||||
|
|
||||||
|
def initiate_event(self, event: Event):
|
||||||
|
event.operation.generate()
|
||||||
|
event.mission.save("build/next_mission.miz")
|
||||||
|
|
||||||
|
def finish_event(self, event: Event, debriefing: Debriefing):
|
||||||
|
event.commit(debriefing)
|
||||||
|
if event.is_successfull(debriefing):
|
||||||
|
self.budget += event.bonus()
|
||||||
|
|
||||||
|
self.events.remove(event)
|
||||||
|
|
||||||
|
def is_player_attack(self, event: Event):
|
||||||
|
return event.attacker.name == self.player
|
||||||
|
|
||||||
def pass_turn(self):
|
def pass_turn(self):
|
||||||
self.events = [] # type: typing.List[Event]
|
for event in self.events:
|
||||||
self._fill_cap_events()
|
event.skip()
|
||||||
|
|
||||||
|
self._budget_player()
|
||||||
|
for cp in self.theater.enemy_bases():
|
||||||
|
self._commision_units(cp)
|
||||||
|
|
||||||
|
self.events = [] # type: typing.List[Event]
|
||||||
|
self._fill_cap_events()
|
||||||
|
self._generate_enemy_caps()
|
||||||
|
self._generate_interceptions()
|
||||||
|
self._generate_groundinterceptions()
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
from globals import *
|
from globals import *
|
||||||
|
from userdata.debriefing_parser import *
|
||||||
from dcs.mission import *
|
from dcs.mission import *
|
||||||
from dcs.unitgroup import *
|
from dcs.unitgroup import *
|
||||||
from dcs.vehicles import *
|
from dcs.vehicles import *
|
||||||
@ -22,6 +23,15 @@ class Operation:
|
|||||||
self.airgen = AircraftConflictGenerator(self.mission, self.conflict)
|
self.airgen = AircraftConflictGenerator(self.mission, self.conflict)
|
||||||
self.aagen = AAConflictGenerator(self.mission, self.conflict)
|
self.aagen = AAConflictGenerator(self.mission, self.conflict)
|
||||||
|
|
||||||
|
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CaptureOperation(Operation):
|
class CaptureOperation(Operation):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -35,6 +35,7 @@ class AircraftConflictGenerator:
|
|||||||
def __init__(self, mission: Mission, conflict: Conflict):
|
def __init__(self, mission: Mission, conflict: Conflict):
|
||||||
self.m = mission
|
self.m = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
|
self.escort_targets = []
|
||||||
|
|
||||||
def _group_point(self, point) -> Point:
|
def _group_point(self, point) -> Point:
|
||||||
distance = randint(
|
distance = randint(
|
||||||
@ -53,6 +54,8 @@ class AircraftConflictGenerator:
|
|||||||
airport: Airport = None) -> PlaneGroup:
|
airport: Airport = None) -> PlaneGroup:
|
||||||
starttype = airport == None and StartType.Warm or StartType.Cold
|
starttype = airport == None and StartType.Warm or StartType.Cold
|
||||||
print("generating {} ({}) at {} {}".format(unit, count, at, airport, side))
|
print("generating {} ({}) at {} {}".format(unit, count, at, airport, side))
|
||||||
|
assert count > 0
|
||||||
|
|
||||||
return self.m.flight_group(
|
return self.m.flight_group(
|
||||||
country=side,
|
country=side,
|
||||||
name=name,
|
name=name,
|
||||||
@ -66,7 +69,8 @@ class AircraftConflictGenerator:
|
|||||||
group_size=count)
|
group_size=count)
|
||||||
|
|
||||||
def _generate_escort(self, units: typing.Dict[PlaneType, int], airport: Airport, side: Country, location: Point):
|
def _generate_escort(self, units: typing.Dict[PlaneType, int], airport: Airport, side: Country, location: Point):
|
||||||
assert len(self.escort_targets) > 0
|
if len(self.escort_targets) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
for type, count in units.items():
|
for type, count in units.items():
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
@ -81,7 +85,7 @@ class AircraftConflictGenerator:
|
|||||||
group.load_task_default_loadout(dcs.task.Escort)
|
group.load_task_default_loadout(dcs.task.Escort)
|
||||||
|
|
||||||
heading = group.position.heading_between_point(self.conflict.position)
|
heading = group.position.heading_between_point(self.conflict.position)
|
||||||
position = group.position # type: Point
|
position = group.position # type: Point
|
||||||
wayp = group.add_waypoint(position.point_from_heading(heading, WORKAROUND_WAYP_DIST), CAS_ALTITUDE)
|
wayp = group.add_waypoint(position.point_from_heading(heading, WORKAROUND_WAYP_DIST), CAS_ALTITUDE)
|
||||||
|
|
||||||
for group in self.escort_targets:
|
for group in self.escort_targets:
|
||||||
|
|||||||
@ -25,6 +25,7 @@ INTERCEPT_DEFENDERS_HEADING = -10, 10
|
|||||||
INTERCEPT_ATTACKERS_DISTANCE = 60000
|
INTERCEPT_ATTACKERS_DISTANCE = 60000
|
||||||
INTERCEPT_DEFENDERS_DISTANCE = 30000
|
INTERCEPT_DEFENDERS_DISTANCE = 30000
|
||||||
|
|
||||||
|
|
||||||
class Conflict:
|
class Conflict:
|
||||||
attackers_side = None # type: Country
|
attackers_side = None # type: Country
|
||||||
defenders_side = None # type: Country
|
defenders_side = None # type: Country
|
||||||
|
|||||||
BIN
resources/caumap.gif
Normal file
BIN
resources/caumap.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
37
shop/db.py
37
shop/db.py
@ -13,6 +13,7 @@ PRICES = {
|
|||||||
# planes
|
# planes
|
||||||
|
|
||||||
Su_25T: 10,
|
Su_25T: 10,
|
||||||
|
Su_25: 10,
|
||||||
A_10A: 15,
|
A_10A: 15,
|
||||||
A_10C: 20,
|
A_10C: 20,
|
||||||
|
|
||||||
@ -23,6 +24,10 @@ PRICES = {
|
|||||||
|
|
||||||
MiG_15bis: 10,
|
MiG_15bis: 10,
|
||||||
MiG_21Bis: 13,
|
MiG_21Bis: 13,
|
||||||
|
MiG_29A: 23,
|
||||||
|
|
||||||
|
IL_76MD: 20,
|
||||||
|
S_3B_Tanker: 20,
|
||||||
|
|
||||||
# armor
|
# armor
|
||||||
|
|
||||||
@ -40,5 +45,35 @@ PRICES = {
|
|||||||
UNIT_BY_TASK = {
|
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, ],
|
||||||
CAS: [Su_25T, A_10A, A_10C, ],
|
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, ]
|
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, ],
|
||||||
|
Transport: [IL_76MD, S_3B_Tanker, ],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UNIT_BY_COUNTRY = {
|
||||||
|
"Russia": [Su_25T, A_10C, 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],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def unit_task(unit: UnitType) -> Task:
|
||||||
|
for task, units in UNIT_BY_TASK.items():
|
||||||
|
if unit in units:
|
||||||
|
return task
|
||||||
|
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def find_unittype(for_task: Task, country_name: str) -> typing.List[UnitType]:
|
||||||
|
return [x for x in UNIT_BY_TASK[for_task] if x in UNIT_BY_COUNTRY[country_name]]
|
||||||
|
|
||||||
|
|
||||||
|
def unit_type_name(unit_type) -> str:
|
||||||
|
return unit_type.id and unit_type.id or unit_type.name
|
||||||
|
|
||||||
|
|
||||||
|
def task_name(task) -> str:
|
||||||
|
if task == AirDefence:
|
||||||
|
return "AirDefence"
|
||||||
|
else:
|
||||||
|
return task.name
|
||||||
|
|||||||
107
theater/base.py
107
theater/base.py
@ -1,6 +1,7 @@
|
|||||||
import typing
|
import typing
|
||||||
import dcs
|
import dcs
|
||||||
import math
|
import math
|
||||||
|
import itertools
|
||||||
|
|
||||||
from shop import db
|
from shop import db
|
||||||
from theater.controlpoint import ControlPoint
|
from theater.controlpoint import ControlPoint
|
||||||
@ -17,10 +18,15 @@ ARMOR_IMPORTANCE_FACTOR = 4
|
|||||||
class Base:
|
class Base:
|
||||||
aircraft = {} # type: typing.Dict[PlaneType, int]
|
aircraft = {} # type: typing.Dict[PlaneType, int]
|
||||||
armor = {} # type: typing.Dict[Armor, int]
|
armor = {} # type: typing.Dict[Armor, int]
|
||||||
aa = {} # type: typing.Dict[AirDefence, int]
|
aa = {} # type: typing.Dict[AirDefence, int]
|
||||||
|
strength = 1 # type: float
|
||||||
|
commision_points = {}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self.aircraft = {}
|
||||||
|
self.armor = {}
|
||||||
|
self.aa = {}
|
||||||
|
self.commision_points = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_planes(self) -> int:
|
def total_planes(self) -> int:
|
||||||
@ -30,19 +36,40 @@ class Base:
|
|||||||
def total_armor(self) -> int:
|
def total_armor(self) -> int:
|
||||||
return sum(self.armor.values())
|
return sum(self.armor.values())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_aa(self) -> int:
|
||||||
|
return sum(self.aa.values())
|
||||||
|
|
||||||
|
def total_units(self, task: Task) -> int:
|
||||||
|
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t in db.UNIT_BY_TASK[task]])
|
||||||
|
|
||||||
|
def total_units_of_type(self, unit_type) -> int:
|
||||||
|
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t == unit_type])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_units(self):
|
||||||
|
return itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items())
|
||||||
|
|
||||||
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
|
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
|
||||||
sorted_planes = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]]
|
assert count > 0
|
||||||
sorted_planes.sort(key=lambda x: db.PRICES[x], reverse=True)
|
|
||||||
|
sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]]
|
||||||
|
sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True)
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for plane in sorted_planes:
|
for unit_type in sorted_units:
|
||||||
existing_count = dict[plane] # type: int
|
existing_count = dict[unit_type] # type: int
|
||||||
if not existing_count:
|
if not existing_count:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if count <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
result_unit_count = min(count, existing_count)
|
result_unit_count = min(count, existing_count)
|
||||||
count -= result_unit_count
|
count -= result_unit_count
|
||||||
result[plane] = result.get(plane, 0) + result_unit_count
|
|
||||||
|
assert result_unit_count > 0
|
||||||
|
result[unit_type] = result.get(unit_type, 0) + result_unit_count
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -65,24 +92,68 @@ class Base:
|
|||||||
total_scrambled += PLANES_IN_GROUP
|
total_scrambled += PLANES_IN_GROUP
|
||||||
yield PLANES_IN_GROUP and total_scrambled < total_planes or total_planes - total_scrambled
|
yield PLANES_IN_GROUP and total_scrambled < total_planes or total_planes - total_scrambled
|
||||||
|
|
||||||
def commit_scramble(self, scrambled_aircraft: typing.Dict[PlaneType, int]):
|
def append_commision_points(self, for_type, points: float) -> int:
|
||||||
for k, c in scrambled_aircraft:
|
self.commision_points[for_type] = self.commision_points.get(for_type, 0) + points
|
||||||
self.aircraft[k] -= c
|
points = self.commision_points[for_type]
|
||||||
assert self.aircraft[k] >= 0
|
if points >= 1:
|
||||||
if self.aircraft[k] == 0:
|
self.commision_points[for_type] = points - math.floor(points)
|
||||||
del self.aircraft[k]
|
return int(math.floor(points))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def commision_units(self, units: typing.Dict[typing.Any, int]):
|
||||||
|
for value in units.values():
|
||||||
|
assert value > 0
|
||||||
|
assert value == math.floor(value)
|
||||||
|
|
||||||
|
for unit_type, unit_count in units.items():
|
||||||
|
for_task = db.unit_task(unit_type)
|
||||||
|
|
||||||
|
target_dict = None
|
||||||
|
if for_task == CAS or for_task == FighterSweep:
|
||||||
|
target_dict = self.aircraft
|
||||||
|
elif for_task == CAP:
|
||||||
|
target_dict = self.armor
|
||||||
|
elif for_task == AirDefence:
|
||||||
|
target_dict = self.aa
|
||||||
|
|
||||||
|
assert target_dict is not None
|
||||||
|
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
|
||||||
|
|
||||||
|
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
|
||||||
|
for unit_type, count in units_lost.items():
|
||||||
|
aircraft_key = next((x for x in self.aircraft.keys() if x.id == unit_type), None)
|
||||||
|
if aircraft_key:
|
||||||
|
self.aircraft[aircraft_key] = self.aircraft[aircraft_key] - count
|
||||||
|
|
||||||
|
armor_key = next((x for x in self.armor.keys() if x.name == unit_type), None)
|
||||||
|
if armor_key:
|
||||||
|
self.armor[armor_key] = self.armor[armor_key] - count
|
||||||
|
|
||||||
|
aa_key = next((x for x in self.aa.keys() if x.name == unit_type), None)
|
||||||
|
if aa_key:
|
||||||
|
self.aa[aa_key] = self.aa[aa_key] - count
|
||||||
|
|
||||||
|
def affect_strength(self, amount):
|
||||||
|
self.strength += amount
|
||||||
|
if self.strength > 1:
|
||||||
|
self.strength = 1
|
||||||
|
|
||||||
def scramble_cas(self, for_target: ControlPoint) -> typing.Dict[PlaneType, int]:
|
def scramble_cas(self, for_target: ControlPoint) -> typing.Dict[PlaneType, int]:
|
||||||
return self._find_best_planes(CAS, int(for_target.importance * PLANES_IMPORTANCE_FACTOR))
|
return self._find_best_planes(CAS, math.ceil(for_target.importance * PLANES_IMPORTANCE_FACTOR * self.strength))
|
||||||
|
|
||||||
def scramble_sweep(self, for_target: ControlPoint) -> typing.Dict[PlaneType, int]:
|
def scramble_sweep(self, for_target: ControlPoint) -> typing.Dict[PlaneType, int]:
|
||||||
return self._find_best_planes(FighterSweep, int(for_target.importance * PLANES_IMPORTANCE_FACTOR))
|
return self._find_best_planes(FighterSweep, math.ceil(for_target.importance * PLANES_IMPORTANCE_FACTOR * self.strength))
|
||||||
|
|
||||||
def scramble_interceptors(self, factor: float) -> typing.Dict[PlaneType, int]:
|
def scramble_interceptors(self, factor: float) -> typing.Dict[PlaneType, int]:
|
||||||
return self._find_best_planes(FighterSweep, int(self.total_planes * factor))
|
return self._find_best_planes(FighterSweep, math.ceil(self.total_planes * factor * self.strength))
|
||||||
|
|
||||||
|
def scramble_interceptors_count(self, count: int) -> typing.Dict[PlaneType, int]:
|
||||||
|
assert count > 0
|
||||||
|
return self._find_best_planes(FighterSweep, count)
|
||||||
|
|
||||||
def assemble_cap(self, for_target: ControlPoint) -> typing.Dict[Armor, int]:
|
def assemble_cap(self, for_target: ControlPoint) -> typing.Dict[Armor, int]:
|
||||||
return self._find_best_armor(CAP, int(for_target.importance * ARMOR_IMPORTANCE_FACTOR))
|
return self._find_best_armor(CAP, math.ceil(for_target.importance * ARMOR_IMPORTANCE_FACTOR * self.strength))
|
||||||
|
|
||||||
def assemble_defense(self, factor: float) -> typing.Dict[Armor, int]:
|
def assemble_defense(self, factor: float) -> typing.Dict[Armor, int]:
|
||||||
return self._find_best_armor(CAP, int(self.total_armor * factor))
|
return self._find_best_armor(CAP, math.ceil(self.total_armor * factor * self.strength))
|
||||||
|
|||||||
@ -4,20 +4,22 @@ from .conflicttheater import *
|
|||||||
from .base import *
|
from .base import *
|
||||||
|
|
||||||
class CaucasusTheater(ConflictTheater):
|
class CaucasusTheater(ConflictTheater):
|
||||||
kutaisi = ControlPoint(caucasus.Kutaisi.position, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW)
|
kutaisi = ControlPoint(caucasus.Kutaisi, ALL_RADIALS, SIZE_SMALL, IMPORTANCE_LOW)
|
||||||
senaki = ControlPoint(caucasus.Senaki.position, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_LOW)
|
senaki = ControlPoint(caucasus.Senaki, ALL_RADIALS, SIZE_REGULAR, IMPORTANCE_LOW)
|
||||||
kobuleti = ControlPoint(caucasus.Kobuleti.position, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_LOW)
|
kobuleti = ControlPoint(caucasus.Kobuleti, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_LOW)
|
||||||
batumi = ControlPoint(caucasus.Batumi.position, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_MEDIUM)
|
batumi = ControlPoint(caucasus.Batumi, COAST_VERTICAL, SIZE_SMALL, IMPORTANCE_MEDIUM)
|
||||||
sukhumi = ControlPoint(caucasus.Sukhumi.position, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM)
|
sukhumi = ControlPoint(caucasus.Sukhumi, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM)
|
||||||
gudauta = ControlPoint(caucasus.Gudauta.position, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM)
|
gudauta = ControlPoint(caucasus.Gudauta, COAST_VERTICAL, SIZE_REGULAR, IMPORTANCE_MEDIUM)
|
||||||
sochi = ControlPoint(caucasus.Sochi.position, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH)
|
sochi = ControlPoint(caucasus.Sochi, COAST_VERTICAL, SIZE_BIG, IMPORTANCE_HIGH)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.add_controlpoint(self.kutaisi, connected_to=[self.senaki])
|
self.kutaisi.captured = True
|
||||||
self.add_controlpoint(self.senaki, connected_to=[self.kobuleti, self.sukhumi])
|
|
||||||
self.add_controlpoint(self.kobuleti, connected_to=[self.batumi])
|
|
||||||
self.add_controlpoint(self.batumi)
|
|
||||||
|
|
||||||
self.add_controlpoint(self.sukhumi, connected_to=[self.gudauta])
|
self.add_controlpoint(self.kutaisi, connected_to=[self.senaki])
|
||||||
self.add_controlpoint(self.gudauta, connected_to=[self.sochi])
|
self.add_controlpoint(self.senaki, connected_to=[self.kobuleti, self.sukhumi, self.kutaisi])
|
||||||
self.add_controlpoint(self.sochi)
|
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])
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import typing
|
import typing
|
||||||
|
import itertools
|
||||||
|
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
from .controlpoint import *
|
from .controlpoint import *
|
||||||
@ -9,8 +11,8 @@ SIZE_BIG = 2000
|
|||||||
SIZE_LARGE = 3000
|
SIZE_LARGE = 3000
|
||||||
|
|
||||||
IMPORTANCE_LOW = 1
|
IMPORTANCE_LOW = 1
|
||||||
IMPORTANCE_MEDIUM = 2
|
IMPORTANCE_MEDIUM = 1.2
|
||||||
IMPORTANCE_HIGH = 3
|
IMPORTANCE_HIGH = 1.4
|
||||||
|
|
||||||
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
|
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
|
||||||
COAST_VERTICAL = [45, 90, 135, ]
|
COAST_VERTICAL = [45, 90, 135, ]
|
||||||
@ -20,11 +22,22 @@ COAST_HORIZONTAL = [315, 0, 45, ]
|
|||||||
class ConflictTheater:
|
class ConflictTheater:
|
||||||
controlpoints = [] # type: typing.List[ControlPoint]
|
controlpoints = [] # type: typing.List[ControlPoint]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.controlpoints = []
|
||||||
|
|
||||||
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
|
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
|
||||||
for connected_point in connected_to:
|
for connected_point in connected_to:
|
||||||
point.connect(to=connected_point)
|
point.connect(to=connected_point)
|
||||||
|
|
||||||
self.controlpoints.append(point)
|
self.controlpoints.append(point)
|
||||||
|
|
||||||
def player_bases(self) -> typing.Collection[ControlPoint]:
|
def player_points(self) -> typing.Collection[ControlPoint]:
|
||||||
return [point for point in self.controlpoints if point.captured and point.base]
|
return [point for point in self.controlpoints if point.captured]
|
||||||
|
|
||||||
|
def conflicts(self, from_player=True) -> typing.Collection[typing.Tuple[ControlPoint, ControlPoint]]:
|
||||||
|
for cp in [x for x in self.controlpoints if x.captured == from_player]:
|
||||||
|
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
|
||||||
|
yield (cp, connected_point)
|
||||||
|
|
||||||
|
def enemy_bases(self) -> typing.Collection[ControlPoint]:
|
||||||
|
return [point for point in self.controlpoints if not point.captured]
|
||||||
|
|||||||
@ -7,26 +7,36 @@ from dcs.country import *
|
|||||||
|
|
||||||
from gen.conflictgen import *
|
from gen.conflictgen import *
|
||||||
|
|
||||||
class ControlPoint:
|
|
||||||
connected_points = [] # type: typing.List[ControlPoint]
|
|
||||||
position = None # type: Point
|
|
||||||
captured = False
|
|
||||||
strength = 100
|
|
||||||
base: None # type: theater.base.Base
|
|
||||||
|
|
||||||
def __init__(self, point: Point, radials: typing.Collection[int], size: int, importance: int):
|
class ControlPoint:
|
||||||
|
connected_points = [] # type: typing.List[ControlPoint]
|
||||||
|
position = None # type: Point
|
||||||
|
captured = False
|
||||||
|
base: None # type: theater.base.Base
|
||||||
|
airport: None # type: Airport
|
||||||
|
|
||||||
|
def __init__(self, airport: Airport, radials: typing.Collection[int], size: int, importance: int):
|
||||||
import theater.base
|
import theater.base
|
||||||
|
|
||||||
self.position = point
|
self.name = airport.name
|
||||||
|
self.position = airport.position
|
||||||
|
self.airport = airport
|
||||||
self.size = size
|
self.size = size
|
||||||
self.importance = importance
|
self.importance = importance
|
||||||
self.captured = False
|
self.captured = False
|
||||||
self.radials = radials
|
self.radials = radials
|
||||||
|
self.connected_points = []
|
||||||
self.base = theater.base.Base()
|
self.base = theater.base.Base()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
def connect(self, to):
|
def connect(self, to):
|
||||||
self.connected_points.append(to)
|
self.connected_points.append(to)
|
||||||
|
|
||||||
|
def is_connected(self, to) -> bool:
|
||||||
|
return to in self.connected_points
|
||||||
|
|
||||||
def find_radial(self, heading: int, ignored_radial: int = None):
|
def find_radial(self, heading: int, ignored_radial: int = None):
|
||||||
closest_radial = 0
|
closest_radial = 0
|
||||||
closest_radial_delta = 360
|
closest_radial_delta = 360
|
||||||
@ -39,7 +49,7 @@ class ControlPoint:
|
|||||||
return closest_radial
|
return closest_radial
|
||||||
|
|
||||||
def conflict_attack(self, from_cp, attacker: Country, defender: Country) -> Conflict:
|
def conflict_attack(self, from_cp, attacker: Country, defender: Country) -> Conflict:
|
||||||
cp = from_cp # type: ControlPoint
|
cp = from_cp # type: ControlPoint
|
||||||
|
|
||||||
attack_radial = self.find_radial(cp.position.heading_between_point(self.position))
|
attack_radial = self.find_radial(cp.position.heading_between_point(self.position))
|
||||||
defense_radial = self.find_radial(self.position.heading_between_point(cp.position), ignored_radial=attack_radial)
|
defense_radial = self.find_radial(self.position.heading_between_point(cp.position), ignored_radial=attack_radial)
|
||||||
|
|||||||
11
theater/start_generator.py
Normal file
11
theater/start_generator.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import typing
|
||||||
|
import random
|
||||||
|
|
||||||
|
import dcs
|
||||||
|
|
||||||
|
from theater.controlpoint import *
|
||||||
|
from theater.base import *
|
||||||
|
from theater.conflicttheater import *
|
||||||
|
|
||||||
|
def generate_initial(theater: ConflictTheater):
|
||||||
|
pass
|
||||||
0
ui/__init__.py
Normal file
0
ui/__init__.py
Normal file
62
ui/basemenu.py
Normal file
62
ui/basemenu.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from shop import db
|
||||||
|
|
||||||
|
from tkinter import *
|
||||||
|
from ui.window import *
|
||||||
|
from ui.eventmenu import *
|
||||||
|
|
||||||
|
from game.game import *
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMenu:
|
||||||
|
def __init__(self, window: Window, parent, game: Game, base: Base):
|
||||||
|
self.window = window
|
||||||
|
self.frame = window.right_pane
|
||||||
|
self.parent = parent
|
||||||
|
self.game = game
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def go_back(self):
|
||||||
|
self.parent.update()
|
||||||
|
|
||||||
|
def buy(self, unit_type):
|
||||||
|
def action():
|
||||||
|
price = db.PRICES[unit_type]
|
||||||
|
if self.game.budget > price:
|
||||||
|
self.base.commision_units({unit_type: 1})
|
||||||
|
self.game.budget -= price
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
return action
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.window.clear_right_pane()
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
def purchase_row(unit_type, unit_price):
|
||||||
|
nonlocal row
|
||||||
|
|
||||||
|
existing_units = self.base.total_units_of_type(unit_type)
|
||||||
|
Label(self.frame, text=db.unit_type_name(unit_type)).grid(column=0, row=row, sticky=W)
|
||||||
|
Label(self.frame, text="{}m {}".format(unit_price, existing_units)).grid(column=1, row=row)
|
||||||
|
Button(self.frame, text="Buy", command=self.buy(unit_type)).grid(column=2, row=row)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
units = {
|
||||||
|
CAP: db.find_unittype(CAP, self.game.player),
|
||||||
|
CAS: db.find_unittype(CAS, self.game.player),
|
||||||
|
FighterSweep: db.find_unittype(FighterSweep, self.game.player),
|
||||||
|
AirDefence: db.find_unittype(AirDefence, self.game.player),
|
||||||
|
}
|
||||||
|
|
||||||
|
Label(self.frame, text="Budget: {}m".format(self.game.budget)).grid(column=0, row=row, sticky=W)
|
||||||
|
Button(self.frame, text="Back", command=self.go_back).grid(column=2, row=row)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
for task_type, units in units.items():
|
||||||
|
Label(self.frame, text="{}".format(db.task_name(task_type))).grid(column=0, row=row, columnspan=3); row += 1
|
||||||
|
for unit_type in units:
|
||||||
|
purchase_row(unit_type, db.PRICES[unit_type])
|
||||||
|
|
||||||
98
ui/eventmenu.py
Normal file
98
ui/eventmenu.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
from tkinter import *
|
||||||
|
from ui.window import *
|
||||||
|
from ui.eventresultsmenu import *
|
||||||
|
|
||||||
|
from game.game import *
|
||||||
|
from game import event
|
||||||
|
|
||||||
|
|
||||||
|
class EventMenu:
|
||||||
|
aircraft_scramble_entries = None # type: typing.Dict[PlaneType, Entry]
|
||||||
|
armor_scramble_entries = None # type: typing.Dict[Armor, Entry]
|
||||||
|
|
||||||
|
def __init__(self, window: Window, parent, game: Game, event: event.Event):
|
||||||
|
self.window = window
|
||||||
|
self.frame = self.window.right_pane
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.event = event
|
||||||
|
self.game = game
|
||||||
|
|
||||||
|
self.aircraft_scramble_entries = {}
|
||||||
|
self.armor_scramble_entries = {}
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
scrambled_aircraft = {}
|
||||||
|
scrambled_sweep = {}
|
||||||
|
scrambled_cas = {}
|
||||||
|
for unit_type, field in self.aircraft_scramble_entries.items():
|
||||||
|
value = field.get()
|
||||||
|
if value and int(value) > 0:
|
||||||
|
amount = int(value)
|
||||||
|
task = db.unit_task(unit_type)
|
||||||
|
|
||||||
|
scrambled_aircraft[unit_type] = amount
|
||||||
|
if task == CAS:
|
||||||
|
scrambled_cas[unit_type] = amount
|
||||||
|
elif task == FighterSweep:
|
||||||
|
scrambled_sweep[unit_type] = amount
|
||||||
|
|
||||||
|
scrambled_armor = {}
|
||||||
|
for unit_type, field in self.armor_scramble_entries.items():
|
||||||
|
value = field.get()
|
||||||
|
if value and int(value) > 0:
|
||||||
|
scrambled_armor[unit_type] = int(value)
|
||||||
|
|
||||||
|
if type(self.event) is CaptureEvent:
|
||||||
|
e = self.event # type: CaptureEvent
|
||||||
|
if self.game.is_player_attack(self.event):
|
||||||
|
e.player_attacking(cas=scrambled_cas,
|
||||||
|
escort=scrambled_sweep,
|
||||||
|
armor=scrambled_armor)
|
||||||
|
else:
|
||||||
|
e.player_defending(interceptors=scrambled_aircraft)
|
||||||
|
elif type(self.event) is InterceptEvent:
|
||||||
|
e = self.event # type: InterceptEvent
|
||||||
|
if self.game.is_player_attack(self.event):
|
||||||
|
e.player_attacking(interceptors=scrambled_aircraft)
|
||||||
|
else:
|
||||||
|
e.player_defending(escort=scrambled_aircraft)
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.game.initiate_event(self.event)
|
||||||
|
EventResultsMenu(self.window, self.parent, self.game, self.event)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.window.clear_right_pane()
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
def label(text):
|
||||||
|
nonlocal row
|
||||||
|
Label(self.frame, text=text).grid(column=0, row=0)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def scrable_row(unit_type, unit_count):
|
||||||
|
nonlocal row
|
||||||
|
Label(self.frame, text="{} ({})".format(unit_type.id and unit_type.id or unit_type.name, unit_count)).grid(column=0, row=row)
|
||||||
|
e = Entry(self.frame)
|
||||||
|
e.grid(column=1, row=row)
|
||||||
|
|
||||||
|
self.aircraft_scramble_entries[unit_type] = e
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
base = None # type: Base
|
||||||
|
if self.event.attacker.name == self.game.player:
|
||||||
|
base = self.event.from_cp.base
|
||||||
|
else:
|
||||||
|
base = self.event.to_cp.base
|
||||||
|
|
||||||
|
label("Aircraft")
|
||||||
|
for unit, count in base.aircraft.items():
|
||||||
|
scrable_row(unit, count)
|
||||||
|
|
||||||
|
Button(self.frame, text="Commit", command=self.start).grid(column=0, row=row)
|
||||||
61
ui/eventresultsmenu.py
Normal file
61
ui/eventresultsmenu.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
from tkinter import *
|
||||||
|
from ui.window import *
|
||||||
|
|
||||||
|
from userdata.debriefing_parser import *
|
||||||
|
from game.game import *
|
||||||
|
from game import event
|
||||||
|
|
||||||
|
|
||||||
|
class EventResultsMenu:
|
||||||
|
def __init__(self, window: Window, parent, game: Game, event: Event):
|
||||||
|
self.window = window
|
||||||
|
self.frame = window.right_pane
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.game = game
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def simulate_result(self, player_factor: float, enemy_factor: float, result: bool):
|
||||||
|
def action():
|
||||||
|
debriefing = Debriefing()
|
||||||
|
|
||||||
|
def count_planes(groups: typing.List[FlyingGroup], mult: float) -> typing.Dict[UnitType, int]:
|
||||||
|
result = {}
|
||||||
|
for group in groups:
|
||||||
|
for unit in group.units:
|
||||||
|
result[unit.type] = result.get(unit.type, 0) + 1 * mult
|
||||||
|
|
||||||
|
return {x: math.floor(y) for x, y in result.items()}
|
||||||
|
|
||||||
|
player_planes = self.event.operation.mission.country(self.game.player).plane_group
|
||||||
|
enemy_planes = self.event.operation.mission.country(self.game.enemy).plane_group
|
||||||
|
|
||||||
|
player_losses = count_planes(player_planes, player_factor)
|
||||||
|
enemy_losses = count_planes(enemy_planes, enemy_factor)
|
||||||
|
|
||||||
|
debriefing.destroyed_units = {
|
||||||
|
self.game.player: player_losses,
|
||||||
|
self.game.enemy: enemy_losses,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.game.finish_event(self.event, debriefing)
|
||||||
|
self.game.pass_turn()
|
||||||
|
self.parent.update()
|
||||||
|
|
||||||
|
return action
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
|
Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1, True)).grid(row=0, column=0)
|
||||||
|
Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1, False)).grid(row=0, column=1)
|
||||||
|
|
||||||
|
Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5, True)).grid(row=1, column=0)
|
||||||
|
Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5, False)).grid(row=1, column=1)
|
||||||
|
|
||||||
|
Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0, True)).grid(row=2, column=0)
|
||||||
|
Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0, False)).grid(row=2, column=1)
|
||||||
78
ui/mainmenu.py
Normal file
78
ui/mainmenu.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from tkinter import *
|
||||||
|
from tkinter.ttk import *
|
||||||
|
|
||||||
|
from ui.window import *
|
||||||
|
from ui.eventmenu import *
|
||||||
|
from ui.basemenu import *
|
||||||
|
|
||||||
|
from game.game import *
|
||||||
|
|
||||||
|
class MainMenu:
|
||||||
|
def __init__(self, game: Game, window: Window):
|
||||||
|
self.image = PhotoImage(file="resources/caumap.gif")
|
||||||
|
self.game = game
|
||||||
|
self.window = window
|
||||||
|
|
||||||
|
map = Label(window.left_pane, image=self.image)
|
||||||
|
map.grid(column=0, row=0)
|
||||||
|
|
||||||
|
self.frame = self.window.right_pane
|
||||||
|
self.frame.grid_columnconfigure(0, weight=1)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def pass_turn(self):
|
||||||
|
self.game.pass_turn()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def start_event(self, event) -> typing.Callable:
|
||||||
|
return lambda: EventMenu(self.window, self, self.game, event)
|
||||||
|
|
||||||
|
def go_cp(self, cp: ControlPoint) -> typing.Callable:
|
||||||
|
return lambda: BaseMenu(self.window, self, self.game, cp.base)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
|
||||||
|
def label(text):
|
||||||
|
nonlocal row
|
||||||
|
Label(self.frame, text=text).grid(column=0, row=row, sticky=NW)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def event_button(event, text):
|
||||||
|
nonlocal row
|
||||||
|
Button(self.frame, text=text, command=self.start_event(event)).grid(column=0, 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(column=0, row=row, sticky=NW)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=0, row=row, sticky=N); row += 1
|
||||||
|
label("Budget: {}m".format(self.game.budget))
|
||||||
|
|
||||||
|
for event in self.game.events:
|
||||||
|
event_button(event, "{} {}".format(event.attacker.name != self.game.player and "!" or " ", event))
|
||||||
|
|
||||||
|
Separator(self.frame, orient='horizontal').grid(column=0, row=row, sticky=EW); row += 1
|
||||||
|
for cp in self.game.theater.player_points():
|
||||||
|
cp_button(cp)
|
||||||
|
|
||||||
|
Separator(self.frame, orient='horizontal').grid(column=0, 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(column=0, row=row, sticky=NE)
|
||||||
|
row += 1
|
||||||
|
|
||||||
41
ui/window.py
Normal file
41
ui/window.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from tkinter import *
|
||||||
|
|
||||||
|
|
||||||
|
class Window:
|
||||||
|
image = None
|
||||||
|
left_pane = None # type: Frame
|
||||||
|
right_pane = None # type: Frame
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tk = Tk()
|
||||||
|
self.tk.grid_columnconfigure(0, weight=1)
|
||||||
|
self.tk.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.frame = Frame(self.tk)
|
||||||
|
self.frame.grid(column=0, row=0, sticky=NSEW)
|
||||||
|
self.frame.grid_columnconfigure(0, minsize=300)
|
||||||
|
self.frame.grid_columnconfigure(1, minsize=300)
|
||||||
|
|
||||||
|
self.frame.grid_columnconfigure(0, weight=0)
|
||||||
|
self.frame.grid_columnconfigure(1, weight=1)
|
||||||
|
self.frame.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.left_pane = Frame(self.frame)
|
||||||
|
self.left_pane.grid(column=0, row=0, sticky=NSEW)
|
||||||
|
self.right_pane = Frame(self.frame)
|
||||||
|
self.right_pane.grid(column=1, row=0, sticky=NSEW)
|
||||||
|
|
||||||
|
self.tk.focus()
|
||||||
|
|
||||||
|
def clear_right_pane(self):
|
||||||
|
for x in self.right_pane.winfo_children():
|
||||||
|
x.grid_remove()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for x in self.left_pane.winfo_children():
|
||||||
|
x.grid_remove()
|
||||||
|
for x in self.right_pane.winfo_children():
|
||||||
|
x.grid_remove()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.tk.mainloop()
|
||||||
12
userdata/debriefing_parser.py
Normal file
12
userdata/debriefing_parser.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import typing
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Debriefing:
|
||||||
|
def __init__(self):
|
||||||
|
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[str, int]]
|
||||||
|
|
||||||
|
def parse(self, path: str):
|
||||||
|
with open(path, "r") as f:
|
||||||
|
events = json.load(f)
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user