.
0
src/scon/__init__.py
Normal file
90
src/scon/analyze.py
Normal file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tool to analyze Logs in general.
|
||||
"""
|
||||
import os, sys, logging
|
||||
from logs.logfiles import LogFileResolver as LogFile
|
||||
from logs import combat, game, chat
|
||||
from logs.session import LogSessionCollector
|
||||
from logs.game import ClientInfo
|
||||
|
||||
# for windows its kinda this:
|
||||
settings = {'root_path': os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',),
|
||||
'logfiles': os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',
|
||||
'logs'
|
||||
)}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
|
||||
'Documents', 'My Games', 'sc'))
|
||||
coll.collect_unique()
|
||||
#f = open('output.txt', 'w')
|
||||
rex_combat = {}
|
||||
rex_game = {}
|
||||
rex_chat = {}
|
||||
LOG_GOOD = True # Log good packets.
|
||||
for logf in coll.sessions:
|
||||
logf.parse_files(['game.log', 'combat.log', 'chat.log'])
|
||||
|
||||
print "----- Log %s -----" % logf.idstr
|
||||
if logf.combat_log:
|
||||
for l in logf.combat_log.lines:
|
||||
if isinstance(l, dict):
|
||||
#print l
|
||||
rex_combat['dict'] = rex_combat.get('dict', 0) + 1
|
||||
else:
|
||||
if not l.unpack() or LOG_GOOD:
|
||||
rex_combat[l.__class__.__name__] = rex_combat.get(l.__class__.__name__, 0) + 1
|
||||
if not isinstance(l, combat.UserEvent):
|
||||
if not LOG_GOOD:
|
||||
print l.values['log']
|
||||
if logf.game_log:
|
||||
for l in logf.game_log.lines:
|
||||
if isinstance(l, dict):
|
||||
rex_game['dict'] = rex_game.get('dict', 0) + 1
|
||||
elif isinstance(l, str):
|
||||
print l
|
||||
else:
|
||||
if l.unpack() and not LOG_GOOD:
|
||||
pass
|
||||
else:
|
||||
rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1
|
||||
if not LOG_GOOD:
|
||||
print l.values['log']
|
||||
if logf.chat_log:
|
||||
for l in logf.chat_log.lines:
|
||||
if isinstance(l, dict):
|
||||
rex_chat['dict'] = rex_chat.get('dict', 0) + 1
|
||||
elif isinstance(l, str):
|
||||
print l
|
||||
else:
|
||||
if l.unpack() and not LOG_GOOD:
|
||||
pass
|
||||
else:
|
||||
rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1
|
||||
if not LOG_GOOD:
|
||||
print l.values['log']
|
||||
logf.clean(True)
|
||||
# additional cleanup:
|
||||
logf.chat_log.lines = []
|
||||
logf.game_log.lines = []
|
||||
logf.combat_log.lines = []
|
||||
print 'Analysis complete:'
|
||||
print '#'*20+' RexCombat ' + '#' *20
|
||||
print rex_combat
|
||||
print '#'*20+' RexGame ' + '#' *20
|
||||
print rex_game
|
||||
print '#'*20+' RexChat ' + '#' *20
|
||||
print rex_chat
|
17
src/scon/app.py
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Main application functions.
|
||||
|
||||
* backing up logs.
|
||||
- from directory to directory
|
||||
- compression as option
|
||||
|
||||
* log-sessions:
|
||||
- contains one session of log
|
||||
- has a source (directory, file)
|
||||
- determines user
|
||||
- parses logs
|
||||
|
||||
|
||||
"""
|
0
src/scon/archive/__init__.py
Normal file
142
src/scon/archive/localbrowser.py
Normal file
@ -0,0 +1,142 @@
|
||||
import os, logging
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
|
||||
from treeview import TreeViewModel, Node
|
||||
from django.test import Client
|
||||
|
||||
class DebugPage(QtWebKit.QWebPage):
|
||||
def sayMyName(self):
|
||||
return 'DebugPage'
|
||||
|
||||
class LocalWebView(QtWebKit.QWebView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
basedir = kwargs.pop('basedir', None)
|
||||
QtWebKit.QWebView.__init__(self, *args, **kwargs)
|
||||
oldManager = self.page().networkAccessManager()
|
||||
self.setPage(DebugPage())
|
||||
self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir))
|
||||
|
||||
def set_basedir(self, basedir):
|
||||
self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir))
|
||||
|
||||
class LocalNetworkAccessManager(QtNetwork.QNetworkAccessManager):
|
||||
USE_NETWORK = False
|
||||
def __init__(self, parent=None, basedir=None):
|
||||
QtNetwork.QNetworkAccessManager.__init__(self, parent=None)
|
||||
if not basedir:
|
||||
# take current dir as basedir.
|
||||
self.basedir = os.path.dirname(os.path.abspath(__file__))
|
||||
else:
|
||||
self.basedir = basedir
|
||||
|
||||
def createRequest(self, operation, request, data):
|
||||
scheme = request.url().scheme()
|
||||
if scheme != 'page' and scheme != 'image':
|
||||
if self.USE_NETWORK:
|
||||
return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
|
||||
elif scheme == 'page':
|
||||
if operation == self.GetOperation:
|
||||
# Handle page:// URLs separately by creating custom
|
||||
# QNetworkReply objects.
|
||||
reply = PageReply(self, request.url(), self.GetOperation)
|
||||
#print('here')
|
||||
#print reply
|
||||
return reply
|
||||
elif operation == self.PostOperation:
|
||||
#print data.readAll()
|
||||
#print request
|
||||
reply = PageReply(self, request.url(), self.PostOperation)
|
||||
return reply
|
||||
elif scheme == 'image':
|
||||
if operation == self.GetOperation:
|
||||
return ImageReply(self, request.url(), self.GetOperation, self.basedir)
|
||||
else:
|
||||
if self.USE_NETWORK:
|
||||
return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
|
||||
return NoNetworkReply(self, request.url(), self.GetOperation)
|
||||
|
||||
class BasePageReply(QtNetwork.QNetworkReply):
|
||||
content_type = 'text/html; charset=utf-8'
|
||||
def __init__(self, parent, url, operation):
|
||||
QtNetwork.QNetworkReply.__init__(self, parent)
|
||||
self.content = self.initialize_content(url, operation)
|
||||
self.offset = 0
|
||||
self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type())
|
||||
self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content))
|
||||
QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()'))
|
||||
QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()'))
|
||||
self.open(self.ReadOnly | self.Unbuffered)
|
||||
self.setUrl(url)
|
||||
|
||||
def get_content_type(self):
|
||||
return self.content_type
|
||||
|
||||
def initialize_content(self, url, operation):
|
||||
return '''
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body><form method="POST" action=".">
|
||||
<img src="image:///scon/conflict-logo.png">
|
||||
<input type="text" name="a"></input>
|
||||
<input type="text" name="b"></input>
|
||||
<button class="submit">Submit</button></form></body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
def abort(self):
|
||||
pass
|
||||
|
||||
def bytesAvailable(self):
|
||||
return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self)
|
||||
|
||||
def isSequential(self):
|
||||
return True
|
||||
|
||||
def readData(self, maxSize):
|
||||
if self.offset < len(self.content):
|
||||
end = min(self.offset + maxSize, len(self.content))
|
||||
data = self.content[self.offset:end]
|
||||
self.offset = end
|
||||
return data
|
||||
|
||||
class PageReply(BasePageReply):
|
||||
def initialize_content(self, url, operation):
|
||||
c = Client()
|
||||
print "Response for %s, method %s" % (url.path(), operation)
|
||||
if operation == LocalNetworkAccessManager.GetOperation:
|
||||
response = c.get(unicode(url.path()), )
|
||||
elif operation == LocalNetworkAccessManager.PostOperation:
|
||||
response = c.post(unicode(url.path()))
|
||||
# response code
|
||||
print "Response Status: %s" % response.status_code
|
||||
# note: on a 404, we might need to trigger file response.
|
||||
return response.content
|
||||
|
||||
class NoNetworkReply(BasePageReply):
|
||||
def initialize_content(self, url, operation):
|
||||
return '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
class ImageReply(BasePageReply):
|
||||
content_type = 'image/png'
|
||||
def __init__(self, parent, url, operation, basedir):
|
||||
self.basedir = basedir
|
||||
BasePageReply.__init__(self, parent, url, operation)
|
||||
|
||||
def initialize_content(self, url, operation):
|
||||
path = os.path.join(self.basedir, unicode(url.path()).lstrip('/'))
|
||||
if not os.path.exists(path):
|
||||
logging.error('Image does not exist: %s' % path)
|
||||
return ''
|
||||
h = url.host()
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
71
src/scon/backup.py
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Backup Directories, Handle Files...
|
||||
"""
|
||||
|
||||
import os, logging, zipfile
|
||||
|
||||
def make_zipfile(output_filename, source_dir):
|
||||
relroot = os.path.abspath(os.path.join(source_dir, os.pardir))
|
||||
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
# add directory (needed for empty dirs)
|
||||
zip.write(root, os.path.relpath(root, relroot))
|
||||
for file in files:
|
||||
filename = os.path.join(root, file)
|
||||
if os.path.isfile(filename): # regular files only
|
||||
arcname = os.path.join(os.path.relpath(root, relroot), file)
|
||||
zip.write(filename, arcname)
|
||||
|
||||
def backup_log_directory(log_directory, backup_directory, compress=True,
|
||||
ommit_level=2, verbose=False):
|
||||
# @todo: raw copy
|
||||
# ommit_level 0: overwrite.
|
||||
# ommit_level 1: write if selected compression method not backuped yet
|
||||
# ommit_level 2: write only if neither method contains directory.
|
||||
nothing_found = True
|
||||
# get all directory names in log_directory.
|
||||
# zip them into backup_directory
|
||||
for directory in os.listdir(log_directory):
|
||||
full_dir = os.path.join(log_directory, directory)
|
||||
nothing_found = False
|
||||
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')):
|
||||
output_filename = '%s.zip' % directory
|
||||
if os.path.exists(os.path.join(backup_directory, output_filename))\
|
||||
and ((ommit_level >= 1 and compress) or (ommit_level==2 and not compress)):
|
||||
logging.warning('Log %s exists as zip backup, ommited.' % output_filename)
|
||||
elif os.path.exists(os.path.join(backup_directory, directory))\
|
||||
and ((ommit_level == 2 and compress) or (ommit_level>=1 and not compress)):
|
||||
logging.warning('Log %s exists as directory backup, ommited.' % directory)
|
||||
else:
|
||||
# do the backup
|
||||
if compress:
|
||||
make_zipfile(os.path.join(backup_directory, output_filename),
|
||||
full_dir)
|
||||
logging.info('Backed up %s' % directory)
|
||||
if verbose:
|
||||
print "Backed up %s" % directory
|
||||
else:
|
||||
if verbose:
|
||||
print "Directory Raw Backup not implemented yet."
|
||||
raise NotImplementedError
|
||||
else:
|
||||
if verbose:
|
||||
print "%s is not a directory." % full_dir
|
||||
if verbose and nothing_found:
|
||||
print "Nothing to backup found in %s" % log_directory
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "Performing Log Backup (Dev)"
|
||||
log_source = os.path.join(os.path.expanduser('~'),
|
||||
'Documents', 'My Games', 'StarConflict', 'logs')
|
||||
log_dest = os.path.join(os.path.expanduser('~'),
|
||||
'Documents', 'My Games', 'sc')
|
||||
backup_log_directory(log_source, log_dest, verbose=True, compress=True)
|
||||
|
33
src/scon/battle.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tool to analyze Logs in general.
|
||||
"""
|
||||
import os, sys, logging
|
||||
from logs.logfiles import LogFileResolver as LogFile
|
||||
from logs import combat, game, chat
|
||||
from logs.session import LogSessionCollector
|
||||
from logs.game import ClientInfo
|
||||
|
||||
# for windows its kinda this:
|
||||
settings = {'root_path': os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',),
|
||||
'logfiles': os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',
|
||||
'logs'
|
||||
)}
|
||||
if __name__ == '__main__':
|
||||
coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
|
||||
'Documents', 'My Games', 'sc'))
|
||||
coll.collect_unique()
|
||||
for logf in coll.sessions:
|
||||
logf.parse_files(['game.log', 'combat.log'])
|
||||
logf.clean()
|
||||
if logf.combat_log:
|
||||
print 'length combat log ', len(logf.combat_log.lines)
|
||||
if logf.game_log:
|
||||
print 'length game log ', len(logf.game_log.lines)
|
85
src/scon/brainstorm.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""
|
||||
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.logfiles import LogFileResolver as LogFile
|
||||
from logs import combat
|
||||
|
||||
# for windows its kinda this:
|
||||
settings = {'root_path': os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',),
|
||||
'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.read()
|
||||
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
|
4
src/scon/config/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""
|
||||
Handle SCon's config file.
|
||||
|
||||
"""
|
5
src/scon/config/common.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""
|
||||
|
||||
|
||||
|
||||
"""
|
119
src/scon/config/display_config.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""
|
||||
Simple brainstorm to display a config file.
|
||||
"""
|
||||
import os, logging
|
||||
from settings import settings
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# import ET:
|
||||
try:
|
||||
ET = None
|
||||
import lxml.etree as ET
|
||||
logging.info('Using LXML.')
|
||||
except ImportError:
|
||||
try:
|
||||
import cElementTree as ET
|
||||
logging.info('Using cElementTree')
|
||||
except ImportError:
|
||||
try:
|
||||
import elementtree.ElementTree as ET
|
||||
logging.info('Using ElementTree')
|
||||
except ImportError:
|
||||
import xml.etree.ElementTree as ET # python 2.5
|
||||
logging.info('Using xml.ElementTree')
|
||||
finally:
|
||||
if not ET:
|
||||
raise NotImplementedError, "XML Parser not found in your Python."
|
||||
##################################################################################################
|
||||
|
||||
class ConfigFile(object):
|
||||
def __init__(self, config_file=None):
|
||||
self.cvars = []
|
||||
if config_file:
|
||||
self.config_file = config_file
|
||||
elif settings:
|
||||
# settings based loading.
|
||||
self.config_file = os.path.join(settings.get_path(), 'user_config.xml')
|
||||
|
||||
def open(self, filename = None):
|
||||
# reads a config file.
|
||||
filename = filename or self.config_file
|
||||
self.tree = ET.parse(filename)
|
||||
doc = self.tree.getroot()
|
||||
if doc.tag == 'UserConfig' \
|
||||
and len(doc) == 1\
|
||||
and doc[0].tag == 'CVars'\
|
||||
and doc[0].attrib['version'] == '4':
|
||||
logging.info( "Found valid config file." )
|
||||
# save my cvars
|
||||
self.cvars = doc[0]
|
||||
else:
|
||||
logging.info( "Config File not supported." )
|
||||
return self
|
||||
|
||||
def pprint(self):
|
||||
# print out my cvars
|
||||
for child in self.cvars:
|
||||
print '%s = %s' % (child.tag, child.attrib['val'])
|
||||
|
||||
def write(self, filename):
|
||||
output = '<?xml version="1.0"?>\n'
|
||||
doc = self.tree.getroot()
|
||||
# we manually serialize it to keep it exactly the same
|
||||
# like original SC to avoid problems with their software.
|
||||
def append_node(node, depth=0):
|
||||
# xml serializing helper function...
|
||||
s = ['%s<%s' % (' '*depth*2, node.tag),]
|
||||
for key, val in node.attrib.items():
|
||||
s.append(' %s="%s"' % (key, val))
|
||||
if len(node):
|
||||
s.append('>\n')
|
||||
# append children
|
||||
for child in node:
|
||||
s.extend(append_node(child, depth+1))
|
||||
s.append('%s</%s>\n' % (' '*depth*2, node.tag))
|
||||
else:
|
||||
s.append(' />\n')
|
||||
return s
|
||||
l = append_node(doc)
|
||||
output = output + ''.join( l )
|
||||
if filename is None:
|
||||
# dev.
|
||||
assert output[-1], '\n'
|
||||
else:
|
||||
try:
|
||||
f = open(filename, 'w')
|
||||
f.write(output)
|
||||
finally:
|
||||
f.close()
|
||||
return output
|
||||
|
||||
def debug_serializing(self):
|
||||
# detects if output would result in the same data as input
|
||||
input, output = None, None
|
||||
try:
|
||||
f = open(self.config_file, 'r')
|
||||
input = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
output = self.write(None)
|
||||
return output == input
|
||||
|
||||
|
||||
def read_config(config_file):
|
||||
tree = ET.parse(config_file)
|
||||
# doc = tree.getroot()
|
||||
return tree
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Read the config
|
||||
settings.autodetect()
|
||||
c = ConfigFile().open()
|
||||
print '#' * 80
|
||||
print "Output File would be:"
|
||||
print c.write(None)
|
||||
print '#' * 80
|
||||
print "Detected Settings:"
|
||||
c.pprint()
|
||||
print '#' * 80
|
||||
print 'Serializing Test successful: %s' % c.debug_serializing()
|
||||
|
11
src/scon/config/readme.txt
Normal file
@ -0,0 +1,11 @@
|
||||
This Package deals with config files, but also deals with configuration itself.
|
||||
|
||||
This includes:
|
||||
- The config files for SCON itself (config_user.xml)
|
||||
- The config files for the applications in the scon package itself
|
||||
- basic config throughout the apps in this app, including...
|
||||
|
||||
* loading/importing XML Libraries
|
||||
* OS detection
|
||||
* logging setup
|
||||
etc.
|
31
src/scon/config/settings.py
Normal file
@ -0,0 +1,31 @@
|
||||
import os
|
||||
import platform
|
||||
|
||||
class Settings(dict):
|
||||
def autodetect(self, path=None):
|
||||
# autodetect settings.
|
||||
d = path
|
||||
system = platform.system()
|
||||
if system == 'Windows' or system.startswith('CYGWIN_NT'):
|
||||
# try to find user folder:
|
||||
d = d or os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',)
|
||||
elif system == 'Linux':
|
||||
raise NotImplementedError, "Implement Linux!"
|
||||
elif system == 'Darwin':
|
||||
raise NotImplementedError, "Implement Mac!"
|
||||
else:
|
||||
raise NotImplementedError, "Unknown System! %s" % platform.system()
|
||||
if not os.path.exists(d) or not os.path.isdir(d):
|
||||
raise Exception, "Configuration Autodetection failed. "
|
||||
self['root_path'] = d
|
||||
|
||||
def get_path(self):
|
||||
return self.get('root_path', None)
|
||||
|
||||
def get_logs_path(self):
|
||||
return os.path.join(self.get_path, 'logs')
|
||||
|
||||
settings = Settings()
|
3
src/scon/dejaqt/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""
|
||||
|
||||
"""
|
84
src/scon/dejaqt/folders.py
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
import logging, os
|
||||
try:
|
||||
from django.conf import settings
|
||||
except:
|
||||
logging.error('Django Settings could not be loaded. Maybe Django has not been initialized?')
|
||||
settings = None
|
||||
|
||||
class FolderLibrary(object):
|
||||
def __init__(self, folders=None):
|
||||
self._folders = {}
|
||||
try:
|
||||
if settings:
|
||||
self.folders.update( getattr(settings, 'DEJAQT_DIRS', {}) )
|
||||
except:
|
||||
logging.error('DEJAQT_DIRS in django settings threw error.')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if folders:
|
||||
# no try here: if this fails, you got yourself a programming error.
|
||||
self.folders.update(folders)
|
||||
self._keys = []
|
||||
self.build_keycache()
|
||||
|
||||
def get_folders(self):
|
||||
return self._folders
|
||||
|
||||
def set_folders(self, folders):
|
||||
self._folders = folders
|
||||
self.build_keycache()
|
||||
folders = property(get_folders, set_folders)
|
||||
|
||||
def build_keycache(self):
|
||||
self._keys = self._folders.keys()
|
||||
self._keys.sort(key=lambda item: (-len(item), item))
|
||||
|
||||
def add_folder(self, url, folder):
|
||||
if not url:
|
||||
url = ''
|
||||
self._folders[url] = folder
|
||||
self.build_keycache()
|
||||
|
||||
def match(self, url):
|
||||
# run down our keycache, first match wins.
|
||||
for key in self._keys:
|
||||
if url.startswith(key):
|
||||
return key
|
||||
|
||||
def matched_folder(self, url):
|
||||
m = self.match(url)
|
||||
if m is not None:
|
||||
folder = self._folders[m]
|
||||
#heading, rest = url[:len(m)], url[len(m):]
|
||||
rest = url[len(m):]
|
||||
real_folder = os.path.abspath( os.path.join(folder, rest) )
|
||||
if real_folder.startswith(os.path.abspath(folder)):
|
||||
return real_folder
|
||||
else:
|
||||
logging.error('%s does not seem to be a subpath of %s' % (real_folder, folder))
|
||||
|
||||
def print_folders(self):
|
||||
print '{'
|
||||
for k in self._keys:
|
||||
print "'%s': '%s'," % (k, self._folders[k])
|
||||
print '}'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test this:
|
||||
import os
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings'
|
||||
f = FolderLibrary({'abc/dab/': 'c:/media',
|
||||
'abc': 'd:/abc',
|
||||
'abc/dab/tmp': '/tmp',
|
||||
'uiuiui': 'x:/',
|
||||
'abc/vul/no': 'x:/2',
|
||||
'abc/vul': 'x:/3',
|
||||
'abc/vul/yes': 'x:/1',
|
||||
})
|
||||
f.add_folder('abc/dub/', 'c:/dubdub')
|
||||
f.print_folders()
|
||||
|
||||
print f.matched_folder('abc/dab/okokok/some.png')
|
||||
|
198
src/scon/dejaqt/qweb.py
Normal file
@ -0,0 +1,198 @@
|
||||
"""
|
||||
Qt WebKit Browser for local access to internal Django Views.
|
||||
"""
|
||||
import os, logging
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
|
||||
from django.test import Client
|
||||
from folders import FolderLibrary
|
||||
from django.http.request import QueryDict
|
||||
from urlparse import urlparse, parse_qs
|
||||
import cgi
|
||||
from io import BytesIO
|
||||
from django.http.multipartparser import MultiPartParser
|
||||
|
||||
class DebugPage(QtWebKit.QWebPage):
|
||||
def sayMyName(self):
|
||||
return 'DebugPage'
|
||||
|
||||
class DejaWebView(QtWebKit.QWebView):
|
||||
'''
|
||||
Optional:
|
||||
* folders: FolderLibrary() Instance.
|
||||
* page: Initialized QWebPage instance for initial page (default DebugPage())
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.folders = kwargs.pop('folders', FolderLibrary())
|
||||
page = kwargs.pop('page', DebugPage())
|
||||
QtWebKit.QWebView.__init__(self, *args, **kwargs)
|
||||
#self.oldManager = self.page().networkAccessManager()
|
||||
self.setPage(page)
|
||||
self.page().setNetworkAccessManager(DejaNetworkAccessManager(self))
|
||||
self.client = Client()
|
||||
#self.client.login(username='admin', password='admin')
|
||||
|
||||
class DejaNetworkAccessManager(QtNetwork.QNetworkAccessManager):
|
||||
'''
|
||||
The Deja Network Access Manager provides access to two new protocols:
|
||||
- page:/// tries to resolve a page internally via a django test client.
|
||||
- res:/// direct access to a resource.
|
||||
|
||||
USE_NETWORK delegates to other network access manager protocols, if False, it will not
|
||||
allow any requests outside these two protocols
|
||||
(hopefully disabling network access for your internal browser)
|
||||
|
||||
Note, if page does not find the page, a res:/// attempt is made automatically.
|
||||
This has to be expanded!
|
||||
|
||||
Note2: not sure if cookies and sessions will work this way!
|
||||
'''
|
||||
USE_NETWORK = False
|
||||
def __init__(self, parent=None):
|
||||
QtNetwork.QNetworkAccessManager.__init__(self, parent=parent)
|
||||
if parent:
|
||||
self.folders = getattr(parent, 'folders', FolderLibrary())
|
||||
|
||||
def createRequest(self, operation, request, data):
|
||||
scheme = request.url().scheme()
|
||||
if scheme != 'page' and scheme != 'res':
|
||||
if self.USE_NETWORK:
|
||||
return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
|
||||
elif scheme == 'page':
|
||||
if operation == self.GetOperation:
|
||||
# Handle page:// URLs separately by creating custom
|
||||
# QNetworkReply objects.
|
||||
reply = PageReply(self, request.url(), self.GetOperation)
|
||||
#print('here')
|
||||
#print reply
|
||||
return reply
|
||||
elif operation == self.PostOperation:
|
||||
reply = PageReply(self, request.url(), self.PostOperation, request, data)
|
||||
return reply
|
||||
elif scheme == 'res':
|
||||
if operation == self.GetOperation:
|
||||
return ResourceReply(self, request.url(), self.GetOperation)
|
||||
else:
|
||||
if self.USE_NETWORK:
|
||||
return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
|
||||
return NoNetworkReply(self, request.url(), self.GetOperation)
|
||||
|
||||
class BasePageReply(QtNetwork.QNetworkReply):
|
||||
content_type = 'text/html; charset=utf-8'
|
||||
def __init__(self, parent, url, operation, request=None, data=None):
|
||||
QtNetwork.QNetworkReply.__init__(self, parent)
|
||||
self.data = data
|
||||
self.request = request
|
||||
self.content = self.initialize_content(url, operation)
|
||||
self.offset = 0
|
||||
self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type())
|
||||
self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content))
|
||||
QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()'))
|
||||
QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()'))
|
||||
self.open(self.ReadOnly | self.Unbuffered)
|
||||
self.setUrl(url)
|
||||
|
||||
def get_content_type(self):
|
||||
return self.content_type
|
||||
|
||||
def initialize_content(self, url, operation):
|
||||
return '''
|
||||
<html>
|
||||
<head><title>Empty Page</title></head>
|
||||
<body>This is an empty page. If you see this, you need to subclass BasePageReply.</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
def abort(self):
|
||||
pass
|
||||
|
||||
def bytesAvailable(self):
|
||||
return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self)
|
||||
|
||||
def isSequential(self):
|
||||
return True
|
||||
|
||||
def readData(self, maxSize):
|
||||
if self.offset < len(self.content):
|
||||
end = min(self.offset + maxSize, len(self.content))
|
||||
data = self.content[self.offset:end]
|
||||
self.offset = end
|
||||
return data
|
||||
|
||||
class ResourceReply(BasePageReply):
|
||||
content_type = 'image/png'
|
||||
|
||||
def determine_content_type(self, path):
|
||||
return self.content_type
|
||||
|
||||
def initialize_content(self, url, operation):
|
||||
# determine folder:
|
||||
path = unicode(url.path()).lstrip('/')
|
||||
folders = getattr(self.parent(), 'folders')
|
||||
if folders:
|
||||
path = folders.matched_folder(path)
|
||||
if path:
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
else:
|
||||
logging.warning('Path does not exist: %s' % path)
|
||||
else:
|
||||
logging.error('Containing Folder not found for %s' % path)
|
||||
else:
|
||||
logging.error('Configuration Error: No Folders found.')
|
||||
return ''
|
||||
|
||||
class PageReply(ResourceReply):
|
||||
content_type = 'text/html'
|
||||
|
||||
def initialize_content(self, url, operation):
|
||||
try:
|
||||
c = self.parent().parent().client
|
||||
except:
|
||||
logging.error('Internal HTTP Client not found. Creating new.')
|
||||
c = Client()
|
||||
logging.info( "Response for %s, method %s" % (url.path(), operation) )
|
||||
if operation == DejaNetworkAccessManager.GetOperation:
|
||||
response = c.get(unicode(url.path()), follow=True )
|
||||
elif operation == DejaNetworkAccessManager.PostOperation:
|
||||
ct = str(self.request.rawHeader('Content-Type'))
|
||||
cl = str(self.request.rawHeader('Content-Length'))
|
||||
s = str(self.data.readAll())
|
||||
if ct.startswith('multipart/form-data'):
|
||||
# multipart parsing
|
||||
logging.error('Multipart Parsing Try...')
|
||||
b = BytesIO(s)
|
||||
q, files = MultiPartParser({'CONTENT_TYPE': ct,
|
||||
'CONTENT_LENGTH': cl,
|
||||
},
|
||||
b,
|
||||
[]).parse()
|
||||
response = c.post(unicode(url.path()), q, follow=True)
|
||||
else:
|
||||
# assume post data.
|
||||
q = QueryDict( s )
|
||||
response = c.post(unicode(url.path()), q, follow=True)
|
||||
self.content_type = response.get('Content-Type', self.content_type)
|
||||
# response code
|
||||
#print "Response Status: %s" % response.status_code
|
||||
# note: on a 404, we might need to trigger file response.
|
||||
if response.status_code == 404:
|
||||
return ResourceReply.initialize_content(self, url, DejaNetworkAccessManager.GetOperation)
|
||||
if hasattr(response, 'streaming_content'):
|
||||
return ''.join(response.streaming_content)
|
||||
return response.content
|
||||
|
||||
class NoNetworkReply(BasePageReply):
|
||||
def initialize_content(self, url, operation):
|
||||
return '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
26
src/scon/dejaqt/readme.txt
Normal file
@ -0,0 +1,26 @@
|
||||
I am always happy if a project spawns another project.
|
||||
|
||||
In this case DejaQt, a little django-qt bridge for yet another attempt to
|
||||
ease up programming for myself - and possibly for others.
|
||||
|
||||
Basicly, the theory is the following:
|
||||
- I create a Django App, that might run on a server.
|
||||
- But I actually want to make a standalone app too, which is executable, too
|
||||
Maybe even deploy an EXE with py2exe, and so on.
|
||||
- The standalone might have a lot more local functionality,
|
||||
But also shared codebase, with database, forms, etc.
|
||||
- I do not want shared code to be executed in a local webserver and create
|
||||
a network client to it. This is just too much abstraction, for a simple task.
|
||||
Not to speak about security...
|
||||
I also do not want to create two different codebases to do the same stuff.
|
||||
- Instead, I want a QWebView, using WebKit, which can call views and urls internally
|
||||
without actually starting up a django server instance, but still use django
|
||||
|
||||
DejaQt wants actually this.
|
||||
|
||||
Roadmap:
|
||||
- PyQt4 base implementation
|
||||
- GET and POST requests, and file transfers.
|
||||
- Basic setup layout:
|
||||
* define directories for file transfers
|
||||
* save client
|
0
src/scon/dj/__init__.py
Normal file
3
src/scon/dj/deprecated.txt
Normal file
@ -0,0 +1,3 @@
|
||||
this module will be reimplented as "djascon" for now.
|
||||
|
||||
better name pending.
|
0
src/scon/dj/scon/__init__.py
Normal file
6
src/scon/dj/scon/admin.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
import models
|
||||
# Register your models here.
|
||||
admin.site.register(models.Crafting)
|
||||
admin.site.register(models.CraftingInput)
|
||||
admin.site.register(models.Item)
|
1
src/scon/dj/scon/fixtures/generated.json
Normal file
10
src/scon/dj/scon/forms.py
Normal file
@ -0,0 +1,10 @@
|
||||
'''
|
||||
Created on 27.05.2014
|
||||
|
||||
@author: g4b
|
||||
'''
|
||||
from django import forms
|
||||
|
||||
class ConfigForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
592
src/scon/dj/scon/generate_fixtures.py
Normal file
@ -0,0 +1,592 @@
|
||||
"""
|
||||
Generate Fixtures for Crafting.
|
||||
Simple generator, does not integrate well into existing stuff, so please use
|
||||
only for bootstrapping.
|
||||
"""
|
||||
import os, json
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
DIR = os.path.join(BASE_DIR, 'scon', 'fixtures')
|
||||
|
||||
def write_fixture(data):
|
||||
f = open(os.path.join(DIR, 'generated.json'), 'w')
|
||||
f.write(json.dumps(data))
|
||||
f.close()
|
||||
|
||||
def build_pk_cache(data, models=None):
|
||||
pk_cache = {}
|
||||
# fill cache from existing
|
||||
for d in data:
|
||||
if 'pk' in d.keys():
|
||||
# has pk
|
||||
pk_cache[d['model']] = max(pk_cache.get('model', 0), d['pk'])
|
||||
for d in data:
|
||||
m = d['model']
|
||||
if models:
|
||||
if m not in models:
|
||||
continue
|
||||
if 'pk' in d.keys():
|
||||
#print "PK was already in there! %s" % d
|
||||
pass
|
||||
else:
|
||||
if m not in pk_cache.keys():
|
||||
pk_cache[m] = 1
|
||||
i = 1
|
||||
else:
|
||||
i = pk_cache[m] + 1
|
||||
pk_cache[m] = i
|
||||
d['pk'] = i
|
||||
return data
|
||||
|
||||
def lookup_pk(data, name, mdl='scon.item', kwargs=None):
|
||||
for d in data:
|
||||
if d['model'] == mdl:
|
||||
if d['fields'].get('name', '').lower() == name.lower():
|
||||
found = True
|
||||
if kwargs is not None:
|
||||
for key, val in kwargs.items():
|
||||
if not d['fields'].get(key, None) == val:
|
||||
found = False
|
||||
if found:
|
||||
return d['pk']
|
||||
|
||||
def generate_fixtures():
|
||||
data = []
|
||||
|
||||
ORES = [
|
||||
{'name': 'Impure tungsten', 'sell_price': 6600, 'icon': 'resource_tungsten_ore'},
|
||||
{'name': 'Impure osmium', 'sell_price': 4500, 'icon': 'resource_osmium_ore'},
|
||||
{'name': 'Impure silicon', 'sell_price': 600, 'icon': 'resource_silicon_ore'},
|
||||
{'name': 'Vanadium', 'sell_price': 500, 'icon': 'resource_vanadium'},
|
||||
{'name': 'Crystal shard', 'sell_price': 3500, 'icon': 'resource_crystal_shard'},
|
||||
]
|
||||
MATERIALS = [
|
||||
{'name': 'Tungsten plate',
|
||||
'description': 'Durable tungsten plate',
|
||||
'sell_price': 20000,
|
||||
'icon': 'component_tungsten_plate'},
|
||||
{'name': 'Screened battery', 'sell_price': 42000,
|
||||
'icon': 'component_screened_battery'},
|
||||
{'name': 'Osmium crystals', 'sell_price': 5500,
|
||||
'icon': 'component_osmium_crystals'},
|
||||
{'name': 'Pure Silicon', 'sell_price': 2500,
|
||||
'icon': 'component_pure_silicon'},
|
||||
{'name': 'Processing block', 'sell_price': 22000,
|
||||
'icon': 'component_processing_block'},
|
||||
{'name': 'Metal blank', 'sell_price': 1600,
|
||||
'icon': 'component_metal_blank'},
|
||||
{'name': 'Alien Monocrystal', 'sell_price': 25000,
|
||||
'icon': 'component_alien_monocrystal'},
|
||||
{'name': 'Computing chip', 'sell_price': 4500,
|
||||
'icon': 'component_computing_chip'},
|
||||
]
|
||||
AMMOS = [
|
||||
{'name': 'Explosive Shells',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_explosive_shells_mk4',
|
||||
},
|
||||
{'name': 'Double Deflector',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_double_deflector_mk4',
|
||||
},
|
||||
{'name': 'Xenon Lamp',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_xenon_lamp_mk4',
|
||||
},
|
||||
{'name': 'Attack Drone',
|
||||
'quality': 10,
|
||||
'sell_price': 1092,
|
||||
'icon': 'ammo_attack_drone',
|
||||
},
|
||||
{'name': 'Focusing Lens',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_focusing_lens',
|
||||
},
|
||||
{'name': 'Iridium Slugs',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_iridium_slugs',
|
||||
},
|
||||
{'name': 'Supercooled Charges',
|
||||
'quality': 4,
|
||||
'sell_price': 1000,
|
||||
'icon': 'ammo_supercooled_charges',
|
||||
},
|
||||
{'name': 'Doomsday Missile',
|
||||
'quality': 1,
|
||||
'sell_price': 1000,
|
||||
#'tech': 5,
|
||||
'icon': 'ammo_doomsday_missile',
|
||||
}
|
||||
]
|
||||
|
||||
ITEMS_NON_CRAFTING = [
|
||||
{'name': 'Target Tracking Coprocessor III',
|
||||
'typ': 5, # cpu
|
||||
'tech': 3,
|
||||
'sell_price': 20188,
|
||||
'description': 'Increases Critical Damage',
|
||||
'icon': 'cpu_target_tracking_coprocessor',
|
||||
},
|
||||
{'name': 'Plasma Gun III',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 3,
|
||||
'icon': 'weapon_plasma_gun',
|
||||
},
|
||||
{'name': 'Plasma Gun IV',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 4,
|
||||
'icon': 'weapon_plasma_gun',
|
||||
},
|
||||
{'name': 'Plasma Gun V',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 5,
|
||||
'icon': 'weapon_plasma_gun',
|
||||
},
|
||||
# assault rails:
|
||||
{'name': 'Assault Railgun III',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 3,
|
||||
'icon': 'weapon_assault_railgun',
|
||||
},
|
||||
{'name': 'Assault Railgun IV',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 4,
|
||||
'icon': 'weapon_assault_railgun',
|
||||
},
|
||||
{'name': 'Assault Railgun V',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 5,
|
||||
'icon': 'weapon_assault_railgun',
|
||||
},
|
||||
# beam cannon:
|
||||
{'name': 'Beam Cannon III',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 3,
|
||||
'icon': 'weapon_beam_cannon',
|
||||
},
|
||||
{'name': 'Beam Cannon IV',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 4,
|
||||
'icon': 'weapon_beam_cannon',
|
||||
},
|
||||
{'name': 'Beam Cannon V',
|
||||
'typ': 7, # weap
|
||||
'quality': 4,
|
||||
'tech': 5,
|
||||
'icon': 'weapon_beam_cannon',
|
||||
},
|
||||
]
|
||||
|
||||
ITEMS = [
|
||||
{'name': 'Duplicator',
|
||||
'typ': 0,
|
||||
'sell_price': 8000,
|
||||
'buy_price_premium': 200,
|
||||
'description': 'Revives in Invasion with Cargo once.',
|
||||
'icon': 'duplicator',
|
||||
},
|
||||
{'name': 'A1MA IV',
|
||||
'quality': 1,
|
||||
'tech': 4,
|
||||
'typ': 8, # active.
|
||||
'role': 0, # multipurp.
|
||||
'sell_price': 26910,
|
||||
'icon': 'active_a1ma',
|
||||
},
|
||||
{'name': 'Pirate "Orion" Targeting Complex V',
|
||||
'quality': 14,
|
||||
'tech': 5,
|
||||
'typ': 8, # active.
|
||||
'role': 3, # covops
|
||||
'icon': 'active_t5_orion_targeting_complex_pirate',
|
||||
},
|
||||
{'name': 'Pirate Engine Overcharge V',
|
||||
'quality': 14,
|
||||
'tech': 5,
|
||||
'typ': 8, # active.
|
||||
'role': 6, # gunship
|
||||
'icon': 'active_t5_engine_overcharge_pirate',
|
||||
},
|
||||
{'name': 'Pirate Mass Shield Generator V',
|
||||
'quality': 14,
|
||||
'tech': 5,
|
||||
'typ': 8, # active.
|
||||
'role': 7, # engi
|
||||
'icon': 'active_t5_mass_shield_generator_pirate',
|
||||
},
|
||||
{'name': 'Reverse Thruster III',
|
||||
'quality': 1,
|
||||
'tech': 3,
|
||||
'typ': 8, # active.
|
||||
'role': 9, # LRF
|
||||
'icon': 'active_reverse_thruster',
|
||||
},
|
||||
{'name': 'Reverse Thruster IV',
|
||||
'quality': 1,
|
||||
'tech': 4,
|
||||
'typ': 8, # active.
|
||||
'role': 9, # LRF
|
||||
'icon': 'active_reverse_thruster',
|
||||
},
|
||||
{'name': 'Reverse Thruster V',
|
||||
'quality': 1,
|
||||
'tech': 5,
|
||||
'typ': 8, # active.
|
||||
'role': 9, # LRF
|
||||
'icon': 'active_reverse_thruster',
|
||||
},
|
||||
{'name': 'Plasma Gun III',
|
||||
'quality': 5,
|
||||
'tech': 3,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_plasma_gun_mk5',
|
||||
},
|
||||
{'name': 'Plasma Gun IV',
|
||||
'quality': 5,
|
||||
'tech': 4,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_plasma_gun_mk5',
|
||||
},
|
||||
{'name': 'Plasma Gun V',
|
||||
'quality': 5,
|
||||
'tech': 5,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_plasma_gun_mk5',
|
||||
},
|
||||
{'name': 'Assault Railgun III',
|
||||
'quality': 5,
|
||||
'tech': 3,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_assault_rail_mk5',
|
||||
},
|
||||
{'name': 'Assault Railgun IV',
|
||||
'quality': 5,
|
||||
'tech': 4,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_assault_rail_mk5',
|
||||
},
|
||||
{'name': 'Assault Railgun V',
|
||||
'quality': 5,
|
||||
'tech': 5,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_assault_rail_mk5',
|
||||
},
|
||||
{'name': 'Beam Cannon III',
|
||||
'quality': 5,
|
||||
'tech': 3,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_beam_cannon_mk5',
|
||||
},
|
||||
{'name': 'Beam Cannon IV',
|
||||
'quality': 5,
|
||||
'tech': 4,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_beam_cannon_mk5',
|
||||
},
|
||||
{'name': 'Beam Cannon V',
|
||||
'quality': 5,
|
||||
'tech': 5,
|
||||
'typ': 7, # weap
|
||||
'icon': 'weapon_beam_cannon_mk5',
|
||||
},
|
||||
]
|
||||
BLUEPRINTS = [
|
||||
{'name': 'Focusing Lens Blueprint'},
|
||||
{'name': 'Iridium Slugs Blueprint'},
|
||||
{'name': 'Supercooled Charges Blueprint'},
|
||||
{'name': 'A1MA T4 Blueprint'},
|
||||
{'name': 'Orion-2 Targeting Complex Blueprint',
|
||||
'description': 'Module works twice as long but much weaker.'},
|
||||
{'name': 'Engine Warp Overcharge Blueprint'},
|
||||
{'name': 'Mass Shield Energizer Blueprint'},
|
||||
{'name': 'Reverse Thruster T3 Blueprint'},
|
||||
{'name': 'Reverse Thruster T4 Blueprint'},
|
||||
{'name': 'Reverse Thruster T5 Blueprint'},
|
||||
{'name': 'Beam Cannon Prototype T3 Blueprint'},
|
||||
{'name': 'Beam Cannon Prototype T4 Blueprint'},
|
||||
{'name': 'Beam Cannon Prototype T5 Blueprint'},
|
||||
{'name': 'Assault Railgun Prototype T3 Blueprint'},
|
||||
{'name': 'Assault Railgun Prototype T4 Blueprint'},
|
||||
{'name': 'Assault Railgun Prototype T5 Blueprint'},
|
||||
{'name': 'Plasma Gun Prototype T3 Blueprint'},
|
||||
{'name': 'Plasma Gun Prototype T4 Blueprint'},
|
||||
{'name': 'Plasma Gun Prototype T5 Blueprint'},
|
||||
{'name': 'Doomsday Missile Blueprint'},
|
||||
]
|
||||
CRAFTING = [
|
||||
{'item': 'Duplicator',
|
||||
'recipee': [(1, 'Processing Block'), (2,'Computing chip'), (2, 'Metal blank')]},
|
||||
{'item': 'Tungsten plate',
|
||||
'recipee': [(2, 'Impure tungsten'),]},
|
||||
{'item': 'Screened battery',
|
||||
'recipee': [(1, 'Tungsten plate'), (2, 'Computing chip')]},
|
||||
{'item': 'Osmium crystals',
|
||||
'recipee': [(1, 'Impure osmium'),]},
|
||||
{'item': 'Pure Silicon',
|
||||
'recipee': [(1, 'Impure silicon'),]},
|
||||
{'item': 'Computing chip',
|
||||
'recipee': [(1, 'Crystal shard'),]},
|
||||
{'item': 'Processing block',
|
||||
'recipee': [(4, 'Pure Silicon'), (2, 'Computing chip')]},
|
||||
{'item': 'Metal blank',
|
||||
'recipee': [(2, 'Vanadium'),]},
|
||||
{'item': 'Target Tracking Coprocessor III',
|
||||
'recipee': [(1, 'Screened battery'), (7, 'Metal blank'), (5, 'Pure silicon')]},
|
||||
{'item': 'Explosive Shells',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Metal blank'),(2, 'Pure Silicon')]},
|
||||
{'item': 'Attack drone',
|
||||
'recipee': [(1, 'Alien Monocrystal'), (1,'Computing chip')]},
|
||||
{'item': 'Double Deflector',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Osmium crystals'),]},
|
||||
{'item': 'Xenon Lamp',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Computing chip'), (1, 'Pure Silicon')]},
|
||||
|
||||
{'item': 'Focusing Lens',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Focusing Lens Blueprint'), (1, 'Osmium crystals')]},
|
||||
{'item': 'Supercooled Charges',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Supercooled Charges Blueprint'), (1, 'Computing chip')]},
|
||||
{'item': 'Iridium Slugs',
|
||||
'amount': 2,
|
||||
'recipee': [(1, 'Iridium Slugs Blueprint'), (1, 'Metal blank')]},
|
||||
{'item': 'A1MA IV',
|
||||
'recipee': [(1, 'A1MA T4 Blueprint'), (2, 'Processing block'),
|
||||
(14, 'Metal blank'), (2, 'Screened battery'),
|
||||
(20, 'Alien Monocrystal')]},
|
||||
{'item': 'Pirate "Orion" Targeting Complex V',
|
||||
'recipee': [(1, 'Orion-2 Targeting Complex Blueprint'),
|
||||
(3, 'Tungsten plate'),
|
||||
(4, 'Computing chip'),
|
||||
(2, 'Processing block'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': 'Pirate Engine Overcharge V',
|
||||
'recipee': [(1, 'Engine Warp Overcharge Blueprint'),
|
||||
(3, 'Tungsten plate'),
|
||||
(2, 'Osmium crystals'),
|
||||
(2, 'Processing block'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': 'Pirate Mass Shield Generator V',
|
||||
'recipee': [(1, 'Mass Shield Energizer Blueprint'),
|
||||
(10, 'Metal blank'),
|
||||
(3, 'Computing chip'),
|
||||
(3, 'Processing block'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
# lrf reverse blink:
|
||||
{'item': 'Reverse Thruster III',
|
||||
'recipee': [(1, 'Reverse Thruster T3 Blueprint'),
|
||||
(7, 'Metal blank'),
|
||||
(1, 'Screened battery'),
|
||||
(4, 'Computing chip'),
|
||||
(15, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': 'Reverse Thruster IV',
|
||||
'recipee': [(1, 'Reverse Thruster T4 Blueprint'),
|
||||
(12, 'Metal blank'),
|
||||
(2, 'Screened battery'),
|
||||
(5, 'Computing chip'),
|
||||
(20, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': 'Reverse Thruster V',
|
||||
'recipee': [(1, 'Reverse Thruster T5 Blueprint'),
|
||||
(7, 'Tungsten plate'),
|
||||
(3, 'Screened battery'),
|
||||
(6, 'Computing chip'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
|
||||
# plasma
|
||||
{'item': ('Plasma Gun III', {'quality': 5}),
|
||||
'recipee': [(1, 'Plasma Gun Prototype T3 Blueprint'),
|
||||
(1, ('Plasma Gun III', {'quality': 4})),
|
||||
(6, 'Metal blank'),
|
||||
(3, 'Screened battery'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Plasma Gun IV', {'quality': 5}),
|
||||
'recipee': [(1, 'Plasma Gun Prototype T4 Blueprint'),
|
||||
(1, ('Plasma Gun IV', {'quality': 4})),
|
||||
(1, 'Tungsten plate'),
|
||||
(4, 'Screened battery'),
|
||||
(50, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Plasma Gun V', {'quality': 5}),
|
||||
'recipee': [(1, 'Plasma Gun Prototype T5 Blueprint'),
|
||||
(1, ('Plasma Gun V', {'quality': 4})),
|
||||
(3, 'Tungsten plate'),
|
||||
(5, 'Screened battery'),
|
||||
(70, 'Alien Monocrystal')
|
||||
]},
|
||||
# assault
|
||||
{'item': ('Assault Railgun III', {'quality': 5}),
|
||||
'recipee': [(1, 'Assault Railgun Prototype T3 Blueprint'),
|
||||
(1, ('Assault Railgun III', {'quality': 4})),
|
||||
(6, 'Metal blank'),
|
||||
(3, 'Screened battery'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Assault Railgun IV', {'quality': 5}),
|
||||
'recipee': [(1, 'Assault Railgun Prototype T4 Blueprint'),
|
||||
(1, ('Assault Railgun IV', {'quality': 4})),
|
||||
(1, 'Tungsten plate'),
|
||||
(4, 'Screened battery'),
|
||||
(50, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Assault Railgun V', {'quality': 5}),
|
||||
'recipee': [(1, 'Assault Railgun Prototype T5 Blueprint'),
|
||||
(1, ('Assault Railgun V', {'quality': 4})),
|
||||
(3, 'Tungsten plate'),
|
||||
(5, 'Screened battery'),
|
||||
(70, 'Alien Monocrystal')
|
||||
]},
|
||||
# beam
|
||||
{'item': ('Beam Cannon III', {'quality': 5}),
|
||||
'recipee': [(1, 'Beam Cannon Prototype T3 Blueprint'),
|
||||
(1, ('Beam Cannon III', {'quality': 4})),
|
||||
(6, 'Metal blank'),
|
||||
(3, 'Screened battery'),
|
||||
(30, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Beam Cannon IV', {'quality': 5}),
|
||||
'recipee': [(1, 'Beam Cannon Prototype T4 Blueprint'),
|
||||
(1, ('Beam Cannon IV', {'quality': 4})),
|
||||
(1, 'Tungsten plate'),
|
||||
(4, 'Screened battery'),
|
||||
(50, 'Alien Monocrystal')
|
||||
]},
|
||||
{'item': ('Beam Cannon V', {'quality': 5}),
|
||||
'recipee': [(1, 'Beam Cannon Prototype T5 Blueprint'),
|
||||
(1, ('Beam Cannon V', {'quality': 4})),
|
||||
(3, 'Tungsten plate'),
|
||||
(5, 'Screened battery'),
|
||||
(70, 'Alien Monocrystal')
|
||||
]},
|
||||
# missiles
|
||||
{'item': 'Doomsday Missile',
|
||||
'recipee': [(1, 'Doomsday Missile Blueprint'),
|
||||
(2, 'Osmium crystals'),
|
||||
(1, 'Computing chip'),
|
||||
(1, 'Metal blank'),
|
||||
]},
|
||||
]
|
||||
|
||||
for ore in ORES:
|
||||
fields = {'typ': 12,
|
||||
'tech': 0,
|
||||
'craftable': True,
|
||||
}
|
||||
fields.update(ore)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
for mat in MATERIALS:
|
||||
fields = {'typ': 13,
|
||||
'tech': 0,
|
||||
'craftable': True,
|
||||
}
|
||||
fields.update(mat)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
|
||||
for ammo in AMMOS:
|
||||
fields = {'typ': 8,
|
||||
'tech': 0,
|
||||
'craftable': True,
|
||||
}
|
||||
fields.update(ammo)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
|
||||
for item in ITEMS:
|
||||
fields = {
|
||||
# items define typ and tech!
|
||||
'craftable': True,
|
||||
}
|
||||
fields.update(item)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
|
||||
for item in ITEMS_NON_CRAFTING:
|
||||
fields = {
|
||||
# items define typ and tech!
|
||||
'craftable': False,
|
||||
}
|
||||
fields.update(item)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
|
||||
for bluep in BLUEPRINTS:
|
||||
fields = {
|
||||
'typ': 11, # blueprint
|
||||
'tech': 0,
|
||||
'craftable': True,
|
||||
'icon': 'blueprint',
|
||||
}
|
||||
fields.update(bluep)
|
||||
data.append({'model': 'scon.item', 'fields': fields})
|
||||
|
||||
|
||||
build_pk_cache(data)
|
||||
# now to the crafting recipees:
|
||||
i = 1 # counter for crafting
|
||||
j = 1 # counter for input
|
||||
for craft in CRAFTING:
|
||||
try:
|
||||
item = craft['item']
|
||||
kwargs = None
|
||||
if isinstance(item, tuple) or isinstance(item, list):
|
||||
kwargs = item[1]
|
||||
item = item[0]
|
||||
crafting = {'model': 'scon.crafting',
|
||||
'pk': i,
|
||||
'fields': { 'output': lookup_pk(data, item, kwargs=kwargs ),
|
||||
'amount': craft.get('amount', 1) }}
|
||||
data.append(crafting)
|
||||
primary = True
|
||||
for amount, recipee in craft['recipee']:
|
||||
item = recipee
|
||||
kwargs = None
|
||||
if isinstance(item, tuple) or isinstance(item, list):
|
||||
print item
|
||||
kwargs = item[1]
|
||||
item = item[0]
|
||||
|
||||
crafting_input = {'model': 'scon.craftinginput',
|
||||
'pk': j,
|
||||
'fields': {'crafting': i,
|
||||
'item': lookup_pk(data, item, kwargs=kwargs),
|
||||
'amount': amount,
|
||||
'primary': primary}
|
||||
}
|
||||
primary = False
|
||||
j = j + 1
|
||||
data.append(crafting_input)
|
||||
i = i + 1
|
||||
except:
|
||||
raise
|
||||
|
||||
build_pk_cache(data)
|
||||
return data
|
||||
|
||||
if __name__ == "__main__":
|
||||
fixes = generate_fixtures()
|
||||
from pprint import pprint
|
||||
pprint( fixes )
|
||||
# check pks:
|
||||
for d in fixes:
|
||||
if d.get('pk', None) is None:
|
||||
print "%s is fail." % d
|
||||
|
||||
write_fixture(fixes)
|
10
src/scon/dj/scon/logic.py
Normal file
@ -0,0 +1,10 @@
|
||||
'''
|
||||
'''
|
||||
|
||||
def config(condict):
|
||||
# modify condict.
|
||||
return condict
|
||||
|
||||
def overview(condict):
|
||||
return condict
|
||||
|
BIN
src/scon/dj/scon/media/scon/conflict-logo.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/active_a1ma.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
src/scon/dj/scon/media/scon/icons/active_reverse_thruster.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.8 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_attack_drone.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_doomsday_missile.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_double_deflector_mk4.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_explosive_shells_mk4.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_focusing_lens.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_iridium_slugs.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_supercooled_charges.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/ammo_xenon_lamp_mk4.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/scon/dj/scon/media/scon/icons/blueprint.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_computing_chip.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_metal_blank.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_osmium_crystals.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_processing_block.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_pure_silicon.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_screened_battery.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/component_tungsten_plate.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 5.1 KiB |
BIN
src/scon/dj/scon/media/scon/icons/duplicator.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
src/scon/dj/scon/media/scon/icons/resource_crystal_shard.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/scon/dj/scon/media/scon/icons/resource_osmium_ore.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/scon/dj/scon/media/scon/icons/resource_silicon_ore.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/scon/dj/scon/media/scon/icons/resource_tungsten_ore.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/scon/dj/scon/media/scon/icons/resource_vanadium.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/scon/dj/scon/media/scon/icons/weapon_assault_rail_mk5.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/scon/dj/scon/media/scon/icons/weapon_beam_cannon_mk5.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
src/scon/dj/scon/media/scon/icons/weapon_plasma_gun_mk5.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
148
src/scon/dj/scon/models.py
Normal file
@ -0,0 +1,148 @@
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
ITEM_TYPES = (
|
||||
(0, 'Misc'),
|
||||
(1, 'Engine'),
|
||||
(2, 'Capacitor'),
|
||||
(3, 'Shield'),
|
||||
(4, 'Hull'),
|
||||
(5, 'Cpu'),
|
||||
(6, 'Active'),
|
||||
(7, 'Weapon'),
|
||||
(8, 'Ammo'),
|
||||
(9, 'Missile'),
|
||||
(10, 'Class'),
|
||||
(11, 'Blueprint'),
|
||||
(12, 'Resource'),
|
||||
(13, 'Component'),
|
||||
)
|
||||
|
||||
QUALITY_TYPES = (
|
||||
(0, '-'),
|
||||
(1, 'Mk1'),
|
||||
(2, 'Mk2'),
|
||||
(3, 'Mk3'),
|
||||
(4, 'Mk4'),
|
||||
(5, 'Mk5'),
|
||||
(10, 'Universal'),
|
||||
(14, 'Pirate Mk4'),
|
||||
(99, 'Alien'),
|
||||
)
|
||||
D_QUALITY = dict(QUALITY_TYPES)
|
||||
|
||||
TECH_TYPES = (
|
||||
(0, ''),
|
||||
(1, 'I'),
|
||||
(2, 'II'),
|
||||
(3, 'III'),
|
||||
(4, 'IV'),
|
||||
(5, 'V'),
|
||||
)
|
||||
|
||||
ROLE_TYPES = (
|
||||
(-1, ''),
|
||||
(0, 'Multipurpose'),
|
||||
(1, 'Recon'),
|
||||
(2, 'ECM'),
|
||||
(3, 'Covert Ops'),
|
||||
(4, 'Tackler'),
|
||||
(5, 'Command'),
|
||||
(6, 'Gunship'),
|
||||
(7, 'Engineer'),
|
||||
(8, 'Guard'),
|
||||
(9, 'Longrange'),
|
||||
(10, 'Interceptors'),
|
||||
(20, 'Fighters'),
|
||||
(30, 'Frigates'),
|
||||
)
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
tech = models.IntegerField(default=0, blank=True)
|
||||
quality = models.IntegerField(default=0, blank=True, choices=QUALITY_TYPES)
|
||||
icon = models.CharField(max_length=128, blank=True, null=True)
|
||||
typ = models.IntegerField(default=0, choices=ITEM_TYPES)
|
||||
craftable = models.BooleanField(default=False, blank=True)
|
||||
sell_price = models.IntegerField(default=0, blank=True)
|
||||
buy_price = models.IntegerField(default=0, blank=True)
|
||||
buy_price_premium = models.IntegerField(default=0, blank=True)
|
||||
|
||||
role = models.IntegerField(default=-1, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.icon is None or self.icon == '':
|
||||
if self.typ>0:
|
||||
item_types = dict(ITEM_TYPES)
|
||||
try:
|
||||
s = item_types[self.typ]
|
||||
s = s.lower()
|
||||
except:
|
||||
s = 'unknown'
|
||||
self.icon = '%s_t%s_%s' % (s, self.tech, slugify(self.name))
|
||||
else:
|
||||
self.icon = 't%s_%s' % (self.tech, slugify(self.name))
|
||||
return super(Item, self).save(*args, **kwargs)
|
||||
|
||||
def primary_recipee(self):
|
||||
f=CraftingInput.objects.filter(primary=True, item=self)
|
||||
if len(f) == 1:
|
||||
return f[0].crafting
|
||||
|
||||
def crafting_used_in(self):
|
||||
return CraftingInput.objects.filter(item=self)
|
||||
|
||||
def parents(self):
|
||||
my_recipee = Crafting.objects.filter(output=self)
|
||||
if my_recipee.count():
|
||||
ci = CraftingInput.objects.filter(crafting=my_recipee).filter(primary=True)
|
||||
if ci.count():
|
||||
# ci.item is my parent
|
||||
ret = [ci[0].item]
|
||||
ret.extend(ci[0].item.parents())
|
||||
return ret
|
||||
return []
|
||||
|
||||
def get_full_name(self):
|
||||
if self.quality:
|
||||
return '%s (%s)' % (self.name, D_QUALITY.get(self.quality, ''))
|
||||
return '%s' % (self.name,)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.get_full_name()
|
||||
|
||||
def html(self):
|
||||
# returns a html coded span with the items name.
|
||||
classes = []
|
||||
if self.quality:
|
||||
classes.append('quality-%s' % self.quality)
|
||||
ret = '<span'
|
||||
if classes:
|
||||
ret += ' class="%s"' % ' '.join(classes)
|
||||
ret += '/>%s</span>' % self.name
|
||||
return mark_safe(ret)
|
||||
|
||||
|
||||
class Crafting(models.Model):
|
||||
output = models.ForeignKey(Item, related_name='crafting')
|
||||
amount = models.IntegerField(default=1, blank=True)
|
||||
input = models.ManyToManyField(Item, related_name='recipees', through='CraftingInput')
|
||||
|
||||
def ingredients(self):
|
||||
return CraftingInput.objects.filter(crafting=self)
|
||||
|
||||
def __unicode__(self):
|
||||
return 'Recipee for %s' % (self.output.name,)
|
||||
|
||||
class CraftingInput(models.Model):
|
||||
crafting = models.ForeignKey(Crafting) # the items you need.
|
||||
item = models.ForeignKey(Item) # the item you get.
|
||||
amount = models.IntegerField(default=1)
|
||||
primary = models.BooleanField(default=False, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return 'Part of Recipee for %s (x%s): %s (x%s)' % (self.crafting.output, self.crafting.amount, self.item, self.amount)
|
||||
|
1
src/scon/dj/scon/templates/404.html
Normal file
@ -0,0 +1 @@
|
||||
Site Not Found.
|
1
src/scon/dj/scon/templates/500.html
Normal file
@ -0,0 +1 @@
|
||||
Internal Server Error.
|
13
src/scon/dj/scon/templates/base.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="author" content="g4b.org">
|
||||
<title>{{ title }}</title>
|
||||
{% block extrahead %}{% endblock extrahead%}
|
||||
{% block css %}{% endblock css %}
|
||||
{% block js %}{% endblock js %}
|
||||
</head>
|
||||
<body>{% block context %}
|
||||
{% endblock context %}</body>
|
||||
</html>
|
144
src/scon/dj/scon/templates/scon/base.html
Normal file
@ -0,0 +1,144 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
<!--
|
||||
@media print {
|
||||
.nobreak { page-break-inside: ''; }
|
||||
tr { page-break-inside: '';
|
||||
page-break-after: '';
|
||||
page-break-before: ''; }
|
||||
td { page-break-inside: '';
|
||||
page-break-after: '';
|
||||
page-break-before: ''; }
|
||||
ul { page-break-inside: avoid; }
|
||||
.item { page-break-inside: avoid;
|
||||
page-break-after: avoid; }
|
||||
.panel { page-break-inside: avoid;
|
||||
page-break-after: ''; }
|
||||
.item-sub { page-break-inside: avoid;
|
||||
page-break-before: ''; }
|
||||
.remarks { page-break-inside: avoid; }
|
||||
.arrowright { page-break-inside: avoid; }
|
||||
.breakable {
|
||||
page-break-inside: auto !important;
|
||||
page-break-before: auto !important;
|
||||
page-break-after: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: #2d2d2d;
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
.panel-light {
|
||||
background-color: #3c3c3c;
|
||||
color: #afafaf;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size:0.8em;
|
||||
margin-top:-0.8em;
|
||||
padding: 2px;
|
||||
padding-left: 54px;
|
||||
position: relative;
|
||||
height: 56px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.item-sub {
|
||||
margin-top: -17px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.item img {
|
||||
left: 3px;
|
||||
position: absolute;
|
||||
border: 1px solid #3c3c3c;
|
||||
}
|
||||
|
||||
.remarks {
|
||||
background-color: #444444;
|
||||
color: #afafaf;
|
||||
font-size: 0.6em;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
<!-- Copyright notice: -->
|
||||
.arrowright {
|
||||
background: #2C2C2C;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-left: -0.7em;
|
||||
color: #ffffff;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.arrowright::before {
|
||||
bottom: -0.666em;
|
||||
left: 0.8em;
|
||||
position: absolute;
|
||||
border-left: 1.2em solid #2C2C2C;
|
||||
border-top: 1.2em solid rgba(44, 44, 44, 0);
|
||||
border-bottom: 1.2em solid rgba(44, 44, 44, 0);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.quality-1 {
|
||||
color: #e3e3e3;
|
||||
}
|
||||
.quality-2 {
|
||||
color: #42d100;
|
||||
}
|
||||
.quality-3 {
|
||||
color: #a9b8df;
|
||||
}
|
||||
.quality-4 {
|
||||
color: #975aa0;
|
||||
}
|
||||
.quality-5 {
|
||||
color: #d76d39;
|
||||
}
|
||||
.quality-14 {
|
||||
color: #975aa0;
|
||||
}
|
||||
.quality-15 {
|
||||
color: #d76d39;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
.col1 {
|
||||
width: 240px;
|
||||
|
||||
}
|
||||
|
||||
.col2 {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.col3 {
|
||||
width: 240px;
|
||||
|
||||
}
|
||||
|
||||
.col4 {
|
||||
width: 240px;
|
||||
|
||||
}
|
||||
|
||||
.col {
|
||||
display: inline-table;
|
||||
}
|
||||
|
||||
-->
|
||||
</style>
|
||||
{% endblock css %}
|
11
src/scon/dj/scon/templates/scon/config.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "scon/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block context %}
|
||||
{% blocktrans %}{% endblocktrans %}
|
||||
<form class="form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
{% endblock context %}
|
581
src/scon/dj/scon/templates/scon/crafting/bbcode.txt
Normal file
@ -0,0 +1,581 @@
|
||||
[quote name="FunkyDonut" post="271113" timestamp="1402589748"]
|
||||
This guide will only focus on the crafting system in the Invasion game mode. Please use the [url=http://forum.star-conflict.com/index.php?/topic/23371-invasion-introductory-guide/?p=269862][color=#0000cd]introductory guide[/color][/url], if you have general questions on the game mode itself, rather than the crafting system.
|
||||
[center] [/center]
|
||||
[center] [/center]
|
||||
[center][color=#008000][b]Special thanks go to [/b][/color][b][url=http://forum.star-conflict.com/index.php?/user/239042-g4borg/][color=#0000cd]g4borg[/color][/url][/b][color=#008000][b], who mostly provided the information![/b][/color][/center]
|
||||
|
||||
In the Invasion game mode, you will be able to find trophies floating around in space. Those trophies can be simply credits, artefacts or debris, which is sold autmatically once you used a drone/returned to the station. But you may also find ressources - mostly close to asteroids, but sometimes also in cargo transports, shipwrecks or stolen containers from other pilots, which you shot down. These ressources can be used to craft to usefull items, such as new and powerful modules, new types of ammunition or the valuable duplicators, which allow you to reconstruct your ship, if you have been destroyed once. Below, you will see a listing
|
||||
|
||||
[color=#ff0000][u][b]Concerning updates:[/b][/u][/color] Please write me a private message, if you have something to add, or if there is anything wrong. I will edit/update this guide as soon as possible.
|
||||
[indent=1] [/indent]
|
||||
[size=6][b]Ressources[/b][/size]
|
||||
[table][tr][th][b]Ressources[/b][/th][th][b][color=#ff8c00]Crafting result[/color]/[color=#8b4513]necessary ressources[/color][/b][/th][th][b]Used for...[/b][/th][/tr][tr]
|
||||
[td][b]Tungsten Ore[/b]
|
||||
[/td][td][b][color=#ff8c00]Tungsten Plate[/color]
|
||||
[color=#8b4513]Tungsten ore x 2[/color][/b]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Screened battery (Tungsten plate)[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V (Tungsten plate)[/*]
|
||||
[*]Pirate Engine Overcharge V (Tungsten plate)[/*]
|
||||
[*]Reverse Thruster V (Tungsten plate)[/*]
|
||||
[*]Alien Plasma Gun IV (Tungsten plate)[/*]
|
||||
[*]Alien Plasma Gun V (Tungsten plate)[/*]
|
||||
[*]Alien Assault Railgun IV (Tungsten plate)[/*]
|
||||
[*]Alien Assault Railgun V (Tungsten plate)[/*]
|
||||
[*]Alien Beam Cannon IV (Tungsten plate)[/*]
|
||||
[*]Alien Beam Cannon V (Tungsten plate)[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr][td][b]Osmium ore[/b]
|
||||
[/td][td]
|
||||
[b][color=#ff8c00]Osmium crystals[/color]
|
||||
[color=#8b4513]Osmium ore x 1[/color][/b]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Double Deflector (Osmium crystals)[/*]
|
||||
[*]Focusing Lens (Osmium crystals)[/*]
|
||||
[*]Pirate Engine Overcharge V (Osmium crystals)[/*]
|
||||
[*]Doomsday Missile (Osmium crystals)[/*]
|
||||
[/LIST]
|
||||
[indent=1][/td][/tr][tr][td][b]Silicon ore[/b]
|
||||
[/td][td]
|
||||
[b][color=#ff8c00]Pure Silicon[/color]
|
||||
[color=#8b4513]Silicon ore x 1[/color][/b][/indent]
|
||||
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Processing block (Pure Silicon)[/*]
|
||||
[*]Target Tracking Coprocessor III (Pure Silicon)[/*]
|
||||
[*]Explosive Shells (Pure Silicon)[/*]
|
||||
[*]Xenon Lamp (Pure Silicon)[/*]
|
||||
[/LIST]
|
||||
[indent=1][/td][/tr][tr][td][b]Vanadium[/b]
|
||||
[/td][td]
|
||||
[b][color=#ff8c00]Metal blank[/color]
|
||||
[color=#8b4513]Vanadium x 2[/color][/b][/indent]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Duplicator (Metal blank)[/*]
|
||||
[*]Target Tracking Coprocessor III (Metal blank)[/*]
|
||||
[*]Explosive Shells (Metal blank)[/*]
|
||||
[*]Iridium Slugs (Metal blank)[/*]
|
||||
[*]A1MA IV (Metal blank)[/*]
|
||||
[*]Pirate Mass Shield Generator V (Metal blank)[/*]
|
||||
[*]Reverse Thruster III (Metal blank)[/*]
|
||||
[*]Reverse Thruster IV (Metal blank)[/*]
|
||||
[*]Alien Plasma Gun III (Metal blank)[/*]
|
||||
[*]Alien Assault Railgun III (Metal blank)[/*]
|
||||
[*]Alien Beam Cannon III (Metal blank)[/*]
|
||||
[*]Doomsday Missile (Metal blank)[/td][/*]
|
||||
[/LIST]
|
||||
[/tr][tr][td][b]Crystal shard[/b]
|
||||
[/td]
|
||||
[td]
|
||||
[b][color=#ff8c00]Computing chip[/color][/b]
|
||||
[color=#8b4513][b]Crystal shard x 1[/b][/color]
|
||||
|
||||
[/td]
|
||||
[td]
|
||||
[LIST]
|
||||
[*]Duplicator (Computing chip)[/*]
|
||||
[*]Screened battery (Computing chip)[/*]
|
||||
[*]Processing block (Computing chip)[/*]
|
||||
[*]Attack Drone (Computing chip)[/*]
|
||||
[*]Xenon Lamp (Computing chip)[/*]
|
||||
[*]Supercooled Charges (Computing chip)[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V (Computing chip)[/*]
|
||||
[*]Pirate Mass Shield Generator V (Computing chip)[/*]
|
||||
[*]Reverse Thruster III (Computing chip)[/*]
|
||||
[*]Reverse Thruster IV (Computing chip)[/*]
|
||||
[*]Reverse Thruster V (Computing chip)[/*]
|
||||
[*]Doomsday Missile (Computing chip)[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr][td][b]Osmium crystals[/b]
|
||||
[/td]
|
||||
[td]
|
||||
[b][color=#ff8c00]Double Deflector (Mk4)[/color][/b]
|
||||
[color=#8b4513][b]Osmium crystals x 1[/b][/color][/td]
|
||||
|
||||
[td]
|
||||
[LIST]
|
||||
[*]Focusing Lens[/*]
|
||||
[*]Pirate Engine Overcharge V[/*]
|
||||
[*]Doomsday Missile[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr][td][b]Alien Monocrystal[/b]
|
||||
[/td][td]
|
||||
[b][color=#ff8c00]Attack Drone (Universal)[/color][/b]
|
||||
[color=#8b4513][b]Alien Monocrystal x 1[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Computing chip x 1[/b][/color][/td][td]
|
||||
[LIST]
|
||||
[*]A1MA IV[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V[/*]
|
||||
[*]Pirate Engine Overcharge V[/*]
|
||||
[*]Pirate Mass Shield Generator V[/*]
|
||||
[*]Reverse Thruster III[/*]
|
||||
[*]Reverse Thruster IV[/*]
|
||||
[*]Reverse Thruster V[/*]
|
||||
[*]Alien Plasma Gun III[/*]
|
||||
[*]Alien Plasma Gun IV[/*]
|
||||
[*]Alien Plasma Gun V[/*]
|
||||
[*]Alien Assault Railgun III[/*]
|
||||
[*]Alien Assault Railgun IV[/*]
|
||||
[*]Alien Assault Railgun V[/*]
|
||||
[*]Alien Beam Cannon III[/*]
|
||||
[*]Alien Beam Cannon IV[/*]
|
||||
[*]Alien Beam Cannon V[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr]
|
||||
|
||||
[td][b]Tungsten plate[/b][/td][td]
|
||||
[b][color=#ff8c00]Screened battery[/color][/b]
|
||||
[color=#8b4513][b]Tungsten plate x 1[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Computing chip x 2[/b][/color][/td]
|
||||
[td]
|
||||
[LIST]
|
||||
[*]Target Tracking Coprocessor III (Screened battery)[/*]
|
||||
[*]A1MA IV (Screened battery)[/*]
|
||||
[*]Reverse Thruster III (Screened battery)[/*]
|
||||
[*]Reverse Thruster IV (Screened battery)[/*]
|
||||
[*]Reverse Thruster V (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun III (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun IV (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun V (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun III (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun IV (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun V (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon III (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon IV (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon V (Screened battery)[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V[/*]
|
||||
[*]Pirate Engine Overcharge V[/*]
|
||||
[*]Reverse Thruster V[/*]
|
||||
[*]Alien Plasma Gun IV[/*]
|
||||
[*]Alien Plasma Gun V[/*]
|
||||
[*]Alien Assault Railgun IV[/*]
|
||||
[*]Alien Assault Railgun V[/*]
|
||||
[*]Alien Beam Cannon IV[/*]
|
||||
[*]Alien Beam Cannon V[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr]
|
||||
[td][b]Screened battery[/b][/td][td]
|
||||
[b][color=#ff8c00]Target Tracking Coprocessor III[/color][/b]
|
||||
[color=#8b4513][b]Screened battery x 1[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Metal blank x 7[/b][/color]
|
||||
[color=#8b4513][b]Pure Silicon x 5[/b][/color]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]A1MA IV[/*]
|
||||
[*]Reverse Thruster III[/*]
|
||||
[*]Reverse Thruster IV[/*]
|
||||
[*]Reverse Thruster V[/*]
|
||||
[*]Alien Plasma Gun III[/*]
|
||||
[*]Alien Plasma Gun IV[/*]
|
||||
[*]Alien Plasma Gun V[/*]
|
||||
[*]Alien Assault Railgun III[/*]
|
||||
[*]Alien Assault Railgun IV[/*]
|
||||
[*]Alien Assault Railgun V[/*]
|
||||
[*]Alien Beam Cannon III[/*]
|
||||
[*]Alien Beam Cannon IV[/*]
|
||||
[*]Alien Beam Cannon V[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr][td][b]Pure Silicon[/b][/td]
|
||||
[td]
|
||||
[b][color=#ff8c00]Processing block[/color][/b]
|
||||
[color=#8b4513][b]Pure Silicon x 4[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Computing chip x 2[/b][/color]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Duplicator (Processing block)[/*]
|
||||
[*]A1MA IV (Processing block)[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V (Processing block)[/*]
|
||||
[*]Pirate Engine Overcharge V (Processing block)[/*]
|
||||
[*]Pirate Mass Shield Generator V (Processing block)[/*]
|
||||
[*]Target Tracking Coprocessor III[/*]
|
||||
[*]Explosive Shells[/*]
|
||||
[*]Xenon Lamp[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr]
|
||||
[td][b]Metal blank[/b][/td]
|
||||
[td]
|
||||
[b][color=#ff8c00]Explosive Shells (Mk4)[/color][/b]
|
||||
[color=#8b4513][b]Metal blank x 1[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Pure Silicon x 2[/b][/color]
|
||||
[/td][td]
|
||||
[LIST]
|
||||
[*]Duplicator[/*]
|
||||
[*]Target Tracking Coprocessor III[/*]
|
||||
[*]Explosive Shells[/*]
|
||||
[*]Iridium Slugs[/*]
|
||||
[*]A1MA IV[/*]
|
||||
[*]Pirate Mass Shield Generator V[/*]
|
||||
[*]Reverse Thruster III[/*]
|
||||
[*]Reverse Thruster IV[/*]
|
||||
[*]Alien Plasma Gun III[/*]
|
||||
[*]Alien Assault Railgun III[/*]
|
||||
[*]Alien Beam Cannon III[/*]
|
||||
[*]Doomsday Missile[/*]
|
||||
[/LIST]
|
||||
[/td][/tr][tr]
|
||||
[td][b]Computing chip[/b][/td][td]
|
||||
[b][color=#ff8c00]Xenon Lamp (Mk4)[/color][/b]
|
||||
[color=#8b4513][b]Computing chip x 1[/b][/color]
|
||||
|
||||
[color=#8b4513][b]Pure Silicon x 1[/b][/color] [/td][td]
|
||||
[LIST]
|
||||
[*]Duplicator[/*]
|
||||
[*]Screened battery[/*]
|
||||
[*]Target Tracking Coprocessor III (Screened battery)[/*]
|
||||
[*]A1MA IV (Screened battery)[/*]
|
||||
[*]Reverse Thruster III (Screened battery)[/*]
|
||||
[*]Reverse Thruster IV (Screened battery)[/*]
|
||||
[*]Reverse Thruster V (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun III (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun IV (Screened battery)[/*]
|
||||
[*]Alien Plasma Gun V (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun III (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun IV (Screened battery)[/*]
|
||||
[*]Alien Assault Railgun V (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon III (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon IV (Screened battery)[/*]
|
||||
[*]Alien Beam Cannon V (Screened battery)[/*]
|
||||
[*]Processing block[/*]
|
||||
[*]Duplicator (Processing block)[/*]
|
||||
[*]A1MA IV (Processing block)[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V (Processing block)[/*]
|
||||
[*]Pirate Engine Overcharge V (Processing block)[/*]
|
||||
[*]Pirate Mass Shield Generator V (Processing block)[/*]
|
||||
[*]Attack Drone[/*]
|
||||
[*]Xenon Lamp[/*]
|
||||
[*]Supercooled Charges[/*]
|
||||
[*]Pirate "Orion" Targeting Complex V[/*]
|
||||
[*]Pirate Mass Shield Generator V[/*]
|
||||
[*]Reverse Thruster III[/*]
|
||||
[*]Reverse Thruster IV[/*]
|
||||
[*]Reverse Thruster V[/*]
|
||||
[*]Doomsday Missile[/*]
|
||||
[/LIST]
|
||||
[/td][/tr]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[/table]
|
||||
|
||||
|
||||
[size=6][b]Known/Available Blueprints[/b][/size]
|
||||
|
||||
[indent=1][size=6][b][size=4][size=5][color=#008000]Important: [/color][/size][/size][/b][size=4][size=5]The ammunition-blueprints produce consumable items, which only last for one battle each! The same goes for duplicators and Doomsday missiles![/size][/size][/size][/indent]
|
||||
|
||||
[table][tr][th][b]Blueprints[/b][/th][th][color=#8b4513][b]Crafting parts[/b][/color][/th][th][b]Result[/b][/th][/tr][tr]
|
||||
[td][b]Focusing Lens[/b][/td][td]
|
||||
|
||||
[LIST]
|
||||
[*][b][color=#8b4513]Focusing Lens Blueprint x 1[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Osmium crystals x 1[/color][/b][/*]
|
||||
[/LIST]
|
||||
[/td][td]Focusing Lens Mk.4[/td][/tr][tr]
|
||||
[td][b]Iridium Slugs[/b][/td][td]
|
||||
|
||||
[LIST]
|
||||
[*][b][color=#8b4513]Iridium Slugs Blueprint x 1[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Metal blank x 1[/color][/b][/*]
|
||||
[/LIST]
|
||||
[/td][td]Iridium Slugs Mk.4[/td][/tr][tr]
|
||||
[td][b]Supercooled Charges[/b][/td][td]
|
||||
|
||||
[LIST]
|
||||
[*][b][color=#8b4513]Supercooled Charges Blueprint x 1[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Computing chip x 1[/color][/b][/*]
|
||||
[/LIST]
|
||||
[/td][td]Supercooled Charges Mk.4[/td][/tr][tr]
|
||||
[td][b]A1MA T4[/b][/td][td]
|
||||
|
||||
[LIST]
|
||||
[*][b][color=#8b4513]A1MA T4 Blueprint x 1[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Processing block x 2[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Metal blank x 14[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Screened battery x 2[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Alien Monocrystal x 20[/color][/b][/*]
|
||||
[/LIST]
|
||||
[/td][td]
|
||||
A1MA IV[/td][/tr][tr]
|
||||
[td][b]Orion-2 Targeting Complex[/b][/td][td]
|
||||
Pirate "Orion" Targeting Complex V (Pirate Mk4)
|
||||
|
||||
[LIST]
|
||||
[*][b][color=#8b4513]Orion-2 Targeting Complex Blueprint x 1[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Tungsten plate x 3[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Computing chip x 4[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Processing block x 2[/color][/b][/*]
|
||||
[*][b][color=#8b4513]Alien Monocrystal x 30[/color][/b][/*]
|
||||
[/LIST]
|
||||
[/td][td]Pirate "Orion" Targeting Complex V[/td][/tr][tr]
|
||||
[td][b]Engine Warp Overcharge[/b][/td][td]
|
||||
Pirate Engine Overcharge V (Pirate Mk4)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Engine Warp Overcharge Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Osmium crystals x 2[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Processing block x 2[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Pirate Engine Overcharge V[/td][/tr][tr]
|
||||
[td][b]Mass Shield Energizer[/b][/td][td]
|
||||
Pirate Mass Shield Generator V (Pirate Mk4)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Mass Shield Energizer Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 10[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Processing block x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Pirate Mass Shield Generator V[/td][/tr][tr]
|
||||
[td][b]Reverse Thruster T3[/b][/td][td]
|
||||
Reverse Thruster III (Mk1)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Reverse Thruster T3 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 7[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 4[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 15[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Reverse Thruster III[/td][/tr][tr]
|
||||
[td][b]Reverse Thruster T4[/b][/td][td]
|
||||
Reverse Thruster IV (Mk1)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Reverse Thruster T4 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 12[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 2[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 5[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 20[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Reverse Thruster IV[/td][/tr][tr][td][b]Reverse Thruster T5[/b][/td]
|
||||
[td]
|
||||
Reverse Thruster V (Mk1)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Reverse Thruster T5 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 7[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 6[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Reverse Thruster V[/td][/tr][tr]
|
||||
[td][b]Beam Cannon Prototype T3[/b][/td][td]
|
||||
Alien Beam Cannon III (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Beam Cannon Prototype T3 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Beam Cannon III x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 6[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
[/td][td]Alien Beam Cannon III[/td][/tr][tr]
|
||||
[td][b]Beam Cannon Prototype T4[/b][/td][td]
|
||||
Alien Beam Cannon IV (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Beam Cannon Prototype T4 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Beam Cannon IV x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 4[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 50[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Beam Cannon IV[/td][/tr][tr]
|
||||
[td][b]Beam Cannon Prototype T5[/b][/td][td]
|
||||
Alien Beam Cannon V (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Beam Cannon Prototype T5 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Beam Cannon V x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 5[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 70[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Beam Cannon V[/td][/tr][tr]
|
||||
[td][b]Assault Railgun Prototype T3[/b][/td][td]
|
||||
Alien Assault Railgun III (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Assault Railgun Prototype T3 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Assault Railgun III x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 6[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]
|
||||
Alien Assault Railgun III
|
||||
[/td][/tr][tr]
|
||||
[td][b]Assault Railgun Prototype T4[/b][/td][td]
|
||||
Alien Assault Railgun IV (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Assault Railgun Prototype T4 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Assault Railgun IV x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 4[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 50[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Assault Railgun IV[/td][/tr][tr]
|
||||
[td][b]Assault Railgun Prototype T5[/b][/td][td]
|
||||
Alien Assault Railgun V (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Assault Railgun Prototype T5 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Assault Railgun V x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 5[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 70[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Assault Railgun V[/td][/tr][tr]
|
||||
[td][b]Plasma Gun Prototype T3[/b][/td][td]
|
||||
Alien Plasma Gun III (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Plasma Gun Prototype T3 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Plasma Gun III x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 6[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 30[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Plasma Gun III[/td][/tr][tr]
|
||||
[td][b]Plasma Gun Prototype T4[/b][/td][td]
|
||||
Alien Plasma Gun IV (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Plasma Gun Prototype T4 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Plasma Gun IV x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 4[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 50[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Plasma Gun IV[/td][/tr][tr]
|
||||
[td][b]Plasma Gun Prototype T5[/b][/td][td]
|
||||
Alien Plasma Gun V (Mk5)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Plasma Gun Prototype T5 Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Plasma Gun V x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Tungsten plate x 3[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Screened battery x 5[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Alien Monocrystal x 70[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Alien Plasma Gun V[/td][/tr][tr]
|
||||
[td][b]Doomsday Missile[/b][/td][td]
|
||||
Doomsday Missile (Mk1)
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Doomsday Missile Blueprint x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Osmium crystals x 2[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 1[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
|
||||
[/td][td]Doomsday Missile[/td][/tr][tr]
|
||||
[td][b]Duplicator[/b][/td][td]
|
||||
Duplicator
|
||||
|
||||
[LIST]
|
||||
[*][color=#8b4513][b]Processing block x 1[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Computing chip x 2[/b][/color][/*]
|
||||
[*][color=#8b4513][b]Metal blank x 2[/b][/color][/*]
|
||||
[/LIST]
|
||||
|
||||
[/td][td]Duplicator[/td][/tr]
|
||||
[/table]
|
||||
[/quote]
|
||||
|
||||
|
||||
|
||||
|
||||
[b][font=arial, sans-serif][size=14]The Color Code[/size][/font][/b]
|
||||
[b][color=#595959][font=arial, sans-serif][size=12]Gray [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials of this color will be raw resources. [/size][/font]
|
||||
[b][color=#00b050][font=arial, sans-serif][size=12]Green [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials here have been crafted once since being a raw resource.[/size][/font]
|
||||
[b][color=#1f497d][font=arial, sans-serif][size=12]Blue [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials here have been at most crafted twice since being a raw resource.[/size][/font]
|
||||
[b][color=#7030a0][font=arial, sans-serif][size=12]Purple [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials here have been at most crafted three times since being a raw resource.[/size][/font]
|
||||
[b][color=#e46c0a][font=arial, sans-serif][size=12]Orange [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials here have been at most crafted four times since being a raw resource.[/size][/font]
|
||||
[b][color=#ff3399][font=arial, sans-serif][size=12]Pink [/size][/font][/color][/b][font=arial, sans-serif][size=12]– Materials here have been at most crafted five times since being a raw resource (I doubt this is even possible but just in case).[/size][/font]
|
||||
|
||||
|
||||
[table]
|
||||
[tr][th][b][font=arial, sans-serif][size=12]Crafting Result [/size][/font][/b]
|
||||
[b][font=arial, sans-serif][size=12](the components you want)[/size][/font][/b][/th][th][b][font=arial, sans-serif][size=12]Prerequisite Materials [/size][/font][/b]
|
||||
[b][font=arial, sans-serif][size=12](what you need to get the result)[/size][/font][/b][/th][/tr]
|
||||
[tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Crystal Shard[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A[/size][/font][/td]
|
||||
[/tr]
|
||||
[tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Osmium Ore[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A[/size][/font][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Silicon Ore[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A[/size][/font][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Tungsten Ore[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A[/size][/font][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Vanadium (Ore)[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A[/size][/font][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#595959][font=arial, sans-serif][size=12]Alien Monocrystal[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]N/A [/size][/font][font=arial, sans-serif](Although this is a component you cannot craft it using raw resources.)[/size][/font][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#00b050][font=arial, sans-serif][size=12]Computing Chip[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]1x [/size][/font][b][color=#595959]Crystal Shard[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#00b050][font=arial, sans-serif][size=12]Metal Blank[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]2x [/size][/font][b][color=#595959]Vanadium (Ore)[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#00b050][font=arial, sans-serif][size=12]Osmium Crystals[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]1x [/size][/font][b][color=#595959]Osmium Ore[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#00b050][font=arial, sans-serif][size=12]Pure Silicon[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]1x [/size][/font][b][color=#595959]Silicon Ore[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#00b050][font=arial, sans-serif][size=12]Tungsten Plate[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]2x [/size][/font][b][color=#595959]Tungsten Ore[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#1f497d][font=arial, sans-serif][size=12]Processing Block[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]4x [/size][/font][b][color=#00b050]Pure Silicon[/color][/b]
|
||||
[font=arial, sans-serif]2x [/size][/font][b][color=#00b050]Computing Chip[/color][/b][/td]
|
||||
[/tr][tr]
|
||||
[td][b][color=#1f497d][font=arial, sans-serif][size=12]Screened Battery[/size][/font][/color][/b][/td]
|
||||
[td][font=arial, sans-serif]1x [/size][/font][b][color=#00b050]Tungsten Plate[/color][/b]
|
||||
[font=arial, sans-serif]2x [/size][/font][b][color=#00b050]Computing Chip[/color][/b][/td]
|
||||
[/tr]
|
||||
[/table]
|
||||
|
41
src/scon/dj/scon/templates/scon/crafting/forum.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% extends "scon/base.html" %}
|
||||
|
||||
{% block context %}
|
||||
<h1>Forum</h1>
|
||||
|
||||
|
||||
<code>
|
||||
[table][tr][th]Item[/th][th]Produces[/th][/tr]
|
||||
{% for item in items %}
|
||||
{% if item.primary_recipee %}
|
||||
[tr]
|
||||
{% with item.primary_recipee as recipee %}
|
||||
|
||||
[td]
|
||||
{{ item.name }}
|
||||
[/td]
|
||||
|
||||
|
||||
|
||||
|
||||
[td]
|
||||
{{ recipee.output.name }}
|
||||
[LIST]
|
||||
{% for ingredient in recipee.ingredients %}
|
||||
{% if ingredient.item != item %}
|
||||
[*]{{ ingredient.amount }} x {{ ingredient.item.name }}[/*]
|
||||
{% else %}
|
||||
{% if ingredient.amount > 1 %}
|
||||
[*][b]{{ ingredient.amount }} x [/b]{{ ingredient.item.name }}[/*]
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
[/LIST]
|
||||
[/td]
|
||||
{% endwith %}
|
||||
[/tr]
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
[/table]
|
||||
</code>
|
||||
{% endblock context %}
|
133
src/scon/dj/scon/templates/scon/crafting/forum_efefay.html
Normal file
@ -0,0 +1,133 @@
|
||||
{% extends "scon/base.html" %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
|
||||
<style>
|
||||
<!--
|
||||
.parents-0 {
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.parents-1 {
|
||||
color: #00b050;
|
||||
}
|
||||
|
||||
.parents-2 {
|
||||
color: #1f497d;
|
||||
}
|
||||
|
||||
.parents-3 {
|
||||
color: #7030a0;
|
||||
}
|
||||
|
||||
.parents-4 {
|
||||
color: #e46c0a;
|
||||
}
|
||||
|
||||
.parents-5 {
|
||||
color: #ff3399;
|
||||
}
|
||||
-->
|
||||
</style>
|
||||
|
||||
{% endblock css %}
|
||||
|
||||
{% block context %}
|
||||
|
||||
<h1>Forum</h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>What ya wanna</th>
|
||||
<th>What ya needze</th>
|
||||
</tr>
|
||||
{% for ore in ores %}
|
||||
<tr>
|
||||
<td>{{ ore.name }}</td>
|
||||
<td>N/A{% if ore.typ == 13 %} (This is a component you cannot craft){% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for item in items %}
|
||||
|
||||
{% if item.primary_recipee %}
|
||||
<tr>
|
||||
{% with item.primary_recipee as recipee %}
|
||||
<td>
|
||||
{{ recipee.output.name }}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<ul>
|
||||
{% for ingredient in recipee.ingredients %}
|
||||
<li class="parents-{{ ingredient.item.parents|length }}">
|
||||
{% if ingredient.item != item %}
|
||||
{{ ingredient.amount }} x {{ ingredient.item.name }}
|
||||
{% else %}
|
||||
{% if ingredient.amount > 1 %}
|
||||
<b><i>{{ ingredient.amount }}</i> x {{ ingredient.item.name }}</b>
|
||||
{% else %}
|
||||
<b>{{ ingredient.amount }} x {{ ingredient.item.name }}</b>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<code>
|
||||
[table]
|
||||
[tr][th][b][font=arial, sans-serif][size=12]Crafting Result [/size][/font][/b]
|
||||
[b][font=arial, sans-serif][size=12](the components you want)[/size][/font][/b][/th][th][b][font=arial, sans-serif][size=12]Prerequisite Materials [/size][/font][/b]
|
||||
[b][font=arial, sans-serif][size=12](what you need to get the result)[/size][/font][/b][/th][/tr]
|
||||
{% for ore in ores %}
|
||||
[tr]
|
||||
[td][color=#595959]{{ ore.name }}[/color][/td]
|
||||
[td]N/A{% if ore.typ == 13 %} (This is a component you cannot craft){% endif %}[/td]
|
||||
[/tr]
|
||||
{% endfor %}
|
||||
{% for item in items %}
|
||||
{% if item.primary_recipee %}
|
||||
[tr]
|
||||
{% with item.primary_recipee as recipee %}
|
||||
[td]
|
||||
{% with recipee.output.parents|length as depth %}
|
||||
[color={% if depth == 0 %}#595959{% elif depth == 1 %}#00b050{% elif depth == 2 %}#1f497d{% else %}#7030a0{% endif %}]{{ recipee.output.name }}[/color]
|
||||
{% endwith %}
|
||||
[/td]
|
||||
|
||||
|
||||
[td]
|
||||
[LIST]
|
||||
{% for ingredient in recipee.ingredients %}
|
||||
[*]{% with ingredient.item.parents|length as depth %}
|
||||
[font=arial, sans-serif][size=12]
|
||||
{% if ingredient.item != item %}
|
||||
{{ ingredient.amount }} x [color={% if depth == 0 %}#595959{% elif depth == 1 %}#00b050{% elif depth == 2 %}#1f497d{% else %}#7030a0{% endif %}]{{ ingredient.item.name }}[/color]
|
||||
{% else %}
|
||||
{% if ingredient.amount > 1 %}
|
||||
[b][i]{{ ingredient.amount }}[/i] x [color={% if depth == 0 %}#595959{% elif depth == 1 %}#00b050{% elif depth == 2 %}#1f497d{% else %}#7030a0{% endif %}]{{ ingredient.item.name }}[/color][/b]
|
||||
{% else %}
|
||||
[b]{{ ingredient.amount }} x [color={% if depth == 0 %}#595959{% elif depth == 1 %}#00b050{% elif depth == 2 %}#1f497d{% else %}#7030a0{% endif %}]{{ ingredient.item.name }}[/color][/b]
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}[/size][/font][/*]
|
||||
{% endfor %}
|
||||
[/LIST]
|
||||
[/td]
|
||||
|
||||
{% endwith %}
|
||||
[/tr]
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
[/table]
|
||||
</code>
|
||||
{% endblock context %}
|
58
src/scon/dj/scon/templates/scon/crafting/overview.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends "scon/base.html" %}
|
||||
|
||||
{% block context %}
|
||||
<img src="{{MEDIA_URL}}scon/conflict-logo.png" style="position: absolute; float:left; top: -42px;">
|
||||
<h1 style="margin-left: 160px;">Crafting Overview</h1>
|
||||
|
||||
{% for item in items %}
|
||||
{% if item.primary_recipee %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col1 col">
|
||||
<div class="panel item">
|
||||
{% if item.icon %}<img src="{{ MEDIA_URL }}scon/icons/{{ item.icon }}.png">{% endif %} {{ item.name }}
|
||||
{% if item.sell_price %}<br><i>Sell: {{item.sell_price}} cr</i>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with item.primary_recipee as recipee %}
|
||||
<div class="col2 col">
|
||||
<div class="arrowright nobreak">{% if recipee.amount > 1 %}{{ recipee.amount }}{% endif %}</div>
|
||||
</div>
|
||||
|
||||
<div class="col3 col">
|
||||
<div class="nobreak">
|
||||
<div class="panel item nobreak">
|
||||
{% if recipee.output.icon %}<img src="{{ MEDIA_URL }}scon/icons/{{ recipee.output.icon }}.png">{% endif %} {{ recipee.output.html }}
|
||||
{% if recipee.output.sell_price %}<br><i>Sell: {{recipee.output.sell_price}} cr</i>{% endif %}
|
||||
</div>
|
||||
<div class="panel-light item-sub nobreak">
|
||||
<ul>
|
||||
{% for ingredient in recipee.ingredients %}
|
||||
<li>{{ ingredient.amount }} x {{ ingredient.item.html }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col4 col">
|
||||
<ul class="remarks">
|
||||
{% for i1 in item.crafting_used_in %}
|
||||
{% with i1.crafting.output as ci %}
|
||||
{% if ci.pk != recipee.output.pk %}
|
||||
<li>{{ ci.html }}</li>
|
||||
{% endif %}
|
||||
{% for i2 in ci.crafting_used_in %}
|
||||
<li><i>{{ i2.crafting.output.html }} ({{ci.html}})</i></li>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
<span class="breakable"> </span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock context %}
|
@ -0,0 +1,67 @@
|
||||
{% extends "scon/base.html" %}
|
||||
|
||||
{% block context %}
|
||||
<img src="{{MEDIA_URL}}scon/conflict-logo.png" style="position: absolute; float:left; top: -42px;">
|
||||
<h1 style="margin-left: 160px;">Crafting Overview</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<th style="width: 1em;"> </th>
|
||||
<th>Crafts into</th>
|
||||
<th>Also used in</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
{% if item.primary_recipee %}
|
||||
<tr class="nobreak">
|
||||
<td class="nobreak">
|
||||
<div class="panel item">
|
||||
{% if item.icon %}<img src="{{ MEDIA_URL }}scon/icons/{{ item.icon }}.png">{% endif %} {{ item.name }}
|
||||
{% if item.sell_price %}<br><i>Sell: {{item.sell_price}} cr</i>{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{% with item.primary_recipee as recipee %}
|
||||
<td class="nobreak">
|
||||
<div class="arrowright nobreak">{% if recipee.amount > 1 %}{{ recipee.amount }}{% endif %}</div>
|
||||
</td>
|
||||
|
||||
<td class="nobreak">
|
||||
<div class="nobreak">
|
||||
<div class="panel item nobreak">
|
||||
{% if recipee.output.icon %}<img src="{{ MEDIA_URL }}scon/icons/{{ recipee.output.icon }}.png">{% endif %} {{ recipee.output.html }}
|
||||
{% if recipee.output.sell_price %}<br><i>Sell: {{recipee.output.sell_price}} cr</i>{% endif %}
|
||||
</div>
|
||||
<div class="panel-light item-sub nobreak">
|
||||
<ul>
|
||||
{% for ingredient in recipee.ingredients %}
|
||||
<li>{{ ingredient.amount }} x {{ ingredient.item.html }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="nobreak">
|
||||
<ul class="remarks">
|
||||
{% for i1 in item.crafting_used_in %}
|
||||
{% with i1.crafting.output as ci %}
|
||||
{% if ci.pk != recipee.output.pk %}
|
||||
<li>{{ ci.html }}</li>
|
||||
{% endif %}
|
||||
{% for i2 in ci.crafting_used_in %}
|
||||
<li><i>{{ i2.crafting.output.html }} ({{ci.html}})</i></li>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
<tr class="breakable"><td class="breakable" colspan="4"><span class="breakable"> </span></td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock context %}
|
3
src/scon/dj/scon/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
32
src/scon/dj/scon/views.py
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponse
|
||||
from django.template import RequestContext, loader
|
||||
import logic
|
||||
import models
|
||||
|
||||
def config(request):
|
||||
t = loader.get_template('scon/config.html')
|
||||
c = RequestContext(request, logic.config({'title': 'Configure your Client'}))
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
def crafting(request):
|
||||
t = loader.get_template('scon/crafting/overview.html')
|
||||
items = models.Item.objects.filter(craftable=True)
|
||||
tree = None
|
||||
c = RequestContext(request, {'tree': tree,
|
||||
'items': items})
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
def crafting_forum(request):
|
||||
t = loader.get_template('scon/crafting/forum_efefay.html')
|
||||
items = models.Item.objects.filter(craftable=True)
|
||||
ores = []
|
||||
for item in items.filter(typ__in=[12, 13]):
|
||||
if len(item.parents()) == 0 and item.primary_recipee():
|
||||
ores.append(item)
|
||||
tree = None
|
||||
c = RequestContext(request, {'tree': tree,
|
||||
'ores': ores,
|
||||
'items': items})
|
||||
return HttpResponse(t.render(c))
|
99
src/scon/dj/settings.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""
|
||||
Django settings for dj project.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.6/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.6/ref/settings/
|
||||
"""
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
SERVE_INTERNAL = True
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'xp$g5ho)(=013v9#qb@sncz%ye7#oy34&1=ltj1315d)j+lwm)'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'dj.scon',
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.core.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.media",
|
||||
"django.core.context_processors.static",
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'dj.urls'
|
||||
|
||||
WSGI_APPLICATION = 'dj.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3.db'),
|
||||
}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.6/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.6/howto/static-files/
|
||||
STATIC_URL = '/static/'
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = r'D:\work\workspace\scon\src\scon\dj\scon\media'
|
||||
|
||||
#SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
#SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||
#SESSION_ENGINE = "django.contrib.sessions.backends.file"
|
||||
#SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
|
||||
|
||||
DEJAQT_DIRS = {
|
||||
STATIC_URL: '',
|
||||
}
|
22
src/scon/dj/urls.py
Normal file
@ -0,0 +1,22 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Examples:
|
||||
# url(r'^$', 'dj.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^crafting/forum/$', 'dj.scon.views.crafting_forum', name='scon_crafting_forum'),
|
||||
url(r'^crafting/$', 'dj.scon.views.crafting', name='scon_crafting'),
|
||||
|
||||
)
|
||||
|
||||
if settings.DEBUG or getattr(settings, 'SERVE_INTERNAL', False):
|
||||
urlpatterns += patterns('',
|
||||
url(r'^static/(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
|
||||
url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
|
||||
'document_root': settings.MEDIA_ROOT,
|
||||
}),
|
||||
)
|
14
src/scon/dj/wsgi.py
Normal file
@ -0,0 +1,14 @@
|
||||
"""
|
||||
WSGI config for dj project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
0
src/scon/game/__init__.py
Normal file
77
src/scon/game/battle.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
Represents a battle instance.
|
||||
|
||||
todo: finding battles. factory for missions, skirmishes?
|
||||
"""
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
def parse_details(self):
|
||||
# fast parse strategy: fill in all details about this battle.
|
||||
pass
|
||||
|
||||
def parse_statistics(self):
|
||||
# parse battle statistics.
|
||||
pass
|
||||
|
||||
class PvPBattle(Battle):
|
||||
pass
|
||||
|
||||
class PvPTDM(PvPBattle):
|
||||
pass
|
||||
|
||||
class PvPDomination(PvPBattle):
|
||||
pass
|
||||
|
||||
class PvPCombatRecon(PvPBattle):
|
||||
pass
|
||||
|
||||
class PvPCtB(PvPBattle):
|
||||
pass
|
||||
|
||||
class PvPDetonation(PvPBattle):
|
||||
pass
|
||||
|
||||
class PvPBeaconHunt(PvPBattle):
|
||||
pass
|
||||
|
||||
# Dreads
|
||||
class DreadnoughtBattle(Battle):
|
||||
pass
|
||||
|
||||
### PvE Stuff: low prio.
|
||||
class PvEBattle(Battle):
|
||||
pass
|
||||
|
||||
class PvERaidBattle(PvEBattle):
|
||||
pass
|
||||
|
||||
# Openspace time.
|
||||
class Openspace(Battle):
|
||||
pass
|
||||
|
||||
|
||||
###
|
||||
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 []
|
||||
|
||||
|
0
src/scon/game/mission.py
Normal file
100
src/scon/game/pieces.py
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
def res_to_red(res):
|
||||
''' calculates reduction % of damage from base resistance
|
||||
incoming damage is assumed to be 100.0 to get percentages.
|
||||
'''
|
||||
if res >= 0:
|
||||
fd = 100 / (1.0+res/100.0)
|
||||
else:
|
||||
fd = 100 / (1.0-res/100.0)
|
||||
return 100.0 - fd
|
||||
|
||||
def dam_res(dam, res):
|
||||
''' calculates damage modified by resistance.
|
||||
'''
|
||||
if res >= 0:
|
||||
fd = dam / (1.0+res/100.0)
|
||||
else:
|
||||
fd = dam / (1.0-res/100.0)
|
||||
return fd
|
||||
|
||||
class ShipInstance(object):
|
||||
# just testin something.
|
||||
def __init__(self,
|
||||
shields=None,
|
||||
hulls=None,
|
||||
shield_resis=None,
|
||||
hull_resis=None ):
|
||||
self.shield_max = shields or 5000
|
||||
self.hull_max = hulls or 5000
|
||||
shield_resis = shield_resis or (100,100,100)
|
||||
hull_resis = hull_resis or (100,100,100)
|
||||
self.set_shield_res(*shield_resis)
|
||||
self.set_hull_res(*hull_resis)
|
||||
|
||||
|
||||
def set_shield_res(self, kn, em, th):
|
||||
self.shield_res_kn = kn
|
||||
self.shield_res_em = em
|
||||
self.shield_res_th = th
|
||||
|
||||
def set_hull_res(self, kn, em, th):
|
||||
self.hull_res_kn = kn
|
||||
self.hull_res_em = em
|
||||
self.hull_res_th = th
|
||||
|
||||
def survivability(self):
|
||||
# i have no clue how they calc this.
|
||||
# multiple attempts shows, they are using base pts as measure, but how exactly the calc is?
|
||||
krs = (self.shield_max/100.0 * self.shield_res_kn)
|
||||
ers = (self.shield_max/100.0 * self.shield_res_em)
|
||||
trs = (self.shield_max/100.0 * self.shield_res_th)
|
||||
print "Shield.", krs, ers, trs
|
||||
|
||||
krh = (self.hull_max/100.0 * self.hull_res_kn)
|
||||
erh = (self.hull_max/100.0 * self.hull_res_em)
|
||||
trh = (self.hull_max/100.0 * self.hull_res_th)
|
||||
print "Hull.", krh, erh, trh
|
||||
|
||||
#print "?1", ((krs+ers+trs+krh+erh+trh)/6.0)+self.shield_max + self.hull_max
|
||||
print "?2", ((krs+ers+trs+3*self.shield_max)/3.0)+((krh+erh+trh+3*self.hull_max)/3.0)
|
||||
|
||||
|
||||
# another try:
|
||||
"""
|
||||
lets assume survivability is really measured through applying 1000 dps for 10 secs.
|
||||
|
||||
"""
|
||||
print "Assuming dps..."
|
||||
shield = self.shield_max
|
||||
hull = self.hull_max
|
||||
r1s = shield / (1.0*dam_res(1000, self.shield_res_kn))
|
||||
r2s = shield / (1.0*dam_res(1000, self.shield_res_em))
|
||||
r3s = shield / (1.0*dam_res(1000, self.shield_res_th))
|
||||
print r1s, r2s, r3s
|
||||
rXs = (r1s+r2s+r3s) / 3.0
|
||||
print "Shield survival time at 1kdps", rXs
|
||||
|
||||
r1h = hull / (1.0*dam_res(1000, self.hull_res_kn))
|
||||
r2h = hull / (1.0*dam_res(1000, self.hull_res_em))
|
||||
r3h = hull / (1.0*dam_res(1000, self.hull_res_th))
|
||||
print r1h, r2h, r3h
|
||||
rXh = (r1h+r2h+r3h) / 3.0
|
||||
print "Hull survival time at 1kdps", rXh
|
||||
|
||||
print "Total survival time ", rXs + rXh, " sec"
|
||||
print "Surv should be ", int(round((rXs+rXh) * 1000))
|
||||
|
||||
|
||||
|
||||
return ((krs+ers+trs)/3.0)+self.shield_max + self.hull_max + ((krh+erh+trh)/3.0)
|
||||
|
||||
|
||||
|
||||
ship = ShipInstance()
|
||||
print ship.survivability()
|
||||
|
||||
print "#" * 80
|
||||
mykatanas=ShipInstance(7664, 4296, (70,61,100), (20,80,50))
|
||||
print "We know its 19736... but own calcs say..."
|
||||
print mykatanas.survivability()
|
28
src/scon/game/screener.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
"""
|
||||
Screener Module.
|
||||
|
||||
Upon receiving a logfile, the screener module tries a first pass to retrieve the informations:
|
||||
- who am i? am i in steam?
|
||||
- which os do i use? whats the date? other specifics?
|
||||
- battles, when did they begin, when did they end, which players were in it, which teams (game.log)
|
||||
|
||||
It should act as a factory for a second, more in depth parsing mechanism, which can retrieve and
|
||||
manage the rest of the details.
|
||||
|
||||
"""
|
||||
class Information:
|
||||
def __init__(self):
|
||||
self.steam_id = None # steam id
|
||||
self.steam_username = None # optional steam username.
|
||||
self.username = None # ingame username.
|
||||
self.uid = None # does not change.
|
||||
self.pid = None # changes per battle. needed once to identify pilot.
|
||||
|
||||
|
||||
|
||||
class Screener(object):
|
||||
def __init__(self):
|
||||
pass
|
0
src/scon/game/skirmish.py
Normal file
0
src/scon/gui/__init__.py
Normal file
77
src/scon/gui/qbrowser.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
********************* VerySimpleWebBrowser ************************
|
||||
|
||||
This is a Very Simple Web Browser implemented over Qt and QtWebKit.
|
||||
|
||||
author: Juan Manuel Garcia <jmg.utn@gmail.com>
|
||||
|
||||
*******************************************************************
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit
|
||||
|
||||
class Browser(QtGui.QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the browser GUI and connect the events
|
||||
"""
|
||||
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
self.resize(800,600)
|
||||
self.centralwidget = QtGui.QWidget(self)
|
||||
|
||||
self.mainLayout = QtGui.QHBoxLayout(self.centralwidget)
|
||||
self.mainLayout.setSpacing(0)
|
||||
self.mainLayout.setMargin(1)
|
||||
|
||||
self.frame = QtGui.QFrame(self.centralwidget)
|
||||
|
||||
self.gridLayout = QtGui.QVBoxLayout(self.frame)
|
||||
self.gridLayout.setMargin(0)
|
||||
self.gridLayout.setSpacing(0)
|
||||
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.tb_url = QtGui.QLineEdit(self.frame)
|
||||
self.bt_back = QtGui.QPushButton(self.frame)
|
||||
self.bt_ahead = QtGui.QPushButton(self.frame)
|
||||
|
||||
self.bt_back.setIcon(QtGui.QIcon().fromTheme("go-previous"))
|
||||
self.bt_ahead.setIcon(QtGui.QIcon().fromTheme("go-next"))
|
||||
|
||||
self.horizontalLayout.addWidget(self.bt_back)
|
||||
self.horizontalLayout.addWidget(self.bt_ahead)
|
||||
self.horizontalLayout.addWidget(self.tb_url)
|
||||
self.gridLayout.addLayout(self.horizontalLayout)
|
||||
|
||||
self.html = QtWebKit.QWebView()
|
||||
self.gridLayout.addWidget(self.html)
|
||||
self.mainLayout.addWidget(self.frame)
|
||||
self.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.connect(self.tb_url, QtCore.SIGNAL("returnPressed()"), self.browse)
|
||||
self.connect(self.bt_back, QtCore.SIGNAL("clicked()"), self.html.back)
|
||||
self.connect(self.bt_ahead, QtCore.SIGNAL("clicked()"), self.html.forward)
|
||||
|
||||
self.default_url = "http://google.com"
|
||||
self.tb_url.setText(self.default_url)
|
||||
self.browse()
|
||||
|
||||
def browse(self):
|
||||
"""
|
||||
Make a web browse on a specific url and show the page on the
|
||||
Webview widget.
|
||||
"""
|
||||
|
||||
url = self.tb_url.text() if self.tb_url.text() else self.default_url
|
||||
self.html.load(QtCore.QUrl(url))
|
||||
self.html.show()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
main = Browser()
|
||||
main.show()
|
||||
sys.exit(app.exec_())
|
||||
|
444
src/scon/gui/treeview.py
Normal file
@ -0,0 +1,444 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
## FROM:
|
||||
# https://github.com/cloudformdesign/cloudtb/blob/master/extra/PyQt/treeview.py
|
||||
# ****** The Cloud Toolbox v0.1.2******
|
||||
# This is the cloud toolbox -- a single module used in several packages
|
||||
# found at <https://github.com/cloudformdesign>
|
||||
# For more information see <cloudformdesign.com>
|
||||
#
|
||||
# This module may be a part of a python package, and may be out of date.
|
||||
# This behavior is intentional, do NOT update it.
|
||||
#
|
||||
# You are encouraged to use this pacakge, or any code snippets in it, in
|
||||
# your own projects. Hopefully they will be helpful to you!
|
||||
#
|
||||
# This project is Licenced under The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2013 Garrett Berg cloudformdesign.com
|
||||
# An updated version of this file can be found at:
|
||||
# <https://github.com/cloudformdesign/cloudtb>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# http://opensource.org/licenses/MIT
|
||||
|
||||
# import pdb
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import sys
|
||||
#import icons_rc
|
||||
|
||||
|
||||
# from cloudtb import dbe
|
||||
|
||||
class Node(object):
|
||||
'''A general node stucture to be used in treeview
|
||||
the attrib_dict can store any information your overall treeview
|
||||
needs it to store.
|
||||
'''
|
||||
def __init__(self, name, parent=None, icon = None,
|
||||
attrib_dict = None):
|
||||
|
||||
self._name = name
|
||||
self._attrib = attrib_dict
|
||||
self._children = []
|
||||
self._parent = parent
|
||||
self.icon = icon
|
||||
|
||||
if parent is not None:
|
||||
parent.addChild(self)
|
||||
|
||||
def addChild(self, child):
|
||||
self._children.append(child)
|
||||
|
||||
def insertChild(self, position, child):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
|
||||
self._children.insert(position, child)
|
||||
child._parent = self
|
||||
return True
|
||||
|
||||
def removeChild(self, position):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
child = self._children.pop(position)
|
||||
child._parent = None
|
||||
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def setName(self, name):
|
||||
self._name = name
|
||||
|
||||
def child(self, row):
|
||||
return self._children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def row(self):
|
||||
if self._parent is not None:
|
||||
return self._parent._children.index(self)
|
||||
|
||||
|
||||
def log(self, tabLevel=-1):
|
||||
|
||||
output = ""
|
||||
tabLevel += 1
|
||||
|
||||
for i in range(tabLevel):
|
||||
output += " "
|
||||
|
||||
output += "|-" + self._name + "\n"
|
||||
|
||||
for child in self._children:
|
||||
output += child.log(tabLevel)
|
||||
|
||||
tabLevel -= 1
|
||||
# output += "\n"
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return self.log()
|
||||
|
||||
class TreeViewModel(QtCore.QAbstractItemModel):
|
||||
"""INPUTS: Node, QObject"""
|
||||
def __init__(self, root, parent=None, header_title = None):
|
||||
super(TreeViewModel, self).__init__(parent)
|
||||
self.is_editable = False
|
||||
self.is_selectable = True
|
||||
self.is_enabled = True
|
||||
self.set_flags()
|
||||
self._rootNode = root
|
||||
self.header_title = header_title
|
||||
|
||||
# The following are created functions called in "data" -- which is a
|
||||
# Qt defined funciton. This way of doing things is FAR more pythonic
|
||||
# and allows classes to inherit this one and not have to rewrite the
|
||||
# entire data method
|
||||
# all of them recieve an index and a node
|
||||
|
||||
def role_display(self, index, node):
|
||||
if index.column() == 0:
|
||||
return node.name()
|
||||
|
||||
def role_edit(self, index, node):
|
||||
return self.role_display(index, node)
|
||||
|
||||
def role_tool_tip(self, index, node):
|
||||
return
|
||||
|
||||
def role_check_state(self, index, node):
|
||||
return
|
||||
|
||||
def role_decoration(self, index, node):
|
||||
if index.column() == 0:
|
||||
icon = node.icon
|
||||
if icon == None:
|
||||
return False
|
||||
else:
|
||||
return icon
|
||||
|
||||
def role_flags(self, index, node):
|
||||
'''While not technically a "role" it behaves in much the same way.
|
||||
This method is called by the "flags" method for all indexes with
|
||||
a node'''
|
||||
return self.BASE_FLAGS
|
||||
|
||||
def set_flags(self, is_editable = None, is_selectable = None,
|
||||
is_enabled = None):
|
||||
''' Sets new flags to the BASE_FLAGS variable'''
|
||||
if is_editable != None:
|
||||
self.is_editable = is_editable
|
||||
if is_selectable != None:
|
||||
self.is_selectable = is_selectable
|
||||
if is_enabled != None:
|
||||
self.is_enabled = is_enabled
|
||||
self.BASE_FLAGS = QtCore.Qt.ItemFlags(
|
||||
(QtCore.Qt.ItemIsEnabled * bool(self.is_enabled))
|
||||
| (QtCore.Qt.ItemIsSelectable * bool(self.is_selectable))
|
||||
| (QtCore.Qt.ItemIsEditable * bool(self.is_editable))
|
||||
)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def rowCount(self, parent):
|
||||
if not parent.isValid():
|
||||
parentNode = self._rootNode
|
||||
else:
|
||||
parentNode = parent.internalPointer()
|
||||
|
||||
return parentNode.childCount()
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def columnCount(self, parent):
|
||||
return 1
|
||||
|
||||
"""INPUTS: QModelIndex, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def data(self, index, role):
|
||||
'''index is an object that contains a pointer to the item inside
|
||||
internPointer(). Note that this was set during the insertRows
|
||||
method call, so you don't need to track them!
|
||||
'''
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
node = index.internalPointer()
|
||||
|
||||
if role == QtCore.Qt.EditRole:
|
||||
return self.role_edit(index, node)
|
||||
|
||||
if role == QtCore.Qt.ToolTipRole:
|
||||
return self.role_tool_tip(index, node)
|
||||
|
||||
if role == QtCore.Qt.CheckStateRole:
|
||||
return self.role_check_state(index, node)
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
return self.role_display(index, node)
|
||||
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
return self.role_decoration(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex, QVariant, int (flag)"""
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
if index.isValid():
|
||||
if role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
node.setName(value)
|
||||
return True
|
||||
return False
|
||||
|
||||
"""INPUTS: int, Qt::Orientation, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if self.header_title != None:
|
||||
return self.header_title
|
||||
if section == 0:
|
||||
return "Scenegraph"
|
||||
else:
|
||||
return "Typeinfo"
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int (flag)"""
|
||||
# def flags(self, index):
|
||||
# return (QtCore.Qt.ItemIsEnabled |
|
||||
# QtCore.Qt.ItemIsSelectable #|
|
||||
## QtCore.Qt.ItemIsEditable
|
||||
# )
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return self.BASE_FLAGS
|
||||
|
||||
node = index.internalPointer()
|
||||
return self.role_flags(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return the parent of the node with the given QModelIndex"""
|
||||
def parent(self, index):
|
||||
node = self.getNode(index)
|
||||
parentNode = node.parent()
|
||||
|
||||
if parentNode == self._rootNode:
|
||||
return QtCore.QModelIndex()
|
||||
return self.createIndex(parentNode.row(), 0, parentNode)
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return a QModelIndex that corresponds to the given row,
|
||||
column and parent node"""
|
||||
def index(self, row, column, parent):
|
||||
# This is how Qt creates the nested (tree) list. It knows how many
|
||||
# rows it has because of insertRows, and it uses index and
|
||||
# createIndex to build the tree.
|
||||
# print 'Index called', row, column
|
||||
parentNode = self.getNode(parent)
|
||||
childItem = parentNode.child(row)
|
||||
if childItem:
|
||||
return self.createIndex(row, column, childItem)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
"""CUSTOM"""
|
||||
"""INPUTS: QModelIndex"""
|
||||
def getNode(self, index):
|
||||
if index.isValid():
|
||||
node = index.internalPointer()
|
||||
if node:
|
||||
return node
|
||||
|
||||
return self._rootNode
|
||||
|
||||
|
||||
"""INPUTS: int, List of Nodes, QModelIndex"""
|
||||
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
parentNode = self.getNode(parent)
|
||||
|
||||
self.beginInsertRows(parent, position, position + len(rows) - 1)
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
# childCount = parentNode.childCount()
|
||||
childNode = row
|
||||
success = parentNode.insertChild(position + i, childNode)
|
||||
|
||||
self.endInsertRows()
|
||||
|
||||
return success
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
if rows == 0:
|
||||
return
|
||||
|
||||
parentNode = self.getNode(parent)
|
||||
self.beginRemoveRows(parent, position, position + rows - 1)
|
||||
|
||||
for row in range(rows):
|
||||
success = parentNode.removeChild(position)
|
||||
# TODO: break if not success?
|
||||
|
||||
self.endRemoveRows()
|
||||
|
||||
return success
|
||||
|
||||
def clear_rows(self):
|
||||
return self.removeRows(0, self._rootNode.childCount())
|
||||
|
||||
# TODO: doesn't work. Not sure how to get icons
|
||||
ICON_FOLDER = QtGui.QIcon.fromTheme('folder')
|
||||
|
||||
def _node_compare(a, b):
|
||||
return b.isdir - a.isdir
|
||||
|
||||
def get_file_folder_node(fdata, parent):
|
||||
'''return the node structure of the data.
|
||||
[[(dir_name, path),
|
||||
[dir_name, path),
|
||||
[(file, path),
|
||||
(file, path)]]
|
||||
]
|
||||
]
|
||||
'''
|
||||
# TODO: set icons correctly
|
||||
|
||||
nodes = []
|
||||
for fobj in fdata:
|
||||
path = fobj[0]
|
||||
name = os.path.split(path)[1]
|
||||
|
||||
if len(fobj) == 1:
|
||||
fileobj = Node(name, parent = parent, icon = None)
|
||||
fileobj.full_path = path
|
||||
fileobj.isdir = False
|
||||
nodes.append(fileobj)
|
||||
continue
|
||||
folderobj = Node(name, parent = parent, icon = ICON_FOLDER,
|
||||
)
|
||||
folderobj.full_path = path
|
||||
folderobj.isdir = True
|
||||
|
||||
get_file_folder_node(fobj[1], parent = folderobj)
|
||||
nodes.append(folderobj)
|
||||
nodes.sort(cmp = _node_compare)
|
||||
return nodes
|
||||
import itertools
|
||||
|
||||
def _get_filelist_nodes(iter_file_list, dir_path = ''):
|
||||
'''Takes a sorted file list iterator and returns the files in a
|
||||
format that can be converted'''
|
||||
files = []
|
||||
dir_path = os.path.join(dir_path, '') # Put into directory syntax
|
||||
len_dp = len(dir_path)
|
||||
while True:
|
||||
try:
|
||||
fpath = next(iter_file_list)
|
||||
except StopIteration:
|
||||
break
|
||||
if dir_path != fpath[:len_dp]:
|
||||
iter_file_list = itertools.chain((fpath,), iter_file_list)
|
||||
break
|
||||
|
||||
if os.path.isdir(fpath):
|
||||
iter_file_list, new_files = _get_filelist_nodes(iter_file_list,
|
||||
dir_path = fpath)
|
||||
files.append((fpath, new_files))
|
||||
else:
|
||||
files.append((fpath,))
|
||||
return iter_file_list, files
|
||||
|
||||
def get_filelist_nodes(file_list, parent = None):
|
||||
file_list = sorted(file_list)
|
||||
file_tuples = _get_filelist_nodes(iter(file_list))[1]
|
||||
return get_file_folder_node(file_tuples, parent)
|
||||
|
||||
def dev_show_file_list(file_objects):
|
||||
'''For developemnet'''
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
rootNode = Node("Rootdir")
|
||||
model = TreeViewModel(rootNode)
|
||||
|
||||
treeView = QtGui.QTreeView()
|
||||
treeView.show()
|
||||
|
||||
treeView.setModel(model)
|
||||
model.insertRows(0, file_objects, QtCore.QModelIndex())
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint
|
||||
|
||||
files = '''/home/user/Projects/Learning/LearningQt/LearningQt.pro.user
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.pro
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.h
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.cpp
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.pri
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt64.png
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt_harmattan.desktop
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.svg
|
||||
/home/user/Projects/Learning/LearningQt/main.cpp
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.desktop
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt/main.qml
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt
|
||||
/home/user/Projects/Learning/LearningQt/qml
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt80.png'''
|
||||
nodes = get_filelist_nodes(files.split('\n'))
|
||||
for n in nodes:
|
||||
print n
|
||||
dev_show_file_list(nodes)
|
||||
|
108
src/scon/gui/viewer.py
Normal file
@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Viewer - starts a webbrowser which is coupled to a local renderer
|
||||
|
||||
"""
|
||||
import os
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings'
|
||||
#from django.core.management import setup_environ
|
||||
#from scon.dj import settings
|
||||
#setup_environ(settings)
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
|
||||
from scon.dejaqt.folders import FolderLibrary
|
||||
from scon.dejaqt.qweb import DejaWebView
|
||||
from treeview import TreeViewModel, Node
|
||||
|
||||
class MenuTree(QtGui.QTreeView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
QtGui.QTreeView.__init__(self, *args, **kwargs)
|
||||
self.test_tree()
|
||||
|
||||
def test_tree(self):
|
||||
self.rootNode = Node("Rootdir")
|
||||
model = TreeViewModel(self.rootNode)
|
||||
self.setModel(model)
|
||||
self.rootNode.addChild(Node('Hey'))
|
||||
|
||||
|
||||
class Browser(QtGui.QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the browser GUI and connect the events
|
||||
"""
|
||||
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
self.resize(860,600)
|
||||
self.centralwidget = QtGui.QWidget(self)
|
||||
|
||||
self.mainLayout = QtGui.QHBoxLayout(self.centralwidget)
|
||||
self.mainLayout.setSpacing(0)
|
||||
self.mainLayout.setMargin(1)
|
||||
|
||||
self.frame = QtGui.QFrame(self.centralwidget)
|
||||
|
||||
self.gridLayout = QtGui.QVBoxLayout(self.frame)
|
||||
self.gridLayout.setMargin(0)
|
||||
self.gridLayout.setSpacing(0)
|
||||
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.tb_url = QtGui.QLineEdit(self.frame)
|
||||
self.bt_back = QtGui.QPushButton(self.frame)
|
||||
self.bt_ahead = QtGui.QPushButton(self.frame)
|
||||
|
||||
self.bt_back.setIcon(QtGui.QIcon().fromTheme("go-previous"))
|
||||
self.bt_ahead.setIcon(QtGui.QIcon().fromTheme("go-next"))
|
||||
|
||||
self.horizontalLayout.addWidget(self.bt_back)
|
||||
self.horizontalLayout.addWidget(self.bt_ahead)
|
||||
self.horizontalLayout.addWidget(self.tb_url)
|
||||
self.gridLayout.addLayout(self.horizontalLayout)
|
||||
|
||||
self.horizontalMainLayout = QtGui.QHBoxLayout()
|
||||
self.gridLayout.addLayout(self.horizontalMainLayout)
|
||||
#
|
||||
#self.menu = MenuTree()
|
||||
self.html = DejaWebView(folders=FolderLibrary({'':
|
||||
'D:/work/workspace/scon/src/scon/dj/scon/media/'})
|
||||
)
|
||||
#self.horizontalMainLayout.addWidget(self.menu)
|
||||
self.horizontalMainLayout.addWidget(self.html)
|
||||
self.mainLayout.addWidget(self.frame)
|
||||
self.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.connect(self.tb_url, QtCore.SIGNAL("returnPressed()"), self.browse)
|
||||
self.connect(self.bt_back, QtCore.SIGNAL("clicked()"), self.html.back)
|
||||
self.connect(self.bt_ahead, QtCore.SIGNAL("clicked()"), self.html.forward)
|
||||
|
||||
self.tb_url.setText('/crafting/forum')
|
||||
|
||||
self.browse()
|
||||
|
||||
def browse(self):
|
||||
"""
|
||||
Make a web browse on a specific url and show the page on the
|
||||
Webview widget.
|
||||
"""
|
||||
|
||||
url = self.tb_url.text() if self.tb_url.text() else 'page:///'
|
||||
if not str(url).startswith('page://'):
|
||||
url = 'page://' + url
|
||||
self.html.load(QtCore.QUrl(url))
|
||||
#self.html.setHtml(self.serve())
|
||||
|
||||
#self.html.load(QtCore.QUrl('page:///crafting/forum/'))
|
||||
self.html.show()
|
||||
|
||||
def serve(self, what=None):
|
||||
return "<html><body><h1>It works!</h1></body></html>"
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
main = Browser()
|
||||
main.show()
|
||||
sys.exit(app.exec_())
|
||||
|
14
src/scon/logs/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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.
|
||||
|
||||
"""
|
89
src/scon/logs/base.py
Normal file
@ -0,0 +1,89 @@
|
||||
import logging
|
||||
"""
|
||||
Base Class for a Logentry is Log. Stacktrace is an exception, which gets injected if a stacktrace
|
||||
is assumed, and swallows all following unrecognized logs.
|
||||
|
||||
-> It gets parsed by a Logstream, like the Logfile, but might also be used to be feeded
|
||||
by live-streams of currently open log files.
|
||||
|
||||
-> Logfiles is responsible to read whole packs of files, and
|
||||
-> Sessions are responsible for reading whole directories.
|
||||
|
||||
A Log object usually will expand itself containing "values", and is responsible to retain all dynamic data needed to describe it in unpack()
|
||||
The classmethod is_handler should pre-scan a log, which is usually a dict containing the actual log in log['log']
|
||||
but it could be a string aswell.
|
||||
|
||||
clean is called to make a log object independent of its source information, and delete all incoming data, so it becomes sleek.
|
||||
reviewed is an internal boolean, which supposed to be saved on successful unpack, unpack should ignore already unpacked logs.
|
||||
matcher is a regex object to match, or a list of them.
|
||||
trash is a boolean flag to indicate, this log is possibly unknown information or unneeded, and should be removed or ignored.
|
||||
|
||||
-> Note for anyone creating new subclasses for parsing:
|
||||
All classes are to be __slot__-ed so they can be created more efficiently by python.
|
||||
A class without __slot__ will slow down parsing exponentially in CPython.
|
||||
__slots__ hinder you to add new properties on the fly in the code, but having this immutable class optimizes memory allocation.
|
||||
|
||||
This is the reason, the base layout of the log object is explained here.
|
||||
"""
|
||||
|
||||
|
||||
L_CMBT = 'CMBT'
|
||||
L_WARNING = 'WARNING'
|
||||
L_NET = 'NET' # Not supported in near future.
|
||||
L_CHAT = 'CHAT'
|
||||
|
||||
class Log(object):
|
||||
__slots__ = ['matcher', 'trash', 'reviewed']
|
||||
matcher = None
|
||||
trash = False
|
||||
reviewed = False
|
||||
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
return False
|
||||
|
||||
def unpack(self, force=False):
|
||||
''' unpacks this log from its data and saves values '''
|
||||
pass
|
||||
|
||||
def explain(self):
|
||||
''' returns a String readable by humans explaining this Log '''
|
||||
return ''
|
||||
|
||||
def clean(self):
|
||||
''' tell the log to forget all non-essential data '''
|
||||
pass
|
||||
|
||||
def append(self, something):
|
||||
''' returns true if this logfile wants an unrecognized log appended to it. '''
|
||||
return False
|
||||
|
||||
class Stacktrace(Log):
|
||||
''' Special Log to catch error reports '''
|
||||
def __init__(self, values=None):
|
||||
super(Stacktrace, self).__init__()
|
||||
self.message = values or ''
|
||||
if isinstance(self.message, dict):
|
||||
self.message = self.message.get('log', '')
|
||||
#self.trash = True
|
||||
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
# do i have a system crash report beginning here?
|
||||
if isinstance(log, basestring):
|
||||
l = log.strip()
|
||||
elif isinstance(log, dict):
|
||||
l = log.get('log', '').strip()
|
||||
else:
|
||||
return False
|
||||
if l.startswith('Stack trace:') or l.startswith('BitStream::DbgLog'):
|
||||
return True
|
||||
|
||||
def clean(self):
|
||||
self.message = ''
|
||||
|
||||
def append(self, something):
|
||||
''' I take anything! '''
|
||||
logging.debug( "EXC: %s" % something )
|
||||
self.message = '%s\n%s' % (self.message, something)
|
||||
return True
|
206
src/scon/logs/chat.py
Normal file
@ -0,0 +1,206 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from logs.base import Log, L_WARNING, Stacktrace
|
||||
import re
|
||||
"""
|
||||
Responsible for Chat Log.
|
||||
|
||||
Anything related to chat gets logged here, basicly interesting for chat related stuff mainly.
|
||||
Channel leaving and joining and connection to the chat server get logged here too.
|
||||
|
||||
-------------------------------
|
||||
Maybe add something to create a ColorChart of usernames?
|
||||
between 33-33-33 and FF-33 FF-33 FF-33
|
||||
|
||||
"""
|
||||
|
||||
class ChatLog(Log):
|
||||
__slots__ = ['matcher', 'trash', '_match_id', 'values']
|
||||
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
if log.get('logtype', None) == 'CHAT':
|
||||
return cls._is_handler(log)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
return False
|
||||
|
||||
def __init__(self, values=None):
|
||||
self.values = values or {}
|
||||
self.reviewed = False
|
||||
|
||||
def unpack(self, force=False):
|
||||
if self.reviewed and not force:
|
||||
return True
|
||||
self._match_id = None
|
||||
# unpacks the data from the values.
|
||||
if hasattr(self, 'matcher') and self.matcher:
|
||||
matchers = self.matcher
|
||||
if not isinstance(matchers, list):
|
||||
matchers = [matchers,]
|
||||
for i, matcher in enumerate(matchers):
|
||||
m = matcher.match(self.values.get('log', ''))
|
||||
if m:
|
||||
self.values.update(m.groupdict())
|
||||
self._match_id = i
|
||||
self.reviewed = True
|
||||
return True
|
||||
# unknown?
|
||||
self.trash = True
|
||||
|
||||
def explain(self):
|
||||
''' returns a String readable by humans explaining this Log '''
|
||||
return self.values.get('log', 'Unknown Chat Log')
|
||||
|
||||
def clean(self):
|
||||
if 'log' in self.values.keys():
|
||||
del self.values['log']
|
||||
|
||||
class SystemMessage(ChatLog):
|
||||
matcher = re.compile(r"^<\s+SYSTEM>\s(?P<message>.*)")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('< SYSTEM>'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[SYSTEM]: %(message)s' % self.values
|
||||
|
||||
def append(self, something):
|
||||
''' System Messages accept appends '''
|
||||
if 'message' in self.values.keys():
|
||||
self.values['message'] = '%s\n%s' % (self.values['message'], something)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class PrivateMessageReceived(ChatLog):
|
||||
matcher = re.compile(r"^<\s\s\s\sPRIVATE From>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('< PRIVATE From>'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[From %(nickname)s]: %(message)s' % self.values
|
||||
|
||||
def append(self, something):
|
||||
''' Private Messages accept appends '''
|
||||
if 'message' in self.values.keys():
|
||||
self.values['message'] = '%s\n%s' % (self.values['message'], something)
|
||||
return True
|
||||
|
||||
class PrivateMessageSent(ChatLog):
|
||||
matcher = re.compile(r"^<\s\s\s\sPRIVATE To\s\s>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('< PRIVATE To >'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[To %(nickname)s]: %(message)s' % self.values
|
||||
|
||||
def append(self, something):
|
||||
''' Private Messages accept appends '''
|
||||
if 'message' in self.values.keys():
|
||||
self.values['message'] = '%s\n%s' % (self.values['message'], something)
|
||||
return True
|
||||
|
||||
class ChatMessage(ChatLog):
|
||||
matcher = re.compile(r"^<\s*#(?P<channel>[^>]+)>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('<'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[%(channel)s] <%(nickname)s>: %(message)s' % self.values
|
||||
|
||||
def append(self, something):
|
||||
''' ChatMessages accept appends '''
|
||||
if not 'message' in self.values.keys():
|
||||
print "Missing message? %s" % self.values
|
||||
self.values['message'] = ''
|
||||
self.values['message'] = '%s\n%s' % (self.values['message'], something)
|
||||
return True
|
||||
|
||||
class ChatJoinChannel(ChatLog):
|
||||
matcher = re.compile(r"^Join\schannel\s<\s*#(?P<channel>[^>]+)>")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('Join channel'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[joined %(channel)s]' % self.values
|
||||
|
||||
class ChatLeaveChannel(ChatLog):
|
||||
matcher = re.compile(r"^Leave\schannel\s<\s*#(?P<channel>[^>]+)>")
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('Leave channel'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def explain(self):
|
||||
return '[left %(channel)s]' % self.values
|
||||
|
||||
|
||||
class ChatServerConnect(ChatLog):
|
||||
# 00:12:47.668 CHAT| Connection to chat-server established
|
||||
matcher = []
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('Connection to'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def unpack(self, force=False):
|
||||
self.reviewed = True
|
||||
return True
|
||||
|
||||
def explain(self):
|
||||
return '[connected]'
|
||||
|
||||
|
||||
class ChatServerDisconnect(ChatLog):
|
||||
# 00:53:03.738 CHAT| Disconnect form chat-server (reason 0)
|
||||
matcher = []
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').lstrip().startswith('Disconnect'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def unpack(self, force=False):
|
||||
self.reviewed = True
|
||||
return True
|
||||
|
||||
def explain(self):
|
||||
return '[disconnected]'
|
||||
|
||||
CHAT_LOGS = [
|
||||
SystemMessage,
|
||||
PrivateMessageReceived,
|
||||
PrivateMessageSent,
|
||||
ChatMessage, # private messages need to be before chatmessage.
|
||||
ChatServerConnect,
|
||||
ChatServerDisconnect,
|
||||
ChatJoinChannel,
|
||||
ChatLeaveChannel,
|
||||
Stacktrace,
|
||||
]
|
304
src/scon/logs/combat.py
Normal file
@ -0,0 +1,304 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Primary Packets for Combat.Log Files.
|
||||
|
||||
This is the most important part for dealing with actual statistics, since every action taken
|
||||
in a combat instance gets logged here.
|
||||
|
||||
|
||||
------------------------------------
|
||||
Note:
|
||||
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 |
|
||||
"""
|
||||
import re
|
||||
from base import Log, L_CMBT, Stacktrace
|
||||
import logging
|
||||
|
||||
class CombatLog(Log):
|
||||
__slots__ = Log.__slots__ + [ '_match_id', 'values']
|
||||
@classmethod
|
||||
def _log_handler(cls, log):
|
||||
if log.startswith(cls.__name__):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
if log.get('logtype', None) == L_CMBT:
|
||||
return cls._log_handler(log.get('log', '').strip())
|
||||
return False
|
||||
|
||||
def __init__(self, values=None):
|
||||
self.values = values or {}
|
||||
self.reviewed = False
|
||||
|
||||
def unpack(self, force=False):
|
||||
if self.reviewed and not force:
|
||||
return True
|
||||
self._match_id = None
|
||||
# unpacks the data from the values.
|
||||
if hasattr(self, 'matcher') and self.matcher:
|
||||
matchers = self.matcher
|
||||
if not isinstance(matchers, list):
|
||||
matchers = [matchers,]
|
||||
for i, matcher in enumerate(matchers):
|
||||
m = matcher.match(self.values.get('log', ''))
|
||||
if m:
|
||||
self.values.update(m.groupdict())
|
||||
self._match_id = i
|
||||
self.reviewed = True
|
||||
return True
|
||||
# unknown?
|
||||
if not isinstance(self, UserEvent):
|
||||
logging.warning('Unknown Packet for %s:\n%s' % (self.__class__.__name__,
|
||||
self.values.get('log', '')))
|
||||
# trash if unknown or no matcher.
|
||||
self.trash = True
|
||||
|
||||
def explain(self):
|
||||
''' returns a String readable by humans explaining this Log '''
|
||||
return self.values.get('log', 'Unknown Combat Log')
|
||||
|
||||
def clean(self):
|
||||
if 'log' in self.values.keys():
|
||||
del self.values['log']
|
||||
|
||||
|
||||
# @todo: where does this come from?
|
||||
class Action(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
pass
|
||||
|
||||
class Gameplay(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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.
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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>.+)|\s*)")
|
||||
|
||||
class Reward(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# ordinary reward:
|
||||
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>.*)"),
|
||||
# openspace reward (karma):
|
||||
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)\s+(?P<karma>[\+\-]\d+)\skarma\spoints\s+for\s(?P<reward_reason>.*)"),
|
||||
]
|
||||
|
||||
class Participant(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s+(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P<module_class>[^']+)';\s*(?P<additional>.*)|<(?P<other>\w+)>)")
|
||||
|
||||
"""
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket launch 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894)
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket detonation 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse'
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket launch 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894)
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket detonation 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse'
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket detonation 18892, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'ttl'
|
||||
2017-03-29 13:25:49 - Unknown Packet for Rocket:
|
||||
Rocket detonation 18931, owner 'optimistik', def 'Weapon_Railgun_Heavy_T5_Epic', reason 'hit'
|
||||
2017-03-29 13:25:49 - Unknown Packet for Participant:
|
||||
Participant white213mouse Ship_Race5_M_ATTACK_Rank15
|
||||
"""
|
||||
|
||||
class Rocket(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
|
||||
|
||||
class AddStack(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)")
|
||||
|
||||
class Uncaptured(CombatLog):
|
||||
"""
|
||||
Variables:
|
||||
- objective (which was uncaptured (most likely something like VitalPointXY))
|
||||
- team (number)
|
||||
- attackers (split by space, names of the attackers, contains bots)
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Uncaptured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:\s(?P<attackers>.*)|)")
|
||||
|
||||
# Special classes
|
||||
class GameEvent(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
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.startswith('======='):
|
||||
return True
|
||||
return False
|
||||
|
||||
def unpack(self, force=False):
|
||||
if self.reviewed and not force:
|
||||
return True
|
||||
self._match_id = None
|
||||
# unpacks the data from the values.
|
||||
# small override to remove trailing "="s in the matching.
|
||||
if hasattr(self, 'matcher') and self.matcher:
|
||||
matchers = self.matcher
|
||||
if not isinstance(matchers, list):
|
||||
matchers = [matchers,]
|
||||
for i, matcher in enumerate(matchers):
|
||||
m = matcher.match(self.values.get('log', '').strip('=').strip())
|
||||
if m:
|
||||
self.values.update(m.groupdict())
|
||||
self._match_id = i
|
||||
self.reviewed = True
|
||||
return True
|
||||
# unknown?
|
||||
self.trash = True
|
||||
|
||||
def clean(self):
|
||||
if 'log' in self.values.keys():
|
||||
del self.values['log']
|
||||
|
||||
class PVE_Mission(CombatLog):
|
||||
"""
|
||||
- mission: contains the mission id.
|
||||
- message: contains the pve mission message, like starting rounds, waves, etc.
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^PVE_Mission:\s'(?P<mission>[^']+)'.\s(?P<message>.*)") # this is very general, but we dont care for pve now.
|
||||
|
||||
class Looted(CombatLog):
|
||||
"""
|
||||
called on looting in openspace.
|
||||
- loot contains the loot id.
|
||||
- container contains the container looted from.
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Looted\s'(?P<loot>[^']+)'\sfrom\s'(?P<container>[^']+)'")
|
||||
|
||||
class Dropped(CombatLog):
|
||||
"""
|
||||
called on dropping in openspace.
|
||||
- loot contains the loot id. it can be '<all>'
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Dropped\sloot\s'(?P<loot>[^']+)'")
|
||||
|
||||
class Set(CombatLog):
|
||||
"""
|
||||
called on setting "relationship" / OpenSpace
|
||||
Variables in values:
|
||||
- what (relationship)
|
||||
- name (who do i set?)
|
||||
- value (to what value?)
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Set\s(?P<what>\w+)\s(?P<name>[^\s]+)\sto\s(?P<value>\w+)")
|
||||
|
||||
class SqIdChange(CombatLog):
|
||||
""" - number: player number
|
||||
- name: player name
|
||||
- old_sqid: sqid of player before
|
||||
- sqid: new player sqid
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Player\s(?P<number>\d+)\((?P<name>[^\)]+)\)\schanged\ssqid\sfrom\s(?P<old_sqid>\d+)\sto\s(?P<sqid>\d+)")
|
||||
|
||||
@classmethod
|
||||
def _log_handler(cls, log):
|
||||
if log.startswith('Player'):
|
||||
return True
|
||||
return False
|
||||
|
||||
class Mailed(CombatLog):
|
||||
""" has no information. only that loot has been mailed """
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("Mailed\sloot")
|
||||
|
||||
class UserEvent(CombatLog):
|
||||
""" special class for combat logs that might be associated with the playing player """
|
||||
__slots__ = CombatLog.__slots__
|
||||
@classmethod
|
||||
def _log_handler(cls, log):
|
||||
if log and 'earned medal' in log:
|
||||
return True
|
||||
elif log:
|
||||
logging.debug('UserEvent saw unknown line:\n%s' % log)
|
||||
return False
|
||||
|
||||
# Action?
|
||||
COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal,
|
||||
Gameplay, #?
|
||||
Scores,
|
||||
Killed, Captured, AddStack, Cancel, Uncaptured,
|
||||
# undone openspace:
|
||||
PVE_Mission, Looted, Set, Dropped,
|
||||
SqIdChange, Mailed, # unknown if these are important...
|
||||
# always last:
|
||||
GameEvent, UserEvent,
|
||||
Stacktrace,
|
||||
]
|
||||
|
195
src/scon/logs/game.py
Normal file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from logs.base import Log, L_WARNING, Stacktrace
|
||||
import re
|
||||
"""
|
||||
This deals with the Game.Log file
|
||||
This file records lots of junk, but is needed to establish actions taken between combat sessions,
|
||||
or retrieve more detailed information about running instances.
|
||||
It is also the typical place for a Stacktrace to happen.
|
||||
|
||||
|
||||
--------------------------------------
|
||||
Interesting Lines:
|
||||
|
||||
23:16:27.427 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg'
|
||||
23:16:36.214 | ====== starting level: 'levels/mainmenu/mainmenu' ======
|
||||
23:16:38.822 | ====== level started: 'levels/mainmenu/mainmenu' success ======
|
||||
23:16:44.251 | ====== starting level: 'levels\mainmenu\mm_empire' ======
|
||||
23:16:46.464 | ====== level started: 'levels\mainmenu\mm_empire' success ======
|
||||
|
||||
--- Date: 2014-07-18 (Fri Jul 2014) Mitteleuropäische Sommerzeit UTC+01:00
|
||||
|
||||
23:55:55.517 | MasterServerSession: connect to dedicated server, session 6777304, at addr 159.253.138.162|35005
|
||||
23:55:55.543 | client: start connecting to 159.253.138.162|35005...
|
||||
23:55:55.683 | client: connected to 159.253.138.162|35005, setting up session...
|
||||
23:55:55.886 | client: ADD_PLAYER 0 (OregyenDuero [OWL], 00039C86) status 6 team 1 group 1178422
|
||||
23:55:55.886 | client: ADD_PLAYER 1 (R0gue, 0012768A) status 6 team 2 group 1178451
|
||||
23:55:55.886 | client: ADD_PLAYER 2 (g4borg [OWL], 0003A848) status 1 team 1 group 1178422
|
||||
23:55:55.886 | client: ADD_PLAYER 3 (WladTepes, 001210D8) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 4 (oberus [], 000FE9B2) status 6 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 5 (TheGuns58, 00121C58) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 6 (Belleraphon, 0004C744) status 2 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 7 (TopoL, 00007E1F) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 8 (unicoimbraPT, 000C4FAC) status 6 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 9 (AeroBobik [], 00082047) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 10 (Samson4321 [], 000B93AF) status 6 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 11 (nol [], 00069165) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 12 (Pudwoppa, 000334A4) status 2 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 13 (IgorMad [], 000D2AF3) status 6 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 14 (YokaI, 000F1CC9) status 6 team 2
|
||||
23:55:55.886 | client: ADD_PLAYER 15 (MrAnyKey [], 0012246C) status 6 team 2 group 1178451
|
||||
23:55:55.886 | client: ADD_PLAYER 30 ((bot)David, 00000000) status 4 team 1
|
||||
23:55:55.886 | client: ADD_PLAYER 31 ((bot)George, 00000000) status 4 team 2
|
||||
23:55:55.886 | client: server assigned id 2
|
||||
23:55:55.886 | client: got level load message 's1340_thar_aliendebris13'
|
||||
23:55:55.889 | reset d3d device
|
||||
23:55:56.487 | ReplayManager: stopping activity due to map change
|
||||
23:55:56.576 | ====== starting level: 'levels\area2\s1340_thar_aliendebris13' KingOfTheHill client ======
|
||||
|
||||
|
||||
"""
|
||||
|
||||
class GameLog(Log):
|
||||
__slots__ = ['matcher', 'trash', '_match_id', 'values']
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
if log.get('logtype', None) == '': # we handle only logs with empty logtype.
|
||||
return cls._is_handler(log)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
return False
|
||||
|
||||
def __init__(self, values=None):
|
||||
self.values = values
|
||||
self.reviewed = False
|
||||
|
||||
def clean(self):
|
||||
if 'log' in self.values.keys():
|
||||
del self.values['log']
|
||||
|
||||
def unpack(self, force=False):
|
||||
if self.reviewed and not force:
|
||||
return True
|
||||
self._match_id = None
|
||||
# unpacks the data from the values.
|
||||
if hasattr(self, 'matcher') and self.matcher:
|
||||
matchers = self.matcher
|
||||
if not isinstance(matchers, list):
|
||||
matchers = [matchers,]
|
||||
for i, matcher in enumerate(matchers):
|
||||
m = matcher.match(self.values.get('log', ''))
|
||||
if m:
|
||||
self.values.update(m.groupdict())
|
||||
self._match_id = i
|
||||
self.reviewed = True
|
||||
return True
|
||||
# unknown?
|
||||
self.trash = True
|
||||
|
||||
def explain(self):
|
||||
''' returns a String readable by humans explaining this Log '''
|
||||
return self.values.get('log', 'Unknown Game Log')
|
||||
|
||||
class WarningLog(Log):
|
||||
__slots__ = ['trash',]
|
||||
trash = True
|
||||
|
||||
@classmethod
|
||||
def is_handler(cls, log):
|
||||
if log.get('logtype', None) == L_WARNING:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self, values=None):
|
||||
pass
|
||||
|
||||
########################################################################################################
|
||||
# Individual logs.
|
||||
|
||||
class SteamInitialization(GameLog):
|
||||
matcher = [
|
||||
re.compile(r"^Steam\sinitialized\sappId\s(?P<steam_app_id>\d+),\suserSteamID\s(?P<steam_id_universe>\d+)\|(?P<steam_id_type>\d+)\|(?P<steam_id_account_hex>\w+),\suserName\s'(?P<steam_username>[^']+)'"),
|
||||
]
|
||||
|
||||
class MasterServerSession(GameLog):
|
||||
matcher = [
|
||||
re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P<session_id>\d+)|at addr (?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+))+"),
|
||||
re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P<session_id>\d+),\sat\saddr\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+),\szoneId\s(?P<zone_id>\d+)"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').startswith('MasterServerSession'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ClientInfo(GameLog):
|
||||
# Note: clinfo holds the subtype of this packet.
|
||||
matcher = [
|
||||
# connecting; addr, port
|
||||
re.compile(r"^client\:\sstart\s(?P<clinfo>connecting)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.\.\."),
|
||||
# connected; addr, port
|
||||
re.compile(r"^client\:\s(?P<clinfo>connected)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+).*"),
|
||||
# ADD_PLAYER; pnr, player, clantag, player_id, status, team, group
|
||||
re.compile(r"^client\:\s(?P<clinfo>ADD_PLAYER)\s+(?P<pnr>\d+)\s+\((?P<player>[^\s\,]+)(?:\s\[(?P<clantag>\w+)\],|\s\[\],|,)\s(?P<player_id>\w+)\)(?:\s|status\s(?P<status>\d+)|team\s(?P<team>\d+)|group\s(?P<group>\d+))+"),
|
||||
# assigned; myid
|
||||
re.compile(r"^client\:\sserver\s(?P<clinfo>assigned)\sid\s(?P<myid>\d+)"),
|
||||
# level; level
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>level)\sload\smessage\s'(?P<level>[^']+)'"),
|
||||
# leave; pnr
|
||||
re.compile(r"^client\:\splayer\s(?P<pnr>\d+)\s(?P<clinfo>leave)\sgame"),
|
||||
# avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss
|
||||
re.compile(r"^client\:\s(?P<clinfo>avgPing)\s(?P<avg_ping>[^;]+)(?:\;|\s|avgPacketLoss\s(?P<avg_packet_loss>[^;]+)|avgSnapshotLoss\s(?P<avg_snapshot_loss>[^;$]+))+"),
|
||||
# closed; dr
|
||||
re.compile(r"^client\:\sconnection\s(?P<clinfo>closed)\.(?:\s|(?P<dr>.*))+"),
|
||||
# disconnect; addr, port, dr
|
||||
re.compile(r"^client\:\s(?P<clinfo>disconnect)\sfrom\sserver\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.(?:\s|(?P<dr>\w+))+"),
|
||||
# ready;
|
||||
re.compile(r"^client\:\ssend\s(?P<clinfo>ready)\smessage"),
|
||||
# init; ping
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>init)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P<ping>\d+)"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').startswith('client:'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class StartingLevel(GameLog):
|
||||
# level, gametype, unknown_gametype
|
||||
matcher = [
|
||||
re.compile(r"^======\sstarting\slevel\:\s'(?P<level>[^']+)'(?:\s|client|(?P<gametype>KingOfTheHill)|(?P<unknown_gametype>[^\s]+))+======"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').startswith('====== starting'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class LevelStarted(GameLog):
|
||||
matcher = []
|
||||
|
||||
@classmethod
|
||||
def _is_handler(cls, log):
|
||||
if log.get('log', '').startswith('====== level'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
GAME_LOGS = [#SteamInitialization,
|
||||
MasterServerSession,
|
||||
ClientInfo,
|
||||
StartingLevel,
|
||||
#LevelStarted,
|
||||
Stacktrace,
|
||||
]
|
60
src/scon/logs/logfile.py
Normal file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
from .logstream import LogStream
|
||||
|
||||
|
||||
class LogFile(LogStream):
|
||||
def __init__(self, fname=None,
|
||||
folder=None):
|
||||
super(LogFile, self).__init__()
|
||||
self.fname = fname
|
||||
self.folder = folder # only for custom tagging.
|
||||
self._data = None
|
||||
|
||||
def read(self, fname=None):
|
||||
fname = fname or self.fname
|
||||
try:
|
||||
f = open(fname, 'r')
|
||||
self.set_data(f.read())
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def filter(self, klasses):
|
||||
ret = []
|
||||
for line in self.lines:
|
||||
for k in klasses:
|
||||
if isinstance(line, k):
|
||||
ret.append(line)
|
||||
break
|
||||
return ret
|
||||
|
||||
def parse(self):
|
||||
# parse _data if we still have no lines.
|
||||
lines = []
|
||||
if self.has_data():
|
||||
data_lines = self.get_data(
|
||||
).replace('\r', '\n'
|
||||
).replace('\n\n', '\n'
|
||||
).split('\n'
|
||||
)
|
||||
for line in data_lines:
|
||||
line = self.pre_parse_line(line)
|
||||
if not line:
|
||||
continue
|
||||
else:
|
||||
lines.append(line)
|
||||
elif self.lines:
|
||||
lines = self.lines
|
||||
if lines:
|
||||
for line in lines:
|
||||
self._parse_line(line)
|
||||
|
||||
|
49
src/scon/logs/logfiles.py
Normal file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Resolves Logs.
|
||||
"""
|
||||
|
||||
from logfile import LogFile
|
||||
from combat import COMBAT_LOGS
|
||||
from game import GAME_LOGS
|
||||
from chat import CHAT_LOGS
|
||||
|
||||
class LogFileResolver(LogFile):
|
||||
''' dynamic logfile resolver '''
|
||||
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
|
||||
|
||||
class CombatLogFile(LogFile):
|
||||
''' Combat Log '''
|
||||
def resolve(self, line):
|
||||
for klass in COMBAT_LOGS:
|
||||
if klass.is_handler(line):
|
||||
return klass(line)
|
||||
return line
|
||||
|
||||
class GameLogFile(LogFile):
|
||||
''' Game Log '''
|
||||
def resolve(self, line):
|
||||
for klass in GAME_LOGS:
|
||||
if klass.is_handler(line):
|
||||
return klass(line)
|
||||
return line
|
||||
|
||||
class ChatLogFile(LogFile):
|
||||
''' Chat Log '''
|
||||
def resolve(self, line):
|
||||
for klass in CHAT_LOGS:
|
||||
if klass.is_handler(line):
|
||||
return klass(line)
|
||||
return line
|
||||
|
144
src/scon/logs/logstream.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""
|
||||
A LogStream is supposed to:
|
||||
- parse data feeded into it.
|
||||
- yield new objects
|
||||
- remember errors
|
||||
|
||||
LogStream.Initialize:
|
||||
- initialize the logstream in some way.
|
||||
|
||||
LogStream.Next:
|
||||
- once initialized, read your stream until you can yield a new class
|
||||
the next function reads the read-stream ahead.
|
||||
empty lines are omitted
|
||||
it tries to match the data into a new class and yields it
|
||||
if it runs into trouble, it just outputs the line for now.
|
||||
|
||||
InitializeString:
|
||||
- init with a data blob
|
||||
- nice for trying it on files
|
||||
|
||||
@TODO: look at how file streams in python are implemented and find a good generic solution
|
||||
combine it with the lookup for "watching files being changed", to create a program which listens to the logs live
|
||||
@see: monitor.py
|
||||
@see: watchdog https://pypi.python.org/pypi/watchdog
|
||||
"""
|
||||
from .base import Log
|
||||
import re
|
||||
from logs.base import Stacktrace
|
||||
import logging
|
||||
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 LogStream(object):
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
self._data = None
|
||||
self._last_object = None
|
||||
|
||||
def add_to_queue(self, line):
|
||||
# adds a line to the queue
|
||||
pass
|
||||
|
||||
def new_packets(self, finish=False):
|
||||
# yields new packets.
|
||||
# processes the queue a bit.
|
||||
# yields new packets, once they are done.
|
||||
# watch out not to process the last packet until it has a follow up!
|
||||
# finish: override and yield all packets to finish.
|
||||
pass
|
||||
|
||||
#####################################################################
|
||||
def has_data(self):
|
||||
if self._data:
|
||||
return True
|
||||
|
||||
def set_data(self, data):
|
||||
self._data = data
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
def clean(self, remove_log=True):
|
||||
# cleans the logs by removing all non parsed packets.
|
||||
# remove_log: should i remove the raw log entry?
|
||||
lines = []
|
||||
for l in self.lines:
|
||||
if isinstance(l, Log):
|
||||
if l.unpack():
|
||||
if not getattr(l, 'trash', False):
|
||||
if remove_log:
|
||||
l.clean()
|
||||
lines.append(l)
|
||||
else:
|
||||
print type(l)
|
||||
print l
|
||||
self.lines = lines
|
||||
self._unset_data()
|
||||
|
||||
data = property(set_data, get_data)
|
||||
|
||||
def _unset_data(self):
|
||||
self._data = None
|
||||
|
||||
def pre_parse_line(self, line):
|
||||
if not isinstance(line, basestring):
|
||||
return line
|
||||
elif line.startswith('---'):
|
||||
return None
|
||||
elif line == '' or line == '\n':
|
||||
if line == '\n':
|
||||
logging.debug('Empty Newline detected.')
|
||||
return None
|
||||
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()
|
||||
return g
|
||||
else:
|
||||
return line
|
||||
return None
|
||||
|
||||
def _parse_line(self, line):
|
||||
# add the line to my lines.
|
||||
if line is not None:
|
||||
o = line
|
||||
if isinstance(line, basestring):
|
||||
# Unknown Log?
|
||||
if not line:
|
||||
return
|
||||
# It might be a stacktrace. inject it./
|
||||
if Stacktrace.is_handler(o):
|
||||
o = Stacktrace(o)
|
||||
self._last_object = o
|
||||
else:
|
||||
#if isinstance(self._last_object, Stacktrace) and line.startswith('\t'):
|
||||
# logging.debug('Workaround: %s, worked: %s' % (line, self._last_object.append(line)))
|
||||
# return
|
||||
if self._last_object is not None:
|
||||
self._last_object.unpack()
|
||||
if self._last_object.append(line):
|
||||
return
|
||||
logging.debug('#: %s' % line)
|
||||
o = None
|
||||
elif isinstance(line, dict):
|
||||
# Unresolved Log.
|
||||
o = self.resolve(line)
|
||||
self._last_object = o
|
||||
else:
|
||||
self._last_object = o
|
||||
if o is None:
|
||||
self._last_object = None
|
||||
return
|
||||
self.lines.append(o)
|
||||
|
||||
def parse_line(self, line):
|
||||
return self._parse_line(self.pre_parse_line(line))
|
||||
|
||||
def resolve(self, gd):
|
||||
# gd is a dict.
|
||||
# try to find a class that is responsible for this log.
|
||||
return gd
|
209
src/scon/logs/session.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""
|
||||
Logging Session.
|
||||
"""
|
||||
import zipfile, logging, os
|
||||
from logfiles import CombatLogFile, GameLogFile, ChatLogFile
|
||||
|
||||
class LogSession(object):
|
||||
"""
|
||||
A basic logsession.
|
||||
deal with data as it comes along, and output interpretable data for the outside world.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
class LogFileSession(LogSession):
|
||||
"""
|
||||
The Log-File-Session is supposed to save one directory of logs.
|
||||
It can parse its logs, and should become able to build up its internal
|
||||
structure into Battle Instances etc.
|
||||
"""
|
||||
VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs.
|
||||
|
||||
def __init__(self, directory):
|
||||
''' if directory is a file, it will be handled as a compressed folder '''
|
||||
self.battles = []
|
||||
self.user = None
|
||||
self.files_parsed = []
|
||||
|
||||
# various logfiles used.
|
||||
self.combat_log = None
|
||||
self.game_log = None
|
||||
self.chat_log = None
|
||||
# self.net_log = None
|
||||
|
||||
self.directory = directory
|
||||
self._zip_source = None
|
||||
self.idstr = None # id string to identify this log instance.
|
||||
self._error = False
|
||||
|
||||
def clean(self, remove_log=True):
|
||||
if self.combat_log:
|
||||
self.combat_log.clean(remove_log)
|
||||
if self.game_log:
|
||||
self.game_log.clean(remove_log)
|
||||
if self.chat_log:
|
||||
self.chat_log.clean(remove_log)
|
||||
|
||||
|
||||
def validate(self, contents=False):
|
||||
"""
|
||||
- validates if the logfiles are within this package.
|
||||
- sets the idstr of this object.
|
||||
@todo: in-depth validation of logs, on contents=True.
|
||||
"""
|
||||
self._zip_source = os.path.isfile(self.directory) or False
|
||||
v = False
|
||||
try:
|
||||
if self._zip_source:
|
||||
v = self._unzip_validate()
|
||||
if v > 0:
|
||||
self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower()
|
||||
else:
|
||||
v = self._validate_files_exist()
|
||||
if v > 0:
|
||||
self.idstr = os.path.split(self.directory)[1].lower()
|
||||
except:
|
||||
return False
|
||||
return v
|
||||
|
||||
def parse_files(self, files=None):
|
||||
''' parses the logfiles '''
|
||||
# perform simple validation.
|
||||
if self._zip_source is None:
|
||||
self.validate(False)
|
||||
if self._zip_source:
|
||||
self._unzip_logs(files)
|
||||
else:
|
||||
if files is None:
|
||||
files = self.VALID_FILES
|
||||
if 'combat.log' in files and not 'combat.log' in self.files_parsed:
|
||||
self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log'))
|
||||
self.combat_log.read()
|
||||
self.combat_log.parse()
|
||||
self.files_parsed.append('combat.log')
|
||||
if 'game.log' in files and not 'game.log' in self.files_parsed:
|
||||
self.game_log = GameLogFile(os.path.join(self.directory, 'game.log'))
|
||||
self.game_log.read()
|
||||
self.game_log.parse()
|
||||
self.files_parsed.append('game.log')
|
||||
if 'chat.log' in files and not 'chat.log' in self.files_parsed:
|
||||
self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log'))
|
||||
self.chat_log.read()
|
||||
self.chat_log.parse()
|
||||
self.files_parsed.append('chat.log')
|
||||
|
||||
def determine_owner(self):
|
||||
''' determines the user in the parsed gamelog '''
|
||||
pass
|
||||
|
||||
def parse_battles(self):
|
||||
''' parses the battles '''
|
||||
pass
|
||||
|
||||
def _unzip_logs(self, files=None):
|
||||
z = zipfile.ZipFile(self.directory, "r")
|
||||
try:
|
||||
for filename in z.namelist():
|
||||
fn = os.path.split(filename)[1] or ''
|
||||
fn = fn.lower()
|
||||
if fn:
|
||||
if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed:
|
||||
self.combat_log = CombatLogFile(fn)
|
||||
self.combat_log.set_data(z.read(filename))
|
||||
self.combat_log.parse()
|
||||
self.files_parsed.append('combat.log')
|
||||
elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed:
|
||||
self.game_log = GameLogFile(fn)
|
||||
self.game_log.set_data(z.read(filename))
|
||||
self.game_log.parse()
|
||||
self.files_parsed.append('game.log')
|
||||
elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed:
|
||||
self.chat_log = ChatLogFile(fn)
|
||||
self.chat_log.set_data(z.read(filename))
|
||||
self.chat_log.parse()
|
||||
self.files_parsed.append('chat.log')
|
||||
except:
|
||||
self._error = True
|
||||
return
|
||||
finally:
|
||||
z.close()
|
||||
|
||||
def _unzip_validate(self):
|
||||
z = zipfile.ZipFile(self.directory, "r")
|
||||
found = 0
|
||||
for filename in z.namelist():
|
||||
fn = os.path.split(filename)[1] or ''
|
||||
fn = fn.lower()
|
||||
if fn and fn in self.VALID_FILES:
|
||||
found += 1
|
||||
z.close()
|
||||
return found
|
||||
|
||||
def _validate_files_exist(self):
|
||||
found = 0
|
||||
for f in self.VALID_FILES:
|
||||
if os.path.exists(os.path.join(self.directory, f)):
|
||||
found += 1
|
||||
return found
|
||||
|
||||
class LogSessionCollector(object):
|
||||
"""
|
||||
finds sessions in a directory, a.k.a. you load the log directories
|
||||
of SC into sessions.
|
||||
|
||||
- find_sessions: only find and instantiate sessions.
|
||||
- collect: validates each found session and returns them as list.
|
||||
- collect_unique: instead of a list, a dict is returned, where each
|
||||
session can be accessed via its idstr. does not parse/validate sessions.
|
||||
"""
|
||||
def __init__(self, directory):
|
||||
self.initial_directory = directory
|
||||
self.sessions = []
|
||||
self.find_sessions()
|
||||
|
||||
def find_sessions(self):
|
||||
for f in os.listdir(self.initial_directory):
|
||||
full_dir = os.path.join(self.initial_directory, f)
|
||||
if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'):
|
||||
self.sessions.append(LogFileSession(full_dir))
|
||||
|
||||
def collect(self):
|
||||
sessions = []
|
||||
for session in self.sessions:
|
||||
try:
|
||||
if session.validate():
|
||||
sessions.append(session)
|
||||
except:
|
||||
continue
|
||||
return sessions
|
||||
|
||||
def collect_unique(self):
|
||||
''' collects all sessions into a dictionary ordered by their idstr.
|
||||
sessions without idstr, or already existing (first served) are omitted
|
||||
parsing is not done.
|
||||
'''
|
||||
# note this function resets sessions to the working ones.
|
||||
self.sessions = self.collect()
|
||||
sessions_dict = {}
|
||||
for session in self.sessions:
|
||||
if session.idstr and not session.idstr in sessions_dict.keys():
|
||||
sessions_dict[session.idstr] = session
|
||||
return sessions_dict
|
||||
|
||||
def clean(self, remove_log=True):
|
||||
for session in self.sessions:
|
||||
session.clean(remove_log)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
|
||||
l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
|
||||
|
||||
l_zip.parse_files()
|
||||
print l_zip.combat_log.lines
|
||||
|
||||
collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\')
|
||||
print collector.collect_unique()
|
10
src/scon/manage.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
161
src/scon/monitor.py
Normal file
@ -0,0 +1,161 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Monitor a StarConflict Log Directory.
|
||||
"""
|
||||
import sys, os
|
||||
import time
|
||||
import logging
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import LoggingEventHandler
|
||||
|
||||
class SconEventHandler(LoggingEventHandler):
|
||||
def __init__(self, monitor, *args, **kwargs):
|
||||
self.monitor = monitor
|
||||
return super(SconEventHandler, self).__init__(*args, **kwargs)
|
||||
|
||||
def on_moved(self, event):
|
||||
super(SconEventHandler, self).on_moved(event)
|
||||
if not event.is_directory:
|
||||
self.monitor.close(event.src_path)
|
||||
self.monitor.notify_event('moved', {'src': event.src_path,
|
||||
'is_dir': event.is_directory,
|
||||
'dest': event.dest_path})
|
||||
|
||||
def on_created(self, event):
|
||||
super(SconEventHandler, self).on_created(event)
|
||||
if not event.is_directory:
|
||||
self.monitor.open(event.src_path)
|
||||
self.monitor.notify_event('created', {'src': event.src_path,
|
||||
'is_dir': event.is_directory})
|
||||
|
||||
def on_deleted(self, event):
|
||||
super(SconEventHandler, self).on_deleted(event)
|
||||
if not event.is_directory:
|
||||
self.monitor.close(event.src_path)
|
||||
self.monitor.notify_event('deleted', {'src': event.src_path,
|
||||
'is_dir': event.is_directory})
|
||||
|
||||
def on_modified(self, event):
|
||||
super(SconEventHandler, self).on_modified(event)
|
||||
self.monitor.notify_event('modified', {'src': event.src_path,
|
||||
'is_dir': event.is_directory})
|
||||
|
||||
|
||||
|
||||
class SconMonitor(object):
|
||||
|
||||
def notify(self, filename, lines):
|
||||
# notify somebody, filename has a few lines.
|
||||
# this is basicly the function you want to overwrite.
|
||||
# @see: self.run for how to integrate monitor into your main loop.
|
||||
if self.notifier is not None:
|
||||
self.notifier.notify(filename, lines)
|
||||
|
||||
def notify_event(self, event_type, data):
|
||||
if self.notifier is not None:
|
||||
self.notifier.notify_event(event_type, data)
|
||||
|
||||
def __init__(self, path=None, notifier=None):
|
||||
# if you initialize path directly, you lose success boolean.
|
||||
# albeit atm. this is always true.
|
||||
if path is not None:
|
||||
self.initialize(path)
|
||||
self.notifier = notifier
|
||||
|
||||
def initialize(self, path):
|
||||
# initialize the monitor with a path to observe.
|
||||
self.files = {}
|
||||
self.event_handler = SconEventHandler(self)
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(self.event_handler,
|
||||
path,
|
||||
recursive=True)
|
||||
return True
|
||||
|
||||
def open(self, filename=None):
|
||||
# open a logfile and add it to the read-list...
|
||||
f = open(filename, 'r')
|
||||
new_file = { 'file': f,
|
||||
'cursor': f.tell() }
|
||||
self.files[filename] = new_file
|
||||
return new_file
|
||||
|
||||
def close(self, filename):
|
||||
# close a single file by key, does not do anything if not found.
|
||||
if filename in self.files.keys():
|
||||
close_file = self.files.pop(filename)
|
||||
close_file['file'].close()
|
||||
del close_file
|
||||
|
||||
def close_all(self):
|
||||
""" closes all open files in the monitor """
|
||||
for key in self.files.keys():
|
||||
self.close(key)
|
||||
|
||||
def read_line(self, afile):
|
||||
# read a single line in a file.
|
||||
f = afile.get('file', None)
|
||||
if f is None:
|
||||
return
|
||||
afile['cursor'] = f.tell()
|
||||
line = f.readline()
|
||||
if not line:
|
||||
f.seek(afile['cursor'])
|
||||
return None
|
||||
else:
|
||||
return line
|
||||
|
||||
def do(self):
|
||||
''' Monitor main task handler, call this in your mainloop in ~1 sec intervals '''
|
||||
# read all file changes.
|
||||
for key, value in self.files.items():
|
||||
lines = []
|
||||
data = self.read_line(value)
|
||||
while data is not None:
|
||||
lines.append(data)
|
||||
data = self.read_line(value)
|
||||
if lines:
|
||||
self.notify(key, lines)
|
||||
#
|
||||
|
||||
def initialize_loop(self):
|
||||
''' initializes the main loop for the monitor '''
|
||||
self.observer.start()
|
||||
|
||||
def break_loop(self):
|
||||
''' call this if you want to break the monitors tasks '''
|
||||
self.observer.stop()
|
||||
|
||||
def end_loop(self):
|
||||
''' always call this before exiting your main loop '''
|
||||
self.close_all()
|
||||
self.observer.join()
|
||||
|
||||
def run(self):
|
||||
''' Basic Standalone Main Loop implementation, do not call this in your app. '''
|
||||
# if you want to run this on its own.
|
||||
# everytime any logfile is updated, print it.
|
||||
self.initialize_loop()
|
||||
try:
|
||||
while True:
|
||||
self.do()
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
self.break_loop()
|
||||
self.end_loop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
monitor = SconMonitor()
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
path = os.path.join(os.path.expanduser('~'),
|
||||
'Documents',
|
||||
'My Games',
|
||||
'StarConflict',
|
||||
'logs'
|
||||
)
|
||||
if monitor.initialize(path) is True:
|
||||
monitor.run()
|
||||
|
103
src/scon/qscon.py
Normal file
@ -0,0 +1,103 @@
|
||||
"""
|
||||
Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files.
|
||||
|
||||
"""
|
||||
import os, sys, logging
|
||||
import sys
|
||||
import urllib2
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from monitor import SconMonitor
|
||||
from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
|
||||
|
||||
class SconMonitorThread(QtCore.QThread):
|
||||
updated = pyqtSignal(str, list)
|
||||
created = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, path):
|
||||
QtCore.QThread.__init__(self)
|
||||
self.path = path
|
||||
|
||||
def notify(self, filename, lines):
|
||||
self.updated.emit(filename, lines)
|
||||
#self.mainwindow.notify_filelines(filename, lines)
|
||||
#self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines)))
|
||||
|
||||
def notify_event(self, event_type, data):
|
||||
if event_type == 'created':
|
||||
self.created.emit(data['src'], data['is_dir'])
|
||||
|
||||
def run(self):
|
||||
monitor = SconMonitor(self.path, notifier=self)
|
||||
#self.list_widget.addItem('Starting to monitor: %s' % self.path)
|
||||
monitor.run()
|
||||
|
||||
class MainWindow(QtGui.QWidget):
|
||||
def __init__(self):
|
||||
super(MainWindow, self).__init__()
|
||||
self.tab_list = QtGui.QTabWidget()
|
||||
self.tabs = {}
|
||||
self.button = QtGui.QPushButton("Start")
|
||||
self.button.clicked.connect(self.start_monitor)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.button)
|
||||
layout.addWidget(self.tab_list)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def notify_filelines(self, filename, lines):
|
||||
if filename not in self.tabs.keys():
|
||||
new_tab = QtGui.QWidget()
|
||||
new_tab.list_widget = QtGui.QListWidget()
|
||||
layout = QtGui.QVBoxLayout(new_tab)
|
||||
layout.addWidget(new_tab.list_widget)
|
||||
self.tabs[filename] = new_tab
|
||||
self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1])
|
||||
self.tabs[filename].list_widget.addItem(''.join(lines)[:-1])
|
||||
|
||||
def notify_created(self, filename, is_directory):
|
||||
if is_directory:
|
||||
print "Created Directory %s" % filename
|
||||
else:
|
||||
print "Created File %s" % filename
|
||||
|
||||
def start_monitor(self):
|
||||
self.button.setDisabled(True)
|
||||
paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'),
|
||||
]
|
||||
self.threads = []
|
||||
for path in paths:
|
||||
athread = SconMonitorThread(path)
|
||||
athread.updated.connect(self.notify_filelines)
|
||||
athread.created.connect(self.notify_created)
|
||||
self.threads.append(athread)
|
||||
athread.start()
|
||||
|
||||
########################################################################
|
||||
|
||||
def _main():
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.resize(640, 480)
|
||||
window.show()
|
||||
return app.exec_()
|
||||
|
||||
def main():
|
||||
r = _main()
|
||||
try:
|
||||
import psutil #@UnresolvedImport
|
||||
except ImportError:
|
||||
logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.')
|
||||
sys.exit(r)
|
||||
|
||||
def kill_proc_tree(pid, including_parent=True):
|
||||
parent = psutil.Process(pid)
|
||||
if including_parent:
|
||||
parent.kill()
|
||||
me = os.getpid()
|
||||
kill_proc_tree(me)
|
||||
sys.exit(r)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
0
src/scon/test/__init__.py
Normal file
25
src/scon/test/regexes.py
Normal 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()
|
0
src/scon/utils/__init__.py
Normal file
28
src/scon/utils/steam.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
X:Y:Z
|
||||
|
||||
X - Universe
|
||||
0 Individual / Unspecified
|
||||
1 Public
|
||||
2 Beta
|
||||
3 Internal
|
||||
4 Dev
|
||||
5 RC
|
||||
|
||||
Y - Steam Type
|
||||
0 I Invalid No
|
||||
1 U Individual Yes profiles / id 0x0110000100000000
|
||||
2 M Multiseat Yes
|
||||
3 G GameServer Yes
|
||||
4 A AnonGameServer Yes
|
||||
5 P Pending No
|
||||
6 C ContentServer Unknown
|
||||
7 g Clan Yes groups / gid 0x0170000000000000
|
||||
8 c, L, T Chat Yes
|
||||
9 P2P SuperSeeder No
|
||||
10 AnonUser No
|
||||
|
||||
Z - account or group uid
|
||||
|
||||
|
||||
"""
|