This commit is contained in:
Gabor Körber 2017-04-14 19:02:39 +02:00
parent bb880585ed
commit 74263690b3
100 changed files with 5340 additions and 0 deletions

0
src/scon/__init__.py Normal file
View File

90
src/scon/analyze.py Normal file
View 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
View 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
"""

View File

View 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
View 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
View 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
View 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

View File

@ -0,0 +1,4 @@
"""
Handle SCon's config file.
"""

View File

@ -0,0 +1,5 @@
"""
"""

View 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()

View 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.

View 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()

View File

@ -0,0 +1,3 @@
"""
"""

View 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
View 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>
'''

View 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
View File

View File

@ -0,0 +1,3 @@
this module will be reimplented as "djascon" for now.
better name pending.

View File

View 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)

File diff suppressed because one or more lines are too long

10
src/scon/dj/scon/forms.py Normal file
View 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):

View 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
View File

@ -0,0 +1,10 @@
'''
'''
def config(condict):
# modify condict.
return condict
def overview(condict):
return condict

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

148
src/scon/dj/scon/models.py Normal file
View 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)

View File

@ -0,0 +1 @@
Site Not Found.

View File

@ -0,0 +1 @@
Internal Server Error.

View 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>

View 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 %}

View 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 %}

View 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]

View 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 %}

View 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 %}

View 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">&nbsp;
<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">&nbsp;</span>
{% endif %}
{% endfor %}
{% endblock context %}

View File

@ -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;">&nbsp;</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">&nbsp;</span></td></tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endblock context %}

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

32
src/scon/dj/scon/views.py Normal file
View 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
View 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
View 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
View 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()

View File

77
src/scon/game/battle.py Normal file
View 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
View File

100
src/scon/game/pieces.py Normal file
View 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
View 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

View File

0
src/scon/gui/__init__.py Normal file
View File

77
src/scon/gui/qbrowser.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()

View File

25
src/scon/test/regexes.py Normal file
View File

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

View File

28
src/scon/utils/steam.py Normal file
View 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
"""