def __repr__(self): if self.mtga_id != -1: return "<Ability, {}: '{}...' {} iid={}>".format( all_mtga_cards.find_one(self.source_id).name, self.pretty_name[:20], self.mtga_id, self.game_id) else: return "<UnknownAbility: iid={}>".format(self.game_id)
def build_event_texts_from_iid_or_grpid(iid, game, grpid=None): if iid < 3: return build_card_event_texts(game.get_player_in_seat(iid), game) else: card_or_ability = game.find_card_by_iid(iid) or game.find_card_by_iid(grpid) if not card_or_ability: card_or_ability = all_mtga_cards.find_one(iid) return build_card_event_texts(card_or_ability, game)
def build_card_event_texts(card, game): if isinstance(card, Ability): owner_is_hero = game.hero == game.get_player_in_seat(card.owner_seat_id) text_type = "{}".format("hero" if owner_is_hero else "opponent") ability_source = all_mtga_cards.find_one(card.source_id) ability_source_text = build_event_text(ability_source.pretty_name, text_type) ability_text = build_event_text("ability", "ability", card.pretty_name) card_texts = [ability_source_text, "'s ", ability_text] elif isinstance(card, Player): text_type = "{}".format("hero" if card == game.hero else "opponent") card_texts = [build_event_text(card.player_name, text_type)] else: # it's a GameCard owner_is_hero = game.hero == game.get_player_in_seat(card.owner_seat_id) text_type = "{}".format("hero" if owner_is_hero else "opponent") card_texts = [build_event_text(card.pretty_name, text_type)] return card_texts
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)