Merge branch 'singularity'

This commit is contained in:
DarkPhoenix
2020-06-15 14:42:13 +03:00
3269 changed files with 21518 additions and 1077 deletions

View File

@@ -0,0 +1,30 @@
"""
Migration 39
- Shield amplifier tiericide
"""
CONVERSIONS = {
1798: ( # 'Basic' EM Shield Amplifier
9562, # Supplemental EM Ward Amplifier
),
1804: ( # 'Basic' Explosive Shield Amplifier
9574, # Supplemental Explosive Deflection Amplifier
),
1802: ( # 'Basic' Kinetic Shield Amplifier
9570, # Supplemental Kinetic Deflection Amplifier
),
1800: ( # 'Basic' Thermal Shield Amplifier
9566, # Supplemental Thermal Dissipation Amplifier
)
}
def upgrade(saveddata_engine):
# Convert modules
for replacement_item, list in CONVERSIONS.items():
for retired_item in list:
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
(replacement_item, retired_item))
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
(replacement_item, retired_item))

View File

@@ -26,6 +26,8 @@ from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, re
class BaseEffect:
dealsDamage = False
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
pass
@@ -62,6 +64,7 @@ class Effect10(BaseEffect):
Modules from group: Energy Weapon (212 of 214)
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -96,7 +99,7 @@ class Effect21(BaseEffect):
Used by:
Modules from group: Shield Extender (36 of 36)
Modules from group: Shield Resistance Amplifier (88 of 88)
Modules from group: Shield Resistance Amplifier (84 of 84)
"""
type = 'passive'
@@ -169,6 +172,7 @@ class Effect34(BaseEffect):
Modules from group: Projectile Weapon (165 of 165)
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -189,6 +193,7 @@ class Effect38(BaseEffect):
Modules from group: Smart Bomb (118 of 118)
"""
dealsDamage = True
type = 'active'
@@ -555,6 +560,7 @@ class Effect101(BaseEffect):
Structure Modules named like: Standup Launcher (7 of 7)
"""
dealsDamage = True
type = 'active', 'projected'
@staticmethod
@@ -1633,7 +1639,7 @@ class Effect581(BaseEffect):
@staticmethod
def handler(fit, container, context, projectionRange, **kwargs):
level = container.level if 'skill' in context else 1
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Gunnery'),
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Gunnery') or mod.item.requiresSkill('Vorton Projector Operation'),
'cpu', container.getModifiedItemAttr('cpuNeedBonus') * level, **kwargs)
@@ -1742,7 +1748,7 @@ class Effect596(BaseEffect):
ammoInfluenceRange
Used by:
Items from category: Charge (590 of 955)
Items from category: Charge (608 of 973)
"""
type = 'passive'
@@ -2328,7 +2334,7 @@ class Effect804(BaseEffect):
ammoInfluenceCapNeed
Used by:
Items from category: Charge (496 of 955)
Items from category: Charge (514 of 973)
"""
type = 'passive'
@@ -4898,7 +4904,9 @@ class Effect1638(BaseEffect):
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Gunnery') or mod.item.requiresSkill('Missile Launcher Operation'),
lambda mod: (mod.item.requiresSkill('Gunnery') or
mod.item.requiresSkill('Missile Launcher Operation') or
mod.item.requiresSkill('Vorton Projector Operation')),
'power', skill.getModifiedItemAttr('powerNeedBonus') * skill.level, **kwargs)
@@ -6054,7 +6062,7 @@ class Effect2052(BaseEffect):
modifyShieldResonancePostPercent
Used by:
Modules from group: Shield Resistance Amplifier (88 of 88)
Modules from group: Shield Resistance Amplifier (84 of 84)
"""
type = 'passive'
@@ -7822,7 +7830,10 @@ class Effect2735(BaseEffect):
boosterArmorHpPenalty
Used by:
Implants named like: Booster (12 of 38)
Implants named like: Crash Booster (3 of 4)
Implants named like: Exile Booster (3 of 4)
Implants named like: Frentix Booster (3 of 4)
Implants named like: X Instinct Booster (3 of 4)
"""
attr = 'boosterArmorHPPenalty'
@@ -8441,6 +8452,7 @@ class Effect2847(BaseEffect):
Used by:
Implants named like: Drop Booster (4 of 4)
Implants named like: EDENCOM Vorton Booster (6 of 9)
Implants named like: Eifyr and Co. 'Gunslinger' Motion Prediction MR (6 of 6)
Implant: Antipharmakon Iokira
Implant: Ogdin's Eye Coordination Enhancer
@@ -9176,7 +9188,7 @@ class Effect3001(BaseEffect):
Used by:
Modules from group: Missile Launcher Torpedo (22 of 22)
Items from market group: Ship Equipment > Turrets & Launchers (429 of 889)
Items from market group: Ship Equipment > Turrets & Launchers (444 of 907)
Module: Interdiction Sphere Launcher I
"""
@@ -15118,6 +15130,7 @@ class Effect4489(BaseEffect):
Module: 'Judgment' Electromagnetic Doomsday
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -15134,6 +15147,7 @@ class Effect4490(BaseEffect):
Module: 'Oblivion' Kinetic Doomsday
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -15150,6 +15164,7 @@ class Effect4491(BaseEffect):
Module: 'Aurora Ominae' Thermal Doomsday
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -15166,6 +15181,7 @@ class Effect4492(BaseEffect):
Module: 'Gjallarhorn' Explosive Doomsday
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -27358,6 +27374,7 @@ class Effect6431(BaseEffect):
Fighters from group: Light Fighter (32 of 32)
"""
dealsDamage = True
displayName = 'Missile Attack'
hasCharges = True
prefix = 'fighterAbilityMissiles'
@@ -27638,6 +27655,7 @@ class Effect6465(BaseEffect):
Fighters from group: Heavy Fighter (34 of 34)
"""
dealsDamage = True
displayName = 'Turret Attack'
prefix = 'fighterAbilityAttackMissile'
type = 'active'
@@ -27680,6 +27698,7 @@ class Effect6472(BaseEffect):
Modules named like: Lance (4 of 4)
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -27696,6 +27715,7 @@ class Effect6473(BaseEffect):
Module: Bosonic Field Generator
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -27906,6 +27926,7 @@ class Effect6485(BaseEffect):
Fighters from group: Heavy Fighter (16 of 34)
"""
dealsDamage = True
displayName = 'Bomb'
hasCharges = True
prefix = 'fighterAbilityLaunchBomb'
@@ -31290,7 +31311,6 @@ class Effect6713(BaseEffect):
shipBonusSupercarrierM1BurstProjectorWebBonus
Used by:
Ship: Hel
Ship: Vendetta
"""
@@ -33959,6 +33979,7 @@ class Effect6995(BaseEffect):
Modules from group: Precursor Weapon (19 of 19)
"""
dealsDamage = True
type = 'active'
@staticmethod
@@ -36370,3 +36391,331 @@ class Effect8029(BaseEffect):
fit.modules.filteredItemForce(
lambda mod: mod.item.group.name == 'Capacitor Booster',
attr, ship.getModifiedItemAttr('shipBonusRole7'), **kwargs)
class Effect8034(BaseEffect):
"""
smallUpwellWeaponDmgBonusRequiredSkill
Used by:
Skill: Small Vorton Projector
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Vorton Projector'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8035(BaseEffect):
"""
mediumUpwellWeaponDmgBonusRequiredSkill
Used by:
Skill: Medium Vorton Projector
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Vorton Projector'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8036(BaseEffect):
"""
largeUpwellWeaponDmgBonusRequiredSkill
Used by:
Skill: Large Vorton Projector
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Large Vorton Projector'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8037(BaseEffect):
"""
ChainLightning
Used by:
Modules from group: Vorton Projector (15 of 15)
"""
type = 'active'
class Effect8039(BaseEffect):
"""
upwellSkillaoeVelocityaoeCloudSizeBonus
Used by:
Skill: Vorton Arc Guidance
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Vorton Projector Operation'),
'aoeVelocity', skill.getModifiedItemAttr('aoeVelocityBonus') * skill.level, **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Vorton Projector Operation'),
'aoeCloudSize', skill.getModifiedItemAttr('aoeCloudSizeBonus') * skill.level, **kwargs)
class Effect8041(BaseEffect):
"""
upwellSkillDamageMuliplierBonus
Used by:
Skill: Vorton Power Amplification
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Vorton Projector',
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8042(BaseEffect):
"""
upwellSkillSpeedBonus
Used by:
Skill: Vorton Projector Operation
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Vorton Projector Operation'),
'speed', skill.getModifiedItemAttr('turretSpeeBonus') * skill.level, **kwargs)
class Effect8044(BaseEffect):
"""
smallVortonProjectorSkillDmgBonus
Used by:
Skill: Small Vorton Specialization
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Vorton Specialization'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8045(BaseEffect):
"""
mediumVortonProjectorSkillDmgBonus
Used by:
Skill: Medium Vorton Specialization
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Vorton Specialization'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8046(BaseEffect):
"""
largeVortonProjectorSkillDmgBonus
Used by:
Skill: Large Vorton Specialization
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Large Vorton Specialization'),
'damageMultiplier', skill.getModifiedItemAttr('damageMultiplierBonus') * skill.level, **kwargs)
class Effect8047(BaseEffect):
"""
shipBonusUF1shieldResistance
Used by:
Ship: Skybreaker
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
for type in ('kinetic', 'thermal', 'explosive', 'em'):
fit.ship.boostItemAttr('shield%sDamageResonance' % type.capitalize(),
ship.getModifiedItemAttr('shipBonusUF1'),
skill='EDENCOM Frigate', **kwargs)
class Effect8048(BaseEffect):
"""
shipBonusUF2damage
Used by:
Ship: Skybreaker
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Small Vorton Projector'), 'damageMultiplier',
ship.getModifiedItemAttr('shipBonusUF2'), skill='EDENCOM Frigate', **kwargs)
class Effect8052(BaseEffect):
"""
shipBonusUC2ShieldResistance
Used by:
Ship: Stormbringer
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
for type in ('kinetic', 'thermal', 'explosive', 'em'):
fit.ship.boostItemAttr('shield%sDamageResonance' % type.capitalize(),
ship.getModifiedItemAttr('shipBonusUC2'),
skill='EDENCOM Cruiser', **kwargs)
class Effect8053(BaseEffect):
"""
shipBonusUC1maxRange
Used by:
Ship: Stormbringer
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Medium Vorton Projector'), 'maxRange',
ship.getModifiedItemAttr('shipBonusUC1'), skill='EDENCOM Cruiser', **kwargs)
class Effect8054(BaseEffect):
"""
shipBonusUB1upwellDamage
Used by:
Ship: Thunderchild
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Large Vorton Projector'), 'damageMultiplier',
ship.getModifiedItemAttr('shipBonusUB1'), skill='EDENCOM Battleship', **kwargs)
class Effect8056(BaseEffect):
"""
shipBonusUB2upwellROF
Used by:
Ship: Thunderchild
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Large Vorton Projector'), 'speed',
ship.getModifiedItemAttr('shipBonusUB2'), skill='EDENCOM Battleship', **kwargs)
class Effect8057(BaseEffect):
"""
vortonWeaponDamageSpeedMultiply
Used by:
Modules from group: Vorton Projector Upgrade (3 of 3)
"""
type = 'passive'
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
fit.modules.filteredItemMultiply(lambda mod: mod.item.group.name == 'Vorton Projector',
'damageMultiplier', module.getModifiedItemAttr('damageMultiplier'),
stackingPenalties=True, **kwargs)
fit.modules.filteredItemMultiply(lambda mod: mod.item.group.name == 'Vorton Projector',
'speed', module.getModifiedItemAttr('speedMultiplier'),
stackingPenalties=True, **kwargs)
class Effect8062(BaseEffect):
"""
ammoAOEvelocityMultiplier
Used by:
Charges from group: Advanced Condenser Pack (6 of 6)
"""
type = 'passive'
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
module.multiplyItemAttr('aoeVelocity', module.getModifiedChargeAttr('aoeVelocityBonus') or 0, **kwargs)
class Effect8064(BaseEffect):
"""
vortonProjectorOptimalRangeBonus
Used by:
Implants named like: EDENCOM Vorton Booster RA (3 of 3)
"""
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Vorton Projector Operation'),
'maxRange', implant.getModifiedItemAttr('rangeSkillBonus'), **kwargs)
class Effect8065(BaseEffect):
"""
vortonProjectorSkillRangeBonus
Used by:
Skill: Vorton Arc Extension
"""
type = 'passive'
@staticmethod
def handler(fit, skill, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Vorton Projector Operation'),
'maxRange', skill.getModifiedItemAttr('rangeSkillBonus') * skill.level, **kwargs)

View File

@@ -146,6 +146,12 @@ class Effect(EqBase):
return self.__effectDef is not None
@property
def dealsDamage(self):
if not self.__generated:
self.__generateHandler()
return self.__dealsDamage
def isType(self, type):
"""
Check if this effect is of the passed type
@@ -167,6 +173,7 @@ class Effect(EqBase):
self.__handler = getattr(effectDef, "handler", eos.effects.BaseEffect.handler)
self.__runTime = getattr(effectDef, "runTime", "normal")
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
self.__dealsDamage = effectDef.dealsDamage
effectType = getattr(effectDef, "type", None)
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
self.__type = effectType
@@ -175,6 +182,7 @@ class Effect(EqBase):
self.__handler = eos.effects.DummyEffect.handler
self.__runTime = "normal"
self.__activeByDefault = True
self.__dealsDamage = False
self.__type = None
pyfalog.debug("ImportError generating handler: {0}", e)
except AttributeError as e:
@@ -182,6 +190,7 @@ class Effect(EqBase):
self.__handler = eos.effects.DummyEffect.handler
self.__runTime = "normal"
self.__activeByDefault = True
self.__dealsDamage = False
self.__type = None
pyfalog.error("AttributeError generating handler: {0}", e)
except (KeyboardInterrupt, SystemExit):
@@ -190,6 +199,7 @@ class Effect(EqBase):
self.__handler = eos.effects.DummyEffect.handler
self.__runTime = "normal"
self.__activeByDefault = True
self.__dealsDamage = False
self.__type = None
pyfalog.critical("Exception generating handler:")
pyfalog.critical(e)

View File

@@ -364,3 +364,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.item.groupID in fitDroneGroupLimits:
return True
return False
def canDealDamage(self, ignoreState=False):
if self.item is None:
return False
for effect in self.item.effects.values():
if effect.dealsDamage and (ignoreState or self.amountActive > 0):
return True
return False

View File

@@ -441,3 +441,15 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return False
return True
def canDealDamage(self, ignoreState=False, ignoreAbilityState=False):
if self.item is None:
return False
if not self.active and not ignoreState:
return False
for ability in self.abilities:
if not ability.active and not ignoreAbilityState:
continue
if ability.effect.dealsDamage:
return True
return False

View File

@@ -461,6 +461,20 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return True
return False
def canDealDamage(self, ignoreState=False):
if self.isEmpty:
return False
for effect in self.item.effects.values():
if effect.dealsDamage and (
ignoreState or
effect.isType('offline') or
(effect.isType('passive') and self.state >= FittingModuleState.ONLINE) or
(effect.isType('active') and self.state >= FittingModuleState.ACTIVE) or
(effect.isType('overheat') and self.state >= FittingModuleState.OVERHEATED)
):
return True
return False
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
return {0: DmgTypes(0, 0, 0, 0)}

View File

@@ -37,7 +37,14 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
for mod in src.item.activeModulesIter():
if not mod.isDealingDamage():
continue
if mod.hardpoint == FittingHardpoint.TURRET:
if "ChainLightning" in mod.item.effects:
if inLockRange:
applicationMap[mod] = getVortonMult(
mod=mod,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
elif mod.hardpoint == FittingHardpoint.TURRET:
if inLockRange:
applicationMap[mod] = getTurretMult(
mod=mod,
@@ -56,7 +63,6 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
applicationMap[mod] = getLauncherMult(
mod=mod,
src=src,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
@@ -151,7 +157,21 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
return mult
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
def getVortonMult(mod, distance, tgtSpeed, tgtSigRadius):
rangeFactor = calculateRangeFactor(
mod.getModifiedItemAttr('maxRange'),
0,
distance)
applicationFactor = _calcMissileFactor(
atkEr=mod.getModifiedItemAttr('aoeCloudSize'),
atkEv=mod.getModifiedItemAttr('aoeVelocity'),
atkDrf=mod.getModifiedItemAttr('aoeDamageReductionFactor'),
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
return rangeFactor * applicationFactor
def getLauncherMult(mod, distance, tgtSpeed, tgtSigRadius):
missileMaxRangeData = mod.missileMaxRangeData
if missileMaxRangeData is None:
return 0

View File

@@ -27,7 +27,7 @@ import gui.globalEvents as GE
import gui.mainFrame
from graphs.data.base import FitGraph
from graphs.events import RESIST_MODE_CHANGED
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from service.const import GraphCacheCleanupReason
from service.settings import GraphSettings

View File

@@ -22,7 +22,7 @@
import wx
class AuxiliaryFrame(wx.Frame):
class AuxiliaryMixin:
_instance = None
@@ -55,14 +55,26 @@ class AuxiliaryFrame(wx.Frame):
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
@classmethod
def openOne(cls, parent, *args, **kwargs):
def openOne(cls, parent, *args, forceReopen=False, **kwargs):
"""If window is open and alive - raise it, open otherwise"""
if not cls._instance:
if not cls._instance or forceReopen:
if cls._instance:
cls._instance.Close()
frame = cls(parent, *args, **kwargs)
cls._instance = frame
frame.Show()
else:
cls._instance.Raise()
return cls._instance
def OnSuppressedAction(self, event):
return
class AuxiliaryFrame(AuxiliaryMixin, wx.Frame):
pass
class AuxiliaryDialog(AuxiliaryMixin, wx.Dialog):
pass

View File

@@ -36,6 +36,11 @@ import gui.fitCommands as cmd
from gui.fitCommands.helpers import droneStackLimit
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
class DroneViewDrop(wx.DropTarget):
def __init__(self, dropFn, *args, **kwargs):
super(DroneViewDrop, self).__init__(*args, **kwargs)
@@ -186,17 +191,13 @@ class DroneView(Display):
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
def droneKey(self, drone):
@staticmethod
def droneKey(drone):
sMkt = Market.getInstance()
groupName = sMkt.getMarketGroupByItem(drone.item).name
return (self.DRONE_ORDER.index(groupName),
drone.item.name)
return (DRONE_ORDER.index(groupName), drone.item.name)
def fitChanged(self, event):
event.Skip()

View File

@@ -34,6 +34,9 @@ from service.fit import Fit
from service.market import Market
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
class FighterViewDrop(wx.DropTarget):
def __init__(self, dropFn, *args, **kwargs):
super(FighterViewDrop, self).__init__(*args, **kwargs)
@@ -250,11 +253,10 @@ class FighterDisplay(d.Display):
def _merge(src, dst):
return
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
def fighterKey(self, fighter):
@staticmethod
def fighterKey(fighter):
groupName = Market.getInstance().getGroupByItem(fighter.item).name
orderPos = self.FIGHTER_ORDER.index(groupName)
orderPos = FIGHTER_ORDER.index(groupName)
# Sort support fighters by name, ignore their abilities
if groupName == 'Support Fighter':
abilityEffectIDs = ()

View File

@@ -119,12 +119,12 @@ class ProjectedView(d.Display):
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
elif data[0] == 'market':
itemID = int(data[1])
category = Market.getInstance().getItem(itemID, eager=('group.category')).category.name
if category == 'Module':
item = Market.getInstance().getItem(itemID)
if item.isModule:
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=itemID))
elif category == 'Drone':
elif item.isDrone:
self.mainFrame.command.Submit(cmd.GuiAddProjectedDroneCommand(fitID=fitID, itemID=itemID))
elif category == 'Fighter':
elif item.isFighter:
self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=itemID))
def kbEvent(self, event):

View File

@@ -6,6 +6,7 @@ from gui.builtinContextMenus import fitAddCurrentlyOpen
from gui.builtinContextMenus import envEffectAdd
from gui.builtinContextMenus import commandFitAdd
from gui.builtinContextMenus.targetProfile import adder
from gui.builtinContextMenus import graphFitAmmoPicker
# Often-used item manipulations
from gui.builtinContextMenus import shipModeChange
from gui.builtinContextMenus import moduleAmmoChange

View File

@@ -0,0 +1,241 @@
# noinspection PyPackageRequirements
import wx
import gui.mainFrame
from gui.auxWindow import AuxiliaryDialog
from gui.contextMenu import ContextMenuSingle
from service.ammo import Ammo
from service.market import Market
class GraphFitAmmoPicker(ContextMenuSingle):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if srcContext != 'graphFitList':
return False
if mainItem is None or not mainItem.isFit:
return False
if callingWindow.graphFrame.getView().internalName != 'dmgStatsGraph':
return False
return True
def getText(self, callingWindow, itmContext, mainItem):
return 'Plot with Different Ammo...'
def activate(self, callingWindow, fullContext, mainItem, i):
AmmoPickerFrame.openOne(callingWindow, mainItem.item, forceReopen=True)
# GraphFitAmmoPicker.register()
class AmmoPickerFrame(AuxiliaryDialog):
def __init__(self, parent, fit):
super().__init__(parent, title='Choose Different Ammo', style=wx.DEFAULT_DIALOG_STYLE, resizeable=True)
padding = 5
mainSizer = wx.BoxSizer(wx.VERTICAL)
contents = AmmoPickerContents(self, fit)
mainSizer.Add(contents, 1, wx.EXPAND | wx.ALL, padding)
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
if buttonSizer:
mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, padding)
self.SetSizer(mainSizer)
self.Layout()
contW, contH = contents.GetVirtualSize()
bestW = contW + padding * 2
bestH = contH + padding * 2
if buttonSizer:
# Yeah right... whatever
buttW, buttH = buttonSizer.GetSize()
bestW = max(bestW, buttW + padding * 2)
bestH += buttH + padding * 2
bestW = min(1000, bestW)
bestH = min(700, bestH)
self.SetSize(bestW, bestH)
self.SetMinSize(wx.Size(int(bestW * 0.7), int(bestH * 0.7)))
self.CenterOnParent()
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
def kbEvent(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
self.Close()
return
event.Skip()
class AmmoPickerContents(wx.ScrolledCanvas):
indent = 15
def __init__(self, parent, fit):
wx.ScrolledCanvas.__init__(self, parent)
self.SetScrollRate(0, 15)
mods = self.getMods(fit)
drones = self.getDrones(fit)
fighters = self.getFighters(fit)
self.rbLabelMap = {}
self.rbCheckboxMap = {}
mainSizer = wx.BoxSizer(wx.VERTICAL)
moduleSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(moduleSizer, 0, wx.ALL, 0)
self.droneSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.droneSizer, 0, wx.ALL, 0)
fighterSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(fighterSizer, 0, wx.ALL, 0)
firstRadio = True
for modInfo, modAmmo in mods:
text = '\n'.join('{}x {}'.format(amount, item.name) for item, amount in modInfo)
modRb = self.addRadioButton(moduleSizer, text, firstRadio)
firstRadio = False
# Get actual module, as ammo getters need it
mod = next((m for m in fit.modules if m.itemID == next(iter(modInfo))[0].ID), None)
_, ammoTree = Ammo.getInstance().getModuleStructuredAmmo(mod)
if len(ammoTree) == 1:
for ammoCatName, ammos in ammoTree.items():
for ammo in ammos:
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
else:
for ammoCatName, ammos in ammoTree.items():
if len(ammos) == 1:
ammo = next(iter(ammos))
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
else:
self.addLabel(moduleSizer, '{}:'.format(ammoCatName), modRb, indentLvl=1)
for ammo in ammos:
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=2)
if drones:
droneRb = self.addRadioButton(self.droneSizer, 'Drones', firstRadio)
from gui.builtinAdditionPanes.droneView import DroneView
for drone in sorted(drones, key=DroneView.droneKey):
self.addCheckbox(self.droneSizer, '{}x {}'.format(drone.amount, drone.item.name), droneRb, indentLvl=1)
addBtn = wx.Button(self, wx.ID_ANY, '+', style=wx.BU_EXACTFIT)
addBtn.Bind(wx.EVT_BUTTON, self.OnDroneGroupAdd)
mainSizer.Add(addBtn, 0, wx.LEFT, self.indent)
if fighters:
fighterRb = self.addRadioButton(fighterSizer, 'Fighters', firstRadio)
from gui.builtinAdditionPanes.fighterView import FighterDisplay
for fighter in sorted(fighters, key=FighterDisplay.fighterKey):
self.addCheckbox(fighterSizer, '{}x {}'.format(fighter.amount, fighter.item.name), fighterRb, indentLvl=1)
self.SetSizer(mainSizer)
self.refreshStatus()
def addRadioButton(self, sizer, text, firstRadio=False):
if firstRadio:
rb = wx.RadioButton(self, wx.ID_ANY, text, style=wx.RB_GROUP)
rb.SetValue(True)
else:
rb = wx.RadioButton(self, wx.ID_ANY, text)
rb.SetValue(False)
rb.Bind(wx.EVT_RADIOBUTTON, self.rbSelected)
sizer.Add(rb, 0, wx.EXPAND | wx.ALL, 0)
return rb
def addCheckbox(self, sizer, text, currentRb, indentLvl=0):
cb = wx.CheckBox(self, -1, text)
sizer.Add(cb, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
if currentRb is not None:
self.rbCheckboxMap.setdefault(currentRb, []).append(cb)
def addLabel(self, sizer, text, currentRb, indentLvl=0):
text = text[0].capitalize() + text[1:]
label = wx.StaticText(self, wx.ID_ANY, text)
sizer.Add(label, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
if currentRb is not None:
self.rbLabelMap.setdefault(currentRb, []).append(label)
def getMods(self, fit):
sMkt = Market.getInstance()
sAmmo = Ammo.getInstance()
loadableChargesCache = {}
# Modules, format: {frozenset(ammo): {item: count}}
modsPrelim = {}
if fit is not None:
for mod in fit.modules:
if not mod.canDealDamage():
continue
typeID = mod.item.ID
if typeID not in loadableChargesCache:
loadableChargesCache[typeID] = sAmmo.getModuleFlatAmmo(mod)
charges = loadableChargesCache[typeID]
# We're not interested in modules which contain no charges
if charges:
data = modsPrelim.setdefault(frozenset(charges), {})
if mod.item not in data:
data[mod.item] = 0
data[mod.item] += 1
# Format: [([(item, count), ...], frozenset(ammo)), ...]
modsFinal = []
for charges, itemCounts in modsPrelim.items():
modsFinal.append((
# Sort items within group
sorted(itemCounts.items(), key=lambda i: sMkt.itemSort(i[0], reverseMktGrp=True), reverse=True),
charges))
# Sort item groups
modsFinal.sort(key=lambda i: sMkt.itemSort(i[0][0][0], reverseMktGrp=True), reverse=True)
return modsFinal
def getDrones(self, fit):
drones = []
if fit is not None:
for drone in fit.drones:
if drone.item is None:
continue
# Drones are our "ammo", so we want to pick even those which are inactive
if drone.canDealDamage(ignoreState=True):
drones.append(drone)
continue
if {'remoteWebifierEntity', 'remoteTargetPaintEntity'}.intersection(drone.item.effects):
drones.append(drone)
continue
return drones
def getFighters(self, fit):
fighters = []
if fit is not None:
for fighter in fit.fighters:
if fighter.item is None:
continue
# Fighters are our "ammo" as well
if fighter.canDealDamage(ignoreState=True):
fighters.append(fighter)
continue
for ability in fighter.abilities:
if not ability.active:
continue
if ability.effect.name == 'fighterAbilityStasisWebifier':
fighters.append(fighter)
break
return fighters
def OnDroneGroupAdd(self, event):
event.Skip()
sizer = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText()
self.droneSizer.Add(sizer, 0, wx.EXPAND | wx.LEFT, self.indent)
def refreshStatus(self):
for map in (self.rbLabelMap, self.rbCheckboxMap):
for rb, items in map.items():
for item in items:
item.Enable(rb.GetValue())
def rbSelected(self, event):
event.Skip()
self.refreshStatus()

View File

@@ -3,33 +3,28 @@ import wx
import gui.fitCommands as cmd
import gui.mainFrame
from eos.const import FittingHardpoint
from eos.saveddata.module import Module
from gui.bitmap_loader import BitmapLoader
from gui.contextMenu import ContextMenuCombined
from gui.fitCommands.helpers import getSimilarModPositions
from service.ammo import Ammo
from service.fit import Fit
from service.market import Market
class ChangeModuleAmmo(ContextMenuCombined):
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
# Format: {type ID: set(loadable, charges)}
self.loadableCharges = {}
self.loadableChargesCache = {}
def display(self, callingWindow, srcContext, mainItem, selection):
if srcContext not in ("fittingModule", "projectedModule"):
if srcContext not in ('fittingModule', 'projectedModule'):
return False
if self.mainFrame.getActiveFit() is None:
return False
self.mainCharges = self.getChargesForMod(mainItem)
self.mainCharges = self._getAmmo(mainItem)
if not self.mainCharges:
return False
@@ -39,186 +34,81 @@ class ChangeModuleAmmo(ContextMenuCombined):
return True
def getText(self, callingWindow, itmContext, mainItem, selection):
return "Charge"
return 'Charge'
def getChargesForMod(self, mod):
sMkt = Market.getInstance()
if mod is None or mod.isEmpty:
def _getAmmo(self, mod):
if mod.itemID is None:
return set()
typeID = mod.item.ID
if typeID in self.loadableCharges:
return self.loadableCharges[typeID]
chargeSet = self.loadableCharges.setdefault(typeID, set())
# Do not try to grab it for modes which can also be passed as part of selection
if isinstance(mod, Module):
for charge in mod.getValidCharges():
if sMkt.getPublicityByItem(charge):
chargeSet.add(charge)
return chargeSet
if mod.itemID not in self.loadableChargesCache:
self.loadableChargesCache[mod.itemID] = Ammo.getInstance().getModuleFlatAmmo(mod)
return self.loadableChargesCache[mod.itemID]
def turretSorter(self, charge):
damage = 0
range_ = (self.module.item.getAttribute("maxRange")) * \
(charge.getAttribute("weaponRangeMultiplier") or 1)
falloff = (self.module.item.getAttribute("falloff") or 0) * \
(charge.getAttribute("fallofMultiplier") or 1)
for type_ in self.DAMAGE_TYPES:
d = charge.getAttribute("%sDamage" % type_)
if d > 0:
damage += d
# Take optimal and falloff as range factor
rangeFactor = range_ + falloff
return - rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
def missileSorter(self, charge):
# Get charge damage type and total damage
chargeDamageType, totalDamage = self.damageInfo(charge)
# Find its position in sort list
position = self.MISSILE_ORDER.index(chargeDamageType)
return position, totalDamage, charge.name
def damageInfo(self, charge):
# Set up data storage for missile damage stuff
damageMap = {}
totalDamage = 0
# Fill them with the data about charge
for damageType in self.DAMAGE_TYPES:
currentDamage = charge.getAttribute("{0}Damage".format(damageType)) or 0
damageMap[damageType] = currentDamage
totalDamage += currentDamage
# Detect type of ammo
chargeDamageType = None
for damageType in damageMap:
# If all damage belongs to certain type purely, set appropriate
# ammoType
if damageMap[damageType] == totalDamage:
chargeDamageType = damageType
break
# Else consider ammo as mixed damage
if chargeDamageType is None:
chargeDamageType = "mixed"
return chargeDamageType, totalDamage
@staticmethod
def numericConverter(string):
return int(string) if string.isdigit() else string
def nameSorter(self, charge):
parts = charge.name.split(" ")
return list(map(self.numericConverter, parts))
def addCharge(self, menu, charge):
def _addCharge(self, menu, charge):
id_ = ContextMenuCombined.nextID()
name = charge.name if charge is not None else "Empty"
self.chargeIds[id_] = charge
name = charge.name if charge is not None else 'Empty'
self.chargeEventMap[id_] = charge
item = wx.MenuItem(menu, id_, name)
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
item.charge = charge
if charge is not None and charge.iconID is not None:
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
bitmap = BitmapLoader.getBitmap(charge.iconID, 'icons')
if bitmap is not None:
item.SetBitmap(bitmap)
return item
@staticmethod
def addSeperator(m, text):
def _addSeparator(m, text):
id_ = ContextMenuCombined.nextID()
m.Append(id_, '%s' % text)
m.Enable(id_, False)
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
msw = True if "wxMSW" in wx.PlatformInfo else False
m = wx.Menu()
self.chargeIds = {}
hardpoint = self.module.hardpoint
moduleName = self.module.item.name
# Make sure we do not consider mining turrets as combat turrets
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
self.addSeperator(m, "Long Range")
items = []
range_ = None
nameBase = None
sub = None
chargesSorted = sorted(self.mainCharges, key=self.turretSorter)
for charge in chargesSorted:
if "civilian" in charge.name.lower():
continue
currBase = charge.name.rsplit()[-2:]
currRange = charge.getAttribute("weaponRangeMultiplier")
if nameBase is None or range_ != currRange or nameBase != currBase:
if sub is not None:
self.addSeperator(sub, "More Damage")
sub = None
base = charge
nameBase = currBase
range_ = currRange
item = self.addCharge(rootMenu if msw else m, charge)
items.append(item)
msw = True if 'wxMSW' in wx.PlatformInfo else False
menu = wx.Menu()
self.chargeEventMap = {}
modType, chargeDict = Ammo.getInstance().getModuleStructuredAmmo(self.module, ammo=self.mainCharges)
if modType == 'ddTurret':
self._addSeparator(menu, 'Long Range')
menuItems = []
for charges in chargeDict.values():
if len(charges) == 1:
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
else:
if sub is None and item and base:
sub = wx.Menu()
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
self.addSeperator(sub, "Less Damage")
item.SetSubMenu(sub)
sub.Append(self.addCharge(rootMenu if msw else sub, base))
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
if sub is not None:
self.addSeperator(sub, "More Damage")
for item in items:
m.Append(item)
self.addSeperator(m, "Short Range")
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
type_ = None
sub = None
defender = None
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
for charge in chargesSorted:
currType = self.damageInfo(charge)[0]
if currType != type_ or type_ is None:
if sub is not None:
self.addSeperator(sub, "More Damage")
type_ = currType
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
if bitmap is not None:
item.SetBitmap(bitmap)
sub = wx.Menu()
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
self.addSeperator(sub, "Less Damage")
item.SetSubMenu(sub)
m.Append(item)
if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"):
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
else:
defender = charge
if defender is not None:
m.Append(self.addCharge(rootMenu if msw else m, defender))
if sub is not None:
self.addSeperator(sub, "More Damage")
else:
chargesSorted = sorted(self.mainCharges, key=self.nameSorter)
for charge in chargesSorted:
m.Append(self.addCharge(rootMenu if msw else m, charge))
m.Append(self.addCharge(rootMenu if msw else m, None))
return m
baseCharge = charges[0]
menuItem = self._addCharge(rootMenu if msw else menu, baseCharge)
menuItems.append(menuItem)
subMenu = wx.Menu()
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
menuItem.SetSubMenu(subMenu)
self._addSeparator(subMenu, 'Less Damage')
for charge in charges:
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
self._addSeparator(subMenu, 'More Damage')
for menuItem in menuItems:
menu.Append(menuItem)
self._addSeparator(menu, 'Short Range')
elif modType == 'ddMissile':
menuItems = []
for chargeCatName, charges in chargeDict.items():
menuItem = wx.MenuItem(menu, wx.ID_ANY, chargeCatName.capitalize())
menuItems.append(menuItem)
subMenu = wx.Menu()
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
menuItem.SetSubMenu(subMenu)
self._addSeparator(subMenu, 'Less Damage')
for charge in charges:
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
self._addSeparator(subMenu, 'More Damage')
for menuItem in menuItems:
menu.Append(menuItem)
elif modType == 'general':
for charge in chargeDict['general']:
menu.Append(self._addCharge(rootMenu if msw else menu, charge))
menu.Append(self._addCharge(rootMenu if msw else menu, None))
return menu
def handleAmmoSwitch(self, event):
charge = self.chargeIds.get(event.Id, False)
charge = self.chargeEventMap.get(event.Id, False)
if charge is False:
event.Skip()
return
@@ -254,7 +144,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
positions = []
for position, mod in enumerate(modContainer):
if mod in self.selection:
modCharges = self.getChargesForMod(mod)
modCharges = self._getAmmo(mod)
if modCharges.issubset(self.mainCharges):
positions.append(position)
self.mainFrame.command.Submit(command(

View File

@@ -35,7 +35,7 @@ class ItemDescription(wx.Panel):
self.Layout()
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
self.description.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.description.Bind(wx.EVT_KEY_UP, self.onKeyUp)
self.popupMenu = wx.Menu()
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
@@ -50,7 +50,7 @@ class ItemDescription(wx.Panel):
if selectedMenuItem == 1: # Copy was chosen
self.copySelectionToClipboard()
def onKeyDown(self, event):
def onKeyUp(self, event):
keyCode = event.GetKeyCode()
# Ctrl + C
if keyCode == 67 and event.ControlDown():

View File

@@ -14,7 +14,7 @@ class ItemTraits(wx.Panel):
self.traits.SetPage(item.traits.traitText)
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
self.traits.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.traits.Bind(wx.EVT_KEY_UP, self.onKeyUp)
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
self.Layout()
@@ -32,7 +32,7 @@ class ItemTraits(wx.Panel):
if selectedMenuItem == 1: # Copy was chosen
self.copySelectionToClipboard()
def onKeyDown(self, event):
def onKeyUp(self, event):
keyCode = event.GetKeyCode()
# Ctrl + C
if keyCode == 67 and event.ControlDown():

View File

@@ -203,22 +203,6 @@ class ItemView(Display):
self.setToggles()
self.filterItemStore()
def itemSort(self, item):
sMkt = self.sMkt
catname = sMkt.getCategoryByItem(item).name
try:
mktgrpid = sMkt.getMarketGroupByItem(item).ID
except AttributeError:
mktgrpid = -1
pyfalog.warning("unable to find market group for {}".format(item.name))
parentname = sMkt.getParentItemByItem(item).name
# Get position of market group
metagrpid = sMkt.getMetaGroupIdByItem(item)
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
metalvl = item.metaLevel or 0
return catname, mktgrpid, parentname, metatab, metalvl, item.name
def contextMenu(self, event):
clickedPos = self.getRowByAbs(event.Position)
self.ensureSelection(clickedPos)
@@ -241,7 +225,7 @@ class ItemView(Display):
self.unselectAll()
# Perform sorting, using item's meta levels besides other stuff
if self.marketBrowser.mode != 'recent':
items.sort(key=self.itemSort)
items.sort(key=self.sMkt.itemSort)
# Mark current item list as active
self.active = items
# Show them
@@ -251,12 +235,10 @@ class ItemView(Display):
if len(items) > 1:
# Re-sort stuff
if self.marketBrowser.mode != 'recent':
items.sort(key=self.itemSort)
items.sort(key=self.sMkt.itemSort)
for i, item in enumerate(items[:9]):
# set shortcut info for first 9 modules
item.marketShortcut = i + 1
Display.refresh(self, items)
def columnBackground(self, colItem, item):

View File

@@ -60,7 +60,8 @@ class MarketTree(wx.TreeCtrl):
# If market should have items but it doesn't, do not show it
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
continue
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
icon = sMkt.getIconByMarketGroup(childMktGrp)
iconId = -1 if icon is None else self.addImage(icon)
try:
childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID)
except (KeyboardInterrupt, SystemExit):

View File

@@ -86,7 +86,7 @@ class PFSearchBox(wx.Window):
def OnKeyPress(self, event):
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
HandleCtrlBackspace(self.EditBox)
HandleCtrlBackspace(self.EditBox)
else:
event.Skip()

View File

@@ -123,6 +123,15 @@ class Miscellanea(ViewColumn):
text = ' | '.join(i[0] for i in info)
tooltip = ' and '.join(i[1] for i in info).capitalize()
return text, tooltip
elif itemGroup == "Vorton Projector":
cloudSize = stuff.getModifiedItemAttr("aoeCloudSize")
aoeVelocity = stuff.getModifiedItemAttr("aoeVelocity")
if not cloudSize or not aoeVelocity:
return "", None
text = "{0}{1} | {2}{3}".format(formatAmount(cloudSize, 3, 0, 3), "m",
formatAmount(aoeVelocity, 3, 0, 3), "m/s")
tooltip = "Explosion radius and explosion velocity"
return text, tooltip
elif itemCategory == "Subsystem":
slots = ("hi", "med", "low")
info = []
@@ -133,7 +142,7 @@ class Miscellanea(ViewColumn):
return "+ " + ", ".join(info), "Slot Modifiers"
elif (
itemGroup in ("Energy Neutralizer", "Structure Energy Neutralizer") or
(itemGroup == "Structure Burst Projector" and "doomsdayAOENeut" in item.effects)
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOENeut" in item.effects)
):
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
cycleParams = stuff.getCycleParameters()
@@ -182,7 +191,7 @@ class Miscellanea(ViewColumn):
return text, tooltip
elif (
itemGroup in ("Stasis Web", "Stasis Webifying Drone", "Structure Stasis Webifier") or
(itemGroup == "Structure Burst Projector" and "doomsdayAOEWeb" in item.effects)
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEWeb" in item.effects)
):
speedFactor = stuff.getModifiedItemAttr("speedFactor")
if not speedFactor:
@@ -193,7 +202,7 @@ class Miscellanea(ViewColumn):
elif (
itemGroup == "Target Painter" or
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectTargetPainter" in item.effects) or
(itemGroup == "Structure Burst Projector" and "doomsdayAOEPaint" in item.effects)
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEPaint" in item.effects)
):
sigRadBonus = stuff.getModifiedItemAttr("signatureRadiusBonus")
if not sigRadBonus:
@@ -204,7 +213,7 @@ class Miscellanea(ViewColumn):
elif (
itemGroup == "Sensor Dampener" or
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectRemoteSensorDampener" in item.effects) or
(itemGroup == "Structure Burst Projector" and "doomsdayAOEDamp" in item.effects)
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEDamp" in item.effects)
):
lockRangeBonus = stuff.getModifiedItemAttr("maxTargetRangeBonus")
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
@@ -226,7 +235,7 @@ class Miscellanea(ViewColumn):
return text, tooltip
elif (
itemGroup in ("Weapon Disruptor", "Structure Disruption Battery") or
(itemGroup == "Structure Burst Projector" and "doomsdayAOETrack" in item.effects)
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOETrack" in item.effects)
):
# Weapon disruption now covers both tracking and guidance (missile) disruptors
# First get the attributes for tracking disruptors
@@ -279,7 +288,8 @@ class Miscellanea(ViewColumn):
"Heat Sink",
"Ballistic Control system",
"Structure Weapon Upgrade",
"Entropic Radiation Sink"
"Entropic Radiation Sink",
"Vorton Projector Upgrade"
):
attrMap = {
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
@@ -287,7 +297,8 @@ class Miscellanea(ViewColumn):
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
"Ballistic Control system": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon")}
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon"),
"Vorton Projector Upgrade": ("damageMultiplier", "speedMultiplier", "Vorton projector")}
dmgAttr, rofAttr, weaponName = attrMap[itemGroup]
dmg = stuff.getModifiedItemAttr(dmgAttr)
rof = stuff.getModifiedItemAttr(rofAttr)
@@ -311,8 +322,8 @@ class Miscellanea(ViewColumn):
tooltip = "Drone DPS boost"
return text, tooltip
elif (
itemGroup in ("ECM", "Burst Jammer", "Burst Projectors", "Structure ECM Battery") or
(itemGroup == "Structure Burst Projector" and "doomsdayAOEECM" in item.effects)
itemGroup in ("ECM", "Burst Jammer", "Structure ECM Battery") or
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEECM" in item.effects)
):
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthBonus")
ladar = stuff.getModifiedItemAttr("scanLadarStrengthBonus")

View File

@@ -166,7 +166,7 @@ class FittingView(d.Display):
self.hoveredRow = None
self.hoveredColumn = None
self.Bind(wx.EVT_KEY_DOWN, self.kbEvent)
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
self.Bind(wx.EVT_LEFT_DOWN, self.click)
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
self.Bind(wx.EVT_MIDDLE_DOWN, self.click)

View File

@@ -34,7 +34,7 @@ from wx.lib.agw.floatspin import FloatSpin
import config
import gui.globalEvents as GE
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
from gui.builtinViews.implantEditor import BaseImplantEditorView

View File

@@ -26,7 +26,7 @@ import wx
from logbook import Logger
import eos.db
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.builtinShipBrowser.events import FitSelected

View File

@@ -26,7 +26,7 @@ import wx
from logbook import Logger
import config
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from service.prereqsCheck import version_block

View File

@@ -9,7 +9,7 @@ import config
import gui.globalEvents as GE
from eos.db import getItem
from eos.saveddata.cargo import Cargo
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.display import Display
from gui.characterEditor import APIView
from service.character import Character

View File

@@ -23,7 +23,7 @@ import wx
import config
import gui.mainFrame
from eos.saveddata.module import Module
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
from gui.builtinItemStatsViews.itemAttributes import ItemParams

View File

@@ -21,7 +21,7 @@
import wx
from logbook import Logger
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
from gui.utils.clipboard import fromClipboard, toClipboard

View File

@@ -10,7 +10,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
import gui.display as d
import gui.globalEvents as GE
from eos.db.gamedata.queries import getAttributeInfo, getItem
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.marketBrowser import SearchBox
from service.fit import Fit
@@ -213,19 +213,7 @@ class ItemView(d.Display):
def itemSort(self, item):
sMkt = Market.getInstance()
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
catname = sMkt.getCategoryByItem(item).name
try:
mktgrpid = sMkt.getMarketGroupByItem(item).ID
except AttributeError:
mktgrpid = -1
pyfalog.warning("unable to find market group for {}".format(item.name))
parentname = sMkt.getParentItemByItem(item).name
# Get position of market group
metagrpid = sMkt.getMetaGroupIdByItem(item)
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
metalvl = item.metaLevel or 0
return not isFittable, catname, mktgrpid, parentname, metatab, metalvl, item.name
return (not isFittable, *sMkt.itemSort(item))
def populateSearch(self, itemIDs):
items = Market.getItems(itemIDs)

View File

@@ -21,7 +21,7 @@
import wx
from logbook import Logger
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
from gui.builtinViews.implantEditor import BaseImplantEditorView
from gui.utils.clipboard import fromClipboard, toClipboard

View File

@@ -27,7 +27,7 @@ from logbook import Logger
import gui.globalEvents as GE
import gui.mainFrame
from gui.auxFrame import AuxiliaryFrame
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
from gui.utils.clipboard import fromClipboard, toClipboard

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 B

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 837 B

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 931 B

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 B

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 978 B

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1002 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 1002 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 B

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

After

Width:  |  Height:  |  Size: 863 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 B

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 931 B

Some files were not shown because too many files have changed in this diff Show More