Merge branch 'master' into singularity

This commit is contained in:
DarkPhoenix
2015-07-06 01:39:50 +03:00
20 changed files with 401 additions and 610 deletions

View File

@@ -1,6 +1,11 @@
import os import os
import sys 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 # Load variable overrides specific to distribution type
try: try:
import configforced import configforced
@@ -12,6 +17,8 @@ debug = False
# Defines if our saveddata will be in pyfa root or not # Defines if our saveddata will be in pyfa root or not
saveInRoot = False saveInRoot = False
logLevel = logging.WARN
# Version data # Version data
version = "1.12.1" version = "1.12.1"
tag = "git" tag = "git"
@@ -25,11 +32,6 @@ staticPath = None
saveDB = None saveDB = None
gameDB = 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(): def defPaths():
global pyfaPath global pyfaPath
global savePath global savePath
@@ -55,6 +57,15 @@ def defPaths():
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")), savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
sys.getfilesystemencoding()) 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 # Redirect stderr to file if we're requested to do so
stderrToFile = getattr(configforced, "stderrToFile", None) stderrToFile = getattr(configforced, "stderrToFile", None)
if stderrToFile is True: if stderrToFile is True:

View File

@@ -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")

View File

@@ -29,7 +29,7 @@ boosters_table = Table("boosters", saveddata_meta,
Column("itemID", Integer), Column("itemID", Integer),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False), Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
Column("active", Boolean), Column("active", Boolean),
UniqueConstraint("itemID", "fitID")) )
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta, activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True), Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),

View File

@@ -27,9 +27,7 @@ from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.cargo import cargo_table from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_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.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \ from eos.effectHandlerHelpers import *
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
HandledProjectedFitList, HandledCargoList
fits_table = Table("fits", saveddata_meta, fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key = True), 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, "_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)), primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
"owner" : relation(User, backref = "fits"), "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__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)), 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)), 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, "_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)), 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, primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin = fitImplants_table.c.implantID == Implant.ID, secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
secondary = fitImplants_table), secondary = fitImplants_table),

View File

@@ -185,6 +185,12 @@ def getFit(lookfor, eager=None):
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first() fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
else: else:
raise TypeError("Need integer as argument") raise TypeError("Need integer as argument")
if fit.isInvalid:
with sd_lock:
removeInvalid([fit])
return None
return fit return fit
@cachedQuery(Fleet, 1, "fleetID") @cachedQuery(Fleet, 1, "fleetID")
@@ -244,9 +250,10 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None):
filter = processWhere(filter, where) filter = processWhere(filter, where)
eager = processEager(eager) eager = processEager(eager)
with sd_lock: 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: else:
raise TypeError("ShipID must be integer") raise TypeError("ShipID must be integer")
return fits return fits
def getBoosterFits(ownerID=None, where=None, eager=None): def getBoosterFits(ownerID=None, where=None, eager=None):
@@ -264,7 +271,8 @@ def getBoosterFits(ownerID=None, where=None, eager=None):
filter = processWhere(filter, where) filter = processWhere(filter, where)
eager = processEager(eager) eager = processEager(eager)
with sd_lock: 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 return fits
def countAllFits(): def countAllFits():
@@ -295,7 +303,8 @@ def countFitsWithShip(shipID, ownerID=None, where=None, eager=None):
def getFitList(eager=None): def getFitList(eager=None):
eager = processEager(eager) eager = processEager(eager)
with sd_lock: with sd_lock:
fits = saveddata_session.query(Fit).options(*eager).all() fits = removeInvalid(saveddata_session.query(Fit).options(*eager).all())
return fits return fits
def getFleetList(eager=None): def getFleetList(eager=None):
@@ -385,7 +394,8 @@ def searchFits(nameLike, where=None, eager=None):
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where) filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
eager = processEager(eager) eager = processEager(eager)
with sd_lock: 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 return fits
def getSquadsIDsWithFitID(fitID): def getSquadsIDsWithFitID(fitID):
@@ -406,6 +416,16 @@ def getProjectedFits(fitID):
else: else:
raise TypeError("Need integer as argument") 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): def add(stuff):
with sd_lock: with sd_lock:
saveddata_session.add(stuff) saveddata_session.add(stuff)

View File

@@ -17,8 +17,12 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
#=============================================================================== #===============================================================================
from sqlalchemy.orm.attributes import flag_modified
import eos.db import eos.db
import eos.types import eos.types
import logging
logger = logging.getLogger(__name__)
class HandledList(list): class HandledList(list):
def filteredItemPreAssign(self, filter, *args, **kwargs): def filteredItemPreAssign(self, filter, *args, **kwargs):
@@ -101,6 +105,11 @@ class HandledList(list):
except AttributeError: except AttributeError:
pass 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): class HandledModuleList(HandledList):
def append(self, mod): def append(self, mod):
emptyPosition = float("Inf") emptyPosition = float("Inf")
@@ -115,10 +124,14 @@ class HandledModuleList(HandledList):
del self[emptyPosition] del self[emptyPosition]
mod.position = emptyPosition mod.position = emptyPosition
HandledList.insert(self, emptyPosition, mod) HandledList.insert(self, emptyPosition, mod)
if mod.isInvalid:
self.remove(mod)
return return
mod.position = len(self) mod.position = len(self)
HandledList.append(self, mod) HandledList.append(self, mod)
if mod.isInvalid:
self.remove(mod)
def insert(self, index, mod): def insert(self, index, mod):
mod.position = index mod.position = index
@@ -149,128 +162,71 @@ class HandledModuleList(HandledList):
if mod.getModifiedItemAttr("subSystemSlot") == slot: if mod.getModifiedItemAttr("subSystemSlot") == slot:
del self[i] del self[i]
class HandledDroneList(HandledList): class HandledDroneCargoList(HandledList):
def find(self, item): def find(self, item):
for d in self: for o in self:
if d.item == item: if o.item == item:
yield d yield o
def findFirst(self, item): def findFirst(self, item):
for d in self.find(item): for o in self.find(item):
return d return o
def append(self, drone): def append(self, thing):
list.append(self, drone) HandledList.append(self, thing)
def remove(self, drone): if thing.isInvalid:
HandledList.remove(self, drone) self.remove(thing)
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
class HandledImplantBoosterList(HandledList): class HandledImplantBoosterList(HandledList):
def __init__(self): def append(self, thing):
self.__slotCache = {} if thing.isInvalid:
HandledList.append(self, thing)
self.remove(thing)
return
def append(self, implant): # if needed, remove booster that was occupying slot
if self.__slotCache.has_key(implant.slot): oldObj = next((m for m in self if m.slot == thing.slot), None)
raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True") if oldObj:
self.__slotCache[implant.slot] = implant logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name)
HandledList.append(self, implant) self.remove(oldObj)
def remove(self, implant): HandledList.append(self, thing)
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
class HandledProjectedModList(HandledList): 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): def append(self, proj):
proj.projected = True proj.projected = True
HandledList.append(self, proj) HandledList.append(self, proj)
class HandledProjectedDroneList(HandledDroneList): # Remove invalid or non-projectable drones
def append(self, proj): if proj.isInvalid or not proj.item.isType("projected"):
proj.projected = True self.remove(proj)
list.append(self, proj)
class HandledProjectedFitList(HandledList): class HandledProjectedFitList(HandledList):
def append(self, proj): def append(self, proj):

View File

@@ -20,36 +20,53 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from sqlalchemy.orm import reconstructor, validates from sqlalchemy.orm import reconstructor, validates
import eos.db
import logging
logger = logging.getLogger(__name__)
class Booster(HandledItem, ItemAttrShortcut): class Booster(HandledItem, ItemAttrShortcut):
def __init__(self, item): def __init__(self, item):
self.__slot = self.__calculateSlot(item)
self.itemID = item.ID
self.__item = item 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.active = True
self.build() self.build()
@reconstructor @reconstructor
def init(self): def init(self):
"""Initialize a booster from the database and validate"""
self.__item = None 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): def build(self):
""" Build object. Assumes proper and valid item already set """
self.__sideEffects = []
self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.original = self.__item.attributes
self.__sideEffects = [] self.__slot = self.__calculateSlot(self.__item)
for effect in self.item.effects.itervalues():
for effect in self.__item.effects.itervalues():
if effect.isType("boosterSideEffect"): if effect.isType("boosterSideEffect"):
s = SideEffect(self) s = SideEffect(self)
s.effect = effect s.effect = effect
s.active = effect.ID in self.__activeSideEffectIDs s.active = effect.ID in self.__activeSideEffectIDs
self.__sideEffects.append(s) 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): def iterSideEffects(self):
return self.__sideEffects.__iter__() return self.__sideEffects.__iter__()
@@ -62,23 +79,18 @@ class Booster(HandledItem, ItemAttrShortcut):
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
@property @property
def slot(self): def isInvalid(self):
if self.__item is None: return self.__item is None or self.__item.group.name != "Booster"
self.__fetchItemInfo()
@property
def slot(self):
return self.__slot return self.__slot
@property @property
def item(self): def item(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__item return self.__item
def __calculateSlot(self, item): def __calculateSlot(self, item):

View File

@@ -20,40 +20,45 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
# Cargo class copied from Implant class and hacked to make work. \o/ logger = logging.getLogger(__name__)
# @todo: clean me up, Scotty
class Cargo(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): class Cargo(HandledItem, ItemAttrShortcut):
def __init__(self, item): def __init__(self, item):
"""Initialize cargo from the program"""
self.__item = item self.__item = item
self.itemID = item.ID self.itemID = item.ID if item is not None else None
self.amount = 0 self.amount = 0
self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes self.__itemModifiedAttributes.original = item.attributes
@reconstructor @reconstructor
def init(self): def init(self):
"""Initialize cargo from the database and validate"""
self.__item = None self.__item = None
def __fetchItemInfo(self): if self.itemID:
import eos.db self.__item = eos.db.getItem(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 = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.original = self.__item.attributes
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
@property @property
def item(self): def isInvalid(self):
if self.__item is None: return self.__item is None
self.__fetchItemInfo()
@property
def item(self):
return self.__item return self.__item
def clear(self): def clear(self):

View File

@@ -20,81 +20,80 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
logger = logging.getLogger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal") DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",) MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item): def __init__(self, item):
if item.category.name != "Drone": """Initialize a drone from the program"""
raise ValueError("Passed item is not a drone")
self.__item = item 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.amount = 0
self.amountActive = 0 self.amountActive = 0
self.__dps = None
self.__volley = None
self.__miningyield = None
self.projected = False self.projected = False
self.__itemModifiedAttributes = ModifiedAttributeDict() self.build()
self.itemModifiedAttributes.original = self.item.attributes
@reconstructor @reconstructor
def init(self): 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.__dps = None
self.__volley = None self.__volley = None
self.__miningyield = 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 = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes self.__itemModifiedAttributes.original = self.__item.attributes
def __fetchChargeInfo(self):
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
self.__chargeModifiedAttributes = ModifiedAttributeDict() self.__chargeModifiedAttributes = ModifiedAttributeDict()
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
if chargeID is not None: if chargeID is not None:
import eos.db
charge = eos.db.getItem(int(chargeID)) charge = eos.db.getItem(int(chargeID))
self.__charge = charge self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
self.chargeModifiedAttributes.original = charge.attributes
else:
self.__charge = 0
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
@property @property
def chargeModifiedAttributes(self): def chargeModifiedAttributes(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__chargeModifiedAttributes return self.__chargeModifiedAttributes
@property @property
def item(self): def isInvalid(self):
if self.__item is None: return self.__item is None or self.__item.category.name != "Drone"
self.__fetchItemInfo()
@property
def item(self):
return self.__item return self.__item
@property @property
def charge(self): def charge(self):
if self.__charge is None: return self.__charge
self.__fetchChargeInfo()
return self.__charge if self.__charge != 0 else None
@property @property
def dealsDamage(self): def dealsDamage(self):

View File

@@ -17,8 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
#=============================================================================== #===============================================================================
from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \ from eos.effectHandlerHelpers import *
HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList
from eos.modifiedAttributeDict import ModifiedAttributeDict from eos.modifiedAttributeDict import ModifiedAttributeDict
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
from itertools import chain 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.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
from eos.saveddata.module import State from eos.saveddata.module import State
from eos.saveddata.mode import Mode from eos.saveddata.mode import Mode
import eos.db
import time import time
import logging
logger = logging.getLogger(__name__)
try: try:
from collections import OrderedDict from collections import OrderedDict
@@ -48,10 +51,14 @@ class Fit(object):
PEAK_RECHARGE = 0.25 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.__modules = HandledModuleList()
self.__drones = HandledDroneList() self.__drones = HandledDroneCargoList()
self.__cargo = HandledCargoList() self.__cargo = HandledDroneCargoList()
self.__implants = HandledImplantBoosterList() self.__implants = HandledImplantBoosterList()
self.__boosters = HandledImplantBoosterList() self.__boosters = HandledImplantBoosterList()
self.__projectedFits = HandledProjectedFitList() self.__projectedFits = HandledProjectedFitList()
@@ -59,23 +66,40 @@ class Fit(object):
self.__projectedDrones = HandledProjectedDroneList() self.__projectedDrones = HandledProjectedDroneList()
self.__character = None self.__character = None
self.__owner = None self.__owner = None
self.shipID = None
self.projected = False self.projected = False
self.name = "" self.name = name
self.fleet = None
self.boostsFits = set()
self.gangBoosts = None
self.timestamp = time.time() self.timestamp = time.time()
self.ecmProjectedStr = 1
self.modeID = None self.modeID = None
self.build() self.build()
@reconstructor @reconstructor
def init(self): 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() self.build()
def build(self): def build(self):
from eos import db
self.__extraDrains = [] self.__extraDrains = []
self.__ehp = None self.__ehp = None
self.__weaponDPS = None self.__weaponDPS = None
@@ -100,11 +124,6 @@ class Fit(object):
self.ecmProjectedStr = 1 self.ecmProjectedStr = 1
self.extraAttributes = ModifiedAttributeDict(self) self.extraAttributes = ModifiedAttributeDict(self)
self.extraAttributes.original = self.EXTRA_ATTRIBUTES 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 @property
def targetResists(self): def targetResists(self):
@@ -128,13 +147,17 @@ class Fit(object):
self.__ehp = None self.__ehp = None
self.__effectiveTank = None self.__effectiveTank = None
@property
def isInvalid(self):
return self.__ship is None
@property @property
def mode(self): def mode(self):
return self._mode return self.__mode
@mode.setter @mode.setter
def mode(self, mode): def mode(self, mode):
self._mode = mode self.__mode = mode
self.modeID = mode.item.ID if mode is not None else None self.modeID = mode.item.ID if mode is not None else None
@property @property
@@ -154,7 +177,7 @@ class Fit(object):
self.__ship = ship self.__ship = ship
self.shipID = ship.item.ID if ship is not None else None self.shipID = ship.item.ID if ship is not None else None
# set mode of new ship # 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 @property
def drones(self): def drones(self):

View File

@@ -20,46 +20,58 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from sqlalchemy.orm import validates, reconstructor from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
logger = logging.getLogger(__name__)
class Implant(HandledItem, ItemAttrShortcut): class Implant(HandledItem, ItemAttrShortcut):
def __init__(self, item): def __init__(self, item):
self.__slot = self.__calculateSlot(item)
self.__item = 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.active = True
self.__itemModifiedAttributes = ModifiedAttributeDict() self.build()
self.__itemModifiedAttributes.original = self.item.attributes
@reconstructor @reconstructor
def init(self): def init(self):
self.__item = None self.__item = None
def __fetchItemInfo(self): if self.itemID:
import eos.db self.__item = eos.db.getItem(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 = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.original = self.__item.attributes
self.__slot = self.__calculateSlot(self.__item) self.__slot = self.__calculateSlot(self.__item)
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
@property @property
def slot(self): def isInvalid(self):
if self.__item is None: return self.__item is None or self.__item.category.name != "Implant"
self.__fetchItemInfo()
@property
def slot(self):
return self.__slot return self.__slot
@property @property
def item(self): def item(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__item return self.__item
def __calculateSlot(self, item): def __calculateSlot(self, item):

View File

@@ -19,36 +19,24 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
import eos.db
class Mode(ItemAttrShortcut, HandledItem): class Mode(ItemAttrShortcut, HandledItem):
def __init__(self, item): 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.__item = item
self.__itemModifiedAttributes = ModifiedAttributeDict() 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 self.__itemModifiedAttributes.original = self.item.attributes
@property @property
def item(self): def item(self):
if isinstance(self.__item, int):
self.__fetchItemInfo()
return self.__item return self.__item
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if isinstance(self.__item, int):
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
# @todo: rework to fit only on t3 dessy # @todo: rework to fit only on t3 dessy

View File

@@ -23,6 +23,10 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, C
from eos.effectHandlerHelpers import HandledItem, HandledCharge from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum from eos.enum import Enum
from eos.mathUtils import floorFloat from eos.mathUtils import floorFloat
import eos.db
import logging
logger = logging.getLogger(__name__)
class State(Enum): class State(Enum):
OFFLINE = -1 OFFLINE = -1
@@ -49,95 +53,78 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
MINING_ATTRIBUTES = ("miningAmount", ) MINING_ATTRIBUTES = ("miningAmount", )
def __init__(self, item): 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.itemID = item.ID if item is not None else None
self.__charge = 0
self.projected = False self.projected = False
self.state = State.ONLINE 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.__dps = None
self.__miningyield = None self.__miningyield = None
self.__volley = None self.__volley = None
self.__reloadTime = None self.__reloadTime = None
self.__reloadForce = None self.__reloadForce = None
self.__chargeCycles = None self.__chargeCycles = None
self.__hardpoint = Hardpoint.NONE
self.__itemModifiedAttributes = ModifiedAttributeDict() 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.__chargeModifiedAttributes = ModifiedAttributeDict()
self.__slot = self.dummySlot # defaults to None
@reconstructor if self.__item:
def init(self): self.__itemModifiedAttributes.original = self.__item.attributes
if self.dummySlot is None: self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__item = None self.__slot = self.__calculateSlot(self.__item)
self.__charge = None if self.__charge:
self.__volley = None self.__chargeModifiedAttributes.original = self.__charge.attributes
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
@classmethod @classmethod
def buildEmpty(cls, slot): def buildEmpty(cls, slot):
empty = Module(None) empty = Module(None)
empty.__slot = slot empty.__slot = slot
empty.__hardpoint = Hardpoint.NONE
empty.__item = 0
empty.__charge = 0
empty.dummySlot = slot empty.dummySlot = slot
empty.__itemModifiedAttributes = ModifiedAttributeDict()
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
return empty return empty
@classmethod @classmethod
def buildRack(cls, slot): def buildRack(cls, slot):
empty = Rack(None) empty = Rack(None)
empty.__slot = slot empty.__slot = slot
empty.__hardpoint = Hardpoint.NONE
empty.__item = 0
empty.__charge = 0
empty.dummySlot = slot empty.dummySlot = slot
empty.__itemModifiedAttributes = ModifiedAttributeDict()
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
return empty return empty
@property @property
@@ -146,11 +133,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property @property
def hardpoint(self): def hardpoint(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__hardpoint 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 @property
def numCharges(self): def numCharges(self):
if self.charge is None: if self.charge is None:
@@ -263,38 +253,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property @property
def slot(self): def slot(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__slot return self.__slot
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
@property @property
def chargeModifiedAttributes(self): def chargeModifiedAttributes(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__chargeModifiedAttributes return self.__chargeModifiedAttributes
@property @property
def item(self): def item(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__item if self.__item != 0 else None return self.__item if self.__item != 0 else None
@property @property
def charge(self): def charge(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__charge if self.__charge != 0 else None return self.__charge if self.__charge != 0 else None
@charge.setter @charge.setter
@@ -516,7 +491,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getValidCharges(self): def getValidCharges(self):
validCharges = set() validCharges = set()
import eos.db
for i in range(5): for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i)) itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
if itemChargeGroup is not None: if itemChargeGroup is not None:

View File

@@ -20,6 +20,10 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem from eos.effectHandlerHelpers import HandledItem
from eos.saveddata.mode import Mode from eos.saveddata.mode import Mode
import eos.db
import logging
logger = logging.getLogger(__name__)
class Ship(ItemAttrShortcut, HandledItem): class Ship(ItemAttrShortcut, HandledItem):
def __init__(self, item): 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)) raise ValueError('Passed item "%s" (category: (%s)) is not under Ship category'%(item.name, item.category.name))
self.__item = item self.__item = item
self.__modeItems = self.__getModeItems()
self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__modeItems = self._getModeItems() self.__itemModifiedAttributes.original = self.item.attributes
if not isinstance(item, int):
self.__buildOriginal()
self.commandBonus = 0 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 @property
def item(self): def item(self):
if isinstance(self.__item, int):
self.__fetchItemInfo()
return self.__item return self.__item
@property @property
def itemModifiedAttributes(self): def itemModifiedAttributes(self):
if isinstance(self.__item, int):
self.__fetchItemInfo()
return self.__itemModifiedAttributes return self.__itemModifiedAttributes
def clear(self): def clear(self):
@@ -67,25 +56,16 @@ class Ship(ItemAttrShortcut, HandledItem):
if effect.runTime == runTime and effect.isType("passive"): if effect.runTime == runTime and effect.isType("passive"):
effect.handler(fit, self, ("ship",)) effect.handler(fit, self, ("ship",))
def checkModeItem(self, item): def validateModeItem(self, item):
""" """ Checks if provided item is a valid mode """
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
"""
items = self.__modeItems items = self.__modeItems
if items != None: if items is not None:
if item == None or item not in items: # if we have items, then we are in a tactical destroyer and must have a mode
# We have a tact dessy, but mode is None or not valid. Force new mode if item is None or item not in items:
# If provided item is invalid mode, force new one
return Mode(items[0]) return Mode(items[0])
elif item in items: return Mode(item)
# We have a valid mode
return Mode(item)
return None return None
@property @property
@@ -96,21 +76,17 @@ class Ship(ItemAttrShortcut, HandledItem):
def modes(self): def modes(self):
return [Mode(item) for item in self.__modeItems] if self.__modeItems else None 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 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 valid Item objects, not the Mode objects. Returns None if not a
t3 dessy t3 dessy
""" """
# @todo: is there a better way to determine this that isn't hardcoded groupIDs? if self.item.group.name != "Tactical Destroyer":
if self.item.groupID != 1305:
return None return None
modeGroupID = 1306
import eos.db
items = [] 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: for item in g.items:
# Rely on name detection because race is not reliable # Rely on name detection because race is not reliable
if item.name.lower().startswith(self.item.name.lower()): if item.name.lower().startswith(self.item.name.lower()):

View File

@@ -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 <http://www.gnu.org/licenses/>.
#===============================================================================
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]

View File

@@ -36,7 +36,8 @@ class DroneViewDrop(wx.PyDropTarget):
def OnData(self, x, y, t): def OnData(self, x, y, t):
if self.GetData(): if self.GetData():
self.dropFn(x, y, int(self.dropData.GetText())) data = self.dropData.GetText().split(':')
self.dropFn(x, y, data)
return t return t
class DroneView(d.Display): class DroneView(d.Display):
@@ -72,7 +73,7 @@ class DroneView(d.Display):
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
self.SetDropTarget(DroneViewDrop(self.mergeDrones)) self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
def OnLeaveWindow(self, event): def OnLeaveWindow(self, event):
self.SetToolTip(None) self.SetToolTip(None)
@@ -117,17 +118,27 @@ class DroneView(d.Display):
row = event.GetIndex() row = event.GetIndex()
if row != -1: if row != -1:
data = wx.PyTextDataObject() data = wx.PyTextDataObject()
data.SetText(str(self.GetItemData(row))) data.SetText("drone:"+str(row))
dropSource = wx.DropSource(self) dropSource = wx.DropSource(self)
dropSource.SetData(data) dropSource.SetData(data)
res = dropSource.DoDragDrop() res = dropSource.DoDragDrop()
def mergeDrones(self, x, y, itemID): def handleDragDrop(self, x, y, data):
srcRow = self.FindItemData(-1,itemID) '''
dstRow, _ = self.HitTest((x, y)) Handles dragging of items from various pyfa displays which support it
if srcRow != -1 and dstRow != -1:
self._merge(srcRow, dstRow) 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): def _merge(self, src, dst):
sFit = service.Fit.getInstance() sFit = service.Fit.getInstance()

View File

@@ -146,6 +146,7 @@ class MainFrame(wx.Frame):
self.marketBrowser = MarketBrowser(self.notebookBrowsers) self.marketBrowser = MarketBrowser(self.notebookBrowsers)
self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False) self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage = marketImg, showClose = False)
self.marketBrowser.splitter.SetSashPosition(self.marketHeight)
self.shipBrowser = ShipBrowser(self.notebookBrowsers) self.shipBrowser = ShipBrowser(self.notebookBrowsers)
self.notebookBrowsers.AddPage(self.shipBrowser, "Ships", tabImage = shipBrowserImg, showClose = False) self.notebookBrowsers.AddPage(self.shipBrowser, "Ships", tabImage = shipBrowserImg, showClose = False)
@@ -159,8 +160,8 @@ class MainFrame(wx.Frame):
self.notebookBrowsers.SetSelection(1) self.notebookBrowsers.SetSelection(1)
self.splitter.SplitVertically(self.notebookBrowsers, self.FitviewAdditionsPanel) self.splitter.SplitVertically(self.notebookBrowsers, self.FitviewAdditionsPanel)
self.splitter.SetMinimumPaneSize(204) self.splitter.SetMinimumPaneSize(220)
self.splitter.SetSashPosition(300) self.splitter.SetSashPosition(self.browserWidth)
cstatsSizer = wx.BoxSizer(wx.VERTICAL) cstatsSizer = wx.BoxSizer(wx.VERTICAL)
@@ -227,7 +228,7 @@ class MainFrame(wx.Frame):
def LoadMainFrameAttribs(self): 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) self.mainFrameAttribs = service.SettingsProvider.getInstance().getSettings("pyfaMainWindowAttribs", mainFrameDefaultAttribs)
if self.mainFrameAttribs["wnd_maximized"]: if self.mainFrameAttribs["wnd_maximized"]:
@@ -241,6 +242,9 @@ class MainFrame(wx.Frame):
self.SetSize((width, height)) self.SetSize((width, height))
self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"])) self.SetMinSize((mainFrameDefaultAttribs["wnd_width"], mainFrameDefaultAttribs["wnd_height"]))
self.browserWidth = self.mainFrameAttribs["browser_width"]
self.marketHeight = self.mainFrameAttribs["market_height"]
def UpdateMainFrameAttribs(self): def UpdateMainFrameAttribs(self):
if self.IsIconized(): if self.IsIconized():
return return
@@ -250,6 +254,9 @@ class MainFrame(wx.Frame):
self.mainFrameAttribs["wnd_height"] = height self.mainFrameAttribs["wnd_height"] = height
self.mainFrameAttribs["wnd_maximized"] = self.IsMaximized() 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): def SetActiveStatsWindow(self, wnd):
self.activeStatsWnd = wnd self.activeStatsWnd = wnd

View File

@@ -51,7 +51,7 @@ class MarketBrowser(wx.Panel):
self.itemView = ItemView(self.splitter, self) self.itemView = ItemView(self.splitter, self)
self.splitter.SplitHorizontally(self.marketView, self.itemView) self.splitter.SplitHorizontally(self.marketView, self.itemView)
self.splitter.SetMinimumPaneSize(250) self.splitter.SetMinimumPaneSize(150)
# Setup our buttons for metaGroup selection # Setup our buttons for metaGroup selection
# Same fix as for search box on macs, # Same fix as for search box on macs,

View File

@@ -342,7 +342,6 @@ class ResistsEditorDlg(wx.Dialog):
self.patternChanged() self.patternChanged()
def showInput(self, bool): def showInput(self, bool):
print self.namePicker.IsShown(), bool
if bool and not self.namePicker.IsShown(): if bool and not self.namePicker.IsShown():
self.ccResists.Hide() self.ccResists.Hide()
self.namePicker.Show() self.namePicker.Show()

View File

@@ -150,8 +150,8 @@ class Fit(object):
return fit.modules[pos] return fit.modules[pos]
def newFit(self, shipID, name=None): def newFit(self, shipID, name=None):
fit = eos.types.Fit() ship = eos.types.Ship(eos.db.getItem(shipID))
fit.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.name = name if name is not None else "New %s" % fit.ship.item.name
fit.damagePattern = self.pattern fit.damagePattern = self.pattern
fit.targetResists = self.targetResists fit.targetResists = self.targetResists
@@ -275,7 +275,6 @@ class Fit(object):
except ValueError: except ValueError:
return False return False
fit.implants.freeSlot(implant)
fit.implants.append(implant) fit.implants.append(implant)
self.recalc(fit) self.recalc(fit)
return True return True
@@ -301,7 +300,6 @@ class Fit(object):
except ValueError: except ValueError:
return False return False
fit.boosters.freeSlot(booster)
fit.boosters.append(booster) fit.boosters.append(booster)
self.recalc(fit) self.recalc(fit)
return True return True