From 74263690b31a726cef7e6db59b4e409a0c9feea5 Mon Sep 17 00:00:00 2001 From: Gabor Guzmics Date: Fri, 14 Apr 2017 19:02:39 +0200 Subject: [PATCH] . --- src/scon/__init__.py | 0 src/scon/analyze.py | 90 +++ src/scon/app.py | 17 + src/scon/archive/__init__.py | 0 src/scon/archive/localbrowser.py | 142 +++++ src/scon/backup.py | 71 +++ src/scon/battle.py | 33 + src/scon/brainstorm.py | 85 +++ src/scon/config/__init__.py | 4 + src/scon/config/common.py | 5 + src/scon/config/display_config.py | 119 ++++ src/scon/config/readme.txt | 11 + src/scon/config/settings.py | 31 + src/scon/dejaqt/__init__.py | 3 + src/scon/dejaqt/folders.py | 84 +++ src/scon/dejaqt/qweb.py | 198 ++++++ src/scon/dejaqt/readme.txt | 26 + src/scon/dj/__init__.py | 0 src/scon/dj/deprecated.txt | 3 + src/scon/dj/scon/__init__.py | 0 src/scon/dj/scon/admin.py | 6 + src/scon/dj/scon/fixtures/generated.json | 1 + src/scon/dj/scon/forms.py | 10 + src/scon/dj/scon/generate_fixtures.py | 592 ++++++++++++++++++ src/scon/dj/scon/logic.py | 10 + src/scon/dj/scon/media/scon/conflict-logo.png | Bin 0 -> 3010 bytes .../dj/scon/media/scon/icons/active_a1ma.png | Bin 0 -> 4117 bytes .../scon/icons/active_reverse_thruster.png | Bin 0 -> 5373 bytes .../active_t5_engine_overcharge_pirate.png | Bin 0 -> 5706 bytes ...active_t5_mass_shield_generator_pirate.png | Bin 0 -> 5649 bytes ...tive_t5_orion_targeting_complex_pirate.png | Bin 0 -> 5921 bytes .../media/scon/icons/ammo_attack_drone.png | Bin 0 -> 5291 bytes .../scon/icons/ammo_doomsday_missile.png | Bin 0 -> 4056 bytes .../scon/icons/ammo_double_deflector_mk4.png | Bin 0 -> 4896 bytes .../scon/icons/ammo_explosive_shells_mk4.png | Bin 0 -> 5370 bytes .../media/scon/icons/ammo_focusing_lens.png | Bin 0 -> 5435 bytes .../media/scon/icons/ammo_iridium_slugs.png | Bin 0 -> 5282 bytes .../scon/icons/ammo_supercooled_charges.png | Bin 0 -> 5031 bytes .../media/scon/icons/ammo_xenon_lamp_mk4.png | Bin 0 -> 5278 bytes .../dj/scon/media/scon/icons/blueprint.png | Bin 0 -> 4590 bytes .../icons/component_alien_monocrystal.png | Bin 0 -> 5063 bytes .../scon/icons/component_computing_chip.png | Bin 0 -> 4904 bytes .../scon/icons/component_metal_blank.png | Bin 0 -> 4837 bytes .../scon/icons/component_osmium_crystals.png | Bin 0 -> 4421 bytes .../scon/icons/component_processing_block.png | Bin 0 -> 5016 bytes .../scon/icons/component_pure_silicon.png | Bin 0 -> 4515 bytes .../scon/icons/component_screened_battery.png | Bin 0 -> 4989 bytes .../scon/icons/component_tungsten_plate.png | Bin 0 -> 4513 bytes .../icons/cpu_target_tracking_coprocessor.png | Bin 0 -> 5189 bytes .../dj/scon/media/scon/icons/duplicator.png | Bin 0 -> 4730 bytes .../scon/icons/resource_crystal_shard.png | Bin 0 -> 4651 bytes .../media/scon/icons/resource_osmium_ore.png | Bin 0 -> 4852 bytes .../media/scon/icons/resource_silicon_ore.png | Bin 0 -> 4798 bytes .../scon/icons/resource_tungsten_ore.png | Bin 0 -> 5014 bytes .../media/scon/icons/resource_vanadium.png | Bin 0 -> 4629 bytes .../scon/icons/weapon_assault_rail_mk5.png | Bin 0 -> 4165 bytes .../scon/icons/weapon_beam_cannon_mk5.png | Bin 0 -> 4497 bytes .../scon/icons/weapon_plasma_gun_mk5.png | Bin 0 -> 4300 bytes src/scon/dj/scon/models.py | 148 +++++ src/scon/dj/scon/templates/404.html | 1 + src/scon/dj/scon/templates/500.html | 1 + src/scon/dj/scon/templates/base.html | 13 + src/scon/dj/scon/templates/scon/base.html | 144 +++++ src/scon/dj/scon/templates/scon/config.html | 11 + .../scon/templates/scon/crafting/bbcode.txt | 581 +++++++++++++++++ .../scon/templates/scon/crafting/forum.html | 41 ++ .../templates/scon/crafting/forum_efefay.html | 133 ++++ .../templates/scon/crafting/overview.html | 58 ++ .../scon/crafting/overview_tables.html | 67 ++ src/scon/dj/scon/tests.py | 3 + src/scon/dj/scon/views.py | 32 + src/scon/dj/settings.py | 99 +++ src/scon/dj/urls.py | 22 + src/scon/dj/wsgi.py | 14 + src/scon/game/__init__.py | 0 src/scon/game/battle.py | 77 +++ src/scon/game/mission.py | 0 src/scon/game/pieces.py | 100 +++ src/scon/game/screener.py | 28 + src/scon/game/skirmish.py | 0 src/scon/gui/__init__.py | 0 src/scon/gui/qbrowser.py | 77 +++ src/scon/gui/treeview.py | 444 +++++++++++++ src/scon/gui/viewer.py | 108 ++++ src/scon/logs/__init__.py | 14 + src/scon/logs/base.py | 89 +++ src/scon/logs/chat.py | 206 ++++++ src/scon/logs/combat.py | 304 +++++++++ src/scon/logs/game.py | 195 ++++++ src/scon/logs/logfile.py | 60 ++ src/scon/logs/logfiles.py | 49 ++ src/scon/logs/logstream.py | 144 +++++ src/scon/logs/session.py | 209 +++++++ src/scon/manage.py | 10 + src/scon/monitor.py | 161 +++++ src/scon/qscon.py | 103 +++ src/scon/test/__init__.py | 0 src/scon/test/regexes.py | 25 + src/scon/utils/__init__.py | 0 src/scon/utils/steam.py | 28 + 100 files changed, 5340 insertions(+) create mode 100644 src/scon/__init__.py create mode 100644 src/scon/analyze.py create mode 100644 src/scon/app.py create mode 100644 src/scon/archive/__init__.py create mode 100644 src/scon/archive/localbrowser.py create mode 100644 src/scon/backup.py create mode 100644 src/scon/battle.py create mode 100644 src/scon/brainstorm.py create mode 100644 src/scon/config/__init__.py create mode 100644 src/scon/config/common.py create mode 100644 src/scon/config/display_config.py create mode 100644 src/scon/config/readme.txt create mode 100644 src/scon/config/settings.py create mode 100644 src/scon/dejaqt/__init__.py create mode 100644 src/scon/dejaqt/folders.py create mode 100644 src/scon/dejaqt/qweb.py create mode 100644 src/scon/dejaqt/readme.txt create mode 100644 src/scon/dj/__init__.py create mode 100644 src/scon/dj/deprecated.txt create mode 100644 src/scon/dj/scon/__init__.py create mode 100644 src/scon/dj/scon/admin.py create mode 100644 src/scon/dj/scon/fixtures/generated.json create mode 100644 src/scon/dj/scon/forms.py create mode 100644 src/scon/dj/scon/generate_fixtures.py create mode 100644 src/scon/dj/scon/logic.py create mode 100644 src/scon/dj/scon/media/scon/conflict-logo.png create mode 100644 src/scon/dj/scon/media/scon/icons/active_a1ma.png create mode 100644 src/scon/dj/scon/media/scon/icons/active_reverse_thruster.png create mode 100644 src/scon/dj/scon/media/scon/icons/active_t5_engine_overcharge_pirate.png create mode 100644 src/scon/dj/scon/media/scon/icons/active_t5_mass_shield_generator_pirate.png create mode 100644 src/scon/dj/scon/media/scon/icons/active_t5_orion_targeting_complex_pirate.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_attack_drone.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_doomsday_missile.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_double_deflector_mk4.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_explosive_shells_mk4.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_focusing_lens.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_iridium_slugs.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_supercooled_charges.png create mode 100644 src/scon/dj/scon/media/scon/icons/ammo_xenon_lamp_mk4.png create mode 100644 src/scon/dj/scon/media/scon/icons/blueprint.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_alien_monocrystal.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_computing_chip.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_metal_blank.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_osmium_crystals.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_processing_block.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_pure_silicon.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_screened_battery.png create mode 100644 src/scon/dj/scon/media/scon/icons/component_tungsten_plate.png create mode 100644 src/scon/dj/scon/media/scon/icons/cpu_target_tracking_coprocessor.png create mode 100644 src/scon/dj/scon/media/scon/icons/duplicator.png create mode 100644 src/scon/dj/scon/media/scon/icons/resource_crystal_shard.png create mode 100644 src/scon/dj/scon/media/scon/icons/resource_osmium_ore.png create mode 100644 src/scon/dj/scon/media/scon/icons/resource_silicon_ore.png create mode 100644 src/scon/dj/scon/media/scon/icons/resource_tungsten_ore.png create mode 100644 src/scon/dj/scon/media/scon/icons/resource_vanadium.png create mode 100644 src/scon/dj/scon/media/scon/icons/weapon_assault_rail_mk5.png create mode 100644 src/scon/dj/scon/media/scon/icons/weapon_beam_cannon_mk5.png create mode 100644 src/scon/dj/scon/media/scon/icons/weapon_plasma_gun_mk5.png create mode 100644 src/scon/dj/scon/models.py create mode 100644 src/scon/dj/scon/templates/404.html create mode 100644 src/scon/dj/scon/templates/500.html create mode 100644 src/scon/dj/scon/templates/base.html create mode 100644 src/scon/dj/scon/templates/scon/base.html create mode 100644 src/scon/dj/scon/templates/scon/config.html create mode 100644 src/scon/dj/scon/templates/scon/crafting/bbcode.txt create mode 100644 src/scon/dj/scon/templates/scon/crafting/forum.html create mode 100644 src/scon/dj/scon/templates/scon/crafting/forum_efefay.html create mode 100644 src/scon/dj/scon/templates/scon/crafting/overview.html create mode 100644 src/scon/dj/scon/templates/scon/crafting/overview_tables.html create mode 100644 src/scon/dj/scon/tests.py create mode 100644 src/scon/dj/scon/views.py create mode 100644 src/scon/dj/settings.py create mode 100644 src/scon/dj/urls.py create mode 100644 src/scon/dj/wsgi.py create mode 100644 src/scon/game/__init__.py create mode 100644 src/scon/game/battle.py create mode 100644 src/scon/game/mission.py create mode 100644 src/scon/game/pieces.py create mode 100644 src/scon/game/screener.py create mode 100644 src/scon/game/skirmish.py create mode 100644 src/scon/gui/__init__.py create mode 100644 src/scon/gui/qbrowser.py create mode 100644 src/scon/gui/treeview.py create mode 100644 src/scon/gui/viewer.py create mode 100644 src/scon/logs/__init__.py create mode 100644 src/scon/logs/base.py create mode 100644 src/scon/logs/chat.py create mode 100644 src/scon/logs/combat.py create mode 100644 src/scon/logs/game.py create mode 100644 src/scon/logs/logfile.py create mode 100644 src/scon/logs/logfiles.py create mode 100644 src/scon/logs/logstream.py create mode 100644 src/scon/logs/session.py create mode 100644 src/scon/manage.py create mode 100644 src/scon/monitor.py create mode 100644 src/scon/qscon.py create mode 100644 src/scon/test/__init__.py create mode 100644 src/scon/test/regexes.py create mode 100644 src/scon/utils/__init__.py create mode 100644 src/scon/utils/steam.py 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\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 0000000000000000000000000000000000000000..22f1eb61ae9aaf0783ba60a6b92a89980c9ea786 GIT binary patch literal 3010 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ecefa7875645d6b8ebcd3426f15f96bdab32987d GIT binary patch literal 4117 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 TL<>}L00000NkvXXu0mjfWoY;U literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f793db0ee2c2cf61ef11f5f927c530f2d14d9ca7 GIT binary patch literal 5373 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..842301a9949beb762ec1666a20d8f88c3573d3bd GIT binary patch literal 5649 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e2ee3b06850a01246a11f5c23265d79870973276 GIT binary patch literal 5921 zcmV++7vAWJP)WFU8GbZ8()Nlj2>E@cM*02YNwL_t(&-ldvZjHK6f z-+y=cmRh>1tM_$!_JuRUAvp`FAt_Q4Wyh3c3vdF%@LK`|Nq_)>UxNURz^{G@0^}(` zUJ^UDq#%IoL=i=av`KN1IGoY!OHWVFOz&OQ)wO=hy(bUVq!>#U666B6z7Jo0sJiEW z&vMWCA9Z2sD{|uSN&f2n|HdwxU_CZ3Q=`To|KoqfzxywL%387tDg<9-Ffy!CSQDa@ zCe8Z$&jm$}2EYUQ3v)0O9`HCUziBAc{x8a}eeOxnDD1}OHkZzfF!eN;V z4$-SCx_uAU0G`0c6v857b7NF0!x&>QI^f>zyEJw-QN|jIfRM2dAo%<@!O!0T{#Rd-2tm+2cDR1;BhDN;#x&#D z)KaklM^7!Vyt>5v=F>a zKOKmGcY{Uw{l)#J`G4__-v`AyowHeAqF^j@#VOj|ZDuE@*w|d5IAp2KP0%mzk@ecx zAY-^zBnvaF^7Qu_xFo0J;mCm*lD!3P-nxo+ztnmyY9CUA5(T({JeXh6@Bf#f_CF1p z#Z(t41_wA+Jx*hLm5Uc&;=QNW$!nTe^*JzFqNOt$l`?%*Cf%~EwD(x8d)ir*t{Wn^ zqf{nN((Y(FjXFYtm;fJ*L7@iOE*4J)cwHeB2Ut7-CqHUgbN)?#F24n1~(elU(02>l+3%E(E846Qs$z$r*H zcyg5ZfiL*X2dIJXQJ;%27zWXcM(5;Nm$L_+V`}6GPc|An+<8bmZs_F=LZz6UoW|vv zo!vh1&;<1}&og=GS!$IU2gf3gj(B>zn=}*6*pX8#Ke`XyJ*>~j;#o#6ev5WnLvNF+ z>(dKAQ!xQip!DaKYXOT4kmfTdHn1mypip?_F{05Hj|Tee^2zc?96WcJU);Sxzu!k( zhB1nb`WDUB9wWmQrbkDas0}fuJ!<<2HP9!18%3NELX^~hUoo~$*wv)<*_(+=%SIDYgX zNo$M7_9`X{NsA@YB<1MqU!{EP4E2T(H&-!Up^U*94JINWBoL3&kVP7_#yTi`=A6tx zm8^i%3N0E#z@qF2%QQ`R`1l@e*}|!ep35nXPEsn35u2E5p~&p)IBQ#v*|>6xrN>X$ zj)#d(zeMHeISQo;qjA86b0>Jby~4kI;`v3L}1r8xLIZ}Mbpl;Gwk^s@xCLs^YD zPoM&f3GuPTSp(h>=9Z8E59G@43t;dXv>1%&a1jj#1Ip5KJ&g5u)dw%g@GSrGkN*wR zBQwm8jS`mv{>M8%Am8z*Bc~Yo`nMRpc$w5Y8h5WzzjuYAwD~@vZ>R2L}!<8ig4IMxCRKpwVb=FhwaW2IA35k?9Z@6bVbiWQoH%k4E$4 z=_;4L`cn*Es*&3p}`c zi%K=-(u?OA8kuDC{u&PsEHE>=K+%^Os!T94KaaJRtlorH2bcEAbPgs*DUT6{RvDsG zyvaEM{SV!(yBxMDSsWU~B;u z4nxu*^nKDiWqEazb}wOcVuBOrpJ8=*iSGIeqa{TUW~50Q$rCE&0y77usZ@t3c)_ji zz)q9L_m@d?;qptDIDKdyS1a(ORv?dJ+{342H*b^gu94G4e1>%nBLdnW%Ag4d#0-pD zE36nyV53D*X5WPg3P&OaiW5}kj?oWdTHOw@2kBg$H`XbX z0?LJuiP{(o3kMh;8KakYo^G`0n;K^32zGLgZl^^tX>lwvY$q)?V?%L#g3jZ|)NfqH z>@|?AgYq6N9^?1*pJ3nXSRx;iT8Hr#6YJr{$QeSfz-XKX@d6T|N9HkyPtmgh#yLuv zP_9m~r^?7|g^SOhqrbb!`ojmzPR?-b@JXz(NN#ED^l7Cb&FTTV&wmN`@|P(cJI-)F zVekE)a{I>nJic*<#@a4YDiBT9*xXu0*0-p16O_wIwa4KJWZ-+08XO_4D6+s|#9#vo z#IBFWlUPk2C~Pj6?l!%hr;I%JJj~6~`0y>F?jANOP$||BTcn>PkR%i(q$njyr3rdz zft5#F_+S<_xxm=vmuQ?iMk|Qm!6TylDy%nHZf|g4FC*GhY=jZ|jh7hy+GQG#)`%M& zf;pTBm&N9^>^5rbl4r za@Pm7uUFR`5Xx zN@K)fRDzI^2N9`LxY>i8c;o9d6_9p0JW}HLD;L!}!r7#egKxpdet<~>2HOKo_RBPUNWHe8`uUnP(Z)wIs!wJTh{@(bQvTjj-YkMZ$@-To7n z@7`eP<_+FiU1KB9xo8xp*4KEny3WSA7cm!JrupejhL@gFa+*CGqIFIsZBrB>BOuP= zK~6prj4z@+7$Fdal|iUhN@118hA~}Z+4G+HXU;HkWftdb->@+?JMNR&ah)o0Ur%H!i4D-|j1)M>^s>4oRn4Gr1VPdK=!|_F}6h;P_gVqD|9fFD?g1AUQ8OkbTtI?!({w(e33gzn$DfRa_ zR#Z$D4Ap9doo1UP%{Y7VC{gHHeY(tl{fj?iY3TvIZX2KFWSPUIigsS6KnYF6R?;I^ zDTiwzL1&jw?%wC<#n-77t2BQ66Gj``I0L&jB-An044m2DfzfzkabV;F^u1AdqflW; z<^^LdCWuisU}$2Tk+D&x;v%)pbqf;AW<$u(rC%($W&wesP<(-u@|AOE>=G?2OLWx_Zfb(sNlB1l0FdiK2#AB7Vco+$?YZ_$>luxN!b? zQm0v7Ug3?e|298->-+5Fo^*PF`OCkHym1aW6tb(JRo@}cQzY=@9Y@md5EQG7&5!cw zryo$Z5i03pl}1IDEbC!ZjK`z3wkV?!W6{bGN=Tds6eS=QjdC8GW@>Dfw9`YUyZF{J zKm6~1&iB6iXZ+OH6jQC$_~4@tnVOwOoT9zcLK;n)OE-9BdWdreC+Q_Q-4sIScy#wB z?|krcY73`83=e&5T>t~1=A`ng4dHTv=0*#p}9#}N{r3~%OAYD@mZ563cQ9(pz zEsdnd_{<1G%9ZO^*jRnSkt0X>@|Ry_e5Ata(`9y>drZw8U~b_cx9;3#s5Hd*=s1~E z{Nlz;Sf6;>ohi2>9iY! zg(6lJFuB1vyC03g%$5%=Dy0cT4^lU!amFK7$nogFB4!|rXyNGTGd$efWuo6=xKQDh zix)X^Xn~!bZSLHENV}IafABCvr4lPk4;d;}xOw+3-~NMt$XDKYjaOfMj(_m_CBFLd zS9sz43j~ECoAoBmy(S*b*u*ra&pyYq&pyMElSjDl@=MgFCV6n{HlcS!K>=(TuXB_Z z%zXLqB1(ZW7^P845T(eiLIN`g(#{hZ&0arY{><|f=Z-Sl*`~4Cpi(F^e_#P2iLlCp60A`cPYxOsg7E@M$V5OuDrL^WEC4()cE%8|2#xv+fuChI?Wo6*q;;y6aR zjIFg5);FH;PygA!V19mqci#Ug))?B&9zG2ysu<-IVHi^H_Gl!YAzdXJDluGij8uyh za+n&OrI+>i{$`y}LINEV>5$xNa%V6&EZPoINpXmVTx&!bA}<8m(Dx}l8=;Cduq9Nh z&)s+5MV&qgagEfa>@_y&x<0+6OH_@Sn4aaxiIWUf#!<3Dx1F-`aD|@B@tL91-(koY z3grUnUYFIa4N75)*{K@GN5}EG(5m-XT7E>NJX`mc&|M%Z;*=ef@3qE@!fI_W-eJ9v zA~>TEZ_&;XIZb8*l0uPo5Kt`)(c9T&bA5?+UvqvorVs^$)sWq!!CHNbXD*)M8*ly& zXU<;W?!Bk{GL1|(|_jC zdXu%)4P<8r*6WO!GDrkb2`>p&xq;JpZR+<#YdzqtL~!Ep3NmsWLT(j0(5N^>j3UVs zk}O5rVP1ao>jYy%Y;Qfnw(GpGFw1Yf_6pUh3F^7$hwr|}cmDEw)GAem3Pl31@m)cC z!`+Q*y!q|l<;{Qe`!sqTqC%0plk(pGdWR3+`WqhIybrk$>40KT7*r#2P2j=gkc2>( zW3>itWI(|Q=)o<@3rY!E1o0l1dK^OPQnEb9r)@rb?{9hT^%uE(=@Qcw!>NfHwE*1S zHr>q~?!W&wGeOEk#o^Lza+i}?MV1L^w}9|QDX0(z%^*u+opVI3kbg~R37UO|5C+qd` zsbIw5J>)*cMBu{!uM}E=k^rqeHo*LncoK~lHy{-}ST!hhdokquD1F9lsh&qEp}Dli z^4KyXV>Nsb(0#hgmB0OfcYpLV(%fM}L$|$4mUoHDF~l)pxyI0V&d}H-QwI*?1cpjw z=;c@+k?dtu!vdkQ19i}vf{qAPfR#a&DAN`%4)u)#fAD`WZ6Xd4A@>6w*Q;MLf?s^G z;50o2TfR@f7IOH~v%K)i1$NdqSzBI1QpM!V0;TE@#W;qn56+X|+1T0P{wMFy?6esg z8D@BRn1YRIu5Pk&^Bz;h8euMkDnR@|J3<>`QD`q1QS?wvl1B!MV21}}nFgOZB!0$#8vGF>3mT`XF|{$dA5ieJ)bYX6Y;Ye&J7 zcKeK$DirdR5km5fF52}N(FMG7dmbCQVaJc?K0)oCk+sm4?g_D}%`g z^pj(dSb1g05zt_aLcE~NK!vSU17cGUwAOsK8-s$#~bKU=J;eP)bqH#XM2H<7THedonN`{IRspM!G zBIGze^DQ31YuUf|i1>k8ggHoxUGF}^iy*S! z9isRg`Cs-W{F-J9XAmWLTB{c%p4?J70HJMQ;fhdQ_390b|CUT#V{QLgv z*L|n)c$eUf$NI7Uz|YG*@__UHSA_Ut%M5_H0MP>hvWFU8GbZ8()Nlj2>E@cM*02B{NL_t(&-kq9RjGWh% z-+y=cwpzND-uG^@xs#H$kk-hiXXx>GY{M`n0p@vtJOp{lYhDFRUj3MPb$~2Lf-&rI zfXv9-D3+|qlqiYfYLZQ|_pQ2msjlt&zI$&TYGJpeLB_d2p}wu^p8q-jbMCq4eB#E9 z8%_v;5CVY(rvY%nVo4AH&c`8%U4kziflz>?Z3VG#XdIq%xb8U94QiDYF0U6hs%+diSgg{E!f2IdQ`yOeU_JtQhkftd?5Rj&6 zA6yhgwA*czQV1!fbIu`zK#8vK7#tEK=l}qevuGtSCPko0g@<#1OVQGxou%bGlvG%o zBD+@YfG`Llu(-ZPg%CK?)heQ+RUAqwdh!4mWAJ?+Yi<8}nx-8ENQnSk&n#VQcR>I` zb^r(q)}p%i3=*UU=fD{ho!_QHgHn*1uDR)(9xTXXGdKqhME5!AAVr|HCP|XM1$tUP zKBSa=>&o7R?yWw3j-U620F)F6;lQQfOvi6}fO~=gQVF6c>i%~ZgmqoPyTW(pv9=@Z z9%Gd3q-4|A^0<0dOybbWVVC2xswyCF}xosw z$YZvZU7B`uBXEF{ieh<)QmJ&zV)QB08(QlV!UO2zUM`N4Nc1GOeM4C1AzX(_`2bE& zjT2>yY#lY&ZMIOxQLEKxH(N+y@U_HQgE;Ez^hS^S zy+F{{L+_6ql9L#0H?-2fFHrcvnei+aPmM7>F^rU&qgINN7HJa%j?mLc;ShjUor(G` z;OLl3K(Sn6ZtfIuoU*yONhXuw%$YNk%jG_GdYl(R^g;D}K!4sOCx=D$sM~dDN0Sh^ zJUhnZ&;V;SXeWktBPMp1)TC(H5jOOE1Yk@@FV=N+(M6)PW_V~6r8U)RmD%|@hKGkq z+A&&d#>U3Dd-rZ%_~UZ+&gB z-cZ0>Uy4Ay`0DUifpBCn3l{^0?MNqOa!SGaibB1=n4_`c83&=4!D5%qeVAPCS}GdeoT`uaK(6BDFq zN~KaEjgu2Vv^ch63JKbF;=Gj-DYb1?yMZVG&1c<}T&Z$G`q#Hov1Iy1-S zVT+kukt^DSJU4IN?Bqb*mAen1=jBF#6b|XYAh1GVr9#^dnG|qg7T&l# zLeBHJ`|vT_jU3merWhH>vQambO9jR!rfJ27)zuAlcPrfe><+7|>+Do2lu9KQ78cmv z-saY=TU06)thLk*_j&c&RW4k(z`c9-XtY`=At)3IjEszsBuPI75s3cc+qVF{$R-5R zb$KQQ*bbGX17E}CbEnwacuKn&(@s+^Tz;7-iYVo?eCx^|F*PxXuN7Mx8#LQ(wDwqC zU!znWqO!Y7v)Sb4?>-_mF#xZ<_8NibbMM|gHa9o9cI_H#Yin$6ZQ*Op(9jUoYL)f% z^%ssT1(;4`(mfBCQj$s9w8Mb9(_~{q(FqQeAoeT;(_%J;!8FHDmLU#C5%a^G8y!O{ zg&Uva<*#3(=sbS^{!Jc#_yM{QkuMi8o=++woR?>8Zj_5xPO-f7jLnrj7S3GZ%Ee2R zOC$W|gZBxe2b?>9ne~kwPA#0LR&Vp@_fIg!kj4gI2N;tey2+eAzDTtE3=&P`3|1Lx z(qcUy-};ay9Qi5hxdMB5Y(03!*KV9=xL9U%u*_JLA+A(;zPr!E#Yec%V`s0*-~HGB zLOU^h<(1dit8Ad1;(Oov0m}FI$xnaA-~F$j(P$j;=imJ<^`yU%CpXcBI^)t`6feL0DuryGjr9#simmD{|KV@`hSXVp@E1Q|es+SS+2G#oJFF~i zQfst%^J{OSwd3QDZy}_lR4((u2Ol7XV`ylQAOHAo85!(QNGa79UI_Z>w0@C^f=3J< z7zYs+jpa~jV&_pznoP7ThyUkYO5p%HpCKw_$vT0taANYmH+wozu?0Uf5#hd zUgylYGdy|ri1lTd9v@<`SmOEfRiqygXva(E7jUfxzxdV9(NZxvF~;iZDnI<;4;ddB z?f8Q4b+%3Q6JW zRTj54DTN`IF1<>_%kl32{s;cs-~N=9)os51=GPgY80Vu;J|dgT;tNS82)JnzR?E_FkV5Nt366tA- z=MyJ_apU-!FZh#^=Jd=MYuaPx`VHRu>@%u66(&a~Nn^w9Tfb#%tHRf=|1qUfi6@VL zPZ$Kmt(d2epU`TwD3?l1PEE2`t+2JZitl+8OM?WNJQ#zg6-g2^Ju}7O!9FHUI~M2$ znx5c2AS##3^#QcKB4O;vI7ym~XlTV~v&Hqq@kiN^nrX2=K1@0`L1}n`4?n)koyUvZ zy?2|%kKe~Lia)+_108z&;#dDbv(*IWu+}kHDzm?Th(a(mKE>YQK28fNwF90#U!yoM z#Kiarl}d%c^HEaLYPC>Gp_F>Dg%qOi4@#+y5A-5YtU^k{5MgLoLEzvk!tqVprkHAe z+>WVk9dJHZ;?e*3?;I|!GxgF}7%b=6z5glOOS^c1&*1zRLNtjoWk$&)M48rqbFYahAb+iN?_p8>?H`G{N_Mqy!-xTEE!nACE#kYp7ze_*Q?TEj1aJ zU@$n(XDCUy+C1V~F62S&fOmt4M@lnY8esXu+YC)k@%?}GZ}{$azQ=5#z`aLHJYKv< z7)Yk4W+)DfP%I3gltd_p#W6fQ%ALFSc=r4$jm8lZQyiZedxg5=A z6Kfqo5cJC^Ju3hm%N$2kv^9)xspkWPgpx@a7#LtF3i(tj_Ps0~9;THd^OBh}(_B5j zz;~`(<$D+2p;%6scTK_1!S#Z$|MZtvMdR>gGW8e z6Bt9*wkZ#nX-h*8H<63?5sz;3u)aim)ZnOYsa9h~#%9RoaxAYbQQhAslM6Z6-(>O8 z9a@bhL6E1_N)gVGjXbm;lF#Q!)09S|fl><33oyo@v_1i#4`gs~un#~-m6n@)wy={ zRX+LjHu*x4m1j@*_=9(dJc;r`YO$b|It~wO)N1>nH0?OS8jBDTDPPoxeBbX!pc5aZ z6uOJ9p(6rdu64xKq{WqHoAG9gyfrA>F)4v26$znf@@moJJLhtI|JzyS-`wLObryO}`M4q?Daxu0Q+`uhC}8_joN=WISkd;7KZhX2}W~(&3~-JIITK>72(@ zQIkJ6Mm~GM-lti%clJ^KD8r*eRM(#K%YXbiuf2AiJD=V{#dR`~Vy$w-pItr2$nFYD z%}u<)65-GsN~R=9LaWt6YfTu2BuRoXhHN(5k4DF<5^8jG^sQc8#X0;qp#Ti~J_5&{ zR4n7ME+t9m;fa_MZ7xm>b68oUQrRQKqgBiCv)?uOPrtmwMpH61Imzf?f#sD|e*KGI zqi}gz&ln8`n>l0xlj*{ zZxgclJO}kAzuVhm)TRh;fDILr0;dBSnLNwScUk(6OEfp(r@(d$&8O?s z4;ozk>PsX>fG|j5`I8%8=U@Koe~T#1QosKR<&$^_(LxAqQbdGmI!CJ`;7ADP(8`F*RX87ipP@I1FjFk0|3a zHNL<<*5E%kTij?`tUth}Ytm@#v-$WjwY@`(3kisMb0ZdAPX2+rNE)7(9oV zyTAv_Ypgt2L`g|DlOdbQ5C&nNGCe=-#s6O5(Z}-(Ck;vo(j?_zz;LKwShSf=C5J9y z$v;9Ad~!*`WT?3~GmH+i46m>A7nfh*-_0KKek04<)qr)CNV|mwrLFdkl1(xJRSJPY8{H$Yakp zJaR3{ZNpjTAgNO~HIDKVlxz-nut{7?sMQj_dTN-l;(2BZ@36cr2{tUH+4D5+JV1yh zvkw09`UKfxhR4e*?1zH&trcwKh}&R~T1UrK5>Y>MrTzv}&AuuY!OWOFJ zM^#2_XY*``eNM$2yfoq%J~Pj6OSi``*;ix<+Io*KGB)^{PA(2PgJLm zm+hp2Fq6f$9U%=;>X>kkWf}AK;ugQWzr~8p(@Y&`#o~eCld2+{9i)1&$L8iU^3gi4 zoo?~{*UJn^pS@PZn#l3%=i7X=yhpJxK%?CN;Je@c7vu-Vx%J6=G}`M(Id&r3bk3dV z%zPfm2?}*OFHPDagY+GxY>kPL7@cUAp9_Bd;U)(ZIB*t4O!LridUhHej1Z>=W11A| zw|MjP0IyH^_%6fZ4&2*@`w#B(V693}C~(j^V1Id^`P1k5_P4*!gNM(+I&xvIzi~Oy zz&N&f`LZJKiFLqp8lwWFums*7V+F~|QiG44AF>kPyV%J9|60I!j9Yoo!Z2L(RcN~p(%f-h(%HR_EP#lj$Ita#_0pR-k|AO#F(M>^%p zV+!pF-s?*C!siJ9p6B(w--{zm5MW6;+)MfU2lv=MFqnZ+oDOLn)e((0XXnmyZep6@ z<{>l9BgT(vl+i2)S!&PgG>p&sQJyE;Esolb`Kb|hwpQ6`$IQHR{~Ia}yN> z6w74-6=Jmb(jtE^2l}#}nLfU|@pOp5VT5G+Q1WC|W3*2gX_8i*LYQG}c$~5E32Ioj z;+S#iGniUtj<)$|XM%MZQjr;MS8FV9Rv8$~b8t}Qpq(-~cb0Hqm|PGc^BEFXAoVpu zdte31{!@L5e|nrq=8TkRVE~8Dm&uKvqII;7tGAdajW9cNmPmr{8(PY+lON^bVUbLd zl1W>vG<;T(EMvJ}*&`@MB#~lqufoXmIdY>jctRkA0WUz3MMB(ns4E=S1fn0H+ZSv;?gjDpmh~7U&^T$|9Y{*Akhw$#_0; zd<=igL;C^7CJ1ejDj?`~6ja7ze`1`wr2&%t3heEZ#0?s?L+Z_h^9vU^d+IbMNx^p0 zVI|N=uqo2XPE*`@I3@oe?VpH3&N)IELD~sb880Gr2~K$^FGKz4kW4n95gTGOzS2ZC z1)GrgK3o0}X%KQ&dVKcrhnVUC*Is&+v5{dyYtc&5b^@!sP9NHtZsh@lm0%UA{O4)? z3kzAlVJ5){flDnmP@p6x>AV6EW%C$o34;)X!r2%rVo(+(Jf!t;xriZej6W^?DF^kK z(#ROv6Oc4;S|A7jjkFf!($4!BLf|AQVPEu+|4<9`a%^W~GJZsgpsgXX2|=JyNvB6_ zrH>Q}k+u*@U~>^|Aqblud823tiqH=bp~f$TSRInO7-U2OSP>$u!V``FOClw0=RhP# z5qG-f{}0W7em|gldA2bIp);6nQ!jK73a?l7Md47iU$h3%=tL8B&y%P{-ipq{+Uu5I xTiy2)bgxwnG6kU5=?Y%#XHkDh^y%hs{};RZo$4-hOaA}>002ovPDHLkV1oG``E39I literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..003351fc86233e297c1fedcbeeccb570814e9b8b GIT binary patch literal 4056 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cd652028b80c424d81af25efbd96d9f97d354c6d GIT binary patch literal 5282 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..553c6a80d198913a1c40c1989bd142e2190a750d GIT binary patch literal 5278 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a44c67530f1cf2bede594c523b93ba659d3626ea GIT binary patch literal 4590 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 Ye*=keSqZ8ei2wiq07*qoM6N<$f>FPGu>b%7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0ea8b3d17bab03dad869915a26ec39f46c31723b GIT binary patch literal 5063 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6f8f29090989fdd17fa59d4776abf5ea1bf20a3c GIT binary patch literal 4904 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 LNkvXXu0mjf=5AA8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b0b59d61a3ffe4f5c0ed3d96d4b0543282f705f1 GIT binary patch literal 5016 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 Bcq9M- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ac1e7b838dbbd2d73d391f7303881780dcf40616 GIT binary patch literal 4989 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 Hu0mjf-43>& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ef03e881da306546db7967f7fd3011e2125866bb GIT binary patch literal 4513 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e1e2851041b8c272d3d1943f76d2c736c4589cf6 GIT binary patch literal 4730 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 IM6N<$f(ukN$^ZZW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b94394fb654375e761be583a495ec006f604e28e GIT binary patch literal 4651 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d2d3cdc5dfed4801c330f2346f637d3f37adf84b GIT binary patch literal 4852 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 Y0zLT9l9&1~n*aa+07*qoM6N<$g73XN@&Et; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..43f8e1ac1d37959e796930e46fa668c2b17b83c8 GIT binary patch literal 5014 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

)ivDC&wD<_s?jBCtpk^xjLDzn>} zRc6^OCJD`j7O%X1gO=os4h|{D1M(N&=GynZOV#bLcXyl9pMSv7$M18z`x&P?X8om? zsfR-zJ$S&4YcDb0EhxH+O!J)mg9F^H_qg|?cQB8)(PD`0ZjfKNfHb>=@rXP>A$KF9 zR@5<&n3P~GNxMn9dJdPhnCJ<$wO|d#BtU|C5HVPjq+k=uSTU|^?md2pAC;Uw-XV^M zTs(DT%bToRd!D4TPHQklcOJ0+^cDvX4mmE4$ZZRw3Foo}?BEzv3_1Gr5x0Nxzo~xq zU)g>11Y4zSU3!(RSFWbfSx0YipJf531OFsbG4pFg0HwO~*(VaQaP zWLn|kguUH|JUu$WJ>BNBokt9-LypI%RBpm@cagZVOvo%lEjgGBcrt=2YhW7L3_NiG zBL=YwD%d1#V(P$X(1!@EJfoaC##K!sP}qC)e|bpn-fb={bXZPms_~Gi3rvfe($$m_ za7;j?bwFnajX-a}4>_6biPyRc{+NA1FNR6XE*ulpk)p$T7f%TU!(QCGO z)bH`If5IeI7`u*SDUu|Z5W$97DJ_N|U`Y=m$4mp5=&%_yB4i4_f5K!mKx~RHC%9C=Oev?4)sC{X zzD2(A96!6i&AsD3QI&;-b6DHL8-w$aL?AH<-p|``8nGG#&6-G}6jO_~67sqt3`bCp zS@KhAfkI&%YHUo&Q$yuyPOB1$mX;WnI&CIZO;JN75S-A>5*Df{(-=`>Kmx&yNu-=H zA<*m^S}V^}rOUi`=Lv_k&{|q2&)S%vIPs_?q$Z-lp)!w}#uA9wM$%vs9g^fE&5T%% zD2GE<^DY;jU*J;KX1^Nnuo$pA8BvTUfTeP;{)ot#G^Q|5DYfYX|FA^&|Rb` zN~VJWV~NDHq=_d<3^a1awnY7~+Tr;*xvrcKAxr=Ts2SZpv*Pw}A!hO}Bunyp2$<|2jnOrk?WK(d^C zeTmU@N~0>UenK?Dw9#bJ=u+7(K^nvuNvwc)YVT3+uxc;{V)R%Hk+}teCFA>WjPp2>Os8{qQ~P91EOJ$>_BRS80^f22&kH8PZ5IxHiUrDh!sl! zj7>4t;-cbwLRK517X*g{K!w=ofNfy1jOZq)&G2cPEN#zB8){q(cq}GKFg7Q~l&FHJ z!&0HX#Q2KTG^kOWq^L#=4qTYybqdB1A~UG<3<40HHN>J|lf+#RSrle96B07#wl7 zo&*}fpgtl-nYFPZUKBx5BJ%Y=`X})>Q4~GHcqRN!82tYy#b^-B41f_uWCo!VYb*|H z5W*M!x1eXBAI#gQf48JY=NPm>VkE{u3<0YVV+Cu?JWM?U`92q54ix|0T7VJ}(9i!9 ggh>rX1XoV@KcO<{907*qoM6N<$g3eY^2LJ#7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a2a4eebaa832b8b45049271f876d39e5ec377129 GIT binary patch literal 4629 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 LNkvXXu0mjfr+VZs literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7219d8502e96252d0232653f5e691168069a2d33 GIT binary patch literal 4165 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 P00000NkvXXu0mjfF+{zZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2e905c7bc8775a0b6b21e9bcea987eb5e95d0aed GIT binary patch literal 4497 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1c672f09772fefe753e32e00e93c60c85e61dc34 GIT binary patch literal 4300 zcmV;-5Hs(IP)WFU8GbZ8()Nlj2>E@cM*01z-qL_t(&-o={vk7dVI z$3N#(-TRi_rk7b`yKRr{@pzf>#5Q)E*l|KEV}&>(Avl2ug^&cn|9~_<08&6A3KHQ5 z2to)&fD{yh0Vl!)l+`gIalFNz8QbGoJWEgSZ@agu4nNfGo*vtXk&NV$p8CFb?|XI5 zsk45+E%P%S3wR6%rXUFsh~mIJ-YU9UyfWJ=TK7+C5hx~z$E__Q(_w7P!b*m6vL|_mOvFz1*f=XV>5_= zlkyG1ZxwRm^9_Mtzb;2EabQB;&;ZwbIRXecgBCCnPc(;+@YHDYeBAQvr4oWmyrp$H za`}kI6S5XSGI+Ct6^nqw07U>J0dYfc(Y%uYl0YCP|CSWE9JxFs6cD2kZi}{-<#9kz zvM*ZxhGtt_T&Xz(OL$NzY~g5I6u9&t*rL~4T9+f22PKfR38I*#MmPlrF=b&j zCK3@5%SxaZO(bda2W_j#Oy80MmnGL5zO{AJq?@uRHJ*SFbNUmq7n<9Q4cqFy*5dzl z{V#HPX?!WBY*Be#u=p+SWvgRKBRCsBmbNHS03KoVmV@~JlFQp>nhcvJtTs}ir6%h@ zbh$uDf;JGW&Yvad7?h3s)Id#*klql&PK7$zB{8C^q?EQ4xV#USDVIlcs&cmJxI}4! z2f>+T8^^{o)G;(cyk@W0$Us^Qq{x}7uF$rU`s-O}r;v;ojg$=UJShfLlw=7tP<6x< zao&^C#oT{6a(PvUq`2uMVnz;AZG7Ky>QY6u#Ep$}{Y^JAHPL0~>~{8CeGN7>$EY(& zMA1>7o#UQ=`r6;|{P&+A4st)_S_QGH2r*Eo!&0Plju0c-xSpku+lpwYzdX5I>59V; z5d@KZQ*vj9;wUWPft&ArhzB2di1Q0)nVjtMXJ7gfYrTrpn&#ED5p{QrnH_U`bzM^yo)lAdBN4oFm>FIj$&A7kT#j5q zf%ya-by$o!E=}mRrr3Y~ZQSzC_pp8U4V--CETOl;_9@2~{_yv>`QUAQ;ZOgPpT2sQ zqpzKzl0e}Tb#I-O3(M41;NYI^96x@XfBWV)nA<+XBOm)1-FC@;e(T?P^y^>4Vj_k> zp^}@sl#nQR@2Nvg^f{E(G&hcthy*hwm9sPPzS&{qAS?D?-t&7|A**o zD=7qKCdRpP&n{-Cr%@-AWyuE~e1NB({s}+&;S*f3dpAqxFEF=l1{Gt}A21sAs8e7t z98uRb{lNg|3qJ6`gFN)$FYqT{{36dj{S3=X%a9VKQ&On$4peebr{>xLPVzOHW2V4x z2Cf5#RUD-+2q{uU;ncav+!ePmSbLe3#bsiQNJz8{!m!Wxp7;*EL7xvl{1N{C@4rfa zSkoU=s48_;bHz3DOzfDWXctV+ZDW4#wTw+n@alqH2<{8(#cU{k6NrDnEHP*CExauH3SoXa1-~Yi=&%Vgf=U?QR zAN&_DKK&Fg9{Cx^P8?_V?mc|^*M5UAsQK169;Y)qhc8>K_tuGG-1FZ1xa*#~*uHBA zs4%x<7t_=KL?z@o#2M^(VLD}lCWA`5R?Z1h#-2oM@-Fq#nLZ|~?l@ucuGdPf<;foet zl@JonL)nsi5-BU?x?}#ZchI#&5nv!5o!`gU{55Rv%rGelS-HSOOWCn|8*76VjvPJ0 zv7<*>T3+V%+YfQ@ru}^Nt6!lz*`gdPdF9y4oIY`e-ui%c*=5hJJzR6`bxhC9aCTvV z<+T;cPK$Q8Lw9_Pxg9$>bm$PfcI{$V1=Km}08xYx1$BsJDn~B`T@uPHm&Epb*c_sYzZt{xa35M~p_hGtRZw-^>ly@25W+^2~G3 zvb?&&HGB7R)4^MroSNbtSMFikwrwmdEpq0}8P-=;X|-DPdp*nyi-~qA^j20m`s_0- zoqids)*&WJb@-~L6mVCxIUIq?dj^(p8W_`>*4hPD7G7hta0*#lrZuc!SmRnPT&II? z6_f&N7ZzFV^{J9#l`u0tjT)Rf^)lV@S?;_4ehwbEjn&l^UU=aJj=%g0SI=KbjFFwY zb}~CNOI6pbtoP_u6;c$mCnuPgoue$2sp)CvuXqR7@14h!IKOZnHRyU!Gi3ibW|G?` zBo_xG=i3@jOi-mnof36|IzmcKN(0KmfqDj`ih$6Wm_&+#et!tV#5?A%=0hL;D7PHE zjqiN>yZqzVzeWfZ^YimO^2uLi_mx*+&Jh!wJNp{vPM>7BzQWql68#H(sxV@8@f7`q zSFphnsSb!i8I6{K+&r1bg(MdzD{I~hO2UYuL{X9n!Hk#^B%$B080!?cqQ$5R3_^`L zkNARU0>Lq?1Bm0mZMXBO-~0?^r^}!J#g}>N$saqdfljiN1$*6`s%FrYA7MULF;KGmzdiVfxV5-3y#5D@UrfCvf%sqHiFz2yGJu}&C zeIZX&h7v^>#YAsZQOAUoT~dUq3YdDf@4kwMKl*X*diT8?Id+sk{-e(mR~ET$-(Ci5 ztE~41w7S#u>O>VCuB}v2*}i={d-rT-_hgG7|KPh+i)YxTD|C`&1q2A{^SICeQvso6 zM%)-FC6~w8j7tt7#)yiLj9mNGF$|GGU1LBoImPN)k3m0xDAF3IHL;Bw_TS1Qk9>kX zd-m|RU-=sz|Hfm?PS4P3kJ0XoGcmn`nYkTw#yw?sg1R0tT6~R_)2}dCT43q)ac2Ag zUj^iy(;G41GumL081n>K7zE3`Nz_Ym9ubHUyvyC8Np5vRGy;NnM?X}QtuCVogJGnq z4b1RkQ_Rlo;KRT4%iMbC4vrpsg$s+Tkc@iJr|$JQdEz)r%jZ~KU!(3XGbZa4)jC*Y zqSHZoeYyhjbF)~q3|nucuPQ)@8Imz+z$(pwjcEg`7=`BR81qS~_av2!*<81u;+sstfiM8`xfqHY_gfm!YhH|}i$Th8a$2?0b<-y(`&;^_?o z{o#noscBaGeI_TjQO8I<8W9HlP57`-A*70;>>z2#OleFO&}n-L6I#s>w^6dT-B!IDwBTsJ~pQy_>q)Im|SsfQ!V(xatfF%YD}w>xOr zrae~Dav((;RZmfuxHQD6fJTr3YAK(9Zb_erWxQIT&~OnW(=C9M36L6;z}O&IEM1yr zQ_OA^QjVz#s8fw28*l0*f8SBwlfdx8@GWSNLBhYG$f<=m$@XDK? zKvt$f12(vUO9+RC%#$}^08BPjF-^JQ+Cp}i1CPU`rbEQk@Y*MZNFw1~8wy84Pz?kr zNR+r_xR}5M6``e>Q1RMip!9lXMC>9E-S~)_Hg1$iTR>R5Y?j_+|es3n5M)OWyhx=~` z*8oaeZ|R1)8`nu&$(T(gFS2J&T<(Fy5DU1JkOm@2*3=EBP;>~1Jgi8hNEH&G@EfFO z%44K~NZL&K$-qxE7O$FgT4XmP5VugMyeaeL-jtEsV6dRr%49YS#a42#016U>-Wx4d zZV24$mGTJ}0tH6V%q_mstiVXiY#A3w2ti7UH014ELziiFA(1qqW|>bCL!;$NjDm;* z%FUdeM2S++mg;R)AgDvhZN69@Ax&I%L#+@6-x7!xQ5m8L1vQo+N{zGlwkpsLk=Pi~ z*+xk0: + 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 = ' + + + + + {{ 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 %} +

+ {% csrf_token %} + {{ form.as_p }} + + +{% 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

+ + + + + + +{% for ore in ores %} + + + + +{% endfor %} + {% for item in items %} + + {% if item.primary_recipee %} + + {% with item.primary_recipee as recipee %} + + + + {% endwith %} + + {% else %} + + {% endif %} + + {% endfor %} +
What ya wannaWhat ya needze
{{ ore.name }}N/A{% if ore.typ == 13 %} (This is a component you cannot craft){% endif %}
+ {{ 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 %} +
+
+ + + [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 %} +
+
+
+
+ +
+
    + {% for i1 in item.crafting_used_in %} + {% with i1.crafting.output as ci %} + {% if ci.pk != recipee.output.pk %} +
  • {{ ci.html }}
  • + {% endif %} + {% for i2 in ci.crafting_used_in %} +
  • {{ i2.crafting.output.html }} ({{ci.html}})
  • + {% endfor %} + {% endwith %} + {% 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

+ + + + + + + + + + + {% for item in items %} + {% if item.primary_recipee %} + + + + {% with item.primary_recipee as recipee %} + + + + + {% endwith %} + + + {% endif %} + {% endfor %} + +
Source Crafts intoAlso used in
+
+ {% if item.icon %}{% endif %} {{ item.name }} + {% if item.sell_price %}
Sell: {{item.sell_price}} cr{% endif %} +
+
+
{% 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 %} +
+
+
+
+
    + {% for i1 in item.crafting_used_in %} + {% with i1.crafting.output as ci %} + {% if ci.pk != recipee.output.pk %} +
  • {{ ci.html }}
  • + {% endif %} + {% for i2 in ci.crafting_used_in %} +
  • {{ i2.crafting.output.html }} ({{ci.html}})
  • + {% endfor %} + {% endwith %} + {% 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[^\(]+)\((?P\w+)\);\s+killer\s(?P[^\s]+)\s*"), + re.compile(r"^Killed\s(?P[^\;]+);\s+killer\s(?P[^\s]+)\s+.*"), + ] + +class Captured(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Captured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:(?P.*)|.*)") + +class AddStack(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^AddStack\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\.\snew\sstacks\scount\s(?P\d+)") + +class Cancel(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Cancel\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sfrom\s'(?P[^']*)'") + +class Scores(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Scores\s+-\sTeam1\((?P(?:\d+|\d+\.\d+))\)\sTeam2\((?P(?:\d+|\d+\.\d+))\)") + +class Uncaptured(CombatLog): + """ + Variables: + - objective (which was uncaptured (most likely something like VitalPointXY)) + - team (number) + - attackers (split by space, names of the attackers, contains bots) + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Uncaptured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:\s(?P.*)|)") + +# Special classes +class GameEvent(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + # game session identifier. + re.compile(r"^Connect\sto\sgame\ssession\s+(?P\d+)"), + # start gameplay identifier. + re.compile(r"^Start\sgameplay\s'(?P\w+)'\smap\s+'(?P\w+)',\slocal\sclient\steam\s(?P\d+)"), + # pve mission identifier. + re.compile(r"^Start\sPVE\smission\s'(?P\w+)'\smap\s+'(?P\w+)'"), + ] + + @classmethod + def _log_handler(cls, log): + if log.startswith('======='): + return True + return False + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + # small override to remove trailing "="s in the matching. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '').strip('=').strip()) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + self.trash = True + + def clean(self): + if 'log' in self.values.keys(): + del self.values['log'] + +class PVE_Mission(CombatLog): + """ + - mission: contains the mission id. + - message: contains the pve mission message, like starting rounds, waves, etc. + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^PVE_Mission:\s'(?P[^']+)'.\s(?P.*)") # this is very general, but we dont care for pve now. + +class Looted(CombatLog): + """ + called on looting in openspace. + - loot contains the loot id. + - container contains the container looted from. + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Looted\s'(?P[^']+)'\sfrom\s'(?P[^']+)'") + +class Dropped(CombatLog): + """ + called on dropping in openspace. + - loot contains the loot id. it can be '' + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Dropped\sloot\s'(?P[^']+)'") + +class Set(CombatLog): + """ + called on setting "relationship" / OpenSpace + Variables in values: + - what (relationship) + - name (who do i set?) + - value (to what value?) + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Set\s(?P\w+)\s(?P[^\s]+)\sto\s(?P\w+)") + +class SqIdChange(CombatLog): + """ - number: player number + - name: player name + - old_sqid: sqid of player before + - sqid: new player sqid + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Player\s(?P\d+)\((?P[^\)]+)\)\schanged\ssqid\sfrom\s(?P\d+)\sto\s(?P\d+)") + + @classmethod + def _log_handler(cls, log): + if log.startswith('Player'): + return True + return False + +class Mailed(CombatLog): + """ has no information. only that loot has been mailed """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("Mailed\sloot") + +class UserEvent(CombatLog): + """ special class for combat logs that might be associated with the playing player """ + __slots__ = CombatLog.__slots__ + @classmethod + def _log_handler(cls, log): + if log and 'earned medal' in log: + return True + elif log: + logging.debug('UserEvent saw unknown line:\n%s' % log) + return False + +# Action? +COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal, + Gameplay, #? + Scores, + Killed, Captured, AddStack, Cancel, Uncaptured, + # undone openspace: + PVE_Mission, Looted, Set, Dropped, + SqIdChange, Mailed, # unknown if these are important... + # always last: + GameEvent, UserEvent, + Stacktrace, + ] + diff --git a/src/scon/logs/game.py b/src/scon/logs/game.py new file mode 100644 index 0000000..b5052d6 --- /dev/null +++ b/src/scon/logs/game.py @@ -0,0 +1,195 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from logs.base import Log, L_WARNING, Stacktrace +import re +""" + This deals with the Game.Log file + This file records lots of junk, but is needed to establish actions taken between combat sessions, + or retrieve more detailed information about running instances. + It is also the typical place for a Stacktrace to happen. + + +-------------------------------------- +Interesting Lines: + +23:16:27.427 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg' +23:16:36.214 | ====== starting level: 'levels/mainmenu/mainmenu' ====== +23:16:38.822 | ====== level started: 'levels/mainmenu/mainmenu' success ====== +23:16:44.251 | ====== starting level: 'levels\mainmenu\mm_empire' ====== +23:16:46.464 | ====== level started: 'levels\mainmenu\mm_empire' success ====== + +--- Date: 2014-07-18 (Fri Jul 2014) Mitteleuropäische Sommerzeit UTC+01:00 + +23:55:55.517 | MasterServerSession: connect to dedicated server, session 6777304, at addr 159.253.138.162|35005 +23:55:55.543 | client: start connecting to 159.253.138.162|35005... +23:55:55.683 | client: connected to 159.253.138.162|35005, setting up session... +23:55:55.886 | client: ADD_PLAYER 0 (OregyenDuero [OWL], 00039C86) status 6 team 1 group 1178422 +23:55:55.886 | client: ADD_PLAYER 1 (R0gue, 0012768A) status 6 team 2 group 1178451 +23:55:55.886 | client: ADD_PLAYER 2 (g4borg [OWL], 0003A848) status 1 team 1 group 1178422 +23:55:55.886 | client: ADD_PLAYER 3 (WladTepes, 001210D8) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 4 (oberus [], 000FE9B2) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 5 (TheGuns58, 00121C58) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 6 (Belleraphon, 0004C744) status 2 team 2 +23:55:55.886 | client: ADD_PLAYER 7 (TopoL, 00007E1F) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 8 (unicoimbraPT, 000C4FAC) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 9 (AeroBobik [], 00082047) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 10 (Samson4321 [], 000B93AF) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 11 (nol [], 00069165) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 12 (Pudwoppa, 000334A4) status 2 team 2 +23:55:55.886 | client: ADD_PLAYER 13 (IgorMad [], 000D2AF3) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 14 (YokaI, 000F1CC9) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 15 (MrAnyKey [], 0012246C) status 6 team 2 group 1178451 +23:55:55.886 | client: ADD_PLAYER 30 ((bot)David, 00000000) status 4 team 1 +23:55:55.886 | client: ADD_PLAYER 31 ((bot)George, 00000000) status 4 team 2 +23:55:55.886 | client: server assigned id 2 +23:55:55.886 | client: got level load message 's1340_thar_aliendebris13' +23:55:55.889 | reset d3d device +23:55:56.487 | ReplayManager: stopping activity due to map change +23:55:56.576 | ====== starting level: 'levels\area2\s1340_thar_aliendebris13' KingOfTheHill client ====== + + +""" + +class GameLog(Log): + __slots__ = ['matcher', 'trash', '_match_id', 'values'] + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == '': # we handle only logs with empty logtype. + return cls._is_handler(log) + return False + + @classmethod + def _is_handler(cls, log): + return False + + def __init__(self, values=None): + self.values = values + self.reviewed = False + + def clean(self): + if 'log' in self.values.keys(): + del self.values['log'] + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '')) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + self.trash = True + + def explain(self): + ''' returns a String readable by humans explaining this Log ''' + return self.values.get('log', 'Unknown Game Log') + +class WarningLog(Log): + __slots__ = ['trash',] + trash = True + + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == L_WARNING: + return True + return False + + def __init__(self, values=None): + pass + +######################################################################################################## +# Individual logs. + +class SteamInitialization(GameLog): + matcher = [ + re.compile(r"^Steam\sinitialized\sappId\s(?P\d+),\suserSteamID\s(?P\d+)\|(?P\d+)\|(?P\w+),\suserName\s'(?P[^']+)'"), + ] + +class MasterServerSession(GameLog): + matcher = [ + re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P\d+)|at addr (?P\d+\.\d+\.\d+\.\d+)\|(?P\d+))+"), + re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P\d+),\sat\saddr\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+),\szoneId\s(?P\d+)"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('MasterServerSession'): + return True + return False + + +class ClientInfo(GameLog): + # Note: clinfo holds the subtype of this packet. + matcher = [ + # connecting; addr, port + re.compile(r"^client\:\sstart\s(?Pconnecting)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.\.\."), + # connected; addr, port + re.compile(r"^client\:\s(?Pconnected)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+).*"), + # ADD_PLAYER; pnr, player, clantag, player_id, status, team, group + re.compile(r"^client\:\s(?PADD_PLAYER)\s+(?P\d+)\s+\((?P[^\s\,]+)(?:\s\[(?P\w+)\],|\s\[\],|,)\s(?P\w+)\)(?:\s|status\s(?P\d+)|team\s(?P\d+)|group\s(?P\d+))+"), + # assigned; myid + re.compile(r"^client\:\sserver\s(?Passigned)\sid\s(?P\d+)"), + # level; level + re.compile(r"^client\:\sgot\s(?Plevel)\sload\smessage\s'(?P[^']+)'"), + # leave; pnr + re.compile(r"^client\:\splayer\s(?P\d+)\s(?Pleave)\sgame"), + # avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss + re.compile(r"^client\:\s(?PavgPing)\s(?P[^;]+)(?:\;|\s|avgPacketLoss\s(?P[^;]+)|avgSnapshotLoss\s(?P[^;$]+))+"), + # closed; dr + re.compile(r"^client\:\sconnection\s(?Pclosed)\.(?:\s|(?P.*))+"), + # disconnect; addr, port, dr + re.compile(r"^client\:\s(?Pdisconnect)\sfrom\sserver\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.(?:\s|(?P\w+))+"), + # ready; + re.compile(r"^client\:\ssend\s(?Pready)\smessage"), + # init; ping + re.compile(r"^client\:\sgot\s(?Pinit)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P\d+)"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('client:'): + return True + return False + + +class StartingLevel(GameLog): + # level, gametype, unknown_gametype + matcher = [ + re.compile(r"^======\sstarting\slevel\:\s'(?P[^']+)'(?:\s|client|(?PKingOfTheHill)|(?P[^\s]+))+======"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('====== starting'): + return True + return False + + +class LevelStarted(GameLog): + matcher = [] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('====== level'): + return True + return False + + + +GAME_LOGS = [#SteamInitialization, + MasterServerSession, + ClientInfo, + StartingLevel, + #LevelStarted, + Stacktrace, + ] \ No newline at end of file diff --git a/src/scon/logs/logfile.py b/src/scon/logs/logfile.py new file mode 100644 index 0000000..aded160 --- /dev/null +++ b/src/scon/logs/logfile.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Author: Gabor Guzmics, 2013-2014 + + LogFile is an object capable to load SCon Logfiles and parse their ingredients + It can be extended by overriding resolve to understand Logentries further. + Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary + data by setting the LogFile._data yourself. +""" +from .logstream import LogStream + + +class LogFile(LogStream): + def __init__(self, fname=None, + folder=None): + super(LogFile, self).__init__() + self.fname = fname + self.folder = folder # only for custom tagging. + self._data = None + + def read(self, fname=None): + fname = fname or self.fname + try: + f = open(fname, 'r') + self.set_data(f.read()) + finally: + f.close() + + def filter(self, klasses): + ret = [] + for line in self.lines: + for k in klasses: + if isinstance(line, k): + ret.append(line) + break + return ret + + def parse(self): + # parse _data if we still have no lines. + lines = [] + if self.has_data(): + data_lines = self.get_data( + ).replace('\r', '\n' + ).replace('\n\n', '\n' + ).split('\n' + ) + for line in data_lines: + line = self.pre_parse_line(line) + if not line: + continue + else: + lines.append(line) + elif self.lines: + lines = self.lines + if lines: + for line in lines: + self._parse_line(line) + + diff --git a/src/scon/logs/logfiles.py b/src/scon/logs/logfiles.py new file mode 100644 index 0000000..8b578c1 --- /dev/null +++ b/src/scon/logs/logfiles.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Resolves Logs. +""" + +from logfile import LogFile +from combat import COMBAT_LOGS +from game import GAME_LOGS +from chat import CHAT_LOGS + +class LogFileResolver(LogFile): + ''' dynamic logfile resolver ''' + resolution_classes = COMBAT_LOGS + + def __init__(self, *args, **kwargs): + super(LogFileResolver, self).__init__(*args, **kwargs) + self.resolution_classes = self.resolution_classes or [] + + def resolve(self, line): + for klass in self.resolution_classes: + if klass.is_handler(line): + return klass(line) + return line + +class CombatLogFile(LogFile): + ''' Combat Log ''' + def resolve(self, line): + for klass in COMBAT_LOGS: + if klass.is_handler(line): + return klass(line) + return line + +class GameLogFile(LogFile): + ''' Game Log ''' + def resolve(self, line): + for klass in GAME_LOGS: + if klass.is_handler(line): + return klass(line) + return line + +class ChatLogFile(LogFile): + ''' Chat Log ''' + def resolve(self, line): + for klass in CHAT_LOGS: + if klass.is_handler(line): + return klass(line) + return line + diff --git a/src/scon/logs/logstream.py b/src/scon/logs/logstream.py new file mode 100644 index 0000000..bd1a314 --- /dev/null +++ b/src/scon/logs/logstream.py @@ -0,0 +1,144 @@ +""" + A LogStream is supposed to: + - parse data feeded into it. + - yield new objects + - remember errors + + LogStream.Initialize: + - initialize the logstream in some way. + + LogStream.Next: + - once initialized, read your stream until you can yield a new class + the next function reads the read-stream ahead. + empty lines are omitted + it tries to match the data into a new class and yields it + if it runs into trouble, it just outputs the line for now. + + InitializeString: + - init with a data blob + - nice for trying it on files + + @TODO: look at how file streams in python are implemented and find a good generic solution + combine it with the lookup for "watching files being changed", to create a program which listens to the logs live + @see: monitor.py + @see: watchdog https://pypi.python.org/pypi/watchdog +""" +from .base import Log +import re +from logs.base import Stacktrace +import logging +RE_SCLOG = r'^(?P\d{2,2})\:(?P\d{2,2})\:(?P\d{2,2})\.(?P\d{3,3})\s(?P\s*[^\|\s]+\s*|\s+)\|\s(?P.*)' +R_SCLOG = re.compile(RE_SCLOG) + +class LogStream(object): + def __init__(self): + self.lines = [] + self._data = None + self._last_object = None + + def add_to_queue(self, line): + # adds a line to the queue + pass + + def new_packets(self, finish=False): + # yields new packets. + # processes the queue a bit. + # yields new packets, once they are done. + # watch out not to process the last packet until it has a follow up! + # finish: override and yield all packets to finish. + pass + + ##################################################################### + def has_data(self): + if self._data: + return True + + def set_data(self, data): + self._data = data + + def get_data(self): + return self._data + + def clean(self, remove_log=True): + # cleans the logs by removing all non parsed packets. + # remove_log: should i remove the raw log entry? + lines = [] + for l in self.lines: + if isinstance(l, Log): + if l.unpack(): + if not getattr(l, 'trash', False): + if remove_log: + l.clean() + lines.append(l) + else: + print type(l) + print l + self.lines = lines + self._unset_data() + + data = property(set_data, get_data) + + def _unset_data(self): + self._data = None + + def pre_parse_line(self, line): + if not isinstance(line, basestring): + return line + elif line.startswith('---'): + return None + elif line == '' or line == '\n': + if line == '\n': + logging.debug('Empty Newline detected.') + return None + else: + # get the timecode & logtype + m = R_SCLOG.match(line) + if m: + g = m.groupdict() + if 'logtype' in g.keys(): + g['logtype'] = g['logtype'].strip() + return g + else: + return line + return None + + def _parse_line(self, line): + # add the line to my lines. + if line is not None: + o = line + if isinstance(line, basestring): + # Unknown Log? + if not line: + return + # It might be a stacktrace. inject it./ + if Stacktrace.is_handler(o): + o = Stacktrace(o) + self._last_object = o + else: + #if isinstance(self._last_object, Stacktrace) and line.startswith('\t'): + # logging.debug('Workaround: %s, worked: %s' % (line, self._last_object.append(line))) + # return + if self._last_object is not None: + self._last_object.unpack() + if self._last_object.append(line): + return + logging.debug('#: %s' % line) + o = None + elif isinstance(line, dict): + # Unresolved Log. + o = self.resolve(line) + self._last_object = o + else: + self._last_object = o + if o is None: + self._last_object = None + return + self.lines.append(o) + + def parse_line(self, line): + return self._parse_line(self.pre_parse_line(line)) + + def resolve(self, gd): + # gd is a dict. + # try to find a class that is responsible for this log. + return gd diff --git a/src/scon/logs/session.py b/src/scon/logs/session.py new file mode 100644 index 0000000..3177f6a --- /dev/null +++ b/src/scon/logs/session.py @@ -0,0 +1,209 @@ +""" + Logging Session. +""" +import zipfile, logging, os +from logfiles import CombatLogFile, GameLogFile, ChatLogFile + +class LogSession(object): + """ + A basic logsession. + deal with data as it comes along, and output interpretable data for the outside world. + + """ + pass + +class LogFileSession(LogSession): + """ + The Log-File-Session is supposed to save one directory of logs. + It can parse its logs, and should become able to build up its internal + structure into Battle Instances etc. + """ + VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs. + + def __init__(self, directory): + ''' if directory is a file, it will be handled as a compressed folder ''' + self.battles = [] + self.user = None + self.files_parsed = [] + + # various logfiles used. + self.combat_log = None + self.game_log = None + self.chat_log = None + # self.net_log = None + + self.directory = directory + self._zip_source = None + self.idstr = None # id string to identify this log instance. + self._error = False + + def clean(self, remove_log=True): + if self.combat_log: + self.combat_log.clean(remove_log) + if self.game_log: + self.game_log.clean(remove_log) + if self.chat_log: + self.chat_log.clean(remove_log) + + + def validate(self, contents=False): + """ + - validates if the logfiles are within this package. + - sets the idstr of this object. + @todo: in-depth validation of logs, on contents=True. + """ + self._zip_source = os.path.isfile(self.directory) or False + v = False + try: + if self._zip_source: + v = self._unzip_validate() + if v > 0: + self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower() + else: + v = self._validate_files_exist() + if v > 0: + self.idstr = os.path.split(self.directory)[1].lower() + except: + return False + return v + + def parse_files(self, files=None): + ''' parses the logfiles ''' + # perform simple validation. + if self._zip_source is None: + self.validate(False) + if self._zip_source: + self._unzip_logs(files) + else: + if files is None: + files = self.VALID_FILES + if 'combat.log' in files and not 'combat.log' in self.files_parsed: + self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log')) + self.combat_log.read() + self.combat_log.parse() + self.files_parsed.append('combat.log') + if 'game.log' in files and not 'game.log' in self.files_parsed: + self.game_log = GameLogFile(os.path.join(self.directory, 'game.log')) + self.game_log.read() + self.game_log.parse() + self.files_parsed.append('game.log') + if 'chat.log' in files and not 'chat.log' in self.files_parsed: + self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log')) + self.chat_log.read() + self.chat_log.parse() + self.files_parsed.append('chat.log') + + def determine_owner(self): + ''' determines the user in the parsed gamelog ''' + pass + + def parse_battles(self): + ''' parses the battles ''' + pass + + def _unzip_logs(self, files=None): + z = zipfile.ZipFile(self.directory, "r") + try: + for filename in z.namelist(): + fn = os.path.split(filename)[1] or '' + fn = fn.lower() + if fn: + if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed: + self.combat_log = CombatLogFile(fn) + self.combat_log.set_data(z.read(filename)) + self.combat_log.parse() + self.files_parsed.append('combat.log') + elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed: + self.game_log = GameLogFile(fn) + self.game_log.set_data(z.read(filename)) + self.game_log.parse() + self.files_parsed.append('game.log') + elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed: + self.chat_log = ChatLogFile(fn) + self.chat_log.set_data(z.read(filename)) + self.chat_log.parse() + self.files_parsed.append('chat.log') + except: + self._error = True + return + finally: + z.close() + + def _unzip_validate(self): + z = zipfile.ZipFile(self.directory, "r") + found = 0 + for filename in z.namelist(): + fn = os.path.split(filename)[1] or '' + fn = fn.lower() + if fn and fn in self.VALID_FILES: + found += 1 + z.close() + return found + + def _validate_files_exist(self): + found = 0 + for f in self.VALID_FILES: + if os.path.exists(os.path.join(self.directory, f)): + found += 1 + return found + +class LogSessionCollector(object): + """ + finds sessions in a directory, a.k.a. you load the log directories + of SC into sessions. + + - find_sessions: only find and instantiate sessions. + - collect: validates each found session and returns them as list. + - collect_unique: instead of a list, a dict is returned, where each + session can be accessed via its idstr. does not parse/validate sessions. + """ + def __init__(self, directory): + self.initial_directory = directory + self.sessions = [] + self.find_sessions() + + def find_sessions(self): + for f in os.listdir(self.initial_directory): + full_dir = os.path.join(self.initial_directory, f) + if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'): + self.sessions.append(LogFileSession(full_dir)) + + def collect(self): + sessions = [] + for session in self.sessions: + try: + if session.validate(): + sessions.append(session) + except: + continue + return sessions + + def collect_unique(self): + ''' collects all sessions into a dictionary ordered by their idstr. + sessions without idstr, or already existing (first served) are omitted + parsing is not done. + ''' + # note this function resets sessions to the working ones. + self.sessions = self.collect() + sessions_dict = {} + for session in self.sessions: + if session.idstr and not session.idstr in sessions_dict.keys(): + sessions_dict[session.idstr] = session + return sessions_dict + + def clean(self, remove_log=True): + for session in self.sessions: + session.clean(remove_log) + + + + +if __name__ == '__main__': + l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28') + l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip') + + l_zip.parse_files() + print l_zip.combat_log.lines + + collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\') + print collector.collect_unique() \ No newline at end of file diff --git a/src/scon/manage.py b/src/scon/manage.py new file mode 100644 index 0000000..e6b047b --- /dev/null +++ b/src/scon/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/src/scon/monitor.py b/src/scon/monitor.py new file mode 100644 index 0000000..e6bc3ca --- /dev/null +++ b/src/scon/monitor.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Monitor a StarConflict Log Directory. +""" +import sys, os +import time +import logging +from watchdog.observers import Observer +from watchdog.events import LoggingEventHandler + +class SconEventHandler(LoggingEventHandler): + def __init__(self, monitor, *args, **kwargs): + self.monitor = monitor + return super(SconEventHandler, self).__init__(*args, **kwargs) + + def on_moved(self, event): + super(SconEventHandler, self).on_moved(event) + if not event.is_directory: + self.monitor.close(event.src_path) + self.monitor.notify_event('moved', {'src': event.src_path, + 'is_dir': event.is_directory, + 'dest': event.dest_path}) + + def on_created(self, event): + super(SconEventHandler, self).on_created(event) + if not event.is_directory: + self.monitor.open(event.src_path) + self.monitor.notify_event('created', {'src': event.src_path, + 'is_dir': event.is_directory}) + + def on_deleted(self, event): + super(SconEventHandler, self).on_deleted(event) + if not event.is_directory: + self.monitor.close(event.src_path) + self.monitor.notify_event('deleted', {'src': event.src_path, + 'is_dir': event.is_directory}) + + def on_modified(self, event): + super(SconEventHandler, self).on_modified(event) + self.monitor.notify_event('modified', {'src': event.src_path, + 'is_dir': event.is_directory}) + + + +class SconMonitor(object): + + def notify(self, filename, lines): + # notify somebody, filename has a few lines. + # this is basicly the function you want to overwrite. + # @see: self.run for how to integrate monitor into your main loop. + if self.notifier is not None: + self.notifier.notify(filename, lines) + + def notify_event(self, event_type, data): + if self.notifier is not None: + self.notifier.notify_event(event_type, data) + + def __init__(self, path=None, notifier=None): + # if you initialize path directly, you lose success boolean. + # albeit atm. this is always true. + if path is not None: + self.initialize(path) + self.notifier = notifier + + def initialize(self, path): + # initialize the monitor with a path to observe. + self.files = {} + self.event_handler = SconEventHandler(self) + self.observer = Observer() + self.observer.schedule(self.event_handler, + path, + recursive=True) + return True + + def open(self, filename=None): + # open a logfile and add it to the read-list... + f = open(filename, 'r') + new_file = { 'file': f, + 'cursor': f.tell() } + self.files[filename] = new_file + return new_file + + def close(self, filename): + # close a single file by key, does not do anything if not found. + if filename in self.files.keys(): + close_file = self.files.pop(filename) + close_file['file'].close() + del close_file + + def close_all(self): + """ closes all open files in the monitor """ + for key in self.files.keys(): + self.close(key) + + def read_line(self, afile): + # read a single line in a file. + f = afile.get('file', None) + if f is None: + return + afile['cursor'] = f.tell() + line = f.readline() + if not line: + f.seek(afile['cursor']) + return None + else: + return line + + def do(self): + ''' Monitor main task handler, call this in your mainloop in ~1 sec intervals ''' + # read all file changes. + for key, value in self.files.items(): + lines = [] + data = self.read_line(value) + while data is not None: + lines.append(data) + data = self.read_line(value) + if lines: + self.notify(key, lines) + # + + def initialize_loop(self): + ''' initializes the main loop for the monitor ''' + self.observer.start() + + def break_loop(self): + ''' call this if you want to break the monitors tasks ''' + self.observer.stop() + + def end_loop(self): + ''' always call this before exiting your main loop ''' + self.close_all() + self.observer.join() + + def run(self): + ''' Basic Standalone Main Loop implementation, do not call this in your app. ''' + # if you want to run this on its own. + # everytime any logfile is updated, print it. + self.initialize_loop() + try: + while True: + self.do() + time.sleep(1) + except KeyboardInterrupt: + self.break_loop() + self.end_loop() + +if __name__ == '__main__': + monitor = SconMonitor() + logging.basicConfig(level=logging.INFO, + format='%(asctime)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + path = os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict', + 'logs' + ) + if monitor.initialize(path) is True: + monitor.run() + \ No newline at end of file diff --git a/src/scon/qscon.py b/src/scon/qscon.py new file mode 100644 index 0000000..f02bddb --- /dev/null +++ b/src/scon/qscon.py @@ -0,0 +1,103 @@ +""" + Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files. + +""" +import os, sys, logging +import sys +import urllib2 +from PyQt4 import QtCore, QtGui +from monitor import SconMonitor +from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot + + +class SconMonitorThread(QtCore.QThread): + updated = pyqtSignal(str, list) + created = pyqtSignal(str, bool) + + def __init__(self, path): + QtCore.QThread.__init__(self) + self.path = path + + def notify(self, filename, lines): + self.updated.emit(filename, lines) + #self.mainwindow.notify_filelines(filename, lines) + #self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines))) + + def notify_event(self, event_type, data): + if event_type == 'created': + self.created.emit(data['src'], data['is_dir']) + + def run(self): + monitor = SconMonitor(self.path, notifier=self) + #self.list_widget.addItem('Starting to monitor: %s' % self.path) + monitor.run() + +class MainWindow(QtGui.QWidget): + def __init__(self): + super(MainWindow, self).__init__() + self.tab_list = QtGui.QTabWidget() + self.tabs = {} + self.button = QtGui.QPushButton("Start") + self.button.clicked.connect(self.start_monitor) + layout = QtGui.QVBoxLayout() + layout.addWidget(self.button) + layout.addWidget(self.tab_list) + self.setLayout(layout) + + + def notify_filelines(self, filename, lines): + if filename not in self.tabs.keys(): + new_tab = QtGui.QWidget() + new_tab.list_widget = QtGui.QListWidget() + layout = QtGui.QVBoxLayout(new_tab) + layout.addWidget(new_tab.list_widget) + self.tabs[filename] = new_tab + self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1]) + self.tabs[filename].list_widget.addItem(''.join(lines)[:-1]) + + def notify_created(self, filename, is_directory): + if is_directory: + print "Created Directory %s" % filename + else: + print "Created File %s" % filename + + def start_monitor(self): + self.button.setDisabled(True) + paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'), + ] + self.threads = [] + for path in paths: + athread = SconMonitorThread(path) + athread.updated.connect(self.notify_filelines) + athread.created.connect(self.notify_created) + self.threads.append(athread) + athread.start() + +######################################################################## + +def _main(): + app = QtGui.QApplication(sys.argv) + window = MainWindow() + window.resize(640, 480) + window.show() + return app.exec_() + +def main(): + r = _main() + try: + import psutil #@UnresolvedImport + except ImportError: + logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.') + sys.exit(r) + + def kill_proc_tree(pid, including_parent=True): + parent = psutil.Process(pid) + if including_parent: + parent.kill() + me = os.getpid() + kill_proc_tree(me) + sys.exit(r) + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/src/scon/test/__init__.py b/src/scon/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/scon/test/regexes.py b/src/scon/test/regexes.py new file mode 100644 index 0000000..1f0e104 --- /dev/null +++ b/src/scon/test/regexes.py @@ -0,0 +1,25 @@ + + +""" + Each Line has to be separated first into timecode and kind of log. + + + +""" + +import re +Lines = [ + "23:53:29.239 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg'", + "23:53:29.841 WARNING| ^1UiResourceManager::LoadStrings(): empty string \"gameplay_map_pve_gate\" for default language", + "01:05:08.735 CMBT | Spawn SpaceShip for player0 (OregyenDuero, #00039C86). 'Ship_Race3_L_T3'", + "03:43:29.796 CMBT | AddStack aura 'Spell_Cold_Ray' id 3492 type AURA_SLOW_MOVEMENT. new stacks count 5", + ] + +RE_TIME = r'\d{2,2}\:\d{2,2}\:\d{2,2}\.\d{3,3}\s\w+' +RE_SCLOG = r'^(?P\d{2,2})\:(?P\d{2,2})\:(?P\d{2,2})\.(?P\d{3,3})\s(?P\s*[^\|\s]+\s*|\s+)\|\s(?P.*)' +R_SCLOG = re.compile(RE_SCLOG) + +for line in Lines: + m = R_SCLOG.match(line) + if m: + print m.groups() diff --git a/src/scon/utils/__init__.py b/src/scon/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/scon/utils/steam.py b/src/scon/utils/steam.py new file mode 100644 index 0000000..893959b --- /dev/null +++ b/src/scon/utils/steam.py @@ -0,0 +1,28 @@ +""" +X:Y:Z + +X - Universe +0 Individual / Unspecified +1 Public +2 Beta +3 Internal +4 Dev +5 RC + +Y - Steam Type +0 I Invalid No +1 U Individual Yes profiles / id 0x0110000100000000 +2 M Multiseat Yes +3 G GameServer Yes +4 A AnonGameServer Yes +5 P Pending No +6 C ContentServer Unknown +7 g Clan Yes groups / gid 0x0170000000000000 +8 c, L, T Chat Yes +9 P2P SuperSeeder No +10 AnonUser No + +Z - account or group uid + + +""" \ No newline at end of file