mainly more work on examples

but also first logic in somewhat usable battle_factory
initial timestamping method (TBD)
This commit is contained in:
Gabor Körber 2017-05-22 20:12:15 +02:00
parent a5d353e302
commit 39bc7604bf
6 changed files with 123 additions and 46 deletions

View File

@ -38,8 +38,8 @@ if __name__ == '__main__':
COUNT_GOOD = True # count via rex good packets aswell. useful to see total encountered packets in summary. 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_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_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_CHAT = True
LOG_BAD_GAME = False LOG_BAD_GAME = True
# set up our logging to do our task: # set up our logging to do our task:
FILE_MAIN_LOG = 'scon.log.bak' FILE_MAIN_LOG = 'scon.log.bak'
@ -123,7 +123,10 @@ if __name__ == '__main__':
if not l.unpack() or COUNT_GOOD: if not l.unpack() or COUNT_GOOD:
rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1 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): 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: else:
logging.warning('No game log in %s' % logf.idstr) logging.warning('No game log in %s' % logf.idstr)
if logf.chat_log: if logf.chat_log:
@ -136,7 +139,10 @@ if __name__ == '__main__':
if not l.unpack() or COUNT_GOOD: if not l.unpack() or COUNT_GOOD:
rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1 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): 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: else:
logging.warning('No chat log in %s' % logf.idstr) logging.warning('No chat log in %s' % logf.idstr)

View File

@ -3,18 +3,29 @@
todo: finding battles. factory for missions, skirmishes? 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. # basic battle: responsible for managing, recognizing and parsing a single battle instance.
class Battle(object): class Battle(object):
def __init__(self, parent=None): _game_type_strings = []
# parent is a log-session usually
@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.players = []
self.teams = [] self.teams = []
self.time_start = None self.time_start = None
self.time_end = None self.time_end = None
self.owner = None self.owner = None
self.live = False # whether this is a streamed object. self.live = False # whether this is a streamed object.
self.map = None self.level = level or ''
self.gametype = gametype or ''
def parse_details(self): def parse_details(self):
# fast parse strategy: fill in all details about this battle. # fast parse strategy: fill in all details about this battle.
@ -25,25 +36,25 @@ class Battle(object):
pass pass
class PvPBattle(Battle): class PvPBattle(Battle):
pass _game_type_strings = ['BombTheBase', 'Control', 'KingOfTheHill', 'CaptureTheBase', 'TeamDeathMatch', 'GreedyTeamDeathMatch', 'Sentinel']
class PvPTDM(PvPBattle): class PvPTDM(PvPBattle):
pass _game_type_strings = ['TeamDeathMatch', 'GreedyTeamDeathMatch' ]
class PvPDomination(PvPBattle): class PvPDomination(PvPBattle):
pass _game_type_strings = ['Control']
class PvPCombatRecon(PvPBattle): class PvPCombatRecon(PvPBattle):
pass _game_type_strings = ['Sentinel']
class PvPCtB(PvPBattle): class PvPCtB(PvPBattle):
pass _game_type_strings = ['CaptureTheBase']
class PvPDetonation(PvPBattle): class PvPDetonation(PvPBattle):
pass _game_type_strings = ['BombTheBase']
class PvPBeaconHunt(PvPBattle): class PvPBeaconHunt(PvPBattle):
pass _game_type_strings = ['KingOfTheHill']
# Dreads # Dreads
class DreadnoughtBattle(Battle): class DreadnoughtBattle(Battle):
@ -51,27 +62,68 @@ class DreadnoughtBattle(Battle):
### PvE Stuff: low prio. ### PvE Stuff: low prio.
class PvEBattle(Battle): class PvEBattle(Battle):
pass _game_type_strings = ['PVE_Mission',]
class PvERaidBattle(PvEBattle): class PvERaidBattle(PvEBattle):
pass pass
# Openspace time. # Openspace time.
class Openspace(Battle): class Openspace(Battle):
_game_type_strings = ['FreeSpace']
pass 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): def battle_factory(logs):
''' takes a log session and returns the battles in it ''' takes a log session and returns the battles in it
makes a preliminary scan for information makes a preliminary scan for information
''' '''
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)
if logs.combat_log and logs.game_log: bklass = Battle
# without these it does not make sense for klass in BATTLE_TYPES:
# check combat_log 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
pass return battles
return []

View File

@ -26,6 +26,7 @@ import logging
This is the reason, the base layout of the log object is explained here. This is the reason, the base layout of the log object is explained here.
""" """
import datetime
L_CMBT = 'CMBT' L_CMBT = 'CMBT'
L_WARNING = 'WARNING' L_WARNING = 'WARNING'
@ -33,7 +34,7 @@ L_NET = 'NET' # Not supported in near future.
L_CHAT = 'CHAT' L_CHAT = 'CHAT'
class Log(object): class Log(object):
__slots__ = ['trash', 'reviewed', '_match_id', 'values'] __slots__ = ['trash', 'reviewed', '_match_id', 'values', '_timestamp']
matcher = None matcher = None
def __init__(self): def __init__(self):
@ -41,11 +42,23 @@ class Log(object):
self.reviewed = False self.reviewed = False
self.values = None self.values = None
self._match_id = None self._match_id = None
self._timestamp = None
@classmethod @classmethod
def is_handler(cls, log): def is_handler(cls, log):
return False 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): def unpack(self, force=False):
''' unpacks this log from its data and saves values ''' ''' unpacks this log from its data and saves values '''
pass pass

View File

@ -87,6 +87,7 @@ class GameLog(Log):
self.values.update(m.groupdict()) self.values.update(m.groupdict())
self._match_id = i self._match_id = i
self.reviewed = True self.reviewed = True
self.trash = False
return True return True
# unknown? # unknown?
self.trash = True self.trash = True
@ -176,6 +177,12 @@ class StartingLevel(GameLog):
re.compile(r"^======\sstarting\slevel\:\s'(?P<level>[^']+)'\s+======"), re.compile(r"^======\sstarting\slevel\:\s'(?P<level>[^']+)'\s+======"),
] ]
def is_mainmenu(self):
if self.reviewed and self.values:
if 'mainmenu' in self.values.get('level', ''):
return True
return False
@classmethod @classmethod
def _is_handler(cls, log): def _is_handler(cls, log):
if log.get('log', '').startswith('====== starting'): if log.get('log', '').startswith('====== starting'):
@ -195,6 +202,7 @@ class LevelStarted(GameLog):
GAME_LOGS = [#SteamInitialization, GAME_LOGS = [#SteamInitialization,
MasterServerSession, MasterServerSession,
ClientInfo, ClientInfo,

View File

@ -2,6 +2,8 @@ import os, io, sys, logging, time
from PyQt5 import QtGui, QtCore, Qt from PyQt5 import QtGui, QtCore, Qt
from scon.logs.session import LogSessionCollector from scon.logs.session import LogSessionCollector
from scon.logs import game, combat, chat from scon.logs import game, combat, chat
from scon.game.battle import battle_factory
from scon.config.settings import settings
class SessionTreeView(Qt.QTreeView): class SessionTreeView(Qt.QTreeView):
def __init__(self, parent): def __init__(self, parent):
@ -44,33 +46,22 @@ class SessionTreeView(Qt.QTreeView):
return return
session = self.sessions[signal.data()] session = self.sessions[signal.data()]
session.parse_files(['game.log']) 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) info_object.setEditable(False)
game_sessions = 0
item.appendRow(info_object) item.appendRow(info_object)
battles = battle_factory(session)
# #
# add all starting events # add all starting events
for line in session.game_log.lines: for battle in battles:
if isinstance(line, game.StartingLevel): o = Qt.QStandardItem("%s (Level '%s', Gametype '%s')" %( battle.__class__.__name__, battle.level, battle.gametype ) )
line.unpack() o.setEditable(False)
v = line.values info_object.appendRow(o)
level = v.get('level') if len(battles) > 0:
o = Qt.QStandardItem("Level '%s' gametype '%s'" %( level, v.get('gametype', '') )) #session.parse_files(['combat.log', 'chat.log'])
if 'mainmenu' not in level: info_object.setText('%s games' % (len(battles),))
game_sessions += 1
o.setEditable(False)
info_object.appendRow(o)
info_object.setText('game.log - %s games' % (game_sessions,))
return 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: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -91,7 +82,10 @@ class MainWindow(Qt.QWidget):
layout.addWidget(self.tree) layout.addWidget(self.tree)
#self.tree.itemClicked.connect(self.onClickItem) #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): # or delayed (not good for debug):
@ -133,4 +127,5 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
settings.autodetect()
main() main()

View File

@ -9,6 +9,7 @@ from scon.logs.logfiles import LogFileResolver as LogFile
from scon.logs import combat, game, chat from scon.logs import combat, game, chat
from scon.logs.session import LogSessionCollector from scon.logs.session import LogSessionCollector
from scon.logs.game import ClientInfo from scon.logs.game import ClientInfo
from scon.game.battle import battle_factory
from scon.config.settings import settings from scon.config.settings import settings
settings.autodetect() settings.autodetect()
@ -24,6 +25,8 @@ if __name__ == '__main__':
print(('length combat log ', len(logf.combat_log.lines))) print(('length combat log ', len(logf.combat_log.lines)))
if logf.game_log: if logf.game_log:
print(('length game log ', len(logf.game_log.lines))) print(('length game log ', len(logf.game_log.lines)))
print(battle_factory(logf))
print ("Cleaning.") print ("Cleaning.")
logf.clean() logf.clean()
if logf.combat_log: if logf.combat_log: