diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index cb3033cc1..8c0d1c2b6 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -1,4 +1,4 @@ -#=============================================================================== +# =============================================================================== # Copyright (C) 2010 Diego Duclos # # This file is part of eos. @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with eos. If not, see . -#=============================================================================== +# =============================================================================== from eos.db.util import processEager, processWhere from eos.db import saveddata_session, sd_lock @@ -28,13 +28,18 @@ import eos.config configVal = getattr(eos.config, "saveddataCache", None) if configVal is True: import weakref + itemCache = {} queryCache = {} + + def cachedQuery(type, amount, *keywords): itemCache[type] = localItemCache = weakref.WeakValueDictionary() queryCache[type] = typeQueryCache = {} + def deco(function): localQueryCache = typeQueryCache[function] = {} + def setCache(cacheKey, args, kwargs): items = function(*args, **kwargs) IDs = set() @@ -43,7 +48,7 @@ if configVal is True: for item in stuff: ID = getattr(item, "ID", None) if ID is None: - #Some uncachable data, don't cache this query + # Some uncachable data, don't cache this query del localQueryCache[cacheKey] break localItemCache[ID] = item @@ -69,7 +74,7 @@ if configVal is True: for ID in IDs: data = localItemCache.get(ID) if data is None: - #Fuck, some of our stuff isn't cached it seems. + # Fuck, some of our stuff isn't cached it seems. items = setCache(cacheKey, args, kwargs) break items.append(data) @@ -81,11 +86,14 @@ if configVal is True: break return items + return checkAndReturn + return deco + def removeCachedEntry(type, ID): - if not type in queryCache: + if type not in queryCache: return functionCache = queryCache[type] for _, localCache in functionCache.iteritems(): @@ -110,11 +118,14 @@ else: return function(*args, **kwargs) return checkAndReturn + return deco + def removeCachedEntry(*args, **kwargs): return + def sqlizeString(line): # Escape backslashes first, as they will be as escape symbol in queries # Then escape percent and underscore signs @@ -122,6 +133,7 @@ def sqlizeString(line): line = line.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_").replace("*", "%") return line + @cachedQuery(User, 1, "lookfor") def getUser(lookfor, eager=None): if isinstance(lookfor, int): @@ -140,6 +152,7 @@ def getUser(lookfor, eager=None): raise TypeError("Need integer or string as argument") return user + @cachedQuery(Character, 1, "lookfor") def getCharacter(lookfor, eager=None): if isinstance(lookfor, int): @@ -153,17 +166,20 @@ def getCharacter(lookfor, eager=None): elif isinstance(lookfor, basestring): eager = processEager(eager) with sd_lock: - character = saveddata_session.query(Character).options(*eager).filter(Character.savedName == lookfor).first() + character = saveddata_session.query(Character).options(*eager).filter( + Character.savedName == lookfor).first() else: raise TypeError("Need integer or string as argument") return character + def getCharacterList(eager=None): eager = processEager(eager) with sd_lock: characters = saveddata_session.query(Character).options(*eager).all() return characters + def getCharactersForUser(lookfor, eager=None): if isinstance(lookfor, int): eager = processEager(eager) @@ -173,6 +189,7 @@ def getCharactersForUser(lookfor, eager=None): raise TypeError("Need integer as argument") return characters + @cachedQuery(Fit, 1, "lookfor") def getFit(lookfor, eager=None): if isinstance(lookfor, int): @@ -214,6 +231,7 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None): return fits + def getBoosterFits(ownerID=None, where=None, eager=None): """ Get all the fits that are flagged as a boosting ship @@ -233,31 +251,41 @@ def getBoosterFits(ownerID=None, where=None, eager=None): return fits + def countAllFits(): with sd_lock: count = saveddata_session.query(Fit).count() return count -def countFitsWithShip(shipID, ownerID=None, where=None, eager=None): + +def countFitsWithShip(lookfor, ownerID=None, where=None, eager=None): """ Get all the fits using a certain ship. If no user is passed, do this for all users. """ - if isinstance(shipID, int): - if ownerID is not None and not isinstance(ownerID, int): - raise TypeError("OwnerID must be integer") - filter = Fit.shipID == shipID - if ownerID is not None: - filter = and_(filter, Fit.ownerID == ownerID) + if ownerID is not None and not isinstance(ownerID, int): + raise TypeError("OwnerID must be integer") - filter = processWhere(filter, where) - eager = processEager(eager) - with sd_lock: - count = saveddata_session.query(Fit).options(*eager).filter(filter).count() + if isinstance(lookfor, int): + filter = Fit.shipID == lookfor + elif isinstance(lookfor, list): + if len(lookfor) == 0: + return 0 + filter = Fit.shipID.in_(lookfor) else: - raise TypeError("ShipID must be integer") + raise TypeError("You must supply either an integer or ShipID must be integer") + + if ownerID is not None: + filter = and_(filter, Fit.ownerID == ownerID) + + filter = processWhere(filter, where) + eager = processEager(eager) + with sd_lock: + count = saveddata_session.query(Fit).options(*eager).filter(filter).count() + return count + def getFitList(eager=None): eager = processEager(eager) with sd_lock: @@ -274,12 +302,14 @@ def getPrice(typeID): raise TypeError("Need integer as argument") return price + def clearPrices(): with sd_lock: deleted_rows = saveddata_session.query(Price).delete() commit() return deleted_rows + def getMiscData(field): if isinstance(field, basestring): with sd_lock: @@ -288,24 +318,28 @@ def getMiscData(field): raise TypeError("Need string as argument") return data + def getDamagePatternList(eager=None): eager = processEager(eager) with sd_lock: patterns = saveddata_session.query(DamagePattern).options(*eager).all() return patterns + def getTargetResistsList(eager=None): eager = processEager(eager) with sd_lock: patterns = saveddata_session.query(TargetResists).options(*eager).all() return patterns + def getImplantSetList(eager=None): eager = processEager(eager) with sd_lock: sets = saveddata_session.query(ImplantSet).options(*eager).all() return sets + @cachedQuery(DamagePattern, 1, "lookfor") def getDamagePattern(lookfor, eager=None): if isinstance(lookfor, int): @@ -315,15 +349,18 @@ def getDamagePattern(lookfor, eager=None): else: eager = processEager(eager) with sd_lock: - pattern = saveddata_session.query(DamagePattern).options(*eager).filter(DamagePattern.ID == lookfor).first() + pattern = saveddata_session.query(DamagePattern).options(*eager).filter( + DamagePattern.ID == lookfor).first() elif isinstance(lookfor, basestring): eager = processEager(eager) with sd_lock: - pattern = saveddata_session.query(DamagePattern).options(*eager).filter(DamagePattern.name == lookfor).first() + pattern = saveddata_session.query(DamagePattern).options(*eager).filter( + DamagePattern.name == lookfor).first() else: raise TypeError("Need integer or string as argument") return pattern + @cachedQuery(TargetResists, 1, "lookfor") def getTargetResists(lookfor, eager=None): if isinstance(lookfor, int): @@ -333,15 +370,18 @@ def getTargetResists(lookfor, eager=None): else: eager = processEager(eager) with sd_lock: - pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.ID == lookfor).first() + pattern = saveddata_session.query(TargetResists).options(*eager).filter( + TargetResists.ID == lookfor).first() elif isinstance(lookfor, basestring): eager = processEager(eager) with sd_lock: - pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.name == lookfor).first() + pattern = saveddata_session.query(TargetResists).options(*eager).filter( + TargetResists.name == lookfor).first() else: raise TypeError("Need integer or string as argument") return pattern + @cachedQuery(ImplantSet, 1, "lookfor") def getImplantSet(lookfor, eager=None): if isinstance(lookfor, int): @@ -351,7 +391,8 @@ def getImplantSet(lookfor, eager=None): else: eager = processEager(eager) with sd_lock: - pattern = saveddata_session.query(ImplantSet).options(*eager).filter(TargetResists.ID == lookfor).first() + pattern = saveddata_session.query(ImplantSet).options(*eager).filter( + TargetResists.ID == lookfor).first() elif isinstance(lookfor, basestring): eager = processEager(eager) with sd_lock: @@ -360,13 +401,14 @@ def getImplantSet(lookfor, eager=None): raise TypeError("Improper argument") return pattern + def searchFits(nameLike, where=None, eager=None): if not isinstance(nameLike, basestring): raise TypeError("Need string as argument") # Prepare our string for request nameLike = u"%{0}%".format(sqlizeString(nameLike)) - #Add any extra components to the search to our where clause + # Add any extra components to the search to our where clause filter = processWhere(Fit.name.like(nameLike, escape="\\"), where) eager = processEager(eager) with sd_lock: @@ -383,12 +425,14 @@ def getProjectedFits(fitID): else: raise TypeError("Need integer as argument") + def getCrestCharacters(eager=None): eager = processEager(eager) with sd_lock: characters = saveddata_session.query(CrestChar).options(*eager).all() return characters + @cachedQuery(CrestChar, 1, "lookfor") def getCrestCharacter(lookfor, eager=None): if isinstance(lookfor, int): @@ -407,21 +451,25 @@ def getCrestCharacter(lookfor, eager=None): raise TypeError("Need integer or string as argument") return character + def getOverrides(itemID, eager=None): if isinstance(itemID, int): return saveddata_session.query(Override).filter(Override.itemID == itemID).all() else: raise TypeError("Need integer as argument") + def clearOverrides(): with sd_lock: deleted_rows = saveddata_session.query(Override).delete() commit() return deleted_rows + def getAllOverrides(eager=None): return saveddata_session.query(Override).all() + def removeInvalid(fits): invalids = [f for f in fits if f.isInvalid] @@ -432,14 +480,17 @@ def removeInvalid(fits): return fits + def add(stuff): with sd_lock: saveddata_session.add(stuff) + def save(stuff): add(stuff) commit() + def remove(stuff): removeCachedEntry(type(stuff), stuff.ID) with sd_lock: diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index 2e42ef69f..300e0b4cc 100644 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -172,10 +172,11 @@ class HandledModuleList(HandledList): self[index] = mod def freeSlot(self, slot): - for i in range(len(self) - 1, -1, -1): + for i in range(len(self)): mod = self[i] if mod.getModifiedItemAttr("subSystemSlot") == slot: - del self[i] + self.toDummy(i) + break class HandledDroneCargoList(HandledList): diff --git a/eos/effects/elitebonuslogisticremotearmorrepairoptimalfalloff1.py b/eos/effects/elitebonuslogisticremotearmorrepairoptimalfalloff1.py index d9180ebd7..f95cfd6f6 100644 --- a/eos/effects/elitebonuslogisticremotearmorrepairoptimalfalloff1.py +++ b/eos/effects/elitebonuslogisticremotearmorrepairoptimalfalloff1.py @@ -4,5 +4,5 @@ # Ship: Rabisu type = "passive" def handler(fit, src, context): - fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "falloffEffectiveness", src.getModifiedItemAttr("eliteBonusLogistics1"), stackingPenalties=True, skill="Logistics Cruisers") - fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "maxRange", src.getModifiedItemAttr("eliteBonusLogistics1"), stackingPenalties=True, skill="Logistics Cruisers") + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "falloffEffectiveness", src.getModifiedItemAttr("eliteBonusLogistics1"), skill="Logistics Cruisers") + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "maxRange", src.getModifiedItemAttr("eliteBonusLogistics1"), skill="Logistics Cruisers") diff --git a/eos/effects/rolebonusremotearmorrepairoptimalfalloff.py b/eos/effects/rolebonusremotearmorrepairoptimalfalloff.py index c28744a06..86acf05b0 100644 --- a/eos/effects/rolebonusremotearmorrepairoptimalfalloff.py +++ b/eos/effects/rolebonusremotearmorrepairoptimalfalloff.py @@ -4,5 +4,5 @@ # Ship: Rabisu type = "passive" def handler(fit, src, context): - fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "falloffEffectiveness", src.getModifiedItemAttr("roleBonusRepairRange"), stackingPenalties=True) - fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "maxRange", src.getModifiedItemAttr("roleBonusRepairRange"), stackingPenalties=True) + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "falloffEffectiveness", src.getModifiedItemAttr("roleBonusRepairRange")) + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"), "maxRange", src.getModifiedItemAttr("roleBonusRepairRange")) diff --git a/eos/effects/skillremoteecmdurationbonus.py b/eos/effects/skillremoteecmdurationbonus.py index d2b13bdad..77c687f34 100644 --- a/eos/effects/skillremoteecmdurationbonus.py +++ b/eos/effects/skillremoteecmdurationbonus.py @@ -6,5 +6,28 @@ type = "passive" def handler(fit, skill, context): - fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Burst Projectors", - "duration", skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) + # We need to make sure that the attribute exists, otherwise we add attributes that don't belong. See #927 + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Burst Projector Operation") and + mod.item.getAttribute("duration"), + "duration", + skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) + + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Burst Projector Operation") and + mod.item.getAttribute("durationECMJammerBurstProjector"), + "durationECMJammerBurstProjector", + skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) + + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Burst Projector Operation") and + mod.item.getAttribute("durationTargetIlluminationBurstProjector"), + "durationTargetIlluminationBurstProjector", + skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) + + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Burst Projector Operation") and + mod.item.getAttribute("durationSensorDampeningBurstProjector"), + "durationSensorDampeningBurstProjector", + skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) + + fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Burst Projector Operation") and + mod.item.getAttribute("durationWeaponDisruptionBurstProjector"), + "durationWeaponDisruptionBurstProjector", + skill.getModifiedItemAttr("projECMDurationBonus") * skill.level) diff --git a/eos/effects/structurerigmaxtargets.py b/eos/effects/structurerigmaxtargets.py index d7b5ecfe5..49506515f 100644 --- a/eos/effects/structurerigmaxtargets.py +++ b/eos/effects/structurerigmaxtargets.py @@ -3,4 +3,5 @@ type = "passive" def handler(fit, src, context): - fit.ship.increaseItemAttr("maxLockedTargets", src.getModifiedItemAttr("structureRigMaxTargetBonus")) + fit.extraAttributes.increase("maxTargetsLockedFromSkills", src.getModifiedItemAttr("structureRigMaxTargetBonus")) + diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index b25831ff7..2e9f8bfe9 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -631,14 +631,6 @@ class Fit(object): groups = ("Energy Weapon", "Hybrid Weapon") self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, stackingPenalties=True) - else: - # Run effect, and get proper bonuses applied - try: - self.register(thing) - effect.handler(self, thing, context) - except: - pass - del self.commandBonuses[warfareBuffID] def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): @@ -754,7 +746,7 @@ class Fit(object): self.__calculated = True # Only apply projected fits if fit it not projected itself. - if not projected: + if not projected and not withBoosters: for fit in self.projectedFits: if fit.getProjectionInfo(self.ID).active: fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index c3f84ef15..6b597a904 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -201,6 +201,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): else: return self.__chargeCycles + @property + def modPosition(self): + if self.owner: + return self.owner.modules.index(self) + @property def hpBeforeReload(self): """ diff --git a/eve.db b/eve.db index 11b1d5084..035d2f36b 100644 Binary files a/eve.db and b/eve.db differ diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index 597687d2d..e97a176a7 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -224,7 +224,7 @@ class FittingView(d.Display): if row != -1 and row not in self.blanks: data = wx.PyTextDataObject() - data.SetText("fitting:"+str(self.mods[row].position)) + data.SetText("fitting:"+str(self.mods[row].modPosition)) dropSource = wx.DropSource(self) dropSource.SetData(data) @@ -356,7 +356,7 @@ class FittingView(d.Display): if dstRow != -1 and dstRow not in self.blanks: sFit = service.Fit.getInstance() fitID = self.mainFrame.getActiveFit() - moduleChanged = sFit.changeModule(fitID, self.mods[dstRow].position, srcIdx) + moduleChanged = sFit.changeModule(fitID, self.mods[dstRow].modPosition, srcIdx) if moduleChanged is None: # the new module doesn't fit in specified slot, try to simply append it wx.PostEvent(self.mainFrame, gui.marketBrowser.ItemSelected(itemID=srcIdx)) @@ -371,7 +371,7 @@ class FittingView(d.Display): module = self.mods[dstRow] sFit = service.Fit.getInstance() - sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, srcIdx, mstate.CmdDown() and module.isEmpty) + sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.modPosition, srcIdx, mstate.CmdDown() and module.isEmpty) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit())) @@ -389,6 +389,7 @@ class FittingView(d.Display): dstRow, _ = self.HitTest((x, y)) if dstRow != -1 and dstRow not in self.blanks: + mod1 = fit.modules[srcIdx] mod2 = self.mods[dstRow] @@ -397,9 +398,9 @@ class FittingView(d.Display): return if clone and mod2.isEmpty: - sFit.cloneModule(self.mainFrame.getActiveFit(), mod1.position, mod2.position) + sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition) else: - sFit.swapModules(self.mainFrame.getActiveFit(), mod1.position, mod2.position) + sFit.swapModules(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit())) diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index e1271b616..96bfb7ad5 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -34,6 +34,7 @@ Stage3Selected, EVT_SB_STAGE3_SEL = wx.lib.newevent.NewEvent() SearchSelected, EVT_SB_SEARCH_SEL = wx.lib.newevent.NewEvent() ImportSelected, EVT_SB_IMPORT_SEL = wx.lib.newevent.NewEvent() + class PFWidgetsContainer(PFListPane): def __init__(self,parent): PFListPane.__init__(self,parent) @@ -685,7 +686,8 @@ class ShipBrowser(wx.Panel): # set map & cache of fittings per category for cat in self.categoryList: - self.categoryFitCache[cat.ID] = sFit.groupHasFits(cat.ID) + itemIDs = [x.ID for x in cat.items] + self.categoryFitCache[cat.ID] = sFit.countFitsWithShip(itemIDs) > 1 for ship in self.categoryList: if self.filterShipsWithNoFits and not self.categoryFitCache[ship.ID]: diff --git a/service/fit.py b/service/fit.py index 42eb60046..7382df1fd 100644 --- a/service/fit.py +++ b/service/fit.py @@ -141,19 +141,10 @@ class Fit(object): def countAllFits(self): return eos.db.countAllFits() - def countFitsWithShip(self, shipID): - count = eos.db.countFitsWithShip(shipID) + def countFitsWithShip(self, stuff): + count = eos.db.countFitsWithShip(stuff) return count - def groupHasFits(self, groupID): - sMkt = Market.getInstance() - grp = sMkt.getGroup(groupID) - items = sMkt.getItemsByGroup(grp) - for item in items: - if self.countFitsWithShip(item.ID) > 0: - return True - return False - def getModule(self, fitID, pos): fit = eos.db.getFit(fitID) return fit.modules[pos] diff --git a/service/market.py b/service/market.py index 7d1cdea19..c851957e3 100644 --- a/service/market.py +++ b/service/market.py @@ -219,6 +219,8 @@ class Market(): "Victorieux Luxury Yacht": self.les_grp, # Worlds Collide prize \o/ chinese getting owned "Imp": self.les_grp, # AT13 prize "Fiend": self.les_grp, # AT13 prize + "Caedes": self.les_grp, # AT14 prize + "Rabisu": self.les_grp, # AT14 prize } self.ITEMS_FORCEGROUP_R = self.__makeRevDict(self.ITEMS_FORCEGROUP) @@ -543,6 +545,7 @@ class Market(): def getGroupsByCategory(self, cat): """Get groups from given category""" groups = set(filter(lambda grp: self.getPublicityByGroup(grp), cat.groups)) + return groups def getMarketGroupChildren(self, mg):