Merge remote-tracking branch 'origin/master' into blitzmann_CIUpdates
# Conflicts: # dist_assets/mac/pyfa.spec
This commit is contained in:
@@ -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
|
||||
|
||||
17
eos/db/migrations/upgrade30.py
Normal file
17
eos/db/migrations/upgrade30.py
Normal file
@@ -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;")
|
||||
@@ -17,17 +17,20 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
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,
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceCapNeed
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (493 of 947)
|
||||
# Items from category: Charge (493 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceRange
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (587 of 947)
|
||||
# Items from category: Charge (587 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
@@ -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)
|
||||
10
eos/effects/miningdurationmultiplieronline.py
Normal file
10
eos/effects/miningdurationmultiplieronline.py
Normal file
@@ -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"))
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# skillBonusDroneDurability
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (4 of 4)
|
||||
# Skill: Drone Durability
|
||||
type = "passive"
|
||||
|
||||
|
||||
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
@@ -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"))
|
||||
@@ -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"
|
||||
|
||||
|
||||
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
@@ -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"))
|
||||
10
eos/effects/stripminerdurationmultiplier.py
Normal file
10
eos/effects/stripminerdurationmultiplier.py
Normal file
@@ -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"))
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -18,24 +18,30 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ class AttributeSlider(wx.Panel):
|
||||
|
||||
self.UserMinValue = minValue
|
||||
self.UserMaxValue = maxValue
|
||||
print(self.UserMinValue, self.UserMaxValue)
|
||||
|
||||
self.inverse = inverse
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -525,6 +525,7 @@ class ContextMenuSettings(object):
|
||||
"tacticalMode" : 1,
|
||||
"targetResists" : 1,
|
||||
"whProjector" : 1,
|
||||
"moduleFill" : 1,
|
||||
}
|
||||
|
||||
self.ContextMenuDefaultSettings = SettingsProvider.getInstance().getSettings("pyfaContextMenuSettings", ContextMenuDefaultSettings)
|
||||
|
||||
Reference in New Issue
Block a user