Merge tag 'v1.13.2' into wx3

Conflicts:
	config.py
	A lot of icons
This commit is contained in:
blitzmann
2015-07-20 13:21:00 -04:00
137 changed files with 1183 additions and 774 deletions

View File

@@ -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

View File

@@ -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))

View File

@@ -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
"""

View 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")

View File

@@ -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),

View File

@@ -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),

View File

@@ -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)

View File

@@ -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):

View File

@@ -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

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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)

View 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)

View 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)

View 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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"))

View 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)

View File

@@ -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(

View 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")
)

View File

@@ -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 (

View 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))
)

View 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"
)

View 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")
)

View File

@@ -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(

View File

@@ -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")

View 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"))

View 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"))

View File

@@ -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"),

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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"),

View File

@@ -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

View File

@@ -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

View 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)

View File

@@ -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(

View File

@@ -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(

View File

@@ -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"):

View 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"
)

View 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)

View 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)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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):

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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()):

View File

@@ -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]

View File

@@ -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:

View File

@@ -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")

View File

@@ -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)))

View File

@@ -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")

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

258
scripts/icons_update.py Normal file
View 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))

View File

@@ -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
View 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))

View File

@@ -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

View File

@@ -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()

View File

@@ -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()))

View File

@@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Some files were not shown because too many files have changed in this diff Show More