Merge branch 'origin_master' into effect_rollup

This commit is contained in:
Ryan Holmes
2019-03-21 19:57:45 -04:00
101 changed files with 1498 additions and 853 deletions

View File

@@ -21,13 +21,14 @@ if istravis is True or hasattr(sys, '_called_from_test'):
# Running in Travis. Run saveddata database in memory.
saveddata_connectionstring = 'sqlite:///:memory:'
else:
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata-py3-db.db"))
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db"))
pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring)
settings = {
"useStaticAdaptiveArmorHardener": False,
"strictSkillLevels": True,
"globalDefaultSpoolupPercentage": 1.0
}
# Autodetect path, only change if the autodetection bugs out.

View File

@@ -1,8 +1,30 @@
from eos.enum import Enum
# =============================================================================
# Copyright (C) 2019 Ryan Holmes
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from enum import IntEnum,unique
class Slot(Enum):
@unique
class FittingSlot(IntEnum):
"""
Contains slots for ship fittings
"""
# These are self-explanatory
LOW = 1
MED = 2
@@ -24,3 +46,50 @@ class Slot(Enum):
FS_LIGHT = 13
FS_SUPPORT = 14
FS_HEAVY = 15
@unique
class ImplantLocation(IntEnum):
"""
Contains location of the implant
"""
FIT = 0
CHARACTER = 1
@unique
class CalcType(IntEnum):
"""
Contains location of the calculation
"""
LOCAL = 0
PROJECTED = 1
COMMAND = 2
@unique
class FittingModuleState(IntEnum):
"""
Contains the state of a fitting module
"""
OFFLINE = -1
ONLINE = 0
ACTIVE = 1
OVERHEATED = 2
@unique
class FittingHardpoint(IntEnum):
"""
Contains the types of a fitting hardpoint
"""
NONE = 0
MISSILE = 1
TURRET = 2
@unique
class SpoolType(IntEnum):
SCALE = 0 # [0..1]
TIME = 1 # Expressed via time in seconds since spool up started
CYCLES = 2 # Expressed in amount of cycles since spool up started

View File

@@ -41,8 +41,7 @@ items_table = Table("invtypes", gamedata_meta,
Column("iconID", Integer),
Column("graphicID", Integer),
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
Column("replaceSame", String),
Column("replaceBetter", String))
Column("replacements", String))
from .metaGroup import metatypes_table # noqa
from .traits import traits_table # noqa

View File

@@ -32,6 +32,4 @@ prices_table = Table("prices", saveddata_meta,
Column("status", Integer, nullable=False))
mapper(Price, prices_table, properties={
"_Price__price": prices_table.c.price,
})
mapper(Price, prices_table)

View File

@@ -21,6 +21,7 @@ import sys
from sqlalchemy.sql import and_
from sqlalchemy import desc, select
from sqlalchemy import func
from eos.db import saveddata_session, sd_lock
from eos.db.saveddata.fit import projectedFits_table
@@ -283,6 +284,12 @@ def countAllFits():
return count
def countFitGroupedByShip():
with sd_lock:
count = eos.db.saveddata_session.query(Fit.shipID, func.count(Fit.shipID)).group_by(Fit.shipID).all()
return count
def countFitsWithShip(lookfor, ownerID=None, where=None, eager=None):
"""
Get all the fits using a certain ship.

View File

@@ -7,7 +7,7 @@
#
# Used by:
# Modules from group: Warp Disrupt Field Generator (7 of 7)
from eos.saveddata.module import State
from eos.const import FittingModuleState
type = "projected", "active"
runTime = "early"
@@ -19,10 +19,10 @@ def handler(fit, module, context):
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
if module.charge is not None and module.charge.ID == 45010:
for mod in fit.modules:
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > FittingModuleState.ONLINE:
mod.state = FittingModuleState.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
mod.state = FittingModuleState.ONLINE
else:
if module.charge is None:
fit.ship.boostItemAttr("mass", module.getModifiedItemAttr("massBonusPercentage"))

View File

@@ -2,7 +2,7 @@
#
# Used by:
# Modules named like: Warp Scrambler (27 of 27)
from eos.saveddata.module import State
from eos.const import FittingModuleState
runTime = "early"
type = "projected", "active"
@@ -16,10 +16,10 @@ def handler(fit, module, context):
# this is such a dirty hack
for mod in fit.modules:
if not mod.isEmpty and mod.state > State.ONLINE and (
if not mod.isEmpty and mod.state > FittingModuleState.ONLINE and (
mod.item.requiresSkill("Micro Jump Drive Operation") or
mod.item.requiresSkill("High Speed Maneuvering")
):
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
mod.state = State.ONLINE
mod.state = FittingModuleState.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
mod.state = FittingModuleState.ONLINE

View File

@@ -2,15 +2,15 @@
#
# Used by:
# Modules from group: Energy Neutralizer (54 of 54)
from eos.saveddata.module import State
from eos.const import FittingModuleState
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
def handler(fit, src, context, **kwargs):
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
hasattr(src, "amountActive")):
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
hasattr(src, "amountActive")):
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
if 'effect' in kwargs:

View File

@@ -2,7 +2,7 @@
#
# Used by:
# Structure Modules from group: Structure Energy Neutralizer (5 of 5)
from eos.saveddata.module import State
from eos.const import FittingModuleState
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
@@ -11,7 +11,7 @@ type = "active", "projected"
def handler(fit, src, context, **kwargs):
amount = 0
if "projected" in context:
if (hasattr(src, "state") and src.state >= State.ACTIVE) or hasattr(src, "amountActive"):
if (hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or hasattr(src, "amountActive"):
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
if 'effect' in kwargs:

View File

@@ -2,7 +2,7 @@
#
# Used by:
# Structure Modules from group: Structure Warp Scrambler (2 of 2)
from eos.saveddata.module import State
from eos.const import FittingModuleState
# Not used by any item
runTime = "early"
@@ -14,7 +14,7 @@ def handler(fit, module, context):
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
if module.charge is not None and module.charge.ID == 47336:
for mod in fit.modules:
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > FittingModuleState.ONLINE:
mod.state = FittingModuleState.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > FittingModuleState.ONLINE:
mod.state = FittingModuleState.ONLINE

View File

@@ -3,15 +3,15 @@
# Used by:
# Module: Energy Neutralization Burst Projector
# Structure Module: Standup Energy Neutralization Burst Projector
from eos.saveddata.module import State
from eos.const import FittingModuleState
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
def handler(fit, src, context, **kwargs):
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
hasattr(src, "amountActive")):
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
hasattr(src, "amountActive")):
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
if 'effect' in kwargs:

View File

@@ -2,15 +2,15 @@
#
# Used by:
# Drones from group: Energy Neutralizer Drone (3 of 3)
from eos.saveddata.module import State
from eos.const import FittingModuleState
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
def handler(fit, src, context, **kwargs):
if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
hasattr(src, "amountActive")):
if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
hasattr(src, "amountActive")):
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
time = src.getModifiedItemAttr("energyNeutralizerDuration")

View File

@@ -4,6 +4,7 @@
# Modules from group: Mutadaptive Remote Armor Repairer (5 of 5)
import eos.config
from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, resolveSpoolOptions
@@ -17,8 +18,7 @@ def handler(fit, container, context, **kwargs):
cycleTime = container.getModifiedItemAttr("duration") / 1000.0
repSpoolMax = container.getModifiedItemAttr("repairMultiplierBonusMax")
repSpoolPerCycle = container.getModifiedItemAttr("repairMultiplierBonusPerCycle")
# TODO: fetch spoolup option
defaultSpoolValue = 1
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
spoolType, spoolAmount = resolveSpoolOptions(SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False), container)
rps = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, spoolType, spoolAmount)[0]) / cycleTime
rpsPreSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 0)[0]) / cycleTime

View File

@@ -0,0 +1,17 @@
runTime = "early"
type = "passive"
def handler(fit, module, context):
secModifier = module.getModifiedItemAttr("securityModifier")
module.multiplyItemAttr("structureRigDoomsdayDamageLossTargetBonus", secModifier)
module.multiplyItemAttr("structureRigScanResBonus", secModifier)
module.multiplyItemAttr("structureRigPDRangeBonus", secModifier)
module.multiplyItemAttr("structureRigPDCapUseBonus", secModifier)
module.multiplyItemAttr("structureRigMissileExploVeloBonus", secModifier)
module.multiplyItemAttr("structureRigMissileVelocityBonus", secModifier)
module.multiplyItemAttr("structureRigEwarOptimalBonus", secModifier)
module.multiplyItemAttr("structureRigEwarFalloffBonus", secModifier)
module.multiplyItemAttr("structureRigEwarCapUseBonus", secModifier)
module.multiplyItemAttr("structureRigMissileExplosionRadiusBonus", secModifier)
module.multiplyItemAttr("structureRigMaxTargetRangeBonus", secModifier)

View File

@@ -1,23 +0,0 @@
class Enum(object):
def __init__(self):
pass
@classmethod
def getTypes(cls):
for stuff in cls.__dict__:
if stuff.upper() == stuff:
yield stuff
@classmethod
def getName(cls, v):
map = getattr(cls, "_map", None)
if map is None:
map = cls._map = {}
for type in cls.getTypes():
map[cls.getValue(type)] = type
return map.get(v)
@classmethod
def getValue(cls, type):
return cls.__dict__[type]

View File

@@ -20,7 +20,7 @@
from math import log, sin, radians, exp
from eos.graph import Graph
from eos.saveddata.module import State, Hardpoint
from eos.const import FittingModuleState, FittingHardpoint
from logbook import Logger
pyfalog = Logger(__name__)
@@ -46,7 +46,7 @@ class FitDpsGraph(Graph):
abssort = lambda _val: -abs(_val - 1)
for mod in fit.modules:
if not mod.isEmpty and mod.state >= State.ACTIVE:
if not mod.isEmpty and mod.state >= FittingModuleState.ACTIVE:
if "remoteTargetPaintFalloff" in mod.item.effects or "structureModuleEffectTargetPainter" in mod.item.effects:
ew['signatureRadius'].append(
1 + (mod.getModifiedItemAttr("signatureRadiusBonus") / 100) * self.calculateModuleMultiplier(
@@ -76,12 +76,12 @@ class FitDpsGraph(Graph):
for mod in fit.modules:
dps = mod.getDps(targetResists=fit.targetResists).total
if mod.hardpoint == Hardpoint.TURRET:
if mod.state >= State.ACTIVE:
if mod.hardpoint == FittingHardpoint.TURRET:
if mod.state >= FittingModuleState.ACTIVE:
total += dps * self.calculateTurretMultiplier(mod, data)
elif mod.hardpoint == Hardpoint.MISSILE:
if mod.state >= State.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
elif mod.hardpoint == FittingHardpoint.MISSILE:
if mod.state >= FittingModuleState.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
total += dps * self.calculateMissileMultiplier(mod, data)
if distance <= fit.extraAttributes["droneControlRange"]:

View File

@@ -30,6 +30,7 @@ pyfalog = Logger(__name__)
class Booster(HandledItem, ItemAttrShortcut):
def __init__(self, item):
self.__item = item
@@ -147,3 +148,17 @@ class Booster(HandledItem, ItemAttrShortcut):
copyEffect.active = sideEffect.active
return copy
def rebase(self, item):
active = self.active
sideEffectStates = {se.effectID: se.active for se in self.sideEffects}
Booster.__init__(self, item)
self.active = active
for sideEffect in self.sideEffects:
if sideEffect.effectID in sideEffectStates:
sideEffect.active = sideEffectStates[sideEffect.effectID]
def __repr__(self):
return "Booster(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))
)

View File

@@ -89,3 +89,13 @@ class Cargo(HandledItem, ItemAttrShortcut):
copy = Cargo(self.item)
copy.amount = self.amount
return copy
def rebase(self, item):
amount = self.amount
Cargo.__init__(self, item)
self.amount = amount
def __repr__(self):
return "Cargo(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))
)

View File

@@ -296,6 +296,13 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
copy.amountActive = self.amountActive
return copy
def rebase(self, item):
amount = self.amount
amountActive = self.amountActive
Drone.__init__(self, item)
self.amount = amount
self.amountActive = amountActive
def fits(self, fit):
fitDroneGroupLimits = set()
for i in range(1, 3):

View File

@@ -25,8 +25,8 @@ import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.saveddata.fighterAbility import FighterAbility
from eos.saveddata.module import Slot
from eos.utils.stats import DmgTypes
from eos.const import FittingSlot
pyfalog = Logger(__name__)
@@ -116,12 +116,12 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def __calculateSlot(self, item):
types = {
"Light" : Slot.F_LIGHT,
"Support": Slot.F_SUPPORT,
"Heavy" : Slot.F_HEAVY,
"StandupLight": Slot.FS_LIGHT,
"StandupSupport": Slot.FS_SUPPORT,
"StandupHeavy": Slot.FS_HEAVY
"Light" : FittingSlot.F_LIGHT,
"Support": FittingSlot.F_SUPPORT,
"Heavy" : FittingSlot.F_HEAVY,
"StandupLight": FittingSlot.FS_LIGHT,
"StandupSupport": FittingSlot.FS_SUPPORT,
"StandupHeavy": FittingSlot.FS_HEAVY
}
for t, slot in types.items():
@@ -355,6 +355,17 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
copyAbility.active = ability.active
return copy
def rebase(self, item):
amount = self.amount
active = self.active
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
Fighter.__init__(self, item)
self.amount = amount
self.active = active
for ability in self.abilities:
if ability.effectID in abilityEffectStates:
ability.active = abilityEffectStates[ability.effectID]
def fits(self, fit):
# If ships doesn't support this type of fighter, don't add it
if fit.getNumSlots(self.slot) == 0:

View File

@@ -28,28 +28,19 @@ from sqlalchemy.orm import validates, reconstructor
import eos.db
from eos import capSim
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneCargoList, HandledImplantBoosterList, HandledProjectedDroneList, HandledProjectedModList
from eos.enum import Enum
from eos.const import ImplantLocation, CalcType, FittingSlot
from eos.saveddata.ship import Ship
from eos.saveddata.drone import Drone
from eos.saveddata.character import Character
from eos.saveddata.citadel import Citadel
from eos.saveddata.module import Module, State, Slot, Hardpoint
from eos.const import FittingModuleState, FittingHardpoint
from eos.saveddata.module import Module
from eos.utils.stats import DmgTypes
from logbook import Logger
pyfalog = Logger(__name__)
class ImplantLocation(Enum):
FIT = 0
CHARACTER = 1
class CalcType(Enum):
LOCAL = 0
PROJECTED = 1
COMMAND = 2
class Fit(object):
"""Represents a fitting, with modules, ship, implants, etc."""
@@ -393,6 +384,32 @@ class Fit(object):
else:
return val
def canFit(self, item):
# Whereas Module.fits() deals with current state of the fit in order to determine if somethign fits (for example maxGroupFitted which can be modified by effects),
# this function should be used against Items to see if the item is even allowed on the fit with rules that don't change
fitsOnType = set()
fitsOnGroup = set()
shipType = item.attributes.get("fitsToShipType", None)
if shipType is not None:
fitsOnType.add(shipType.value)
fitsOnType.update([item.attributes[attr].value for attr in item.attributes if attr.startswith("canFitShipType")])
fitsOnGroup.update([item.attributes[attr].value for attr in item.attributes if attr.startswith("canFitShipGroup")])
if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \
and self.ship.item.group.ID not in fitsOnGroup \
and self.ship.item.ID not in fitsOnType:
return False
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
if isinstance(self.ship, Citadel) and item.category.name != "Structure Module" or \
not isinstance(self.ship, Citadel) and item.category.name == "Structure Module":
return False
return True
def clear(self, projected=False, command=False):
self.__effectiveTank = None
self.__weaponDpsMap = {}
@@ -741,7 +758,7 @@ class Fit(object):
The type of calculation our current iteration is in. This helps us determine the interactions between
fits that rely on others for proper calculations
"""
pyfalog.info("Starting fit calculation on: {0}, calc: {1}", repr(self), CalcType.getName(type))
pyfalog.info("Starting fit calculation on: {0}, calc: {1}", repr(self), CalcType(type).name)
# If we are projecting this fit onto another one, collect the projection info for later use
@@ -901,7 +918,7 @@ class Fit(object):
if self.ship is None:
return
for slotType in (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE):
for slotType in (FittingSlot.LOW.value, FittingSlot.MED.value, FittingSlot.HIGH.value, FittingSlot.RIG.value, FittingSlot.SUBSYSTEM.value, FittingSlot.SERVICE.value):
amount = self.getSlotsFree(slotType, True)
if amount > 0:
for _ in range(int(amount)):
@@ -948,7 +965,7 @@ class Fit(object):
def getItemAttrOnlineSum(dict, attr):
amount = 0
for mod in dict:
add = mod.getModifiedItemAttr(attr) if mod.state >= State.ONLINE else None
add = mod.getModifiedItemAttr(attr) if mod.state >= FittingModuleState.ONLINE else None
if add is not None:
amount += add
@@ -967,29 +984,29 @@ class Fit(object):
for mod in chain(self.modules, self.fighters):
if mod.slot is type and (not getattr(mod, "isEmpty", False) or countDummies):
if type in (Slot.F_HEAVY, Slot.F_SUPPORT, Slot.F_LIGHT, Slot.FS_HEAVY, Slot.FS_LIGHT, Slot.FS_SUPPORT) and not mod.active:
if type in (FittingSlot.F_HEAVY, FittingSlot.F_SUPPORT, FittingSlot.F_LIGHT, FittingSlot.FS_HEAVY, FittingSlot.FS_LIGHT, FittingSlot.FS_SUPPORT) and not mod.active:
continue
amount += 1
return amount
slots = {
Slot.LOW : "lowSlots",
Slot.MED : "medSlots",
Slot.HIGH : "hiSlots",
Slot.RIG : "rigSlots",
Slot.SUBSYSTEM: "maxSubSystems",
Slot.SERVICE : "serviceSlots",
Slot.F_LIGHT : "fighterLightSlots",
Slot.F_SUPPORT: "fighterSupportSlots",
Slot.F_HEAVY : "fighterHeavySlots",
Slot.FS_LIGHT: "fighterStandupLightSlots",
Slot.FS_SUPPORT: "fighterStandupSupportSlots",
Slot.FS_HEAVY: "fighterStandupHeavySlots",
FittingSlot.LOW : "lowSlots",
FittingSlot.MED : "medSlots",
FittingSlot.HIGH : "hiSlots",
FittingSlot.RIG : "rigSlots",
FittingSlot.SUBSYSTEM: "maxSubSystems",
FittingSlot.SERVICE : "serviceSlots",
FittingSlot.F_LIGHT : "fighterLightSlots",
FittingSlot.F_SUPPORT: "fighterSupportSlots",
FittingSlot.F_HEAVY : "fighterHeavySlots",
FittingSlot.FS_LIGHT: "fighterStandupLightSlots",
FittingSlot.FS_SUPPORT: "fighterStandupSupportSlots",
FittingSlot.FS_HEAVY: "fighterStandupHeavySlots",
}
def getSlotsFree(self, type, countDummies=False):
if type in (Slot.MODE, Slot.SYSTEM):
if type in (FittingSlot.MODE, FittingSlot.SYSTEM):
# These slots don't really exist, return default 0
return 0
@@ -1001,12 +1018,12 @@ class Fit(object):
return self.ship.getModifiedItemAttr(self.slots[type]) or 0
def getHardpointsFree(self, type):
if type == Hardpoint.NONE:
if type == FittingHardpoint.NONE:
return 1
elif type == Hardpoint.TURRET:
return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(Hardpoint.TURRET)
elif type == Hardpoint.MISSILE:
return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(Hardpoint.MISSILE)
elif type == FittingHardpoint.TURRET:
return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(FittingHardpoint.TURRET)
elif type == FittingHardpoint.MISSILE:
return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(FittingHardpoint.MISSILE)
else:
raise ValueError("%d is not a valid value for Hardpoint Enum", type)
@@ -1168,7 +1185,7 @@ class Fit(object):
capUsed = 0
capAdded = 0
for mod in self.modules:
if mod.state >= State.ACTIVE:
if mod.state >= FittingModuleState.ACTIVE:
if (mod.getModifiedItemAttr("capacitorNeed") or 0) != 0:
cycleTime = mod.rawCycleTime or 0
reactivationTime = mod.getModifiedItemAttr("moduleReactivationDelay") or 0
@@ -1182,7 +1199,7 @@ class Fit(object):
capAdded -= capNeed
# If this is a turret, don't stagger activations
disableStagger = mod.hardpoint == Hardpoint.TURRET
disableStagger = mod.hardpoint == FittingHardpoint.TURRET
drains.append((int(fullCycleTime), mod.getModifiedItemAttr("capacitorNeed") or 0,
mod.numShots or 0, disableStagger, reloadTime))

View File

@@ -115,6 +115,11 @@ class Implant(HandledItem, ItemAttrShortcut):
copy.active = self.active
return copy
def rebase(self, item):
active = self.active
Implant.__init__(self, item)
self.active = active
def __repr__(self):
return "Implant(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))

View File

@@ -23,9 +23,8 @@ from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.const import Slot
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.enum import Enum
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.citadel import Citadel
from eos.saveddata.mutator import Mutator
@@ -35,44 +34,30 @@ from eos.utils.stats import DmgTypes
pyfalog = Logger(__name__)
class State(Enum):
OFFLINE = -1
ONLINE = 0
ACTIVE = 1
OVERHEATED = 2
ProjectedMap = {
State.OVERHEATED: State.ACTIVE,
State.ACTIVE: State.OFFLINE,
State.OFFLINE: State.ACTIVE,
State.ONLINE: State.ACTIVE # Just in case
FittingModuleState.OVERHEATED: FittingModuleState.ACTIVE,
FittingModuleState.ACTIVE: FittingModuleState.OFFLINE,
FittingModuleState.OFFLINE: FittingModuleState.ACTIVE,
FittingModuleState.ONLINE: FittingModuleState.ACTIVE # Just in case
}
# Old state : New State
LocalMap = {
State.OVERHEATED: State.ACTIVE,
State.ACTIVE: State.ONLINE,
State.OFFLINE: State.ONLINE,
State.ONLINE: State.ACTIVE
FittingModuleState.OVERHEATED: FittingModuleState.ACTIVE,
FittingModuleState.ACTIVE: FittingModuleState.ONLINE,
FittingModuleState.OFFLINE: FittingModuleState.ONLINE,
FittingModuleState.ONLINE: FittingModuleState.ACTIVE
}
# For system effects. They should only ever be online or offline
ProjectedSystem = {
State.OFFLINE: State.ONLINE,
State.ONLINE: State.OFFLINE
FittingModuleState.OFFLINE: FittingModuleState.ONLINE,
FittingModuleState.ONLINE: FittingModuleState.OFFLINE
}
class Hardpoint(Enum):
NONE = 0
MISSILE = 1
TURRET = 2
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""An instance of this class represents a module together with its charge and modified attributes"""
MINING_ATTRIBUTES = ("miningAmount",)
@@ -104,7 +89,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__charge = None
self.projected = False
self.state = State.ONLINE
self.state = FittingModuleState.ONLINE
self.build()
@reconstructor
@@ -153,7 +138,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
self.__hardpoint = Hardpoint.NONE
self.__hardpoint = FittingHardpoint.NONE
self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
self.__slot = self.dummySlot # defaults to None
@@ -396,7 +381,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.isEmpty:
self.__miningyield = 0
else:
if self.state >= State.ACTIVE:
if self.state >= FittingModuleState.ACTIVE:
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
"miningAmount") or 0
if volley:
@@ -410,7 +395,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return self.__miningyield
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
return DmgTypes(0, 0, 0, 0)
if self.__baseVolley is None:
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
@@ -448,7 +433,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return dps
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
return None, 0
def getBaseRemoteReps(module):
@@ -554,34 +539,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return fits
def __fitRestrictions(self, fit, hardpointLimit=True):
# Check ship type restrictions
fitsOnType = set()
fitsOnGroup = set()
shipType = self.getModifiedItemAttr("fitsToShipType", None)
if shipType is not None:
fitsOnType.add(shipType)
for attr in list(self.itemModifiedAttributes.keys()):
if attr.startswith("canFitShipType"):
shipType = self.getModifiedItemAttr(attr, None)
if shipType is not None:
fitsOnType.add(shipType)
for attr in list(self.itemModifiedAttributes.keys()):
if attr.startswith("canFitShipGroup"):
shipGroup = self.getModifiedItemAttr(attr, None)
if shipGroup is not None:
fitsOnGroup.add(shipGroup)
if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \
and fit.ship.item.group.ID not in fitsOnGroup \
and fit.ship.item.ID not in fitsOnType:
return False
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
if isinstance(fit.ship, Citadel) and self.item.category.name != "Structure Module" or \
not isinstance(fit.ship, Citadel) and self.item.category.name == "Structure Module":
if not fit.canFit(self.item):
return False
# EVE doesn't let capital modules be fit onto subcapital hulls. Confirmed by CCP Larrikin that this is dictated
@@ -590,14 +549,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return False
# If the mod is a subsystem, don't let two subs in the same slot fit
if self.slot == Slot.SUBSYSTEM:
if self.slot == FittingSlot.SUBSYSTEM:
subSlot = self.getModifiedItemAttr("subSystemSlot")
for mod in fit.modules:
if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
return False
# Check rig sizes
if self.slot == Slot.RIG:
if self.slot == FittingSlot.RIG:
if self.getModifiedItemAttr("rigSize") != fit.ship.getModifiedItemAttr("rigSize"):
return False
@@ -627,9 +586,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# Check if we're within bounds
if state < -1 or state > 2:
return False
elif state >= State.ACTIVE and not self.item.isType("active"):
elif state >= FittingModuleState.ACTIVE and not self.item.isType("active"):
return False
elif state == State.OVERHEATED and not self.item.isType("overheat"):
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
return False
else:
return True
@@ -641,7 +600,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# If we're going to set module to offline or online for local modules or offline for projected,
# it should be fine for all cases
item = self.item
if (state <= State.ONLINE and projectedOnto is None) or (state <= State.OFFLINE):
if (state <= FittingModuleState.ONLINE and projectedOnto is None) or (state <= FittingModuleState.OFFLINE):
return True
# Check if the local module is over it's max limit; if it's not, we're fine
@@ -655,7 +614,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
group = item.group.name
for mod in self.owner.modules:
currItem = getattr(mod, "item", None)
if mod.state >= State.ACTIVE and currItem is not None and currItem.group.name == group:
if mod.state >= FittingModuleState.ACTIVE and currItem is not None and currItem.group.name == group:
currActive += 1
if currActive > maxGroupActive:
break
@@ -718,28 +677,28 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@staticmethod
def __calculateHardpoint(item):
effectHardpointMap = {
"turretFitted" : Hardpoint.TURRET,
"launcherFitted": Hardpoint.MISSILE
"turretFitted" : FittingHardpoint.TURRET,
"launcherFitted": FittingHardpoint.MISSILE
}
if item is None:
return Hardpoint.NONE
return FittingHardpoint.NONE
for effectName, slot in effectHardpointMap.items():
if effectName in item.effects:
return slot
return Hardpoint.NONE
return FittingHardpoint.NONE
@staticmethod
def calculateSlot(item):
effectSlotMap = {
"rigSlot" : Slot.RIG,
"loPower" : Slot.LOW,
"medPower" : Slot.MED,
"hiPower" : Slot.HIGH,
"subSystem" : Slot.SUBSYSTEM,
"serviceSlot": Slot.SERVICE
"rigSlot" : FittingSlot.RIG.value,
"loPower" : FittingSlot.LOW.value,
"medPower" : FittingSlot.MED.value,
"hiPower" : FittingSlot.HIGH.value,
"subSystem" : FittingSlot.SUBSYSTEM.value,
"serviceSlot": FittingSlot.SERVICE.value
}
if item is None:
return None
@@ -747,7 +706,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if effectName in item.effects:
return slot
if item.group.name in Module.SYSTEM_GROUPS:
return Slot.SYSTEM
return FittingSlot.SYSTEM
return None
@@ -801,8 +760,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if effect.runTime == runTime and \
effect.activeByDefault and \
(effect.isType("offline") or
(effect.isType("passive") and self.state >= State.ONLINE) or
(effect.isType("active") and self.state >= State.ACTIVE)) and \
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) and \
(not gang or (gang and effect.isType("gang"))):
chargeContext = ("moduleCharge",)
@@ -815,7 +774,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
effect.handler(fit, self, chargeContext)
if self.item:
if self.state >= State.OVERHEATED:
if self.state >= FittingModuleState.OVERHEATED:
for effect in self.item.effects.values():
if effect.runTime == runTime and \
effect.isType("overheat") \
@@ -828,8 +787,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if effect.runTime == runTime and \
effect.activeByDefault and \
(effect.isType("offline") or
(effect.isType("passive") and self.state >= State.ONLINE) or
(effect.isType("active") and self.state >= State.ACTIVE)) \
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
and ((projected and effect.isType("projected")) or not projected) \
and ((gang and effect.isType("gang")) or not gang):
try:
@@ -904,7 +863,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def capUse(self):
capNeed = self.getModifiedItemAttr("capacitorNeed")
if capNeed and self.state >= State.ACTIVE:
if capNeed and self.state >= FittingModuleState.ACTIVE:
cycleTime = self.cycleTime
if cycleTime > 0:
capUsed = capNeed / (cycleTime / 1000.0)
@@ -916,10 +875,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getProposedState(mod, click, proposedState=None):
# todo: instead of passing in module, make this a instanced function.
pyfalog.debug("Get proposed state for module.")
if mod.slot == Slot.SUBSYSTEM or mod.isEmpty:
return State.ONLINE
if mod.slot == FittingSlot.SUBSYSTEM or mod.isEmpty:
return FittingModuleState.ONLINE
if mod.slot == Slot.SYSTEM:
if mod.slot == FittingSlot.SYSTEM:
transitionMap = ProjectedSystem
else:
transitionMap = ProjectedMap if mod.projected else LocalMap
@@ -929,9 +888,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if proposedState is not None:
state = proposedState
elif click == "right":
state = State.OVERHEATED
state = FittingModuleState.OVERHEATED
elif click == "ctrl":
state = State.OFFLINE
state = FittingModuleState.OFFLINE
else:
state = transitionMap[currState]
if not mod.isValidState(state):
@@ -956,6 +915,16 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return copy
def rebase(self, item):
state = self.state
charge = self.charge
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
self.state = state
if self.isValidCharge(charge):
self.charge = charge
for x in self.mutators.values():
Mutator(self, x.attribute, x.value)
def __repr__(self):
if self.item:
return "Module(ID={}, name={}) at {}".format(

View File

@@ -19,41 +19,56 @@
# ===============================================================================
import time
from enum import IntEnum, unique
from time import time
from logbook import Logger
VALIDITY = 24 * 60 * 60 # Price validity period, 24 hours
REREQUEST = 4 * 60 * 60 # Re-request delay for failed fetches, 4 hours
TIMEOUT = 15 * 60 # Network timeout delay for connection issues, 15 minutes
pyfalog = Logger(__name__)
@unique
class PriceStatus(IntEnum):
notFetched = 0
success = 1
fail = 2
notSupported = 3
initialized = 0
notSupported = 1
fetchSuccess = 2
fetchFail = 3
fetchTimeout = 4
class Price(object):
def __init__(self, typeID):
self.typeID = typeID
self.time = 0
self.__price = 0
self.status = PriceStatus.notFetched
self.price = 0
self.status = PriceStatus.initialized
@property
def isValid(self):
return self.time >= time.time()
@property
def price(self):
if self.status != PriceStatus.success:
return 0
def isValid(self, validityOverride=None):
# Always attempt to update prices which were just initialized, and prices
# of unsupported items (maybe we start supporting them at some point)
if self.status in (PriceStatus.initialized, PriceStatus.notSupported):
return False
elif self.status == PriceStatus.fetchSuccess:
return time() <= self.time + (validityOverride if validityOverride is not None else VALIDITY)
elif self.status == PriceStatus.fetchFail:
return time() <= self.time + REREQUEST
elif self.status == PriceStatus.fetchTimeout:
return time() <= self.time + TIMEOUT
else:
return self.__price or 0
return False
@price.setter
def price(self, price):
self.__price = price
def update(self, status, price=0):
# Keep old price if we failed to fetch new one
if status in (PriceStatus.fetchFail, PriceStatus.fetchTimeout):
price = self.price
elif status != PriceStatus.fetchSuccess:
price = 0
self.time = time()
self.price = price
self.status = status

View File

@@ -19,21 +19,14 @@
from collections import namedtuple
from enum import IntEnum, unique
from eos.const import SpoolType
from eos.utils.float import floatUnerr
SpoolOptions = namedtuple('SpoolOptions', ('spoolType', 'spoolAmount', 'force'))
@unique
class SpoolType(IntEnum):
SCALE = 0 # [0..1]
TIME = 1 # Expressed via time in seconds since spool up started
CYCLES = 2 # Expressed in amount of cycles since spool up started
def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAmount):
"""
Calculate damage multiplier increment based on passed parameters. Module cycle time