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 sys
# TODO: move all logging back to pyfa.py main loop
# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
import logging
import logging.handlers
# Load variable overrides specific to distribution type
try:
import configforced
@@ -12,6 +17,8 @@ debug = False
# Defines if our saveddata will be in pyfa root or not
saveInRoot = False
logLevel = logging.WARN
# Version data
version = "1.12.1"
tag = "git"
@@ -25,11 +32,6 @@ staticPath = None
saveDB = None
gameDB = None
# TODO: move back to pyfa.py main loop
# We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it)
import logging
logging.basicConfig()
def defPaths():
global pyfaPath
global savePath
@@ -55,6 +57,15 @@ def defPaths():
savePath = unicode(os.path.expanduser(os.path.join("~", ".pyfa")),
sys.getfilesystemencoding())
format = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
logging.basicConfig(format=format, level=logLevel)
handler = logging.handlers.RotatingFileHandler(os.path.join(savePath, "log.txt"), maxBytes=1000000, backupCount=3)
formatter = logging.Formatter(format)
handler.setFormatter(formatter)
logging.getLogger('').addHandler(handler)
logging.info("Starting pyfa")
# Redirect stderr to file if we're requested to do so
stderrToFile = getattr(configforced, "stderrToFile", None)
if stderrToFile is True:

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("fitID", Integer, ForeignKey("fits.ID"), nullable = False),
Column("active", Boolean),
UniqueConstraint("itemID", "fitID"))
)
activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta,
Column("boosterID", ForeignKey("boosters.ID"), primary_key = True),

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.implant import fitImplants_table
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
HandledProjectedFitList, HandledCargoList
from eos.effectHandlerHelpers import *
fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key = True),
@@ -55,14 +53,16 @@ mapper(Fit, fits_table,
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
"owner" : relation(User, backref = "fits"),
"itemID" : fits_table.c.shipID,
"shipID" : fits_table.c.shipID,
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
"_Fit__drones" : relation(Drone, collection_class = HandledDroneList, cascade='all, delete, delete-orphan', single_parent=True,
"_Fit__drones" : relation(Drone, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
"_Fit__cargo" : relation(Cargo, collection_class = HandledCargoList, cascade='all, delete, delete-orphan', single_parent=True,
"_Fit__cargo" : relation(Cargo, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)),
"_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True,
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
"_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True,
"_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', backref='fit', single_parent=True,
primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
secondary = fitImplants_table),

View File

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

View File

@@ -17,8 +17,12 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from sqlalchemy.orm.attributes import flag_modified
import eos.db
import eos.types
import logging
logger = logging.getLogger(__name__)
class HandledList(list):
def filteredItemPreAssign(self, filter, *args, **kwargs):
@@ -101,6 +105,11 @@ class HandledList(list):
except AttributeError:
pass
def remove(self, thing):
# We must flag it as modified, otherwise it not be removed from the database
flag_modified(thing, "itemID")
list.remove(self, thing)
class HandledModuleList(HandledList):
def append(self, mod):
emptyPosition = float("Inf")
@@ -115,10 +124,14 @@ class HandledModuleList(HandledList):
del self[emptyPosition]
mod.position = emptyPosition
HandledList.insert(self, emptyPosition, mod)
if mod.isInvalid:
self.remove(mod)
return
mod.position = len(self)
HandledList.append(self, mod)
if mod.isInvalid:
self.remove(mod)
def insert(self, index, mod):
mod.position = index
@@ -149,128 +162,71 @@ class HandledModuleList(HandledList):
if mod.getModifiedItemAttr("subSystemSlot") == slot:
del self[i]
class HandledDroneList(HandledList):
class HandledDroneCargoList(HandledList):
def find(self, item):
for d in self:
if d.item == item:
yield d
for o in self:
if o.item == item:
yield o
def findFirst(self, item):
for d in self.find(item):
return d
for o in self.find(item):
return o
def append(self, drone):
list.append(self, drone)
def append(self, thing):
HandledList.append(self, thing)
def remove(self, drone):
HandledList.remove(self, drone)
def appendItem(self, item, amount = 1):
if amount < 1: ValueError("Amount of drones to add should be >= 1")
d = self.findFirst(item)
if d is None:
d = eos.types.Drone(item)
self.append(d)
d.amount += amount
return d
def removeItem(self, item, amount):
if amount < 1: ValueError("Amount of drones to remove should be >= 1")
d = self.findFirst(item)
if d is None: return
d.amount -= amount
if d.amount <= 0:
self.remove(d)
return None
return d
class HandledCargoList(HandledList):
# shameless copy of HandledDroneList
# I have no idea what this does, but I needed it
# @todo: investigate this
def find(self, item):
for d in self:
if d.item == item:
yield d
def findFirst(self, item):
for d in self.find(item):
return d
def append(self, cargo):
list.append(self, cargo)
def remove(self, cargo):
HandledList.remove(self, cargo)
def appendItem(self, item, qty = 1):
if qty < 1: ValueError("Amount of cargo to add should be >= 1")
d = self.findFirst(item)
if d is None:
d = eos.types.Cargo(item)
self.append(d)
d.qty += qty
return d
def removeItem(self, item, qty):
if qty < 1: ValueError("Amount of cargo to remove should be >= 1")
d = self.findFirst(item)
if d is None: return
d.qty -= qty
if d.qty <= 0:
self.remove(d)
return None
return d
if thing.isInvalid:
self.remove(thing)
class HandledImplantBoosterList(HandledList):
def __init__(self):
self.__slotCache = {}
def append(self, thing):
if thing.isInvalid:
HandledList.append(self, thing)
self.remove(thing)
return
def append(self, implant):
if self.__slotCache.has_key(implant.slot):
raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True")
self.__slotCache[implant.slot] = implant
HandledList.append(self, implant)
# if needed, remove booster that was occupying slot
oldObj = next((m for m in self if m.slot == thing.slot), None)
if oldObj:
logging.info("Slot %d occupied with %s, replacing with %s", thing.slot, oldObj.item.name, thing.item.name)
self.remove(oldObj)
def remove(self, implant):
HandledList.remove(self, implant)
del self.__slotCache[implant.slot]
# While we deleted this implant, in edge case seems like not all references
# to it are removed and object still lives in session; forcibly remove it,
# or otherwise when adding the same booster twice booster's table (typeID, fitID)
# constraint will report database integrity error
# TODO: make a proper fix, probably by adjusting fit-boosters sqlalchemy relationships
eos.db.remove(implant)
def freeSlot(self, slot):
if hasattr(slot, "slot"):
slot = slot.slot
try:
implant = self.__slotCache[slot]
except KeyError:
return False
try:
self.remove(implant)
except ValueError:
return False
return True
HandledList.append(self, thing)
class HandledProjectedModList(HandledList):
def append(self, proj):
if proj.isInvalid:
# we must include it before we remove it. doing it this way ensures
# rows and relationships in database are removed as well
HandledList.append(self, proj)
self.remove(proj)
return
proj.projected = True
isSystemEffect = proj.item.group.name == "Effect Beacon"
if isSystemEffect:
# remove other system effects - only 1 per fit plz
oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None)
if oldEffect:
logging.info("System effect occupied with %s, replacing with %s", oldEffect.item.name, proj.item.name)
self.remove(oldEffect)
HandledList.append(self, proj)
# Remove non-projectable modules
if not proj.item.isType("projected") and not isSystemEffect:
self.remove(proj)
class HandledProjectedDroneList(HandledDroneCargoList):
def append(self, proj):
proj.projected = True
HandledList.append(self, proj)
class HandledProjectedDroneList(HandledDroneList):
def append(self, proj):
proj.projected = True
list.append(self, proj)
# Remove invalid or non-projectable drones
if proj.isInvalid or not proj.item.isType("projected"):
self.remove(proj)
class HandledProjectedFitList(HandledList):
def append(self, proj):

View File

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

View File

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

View File

@@ -20,81 +20,80 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
logger = logging.getLogger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
if item.category.name != "Drone":
raise ValueError("Passed item is not a drone")
"""Initialize a drone from the program"""
self.__item = item
self.__charge = None
self.itemID = item.ID
if self.isInvalid:
raise ValueError("Passed item is not a Drone")
self.itemID = item.ID if item is not None else None
self.amount = 0
self.amountActive = 0
self.__dps = None
self.__volley = None
self.__miningyield = None
self.projected = False
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.itemModifiedAttributes.original = self.item.attributes
self.build()
@reconstructor
def init(self):
"""Initialize a drone from the database and validate"""
self.__item = None
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID)
return
if self.isInvalid:
logger.error("Item (id: %d) is not a Drone", self.itemID)
return
self.build()
def build(self):
""" Build object. Assumes proper and valid item already set """
self.__charge = None
self.__dps = None
self.__volley = None
self.__miningyield = None
self.__item = None
self.__charge = None
def __fetchItemInfo(self):
import eos.db
self.__item = eos.db.getItem(self.itemID)
self.__charge = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes
self.__itemModifiedAttributes.original = self.__item.attributes
def __fetchChargeInfo(self):
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
self.__chargeModifiedAttributes = ModifiedAttributeDict()
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
if chargeID is not None:
import eos.db
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.chargeModifiedAttributes.original = charge.attributes
else:
self.__charge = 0
self.__chargeModifiedAttributes.original = charge.attributes
@property
def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes
@property
def chargeModifiedAttributes(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__chargeModifiedAttributes
@property
def item(self):
if self.__item is None:
self.__fetchItemInfo()
def isInvalid(self):
return self.__item is None or self.__item.category.name != "Drone"
@property
def item(self):
return self.__item
@property
def charge(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__charge if self.__charge != 0 else None
return self.__charge
@property
def dealsDamage(self):

View File

@@ -17,8 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from eos.effectHandlerHelpers import HandledList, HandledModuleList, HandledDroneList, HandledImplantBoosterList, \
HandledProjectedFitList, HandledProjectedModList, HandledProjectedDroneList, HandledCargoList
from eos.effectHandlerHelpers import *
from eos.modifiedAttributeDict import ModifiedAttributeDict
from sqlalchemy.orm import validates, reconstructor
from itertools import chain
@@ -28,7 +27,11 @@ from math import sqrt, log, asinh
from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
from eos.saveddata.module import State
from eos.saveddata.mode import Mode
import eos.db
import time
import logging
logger = logging.getLogger(__name__)
try:
from collections import OrderedDict
@@ -48,10 +51,14 @@ class Fit(object):
PEAK_RECHARGE = 0.25
def __init__(self):
def __init__(self, ship=None, name=""):
"""Initialize a fit from the program"""
# use @mode.setter's to set __attr and IDs. This will set mode as well
self.ship = ship
self.__modules = HandledModuleList()
self.__drones = HandledDroneList()
self.__cargo = HandledCargoList()
self.__drones = HandledDroneCargoList()
self.__cargo = HandledDroneCargoList()
self.__implants = HandledImplantBoosterList()
self.__boosters = HandledImplantBoosterList()
self.__projectedFits = HandledProjectedFitList()
@@ -59,23 +66,40 @@ class Fit(object):
self.__projectedDrones = HandledProjectedDroneList()
self.__character = None
self.__owner = None
self.shipID = None
self.projected = False
self.name = ""
self.fleet = None
self.boostsFits = set()
self.gangBoosts = None
self.name = name
self.timestamp = time.time()
self.ecmProjectedStr = 1
self.modeID = None
self.build()
@reconstructor
def init(self):
"""Initialize a fit from the database and validate"""
self.__ship = None
self.__mode = None
if self.shipID:
item = eos.db.getItem(self.shipID)
if item is None:
logger.error("Item (id: %d) does not exist", self.shipID)
return
try:
self.__ship = Ship(item)
except ValueError:
logger.error("Item (id: %d) is not a Ship", self.shipID)
return
if self.modeID and self.__ship:
item = eos.db.getItem(self.modeID)
# Don't need to verify if it's a proper item, as validateModeItem assures this
self.__mode = self.ship.validateModeItem(item)
self.build()
def build(self):
from eos import db
self.__extraDrains = []
self.__ehp = None
self.__weaponDPS = None
@@ -100,11 +124,6 @@ class Fit(object):
self.ecmProjectedStr = 1
self.extraAttributes = ModifiedAttributeDict(self)
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
if self.ship is not None:
self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
else:
self.mode = None
@property
def targetResists(self):
@@ -128,13 +147,17 @@ class Fit(object):
self.__ehp = None
self.__effectiveTank = None
@property
def isInvalid(self):
return self.__ship is None
@property
def mode(self):
return self._mode
return self.__mode
@mode.setter
def mode(self, mode):
self._mode = mode
self.__mode = mode
self.modeID = mode.item.ID if mode is not None else None
@property
@@ -154,7 +177,7 @@ class Fit(object):
self.__ship = ship
self.shipID = ship.item.ID if ship is not None else None
# set mode of new ship
self.mode = self.ship.checkModeItem(None) if ship is not None else None
self.mode = self.ship.validateModeItem(None) if ship is not None else None
@property
def drones(self):

View File

@@ -20,46 +20,58 @@
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
from eos.effectHandlerHelpers import HandledItem
from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
logger = logging.getLogger(__name__)
class Implant(HandledItem, ItemAttrShortcut):
def __init__(self, item):
self.__slot = self.__calculateSlot(item)
self.__item = item
self.itemID = item.ID
if self.isInvalid:
raise ValueError("Passed item is not an Implant")
self.itemID = item.ID if item is not None else None
self.active = True
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.item.attributes
self.build()
@reconstructor
def init(self):
self.__item = None
def __fetchItemInfo(self):
import eos.db
self.__item = eos.db.getItem(self.itemID)
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID)
return
if self.isInvalid:
logger.error("Item (id: %d) is not an Implant", self.itemID)
return
self.build()
def build(self):
""" Build object. Assumes proper and valid item already set """
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
self.__slot = self.__calculateSlot(self.__item)
@property
def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes
@property
def slot(self):
if self.__item is None:
self.__fetchItemInfo()
def isInvalid(self):
return self.__item is None or self.__item.category.name != "Implant"
@property
def slot(self):
return self.__slot
@property
def item(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__item
def __calculateSlot(self, item):

View File

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

View File

@@ -23,6 +23,10 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, C
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum
from eos.mathUtils import floorFloat
import eos.db
import logging
logger = logging.getLogger(__name__)
class State(Enum):
OFFLINE = -1
@@ -49,95 +53,78 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
MINING_ATTRIBUTES = ("miningAmount", )
def __init__(self, item):
self.__item = item if item != None else 0
"""Initialize a module from the program"""
self.__item = item
if item is not None and self.isInvalid:
raise ValueError("Passed item is not a Module")
self.__charge = None
self.itemID = item.ID if item is not None else None
self.__charge = 0
self.projected = False
self.state = State.ONLINE
self.build()
@reconstructor
def init(self):
"""Initialize a module from the database and validate"""
self.__item = None
self.__charge = None
# we need this early if module is invalid and returns early
self.__slot = self.dummySlot
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID)
return
if self.isInvalid:
logger.error("Item (id: %d) is not a Module", self.itemID)
return
if self.chargeID:
self.__charge = eos.db.getItem(self.chargeID)
self.build()
def build(self):
""" Builds internal module variables from both init's """
if self.__charge and self.__charge.category.name != "Charge":
self.__charge = None
self.__dps = None
self.__miningyield = None
self.__volley = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
self.__hardpoint = Hardpoint.NONE
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__slot = None
if item != None:
self.__itemModifiedAttributes.original = item.attributes
self.__hardpoint = self.__calculateHardpoint(item)
self.__slot = self.__calculateSlot(item)
self.__chargeModifiedAttributes = ModifiedAttributeDict()
self.__slot = self.dummySlot # defaults to None
@reconstructor
def init(self):
if self.dummySlot is None:
self.__item = None
self.__charge = None
self.__volley = None
self.__dps = None
self.__miningyield = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
else:
self.__slot = self.dummySlot
self.__item = 0
self.__charge = 0
self.__dps = 0
self.__miningyield = 0
self.__volley = 0
self.__reloadTime = 0
self.__reloadForce = None
self.__chargeCycles = 0
self.__hardpoint = Hardpoint.NONE
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__chargeModifiedAttributes = ModifiedAttributeDict()
def __fetchItemInfo(self):
import eos.db
item = eos.db.getItem(self.itemID)
self.__item = item
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = item.attributes
self.__hardpoint = self.__calculateHardpoint(item)
self.__slot = self.__calculateSlot(item)
def __fetchChargeInfo(self):
self.__chargeModifiedAttributes = ModifiedAttributeDict()
if self.chargeID is not None:
import eos.db
charge = eos.db.getItem(self.chargeID)
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
else:
self.__charge = 0
if self.__item:
self.__itemModifiedAttributes.original = self.__item.attributes
self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__slot = self.__calculateSlot(self.__item)
if self.__charge:
self.__chargeModifiedAttributes.original = self.__charge.attributes
@classmethod
def buildEmpty(cls, slot):
empty = Module(None)
empty.__slot = slot
empty.__hardpoint = Hardpoint.NONE
empty.__item = 0
empty.__charge = 0
empty.dummySlot = slot
empty.__itemModifiedAttributes = ModifiedAttributeDict()
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
return empty
@classmethod
def buildRack(cls, slot):
empty = Rack(None)
empty.__slot = slot
empty.__hardpoint = Hardpoint.NONE
empty.__item = 0
empty.__charge = 0
empty.dummySlot = slot
empty.__itemModifiedAttributes = ModifiedAttributeDict()
empty.__chargeModifiedAttributes = ModifiedAttributeDict()
return empty
@property
@@ -146,11 +133,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def hardpoint(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__hardpoint
@property
def isInvalid(self):
if self.isEmpty:
return False
return self.__item is None or (self.__item.category.name not in ("Module", "Subsystem") and self.__item.group.name != "Effect Beacon")
@property
def numCharges(self):
if self.charge is None:
@@ -263,38 +253,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def slot(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__slot
@property
def itemModifiedAttributes(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__itemModifiedAttributes
@property
def chargeModifiedAttributes(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__chargeModifiedAttributes
@property
def item(self):
if self.__item is None:
self.__fetchItemInfo()
return self.__item if self.__item != 0 else None
@property
def charge(self):
if self.__charge is None:
self.__fetchChargeInfo()
return self.__charge if self.__charge != 0 else None
@charge.setter
@@ -516,7 +491,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getValidCharges(self):
validCharges = set()
import eos.db
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
if itemChargeGroup is not None:

View File

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

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):
if self.GetData():
self.dropFn(x, y, int(self.dropData.GetText()))
data = self.dropData.GetText().split(':')
self.dropFn(x, y, data)
return t
class DroneView(d.Display):
@@ -72,7 +73,7 @@ class DroneView(d.Display):
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
self.SetDropTarget(DroneViewDrop(self.mergeDrones))
self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
def OnLeaveWindow(self, event):
self.SetToolTip(None)
@@ -117,17 +118,27 @@ class DroneView(d.Display):
row = event.GetIndex()
if row != -1:
data = wx.PyTextDataObject()
data.SetText(str(self.GetItemData(row)))
data.SetText("drone:"+str(row))
dropSource = wx.DropSource(self)
dropSource.SetData(data)
res = dropSource.DoDragDrop()
def mergeDrones(self, x, y, itemID):
srcRow = self.FindItemData(-1,itemID)
dstRow, _ = self.HitTest((x, y))
if srcRow != -1 and dstRow != -1:
self._merge(srcRow, dstRow)
def handleDragDrop(self, x, y, data):
'''
Handles dragging of items from various pyfa displays which support it
data is list with two indices:
data[0] is hard-coded str of originating source
data[1] is typeID or index of data we want to manipulate
'''
if data[0] == "drone": # we want to merge drones
srcRow = int(data[1])
dstRow, _ = self.HitTest((x, y))
if srcRow != -1 and dstRow != -1:
self._merge(srcRow, dstRow)
elif data[0] == "market":
wx.PostEvent(self.mainFrame, mb.ItemSelected(itemID=int(data[1])))
def _merge(self, src, dst):
sFit = service.Fit.getInstance()

View File

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

View File

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

View File

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

View File

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