Compare commits
117 Commits
singularit
...
preview_pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63fce4be17 | ||
|
|
86ee5292d8 | ||
|
|
23b458534f | ||
|
|
2256efacb0 | ||
|
|
28a5318e3b | ||
|
|
609ee13cd6 | ||
|
|
4216904736 | ||
|
|
496e9b56b5 | ||
|
|
b8f73a7c94 | ||
|
|
b4604f8207 | ||
|
|
68dddf2810 | ||
|
|
4c17f38b1a | ||
|
|
221a3fde14 | ||
|
|
c17e03d8d0 | ||
|
|
af9f64db5f | ||
|
|
1f82465a65 | ||
|
|
d885bd4636 | ||
|
|
c17bce55bb | ||
|
|
4137a7cda9 | ||
|
|
c92911b79a | ||
|
|
adc9fb6d00 | ||
|
|
5baf70694a | ||
|
|
f08dc97576 | ||
|
|
35094ae1ce | ||
|
|
5ac31920ee | ||
|
|
e63c3541c4 | ||
|
|
4976516d4d | ||
|
|
e042a21d32 | ||
|
|
8a22907940 | ||
|
|
a97847e644 | ||
|
|
23309a5da6 | ||
|
|
06e4a7e80f | ||
|
|
b95a10d284 | ||
|
|
2bca3ddcc8 | ||
|
|
9ef182aa99 | ||
|
|
5e56107582 | ||
|
|
972c08e7e4 | ||
|
|
091832af21 | ||
|
|
16d1891e16 | ||
|
|
40ee68e2cf | ||
|
|
bfe3b4a26d | ||
|
|
9aa1332b15 | ||
|
|
0521d242eb | ||
|
|
2b3f3773e5 | ||
|
|
4041407878 | ||
|
|
1b5e0467fc | ||
|
|
a7c346f78e | ||
|
|
3cc51aaf89 | ||
|
|
a339ae1c55 | ||
|
|
3773d1c28e | ||
|
|
41b8db346f | ||
|
|
7959593c6c | ||
|
|
aaa60cbc14 | ||
|
|
8ae5a96047 | ||
|
|
8c90b3132b | ||
|
|
1326e21f6b | ||
|
|
bfdc2161e0 | ||
|
|
9ab79af70c | ||
|
|
f0de2000bf | ||
|
|
5991d19b3e | ||
|
|
51fed996f1 | ||
|
|
f6bbc6c410 | ||
|
|
3de6b63325 | ||
|
|
dd48815f30 | ||
|
|
5608676dc8 | ||
|
|
86ab1f7444 | ||
|
|
8f51642f70 | ||
|
|
de71123a48 | ||
|
|
874cf4ef0a | ||
|
|
87e5929cb1 | ||
|
|
84b1e0ac41 | ||
|
|
539360d5f6 | ||
|
|
ca08f8d8da | ||
|
|
e1ce672569 | ||
|
|
717080b58c | ||
|
|
51696c509f | ||
|
|
4a5ae9f6f1 | ||
|
|
fa9f324f78 | ||
|
|
bcc77f11cd | ||
|
|
f737f292e3 | ||
|
|
1c18a5207c | ||
|
|
fa2b1e3821 | ||
|
|
6184753822 | ||
|
|
91a9c860ea | ||
|
|
0730ac369f | ||
|
|
aaa5a6ae18 | ||
|
|
646f3afd27 | ||
|
|
98815f2b85 | ||
|
|
95eb5a6117 | ||
|
|
dc035469ed | ||
|
|
ec4a00cdfc | ||
|
|
21937c02ff | ||
|
|
edfd446e46 | ||
|
|
7ec78b941e | ||
|
|
95bf1039c0 | ||
|
|
e6def6f5f9 | ||
|
|
de0b03630a | ||
|
|
90a2a79d5b | ||
|
|
21efd6d06a | ||
|
|
ea288a6133 | ||
|
|
23baaa7dba | ||
|
|
8008c986d3 | ||
|
|
b9efc919ea | ||
|
|
6cc6fd9468 | ||
|
|
53c9169043 | ||
|
|
9e96aac04d | ||
|
|
3395f8ebe6 | ||
|
|
1d45102100 | ||
|
|
1694d74afa | ||
|
|
9c9f1dcefa | ||
|
|
a4ca2e90f9 | ||
|
|
dbfcfd9acf | ||
|
|
8c30ee3fd3 | ||
|
|
ca17d17232 | ||
|
|
b78c0a5845 | ||
|
|
ef6e25bfce | ||
|
|
e81f7eb765 |
@@ -6,6 +6,13 @@ It provides many advanced features such as graphs and full calculations of any p
|
|||||||
|
|
||||||
Please see the [FAQ](https://github.com/DarkFenX/Pyfa/wiki/FAQ) for answers to common questions / concerns
|
Please see the [FAQ](https://github.com/DarkFenX/Pyfa/wiki/FAQ) for answers to common questions / concerns
|
||||||
|
|
||||||
|
#### A note for Linux users
|
||||||
|
pyfa currently only supports wxPython 2.8. However, there are some distros that have started to support 3.0 and subsequently dropped support for 2.8 altogether (such as Debian Jessie). If this is the case and wxPython 3.0 is the only version installed, the official pyfa releases will not run. You must either find a package for 2.8 or compile it yourself.
|
||||||
|
|
||||||
|
For Debian Jessie, wxPython 2.8 is available in Sid (the unstable repo). you can use apt-pinning to install select packages from unstable and still keep your stable system. See http://jaqque.sbih.org/kplug/apt-pinning.html for me details.
|
||||||
|
|
||||||
|
3.0 support is being worked on and can be found on the wx3 branch. It may be stable enough for you, but there are a few bugs related to it. Please see the wx3 label on the GitHub issues area for me information on known issues (biggest one currently is GTK warning spam): https://github.com/DarkFenX/Pyfa/labels/wx3
|
||||||
|
|
||||||
#### Links
|
#### Links
|
||||||
* [Development repository: http://github.com/DarkFenX/Pyfa](http://github.com/DarkFenX/Pyfa)
|
* [Development repository: http://github.com/DarkFenX/Pyfa](http://github.com/DarkFenX/Pyfa)
|
||||||
* [XMPP conference:
|
* [XMPP conference:
|
||||||
|
|||||||
73
config.py
73
config.py
@@ -1,6 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# TODO: move all logging back to pyfa.py main loop
|
||||||
|
# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
|
||||||
# Load variable overrides specific to distribution type
|
# Load variable overrides specific to distribution type
|
||||||
try:
|
try:
|
||||||
import configforced
|
import configforced
|
||||||
@@ -12,16 +17,14 @@ debug = False
|
|||||||
# Defines if our saveddata will be in pyfa root or not
|
# Defines if our saveddata will be in pyfa root or not
|
||||||
saveInRoot = False
|
saveInRoot = False
|
||||||
|
|
||||||
# Version data
|
logLevel = logging.DEBUG
|
||||||
version = "1.11.1"
|
|
||||||
tag = "git"
|
|
||||||
expansionName = "Singularity"
|
|
||||||
expansionVersion = "883859"
|
|
||||||
evemonMinVersion = "4081"
|
|
||||||
|
|
||||||
# Database version (int ONLY)
|
# Version data
|
||||||
# Increment every time we need to flag for user database upgrade/modification
|
version = "1.13.3"
|
||||||
dbversion = 7
|
tag = "git"
|
||||||
|
expansionName = "Aegis"
|
||||||
|
expansionVersion = "1.0"
|
||||||
|
evemonMinVersion = "4081"
|
||||||
|
|
||||||
pyfaPath = None
|
pyfaPath = None
|
||||||
savePath = None
|
savePath = None
|
||||||
@@ -29,10 +32,24 @@ staticPath = None
|
|||||||
saveDB = None
|
saveDB = None
|
||||||
gameDB = None
|
gameDB = None
|
||||||
|
|
||||||
# TODO: move back to pyfa.py main loop
|
|
||||||
# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
|
class StreamToLogger(object):
|
||||||
import logging
|
"""
|
||||||
logging.basicConfig()
|
Fake file-like stream object that redirects writes to a logger instance.
|
||||||
|
From: http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/
|
||||||
|
"""
|
||||||
|
def __init__(self, logger, log_level=logging.INFO):
|
||||||
|
self.logger = logger
|
||||||
|
self.log_level = log_level
|
||||||
|
self.linebuf = ''
|
||||||
|
|
||||||
|
def write(self, buf):
|
||||||
|
for line in buf.rstrip().splitlines():
|
||||||
|
self.logger.log(self.log_level, line.rstrip())
|
||||||
|
|
||||||
|
def __createDirs(path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
|
||||||
def defPaths():
|
def defPaths():
|
||||||
global pyfaPath
|
global pyfaPath
|
||||||
@@ -59,19 +76,25 @@ def defPaths():
|
|||||||
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
|
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
|
||||||
sys.getfilesystemencoding())
|
sys.getfilesystemencoding())
|
||||||
|
|
||||||
# Redirect stderr to file if we're requested to do so
|
__createDirs(savePath)
|
||||||
stderrToFile = getattr(configforced, "stderrToFile", None)
|
|
||||||
if stderrToFile is True:
|
|
||||||
if not os.path.exists(savePath):
|
|
||||||
os.mkdir(savePath)
|
|
||||||
sys.stderr = open(os.path.join(savePath, "error_log.txt"), "w")
|
|
||||||
|
|
||||||
# Same for stdout
|
format = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
|
||||||
stdoutToFile = getattr(configforced, "stdoutToFile", None)
|
logging.basicConfig(format=format, level=logLevel)
|
||||||
if stdoutToFile is True:
|
handler = logging.handlers.RotatingFileHandler(os.path.join(savePath, "log.txt"), maxBytes=1000000, backupCount=3)
|
||||||
if not os.path.exists(savePath):
|
formatter = logging.Formatter(format)
|
||||||
os.mkdir(savePath)
|
handler.setFormatter(formatter)
|
||||||
sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w")
|
logging.getLogger('').addHandler(handler)
|
||||||
|
|
||||||
|
logging.info("Starting pyfa")
|
||||||
|
|
||||||
|
if hasattr(sys, 'frozen'):
|
||||||
|
stdout_logger = logging.getLogger('STDOUT')
|
||||||
|
sl = StreamToLogger(stdout_logger, logging.INFO)
|
||||||
|
sys.stdout = sl
|
||||||
|
|
||||||
|
stderr_logger = logging.getLogger('STDERR')
|
||||||
|
sl = StreamToLogger(stderr_logger, logging.ERROR)
|
||||||
|
sys.stderr = sl
|
||||||
|
|
||||||
# Static EVE Data from the staticdata repository, should be in the staticdata
|
# Static EVE Data from the staticdata repository, should be in the staticdata
|
||||||
# directory in our pyfa directory
|
# directory in our pyfa directory
|
||||||
|
|||||||
@@ -1,32 +1,46 @@
|
|||||||
import config
|
import config
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
def getAppVersion():
|
||||||
|
# calculate app version based on upgrade files we have
|
||||||
|
appVersion = 0
|
||||||
|
for fname in os.listdir(os.path.join(os.path.dirname(__file__), "migrations")):
|
||||||
|
m = re.match("^upgrade(?P<index>\d+)\.py$", fname)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
index = int(m.group("index"))
|
||||||
|
appVersion = max(appVersion, index)
|
||||||
|
return appVersion
|
||||||
|
|
||||||
def getVersion(db):
|
def getVersion(db):
|
||||||
cursor = db.execute('PRAGMA user_version')
|
cursor = db.execute('PRAGMA user_version')
|
||||||
return cursor.fetchone()[0]
|
return cursor.fetchone()[0]
|
||||||
|
|
||||||
def update(saveddata_engine):
|
def update(saveddata_engine):
|
||||||
currversion = getVersion(saveddata_engine)
|
dbVersion = getVersion(saveddata_engine)
|
||||||
|
appVersion = getAppVersion()
|
||||||
|
|
||||||
if currversion == config.dbversion:
|
if dbVersion == appVersion:
|
||||||
return
|
return
|
||||||
|
|
||||||
if currversion < config.dbversion:
|
if dbVersion < appVersion:
|
||||||
# Automatically backup database
|
# Automatically backup database
|
||||||
toFile = "%s/saveddata_migration_%d-%d_%s.db"%(
|
toFile = "%s/saveddata_migration_%d-%d_%s.db"%(
|
||||||
config.savePath,
|
config.savePath,
|
||||||
currversion,
|
dbVersion,
|
||||||
config.dbversion,
|
appVersion,
|
||||||
time.strftime("%Y%m%d_%H%M%S"))
|
time.strftime("%Y%m%d_%H%M%S"))
|
||||||
|
|
||||||
shutil.copyfile(config.saveDB, toFile)
|
shutil.copyfile(config.saveDB, toFile)
|
||||||
|
|
||||||
for version in xrange(currversion, config.dbversion):
|
for version in xrange(dbVersion, appVersion):
|
||||||
module = __import__('eos.db.migrations.upgrade%d'%(version+1), fromlist=True)
|
module = __import__("eos.db.migrations.upgrade{}".format(version + 1), fromlist=True)
|
||||||
upgrade = getattr(module, "upgrade", False)
|
upgrade = getattr(module, "upgrade", False)
|
||||||
if upgrade:
|
if upgrade:
|
||||||
upgrade(saveddata_engine)
|
upgrade(saveddata_engine)
|
||||||
|
|
||||||
# when all is said and done, set version to current
|
# when all is said and done, set version to current
|
||||||
saveddata_engine.execute('PRAGMA user_version = %d'%config.dbversion)
|
saveddata_engine.execute("PRAGMA user_version = {}".format(appVersion))
|
||||||
|
|||||||
16
eos/db/migrations/upgrade10.py
Normal file
16
eos/db/migrations/upgrade10.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
Migration 10
|
||||||
|
|
||||||
|
- Adds active attribute to projected fits
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
# Update projectedFits schema to include active attribute
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT active FROM projectedFits LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN active BOOLEAN")
|
||||||
|
saveddata_engine.execute("UPDATE projectedFits SET active = 1")
|
||||||
|
saveddata_engine.execute("UPDATE projectedFits SET amount = 1")
|
||||||
85
eos/db/migrations/upgrade8.py
Normal file
85
eos/db/migrations/upgrade8.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
Migration 8
|
||||||
|
|
||||||
|
- Converts modules based on Carnyx Module Tiericide
|
||||||
|
Some modules have been unpublished (and unpublished module attributes are removed
|
||||||
|
from database), which causes pyfa to crash. We therefore replace these
|
||||||
|
modules with their new replacements
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
8529: ( # Large F-S9 Regolith Compact Shield Extender
|
||||||
|
8409, # Large Subordinate Screen Stabilizer I
|
||||||
|
),
|
||||||
|
8419: ( # Large Azeotropic Restrained Shield Extender
|
||||||
|
8489, # Large Supplemental Barrier Emitter I
|
||||||
|
),
|
||||||
|
8517: ( # Medium F-S9 Regolith Compact Shield Extender
|
||||||
|
8397, # Medium Subordinate Screen Stabilizer I
|
||||||
|
),
|
||||||
|
8433: ( # Medium Azeotropic Restrained Shield Extender
|
||||||
|
8477, # Medium Supplemental Barrier Emitter I
|
||||||
|
),
|
||||||
|
20627: ( # Small 'Trapper' Shield Extender
|
||||||
|
8437, # Micro Azeotropic Ward Salubrity I
|
||||||
|
8505, # Micro F-S9 Regolith Shield Induction
|
||||||
|
3849, # Micro Shield Extender I
|
||||||
|
3851, # Micro Shield Extender II
|
||||||
|
8387, # Micro Subordinate Screen Stabilizer I
|
||||||
|
8465, # Micro Supplemental Barrier Emitter I
|
||||||
|
),
|
||||||
|
8521: ( # Small F-S9 Regolith Compact Shield Extender
|
||||||
|
8401, # Small Subordinate Screen Stabilizer I
|
||||||
|
),
|
||||||
|
8427: ( # Small Azeotropic Restrained Shield Extender
|
||||||
|
8481, # Small Supplemental Barrier Emitter I
|
||||||
|
),
|
||||||
|
11343: ( # 100mm Crystalline Carbonide Restrained Plates
|
||||||
|
11345, # 100mm Reinforced Nanofiber Plates I
|
||||||
|
),
|
||||||
|
11341: ( # 100mm Rolled Tungsten Compact Plates
|
||||||
|
11339, # 100mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
11327: ( # 1600mm Crystalline Carbonide Restrained Plates
|
||||||
|
11329, # 1600mm Reinforced Nanofiber Plates I
|
||||||
|
),
|
||||||
|
11325: ( # 1600mm Rolled Tungsten Compact Plates
|
||||||
|
11323, # 1600mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
11351: ( # 200mm Crystalline Carbonide Restrained Plates
|
||||||
|
11353, # 200mm Reinforced Nanofiber Plates I
|
||||||
|
),
|
||||||
|
11349: ( # 200mm Rolled Tungsten Compact Plates
|
||||||
|
11347, # 200mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
11311: ( # 400mm Crystalline Carbonide Restrained Plates
|
||||||
|
11313, # 400mm Reinforced Nanofiber Plates I
|
||||||
|
),
|
||||||
|
11309: ( # 400mm Rolled Tungsten Compact Plates
|
||||||
|
11307, # 400mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
23791: ( # 'Citadella' 100mm Steel Plates
|
||||||
|
11335, # 50mm Reinforced Crystalline Carbonide Plates I
|
||||||
|
11337, # 50mm Reinforced Nanofiber Plates I
|
||||||
|
11333, # 50mm Reinforced Rolled Tungsten Plates I
|
||||||
|
11291, # 50mm Reinforced Steel Plates I
|
||||||
|
20343, # 50mm Reinforced Steel Plates II
|
||||||
|
11331, # 50mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
11319: ( # 800mm Crystalline Carbonide Restrained Plates
|
||||||
|
11321, # 800mm Reinforced Nanofiber Plates I
|
||||||
|
),
|
||||||
|
11317: ( # 800mm Rolled Tungsten Compact Plates
|
||||||
|
11315, # 800mm Reinforced Titanium Plates I
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
|
||||||
|
# Convert modules
|
||||||
|
for replacement_item, list in CONVERSIONS.iteritems():
|
||||||
|
for retired_item in list:
|
||||||
|
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item))
|
||||||
|
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item))
|
||||||
|
|
||||||
23
eos/db/migrations/upgrade9.py
Normal file
23
eos/db/migrations/upgrade9.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
Migration 9
|
||||||
|
|
||||||
|
Effectively drops UNIQUE constraint from boosters table. SQLite does not support
|
||||||
|
this, so we have to copy the table to the updated schema and then rename it
|
||||||
|
"""
|
||||||
|
|
||||||
|
tmpTable = """
|
||||||
|
CREATE TABLE boostersTemp (
|
||||||
|
'ID' INTEGER NOT NULL,
|
||||||
|
'itemID' INTEGER,
|
||||||
|
'fitID' INTEGER NOT NULL,
|
||||||
|
'active' BOOLEAN,
|
||||||
|
PRIMARY KEY(ID),
|
||||||
|
FOREIGN KEY('fitID') REFERENCES fits ('ID')
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
saveddata_engine.execute(tmpTable)
|
||||||
|
saveddata_engine.execute("INSERT INTO boostersTemp (ID, itemID, fitID, active) SELECT ID, itemID, fitID, active FROM boosters")
|
||||||
|
saveddata_engine.execute("DROP TABLE boosters")
|
||||||
|
saveddata_engine.execute("ALTER TABLE boostersTemp RENAME TO boosters")
|
||||||
@@ -29,7 +29,7 @@ boosters_table = Table("boosters", saveddata_meta,
|
|||||||
Column("itemID", Integer),
|
Column("itemID", Integer),
|
||||||
Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
|
Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
|
||||||
Column("active", Boolean),
|
Column("active", Boolean),
|
||||||
UniqueConstraint("itemID", "fitID"))
|
)
|
||||||
|
|
||||||
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
|
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
|
||||||
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),
|
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String, Boolean
|
from sqlalchemy import *
|
||||||
from sqlalchemy.orm import relation, mapper
|
from sqlalchemy.orm import *
|
||||||
from sqlalchemy.sql import and_
|
from sqlalchemy.sql import and_
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
|
|
||||||
from eos.db import saveddata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.db.saveddata.module import modules_table
|
from eos.db.saveddata.module import modules_table
|
||||||
@@ -27,9 +29,7 @@ from eos.db.saveddata.drone import drones_table
|
|||||||
from eos.db.saveddata.cargo import cargo_table
|
from eos.db.saveddata.cargo import cargo_table
|
||||||
from eos.db.saveddata.implant import fitImplants_table
|
from eos.db.saveddata.implant import fitImplants_table
|
||||||
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
|
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
|
||||||
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
|
from eos.effectHandlerHelpers import *
|
||||||
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
|
|
||||||
HandledProjectedFitList, HandledCargoList
|
|
||||||
|
|
||||||
fits_table = Table("fits", saveddata_meta,
|
fits_table = Table("fits", saveddata_meta,
|
||||||
Column("ID", Integer, primary_key = True),
|
Column("ID", Integer, primary_key = True),
|
||||||
@@ -47,31 +47,100 @@ fits_table = Table("fits", saveddata_meta,
|
|||||||
projectedFits_table = Table("projectedFits", saveddata_meta,
|
projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||||
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
|
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
|
||||||
Column("victimID", ForeignKey("fits.ID"), primary_key = True),
|
Column("victimID", ForeignKey("fits.ID"), primary_key = True),
|
||||||
Column("amount", Integer))
|
Column("amount", Integer, nullable = False, default = 1),
|
||||||
|
Column("active", Boolean, nullable = False, default = 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProjectedFit(object):
|
||||||
|
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
||||||
|
self.sourceID = sourceID
|
||||||
|
self.source_fit = source_fit
|
||||||
|
self.amount = amount
|
||||||
|
self.active = active
|
||||||
|
|
||||||
|
@reconstructor
|
||||||
|
def init(self):
|
||||||
|
if self.source_fit.isInvalid:
|
||||||
|
# Very rare for this to happen, but be prepared for it
|
||||||
|
eos.db.saveddata_session.delete(self.source_fit)
|
||||||
|
eos.db.saveddata_session.flush()
|
||||||
|
eos.db.saveddata_session.refresh(self.victim_fit)
|
||||||
|
|
||||||
|
Fit._Fit__projectedFits = association_proxy(
|
||||||
|
"victimOf", # look at the victimOf association...
|
||||||
|
"source_fit", # .. and return the source fits
|
||||||
|
creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit)
|
||||||
|
)
|
||||||
|
|
||||||
mapper(Fit, fits_table,
|
mapper(Fit, fits_table,
|
||||||
properties = {"_Fit__modules" : relation(Module, collection_class = HandledModuleList,
|
properties = {
|
||||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
"_Fit__modules": relation(
|
||||||
order_by = modules_table.c.position, cascade='all, delete, delete-orphan'),
|
Module,
|
||||||
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
|
collection_class=HandledModuleList,
|
||||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
||||||
"owner" : relation(User, backref = "fits"),
|
order_by=modules_table.c.position,
|
||||||
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
|
cascade='all, delete, delete-orphan'),
|
||||||
"_Fit__drones" : relation(Drone, collection_class = HandledDroneList, cascade='all, delete, delete-orphan', single_parent=True,
|
"_Fit__projectedModules": relation(
|
||||||
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
|
Module,
|
||||||
"_Fit__cargo" : relation(Cargo, collection_class = HandledCargoList, cascade='all, delete, delete-orphan', single_parent=True,
|
collection_class=HandledProjectedModList,
|
||||||
primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)),
|
cascade='all, delete, delete-orphan',
|
||||||
"_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True,
|
single_parent=True,
|
||||||
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
|
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
||||||
"_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True,
|
"owner": relation(
|
||||||
primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
|
User,
|
||||||
secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
|
backref="fits"),
|
||||||
secondary = fitImplants_table),
|
"itemID": fits_table.c.shipID,
|
||||||
"_Fit__character" : relation(Character, backref = "fits"),
|
"shipID": fits_table.c.shipID,
|
||||||
"_Fit__damagePattern" : relation(DamagePattern),
|
"_Fit__boosters": relation(
|
||||||
"_Fit__targetResists" : relation(TargetResists),
|
Booster,
|
||||||
"_Fit__projectedFits" : relation(Fit,
|
collection_class=HandledImplantBoosterList,
|
||||||
primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID,
|
cascade='all, delete, delete-orphan',
|
||||||
secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID,
|
single_parent=True),
|
||||||
secondary = projectedFits_table,
|
"_Fit__drones": relation(
|
||||||
collection_class = HandledProjectedFitList)
|
Drone,
|
||||||
})
|
collection_class=HandledDroneCargoList,
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
single_parent=True,
|
||||||
|
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
|
||||||
|
"_Fit__cargo": relation(
|
||||||
|
Cargo,
|
||||||
|
collection_class=HandledDroneCargoList,
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
single_parent=True,
|
||||||
|
primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)),
|
||||||
|
"_Fit__projectedDrones": relation(
|
||||||
|
Drone,
|
||||||
|
collection_class=HandledProjectedDroneList,
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
single_parent=True,
|
||||||
|
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
|
||||||
|
"_Fit__implants": relation(
|
||||||
|
Implant,
|
||||||
|
collection_class=HandledImplantBoosterList,
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
backref='fit',
|
||||||
|
single_parent=True,
|
||||||
|
primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID,
|
||||||
|
secondaryjoin=fitImplants_table.c.implantID == Implant.ID,
|
||||||
|
secondary=fitImplants_table),
|
||||||
|
"_Fit__character": relation(
|
||||||
|
Character,
|
||||||
|
backref="fits"),
|
||||||
|
"_Fit__damagePattern": relation(DamagePattern),
|
||||||
|
"_Fit__targetResists": relation(TargetResists),
|
||||||
|
"projectedOnto": relationship(
|
||||||
|
ProjectedFit,
|
||||||
|
primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
|
||||||
|
backref='source_fit',
|
||||||
|
collection_class=attribute_mapped_collection('victimID'),
|
||||||
|
cascade='all, delete, delete-orphan'),
|
||||||
|
"victimOf": relationship(
|
||||||
|
ProjectedFit,
|
||||||
|
primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID,
|
||||||
|
backref='victim_fit',
|
||||||
|
collection_class=attribute_mapped_collection('sourceID'),
|
||||||
|
cascade='all, delete, delete-orphan'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
mapper(ProjectedFit, projectedFits_table)
|
||||||
|
|||||||
@@ -185,6 +185,12 @@ def getFit(lookfor, eager=None):
|
|||||||
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
|
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
|
|
||||||
|
if fit and fit.isInvalid:
|
||||||
|
with sd_lock:
|
||||||
|
removeInvalid([fit])
|
||||||
|
return None
|
||||||
|
|
||||||
return fit
|
return fit
|
||||||
|
|
||||||
@cachedQuery(Fleet, 1, "fleetID")
|
@cachedQuery(Fleet, 1, "fleetID")
|
||||||
@@ -244,9 +250,10 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None):
|
|||||||
filter = processWhere(filter, where)
|
filter = processWhere(filter, where)
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
|
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
||||||
else:
|
else:
|
||||||
raise TypeError("ShipID must be integer")
|
raise TypeError("ShipID must be integer")
|
||||||
|
|
||||||
return fits
|
return fits
|
||||||
|
|
||||||
def getBoosterFits(ownerID=None, where=None, eager=None):
|
def getBoosterFits(ownerID=None, where=None, eager=None):
|
||||||
@@ -264,7 +271,8 @@ def getBoosterFits(ownerID=None, where=None, eager=None):
|
|||||||
filter = processWhere(filter, where)
|
filter = processWhere(filter, where)
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
|
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
||||||
|
|
||||||
return fits
|
return fits
|
||||||
|
|
||||||
def countAllFits():
|
def countAllFits():
|
||||||
@@ -295,7 +303,8 @@ def countFitsWithShip(shipID, ownerID=None, where=None, eager=None):
|
|||||||
def getFitList(eager=None):
|
def getFitList(eager=None):
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
fits = saveddata_session.query(Fit).options(*eager).all()
|
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).all())
|
||||||
|
|
||||||
return fits
|
return fits
|
||||||
|
|
||||||
def getFleetList(eager=None):
|
def getFleetList(eager=None):
|
||||||
@@ -385,7 +394,8 @@ def searchFits(nameLike, where=None, eager=None):
|
|||||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
|
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
||||||
|
|
||||||
return fits
|
return fits
|
||||||
|
|
||||||
def getSquadsIDsWithFitID(fitID):
|
def getSquadsIDsWithFitID(fitID):
|
||||||
@@ -406,6 +416,16 @@ def getProjectedFits(fitID):
|
|||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
|
|
||||||
|
def removeInvalid(fits):
|
||||||
|
invalids = [f for f in fits if f.isInvalid]
|
||||||
|
|
||||||
|
if invalids:
|
||||||
|
map(fits.remove, invalids)
|
||||||
|
map(saveddata_session.delete, invalids)
|
||||||
|
saveddata_session.commit()
|
||||||
|
|
||||||
|
return fits
|
||||||
|
|
||||||
def add(stuff):
|
def add(stuff):
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
saveddata_session.add(stuff)
|
saveddata_session.add(stuff)
|
||||||
|
|||||||
@@ -17,8 +17,12 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
|
|
||||||
|
#from sqlalchemy.orm.attributes import flag_modified
|
||||||
import eos.db
|
import eos.db
|
||||||
import eos.types
|
import eos.types
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HandledList(list):
|
class HandledList(list):
|
||||||
def filteredItemPreAssign(self, filter, *args, **kwargs):
|
def filteredItemPreAssign(self, filter, *args, **kwargs):
|
||||||
@@ -101,6 +105,14 @@ class HandledList(list):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def remove(self, thing):
|
||||||
|
# We must flag it as modified, otherwise it not be removed from the database
|
||||||
|
# @todo: flag_modified isn't in os x skel. need to rebuild to include
|
||||||
|
#flag_modified(thing, "itemID")
|
||||||
|
if thing.isInvalid: # see GH issue #324
|
||||||
|
thing.itemID = 0
|
||||||
|
list.remove(self, thing)
|
||||||
|
|
||||||
class HandledModuleList(HandledList):
|
class HandledModuleList(HandledList):
|
||||||
def append(self, mod):
|
def append(self, mod):
|
||||||
emptyPosition = float("Inf")
|
emptyPosition = float("Inf")
|
||||||
@@ -115,10 +127,14 @@ class HandledModuleList(HandledList):
|
|||||||
del self[emptyPosition]
|
del self[emptyPosition]
|
||||||
mod.position = emptyPosition
|
mod.position = emptyPosition
|
||||||
HandledList.insert(self, emptyPosition, mod)
|
HandledList.insert(self, emptyPosition, mod)
|
||||||
|
if mod.isInvalid:
|
||||||
|
self.remove(mod)
|
||||||
return
|
return
|
||||||
|
|
||||||
mod.position = len(self)
|
mod.position = len(self)
|
||||||
HandledList.append(self, mod)
|
HandledList.append(self, mod)
|
||||||
|
if mod.isInvalid:
|
||||||
|
self.remove(mod)
|
||||||
|
|
||||||
def insert(self, index, mod):
|
def insert(self, index, mod):
|
||||||
mod.position = index
|
mod.position = index
|
||||||
@@ -149,133 +165,72 @@ class HandledModuleList(HandledList):
|
|||||||
if mod.getModifiedItemAttr("subSystemSlot") == slot:
|
if mod.getModifiedItemAttr("subSystemSlot") == slot:
|
||||||
del self[i]
|
del self[i]
|
||||||
|
|
||||||
class HandledDroneList(HandledList):
|
class HandledDroneCargoList(HandledList):
|
||||||
def find(self, item):
|
def find(self, item):
|
||||||
for d in self:
|
for o in self:
|
||||||
if d.item == item:
|
if o.item == item:
|
||||||
yield d
|
yield o
|
||||||
|
|
||||||
def findFirst(self, item):
|
def findFirst(self, item):
|
||||||
for d in self.find(item):
|
for o in self.find(item):
|
||||||
return d
|
return o
|
||||||
|
|
||||||
def append(self, drone):
|
def append(self, thing):
|
||||||
list.append(self, drone)
|
HandledList.append(self, thing)
|
||||||
|
|
||||||
def remove(self, drone):
|
if thing.isInvalid:
|
||||||
HandledList.remove(self, drone)
|
self.remove(thing)
|
||||||
|
|
||||||
def appendItem(self, item, amount = 1):
|
|
||||||
if amount < 1: ValueError("Amount of drones to add should be >= 1")
|
|
||||||
d = self.findFirst(item)
|
|
||||||
|
|
||||||
if d is None:
|
|
||||||
d = eos.types.Drone(item)
|
|
||||||
self.append(d)
|
|
||||||
|
|
||||||
d.amount += amount
|
|
||||||
return d
|
|
||||||
|
|
||||||
def removeItem(self, item, amount):
|
|
||||||
if amount < 1: ValueError("Amount of drones to remove should be >= 1")
|
|
||||||
d = self.findFirst(item)
|
|
||||||
if d is None: return
|
|
||||||
d.amount -= amount
|
|
||||||
if d.amount <= 0:
|
|
||||||
self.remove(d)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
class HandledCargoList(HandledList):
|
|
||||||
# shameless copy of HandledDroneList
|
|
||||||
# I have no idea what this does, but I needed it
|
|
||||||
# @todo: investigate this
|
|
||||||
def find(self, item):
|
|
||||||
for d in self:
|
|
||||||
if d.item == item:
|
|
||||||
yield d
|
|
||||||
|
|
||||||
def findFirst(self, item):
|
|
||||||
for d in self.find(item):
|
|
||||||
return d
|
|
||||||
|
|
||||||
def append(self, cargo):
|
|
||||||
list.append(self, cargo)
|
|
||||||
|
|
||||||
def remove(self, cargo):
|
|
||||||
HandledList.remove(self, cargo)
|
|
||||||
|
|
||||||
def appendItem(self, item, qty = 1):
|
|
||||||
if qty < 1: ValueError("Amount of cargo to add should be >= 1")
|
|
||||||
d = self.findFirst(item)
|
|
||||||
|
|
||||||
if d is None:
|
|
||||||
d = eos.types.Cargo(item)
|
|
||||||
self.append(d)
|
|
||||||
|
|
||||||
d.qty += qty
|
|
||||||
return d
|
|
||||||
|
|
||||||
def removeItem(self, item, qty):
|
|
||||||
if qty < 1: ValueError("Amount of cargo to remove should be >= 1")
|
|
||||||
d = self.findFirst(item)
|
|
||||||
if d is None: return
|
|
||||||
d.qty -= qty
|
|
||||||
if d.qty <= 0:
|
|
||||||
self.remove(d)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
class HandledImplantBoosterList(HandledList):
|
class HandledImplantBoosterList(HandledList):
|
||||||
def __init__(self):
|
def append(self, thing):
|
||||||
self.__slotCache = {}
|
if thing.isInvalid:
|
||||||
|
HandledList.append(self, thing)
|
||||||
|
self.remove(thing)
|
||||||
|
return
|
||||||
|
|
||||||
def append(self, implant):
|
# if needed, remove booster that was occupying slot
|
||||||
if self.__slotCache.has_key(implant.slot):
|
oldObj = next((m for m in self if m.slot == thing.slot), None)
|
||||||
raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True")
|
if oldObj:
|
||||||
self.__slotCache[implant.slot] = implant
|
logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name)
|
||||||
HandledList.append(self, implant)
|
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
|
||||||
|
self.remove(oldObj)
|
||||||
|
|
||||||
def remove(self, implant):
|
HandledList.append(self, thing)
|
||||||
HandledList.remove(self, implant)
|
|
||||||
del self.__slotCache[implant.slot]
|
|
||||||
# While we deleted this implant, in edge case seems like not all references
|
|
||||||
# to it are removed and object still lives in session; forcibly remove it,
|
|
||||||
# or otherwise when adding the same booster twice booster's table (typeID, fitID)
|
|
||||||
# constraint will report database integrity error
|
|
||||||
# TODO: make a proper fix, probably by adjusting fit-boosters sqlalchemy relationships
|
|
||||||
eos.db.remove(implant)
|
|
||||||
|
|
||||||
def freeSlot(self, slot):
|
|
||||||
if hasattr(slot, "slot"):
|
|
||||||
slot = slot.slot
|
|
||||||
|
|
||||||
try:
|
|
||||||
implant = self.__slotCache[slot]
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
self.remove(implant)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
class HandledProjectedModList(HandledList):
|
class HandledProjectedModList(HandledList):
|
||||||
|
def append(self, proj):
|
||||||
|
if proj.isInvalid:
|
||||||
|
# we must include it before we remove it. doing it this way ensures
|
||||||
|
# rows and relationships in database are removed as well
|
||||||
|
HandledList.append(self, proj)
|
||||||
|
self.remove(proj)
|
||||||
|
return
|
||||||
|
|
||||||
|
proj.projected = True
|
||||||
|
isSystemEffect = proj.item.group.name == "Effect Beacon"
|
||||||
|
|
||||||
|
if isSystemEffect:
|
||||||
|
# remove other system effects - only 1 per fit plz
|
||||||
|
oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None)
|
||||||
|
|
||||||
|
if oldEffect:
|
||||||
|
logging.info("System effect occupied with %s, replacing with %s", oldEffect.item.name, proj.item.name)
|
||||||
|
self.remove(oldEffect)
|
||||||
|
|
||||||
|
HandledList.append(self, proj)
|
||||||
|
|
||||||
|
# Remove non-projectable modules
|
||||||
|
if not proj.item.isType("projected") and not isSystemEffect:
|
||||||
|
self.remove(proj)
|
||||||
|
|
||||||
|
class HandledProjectedDroneList(HandledDroneCargoList):
|
||||||
def append(self, proj):
|
def append(self, proj):
|
||||||
proj.projected = True
|
proj.projected = True
|
||||||
HandledList.append(self, proj)
|
HandledList.append(self, proj)
|
||||||
|
|
||||||
class HandledProjectedDroneList(HandledDroneList):
|
# Remove invalid or non-projectable drones
|
||||||
def append(self, proj):
|
if proj.isInvalid or not proj.item.isType("projected"):
|
||||||
proj.projected = True
|
self.remove(proj)
|
||||||
list.append(self, proj)
|
|
||||||
|
|
||||||
class HandledProjectedFitList(HandledList):
|
|
||||||
def append(self, proj):
|
|
||||||
proj.projected = True
|
|
||||||
list.append(self, proj)
|
|
||||||
|
|
||||||
class HandledItem(object):
|
class HandledItem(object):
|
||||||
def preAssignItemAttr(self, *args, **kwargs):
|
def preAssignItemAttr(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ammoInfluenceCapNeed
|
# ammoInfluenceCapNeed
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Items from category: Charge (458 of 829)
|
# Items from category: Charge (458 of 831)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
# Dirty hack to work around cap charges setting cap booster
|
# Dirty hack to work around cap charges setting cap booster
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ammoInfluenceRange
|
# ammoInfluenceRange
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Items from category: Charge (559 of 829)
|
# Items from category: Charge (559 of 831)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
module.multiplyItemAttr("maxRange", module.getModifiedChargeAttr("weaponRangeMultiplier"))
|
module.multiplyItemAttr("maxRange", module.getModifiedChargeAttr("weaponRangeMultiplier"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# armorHPBonusAdd
|
# armorHPBonusAdd
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Armor Reinforcer (38 of 38)
|
# Modules from group: Armor Reinforcer (41 of 41)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.increaseItemAttr("armorHP", module.getModifiedItemAttr("armorHPBonusAdd"))
|
fit.ship.increaseItemAttr("armorHP", module.getModifiedItemAttr("armorHPBonusAdd"))
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# armorReinforcerMassAdd
|
# armorReinforcerMassAdd
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Armor Reinforcer (38 of 38)
|
# Modules from group: Armor Reinforcer (41 of 41)
|
||||||
# Modules from group: Entosis Link (2 of 2)
|
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.increaseItemAttr("mass", module.getModifiedItemAttr("massAddition"))
|
fit.ship.increaseItemAttr("mass", module.getModifiedItemAttr("massAddition"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# eliteBonusHeavyInterdictorsWarpDisruptFieldGeneratorWarpScrambleRange2
|
# eliteBonusHeavyInterdictorsWarpDisruptFieldGeneratorWarpScrambleRange2
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Heavy Interdiction Cruiser (4 of 4)
|
# Ships from group: Heavy Interdiction Cruiser (4 of 5)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
level = fit.character.getSkill("Heavy Interdiction Cruisers").level
|
level = fit.character.getSkill("Heavy Interdiction Cruisers").level
|
||||||
|
|||||||
12
eos/effects/entosisdurationmultiply.py
Normal file
12
eos/effects/entosisdurationmultiply.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# entosisDurationMultiply
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ships from group: Carrier (4 of 4)
|
||||||
|
# Ships from group: Dreadnought (4 of 4)
|
||||||
|
# Ships from group: Supercarrier (5 of 5)
|
||||||
|
# Ships from group: Titan (4 of 4)
|
||||||
|
# Ship: Rorqual
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, ship, context):
|
||||||
|
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill("Infomorph Psychology"),
|
||||||
|
"duration", ship.getModifiedItemAttr("entosisDurationMultiplier") or 1)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Interceptor2WarpScrambleRange
|
# Interceptor2WarpScrambleRange
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Interceptor (5 of 9)
|
# Ships from group: Interceptor (5 of 10)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
level = fit.character.getSkill("Interceptors").level
|
level = fit.character.getSkill("Interceptors").level
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# interceptorMWDSignatureRadiusBonus
|
# interceptorMWDSignatureRadiusBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Interceptor (9 of 9)
|
# Ships from group: Interceptor (9 of 10)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
level = fit.character.getSkill("Interceptors").level
|
level = fit.character.getSkill("Interceptors").level
|
||||||
|
|||||||
9
eos/effects/missileaoecloudsizebonusonline.py
Normal file
9
eos/effects/missileaoecloudsizebonusonline.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# missileAOECloudSizeBonusOnline
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, container, context):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
|
"aoeCloudSize", container.getModifiedItemAttr("aoeCloudSizeBonus"),
|
||||||
|
stackingPenalties=True)
|
||||||
9
eos/effects/missileaoevelocitybonusonline.py
Normal file
9
eos/effects/missileaoevelocitybonusonline.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# missileAOEVelocityBonusOnline
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, container, context):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
|
"aoeVelocity", container.getModifiedItemAttr("aoeVelocityBonus"),
|
||||||
|
stackingPenalties=True)
|
||||||
9
eos/effects/missileexplosiondelaybonusonline.py
Normal file
9
eos/effects/missileexplosiondelaybonusonline.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# missileExplosionDelayBonusOnline
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, container, context):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
|
"explosionDelay", container.getModifiedItemAttr("explosionDelayBonus"),
|
||||||
|
stackingPenalties=True)
|
||||||
15
eos/effects/missileguidancecomputerbonus4.py
Normal file
15
eos/effects/missileguidancecomputerbonus4.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# missileGuidanceComputerBonus4
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Computer (3 of 3)
|
||||||
|
type = "active"
|
||||||
|
def handler(fit, container, context):
|
||||||
|
for srcAttr, tgtAttr in (
|
||||||
|
("aoeCloudSizeBonus", "aoeCloudSize"),
|
||||||
|
("aoeVelocityBonus", "aoeVelocity"),
|
||||||
|
("missileVelocityBonus", "maxVelocity"),
|
||||||
|
("explosionDelayBonus", "explosionDelay"),
|
||||||
|
):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
|
tgtAttr, container.getModifiedItemAttr(srcAttr),
|
||||||
|
stackingPenalties=True)
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, container, context):
|
def handler(fit, container, context):
|
||||||
level = container.level if "skill" in context else 1
|
level = container.level if "skill" in context else 1
|
||||||
|
penalize = False if "skill" in context or "implant" in context else True
|
||||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
"aoeCloudSize", container.getModifiedItemAttr("aoeCloudSizeBonus") * level,
|
"aoeCloudSize", container.getModifiedItemAttr("aoeCloudSizeBonus") * level,
|
||||||
stackingPenalties = False)
|
stackingPenalties=penalize)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, container, context):
|
def handler(fit, container, context):
|
||||||
level = container.level if "skill" in context else 1
|
level = container.level if "skill" in context else 1
|
||||||
|
penalize = False if "skill" in context or "implant" in context else True
|
||||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
"aoeVelocity", container.getModifiedItemAttr("aoeVelocityBonus") * level,
|
"aoeVelocity", container.getModifiedItemAttr("aoeVelocityBonus") * level,
|
||||||
stackingPenalties = "skill" not in context and "implant" not in context)
|
stackingPenalties=penalize)
|
||||||
|
|||||||
@@ -5,4 +5,4 @@
|
|||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, container, context):
|
def handler(fit, container, context):
|
||||||
fit.modules.filteredChargeMultiply(lambda mod: mod.charge.requiresSkill("Defender Missiles"),
|
fit.modules.filteredChargeMultiply(lambda mod: mod.charge.requiresSkill("Defender Missiles"),
|
||||||
"maxVelocity", container.getModifiedItemAttr("missileVelocityBonus"))
|
"maxVelocity", container.getModifiedItemAttr("missileVelocityBonus"))
|
||||||
|
|||||||
9
eos/effects/missilevelocitybonusonline.py
Normal file
9
eos/effects/missilevelocitybonusonline.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# missileVelocityBonusOnline
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, container, context):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
|
||||||
|
"maxVelocity", container.getModifiedItemAttr("missileVelocityBonus"),
|
||||||
|
stackingPenalties=True)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# modeAgilityPostDiv
|
# modeAgilityPostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules named like: Propulsion Mode (3 of 3)
|
# Modules named like: Propulsion Mode (4 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.multiplyItemAttr(
|
fit.ship.multiplyItemAttr(
|
||||||
|
|||||||
11
eos/effects/modearmorrepdurationpostdiv.py
Normal file
11
eos/effects/modearmorrepdurationpostdiv.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# modeArmorRepDurationPostDiv
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Hecate Defense Mode
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemMultiply(
|
||||||
|
lambda mod: mod.item.requiresSkill("Repair Systems"),
|
||||||
|
"duration",
|
||||||
|
1 / module.getModifiedItemAttr("modeArmorRepDurationPostDiv")
|
||||||
|
)
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# modeArmorResonancePostDiv
|
# modeArmorResonancePostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Module: Confessor Defense Mode
|
# Modules named like: Defense Mode (3 of 4)
|
||||||
# Module: Svipul Defense Mode
|
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
for srcResType, tgtResType in (
|
for srcResType, tgtResType in (
|
||||||
|
|||||||
16
eos/effects/modehullresonancepostdiv.py
Normal file
16
eos/effects/modehullresonancepostdiv.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# modeHullResonancePostDiv
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Hecate Defense Mode
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
for srcResType, tgtResType in (
|
||||||
|
("Em", "em"),
|
||||||
|
("Explosive", "explosive"),
|
||||||
|
("Kinetic", "kinetic"),
|
||||||
|
("Thermic", "thermal")
|
||||||
|
):
|
||||||
|
fit.ship.multiplyItemAttr(
|
||||||
|
"{0}DamageResonance".format(tgtResType),
|
||||||
|
1 / module.getModifiedItemAttr("mode{0}ResistancePostDiv".format(srcResType))
|
||||||
|
)
|
||||||
13
eos/effects/modemwdboostpostdiv.py
Normal file
13
eos/effects/modemwdboostpostdiv.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# modeMWDBoostPostDiv
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Hecate Propulsion Mode
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemMultiply(
|
||||||
|
lambda mod: mod.item.requiresSkill("High Speed Maneuvering"),
|
||||||
|
"speedFactor",
|
||||||
|
1 / module.getModifiedItemAttr("modeMWDVelocityPostDiv"),
|
||||||
|
stackingPenalties=True,
|
||||||
|
penaltyGroup="postDiv"
|
||||||
|
)
|
||||||
11
eos/effects/modemwdcappostdiv.py
Normal file
11
eos/effects/modemwdcappostdiv.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# modeMWDCapPostDiv
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Hecate Propulsion Mode
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemMultiply(
|
||||||
|
lambda mod: mod.item.requiresSkill("High Speed Maneuvering"),
|
||||||
|
"capacitorNeed",
|
||||||
|
1 / module.getModifiedItemAttr("modeMWDCapPostDiv")
|
||||||
|
)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# modeVelocityPostDiv
|
# modeVelocityPostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules named like: Propulsion Mode (3 of 3)
|
# Modules named like: Propulsion Mode (3 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.multiplyItemAttr(
|
fit.ship.multiplyItemAttr(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# OffensiveDefensiveReduction
|
# OffensiveDefensiveReduction
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Incursion ship attributes effects (3 of 3)
|
# Celestials named like: Incursion ship attributes effects (3 of 3)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
14
eos/effects/overloadselfmissileguidancebonus5.py
Normal file
14
eos/effects/overloadselfmissileguidancebonus5.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# overloadSelfMissileGuidanceBonus5
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Missile Guidance Computer (3 of 3)
|
||||||
|
type = "overheat"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
for tgtAttr in (
|
||||||
|
"aoeCloudSizeBonus",
|
||||||
|
"explosionDelayBonus",
|
||||||
|
"missileVelocityBonus",
|
||||||
|
"maxVelocityBonus",
|
||||||
|
"aoeVelocityBonus"
|
||||||
|
):
|
||||||
|
module.boostItemAttr(tgtAttr, module.getModifiedItemAttr("overloadTrackingModuleStrengthBonus"))
|
||||||
7
eos/effects/passivemassadd.py
Normal file
7
eos/effects/passivemassadd.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# passiveMassAdd
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Entosis Link (2 of 2)
|
||||||
|
type = "offline"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.ship.increaseItemAttr("mass", module.getModifiedItemAttr("massAddition"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# probeLauncherCPUPercentBonusTacticalDestroyer
|
# probeLauncherCPUPercentBonusTacticalDestroyer
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Tactical Destroyer (3 of 3)
|
# Ships from group: Tactical Destroyer (4 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Astrometrics"),
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Astrometrics"),
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# scriptMissileGuidanceComputerAOECloudSizeBonusBonus
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Charges from group: Missile Guidance Script (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
module.boostItemAttr("aoeCloudSizeBonus", module.getModifiedChargeAttr("aoeCloudSizeBonusBonus"))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# scriptMissileGuidanceComputerAOEVelocityBonusBonus
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Charges from group: Missile Guidance Script (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
module.boostItemAttr("aoeVelocityBonus", module.getModifiedChargeAttr("aoeVelocityBonusBonus"))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# scriptMissileGuidanceComputerExplosionDelayBonusBonus
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Charges from group: Missile Guidance Script (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
module.boostItemAttr("explosionDelayBonus", module.getModifiedChargeAttr("explosionDelayBonusBonus"))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# scriptMissileGuidanceComputerMissileVelocityBonusBonus
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Charges from group: Missile Guidance Script (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
module.boostItemAttr("missileVelocityBonus", module.getModifiedChargeAttr("missileVelocityBonusBonus"))
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Daredevil
|
# Ship: Daredevil
|
||||||
|
# Ship: Hecate
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Hybrid Turret"),
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Hybrid Turret"),
|
||||||
|
|||||||
9
eos/effects/shipbonusrhmlrofcb.py
Normal file
9
eos/effects/shipbonusrhmlrofcb.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# shipBonusRHMLROFCB
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Scorpion Navy Issue
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, ship, context):
|
||||||
|
level = fit.character.getSkill("Caldari Battleship").level
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Missile Launcher Rapid Heavy",
|
||||||
|
"speed", ship.getModifiedItemAttr("shipBonusCB") * level)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipCapPropulsionJamming
|
# shipCapPropulsionJamming
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Interceptor (9 of 9)
|
# Ships from group: Interceptor (9 of 10)
|
||||||
# Ship: Atron
|
# Ship: Atron
|
||||||
# Ship: Condor
|
# Ship: Condor
|
||||||
# Ship: Executioner
|
# Ship: Executioner
|
||||||
|
|||||||
9
eos/effects/shipheatdamagegallentetacticaldestroyer3.py
Normal file
9
eos/effects/shipheatdamagegallentetacticaldestroyer3.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# shipHeatDamageGallenteTacticalDestroyer3
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Hecate
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, ship, context):
|
||||||
|
level = fit.character.getSkill("Gallente Tactical Destroyer").level
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: True, "heatDamage",
|
||||||
|
ship.getModifiedItemAttr("shipBonusTacticalDestroyerGallente3") * level)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipModeMaxTargetRangePostDiv
|
# shipModeMaxTargetRangePostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.multiplyItemAttr(
|
fit.ship.multiplyItemAttr(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipModeScanResPostDiv
|
# shipModeScanResPostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.multiplyItemAttr(
|
fit.ship.multiplyItemAttr(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipModeScanStrengthPostDiv
|
# shipModeScanStrengthPostDiv
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
for scanType in ("Gravimetric", "Magnetometric", "Radar", "Ladar"):
|
for scanType in ("Gravimetric", "Magnetometric", "Radar", "Ladar"):
|
||||||
|
|||||||
13
eos/effects/shipmodeshtoptimalrangepostdiv.py
Normal file
13
eos/effects/shipmodeshtoptimalrangepostdiv.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# shipModeSHTOptimalRangePostDiv
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Hecate Sharpshooter Mode
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemMultiply(
|
||||||
|
lambda mod: mod.item.requiresSkill("Small Hybrid Turret"),
|
||||||
|
"maxRange",
|
||||||
|
1 / module.getModifiedItemAttr("modeMaxRangePostDiv"),
|
||||||
|
stackingPenalties=True,
|
||||||
|
penaltyGroup="postDiv"
|
||||||
|
)
|
||||||
9
eos/effects/shipshtrofgallentetacticaldestroyer1.py
Normal file
9
eos/effects/shipshtrofgallentetacticaldestroyer1.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# shipSHTRoFGallenteTacticalDestroyer1
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Hecate
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, ship, context):
|
||||||
|
level = fit.character.getSkill("Gallente Tactical Destroyer").level
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Hybrid Turret"),
|
||||||
|
"speed", ship.getModifiedItemAttr("shipBonusTacticalDestroyerGallente1") * level)
|
||||||
9
eos/effects/shipshttrackinggallentetacticaldestroyer2.py
Normal file
9
eos/effects/shipshttrackinggallentetacticaldestroyer2.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# shipSHTTrackingGallenteTacticalDestroyer2
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Hecate
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, ship, context):
|
||||||
|
level = fit.character.getSkill("Gallente Tactical Destroyer").level
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Hybrid Turret"),
|
||||||
|
"trackingSpeed", ship.getModifiedItemAttr("shipBonusTacticalDestroyerGallente2") * level)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageDrones
|
# systemDamageDrones
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageEmMissiles
|
# systemDamageEmMissiles
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageExplosiveMissiles
|
# systemDamageExplosiveMissiles
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageKineticMissiles
|
# systemDamageKineticMissiles
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageMultiplierGunnery
|
# systemDamageMultiplierGunnery
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemDamageThermalMissiles
|
# systemDamageThermalMissiles
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Celestials named like: Black Hole Effect Beacon Class (6 of 6)
|
# Celestials named like: Black Hole Effect Beacon Class (6 of 6)
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
def handler(fit, beacon, context):
|
def handler(fit, beacon, context):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemSmartBombEmDamage
|
# systemSmartBombEmDamage
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemSmartBombExplosiveDamage
|
# systemSmartBombExplosiveDamage
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemSmartBombKineticDamage
|
# systemSmartBombKineticDamage
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# systemSmartBombThermalDamage
|
# systemSmartBombThermalDamage
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Celestials named like: Drifter Incursion (6 of 6)
|
||||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
type = ("projected", "offline")
|
type = ("projected", "offline")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Used by:
|
# Used by:
|
||||||
# Module: Triage Module I
|
# Module: Triage Module I
|
||||||
type = "active"
|
type = "active"
|
||||||
|
runTime = "early"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
# Remote armor reps
|
# Remote armor reps
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Used by:
|
# Used by:
|
||||||
# Module: Triage Module II
|
# Module: Triage Module II
|
||||||
type = "active"
|
type = "active"
|
||||||
|
runTime = "early"
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
# Remote armor reps
|
# Remote armor reps
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||||
|
|||||||
@@ -20,36 +20,53 @@
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem
|
from eos.effectHandlerHelpers import HandledItem
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
from sqlalchemy.orm import reconstructor, validates
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Booster(HandledItem, ItemAttrShortcut):
|
class Booster(HandledItem, ItemAttrShortcut):
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
self.__slot = self.__calculateSlot(item)
|
|
||||||
self.itemID = item.ID
|
|
||||||
self.__item = item
|
self.__item = item
|
||||||
|
|
||||||
|
if self.isInvalid:
|
||||||
|
raise ValueError("Passed item is not a Booster")
|
||||||
|
|
||||||
|
self.itemID = item.ID if item is not None else None
|
||||||
self.active = True
|
self.active = True
|
||||||
self.build()
|
self.build()
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
|
"""Initialize a booster from the database and validate"""
|
||||||
self.__item = None
|
self.__item = None
|
||||||
|
|
||||||
|
if self.itemID:
|
||||||
|
self.__item = eos.db.getItem(self.itemID)
|
||||||
|
if self.__item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.isInvalid:
|
||||||
|
logger.error("Item (id: %d) is not a Booser", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
|
""" Build object. Assumes proper and valid item already set """
|
||||||
|
self.__sideEffects = []
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
self.__sideEffects = []
|
self.__slot = self.__calculateSlot(self.__item)
|
||||||
for effect in self.item.effects.itervalues():
|
|
||||||
|
for effect in self.__item.effects.itervalues():
|
||||||
if effect.isType("boosterSideEffect"):
|
if effect.isType("boosterSideEffect"):
|
||||||
s = SideEffect(self)
|
s = SideEffect(self)
|
||||||
s.effect = effect
|
s.effect = effect
|
||||||
s.active = effect.ID in self.__activeSideEffectIDs
|
s.active = effect.ID in self.__activeSideEffectIDs
|
||||||
self.__sideEffects.append(s)
|
self.__sideEffects.append(s)
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
|
||||||
import eos.db
|
|
||||||
self.__item = eos.db.getItem(self.itemID)
|
|
||||||
self.__slot = self.__calculateSlot(self.__item)
|
|
||||||
self.build()
|
|
||||||
|
|
||||||
def iterSideEffects(self):
|
def iterSideEffects(self):
|
||||||
return self.__sideEffects.__iter__()
|
return self.__sideEffects.__iter__()
|
||||||
|
|
||||||
@@ -62,23 +79,18 @@ class Booster(HandledItem, ItemAttrShortcut):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slot(self):
|
def isInvalid(self):
|
||||||
if self.__item is None:
|
return self.__item is None or self.__item.group.name != "Booster"
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slot(self):
|
||||||
return self.__slot
|
return self.__slot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def item(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
def __calculateSlot(self, item):
|
def __calculateSlot(self, item):
|
||||||
|
|||||||
@@ -20,40 +20,45 @@
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||||
from sqlalchemy.orm import validates, reconstructor
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
# Cargo class copied from Implant class and hacked to make work. \o/
|
logger = logging.getLogger(__name__)
|
||||||
# @todo: clean me up, Scotty
|
|
||||||
class Cargo(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
class Cargo(HandledItem, ItemAttrShortcut):
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
"""Initialize cargo from the program"""
|
||||||
self.__item = item
|
self.__item = item
|
||||||
self.itemID = item.ID
|
self.itemID = item.ID if item is not None else None
|
||||||
self.amount = 0
|
self.amount = 0
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.item.attributes
|
self.__itemModifiedAttributes.original = item.attributes
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
|
"""Initialize cargo from the database and validate"""
|
||||||
self.__item = None
|
self.__item = None
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
if self.itemID:
|
||||||
import eos.db
|
self.__item = eos.db.getItem(self.itemID)
|
||||||
self.__item = eos.db.getItem(self.itemID)
|
if self.__item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def isInvalid(self):
|
||||||
if self.__item is None:
|
return self.__item is None
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item(self):
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ class Character(object):
|
|||||||
map = {"ID": lambda val: isinstance(val, int),
|
map = {"ID": lambda val: isinstance(val, int),
|
||||||
"name" : lambda val: True,
|
"name" : lambda val: True,
|
||||||
"apiKey" : lambda val: val is None or (isinstance(val, basestring) and len(val) > 0),
|
"apiKey" : lambda val: val is None or (isinstance(val, basestring) and len(val) > 0),
|
||||||
"ownerID" : lambda val: isinstance(val, int)}
|
"ownerID" : lambda val: isinstance(val, int) or val is None}
|
||||||
|
|
||||||
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
||||||
else: return val
|
else: return val
|
||||||
|
|||||||
@@ -20,81 +20,80 @@
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||||
from sqlalchemy.orm import validates, reconstructor
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||||
MINING_ATTRIBUTES = ("miningAmount",)
|
MINING_ATTRIBUTES = ("miningAmount",)
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
if item.category.name != "Drone":
|
"""Initialize a drone from the program"""
|
||||||
raise ValueError("Passed item is not a drone")
|
|
||||||
|
|
||||||
self.__item = item
|
self.__item = item
|
||||||
self.__charge = None
|
|
||||||
self.itemID = item.ID
|
if self.isInvalid:
|
||||||
|
raise ValueError("Passed item is not a Drone")
|
||||||
|
|
||||||
|
self.itemID = item.ID if item is not None else None
|
||||||
self.amount = 0
|
self.amount = 0
|
||||||
self.amountActive = 0
|
self.amountActive = 0
|
||||||
self.__dps = None
|
|
||||||
self.__volley = None
|
|
||||||
self.__miningyield = None
|
|
||||||
self.projected = False
|
self.projected = False
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.build()
|
||||||
self.itemModifiedAttributes.original = self.item.attributes
|
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
|
"""Initialize a drone from the database and validate"""
|
||||||
|
self.__item = None
|
||||||
|
|
||||||
|
if self.itemID:
|
||||||
|
self.__item = eos.db.getItem(self.itemID)
|
||||||
|
if self.__item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.isInvalid:
|
||||||
|
logger.error("Item (id: %d) is not a Drone", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
""" Build object. Assumes proper and valid item already set """
|
||||||
|
self.__charge = None
|
||||||
self.__dps = None
|
self.__dps = None
|
||||||
self.__volley = None
|
self.__volley = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__item = None
|
|
||||||
self.__charge = None
|
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
|
||||||
import eos.db
|
|
||||||
self.__item = eos.db.getItem(self.itemID)
|
|
||||||
self.__charge = None
|
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.item.attributes
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
|
|
||||||
def __fetchChargeInfo(self):
|
|
||||||
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
|
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||||
|
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
|
||||||
if chargeID is not None:
|
if chargeID is not None:
|
||||||
import eos.db
|
|
||||||
charge = eos.db.getItem(int(chargeID))
|
charge = eos.db.getItem(int(chargeID))
|
||||||
self.__charge = charge
|
self.__charge = charge
|
||||||
|
self.__chargeModifiedAttributes.original = charge.attributes
|
||||||
self.chargeModifiedAttributes.original = charge.attributes
|
|
||||||
else:
|
|
||||||
self.__charge = 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chargeModifiedAttributes(self):
|
def chargeModifiedAttributes(self):
|
||||||
if self.__charge is None:
|
|
||||||
self.__fetchChargeInfo()
|
|
||||||
|
|
||||||
return self.__chargeModifiedAttributes
|
return self.__chargeModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def isInvalid(self):
|
||||||
if self.__item is None:
|
return self.__item is None or self.__item.category.name != "Drone"
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item(self):
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def charge(self):
|
def charge(self):
|
||||||
if self.__charge is None:
|
return self.__charge
|
||||||
self.__fetchChargeInfo()
|
|
||||||
|
|
||||||
return self.__charge if self.__charge != 0 else None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dealsDamage(self):
|
def dealsDamage(self):
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
|
|
||||||
from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \
|
from eos.effectHandlerHelpers import *
|
||||||
HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList
|
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||||
from sqlalchemy.orm import validates, reconstructor
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
@@ -28,7 +27,14 @@ from math import sqrt, log, asinh
|
|||||||
from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
|
from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
|
||||||
from eos.saveddata.module import State
|
from eos.saveddata.module import State
|
||||||
from eos.saveddata.mode import Mode
|
from eos.saveddata.mode import Mode
|
||||||
|
import eos.db
|
||||||
import time
|
import time
|
||||||
|
import copy
|
||||||
|
from utils.timer import Timer
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -48,34 +54,57 @@ class Fit(object):
|
|||||||
|
|
||||||
PEAK_RECHARGE = 0.25
|
PEAK_RECHARGE = 0.25
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ship=None, name=""):
|
||||||
|
"""Initialize a fit from the program"""
|
||||||
|
# use @mode.setter's to set __attr and IDs. This will set mode as well
|
||||||
|
self.ship = ship
|
||||||
|
|
||||||
self.__modules = HandledModuleList()
|
self.__modules = HandledModuleList()
|
||||||
self.__drones = HandledDroneList()
|
self.__drones = HandledDroneCargoList()
|
||||||
self.__cargo = HandledCargoList()
|
self.__cargo = HandledDroneCargoList()
|
||||||
self.__implants = HandledImplantBoosterList()
|
self.__implants = HandledImplantBoosterList()
|
||||||
self.__boosters = HandledImplantBoosterList()
|
self.__boosters = HandledImplantBoosterList()
|
||||||
self.__projectedFits = HandledProjectedFitList()
|
#self.__projectedFits = {}
|
||||||
self.__projectedModules = HandledProjectedModList()
|
self.__projectedModules = HandledProjectedModList()
|
||||||
self.__projectedDrones = HandledProjectedDroneList()
|
self.__projectedDrones = HandledProjectedDroneList()
|
||||||
self.__character = None
|
self.__character = None
|
||||||
self.__owner = None
|
self.__owner = None
|
||||||
self.shipID = None
|
|
||||||
self.projected = False
|
self.projected = False
|
||||||
self.name = ""
|
self.name = name
|
||||||
self.fleet = None
|
|
||||||
self.boostsFits = set()
|
|
||||||
self.gangBoosts = None
|
|
||||||
self.timestamp = time.time()
|
self.timestamp = time.time()
|
||||||
self.ecmProjectedStr = 1
|
|
||||||
self.modeID = None
|
self.modeID = None
|
||||||
|
|
||||||
self.build()
|
self.build()
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
|
"""Initialize a fit from the database and validate"""
|
||||||
|
self.__ship = None
|
||||||
|
self.__mode = None
|
||||||
|
|
||||||
|
if self.shipID:
|
||||||
|
item = eos.db.getItem(self.shipID)
|
||||||
|
if item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.shipID)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__ship = Ship(item)
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Item (id: %d) is not a Ship", self.shipID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.modeID and self.__ship:
|
||||||
|
item = eos.db.getItem(self.modeID)
|
||||||
|
# Don't need to verify if it's a proper item, as validateModeItem assures this
|
||||||
|
self.__mode = self.ship.validateModeItem(item)
|
||||||
|
else:
|
||||||
|
self.__mode = self.ship.validateModeItem(None)
|
||||||
|
|
||||||
self.build()
|
self.build()
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
from eos import db
|
|
||||||
self.__extraDrains = []
|
self.__extraDrains = []
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.__weaponDPS = None
|
self.__weaponDPS = None
|
||||||
@@ -100,11 +129,6 @@ class Fit(object):
|
|||||||
self.ecmProjectedStr = 1
|
self.ecmProjectedStr = 1
|
||||||
self.extraAttributes = ModifiedAttributeDict(self)
|
self.extraAttributes = ModifiedAttributeDict(self)
|
||||||
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
|
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
|
||||||
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
|
|
||||||
if self.ship is not None:
|
|
||||||
self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
|
|
||||||
else:
|
|
||||||
self.mode = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def targetResists(self):
|
def targetResists(self):
|
||||||
@@ -128,13 +152,17 @@ class Fit(object):
|
|||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.__effectiveTank = None
|
self.__effectiveTank = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isInvalid(self):
|
||||||
|
return self.__ship is None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self):
|
def mode(self):
|
||||||
return self._mode
|
return self.__mode
|
||||||
|
|
||||||
@mode.setter
|
@mode.setter
|
||||||
def mode(self, mode):
|
def mode(self, mode):
|
||||||
self._mode = mode
|
self.__mode = mode
|
||||||
self.modeID = mode.item.ID if mode is not None else None
|
self.modeID = mode.item.ID if mode is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -154,7 +182,7 @@ class Fit(object):
|
|||||||
self.__ship = ship
|
self.__ship = ship
|
||||||
self.shipID = ship.item.ID if ship is not None else None
|
self.shipID = ship.item.ID if ship is not None else None
|
||||||
# set mode of new ship
|
# set mode of new ship
|
||||||
self.mode = self.ship.checkModeItem(None) if ship is not None else None
|
self.mode = self.ship.validateModeItem(None) if ship is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def drones(self):
|
def drones(self):
|
||||||
@@ -182,7 +210,12 @@ class Fit(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def projectedFits(self):
|
def projectedFits(self):
|
||||||
return self.__projectedFits
|
# only in extreme edge cases will the fit be invalid, but to be sure do
|
||||||
|
# not return them.
|
||||||
|
return [fit for fit in self.__projectedFits.values() if not fit.isInvalid]
|
||||||
|
|
||||||
|
def getProjectionInfo(self, fitID):
|
||||||
|
return self.projectedOnto.get(fitID, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def projectedDrones(self):
|
def projectedDrones(self):
|
||||||
@@ -295,13 +328,13 @@ class Fit(object):
|
|||||||
@validates("ID", "ownerID", "shipID")
|
@validates("ID", "ownerID", "shipID")
|
||||||
def validator(self, key, val):
|
def validator(self, key, val):
|
||||||
map = {"ID": lambda val: isinstance(val, int),
|
map = {"ID": lambda val: isinstance(val, int),
|
||||||
"ownerID" : lambda val: isinstance(val, int),
|
"ownerID" : lambda val: isinstance(val, int) or val is None,
|
||||||
"shipID" : lambda val: isinstance(val, int) or val is None}
|
"shipID" : lambda val: isinstance(val, int) or val is None}
|
||||||
|
|
||||||
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
||||||
else: return val
|
else: return val
|
||||||
|
|
||||||
def clear(self):
|
def clear(self, projected=False):
|
||||||
self.__effectiveTank = None
|
self.__effectiveTank = None
|
||||||
self.__weaponDPS = None
|
self.__weaponDPS = None
|
||||||
self.__minerYield = None
|
self.__minerYield = None
|
||||||
@@ -321,10 +354,30 @@ class Fit(object):
|
|||||||
del self.__calculatedTargets[:]
|
del self.__calculatedTargets[:]
|
||||||
del self.__extraDrains[:]
|
del self.__extraDrains[:]
|
||||||
|
|
||||||
if self.ship is not None: self.ship.clear()
|
if self.ship:
|
||||||
c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
|
self.ship.clear()
|
||||||
|
|
||||||
|
c = chain(
|
||||||
|
self.modules,
|
||||||
|
self.drones,
|
||||||
|
self.boosters,
|
||||||
|
self.implants,
|
||||||
|
self.projectedDrones,
|
||||||
|
self.projectedModules,
|
||||||
|
(self.character, self.extraAttributes),
|
||||||
|
)
|
||||||
|
|
||||||
for stuff in c:
|
for stuff in c:
|
||||||
if stuff is not None and stuff != self: stuff.clear()
|
if stuff is not None and stuff != self:
|
||||||
|
stuff.clear()
|
||||||
|
|
||||||
|
# If this is the active fit that we are clearing, not a projected fit,
|
||||||
|
# then this will run and clear the projected ships and flag the next
|
||||||
|
# iteration to skip this part to prevent recursion.
|
||||||
|
if not projected:
|
||||||
|
for stuff in self.projectedFits:
|
||||||
|
if stuff is not None and stuff != self:
|
||||||
|
stuff.clear(projected=True)
|
||||||
|
|
||||||
#Methods to register and get the thing currently affecting the fit,
|
#Methods to register and get the thing currently affecting the fit,
|
||||||
#so we can correctly map "Affected By"
|
#so we can correctly map "Affected By"
|
||||||
@@ -338,7 +391,48 @@ class Fit(object):
|
|||||||
def getModifier(self):
|
def getModifier(self):
|
||||||
return self.__modifier
|
return self.__modifier
|
||||||
|
|
||||||
|
def __calculateGangBoosts(self, runTime):
|
||||||
|
for name, info in self.gangBoosts.iteritems():
|
||||||
|
# Unpack all data required to run effect properly
|
||||||
|
effect, thing = info[1]
|
||||||
|
if effect.runTime == runTime:
|
||||||
|
context = ("gang", thing.__class__.__name__.lower())
|
||||||
|
if isinstance(thing, Module):
|
||||||
|
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
||||||
|
(effect.isType("active") and thing.state >= State.ACTIVE):
|
||||||
|
# Run effect, and get proper bonuses applied
|
||||||
|
try:
|
||||||
|
self.register(thing)
|
||||||
|
effect.handler(self, thing, context)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Run effect, and get proper bonuses applied
|
||||||
|
try:
|
||||||
|
self.register(thing)
|
||||||
|
effect.handler(self, thing, context)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
|
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
|
||||||
|
timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger)
|
||||||
|
logger.debug("Starting fit calculation on: %d %s (%s)" %
|
||||||
|
(self.ID, self.name, self.ship.item.name))
|
||||||
|
shadow = False
|
||||||
|
if targetFit:
|
||||||
|
logger.debug("Applying projections to target: %d %s (%s)",
|
||||||
|
targetFit.ID, targetFit.name, targetFit.ship.item.name)
|
||||||
|
projectionInfo = self.getProjectionInfo(targetFit.ID)
|
||||||
|
logger.debug("ProjectionInfo: %s", ', '.join("%s: %s" % item for item in vars(projectionInfo).items()))
|
||||||
|
if self == targetFit:
|
||||||
|
shadow = True
|
||||||
|
self = copy.deepcopy(self)
|
||||||
|
logger.debug("Handling self projection - making shadow copy of fit. %s => %s", projectionInfo.source_fit, self)
|
||||||
|
# we rollback because when we copy a fit, flush() is called to
|
||||||
|
# properly handle projection updates. However, we do not want to
|
||||||
|
# save this fit to the database, so we can immediately rollback
|
||||||
|
eos.db.saveddata_session.rollback()
|
||||||
|
|
||||||
refreshBoosts = False
|
refreshBoosts = False
|
||||||
if withBoosters is True:
|
if withBoosters is True:
|
||||||
refreshBoosts = True
|
refreshBoosts = True
|
||||||
@@ -347,6 +441,7 @@ class Fit(object):
|
|||||||
if dirtyStorage is not None:
|
if dirtyStorage is not None:
|
||||||
dirtyStorage.update(self.boostsFits)
|
dirtyStorage.update(self.boostsFits)
|
||||||
if self.fleet is not None and refreshBoosts is True:
|
if self.fleet is not None and refreshBoosts is True:
|
||||||
|
logger.debug("Fleet is set, gathering gang boosts")
|
||||||
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||||
elif self.fleet is None:
|
elif self.fleet is None:
|
||||||
self.gangBoosts = None
|
self.gangBoosts = None
|
||||||
@@ -355,80 +450,64 @@ class Fit(object):
|
|||||||
dirtyStorage.remove(self.ID)
|
dirtyStorage.remove(self.ID)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If we're not explicitly asked to project fit onto something,
|
# If we're not explicitly asked to project fit onto something,
|
||||||
# set self as target fit
|
# set self as target fit
|
||||||
if targetFit is None:
|
if targetFit is None:
|
||||||
targetFit = self
|
targetFit = self
|
||||||
forceProjected = False
|
projected = False
|
||||||
# Else, we're checking all target projectee fits
|
|
||||||
elif targetFit not in self.__calculatedTargets:
|
|
||||||
self.__calculatedTargets.append(targetFit)
|
|
||||||
targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
|
|
||||||
forceProjected = True
|
|
||||||
# Or do nothing if target fit is calculated
|
|
||||||
else:
|
else:
|
||||||
return
|
projected = True
|
||||||
|
|
||||||
# If fit is calculated and we have nothing to do here, get out
|
# If fit is calculated and we have nothing to do here, get out
|
||||||
if self.__calculated == True and forceProjected == False:
|
if self.__calculated and not projected:
|
||||||
|
logger.debug("Fit has already been calculated and is not projected, returning")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Mark fit as calculated
|
# Mark fit as calculated
|
||||||
self.__calculated = True
|
self.__calculated = True
|
||||||
|
|
||||||
# There's a few things to keep in mind here
|
|
||||||
# 1: Early effects first, then regular ones, then late ones, regardless of anything else
|
|
||||||
# 2: Some effects aren't implemented
|
|
||||||
# 3: Some effects are implemented poorly and will just explode on us
|
|
||||||
# 4: Errors should be handled gracefully and preferably without crashing unless serious
|
|
||||||
for runTime in ("early", "normal", "late"):
|
for runTime in ("early", "normal", "late"):
|
||||||
# Build a little chain of stuff
|
c = chain(
|
||||||
# Avoid adding projected drones and modules when fit is projected onto self
|
(self.character, self.ship),
|
||||||
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
|
self.drones,
|
||||||
if forceProjected is True:
|
self.boosters,
|
||||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules)
|
self.appliedImplants,
|
||||||
else:
|
self.modules
|
||||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
|
)
|
||||||
self.projectedDrones, self.projectedModules)
|
|
||||||
|
|
||||||
|
if not projected:
|
||||||
|
# if not a projected fit, add a couple of more things
|
||||||
|
c = chain(c, self.projectedDrones, self.projectedModules)
|
||||||
|
|
||||||
|
# We calculate gang bonuses first so that projected fits get them
|
||||||
if self.gangBoosts is not None:
|
if self.gangBoosts is not None:
|
||||||
contextMap = {Skill: "skill",
|
self.__calculateGangBoosts(runTime)
|
||||||
Ship: "ship",
|
|
||||||
Module: "module",
|
|
||||||
Implant: "implant"}
|
|
||||||
for name, info in self.gangBoosts.iteritems():
|
|
||||||
# Unpack all data required to run effect properly
|
|
||||||
effect, thing = info[1]
|
|
||||||
if effect.runTime == runTime:
|
|
||||||
context = ("gang", contextMap[type(thing)])
|
|
||||||
if isinstance(thing, Module):
|
|
||||||
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
|
||||||
(effect.isType("active") and thing.state >= State.ACTIVE):
|
|
||||||
# Run effect, and get proper bonuses applied
|
|
||||||
try:
|
|
||||||
self.register(thing)
|
|
||||||
effect.handler(self, thing, context)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Run effect, and get proper bonuses applied
|
|
||||||
try:
|
|
||||||
self.register(thing)
|
|
||||||
effect.handler(self, thing, context)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for item in c:
|
for item in c:
|
||||||
# Registering the item about to affect the fit allows us to track "Affected By" relations correctly
|
# Registering the item about to affect the fit allows us to
|
||||||
|
# track "Affected By" relations correctly
|
||||||
if item is not None:
|
if item is not None:
|
||||||
self.register(item)
|
self.register(item)
|
||||||
item.calculateModifiedAttributes(self, runTime, False)
|
item.calculateModifiedAttributes(self, runTime, False)
|
||||||
if forceProjected is True:
|
if projected is True:
|
||||||
targetFit.register(item)
|
for _ in xrange(projectionInfo.amount):
|
||||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
targetFit.register(item)
|
||||||
|
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||||
|
|
||||||
for fit in self.projectedFits:
|
timer.checkpoint('Done with runtime: %s'%runTime)
|
||||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
|
||||||
|
# Only apply projected fits if fit it not projected itself.
|
||||||
|
if not projected:
|
||||||
|
for fit in self.projectedFits:
|
||||||
|
if fit.getProjectionInfo(self.ID).active:
|
||||||
|
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||||
|
|
||||||
|
timer.checkpoint('Done with fit calculation')
|
||||||
|
|
||||||
|
if shadow:
|
||||||
|
logger.debug("Delete shadow fit object")
|
||||||
|
del self
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
"""
|
"""
|
||||||
@@ -901,6 +980,9 @@ class Fit(object):
|
|||||||
c.append(deepcopy(i, memo))
|
c.append(deepcopy(i, memo))
|
||||||
|
|
||||||
for fit in self.projectedFits:
|
for fit in self.projectedFits:
|
||||||
copy.projectedFits.append(fit)
|
copy.__projectedFits[fit.ID] = fit
|
||||||
|
# this bit is required -- see GH issue # 83
|
||||||
|
eos.db.saveddata_session.flush()
|
||||||
|
eos.db.saveddata_session.refresh(fit)
|
||||||
|
|
||||||
return copy
|
return copy
|
||||||
|
|||||||
@@ -20,46 +20,58 @@
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem
|
from eos.effectHandlerHelpers import HandledItem
|
||||||
from sqlalchemy.orm import validates, reconstructor
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Implant(HandledItem, ItemAttrShortcut):
|
class Implant(HandledItem, ItemAttrShortcut):
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
self.__slot = self.__calculateSlot(item)
|
|
||||||
self.__item = item
|
self.__item = item
|
||||||
self.itemID = item.ID
|
|
||||||
|
if self.isInvalid:
|
||||||
|
raise ValueError("Passed item is not an Implant")
|
||||||
|
|
||||||
|
self.itemID = item.ID if item is not None else None
|
||||||
self.active = True
|
self.active = True
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.build()
|
||||||
self.__itemModifiedAttributes.original = self.item.attributes
|
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
self.__item = None
|
self.__item = None
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
if self.itemID:
|
||||||
import eos.db
|
self.__item = eos.db.getItem(self.itemID)
|
||||||
self.__item = eos.db.getItem(self.itemID)
|
if self.__item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.isInvalid:
|
||||||
|
logger.error("Item (id: %d) is not an Implant", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
""" Build object. Assumes proper and valid item already set """
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
self.__slot = self.__calculateSlot(self.__item)
|
self.__slot = self.__calculateSlot(self.__item)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slot(self):
|
def isInvalid(self):
|
||||||
if self.__item is None:
|
return self.__item is None or self.__item.category.name != "Implant"
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slot(self):
|
||||||
return self.__slot
|
return self.__slot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def item(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
def __calculateSlot(self, item):
|
def __calculateSlot(self, item):
|
||||||
|
|||||||
@@ -19,36 +19,24 @@
|
|||||||
|
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem
|
from eos.effectHandlerHelpers import HandledItem
|
||||||
|
import eos.db
|
||||||
|
|
||||||
class Mode(ItemAttrShortcut, HandledItem):
|
class Mode(ItemAttrShortcut, HandledItem):
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
|
||||||
|
if item.group.name != "Ship Modifiers":
|
||||||
|
raise ValueError('Passed item "%s" (category: (%s)) is not a Ship Modifier'%(item.name, item.category.name))
|
||||||
|
|
||||||
self.__item = item
|
self.__item = item
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
|
|
||||||
if not isinstance(item, int):
|
|
||||||
self.__buildOriginal()
|
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
|
||||||
import eos.db
|
|
||||||
self.__item = eos.db.getItem(self.__item)
|
|
||||||
self.__buildOriginal()
|
|
||||||
|
|
||||||
def __buildOriginal(self):
|
|
||||||
self.__itemModifiedAttributes.original = self.item.attributes
|
self.__itemModifiedAttributes.original = self.item.attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def item(self):
|
||||||
if isinstance(self.__item, int):
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if isinstance(self.__item, int):
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
# @todo: rework to fit only on t3 dessy
|
# @todo: rework to fit only on t3 dessy
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, C
|
|||||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||||
from eos.enum import Enum
|
from eos.enum import Enum
|
||||||
from eos.mathUtils import floorFloat
|
from eos.mathUtils import floorFloat
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
OFFLINE = -1
|
OFFLINE = -1
|
||||||
@@ -49,95 +53,78 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
MINING_ATTRIBUTES = ("miningAmount", )
|
MINING_ATTRIBUTES = ("miningAmount", )
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
self.__item = item if item != None else 0
|
"""Initialize a module from the program"""
|
||||||
|
self.__item = item
|
||||||
|
|
||||||
|
if item is not None and self.isInvalid:
|
||||||
|
raise ValueError("Passed item is not a Module")
|
||||||
|
|
||||||
|
self.__charge = None
|
||||||
self.itemID = item.ID if item is not None else None
|
self.itemID = item.ID if item is not None else None
|
||||||
self.__charge = 0
|
|
||||||
self.projected = False
|
self.projected = False
|
||||||
self.state = State.ONLINE
|
self.state = State.ONLINE
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
@reconstructor
|
||||||
|
def init(self):
|
||||||
|
"""Initialize a module from the database and validate"""
|
||||||
|
self.__item = None
|
||||||
|
self.__charge = None
|
||||||
|
|
||||||
|
# we need this early if module is invalid and returns early
|
||||||
|
self.__slot = self.dummySlot
|
||||||
|
|
||||||
|
if self.itemID:
|
||||||
|
self.__item = eos.db.getItem(self.itemID)
|
||||||
|
if self.__item is None:
|
||||||
|
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.isInvalid:
|
||||||
|
logger.error("Item (id: %d) is not a Module", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.chargeID:
|
||||||
|
self.__charge = eos.db.getItem(self.chargeID)
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
""" Builds internal module variables from both init's """
|
||||||
|
|
||||||
|
if self.__charge and self.__charge.category.name != "Charge":
|
||||||
|
self.__charge = None
|
||||||
|
|
||||||
self.__dps = None
|
self.__dps = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__volley = None
|
self.__volley = None
|
||||||
self.__reloadTime = None
|
self.__reloadTime = None
|
||||||
self.__reloadForce = None
|
self.__reloadForce = None
|
||||||
self.__chargeCycles = None
|
self.__chargeCycles = None
|
||||||
|
self.__hardpoint = Hardpoint.NONE
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__slot = None
|
|
||||||
|
|
||||||
if item != None:
|
|
||||||
self.__itemModifiedAttributes.original = item.attributes
|
|
||||||
self.__hardpoint = self.__calculateHardpoint(item)
|
|
||||||
self.__slot = self.__calculateSlot(item)
|
|
||||||
|
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||||
|
self.__slot = self.dummySlot # defaults to None
|
||||||
|
|
||||||
@reconstructor
|
if self.__item:
|
||||||
def init(self):
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
if self.dummySlot is None:
|
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
||||||
self.__item = None
|
self.__slot = self.__calculateSlot(self.__item)
|
||||||
self.__charge = None
|
if self.__charge:
|
||||||
self.__volley = None
|
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
||||||
self.__dps = None
|
|
||||||
self.__miningyield = None
|
|
||||||
self.__reloadTime = None
|
|
||||||
self.__reloadForce = None
|
|
||||||
self.__chargeCycles = None
|
|
||||||
else:
|
|
||||||
self.__slot = self.dummySlot
|
|
||||||
self.__item = 0
|
|
||||||
self.__charge = 0
|
|
||||||
self.__dps = 0
|
|
||||||
self.__miningyield = 0
|
|
||||||
self.__volley = 0
|
|
||||||
self.__reloadTime = 0
|
|
||||||
self.__reloadForce = None
|
|
||||||
self.__chargeCycles = 0
|
|
||||||
self.__hardpoint = Hardpoint.NONE
|
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
|
||||||
import eos.db
|
|
||||||
item = eos.db.getItem(self.itemID)
|
|
||||||
self.__item = item
|
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
self.__itemModifiedAttributes.original = item.attributes
|
|
||||||
self.__hardpoint = self.__calculateHardpoint(item)
|
|
||||||
self.__slot = self.__calculateSlot(item)
|
|
||||||
|
|
||||||
def __fetchChargeInfo(self):
|
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
if self.chargeID is not None:
|
|
||||||
import eos.db
|
|
||||||
charge = eos.db.getItem(self.chargeID)
|
|
||||||
self.__charge = charge
|
|
||||||
self.__chargeModifiedAttributes.original = charge.attributes
|
|
||||||
else:
|
|
||||||
self.__charge = 0
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def buildEmpty(cls, slot):
|
def buildEmpty(cls, slot):
|
||||||
empty = Module(None)
|
empty = Module(None)
|
||||||
empty.__slot = slot
|
empty.__slot = slot
|
||||||
empty.__hardpoint = Hardpoint.NONE
|
|
||||||
empty.__item = 0
|
|
||||||
empty.__charge = 0
|
|
||||||
empty.dummySlot = slot
|
empty.dummySlot = slot
|
||||||
empty.__itemModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
|
|
||||||
return empty
|
return empty
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def buildRack(cls, slot):
|
def buildRack(cls, slot):
|
||||||
empty = Rack(None)
|
empty = Rack(None)
|
||||||
empty.__slot = slot
|
empty.__slot = slot
|
||||||
empty.__hardpoint = Hardpoint.NONE
|
|
||||||
empty.__item = 0
|
|
||||||
empty.__charge = 0
|
|
||||||
empty.dummySlot = slot
|
empty.dummySlot = slot
|
||||||
empty.__itemModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
|
|
||||||
|
|
||||||
return empty
|
return empty
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -146,11 +133,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hardpoint(self):
|
def hardpoint(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__hardpoint
|
return self.__hardpoint
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isInvalid(self):
|
||||||
|
if self.isEmpty:
|
||||||
|
return False
|
||||||
|
return self.__item is None or (self.__item.category.name not in ("Module", "Subsystem") and self.__item.group.name != "Effect Beacon")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def numCharges(self):
|
def numCharges(self):
|
||||||
if self.charge is None:
|
if self.charge is None:
|
||||||
@@ -263,38 +253,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def slot(self):
|
def slot(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__slot
|
return self.__slot
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chargeModifiedAttributes(self):
|
def chargeModifiedAttributes(self):
|
||||||
if self.__charge is None:
|
|
||||||
self.__fetchChargeInfo()
|
|
||||||
|
|
||||||
return self.__chargeModifiedAttributes
|
return self.__chargeModifiedAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def item(self):
|
||||||
if self.__item is None:
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__item if self.__item != 0 else None
|
return self.__item if self.__item != 0 else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def charge(self):
|
def charge(self):
|
||||||
if self.__charge is None:
|
|
||||||
self.__fetchChargeInfo()
|
|
||||||
|
|
||||||
return self.__charge if self.__charge != 0 else None
|
return self.__charge if self.__charge != 0 else None
|
||||||
|
|
||||||
@charge.setter
|
@charge.setter
|
||||||
@@ -516,7 +491,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
def getValidCharges(self):
|
def getValidCharges(self):
|
||||||
validCharges = set()
|
validCharges = set()
|
||||||
import eos.db
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
|
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
|
||||||
if itemChargeGroup is not None:
|
if itemChargeGroup is not None:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class Price(object):
|
|||||||
def __init__(self, typeID):
|
def __init__(self, typeID):
|
||||||
self.typeID = typeID
|
self.typeID = typeID
|
||||||
self.time = 0
|
self.time = 0
|
||||||
|
self.price = 0
|
||||||
self.failed = None
|
self.failed = None
|
||||||
self.__item = None
|
self.__item = None
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||||
from eos.effectHandlerHelpers import HandledItem
|
from eos.effectHandlerHelpers import HandledItem
|
||||||
from eos.saveddata.mode import Mode
|
from eos.saveddata.mode import Mode
|
||||||
|
import eos.db
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Ship(ItemAttrShortcut, HandledItem):
|
class Ship(ItemAttrShortcut, HandledItem):
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
@@ -28,33 +32,18 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
raise ValueError('Passed item "%s" (category: (%s)) is not under Ship category'%(item.name, item.category.name))
|
raise ValueError('Passed item "%s" (category: (%s)) is not under Ship category'%(item.name, item.category.name))
|
||||||
|
|
||||||
self.__item = item
|
self.__item = item
|
||||||
|
self.__modeItems = self.__getModeItems()
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__modeItems = self._getModeItems()
|
self.__itemModifiedAttributes.original = self.item.attributes
|
||||||
if not isinstance(item, int):
|
|
||||||
self.__buildOriginal()
|
|
||||||
|
|
||||||
self.commandBonus = 0
|
self.commandBonus = 0
|
||||||
|
|
||||||
def __fetchItemInfo(self):
|
|
||||||
import eos.db
|
|
||||||
self.__item = eos.db.getItem(self.__item)
|
|
||||||
self.__buildOriginal()
|
|
||||||
|
|
||||||
def __buildOriginal(self):
|
|
||||||
self.__itemModifiedAttributes.original = self.item.attributes
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self):
|
def item(self):
|
||||||
if isinstance(self.__item, int):
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__item
|
return self.__item
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemModifiedAttributes(self):
|
def itemModifiedAttributes(self):
|
||||||
if isinstance(self.__item, int):
|
|
||||||
self.__fetchItemInfo()
|
|
||||||
|
|
||||||
return self.__itemModifiedAttributes
|
return self.__itemModifiedAttributes
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
@@ -67,25 +56,16 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
if effect.runTime == runTime and effect.isType("passive"):
|
if effect.runTime == runTime and effect.isType("passive"):
|
||||||
effect.handler(fit, self, ("ship",))
|
effect.handler(fit, self, ("ship",))
|
||||||
|
|
||||||
def checkModeItem(self, item):
|
def validateModeItem(self, item):
|
||||||
"""
|
""" Checks if provided item is a valid mode """
|
||||||
Checks if provided item is a valid mode.
|
|
||||||
|
|
||||||
If ship has modes, and current item is not valid, return forced mode
|
|
||||||
else if mode is valid, return Mode
|
|
||||||
else if ship does not have modes, return None
|
|
||||||
|
|
||||||
@todo: rename this
|
|
||||||
"""
|
|
||||||
items = self.__modeItems
|
items = self.__modeItems
|
||||||
|
|
||||||
if items != None:
|
if items is not None:
|
||||||
if item == None or item not in items:
|
# if we have items, then we are in a tactical destroyer and must have a mode
|
||||||
# We have a tact dessy, but mode is None or not valid. Force new mode
|
if item is None or item not in items:
|
||||||
|
# If provided item is invalid mode, force new one
|
||||||
return Mode(items[0])
|
return Mode(items[0])
|
||||||
elif item in items:
|
return Mode(item)
|
||||||
# We have a valid mode
|
|
||||||
return Mode(item)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -96,21 +76,17 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
def modes(self):
|
def modes(self):
|
||||||
return [Mode(item) for item in self.__modeItems] if self.__modeItems else None
|
return [Mode(item) for item in self.__modeItems] if self.__modeItems else None
|
||||||
|
|
||||||
def _getModeItems(self):
|
def __getModeItems(self):
|
||||||
"""
|
"""
|
||||||
Returns a list of valid mode items for ship. Note that this returns the
|
Returns a list of valid mode items for ship. Note that this returns the
|
||||||
valid Item objects, not the Mode objects. Returns None if not a
|
valid Item objects, not the Mode objects. Returns None if not a
|
||||||
t3 dessy
|
t3 dessy
|
||||||
"""
|
"""
|
||||||
# @todo: is there a better way to determine this that isn't hardcoded groupIDs?
|
if self.item.group.name != "Tactical Destroyer":
|
||||||
if self.item.groupID != 1305:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
modeGroupID = 1306
|
|
||||||
import eos.db
|
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
g = eos.db.getGroup(modeGroupID, eager=("items.icon", "items.attributes"))
|
g = eos.db.getGroup("Ship Modifiers", eager=("items.icon", "items.attributes"))
|
||||||
for item in g.items:
|
for item in g.items:
|
||||||
# Rely on name detection because race is not reliable
|
# Rely on name detection because race is not reliable
|
||||||
if item.name.lower().startswith(self.item.name.lower()):
|
if item.name.lower().startswith(self.item.name.lower()):
|
||||||
|
|||||||
223
eos/slotFill.py
223
eos/slotFill.py
@@ -1,223 +0,0 @@
|
|||||||
#===============================================================================
|
|
||||||
# Copyright (C) 2010 Diego Duclos
|
|
||||||
#
|
|
||||||
# This file is part of eos.
|
|
||||||
#
|
|
||||||
# eos is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# eos is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#===============================================================================
|
|
||||||
|
|
||||||
from eos.types import Slot, Fit, Module, State
|
|
||||||
import random
|
|
||||||
import copy
|
|
||||||
import math
|
|
||||||
import bisect
|
|
||||||
import itertools
|
|
||||||
import time
|
|
||||||
|
|
||||||
class SlotFill(object):
|
|
||||||
def __init__(self, original, modules, attributeWeights=None, propertyWeights=None, specificWeights=None, defaultState = State.ACTIVE):
|
|
||||||
self.original = original
|
|
||||||
self.attributeWeights = attributeWeights or {}
|
|
||||||
self.propertyWeights = propertyWeights or {}
|
|
||||||
self.specificWeights = specificWeights or []
|
|
||||||
self.state = State.ACTIVE
|
|
||||||
self.modules = map(self.__newModule, modules)
|
|
||||||
|
|
||||||
def __newModule(self, item):
|
|
||||||
m = Module(item)
|
|
||||||
m.state = self.state
|
|
||||||
return m
|
|
||||||
|
|
||||||
def __getMetaParent(self, item):
|
|
||||||
metaGroup = item.metaGroup
|
|
||||||
return item if metaGroup is None else metaGroup.parent
|
|
||||||
|
|
||||||
def fitness(self, fit, chromosome):
|
|
||||||
modList = fit.modules
|
|
||||||
modAttr = fit.ship.getModifiedItemAttr
|
|
||||||
|
|
||||||
modList.extend(chromosome)
|
|
||||||
fit.clear()
|
|
||||||
fit.calculateModifiedAttributes()
|
|
||||||
|
|
||||||
if not fit.fits:
|
|
||||||
del modList[-len(chromosome):]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
weight = 0
|
|
||||||
for attr, value in self.attributeWeights.iteritems():
|
|
||||||
weight += modAttr(attr) * (value if value >= 0 else 1.0 / -value)
|
|
||||||
|
|
||||||
for prop, value in self.propertyWeights.iteritems():
|
|
||||||
weight += getattr(fit, prop) * (value if value >= 0 else 1.0 / -value)
|
|
||||||
|
|
||||||
for specific in self.specificWeights:
|
|
||||||
weight += specific(fit)
|
|
||||||
|
|
||||||
totalVars = (fit.ship.getModifiedItemAttr("powerOutput"),
|
|
||||||
fit.ship.getModifiedItemAttr("cpuOutput"),
|
|
||||||
fit.ship.getModifiedItemAttr('upgradeCapacity'))
|
|
||||||
|
|
||||||
usedVars = (fit.pgUsed, fit.cpuUsed, fit.calibrationUsed)
|
|
||||||
|
|
||||||
total = 0
|
|
||||||
used = 0
|
|
||||||
for tv, uv in zip(totalVars, usedVars):
|
|
||||||
if uv > tv:
|
|
||||||
del modList[-len(chromosome):]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
del modList[-len(chromosome):]
|
|
||||||
|
|
||||||
|
|
||||||
return weight
|
|
||||||
|
|
||||||
|
|
||||||
def run(self, elite = 0.05, crossoverChance = 0.8, slotMutationChance = 0.5, typeMutationChance = 0.5):
|
|
||||||
#Use a copy of the original for all our calcs. We don't want to damage it
|
|
||||||
fit = copy.deepcopy(self.original)
|
|
||||||
fit.unfill()
|
|
||||||
|
|
||||||
#First of all, lets check the number of slots we got to play with
|
|
||||||
chromLength = -1
|
|
||||||
slotAmounts = {}
|
|
||||||
for type in Slot.getTypes():
|
|
||||||
slot = Slot.getValue(type)
|
|
||||||
amount = fit.getSlotsFree(slot)
|
|
||||||
if amount > 0:
|
|
||||||
slotAmounts[slot] = amount
|
|
||||||
|
|
||||||
chromLength += amount
|
|
||||||
|
|
||||||
if not slotAmounts:
|
|
||||||
#Nothing to do, joy
|
|
||||||
return
|
|
||||||
|
|
||||||
slotModules = {}
|
|
||||||
metaModules = {}
|
|
||||||
|
|
||||||
for slotType in slotAmounts:
|
|
||||||
slotModules[slotType] = modules = []
|
|
||||||
|
|
||||||
for module in self.modules:
|
|
||||||
#Store the variations of each base for ease and speed
|
|
||||||
metaParent = self.__getMetaParent(module.item)
|
|
||||||
metaList = metaModules.get(metaParent)
|
|
||||||
if metaList is None:
|
|
||||||
metaList = metaModules[metaParent] = []
|
|
||||||
metaList.append(module)
|
|
||||||
|
|
||||||
#Sort stuff by slotType for ease and speed
|
|
||||||
slot = module.slot
|
|
||||||
if slot in slotModules:
|
|
||||||
slotModules[slot].append(module)
|
|
||||||
|
|
||||||
for slotType, modules in slotModules.iteritems():
|
|
||||||
if len(modules) == 0:
|
|
||||||
chromLength -= slotAmounts[slotType]
|
|
||||||
del slotAmounts[slotType]
|
|
||||||
|
|
||||||
#Now, we need an initial set, first thing to do is decide how big that set will be
|
|
||||||
setSize = 10
|
|
||||||
|
|
||||||
#Grab some variables locally for performance improvements
|
|
||||||
rchoice = random.choice
|
|
||||||
rrandom = random.random
|
|
||||||
rrandint = random.randint
|
|
||||||
bbisect = bisect.bisect
|
|
||||||
ccopy = copy.copy
|
|
||||||
|
|
||||||
#Get our list for storage of our chromosomes
|
|
||||||
chromosomes = []
|
|
||||||
|
|
||||||
# Helpers
|
|
||||||
weigher = lambda chromosome: (self.fitness(fit, chromosome), chromosome)
|
|
||||||
keyer = lambda info: info[0]
|
|
||||||
|
|
||||||
eliteCutout = int(math.floor(setSize * (1 - elite)))
|
|
||||||
lastEl = setSize - 1
|
|
||||||
|
|
||||||
#Generate our initial set entirely randomly
|
|
||||||
#Subtelies to take in mind:
|
|
||||||
# * modules of the same slotType are kept together for easy cross-overing
|
|
||||||
state = self.state
|
|
||||||
for _ in xrange(setSize):
|
|
||||||
chrom = []
|
|
||||||
for type, amount in slotAmounts.iteritems():
|
|
||||||
for _ in xrange(amount):
|
|
||||||
chrom.append(rchoice(slotModules[type]))
|
|
||||||
|
|
||||||
chromosomes.append(weigher(chrom))
|
|
||||||
|
|
||||||
#Sort our initial set
|
|
||||||
chromosomes.sort(key=keyer)
|
|
||||||
currentGeneration = chromosomes
|
|
||||||
|
|
||||||
#Yield the best result from our initial set, this is gonna be pretty bad
|
|
||||||
yield currentGeneration[lastEl]
|
|
||||||
|
|
||||||
#Setup's done, now we can actualy apply our genetic algorithm to optimize all this
|
|
||||||
while True:
|
|
||||||
moo = time.time()
|
|
||||||
#First thing we do, we're gonna be elitair
|
|
||||||
#Grab the top x%, we'll put em in the next generation
|
|
||||||
nextGeneration = []
|
|
||||||
for i in xrange(lastEl, eliteCutout - 1, -1):
|
|
||||||
nextGeneration.append(currentGeneration[i])
|
|
||||||
|
|
||||||
#Figure out our ratios to do our roulette wheel
|
|
||||||
fitnessList = map(keyer, currentGeneration)
|
|
||||||
totalFitness = float(sum(fitnessList))
|
|
||||||
|
|
||||||
curr = 0
|
|
||||||
ratios = []
|
|
||||||
for fitness in fitnessList:
|
|
||||||
curr += fitness
|
|
||||||
ratios.append(curr / (totalFitness or 1))
|
|
||||||
|
|
||||||
t = 0
|
|
||||||
#Do our pairing
|
|
||||||
for _ in xrange(0, eliteCutout):
|
|
||||||
# Crossover chance
|
|
||||||
mother = currentGeneration[bbisect(ratios, rrandom())][1]
|
|
||||||
father = currentGeneration[bbisect(ratios, rrandom())][1]
|
|
||||||
if rrandom() <= crossoverChance:
|
|
||||||
crosspoint = rrandint(0, chromLength)
|
|
||||||
luke = mother[:crosspoint] + father[crosspoint:]
|
|
||||||
else:
|
|
||||||
luke = father
|
|
||||||
|
|
||||||
#Chance for slot mutation
|
|
||||||
if rrandom() <= slotMutationChance:
|
|
||||||
target = rrandint(0, chromLength)
|
|
||||||
mod = luke[target]
|
|
||||||
luke[target] = rchoice(slotModules[mod.slot])
|
|
||||||
|
|
||||||
if rrandom() <= typeMutationChance:
|
|
||||||
#Mutation of an item to another one of the same type
|
|
||||||
target = rrandint(0, chromLength)
|
|
||||||
mod = luke[target]
|
|
||||||
vars = metaModules[self.__getMetaParent(mod.item)]
|
|
||||||
luke[target] = rchoice(vars)
|
|
||||||
|
|
||||||
tt = time.time()
|
|
||||||
nextGeneration.append(weigher(luke))
|
|
||||||
t += time.time() - tt
|
|
||||||
|
|
||||||
print "time spent weighing: ", t
|
|
||||||
|
|
||||||
nextGeneration.sort(key=keyer)
|
|
||||||
currentGeneration = nextGeneration
|
|
||||||
print "total time spent this iteration:", time.time() - moo
|
|
||||||
yield currentGeneration[lastEl]
|
|
||||||
@@ -71,10 +71,23 @@ def getBitmap(name,location):
|
|||||||
# print "#BMPs:%d - Current took: %.8f" % (cachedBitmapsCount,time.clock() - start)
|
# print "#BMPs:%d - Current took: %.8f" % (cachedBitmapsCount,time.clock() - start)
|
||||||
return bmp
|
return bmp
|
||||||
|
|
||||||
|
def stripPath(fname):
|
||||||
|
"""
|
||||||
|
Here we extract 'core' of icon name. Path and
|
||||||
|
extension are sometimes specified in database
|
||||||
|
but we don't need them.
|
||||||
|
"""
|
||||||
|
# Path before the icon file name
|
||||||
|
fname = fname.split('/')[-1]
|
||||||
|
# Extension
|
||||||
|
fname = fname.rsplit('.', 1)[0]
|
||||||
|
return fname
|
||||||
|
|
||||||
def getImage(name, location):
|
def getImage(name, location):
|
||||||
if location in locationMap:
|
if location in locationMap:
|
||||||
if location == "pack":
|
if location == "pack":
|
||||||
location = locationMap[location]
|
location = locationMap[location]
|
||||||
|
name = stripPath(name)
|
||||||
filename = "icon{0}.png".format(name)
|
filename = "icon{0}.png".format(name)
|
||||||
path = os.path.join(location, filename)
|
path = os.path.join(location, filename)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ __all__ = [
|
|||||||
#"changeAffectingSkills",
|
#"changeAffectingSkills",
|
||||||
"tacticalMode",
|
"tacticalMode",
|
||||||
"targetResists",
|
"targetResists",
|
||||||
"priceClear"
|
"priceClear",
|
||||||
|
"amount",
|
||||||
]
|
]
|
||||||
|
|||||||
76
gui/builtinContextMenus/amount.py
Normal file
76
gui/builtinContextMenus/amount.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from gui.contextMenu import ContextMenu
|
||||||
|
from gui.itemStats import ItemStatsDialog
|
||||||
|
import eos.types
|
||||||
|
import gui.mainFrame
|
||||||
|
import service
|
||||||
|
import gui.globalEvents as GE
|
||||||
|
import wx
|
||||||
|
|
||||||
|
class ChangeAmount(ContextMenu):
|
||||||
|
def __init__(self):
|
||||||
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
|
||||||
|
def display(self, srcContext, selection):
|
||||||
|
return srcContext in ("cargoItem","projectedFit")
|
||||||
|
|
||||||
|
def getText(self, itmContext, selection):
|
||||||
|
print selection
|
||||||
|
return "Change {0} Quantity".format(itmContext)
|
||||||
|
|
||||||
|
def activate(self, fullContext, selection, i):
|
||||||
|
srcContext = fullContext[0]
|
||||||
|
dlg = AmountChanger(self.mainFrame, selection[0], srcContext)
|
||||||
|
dlg.ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
ChangeAmount.register()
|
||||||
|
|
||||||
|
class AmountChanger(wx.Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, thing, context):
|
||||||
|
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
|
||||||
|
self.thing = thing
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
|
||||||
|
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||||
|
|
||||||
|
bSizer1.Add(self.input, 1, wx.ALL, 5)
|
||||||
|
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
||||||
|
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
|
||||||
|
self.button = wx.Button(self, wx.ID_OK, u"Done")
|
||||||
|
bSizer1.Add(self.button, 0, wx.ALL, 5)
|
||||||
|
|
||||||
|
self.SetSizer(bSizer1)
|
||||||
|
self.Layout()
|
||||||
|
self.Centre(wx.BOTH)
|
||||||
|
self.button.Bind(wx.EVT_BUTTON, self.change)
|
||||||
|
|
||||||
|
def change(self, event):
|
||||||
|
sFit = service.Fit.getInstance()
|
||||||
|
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
fitID = mainFrame.getActiveFit()
|
||||||
|
|
||||||
|
if isinstance(self.thing, eos.types.Cargo):
|
||||||
|
sFit.addCargo(fitID, self.thing.item.ID, int(self.input.GetLineText(0)), replace=True)
|
||||||
|
elif isinstance(self.thing, eos.types.Fit):
|
||||||
|
sFit.changeAmount(fitID, self.thing, int(self.input.GetLineText(0)))
|
||||||
|
|
||||||
|
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
## checks to make sure it's valid number
|
||||||
|
def onChar(self, event):
|
||||||
|
key = event.GetKeyCode()
|
||||||
|
|
||||||
|
acceptable_characters = "1234567890"
|
||||||
|
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
||||||
|
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
||||||
|
event.Skip()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@@ -29,67 +29,3 @@ class Cargo(ContextMenu):
|
|||||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||||
|
|
||||||
Cargo.register()
|
Cargo.register()
|
||||||
|
|
||||||
class CargoAmount(ContextMenu):
|
|
||||||
def __init__(self):
|
|
||||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
|
||||||
|
|
||||||
def display(self, srcContext, selection):
|
|
||||||
return srcContext in ("cargoItem",) and selection[0].amount >= 0
|
|
||||||
|
|
||||||
def getText(self, itmContext, selection):
|
|
||||||
return "Change {0} Quantity".format(itmContext)
|
|
||||||
|
|
||||||
def activate(self, fullContext, selection, i):
|
|
||||||
srcContext = fullContext[0]
|
|
||||||
dlg = CargoChanger(self.mainFrame, selection[0], srcContext)
|
|
||||||
dlg.ShowModal()
|
|
||||||
dlg.Destroy()
|
|
||||||
|
|
||||||
CargoAmount.register()
|
|
||||||
|
|
||||||
class CargoChanger(wx.Dialog):
|
|
||||||
|
|
||||||
def __init__(self, parent, cargo, context):
|
|
||||||
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
|
|
||||||
self.cargo = cargo
|
|
||||||
self.context = context
|
|
||||||
|
|
||||||
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
|
|
||||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
|
||||||
|
|
||||||
bSizer1.Add(self.input, 1, wx.ALL, 5)
|
|
||||||
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
|
||||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
|
|
||||||
self.button = wx.Button(self, wx.ID_OK, u"Done")
|
|
||||||
bSizer1.Add(self.button, 0, wx.ALL, 5)
|
|
||||||
|
|
||||||
self.SetSizer(bSizer1)
|
|
||||||
self.Layout()
|
|
||||||
self.Centre(wx.BOTH)
|
|
||||||
self.button.Bind(wx.EVT_BUTTON, self.change)
|
|
||||||
|
|
||||||
def change(self, event):
|
|
||||||
sFit = service.Fit.getInstance()
|
|
||||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
|
||||||
fitID = mainFrame.getActiveFit()
|
|
||||||
|
|
||||||
sFit.addCargo(fitID, self.cargo.item.ID, int(self.input.GetLineText(0)), replace=True)
|
|
||||||
|
|
||||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
|
||||||
|
|
||||||
event.Skip()
|
|
||||||
self.Destroy()
|
|
||||||
## checks to make sure it's valid number
|
|
||||||
def onChar(self, event):
|
|
||||||
key = event.GetKeyCode()
|
|
||||||
|
|
||||||
acceptable_characters = "1234567890"
|
|
||||||
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
|
||||||
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
|
||||||
event.Skip()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class PFGeneralPref ( PreferenceView):
|
|||||||
self.cbShowTooltip = wx.CheckBox( panel, wx.ID_ANY, u"Show tab tooltips", wx.DefaultPosition, wx.DefaultSize, 0 )
|
self.cbShowTooltip = wx.CheckBox( panel, wx.ID_ANY, u"Show tab tooltips", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||||
mainSizer.Add( self.cbShowTooltip, 0, wx.ALL|wx.EXPAND, 5 )
|
mainSizer.Add( self.cbShowTooltip, 0, wx.ALL|wx.EXPAND, 5 )
|
||||||
|
|
||||||
|
self.cbMarketShortcuts = wx.CheckBox( panel, wx.ID_ANY, u"Show market shortcuts", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||||
|
mainSizer.Add( self.cbMarketShortcuts, 0, wx.ALL|wx.EXPAND, 5 )
|
||||||
|
|
||||||
defCharSizer = wx.BoxSizer( wx.HORIZONTAL )
|
defCharSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||||
|
|
||||||
self.sFit = service.Fit.getInstance()
|
self.sFit = service.Fit.getInstance()
|
||||||
@@ -69,6 +72,7 @@ class PFGeneralPref ( PreferenceView):
|
|||||||
self.cbCompactSkills.SetValue(self.sFit.serviceFittingOptions["compactSkills"] or False)
|
self.cbCompactSkills.SetValue(self.sFit.serviceFittingOptions["compactSkills"] or False)
|
||||||
self.cbReopenFits.SetValue(self.openFitsSettings["enabled"])
|
self.cbReopenFits.SetValue(self.openFitsSettings["enabled"])
|
||||||
self.cbShowTooltip.SetValue(self.sFit.serviceFittingOptions["showTooltip"] or False)
|
self.cbShowTooltip.SetValue(self.sFit.serviceFittingOptions["showTooltip"] or False)
|
||||||
|
self.cbMarketShortcuts.SetValue(self.sFit.serviceFittingOptions["showMarketShortcuts"] or False)
|
||||||
|
|
||||||
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
||||||
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
||||||
@@ -79,6 +83,7 @@ class PFGeneralPref ( PreferenceView):
|
|||||||
self.cbCompactSkills.Bind(wx.EVT_CHECKBOX, self.onCBCompactSkills)
|
self.cbCompactSkills.Bind(wx.EVT_CHECKBOX, self.onCBCompactSkills)
|
||||||
self.cbReopenFits.Bind(wx.EVT_CHECKBOX, self.onCBReopenFits)
|
self.cbReopenFits.Bind(wx.EVT_CHECKBOX, self.onCBReopenFits)
|
||||||
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
|
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
|
||||||
|
self.cbMarketShortcuts.Bind(wx.EVT_CHECKBOX, self.onCBShowShortcuts)
|
||||||
|
|
||||||
self.cbRackLabels.Enable(self.sFit.serviceFittingOptions["rackSlots"] or False)
|
self.cbRackLabels.Enable(self.sFit.serviceFittingOptions["rackSlots"] or False)
|
||||||
|
|
||||||
@@ -135,6 +140,9 @@ class PFGeneralPref ( PreferenceView):
|
|||||||
def onCBShowTooltip(self, event):
|
def onCBShowTooltip(self, event):
|
||||||
self.sFit.serviceFittingOptions["showTooltip"] = self.cbShowTooltip.GetValue()
|
self.sFit.serviceFittingOptions["showTooltip"] = self.cbShowTooltip.GetValue()
|
||||||
|
|
||||||
|
def onCBShowShortcuts(self, event):
|
||||||
|
self.sFit.serviceFittingOptions["showMarketShortcuts"] = self.cbMarketShortcuts.GetValue()
|
||||||
|
|
||||||
def getImage(self):
|
def getImage(self):
|
||||||
return bitmapLoader.getBitmap("prefs_settings", "icons")
|
return bitmapLoader.getBitmap("prefs_settings", "icons")
|
||||||
|
|
||||||
|
|||||||
@@ -30,37 +30,13 @@ class PriceViewFull(StatsView):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
StatsView.__init__(self)
|
StatsView.__init__(self)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._timerId = wx.NewId()
|
|
||||||
self._timer = None
|
|
||||||
self.parent.Bind(wx.EVT_TIMER, self.OnTimer)
|
|
||||||
self._timerRunsBeforeUpdate = 60
|
|
||||||
self._timerRuns = 0
|
|
||||||
self._timerIdUpdate = wx.NewId()
|
|
||||||
self._timerUpdate = None
|
|
||||||
self._cachedShip = 0
|
self._cachedShip = 0
|
||||||
self._cachedFittings = 0
|
self._cachedFittings = 0
|
||||||
self._cachedTotal = 0
|
self._cachedTotal = 0
|
||||||
|
|
||||||
def OnTimer(self, event):
|
|
||||||
if self._timerId == event.GetId():
|
|
||||||
if self._timerRuns >= self._timerRunsBeforeUpdate:
|
|
||||||
self._timerRuns = 0
|
|
||||||
self._timer.Stop()
|
|
||||||
self.refreshPanel(self.fit)
|
|
||||||
else:
|
|
||||||
self.labelEMStatus.SetLabel("Prices update retry in: %d seconds" %(self._timerRunsBeforeUpdate - self._timerRuns))
|
|
||||||
self._timerRuns += 1
|
|
||||||
if self._timerIdUpdate == event.GetId():
|
|
||||||
self._timerUpdate.Stop()
|
|
||||||
self.labelEMStatus.SetLabel("")
|
|
||||||
|
|
||||||
def getHeaderText(self, fit):
|
def getHeaderText(self, fit):
|
||||||
return "Price"
|
return "Price"
|
||||||
|
|
||||||
def getTextExtentW(self, text):
|
|
||||||
width, height = self.parent.GetTextExtent(text)
|
|
||||||
return width
|
|
||||||
|
|
||||||
def populatePanel(self, contentPanel, headerPanel):
|
def populatePanel(self, contentPanel, headerPanel):
|
||||||
contentSizer = contentPanel.GetSizer()
|
contentSizer = contentPanel.GetSizer()
|
||||||
self.panel = contentPanel
|
self.panel = contentPanel
|
||||||
@@ -111,22 +87,11 @@ class PriceViewFull(StatsView):
|
|||||||
for cargo in fit.cargo:
|
for cargo in fit.cargo:
|
||||||
for _ in xrange(cargo.amount):
|
for _ in xrange(cargo.amount):
|
||||||
typeIDs.append(cargo.itemID)
|
typeIDs.append(cargo.itemID)
|
||||||
if self._timer:
|
|
||||||
if self._timer.IsRunning():
|
|
||||||
self._timer.Stop()
|
|
||||||
sMkt = service.Market.getInstance()
|
sMkt = service.Market.getInstance()
|
||||||
sMkt.getPrices(typeIDs, self.processPrices)
|
sMkt.getPrices(typeIDs, self.processPrices)
|
||||||
self.labelEMStatus.SetLabel("Updating prices...")
|
self.labelEMStatus.SetLabel("Updating prices...")
|
||||||
if not self._timerUpdate:
|
|
||||||
self._timerUpdate = wx.Timer(self.parent, self._timerIdUpdate)
|
|
||||||
if self._timerUpdate:
|
|
||||||
if not self._timerUpdate.IsRunning():
|
|
||||||
self._timerUpdate.Start(1000)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self._timer:
|
|
||||||
if self._timer.IsRunning():
|
|
||||||
self._timer.Stop()
|
|
||||||
self.labelEMStatus.SetLabel("")
|
self.labelEMStatus.SetLabel("")
|
||||||
self.labelPriceShip.SetLabel("0.0 ISK")
|
self.labelPriceShip.SetLabel("0.0 ISK")
|
||||||
self.labelPriceFittings.SetLabel("0.0 ISK")
|
self.labelPriceFittings.SetLabel("0.0 ISK")
|
||||||
@@ -136,20 +101,10 @@ class PriceViewFull(StatsView):
|
|||||||
|
|
||||||
def processPrices(self, prices):
|
def processPrices(self, prices):
|
||||||
shipPrice = prices[0].price
|
shipPrice = prices[0].price
|
||||||
if shipPrice == None:
|
|
||||||
if not self._timer:
|
|
||||||
self._timer = wx.Timer(self.parent, self._timerId)
|
|
||||||
self._timer.Start(1000)
|
|
||||||
self._timerRuns = 0
|
|
||||||
else:
|
|
||||||
if self._timer:
|
|
||||||
self._timer.Stop()
|
|
||||||
|
|
||||||
self.labelEMStatus.SetLabel("")
|
|
||||||
|
|
||||||
if shipPrice == None:
|
|
||||||
shipPrice = 0
|
|
||||||
modPrice = sum(map(lambda p: p.price or 0, prices[1:]))
|
modPrice = sum(map(lambda p: p.price or 0, prices[1:]))
|
||||||
|
|
||||||
|
self.labelEMStatus.SetLabel("")
|
||||||
|
|
||||||
if self._cachedShip != shipPrice:
|
if self._cachedShip != shipPrice:
|
||||||
self.labelPriceShip.SetLabel("%s ISK" % formatAmount(shipPrice, 3, 3, 9, currency=True))
|
self.labelPriceShip.SetLabel("%s ISK" % formatAmount(shipPrice, 3, 3, 9, currency=True))
|
||||||
self.labelPriceShip.SetToolTip(wx.ToolTip(locale.format('%.2f', shipPrice, 1)))
|
self.labelPriceShip.SetToolTip(wx.ToolTip(locale.format('%.2f', shipPrice, 1)))
|
||||||
|
|||||||
@@ -197,8 +197,9 @@ class ResistancesViewFull(StatsView):
|
|||||||
lbl = getattr(self, "labelResistance%sEhp" % tankType.capitalize())
|
lbl = getattr(self, "labelResistance%sEhp" % tankType.capitalize())
|
||||||
if ehp is not None:
|
if ehp is not None:
|
||||||
total += ehp[tankType]
|
total += ehp[tankType]
|
||||||
|
rrFactor = fit.ehp[tankType] / fit.hp[tankType]
|
||||||
lbl.SetLabel(formatAmount(ehp[tankType], 3, 0, 9))
|
lbl.SetLabel(formatAmount(ehp[tankType], 3, 0, 9))
|
||||||
lbl.SetToolTip(wx.ToolTip("%s: %d" % (tankType.capitalize(), ehp[tankType])))
|
lbl.SetToolTip(wx.ToolTip("%s: %d\nResist Multiplier: x%.2f" % (tankType.capitalize(), ehp[tankType], rrFactor)))
|
||||||
else:
|
else:
|
||||||
lbl.SetLabel("0")
|
lbl.SetLabel("0")
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
from gui import builtinViewColumns
|
from gui import builtinViewColumns
|
||||||
from gui.viewColumn import ViewColumn
|
from gui.viewColumn import ViewColumn
|
||||||
from gui import bitmapLoader
|
from gui import bitmapLoader
|
||||||
|
import gui.mainFrame
|
||||||
|
|
||||||
import wx
|
import wx
|
||||||
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
|
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
|
||||||
import service
|
import service
|
||||||
@@ -29,6 +31,7 @@ class BaseName(ViewColumn):
|
|||||||
name = "Base Name"
|
name = "Base Name"
|
||||||
def __init__(self, fittingView, params):
|
def __init__(self, fittingView, params):
|
||||||
ViewColumn.__init__(self, fittingView)
|
ViewColumn.__init__(self, fittingView)
|
||||||
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
self.columnText = "Name"
|
self.columnText = "Name"
|
||||||
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons")
|
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons")
|
||||||
self.mask = wx.LIST_MASK_TEXT
|
self.mask = wx.LIST_MASK_TEXT
|
||||||
@@ -39,7 +42,8 @@ class BaseName(ViewColumn):
|
|||||||
elif isinstance(stuff, Cargo):
|
elif isinstance(stuff, Cargo):
|
||||||
return "%dx %s" % (stuff.amount, stuff.item.name)
|
return "%dx %s" % (stuff.amount, stuff.item.name)
|
||||||
elif isinstance(stuff, Fit):
|
elif isinstance(stuff, Fit):
|
||||||
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name)
|
||||||
elif isinstance(stuff, Rack):
|
elif isinstance(stuff, Rack):
|
||||||
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
|
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
|
||||||
if stuff.slot == Slot.MODE:
|
if stuff.slot == Slot.MODE:
|
||||||
@@ -55,6 +59,15 @@ class BaseName(ViewColumn):
|
|||||||
return stuff.item.name
|
return stuff.item.name
|
||||||
else:
|
else:
|
||||||
item = getattr(stuff, "item", stuff)
|
item = getattr(stuff, "item", stuff)
|
||||||
|
|
||||||
|
if service.Fit.getInstance().serviceFittingOptions["showMarketShortcuts"]:
|
||||||
|
marketShortcut = getattr(item, "marketShortcut", None)
|
||||||
|
|
||||||
|
if marketShortcut:
|
||||||
|
# use unicode subscript to display shortcut value
|
||||||
|
shortcut = unichr(marketShortcut+8320)+u" "
|
||||||
|
return shortcut+item.name
|
||||||
|
|
||||||
return item.name
|
return item.name
|
||||||
|
|
||||||
BaseName.register()
|
BaseName.register()
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ class Price(ViewColumn):
|
|||||||
self.imageId = fittingView.imageList.GetImageIndex("totalPrice_small", "icons")
|
self.imageId = fittingView.imageList.GetImageIndex("totalPrice_small", "icons")
|
||||||
|
|
||||||
def getText(self, stuff):
|
def getText(self, stuff):
|
||||||
if stuff.item is None:
|
if stuff.item is None or stuff.item.group.name == "Ship Modifiers":
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
sMkt = service.Market.getInstance()
|
sMkt = service.Market.getInstance()
|
||||||
price = sMkt.getPriceNow(stuff.item.ID)
|
price = sMkt.getPriceNow(stuff.item.ID)
|
||||||
|
|
||||||
if not price or not price.price:
|
if not price or not price.price or not price.isValid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
price = price.price # Set new price variable with what we need
|
price = price.price # Set new price variable with what we need
|
||||||
@@ -50,12 +50,17 @@ class Price(ViewColumn):
|
|||||||
return formatAmount(price, 3, 3, 9, currency=True)
|
return formatAmount(price, 3, 3, 9, currency=True)
|
||||||
|
|
||||||
def delayedText(self, mod, display, colItem):
|
def delayedText(self, mod, display, colItem):
|
||||||
def callback(requests):
|
sMkt = service.Market.getInstance()
|
||||||
price = requests[0].price
|
def callback(item):
|
||||||
colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "")
|
price = sMkt.getPriceNow(item.ID)
|
||||||
|
text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else ""
|
||||||
|
if price.failed: text += " (!)"
|
||||||
|
colItem.SetText(text)
|
||||||
|
|
||||||
display.SetItem(colItem)
|
display.SetItem(colItem)
|
||||||
|
|
||||||
service.Market.getInstance().getPrices([mod.item.ID], callback)
|
|
||||||
|
sMkt.waitForPrice(mod.item, callback)
|
||||||
|
|
||||||
def getImageId(self, mod):
|
def getImageId(self, mod):
|
||||||
return -1
|
return -1
|
||||||
|
|||||||
@@ -19,27 +19,21 @@
|
|||||||
|
|
||||||
from gui.viewColumn import ViewColumn
|
from gui.viewColumn import ViewColumn
|
||||||
from gui import bitmapLoader
|
from gui import bitmapLoader
|
||||||
|
import gui.mainFrame
|
||||||
|
|
||||||
import wx
|
import wx
|
||||||
from eos.types import Drone, Module, Rack
|
from eos.types import Drone, Module, Rack, Fit
|
||||||
from eos.types import State as State_
|
from eos.types import State as State_
|
||||||
|
|
||||||
class State(ViewColumn):
|
class State(ViewColumn):
|
||||||
name = "State"
|
name = "State"
|
||||||
def __init__(self, fittingView, params):
|
def __init__(self, fittingView, params):
|
||||||
ViewColumn.__init__(self, fittingView)
|
ViewColumn.__init__(self, fittingView)
|
||||||
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
self.resizable = False
|
self.resizable = False
|
||||||
self.size = 16
|
self.size = 16
|
||||||
self.maxsize = self.size
|
self.maxsize = self.size
|
||||||
self.mask = wx.LIST_MASK_IMAGE
|
self.mask = wx.LIST_MASK_IMAGE
|
||||||
for name, state in (("checked", wx.CONTROL_CHECKED), ("unchecked", 0)):
|
|
||||||
bitmap = wx.EmptyBitmap(16, 16)
|
|
||||||
dc = wx.MemoryDC()
|
|
||||||
dc.SelectObject(bitmap)
|
|
||||||
dc.SetBackground(wx.TheBrushList.FindOrCreateBrush(fittingView.GetBackgroundColour(), wx.SOLID))
|
|
||||||
dc.Clear()
|
|
||||||
wx.RendererNative.Get().DrawCheckBox(fittingView, dc, wx.Rect(0, 0, 16, 16), state)
|
|
||||||
dc.Destroy()
|
|
||||||
setattr(self, "%sId" % name, fittingView.imageList.Add(bitmap))
|
|
||||||
|
|
||||||
def getText(self, mod):
|
def getText(self, mod):
|
||||||
return ""
|
return ""
|
||||||
@@ -49,8 +43,14 @@ class State(ViewColumn):
|
|||||||
return State_.getName(mod.state).title()
|
return State_.getName(mod.state).title()
|
||||||
|
|
||||||
def getImageId(self, stuff):
|
def getImageId(self, stuff):
|
||||||
|
generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(1).lower(), "icons")
|
||||||
|
generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(-1).lower(), "icons")
|
||||||
|
|
||||||
if isinstance(stuff, Drone):
|
if isinstance(stuff, Drone):
|
||||||
return self.checkedId if stuff.amountActive > 0 else self.uncheckedId
|
if stuff.amountActive > 0:
|
||||||
|
return generic_active
|
||||||
|
else:
|
||||||
|
return generic_inactive
|
||||||
elif isinstance(stuff, Rack):
|
elif isinstance(stuff, Rack):
|
||||||
return -1
|
return -1
|
||||||
elif isinstance(stuff, Module):
|
elif isinstance(stuff, Module):
|
||||||
@@ -58,11 +58,21 @@ class State(ViewColumn):
|
|||||||
return -1
|
return -1
|
||||||
else:
|
else:
|
||||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons")
|
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons")
|
||||||
|
elif isinstance(stuff, Fit):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
projectionInfo = stuff.getProjectionInfo(fitID)
|
||||||
|
|
||||||
|
if projectionInfo is None:
|
||||||
|
return -1
|
||||||
|
if projectionInfo.active:
|
||||||
|
return generic_active
|
||||||
|
return generic_inactive
|
||||||
else:
|
else:
|
||||||
active = getattr(stuff, "active", None)
|
active = getattr(stuff, "active", None)
|
||||||
if active is None:
|
if active is None:
|
||||||
return -1
|
return -1
|
||||||
else:
|
if active:
|
||||||
return self.checkedId if active else self.uncheckedId
|
return generic_active
|
||||||
|
return generic_inactive
|
||||||
|
|
||||||
State.register()
|
State.register()
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ class CharacterEditor(wx.Frame):
|
|||||||
self.viewsNBContainer = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
|
self.viewsNBContainer = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
|
|
||||||
self.sview = SkillTreeView(self.viewsNBContainer)
|
self.sview = SkillTreeView(self.viewsNBContainer)
|
||||||
self.iview = ImplantsTreeView(self.viewsNBContainer)
|
#self.iview = ImplantsTreeView(self.viewsNBContainer)
|
||||||
#=======================================================================
|
#=======================================================================
|
||||||
# RC2
|
# RC2
|
||||||
self.iview.Show(False)
|
#self.iview.Show(False)
|
||||||
#=======================================================================
|
#=======================================================================
|
||||||
self.aview = APIView(self.viewsNBContainer)
|
self.aview = APIView(self.viewsNBContainer)
|
||||||
|
|
||||||
@@ -147,6 +147,7 @@ class CharacterEditor(wx.Frame):
|
|||||||
def restrict(self):
|
def restrict(self):
|
||||||
self.btnRename.Enable(False)
|
self.btnRename.Enable(False)
|
||||||
self.btnDelete.Enable(False)
|
self.btnDelete.Enable(False)
|
||||||
|
self.aview.stDisabledTip.Show(True)
|
||||||
self.aview.inputID.Enable(False)
|
self.aview.inputID.Enable(False)
|
||||||
self.aview.inputKey.Enable(False)
|
self.aview.inputKey.Enable(False)
|
||||||
self.aview.charChoice.Enable(False)
|
self.aview.charChoice.Enable(False)
|
||||||
@@ -158,6 +159,7 @@ class CharacterEditor(wx.Frame):
|
|||||||
def unrestrict(self):
|
def unrestrict(self):
|
||||||
self.btnRename.Enable(True)
|
self.btnRename.Enable(True)
|
||||||
self.btnDelete.Enable(True)
|
self.btnDelete.Enable(True)
|
||||||
|
self.aview.stDisabledTip.Show(False)
|
||||||
self.aview.inputID.Enable(True)
|
self.aview.inputID.Enable(True)
|
||||||
self.aview.inputKey.Enable(True)
|
self.aview.inputKey.Enable(True)
|
||||||
self.aview.btnFetchCharList.Enable(True)
|
self.aview.btnFetchCharList.Enable(True)
|
||||||
@@ -272,6 +274,7 @@ class CharacterEditor(wx.Frame):
|
|||||||
class SkillTreeView (wx.Panel):
|
class SkillTreeView (wx.Panel):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
|
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
|
||||||
|
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
|
||||||
|
|
||||||
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
@@ -531,12 +534,23 @@ class APIView (wx.Panel):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
|
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
|
||||||
self.Parent.Parent.Bind(GE.CHAR_CHANGED, self.charChanged)
|
self.Parent.Parent.Bind(GE.CHAR_CHANGED, self.charChanged)
|
||||||
|
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
|
||||||
|
|
||||||
self.apiUrlCreatePredefined = u"https://community.eveonline.com/support/api-key/CreatePredefined?accessMask=8"
|
self.apiUrlCreatePredefined = u"https://community.eveonline.com/support/api-key/CreatePredefined?accessMask=8"
|
||||||
self.apiUrlKeyList = u"https://community.eveonline.com/support/api-key/"
|
self.apiUrlKeyList = u"https://community.eveonline.com/support/api-key/"
|
||||||
|
|
||||||
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
hintSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||||
|
hintSizer.AddStretchSpacer()
|
||||||
|
self.stDisabledTip = wx.StaticText( self, wx.ID_ANY, u"You cannot add API Details for All 0 and All 5 characters.\n"
|
||||||
|
u"Please select another character or make a new one.", style=wx.ALIGN_CENTER )
|
||||||
|
self.stDisabledTip.Wrap( -1 )
|
||||||
|
hintSizer.Add( self.stDisabledTip, 0, wx.TOP | wx.BOTTOM, 10 )
|
||||||
|
hintSizer.AddStretchSpacer()
|
||||||
|
pmainSizer.Add(hintSizer, 0, wx.EXPAND, 5)
|
||||||
|
|
||||||
|
|
||||||
fgSizerInput = wx.FlexGridSizer(3, 2, 0, 0)
|
fgSizerInput = wx.FlexGridSizer(3, 2, 0, 0)
|
||||||
fgSizerInput.AddGrowableCol(1)
|
fgSizerInput.AddGrowableCol(1)
|
||||||
fgSizerInput.SetFlexibleDirection(wx.BOTH)
|
fgSizerInput.SetFlexibleDirection(wx.BOTH)
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ class Display(wx.ListCtrl):
|
|||||||
newText = col.getText(st)
|
newText = col.getText(st)
|
||||||
if newText is False:
|
if newText is False:
|
||||||
col.delayedText(st, self, colItem)
|
col.delayedText(st, self, colItem)
|
||||||
newText = ""
|
newText = u"\u21bb"
|
||||||
|
|
||||||
newImageId = col.getImageId(st)
|
newImageId = col.getImageId(st)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class DroneViewDrop(wx.PyDropTarget):
|
|||||||
|
|
||||||
def OnData(self, x, y, t):
|
def OnData(self, x, y, t):
|
||||||
if self.GetData():
|
if self.GetData():
|
||||||
self.dropFn(x, y, int(self.dropData.GetText()))
|
data = self.dropData.GetText().split(':')
|
||||||
|
self.dropFn(x, y, data)
|
||||||
return t
|
return t
|
||||||
|
|
||||||
class DroneView(d.Display):
|
class DroneView(d.Display):
|
||||||
@@ -72,7 +73,7 @@ class DroneView(d.Display):
|
|||||||
|
|
||||||
|
|
||||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||||
self.SetDropTarget(DroneViewDrop(self.mergeDrones))
|
self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
|
||||||
|
|
||||||
def OnLeaveWindow(self, event):
|
def OnLeaveWindow(self, event):
|
||||||
self.SetToolTip(None)
|
self.SetToolTip(None)
|
||||||
@@ -117,17 +118,27 @@ class DroneView(d.Display):
|
|||||||
row = event.GetIndex()
|
row = event.GetIndex()
|
||||||
if row != -1:
|
if row != -1:
|
||||||
data = wx.PyTextDataObject()
|
data = wx.PyTextDataObject()
|
||||||
data.SetText(str(self.GetItemData(row)))
|
data.SetText("drone:"+str(row))
|
||||||
|
|
||||||
dropSource = wx.DropSource(self)
|
dropSource = wx.DropSource(self)
|
||||||
dropSource.SetData(data)
|
dropSource.SetData(data)
|
||||||
res = dropSource.DoDragDrop()
|
res = dropSource.DoDragDrop()
|
||||||
|
|
||||||
def mergeDrones(self, x, y, itemID):
|
def handleDragDrop(self, x, y, data):
|
||||||
srcRow = self.FindItemData(-1,itemID)
|
'''
|
||||||
dstRow, _ = self.HitTest((x, y))
|
Handles dragging of items from various pyfa displays which support it
|
||||||
if srcRow != -1 and dstRow != -1:
|
|
||||||
self._merge(srcRow, dstRow)
|
data is list with two indices:
|
||||||
|
data[0] is hard-coded str of originating source
|
||||||
|
data[1] is typeID or index of data we want to manipulate
|
||||||
|
'''
|
||||||
|
if data[0] == "drone": # we want to merge drones
|
||||||
|
srcRow = int(data[1])
|
||||||
|
dstRow, _ = self.HitTest((x, y))
|
||||||
|
if srcRow != -1 and dstRow != -1:
|
||||||
|
self._merge(srcRow, dstRow)
|
||||||
|
elif data[0] == "market":
|
||||||
|
wx.PostEvent(self.mainFrame, mb.ItemSelected(itemID=int(data[1])))
|
||||||
|
|
||||||
def _merge(self, src, dst):
|
def _merge(self, src, dst):
|
||||||
sFit = service.Fit.getInstance()
|
sFit = service.Fit.getInstance()
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import gui.globalEvents as GE
|
|||||||
from gui import bitmapLoader
|
from gui import bitmapLoader
|
||||||
from gui.mainMenuBar import MainMenuBar
|
from gui.mainMenuBar import MainMenuBar
|
||||||
from gui.additionsPane import AdditionsPane
|
from gui.additionsPane import AdditionsPane
|
||||||
from gui.marketBrowser import MarketBrowser
|
from gui.marketBrowser import MarketBrowser, ItemSelected
|
||||||
from gui.multiSwitch import MultiSwitch
|
from gui.multiSwitch import MultiSwitch
|
||||||
from gui.statsPane import StatsPane
|
from gui.statsPane import StatsPane
|
||||||
from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected
|
from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected
|
||||||
@@ -146,6 +146,7 @@ class MainFrame(wx.Frame):
|
|||||||
|
|
||||||
self.marketBrowser = MarketBrowser(self.notebookBrowsers)
|
self.marketBrowser = MarketBrowser(self.notebookBrowsers)
|
||||||
self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False)
|
self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False)
|
||||||
|
self.marketBrowser.splitter.SetSashPosition(self.marketHeight)
|
||||||
|
|
||||||
self.shipBrowser = ShipBrowser(self.notebookBrowsers)
|
self.shipBrowser = ShipBrowser(self.notebookBrowsers)
|
||||||
self.notebookBrowsers.AddPage(self.shipBrowser, "Ships", tabImage = shipBrowserImg, showClose = False)
|
self.notebookBrowsers.AddPage(self.shipBrowser, "Ships", tabImage = shipBrowserImg, showClose = False)
|
||||||
@@ -160,7 +161,7 @@ class MainFrame(wx.Frame):
|
|||||||
|
|
||||||
self.splitter.SplitVertically(self.notebookBrowsers, self.FitviewAdditionsPanel)
|
self.splitter.SplitVertically(self.notebookBrowsers, self.FitviewAdditionsPanel)
|
||||||
self.splitter.SetMinimumPaneSize(204)
|
self.splitter.SetMinimumPaneSize(204)
|
||||||
self.splitter.SetSashPosition(300)
|
self.splitter.SetSashPosition(self.browserWidth)
|
||||||
|
|
||||||
cstatsSizer = wx.BoxSizer(wx.VERTICAL)
|
cstatsSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ class MainFrame(wx.Frame):
|
|||||||
|
|
||||||
|
|
||||||
def LoadMainFrameAttribs(self):
|
def LoadMainFrameAttribs(self):
|
||||||
mainFrameDefaultAttribs = {"wnd_width": 1000, "wnd_height": 680, "wnd_maximized": False}
|
mainFrameDefaultAttribs = {"wnd_width": 1000, "wnd_height": 680, "wnd_maximized": False, "browser_width": 300, "market_height": 0}
|
||||||
self.mainFrameAttribs = service.SettingsProvider.getInstance().getSettings("pyfaMainWindowAttribs", mainFrameDefaultAttribs)
|
self.mainFrameAttribs = service.SettingsProvider.getInstance().getSettings("pyfaMainWindowAttribs", mainFrameDefaultAttribs)
|
||||||
|
|
||||||
if self.mainFrameAttribs["wnd_maximized"]:
|
if self.mainFrameAttribs["wnd_maximized"]:
|
||||||
@@ -241,6 +242,9 @@ class MainFrame(wx.Frame):
|
|||||||
self.SetSize((width, height))
|
self.SetSize((width, height))
|
||||||
self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"]))
|
self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"]))
|
||||||
|
|
||||||
|
self.browserWidth = self.mainFrameAttribs["browser_width"]
|
||||||
|
self.marketHeight = self.mainFrameAttribs["market_height"]
|
||||||
|
|
||||||
def UpdateMainFrameAttribs(self):
|
def UpdateMainFrameAttribs(self):
|
||||||
if self.IsIconized():
|
if self.IsIconized():
|
||||||
return
|
return
|
||||||
@@ -250,6 +254,9 @@ class MainFrame(wx.Frame):
|
|||||||
self.mainFrameAttribs["wnd_height"] = height
|
self.mainFrameAttribs["wnd_height"] = height
|
||||||
self.mainFrameAttribs["wnd_maximized"] = self.IsMaximized()
|
self.mainFrameAttribs["wnd_maximized"] = self.IsMaximized()
|
||||||
|
|
||||||
|
self.mainFrameAttribs["browser_width"] = self.notebookBrowsers.GetSize()[0]
|
||||||
|
self.mainFrameAttribs["market_height"] = self.marketBrowser.marketView.GetSize()[1]
|
||||||
|
|
||||||
def SetActiveStatsWindow(self, wnd):
|
def SetActiveStatsWindow(self, wnd):
|
||||||
self.activeStatsWnd = wnd
|
self.activeStatsWnd = wnd
|
||||||
|
|
||||||
@@ -423,12 +430,6 @@ class MainFrame(wx.Frame):
|
|||||||
ctabnext = wx.NewId()
|
ctabnext = wx.NewId()
|
||||||
ctabprev = wx.NewId()
|
ctabprev = wx.NewId()
|
||||||
|
|
||||||
self.additionstab1 = wx.NewId()
|
|
||||||
self.additionstab2 = wx.NewId()
|
|
||||||
self.additionstab3 = wx.NewId()
|
|
||||||
self.additionstab4 = wx.NewId()
|
|
||||||
self.additionstab5 = wx.NewId()
|
|
||||||
|
|
||||||
# Close Page
|
# Close Page
|
||||||
self.Bind(wx.EVT_MENU, self.CloseCurrentPage, id=self.closePageId)
|
self.Bind(wx.EVT_MENU, self.CloseCurrentPage, id=self.closePageId)
|
||||||
self.Bind(wx.EVT_MENU, self.HAddPage, id = self.addPageId)
|
self.Bind(wx.EVT_MENU, self.HAddPage, id = self.addPageId)
|
||||||
@@ -437,12 +438,6 @@ class MainFrame(wx.Frame):
|
|||||||
self.Bind(wx.EVT_MENU, self.CTabNext, id = ctabnext)
|
self.Bind(wx.EVT_MENU, self.CTabNext, id = ctabnext)
|
||||||
self.Bind(wx.EVT_MENU, self.CTabPrev, id = ctabprev)
|
self.Bind(wx.EVT_MENU, self.CTabPrev, id = ctabprev)
|
||||||
|
|
||||||
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id = self.additionstab1)
|
|
||||||
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id = self.additionstab2)
|
|
||||||
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id = self.additionstab3)
|
|
||||||
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id = self.additionstab4)
|
|
||||||
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id = self.additionstab5)
|
|
||||||
|
|
||||||
actb = [(wx.ACCEL_CTRL, ord('T'), self.addPageId),
|
actb = [(wx.ACCEL_CTRL, ord('T'), self.addPageId),
|
||||||
(wx.ACCEL_CMD, ord('T'), self.addPageId),
|
(wx.ACCEL_CMD, ord('T'), self.addPageId),
|
||||||
|
|
||||||
@@ -462,41 +457,43 @@ class MainFrame(wx.Frame):
|
|||||||
(wx.ACCEL_CMD, wx.WXK_TAB, ctabnext),
|
(wx.ACCEL_CMD, wx.WXK_TAB, ctabnext),
|
||||||
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, wx.WXK_TAB, ctabprev),
|
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, wx.WXK_TAB, ctabprev),
|
||||||
|
|
||||||
# Ctrl+age(Up/Down)
|
# Ctrl+Page(Up/Down)
|
||||||
(wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext),
|
(wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext),
|
||||||
(wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev),
|
(wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev),
|
||||||
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
||||||
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev),
|
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev)
|
||||||
|
|
||||||
(wx.ACCEL_CTRL, ord('1'), self.additionstab1),
|
|
||||||
(wx.ACCEL_CTRL, ord('2'), self.additionstab2),
|
|
||||||
(wx.ACCEL_CTRL, ord('3'), self.additionstab3),
|
|
||||||
(wx.ACCEL_CTRL, ord('4'), self.additionstab4),
|
|
||||||
(wx.ACCEL_CTRL, ord('5'), self.additionstab5),
|
|
||||||
(wx.ACCEL_CMD, ord('1'), self.additionstab1),
|
|
||||||
(wx.ACCEL_CMD, ord('2'), self.additionstab2),
|
|
||||||
(wx.ACCEL_CMD, ord('3'), self.additionstab3),
|
|
||||||
(wx.ACCEL_CMD, ord('4'), self.additionstab4),
|
|
||||||
(wx.ACCEL_CMD, ord('5'), self.additionstab5)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Ctrl/Cmd+# for addition pane selection
|
||||||
|
self.additionsSelect = []
|
||||||
|
for i in range(0, self.additionsPane.notebook.GetPageCount()):
|
||||||
|
self.additionsSelect.append(wx.NewId())
|
||||||
|
self.Bind(wx.EVT_MENU, self.AdditionsTabSelect, id=self.additionsSelect[i])
|
||||||
|
actb.append((wx.ACCEL_CMD, i+49, self.additionsSelect[i]))
|
||||||
|
actb.append((wx.ACCEL_CTRL, i+49, self.additionsSelect[i]))
|
||||||
|
|
||||||
|
# Alt+1-9 for market item selection
|
||||||
|
self.itemSelect = []
|
||||||
|
for i in range(0, 9):
|
||||||
|
self.itemSelect.append(wx.NewId())
|
||||||
|
self.Bind(wx.EVT_MENU, self.ItemSelect, id = self.itemSelect[i])
|
||||||
|
actb.append((wx.ACCEL_ALT, i + 49, self.itemSelect[i]))
|
||||||
|
|
||||||
atable = wx.AcceleratorTable(actb)
|
atable = wx.AcceleratorTable(actb)
|
||||||
self.SetAcceleratorTable(atable)
|
self.SetAcceleratorTable(atable)
|
||||||
|
|
||||||
def AdditionsTabSelect(self, event):
|
def AdditionsTabSelect(self, event):
|
||||||
selTab = None
|
selTab = self.additionsSelect.index(event.GetId())
|
||||||
if event.GetId() == self.additionstab1:
|
|
||||||
selTab = 0
|
if selTab <= self.additionsPane.notebook.GetPageCount():
|
||||||
if event.GetId() == self.additionstab2:
|
|
||||||
selTab = 1
|
|
||||||
if event.GetId() == self.additionstab3:
|
|
||||||
selTab = 2
|
|
||||||
if event.GetId() == self.additionstab4:
|
|
||||||
selTab = 3
|
|
||||||
if event.GetId() == self.additionstab5:
|
|
||||||
selTab = 4
|
|
||||||
if selTab is not None:
|
|
||||||
self.additionsPane.notebook.SetSelection(selTab)
|
self.additionsPane.notebook.SetSelection(selTab)
|
||||||
|
|
||||||
|
def ItemSelect(self, event):
|
||||||
|
selItem = self.itemSelect.index(event.GetId())
|
||||||
|
|
||||||
|
if selItem < len(self.marketBrowser.itemView.active):
|
||||||
|
wx.PostEvent(self, ItemSelected(itemID=self.marketBrowser.itemView.active[selItem].ID))
|
||||||
|
|
||||||
def CTabNext(self, event):
|
def CTabNext(self, event):
|
||||||
self.fitMultiSwitch.NextPage()
|
self.fitMultiSwitch.NextPage()
|
||||||
|
|
||||||
|
|||||||
@@ -438,6 +438,11 @@ class ItemView(d.Display):
|
|||||||
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
||||||
# Re-sort stuff
|
# Re-sort stuff
|
||||||
items.sort(key=self.itemSort)
|
items.sort(key=self.itemSort)
|
||||||
|
|
||||||
|
for i, item in enumerate(items[:9]):
|
||||||
|
# set shortcut info for first 9 modules
|
||||||
|
item.marketShortcut = i+1
|
||||||
|
|
||||||
d.Display.refresh(self, items)
|
d.Display.refresh(self, items)
|
||||||
|
|
||||||
def makeReverseMetaMap(self):
|
def makeReverseMetaMap(self):
|
||||||
|
|||||||
@@ -342,7 +342,6 @@ class ResistsEditorDlg(wx.Dialog):
|
|||||||
self.patternChanged()
|
self.patternChanged()
|
||||||
|
|
||||||
def showInput(self, bool):
|
def showInput(self, bool):
|
||||||
print self.namePicker.IsShown(), bool
|
|
||||||
if bool and not self.namePicker.IsShown():
|
if bool and not self.namePicker.IsShown():
|
||||||
self.ccResists.Hide()
|
self.ccResists.Hide()
|
||||||
self.namePicker.Show()
|
self.namePicker.Show()
|
||||||
|
|||||||
@@ -939,11 +939,12 @@ class ShipBrowser(wx.Panel):
|
|||||||
class PFStaticText(wx.Panel):
|
class PFStaticText(wx.Panel):
|
||||||
def __init__(self, parent, label=wx.EmptyString):
|
def __init__(self, parent, label=wx.EmptyString):
|
||||||
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size = parent.GetSize())
|
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size = parent.GetSize())
|
||||||
|
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
|
||||||
|
|
||||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
text = wx.StaticText( self, wx.ID_ANY, label, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE )
|
text = wx.StaticText( self, wx.ID_ANY, label, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE )
|
||||||
text.Wrap( -1 )
|
text.Wrap( -1 )
|
||||||
mainSizer.Add( text, 1, wx.EXPAND|wx.TOP, 10 )
|
mainSizer.Add( text, 1, wx.ALL, 10 )
|
||||||
self.SetSizer(mainSizer)
|
self.SetSizer(mainSizer)
|
||||||
self.Layout()
|
self.Layout()
|
||||||
def GetType(self):
|
def GetType(self):
|
||||||
@@ -1426,9 +1427,6 @@ class FitItem(SFItem.SFBrowserItem):
|
|||||||
|
|
||||||
self.deleted = False
|
self.deleted = False
|
||||||
|
|
||||||
# @todo: replace all getActiveFit() in class with this variable and test
|
|
||||||
self.activeFit = self.mainFrame.getActiveFit()
|
|
||||||
|
|
||||||
if shipID:
|
if shipID:
|
||||||
self.shipBmp = bitmapLoader.getBitmap(str(shipID),"ships")
|
self.shipBmp = bitmapLoader.getBitmap(str(shipID),"ships")
|
||||||
|
|
||||||
@@ -1441,19 +1439,6 @@ class FitItem(SFItem.SFBrowserItem):
|
|||||||
# see GH issue #62
|
# see GH issue #62
|
||||||
if self.fitBooster is None: self.fitBooster = False
|
if self.fitBooster is None: self.fitBooster = False
|
||||||
|
|
||||||
# access these by index based on toggle for booster fit
|
|
||||||
|
|
||||||
self.fitMenu = wx.Menu()
|
|
||||||
self.toggleItem = self.fitMenu.Append(-1, "Booster Fit", kind=wx.ITEM_CHECK)
|
|
||||||
self.fitMenu.Check(self.toggleItem.GetId(), self.fitBooster)
|
|
||||||
self.Bind(wx.EVT_MENU, self.OnPopupItemSelected, self.toggleItem)
|
|
||||||
|
|
||||||
if self.activeFit:
|
|
||||||
# If there is an active fit, get menu for setting individual boosters
|
|
||||||
self.fitMenu.AppendSeparator()
|
|
||||||
boosterMenu = self.mainFrame.additionsPane.gangPage.FitDNDPopupMenu
|
|
||||||
self.fitMenu.AppendMenu(wx.ID_ANY, 'Set Booster', boosterMenu)
|
|
||||||
|
|
||||||
self.boosterBmp = bitmapLoader.getBitmap("fleet_fc_small", "icons")
|
self.boosterBmp = bitmapLoader.getBitmap("fleet_fc_small", "icons")
|
||||||
self.copyBmp = bitmapLoader.getBitmap("fit_add_small", "icons")
|
self.copyBmp = bitmapLoader.getBitmap("fit_add_small", "icons")
|
||||||
self.renameBmp = bitmapLoader.getBitmap("fit_rename_small", "icons")
|
self.renameBmp = bitmapLoader.getBitmap("fit_rename_small", "icons")
|
||||||
@@ -1530,14 +1515,12 @@ class FitItem(SFItem.SFBrowserItem):
|
|||||||
|
|
||||||
self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)
|
self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)
|
||||||
|
|
||||||
def OnPopupItemSelected(self, event):
|
def OnToggleBooster(self, event):
|
||||||
''' Fires when fit menu item is selected '''
|
|
||||||
# currently only have one menu option (toggle booster)
|
|
||||||
sFit = service.Fit.getInstance()
|
sFit = service.Fit.getInstance()
|
||||||
sFit.toggleBoostFit(self.fitID)
|
sFit.toggleBoostFit(self.fitID)
|
||||||
self.fitBooster = not self.fitBooster
|
self.fitBooster = not self.fitBooster
|
||||||
self.boosterBtn.Show(self.fitBooster)
|
self.boosterBtn.Show(self.fitBooster)
|
||||||
self.fitMenu.Check(self.toggleItem.GetId(), self.fitBooster)
|
self.Refresh()
|
||||||
wx.PostEvent(self.mainFrame, BoosterListUpdated())
|
wx.PostEvent(self.mainFrame, BoosterListUpdated())
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
@@ -1545,9 +1528,23 @@ class FitItem(SFItem.SFBrowserItem):
|
|||||||
''' Handles context menu for fit. Dragging is handled by MouseLeftUp() '''
|
''' Handles context menu for fit. Dragging is handled by MouseLeftUp() '''
|
||||||
pos = wx.GetMousePosition()
|
pos = wx.GetMousePosition()
|
||||||
pos = self.ScreenToClient(pos)
|
pos = self.ScreenToClient(pos)
|
||||||
|
|
||||||
# Even though we may not select a booster, automatically set this so that the fleet pane knows which fit we're applying
|
# Even though we may not select a booster, automatically set this so that the fleet pane knows which fit we're applying
|
||||||
self.mainFrame.additionsPane.gangPage.draggedFitID = self.fitID
|
self.mainFrame.additionsPane.gangPage.draggedFitID = self.fitID
|
||||||
self.PopupMenu(self.fitMenu, pos)
|
|
||||||
|
menu = wx.Menu()
|
||||||
|
toggleItem = menu.Append(wx.ID_ANY, "Booster Fit", kind=wx.ITEM_CHECK)
|
||||||
|
menu.Check(toggleItem.GetId(), self.fitBooster)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_MENU, self.OnToggleBooster, toggleItem)
|
||||||
|
|
||||||
|
if self.mainFrame.getActiveFit():
|
||||||
|
# If there is an active fit, get menu for setting individual boosters
|
||||||
|
menu.AppendSeparator()
|
||||||
|
boosterMenu = self.mainFrame.additionsPane.gangPage.FitDNDPopupMenu
|
||||||
|
menu.AppendSubMenu(boosterMenu, 'Set Booster')
|
||||||
|
|
||||||
|
self.PopupMenu(menu, pos)
|
||||||
|
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
|||||||
BIN
icons/refresh_small.png
Normal file
BIN
icons/refresh_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 506 B |
258
scripts/icons_update.py
Normal file
258
scripts/icons_update.py
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script updates only market/item icons.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='This script updates module icons for pyfa')
|
||||||
|
parser.add_argument('-i', '--icons', required=True, type=str, help='path to unpacked Icons folder from CCP\'s image export')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
db_path = os.path.abspath(os.path.join(script_dir, '..', 'staticdata', 'eve.db'))
|
||||||
|
icons_dir = os.path.abspath(os.path.join(script_dir, '..', 'staticdata', 'icons'))
|
||||||
|
export_dir = os.path.abspath(os.path.expanduser(os.path.join(args.icons, 'items')))
|
||||||
|
|
||||||
|
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
ICON_SIZE = (16, 16)
|
||||||
|
|
||||||
|
ITEM_CATEGORIES = (
|
||||||
|
6, # Ship
|
||||||
|
7, # Module
|
||||||
|
8, # Charge
|
||||||
|
16, # Skill
|
||||||
|
18, # Drone
|
||||||
|
20, # Implant
|
||||||
|
32 # Subsystem
|
||||||
|
)
|
||||||
|
MARKET_ROOTS = {
|
||||||
|
9, # Modules
|
||||||
|
1111, # Rigs
|
||||||
|
157, # Drones
|
||||||
|
11, # Ammo
|
||||||
|
1112, # Subsystems
|
||||||
|
24, # Implants & Boosters
|
||||||
|
404 # Deployables
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add children to market group list
|
||||||
|
# {parent: {children}}
|
||||||
|
mkt_tree = {}
|
||||||
|
for row in cursor.execute('select marketGroupID, parentGroupID from invmarketgroups'):
|
||||||
|
parent = row[1]
|
||||||
|
# We have all the root groups in the set we need anyway
|
||||||
|
if not parent:
|
||||||
|
continue
|
||||||
|
child = row[0]
|
||||||
|
children = mkt_tree.setdefault(parent, set())
|
||||||
|
children.add(child)
|
||||||
|
|
||||||
|
# Traverse the tree we just composed to add all children for all needed roots
|
||||||
|
def get_children(parent):
|
||||||
|
children = set()
|
||||||
|
for child in mkt_tree.get(parent, ()):
|
||||||
|
children.add(child)
|
||||||
|
children.update(get_children(child))
|
||||||
|
return children
|
||||||
|
|
||||||
|
|
||||||
|
market_groups = set()
|
||||||
|
for root in MARKET_ROOTS:
|
||||||
|
market_groups.add(root)
|
||||||
|
market_groups.update(get_children(root))
|
||||||
|
|
||||||
|
|
||||||
|
query_items = 'select distinct i.iconFile from icons as i inner join invtypes as it on it.iconID = i.iconID inner join invgroups as ig on it.groupID = ig.groupID where ig.categoryID in ({})'.format(', '.join(str(i) for i in ITEM_CATEGORIES))
|
||||||
|
query_groups = 'select distinct i.iconFile from icons as i inner join invgroups as ig on ig.iconID = i.iconID where ig.categoryID in ({})'.format(', '.join(str(i) for i in ITEM_CATEGORIES))
|
||||||
|
query_cats = 'select distinct i.iconFile from icons as i inner join invcategories as ic on ic.iconID = i.iconID where ic.categoryID in ({})'.format(', '.join(str(i) for i in ITEM_CATEGORIES))
|
||||||
|
query_market = 'select distinct i.iconFile from icons as i inner join invmarketgroups as img on img.iconID = i.iconID where img.marketGroupID in ({})'.format(', '.join(str(i) for i in market_groups))
|
||||||
|
query_attrib = 'select distinct i.iconFile from icons as i inner join dgmattribs as da on da.iconID = i.iconID'
|
||||||
|
|
||||||
|
|
||||||
|
needed = set()
|
||||||
|
existing = set()
|
||||||
|
export = {}
|
||||||
|
|
||||||
|
|
||||||
|
def strip_path(fname):
|
||||||
|
"""
|
||||||
|
Here we extract 'core' of icon name. Path and
|
||||||
|
extension are sometimes specified in database
|
||||||
|
but we don't need them.
|
||||||
|
"""
|
||||||
|
# Path before the icon file name
|
||||||
|
fname = fname.split('/')[-1]
|
||||||
|
# Extension
|
||||||
|
fname = fname.rsplit('.', 1)[0]
|
||||||
|
return fname
|
||||||
|
|
||||||
|
|
||||||
|
def unzero(fname):
|
||||||
|
"""
|
||||||
|
Get rid of leading zeros in triplet. They are often specified in DB
|
||||||
|
but almost never in actual files.
|
||||||
|
"""
|
||||||
|
m = re.match(r'^(?P<prefix>[^_\.]+)_((?P<size>\d+)_)?(?P<suffix>[^_\.]+)(?P<tail>\..*)?$', fname)
|
||||||
|
if m:
|
||||||
|
prefix = m.group('prefix')
|
||||||
|
size = m.group('size')
|
||||||
|
suffix = m.group('suffix')
|
||||||
|
tail = m.group('tail')
|
||||||
|
try:
|
||||||
|
prefix = int(prefix)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
size = int(size)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
suffix = int(suffix)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
if size is None:
|
||||||
|
fname = '{}_{}{}'.format(prefix, suffix, tail)
|
||||||
|
else:
|
||||||
|
fname = '{}_{}_{}{}'.format(prefix, size, suffix, tail)
|
||||||
|
return fname
|
||||||
|
else:
|
||||||
|
return fname
|
||||||
|
|
||||||
|
for query in (query_items, query_groups, query_cats, query_market, query_attrib):
|
||||||
|
for row in cursor.execute(query):
|
||||||
|
fname = row[0]
|
||||||
|
if not fname:
|
||||||
|
continue
|
||||||
|
fname = strip_path(fname)
|
||||||
|
needed.add(fname)
|
||||||
|
|
||||||
|
for fname in os.listdir(icons_dir):
|
||||||
|
if not os.path.isfile(os.path.join(icons_dir, fname)):
|
||||||
|
continue
|
||||||
|
if not fname.startswith('icon') or not fname.endswith('.png'):
|
||||||
|
continue
|
||||||
|
fname = strip_path(fname)
|
||||||
|
# Get rid of "icon" prefix as well
|
||||||
|
fname = re.sub('^icon', '', fname)
|
||||||
|
existing.add(fname)
|
||||||
|
|
||||||
|
|
||||||
|
for fname in os.listdir(export_dir):
|
||||||
|
if not os.path.isfile(os.path.join(export_dir, fname)):
|
||||||
|
continue
|
||||||
|
stripped = strip_path(fname)
|
||||||
|
stripped = unzero(stripped)
|
||||||
|
# Icons in export often specify size in their name, but references often use
|
||||||
|
# convention without size specification
|
||||||
|
sizeless = re.sub('^(?P<prefix>[^_]+)_(?P<size>\d+)_(?P<suffix>[^_]+)$', r'\1_\3', stripped)
|
||||||
|
# Often items referred to with 01_01 format,
|
||||||
|
fnames = export.setdefault(stripped, set())
|
||||||
|
fnames.add(fname)
|
||||||
|
fnames = export.setdefault(sizeless, set())
|
||||||
|
fnames.add(fname)
|
||||||
|
|
||||||
|
|
||||||
|
def crop_image(img):
|
||||||
|
w, h = img.size
|
||||||
|
if h == w:
|
||||||
|
return img
|
||||||
|
normal = min(h, w)
|
||||||
|
diff_w = w - normal
|
||||||
|
diff_h = h - normal
|
||||||
|
crop_top = diff_h // 2
|
||||||
|
crop_bot = diff_h // 2 + diff_h % 2
|
||||||
|
crop_left = diff_w // 2
|
||||||
|
crop_right = diff_w // 2 + diff_w % 2
|
||||||
|
box = (crop_left, crop_top, w - crop_right, h - crop_bot)
|
||||||
|
return img.crop(box)
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon_file(request):
|
||||||
|
"""
|
||||||
|
Get the iconFile field value and find proper
|
||||||
|
icon for it. Return as PIL image object down-
|
||||||
|
scaled for use in pyfa.
|
||||||
|
"""
|
||||||
|
rq = strip_path(request)
|
||||||
|
rq = unzero(rq)
|
||||||
|
try:
|
||||||
|
fnames = export[rq]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
# {(h, w): source full path}
|
||||||
|
sizes = {}
|
||||||
|
for fname in fnames:
|
||||||
|
fullpath = os.path.join(export_dir, fname)
|
||||||
|
img = Image.open(fullpath)
|
||||||
|
sizes[img.size] = fullpath
|
||||||
|
# Try to return image which is already in necessary format
|
||||||
|
try:
|
||||||
|
fullpath = sizes[ICON_SIZE]
|
||||||
|
# Otherwise, convert biggest image
|
||||||
|
except KeyError:
|
||||||
|
fullpath = sizes[max(sizes)]
|
||||||
|
img = Image.open(fullpath)
|
||||||
|
img = crop_image(img)
|
||||||
|
img.thumbnail(ICON_SIZE, Image.ANTIALIAS)
|
||||||
|
else:
|
||||||
|
img = Image.open(fullpath)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
toremove = existing.difference(needed)
|
||||||
|
toupdate = existing.intersection(needed)
|
||||||
|
toadd = needed.difference(existing)
|
||||||
|
|
||||||
|
|
||||||
|
if toremove:
|
||||||
|
print('Some icons are not used and will be removed:')
|
||||||
|
for fname in sorted(toremove):
|
||||||
|
fullname = 'icon{}.png'.format(fname)
|
||||||
|
print(' {}'.format(fullname))
|
||||||
|
fullpath = os.path.join(icons_dir, fullname)
|
||||||
|
os.remove(fullpath)
|
||||||
|
|
||||||
|
if toupdate:
|
||||||
|
print('Updating {} icons...'.format(len(toupdate)))
|
||||||
|
missing = set()
|
||||||
|
for fname in sorted(toupdate):
|
||||||
|
icon = get_icon_file(fname)
|
||||||
|
if icon is None:
|
||||||
|
missing.add(fname)
|
||||||
|
continue
|
||||||
|
fullname = 'icon{}.png'.format(fname)
|
||||||
|
fullpath = os.path.join(icons_dir, fullname)
|
||||||
|
icon.save(fullpath, 'PNG')
|
||||||
|
if missing:
|
||||||
|
print(' {} icons are missing in export:'.format(len(missing)))
|
||||||
|
for fname in sorted(missing):
|
||||||
|
print(' {}'.format(fname))
|
||||||
|
|
||||||
|
if toadd:
|
||||||
|
print('Adding {} icons...'.format(len(toadd)))
|
||||||
|
missing = set()
|
||||||
|
for fname in sorted(toadd):
|
||||||
|
icon = get_icon_file(fname)
|
||||||
|
if icon is None:
|
||||||
|
missing.add(fname)
|
||||||
|
continue
|
||||||
|
fullname = 'icon{}.png'.format(fname)
|
||||||
|
fullpath = os.path.join(icons_dir, fullname)
|
||||||
|
icon.save(fullpath, 'PNG')
|
||||||
|
if missing:
|
||||||
|
print(' {} icons are missing in export:'.format(len(missing)))
|
||||||
|
for fname in sorted(missing):
|
||||||
|
print(' {}'.format(fname))
|
||||||
@@ -1,202 +1,212 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#======================================================================
|
#======================================================================
|
||||||
# Copyright (C) 2012 Diego Duclos
|
# Copyright (C) 2012 Diego Duclos
|
||||||
#
|
#
|
||||||
# This file is part of eos.
|
# This file is part of eos.
|
||||||
#
|
#
|
||||||
# eos is free software: you can redistribute it and/or modify
|
# eos is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Lesser General Public License as
|
# it under the terms of the GNU Lesser General Public License as
|
||||||
# published by the Free Software Foundation, either version 3 of
|
# published by the Free Software Foundation, either version 3 of
|
||||||
# the License, or (at your option) any later version.
|
# the License, or (at your option) any later version.
|
||||||
#
|
#
|
||||||
# eos is distributed in the hope that it will be useful,
|
# eos is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU Lesser General Public License for more details.
|
# GNU Lesser General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#======================================================================
|
#======================================================================
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Add eos root path to sys.path so we can import ourselves
|
# Add eos root path to sys.path so we can import ourselves
|
||||||
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||||
sys.path.append(os.path.realpath(os.path.join(path, "..")))
|
sys.path.append(os.path.realpath(os.path.join(path, "..")))
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
def main(db, json_path):
|
def main(db, json_path):
|
||||||
|
|
||||||
jsonPath = os.path.expanduser(json_path)
|
jsonPath = os.path.expanduser(json_path)
|
||||||
|
|
||||||
# Import eos.config first and change it
|
# Import eos.config first and change it
|
||||||
import eos.config
|
import eos.config
|
||||||
eos.config.gamedata_connectionstring = db
|
eos.config.gamedata_connectionstring = db
|
||||||
eos.config.debug = False
|
eos.config.debug = False
|
||||||
|
|
||||||
# Now thats done, we can import the eos modules using the config
|
# Now thats done, we can import the eos modules using the config
|
||||||
import eos.db
|
import eos.db
|
||||||
import eos.gamedata
|
import eos.gamedata
|
||||||
|
|
||||||
# Create the database tables
|
# Create the database tables
|
||||||
eos.db.gamedata_meta.create_all()
|
eos.db.gamedata_meta.create_all()
|
||||||
|
|
||||||
# Config dict
|
# Config dict
|
||||||
tables = {
|
tables = {
|
||||||
"dgmattribs": eos.gamedata.AttributeInfo,
|
"dgmattribs": eos.gamedata.AttributeInfo,
|
||||||
"dgmeffects": eos.gamedata.EffectInfo,
|
"dgmeffects": eos.gamedata.EffectInfo,
|
||||||
"dgmtypeattribs": eos.gamedata.Attribute,
|
"dgmtypeattribs": eos.gamedata.Attribute,
|
||||||
"dgmtypeeffects": eos.gamedata.Effect,
|
"dgmtypeeffects": eos.gamedata.Effect,
|
||||||
"dgmunits": eos.gamedata.Unit,
|
"dgmunits": eos.gamedata.Unit,
|
||||||
"icons": eos.gamedata.Icon,
|
"icons": eos.gamedata.Icon,
|
||||||
"invcategories": eos.gamedata.Category,
|
"evecategories": eos.gamedata.Category,
|
||||||
"invgroups": eos.gamedata.Group,
|
"evegroups": eos.gamedata.Group,
|
||||||
"invmetagroups": eos.gamedata.MetaGroup,
|
"invmetagroups": eos.gamedata.MetaGroup,
|
||||||
"invmetatypes": eos.gamedata.MetaType,
|
"invmetatypes": eos.gamedata.MetaType,
|
||||||
"invtypes": eos.gamedata.Item,
|
"evetypes": eos.gamedata.Item,
|
||||||
"phbtraits": eos.gamedata.Traits,
|
"phbtraits": eos.gamedata.Traits,
|
||||||
"phbmetadata": eos.gamedata.MetaData,
|
"phbmetadata": eos.gamedata.MetaData,
|
||||||
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldMapping = {
|
fieldMapping = {
|
||||||
"dgmattribs": {
|
"dgmattribs": {
|
||||||
"displayName_en-us": "displayName"
|
"displayName_en-us": "displayName"
|
||||||
},
|
},
|
||||||
"dgmeffects": {
|
"dgmeffects": {
|
||||||
"displayName_en-us": "displayName",
|
"displayName_en-us": "displayName",
|
||||||
"description_en-us": "description"
|
"description_en-us": "description"
|
||||||
},
|
},
|
||||||
"dgmunits": {
|
"dgmunits": {
|
||||||
"displayName_en-us": "displayName"
|
"displayName_en-us": "displayName"
|
||||||
},
|
},
|
||||||
#icons???
|
#icons???
|
||||||
"invcategories": {
|
"evecategories": {
|
||||||
"categoryName_en-us": "categoryName"
|
"categoryName_en-us": "categoryName"
|
||||||
},
|
},
|
||||||
"invgroups": {
|
"evegroups": {
|
||||||
"groupName_en-us": "groupName"
|
"groupName_en-us": "groupName"
|
||||||
},
|
},
|
||||||
"invmetagroups": {
|
"invmetagroups": {
|
||||||
"metaGroupName_en-us": "metaGroupName"
|
"metaGroupName_en-us": "metaGroupName"
|
||||||
},
|
},
|
||||||
"invtypes": {
|
"evetypes": {
|
||||||
"typeName_en-us": "typeName",
|
"typeName_en-us": "typeName",
|
||||||
"description_en-us": "description"
|
"description_en-us": "description"
|
||||||
},
|
},
|
||||||
#phbtraits???
|
#phbtraits???
|
||||||
"mapbulk_marketGroups": {
|
"mapbulk_marketGroups": {
|
||||||
"marketGroupName_en-us": "marketGroupName",
|
"marketGroupName_en-us": "marketGroupName",
|
||||||
"description_en-us": "description"
|
"description_en-us": "description"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def convertIcons(data):
|
rowsInValues = (
|
||||||
new = []
|
"evetypes",
|
||||||
for k, v in data.items():
|
"evegroups",
|
||||||
v["iconID"] = k
|
"evecategories"
|
||||||
new.append(v)
|
)
|
||||||
return new
|
|
||||||
|
def convertIcons(data):
|
||||||
def convertTraits(data):
|
new = []
|
||||||
|
for k, v in data.items():
|
||||||
def convertSection(sectionData):
|
v["iconID"] = k
|
||||||
sectionLines = []
|
new.append(v)
|
||||||
headerText = u"<b>{}</b>".format(sectionData["header"])
|
return new
|
||||||
sectionLines.append(headerText)
|
|
||||||
for bonusData in sectionData["bonuses"]:
|
def convertTraits(data):
|
||||||
prefix = u"{} ".format(bonusData["number"]) if "number" in bonusData else ""
|
|
||||||
bonusText = u"{}{}".format(prefix, bonusData["text"].replace(u"\u00B7", u"\u2022 "))
|
def convertSection(sectionData):
|
||||||
sectionLines.append(bonusText)
|
sectionLines = []
|
||||||
sectionLine = u"<br />\n".join(sectionLines)
|
headerText = u"<b>{}</b>".format(sectionData["header"])
|
||||||
return sectionLine
|
sectionLines.append(headerText)
|
||||||
|
for bonusData in sectionData["bonuses"]:
|
||||||
newData = []
|
prefix = u"{} ".format(bonusData["number"]) if "number" in bonusData else ""
|
||||||
for row in data:
|
bonusText = u"{}{}".format(prefix, bonusData["text"].replace(u"\u00B7", u"\u2022 "))
|
||||||
typeLines = []
|
sectionLines.append(bonusText)
|
||||||
typeId = row["typeID"]
|
sectionLine = u"<br />\n".join(sectionLines)
|
||||||
traitData = row["traits_en-us"]
|
return sectionLine
|
||||||
for skillData in sorted(traitData.get("skills", ()), key=lambda i: i["header"]):
|
|
||||||
typeLines.append(convertSection(skillData))
|
newData = []
|
||||||
if "role" in traitData:
|
for row in data:
|
||||||
typeLines.append(convertSection(traitData["role"]))
|
typeLines = []
|
||||||
if "misc" in traitData:
|
typeId = row["typeID"]
|
||||||
typeLines.append(convertSection(traitData["misc"]))
|
traitData = row["traits_en-us"]
|
||||||
traitLine = u"<br />\n<br />\n".join(typeLines)
|
for skillData in sorted(traitData.get("skills", ()), key=lambda i: i["header"]):
|
||||||
newRow = {"typeID": typeId, "traitText": traitLine}
|
typeLines.append(convertSection(skillData))
|
||||||
newData.append(newRow)
|
if "role" in traitData:
|
||||||
return newData
|
typeLines.append(convertSection(traitData["role"]))
|
||||||
|
if "misc" in traitData:
|
||||||
def convertTypes(typesData):
|
typeLines.append(convertSection(traitData["misc"]))
|
||||||
"""
|
traitLine = u"<br />\n<br />\n".join(typeLines)
|
||||||
Add factionID column to invtypes table.
|
newRow = {"typeID": typeId, "traitText": traitLine}
|
||||||
"""
|
newData.append(newRow)
|
||||||
factionMap = {}
|
return newData
|
||||||
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
|
||||||
overridesData = json.load(f)
|
def convertTypes(typesData):
|
||||||
for typeID, typeData in overridesData.items():
|
"""
|
||||||
factionID = typeData.get("factionID")
|
Add factionID column to evetypes table.
|
||||||
if factionID is not None:
|
"""
|
||||||
factionMap[int(typeID)] = factionID
|
factionMap = {}
|
||||||
for row in typesData:
|
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
||||||
row['factionID'] = factionMap.get(int(row['typeID']))
|
overridesData = json.load(f)
|
||||||
return typesData
|
for typeID, typeData in overridesData.items():
|
||||||
|
factionID = typeData.get("factionID")
|
||||||
data = {}
|
if factionID is not None:
|
||||||
|
factionMap[int(typeID)] = factionID
|
||||||
# Dump all data to memory so we can easely cross check ignored rows
|
for row in typesData:
|
||||||
for jsonName, cls in tables.iteritems():
|
row['factionID'] = factionMap.get(int(row['typeID']))
|
||||||
with open(os.path.join(jsonPath, "{}.json".format(jsonName))) as f:
|
return typesData
|
||||||
tableData = json.load(f)
|
|
||||||
if jsonName == "icons":
|
data = {}
|
||||||
tableData = convertIcons(tableData)
|
|
||||||
if jsonName == "phbtraits":
|
# Dump all data to memory so we can easely cross check ignored rows
|
||||||
tableData = convertTraits(tableData)
|
for jsonName, cls in tables.iteritems():
|
||||||
if jsonName == "invtypes":
|
with open(os.path.join(jsonPath, "{}.json".format(jsonName))) as f:
|
||||||
tableData = convertTypes(tableData)
|
tableData = json.load(f)
|
||||||
data[jsonName] = tableData
|
if jsonName in rowsInValues:
|
||||||
|
tableData = list(tableData.values())
|
||||||
# Set with typeIDs which we will have in our database
|
if jsonName == "icons":
|
||||||
invTypes = set()
|
tableData = convertIcons(tableData)
|
||||||
for row in data["invtypes"]:
|
if jsonName == "phbtraits":
|
||||||
# 1306 - group Ship Modifiers, for items like tactical t3 ship modes
|
tableData = convertTraits(tableData)
|
||||||
if (row["published"] or row['groupID'] == 1306):
|
if jsonName == "evetypes":
|
||||||
invTypes.add(row["typeID"])
|
tableData = convertTypes(tableData)
|
||||||
|
data[jsonName] = tableData
|
||||||
# ignore checker
|
|
||||||
def isIgnored(file, row):
|
# Set with typeIDs which we will have in our database
|
||||||
if file in ("invtypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in invTypes:
|
# Sometimes CCP unpublishes some items we want to have published, we
|
||||||
return True
|
# can do it here - just add them to initial set
|
||||||
return False
|
eveTypes = set()
|
||||||
|
for row in data["evetypes"]:
|
||||||
# Loop through each json file and write it away, checking ignored rows
|
# 1306 - group Ship Modifiers, for items like tactical t3 ship modes
|
||||||
for jsonName, table in data.iteritems():
|
if (row["published"] or row['groupID'] == 1306):
|
||||||
fieldMap = fieldMapping.get(jsonName, {})
|
eveTypes.add(row["typeID"])
|
||||||
print "processing {}".format(jsonName)
|
|
||||||
for row in table:
|
# ignore checker
|
||||||
# We don't care about some kind of rows, filter it out if so
|
def isIgnored(file, row):
|
||||||
if not isIgnored(jsonName, row):
|
if file in ("evetypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in eveTypes:
|
||||||
instance = tables[jsonName]()
|
return True
|
||||||
# fix for issue 80
|
return False
|
||||||
if jsonName is "icons" and "res:/UI/Texture/Icons/" in str(row["iconFile"]):
|
|
||||||
row["iconFile"] = row["iconFile"].replace("res:/UI/Texture/Icons/","").replace(".png", "")
|
# Loop through each json file and write it away, checking ignored rows
|
||||||
for k, v in row.iteritems():
|
for jsonName, table in data.iteritems():
|
||||||
setattr(instance, fieldMap.get(k, k), v)
|
fieldMap = fieldMapping.get(jsonName, {})
|
||||||
|
print "processing {}".format(jsonName)
|
||||||
eos.db.gamedata_session.add(instance)
|
for row in table:
|
||||||
|
# We don't care about some kind of rows, filter it out if so
|
||||||
eos.db.gamedata_session.commit()
|
if not isIgnored(jsonName, row):
|
||||||
|
instance = tables[jsonName]()
|
||||||
print("done")
|
# fix for issue 80
|
||||||
|
if jsonName is "icons" and "res:/UI/Texture/Icons/" in str(row["iconFile"]):
|
||||||
if __name__ == "__main__":
|
row["iconFile"] = row["iconFile"].replace("res:/UI/Texture/Icons/","").replace(".png", "")
|
||||||
parser = argparse.ArgumentParser(description="This scripts dumps effects from an sqlite cache dump to mongo")
|
for k, v in row.iteritems():
|
||||||
parser.add_argument("-d", "--db", required=True, type=str, help="The sqlalchemy connectionstring, example: sqlite:///c:/tq.db")
|
setattr(instance, fieldMap.get(k, k), v)
|
||||||
parser.add_argument("-j", "--json", required=True, type=str, help="The path to the json dump")
|
|
||||||
args = parser.parse_args()
|
eos.db.gamedata_session.add(instance)
|
||||||
|
|
||||||
main(args.db, args.json)
|
eos.db.gamedata_session.commit()
|
||||||
|
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="This scripts dumps effects from an sqlite cache dump to mongo")
|
||||||
|
parser.add_argument("-d", "--db", required=True, type=str, help="The sqlalchemy connectionstring, example: sqlite:///c:/tq.db")
|
||||||
|
parser.add_argument("-j", "--json", required=True, type=str, help="The path to the json dump")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
main(args.db, args.json)
|
||||||
|
|||||||
120
scripts/renders_update.py
Normal file
120
scripts/renders_update.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script updates ship renders and removes unused ones.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='This script updates ship renders for pyfa')
|
||||||
|
parser.add_argument('-r', '--renders', required=True, type=str, help='path to unpacked Renders folder from CCP\'s image export')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
db_path = os.path.abspath(os.path.join(script_dir, '..', 'staticdata', 'eve.db'))
|
||||||
|
icons_dir = os.path.abspath(os.path.join(script_dir, '..', 'staticdata', 'icons', 'ships'))
|
||||||
|
export_dir = os.path.abspath(os.path.expanduser(args.renders))
|
||||||
|
|
||||||
|
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
|
RENDER_SIZE = (32, 32)
|
||||||
|
|
||||||
|
|
||||||
|
query_ships = 'select it.typeID from invtypes as it inner join invgroups as ig on it.groupID = ig.groupID where ig.categoryID = 6'
|
||||||
|
|
||||||
|
|
||||||
|
needed = set()
|
||||||
|
existing = set()
|
||||||
|
export = set()
|
||||||
|
|
||||||
|
|
||||||
|
for row in cursor.execute(query_ships):
|
||||||
|
needed.add(row[0])
|
||||||
|
|
||||||
|
for container, filedir in (
|
||||||
|
(existing, icons_dir),
|
||||||
|
(export, export_dir)
|
||||||
|
):
|
||||||
|
for fname in os.listdir(filedir):
|
||||||
|
if not os.path.isfile(os.path.join(filedir, fname)):
|
||||||
|
continue
|
||||||
|
m = re.match(r'^(?P<typeid>\d+)\.png', fname)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
container.add(int(m.group('typeid')))
|
||||||
|
|
||||||
|
toremove = existing.difference(needed)
|
||||||
|
toupdate = existing.intersection(needed)
|
||||||
|
toadd = needed.difference(existing)
|
||||||
|
|
||||||
|
|
||||||
|
def crop_image(img):
|
||||||
|
w, h = img.size
|
||||||
|
if h == w:
|
||||||
|
return img
|
||||||
|
normal = min(h, w)
|
||||||
|
diff_w = w - normal
|
||||||
|
diff_h = h - normal
|
||||||
|
crop_top = diff_h // 2
|
||||||
|
crop_bot = diff_h // 2 + diff_h % 2
|
||||||
|
crop_left = diff_w // 2
|
||||||
|
crop_right = diff_w // 2 + diff_w % 2
|
||||||
|
box = (crop_left, crop_top, w - crop_right, h - crop_bot)
|
||||||
|
return img.crop(box)
|
||||||
|
|
||||||
|
|
||||||
|
def get_render(type_id):
|
||||||
|
fname = '{}.png'.format(type_id)
|
||||||
|
fullpath = os.path.join(export_dir, fname)
|
||||||
|
img = Image.open(fullpath)
|
||||||
|
if img.size != RENDER_SIZE:
|
||||||
|
img = crop_image(img)
|
||||||
|
img.thumbnail(RENDER_SIZE, Image.ANTIALIAS)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
if toremove:
|
||||||
|
print('Some renders are not used and will be removed:')
|
||||||
|
for type_id in sorted(toremove):
|
||||||
|
fullname = '{}.png'.format(type_id)
|
||||||
|
print(' {}'.format(fullname))
|
||||||
|
fullpath = os.path.join(icons_dir, fullname)
|
||||||
|
os.remove(fullpath)
|
||||||
|
|
||||||
|
if toupdate:
|
||||||
|
print('Updating {} renders...'.format(len(toupdate)))
|
||||||
|
missing = toupdate.difference(export)
|
||||||
|
toupdate.intersection_update(export)
|
||||||
|
for type_id in sorted(toupdate):
|
||||||
|
render = get_render(type_id)
|
||||||
|
fname = '{}.png'.format(type_id)
|
||||||
|
fullpath = os.path.join(icons_dir, fname)
|
||||||
|
render.save(fullpath, 'PNG')
|
||||||
|
if missing:
|
||||||
|
print(' {} renders are missing in export:'.format(len(missing)))
|
||||||
|
for type_id in sorted(missing):
|
||||||
|
print(' {}.png'.format(type_id))
|
||||||
|
|
||||||
|
if toadd:
|
||||||
|
print('Adding {} renders...'.format(len(toadd)))
|
||||||
|
missing = toadd.difference(export)
|
||||||
|
toadd.intersection_update(export)
|
||||||
|
for type_id in sorted(toadd):
|
||||||
|
render = get_render(type_id)
|
||||||
|
fname = '{}.png'.format(type_id)
|
||||||
|
fullpath = os.path.join(icons_dir, fname)
|
||||||
|
render.save(fullpath, 'PNG')
|
||||||
|
if missing:
|
||||||
|
print(' {} renders are missing in export:'.format(len(missing)))
|
||||||
|
for type_id in sorted(missing):
|
||||||
|
print(' {}.png'.format(type_id))
|
||||||
146
service/conversions/releaseCarnyx.py
Normal file
146
service/conversions/releaseCarnyx.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""
|
||||||
|
Conversion pack for Carnyx Module Tiericide
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
# Renamed items
|
||||||
|
"1MN Microwarpdrive I": "5MN Microwarpdrive I",
|
||||||
|
"1MN Microwarpdrive II": "5MN Microwarpdrive II",
|
||||||
|
"Prototype 100MN Microwarpdrive I": "500MN Cold-Gas Enduring Microwarpdrive",
|
||||||
|
"Experimental 100MN Afterburner I": "100MN Monopropellant Enduring Afterburner",
|
||||||
|
"Upgraded 1MN Microwarpdrive I": "5MN Cold-Gas Enduring Microwarpdrive",
|
||||||
|
"Limited 1MN Microwarpdrive I": "5MN Y-T8 Compact Microwarpdrive",
|
||||||
|
"Experimental 10MN Microwarpdrive I": "50MN Cold-Gas Enduring Microwarpdrive",
|
||||||
|
"Limited 1MN Afterburner I": "1MN Y-S8 Compact Afterburner",
|
||||||
|
"Experimental 1MN Afterburner I": "1MN Monopropellant Enduring Afterburner",
|
||||||
|
"Experimental 10MN Afterburner I": "10MN Monopropellant Enduring Afterburner",
|
||||||
|
"Large Azeotropic Ward Salubrity I": "Large Azeotropic Restrained Shield Extender",
|
||||||
|
"Small Azeotropic Ward Salubrity I": "Small Azeotropic Restrained Shield Extender",
|
||||||
|
"Medium Azeotropic Ward Salubrity I": "Medium Azeotropic Restrained Shield Extender",
|
||||||
|
"Medium F-S9 Regolith Shield Induction": "Medium F-S9 Regolith Compact Shield Extender",
|
||||||
|
"Small F-S9 Regolith Shield Induction": "Small F-S9 Regolith Compact Shield Extender",
|
||||||
|
"Large F-S9 Regolith Shield Induction": "Large F-S9 Regolith Compact Shield Extender",
|
||||||
|
"1600mm Reinforced Steel Plates I": "1600mm Steel Plates I",
|
||||||
|
"100mm Reinforced Steel Plates I": "100mm Steel Plates I",
|
||||||
|
"200mm Reinforced Steel Plates I": "200mm Steel Plates I",
|
||||||
|
"400mm Reinforced Steel Plates I": "400mm Steel Plates I",
|
||||||
|
"800mm Reinforced Steel Plates I": "800mm Steel Plates I",
|
||||||
|
"400mm Reinforced Rolled Tungsten Plates I": "400mm Rolled Tungsten Compact Plates",
|
||||||
|
"400mm Reinforced Crystalline Carbonide Plates I": "400mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"800mm Reinforced Rolled Tungsten Plates I": "800mm Rolled Tungsten Compact Plates",
|
||||||
|
"800mm Reinforced Crystalline Carbonide Plates I": "800mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"1600mm Reinforced Rolled Tungsten Plates I": "1600mm Rolled Tungsten Compact Plates",
|
||||||
|
"1600mm Reinforced Crystalline Carbonide Plates I": "1600mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"100mm Reinforced Rolled Tungsten Plates I": "100mm Rolled Tungsten Compact Plates",
|
||||||
|
"100mm Reinforced Crystalline Carbonide Plates I": "100mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"200mm Reinforced Rolled Tungsten Plates I": "200mm Rolled Tungsten Compact Plates",
|
||||||
|
"200mm Reinforced Crystalline Carbonide Plates I": "200mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"10MN Microwarpdrive I": "50MN Microwarpdrive I",
|
||||||
|
"100MN Microwarpdrive I": "500MN Microwarpdrive I",
|
||||||
|
"10MN Microwarpdrive II": "50MN Microwarpdrive II",
|
||||||
|
"100MN Microwarpdrive II": "500MN Microwarpdrive II",
|
||||||
|
"Domination 100MN Microwarpdrive": "Domination 500MN Microwarpdrive",
|
||||||
|
"Shadow Serpentis 100MN Microwarpdrive": "Shadow Serpentis 500MN Microwarpdrive",
|
||||||
|
"Domination 10MN Microwarpdrive": "Domination 50MN Microwarpdrive",
|
||||||
|
"Shadow Serpentis 10MN Microwarpdrive": "Shadow Serpentis 50MN Microwarpdrive",
|
||||||
|
"Domination 1MN Microwarpdrive": "Domination 5MN Microwarpdrive",
|
||||||
|
"Shadow Serpentis 1MN Microwarpdrive": "Shadow Serpentis 5MN Microwarpdrive",
|
||||||
|
"Mizuro's Modified 100MN Microwarpdrive": "Mizuro's Modified 500MN Microwarpdrive",
|
||||||
|
"Hakim's Modified 100MN Microwarpdrive": "Hakim's Modified 500MN Microwarpdrive",
|
||||||
|
"Gotan's Modified 100MN Microwarpdrive": "Gotan's Modified 500MN Microwarpdrive",
|
||||||
|
"Tobias' Modified 100MN Microwarpdrive": "Tobias' Modified 500MN Microwarpdrive",
|
||||||
|
"Brynn's Modified 100MN Microwarpdrive": "Brynn's Modified 500MN Microwarpdrive",
|
||||||
|
"Tuvan's Modified 100MN Microwarpdrive": "Tuvan's Modified 500MN Microwarpdrive",
|
||||||
|
"Setele's Modified 100MN Microwarpdrive": "Setele's Modified 500MN Microwarpdrive",
|
||||||
|
"Cormack's Modified 100MN Microwarpdrive": "Cormack's Modified 500MN Microwarpdrive",
|
||||||
|
"Republic Fleet 1MN Microwarpdrive": "Republic Fleet 5MN Microwarpdrive",
|
||||||
|
"Republic Fleet 10MN Microwarpdrive": "Republic Fleet 50MN Microwarpdrive",
|
||||||
|
"Republic Fleet 100MN Microwarpdrive": "Republic Fleet 500MN Microwarpdrive",
|
||||||
|
"Federation Navy 1MN Microwarpdrive": "Federation Navy 5MN Microwarpdrive",
|
||||||
|
"Federation Navy 10MN Microwarpdrive": "Federation Navy 50MN Microwarpdrive",
|
||||||
|
"Federation Navy 100MN Microwarpdrive": "Federation Navy 500MN Microwarpdrive",
|
||||||
|
"Coreli C-Type 1MN Microwarpdrive": "Coreli C-Type 5MN Microwarpdrive",
|
||||||
|
"Corelum C-Type 10MN Microwarpdrive": "Corelum C-Type 50MN Microwarpdrive",
|
||||||
|
"Core C-Type 100MN Microwarpdrive": "Core C-Type 500MN Microwarpdrive",
|
||||||
|
"Coreli B-Type 1MN Microwarpdrive": "Coreli B-Type 5MN Microwarpdrive",
|
||||||
|
"Corelum B-Type 10MN Microwarpdrive": "Corelum B-Type 50MN Microwarpdrive",
|
||||||
|
"Core B-Type 100MN Microwarpdrive": "Core B-Type 500MN Microwarpdrive",
|
||||||
|
"Coreli A-Type 1MN Microwarpdrive": "Coreli A-Type 5MN Microwarpdrive",
|
||||||
|
"Corelum A-Type 10MN Microwarpdrive": "Corelum A-Type 50MN Microwarpdrive",
|
||||||
|
"Core A-Type 100MN Microwarpdrive": "Core A-Type 500MN Microwarpdrive",
|
||||||
|
"Core X-Type 100MN Microwarpdrive": "Core X-Type 500MN Microwarpdrive",
|
||||||
|
"Gistii C-Type 1MN Microwarpdrive": "Gistii C-Type 5MN Microwarpdrive",
|
||||||
|
"Gistum C-Type 10MN Microwarpdrive": "Gistum C-Type 50MN Microwarpdrive",
|
||||||
|
"Gist C-Type 100MN Microwarpdrive": "Gist C-Type 500MN Microwarpdrive",
|
||||||
|
"Gistii B-Type 1MN Microwarpdrive": "Gistii B-Type 5MN Microwarpdrive",
|
||||||
|
"Gistum B-Type 10MN Microwarpdrive": "Gistum B-Type 50MN Microwarpdrive",
|
||||||
|
"Gist B-Type 100MN Microwarpdrive": "Gist B-Type 500MN Microwarpdrive",
|
||||||
|
"Gistii A-Type 1MN Microwarpdrive": "Gistii A-Type 5MN Microwarpdrive",
|
||||||
|
"Gistum A-Type 10MN Microwarpdrive": "Gistum A-Type 50MN Microwarpdrive",
|
||||||
|
"Gist A-Type 100MN Microwarpdrive": "Gist A-Type 500MN Microwarpdrive",
|
||||||
|
"Gist X-Type 100MN Microwarpdrive": "Gist X-Type 500MN Microwarpdrive",
|
||||||
|
"100mm Reinforced Steel Plates II": "100mm Steel Plates II",
|
||||||
|
"200mm Reinforced Steel Plates II": "200mm Steel Plates II",
|
||||||
|
"400mm Reinforced Steel Plates II": "400mm Steel Plates II",
|
||||||
|
"800mm Reinforced Steel Plates II": "800mm Steel Plates II",
|
||||||
|
"1600mm Reinforced Steel Plates II": "1600mm Steel Plates II",
|
||||||
|
"Micro 'Trapper' Shield Extender": "Small 'Trapper' Shield Extender",
|
||||||
|
"1MN Analog Booster Rockets": "1MN Analog Booster Afterburner",
|
||||||
|
"10MN Analog Booster Rockets": "10MN Analog Booster Afterburner",
|
||||||
|
"100MN Analog Booster Rockets": "100MN Analog Booster Afterburner",
|
||||||
|
"1MN Digital Booster Rockets": "5MN Digital Booster Microwarpdrive",
|
||||||
|
"10MN Digital Booster Rockets": "50MN Digital Booster Microwarpdrive",
|
||||||
|
"100MN Digital Booster Rockets": "500MN Digital Booster Microwarpdrive",
|
||||||
|
"Civilian Afterburner": "1MN Civilian Afterburner",
|
||||||
|
"'Abatis' 100mm Reinforced Steel Plates I": "'Abatis' 100mm Steel Plates",
|
||||||
|
"'Bailey' 1600mm Reinforced Steel Plates I": "'Bailey' 1600mm Steel Plates",
|
||||||
|
"'Chainmail' 200mm Reinforced Steel Plates I": "'Chainmail' 200mm Steel Plates",
|
||||||
|
"'Bastion' 400mm Reinforced Steel Plates I": "'Bastion' 400mm Steel Plates",
|
||||||
|
"'Citadella' 50mm Reinforced Steel Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"'Barbican' 800mm Reinforced Steel Plates I": "'Barbican' 800mm Steel Plates",
|
||||||
|
"Syndicate 100mm Reinforced Steel Plates": "Syndicate 100mm Steel Plates",
|
||||||
|
"Syndicate 1600mm Reinforced Steel Plates": "Syndicate 1600mm Steel Plates",
|
||||||
|
"Syndicate 200mm Reinforced Steel Plates": "Syndicate 200mm Steel Plates",
|
||||||
|
"Syndicate 400mm Reinforced Steel Plates": "Syndicate 400mm Steel Plates",
|
||||||
|
"Syndicate 800mm Reinforced Steel Plates": "Syndicate 800mm Steel Plates",
|
||||||
|
"Imperial Navy 100mm Reinforced Steel Plates": "Imperial Navy 100mm Steel Plates",
|
||||||
|
"Federation Navy 100mm Reinforced Steel Plates": "Federation Navy 100mm Steel Plates",
|
||||||
|
"Imperial Navy 1600mm Reinforced Steel Plates": "Imperial Navy 1600mm Steel Plates",
|
||||||
|
"Federation Navy 1600mm Reinforced Steel Plates": "Federation Navy 1600mm Steel Plates",
|
||||||
|
"Imperial Navy 200mm Reinforced Steel Plates": "Imperial Navy 200mm Steel Plates",
|
||||||
|
"Federation Navy 200mm Reinforced Steel Plates": "Federation Navy 200mm Steel Plates",
|
||||||
|
"Imperial Navy 400mm Reinforced Steel Plates": "Imperial Navy 400mm Steel Plates",
|
||||||
|
"Federation Navy 400mm Reinforced Steel Plates": "Federation Navy 400mm Steel Plates",
|
||||||
|
"Imperial Navy 800mm Reinforced Steel Plates": "Imperial Navy 800mm Steel Plates",
|
||||||
|
"Federation Navy 800mm Reinforced Steel Plates": "Federation Navy 800mm Steel Plates",
|
||||||
|
"Polarized Small Pulse Laser": "Polarized Small Focused Pulse Laser",
|
||||||
|
# Converted items
|
||||||
|
"Large Subordinate Screen Stabilizer I": "Large F-S9 Regolith Compact Shield Extender",
|
||||||
|
"Large Supplemental Barrier Emitter I": "Large Azeotropic Restrained Shield Extender",
|
||||||
|
"Medium Subordinate Screen Stabilizer I": "Medium F-S9 Regolith Compact Shield Extender",
|
||||||
|
"Medium Supplemental Barrier Emitter I": "Medium Azeotropic Restrained Shield Extender",
|
||||||
|
"Micro Azeotropic Ward Salubrity I": "Small 'Trapper' Shield Extender",
|
||||||
|
"Micro F-S9 Regolith Shield Induction": "Small 'Trapper' Shield Extender",
|
||||||
|
"Micro Shield Extender I": "Small 'Trapper' Shield Extender",
|
||||||
|
"Micro Shield Extender II": "Small 'Trapper' Shield Extender",
|
||||||
|
"Micro Subordinate Screen Stabilizer I": "Small 'Trapper' Shield Extender",
|
||||||
|
"Micro Supplemental Barrier Emitter I": "Small 'Trapper' Shield Extender",
|
||||||
|
"Small Subordinate Screen Stabilizer I": "Small F-S9 Regolith Compact Shield Extender",
|
||||||
|
"Small Supplemental Barrier Emitter I": "Small Azeotropic Restrained Shield Extender",
|
||||||
|
"100mm Reinforced Nanofiber Plates I": "100mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"100mm Reinforced Titanium Plates I": "100mm Rolled Tungsten Compact Plates",
|
||||||
|
"1600mm Reinforced Nanofiber Plates I": "1600mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"1600mm Reinforced Titanium Plates I": "1600mm Rolled Tungsten Compact Plates",
|
||||||
|
"200mm Reinforced Nanofiber Plates I": "200mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"200mm Reinforced Titanium Plates I": "200mm Rolled Tungsten Compact Plates",
|
||||||
|
"400mm Reinforced Nanofiber Plates I": "400mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"400mm Reinforced Titanium Plates I": "400mm Rolled Tungsten Compact Plates",
|
||||||
|
"50mm Reinforced Crystalline Carbonide Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"50mm Reinforced Nanofiber Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"50mm Reinforced Rolled Tungsten Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"50mm Reinforced Steel Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"50mm Reinforced Steel Plates II": "'Citadella' 100mm Steel Plates",
|
||||||
|
"50mm Reinforced Titanium Plates I": "'Citadella' 100mm Steel Plates",
|
||||||
|
"800mm Reinforced Nanofiber Plates I": "800mm Crystalline Carbonide Restrained Plates",
|
||||||
|
"800mm Reinforced Titanium Plates I": "800mm Rolled Tungsten Compact Plates"
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ from service.fleet import Fleet
|
|||||||
from service.settings import SettingsProvider
|
from service.settings import SettingsProvider
|
||||||
from service.port import Port
|
from service.port import Port
|
||||||
|
|
||||||
logger = logging.getLogger("pyfa.service.fit")
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class FitBackupThread(threading.Thread):
|
class FitBackupThread(threading.Thread):
|
||||||
def __init__(self, path, callback):
|
def __init__(self, path, callback):
|
||||||
@@ -97,7 +97,8 @@ class Fit(object):
|
|||||||
"rackSlots": True,
|
"rackSlots": True,
|
||||||
"rackLabels": True,
|
"rackLabels": True,
|
||||||
"compactSkills": True,
|
"compactSkills": True,
|
||||||
"showTooltip": True}
|
"showTooltip": True,
|
||||||
|
"showMarketShortcuts": False}
|
||||||
|
|
||||||
self.serviceFittingOptions = SettingsProvider.getInstance().getSettings(
|
self.serviceFittingOptions = SettingsProvider.getInstance().getSettings(
|
||||||
"pyfaServiceFittingOptions", serviceFittingDefaultOptions)
|
"pyfaServiceFittingOptions", serviceFittingDefaultOptions)
|
||||||
@@ -149,8 +150,8 @@ class Fit(object):
|
|||||||
return fit.modules[pos]
|
return fit.modules[pos]
|
||||||
|
|
||||||
def newFit(self, shipID, name=None):
|
def newFit(self, shipID, name=None):
|
||||||
fit = eos.types.Fit()
|
ship = eos.types.Ship(eos.db.getItem(shipID))
|
||||||
fit.ship = eos.types.Ship(eos.db.getItem(shipID))
|
fit = eos.types.Fit(ship)
|
||||||
fit.name = name if name is not None else "New %s" % fit.ship.item.name
|
fit.name = name if name is not None else "New %s" % fit.ship.item.name
|
||||||
fit.damagePattern = self.pattern
|
fit.damagePattern = self.pattern
|
||||||
fit.targetResists = self.targetResists
|
fit.targetResists = self.targetResists
|
||||||
@@ -174,10 +175,14 @@ class Fit(object):
|
|||||||
fit = eos.db.getFit(fitID)
|
fit = eos.db.getFit(fitID)
|
||||||
sFleet = Fleet.getInstance()
|
sFleet = Fleet.getInstance()
|
||||||
sFleet.removeAssociatedFleetData(fit)
|
sFleet.removeAssociatedFleetData(fit)
|
||||||
self.removeProjectedData(fitID)
|
|
||||||
|
|
||||||
eos.db.remove(fit)
|
eos.db.remove(fit)
|
||||||
|
|
||||||
|
# refresh any fits this fit is projected onto. Otherwise, if we have
|
||||||
|
# already loaded those fits, they will not reflect the changes
|
||||||
|
for projection in fit.projectedOnto.values():
|
||||||
|
eos.db.saveddata_session.refresh(projection.victim_fit)
|
||||||
|
|
||||||
def copyFit(self, fitID):
|
def copyFit(self, fitID):
|
||||||
fit = eos.db.getFit(fitID)
|
fit = eos.db.getFit(fitID)
|
||||||
newFit = copy.deepcopy(fit)
|
newFit = copy.deepcopy(fit)
|
||||||
@@ -192,14 +197,6 @@ class Fit(object):
|
|||||||
fit.clear()
|
fit.clear()
|
||||||
return fit
|
return fit
|
||||||
|
|
||||||
def removeProjectedData(self, fitID):
|
|
||||||
"""Removes projection relation from ships that have fitID as projection. See GitHub issue #90"""
|
|
||||||
fit = eos.db.getFit(fitID)
|
|
||||||
fits = eos.db.getProjectedFits(fitID)
|
|
||||||
|
|
||||||
for projectee in fits:
|
|
||||||
projectee.projectedFits.remove(fit)
|
|
||||||
|
|
||||||
def toggleFactorReload(self, fitID):
|
def toggleFactorReload(self, fitID):
|
||||||
if fitID is None:
|
if fitID is None:
|
||||||
return None
|
return None
|
||||||
@@ -235,6 +232,7 @@ class Fit(object):
|
|||||||
return None
|
return None
|
||||||
fit = eos.db.getFit(fitID)
|
fit = eos.db.getFit(fitID)
|
||||||
inited = getattr(fit, "inited", None)
|
inited = getattr(fit, "inited", None)
|
||||||
|
|
||||||
if inited is None or inited is False:
|
if inited is None or inited is False:
|
||||||
sFleet = Fleet.getInstance()
|
sFleet = Fleet.getInstance()
|
||||||
f = sFleet.getLinearFleet(fit)
|
f = sFleet.getLinearFleet(fit)
|
||||||
@@ -245,6 +243,7 @@ class Fit(object):
|
|||||||
fit.fleet = f
|
fit.fleet = f
|
||||||
|
|
||||||
if not projected:
|
if not projected:
|
||||||
|
print "Not projected, getting projected fits"
|
||||||
for fitP in fit.projectedFits:
|
for fitP in fit.projectedFits:
|
||||||
self.getFit(fitP.ID, projected = True)
|
self.getFit(fitP.ID, projected = True)
|
||||||
self.recalc(fit, withBoosters=True)
|
self.recalc(fit, withBoosters=True)
|
||||||
@@ -274,7 +273,6 @@ class Fit(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
fit.implants.freeSlot(implant)
|
|
||||||
fit.implants.append(implant)
|
fit.implants.append(implant)
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
return True
|
return True
|
||||||
@@ -300,7 +298,6 @@ class Fit(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
fit.boosters.freeSlot(booster)
|
|
||||||
fit.boosters.append(booster)
|
fit.boosters.append(booster)
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
return True
|
return True
|
||||||
@@ -323,9 +320,14 @@ class Fit(object):
|
|||||||
eager=("attributes", "group.category"))
|
eager=("attributes", "group.category"))
|
||||||
|
|
||||||
if isinstance(thing, eos.types.Fit):
|
if isinstance(thing, eos.types.Fit):
|
||||||
if thing.ID == fitID:
|
if thing in fit.projectedFits:
|
||||||
return
|
return
|
||||||
fit.projectedFits.append(thing)
|
|
||||||
|
fit.__projectedFits[thing.ID] = thing
|
||||||
|
|
||||||
|
# this bit is required -- see GH issue # 83
|
||||||
|
eos.db.saveddata_session.flush()
|
||||||
|
eos.db.saveddata_session.refresh(thing)
|
||||||
elif thing.category.name == "Drone":
|
elif thing.category.name == "Drone":
|
||||||
drone = None
|
drone = None
|
||||||
for d in fit.projectedDrones.find(thing):
|
for d in fit.projectedDrones.find(thing):
|
||||||
@@ -364,6 +366,22 @@ class Fit(object):
|
|||||||
thing.state = self.__getProposedState(thing, click)
|
thing.state = self.__getProposedState(thing, click)
|
||||||
if not thing.canHaveState(thing.state, fit):
|
if not thing.canHaveState(thing.state, fit):
|
||||||
thing.state = State.OFFLINE
|
thing.state = State.OFFLINE
|
||||||
|
elif isinstance(thing, eos.types.Fit):
|
||||||
|
print "toggle fit"
|
||||||
|
projectionInfo = thing.getProjectionInfo(fitID)
|
||||||
|
if projectionInfo:
|
||||||
|
projectionInfo.active = not projectionInfo.active
|
||||||
|
|
||||||
|
eos.db.commit()
|
||||||
|
self.recalc(fit)
|
||||||
|
|
||||||
|
def changeAmount(self, fitID, projected_fit, amount):
|
||||||
|
"""Change amount of projected fits"""
|
||||||
|
fit = eos.db.getFit(fitID)
|
||||||
|
amount = min(20, max(1, amount)) # 1 <= a <= 20
|
||||||
|
projectionInfo = projected_fit.getProjectionInfo(fitID)
|
||||||
|
if projectionInfo:
|
||||||
|
projectionInfo.amount = amount
|
||||||
|
|
||||||
eos.db.commit()
|
eos.db.commit()
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
@@ -375,7 +393,8 @@ class Fit(object):
|
|||||||
elif isinstance(thing, eos.types.Module):
|
elif isinstance(thing, eos.types.Module):
|
||||||
fit.projectedModules.remove(thing)
|
fit.projectedModules.remove(thing)
|
||||||
else:
|
else:
|
||||||
fit.projectedFits.remove(thing)
|
del fit.__projectedFits[thing.ID]
|
||||||
|
#fit.projectedFits.remove(thing)
|
||||||
|
|
||||||
eos.db.commit()
|
eos.db.commit()
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
@@ -398,8 +417,11 @@ class Fit(object):
|
|||||||
if m.isValidState(State.ACTIVE):
|
if m.isValidState(State.ACTIVE):
|
||||||
m.state = State.ACTIVE
|
m.state = State.ACTIVE
|
||||||
|
|
||||||
|
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
|
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||||
self.checkStates(fit, m)
|
self.checkStates(fit, m)
|
||||||
|
|
||||||
fit.fill()
|
fit.fill()
|
||||||
eos.db.commit()
|
eos.db.commit()
|
||||||
|
|
||||||
@@ -857,7 +879,10 @@ class Fit(object):
|
|||||||
if drone.amountActive > 0 and not drone.canBeApplied(fit):
|
if drone.amountActive > 0 and not drone.canBeApplied(fit):
|
||||||
drone.amountActive = 0
|
drone.amountActive = 0
|
||||||
changed = True
|
changed = True
|
||||||
return changed
|
|
||||||
|
# If any state was changed, recalculate attributes again
|
||||||
|
if changed:
|
||||||
|
self.recalc(fit)
|
||||||
|
|
||||||
def toggleModulesState(self, fitID, base, modules, click):
|
def toggleModulesState(self, fitID, base, modules, click):
|
||||||
proposedState = self.__getProposedState(base, click)
|
proposedState = self.__getProposedState(base, click)
|
||||||
@@ -873,11 +898,8 @@ class Fit(object):
|
|||||||
|
|
||||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
# Then, check states of all modules and change where needed
|
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||||
changed = self.checkStates(fit, base)
|
self.checkStates(fit, base)
|
||||||
# If any state was changed, recalulate attributes again
|
|
||||||
if changed is True:
|
|
||||||
self.recalc(fit)
|
|
||||||
|
|
||||||
# Old state : New State
|
# Old state : New State
|
||||||
localMap = {State.OVERHEATED: State.ACTIVE,
|
localMap = {State.OVERHEATED: State.ACTIVE,
|
||||||
@@ -890,7 +912,7 @@ class Fit(object):
|
|||||||
State.ONLINE: State.ACTIVE} # Just in case
|
State.ONLINE: State.ACTIVE} # Just in case
|
||||||
|
|
||||||
def __getProposedState(self, mod, click, proposedState=None):
|
def __getProposedState(self, mod, click, proposedState=None):
|
||||||
if mod.slot in (Slot.RIG, Slot.SUBSYSTEM) or mod.isEmpty:
|
if mod.slot is Slot.SUBSYSTEM or mod.isEmpty:
|
||||||
return State.ONLINE
|
return State.ONLINE
|
||||||
|
|
||||||
currState = mod.state
|
currState = mod.state
|
||||||
@@ -920,6 +942,7 @@ class Fit(object):
|
|||||||
self.recalc(fit)
|
self.recalc(fit)
|
||||||
|
|
||||||
def recalc(self, fit, withBoosters=False):
|
def recalc(self, fit, withBoosters=False):
|
||||||
|
logger.debug("="*10+"recalc"+"="*10)
|
||||||
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
|
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
|
||||||
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
|
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
|
||||||
fit.clear()
|
fit.clear()
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class ShipBrowserWorkerThread(threading.Thread):
|
|||||||
class PriceWorkerThread(threading.Thread):
|
class PriceWorkerThread(threading.Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
self.queue = Queue.Queue()
|
self.queue = Queue.Queue()
|
||||||
|
self.wait = {}
|
||||||
self.processUpdates()
|
self.processUpdates()
|
||||||
|
|
||||||
def processUpdates(self):
|
def processUpdates(self):
|
||||||
@@ -86,9 +87,21 @@ class PriceWorkerThread(threading.Thread):
|
|||||||
wx.CallAfter(callback)
|
wx.CallAfter(callback)
|
||||||
queue.task_done()
|
queue.task_done()
|
||||||
|
|
||||||
|
# After we fetch prices, go through the list of waiting items and call their callbacks
|
||||||
|
for price in requests:
|
||||||
|
callbacks = self.wait.pop(price.typeID, None)
|
||||||
|
if callbacks:
|
||||||
|
for callback in callbacks:
|
||||||
|
wx.CallAfter(callback)
|
||||||
|
|
||||||
def trigger(self, prices, callbacks):
|
def trigger(self, prices, callbacks):
|
||||||
self.queue.put((callbacks, prices))
|
self.queue.put((callbacks, prices))
|
||||||
|
|
||||||
|
def setToWait(self, itemID, callback):
|
||||||
|
if itemID not in self.wait:
|
||||||
|
self.wait[itemID] = []
|
||||||
|
self.wait[itemID].append(callback)
|
||||||
|
|
||||||
class SearchWorkerThread(threading.Thread):
|
class SearchWorkerThread(threading.Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
self.cv = threading.Condition()
|
self.cv = threading.Condition()
|
||||||
@@ -218,6 +231,8 @@ class Market():
|
|||||||
"Mobile Decoy Unit": False, # Seems to be left over test mod for deployables
|
"Mobile Decoy Unit": False, # Seems to be left over test mod for deployables
|
||||||
"Tournament Micro Jump Unit": False, # Normally seen only on tournament arenas
|
"Tournament Micro Jump Unit": False, # Normally seen only on tournament arenas
|
||||||
"Council Diplomatic Shuttle": False, # CSM X celebration
|
"Council Diplomatic Shuttle": False, # CSM X celebration
|
||||||
|
"Imp": False, # AT13 prize, not a real ship yet
|
||||||
|
"Fiend": False, # AT13 prize, not a real ship yet
|
||||||
}
|
}
|
||||||
|
|
||||||
# do not publish ships that we convert
|
# do not publish ships that we convert
|
||||||
@@ -690,10 +705,6 @@ class Market():
|
|||||||
|
|
||||||
self.priceCache[typeID] = price
|
self.priceCache[typeID] = price
|
||||||
|
|
||||||
if not price.isValid:
|
|
||||||
# if the price has expired
|
|
||||||
price.price = None
|
|
||||||
|
|
||||||
return price
|
return price
|
||||||
|
|
||||||
def getPricesNow(self, typeIDs):
|
def getPricesNow(self, typeIDs):
|
||||||
@@ -716,6 +727,21 @@ class Market():
|
|||||||
|
|
||||||
self.priceWorkerThread.trigger(requests, cb)
|
self.priceWorkerThread.trigger(requests, cb)
|
||||||
|
|
||||||
|
def waitForPrice(self, item, callback):
|
||||||
|
"""
|
||||||
|
Wait for prices to be fetched and callback when finished. This is used with the column prices for modules.
|
||||||
|
Instead of calling them individually, we set them to wait until the entire fit price is called and calculated
|
||||||
|
(see GH #290)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def cb():
|
||||||
|
try:
|
||||||
|
callback(item)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.priceWorkerThread.setToWait(item.ID, cb)
|
||||||
|
|
||||||
def clearPriceCache(self):
|
def clearPriceCache(self):
|
||||||
self.priceCache.clear()
|
self.priceCache.clear()
|
||||||
deleted_rows = eos.db.clearPrices()
|
deleted_rows = eos.db.clearPrices()
|
||||||
|
|||||||
@@ -55,4 +55,4 @@ else:
|
|||||||
# If database does not exist, do not worry about migration. Simply
|
# If database does not exist, do not worry about migration. Simply
|
||||||
# create and set version
|
# create and set version
|
||||||
eos.db.saveddata_meta.create_all()
|
eos.db.saveddata_meta.create_all()
|
||||||
eos.db.saveddata_engine.execute('PRAGMA user_version = %d'%config.dbversion)
|
eos.db.saveddata_engine.execute('PRAGMA user_version = {}'.format(migration.getAppVersion()))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user