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