Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
813db9340f | ||
|
|
acbd8a3298 | ||
|
|
561e22e894 | ||
|
|
05ac0a528a | ||
|
|
c040353f6e | ||
|
|
f23a8fa0c8 | ||
|
|
ba93467646 | ||
|
|
00d480860f | ||
|
|
c94384acb8 | ||
|
|
0c2c0ac6ef | ||
|
|
61a33a331e | ||
|
|
e374a6f2c6 | ||
|
|
dbd84dce28 | ||
|
|
9d554f9c68 | ||
|
|
576cf56735 | ||
|
|
e2aaabbc16 | ||
|
|
ef226898c0 | ||
|
|
a0db235e5a | ||
|
|
5bf05ba775 | ||
|
|
c073b1fa2a | ||
|
|
5f58307bf3 | ||
|
|
8741b17a5e | ||
|
|
4c1fa09795 | ||
|
|
ce7df2d01f | ||
|
|
b433b0ea7c | ||
|
|
20868d6b44 | ||
|
|
33103dbee9 | ||
|
|
2a05ac5a85 | ||
|
|
a013828128 | ||
|
|
e19510b3d4 | ||
|
|
390f2048f2 | ||
|
|
0bb732300e | ||
|
|
fd017df561 | ||
|
|
0ed16b9a6f | ||
|
|
865978fcc1 | ||
|
|
a43f9930de | ||
|
|
c13cd23d54 | ||
|
|
ed1f52a114 | ||
|
|
7dd063f04e | ||
|
|
6e9fc1d1d9 | ||
|
|
cae0172e48 | ||
|
|
e2b492ee8d | ||
|
|
545ddc7492 | ||
|
|
d0b7c58a1d | ||
|
|
a9ad094422 | ||
|
|
68154333c2 | ||
|
|
5df2db5879 | ||
|
|
5a34db0d2f | ||
|
|
6f50be1e7e | ||
|
|
d15fefcf1b | ||
|
|
07bf1b400c | ||
|
|
9f975a958e | ||
|
|
c2a240bab0 | ||
|
|
40c3bf723f | ||
|
|
7a92ace2db | ||
|
|
500f5b8310 | ||
|
|
44830a4de6 | ||
|
|
f3f13e7ba8 | ||
|
|
0269a64ae1 | ||
|
|
5d6cdcbd23 | ||
|
|
81906a7bd2 | ||
|
|
b25b038934 | ||
|
|
b469fa520e | ||
|
|
4f865896c7 | ||
|
|
3b50dddef2 | ||
|
|
380e9c2e87 | ||
|
|
1c1443c862 | ||
|
|
0529baac4a | ||
|
|
7dab220009 | ||
|
|
0ea0f8cdf2 | ||
|
|
eebd59413b | ||
|
|
f4a635eb43 | ||
|
|
0e57258cc5 | ||
|
|
67462c3278 | ||
|
|
fce8129fa2 | ||
|
|
1d76f3ec31 | ||
|
|
707dbeecf8 | ||
|
|
56672f5830 | ||
|
|
13a0bf9d42 | ||
|
|
1bff10c973 | ||
|
|
1d4aece7cc | ||
|
|
cdb79f499a | ||
|
|
837dbb3677 |
33
eos/calc.py
Normal file
33
eos/calc.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2019 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
if distance is None:
|
||||
return 1
|
||||
if srcFalloffRange > 0:
|
||||
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
||||
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
||||
return 0
|
||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||
elif distance <= srcOptimalRange:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
@@ -148,6 +148,7 @@ class CapSimulator:
|
||||
stability_precision = self.stability_precision
|
||||
period = self.period
|
||||
|
||||
activation = None
|
||||
iterations = 0
|
||||
|
||||
capCapacity = self.capacitorCapacity
|
||||
@@ -162,7 +163,12 @@ class CapSimulator:
|
||||
t_max = self.t_max
|
||||
|
||||
while 1:
|
||||
activation = pop(state)
|
||||
# Nothing to pop - might happen when no mods are activated, or when
|
||||
# only cap injectors are active (and are postponed by code below)
|
||||
try:
|
||||
activation = pop(state)
|
||||
except IndexError:
|
||||
break
|
||||
t_now, duration, capNeed, shot, clipSize, reloadTime, isInjector = activation
|
||||
|
||||
# Max time reached, stop simulation - we're stable
|
||||
@@ -275,7 +281,8 @@ class CapSimulator:
|
||||
activation[3] = shot
|
||||
|
||||
push(state, activation)
|
||||
push(state, activation)
|
||||
if activation is not None:
|
||||
push(state, activation)
|
||||
|
||||
# update instance with relevant results.
|
||||
self.t = t_last
|
||||
|
||||
@@ -90,9 +90,12 @@ class FittingHardpoint(IntEnum):
|
||||
|
||||
@unique
|
||||
class SpoolType(IntEnum):
|
||||
SCALE = 0 # [0..1]
|
||||
TIME = 1 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||
# Spool and cycle scale are different in case if max spool amount cannot
|
||||
# be divided by spool step without remainder
|
||||
SPOOL_SCALE = 0 # [0..1]
|
||||
CYCLE_SCALE = 1 # [0..1]
|
||||
TIME = 2 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 3 # Expressed in amount of cycles since spool up started
|
||||
|
||||
|
||||
@unique
|
||||
|
||||
25
eos/db/migrations/upgrade34.py
Normal file
25
eos/db/migrations/upgrade34.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Migration 34
|
||||
|
||||
- Adds projection range columns to projectable entities
|
||||
"""
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM projectedFits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM modules LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM drones LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE drones ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM fighters LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fighters ADD COLUMN projectionRange FLOAT;")
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy.orm import mapper, relation
|
||||
import datetime
|
||||
|
||||
@@ -33,7 +33,8 @@ drones_table = Table("drones", saveddata_meta,
|
||||
Column("amountActive", Integer, nullable=False),
|
||||
Column("projected", Boolean, default=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True)
|
||||
)
|
||||
|
||||
mapper(Drone, drones_table,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy.orm import mapper, relation
|
||||
import datetime
|
||||
|
||||
@@ -34,7 +34,8 @@ fighters_table = Table("fighters", saveddata_meta,
|
||||
Column("amount", Integer, nullable=False),
|
||||
Column("projected", Boolean, default=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
)
|
||||
|
||||
fighter_abilities_table = Table("fightersAbilities", saveddata_meta,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Float, String, Table
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
@@ -70,7 +70,8 @@ projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
Column("amount", Integer, nullable=False, default=1),
|
||||
Column("active", Boolean, nullable=False, default=1),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
)
|
||||
|
||||
commandFits_table = Table("commandFits", saveddata_meta,
|
||||
@@ -83,6 +84,7 @@ commandFits_table = Table("commandFits", saveddata_meta,
|
||||
|
||||
|
||||
class ProjectedFit:
|
||||
|
||||
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
||||
self.sourceID = sourceID
|
||||
self.source_fit = source_fit
|
||||
|
||||
@@ -42,6 +42,7 @@ modules_table = Table("modules", saveddata_meta,
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("spoolType", Integer, nullable=True),
|
||||
Column("spoolAmount", Float, nullable=True),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||
|
||||
mapper(Module, modules_table,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm.collections import collection
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -138,9 +139,10 @@ class HandledModuleList(HandledList):
|
||||
else:
|
||||
self.appendIgnoreEmpty(mod)
|
||||
|
||||
@collection.appender
|
||||
def appendIgnoreEmpty(self, mod):
|
||||
mod.position = len(self)
|
||||
HandledList.append(self, mod)
|
||||
super().append(mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
|
||||
|
||||
4889
eos/effects.py
4889
eos/effects.py
File diff suppressed because it is too large
Load Diff
@@ -530,6 +530,14 @@ class Item(EqBase):
|
||||
def isBooster(self):
|
||||
return self.group.name == 'Booster' and self.category.name == 'Implant'
|
||||
|
||||
@property
|
||||
def isStandup(self):
|
||||
if self.category.name == "Structure Module":
|
||||
return True
|
||||
if self.isFighter and {'fighterSquadronIsStandupLight', 'fighterSquadronIsStandupHeavy', 'fighterSquadronIsStandupSupport'}.intersection(self.attributes):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "Item(ID={}, name={}) at {}".format(
|
||||
self.ID, self.name, hex(id(self))
|
||||
|
||||
@@ -29,6 +29,7 @@ from eos.db.gamedata.queries import getAttributeInfo
|
||||
|
||||
defaultValuesCache = {}
|
||||
cappingAttrKeyCache = {}
|
||||
resistanceCache = {}
|
||||
|
||||
|
||||
def getAttrDefault(key, fallback=None):
|
||||
@@ -46,19 +47,23 @@ def getAttrDefault(key, fallback=None):
|
||||
|
||||
|
||||
def getResistanceAttrID(modifyingItem, effect):
|
||||
# If it doesn't exist on the effect, check the modifying modules attributes. If it's there, set it on the
|
||||
# effect for this session so that we don't have to look here again (won't always work when it's None, but
|
||||
# will catch most)
|
||||
if not effect.getattr('resistanceCalculated'):
|
||||
# If it doesn't exist on the effect, check the modifying module's attributes.
|
||||
# If it's there, cache it and return
|
||||
if effect.resistanceID:
|
||||
return effect.resistanceID
|
||||
cacheKey = (modifyingItem.item.ID, effect.ID)
|
||||
try:
|
||||
return resistanceCache[cacheKey]
|
||||
except KeyError:
|
||||
attrPrefix = effect.getattr('prefix')
|
||||
if attrPrefix:
|
||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None
|
||||
if not effect.resistanceID:
|
||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None
|
||||
resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None
|
||||
if not resistanceID:
|
||||
resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None
|
||||
else:
|
||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None
|
||||
effect.resistanceCalculated = True
|
||||
return effect.resistanceID
|
||||
resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None
|
||||
resistanceCache[cacheKey] = resistanceID
|
||||
return resistanceID
|
||||
|
||||
|
||||
class ItemAttrShortcut:
|
||||
@@ -67,14 +72,8 @@ class ItemAttrShortcut:
|
||||
return_value = self.itemModifiedAttributes.get(key)
|
||||
return return_value or default
|
||||
|
||||
def getModifiedItemAttrWithExtraMods(self, key, extraMultipliers=None, default=0):
|
||||
"""Returns attribute value with passed modifiers applied to it."""
|
||||
return_value = self.itemModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
|
||||
return return_value or default
|
||||
|
||||
def getModifiedItemAttrWithoutAfflictor(self, key, afflictor, default=0):
|
||||
"""Returns attribute value with passed afflictor modification removed."""
|
||||
return_value = self.itemModifiedAttributes.getWithoutAfflictor(key, afflictor)
|
||||
def getModifiedItemAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
|
||||
return_value = self.itemModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
|
||||
return return_value or default
|
||||
|
||||
def getItemBaseAttrValue(self, key, default=0):
|
||||
@@ -88,14 +87,8 @@ class ChargeAttrShortcut:
|
||||
return_value = self.chargeModifiedAttributes.get(key)
|
||||
return return_value or default
|
||||
|
||||
def getModifiedChargeAttrWithExtraMods(self, key, extraMultipliers=None, default=0):
|
||||
"""Returns attribute value with passed modifiers applied to it."""
|
||||
return_value = self.chargeModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
|
||||
return return_value or default
|
||||
|
||||
def getModifiedChargeAttrWithoutAfflictor(self, key, afflictor, default=0):
|
||||
"""Returns attribute value with passed modifiers applied to it."""
|
||||
return_value = self.chargeModifiedAttributes.getWithoutAfflictor(key, afflictor)
|
||||
def getModifiedChargeAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
|
||||
return_value = self.chargeModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
|
||||
return return_value or default
|
||||
|
||||
def getChargeBaseAttrValue(self, key, default=0):
|
||||
@@ -211,32 +204,11 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
# Original value is the least priority
|
||||
return self.getOriginal(key)
|
||||
|
||||
def getWithExtraMods(self, key, extraMultipliers=None, default=0):
|
||||
"""Copy of __getitem__ with some modifications."""
|
||||
if not extraMultipliers:
|
||||
return self.get(key, default=default)
|
||||
|
||||
val = self.__calculateValue(key, extraMultipliers=extraMultipliers)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Then in values which are not yet calculated
|
||||
if self.__intermediary:
|
||||
val = self.__intermediary.get(key)
|
||||
else:
|
||||
val = None
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Original value
|
||||
val = self.getOriginal(key)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Passed in default value
|
||||
return default
|
||||
|
||||
def getWithoutAfflictor(self, key, afflictor, default=0):
|
||||
def getExtended(self, key, extraMultipliers=None, ignoreAfflictors=None, default=0):
|
||||
"""
|
||||
Here we consider couple of parameters. If they affect final result, we do
|
||||
not store result, and if they are - we do.
|
||||
"""
|
||||
# Here we do not have support for preAssigns/forceds, as doing them would
|
||||
# mean that we have to store all of them in a list which increases memory use,
|
||||
# and we do not actually need those operators atm
|
||||
@@ -245,8 +217,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
ignorePenalizedMultipliers = {}
|
||||
postIncreaseAdjustment = 0
|
||||
for fit, afflictors in self.getAfflictions(key).items():
|
||||
for innerAfflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||
if innerAfflictor is afflictor:
|
||||
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||
if afflictor in ignoreAfflictors:
|
||||
if operator == Operator.MULTIPLY:
|
||||
if stackingGroup is None:
|
||||
multiplierAdjustment /= postResAmount
|
||||
@@ -257,29 +229,31 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
elif operator == Operator.POSTINCREASE:
|
||||
postIncreaseAdjustment -= postResAmount
|
||||
|
||||
if preIncreaseAdjustment == 0 and multiplierAdjustment == 1 and postIncreaseAdjustment == 0 and len(ignorePenalizedMultipliers) == 0:
|
||||
# If we apply no customizations - use regular getter
|
||||
if (
|
||||
not extraMultipliers and
|
||||
preIncreaseAdjustment == 0 and multiplierAdjustment == 1 and
|
||||
postIncreaseAdjustment == 0 and len(ignorePenalizedMultipliers) == 0
|
||||
):
|
||||
return self.get(key, default=default)
|
||||
|
||||
# Try to calculate custom values
|
||||
val = self.__calculateValue(
|
||||
key, preIncAdj=preIncreaseAdjustment, multAdj=multiplierAdjustment,
|
||||
key, extraMultipliers=extraMultipliers, preIncAdj=preIncreaseAdjustment, multAdj=multiplierAdjustment,
|
||||
postIncAdj=postIncreaseAdjustment, ignorePenMult=ignorePenalizedMultipliers)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Then in values which are not yet calculated
|
||||
# Then the same fallbacks as in regular getter
|
||||
if self.__intermediary:
|
||||
val = self.__intermediary.get(key)
|
||||
else:
|
||||
val = None
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Original value
|
||||
val = self.getOriginal(key)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Passed in default value
|
||||
return default
|
||||
|
||||
def __delitem__(self, key):
|
||||
@@ -584,10 +558,12 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if 'projected' not in effectType:
|
||||
return 1
|
||||
remoteResistID = getResistanceAttrID(modifyingItem=fit.getModifier(), effect=effect)
|
||||
if not remoteResistID:
|
||||
return 1
|
||||
attrInfo = getAttributeInfo(remoteResistID)
|
||||
# Get the attribute of the resist
|
||||
resist = fit.ship.itemModifiedAttributes[attrInfo.attributeName] or None
|
||||
return resist or 1.0
|
||||
return resist or 1
|
||||
|
||||
|
||||
class Affliction:
|
||||
|
||||
@@ -122,7 +122,7 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
(effect.isType("passive") or effect.isType("boosterSideEffect")):
|
||||
if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects:
|
||||
continue
|
||||
effect.handler(fit, self, ("booster",))
|
||||
effect.handler(fit, self, ("booster",), None)
|
||||
|
||||
@validates("ID", "itemID", "ammoID", "active")
|
||||
def validator(self, key, val):
|
||||
|
||||
@@ -422,7 +422,7 @@ class Skill(HandledItem):
|
||||
(not fit.isStructure or effect.isType("structure")) and \
|
||||
effect.activeByDefault:
|
||||
try:
|
||||
effect.handler(fit, self, ("skill",))
|
||||
effect.handler(fit, self, ("skill",), None)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -25,6 +26,7 @@ import eos.db
|
||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.utils.cycles import CycleInfo
|
||||
from eos.utils.default import DEFAULT
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
|
||||
|
||||
@@ -45,6 +47,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.amount = 0
|
||||
self.amountActive = 0
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
@@ -304,7 +307,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return True
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||
if self.projected or forceProjected:
|
||||
context = "projected", "drone"
|
||||
projected = True
|
||||
@@ -312,6 +315,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("drone",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
effect.activeByDefault and \
|
||||
@@ -319,36 +324,34 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
projected is False and effect.isType("passive")):
|
||||
# See GH issue #765
|
||||
if effect.getattr('grouped'):
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
else:
|
||||
i = 0
|
||||
while i != self.amountActive:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
i += 1
|
||||
|
||||
if self.charge:
|
||||
for effect in self.charge.effects.values():
|
||||
if effect.runTime == runTime and effect.activeByDefault:
|
||||
effect.handler(fit, self, ("droneCharge",))
|
||||
effect.handler(fit, self, ("droneCharge",), projectionRange)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Drone(self.item)
|
||||
copy.amount = self.amount
|
||||
copy.amountActive = self.amountActive
|
||||
copy.projectionRange = self.projectionRange
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self.amount
|
||||
amountActive = self.amountActive
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Drone.__init__(self, item)
|
||||
self.amount = amount
|
||||
self.amountActive = amountActive
|
||||
self.projectionRange = projectionRange
|
||||
|
||||
def fits(self, fit):
|
||||
fitDroneGroupLimits = set()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -27,8 +28,9 @@ 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.default import DEFAULT
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -47,6 +49,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
self.itemID = item.ID if item is not None else None
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.active = True
|
||||
|
||||
# -1 is a placeholder that represents max squadron size, which we may not know yet as ships may modify this with
|
||||
@@ -380,7 +383,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return True
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
@@ -391,6 +394,8 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("fighter",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
for ability in self.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
@@ -399,17 +404,11 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if effect.runTime == runTime and effect.activeByDefault and \
|
||||
((projected and effect.isType("projected")) or not projected):
|
||||
if ability.grouped:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
else:
|
||||
i = 0
|
||||
while i != self.amount:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
i += 1
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
@@ -419,18 +418,22 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
for ability in self.abilities:
|
||||
copyAbility = next(filter(lambda a: a.effectID == ability.effectID, copy.abilities))
|
||||
copyAbility.active = ability.active
|
||||
copy.projectionRange = self.projectionRange
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self._amount
|
||||
active = self.active
|
||||
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Fighter.__init__(self, item)
|
||||
self._amount = amount
|
||||
self.active = active
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in abilityEffectStates:
|
||||
ability.active = abilityEffectStates[ability.effectID]
|
||||
self.projectionRange = projectionRange
|
||||
|
||||
def fits(self, fit):
|
||||
# If ships doesn't support this type of fighter, don't add it
|
||||
|
||||
@@ -21,9 +21,9 @@ import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import asinh, log, sqrt
|
||||
|
||||
from logbook import Logger
|
||||
from math import asinh, log, sqrt
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
@@ -440,10 +440,8 @@ class Fit:
|
||||
return False
|
||||
|
||||
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
||||
if isinstance(self.ship, Citadel) and item.category.name != "Structure Module" or \
|
||||
not isinstance(self.ship, Citadel) and item.category.name == "Structure Module":
|
||||
if isinstance(self.ship, Citadel) is not item.isStandup:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def clear(self, projected=False, command=False):
|
||||
@@ -934,13 +932,20 @@ class Fit:
|
||||
To support a simpler way of doing self projections (so that we don't have to make a copy of the fit and
|
||||
recalculate), this function was developed to be a common source of projected effect application.
|
||||
"""
|
||||
c = chain(self.drones, self.fighters, self.modules)
|
||||
for item in c:
|
||||
for item in chain(self.drones, self.fighters):
|
||||
if item is not None:
|
||||
# apply effects onto target fit x amount of times
|
||||
for _ in range(projectionInfo.amount):
|
||||
targetFit.register(item, origin=self)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
item.calculateModifiedAttributes(
|
||||
targetFit, runTime, forceProjected=True,
|
||||
forcedProjRange=0)
|
||||
for mod in self.modules:
|
||||
for _ in range(projectionInfo.amount):
|
||||
targetFit.register(mod, origin=self)
|
||||
mod.calculateModifiedAttributes(
|
||||
targetFit, runTime, forceProjected=True,
|
||||
forcedProjRange=projectionInfo.projectionRange)
|
||||
|
||||
def fill(self):
|
||||
"""
|
||||
@@ -1219,8 +1224,8 @@ class Fit:
|
||||
# Signature reduction, uses the bomb formula as per CCP Larrikin
|
||||
if energyNeutralizerSignatureResolution:
|
||||
capNeed = capNeed * min(1, signatureRadius / energyNeutralizerSignatureResolution)
|
||||
|
||||
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
||||
if capNeed:
|
||||
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
||||
|
||||
def removeDrain(self, i):
|
||||
del self.__extraDrains[i]
|
||||
@@ -1320,8 +1325,8 @@ class Fit:
|
||||
"""Return how much cap regen do we gain from having this module"""
|
||||
currentRegen = self.calculateCapRecharge()
|
||||
nomodRegen = self.calculateCapRecharge(
|
||||
capacity=self.ship.getModifiedItemAttrWithoutAfflictor("capacitorCapacity", mod),
|
||||
rechargeRate=self.ship.getModifiedItemAttrWithoutAfflictor("rechargeRate", mod) / 1000.0)
|
||||
capacity=self.ship.getModifiedItemAttrExtended("capacitorCapacity", ignoreAfflictors=[mod]),
|
||||
rechargeRate=self.ship.getModifiedItemAttrExtended("rechargeRate", ignoreAfflictors=[mod]) / 1000.0)
|
||||
return currentRegen - nomodRegen
|
||||
|
||||
def getRemoteReps(self, spoolOptions=None):
|
||||
@@ -1671,6 +1676,8 @@ class Fit:
|
||||
copyProjectionInfo = fit.getProjectionInfo(fitCopy.ID)
|
||||
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
||||
copyProjectionInfo.active = originalProjectionInfo.active
|
||||
copyProjectionInfo.amount = originalProjectionInfo.amount
|
||||
copyProjectionInfo.projectionRange = originalProjectionInfo.projectionRange
|
||||
forceUpdateSavedata(fit)
|
||||
|
||||
return fitCopy
|
||||
|
||||
@@ -95,7 +95,7 @@ class Implant(HandledItem, ItemAttrShortcut):
|
||||
return
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and effect.isType("passive") and effect.activeByDefault:
|
||||
effect.handler(fit, self, ("implant",))
|
||||
effect.handler(fit, self, ("implant",), None)
|
||||
|
||||
@validates("fitID", "itemID", "active")
|
||||
def validator(self, key, val):
|
||||
|
||||
@@ -54,7 +54,7 @@ class Mode(ItemAttrShortcut, HandledItem):
|
||||
if self.item:
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and effect.activeByDefault:
|
||||
effect.handler(fit, self, context=("module",))
|
||||
effect.handler(fit, self, ("module",), None)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Mode(self.item)
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from logbook import Logger
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
@@ -28,6 +29,7 @@ from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, Modi
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||
from eos.utils.default import DEFAULT
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
@@ -90,6 +92,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__charge = None
|
||||
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.state = FittingModuleState.ONLINE
|
||||
self.build()
|
||||
|
||||
@@ -339,7 +342,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
duringAcceleration = maxVelocity / 2 * accelTime
|
||||
# Distance done after being at full speed
|
||||
fullSpeed = maxVelocity * (flightTime - accelTime)
|
||||
return duringAcceleration + fullSpeed
|
||||
maxRange = duringAcceleration + fullSpeed
|
||||
if 'fofMissileLaunching' in self.charge.effects:
|
||||
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
|
||||
if rangeLimit:
|
||||
maxRange = min(maxRange, rangeLimit)
|
||||
return maxRange
|
||||
|
||||
@property
|
||||
def falloff(self):
|
||||
@@ -428,7 +436,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__baseVolley = {}
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0) or self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
||||
# Some delay attributes have non-0 default value, so we have to pick according to effects
|
||||
if {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(self.item.effects):
|
||||
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0)
|
||||
elif {'doomsdayBeamDOT', 'doomsdaySlash', 'doomsdayConeDOT'}.intersection(self.item.effects):
|
||||
dmgDelay = self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
||||
else:
|
||||
dmgDelay = 0
|
||||
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
|
||||
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
|
||||
# Reaper DD can damage each target only once
|
||||
@@ -826,7 +840,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False, forcedProjRange=DEFAULT):
|
||||
# We will run the effect when two conditions are met:
|
||||
# 1: It makes sense to run the effect
|
||||
# The effect is either offline
|
||||
@@ -843,6 +857,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("module",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
if self.charge is not None:
|
||||
# fix for #82 and it's regression #106
|
||||
if not projected or (self.projected and not forceProjected) or gang:
|
||||
@@ -856,13 +872,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
(not gang or (gang and effect.isType("gang")))
|
||||
):
|
||||
contexts = ("moduleCharge",)
|
||||
# For gang effects, we pass in the effect itself as an argument. However, to avoid going through all
|
||||
# the effect definitions and defining this argument, do a simple try/catch here and be done with it.
|
||||
# @todo: possibly fix this
|
||||
try:
|
||||
effect.handler(fit, self, contexts, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, contexts)
|
||||
effect.handler(fit, self, contexts, projectionRange, effect=effect)
|
||||
|
||||
if self.item:
|
||||
if self.state >= FittingModuleState.OVERHEATED:
|
||||
@@ -872,7 +882,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
and not forceProjected \
|
||||
and effect.activeByDefault \
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange)
|
||||
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
@@ -882,10 +892,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
|
||||
and ((projected and effect.isType("projected")) or not projected) \
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
|
||||
def getCycleParameters(self, reloadOverride=None):
|
||||
"""Copied from new eos as well"""
|
||||
@@ -1016,6 +1023,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copy.state = self.state
|
||||
copy.spoolType = self.spoolType
|
||||
copy.spoolAmount = self.spoolAmount
|
||||
copy.projectionRange = self.projectionRange
|
||||
|
||||
for x in self.mutators.values():
|
||||
Mutator(copy, x.attribute, x.value)
|
||||
@@ -1025,10 +1033,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def rebase(self, item):
|
||||
state = self.state
|
||||
charge = self.charge
|
||||
spoolType = self.spoolType
|
||||
spoolAmount = self.spoolAmount
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
|
||||
self.state = state
|
||||
if self.isValidCharge(charge):
|
||||
self.charge = charge
|
||||
self.spoolType = spoolType
|
||||
self.spoolAmount = spoolAmount
|
||||
self.projectionRange = projectionRange
|
||||
for x in self.mutators.values():
|
||||
Mutator(self, x.attribute, x.value)
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
# skillbook modifiers will use the stale modifier value
|
||||
# GH issue #351
|
||||
fit.register(self)
|
||||
effect.handler(fit, self, ("ship",))
|
||||
effect.handler(fit, self, ("ship",), None)
|
||||
|
||||
def validateModeItem(self, item, owner=None):
|
||||
""" Checks if provided item is a valid mode """
|
||||
|
||||
@@ -77,6 +77,8 @@ class TargetProfile:
|
||||
|
||||
@signatureRadius.setter
|
||||
def signatureRadius(self, val):
|
||||
if val is not None and math.isinf(val):
|
||||
val = None
|
||||
self._signatureRadius = val
|
||||
|
||||
@property
|
||||
@@ -106,7 +108,7 @@ class TargetProfile:
|
||||
continue
|
||||
line = line.split('#', 1)[0] # allows for comments
|
||||
type, data = line.rsplit('=', 1)
|
||||
type, data = type.strip(), data.split(',')
|
||||
type, data = type.strip(), [d.strip() for d in data.split(',')]
|
||||
except:
|
||||
pyfalog.warning("Data isn't in correct format, continue to next line.")
|
||||
continue
|
||||
@@ -115,11 +117,13 @@ class TargetProfile:
|
||||
continue
|
||||
|
||||
numPatterns += 1
|
||||
name, data = data[0], data[1:5]
|
||||
name, dataRes, dataMisc = data[0], data[1:5], data[5:8]
|
||||
fields = {}
|
||||
|
||||
for index, val in enumerate(data):
|
||||
val = float(val)
|
||||
for index, val in enumerate(dataRes):
|
||||
val = float(val) if val else 0
|
||||
if math.isinf(val):
|
||||
val = 0
|
||||
try:
|
||||
assert 0 <= val <= 100
|
||||
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100
|
||||
@@ -127,7 +131,18 @@ class TargetProfile:
|
||||
pyfalog.warning("Caught unhandled exception in import patterns.")
|
||||
continue
|
||||
|
||||
if len(fields) == 4: # Avoid possible blank lines
|
||||
if len(dataMisc) == 3:
|
||||
for index, val in enumerate(dataMisc):
|
||||
try:
|
||||
fieldName = ("maxVelocity", "signatureRadius", "radius")[index]
|
||||
except IndexError:
|
||||
break
|
||||
val = float(val) if val else 0
|
||||
if fieldName != "signatureRadius" and math.isinf(val):
|
||||
val = 0
|
||||
fields[fieldName] = val
|
||||
|
||||
if len(fields) in (4, 7): # Avoid possible blank lines
|
||||
if name.strip() in lookup:
|
||||
pattern = lookup[name.strip()]
|
||||
pattern.update(**fields)
|
||||
@@ -142,20 +157,23 @@ class TargetProfile:
|
||||
|
||||
return patterns, numPatterns
|
||||
|
||||
EXPORT_FORMAT = "TargetProfile = %s,%.1f,%.1f,%.1f,%.1f\n"
|
||||
EXPORT_FORMAT = "TargetProfile = %s,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\n"
|
||||
|
||||
@classmethod
|
||||
def exportPatterns(cls, *patterns):
|
||||
out = "# Exported from pyfa\n#\n"
|
||||
out += "# Values are in following format:\n"
|
||||
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %]\n\n"
|
||||
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %],[Max velocity m/s],[Signature radius m],[Radius m]\n\n"
|
||||
for dp in patterns:
|
||||
out += cls.EXPORT_FORMAT % (
|
||||
dp.name,
|
||||
dp.emAmount * 100,
|
||||
dp.thermalAmount * 100,
|
||||
dp.kineticAmount * 100,
|
||||
dp.explosiveAmount * 100
|
||||
dp.explosiveAmount * 100,
|
||||
dp.maxVelocity,
|
||||
dp.signatureRadius,
|
||||
dp.radius
|
||||
)
|
||||
|
||||
return out.strip()
|
||||
|
||||
3
eos/utils/default.py
Normal file
3
eos/utils/default.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class DEFAULT:
|
||||
"""Singleton class to signify default argument value."""
|
||||
pass
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from collections import namedtuple
|
||||
|
||||
from eos.const import SpoolType
|
||||
@@ -36,15 +37,33 @@ def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAm
|
||||
"""
|
||||
if not modMaxValue or not modStepValue:
|
||||
return 0, 0, 0
|
||||
if spoolType == SpoolType.SCALE:
|
||||
cycles = int(floatUnerr(spoolAmount * modMaxValue / modStepValue))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
if spoolType == SpoolType.SPOOL_SCALE:
|
||||
# Find out at which point of spoolup scale we're on, find out how many cycles
|
||||
# is enough to reach it and recalculate spoolup value for that amount of cycles
|
||||
cycles = math.ceil(floatUnerr(modMaxValue * spoolAmount / modStepValue))
|
||||
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||
return spoolValue, cycles, cycles * modCycleTime
|
||||
elif spoolType == SpoolType.CYCLE_SCALE:
|
||||
# For cycle scale, find out max amount of cycles and scale against it
|
||||
cycles = round(spoolAmount * math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||
return spoolValue, cycles, cycles * modCycleTime
|
||||
elif spoolType == SpoolType.TIME:
|
||||
cycles = min(int(floatUnerr(spoolAmount / modCycleTime)), int(floatUnerr(modMaxValue / modStepValue)))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
cycles = min(
|
||||
# How many full cycles mod had by passed time
|
||||
math.floor(floatUnerr(spoolAmount / modCycleTime)),
|
||||
# Max amount of cycles
|
||||
math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||
return spoolValue, cycles, cycles * modCycleTime
|
||||
elif spoolType == SpoolType.CYCLES:
|
||||
cycles = min(int(spoolAmount), int(floatUnerr(modMaxValue / modStepValue)))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
cycles = min(
|
||||
# Consider full cycles only
|
||||
math.floor(spoolAmount),
|
||||
# Max amount of cycles
|
||||
math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||
return spoolValue, cycles, cycles * modCycleTime
|
||||
else:
|
||||
return 0, 0, 0
|
||||
|
||||
|
||||
@@ -20,20 +20,7 @@
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
if distance is None:
|
||||
return 1
|
||||
if srcFalloffRange > 0:
|
||||
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
||||
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
||||
return 0
|
||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||
elif distance <= srcOptimalRange:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||
@@ -63,3 +50,19 @@ def calculateMultiplier(multipliers):
|
||||
bonus = l[i]
|
||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||
return val
|
||||
|
||||
|
||||
def checkLockRange(src, distance):
|
||||
if distance is None:
|
||||
return True
|
||||
if GraphSettings.getInstance().get('ignoreLockRange'):
|
||||
return True
|
||||
return distance <= src.item.maxTargetRange
|
||||
|
||||
|
||||
def checkDroneControlRange(src, distance):
|
||||
if distance is None:
|
||||
return True
|
||||
if GraphSettings.getInstance().get('ignoreDCR'):
|
||||
return True
|
||||
return distance <= src.item.extraAttributes['droneControlRange']
|
||||
|
||||
@@ -21,37 +21,47 @@
|
||||
import math
|
||||
from functools import lru_cache
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from service.attribute import Attribute
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
applicationMap = {}
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.hardpoint == FittingHardpoint.MISSILE:
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# FoF missiles can shoot beyond lock range
|
||||
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
|
||||
applicationMap[mod] = getSmartbombMult(
|
||||
mod=mod,
|
||||
@@ -64,44 +74,58 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
elif mod.item.group.name == 'Structure Guided Bomb Launcher':
|
||||
applicationMap[mod] = getGuidedBombMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getGuidedBombMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
|
||||
applicationMap[mod] = getDoomsdayMult(
|
||||
mod=mod,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# Only single-target DDs need locks
|
||||
if not inLockRange and {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(mod.item.effects):
|
||||
applicationMap[mod] = 0
|
||||
else:
|
||||
applicationMap[mod] = getDoomsdayMult(
|
||||
mod=mod,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isDealingDamage():
|
||||
continue
|
||||
applicationMap[drone] = getDroneMult(
|
||||
drone=drone,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange and inDroneRange:
|
||||
applicationMap[drone] = getDroneMult(
|
||||
drone=drone,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[drone] = 0
|
||||
for fighter in src.item.activeFightersIter():
|
||||
if not fighter.isDealingDamage():
|
||||
continue
|
||||
for ability in fighter.abilities:
|
||||
if not ability.dealsDamage or not ability.active:
|
||||
continue
|
||||
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
||||
fighter=fighter,
|
||||
ability=ability,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# Bomb launching doesn't need locks
|
||||
if inLockRange or ability.effect.name == 'fighterAbilityLaunchBomb':
|
||||
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
||||
fighter=fighter,
|
||||
ability=ability,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[(fighter, ability.effectID)] = 0
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
for k, v in applicationMap.items():
|
||||
applicationMap[k] = floatUnerr(v)
|
||||
@@ -200,7 +224,11 @@ def getGuidedBombMult(mod, src, distance, tgtSigRadius):
|
||||
|
||||
|
||||
def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||
if distance is not None and distance > src.item.extraAttributes['droneControlRange']:
|
||||
if (
|
||||
distance is not None and (
|
||||
(not GraphSettings.getInstance().get('ignoreDCR') and distance > src.item.extraAttributes['droneControlRange']) or
|
||||
(not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange))
|
||||
):
|
||||
return 0
|
||||
droneSpeed = drone.getModifiedItemAttr('maxVelocity')
|
||||
# Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones
|
||||
|
||||
@@ -20,37 +20,85 @@
|
||||
|
||||
import math
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
|
||||
def _isRegularScram(mod):
|
||||
if not mod.item:
|
||||
return False
|
||||
if not {'warpScrambleBlockMWDWithNPCEffect', 'structureWarpScrambleBlockMWDWithNPCEffect'}.intersection(mod.item.effects):
|
||||
return False
|
||||
if not mod.getModifiedItemAttr('activationBlockedStrenght', 0):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _isHicScram(mod):
|
||||
if not mod.item:
|
||||
return False
|
||||
if 'warpDisruptSphere' not in mod.item.effects:
|
||||
return False
|
||||
if not mod.charge:
|
||||
return False
|
||||
if 'shipModuleFocusedWarpScramblingScript' not in mod.charge.effects:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def getScramRange(src):
|
||||
scramRange = None
|
||||
for mod in src.item.activeModulesIter():
|
||||
if _isRegularScram(mod) or _isHicScram(mod):
|
||||
scramRange = max(scramRange or 0, mod.maxRange or 0)
|
||||
return scramRange
|
||||
|
||||
|
||||
def getScrammables(tgt):
|
||||
scrammables = []
|
||||
if tgt.isFit:
|
||||
for mod in tgt.item.activeModulesIter():
|
||||
if not mod.item:
|
||||
continue
|
||||
if {'moduleBonusMicrowarpdrive', 'microJumpDrive', 'microJumpPortalDrive'}.intersection(mod.item.effects):
|
||||
scrammables.append(mod)
|
||||
return scrammables
|
||||
|
||||
|
||||
def getTackledSpeed(src, tgt, currentUntackledSpeed, srcScramRange, tgtScrammables, webMods, webDrones, webFighters, distance):
|
||||
# Can slow down non-immune ships and target profiles
|
||||
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
||||
return currentUnwebbedSpeed
|
||||
maxUnwebbedSpeed = tgt.getMaxVelocity()
|
||||
return currentUntackledSpeed
|
||||
maxUntackledSpeed = tgt.getMaxVelocity()
|
||||
# What's immobile cannot be slowed
|
||||
if maxUnwebbedSpeed == 0:
|
||||
return maxUnwebbedSpeed
|
||||
speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
|
||||
if maxUntackledSpeed == 0:
|
||||
return maxUntackledSpeed
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
speedRatio = currentUntackledSpeed / maxUntackledSpeed
|
||||
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
tgtScrammables = ()
|
||||
appliedMultipliers = {}
|
||||
# Modules first, they are applied always the same way
|
||||
for wData in webMods:
|
||||
appliedBoost = wData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=wData.optimal,
|
||||
srcFalloffRange=wData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
||||
# Modules first, they are always applied the same way
|
||||
if inLockRange:
|
||||
for wData in webMods:
|
||||
appliedBoost = wData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=wData.optimal,
|
||||
srcFalloffRange=wData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||
# Drones and fighters
|
||||
mobileWebs = []
|
||||
mobileWebs.extend(webFighters)
|
||||
# Drones have range limit
|
||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
||||
if inLockRange:
|
||||
mobileWebs.extend(webFighters)
|
||||
if inLockRange and inDroneRange:
|
||||
mobileWebs.extend(webDrones)
|
||||
atkRadius = src.getRadius()
|
||||
# As mobile webs either follow the target or stick to the attacking ship,
|
||||
@@ -60,8 +108,8 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
||||
for mwData in longEnoughMws:
|
||||
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
|
||||
mobileWebs.remove(mwData)
|
||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
||||
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||
# Apply remaining webs, from fastest to slowest
|
||||
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
||||
while mobileWebs:
|
||||
@@ -70,7 +118,7 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
||||
fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
|
||||
for mwData in fastestMws:
|
||||
# Faster than target or set to follow it - apply full slowdown
|
||||
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
|
||||
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentTackledSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
|
||||
appliedMwBoost = mwData.boost
|
||||
# Otherwise project from the center of the ship
|
||||
else:
|
||||
@@ -84,31 +132,37 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
||||
distance=rangeFactorDistance)
|
||||
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
|
||||
mobileWebs.remove(mwData)
|
||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
||||
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
return floatUnerr(currentWebbedSpeed)
|
||||
return floatUnerr(currentTackledSpeed)
|
||||
|
||||
|
||||
def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
||||
def getSigRadiusMult(src, tgt, tgtSpeed, srcScramRange, tgtScrammables, tpMods, tpDrones, tpFighters, distance):
|
||||
# Can blow non-immune ships and target profiles
|
||||
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
||||
return 1
|
||||
untpedSig = tgt.getSigRadius()
|
||||
# Modules
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
initSig = tgt.getSigRadius()
|
||||
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
tgtScrammables = ()
|
||||
# TPing modules
|
||||
appliedMultipliers = {}
|
||||
for tpData in tpMods:
|
||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=tpData.optimal,
|
||||
srcFalloffRange=tpData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||
# Drones and fighters
|
||||
if inLockRange:
|
||||
for tpData in tpMods:
|
||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=tpData.optimal,
|
||||
srcFalloffRange=tpData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||
# TPing drones
|
||||
mobileTps = []
|
||||
mobileTps.extend(tpFighters)
|
||||
# Drones have range limit
|
||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
||||
if inLockRange:
|
||||
mobileTps.extend(tpFighters)
|
||||
if inLockRange and inDroneRange:
|
||||
mobileTps.extend(tpDrones)
|
||||
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
||||
atkRadius = src.getRadius()
|
||||
@@ -127,9 +181,9 @@ def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
||||
srcFalloffRange=mtpData.falloff,
|
||||
distance=rangeFactorDistance)
|
||||
appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
|
||||
tpedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers)
|
||||
if tpedSig == math.inf and untpedSig == math.inf:
|
||||
modifiedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||
if modifiedSig == math.inf and initSig == math.inf:
|
||||
return 1
|
||||
mult = tpedSig / untpedSig
|
||||
mult = modifiedSig / initSig
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
return floatUnerr(mult)
|
||||
|
||||
@@ -24,7 +24,7 @@ from eos.utils.stats import DmgTypes
|
||||
from graphs.data.base import PointGetter, SmoothPointGetter
|
||||
from service.settings import GraphSettings
|
||||
from .calc.application import getApplicationPerKey
|
||||
from .calc.projected import getTpMult, getWebbedSpeed
|
||||
from .calc.projected import getScramRange, getScrammables, getTackledSpeed, getSigRadiusMult
|
||||
|
||||
|
||||
def applyDamage(dmgMap, applicationMap, tgtResists):
|
||||
@@ -54,7 +54,7 @@ class YDpsMixin:
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
|
||||
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isDealingDamage():
|
||||
continue
|
||||
@@ -88,7 +88,7 @@ class YVolleyMixin:
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
|
||||
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isDealingDamage():
|
||||
continue
|
||||
@@ -138,8 +138,11 @@ class XDistanceMixin(SmoothPointGetter):
|
||||
# Prepare time cache here because we need to do it only once,
|
||||
# and this function is called once per point info fetch
|
||||
self._prepareTimeCache(src=src, maxTime=miscParams['time'])
|
||||
applyProjected = GraphSettings.getInstance().get('applyProjected')
|
||||
return {
|
||||
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
|
||||
'applyProjected': applyProjected,
|
||||
'srcScramRange': getScramRange(src=src) if applyProjected else None,
|
||||
'tgtScrammables': getScrammables(tgt=tgt) if applyProjected else (),
|
||||
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
|
||||
'tgtResists': tgt.getResists()}
|
||||
|
||||
@@ -151,18 +154,22 @@ class XDistanceMixin(SmoothPointGetter):
|
||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||
tgtSpeed = getWebbedSpeed(
|
||||
tgtSpeed = getTackledSpeed(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
currentUnwebbedSpeed=tgtSpeed,
|
||||
currentUntackledSpeed=tgtSpeed,
|
||||
srcScramRange=commonData['srcScramRange'],
|
||||
tgtScrammables=commonData['tgtScrammables'],
|
||||
webMods=webMods,
|
||||
webDrones=webDrones,
|
||||
webFighters=webFighters,
|
||||
distance=distance)
|
||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
||||
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
tgtSpeed=tgtSpeed,
|
||||
srcScramRange=commonData['srcScramRange'],
|
||||
tgtScrammables=commonData['tgtScrammables'],
|
||||
tpMods=tpMods,
|
||||
tpDrones=tpDrones,
|
||||
tpFighters=tpFighters,
|
||||
@@ -189,21 +196,27 @@ class XTimeMixin(PointGetter):
|
||||
tgtSpeed = miscParams['tgtSpeed']
|
||||
tgtSigRadius = tgt.getSigRadius()
|
||||
if GraphSettings.getInstance().get('applyProjected'):
|
||||
srcScramRange = getScramRange(src=src)
|
||||
tgtScrammables = getScrammables(tgt=tgt)
|
||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||
tgtSpeed = getWebbedSpeed(
|
||||
tgtSpeed = getTackledSpeed(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
currentUnwebbedSpeed=tgtSpeed,
|
||||
currentUntackledSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
webMods=webMods,
|
||||
webDrones=webDrones,
|
||||
webFighters=webFighters,
|
||||
distance=miscParams['distance'])
|
||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
||||
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
tgtSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
tpMods=tpMods,
|
||||
tpDrones=tpDrones,
|
||||
tpFighters=tpFighters,
|
||||
@@ -303,21 +316,27 @@ class XTgtSpeedMixin(SmoothPointGetter):
|
||||
tgtSpeed = x
|
||||
tgtSigRadius = tgt.getSigRadius()
|
||||
if commonData['applyProjected']:
|
||||
srcScramRange = getScramRange(src=src)
|
||||
tgtScrammables = getScrammables(tgt=tgt)
|
||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||
tgtSpeed = getWebbedSpeed(
|
||||
tgtSpeed = getTackledSpeed(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
currentUnwebbedSpeed=tgtSpeed,
|
||||
currentUntackledSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
webMods=webMods,
|
||||
webDrones=webDrones,
|
||||
webFighters=webFighters,
|
||||
distance=miscParams['distance'])
|
||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
||||
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
tgtSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
tpMods=tpMods,
|
||||
tpDrones=tpDrones,
|
||||
tpFighters=tpFighters,
|
||||
@@ -347,21 +366,27 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
|
||||
tgtSpeed = miscParams['tgtSpeed']
|
||||
tgtSigMult = 1
|
||||
if GraphSettings.getInstance().get('applyProjected'):
|
||||
srcScramRange = getScramRange(src=src)
|
||||
tgtScrammables = getScrammables(tgt=tgt)
|
||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||
tgtSpeed = getWebbedSpeed(
|
||||
tgtSpeed = getTackledSpeed(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
currentUnwebbedSpeed=tgtSpeed,
|
||||
currentUntackledSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
webMods=webMods,
|
||||
webDrones=webDrones,
|
||||
webFighters=webFighters,
|
||||
distance=miscParams['distance'])
|
||||
tgtSigMult = getTpMult(
|
||||
tgtSigMult = getSigRadiusMult(
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
tgtSpeed=tgtSpeed,
|
||||
srcScramRange=srcScramRange,
|
||||
tgtScrammables=tgtScrammables,
|
||||
tpMods=tpMods,
|
||||
tpDrones=tpDrones,
|
||||
tpFighters=tpFighters,
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
import math
|
||||
|
||||
from graphs.calc import calculateMultiplier, calculateRangeFactor
|
||||
from eos.calc import calculateRangeFactor
|
||||
from graphs.calc import calculateMultiplier, checkLockRange, checkDroneControlRange
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
@@ -37,33 +38,37 @@ class Distance2NeutingStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'energyNosferatuFalloff' in mod.item.effects and mod.getModifiedItemAttr('nosOverride'):
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('powerTransferAmount') / self.__getDuration(mod) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'doomsdayAOENeut' in mod.item.effects:
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0))
|
||||
mod.falloff or 0, False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'entityEnergyNeutralizerFalloff' in drone.item.effects:
|
||||
neuts.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('energyNeutralizerAmount') / (drone.getModifiedItemAttr('energyNeutralizerDuration') / 1000) * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0),))
|
||||
math.inf, 0, True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityEnergyNeutralizer':
|
||||
nps = fighter.getModifiedItemAttr('fighterAbilityEnergyNeutralizerAmount') / (ability.cycleTime / 1000)
|
||||
neuts.append((
|
||||
nps * fighter.amount * resonance,
|
||||
math.inf, 0))
|
||||
math.inf, 0, True, False))
|
||||
return {'neuts': neuts}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
combinedStr = 0
|
||||
for strength, optimal, falloff in commonData['neuts']:
|
||||
for strength, optimal, falloff, needsLock, needsDcr in commonData['neuts']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
return combinedStr
|
||||
|
||||
@@ -84,28 +89,32 @@ class Distance2WebbingStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEWeb' in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteWebifierEntity' in drone.item.effects:
|
||||
webs.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('speedFactor') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||
webs.append((
|
||||
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amount * resonance,
|
||||
math.inf, 0, 'default'))
|
||||
math.inf, 0, 'default', True, False))
|
||||
return {'webs': webs}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['webs']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['webs']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -129,28 +138,32 @@ class Distance2EcmStrMaxGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
ecms.append((
|
||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'doomsdayAOEECM' in mod.item.effects:
|
||||
ecms.append((
|
||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0))
|
||||
mod.falloff or 0, False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'entityECMFalloff' in drone.item.effects:
|
||||
ecms.extend(drone.amountActive * ((
|
||||
max(drone.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0),))
|
||||
math.inf, 0, True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityECM':
|
||||
ecms.append((
|
||||
max(fighter.getModifiedItemAttr(a) for a in self.ECM_ATTRS_FIGHTERS) * fighter.amount * resonance,
|
||||
math.inf, 0))
|
||||
math.inf, 0, True, False))
|
||||
return {'ecms': ecms}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
combinedStr = 0
|
||||
for strength, optimal, falloff in commonData['ecms']:
|
||||
for strength, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
return combinedStr
|
||||
|
||||
@@ -168,23 +181,27 @@ class Distance2DampStrLockRangeGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
damps.append((
|
||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEDamp' in mod.item.effects:
|
||||
damps.append((
|
||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteSensorDampEntity' in drone.item.effects:
|
||||
damps.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'damps': damps}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['damps']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['damps']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -205,23 +222,27 @@ class Distance2TdStrOptimalGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
tds.append((
|
||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOETrack' in mod.item.effects:
|
||||
tds.append((
|
||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'npcEntityWeaponDisruptor' in drone.item.effects:
|
||||
tds.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'tds': tds}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['tds']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tds']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -243,20 +264,24 @@ class Distance2GdStrRangeGetter(SmoothPointGetter):
|
||||
gds.append((
|
||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOETrack' in mod.item.effects:
|
||||
gds.append((
|
||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
return {'gds': gds}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
velocityStrMults = {}
|
||||
timeStrMults = {}
|
||||
for velocityStr, timeStr, optimal, falloff, stackingGroup in commonData['gds']:
|
||||
for velocityStr, timeStr, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['gds']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
velocityStr *= rangeFactor
|
||||
timeStr *= rangeFactor
|
||||
@@ -281,23 +306,27 @@ class Distance2TpStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
tps.append((
|
||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEPaint' in mod.item.effects:
|
||||
tps.append((
|
||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteTargetPaintEntity' in drone.item.effects:
|
||||
tps.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'tps': tps}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['tps']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tps']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
|
||||
@@ -18,23 +18,32 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
|
||||
|
||||
def getApplicationPerKey(src, distance):
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
applicationMap = {}
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isRemoteRepping():
|
||||
continue
|
||||
applicationMap[mod] = 1 if distance is None else calculateRangeFactor(
|
||||
srcOptimalRange=mod.maxRange or 0,
|
||||
srcFalloffRange=mod.falloff or 0,
|
||||
distance=distance)
|
||||
if not inLockRange:
|
||||
applicationMap[mod] = 0
|
||||
else:
|
||||
applicationMap[mod] = calculateRangeFactor(
|
||||
srcOptimalRange=mod.maxRange or 0,
|
||||
srcFalloffRange=mod.falloff or 0,
|
||||
distance=distance)
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isRemoteRepping():
|
||||
continue
|
||||
applicationMap[drone] = 1 if distance is None or distance <= src.item.extraAttributes['droneControlRange'] else 0
|
||||
if not inLockRange or not inDroneRange:
|
||||
applicationMap[drone] = 0
|
||||
else:
|
||||
applicationMap[drone] = 1
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
for k, v in applicationMap.items():
|
||||
applicationMap[k] = floatUnerr(v)
|
||||
|
||||
@@ -50,7 +50,7 @@ class YRpsMixin:
|
||||
isAncShield = 'shipModuleAncillaryRemoteShieldBooster' in mod.item.effects
|
||||
isAncArmor = 'shipModuleAncillaryRemoteArmorRepairer' in mod.item.effects
|
||||
rpsMap[mod] = mod.getRemoteReps(
|
||||
spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False),
|
||||
spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False),
|
||||
reloadOverride=ancReload if (isAncShield or isAncArmor) else None)
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isRemoteRepping():
|
||||
|
||||
@@ -106,7 +106,9 @@ class GraphCanvasPanel(wx.Panel):
|
||||
legendData = []
|
||||
chosenX = self.graphFrame.ctrlPanel.xType
|
||||
chosenY = self.graphFrame.ctrlPanel.yType
|
||||
self.subplot.set(xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX), ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY))
|
||||
self.subplot.set(
|
||||
xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX),
|
||||
ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY))
|
||||
|
||||
mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues()
|
||||
view = self.graphFrame.getView()
|
||||
@@ -186,6 +188,7 @@ class GraphCanvasPanel(wx.Panel):
|
||||
if minX is not None and maxX is not None:
|
||||
minY = min(allYs, default=None)
|
||||
maxY = max(allYs, default=None)
|
||||
yDiff = (maxY or 0) - (minY or 0)
|
||||
xMark = max(min(self.xMark, maxX), minX)
|
||||
# If in top 10% of X coordinates, align labels differently
|
||||
if xMark > canvasMinX + 0.9 * (canvasMaxX - canvasMinX):
|
||||
@@ -212,14 +215,16 @@ class GraphCanvasPanel(wx.Panel):
|
||||
def addYMark(val):
|
||||
if val is None:
|
||||
return
|
||||
# Round according to shown Y range - the bigger the range,
|
||||
# the rougher the rounding
|
||||
if yDiff != 0:
|
||||
rounded = roundToPrec(val, 4, nsValue=yDiff)
|
||||
else:
|
||||
rounded = val
|
||||
# If due to some bug or insufficient plot density we're
|
||||
# out of bounds, do not add anything
|
||||
if minY <= val <= maxY:
|
||||
if abs(val) < 0.0001:
|
||||
val = 0
|
||||
else:
|
||||
val = roundToPrec(val, 4)
|
||||
yMarks.add(val)
|
||||
if minY <= val <= maxY or minY <= rounded <= maxY:
|
||||
yMarks.add(rounded)
|
||||
|
||||
for source, target in iterList:
|
||||
xs, ys = plotData[(source, target)]
|
||||
|
||||
@@ -38,6 +38,9 @@ from .ctrlPanel import GraphControlPanel
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
REDRAW_DELAY = 500
|
||||
|
||||
|
||||
class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
@@ -45,7 +48,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
pyfalog.warning('Matplotlib is not enabled. Skipping initialization.')
|
||||
return
|
||||
|
||||
super().__init__(parent, title='Graphs', style=wx.RESIZE_BORDER, size=(520, 390))
|
||||
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||
@@ -90,6 +93,9 @@ class GraphFrame(AuxiliaryFrame):
|
||||
self.mainFrame.Bind(GE.GRAPH_OPTION_CHANGED, self.OnGraphOptionChanged)
|
||||
self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.OnEffectiveHpToggled)
|
||||
|
||||
self.drawTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.OnDrawTimer, self.drawTimer)
|
||||
|
||||
self.Layout()
|
||||
self.UpdateWindowSize()
|
||||
self.draw()
|
||||
@@ -128,7 +134,10 @@ class GraphFrame(AuxiliaryFrame):
|
||||
for fitID in event.fitIDs:
|
||||
self.clearCache(reason=GraphCacheCleanupReason.fitChanged, extraData=fitID)
|
||||
self.ctrlPanel.OnFitChanged(event)
|
||||
self.draw()
|
||||
# Data has to be recalculated - delay redraw
|
||||
# to give time to finish UI update in main window
|
||||
self.drawTimer.Stop()
|
||||
self.drawTimer.Start(REDRAW_DELAY, True)
|
||||
|
||||
def OnFitRemoved(self, event):
|
||||
event.Skip()
|
||||
@@ -184,7 +193,10 @@ class GraphFrame(AuxiliaryFrame):
|
||||
self.ctrlPanel.refreshAxeLabels(restoreSelection=True)
|
||||
self.Layout()
|
||||
self.clearCache(reason=GraphCacheCleanupReason.hpEffectivityChanged)
|
||||
self.draw()
|
||||
# Data has to be recalculated - delay redraw
|
||||
# to give time to finish UI update in main window
|
||||
self.drawTimer.Stop()
|
||||
self.drawTimer.Start(REDRAW_DELAY, True)
|
||||
# Even if graph is not selected, keep it updated
|
||||
for idx in range(self.graphSelection.GetCount()):
|
||||
view = self.getView(idx=idx)
|
||||
@@ -202,6 +214,10 @@ class GraphFrame(AuxiliaryFrame):
|
||||
self.draw()
|
||||
event.Skip()
|
||||
|
||||
def OnDrawTimer(self, event):
|
||||
event.Skip()
|
||||
self.draw()
|
||||
|
||||
def OnClose(self, event):
|
||||
self.mainFrame.Unbind(GE.FIT_RENAMED, handler=self.OnFitRenamed)
|
||||
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.OnFitChanged)
|
||||
|
||||
@@ -54,10 +54,13 @@ class BaseWrapper:
|
||||
return self.item.name
|
||||
return ''
|
||||
|
||||
def getMaxVelocity(self, extraMultipliers=None):
|
||||
def getMaxVelocity(self, extraMultipliers=None, ignoreAfflictors=()):
|
||||
if self.isFit:
|
||||
if extraMultipliers:
|
||||
maxVelocity = self.item.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=extraMultipliers)
|
||||
if extraMultipliers or ignoreAfflictors:
|
||||
maxVelocity = self.item.ship.getModifiedItemAttrExtended(
|
||||
'maxVelocity',
|
||||
extraMultipliers=extraMultipliers,
|
||||
ignoreAfflictors=ignoreAfflictors)
|
||||
else:
|
||||
maxVelocity = self.item.ship.getModifiedItemAttr('maxVelocity')
|
||||
elif self.isProfile:
|
||||
@@ -68,10 +71,13 @@ class BaseWrapper:
|
||||
maxVelocity = None
|
||||
return maxVelocity
|
||||
|
||||
def getSigRadius(self, extraMultipliers=None):
|
||||
def getSigRadius(self, extraMultipliers=None, ignoreAfflictors=()):
|
||||
if self.isFit:
|
||||
if extraMultipliers:
|
||||
sigRadius = self.item.ship.getModifiedItemAttrWithExtraMods('signatureRadius', extraMultipliers=extraMultipliers)
|
||||
if extraMultipliers or ignoreAfflictors:
|
||||
sigRadius = self.item.ship.getModifiedItemAttrExtended(
|
||||
'signatureRadius',
|
||||
extraMultipliers=extraMultipliers,
|
||||
ignoreAfflictors=ignoreAfflictors)
|
||||
else:
|
||||
sigRadius = self.item.ship.getModifiedItemAttr('signatureRadius')
|
||||
elif self.isProfile:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinAdditionPanes.boosterView import BoosterView
|
||||
from gui.builtinAdditionPanes.cargoView import CargoView
|
||||
@@ -35,9 +36,10 @@ from gui.toggle_panel import TogglePanel
|
||||
|
||||
class AdditionsPane(TogglePanel):
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, mainFrame):
|
||||
|
||||
TogglePanel.__init__(self, parent, force_layout=1)
|
||||
self.mainFrame = mainFrame
|
||||
|
||||
self.SetLabel("Additions")
|
||||
pane = self.GetContentPanel()
|
||||
@@ -45,7 +47,7 @@ class AdditionsPane(TogglePanel):
|
||||
baseSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
pane.SetSizer(baseSizer)
|
||||
|
||||
self.notebook = ChromeNotebook(pane, False)
|
||||
self.notebook = ChromeNotebook(pane, can_add=False, tabWidthMode=1)
|
||||
self.notebook.SetMinSize((-1, 1000))
|
||||
|
||||
baseSizer.Add(self.notebook, 1, wx.EXPAND)
|
||||
@@ -83,6 +85,8 @@ class AdditionsPane(TogglePanel):
|
||||
self.notes = NotesView(self.notebook)
|
||||
self.notebook.AddPage(self.notes, "Notes", image=notesImg, closeable=False)
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged)
|
||||
|
||||
self.notebook.SetSelection(0)
|
||||
|
||||
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
|
||||
@@ -106,3 +110,21 @@ class AdditionsPane(TogglePanel):
|
||||
self.parent.SetSashInvisible(False)
|
||||
self.parent.SetMinimumPaneSize(200)
|
||||
self.parent.SetSashPosition(self.old_pos, True)
|
||||
|
||||
def OnFitChanged(self, event):
|
||||
event.Skip()
|
||||
activeFitID = self.mainFrame.getActiveFit()
|
||||
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||
return
|
||||
self.updateExtraText()
|
||||
|
||||
def updateExtraText(self):
|
||||
refresh = False
|
||||
for i in range(self.notebook.GetPageCount()):
|
||||
page = self.notebook.GetPage(i)
|
||||
if hasattr(page, 'getTabExtraText'):
|
||||
refresh = True
|
||||
self.notebook.SetPageTitleExtra(i, page.getTabExtraText() or '', refresh=False)
|
||||
if refresh:
|
||||
self.notebook.tabs_container.AdjustTabsSize()
|
||||
self.notebook.Refresh()
|
||||
|
||||
@@ -26,8 +26,10 @@ class AuxiliaryFrame(wx.Frame):
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None):
|
||||
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None, resizeable=False):
|
||||
baseStyle = wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU
|
||||
if resizeable:
|
||||
baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX
|
||||
kwargs = {
|
||||
'parent': parent,
|
||||
'style': baseStyle if style is None else baseStyle | style}
|
||||
|
||||
@@ -226,3 +226,23 @@ class BoosterView(d.Display):
|
||||
continue
|
||||
boosters.append(booster)
|
||||
return boosters
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active boosters
|
||||
if opt == 1:
|
||||
amount = len([b for b in fit.boosters if b.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of boosters
|
||||
elif opt == 2:
|
||||
amount = len(fit.boosters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -214,3 +214,19 @@ class CargoView(d.Display):
|
||||
continue
|
||||
cargos.append(cargo)
|
||||
return cargos
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Total amount of cargo items
|
||||
if opt in (1, 2):
|
||||
amount = len(fit.cargo)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -247,3 +247,27 @@ class CommandView(d.Display):
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitsCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
commandFitIDs=fitIDs))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active command fits
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for commandFit in fit.commandFits:
|
||||
info = commandFit.getCommandInfo(fitID)
|
||||
if info is not None and info.active:
|
||||
amount += 1
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of command fits
|
||||
elif opt == 2:
|
||||
amount = len(fit.commandFits)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -337,3 +337,27 @@ class DroneView(Display):
|
||||
continue
|
||||
drones.append(drone)
|
||||
return drones
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active drones
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for droneStack in fit.drones:
|
||||
amount += droneStack.amountActive
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of drones
|
||||
elif opt == 2:
|
||||
amount = 0
|
||||
for droneStack in fit.drones:
|
||||
amount += droneStack.amount
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -117,6 +117,26 @@ class FighterView(wx.Panel):
|
||||
|
||||
self.Refresh()
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active fighter squads
|
||||
if opt == 1:
|
||||
amount = len([f for f in fit.fighters if f.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of fighter squads
|
||||
elif opt == 2:
|
||||
amount = len(fit.fighters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class FighterDisplay(d.Display):
|
||||
|
||||
|
||||
@@ -101,6 +101,25 @@ class ImplantView(wx.Panel):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeImplantLocationCommand(
|
||||
fitID=fitID, source=ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active implants
|
||||
if opt == 1:
|
||||
amount = len([i for i in fit.appliedImplants if i.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of implants
|
||||
elif opt == 2:
|
||||
amount = len(fit.appliedImplants)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
class ImplantDisplay(d.Display):
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import gui.builtinAdditionPanes.droneView
|
||||
import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from eos.const import FittingModuleState
|
||||
from eos.saveddata.drone import Drone as EosDrone
|
||||
from eos.saveddata.fighter import Fighter as EosFighter
|
||||
from eos.saveddata.fit import Fit as EosFit
|
||||
@@ -74,7 +75,8 @@ class ProjectedView(d.Display):
|
||||
'Ammo Icon',
|
||||
'Base Icon',
|
||||
'Base Name',
|
||||
'Ammo']
|
||||
'Ammo',
|
||||
'Projection Range']
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
@@ -396,3 +398,34 @@ class ProjectedView(d.Display):
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
projectedFitIDs=fitIDs,
|
||||
amount=1))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active projected items
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for projectedFit in fit.projectedFits:
|
||||
info = projectedFit.getProjectionInfo(fitID)
|
||||
if info is not None and info.active:
|
||||
amount += 1
|
||||
amount += len([m for m in fit.projectedModules if m.state > FittingModuleState.OFFLINE])
|
||||
amount += len([d for d in fit.projectedDrones if d.amountActive > 0])
|
||||
amount += len([f for f in fit.projectedFighters if f.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of projected items
|
||||
elif opt == 2:
|
||||
amount = 0
|
||||
amount += len(fit.projectedFits)
|
||||
amount += len(fit.projectedModules)
|
||||
amount += len(fit.projectedDrones)
|
||||
amount += len(fit.projectedFighters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -22,6 +22,7 @@ from gui.builtinContextMenus import shipJump
|
||||
# Generic item manipulations
|
||||
from gui.builtinContextMenus import itemRemove
|
||||
from gui.builtinContextMenus import itemAmountChange
|
||||
from gui.builtinContextMenus import itemProjectionRange
|
||||
from gui.builtinContextMenus import droneSplitStack
|
||||
from gui.builtinContextMenus import itemVariationChange
|
||||
from gui.builtinContextMenus import moduleMutations
|
||||
@@ -44,8 +45,10 @@ from gui.builtinContextMenus import damagePatternChange
|
||||
from gui.builtinContextMenus import factorReload
|
||||
from gui.builtinContextMenus.targetProfile import switcher
|
||||
# Graph extra options
|
||||
from gui.builtinContextMenus import graphDmgIgnoreResists
|
||||
from gui.builtinContextMenus import graphDmgApplyProjected
|
||||
from gui.builtinContextMenus import graphDmgIgnoreResists
|
||||
from gui.builtinContextMenus import graphLockRange
|
||||
from gui.builtinContextMenus import graphDroneControlRange
|
||||
from gui.builtinContextMenus import graphDmgDroneMode
|
||||
# Additions panel menus
|
||||
from gui.builtinContextMenus import additionsExportSelection
|
||||
|
||||
@@ -69,7 +69,7 @@ class FighterAbilities(ContextMenuCombined):
|
||||
command = cmd.GuiToggleLocalFighterAbilityStateCommand
|
||||
if self.fighter in container:
|
||||
mainPosition = container.index(self.fighter)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(container, self.fighter)
|
||||
else:
|
||||
fighters = self.selection
|
||||
|
||||
@@ -17,7 +17,7 @@ class GraphDmgApplyProjectedMenu(ContextMenuUnconditional):
|
||||
return srcContext == 'dmgStatsGraph'
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Apply Attacker Webs and TPs'
|
||||
return 'Apply Projected Items'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
self.settings.set('applyProjected', not self.settings.get('applyProjected'))
|
||||
|
||||
30
gui/builtinContextMenus/graphDroneControlRange.py
Normal file
30
gui/builtinContextMenus/graphDroneControlRange.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
class GraphIgnoreDcrMenu(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = GraphSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
return srcContext in ('dmgStatsGraph', 'remoteRepsGraph', 'ewarStatsGraph')
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Ignore Drone Control Range'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
self.settings.set('ignoreDCR', not self.settings.get('ignoreDCR'))
|
||||
wx.PostEvent(self.mainFrame, GE.GraphOptionChanged())
|
||||
|
||||
def isChecked(self, i):
|
||||
return self.settings.get('ignoreDCR')
|
||||
|
||||
|
||||
GraphIgnoreDcrMenu.register()
|
||||
30
gui/builtinContextMenus/graphLockRange.py
Normal file
30
gui/builtinContextMenus/graphLockRange.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
class GraphIgnoreLockRangeMenu(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = GraphSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
return srcContext in ('dmgStatsGraph', 'remoteRepsGraph', 'ewarStatsGraph')
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Ignore Lock Range'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
self.settings.set('ignoreLockRange', not self.settings.get('ignoreLockRange'))
|
||||
wx.PostEvent(self.mainFrame, GE.GraphOptionChanged())
|
||||
|
||||
def isChecked(self, i):
|
||||
return self.settings.get('ignoreLockRange')
|
||||
|
||||
|
||||
GraphIgnoreLockRangeMenu.register()
|
||||
126
gui/builtinContextMenus/itemProjectionRange.py
Normal file
126
gui/builtinContextMenus/itemProjectionRange.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.fighter import Fighter as EosFighter
|
||||
from eos.saveddata.fit import Fit as EosFit
|
||||
from eos.saveddata.module import Module as EosModule
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarFighters, getSimilarModPositions
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class ChangeItemProjectionRange(ContextMenuCombined):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if srcContext not in ('projectedFit', 'projectedModule', 'projectedDrone', 'projectedFighter'):
|
||||
return False
|
||||
if mainItem is None:
|
||||
return False
|
||||
if getattr(mainItem, 'isExclusiveSystemEffect', False):
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return 'Change {} Range'.format(itmContext)
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, selection, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if isinstance(mainItem, EosFit):
|
||||
try:
|
||||
value = mainItem.getProjectionInfo(fitID).projectionRange
|
||||
except AttributeError:
|
||||
return
|
||||
else:
|
||||
value = mainItem.projectionRange
|
||||
if value is not None:
|
||||
value /= 1000
|
||||
with RangeChanger(self.mainFrame, value) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
|
||||
if cleanInput:
|
||||
try:
|
||||
cleanInputFloat = float(cleanInput)
|
||||
except ValueError:
|
||||
return
|
||||
newRange = cleanInputFloat * 1000
|
||||
else:
|
||||
newRange = None
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
items = selection
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
if isinstance(mainItem, EosModule):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedItemsProjectionRangeCommand(
|
||||
fitID=fitID, items=items, projectionRange=newRange))
|
||||
|
||||
|
||||
ChangeItemProjectionRange.register()
|
||||
|
||||
|
||||
class RangeChanger(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, value):
|
||||
super().__init__(parent, title='Change Projection Range', style=wx.DEFAULT_DIALOG_STYLE)
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, 'New Range, km:')
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
if value is None:
|
||||
value = ''
|
||||
else:
|
||||
if value == int(value):
|
||||
value = int(value)
|
||||
value = str(value)
|
||||
self.input.SetValue(value)
|
||||
self.input.SelectAll()
|
||||
|
||||
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
|
||||
|
||||
bSizer3 = wx.BoxSizer(wx.VERTICAL)
|
||||
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
|
||||
|
||||
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
|
||||
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
|
||||
|
||||
self.input.SetFocus()
|
||||
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
|
||||
self.SetSizer(bSizer1)
|
||||
self.CenterOnParent()
|
||||
self.Fit()
|
||||
|
||||
def processEnter(self, evt):
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
# checks to make sure it's valid number
|
||||
@staticmethod
|
||||
def onChar(event):
|
||||
key = event.GetKeyCode()
|
||||
|
||||
acceptable_characters = '1234567890.'
|
||||
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
||||
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
||||
event.Skip()
|
||||
return
|
||||
else:
|
||||
return False
|
||||
@@ -65,7 +65,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
def __handleModule(self, callingWindow, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.modules, mainItem)
|
||||
else:
|
||||
positions = []
|
||||
@@ -88,7 +88,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
def __handleFighter(self, callingWindow, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.fighters, mainItem)
|
||||
else:
|
||||
fighters = selection
|
||||
@@ -131,7 +131,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosModule):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
@@ -143,7 +143,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
else:
|
||||
|
||||
@@ -43,6 +43,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
self.mainItem = mainItem
|
||||
self.selection = selection
|
||||
self.srcContext = srcContext
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
@@ -51,6 +52,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
||||
self.moduleLookup = {}
|
||||
sFit = Fit.getInstance()
|
||||
sMkt = Market.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
def get_metalevel(x):
|
||||
@@ -61,7 +63,8 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def get_metagroup(x):
|
||||
# We want deadspace before officer mods
|
||||
remap = {5: 6, 6: 5}
|
||||
return remap.get(x.metaGroup.ID, x.metaGroup.ID) if x.metaGroup is not None else 0
|
||||
metaGroup = sMkt.getMetaGroupByItem(x)
|
||||
return remap.get(metaGroup.ID, metaGroup.ID) if metaGroup is not None else 0
|
||||
|
||||
def get_boosterrank(x):
|
||||
# If we're returning a lot of items, sort my name
|
||||
@@ -84,7 +87,9 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
bindmenu = m
|
||||
|
||||
# Do not show abyssal items
|
||||
items = list(i for i in self.mainVariations if i.metaGroup is None or i.metaGroup.ID != 15)
|
||||
items = list(
|
||||
i for i in self.mainVariations
|
||||
if sMkt.getMetaGroupByItem(i) is None or sMkt.getMetaGroupByItem(i).ID != 15)
|
||||
# Sort items by metalevel, and group within that metalevel
|
||||
# Sort all items by name first
|
||||
items.sort(key=lambda x: x.name)
|
||||
@@ -102,12 +107,13 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
group = None
|
||||
for item in items:
|
||||
# Apparently no metaGroup for the Tech I variant:
|
||||
metaGroup = sMkt.getMetaGroupByItem(item)
|
||||
if 'subSystem' in item.effects:
|
||||
thisgroup = item.marketGroup.marketGroupName
|
||||
elif item.metaGroup is None:
|
||||
elif metaGroup is None:
|
||||
thisgroup = 'Tech I'
|
||||
else:
|
||||
thisgroup = item.metaGroup.name
|
||||
thisgroup = metaGroup.name
|
||||
|
||||
if thisgroup != group and context not in ('implantItem', 'boosterItem'):
|
||||
group = thisgroup
|
||||
@@ -121,7 +127,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
self.moduleLookup[id] = item, context
|
||||
m.Append(mitem)
|
||||
mitem.Enable(fit.canFit(item))
|
||||
mitem.Enable(self.srcContext in ('projectedModule', 'projectedDrone', 'projectedFighter') or fit.canFit(item))
|
||||
|
||||
return m
|
||||
|
||||
@@ -148,7 +154,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.modules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
@@ -187,7 +193,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.fighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
@@ -240,7 +246,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleProjectedModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.projectedModules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
@@ -277,7 +283,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleProjectedFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.projectedFighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
|
||||
@@ -45,10 +45,10 @@ class ChangeModuleSpool(ContextMenuSingle):
|
||||
bindmenu = m
|
||||
|
||||
isNotDefault = self.mod.spoolType is not None and self.mod.spoolAmount is not None
|
||||
cycleDefault = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], True))[0]
|
||||
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]
|
||||
cycleDefault = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], True))[0]
|
||||
cycleCurrent = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], False))[0]
|
||||
cycleMin = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True))[0]
|
||||
cycleMax = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True))[0]
|
||||
cycleTotalMin = min(cycleDefault, cycleCurrent, cycleMin)
|
||||
cycleTotalMax = max(cycleDefault, cycleCurrent, cycleMax)
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ class PFGeneralPref(PreferenceView):
|
||||
'When disabled, reloads charges just in selected modules. Action can be reversed by holding Ctrl or Alt key while changing charge.'))
|
||||
mainSizer.Add(self.cbReloadAll, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
self.rbAddLabels = wx.RadioBox(panel, -1, "Extra info in Additions panel tab names", wx.DefaultPosition, wx.DefaultSize, ["None", "Quantity of active items", "Quantity of all items"], 1, wx.RA_SPECIFY_COLS)
|
||||
mainSizer.Add(self.rbAddLabels, 0, wx.EXPAND | wx.TOP | wx.RIGHT | wx.BOTTOM, 10)
|
||||
self.rbAddLabels.Bind(wx.EVT_RADIOBOX, self.OnAddLabelsChange)
|
||||
|
||||
self.sFit = Fit.getInstance()
|
||||
|
||||
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
|
||||
@@ -101,6 +105,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
|
||||
self.cbShowShipBrowserTooltip.SetValue(self.sFit.serviceFittingOptions["showShipBrowserTooltip"])
|
||||
self.cbReloadAll.SetValue(self.sFit.serviceFittingOptions["ammoChangeAll"])
|
||||
self.rbAddLabels.SetSelection(self.sFit.serviceFittingOptions["additionsLabels"])
|
||||
|
||||
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
||||
self.cbDefaultCharImplants.Bind(wx.EVT_CHECKBOX, self.OnCBDefaultCharImplantsStateChange)
|
||||
@@ -187,6 +192,13 @@ class PFGeneralPref(PreferenceView):
|
||||
def onCBReloadAll(self, event):
|
||||
self.sFit.serviceFittingOptions["ammoChangeAll"] = self.cbReloadAll.GetValue()
|
||||
|
||||
def OnAddLabelsChange(self, event):
|
||||
self.sFit.serviceFittingOptions["additionsLabels"] = event.GetInt()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.sFit.refreshFit(fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
|
||||
event.Skip()
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("prefs_settings", "gui")
|
||||
|
||||
|
||||
@@ -345,9 +345,7 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
self.deleteFit()
|
||||
else:
|
||||
with wx.MessageDialog(
|
||||
self,
|
||||
"Do you really want to delete this fit?",
|
||||
"Confirm Delete",
|
||||
self.GetTopLevelParent(), "Do you really want to delete this fit?", "Confirm Delete",
|
||||
wx.YES | wx.NO | wx.ICON_QUESTION
|
||||
) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
@@ -163,9 +163,9 @@ class FirepowerViewFull(StatsView):
|
||||
stats = (
|
||||
(
|
||||
"labelFullDpsWeapon",
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
3, 0, 0, "{}{} DPS"),
|
||||
(
|
||||
"labelFullDpsDrone",
|
||||
@@ -175,15 +175,15 @@ class FirepowerViewFull(StatsView):
|
||||
3, 0, 0, "{}{} DPS"),
|
||||
(
|
||||
"labelFullVolleyTotal",
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
3, 0, 0, "{}{}"),
|
||||
(
|
||||
"labelFullDpsTotal",
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
3, 0, 0, "{}{}"))
|
||||
|
||||
counter = 0
|
||||
|
||||
@@ -29,27 +29,27 @@ import eos.config
|
||||
stats = [
|
||||
(
|
||||
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).capacitor,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).capacitor,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).shield,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).shield,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).armor,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).armor,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).hull,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).hull,
|
||||
3, 0, 0)]
|
||||
|
||||
|
||||
|
||||
@@ -28,27 +28,27 @@ import eos.config
|
||||
stats = [
|
||||
(
|
||||
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).capacitor,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).capacitor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).capacitor,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).shield,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).shield,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).shield,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).armor,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).armor,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).armor,
|
||||
3, 0, 0),
|
||||
(
|
||||
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).hull,
|
||||
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, spool, False)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).hull,
|
||||
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).hull,
|
||||
3, 0, 0)]
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ class RechargeViewFull(StatsView):
|
||||
self.parent = parent
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.toggleEffective)
|
||||
self.effective = True
|
||||
|
||||
def getHeaderText(self, fit):
|
||||
return "Recharge rates"
|
||||
@@ -45,9 +44,15 @@ class RechargeViewFull(StatsView):
|
||||
width, height = self.parent.GetTextExtent(text)
|
||||
return width
|
||||
|
||||
@property
|
||||
def effective(self):
|
||||
try:
|
||||
return self.parent.nameViewMap['resistancesViewFull'].showEffective
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def toggleEffective(self, event):
|
||||
event.Skip()
|
||||
self.effective = event.effective
|
||||
sFit = Fit.getInstance()
|
||||
self.refreshPanel(sFit.getFit(self.mainFrame.getActiveFit()))
|
||||
|
||||
@@ -104,8 +109,7 @@ class RechargeViewFull(StatsView):
|
||||
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything interesting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
unit = " EHP/s" if self.parent.nameViewMap['resistancesViewFull'].showEffective else " HP/s"
|
||||
unit = " EHP/s" if self.effective else " HP/s"
|
||||
|
||||
for stability in ("reinforced", "sustained"):
|
||||
if stability == "reinforced" and fit is not None:
|
||||
|
||||
@@ -100,6 +100,10 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything interesting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
sensorValues = {
|
||||
"main": lambda: fit.scanStrength,
|
||||
"jamChance": lambda: fit.jamChance}
|
||||
|
||||
cargoNamesOrder = OrderedDict((
|
||||
("fleetHangarCapacity", "Fleet hangar"),
|
||||
("shipMaintenanceBayCapacity", "Maintenance bay"),
|
||||
@@ -117,8 +121,7 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
("specialSalvageHoldCapacity", "Salvage hold"),
|
||||
("specialCommandCenterHoldCapacity", "Command center hold"),
|
||||
("specialPlanetaryCommoditiesHoldCapacity", "Planetary goods hold"),
|
||||
("specialQuafeHoldCapacity", "Quafe hold")
|
||||
))
|
||||
("specialQuafeHoldCapacity", "Quafe hold")))
|
||||
|
||||
cargoValues = {
|
||||
"main": lambda: fit.ship.getModifiedItemAttr("capacity"),
|
||||
@@ -138,13 +141,12 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
"specialSalvageHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSalvageHoldCapacity"),
|
||||
"specialCommandCenterHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialCommandCenterHoldCapacity"),
|
||||
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
|
||||
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity")
|
||||
}
|
||||
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity")}
|
||||
|
||||
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),
|
||||
("labelRange", {"main": lambda: fit.maxTargetRange / 1000}, 3, 0, 0, "km"),
|
||||
("labelScanRes", {"main": lambda: fit.ship.getModifiedItemAttr("scanResolution")}, 3, 0, 0, "mm"),
|
||||
("labelSensorStr", {"main": lambda: fit.scanStrength}, 3, 0, 0, ""),
|
||||
("labelSensorStr", sensorValues, 3, 0, 0, ""),
|
||||
("labelCtrlRange", {"main": lambda: fit.extraAttributes["droneControlRange"] / 1000}, 3, 0, 0, "km"),
|
||||
("labelFullSpeed", {"main": lambda: fit.maxSpeed}, 3, 0, 0, "m/s"),
|
||||
("labelFullAlignTime", {"main": lambda: fit.alignTime}, 3, 0, 0, "s"),
|
||||
@@ -176,6 +178,15 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
unit))
|
||||
else:
|
||||
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
|
||||
elif labelName == "labelSensorStr":
|
||||
ecmChance = otherValues["jamChance"]
|
||||
ecmChance = round(ecmChance, 1)
|
||||
if ecmChance:
|
||||
label.SetLabel("{} ({}%)".format(
|
||||
formatAmount(mainValue, prec, lowest, highest),
|
||||
formatAmount(ecmChance, 3, 0, 0)))
|
||||
else:
|
||||
label.SetLabel("{}".format(formatAmount(mainValue, prec, lowest, highest)))
|
||||
else:
|
||||
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
|
||||
# Tooltip stuff
|
||||
@@ -195,10 +206,14 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
warpScrambleStatus = "Warp Core Strength: %.1f" % 0
|
||||
label.SetToolTip(wx.ToolTip("%s\n%s" % (maxWarpDistance, warpScrambleStatus)))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
ecmChance = otherValues["jamChance"]
|
||||
ecmChance = round(ecmChance, 1)
|
||||
if ecmChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: {}\n{}% chance to be jammed".format(
|
||||
fit.scanType,
|
||||
formatAmount(ecmChance, 3, 0, 0))))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
|
||||
label.SetToolTip(wx.ToolTip("Type: {}".format(fit.scanType)))
|
||||
elif labelName == "labelFullAlignTime":
|
||||
alignTime = "Align:\t%.3fs" % mainValue
|
||||
mass = 'Mass:\t{:,.0f}kg'.format(fit.ship.getModifiedItemAttr("mass"))
|
||||
@@ -225,14 +240,6 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
label.SetToolTip(wx.ToolTip("%s\n%s" % (maxWarpDistance, warpScrambleStatus)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit:
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelFullCargo":
|
||||
if fit:
|
||||
cachedCargo = self._cachedValues[counter]
|
||||
|
||||
@@ -84,11 +84,12 @@ class AttributeDisplay(ViewColumn):
|
||||
return ""
|
||||
|
||||
if self.info.name == "volume":
|
||||
str_ = (formatAmount(attr, 3, 0, 3))
|
||||
if hasattr(mod, "amount"):
|
||||
str_ += "m\u00B3 (%s m\u00B3)" % (formatAmount(attr * mod.amount, 3, 0, 3))
|
||||
attr = str_
|
||||
|
||||
if getattr(mod, "amount", 1) != 1:
|
||||
attr = "{} m\u00B3 ({} m\u00B3)".format(
|
||||
formatAmount(attr, 3, 0, 6),
|
||||
formatAmount(attr * mod.amount, 3, 0, 6))
|
||||
else:
|
||||
attr = "{} m\u00B3".format(formatAmount(attr, 3, 0, 6))
|
||||
if isinstance(attr, (float, int)):
|
||||
attr = (formatAmount(attr, 3, 0, 3))
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class DpsColumn(GraphColumn):
|
||||
|
||||
def _getValue(self, fit):
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
return fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total, None
|
||||
return fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total, None
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Declared DPS'
|
||||
@@ -102,7 +102,7 @@ class VolleyColumn(GraphColumn):
|
||||
|
||||
def _getValue(self, fit):
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
return fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total, None
|
||||
return fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total, None
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Declared volley'
|
||||
@@ -329,7 +329,7 @@ class ShieldRRColumn(GraphColumn):
|
||||
|
||||
def _getValue(self, fit):
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).shield, 'HP/s'
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).shield, 'HP/s'
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Declared shield repair speed'
|
||||
@@ -348,7 +348,7 @@ class ArmorRRColumn(GraphColumn):
|
||||
|
||||
def _getValue(self, fit):
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).armor, 'HP/s'
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).armor, 'HP/s'
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Declared armor repair speed'
|
||||
@@ -367,7 +367,7 @@ class HullRRColumn(GraphColumn):
|
||||
|
||||
def _getValue(self, fit):
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).hull, 'HP/s'
|
||||
return fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).hull, 'HP/s'
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Declared hull repair speed'
|
||||
|
||||
@@ -33,6 +33,7 @@ from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.builtinContextMenus.envEffectAdd import AddEnvironmentEffect
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
from service.fit import Fit as FitSvc
|
||||
from service.market import Market
|
||||
@@ -64,7 +65,11 @@ class BaseName(ViewColumn):
|
||||
return "%d/%d %s" % \
|
||||
(stuff.amount, stuff.getModifiedItemAttr("fighterSquadronMaxSize"), stuff.item.name)
|
||||
elif isinstance(stuff, Cargo):
|
||||
return "%dx %s" % (stuff.amount, stuff.item.name)
|
||||
if stuff.item.group.name in ("Cargo Container", "Secure Cargo Container", "Audit Log Secure Container", "Freight Container"):
|
||||
capacity = stuff.item.getAttribute('capacity')
|
||||
if capacity:
|
||||
return "{:d}x {} ({} m\u00B3)".format(stuff.amount, stuff.item.name, formatAmount(capacity, 3, 0, 6))
|
||||
return "{:d}x {}".format(stuff.amount, stuff.item.name)
|
||||
elif isinstance(stuff, Fit):
|
||||
if self.projectedView:
|
||||
# we need a little more information for the projected view
|
||||
|
||||
@@ -113,7 +113,7 @@ class Miscellanea(ViewColumn):
|
||||
info.append((text, tooltip))
|
||||
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
spoolTime = stuff.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))[1]
|
||||
spoolTime = stuff.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False))[1]
|
||||
if spoolTime:
|
||||
text = "{0}s".format(formatAmount(spoolTime, 3, 0, 3))
|
||||
tooltip = "spool up time"
|
||||
@@ -396,9 +396,9 @@ class Miscellanea(ViewColumn):
|
||||
return text, tooltip
|
||||
elif itemGroup == "Mutadaptive Remote Armor Repairer":
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
spoolOptDefault = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)
|
||||
spoolOptPre = SpoolOptions(SpoolType.SCALE, 0, True)
|
||||
spoolOptFull = SpoolOptions(SpoolType.SCALE, 1, True)
|
||||
spoolOptDefault = SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)
|
||||
spoolOptPre = SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)
|
||||
spoolOptFull = SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)
|
||||
rps = stuff.getRemoteReps(spoolOptions=spoolOptDefault, ignoreState=True).armor
|
||||
rpsPre = stuff.getRemoteReps(spoolOptions=spoolOptPre, ignoreState=True).armor
|
||||
rpsFull = stuff.getRemoteReps(spoolOptions=spoolOptFull, ignoreState=True).armor
|
||||
@@ -612,7 +612,10 @@ class Miscellanea(ViewColumn):
|
||||
fit = Fit.getInstance().getFit(self.fittingView.getActiveFit())
|
||||
ehpTotal = fit.ehp
|
||||
hpTotal = fit.hp
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
try:
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
except KeyError:
|
||||
useEhp = False
|
||||
tooltip = "{0} restored over duration using charges (plus reload)".format(boosted_attribute)
|
||||
|
||||
if useEhp and boosted_attribute == "HP" and "Remote" not in itemGroup:
|
||||
|
||||
61
gui/builtinViewColumns/projectionRange.py
Normal file
61
gui/builtinViewColumns/projectionRange.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# coding: utf-8
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.fit import Fit
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class ProjectionRangeColumn(ViewColumn):
|
||||
|
||||
name = 'Projection Range'
|
||||
|
||||
def __init__(self, fittingView, params):
|
||||
super().__init__(fittingView)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.imageId = fittingView.imageList.GetImageIndex(1391, "icons")
|
||||
self.bitmap = BitmapLoader.getBitmap(1391, "icons")
|
||||
self.mask = wx.LIST_MASK_IMAGE
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, Fit):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
info = stuff.getProjectionInfo(fitID)
|
||||
projRange = info.projectionRange
|
||||
else:
|
||||
projRange = getattr(stuff, 'projectionRange', None)
|
||||
if projRange is None:
|
||||
return ''
|
||||
return formatAmount(projRange, 3, 0, 3, unitName='m')
|
||||
|
||||
def getToolTip(self, mod):
|
||||
return 'Projection Range'
|
||||
|
||||
|
||||
ProjectionRangeColumn.register()
|
||||
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -10,6 +12,12 @@ from gui.marketBrowser import SearchBox
|
||||
from service.market import Market
|
||||
|
||||
|
||||
def stripHtml(text):
|
||||
text = re.sub('<\s*br\s*/?\s*>', '\n', text)
|
||||
text = re.sub('</?[^/]+?(/\s*)?>', '', text)
|
||||
return text
|
||||
|
||||
|
||||
class BaseImplantEditorView(wx.Panel):
|
||||
|
||||
def addMarketViewImage(self, iconFile):
|
||||
@@ -68,8 +76,10 @@ class BaseImplantEditorView(wx.Panel):
|
||||
|
||||
self.SetSizer(pmainSizer)
|
||||
|
||||
# Populate the market tree
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.hoveredRightListRow = None
|
||||
|
||||
# Populate the market tree
|
||||
sMkt = Market.getInstance()
|
||||
for mktGrp in sMkt.getImplantTree():
|
||||
iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(mktGrp))
|
||||
@@ -82,9 +92,13 @@ class BaseImplantEditorView(wx.Panel):
|
||||
# Bind the event to replace dummies by real data
|
||||
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
|
||||
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.itemSelected)
|
||||
self.availableImplantsTree.Bind(wx.EVT_MOTION, self.OnLeftTreeMouseMove)
|
||||
self.availableImplantsTree.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeftTreeMouseLeave)
|
||||
|
||||
self.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemSelected)
|
||||
|
||||
self.pluggedImplantsTree.Bind(wx.EVT_MOTION, self.OnRightListMouseMove)
|
||||
|
||||
# Bind add & remove buttons
|
||||
self.btnAdd.Bind(wx.EVT_BUTTON, self.itemSelected)
|
||||
self.btnRemove.Bind(wx.EVT_BUTTON, self.removeItem)
|
||||
@@ -193,6 +207,55 @@ class BaseImplantEditorView(wx.Panel):
|
||||
self.removeImplantFromContext(self.implants[pos])
|
||||
self.update()
|
||||
|
||||
# Due to https://github.com/wxWidgets/Phoenix/issues/1372 we cannot set tooltips on
|
||||
# tree itself; work this around with following two methods, by setting tooltip to
|
||||
# parent window
|
||||
def OnLeftTreeMouseMove(self, event):
|
||||
event.Skip()
|
||||
treeItemId, _ = self.availableImplantsTree.HitTest(event.Position)
|
||||
if not treeItemId:
|
||||
if self.hoveredLeftTreeTypeID is not None:
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.SetToolTip(None)
|
||||
return
|
||||
item = self.availableImplantsTree.GetItemData(treeItemId)
|
||||
isImplant = getattr(item, 'isImplant', False)
|
||||
if not isImplant:
|
||||
if self.hoveredLeftTreeTypeID is not None:
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.SetToolTip(None)
|
||||
return
|
||||
if self.hoveredLeftTreeTypeID == item.ID:
|
||||
return
|
||||
if self.ToolTip is not None:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredLeftTreeTypeID = item.ID
|
||||
toolTip = wx.ToolTip(stripHtml(item.description))
|
||||
toolTip.SetMaxWidth(self.GetSize().Width)
|
||||
self.SetToolTip(toolTip)
|
||||
|
||||
def OnLeftTreeMouseLeave(self, event):
|
||||
event.Skip()
|
||||
self.SetToolTip(None)
|
||||
|
||||
def OnRightListMouseMove(self, event):
|
||||
event.Skip()
|
||||
row, _, col = self.pluggedImplantsTree.HitTestSubItem(event.Position)
|
||||
if row != self.hoveredRightListRow:
|
||||
if self.pluggedImplantsTree.ToolTip is not None:
|
||||
self.pluggedImplantsTree.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredRightListRow = row
|
||||
try:
|
||||
implant = self.implants[row]
|
||||
except IndexError:
|
||||
self.pluggedImplantsTree.SetToolTip(None)
|
||||
else:
|
||||
toolTip = wx.ToolTip(stripHtml(implant.item.description))
|
||||
toolTip.SetMaxWidth(self.pluggedImplantsTree.GetSize().Width)
|
||||
self.pluggedImplantsTree.SetToolTip(toolTip)
|
||||
|
||||
|
||||
class AvailableImplantsView(d.Display):
|
||||
DEFAULT_COLS = ["attr:implantness",
|
||||
@@ -212,6 +275,7 @@ class ItemView(d.Display):
|
||||
self.parent = parent
|
||||
self.searchBox = parent.searchBox
|
||||
|
||||
self.hoveredRow = None
|
||||
self.items = []
|
||||
|
||||
# Bind search actions
|
||||
@@ -220,6 +284,8 @@ class ItemView(d.Display):
|
||||
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
|
||||
|
||||
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
if self.IsShown():
|
||||
self.parent.availableImplantsTree.Show()
|
||||
@@ -255,3 +321,20 @@ class ItemView(d.Display):
|
||||
self.items = sorted(list(items), key=lambda i: i.name)
|
||||
|
||||
self.update(self.items)
|
||||
|
||||
def OnMouseMove(self, event):
|
||||
event.Skip()
|
||||
row, _, col = self.HitTestSubItem(event.Position)
|
||||
if row != self.hoveredRow:
|
||||
if self.ToolTip is not None:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredRow = row
|
||||
try:
|
||||
item = self.items[row]
|
||||
except IndexError:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
toolTip = wx.ToolTip(stripHtml(item.description))
|
||||
toolTip.SetMaxWidth(self.GetSize().Width)
|
||||
self.SetToolTip(toolTip)
|
||||
|
||||
@@ -154,7 +154,9 @@ class CharacterEntityEditor(EntityEditor):
|
||||
class CharacterEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, id=wx.ID_ANY, title="Character Editor", pos=wx.DefaultPosition, size=wx.Size(640, 600))
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Character Editor", resizeable=True, pos=wx.DefaultPosition,
|
||||
size=wx.Size(950, 650) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 600))
|
||||
|
||||
i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
@@ -207,6 +209,7 @@ class CharacterEditor(AuxiliaryFrame):
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
|
||||
self.SetMinSize(self.GetSize())
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
#
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from functools import lru_cache
|
||||
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils import draw
|
||||
from gui.utils import color as color_utils
|
||||
from gui.utils import color as color_utils, draw, fonts
|
||||
from service.fit import Fit
|
||||
from gui.utils import fonts
|
||||
|
||||
|
||||
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
|
||||
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
|
||||
@@ -89,11 +92,15 @@ class PageAdding(_PageAdding, VetoAble):
|
||||
|
||||
class ChromeNotebook(wx.Panel):
|
||||
|
||||
def __init__(self, parent, can_add=True):
|
||||
def __init__(self, parent, can_add=True, tabWidthMode=0):
|
||||
"""
|
||||
Instance of Notebook. Initializes general layout, includes methods
|
||||
for setting current page, replacing pages, any public function for the
|
||||
notebook
|
||||
|
||||
width modes:
|
||||
- 0: legacy (all tabs have equal width)
|
||||
- 1: all tabs take just enough space to fit text
|
||||
"""
|
||||
super().__init__(parent, wx.ID_ANY, size=(-1, -1))
|
||||
|
||||
@@ -103,7 +110,7 @@ class ChromeNotebook(wx.Panel):
|
||||
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
tabs_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.tabs_container = _TabsContainer(self, can_add=can_add)
|
||||
self.tabs_container = _TabsContainer(self, can_add=can_add, tabWidthMode=tabWidthMode)
|
||||
tabs_sizer.Add(self.tabs_container, 0, wx.EXPAND)
|
||||
|
||||
if 'wxMSW' in wx.PlatformInfo:
|
||||
@@ -296,7 +303,14 @@ class ChromeNotebook(wx.Panel):
|
||||
|
||||
def SetPageTitle(self, i, text, refresh=True):
|
||||
tab = self.tabs_container.tabs[i]
|
||||
tab.text = text
|
||||
tab.baseText = text
|
||||
if refresh:
|
||||
self.tabs_container.AdjustTabsSize()
|
||||
self.Refresh()
|
||||
|
||||
def SetPageTitleExtra(self, i, text, refresh=True):
|
||||
tab = self.tabs_container.tabs[i]
|
||||
tab.extraText = text
|
||||
if refresh:
|
||||
self.tabs_container.AdjustTabsSize()
|
||||
self.Refresh()
|
||||
@@ -354,7 +368,8 @@ class _TabRenderer:
|
||||
height = max(height, self.min_height)
|
||||
|
||||
self.disabled = False
|
||||
self.text = text
|
||||
self.baseText = text
|
||||
self.extraText = ''
|
||||
self.tab_size = (width, height)
|
||||
self.closeable = closeable
|
||||
self.selected = False
|
||||
@@ -368,6 +383,10 @@ class _TabRenderer:
|
||||
self.position = (0, 0) # Not used internally for rendering - helper for tab container
|
||||
self.InitTab()
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.baseText + self.extraText
|
||||
|
||||
def SetPosition(self, position):
|
||||
self.position = position
|
||||
|
||||
@@ -685,15 +704,15 @@ class _AddRenderer:
|
||||
|
||||
class _TabsContainer(wx.Panel):
|
||||
def __init__(self, parent, pos=(50, 0), size=(100, 22), id=wx.ID_ANY,
|
||||
can_add=True):
|
||||
can_add=True, tabWidthMode=0):
|
||||
"""
|
||||
Defines the tab container. Handles functions such as tab selection and
|
||||
dragging, and defines minimum width of tabs (all tabs are of equal
|
||||
width, which is determined via widest tab). Also handles the tab
|
||||
preview, if any.
|
||||
"""
|
||||
|
||||
super().__init__(parent, id, pos, size)
|
||||
self.tabWidthMode = tabWidthMode
|
||||
|
||||
self.tabs = []
|
||||
self.width, self.height = size
|
||||
@@ -720,8 +739,7 @@ class _TabsContainer(wx.Panel):
|
||||
self.show_add_button = can_add
|
||||
|
||||
self.tab_container_width = self.width - self.reserved
|
||||
self.tab_min_width = self.width
|
||||
self.tab_shadow = _TabRenderer((self.tab_min_width, self.height + 1))
|
||||
self.fxBmps = {}
|
||||
|
||||
self.add_button = _AddRenderer()
|
||||
self.add_bitmap = self.add_button.Render()
|
||||
@@ -1173,7 +1191,7 @@ class _TabsContainer(wx.Panel):
|
||||
|
||||
if not tab.IsSelected():
|
||||
# drop shadow first
|
||||
mdc.DrawBitmap(self.fx_bmp, posx, posy, True)
|
||||
mdc.DrawBitmap(self.fxBmps[tab], posx, posy, True)
|
||||
bmp = tab.Render()
|
||||
img = bmp.ConvertToImage()
|
||||
img = img.AdjustChannels(1, 1, 1, 0.85)
|
||||
@@ -1191,7 +1209,7 @@ class _TabsContainer(wx.Panel):
|
||||
if selected:
|
||||
posx, posy = selected.GetPosition()
|
||||
# drop shadow first
|
||||
mdc.DrawBitmap(self.fx_bmp, posx, posy, True)
|
||||
mdc.DrawBitmap(self.fxBmps[selected], posx, posy, True)
|
||||
|
||||
bmp = selected.Render()
|
||||
|
||||
@@ -1211,16 +1229,21 @@ class _TabsContainer(wx.Panel):
|
||||
|
||||
def UpdateTabFX(self):
|
||||
""" Updates tab drop shadow bitmap """
|
||||
self.tab_shadow.SetSize((self.tab_min_width, self.height + 1))
|
||||
fx_bmp = self.tab_shadow.Render()
|
||||
self.fxBmps.clear()
|
||||
for tab in self.tabs:
|
||||
tabW, tabH = tab.tab_size
|
||||
self.fxBmps[tab] = self.GetTabFx(tabW, self.height + 1)
|
||||
|
||||
@lru_cache(maxsize=50)
|
||||
def GetTabFx(self, width, height):
|
||||
renderer = _TabRenderer((width, height))
|
||||
fx_bmp = renderer.Render()
|
||||
img = fx_bmp.ConvertToImage()
|
||||
if not img.HasAlpha():
|
||||
img.InitAlpha()
|
||||
img = img.Blur(2)
|
||||
img = img.AdjustChannels(0.3, 0.3, 0.3, 0.35)
|
||||
|
||||
self.fx_bmp = wx.Bitmap(img)
|
||||
return wx.Bitmap(img)
|
||||
|
||||
def AddTab(self, title=wx.EmptyString, img=None, closeable=False):
|
||||
self.ClearTabsSelected()
|
||||
@@ -1262,29 +1285,74 @@ class _TabsContainer(wx.Panel):
|
||||
Adjust tab sizes to ensure that they are all consistent and can fit into
|
||||
the tab container.
|
||||
"""
|
||||
if self.tabWidthMode == 1:
|
||||
if self.GetTabsCount() > 0:
|
||||
availableW = self.tab_container_width
|
||||
overlapSavedW = max(0, len(self.tabs)) * self.inclination * 2
|
||||
tabsGrouped = {}
|
||||
for tab in self.tabs:
|
||||
tabW, _ = tab.GetMinSize()
|
||||
tabsGrouped.setdefault(math.ceil(tabW), []).append(tab)
|
||||
clippedTabs = []
|
||||
clipW = max(tabsGrouped, default=0)
|
||||
|
||||
# first we loop through our tabs and calculate the the largest tab. This
|
||||
# is the size that we will base our calculations off
|
||||
def getUnclippedW():
|
||||
unclippedW = 0
|
||||
for w, tabs in tabsGrouped.items():
|
||||
unclippedW += w * len(tabs)
|
||||
return unclippedW
|
||||
while tabsGrouped:
|
||||
# Check if we're within width limit
|
||||
neededW = 0
|
||||
for w, tabs in tabsGrouped.items():
|
||||
neededW += w * len(tabs)
|
||||
if clippedTabs:
|
||||
neededW += clipW * len(clippedTabs)
|
||||
if neededW <= availableW + overlapSavedW:
|
||||
break
|
||||
# If we're not, extract widest tab group and mark it for clipping
|
||||
currentTabs = tabsGrouped.pop(max(tabsGrouped))
|
||||
clippedTabs.extend(currentTabs)
|
||||
proposedClipWidth = math.floor((availableW + overlapSavedW - getUnclippedW()) / len(clippedTabs))
|
||||
if not tabsGrouped or proposedClipWidth >= max(tabsGrouped, default=0):
|
||||
clipW = max(0, proposedClipWidth)
|
||||
break
|
||||
else:
|
||||
clipW = max(tabsGrouped)
|
||||
# Assign width for unclipped tabs
|
||||
for w, tabs in tabsGrouped.items():
|
||||
for tab in tabs:
|
||||
tab.SetSize((w, self.height))
|
||||
if clippedTabs:
|
||||
# Some width remains to be used due to rounding to integer
|
||||
extraWTotal = availableW + overlapSavedW - getUnclippedW() - clipW * len(clippedTabs)
|
||||
extraWPerTab = math.ceil(extraWTotal / len(clippedTabs))
|
||||
# Assign width for clipped tabs
|
||||
for tab in clippedTabs:
|
||||
extraW = min(extraWTotal, extraWPerTab)
|
||||
extraWTotal -= extraW
|
||||
tab.SetSize((clipW + extraW, self.height))
|
||||
else:
|
||||
# first we loop through our tabs and calculate the the largest tab. This
|
||||
# is the size that we will base our calculations off
|
||||
max_width = 100 # Tab should be at least 100
|
||||
for tab in self.tabs:
|
||||
mw, _ = tab.GetMinSize() # Tab min size includes tab contents
|
||||
max_width = max(mw, max_width)
|
||||
|
||||
max_width = 100 # Tab should be at least 100
|
||||
for tab in self.tabs:
|
||||
mw, _ = tab.GetMinSize() # Tab min size includes tab contents
|
||||
max_width = max(mw, max_width)
|
||||
tabWidth = 0
|
||||
# Divide tab container by number of tabs and add inclination. This will
|
||||
# return the ideal max size for the containers size
|
||||
if self.GetTabsCount() > 0:
|
||||
dx = self.tab_container_width / self.GetTabsCount() + self.inclination * 2
|
||||
tabWidth = min(dx, max_width)
|
||||
|
||||
# Divide tab container by number of tabs and add inclination. This will
|
||||
# return the ideal max size for the containers size
|
||||
if self.GetTabsCount() > 0:
|
||||
dx = self.tab_container_width / self.GetTabsCount() + self.inclination * 2
|
||||
self.tab_min_width = min(dx, max_width)
|
||||
|
||||
# Apply new size to all tabs
|
||||
for tab in self.tabs:
|
||||
tab.SetSize((self.tab_min_width, self.height))
|
||||
|
||||
if self.GetTabsCount() > 0:
|
||||
# update drop shadow based on new sizes
|
||||
self.UpdateTabFX()
|
||||
# Apply new size to all tabs
|
||||
for tab in self.tabs:
|
||||
tab.SetSize((tabWidth, self.height))
|
||||
|
||||
# update drop shadow based on new sizes
|
||||
self.UpdateTabFX()
|
||||
self.UpdateTabsPosition()
|
||||
|
||||
def UpdateTabsPosition(self, skip_tab=None):
|
||||
|
||||
@@ -39,7 +39,7 @@ class DevTools(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Development Tools", style=wx.RESIZE_BORDER,
|
||||
parent, id=wx.ID_ANY, title="Development Tools", resizeable=True,
|
||||
size=wx.Size(400, 320) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240))
|
||||
self.mainFrame = parent
|
||||
self.block = False
|
||||
|
||||
@@ -301,10 +301,13 @@ class Display(wx.ListCtrl):
|
||||
|
||||
def ensureSelection(self, clickedPos):
|
||||
"""
|
||||
On mac, when right-click on any item happens, it doesn't get selected.
|
||||
This method ensures that selection actually happens.
|
||||
On windows with Ctrl is pressed, or on Mac, when right-click on any item happens,
|
||||
the item doesn't get selected. This method ensures that only clicked item is selected.
|
||||
"""
|
||||
if 'wxMac' in wx.PlatformInfo:
|
||||
if (
|
||||
'wxMac' in wx.PlatformInfo or
|
||||
('wxMSW' in wx.PlatformInfo and wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL)
|
||||
):
|
||||
if clickedPos != -1:
|
||||
selectedPoss = self.getSelectedRows()
|
||||
if clickedPos not in selectedPoss:
|
||||
|
||||
@@ -25,7 +25,7 @@ class EveFittings(AuxiliaryFrame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition,
|
||||
size=wx.Size(750, 450), style=wx.RESIZE_BORDER)
|
||||
size=wx.Size(750, 450), resizeable=True)
|
||||
|
||||
self.mainFrame = parent
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
@@ -204,7 +204,7 @@ class ExportToEve(AuxiliaryFrame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition,
|
||||
size=wx.Size(400, 120) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 100), style=wx.RESIZE_BORDER)
|
||||
size=wx.Size(400, 120) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 100), resizeable=True)
|
||||
|
||||
self.mainFrame = parent
|
||||
|
||||
@@ -273,7 +273,15 @@ class ExportToEve(AuxiliaryFrame):
|
||||
sEsi = Esi.getInstance()
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
data = sPort.exportESI(sFit.getFit(fitID))
|
||||
try:
|
||||
data = sPort.exportESI(sFit.getFit(fitID))
|
||||
except ESIExportException as e:
|
||||
msg = str(e)
|
||||
if not msg:
|
||||
msg = "Failed to generate export data"
|
||||
pyfalog.warning(msg)
|
||||
self.statusbar.SetStatusText(msg, 1)
|
||||
return
|
||||
activeChar = self.getActiveCharacter()
|
||||
if activeChar is None:
|
||||
msg = "Need at least one ESI character to export"
|
||||
@@ -309,7 +317,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="SSO Character Management", pos=wx.DefaultPosition,
|
||||
size=wx.Size(550, 250), style=wx.RESIZE_BORDER)
|
||||
size=wx.Size(550, 250), resizeable=True)
|
||||
self.mainFrame = parent
|
||||
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ from .gui.localModule.replace import GuiReplaceLocalModuleCommand
|
||||
from .gui.localModule.swap import GuiSwapLocalModulesCommand
|
||||
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
|
||||
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
|
||||
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
|
||||
from .gui.projectedChangeStates import GuiChangeProjectedItemStatesCommand
|
||||
from .gui.projectedDrone.add import GuiAddProjectedDroneCommand
|
||||
from .gui.projectedDrone.changeAmount import GuiChangeProjectedDroneAmountCommand
|
||||
|
||||
40
gui/fitCommands/calc/drone/projectedChangeProjectionRange.py
Normal file
40
gui/fitCommands/calc/drone/projectedChangeProjectionRange.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class CalcChangeProjectedDroneProjectionRangeCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, itemID, projectionRange):
|
||||
wx.Command.__init__(self, True, 'Change Projected Drone Projection Range')
|
||||
self.fitID = fitID
|
||||
self.itemID = itemID
|
||||
self.projectionRange = projectionRange
|
||||
self.savedProjectionRange = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing change of projected drone {} projection range to {} on fit {}'.format(
|
||||
self.itemID, self.projectionRange, self.fitID))
|
||||
fit = Fit.getInstance().getFit(self.fitID)
|
||||
drone = next((pd for pd in fit.projectedDrones if pd.itemID == self.itemID), None)
|
||||
if drone is None:
|
||||
pyfalog.warning('Cannot find projected drone')
|
||||
return False
|
||||
if drone.projectionRange == self.projectionRange:
|
||||
return False
|
||||
self.savedProjectionRange = drone.projectionRange
|
||||
drone.projectionRange = self.projectionRange
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
pyfalog.debug('Undoing change of projected drone {} projection range to {} on fit {}'.format(
|
||||
self.itemID, self.projectionRange, self.fitID))
|
||||
cmd = CalcChangeProjectedDroneProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
itemID=self.itemID,
|
||||
projectionRange=self.savedProjectionRange)
|
||||
return cmd.Do()
|
||||
@@ -0,0 +1,37 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class CalcChangeProjectedFighterProjectionRangeCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, position, projectionRange):
|
||||
wx.Command.__init__(self, True, 'Change Projected Fighter Projection Range')
|
||||
self.fitID = fitID
|
||||
self.position = position
|
||||
self.projectionRange = projectionRange
|
||||
self.savedProjectionRange = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing changing of projected fighter projection range to {} at position {} for fit {}'.format(
|
||||
self.projectionRange, self.position, self.fitID))
|
||||
fit = Fit.getInstance().getFit(self.fitID)
|
||||
fighter = fit.projectedFighters[self.position]
|
||||
if fighter.projectionRange == self.projectionRange:
|
||||
return False
|
||||
self.savedProjectionRange = fighter.projectionRange
|
||||
fighter.projectionRange = self.projectionRange
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
pyfalog.debug('Undoing changing of projected fighter projection range to {} at position {} for fit {}'.format(
|
||||
self.projectionRange, self.position, self.fitID))
|
||||
cmd = CalcChangeProjectedFighterProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
position=self.position,
|
||||
projectionRange=self.savedProjectionRange)
|
||||
return cmd.Do()
|
||||
@@ -0,0 +1,55 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.fitCommands.helpers import restoreCheckedStates
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class CalcChangeProjectedModuleProjectionRangeCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, position, projectionRange):
|
||||
wx.Command.__init__(self, True)
|
||||
self.fitID = fitID
|
||||
self.position = position
|
||||
self.projectionRange = projectionRange
|
||||
self.savedProjectionRange = None
|
||||
self.savedStateCheckChanges = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing change of projected module projection range at position {} to range {} on fit {}'.format(
|
||||
self.position, self.projectionRange, self.fitID))
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.fitID)
|
||||
mod = fit.projectedModules[self.position]
|
||||
if mod.projectionRange == self.projectionRange:
|
||||
return False
|
||||
self.savedProjectionRange = mod.projectionRange
|
||||
mod.projectionRange = self.projectionRange
|
||||
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, mod)
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
pyfalog.debug('Undoing change of projected module projection range at position {} to range {} on fit {}'.format(
|
||||
self.position, self.projectionRange, self.fitID))
|
||||
cmd = CalcChangeProjectedModuleProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
position=self.position,
|
||||
projectionRange=self.savedProjectionRange)
|
||||
result = cmd.Do()
|
||||
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
||||
return result
|
||||
|
||||
@property
|
||||
def needsGuiRecalc(self):
|
||||
if self.savedStateCheckChanges is None:
|
||||
return True
|
||||
for container in self.savedStateCheckChanges:
|
||||
if len(container) > 0:
|
||||
return True
|
||||
return False
|
||||
60
gui/fitCommands/calc/projectedFit/changeProjectionRange.py
Normal file
60
gui/fitCommands/calc/projectedFit/changeProjectionRange.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.fitCommands.helpers import restoreCheckedStates
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class CalcChangeProjectedFitProjectionRangeCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, projectedFitID, projectionRange):
|
||||
wx.Command.__init__(self, True, 'Change Projected Fit Projection Range')
|
||||
self.fitID = fitID
|
||||
self.projectedFitID = projectedFitID
|
||||
self.projectionRange = projectionRange
|
||||
self.savedProjectionRange = None
|
||||
self.savedStateCheckChanges = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing change of projected fit {} range to {} for fit {}'.format(self.projectedFitID, self.projectionRange, self.fitID))
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.fitID)
|
||||
projectedFit = sFit.getFit(self.projectedFitID, projected=True)
|
||||
# Projected fit could have been deleted if we are redoing
|
||||
if projectedFit is None:
|
||||
pyfalog.debug('Projected fit is not available')
|
||||
return False
|
||||
projectionInfo = projectedFit.getProjectionInfo(self.fitID)
|
||||
if projectionInfo is None:
|
||||
pyfalog.warning('Fit projection info is not available')
|
||||
return False
|
||||
if projectionInfo.projectionRange == self.projectionRange:
|
||||
return False
|
||||
self.savedProjectionRange = projectionInfo.projectionRange
|
||||
projectionInfo.projectionRange = self.projectionRange
|
||||
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, None)
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
pyfalog.debug('Undoing change of projected fit {} range to {} for fit {}'.format(self.projectedFitID, self.projectionRange, self.fitID))
|
||||
cmd = CalcChangeProjectedFitProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
projectedFitID=self.projectedFitID,
|
||||
projectionRange=self.savedProjectionRange)
|
||||
result = cmd.Do()
|
||||
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
||||
return result
|
||||
|
||||
@property
|
||||
def needsGuiRecalc(self):
|
||||
if self.savedStateCheckChanges is None:
|
||||
return True
|
||||
for container in self.savedStateCheckChanges:
|
||||
if len(container) > 0:
|
||||
return True
|
||||
return False
|
||||
91
gui/fitCommands/gui/projectedChangeProjectionRange.py
Normal file
91
gui/fitCommands/gui/projectedChangeProjectionRange.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import wx
|
||||
|
||||
import eos.db
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.drone import Drone as EosDrone
|
||||
from eos.saveddata.fighter import Fighter as EosFighter
|
||||
from eos.saveddata.fit import Fit as EosFit
|
||||
from eos.saveddata.module import Module as EosModule
|
||||
from gui import globalEvents as GE
|
||||
from gui.fitCommands.calc.drone.projectedChangeProjectionRange import CalcChangeProjectedDroneProjectionRangeCommand
|
||||
from gui.fitCommands.calc.fighter.projectedChangeProjectionRange import CalcChangeProjectedFighterProjectionRangeCommand
|
||||
from gui.fitCommands.calc.module.projectedChangeProjectionRange import CalcChangeProjectedModuleProjectionRangeCommand
|
||||
from gui.fitCommands.calc.projectedFit.changeProjectionRange import CalcChangeProjectedFitProjectionRangeCommand
|
||||
from gui.fitCommands.helpers import InternalCommandHistory
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class GuiChangeProjectedItemsProjectionRangeCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, items, projectionRange):
|
||||
wx.Command.__init__(self, True, 'Change Projected Items Projection Range')
|
||||
self.internalHistory = InternalCommandHistory()
|
||||
self.fitID = fitID
|
||||
self.projectionRange = projectionRange
|
||||
self.pModPositions = []
|
||||
self.pDroneItemIDs = []
|
||||
self.pFighterPositions = []
|
||||
self.pFitIDs = []
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
for item in items:
|
||||
if isinstance(item, EosModule):
|
||||
if item in fit.projectedModules and not getattr(item, 'isExclusiveSystemEffect', False):
|
||||
self.pModPositions.append(fit.projectedModules.index(item))
|
||||
elif isinstance(item, EosDrone):
|
||||
self.pDroneItemIDs.append(item.itemID)
|
||||
elif isinstance(item, EosFighter):
|
||||
if item in fit.projectedFighters:
|
||||
self.pFighterPositions.append(fit.projectedFighters.index(item))
|
||||
elif isinstance(item, EosFit):
|
||||
self.pFitIDs.append(item.ID)
|
||||
|
||||
def Do(self):
|
||||
results = []
|
||||
needRecalc = True
|
||||
for pModPosition in self.pModPositions:
|
||||
cmd = CalcChangeProjectedModuleProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
position=pModPosition,
|
||||
projectionRange=self.projectionRange)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = cmd.needsGuiRecalc
|
||||
for pDroneItemID in self.pDroneItemIDs:
|
||||
cmd = CalcChangeProjectedDroneProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
itemID=pDroneItemID,
|
||||
projectionRange=self.projectionRange)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFighterPosition in self.pFighterPositions:
|
||||
cmd = CalcChangeProjectedFighterProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
position=pFighterPosition,
|
||||
projectionRange=self.projectionRange)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFitID in self.pFitIDs:
|
||||
cmd = CalcChangeProjectedFitProjectionRangeCommand(
|
||||
fitID=self.fitID,
|
||||
projectedFitID=pFitID,
|
||||
projectionRange=self.projectionRange)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = cmd.needsGuiRecalc
|
||||
success = any(results)
|
||||
sFit = Fit.getInstance()
|
||||
if needRecalc:
|
||||
eos.db.flush()
|
||||
sFit.recalc(self.fitID)
|
||||
sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
return success
|
||||
|
||||
def Undo(self):
|
||||
success = self.internalHistory.undoAll()
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
return success
|
||||
@@ -72,18 +72,21 @@ class GuiChangeProjectedItemStatesCommand(wx.Command):
|
||||
itemID=pDroneItemID,
|
||||
state=False if self.proposedState == 'inactive' else True)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFighterPosition in self.pFighterPositions:
|
||||
cmd = CalcChangeProjectedFighterStateCommand(
|
||||
fitID=self.fitID,
|
||||
position=pFighterPosition,
|
||||
state=False if self.proposedState == 'inactive' else True)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFitID in self.pFitIDs:
|
||||
cmd = CalcChangeProjectedFitStateCommand(
|
||||
fitID=self.fitID,
|
||||
projectedFitID=pFitID,
|
||||
state=False if self.proposedState == 'inactive' else True)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = cmd.needsGuiRecalc
|
||||
success = any(results)
|
||||
sFit = Fit.getInstance()
|
||||
if needRecalc:
|
||||
|
||||
@@ -7,7 +7,7 @@ import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from gui.fitCommands.calc.drone.projectedChangeAmount import CalcChangeProjectedDroneAmountCommand
|
||||
from gui.fitCommands.calc.drone.projectedRemove import CalcRemoveProjectedDroneCommand
|
||||
from gui.fitCommands.helpers import DroneInfo, InternalCommandHistory
|
||||
from gui.fitCommands.helpers import InternalCommandHistory
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
|
||||
@@ -49,12 +49,15 @@ class GuiRemoveProjectedItemsCommand(wx.Command):
|
||||
for pDroneItemID in self.pDroneItemIDs:
|
||||
cmd = CalcRemoveProjectedDroneCommand(fitID=self.fitID, itemID=pDroneItemID, amount=self.amount)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFighterPosition in sorted(self.pFighterPositions, reverse=True):
|
||||
cmd = CalcRemoveProjectedFighterCommand(fitID=self.fitID, position=pFighterPosition)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = True
|
||||
for pFitID in self.pFitIDs:
|
||||
cmd = CalcRemoveProjectedFitCommand(fitID=self.fitID, projectedFitID=pFitID, amount=self.amount)
|
||||
results.append(self.internalHistory.submit(cmd))
|
||||
needRecalc = cmd.needsGuiRecalc
|
||||
success = any(results)
|
||||
sFit = Fit.getInstance()
|
||||
if needRecalc:
|
||||
|
||||
@@ -56,7 +56,7 @@ class ItemStatsFrame(AuxiliaryFrame):
|
||||
title="Item stats",
|
||||
pos=pos,
|
||||
size=size,
|
||||
style=wx.RESIZE_BORDER)
|
||||
resizeable=True)
|
||||
|
||||
empty = getattr(victim, "isEmpty", False)
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class MainFrame(wx.Frame):
|
||||
mainSizer.Add(self.browser_fitting_split, 1, wx.EXPAND | wx.LEFT, 2)
|
||||
|
||||
self.fitMultiSwitch = MultiSwitch(self.fitting_additions_split)
|
||||
self.additionsPane = AdditionsPane(self.fitting_additions_split)
|
||||
self.additionsPane = AdditionsPane(self.fitting_additions_split, self)
|
||||
|
||||
self.notebookBrowsers = ChromeNotebook(self.browser_fitting_split, False)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import gui.builtinViews.emptyView
|
||||
|
||||
class MultiSwitch(ChromeNotebook):
|
||||
def __init__(self, parent):
|
||||
ChromeNotebook.__init__(self, parent)
|
||||
ChromeNotebook.__init__(self, parent, can_add=True, tabWidthMode=1)
|
||||
# self.AddPage() # now handled by mainFrame
|
||||
self.handlers = handlers = []
|
||||
for type in TabSpawner.tabTypes:
|
||||
|
||||
@@ -94,7 +94,7 @@ class DmgPatternEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Damage Pattern Editor", style=wx.RESIZE_BORDER,
|
||||
parent, id=wx.ID_ANY, title="Damage Pattern Editor", resizeable=True,
|
||||
# Dropdown list widget is scaled to its longest content line on GTK, adapt to that
|
||||
size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240))
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class AttributeEditor(AuxiliaryFrame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, wx.ID_ANY, title="Attribute Editor", pos=wx.DefaultPosition,
|
||||
size=wx.Size(650, 600), style=wx.RESIZE_BORDER)
|
||||
size=wx.Size(650, 600), resizeable=True)
|
||||
|
||||
i = wx.Icon(BitmapLoader.getBitmap("fit_rename_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
|
||||
@@ -119,7 +119,7 @@ class ImplantSetEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Implant Set Editor", style=wx.RESIZE_BORDER,
|
||||
parent, id=wx.ID_ANY, title="Implant Set Editor", resizeable=True,
|
||||
size=wx.Size(950, 500) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 420))
|
||||
|
||||
self.block = False
|
||||
|
||||
@@ -33,23 +33,18 @@ class SsoLogin(wx.Dialog):
|
||||
self.SetSizer(bSizer1)
|
||||
self.Center()
|
||||
|
||||
mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
|
||||
|
||||
from service.esi import Esi
|
||||
|
||||
self.sEsi = Esi.getInstance()
|
||||
uri = self.sEsi.getLoginURI(None)
|
||||
webbrowser.open(uri)
|
||||
|
||||
def OnLogin(self, event):
|
||||
self.Close()
|
||||
event.Skip()
|
||||
|
||||
|
||||
class SsoLoginServer(wx.Dialog):
|
||||
|
||||
def __init__(self, port):
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
super().__init__(mainFrame, id=wx.ID_ANY, title="SSO Login", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
super().__init__(self.mainFrame, id=wx.ID_ANY, title="SSO Login", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
|
||||
from service.esi import Esi
|
||||
|
||||
@@ -59,8 +54,8 @@ class SsoLoginServer(wx.Dialog):
|
||||
uri = self.sEsi.getLoginURI(serverAddr)
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
|
||||
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
|
||||
|
||||
text = wx.StaticText(self, wx.ID_ANY, "Waiting for character login through EVE Single Sign-On.")
|
||||
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
|
||||
@@ -78,9 +73,10 @@ class SsoLoginServer(wx.Dialog):
|
||||
webbrowser.open(uri)
|
||||
|
||||
def OnLogin(self, event):
|
||||
self.Close()
|
||||
self.EndModal(wx.ID_OK)
|
||||
event.Skip()
|
||||
|
||||
def OnClose(self, event):
|
||||
def OnDestroy(self, event):
|
||||
self.mainFrame.Unbind(GE.EVT_SSO_LOGIN, handler=self.OnLogin)
|
||||
self.sEsi.stopServer()
|
||||
event.Skip()
|
||||
|
||||
@@ -126,7 +126,7 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Target Profile Editor", style=wx.RESIZE_BORDER,
|
||||
parent, id=wx.ID_ANY, title="Target Profile Editor", resizeable=True,
|
||||
# Dropdown list widget is scaled to its longest content line on GTK, adapt to that
|
||||
size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 240))
|
||||
|
||||
|
||||
@@ -110,7 +110,11 @@ class FloatRangeBox(wx.TextCtrl):
|
||||
super().__init__(parent=parent, id=id, style=style, **kwargs)
|
||||
self.Bind(wx.EVT_TEXT, self.OnText)
|
||||
self._storedValue = ''
|
||||
self.ChangeValue('{}-{}'.format(valToStr(min(value)), valToStr(max(value))))
|
||||
value = [v for v in value if v is not None]
|
||||
if not value:
|
||||
self.ChangeValue('')
|
||||
else:
|
||||
self.ChangeValue('{}-{}'.format(valToStr(min(value)), valToStr(max(value))))
|
||||
|
||||
def ChangeValue(self, value):
|
||||
self._storedValue = value
|
||||
|
||||
@@ -99,14 +99,15 @@ def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=Fal
|
||||
return result
|
||||
|
||||
|
||||
def roundToPrec(val, prec):
|
||||
def roundToPrec(val, prec, nsValue=None):
|
||||
"""
|
||||
nsValue: custom value which should be used to determine normalization shift
|
||||
"""
|
||||
# We're not rounding integers anyway
|
||||
# Also make sure that we do not ask to calculate logarithm of zero
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
# Find round factor, taking into consideration that we want to keep at least prec
|
||||
# positions for fractions with zero integer part (e.g. 0.0000354 for prec=3)
|
||||
roundFactor = int(prec - math.ceil(math.log10(abs(val))))
|
||||
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||
# But we don't want to round integers
|
||||
if roundFactor < 0:
|
||||
roundFactor = 0
|
||||
|
||||
@@ -83,6 +83,7 @@ from gui.builtinViewColumns import ( # noqa: E402, F401
|
||||
maxRange,
|
||||
misc,
|
||||
price,
|
||||
projectionRange,
|
||||
propertyDisplay,
|
||||
state,
|
||||
sideEffects,
|
||||
|
||||
@@ -55,23 +55,23 @@ def main(db, json_path):
|
||||
|
||||
# Config dict
|
||||
tables = {
|
||||
'clonegrades': eos.gamedata.AlphaCloneSkill,
|
||||
'dgmattribs': eos.gamedata.AttributeInfo,
|
||||
'dgmeffects': eos.gamedata.Effect,
|
||||
'dgmtypeattribs': eos.gamedata.Attribute,
|
||||
'dgmtypeeffects': eos.gamedata.ItemEffect,
|
||||
'dgmunits': eos.gamedata.Unit,
|
||||
'evecategories': eos.gamedata.Category,
|
||||
'evegroups': eos.gamedata.Group,
|
||||
'invmetagroups': eos.gamedata.MetaGroup,
|
||||
'invmetatypes': eos.gamedata.MetaType,
|
||||
'evetypes': eos.gamedata.Item,
|
||||
'phbtraits': eos.gamedata.Traits,
|
||||
'phbmetadata': eos.gamedata.MetaData,
|
||||
'marketGroups': eos.gamedata.MarketGroup}
|
||||
'clonegrades': ('fsd_lite', eos.gamedata.AlphaCloneSkill),
|
||||
'dgmattribs': ('bulkdata', eos.gamedata.AttributeInfo),
|
||||
'dgmeffects': ('bulkdata', eos.gamedata.Effect),
|
||||
'dgmtypeattribs': ('bulkdata', eos.gamedata.Attribute),
|
||||
'dgmtypeeffects': ('bulkdata', eos.gamedata.ItemEffect),
|
||||
'dgmunits': ('bulkdata', eos.gamedata.Unit),
|
||||
'evecategories': ('fsd_lite', eos.gamedata.Category),
|
||||
'evegroups': ('fsd_lite', eos.gamedata.Group),
|
||||
'invmetagroups': ('bulkdata', eos.gamedata.MetaGroup),
|
||||
'invmetatypes': ('bulkdata', eos.gamedata.MetaType),
|
||||
'evetypes': ('fsd_lite', eos.gamedata.Item),
|
||||
'traits': ('phobos', eos.gamedata.Traits),
|
||||
'metadata': ('phobos', eos.gamedata.MetaData),
|
||||
'marketgroups': ('fsd_binary', eos.gamedata.MarketGroup)}
|
||||
|
||||
fieldMapping = {
|
||||
'marketGroups': {
|
||||
'marketgroups': {
|
||||
'id': 'marketGroupID',
|
||||
'name': 'marketGroupName'}}
|
||||
|
||||
@@ -79,7 +79,7 @@ def main(db, json_path):
|
||||
'evetypes',
|
||||
'evegroups',
|
||||
'evecategories',
|
||||
'marketGroups')
|
||||
'marketgroups')
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
@@ -271,8 +271,8 @@ def main(db, json_path):
|
||||
data = {}
|
||||
|
||||
# Dump all data to memory so we can easely cross check ignored rows
|
||||
for jsonName, cls in tables.items():
|
||||
with open(os.path.join(jsonPath, '{}.json'.format(jsonName)), encoding='utf-8') as f:
|
||||
for jsonName, (minerName, cls) in tables.items():
|
||||
with open(os.path.join(jsonPath, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
|
||||
tableData = json.load(f)
|
||||
if jsonName in rowsInValues:
|
||||
newTableData = []
|
||||
@@ -285,7 +285,7 @@ def main(db, json_path):
|
||||
tableData = newTableData
|
||||
if jsonName == 'icons':
|
||||
tableData = convertIcons(tableData)
|
||||
if jsonName == 'phbtraits':
|
||||
if jsonName == 'traits':
|
||||
tableData = convertTraits(tableData)
|
||||
if jsonName == 'clonegrades':
|
||||
tableData = convertClones(tableData)
|
||||
@@ -342,7 +342,7 @@ def main(db, json_path):
|
||||
):
|
||||
row['published'] = True
|
||||
|
||||
instance = tables[jsonName]()
|
||||
instance = tables[jsonName][1]()
|
||||
# fix for issue 80
|
||||
if jsonName is 'icons' and 'res:/ui/texture/icons/' in str(row['iconFile']).lower():
|
||||
row['iconFile'] = row['iconFile'].lower().replace('res:/ui/texture/icons/', '').replace('.png', '')
|
||||
@@ -370,7 +370,7 @@ def main(db, json_path):
|
||||
eos.db.gamedata_session.add(instance)
|
||||
|
||||
# quick and dirty hack to get this data in
|
||||
with open(os.path.join(jsonPath, 'dynamicattributes.json'), encoding='utf-8') as f:
|
||||
with open(os.path.join(jsonPath, 'fsd_binary', 'dynamicitemattributes.json'), encoding='utf-8') as f:
|
||||
bulkdata = json.load(f)
|
||||
for mutaID, data in bulkdata.items():
|
||||
muta = eos.gamedata.DynamicItem()
|
||||
|
||||
@@ -92,6 +92,7 @@ class Fit:
|
||||
"showShipBrowserTooltip": True,
|
||||
"marketSearchDelay": 250,
|
||||
"ammoChangeAll": False,
|
||||
"additionsLabels": 1,
|
||||
}
|
||||
|
||||
self.serviceFittingOptions = SettingsProvider.getInstance().getSettings(
|
||||
|
||||
@@ -305,9 +305,10 @@ class Market:
|
||||
self.ITEMS_FORCEDMARKETGROUP_R = self.__makeRevDict(self.ITEMS_FORCEDMARKETGROUP)
|
||||
|
||||
self.FORCEDMARKETGROUP = {
|
||||
685: False, # Ship Equipment > Electronic Warfare > ECCM
|
||||
681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays
|
||||
1639: False # Ship Equipment > Fleet Assistance > Command Processors
|
||||
685: False, # Ship Equipment > Electronic Warfare > ECCM
|
||||
681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays
|
||||
1639: False, # Ship Equipment > Fleet Assistance > Command Processors
|
||||
2527: True, # Ship Equipment > Hull & Armor > Mutadaptive Remote Armor Repairers - has hasTypes set to 1 while actually having no types
|
||||
}
|
||||
|
||||
# Misc definitions
|
||||
@@ -328,7 +329,7 @@ class Market:
|
||||
"Structure",
|
||||
"Structure Module",
|
||||
)
|
||||
self.SEARCH_GROUPS = ("Ice Product",)
|
||||
self.SEARCH_GROUPS = ("Ice Product", "Cargo Container", "Secure Cargo Container", "Audit Log Secure Container", "Freight Container")
|
||||
self.ROOT_MARKET_GROUPS = (9, # Ship Equipment
|
||||
1111, # Rigs
|
||||
157, # Drones
|
||||
|
||||
@@ -363,7 +363,7 @@ class EfsPort:
|
||||
groups = {}
|
||||
# Export at maximum spool for consistency, spoolup data is exported anyway.
|
||||
defaultSpoolValue = 1
|
||||
spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, True)
|
||||
spoolOptions = SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, True)
|
||||
for mod in fit.modules:
|
||||
if mod.getDps(spoolOptions=spoolOptions).total > 0:
|
||||
# Group weapon + ammo combinations that occur more than once
|
||||
@@ -689,7 +689,7 @@ class EfsPort:
|
||||
shipSize = EfsPort.getShipSize(fit.ship.item.groupID)
|
||||
# Export at maximum spool for consistency, spoolup data is exported anyway.
|
||||
defaultSpoolValue = 1
|
||||
spoolOptions = SpoolOptions(SpoolType.SCALE, defaultSpoolValue, True)
|
||||
spoolOptions = SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, True)
|
||||
|
||||
cargoIDs = []
|
||||
for cargo in fit.cargo:
|
||||
|
||||
@@ -519,7 +519,9 @@ class GraphSettings:
|
||||
def __init__(self):
|
||||
defaults = {
|
||||
'mobileDroneMode': GraphDpsDroneMode.auto,
|
||||
'ignoreDCR': False,
|
||||
'ignoreResists': True,
|
||||
'ignoreLockRange': True,
|
||||
'applyProjected': True}
|
||||
self.settings = SettingsProvider.getInstance().getSettings('graphSettings', defaults)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: v2.10.0
|
||||
version: v2.11.1
|
||||
|
||||
Reference in New Issue
Block a user