diff --git a/src/scon/__init__.py b/src/scon/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/analyze.py b/src/scon/analyze.py
new file mode 100644
index 0000000..43f92ed
--- /dev/null
+++ b/src/scon/analyze.py
@@ -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
diff --git a/src/scon/app.py b/src/scon/app.py
new file mode 100644
index 0000000..db66202
--- /dev/null
+++ b/src/scon/app.py
@@ -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
+
+
+"""
\ No newline at end of file
diff --git a/src/scon/archive/__init__.py b/src/scon/archive/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/archive/localbrowser.py b/src/scon/archive/localbrowser.py
new file mode 100644
index 0000000..31a995b
--- /dev/null
+++ b/src/scon/archive/localbrowser.py
@@ -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 '''
+
+
Test
+
+
+ '''
+
+ 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 '''
+
+ No Network Access.
+
+ Internal access to the network has been disabled.
+
+
+ '''
+
+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()
+
\ No newline at end of file
diff --git a/src/scon/backup.py b/src/scon/backup.py
new file mode 100644
index 0000000..29bb191
--- /dev/null
+++ b/src/scon/backup.py
@@ -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)
+
diff --git a/src/scon/battle.py b/src/scon/battle.py
new file mode 100644
index 0000000..8b81c05
--- /dev/null
+++ b/src/scon/battle.py
@@ -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)
\ No newline at end of file
diff --git a/src/scon/brainstorm.py b/src/scon/brainstorm.py
new file mode 100644
index 0000000..df6fcda
--- /dev/null
+++ b/src/scon/brainstorm.py
@@ -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
\ No newline at end of file
diff --git a/src/scon/config/__init__.py b/src/scon/config/__init__.py
new file mode 100644
index 0000000..1f3341d
--- /dev/null
+++ b/src/scon/config/__init__.py
@@ -0,0 +1,4 @@
+"""
+ Handle SCon's config file.
+
+"""
\ No newline at end of file
diff --git a/src/scon/config/common.py b/src/scon/config/common.py
new file mode 100644
index 0000000..10f6bdf
--- /dev/null
+++ b/src/scon/config/common.py
@@ -0,0 +1,5 @@
+"""
+
+
+
+"""
\ No newline at end of file
diff --git a/src/scon/config/display_config.py b/src/scon/config/display_config.py
new file mode 100644
index 0000000..b26c4b4
--- /dev/null
+++ b/src/scon/config/display_config.py
@@ -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 = '\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()
+
\ No newline at end of file
diff --git a/src/scon/config/readme.txt b/src/scon/config/readme.txt
new file mode 100644
index 0000000..c04afa6
--- /dev/null
+++ b/src/scon/config/readme.txt
@@ -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.
\ No newline at end of file
diff --git a/src/scon/config/settings.py b/src/scon/config/settings.py
new file mode 100644
index 0000000..62984b2
--- /dev/null
+++ b/src/scon/config/settings.py
@@ -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()
diff --git a/src/scon/dejaqt/__init__.py b/src/scon/dejaqt/__init__.py
new file mode 100644
index 0000000..f2a3fcb
--- /dev/null
+++ b/src/scon/dejaqt/__init__.py
@@ -0,0 +1,3 @@
+"""
+
+"""
\ No newline at end of file
diff --git a/src/scon/dejaqt/folders.py b/src/scon/dejaqt/folders.py
new file mode 100644
index 0000000..bbc5d7f
--- /dev/null
+++ b/src/scon/dejaqt/folders.py
@@ -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')
+
\ No newline at end of file
diff --git a/src/scon/dejaqt/qweb.py b/src/scon/dejaqt/qweb.py
new file mode 100644
index 0000000..2d1f29d
--- /dev/null
+++ b/src/scon/dejaqt/qweb.py
@@ -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 '''
+
+ Empty Page
+ This is an empty page. If you see this, you need to subclass BasePageReply.
+
+ '''
+
+ 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 '''
+
+ No Network Access.
+
+ Internal access to the network has been disabled.
+
+
+ '''
+
diff --git a/src/scon/dejaqt/readme.txt b/src/scon/dejaqt/readme.txt
new file mode 100644
index 0000000..e7f1df3
--- /dev/null
+++ b/src/scon/dejaqt/readme.txt
@@ -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
\ No newline at end of file
diff --git a/src/scon/dj/__init__.py b/src/scon/dj/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/dj/deprecated.txt b/src/scon/dj/deprecated.txt
new file mode 100644
index 0000000..f0140cf
--- /dev/null
+++ b/src/scon/dj/deprecated.txt
@@ -0,0 +1,3 @@
+this module will be reimplented as "djascon" for now.
+
+better name pending.
diff --git a/src/scon/dj/scon/__init__.py b/src/scon/dj/scon/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/dj/scon/admin.py b/src/scon/dj/scon/admin.py
new file mode 100644
index 0000000..1c94f0c
--- /dev/null
+++ b/src/scon/dj/scon/admin.py
@@ -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)
diff --git a/src/scon/dj/scon/fixtures/generated.json b/src/scon/dj/scon/fixtures/generated.json
new file mode 100644
index 0000000..14d3902
--- /dev/null
+++ b/src/scon/dj/scon/fixtures/generated.json
@@ -0,0 +1 @@
+[{"fields": {"sell_price": 6600, "tech": 0, "name": "Impure tungsten", "craftable": true, "typ": 12, "icon": "resource_tungsten_ore"}, "model": "scon.item", "pk": 1}, {"fields": {"sell_price": 4500, "tech": 0, "name": "Impure osmium", "craftable": true, "typ": 12, "icon": "resource_osmium_ore"}, "model": "scon.item", "pk": 2}, {"fields": {"sell_price": 600, "tech": 0, "name": "Impure silicon", "craftable": true, "typ": 12, "icon": "resource_silicon_ore"}, "model": "scon.item", "pk": 3}, {"fields": {"sell_price": 500, "tech": 0, "name": "Vanadium", "craftable": true, "typ": 12, "icon": "resource_vanadium"}, "model": "scon.item", "pk": 4}, {"fields": {"sell_price": 3500, "tech": 0, "name": "Crystal shard", "craftable": true, "typ": 12, "icon": "resource_crystal_shard"}, "model": "scon.item", "pk": 5}, {"fields": {"sell_price": 20000, "tech": 0, "name": "Tungsten plate", "description": "Durable tungsten plate", "craftable": true, "typ": 13, "icon": "component_tungsten_plate"}, "model": "scon.item", "pk": 6}, {"fields": {"sell_price": 42000, "tech": 0, "name": "Screened battery", "craftable": true, "typ": 13, "icon": "component_screened_battery"}, "model": "scon.item", "pk": 7}, {"fields": {"sell_price": 5500, "tech": 0, "name": "Osmium crystals", "craftable": true, "typ": 13, "icon": "component_osmium_crystals"}, "model": "scon.item", "pk": 8}, {"fields": {"sell_price": 2500, "tech": 0, "name": "Pure Silicon", "craftable": true, "typ": 13, "icon": "component_pure_silicon"}, "model": "scon.item", "pk": 9}, {"fields": {"sell_price": 22000, "tech": 0, "name": "Processing block", "craftable": true, "typ": 13, "icon": "component_processing_block"}, "model": "scon.item", "pk": 10}, {"fields": {"sell_price": 1600, "tech": 0, "name": "Metal blank", "craftable": true, "typ": 13, "icon": "component_metal_blank"}, "model": "scon.item", "pk": 11}, {"fields": {"sell_price": 25000, "tech": 0, "name": "Alien Monocrystal", "craftable": true, "typ": 13, "icon": "component_alien_monocrystal"}, "model": "scon.item", "pk": 12}, {"fields": {"sell_price": 4500, "tech": 0, "name": "Computing chip", "craftable": true, "typ": 13, "icon": "component_computing_chip"}, "model": "scon.item", "pk": 13}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Explosive Shells", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_explosive_shells_mk4"}, "model": "scon.item", "pk": 14}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Double Deflector", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_double_deflector_mk4"}, "model": "scon.item", "pk": 15}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Xenon Lamp", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_xenon_lamp_mk4"}, "model": "scon.item", "pk": 16}, {"fields": {"sell_price": 1092, "tech": 0, "name": "Attack Drone", "quality": 10, "craftable": true, "typ": 8, "icon": "ammo_attack_drone"}, "model": "scon.item", "pk": 17}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Focusing Lens", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_focusing_lens"}, "model": "scon.item", "pk": 18}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Iridium Slugs", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_iridium_slugs"}, "model": "scon.item", "pk": 19}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Supercooled Charges", "quality": 4, "craftable": true, "typ": 8, "icon": "ammo_supercooled_charges"}, "model": "scon.item", "pk": 20}, {"fields": {"sell_price": 1000, "tech": 0, "name": "Doomsday Missile", "quality": 1, "craftable": true, "typ": 8, "icon": "ammo_doomsday_missile"}, "model": "scon.item", "pk": 21}, {"fields": {"sell_price": 8000, "buy_price_premium": 200, "name": "Duplicator", "icon": "duplicator", "craftable": true, "typ": 0, "description": "Revives in Invasion with Cargo once."}, "model": "scon.item", "pk": 22}, {"fields": {"name": "A1MA IV", "typ": 8, "craftable": true, "sell_price": 26910, "tech": 4, "role": 0, "quality": 1, "icon": "active_a1ma"}, "model": "scon.item", "pk": 23}, {"fields": {"tech": 5, "name": "Pirate \"Orion\" Targeting Complex V", "role": 3, "craftable": true, "quality": 14, "typ": 8, "icon": "active_t5_orion_targeting_complex_pirate"}, "model": "scon.item", "pk": 24}, {"fields": {"tech": 5, "name": "Pirate Engine Overcharge V", "role": 6, "craftable": true, "quality": 14, "typ": 8, "icon": "active_t5_engine_overcharge_pirate"}, "model": "scon.item", "pk": 25}, {"fields": {"tech": 5, "name": "Pirate Mass Shield Generator V", "role": 7, "craftable": true, "quality": 14, "typ": 8, "icon": "active_t5_mass_shield_generator_pirate"}, "model": "scon.item", "pk": 26}, {"fields": {"tech": 3, "name": "Reverse Thruster III", "role": 9, "craftable": true, "quality": 1, "typ": 8, "icon": "active_reverse_thruster"}, "model": "scon.item", "pk": 27}, {"fields": {"tech": 4, "name": "Reverse Thruster IV", "role": 9, "craftable": true, "quality": 1, "typ": 8, "icon": "active_reverse_thruster"}, "model": "scon.item", "pk": 28}, {"fields": {"tech": 5, "name": "Reverse Thruster V", "role": 9, "craftable": true, "quality": 1, "typ": 8, "icon": "active_reverse_thruster"}, "model": "scon.item", "pk": 29}, {"fields": {"tech": 3, "name": "Plasma Gun III", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_plasma_gun_mk5"}, "model": "scon.item", "pk": 30}, {"fields": {"tech": 4, "name": "Plasma Gun IV", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_plasma_gun_mk5"}, "model": "scon.item", "pk": 31}, {"fields": {"tech": 5, "name": "Plasma Gun V", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_plasma_gun_mk5"}, "model": "scon.item", "pk": 32}, {"fields": {"tech": 3, "name": "Assault Railgun III", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_assault_rail_mk5"}, "model": "scon.item", "pk": 33}, {"fields": {"tech": 4, "name": "Assault Railgun IV", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_assault_rail_mk5"}, "model": "scon.item", "pk": 34}, {"fields": {"tech": 5, "name": "Assault Railgun V", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_assault_rail_mk5"}, "model": "scon.item", "pk": 35}, {"fields": {"tech": 3, "name": "Beam Cannon III", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_beam_cannon_mk5"}, "model": "scon.item", "pk": 36}, {"fields": {"tech": 4, "name": "Beam Cannon IV", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_beam_cannon_mk5"}, "model": "scon.item", "pk": 37}, {"fields": {"tech": 5, "name": "Beam Cannon V", "quality": 5, "craftable": true, "typ": 7, "icon": "weapon_beam_cannon_mk5"}, "model": "scon.item", "pk": 38}, {"fields": {"sell_price": 20188, "tech": 3, "name": "Target Tracking Coprocessor III", "icon": "cpu_target_tracking_coprocessor", "craftable": false, "typ": 5, "description": "Increases Critical Damage"}, "model": "scon.item", "pk": 39}, {"fields": {"tech": 3, "name": "Plasma Gun III", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_plasma_gun"}, "model": "scon.item", "pk": 40}, {"fields": {"tech": 4, "name": "Plasma Gun IV", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_plasma_gun"}, "model": "scon.item", "pk": 41}, {"fields": {"tech": 5, "name": "Plasma Gun V", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_plasma_gun"}, "model": "scon.item", "pk": 42}, {"fields": {"tech": 3, "name": "Assault Railgun III", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_assault_railgun"}, "model": "scon.item", "pk": 43}, {"fields": {"tech": 4, "name": "Assault Railgun IV", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_assault_railgun"}, "model": "scon.item", "pk": 44}, {"fields": {"tech": 5, "name": "Assault Railgun V", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_assault_railgun"}, "model": "scon.item", "pk": 45}, {"fields": {"tech": 3, "name": "Beam Cannon III", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_beam_cannon"}, "model": "scon.item", "pk": 46}, {"fields": {"tech": 4, "name": "Beam Cannon IV", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_beam_cannon"}, "model": "scon.item", "pk": 47}, {"fields": {"tech": 5, "name": "Beam Cannon V", "typ": 7, "craftable": false, "quality": 4, "icon": "weapon_beam_cannon"}, "model": "scon.item", "pk": 48}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Focusing Lens Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 49}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Iridium Slugs Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 50}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Supercooled Charges Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 51}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "A1MA T4 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 52}, {"fields": {"tech": 0, "name": "Orion-2 Targeting Complex Blueprint", "description": "Module works twice as long but much weaker.", "craftable": true, "typ": 11, "icon": "blueprint"}, "model": "scon.item", "pk": 53}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Engine Warp Overcharge Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 54}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Mass Shield Energizer Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 55}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Reverse Thruster T3 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 56}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Reverse Thruster T4 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 57}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Reverse Thruster T5 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 58}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Beam Cannon Prototype T3 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 59}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Beam Cannon Prototype T4 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 60}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Beam Cannon Prototype T5 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 61}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Assault Railgun Prototype T3 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 62}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Assault Railgun Prototype T4 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 63}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Assault Railgun Prototype T5 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 64}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Plasma Gun Prototype T3 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 65}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Plasma Gun Prototype T4 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 66}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Plasma Gun Prototype T5 Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 67}, {"fields": {"tech": 0, "craftable": true, "typ": 11, "name": "Doomsday Missile Blueprint", "icon": "blueprint"}, "model": "scon.item", "pk": 68}, {"pk": 1, "model": "scon.crafting", "fields": {"output": 22, "amount": 1}}, {"pk": 1, "model": "scon.craftinginput", "fields": {"item": 10, "crafting": 1, "amount": 1, "primary": true}}, {"pk": 2, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 1, "amount": 2, "primary": false}}, {"pk": 3, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 1, "amount": 2, "primary": false}}, {"pk": 2, "model": "scon.crafting", "fields": {"output": 6, "amount": 1}}, {"pk": 4, "model": "scon.craftinginput", "fields": {"item": 1, "crafting": 2, "amount": 2, "primary": true}}, {"pk": 3, "model": "scon.crafting", "fields": {"output": 7, "amount": 1}}, {"pk": 5, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 3, "amount": 1, "primary": true}}, {"pk": 6, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 3, "amount": 2, "primary": false}}, {"pk": 4, "model": "scon.crafting", "fields": {"output": 8, "amount": 1}}, {"pk": 7, "model": "scon.craftinginput", "fields": {"item": 2, "crafting": 4, "amount": 1, "primary": true}}, {"pk": 5, "model": "scon.crafting", "fields": {"output": 9, "amount": 1}}, {"pk": 8, "model": "scon.craftinginput", "fields": {"item": 3, "crafting": 5, "amount": 1, "primary": true}}, {"pk": 6, "model": "scon.crafting", "fields": {"output": 13, "amount": 1}}, {"pk": 9, "model": "scon.craftinginput", "fields": {"item": 5, "crafting": 6, "amount": 1, "primary": true}}, {"pk": 7, "model": "scon.crafting", "fields": {"output": 10, "amount": 1}}, {"pk": 10, "model": "scon.craftinginput", "fields": {"item": 9, "crafting": 7, "amount": 4, "primary": true}}, {"pk": 11, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 7, "amount": 2, "primary": false}}, {"pk": 8, "model": "scon.crafting", "fields": {"output": 11, "amount": 1}}, {"pk": 12, "model": "scon.craftinginput", "fields": {"item": 4, "crafting": 8, "amount": 2, "primary": true}}, {"pk": 9, "model": "scon.crafting", "fields": {"output": 39, "amount": 1}}, {"pk": 13, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 9, "amount": 1, "primary": true}}, {"pk": 14, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 9, "amount": 7, "primary": false}}, {"pk": 15, "model": "scon.craftinginput", "fields": {"item": 9, "crafting": 9, "amount": 5, "primary": false}}, {"pk": 10, "model": "scon.crafting", "fields": {"output": 14, "amount": 2}}, {"pk": 16, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 10, "amount": 1, "primary": true}}, {"pk": 17, "model": "scon.craftinginput", "fields": {"item": 9, "crafting": 10, "amount": 2, "primary": false}}, {"pk": 11, "model": "scon.crafting", "fields": {"output": 17, "amount": 1}}, {"pk": 18, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 11, "amount": 1, "primary": true}}, {"pk": 19, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 11, "amount": 1, "primary": false}}, {"pk": 12, "model": "scon.crafting", "fields": {"output": 15, "amount": 2}}, {"pk": 20, "model": "scon.craftinginput", "fields": {"item": 8, "crafting": 12, "amount": 1, "primary": true}}, {"pk": 13, "model": "scon.crafting", "fields": {"output": 16, "amount": 2}}, {"pk": 21, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 13, "amount": 1, "primary": true}}, {"pk": 22, "model": "scon.craftinginput", "fields": {"item": 9, "crafting": 13, "amount": 1, "primary": false}}, {"pk": 14, "model": "scon.crafting", "fields": {"output": 18, "amount": 2}}, {"pk": 23, "model": "scon.craftinginput", "fields": {"item": 49, "crafting": 14, "amount": 1, "primary": true}}, {"pk": 24, "model": "scon.craftinginput", "fields": {"item": 8, "crafting": 14, "amount": 1, "primary": false}}, {"pk": 15, "model": "scon.crafting", "fields": {"output": 20, "amount": 2}}, {"pk": 25, "model": "scon.craftinginput", "fields": {"item": 51, "crafting": 15, "amount": 1, "primary": true}}, {"pk": 26, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 15, "amount": 1, "primary": false}}, {"pk": 16, "model": "scon.crafting", "fields": {"output": 19, "amount": 2}}, {"pk": 27, "model": "scon.craftinginput", "fields": {"item": 50, "crafting": 16, "amount": 1, "primary": true}}, {"pk": 28, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 16, "amount": 1, "primary": false}}, {"pk": 17, "model": "scon.crafting", "fields": {"output": 23, "amount": 1}}, {"pk": 29, "model": "scon.craftinginput", "fields": {"item": 52, "crafting": 17, "amount": 1, "primary": true}}, {"pk": 30, "model": "scon.craftinginput", "fields": {"item": 10, "crafting": 17, "amount": 2, "primary": false}}, {"pk": 31, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 17, "amount": 14, "primary": false}}, {"pk": 32, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 17, "amount": 2, "primary": false}}, {"pk": 33, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 17, "amount": 20, "primary": false}}, {"pk": 18, "model": "scon.crafting", "fields": {"output": 24, "amount": 1}}, {"pk": 34, "model": "scon.craftinginput", "fields": {"item": 53, "crafting": 18, "amount": 1, "primary": true}}, {"pk": 35, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 18, "amount": 3, "primary": false}}, {"pk": 36, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 18, "amount": 4, "primary": false}}, {"pk": 37, "model": "scon.craftinginput", "fields": {"item": 10, "crafting": 18, "amount": 2, "primary": false}}, {"pk": 38, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 18, "amount": 30, "primary": false}}, {"pk": 19, "model": "scon.crafting", "fields": {"output": 25, "amount": 1}}, {"pk": 39, "model": "scon.craftinginput", "fields": {"item": 54, "crafting": 19, "amount": 1, "primary": true}}, {"pk": 40, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 19, "amount": 3, "primary": false}}, {"pk": 41, "model": "scon.craftinginput", "fields": {"item": 8, "crafting": 19, "amount": 2, "primary": false}}, {"pk": 42, "model": "scon.craftinginput", "fields": {"item": 10, "crafting": 19, "amount": 2, "primary": false}}, {"pk": 43, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 19, "amount": 30, "primary": false}}, {"pk": 20, "model": "scon.crafting", "fields": {"output": 26, "amount": 1}}, {"pk": 44, "model": "scon.craftinginput", "fields": {"item": 55, "crafting": 20, "amount": 1, "primary": true}}, {"pk": 45, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 20, "amount": 10, "primary": false}}, {"pk": 46, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 20, "amount": 3, "primary": false}}, {"pk": 47, "model": "scon.craftinginput", "fields": {"item": 10, "crafting": 20, "amount": 3, "primary": false}}, {"pk": 48, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 20, "amount": 30, "primary": false}}, {"pk": 21, "model": "scon.crafting", "fields": {"output": 27, "amount": 1}}, {"pk": 49, "model": "scon.craftinginput", "fields": {"item": 56, "crafting": 21, "amount": 1, "primary": true}}, {"pk": 50, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 21, "amount": 7, "primary": false}}, {"pk": 51, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 21, "amount": 1, "primary": false}}, {"pk": 52, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 21, "amount": 4, "primary": false}}, {"pk": 53, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 21, "amount": 15, "primary": false}}, {"pk": 22, "model": "scon.crafting", "fields": {"output": 28, "amount": 1}}, {"pk": 54, "model": "scon.craftinginput", "fields": {"item": 57, "crafting": 22, "amount": 1, "primary": true}}, {"pk": 55, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 22, "amount": 12, "primary": false}}, {"pk": 56, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 22, "amount": 2, "primary": false}}, {"pk": 57, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 22, "amount": 5, "primary": false}}, {"pk": 58, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 22, "amount": 20, "primary": false}}, {"pk": 23, "model": "scon.crafting", "fields": {"output": 29, "amount": 1}}, {"pk": 59, "model": "scon.craftinginput", "fields": {"item": 58, "crafting": 23, "amount": 1, "primary": true}}, {"pk": 60, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 23, "amount": 7, "primary": false}}, {"pk": 61, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 23, "amount": 3, "primary": false}}, {"pk": 62, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 23, "amount": 6, "primary": false}}, {"pk": 63, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 23, "amount": 30, "primary": false}}, {"pk": 24, "model": "scon.crafting", "fields": {"output": 30, "amount": 1}}, {"pk": 64, "model": "scon.craftinginput", "fields": {"item": 65, "crafting": 24, "amount": 1, "primary": true}}, {"pk": 65, "model": "scon.craftinginput", "fields": {"item": 40, "crafting": 24, "amount": 1, "primary": false}}, {"pk": 66, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 24, "amount": 6, "primary": false}}, {"pk": 67, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 24, "amount": 3, "primary": false}}, {"pk": 68, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 24, "amount": 30, "primary": false}}, {"pk": 25, "model": "scon.crafting", "fields": {"output": 31, "amount": 1}}, {"pk": 69, "model": "scon.craftinginput", "fields": {"item": 66, "crafting": 25, "amount": 1, "primary": true}}, {"pk": 70, "model": "scon.craftinginput", "fields": {"item": 41, "crafting": 25, "amount": 1, "primary": false}}, {"pk": 71, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 25, "amount": 1, "primary": false}}, {"pk": 72, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 25, "amount": 4, "primary": false}}, {"pk": 73, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 25, "amount": 50, "primary": false}}, {"pk": 26, "model": "scon.crafting", "fields": {"output": 32, "amount": 1}}, {"pk": 74, "model": "scon.craftinginput", "fields": {"item": 67, "crafting": 26, "amount": 1, "primary": true}}, {"pk": 75, "model": "scon.craftinginput", "fields": {"item": 42, "crafting": 26, "amount": 1, "primary": false}}, {"pk": 76, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 26, "amount": 3, "primary": false}}, {"pk": 77, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 26, "amount": 5, "primary": false}}, {"pk": 78, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 26, "amount": 70, "primary": false}}, {"pk": 27, "model": "scon.crafting", "fields": {"output": 33, "amount": 1}}, {"pk": 79, "model": "scon.craftinginput", "fields": {"item": 62, "crafting": 27, "amount": 1, "primary": true}}, {"pk": 80, "model": "scon.craftinginput", "fields": {"item": 43, "crafting": 27, "amount": 1, "primary": false}}, {"pk": 81, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 27, "amount": 6, "primary": false}}, {"pk": 82, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 27, "amount": 3, "primary": false}}, {"pk": 83, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 27, "amount": 30, "primary": false}}, {"pk": 28, "model": "scon.crafting", "fields": {"output": 34, "amount": 1}}, {"pk": 84, "model": "scon.craftinginput", "fields": {"item": 63, "crafting": 28, "amount": 1, "primary": true}}, {"pk": 85, "model": "scon.craftinginput", "fields": {"item": 44, "crafting": 28, "amount": 1, "primary": false}}, {"pk": 86, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 28, "amount": 1, "primary": false}}, {"pk": 87, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 28, "amount": 4, "primary": false}}, {"pk": 88, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 28, "amount": 50, "primary": false}}, {"pk": 29, "model": "scon.crafting", "fields": {"output": 35, "amount": 1}}, {"pk": 89, "model": "scon.craftinginput", "fields": {"item": 64, "crafting": 29, "amount": 1, "primary": true}}, {"pk": 90, "model": "scon.craftinginput", "fields": {"item": 45, "crafting": 29, "amount": 1, "primary": false}}, {"pk": 91, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 29, "amount": 3, "primary": false}}, {"pk": 92, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 29, "amount": 5, "primary": false}}, {"pk": 93, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 29, "amount": 70, "primary": false}}, {"pk": 30, "model": "scon.crafting", "fields": {"output": 36, "amount": 1}}, {"pk": 94, "model": "scon.craftinginput", "fields": {"item": 59, "crafting": 30, "amount": 1, "primary": true}}, {"pk": 95, "model": "scon.craftinginput", "fields": {"item": 46, "crafting": 30, "amount": 1, "primary": false}}, {"pk": 96, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 30, "amount": 6, "primary": false}}, {"pk": 97, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 30, "amount": 3, "primary": false}}, {"pk": 98, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 30, "amount": 30, "primary": false}}, {"pk": 31, "model": "scon.crafting", "fields": {"output": 37, "amount": 1}}, {"pk": 99, "model": "scon.craftinginput", "fields": {"item": 60, "crafting": 31, "amount": 1, "primary": true}}, {"pk": 100, "model": "scon.craftinginput", "fields": {"item": 47, "crafting": 31, "amount": 1, "primary": false}}, {"pk": 101, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 31, "amount": 1, "primary": false}}, {"pk": 102, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 31, "amount": 4, "primary": false}}, {"pk": 103, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 31, "amount": 50, "primary": false}}, {"pk": 32, "model": "scon.crafting", "fields": {"output": 38, "amount": 1}}, {"pk": 104, "model": "scon.craftinginput", "fields": {"item": 61, "crafting": 32, "amount": 1, "primary": true}}, {"pk": 105, "model": "scon.craftinginput", "fields": {"item": 48, "crafting": 32, "amount": 1, "primary": false}}, {"pk": 106, "model": "scon.craftinginput", "fields": {"item": 6, "crafting": 32, "amount": 3, "primary": false}}, {"pk": 107, "model": "scon.craftinginput", "fields": {"item": 7, "crafting": 32, "amount": 5, "primary": false}}, {"pk": 108, "model": "scon.craftinginput", "fields": {"item": 12, "crafting": 32, "amount": 70, "primary": false}}, {"pk": 33, "model": "scon.crafting", "fields": {"output": 21, "amount": 1}}, {"pk": 109, "model": "scon.craftinginput", "fields": {"item": 68, "crafting": 33, "amount": 1, "primary": true}}, {"pk": 110, "model": "scon.craftinginput", "fields": {"item": 8, "crafting": 33, "amount": 2, "primary": false}}, {"pk": 111, "model": "scon.craftinginput", "fields": {"item": 13, "crafting": 33, "amount": 1, "primary": false}}, {"pk": 112, "model": "scon.craftinginput", "fields": {"item": 11, "crafting": 33, "amount": 1, "primary": false}}]
\ No newline at end of file
diff --git a/src/scon/dj/scon/forms.py b/src/scon/dj/scon/forms.py
new file mode 100644
index 0000000..5e8242e
--- /dev/null
+++ b/src/scon/dj/scon/forms.py
@@ -0,0 +1,10 @@
+'''
+Created on 27.05.2014
+
+@author: g4b
+'''
+from django import forms
+
+class ConfigForm(forms.Form):
+ def __init__(self, *args, **kwargs):
+
\ No newline at end of file
diff --git a/src/scon/dj/scon/generate_fixtures.py b/src/scon/dj/scon/generate_fixtures.py
new file mode 100644
index 0000000..9b5714a
--- /dev/null
+++ b/src/scon/dj/scon/generate_fixtures.py
@@ -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)
\ No newline at end of file
diff --git a/src/scon/dj/scon/logic.py b/src/scon/dj/scon/logic.py
new file mode 100644
index 0000000..8fc8823
--- /dev/null
+++ b/src/scon/dj/scon/logic.py
@@ -0,0 +1,10 @@
+'''
+'''
+
+def config(condict):
+ # modify condict.
+ return condict
+
+def overview(condict):
+ return condict
+
diff --git a/src/scon/dj/scon/media/scon/conflict-logo.png b/src/scon/dj/scon/media/scon/conflict-logo.png
new file mode 100644
index 0000000..22f1eb6
Binary files /dev/null and b/src/scon/dj/scon/media/scon/conflict-logo.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/active_a1ma.png b/src/scon/dj/scon/media/scon/icons/active_a1ma.png
new file mode 100644
index 0000000..ecefa78
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/active_a1ma.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/active_reverse_thruster.png b/src/scon/dj/scon/media/scon/icons/active_reverse_thruster.png
new file mode 100644
index 0000000..f793db0
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/active_reverse_thruster.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/active_t5_engine_overcharge_pirate.png b/src/scon/dj/scon/media/scon/icons/active_t5_engine_overcharge_pirate.png
new file mode 100644
index 0000000..24b8414
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/active_t5_engine_overcharge_pirate.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/active_t5_mass_shield_generator_pirate.png b/src/scon/dj/scon/media/scon/icons/active_t5_mass_shield_generator_pirate.png
new file mode 100644
index 0000000..842301a
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/active_t5_mass_shield_generator_pirate.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/active_t5_orion_targeting_complex_pirate.png b/src/scon/dj/scon/media/scon/icons/active_t5_orion_targeting_complex_pirate.png
new file mode 100644
index 0000000..e2ee3b0
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/active_t5_orion_targeting_complex_pirate.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_attack_drone.png b/src/scon/dj/scon/media/scon/icons/ammo_attack_drone.png
new file mode 100644
index 0000000..bc12698
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_attack_drone.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_doomsday_missile.png b/src/scon/dj/scon/media/scon/icons/ammo_doomsday_missile.png
new file mode 100644
index 0000000..003351f
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_doomsday_missile.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_double_deflector_mk4.png b/src/scon/dj/scon/media/scon/icons/ammo_double_deflector_mk4.png
new file mode 100644
index 0000000..5115530
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_double_deflector_mk4.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_explosive_shells_mk4.png b/src/scon/dj/scon/media/scon/icons/ammo_explosive_shells_mk4.png
new file mode 100644
index 0000000..b909f25
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_explosive_shells_mk4.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_focusing_lens.png b/src/scon/dj/scon/media/scon/icons/ammo_focusing_lens.png
new file mode 100644
index 0000000..4fcf0ca
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_focusing_lens.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_iridium_slugs.png b/src/scon/dj/scon/media/scon/icons/ammo_iridium_slugs.png
new file mode 100644
index 0000000..cd65202
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_iridium_slugs.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_supercooled_charges.png b/src/scon/dj/scon/media/scon/icons/ammo_supercooled_charges.png
new file mode 100644
index 0000000..a167d5a
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_supercooled_charges.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/ammo_xenon_lamp_mk4.png b/src/scon/dj/scon/media/scon/icons/ammo_xenon_lamp_mk4.png
new file mode 100644
index 0000000..553c6a8
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/ammo_xenon_lamp_mk4.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/blueprint.png b/src/scon/dj/scon/media/scon/icons/blueprint.png
new file mode 100644
index 0000000..a44c675
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/blueprint.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_alien_monocrystal.png b/src/scon/dj/scon/media/scon/icons/component_alien_monocrystal.png
new file mode 100644
index 0000000..0ea8b3d
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_alien_monocrystal.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_computing_chip.png b/src/scon/dj/scon/media/scon/icons/component_computing_chip.png
new file mode 100644
index 0000000..6f8f290
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_computing_chip.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_metal_blank.png b/src/scon/dj/scon/media/scon/icons/component_metal_blank.png
new file mode 100644
index 0000000..150bb8b
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_metal_blank.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_osmium_crystals.png b/src/scon/dj/scon/media/scon/icons/component_osmium_crystals.png
new file mode 100644
index 0000000..50418c2
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_osmium_crystals.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_processing_block.png b/src/scon/dj/scon/media/scon/icons/component_processing_block.png
new file mode 100644
index 0000000..b0b59d6
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_processing_block.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_pure_silicon.png b/src/scon/dj/scon/media/scon/icons/component_pure_silicon.png
new file mode 100644
index 0000000..d388d7c
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_pure_silicon.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_screened_battery.png b/src/scon/dj/scon/media/scon/icons/component_screened_battery.png
new file mode 100644
index 0000000..ac1e7b8
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_screened_battery.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/component_tungsten_plate.png b/src/scon/dj/scon/media/scon/icons/component_tungsten_plate.png
new file mode 100644
index 0000000..ef03e88
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/component_tungsten_plate.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/cpu_target_tracking_coprocessor.png b/src/scon/dj/scon/media/scon/icons/cpu_target_tracking_coprocessor.png
new file mode 100644
index 0000000..82d05ea
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/cpu_target_tracking_coprocessor.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/duplicator.png b/src/scon/dj/scon/media/scon/icons/duplicator.png
new file mode 100644
index 0000000..e1e2851
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/duplicator.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/resource_crystal_shard.png b/src/scon/dj/scon/media/scon/icons/resource_crystal_shard.png
new file mode 100644
index 0000000..b94394f
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/resource_crystal_shard.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/resource_osmium_ore.png b/src/scon/dj/scon/media/scon/icons/resource_osmium_ore.png
new file mode 100644
index 0000000..d2d3cdc
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/resource_osmium_ore.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/resource_silicon_ore.png b/src/scon/dj/scon/media/scon/icons/resource_silicon_ore.png
new file mode 100644
index 0000000..35e4ff3
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/resource_silicon_ore.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/resource_tungsten_ore.png b/src/scon/dj/scon/media/scon/icons/resource_tungsten_ore.png
new file mode 100644
index 0000000..43f8e1a
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/resource_tungsten_ore.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/resource_vanadium.png b/src/scon/dj/scon/media/scon/icons/resource_vanadium.png
new file mode 100644
index 0000000..a2a4eeb
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/resource_vanadium.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/weapon_assault_rail_mk5.png b/src/scon/dj/scon/media/scon/icons/weapon_assault_rail_mk5.png
new file mode 100644
index 0000000..7219d85
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/weapon_assault_rail_mk5.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/weapon_beam_cannon_mk5.png b/src/scon/dj/scon/media/scon/icons/weapon_beam_cannon_mk5.png
new file mode 100644
index 0000000..2e905c7
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/weapon_beam_cannon_mk5.png differ
diff --git a/src/scon/dj/scon/media/scon/icons/weapon_plasma_gun_mk5.png b/src/scon/dj/scon/media/scon/icons/weapon_plasma_gun_mk5.png
new file mode 100644
index 0000000..1c672f0
Binary files /dev/null and b/src/scon/dj/scon/media/scon/icons/weapon_plasma_gun_mk5.png differ
diff --git a/src/scon/dj/scon/models.py b/src/scon/dj/scon/models.py
new file mode 100644
index 0000000..ad2244e
--- /dev/null
+++ b/src/scon/dj/scon/models.py
@@ -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 = '%s' % 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)
+
diff --git a/src/scon/dj/scon/templates/404.html b/src/scon/dj/scon/templates/404.html
new file mode 100644
index 0000000..b5266ad
--- /dev/null
+++ b/src/scon/dj/scon/templates/404.html
@@ -0,0 +1 @@
+Site Not Found.
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/500.html b/src/scon/dj/scon/templates/500.html
new file mode 100644
index 0000000..55b8a08
--- /dev/null
+++ b/src/scon/dj/scon/templates/500.html
@@ -0,0 +1 @@
+Internal Server Error.
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/base.html b/src/scon/dj/scon/templates/base.html
new file mode 100644
index 0000000..d09a04b
--- /dev/null
+++ b/src/scon/dj/scon/templates/base.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{ title }}
+ {% block extrahead %}{% endblock extrahead%}
+ {% block css %}{% endblock css %}
+ {% block js %}{% endblock js %}
+
+{% block context %}
+{% endblock context %}
+
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/base.html b/src/scon/dj/scon/templates/scon/base.html
new file mode 100644
index 0000000..1870b99
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/base.html
@@ -0,0 +1,144 @@
+{% extends "base.html" %}
+
+{% block css %}
+
+{% endblock css %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/config.html b/src/scon/dj/scon/templates/scon/config.html
new file mode 100644
index 0000000..4771f7e
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/config.html
@@ -0,0 +1,11 @@
+{% extends "scon/base.html" %}
+{% load i18n %}
+
+{% block context %}
+{% blocktrans %}{% endblocktrans %}
+
+{% endblock context %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/crafting/bbcode.txt b/src/scon/dj/scon/templates/scon/crafting/bbcode.txt
new file mode 100644
index 0000000..96fbaa9
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/crafting/bbcode.txt
@@ -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]
+
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/crafting/forum.html b/src/scon/dj/scon/templates/scon/crafting/forum.html
new file mode 100644
index 0000000..16e4ee0
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/crafting/forum.html
@@ -0,0 +1,41 @@
+{% extends "scon/base.html" %}
+
+{% block context %}
+Forum
+
+
+
+ [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]
+
+{% endblock context %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/crafting/forum_efefay.html b/src/scon/dj/scon/templates/scon/crafting/forum_efefay.html
new file mode 100644
index 0000000..bd5922b
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/crafting/forum_efefay.html
@@ -0,0 +1,133 @@
+{% extends "scon/base.html" %}
+
+{% block css %}
+{{ block.super }}
+
+
+
+{% endblock css %}
+
+{% block context %}
+
+Forum
+
+
+
+What ya wanna |
+What ya needze |
+
+{% for ore in ores %}
+
+ {{ ore.name }} |
+ N/A{% if ore.typ == 13 %} (This is a component you cannot craft){% endif %} |
+
+{% endfor %}
+ {% for item in items %}
+
+ {% if item.primary_recipee %}
+
+ {% with item.primary_recipee as recipee %}
+
+ {{ recipee.output.name }}
+ |
+
+
+
+ {% for ingredient in recipee.ingredients %}
+ -
+ {% if ingredient.item != item %}
+ {{ ingredient.amount }} x {{ ingredient.item.name }}
+ {% else %}
+ {% if ingredient.amount > 1 %}
+ {{ ingredient.amount }} x {{ ingredient.item.name }}
+ {% else %}
+ {{ ingredient.amount }} x {{ ingredient.item.name }}
+ {% endif %}
+ {% endif %}
+
+ {% endfor %}
+
+ |
+ {% endwith %}
+
+ {% else %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+ [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]
+
+{% endblock context %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/crafting/overview.html b/src/scon/dj/scon/templates/scon/crafting/overview.html
new file mode 100644
index 0000000..37f8a0b
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/crafting/overview.html
@@ -0,0 +1,58 @@
+{% extends "scon/base.html" %}
+
+{% block context %}
+
+Crafting Overview
+
+ {% for item in items %}
+ {% if item.primary_recipee %}
+
+
+
+
+ {% if item.icon %}

{% endif %} {{ item.name }}
+ {% if item.sell_price %}
Sell: {{item.sell_price}} cr{% endif %}
+
+
+
+ {% with item.primary_recipee as recipee %}
+
+
{% if recipee.amount > 1 %}{{ recipee.amount }}{% endif %}
+
+
+
+
+
+ {% if recipee.output.icon %}

{% endif %} {{ recipee.output.html }}
+ {% if recipee.output.sell_price %}
Sell: {{recipee.output.sell_price}} cr{% endif %}
+
+
+
+ {% for ingredient in recipee.ingredients %}
+ - {{ ingredient.amount }} x {{ ingredient.item.html }}
+ {% endfor %}
+
+
+
+
+
+
+
+
+ {% endwith %}
+
+
+ {% endif %}
+ {% endfor %}
+{% endblock context %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/templates/scon/crafting/overview_tables.html b/src/scon/dj/scon/templates/scon/crafting/overview_tables.html
new file mode 100644
index 0000000..584eed4
--- /dev/null
+++ b/src/scon/dj/scon/templates/scon/crafting/overview_tables.html
@@ -0,0 +1,67 @@
+{% extends "scon/base.html" %}
+
+{% block context %}
+
+Crafting Overview
+
+
+
+ Source |
+ |
+ Crafts into |
+ Also used in |
+
+
+
+ {% for item in items %}
+ {% if item.primary_recipee %}
+
+
+
+ {% if item.icon %}  {% endif %} {{ item.name }}
+ {% if item.sell_price %} Sell: {{item.sell_price}} cr{% endif %}
+
+ |
+
+ {% with item.primary_recipee as recipee %}
+
+ {% if recipee.amount > 1 %}{{ recipee.amount }}{% endif %}
+ |
+
+
+
+
+ {% if recipee.output.icon %}  {% endif %} {{ recipee.output.html }}
+ {% if recipee.output.sell_price %} Sell: {{recipee.output.sell_price}} cr{% endif %}
+
+
+
+ {% for ingredient in recipee.ingredients %}
+ - {{ ingredient.amount }} x {{ ingredient.item.html }}
+ {% endfor %}
+
+
+
+ |
+
+
+ |
+ {% endwith %}
+
+ |
+ {% endif %}
+ {% endfor %}
+
+
+{% endblock context %}
\ No newline at end of file
diff --git a/src/scon/dj/scon/tests.py b/src/scon/dj/scon/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/src/scon/dj/scon/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/src/scon/dj/scon/views.py b/src/scon/dj/scon/views.py
new file mode 100644
index 0000000..022d568
--- /dev/null
+++ b/src/scon/dj/scon/views.py
@@ -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))
diff --git a/src/scon/dj/settings.py b/src/scon/dj/settings.py
new file mode 100644
index 0000000..adec84c
--- /dev/null
+++ b/src/scon/dj/settings.py
@@ -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: '',
+ }
\ No newline at end of file
diff --git a/src/scon/dj/urls.py b/src/scon/dj/urls.py
new file mode 100644
index 0000000..e8464b3
--- /dev/null
+++ b/src/scon/dj/urls.py
@@ -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.*)$', 'django.contrib.staticfiles.views.serve'),
+ url(r'^media/(?P.*)$', 'django.views.static.serve', {
+ 'document_root': settings.MEDIA_ROOT,
+ }),
+ )
\ No newline at end of file
diff --git a/src/scon/dj/wsgi.py b/src/scon/dj/wsgi.py
new file mode 100644
index 0000000..cb6ef28
--- /dev/null
+++ b/src/scon/dj/wsgi.py
@@ -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()
diff --git a/src/scon/game/__init__.py b/src/scon/game/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/game/battle.py b/src/scon/game/battle.py
new file mode 100644
index 0000000..564c0ee
--- /dev/null
+++ b/src/scon/game/battle.py
@@ -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 []
+
+
\ No newline at end of file
diff --git a/src/scon/game/mission.py b/src/scon/game/mission.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/game/pieces.py b/src/scon/game/pieces.py
new file mode 100644
index 0000000..22d47b9
--- /dev/null
+++ b/src/scon/game/pieces.py
@@ -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()
\ No newline at end of file
diff --git a/src/scon/game/screener.py b/src/scon/game/screener.py
new file mode 100644
index 0000000..06fdd71
--- /dev/null
+++ b/src/scon/game/screener.py
@@ -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
\ No newline at end of file
diff --git a/src/scon/game/skirmish.py b/src/scon/game/skirmish.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/gui/__init__.py b/src/scon/gui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/scon/gui/qbrowser.py b/src/scon/gui/qbrowser.py
new file mode 100644
index 0000000..5795c6c
--- /dev/null
+++ b/src/scon/gui/qbrowser.py
@@ -0,0 +1,77 @@
+"""
+ ********************* VerySimpleWebBrowser ************************
+
+ This is a Very Simple Web Browser implemented over Qt and QtWebKit.
+
+ author: Juan Manuel Garcia
+
+ *******************************************************************
+"""
+
+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_())
+
diff --git a/src/scon/gui/treeview.py b/src/scon/gui/treeview.py
new file mode 100644
index 0000000..e42a9c2
--- /dev/null
+++ b/src/scon/gui/treeview.py
@@ -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
+# For more information see
+#
+# 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:
+#
+#
+# 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)
+
\ No newline at end of file
diff --git a/src/scon/gui/viewer.py b/src/scon/gui/viewer.py
new file mode 100644
index 0000000..ff59fa6
--- /dev/null
+++ b/src/scon/gui/viewer.py
@@ -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 "It works!
"
+
+if __name__ == "__main__":
+
+ app = QtGui.QApplication(sys.argv)
+ main = Browser()
+ main.show()
+ sys.exit(app.exec_())
+
diff --git a/src/scon/logs/__init__.py b/src/scon/logs/__init__.py
new file mode 100644
index 0000000..4bf1f8d
--- /dev/null
+++ b/src/scon/logs/__init__.py
@@ -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.
+
+"""
\ No newline at end of file
diff --git a/src/scon/logs/base.py b/src/scon/logs/base.py
new file mode 100644
index 0000000..8415e91
--- /dev/null
+++ b/src/scon/logs/base.py
@@ -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
\ No newline at end of file
diff --git a/src/scon/logs/chat.py b/src/scon/logs/chat.py
new file mode 100644
index 0000000..bf31589
--- /dev/null
+++ b/src/scon/logs/chat.py
@@ -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.*)")
+
+ @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[^\]]+)\]\s(?P.*)")
+
+ @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[^\]]+)\]\s(?P.*)")
+
+ @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[^>]+)>\[\s*(?P[^\]]+)\]\s(?P.*)")
+
+ @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[^>]+)>")
+
+ @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[^>]+)>")
+
+ @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,
+ ]
diff --git a/src/scon/logs/combat.py b/src/scon/logs/combat.py
new file mode 100644
index 0000000..d39c450
--- /dev/null
+++ b/src/scon/logs/combat.py
@@ -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\d+)\((?P\w+)\)\.\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"),
+ # team, unexplained reason (unknown, Timeout)
+ re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P\d+).\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"),
+ ]
+
+class Apply(CombatLog): # Apply Aura.
+ __slots__ = CombatLog.__slots__
+ matcher = re.compile(r"^Apply\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sto\s'(?P[^\']+)'")
+
+class Damage(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = re.compile(r"^Damage\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))(?:\s(?P[^\s]+)\s|\s{2,2})(?P(?:\w|\|)+)")
+
+class Spawn(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P\d+)\s\((?P[^,]+),\s+(?P#\w+)\)\.\s+'(?P\w+)'")
+
+class Spell(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = re.compile(r"^Spell\s'(?P\w+)'\sby\s+(?P.*)(?:\((?P\w+)\)|)\stargets\((?P\d+)\)\:(?:\s(?P.+)|\s*)")
+
+class Reward(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = [
+ # ordinary reward:
+ re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)(?P\d+)\s(?P.*)\s+for\s(?P.*)"),
+ # openspace reward (karma):
+ re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)\s+(?P[\+\-]\d+)\skarma\spoints\s+for\s(?P.*)"),
+ ]
+
+class Participant(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = re.compile(r"^\s+Participant\s+(?P[^\s]+)(?:\s+(?P\w+)|\s{30,})\s+(?:totalDamage\s(?P(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P[^']+)';\s*(?P.*)|<(?P\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(?Plaunch|detonation)\.\sowner\s'(?P[^']+)'(?:,\s(?:def\s'(?P\w+)'|target\s'(?P[^']+)'|reason\s'(?P\w+)'|directHit\s'(?P[^']+)'))+")
+
+class Heal(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = [
+ # heal by module
+ re.compile(r"^Heal\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))\s(?P[^\s]+)"),
+ # direct heal by source or n/a (global buff)
+ re.compile(r"^Heal\s+(?:n/a|(?P\w+))\s+\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))"),
+ ]
+
+class Killed(CombatLog):
+ __slots__ = CombatLog.__slots__
+ matcher = [
+ re.compile(r"^Killed\s(?P[^\s]+)\s+(?P\w+);\s+killer\s(?P[^\s]+)\s*"),
+ re.compile(r"^Killed\s(?P