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/saveddata/fit.py b/eos/saveddata/fit.py index 12cf1a7bd..ceb73b258 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -27,6 +27,7 @@ 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 try: @@ -47,7 +48,12 @@ 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.__invalid = False self.__modules = HandledModuleList() self.__drones = HandledDroneCargoList() self.__cargo = HandledDroneCargoList() @@ -58,23 +64,37 @@ 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 drone from the database and validate""" + self.__ship = None + self.__mode = None + self.__invalid = False + + if self.shipID: + # if item does not exist, set invalid + item = eos.db.getItem(self.shipID) + if item is None or item.category.name != "Ship": + self.__invalid = True + else: + self.__ship = Ship(item) + + if self.modeID and self.__ship: + item = eos.db.getItem(self.modeID) + # Don't need to verify if it's a proper item, as checkModeItem assures this + self.__mode = self.ship.checkModeItem(item) + self.build() def build(self): - from eos import db self.__extraDrains = [] self.__ehp = None self.__weaponDPS = None @@ -99,11 +119,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): @@ -127,13 +142,17 @@ class Fit(object): self.__ehp = None self.__effectiveTank = None + @property + def isInvalid(self): + return self.__invalid + @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 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/ship.py b/eos/saveddata/ship.py index 03eeef3d4..123b56b73 100644 --- a/eos/saveddata/ship.py +++ b/eos/saveddata/ship.py @@ -20,6 +20,7 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.effectHandlerHelpers import HandledItem from eos.saveddata.mode import Mode +import eos.db class Ship(ItemAttrShortcut, HandledItem): def __init__(self, item): @@ -28,33 +29,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): @@ -96,21 +82,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/service/fit.py b/service/fit.py index f6c1f362d..f74d40590 100644 --- a/service/fit.py +++ b/service/fit.py @@ -149,8 +149,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