Merge remote-tracking branch 'origin/master' into blitzmann_CIUpdates

# Conflicts:
#	dist_assets/mac/pyfa.spec
This commit is contained in:
Ryan Holmes
2019-02-17 15:52:57 -05:00
47 changed files with 473 additions and 124 deletions

View File

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

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
# ammoInfluenceCapNeed
#
# Used by:
# Items from category: Charge (493 of 947)
# Items from category: Charge (493 of 949)
type = "passive"

View File

@@ -1,7 +1,7 @@
# ammoInfluenceRange
#
# Used by:
# Items from category: Charge (587 of 947)
# Items from category: Charge (587 of 949)
type = "passive"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
# skillBonusDroneDurability
#
# Used by:
# Implants from group: Cyber Drones (4 of 4)
# Skill: Drone Durability
type = "passive"

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

BIN
eve.db

Binary file not shown.

View File

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

View File

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

View File

@@ -58,7 +58,6 @@ class AttributeSlider(wx.Panel):
self.UserMinValue = minValue
self.UserMaxValue = maxValue
print(self.UserMinValue, self.UserMaxValue)
self.inverse = inverse

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -525,6 +525,7 @@ class ContextMenuSettings(object):
"tacticalMode" : 1,
"targetResists" : 1,
"whProjector" : 1,
"moduleFill" : 1,
}
self.ContextMenuDefaultSettings = SettingsProvider.getInstance().getSettings("pyfaContextMenuSettings", ContextMenuDefaultSettings)