def load_settings(self): mtga_logger.info("{}loading settings".format(util.ld())) os.makedirs(self._settings_path, exist_ok=True) if not os.path.exists(self._settings_json_path): with open(self._settings_json_path, 'w') as wp: json.dump({"player_decks": {}, "player_id": None}, wp) try: with open(self._settings_json_path) as rp: settings = json.load(rp) except: mtga_logger.error("{}had to move settings.json -> settings.json.bak, trying again...".format(util.ld())) # mtga_watch_app.send_error("Error loading settings; had to move settings.json -> settings.json.bak") if os.path.exists(self._settings_json_path + ".bak"): os.remove(self._settings_json_path + ".bak") os.rename(self._settings_json_path, self._settings_json_path + ".bak") return self.load_settings() if "collection" in settings and settings["collection"]: self.collection = settings["collection"] if "player_id" in settings and settings["player_id"]: self.player_id = settings["player_id"] if "player_decks" in settings and settings["player_decks"]: for deck_id in settings["player_decks"]: self.player_decks[deck_id] = Deck.from_dict(settings['player_decks'][deck_id]) mtga_logger.debug("{}queue put from settings {}".format(util.ld(), id(decklist_change_queue))) new_dl = {k: v.to_serializable(transform_to_counted=True) for k, v in self.player_decks.items()} if not new_dl: new_dl = {"no_decks_defined": True} decklist_change_queue.put(new_dl)
def dispatch_gre_to_client(blob): client_messages = blob["greToClientEvent"]['greToClientMessages'] dont_care_types = ["GREMessageType_UIMessage"] for message in client_messages: message_type = message["type"] if message_type in dont_care_types: pass elif message_type in ["GREMessageType_GameStateMessage", "GREMessageType_QueuedGameStateMessage"]: game_state_message = message['gameStateMessage'] try: parsers.parse_game_state_message(game_state_message, blob["timestamp"] if "timestamp" in blob.keys() else None) except: import traceback exc = traceback.format_exc() stack = traceback.format_stack() app.mtga_app.mtga_logger.error("{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error(exc) app.mtga_app.mtga_logger.error(stack) app.mtga_app.mtga_watch_app.send_error("Exception during parse game state. Check log for more details") elif message_type == "GREMessageType_MulliganReq": try: parsers.parse_mulligan_req_message(message, blob["timestamp"] if "timestamp" in blob.keys() else None) except: import traceback exc = traceback.format_exc() stack = traceback.format_stack() app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error(exc) app.mtga_app.mtga_logger.error(stack) app.mtga_app.mtga_watch_app.send_error("Exception during parse game state. Check log for more details")
def dispatch_gre_to_client(blob): client_messages = blob["greToClientEvent"]['greToClientMessages'] dont_care_types = ["GREMessageType_UIMessage"] action_required_types = ["GREMessageType_PayCostsReq", "GREMessageType_ActionsAvailableReq", "GREMessageType_DeclareAttackersReq", "GREMessageType_DeclareBlockersReq", "GREMessageType_SelectNReq"] prompt_action_required_types = ["GREMessageType_PromptReq"] doing_action = False for message in client_messages: message_type = message["type"] if message_type in dont_care_types: pass elif message_type in action_required_types: doing_action = True parsers.parse_action_required_message(message) elif message_type in prompt_action_required_types: doing_action = True parsers.parse_prompt_action_required(message) elif message_type in ["GREMessageType_GameStateMessage", "GREMessageType_QueuedGameStateMessage"]: game_state_message = message['gameStateMessage'] try: parsers.parse_game_state_message(game_state_message, blob["timestamp"] if "timestamp" in blob.keys() else None) except: import traceback exc = traceback.format_exc() stack = traceback.format_stack() app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error(exc) app.mtga_app.mtga_logger.error(stack) app.mtga_app.mtga_watch_app.send_error("Exception during parse game state. Check log for more details") elif message_type == "GREMessageType_MulliganReq": try: parsers.parse_mulligan_req_message(message, blob["timestamp"] if "timestamp" in blob.keys() else None) except: import traceback exc = traceback.format_exc() stack = traceback.format_stack() app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error(exc) app.mtga_app.mtga_logger.error(stack) app.mtga_app.mtga_watch_app.send_error("Exception during parse game state. Check log for more details")
def save_settings(self): mtga_logger.info("{}saving settings".format(util.ld())) with open(self._settings_json_path, 'w') as wp: write_obj = { "player_decks": {d.deck_id: d.to_serializable() for d in self.player_decks.values()}, "player_id": self.player_id, "collection": self.collection, } json.dump(write_obj, wp) mtga_logger.debug("{}queue put from settings {}".format(util.ld(), id(decklist_change_queue))) new_dl = {k: v.to_serializable(transform_to_counted=True) for k, v in self.player_decks.items()} if not new_dl: new_dl = {"no_decks_defined": True} decklist_change_queue.put(new_dl)
def dispatch_blob(blob): seq = blob.get("block_title_sequence", -1) log_line = blob.get("log_line", -1) if seq: app.mtga_app.mtga_logger.debug("{}dispatching seq ({}) / log_line {}".format(util.ld(), seq, log_line)) if "method" in blob and "jsonrpc" in blob: dispatch_jsonrpc_method(blob) elif "greToClientEvent" in blob: dispatch_gre_to_client(blob) elif "clientToGreMessage" in blob: dispatch_client_to_gre(blob) elif "Deck.GetDeckLists" in blob: # this looks like it's a response to a jsonrpc method parsers.parse_get_decklists(blob) elif "block_title" in blob and (blob["block_title"] == "Event.DeckSubmit" or blob["block_title"] == "Event.GetPlayerCourse"): parsers.parse_event_decksubmit(blob) elif "block_title" in blob and blob["block_title"] == "PlayerInventory.GetPlayerCardsV3": parsers.parse_get_player_cards_v3(blob) elif "block_title" in blob and (blob["block_title"] == "Draft.DraftStatus" or blob["block_title"] == "Draft.MakePick"): parsers.parse_draft_status(blob) # PlayerInventory.GetPlayerInventory elif "block_title" in blob and blob["block_title"] == "PlayerInventory.GetPlayerInventory": parsers.pass_through("inventory", blob["playerId"], blob) elif "block_title" in blob and blob["block_title"] == "Rank.Updated": parsers.pass_through("rank_change", blob["playerId"], blob) elif "block_title" in blob and blob["block_title"] == "Inventory.Updated": parsers.pass_through("inventory_update", None, blob) elif ("block_title" in blob and blob["block_title"] == "ClientToMatchServiceMessageType_ClientToGREMessage" and "Payload" in blob and "SubmitDeckResp" in blob['Payload']): parsers.parse_sideboard_submit(blob) elif "matchGameRoomStateChangedEvent" in blob: dispatch_match_gametoom_state_change(blob) elif "block_title" in blob and blob["block_title"] == "Event.MatchCreated": parsers.parse_match_created(blob)
def dispatch_client_to_gre(blob): # TODO: seems this is dead code (9/10/18) :( client_message = blob['clientToGreMessage'] message_type = client_message['type'] dont_care_types = ["ClientMessageType_UIMessage"] unknown_types = [ "ClientMessageType_PerformActionResp", "ClientMessageType_DeclareAttackersResp" "ClientMessageType_DeclareBlockersResp", "ClientMessageType_SetSettingsReq", "ClientMessageType_SelectNResp", "ClientMessageType_SelectTargetsResp", "ClientMessageType_SubmitTargetsReq", "ClientMessageType_SubmitAttackersReq", "ClientMessageType_ConnectReq" ] if message_type in dont_care_types: pass elif message_type == "ClientMessageType_MulliganResp": parsers.parse_mulligan_response(client_message) elif message_type in unknown_types: # TODO: log ? pass else: app.mtga_app.mtga_logger.warning( "{}WARNING: unknown clientToGreMessage type: {}".format( util.ld(), message_type))
def dispatch_blob(blob): seq = blob.get("block_title_sequence", -1) log_line = blob.get("log_line", -1) if seq: app.mtga_app.mtga_logger.debug( "{}dispatching seq ({}) / log_line {}".format( util.ld(), seq, log_line)) if "method" in blob and "jsonrpc" in blob: dispatch_jsonrpc_method(blob) elif "greToClientEvent" in blob: dispatch_gre_to_client(blob) elif "clientToGreMessage" in blob: dispatch_client_to_gre(blob) elif "Deck.GetDeckLists" in blob: # this looks like it's a response to a jsonrpc method parsers.parse_get_decklists(blob) elif "block_title" in blob and (blob["block_title"] == "Event.DeckSubmit" or blob["block_title"] == "Event.GetPlayerCourse"): parsers.parse_event_decksubmit(blob) elif "block_title" in blob and blob[ "block_title"] == "PlayerInventory.GetPlayerCardsV3": parsers.parse_get_player_cards_v3(blob) elif "block_title" in blob and (blob["block_title"] == "Draft.DraftStatus" or blob["block_title"] == "Draft.MakePick"): parsers.parse_draft_status(blob) elif "matchGameRoomStateChangedEvent" in blob: dispatch_match_gametoom_state_change(blob)
def parse_draft_status(blob): # TODO: need to implement the sorting algo shown here: # TODO: https://github.com/Fugiman/deckmaster/blob/559e3b94bb105387a0e33463e4b5f718ab91721d/client/updater.go#L113 """ return a.RarityRank() > b.RarityRank() || (a.RarityRank() == b.RarityRank() && a.ColorRank() > b.ColorRank()) || (a.RarityRank() == b.RarityRank() && a.ColorRank() == b.ColorRank() && a.CMC < b.CMC) || (a.RarityRank() == b.RarityRank() && a.ColorRank() == b.ColorRank() && a.CMC == b.CMC && a.Name < b.Name)""" import app.mtga_app as mtga_app collection_count = [] picked_cards_this_draft = [] if "pickedCards" in blob and blob["pickedCards"]: picked_cards_this_draft = blob["pickedCards"] if blob["draftPack"]: for card in blob["draftPack"]: card_obj = util.all_mtga_cards.find_one(card).to_serializable() if card in mtga_app.mtga_watch_app.collection: card_obj["count"] = min(mtga_app.mtga_watch_app.collection[card] + picked_cards_this_draft.count(card), 4) else: card_obj["count"] = min(0 + picked_cards_this_draft.count(card), 4) collection_count.append(card_obj) collection_count.sort(key=lambda x: (-1 * util.rank_rarity(x["rarity"]), util.rank_colors(x["color_identity"]), util.rank_cost(x["cost"]), x["pretty_name"])) general_output_queue.put({"draft_collection_count": collection_count}) else: blob["draftPack"] = [] draftId = blob["draftId"] picks = picked_cards_this_draft[:] pack = blob['draftPack'][:] draft_history = mtga_app.mtga_watch_app.draft_history if draft_history.get(draftId, None): report = {} # report['picks'] = [int(grpid) for grpid in draft_history[draftId]['picks'] ] report['draftID'] = draftId report['playerID'] = blob["playerId"] report['pickNumber'] = draft_history[draftId]['picknum'] report['packNumber'] = draft_history[draftId]['packnum'] report['pack'] = [int(grpid) for grpid in draft_history[draftId]['pack']] old = draft_history[draftId]['picks'][:] new = picks[:] for c in old: new.remove(c) report['pick'] = int(new[0]) # send report to inspector app.mtga_app.mtga_logger.info("{}{}".format(util.ld(), report)) pass_through("draftPick", report["playerID"], report) if pack: draft_history[draftId] = {'picks': picks, 'pack': pack, 'picknum': blob["pickNumber"], 'packnum': blob["packNumber"]} else: draft_history[draftId] = None
def check_for_client_id(blob): if "authenticateResponse" in blob: if "clientId" in blob["authenticateResponse"]: with mtga_watch_app.game_lock: if mtga_watch_app.player_id != blob[ "authenticateResponse"]['clientId']: mtga_watch_app.player_id = blob[ "authenticateResponse"]['clientId'] mtga_logger.debug( "{}check_for_client_id: got new clientId".format( util.ld())) mtga_watch_app.save_settings()
def dispatch_jsonrpc_method(blob): """ route what parser to run on this jsonrpc methoc blob :param blob: dict, must contain "method" as top level key """ from app.mtga_app import mtga_watch_app dont_care_rpc_methods = [ 'Event.DeckSelect', "Log.Info", "Deck.GetDeckLists", "Quest.CompletePlayerQuest" ] current_method = blob['method'] request_or_response = blob['request_or_response'] if current_method in dont_care_rpc_methods: pass elif current_method == "PlayerInventory.GetPlayerInventory": # TODO: keep an eye on this one. currently empty, but maybe it will show up sometime app.mtga_app.mtga_logger.info( "{}PlayerInventory.GetPlayerInventory found".format(util.ld())) else: app.mtga_app.mtga_logger.debug( "{}not sure what to do with jsonrpc method {}".format( util.ld(), current_method))
def check_for_client_id(blob): if "authenticateResponse" in blob: if "clientId" in blob["authenticateResponse"]: # screw it, no one else is going to use this message, mess up the timestamp, who cares with mtga_watch_app.game_lock: if mtga_watch_app.player_id != blob[ "authenticateResponse"]['clientId']: mtga_watch_app.player_id = blob[ "authenticateResponse"]['clientId'] mtga_logger.debug( "{}check_for_client_id: got new clientId".format( util.ld())) mtga_watch_app.save_settings() general_output_queue.put( {"authenticateResponse": blob["authenticateResponse"]})
def dispatch_blob(blob): seq = blob.get("block_title_sequence", -1) log_line = blob.get("log_line", -1) if seq: app.mtga_app.mtga_logger.debug("{}dispatching seq ({}) / log_line {}".format(util.ld(), seq, log_line)) if "method" in blob and "jsonrpc" in blob: dispatch_jsonrpc_method(blob) elif "greToClientEvent" in blob: dispatch_gre_to_client(blob) elif "clientToGreMessage" in blob: dispatch_client_to_gre(blob) elif "block_title" in blob and blob["block_title"] == "Deck.GetDeckListsV3": parsers.parse_get_decklists(blob, version=3) elif "block_title" in blob and blob["block_title"] == "Deck.UpdateDeckV3": parsers.parse_update_deck_v3(blob) elif "block_title" in blob and (blob["block_title"] == "Event.DeckSubmit" or blob["block_title"] == "Event.GetPlayerCourse"): parsers.parse_event_decksubmit(blob) elif "block_title" in blob and blob["block_title"] == "Event.DeckSubmitV3": parsers.parse_event_decksubmit(blob, version=3) elif "block_title" in blob and blob["block_title"] == "Event.GetPlayerCourseV2": parsers.parse_event_decksubmit(blob, version=3) # TODO: is GetPlayerCoursesV2 useful? # elif "block_title" in blob and blob["block_title"] == "Event.GetPlayerCoursesV2": # parsers.parse_player_courses_v2(blob) elif "block_title" in blob and blob["block_title"] == "PlayerInventory.GetPlayerCardsV3": parsers.parse_get_player_cards_v3(blob["payload"]) elif "block_title" in blob and (blob["block_title"] == "Draft.DraftStatus" or blob["block_title"] == "Draft.MakePick"): parsers.parse_draft_status(blob) elif "block_title" in blob and blob["block_title"] == "PlayerInventory.GetPlayerInventory": parsers.pass_through("inventory", blob["payload"]["playerId"], blob["payload"]) elif "block_title" in blob and blob["block_title"] == "Rank.Updated": parsers.pass_through("rank_change", blob["playerId"], blob) elif "block_title" in blob and blob["block_title"] == "Inventory.Updated": parsers.pass_through("inventory_update", None, blob) elif ("block_title" in blob and blob["block_title"] == "ClientToMatchServiceMessageType_ClientToGREMessage" and "Payload" in blob and "SubmitDeckResp" in blob['Payload']): parsers.parse_sideboard_submit(blob) elif "matchGameRoomStateChangedEvent" in blob: dispatch_match_gameroom_state_change(blob) elif "block_title" in blob and blob["block_title"] == "Event.MatchCreated": parsers.parse_match_created(blob) # parse on hover event to check what we are looking at, at this moment elif "block_title" in blob and blob["block_title"] == "ClientToMatchServiceMessageType_ClientToGREUIMessage": parsers.parse_hover(blob)
def dispatch_blob(blob): seq = blob.get("block_title_sequence", -1) if seq: app.mtga_app.mtga_logger.debug("{}dispatching seq ({})".format( util.ld(), seq)) if "method" in blob and "jsonrpc" in blob: dispatch_jsonrpc_method(blob) elif "greToClientEvent" in blob: dispatch_gre_to_client(blob) elif "clientToGreMessage" in blob: dispatch_client_to_gre(blob) elif "Deck.GetDeckLists" in blob: # this looks like it's a response to a jsonrpc method parsers.parse_get_decklists(blob) elif "block_title" in blob and (blob["block_title"] == "Event.DeckSubmit" or \ blob["block_title"] == "Event.GetPlayerCourse"): parsers.parse_event_decksubmit(blob) elif "matchGameRoomStateChangedEvent" in blob: dispatch_match_gametoom_state_change(blob)
def json_blob_reader_task(in_queue, out_queue): def check_for_client_id(blob): if "authenticateResponse" in blob: if "clientId" in blob["authenticateResponse"]: # screw it, no one else is going to use this message, mess up the timestamp, who cares with mtga_watch_app.game_lock: if mtga_watch_app.player_id != blob[ "authenticateResponse"]['clientId']: mtga_watch_app.player_id = blob[ "authenticateResponse"]['clientId'] mtga_logger.debug( "{}check_for_client_id: got new clientId".format( util.ld())) mtga_watch_app.save_settings() general_output_queue.put( {"authenticateResponse": blob["authenticateResponse"]}) last_blob = None last_decklist = None error_count = 0 while all_die_queue.empty(): json_recieved = in_queue.get() if json_recieved is None: out_queue.put(None) break if last_blob == json_recieved: continue # don't double fire # check for decklist changes if mtga_watch_app.player_decks != last_decklist: last_decklist = mtga_watch_app.player_decks decklist_change_queue.put({ k: v.to_serializable(transform_to_counted=True) for k, v in last_decklist.items() }) # check for gamestate changes try: hero_library_hash = -1 opponent_hand_hash = -1 if mtga_watch_app.game: hero_library_hash = hash(mtga_watch_app.game.hero.library) opponent_hand_hash = hash(mtga_watch_app.game.opponent.hand) check_for_client_id(json_recieved) dispatchers.dispatch_blob(json_recieved) mtga_watch_app.last_blob = json_recieved error_count = 0 hero_library_hash_post = -1 opponent_hand_hash_post = -1 if mtga_watch_app.game: hero_library_hash_post = hash(mtga_watch_app.game.hero.library) opponent_hand_hash_post = hash( mtga_watch_app.game.opponent.hand) if hero_library_hash != hero_library_hash_post or opponent_hand_hash != opponent_hand_hash_post: game_state_change_queue.put(mtga_watch_app.game.game_state( )) # TODO: BREAKPOINT HERE if mtga_watch_app.game.final: game_state_change_queue.put({ "match_complete": True, "gameID": mtga_watch_app.game.match_id }) except: import traceback exc = traceback.format_exc() stack = traceback.format_stack() mtga_logger.error("{}Exception @ count {}".format( util.ld(True), mtga_watch_app.error_count)) mtga_logger.error(exc) mtga_logger.error(stack) mtga_watch_app.send_error( "Exception during check game state. Check log for more details" ) if error_count > 5: mtga_logger.error("{}error count too high; exiting".format( util.ld())) return last_blob = json_recieved
def parse_game_state_message(message): # DOM: ok import app.mtga_app as mtga_app with mtga_app.mtga_watch_app.game_lock: # the game state may become inconsistent in between these steps, so lock it if 'gameInfo' in message.keys(): if 'matchState' in message['gameInfo']: match_id = message['gameInfo']['matchID'] game_number = message['gameInfo']['gameNumber'] if message['gameInfo'][ 'matchState'] == "MatchState_GameInProgress": shared_battlefield = Zone("battlefield") shared_exile = Zone("exile") shared_limbo = Zone("limbo") shared_stack = Zone("stack") new_hero = Player( mtga_app.mtga_watch_app.game.hero.player_name, mtga_app.mtga_watch_app.game.hero.player_id, mtga_app.mtga_watch_app.game.hero.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.hero._deck_cards) new_oppo = Player( mtga_app.mtga_watch_app.game.opponent.player_name, mtga_app.mtga_watch_app.game.opponent.player_id, mtga_app.mtga_watch_app.game.opponent.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.opponent._deck_cards) new_hero.is_hero = True if mtga_app.mtga_watch_app.intend_to_join_game_with: new_hero.original_deck = mtga_app.mtga_watch_app.intend_to_join_game_with new_match_id = match_id + "-game{}".format(game_number) mtga_app.mtga_watch_app.game = Game( new_match_id, new_hero, new_oppo, shared_battlefield, shared_exile, shared_limbo, shared_stack) if message['gameInfo'][ 'matchState'] == "MatchState_GameComplete": results = message['gameInfo']['results'] parse_game_results( True, match_id + "-game{}".format(game_number), results) if 'annotations' in message.keys(): for annotation in message['annotations']: annotation_type = annotation['type'][0] if annotation_type == 'AnnotationType_ObjectIdChanged': try: original_id = None new_id = None details = annotation['details'] for detail in details: if detail['key'] == "orig_id": original_id = detail["valueInt32"][0] mtga_app.mtga_watch_app.game.ignored_iids.add( original_id) elif detail['key'] == "new_id": new_id = detail["valueInt32"][0] card_with_iid = mtga_app.mtga_watch_app.game.find_card_by_iid( original_id) if not card_with_iid: # no one has ref'd yet, we don't care continue new_card_already_exists = mtga_app.mtga_watch_app.game.find_card_by_iid( new_id) if new_card_already_exists: # just wipe the old card, the new card is already there assert new_card_already_exists.mtga_id == card_with_iid.mtga_id or -1 in [ new_card_already_exists.mtga_id, card_with_iid.mtga_id ], "{} / {}".format( new_card_already_exists.mtga_id, card_with_iid.mtga_id) card_with_iid.mtga_id = -1 else: card_with_iid.previous_iids.append(original_id) card_with_iid.game_id = new_id except: app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format( util.ld(), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error( "{}parsers:parse_game_state_message - error parsing annotation:" .format(util.ld())) app.mtga_app.mtga_logger.error( pprint.pformat(annotation)) app.mtga_app.mtga_watch_app.send_error( "Exception during parse annotation. Check log for more details" ) if 'gameObjects' in message.keys(): game_objects = message['gameObjects'] for object in game_objects: card_id = object['grpId'] instance_id = object['instanceId'] if instance_id in mtga_app.mtga_watch_app.game.ignored_iids: continue owner = object['controllerSeatId'] type = object["type"] zone = object['zoneId'] if type != "GameObjectType_Card": mtga_app.mtga_watch_app.game.ignored_iids.add(instance_id) else: player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup( zone) if zone: if not player: player = mtga_app.mtga_watch_app.game.hero # if zone is shared, don't care what player we use to put this card into it assert isinstance(player, Player) player.put_instance_id_in_zone(instance_id, owner, zone) zone.match_game_id_to_card(instance_id, card_id) if 'zones' in message.keys(): cards_to_remove_from_zones = {} for zone in message['zones']: try: removable = parse_zone(zone) if removable: cards_to_remove_from_zones[zone["zoneId"]] = removable except: app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format( util.ld(), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error( "{}error parsing zone:".format(util.ld())) app.mtga_app.mtga_logger.error(pprint.pformat(zone)) app.mtga_app.mtga_watch_app.send_error( "Exception during parse zone. Check log for more details" ) raise for zone_id in cards_to_remove_from_zones.keys(): remove_these = cards_to_remove_from_zones[zone_id] player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup( zone_id) for card in remove_these: if card in zone.cards: zone.cards.remove(card)
def parse_game_state_message(message, timestamp=None): # DOM: ok import app.mtga_app as mtga_app with mtga_app.mtga_watch_app.game_lock: # the game state may become inconsistent in between these steps, so lock it if "turnInfo" in message.keys(): if "turnNumber" in message["turnInfo"].keys(): player = app.mtga_app.mtga_watch_app.game.get_player_in_seat( message["turnInfo"]["activePlayer"]) if "decisionPlayer" in message["turnInfo"].keys(): decisionPlayer = app.mtga_app.mtga_watch_app.game.get_player_in_seat( message["turnInfo"]["decisionPlayer"]) else: decisionPlayer = app.mtga_app.mtga_watch_app.game.last_decision_player if timestamp: now = datetime.datetime.now() if app.mtga_app.mtga_watch_app.game.last_log_timestamp is None: app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now app.mtga_app.mtga_watch_app.game.log_start_time = timestamp app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer measured_time_diff = now - app.mtga_app.mtga_watch_app.game.last_measured_timestamp log_time_diff = timestamp - app.mtga_app.mtga_watch_app.game.last_log_timestamp if measured_time_diff > log_time_diff: log_time_diff = measured_time_diff # some turns are really fast, and the logs see it as 0 seconds. Add what we measured instead, app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now ct_obj = { "turnInfo": message["turnInfo"], "diff": log_time_diff, "countsAgainst": app.mtga_app.mtga_watch_app.game.last_decision_player } app.mtga_app.mtga_watch_app.game.chess_timer.append(ct_obj) general_output_queue.put({ "decisionPlayerChange": True, "heroIsDeciding": decisionPlayer == app.mtga_app.mtga_watch_app.game.hero }) app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer app.mtga_app.mtga_watch_app.game.turn_number = message[ "turnInfo"]["turnNumber"] other_player_seat = 2 if message["turnInfo"][ "activePlayer"] == 1 else 1 other_player = app.mtga_app.mtga_watch_app.game.get_player_in_seat( other_player_seat) app.mtga_app.mtga_watch_app.game.current_player = player.player_name if not app.mtga_app.mtga_watch_app.game.on_the_play: if message["turnInfo"]["turnNumber"] % 2 == 1: app.mtga_app.mtga_watch_app.game.on_the_play = player.player_name else: app.mtga_app.mtga_watch_app.game.on_the_play = other_player.player_name app.mtga_app.mtga_watch_app.game.current_phase = message[ "turnInfo"]["phase"] turn_tuple = (message["turnInfo"]["turnNumber"], "phase") if turn_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs: mtga_app.mtga_watch_app.game.recorded_targetspecs.append( turn_tuple) turn = turn_tuple[0] active_player_seat = message["turnInfo"]["activePlayer"] active_player = mtga_app.mtga_watch_app.game.get_player_in_seat( active_player_seat) if turn % 2 == 1: text = "{} / {} Turn {}".format( turn, active_player.player_name, int( (turn + 1) / 2)) else: text = "{} / {} Turn {}".format( turn, active_player.player_name, int((turn / 2))) text_obj = build_event_text(text, "turn") queue_obj = {"game_history_event": [text_obj]} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) if "step" in message["turnInfo"].keys(): app.mtga_app.mtga_watch_app.game.current_phase += "-{}".format( message["turnInfo"]["step"]) app.mtga_app.mtga_logger.debug(message["turnInfo"]) if 'gameInfo' in message.keys(): if 'matchState' in message['gameInfo']: game_number = message['gameInfo']['gameNumber'] game_player_id = "-game{}-{}".format( game_number, mtga_app.mtga_watch_app.game.hero.player_id) match_id_raw = message['gameInfo']['matchID'] match_id = message['gameInfo']['matchID'] + game_player_id if 'results' in message['gameInfo']: results = message['gameInfo']['results'] parse_game_results(True, match_id, results) if message['gameInfo']['matchState'] == "MatchState_GameInProgress" and \ game_number > max(len(app.mtga_app.mtga_watch_app.match.game_results), 1): shared_battlefield = Zone("battlefield") shared_exile = Zone("exile") shared_limbo = Zone("limbo") shared_stack = Zone("stack") new_hero = Player( mtga_app.mtga_watch_app.game.hero.player_name, mtga_app.mtga_watch_app.game.hero.player_id, mtga_app.mtga_watch_app.game.hero.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.hero._deck_cards) new_oppo = Player( mtga_app.mtga_watch_app.game.opponent.player_name, mtga_app.mtga_watch_app.game.opponent.player_id, mtga_app.mtga_watch_app.game.opponent.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.opponent._deck_cards) new_hero.is_hero = True if mtga_app.mtga_watch_app.intend_to_join_game_with: new_hero.original_deck = mtga_app.mtga_watch_app.intend_to_join_game_with new_match_id = match_id_raw + "-game{}-{}".format( game_number, new_hero.player_id) mtga_app.mtga_watch_app.game = Game( new_match_id, new_hero, new_oppo, shared_battlefield, shared_exile, shared_limbo, shared_stack, app.mtga_app.mtga_watch_app.match.event_id, app.mtga_app.mtga_watch_app.match.opponent_rank) if 'annotations' in message.keys(): for annotation in message['annotations']: annotation_type = annotation['type'][0] if annotation_type == 'AnnotationType_ObjectIdChanged': try: original_id = None new_id = None details = annotation['details'] for detail in details: if detail['key'] == "orig_id": original_id = detail["valueInt32"][0] mtga_app.mtga_watch_app.game.ignored_iids.add( original_id) # NOTE: at one point Spencer thought it might be correct to ignore these AFTER # parsing the whole gameStateMessage, i.e. put these in a list here, and only add them # to ignored_iid's at the end of this function. # # That was incorrect, and led to cards flip-flopping in the UI. # This is correct as is. elif detail['key'] == "new_id": new_id = detail["valueInt32"][0] card_with_iid = mtga_app.mtga_watch_app.game.find_card_by_iid( original_id) if not card_with_iid: # no one has ref'd yet, we don't care continue new_card_already_exists = mtga_app.mtga_watch_app.game.find_card_by_iid( new_id) if new_card_already_exists: # just wipe the old card, the new card is already there assert new_card_already_exists.mtga_id == card_with_iid.mtga_id or -1 in [ new_card_already_exists.mtga_id, card_with_iid.mtga_id ], "{} / {}".format( new_card_already_exists.mtga_id, card_with_iid.mtga_id) card_with_iid.mtga_id = -1 else: card_with_iid.previous_iids.append(original_id) card_with_iid.game_id = new_id except: app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format( util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error( "{}parsers:parse_game_state_message - error parsing annotation:" .format(util.ld(True))) app.mtga_app.mtga_logger.error( pprint.pformat(annotation)) app.mtga_app.mtga_watch_app.send_error( "Exception during parse AnnotationType_ObjectIdChanged. Check log for more details" ) if annotation_type == "AnnotationType_TargetSpec": affector_id = annotation["affectorId"] affected_ids = annotation["affectedIds"] affector_card = mtga_app.mtga_watch_app.game.find_card_by_iid( affector_id) if not affector_card: # try abilitiy details = annotation["details"] grpid = None for detail in details: if detail["key"] == "grpid": grpid = detail["valueInt32"][0] affector_card = all_mtga_cards.find_one(grpid) targets = [] target_texts = [] for affected_id in affected_ids: affected_texts = build_event_texts_from_iid_or_grpid( affected_id, mtga_app.mtga_watch_app.game) target_texts.extend(affected_texts) game_obj = mtga_app.mtga_watch_app.game.find_card_by_iid( affected_id) target = game_obj if game_obj else affected_id targets.append(target) if ( affector_card, targets ) not in mtga_app.mtga_watch_app.game.recorded_targetspecs: mtga_app.mtga_watch_app.game.recorded_targetspecs.append( (affector_card, targets)) affector_texts = build_card_event_texts( affector_card, mtga_app.mtga_watch_app.game) event_texts = [*affector_texts, " targets "] if len(target_texts) > 2: for target in target_texts: event_texts.extend([target, ", "]) event_texts.append(target[-2]) event_texts.append(", and") event_texts.append(target[-1]) elif len(target_texts) > 1: event_texts.extend( [target_texts[-2], " and ", target_texts[-1]]) else: event_texts.extend(target_texts) queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) if annotation_type == "AnnotationType_ResolutionComplete": try: affector_id = annotation["affectorId"] card = mtga_app.mtga_watch_app.game.find_card_by_iid( affector_id) if isinstance(card, Ability): # card resolutions are handled in annotations below __unused_affected_ids = annotation["affectedIds"] grpid = None details = annotation["details"] for detail in details: if detail["key"] == "grpid": grpid = detail["valueInt32"][0] resolved_texts = build_event_texts_from_iid_or_grpid( affector_id, mtga_app.mtga_watch_app.game, grpid) event_texts = [*resolved_texts, " resolves"] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) except: app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format( util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error( "{}parsers:parse_game_state_message - error parsing annotation:" .format(util.ld(True))) app.mtga_app.mtga_logger.error( pprint.pformat(annotation)) app.mtga_app.mtga_watch_app.send_error( "Exception during parse AnnotationType_ResolutionComplete. Check log for more details" ) if 'gameObjects' in message.keys(): game_objects = message['gameObjects'] for object in game_objects: card_id = object['grpId'] instance_id = object['instanceId'] if instance_id in mtga_app.mtga_watch_app.game.ignored_iids: continue owner = object['controllerSeatId'] type = object["type"] zone = object['zoneId'] if type not in [ "GameObjectType_Card", "GameObjectType_Ability", "GameObjectType_SplitCard" ]: mtga_app.mtga_watch_app.game.ignored_iids.add(instance_id) else: player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup( zone) if zone: if not player: player = mtga_app.mtga_watch_app.game.hero # if zone is shared, don't care what player we use to put this card into it assert isinstance(player, Player) if type in [ "GameObjectType_Card", "GameObjectType_SplitCard" ]: player.put_instance_id_in_zone( instance_id, owner, zone) zone.match_game_id_to_card(instance_id, card_id) elif type == "GameObjectType_Ability": source_instance_id = object['parentId'] source_grp_id = object['objectSourceGrpId'] ability_name = all_mtga_cards.find_one(card_id) ability = Ability(ability_name, source_grp_id, source_instance_id, card_id, owner, instance_id) zone.abilities.append(ability) if "attackState" in object and object[ "attackState"] == "AttackState_Attacking": card = mtga_app.mtga_watch_app.game.find_card_by_iid( instance_id) limit_tuple = (mtga_app.mtga_watch_app.game.turn_number, "attacks", card) if limit_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs: mtga_app.mtga_watch_app.game.recorded_targetspecs.append( limit_tuple) card_texts = build_event_texts_from_iid_or_grpid( instance_id, mtga_app.mtga_watch_app.game) event_texts = [*card_texts, " attacking"] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) if "blockState" in object and object[ "blockState"] == "BlockState_Blocking": card = mtga_app.mtga_watch_app.game.find_card_by_iid( instance_id) block_info = object["blockInfo"] attacker_list = block_info["attackerIds"] for attacker in attacker_list: attacker_card = mtga_app.mtga_watch_app.game.find_card_by_iid( attacker) limit_tuple = ( mtga_app.mtga_watch_app.game.turn_number, "blocks", card, attacker_card) if limit_tuple not in mtga_app.mtga_watch_app.game.recorded_targetspecs: mtga_app.mtga_watch_app.game.recorded_targetspecs.append( limit_tuple) attacker_texts = build_event_texts_from_iid_or_grpid( attacker, mtga_app.mtga_watch_app.game) blocker_texts = build_event_texts_from_iid_or_grpid( instance_id, mtga_app.mtga_watch_app.game) event_texts = [ *blocker_texts, " blocks ", *attacker_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) if 'zones' in message.keys(): cards_to_remove_from_zones = {} for zone in message['zones']: try: removable = parse_zone(zone) if removable: cards_to_remove_from_zones[zone["zoneId"]] = removable except: app.mtga_app.mtga_logger.error( "{}Exception @ count {}".format( util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error( "{}error parsing zone:".format(util.ld(True))) app.mtga_app.mtga_logger.error(pprint.pformat(zone)) app.mtga_app.mtga_watch_app.send_error( "Exception during parse zone. Check log for more details" ) import traceback exc = traceback.format_exc() app.mtga_app.mtga_logger.error(exc) for zone_id in cards_to_remove_from_zones.keys(): remove_these = cards_to_remove_from_zones[zone_id] player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup( zone_id) for card in remove_these: if card in zone.cards: zone.cards.remove(card) if message[ "type"] == "GameStateType_Diff" and "players" in message.keys( ): players = message["players"] for player in players: seat = player["systemSeatNumber"] life_total = player["lifeTotal"] player_obj = mtga_app.mtga_watch_app.game.get_player_in_seat( seat) if player_obj.current_life_total != life_total: player_is_hero = mtga_app.mtga_watch_app.game.hero == player_obj player_life_text_type = "{}".format( "hero" if player_is_hero else "opponent") player_life_text = build_event_text( player_obj.player_name, player_life_text_type) event_texts = [ player_life_text, "'s life total changed ", "{} -> {}".format(player_obj.current_life_total, life_total) ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) player_obj.current_life_total = life_total # AFTER we've processed gameObjects, look for actions that should go in the log # If this code is in the block above gameObjects, then we will end up with lots of # "unknown" cards for opponent cards and actions if 'annotations' in message.keys(): for annotation in message['annotations']: annotation_type = annotation['type'][0] if annotation_type == "AnnotationType_ZoneTransfer": if "affectorId" not in annotation.keys(): affector_id = 0 else: affector_id = annotation["affectorId"] affected_ids = annotation["affectedIds"] details = annotation["details"] zone_src, zone_dest, category = None, None, None for detail in details: if detail["key"] == "zone_src": zone_src = detail["valueInt32"][0] if detail["key"] == "zone_dest": zone_dest = detail["valueInt32"][0] if detail["key"] == "category": category = detail["valueString"][0] card = mtga_app.mtga_watch_app.game.find_card_by_iid( affected_ids[0]) if affector_id == 0: affector_id = card.owner_seat_id player_texts = build_event_texts_from_iid_or_grpid( affector_id, mtga_app.mtga_watch_app.game) annotation_texts = build_event_texts_from_iid_or_grpid( affected_ids[0], mtga_app.mtga_watch_app.game) if category == "PlayLand": event_texts = [ *player_texts, " plays ", *annotation_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) elif category == "Draw": if affector_id > 2: owner = card.owner_seat_id player_texts.extend([ ": ", *build_event_texts_from_iid_or_grpid( owner, mtga_app.mtga_watch_app.game) ]) if card.pretty_name == "unknown": event_texts = [*player_texts, " draws"] else: event_texts = [ *player_texts, " draws ", *annotation_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) # build draw log event elif category == "CastSpell": event_texts = [ *player_texts, " casts ", *annotation_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) # build draw log event # TODO: see if this is redundant elif category == "Countered": event_texts = [ *player_texts, " counters ", *annotation_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) elif category == "Resolve": event_texts = [*annotation_texts, " resolves"] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) elif category == "Exile": event_texts = [ *player_texts, " exiles ", *annotation_texts ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj) # TODO: category == "Put" ? elif zone_dest == 37 or zone_dest == 33: # TODO: get rid of this hardcoded bs event_texts = [ *annotation_texts, " sent to graveyard ", "(" + category + ")" ] queue_obj = {"game_history_event": event_texts} mtga_app.mtga_watch_app.game.events.append( queue_obj["game_history_event"]) general_output_queue.put(queue_obj)
def block_watch_task(in_queue, out_queue): while all_die_queue.empty(): block_recieved = in_queue.get() if block_recieved is None: out_queue.put(None) break if "[" not in block_recieved and "{" not in block_recieved: continue block_lines = block_recieved.split("\n") if len(block_lines) < 2: continue request_or_response = None json_str = "" # hit the ex if block_lines[1] and block_lines[1].startswith( "==>") or block_lines[1].startswith("<=="): """ these logs looks like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM ==> Log.Info(530): { "json": "stuff" } """ title_line = block_lines[1] block_title = " ".join(title_line.split(" ")[1:]).split("(")[0] block_title_seq = None if "(" in title_line and ")" in title_line: block_title_seq = title_line.split("(")[1].split(")")[ 0] # wtf is this thing? request_or_response = "response" if title_line.startswith("==>"): request_or_response = "request" json_str = "\n".join(block_lines[2:]).strip() if json_str.startswith("["): # this is not valid json, we need to surround it with a header such that it's an object instead of a list json_str = '{{"{}": {}}}'.format(block_title, json_str) elif block_lines[1].strip() == "{": """ these logs look like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM: Match to 26848417E29213FE: GreToClientEvent { "json": "stuff" } """ block_title = block_lines[0].split(" ")[-1] json_str = "\n".join(block_lines[1:]) if json_str: try: blob = json.loads(json_str) mtga_logger.info("{}success parsing blob: {}({})".format( util.ld(), block_title, block_title_seq)) if request_or_response: blob["request_or_response"] = request_or_response if block_title: blob["block_title"] = block_title.strip() if block_title_seq: blob["block_title_sequence"] = block_title_seq out_queue.put(blob) except: mtga_logger.error("{}Could not parse json_blob `{}`".format( util.ld(), json_str)) mtga_watch_app.send_error( "Could not parse json_blob {}".format(json_str))
def block_watch_task(in_queue, out_queue): BLOCK_SEQ = 0 while all_die_queue.empty(): block_recieved = in_queue.get() if block_recieved is None: out_queue.put(None) break log_line = None if isinstance(block_recieved, tuple): log_line, block_recieved = block_recieved if "[" not in block_recieved and "{" not in block_recieved: continue block_lines = block_recieved.split("\n") request_or_response = None json_str = "" # hit the ex timestamp = None block_title_seq = None if block_lines[0] and block_lines[0].startswith( "[UnityCrossThreadLogger]"): line = block_lines[0].split("[UnityCrossThreadLogger]")[1] if line.startswith("==>") or line.startswith("<=="): request_or_response = "response" if line.startswith("==>"): request_or_response = "request" line = line[4:] block_title = line.split(" ")[0] indexes = [] if "{" in line: indexes.append(line.index("{")) if "[" in line: indexes.append(line.index("[")) first_open_bracket = min(indexes) json_str = line[first_open_bracket:] elif len(block_lines) > 1: try: timestamp = dateutil.parser.parse( block_lines[0].split("]")[1].split(": ")[0]) except: pass block_title = block_lines[0].split(" ")[-1] json_str = "\n".join(block_lines[1:]) # I think everything below this is deprecated... elif block_lines[1] and block_lines[1].startswith( "==>") or block_lines[1].startswith("<=="): """ these logs looks like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM ==> Log.Info(530): { "json": "stuff" } """ title_line = block_lines[1] block_title = " ".join(title_line.split(" ")[1:]).split("(")[0] block_title_seq = None if "(" in title_line and ")" in title_line: block_title_seq = title_line.split("(")[1].split(")")[ 0] # wtf is this thing? request_or_response = "response" if title_line.startswith("==>"): request_or_response = "request" json_str = "\n".join(block_lines[2:]).strip() if json_str.startswith("["): # this is not valid json, we need to surround it with a header such that it's an object instead of a list json_str = '{{"{}": {}}}'.format(block_title, json_str) elif block_lines[1].strip() == "{": """ DEPRECATED these logs look like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM: Match to 26848417E29213FE: GreToClientEvent { "json": "stuff" } """ try: timestamp = dateutil.parser.parse( block_lines[0].split("]")[1].split(": ")[0]) except: pass block_title = block_lines[0].split(" ")[-1] json_str = "\n".join(block_lines[1:]) elif block_lines[1].strip().endswith("{"): """ these blocks looks like: [UnityCrossThreadLogger]7/2/2018 10:27:59 PM (-1) Incoming Rank.Updated { "json": "stuff } """ block_title = block_lines[1].strip().split(" ")[ -2] # skip trailing { json_str = "{" + "\n".join( block_lines[2:] ) # cut the first two lines and manually add { back in if json_str: try: blob = json.loads(json_str) BLOCK_SEQ += 1 # useful: next time you're trying to figure out why a blob isn't getting through the queue: # if "DirectGame" in json_str and "method" in blob: # import pprint # pprint.pprint(blob) if log_line: blob["log_line"] = log_line if timestamp: blob["timestamp"] = timestamp mtga_logger.info( "{}success parsing blob: {}({}) / log_line {}".format( util.ld(), block_title, block_title_seq, log_line)) if request_or_response: blob["request_or_response"] = request_or_response if block_title: blob["block_title"] = block_title.strip() blob["block_title_sequence"] = BLOCK_SEQ out_queue.put(blob) except Exception as e: mtga_logger.error("{}Could not parse json_blob `{}`".format( util.ld(), json_str)) mtga_watch_app.send_error( "Could not parse json_blob {}".format(json_str))
def block_watch_task(in_queue, out_queue): while all_die_queue.empty(): block_recieved = in_queue.get() if block_recieved is None: out_queue.put(None) break log_line = None if isinstance(block_recieved, tuple): log_line, block_recieved = block_recieved if "[" not in block_recieved and "{" not in block_recieved: continue block_lines = block_recieved.split("\n") if len(block_lines) < 2: continue request_or_response = None json_str = "" # hit the ex timestamp = None block_title_seq = None if block_lines[1] and block_lines[1].startswith( "==>") or block_lines[1].startswith("<=="): """ these logs looks like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM ==> Log.Info(530): { "json": "stuff" } """ title_line = block_lines[1] block_title = " ".join(title_line.split(" ")[1:]).split("(")[0] block_title_seq = None if "(" in title_line and ")" in title_line: block_title_seq = title_line.split("(")[1].split(")")[ 0] # wtf is this thing? request_or_response = "response" if title_line.startswith("==>"): request_or_response = "request" json_str = "\n".join(block_lines[2:]).strip() if json_str.startswith("["): # this is not valid json, we need to surround it with a header such that it's an object instead of a list json_str = '{{"{}": {}}}'.format(block_title, json_str) elif block_lines[1].strip() == "{": """ these logs look like: [UnityCrossThreadLogger]6/7/2018 7:21:03 PM: Match to 26848417E29213FE: GreToClientEvent { "json": "stuff" } """ try: timestamp = dateutil.parser.parse( block_lines[0].split("]")[1].split(": ")[0]) except: pass block_title = block_lines[0].split(" ")[-1] json_str = "\n".join(block_lines[1:]) elif block_lines[0].strip().endswith("{"): """ these blocks looks like: [UnityCrossThreadLogger]7/2/2018 10:27:59 PM (-1) Incoming Rank.Updated { "json": "stuff } """ block_title = block_lines[0].strip().split(" ")[ -2] # skip trailing { json_str = "{" + "\n".join( block_lines[1:] ) # just cut the first line and manually add { back in if json_str: try: blob = json.loads(json_str) if log_line: blob["log_line"] = log_line if timestamp: blob["timestamp"] = timestamp mtga_logger.info( "{}success parsing blob: {}({}) / log_line {}".format( util.ld(), block_title, block_title_seq, log_line)) if request_or_response: blob["request_or_response"] = request_or_response if block_title: blob["block_title"] = block_title.strip() if block_title_seq: blob["block_title_sequence"] = block_title_seq out_queue.put(blob) except Exception as e: mtga_logger.error("{}Could not parse json_blob `{}`".format( util.ld(), json_str)) mtga_watch_app.send_error( "Could not parse json_blob {}".format(json_str))
def parse_game_state_message(message, timestamp=None): # DOM: ok import app.mtga_app as mtga_app with mtga_app.mtga_watch_app.game_lock: # the game state may become inconsistent in between these steps, so lock it if "turnInfo" in message.keys(): if "turnNumber" in message["turnInfo"].keys(): player = app.mtga_app.mtga_watch_app.game.get_player_in_seat(message["turnInfo"]["activePlayer"]) if "decisionPlayer" in message["turnInfo"].keys(): decisionPlayer = app.mtga_app.mtga_watch_app.game.get_player_in_seat(message["turnInfo"]["decisionPlayer"]) else: decisionPlayer = app.mtga_app.mtga_watch_app.game.last_decision_player if timestamp: now = datetime.datetime.now() if app.mtga_app.mtga_watch_app.game.last_log_timestamp is None: app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now app.mtga_app.mtga_watch_app.game.log_start_time = timestamp app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer measured_time_diff = now - app.mtga_app.mtga_watch_app.game.last_measured_timestamp log_time_diff = timestamp - app.mtga_app.mtga_watch_app.game.last_log_timestamp if measured_time_diff > log_time_diff: log_time_diff = measured_time_diff # some turns are really fast, and the logs see it as 0 seconds. Add what we measured instead, app.mtga_app.mtga_watch_app.game.last_log_timestamp = timestamp app.mtga_app.mtga_watch_app.game.last_measured_timestamp = now ct_obj = {"turnInfo": message["turnInfo"], "diff": log_time_diff, "countsAgainst": app.mtga_app.mtga_watch_app.game.last_decision_player} app.mtga_app.mtga_watch_app.game.chess_timer.append(ct_obj) general_output_queue.put({"decisionPlayerChange": True, "heroIsDeciding": decisionPlayer == app.mtga_app.mtga_watch_app.game.hero}) app.mtga_app.mtga_watch_app.game.last_decision_player = decisionPlayer app.mtga_app.mtga_watch_app.game.turn_number = message["turnInfo"]["turnNumber"] other_player_seat = 2 if message["turnInfo"]["activePlayer"] == 1 else 1 other_player = app.mtga_app.mtga_watch_app.game.get_player_in_seat(other_player_seat) app.mtga_app.mtga_watch_app.game.current_player = player.player_name if not app.mtga_app.mtga_watch_app.game.on_the_play: if message["turnInfo"]["turnNumber"] % 2 == 1: app.mtga_app.mtga_watch_app.game.on_the_play = player.player_name else: app.mtga_app.mtga_watch_app.game.on_the_play = other_player.player_name app.mtga_app.mtga_watch_app.game.current_phase = message["turnInfo"]["phase"] if "step" in message["turnInfo"].keys(): app.mtga_app.mtga_watch_app.game.current_phase += "-{}".format(message["turnInfo"]["step"]) app.mtga_app.mtga_logger.debug(message["turnInfo"]) if 'gameInfo' in message.keys(): if 'matchState' in message['gameInfo']: game_number = message['gameInfo']['gameNumber'] game_player_id = "-game{}-{}".format(game_number, mtga_app.mtga_watch_app.game.hero.player_id) match_id_raw = message['gameInfo']['matchID'] match_id = message['gameInfo']['matchID'] + game_player_id if 'results' in message['gameInfo']: results = message['gameInfo']['results'] parse_game_results(True, match_id, results) if message['gameInfo']['matchState'] == "MatchState_GameInProgress" and \ game_number > max(len(app.mtga_app.mtga_watch_app.match.game_results), 1): shared_battlefield = Zone("battlefield") shared_exile = Zone("exile") shared_limbo = Zone("limbo") shared_stack = Zone("stack") new_hero = Player(mtga_app.mtga_watch_app.game.hero.player_name, mtga_app.mtga_watch_app.game.hero.player_id, mtga_app.mtga_watch_app.game.hero.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.hero._deck_cards) new_oppo = Player(mtga_app.mtga_watch_app.game.opponent.player_name, mtga_app.mtga_watch_app.game.opponent.player_id, mtga_app.mtga_watch_app.game.opponent.seat, shared_battlefield, shared_exile, shared_limbo, shared_stack, mtga_app.mtga_watch_app.game.opponent._deck_cards) new_hero.is_hero = True if mtga_app.mtga_watch_app.intend_to_join_game_with: new_hero.original_deck = mtga_app.mtga_watch_app.intend_to_join_game_with new_match_id = match_id_raw + "-game{}-{}".format(game_number, new_hero.player_id) mtga_app.mtga_watch_app.game = Game(new_match_id, new_hero, new_oppo, shared_battlefield, shared_exile, shared_limbo, shared_stack, app.mtga_app.mtga_watch_app.match.event_id, app.mtga_app.mtga_watch_app.match.opponent_rank) if 'annotations' in message.keys(): for annotation in message['annotations']: annotation_type = annotation['type'][0] if annotation_type == 'AnnotationType_ObjectIdChanged': try: original_id = None new_id = None details = annotation['details'] for detail in details: if detail['key'] == "orig_id": original_id = detail["valueInt32"][0] mtga_app.mtga_watch_app.game.ignored_iids.add(original_id) # NOTE: at one point Spencer thought it might be correct to ignore these AFTER # parsing the whole gameStateMessage, i.e. put these in a list here, and only add them # to ignored_iid's at the end of this function. # # That was incorrect, and led to cards flip-flopping in the UI. # This is correct as is. elif detail['key'] == "new_id": new_id = detail["valueInt32"][0] card_with_iid = mtga_app.mtga_watch_app.game.find_card_by_iid(original_id) if not card_with_iid: # no one has ref'd yet, we don't care continue new_card_already_exists = mtga_app.mtga_watch_app.game.find_card_by_iid(new_id) if new_card_already_exists: # just wipe the old card, the new card is already there assert new_card_already_exists.mtga_id == card_with_iid.mtga_id or -1 in [new_card_already_exists.mtga_id, card_with_iid.mtga_id], "{} / {}".format(new_card_already_exists.mtga_id , card_with_iid.mtga_id) card_with_iid.mtga_id = -1 else: card_with_iid.previous_iids.append(original_id) card_with_iid.game_id = new_id except: app.mtga_app.mtga_logger.error("{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error("{}parsers:parse_game_state_message - error parsing annotation:".format(util.ld(True))) app.mtga_app.mtga_logger.error(pprint.pformat(annotation)) app.mtga_app.mtga_watch_app.send_error("Exception during parse annotation. Check log for more details") if 'gameObjects' in message.keys(): game_objects = message['gameObjects'] for object in game_objects: card_id = object['grpId'] instance_id = object['instanceId'] if instance_id in mtga_app.mtga_watch_app.game.ignored_iids: continue owner = object['controllerSeatId'] type = object["type"] zone = object['zoneId'] if type != "GameObjectType_Card": mtga_app.mtga_watch_app.game.ignored_iids.add(instance_id) else: player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup(zone) if zone: if not player: player = mtga_app.mtga_watch_app.game.hero # if zone is shared, don't care what player we use to put this card into it assert isinstance(player, Player) player.put_instance_id_in_zone(instance_id, owner, zone) zone.match_game_id_to_card(instance_id, card_id) if 'zones' in message.keys(): cards_to_remove_from_zones = {} for zone in message['zones']: try: removable = parse_zone(zone) if removable: cards_to_remove_from_zones[zone["zoneId"]] = removable except: app.mtga_app.mtga_logger.error("{}Exception @ count {}".format(util.ld(True), app.mtga_app.mtga_watch_app.error_count)) app.mtga_app.mtga_logger.error("{}error parsing zone:".format(util.ld(True))) app.mtga_app.mtga_logger.error(pprint.pformat(zone)) app.mtga_app.mtga_watch_app.send_error("Exception during parse zone. Check log for more details") import traceback exc = traceback.format_exc() app.mtga_app.mtga_logger.error(exc) for zone_id in cards_to_remove_from_zones.keys(): remove_these = cards_to_remove_from_zones[zone_id] player, zone = mtga_app.mtga_watch_app.game.get_owner_zone_tup(zone_id) for card in remove_these: if card in zone.cards: zone.cards.remove(card)