Compare commits
53 Commits
v2.61.0dev
...
v2.62.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c519b878e | ||
|
|
bb07dd8553 | ||
|
|
60555e72d8 | ||
|
|
a457d1d629 | ||
|
|
0e62b29028 | ||
|
|
8a0cb156b2 | ||
|
|
654f8ed7f5 | ||
|
|
cfa927dcd0 | ||
|
|
4383481a5f | ||
|
|
22063842f9 | ||
|
|
0e5d29a8f9 | ||
|
|
5ee16476dc | ||
|
|
2abb73eab8 | ||
|
|
547d8be21e | ||
|
|
b6d8582867 | ||
|
|
87fd358c4e | ||
|
|
0f4b082b4b | ||
|
|
59847c3756 | ||
|
|
b5d3dc6d56 | ||
|
|
3500867336 | ||
|
|
ea26d57566 | ||
|
|
45f0743c57 | ||
|
|
08d4e852d3 | ||
|
|
7edf1f93cb | ||
|
|
31cfa7c93a | ||
|
|
da1d42b578 | ||
|
|
502e5a8d8e | ||
|
|
087867bdca | ||
|
|
86ad17b075 | ||
|
|
b8ad4772b8 | ||
|
|
e0e5be5870 | ||
|
|
8b2f7c56db | ||
|
|
0ca1a675c2 | ||
|
|
6a0bfce262 | ||
|
|
362aebe658 | ||
|
|
bbe3e931f7 | ||
|
|
f5743af6a4 | ||
|
|
dbc2993fb9 | ||
|
|
219173c43e | ||
|
|
6074cfe15a | ||
|
|
f0a72f4307 | ||
|
|
ecc3f9fa7e | ||
|
|
c660e4058c | ||
|
|
7664b00b59 | ||
|
|
5721beacf5 | ||
|
|
13f3793515 | ||
|
|
f8e0520344 | ||
|
|
2c8e306dc2 | ||
|
|
7c37d8fd74 | ||
|
|
88129c0df4 | ||
|
|
3892dfc78c | ||
|
|
954a164922 | ||
|
|
32fd38ad7c |
@@ -25,6 +25,7 @@ for:
|
||||
before_build:
|
||||
# Prepare pyfa data
|
||||
- sh: find locale/ -type f -name "*.po" -exec msgen "{}" -o "{}" \;
|
||||
- sh: pyenv global system
|
||||
- sh: python3 -B scripts/compile_lang.py
|
||||
- sh: python3 -B scripts/dump_crowdin_progress.py
|
||||
- sh: python3 -B db_update.py
|
||||
|
||||
@@ -141,14 +141,14 @@ def update_db():
|
||||
(row['typeName_en-us'].startswith('Civilian') and "Shuttle" not in row['typeName_en-us'])
|
||||
or row['typeName_en-us'] == 'Capsule'
|
||||
or row['groupID'] == 4033 # destructible effect beacons
|
||||
or re.match('AIR .+Booster.*', row['typeName_en-us'])
|
||||
or re.match(r'AIR .+Booster.*', row['typeName_en-us'])
|
||||
):
|
||||
row['published'] = True
|
||||
# Nearly useless and clutter search results too much
|
||||
elif (
|
||||
row['typeName_en-us'].startswith('Limited Synth ')
|
||||
or row['typeName_en-us'].startswith('Expired ')
|
||||
or re.match('Mining Blitz .+ Booster Dose .+', row['typeName_en-us'])
|
||||
or re.match(r'Mining Blitz .+ Booster Dose .+', row['typeName_en-us'])
|
||||
or row['typeName_en-us'].endswith(' Filament') and (
|
||||
"'Needlejack'" not in row['typeName_en-us'] and
|
||||
"'Devana'" not in row['typeName_en-us'] and
|
||||
|
||||
@@ -14,7 +14,7 @@ with open("version.yml", 'r') as file:
|
||||
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
||||
|
||||
os.environ["PYFA_VERSION"] = version
|
||||
iscc = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||
iscc = r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||
|
||||
source = os.path.join(os.environ["PYFA_DIST_DIR"], "pyfa")
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ for modName in iterNamespace(__name__, __path__):
|
||||
# loop through python files, extracting update number and function, and
|
||||
# adding it to a list
|
||||
modname_tail = modName.rsplit('.', 1)[-1]
|
||||
m = re.match("^upgrade(?P<index>\d+)$", modname_tail)
|
||||
m = re.match(r"^upgrade(?P<index>\d+)$", modname_tail)
|
||||
if not m:
|
||||
continue
|
||||
index = int(m.group("index"))
|
||||
|
||||
15
eos/db/migrations/upgrade49.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Migration 49
|
||||
|
||||
- added hp column to targetResists table
|
||||
"""
|
||||
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT hp FROM targetResists LIMIT 1;")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE targetResists ADD COLUMN hp FLOAT;")
|
||||
@@ -37,6 +37,7 @@ targetProfiles_table = Table(
|
||||
Column('maxVelocity', Float, nullable=True),
|
||||
Column('signatureRadius', Float, nullable=True),
|
||||
Column('radius', Float, nullable=True),
|
||||
Column('hp', Float, nullable=True),
|
||||
Column('ownerID', ForeignKey('users.ID'), nullable=True),
|
||||
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
|
||||
@@ -48,4 +49,5 @@ mapper(
|
||||
'rawName': targetProfiles_table.c.name,
|
||||
'_maxVelocity': targetProfiles_table.c.maxVelocity,
|
||||
'_signatureRadius': targetProfiles_table.c.signatureRadius,
|
||||
'_radius': targetProfiles_table.c.radius})
|
||||
'_radius': targetProfiles_table.c.radius,
|
||||
'_hp': targetProfiles_table.c.hp})
|
||||
|
||||
230
eos/effects.py
@@ -462,7 +462,7 @@ class Effect67(BaseEffect):
|
||||
Used by:
|
||||
Modules from group: Frequency Mining Laser (3 of 3)
|
||||
Modules from group: Mining Laser (15 of 15)
|
||||
Modules from group: Strip Miner (5 of 5)
|
||||
Modules from group: Strip Miner (6 of 6)
|
||||
Module: Citizen Miner
|
||||
"""
|
||||
|
||||
@@ -1775,7 +1775,6 @@ class Effect584(BaseEffect):
|
||||
Implants named like: Eifyr and Co. 'Gunslinger' Surgical Strike SS (6 of 6)
|
||||
Implants named like: Halcyon Y Booster (5 of 5)
|
||||
Implant: AIR Pyrolancea Booster II
|
||||
Implant: Standard Cerebral Accelerator
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -2414,6 +2413,7 @@ class Effect784(BaseEffect):
|
||||
Implants named like: Zainou 'Deadeye' Missile Bombardment MB (6 of 6)
|
||||
Modules named like: Rocket Fuel Cache Partition (8 of 8)
|
||||
Implant: Antipharmakon Toxot
|
||||
Implant: Mithridate Volatile Booster
|
||||
Skill: Missile Bombardment
|
||||
"""
|
||||
|
||||
@@ -5253,7 +5253,6 @@ class Effect1763(BaseEffect):
|
||||
Used by:
|
||||
Implants named like: Halcyon R Booster (5 of 5)
|
||||
Implants named like: Zainou 'Deadeye' Rapid Launch RL (6 of 6)
|
||||
Implant: Standard Cerebral Accelerator
|
||||
Implant: Whelan Machorin's Ballistic Smartlink
|
||||
Skill: Missile Launcher Operation
|
||||
Skill: Rapid Launch
|
||||
@@ -6786,7 +6785,6 @@ class Effect2296(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Implants named like: Halcyon Y Booster (5 of 5)
|
||||
Implants named like: Tetrimon Resistance Booster (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -6984,8 +6982,8 @@ class Effect2432(BaseEffect):
|
||||
Implants named like: Halcyon Y Booster (5 of 5)
|
||||
Implants named like: Inherent Implants 'Squire' Capacitor Management EM (6 of 6)
|
||||
Implants named like: Mindflood Booster (4 of 4)
|
||||
Implants named like: Tetrimon Capacitor Booster (4 of 4)
|
||||
Modules named like: Semiconductor Memory Cell (8 of 8)
|
||||
Implant: Aegytica Volatile Booster
|
||||
Implant: Antipharmakon Aeolis
|
||||
Implant: Basic Capsuleer Engineering Augmentation Chip
|
||||
Implant: Genolution Core Augmentation CA-1
|
||||
@@ -7611,7 +7609,6 @@ class Effect2696(BaseEffect):
|
||||
maxRangeBonusEffectLasers
|
||||
|
||||
Used by:
|
||||
Implants named like: Tetrimon Precision Booster (4 of 4)
|
||||
Modules named like: Energy Locus Coordinator (8 of 8)
|
||||
"""
|
||||
|
||||
@@ -7864,6 +7861,7 @@ class Effect2735(BaseEffect):
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterArmorHPPenalty'
|
||||
displayName = 'Armor Capacity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7883,6 +7881,7 @@ class Effect2736(BaseEffect):
|
||||
Implants named like: Sooth Sayer Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterArmorRepairAmountPenalty'
|
||||
displayName = 'Armor Repair Amount'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7904,6 +7903,7 @@ class Effect2737(BaseEffect):
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterShieldCapacityPenalty'
|
||||
displayName = 'Shield Capacity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7923,6 +7923,7 @@ class Effect2739(BaseEffect):
|
||||
Implants named like: Sooth Sayer Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterTurretOptimalRangePenalty'
|
||||
displayName = 'Turret Optimal Range'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7942,6 +7943,7 @@ class Effect2741(BaseEffect):
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterTurretFalloffPenalty'
|
||||
displayName = 'Turret Falloff'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7961,6 +7963,7 @@ class Effect2745(BaseEffect):
|
||||
Implants named like: Exile Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterCapacitorCapacityPenalty'
|
||||
displayName = 'Cap Capacity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7979,6 +7982,7 @@ class Effect2746(BaseEffect):
|
||||
Items from market group: Implants & Boosters > Booster > Booster Slot 02 (9 of 13)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterMaxVelocityPenalty'
|
||||
displayName = 'Velocity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -7997,6 +8001,7 @@ class Effect2747(BaseEffect):
|
||||
Implants named like: Frentix Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterTurretTrackingPenalty'
|
||||
displayName = 'Turret Tracking'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -8016,6 +8021,7 @@ class Effect2748(BaseEffect):
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterMissileVelocityPenalty'
|
||||
displayName = 'Missile Velocity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -8034,6 +8040,7 @@ class Effect2749(BaseEffect):
|
||||
Implants named like: Blue Pill Booster (3 of 5)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
attr = 'boosterAOEVelocityPenalty'
|
||||
displayName = 'Missile Explosion Velocity'
|
||||
type = 'boosterSideEffect'
|
||||
@@ -8362,7 +8369,6 @@ class Effect2803(BaseEffect):
|
||||
energyWeaponDamageMultiplyPassive
|
||||
|
||||
Used by:
|
||||
Implants named like: Harvest Damage Booster (4 of 4)
|
||||
Modules named like: Energy Collision Accelerator (8 of 8)
|
||||
Implant: Wisdom of Gheinok
|
||||
"""
|
||||
@@ -8473,6 +8479,7 @@ class Effect2847(BaseEffect):
|
||||
Implants named like: Halcyon G Booster (5 of 5)
|
||||
Implant: Antipharmakon Iokira
|
||||
Implant: Ogdin's Eye Coordination Enhancer
|
||||
Implant: Theriac Volatile Booster
|
||||
Skill: Motion Prediction
|
||||
"""
|
||||
|
||||
@@ -13104,7 +13111,8 @@ class Effect4088(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Class Cataclysmic Variable Effects (6 of 6)
|
||||
Celestial: Dazh Liminality Locus
|
||||
Celestial: Final Liminality
|
||||
Celestial: Triglavian Minor Victory
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -13125,7 +13133,8 @@ class Effect4089(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Class Cataclysmic Variable Effects (6 of 6)
|
||||
Celestial: Dazh Liminality Locus
|
||||
Celestial: Final Liminality
|
||||
Celestial: Triglavian Minor Victory
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -13281,6 +13290,7 @@ class Effect4135(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Class Wolf Rayet Effects (6 of 6)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -13298,6 +13308,7 @@ class Effect4136(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Class Wolf Rayet Effects (6 of 6)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -13316,6 +13327,7 @@ class Effect4137(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Class Wolf Rayet Effects (6 of 6)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -13335,6 +13347,7 @@ class Effect4138(BaseEffect):
|
||||
Used by:
|
||||
Celestials named like: Class Wolf Rayet Effects (6 of 6)
|
||||
Celestials named like: Volatile Ice Storm (2 of 2)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -16293,6 +16306,7 @@ class Effect4902(BaseEffect):
|
||||
Used by:
|
||||
Ships from group: Assault Frigate (10 of 15)
|
||||
Ships from group: Command Destroyer (5 of 5)
|
||||
Ships from group: Heavy Assault Cruiser (9 of 14)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -16589,6 +16603,7 @@ class Effect4951(BaseEffect):
|
||||
Implants named like: Halcyon R Booster (5 of 5)
|
||||
Implant: AIR Hardshell Booster II
|
||||
Implant: Antipharmakon Thureo
|
||||
Implant: Balneum Volatile Booster
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -18008,7 +18023,6 @@ class Effect5189(BaseEffect):
|
||||
trackingSpeedBonusEffectLasers
|
||||
|
||||
Used by:
|
||||
Implants named like: Tetrimon Precision Booster (4 of 4)
|
||||
Modules named like: Energy Metastasis Adjuster (8 of 8)
|
||||
"""
|
||||
|
||||
@@ -19360,7 +19374,7 @@ class Effect5343(BaseEffect):
|
||||
shipBonusDroneDamageMultiplierGBC1
|
||||
|
||||
Used by:
|
||||
Variations of ship: Myrmidon (3 of 3)
|
||||
Ships named like: Myrmidon (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -19377,7 +19391,7 @@ class Effect5348(BaseEffect):
|
||||
shipBonusDroneHitpointsGBC1
|
||||
|
||||
Used by:
|
||||
Variations of ship: Myrmidon (3 of 3)
|
||||
Ships named like: Myrmidon (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -19616,6 +19630,7 @@ class Effect5364(BaseEffect):
|
||||
Implants named like: Halcyon R Booster (5 of 5)
|
||||
Implant: AIR Hardshell Booster II
|
||||
Implant: Antipharmakon Kosybo
|
||||
Implant: Vis Vitalis Volatile Booster
|
||||
Implant: Wisdom of Gheinok
|
||||
"""
|
||||
|
||||
@@ -19872,7 +19887,7 @@ class Effect5397(BaseEffect):
|
||||
baseMaxScanDeviationModifierModuleOnline2None
|
||||
|
||||
Used by:
|
||||
Variations of module: Scan Pinpointing Array I (2 of 2)
|
||||
Variations of module: Scan Pinpointing Array I (3 of 3)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -19890,7 +19905,7 @@ class Effect5398(BaseEffect):
|
||||
systemScanDurationModuleModifier
|
||||
|
||||
Used by:
|
||||
Modules from group: Scanning Upgrade Time (2 of 2)
|
||||
Modules from group: Scanning Upgrade Time (3 of 3)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -19906,7 +19921,7 @@ class Effect5399(BaseEffect):
|
||||
baseSensorStrengthModifierModule
|
||||
|
||||
Used by:
|
||||
Variations of module: Scan Rangefinding Array I (2 of 2)
|
||||
Modules named like: Scan Rangefinding Array (3 of 3)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -20627,23 +20642,6 @@ class Effect5501(BaseEffect):
|
||||
skill='Command Ships', **kwargs)
|
||||
|
||||
|
||||
class Effect5503(BaseEffect):
|
||||
"""
|
||||
eliteBonusCommandShipDroneTrackingCS2
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'trackingSpeed', ship.getModifiedItemAttr('eliteBonusCommandShips2'),
|
||||
skill='Command Ships', **kwargs)
|
||||
|
||||
|
||||
class Effect5505(BaseEffect):
|
||||
"""
|
||||
eliteBonusCommandShipMediumHybridRoFCS1
|
||||
@@ -24488,6 +24486,7 @@ class Effect6164(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Drifter Incursion (6 of 6)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -34790,6 +34789,11 @@ class Effect7117(BaseEffect):
|
||||
roleBonusWarpSpeed
|
||||
|
||||
Used by:
|
||||
Ships from group: Blockade Runner (5 of 5)
|
||||
Ships from group: Covert Ops (9 of 9)
|
||||
Ships from group: Hauler (5 of 18)
|
||||
Ships from group: Interceptor (10 of 10)
|
||||
Ships from group: Interdictor (4 of 4)
|
||||
Ship: Azariel
|
||||
Ship: Cynabal
|
||||
Ship: Dramiel
|
||||
@@ -35279,6 +35283,7 @@ class Effect7202(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Triglavian Invasion System Effects (3 of 3)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -35297,6 +35302,7 @@ class Effect7203(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Celestials named like: Triglavian Invasion System Effects (3 of 3)
|
||||
Celestial: Drifter Crisis
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -35579,7 +35585,8 @@ class Effect7237(BaseEffect):
|
||||
systemWarpSpeedBonus
|
||||
|
||||
Used by:
|
||||
Celestial: Dazh Liminality Locus
|
||||
Celestial: Drifter Crisis
|
||||
Celestial: Final Liminality
|
||||
Celestial: Turnur Aftermath
|
||||
"""
|
||||
|
||||
@@ -35887,7 +35894,8 @@ class Effect8031(BaseEffect):
|
||||
systemMaxTargets
|
||||
|
||||
Used by:
|
||||
Celestial: Dazh Liminality Locus
|
||||
Celestial: Final Liminality
|
||||
Celestial: Triglavian Minor Victory
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -37673,64 +37681,12 @@ class Effect8264(BaseEffect):
|
||||
skill='Industrial Command Ships', **kwargs)
|
||||
|
||||
|
||||
class Effect8267(BaseEffect):
|
||||
"""
|
||||
weaponDisruptorResistanceBonusPassive
|
||||
|
||||
Used by:
|
||||
Implants named like: Harvest Anti Disruptor Booster (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, container, context, projectionRange, **kwargs):
|
||||
fit.ship.boostItemAttr(
|
||||
'weaponDisruptionResistance',
|
||||
container.getModifiedItemAttr('weaponDisruptionResistanceBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8268(BaseEffect):
|
||||
"""
|
||||
nosferatuDurationBonusPassive
|
||||
|
||||
Used by:
|
||||
Implants named like: Harvest Nosferatu Booster (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.group.name == 'Energy Nosferatu', 'duration',
|
||||
module.getModifiedItemAttr('durationBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8269(BaseEffect):
|
||||
"""
|
||||
stasisWebifierMaxRangeAddPassive
|
||||
|
||||
Used by:
|
||||
Implants named like: Harvest Webifier Booster (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemIncrease(
|
||||
lambda mod: mod.item.group.name == 'Stasis Web', 'maxRange',
|
||||
module.getModifiedItemAttr('stasisWebRangeAdd'), **kwargs)
|
||||
|
||||
|
||||
class Effect8270(BaseEffect):
|
||||
"""
|
||||
capacitorWarfareResistanceBonusPassive
|
||||
|
||||
Used by:
|
||||
Implants named like: Halcyon Y Booster (5 of 5)
|
||||
Implants named like: Tetrimon Anti Drain Booster (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -41151,7 +41107,7 @@ class Effect12195(BaseEffect):
|
||||
def handler(fit, src, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Shield Operation'), 'shieldBonus',
|
||||
src.getModifiedItemAttr('roleBonusCBC'), **kwargs)
|
||||
src.getModifiedItemAttr('shipBonusCBC1'), skill='Caldari Battlecruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect12202(BaseEffect):
|
||||
@@ -41313,3 +41269,103 @@ class Effect12221(BaseEffect):
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Breacher Pod Launcher Operation'), 'dotDuration',
|
||||
skill.getModifiedItemAttr('durationBonus') * skill.level, **kwargs)
|
||||
|
||||
|
||||
class Effect12249(BaseEffect):
|
||||
"""
|
||||
shipBonusDroneDamageHeavyMediumLightMultiplierGBC1
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: (
|
||||
drone.item.requiresSkill('Light Drone Operation')
|
||||
or drone.item.requiresSkill('Medium Drone Operation')
|
||||
or drone.item.requiresSkill('Heavy Drone Operation')),
|
||||
'damageMultiplier', ship.getModifiedItemAttr('shipBonusGBC1'),
|
||||
skill='Gallente Battlecruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect12250(BaseEffect):
|
||||
"""
|
||||
shipBonusSentryDroneDamageAndSentryHPMultiplierGBC3
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
for attrName in ('damageMultiplier', 'shieldCapacity', 'armorHP', 'hp'):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: drone.item.requiresSkill('Sentry Drone Interfacing'), attrName,
|
||||
ship.getModifiedItemAttr('shipBonusGBC3'), skill='Gallente Battlecruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect12251(BaseEffect):
|
||||
"""
|
||||
shipBonusDroneHPHeavyMediumLightGBC1
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
for attrName in ('shieldCapacity', 'armorHP', 'hp'):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: (
|
||||
drone.item.requiresSkill('Light Drone Operation')
|
||||
or drone.item.requiresSkill('Medium Drone Operation')
|
||||
or drone.item.requiresSkill('Heavy Drone Operation')),
|
||||
attrName, ship.getModifiedItemAttr('shipBonusGBC1'),
|
||||
skill='Gallente Battlecruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect12252(BaseEffect):
|
||||
"""
|
||||
eliteBonusCommandShipDroneTrackingHeavyMediumLightCS2
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: (
|
||||
drone.item.requiresSkill('Light Drone Operation')
|
||||
or drone.item.requiresSkill('Medium Drone Operation')
|
||||
or drone.item.requiresSkill('Heavy Drone Operation')),
|
||||
'trackingSpeed', ship.getModifiedItemAttr('eliteBonusCommandShips2'),
|
||||
skill='Command Ships', **kwargs)
|
||||
|
||||
|
||||
class Effect12253(BaseEffect):
|
||||
"""
|
||||
eliteBonusCommandShipDroneTrackingSentryCS4
|
||||
|
||||
Used by:
|
||||
Ship: Eos
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: drone.item.requiresSkill('Sentry Drone Interfacing'),
|
||||
'trackingSpeed', ship.getModifiedItemAttr('eliteBonusCommandShips4'),
|
||||
skill='Command Ships', **kwargs)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import math
|
||||
|
||||
from copy import deepcopy
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -161,7 +162,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
||||
|
||||
def getVolleyParameters(self, targetProfile=None):
|
||||
if not self.dealsDamage or self.amountActive <= 0:
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
return {0: DmgTypes.default()}
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
|
||||
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
|
||||
@@ -170,11 +171,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetProfile, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetProfile, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetProfile, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetProfile, "explosiveAmount", 0)))
|
||||
volley = deepcopy(self.__baseVolley)
|
||||
volley.profile = targetProfile
|
||||
return {0: volley}
|
||||
|
||||
def getVolley(self, targetProfile=None):
|
||||
@@ -183,16 +181,12 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
||||
def getDps(self, targetProfile=None):
|
||||
volley = self.getVolley(targetProfile=targetProfile)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return DmgTypes.default()
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return DmgTypes.default()
|
||||
dpsFactor = 1 / (cycleParams.averageTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
dps = volley * dpsFactor
|
||||
return dps
|
||||
|
||||
def isRemoteRepping(self, ignoreState=False):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import math
|
||||
|
||||
from copy import deepcopy
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -198,16 +199,14 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
for ability in self.abilities:
|
||||
# Not passing resists here as we want to calculate and store base volley
|
||||
self.__baseVolley[ability.effectID] = {0: ability.getVolley()}
|
||||
adjustedVolley = {}
|
||||
adjustedVolleys = {}
|
||||
for effectID, effectData in self.__baseVolley.items():
|
||||
adjustedVolley[effectID] = {}
|
||||
for volleyTime, volleyValue in effectData.items():
|
||||
adjustedVolley[effectID][volleyTime] = DmgTypes(
|
||||
em=volleyValue.em * (1 - getattr(targetProfile, "emAmount", 0)),
|
||||
thermal=volleyValue.thermal * (1 - getattr(targetProfile, "thermalAmount", 0)),
|
||||
kinetic=volleyValue.kinetic * (1 - getattr(targetProfile, "kineticAmount", 0)),
|
||||
explosive=volleyValue.explosive * (1 - getattr(targetProfile, "explosiveAmount", 0)))
|
||||
return adjustedVolley
|
||||
adjustedVolleys[effectID] = {}
|
||||
for volleyTime, baseVolley in effectData.items():
|
||||
adjustedVolley = deepcopy(baseVolley)
|
||||
adjustedVolley.profile = targetProfile
|
||||
adjustedVolleys[effectID][volleyTime] = adjustedVolley
|
||||
return adjustedVolleys
|
||||
|
||||
def getVolleyPerEffect(self, targetProfile=None):
|
||||
volleyParams = self.getVolleyParametersPerEffect(targetProfile=targetProfile)
|
||||
@@ -218,28 +217,16 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
def getVolley(self, targetProfile=None):
|
||||
volleyParams = self.getVolleyParametersPerEffect(targetProfile=targetProfile)
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
volley = DmgTypes.default()
|
||||
for volleyData in volleyParams.values():
|
||||
em += volleyData[0].em
|
||||
therm += volleyData[0].thermal
|
||||
kin += volleyData[0].kinetic
|
||||
exp += volleyData[0].explosive
|
||||
return DmgTypes(em, therm, kin, exp)
|
||||
volley += volleyData[0]
|
||||
return volley
|
||||
|
||||
def getDps(self, targetProfile=None):
|
||||
em = 0
|
||||
thermal = 0
|
||||
kinetic = 0
|
||||
explosive = 0
|
||||
for dps in self.getDpsPerEffect(targetProfile=targetProfile).values():
|
||||
em += dps.em
|
||||
thermal += dps.thermal
|
||||
kinetic += dps.kinetic
|
||||
explosive += dps.explosive
|
||||
return DmgTypes(em=em, thermal=thermal, kinetic=kinetic, explosive=explosive)
|
||||
dps = DmgTypes.default()
|
||||
for subdps in self.getDpsPerEffect(targetProfile=targetProfile).values():
|
||||
dps += subdps
|
||||
return dps
|
||||
|
||||
def getDpsPerEffect(self, targetProfile=None):
|
||||
if not self.active or self.amount <= 0:
|
||||
|
||||
@@ -116,7 +116,7 @@ class FighterAbility:
|
||||
|
||||
def getVolley(self, targetProfile=None):
|
||||
if not self.dealsDamage or not self.active:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return DmgTypes.default()
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
em = self.fighter.getModifiedChargeAttr("emDamage", 0)
|
||||
therm = self.fighter.getModifiedChargeAttr("thermalDamage", 0)
|
||||
@@ -128,24 +128,17 @@ class FighterAbility:
|
||||
kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix), 0)
|
||||
exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix), 0)
|
||||
dmgMult = self.fighter.amount * self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix), 1)
|
||||
volley = DmgTypes(
|
||||
em=em * dmgMult * (1 - getattr(targetProfile, "emAmount", 0)),
|
||||
thermal=therm * dmgMult * (1 - getattr(targetProfile, "thermalAmount", 0)),
|
||||
kinetic=kin * dmgMult * (1 - getattr(targetProfile, "kineticAmount", 0)),
|
||||
explosive=exp * dmgMult * (1 - getattr(targetProfile, "explosiveAmount", 0)))
|
||||
volley = DmgTypes(em=em * dmgMult, thermal=therm * dmgMult, kinetic=kin * dmgMult, explosive=exp * dmgMult)
|
||||
volley.profile = targetProfile
|
||||
return volley
|
||||
|
||||
def getDps(self, targetProfile=None, cycleTimeOverride=None):
|
||||
volley = self.getVolley(targetProfile=targetProfile)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return DmgTypes.default()
|
||||
cycleTime = cycleTimeOverride if cycleTimeOverride is not None else self.cycleTime
|
||||
dpsFactor = 1 / (cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
dps = volley * dpsFactor
|
||||
return dps
|
||||
|
||||
def clear(self):
|
||||
|
||||
@@ -1688,27 +1688,33 @@ class Fit:
|
||||
self.__droneWaste = droneWaste
|
||||
|
||||
def calculateWeaponDmgStats(self, spoolOptions):
|
||||
weaponVolley = DmgTypes(0, 0, 0, 0)
|
||||
weaponDps = DmgTypes(0, 0, 0, 0)
|
||||
weaponVolley = DmgTypes.default()
|
||||
weaponDps = DmgTypes.default()
|
||||
|
||||
for mod in self.modules:
|
||||
weaponVolley += mod.getVolley(spoolOptions=spoolOptions, targetProfile=self.targetProfile)
|
||||
weaponDps += mod.getDps(spoolOptions=spoolOptions, targetProfile=self.targetProfile)
|
||||
weaponVolley += mod.getVolley(spoolOptions=spoolOptions)
|
||||
weaponDps += mod.getDps(spoolOptions=spoolOptions)
|
||||
|
||||
weaponVolley.profile = self.targetProfile
|
||||
weaponDps.profile = self.targetProfile
|
||||
|
||||
self.__weaponVolleyMap[spoolOptions] = weaponVolley
|
||||
self.__weaponDpsMap[spoolOptions] = weaponDps
|
||||
|
||||
def calculateDroneDmgStats(self):
|
||||
droneVolley = DmgTypes(0, 0, 0, 0)
|
||||
droneDps = DmgTypes(0, 0, 0, 0)
|
||||
droneVolley = DmgTypes.default()
|
||||
droneDps = DmgTypes.default()
|
||||
|
||||
for drone in self.drones:
|
||||
droneVolley += drone.getVolley(targetProfile=self.targetProfile)
|
||||
droneDps += drone.getDps(targetProfile=self.targetProfile)
|
||||
droneVolley += drone.getVolley()
|
||||
droneDps += drone.getDps()
|
||||
|
||||
for fighter in self.fighters:
|
||||
droneVolley += fighter.getVolley(targetProfile=self.targetProfile)
|
||||
droneDps += fighter.getDps(targetProfile=self.targetProfile)
|
||||
droneVolley += fighter.getVolley()
|
||||
droneDps += fighter.getDps()
|
||||
|
||||
droneVolley.profile = self.targetProfile
|
||||
droneDps.profile = self.targetProfile
|
||||
|
||||
self.__droneDps = droneDps
|
||||
self.__droneVolley = droneVolley
|
||||
|
||||
@@ -33,7 +33,7 @@ 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
|
||||
from eos.utils.stats import BreacherInfo, DmgTypes, RRTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -453,6 +453,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def isBreacher(self):
|
||||
return self.charge and 'dotMissileLaunching' in self.charge.effects
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.isEmpty:
|
||||
return False
|
||||
@@ -469,75 +473,77 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
||||
|
||||
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
return {0: DmgTypes.default()}
|
||||
if self.__baseVolley is None:
|
||||
self.__baseVolley = {}
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||
# 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', 'debuffLance'}.intersection(self.item.effects):
|
||||
dmgDelay = self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
||||
if self.isBreacher:
|
||||
dmgDelay = 1
|
||||
subcycles = math.floor(self.getModifiedChargeAttr("dotDuration", 0) / 1000)
|
||||
breacher_info = BreacherInfo(
|
||||
absolute=self.getModifiedChargeAttr("dotMaxDamagePerTick", 0),
|
||||
relative=self.getModifiedChargeAttr("dotMaxHPPercentagePerTick", 0) / 100)
|
||||
for i in range(subcycles):
|
||||
volley = DmgTypes.default()
|
||||
volley.add_breacher(dmgDelay + i, breacher_info)
|
||||
self.__baseVolley[dmgDelay + i] = volley
|
||||
else:
|
||||
dmgDelay = 0
|
||||
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
|
||||
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
|
||||
# Reaper DD can damage each target only once
|
||||
if dmgDuration != 0 and dmgSubcycle != 0 and 'doomsdaySlash' not in self.item.effects:
|
||||
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
|
||||
else:
|
||||
subcycles = 1
|
||||
for i in range(subcycles):
|
||||
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||
# 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', 'debuffLance'}.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
|
||||
if dmgDuration != 0 and dmgSubcycle != 0 and 'doomsdaySlash' not in self.item.effects:
|
||||
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
|
||||
else:
|
||||
subcycles = 1
|
||||
for i in range(subcycles):
|
||||
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
spoolBoost = calculateSpoolup(
|
||||
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
|
||||
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||
spoolMultiplier = 1 + spoolBoost
|
||||
adjustedVolley = {}
|
||||
for volleyTime, volleyValue in self.__baseVolley.items():
|
||||
adjustedVolley[volleyTime] = DmgTypes(
|
||||
em=volleyValue.em * spoolMultiplier * (1 - getattr(targetProfile, "emAmount", 0)),
|
||||
thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetProfile, "thermalAmount", 0)),
|
||||
kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetProfile, "kineticAmount", 0)),
|
||||
explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetProfile, "explosiveAmount", 0)))
|
||||
return adjustedVolley
|
||||
adjustedVolleys = {}
|
||||
for volleyTime, baseVolley in self.__baseVolley.items():
|
||||
adjustedVolley = baseVolley * spoolMultiplier
|
||||
adjustedVolley.profile = targetProfile
|
||||
adjustedVolleys[volleyTime] = adjustedVolley
|
||||
return adjustedVolleys
|
||||
|
||||
def getVolley(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState)
|
||||
if len(volleyParams) == 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return DmgTypes.default()
|
||||
return volleyParams[min(volleyParams)]
|
||||
|
||||
def getDps(self, spoolOptions=None, targetProfile=None, ignoreState=False, getSpreadDPS=False):
|
||||
dmgDuringCycle = DmgTypes(0, 0, 0, 0)
|
||||
def getDps(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
dps = DmgTypes.default()
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return dmgDuringCycle
|
||||
return dps
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState)
|
||||
avgCycleTime = cycleParams.averageTime
|
||||
if len(volleyParams) == 0 or avgCycleTime == 0:
|
||||
return dmgDuringCycle
|
||||
for volleyValue in volleyParams.values():
|
||||
dmgDuringCycle += volleyValue
|
||||
dpsFactor = 1 / (avgCycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=dmgDuringCycle.em * dpsFactor,
|
||||
thermal=dmgDuringCycle.thermal * dpsFactor,
|
||||
kinetic=dmgDuringCycle.kinetic * dpsFactor,
|
||||
explosive=dmgDuringCycle.explosive * dpsFactor)
|
||||
if not getSpreadDPS:
|
||||
return dps
|
||||
return {'em':dmgDuringCycle.em * dpsFactor,
|
||||
'therm': dmgDuringCycle.thermal * dpsFactor,
|
||||
'kin': dmgDuringCycle.kinetic * dpsFactor,
|
||||
'exp': dmgDuringCycle.explosive * dpsFactor}
|
||||
if self.isBreacher:
|
||||
return volleyParams[min(volleyParams)]
|
||||
for volleyValue in volleyParams.values():
|
||||
dps += volleyValue
|
||||
dpsFactor = 1 / (avgCycleTime / 1000)
|
||||
dps *= dpsFactor
|
||||
return dps
|
||||
|
||||
def isRemoteRepping(self, ignoreState=False):
|
||||
repParams = self.getRepAmountParameters(ignoreState=ignoreState)
|
||||
@@ -949,6 +955,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
|
||||
def getCycleParametersForDps(self, reloadOverride=None):
|
||||
# Special hack for breachers, since those are DoT and work independently of gun cycle
|
||||
if self.isBreacher:
|
||||
return CycleInfo(activeTime=1000, inactiveTime=0, quantity=math.inf, isInactivityReload=False)
|
||||
else:
|
||||
return self.getCycleParameters(reloadOverride=reloadOverride)
|
||||
|
||||
def getCycleParameters(self, reloadOverride=None):
|
||||
"""Copied from new eos as well"""
|
||||
# Determine if we'll take into account reload time or not
|
||||
|
||||
@@ -254,7 +254,7 @@ class TargetProfile:
|
||||
def init(self):
|
||||
self.builtin = False
|
||||
|
||||
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None):
|
||||
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None, hp=None):
|
||||
self.emAmount = emAmount
|
||||
self.thermalAmount = thermalAmount
|
||||
self.kineticAmount = kineticAmount
|
||||
@@ -262,6 +262,7 @@ class TargetProfile:
|
||||
self._maxVelocity = maxVelocity
|
||||
self._signatureRadius = signatureRadius
|
||||
self._radius = radius
|
||||
self._hp = hp
|
||||
|
||||
@classmethod
|
||||
def getBuiltinList(cls):
|
||||
@@ -331,6 +332,18 @@ class TargetProfile:
|
||||
def radius(self, val):
|
||||
self._radius = val
|
||||
|
||||
@property
|
||||
def hp(self):
|
||||
if self._hp is None or self._hp == -1:
|
||||
return math.inf
|
||||
return self._hp
|
||||
|
||||
@hp.setter
|
||||
def hp(self, val):
|
||||
if val is not None and math.isinf(val):
|
||||
val = None
|
||||
self._hp = val
|
||||
|
||||
@classmethod
|
||||
def importPatterns(cls, text):
|
||||
lines = re.split('[\n\r]+', text)
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
from eos.utils.float import floatUnerr
|
||||
from utils.repr import makeReprStr
|
||||
|
||||
@@ -26,15 +29,133 @@ def _t(x):
|
||||
return x
|
||||
|
||||
|
||||
class BreacherInfo:
|
||||
|
||||
def __init__(self, absolute, relative):
|
||||
self.absolute = absolute
|
||||
self.relative = relative
|
||||
|
||||
def __mul__(self, mul):
|
||||
return type(self)(absolute=self.absolute * mul, relative=self.relative * mul)
|
||||
|
||||
def __imul__(self, mul):
|
||||
if mul == 1:
|
||||
return self
|
||||
self.absolute *= mul
|
||||
self.relative *= mul
|
||||
return self
|
||||
|
||||
def __truediv__(self, div):
|
||||
return type(self)(absolute=self.absolute / div, relative=self.relative / div)
|
||||
|
||||
|
||||
class DmgTypes:
|
||||
"""Container for damage data stats."""
|
||||
"""
|
||||
Container for volley stats, which stores breacher pod data
|
||||
in raw form, before application of it to target profile.
|
||||
"""
|
||||
|
||||
def __init__(self, em, thermal, kinetic, explosive):
|
||||
self.em = em
|
||||
self.thermal = thermal
|
||||
self.kinetic = kinetic
|
||||
self.explosive = explosive
|
||||
self._calcTotal()
|
||||
self._em = em
|
||||
self._thermal = thermal
|
||||
self._kinetic = kinetic
|
||||
self._explosive = explosive
|
||||
self._breachers = defaultdict(lambda: [])
|
||||
self.__profile = None
|
||||
# Cached data
|
||||
self.__cached_em = None
|
||||
self.__cached_thermal = None
|
||||
self.__cached_kinetic = None
|
||||
self.__cached_explosive = None
|
||||
self.__cached_pure = None
|
||||
self.__cached_total = None
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls(0, 0, 0, 0)
|
||||
|
||||
def _clear_cached(self):
|
||||
self.__cached_em = None
|
||||
self.__cached_thermal = None
|
||||
self.__cached_kinetic = None
|
||||
self.__cached_explosive = None
|
||||
self.__cached_pure = None
|
||||
self.__cached_total = None
|
||||
|
||||
def add_breacher(self, key, data):
|
||||
self._breachers[key].append(data)
|
||||
|
||||
@property
|
||||
def profile(self):
|
||||
return self.__profile
|
||||
|
||||
@profile.setter
|
||||
def profile(self, profile):
|
||||
self.__profile = profile
|
||||
self._clear_cached()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
if self.__cached_em is not None:
|
||||
return self.__cached_em
|
||||
dmg = self._em
|
||||
if self.profile is not None:
|
||||
dmg *= 1 - getattr(self.profile, "emAmount", 0)
|
||||
self.__cached_em = dmg
|
||||
return dmg
|
||||
|
||||
@property
|
||||
def thermal(self):
|
||||
if self.__cached_thermal is not None:
|
||||
return self.__cached_thermal
|
||||
dmg = self._thermal
|
||||
if self.profile is not None:
|
||||
dmg *= 1 - getattr(self.profile, "thermalAmount", 0)
|
||||
self.__cached_thermal = dmg
|
||||
return dmg
|
||||
|
||||
@property
|
||||
def kinetic(self):
|
||||
if self.__cached_kinetic is not None:
|
||||
return self.__cached_kinetic
|
||||
dmg = self._kinetic
|
||||
if self.profile is not None:
|
||||
dmg *= 1 - getattr(self.profile, "kineticAmount", 0)
|
||||
self.__cached_kinetic = dmg
|
||||
return dmg
|
||||
|
||||
@property
|
||||
def explosive(self):
|
||||
if self.__cached_explosive is not None:
|
||||
return self.__cached_explosive
|
||||
dmg = self._explosive
|
||||
if self.profile is not None:
|
||||
dmg *= 1 - getattr(self.profile, "explosiveAmount", 0)
|
||||
self.__cached_explosive = dmg
|
||||
return dmg
|
||||
|
||||
@property
|
||||
def pure(self):
|
||||
if self.__cached_pure is not None:
|
||||
return self.__cached_pure
|
||||
if self.profile is None:
|
||||
dmg = sum(
|
||||
max((b.absolute for b in bs), default=0)
|
||||
for bs in self._breachers.values())
|
||||
else:
|
||||
dmg = sum(
|
||||
max((min(b.absolute, b.relative * getattr(self.profile, "hp", math.inf)) for b in bs), default=0)
|
||||
for bs in self._breachers.values())
|
||||
self.__cached_pure = dmg
|
||||
return dmg
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
if self.__cached_total is not None:
|
||||
return self.__cached_total
|
||||
dmg = self.em + self.thermal + self.kinetic + self.explosive + self.pure
|
||||
self.__cached_total = dmg
|
||||
return dmg
|
||||
|
||||
# Iterator is needed to support tuple-style unpacking
|
||||
def __iter__(self):
|
||||
@@ -42,6 +163,7 @@ class DmgTypes:
|
||||
yield self.thermal
|
||||
yield self.kinetic
|
||||
yield self.explosive
|
||||
yield self.pure
|
||||
yield self.total
|
||||
|
||||
def __eq__(self, other):
|
||||
@@ -50,77 +172,87 @@ class DmgTypes:
|
||||
# Round for comparison's sake because often damage profiles are
|
||||
# generated from data which includes float errors
|
||||
return (
|
||||
floatUnerr(self.em) == floatUnerr(other.em) and
|
||||
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
|
||||
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
|
||||
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
|
||||
floatUnerr(self.total) == floatUnerr(other.total))
|
||||
|
||||
def __bool__(self):
|
||||
return any((
|
||||
self.em, self.thermal, self.kinetic,
|
||||
self.explosive, self.total))
|
||||
|
||||
def _calcTotal(self):
|
||||
self.total = self.em + self.thermal + self.kinetic + self.explosive
|
||||
floatUnerr(self._em) == floatUnerr(other._em) and
|
||||
floatUnerr(self._thermal) == floatUnerr(other._thermal) and
|
||||
floatUnerr(self._kinetic) == floatUnerr(other._kinetic) and
|
||||
floatUnerr(self._explosive) == floatUnerr(other._explosive) and
|
||||
sorted(self._breachers) == sorted(other._breachers) and
|
||||
self.profile == other.profile)
|
||||
|
||||
def __add__(self, other):
|
||||
return type(self)(
|
||||
em=self.em + other.em,
|
||||
thermal=self.thermal + other.thermal,
|
||||
kinetic=self.kinetic + other.kinetic,
|
||||
explosive=self.explosive + other.explosive)
|
||||
new = type(self)(
|
||||
em=self._em + other._em,
|
||||
thermal=self._thermal + other._thermal,
|
||||
kinetic=self._kinetic + other._kinetic,
|
||||
explosive=self._explosive + other._explosive)
|
||||
new.profile = self.profile
|
||||
for k, v in self._breachers.items():
|
||||
new._breachers[k].extend(v)
|
||||
for k, v in other._breachers.items():
|
||||
new._breachers[k].extend(v)
|
||||
return new
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.em += other.em
|
||||
self.thermal += other.thermal
|
||||
self.kinetic += other.kinetic
|
||||
self.explosive += other.explosive
|
||||
self._calcTotal()
|
||||
self._em += other._em
|
||||
self._thermal += other._thermal
|
||||
self._kinetic += other._kinetic
|
||||
self._explosive += other._explosive
|
||||
for k, v in other._breachers.items():
|
||||
self._breachers[k].extend(v)
|
||||
self._clear_cached()
|
||||
return self
|
||||
|
||||
def __mul__(self, mul):
|
||||
return type(self)(
|
||||
em=self.em * mul,
|
||||
thermal=self.thermal * mul,
|
||||
kinetic=self.kinetic * mul,
|
||||
explosive=self.explosive * mul)
|
||||
new = type(self)(
|
||||
em=self._em * mul,
|
||||
thermal=self._thermal * mul,
|
||||
kinetic=self._kinetic * mul,
|
||||
explosive=self._explosive * mul)
|
||||
new.profile = self.profile
|
||||
for k, v in self._breachers.items():
|
||||
new._breachers[k] = [b * mul for b in v]
|
||||
return new
|
||||
|
||||
def __imul__(self, mul):
|
||||
if mul == 1:
|
||||
return
|
||||
self.em *= mul
|
||||
self.thermal *= mul
|
||||
self.kinetic *= mul
|
||||
self.explosive *= mul
|
||||
self._calcTotal()
|
||||
return self
|
||||
self._em *= mul
|
||||
self._thermal *= mul
|
||||
self._kinetic *= mul
|
||||
self._explosive *= mul
|
||||
for v in self._breachers.values():
|
||||
for b in v:
|
||||
b *= mul
|
||||
self._clear_cached()
|
||||
return self
|
||||
|
||||
def __truediv__(self, div):
|
||||
return type(self)(
|
||||
em=self.em / div,
|
||||
thermal=self.thermal / div,
|
||||
kinetic=self.kinetic / div,
|
||||
explosive=self.explosive / div)
|
||||
new = type(self)(
|
||||
em=self._em / div,
|
||||
thermal=self._thermal / div,
|
||||
kinetic=self._kinetic / div,
|
||||
explosive=self._explosive / div)
|
||||
new.profile = self.profile
|
||||
for k, v in self._breachers.items():
|
||||
new._breachers[k] = [b / div for b in v]
|
||||
return new
|
||||
|
||||
def __itruediv__(self, div):
|
||||
if div == 1:
|
||||
return
|
||||
self.em /= div
|
||||
self.thermal /= div
|
||||
self.kinetic /= div
|
||||
self.explosive /= div
|
||||
self._calcTotal()
|
||||
return self
|
||||
def __bool__(self):
|
||||
return any((
|
||||
self._em, self._thermal, self._kinetic, self._explosive,
|
||||
any(b.absolute or b.relative for b in self._breachers)))
|
||||
|
||||
def __repr__(self):
|
||||
spec = DmgTypes.names()
|
||||
spec.append('total')
|
||||
return makeReprStr(self, spec)
|
||||
class_name = type(self).__name__
|
||||
return (f'<{class_name}(em={self._em}, thermal={self._thermal}, kinetic={self._kinetic}, '
|
||||
f'explosive={self._explosive}, breachers={len(self._breachers)})>')
|
||||
|
||||
@staticmethod
|
||||
def names(short=None, postProcessor=None):
|
||||
def names(short=None, postProcessor=None, includePure=False):
|
||||
|
||||
value = [_t('em'), _t('th'), _t('kin'), _t('exp')] if short else [_t('em'), _t('thermal'), _t('kinetic'), _t('explosive')]
|
||||
if includePure:
|
||||
value += [_t('pure')]
|
||||
|
||||
if postProcessor:
|
||||
value = [postProcessor(x) for x in value]
|
||||
|
||||
24
graphs/data/fitDamageStats/cache/time.py
vendored
@@ -117,7 +117,7 @@ class TimeCache(FitDataCache):
|
||||
pointData[timeStart] = (dps, volley)
|
||||
# Gap between items
|
||||
elif floatUnerr(prevTimeEnd) < floatUnerr(timeStart):
|
||||
pointData[prevTimeEnd] = (DmgTypes(0, 0, 0, 0), DmgTypes(0, 0, 0, 0))
|
||||
pointData[prevTimeEnd] = (DmgTypes.default(), DmgTypes.default())
|
||||
pointData[timeStart] = (dps, volley)
|
||||
# Changed value
|
||||
elif dps != prevDps or volley != prevVolley:
|
||||
@@ -157,7 +157,7 @@ class TimeCache(FitDataCache):
|
||||
def addDpsVolley(ddKey, addedTimeStart, addedTimeFinish, addedVolleys):
|
||||
if not addedVolleys:
|
||||
return
|
||||
volleySum = sum(addedVolleys, DmgTypes(0, 0, 0, 0))
|
||||
volleySum = sum(addedVolleys, DmgTypes.default())
|
||||
if volleySum.total > 0:
|
||||
addedDps = volleySum / (addedTimeFinish - addedTimeStart)
|
||||
# We can take "just best" volley, no matter target resistances, because all
|
||||
@@ -170,24 +170,38 @@ class TimeCache(FitDataCache):
|
||||
def addDmg(ddKey, addedTime, addedDmg):
|
||||
if addedDmg.total == 0:
|
||||
return
|
||||
addedDmg._breachers = {addedTime + k: v for k, v in addedDmg._breachers.items()}
|
||||
addedDmg._clear_cached()
|
||||
intCacheDmg.setdefault(ddKey, {})[addedTime] = addedDmg
|
||||
|
||||
# Modules
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
cycleParams = mod.getCycleParameters(reloadOverride=True)
|
||||
cycleParams = mod.getCycleParametersForDps(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
nonstopCycles = 0
|
||||
isBreacher = mod.isBreacher
|
||||
for cycleTimeMs, inactiveTimeMs, isInactivityReload in cycleParams.iterCycles():
|
||||
cycleVolleys = []
|
||||
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
|
||||
|
||||
for volleyTimeMs, volley in volleyParams.items():
|
||||
cycleVolleys.append(volley)
|
||||
addDmg(mod, currentTime + volleyTimeMs / 1000, volley)
|
||||
addDpsVolley(mod, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
|
||||
time = currentTime + volleyTimeMs / 1000
|
||||
if isBreacher:
|
||||
time += 1
|
||||
addDmg(mod, time, volley)
|
||||
if isBreacher:
|
||||
break
|
||||
timeStart = currentTime
|
||||
timeFinish = currentTime + cycleTimeMs / 1000
|
||||
if isBreacher:
|
||||
timeStart += 1
|
||||
timeFinish += 1
|
||||
addDpsVolley(mod, timeStart, timeFinish, cycleVolleys)
|
||||
if inactiveTimeMs > 0:
|
||||
nonstopCycles = 0
|
||||
else:
|
||||
|
||||
@@ -98,6 +98,8 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
elif mod.isBreacher:
|
||||
applicationMap[mod] = getBreacherMult(mod=mod, distance=distance) if inLockRange else 0
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isDealingDamage():
|
||||
continue
|
||||
@@ -192,6 +194,21 @@ def getLauncherMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||
return distanceFactor * applicationFactor
|
||||
|
||||
|
||||
def getBreacherMult(mod, distance):
|
||||
missileMaxRangeData = mod.missileMaxRangeData
|
||||
if missileMaxRangeData is None:
|
||||
return 0
|
||||
# The ranges already consider ship radius
|
||||
lowerRange, higherRange, higherChance = missileMaxRangeData
|
||||
if distance is None or distance <= lowerRange:
|
||||
distanceFactor = 1
|
||||
elif lowerRange < distance <= higherRange:
|
||||
distanceFactor = higherChance
|
||||
else:
|
||||
distanceFactor = 0
|
||||
return distanceFactor
|
||||
|
||||
|
||||
def getSmartbombMult(mod, distance):
|
||||
modRange = mod.maxRange
|
||||
if modRange is None:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
import eos.config
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from eos.utils.spoolSupport import SpoolOptions, SpoolType
|
||||
from eos.utils.stats import DmgTypes
|
||||
from graphs.data.base import PointGetter, SmoothPointGetter
|
||||
@@ -27,17 +28,16 @@ from .calc.application import getApplicationPerKey
|
||||
from .calc.projected import getScramRange, getScrammables, getTackledSpeed, getSigRadiusMult
|
||||
|
||||
|
||||
def applyDamage(dmgMap, applicationMap, tgtResists):
|
||||
total = DmgTypes(em=0, thermal=0, kinetic=0, explosive=0)
|
||||
def applyDamage(dmgMap, applicationMap, tgtResists, tgtFullHp):
|
||||
total = DmgTypes.default()
|
||||
for key, dmg in dmgMap.items():
|
||||
total += dmg * applicationMap.get(key, 0)
|
||||
if not GraphSettings.getInstance().get('ignoreResists'):
|
||||
emRes, thermRes, kinRes, exploRes = tgtResists
|
||||
total = DmgTypes(
|
||||
em=total.em * (1 - emRes),
|
||||
thermal=total.thermal * (1 - thermRes),
|
||||
kinetic=total.kinetic * (1 - kinRes),
|
||||
explosive=total.explosive * (1 - exploRes))
|
||||
else:
|
||||
emRes = thermRes = kinRes = exploRes = 0
|
||||
total.profile = TargetProfile(
|
||||
emAmount=emRes, thermalAmount=thermRes, kineticAmount=kinRes, explosiveAmount=exploRes, hp=tgtFullHp)
|
||||
return total
|
||||
|
||||
|
||||
@@ -144,7 +144,8 @@ class XDistanceMixin(SmoothPointGetter):
|
||||
'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()}
|
||||
'tgtResists': tgt.getResists(),
|
||||
'tgtFullHp': tgt.getFullHp()}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
@@ -186,7 +187,8 @@ class XDistanceMixin(SmoothPointGetter):
|
||||
y = applyDamage(
|
||||
dmgMap=commonData['dmgMap'],
|
||||
applicationMap=applicationMap,
|
||||
tgtResists=commonData['tgtResists']).total
|
||||
tgtResists=commonData['tgtResists'],
|
||||
tgtFullHp=commonData['tgtFullHp']).total
|
||||
return y
|
||||
|
||||
|
||||
@@ -241,14 +243,17 @@ class XTimeMixin(PointGetter):
|
||||
self._prepareTimeCache(src=src, maxTime=maxTime)
|
||||
timeCache = self._getTimeCacheData(src=src)
|
||||
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
|
||||
tgtResists = tgt.getResists()
|
||||
# Custom iteration for time graph to show all data points
|
||||
currentDmg = None
|
||||
currentTime = None
|
||||
for currentTime in sorted(timeCache):
|
||||
prevDmg = currentDmg
|
||||
currentDmgData = timeCache[currentTime]
|
||||
currentDmg = applyDamage(dmgMap=currentDmgData, applicationMap=applicationMap, tgtResists=tgtResists).total
|
||||
currentDmg = applyDamage(
|
||||
dmgMap=currentDmgData,
|
||||
applicationMap=applicationMap,
|
||||
tgtResists=tgt.getResists(),
|
||||
tgtFullHp=tgt.getFullHp()).total
|
||||
if currentTime < minTime:
|
||||
continue
|
||||
# First set of data points
|
||||
@@ -294,7 +299,11 @@ class XTimeMixin(PointGetter):
|
||||
self._prepareTimeCache(src=src, maxTime=time)
|
||||
dmgData = self._getTimeCacheDataPoint(src=src, time=time)
|
||||
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
|
||||
y = applyDamage(dmgMap=dmgData, applicationMap=applicationMap, tgtResists=tgt.getResists()).total
|
||||
y = applyDamage(
|
||||
dmgMap=dmgData,
|
||||
applicationMap=applicationMap,
|
||||
tgtResists=tgt.getResists(),
|
||||
tgtFullHp=tgt.getFullHp()).total
|
||||
return y
|
||||
|
||||
|
||||
@@ -310,7 +319,8 @@ class XTgtSpeedMixin(SmoothPointGetter):
|
||||
return {
|
||||
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
|
||||
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
|
||||
'tgtResists': tgt.getResists()}
|
||||
'tgtResists': tgt.getResists(),
|
||||
'tgtFullHp': tgt.getFullHp()}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
tgtSpeed = x
|
||||
@@ -353,7 +363,8 @@ class XTgtSpeedMixin(SmoothPointGetter):
|
||||
y = applyDamage(
|
||||
dmgMap=commonData['dmgMap'],
|
||||
applicationMap=applicationMap,
|
||||
tgtResists=commonData['tgtResists']).total
|
||||
tgtResists=commonData['tgtResists'],
|
||||
tgtFullHp=commonData['tgtFullHp']).total
|
||||
return y
|
||||
|
||||
|
||||
@@ -398,7 +409,8 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
|
||||
'tgtSpeed': tgtSpeed,
|
||||
'tgtSigMult': tgtSigMult,
|
||||
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
|
||||
'tgtResists': tgt.getResists()}
|
||||
'tgtResists': tgt.getResists(),
|
||||
'tgtFullHp': tgt.getFullHp()}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
tgtSigRadius = x
|
||||
@@ -414,7 +426,8 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
|
||||
y = applyDamage(
|
||||
dmgMap=commonData['dmgMap'],
|
||||
applicationMap=applicationMap,
|
||||
tgtResists=commonData['tgtResists']).total
|
||||
tgtResists=commonData['tgtResists'],
|
||||
tgtFullHp=commonData['tgtFullHp']).total
|
||||
return y
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class FitDamageStatsGraph(FitGraph):
|
||||
cols = []
|
||||
if not GraphSettings.getInstance().get('ignoreResists'):
|
||||
cols.append('Target Resists')
|
||||
cols.extend(('Speed', 'SigRadius', 'Radius'))
|
||||
cols.extend(('Speed', 'SigRadius', 'Radius', 'FullHP'))
|
||||
return cols
|
||||
|
||||
# Calculation stuff
|
||||
|
||||
@@ -273,7 +273,7 @@ class GraphCanvasPanel(wx.Panel):
|
||||
legendLines = []
|
||||
for i, iData in enumerate(legendData):
|
||||
color, lineStyle, label = iData
|
||||
legendLines.append(Line2D([0], [0], color=color, linestyle=lineStyle, label=label.replace('$', '\$')))
|
||||
legendLines.append(Line2D([0], [0], color=color, linestyle=lineStyle, label=label.replace('$', r'\$')))
|
||||
|
||||
if len(legendLines) > 0 and self.graphFrame.ctrlPanel.showLegend:
|
||||
legend = self.subplot.legend(handles=legendLines)
|
||||
|
||||
@@ -145,6 +145,11 @@ class TargetWrapper(BaseWrapper):
|
||||
else:
|
||||
return em, therm, kin, explo
|
||||
|
||||
def getFullHp(self):
|
||||
if self.isProfile:
|
||||
return self.item.hp
|
||||
if self.isFit:
|
||||
return self.item.hp.get('shield', 0) + self.item.hp.get('armor', 0) + self.item.hp.get('hull', 0)
|
||||
|
||||
|
||||
def _getShieldResists(ship):
|
||||
|
||||
@@ -43,6 +43,10 @@ class BoosterViewDrop(wx.DropTarget):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
data = dragged_data.split(':')
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ class CargoViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -56,6 +56,10 @@ class CommandViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -52,6 +52,10 @@ class DroneViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
@@ -195,7 +199,11 @@ class DroneView(Display):
|
||||
|
||||
@staticmethod
|
||||
def droneKey(drone):
|
||||
groupName = Market.getInstance().getMarketGroupByItem(drone.item).marketGroupName
|
||||
if drone.isMutated:
|
||||
item = drone.baseItem
|
||||
else:
|
||||
item = drone.item
|
||||
groupName = Market.getInstance().getMarketGroupByItem(item).marketGroupName
|
||||
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
|
||||
|
||||
def fitChanged(self, event):
|
||||
|
||||
@@ -52,6 +52,10 @@ class FighterViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -46,6 +46,10 @@ class ImplantViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -65,6 +65,10 @@ class ProjectedViewDrop(wx.DropTarget):
|
||||
def OnData(self, x, y, t):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -22,9 +22,9 @@ class ItemDescription(wx.Panel):
|
||||
|
||||
desc = item.description.replace("\n", "<br>")
|
||||
# Strip font tags
|
||||
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
|
||||
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", r"\g<inside>", desc)
|
||||
# Strip URLs
|
||||
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
|
||||
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", r"\g<inside>", desc)
|
||||
desc = "<body bgcolor='{}' text='{}'>{}</body>".format(
|
||||
bgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
|
||||
fgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
|
||||
|
||||
@@ -163,6 +163,10 @@ class PFListPane(wx.ScrolledWindow):
|
||||
def RemoveAllChildren(self):
|
||||
for widget in self._wList:
|
||||
widget.Destroy()
|
||||
# this forces the garbage collector to work properly by removing dangling references to objects which are still alive, otherwise widget cannot be gc-ed eventually causing GDI id exhaustion and crash
|
||||
for i in widget.__dict__.keys():
|
||||
widget.__dict__[i] =None
|
||||
del widget
|
||||
|
||||
self.Scroll(0, 0)
|
||||
self._wList = []
|
||||
|
||||
@@ -173,7 +173,7 @@ class FirepowerViewFull(StatsView):
|
||||
if hasSpool:
|
||||
lines.append("")
|
||||
lines.append(_t("Current") + ": {}".format(formatAmount(normal.total, prec, lowest, highest)))
|
||||
for dmgType in normal.names():
|
||||
for dmgType in normal.names(includePure=True):
|
||||
val = getattr(normal, dmgType, None)
|
||||
if val:
|
||||
lines.append("{}{}: {}%".format(
|
||||
@@ -215,13 +215,13 @@ class FirepowerViewFull(StatsView):
|
||||
val = val() if fit is not None else None
|
||||
preSpoolVal = preSpoolVal() if fit is not None else None
|
||||
fullSpoolVal = fullSpoolVal() if fit is not None else None
|
||||
if self._cachedValues[counter] != val:
|
||||
if self._cachedValues[counter] != getattr(val, 'total', None):
|
||||
tooltipText = dpsToolTip(val, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat.format(
|
||||
formatAmount(0 if val is None else val.total, prec, lowest, highest),
|
||||
"\u02e2" if hasSpoolUp(preSpoolVal, fullSpoolVal) else ""))
|
||||
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||
self._cachedValues[counter] = val
|
||||
self._cachedValues[counter] = getattr(val, 'total', None)
|
||||
counter += 1
|
||||
|
||||
self.panel.Layout()
|
||||
|
||||
@@ -197,6 +197,30 @@ class SignatureRadiusColumn(GraphColumn):
|
||||
SignatureRadiusColumn.register()
|
||||
|
||||
|
||||
class FullHpColumn(GraphColumn):
|
||||
|
||||
name = 'FullHP'
|
||||
stickPrefixToValue = True
|
||||
|
||||
def __init__(self, fittingView, params):
|
||||
super().__init__(fittingView, 68)
|
||||
|
||||
def _getValue(self, stuff):
|
||||
if isinstance(stuff, Fit):
|
||||
full_hp = stuff.hp.get('shield', 0) + stuff.hp.get('armor', 0) + stuff.hp.get('hull', 0)
|
||||
elif isinstance(stuff, TargetProfile):
|
||||
full_hp = stuff.hp
|
||||
else:
|
||||
full_hp = 0
|
||||
return full_hp, 'hp'
|
||||
|
||||
def _getFitTooltip(self):
|
||||
return 'Total raw HP'
|
||||
|
||||
|
||||
FullHpColumn.register()
|
||||
|
||||
|
||||
class ShieldAmountColumn(GraphColumn):
|
||||
|
||||
name = 'ShieldAmount'
|
||||
|
||||
@@ -810,13 +810,16 @@ class Miscellanea(ViewColumn):
|
||||
return text, tooltip
|
||||
elif chargeGroup in ("SCARAB Breacher Pods",):
|
||||
duration = stuff.getModifiedChargeAttr("dotDuration") / 1000
|
||||
dmgAbs = stuff.getModifiedChargeAttr("dotMaxDamagePerTick") * duration
|
||||
dmgRel = stuff.getModifiedChargeAttr("dotMaxHPPercentagePerTick") * duration
|
||||
dmgAbs = stuff.getModifiedChargeAttr("dotMaxDamagePerTick")
|
||||
dmgRel = stuff.getModifiedChargeAttr("dotMaxHPPercentagePerTick")
|
||||
text = "{}/{}% over {}s".format(
|
||||
formatAmount(dmgAbs, 3, 0, 6),
|
||||
formatAmount(dmgRel, 3, 0, 6),
|
||||
formatAmount(dmgAbs * duration, 3, 0, 6),
|
||||
formatAmount(dmgRel * duration, 3, 0, 6),
|
||||
formatAmount(duration, 0, 0, 0))
|
||||
tooltip = "Pure damage done over time, minimum of absolute / relative"
|
||||
fullDmgHp = dmgAbs / (dmgRel / 100)
|
||||
tooltip = (
|
||||
'Pure damage inflicted over time, minimum of absolute / relative\n'
|
||||
'Full DPS from {} target HP').format(formatAmount(fullDmgHp, 3, 0, 6))
|
||||
return text, tooltip
|
||||
else:
|
||||
return "", None
|
||||
|
||||
@@ -127,6 +127,10 @@ class FittingViewDrop(wx.DropTarget):
|
||||
if self.GetData():
|
||||
dragged_data = DragDropHelper.data
|
||||
# pyfalog.debug("fittingView: recieved drag: " + self.dropData.GetText())
|
||||
|
||||
if dragged_data is None:
|
||||
return t
|
||||
|
||||
data = dragged_data.split(':')
|
||||
self.dropFn(x, y, data)
|
||||
return t
|
||||
|
||||
@@ -13,8 +13,8 @@ from service.market import Market
|
||||
|
||||
|
||||
def stripHtml(text):
|
||||
text = re.sub('<\s*br\s*/?\s*>', '\n', text)
|
||||
text = re.sub('</?[^/]+?(/\s*)?>', '', text)
|
||||
text = re.sub(r'<\s*br\s*/?\s*>', '\n', text)
|
||||
text = re.sub(r'</?[^/]+?(/\s*)?>', '', text)
|
||||
return text
|
||||
|
||||
|
||||
|
||||
@@ -481,6 +481,35 @@ class SkillTreeView(wx.Panel):
|
||||
|
||||
toClipboard(list)
|
||||
|
||||
def exportSkillsSuperCondensed(self, evt):
|
||||
char = self.charEditor.entityEditor.getActiveEntity()
|
||||
|
||||
skills = {}
|
||||
explicit_levels = {}
|
||||
implicit_levels = {}
|
||||
for s in char.__class__.getSkillNameMap().keys():
|
||||
skill = char.getSkill(s)
|
||||
if skill.level < 1:
|
||||
continue
|
||||
skills[skill.item.ID] = skill
|
||||
explicit_levels[skill.item.ID] = skill.level
|
||||
|
||||
for skill in skills.values():
|
||||
for req_skill, level in skill.item.requiredSkills.items():
|
||||
if req_skill.ID not in implicit_levels or implicit_levels[req_skill.ID] < level:
|
||||
implicit_levels[req_skill.ID] = level
|
||||
|
||||
condensed = {}
|
||||
for typeID, level in explicit_levels.items():
|
||||
if typeID not in implicit_levels or implicit_levels[typeID] < level:
|
||||
condensed[skills[typeID].item.name] = level
|
||||
|
||||
lines = []
|
||||
for skill in sorted(condensed):
|
||||
lines.append(f'{skill}\t{condensed[skill]}')
|
||||
|
||||
toClipboard('\n'.join(lines))
|
||||
|
||||
def onSecStatus(self, event):
|
||||
sChar = Character.getInstance()
|
||||
char = self.charEditor.entityEditor.getActiveEntity()
|
||||
|
||||
@@ -123,13 +123,14 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
ATTRIBUTES = OrderedDict([
|
||||
('maxVelocity', (_t('Maximum speed'), 'm/s')),
|
||||
('signatureRadius', (_t('Signature radius\nLeave blank for infinitely big value'), 'm')),
|
||||
('radius', (_t('Radius'), 'm'))])
|
||||
('radius', (_t('Radius\nThe radius of the sphere that represents a ship/drone in space. Affects range calculations.'), 'm')),
|
||||
('hp', (_t('Total HP\nAffects how much damage breacher pods can do. Leave blank for infinitely big value'), 'hp'))])
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title=_t("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))
|
||||
size=wx.Size(630, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(450, 240))
|
||||
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.block = False
|
||||
@@ -145,36 +146,29 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
|
||||
contentSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
resistEditSizer = wx.FlexGridSizer(2, 6, 0, 2)
|
||||
resistEditSizer.AddGrowableCol(0)
|
||||
resistEditSizer.AddGrowableCol(5)
|
||||
resistEditSizer.SetFlexibleDirection(wx.BOTH)
|
||||
resistEditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
|
||||
resistEditSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
resistEditSizer.AddStretchSpacer()
|
||||
|
||||
defSize = wx.Size(50, -1)
|
||||
defSize = wx.Size(70, -1)
|
||||
|
||||
for i, type_ in enumerate(self.DAMAGE_TYPES):
|
||||
if i % 2:
|
||||
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
|
||||
border = 25
|
||||
else:
|
||||
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
|
||||
border = 5
|
||||
for type_ in self.DAMAGE_TYPES:
|
||||
leftPad = 25 if type_ != list(self.DAMAGE_TYPES)[0] else 0
|
||||
ttText = self.DAMAGE_TYPES[type_]
|
||||
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % type_, "gui"))
|
||||
bmp.SetToolTip(wx.ToolTip(ttText))
|
||||
resistEditSizer.Add(bmp, 0, style, border)
|
||||
resistEditSizer.Add(bmp, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, leftPad)
|
||||
# set text edit
|
||||
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize, validator=ResistValidator())
|
||||
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
|
||||
editBox.SetToolTip(wx.ToolTip(ttText))
|
||||
self.Bind(event=wx.EVT_TEXT, handler=self.OnFieldChanged, source=editBox)
|
||||
setattr(self, '{}Edit'.format(type_), editBox)
|
||||
resistEditSizer.Add(editBox, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
resistEditSizer.Add(editBox, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
unit = wx.StaticText(self, wx.ID_ANY, "%", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
unit.SetToolTip(wx.ToolTip(ttText))
|
||||
resistEditSizer.Add(unit, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
contentSizer.Add(resistEditSizer, 0, wx.EXPAND | wx.ALL, 5)
|
||||
resistEditSizer.AddStretchSpacer()
|
||||
contentSizer.Add(resistEditSizer, 1, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
miscAttrSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
miscAttrSizer.AddStretchSpacer()
|
||||
|
||||
@@ -66,8 +66,8 @@ class UpdateDialog(wx.Dialog):
|
||||
self.browser.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.OnNewWindow)
|
||||
|
||||
link_patterns = [
|
||||
(re.compile("#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"),
|
||||
(re.compile("@(\w+)", re.I), r"https://github.com/\1")
|
||||
(re.compile(r"#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"),
|
||||
(re.compile(r"@(\w+)", re.I), r"https://github.com/\1")
|
||||
]
|
||||
|
||||
markdowner = markdown2.Markdown(
|
||||
|
||||
32
gui/utils/gdi.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import gc
|
||||
from ctypes import *
|
||||
from collections import defaultdict
|
||||
import os
|
||||
def gdiReport(desc=''):
|
||||
PH = windll.kernel32.OpenProcess(0x400, 0, os.getpid())
|
||||
numGdi = windll.user32.GetGuiResources(PH, 0)
|
||||
windll.kernel32.CloseHandle(PH)
|
||||
print (f'{desc}, {numGdi}')
|
||||
|
||||
|
||||
last = None
|
||||
def output_memory():
|
||||
global last
|
||||
d = defaultdict(int)
|
||||
for o in gc.get_objects():
|
||||
name = type(o).__name__
|
||||
if name == 'Bitmap':
|
||||
del o
|
||||
d[name] += 1
|
||||
|
||||
items = d.items()
|
||||
items = sorted(items,key=lambda x:x[1])
|
||||
print('------')
|
||||
for key, value in items:
|
||||
if last is not None:
|
||||
if value -last[key] !=0:
|
||||
print(f'{key} {value - last[key]}, {value}')
|
||||
else:
|
||||
print( key, value)
|
||||
|
||||
last = d
|
||||
@@ -96,7 +96,7 @@ class FloatBox(wx.TextCtrl):
|
||||
if currentValue == self._storedValue:
|
||||
event.Skip()
|
||||
return
|
||||
if currentValue == '' or re.match('^\d*\.?\d*$', currentValue):
|
||||
if currentValue == '' or re.match(r'^\d*\.?\d*$', currentValue):
|
||||
self._storedValue = currentValue
|
||||
self.updateColor()
|
||||
event.Skip()
|
||||
@@ -131,7 +131,7 @@ class FloatRangeBox(wx.TextCtrl):
|
||||
if currentValue == self._storedValue:
|
||||
event.Skip()
|
||||
return
|
||||
if currentValue == '' or re.match('^\d*\.?\d*-?\d*\.?\d*$', currentValue):
|
||||
if currentValue == '' or re.match(r'^\d*\.?\d*-?\d*\.?\d*$', currentValue):
|
||||
self._storedValue = currentValue
|
||||
event.Skip()
|
||||
else:
|
||||
|
||||
BIN
imgs/gui/hp_big.png
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
imgs/icons/10877@1x.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
imgs/icons/10877@2x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
imgs/icons/1156@1x.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
imgs/icons/1156@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 784 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 808 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 782 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 816 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/25666@1x.png
Normal file
|
After Width: | Height: | Size: 899 B |
BIN
imgs/icons/25666@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 890 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 901 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 901 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 895 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 913 B After Width: | Height: | Size: 905 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 910 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 915 B After Width: | Height: | Size: 905 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 890 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 666 B After Width: | Height: | Size: 902 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 906 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 675 B After Width: | Height: | Size: 902 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 664 B After Width: | Height: | Size: 881 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 899 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 893 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 907 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 909 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.8 KiB |