Merge tag 'v1.13.2' into wx3
Conflicts: config.py A lot of icons
36
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,24 +17,21 @@ debug = False
|
||||
# Defines if our saveddata will be in pyfa root or not
|
||||
saveInRoot = False
|
||||
|
||||
logLevel = logging.WARN
|
||||
|
||||
# Version data
|
||||
version = "1.12.1"
|
||||
tag = "git"
|
||||
expansionName = "Carnyx"
|
||||
version = "1.13.2"
|
||||
tag = "Stable"
|
||||
expansionName = "Aegis"
|
||||
expansionVersion = "1.0"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
# Database version (int ONLY)
|
||||
# Increment every time we need to flag for user database upgrade/modification
|
||||
dbversion = 7
|
||||
|
||||
pyfaPath = None
|
||||
savePath = None
|
||||
staticPath = None
|
||||
saveDB = None
|
||||
gameDB = None
|
||||
|
||||
|
||||
def isFrozen():
|
||||
if hasattr(sys, 'frozen'):
|
||||
return True
|
||||
@@ -43,6 +45,9 @@ def getPyfaRoot():
|
||||
root = unicode(root, sys.getfilesystemencoding())
|
||||
return root
|
||||
|
||||
def __createDirs(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
def defPaths():
|
||||
global pyfaPath
|
||||
@@ -68,13 +73,22 @@ def defPaths():
|
||||
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
|
||||
sys.getfilesystemencoding())
|
||||
|
||||
__createDirs(savePath)
|
||||
|
||||
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")
|
||||
|
||||
# Redirect stderr to file if we're requested to do so
|
||||
stderrToFile = getattr(configforced, "stderrToFile", None)
|
||||
if stderrToFile is None:
|
||||
stderrToFile = True if isFrozen() else False
|
||||
if stderrToFile is True:
|
||||
if not os.path.exists(savePath):
|
||||
os.mkdir(savePath)
|
||||
sys.stderr = open(os.path.join(savePath, "error_log.txt"), "w")
|
||||
|
||||
# Same for stdout
|
||||
@@ -82,8 +96,6 @@ def defPaths():
|
||||
if stdoutToFile is None:
|
||||
stdoutToFile = True if isFrozen() else False
|
||||
if stdoutToFile is True:
|
||||
if not os.path.exists(savePath):
|
||||
os.mkdir(savePath)
|
||||
sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w")
|
||||
|
||||
# Static EVE Data from the staticdata repository, should be in the staticdata
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
"""
|
||||
Migration 4
|
||||
Migration 8
|
||||
|
||||
- Converts modules based on Proteus Module Tiericide
|
||||
- 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
|
||||
|
||||
Based on http://community.eveonline.com/news/patch-notes/patch-notes-for-proteus/
|
||||
and output of itemDiff.py
|
||||
"""
|
||||
|
||||
|
||||
|
||||
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),
|
||||
|
||||
@@ -27,9 +27,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),
|
||||
@@ -55,14 +53,16 @@ mapper(Fit, fits_table,
|
||||
"_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 = HandledDroneList, 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 = HandledCargoList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
"_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', single_parent=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),
|
||||
|
||||
@@ -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,128 +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)
|
||||
# Remove invalid or non-projectable drones
|
||||
if proj.isInvalid or not proj.item.isType("projected"):
|
||||
self.remove(proj)
|
||||
|
||||
class HandledProjectedFitList(HandledList):
|
||||
def append(self, proj):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"),
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# shipBonusRHMLROFCB
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Scorpion Navy Issue
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Caldari Battleship").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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,11 @@ 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 logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
@@ -48,10 +51,14 @@ 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()
|
||||
@@ -59,23 +66,42 @@ class Fit(object):
|
||||
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 +126,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 +149,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 +179,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):
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -196,8 +196,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")
|
||||
|
||||
|
||||
@@ -55,6 +55,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
|
||||
|
||||
@@ -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
|
||||
@@ -144,6 +144,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)
|
||||
@@ -158,7 +159,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)
|
||||
|
||||
@@ -225,7 +226,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"]:
|
||||
@@ -239,6 +240,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
|
||||
@@ -248,6 +252,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
|
||||
|
||||
@@ -421,12 +428,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)
|
||||
@@ -435,12 +436,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),
|
||||
|
||||
@@ -460,41 +455,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):
|
||||
|
||||
BIN
icons/refresh_small.png
Normal file
|
After Width: | Height: | Size: 506 B |
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))
|
||||
@@ -52,11 +52,11 @@ def main(db, json_path):
|
||||
"dgmtypeeffects": eos.gamedata.Effect,
|
||||
"dgmunits": eos.gamedata.Unit,
|
||||
"icons": eos.gamedata.Icon,
|
||||
"invcategories": eos.gamedata.Category,
|
||||
"invgroups": eos.gamedata.Group,
|
||||
"evecategories": eos.gamedata.Category,
|
||||
"evegroups": eos.gamedata.Group,
|
||||
"invmetagroups": eos.gamedata.MetaGroup,
|
||||
"invmetatypes": eos.gamedata.MetaType,
|
||||
"invtypes": eos.gamedata.Item,
|
||||
"evetypes": eos.gamedata.Item,
|
||||
"phbtraits": eos.gamedata.Traits,
|
||||
"phbmetadata": eos.gamedata.MetaData,
|
||||
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
||||
@@ -74,16 +74,16 @@ def main(db, json_path):
|
||||
"displayName_en-us": "displayName"
|
||||
},
|
||||
#icons???
|
||||
"invcategories": {
|
||||
"evecategories": {
|
||||
"categoryName_en-us": "categoryName"
|
||||
},
|
||||
"invgroups": {
|
||||
"evegroups": {
|
||||
"groupName_en-us": "groupName"
|
||||
},
|
||||
"invmetagroups": {
|
||||
"metaGroupName_en-us": "metaGroupName"
|
||||
},
|
||||
"invtypes": {
|
||||
"evetypes": {
|
||||
"typeName_en-us": "typeName",
|
||||
"description_en-us": "description"
|
||||
},
|
||||
@@ -95,6 +95,12 @@ def main(db, json_path):
|
||||
|
||||
}
|
||||
|
||||
rowsInValues = (
|
||||
"evetypes",
|
||||
"evegroups",
|
||||
"evecategories"
|
||||
)
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
for k, v in data.items():
|
||||
@@ -133,7 +139,7 @@ def main(db, json_path):
|
||||
|
||||
def convertTypes(typesData):
|
||||
"""
|
||||
Add factionID column to invtypes table.
|
||||
Add factionID column to evetypes table.
|
||||
"""
|
||||
factionMap = {}
|
||||
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
||||
@@ -152,30 +158,28 @@ def main(db, json_path):
|
||||
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 == "invtypes":
|
||||
if jsonName == "evetypes":
|
||||
tableData = convertTypes(tableData)
|
||||
data[jsonName] = tableData
|
||||
|
||||
# Set with typeIDs which we will have in our database
|
||||
invTypes = {
|
||||
# Sometimes CCP unpublishes some items we want to have published, we
|
||||
# can do it here
|
||||
31906, # Federation Navy 200mm Steel Plates
|
||||
31904, # Imperial Navy 200mm Steel Plates
|
||||
28782, # Syndicate 200mm Steel Plates
|
||||
}
|
||||
for row in data["invtypes"]:
|
||||
# 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):
|
||||
invTypes.add(row["typeID"])
|
||||
eveTypes.add(row["typeID"])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file in ("invtypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in invTypes:
|
||||
if file in ("evetypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in eveTypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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))
|
||||
@@ -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
|
||||
@@ -274,7 +275,6 @@ class Fit(object):
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
fit.implants.freeSlot(implant)
|
||||
fit.implants.append(implant)
|
||||
self.recalc(fit)
|
||||
return True
|
||||
@@ -300,7 +300,6 @@ class Fit(object):
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
fit.boosters.freeSlot(booster)
|
||||
fit.boosters.append(booster)
|
||||
self.recalc(fit)
|
||||
return True
|
||||
@@ -893,7 +892,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
|
||||
|
||||
@@ -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,9 +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
|
||||
"Federation Navy 200mm Steel Plates": True, # Accidentally unpublished by CCP
|
||||
"Imperial Navy 200mm Steel Plates": True, # Accidentally unpublished by CCP
|
||||
"Syndicate 200mm Steel Plates": True, # Accidentally unpublished by CCP
|
||||
"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
|
||||
@@ -693,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):
|
||||
@@ -719,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()))
|
||||
|
||||
@@ -52,7 +52,7 @@ class Price():
|
||||
item = eos.db.getItem(typeID)
|
||||
# We're not going to request items only with market group, as eve-central
|
||||
# doesn't provide any data for items not on the market
|
||||
if item.marketGroupID:
|
||||
if item is not None and item.marketGroupID:
|
||||
toRequest.add(typeID)
|
||||
|
||||
# Do not waste our time if all items are not on the market
|
||||
@@ -71,7 +71,6 @@ class Price():
|
||||
|
||||
# Attempt to send request and process it
|
||||
try:
|
||||
len(priceMap)
|
||||
network = service.Network.getInstance()
|
||||
data = network.request(baseurl, network.PRICES, data)
|
||||
xml = minidom.parse(data)
|
||||
@@ -102,7 +101,7 @@ class Price():
|
||||
for typeID in priceMap.keys():
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.time = time.time() + TIMEOUT
|
||||
priceobj.failed = None
|
||||
priceobj.failed = True
|
||||
del priceMap[typeID]
|
||||
except:
|
||||
# all other errors will pass and continue onward to the REREQUEST delay
|
||||
@@ -111,6 +110,5 @@ class Price():
|
||||
# if we get to this point, then we've got an error. Set to REREQUEST delay
|
||||
for typeID in priceMap.keys():
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.price = 0
|
||||
priceobj.time = time.time() + REREQUEST
|
||||
priceobj.failed = None
|
||||
priceobj.failed = True
|
||||
|
||||
BIN
staticdata/icons/icon07_12.png
Normal file
|
After Width: | Height: | Size: 879 B |
BIN
staticdata/icons/icon107_1.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
staticdata/icons/icon107_10.png
Normal file
|
After Width: | Height: | Size: 858 B |
BIN
staticdata/icons/icon107_11.png
Normal file
|
After Width: | Height: | Size: 864 B |
BIN
staticdata/icons/icon107_12.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
staticdata/icons/icon107_2.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
staticdata/icons/icon107_3.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
staticdata/icons/icon107_64_4.png
Normal file
|
After Width: | Height: | Size: 851 B |
BIN
staticdata/icons/icon108_1.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
staticdata/icons/icon108_10.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
staticdata/icons/icon108_11.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
staticdata/icons/icon108_12.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |