From 9dbc9a8a561f7cb40ccc2c12eb4babb5f7ce0f0c Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Sun, 29 Jul 2018 04:16:39 +0300 Subject: [PATCH] prompt window with logs on raised exception; minor UI updates; minor fixes --- __init__.py | 2 +- game/db.py | 3 ++ game/event/antiaastrike.py | 2 +- game/event/baseattack.py | 2 +- game/event/frontlineattack.py | 2 +- game/event/frontlinepatrol.py | 2 +- game/event/infantrytransport.py | 2 +- game/event/insurgentattack.py | 2 +- game/event/intercept.py | 2 +- game/event/navalintercept.py | 2 +- game/game.py | 9 ++++- gen/aircraft.py | 12 +++--- gen/armor.py | 4 +- gen/conflictgen.py | 3 +- gen/environmentgen.py | 3 +- gen/shipgen.py | 4 +- start.bat | 2 +- theater/base.py | 5 ++- theater/start_generator.py | 2 +- ui/eventmenu.py | 1 + ui/eventresultsmenu.py | 71 ++++++++++++++++++++++----------- ui/mainmenu.py | 26 +++++++++--- userdata/debriefing.py | 8 ++-- userdata/logging.py | 29 ++++++++++++++ userdata/persistency.py | 3 +- 25 files changed, 146 insertions(+), 57 deletions(-) create mode 100644 userdata/logging.py diff --git a/__init__.py b/__init__.py index 92d39a9c..a4e2836b 100755 --- a/__init__.py +++ b/__init__.py @@ -14,7 +14,7 @@ import ui.corruptedsavemenu from game.game import Game from theater import start_generator -from userdata import persistency +from userdata import persistency, logging persistency.setup(sys.argv[1]) diff --git a/game/db.py b/game/db.py index c947a366..fc80fecb 100644 --- a/game/db.py +++ b/game/db.py @@ -463,6 +463,9 @@ def unitdict_split(unit_dict: UnitsDict, count: int): def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict: + if total_count == 0: + return {} + groups = list(unitdict_split(unit_dict, total_count)) if len(groups) > 0: return groups[0] diff --git a/game/event/antiaastrike.py b/game/event/antiaastrike.py index 46b993fb..66526adc 100644 --- a/game/event/antiaastrike.py +++ b/game/event/antiaastrike.py @@ -17,7 +17,7 @@ class AntiAAStrikeEvent(Event): targets = None # type: db.ArmorDict def __str__(self): - return "Anti-AA strike from {} at {}".format(self.from_cp, self.to_cp) + return "Anti-AA strike" def is_successfull(self, debriefing: Debriefing): total_targets = sum(self.targets.values()) diff --git a/game/event/baseattack.py b/game/event/baseattack.py index e93aba31..0650ec0c 100644 --- a/game/event/baseattack.py +++ b/game/event/baseattack.py @@ -16,7 +16,7 @@ class BaseAttackEvent(Event): STRENGTH_RECOVERY = 0.55 def __str__(self): - return "Base attack from {} to {}".format(self.from_cp, self.to_cp) + return "Base attack" def is_successfull(self, debriefing: Debriefing): alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index 6efb896d..7a4ce52b 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -25,7 +25,7 @@ class FrontlineAttackEvent(Event): return "{} vehicles".format(self.to_cp.base.assemble_count()) def __str__(self): - return "Frontline attack from {} at {}".format(self.from_cp, self.to_cp) + return "Frontline attack" def is_successfull(self, debriefing: Debriefing): alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) diff --git a/game/event/frontlinepatrol.py b/game/event/frontlinepatrol.py index 1643691a..1fff394d 100644 --- a/game/event/frontlinepatrol.py +++ b/game/event/frontlinepatrol.py @@ -23,7 +23,7 @@ class FrontlinePatrolEvent(Event): return "{} aircraft + ? CAS".format(self.to_cp.base.scramble_count(self.game.settings.multiplier * self.ESCORT_FACTOR, CAP)) def __str__(self): - return "Frontline CAP from {} at {}".format(self.from_cp, self.to_cp) + return "Frontline CAP" """ def is_successfull(self, debriefing: Debriefing): diff --git a/game/event/infantrytransport.py b/game/event/infantrytransport.py index cc1d9652..fb180513 100644 --- a/game/event/infantrytransport.py +++ b/game/event/infantrytransport.py @@ -16,7 +16,7 @@ class InfantryTransportEvent(Event): STRENGTH_INFLUENCE = 0.3 def __str__(self): - return "Frontline transport troops to {}".format(self.to_cp) + return "Frontline transport troops" def is_successfull(self, debriefing: Debriefing): return True diff --git a/game/event/insurgentattack.py b/game/event/insurgentattack.py index a530507e..ea1a6eab 100644 --- a/game/event/insurgentattack.py +++ b/game/event/insurgentattack.py @@ -19,7 +19,7 @@ class InsurgentAttackEvent(Event): return "" def __str__(self): - return "Destroy insurgents at {}".format(self.to_cp) + return "Destroy insurgents" def is_successfull(self, debriefing: Debriefing): killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) diff --git a/game/event/intercept.py b/game/event/intercept.py index 76b1f276..2449bfe9 100644 --- a/game/event/intercept.py +++ b/game/event/intercept.py @@ -20,7 +20,7 @@ class InterceptEvent(Event): transport_unit = None # type: FlyingType def __str__(self): - return "Intercept from {} at {}".format(self.from_cp, self.to_cp) + return "Intercept" def _enemy_scramble_multiplier(self) -> float: is_global = self.from_cp.is_global or self.to_cp.is_global diff --git a/game/event/navalintercept.py b/game/event/navalintercept.py index 55ef552a..75e253b4 100644 --- a/game/event/navalintercept.py +++ b/game/event/navalintercept.py @@ -24,7 +24,7 @@ class NavalInterceptEvent(Event): return max(int(factor), 1) def __str__(self) -> str: - return "Naval intercept at {}".format(self.to_cp) + return "Naval intercept" @property def threat_description(self): diff --git a/game/game.py b/game/game.py index 13df78a5..3738d263 100644 --- a/game/game.py +++ b/game/game.py @@ -1,3 +1,4 @@ +import logging import typing import random import math @@ -171,7 +172,7 @@ class Game: if points_to_spend > 0: unittypes = self.commision_unit_types(cp, for_task) d = {random.choice(unittypes): points_to_spend} - print("Commision {}: {}".format(cp, d)) + logging.info("Commision {}: {}".format(cp, d)) cp.base.commision_units(d) @property @@ -204,10 +205,13 @@ class Game: def initiate_event(self, event: Event): assert event in self.events + logging.info("Generating {} (regular)".format(event)) event.generate() + logging.info("Generating {} (quick)".format(event)) event.generate_quick() def finish_event(self, event: Event, debriefing: Debriefing): + logging.info("Finishing event {}".format(event)) event.commit(debriefing) if event.is_successfull(debriefing): self.budget += event.bonus() @@ -215,7 +219,7 @@ class Game: if event in self.events: self.events.remove(event) else: - print("finish_event: event not in the events!") + logging.info("finish_event: event not in the events!") def is_player_attack(self, event): if isinstance(event, Event): @@ -224,6 +228,7 @@ class Game: return event.name == self.player def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None): + logging.info("Pass turn") for event in self.events: event.skip() diff --git a/gen/aircraft.py b/gen/aircraft.py index 05bf6ae9..33b9511b 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1,3 +1,5 @@ +import logging + from game import db from game.settings import Settings from .conflictgen import * @@ -14,7 +16,7 @@ SPREAD_DISTANCE_FACTOR = 1, 2 ESCORT_ENGAGEMENT_MAX_DIST = 100000 WORKAROUND_WAYP_DIST = 1000 -WARM_START_HELI_AIRSPEED = 200 +WARM_START_HELI_AIRSPEED = 120 WARM_START_HELI_ALT = 1000 WARM_START_ALTITUDE = 3000 @@ -106,7 +108,7 @@ class AircraftConflictGenerator: assert count > 0 assert unit is not None - print("airgen: {} for {} at {}".format(unit_type, side.id, airport)) + logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport)) return self.m.flight_group_from_airport( country=side, name=name, @@ -121,7 +123,7 @@ class AircraftConflictGenerator: assert count > 0 assert unit is not None - if unit_type in helicopters.helicopter_map: + if unit_type in helicopters.helicopter_map.values(): alt = WARM_START_HELI_ALT + random.randint(50, 200) speed = WARM_START_HELI_AIRSPEED else: @@ -130,7 +132,7 @@ class AircraftConflictGenerator: pos = Point(at.x + random.randint(100, 200), at.y + random.randint(100, 200)) - print("airgen: {} for {} at {} at {}".format(unit_type, side.id, alt, speed)) + logging.info("airgen: {} for {} at {} at {}".format(unit_type, side.id, alt, speed)) return self.m.flight_group( country=side, name=name, @@ -147,7 +149,7 @@ class AircraftConflictGenerator: assert count > 0 assert unit is not None - print("airgen: {} for {} at carrier {}".format(unit_type, side.id, at)) + logging.info("airgen: {} for {} at carrier {}".format(unit_type, side.id, at)) return self.m.flight_group_from_unit( country=side, name=name, diff --git a/gen/armor.py b/gen/armor.py index 51feca4f..7ba55402 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,3 +1,5 @@ +import logging + from random import randint from itertools import zip_longest @@ -34,7 +36,7 @@ class ArmorConflictGenerator: def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None): for c in range(count): - print("armorgen: {} for {}".format(unit, side.id)) + logging.info("armorgen: {} for {}".format(unit, side.id)) group = self.m.vehicle_group( side, namegen.next_unit_name(side, unit), diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 712f5f81..b7dc71fe 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -1,3 +1,4 @@ +import logging import typing import pdb import dcs @@ -182,7 +183,7 @@ class Conflict: initial = initial.point_from_heading(heading, 800) - print("Didn't find ground position!") + logging.info("Didn't find ground position!") return None @classmethod diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 9dc4b648..6f1a3cdd 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -1,3 +1,4 @@ +import logging import typing import random from datetime import datetime, timedelta, time @@ -68,7 +69,7 @@ class EnviromentGenerator: weather_type = k break - print("generated weather {}".format(weather_type)) + logging.info("generated weather {}".format(weather_type)) if weather_type == 1: self.mission.weather.heavy_rain() elif weather_type == 2: diff --git a/gen/shipgen.py b/gen/shipgen.py index 48797acb..5103d187 100644 --- a/gen/shipgen.py +++ b/gen/shipgen.py @@ -1,3 +1,5 @@ +import logging + from game import db from .conflictgen import * from .naming import * @@ -28,7 +30,7 @@ class ShipGenerator: def generate_cargo(self, units: db.ShipDict) -> typing.Collection[ShipGroup]: groups = [] for unit_type, unit_count in units.items(): - print("shipgen: {} ({}) for {}".format(unit_type, unit_count, self.conflict.defenders_side)) + logging.info("shipgen: {} ({}) for {}".format(unit_type, unit_count, self.conflict.defenders_side)) group = self.m.ship_group( country=self.conflict.defenders_side, name=namegen.next_unit_name(self.conflict.defenders_side, unit_type), diff --git a/start.bat b/start.bat index bf73def9..b3235fcd 100644 --- a/start.bat +++ b/start.bat @@ -1 +1 @@ -py.exe __init__.py "%UserProfile%" > logs.txt 2>&1 +py.exe __init__.py "%UserProfile%" diff --git a/theater/base.py b/theater/base.py index b6b79ebc..46850300 100644 --- a/theater/base.py +++ b/theater/base.py @@ -1,3 +1,4 @@ +import logging import typing import math import itertools @@ -53,7 +54,7 @@ class Base: def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict: if count <= 0: - print("{}: no units for {}".format(self, for_type)) + logging.info("{}: no units for {}".format(self, for_type)) return {} sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]] @@ -74,7 +75,7 @@ class Base: assert result_unit_count > 0 result[unit_type] = result.get(unit_type, 0) + result_unit_count - print("{} for {} ({}): {}".format(self, for_type, count, result)) + logging.info("{} for {} ({}): {}".format(self, for_type, count, result)) return result def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[PlaneType, int]: diff --git a/theater/start_generator.py b/theater/start_generator.py index 44c628c2..c7748888 100644 --- a/theater/start_generator.py +++ b/theater/start_generator.py @@ -35,5 +35,5 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie count = max(COUNT_BY_TASK[task] * multiplier * (1+count_log), 1) count_per_type = max(int(float(count) / len(unittypes)), 1) for unit_type in unittypes: - print("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type)) + logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type)) cp.base.commision_units({unit_type: count_per_type}) diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 35b5e9a8..9d352d57 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -9,6 +9,7 @@ from game.event import * UNITTYPES_FOR_EVENTS = { FrontlineAttackEvent: [CAS, PinpointStrike], FrontlinePatrolEvent: [CAP, PinpointStrike], + BaseAttackEvent: [CAP, CAS, PinpointStrike], InterceptEvent: [CAP], InsurgentAttackEvent: [CAS], NavalInterceptEvent: [CAS], diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index 3b1d6cb8..7bfe1662 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -22,21 +22,20 @@ class EventResultsMenu(Menu): self.window.clear_right_pane() if not self.finished: - """ - For debugging purposes - - Button(self.frame, text="no losses, succ", command=self.simulate_result(0, 1)).grid() - Button(self.frame, text="no losses, fail", command=self.simulate_result(0, 1)).grid(row=1, column=1) - - Button(self.frame, text="half losses, succ", command=self.simulate_result(0.5, 0.5)).grid(row=2, ) - Button(self.frame, text="half losses, fail", command=self.simulate_result(0.5, 0.5)).grid(row=2, column=1) - - Button(self.frame, text="full losses, succ", command=self.simulate_result(1, 0)).grid(row=3, ) - Button(self.frame, text="full losses, fail", command=self.simulate_result(1, 0)).grid(row=3, column=1) - """ - Label(self.frame, text="Play the mission and save debriefing to").grid(row=0, column=0) Label(self.frame, text=debriefing_directory_location()).grid(row=1, column=0) + + """ + For debugging purposes + """ + + row = 3 + Separator(self.frame, orient=HORIZONTAL).grid(row=row, sticky=EW); row += 1 + Label(self.frame, text="Cheat operation results: ").grid(row=row); row += 1 + Button(self.frame, text="full enemy losses", command=self.simulate_result(0, 1)).grid(row=row); row += 1 + Button(self.frame, text="full player losses", command=self.simulate_result(1, 0)).grid(row=row); row += 1 + Button(self.frame, text="some enemy losses", command=self.simulate_result(0, 0.8)).grid(row=row); row += 1 + Button(self.frame, text="some player losses", command=self.simulate_result(0.8, 0)).grid(row=row); row += 1 else: row = 0 if self.event.is_successfull(self.debriefing): @@ -81,27 +80,53 @@ class EventResultsMenu(Menu): def action(): debriefing = Debriefing({}) - def count_planes(groups: typing.List[FlyingGroup], mult: float) -> typing.Dict[UnitType, int]: + def count(country: Country) -> typing.Dict[UnitType, int]: result = {} - for group in groups: + for g in country.plane_group + country.vehicle_group + country.helicopter_group + country.ship_group: + group = g # type: Group for unit in group.units: - result[unit.unit_type] = result.get(unit.unit_type, 0) + 1 * mult + unit_type = None + if isinstance(unit, Vehicle): + unit_type = vehicle_map[unit.type] + elif isinstance(unit, Ship): + unit_type = ship_map[unit.type] + else: + unit_type = unit.unit_type - return {x: math.ceil(y) for x, y in result.items() if y >= 1} + if unit_type in db.EXTRA_AA.values(): + continue - player_planes = self.event.operation.mission.country(self.game.player).plane_group - enemy_planes = self.event.operation.mission.country(self.game.enemy).plane_group + result[unit_type] = result.get(unit_type, 0) + 1 - self.player_losses = count_planes(player_planes, player_factor) - self.enemy_losses = count_planes(enemy_planes, enemy_factor) + return result + + player = self.event.operation.mission.country(self.game.player) + enemy = self.event.operation.mission.country(self.game.enemy) + + alive_player_units = count(player) + alive_enemy_units = count(enemy) + + destroyed_player_units = db.unitdict_restrict_count(alive_player_units, math.ceil(sum(alive_player_units.values()) * player_factor)) + destroyed_enemy_units = db.unitdict_restrict_count(alive_enemy_units, math.ceil(sum(alive_enemy_units.values()) * enemy_factor)) + + alive_player_units = {k: v - destroyed_player_units.get(k, 0) for k, v in alive_player_units.items()} + alive_enemy_units = {k: v - destroyed_enemy_units.get(k, 0) for k, v in alive_enemy_units.items()} + + debriefing.alive_units = { + enemy.name: alive_enemy_units, + player.name: alive_player_units, + } debriefing.destroyed_units = { - self.game.player: self.player_losses, - self.game.enemy: self.enemy_losses, + player.name: destroyed_player_units, + enemy.name: destroyed_enemy_units, } self.finished = True self.debriefing = debriefing + self.player_losses = debriefing.destroyed_units.get(self.game.player, {}) + self.enemy_losses = debriefing.destroyed_units.get(self.game.enemy, {}) + self.game.finish_event(self.event, debriefing) self.display() self.game.pass_turn() diff --git a/ui/mainmenu.py b/ui/mainmenu.py index bf19d5b0..b7aeb2be 100644 --- a/ui/mainmenu.py +++ b/ui/mainmenu.py @@ -34,13 +34,18 @@ class MainMenu(Menu): def event_button(event): nonlocal row - Message(self.frame, text="{}{}".format( + Message(self.frame, text="{}{} at {}".format( event.defender_name == self.game.player and "Enemy attacking: " or "", - event + event, + event.to_cp, ), aspect=1600).grid(column=0, row=row, sticky=NW) - Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S) - row += 1 - Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1 + Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S); row += 1 + + def destination_header(text, separator=True): + nonlocal row + if separator: + Separator(self.frame, orient=HORIZONTAL).grid(row=row, sticky=EW); row += 1 + Label(self.frame, text=text).grid(column=0, row=row, sticky=N); row += 1 Button(self.frame, text="Configuration", command=self.configuration_menu).grid(column=0, row=0, sticky=NE) Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=0, row=0, sticky=NW) @@ -48,9 +53,20 @@ class MainMenu(Menu): Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1 events = self.game.events + 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)) + destination = None for event in events: + if not event.informational: + if self.game.is_player_attack(event): + new_destination = event.from_cp.name + else: + new_destination = "Enemy attack" + if destination != new_destination: + destination_header(new_destination, destination is not None) + destination = new_destination + if event.informational: label(str(event)) else: diff --git a/userdata/debriefing.py b/userdata/debriefing.py index 1a6c0bb7..9b641578 100644 --- a/userdata/debriefing.py +++ b/userdata/debriefing.py @@ -1,3 +1,4 @@ +import logging import typing import re import threading @@ -83,17 +84,16 @@ class Debriefing: try: components = event["initiator"].split("|") - print(components) category, country_id, group_id, unit_type = components[0], int(components[1]), int(components[2]), db.unit_type_from_name(components[3]) if unit_type is None: - print("Skipped due to no unit type") + logging.info("Skipped due to no unit type") continue if category != "unit": - print("Skipped due to category") + logging.info("Skipped due to category") continue except Exception as e: - print(e) + logging.error(e) continue if country_id not in dead_units: diff --git a/userdata/logging.py b/userdata/logging.py new file mode 100644 index 00000000..515f76f2 --- /dev/null +++ b/userdata/logging.py @@ -0,0 +1,29 @@ +import logging +import traceback + +from io import StringIO +from tkinter import * +from tkinter.scrolledtext import * + +log_stream = StringIO() +logging.basicConfig(stream=log_stream, level=logging.INFO) + + +def _error_prompt(): + tk = Tk() + Label(tk, text="Oops, something went wrong.").grid(row=0) + Label(tk, text="Please send following text to the developer:").grid(row=1) + + text = ScrolledText(tk) + text.insert("0.0", log_stream.getvalue()) + text.grid(row=2, sticky=NSEW) + tk.focus() + + +def _handle_exception(self, exception: BaseException, *args): + logging.exception(exception) + _error_prompt() + + +Tk.report_callback_exception = _handle_exception +logging.info("DCS Libration 1.13 RC2") diff --git a/userdata/persistency.py b/userdata/persistency.py index f4feb69d..c2b6f994 100644 --- a/userdata/persistency.py +++ b/userdata/persistency.py @@ -1,3 +1,4 @@ +import logging import typing import pickle import os @@ -56,5 +57,5 @@ def save_game(game) -> bool: shutil.copy(_temporary_save_file(), _save_file()) return True except Exception as e: - print(e) + logging.error(e) return False