diff --git a/config.py b/config.py
index 3143e80f0..c20489972 100644
--- a/config.py
+++ b/config.py
@@ -1,6 +1,11 @@
import os
import sys
+# TODO: move all logging back to pyfa.py main loop
+# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
+import logging
+import logging.handlers
+
# Load variable overrides specific to distribution type
try:
import configforced
@@ -12,6 +17,8 @@ debug = False
# Defines if our saveddata will be in pyfa root or not
saveInRoot = False
+logLevel = logging.WARN
+
# Version data
version = "1.12.1"
tag = "git"
@@ -25,11 +32,6 @@ staticPath = None
saveDB = None
gameDB = None
-# TODO: move back to pyfa.py main loop
-# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
-import logging
-logging.basicConfig()
-
def defPaths():
global pyfaPath
global savePath
@@ -55,6 +57,15 @@ def defPaths():
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
sys.getfilesystemencoding())
+ format = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
+ logging.basicConfig(format=format, level=logLevel)
+ handler = logging.handlers.RotatingFileHandler(os.path.join(savePath, "log.txt"), maxBytes=1000000, backupCount=3)
+ formatter = logging.Formatter(format)
+ handler.setFormatter(formatter)
+ logging.getLogger('').addHandler(handler)
+
+ logging.info("Starting pyfa")
+
# Redirect stderr to file if we're requested to do so
stderrToFile = getattr(configforced, "stderrToFile", None)
if stderrToFile is True:
diff --git a/eos/db/migrations/upgrade9.py b/eos/db/migrations/upgrade9.py
new file mode 100644
index 000000000..ae7b66ad5
--- /dev/null
+++ b/eos/db/migrations/upgrade9.py
@@ -0,0 +1,23 @@
+"""
+Migration 9
+
+Effectively drops UNIQUE constraint from boosters table. SQLite does not support
+this, so we have to copy the table to the updated schema and then rename it
+"""
+
+tmpTable = """
+CREATE TABLE boostersTemp (
+ 'ID' INTEGER NOT NULL,
+ 'itemID' INTEGER,
+ 'fitID' INTEGER NOT NULL,
+ 'active' BOOLEAN,
+ PRIMARY KEY(ID),
+ FOREIGN KEY('fitID') REFERENCES fits ('ID')
+)
+"""
+
+def upgrade(saveddata_engine):
+ saveddata_engine.execute(tmpTable)
+ saveddata_engine.execute("INSERT INTO boostersTemp (ID, itemID, fitID, active) SELECT ID, itemID, fitID, active FROM boosters")
+ saveddata_engine.execute("DROP TABLE boosters")
+ saveddata_engine.execute("ALTER TABLE boostersTemp RENAME TO boosters")
diff --git a/eos/db/saveddata/booster.py b/eos/db/saveddata/booster.py
index 459bb76b5..a31904b12 100644
--- a/eos/db/saveddata/booster.py
+++ b/eos/db/saveddata/booster.py
@@ -29,7 +29,7 @@ boosters_table = Table("boosters", saveddata_meta,
Column("itemID", Integer),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
Column("active", Boolean),
- UniqueConstraint("itemID", "fitID"))
+ )
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),
diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py
index 6dfea588b..0c5edaee9 100644
--- a/eos/db/saveddata/fit.py
+++ b/eos/db/saveddata/fit.py
@@ -27,9 +27,7 @@ from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_table
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
-from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
-HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
-HandledProjectedFitList, HandledCargoList
+from eos.effectHandlerHelpers import *
fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key = True),
@@ -55,14 +53,16 @@ mapper(Fit, fits_table,
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
"owner" : relation(User, backref = "fits"),
+ "itemID" : fits_table.c.shipID,
+ "shipID" : fits_table.c.shipID,
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
- "_Fit__drones" : relation(Drone, collection_class = HandledDroneList, cascade='all, delete, delete-orphan', single_parent=True,
+ "_Fit__drones" : relation(Drone, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
- "_Fit__cargo" : relation(Cargo, collection_class = HandledCargoList, cascade='all, delete, delete-orphan', single_parent=True,
+ "_Fit__cargo" : relation(Cargo, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)),
"_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
- "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True,
+ "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', backref='fit', single_parent=True,
primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
secondary = fitImplants_table),
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index be7c078fc..fe4f0310c 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -185,6 +185,12 @@ def getFit(lookfor, eager=None):
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
else:
raise TypeError("Need integer as argument")
+
+ if fit.isInvalid:
+ with sd_lock:
+ removeInvalid([fit])
+ return None
+
return fit
@cachedQuery(Fleet, 1, "fleetID")
@@ -244,9 +250,10 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None):
filter = processWhere(filter, where)
eager = processEager(eager)
with sd_lock:
- fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
+ fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
else:
raise TypeError("ShipID must be integer")
+
return fits
def getBoosterFits(ownerID=None, where=None, eager=None):
@@ -264,7 +271,8 @@ def getBoosterFits(ownerID=None, where=None, eager=None):
filter = processWhere(filter, where)
eager = processEager(eager)
with sd_lock:
- fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
+ fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
+
return fits
def countAllFits():
@@ -295,7 +303,8 @@ def countFitsWithShip(shipID, ownerID=None, where=None, eager=None):
def getFitList(eager=None):
eager = processEager(eager)
with sd_lock:
- fits = saveddata_session.query(Fit).options(*eager).all()
+ fits = removeInvalid(saveddata_session.query(Fit).options(*eager).all())
+
return fits
def getFleetList(eager=None):
@@ -385,7 +394,8 @@ def searchFits(nameLike, where=None, eager=None):
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
eager = processEager(eager)
with sd_lock:
- fits = saveddata_session.query(Fit).options(*eager).filter(filter).all()
+ fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
+
return fits
def getSquadsIDsWithFitID(fitID):
@@ -406,6 +416,16 @@ def getProjectedFits(fitID):
else:
raise TypeError("Need integer as argument")
+def removeInvalid(fits):
+ invalids = [f for f in fits if f.isInvalid]
+
+ if invalids:
+ map(fits.remove, invalids)
+ map(saveddata_session.delete, invalids)
+ saveddata_session.commit()
+
+ return fits
+
def add(stuff):
with sd_lock:
saveddata_session.add(stuff)
diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py
index b3b4c75ee..c658cf0a9 100644
--- a/eos/effectHandlerHelpers.py
+++ b/eos/effectHandlerHelpers.py
@@ -17,8 +17,12 @@
# along with eos. If not, see .
#===============================================================================
+from sqlalchemy.orm.attributes import flag_modified
import eos.db
import eos.types
+import logging
+
+logger = logging.getLogger(__name__)
class HandledList(list):
def filteredItemPreAssign(self, filter, *args, **kwargs):
@@ -101,6 +105,11 @@ class HandledList(list):
except AttributeError:
pass
+ def remove(self, thing):
+ # We must flag it as modified, otherwise it not be removed from the database
+ flag_modified(thing, "itemID")
+ list.remove(self, thing)
+
class HandledModuleList(HandledList):
def append(self, mod):
emptyPosition = float("Inf")
@@ -115,10 +124,14 @@ class HandledModuleList(HandledList):
del self[emptyPosition]
mod.position = emptyPosition
HandledList.insert(self, emptyPosition, mod)
+ if mod.isInvalid:
+ self.remove(mod)
return
mod.position = len(self)
HandledList.append(self, mod)
+ if mod.isInvalid:
+ self.remove(mod)
def insert(self, index, mod):
mod.position = index
@@ -149,128 +162,71 @@ class HandledModuleList(HandledList):
if mod.getModifiedItemAttr("subSystemSlot") == slot:
del self[i]
-class HandledDroneList(HandledList):
+class HandledDroneCargoList(HandledList):
def find(self, item):
- for d in self:
- if d.item == item:
- yield d
+ for o in self:
+ if o.item == item:
+ yield o
def findFirst(self, item):
- for d in self.find(item):
- return d
+ for o in self.find(item):
+ return o
- def append(self, drone):
- list.append(self, drone)
+ def append(self, thing):
+ HandledList.append(self, thing)
- def remove(self, drone):
- HandledList.remove(self, drone)
-
- def appendItem(self, item, amount = 1):
- if amount < 1: ValueError("Amount of drones to add should be >= 1")
- d = self.findFirst(item)
-
- if d is None:
- d = eos.types.Drone(item)
- self.append(d)
-
- d.amount += amount
- return d
-
- def removeItem(self, item, amount):
- if amount < 1: ValueError("Amount of drones to remove should be >= 1")
- d = self.findFirst(item)
- if d is None: return
- d.amount -= amount
- if d.amount <= 0:
- self.remove(d)
- return None
-
- return d
-
-class HandledCargoList(HandledList):
- # shameless copy of HandledDroneList
- # I have no idea what this does, but I needed it
- # @todo: investigate this
- def find(self, item):
- for d in self:
- if d.item == item:
- yield d
-
- def findFirst(self, item):
- for d in self.find(item):
- return d
-
- def append(self, cargo):
- list.append(self, cargo)
-
- def remove(self, cargo):
- HandledList.remove(self, cargo)
-
- def appendItem(self, item, qty = 1):
- if qty < 1: ValueError("Amount of cargo to add should be >= 1")
- d = self.findFirst(item)
-
- if d is None:
- d = eos.types.Cargo(item)
- self.append(d)
-
- d.qty += qty
- return d
-
- def removeItem(self, item, qty):
- if qty < 1: ValueError("Amount of cargo to remove should be >= 1")
- d = self.findFirst(item)
- if d is None: return
- d.qty -= qty
- if d.qty <= 0:
- self.remove(d)
- return None
-
- return d
+ if thing.isInvalid:
+ self.remove(thing)
class HandledImplantBoosterList(HandledList):
- def __init__(self):
- self.__slotCache = {}
+ def append(self, thing):
+ if thing.isInvalid:
+ HandledList.append(self, thing)
+ self.remove(thing)
+ return
- def append(self, implant):
- if self.__slotCache.has_key(implant.slot):
- raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True")
- self.__slotCache[implant.slot] = implant
- HandledList.append(self, implant)
+ # if needed, remove booster that was occupying slot
+ oldObj = next((m for m in self if m.slot == thing.slot), None)
+ if oldObj:
+ logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name)
+ self.remove(oldObj)
- def remove(self, implant):
- HandledList.remove(self, implant)
- del self.__slotCache[implant.slot]
- # While we deleted this implant, in edge case seems like not all references
- # to it are removed and object still lives in session; forcibly remove it,
- # or otherwise when adding the same booster twice booster's table (typeID, fitID)
- # constraint will report database integrity error
- # TODO: make a proper fix, probably by adjusting fit-boosters sqlalchemy relationships
- eos.db.remove(implant)
-
- def freeSlot(self, slot):
- if hasattr(slot, "slot"):
- slot = slot.slot
-
- try:
- implant = self.__slotCache[slot]
- except KeyError:
- return False
- try:
- self.remove(implant)
- except ValueError:
- return False
- return True
+ HandledList.append(self, thing)
class HandledProjectedModList(HandledList):
+ def append(self, proj):
+ if proj.isInvalid:
+ # we must include it before we remove it. doing it this way ensures
+ # rows and relationships in database are removed as well
+ HandledList.append(self, proj)
+ self.remove(proj)
+ return
+
+ proj.projected = True
+ isSystemEffect = proj.item.group.name == "Effect Beacon"
+
+ if isSystemEffect:
+ # remove other system effects - only 1 per fit plz
+ oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None)
+
+ if oldEffect:
+ logging.info("System effect occupied with %s, replacing with %s", oldEffect.item.name, proj.item.name)
+ self.remove(oldEffect)
+
+ HandledList.append(self, proj)
+
+ # Remove non-projectable modules
+ if not proj.item.isType("projected") and not isSystemEffect:
+ self.remove(proj)
+
+class HandledProjectedDroneList(HandledDroneCargoList):
def append(self, proj):
proj.projected = True
HandledList.append(self, proj)
-class HandledProjectedDroneList(HandledDroneList):
- def append(self, proj):
- proj.projected = True
- list.append(self, proj)
+ # Remove invalid or non-projectable drones
+ if proj.isInvalid or not proj.item.isType("projected"):
+ self.remove(proj)
class HandledProjectedFitList(HandledList):
def append(self, proj):
diff --git a/eos/saveddata/booster.py b/eos/saveddata/booster.py
index 001a163de..c0874fe94 100644
--- a/eos/saveddata/booster.py
+++ b/eos/saveddata/booster.py
@@ -20,36 +20,53 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem
from sqlalchemy.orm import reconstructor, validates
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
class Booster(HandledItem, ItemAttrShortcut):
def __init__(self, item):
- self.__slot = self.__calculateSlot(item)
- self.itemID = item.ID
self.__item = item
+
+ if self.isInvalid:
+ raise ValueError("Passed item is not a Booster")
+
+ self.itemID = item.ID if item is not None else None
self.active = True
self.build()
@reconstructor
def init(self):
+ """Initialize a booster from the database and validate"""
self.__item = None
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
+ if self.isInvalid:
+ logger.error("Item (id: %d) is not a Booser", self.itemID)
+ return
+
+ self.build()
+
def build(self):
+ """ Build object. Assumes proper and valid item already set """
+ self.__sideEffects = []
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
- self.__sideEffects = []
- for effect in self.item.effects.itervalues():
+ self.__slot = self.__calculateSlot(self.__item)
+
+ for effect in self.__item.effects.itervalues():
if effect.isType("boosterSideEffect"):
s = SideEffect(self)
s.effect = effect
s.active = effect.ID in self.__activeSideEffectIDs
self.__sideEffects.append(s)
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.itemID)
- self.__slot = self.__calculateSlot(self.__item)
- self.build()
-
def iterSideEffects(self):
return self.__sideEffects.__iter__()
@@ -62,23 +79,18 @@ class Booster(HandledItem, ItemAttrShortcut):
@property
def itemModifiedAttributes(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
@property
- def slot(self):
- if self.__item is None:
- self.__fetchItemInfo()
+ def isInvalid(self):
+ return self.__item is None or self.__item.group.name != "Booster"
+ @property
+ def slot(self):
return self.__slot
@property
def item(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__item
def __calculateSlot(self, item):
diff --git a/eos/saveddata/cargo.py b/eos/saveddata/cargo.py
index 95ef072b6..26509d525 100644
--- a/eos/saveddata/cargo.py
+++ b/eos/saveddata/cargo.py
@@ -20,40 +20,45 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
+import eos.db
+import logging
-# Cargo class copied from Implant class and hacked to make work. \o/
-# @todo: clean me up, Scotty
-class Cargo(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
+logger = logging.getLogger(__name__)
+
+class Cargo(HandledItem, ItemAttrShortcut):
def __init__(self, item):
+ """Initialize cargo from the program"""
self.__item = item
- self.itemID = item.ID
+ self.itemID = item.ID if item is not None else None
self.amount = 0
self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__itemModifiedAttributes.original = self.item.attributes
+ self.__itemModifiedAttributes.original = item.attributes
@reconstructor
def init(self):
+ """Initialize cargo from the database and validate"""
self.__item = None
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.itemID)
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
@property
def itemModifiedAttributes(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
@property
- def item(self):
- if self.__item is None:
- self.__fetchItemInfo()
+ def isInvalid(self):
+ return self.__item is None
+ @property
+ def item(self):
return self.__item
def clear(self):
diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py
index 289674f1e..1a63d57a3 100644
--- a/eos/saveddata/drone.py
+++ b/eos/saveddata/drone.py
@@ -20,81 +20,80 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
- if item.category.name != "Drone":
- raise ValueError("Passed item is not a drone")
-
+ """Initialize a drone from the program"""
self.__item = item
- self.__charge = None
- self.itemID = item.ID
+
+ if self.isInvalid:
+ raise ValueError("Passed item is not a Drone")
+
+ self.itemID = item.ID if item is not None else None
self.amount = 0
self.amountActive = 0
- self.__dps = None
- self.__volley = None
- self.__miningyield = None
self.projected = False
- self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.itemModifiedAttributes.original = self.item.attributes
+ self.build()
@reconstructor
def init(self):
+ """Initialize a drone from the database and validate"""
+ self.__item = None
+
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
+ if self.isInvalid:
+ logger.error("Item (id: %d) is not a Drone", self.itemID)
+ return
+
+ self.build()
+
+ def build(self):
+ """ Build object. Assumes proper and valid item already set """
+ self.__charge = None
self.__dps = None
self.__volley = None
self.__miningyield = None
- self.__item = None
- self.__charge = None
-
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.itemID)
- self.__charge = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__itemModifiedAttributes.original = self.item.attributes
+ self.__itemModifiedAttributes.original = self.__item.attributes
- def __fetchChargeInfo(self):
- chargeID = self.getModifiedItemAttr("entityMissileTypeID")
self.__chargeModifiedAttributes = ModifiedAttributeDict()
+ chargeID = self.getModifiedItemAttr("entityMissileTypeID")
if chargeID is not None:
- import eos.db
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
-
- self.chargeModifiedAttributes.original = charge.attributes
- else:
- self.__charge = 0
+ self.__chargeModifiedAttributes.original = charge.attributes
@property
def itemModifiedAttributes(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
@property
def chargeModifiedAttributes(self):
- if self.__charge is None:
- self.__fetchChargeInfo()
-
return self.__chargeModifiedAttributes
@property
- def item(self):
- if self.__item is None:
- self.__fetchItemInfo()
+ def isInvalid(self):
+ return self.__item is None or self.__item.category.name != "Drone"
+ @property
+ def item(self):
return self.__item
@property
def charge(self):
- if self.__charge is None:
- self.__fetchChargeInfo()
-
- return self.__charge if self.__charge != 0 else None
+ return self.__charge
@property
def dealsDamage(self):
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index f8328371f..5c03d6a2a 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -17,8 +17,7 @@
# along with eos. If not, see .
#===============================================================================
-from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \
-HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList
+from eos.effectHandlerHelpers import *
from eos.modifiedAttributeDict import ModifiedAttributeDict
from sqlalchemy.orm import validates, reconstructor
from itertools import chain
@@ -28,7 +27,11 @@ from math import sqrt, log, asinh
from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
from eos.saveddata.module import State
from eos.saveddata.mode import Mode
+import eos.db
import time
+import logging
+
+logger = logging.getLogger(__name__)
try:
from collections import OrderedDict
@@ -48,10 +51,14 @@ class Fit(object):
PEAK_RECHARGE = 0.25
- def __init__(self):
+ def __init__(self, ship=None, name=""):
+ """Initialize a fit from the program"""
+ # use @mode.setter's to set __attr and IDs. This will set mode as well
+ self.ship = ship
+
self.__modules = HandledModuleList()
- self.__drones = HandledDroneList()
- self.__cargo = HandledCargoList()
+ self.__drones = HandledDroneCargoList()
+ self.__cargo = HandledDroneCargoList()
self.__implants = HandledImplantBoosterList()
self.__boosters = HandledImplantBoosterList()
self.__projectedFits = HandledProjectedFitList()
@@ -59,23 +66,40 @@ class Fit(object):
self.__projectedDrones = HandledProjectedDroneList()
self.__character = None
self.__owner = None
- self.shipID = None
+
self.projected = False
- self.name = ""
- self.fleet = None
- self.boostsFits = set()
- self.gangBoosts = None
+ self.name = name
self.timestamp = time.time()
- self.ecmProjectedStr = 1
self.modeID = None
+
self.build()
@reconstructor
def init(self):
+ """Initialize a fit from the database and validate"""
+ self.__ship = None
+ self.__mode = None
+
+ if self.shipID:
+ item = eos.db.getItem(self.shipID)
+ if item is None:
+ logger.error("Item (id: %d) does not exist", self.shipID)
+ return
+
+ try:
+ self.__ship = Ship(item)
+ except ValueError:
+ logger.error("Item (id: %d) is not a Ship", self.shipID)
+ return
+
+ if self.modeID and self.__ship:
+ item = eos.db.getItem(self.modeID)
+ # Don't need to verify if it's a proper item, as validateModeItem assures this
+ self.__mode = self.ship.validateModeItem(item)
+
self.build()
def build(self):
- from eos import db
self.__extraDrains = []
self.__ehp = None
self.__weaponDPS = None
@@ -100,11 +124,6 @@ class Fit(object):
self.ecmProjectedStr = 1
self.extraAttributes = ModifiedAttributeDict(self)
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
- self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
- if self.ship is not None:
- self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
- else:
- self.mode = None
@property
def targetResists(self):
@@ -128,13 +147,17 @@ class Fit(object):
self.__ehp = None
self.__effectiveTank = None
+ @property
+ def isInvalid(self):
+ return self.__ship is None
+
@property
def mode(self):
- return self._mode
+ return self.__mode
@mode.setter
def mode(self, mode):
- self._mode = mode
+ self.__mode = mode
self.modeID = mode.item.ID if mode is not None else None
@property
@@ -154,7 +177,7 @@ class Fit(object):
self.__ship = ship
self.shipID = ship.item.ID if ship is not None else None
# set mode of new ship
- self.mode = self.ship.checkModeItem(None) if ship is not None else None
+ self.mode = self.ship.validateModeItem(None) if ship is not None else None
@property
def drones(self):
diff --git a/eos/saveddata/implant.py b/eos/saveddata/implant.py
index 036eabbc3..64670d769 100644
--- a/eos/saveddata/implant.py
+++ b/eos/saveddata/implant.py
@@ -20,46 +20,58 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem
from sqlalchemy.orm import validates, reconstructor
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
class Implant(HandledItem, ItemAttrShortcut):
def __init__(self, item):
- self.__slot = self.__calculateSlot(item)
self.__item = item
- self.itemID = item.ID
+
+ if self.isInvalid:
+ raise ValueError("Passed item is not an Implant")
+
+ self.itemID = item.ID if item is not None else None
self.active = True
- self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__itemModifiedAttributes.original = self.item.attributes
+ self.build()
@reconstructor
def init(self):
self.__item = None
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.itemID)
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
+ if self.isInvalid:
+ logger.error("Item (id: %d) is not an Implant", self.itemID)
+ return
+
+ self.build()
+
+ def build(self):
+ """ Build object. Assumes proper and valid item already set """
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
self.__slot = self.__calculateSlot(self.__item)
@property
def itemModifiedAttributes(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
@property
- def slot(self):
- if self.__item is None:
- self.__fetchItemInfo()
+ def isInvalid(self):
+ return self.__item is None or self.__item.category.name != "Implant"
+ @property
+ def slot(self):
return self.__slot
@property
def item(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__item
def __calculateSlot(self, item):
diff --git a/eos/saveddata/mode.py b/eos/saveddata/mode.py
index 83d0c0bc2..3a344d74d 100644
--- a/eos/saveddata/mode.py
+++ b/eos/saveddata/mode.py
@@ -19,36 +19,24 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem
+import eos.db
class Mode(ItemAttrShortcut, HandledItem):
-
def __init__(self, item):
+
+ if item.group.name != "Ship Modifiers":
+ raise ValueError('Passed item "%s" (category: (%s)) is not a Ship Modifier'%(item.name, item.category.name))
+
self.__item = item
self.__itemModifiedAttributes = ModifiedAttributeDict()
-
- if not isinstance(item, int):
- self.__buildOriginal()
-
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.__item)
- self.__buildOriginal()
-
- def __buildOriginal(self):
self.__itemModifiedAttributes.original = self.item.attributes
@property
def item(self):
- if isinstance(self.__item, int):
- self.__fetchItemInfo()
-
return self.__item
@property
def itemModifiedAttributes(self):
- if isinstance(self.__item, int):
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
# @todo: rework to fit only on t3 dessy
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index 4688e53c4..a4fdbec07 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -23,6 +23,10 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, C
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum
from eos.mathUtils import floorFloat
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
class State(Enum):
OFFLINE = -1
@@ -49,95 +53,78 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
MINING_ATTRIBUTES = ("miningAmount", )
def __init__(self, item):
- self.__item = item if item != None else 0
+ """Initialize a module from the program"""
+ self.__item = item
+
+ if item is not None and self.isInvalid:
+ raise ValueError("Passed item is not a Module")
+
+ self.__charge = None
self.itemID = item.ID if item is not None else None
- self.__charge = 0
self.projected = False
self.state = State.ONLINE
+ self.build()
+
+ @reconstructor
+ def init(self):
+ """Initialize a module from the database and validate"""
+ self.__item = None
+ self.__charge = None
+
+ # we need this early if module is invalid and returns early
+ self.__slot = self.dummySlot
+
+ if self.itemID:
+ self.__item = eos.db.getItem(self.itemID)
+ if self.__item is None:
+ logger.error("Item (id: %d) does not exist", self.itemID)
+ return
+
+ if self.isInvalid:
+ logger.error("Item (id: %d) is not a Module", self.itemID)
+ return
+
+ if self.chargeID:
+ self.__charge = eos.db.getItem(self.chargeID)
+
+ self.build()
+
+ def build(self):
+ """ Builds internal module variables from both init's """
+
+ if self.__charge and self.__charge.category.name != "Charge":
+ self.__charge = None
+
self.__dps = None
self.__miningyield = None
self.__volley = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
+ self.__hardpoint = Hardpoint.NONE
self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__slot = None
-
- if item != None:
- self.__itemModifiedAttributes.original = item.attributes
- self.__hardpoint = self.__calculateHardpoint(item)
- self.__slot = self.__calculateSlot(item)
-
self.__chargeModifiedAttributes = ModifiedAttributeDict()
+ self.__slot = self.dummySlot # defaults to None
- @reconstructor
- def init(self):
- if self.dummySlot is None:
- self.__item = None
- self.__charge = None
- self.__volley = None
- self.__dps = None
- self.__miningyield = None
- self.__reloadTime = None
- self.__reloadForce = None
- self.__chargeCycles = None
- else:
- self.__slot = self.dummySlot
- self.__item = 0
- self.__charge = 0
- self.__dps = 0
- self.__miningyield = 0
- self.__volley = 0
- self.__reloadTime = 0
- self.__reloadForce = None
- self.__chargeCycles = 0
- self.__hardpoint = Hardpoint.NONE
- self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__chargeModifiedAttributes = ModifiedAttributeDict()
-
- def __fetchItemInfo(self):
- import eos.db
- item = eos.db.getItem(self.itemID)
- self.__item = item
- self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__itemModifiedAttributes.original = item.attributes
- self.__hardpoint = self.__calculateHardpoint(item)
- self.__slot = self.__calculateSlot(item)
-
- def __fetchChargeInfo(self):
- self.__chargeModifiedAttributes = ModifiedAttributeDict()
- if self.chargeID is not None:
- import eos.db
- charge = eos.db.getItem(self.chargeID)
- self.__charge = charge
- self.__chargeModifiedAttributes.original = charge.attributes
- else:
- self.__charge = 0
+ if self.__item:
+ self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__hardpoint = self.__calculateHardpoint(self.__item)
+ self.__slot = self.__calculateSlot(self.__item)
+ if self.__charge:
+ self.__chargeModifiedAttributes.original = self.__charge.attributes
@classmethod
def buildEmpty(cls, slot):
empty = Module(None)
empty.__slot = slot
- empty.__hardpoint = Hardpoint.NONE
- empty.__item = 0
- empty.__charge = 0
empty.dummySlot = slot
- empty.__itemModifiedAttributes = ModifiedAttributeDict()
- empty.__chargeModifiedAttributes = ModifiedAttributeDict()
-
return empty
@classmethod
def buildRack(cls, slot):
empty = Rack(None)
empty.__slot = slot
- empty.__hardpoint = Hardpoint.NONE
- empty.__item = 0
- empty.__charge = 0
empty.dummySlot = slot
- empty.__itemModifiedAttributes = ModifiedAttributeDict()
- empty.__chargeModifiedAttributes = ModifiedAttributeDict()
-
return empty
@property
@@ -146,11 +133,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def hardpoint(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__hardpoint
+ @property
+ def isInvalid(self):
+ if self.isEmpty:
+ return False
+ return self.__item is None or (self.__item.category.name not in ("Module", "Subsystem") and self.__item.group.name != "Effect Beacon")
+
@property
def numCharges(self):
if self.charge is None:
@@ -263,38 +253,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def slot(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__slot
@property
def itemModifiedAttributes(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
@property
def chargeModifiedAttributes(self):
- if self.__charge is None:
- self.__fetchChargeInfo()
-
return self.__chargeModifiedAttributes
@property
def item(self):
- if self.__item is None:
- self.__fetchItemInfo()
-
return self.__item if self.__item != 0 else None
@property
def charge(self):
- if self.__charge is None:
- self.__fetchChargeInfo()
-
return self.__charge if self.__charge != 0 else None
@charge.setter
@@ -516,7 +491,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getValidCharges(self):
validCharges = set()
- import eos.db
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
if itemChargeGroup is not None:
diff --git a/eos/saveddata/ship.py b/eos/saveddata/ship.py
index 03eeef3d4..86123bfc1 100644
--- a/eos/saveddata/ship.py
+++ b/eos/saveddata/ship.py
@@ -20,6 +20,10 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem
from eos.saveddata.mode import Mode
+import eos.db
+import logging
+
+logger = logging.getLogger(__name__)
class Ship(ItemAttrShortcut, HandledItem):
def __init__(self, item):
@@ -28,33 +32,18 @@ class Ship(ItemAttrShortcut, HandledItem):
raise ValueError('Passed item "%s" (category: (%s)) is not under Ship category'%(item.name, item.category.name))
self.__item = item
+ self.__modeItems = self.__getModeItems()
self.__itemModifiedAttributes = ModifiedAttributeDict()
- self.__modeItems = self._getModeItems()
- if not isinstance(item, int):
- self.__buildOriginal()
+ self.__itemModifiedAttributes.original = self.item.attributes
self.commandBonus = 0
- def __fetchItemInfo(self):
- import eos.db
- self.__item = eos.db.getItem(self.__item)
- self.__buildOriginal()
-
- def __buildOriginal(self):
- self.__itemModifiedAttributes.original = self.item.attributes
-
@property
def item(self):
- if isinstance(self.__item, int):
- self.__fetchItemInfo()
-
return self.__item
@property
def itemModifiedAttributes(self):
- if isinstance(self.__item, int):
- self.__fetchItemInfo()
-
return self.__itemModifiedAttributes
def clear(self):
@@ -67,25 +56,16 @@ class Ship(ItemAttrShortcut, HandledItem):
if effect.runTime == runTime and effect.isType("passive"):
effect.handler(fit, self, ("ship",))
- def checkModeItem(self, item):
- """
- Checks if provided item is a valid mode.
-
- If ship has modes, and current item is not valid, return forced mode
- else if mode is valid, return Mode
- else if ship does not have modes, return None
-
- @todo: rename this
- """
+ def validateModeItem(self, item):
+ """ Checks if provided item is a valid mode """
items = self.__modeItems
- if items != None:
- if item == None or item not in items:
- # We have a tact dessy, but mode is None or not valid. Force new mode
+ if items is not None:
+ # if we have items, then we are in a tactical destroyer and must have a mode
+ if item is None or item not in items:
+ # If provided item is invalid mode, force new one
return Mode(items[0])
- elif item in items:
- # We have a valid mode
- return Mode(item)
+ return Mode(item)
return None
@property
@@ -96,21 +76,17 @@ class Ship(ItemAttrShortcut, HandledItem):
def modes(self):
return [Mode(item) for item in self.__modeItems] if self.__modeItems else None
- def _getModeItems(self):
+ def __getModeItems(self):
"""
Returns a list of valid mode items for ship. Note that this returns the
valid Item objects, not the Mode objects. Returns None if not a
t3 dessy
"""
- # @todo: is there a better way to determine this that isn't hardcoded groupIDs?
- if self.item.groupID != 1305:
+ if self.item.group.name != "Tactical Destroyer":
return None
- modeGroupID = 1306
- import eos.db
-
items = []
- g = eos.db.getGroup(modeGroupID, eager=("items.icon", "items.attributes"))
+ g = eos.db.getGroup("Ship Modifiers", eager=("items.icon", "items.attributes"))
for item in g.items:
# Rely on name detection because race is not reliable
if item.name.lower().startswith(self.item.name.lower()):
diff --git a/eos/slotFill.py b/eos/slotFill.py
deleted file mode 100644
index ce00a07ae..000000000
--- a/eos/slotFill.py
+++ /dev/null
@@ -1,223 +0,0 @@
-#===============================================================================
-# Copyright (C) 2010 Diego Duclos
-#
-# This file is part of eos.
-#
-# eos is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# eos is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with eos. If not, see .
-#===============================================================================
-
-from eos.types import Slot, Fit, Module, State
-import random
-import copy
-import math
-import bisect
-import itertools
-import time
-
-class SlotFill(object):
- def __init__(self, original, modules, attributeWeights=None, propertyWeights=None, specificWeights=None, defaultState = State.ACTIVE):
- self.original = original
- self.attributeWeights = attributeWeights or {}
- self.propertyWeights = propertyWeights or {}
- self.specificWeights = specificWeights or []
- self.state = State.ACTIVE
- self.modules = map(self.__newModule, modules)
-
- def __newModule(self, item):
- m = Module(item)
- m.state = self.state
- return m
-
- def __getMetaParent(self, item):
- metaGroup = item.metaGroup
- return item if metaGroup is None else metaGroup.parent
-
- def fitness(self, fit, chromosome):
- modList = fit.modules
- modAttr = fit.ship.getModifiedItemAttr
-
- modList.extend(chromosome)
- fit.clear()
- fit.calculateModifiedAttributes()
-
- if not fit.fits:
- del modList[-len(chromosome):]
- return 0
-
- weight = 0
- for attr, value in self.attributeWeights.iteritems():
- weight += modAttr(attr) * (value if value >= 0 else 1.0 / -value)
-
- for prop, value in self.propertyWeights.iteritems():
- weight += getattr(fit, prop) * (value if value >= 0 else 1.0 / -value)
-
- for specific in self.specificWeights:
- weight += specific(fit)
-
- totalVars = (fit.ship.getModifiedItemAttr("powerOutput"),
- fit.ship.getModifiedItemAttr("cpuOutput"),
- fit.ship.getModifiedItemAttr('upgradeCapacity'))
-
- usedVars = (fit.pgUsed, fit.cpuUsed, fit.calibrationUsed)
-
- total = 0
- used = 0
- for tv, uv in zip(totalVars, usedVars):
- if uv > tv:
- del modList[-len(chromosome):]
- return 0
-
- del modList[-len(chromosome):]
-
-
- return weight
-
-
- def run(self, elite = 0.05, crossoverChance = 0.8, slotMutationChance = 0.5, typeMutationChance = 0.5):
- #Use a copy of the original for all our calcs. We don't want to damage it
- fit = copy.deepcopy(self.original)
- fit.unfill()
-
- #First of all, lets check the number of slots we got to play with
- chromLength = -1
- slotAmounts = {}
- for type in Slot.getTypes():
- slot = Slot.getValue(type)
- amount = fit.getSlotsFree(slot)
- if amount > 0:
- slotAmounts[slot] = amount
-
- chromLength += amount
-
- if not slotAmounts:
- #Nothing to do, joy
- return
-
- slotModules = {}
- metaModules = {}
-
- for slotType in slotAmounts:
- slotModules[slotType] = modules = []
-
- for module in self.modules:
- #Store the variations of each base for ease and speed
- metaParent = self.__getMetaParent(module.item)
- metaList = metaModules.get(metaParent)
- if metaList is None:
- metaList = metaModules[metaParent] = []
- metaList.append(module)
-
- #Sort stuff by slotType for ease and speed
- slot = module.slot
- if slot in slotModules:
- slotModules[slot].append(module)
-
- for slotType, modules in slotModules.iteritems():
- if len(modules) == 0:
- chromLength -= slotAmounts[slotType]
- del slotAmounts[slotType]
-
- #Now, we need an initial set, first thing to do is decide how big that set will be
- setSize = 10
-
- #Grab some variables locally for performance improvements
- rchoice = random.choice
- rrandom = random.random
- rrandint = random.randint
- bbisect = bisect.bisect
- ccopy = copy.copy
-
- #Get our list for storage of our chromosomes
- chromosomes = []
-
- # Helpers
- weigher = lambda chromosome: (self.fitness(fit, chromosome), chromosome)
- keyer = lambda info: info[0]
-
- eliteCutout = int(math.floor(setSize * (1 - elite)))
- lastEl = setSize - 1
-
- #Generate our initial set entirely randomly
- #Subtelies to take in mind:
- # * modules of the same slotType are kept together for easy cross-overing
- state = self.state
- for _ in xrange(setSize):
- chrom = []
- for type, amount in slotAmounts.iteritems():
- for _ in xrange(amount):
- chrom.append(rchoice(slotModules[type]))
-
- chromosomes.append(weigher(chrom))
-
- #Sort our initial set
- chromosomes.sort(key=keyer)
- currentGeneration = chromosomes
-
- #Yield the best result from our initial set, this is gonna be pretty bad
- yield currentGeneration[lastEl]
-
- #Setup's done, now we can actualy apply our genetic algorithm to optimize all this
- while True:
- moo = time.time()
- #First thing we do, we're gonna be elitair
- #Grab the top x%, we'll put em in the next generation
- nextGeneration = []
- for i in xrange(lastEl, eliteCutout - 1, -1):
- nextGeneration.append(currentGeneration[i])
-
- #Figure out our ratios to do our roulette wheel
- fitnessList = map(keyer, currentGeneration)
- totalFitness = float(sum(fitnessList))
-
- curr = 0
- ratios = []
- for fitness in fitnessList:
- curr += fitness
- ratios.append(curr / (totalFitness or 1))
-
- t = 0
- #Do our pairing
- for _ in xrange(0, eliteCutout):
- # Crossover chance
- mother = currentGeneration[bbisect(ratios, rrandom())][1]
- father = currentGeneration[bbisect(ratios, rrandom())][1]
- if rrandom() <= crossoverChance:
- crosspoint = rrandint(0, chromLength)
- luke = mother[:crosspoint] + father[crosspoint:]
- else:
- luke = father
-
- #Chance for slot mutation
- if rrandom() <= slotMutationChance:
- target = rrandint(0, chromLength)
- mod = luke[target]
- luke[target] = rchoice(slotModules[mod.slot])
-
- if rrandom() <= typeMutationChance:
- #Mutation of an item to another one of the same type
- target = rrandint(0, chromLength)
- mod = luke[target]
- vars = metaModules[self.__getMetaParent(mod.item)]
- luke[target] = rchoice(vars)
-
- tt = time.time()
- nextGeneration.append(weigher(luke))
- t += time.time() - tt
-
- print "time spent weighing: ", t
-
- nextGeneration.sort(key=keyer)
- currentGeneration = nextGeneration
- print "total time spent this iteration:", time.time() - moo
- yield currentGeneration[lastEl]
diff --git a/gui/droneView.py b/gui/droneView.py
index ddf16bf72..f71c7cb25 100644
--- a/gui/droneView.py
+++ b/gui/droneView.py
@@ -36,7 +36,8 @@ class DroneViewDrop(wx.PyDropTarget):
def OnData(self, x, y, t):
if self.GetData():
- self.dropFn(x, y, int(self.dropData.GetText()))
+ data = self.dropData.GetText().split(':')
+ self.dropFn(x, y, data)
return t
class DroneView(d.Display):
@@ -72,7 +73,7 @@ class DroneView(d.Display):
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
- self.SetDropTarget(DroneViewDrop(self.mergeDrones))
+ self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
def OnLeaveWindow(self, event):
self.SetToolTip(None)
@@ -117,17 +118,27 @@ class DroneView(d.Display):
row = event.GetIndex()
if row != -1:
data = wx.PyTextDataObject()
- data.SetText(str(self.GetItemData(row)))
+ data.SetText("drone:"+str(row))
dropSource = wx.DropSource(self)
dropSource.SetData(data)
res = dropSource.DoDragDrop()
- def mergeDrones(self, x, y, itemID):
- srcRow = self.FindItemData(-1,itemID)
- dstRow, _ = self.HitTest((x, y))
- if srcRow != -1 and dstRow != -1:
- self._merge(srcRow, dstRow)
+ def handleDragDrop(self, x, y, data):
+ '''
+ Handles dragging of items from various pyfa displays which support it
+
+ data is list with two indices:
+ data[0] is hard-coded str of originating source
+ data[1] is typeID or index of data we want to manipulate
+ '''
+ if data[0] == "drone": # we want to merge drones
+ srcRow = int(data[1])
+ dstRow, _ = self.HitTest((x, y))
+ if srcRow != -1 and dstRow != -1:
+ self._merge(srcRow, dstRow)
+ elif data[0] == "market":
+ wx.PostEvent(self.mainFrame, mb.ItemSelected(itemID=int(data[1])))
def _merge(self, src, dst):
sFit = service.Fit.getInstance()
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 82c33791b..362e0b134 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -146,6 +146,7 @@ class MainFrame(wx.Frame):
self.marketBrowser = MarketBrowser(self.notebookBrowsers)
self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False)
+ self.marketBrowser.splitter.SetSashPosition(self.marketHeight)
self.shipBrowser = ShipBrowser(self.notebookBrowsers)
self.notebookBrowsers.AddPage(self.shipBrowser, "Ships", tabImage = shipBrowserImg, showClose = False)
@@ -159,8 +160,8 @@ class MainFrame(wx.Frame):
self.notebookBrowsers.SetSelection(1)
self.splitter.SplitVertically(self.notebookBrowsers, self.FitviewAdditionsPanel)
- self.splitter.SetMinimumPaneSize(204)
- self.splitter.SetSashPosition(300)
+ self.splitter.SetMinimumPaneSize(220)
+ self.splitter.SetSashPosition(self.browserWidth)
cstatsSizer = wx.BoxSizer(wx.VERTICAL)
@@ -227,7 +228,7 @@ class MainFrame(wx.Frame):
def LoadMainFrameAttribs(self):
- mainFrameDefaultAttribs = {"wnd_width": 1000, "wnd_height": 680, "wnd_maximized": False}
+ mainFrameDefaultAttribs = {"wnd_width": 1000, "wnd_height": 680, "wnd_maximized": False, "browser_width": 300, "market_height": 0}
self.mainFrameAttribs = service.SettingsProvider.getInstance().getSettings("pyfaMainWindowAttribs", mainFrameDefaultAttribs)
if self.mainFrameAttribs["wnd_maximized"]:
@@ -241,6 +242,9 @@ class MainFrame(wx.Frame):
self.SetSize((width, height))
self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"]))
+ self.browserWidth = self.mainFrameAttribs["browser_width"]
+ self.marketHeight = self.mainFrameAttribs["market_height"]
+
def UpdateMainFrameAttribs(self):
if self.IsIconized():
return
@@ -250,6 +254,9 @@ class MainFrame(wx.Frame):
self.mainFrameAttribs["wnd_height"] = height
self.mainFrameAttribs["wnd_maximized"] = self.IsMaximized()
+ self.mainFrameAttribs["browser_width"] = self.notebookBrowsers.GetSize()[0]
+ self.mainFrameAttribs["market_height"] = self.marketBrowser.marketView.GetSize()[1]
+
def SetActiveStatsWindow(self, wnd):
self.activeStatsWnd = wnd
diff --git a/gui/marketBrowser.py b/gui/marketBrowser.py
index 1c8329a1b..7f4404c59 100644
--- a/gui/marketBrowser.py
+++ b/gui/marketBrowser.py
@@ -51,7 +51,7 @@ class MarketBrowser(wx.Panel):
self.itemView = ItemView(self.splitter, self)
self.splitter.SplitHorizontally(self.marketView, self.itemView)
- self.splitter.SetMinimumPaneSize(250)
+ self.splitter.SetMinimumPaneSize(150)
# Setup our buttons for metaGroup selection
# Same fix as for search box on macs,
diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py
index 0d26be5da..8dcfe4b0f 100644
--- a/gui/resistsEditor.py
+++ b/gui/resistsEditor.py
@@ -342,7 +342,6 @@ class ResistsEditorDlg(wx.Dialog):
self.patternChanged()
def showInput(self, bool):
- print self.namePicker.IsShown(), bool
if bool and not self.namePicker.IsShown():
self.ccResists.Hide()
self.namePicker.Show()
diff --git a/service/fit.py b/service/fit.py
index 8a55fb5b4..f20d9183a 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -150,8 +150,8 @@ class Fit(object):
return fit.modules[pos]
def newFit(self, shipID, name=None):
- fit = eos.types.Fit()
- fit.ship = eos.types.Ship(eos.db.getItem(shipID))
+ ship = eos.types.Ship(eos.db.getItem(shipID))
+ fit = eos.types.Fit(ship)
fit.name = name if name is not None else "New %s" % fit.ship.item.name
fit.damagePattern = self.pattern
fit.targetResists = self.targetResists
@@ -275,7 +275,6 @@ class Fit(object):
except ValueError:
return False
- fit.implants.freeSlot(implant)
fit.implants.append(implant)
self.recalc(fit)
return True
@@ -301,7 +300,6 @@ class Fit(object):
except ValueError:
return False
- fit.boosters.freeSlot(booster)
fit.boosters.append(booster)
self.recalc(fit)
return True