This commit is contained in:
Gabor Körber 2014-05-04 14:12:24 +02:00
commit 3e51a2cff7
10 changed files with 578878 additions and 0 deletions

0
__init__.py Normal file
View File

0
battle.py Normal file
View File

80
brainstorm.py Normal file
View File

@ -0,0 +1,80 @@
"""
Brainstorm File for Star Conflict Log Parsing
Needed
- find steam/scon folder on windows
- find steam/scon folder on mac
- find steam/scon folder on linux
- what about steamless installs?
Elaborate
- which GUI to use? wx? PyQt4? PySide?
- take over the database stuff from weltenfall.starconflict?
Investigate
- language based log files?
"""
#from win32com.shell import shell, shellcon
import os, sys, logging
from logs.logresolver import LogFileResolver as LogFile
from logs import combat
# for windows its kinda this:
settings = {'logfiles': os.path.join(os.path.expanduser('~'),
'Documents',
'My Games',
'StarConflict',
'logs'
)}
def find_log_files(logpath):
''' returns a list of 4-tuples representing
(combat.log, game.log, chat.log, game.net.log)
for each directory in the logpath
'''
ret = []
for directory in os.listdir(logpath):
full_dir = os.path.join(logpath, directory)
if os.path.isdir(full_dir):
if os.path.exists(os.path.join(full_dir, 'combat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.log'))\
and os.path.exists(os.path.join(full_dir, 'chat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.net.log')):
ret.append((
os.path.join(full_dir, 'combat.log'),
os.path.join(full_dir, 'game.log'),
os.path.join(full_dir, 'chat.log'),
os.path.join(full_dir, 'game.net.log')
))
return ret
def parse_games(logfiles):
_logfiles = []
for logpack in logfiles:
combatlog, gamelog, chatlog, gamenetlog = logpack
_logfiles.append(LogFile(combatlog))
#_logfiles.append(LogFile(gamelog))
#_logfiles.append(LogFile(chatlog))
#_logfiles.append(LogFile(gamenetlog))
return _logfiles
if __name__ == '__main__':
logfiles = find_log_files(settings['logfiles'])
logfiles = parse_games(logfiles)
#f = open('output.txt', 'w')
rex = {}
for logf in logfiles:
logf.parse()
for l in logf.lines:
if isinstance(l, dict):
#print l
pass
else:
if not l.unpack():
rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
if not isinstance(l, combat.UserEvent):
print l.values['log']
#f.write(l.values['log'] + '\n')
#f.close()
#print type(l)
print rex

13
logs/__init__.py Normal file
View File

@ -0,0 +1,13 @@
"""
Library dedicated to Star Conflict Logs.
Development plan:
- Make Combat Log parse completely.
- Make Game Log parse to get information like the local nickname or other needed infos.
- Create a soft emulation, which keeps track of the basic game outcome / events.
Additional Goals:
- probably save games in own format to keep space.
- database entries for kills, encountered players, etc.
"""

162
logs/combat.py Normal file
View File

@ -0,0 +1,162 @@
"""
todo:
- English implementation first.
- parsing combat.log
Prosa.
All logs start with something like
23:53:29.137 | LOGDATA
LOGDATA can be quite different depending on the logfile.
other forms encountered:
23:54:00.600 WARNING|
combat logs:
01:04:38.805 CMBT |
The typical log entry
"""
import re
class Log(object):
matcher = None
@classmethod
def is_handler(cls, log):
return False
class CombatLog(Log):
@classmethod
def _log_handler(cls, log):
if log.get('log', '').strip().startswith(cls.__name__):
return True
return False
@classmethod
def is_handler(cls, log):
if log.get('logtype', None) == 'CMBT':
return cls._log_handler(log)
return False
def __init__(self, values=None):
self.values = values
def unpack(self):
# unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
matchers = self.matcher
if not isinstance(matchers, list):
matchers = [matchers,]
for matcher in matchers:
m = matcher.match(self.values.get('log', ''))
if m:
self.values.update(m.groupdict())
return True
# @todo: where does this come from?
class Action(CombatLog):
pass
class Gameplay(CombatLog):
matcher = [
# usual: team(reason). explained reason.
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
# team, unexplained reason (unknown, Timeout)
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
]
class Apply(CombatLog): # Apply Aura.
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
class Damage(CombatLog):
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
class Spawn(CombatLog):
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
class Spell(CombatLog):
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:$|\s(?P<targets>.+))")
class Reward(CombatLog):
matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)")
class Participant(CombatLog):
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s{2}(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\smostDamageWith\s'(?P<module_class>[^']+)';(?P<additional>.*)|<(?P<other>\w+)>)")
class Rocket(CombatLog):
matcher = re.compile(r"^Rocket\s(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
class Heal(CombatLog):
matcher = [
# heal by module
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
# direct heal by source or n/a (global buff)
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
]
class Killed(CombatLog):
matcher = [
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
]
class Captured(CombatLog):
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
class AddStack(CombatLog):
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
class Cancel(CombatLog):
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'")
class Scores(CombatLog):
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)")
# Special classes
class GameEvent(CombatLog):
matcher = [
# game session identifier.
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
# start gameplay identifier.
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
# pve mission identifier.
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\w+)'"),
]
@classmethod
def _log_handler(cls, log):
if log.get('log', '').strip().startswith('======='):
return True
return False
def unpack(self):
# unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
matchers = self.matcher
if not isinstance(matchers, list):
matchers = [matchers,]
for matcher in matchers:
m = matcher.match(self.values.get('log', '').strip('=').strip())
if m:
self.values.update(m.groupdict())
return True
class UserEvent(CombatLog):
""" special class for combat logs that might be associated with the playing player """
@classmethod
def _log_handler(cls, log):
if log.get('log', '').strip():
return True
return False
# Action?
COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal,
Gameplay, #?
Scores,
Killed, Captured, AddStack, Cancel,
GameEvent, UserEvent
]

78
logs/logfile.py Normal file
View File

@ -0,0 +1,78 @@
#
"""
Author: Gabor Guzmics, 2013-2014
LogFile is an object capable to load SCon Logfiles and parse their ingredients
It can be extended by overriding resolve to understand Logentries further.
Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary
data by setting the LogFile<instance>._data yourself.
"""
import re
RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
R_SCLOG = re.compile(RE_SCLOG)
class LogFile(object):
def __init__(self, fname=None,
folder=None):
self.fname = fname
self.folder = folder # only for custom tagging.
self.lines = []
self._data = None
if self.fname is not None:
self.open(self.fname)
def open(self, fname):
f = open(fname, 'r')
self._data = f.read()
f.close()
def parse(self):
# parse _data if we still have no lines.
if self._data:
data_lines = self._data.replace('\r', '\n').replace('\n\n', '\n').split('\n')
lines = []
for line in data_lines:
if not line:
continue
elif not isinstance(line, basestring):
lines.append(line)
continue
elif line.startswith('---'):
continue
else:
# get the timecode & logtype
m = R_SCLOG.match(line)
if m:
g = m.groupdict()
if 'logtype' in g.keys():
g['logtype'] = g['logtype'].strip()
lines.append(g)
else:
lines.append(line)
self.lines = lines
# try to identify (resolve) lines.
if self.lines:
lines = []
for line in self.lines:
l = line
if isinstance(line, basestring):
# Unknown Log?
pass
elif isinstance(line, dict):
# Unresolved Log.
l = self.resolve(line)
elif line is None:
# dafuq?
pass
else:
# might be an object?
pass
lines.append(l)
self.lines = lines
def resolve(self, line):
# line is a dict.
# try to find a class that is responsible for this log.
return line

19
logs/logresolver.py Normal file
View File

@ -0,0 +1,19 @@
"""
Resolves Logs.
"""
from logfile import LogFile
from combat import COMBAT_LOGS
class LogFileResolver(LogFile):
resolution_classes = COMBAT_LOGS
def __init__(self, *args, **kwargs):
super(LogFileResolver, self).__init__(*args, **kwargs)
self.resolution_classes = self.resolution_classes or []
def resolve(self, line):
for klass in self.resolution_classes:
if klass.is_handler(line):
return klass(line)
return line

578501
output.txt Normal file

File diff suppressed because it is too large Load Diff

0
test/__init__.py Normal file
View File

25
test/regexes.py Normal file
View File

@ -0,0 +1,25 @@
"""
Each Line has to be separated first into timecode and kind of log.
"""
import re
Lines = [
"23:53:29.239 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg'",
"23:53:29.841 WARNING| ^1UiResourceManager::LoadStrings(): empty string \"gameplay_map_pve_gate\" for default language",
"01:05:08.735 CMBT | Spawn SpaceShip for player0 (OregyenDuero, #00039C86). 'Ship_Race3_L_T3'",
"03:43:29.796 CMBT | AddStack aura 'Spell_Cold_Ray' id 3492 type AURA_SLOW_MOVEMENT. new stacks count 5",
]
RE_TIME = r'\d{2,2}\:\d{2,2}\:\d{2,2}\.\d{3,3}\s\w+'
RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
R_SCLOG = re.compile(RE_SCLOG)
for line in Lines:
m = R_SCLOG.match(line)
if m:
print m.groups()