From 832bbfc734138ecd142cdbb24b17ec20d2df6578 Mon Sep 17 00:00:00 2001 From: aussig Date: Mon, 30 Dec 2024 18:00:04 +0000 Subject: [PATCH 1/6] WIP on synthetic events --- bgstally/activity.py | 73 ++++++++++++++++++++++++++++++++++++------ bgstally/api.py | 5 +-- bgstally/apimanager.py | 31 ++++++++++++++---- bgstally/bgstally.py | 4 +-- 4 files changed, 93 insertions(+), 20 deletions(-) diff --git a/bgstally/activity.py b/bgstally/activity.py index 7132d6b..275cc95 100644 --- a/bgstally/activity.py +++ b/bgstally/activity.py @@ -1,10 +1,11 @@ import json import re from copy import deepcopy -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC from typing import Dict -from bgstally.constants import FILE_SUFFIX, CheckStates +from bgstally.apimanager import SyntheticEvent, SyntheticCZObjectiveType +from bgstally.constants import FILE_SUFFIX, CheckStates, DATETIME_FORMAT_JOURNAL from bgstally.debug import Debug from bgstally.missionlog import MissionLog from bgstally.state import State @@ -585,9 +586,13 @@ def bv_redeemed(self, journal_entry: Dict, state: State): self.recalculate_zero_activity() - def cb_received(self, journal_entry: dict, state: State): - """ - Handle a combat bond received for a kill + def cb_received(self, journal_entry: dict, state: State, cmdr: str): + """Handle a combat bond received for a kill + + Args: + journal_entry (dict): The journal data + state (State): The state data + cmdr (str): The CMDR name """ current_system = self.systems.get(state.current_system_id) if not current_system: return @@ -617,7 +622,7 @@ def cb_received(self, journal_entry: dict, state: State): else: # We're within the timeout, refresh timestamp and handle the CB state.last_spacecz_approached['timestamp'] = journal_entry['timestamp'] - self._cb_space_cz(journal_entry, current_system, state) + self._cb_space_cz(journal_entry, current_system, state, cmdr) def cb_redeemed(self, journal_entry: Dict, state: State): @@ -636,7 +641,7 @@ def cb_redeemed(self, journal_entry: Dict, state: State): self.recalculate_zero_activity() - def cap_ship_bond_received(self, journal_entry: dict): + def cap_ship_bond_received(self, journal_entry: dict, cmdr: str): """Handle a capital ship bond Args: @@ -653,6 +658,14 @@ def cap_ship_bond_received(self, journal_entry: dict): faction['SpaceCZ']['cs'] = int(faction['SpaceCZ'].get('cs', '0')) + 1 + event: dict = { + 'event': SyntheticEvent.CZOBJECTIVE, + 'count': 1, + 'type': SyntheticCZObjectiveType.CAPSHIP, + 'Faction': faction + } + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) self.recalculate_zero_activity() @@ -1022,16 +1035,18 @@ def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): self.recalculate_zero_activity() - def _cb_space_cz(self, journal_entry:dict, current_system:dict, state:State): + def _cb_space_cz(self, journal_entry: dict, current_system: dict, state: State, cmdr: str): """Combat bond received while we are in an active space CZ Args: journal_entry (dict): The journal entry data current_system (dict): The current system dict state (State): The bgstally state object + cmdr (str): The CMDR name """ - faction = current_system['Factions'].get(journal_entry.get('AwardingFaction', "")) + faction_name: str = journal_entry.get('AwardingFaction', "") + faction: dict = current_system['Factions'].get(faction_name) if not faction: return # Check for side objectives detected by CBs @@ -1040,18 +1055,45 @@ def _cb_space_cz(self, journal_entry:dict, current_system:dict, state:State): # Tally a captain kill. Unreliable because of journal order unpredictability. state.last_spacecz_approached['capt'] = True faction['SpaceCZ']['cp'] = int(faction['SpaceCZ'].get('cp', '0')) + 1 + + event: dict = { + 'event': SyntheticEvent.CZOBJECTIVE, + 'count': 1, + 'type': SyntheticCZObjectiveType.GENERAL, + 'Faction': faction_name + } + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) elif state.last_ship_targeted.get('PilotName', "") in SPACECZ_PILOTNAMES_SPECOPS and not state.last_spacecz_approached.get('specops'): # Tally a specops kill. We would like to only tally this after 4 kills in a CZ, but sadly due to journal order # unpredictability we tally as soon as we spot a kill after targeting a spec ops state.last_spacecz_approached['specops'] = True faction['SpaceCZ']['so'] = int(faction['SpaceCZ'].get('so', '0')) + 1 + + event: dict = { + 'event': SyntheticEvent.CZOBJECTIVE, + 'count': 1, + 'type': SyntheticCZObjectiveType.SPECOPS, + 'Faction': faction_name + } + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) elif state.last_ship_targeted.get('PilotName', "") == SPACECZ_PILOTNAME_PROPAGAND and not state.last_spacecz_approached.get('propagand'): # Tally a propagandist kill. We would like to only tally this after 3 kills in a CZ, but sadly due to journal order # unpredictability we tally as soon as we spot a kill after targeting a propagandist state.last_spacecz_approached['propagand'] = True faction['SpaceCZ']['pr'] = int(faction['SpaceCZ'].get('pr', '0')) + 1 + + event: dict = { + 'event': SyntheticEvent.CZOBJECTIVE, + 'count': 1, + 'type': SyntheticCZObjectiveType.CORRESPONDENT, + 'Faction': faction_name + } + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) # If we've already counted this CZ, exit @@ -1061,9 +1103,20 @@ def _cb_space_cz(self, journal_entry:dict, current_system:dict, state:State): state.last_spacecz_approached['ally_faction'] = faction.get('Faction', "") self.dirty = True - type:str = state.last_spacecz_approached.get('type', 'l') + type: str = state.last_spacecz_approached.get('type', 'l') faction['SpaceCZ'][type] = int(faction['SpaceCZ'].get(type, '0')) + 1 + event: dict = { + 'event': SyntheticEvent.CZ, + 'Faction': faction_name + } + match type: + case 'l': event['low'] = 1 + case 'm': event['medium'] = 1 + case 'h': event['high'] = 1 + + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) self.recalculate_zero_activity() diff --git a/bgstally/api.py b/bgstally/api.py index 1c32bb0..ec7488b 100644 --- a/bgstally/api.py +++ b/bgstally/api.py @@ -13,7 +13,7 @@ from bgstally.requestmanager import BGSTallyRequest from bgstally.utils import get_by_path, string_to_alphanumeric -API_VERSION = "1.5.0" +API_VERSION = "1.6.0" ENDPOINT_ACTIVITIES = "activities" # Used as both the dict key and default path ENDPOINT_DISCOVERY = "discovery" # Used as the path @@ -25,7 +25,8 @@ ENDPOINTS_DEFAULT = {ENDPOINT_ACTIVITIES: {'path': ENDPOINT_ACTIVITIES}, ENDPOINT_EVENTS: {'path': ENDPOINT_EVENTS}} EVENTS_FILTER_DEFAULTS = {'ApproachSettlement': {}, 'CarrierJump': {}, 'CommitCrime': {}, 'Died': {}, 'Docked': {}, 'FactionKillBond': {}, 'FSDJump': {}, 'Location': {}, 'MarketBuy': {}, 'MarketSell': {}, 'MissionAbandoned': {}, 'MissionAccepted': {}, 'MissionCompleted': {}, - 'MissionFailed': {}, 'MultiSellExplorationData': {}, 'RedeemVoucher': {}, 'SellExplorationData': {}, 'StartUp': {}} + 'MissionFailed': {}, 'MultiSellExplorationData': {}, 'RedeemVoucher': {}, 'SellExplorationData': {}, 'StartUp': {}, + 'SyntheticCZ': {}, 'SyntheticGroundCZ': {}, 'SyntheticCZObjective': {}, 'SyntheticScenario': {}} HEADER_APIKEY = "apikey" HEADER_APIVERSION = "apiversion" diff --git a/bgstally/apimanager.py b/bgstally/apimanager.py index e18a745..f7fb3d1 100644 --- a/bgstally/apimanager.py +++ b/bgstally/apimanager.py @@ -1,6 +1,7 @@ import json -from datetime import datetime +from datetime import datetime, UTC from os import path +from enum import Enum from bgstally.activity import Activity from bgstally.api import API @@ -10,6 +11,18 @@ FILENAME = "apis.json" +class SyntheticEvent(str, Enum): + CZ = 'SyntheticCZ' + GROUNDCZ = 'SyntheticGroundCZ' + CZOBJECTIVE = 'SyntheticCZObjective' + SCENARIO = 'SyntheticScenario' + +class SyntheticCZObjectiveType(str, Enum): + CAPSHIP = 'CapShip' + SPECOPS = 'SpecOps' + GENERAL = 'WarzoneGeneral' + CORRESPONDENT = 'WarzoneCorrespondent' + class APIManager: """ @@ -69,11 +82,16 @@ def send_activity(self, activity:Activity, cmdr:str): api.send_activity(api_activity) - def send_event(self, event:dict, activity:Activity, cmdr:str, mission:dict): - """ - Event has been received. Add it to the events queue. + def send_event(self, event: dict, activity: Activity, cmdr: str, mission: dict = {}): + """Event has been received. Add it to the events queue. + + Args: + event (dict): A dict containing all the event fields + activity (Activity): The activity object + cmdr (str): The CMDR name + mission (dict, optional): Information about the mission, if applicable. Defaults to {}. """ - api_event:dict = self._build_api_event(event, activity, cmdr, mission) + api_event: dict = self._build_api_event(event, activity, cmdr, mission) for api in self.apis: api.send_event(api_event) @@ -253,7 +271,7 @@ def _build_api_activity(self, activity:Activity, cmdr:str): return api_activity - def _build_api_event(self, event:dict, activity:Activity, cmdr:str, mission:dict): + def _build_api_event(self, event:dict, activity:Activity, cmdr:str, mission:dict = {}): """ Build an API-ready event ready for sending. This just involves enhancing the event with some additional data @@ -271,6 +289,7 @@ def _build_api_event(self, event:dict, activity:Activity, cmdr:str, mission:dict if 'StationFaction' not in event: event['StationFaction'] = {'Name': self.bgstally.state.station_faction} if 'StarSystem' not in event: event['StarSystem'] = get_by_path(activity.systems, [self.bgstally.state.current_system_id, 'System'], "") if 'SystemAddress' not in event: event['SystemAddress'] = self.bgstally.state.current_system_id + if 'timestamp' not in event: event['timestamp'] = datetime.now(UTC).strftime(DATETIME_FORMAT_JOURNAL), # Event-specific enhancements match event.get('event'): diff --git a/bgstally/bgstally.py b/bgstally/bgstally.py index ce28087..b4f1647 100644 --- a/bgstally/bgstally.py +++ b/bgstally/bgstally.py @@ -134,7 +134,7 @@ def journal_entry(self, cmdr, is_beta, system, station, entry, state): dirty = True case 'CapShipBond': - activity.cap_ship_bond_received(entry) + activity.cap_ship_bond_received(entry, cmdr) dirty = True case 'Cargo': @@ -173,7 +173,7 @@ def journal_entry(self, cmdr, is_beta, system, station, entry, state): dirty = True case 'FactionKillBond' if state['Odyssey']: - activity.cb_received(entry, self.state) + activity.cb_received(entry, self.state, cmdr) dirty = True case 'Friends' if entry.get('Status') == "Requested": From 529762a2e3a1ceae30d3fac5187c6b98600986d5 Mon Sep 17 00:00:00 2001 From: aussig Date: Tue, 31 Dec 2024 10:27:39 +0000 Subject: [PATCH 2/6] Implement more synthetic events --- bgstally/activity.py | 131 +++++++++++++++++++++++++++++------------ bgstally/apimanager.py | 13 ---- bgstally/bgstally.py | 2 +- bgstally/constants.py | 21 +++++++ 4 files changed, 115 insertions(+), 52 deletions(-) diff --git a/bgstally/activity.py b/bgstally/activity.py index 275cc95..a132eda 100644 --- a/bgstally/activity.py +++ b/bgstally/activity.py @@ -1,11 +1,10 @@ import json import re from copy import deepcopy -from datetime import datetime, timedelta, UTC +from datetime import datetime, timedelta from typing import Dict -from bgstally.apimanager import SyntheticEvent, SyntheticCZObjectiveType -from bgstally.constants import FILE_SUFFIX, CheckStates, DATETIME_FORMAT_JOURNAL +from bgstally.constants import ApiSizeLookup, ApiSyntheticEvent, ApiSyntheticCZObjectiveType, ApiSyntheticScenarioType, FILE_SUFFIX, CheckStates from bgstally.debug import Debug from bgstally.missionlog import MissionLog from bgstally.state import State @@ -90,7 +89,7 @@ '$LUASC_Scenario_Warzone_NPC_SpecOps_D;' ] -SPACECZ_PILOTNAME_PROPAGAND = '$LUASC_Scenario_Warzone_NPC_WarzoneCorrespondent;' +SPACECZ_PILOTNAME_CORRESPONDENT = '$LUASC_Scenario_Warzone_NPC_WarzoneCorrespondent;' CZ_GROUND_LOW_CB_MAX = 5000 CZ_GROUND_MED_CB_MAX = 38000 @@ -547,23 +546,27 @@ def organic_data_sold(self, journal_entry: Dict, state: State): self.recalculate_zero_activity() - def bv_received(self, journal_entry: Dict, state: State): - """ - Handle a bounty voucher for a kill + def bv_received(self, journal_entry: Dict, state: State, cmdr: str): + """Handle a bounty voucher for a kill + + Args: + journal_entry (Dict): The journal data + state (State): The bgstally State object + cmdr (str): The CMDR name """ - current_system = self.systems.get(state.current_system_id) + current_system: dict = self.systems.get(state.current_system_id) if not current_system: return # Check whether in megaship scenario for scenario tracking if state.last_megaship_approached != {}: - timedifference = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_megaship_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") + timedifference: datetime = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_megaship_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") if timedifference > timedelta(minutes=5): # Too long since we last entered a megaship scenario, we can't be sure we're fighting at that scenario, clear down state.last_megaship_approached = {} else: # We're within the timeout, refresh timestamp and handle the CB state.last_megaship_approached['timestamp'] = journal_entry['timestamp'] - self._bv_megaship_scenario(journal_entry, current_system, state) + self._bv_megaship_scenario(journal_entry, current_system, state, cmdr) def bv_redeemed(self, journal_entry: Dict, state: State): @@ -591,7 +594,7 @@ def cb_received(self, journal_entry: dict, state: State, cmdr: str): Args: journal_entry (dict): The journal data - state (State): The state data + state (State): The bgstally State object cmdr (str): The CMDR name """ current_system = self.systems.get(state.current_system_id) @@ -612,7 +615,7 @@ def cb_received(self, journal_entry: dict, state: State, cmdr: str): else: # We're within the timeout, refresh timestamp and handle the CB state.last_settlement_approached['timestamp'] = journal_entry['timestamp'] - self._cb_ground_cz(journal_entry, current_system, state) + self._cb_ground_cz(journal_entry, current_system, state, cmdr) elif state.last_spacecz_approached != {}: timedifference = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_spacecz_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") @@ -659,9 +662,9 @@ def cap_ship_bond_received(self, journal_entry: dict, cmdr: str): faction['SpaceCZ']['cs'] = int(faction['SpaceCZ'].get('cs', '0')) + 1 event: dict = { - 'event': SyntheticEvent.CZOBJECTIVE, + 'event': ApiSyntheticEvent.CZOBJECTIVE, 'count': 1, - 'type': SyntheticCZObjectiveType.CAPSHIP, + 'type': ApiSyntheticCZObjectiveType.CAPSHIP, 'Faction': faction } self.bgstally.api_manager.send_event(event, self, cmdr) @@ -975,11 +978,17 @@ def _cb_tw(self, journal_entry:dict, current_system:dict): self.bgstally.ui.show_system_report(current_system['SystemAddress']) - def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): - """ - Combat bond received while we are in an active ground CZ + def _cb_ground_cz(self, journal_entry: dict, current_system: dict, state: State, cmdr: str): + """Combat bond received while we are in an active ground CZ + + Args: + journal_entry (dict): The journal entry data + current_system (dict): The current system data + state (State): The bgstally State object + cmdr (str): The CMDR name """ - faction = current_system['Factions'].get(journal_entry['AwardingFaction']) + faction_name: str = journal_entry.get('AwardingFaction', "") + faction: dict = current_system['Factions'].get(faction_name) if not faction: return self.dirty = True @@ -991,7 +1000,7 @@ def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): faction['GroundCZSettlements'][state.last_settlement_approached['name']] = self._get_new_groundcz_settlement_data() # Store the previously counted size of this settlement - previous_size = state.last_settlement_approached['size'] + previous_size: str = state.last_settlement_approached['size'] # Increment this settlement's overall count if this is the first bond counted if state.last_settlement_approached['size'] == None: @@ -1009,6 +1018,16 @@ def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): faction['GroundCZSettlements'][state.last_settlement_approached['name']]['type'] = 'l' # Store last settlement type state.last_settlement_approached['size'] = 'l' + + # Send to API + event: dict = { + 'event': ApiSyntheticEvent.GROUNDCZ, + 'low': 1, + 'settlement': state.last_settlement_approached['name'], + 'Faction': faction_name + } + self.bgstally.api_manager.send_event(event, self, cmdr) + elif journal_entry['Reward'] < CZ_GROUND_MED_CB_MAX: # Handle as 'Med' if this is either the first CB or we've counted this settlement as a 'Low' before if state.last_settlement_approached['size'] == None or state.last_settlement_approached['size'] == 'l': @@ -1020,6 +1039,16 @@ def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): faction['GroundCZSettlements'][state.last_settlement_approached['name']]['type'] = 'm' # Store last settlement type state.last_settlement_approached['size'] = 'm' + + # Send to API + event: dict = { + 'event': ApiSyntheticEvent.GROUNDCZ, + 'medium': 1, + 'settlement': state.last_settlement_approached['name'], + 'Faction': faction_name + } + if previous_size != None: event[ApiSizeLookup[previous_size]] = -1 + self.bgstally.api_manager.send_event(event, self, cmdr) else: # Handle as 'High' if this is either the first CB or we've counted this settlement as a 'Low' or 'Med' before if state.last_settlement_approached['size'] == None or state.last_settlement_approached['size'] == 'l' or state.last_settlement_approached['size'] == 'm': @@ -1032,6 +1061,16 @@ def _cb_ground_cz(self, journal_entry:dict, current_system:dict, state:State): # Store last settlement type state.last_settlement_approached['size'] = 'h' + # Send to API + event: dict = { + 'event': ApiSyntheticEvent.GROUNDCZ, + 'high': 1, + 'settlement': state.last_settlement_approached['name'], + 'Faction': faction_name + } + if previous_size != None: event[ApiSizeLookup[previous_size]] = -1 + self.bgstally.api_manager.send_event(event, self, cmdr) + self.recalculate_zero_activity() @@ -1040,7 +1079,7 @@ def _cb_space_cz(self, journal_entry: dict, current_system: dict, state: State, Args: journal_entry (dict): The journal entry data - current_system (dict): The current system dict + current_system (dict): The current system data state (State): The bgstally state object cmdr (str): The CMDR name """ @@ -1056,10 +1095,11 @@ def _cb_space_cz(self, journal_entry: dict, current_system: dict, state: State, state.last_spacecz_approached['capt'] = True faction['SpaceCZ']['cp'] = int(faction['SpaceCZ'].get('cp', '0')) + 1 + # Send to API event: dict = { - 'event': SyntheticEvent.CZOBJECTIVE, + 'event': ApiSyntheticEvent.CZOBJECTIVE, 'count': 1, - 'type': SyntheticCZObjectiveType.GENERAL, + 'type': ApiSyntheticCZObjectiveType.GENERAL, 'Faction': faction_name } self.bgstally.api_manager.send_event(event, self, cmdr) @@ -1071,25 +1111,27 @@ def _cb_space_cz(self, journal_entry: dict, current_system: dict, state: State, state.last_spacecz_approached['specops'] = True faction['SpaceCZ']['so'] = int(faction['SpaceCZ'].get('so', '0')) + 1 + # Send to API event: dict = { - 'event': SyntheticEvent.CZOBJECTIVE, + 'event': ApiSyntheticEvent.CZOBJECTIVE, 'count': 1, - 'type': SyntheticCZObjectiveType.SPECOPS, + 'type': ApiSyntheticCZObjectiveType.SPECOPS, 'Faction': faction_name } self.bgstally.api_manager.send_event(event, self, cmdr) self.bgstally.ui.show_system_report(current_system['SystemAddress']) - elif state.last_ship_targeted.get('PilotName', "") == SPACECZ_PILOTNAME_PROPAGAND and not state.last_spacecz_approached.get('propagand'): + elif state.last_ship_targeted.get('PilotName', "") == SPACECZ_PILOTNAME_CORRESPONDENT and not state.last_spacecz_approached.get('propagand'): # Tally a propagandist kill. We would like to only tally this after 3 kills in a CZ, but sadly due to journal order # unpredictability we tally as soon as we spot a kill after targeting a propagandist state.last_spacecz_approached['propagand'] = True faction['SpaceCZ']['pr'] = int(faction['SpaceCZ'].get('pr', '0')) + 1 + # Send to API event: dict = { - 'event': SyntheticEvent.CZOBJECTIVE, + 'event': ApiSyntheticEvent.CZOBJECTIVE, 'count': 1, - 'type': SyntheticCZObjectiveType.CORRESPONDENT, + 'type': ApiSyntheticCZObjectiveType.CORRESPONDENT, 'Faction': faction_name } self.bgstally.api_manager.send_event(event, self, cmdr) @@ -1106,28 +1148,32 @@ def _cb_space_cz(self, journal_entry: dict, current_system: dict, state: State, type: str = state.last_spacecz_approached.get('type', 'l') faction['SpaceCZ'][type] = int(faction['SpaceCZ'].get(type, '0')) + 1 + # Send to API event: dict = { - 'event': SyntheticEvent.CZ, + 'event': ApiSyntheticEvent.CZ, + ApiSizeLookup[type]: 1, 'Faction': faction_name } - match type: - case 'l': event['low'] = 1 - case 'm': event['medium'] = 1 - case 'h': event['high'] = 1 - self.bgstally.api_manager.send_event(event, self, cmdr) self.bgstally.ui.show_system_report(current_system['SystemAddress']) self.recalculate_zero_activity() - def _bv_megaship_scenario(self, journal_entry:dict, current_system:dict, state:State): - """ - We are in an active megaship scenario + def _bv_megaship_scenario(self, journal_entry: dict, current_system: dict, state: State, cmdr: str): + """We are in an active megaship scenario + + Args: + journal_entry (dict): The journal entry data + current_system (dict): The current system data + state (State): The bgstally State object + cmdr (str): The CMDR name """ - faction:dict = current_system['Factions'].get(journal_entry['VictimFaction']) + faction_name: str = journal_entry.get('VictimFaction', "") + faction: dict = current_system['Factions'].get(faction_name) if not faction: return - opponent_faction:dict = current_system['Factions'].get(faction.get('Opponent', "")) + opponent_faction_name: str = faction.get('Opponent', "") + opponent_faction: dict = current_system['Factions'].get(opponent_faction_name) if not opponent_faction: return # If we've already counted this scenario, exit @@ -1139,6 +1185,15 @@ def _bv_megaship_scenario(self, journal_entry:dict, current_system:dict, state:S # The scenario should be counted against the opponent faction of the ship just killed opponent_faction['Scenarios'] += 1 + # Send to API + event: dict = { + 'event': ApiSyntheticEvent.SCENARIO, + 'type': ApiSyntheticScenarioType.MEGASHIP, + 'count': 1, + 'Faction': opponent_faction_name + } + self.bgstally.api_manager.send_event(event, self, cmdr) + self.bgstally.ui.show_system_report(current_system['SystemAddress']) self.recalculate_zero_activity() diff --git a/bgstally/apimanager.py b/bgstally/apimanager.py index f7fb3d1..ad28035 100644 --- a/bgstally/apimanager.py +++ b/bgstally/apimanager.py @@ -11,19 +11,6 @@ FILENAME = "apis.json" -class SyntheticEvent(str, Enum): - CZ = 'SyntheticCZ' - GROUNDCZ = 'SyntheticGroundCZ' - CZOBJECTIVE = 'SyntheticCZObjective' - SCENARIO = 'SyntheticScenario' - -class SyntheticCZObjectiveType(str, Enum): - CAPSHIP = 'CapShip' - SPECOPS = 'SpecOps' - GENERAL = 'WarzoneGeneral' - CORRESPONDENT = 'WarzoneCorrespondent' - - class APIManager: """ Handles a list of API objects. diff --git a/bgstally/bgstally.py b/bgstally/bgstally.py index b4f1647..87122d7 100644 --- a/bgstally/bgstally.py +++ b/bgstally/bgstally.py @@ -130,7 +130,7 @@ def journal_entry(self, cmdr, is_beta, system, station, entry, state): dirty = True case 'Bounty': - activity.bv_received(entry, self.state) + activity.bv_received(entry, self.state, cmdr) dirty = True case 'CapShipBond': diff --git a/bgstally/constants.py b/bgstally/constants.py index be6b8a3..56dac4e 100644 --- a/bgstally/constants.py +++ b/bgstally/constants.py @@ -84,6 +84,27 @@ class CmdrInteractionReason(int, Enum): TEAM_INVITE_RECEIVED = 5 FRIEND_ADDED = 6 +class ApiSyntheticEvent(str, Enum): + CZ = 'SyntheticCZ' + GROUNDCZ = 'SyntheticGroundCZ' + CZOBJECTIVE = 'SyntheticCZObjective' + SCENARIO = 'SyntheticScenario' + +class ApiSyntheticCZObjectiveType(str, Enum): + CAPSHIP = 'CapShip' + SPECOPS = 'SpecOps' + GENERAL = 'WarzoneGeneral' + CORRESPONDENT = 'WarzoneCorrespondent' + +class ApiSyntheticScenarioType(str, Enum): + MEGASHIP = 'Megaship' + INSTALLATION = 'Installation' + +ApiSizeLookup: dict = { + 'l': 'low', + 'm': 'medium', + 'h': 'high' +} DATETIME_FORMAT_JOURNAL: str = "%Y-%m-%dT%H:%M:%SZ" FILE_SUFFIX: str = ".json" From 9bd6e2e1a65e7d2750f140bae2c7901470d53605 Mon Sep 17 00:00:00 2001 From: aussig Date: Tue, 31 Dec 2024 10:28:36 +0000 Subject: [PATCH 3/6] Replace deprecated `datetime.utcnow()` --- bgstally/apimanager.py | 6 +++--- bgstally/discord.py | 6 +++--- bgstally/missionlog.py | 6 +++--- bgstally/targetmanager.py | 6 +++--- bgstally/tick.py | 5 ++--- bgstally/ui.py | 11 +++++------ 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/bgstally/apimanager.py b/bgstally/apimanager.py index ad28035..c77e1e6 100644 --- a/bgstally/apimanager.py +++ b/bgstally/apimanager.py @@ -1,7 +1,7 @@ import json -from datetime import datetime, UTC -from os import path +from datetime import UTC, datetime from enum import Enum +from os import path from bgstally.activity import Activity from bgstally.api import API @@ -91,7 +91,7 @@ def _build_api_activity(self, activity:Activity, cmdr:str): 'cmdr': cmdr, 'tickid': activity.tick_id, 'ticktime': activity.tick_time.strftime(DATETIME_FORMAT_JOURNAL), - 'timestamp': datetime.utcnow().strftime(DATETIME_FORMAT_JOURNAL), + 'timestamp': datetime.now(UTC).strftime(DATETIME_FORMAT_JOURNAL), 'systems': [] } diff --git a/bgstally/discord.py b/bgstally/discord.py index ce96475..173f976 100644 --- a/bgstally/discord.py +++ b/bgstally/discord.py @@ -1,5 +1,5 @@ from copy import deepcopy -from datetime import datetime +from datetime import UTC, datetime from requests import Response @@ -53,7 +53,7 @@ def post_plaintext(self, discord_text:str, webhooks_data:dict|None, channel:Disc # Get the previous state for this webhook's uuid from the passed in data, if it exists. Default to the state from the webhook manager specific_webhook_data:dict = {} if webhooks_data is None else webhooks_data.get(webhook.get('uuid', ""), webhook) - utc_time_now:str = datetime.utcnow().strftime(DATETIME_FORMAT) + " " + __("game", lang=self.bgstally.state.discord_lang) # LANG: Discord date/time suffix for game time + utc_time_now:str = datetime.now(UTC).strftime(DATETIME_FORMAT) + " " + __("game", lang=self.bgstally.state.discord_lang) # LANG: Discord date/time suffix for game time data:dict = {'channel': channel, 'callback': callback, 'webhookdata': specific_webhook_data} # Data that's carried through the request queue and back to the callback # Fetch the previous post ID, if present, from the webhook data for the channel we're posting in. May be the default True / False value @@ -184,7 +184,7 @@ def _get_embed(self, title: str | None = None, description: str | None = None, f Returns: dict[str, any]: The post structure, for converting to JSON """ - footer_timestamp: str = (__("Updated at {date_time} (game)", lang=self.bgstally.state.discord_lang) if update else __("Posted at {date_time} (game)", lang=self.bgstally.state.discord_lang)).format(date_time=datetime.utcnow().strftime(DATETIME_FORMAT)) # LANG: Discord footer message, modern embed mode + footer_timestamp: str = (__("Updated at {date_time} (game)", lang=self.bgstally.state.discord_lang) if update else __("Posted at {date_time} (game)", lang=self.bgstally.state.discord_lang)).format(date_time=datetime.now(UTC).strftime(DATETIME_FORMAT)) # LANG: Discord footer message, modern embed mode footer_version: str = f"v{str(self.bgstally.version)}" footer_pad: int = 108 - len(footer_version) diff --git a/bgstally/missionlog.py b/bgstally/missionlog.py index 8f567ee..cdf744d 100644 --- a/bgstally/missionlog.py +++ b/bgstally/missionlog.py @@ -1,6 +1,6 @@ import json +from datetime import UTC, datetime, timedelta from os import path, remove -from datetime import datetime, timedelta from bgstally.constants import DATETIME_FORMAT_JOURNAL, FOLDER_OTHER_DATA from bgstally.debug import Debug @@ -118,9 +118,9 @@ def _expire_old_missions(self): """ for mission in reversed(self.missionlog): # Old missions pre v1.11.0 and missions with missing expiry dates don't have Expiry stored. Set to 7 days ahead for safety - if not 'Expiry' in mission or mission['Expiry'] == "": mission['Expiry'] = (datetime.utcnow() + timedelta(days = TIME_MISSION_EXPIRY_D)).strftime(DATETIME_FORMAT_JOURNAL) + if not 'Expiry' in mission or mission['Expiry'] == "": mission['Expiry'] = (datetime.now(UTC) + timedelta(days = TIME_MISSION_EXPIRY_D)).strftime(DATETIME_FORMAT_JOURNAL) - timedifference = datetime.utcnow() - datetime.strptime(mission['Expiry'], DATETIME_FORMAT_JOURNAL) + timedifference = datetime.now(UTC) - datetime.strptime(mission['Expiry'], DATETIME_FORMAT_JOURNAL) if timedifference > timedelta(days = TIME_MISSION_EXPIRY_D): # Keep missions for a while after they have expired, so we can log failed missions correctly self.missionlog.remove(mission) diff --git a/bgstally/targetmanager.py b/bgstally/targetmanager.py index abdc877..54f1b5b 100644 --- a/bgstally/targetmanager.py +++ b/bgstally/targetmanager.py @@ -2,7 +2,7 @@ import os.path import re from copy import copy -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from requests import Response @@ -336,7 +336,7 @@ def _fetch_cmdr_info(self, cmdr_name:str, cmdr_data:dict): 'events': [ { 'eventName': "getCommanderProfile", - 'eventTimestamp': datetime.utcnow().strftime(DATETIME_FORMAT_INARA), + 'eventTimestamp': datetime.now(UTC).strftime(DATETIME_FORMAT_INARA), 'eventData': { 'searchName': cmdr_name } @@ -377,6 +377,6 @@ def _expire_old_targets(self): Clear out all old targets from the target log """ for target in reversed(self.targetlog): - timedifference = datetime.utcnow() - datetime.strptime(target['Timestamp'], DATETIME_FORMAT_JOURNAL) + timedifference = datetime.now(UTC) - datetime.strptime(target['Timestamp'], DATETIME_FORMAT_JOURNAL) if timedifference > timedelta(days = TIME_TARGET_LOG_EXPIRY_D): self.targetlog.remove(target) diff --git a/bgstally/tick.py b/bgstally/tick.py index fc5f5aa..53ff878 100644 --- a/bgstally/tick.py +++ b/bgstally/tick.py @@ -1,6 +1,5 @@ import hashlib -from datetime import datetime, timedelta -from secrets import token_hex +from datetime import UTC, datetime, timedelta import plug import requests @@ -23,7 +22,7 @@ class Tick: def __init__(self, bgstally, load: bool = False): self.bgstally = bgstally self.tick_id: str = TICKID_UNKNOWN - self.tick_time: datetime = (datetime.utcnow() - timedelta(days = 30)) # Default to a tick a month old + self.tick_time: datetime = (datetime.now(UTC) - timedelta(days = 30)) # Default to a tick a month old if load: self.load() diff --git a/bgstally/ui.py b/bgstally/ui.py index 6fc25cb..d671812 100644 --- a/bgstally/ui.py +++ b/bgstally/ui.py @@ -1,5 +1,5 @@ import tkinter as tk -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from functools import partial from os import path from threading import Thread @@ -11,7 +11,6 @@ import myNotebook as nb from ttkHyperlinkLabel import HyperlinkLabel -import l10n from bgstally.activity import Activity from bgstally.constants import FOLDER_ASSETS, FONT_HEADING_2, FONT_SMALL, CheckStates, DiscordActivity, DiscordPostStyle, UpdateUIPolicy from bgstally.debug import Debug @@ -368,13 +367,13 @@ def _worker(self) -> None: self.bgstally.overlay.display_message("tick", _("Curr Tick:") + " " + self.bgstally.tick.get_formatted(DATETIME_FORMAT_OVERLAY), True) # Overlay tick message # Tick Warning - minutes_delta:int = int((datetime.utcnow() - self.bgstally.tick.next_predicted()) / timedelta(minutes=1)) + minutes_delta:int = int((datetime.now(UTC) - self.bgstally.tick.next_predicted()) / timedelta(minutes=1)) if self.bgstally.state.enable_overlay_current_tick: - if datetime.utcnow() > self.bgstally.tick.next_predicted() + timedelta(minutes = TIME_TICK_ALERT_M): + if datetime.now(UTC) > self.bgstally.tick.next_predicted() + timedelta(minutes = TIME_TICK_ALERT_M): self.bgstally.overlay.display_message("tickwarn", _("Tick {minutes_delta}m Overdue (Estimated)").format(minutes_delta=minutes_delta), True) # Overlay tick message - elif datetime.utcnow() > self.bgstally.tick.next_predicted(): + elif datetime.now(UTC) > self.bgstally.tick.next_predicted(): self.bgstally.overlay.display_message("tickwarn", _("Past Estimated Tick Time"), True, text_colour_override="#FFA500") # Overlay tick message - elif datetime.utcnow() > self.bgstally.tick.next_predicted() - timedelta(minutes = TIME_TICK_ALERT_M): + elif datetime.now(UTC) > self.bgstally.tick.next_predicted() - timedelta(minutes = TIME_TICK_ALERT_M): self.bgstally.overlay.display_message("tickwarn", _("Within {minutes_to_tick}m of Next Tick (Estimated)").format(minutes_to_tick=TIME_TICK_ALERT_M), True, text_colour_override="yellow") # Overlay tick message # Activity Indicator From e98a78b2c2df0e0435cc703d4d470cb5a3abf613 Mon Sep 17 00:00:00 2001 From: aussig Date: Tue, 31 Dec 2024 11:49:15 +0000 Subject: [PATCH 4/6] Use API time format in API --- bgstally/apimanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bgstally/apimanager.py b/bgstally/apimanager.py index 718fd9d..086aef7 100644 --- a/bgstally/apimanager.py +++ b/bgstally/apimanager.py @@ -276,7 +276,7 @@ def _build_api_event(self, event:dict, activity:Activity, cmdr:str, mission:dict if 'StationFaction' not in event: event['StationFaction'] = {'Name': self.bgstally.state.station_faction} if 'StarSystem' not in event: event['StarSystem'] = get_by_path(activity.systems, [self.bgstally.state.current_system_id, 'System'], "") if 'SystemAddress' not in event: event['SystemAddress'] = self.bgstally.state.current_system_id - if 'timestamp' not in event: event['timestamp'] = datetime.now(UTC).strftime(DATETIME_FORMAT_JOURNAL), + if 'timestamp' not in event: event['timestamp'] = datetime.now(UTC).strftime(DATETIME_FORMAT_API), # Event-specific enhancements match event.get('event'): From 56e192d870dbf8672571441dc877773212873ac4 Mon Sep 17 00:00:00 2001 From: aussig Date: Tue, 31 Dec 2024 13:42:17 +0000 Subject: [PATCH 5/6] Fix datetime comparisons (tz naive vs tz aware) after `utcnow()` replacement. --- bgstally/activity.py | 11 ++++++----- bgstally/missionlog.py | 6 +++++- bgstally/objectivesmanager.py | 4 +++- bgstally/targetmanager.py | 6 +++++- bgstally/tick.py | 2 ++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/bgstally/activity.py b/bgstally/activity.py index df6e6d4..de8745f 100644 --- a/bgstally/activity.py +++ b/bgstally/activity.py @@ -1,10 +1,10 @@ import json import re from copy import deepcopy -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import Dict -from bgstally.constants import ApiSizeLookup, ApiSyntheticEvent, ApiSyntheticCZObjectiveType, ApiSyntheticScenarioType, FILE_SUFFIX, CheckStates +from bgstally.constants import ApiSizeLookup, ApiSyntheticEvent, ApiSyntheticCZObjectiveType, ApiSyntheticScenarioType, DATETIME_FORMAT_JOURNAL, FILE_SUFFIX, CheckStates from bgstally.debug import Debug from bgstally.missionlog import MissionLog from bgstally.state import State @@ -570,7 +570,7 @@ def bv_received(self, journal_entry: Dict, state: State, cmdr: str): # Check whether in megaship scenario for scenario tracking if state.last_megaship_approached != {}: - timedifference: datetime = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_megaship_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") + timedifference: datetime = datetime.strptime(journal_entry['timestamp'], DATETIME_FORMAT_JOURNAL) - datetime.strptime(state.last_megaship_approached['timestamp'], DATETIME_FORMAT_JOURNAL) if timedifference > timedelta(minutes=5): # Too long since we last entered a megaship scenario, we can't be sure we're fighting at that scenario, clear down state.last_megaship_approached = {} @@ -618,7 +618,7 @@ def cb_received(self, journal_entry: dict, state: State, cmdr: str): # Otherwise, must be on-ground or in-space CZ for CB kill tracking if state.last_settlement_approached != {}: - timedifference = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_settlement_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") + timedifference = datetime.strptime(journal_entry['timestamp'], DATETIME_FORMAT_JOURNAL) - datetime.strptime(state.last_settlement_approached['timestamp'], DATETIME_FORMAT_JOURNAL) if timedifference > timedelta(minutes=5): # Too long since we last approached a settlement, we can't be sure we're fighting at that settlement, clear down state.last_settlement_approached = {} @@ -629,7 +629,7 @@ def cb_received(self, journal_entry: dict, state: State, cmdr: str): self._cb_ground_cz(journal_entry, current_system, state, cmdr) elif state.last_spacecz_approached != {}: - timedifference = datetime.strptime(journal_entry['timestamp'], "%Y-%m-%dT%H:%M:%SZ") - datetime.strptime(state.last_spacecz_approached['timestamp'], "%Y-%m-%dT%H:%M:%SZ") + timedifference = datetime.strptime(journal_entry['timestamp'], DATETIME_FORMAT_JOURNAL) - datetime.strptime(state.last_spacecz_approached['timestamp'], DATETIME_FORMAT_JOURNAL) if timedifference > timedelta(minutes=5): # Too long since we last entered a space cz, we can't be sure we're fighting at that cz, clear down state.last_spacecz_approached = {} @@ -1476,6 +1476,7 @@ def _from_dict(self, dict: Dict): """ self.tick_id = dict.get('tickid') self.tick_time = datetime.strptime(dict.get('ticktime'), DATETIME_FORMAT_ACTIVITY) + self.tick_time = self.tick_time.replace(tzinfo=UTC) self.tick_forced = dict.get('tickforced', False) self.discord_webhook_data = dict.get('discordwebhookdata', {}) self.discord_notes = dict.get('discordnotes', "") diff --git a/bgstally/missionlog.py b/bgstally/missionlog.py index cdf744d..f225255 100644 --- a/bgstally/missionlog.py +++ b/bgstally/missionlog.py @@ -120,7 +120,11 @@ def _expire_old_missions(self): # Old missions pre v1.11.0 and missions with missing expiry dates don't have Expiry stored. Set to 7 days ahead for safety if not 'Expiry' in mission or mission['Expiry'] == "": mission['Expiry'] = (datetime.now(UTC) + timedelta(days = TIME_MISSION_EXPIRY_D)).strftime(DATETIME_FORMAT_JOURNAL) - timedifference = datetime.now(UTC) - datetime.strptime(mission['Expiry'], DATETIME_FORMAT_JOURNAL) + # Need to do this shenanegans to parse a tz-aware timestamp from a string + expiry_timestamp: datetime = datetime.strptime(mission['Expiry'], DATETIME_FORMAT_JOURNAL) + expiry_timestamp = expiry_timestamp.replace(tzinfo=UTC) + + timedifference = datetime.now(UTC) - expiry_timestamp if timedifference > timedelta(days = TIME_MISSION_EXPIRY_D): # Keep missions for a while after they have expired, so we can log failed missions correctly self.missionlog.remove(mission) diff --git a/bgstally/objectivesmanager.py b/bgstally/objectivesmanager.py index 3f58bf8..e41b88b 100644 --- a/bgstally/objectivesmanager.py +++ b/bgstally/objectivesmanager.py @@ -73,7 +73,9 @@ def get_human_readable_objectives(self) -> str: mission_system: str|None = mission.get('system') mission_faction: str|None = mission.get('faction') mission_startdate: datetime = datetime.strptime(mission.get('startdate', datetime.now(UTC).strftime(DATETIME_FORMAT_API)), DATETIME_FORMAT_API) - mission_enddate: datetime|None = datetime.strptime(mission.get('enddate', None), DATETIME_FORMAT_API) + mission_startdate = mission_startdate.replace(tzinfo=UTC) + mission_enddate: datetime = datetime.strptime(mission.get('enddate', datetime(3999, 12, 31, 23, 59, 59, 0, UTC).strftime(DATETIME_FORMAT_API)), DATETIME_FORMAT_API) + mission_enddate = mission_enddate.replace(tzinfo=UTC) if mission_enddate < datetime.now(UTC): continue mission_activity: Activity = self.bgstally.activity_manager.query_activity(mission_startdate) diff --git a/bgstally/targetmanager.py b/bgstally/targetmanager.py index 54f1b5b..97be13e 100644 --- a/bgstally/targetmanager.py +++ b/bgstally/targetmanager.py @@ -377,6 +377,10 @@ def _expire_old_targets(self): Clear out all old targets from the target log """ for target in reversed(self.targetlog): - timedifference = datetime.now(UTC) - datetime.strptime(target['Timestamp'], DATETIME_FORMAT_JOURNAL) + # Need to do this shenanegans to parse a tz-aware timestamp from a string + target_timestamp: datetime = datetime.strptime(target['Timestamp'], DATETIME_FORMAT_JOURNAL) + target_timestamp = target_timestamp.replace(tzinfo=UTC) + + timedifference: datetime = datetime.now(UTC) - target_timestamp if timedifference > timedelta(days = TIME_TARGET_LOG_EXPIRY_D): self.targetlog.remove(target) diff --git a/bgstally/tick.py b/bgstally/tick.py index 53ff878..2d39b99 100644 --- a/bgstally/tick.py +++ b/bgstally/tick.py @@ -47,6 +47,7 @@ def fetch_tick(self): return None tick_time: datetime = datetime.strptime(tick_time_raw, DATETIME_FORMAT_TICK_DETECTOR) + tick_time = tick_time.replace(tzinfo=UTC) if tick_time > self.tick_time: # There is a newer tick @@ -75,6 +76,7 @@ def load(self): """ self.tick_id = config.get_str("XLastTick") self.tick_time = datetime.strptime(config.get_str("XTickTime", default=self.tick_time.strftime(DATETIME_FORMAT_TICK_DETECTOR)), DATETIME_FORMAT_TICK_DETECTOR) + self.tick_time = self.tick_time.replace(tzinfo=UTC) def save(self): From 47aef92d0d05dc55a98780bba31961546e9bbe9f Mon Sep 17 00:00:00 2001 From: aussig Date: Tue, 31 Dec 2024 16:41:30 +0000 Subject: [PATCH 6/6] Implementation complete of synthetic events Closes #288 --- CHANGELOG.md | 5 ++++ bgstally/activity.py | 5 +++- bgstally/apimanager.py | 2 +- bgstally/bgstally.py | 6 ++++ bgstally/windows/activity.py | 57 ++++++++++++++++++++++++++---------- 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb657f4..f3317d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ ### API Changes ([v1.6](https://studio-ws.apicur.io/sharing/xxxxxxxxxxxxxxxxxxxxxxxxxxxx)): * New `/objectives` endpoint. +* `/events` endpoint: Synthetic events added for certain activities in game that the game itself doesn't log in the journal: + - `SyntheticCZ`: Sent when a Space CZ is won. + - `SyntheticCZObjective`: Sent when an objective is completed in a Space CZ (cap ship / spec ops / enemy captain / enemy correspondent). + - `SyntheticGroundCZ`: Sent when a Ground CZ is won. + - `SyntheticScenario`: Sent when a scenario is won (only Megaship scenarios for the moment, Installation scenarios cannot be tracked). ## v4.2.0 - 2024-12-22 diff --git a/bgstally/activity.py b/bgstally/activity.py index de8745f..5c2336c 100644 --- a/bgstally/activity.py +++ b/bgstally/activity.py @@ -126,13 +126,14 @@ class Activity: factions with their activity """ - def __init__(self, bgstally, tick: Tick = None, sample: bool = False): + def __init__(self, bgstally, tick: Tick = None, sample: bool = False, cmdr = None): """Constructor Args: bgstally (BGSTally): The BGSTally object tick (Tick, optional): The Tick object to instantiate from. If None, the last known tick is used. Defaults to None. sample (bool, optional): Populate with sample data. Defaults to False. + cmdr (str, optional): The CMDR name. This is not done properly (yet) - the cmdr name is simply updated often to be the latest cmdr seen. """ self.bgstally = bgstally if tick == None: tick = Tick(self.bgstally) @@ -145,6 +146,8 @@ def __init__(self, bgstally, tick: Tick = None, sample: bool = False): self.discord_notes: str = "" self.dirty: bool = False + self.cmdr: str = cmdr # Not saved / loaded (yet) because it's not implemented properly + if sample: self.systems: dict = {"Sample System ID": self.get_sample_system_data()} else: diff --git a/bgstally/apimanager.py b/bgstally/apimanager.py index 086aef7..cedeeed 100644 --- a/bgstally/apimanager.py +++ b/bgstally/apimanager.py @@ -276,7 +276,7 @@ def _build_api_event(self, event:dict, activity:Activity, cmdr:str, mission:dict if 'StationFaction' not in event: event['StationFaction'] = {'Name': self.bgstally.state.station_faction} if 'StarSystem' not in event: event['StarSystem'] = get_by_path(activity.systems, [self.bgstally.state.current_system_id, 'System'], "") if 'SystemAddress' not in event: event['SystemAddress'] = self.bgstally.state.current_system_id - if 'timestamp' not in event: event['timestamp'] = datetime.now(UTC).strftime(DATETIME_FORMAT_API), + if 'timestamp' not in event: event['timestamp'] = datetime.now(UTC).strftime(DATETIME_FORMAT_API) # Event-specific enhancements match event.get('event'): diff --git a/bgstally/bgstally.py b/bgstally/bgstally.py index 3703944..3318307 100644 --- a/bgstally/bgstally.py +++ b/bgstally/bgstally.py @@ -118,6 +118,12 @@ def journal_entry(self, cmdr, is_beta, system, station, entry, state): return activity: Activity = self.activity_manager.get_current_activity() + + # Total hack for now. We need cmdr in Activity to allow us to send it to the API when the user changes values in the UI. + # What **should** happen is each Activity object should be associated with a single CMDR, and then all reporting + # kept separate per CMDR. + activity.cmdr = cmdr + dirty: bool = False if entry.get('event') in ['StartUp', 'Location', 'FSDJump', 'CarrierJump']: diff --git a/bgstally/windows/activity.py b/bgstally/windows/activity.py index 8861e21..66d5773 100644 --- a/bgstally/windows/activity.py +++ b/bgstally/windows/activity.py @@ -5,9 +5,9 @@ from ttkHyperlinkLabel import HyperlinkLabel -from bgstally.activity import STATES_WAR, STATES_ELECTION, Activity -from bgstally.constants import (COLOUR_HEADING_1, FOLDER_ASSETS, FONT_HEADING_1, FONT_HEADING_2, FONT_TEXT, CheckStates, CZs, DiscordActivity, DiscordChannel, - DiscordPostStyle) +from bgstally.activity import STATES_ELECTION, STATES_WAR, Activity +from bgstally.constants import (COLOUR_HEADING_1, FOLDER_ASSETS, FONT_HEADING_1, FONT_HEADING_2, FONT_TEXT, ApiSizeLookup, ApiSyntheticEvent, CheckStates, CZs, + DiscordActivity, DiscordChannel, DiscordPostStyle) from bgstally.debug import Debug from bgstally.formatters.base import BaseActivityFormatterInterface from bgstally.utils import _, __, human_format @@ -560,18 +560,45 @@ def _cz_change(self, notebook: ScrollableNotebook, tab_index: int, CZVar: tk.Int """ Callback (set as a variable trace) for when a CZ Variable is changed """ - if cz_type == CZs.SPACE_LOW: - faction['SpaceCZ']['l'] = CZVar.get() - elif cz_type == CZs.SPACE_MED: - faction['SpaceCZ']['m'] = CZVar.get() - elif cz_type == CZs.SPACE_HIGH: - faction['SpaceCZ']['h'] = CZVar.get() - elif cz_type == CZs.GROUND_LOW: - faction['GroundCZ']['l'] = CZVar.get() - elif cz_type == CZs.GROUND_MED: - faction['GroundCZ']['m'] = CZVar.get() - elif cz_type == CZs.GROUND_HIGH: - faction['GroundCZ']['h'] = CZVar.get() + match cz_type: + case CZs.SPACE_LOW: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.CZ + event_size: str = ApiSizeLookup['l'] + event_diff: int = int(CZVar.get()) - int(faction['SpaceCZ']['l']) + faction['SpaceCZ']['l'] = CZVar.get() + case CZs.SPACE_MED: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.CZ + event_size: str = ApiSizeLookup['m'] + event_diff: int = int(CZVar.get()) - int(faction['SpaceCZ']['m']) + faction['SpaceCZ']['m'] = CZVar.get() + case CZs.SPACE_HIGH: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.CZ + event_size: str = ApiSizeLookup['h'] + event_diff: int = int(CZVar.get()) - int(faction['SpaceCZ']['h']) + faction['SpaceCZ']['h'] = CZVar.get() + case CZs.GROUND_LOW: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.GROUNDCZ + event_size: str = ApiSizeLookup['l'] + event_diff: int = int(CZVar.get()) - int(faction['GroundCZ']['l']) + faction['GroundCZ']['l'] = CZVar.get() + case CZs.GROUND_MED: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.GROUNDCZ + event_size: str = ApiSizeLookup['m'] + event_diff: int = int(CZVar.get()) - int(faction['GroundCZ']['m']) + faction['GroundCZ']['m'] = CZVar.get() + case CZs.GROUND_HIGH: + event_type: ApiSyntheticEvent = ApiSyntheticEvent.GROUNDCZ + event_size: str = ApiSizeLookup['h'] + event_diff: int = int(CZVar.get()) - int(faction['GroundCZ']['h']) + faction['GroundCZ']['h'] = CZVar.get() + + # Send to API + event: dict = { + 'event': event_type, + event_size: event_diff, + 'Faction': faction['Faction'] + } + if activity.cmdr is not None: self.bgstally.api_manager.send_event(event, activity, activity.cmdr) activity.recalculate_zero_activity() self._update_tab_image(notebook, tab_index, EnableAllCheckbutton, system)