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
|
||||
|
||||
#### 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
|
||||
* [Development repository: http://github.com/DarkFenX/Pyfa](http://github.com/DarkFenX/Pyfa)
|
||||
* [XMPP conference:
|
||||
|
||||
73
config.py
73
config.py
@@ -1,6 +1,11 @@
|
||||
import os
|
||||
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
|
||||
try:
|
||||
import configforced
|
||||
@@ -12,16 +17,14 @@ debug = False
|
||||
# Defines if our saveddata will be in pyfa root or not
|
||||
saveInRoot = False
|
||||
|
||||
# Version data
|
||||
version = "1.11.1"
|
||||
tag = "git"
|
||||
expansionName = "Singularity"
|
||||
expansionVersion = "883859"
|
||||
evemonMinVersion = "4081"
|
||||
logLevel = logging.DEBUG
|
||||
|
||||
# Database version (int ONLY)
|
||||
# Increment every time we need to flag for user database upgrade/modification
|
||||
dbversion = 7
|
||||
# Version data
|
||||
version = "1.13.3"
|
||||
tag = "git"
|
||||
expansionName = "Aegis"
|
||||
expansionVersion = "1.0"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
pyfaPath = None
|
||||
savePath = None
|
||||
@@ -29,10 +32,24 @@ staticPath = None
|
||||
saveDB = 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)
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
class StreamToLogger(object):
|
||||
"""
|
||||
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():
|
||||
global pyfaPath
|
||||
@@ -59,19 +76,25 @@ def defPaths():
|
||||
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
|
||||
sys.getfilesystemencoding())
|
||||
|
||||
# Redirect stderr to file if we're requested to do so
|
||||
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")
|
||||
__createDirs(savePath)
|
||||
|
||||
# Same for stdout
|
||||
stdoutToFile = getattr(configforced, "stdoutToFile", None)
|
||||
if stdoutToFile is True:
|
||||
if not os.path.exists(savePath):
|
||||
os.mkdir(savePath)
|
||||
sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w")
|
||||
format = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
|
||||
logging.basicConfig(format=format, level=logLevel)
|
||||
handler = logging.handlers.RotatingFileHandler(os.path.join(savePath, "log.txt"), maxBytes=1000000, backupCount=3)
|
||||
formatter = logging.Formatter(format)
|
||||
handler.setFormatter(formatter)
|
||||
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
|
||||
# directory in our pyfa directory
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
import config
|
||||
import shutil
|
||||
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):
|
||||
cursor = db.execute('PRAGMA user_version')
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
def update(saveddata_engine):
|
||||
currversion = getVersion(saveddata_engine)
|
||||
dbVersion = getVersion(saveddata_engine)
|
||||
appVersion = getAppVersion()
|
||||
|
||||
if currversion == config.dbversion:
|
||||
if dbVersion == appVersion:
|
||||
return
|
||||
|
||||
if currversion < config.dbversion:
|
||||
if dbVersion < appVersion:
|
||||
# Automatically backup database
|
||||
toFile = "%s/saveddata_migration_%d-%d_%s.db"%(
|
||||
config.savePath,
|
||||
currversion,
|
||||
config.dbversion,
|
||||
dbVersion,
|
||||
appVersion,
|
||||
time.strftime("%Y%m%d_%H%M%S"))
|
||||
|
||||
shutil.copyfile(config.saveDB, toFile)
|
||||
|
||||
for version in xrange(currversion, config.dbversion):
|
||||
module = __import__('eos.db.migrations.upgrade%d'%(version+1), fromlist=True)
|
||||
for version in xrange(dbVersion, appVersion):
|
||||
module = __import__("eos.db.migrations.upgrade{}".format(version + 1), fromlist=True)
|
||||
upgrade = getattr(module, "upgrade", False)
|
||||
if upgrade:
|
||||
upgrade(saveddata_engine)
|
||||
|
||||
# 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("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
|
||||
Column("active", Boolean),
|
||||
UniqueConstraint("itemID", "fitID"))
|
||||
)
|
||||
|
||||
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
|
||||
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String, Boolean
|
||||
from sqlalchemy.orm import relation, mapper
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
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.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.implant import fitImplants_table
|
||||
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
|
||||
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
|
||||
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
|
||||
HandledProjectedFitList, HandledCargoList
|
||||
from eos.effectHandlerHelpers import *
|
||||
|
||||
fits_table = Table("fits", saveddata_meta,
|
||||
Column("ID", Integer, primary_key = True),
|
||||
@@ -47,31 +47,100 @@ fits_table = Table("fits", saveddata_meta,
|
||||
projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
Column("sourceID", 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,
|
||||
properties = {"_Fit__modules" : relation(Module, collection_class = HandledModuleList,
|
||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
||||
order_by = modules_table.c.position, cascade='all, delete, delete-orphan'),
|
||||
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
||||
"owner" : relation(User, backref = "fits"),
|
||||
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
|
||||
"_Fit__drones" : relation(Drone, collection_class = HandledDroneList, 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 = HandledCargoList, 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', 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),
|
||||
"_Fit__projectedFits" : relation(Fit,
|
||||
primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID,
|
||||
secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID,
|
||||
secondary = projectedFits_table,
|
||||
collection_class = HandledProjectedFitList)
|
||||
})
|
||||
properties = {
|
||||
"_Fit__modules": relation(
|
||||
Module,
|
||||
collection_class=HandledModuleList,
|
||||
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
||||
order_by=modules_table.c.position,
|
||||
cascade='all, delete, delete-orphan'),
|
||||
"_Fit__projectedModules": relation(
|
||||
Module,
|
||||
collection_class=HandledProjectedModList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True,
|
||||
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
||||
"owner": relation(
|
||||
User,
|
||||
backref="fits"),
|
||||
"itemID": fits_table.c.shipID,
|
||||
"shipID": fits_table.c.shipID,
|
||||
"_Fit__boosters": relation(
|
||||
Booster,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True),
|
||||
"_Fit__drones": relation(
|
||||
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()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
|
||||
if fit and fit.isInvalid:
|
||||
with sd_lock:
|
||||
removeInvalid([fit])
|
||||
return None
|
||||
|
||||
return fit
|
||||
|
||||
@cachedQuery(Fleet, 1, "fleetID")
|
||||
@@ -244,9 +250,10 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None):
|
||||
filter = processWhere(filter, where)
|
||||
eager = processEager(eager)
|
||||
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:
|
||||
raise TypeError("ShipID must be integer")
|
||||
|
||||
return fits
|
||||
|
||||
def getBoosterFits(ownerID=None, where=None, eager=None):
|
||||
@@ -264,7 +271,8 @@ def getBoosterFits(ownerID=None, where=None, eager=None):
|
||||
filter = processWhere(filter, where)
|
||||
eager = processEager(eager)
|
||||
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
|
||||
|
||||
def countAllFits():
|
||||
@@ -295,7 +303,8 @@ def countFitsWithShip(shipID, ownerID=None, where=None, eager=None):
|
||||
def getFitList(eager=None):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
fits = saveddata_session.query(Fit).options(*eager).all()
|
||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).all())
|
||||
|
||||
return fits
|
||||
|
||||
def getFleetList(eager=None):
|
||||
@@ -385,7 +394,8 @@ def searchFits(nameLike, where=None, eager=None):
|
||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||
eager = processEager(eager)
|
||||
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
|
||||
|
||||
def getSquadsIDsWithFitID(fitID):
|
||||
@@ -406,6 +416,16 @@ def getProjectedFits(fitID):
|
||||
else:
|
||||
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):
|
||||
with sd_lock:
|
||||
saveddata_session.add(stuff)
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
#from sqlalchemy.orm.attributes import flag_modified
|
||||
import eos.db
|
||||
import eos.types
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HandledList(list):
|
||||
def filteredItemPreAssign(self, filter, *args, **kwargs):
|
||||
@@ -101,6 +105,14 @@ class HandledList(list):
|
||||
except AttributeError:
|
||||
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):
|
||||
def append(self, mod):
|
||||
emptyPosition = float("Inf")
|
||||
@@ -115,10 +127,14 @@ class HandledModuleList(HandledList):
|
||||
del self[emptyPosition]
|
||||
mod.position = emptyPosition
|
||||
HandledList.insert(self, emptyPosition, mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
return
|
||||
|
||||
mod.position = len(self)
|
||||
HandledList.append(self, mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
|
||||
def insert(self, index, mod):
|
||||
mod.position = index
|
||||
@@ -149,133 +165,72 @@ class HandledModuleList(HandledList):
|
||||
if mod.getModifiedItemAttr("subSystemSlot") == slot:
|
||||
del self[i]
|
||||
|
||||
class HandledDroneList(HandledList):
|
||||
class HandledDroneCargoList(HandledList):
|
||||
def find(self, item):
|
||||
for d in self:
|
||||
if d.item == item:
|
||||
yield d
|
||||
for o in self:
|
||||
if o.item == item:
|
||||
yield o
|
||||
|
||||
def findFirst(self, item):
|
||||
for d in self.find(item):
|
||||
return d
|
||||
for o in self.find(item):
|
||||
return o
|
||||
|
||||
def append(self, drone):
|
||||
list.append(self, drone)
|
||||
def append(self, thing):
|
||||
HandledList.append(self, thing)
|
||||
|
||||
def remove(self, drone):
|
||||
HandledList.remove(self, drone)
|
||||
|
||||
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
|
||||
if thing.isInvalid:
|
||||
self.remove(thing)
|
||||
|
||||
class HandledImplantBoosterList(HandledList):
|
||||
def __init__(self):
|
||||
self.__slotCache = {}
|
||||
def append(self, thing):
|
||||
if thing.isInvalid:
|
||||
HandledList.append(self, thing)
|
||||
self.remove(thing)
|
||||
return
|
||||
|
||||
def append(self, implant):
|
||||
if self.__slotCache.has_key(implant.slot):
|
||||
raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True")
|
||||
self.__slotCache[implant.slot] = implant
|
||||
HandledList.append(self, implant)
|
||||
# if needed, remove booster that was occupying slot
|
||||
oldObj = next((m for m in self if m.slot == thing.slot), None)
|
||||
if oldObj:
|
||||
logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name)
|
||||
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
|
||||
self.remove(oldObj)
|
||||
|
||||
def remove(self, implant):
|
||||
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
|
||||
HandledList.append(self, thing)
|
||||
|
||||
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):
|
||||
proj.projected = True
|
||||
HandledList.append(self, proj)
|
||||
|
||||
class HandledProjectedDroneList(HandledDroneList):
|
||||
def append(self, proj):
|
||||
proj.projected = True
|
||||
list.append(self, proj)
|
||||
|
||||
class HandledProjectedFitList(HandledList):
|
||||
def append(self, proj):
|
||||
proj.projected = True
|
||||
list.append(self, proj)
|
||||
# Remove invalid or non-projectable drones
|
||||
if proj.isInvalid or not proj.item.isType("projected"):
|
||||
self.remove(proj)
|
||||
|
||||
class HandledItem(object):
|
||||
def preAssignItemAttr(self, *args, **kwargs):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceCapNeed
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (458 of 829)
|
||||
# Items from category: Charge (458 of 831)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
# Dirty hack to work around cap charges setting cap booster
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceRange
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (559 of 829)
|
||||
# Items from category: Charge (559 of 831)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
module.multiplyItemAttr("maxRange", module.getModifiedChargeAttr("weaponRangeMultiplier"))
|
||||
@@ -1,7 +1,7 @@
|
||||
# armorHPBonusAdd
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Armor Reinforcer (38 of 38)
|
||||
# Modules from group: Armor Reinforcer (41 of 41)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.increaseItemAttr("armorHP", module.getModifiedItemAttr("armorHPBonusAdd"))
|
||||
@@ -1,8 +1,7 @@
|
||||
# armorReinforcerMassAdd
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Armor Reinforcer (38 of 38)
|
||||
# Modules from group: Entosis Link (2 of 2)
|
||||
# Modules from group: Armor Reinforcer (41 of 41)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.increaseItemAttr("mass", module.getModifiedItemAttr("massAddition"))
|
||||
@@ -1,7 +1,7 @@
|
||||
# eliteBonusHeavyInterdictorsWarpDisruptFieldGeneratorWarpScrambleRange2
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Heavy Interdiction Cruiser (4 of 4)
|
||||
# Ships from group: Heavy Interdiction Cruiser (4 of 5)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Interceptor (5 of 9)
|
||||
# Ships from group: Interceptor (5 of 10)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Interceptors").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# interceptorMWDSignatureRadiusBonus
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Interceptor (9 of 9)
|
||||
# Ships from group: Interceptor (9 of 10)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
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"
|
||||
def handler(fit, container, context):
|
||||
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"),
|
||||
"aoeCloudSize", container.getModifiedItemAttr("aoeCloudSizeBonus") * level,
|
||||
stackingPenalties = False)
|
||||
stackingPenalties=penalize)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
type = "passive"
|
||||
def handler(fit, container, context):
|
||||
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"),
|
||||
"aoeVelocity", container.getModifiedItemAttr("aoeVelocityBonus") * level,
|
||||
stackingPenalties = "skill" not in context and "implant" not in context)
|
||||
stackingPenalties=penalize)
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
type = "passive"
|
||||
def handler(fit, container, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Propulsion Mode (3 of 3)
|
||||
# Modules named like: Propulsion Mode (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Module: Confessor Defense Mode
|
||||
# Module: Svipul Defense Mode
|
||||
# Modules named like: Defense Mode (3 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Propulsion Mode (3 of 3)
|
||||
# Modules named like: Propulsion Mode (3 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# OffensiveDefensiveReduction
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Incursion ship attributes effects (3 of 3)
|
||||
runTime = "early"
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Tactical Destroyer (3 of 3)
|
||||
# Ships from group: Tactical Destroyer (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
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:
|
||||
# Ship: Daredevil
|
||||
# Ship: Hecate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Interceptor (9 of 9)
|
||||
# Ships from group: Interceptor (9 of 10)
|
||||
# Ship: Atron
|
||||
# Ship: Condor
|
||||
# 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
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
||||
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipModeScanResPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
||||
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipModeScanStrengthPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Sharpshooter Mode (3 of 3)
|
||||
# Modules named like: Sharpshooter Mode (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemDamageEmMissiles
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemDamageExplosiveMissiles
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemDamageKineticMissiles
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemDamageMultiplierGunnery
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemDamageThermalMissiles
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Magnetar Effect Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Black Hole Effect Beacon Class (6 of 6)
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
def handler(fit, beacon, context):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemSmartBombEmDamage
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemSmartBombExplosiveDamage
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemSmartBombKineticDamage
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# systemSmartBombThermalDamage
|
||||
#
|
||||
# Used by:
|
||||
# Celestials named like: Drifter Incursion (6 of 6)
|
||||
# Celestials named like: Red Giant Beacon Class (6 of 6)
|
||||
runTime = "early"
|
||||
type = ("projected", "offline")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Used by:
|
||||
# Module: Triage Module I
|
||||
type = "active"
|
||||
runTime = "early"
|
||||
def handler(fit, module, context):
|
||||
# Remote armor reps
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Used by:
|
||||
# Module: Triage Module II
|
||||
type = "active"
|
||||
runTime = "early"
|
||||
def handler(fit, module, context):
|
||||
# Remote armor reps
|
||||
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.effectHandlerHelpers import HandledItem
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Booster(HandledItem, ItemAttrShortcut):
|
||||
def __init__(self, item):
|
||||
self.__slot = self.__calculateSlot(item)
|
||||
self.itemID = item.ID
|
||||
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.build()
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
"""Initialize a booster 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 Booser", self.itemID)
|
||||
return
|
||||
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__sideEffects = []
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__sideEffects = []
|
||||
for effect in self.item.effects.itervalues():
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
|
||||
for effect in self.__item.effects.itervalues():
|
||||
if effect.isType("boosterSideEffect"):
|
||||
s = SideEffect(self)
|
||||
s.effect = effect
|
||||
s.active = effect.ID in self.__activeSideEffectIDs
|
||||
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):
|
||||
return self.__sideEffects.__iter__()
|
||||
|
||||
@@ -62,23 +79,18 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
def isInvalid(self):
|
||||
return self.__item is None or self.__item.group.name != "Booster"
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
return self.__slot
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item
|
||||
|
||||
def __calculateSlot(self, item):
|
||||
|
||||
@@ -20,40 +20,45 @@
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
# Cargo class copied from Implant class and hacked to make work. \o/
|
||||
# @todo: clean me up, Scotty
|
||||
class Cargo(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Cargo(HandledItem, ItemAttrShortcut):
|
||||
|
||||
def __init__(self, item):
|
||||
"""Initialize cargo from the program"""
|
||||
self.__item = item
|
||||
self.itemID = item.ID
|
||||
self.itemID = item.ID if item is not None else None
|
||||
self.amount = 0
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
self.__itemModifiedAttributes.original = item.attributes
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
"""Initialize cargo from the database and validate"""
|
||||
self.__item = None
|
||||
|
||||
def __fetchItemInfo(self):
|
||||
import eos.db
|
||||
self.__item = eos.db.getItem(self.itemID)
|
||||
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
|
||||
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
def isInvalid(self):
|
||||
return self.__item is None
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
return self.__item
|
||||
|
||||
def clear(self):
|
||||
|
||||
@@ -192,7 +192,7 @@ class Character(object):
|
||||
map = {"ID": lambda val: isinstance(val, int),
|
||||
"name" : lambda val: True,
|
||||
"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)
|
||||
else: return val
|
||||
|
||||
@@ -20,81 +20,80 @@
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
|
||||
def __init__(self, item):
|
||||
if item.category.name != "Drone":
|
||||
raise ValueError("Passed item is not a drone")
|
||||
|
||||
"""Initialize a drone from the program"""
|
||||
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.amountActive = 0
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__miningyield = None
|
||||
self.projected = False
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.itemModifiedAttributes.original = self.item.attributes
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
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.__volley = 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.original = self.item.attributes
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
|
||||
def __fetchChargeInfo(self):
|
||||
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
|
||||
if chargeID is not None:
|
||||
import eos.db
|
||||
charge = eos.db.getItem(int(chargeID))
|
||||
self.__charge = charge
|
||||
|
||||
self.chargeModifiedAttributes.original = charge.attributes
|
||||
else:
|
||||
self.__charge = 0
|
||||
self.__chargeModifiedAttributes.original = charge.attributes
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def chargeModifiedAttributes(self):
|
||||
if self.__charge is None:
|
||||
self.__fetchChargeInfo()
|
||||
|
||||
return self.__chargeModifiedAttributes
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
def isInvalid(self):
|
||||
return self.__item is None or self.__item.category.name != "Drone"
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
return self.__item
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
if self.__charge is None:
|
||||
self.__fetchChargeInfo()
|
||||
|
||||
return self.__charge if self.__charge != 0 else None
|
||||
return self.__charge
|
||||
|
||||
@property
|
||||
def dealsDamage(self):
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \
|
||||
HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList
|
||||
from eos.effectHandlerHelpers import *
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
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.saveddata.module import State
|
||||
from eos.saveddata.mode import Mode
|
||||
import eos.db
|
||||
import time
|
||||
import copy
|
||||
from utils.timer import Timer
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
@@ -48,34 +54,57 @@ class Fit(object):
|
||||
|
||||
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.__drones = HandledDroneList()
|
||||
self.__cargo = HandledCargoList()
|
||||
self.__drones = HandledDroneCargoList()
|
||||
self.__cargo = HandledDroneCargoList()
|
||||
self.__implants = HandledImplantBoosterList()
|
||||
self.__boosters = HandledImplantBoosterList()
|
||||
self.__projectedFits = HandledProjectedFitList()
|
||||
#self.__projectedFits = {}
|
||||
self.__projectedModules = HandledProjectedModList()
|
||||
self.__projectedDrones = HandledProjectedDroneList()
|
||||
self.__character = None
|
||||
self.__owner = None
|
||||
self.shipID = None
|
||||
|
||||
self.projected = False
|
||||
self.name = ""
|
||||
self.fleet = None
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
self.name = name
|
||||
self.timestamp = time.time()
|
||||
self.ecmProjectedStr = 1
|
||||
self.modeID = None
|
||||
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
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()
|
||||
|
||||
def build(self):
|
||||
from eos import db
|
||||
self.__extraDrains = []
|
||||
self.__ehp = None
|
||||
self.__weaponDPS = None
|
||||
@@ -100,11 +129,6 @@ class Fit(object):
|
||||
self.ecmProjectedStr = 1
|
||||
self.extraAttributes = ModifiedAttributeDict(self)
|
||||
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
|
||||
def targetResists(self):
|
||||
@@ -128,13 +152,17 @@ class Fit(object):
|
||||
self.__ehp = None
|
||||
self.__effectiveTank = None
|
||||
|
||||
@property
|
||||
def isInvalid(self):
|
||||
return self.__ship is None
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
return self._mode
|
||||
return self.__mode
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode):
|
||||
self._mode = mode
|
||||
self.__mode = mode
|
||||
self.modeID = mode.item.ID if mode is not None else None
|
||||
|
||||
@property
|
||||
@@ -154,7 +182,7 @@ class Fit(object):
|
||||
self.__ship = ship
|
||||
self.shipID = ship.item.ID if ship is not None else None
|
||||
# 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
|
||||
def drones(self):
|
||||
@@ -182,7 +210,12 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
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
|
||||
def projectedDrones(self):
|
||||
@@ -295,13 +328,13 @@ class Fit(object):
|
||||
@validates("ID", "ownerID", "shipID")
|
||||
def validator(self, key, val):
|
||||
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}
|
||||
|
||||
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
||||
else: return val
|
||||
|
||||
def clear(self):
|
||||
def clear(self, projected=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDPS = None
|
||||
self.__minerYield = None
|
||||
@@ -321,10 +354,30 @@ class Fit(object):
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
if self.ship is not None: self.ship.clear()
|
||||
c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
|
||||
if self.ship:
|
||||
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:
|
||||
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,
|
||||
#so we can correctly map "Affected By"
|
||||
@@ -338,7 +391,48 @@ class Fit(object):
|
||||
def getModifier(self):
|
||||
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):
|
||||
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
|
||||
if withBoosters is True:
|
||||
refreshBoosts = True
|
||||
@@ -347,6 +441,7 @@ class Fit(object):
|
||||
if dirtyStorage is not None:
|
||||
dirtyStorage.update(self.boostsFits)
|
||||
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)
|
||||
elif self.fleet is None:
|
||||
self.gangBoosts = None
|
||||
@@ -355,80 +450,64 @@ class Fit(object):
|
||||
dirtyStorage.remove(self.ID)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# If we're not explicitly asked to project fit onto something,
|
||||
# set self as target fit
|
||||
if targetFit is None:
|
||||
targetFit = self
|
||||
forceProjected = 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
|
||||
projected = False
|
||||
else:
|
||||
return
|
||||
projected = True
|
||||
|
||||
# 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
|
||||
|
||||
# Mark fit as calculated
|
||||
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"):
|
||||
# Build a little chain of stuff
|
||||
# Avoid adding projected drones and modules when fit is projected onto self
|
||||
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
|
||||
if forceProjected is True:
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules)
|
||||
else:
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
self.projectedDrones, self.projectedModules)
|
||||
c = chain(
|
||||
(self.character, self.ship),
|
||||
self.drones,
|
||||
self.boosters,
|
||||
self.appliedImplants,
|
||||
self.modules
|
||||
)
|
||||
|
||||
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:
|
||||
contextMap = {Skill: "skill",
|
||||
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
|
||||
self.__calculateGangBoosts(runTime)
|
||||
|
||||
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:
|
||||
self.register(item)
|
||||
item.calculateModifiedAttributes(self, runTime, False)
|
||||
if forceProjected is True:
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
if projected is True:
|
||||
for _ in xrange(projectionInfo.amount):
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
|
||||
for fit in self.projectedFits:
|
||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
timer.checkpoint('Done with runtime: %s'%runTime)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
@@ -901,6 +980,9 @@ class Fit(object):
|
||||
c.append(deepcopy(i, memo))
|
||||
|
||||
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
|
||||
|
||||
@@ -20,46 +20,58 @@
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Implant(HandledItem, ItemAttrShortcut):
|
||||
def __init__(self, item):
|
||||
self.__slot = self.__calculateSlot(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.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__item = None
|
||||
|
||||
def __fetchItemInfo(self):
|
||||
import eos.db
|
||||
self.__item = eos.db.getItem(self.itemID)
|
||||
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 an Implant", self.itemID)
|
||||
return
|
||||
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
def isInvalid(self):
|
||||
return self.__item is None or self.__item.category.name != "Implant"
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
return self.__slot
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item
|
||||
|
||||
def __calculateSlot(self, item):
|
||||
|
||||
@@ -19,36 +19,24 @@
|
||||
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
import eos.db
|
||||
|
||||
class Mode(ItemAttrShortcut, HandledItem):
|
||||
|
||||
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.__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
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
# @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.enum import Enum
|
||||
from eos.mathUtils import floorFloat
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class State(Enum):
|
||||
OFFLINE = -1
|
||||
@@ -49,95 +53,78 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
MINING_ATTRIBUTES = ("miningAmount", )
|
||||
|
||||
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.__charge = 0
|
||||
self.projected = False
|
||||
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.__miningyield = None
|
||||
self.__volley = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
self.__hardpoint = Hardpoint.NONE
|
||||
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.__slot = self.dummySlot # defaults to None
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
if self.dummySlot is None:
|
||||
self.__item = None
|
||||
self.__charge = None
|
||||
self.__volley = None
|
||||
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
|
||||
if self.__item:
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
if self.__charge:
|
||||
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
||||
|
||||
@classmethod
|
||||
def buildEmpty(cls, slot):
|
||||
empty = Module(None)
|
||||
empty.__slot = slot
|
||||
empty.__hardpoint = Hardpoint.NONE
|
||||
empty.__item = 0
|
||||
empty.__charge = 0
|
||||
empty.dummySlot = slot
|
||||
empty.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
|
||||
return empty
|
||||
|
||||
@classmethod
|
||||
def buildRack(cls, slot):
|
||||
empty = Rack(None)
|
||||
empty.__slot = slot
|
||||
empty.__hardpoint = Hardpoint.NONE
|
||||
empty.__item = 0
|
||||
empty.__charge = 0
|
||||
empty.dummySlot = slot
|
||||
empty.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
|
||||
return empty
|
||||
|
||||
@property
|
||||
@@ -146,11 +133,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def hardpoint(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
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
|
||||
def numCharges(self):
|
||||
if self.charge is None:
|
||||
@@ -263,38 +253,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def slot(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__slot
|
||||
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def chargeModifiedAttributes(self):
|
||||
if self.__charge is None:
|
||||
self.__fetchChargeInfo()
|
||||
|
||||
return self.__chargeModifiedAttributes
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self.__item is None:
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item if self.__item != 0 else None
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
if self.__charge is None:
|
||||
self.__fetchChargeInfo()
|
||||
|
||||
return self.__charge if self.__charge != 0 else None
|
||||
|
||||
@charge.setter
|
||||
@@ -516,7 +491,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
def getValidCharges(self):
|
||||
validCharges = set()
|
||||
import eos.db
|
||||
for i in range(5):
|
||||
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
|
||||
if itemChargeGroup is not None:
|
||||
|
||||
@@ -26,6 +26,7 @@ class Price(object):
|
||||
def __init__(self, typeID):
|
||||
self.typeID = typeID
|
||||
self.time = 0
|
||||
self.price = 0
|
||||
self.failed = None
|
||||
self.__item = None
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
from eos.saveddata.mode import Mode
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Ship(ItemAttrShortcut, HandledItem):
|
||||
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))
|
||||
|
||||
self.__item = item
|
||||
self.__modeItems = self.__getModeItems()
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__modeItems = self._getModeItems()
|
||||
if not isinstance(item, int):
|
||||
self.__buildOriginal()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
|
||||
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
|
||||
def item(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
def clear(self):
|
||||
@@ -67,25 +56,16 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
if effect.runTime == runTime and effect.isType("passive"):
|
||||
effect.handler(fit, self, ("ship",))
|
||||
|
||||
def checkModeItem(self, item):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
def validateModeItem(self, item):
|
||||
""" Checks if provided item is a valid mode """
|
||||
items = self.__modeItems
|
||||
|
||||
if items != None:
|
||||
if item == None or item not in items:
|
||||
# We have a tact dessy, but mode is None or not valid. Force new mode
|
||||
if items is not None:
|
||||
# if we have items, then we are in a tactical destroyer and must have a mode
|
||||
if item is None or item not in items:
|
||||
# If provided item is invalid mode, force new one
|
||||
return Mode(items[0])
|
||||
elif item in items:
|
||||
# We have a valid mode
|
||||
return Mode(item)
|
||||
return Mode(item)
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -96,21 +76,17 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
def modes(self):
|
||||
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
|
||||
valid Item objects, not the Mode objects. Returns None if not a
|
||||
t3 dessy
|
||||
"""
|
||||
# @todo: is there a better way to determine this that isn't hardcoded groupIDs?
|
||||
if self.item.groupID != 1305:
|
||||
if self.item.group.name != "Tactical Destroyer":
|
||||
return None
|
||||
|
||||
modeGroupID = 1306
|
||||
import eos.db
|
||||
|
||||
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:
|
||||
# Rely on name detection because race is not reliable
|
||||
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)
|
||||
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):
|
||||
if location in locationMap:
|
||||
if location == "pack":
|
||||
location = locationMap[location]
|
||||
name = stripPath(name)
|
||||
filename = "icon{0}.png".format(name)
|
||||
path = os.path.join(location, filename)
|
||||
else:
|
||||
|
||||
@@ -17,5 +17,6 @@ __all__ = [
|
||||
#"changeAffectingSkills",
|
||||
"tacticalMode",
|
||||
"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))
|
||||
|
||||
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 )
|
||||
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 )
|
||||
|
||||
self.sFit = service.Fit.getInstance()
|
||||
@@ -69,6 +72,7 @@ class PFGeneralPref ( PreferenceView):
|
||||
self.cbCompactSkills.SetValue(self.sFit.serviceFittingOptions["compactSkills"] or False)
|
||||
self.cbReopenFits.SetValue(self.openFitsSettings["enabled"])
|
||||
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.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
||||
@@ -79,6 +83,7 @@ class PFGeneralPref ( PreferenceView):
|
||||
self.cbCompactSkills.Bind(wx.EVT_CHECKBOX, self.onCBCompactSkills)
|
||||
self.cbReopenFits.Bind(wx.EVT_CHECKBOX, self.onCBReopenFits)
|
||||
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)
|
||||
|
||||
@@ -135,6 +140,9 @@ class PFGeneralPref ( PreferenceView):
|
||||
def onCBShowTooltip(self, event):
|
||||
self.sFit.serviceFittingOptions["showTooltip"] = self.cbShowTooltip.GetValue()
|
||||
|
||||
def onCBShowShortcuts(self, event):
|
||||
self.sFit.serviceFittingOptions["showMarketShortcuts"] = self.cbMarketShortcuts.GetValue()
|
||||
|
||||
def getImage(self):
|
||||
return bitmapLoader.getBitmap("prefs_settings", "icons")
|
||||
|
||||
|
||||
@@ -30,37 +30,13 @@ class PriceViewFull(StatsView):
|
||||
def __init__(self, parent):
|
||||
StatsView.__init__(self)
|
||||
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._cachedFittings = 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):
|
||||
return "Price"
|
||||
|
||||
def getTextExtentW(self, text):
|
||||
width, height = self.parent.GetTextExtent(text)
|
||||
return width
|
||||
|
||||
def populatePanel(self, contentPanel, headerPanel):
|
||||
contentSizer = contentPanel.GetSizer()
|
||||
self.panel = contentPanel
|
||||
@@ -111,22 +87,11 @@ class PriceViewFull(StatsView):
|
||||
for cargo in fit.cargo:
|
||||
for _ in xrange(cargo.amount):
|
||||
typeIDs.append(cargo.itemID)
|
||||
if self._timer:
|
||||
if self._timer.IsRunning():
|
||||
self._timer.Stop()
|
||||
|
||||
sMkt = service.Market.getInstance()
|
||||
sMkt.getPrices(typeIDs, self.processPrices)
|
||||
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:
|
||||
if self._timer:
|
||||
if self._timer.IsRunning():
|
||||
self._timer.Stop()
|
||||
self.labelEMStatus.SetLabel("")
|
||||
self.labelPriceShip.SetLabel("0.0 ISK")
|
||||
self.labelPriceFittings.SetLabel("0.0 ISK")
|
||||
@@ -136,20 +101,10 @@ class PriceViewFull(StatsView):
|
||||
|
||||
def processPrices(self, prices):
|
||||
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:]))
|
||||
|
||||
self.labelEMStatus.SetLabel("")
|
||||
|
||||
if self._cachedShip != shipPrice:
|
||||
self.labelPriceShip.SetLabel("%s ISK" % formatAmount(shipPrice, 3, 3, 9, currency=True))
|
||||
self.labelPriceShip.SetToolTip(wx.ToolTip(locale.format('%.2f', shipPrice, 1)))
|
||||
|
||||
@@ -197,8 +197,9 @@ class ResistancesViewFull(StatsView):
|
||||
lbl = getattr(self, "labelResistance%sEhp" % tankType.capitalize())
|
||||
if ehp is not None:
|
||||
total += ehp[tankType]
|
||||
rrFactor = fit.ehp[tankType] / fit.hp[tankType]
|
||||
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:
|
||||
lbl.SetLabel("0")
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
from gui import builtinViewColumns
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
import gui.mainFrame
|
||||
|
||||
import wx
|
||||
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
|
||||
import service
|
||||
@@ -29,6 +31,7 @@ class BaseName(ViewColumn):
|
||||
name = "Base Name"
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.columnText = "Name"
|
||||
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons")
|
||||
self.mask = wx.LIST_MASK_TEXT
|
||||
@@ -39,7 +42,8 @@ class BaseName(ViewColumn):
|
||||
elif isinstance(stuff, Cargo):
|
||||
return "%dx %s" % (stuff.amount, stuff.item.name)
|
||||
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):
|
||||
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
|
||||
if stuff.slot == Slot.MODE:
|
||||
@@ -55,6 +59,15 @@ class BaseName(ViewColumn):
|
||||
return stuff.item.name
|
||||
else:
|
||||
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
|
||||
|
||||
BaseName.register()
|
||||
|
||||
@@ -33,13 +33,13 @@ class Price(ViewColumn):
|
||||
self.imageId = fittingView.imageList.GetImageIndex("totalPrice_small", "icons")
|
||||
|
||||
def getText(self, stuff):
|
||||
if stuff.item is None:
|
||||
if stuff.item is None or stuff.item.group.name == "Ship Modifiers":
|
||||
return ""
|
||||
|
||||
sMkt = service.Market.getInstance()
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
def delayedText(self, mod, display, colItem):
|
||||
def callback(requests):
|
||||
price = requests[0].price
|
||||
colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "")
|
||||
sMkt = service.Market.getInstance()
|
||||
def callback(item):
|
||||
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)
|
||||
|
||||
service.Market.getInstance().getPrices([mod.item.ID], callback)
|
||||
|
||||
sMkt.waitForPrice(mod.item, callback)
|
||||
|
||||
def getImageId(self, mod):
|
||||
return -1
|
||||
|
||||
@@ -19,27 +19,21 @@
|
||||
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
import gui.mainFrame
|
||||
|
||||
import wx
|
||||
from eos.types import Drone, Module, Rack
|
||||
from eos.types import Drone, Module, Rack, Fit
|
||||
from eos.types import State as State_
|
||||
|
||||
class State(ViewColumn):
|
||||
name = "State"
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.resizable = False
|
||||
self.size = 16
|
||||
self.maxsize = self.size
|
||||
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):
|
||||
return ""
|
||||
@@ -49,8 +43,14 @@ class State(ViewColumn):
|
||||
return State_.getName(mod.state).title()
|
||||
|
||||
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):
|
||||
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):
|
||||
return -1
|
||||
elif isinstance(stuff, Module):
|
||||
@@ -58,11 +58,21 @@ class State(ViewColumn):
|
||||
return -1
|
||||
else:
|
||||
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:
|
||||
active = getattr(stuff, "active", None)
|
||||
if active is None:
|
||||
return -1
|
||||
else:
|
||||
return self.checkedId if active else self.uncheckedId
|
||||
if active:
|
||||
return generic_active
|
||||
return generic_inactive
|
||||
|
||||
State.register()
|
||||
|
||||
@@ -91,10 +91,10 @@ class CharacterEditor(wx.Frame):
|
||||
self.viewsNBContainer = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
|
||||
self.sview = SkillTreeView(self.viewsNBContainer)
|
||||
self.iview = ImplantsTreeView(self.viewsNBContainer)
|
||||
#self.iview = ImplantsTreeView(self.viewsNBContainer)
|
||||
#=======================================================================
|
||||
# RC2
|
||||
self.iview.Show(False)
|
||||
#self.iview.Show(False)
|
||||
#=======================================================================
|
||||
self.aview = APIView(self.viewsNBContainer)
|
||||
|
||||
@@ -147,6 +147,7 @@ class CharacterEditor(wx.Frame):
|
||||
def restrict(self):
|
||||
self.btnRename.Enable(False)
|
||||
self.btnDelete.Enable(False)
|
||||
self.aview.stDisabledTip.Show(True)
|
||||
self.aview.inputID.Enable(False)
|
||||
self.aview.inputKey.Enable(False)
|
||||
self.aview.charChoice.Enable(False)
|
||||
@@ -158,6 +159,7 @@ class CharacterEditor(wx.Frame):
|
||||
def unrestrict(self):
|
||||
self.btnRename.Enable(True)
|
||||
self.btnDelete.Enable(True)
|
||||
self.aview.stDisabledTip.Show(False)
|
||||
self.aview.inputID.Enable(True)
|
||||
self.aview.inputKey.Enable(True)
|
||||
self.aview.btnFetchCharList.Enable(True)
|
||||
@@ -272,6 +274,7 @@ class CharacterEditor(wx.Frame):
|
||||
class SkillTreeView (wx.Panel):
|
||||
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)
|
||||
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
|
||||
|
||||
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -531,12 +534,23 @@ class APIView (wx.Panel):
|
||||
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)
|
||||
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.apiUrlKeyList = u"https://community.eveonline.com/support/api-key/"
|
||||
|
||||
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.AddGrowableCol(1)
|
||||
fgSizerInput.SetFlexibleDirection(wx.BOTH)
|
||||
|
||||
@@ -250,7 +250,7 @@ class Display(wx.ListCtrl):
|
||||
newText = col.getText(st)
|
||||
if newText is False:
|
||||
col.delayedText(st, self, colItem)
|
||||
newText = ""
|
||||
newText = u"\u21bb"
|
||||
|
||||
newImageId = col.getImageId(st)
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ class DroneViewDrop(wx.PyDropTarget):
|
||||
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
self.dropFn(x, y, int(self.dropData.GetText()))
|
||||
data = self.dropData.GetText().split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
class DroneView(d.Display):
|
||||
@@ -72,7 +73,7 @@ class DroneView(d.Display):
|
||||
|
||||
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
self.SetDropTarget(DroneViewDrop(self.mergeDrones))
|
||||
self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
|
||||
|
||||
def OnLeaveWindow(self, event):
|
||||
self.SetToolTip(None)
|
||||
@@ -117,17 +118,27 @@ class DroneView(d.Display):
|
||||
row = event.GetIndex()
|
||||
if row != -1:
|
||||
data = wx.PyTextDataObject()
|
||||
data.SetText(str(self.GetItemData(row)))
|
||||
data.SetText("drone:"+str(row))
|
||||
|
||||
dropSource = wx.DropSource(self)
|
||||
dropSource.SetData(data)
|
||||
res = dropSource.DoDragDrop()
|
||||
|
||||
def mergeDrones(self, x, y, itemID):
|
||||
srcRow = self.FindItemData(-1,itemID)
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
if srcRow != -1 and dstRow != -1:
|
||||
self._merge(srcRow, dstRow)
|
||||
def handleDragDrop(self, x, y, data):
|
||||
'''
|
||||
Handles dragging of items from various pyfa displays which support it
|
||||
|
||||
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):
|
||||
sFit = service.Fit.getInstance()
|
||||
|
||||
@@ -39,7 +39,7 @@ import gui.globalEvents as GE
|
||||
from gui import bitmapLoader
|
||||
from gui.mainMenuBar import MainMenuBar
|
||||
from gui.additionsPane import AdditionsPane
|
||||
from gui.marketBrowser import MarketBrowser
|
||||
from gui.marketBrowser import MarketBrowser, ItemSelected
|
||||
from gui.multiSwitch import MultiSwitch
|
||||
from gui.statsPane import StatsPane
|
||||
from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected
|
||||
@@ -146,6 +146,7 @@ class MainFrame(wx.Frame):
|
||||
|
||||
self.marketBrowser = MarketBrowser(self.notebookBrowsers)
|
||||
self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False)
|
||||
self.marketBrowser.splitter.SetSashPosition(self.marketHeight)
|
||||
|
||||
self.shipBrowser = ShipBrowser(self.notebookBrowsers)
|
||||
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.SetMinimumPaneSize(204)
|
||||
self.splitter.SetSashPosition(300)
|
||||
self.splitter.SetSashPosition(self.browserWidth)
|
||||
|
||||
cstatsSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -227,7 +228,7 @@ class MainFrame(wx.Frame):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if self.mainFrameAttribs["wnd_maximized"]:
|
||||
@@ -241,6 +242,9 @@ class MainFrame(wx.Frame):
|
||||
self.SetSize((width, height))
|
||||
self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"]))
|
||||
|
||||
self.browserWidth = self.mainFrameAttribs["browser_width"]
|
||||
self.marketHeight = self.mainFrameAttribs["market_height"]
|
||||
|
||||
def UpdateMainFrameAttribs(self):
|
||||
if self.IsIconized():
|
||||
return
|
||||
@@ -250,6 +254,9 @@ class MainFrame(wx.Frame):
|
||||
self.mainFrameAttribs["wnd_height"] = height
|
||||
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):
|
||||
self.activeStatsWnd = wnd
|
||||
|
||||
@@ -423,12 +430,6 @@ class MainFrame(wx.Frame):
|
||||
ctabnext = 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
|
||||
self.Bind(wx.EVT_MENU, self.CloseCurrentPage, id=self.closePageId)
|
||||
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.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),
|
||||
(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.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_PAGEUP, ctabprev),
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
||||
(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)
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev)
|
||||
]
|
||||
|
||||
# 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)
|
||||
self.SetAcceleratorTable(atable)
|
||||
|
||||
def AdditionsTabSelect(self, event):
|
||||
selTab = None
|
||||
if event.GetId() == self.additionstab1:
|
||||
selTab = 0
|
||||
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:
|
||||
selTab = self.additionsSelect.index(event.GetId())
|
||||
|
||||
if selTab <= self.additionsPane.notebook.GetPageCount():
|
||||
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):
|
||||
self.fitMultiSwitch.NextPage()
|
||||
|
||||
|
||||
@@ -438,6 +438,11 @@ class ItemView(d.Display):
|
||||
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
||||
# Re-sort stuff
|
||||
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)
|
||||
|
||||
def makeReverseMetaMap(self):
|
||||
|
||||
@@ -342,7 +342,6 @@ class ResistsEditorDlg(wx.Dialog):
|
||||
self.patternChanged()
|
||||
|
||||
def showInput(self, bool):
|
||||
print self.namePicker.IsShown(), bool
|
||||
if bool and not self.namePicker.IsShown():
|
||||
self.ccResists.Hide()
|
||||
self.namePicker.Show()
|
||||
|
||||
@@ -939,11 +939,12 @@ class ShipBrowser(wx.Panel):
|
||||
class PFStaticText(wx.Panel):
|
||||
def __init__(self, parent, label=wx.EmptyString):
|
||||
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)
|
||||
text = wx.StaticText( self, wx.ID_ANY, label, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE )
|
||||
text.Wrap( -1 )
|
||||
mainSizer.Add( text, 1, wx.EXPAND|wx.TOP, 10 )
|
||||
mainSizer.Add( text, 1, wx.ALL, 10 )
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
def GetType(self):
|
||||
@@ -1426,9 +1427,6 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
|
||||
self.deleted = False
|
||||
|
||||
# @todo: replace all getActiveFit() in class with this variable and test
|
||||
self.activeFit = self.mainFrame.getActiveFit()
|
||||
|
||||
if shipID:
|
||||
self.shipBmp = bitmapLoader.getBitmap(str(shipID),"ships")
|
||||
|
||||
@@ -1441,19 +1439,6 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
# see GH issue #62
|
||||
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.copyBmp = bitmapLoader.getBitmap("fit_add_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)
|
||||
|
||||
def OnPopupItemSelected(self, event):
|
||||
''' Fires when fit menu item is selected '''
|
||||
# currently only have one menu option (toggle booster)
|
||||
def OnToggleBooster(self, event):
|
||||
sFit = service.Fit.getInstance()
|
||||
sFit.toggleBoostFit(self.fitID)
|
||||
self.fitBooster = not self.fitBooster
|
||||
self.boosterBtn.Show(self.fitBooster)
|
||||
self.fitMenu.Check(self.toggleItem.GetId(), self.fitBooster)
|
||||
self.Refresh()
|
||||
wx.PostEvent(self.mainFrame, BoosterListUpdated())
|
||||
event.Skip()
|
||||
|
||||
@@ -1545,9 +1528,23 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
''' Handles context menu for fit. Dragging is handled by MouseLeftUp() '''
|
||||
pos = wx.GetMousePosition()
|
||||
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
|
||||
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()
|
||||
|
||||
|
||||
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
|
||||
#======================================================================
|
||||
# Copyright (C) 2012 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 3 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/>.
|
||||
#======================================================================
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add eos root path to sys.path so we can import ourselves
|
||||
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||
sys.path.append(os.path.realpath(os.path.join(path, "..")))
|
||||
|
||||
import json
|
||||
import argparse
|
||||
|
||||
def main(db, json_path):
|
||||
|
||||
jsonPath = os.path.expanduser(json_path)
|
||||
|
||||
# Import eos.config first and change it
|
||||
import eos.config
|
||||
eos.config.gamedata_connectionstring = db
|
||||
eos.config.debug = False
|
||||
|
||||
# Now thats done, we can import the eos modules using the config
|
||||
import eos.db
|
||||
import eos.gamedata
|
||||
|
||||
# Create the database tables
|
||||
eos.db.gamedata_meta.create_all()
|
||||
|
||||
# Config dict
|
||||
tables = {
|
||||
"dgmattribs": eos.gamedata.AttributeInfo,
|
||||
"dgmeffects": eos.gamedata.EffectInfo,
|
||||
"dgmtypeattribs": eos.gamedata.Attribute,
|
||||
"dgmtypeeffects": eos.gamedata.Effect,
|
||||
"dgmunits": eos.gamedata.Unit,
|
||||
"icons": eos.gamedata.Icon,
|
||||
"invcategories": eos.gamedata.Category,
|
||||
"invgroups": eos.gamedata.Group,
|
||||
"invmetagroups": eos.gamedata.MetaGroup,
|
||||
"invmetatypes": eos.gamedata.MetaType,
|
||||
"invtypes": eos.gamedata.Item,
|
||||
"phbtraits": eos.gamedata.Traits,
|
||||
"phbmetadata": eos.gamedata.MetaData,
|
||||
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
||||
}
|
||||
|
||||
fieldMapping = {
|
||||
"dgmattribs": {
|
||||
"displayName_en-us": "displayName"
|
||||
},
|
||||
"dgmeffects": {
|
||||
"displayName_en-us": "displayName",
|
||||
"description_en-us": "description"
|
||||
},
|
||||
"dgmunits": {
|
||||
"displayName_en-us": "displayName"
|
||||
},
|
||||
#icons???
|
||||
"invcategories": {
|
||||
"categoryName_en-us": "categoryName"
|
||||
},
|
||||
"invgroups": {
|
||||
"groupName_en-us": "groupName"
|
||||
},
|
||||
"invmetagroups": {
|
||||
"metaGroupName_en-us": "metaGroupName"
|
||||
},
|
||||
"invtypes": {
|
||||
"typeName_en-us": "typeName",
|
||||
"description_en-us": "description"
|
||||
},
|
||||
#phbtraits???
|
||||
"mapbulk_marketGroups": {
|
||||
"marketGroupName_en-us": "marketGroupName",
|
||||
"description_en-us": "description"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
for k, v in data.items():
|
||||
v["iconID"] = k
|
||||
new.append(v)
|
||||
return new
|
||||
|
||||
def convertTraits(data):
|
||||
|
||||
def convertSection(sectionData):
|
||||
sectionLines = []
|
||||
headerText = u"<b>{}</b>".format(sectionData["header"])
|
||||
sectionLines.append(headerText)
|
||||
for bonusData in sectionData["bonuses"]:
|
||||
prefix = u"{} ".format(bonusData["number"]) if "number" in bonusData else ""
|
||||
bonusText = u"{}{}".format(prefix, bonusData["text"].replace(u"\u00B7", u"\u2022 "))
|
||||
sectionLines.append(bonusText)
|
||||
sectionLine = u"<br />\n".join(sectionLines)
|
||||
return sectionLine
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
typeLines = []
|
||||
typeId = row["typeID"]
|
||||
traitData = row["traits_en-us"]
|
||||
for skillData in sorted(traitData.get("skills", ()), key=lambda i: i["header"]):
|
||||
typeLines.append(convertSection(skillData))
|
||||
if "role" in traitData:
|
||||
typeLines.append(convertSection(traitData["role"]))
|
||||
if "misc" in traitData:
|
||||
typeLines.append(convertSection(traitData["misc"]))
|
||||
traitLine = u"<br />\n<br />\n".join(typeLines)
|
||||
newRow = {"typeID": typeId, "traitText": traitLine}
|
||||
newData.append(newRow)
|
||||
return newData
|
||||
|
||||
def convertTypes(typesData):
|
||||
"""
|
||||
Add factionID column to invtypes table.
|
||||
"""
|
||||
factionMap = {}
|
||||
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
||||
overridesData = json.load(f)
|
||||
for typeID, typeData in overridesData.items():
|
||||
factionID = typeData.get("factionID")
|
||||
if factionID is not None:
|
||||
factionMap[int(typeID)] = factionID
|
||||
for row in typesData:
|
||||
row['factionID'] = factionMap.get(int(row['typeID']))
|
||||
return typesData
|
||||
|
||||
data = {}
|
||||
|
||||
# Dump all data to memory so we can easely cross check ignored rows
|
||||
for jsonName, cls in tables.iteritems():
|
||||
with open(os.path.join(jsonPath, "{}.json".format(jsonName))) as f:
|
||||
tableData = json.load(f)
|
||||
if jsonName == "icons":
|
||||
tableData = convertIcons(tableData)
|
||||
if jsonName == "phbtraits":
|
||||
tableData = convertTraits(tableData)
|
||||
if jsonName == "invtypes":
|
||||
tableData = convertTypes(tableData)
|
||||
data[jsonName] = tableData
|
||||
|
||||
# Set with typeIDs which we will have in our database
|
||||
invTypes = set()
|
||||
for row in data["invtypes"]:
|
||||
# 1306 - group Ship Modifiers, for items like tactical t3 ship modes
|
||||
if (row["published"] or row['groupID'] == 1306):
|
||||
invTypes.add(row["typeID"])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file in ("invtypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in invTypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Loop through each json file and write it away, checking ignored rows
|
||||
for jsonName, table in data.iteritems():
|
||||
fieldMap = fieldMapping.get(jsonName, {})
|
||||
print "processing {}".format(jsonName)
|
||||
for row in table:
|
||||
# We don't care about some kind of rows, filter it out if so
|
||||
if not isIgnored(jsonName, row):
|
||||
instance = tables[jsonName]()
|
||||
# fix for issue 80
|
||||
if jsonName is "icons" and "res:/UI/Texture/Icons/" in str(row["iconFile"]):
|
||||
row["iconFile"] = row["iconFile"].replace("res:/UI/Texture/Icons/","").replace(".png", "")
|
||||
for k, v in row.iteritems():
|
||||
setattr(instance, fieldMap.get(k, k), v)
|
||||
|
||||
eos.db.gamedata_session.add(instance)
|
||||
|
||||
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)
|
||||
#!/usr/bin/env python
|
||||
#======================================================================
|
||||
# Copyright (C) 2012 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 3 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/>.
|
||||
#======================================================================
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add eos root path to sys.path so we can import ourselves
|
||||
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||
sys.path.append(os.path.realpath(os.path.join(path, "..")))
|
||||
|
||||
import json
|
||||
import argparse
|
||||
|
||||
def main(db, json_path):
|
||||
|
||||
jsonPath = os.path.expanduser(json_path)
|
||||
|
||||
# Import eos.config first and change it
|
||||
import eos.config
|
||||
eos.config.gamedata_connectionstring = db
|
||||
eos.config.debug = False
|
||||
|
||||
# Now thats done, we can import the eos modules using the config
|
||||
import eos.db
|
||||
import eos.gamedata
|
||||
|
||||
# Create the database tables
|
||||
eos.db.gamedata_meta.create_all()
|
||||
|
||||
# Config dict
|
||||
tables = {
|
||||
"dgmattribs": eos.gamedata.AttributeInfo,
|
||||
"dgmeffects": eos.gamedata.EffectInfo,
|
||||
"dgmtypeattribs": eos.gamedata.Attribute,
|
||||
"dgmtypeeffects": eos.gamedata.Effect,
|
||||
"dgmunits": eos.gamedata.Unit,
|
||||
"icons": eos.gamedata.Icon,
|
||||
"evecategories": eos.gamedata.Category,
|
||||
"evegroups": eos.gamedata.Group,
|
||||
"invmetagroups": eos.gamedata.MetaGroup,
|
||||
"invmetatypes": eos.gamedata.MetaType,
|
||||
"evetypes": eos.gamedata.Item,
|
||||
"phbtraits": eos.gamedata.Traits,
|
||||
"phbmetadata": eos.gamedata.MetaData,
|
||||
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
||||
}
|
||||
|
||||
fieldMapping = {
|
||||
"dgmattribs": {
|
||||
"displayName_en-us": "displayName"
|
||||
},
|
||||
"dgmeffects": {
|
||||
"displayName_en-us": "displayName",
|
||||
"description_en-us": "description"
|
||||
},
|
||||
"dgmunits": {
|
||||
"displayName_en-us": "displayName"
|
||||
},
|
||||
#icons???
|
||||
"evecategories": {
|
||||
"categoryName_en-us": "categoryName"
|
||||
},
|
||||
"evegroups": {
|
||||
"groupName_en-us": "groupName"
|
||||
},
|
||||
"invmetagroups": {
|
||||
"metaGroupName_en-us": "metaGroupName"
|
||||
},
|
||||
"evetypes": {
|
||||
"typeName_en-us": "typeName",
|
||||
"description_en-us": "description"
|
||||
},
|
||||
#phbtraits???
|
||||
"mapbulk_marketGroups": {
|
||||
"marketGroupName_en-us": "marketGroupName",
|
||||
"description_en-us": "description"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rowsInValues = (
|
||||
"evetypes",
|
||||
"evegroups",
|
||||
"evecategories"
|
||||
)
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
for k, v in data.items():
|
||||
v["iconID"] = k
|
||||
new.append(v)
|
||||
return new
|
||||
|
||||
def convertTraits(data):
|
||||
|
||||
def convertSection(sectionData):
|
||||
sectionLines = []
|
||||
headerText = u"<b>{}</b>".format(sectionData["header"])
|
||||
sectionLines.append(headerText)
|
||||
for bonusData in sectionData["bonuses"]:
|
||||
prefix = u"{} ".format(bonusData["number"]) if "number" in bonusData else ""
|
||||
bonusText = u"{}{}".format(prefix, bonusData["text"].replace(u"\u00B7", u"\u2022 "))
|
||||
sectionLines.append(bonusText)
|
||||
sectionLine = u"<br />\n".join(sectionLines)
|
||||
return sectionLine
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
typeLines = []
|
||||
typeId = row["typeID"]
|
||||
traitData = row["traits_en-us"]
|
||||
for skillData in sorted(traitData.get("skills", ()), key=lambda i: i["header"]):
|
||||
typeLines.append(convertSection(skillData))
|
||||
if "role" in traitData:
|
||||
typeLines.append(convertSection(traitData["role"]))
|
||||
if "misc" in traitData:
|
||||
typeLines.append(convertSection(traitData["misc"]))
|
||||
traitLine = u"<br />\n<br />\n".join(typeLines)
|
||||
newRow = {"typeID": typeId, "traitText": traitLine}
|
||||
newData.append(newRow)
|
||||
return newData
|
||||
|
||||
def convertTypes(typesData):
|
||||
"""
|
||||
Add factionID column to evetypes table.
|
||||
"""
|
||||
factionMap = {}
|
||||
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
||||
overridesData = json.load(f)
|
||||
for typeID, typeData in overridesData.items():
|
||||
factionID = typeData.get("factionID")
|
||||
if factionID is not None:
|
||||
factionMap[int(typeID)] = factionID
|
||||
for row in typesData:
|
||||
row['factionID'] = factionMap.get(int(row['typeID']))
|
||||
return typesData
|
||||
|
||||
data = {}
|
||||
|
||||
# Dump all data to memory so we can easely cross check ignored rows
|
||||
for jsonName, cls in tables.iteritems():
|
||||
with open(os.path.join(jsonPath, "{}.json".format(jsonName))) as f:
|
||||
tableData = json.load(f)
|
||||
if jsonName in rowsInValues:
|
||||
tableData = list(tableData.values())
|
||||
if jsonName == "icons":
|
||||
tableData = convertIcons(tableData)
|
||||
if jsonName == "phbtraits":
|
||||
tableData = convertTraits(tableData)
|
||||
if jsonName == "evetypes":
|
||||
tableData = convertTypes(tableData)
|
||||
data[jsonName] = tableData
|
||||
|
||||
# Set with typeIDs which we will have in our database
|
||||
# Sometimes CCP unpublishes some items we want to have published, we
|
||||
# can do it here - just add them to initial set
|
||||
eveTypes = set()
|
||||
for row in data["evetypes"]:
|
||||
# 1306 - group Ship Modifiers, for items like tactical t3 ship modes
|
||||
if (row["published"] or row['groupID'] == 1306):
|
||||
eveTypes.add(row["typeID"])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file in ("evetypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in eveTypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Loop through each json file and write it away, checking ignored rows
|
||||
for jsonName, table in data.iteritems():
|
||||
fieldMap = fieldMapping.get(jsonName, {})
|
||||
print "processing {}".format(jsonName)
|
||||
for row in table:
|
||||
# We don't care about some kind of rows, filter it out if so
|
||||
if not isIgnored(jsonName, row):
|
||||
instance = tables[jsonName]()
|
||||
# fix for issue 80
|
||||
if jsonName is "icons" and "res:/UI/Texture/Icons/" in str(row["iconFile"]):
|
||||
row["iconFile"] = row["iconFile"].replace("res:/UI/Texture/Icons/","").replace(".png", "")
|
||||
for k, v in row.iteritems():
|
||||
setattr(instance, fieldMap.get(k, k), v)
|
||||
|
||||
eos.db.gamedata_session.add(instance)
|
||||
|
||||
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.port import Port
|
||||
|
||||
logger = logging.getLogger("pyfa.service.fit")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FitBackupThread(threading.Thread):
|
||||
def __init__(self, path, callback):
|
||||
@@ -97,7 +97,8 @@ class Fit(object):
|
||||
"rackSlots": True,
|
||||
"rackLabels": True,
|
||||
"compactSkills": True,
|
||||
"showTooltip": True}
|
||||
"showTooltip": True,
|
||||
"showMarketShortcuts": False}
|
||||
|
||||
self.serviceFittingOptions = SettingsProvider.getInstance().getSettings(
|
||||
"pyfaServiceFittingOptions", serviceFittingDefaultOptions)
|
||||
@@ -149,8 +150,8 @@ class Fit(object):
|
||||
return fit.modules[pos]
|
||||
|
||||
def newFit(self, shipID, name=None):
|
||||
fit = eos.types.Fit()
|
||||
fit.ship = eos.types.Ship(eos.db.getItem(shipID))
|
||||
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.damagePattern = self.pattern
|
||||
fit.targetResists = self.targetResists
|
||||
@@ -174,10 +175,14 @@ class Fit(object):
|
||||
fit = eos.db.getFit(fitID)
|
||||
sFleet = Fleet.getInstance()
|
||||
sFleet.removeAssociatedFleetData(fit)
|
||||
self.removeProjectedData(fitID)
|
||||
|
||||
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):
|
||||
fit = eos.db.getFit(fitID)
|
||||
newFit = copy.deepcopy(fit)
|
||||
@@ -192,14 +197,6 @@ class Fit(object):
|
||||
fit.clear()
|
||||
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):
|
||||
if fitID is None:
|
||||
return None
|
||||
@@ -235,6 +232,7 @@ class Fit(object):
|
||||
return None
|
||||
fit = eos.db.getFit(fitID)
|
||||
inited = getattr(fit, "inited", None)
|
||||
|
||||
if inited is None or inited is False:
|
||||
sFleet = Fleet.getInstance()
|
||||
f = sFleet.getLinearFleet(fit)
|
||||
@@ -245,6 +243,7 @@ class Fit(object):
|
||||
fit.fleet = f
|
||||
|
||||
if not projected:
|
||||
print "Not projected, getting projected fits"
|
||||
for fitP in fit.projectedFits:
|
||||
self.getFit(fitP.ID, projected = True)
|
||||
self.recalc(fit, withBoosters=True)
|
||||
@@ -274,7 +273,6 @@ class Fit(object):
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
fit.implants.freeSlot(implant)
|
||||
fit.implants.append(implant)
|
||||
self.recalc(fit)
|
||||
return True
|
||||
@@ -300,7 +298,6 @@ class Fit(object):
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
fit.boosters.freeSlot(booster)
|
||||
fit.boosters.append(booster)
|
||||
self.recalc(fit)
|
||||
return True
|
||||
@@ -323,9 +320,14 @@ class Fit(object):
|
||||
eager=("attributes", "group.category"))
|
||||
|
||||
if isinstance(thing, eos.types.Fit):
|
||||
if thing.ID == fitID:
|
||||
if thing in fit.projectedFits:
|
||||
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":
|
||||
drone = None
|
||||
for d in fit.projectedDrones.find(thing):
|
||||
@@ -364,6 +366,22 @@ class Fit(object):
|
||||
thing.state = self.__getProposedState(thing, click)
|
||||
if not thing.canHaveState(thing.state, fit):
|
||||
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()
|
||||
self.recalc(fit)
|
||||
@@ -375,7 +393,8 @@ class Fit(object):
|
||||
elif isinstance(thing, eos.types.Module):
|
||||
fit.projectedModules.remove(thing)
|
||||
else:
|
||||
fit.projectedFits.remove(thing)
|
||||
del fit.__projectedFits[thing.ID]
|
||||
#fit.projectedFits.remove(thing)
|
||||
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
@@ -398,8 +417,11 @@ class Fit(object):
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
self.recalc(fit)
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
self.checkStates(fit, m)
|
||||
|
||||
fit.fill()
|
||||
eos.db.commit()
|
||||
|
||||
@@ -857,7 +879,10 @@ class Fit(object):
|
||||
if drone.amountActive > 0 and not drone.canBeApplied(fit):
|
||||
drone.amountActive = 0
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
# If any state was changed, recalculate attributes again
|
||||
if changed:
|
||||
self.recalc(fit)
|
||||
|
||||
def toggleModulesState(self, fitID, base, modules, 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
|
||||
self.recalc(fit)
|
||||
# Then, check states of all modules and change where needed
|
||||
changed = self.checkStates(fit, base)
|
||||
# If any state was changed, recalulate attributes again
|
||||
if changed is True:
|
||||
self.recalc(fit)
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
self.checkStates(fit, base)
|
||||
|
||||
# Old state : New State
|
||||
localMap = {State.OVERHEATED: State.ACTIVE,
|
||||
@@ -890,7 +912,7 @@ class Fit(object):
|
||||
State.ONLINE: State.ACTIVE} # Just in case
|
||||
|
||||
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
|
||||
|
||||
currState = mod.state
|
||||
@@ -920,6 +942,7 @@ class Fit(object):
|
||||
self.recalc(fit)
|
||||
|
||||
def recalc(self, fit, withBoosters=False):
|
||||
logger.debug("="*10+"recalc"+"="*10)
|
||||
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
|
||||
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
|
||||
fit.clear()
|
||||
|
||||
@@ -71,6 +71,7 @@ class ShipBrowserWorkerThread(threading.Thread):
|
||||
class PriceWorkerThread(threading.Thread):
|
||||
def run(self):
|
||||
self.queue = Queue.Queue()
|
||||
self.wait = {}
|
||||
self.processUpdates()
|
||||
|
||||
def processUpdates(self):
|
||||
@@ -86,9 +87,21 @@ class PriceWorkerThread(threading.Thread):
|
||||
wx.CallAfter(callback)
|
||||
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):
|
||||
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):
|
||||
def run(self):
|
||||
self.cv = threading.Condition()
|
||||
@@ -218,6 +231,8 @@ class Market():
|
||||
"Mobile Decoy Unit": False, # Seems to be left over test mod for deployables
|
||||
"Tournament Micro Jump Unit": False, # Normally seen only on tournament arenas
|
||||
"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
|
||||
@@ -690,10 +705,6 @@ class Market():
|
||||
|
||||
self.priceCache[typeID] = price
|
||||
|
||||
if not price.isValid:
|
||||
# if the price has expired
|
||||
price.price = None
|
||||
|
||||
return price
|
||||
|
||||
def getPricesNow(self, typeIDs):
|
||||
@@ -716,6 +727,21 @@ class Market():
|
||||
|
||||
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):
|
||||
self.priceCache.clear()
|
||||
deleted_rows = eos.db.clearPrices()
|
||||
|
||||
@@ -55,4 +55,4 @@ else:
|
||||
# If database does not exist, do not worry about migration. Simply
|
||||
# create and set version
|
||||
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