Merge remote-tracking branch 'origin/development' into release/v2.5.0

This commit is contained in:
blitzmann
2018-10-08 12:40:23 -04:00
181 changed files with 6606 additions and 2836 deletions

View File

@@ -17,8 +17,8 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, String, Integer, ForeignKey, Boolean, Table
from sqlalchemy.orm import relation, mapper, synonym, deferred
from sqlalchemy import Boolean, Column, Integer, String, Table
from sqlalchemy.orm import deferred, mapper, synonym
from eos.db import gamedata_meta
from eos.gamedata import Category

View File

@@ -17,15 +17,15 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table, Float
from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, String, Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
from sqlalchemy.orm import backref, deferred, mapper, relation, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection
from eos.db.gamedata.effect import typeeffects_table
from eos.db import gamedata_meta
from eos.gamedata import Attribute, Effect, Group, Item, MetaType, Traits, DynamicItemItem, DynamicItem
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table, dynamic_table
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table
from eos.db.gamedata.effect import typeeffects_table
from eos.gamedata import Attribute, DynamicItem, Effect, Group, Item, MetaType, Traits
items_table = Table("invtypes", gamedata_meta,
Column("typeID", Integer, primary_key=True),

View File

@@ -17,16 +17,16 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy.orm import join, exc, aliased, joinedload, subqueryload
from sqlalchemy.sql import and_, or_, select
from sqlalchemy.inspection import inspect
from sqlalchemy.orm import aliased, exc, join
from sqlalchemy.sql import and_, or_, select
import eos.config
from eos.db import gamedata_session
from eos.db.gamedata.metaGroup import metatypes_table, items_table
from eos.db.gamedata.group import groups_table
from eos.db.gamedata.metaGroup import items_table, metatypes_table
from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData, DynamicItem
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup
cache = {}
configVal = getattr(eos.config, "gamedataCache", None)
@@ -396,6 +396,21 @@ def getAbyssalTypes():
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
@cachedQuery(1, "itemID")
def getDynamicItem(itemID, eager=None):
try:
if isinstance(itemID, int):
if eager is None:
result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
else:
result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
else:
raise TypeError("Need integer as argument")
except exc.NoResultFound:
result = None
return result
def getRequiredFor(itemID, attrMapping):
Attribute1 = aliased(Attribute)
Attribute2 = aliased(Attribute)

View File

@@ -17,33 +17,32 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql import and_
from sqlalchemy.orm import relation, reconstructor, mapper, relationship
from sqlalchemy import ForeignKey, Column, Integer, String, Table, Boolean, DateTime
import datetime
from eos.db import saveddata_meta
from eos.db import saveddata_session
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql import and_
from eos.db import saveddata_meta, saveddata_session
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.fighter import fighters_table
from eos.db.saveddata.implant import fitImplants_table
from eos.db.saveddata.module import modules_table
from eos.effectHandlerHelpers import HandledModuleList, HandledImplantBoosterList, HandledProjectedModList, \
HandledDroneCargoList, HandledProjectedDroneList
from eos.saveddata.implant import Implant
from eos.saveddata.character import Character
from eos.saveddata.user import User
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit as es_Fit, ImplantLocation
from eos.saveddata.drone import Drone
from eos.effectHandlerHelpers import HandledDroneCargoList, HandledImplantBoosterList, HandledModuleList, HandledProjectedDroneList, HandledProjectedModList
from eos.saveddata.booster import Booster
from eos.saveddata.module import Module
from eos.saveddata.cargo import Cargo
from eos.saveddata.character import Character
from eos.saveddata.damagePattern import DamagePattern
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit as es_Fit
from eos.saveddata.implant import Implant
from eos.saveddata.module import Module
from eos.saveddata.targetResists import TargetResists
from eos.saveddata.user import User
fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key=True),
@@ -132,13 +131,13 @@ class CommandFit(object):
)
es_Fit._Fit__projectedFits = association_proxy(
es_Fit.projectedFitDict = association_proxy(
"victimOf", # look at the victimOf association...
"source_fit", # .. and return the source fits
creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit)
)
es_Fit._Fit__commandFits = association_proxy(
es_Fit.commandFitDict = association_proxy(
"boostedOf", # look at the boostedOf association...
"booster_fit", # .. and return the booster fit
creator=lambda boosterID, booster_fit: CommandFit(boosterID, booster_fit)

View File

@@ -17,10 +17,11 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime, Float
from sqlalchemy.orm import mapper
import datetime
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table
from sqlalchemy.orm import mapper
from eos.db import saveddata_meta
from eos.saveddata.mutator import Mutator

View File

@@ -18,6 +18,7 @@
# ===============================================================================
from logbook import Logger
from utils.deprecated import deprecated
pyfalog = Logger(__name__)
@@ -113,6 +114,7 @@ class HandledList(list):
class HandledModuleList(HandledList):
def append(self, mod):
emptyPosition = float("Inf")
for i in range(len(self)):
@@ -130,6 +132,9 @@ class HandledModuleList(HandledList):
self.remove(mod)
return
self.appendIgnoreEmpty(mod)
def appendIgnoreEmpty(self, mod):
mod.position = len(self)
HandledList.append(self, mod)
if mod.isInvalid:
@@ -163,6 +168,7 @@ class HandledModuleList(HandledList):
mod.position = index
self[index] = mod
@deprecated
def freeSlot(self, slot):
for i in range(len(self)):
mod = self[i]
@@ -195,14 +201,20 @@ class HandledImplantBoosterList(HandledList):
self.remove(thing)
return
self.makeRoom(thing)
HandledList.append(self, thing)
def makeRoom(self, thing):
# if needed, remove booster that was occupying slot
oldObj = next((m for m in self if m.slot == thing.slot), None)
if oldObj:
pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", thing.slot, oldObj.item.name, thing.item.name)
pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", thing.slot, oldObj.item.name,
thing.item.name)
itemID = oldObj.itemID
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
self.remove(oldObj)
HandledList.append(self, thing)
return itemID
return None
class HandledSsoCharacterList(list):
@@ -228,12 +240,7 @@ class HandledProjectedModList(HandledList):
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:
pyfalog.info("System effect occupied with {0}, replacing with {1}", oldEffect.item.name, proj.item.name)
self.remove(oldEffect)
self.makeRoom(proj)
HandledList.append(self, proj)
@@ -241,6 +248,16 @@ class HandledProjectedModList(HandledList):
if not proj.item.isType("projected") and not isSystemEffect:
self.remove(proj)
def makeRoom(self, proj):
# 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:
pyfalog.info("System effect occupied with {0}, replacing with {1}", oldEffect.item.name, proj.item.name)
self.remove(oldEffect)
return oldEffect.itemID
return None
class HandledProjectedDroneList(HandledDroneCargoList):
def append(self, proj):

View File

@@ -3,14 +3,14 @@
# Used by:
# Variations of module: Armor Command Burst I (2 of 2)
'''
"""
Some documentation:
When the fit is calculated, we gather up all the gang effects and stick them onto the fit. We don't run the actual
effect yet, only give the fit details so that it can run the effect at a later time. We need to do this so that we can
only run the strongest effect. When we are done, one of the last things that we do with the fit is to loop through those
bonuses and actually run the effect. To do this, we have a special argument passed into the effect handler that tells it
which warfareBuffID to run (shouldn't need this right now, but better safe than sorry)
'''
"""
type = "active", "gang"

View File

@@ -166,14 +166,14 @@ class Effect(EqBase):
t = t if isinstance(t, tuple) or t is None else (t,)
self.__type = t
except (ImportError) as e:
except ImportError as e:
# Effect probably doesn't exist, so create a dummy effect and flag it with a warning.
self.__handler = effectDummy
self.__runTime = "normal"
self.__activeByDefault = True
self.__type = None
pyfalog.debug("ImportError generating handler: {0}", e)
except (AttributeError) as e:
except AttributeError as e:
# Effect probably exists but there is an issue with it. Turn it into a dummy effect so we can continue, but flag it with an error.
self.__handler = effectDummy
self.__runTime = "normal"
@@ -476,6 +476,10 @@ class Item(EqBase):
def getAbyssalYypes(cls):
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
@property
def isCharge(self):
return self.category.name == "Charge"
def __repr__(self):
return "Item(ID={}, name={}) at {}".format(
self.ID, self.name, hex(id(self))
@@ -626,8 +630,8 @@ class Unit(EqBase):
def attributeIDCallback(v):
v = int(v)
if not v: # some attributes come through with a value of 0? See #1387
return "%d" % (v)
attribute = eos.db.getAttributeInfo(v, eager=("unit"))
return "%d" % v
attribute = eos.db.getAttributeInfo(v, eager="unit")
return "%s (%d)" % (attribute.name.capitalize(), v)
def TranslateValue(self, value):

View File

@@ -34,10 +34,10 @@ class ItemAttrShortcut(object):
return return_value or default
def getBaseAttrValue(self, key, default=0):
'''
"""
Gets base value in this order:
Mutated value > override value > attribute value
'''
"""
return_value = self.itemModifiedAttributes.getOriginal(key)
return return_value or default
@@ -382,7 +382,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
if resist:
afflictPenal += "r"
self.__afflict(attributeName, "%s*" % (afflictPenal), multiplier, multiplier != 1)
self.__afflict(attributeName, "%s*" % afflictPenal, multiplier, multiplier != 1)
def boost(self, attributeName, boostFactor, skill=None, *args, **kwargs):
"""Boost value by some percentage"""

View File

@@ -53,6 +53,20 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.build()
standardAttackActive = False
for ability in self.abilities:
if ability.effect.isImplemented and ability.effect.handlerName == 'fighterabilityattackm':
# Activate "standard attack" if available
ability.active = True
standardAttackActive = True
else:
# Activate all other abilities (Neut, Web, etc) except propmods if no standard attack is active
if ability.effect.isImplemented and \
standardAttackActive is False and \
ability.effect.handlerName != 'fighterabilitymicrowarpdrive' and \
ability.effect.handlerName != 'fighterabilityevasivemaneuvers':
ability.active = True
@reconstructor
def init(self):
"""Initialize a fighter from the database and validate"""

View File

@@ -35,7 +35,6 @@ from eos.saveddata.character import Character
from eos.saveddata.citadel import Citadel
from eos.saveddata.module import Module, State, Slot, Hardpoint
from logbook import Logger
pyfalog = Logger(__name__)
@@ -258,11 +257,11 @@ class Fit(object):
def projectedFits(self):
# only in extreme edge cases will the fit be invalid, but to be sure do
# not return them.
return [fit for fit in list(self.__projectedFits.values()) if not fit.isInvalid]
return [fit for fit in list(self.projectedFitDict.values()) if not fit.isInvalid]
@property
def commandFits(self):
return [fit for fit in list(self.__commandFits.values()) if not fit.isInvalid]
return [fit for fit in list(self.commandFitDict.values()) if not fit.isInvalid]
def getProjectionInfo(self, fitID):
return self.projectedOnto.get(fitID, None)
@@ -911,6 +910,9 @@ class Fit(object):
Fill this fit's module slots with enough dummy slots so that all slots are used.
This is mostly for making the life of gui's easier.
GUI's can call fill() and then stop caring about empty slots completely.
todo: want to get rid of using this from the gui/commands, and instead make it a more built-in feature within
recalc. Figure out a way to keep track of any changes to slot layout and call this automatically
"""
if self.ship is None:
return
@@ -1603,7 +1605,7 @@ class Fit(object):
eos.db.saveddata_session.refresh(fit)
for fit in self.commandFits:
copy_ship.__commandFits[fit.ID] = fit
copy_ship.commandFitDict[fit.ID] = fit
forceUpdateSavedata(fit)
copyCommandInfo = fit.getCommandInfo(copy_ship.ID)
originalCommandInfo = fit.getCommandInfo(self.ID)
@@ -1611,7 +1613,7 @@ class Fit(object):
forceUpdateSavedata(fit)
for fit in self.projectedFits:
copy_ship.__projectedFits[fit.ID] = fit
copy_ship.projectedFitDict[fit.ID] = fit
forceUpdateSavedata(fit)
copyProjectionInfo = fit.getProjectionInfo(copy_ship.ID)
originalProjectionInfo = fit.getProjectionInfo(self.ID)

View File

@@ -17,16 +17,15 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from logbook import Logger
from copy import deepcopy
from sqlalchemy.orm import validates, reconstructor
from math import floor
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.enum import Enum
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.citadel import Citadel
from eos.saveddata.mutator import Mutator
@@ -64,6 +63,30 @@ class Slot(Enum):
FS_HEAVY = 15
ProjectedMap = {
State.OVERHEATED: State.ACTIVE,
State.ACTIVE: State.OFFLINE,
State.OFFLINE: State.ACTIVE,
State.ONLINE: State.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
}
# For system effects. They should only ever be online or offline
ProjectedSystem = {
State.OFFLINE: State.ONLINE,
State.ONLINE: State.OFFLINE
}
class Hardpoint(Enum):
NONE = 0
MISSILE = 1
@@ -626,7 +649,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is not None:
g = eos.db.getGroup(int(itemChargeGroup), eager=("items.attributes"))
g = eos.db.getGroup(int(itemChargeGroup), eager="items.attributes")
if g is None:
continue
for singleItem in g.items:
@@ -832,6 +855,36 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
else:
return 0
@staticmethod
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 == Slot.SYSTEM:
transitionMap = ProjectedSystem
else:
transitionMap = ProjectedMap if mod.projected else LocalMap
currState = mod.state
if proposedState is not None:
state = proposedState
elif click == "right":
state = State.OVERHEATED
elif click == "ctrl":
state = State.OFFLINE
else:
state = transitionMap[currState]
if not mod.isValidState(state):
state = -1
if mod.isValidState(state):
return state
else:
return currState
def __deepcopy__(self, memo):
item = self.item
if item is None:

View File

@@ -131,7 +131,7 @@ class Ship(ItemAttrShortcut, HandledItem):
return None
items = []
g = eos.db.getGroup("Ship Modifiers", eager=("items.attributes"))
g = eos.db.getGroup("Ship Modifiers", eager="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()):