diff --git a/eos/db/gamedata/item.py b/eos/db/gamedata/item.py
index df7508e43..fd7be477d 100644
--- a/eos/db/gamedata/item.py
+++ b/eos/db/gamedata/item.py
@@ -40,7 +40,9 @@ items_table = Table("invtypes", gamedata_meta,
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
Column("iconID", Integer),
Column("graphicID", Integer),
- Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
+ Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
+ Column("replaceSame", String),
+ Column("replaceBetter", String))
from .metaGroup import metatypes_table # noqa
from .traits import traits_table # noqa
diff --git a/eos/db/migrations/upgrade30.py b/eos/db/migrations/upgrade30.py
new file mode 100644
index 000000000..7954f2d37
--- /dev/null
+++ b/eos/db/migrations/upgrade30.py
@@ -0,0 +1,17 @@
+"""
+Migration 30
+
+- changes to prices table
+"""
+
+
+import sqlalchemy
+
+
+def upgrade(saveddata_engine):
+ try:
+ saveddata_engine.execute("SELECT status FROM prices LIMIT 1")
+ except sqlalchemy.exc.DatabaseError:
+ # Just drop table, table will be re-created by sqlalchemy and
+ # data will be re-fetched
+ saveddata_engine.execute("DROP TABLE prices;")
diff --git a/eos/db/saveddata/price.py b/eos/db/saveddata/price.py
index 8be7f6519..8abd07132 100644
--- a/eos/db/saveddata/price.py
+++ b/eos/db/saveddata/price.py
@@ -17,17 +17,20 @@
# along with eos. If not, see .
# ===============================================================================
+
from sqlalchemy import Table, Column, Float, Integer
from sqlalchemy.orm import mapper
from eos.db import saveddata_meta
from eos.saveddata.price import Price
+
prices_table = Table("prices", saveddata_meta,
Column("typeID", Integer, primary_key=True),
Column("price", Float, default=0.0),
Column("time", Integer, nullable=False),
- Column("failed", Integer))
+ Column("status", Integer, nullable=False))
+
mapper(Price, prices_table, properties={
"_Price__price": prices_table.c.price,
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index e582eef87..448584420 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -542,8 +542,17 @@ def commit():
with sd_lock:
try:
saveddata_session.commit()
- saveddata_session.flush()
- except Exception as ex:
+ except Exception:
+ saveddata_session.rollback()
+ exc_info = sys.exc_info()
+ raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
+
+
+def flush():
+ with sd_lock:
+ try:
+ saveddata_session.flush()
+ except Exception:
saveddata_session.rollback()
exc_info = sys.exc_info()
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
diff --git a/eos/effects/ammoinfluencecapneed.py b/eos/effects/ammoinfluencecapneed.py
index 587e55cd5..3bbb1103c 100644
--- a/eos/effects/ammoinfluencecapneed.py
+++ b/eos/effects/ammoinfluencecapneed.py
@@ -1,7 +1,7 @@
# ammoInfluenceCapNeed
#
# Used by:
-# Items from category: Charge (493 of 947)
+# Items from category: Charge (493 of 949)
type = "passive"
diff --git a/eos/effects/ammoinfluencerange.py b/eos/effects/ammoinfluencerange.py
index 4358a6ff9..58c331911 100644
--- a/eos/effects/ammoinfluencerange.py
+++ b/eos/effects/ammoinfluencerange.py
@@ -1,7 +1,7 @@
# ammoInfluenceRange
#
# Used by:
-# Items from category: Charge (587 of 947)
+# Items from category: Charge (587 of 949)
type = "passive"
diff --git a/eos/effects/ammospeedmultiplier.py b/eos/effects/ammospeedmultiplier.py
index 2a6c2e4d3..093953c11 100644
--- a/eos/effects/ammospeedmultiplier.py
+++ b/eos/effects/ammospeedmultiplier.py
@@ -1,10 +1,9 @@
# ammoSpeedMultiplier
#
# Used by:
-# Charges from group: Festival Charges (23 of 23)
+# Charges from group: Festival Charges (26 of 26)
# Charges from group: Interdiction Probe (2 of 2)
-# Charges from group: Structure Festival Charges (3 of 3)
-# Special Edition Assetss from group: Festival Charges Expired (2 of 2)
+# Items from market group: Special Edition Assets > Special Edition Festival Assets (30 of 33)
type = "passive"
diff --git a/eos/effects/ammotrackingmultiplier.py b/eos/effects/ammotrackingmultiplier.py
index 6153af15c..95033d743 100644
--- a/eos/effects/ammotrackingmultiplier.py
+++ b/eos/effects/ammotrackingmultiplier.py
@@ -1,7 +1,7 @@
# ammoTrackingMultiplier
#
# Used by:
-# Items from category: Charge (182 of 947)
+# Items from category: Charge (182 of 949)
# Charges from group: Projectile Ammo (128 of 128)
type = "passive"
diff --git a/eos/effects/boostershieldcapacitypenalty.py b/eos/effects/boostershieldcapacitypenalty.py
index 41108747f..f93f04af9 100644
--- a/eos/effects/boostershieldcapacitypenalty.py
+++ b/eos/effects/boostershieldcapacitypenalty.py
@@ -1,7 +1,7 @@
# boosterShieldCapacityPenalty
#
# Used by:
-# Implants from group: Booster (12 of 71)
+# Implants from group: Booster (12 of 70)
type = "boosterSideEffect"
# User-friendly name for the side effect
diff --git a/eos/effects/cynosuraldurationbonus.py b/eos/effects/cynosuraldurationbonus.py
index 4f78ea09e..defbd9ca3 100644
--- a/eos/effects/cynosuraldurationbonus.py
+++ b/eos/effects/cynosuraldurationbonus.py
@@ -6,5 +6,5 @@ type = "passive"
def handler(fit, ship, context):
- fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
+ fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
"duration", ship.getModifiedItemAttr("durationBonus"))
diff --git a/eos/effects/cynosuralgeneration.py b/eos/effects/cynosuralgeneration.py
index b99325c0f..36e29c52d 100644
--- a/eos/effects/cynosuralgeneration.py
+++ b/eos/effects/cynosuralgeneration.py
@@ -1,7 +1,7 @@
# cynosuralGeneration
#
# Used by:
-# Modules from group: Cynosural Field (2 of 2)
+# Modules from group: Cynosural Field Generator (2 of 2)
type = "active"
diff --git a/eos/effects/cynosuraltheoryconsumptionbonus.py b/eos/effects/cynosuraltheoryconsumptionbonus.py
index d67c969f8..d6bda31b7 100644
--- a/eos/effects/cynosuraltheoryconsumptionbonus.py
+++ b/eos/effects/cynosuraltheoryconsumptionbonus.py
@@ -8,6 +8,6 @@ type = "passive"
def handler(fit, container, context):
level = container.level if "skill" in context else 1
- fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
+ fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
"consumptionQuantity",
container.getModifiedItemAttr("consumptionQuantityBonusPercentage") * level)
diff --git a/eos/effects/implantwarpscramblerangebonus.py b/eos/effects/implantwarpscramblerangebonus.py
new file mode 100644
index 000000000..8eea16959
--- /dev/null
+++ b/eos/effects/implantwarpscramblerangebonus.py
@@ -0,0 +1,10 @@
+# implantWarpScrambleRangeBonus
+#
+# Used by:
+# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
+type = "passive"
+
+
+def handler(fit, src, context):
+ fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Warp Scrambler", "maxRange",
+ src.getModifiedItemAttr("warpScrambleRangeBonus"), stackingPenalties=False)
diff --git a/eos/effects/miningdurationmultiplieronline.py b/eos/effects/miningdurationmultiplieronline.py
new file mode 100644
index 000000000..3d4cad5a5
--- /dev/null
+++ b/eos/effects/miningdurationmultiplieronline.py
@@ -0,0 +1,10 @@
+# miningDurationMultiplierOnline
+#
+# Used by:
+# Module: Frostline 'Omnivore' Harvester Upgrade
+type = "passive"
+
+
+def handler(fit, module, context):
+ fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mining Laser",
+ "duration", module.getModifiedItemAttr("miningDurationMultiplier"))
diff --git a/eos/effects/overloadrofbonus.py b/eos/effects/overloadrofbonus.py
index b362b9cc5..c0fedef1a 100644
--- a/eos/effects/overloadrofbonus.py
+++ b/eos/effects/overloadrofbonus.py
@@ -2,7 +2,7 @@
#
# Used by:
# Modules from group: Missile Launcher Torpedo (22 of 22)
-# Items from market group: Ship Equipment > Turrets & Bays (429 of 882)
+# Items from market group: Ship Equipment > Turrets & Bays (429 of 883)
# Module: Interdiction Sphere Launcher I
type = "overheat"
diff --git a/eos/effects/remotewebifiermaxrangebonus.py b/eos/effects/remotewebifiermaxrangebonus.py
index 46e5887dd..531f18ded 100644
--- a/eos/effects/remotewebifiermaxrangebonus.py
+++ b/eos/effects/remotewebifiermaxrangebonus.py
@@ -2,6 +2,7 @@
#
# Used by:
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
+# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
type = "passive"
diff --git a/eos/effects/shipbonusnosneutcapneedrolebonus2.py b/eos/effects/shipbonusnosneutcapneedrolebonus2.py
index 314736818..ec427ec11 100644
--- a/eos/effects/shipbonusnosneutcapneedrolebonus2.py
+++ b/eos/effects/shipbonusnosneutcapneedrolebonus2.py
@@ -1,8 +1,7 @@
# shipBonusNosNeutCapNeedRoleBonus2
#
# Used by:
-# Ship: Rodiva
-# Ship: Zarmazd
+# Variations of ship: Rodiva (2 of 2)
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capacitor Emission Systems"), "capacitorNeed", src.getModifiedItemAttr("shipBonusRole2"))
diff --git a/eos/effects/shipbonusremotecapacitortransferrangerole1.py b/eos/effects/shipbonusremotecapacitortransferrangerole1.py
index 22e7efe3b..bfd670862 100644
--- a/eos/effects/shipbonusremotecapacitortransferrangerole1.py
+++ b/eos/effects/shipbonusremotecapacitortransferrangerole1.py
@@ -1,8 +1,7 @@
# shipBonusRemoteCapacitorTransferRangeRole1
#
# Used by:
-# Ship: Rodiva
-# Ship: Zarmazd
+# Variations of ship: Rodiva (2 of 2)
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Remote Capacitor Transmitter", "maxRange", src.getModifiedItemAttr("shipBonusRole1"))
diff --git a/eos/effects/shipbonussmartbombcapneedrolebonus2.py b/eos/effects/shipbonussmartbombcapneedrolebonus2.py
index 71df39ec4..69e4017a8 100644
--- a/eos/effects/shipbonussmartbombcapneedrolebonus2.py
+++ b/eos/effects/shipbonussmartbombcapneedrolebonus2.py
@@ -1,15 +1,14 @@
# shipBonusSmartbombCapNeedRoleBonus2
#
# Used by:
+# Variations of ship: Rodiva (2 of 2)
# Ship: Damavik
# Ship: Drekavac
# Ship: Hydra
# Ship: Kikimora
# Ship: Leshak
-# Ship: Rodiva
# Ship: Tiamat
# Ship: Vedmak
-# Ship: Zarmazd
type = "passive"
diff --git a/eos/effects/skillbombdeploymentmodulereactivationdelaybonus.py b/eos/effects/skillbombdeploymentmodulereactivationdelaybonus.py
index e56b2fb94..4db900c9b 100644
--- a/eos/effects/skillbombdeploymentmodulereactivationdelaybonus.py
+++ b/eos/effects/skillbombdeploymentmodulereactivationdelaybonus.py
@@ -7,4 +7,4 @@ type = "passive"
def handler(fit, skill, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Missile Launcher Bomb",
- "moduleReactivationDelay", skill.getModifiedItemAttr("rofBonus") * skill.level)
+ "moduleReactivationDelay", skill.getModifiedItemAttr("reactivationDelayBonus") * skill.level)
diff --git a/eos/effects/skillbonusdronedurability.py b/eos/effects/skillbonusdronedurability.py
index e68cb4254..f36e82a3f 100644
--- a/eos/effects/skillbonusdronedurability.py
+++ b/eos/effects/skillbonusdronedurability.py
@@ -1,7 +1,6 @@
# skillBonusDroneDurability
#
# Used by:
-# Implants from group: Cyber Drones (4 of 4)
# Skill: Drone Durability
type = "passive"
diff --git a/eos/effects/skillbonusdronedurabilitynotfighters.py b/eos/effects/skillbonusdronedurabilitynotfighters.py
new file mode 100644
index 000000000..d2b8a0967
--- /dev/null
+++ b/eos/effects/skillbonusdronedurabilitynotfighters.py
@@ -0,0 +1,14 @@
+# skillBonusDroneDurabilityNotFighters
+#
+# Used by:
+# Implants from group: Cyber Drones (4 of 4)
+type = "passive"
+
+
+def handler(fit, src, context):
+ fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "hp",
+ src.getModifiedItemAttr("hullHpBonus"))
+ fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
+ src.getModifiedItemAttr("armorHpBonus"))
+ fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "shieldCapacity",
+ src.getModifiedItemAttr("shieldCapacityBonus"))
diff --git a/eos/effects/skillbonusdroneinterfacing.py b/eos/effects/skillbonusdroneinterfacing.py
index 93093efc3..d832d432b 100644
--- a/eos/effects/skillbonusdroneinterfacing.py
+++ b/eos/effects/skillbonusdroneinterfacing.py
@@ -1,8 +1,6 @@
# skillBonusDroneInterfacing
#
# Used by:
-# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
-# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
# Skill: Drone Interfacing
type = "passive"
diff --git a/eos/effects/skillbonusdroneinterfacingnotfighters.py b/eos/effects/skillbonusdroneinterfacingnotfighters.py
new file mode 100644
index 000000000..01ab85de7
--- /dev/null
+++ b/eos/effects/skillbonusdroneinterfacingnotfighters.py
@@ -0,0 +1,11 @@
+# skillBonusDroneInterfacingNotFighters
+#
+# Used by:
+# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
+# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
+type = "passive"
+
+
+def handler(fit, src, context):
+ fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "damageMultiplier",
+ src.getModifiedItemAttr("damageMultiplierBonus"))
diff --git a/eos/effects/stripminerdurationmultiplier.py b/eos/effects/stripminerdurationmultiplier.py
new file mode 100644
index 000000000..c9d0179a6
--- /dev/null
+++ b/eos/effects/stripminerdurationmultiplier.py
@@ -0,0 +1,10 @@
+# stripMinerDurationMultiplier
+#
+# Used by:
+# Module: Frostline 'Omnivore' Harvester Upgrade
+type = "passive"
+
+
+def handler(fit, module, context):
+ fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Strip Miner",
+ "duration", module.getModifiedItemAttr("miningDurationMultiplier"))
diff --git a/eos/gamedata.py b/eos/gamedata.py
index f8345ac87..5ec2a1e14 100644
--- a/eos/gamedata.py
+++ b/eos/gamedata.py
@@ -239,7 +239,7 @@ class Item(EqBase):
self.__offensive = None
self.__assistive = None
self.__overrides = None
- self.__price = None
+ self.__priceObj = None
@property
def attributes(self):
@@ -446,34 +446,33 @@ class Item(EqBase):
@property
def price(self):
-
# todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8)
- if self.__price is not None and getattr(self.__price, '_sa_instance_state', None) and self.__price._sa_instance_state.deleted:
+ if self.__priceObj is not None and getattr(self.__priceObj, '_sa_instance_state', None) and self.__priceObj._sa_instance_state.deleted:
pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID))
- self.__price = None
+ self.__priceObj = None
- if self.__price is None:
+ if self.__priceObj is None:
db_price = eos.db.getPrice(self.ID)
# do not yet have a price in the database for this item, create one
if db_price is None:
pyfalog.debug("Creating a price for {}".format(self.ID))
- self.__price = types_Price(self.ID)
- eos.db.add(self.__price)
- eos.db.commit()
+ self.__priceObj = types_Price(self.ID)
+ eos.db.add(self.__priceObj)
+ eos.db.flush()
else:
- self.__price = db_price
+ self.__priceObj = db_price
- return self.__price
+ return self.__priceObj
@property
def isAbyssal(self):
if Item.ABYSSAL_TYPES is None:
- Item.getAbyssalYypes()
+ Item.getAbyssalTypes()
return self.ID in Item.ABYSSAL_TYPES
@classmethod
- def getAbyssalYypes(cls):
+ def getAbyssalTypes(cls):
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
@property
diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py
index d660dd38b..756a9f806 100644
--- a/eos/modifiedAttributeDict.py
+++ b/eos/modifiedAttributeDict.py
@@ -215,6 +215,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
if force is not None:
if cappingValue is not None:
force = min(force, cappingValue)
+ if key in (50, 30, 48, 11):
+ force = round(force, 2)
return force
# Grab our values if they're there, otherwise we'll take default values
preIncrease = self.__preIncreases.get(key, 0)
@@ -268,7 +270,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
# Cap value if we have cap defined
if cappingValue is not None:
val = min(val, cappingValue)
-
+ if key in (50, 30, 48, 11):
+ val = round(val, 2)
return val
def __handleSkill(self, skillName):
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index 4373bcdd0..f33a78651 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -1016,11 +1016,11 @@ class Fit(object):
@property
def pgUsed(self):
- return self.getItemAttrOnlineSum(self.modules, "power")
+ return round(self.getItemAttrOnlineSum(self.modules, "power"), 2)
@property
def cpuUsed(self):
- return self.getItemAttrOnlineSum(self.modules, "cpu")
+ return round(self.getItemAttrOnlineSum(self.modules, "cpu"), 2)
@property
def droneBandwidthUsed(self):
diff --git a/eos/saveddata/price.py b/eos/saveddata/price.py
index 6618119d0..a2a630c30 100644
--- a/eos/saveddata/price.py
+++ b/eos/saveddata/price.py
@@ -18,24 +18,30 @@
# along with eos. If not, see .
# ===============================================================================
-import time
-from sqlalchemy.orm import reconstructor
+import time
+from enum import IntEnum, unique
+
from logbook import Logger
+
pyfalog = Logger(__name__)
+@unique
+class PriceStatus(IntEnum):
+ notFetched = 0
+ success = 1
+ fail = 2
+ notSupported = 3
+
+
class Price(object):
def __init__(self, typeID):
self.typeID = typeID
self.time = 0
self.__price = 0
- self.failed = None
-
- @reconstructor
- def init(self):
- self.__item = None
+ self.status = PriceStatus.notFetched
@property
def isValid(self):
@@ -43,7 +49,10 @@ class Price(object):
@property
def price(self):
- return self.__price or 0.0
+ if self.status != PriceStatus.success:
+ return 0
+ else:
+ return self.__price or 0
@price.setter
def price(self, price):
diff --git a/eve.db b/eve.db
index fe0f660a1..417b53d1c 100644
Binary files a/eve.db and b/eve.db differ
diff --git a/gui/builtinContextMenus/fillWithModule.py b/gui/builtinContextMenus/fillWithModule.py
index 9e32dbc0d..0dd8d0d91 100644
--- a/gui/builtinContextMenus/fillWithModule.py
+++ b/gui/builtinContextMenus/fillWithModule.py
@@ -13,6 +13,8 @@ class FillWithModule(ContextMenu):
self.settings = ContextMenuSettings.getInstance()
def display(self, srcContext, selection):
+ if not self.settings.get('moduleFill'):
+ return False
return srcContext in ("fittingModule")
def getText(self, itmContext, selection):
diff --git a/gui/builtinContextMenus/marketJump.py b/gui/builtinContextMenus/marketJump.py
index c630312c1..a5d5e8eeb 100644
--- a/gui/builtinContextMenus/marketJump.py
+++ b/gui/builtinContextMenus/marketJump.py
@@ -26,7 +26,10 @@ class MarketJump(ContextMenu):
sMkt = Market.getInstance()
item = getattr(selection[0], "item", selection[0])
+ isMutated = getattr(selection[0], "isMutated", False)
mktGrp = sMkt.getMarketGroupByItem(item)
+ if mktGrp is None and isMutated:
+ mktGrp = sMkt.getMarketGroupByItem(selection[0].baseItem)
# 1663 is Special Edition Festival Assets, we don't have root group for it
if mktGrp is None or mktGrp.ID == 1663:
@@ -43,7 +46,10 @@ class MarketJump(ContextMenu):
if srcContext in ("fittingCharge", "projectedCharge"):
item = selection[0].charge
elif hasattr(selection[0], "item"):
- item = selection[0].item
+ if getattr(selection[0], "isMutated", False):
+ item = selection[0].baseItem
+ else:
+ item = selection[0].item
else:
item = selection[0]
diff --git a/gui/builtinItemStatsViews/attributeSlider.py b/gui/builtinItemStatsViews/attributeSlider.py
index e45fa8f49..9e3585ea9 100644
--- a/gui/builtinItemStatsViews/attributeSlider.py
+++ b/gui/builtinItemStatsViews/attributeSlider.py
@@ -58,7 +58,6 @@ class AttributeSlider(wx.Panel):
self.UserMinValue = minValue
self.UserMaxValue = maxValue
- print(self.UserMinValue, self.UserMaxValue)
self.inverse = inverse
diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py
index 1561ed58b..97a4b953d 100644
--- a/gui/builtinItemStatsViews/itemCompare.py
+++ b/gui/builtinItemStatsViews/itemCompare.py
@@ -8,6 +8,10 @@ from service.attribute import Attribute
from gui.utils.numberFormatter import formatAmount
+def defaultSort(item):
+ return (item.attributes['metaLevel'].value if 'metaLevel' in item.attributes else 0, item.name)
+
+
class ItemCompare(wx.Panel):
def __init__(self, parent, stuff, item, items, context=None):
# Start dealing with Price stuff to get that thread going
@@ -27,8 +31,7 @@ class ItemCompare(wx.Panel):
self.currentSort = None
self.sortReverse = False
self.item = item
- self.items = sorted(items,
- key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0)
+ self.items = sorted(items, key=defaultSort)
self.attrs = {}
# get a dict of attrName: attrInfo of all unique attributes across all items
@@ -126,10 +129,15 @@ class ItemCompare(wx.Panel):
# starts at 0 while the list has the item name as column 0.
attr = str(list(self.attrs.keys())[sort - 1])
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
+ # Clicked on a column that's not part of our array (price most likely)
except IndexError:
- # Clicked on a column that's not part of our array (price most likely)
- self.sortReverse = False
- func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0
+ # Price
+ if sort == len(self.attrs) + 1:
+ func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
+ # Something else
+ else:
+ self.sortReverse = False
+ func = defaultSort
self.items = sorted(self.items, key=func, reverse=self.sortReverse)
@@ -160,7 +168,7 @@ class ItemCompare(wx.Panel):
self.paramList.SetItem(i, x + 1, valueUnit)
# Add prices
- self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True))
+ self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
self.paramList.RefreshRows()
self.Layout()
diff --git a/gui/builtinItemStatsViews/itemDescription.py b/gui/builtinItemStatsViews/itemDescription.py
index 475b88788..9c62dcc78 100644
--- a/gui/builtinItemStatsViews/itemDescription.py
+++ b/gui/builtinItemStatsViews/itemDescription.py
@@ -15,7 +15,6 @@ class ItemDescription(wx.Panel):
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
self.description = wx.html.HtmlWindow(self)
-
if not item.description:
return
diff --git a/gui/builtinItemStatsViews/itemMutator.py b/gui/builtinItemStatsViews/itemMutator.py
index cf7568350..3e9edc582 100644
--- a/gui/builtinItemStatsViews/itemMutator.py
+++ b/gui/builtinItemStatsViews/itemMutator.py
@@ -22,8 +22,23 @@ class ItemMutator(wx.Panel):
self.item = item
self.timer = None
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
+
+ font = parent.GetFont()
+ font.SetWeight(wx.BOLD)
+
mainSizer = wx.BoxSizer(wx.VERTICAL)
+ sourceItemsSizer = wx.BoxSizer(wx.HORIZONTAL)
+ sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons"), 0, wx.LEFT, 5)
+ sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons"), 0, wx.LEFT, 0)
+ sourceItemShort = "{} {}".format(stuff.mutaplasmid.item.name.split(" ")[0], stuff.baseItem.name)
+ sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
+ sourceItemText.SetFont(font)
+ sourceItemsSizer.Add(sourceItemText, 0, wx.LEFT, 10)
+ mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.EXPAND, 10)
+
+ mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
+
self.goodColor = wx.Colour(96, 191, 0)
self.badColor = wx.Colour(255, 64, 0)
@@ -31,28 +46,33 @@ class ItemMutator(wx.Panel):
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
# Format: [raw value, modifier applied to base raw value, display value]
- range1 = (m.minValue, m.minMod, m.attribute.unit.SimplifyValue(m.minValue))
- range2 = (m.maxValue, m.maxMod, m.attribute.unit.SimplifyValue(m.maxValue))
+ minRange = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
+ maxRange = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue))
- if (m.highIsGood and range1[0] >= range2[0]) or (not m.highIsGood and range1[0] <= range2[0]):
- betterRange = range1
- worseRange = range2
+ if (m.highIsGood and minRange[0] >= maxRange[0]) or (not m.highIsGood and minRange[0] <= maxRange[0]):
+ betterRange = minRange
+ worseRange = maxRange
else:
- betterRange = range2
- worseRange = range1
+ betterRange = maxRange
+ worseRange = minRange
- if range1[2] >= range2[2]:
- displayMaxRange = range1
- displayMinRange = range2
+ if minRange[1] >= maxRange[1]:
+ displayMaxRange = minRange
+ displayMinRange = maxRange
else:
- displayMaxRange = range2
- displayMinRange = range1
+ displayMaxRange = maxRange
+ displayMinRange = minRange
+
+ # If base value is outside of mutation range, make sure that center of slider
+ # corresponds to the value which is closest available to actual base value. It's
+ # how EVE handles it
+ if m.minValue <= m.baseValue <= m.maxValue:
+ sliderBaseValue = m.baseValue
+ else:
+ sliderBaseValue = max(m.minValue, min(m.maxValue, m.baseValue))
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
- font = parent.GetFont()
- font.SetWeight(wx.BOLD)
-
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName)
@@ -75,9 +95,9 @@ class ItemMutator(wx.Panel):
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
slider = AttributeSlider(parent=self,
- baseValue=m.attribute.unit.SimplifyValue(m.baseValue),
- minValue=displayMinRange[2],
- maxValue=displayMaxRange[2],
+ baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue),
+ minValue=displayMinRange[1],
+ maxValue=displayMaxRange[1],
inverse=displayMaxRange is worseRange)
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
diff --git a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py
index 458424dce..7c767c5fb 100644
--- a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py
+++ b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py
@@ -81,6 +81,11 @@ class PFContextMenuPref(PreferenceView):
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
+ self.rbBox8 = wx.RadioBox(panel, -1, "Fill with module", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
+ self.rbBox8.SetSelection(self.settings.get('moduleFill'))
+ rbSizerRow3.Add(self.rbBox8, 1, wx.TOP | wx.RIGHT, 5)
+ self.rbBox8.Bind(wx.EVT_RADIOBOX, self.OnSetting8Change)
+
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
panel.SetSizer(mainSizer)
@@ -107,6 +112,9 @@ class PFContextMenuPref(PreferenceView):
def OnSetting7Change(self, event):
self.settings.set('project', event.GetInt())
+ def OnSetting8Change(self, event):
+ self.settings.set('moduleFill', event.GetInt())
+
def getImage(self):
return BitmapLoader.getBitmap("settings_menu", "gui")
diff --git a/gui/builtinViewColumns/misc.py b/gui/builtinViewColumns/misc.py
index 85a0d545b..e9bff3d56 100644
--- a/gui/builtinViewColumns/misc.py
+++ b/gui/builtinViewColumns/misc.py
@@ -504,7 +504,7 @@ class Miscellanea(ViewColumn):
text = "{0}s".format(cycleTime)
tooltip = "Spoolup time"
return text, tooltip
- elif itemGroup in ("Siege Module", "Cynosural Field"):
+ elif itemGroup in ("Siege Module", "Cynosural Field Generator"):
amt = stuff.getModifiedItemAttr("consumptionQuantity")
if amt:
typeID = stuff.getModifiedItemAttr("consumptionType")
diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py
index a68614b83..56901c172 100644
--- a/gui/builtinViewColumns/price.py
+++ b/gui/builtinViewColumns/price.py
@@ -22,6 +22,7 @@ import wx
from eos.saveddata.cargo import Cargo
from eos.saveddata.drone import Drone
+from eos.saveddata.price import PriceStatus
from service.price import Price as ServicePrice
from gui.viewColumn import ViewColumn
from gui.bitmap_loader import BitmapLoader
@@ -45,13 +46,16 @@ class Price(ViewColumn):
if stuff.isEmpty:
return ""
- price = stuff.item.price
+ priceObj = stuff.item.price
- if not price or not price.isValid:
+ if not priceObj.isValid:
return False
# Fetch actual price as float to not modify its value on Price object
- price = price.price
+ price = priceObj.price
+
+ if price == 0:
+ return ""
if isinstance(stuff, Drone) or isinstance(stuff, Cargo):
price *= stuff.amount
@@ -63,10 +67,12 @@ class Price(ViewColumn):
def callback(item):
price = item[0]
- text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else ""
- if price.failed:
- text += " (!)"
- colItem.SetText(text)
+ textItems = []
+ if price.price:
+ textItems.append(formatAmount(price.price, 3, 3, 9, currency=True))
+ if price.status == PriceStatus.fail:
+ textItems.append("(!)")
+ colItem.SetText(" ".join(textItems))
display.SetItem(colItem)
diff --git a/gui/itemStats.py b/gui/itemStats.py
index 3768780e2..50a02f065 100644
--- a/gui/itemStats.py
+++ b/gui/itemStats.py
@@ -91,7 +91,7 @@ class ItemStatsDialog(wx.Dialog):
self.SetMinSize((300, 200))
if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room
- self.SetSize((580, 500))
+ self.SetSize((630, 550))
else:
self.SetSize((550, 500))
# self.SetMaxSize((500, -1))
@@ -168,8 +168,9 @@ class ItemStatsContainer(wx.Panel):
self.mutator = ItemMutator(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.mutator, "Mutations")
- self.desc = ItemDescription(self.nbContainer, stuff, item)
- self.nbContainer.AddPage(self.desc, "Description")
+ if item.description:
+ self.desc = ItemDescription(self.nbContainer, stuff, item)
+ self.nbContainer.AddPage(self.desc, "Description")
self.params = ItemParams(self.nbContainer, stuff, item, context)
self.nbContainer.AddPage(self.params, "Attributes")
diff --git a/scripts/iconIDs.yaml b/scripts/iconIDs.yaml
index 1fec32850..a2bdfc349 100644
--- a/scripts/iconIDs.yaml
+++ b/scripts/iconIDs.yaml
@@ -706,7 +706,7 @@
- tech2
iconFile: res:/ui/texture/icons/12_64_13.png
398:
- description: Corpse floating in space (male?). - Corpse
+ description: chemical item
iconFile: res:/ui/texture/icons/11_64_5.png
400:
description: Mineral - Pyerite
@@ -10432,6 +10432,66 @@
22076:
description: Gift Box with Ribbon
iconFile: res:/ui/texture/icons/76_64_3.png
+22077:
+ description: 49978_Male_outer_ExplorationSuit_M01_Types_ExplorationSuit_M01_W.png
+ iconFile: res:/UI/Asset/mannequin/outer/49978_Male_outer_ExplorationSuit_M01_Types_ExplorationSuit_M01_W.png
+22078:
+ description: 49980_Female_Outer_ExplorationSuit_F01_Types_ExplorationSuit_F01_W.png
+ iconFile: res:/UI/Asset/mannequin/outer/49980_Female_Outer_ExplorationSuit_F01_Types_ExplorationSuit_F01_W.png
+22079:
+ description: Preview for reward track augmentations
+ iconFile: res:/UI/Texture/Icons/RewardTrack/reward_Holiday2018_Augmentation1.png
+22080:
+ description: Preview for reward track augmentations
+ iconFile: res:/UI/Texture/Icons/RewardTrack/reward_Holiday2018_Augmentation2.png
+22081:
+ description: Holiday'18 Crate - Drake/Rupture Splash
+ iconFile: res:/UI/Texture/classes/ItemPacks/SplashImages/Holiday2018/splash_Holiday2018_DrakeRupture.png
+22082:
+ description: Holiday'18 Crate - ExpSuits
+ iconFile: res:/UI/Texture/classes/ItemPacks/SplashImages/Holiday2018/splash_Holiday2018_ExplorationSuits.png
+22084:
+ description: Holiday'18 Crate - Augmentation Set 1
+ iconFile: res:/UI/Texture/classes/ItemPacks/SplashImages/Holiday2018/splash_Holiday2018_FaceAugmentation1.png
+22085:
+ description: Holiday'18 Crate - Augmentation Set 2
+ iconFile: res:/UI/Texture/classes/ItemPacks/SplashImages/Holiday2018/splash_Holiday2018_FaceAugmentation2.png
+22086:
+ description: Holiday'18 Crate - Gnosis/Punisher Splash
+ iconFile: res:/UI/Texture/classes/ItemPacks/SplashImages/Holiday2018/splash_Holiday2018_GnosisPunisher.png
+22087:
+ description: 49978_Male_outer_ExplorationSuit_M01_Types_ExplorationSuit_M01_W.png
+ iconFile: res:/UI/Asset/mannequin/outer/49978_Male_outer_ExplorationSuit_M01_Types_ExplorationSuit_M01_W.png
+22088:
+ description: 49980_Female_Outer_ExplorationSuit_F01_Types_ExplorationSuit_F01_W.png
+ iconFile: res:/UI/Asset/mannequin/outer/49980_Female_Outer_ExplorationSuit_F01_Types_ExplorationSuit_F01_W.png
+22089:
+ description: 49984_Female_Makeup_Augmentations_Face_Paint_F01_Types_Face_Paint_F01_V0_Blue.png
+ iconFile: res:/UI/Asset/mannequin/makeup_augmentations/49984_Female_Makeup_Augmentations_Face_Paint_F01_Types_Face_Paint_F01_V0_Blue.png
+22090:
+ description: 49985_Female_Makeup_Augmentations_Face_Paint_F01_Types_Face_Paint_F01_V10_W.png
+ iconFile: res:/UI/Asset/mannequin/makeup_augmentations/49985_Female_Makeup_Augmentations_Face_Paint_F01_Types_Face_Paint_F01_V10_W.png
22091:
- description: Manually added mutadaptive RR icon path
- iconFile: res:/ui/texture/icons/modules/abyssalremoterepairer.png
+ backgrounds:
+ - blueprint
+ - blueprintCopy
+ description: Mutadaptive Remote Repairer
+ foregrounds:
+ - faction
+ - tech2
+ iconFile: res:/ui/texture/icons/modules/abyssalRemoteRepairer.png
+22092:
+ description: 49987_Male_Makeup_Augmentations_Face_Paint_M01_Types_Face_Paint_M01_V6_Blue.png
+ iconFile: res:/UI/Asset/mannequin/makeup_augmentations/49987_Male_Makeup_Augmentations_Face_Paint_M01_Types_Face_Paint_M01_V6_Blue.png
+22093:
+ description: Generic SKIN icon for crates containing multiple SKINs
+ iconFile: res:/UI/Texture/Icons/RewardTrack/crateSkinContainer.png
+22094:
+ description: Preview icon for exploration suit reward track winter'18
+ iconFile: res:/UI/Texture/Icons/RewardTrack/crateWinterExplorationSuit.png
+22095:
+ description: 49986_male_Makeup_Augmentations_Face_Paint_M01_Types_Face_Paint_M01_V10_W.png
+ iconFile: res:/UI/Asset/mannequin/makeup_augmentations/49986_male_Makeup_Augmentations_Face_Paint_M01_Types_Face_Paint_M01_V10_W.png
+22096:
+ description: Winter Login Campaign Banner
+ iconFile: res:/UI/Texture/LoginCampaigns/Winter_2018.png
diff --git a/scripts/jsonToSql.py b/scripts/jsonToSql.py
index 39e51d198..913b74b1c 100755
--- a/scripts/jsonToSql.py
+++ b/scripts/jsonToSql.py
@@ -28,6 +28,8 @@ sys.path.insert(0, os.path.realpath(os.path.join(path, '..')))
import json
import argparse
+import itertools
+
CATEGORIES_TO_REMOVE = [
30 # Apparel
@@ -174,6 +176,119 @@ def main(db, json_path):
newData.append(newRow)
return newData
+ def fillReplacements(tables):
+
+ def compareAttrs(attrs1, attrs2, attrHig):
+ """
+ Compares received attribute sets. Returns:
+ - 0 if sets are different
+ - 1 if sets are exactly the same
+ - 2 if first set is strictly better
+ - 3 if second set is strictly better
+ """
+ if set(attrs1) != set(attrs2):
+ return 0
+ if all(attrs1[aid] == attrs2[aid] for aid in attrs1):
+ return 1
+ if all(
+ (attrs1[aid] >= attrs2[aid] and attrHig[aid]) or
+ (attrs1[aid] <= attrs2[aid] and not attrHig[aid])
+ for aid in attrs1
+ ):
+ return 2
+ if all(
+ (attrs2[aid] >= attrs1[aid] and attrHig[aid]) or
+ (attrs2[aid] <= attrs1[aid] and not attrHig[aid])
+ for aid in attrs1
+ ):
+ return 3
+ return 0
+
+ skillReqAttribs = {
+ 182: 277,
+ 183: 278,
+ 184: 279,
+ 1285: 1286,
+ 1289: 1287,
+ 1290: 1288}
+ skillReqAttribsFlat = set(skillReqAttribs.keys()).union(skillReqAttribs.values())
+ # Get data on type groups
+ typesGroups = {}
+ for row in tables['evetypes']:
+ typesGroups[row['typeID']] = row['groupID']
+ # Get data on type attributes
+ typesNormalAttribs = {}
+ typesSkillAttribs = {}
+ for row in tables['dgmtypeattribs']:
+ attributeID = row['attributeID']
+ if attributeID in skillReqAttribsFlat:
+ typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
+ typeSkillAttribs[row['attributeID']] = row['value']
+ # Ignore these attributes for comparison purposes
+ elif attributeID in (
+ 422, # techLevel
+ 633, # metaLevel
+ 1692 # metaGroupID
+ ):
+ continue
+ else:
+ typeNormalAttribs = typesNormalAttribs.setdefault(row['typeID'], {})
+ typeNormalAttribs[row['attributeID']] = row['value']
+ # Get data on skill requirements
+ typesSkillReqs = {}
+ for typeID, typeAttribs in typesSkillAttribs.items():
+ typeSkillAttribs = typesSkillAttribs.get(typeID, {})
+ if not typeSkillAttribs:
+ continue
+ typeSkillReqs = typesSkillReqs.setdefault(typeID, {})
+ for skillreqTypeAttr, skillreqLevelAttr in skillReqAttribs.items():
+ try:
+ skillType = int(typeSkillAttribs[skillreqTypeAttr])
+ skillLevel = int(typeSkillAttribs[skillreqLevelAttr])
+ except (KeyError, ValueError):
+ continue
+ typeSkillReqs[skillType] = skillLevel
+ # Get data on attribute highIsGood flag
+ attrHig = {}
+ for row in tables['dgmattribs']:
+ attrHig[row['attributeID']] = bool(row['highIsGood'])
+ # As EVE affects various types mostly depending on their group or skill requirements,
+ # we're going to group various types up this way
+ groupedData = {}
+ for row in tables['evetypes']:
+ typeID = row['typeID']
+ typeAttribs = typesNormalAttribs.get(typeID, {})
+ # Ignore stuff w/o attributes
+ if not typeAttribs:
+ continue
+ # We need only skill types, not levels for keys
+ typeSkillreqs = frozenset(typesSkillReqs.get(typeID, {}))
+ typeGroup = typesGroups[typeID]
+ groupData = groupedData.setdefault((typeGroup, typeSkillreqs), [])
+ groupData.append((typeID, typeAttribs))
+ same = {}
+ better = {}
+ # Now, go through composed groups and for every item within it find items which are
+ # the same and which are better
+ for groupData in groupedData.values():
+ for type1, type2 in itertools.combinations(groupData, 2):
+ comparisonResult = compareAttrs(type1[1], type2[1], attrHig)
+ # Equal
+ if comparisonResult == 1:
+ same.setdefault(type1[0], set()).add(type2[0])
+ same.setdefault(type2[0], set()).add(type1[0])
+ # First is better
+ elif comparisonResult == 2:
+ better.setdefault(type2[0], set()).add(type1[0])
+ # Second is better
+ elif comparisonResult == 3:
+ better.setdefault(type1[0], set()).add(type2[0])
+ # Put this data into types table so that normal process hooks it up
+ for row in tables['evetypes']:
+ typeID = row['typeID']
+ row['replaceSame'] = ','.join('{}'.format(tid) for tid in sorted(same.get(typeID, ())))
+ row['replaceBetter'] = ','.join('{}'.format(tid) for tid in sorted(better.get(typeID, ())))
+
data = {}
# Dump all data to memory so we can easely cross check ignored rows
@@ -190,6 +305,8 @@ def main(db, json_path):
tableData = convertClones(tableData)
data[jsonName] = tableData
+ fillReplacements(data)
+
# Set with typeIDs which we will have in our database
# Sometimes CCP unpublishes some items we want to have published, we
# can do it here - just add them to initial set
diff --git a/service/marketSources/evemarketdata.py b/service/marketSources/evemarketdata.py
index bd1f8f3af..15edc92ef 100644
--- a/service/marketSources/evemarketdata.py
+++ b/service/marketSources/evemarketdata.py
@@ -22,6 +22,7 @@ from xml.dom import minidom
from logbook import Logger
+from eos.saveddata.price import PriceStatus
from service.network import Network
from service.price import Price, TIMEOUT, VALIDITY
@@ -52,6 +53,7 @@ class EveMarketData(object):
price = float(type_.firstChild.data)
except (TypeError, ValueError):
pyfalog.warning("Failed to get price for: {0}", type_)
+ continue
# Fill price data
priceobj = priceMap[typeID]
@@ -61,11 +63,10 @@ class EveMarketData(object):
if price != 0:
priceobj.price = price
priceobj.time = time.time() + VALIDITY
+ priceobj.status = PriceStatus.success
else:
priceobj.time = time.time() + TIMEOUT
- priceobj.failed = None
-
# delete price from working dict
del priceMap[typeID]
diff --git a/service/marketSources/evemarketer.py b/service/marketSources/evemarketer.py
index a2728ddfb..478de4371 100644
--- a/service/marketSources/evemarketer.py
+++ b/service/marketSources/evemarketer.py
@@ -22,13 +22,14 @@ from xml.dom import minidom
from logbook import Logger
+from eos.saveddata.price import PriceStatus
from service.network import Network
from service.price import Price, VALIDITY
pyfalog = Logger(__name__)
-class EveCentral(object):
+class EveMarketer(object):
name = "evemarketer"
@@ -61,10 +62,10 @@ class EveCentral(object):
priceobj = priceMap[typeID]
priceobj.price = percprice
priceobj.time = time.time() + VALIDITY
- priceobj.failed = None
+ priceobj.status = PriceStatus.success
# delete price from working dict
del priceMap[typeID]
-Price.register(EveCentral)
+Price.register(EveMarketer)
diff --git a/service/port/efs.py b/service/port/efs.py
index 0e021902c..51ff5c518 100755
--- a/service/port/efs.py
+++ b/service/port/efs.py
@@ -1,13 +1,8 @@
-import inspect
-import os
-import platform
-import re
-import sys
-import traceback
import json
import eos.db
from math import log
+from numbers import Number
from config import version as pyfaVersion
from service.fit import Fit
from service.market import Market
@@ -15,9 +10,11 @@ from eos.enum import Enum
from eos.saveddata.module import Hardpoint, Slot, Module, State
from eos.saveddata.drone import Drone
from eos.effectHandlerHelpers import HandledList
-from eos.db import gamedata_session, getItemsByCategory, getCategory, getAttributeInfo, getGroup
-from eos.gamedata import Category, Group, Item, Traits, Attribute, Effect, ItemEffect
+from eos.db import gamedata_session, getCategory, getAttributeInfo, getGroup
+from eos.gamedata import Attribute, Effect, Group, Item, ItemEffect
from eos.utils.spoolSupport import SpoolType, SpoolOptions
+from gui.fitCommands.calc.fitAddModule import FitAddModuleCommand
+from gui.fitCommands.calc.fitRemoveModule import FitRemoveModuleCommand
from logbook import Logger
pyfalog = Logger(__name__)
@@ -30,9 +27,9 @@ class RigSize(Enum):
CAPITAL = 4
-class EfsPort():
+class EfsPort:
wepTestSet = {}
- version = 0.02
+ version = 0.03
@staticmethod
def attrDirectMap(values, target, source):
@@ -72,12 +69,12 @@ class EfsPort():
if propID is None:
return None
- sFit.appendModule(fitID, propID)
+ FitAddModuleCommand(fitID, propID).Do()
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
mwdPropSpeed = fit.maxSpeed
mwdPosition = list(filter(lambda mod: mod.item and mod.item.ID == propID, fit.modules))[0].position
- sFit.removeModule(fitID, mwdPosition)
+ FitRemoveModuleCommand(fitID, [mwdPosition]).Do()
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
return mwdPropSpeed
@@ -115,6 +112,9 @@ class EfsPort():
"Titan Phenomena Generator", "Non-Repeating Hardeners"
]
projectedMods = list(filter(lambda mod: mod.item and mod.item.group.name in modGroupNames, fit.modules))
+ # Sort projections to prevent the order needlessly changing as pyfa updates.
+ projectedMods.sort(key=lambda mod: mod.item.ID)
+ projectedMods.sort(key=lambda mod: mod.item.group.ID)
projections = []
for mod in projectedMods:
maxRangeDefault = 0
@@ -191,7 +191,7 @@ class EfsPort():
return projections
# Note that unless padTypeIDs is True all 0s will be removed from modTypeIDs in the return.
- # They always are added initally for the sake of brevity, as this option may not be retained long term.
+ # They always are added initially for the sake of brevity, as this option may not be retained long term.
@staticmethod
def getModuleInfo(fit, padTypeIDs=False):
moduleNames = []
@@ -303,9 +303,9 @@ class EfsPort():
def getWeaponSystemData(fit):
weaponSystems = []
groups = {}
- # TODO: fetch spoolup option
+ # Export at maximum spool for consistency, spoolup data is exported anyway.
defaultSpoolValue = 1
- spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)
+ spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, True)
for mod in fit.modules:
if mod.getDps(spoolOptions=spoolOptions).total > 0:
# Group weapon + ammo combinations that occur more than once
@@ -344,7 +344,7 @@ class EfsPort():
aoeFieldRange = stats.getModifiedItemAttr("empFieldRange")
# This also covers non-bomb weapons with dps values and no hardpoints, most notably targeted doomsdays.
typeing = "SmartBomb"
- # Targeted DDs are the only non drone/fighter weapon without an explict max range
+ # Targeted DDs are the only non drone/fighter weapon without an explicit max range
if stats.item.group.name == 'Super Weapon' and stats.maxRange is None:
maxRange = 300000
else:
@@ -515,7 +515,7 @@ class EfsPort():
# Since the effect modules are fairly opaque a mock test fit is used to test the impact of traits.
# standin class used to prevent . notation causing issues when used as an arg
- class standin():
+ class standin:
pass
tf = standin()
tf.modules = HandledList(turrets + launchers)
@@ -551,7 +551,7 @@ class EfsPort():
@staticmethod
def getShipSize(groupID):
- # Size groupings are somewhat arbitrary but allow for a more managable number of top level groupings in a tree structure.
+ # Size groupings are somewhat arbitrary but allow for a more manageable number of top level groupings in a tree structure.
frigateGroupNames = ["Frigate", "Shuttle", "Corvette", "Assault Frigate", "Covert Ops", "Interceptor",
"Stealth Bomber", "Electronic Attack Ship", "Expedition Frigate", "Logistics Frigate"]
destroyerGroupNames = ["Destroyer", "Interdictor", "Tactical Destroyer", "Command Destroyer"]
@@ -625,9 +625,29 @@ class EfsPort():
}
resonance = {"hull": hullResonance, "armor": armorResonance, "shield": shieldResonance}
shipSize = EfsPort.getShipSize(fit.ship.item.groupID)
- # TODO: fetch spoolup option
+ # Export at maximum spool for consistency, spoolup data is exported anyway.
defaultSpoolValue = 1
- spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)
+ spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, True)
+
+ def roundNumbers(data, digits):
+ if isinstance(data, str):
+ return
+ if isinstance(data, dict):
+ for key in data:
+ if isinstance(data[key], Number):
+ data[key] = round(data[key], digits)
+ else:
+ roundNumbers(data[key], digits)
+ if isinstance(data, list) or isinstance(data, tuple):
+ for val in data:
+ roundNumbers(val, digits)
+ if isinstance(data, Number):
+ rounded = round(data, digits)
+ if data != rounded:
+ pyfalog.error("Error rounding numbers for EFS export, export may be inconsistent."
+ "This suggests the format has been broken somewhere.")
+ return
+
try:
dataDict = {
"name": fitName, "ehp": fit.ehp, "droneDPS": fit.getDroneDps().total,
@@ -650,9 +670,12 @@ class EfsPort():
"modTypeIDs": modTypeIDs, "moduleNames": moduleNames,
"pyfaVersion": pyfaVersion, "efsExportVersion": EfsPort.version
}
- except TypeError:
+ # Recursively round any numbers in dicts to 6 decimal places.
+ # This prevents meaningless rounding errors from changing the output whenever pyfa changes.
+ roundNumbers(dataDict, 6)
+ except TypeError as e:
pyfalog.error("Error parsing fit:" + str(fit))
- pyfalog.error(TypeError)
+ pyfalog.error(e)
dataDict = {"name": fitName + "Fit could not be correctly parsed"}
export = json.dumps(dataDict, skipkeys=True)
return export
diff --git a/service/price.py b/service/price.py
index 9a8485274..3028b74ad 100644
--- a/service/price.py
+++ b/service/price.py
@@ -26,6 +26,7 @@ import wx
from logbook import Logger
from eos import db
+from eos.saveddata.price import PriceStatus
from service.fit import Fit
from service.market import Market
from service.network import TimeoutError
@@ -85,13 +86,18 @@ class Price(object):
toRequest = set()
# Compose list of items we're going to request
- for typeID in priceMap:
+ for typeID in tuple(priceMap):
# Get item object
item = 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 is not None and item.marketGroupID:
- toRequest.add(typeID)
+ if item is None:
+ continue
+ if not item.marketGroupID:
+ priceMap[typeID].status = PriceStatus.notSupported
+ del priceMap[typeID]
+ continue
+ toRequest.add(typeID)
# Do not waste our time if all items are not on the market
if len(toRequest) == 0:
@@ -117,11 +123,10 @@ class Price(object):
except TimeoutError:
# Timeout error deserves special treatment
pyfalog.warning("Price fetch timout")
- for typeID in priceMap.keys():
+ for typeID in tuple(priceMap):
priceobj = priceMap[typeID]
priceobj.time = time.time() + TIMEOUT
- priceobj.failed = True
-
+ priceobj.status = PriceStatus.fail
del priceMap[typeID]
except Exception as ex:
# something happened, try another source
@@ -134,7 +139,7 @@ class Price(object):
for typeID in priceMap.keys():
priceobj = priceMap[typeID]
priceobj.time = time.time() + REREQUEST
- priceobj.failed = True
+ priceobj.status = PriceStatus.fail
@classmethod
def fitItemsList(cls, fit):
@@ -172,8 +177,8 @@ class Price(object):
def getPrices(self, objitems, callback, waitforthread=False):
"""Get prices for multiple typeIDs"""
requests = []
+ sMkt = Market.getInstance()
for objitem in objitems:
- sMkt = Market.getInstance()
item = sMkt.getItem(objitem)
requests.append(item.price)
@@ -197,6 +202,7 @@ class Price(object):
class PriceWorkerThread(threading.Thread):
+
def __init__(self):
threading.Thread.__init__(self)
self.name = "PriceWorker"
diff --git a/service/settings.py b/service/settings.py
index 39f8def9c..df5038d1f 100644
--- a/service/settings.py
+++ b/service/settings.py
@@ -525,6 +525,7 @@ class ContextMenuSettings(object):
"tacticalMode" : 1,
"targetResists" : 1,
"whProjector" : 1,
+ "moduleFill" : 1,
}
self.ContextMenuDefaultSettings = SettingsProvider.getInstance().getSettings("pyfaContextMenuSettings", ContextMenuDefaultSettings)