diff --git a/src/scon/analyze.py b/src/scon/analyze.py index 58fcdd1..3b48faa 100644 --- a/src/scon/analyze.py +++ b/src/scon/analyze.py @@ -38,8 +38,8 @@ if __name__ == '__main__': COUNT_GOOD = True # count via rex good packets aswell. useful to see total encountered packets in summary. LOG_GOOD_ONLY = False # Log good packets only. if set to false, will log unknown packets to trash_log. LOG_BAD_CMBT = True # by default, the main logs of interest for unknown entries is combat logs. here you can finetune which logs to catch. - LOG_BAD_CHAT = False - LOG_BAD_GAME = False + LOG_BAD_CHAT = True + LOG_BAD_GAME = True # set up our logging to do our task: FILE_MAIN_LOG = 'scon.log.bak' @@ -123,7 +123,10 @@ if __name__ == '__main__': if not l.unpack() or COUNT_GOOD: rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1 if not LOG_GOOD_ONLY and LOG_BAD_GAME and not isinstance(l, game.GameLog): - trash_log.info((l.values['log'])) + if l and l.values: + trash_log.info((l.values['log'])) + else: + print(l) else: logging.warning('No game log in %s' % logf.idstr) if logf.chat_log: @@ -136,7 +139,10 @@ if __name__ == '__main__': if not l.unpack() or COUNT_GOOD: rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1 if not LOG_GOOD_ONLY and LOG_BAD_CHAT and not isinstance(l, chat.ChatLog): - trash_log.info((l.values['log'])) + if l and l.values: + trash_log.info((l.values['log'])) + else: + print(l) else: logging.warning('No chat log in %s' % logf.idstr) diff --git a/src/scon/game/battle.py b/src/scon/game/battle.py index 564c0ee..bc7440c 100644 --- a/src/scon/game/battle.py +++ b/src/scon/game/battle.py @@ -3,18 +3,29 @@ todo: finding battles. factory for missions, skirmishes? """ +from scon.logs import game, combat # basic battle: responsible for managing, recognizing and parsing a single battle instance. class Battle(object): - def __init__(self, parent=None): - # parent is a log-session usually + _game_type_strings = [] + + @classmethod + def is_my_gametype(cls, gametype=None, level=None): + if gametype: + if gametype in cls._game_type_strings: + return True + + def __init__(self, parent=None, gametype=None, level=None): + # parent is a log-session + self.parent = parent self.players = [] self.teams = [] self.time_start = None self.time_end = None self.owner = None self.live = False # whether this is a streamed object. - self.map = None + self.level = level or '' + self.gametype = gametype or '' def parse_details(self): # fast parse strategy: fill in all details about this battle. @@ -25,25 +36,25 @@ class Battle(object): pass class PvPBattle(Battle): - pass + _game_type_strings = ['BombTheBase', 'Control', 'KingOfTheHill', 'CaptureTheBase', 'TeamDeathMatch', 'GreedyTeamDeathMatch', 'Sentinel'] class PvPTDM(PvPBattle): - pass + _game_type_strings = ['TeamDeathMatch', 'GreedyTeamDeathMatch' ] class PvPDomination(PvPBattle): - pass + _game_type_strings = ['Control'] class PvPCombatRecon(PvPBattle): - pass + _game_type_strings = ['Sentinel'] class PvPCtB(PvPBattle): - pass + _game_type_strings = ['CaptureTheBase'] class PvPDetonation(PvPBattle): - pass + _game_type_strings = ['BombTheBase'] class PvPBeaconHunt(PvPBattle): - pass + _game_type_strings = ['KingOfTheHill'] # Dreads class DreadnoughtBattle(Battle): @@ -51,27 +62,68 @@ class DreadnoughtBattle(Battle): ### PvE Stuff: low prio. class PvEBattle(Battle): - pass + _game_type_strings = ['PVE_Mission',] class PvERaidBattle(PvEBattle): pass # Openspace time. class Openspace(Battle): + _game_type_strings = ['FreeSpace'] pass +class UnknownBattle(Battle): + @classmethod + def is_my_gametype(cls, gametype=None, level=None): + if gametype: + return True + +BATTLE_TYPES = [ + # here the more detailed ones + PvPTDM, PvPDomination, PvPCombatRecon, + PvPCtB, PvPDetonation, PvPBeaconHunt, + DreadnoughtBattle, + PvEBattle, PvERaidBattle, + # freespace, general pvp battle + Openspace, + PvPBattle, + # unknowns: + UnknownBattle + ] ### def battle_factory(logs): ''' takes a log session and returns the battles in it makes a preliminary scan for information ''' - - if logs.combat_log and logs.game_log: - # without these it does not make sense - # check combat_log - - pass - return [] + battles = [] + battle = None + if logs.game_log: + # check game log + for line in logs.game_log.lines: + if isinstance(line, game.StartingLevel): + if not line.unpack(): + print('Encountered broken packet.') + continue + if not line.is_mainmenu(): + # this is the beginning of a new battle. + if battle: + battles.append(battle) + + bklass = Battle + for klass in BATTLE_TYPES: + if klass.is_my_gametype(line.values.get('gametype', None), line.values.get('level', None)): + bklass = klass + break + if bklass: + battle = bklass(logs, line.values.get('gametype', None), line.values.get('level', None)) + else: + battle = None + else: + if battle: + battles.append(battle) + battle = None + + return battles \ No newline at end of file diff --git a/src/scon/logs/base.py b/src/scon/logs/base.py index b5302c2..73c890a 100644 --- a/src/scon/logs/base.py +++ b/src/scon/logs/base.py @@ -26,6 +26,7 @@ import logging This is the reason, the base layout of the log object is explained here. """ +import datetime L_CMBT = 'CMBT' L_WARNING = 'WARNING' @@ -33,7 +34,7 @@ L_NET = 'NET' # Not supported in near future. L_CHAT = 'CHAT' class Log(object): - __slots__ = ['trash', 'reviewed', '_match_id', 'values'] + __slots__ = ['trash', 'reviewed', '_match_id', 'values', '_timestamp'] matcher = None def __init__(self): @@ -41,11 +42,23 @@ class Log(object): self.reviewed = False self.values = None self._match_id = None + self._timestamp = None @classmethod def is_handler(cls, log): return False + @property + def timestamp(self): + if self._timestamp: + return self._timestamp + # build timestamp: + # datetime.time(hour[, minute[, second[, microsecond[, tzinfo]]]]) + _time = datetime.time( int(self.values.get('hh')), + int(self.values.get('mm')), + int(self.values.get('ss'), 0), + int(self.values.get('ns', 0)) ) + def unpack(self, force=False): ''' unpacks this log from its data and saves values ''' pass diff --git a/src/scon/logs/game.py b/src/scon/logs/game.py index 83acbfa..ee06869 100644 --- a/src/scon/logs/game.py +++ b/src/scon/logs/game.py @@ -87,6 +87,7 @@ class GameLog(Log): self.values.update(m.groupdict()) self._match_id = i self.reviewed = True + self.trash = False return True # unknown? self.trash = True @@ -176,6 +177,12 @@ class StartingLevel(GameLog): re.compile(r"^======\sstarting\slevel\:\s'(?P[^']+)'\s+======"), ] + def is_mainmenu(self): + if self.reviewed and self.values: + if 'mainmenu' in self.values.get('level', ''): + return True + return False + @classmethod def _is_handler(cls, log): if log.get('log', '').startswith('====== starting'): @@ -192,6 +199,7 @@ class LevelStarted(GameLog): if log.get('log', '').startswith('====== level'): return True return False + diff --git a/src/scon/qlogviewer.py b/src/scon/qlogviewer.py index 7755ee3..0a12f2d 100644 --- a/src/scon/qlogviewer.py +++ b/src/scon/qlogviewer.py @@ -2,6 +2,8 @@ import os, io, sys, logging, time from PyQt5 import QtGui, QtCore, Qt from scon.logs.session import LogSessionCollector from scon.logs import game, combat, chat +from scon.game.battle import battle_factory +from scon.config.settings import settings class SessionTreeView(Qt.QTreeView): def __init__(self, parent): @@ -44,33 +46,22 @@ class SessionTreeView(Qt.QTreeView): return session = self.sessions[signal.data()] session.parse_files(['game.log']) - info_object = Qt.QStandardItem('game.log - %s' % len(session.game_log.lines)) + info_object = Qt.QStandardItem('no games. (game.log has %s lines)' % len(session.game_log.lines)) info_object.setEditable(False) - game_sessions = 0 item.appendRow(info_object) + + battles = battle_factory(session) # # add all starting events - for line in session.game_log.lines: - if isinstance(line, game.StartingLevel): - line.unpack() - v = line.values - level = v.get('level') - o = Qt.QStandardItem("Level '%s' gametype '%s'" %( level, v.get('gametype', '') )) - if 'mainmenu' not in level: - game_sessions += 1 - o.setEditable(False) - info_object.appendRow(o) - info_object.setText('game.log - %s games' % (game_sessions,)) + for battle in battles: + o = Qt.QStandardItem("%s (Level '%s', Gametype '%s')" %( battle.__class__.__name__, battle.level, battle.gametype ) ) + o.setEditable(False) + info_object.appendRow(o) + if len(battles) > 0: + #session.parse_files(['combat.log', 'chat.log']) + info_object.setText('%s games' % (len(battles),)) return - session.parse_files(['combat.log']) - info_object = Qt.QStandardItem('combat.log - %s' % len(session.combat_log.lines)) - info_object.setEditable(False) - item.appendRow(info_object) - # - session.parse_files(['chat.log']) - info_object = Qt.QStandardItem('chat.log - %s' % len(session.chat_log.lines)) - info_object.setEditable(False) - item.appendRow(info_object) + except: import traceback traceback.print_exc() @@ -91,7 +82,10 @@ class MainWindow(Qt.QWidget): layout.addWidget(self.tree) #self.tree.itemClicked.connect(self.onClickItem) - self.tree.load_from_directory(os.path.join(os.path.expanduser('~'), 'Documents', 'My Games', 'sc')) + if os.path.exists(os.path.join(os.path.expanduser('~'), 'Documents', 'My Games', 'sc')): + self.tree.load_from_directory(os.path.join(os.path.expanduser('~'), 'Documents', 'My Games', 'sc')) + else: + self.tree.load_from_directory(settings.get_logs_path()) # or delayed (not good for debug): @@ -133,4 +127,5 @@ def main(): if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) + settings.autodetect() main() \ No newline at end of file diff --git a/src/scon/quick.py b/src/scon/quick.py index 309c579..423bf26 100644 --- a/src/scon/quick.py +++ b/src/scon/quick.py @@ -9,6 +9,7 @@ from scon.logs.logfiles import LogFileResolver as LogFile from scon.logs import combat, game, chat from scon.logs.session import LogSessionCollector from scon.logs.game import ClientInfo +from scon.game.battle import battle_factory from scon.config.settings import settings settings.autodetect() @@ -24,6 +25,8 @@ if __name__ == '__main__': print(('length combat log ', len(logf.combat_log.lines))) if logf.game_log: print(('length game log ', len(logf.game_log.lines))) + print(battle_factory(logf)) + print ("Cleaning.") logf.clean() if logf.combat_log: