Merge branch 'origin_master' into effect_rollup
This commit is contained in:
@@ -129,7 +129,8 @@ def Saveddata():
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.booster import Booster
|
||||
|
||||
@@ -139,7 +140,7 @@ def Saveddata():
|
||||
'Fit' : Fit,
|
||||
'Character': Character,
|
||||
'Module' : Module,
|
||||
'State' : State,
|
||||
'State' : FittingModuleState,
|
||||
'Booster' : Booster,
|
||||
}
|
||||
return helper
|
||||
|
||||
14
config.py
14
config.py
@@ -6,7 +6,7 @@ import wx
|
||||
from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \
|
||||
StreamHandler, TimedRotatingFileHandler, WARNING
|
||||
import hashlib
|
||||
from eos.const import Slot
|
||||
from eos.const import FittingSlot
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
@@ -50,11 +50,11 @@ LOGLEVEL_MAP = {
|
||||
}
|
||||
|
||||
slotColourMap = {
|
||||
Slot.LOW: wx.Colour(250, 235, 204), # yellow = low slots
|
||||
Slot.MED: wx.Colour(188, 215, 241), # blue = mid slots
|
||||
Slot.HIGH: wx.Colour(235, 204, 209), # red = high slots
|
||||
Slot.RIG: '',
|
||||
Slot.SUBSYSTEM: ''
|
||||
FittingSlot.LOW: wx.Colour(250, 235, 204), # yellow = low slots
|
||||
FittingSlot.MED: wx.Colour(188, 215, 241), # blue = mid slots
|
||||
FittingSlot.HIGH: wx.Colour(235, 204, 209), # red = high slots
|
||||
FittingSlot.RIG: '',
|
||||
FittingSlot.SUBSYSTEM: ''
|
||||
}
|
||||
|
||||
def getClientSecret():
|
||||
@@ -95,7 +95,7 @@ def defPaths(customSavePath=None):
|
||||
global pyfaPath
|
||||
global savePath
|
||||
global saveDB
|
||||
global gameDB
|
||||
global gameDB
|
||||
global saveInRoot
|
||||
global logPath
|
||||
global cipher
|
||||
|
||||
@@ -21,13 +21,14 @@ if istravis is True or hasattr(sys, '_called_from_test'):
|
||||
# Running in Travis. Run saveddata database in memory.
|
||||
saveddata_connectionstring = 'sqlite:///:memory:'
|
||||
else:
|
||||
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata-py3-db.db"))
|
||||
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db"))
|
||||
|
||||
pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring)
|
||||
|
||||
settings = {
|
||||
"useStaticAdaptiveArmorHardener": False,
|
||||
"strictSkillLevels": True,
|
||||
"globalDefaultSpoolupPercentage": 1.0
|
||||
}
|
||||
|
||||
# Autodetect path, only change if the autodetection bugs out.
|
||||
|
||||
75
eos/const.py
75
eos/const.py
@@ -1,8 +1,30 @@
|
||||
from eos.enum import Enum
|
||||
# =============================================================================
|
||||
# Copyright (C) 2019 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
from enum import IntEnum,unique
|
||||
|
||||
|
||||
|
||||
class Slot(Enum):
|
||||
@unique
|
||||
class FittingSlot(IntEnum):
|
||||
"""
|
||||
Contains slots for ship fittings
|
||||
"""
|
||||
# These are self-explanatory
|
||||
LOW = 1
|
||||
MED = 2
|
||||
@@ -24,3 +46,50 @@ class Slot(Enum):
|
||||
FS_LIGHT = 13
|
||||
FS_SUPPORT = 14
|
||||
FS_HEAVY = 15
|
||||
|
||||
|
||||
@unique
|
||||
class ImplantLocation(IntEnum):
|
||||
"""
|
||||
Contains location of the implant
|
||||
"""
|
||||
FIT = 0
|
||||
CHARACTER = 1
|
||||
|
||||
|
||||
@unique
|
||||
class CalcType(IntEnum):
|
||||
"""
|
||||
Contains location of the calculation
|
||||
"""
|
||||
LOCAL = 0
|
||||
PROJECTED = 1
|
||||
COMMAND = 2
|
||||
|
||||
|
||||
@unique
|
||||
class FittingModuleState(IntEnum):
|
||||
"""
|
||||
Contains the state of a fitting module
|
||||
"""
|
||||
OFFLINE = -1
|
||||
ONLINE = 0
|
||||
ACTIVE = 1
|
||||
OVERHEATED = 2
|
||||
|
||||
|
||||
@unique
|
||||
class FittingHardpoint(IntEnum):
|
||||
"""
|
||||
Contains the types of a fitting hardpoint
|
||||
"""
|
||||
NONE = 0
|
||||
MISSILE = 1
|
||||
TURRET = 2
|
||||
|
||||
|
||||
@unique
|
||||
class SpoolType(IntEnum):
|
||||
SCALE = 0 # [0..1]
|
||||
TIME = 1 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||
|
||||
@@ -41,8 +41,7 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("iconID", Integer),
|
||||
Column("graphicID", Integer),
|
||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
||||
Column("replaceSame", String),
|
||||
Column("replaceBetter", String))
|
||||
Column("replacements", String))
|
||||
|
||||
from .metaGroup import metatypes_table # noqa
|
||||
from .traits import traits_table # noqa
|
||||
|
||||
@@ -32,6 +32,4 @@ prices_table = Table("prices", saveddata_meta,
|
||||
Column("status", Integer, nullable=False))
|
||||
|
||||
|
||||
mapper(Price, prices_table, properties={
|
||||
"_Price__price": prices_table.c.price,
|
||||
})
|
||||
mapper(Price, prices_table)
|
||||
|
||||
@@ -21,6 +21,7 @@ import sys
|
||||
|
||||
from sqlalchemy.sql import and_
|
||||
from sqlalchemy import desc, select
|
||||
from sqlalchemy import func
|
||||
|
||||
from eos.db import saveddata_session, sd_lock
|
||||
from eos.db.saveddata.fit import projectedFits_table
|
||||
@@ -283,6 +284,12 @@ def countAllFits():
|
||||
return count
|
||||
|
||||
|
||||
def countFitGroupedByShip():
|
||||
with sd_lock:
|
||||
count = eos.db.saveddata_session.query(Fit.shipID, func.count(Fit.shipID)).group_by(Fit.shipID).all()
|
||||
return count
|
||||
|
||||
|
||||
def countFitsWithShip(lookfor, ownerID=None, where=None, eager=None):
|
||||
"""
|
||||
Get all the fits using a certain ship.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "early"
|
||||
@@ -19,10 +19,10 @@ def handler(fit, module, context):
|
||||
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
|
||||
if module.charge is not None and module.charge.ID == 45010:
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
|
||||
mod.state = State.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
|
||||
mod.state = State.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
else:
|
||||
if module.charge is None:
|
||||
fit.ship.boostItemAttr("mass", module.getModifiedItemAttr("massBonusPercentage"))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Warp Scrambler (27 of 27)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
|
||||
runTime = "early"
|
||||
type = "projected", "active"
|
||||
@@ -16,10 +16,10 @@ def handler(fit, module, context):
|
||||
|
||||
# this is such a dirty hack
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.state > State.ONLINE and (
|
||||
if not mod.isEmpty and mod.state > FittingModuleState.ONLINE and (
|
||||
mod.item.requiresSkill("Micro Jump Drive Operation") or
|
||||
mod.item.requiresSkill("High Speed Maneuvering")
|
||||
):
|
||||
mod.state = State.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
|
||||
mod.state = State.ONLINE
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Energy Neutralizer (54 of 54)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
type = "active", "projected"
|
||||
|
||||
|
||||
def handler(fit, src, context, **kwargs):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
|
||||
if 'effect' in kwargs:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Structure Modules from group: Structure Energy Neutralizer (5 of 5)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
type = "active", "projected"
|
||||
@@ -11,7 +11,7 @@ type = "active", "projected"
|
||||
def handler(fit, src, context, **kwargs):
|
||||
amount = 0
|
||||
if "projected" in context:
|
||||
if (hasattr(src, "state") and src.state >= State.ACTIVE) or hasattr(src, "amountActive"):
|
||||
if (hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or hasattr(src, "amountActive"):
|
||||
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
|
||||
if 'effect' in kwargs:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Structure Modules from group: Structure Warp Scrambler (2 of 2)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
|
||||
# Not used by any item
|
||||
runTime = "early"
|
||||
@@ -14,7 +14,7 @@ def handler(fit, module, context):
|
||||
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
|
||||
if module.charge is not None and module.charge.ID == 47336:
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
|
||||
mod.state = State.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
|
||||
mod.state = State.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
# Used by:
|
||||
# Module: Energy Neutralization Burst Projector
|
||||
# Structure Module: Standup Energy Neutralization Burst Projector
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
type = "active", "projected"
|
||||
|
||||
|
||||
def handler(fit, src, context, **kwargs):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
|
||||
if 'effect' in kwargs:
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
#
|
||||
# Used by:
|
||||
# Drones from group: Energy Neutralizer Drone (3 of 3)
|
||||
from eos.saveddata.module import State
|
||||
from eos.const import FittingModuleState
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
type = "active", "projected"
|
||||
|
||||
|
||||
def handler(fit, src, context, **kwargs):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
|
||||
hasattr(src, "amountActive")):
|
||||
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
time = src.getModifiedItemAttr("energyNeutralizerDuration")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# Modules from group: Mutadaptive Remote Armor Repairer (5 of 5)
|
||||
|
||||
|
||||
import eos.config
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, resolveSpoolOptions
|
||||
|
||||
|
||||
@@ -17,8 +18,7 @@ def handler(fit, container, context, **kwargs):
|
||||
cycleTime = container.getModifiedItemAttr("duration") / 1000.0
|
||||
repSpoolMax = container.getModifiedItemAttr("repairMultiplierBonusMax")
|
||||
repSpoolPerCycle = container.getModifiedItemAttr("repairMultiplierBonusPerCycle")
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
spoolType, spoolAmount = resolveSpoolOptions(SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False), container)
|
||||
rps = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, spoolType, spoolAmount)[0]) / cycleTime
|
||||
rpsPreSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 0)[0]) / cycleTime
|
||||
|
||||
17
eos/effects/structurecombatrigsecuritymodification.py
Normal file
17
eos/effects/structurecombatrigsecuritymodification.py
Normal file
@@ -0,0 +1,17 @@
|
||||
runTime = "early"
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, module, context):
|
||||
secModifier = module.getModifiedItemAttr("securityModifier")
|
||||
module.multiplyItemAttr("structureRigDoomsdayDamageLossTargetBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigScanResBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigPDRangeBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigPDCapUseBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigMissileExploVeloBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigMissileVelocityBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigEwarOptimalBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigEwarFalloffBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigEwarCapUseBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigMissileExplosionRadiusBonus", secModifier)
|
||||
module.multiplyItemAttr("structureRigMaxTargetRangeBonus", secModifier)
|
||||
23
eos/enum.py
23
eos/enum.py
@@ -1,23 +0,0 @@
|
||||
class Enum(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def getTypes(cls):
|
||||
for stuff in cls.__dict__:
|
||||
if stuff.upper() == stuff:
|
||||
yield stuff
|
||||
|
||||
@classmethod
|
||||
def getName(cls, v):
|
||||
map = getattr(cls, "_map", None)
|
||||
if map is None:
|
||||
map = cls._map = {}
|
||||
for type in cls.getTypes():
|
||||
map[cls.getValue(type)] = type
|
||||
|
||||
return map.get(v)
|
||||
|
||||
@classmethod
|
||||
def getValue(cls, type):
|
||||
return cls.__dict__[type]
|
||||
@@ -20,7 +20,7 @@
|
||||
from math import log, sin, radians, exp
|
||||
|
||||
from eos.graph import Graph
|
||||
from eos.saveddata.module import State, Hardpoint
|
||||
from eos.const import FittingModuleState, FittingHardpoint
|
||||
from logbook import Logger
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -46,7 +46,7 @@ class FitDpsGraph(Graph):
|
||||
abssort = lambda _val: -abs(_val - 1)
|
||||
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.state >= State.ACTIVE:
|
||||
if not mod.isEmpty and mod.state >= FittingModuleState.ACTIVE:
|
||||
if "remoteTargetPaintFalloff" in mod.item.effects or "structureModuleEffectTargetPainter" in mod.item.effects:
|
||||
ew['signatureRadius'].append(
|
||||
1 + (mod.getModifiedItemAttr("signatureRadiusBonus") / 100) * self.calculateModuleMultiplier(
|
||||
@@ -76,12 +76,12 @@ class FitDpsGraph(Graph):
|
||||
|
||||
for mod in fit.modules:
|
||||
dps = mod.getDps(targetResists=fit.targetResists).total
|
||||
if mod.hardpoint == Hardpoint.TURRET:
|
||||
if mod.state >= State.ACTIVE:
|
||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
||||
if mod.state >= FittingModuleState.ACTIVE:
|
||||
total += dps * self.calculateTurretMultiplier(mod, data)
|
||||
|
||||
elif mod.hardpoint == Hardpoint.MISSILE:
|
||||
if mod.state >= State.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
|
||||
elif mod.hardpoint == FittingHardpoint.MISSILE:
|
||||
if mod.state >= FittingModuleState.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
|
||||
total += dps * self.calculateMissileMultiplier(mod, data)
|
||||
|
||||
if distance <= fit.extraAttributes["droneControlRange"]:
|
||||
|
||||
@@ -30,6 +30,7 @@ pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class Booster(HandledItem, ItemAttrShortcut):
|
||||
|
||||
def __init__(self, item):
|
||||
self.__item = item
|
||||
|
||||
@@ -147,3 +148,17 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
copyEffect.active = sideEffect.active
|
||||
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
active = self.active
|
||||
sideEffectStates = {se.effectID: se.active for se in self.sideEffects}
|
||||
Booster.__init__(self, item)
|
||||
self.active = active
|
||||
for sideEffect in self.sideEffects:
|
||||
if sideEffect.effectID in sideEffectStates:
|
||||
sideEffect.active = sideEffectStates[sideEffect.effectID]
|
||||
|
||||
def __repr__(self):
|
||||
return "Booster(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
)
|
||||
|
||||
@@ -89,3 +89,13 @@ class Cargo(HandledItem, ItemAttrShortcut):
|
||||
copy = Cargo(self.item)
|
||||
copy.amount = self.amount
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self.amount
|
||||
Cargo.__init__(self, item)
|
||||
self.amount = amount
|
||||
|
||||
def __repr__(self):
|
||||
return "Cargo(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
)
|
||||
|
||||
@@ -296,6 +296,13 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copy.amountActive = self.amountActive
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self.amount
|
||||
amountActive = self.amountActive
|
||||
Drone.__init__(self, item)
|
||||
self.amount = amount
|
||||
self.amountActive = amountActive
|
||||
|
||||
def fits(self, fit):
|
||||
fitDroneGroupLimits = set()
|
||||
for i in range(1, 3):
|
||||
|
||||
@@ -25,8 +25,8 @@ import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.saveddata.module import Slot
|
||||
from eos.utils.stats import DmgTypes
|
||||
from eos.const import FittingSlot
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -116,12 +116,12 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
def __calculateSlot(self, item):
|
||||
types = {
|
||||
"Light" : Slot.F_LIGHT,
|
||||
"Support": Slot.F_SUPPORT,
|
||||
"Heavy" : Slot.F_HEAVY,
|
||||
"StandupLight": Slot.FS_LIGHT,
|
||||
"StandupSupport": Slot.FS_SUPPORT,
|
||||
"StandupHeavy": Slot.FS_HEAVY
|
||||
"Light" : FittingSlot.F_LIGHT,
|
||||
"Support": FittingSlot.F_SUPPORT,
|
||||
"Heavy" : FittingSlot.F_HEAVY,
|
||||
"StandupLight": FittingSlot.FS_LIGHT,
|
||||
"StandupSupport": FittingSlot.FS_SUPPORT,
|
||||
"StandupHeavy": FittingSlot.FS_HEAVY
|
||||
}
|
||||
|
||||
for t, slot in types.items():
|
||||
@@ -355,6 +355,17 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copyAbility.active = ability.active
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self.amount
|
||||
active = self.active
|
||||
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
|
||||
Fighter.__init__(self, item)
|
||||
self.amount = amount
|
||||
self.active = active
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in abilityEffectStates:
|
||||
ability.active = abilityEffectStates[ability.effectID]
|
||||
|
||||
def fits(self, fit):
|
||||
# If ships doesn't support this type of fighter, don't add it
|
||||
if fit.getNumSlots(self.slot) == 0:
|
||||
|
||||
@@ -28,28 +28,19 @@ from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
from eos import capSim
|
||||
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneCargoList, HandledImplantBoosterList, HandledProjectedDroneList, HandledProjectedModList
|
||||
from eos.enum import Enum
|
||||
from eos.const import ImplantLocation, CalcType, FittingSlot
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.module import Module, State, Slot, Hardpoint
|
||||
from eos.const import FittingModuleState, FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from eos.utils.stats import DmgTypes
|
||||
from logbook import Logger
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class ImplantLocation(Enum):
|
||||
FIT = 0
|
||||
CHARACTER = 1
|
||||
|
||||
|
||||
class CalcType(Enum):
|
||||
LOCAL = 0
|
||||
PROJECTED = 1
|
||||
COMMAND = 2
|
||||
|
||||
|
||||
class Fit(object):
|
||||
"""Represents a fitting, with modules, ship, implants, etc."""
|
||||
|
||||
@@ -393,6 +384,32 @@ class Fit(object):
|
||||
else:
|
||||
return val
|
||||
|
||||
def canFit(self, item):
|
||||
# Whereas Module.fits() deals with current state of the fit in order to determine if somethign fits (for example maxGroupFitted which can be modified by effects),
|
||||
# this function should be used against Items to see if the item is even allowed on the fit with rules that don't change
|
||||
|
||||
fitsOnType = set()
|
||||
fitsOnGroup = set()
|
||||
|
||||
shipType = item.attributes.get("fitsToShipType", None)
|
||||
if shipType is not None:
|
||||
fitsOnType.add(shipType.value)
|
||||
|
||||
fitsOnType.update([item.attributes[attr].value for attr in item.attributes if attr.startswith("canFitShipType")])
|
||||
fitsOnGroup.update([item.attributes[attr].value for attr in item.attributes if attr.startswith("canFitShipGroup")])
|
||||
|
||||
if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \
|
||||
and self.ship.item.group.ID not in fitsOnGroup \
|
||||
and self.ship.item.ID not in fitsOnType:
|
||||
return False
|
||||
|
||||
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
||||
if isinstance(self.ship, Citadel) and item.category.name != "Structure Module" or \
|
||||
not isinstance(self.ship, Citadel) and item.category.name == "Structure Module":
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def clear(self, projected=False, command=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDpsMap = {}
|
||||
@@ -741,7 +758,7 @@ class Fit(object):
|
||||
The type of calculation our current iteration is in. This helps us determine the interactions between
|
||||
fits that rely on others for proper calculations
|
||||
"""
|
||||
pyfalog.info("Starting fit calculation on: {0}, calc: {1}", repr(self), CalcType.getName(type))
|
||||
pyfalog.info("Starting fit calculation on: {0}, calc: {1}", repr(self), CalcType(type).name)
|
||||
|
||||
# If we are projecting this fit onto another one, collect the projection info for later use
|
||||
|
||||
@@ -901,7 +918,7 @@ class Fit(object):
|
||||
if self.ship is None:
|
||||
return
|
||||
|
||||
for slotType in (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE):
|
||||
for slotType in (FittingSlot.LOW.value, FittingSlot.MED.value, FittingSlot.HIGH.value, FittingSlot.RIG.value, FittingSlot.SUBSYSTEM.value, FittingSlot.SERVICE.value):
|
||||
amount = self.getSlotsFree(slotType, True)
|
||||
if amount > 0:
|
||||
for _ in range(int(amount)):
|
||||
@@ -948,7 +965,7 @@ class Fit(object):
|
||||
def getItemAttrOnlineSum(dict, attr):
|
||||
amount = 0
|
||||
for mod in dict:
|
||||
add = mod.getModifiedItemAttr(attr) if mod.state >= State.ONLINE else None
|
||||
add = mod.getModifiedItemAttr(attr) if mod.state >= FittingModuleState.ONLINE else None
|
||||
if add is not None:
|
||||
amount += add
|
||||
|
||||
@@ -967,29 +984,29 @@ class Fit(object):
|
||||
|
||||
for mod in chain(self.modules, self.fighters):
|
||||
if mod.slot is type and (not getattr(mod, "isEmpty", False) or countDummies):
|
||||
if type in (Slot.F_HEAVY, Slot.F_SUPPORT, Slot.F_LIGHT, Slot.FS_HEAVY, Slot.FS_LIGHT, Slot.FS_SUPPORT) and not mod.active:
|
||||
if type in (FittingSlot.F_HEAVY, FittingSlot.F_SUPPORT, FittingSlot.F_LIGHT, FittingSlot.FS_HEAVY, FittingSlot.FS_LIGHT, FittingSlot.FS_SUPPORT) and not mod.active:
|
||||
continue
|
||||
amount += 1
|
||||
|
||||
return amount
|
||||
|
||||
slots = {
|
||||
Slot.LOW : "lowSlots",
|
||||
Slot.MED : "medSlots",
|
||||
Slot.HIGH : "hiSlots",
|
||||
Slot.RIG : "rigSlots",
|
||||
Slot.SUBSYSTEM: "maxSubSystems",
|
||||
Slot.SERVICE : "serviceSlots",
|
||||
Slot.F_LIGHT : "fighterLightSlots",
|
||||
Slot.F_SUPPORT: "fighterSupportSlots",
|
||||
Slot.F_HEAVY : "fighterHeavySlots",
|
||||
Slot.FS_LIGHT: "fighterStandupLightSlots",
|
||||
Slot.FS_SUPPORT: "fighterStandupSupportSlots",
|
||||
Slot.FS_HEAVY: "fighterStandupHeavySlots",
|
||||
FittingSlot.LOW : "lowSlots",
|
||||
FittingSlot.MED : "medSlots",
|
||||
FittingSlot.HIGH : "hiSlots",
|
||||
FittingSlot.RIG : "rigSlots",
|
||||
FittingSlot.SUBSYSTEM: "maxSubSystems",
|
||||
FittingSlot.SERVICE : "serviceSlots",
|
||||
FittingSlot.F_LIGHT : "fighterLightSlots",
|
||||
FittingSlot.F_SUPPORT: "fighterSupportSlots",
|
||||
FittingSlot.F_HEAVY : "fighterHeavySlots",
|
||||
FittingSlot.FS_LIGHT: "fighterStandupLightSlots",
|
||||
FittingSlot.FS_SUPPORT: "fighterStandupSupportSlots",
|
||||
FittingSlot.FS_HEAVY: "fighterStandupHeavySlots",
|
||||
}
|
||||
|
||||
def getSlotsFree(self, type, countDummies=False):
|
||||
if type in (Slot.MODE, Slot.SYSTEM):
|
||||
if type in (FittingSlot.MODE, FittingSlot.SYSTEM):
|
||||
# These slots don't really exist, return default 0
|
||||
return 0
|
||||
|
||||
@@ -1001,12 +1018,12 @@ class Fit(object):
|
||||
return self.ship.getModifiedItemAttr(self.slots[type]) or 0
|
||||
|
||||
def getHardpointsFree(self, type):
|
||||
if type == Hardpoint.NONE:
|
||||
if type == FittingHardpoint.NONE:
|
||||
return 1
|
||||
elif type == Hardpoint.TURRET:
|
||||
return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(Hardpoint.TURRET)
|
||||
elif type == Hardpoint.MISSILE:
|
||||
return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(Hardpoint.MISSILE)
|
||||
elif type == FittingHardpoint.TURRET:
|
||||
return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(FittingHardpoint.TURRET)
|
||||
elif type == FittingHardpoint.MISSILE:
|
||||
return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(FittingHardpoint.MISSILE)
|
||||
else:
|
||||
raise ValueError("%d is not a valid value for Hardpoint Enum", type)
|
||||
|
||||
@@ -1168,7 +1185,7 @@ class Fit(object):
|
||||
capUsed = 0
|
||||
capAdded = 0
|
||||
for mod in self.modules:
|
||||
if mod.state >= State.ACTIVE:
|
||||
if mod.state >= FittingModuleState.ACTIVE:
|
||||
if (mod.getModifiedItemAttr("capacitorNeed") or 0) != 0:
|
||||
cycleTime = mod.rawCycleTime or 0
|
||||
reactivationTime = mod.getModifiedItemAttr("moduleReactivationDelay") or 0
|
||||
@@ -1182,7 +1199,7 @@ class Fit(object):
|
||||
capAdded -= capNeed
|
||||
|
||||
# If this is a turret, don't stagger activations
|
||||
disableStagger = mod.hardpoint == Hardpoint.TURRET
|
||||
disableStagger = mod.hardpoint == FittingHardpoint.TURRET
|
||||
|
||||
drains.append((int(fullCycleTime), mod.getModifiedItemAttr("capacitorNeed") or 0,
|
||||
mod.numShots or 0, disableStagger, reloadTime))
|
||||
|
||||
@@ -115,6 +115,11 @@ class Implant(HandledItem, ItemAttrShortcut):
|
||||
copy.active = self.active
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
active = self.active
|
||||
Implant.__init__(self, item)
|
||||
self.active = active
|
||||
|
||||
def __repr__(self):
|
||||
return "Implant(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
|
||||
@@ -23,9 +23,8 @@ from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos.const import Slot
|
||||
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
|
||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.enum import Enum
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
@@ -35,44 +34,30 @@ from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class State(Enum):
|
||||
OFFLINE = -1
|
||||
ONLINE = 0
|
||||
ACTIVE = 1
|
||||
OVERHEATED = 2
|
||||
|
||||
|
||||
ProjectedMap = {
|
||||
State.OVERHEATED: State.ACTIVE,
|
||||
State.ACTIVE: State.OFFLINE,
|
||||
State.OFFLINE: State.ACTIVE,
|
||||
State.ONLINE: State.ACTIVE # Just in case
|
||||
FittingModuleState.OVERHEATED: FittingModuleState.ACTIVE,
|
||||
FittingModuleState.ACTIVE: FittingModuleState.OFFLINE,
|
||||
FittingModuleState.OFFLINE: FittingModuleState.ACTIVE,
|
||||
FittingModuleState.ONLINE: FittingModuleState.ACTIVE # Just in case
|
||||
}
|
||||
|
||||
|
||||
# Old state : New State
|
||||
LocalMap = {
|
||||
State.OVERHEATED: State.ACTIVE,
|
||||
State.ACTIVE: State.ONLINE,
|
||||
State.OFFLINE: State.ONLINE,
|
||||
State.ONLINE: State.ACTIVE
|
||||
FittingModuleState.OVERHEATED: FittingModuleState.ACTIVE,
|
||||
FittingModuleState.ACTIVE: FittingModuleState.ONLINE,
|
||||
FittingModuleState.OFFLINE: FittingModuleState.ONLINE,
|
||||
FittingModuleState.ONLINE: FittingModuleState.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
# For system effects. They should only ever be online or offline
|
||||
ProjectedSystem = {
|
||||
State.OFFLINE: State.ONLINE,
|
||||
State.ONLINE: State.OFFLINE
|
||||
FittingModuleState.OFFLINE: FittingModuleState.ONLINE,
|
||||
FittingModuleState.ONLINE: FittingModuleState.OFFLINE
|
||||
}
|
||||
|
||||
|
||||
class Hardpoint(Enum):
|
||||
NONE = 0
|
||||
MISSILE = 1
|
||||
TURRET = 2
|
||||
|
||||
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
@@ -104,7 +89,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__charge = None
|
||||
|
||||
self.projected = False
|
||||
self.state = State.ONLINE
|
||||
self.state = FittingModuleState.ONLINE
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
@@ -153,7 +138,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
self.__hardpoint = Hardpoint.NONE
|
||||
self.__hardpoint = FittingHardpoint.NONE
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
|
||||
self.__slot = self.dummySlot # defaults to None
|
||||
@@ -396,7 +381,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.isEmpty:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
if self.state >= State.ACTIVE:
|
||||
if self.state >= FittingModuleState.ACTIVE:
|
||||
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
|
||||
"miningAmount") or 0
|
||||
if volley:
|
||||
@@ -410,7 +395,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return self.__miningyield
|
||||
|
||||
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
@@ -448,7 +433,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return dps
|
||||
|
||||
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return None, 0
|
||||
|
||||
def getBaseRemoteReps(module):
|
||||
@@ -554,34 +539,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return fits
|
||||
|
||||
def __fitRestrictions(self, fit, hardpointLimit=True):
|
||||
# Check ship type restrictions
|
||||
fitsOnType = set()
|
||||
fitsOnGroup = set()
|
||||
|
||||
shipType = self.getModifiedItemAttr("fitsToShipType", None)
|
||||
if shipType is not None:
|
||||
fitsOnType.add(shipType)
|
||||
|
||||
for attr in list(self.itemModifiedAttributes.keys()):
|
||||
if attr.startswith("canFitShipType"):
|
||||
shipType = self.getModifiedItemAttr(attr, None)
|
||||
if shipType is not None:
|
||||
fitsOnType.add(shipType)
|
||||
|
||||
for attr in list(self.itemModifiedAttributes.keys()):
|
||||
if attr.startswith("canFitShipGroup"):
|
||||
shipGroup = self.getModifiedItemAttr(attr, None)
|
||||
if shipGroup is not None:
|
||||
fitsOnGroup.add(shipGroup)
|
||||
|
||||
if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \
|
||||
and fit.ship.item.group.ID not in fitsOnGroup \
|
||||
and fit.ship.item.ID not in fitsOnType:
|
||||
return False
|
||||
|
||||
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
||||
if isinstance(fit.ship, Citadel) and self.item.category.name != "Structure Module" or \
|
||||
not isinstance(fit.ship, Citadel) and self.item.category.name == "Structure Module":
|
||||
if not fit.canFit(self.item):
|
||||
return False
|
||||
|
||||
# EVE doesn't let capital modules be fit onto subcapital hulls. Confirmed by CCP Larrikin that this is dictated
|
||||
@@ -590,14 +549,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
|
||||
# If the mod is a subsystem, don't let two subs in the same slot fit
|
||||
if self.slot == Slot.SUBSYSTEM:
|
||||
if self.slot == FittingSlot.SUBSYSTEM:
|
||||
subSlot = self.getModifiedItemAttr("subSystemSlot")
|
||||
for mod in fit.modules:
|
||||
if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
|
||||
return False
|
||||
|
||||
# Check rig sizes
|
||||
if self.slot == Slot.RIG:
|
||||
if self.slot == FittingSlot.RIG:
|
||||
if self.getModifiedItemAttr("rigSize") != fit.ship.getModifiedItemAttr("rigSize"):
|
||||
return False
|
||||
|
||||
@@ -627,9 +586,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# Check if we're within bounds
|
||||
if state < -1 or state > 2:
|
||||
return False
|
||||
elif state >= State.ACTIVE and not self.item.isType("active"):
|
||||
elif state >= FittingModuleState.ACTIVE and not self.item.isType("active"):
|
||||
return False
|
||||
elif state == State.OVERHEATED and not self.item.isType("overheat"):
|
||||
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -641,7 +600,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# If we're going to set module to offline or online for local modules or offline for projected,
|
||||
# it should be fine for all cases
|
||||
item = self.item
|
||||
if (state <= State.ONLINE and projectedOnto is None) or (state <= State.OFFLINE):
|
||||
if (state <= FittingModuleState.ONLINE and projectedOnto is None) or (state <= FittingModuleState.OFFLINE):
|
||||
return True
|
||||
|
||||
# Check if the local module is over it's max limit; if it's not, we're fine
|
||||
@@ -655,7 +614,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
group = item.group.name
|
||||
for mod in self.owner.modules:
|
||||
currItem = getattr(mod, "item", None)
|
||||
if mod.state >= State.ACTIVE and currItem is not None and currItem.group.name == group:
|
||||
if mod.state >= FittingModuleState.ACTIVE and currItem is not None and currItem.group.name == group:
|
||||
currActive += 1
|
||||
if currActive > maxGroupActive:
|
||||
break
|
||||
@@ -718,28 +677,28 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
@staticmethod
|
||||
def __calculateHardpoint(item):
|
||||
effectHardpointMap = {
|
||||
"turretFitted" : Hardpoint.TURRET,
|
||||
"launcherFitted": Hardpoint.MISSILE
|
||||
"turretFitted" : FittingHardpoint.TURRET,
|
||||
"launcherFitted": FittingHardpoint.MISSILE
|
||||
}
|
||||
|
||||
if item is None:
|
||||
return Hardpoint.NONE
|
||||
return FittingHardpoint.NONE
|
||||
|
||||
for effectName, slot in effectHardpointMap.items():
|
||||
if effectName in item.effects:
|
||||
return slot
|
||||
|
||||
return Hardpoint.NONE
|
||||
return FittingHardpoint.NONE
|
||||
|
||||
@staticmethod
|
||||
def calculateSlot(item):
|
||||
effectSlotMap = {
|
||||
"rigSlot" : Slot.RIG,
|
||||
"loPower" : Slot.LOW,
|
||||
"medPower" : Slot.MED,
|
||||
"hiPower" : Slot.HIGH,
|
||||
"subSystem" : Slot.SUBSYSTEM,
|
||||
"serviceSlot": Slot.SERVICE
|
||||
"rigSlot" : FittingSlot.RIG.value,
|
||||
"loPower" : FittingSlot.LOW.value,
|
||||
"medPower" : FittingSlot.MED.value,
|
||||
"hiPower" : FittingSlot.HIGH.value,
|
||||
"subSystem" : FittingSlot.SUBSYSTEM.value,
|
||||
"serviceSlot": FittingSlot.SERVICE.value
|
||||
}
|
||||
if item is None:
|
||||
return None
|
||||
@@ -747,7 +706,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if effectName in item.effects:
|
||||
return slot
|
||||
if item.group.name in Module.SYSTEM_GROUPS:
|
||||
return Slot.SYSTEM
|
||||
return FittingSlot.SYSTEM
|
||||
|
||||
return None
|
||||
|
||||
@@ -801,8 +760,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if effect.runTime == runTime and \
|
||||
effect.activeByDefault and \
|
||||
(effect.isType("offline") or
|
||||
(effect.isType("passive") and self.state >= State.ONLINE) or
|
||||
(effect.isType("active") and self.state >= State.ACTIVE)) and \
|
||||
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) and \
|
||||
(not gang or (gang and effect.isType("gang"))):
|
||||
|
||||
chargeContext = ("moduleCharge",)
|
||||
@@ -815,7 +774,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
effect.handler(fit, self, chargeContext)
|
||||
|
||||
if self.item:
|
||||
if self.state >= State.OVERHEATED:
|
||||
if self.state >= FittingModuleState.OVERHEATED:
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
effect.isType("overheat") \
|
||||
@@ -828,8 +787,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if effect.runTime == runTime and \
|
||||
effect.activeByDefault and \
|
||||
(effect.isType("offline") or
|
||||
(effect.isType("passive") and self.state >= State.ONLINE) or
|
||||
(effect.isType("active") and self.state >= State.ACTIVE)) \
|
||||
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
|
||||
and ((projected and effect.isType("projected")) or not projected) \
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
try:
|
||||
@@ -904,7 +863,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
@property
|
||||
def capUse(self):
|
||||
capNeed = self.getModifiedItemAttr("capacitorNeed")
|
||||
if capNeed and self.state >= State.ACTIVE:
|
||||
if capNeed and self.state >= FittingModuleState.ACTIVE:
|
||||
cycleTime = self.cycleTime
|
||||
if cycleTime > 0:
|
||||
capUsed = capNeed / (cycleTime / 1000.0)
|
||||
@@ -916,10 +875,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def getProposedState(mod, click, proposedState=None):
|
||||
# todo: instead of passing in module, make this a instanced function.
|
||||
pyfalog.debug("Get proposed state for module.")
|
||||
if mod.slot == Slot.SUBSYSTEM or mod.isEmpty:
|
||||
return State.ONLINE
|
||||
if mod.slot == FittingSlot.SUBSYSTEM or mod.isEmpty:
|
||||
return FittingModuleState.ONLINE
|
||||
|
||||
if mod.slot == Slot.SYSTEM:
|
||||
if mod.slot == FittingSlot.SYSTEM:
|
||||
transitionMap = ProjectedSystem
|
||||
else:
|
||||
transitionMap = ProjectedMap if mod.projected else LocalMap
|
||||
@@ -929,9 +888,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if proposedState is not None:
|
||||
state = proposedState
|
||||
elif click == "right":
|
||||
state = State.OVERHEATED
|
||||
state = FittingModuleState.OVERHEATED
|
||||
elif click == "ctrl":
|
||||
state = State.OFFLINE
|
||||
state = FittingModuleState.OFFLINE
|
||||
else:
|
||||
state = transitionMap[currState]
|
||||
if not mod.isValidState(state):
|
||||
@@ -956,6 +915,16 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
state = self.state
|
||||
charge = self.charge
|
||||
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
|
||||
self.state = state
|
||||
if self.isValidCharge(charge):
|
||||
self.charge = charge
|
||||
for x in self.mutators.values():
|
||||
Mutator(self, x.attribute, x.value)
|
||||
|
||||
def __repr__(self):
|
||||
if self.item:
|
||||
return "Module(ID={}, name={}) at {}".format(
|
||||
|
||||
@@ -19,41 +19,56 @@
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import time
|
||||
from enum import IntEnum, unique
|
||||
from time import time
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
VALIDITY = 24 * 60 * 60 # Price validity period, 24 hours
|
||||
REREQUEST = 4 * 60 * 60 # Re-request delay for failed fetches, 4 hours
|
||||
TIMEOUT = 15 * 60 # Network timeout delay for connection issues, 15 minutes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@unique
|
||||
class PriceStatus(IntEnum):
|
||||
notFetched = 0
|
||||
success = 1
|
||||
fail = 2
|
||||
notSupported = 3
|
||||
initialized = 0
|
||||
notSupported = 1
|
||||
fetchSuccess = 2
|
||||
fetchFail = 3
|
||||
fetchTimeout = 4
|
||||
|
||||
|
||||
class Price(object):
|
||||
def __init__(self, typeID):
|
||||
self.typeID = typeID
|
||||
self.time = 0
|
||||
self.__price = 0
|
||||
self.status = PriceStatus.notFetched
|
||||
self.price = 0
|
||||
self.status = PriceStatus.initialized
|
||||
|
||||
@property
|
||||
def isValid(self):
|
||||
return self.time >= time.time()
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
if self.status != PriceStatus.success:
|
||||
return 0
|
||||
def isValid(self, validityOverride=None):
|
||||
# Always attempt to update prices which were just initialized, and prices
|
||||
# of unsupported items (maybe we start supporting them at some point)
|
||||
if self.status in (PriceStatus.initialized, PriceStatus.notSupported):
|
||||
return False
|
||||
elif self.status == PriceStatus.fetchSuccess:
|
||||
return time() <= self.time + (validityOverride if validityOverride is not None else VALIDITY)
|
||||
elif self.status == PriceStatus.fetchFail:
|
||||
return time() <= self.time + REREQUEST
|
||||
elif self.status == PriceStatus.fetchTimeout:
|
||||
return time() <= self.time + TIMEOUT
|
||||
else:
|
||||
return self.__price or 0
|
||||
return False
|
||||
|
||||
@price.setter
|
||||
def price(self, price):
|
||||
self.__price = price
|
||||
def update(self, status, price=0):
|
||||
# Keep old price if we failed to fetch new one
|
||||
if status in (PriceStatus.fetchFail, PriceStatus.fetchTimeout):
|
||||
price = self.price
|
||||
elif status != PriceStatus.fetchSuccess:
|
||||
price = 0
|
||||
self.time = time()
|
||||
self.price = price
|
||||
self.status = status
|
||||
|
||||
@@ -19,21 +19,14 @@
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
from enum import IntEnum, unique
|
||||
|
||||
from eos.const import SpoolType
|
||||
from eos.utils.float import floatUnerr
|
||||
|
||||
|
||||
SpoolOptions = namedtuple('SpoolOptions', ('spoolType', 'spoolAmount', 'force'))
|
||||
|
||||
|
||||
@unique
|
||||
class SpoolType(IntEnum):
|
||||
SCALE = 0 # [0..1]
|
||||
TIME = 1 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||
|
||||
|
||||
def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAmount):
|
||||
"""
|
||||
Calculate damage multiplier increment based on passed parameters. Module cycle time
|
||||
|
||||
@@ -110,6 +110,9 @@ class BoosterView(d.Display):
|
||||
|
||||
self.origional = fit.boosters if fit is not None else None
|
||||
self.boosters = stuff = fit.boosters[:] if fit is not None else None
|
||||
if stuff is not None:
|
||||
stuff.sort(key=lambda booster: booster.slot or 0)
|
||||
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
|
||||
@@ -81,7 +81,9 @@ class CargoView(d.Display):
|
||||
if data[0] == "fitting":
|
||||
self.swapModule(x, y, int(data[1]))
|
||||
elif data[0] == "market":
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(self.mainFrame.getActiveFit(), int(data[1])))
|
||||
fit = self.mainFrame.getActiveFit()
|
||||
if fit:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(fit, int(data[1])))
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
|
||||
@@ -25,7 +25,7 @@ from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED
|
||||
import gui.mainFrame
|
||||
import gui.display as d
|
||||
from gui.builtinViewColumns.state import State
|
||||
from eos.saveddata.module import Slot
|
||||
from eos.const import FittingSlot
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
@@ -93,9 +93,9 @@ class FighterView(wx.Panel):
|
||||
if fit:
|
||||
for x in self.labels:
|
||||
if fit.isStructure:
|
||||
slot = getattr(Slot, "FS_{}".format(x.upper()))
|
||||
slot = getattr(FittingSlot, "FS_{}".format(x.upper()))
|
||||
else:
|
||||
slot = getattr(Slot, "F_{}".format(x.upper()))
|
||||
slot = getattr(FittingSlot, "F_{}".format(x.upper()))
|
||||
used = fit.getSlotsUsed(slot)
|
||||
total = fit.getNumSlots(slot)
|
||||
color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings.GetColour(
|
||||
@@ -122,8 +122,8 @@ class FighterDisplay(d.Display):
|
||||
# "Max Range",
|
||||
# "Miscellanea",
|
||||
"attr:maxVelocity",
|
||||
"Fighter Abilities"
|
||||
# "Price",
|
||||
"Fighter Abilities",
|
||||
"Price",
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
|
||||
@@ -26,7 +26,7 @@ from gui.builtinViewColumns.state import State
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.globalEvents as GE
|
||||
from eos.saveddata.fit import ImplantLocation
|
||||
from eos.const import ImplantLocation
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
@@ -204,7 +204,10 @@ class ImplantDisplay(d.Display):
|
||||
|
||||
def removeImplant(self, implant):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantCommand(fitID, self.original.index(implant)))
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit.implantLocation == ImplantLocation.FIT:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantCommand(fitID, self.original.index(implant)))
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
|
||||
@@ -8,6 +8,7 @@ import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class MetaSwap(ContextMenu):
|
||||
@@ -53,6 +54,8 @@ class MetaSwap(ContextMenu):
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
self.moduleLookup = {}
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
def get_metalevel(x):
|
||||
if "metaLevel" not in x.attributes:
|
||||
@@ -114,6 +117,7 @@ class MetaSwap(ContextMenu):
|
||||
|
||||
id = ContextMenu.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, item.name)
|
||||
mitem.Enable(fit.canFit(item))
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleModule, mitem)
|
||||
|
||||
self.moduleLookup[id] = item, context
|
||||
|
||||
@@ -5,7 +5,7 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.module import Hardpoint
|
||||
from eos.const import FittingHardpoint
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.market import Market
|
||||
@@ -136,7 +136,7 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
hardpoint = self.module.hardpoint
|
||||
moduleName = self.module.item.name
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if hardpoint == Hardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
||||
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
||||
self.addSeperator(m, "Long Range")
|
||||
items = []
|
||||
range_ = None
|
||||
@@ -180,7 +180,7 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
m.Append(item)
|
||||
|
||||
self.addSeperator(m, "Short Range")
|
||||
elif hardpoint == Hardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
self.charges.sort(key=self.missileSorter)
|
||||
type_ = None
|
||||
sub = None
|
||||
|
||||
82
gui/builtinContextMenus/spoolUp.py
Normal file
82
gui/builtinContextMenus/spoolUp.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import eos.config
|
||||
import gui.mainFrame
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from gui import globalEvents as GE
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class SpoolUp(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.cycleMap = {}
|
||||
self.resetId = None
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('spoolup'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("fittingModule") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
self.mod = selection[0]
|
||||
|
||||
return self.mod.item.group.name in ("Precursor Weapon", "Mutadaptive Remote Armor Repairer")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Spoolup Cycles"
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
if "wxMSW" in wx.PlatformInfo:
|
||||
bindmenu = rootMenu
|
||||
else:
|
||||
bindmenu = m
|
||||
|
||||
isNotDefault = self.mod.spoolType is not None and self.mod.spoolAmount is not None
|
||||
cycleDefault = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], True))[0]
|
||||
cycleCurrent = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], False))[0]
|
||||
cycleMin = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True))[0]
|
||||
cycleMax = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True))[0]
|
||||
|
||||
for cycle in range(cycleMin, cycleMax + 1):
|
||||
menuId = ContextMenu.nextID()
|
||||
|
||||
# Show default only for current value and when not overriden
|
||||
if not isNotDefault and cycle == cycleDefault:
|
||||
text = "{} (default)".format(cycle)
|
||||
else:
|
||||
text = "{}".format(cycle)
|
||||
|
||||
item = wx.MenuItem(m, menuId, text, kind=wx.ITEM_CHECK)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
item.Check(isNotDefault and cycle == cycleCurrent)
|
||||
self.cycleMap[menuId] = cycle
|
||||
|
||||
self.resetId = ContextMenu.nextID()
|
||||
item = wx.MenuItem(m, self.resetId, "Reset")
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
|
||||
return m
|
||||
|
||||
def handleSpoolChange(self, event):
|
||||
if event.Id == self.resetId:
|
||||
self.mod.spoolType = None
|
||||
self.mod.spoolAmount = None
|
||||
elif event.Id in self.cycleMap:
|
||||
cycles = self.cycleMap[event.Id]
|
||||
self.mod.spoolType = SpoolType.CYCLES
|
||||
self.mod.spoolAmount = cycles
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
Fit.getInstance().recalc(fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
SpoolUp.register()
|
||||
@@ -1,22 +1,4 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
# Define the various groups of attributes
|
||||
class AttrGroup(Enum):
|
||||
FITTING = auto()
|
||||
STRUCTURE = auto()
|
||||
SHIELD = auto()
|
||||
ARMOR = auto()
|
||||
TARGETING = auto()
|
||||
EWAR_RESISTS = auto()
|
||||
CAPACITOR = auto()
|
||||
SHARED_FACILITIES = auto()
|
||||
FIGHTER_FACILITIES = auto()
|
||||
ON_DEATH = auto()
|
||||
JUMP_SYSTEMS = auto()
|
||||
PROPULSIONS = auto()
|
||||
FIGHTERS = auto()
|
||||
|
||||
from service.const import GuiAttrGroup
|
||||
|
||||
RequiredSkillAttrs = sum((["requiredSkill{}".format(x), "requiredSkill{}Level".format(x)] for x in range(1, 7)), [])
|
||||
|
||||
@@ -45,7 +27,7 @@ for x in AttrGroups:
|
||||
|
||||
# Start defining all the known attribute groups
|
||||
AttrGroupDict = {
|
||||
AttrGroup.FITTING : {
|
||||
GuiAttrGroup.FITTING : {
|
||||
"label" : "Fitting",
|
||||
"attributes": [
|
||||
# parent-level attributes
|
||||
@@ -67,7 +49,7 @@ AttrGroupDict = {
|
||||
# "mass",
|
||||
]
|
||||
},
|
||||
AttrGroup.STRUCTURE : {
|
||||
GuiAttrGroup.STRUCTURE : {
|
||||
"label" : "Structure",
|
||||
"attributes": [
|
||||
"hp",
|
||||
@@ -97,7 +79,7 @@ AttrGroupDict = {
|
||||
"explosiveDamageResonance"
|
||||
]
|
||||
},
|
||||
AttrGroup.ARMOR : {
|
||||
GuiAttrGroup.ARMOR : {
|
||||
"label": "Armor",
|
||||
"attributes":[
|
||||
"armorHP",
|
||||
@@ -109,7 +91,7 @@ AttrGroupDict = {
|
||||
]
|
||||
|
||||
},
|
||||
AttrGroup.SHIELD : {
|
||||
GuiAttrGroup.SHIELD : {
|
||||
"label": "Shield",
|
||||
"attributes": [
|
||||
"shieldCapacity",
|
||||
@@ -122,7 +104,7 @@ AttrGroupDict = {
|
||||
]
|
||||
|
||||
},
|
||||
AttrGroup.EWAR_RESISTS : {
|
||||
GuiAttrGroup.EWAR_RESISTS : {
|
||||
"label": "Electronic Warfare",
|
||||
"attributes": [
|
||||
"ECMResistance",
|
||||
@@ -135,14 +117,14 @@ AttrGroupDict = {
|
||||
"weaponDisruptionResistance",
|
||||
]
|
||||
},
|
||||
AttrGroup.CAPACITOR : {
|
||||
GuiAttrGroup.CAPACITOR : {
|
||||
"label": "Capacitor",
|
||||
"attributes": [
|
||||
"capacitorCapacity",
|
||||
"rechargeRate",
|
||||
]
|
||||
},
|
||||
AttrGroup.TARGETING : {
|
||||
GuiAttrGroup.TARGETING : {
|
||||
"label": "Targeting",
|
||||
"attributes": [
|
||||
"maxTargetRange",
|
||||
@@ -160,7 +142,7 @@ AttrGroupDict = {
|
||||
"scanLadarStrength",
|
||||
]
|
||||
},
|
||||
AttrGroup.SHARED_FACILITIES : {
|
||||
GuiAttrGroup.SHARED_FACILITIES : {
|
||||
"label" : "Shared Facilities",
|
||||
"attributes": [
|
||||
"fleetHangarCapacity",
|
||||
@@ -168,7 +150,7 @@ AttrGroupDict = {
|
||||
"maxJumpClones",
|
||||
]
|
||||
},
|
||||
AttrGroup.FIGHTER_FACILITIES: {
|
||||
GuiAttrGroup.FIGHTER_FACILITIES: {
|
||||
"label": "Fighter Squadron Facilities",
|
||||
"attributes": [
|
||||
"fighterCapacity",
|
||||
@@ -181,7 +163,7 @@ AttrGroupDict = {
|
||||
"fighterStandupHeavySlots",
|
||||
]
|
||||
},
|
||||
AttrGroup.ON_DEATH : {
|
||||
GuiAttrGroup.ON_DEATH : {
|
||||
"label": "On Death",
|
||||
"attributes": [
|
||||
"onDeathDamageEM",
|
||||
@@ -192,7 +174,7 @@ AttrGroupDict = {
|
||||
"onDeathSignatureRadius",
|
||||
]
|
||||
},
|
||||
AttrGroup.JUMP_SYSTEMS : {
|
||||
GuiAttrGroup.JUMP_SYSTEMS : {
|
||||
"label": "Jump Drive Systems",
|
||||
"attributes": [
|
||||
"jumpDriveCapacitorNeed",
|
||||
@@ -206,13 +188,13 @@ AttrGroupDict = {
|
||||
"jumpPortalDuration",
|
||||
]
|
||||
},
|
||||
AttrGroup.PROPULSIONS : {
|
||||
GuiAttrGroup.PROPULSIONS : {
|
||||
"label": "Propulsion",
|
||||
"attributes": [
|
||||
"maxVelocity"
|
||||
]
|
||||
},
|
||||
AttrGroup.FIGHTERS : {
|
||||
GuiAttrGroup.FIGHTERS : {
|
||||
"label": "Fighter",
|
||||
"attributes": [
|
||||
"mass",
|
||||
@@ -225,28 +207,36 @@ AttrGroupDict = {
|
||||
"fighterSquadronOrbitRange",
|
||||
]
|
||||
},
|
||||
GuiAttrGroup.SHIP_GROUP : {
|
||||
"label" : "Can Fit To",
|
||||
"attributes": []
|
||||
},
|
||||
}
|
||||
|
||||
AttrGroupDict[GuiAttrGroup.SHIP_GROUP]["attributes"].extend([("canFitShipGroup{:02d}".format(i+1), "Group") for i in range(20)])
|
||||
AttrGroupDict[GuiAttrGroup.SHIP_GROUP]["attributes"].extend([("canFitShipType{:01d}".format(i+1), "Ship") for i in range(20)])
|
||||
|
||||
Group1 = [
|
||||
AttrGroup.FITTING,
|
||||
AttrGroup.STRUCTURE,
|
||||
AttrGroup.ARMOR,
|
||||
AttrGroup.SHIELD,
|
||||
AttrGroup.EWAR_RESISTS,
|
||||
AttrGroup.CAPACITOR,
|
||||
AttrGroup.TARGETING,
|
||||
AttrGroup.SHARED_FACILITIES,
|
||||
AttrGroup.FIGHTER_FACILITIES,
|
||||
AttrGroup.ON_DEATH,
|
||||
AttrGroup.JUMP_SYSTEMS,
|
||||
AttrGroup.PROPULSIONS,
|
||||
GuiAttrGroup.FITTING,
|
||||
GuiAttrGroup.STRUCTURE,
|
||||
GuiAttrGroup.ARMOR,
|
||||
GuiAttrGroup.SHIELD,
|
||||
GuiAttrGroup.EWAR_RESISTS,
|
||||
GuiAttrGroup.CAPACITOR,
|
||||
GuiAttrGroup.TARGETING,
|
||||
GuiAttrGroup.SHARED_FACILITIES,
|
||||
GuiAttrGroup.FIGHTER_FACILITIES,
|
||||
GuiAttrGroup.ON_DEATH,
|
||||
GuiAttrGroup.JUMP_SYSTEMS,
|
||||
GuiAttrGroup.PROPULSIONS,
|
||||
GuiAttrGroup.SHIP_GROUP
|
||||
]
|
||||
|
||||
CategoryGroups = {
|
||||
"Fighter" : [
|
||||
AttrGroup.FIGHTERS,
|
||||
AttrGroup.SHIELD,
|
||||
AttrGroup.TARGETING,
|
||||
GuiAttrGroup.FIGHTERS,
|
||||
GuiAttrGroup.SHIELD,
|
||||
GuiAttrGroup.TARGETING,
|
||||
],
|
||||
"Ship" : Group1,
|
||||
"Drone" : Group1,
|
||||
|
||||
@@ -4,12 +4,12 @@ import config
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import wx.lib.agw.hypertreelist
|
||||
from gui.builtinItemStatsViews.helpers import AutoListCtrl
|
||||
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount, roundDec
|
||||
from enum import IntEnum
|
||||
from gui.builtinItemStatsViews.attributeGrouping import *
|
||||
from service.const import GuiAttrGroup
|
||||
|
||||
|
||||
class AttributeView(IntEnum):
|
||||
@@ -19,7 +19,8 @@ class AttributeView(IntEnum):
|
||||
|
||||
class ItemParams(wx.Panel):
|
||||
def __init__(self, parent, stuff, item, context=None):
|
||||
wx.Panel.__init__(self, parent)
|
||||
# Had to manually set the size here, otherwise column widths couldn't be calculated correctly. See #1878
|
||||
wx.Panel.__init__(self, parent, size=(1000, 1000))
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.paramList = wx.lib.agw.hypertreelist.HyperTreeList(self, wx.ID_ANY, agwStyle=wx.TR_HIDE_ROOT | wx.TR_NO_LINES | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_HAS_BUTTONS)
|
||||
@@ -28,7 +29,7 @@ class ItemParams(wx.Panel):
|
||||
mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.SetSizer(mainSizer)
|
||||
|
||||
self.toggleView = 1
|
||||
self.toggleView = AttributeView.NORMAL
|
||||
self.stuff = stuff
|
||||
self.item = item
|
||||
self.attrInfo = {}
|
||||
@@ -40,9 +41,6 @@ class ItemParams(wx.Panel):
|
||||
if self.stuff is not None:
|
||||
self.paramList.AddColumn("Base Value")
|
||||
|
||||
self.paramList.SetMainColumn(0) # the one with the tree in it...
|
||||
self.paramList.SetColumnWidth(0, 300)
|
||||
|
||||
self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
|
||||
bSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
@@ -62,6 +60,8 @@ class ItemParams(wx.Panel):
|
||||
|
||||
mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT)
|
||||
|
||||
self.imageList = wx.ImageList(16, 16)
|
||||
|
||||
self.PopulateList()
|
||||
|
||||
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
|
||||
@@ -164,10 +164,24 @@ class ItemParams(wx.Panel):
|
||||
]
|
||||
)
|
||||
|
||||
def SetupImageList(self):
|
||||
self.imageList.RemoveAll()
|
||||
|
||||
self.blank_icon = self.imageList.Add(BitmapLoader.getBitmap("transparent16x16", "gui"))
|
||||
self.unknown_icon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
|
||||
self.paramList.AssignImageList(self.imageList)
|
||||
|
||||
def AddAttribute(self, parent, attr):
|
||||
display = None
|
||||
|
||||
if isinstance(attr, tuple):
|
||||
display = attr[1]
|
||||
attr = attr[0]
|
||||
|
||||
if attr in self.attrValues and attr not in self.processed_attribs:
|
||||
|
||||
data = self.GetData(attr)
|
||||
data = self.GetData(attr, display)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
@@ -188,14 +202,14 @@ class ItemParams(wx.Panel):
|
||||
|
||||
def PopulateList(self):
|
||||
# self.paramList.setResizeColumn(0)
|
||||
self.imageList = wx.ImageList(16, 16)
|
||||
self.SetupImageList()
|
||||
|
||||
self.processed_attribs = set()
|
||||
root = self.paramList.AddRoot("The Root Item")
|
||||
misc_parent = root
|
||||
|
||||
# We must first deet4ermine if it's categorey already has defined groupings set for it. Otherwise, we default to just using the fitting group
|
||||
order = CategoryGroups.get(self.item.category.categoryName, [AttrGroup.FITTING])
|
||||
order = CategoryGroups.get(self.item.category.categoryName, [GuiAttrGroup.FITTING, GuiAttrGroup.SHIP_GROUP])
|
||||
# start building out the tree
|
||||
for data in [AttrGroupDict[o] for o in order]:
|
||||
heading = data.get("label")
|
||||
@@ -243,10 +257,14 @@ class ItemParams(wx.Panel):
|
||||
|
||||
self.AddAttribute(root, name)
|
||||
|
||||
self.paramList.AssignImageList(self.imageList)
|
||||
self.Layout()
|
||||
|
||||
for i in range(self.paramList.GetMainWindow().GetColumnCount()):
|
||||
self.paramList.SetColumnWidth(i, wx.LIST_AUTOSIZE)
|
||||
|
||||
def GetData(self, attr):
|
||||
|
||||
def GetData(self, attr, displayOveride = None):
|
||||
info = self.attrInfo.get(attr)
|
||||
att = self.attrValues[attr]
|
||||
|
||||
@@ -264,8 +282,8 @@ class ItemParams(wx.Panel):
|
||||
if self.toggleView == AttributeView.NORMAL and ((attr not in GroupedAttributes and not value) or info is None or not info.published or attr in RequiredSkillAttrs):
|
||||
return None
|
||||
|
||||
if info and info.displayName and self.toggleView == 1:
|
||||
attrName = info.displayName
|
||||
if info and info.displayName and self.toggleView == AttributeView.NORMAL:
|
||||
attrName = displayOveride or info.displayName
|
||||
else:
|
||||
attrName = attr
|
||||
|
||||
@@ -278,27 +296,27 @@ class ItemParams(wx.Panel):
|
||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
|
||||
if icon is None:
|
||||
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
||||
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
attrIcon = self.blank_icon
|
||||
else:
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
attrIcon = self.unknown_icon
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
attrIcon = self.unknown_icon
|
||||
|
||||
# index = self.paramList.AppendItem(root, attrName)
|
||||
# idNameMap[idCount] = attrName
|
||||
# self.paramList.SetPyData(index, idCount)
|
||||
# idCount += 1
|
||||
|
||||
if self.toggleView != 1:
|
||||
if self.toggleView == AttributeView.RAW:
|
||||
valueUnit = str(value)
|
||||
elif info and info.unit:
|
||||
valueUnit = self.FormatValue(*info.unit.PreformatValue(value))
|
||||
else:
|
||||
valueUnit = formatAmount(value, 3, 0, 0)
|
||||
|
||||
if self.toggleView != 1:
|
||||
if self.toggleView == AttributeView.RAW:
|
||||
valueUnitDefault = str(valueDefault)
|
||||
elif info and info.unit:
|
||||
valueUnitDefault = self.FormatValue(*info.unit.PreformatValue(valueDefault))
|
||||
@@ -346,6 +364,7 @@ if __name__ == "__main__":
|
||||
#item = eos.db.getItem(526) # Stasis Webifier I
|
||||
item = eos.db.getItem(486) # 200mm AutoCannon I
|
||||
#item = eos.db.getItem(200) # Phased Plasma L
|
||||
|
||||
super().__init__(None, title="Test Attribute Window | {} - {}".format(item.ID, item.name), size=(1000, 500))
|
||||
|
||||
if 'wxMSW' in wx.PlatformInfo:
|
||||
@@ -359,6 +378,7 @@ if __name__ == "__main__":
|
||||
main_sizer.Add(panel, 1, wx.EXPAND | wx.ALL, 2)
|
||||
|
||||
self.SetSizer(main_sizer)
|
||||
self.Layout()
|
||||
|
||||
app = wx.App(redirect=False) # Error messages go to popup window
|
||||
top = Frame()
|
||||
|
||||
@@ -16,7 +16,7 @@ class ItemCompare(wx.Panel):
|
||||
def __init__(self, parent, stuff, item, items, context=None):
|
||||
# Start dealing with Price stuff to get that thread going
|
||||
sPrice = ServicePrice.getInstance()
|
||||
sPrice.getPrices(items, self.UpdateList)
|
||||
sPrice.getPrices(items, self.UpdateList, fetchTimeout=90)
|
||||
|
||||
wx.Panel.__init__(self, parent)
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -43,8 +43,12 @@ class ItemMutator(wx.Panel):
|
||||
self.badColor = wx.Colour(255, 64, 0)
|
||||
|
||||
self.event_mapping = {}
|
||||
higOverrides = {
|
||||
('Stasis Web', 'speedFactor'): False,
|
||||
}
|
||||
|
||||
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||
highIsGood = higOverrides.get((stuff.item.group.name, m.attribute.name), m.highIsGood)
|
||||
# Format: [raw value, modifier applied to base raw value, display value]
|
||||
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
|
||||
range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue))
|
||||
@@ -59,7 +63,7 @@ class ItemMutator(wx.Panel):
|
||||
minRange = range2
|
||||
maxRange = range1
|
||||
|
||||
if (m.highIsGood and minRange[0] >= maxRange[0]) or (not m.highIsGood and minRange[0] <= maxRange[0]):
|
||||
if (highIsGood and minRange[0] >= maxRange[0]) or (not highIsGood and minRange[0] <= maxRange[0]):
|
||||
betterRange = minRange
|
||||
worseRange = maxRange
|
||||
else:
|
||||
|
||||
@@ -36,7 +36,7 @@ class PFContextMenuPref(PreferenceView):
|
||||
|
||||
self.rbBox1 = wx.RadioBox(panel, -1, "Set as Damage Pattern", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox1.SetSelection(self.settings.get('ammoPattern'))
|
||||
rbSizerRow1.Add(self.rbBox1, 1, wx.TOP | wx.RIGHT, 5)
|
||||
rbSizerRow1.Add(self.rbBox1, 1, wx.ALL, 5)
|
||||
self.rbBox1.Bind(wx.EVT_RADIOBOX, self.OnSetting1Change)
|
||||
|
||||
self.rbBox2 = wx.RadioBox(panel, -1, "Change Skills", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
@@ -56,36 +56,39 @@ class PFContextMenuPref(PreferenceView):
|
||||
|
||||
self.rbBox4 = wx.RadioBox(panel, -1, "Variations", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox4.SetSelection(self.settings.get('metaSwap'))
|
||||
rbSizerRow2.Add(self.rbBox4, 1, wx.TOP | wx.RIGHT, 5)
|
||||
rbSizerRow2.Add(self.rbBox4, 1, wx.ALL, 5)
|
||||
self.rbBox4.Bind(wx.EVT_RADIOBOX, self.OnSetting4Change)
|
||||
|
||||
'''
|
||||
self.rbBox5 = wx.RadioBox(panel, -1, "Charge", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox5.SetSelection(self.settings.get('moduleAmmoPicker'))
|
||||
rbSizerRow2.Add(self.rbBox5, 1, wx.ALL, 5)
|
||||
self.rbBox5.Bind(wx.EVT_RADIOBOX, self.OnSetting5Change)
|
||||
'''
|
||||
# self.rbBox5 = wx.RadioBox(panel, -1, "Charge", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
# self.rbBox5.SetSelection(self.settings.get('moduleAmmoPicker'))
|
||||
# rbSizerRow2.Add(self.rbBox5, 0, wx.ALL, 5)
|
||||
# self.rbBox5.Bind(wx.EVT_RADIOBOX, self.OnSetting5Change)
|
||||
|
||||
self.rbBox6 = wx.RadioBox(panel, -1, "Charge (All)", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox6.SetSelection(self.settings.get('moduleGlobalAmmoPicker'))
|
||||
rbSizerRow2.Add(self.rbBox6, 1, wx.ALL, 5)
|
||||
self.rbBox6.Bind(wx.EVT_RADIOBOX, self.OnSetting6Change)
|
||||
|
||||
self.rbBox7 = wx.RadioBox(panel, -1, "Project onto Fit", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox7.SetSelection(self.settings.get('project'))
|
||||
rbSizerRow2.Add(self.rbBox7, 1, wx.ALL, 5)
|
||||
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
|
||||
|
||||
mainSizer.Add(rbSizerRow2, 1, wx.ALL | wx.EXPAND, 0)
|
||||
|
||||
# Row 3
|
||||
rbSizerRow3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.rbBox7 = wx.RadioBox(panel, -1, "Project onto Fit", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox7.SetSelection(self.settings.get('project'))
|
||||
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)
|
||||
rbSizerRow3.Add(self.rbBox8, 1, wx.ALL, 5)
|
||||
self.rbBox8.Bind(wx.EVT_RADIOBOX, self.OnSetting8Change)
|
||||
|
||||
self.rbBox9 = wx.RadioBox(panel, -1, "Spoolup", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbBox9.SetSelection(self.settings.get('spoolup'))
|
||||
rbSizerRow3.Add(self.rbBox9, 1, wx.ALL, 5)
|
||||
self.rbBox9.Bind(wx.EVT_RADIOBOX, self.OnSetting9Change)
|
||||
|
||||
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
@@ -115,6 +118,9 @@ class PFContextMenuPref(PreferenceView):
|
||||
def OnSetting8Change(self, event):
|
||||
self.settings.set('moduleFill', event.GetInt())
|
||||
|
||||
def OnSetting9Change(self, event):
|
||||
self.settings.set('spoolup', event.GetInt())
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("settings_menu", "gui")
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import gui.globalEvents as GE
|
||||
from gui.preferenceView import PreferenceView
|
||||
from service.settings import EOSSettings
|
||||
import gui.mainFrame
|
||||
from wx.lib.intctrl import IntCtrl
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,6 +62,22 @@ class PFFittingEnginePref(PreferenceView):
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
mainSizer.Add(self.cbUniversalAdaptiveArmorHardener, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
|
||||
spoolup_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.spool_up_label = wx.StaticText(panel, wx.ID_ANY, "Global Default Spoolup Percentage:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.spool_up_label.Wrap(-1)
|
||||
self.spool_up_label.SetCursor(helpCursor)
|
||||
self.spool_up_label.SetToolTip(
|
||||
wx.ToolTip('The amount of spoolup to use by default on module which support it. Can be changed on a per-module basis'))
|
||||
|
||||
spoolup_sizer.Add(self.spool_up_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.spoolup_value = IntCtrl(panel, min=0, max=100, limited=True)
|
||||
spoolup_sizer.Add(self.spoolup_value , 0, wx.ALL, 5)
|
||||
|
||||
mainSizer.Add(spoolup_sizer, 0, wx.ALL | wx.EXPAND, 0)
|
||||
|
||||
# Future code once new cap sim is implemented
|
||||
'''
|
||||
self.cbGlobalForceReactivationTimer = wx.CheckBox( panel, wx.ID_ANY, u"Factor in reactivation timer", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
@@ -96,9 +113,15 @@ class PFFittingEnginePref(PreferenceView):
|
||||
self.cbUniversalAdaptiveArmorHardener.SetValue(self.engine_settings.get("useStaticAdaptiveArmorHardener"))
|
||||
self.cbUniversalAdaptiveArmorHardener.Bind(wx.EVT_CHECKBOX, self.OnCBUniversalAdaptiveArmorHardenerChange)
|
||||
|
||||
self.spoolup_value.SetValue(int(self.engine_settings.get("globalDefaultSpoolupPercentage") * 100))
|
||||
self.spoolup_value.Bind(wx.lib.intctrl.EVT_INT, self.OnSpoolupChange)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
panel.Layout()
|
||||
|
||||
def OnSpoolupChange(self, event):
|
||||
self.engine_settings.set("globalDefaultSpoolupPercentage", self.spoolup_value.GetValue() / 100)
|
||||
|
||||
def OnCBGlobalForceReloadStateChange(self, event):
|
||||
self.sFit.serviceFittingOptions["useGlobalForceReload"] = self.cbGlobalForceReload.GetValue()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
@@ -25,7 +25,7 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from service.fit import Fit
|
||||
|
||||
import eos.config
|
||||
|
||||
class FirepowerViewFull(StatsView):
|
||||
name = "firepowerViewFull"
|
||||
@@ -157,8 +157,7 @@ class FirepowerViewFull(StatsView):
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
stats = (
|
||||
(
|
||||
"labelFullDpsWeapon",
|
||||
|
||||
@@ -23,6 +23,7 @@ from gui.statsView import StatsView
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
import eos.config
|
||||
|
||||
|
||||
stats = [
|
||||
@@ -101,8 +102,7 @@ class OutgoingViewFull(StatsView):
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
counter = 0
|
||||
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||
label = getattr(self, labelName)
|
||||
|
||||
@@ -22,6 +22,7 @@ import wx
|
||||
from gui.statsView import StatsView
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
import eos.config
|
||||
|
||||
|
||||
stats = [
|
||||
@@ -100,8 +101,7 @@ class OutgoingViewMinimal(StatsView):
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
counter = 0
|
||||
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||
label = getattr(self, labelName)
|
||||
|
||||
@@ -22,7 +22,7 @@ import wx
|
||||
from gui.statsView import StatsView
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from service.price import Price
|
||||
from service.price import Fit, Price
|
||||
from service.settings import PriceMenuSettings
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class PriceViewFull(StatsView):
|
||||
|
||||
gridPrice = wx.GridSizer(2, 3, 0, 0)
|
||||
contentSizer.Add(gridPrice, 0, wx.EXPAND | wx.ALL, 0)
|
||||
for _type in ("ship", "fittings", "total", "drones", "cargoBay", "character"):
|
||||
for _type in ("ship", "fittings", "character", "drones", "cargoBay", "total"):
|
||||
if _type in "ship":
|
||||
image = "ship_big"
|
||||
elif _type in ("fittings", "total"):
|
||||
@@ -79,11 +79,8 @@ class PriceViewFull(StatsView):
|
||||
def refreshPanel(self, fit):
|
||||
if fit is not None:
|
||||
self.fit = fit
|
||||
|
||||
fit_items = Price.fitItemsList(fit)
|
||||
|
||||
sPrice = Price.getInstance()
|
||||
sPrice.getPrices(fit_items, self.processPrices)
|
||||
fit_items = set(Fit.fitItemIter(fit))
|
||||
Price.getInstance().getPrices(fit_items, self.processPrices, fetchTimeout=30)
|
||||
self.labelEMStatus.SetLabel("Updating prices...")
|
||||
|
||||
self.refreshPanelPrices(fit)
|
||||
|
||||
@@ -22,7 +22,7 @@ import wx
|
||||
from gui.statsView import StatsView
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from service.price import Price
|
||||
from service.price import Fit, Price
|
||||
from service.settings import PriceMenuSettings
|
||||
|
||||
|
||||
@@ -73,11 +73,8 @@ class PriceViewMinimal(StatsView):
|
||||
def refreshPanel(self, fit):
|
||||
if fit is not None:
|
||||
self.fit = fit
|
||||
|
||||
fit_items = Price.fitItemsList(fit)
|
||||
|
||||
sPrice = Price.getInstance()
|
||||
sPrice.getPrices(fit_items, self.processPrices)
|
||||
fit_items = set(Fit.fitItemIter(fit))
|
||||
Price.getInstance().getPrices(fit_items, self.processPrices, fetchTimeout=30)
|
||||
self.labelEMStatus.SetLabel("Updating prices...")
|
||||
|
||||
self.refreshPanelPrices(fit)
|
||||
|
||||
@@ -26,7 +26,7 @@ import gui.mainFrame
|
||||
from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED
|
||||
from gui.utils import fonts
|
||||
|
||||
from eos.saveddata.module import Hardpoint
|
||||
from eos.const import FittingHardpoint
|
||||
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
@@ -196,9 +196,9 @@ class ResourcesViewFull(StatsView):
|
||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
stats = (
|
||||
("label%sUsedTurretHardpoints", lambda: fit.getHardpointsUsed(Hardpoint.TURRET), 0, 0, 0),
|
||||
("label%sUsedTurretHardpoints", lambda: fit.getHardpointsUsed(FittingHardpoint.TURRET), 0, 0, 0),
|
||||
("label%sTotalTurretHardpoints", lambda: fit.ship.getModifiedItemAttr('turretSlotsLeft'), 0, 0, 0),
|
||||
("label%sUsedLauncherHardpoints", lambda: fit.getHardpointsUsed(Hardpoint.MISSILE), 0, 0, 0),
|
||||
("label%sUsedLauncherHardpoints", lambda: fit.getHardpointsUsed(FittingHardpoint.MISSILE), 0, 0, 0),
|
||||
("label%sTotalLauncherHardpoints", lambda: fit.ship.getModifiedItemAttr('launcherSlotsLeft'), 0, 0, 0),
|
||||
("label%sUsedDronesActive", lambda: fit.activeDrones, 0, 0, 0),
|
||||
("label%sTotalDronesActive", lambda: fit.extraAttributes["maxActiveDrones"], 0, 0, 0),
|
||||
@@ -278,12 +278,16 @@ class ResourcesViewFull(StatsView):
|
||||
totalCalibrationPoints = value
|
||||
labelTCP = label
|
||||
|
||||
# See #1877
|
||||
shown = label.Shown
|
||||
label.Show(True)
|
||||
if isinstance(value, str):
|
||||
label.SetLabel(value)
|
||||
label.SetToolTip(wx.ToolTip(value))
|
||||
else:
|
||||
label.SetLabel(formatAmount(value, prec, lowest, highest))
|
||||
label.SetToolTip(wx.ToolTip("%.1f" % value))
|
||||
label.Show(shown)
|
||||
|
||||
colorWarn = wx.Colour(204, 51, 51)
|
||||
colorNormal = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
import wx
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.module import Module, Slot, Rack
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.const import FittingSlot
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
@@ -32,7 +33,7 @@ class BaseIcon(ViewColumn):
|
||||
return self.shipImage
|
||||
elif isinstance(stuff, Module):
|
||||
if stuff.isEmpty:
|
||||
return self.fittingView.imageList.GetImageIndex("slot_%s_small" % Slot.getName(stuff.slot).lower(),
|
||||
return self.fittingView.imageList.GetImageIndex("slot_%s_small" % FittingSlot(stuff.slot).name.lower(),
|
||||
"gui")
|
||||
else:
|
||||
return self.loadIconFile(stuff.item.iconID or "")
|
||||
|
||||
@@ -25,8 +25,9 @@ from eos.saveddata.cargo import Cargo
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.module import Module, Slot, Rack
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.const import FittingSlot
|
||||
from service.fit import Fit as FitSvc
|
||||
from service.market import Market
|
||||
from gui.viewColumn import ViewColumn
|
||||
@@ -72,10 +73,10 @@ class BaseName(ViewColumn):
|
||||
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
|
||||
elif isinstance(stuff, Rack):
|
||||
if FitSvc.getInstance().serviceFittingOptions["rackLabels"]:
|
||||
if stuff.slot == Slot.MODE:
|
||||
if stuff.slot == FittingSlot.MODE:
|
||||
return '─ Tactical Mode ─'
|
||||
else:
|
||||
return '─ {} {} Slot{}─'.format(stuff.num, Slot.getName(stuff.slot).capitalize(), '' if stuff.num == 1 else 's')
|
||||
return '─ {} {} Slot{}─'.format(stuff.num, FittingSlot(stuff.slot).name.capitalize(), '' if stuff.num == 1 else 's')
|
||||
else:
|
||||
return ""
|
||||
elif isinstance(stuff, Module):
|
||||
@@ -89,7 +90,7 @@ class BaseName(ViewColumn):
|
||||
return "{} {}".format(type.name, stuff.item.name[-1:])
|
||||
|
||||
if stuff.isEmpty:
|
||||
return "%s Slot" % Slot.getName(stuff.slot).capitalize()
|
||||
return "%s Slot" % FittingSlot(stuff.slot).name.capitalize()
|
||||
else:
|
||||
return stuff.item.name
|
||||
elif isinstance(stuff, Implant):
|
||||
|
||||
@@ -28,6 +28,7 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.utils.listFormatter import formatList
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
import eos.config
|
||||
|
||||
|
||||
class Miscellanea(ViewColumn):
|
||||
@@ -117,8 +118,8 @@ class Miscellanea(ViewColumn):
|
||||
text = "{0}".format(formatAmount(trackingSpeed, 3, 0, 3))
|
||||
tooltip = "tracking speed"
|
||||
info.append((text, tooltip))
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
spoolTime = stuff.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))[1]
|
||||
if spoolTime:
|
||||
text = "{0}s".format(formatAmount(spoolTime, 3, 0, 3))
|
||||
@@ -339,8 +340,7 @@ class Miscellanea(ViewColumn):
|
||||
tooltip = "Armor repaired per second"
|
||||
return text, tooltip
|
||||
elif itemGroup == "Mutadaptive Remote Armor Repairer":
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
spoolOptDefault = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)
|
||||
spoolOptPre = SpoolOptions(SpoolType.SCALE, 0, True)
|
||||
spoolOptFull = SpoolOptions(SpoolType.SCALE, 1, True)
|
||||
|
||||
@@ -22,6 +22,7 @@ import wx
|
||||
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.price import PriceStatus
|
||||
from service.price import Price as ServicePrice
|
||||
from gui.viewColumn import ViewColumn
|
||||
@@ -29,6 +30,20 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
|
||||
def formatPrice(stuff, priceObj):
|
||||
textItems = []
|
||||
if priceObj.price:
|
||||
mult = 1
|
||||
if isinstance(stuff, (Drone, Cargo)):
|
||||
mult = stuff.amount
|
||||
elif isinstance(stuff, Fighter):
|
||||
mult = stuff.amountActive
|
||||
textItems.append(formatAmount(priceObj.price * mult, 3, 3, 9, currency=True))
|
||||
if priceObj.status in (PriceStatus.fetchFail, PriceStatus.fetchTimeout):
|
||||
textItems.append("(!)")
|
||||
return " ".join(textItems)
|
||||
|
||||
|
||||
class Price(ViewColumn):
|
||||
name = "Price"
|
||||
|
||||
@@ -48,35 +63,21 @@ class Price(ViewColumn):
|
||||
|
||||
priceObj = stuff.item.price
|
||||
|
||||
if not priceObj.isValid:
|
||||
if not priceObj.isValid():
|
||||
return False
|
||||
|
||||
# Fetch actual price as float to not modify its value on Price object
|
||||
price = priceObj.price
|
||||
|
||||
if price == 0:
|
||||
return ""
|
||||
|
||||
if isinstance(stuff, Drone) or isinstance(stuff, Cargo):
|
||||
price *= stuff.amount
|
||||
|
||||
return formatAmount(price, 3, 3, 9, currency=True)
|
||||
return formatPrice(stuff, priceObj)
|
||||
|
||||
def delayedText(self, mod, display, colItem):
|
||||
sPrice = ServicePrice.getInstance()
|
||||
|
||||
def callback(item):
|
||||
price = item[0]
|
||||
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))
|
||||
priceObj = item[0]
|
||||
colItem.SetText(formatPrice(mod, priceObj))
|
||||
|
||||
display.SetItem(colItem)
|
||||
|
||||
sPrice.getPrices([mod.item], callback, True)
|
||||
sPrice.getPrices([mod.item], callback, waitforthread=True)
|
||||
|
||||
def getImageId(self, mod):
|
||||
return -1
|
||||
|
||||
@@ -24,7 +24,8 @@ import wx
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.module import Module, State as State_, Rack
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.const import FittingModuleState as State_
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
import gui.mainFrame
|
||||
@@ -46,12 +47,11 @@ class State(ViewColumn):
|
||||
|
||||
def getToolTip(self, mod):
|
||||
if isinstance(mod, Module) and not mod.isEmpty:
|
||||
return State_.getName(mod.state).title()
|
||||
return State_(mod.state).name.title()
|
||||
|
||||
def getImageId(self, stuff):
|
||||
generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(1).lower(), "gui")
|
||||
generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(-1).lower(),
|
||||
"gui")
|
||||
generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.ACTIVE.name.lower(), "gui")
|
||||
generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.OFFLINE.name.lower(), "gui")
|
||||
|
||||
if isinstance(stuff, Drone):
|
||||
if stuff.amountActive > 0:
|
||||
@@ -64,7 +64,7 @@ class State(ViewColumn):
|
||||
if stuff.isEmpty:
|
||||
return -1
|
||||
else:
|
||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(),
|
||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_(stuff.state).name.lower(),
|
||||
"gui")
|
||||
elif isinstance(stuff, Fit):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
@@ -83,7 +83,7 @@ class State(ViewColumn):
|
||||
return generic_inactive
|
||||
elif isinstance(stuff, Implant) and stuff.character:
|
||||
# if we're showing character implants, show an "online" state, which should not be changed
|
||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(0).lower(), "gui")
|
||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.ONLINE.name.lower(), "gui")
|
||||
else:
|
||||
active = getattr(stuff, "active", None)
|
||||
if active is None:
|
||||
|
||||
@@ -30,7 +30,8 @@ import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
import gui.multiSwitch
|
||||
from eos.saveddata.mode import Mode
|
||||
from eos.saveddata.module import Module, Rack, Slot
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.const import FittingSlot
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED
|
||||
from gui.builtinShipBrowser.events import EVT_FIT_REMOVED, EVT_FIT_RENAMED, EVT_FIT_SELECTED, FitSelected
|
||||
@@ -474,7 +475,14 @@ class FittingView(d.Display):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.activeFitID)
|
||||
|
||||
slotOrder = [Slot.SUBSYSTEM, Slot.HIGH, Slot.MED, Slot.LOW, Slot.RIG, Slot.SERVICE]
|
||||
slotOrder = [
|
||||
FittingSlot.SUBSYSTEM,
|
||||
FittingSlot.HIGH,
|
||||
FittingSlot.MED,
|
||||
FittingSlot.LOW,
|
||||
FittingSlot.RIG,
|
||||
FittingSlot.SERVICE
|
||||
]
|
||||
|
||||
if fit is not None:
|
||||
self.mods = fit.modules[:]
|
||||
@@ -507,7 +515,7 @@ class FittingView(d.Display):
|
||||
# while also marking the mode header position in the Blanks list
|
||||
if sFit.serviceFittingOptions["rackSlots"]:
|
||||
self.blanks.append(len(self.mods))
|
||||
self.mods.append(Rack.buildRack(Slot.MODE, None))
|
||||
self.mods.append(Rack.buildRack(FittingSlot.MODE, None))
|
||||
|
||||
self.mods.append(fit.mode)
|
||||
else:
|
||||
@@ -648,8 +656,7 @@ class FittingView(d.Display):
|
||||
slotMap = {}
|
||||
|
||||
# test for too many modules (happens with t3s / CCP change in slot layout)
|
||||
for slotType in Slot.getTypes():
|
||||
slot = Slot.getValue(slotType)
|
||||
for slot in [e.value for e in FittingSlot]:
|
||||
slotMap[slot] = fit.getSlotsFree(slot) < 0
|
||||
|
||||
for i, mod in enumerate(self.mods):
|
||||
@@ -735,8 +742,8 @@ class FittingView(d.Display):
|
||||
return
|
||||
|
||||
slotMap = {}
|
||||
for slotType in Slot.getTypes():
|
||||
slot = Slot.getValue(slotType)
|
||||
|
||||
for slot in [e.value for e in FittingSlot]:
|
||||
slotMap[slot] = fit.getSlotsFree(slot) < 0
|
||||
|
||||
padding = 2
|
||||
|
||||
@@ -150,7 +150,7 @@ class CharacterEntityEditor(EntityEditor):
|
||||
class CharacterEditor(wx.Frame):
|
||||
def __init__(self, parent):
|
||||
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="pyfa: Character Editor", pos=wx.DefaultPosition,
|
||||
size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
|
||||
size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.FRAME_FLOAT_ON_PARENT)
|
||||
|
||||
i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
@@ -353,7 +353,7 @@ class SkillTreeView(wx.Panel):
|
||||
self.skillBookDirtyImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small_red", "gui")))
|
||||
|
||||
tree.AppendColumn("Skill")
|
||||
tree.AppendColumn("Level")
|
||||
tree.AppendColumn("Level", align=wx.ALIGN_CENTER)
|
||||
# tree.SetMainColumn(0)
|
||||
|
||||
self.root = tree.GetRootItem()
|
||||
@@ -361,13 +361,17 @@ class SkillTreeView(wx.Panel):
|
||||
#
|
||||
# tree.SetItemText(self.root, 1, "Levels")
|
||||
|
||||
# tree.SetColumnWidth(0, 300)
|
||||
# first one doesn't work right in Windows. Second one doesn't work right in GTK. Together, we make sure it works.
|
||||
# Gotta love wx
|
||||
tree.SetColumnWidth(0, 525)
|
||||
tree.SetColumnWidth(1, 100)
|
||||
|
||||
self.btnSecStatus = wx.Button(self, wx.ID_ANY, "Sec Status: {0:.2f}".format(char.secStatus or 0.0))
|
||||
self.btnSecStatus.Bind(wx.EVT_BUTTON, self.onSecStatus)
|
||||
|
||||
self.populateSkillTree()
|
||||
|
||||
tree.Bind(wx.dataview.EVT_TREELIST_ITEM_ACTIVATED, self.expand)
|
||||
tree.Bind(wx.dataview.EVT_TREELIST_ITEM_EXPANDING, self.expandLookup)
|
||||
tree.Bind(wx.dataview.EVT_TREELIST_ITEM_CONTEXT_MENU, self.scheduleMenu)
|
||||
|
||||
@@ -427,7 +431,8 @@ class SkillTreeView(wx.Panel):
|
||||
self.levelChangeMenu.Bind(wx.EVT_MENU, self.changeLevel)
|
||||
self.SetSizer(pmainSizer)
|
||||
|
||||
self.Layout()
|
||||
# This cuases issues with GTK, see #1866
|
||||
# self.Layout()
|
||||
|
||||
def importSkills(self, evt):
|
||||
|
||||
@@ -554,9 +559,18 @@ class SkillTreeView(wx.Panel):
|
||||
if event:
|
||||
event.Skip()
|
||||
|
||||
def expand(self, event):
|
||||
root = event.GetItem()
|
||||
tree = self.skillTreeListCtrl
|
||||
if tree.IsExpanded(root):
|
||||
tree.Collapse(root)
|
||||
else:
|
||||
tree.Expand(root)
|
||||
|
||||
def expandLookup(self, event):
|
||||
root = event.GetItem()
|
||||
tree = self.skillTreeListCtrl
|
||||
|
||||
child = tree.GetFirstChild(root)
|
||||
if tree.GetItemText(child) == "dummy":
|
||||
tree.DeleteItem(child)
|
||||
|
||||
@@ -183,6 +183,7 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
|
||||
openFit,
|
||||
moduleGlobalAmmoPicker,
|
||||
moduleAmmoPicker,
|
||||
spoolUp,
|
||||
itemStats,
|
||||
damagePattern,
|
||||
marketJump,
|
||||
|
||||
@@ -26,6 +26,10 @@ import wx
|
||||
from service.port.eft import EFT_OPTIONS
|
||||
from service.port.multibuy import MULTIBUY_OPTIONS
|
||||
from service.settings import SettingsProvider
|
||||
from service.port import EfsPort, Port
|
||||
from service.const import PortMultiBuyOptions
|
||||
from eos.db import getFit
|
||||
from gui.utils.clipboard import toClipboard
|
||||
|
||||
|
||||
class CopySelectDialog(wx.Dialog):
|
||||
@@ -39,6 +43,17 @@ class CopySelectDialog(wx.Dialog):
|
||||
def __init__(self, parent):
|
||||
wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1),
|
||||
style=wx.DEFAULT_DIALOG_STYLE)
|
||||
|
||||
self.CopySelectDict = {
|
||||
CopySelectDialog.copyFormatEft : self.exportEft,
|
||||
CopySelectDialog.copyFormatXml : self.exportXml,
|
||||
CopySelectDialog.copyFormatDna : self.exportDna,
|
||||
CopySelectDialog.copyFormatEsi : self.exportEsi,
|
||||
CopySelectDialog.copyFormatMultiBuy: self.exportMultiBuy,
|
||||
CopySelectDialog.copyFormatEfs : self.exportEfs
|
||||
}
|
||||
|
||||
self.mainFrame = parent
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.copyFormats = OrderedDict((
|
||||
@@ -87,8 +102,8 @@ class CopySelectDialog(wx.Dialog):
|
||||
self.options[formatId][optId] = checkbox
|
||||
if self.settings['options'].get(formatId, {}).get(optId, defaultFormatOptions.get(formatId, {}).get(optId)):
|
||||
checkbox.SetValue(True)
|
||||
bsizer.Add(checkbox, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
|
||||
mainSizer.Add(bsizer, 1, wx.EXPAND | wx.LEFT, 20)
|
||||
bsizer.Add(checkbox, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
|
||||
mainSizer.Add(bsizer, 0, wx.EXPAND | wx.LEFT, 20)
|
||||
|
||||
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
||||
if buttonSizer:
|
||||
@@ -99,6 +114,31 @@ class CopySelectDialog(wx.Dialog):
|
||||
self.Fit()
|
||||
self.Center()
|
||||
|
||||
def Validate(self):
|
||||
# Since this dialog is shown through aa ShowModal(), we hook into the Validate function to veto the closing of the dialog until we're ready.
|
||||
# This always returns False, and when we're ready will EndModal()
|
||||
selected = self.GetSelected()
|
||||
options = self.GetOptions()
|
||||
|
||||
settings = SettingsProvider.getInstance().getSettings("pyfaExport")
|
||||
settings["format"] = selected
|
||||
settings["options"] = options
|
||||
self.waitDialog = None
|
||||
|
||||
def cb(text):
|
||||
if self.waitDialog:
|
||||
del self.waitDialog
|
||||
toClipboard(text)
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
export_options = options.get(selected)
|
||||
if selected == CopySelectDialog.copyFormatMultiBuy and export_options.get(PortMultiBuyOptions.OPTIMIZE_PRICES, False):
|
||||
self.waitDialog = wx.BusyInfo("Optimizing Prices", parent=self)
|
||||
|
||||
self.CopySelectDict[selected](export_options, callback=cb)
|
||||
|
||||
return False
|
||||
|
||||
def Selected(self, event):
|
||||
obj = event.GetEventObject()
|
||||
formatName = obj.GetLabel()
|
||||
@@ -119,3 +159,27 @@ class CopySelectDialog(wx.Dialog):
|
||||
for formatId in self.options:
|
||||
options[formatId] = {optId: ch.IsChecked() for optId, ch in self.options[formatId].items()}
|
||||
return options
|
||||
|
||||
def exportEft(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportEft(fit, options, callback)
|
||||
|
||||
def exportDna(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportDna(fit, callback)
|
||||
|
||||
def exportEsi(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportESI(fit, callback)
|
||||
|
||||
def exportXml(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportXml(None, fit, callback)
|
||||
|
||||
def exportMultiBuy(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportMultiBuy(fit, options, callback)
|
||||
|
||||
def exportEfs(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
EfsPort.exportEfs(fit, 0, callback)
|
||||
@@ -35,3 +35,5 @@ from .guiToggleDrone import GuiToggleDroneCommand
|
||||
from .guiFitRename import GuiFitRenameCommand
|
||||
from .guiChangeImplantLocation import GuiChangeImplantLocation
|
||||
from .guiImportMutatedModule import GuiImportMutatedModuleCommand
|
||||
from .guiSetSpoolup import GuiSetSpoolup
|
||||
from .guiRebaseItems import GuiRebaseItemsCommand
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import wx
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
import eos.db
|
||||
from logbook import Logger
|
||||
from service.fit import Fit
|
||||
@@ -52,8 +53,8 @@ class FitAddModuleCommand(wx.Command):
|
||||
self.module.owner = fit
|
||||
numSlots = len(fit.modules)
|
||||
fit.modules.append(self.module)
|
||||
if self.module.isValidState(State.ACTIVE):
|
||||
self.module.state = State.ACTIVE
|
||||
if self.module.isValidState(FittingModuleState.ACTIVE):
|
||||
self.module.state = FittingModuleState.ACTIVE
|
||||
|
||||
# todo: fix these
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import wx
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
import eos.db
|
||||
from logbook import Logger
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -28,7 +29,7 @@ class FitAddProjectedEnvCommand(wx.Command):
|
||||
|
||||
# todo: thing to check for existing environmental effects
|
||||
|
||||
module.state = State.ONLINE
|
||||
module.state = FittingModuleState.ONLINE
|
||||
if module.isExclusiveSystemEffect:
|
||||
# if this is an exclusive system effect, we need to cache the old one. We make room for the new one here, which returns the old one
|
||||
self.old_item = fit.projectedModules.makeRoom(module)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import wx
|
||||
import eos.db
|
||||
from logbook import Logger
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@@ -27,9 +28,9 @@ class FitAddProjectedModuleCommand(wx.Command):
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
module.state = State.ACTIVE
|
||||
module.state = FittingModuleState.ACTIVE
|
||||
if not module.canHaveState(module.state, fit):
|
||||
module.state = State.OFFLINE
|
||||
module.state = FittingModuleState.OFFLINE
|
||||
fit.projectedModules.append(module)
|
||||
|
||||
eos.db.commit()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import wx
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
import eos.db
|
||||
from eos.db.gamedata.queries import getDynamicItem
|
||||
from logbook import Logger
|
||||
@@ -62,8 +63,8 @@ class FitImportMutatedCommand(wx.Command):
|
||||
module.owner = fit
|
||||
numSlots = len(fit.modules)
|
||||
fit.modules.append(module)
|
||||
if module.isValidState(State.ACTIVE):
|
||||
module.state = State.ACTIVE
|
||||
if module.isValidState(FittingModuleState.ACTIVE):
|
||||
module.state = FittingModuleState.ACTIVE
|
||||
|
||||
# todo: fix these
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
|
||||
31
gui/fitCommands/calc/fitRebaseItem.py
Normal file
31
gui/fitCommands/calc/fitRebaseItem.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import wx
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitRebaseItemCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, containerName, position, newTypeID):
|
||||
wx.Command.__init__(self, True, "Rebase Item")
|
||||
self.fitID = fitID
|
||||
self.containerName = containerName
|
||||
self.position = position
|
||||
self.newTypeID = newTypeID
|
||||
self.oldTypeID = None
|
||||
|
||||
def Do(self):
|
||||
fit = eos.db.getFit(self.fitID)
|
||||
obj = getattr(fit, self.containerName)[self.position]
|
||||
self.oldTypeID = getattr(obj.item, "ID", None)
|
||||
newItem = eos.db.getItem(self.newTypeID)
|
||||
obj.rebase(newItem)
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
cmd = FitRebaseItemCommand(self.fitID, self.containerName, self.position, self.oldTypeID)
|
||||
return cmd.Do()
|
||||
@@ -2,7 +2,8 @@ import wx
|
||||
from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
from eos.saveddata.module import Module, State
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingModuleState
|
||||
from gui.fitCommands.helpers import ModuleInfoCache
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -72,19 +73,21 @@ class FitReplaceModuleCommand(wx.Command):
|
||||
# Dummy it out in case the next bit fails
|
||||
fit.modules.toDummy(self.position)
|
||||
|
||||
if self.module.fits(fit):
|
||||
self.module.owner = fit
|
||||
fit.modules.toModule(self.position, self.module)
|
||||
if self.module.isValidState(State.ACTIVE):
|
||||
self.module.state = State.ACTIVE
|
||||
if not self.module.fits(fit):
|
||||
self.Undo()
|
||||
return False
|
||||
|
||||
if self.old_module and self.old_module.charge and self.module.isValidCharge(self.old_module.charge):
|
||||
self.module.charge = self.old_module.charge
|
||||
self.module.owner = fit
|
||||
fit.modules.toModule(self.position, self.module)
|
||||
if self.module.isValidState(FittingModuleState.ACTIVE):
|
||||
self.module.state = FittingModuleState.ACTIVE
|
||||
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
# self.checkStates(fit, m)
|
||||
if self.old_module and self.old_module.charge and self.module.isValidCharge(self.old_module.charge):
|
||||
self.module.charge = self.old_module.charge
|
||||
|
||||
# fit.fill()
|
||||
eos.db.commit()
|
||||
return True
|
||||
return False
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
# self.checkStates(fit, m)
|
||||
|
||||
# fit.fill()
|
||||
eos.db.commit()
|
||||
return True
|
||||
|
||||
37
gui/fitCommands/calc/fitSetSpoolup.py
Normal file
37
gui/fitCommands/calc/fitSetSpoolup.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import wx
|
||||
import eos.db
|
||||
from logbook import Logger
|
||||
from eos.saveddata.booster import Booster
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitSetSpoolupCommand(wx.Command):
|
||||
def __init__(self, fitID, position, spoolType, spoolAmount):
|
||||
wx.Command.__init__(self, True)
|
||||
self.fitID = fitID
|
||||
self.position = position
|
||||
self.spoolType = spoolType
|
||||
self.spoolAmount = spoolAmount
|
||||
self.projected = False # todo: get this to work with projected modules? Is that a thing?
|
||||
self.cache = None
|
||||
|
||||
def Do(self):
|
||||
return self.__set(self.spoolType, self.spoolAmount)
|
||||
|
||||
def Undo(self):
|
||||
if self.cache:
|
||||
self.__set(*self.cache)
|
||||
return True
|
||||
|
||||
def __set(self, type, amount):
|
||||
fit = eos.db.getFit(self.fitID)
|
||||
source = fit.modules if not self.projected else fit.projectedModules
|
||||
|
||||
mod = source[self.position]
|
||||
self.cache = mod.spoolType, mod.spoolAmount
|
||||
|
||||
mod.spoolType = type
|
||||
mod.spoolAmount = amount
|
||||
|
||||
eos.db.commit()
|
||||
return True
|
||||
@@ -8,7 +8,7 @@ from .calc.fitAddDrone import FitAddDroneCommand
|
||||
|
||||
class GuiAddDroneCommand(wx.Command):
|
||||
def __init__(self, fitID, itemID):
|
||||
wx.Command.__init__(self, True, "Cargo Add")
|
||||
wx.Command.__init__(self, True, "Drone Add")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
@@ -8,7 +8,7 @@ from .calc.fitAddFighter import FitAddFighterCommand
|
||||
|
||||
class GuiAddFighterCommand(wx.Command):
|
||||
def __init__(self, fitID, itemID):
|
||||
wx.Command.__init__(self, True, "Cargo Add")
|
||||
wx.Command.__init__(self, True, "Fighter Add")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
@@ -3,7 +3,7 @@ from service.fit import Fit
|
||||
|
||||
import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from eos.saveddata.fit import ImplantLocation
|
||||
from eos.const import ImplantLocation
|
||||
from .calc.fitAddImplant import FitAddImplantCommand
|
||||
from .calc.fitChangeImplantLocation import FitChangeImplantLocation
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class GuiCargoToModuleCommand(wx.Command):
|
||||
"""
|
||||
|
||||
def __init__(self, fitID, moduleIdx, cargoIdx, copy=False):
|
||||
wx.Command.__init__(self, True, "Module State Change")
|
||||
wx.Command.__init__(self, True, "Cargo to Module")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.fitID = fitID
|
||||
|
||||
@@ -17,7 +17,7 @@ class GuiFillWithModuleCommand(wx.Command):
|
||||
set the charge on the underlying module (requires position)
|
||||
:param position: Optional. The position in fit.modules that we are attempting to set the item to
|
||||
"""
|
||||
wx.Command.__init__(self, True, "Module Add: {}".format(itemID))
|
||||
wx.Command.__init__(self, True, "Module Fill: {}".format(itemID))
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.fitID = fitID
|
||||
|
||||
@@ -14,7 +14,7 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class GuiModuleToCargoCommand(wx.Command):
|
||||
def __init__(self, fitID, moduleIdx, cargoIdx, copy=False):
|
||||
wx.Command.__init__(self, True, "Module State Change")
|
||||
wx.Command.__init__(self, True, "Module to Cargo")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.fitID = fitID
|
||||
|
||||
46
gui/fitCommands/guiRebaseItems.py
Normal file
46
gui/fitCommands/guiRebaseItems.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import wx
|
||||
|
||||
import eos.db
|
||||
import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from service.fit import Fit
|
||||
from .calc.fitRebaseItem import FitRebaseItemCommand
|
||||
from .calc.fitSetCharge import FitSetChargeCommand
|
||||
|
||||
|
||||
class GuiRebaseItemsCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, rebaseMap):
|
||||
wx.Command.__init__(self, True, "Mass Rebase Item")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.fitID = fitID
|
||||
self.rebaseMap = rebaseMap
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
def Do(self):
|
||||
fit = eos.db.getFit(self.fitID)
|
||||
for mod in fit.modules:
|
||||
if mod.item is not None and mod.item.ID in self.rebaseMap:
|
||||
self.internal_history.Submit(FitRebaseItemCommand(self.fitID, "modules", mod.modPosition, self.rebaseMap[mod.item.ID]))
|
||||
if mod.charge is not None and mod.charge.ID in self.rebaseMap:
|
||||
self.internal_history.Submit(FitSetChargeCommand(self.fitID, [mod.modPosition], self.rebaseMap[mod.charge.ID]))
|
||||
for containerName in ("drones", "fighters", "implants", "boosters", "cargo"):
|
||||
container = getattr(fit, containerName)
|
||||
for obj in container:
|
||||
if obj.item is not None and obj.item.ID in self.rebaseMap:
|
||||
self.internal_history.Submit(FitRebaseItemCommand(self.fitID, containerName, container.index(obj), self.rebaseMap[obj.item.ID]))
|
||||
if self.internal_history.Commands:
|
||||
eos.db.commit()
|
||||
Fit.getInstance().recalc(self.fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def Undo(self):
|
||||
for _ in self.internal_history.Commands:
|
||||
self.internal_history.Undo()
|
||||
eos.db.commit()
|
||||
Fit.getInstance().recalc(self.fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
|
||||
return True
|
||||
@@ -8,7 +8,7 @@ from .calc.fitRemoveCargo import FitRemoveCargoCommand
|
||||
|
||||
class GuiRemoveCargoCommand(wx.Command):
|
||||
def __init__(self, fitID, itemID):
|
||||
wx.Command.__init__(self, True, "Module Charge Add")
|
||||
wx.Command.__init__(self, True, "Cargo Remove")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
@@ -8,7 +8,7 @@ from .calc.fitRemoveDrone import FitRemoveDroneCommand
|
||||
|
||||
class GuiRemoveDroneCommand(wx.Command):
|
||||
def __init__(self, fitID, position, amount=1):
|
||||
wx.Command.__init__(self, True, "Cargo Add")
|
||||
wx.Command.__init__(self, True, "Drone Remove")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
@@ -8,7 +8,7 @@ from .calc.fitRemoveFighter import FitRemoveFighterCommand
|
||||
|
||||
class GuiRemoveFighterCommand(wx.Command):
|
||||
def __init__(self, fitID, position):
|
||||
wx.Command.__init__(self, True, "Module Remove")
|
||||
wx.Command.__init__(self, True, "Fighter Remove")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.fitID = fitID
|
||||
|
||||
@@ -27,7 +27,7 @@ class GuiRemoveProjectedCommand(wx.Command):
|
||||
}
|
||||
|
||||
def __init__(self, fitID, thing):
|
||||
wx.Command.__init__(self, True, "Projected Add")
|
||||
wx.Command.__init__(self, True, "Projected Remove")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
@@ -8,7 +8,7 @@ from .calc.fitSetMode import FitSetModeCommand
|
||||
|
||||
class GuiSetModeCommand(wx.Command):
|
||||
def __init__(self, fitID, mode):
|
||||
wx.Command.__init__(self, True, "Cargo Add")
|
||||
wx.Command.__init__(self, True, "Mode Set")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
|
||||
32
gui/fitCommands/guiSetSpoolup.py
Normal file
32
gui/fitCommands/guiSetSpoolup.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import wx
|
||||
from service.fit import Fit
|
||||
|
||||
import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from .calc.fitSetSpoolup import FitSetSpoolupCommand
|
||||
|
||||
|
||||
class GuiSetSpoolup(wx.Command):
|
||||
def __init__(self, fitID, module, spoolupType, spoolupAmount):
|
||||
wx.Command.__init__(self, True, "Booster Add")
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
self.internal_history = wx.CommandProcessor()
|
||||
self.fitID = fitID
|
||||
self.position = module.modPosition
|
||||
self.spoolType = spoolupType
|
||||
self.spoolupAmount = spoolupAmount
|
||||
|
||||
def Do(self):
|
||||
if self.internal_history.Submit(FitSetSpoolupCommand(self.fitID, self.position, self.spoolType, self.spoolupAmount)):
|
||||
self.sFit.recalc(self.fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
|
||||
return True
|
||||
return False
|
||||
|
||||
def Undo(self):
|
||||
for _ in self.internal_history.Commands:
|
||||
self.internal_history.Undo()
|
||||
self.sFit.recalc(self.fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
|
||||
return True
|
||||
@@ -37,7 +37,6 @@ import config
|
||||
import gui.globalEvents as GE
|
||||
from eos.config import gamedata_date, gamedata_version
|
||||
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
|
||||
from eos.db.saveddata.queries import getFit as db_getFit
|
||||
# import this to access override setting
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
from gui import graphFrame
|
||||
@@ -64,11 +63,12 @@ from gui.setEditor import ImplantSetEditorDlg
|
||||
from gui.shipBrowser import ShipBrowser
|
||||
from gui.statsPane import StatsPane
|
||||
from gui.updateDialog import UpdateDialog
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
from gui.utils.clipboard import fromClipboard
|
||||
from service.character import Character
|
||||
from service.esi import Esi
|
||||
from service.fit import Fit
|
||||
from service.port import EfsPort, IPortUser, Port
|
||||
from service.port import IPortUser, Port
|
||||
from service.price import Price
|
||||
from service.settings import HTMLExportSettings, SettingsProvider
|
||||
from service.update import Update
|
||||
import gui.fitCommands as cmd
|
||||
@@ -508,6 +508,8 @@ class MainFrame(wx.Frame):
|
||||
self.Bind(wx.EVT_MENU, self.saveCharAs, id=menuBar.saveCharAsId)
|
||||
# Save current character
|
||||
self.Bind(wx.EVT_MENU, self.revertChar, id=menuBar.revertCharId)
|
||||
# Optimize fit price
|
||||
self.Bind(wx.EVT_MENU, self.optimizeFitPrice, id=menuBar.optimizeFitPrice)
|
||||
|
||||
# Browse fittings
|
||||
self.Bind(wx.EVT_MENU, self.eveFittings, id=menuBar.eveFittingsId)
|
||||
@@ -655,6 +657,23 @@ class MainFrame(wx.Frame):
|
||||
sChr.revertCharacter(charID)
|
||||
wx.PostEvent(self, GE.CharListUpdated())
|
||||
|
||||
def optimizeFitPrice(self, event):
|
||||
fitID = self.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
if fit:
|
||||
def updateFitCb(replacementsCheaper):
|
||||
del self.waitDialog
|
||||
del self.disablerAll
|
||||
rebaseMap = {k.ID: v.ID for k, v in replacementsCheaper.items()}
|
||||
self.command.Submit(cmd.GuiRebaseItemsCommand(fitID, rebaseMap))
|
||||
|
||||
fitItems = {i for i in Fit.fitItemIter(fit) if i is not fit.ship.item}
|
||||
self.disablerAll = wx.WindowDisabler()
|
||||
self.waitDialog = wx.BusyInfo("Please Wait...", parent=self)
|
||||
Price.getInstance().findCheaperReplacements(fitItems, updateFitCb, fetchTimeout=10)
|
||||
|
||||
def AdditionsTabSelect(self, event):
|
||||
selTab = self.additionsSelect.index(event.GetId())
|
||||
|
||||
@@ -688,30 +707,6 @@ class MainFrame(wx.Frame):
|
||||
else:
|
||||
self.marketBrowser.search.Focus()
|
||||
|
||||
def clipboardEft(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(Port.exportEft(fit, options))
|
||||
|
||||
def clipboardDna(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(Port.exportDna(fit))
|
||||
|
||||
def clipboardEsi(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(Port.exportESI(fit))
|
||||
|
||||
def clipboardXml(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(Port.exportXml(None, fit))
|
||||
|
||||
def clipboardMultiBuy(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(Port.exportMultiBuy(fit, options))
|
||||
|
||||
def clipboardEfs(self, options):
|
||||
fit = db_getFit(self.getActiveFit())
|
||||
toClipboard(EfsPort.exportEfs(fit, 0))
|
||||
|
||||
def importFromClipboard(self, event):
|
||||
clipboard = fromClipboard()
|
||||
activeFit = self.getActiveFit()
|
||||
@@ -728,28 +723,8 @@ class MainFrame(wx.Frame):
|
||||
self._openAfterImport(importData)
|
||||
|
||||
def exportToClipboard(self, event):
|
||||
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,
|
||||
CopySelectDialog.copyFormatXml: self.clipboardXml,
|
||||
CopySelectDialog.copyFormatDna: self.clipboardDna,
|
||||
CopySelectDialog.copyFormatEsi: self.clipboardEsi,
|
||||
CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy,
|
||||
CopySelectDialog.copyFormatEfs: self.clipboardEfs}
|
||||
dlg = CopySelectDialog(self)
|
||||
btnPressed = dlg.ShowModal()
|
||||
|
||||
if btnPressed == wx.ID_OK:
|
||||
selected = dlg.GetSelected()
|
||||
options = dlg.GetOptions()
|
||||
|
||||
settings = SettingsProvider.getInstance().getSettings("pyfaExport")
|
||||
settings["format"] = selected
|
||||
settings["options"] = options
|
||||
CopySelectDict[selected](options.get(selected))
|
||||
|
||||
try:
|
||||
dlg.Destroy()
|
||||
except RuntimeError:
|
||||
pyfalog.error("Tried to destroy an object that doesn't exist in <exportToClipboard>.")
|
||||
with CopySelectDialog(self) as dlg:
|
||||
dlg.ShowModal()
|
||||
|
||||
def exportSkillsNeeded(self, event):
|
||||
""" Exports skills needed for active fit and active character """
|
||||
|
||||
@@ -28,8 +28,6 @@ import gui.globalEvents as GE
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
|
||||
from logbook import Logger
|
||||
# from service.crest import Crest
|
||||
# from service.crest import CrestModes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -59,6 +57,7 @@ class MainMenuBar(wx.MenuBar):
|
||||
self.importDatabaseDefaultsId = wx.NewId()
|
||||
self.toggleIgnoreRestrictionID = wx.NewId()
|
||||
self.devToolsId = wx.NewId()
|
||||
self.optimizeFitPrice = wx.NewId()
|
||||
|
||||
# pheonix: evaluate if this is needed
|
||||
if 'wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0):
|
||||
@@ -101,6 +100,9 @@ class MainMenuBar(wx.MenuBar):
|
||||
editMenu.Append(self.revertCharId, "Revert Character")
|
||||
editMenu.AppendSeparator()
|
||||
self.ignoreRestrictionItem = editMenu.Append(self.toggleIgnoreRestrictionID, "Ignore Fitting Restrictions")
|
||||
editMenu.AppendSeparator()
|
||||
editMenu.Append(self.optimizeFitPrice, "Optimize Fit Price")
|
||||
|
||||
|
||||
# Character menu
|
||||
windowMenu = wx.Menu()
|
||||
@@ -134,8 +136,6 @@ class MainMenuBar(wx.MenuBar):
|
||||
preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui"))
|
||||
windowMenu.Append(preferencesItem)
|
||||
|
||||
# self.sEsi = Crest.getInstance()
|
||||
|
||||
# CREST Menu
|
||||
esiMMenu = wx.Menu()
|
||||
self.Append(esiMMenu, "EVE &SSO")
|
||||
|
||||
@@ -164,12 +164,12 @@ class ShipBrowser(wx.Panel):
|
||||
self.categoryList = list(sMkt.getShipRoot())
|
||||
self.categoryList.sort(key=lambda _ship: _ship.name)
|
||||
|
||||
counts = sFit.countAllFitsGroupedByShip()
|
||||
|
||||
# set map & cache of fittings per category
|
||||
for cat in self.categoryList:
|
||||
itemIDs = [x.ID for x in cat.items]
|
||||
num = sFit.countFitsWithShip(itemIDs)
|
||||
self.categoryFitCache[cat.ID] = num > 0
|
||||
|
||||
itemIDs = [x.ID for x in sMkt.getItemsByGroup(cat)]
|
||||
self.categoryFitCache[cat.ID] = sum([count for shipID, count in counts if shipID in itemIDs]) > 0
|
||||
for ship in self.categoryList:
|
||||
if self.filterShipsWithNoFits and not self.categoryFitCache[ship.ID]:
|
||||
continue
|
||||
|
||||
@@ -2,6 +2,7 @@ import threading
|
||||
import time
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from service.const import PortEftOptions
|
||||
from service.settings import HTMLExportSettings
|
||||
from service.fit import Fit
|
||||
from service.port import Port
|
||||
@@ -208,8 +209,10 @@ class exportHtmlThread(threading.Thread):
|
||||
if self.stopRunning:
|
||||
return
|
||||
try:
|
||||
eftFit = Port.exportEft(getFit(fit[0]))
|
||||
print(eftFit)
|
||||
eftFit = Port.exportEft(getFit(fit[0]), options={
|
||||
PortEftOptions.IMPLANTS: True,
|
||||
PortEftOptions.MUTATIONS: True,
|
||||
PortEftOptions.LOADED_CHARGES: True})
|
||||
|
||||
HTMLfit = (
|
||||
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" '
|
||||
|
||||
@@ -178,32 +178,17 @@ def main(db, json_path):
|
||||
|
||||
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
|
||||
"""
|
||||
def compareAttrs(attrs1, attrs2):
|
||||
# Consider items as different if they have no attrs
|
||||
if len(attrs1) == 0 and len(attrs2) == 0:
|
||||
return False
|
||||
if set(attrs1) != set(attrs2):
|
||||
return 0
|
||||
return False
|
||||
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
|
||||
return True
|
||||
return False
|
||||
|
||||
print('finding replacements')
|
||||
skillReqAttribs = {
|
||||
182: 277,
|
||||
183: 278,
|
||||
@@ -213,10 +198,17 @@ def main(db, json_path):
|
||||
1290: 1288}
|
||||
skillReqAttribsFlat = set(skillReqAttribs.keys()).union(skillReqAttribs.values())
|
||||
# Get data on type groups
|
||||
# Format: {type ID: group ID}
|
||||
typesGroups = {}
|
||||
for row in tables['evetypes']:
|
||||
typesGroups[row['typeID']] = row['groupID']
|
||||
# Get data on item effects
|
||||
# Format: {type ID: set(effect, IDs)}
|
||||
typesEffects = {}
|
||||
for row in tables['dgmtypeeffects']:
|
||||
typesEffects.setdefault(row['typeID'], set()).add(row['effectID'])
|
||||
# Get data on type attributes
|
||||
# Format: {type ID: {attribute ID: attribute value}}
|
||||
typesNormalAttribs = {}
|
||||
typesSkillAttribs = {}
|
||||
for row in tables['dgmtypeattribs']:
|
||||
@@ -226,15 +218,23 @@ def main(db, json_path):
|
||||
typeSkillAttribs[row['attributeID']] = row['value']
|
||||
# Ignore these attributes for comparison purposes
|
||||
elif attributeID in (
|
||||
# We do not need mass as it affects final ship stats only when carried by ship itself
|
||||
# (and we're not going to replace ships), but it's wildly inconsistent for other items,
|
||||
# which otherwise would be the same
|
||||
4, # mass
|
||||
124, # mainColor
|
||||
162, # radius
|
||||
422, # techLevel
|
||||
633, # metaLevel
|
||||
1692 # metaGroupID
|
||||
1692, # metaGroupID
|
||||
1768 # typeColorScheme
|
||||
):
|
||||
continue
|
||||
else:
|
||||
typeNormalAttribs = typesNormalAttribs.setdefault(row['typeID'], {})
|
||||
typeNormalAttribs[row['attributeID']] = row['value']
|
||||
# Get data on skill requirements
|
||||
# Format: {type ID: {skill type ID: skill level}}
|
||||
typesSkillReqs = {}
|
||||
for typeID, typeAttribs in typesSkillAttribs.items():
|
||||
typeSkillAttribs = typesSkillAttribs.get(typeID, {})
|
||||
@@ -248,46 +248,54 @@ def main(db, json_path):
|
||||
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'])
|
||||
# Format: {group ID: category ID}
|
||||
groupCategories = {}
|
||||
for row in tables['evegroups']:
|
||||
groupCategories[row['groupID']] = row['categoryID']
|
||||
# As EVE affects various types mostly depending on their group or skill requirements,
|
||||
# we're going to group various types up this way
|
||||
# Format: {(group ID, frozenset(skillreq, type, IDs), frozenset(type, effect, IDs): [type ID, {attribute ID: attribute value}]}
|
||||
groupedData = {}
|
||||
for row in tables['evetypes']:
|
||||
typeID = row['typeID']
|
||||
# Ignore items outside of categories we need
|
||||
if groupCategories[typesGroups[typeID]] not in (
|
||||
6, # Ship
|
||||
7, # Module
|
||||
8, # Charge
|
||||
18, # Drone
|
||||
20, # Implant
|
||||
22, # Deployable
|
||||
23, # Starbase
|
||||
32, # Subsystem
|
||||
35, # Decryptors
|
||||
65, # Structure
|
||||
66, # Structure Module
|
||||
87, # Fighter
|
||||
):
|
||||
continue
|
||||
typeAttribs = typesNormalAttribs.get(typeID, {})
|
||||
# Ignore stuff w/o attributes
|
||||
# Ignore items 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), [])
|
||||
typeEffects = frozenset(typesEffects.get(typeID, ()))
|
||||
groupData = groupedData.setdefault((typeGroup, typeSkillreqs, typeEffects), [])
|
||||
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
|
||||
# Format: {type ID: set(type IDs)}
|
||||
replacements = {}
|
||||
# Now, go through composed groups and for every item within it
|
||||
# find items which are the same
|
||||
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])
|
||||
if compareAttrs(type1[1], type2[1]):
|
||||
replacements.setdefault(type1[0], set()).add(type2[0])
|
||||
replacements.setdefault(type2[0], set()).add(type1[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, ())))
|
||||
row['replacements'] = ','.join('{}'.format(tid) for tid in sorted(replacements.get(row['typeID'], ())))
|
||||
|
||||
data = {}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ from service.esi import Esi
|
||||
|
||||
from eos.saveddata.implant import Implant as es_Implant
|
||||
from eos.saveddata.character import Character as es_Character, Skill
|
||||
from eos.saveddata.module import Slot as es_Slot, Module as es_Module
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from eos.const import FittingSlot as es_Slot
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
104
service/const.py
Normal file
104
service/const.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2019 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
from enum import Enum, IntEnum, unique, auto
|
||||
|
||||
|
||||
@unique
|
||||
class EsiLoginMethod(IntEnum):
|
||||
"""
|
||||
Contains the method of ESI login
|
||||
"""
|
||||
SERVER = 0
|
||||
MANUAL = 1
|
||||
|
||||
|
||||
@unique
|
||||
class EsiSsoMode(IntEnum):
|
||||
"""
|
||||
Contains the mode of ESI sso mode
|
||||
"""
|
||||
AUTO = 0
|
||||
CUSTOM = 1
|
||||
|
||||
|
||||
class EsiEndpoints(Enum):
|
||||
"""
|
||||
Contains the endpoint paths for the ESI access
|
||||
"""
|
||||
CHAR = "/v4/characters/{character_id}/"
|
||||
CHAR_SKILLS = "/v4/characters/{character_id}/skills/"
|
||||
CHAR_FITTINGS = "/v1/characters/{character_id}/fittings/"
|
||||
CHAR_DEL_FIT = "/v1/characters/{character_id}/fittings/{fitting_id}/"
|
||||
|
||||
|
||||
@unique
|
||||
class PortMultiBuyOptions(IntEnum):
|
||||
"""
|
||||
Contains different types of items for multibuy export
|
||||
"""
|
||||
IMPLANTS = 1
|
||||
CARGO = 2
|
||||
LOADED_CHARGES = 3
|
||||
OPTIMIZE_PRICES = 4
|
||||
|
||||
|
||||
@unique
|
||||
class PortEftOptions(IntEnum):
|
||||
"""
|
||||
Contains different options for eft-export
|
||||
"""
|
||||
IMPLANTS = 1
|
||||
MUTATIONS = 2
|
||||
LOADED_CHARGES = 3
|
||||
|
||||
|
||||
@unique
|
||||
class PortEftRigSize(IntEnum):
|
||||
"""
|
||||
Contains different sizes of ship rigs
|
||||
This enum is not actively used, but maybe useful someday.
|
||||
"""
|
||||
SMALL = 1
|
||||
MEDIUM = 2
|
||||
LARGE = 3
|
||||
CAPITAL = 4
|
||||
|
||||
|
||||
@unique
|
||||
class GuiAttrGroup(IntEnum):
|
||||
"""
|
||||
Define the various groups of attributes.
|
||||
This enum is used for GUI functions and getting redefined in
|
||||
/gui/builtinItemStatsViews/attributeGrouping.py
|
||||
"""
|
||||
FITTING = auto()
|
||||
STRUCTURE = auto()
|
||||
SHIELD = auto()
|
||||
ARMOR = auto()
|
||||
TARGETING = auto()
|
||||
EWAR_RESISTS = auto()
|
||||
CAPACITOR = auto()
|
||||
SHARED_FACILITIES = auto()
|
||||
FIGHTER_FACILITIES = auto()
|
||||
ON_DEATH = auto()
|
||||
JUMP_SYSTEMS = auto()
|
||||
PROPULSIONS = auto()
|
||||
FIGHTERS = auto()
|
||||
SHIP_GROUP = auto()
|
||||
@@ -9,9 +9,9 @@ import config
|
||||
import webbrowser
|
||||
|
||||
import eos.db
|
||||
from eos.enum import Enum
|
||||
from service.const import EsiLoginMethod, EsiSsoMode
|
||||
from eos.saveddata.ssocharacter import SsoCharacter
|
||||
from service.esiAccess import APIException, SsoMode
|
||||
from service.esiAccess import APIException
|
||||
import gui.globalEvents as GE
|
||||
from gui.ssoLogin import SsoLogin, SsoLoginServer
|
||||
from service.server import StoppableHTTPServer, AuthHandler
|
||||
@@ -24,11 +24,6 @@ from requests import Session
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class LoginMethod(Enum):
|
||||
SERVER = 0
|
||||
MANUAL = 1
|
||||
|
||||
|
||||
class Esi(EsiAccess):
|
||||
_instance = None
|
||||
|
||||
@@ -107,8 +102,8 @@ class Esi(EsiAccess):
|
||||
|
||||
def login(self):
|
||||
# always start the local server if user is using client details. Otherwise, start only if they choose to do so.
|
||||
if self.settings.get('ssoMode') == SsoMode.CUSTOM or self.settings.get('loginMode') == LoginMethod.SERVER:
|
||||
dlg = gui.ssoLogin.SsoLoginServer(6461 if self.settings.get('ssoMode') == SsoMode.CUSTOM else 0)
|
||||
if self.settings.get('ssoMode') == EsiSsoMode.CUSTOM or self.settings.get('loginMode') == EsiLoginMethod.SERVER:
|
||||
dlg = gui.ssoLogin.SsoLoginServer(6461 if self.settings.get('ssoMode') == EsiSsoMode.CUSTOM else 0)
|
||||
dlg.ShowModal()
|
||||
else:
|
||||
dlg = gui.ssoLogin.SsoLogin()
|
||||
@@ -142,7 +137,7 @@ class Esi(EsiAccess):
|
||||
def handleLogin(self, message):
|
||||
|
||||
# we already have authenticated stuff for the auto mode
|
||||
if self.settings.get('ssoMode') == SsoMode.AUTO:
|
||||
if self.settings.get('ssoMode') == EsiSsoMode.AUTO:
|
||||
ssoInfo = message['SSOInfo'][0]
|
||||
auth_response = json.loads(base64.b64decode(ssoInfo))
|
||||
else:
|
||||
|
||||
@@ -17,7 +17,7 @@ import config
|
||||
import base64
|
||||
|
||||
import datetime
|
||||
from eos.enum import Enum
|
||||
from service.const import EsiSsoMode, EsiEndpoints
|
||||
from service.settings import EsiSettings, NetworkSettings
|
||||
|
||||
from requests import Session
|
||||
@@ -42,11 +42,6 @@ scopes = [
|
||||
]
|
||||
|
||||
|
||||
class SsoMode(Enum):
|
||||
AUTO = 0
|
||||
CUSTOM = 1
|
||||
|
||||
|
||||
class APIException(Exception):
|
||||
""" Exception for SSO related errors """
|
||||
|
||||
@@ -66,13 +61,6 @@ class APIException(Exception):
|
||||
return 'HTTP Error %s' % self.status_code
|
||||
|
||||
|
||||
class ESIEndpoints(Enum):
|
||||
CHAR = "/v4/characters/{character_id}/"
|
||||
CHAR_SKILLS = "/v4/characters/{character_id}/skills/"
|
||||
CHAR_FITTINGS = "/v1/characters/{character_id}/fittings/"
|
||||
CHAR_DEL_FIT = "/v1/characters/{character_id}/fittings/{fitting_id}/"
|
||||
|
||||
|
||||
class EsiAccess(object):
|
||||
def __init__(self):
|
||||
self.settings = EsiSettings.getInstance()
|
||||
@@ -89,7 +77,7 @@ class EsiAccess(object):
|
||||
|
||||
@property
|
||||
def sso_url(self):
|
||||
if self.settings.get("ssoMode") == SsoMode.CUSTOM:
|
||||
if self.settings.get("ssoMode") == EsiSsoMode.CUSTOM:
|
||||
return "https://login.eveonline.com"
|
||||
return "https://www.pyfa.io"
|
||||
|
||||
@@ -110,20 +98,20 @@ class EsiAccess(object):
|
||||
return '%s/oauth/token' % self.sso_url
|
||||
|
||||
def getSkills(self, char):
|
||||
return self.get(char, ESIEndpoints.CHAR_SKILLS, character_id=char.characterID)
|
||||
return self.get(char, EsiEndpoints.CHAR_SKILLS.value, character_id=char.characterID)
|
||||
|
||||
def getSecStatus(self, char):
|
||||
return self.get(char, ESIEndpoints.CHAR, character_id=char.characterID)
|
||||
return self.get(char, EsiEndpoints.CHAR.value, character_id=char.characterID)
|
||||
|
||||
def getFittings(self, char):
|
||||
return self.get(char, ESIEndpoints.CHAR_FITTINGS, character_id=char.characterID)
|
||||
return self.get(char, EsiEndpoints.CHAR_FITTINGS.value, character_id=char.characterID)
|
||||
|
||||
def postFitting(self, char, json_str):
|
||||
# @todo: new fitting ID can be recovered from resp.data,
|
||||
return self.post(char, ESIEndpoints.CHAR_FITTINGS, json_str, character_id=char.characterID)
|
||||
return self.post(char, EsiEndpoints.CHAR_FITTINGS.value, json_str, character_id=char.characterID)
|
||||
|
||||
def delFitting(self, char, fittingID):
|
||||
return self.delete(char, ESIEndpoints.CHAR_DEL_FIT, character_id=char.characterID, fitting_id=fittingID)
|
||||
return self.delete(char, EsiEndpoints.CHAR_DEL_FIT.value, character_id=char.characterID, fitting_id=fittingID)
|
||||
|
||||
@staticmethod
|
||||
def update_token(char, tokenResponse):
|
||||
@@ -136,7 +124,7 @@ class EsiAccess(object):
|
||||
def getLoginURI(self, redirect=None):
|
||||
self.state = str(uuid.uuid4())
|
||||
|
||||
if self.settings.get("ssoMode") == SsoMode.AUTO:
|
||||
if self.settings.get("ssoMode") == EsiSsoMode.AUTO:
|
||||
args = {
|
||||
'state': self.state,
|
||||
'pyfa_version': config.version,
|
||||
@@ -183,7 +171,7 @@ class EsiAccess(object):
|
||||
'refresh_token': refreshToken,
|
||||
}
|
||||
|
||||
if self.settings.get('ssoMode') == SsoMode.AUTO:
|
||||
if self.settings.get('ssoMode') == EsiSsoMode.AUTO:
|
||||
# data is all we really need, the rest is handled automatically by pyfa.io
|
||||
return {
|
||||
'data': data,
|
||||
|
||||
@@ -30,8 +30,9 @@ from eos.saveddata.citadel import Citadel as es_Citadel
|
||||
from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern
|
||||
from eos.saveddata.drone import Drone as es_Drone
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.fit import Fit as FitType, ImplantLocation
|
||||
from eos.saveddata.module import Module as es_Module, State
|
||||
from eos.const import ImplantLocation, FittingModuleState
|
||||
from eos.saveddata.fit import Fit as FitType
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from eos.saveddata.ship import Ship as es_Ship
|
||||
from service.character import Character
|
||||
from service.damagePattern import DamagePattern
|
||||
@@ -146,6 +147,11 @@ class Fit(FitDeprecated):
|
||||
pyfalog.debug("Getting count of all fits.")
|
||||
return eos.db.countAllFits()
|
||||
|
||||
@staticmethod
|
||||
def countAllFitsGroupedByShip():
|
||||
count = eos.db.countFitGroupedByShip()
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def countFitsWithShip(stuff):
|
||||
pyfalog.debug("Getting count of all fits for: {0}", stuff)
|
||||
@@ -347,7 +353,7 @@ class Fit(FitDeprecated):
|
||||
elif isinstance(thing, es_Module):
|
||||
thing.state = es_Module.getProposedState(thing, click)
|
||||
if not thing.canHaveState(thing.state, fit):
|
||||
thing.state = State.OFFLINE
|
||||
thing.state = FittingModuleState.OFFLINE
|
||||
elif isinstance(thing, FitType):
|
||||
projectionInfo = thing.getProjectionInfo(fitID)
|
||||
if projectionInfo:
|
||||
@@ -379,8 +385,8 @@ class Fit(FitDeprecated):
|
||||
if m.fits(fit):
|
||||
m.owner = fit
|
||||
fit.modules.toModule(position, m)
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
self.recalc(fit)
|
||||
@@ -534,13 +540,13 @@ class Fit(FitDeprecated):
|
||||
if mod != base:
|
||||
# fix for #529, where a module may be in incorrect state after CCP changes mechanics of module
|
||||
if not mod.canHaveState(mod.state) or not mod.isValidState(mod.state):
|
||||
mod.state = State.ONLINE
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
changed = True
|
||||
|
||||
for mod in fit.projectedModules:
|
||||
# fix for #529, where a module may be in incorrect state after CCP changes mechanics of module
|
||||
if not mod.canHaveState(mod.state, fit) or not mod.isValidState(mod.state):
|
||||
mod.state = State.OFFLINE
|
||||
mod.state = FittingModuleState.OFFLINE
|
||||
changed = True
|
||||
|
||||
for drone in fit.projectedDrones:
|
||||
@@ -549,9 +555,26 @@ class Fit(FitDeprecated):
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
# If any state was changed, recalculate attributes again
|
||||
# if changed:
|
||||
# self.recalc(fit)
|
||||
|
||||
@classmethod
|
||||
def fitObjectIter(cls, fit):
|
||||
yield fit.ship
|
||||
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty:
|
||||
yield mod
|
||||
|
||||
for container in (fit.drones, fit.fighters, fit.implants, fit.boosters, fit.cargo):
|
||||
for obj in container:
|
||||
yield obj
|
||||
|
||||
@classmethod
|
||||
def fitItemIter(cls, fit):
|
||||
for fitobj in cls.fitObjectIter(fit):
|
||||
yield fitobj.item
|
||||
charge = getattr(fitobj, 'charge', None)
|
||||
if charge:
|
||||
yield charge
|
||||
|
||||
def refreshFit(self, fitID):
|
||||
pyfalog.debug("Refresh fit for fit ID: {0}", fitID)
|
||||
|
||||
@@ -27,7 +27,8 @@ from eos.saveddata.cargo import Cargo as es_Cargo
|
||||
from eos.saveddata.drone import Drone as es_Drone
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.implant import Implant as es_Implant
|
||||
from eos.saveddata.module import Module as es_Module, State
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from eos.const import FittingModuleState
|
||||
from eos.saveddata.fit import Fit as FitType
|
||||
from utils.deprecated import deprecated
|
||||
|
||||
@@ -304,16 +305,16 @@ class FitDeprecated(object):
|
||||
fit.projectedFighters.append(fighter)
|
||||
elif thing.group.name in es_Module.SYSTEM_GROUPS:
|
||||
module = es_Module(thing)
|
||||
module.state = State.ONLINE
|
||||
module.state = FittingModuleState.ONLINE
|
||||
fit.projectedModules.append(module)
|
||||
else:
|
||||
try:
|
||||
module = es_Module(thing)
|
||||
except ValueError:
|
||||
return False
|
||||
module.state = State.ACTIVE
|
||||
module.state = FittingModuleState.ACTIVE
|
||||
if not module.canHaveState(module.state, fit):
|
||||
module.state = State.OFFLINE
|
||||
module.state = FittingModuleState.OFFLINE
|
||||
fit.projectedModules.append(module)
|
||||
|
||||
eos.db.commit()
|
||||
@@ -396,8 +397,8 @@ class FitDeprecated(object):
|
||||
m.owner = fit
|
||||
numSlots = len(fit.modules)
|
||||
fit.modules.append(m)
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
self.recalc(fit)
|
||||
@@ -465,8 +466,8 @@ class FitDeprecated(object):
|
||||
if m.fits(fit):
|
||||
m.owner = fit
|
||||
fit.modules.toModule(position, m)
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
|
||||
if recalc:
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
@@ -508,8 +509,8 @@ class FitDeprecated(object):
|
||||
try:
|
||||
cargoP = es_Module(cargo.item)
|
||||
cargoP.owner = fit
|
||||
if cargoP.isValidState(State.ACTIVE):
|
||||
cargoP.state = State.ACTIVE
|
||||
if cargoP.isValidState(FittingModuleState.ACTIVE):
|
||||
cargoP.state = FittingModuleState.ACTIVE
|
||||
except:
|
||||
pyfalog.warning("Invalid item: {0}", cargo.item)
|
||||
return
|
||||
|
||||
@@ -795,3 +795,20 @@ class Market(object):
|
||||
"""Filter items by meta lvl"""
|
||||
filtered = set([item for item in items if self.getMetaGroupIdByItem(item) in metas])
|
||||
return filtered
|
||||
|
||||
def getReplacements(self, identity):
|
||||
item = self.getItem(identity)
|
||||
# We already store needed type IDs in database
|
||||
replTypeIDs = {int(i) for i in item.replacements.split(",") if i}
|
||||
if not replTypeIDs:
|
||||
return ()
|
||||
# As replacements were generated without keeping track which items were published,
|
||||
# filter them out here
|
||||
items = []
|
||||
for typeID in replTypeIDs:
|
||||
item = self.getItem(typeID)
|
||||
if not item:
|
||||
continue
|
||||
if self.getPublicityByItem(item):
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
@@ -17,30 +17,37 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
import time
|
||||
|
||||
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
|
||||
from service.price import Price
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class EveMarketData(object):
|
||||
class EveMarketData:
|
||||
|
||||
name = "eve-marketdata.com"
|
||||
|
||||
def __init__(self, types, system, priceMap):
|
||||
data = {}
|
||||
baseurl = "https://eve-marketdata.com/api/item_prices.xml"
|
||||
data["system_id"] = system # Use Jita for market
|
||||
data["type_ids"] = ','.join(str(x) for x in types)
|
||||
def __init__(self, priceMap, system, fetchTimeout):
|
||||
# Try selected system first
|
||||
self.fetchPrices(priceMap, max(2 * fetchTimeout / 3, 2), system)
|
||||
# If price was not available - try globally
|
||||
if priceMap:
|
||||
self.fetchPrices(priceMap, max(fetchTimeout / 3, 2))
|
||||
|
||||
@staticmethod
|
||||
def fetchPrices(priceMap, fetchTimeout, system=None):
|
||||
params = {"type_ids": ','.join(str(typeID) for typeID in priceMap)}
|
||||
if system is not None:
|
||||
params["system_id"] = system
|
||||
baseurl = "https://eve-marketdata.com/api/item_prices.xml"
|
||||
network = Network.getInstance()
|
||||
data = network.request(baseurl, network.PRICES, params=data)
|
||||
data = network.request(baseurl, network.PRICES, params=params, timeout=fetchTimeout)
|
||||
xml = minidom.parseString(data.text)
|
||||
types = xml.getElementsByTagName("eve").item(0).getElementsByTagName("price")
|
||||
|
||||
@@ -55,19 +62,10 @@ class EveMarketData(object):
|
||||
pyfalog.warning("Failed to get price for: {0}", type_)
|
||||
continue
|
||||
|
||||
# Fill price data
|
||||
priceobj = priceMap[typeID]
|
||||
|
||||
# eve-marketdata returns 0 if price data doesn't even exist for the item. In this case, don't reset the
|
||||
# cached price, and set the price timeout to TIMEOUT (every 15 minutes currently). Se GH issue #1334
|
||||
if price != 0:
|
||||
priceobj.price = price
|
||||
priceobj.time = time.time() + VALIDITY
|
||||
priceobj.status = PriceStatus.success
|
||||
else:
|
||||
priceobj.time = time.time() + TIMEOUT
|
||||
|
||||
# delete price from working dict
|
||||
# eve-marketdata returns 0 if price data doesn't even exist for the item
|
||||
if price == 0:
|
||||
continue
|
||||
priceMap[typeID].update(PriceStatus.fetchSuccess, price)
|
||||
del priceMap[typeID]
|
||||
|
||||
|
||||
|
||||
@@ -17,33 +17,37 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
import time
|
||||
|
||||
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
|
||||
from service.price import Price
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class EveMarketer(object):
|
||||
class EveMarketer:
|
||||
|
||||
name = "evemarketer"
|
||||
|
||||
def __init__(self, types, system, priceMap):
|
||||
data = {}
|
||||
def __init__(self, priceMap, system, fetchTimeout):
|
||||
# Try selected system first
|
||||
self.fetchPrices(priceMap, max(2 * fetchTimeout / 3, 2), system)
|
||||
# If price was not available - try globally
|
||||
if priceMap:
|
||||
self.fetchPrices(priceMap, max(fetchTimeout / 3, 2))
|
||||
|
||||
@staticmethod
|
||||
def fetchPrices(priceMap, fetchTimeout, system=None):
|
||||
params = {"typeid": {typeID for typeID in priceMap}}
|
||||
if system is not None:
|
||||
params["usesystem"] = system
|
||||
baseurl = "https://api.evemarketer.com/ec/marketstat"
|
||||
|
||||
data["usesystem"] = system # Use Jita for market
|
||||
data["typeid"] = set()
|
||||
for typeID in types: # Add all typeID arguments
|
||||
data["typeid"].add(typeID)
|
||||
|
||||
network = Network.getInstance()
|
||||
data = network.request(baseurl, network.PRICES, params=data)
|
||||
data = network.request(baseurl, network.PRICES, params=params, timeout=fetchTimeout)
|
||||
xml = minidom.parseString(data.text)
|
||||
types = xml.getElementsByTagName("marketstat").item(0).getElementsByTagName("type")
|
||||
# Cycle through all types we've got from request
|
||||
@@ -56,15 +60,15 @@ class EveMarketer(object):
|
||||
percprice = float(sell.getElementsByTagName("percentile").item(0).firstChild.data)
|
||||
except (TypeError, ValueError):
|
||||
pyfalog.warning("Failed to get price for: {0}", type_)
|
||||
percprice = 0
|
||||
continue
|
||||
|
||||
# Fill price data
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.price = percprice
|
||||
priceobj.time = time.time() + VALIDITY
|
||||
priceobj.status = PriceStatus.success
|
||||
# Price is 0 if evemarketer has info on this item, but it is not available
|
||||
# for current scope limit. If we provided scope limit - make sure to skip
|
||||
# such items to check globally, and do not skip if requested globally
|
||||
if percprice == 0 and system is not None:
|
||||
continue
|
||||
|
||||
# delete price from working dict
|
||||
priceMap[typeID].update(PriceStatus.fetchSuccess, percprice)
|
||||
del priceMap[typeID]
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Network(object):
|
||||
class Network:
|
||||
# Request constants - every request must supply this, as it is checked if
|
||||
# enabled or not via settings
|
||||
ENABLED = 1
|
||||
|
||||
@@ -28,8 +28,9 @@ from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module, State, Slot
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.const import FittingSlot, FittingModuleState
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
|
||||
@@ -106,8 +107,8 @@ def importDna(string):
|
||||
f.modules.append(m)
|
||||
else:
|
||||
m.owner = f
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
moduleList.append(m)
|
||||
|
||||
# Recalc to get slot numbers correct for T3 cruisers
|
||||
@@ -116,14 +117,14 @@ def importDna(string):
|
||||
for module in moduleList:
|
||||
if module.fits(f):
|
||||
module.owner = f
|
||||
if module.isValidState(State.ACTIVE):
|
||||
module.state = State.ACTIVE
|
||||
if module.isValidState(FittingModuleState.ACTIVE):
|
||||
module.state = FittingModuleState.ACTIVE
|
||||
f.modules.append(module)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def exportDna(fit):
|
||||
def exportDna(fit, callback):
|
||||
dna = str(fit.shipID)
|
||||
subsystems = [] # EVE cares which order you put these in
|
||||
mods = OrderedDict()
|
||||
@@ -131,7 +132,7 @@ def exportDna(fit):
|
||||
sFit = svcFit.getInstance()
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty:
|
||||
if mod.slot == Slot.SUBSYSTEM:
|
||||
if mod.slot == FittingSlot.SUBSYSTEM:
|
||||
subsystems.append(mod)
|
||||
continue
|
||||
if mod.itemID not in mods:
|
||||
@@ -173,4 +174,9 @@ def exportDna(fit):
|
||||
for charge in charges:
|
||||
dna += ":{0};{1}".format(charge, charges[charge])
|
||||
|
||||
return dna + "::"
|
||||
text = dna + "::"
|
||||
|
||||
if callback:
|
||||
callback(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
@@ -6,8 +6,9 @@ from numbers import Number
|
||||
from config import version as pyfaVersion
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from eos.enum import Enum
|
||||
from eos.saveddata.module import Hardpoint, Slot, Module, State
|
||||
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
|
||||
from service.const import PortEftRigSize
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.effectHandlerHelpers import HandledList
|
||||
from eos.db import gamedata_session, getCategory, getAttributeInfo, getGroup
|
||||
@@ -19,14 +20,6 @@ from logbook import Logger
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class RigSize(Enum):
|
||||
# Matches to item attribute "rigSize" on ship and rig items
|
||||
SMALL = 1
|
||||
MEDIUM = 2
|
||||
LARGE = 3
|
||||
CAPITAL = 4
|
||||
|
||||
|
||||
class EfsPort:
|
||||
wepTestSet = {}
|
||||
version = 0.03
|
||||
@@ -58,13 +51,13 @@ class EfsPort:
|
||||
mwd50mn = mapPropData("50MN Microwarpdrive II")
|
||||
mwd500mn = mapPropData("500MN Microwarpdrive II")
|
||||
mwd50000mn = mapPropData("50000MN Microwarpdrive II")
|
||||
if rigSize == RigSize.SMALL or rigSize is None:
|
||||
if rigSize == PortEftRigSize.SMALL or rigSize is None:
|
||||
propID = mwd5mn["id"] if shipPower > mwd5mn["powerReq"] else None
|
||||
elif rigSize == RigSize.MEDIUM:
|
||||
elif rigSize == PortEftRigSize.MEDIUM:
|
||||
propID = mwd50mn["id"] if shipPower > mwd50mn["powerReq"] else mwd5mn["id"]
|
||||
elif rigSize == RigSize.LARGE:
|
||||
elif rigSize == PortEftRigSize.LARGE:
|
||||
propID = mwd500mn["id"] if shipPower > mwd500mn["powerReq"] else mwd50mn["id"]
|
||||
elif rigSize == RigSize.CAPITAL:
|
||||
elif rigSize == PortEftRigSize.CAPITAL:
|
||||
propID = mwd50000mn["id"] if shipPower > mwd50000mn["powerReq"] else mwd500mn["id"]
|
||||
|
||||
if propID is None:
|
||||
@@ -86,7 +79,7 @@ class EfsPort:
|
||||
propWithBloom = next(filter(activePropWBloomFilter, propMods), None)
|
||||
if propWithBloom is not None:
|
||||
oldPropState = propWithBloom.state
|
||||
propWithBloom.state = State.ONLINE
|
||||
propWithBloom.state = FittingModuleState.ONLINE
|
||||
sFit.recalc(fit)
|
||||
sp = fit.maxSpeed
|
||||
sig = fit.ship.getModifiedItemAttr("signatureRadius")
|
||||
@@ -198,8 +191,8 @@ class EfsPort:
|
||||
def getModuleInfo(fit, padTypeIDs=False):
|
||||
moduleNames = []
|
||||
modTypeIDs = []
|
||||
moduleNameSets = {Slot.LOW: [], Slot.MED: [], Slot.HIGH: [], Slot.RIG: [], Slot.SUBSYSTEM: []}
|
||||
modTypeIDSets = {Slot.LOW: [], Slot.MED: [], Slot.HIGH: [], Slot.RIG: [], Slot.SUBSYSTEM: []}
|
||||
moduleNameSets = {FittingSlot.LOW: [], FittingSlot.MED: [], FittingSlot.HIGH: [], FittingSlot.RIG: [], FittingSlot.SUBSYSTEM: []}
|
||||
modTypeIDSets = {FittingSlot.LOW: [], FittingSlot.MED: [], FittingSlot.HIGH: [], FittingSlot.RIG: [], FittingSlot.SUBSYSTEM: []}
|
||||
for mod in fit.modules:
|
||||
try:
|
||||
if mod.item is not None:
|
||||
@@ -216,17 +209,17 @@ class EfsPort:
|
||||
pyfalog.error("Could not find name for module {0}".format(vars(mod)))
|
||||
|
||||
for modInfo in [
|
||||
["High Slots:"], moduleNameSets[Slot.HIGH], ["", "Med Slots:"], moduleNameSets[Slot.MED],
|
||||
["", "Low Slots:"], moduleNameSets[Slot.LOW], ["", "Rig Slots:"], moduleNameSets[Slot.RIG]
|
||||
["High Slots:"], moduleNameSets[FittingSlot.HIGH], ["", "Med Slots:"], moduleNameSets[FittingSlot.MED],
|
||||
["", "Low Slots:"], moduleNameSets[FittingSlot.LOW], ["", "Rig Slots:"], moduleNameSets[FittingSlot.RIG]
|
||||
]:
|
||||
moduleNames.extend(modInfo)
|
||||
if len(moduleNameSets[Slot.SUBSYSTEM]) > 0:
|
||||
if len(moduleNameSets[FittingSlot.SUBSYSTEM]) > 0:
|
||||
moduleNames.extend(["", "Subsystems:"])
|
||||
moduleNames.extend(moduleNameSets[Slot.SUBSYSTEM])
|
||||
moduleNames.extend(moduleNameSets[FittingSlot.SUBSYSTEM])
|
||||
|
||||
for slotType in [Slot.HIGH, Slot.MED, Slot.LOW, Slot.RIG, Slot.SUBSYSTEM]:
|
||||
if slotType is not Slot.SUBSYSTEM or len(modTypeIDSets[slotType]) > 0:
|
||||
modTypeIDs.extend([0, 0] if slotType is not Slot.HIGH else [0])
|
||||
for slotType in [FittingSlot.HIGH, FittingSlot.MED, FittingSlot.LOW, FittingSlot.RIG, FittingSlot.SUBSYSTEM]:
|
||||
if slotType is not FittingSlot.SUBSYSTEM or len(modTypeIDSets[slotType]) > 0:
|
||||
modTypeIDs.extend([0, 0] if slotType is not FittingSlot.HIGH else [0])
|
||||
modTypeIDs.extend(modTypeIDSets[slotType])
|
||||
|
||||
droneNames = []
|
||||
@@ -331,18 +324,18 @@ class EfsPort:
|
||||
name = stats.item.name + ", " + stats.charge.name
|
||||
else:
|
||||
name = stats.item.name
|
||||
if stats.hardpoint == Hardpoint.TURRET:
|
||||
if stats.hardpoint == FittingHardpoint.TURRET:
|
||||
tracking = stats.getModifiedItemAttr("trackingSpeed")
|
||||
typeing = "Turret"
|
||||
# Bombs share most attributes with missiles despite not needing the hardpoint
|
||||
elif stats.hardpoint == Hardpoint.MISSILE or "Bomb Launcher" in stats.item.name:
|
||||
elif stats.hardpoint == FittingHardpoint.MISSILE or "Bomb Launcher" in stats.item.name:
|
||||
maxVelocity = stats.getModifiedChargeAttr("maxVelocity")
|
||||
explosionDelay = stats.getModifiedChargeAttr("explosionDelay")
|
||||
damageReductionFactor = stats.getModifiedChargeAttr("aoeDamageReductionFactor")
|
||||
explosionRadius = stats.getModifiedChargeAttr("aoeCloudSize")
|
||||
explosionVelocity = stats.getModifiedChargeAttr("aoeVelocity")
|
||||
typeing = "Missile"
|
||||
elif stats.hardpoint == Hardpoint.NONE:
|
||||
elif stats.hardpoint == FittingHardpoint.NONE:
|
||||
aoeFieldRange = stats.getModifiedItemAttr("empFieldRange")
|
||||
# This also covers non-bomb weapons with dps values and no hardpoints, most notably targeted doomsdays.
|
||||
typeing = "SmartBomb"
|
||||
@@ -496,11 +489,11 @@ class EfsPort:
|
||||
getDroneMulti = lambda d: sumDamage(d.getModifiedItemAttr) * d.getModifiedItemAttr("damageMultiplier")
|
||||
fitMultipliers["drones"] = list(map(getDroneMulti, tf.drones))
|
||||
|
||||
getFitTurrets = lambda f: filter(lambda mod: mod.hardpoint == Hardpoint.TURRET, f.modules)
|
||||
getFitTurrets = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.TURRET, f.modules)
|
||||
getTurretMulti = lambda mod: mod.getModifiedItemAttr("damageMultiplier") / mod.cycleTime
|
||||
fitMultipliers["turrets"] = list(map(getTurretMulti, getFitTurrets(tf)))
|
||||
|
||||
getFitLaunchers = lambda f: filter(lambda mod: mod.hardpoint == Hardpoint.MISSILE, f.modules)
|
||||
getFitLaunchers = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.MISSILE, f.modules)
|
||||
getLauncherMulti = lambda mod: sumDamage(mod.getModifiedChargeAttr) / mod.cycleTime
|
||||
fitMultipliers["launchers"] = list(map(getLauncherMulti, getFitLaunchers(tf)))
|
||||
return fitMultipliers
|
||||
@@ -538,7 +531,7 @@ class EfsPort:
|
||||
if effect._Effect__effectModule is not None:
|
||||
effect.handler(tf, fit.mode, [])
|
||||
if fit.ship.item.groupID == getGroup("Strategic Cruiser").ID:
|
||||
subSystems = list(filter(lambda mod: mod.slot == Slot.SUBSYSTEM and mod.item, fit.modules))
|
||||
subSystems = list(filter(lambda mod: mod.slot == FittingSlot.SUBSYSTEM and mod.item, fit.modules))
|
||||
for sub in subSystems:
|
||||
for effect in sub.item.effects.values():
|
||||
if effect._Effect__effectModule is not None:
|
||||
@@ -583,7 +576,7 @@ class EfsPort:
|
||||
return sizeNotFoundMsg
|
||||
|
||||
@staticmethod
|
||||
def exportEfs(fit, typeNotFitFlag):
|
||||
def exportEfs(fit, typeNotFitFlag, callback):
|
||||
sFit = Fit.getInstance()
|
||||
includeShipTypeData = typeNotFitFlag > 0
|
||||
if includeShipTypeData:
|
||||
@@ -680,4 +673,8 @@ class EfsPort:
|
||||
pyfalog.error(e)
|
||||
dataDict = {"name": fitName + "Fit could not be correctly parsed"}
|
||||
export = json.dumps(dataDict, skipkeys=True)
|
||||
return export
|
||||
|
||||
if callback:
|
||||
callback(export)
|
||||
else:
|
||||
return export
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
@@ -30,9 +29,11 @@ from eos.saveddata.booster import Booster
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.module import Module, State, Slot
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.const import FittingSlot, FittingModuleState
|
||||
from service.const import PortEftOptions, PortEftRigSize
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
from service.port.muta import parseMutant, renderMutant
|
||||
@@ -41,26 +42,19 @@ from service.port.shared import IPortUser, fetchItem, processing_notify
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class Options(Enum):
|
||||
IMPLANTS = 1
|
||||
MUTATIONS = 2
|
||||
LOADED_CHARGES = 3
|
||||
|
||||
|
||||
EFT_OPTIONS = (
|
||||
(Options.LOADED_CHARGES.value, 'Loaded Charges', 'Export charges loaded into modules', True),
|
||||
(Options.MUTATIONS.value, 'Mutated Attributes', 'Export mutated modules\' stats', True),
|
||||
(Options.IMPLANTS.value, 'Implants && Boosters', 'Export implants and boosters', True),
|
||||
(PortEftOptions.LOADED_CHARGES, 'Loaded Charges', 'Export charges loaded into modules', True),
|
||||
(PortEftOptions.MUTATIONS, 'Mutated Attributes', 'Export mutated modules\' stats', True),
|
||||
(PortEftOptions.IMPLANTS, 'Implants && Boosters', 'Export implants and boosters', True),
|
||||
)
|
||||
|
||||
|
||||
MODULE_CATS = ('Module', 'Subsystem', 'Structure Module')
|
||||
SLOT_ORDER = (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE)
|
||||
SLOT_ORDER = (FittingSlot.LOW, FittingSlot.MED, FittingSlot.HIGH, FittingSlot.RIG, FittingSlot.SUBSYSTEM, FittingSlot.SERVICE)
|
||||
OFFLINE_SUFFIX = '/OFFLINE'
|
||||
|
||||
|
||||
def exportEft(fit, options):
|
||||
def exportEft(fit, options, callback):
|
||||
# EFT formatted export is split in several sections, each section is
|
||||
# separated from another using 2 blank lines. Sections might have several
|
||||
# sub-sections, which are separated by 1 blank line
|
||||
@@ -86,21 +80,21 @@ def exportEft(fit, options):
|
||||
modName = module.baseItem.name
|
||||
else:
|
||||
modName = module.item.name
|
||||
if module.isMutated and options[Options.MUTATIONS.value]:
|
||||
if module.isMutated and options[PortEftOptions.MUTATIONS]:
|
||||
mutants[mutantReference] = module
|
||||
mutationSuffix = ' [{}]'.format(mutantReference)
|
||||
mutantReference += 1
|
||||
else:
|
||||
mutationSuffix = ''
|
||||
modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == State.OFFLINE else ''
|
||||
if module.charge and options[Options.LOADED_CHARGES.value]:
|
||||
modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == FittingModuleState.OFFLINE else ''
|
||||
if module.charge and options[PortEftOptions.LOADED_CHARGES]:
|
||||
rackLines.append('{}, {}{}{}'.format(
|
||||
modName, module.charge.name, modOfflineSuffix, mutationSuffix))
|
||||
else:
|
||||
rackLines.append('{}{}{}'.format(modName, modOfflineSuffix, mutationSuffix))
|
||||
else:
|
||||
rackLines.append('[Empty {} slot]'.format(
|
||||
Slot.getName(slotType).capitalize() if slotType is not None else ''))
|
||||
FittingSlot(slotType).name.capitalize() if slotType is not None else ''))
|
||||
if rackLines:
|
||||
modSection.append('\n'.join(rackLines))
|
||||
if modSection:
|
||||
@@ -122,15 +116,15 @@ def exportEft(fit, options):
|
||||
sections.append('\n\n'.join(minionSection))
|
||||
|
||||
# Section 3: implants, boosters
|
||||
if options[Options.IMPLANTS.value]:
|
||||
if options[PortEftOptions.IMPLANTS]:
|
||||
charSection = []
|
||||
implantLines = []
|
||||
for implant in fit.implants:
|
||||
for implant in sorted(fit.implants, key=lambda i: i.slot or 0):
|
||||
implantLines.append(implant.item.name)
|
||||
if implantLines:
|
||||
charSection.append('\n'.join(implantLines))
|
||||
boosterLines = []
|
||||
for booster in fit.boosters:
|
||||
for booster in sorted(fit.boosters, key=lambda b: b.slot or 0):
|
||||
boosterLines.append(booster.item.name)
|
||||
if boosterLines:
|
||||
charSection.append('\n'.join(boosterLines))
|
||||
@@ -149,14 +143,19 @@ def exportEft(fit, options):
|
||||
|
||||
# Section 5: mutated modules' details
|
||||
mutationLines = []
|
||||
if mutants and options[Options.MUTATIONS.value]:
|
||||
if mutants and options[PortEftOptions.MUTATIONS]:
|
||||
for mutantReference in sorted(mutants):
|
||||
mutant = mutants[mutantReference]
|
||||
mutationLines.append(renderMutant(mutant, firstPrefix='[{}] '.format(mutantReference), prefix=' '))
|
||||
if mutationLines:
|
||||
sections.append('\n'.join(mutationLines))
|
||||
|
||||
return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
|
||||
text = '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
|
||||
|
||||
if callback:
|
||||
callback(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def importEft(lines):
|
||||
@@ -441,8 +440,8 @@ def importEftCfg(shipname, lines, iportuser):
|
||||
else:
|
||||
m.owner = fitobj
|
||||
# Activate mod if it is activable
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
# Add charge to mod if applicable, on any errors just don't add anything
|
||||
if chargeName:
|
||||
try:
|
||||
@@ -722,12 +721,12 @@ class AbstractFit:
|
||||
@property
|
||||
def __slotContainerMap(self):
|
||||
return {
|
||||
Slot.HIGH: self.modulesHigh,
|
||||
Slot.MED: self.modulesMed,
|
||||
Slot.LOW: self.modulesLow,
|
||||
Slot.RIG: self.rigs,
|
||||
Slot.SUBSYSTEM: self.subsystems,
|
||||
Slot.SERVICE: self.services}
|
||||
FittingSlot.HIGH: self.modulesHigh,
|
||||
FittingSlot.MED: self.modulesMed,
|
||||
FittingSlot.LOW: self.modulesLow,
|
||||
FittingSlot.RIG: self.rigs,
|
||||
FittingSlot.SUBSYSTEM: self.subsystems,
|
||||
FittingSlot.SERVICE: self.services}
|
||||
|
||||
def getContainerBySlot(self, slotType):
|
||||
return self.__slotContainerMap.get(slotType)
|
||||
@@ -798,10 +797,10 @@ class AbstractFit:
|
||||
|
||||
if itemSpec.charge is not None and m.isValidCharge(itemSpec.charge):
|
||||
m.charge = itemSpec.charge
|
||||
if itemSpec.offline and m.isValidState(State.OFFLINE):
|
||||
m.state = State.OFFLINE
|
||||
elif m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if itemSpec.offline and m.isValidState(FittingModuleState.OFFLINE):
|
||||
m.state = FittingModuleState.OFFLINE
|
||||
elif m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
return m
|
||||
|
||||
def addImplant(self, itemSpec):
|
||||
|
||||
@@ -28,7 +28,8 @@ from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module, State, Slot
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingSlot, FittingModuleState
|
||||
from eos.saveddata.ship import Ship
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
@@ -41,12 +42,12 @@ class ESIExportException(Exception):
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
INV_FLAGS = {
|
||||
Slot.LOW: 11,
|
||||
Slot.MED: 19,
|
||||
Slot.HIGH: 27,
|
||||
Slot.RIG: 92,
|
||||
Slot.SUBSYSTEM: 125,
|
||||
Slot.SERVICE: 164
|
||||
FittingSlot.LOW: 11,
|
||||
FittingSlot.MED: 19,
|
||||
FittingSlot.HIGH: 27,
|
||||
FittingSlot.RIG: 92,
|
||||
FittingSlot.SUBSYSTEM: 125,
|
||||
FittingSlot.SERVICE: 164
|
||||
}
|
||||
|
||||
INV_FLAG_CARGOBAY = 5
|
||||
@@ -54,7 +55,7 @@ INV_FLAG_DRONEBAY = 87
|
||||
INV_FLAG_FIGHTER = 158
|
||||
|
||||
|
||||
def exportESI(ofit):
|
||||
def exportESI(ofit, callback):
|
||||
# A few notes:
|
||||
# max fit name length is 50 characters
|
||||
# Most keys are created simply because they are required, but bogus data is okay
|
||||
@@ -82,7 +83,7 @@ def exportESI(ofit):
|
||||
item = nested_dict()
|
||||
slot = module.slot
|
||||
|
||||
if slot == Slot.SUBSYSTEM:
|
||||
if slot == FittingSlot.SUBSYSTEM:
|
||||
# Order of subsystem matters based on this attr. See GH issue #130
|
||||
slot = int(module.getModifiedItemAttr("subSystemSlot"))
|
||||
item['flag'] = slot
|
||||
@@ -134,7 +135,12 @@ def exportESI(ofit):
|
||||
if len(fit['items']) == 0:
|
||||
raise ESIExportException("Cannot export fitting: module list cannot be empty.")
|
||||
|
||||
return json.dumps(fit)
|
||||
text = json.dumps(fit)
|
||||
|
||||
if callback:
|
||||
callback(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def importESI(string):
|
||||
@@ -189,8 +195,8 @@ def importESI(string):
|
||||
if m.fits(fitobj):
|
||||
fitobj.modules.append(m)
|
||||
else:
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
|
||||
moduleList.append(m)
|
||||
|
||||
|
||||
@@ -18,60 +18,77 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Options(Enum):
|
||||
IMPLANTS = 1
|
||||
CARGO = 2
|
||||
LOADED_CHARGES = 3
|
||||
from service.const import PortMultiBuyOptions
|
||||
from service.price import Price as sPrc
|
||||
|
||||
|
||||
MULTIBUY_OPTIONS = (
|
||||
(Options.LOADED_CHARGES.value, 'Loaded Charges', 'Export charges loaded into modules', True),
|
||||
(Options.IMPLANTS.value, 'Implants && Boosters', 'Export implants and boosters', False),
|
||||
(Options.CARGO.value, 'Cargo', 'Export cargo contents', True),
|
||||
(PortMultiBuyOptions.LOADED_CHARGES, 'Loaded Charges', 'Export charges loaded into modules', True),
|
||||
(PortMultiBuyOptions.IMPLANTS, 'Implants && Boosters', 'Export implants and boosters', False),
|
||||
(PortMultiBuyOptions.CARGO, 'Cargo', 'Export cargo contents', True),
|
||||
(PortMultiBuyOptions.OPTIMIZE_PRICES, 'Optimize Prices', 'Replace items by cheaper alternatives', False),
|
||||
)
|
||||
|
||||
|
||||
def exportMultiBuy(fit, options):
|
||||
itemCounts = {}
|
||||
|
||||
def addItem(item, quantity=1):
|
||||
if item not in itemCounts:
|
||||
itemCounts[item] = 0
|
||||
itemCounts[item] += quantity
|
||||
def exportMultiBuy(fit, options, callback):
|
||||
itemAmounts = {}
|
||||
|
||||
for module in fit.modules:
|
||||
if module.item:
|
||||
# Mutated items are of no use for multibuy
|
||||
if module.isMutated:
|
||||
continue
|
||||
addItem(module.item)
|
||||
if module.charge and options[Options.LOADED_CHARGES.value]:
|
||||
addItem(module.charge, module.numCharges)
|
||||
_addItem(itemAmounts, module.item)
|
||||
if module.charge and options[PortMultiBuyOptions.LOADED_CHARGES]:
|
||||
_addItem(itemAmounts, module.charge, module.numCharges)
|
||||
|
||||
for drone in fit.drones:
|
||||
addItem(drone.item, drone.amount)
|
||||
_addItem(itemAmounts, drone.item, drone.amount)
|
||||
|
||||
for fighter in fit.fighters:
|
||||
addItem(fighter.item, fighter.amountActive)
|
||||
_addItem(itemAmounts, fighter.item, fighter.amountActive)
|
||||
|
||||
if options[Options.CARGO.value]:
|
||||
if options[PortMultiBuyOptions.CARGO]:
|
||||
for cargo in fit.cargo:
|
||||
addItem(cargo.item, cargo.amount)
|
||||
_addItem(itemAmounts, cargo.item, cargo.amount)
|
||||
|
||||
if options[Options.IMPLANTS.value]:
|
||||
if options[PortMultiBuyOptions.IMPLANTS]:
|
||||
for implant in fit.implants:
|
||||
addItem(implant.item)
|
||||
_addItem(itemAmounts, implant.item)
|
||||
|
||||
for booster in fit.boosters:
|
||||
addItem(booster.item)
|
||||
_addItem(itemAmounts, booster.item)
|
||||
|
||||
if options[PortMultiBuyOptions.OPTIMIZE_PRICES]:
|
||||
|
||||
def formatCheaperExportCb(replacementsCheaper):
|
||||
updatedAmounts = {}
|
||||
for item, itemAmount in itemAmounts.items():
|
||||
_addItem(updatedAmounts, replacementsCheaper.get(item, item), itemAmount)
|
||||
string = _prepareString(fit.ship.item, updatedAmounts)
|
||||
callback(string)
|
||||
|
||||
priceSvc = sPrc.getInstance()
|
||||
priceSvc.findCheaperReplacements(itemAmounts, formatCheaperExportCb)
|
||||
else:
|
||||
string = _prepareString(fit.ship.item, itemAmounts)
|
||||
if callback:
|
||||
callback(string)
|
||||
else:
|
||||
return string
|
||||
|
||||
|
||||
def _addItem(container, item, quantity=1):
|
||||
if item not in container:
|
||||
container[item] = 0
|
||||
container[item] += quantity
|
||||
|
||||
|
||||
def _prepareString(shipItem, itemAmounts):
|
||||
exportLines = []
|
||||
exportLines.append(fit.ship.item.name)
|
||||
for item in sorted(itemCounts, key=lambda i: (i.group.category.name, i.group.name, i.name)):
|
||||
count = itemCounts[item]
|
||||
exportLines.append(shipItem.name)
|
||||
for item in sorted(itemAmounts, key=lambda i: (i.group.category.name, i.group.name, i.name)):
|
||||
count = itemAmounts[item]
|
||||
if count == 1:
|
||||
exportLines.append(item.name)
|
||||
else:
|
||||
|
||||
@@ -29,7 +29,7 @@ from bs4 import UnicodeDammit
|
||||
from logbook import Logger
|
||||
|
||||
from eos import db
|
||||
from eos.saveddata.fit import ImplantLocation
|
||||
from eos.const import ImplantLocation
|
||||
from service.fit import Fit as svcFit
|
||||
from service.port.dna import exportDna, importDna
|
||||
from service.port.eft import exportEft, importEft, importEftCfg
|
||||
@@ -257,8 +257,8 @@ class Port(object):
|
||||
return importEftCfg(shipname, lines, iportuser)
|
||||
|
||||
@classmethod
|
||||
def exportEft(cls, fit, options):
|
||||
return exportEft(fit, options)
|
||||
def exportEft(cls, fit, options, callback=None):
|
||||
return exportEft(fit, options, callback=callback)
|
||||
|
||||
# DNA-related methods
|
||||
@staticmethod
|
||||
@@ -266,8 +266,8 @@ class Port(object):
|
||||
return importDna(string)
|
||||
|
||||
@staticmethod
|
||||
def exportDna(fit):
|
||||
return exportDna(fit)
|
||||
def exportDna(fit, callback=None):
|
||||
return exportDna(fit, callback=callback)
|
||||
|
||||
# ESI-related methods
|
||||
@staticmethod
|
||||
@@ -275,8 +275,8 @@ class Port(object):
|
||||
return importESI(string)
|
||||
|
||||
@staticmethod
|
||||
def exportESI(fit):
|
||||
return exportESI(fit)
|
||||
def exportESI(fit, callback=None):
|
||||
return exportESI(fit, callback=callback)
|
||||
|
||||
# XML-related methods
|
||||
@staticmethod
|
||||
@@ -284,10 +284,10 @@ class Port(object):
|
||||
return importXml(text, iportuser)
|
||||
|
||||
@staticmethod
|
||||
def exportXml(iportuser=None, *fits):
|
||||
return exportXml(iportuser, *fits)
|
||||
def exportXml(iportuser=None, callback=None, *fits):
|
||||
return exportXml(iportuser, callback=callback, *fits)
|
||||
|
||||
# Multibuy-related methods
|
||||
@staticmethod
|
||||
def exportMultiBuy(fit, options):
|
||||
return exportMultiBuy(fit, options)
|
||||
def exportMultiBuy(fit, options, callback=None):
|
||||
return exportMultiBuy(fit, options, callback=callback)
|
||||
|
||||
@@ -28,8 +28,9 @@ from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module, State, Slot
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.const import FittingSlot, FittingModuleState
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
from utils.strfunctions import sequential_rep, replace_ltgt
|
||||
@@ -198,8 +199,8 @@ def importXml(text, iportuser):
|
||||
m.owner = fitobj
|
||||
fitobj.modules.append(m)
|
||||
else:
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
if m.isValidState(FittingModuleState.ACTIVE):
|
||||
m.state = FittingModuleState.ACTIVE
|
||||
|
||||
moduleList.append(m)
|
||||
|
||||
@@ -225,14 +226,13 @@ def importXml(text, iportuser):
|
||||
return fit_list
|
||||
|
||||
|
||||
def exportXml(iportuser, *fits):
|
||||
def exportXml(iportuser, callback, *fits):
|
||||
doc = xml.dom.minidom.Document()
|
||||
fittings = doc.createElement("fittings")
|
||||
# fit count
|
||||
fit_count = len(fits)
|
||||
fittings.setAttribute("count", "%s" % fit_count)
|
||||
doc.appendChild(fittings)
|
||||
sFit = svcFit.getInstance()
|
||||
|
||||
for i, fit in enumerate(fits):
|
||||
try:
|
||||
@@ -266,7 +266,7 @@ def exportXml(iportuser, *fits):
|
||||
|
||||
slot = module.slot
|
||||
|
||||
if slot == Slot.SUBSYSTEM:
|
||||
if slot == FittingSlot.SUBSYSTEM:
|
||||
# Order of subsystem matters based on this attr. See GH issue #130
|
||||
slotId = module.getModifiedItemAttr("subSystemSlot") - 125
|
||||
else:
|
||||
@@ -278,7 +278,7 @@ def exportXml(iportuser, *fits):
|
||||
|
||||
hardware = doc.createElement("hardware")
|
||||
hardware.setAttribute("type", module.item.name)
|
||||
slotName = Slot.getName(slot).lower()
|
||||
slotName = FittingSlot(slot).name.lower()
|
||||
slotName = slotName if slotName != "high" else "hi"
|
||||
hardware.setAttribute("slot", "%s slot %d" % (slotName, slotId))
|
||||
fitting.appendChild(hardware)
|
||||
@@ -323,4 +323,9 @@ def exportXml(iportuser, *fits):
|
||||
iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
|
||||
(i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
|
||||
)
|
||||
return doc.toprettyxml()
|
||||
text = doc.toprettyxml()
|
||||
|
||||
if callback:
|
||||
callback(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
174
service/price.py
174
service/price.py
@@ -18,28 +18,26 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from itertools import chain
|
||||
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from eos import db
|
||||
from eos.saveddata.price import PriceStatus
|
||||
from gui.fitCommands.guiRebaseItems import GuiRebaseItemsCommand
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from service.network import TimeoutError
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
VALIDITY = 24 * 60 * 60 # Price validity period, 24 hours
|
||||
REREQUEST = 4 * 60 * 60 # Re-request delay for failed fetches, 4 hours
|
||||
TIMEOUT = 15 * 60 # Network timeout delay for connection issues, 15 minutes
|
||||
|
||||
|
||||
class Price(object):
|
||||
class Price:
|
||||
instance = None
|
||||
|
||||
systemsList = {
|
||||
@@ -69,38 +67,34 @@ class Price(object):
|
||||
return cls.instance
|
||||
|
||||
@classmethod
|
||||
def fetchPrices(cls, prices):
|
||||
def fetchPrices(cls, prices, fetchTimeout, validityOverride):
|
||||
"""Fetch all prices passed to this method"""
|
||||
|
||||
# Dictionary for our price objects
|
||||
priceMap = {}
|
||||
# Check all provided price objects, and add invalid ones to dictionary
|
||||
# Check all provided price objects, and add those we want to update to
|
||||
# dictionary
|
||||
for price in prices:
|
||||
if not price.isValid:
|
||||
if not price.isValid(validityOverride):
|
||||
priceMap[price.typeID] = price
|
||||
|
||||
if len(priceMap) == 0:
|
||||
if not priceMap:
|
||||
return
|
||||
|
||||
# Set of items which are still to be requested from this service
|
||||
toRequest = set()
|
||||
|
||||
# Compose list of items we're going to request
|
||||
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
|
||||
# We're not going to request items only with market group, as our current market
|
||||
# sources do not provide any data for items not on the market
|
||||
if item is None:
|
||||
continue
|
||||
if not item.marketGroupID:
|
||||
priceMap[typeID].status = PriceStatus.notSupported
|
||||
priceMap[typeID].update(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:
|
||||
if not priceMap:
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
@@ -110,62 +104,46 @@ class Price(object):
|
||||
return
|
||||
|
||||
# attempt to find user's selected price source, otherwise get first one
|
||||
sourcesToTry = list(cls.sources.keys())
|
||||
curr = sFit.serviceFittingOptions["priceSource"] if sFit.serviceFittingOptions["priceSource"] in sourcesToTry else sourcesToTry[0]
|
||||
sourceAll = list(cls.sources.keys())
|
||||
sourcePrimary = sFit.serviceFittingOptions["priceSource"] if sFit.serviceFittingOptions["priceSource"] in sourceAll else sourceAll[0]
|
||||
|
||||
while len(sourcesToTry) > 0:
|
||||
sourcesToTry.remove(curr)
|
||||
# Format: {source name: timeout weight}
|
||||
sources = {sourcePrimary: len(sourceAll)}
|
||||
for source in sourceAll:
|
||||
if source == sourcePrimary:
|
||||
continue
|
||||
sources[source] = min(sources.values()) - 1
|
||||
timeoutWeightMult = fetchTimeout / sum(sources.values())
|
||||
|
||||
# Record timeouts as it will affect our final decision
|
||||
timedOutSources = {}
|
||||
|
||||
for source, timeoutWeight in sources.items():
|
||||
pyfalog.info('Trying {}'.format(source))
|
||||
timedOutSources[source] = False
|
||||
sourceFetchTimeout = timeoutWeight * timeoutWeightMult
|
||||
try:
|
||||
sourceCls = cls.sources.get(curr)
|
||||
sourceCls(toRequest, cls.systemsList[sFit.serviceFittingOptions["priceSystem"]], priceMap)
|
||||
break
|
||||
# If getting or processing data returned any errors
|
||||
sourceCls = cls.sources.get(source)
|
||||
sourceCls(priceMap, cls.systemsList[sFit.serviceFittingOptions["priceSystem"]], sourceFetchTimeout)
|
||||
except TimeoutError:
|
||||
# Timeout error deserves special treatment
|
||||
pyfalog.warning("Price fetch timout")
|
||||
for typeID in tuple(priceMap):
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.time = time.time() + TIMEOUT
|
||||
priceobj.status = PriceStatus.fail
|
||||
del priceMap[typeID]
|
||||
except Exception as ex:
|
||||
# something happened, try another source
|
||||
pyfalog.warn('Failed to fetch prices from price source {}: {}'.format(curr, ex, sourcesToTry[0]))
|
||||
if len(sourcesToTry) > 0:
|
||||
pyfalog.warn('Trying {}'.format(sourcesToTry[0]))
|
||||
curr = sourcesToTry[0]
|
||||
pyfalog.warning("Price fetch timeout for source {}".format(source))
|
||||
timedOutSources[source] = True
|
||||
except Exception as e:
|
||||
pyfalog.warn('Failed to fetch prices from price source {}: {}'.format(source, e))
|
||||
# Sources remove price map items as they fetch info, if none remain then we're done
|
||||
if not priceMap:
|
||||
break
|
||||
|
||||
# if we get to this point, then we've got an error in all of our sources. Set to REREQUEST delay
|
||||
for typeID in priceMap.keys():
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.time = time.time() + REREQUEST
|
||||
priceobj.status = PriceStatus.fail
|
||||
|
||||
@classmethod
|
||||
def fitItemsList(cls, fit):
|
||||
# Compose a list of all the data we need & request it
|
||||
fit_items = [fit.ship.item]
|
||||
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty:
|
||||
fit_items.append(mod.item)
|
||||
|
||||
for drone in fit.drones:
|
||||
fit_items.append(drone.item)
|
||||
|
||||
for fighter in fit.fighters:
|
||||
fit_items.append(fighter.item)
|
||||
|
||||
for cargo in fit.cargo:
|
||||
fit_items.append(cargo.item)
|
||||
|
||||
for boosters in fit.boosters:
|
||||
fit_items.append(boosters.item)
|
||||
|
||||
for implants in fit.implants:
|
||||
fit_items.append(implants.item)
|
||||
|
||||
return list(set(fit_items))
|
||||
# If we get to this point, then we've failed to get price with all our sources
|
||||
# If all sources failed due to timeouts, set one status
|
||||
if all(to is True for to in timedOutSources.values()):
|
||||
for typeID in priceMap.keys():
|
||||
priceMap[typeID].update(PriceStatus.fetchTimeout)
|
||||
# If some sources failed due to any other reason, then it's definitely not network
|
||||
# timeout and we just set another status
|
||||
else:
|
||||
for typeID in priceMap.keys():
|
||||
priceMap[typeID].update(PriceStatus.fetchFail)
|
||||
|
||||
def getPriceNow(self, objitem):
|
||||
"""Get price for provided typeID"""
|
||||
@@ -174,7 +152,7 @@ class Price(object):
|
||||
|
||||
return item.price.price
|
||||
|
||||
def getPrices(self, objitems, callback, waitforthread=False):
|
||||
def getPrices(self, objitems, callback, fetchTimeout=30, waitforthread=False, validityOverride=None):
|
||||
"""Get prices for multiple typeIDs"""
|
||||
requests = []
|
||||
sMkt = Market.getInstance()
|
||||
@@ -186,7 +164,7 @@ class Price(object):
|
||||
try:
|
||||
callback(requests)
|
||||
except Exception as e:
|
||||
pyfalog.critical("Callback failed.")
|
||||
pyfalog.critical("Execution of callback from getPrices failed.")
|
||||
pyfalog.critical(e)
|
||||
|
||||
db.commit()
|
||||
@@ -194,12 +172,43 @@ class Price(object):
|
||||
if waitforthread:
|
||||
self.priceWorkerThread.setToWait(requests, cb)
|
||||
else:
|
||||
self.priceWorkerThread.trigger(requests, cb)
|
||||
self.priceWorkerThread.trigger(requests, cb, fetchTimeout, validityOverride)
|
||||
|
||||
def clearPriceCache(self):
|
||||
pyfalog.debug("Clearing Prices")
|
||||
db.clearPrices()
|
||||
|
||||
def findCheaperReplacements(self, items, callback, fetchTimeout=10):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
replacementsAll = {} # All possible item replacements
|
||||
for item in items:
|
||||
if item in replacementsAll:
|
||||
continue
|
||||
itemRepls = sMkt.getReplacements(item)
|
||||
if itemRepls:
|
||||
replacementsAll[item] = itemRepls
|
||||
itemsToFetch = {i for i in chain(replacementsAll.keys(), *replacementsAll.values())}
|
||||
|
||||
def makeCheapMapCb(requests):
|
||||
# Decide what we are going to replace
|
||||
replacementsCheaper = {} # Items which should be replaced
|
||||
for replacee, replacers in replacementsAll.items():
|
||||
replacer = min(replacers, key=lambda i: i.price.price or math.inf)
|
||||
if (replacer.price.price or math.inf) < (replacee.price.price or math.inf):
|
||||
replacementsCheaper[replacee] = replacer
|
||||
try:
|
||||
callback(replacementsCheaper)
|
||||
except Exception as e:
|
||||
pyfalog.critical("Execution of callback from findCheaperReplacements failed.")
|
||||
pyfalog.critical(e)
|
||||
|
||||
# Prices older than 2 hours have to be refetched
|
||||
validityOverride = 2 * 60 * 60
|
||||
self.getPrices(itemsToFetch, makeCheapMapCb, fetchTimeout=fetchTimeout, validityOverride=validityOverride)
|
||||
|
||||
|
||||
|
||||
|
||||
class PriceWorkerThread(threading.Thread):
|
||||
|
||||
@@ -214,11 +223,11 @@ class PriceWorkerThread(threading.Thread):
|
||||
queue = self.queue
|
||||
while True:
|
||||
# Grab our data
|
||||
callback, requests = queue.get()
|
||||
callback, requests, fetchTimeout, validityOverride = queue.get()
|
||||
|
||||
# Grab prices, this is the time-consuming part
|
||||
if len(requests) > 0:
|
||||
Price.fetchPrices(requests)
|
||||
Price.fetchPrices(requests, fetchTimeout, validityOverride)
|
||||
|
||||
wx.CallAfter(callback)
|
||||
queue.task_done()
|
||||
@@ -230,14 +239,13 @@ class PriceWorkerThread(threading.Thread):
|
||||
for callback in callbacks:
|
||||
wx.CallAfter(callback)
|
||||
|
||||
def trigger(self, prices, callbacks):
|
||||
self.queue.put((callbacks, prices))
|
||||
def trigger(self, prices, callbacks, fetchTimeout, validityOverride):
|
||||
self.queue.put((callbacks, prices, fetchTimeout, validityOverride))
|
||||
|
||||
def setToWait(self, prices, callback):
|
||||
for x in prices:
|
||||
if x.typeID not in self.wait:
|
||||
self.wait[x.typeID] = []
|
||||
self.wait[x.typeID].append(callback)
|
||||
for price in prices:
|
||||
callbacks = self.wait.setdefault(price.typeID, [])
|
||||
callbacks.append(callback)
|
||||
|
||||
|
||||
# Import market sources only to initialize price source modules, they register on their own
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user