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