Compare commits

...

74 Commits

Author SHA1 Message Date
DarkPhoenix
c85c735f9a Bump version 2019-05-12 18:04:40 +03:00
DarkPhoenix
c65b582497 Remove dot after "vs" 2019-05-12 18:01:44 +03:00
DarkPhoenix
7f2ac83e17 Add context menu which removes fits from graphs 2019-05-12 17:42:56 +03:00
DarkPhoenix
5ef2a40d1e Delete fits by pressing del key in graphs window 2019-05-12 17:32:17 +03:00
DarkPhoenix
5b52da737a Do not trigger esc when modifier keys are pressed 2019-05-12 17:08:41 +03:00
DarkPhoenix
51e8713cd6 Add warp time graph 2019-05-12 17:04:11 +03:00
DarkPhoenix
c9b60f2c65 Add distance vs time graph 2019-05-12 16:26:02 +03:00
DarkPhoenix
d777999af4 Add speed vs time graph 2019-05-12 15:46:50 +03:00
DarkPhoenix
74444d56c4 Add shield amount vs time graph 2019-05-12 15:30:41 +03:00
DarkPhoenix
a433c9638a Add cap-time graph 2019-05-12 14:59:19 +03:00
DarkPhoenix
672141cffc Add cap amount graph 2019-05-12 14:40:57 +03:00
DarkPhoenix
ac132cbb92 Add shield recharge graph 2019-05-12 14:32:57 +03:00
DarkPhoenix
d9535b08b1 Rename graphs 2019-05-12 12:28:47 +03:00
DarkPhoenix
d93544b3bc Fix DPS over time graph 2019-05-12 06:46:28 +03:00
DarkPhoenix
2320c3cb57 Make sure to include all Y-values 2019-05-12 05:54:03 +03:00
DarkPhoenix
bd5710c676 Calculate graph data only once 2019-05-12 05:30:49 +03:00
DarkPhoenix
49f1412d91 Fix bug with drones 2019-05-12 04:56:59 +03:00
DarkPhoenix
54eea7d702 Rework damage over time graph to show actual damage dealt per volley 2019-05-12 04:44:27 +03:00
DarkPhoenix
e26bcb2e5e Move DoT logic into volley parameter fetcher 2019-05-12 03:15:10 +03:00
DarkPhoenix
7305c0a017 Add optional parameter to cycle parameter getters 2019-05-12 02:55:42 +03:00
DarkPhoenix
7d37b9e0e0 Implement volley parameters for modules 2019-05-12 02:49:21 +03:00
DarkPhoenix
87f28db730 Implement volley parameters for drones and fighters 2019-05-12 02:38:36 +03:00
DarkPhoenix
56d9a8b626 Rework fighter calculations to use cycle parameters 2019-05-12 02:18:44 +03:00
DarkPhoenix
cb8f76c582 Implement module cycle parameters logic for modules 2019-05-11 17:34:01 +03:00
DarkPhoenix
af0b7b92c7 Implement cycle parameters for drones 2019-05-11 16:15:03 +03:00
DarkPhoenix
9418b7a709 Calculate 0 range data points 2019-05-11 14:53:59 +03:00
DarkPhoenix
47c34f2186 Sort implant sets by name 2019-05-10 16:22:57 +03:00
DarkPhoenix
2ca418c287 Add damage over time graph 2019-05-10 03:18:25 +03:00
DarkPhoenix
775e69305c Make graphs switchable 2019-05-10 03:06:00 +03:00
DarkPhoenix
0f1cbb4234 Add support for dps over time graph 2019-05-10 02:46:50 +03:00
DarkPhoenix
306710a314 Bump version 2019-05-09 20:12:37 +03:00
DarkPhoenix
776a4ee977 Update effects 2019-05-09 20:11:31 +03:00
DarkPhoenix
9dccfd756a Update database to 1503340 2019-05-09 19:49:13 +03:00
DarkPhoenix
15281ee6ce Merge branch 'master' into singularity 2019-05-07 10:27:01 +03:00
DarkPhoenix
9a0dd6c521 Copy empty spots on fit as well when making copy 2019-05-06 20:21:28 +03:00
DarkPhoenix
a570f291ae Merge branch 'master' into singularity 2019-05-06 15:32:52 +03:00
DarkPhoenix
cde7fdcaba Do not activate MJD and MJFG upon fitting 2019-05-05 05:06:15 +03:00
DarkPhoenix
e4780bc8ba Merge branch 'master' into singularity 2019-05-04 12:18:50 +03:00
DarkPhoenix
4d35e5aee1 Comment out some conflicting jargon entries 2019-05-04 12:18:07 +03:00
DarkPhoenix
f7b705b9e2 Add focused void bomb jargon entry 2019-05-04 12:16:04 +03:00
DarkPhoenix
48f44cdb0c Merge branch 'master' into singularity 2019-05-04 02:44:12 +03:00
DarkPhoenix
013a2264c0 Show tooltip only if there's something to show 2019-05-04 02:43:33 +03:00
DarkPhoenix
8222686dda Enable tooltips for export options 2019-05-04 02:43:02 +03:00
DarkPhoenix
7f2121e98d Add possibility to export formatted DNA 2019-05-04 02:38:19 +03:00
DarkPhoenix
4b6c881dca Re-enable DNA export 2019-05-04 02:26:09 +03:00
DarkPhoenix
5f9bf4a861 Bump version 2019-05-03 22:41:10 +03:00
DarkPhoenix
154db5df0b Merge branch 'master' into singularity 2019-05-03 22:29:26 +03:00
DarkPhoenix
321b939d3a Commit mutated data before destroying window 2019-05-03 22:28:29 +03:00
DarkPhoenix
95a1d669f5 Invasion effects affect module mining cycle only 2019-05-03 19:01:10 +03:00
DarkPhoenix
9e3c9bd056 Merge branch 'master' into singularity 2019-05-03 16:30:38 +03:00
DarkPhoenix
bb9b3780ae Fix context submenu activation 2019-05-03 16:22:18 +03:00
DarkPhoenix
4c976d9f35 Scroll mutated item stats with mousewheel over spincontrol not just on GTK 2019-05-03 14:56:59 +03:00
DarkPhoenix
52a1314803 Merge branch 'master' into singularity 2019-05-03 04:25:27 +03:00
DarkPhoenix
a5475eb244 Do not activate ADC on fit import as well 2019-05-03 04:24:59 +03:00
DarkPhoenix
ba0a5db72f Remove stacking penalties from duration attribute
It cannot be stacking penalized
2019-05-03 04:04:58 +03:00
DarkPhoenix
2bac4a954f Merge branch 'master' into singularity 2019-05-03 04:04:11 +03:00
DarkPhoenix
e9f3453b04 Fix industrial core stacking penalties 2019-05-03 03:57:53 +03:00
DarkPhoenix
c950592b5b If corresponding option is enabled, re-enable all meta buttons on every search change rather than just on search beginning 2019-05-03 03:41:09 +03:00
DarkPhoenix
1cd42669a0 Merge branch 'master' into singularity 2019-05-03 03:37:48 +03:00
DarkPhoenix
2b24f14122 Change interface between commands and item containers once again 2019-05-03 03:36:47 +03:00
DarkPhoenix
4932b685e1 Merge branch 'master' into singularity 2019-05-03 02:31:22 +03:00
DarkPhoenix
cfffa1d99d Do not crash when facing unknown module in saved fit 2019-05-03 02:27:03 +03:00
DarkPhoenix
44a7e53b9e Bump version 2019-05-02 19:54:29 +03:00
DarkPhoenix
b35bdd4e33 Add triglavian invasion effects 2019-05-02 19:50:40 +03:00
DarkPhoenix
7f52f6fe44 Show spoolup time for MJFGs 2019-05-02 19:13:21 +03:00
DarkPhoenix
34e49da0c1 Add renders of new ships 2019-05-02 18:52:53 +03:00
DarkPhoenix
5132698974 Add draugur effect 2019-05-02 18:24:47 +03:00
DarkPhoenix
832cebcaaf Add ikitursa effects 2019-05-02 18:17:23 +03:00
DarkPhoenix
4eaccd1eed Change spoolup context menu to show 20-ish amount of items 2019-05-02 18:03:31 +03:00
DarkPhoenix
5245f289a5 Add nergal effects 2019-05-02 17:16:38 +03:00
DarkPhoenix
672aed44f2 Add trig frigate resist effects 2019-05-02 16:40:38 +03:00
DarkPhoenix
8c890cf9a5 Rerun effectUsedBy script 2019-05-02 16:30:28 +03:00
DarkPhoenix
8f9a95db93 Do not crash when icons are missing, and add ADC mutaplasmid override 2019-05-02 16:10:22 +03:00
DarkPhoenix
5a056e6d47 Update database to 1498791 2019-05-02 15:54:04 +03:00
84 changed files with 2233 additions and 496 deletions

View File

@@ -17,10 +17,8 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from logbook import Logger
from eos.exception import HandledListActionError
from utils.deprecated import deprecated
from logbook import Logger
pyfalog = Logger(__name__)
@@ -137,27 +135,23 @@ class HandledModuleList(HandledList):
self.__toModule(emptyPosition, mod)
if mod.isInvalid:
self.__toDummy(mod.position)
raise HandledListActionError(mod)
return
self.appendIgnoreEmpty(mod)
else:
self.appendIgnoreEmpty(mod)
def appendIgnoreEmpty(self, mod):
mod.position = len(self)
HandledList.append(self, mod)
if mod.isInvalid:
self.remove(mod)
raise HandledListActionError(mod)
def replace(self, idx, mod):
try:
oldMod = self[idx]
except IndexError:
raise HandledListActionError(mod)
return
self.__toModule(idx, mod)
if mod.isInvalid:
self.__toModule(idx, oldMod)
raise HandledListActionError(mod)
def replaceRackPosition(self, rackPosition, mod):
listPositions = []
@@ -182,7 +176,6 @@ class HandledModuleList(HandledList):
self.__toDummy(modListPosition)
else:
self.__toModule(modListPosition, oldMod)
raise HandledListActionError(mod)
def insert(self, idx, mod):
mod.position = idx
@@ -193,8 +186,6 @@ class HandledModuleList(HandledList):
HandledList.insert(self, idx, mod)
if mod.isInvalid:
self.remove(mod)
raise HandledListActionError(mod)
def remove(self, mod):
HandledList.remove(self, mod)
@@ -236,13 +227,11 @@ class HandledDroneCargoList(HandledList):
HandledList.append(self, thing)
if thing.isInvalid:
self.remove(thing)
raise HandledListActionError(thing)
def insert(self, idx, thing):
HandledList.insert(self, idx, thing)
if thing.isInvalid:
self.remove(thing)
raise HandledListActionError(thing)
class HandledImplantList(HandledList):
@@ -251,22 +240,22 @@ class HandledImplantList(HandledList):
if implant.isInvalid:
HandledList.append(self, implant)
self.remove(implant)
raise HandledListActionError(implant)
return
if self.__slotCheck(implant):
HandledList.append(self, implant)
self.remove(implant)
raise HandledListActionError(implant)
return
HandledList.append(self, implant)
def insert(self, idx, implant):
if implant.isInvalid:
HandledList.insert(self, idx, implant)
self.remove(implant)
raise HandledListActionError(implant)
return
if self.__slotCheck(implant):
HandledList.insert(self, idx, implant)
self.remove(implant)
raise HandledListActionError(implant)
return
HandledList.insert(self, idx, implant)
def makeRoom(self, implant):
@@ -292,22 +281,22 @@ class HandledBoosterList(HandledList):
if booster.isInvalid:
HandledList.append(self, booster)
self.remove(booster)
raise HandledListActionError(booster)
return
if self.__slotCheck(booster):
HandledList.append(self, booster)
self.remove(booster)
raise HandledListActionError(booster)
return
HandledList.append(self, booster)
def insert(self, idx, booster):
if booster.isInvalid:
HandledList.insert(self, idx, booster)
self.remove(booster)
raise HandledListActionError(booster)
return
if self.__slotCheck(booster):
HandledList.insert(self, idx, booster)
self.remove(booster)
raise HandledListActionError(booster)
return
HandledList.insert(self, idx, booster)
def makeRoom(self, booster):
@@ -346,16 +335,12 @@ class HandledProjectedModList(HandledList):
# rows and relationships in database are removed as well
HandledList.append(self, proj)
self.remove(proj)
raise HandledListActionError(proj)
return
proj.projected = True
HandledList.append(self, proj)
# Remove non-projectable modules
if not proj.item.isType("projected") and not proj.isExclusiveSystemEffect:
self.remove(proj)
raise HandledListActionError(proj)
def insert(self, idx, proj):
if proj.isInvalid:
@@ -363,16 +348,12 @@ class HandledProjectedModList(HandledList):
# rows and relationships in database are removed as well
HandledList.insert(self, idx, proj)
self.remove(proj)
raise HandledListActionError(proj)
return
proj.projected = True
HandledList.insert(self, idx, proj)
# Remove non-projectable modules
if not proj.item.isType("projected") and not proj.isExclusiveSystemEffect:
self.remove(proj)
raise HandledListActionError(proj)
@property
def currentSystemEffect(self):
@@ -399,24 +380,18 @@ class HandledProjectedDroneList(HandledDroneCargoList):
def append(self, proj):
proj.projected = True
HandledList.append(self, proj)
# Remove invalid or non-projectable drones
if proj.isInvalid or not proj.item.isType("projected"):
self.remove(proj)
proj.projected = False
raise HandledListActionError(proj)
return True
def insert(self, idx, proj):
proj.projected = True
HandledList.insert(self, idx, proj)
# Remove invalid or non-projectable drones
if proj.isInvalid or not proj.item.isType("projected"):
self.remove(proj)
proj.projected = False
raise HandledListActionError(proj)
return True
class HandledItem(object):

View File

@@ -6739,7 +6739,7 @@ class Effect2302(BaseEffect):
damageControl
Used by:
Modules from group: Damage Control (22 of 27)
Modules from group: Damage Control (24 of 29)
"""
type = 'passive'
@@ -9050,7 +9050,7 @@ class Effect3001(BaseEffect):
Used by:
Modules from group: Missile Launcher Torpedo (22 of 22)
Items from market group: Ship Equipment > Turrets & Bays (429 of 883)
Items from market group: Ship Equipment > Turrets & Bays (429 of 888)
Module: Interdiction Sphere Launcher I
"""
@@ -9113,7 +9113,7 @@ class Effect3025(BaseEffect):
Used by:
Modules from group: Energy Weapon (101 of 214)
Modules from group: Hybrid Weapon (105 of 221)
Modules from group: Precursor Weapon (15 of 15)
Modules from group: Precursor Weapon (18 of 18)
Modules from group: Projectile Weapon (99 of 165)
"""
@@ -15232,96 +15232,57 @@ class Effect4575(BaseEffect):
# Remote Shield Repper Bonuses
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
'duration',
src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'),
)
'duration', src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
'maxRange',
src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
stackingPenalties=True
)
'maxRange', src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
stackingPenalties=True)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
'capacitorNeed',
src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus')
)
'capacitorNeed', src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
'falloffEffectiveness',
src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
stackingPenalties=True
)
'falloffEffectiveness', src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
stackingPenalties=True)
# Local Shield Repper Bonuses
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Operation'),
'duration',
src.getModifiedItemAttr('industrialCoreLocalLogisticsDurationBonus'),
)
'duration', src.getModifiedItemAttr('industrialCoreLocalLogisticsDurationBonus'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Operation'),
'shieldBonus',
src.getModifiedItemAttr('industrialCoreLocalLogisticsAmountBonus'),
stackingPenalties=True
)
'shieldBonus', src.getModifiedItemAttr('industrialCoreLocalLogisticsAmountBonus'),
stackingPenalties=True)
# Mining Burst Bonuses
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
'warfareBuff1Value',
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
)
'warfareBuff1Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
'warfareBuff2Value',
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
)
'warfareBuff2Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
'warfareBuff3Value',
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
)
'warfareBuff3Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
'warfareBuff4Value',
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
)
'warfareBuff4Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
# Command Burst Range Bonus
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'),
'maxRange',
src.getModifiedItemAttr('industrialCoreBonusCommandBurstRange'),
stackingPenalties=True
)
'maxRange', src.getModifiedItemAttr('industrialCoreBonusCommandBurstRange'),
stackingPenalties=True)
# Drone Bonuses
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Ice Harvesting Drone Operation'),
'duration',
src.getModifiedItemAttr('industrialCoreBonusDroneIceHarvesting'),
)
'duration', src.getModifiedItemAttr('industrialCoreBonusDroneIceHarvesting'))
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Mining Drone Operation'),
'miningAmount',
src.getModifiedItemAttr('industrialCoreBonusDroneMining'),
)
'miningAmount', src.getModifiedItemAttr('industrialCoreBonusDroneMining'))
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'maxVelocity',
src.getModifiedItemAttr('industrialCoreBonusDroneVelocity'),
)
'maxVelocity', src.getModifiedItemAttr('industrialCoreBonusDroneVelocity'),
stackingPenalties=True)
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'damageMultiplier', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
stackingPenalties=True)
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'shieldCapacity', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'armorHP', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'hp', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'damageMultiplier',
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
stackingPenalties=True
)
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'shieldCapacity',
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
)
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'armorHP',
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
)
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'hp',
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
)
# Todo: remote impedance (no reps, etc)
# Remote impedance (no reps, etc)
fit.ship.increaseItemAttr('warpScrambleStatus', src.getModifiedItemAttr('siegeModeWarpStatus'))
fit.ship.boostItemAttr('remoteRepairImpedance', src.getModifiedItemAttr('remoteRepairImpedanceBonus'))
fit.ship.increaseItemAttr('disallowTethering', src.getModifiedItemAttr('disallowTethering'))
@@ -16285,9 +16246,9 @@ class Effect4902(BaseEffect):
MWDSignatureRadiusRoleBonus
Used by:
Ships from group: Assault Frigate (8 of 12)
Ships from group: Command Destroyer (4 of 4)
Ships from group: Heavy Assault Cruiser (8 of 11)
Ships from group: Assault Frigate (9 of 13)
Ships from group: Command Destroyer (5 of 5)
Ships from group: Heavy Assault Cruiser (9 of 12)
"""
type = 'passive'
@@ -25230,7 +25191,7 @@ class Effect6214(BaseEffect):
roleBonusCDLinksPGReduction
Used by:
Ships from group: Command Destroyer (4 of 4)
Ships from group: Command Destroyer (5 of 5)
Ship: Porpoise
"""
@@ -25812,8 +25773,7 @@ class Effect6315(BaseEffect):
eliteBonusCommandDestroyerSkirmish1
Used by:
Ship: Bifrost
Ship: Magus
Ships from group: Command Destroyer (3 of 5)
"""
type = 'passive'
@@ -25862,7 +25822,7 @@ class Effect6317(BaseEffect):
eliteBonusCommandDestroyerMJFGspool2
Used by:
Ships from group: Command Destroyer (4 of 4)
Ships from group: Command Destroyer (5 of 5)
"""
type = 'passive'
@@ -26135,8 +26095,7 @@ class Effect6334(BaseEffect):
eliteBonusCommandDestroyerInfo1
Used by:
Ship: Pontifex
Ship: Stork
Ships from group: Command Destroyer (3 of 5)
"""
type = 'passive'
@@ -32253,7 +32212,7 @@ class Effect6845(BaseEffect):
shipBonusCommandDestroyerRole1DefenderBonus
Used by:
Ships from group: Command Destroyer (4 of 4)
Ships from group: Command Destroyer (4 of 5)
"""
type = 'passive'
@@ -33802,10 +33761,10 @@ class Effect6994(BaseEffect):
class Effect6995(BaseEffect):
"""
targetABCAttack
targetDisintegratorAttack
Used by:
Modules from group: Precursor Weapon (15 of 15)
Modules from group: Precursor Weapon (18 of 18)
"""
type = 'active'
@@ -33969,6 +33928,7 @@ class Effect7012(BaseEffect):
Used by:
Variations of module: Assault Damage Control I (5 of 5)
Module: Abyssal Assault Damage Control
"""
runTime = 'early'
@@ -34853,7 +34813,7 @@ class Effect7077(BaseEffect):
disintegratorWeaponDamageMultiply
Used by:
Modules from group: Entropic Radiation Sink (4 of 4)
Modules from group: Entropic Radiation Sink (6 of 6)
"""
type = 'passive'
@@ -34870,7 +34830,7 @@ class Effect7078(BaseEffect):
disintegratorWeaponSpeedMultiply
Used by:
Modules from group: Entropic Radiation Sink (4 of 4)
Modules from group: Entropic Radiation Sink (6 of 6)
"""
type = 'passive'
@@ -34919,8 +34879,8 @@ class Effect7085(BaseEffect):
shipbonusPCTDamagePC1
Used by:
Variations of ship: Vedmak (2 of 2)
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -34936,8 +34896,8 @@ class Effect7086(BaseEffect):
shipbonusPCTTrackingPC2
Used by:
Variations of ship: Vedmak (2 of 2)
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -34953,7 +34913,6 @@ class Effect7087(BaseEffect):
shipbonusPCTOptimalPF2
Used by:
Ship: Damavik
Ship: Hydra
"""
@@ -34970,7 +34929,7 @@ class Effect7088(BaseEffect):
shipbonusPCTDamagePF1
Used by:
Ship: Damavik
Variations of ship: Damavik (2 of 2)
Ship: Hydra
"""
@@ -35002,13 +34961,13 @@ class Effect7092(BaseEffect):
shipBonusRemoteRepCapNeedRoleBonus2
Used by:
Ship: Damavik
Variations of ship: Damavik (2 of 2)
Variations of ship: Kikimora (2 of 2)
Variations of ship: Vedmak (2 of 2)
Ship: Drekavac
Ship: Hydra
Ship: Kikimora
Ship: Leshak
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -35024,14 +34983,14 @@ class Effect7093(BaseEffect):
shipBonusSmartbombCapNeedRoleBonus2
Used by:
Variations of ship: Damavik (2 of 2)
Variations of ship: Kikimora (2 of 2)
Variations of ship: Rodiva (2 of 2)
Ship: Damavik
Variations of ship: Vedmak (2 of 2)
Ship: Drekavac
Ship: Hydra
Ship: Kikimora
Ship: Leshak
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -35047,13 +35006,13 @@ class Effect7094(BaseEffect):
shipBonusRemoteRepMaxRangeRoleBonus1
Used by:
Ship: Damavik
Variations of ship: Damavik (2 of 2)
Variations of ship: Kikimora (2 of 2)
Variations of ship: Vedmak (2 of 2)
Ship: Drekavac
Ship: Hydra
Ship: Kikimora
Ship: Leshak
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -35085,7 +35044,7 @@ class Effect7111(BaseEffect):
systemSmallPrecursorTurretDamage
Used by:
Celestials named like: Wolf Rayet Effect Beacon Class (5 of 6)
Celestials named like: Wolf Rayet Effect Beacon Class (6 of 6)
"""
runTime = 'early'
@@ -35103,13 +35062,13 @@ class Effect7112(BaseEffect):
shipBonusNeutCapNeedRoleBonus2
Used by:
Ship: Damavik
Variations of ship: Damavik (2 of 2)
Variations of ship: Kikimora (2 of 2)
Variations of ship: Vedmak (2 of 2)
Ship: Drekavac
Ship: Hydra
Ship: Kikimora
Ship: Leshak
Ship: Tiamat
Ship: Vedmak
"""
type = 'passive'
@@ -35215,7 +35174,7 @@ class Effect7154(BaseEffect):
shipBonusPD1DisintegratorDamage
Used by:
Ship: Kikimora
Variations of ship: Kikimora (2 of 2)
"""
type = 'passive'
@@ -35265,7 +35224,7 @@ class Effect7157(BaseEffect):
shipBonusPD2DisintegratorMaxRange
Used by:
Ship: Kikimora
Variations of ship: Kikimora (2 of 2)
"""
type = 'passive'
@@ -35571,8 +35530,9 @@ class Effect7183(BaseEffect):
@staticmethod
def handler(fit, src, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Warp Scrambler', 'maxRange',
src.getModifiedItemAttr('warpScrambleRangeBonus'), stackingPenalties=False)
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Warp Scrambler',
'maxRange', src.getModifiedItemAttr('warpScrambleRangeBonus'),
stackingPenalties=False)
class Effect7184(BaseEffect):
@@ -35621,3 +35581,232 @@ class Effect7186(BaseEffect):
def handler(fit, ship, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Medium Drone Operation'),
'armorHP', ship.getModifiedItemAttr('shipBonusRole8'))
class Effect7193(BaseEffect):
"""
systemMiningCycleTimeBonus
Used by:
Celestials named like: Invasion Effects (3 of 3)
"""
runTime = 'early'
type = ('projected', 'passive')
@staticmethod
def handler(fit, beacon, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining'),
'duration', beacon.getModifiedItemAttr('miningDurationMultiplier'))
class Effect7202(BaseEffect):
"""
systemDroneSpeedBonusPercent
Used by:
Celestials named like: Invasion Effects (3 of 3)
"""
runTime = 'early'
type = ('projected', 'passive')
@staticmethod
def handler(fit, beacon, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'maxVelocity', beacon.getModifiedItemAttr('droneMaxVelocityBonus'),
stackingPenalties=True)
class Effect7203(BaseEffect):
"""
systemDroneDamageBonusPercent
Used by:
Celestials named like: Invasion Effects (3 of 3)
"""
runTime = 'early'
type = ('projected', 'passive')
@staticmethod
def handler(fit, beacon, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
'damageMultiplier', beacon.getModifiedItemAttr('droneDamageBonus'),
stackingPenalties=True)
class Effect7204(BaseEffect):
"""
shipArmorEMResistancePF2
Used by:
Variations of ship: Damavik (2 of 2)
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.ship.boostItemAttr('armorEmDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
class Effect7205(BaseEffect):
"""
shipArmorKinResistancePF2
Used by:
Variations of ship: Damavik (2 of 2)
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.ship.boostItemAttr('armorKineticDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
class Effect7206(BaseEffect):
"""
shipArmorThermResistancePF2
Used by:
Variations of ship: Damavik (2 of 2)
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.ship.boostItemAttr('armorThermalDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
class Effect7207(BaseEffect):
"""
shipArmorExpResistancePF2
Used by:
Variations of ship: Damavik (2 of 2)
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.ship.boostItemAttr('armorExplosiveDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
class Effect7209(BaseEffect):
"""
shipPCTOptimalBonusEliteGunship2
Used by:
Ship: Nergal
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Precursor Weapon'),
'maxRange', ship.getModifiedItemAttr('eliteBonusGunship2'),
skill='Assault Frigates')
class Effect7210(BaseEffect):
"""
shipBonusCommandDestroyerRole2DefenderBonus
Used by:
Ship: Draugur
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Defender Missiles'),
'moduleReactivationDelay', ship.getModifiedItemAttr('shipBonusRole2'))
class Effect7211(BaseEffect):
"""
shipDmgMultiMaxEliteHeavyGunship1
Used by:
Ship: Ikitursa
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Precursor Weapon'),
'damageMultiplierBonusMax', ship.getModifiedItemAttr('eliteBonusHeavyGunship1'),
skill='Heavy Assault Cruisers')
class Effect7216(BaseEffect):
"""
shipDmgMultiMaxEliteGunship1
Used by:
Ship: Nergal
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Precursor Weapon'),
'damageMultiplierBonusMax', ship.getModifiedItemAttr('eliteBonusGunship1'),
skill='Assault Frigates')
class Effect7223(BaseEffect):
"""
systemAgilityBonusPercentItem
Used by:
Celestials named like: Invasion Effects (3 of 3)
"""
runTime = 'early'
type = ('projected', 'passive')
@staticmethod
def handler(fit, beacon, context):
fit.ship.boostItemAttr('agility', beacon.getModifiedItemAttr('agilityBonus'), stackingPenalties=True)
class Effect7227(BaseEffect):
"""
systemHullHPBonusPercentItem
Used by:
Celestials named like: Invasion Effects (3 of 3)
"""
runTime = 'early'
type = ('projected', 'passive')
@staticmethod
def handler(fit, beacon, context):
fit.ship.boostItemAttr('hp', beacon.getModifiedItemAttr('hullHpBonus'))
class Effect7228(BaseEffect):
"""
shipMediumPrecursorWeaponOptimalEliteHeavyGunship2
Used by:
Ship: Ikitursa
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Precursor Weapon'),
'maxRange', ship.getModifiedItemAttr('eliteBonusHeavyGunship2'),
skill='Heavy Assault Cruisers')

View File

@@ -1,2 +0,0 @@
class HandledListActionError(Exception):
...

View File

@@ -20,7 +20,8 @@
import itertools
class Graph(object):
class Graph:
def __init__(self, fit, function, data=None):
self.fit = fit
self.data = {}
@@ -50,11 +51,11 @@ class Graph(object):
point = {}
for i in range(len(pointValues)):
point[pointNames[i]] = pointValues[i]
yield point, self.function(point)
class Data(object):
class Data:
def __init__(self, name, dataString, step=None):
self.name = name
self.step = step
@@ -83,7 +84,8 @@ class Data(object):
return len(self.data) == 1 and self.data[0].isConstant()
class Constant(object):
class Constant:
def __init__(self, const):
if isinstance(const, str):
self.value = None if const == "" else float(const)
@@ -98,7 +100,8 @@ class Constant(object):
return True
class Range(object):
class Range:
def __init__(self, string, step):
start, end = string.split("-")
self.start = float(start)
@@ -108,8 +111,8 @@ class Range(object):
def __iter__(self):
current = start = self.start
end = self.end
step = self.step or (end - start) / 50.0
i = 1
step = self.step or (end - start) / 200
i = 0
while current < end:
current = start + i * step
i += 1

View File

@@ -0,0 +1,24 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitCapAmountTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcAmount, data if data is not None else self.defaults)
self.fit = fit
def calcAmount(self, data):
time = data["time"]
maxCap = self.fit.ship.getModifiedItemAttr('capacitorCapacity')
regenTime = self.fit.ship.getModifiedItemAttr('rechargeRate') / 1000
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
cap = maxCap * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
return cap

View File

@@ -0,0 +1,25 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitCapRegenAmountGraph(Graph):
defaults = {"percentage": '0-100'}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcRegen, data if data is not None else self.defaults)
self.fit = fit
def calcRegen(self, data):
perc = data['percentage']
maxCap = self.fit.ship.getModifiedItemAttr('capacitorCapacity')
regenTime = self.fit.ship.getModifiedItemAttr('rechargeRate') / 1000
currentCap = maxCap * perc / 100
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
regen = 10 * maxCap / regenTime * (math.sqrt(currentCap / maxCap) - currentCap / maxCap)
return regen

View File

@@ -0,0 +1,26 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitDistanceTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcDistance, data if data is not None else self.defaults)
self.fit = fit
def calcDistance(self, data):
time = data["time"]
maxSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
mass = self.fit.ship.getModifiedItemAttr('mass')
agility = self.fit.ship.getModifiedItemAttr('agility')
# Definite integral of:
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
distance = maxSpeed * time + (maxSpeed * agility * mass * math.exp((-time * 1000000) / (agility * mass)) / 1000000)
return distance

113
eos/graph/fitDmgTime.py Normal file
View File

@@ -0,0 +1,113 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from logbook import Logger
from eos.graph import Graph
from eos.utils.spoolSupport import SpoolType, SpoolOptions
pyfalog = Logger(__name__)
class FitDmgTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcDmg, data if data is not None else self.defaults)
self.fit = fit
self.__cache = {}
def calcDmg(self, data):
time = data["time"] * 1000
closestTime = max((t for t in self.__cache if t <= time), default=None)
if closestTime is None:
return 0
return self.__cache[closestTime]
def recalc(self):
def addDmg(addedTime, addedDmg):
if addedDmg == 0:
return
if addedTime not in self.__cache:
prevTime = max((t for t in self.__cache if t < addedTime), default=None)
if prevTime is None:
self.__cache[addedTime] = 0
else:
self.__cache[addedTime] = self.__cache[prevTime]
for time in (t for t in self.__cache if t >= addedTime):
self.__cache[time] += addedDmg
self.__cache.clear()
fit = self.fit
# We'll handle calculations in milliseconds
maxTime = self.data["time"].data[0].end * 1000
for mod in fit.modules:
cycleParams = mod.getCycleParameters(reloadOverride=True)
if cycleParams is None:
continue
currentTime = 0
nonstopCycles = 0
for cycleTime, inactiveTime in cycleParams.iterCycles():
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
for volleyTime, volley in volleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
addDmg(currentTime + volleyTime, volley.total)
currentTime += cycleTime
currentTime += inactiveTime
if inactiveTime == 0:
nonstopCycles += 1
else:
nonstopCycles = 0
if currentTime > maxTime:
break
for drone in fit.drones:
cycleParams = drone.getCycleParameters(reloadOverride=True)
if cycleParams is None:
continue
currentTime = 0
volleyParams = drone.getVolleyParameters()
for cycleTime, inactiveTime in cycleParams.iterCycles():
for volleyTime, volley in volleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
addDmg(currentTime + volleyTime, volley.total)
currentTime += cycleTime
currentTime += inactiveTime
if currentTime > maxTime:
break
for fighter in fit.fighters:
cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
if cycleParams is None:
continue
volleyParams = fighter.getVolleyParametersPerEffect()
for effectID, abilityCycleParams in cycleParams.items():
if effectID not in volleyParams:
continue
currentTime = 0
abilityVolleyParams = volleyParams[effectID]
for cycleTime, inactiveTime in abilityCycleParams.iterCycles():
for volleyTime, volley in abilityVolleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
addDmg(currentTime + volleyTime, volley.total)
currentTime += cycleTime
currentTime += inactiveTime
if currentTime > maxTime:
break

View File

@@ -17,16 +17,19 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from math import log, sin, radians, exp
from math import exp, log, radians, sin, inf
from eos.graph import Graph
from eos.const import FittingModuleState, FittingHardpoint
from logbook import Logger
from eos.const import FittingHardpoint, FittingModuleState
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitDpsGraph(Graph):
class FitDpsRangeGraph(Graph):
defaults = {
"angle" : 0,
"distance" : 0,
@@ -169,8 +172,7 @@ class FitDpsGraph(Graph):
return min(sigRadiusFactor, velocityFactor, 1)
@staticmethod
def calculateTurretChanceToHit(mod, data):
def calculateTurretChanceToHit(self, mod, data):
distance = data["distance"] * 1000
tracking = mod.getModifiedItemAttr("trackingSpeed")
turretOptimal = mod.maxRange
@@ -179,7 +181,17 @@ class FitDpsGraph(Graph):
targetSigRad = data["signatureRadius"]
targetSigRad = turretSigRes if targetSigRad is None else targetSigRad
transversal = sin(radians(data["angle"])) * data["velocity"]
trackingEq = (((transversal / (distance * tracking)) *
# Angular velocity is calculated using range from ship center to target center.
# We do not know target radius but we know attacker radius
angDistance = distance + self.fit.ship.getModifiedItemAttr('radius', 0)
if angDistance == 0 and transversal == 0:
angularVelocity = 0
elif angDistance == 0 and transversal != 0:
angularVelocity = inf
else:
angularVelocity = transversal / angDistance
trackingEq = (((angularVelocity / tracking) *
(turretSigRes / targetSigRad)) ** 2)
rangeEq = ((max(0, distance - turretOptimal)) / turretFalloff) ** 2

112
eos/graph/fitDpsTime.py Normal file
View File

@@ -0,0 +1,112 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from logbook import Logger
from eos.graph import Graph
from eos.utils.spoolSupport import SpoolType, SpoolOptions
pyfalog = Logger(__name__)
class FitDpsTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcDps, data if data is not None else self.defaults)
self.fit = fit
self.__cache = []
def calcDps(self, data):
time = data["time"] * 1000
entries = (e for e in self.__cache if e[0] <= time < e[1])
dps = sum(e[2] for e in entries)
return dps
def recalc(self):
def addDmg(addedTimeStart, addedTimeFinish, addedDmg):
if addedDmg == 0:
return
addedDps = 1000 * addedDmg / (addedTimeFinish - addedTimeStart)
self.__cache.append((addedTimeStart, addedTimeFinish, addedDps))
self.__cache = []
fit = self.fit
# We'll handle calculations in milliseconds
maxTime = self.data["time"].data[0].end * 1000
for mod in fit.modules:
cycleParams = mod.getCycleParameters(reloadOverride=True)
if cycleParams is None:
continue
currentTime = 0
nonstopCycles = 0
for cycleTime, inactiveTime in cycleParams.iterCycles():
cycleDamage = 0
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
for volleyTime, volley in volleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime
currentTime += inactiveTime
if inactiveTime > 0:
nonstopCycles = 0
else:
nonstopCycles += 1
if currentTime > maxTime:
break
for drone in fit.drones:
cycleParams = drone.getCycleParameters(reloadOverride=True)
if cycleParams is None:
continue
currentTime = 0
for cycleTime, inactiveTime in cycleParams.iterCycles():
cycleDamage = 0
volleyParams = drone.getVolleyParameters()
for volleyTime, volley in volleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime
currentTime += inactiveTime
if currentTime > maxTime:
break
for fighter in fit.fighters:
cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
if cycleParams is None:
continue
volleyParams = fighter.getVolleyParametersPerEffect()
for effectID, abilityCycleParams in cycleParams.items():
if effectID not in volleyParams:
continue
abilityVolleyParams = volleyParams[effectID]
currentTime = 0
for cycleTime, inactiveTime in abilityCycleParams.iterCycles():
cycleDamage = 0
for volleyTime, volley in abilityVolleyParams.items():
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime
currentTime += inactiveTime
if currentTime > maxTime:
break

View File

@@ -0,0 +1,29 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitShieldAmountTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcAmount, data if data is not None else self.defaults)
self.fit = fit
import gui.mainFrame
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def calcAmount(self, data):
time = data["time"]
maxShield = self.fit.ship.getModifiedItemAttr('shieldCapacity')
regenTime = self.fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
shield = maxShield * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
if self.fit.damagePattern is not None and useEhp:
shield = self.fit.damagePattern.effectivify(self.fit, shield, 'shield')
return shield

View File

@@ -0,0 +1,49 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitShieldRegenAmountGraph(Graph):
defaults = {"percentage": '0-100'}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcRegen, data if data is not None else self.defaults)
self.fit = fit
import gui.mainFrame
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def calcRegen(self, data):
perc = data["percentage"]
maxShield = self.fit.ship.getModifiedItemAttr('shieldCapacity')
regenTime = self.fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
currentShield = maxShield * perc / 100
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
regen = 10 * maxShield / regenTime * (math.sqrt(currentShield / maxShield) - currentShield / maxShield)
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
if self.fit.damagePattern is not None and useEhp:
regen = self.fit.damagePattern.effectivify(self.fit, regen, 'shield')
return regen

25
eos/graph/fitSpeedTime.py Normal file
View File

@@ -0,0 +1,25 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
class FitSpeedTimeGraph(Graph):
defaults = {"time": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcSpeed, data if data is not None else self.defaults)
self.fit = fit
def calcSpeed(self, data):
time = data["time"]
maxSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
mass = self.fit.ship.getModifiedItemAttr('mass')
agility = self.fit.ship.getModifiedItemAttr('agility')
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
return speed

View File

@@ -0,0 +1,61 @@
import math
from logbook import Logger
from eos.graph import Graph
pyfalog = Logger(__name__)
AU_METERS = 149597870700
class FitWarpTimeDistanceGraph(Graph):
defaults = {"distance": 0}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcTime, data if data is not None else self.defaults)
self.fit = fit
def calcTime(self, data):
distance = data["distance"]
if distance == 0:
return 0
maxWarpDistance = self.fit.maxWarpDistance
if distance > maxWarpDistance:
return None
maxSubwarpSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
maxWarpSpeed = self.fit.warpSpeed
time = calculate_time_in_warp(maxWarpSpeed, maxSubwarpSpeed, distance * AU_METERS)
return time
# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation
# with minor modifications
# Warp speed in AU/s, subwarp speed in m/s, distance in m
def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist):
k_accel = max_warp_speed
k_decel = min(max_warp_speed / 3, 2)
warp_dropout_speed = max_subwarp_speed / 2
max_ms_warp_speed = max_warp_speed * AU_METERS
accel_dist = AU_METERS
decel_dist = max_ms_warp_speed / k_decel
minimum_dist = accel_dist + decel_dist
cruise_time = 0
if minimum_dist > warp_dist:
max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel)
else:
cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed
accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel
decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel
total_time = cruise_time + accel_time + decel_time
return total_time

View File

@@ -17,13 +17,14 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import math
from logbook import Logger
from sqlalchemy.orm import validates, reconstructor
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.utils.cycles import CycleInfo
from eos.utils.stats import DmgTypes
@@ -104,7 +105,16 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def cycleTime(self):
return max(self.getModifiedItemAttr("duration", 0), 0)
if self.hasAmmo:
cycleTime = self.getModifiedItemAttr("missileLaunchDuration", 0)
else:
for attr in ("speed", "duration"):
cycleTime = self.getModifiedItemAttr(attr, None)
if cycleTime is not None:
break
if cycleTime is None:
return 0
return max(cycleTime, 0)
@property
def dealsDamage(self):
@@ -121,9 +131,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def hasAmmo(self):
return self.charge is not None
def getVolley(self, targetResists=None):
def getVolleyParameters(self, targetResists=None):
if not self.dealsDamage or self.amountActive <= 0:
return DmgTypes(0, 0, 0, 0)
return {0: DmgTypes(0, 0, 0, 0)}
if self.__baseVolley is None:
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
@@ -137,15 +147,19 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
return volley
return {0: volley}
def getVolley(self, targetResists=None):
return self.getVolleyParameters(targetResists=targetResists)[0]
def getDps(self, targetResists=None):
volley = self.getVolley(targetResists=targetResists)
if not volley:
return DmgTypes(0, 0, 0, 0)
cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed"
cycleTime = self.getModifiedItemAttr(cycleAttr)
dpsFactor = 1 / (cycleTime / 1000)
cycleParams = self.getCycleParameters()
if cycleParams is None:
return DmgTypes(0, 0, 0, 0)
dpsFactor = 1 / (cycleParams.averageTime / 1000)
dps = DmgTypes(
em=volley.em * dpsFactor,
thermal=volley.thermal * dpsFactor,
@@ -153,6 +167,12 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
explosive=volley.explosive * dpsFactor)
return dps
def getCycleParameters(self, reloadOverride=None):
cycleTime = self.cycleTime
if cycleTime == 0:
return None
return CycleInfo(self.cycleTime, 0, math.inf)
def getRemoteReps(self, ignoreState=False):
if self.amountActive <= 0 and not ignoreState:
return (None, 0)
@@ -174,7 +194,12 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
rrAmount = 0
if rrAmount:
droneAmount = self.amount if ignoreState else self.amountActive
rrAmount *= droneAmount / (self.cycleTime / 1000)
cycleParams = self.getCycleParameters()
if cycleParams is None:
rrType = None
rrAmount = 0
else:
rrAmount *= droneAmount / (cycleParams.averageTime / 1000)
self.__baseRemoteReps = (rrType, rrAmount)
return self.__baseRemoteReps
@@ -182,12 +207,14 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def miningStats(self):
if self.__miningyield is None:
if self.mines is True and self.amountActive > 0:
attr = "duration"
getter = self.getModifiedItemAttr
cycleTime = self.getModifiedItemAttr(attr)
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
self.__miningyield = volley / (cycleTime / 1000.0)
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0

View File

@@ -17,16 +17,19 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import math
from logbook import Logger
from sqlalchemy.orm import validates, reconstructor
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.saveddata.fighterAbility import FighterAbility
from eos.utils.stats import DmgTypes
from eos.const import FittingSlot
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.fighterAbility import FighterAbility
from eos.utils.cycles import CycleInfo, CycleSequence
from eos.utils.stats import DmgTypes
from eos.utils.float import floatUnerr
pyfalog = Logger(__name__)
@@ -172,28 +175,37 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def hasAmmo(self):
return self.charge is not None
def getVolley(self, targetResists=None):
def getVolleyParametersPerEffect(self, targetResists=None):
if not self.active or self.amountActive <= 0:
return DmgTypes(0, 0, 0, 0)
return {}
if self.__baseVolley is None:
em = 0
therm = 0
kin = 0
exp = 0
self.__baseVolley = {}
for ability in self.abilities:
# Not passing resists here as we want to calculate and store base volley
abilityVolley = ability.getVolley()
em += abilityVolley.em
therm += abilityVolley.thermal
kin += abilityVolley.kinetic
exp += abilityVolley.explosive
self.__baseVolley = DmgTypes(em, therm, kin, exp)
volley = DmgTypes(
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
return volley
self.__baseVolley[ability.effectID] = {0: ability.getVolley()}
adjustedVolley = {}
for effectID, effectData in self.__baseVolley.items():
adjustedVolley[effectID] = {}
for volleyTime, volleyValue in effectData.items():
adjustedVolley[effectID][volleyTime] = DmgTypes(
em=volleyValue.em * (1 - getattr(targetResists, "emAmount", 0)),
thermal=volleyValue.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
kinetic=volleyValue.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
explosive=volleyValue.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
return adjustedVolley
def getVolley(self, targetResists=None):
volleyParams = self.getVolleyParametersPerEffect(targetResists=targetResists)
em = 0
therm = 0
kin = 0
exp = 0
for volleyData in volleyParams.values():
em += volleyData[0].em
therm += volleyData[0].thermal
kin += volleyData[0].kinetic
exp += volleyData[0].explosive
return DmgTypes(em, therm, kin, exp)
def getDps(self, targetResists=None):
em = 0
@@ -207,44 +219,81 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
explosive += dps.explosive
return DmgTypes(em=em, thermal=thermal, kinetic=kinetic, explosive=explosive)
def getUptime(self):
if not self.owner.factorReload:
return 1
activeTimes = []
reloadTimes = []
for ability in self.abilities:
if ability.numShots > 0:
activeTimes.append(ability.numShots * ability.cycleTime)
reloadTimes.append(ability.reloadTime)
if not activeTimes:
return 1
shortestActive = sorted(activeTimes)[0]
longestReload = sorted(reloadTimes, reverse=True)[0]
uptime = shortestActive / (shortestActive + longestReload)
return uptime
def getDpsPerEffect(self, targetResists=None):
if not self.active or self.amountActive <= 0:
return {}
uptime = self.getUptime()
if uptime == 1:
return {a.effectID: a.getDps(targetResists=targetResists) for a in self.abilities}
# Decide if it's better to keep steady dps up and never reload or reload from time to time
dpsMapSteady = {}
dpsMapPeakAdjusted = {}
cycleParams = self.getCycleParametersPerEffectOptimizedDps(targetResists=targetResists)
dpsMap = {}
for ability in self.abilities:
abilityDps = ability.getDps(targetResists=targetResists)
dpsMapPeakAdjusted[ability.effectID] = DmgTypes(
em=abilityDps.em * uptime,
thermal=abilityDps.thermal * uptime,
kinetic=abilityDps.kinetic * uptime,
explosive=abilityDps.explosive * uptime)
# Infinite use - add to steady dps
if ability.numShots == 0:
dpsMapSteady[ability.effectID] = abilityDps
totalSteady = sum(i.total for i in dpsMapSteady.values())
totalPeakAdjusted = sum(i.total for i in dpsMapPeakAdjusted.values())
return dpsMapSteady if totalSteady >= totalPeakAdjusted else dpsMapPeakAdjusted
if ability.effectID in cycleParams:
cycleTime = cycleParams[ability.effectID].averageTime
dpsMap[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
return dpsMap
def getCycleParametersPerEffectOptimizedDps(self, targetResists=None, reloadOverride=None):
cycleParamsInfinite = self.getCycleParametersPerEffectInfinite()
cycleParamsReload = self.getCycleParametersPerEffect(reloadOverride=reloadOverride)
dpsMapOnlyInfinite = {}
dpsMapAllWithReloads = {}
# Decide if it's better to keep steady dps up and never reload or reload from time to time
for ability in self.abilities:
if ability.effectID in cycleParamsInfinite:
cycleTime = cycleParamsInfinite[ability.effectID].averageTime
dpsMapOnlyInfinite[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
if ability.effectID in cycleParamsReload:
cycleTime = cycleParamsReload[ability.effectID].averageTime
dpsMapAllWithReloads[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
totalOnlyInfinite = sum(i.total for i in dpsMapOnlyInfinite.values())
totalAllWithReloads = sum(i.total for i in dpsMapAllWithReloads.values())
return cycleParamsInfinite if totalOnlyInfinite >= totalAllWithReloads else cycleParamsReload
def getCycleParametersPerEffectInfinite(self):
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.numShots == 0 and a.cycleTime > 0}
def getCycleParametersPerEffect(self, reloadOverride=None):
factorReload = reloadOverride if reloadOverride is not None else self.owner.factorReload
# Assume it can cycle infinitely
if not factorReload:
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
limitedAbilities = [a for a in self.abilities if a.numShots > 0 and a.cycleTime > 0]
if len(limitedAbilities) == 0:
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
validAbilities = [a for a in self.abilities if a.cycleTime > 0]
if len(validAbilities) == 0:
return {}
mostLimitedAbility = min(limitedAbilities, key=lambda a: a.cycleTime * a.numShots)
durationToRefuel = mostLimitedAbility.cycleTime * mostLimitedAbility.numShots
# find out how many shots various abilities will do until reload, and how much time
# "extra" cycle will last (None for no extra cycle)
cyclesUntilRefuel = {mostLimitedAbility.effectID: (mostLimitedAbility.numShots, None)}
for ability in (a for a in validAbilities if a is not mostLimitedAbility):
fullCycles = int(floatUnerr(durationToRefuel / ability.cycleTime))
extraShotTime = floatUnerr(durationToRefuel - (fullCycles * ability.cycleTime))
if extraShotTime == 0:
extraShotTime = None
cyclesUntilRefuel[ability.effectID] = (fullCycles, extraShotTime)
refuelTimes = {}
for ability in validAbilities:
spentShots, extraShotTime = cyclesUntilRefuel[ability.effectID]
if extraShotTime is not None:
spentShots += 1
refuelTimes[ability.effectID] = ability.getReloadTime(spentShots)
refuelTime = max(refuelTimes.values())
cycleParams = {}
for ability in validAbilities:
regularShots, extraShotTime = cyclesUntilRefuel[ability.effectID]
sequence = []
if extraShotTime is not None:
if regularShots > 0:
sequence.append(CycleInfo(ability.cycleTime, 0, regularShots))
sequence.append(CycleInfo(extraShotTime, refuelTime, 1))
else:
regularShotsNonReload = regularShots - 1
if regularShotsNonReload > 0:
sequence.append(CycleInfo(ability.cycleTime, 0, regularShotsNonReload))
sequence.append(CycleInfo(ability.cycleTime, refuelTime, 1))
cycleParams[ability.effectID] = CycleSequence(sequence, math.inf)
return cycleParams
@property
def maxRange(self):

View File

@@ -95,8 +95,15 @@ class FighterAbility(object):
@property
def reloadTime(self):
return self.getReloadTime()
def getReloadTime(self, spentShots=None):
if spentShots is not None:
spentShots = max(self.numShots, spentShots)
else:
spentShots = self.numShots
rearm_time = (self.REARM_TIME_MAPPING[self.fighter.getModifiedItemAttr("fighterSquadronRole")] or 0 if self.hasCharges else 0)
return self.fighter.getModifiedItemAttr("fighterRefuelingTime") + rearm_time * self.numShots
return self.fighter.getModifiedItemAttr("fighterRefuelingTime") + rearm_time * spentShots
@property
def numShots(self):
@@ -128,11 +135,12 @@ class FighterAbility(object):
explosive=exp * dmgMult * (1 - getattr(targetResists, "explosiveAmount", 0)))
return volley
def getDps(self, targetResists=None):
def getDps(self, targetResists=None, cycleTimeOverride=None):
volley = self.getVolley(targetResists=targetResists)
if not volley:
return DmgTypes(0, 0, 0, 0)
dpsFactor = 1 / (self.cycleTime / 1000)
cycleTime = cycleTimeOverride if cycleTimeOverride is not None else self.cycleTime
dpsFactor = 1 / (cycleTime / 1000)
dps = DmgTypes(
em=volley.em * dpsFactor,
thermal=volley.thermal * dpsFactor,

View File

@@ -1547,8 +1547,9 @@ class Fit(object):
fitCopy.systemSecurity = self.systemSecurity
fitCopy.notes = self.notes
for i in self.modules:
fitCopy.modules.appendIgnoreEmpty(deepcopy(i))
toCopy = (
"modules",
"drones",
"fighters",
"cargo",

View File

@@ -17,21 +17,22 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from math import floor
from logbook import Logger
import math
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
from eos.const import FittingHardpoint, FittingModuleState, FittingSlot
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.citadel import Citadel
from eos.saveddata.mutator import Mutator
from eos.utils.cycles import CycleInfo, CycleSequence
from eos.utils.float import floatUnerr
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
from eos.utils.stats import DmgTypes
pyfalog = Logger(__name__)
ProjectedMap = {
@@ -287,7 +288,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
chargeRate = self.getModifiedItemAttr("chargeRate")
numCharges = self.numCharges
numShots = floor(numCharges / chargeRate)
numShots = math.floor(numCharges / chargeRate)
else:
numShots = None
return numShots
@@ -300,7 +301,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
chance = self.getModifiedChargeAttr("crystalVolatilityChance")
damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
crystals = self.numCharges
numShots = floor((crystals * hp) / (damage * chance))
numShots = math.floor((crystals * hp) / (damage * chance))
else:
# Set 0 (infinite) for permanent crystals like t1 laser crystals
numShots = 0
@@ -400,8 +401,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
"miningAmount") or 0
if volley:
cycleTime = self.cycleTime
self.__miningyield = volley / (cycleTime / 1000.0)
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
else:
@@ -409,42 +414,64 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return self.__miningyield
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
def getVolleyParameters(self, spoolOptions=None, targetResists=None, ignoreState=False):
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
return DmgTypes(0, 0, 0, 0)
return {0: DmgTypes(0, 0, 0, 0)}
if self.__baseVolley is None:
self.__baseVolley = {}
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
self.__baseVolley = DmgTypes(
em=(dmgGetter("emDamage", 0)) * dmgMult,
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0) or self.getModifiedItemAttr("doomsdayWarningDuration", 0)
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
if dmgDuration != 0 and dmgSubcycle != 0:
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
else:
subcycles = 1
for i in range(subcycles):
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
em=(dmgGetter("emDamage", 0)) * dmgMult,
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
spoolBoost = calculateSpoolup(
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
spoolMultiplier = 1 + spoolBoost
volley = DmgTypes(
em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
return volley
adjustedVolley = {}
for volleyTime, volleyValue in self.__baseVolley.items():
adjustedVolley[volleyTime] = DmgTypes(
em=volleyValue.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
return adjustedVolley
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
if len(volleyParams) == 0:
return DmgTypes(0, 0, 0, 0)
return volleyParams[min(volleyParams)]
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
if not volley:
return DmgTypes(0, 0, 0, 0)
# Some weapons repeat multiple times in one cycle (bosonic doomsdays). Get the number of times it fires off
volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1)
dpsFactor = volleysPerCycle / (self.cycleTime / 1000)
dmgDuringCycle = DmgTypes(0, 0, 0, 0)
cycleParams = self.getCycleParameters()
if cycleParams is None:
return dmgDuringCycle
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
avgCycleTime = cycleParams.averageTime
if len(volleyParams) == 0 or avgCycleTime == 0:
return dmgDuringCycle
for volleyValue in volleyParams.values():
dmgDuringCycle += volleyValue
dpsFactor = 1 / (avgCycleTime / 1000)
dps = DmgTypes(
em=volley.em * dpsFactor,
thermal=volley.thermal * dpsFactor,
kinetic=volley.kinetic * dpsFactor,
explosive=volley.explosive * dpsFactor)
em=dmgDuringCycle.em * dpsFactor,
thermal=dmgDuringCycle.thermal * dpsFactor,
kinetic=dmgDuringCycle.kinetic * dpsFactor,
explosive=dmgDuringCycle.explosive * dpsFactor)
return dps
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
@@ -474,7 +501,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
else:
return None, 0
if rrAmount:
rrAmount *= 1 / (self.cycleTime / 1000)
cycleParams = self.getCycleParameters()
if cycleParams is None:
return None, 0
rrAmount *= 1 / (cycleParams.averageTime / 1000)
if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
@@ -819,49 +849,61 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
except:
effect.handler(fit, self, context)
@property
def cycleTime(self):
def getCycleParameters(self, reloadOverride=None):
"""Copied from new eos as well"""
# Determine if we'll take into account reload time or not
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
numShots = self.numShots
speed = self.rawCycleTime
if factorReload and self.charge:
raw_reload_time = self.reloadTime
if reloadOverride is not None:
factorReload = reloadOverride
else:
raw_reload_time = 0.0
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
# Module can only fire one shot at a time, think bomb launchers or defender launchers
if self.disallowRepeatingAction:
if numShots > 0:
"""
The actual mechanics behind this is complex. Behavior will be (for 3 ammo):
fire, reactivation delay, fire, reactivation delay, fire, max(reactivation delay, reload)
so your effective reload time depends on where you are at in the cycle.
cycles_until_reload = self.numShots
if cycles_until_reload == 0:
cycles_until_reload = math.inf
We can't do that, so instead we'll average it out.
Currently would apply to bomb launchers and defender missiles
"""
effective_reload_time = ((self.reactivationDelay * (numShots - 1)) + max(raw_reload_time, self.reactivationDelay, 0))
else:
"""
Applies to MJD/MJFG
"""
effective_reload_time = max(raw_reload_time, self.reactivationDelay, 0)
speed = speed + effective_reload_time
active_time = self.rawCycleTime
if active_time == 0:
return None
forced_inactive_time = self.reactivationDelay
reload_time = self.reloadTime
# Effects which cannot be reloaded have the same processing whether
# caller wants to take reload time into account or not
if reload_time is None and cycles_until_reload < math.inf:
final_cycles = 1
early_cycles = cycles_until_reload - final_cycles
# Single cycle until effect cannot run anymore
if early_cycles == 0:
return CycleInfo(active_time, 0, 1)
# Multiple cycles with the same parameters
if forced_inactive_time == 0:
return CycleInfo(active_time, 0, cycles_until_reload)
# Multiple cycles with different parameters
return CycleSequence((
CycleInfo(active_time, forced_inactive_time, early_cycles),
CycleInfo(active_time, 0, final_cycles)
), 1)
# Module cycles the same way all the time in 3 cases:
# 1) caller doesn't want to take into account reload time
# 2) effect does not have to reload anything to keep running
# 3) effect has enough time to reload during inactivity periods
if (
not factorReload or
cycles_until_reload == math.inf or
forced_inactive_time >= reload_time
):
return CycleInfo(active_time, forced_inactive_time, math.inf)
# We've got to take reload into consideration
else:
"""
Currently no other modules would have a reactivation delay, so for sanities sake don't try and account for it.
Okay, technically cloaks do, but they also have 0 cycle time and cap usage so why do you care?
"""
effective_reload_time = raw_reload_time
if numShots > 0 and self.charge:
speed = (speed * numShots + effective_reload_time) / numShots
return speed
final_cycles = 1
early_cycles = cycles_until_reload - final_cycles
# If effect has to reload after each its cycle, then its parameters
# are the same all the time
if early_cycles == 0:
return CycleInfo(active_time, reload_time, math.inf)
return CycleSequence((
CycleInfo(active_time, forced_inactive_time, early_cycles),
CycleInfo(active_time, reload_time, final_cycles)
), math.inf)
@property
def rawCycleTime(self):
@@ -887,7 +929,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def capUse(self):
capNeed = self.getModifiedItemAttr("capacitorNeed")
if capNeed and self.state >= FittingModuleState.ACTIVE:
cycleTime = self.cycleTime
cycleParams = self.getCycleParameters()
if cycleParams is None:
return 0
cycleTime = cycleParams.averageTime
if cycleTime > 0:
capUsed = capNeed / (cycleTime / 1000.0)
return capUsed

68
eos/utils/cycles.py Normal file
View File

@@ -0,0 +1,68 @@
# Borrowed from new eos
from utils.repr import makeReprStr
class CycleInfo:
def __init__(self, activeTime, inactiveTime, quantity):
self.activeTime = activeTime
self.inactiveTime = inactiveTime
self.quantity = quantity
@property
def averageTime(self):
return self.activeTime + self.inactiveTime
def iterCycles(self):
i = 0
while i < self.quantity:
yield self.activeTime, self.inactiveTime
i += 1
def _getCycleQuantity(self):
return self.quantity
def _getTime(self):
return (self.activeTime + self.inactiveTime) * self.quantity
def __repr__(self):
spec = ['activeTime', 'inactiveTime', 'quantity']
return makeReprStr(self, spec)
class CycleSequence:
def __init__(self, sequence, quantity):
self.sequence = sequence
self.quantity = quantity
@property
def averageTime(self):
"""Get average time between cycles."""
return self._getTime() / self._getCycleQuantity()
def iterCycles(self):
i = 0
while i < self.quantity:
for cycleInfo in self.sequence:
for cycleTime, inactiveTime in cycleInfo.iterCycles():
yield cycleTime, inactiveTime
i += 1
def _getCycleQuantity(self):
quantity = 0
for item in self.sequence:
quantity += item._getCycleQuantity()
return quantity
def _getTime(self):
time = 0
for item in self.sequence:
time += item._getTime()
return time
def __repr__(self):
spec = ['sequence', 'quantity']
return makeReprStr(self, spec)

BIN
eve.db

Binary file not shown.

View File

@@ -50,8 +50,11 @@ class BitmapLoader(object):
@classmethod
def getStaticBitmap(cls, name, parent, location):
bitmap = cls.getBitmap(name or 0, location)
if bitmap is None:
return None
static = wx.StaticBitmap(parent)
static.SetBitmap(cls.getBitmap(name or 0, location))
static.SetBitmap(bitmap)
return static
@classmethod

View File

@@ -123,7 +123,8 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
# Expressions for matching when detecting effects we're looking for
if incursions:
validgroups = ("Incursion ship attributes effects",)
validgroups = ("Incursion ship attributes effects",
"Invasion Effects")
else:
validgroups = ("Black Hole Effect Beacon",
"Cataclysmic Variable Effect Beacon",
@@ -133,7 +134,7 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
"Wolf Rayet Effect Beacon")
# Stuff we don't want to see in names
garbages = ("Effect", "Beacon", "ship attributes effects")
garbages = ("Effects?", "Beacon", "ship attributes effects")
# Get group with all the system-wide beacons
grp = sMkt.getGroup("Effect Beacon")

View File

@@ -54,7 +54,7 @@ class AddImplantSet(ContextMenuSingle):
self.idmap = {}
for set in implantSets:
for set in sorted(implantSets, key=lambda i: i.name):
id = ContextMenuSingle.nextID()
mitem = wx.MenuItem(rootMenu, id, set.name)
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)

View File

@@ -1,3 +1,5 @@
import math
# noinspection PyPackageRequirements
import wx
@@ -48,15 +50,44 @@ class ChangeModuleSpool(ContextMenuSingle):
cycleCurrent = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], False))[0]
cycleMin = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True))[0]
cycleMax = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True))[0]
cycleTotalMin = min(cycleDefault, cycleCurrent, cycleMin)
cycleTotalMax = max(cycleDefault, cycleCurrent, cycleMax)
for cycle in range(cycleMin, cycleMax + 1):
def findCycles(val1, val2):
# Try to compose list of 21 steps max (0-20)
maxSteps = 20
valDiff = val2 - val1
valScale = valDiff / maxSteps
minStep = math.ceil(round(valScale, 9))
maxStep = math.floor(round(valDiff / 4, 9))
# Check steps from smallest to highest and see if we can go from min value
# to max value using those
for currentStep in range(minStep, maxStep + 1):
if valDiff % currentStep == 0:
return set(range(val1, val2 + currentStep, currentStep))
# Otherwise just split range in halves and go both ends using min values
else:
cycles = set()
while val2 >= val1:
cycles.add(val1)
cycles.add(val2)
val1 += minStep
val2 -= minStep
return cycles
cyclesToShow = findCycles(cycleMin, cycleMax)
for cycle in range(cycleTotalMin, cycleTotalMax + 1):
menuId = ContextMenuSingle.nextID()
# Show default only for current value and when not overriden
if not isNotDefault and cycle == cycleDefault:
text = "{} (default)".format(cycle)
else:
# Always show current selection and stuff which we decided to show via the cycles function
elif cycle == cycleCurrent or cycle in cyclesToShow:
text = "{}".format(cycle)
# Ignore the rest to not have very long menu
else:
continue
item = wx.MenuItem(m, menuId, text, kind=wx.ITEM_CHECK)
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)

View File

@@ -1 +1,12 @@
__all__ = ["fitDps"]
__all__ = [
'fitDpsRange',
'fitDpsTime',
'fitDmgTime',
'fitShieldRegenAmount',
'fitShieldAmountTime',
'fitCapRegenAmount',
'fitCapAmountTime',
'fitSpeedTime',
'fitDistanceTime',
'fitWarpTimeDistance'
]

View File

@@ -0,0 +1,81 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitCapAmountTime import FitCapAmountTimeGraph as EosFitCapAmountTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitCapAmountTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitCapAmountTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-300"
self.name = "Cap Amount vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitCapAmountTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitCapAmountTimeGraph.register()

View File

@@ -0,0 +1,82 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitCapRegenAmount import FitCapRegenAmountGraph as EosFitCapRegenAmountGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitCapRegenAmountGraph(Graph):
propertyLabelMap = {"percentage": "Cap Amount (percent)"}
defaults = EosFitCapRegenAmountGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["percentage"] = "0-100"
self.name = "Cap Regen vs Cap Amount"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('capacitorCapacity').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"percentage": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitCapRegenAmountGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitCapRegenAmountGraph.register()

View File

@@ -0,0 +1,81 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitDistanceTime import FitDistanceTimeGraph as EosFitDistanceTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitDistanceTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitDistanceTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-80"
self.name = "Distance Travelled vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitDistanceTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitDistanceTimeGraph.register()

View File

@@ -0,0 +1,82 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitDmgTime import FitDmgTimeGraph as EosFitDmgTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitDmgTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitDmgTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-80"
self.name = "Damage Inflicted vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitDmgTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
eosGraph.recalc()
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitDmgTimeGraph.register()

View File

@@ -17,15 +17,16 @@
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from gui.graph import Graph
from gui.bitmap_loader import BitmapLoader
from eos.graph.fitDps import FitDpsGraph as FitDps
from eos.graph import Data
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitDpsRange import FitDpsRangeGraph as EosFitDpsRangeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitDpsGraph(Graph):
class FitDpsRangeGraph(Graph):
propertyAttributeMap = {"angle": "maxVelocity",
"distance": "maxRange",
"signatureRadius": "signatureRadius",
@@ -36,13 +37,13 @@ class FitDpsGraph(Graph):
"signatureRadius": "Target Signature Radius (m)",
"velocity": "Target Velocity (m/s)"}
defaults = FitDps.defaults.copy()
defaults = EosFitDpsRangeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["distance"] = "0-100"
self.name = "DPS"
self.fitDps = None
self.name = "DPS vs Range"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
@@ -63,11 +64,11 @@ class FitDpsGraph(Graph):
return icons
def getPoints(self, fit, fields):
fitDps = getattr(self, "fitDps", None)
if fitDps is None or fitDps.fit != fit:
fitDps = self.fitDps = FitDps(fit)
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitDpsRangeGraph(fit)
fitDps.clearData()
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
@@ -78,18 +79,18 @@ class FitDpsGraph(Graph):
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
fitDps.setData(d)
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in fitDps.getIterator():
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitDpsGraph.register()
FitDpsRangeGraph.register()

View File

@@ -0,0 +1,83 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitDpsTime import FitDpsTimeGraph as EosFitDpsTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitDpsTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitDpsTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-80"
self.name = "DPS vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitDpsTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
eosGraph.recalc()
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitDpsTimeGraph.register()

View File

@@ -0,0 +1,85 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitShieldAmountTime import FitShieldAmountTimeGraph as EosFitShieldAmountTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitShieldAmountTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitShieldAmountTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-300"
self.name = "Shield Amount vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitShieldAmountTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
@property
def redrawOnEffectiveChange(self):
return True
FitShieldAmountTimeGraph.register()

View File

@@ -0,0 +1,86 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitShieldRegenAmount import FitShieldRegenAmountGraph as EosFitShieldRegenAmountGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitShieldRegenAmountGraph(Graph):
propertyLabelMap = {"percentage": "Shield Capacity (percent)"}
defaults = EosFitShieldRegenAmountGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["percentage"] = "0-100"
self.name = "Shield Regen vs Shield Amount"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('shieldCapacity').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"percentage": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitShieldRegenAmountGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
@property
def redrawOnEffectiveChange(self):
return True
FitShieldRegenAmountGraph.register()

View File

@@ -0,0 +1,81 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitSpeedTime import FitSpeedTimeGraph as EosFitSpeedTimeGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitSpeedTimeGraph(Graph):
propertyLabelMap = {"time": "Time (seconds)"}
defaults = EosFitSpeedTimeGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["time"] = "0-80"
self.name = "Speed vs Time"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"time": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitSpeedTimeGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
x.append(point[variable])
y.append(val)
return x, y
FitSpeedTimeGraph.register()

View File

@@ -0,0 +1,82 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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/>.
# =============================================================================
import gui.mainFrame
from eos.graph import Data
from eos.graph.fitWarpTimeDistance import FitWarpTimeDistanceGraph as EosFitWarpTimeDistanceGraph
from gui.bitmap_loader import BitmapLoader
from gui.graph import Graph
from service.attribute import Attribute
class FitWarpTimeDistanceGraph(Graph):
propertyLabelMap = {"distance": "Distance (AU)"}
defaults = EosFitWarpTimeDistanceGraph.defaults.copy()
def __init__(self):
Graph.__init__(self)
self.defaults["distance"] = "0-50"
self.name = "Warp Time vs Distance"
self.eosGraph = None
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getFields(self):
return self.defaults
def getLabels(self):
return self.propertyLabelMap
def getIcons(self):
iconFile = Attribute.getInstance().getAttributeInfo('maxRange').iconID
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
return {"distance": bitmap}
def getPoints(self, fit, fields):
eosGraph = getattr(self, "eosGraph", None)
if eosGraph is None or eosGraph.fit != fit:
eosGraph = self.eosGraph = EosFitWarpTimeDistanceGraph(fit)
eosGraph.clearData()
variable = None
for fieldName, value in fields.items():
d = Data(fieldName, value)
if not d.isConstant():
if variable is None:
variable = fieldName
else:
# We can't handle more then one variable atm, OOPS FUCK OUT
return False, "Can only handle 1 variable"
eosGraph.setData(d)
if variable is None:
return False, "No variable"
x = []
y = []
for point, val in eosGraph.getIterator():
if val is not None:
x.append(point[variable])
y.append(val)
return x, y
FitWarpTimeDistanceGraph.register()

View File

@@ -96,8 +96,10 @@ class AttributeSlider(wx.Panel):
self.ctrl = wx.SpinCtrlDouble(self, min=minValue, max=maxValue, inc=getStep(maxValue - minValue))
self.ctrl.SetDigits(getDigitPlaces(minValue, maxValue))
self.ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.UpdateValue)
# GTK scrolls spinboxes with mousewheel, others do not
if "wxGTK" not in wx.PlatformInfo:
self.ctrl.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
self.slider = AttributeGauge(self, size=(-1, 8))
@@ -124,6 +126,16 @@ class AttributeSlider(wx.Panel):
if post_event:
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage))
def OnMouseWheel(self, evt):
if evt.GetWheelRotation() > 0 and evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:
self.ctrl.Value = self.ctrl.Value + self.ctrl.Increment
self.SetValue(self.ctrl.GetValue())
elif evt.GetWheelRotation() < 0 and evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:
self.ctrl.Value = self.ctrl.Value - self.ctrl.Increment
self.SetValue(self.ctrl.GetValue())
else:
evt.Skip()
class TestAttributeSlider(wx.Frame):

View File

@@ -26,8 +26,12 @@ class ItemMutatorPanel(wx.Panel):
headerSizer = wx.BoxSizer(wx.HORIZONTAL)
headerSizer.AddStretchSpacer()
headerSizer.Add(BitmapLoader.getStaticBitmap(mod.item.iconID, self, "icons"), 0, 0, 0)
headerSizer.Add(BitmapLoader.getStaticBitmap(mod.mutaplasmid.item.iconID, self, "icons"), 0, wx.LEFT, 0)
itemIcon = BitmapLoader.getStaticBitmap(mod.item.iconID, self, "icons")
if itemIcon is not None:
headerSizer.Add(itemIcon, 0, 0, 0)
mutaIcon = BitmapLoader.getStaticBitmap(mod.mutaplasmid.item.iconID, self, "icons")
if mutaIcon is not None:
headerSizer.Add(mutaIcon, 0, wx.LEFT, 0)
sourceItemShort = "{} {}".format(mod.mutaplasmid.item.name.split(" ")[0], mod.baseItem.name)
sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
font = parent.GetFont()
@@ -81,6 +85,7 @@ class ItemMutatorList(wx.ScrolledWindow):
self.event_mapping = {}
higOverrides = {
('Stasis Web', 'speedFactor'): False,
('Damage Control', 'duration'): True,
}
first = True

View File

@@ -291,7 +291,9 @@ class FitItem(SFItem.SFBrowserItem):
event.Skip()
def editCheckEsc(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
keycode = event.GetKeyCode()
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.RestoreEditButton()
else:
event.Skip()

View File

@@ -105,7 +105,9 @@ class NavigationPanel(SFItem.SFBrowserItem):
self.BrowserSearchBox.Show(False)
def OnBrowserSearchBoxKeyPress(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
keycode = event.GetKeyCode()
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.BrowserSearchBox.Show(False)
elif event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
HandleCtrlBackspace(self.BrowserSearchBox)

View File

@@ -175,7 +175,9 @@ class ShipItem(SFItem.SFBrowserItem):
self.Refresh()
def editCheckEsc(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:
keycode = event.GetKeyCode()
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.tcFitName.Show(False)
else:
event.Skip()

View File

@@ -140,7 +140,10 @@ class Miscellanea(ViewColumn):
return "+ " + ", ".join(info), "Slot Modifiers"
elif itemGroup == "Energy Neutralizer":
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
cycleTime = stuff.cycleTime
cycleParams = stuff.getCycleParameters()
if cycleParams is None:
return "", None
cycleTime = cycleParams.averageTime
if not neutAmount or not cycleTime:
return "", None
capPerSec = float(-neutAmount) * 1000 / cycleTime
@@ -149,7 +152,10 @@ class Miscellanea(ViewColumn):
return text, tooltip
elif itemGroup == "Energy Nosferatu":
neutAmount = stuff.getModifiedItemAttr("powerTransferAmount")
cycleTime = stuff.cycleTime
cycleParams = stuff.getCycleParameters()
if cycleParams is None:
return "", None
cycleTime = cycleParams.averageTime
if not neutAmount or not cycleTime:
return "", None
capPerSec = float(-neutAmount) * 1000 / cycleTime
@@ -528,9 +534,9 @@ class Miscellanea(ViewColumn):
text = "{0}/s".format(formatAmount(capPerSec, 3, 0, 3))
tooltip = "Energy neutralization per second"
return text, tooltip
elif itemGroup == "Micro Jump Drive":
elif itemGroup in ("Micro Jump Drive", "Micro Jump Field Generators"):
cycleTime = stuff.getModifiedItemAttr("duration") / 1000
text = "{0}s".format(cycleTime)
text = "{0}s".format(formatAmount(cycleTime, 3, 0, 3))
tooltip = "Spoolup time"
return text, tooltip
elif itemGroup in ("Siege Module", "Cynosural Field Generator"):

View File

@@ -256,7 +256,8 @@ class CharacterEditor(wx.Frame):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -67,8 +67,8 @@ class ContextMenu(metaclass=ABCMeta):
(('marketItemGroup', 'Implant'),)
(('fittingShip', 'Ship'),)
"""
cls._idxid = -1
debug_start = len(cls._ids)
ContextMenu._idxid = -1
debug_start = len(ContextMenu._ids)
rootMenu = wx.Menu()
rootMenu.info = {}
@@ -95,7 +95,7 @@ class ContextMenu(metaclass=ABCMeta):
bitmap = m._baseGetBitmap(srcContext, mainItem, selection)
multiple = not isinstance(bitmap, wx.Bitmap)
for it, text in enumerate(texts):
id = cls.nextID()
id = ContextMenu.nextID()
check = m.checked
rootItem = wx.MenuItem(rootMenu, id, text, kind=wx.ITEM_NORMAL if m.checked is None else wx.ITEM_CHECK)
rootMenu.info[id] = (m, fullContext, it)
@@ -104,7 +104,7 @@ class ContextMenu(metaclass=ABCMeta):
if sub is None:
# if there is no sub menu, bind the handler to the rootItem
rootMenu.Bind(wx.EVT_MENU, cls.handler, rootItem)
rootMenu.Bind(wx.EVT_MENU, ContextMenu.handler, rootItem)
elif sub:
# If sub exists and is not False, set submenu.
# Sub might return False when we have a mix of
@@ -141,14 +141,14 @@ class ContextMenu(metaclass=ABCMeta):
if display_amount > 0 and i != len(fullContexts) - 1:
rootMenu.AppendSeparator()
debug_end = len(cls._ids)
debug_end = len(ContextMenu._ids)
if debug_end - debug_start:
pyfalog.debug("{} new IDs created for this menu".format(debug_end - debug_start))
return rootMenu if empty is False else None
@classmethod
def handler(cls, event):
@staticmethod
def handler(event):
menu = event.EventObject
stuff = menu.info.get(event.Id)
if stuff is not None:
@@ -162,21 +162,22 @@ class ContextMenu(metaclass=ABCMeta):
else:
event.Skip()
@classmethod
def nextID(cls):
@staticmethod
def nextID():
"""
Fetches an ID from the pool of IDs allocated to Context Menu.
If we don't have enough ID's to fulfill request, create new
ID and add it to the pool.
See GH Issue #589
See GH Issue #589.
Has to be static method to properly handle modifications of primitives from subclasses (_idxid).
"""
cls._idxid += 1
ContextMenu._idxid += 1
if cls._idxid >= len(cls._ids): # We don't ahve an ID for this index, create one
cls._ids.append(wx.NewId())
if ContextMenu._idxid >= len(ContextMenu._ids): # We don't ahve an ID for this index, create one
ContextMenu._ids.append(wx.NewId())
return cls._ids[cls._idxid]
return ContextMenu._ids[ContextMenu._idxid]
@property
def checked(self):

View File

@@ -23,13 +23,14 @@ from collections import OrderedDict
# noinspection PyPackageRequirements
import wx
from eos.db import getFit
from gui.utils.clipboard import toClipboard
from service.const import PortMultiBuyOptions
from service.port import EfsPort, Port
from service.port.dna import DNA_OPTIONS
from service.port.eft import EFT_OPTIONS
from service.port.multibuy import MULTIBUY_OPTIONS
from service.settings import SettingsProvider
from service.port import EfsPort, Port
from service.const import PortMultiBuyOptions
from eos.db import getFit
from gui.utils.clipboard import toClipboard
class CopySelectDialog(wx.Dialog):
@@ -60,9 +61,9 @@ class CopySelectDialog(wx.Dialog):
("EFT", (CopySelectDialog.copyFormatEft, EFT_OPTIONS)),
("MultiBuy", (CopySelectDialog.copyFormatMultiBuy, MULTIBUY_OPTIONS)),
("ESI", (CopySelectDialog.copyFormatEsi, None)),
("DNA", (CopySelectDialog.copyFormatDna, DNA_OPTIONS)),
("EFS", (CopySelectDialog.copyFormatEfs, None)),
# ("XML", (CopySelectDialog.copyFormatXml, None)),
# ("DNA", (CopySelectDialog.copyFormatDna, None)),
))
defaultFormatOptions = {}
@@ -99,6 +100,8 @@ class CopySelectDialog(wx.Dialog):
for optId, optName, optDesc, _ in formatOptions:
checkbox = wx.CheckBox(self, -1, optName)
if optDesc:
checkbox.SetToolTip(wx.ToolTip(optDesc))
self.options[formatId][optId] = checkbox
if self.settings['options'].get(formatId, {}).get(optId, defaultFormatOptions.get(formatId, {}).get(optId)):
checkbox.SetValue(True)
@@ -166,7 +169,7 @@ class CopySelectDialog(wx.Dialog):
def exportDna(self, options, callback):
fit = getFit(self.mainFrame.getActiveFit())
Port.exportDna(fit, callback)
Port.exportDna(fit, options, callback)
def exportEsi(self, options, callback):
fit = getFit(self.mainFrame.getActiveFit())

View File

@@ -96,7 +96,8 @@ class EveFittings(wx.Frame):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()
@@ -250,7 +251,8 @@ class ExportToEve(wx.Frame):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()
@@ -353,7 +355,8 @@ class SsoCharacterMgmt(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -35,9 +34,8 @@ class CalcAddBoosterCommand(wx.Command):
self.oldPosition, self.oldBoosterInfo = fit.boosters.makeRoom(newBooster)
if self.newPosition is not None:
try:
fit.boosters.insert(self.newPosition, newBooster)
except HandledListActionError:
fit.boosters.insert(self.newPosition, newBooster)
if newBooster not in fit.boosters:
pyfalog.warning('Failed to insert to list')
cmd = CalcAddBoosterCommand(
fitID=self.fitID,
@@ -47,9 +45,8 @@ class CalcAddBoosterCommand(wx.Command):
cmd.Do()
return False
else:
try:
fit.boosters.append(newBooster)
except HandledListActionError:
fit.boosters.append(newBooster)
if newBooster not in fit.boosters:
pyfalog.warning('Failed to append to list')
cmd = CalcAddBoosterCommand(
fitID=self.fitID,

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -25,9 +24,8 @@ class CalcAddCargoCommand(wx.Command):
cargo.amount += self.cargoInfo.amount
else:
cargo = self.cargoInfo.toCargo()
try:
fit.cargo.append(cargo)
except HandledListActionError:
fit.cargo.append(cargo)
if cargo not in fit.cargo:
pyfalog.warning('Failed to append to list')
if self.commit:
eos.db.commit()

View File

@@ -3,7 +3,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import DroneInfo, droneStackLimit
from service.fit import Fit
from service.market import Market
@@ -50,9 +49,8 @@ class CalcAddLocalDroneCommand(wx.Command):
if not self.ignoreRestrictions and not drone.fits(fit):
pyfalog.warning('Drone does not fit')
return False
try:
fit.drones.append(drone)
except HandledListActionError:
fit.drones.append(drone)
if drone not in fit.drones:
pyfalog.warning('Failed to append to list')
if self.commit:
eos.db.commit()

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import DroneInfo
from service.fit import Fit
@@ -48,9 +47,8 @@ class CalcRemoveLocalDroneCommand(wx.Command):
drone = self.savedDroneInfo.toDrone()
if drone is None:
return False
try:
fit.drones.insert(self.position, drone)
except HandledListActionError:
fit.drones.insert(self.position, drone)
if drone not in fit.drones:
pyfalog.warning('Failed to insert to list')
if self.commit:
eos.db.commit()

View File

@@ -4,7 +4,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import DroneInfo
from service.fit import Fit
@@ -43,9 +42,8 @@ class CalcAddProjectedDroneCommand(wx.Command):
if not drone.item.isType('projected'):
pyfalog.debug('Drone is not projectable')
return False
try:
fit.projectedDrones.append(drone)
except HandledListActionError:
fit.projectedDrones.append(drone)
if drone not in fit.projectedDrones:
pyfalog.warning('Failed to append to list')
if self.commit:
eos.db.commit()

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -41,18 +40,16 @@ class CalcAddLocalFighterCommand(wx.Command):
fighter.active = True
if self.position is None:
try:
fit.fighters.append(fighter)
except HandledListActionError:
fit.fighters.append(fighter)
if fighter not in fit.fighters:
pyfalog.warning('Failed to append to list')
if self.commit:
eos.db.commit()
return False
self.position = fit.fighters.index(fighter)
else:
try:
fit.fighters.insert(self.position, fighter)
except HandledListActionError:
fit.fighters.insert(self.position, fighter)
if fighter not in fit.fighters:
pyfalog.warning('Failed to insert to list')
if self.commit:
eos.db.commit()

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -25,16 +24,14 @@ class CalcAddProjectedFighterCommand(wx.Command):
return False
fit = Fit.getInstance().getFit(self.fitID)
if self.position is not None:
try:
fit.projectedFighters.insert(self.position, fighter)
except HandledListActionError:
fit.projectedFighters.insert(self.position, fighter)
if fighter not in fit.projectedFighters:
if self.commit:
eos.db.commit()
return False
else:
try:
fit.projectedFighters.append(fighter)
except HandledListActionError:
fit.projectedFighters.append(fighter)
if fighter not in fit.projectedFighters:
if self.commit:
eos.db.commit()
return False

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -35,9 +34,8 @@ class CalcAddImplantCommand(wx.Command):
self.oldPosition, self.oldImplantInfo = fit.implants.makeRoom(newImplant)
if self.newPosition is not None:
try:
fit.implants.insert(self.newPosition, newImplant)
except HandledListActionError:
fit.implants.insert(self.newPosition, newImplant)
if newImplant not in fit.implants:
pyfalog.warning('Failed to insert to list')
cmd = CalcAddImplantCommand(
fitID=self.fitID,
@@ -47,9 +45,8 @@ class CalcAddImplantCommand(wx.Command):
cmd.Do()
return False
else:
try:
fit.implants.append(newImplant)
except HandledListActionError:
fit.implants.append(newImplant)
if newImplant not in fit.implants:
pyfalog.warning('Failed to append to list')
cmd = CalcAddImplantCommand(
fitID=self.fitID,

View File

@@ -2,8 +2,7 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import restoreCheckedStates, stateLimit
from gui.fitCommands.helpers import restoreCheckedStates, activeStateLimit
from service.fit import Fit
@@ -26,7 +25,7 @@ class CalcAddLocalModuleCommand(wx.Command):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
newMod = self.newModInfo.toModule(fallbackState=stateLimit(self.newModInfo.itemID))
newMod = self.newModInfo.toModule(fallbackState=activeStateLimit(self.newModInfo.itemID))
if newMod is None:
return False
@@ -55,9 +54,8 @@ class CalcAddLocalModuleCommand(wx.Command):
if not newMod.fits(fit):
pyfalog.warning('Module does not fit')
return False
try:
fit.modules.append(newMod)
except HandledListActionError:
fit.modules.append(newMod)
if newMod not in fit.modules:
pyfalog.warning('Failed to append to list')
if self.commit:
eos.db.commit()

View File

@@ -4,7 +4,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import restoreCheckedStates
from service.fit import Fit
@@ -31,9 +30,8 @@ class CalcCloneLocalModuleCommand(wx.Command):
return False
if not fit.modules[self.dstPosition].isEmpty:
return False
try:
fit.modules.replace(self.dstPosition, copyMod)
except HandledListActionError:
fit.modules.replace(self.dstPosition, copyMod)
if copyMod not in fit.modules:
pyfalog.warning('Failed to replace module')
eos.db.commit()
return False

View File

@@ -2,8 +2,7 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates, stateLimit
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates, activeStateLimit
from service.fit import Fit
@@ -34,7 +33,7 @@ class CalcReplaceLocalModuleCommand(wx.Command):
self.oldModInfo = ModuleInfo.fromModule(oldMod)
if self.newModInfo == self.oldModInfo:
return False
newMod = self.newModInfo.toModule(fallbackState=stateLimit(self.newModInfo.itemID))
newMod = self.newModInfo.toModule(fallbackState=activeStateLimit(self.newModInfo.itemID))
if newMod is None:
return False
if newMod.slot != oldMod.slot:
@@ -53,9 +52,8 @@ class CalcReplaceLocalModuleCommand(wx.Command):
pyfalog.warning('Invalid charge')
self.Undo()
return False
try:
fit.modules.replace(self.position, newMod)
except HandledListActionError:
fit.modules.replace(self.position, newMod)
if newMod not in fit.modules:
pyfalog.warning('Failed to replace in list')
self.Undo()
return False
@@ -87,9 +85,8 @@ class CalcReplaceLocalModuleCommand(wx.Command):
if oldMod is None:
return False
fit.modules.free(self.position)
try:
fit.modules.replace(self.position, oldMod)
except HandledListActionError:
fit.modules.replace(self.position, oldMod)
if oldMod not in fit.modules:
pyfalog.warning('Failed to replace in list')
self.Do()
return False

View File

@@ -2,7 +2,6 @@ import wx
from logbook import Logger
import eos.db
from eos.exception import HandledListActionError
from service.fit import Fit
@@ -33,16 +32,14 @@ class CalcSwapLocalModuleCommand(wx.Command):
mod2 = fit.modules[position2]
fit.modules.free(position1)
fit.modules.free(position2)
try:
fit.modules.replace(position2, mod1)
except HandledListActionError:
fit.modules.replace(position2, mod1)
if len(fit.modules) <= position2 or fit.modules[position2] is not mod1:
fit.modules.replace(position1, mod1)
fit.modules.replace(position2, mod2)
eos.db.commit()
return False
try:
fit.modules.replace(position1, mod2)
except HandledListActionError:
fit.modules.replace(position1, mod2)
if len(fit.modules) <= position1 or fit.modules[position1] is not mod2:
fit.modules.free(position2)
fit.modules.replace(position1, mod1)
fit.modules.replace(position2, mod2)

View File

@@ -3,7 +3,6 @@ from logbook import Logger
import eos.db
from eos.const import FittingModuleState
from eos.exception import HandledListActionError
from gui.fitCommands.helpers import restoreCheckedStates
from service.fit import Fit
@@ -39,16 +38,14 @@ class CalcAddProjectedModuleCommand(wx.Command):
self.oldPosition, self.oldModInfo = fit.projectedModules.makeRoom(newMod)
if self.newPosition is not None:
try:
fit.projectedModules.insert(self.newPosition, newMod)
except HandledListActionError:
fit.projectedModules.insert(self.newPosition, newMod)
if newMod not in fit.projectedModules:
if self.commit:
eos.db.commit()
return False
else:
try:
fit.projectedModules.append(newMod)
except HandledListActionError:
fit.projectedModules.append(newMod)
if newMod not in fit.projectedModules:
if self.commit:
eos.db.commit()
return False

View File

@@ -317,9 +317,12 @@ class CargoInfo:
return makeReprStr(self, ['itemID', 'amount'])
def stateLimit(itemIdentity):
def activeStateLimit(itemIdentity):
item = Market.getInstance().getItem(itemIdentity)
if {'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability'}.intersection(item.effects):
if {
'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
'microJumpDrive', 'microJumpPortalDrive'
}.intersection(item.effects):
return FittingModuleState.ONLINE
return FittingModuleState.ACTIVE

View File

@@ -34,6 +34,10 @@ class Graph(object):
def getIcons(self):
return None
@property
def redrawOnEffectiveChange(self):
return False
# noinspection PyUnresolvedReferences
from gui.builtinGraphs import fitDps # noqa: E402, F401
from gui.builtinGraphs import *

View File

@@ -18,18 +18,19 @@
# =============================================================================
import os
from logbook import Logger
import traceback
# noinspection PyPackageRequirements
import wx
from logbook import Logger
from service.fit import Fit
import gui.display
import gui.mainFrame
import gui.globalEvents as GE
from gui.graph import Graph
import gui.mainFrame
from gui.bitmap_loader import BitmapLoader
import traceback
from gui.graph import Graph
from service.fit import Fit
pyfalog = Logger(__name__)
@@ -69,6 +70,7 @@ except Exception:
class GraphFrame(wx.Frame):
def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT):
global graphFrame_enabled
global mplImported
global mpl_version
@@ -159,10 +161,19 @@ class GraphFrame(wx.Frame):
self.mainSizer.Add(self.sl1, 0, wx.EXPAND)
self.mainSizer.Add(self.fitList, 0, wx.EXPAND)
self.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
self.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
self.fitList.fitList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
self.mainFrame.Bind(GE.FIT_CHANGED, self.draw)
self.Bind(wx.EVT_CLOSE, self.closeEvent)
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
self.Bind(wx.EVT_CHOICE, self.graphChanged)
from gui.builtinStatsViews.resistancesViewFull import EFFECTIVE_HP_TOGGLED # Grr crclar gons
self.mainFrame.Bind(EFFECTIVE_HP_TOGGLED, self.ehpToggled)
self.contextMenu = wx.Menu()
removeItem = wx.MenuItem(self.contextMenu, 1, 'Remove Fit')
self.contextMenu.Append(removeItem)
self.contextMenu.Bind(wx.EVT_MENU, self.ContextMenuHandler, removeItem)
self.Fit()
self.SetMinSize(self.GetSize())
@@ -177,14 +188,41 @@ class GraphFrame(wx.Frame):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
self.fitList.fitList.selectAll()
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
self.removeFits(self.getSelectedFits())
event.Skip()
def OnContextMenu(self, event):
if self.getSelectedFits():
self.PopupMenu(self.contextMenu)
def ContextMenuHandler(self, event):
selectedMenuItem = event.GetId()
if selectedMenuItem == 1: # Copy was chosen
fits = self.getSelectedFits()
self.removeFits(fits)
def ehpToggled(self, event):
event.Skip()
view = self.getView()
if view.redrawOnEffectiveChange:
self.draw()
def graphChanged(self, event):
self.select(self.graphSelection.GetSelection())
event.Skip()
def closeWindow(self):
self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.removeItem)
from gui.builtinStatsViews.resistancesViewFull import EFFECTIVE_HP_TOGGLED # Grr crclar gons
self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.OnLeftDClick)
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.draw)
self.mainFrame.Unbind(EFFECTIVE_HP_TOGGLED, handler=self.ehpToggled)
self.Destroy()
def getView(self):
@@ -202,6 +240,7 @@ class GraphFrame(wx.Frame):
icons = view.getIcons()
labels = view.getLabels()
sizer = self.gridSizer
sizer.Clear()
self.gridPanel.DestroyChildren()
self.fields.clear()
@@ -237,6 +276,7 @@ class GraphFrame(wx.Frame):
imgLabelSizer.Add(wx.StaticText(self.gridPanel, wx.ID_ANY, label), 0,
wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
sizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL)
sizer.Layout()
self.draw()
def draw(self, event=None):
@@ -257,6 +297,8 @@ class GraphFrame(wx.Frame):
self.subplot.grid(True)
legend = []
min_y = 0
max_y = 0
for fit in self.fits:
try:
success, status = view.getPoints(fit, values)
@@ -266,6 +308,8 @@ class GraphFrame(wx.Frame):
return
x, y = success, status
min_y = min(min_y, min(y, default=0))
max_y = max(max_y, max(y, default=0))
self.subplot.plot(x, y)
legend.append(fit.name)
@@ -275,6 +319,14 @@ class GraphFrame(wx.Frame):
self.canvas.draw()
return
y_range = max_y - min_y
min_y -= y_range * 0.05
max_y += y_range * 0.05
if min_y == max_y:
min_y -= 5
max_y += 5
self.subplot.set_ylim(bottom=min_y, top=max_y)
if mpl_version < 2:
if self.legendFix and len(legend) > 0:
leg = self.subplot.legend(tuple(legend), "upper right", shadow=False)
@@ -334,15 +386,38 @@ class GraphFrame(wx.Frame):
self.fitList.fitList.update(self.fits)
self.draw()
def removeItem(self, event):
def OnLeftDClick(self, event):
row, _ = self.fitList.fitList.HitTest(event.Position)
if row != -1:
del self.fits[row]
self.fitList.fitList.update(self.fits)
self.draw()
try:
fit = self.fits[row]
except IndexError:
pass
else:
self.removeFits([fit])
def removeFits(self, fits):
toRemove = [f for f in fits if f in self.fits]
if not toRemove:
return
for fit in toRemove:
self.fits.remove(fit)
self.fitList.fitList.update(self.fits)
self.draw()
def getSelectedFits(self):
fits = []
for row in self.fitList.fitList.getSelectedRows():
try:
fit = self.fits[row]
except IndexError:
continue
fits.append(fit)
return fits
class FitList(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)

View File

@@ -140,14 +140,15 @@ class ItemStatsDialog(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()
def closeEvent(self, event):
self.closeWindow()
self.container.onParentClose()
self.closeWindow()
event.Skip()
def closeWindow(self):

View File

@@ -134,7 +134,7 @@ class MarketBrowser(wx.Panel):
@mode.setter
def mode(self, newMode):
oldMode = self.__mode
if newMode == oldMode:
if newMode == oldMode != 'search':
return
# Store meta button states when switching from normal
if oldMode == 'normal':

View File

@@ -276,7 +276,8 @@ class DmgPatternEditorDlg(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -82,7 +82,8 @@ class PreferenceDialog(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -94,7 +94,8 @@ class AttributeEditor(wx.Frame):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -296,7 +296,8 @@ class ResistsEditorDlg(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

View File

@@ -184,7 +184,8 @@ class ImplantSetEditorDlg(wx.Dialog):
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_ESCAPE:
mstate = wx.GetMouseState()
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
self.closeWindow()
return
event.Skip()

BIN
imgs/renders/24469@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
imgs/renders/24469@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
imgs/renders/24470@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
imgs/renders/24470@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
imgs/renders/24471@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
imgs/renders/24471@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -81,6 +81,14 @@ class PortEftRigSize(IntEnum):
CAPITAL = 4
@unique
class PortDnaOptions(IntEnum):
"""
Contains different types of items for DNA export
"""
FORMATTING = 1
@unique
class GuiAttrGroup(IntEnum):
"""
@@ -101,4 +109,4 @@ class GuiAttrGroup(IntEnum):
JUMP_SYSTEMS = auto()
PROPULSIONS = auto()
FIGHTERS = auto()
SHIP_GROUP = auto()
SHIP_GROUP = auto()

View File

@@ -84,7 +84,7 @@ capac: 2500mm Repeating Cannon
3500rt: 3500mm Artillery
caprt: 3500mm Artillery
dlp: Dual Light Pulse
gpl: Gatling Pulse Laser
# gpl: Gatling Pulse Laser - conflicts with giga pulse
sfp: Small Focused Pulse
fmp: Focused Medium Pulse
hpl: Heavy Pulse Laser #also catches dual heavy
@@ -110,7 +110,7 @@ hml: Heavy Missile Launcher #gets HAM + RHML launcher too?
rhml: Rapid Heavy Missile Launcher
cml: Cruise Missile Launcher
tpl: Torpedo Launcher
tl: Torpedo Launcher
# tl: Torpedo Launcher - conflicts with tracking link entry
rtl: Rapid Torpedo Launcher
hawml: Rapid Torpedo Launcher
hawtl: Rapid Torpedo Launcher
@@ -124,6 +124,7 @@ dd: Doomsday
rp: Reaper
disco: Smartbomb
sbomb: Smartbomb
fvb: Focused Void Bomb
# Propulsion Modules
ab: Afterburner

View File

@@ -23,6 +23,7 @@ from collections import OrderedDict
from logbook import Logger
from eos.const import FittingModuleState, FittingSlot
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.drone import Drone
@@ -30,13 +31,18 @@ from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit
from eos.saveddata.module import Module
from eos.saveddata.ship import Ship
from eos.const import FittingSlot, FittingModuleState
from gui.fitCommands.helpers import activeStateLimit
from service.const import PortDnaOptions
from service.fit import Fit as svcFit
from service.market import Market
pyfalog = Logger(__name__)
DNA_OPTIONS = (
(PortDnaOptions.FORMATTING, 'Formatting Tags', 'Include formatting tags to paste fit directly into corp bulletins, MOTD, etc.', True),
)
def importDna(string, fitName=None):
sMkt = Market.getInstance()
@@ -111,7 +117,7 @@ def importDna(string, fitName=None):
else:
m.owner = f
if m.isValidState(FittingModuleState.ACTIVE):
m.state = FittingModuleState.ACTIVE
m.state = activeStateLimit(m.item)
moduleList.append(m)
# Recalc to get slot numbers correct for T3 cruisers
@@ -123,18 +129,17 @@ def importDna(string, fitName=None):
if module.fits(f):
module.owner = f
if module.isValidState(FittingModuleState.ACTIVE):
module.state = FittingModuleState.ACTIVE
module.state = activeStateLimit(module.item)
f.modules.append(module)
return f
def exportDna(fit, callback):
def exportDna(fit, options, callback):
dna = str(fit.shipID)
subsystems = [] # EVE cares which order you put these in
mods = OrderedDict()
charges = OrderedDict()
sFit = svcFit.getInstance()
for mod in fit.modules:
if not mod.isEmpty:
if mod.slot == FittingSlot.SUBSYSTEM:
@@ -162,9 +167,6 @@ def exportDna(fit, callback):
for fighter in fit.fighters:
dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive)
for fighter in fit.fighters:
dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive)
for cargo in fit.cargo:
# DNA format is a simple/dumb format. As CCP uses the slot information of the item itself
# without designating slots in the DNA standard, we need to make sure we only include
@@ -181,6 +183,9 @@ def exportDna(fit, callback):
text = dna + "::"
if options[PortDnaOptions.FORMATTING]:
text = '<url=fitting:{}>{}</url>'.format(text, fit.name)
if callback:
callback(text)
else:

View File

@@ -356,7 +356,7 @@ class EfsPort:
"dps": stats.getDps(spoolOptions=spoolOptions).total * n, "capUse": stats.capUse * n, "falloff": stats.falloff,
"type": typeing, "name": name, "optimal": maxRange,
"numCharges": stats.numCharges, "numShots": stats.numShots, "reloadTime": stats.reloadTime,
"cycleTime": stats.cycleTime, "volley": stats.getVolley(spoolOptions=spoolOptions).total * n, "tracking": tracking,
"cycleTime": stats.getCycleParameters().averageTime, "volley": stats.getVolley(spoolOptions=spoolOptions).total * n, "tracking": tracking,
"maxVelocity": maxVelocity, "explosionDelay": explosionDelay, "damageReductionFactor": damageReductionFactor,
"explosionRadius": explosionRadius, "explosionVelocity": explosionVelocity, "aoeFieldRange": aoeFieldRange,
"damageMultiplierBonusMax": stats.getModifiedItemAttr("damageMultiplierBonusMax"),
@@ -369,7 +369,7 @@ class EfsPort:
# Drones are using the old tracking formula for trackingSpeed. This updates it to match turrets.
newTracking = droneAttr("trackingSpeed") / (droneAttr("optimalSigRadius") / 40000)
statDict = {
"dps": drone.getDps().total, "cycleTime": drone.cycleTime, "type": "Drone",
"dps": drone.getDps().total, "cycleTime": drone.getCycleParameters().averageTime, "type": "Drone",
"optimal": drone.maxRange, "name": drone.item.name, "falloff": drone.falloff,
"maxSpeed": droneAttr("maxVelocity"), "tracking": newTracking,
"volley": drone.getVolley().total
@@ -498,11 +498,11 @@ class EfsPort:
fitMultipliers["drones"] = list(map(getDroneMulti, tf.drones))
getFitTurrets = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.TURRET, f.modules)
getTurretMulti = lambda mod: mod.getModifiedItemAttr("damageMultiplier") / mod.cycleTime
getTurretMulti = lambda mod: mod.getModifiedItemAttr("damageMultiplier") / mod.getCycleParameters().averageTime
fitMultipliers["turrets"] = list(map(getTurretMulti, getFitTurrets(tf)))
getFitLaunchers = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.MISSILE, f.modules)
getLauncherMulti = lambda mod: sumDamage(mod.getModifiedChargeAttr) / mod.cycleTime
getLauncherMulti = lambda mod: sumDamage(mod.getModifiedChargeAttr) / mod.getCycleParameters().averageTime
fitMultipliers["launchers"] = list(map(getLauncherMulti, getFitLaunchers(tf)))
return fitMultipliers

View File

@@ -22,18 +22,19 @@ import re
from logbook import Logger
from eos.const import FittingModuleState, FittingSlot
from eos.db.gamedata.queries import getDynamicItem
from eos.saveddata.booster import Booster
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.booster import Booster
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit
from eos.saveddata.implant import Implant
from eos.saveddata.module import Module
from eos.saveddata.ship import Ship
from eos.saveddata.fit import Fit
from eos.const import FittingSlot, FittingModuleState
from service.const import PortEftOptions, PortEftRigSize
from gui.fitCommands.helpers import activeStateLimit
from service.const import PortEftOptions
from service.fit import Fit as svcFit
from service.market import Market
from service.port.muta import parseMutant, renderMutant
@@ -443,7 +444,7 @@ def importEftCfg(shipname, lines, iportuser):
m.owner = fitobj
# Activate mod if it is activable
if m.isValidState(FittingModuleState.ACTIVE):
m.state = FittingModuleState.ACTIVE
m.state = activeStateLimit(m.item)
# Add charge to mod if applicable, on any errors just don't add anything
if chargeName:
try:
@@ -804,7 +805,7 @@ class AbstractFit:
if itemSpec.offline and m.isValidState(FittingModuleState.OFFLINE):
m.state = FittingModuleState.OFFLINE
elif m.isValidState(FittingModuleState.ACTIVE):
m.state = FittingModuleState.ACTIVE
m.state = activeStateLimit(m.item)
return m
def addImplant(self, itemSpec):

View File

@@ -23,14 +23,15 @@ import json
from logbook import Logger
from eos.const import FittingModuleState, FittingSlot
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit
from eos.saveddata.module import Module
from eos.const import FittingSlot, FittingModuleState
from eos.saveddata.ship import Ship
from gui.fitCommands.helpers import activeStateLimit
from service.fit import Fit as svcFit
from service.market import Market
@@ -196,7 +197,7 @@ def importESI(string):
fitobj.modules.append(m)
else:
if m.isValidState(FittingModuleState.ACTIVE):
m.state = FittingModuleState.ACTIVE
m.state = activeStateLimit(m.item)
moduleList.append(m)

View File

@@ -272,8 +272,8 @@ class Port(object):
return importDna(string, fitName=fitName)
@staticmethod
def exportDna(fit, callback=None):
return exportDna(fit, callback=callback)
def exportDna(fit, options, callback=None):
return exportDna(fit, options, callback=callback)
# ESI-related methods
@staticmethod

View File

@@ -23,6 +23,7 @@ import xml.parsers.expat
from logbook import Logger
from eos.const import FittingModuleState, FittingSlot
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.drone import Drone
@@ -30,12 +31,11 @@ from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit
from eos.saveddata.module import Module
from eos.saveddata.ship import Ship
from eos.const import FittingSlot, FittingModuleState
from gui.fitCommands.helpers import activeStateLimit
from service.fit import Fit as svcFit
from service.market import Market
from utils.strfunctions import sequential_rep, replace_ltgt
from service.port.shared import IPortUser, processing_notify
from utils.strfunctions import replace_ltgt, sequential_rep
pyfalog = Logger(__name__)
@@ -200,7 +200,7 @@ def importXml(text, iportuser):
fitobj.modules.append(m)
else:
if m.isValidState(FittingModuleState.ACTIVE):
m.state = FittingModuleState.ACTIVE
m.state = activeStateLimit(m.item)
moduleList.append(m)

View File

@@ -1 +1 @@
version: v2.9.2
version: v2.9.4dev2