updates to strike missions; frontline operations invalid units placement fixed; minor UI updates

This commit is contained in:
Vasyl Horbachenko 2018-09-09 04:15:44 +03:00
parent e0d82da6cb
commit 4ba1dd87e8
25 changed files with 176 additions and 97 deletions

View File

@ -17,7 +17,7 @@ from game.game import Game
from theater import start_generator
from userdata import persistency, logging as logging_module
assert len(sys.argv) == 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
assert len(sys.argv) >= 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
persistency.setup(sys.argv[1])
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")]
@ -36,6 +36,9 @@ def is_version_compatible(save_version):
current_version = VERSION_STRING.split(".")
save_version = save_version.split(".")
if "--ignore-save" in sys.argv:
return False
if current_version[:2] == save_version[:2]:
return True

View File

@ -32,6 +32,7 @@ class BaseAttackEvent(Event):
if self.is_successfull(debriefing):
if self.from_cp.captured:
self.to_cp.captured = True
self.to_cp.ground_objects = []
self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name])
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)

View File

@ -127,7 +127,10 @@ class Game:
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
pass
else:
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
if event_class == StrikeEvent and not enemy_cp.ground_objects:
pass
else:
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
elif self._roll(enemy_probability, enemy_cp.base.strength):
if event_class in enemy_generated_types:
continue
@ -141,15 +144,15 @@ class Game:
if event_class == NavalInterceptEvent:
if player_cp.radials == LAND:
continue
elif event_class == StrikeEvent:
if not player_cp.ground_objects:
continue
elif event_class == BaseAttackEvent:
if enemy_cap_generated:
continue
if enemy_cp.base.total_armor == 0:
continue
enemy_cap_generated = True
elif event_class == AntiAAStrikeEvent:
if player_cp.base.total_aa == 0:
continue
enemy_generated_types.append(event_class)
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))

View File

@ -47,7 +47,13 @@ class StrikeOperation(Operation):
def generate(self):
targets = [] # type: typing.List[typing.Tuple[str, Point]]
category_counters = {} # type: typing.Dict[str, int]
processed_groups = []
for object in self.to_cp.ground_objects:
if object.group_id in processed_groups:
continue
processed_groups.append(object.group_id)
category_counters[object.category] = category_counters.get(object.category, 0) + 1
markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category])
targets.append((markpoint_name, object.position))
@ -68,4 +74,6 @@ class StrikeOperation(Operation):
clients={},
at=self.defenders_starting_position)
self.briefinggen.title = "Strike"
self.briefinggen.description = "Destroy infrastructure assets and military supplies in the region. Each building destroyed will lower targets strength."
super(StrikeOperation, self).generate()

View File

@ -100,8 +100,10 @@ class ArmorConflictGenerator:
attacker_groups = list(db.unitdict_split(attackers, single_fight_attackers_count))
for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups):
padding = FRONTLINE_CAS_PADDING if FRONTLINE_CAS_PADDING < self.conflict.distance else 0
position = self.conflict.position.point_from_heading(self.conflict.heading,
random.randint(FRONTLINE_CAS_PADDING, int(self.conflict.distance - FRONTLINE_CAS_PADDING)))
random.randint(padding, int(self.conflict.distance - padding)))
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
def generate_passengers(self, count: int):

View File

@ -152,36 +152,48 @@ class Conflict:
@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, right_position = None, None
left_position = center_position
for offset in range(0, int(FRONTLINE_LENGTH / 2), 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, int(FRONTLINE_LENGTH / 2), 1000):
pos = center_position.point_from_heading(_heading_sum(heading, 90), offset)
if not theater.is_on_land(pos):
break
else:
if not theater.is_on_land(center_position):
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, -90), theater)
if pos:
right_position = pos
center_position = pos
else:
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, +90), theater)
if pos:
left_position = pos
center_position = pos
print("{} - {} {}".format(from_cp, to_cp, center_position))
return left_position, _heading_sum(heading, 90), right_position.distance_to_point(left_position)
if left_position is None:
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater)
if right_position is None:
right_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, 90), theater)
return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position))
@classmethod
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
for _ in range(0, int(max_distance), 800):
for _ in range(3):
if theater.is_on_land(initial):
return initial
def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
pos = initial
for offset in range(0, int(max_distance), 500):
new_pos = initial.point_from_heading(heading, offset)
if theater.is_on_land(new_pos):
pos = new_pos
else:
return pos
initial = initial.random_point_within(1000, 1000)
return pos
initial = initial.point_from_heading(heading, 800)
@classmethod
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
pos = initial
for _ in range(0, int(max_distance), 500):
if theater.is_on_land(pos):
return pos
pos = pos.point_from_heading(heading, 500)
logging.info("Didn't find ground position!")
return None
@ -195,10 +207,10 @@ class Conflict:
distance = to_cp.size * GROUND_DISTANCE_FACTOR
attackers_location = position.point_from_heading(attack_heading, distance)
attackers_location = Conflict._find_ground_location(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
defenders_location = position.point_from_heading(defense_heading, distance)
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
return cls(
position=position,
@ -238,7 +250,7 @@ class Conflict:
def ground_attack_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
heading = random.choice(to_cp.radials)
initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE)
position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), 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)
@ -306,7 +318,7 @@ class Conflict:
distance = to_cp.size * GROUND_DISTANCE_FACTOR
defenders_location = position.point_from_heading(defense_heading, distance)
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
return cls(
position=position,
@ -351,7 +363,7 @@ class Conflict:
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
frontline_position, heading = cls.frontline_position(from_cp, to_cp)
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_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
if not dest:
radial = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
dest = to_cp.position.point_from_heading(radial, to_cp.size * GROUND_DISTANCE_FACTOR)

View File

@ -13,6 +13,7 @@ CATEGORY_MAPPING = {
"warehouse": [Warehouse.Warehouse],
"fuel": [Warehouse.Tank],
"ammo": [Warehouse.Ammunition_depot],
"farp": [Fortification.FARP_Tent],
}
@ -40,7 +41,7 @@ class GroundObjectsGenerator:
country=side,
name=ground_object.string_identifier,
_type=unit_type,
position=Point(*ground_object.location),
position=ground_object.position,
heading=ground_object.heading
)
@ -50,7 +51,7 @@ class GroundObjectsGenerator:
country=side,
name=ground_object.string_identifier,
_type=random.choice(CATEGORY_MAPPING[ground_object.category]),
position=Point(*ground_object.location),
position=ground_object.position,
heading=ground_object.heading
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,41 +4,66 @@ import typing
from game import db
from gen.groundobjectsgen import TheaterGroundObject
from dcs.mission import Mission
from dcs.terrain import PersianGulf
from dcs.mapping import Point
m = Mission()
m.load_file("./cau_groundobjects.miz")
result = {}
result_by_groups = {} # type: typing.Dict[int, TheaterGroundObject]
ids_counters = {}
def append_group(cp_id, category, group_id, object_id, position, heading):
global result
global result_by_groups
ground_object = TheaterGroundObject(category, cp_id, group_id, object_id, position, heading)
if cp_id not in result:
result[cp_id] = []
result[cp_id].append(ground_object)
result[cp_id].append(TheaterGroundObject(category, cp_id, group_id, object_id, position, heading))
result_by_groups_key = "{}_{}_{}".format(cp_id, category, group_id)
if result_by_groups_key not in result_by_groups:
result_by_groups[result_by_groups_key] = []
result_by_groups[result_by_groups_key].append(ground_object)
def parse_name(name: str) -> typing.Tuple:
args = str(name).split("|")
if len(args) == 3:
args.append("1")
args = str(name.split()[0]).split("|")
return args[0], int(args[1]), int(args[2]), int(args[3])
return args[0], int(args[1]), int(args[2])
for group in m.country("Russia").static_group + m.country("Russia").vehicle_group:
try:
category, cp_id, group_id, object_id = parse_name(str(group.name))
category, cp_id, group_id = parse_name(str(group.name))
except:
print("Failed to parse {}".format(group.name))
continue
append_group(cp_id, category, group_id, object_id, [group.position.x, group.position.y], group.units[0].heading)
ids_counters_key = "{}_{}".format(cp_id, group_id)
ids_counters[ids_counters_key] = ids_counters.get(ids_counters_key, 0) + 1
object_id = ids_counters[ids_counters_key]
append_group(cp_id, category, group_id, object_id, group.position, group.units[0].heading)
GROUP_TRESHOLD = 300
did_check_pairs = []
for group_id, objects_in_group in result_by_groups.items():
for a in objects_in_group:
for b in objects_in_group:
if (a, b) in did_check_pairs:
continue
did_check_pairs.append((a, b))
distance = a.position.distance_to_point(b.position)
if distance > GROUP_TRESHOLD:
print("Objects {} and {} in group {} are too far apart ({})!".format(a.string_identifier, b.string_identifier, group_id, distance))
print("Total {} objects".format(sum([len(x) for x in result.values()])))
with open("../cau_groundobjects.p", "wb") as f:
pickle.dump(result, f)

View File

@ -1,14 +1,14 @@
import pickle
from dcs.mission import Mission
from dcs.terrain import PersianGulf
m = Mission()
m.load_file("./gulf_terrain.miz")
for terrain in ["cau", "gulf"]:
m = Mission()
m.load_file("./{}_terrain.miz".format(terrain))
landmap = []
for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
landmap = []
for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
with open("../gulflandmap.p", "wb") as f:
pickle.dump(landmap, f)
with open("../{}landmap.p".format(terrain), "wb") as f:
pickle.dump(landmap, f)

View File

@ -13,7 +13,7 @@ class CaucasusTheater(ConflictTheater):
overview_image = "caumap.gif"
reference_points = {(-317948.32727306, 635639.37385346): (282.5, 319),
(-355692.3067714, 617269.96285781): (269, 352), }
landmap_poly = load_poly("resources\\caulandmap.p")
landmap = load_landmap("resources\\caulandmap.p")
daytime_map = {
"dawn": (6, 9),
"day": (9, 18),

View File

@ -4,7 +4,7 @@ import itertools
import dcs
from dcs.mapping import Point
from .landmap import ray_tracing
from .landmap import Landmap, poly_contains
from .controlpoint import ControlPoint
from .theatergroundobject import TheaterGroundObject
@ -52,12 +52,11 @@ class ConflictTheater:
reference_points = None # type: typing.Dict
overview_image = None # type: str
landmap_poly = None
landmap = None # type: landmap.Landmap
daytime_map = None # type: typing.Dict[str, typing.Tuple[int, int]]
def __init__(self):
self.controlpoints = []
self.groundobjects = []
def set_groundobject(self, dictionary: typing.Dict[int, typing.Collection[TheaterGroundObject]]):
for id, value in dictionary.items():
@ -73,14 +72,20 @@ class ConflictTheater:
self.controlpoints.append(point)
def is_on_land(self, point: Point) -> bool:
if not self.landmap_poly:
if not self.landmap:
return True
for poly in self.landmap_poly:
if ray_tracing(point.x, point.y, poly):
return True
# check first poly (main land poly)
if not poly_contains(point.x, point.y, self.landmap[0]):
return False
return False
# check others polys (exclusion zones from main)
for poly in self.landmap[1:]:
if poly_contains(point.x, point.y, poly):
# point is in one of the exclusion zones, meaning that it's in the lake or something
return False
return True
def player_points(self) -> typing.Collection[ControlPoint]:
return [point for point in self.controlpoints if point.captured]

View File

@ -9,7 +9,7 @@ from .theatergroundobject import TheaterGroundObject
class ControlPoint:
connected_points = [] # type: typing.List[ControlPoint]
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.Collection[TheaterGroundObject]
position = None # type: Point
captured = False
@ -26,6 +26,7 @@ class ControlPoint:
self.full_name = name
self.position = position
self.at = at
self.ground_objects = []
self.size = size
self.importance = importance

View File

@ -1,7 +1,10 @@
import pickle
import typing
Landmap = typing.Collection[typing.Collection[typing.Tuple[float, float]]]
def load_poly(filename: str):
def load_landmap(filename: str) -> Landmap:
try:
with open(filename, "rb") as f:
return pickle.load(f)
@ -9,7 +12,7 @@ def load_poly(filename: str):
return None
def ray_tracing(x, y, poly):
def poly_contains(x, y, poly):
n = len(poly)
inside = False
xints = 0.0
@ -25,3 +28,11 @@ def ray_tracing(x, y, poly):
inside = not inside
p1x, p1y = p2x, p2y
return inside
def poly_centroid(poly) -> typing.Tuple[float, float]:
x_list = [vertex[0] for vertex in poly]
y_list = [vertex[1] for vertex in poly]
x = sum(x_list) / len(poly)
y = sum(y_list) / len(poly)
return (x, y)

View File

@ -3,7 +3,7 @@ from dcs import mapping
from .conflicttheater import *
from .base import *
from .landmap import load_poly
from .landmap import load_landmap
class PersianGulfTheater(ConflictTheater):
@ -11,7 +11,7 @@ class PersianGulfTheater(ConflictTheater):
overview_image = "persiangulf.gif"
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321, 145),
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), }
landmap_poly = load_poly("resources\\gulflandmap.p")
landmap = load_landmap("resources\\gulflandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (8, 16),

View File

@ -8,6 +8,7 @@ NAME_BY_CATEGORY = {
"fuel": "Fuel depot",
"defense": "AA Defense Site",
"warehouse": "Warehouse",
"farp": "FARP",
}
ABBREV_NAME = {
@ -16,6 +17,7 @@ ABBREV_NAME = {
"fuel": "FUEL",
"defense": "AA",
"warehouse": "WARE",
"farp": "FARP",
}
@ -24,25 +26,21 @@ class TheaterGroundObject:
cp_id = 0
group_id = 0
heading = 0
location = None # type: typing.Collection[int]
position = None # type: Point
category = None # type: str
def __init__(self, category, cp_id, group_id, object_id, location, heading):
def __init__(self, category, cp_id, group_id, object_id, position, heading):
self.category = category
self.cp_id = cp_id
self.group_id = group_id
self.object_id = object_id
self.location = location
self.position = position
self.heading = heading
@property
def string_identifier(self):
return "{}|{}|{}|{}".format(self.category, self.cp_id, self.group_id, self.object_id)
@property
def position(self) -> Point:
return Point(*self.location)
@property
def name_abbrev(self) -> str:
return ABBREV_NAME[self.category]

View File

@ -1,3 +1,5 @@
import webbrowser
from tkinter import *
from tkinter.ttk import *
from .styles import STYLES
@ -64,7 +66,16 @@ class ConfigurationMenu(Menu):
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=4, column=1, sticky=E)
Button(body, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(row=5, column=1, sticky=E, pady=30)
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=6, column=1)
Label(body, text="Contributors: ", **STYLES["widget"]).grid(row=6, column=0, sticky=W)
Label(body, text="shdwp - author, maintainer", **STYLES["widget"]).grid(row=7, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/shdwp"), **STYLES["widget"]).grid(row=7, column=1, sticky=E)
Label(body, text="Khopa - contributions", **STYLES["widget"]).grid(row=8, column=0, sticky=W)
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=8, column=1, sticky=E)
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=10, column=1, pady=30)
def cheat_money(self):
self.game.budget += 200

View File

@ -13,6 +13,6 @@ class CorruptedSaveMenu(Menu):
def display(self):
self.window.clear_right_pane()
Label(text="Your save game was corrupted!", **STYLES["widget"]).grid(row=0, column=0)
Label(text="Your save game is either incompatible or was corrupted!", **STYLES["widget"]).grid(row=0, column=0)
Label(text="Please restore it by replacing \"liberation_save\" file with \"liberation_save_tmp\" to restore last saved copy.", **STYLES["widget"]).grid(row=1, column=0)
Label(text="You can find those files under user DCS directory.", **STYLES["widget"]).grid(row=2, column=0)
Label(text="You can find those files under user Saved Games\\DCS directory.", **STYLES["widget"]).grid(row=2, column=0)

View File

@ -8,14 +8,14 @@ from .styles import STYLES, RED
UNITTYPES_FOR_EVENTS = {
FrontlineAttackEvent: [CAS, PinpointStrike],
FrontlinePatrolEvent: [CAP, PinpointStrike],
BaseAttackEvent: [CAP, CAS, PinpointStrike],
StrikeEvent: [CAP, CAS],
InterceptEvent: [CAP],
InsurgentAttackEvent: [CAS],
NavalInterceptEvent: [CAS],
InfantryTransportEvent: [Embarking],
FrontlineAttackEvent: [[CAS, PinpointStrike], [CAP]],
FrontlinePatrolEvent: [[CAP, PinpointStrike], [CAP]],
BaseAttackEvent: [[CAP, CAS, PinpointStrike], [CAP, CAS, PinpointStrike]],
StrikeEvent: [[CAP, CAS], [CAP]],
InterceptEvent: [[CAP], [CAP]],
InsurgentAttackEvent: [[CAS], [CAP]],
NavalInterceptEvent: [[CAS], [CAP]],
InfantryTransportEvent: [[Embarking], [CAP]],
}
AI_BAN_FOR_EVENTS = {
@ -115,7 +115,8 @@ class EventMenu(Menu):
Label(self.frame, text="Client slots", **STYLES["widget"]).grid(row=row, column=3, columnspan=2)
row += 1
filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__]
filter_attackers_index = 0 if self.game.is_player_attack(self.event) else 1
filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__][filter_attackers_index]
for unit_type, count in self.base.aircraft.items():
if filter_to and db.unit_task(unit_type) not in filter_to:
continue
@ -262,12 +263,6 @@ class EventMenu(Menu):
elif type(self.event) is NavalInterceptEvent:
e = self.event # type: NavalInterceptEvent
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
else:
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
elif type(self.event) is AntiAAStrikeEvent:
e = self.event # type: AntiAAStrikeEvent
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
else:

View File

@ -86,6 +86,10 @@ class EventResultsMenu(Menu):
header("Enemy losses")
if self.debriefing.destroyed_objects:
Label(self.frame, text="Ground assets", **STYLES["widget"]).grid(row=row)
Label(self.frame, text="{}".format(len(self.debriefing.destroyed_objects)), **STYLES["widget"]).grid(column=1, row=row)
for unit_type, count in self.enemy_losses.items():
if count == 0:
continue

View File

@ -51,10 +51,9 @@ class MainMenu(Menu):
nonlocal row, body
frame = LabelFrame(body, **STYLES["label-frame"])
frame.grid(row=row, sticky=NSEW)
Message(frame, text="{}{} at {}".format(
Message(frame, text="{}{}".format(
event.defender_name == self.game.player and "Enemy attacking: " or "",
event,
event.to_cp,
event
), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=NSEW)
Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E)
row += 1
@ -63,9 +62,8 @@ class MainMenu(Menu):
nonlocal row, body
Label(body, text=text, **STYLES["strong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(pady,0)); row += 1
#Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
events = self.game.events
events.sort(key=lambda x: x.to_cp.name)
events.sort(key=lambda x: x.from_cp.name)
events.sort(key=lambda x: x.informational and 2 or (self.game.is_player_attack(x) and 1 or 0))
@ -74,7 +72,7 @@ class MainMenu(Menu):
for event in events:
if not event.informational:
if self.game.is_player_attack(event):
new_destination = event.from_cp.name
new_destination = "From {} to {}".format(event.from_cp.name, event.to_cp.name)
else:
new_destination = "Enemy attack"
if destination != new_destination:

View File

@ -81,6 +81,7 @@ class OverviewCanvas:
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
frontline_pos, heading, distance = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
distance = max(distance, 1000)
start_coords = self.transform_point(frontline_pos, treshold=10)
end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60)