32 Commits

Author SHA1 Message Date
6bb0938be0 Fix ammo filtering 2025-12-15 22:47:12 +01:00
8a37ee810a Use 7z LIKE GOD INTENDED IT instead of fucking zip 2025-12-15 22:47:12 +01:00
4d1320161a Add a release script 2025-12-15 22:47:12 +01:00
b5d6211ae0 Add a build script 2025-12-15 22:47:12 +01:00
fa05cd625f Add rig filter button 2025-12-15 22:47:12 +01:00
d18ebb6dc0 Filter rigs by calibration fits as well 2025-12-15 22:47:12 +01:00
f3a89157ca Have fits filter rigs by size 2025-12-15 22:47:12 +01:00
766d45dd17 Make fits a check instead of radio button 2025-12-15 22:47:12 +01:00
457bbc0dc3 Fix rigs never passing fitting check because no cpu 2025-12-15 22:47:12 +01:00
4ddf1733e4 Refresh market filter when fit changed 2025-12-15 22:47:12 +01:00
ca2a80cc85 Hallucinte some more buttons below market 2025-12-15 22:47:11 +01:00
DarkPhoenix
c7074f499f Add a guard against situation where fit is empty for some reason 2025-12-15 12:18:52 +01:00
DarkPhoenix
f6f3a69be4 Fail build if image tool couldn't be fetched 2025-12-11 19:21:03 +01:00
DarkPhoenix
23e09729f7 CCP mistype is actually a mistype on my part 2025-12-11 18:50:14 +01:00
DarkPhoenix
0aca05704f Bump version 2025-12-11 18:03:49 +01:00
DarkPhoenix
b08894e984 Anhinga effect changes 2025-12-11 18:03:32 +01:00
DarkPhoenix
a1bc8742c9 Add new renders 2025-12-11 15:58:02 +01:00
DarkPhoenix
6472cabc05 Update static data and make some changes to support new AT ships 2025-12-11 15:54:11 +01:00
DarkPhoenix
56bb8217d3 Do not fail whole app when ESI access object fails instantiation 2025-12-10 19:22:52 +01:00
DarkPhoenix
17f9071317 Bump version 2025-12-09 15:28:26 +01:00
DarkPhoenix
50eda1f4db Add option to copy condensed skill list 2025-12-09 15:26:17 +01:00
DarkPhoenix
84fbc0a46c Fix clipboard on my wayland installation 2025-12-09 15:08:38 +01:00
Anton Vorobyov
9551195078 Merge pull request #2700 from skyride/master
Fixed bug with Clipboard on Wayland
2025-12-09 17:49:24 +04:00
DarkPhoenix
f01949d892 Add expedition hold display 2025-12-09 14:44:43 +01:00
DarkPhoenix
26b4c05b6f Stacking penalize residue probability attribute 2025-12-09 14:41:40 +01:00
Anton Vorobyov
6ecab03fd8 Merge pull request #2683 from cryonox/dev/refresh_tokens
Refresh tokens when pyfa start to avoid expiry
2025-12-09 17:35:55 +04:00
DarkPhoenix
edc0418d9a Add perserverance effects 2025-12-09 14:31:05 +01:00
DarkPhoenix
1d413595b9 Update icons 2025-12-09 14:17:49 +01:00
DarkPhoenix
ce5a593f7b Add missing wightstorm booster effects 2025-12-09 14:15:35 +01:00
DarkPhoenix
faea6a97f0 Update static data 2025-12-09 14:13:14 +01:00
Amy Findlay
b92913cbf9 Fixed bug with Clipboard on Wayland 2025-11-22 19:57:55 +01:00
cryonox
edec81f4b8 Refresh tokens when pyfa start to avoid expiry 2025-09-10 12:39:34 +08:00
50 changed files with 29902 additions and 12376 deletions

View File

@@ -32,7 +32,7 @@ for:
- sh: export PYFA_VERSION="$(python3 -B scripts/dump_version.py)"
- sh: mkdir build
# Download packaging tool
- sh: curl -o $APPIMAGE_TOOL -L https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
- sh: curl --fail-with-body -o $APPIMAGE_TOOL -L https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
- sh: chmod +x $APPIMAGE_TOOL
build_script:
- sh: mkdir -p AppDir/opt/pyfa

3
.gitignore vendored
View File

@@ -126,4 +126,5 @@ gitversion
/locale/progress.json
# vscode settings
.vscode
.vscode
eve.db

28
build.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -e
echo "Building pyfa binary..."
# Ensure we're using the local venv
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
uv venv
fi
# Install dependencies
echo "Installing dependencies..."
uv pip install -r requirements.txt
uv pip install pyinstaller
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf build dist
# Build the binary
echo "Building binary with PyInstaller..."
uv run pyinstaller pyfa.spec
echo ""
echo "Build complete! Binary is located at: dist/pyfa/pyfa.exe"
echo "You can run it with: dist/pyfa/pyfa.exe"

View File

@@ -1333,10 +1333,10 @@ class Effect446(BaseEffect):
Implants named like: Capsuleer Defense Augmentation Chip (3 of 3)
Implants named like: Festival only 'Rock' SH Dose (4 of 4)
Implants named like: Halcyon G Booster (5 of 5)
Implants named like: Nirvana Booster (5 of 5)
Implants named like: Serenity Limited 'Hardshell' Dose (3 of 3)
Implants named like: Zainou 'Gnome' Shield Management SM (6 of 6)
Modules named like: Core Defense Field Extender (8 of 8)
Implant: AIR Nirvana Booster II
Implant: Genolution Core Augmentation CA-3
Implant: Sansha Modified 'Gnome' Implant
Skill: Shield Management
@@ -1358,6 +1358,7 @@ class Effect485(BaseEffect):
Implants named like: Halcyon G Booster (5 of 5)
Implants named like: Halcyon R Booster (5 of 5)
Implants named like: Inherent Implants 'Squire' Capacitor Systems Operation EO (6 of 6)
Implants named like: Wightstorm Rapture Booster (4 of 4)
Implants named like: grade Rapture (15 of 18)
Modules named like: Capacitor Control Circuit (8 of 8)
Implant: AIR Overclocker Booster III
@@ -2603,15 +2604,22 @@ class Effect891(BaseEffect):
Used by:
Variations of ship: Raven (3 of 4)
Module: Anhinga Tertiary Mode
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Cruise Missiles'),
'maxVelocity', ship.getModifiedItemAttr('shipBonusCB3'),
skill='Caldari Battleship', **kwargs)
if 'ship' in context:
skill = 'Caldari Battleship'
penalties = False
else:
skill = None
penalties = True
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Cruise Missiles'), 'maxVelocity',
ship.getModifiedItemAttr('shipBonusCB3'), skill=skill, stackingPenalties=penalties, **kwargs)
class Effect892(BaseEffect):
@@ -2620,15 +2628,22 @@ class Effect892(BaseEffect):
Used by:
Variations of ship: Raven (3 of 4)
Module: Anhinga Tertiary Mode
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Torpedoes'),
'maxVelocity', ship.getModifiedItemAttr('shipBonusCB3'),
skill='Caldari Battleship', **kwargs)
if 'ship' in context:
skill = 'Caldari Battleship'
penalties = False
else:
skill = None
penalties = True
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Torpedoes'), 'maxVelocity',
ship.getModifiedItemAttr('shipBonusCB3'), skill=skill, stackingPenalties=penalties, **kwargs)
class Effect896(BaseEffect):
@@ -3285,6 +3300,7 @@ class Effect1024(BaseEffect):
shipMissileHeavyVelocityBonusCC2
Used by:
Module: Anhinga Tertiary Mode
Ship: Caracal
Ship: Osprey Navy Issue
"""
@@ -3293,9 +3309,15 @@ class Effect1024(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Heavy Missiles'),
'maxVelocity', ship.getModifiedItemAttr('shipBonusCC2'),
skill='Caldari Cruiser', **kwargs)
if 'ship' in context:
skill = 'Caldari Cruiser'
penalties = False
else:
skill = None
penalties = True
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Heavy Missiles'), 'maxVelocity',
ship.getModifiedItemAttr('shipBonusCC2'), skill=skill, stackingPenalties=penalties, **kwargs)
class Effect1030(BaseEffect):
@@ -3905,6 +3927,7 @@ class Effect1230(BaseEffect):
shipMissileVelocityPirateFactionFrigate
Used by:
Module: Anhinga Primary Mode
Ship: Barghest
Ship: Garmur
Ship: Laelaps
@@ -3916,8 +3939,10 @@ class Effect1230(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
'maxVelocity', ship.getModifiedItemAttr('shipBonusRole7'), **kwargs)
penalties = 'ship' not in context
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'), 'maxVelocity',
ship.getModifiedItemAttr('shipBonusRole7'), stackingPenalties=penalties, **kwargs)
class Effect1232(BaseEffect):
@@ -5654,6 +5679,7 @@ class Effect1885(BaseEffect):
shipCruiseLauncherROFBonus2CB
Used by:
Modules named like: Anhinga Mode (3 of 3)
Ship: Raven
Ship: Raven State Issue
"""
@@ -5662,9 +5688,18 @@ class Effect1885(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Cruise',
'speed', ship.getModifiedItemAttr('shipBonus2CB'),
skill='Caldari Battleship', **kwargs)
if 'ship' in context:
skill = 'Caldari Battleship'
penalties = False
penaltyGroup = None
else:
skill = None
penalties = True
penaltyGroup = 'postPerc'
fit.modules.filteredItemBoost(
lambda mod: mod.item.group.name == 'Missile Launcher Cruise', 'speed',
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
class Effect1886(BaseEffect):
@@ -5672,6 +5707,7 @@ class Effect1886(BaseEffect):
shipSiegeLauncherROFBonus2CB
Used by:
Modules named like: Anhinga Mode (3 of 3)
Ship: Raven
Ship: Raven State Issue
"""
@@ -5680,9 +5716,18 @@ class Effect1886(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Torpedo',
'speed', ship.getModifiedItemAttr('shipBonus2CB'),
skill='Caldari Battleship', **kwargs)
if 'ship' in context:
skill = 'Caldari Battleship'
penalties = False
penaltyGroup = None
else:
skill = None
penalties = True
penaltyGroup = 'postPerc'
fit.modules.filteredItemBoost(
lambda mod: mod.item.group.name == 'Missile Launcher Torpedo', 'speed',
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
class Effect1910(BaseEffect):
@@ -8398,6 +8443,7 @@ class Effect2803(BaseEffect):
Used by:
Implants named like: Harvest Damage Booster (4 of 4)
Implants named like: Wightstorm Vitarka Booster (4 of 4)
Modules named like: Energy Collision Accelerator (8 of 8)
Implant: Wisdom of Gheinok
"""
@@ -9674,6 +9720,7 @@ class Effect3196(BaseEffect):
thermodynamicsSkillDamageBonus
Used by:
Implants named like: Wightstorm Sunyata Booster (4 of 4)
Skill: Thermodynamics
"""
@@ -18029,6 +18076,7 @@ class Effect5189(BaseEffect):
Used by:
Implants named like: Tetrimon Precision Booster (4 of 4)
Implants named like: Wightstorm Manasikara Booster (4 of 4)
Modules named like: Energy Metastasis Adjuster (8 of 8)
"""
@@ -18179,6 +18227,7 @@ class Effect5213(BaseEffect):
shipRocketMaxVelocityBonusRookie
Used by:
Module: Skua Sharpshooter Mode
Ship: Taipan
"""
@@ -18186,8 +18235,10 @@ class Effect5213(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Rockets'),
'maxVelocity', ship.getModifiedItemAttr('rookieRocketVelocity'), **kwargs)
penalties = 'ship' not in context
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Rockets'), 'maxVelocity',
ship.getModifiedItemAttr('rookieRocketVelocity'), stackingPenalties=penalties, **kwargs)
class Effect5214(BaseEffect):
@@ -18195,6 +18246,7 @@ class Effect5214(BaseEffect):
shipLightMissileMaxVelocityBonusRookie
Used by:
Module: Skua Sharpshooter Mode
Ship: Taipan
"""
@@ -18202,8 +18254,10 @@ class Effect5214(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Light Missiles'),
'maxVelocity', ship.getModifiedItemAttr('rookieLightMissileVelocity'), **kwargs)
penalties = 'ship' not in context
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Light Missiles'), 'maxVelocity',
ship.getModifiedItemAttr('rookieLightMissileVelocity'), stackingPenalties=penalties, **kwargs)
class Effect5215(BaseEffect):
@@ -20345,6 +20399,7 @@ class Effect5468(BaseEffect):
shipBonusAgilityCI2
Used by:
Module: Anhinga Tertiary Mode
Ship: Badger
"""
@@ -20352,7 +20407,14 @@ class Effect5468(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.ship.boostItemAttr('agility', ship.getModifiedItemAttr('shipBonusCI2'), skill='Caldari Hauler', **kwargs)
if 'ship' in context:
skill = 'Caldari Hauler'
penalties = False
else:
skill = None
penalties = True
fit.ship.boostItemAttr('agility', ship.getModifiedItemAttr('shipBonusCI2'),
skill=skill, stackingPenalties=penalties, **kwargs)
class Effect5469(BaseEffect):
@@ -20885,6 +20947,24 @@ class Effect5559(BaseEffect):
'shieldBonus', ship.getModifiedItemAttr('shipBonusMC2'), skill='Minmatar Cruiser', **kwargs)
class Effect5560(BaseEffect):
"""
roleBonusMarauderMJDRReactivationDelayBonus
Used by:
Module: Anhinga Tertiary Mode
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
penalties = 'ship' not in context
fit.modules.filteredItemBoost(
lambda mod: mod.item.group.name == 'Micro Jump Drive', 'moduleReactivationDelay',
ship.getModifiedItemAttr('roleBonusMarauder'), stackingPenalties=penalties, **kwargs)
class Effect5564(BaseEffect):
"""
subSystemBonusCaldariOffensiveCommandBursts
@@ -21075,6 +21155,7 @@ class Effect5618(BaseEffect):
shipBonusRHMLROF2CB
Used by:
Modules named like: Anhinga Mode (3 of 3)
Ship: Raven
Ship: Widow
"""
@@ -21083,8 +21164,18 @@ class Effect5618(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Rapid Heavy',
'speed', ship.getModifiedItemAttr('shipBonus2CB'), skill='Caldari Battleship', **kwargs)
if 'ship' in context:
skill = 'Caldari Battleship'
penalties = False
penaltyGroup = None
else:
skill = None
penalties = True
penaltyGroup = 'postPerc'
fit.modules.filteredItemBoost(
lambda mod: mod.item.group.name == 'Missile Launcher Rapid Heavy', 'speed',
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
class Effect5619(BaseEffect):
@@ -22373,6 +22464,8 @@ class Effect5867(BaseEffect):
shipBonusMissileExplosionDelayPirateFaction2
Used by:
Module: Anhinga Primary Mode
Module: Anhinga Secondary Mode
Ship: Barghest
Ship: Garmur
Ship: Laelaps
@@ -22384,8 +22477,10 @@ class Effect5867(BaseEffect):
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
'explosionDelay', ship.getModifiedItemAttr('shipBonusRole8'), **kwargs)
penalties = 'ship' not in context
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'), 'explosionDelay',
ship.getModifiedItemAttr('shipBonusRole8'), stackingPenalties=penalties, **kwargs)
class Effect5868(BaseEffect):
@@ -23307,7 +23402,7 @@ class Effect6009(BaseEffect):
Used by:
Ships from group: Strategic Cruiser (4 of 4)
Ships from group: Tactical Destroyer (4 of 4)
Ships from group: Tactical Destroyer (5 of 5)
"""
type = 'passive'
@@ -23324,7 +23419,8 @@ class Effect6010(BaseEffect):
shipModeMaxTargetRangePostDiv
Used by:
Modules named like: Sharpshooter Mode (4 of 4)
Modules named like: Sharpshooter Mode (5 of 5)
Module: Anhinga Primary Mode
"""
type = 'passive'
@@ -23365,7 +23461,7 @@ class Effect6012(BaseEffect):
shipModeScanStrengthPostDiv
Used by:
Modules named like: Sharpshooter Mode (4 of 4)
Modules named like: Sharpshooter Mode (5 of 5)
"""
type = 'passive'
@@ -23386,8 +23482,7 @@ class Effect6014(BaseEffect):
modeSigRadiusPostDiv
Used by:
Module: Confessor Defense Mode
Module: Jackdaw Defense Mode
Modules named like: Defense Mode (3 of 5)
"""
type = 'passive'
@@ -23403,7 +23498,7 @@ class Effect6015(BaseEffect):
modeArmorResonancePostDiv
Used by:
Modules named like: Defense Mode (3 of 4)
Modules named like: Defense Mode (3 of 5)
"""
type = 'passive'
@@ -23429,7 +23524,7 @@ class Effect6016(BaseEffect):
modeAgilityPostDiv
Used by:
Modules named like: Propulsion Mode (4 of 4)
Modules named like: Propulsion Mode (5 of 5)
"""
type = 'passive'
@@ -23643,8 +23738,7 @@ class Effect6041(BaseEffect):
modeShieldResonancePostDiv
Used by:
Module: Jackdaw Defense Mode
Module: Svipul Defense Mode
Modules named like: Defense Mode (3 of 5)
"""
type = 'passive'
@@ -23968,6 +24062,7 @@ class Effect6077(BaseEffect):
Used by:
Ship: Jackdaw
Ship: Skua
"""
type = 'passive'
@@ -23986,6 +24081,7 @@ class Effect6083(BaseEffect):
Used by:
Ship: Jackdaw
Ship: Metamorphosis
Ship: Skua
Ship: Sunesis
"""
@@ -24005,6 +24101,7 @@ class Effect6085(BaseEffect):
Used by:
Ship: Jackdaw
Ship: Skua
"""
type = 'passive'
@@ -24079,6 +24176,7 @@ class Effect6098(BaseEffect):
Used by:
Ship: Jackdaw
Ship: Skua
"""
type = 'passive'
@@ -25472,6 +25570,7 @@ class Effect6316(BaseEffect):
Used by:
Ships from group: Command Destroyer (3 of 6)
Ship: Skua
"""
type = 'passive'
@@ -25490,6 +25589,7 @@ class Effect6317(BaseEffect):
Used by:
Ships from group: Command Destroyer (6 of 6)
Ship: Skua
"""
type = 'passive'
@@ -25763,6 +25863,7 @@ class Effect6334(BaseEffect):
Used by:
Ships from group: Command Destroyer (3 of 6)
Ship: Skua
"""
type = 'passive'
@@ -31727,6 +31828,7 @@ class Effect6799(BaseEffect):
Used by:
Module: Jackdaw Sharpshooter Mode
Module: Skua Sharpshooter Mode
"""
type = 'passive'
@@ -31747,7 +31849,7 @@ class Effect6800(BaseEffect):
modeDampTDResistsPostDiv
Used by:
Modules named like: Sharpshooter Mode (4 of 4)
Modules named like: Sharpshooter Mode (5 of 5)
"""
type = 'passive'
@@ -31763,8 +31865,7 @@ class Effect6801(BaseEffect):
modeMWDandABBoostPostDiv
Used by:
Module: Confessor Propulsion Mode
Module: Svipul Propulsion Mode
Modules named like: Propulsion Mode (3 of 5)
"""
type = 'passive'
@@ -34760,11 +34861,20 @@ class Effect7117(BaseEffect):
roleBonusWarpSpeed
Used by:
Items from category: Ship (42 of 409)
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
Ship: Khizriel
Ship: Leopard
Ship: Machariel
Ship: Mekubal
Ship: Sarathiel
Ship: Victorieux Luxury Yacht
"""
type = 'passive'
@@ -37751,6 +37861,23 @@ class Effect8279(BaseEffect):
skill='Industrial Command Ships', **kwargs)
class Effect8291(BaseEffect):
"""
afterburnerSpeedBoostBonusPassive
Used by:
Implants named like: Wightstorm Cetana Booster (4 of 4)
"""
type = 'passive'
@staticmethod
def handler(fit, booster, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Afterburner'), 'speedFactor',
booster.getModifiedItemAttr('speedFBonus'), **kwargs)
class Effect8294(BaseEffect):
"""
industrialCommandBonusDroneOreMiningYield
@@ -41626,7 +41753,7 @@ class Effect12329(BaseEffect):
shipMiningYieldBonusOreDestroyer1
Used by:
Variations of ship: Pioneer (3 of 3)
Variations of ship: Pioneer (3 of 4)
"""
type = 'passive'
@@ -42378,6 +42505,26 @@ class Effect12757(BaseEffect):
'miningCritBonusYield', src.getModifiedItemAttr('miningCritBonusYieldBonus') * src.level, **kwargs)
class Effect12758(BaseEffect):
"""
shipRoleBonusAnhingaLargeMissilePowerFittingBonus
Used by:
Ship: Anhinga
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemMultiply(
lambda mod: mod.item.group.name in (
'Missile Launcher Rapid Heavy',
'Missile Launcher Cruise',
'Missile Launcher Torpedo'),
'power', ship.getModifiedItemAttr('AnhingaLargeMissilePowerFittingBonus'), **kwargs)
class Effect12759(BaseEffect):
"""
miningCritChanceBonusOreIceOnline
@@ -42428,4 +42575,160 @@ class Effect12761(BaseEffect):
lambda mod: (mod.item.requiresSkill('Mining')
or mod.item.requiresSkill('Ice Harvesting')
or mod.item.requiresSkill('Gas Cloud Harvesting')),
'miningWasteProbability', src.getModifiedItemAttr('miningWasteProbabilityBonus'), **kwargs)
'miningWasteProbability', src.getModifiedItemAttr('miningWasteProbabilityBonus'),
stackingPenalties=True, **kwargs)
class Effect12764(BaseEffect):
"""
shipRoleBonusAnhingaLargeMissileCpuFittingBonus
Used by:
Ship: Anhinga
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemMultiply(
lambda mod: mod.item.group.name in (
'Missile Launcher Rapid Heavy',
'Missile Launcher Cruise',
'Missile Launcher Torpedo'),
'cpu', ship.getModifiedItemAttr('AnhingaLargeMissileCpuFittingBonus'), **kwargs)
class Effect12766(BaseEffect):
"""
shipBonusTorpedoAndCruiseMissileExplosionRadiusCBC1
Used by:
Ship: Anhinga
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Torpedoes') or mod.charge.requiresSkill('Cruise Missiles'),
'aoeCloudSize', ship.getModifiedItemAttr('shipBonusCBC1'), skill='Caldari Battlecruiser', **kwargs)
class Effect12767(BaseEffect):
"""
tacticalBonusSkuaDefensiveShieldRechargeRate
Used by:
Module: Skua Defense Mode
"""
type = 'passive'
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
fit.ship.multiplyItemAttr('shieldRechargeRate', 1 / module.getModifiedItemAttr('modeShieldRechargePostDiv'), **kwargs)
class Effect12771(BaseEffect):
"""
shipRoleBonusPerseveranceIceMiningCriticalHitChanceBonus
Used by:
Ship: Perseverance
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Ice Harvesting'), 'miningCritChance',
ship.getModifiedItemAttr('shipRoleBonusPerseveranceIceMiningCriticalHitChance'), **kwargs)
class Effect12772(BaseEffect):
"""
shipIceMiningCriticalHitChanceBonusOreDestroyer1
Used by:
Ship: Perseverance
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Ice Harvesting'), 'miningCritChance',
ship.getModifiedItemAttr('shipBonusOreDestroyer1'), skill='Mining Destroyer', **kwargs)
class Effect12773(BaseEffect):
"""
shipIceMiningCriticalHitYieldBonusOreDestroyer2
Used by:
Ship: Perseverance
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Ice Harvesting'), 'miningCritBonusYield',
ship.getModifiedItemAttr('shipBonusOreDestroyer2'), skill='Mining Destroyer', **kwargs)
class Effect12774(BaseEffect):
"""
shipIceMiningRangeBonusOreDestroyer3
Used by:
Ship: Perseverance
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Ice Harvesting'), 'maxRange',
ship.getModifiedItemAttr('shipBonusOreDestroyer3'), skill='Mining Destroyer', **kwargs)
class Effect12777(BaseEffect):
"""
roleBonusCDLinksPGCPUReductionSkua
Used by:
Ship: Skua
"""
type = 'passive'
@staticmethod
def handler(fit, src, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'), 'cpu',
src.getModifiedItemAttr('roleBonusCD'), **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'), 'power',
src.getModifiedItemAttr('roleBonusCD'), **kwargs)
class Effect12790(BaseEffect):
"""
shipBonusTorpedoAndCruiseMissileExplosionVelocityCBC2
Used by:
Ship: Anhinga
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Torpedoes') or mod.charge.requiresSkill('Cruise Missiles'),
'aoeVelocity', ship.getModifiedItemAttr('shipBonusCBC2'), skill='Caldari Battlecruiser', **kwargs)

View File

@@ -951,7 +951,7 @@ class Fit:
mod.item.requiresSkill("Mining")
or mod.item.requiresSkill("Ice Harvesting")
or mod.item.requiresSkill("Gas Cloud Harvesting")),
"miningWasteProbability", value)
"miningWasteProbability", value, stackingPenalties=True)
del self.commandBonuses[warfareBuffID]

View File

@@ -126,7 +126,7 @@ class Ship(ItemAttrShortcut, HandledItem):
valid Item objects, not the Mode objects. Returns None if not a
t3 dessy
"""
if self.item.group.name != "Tactical Destroyer":
if self.item.group.name != "Tactical Destroyer" and self.item.name != "Anhinga":
return None
items = []

View File

@@ -47,6 +47,8 @@ class AddCurrentlyOpenFit(ContextMenuUnconditional):
if isinstance(page, BlankPage):
continue
fit = sFit.getFit(page.activeFitID, basic=True)
if fit is None:
continue
id = ContextMenuUnconditional.nextID()
mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name))
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)

View File

@@ -17,7 +17,10 @@ class ChangeShipTacticalMode(ContextMenuUnconditional):
self.modeMap = {
'Defense': _t('Defense'),
'Propulsion': _t('Propulsion'),
'Sharpshooter': _t('Sharpshooter')
'Sharpshooter': _t('Sharpshooter'),
'Primary': _t('Primary'),
'Secondary': _t('Secondary'),
'Tertiary': _t('Tertiary'),
}
def display(self, callingWindow, srcContext):

View File

@@ -5,6 +5,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
import gui.globalEvents as GE
from config import slotColourMap, slotColourMapDark
from eos.saveddata.module import Module
from eos.const import FittingSlot
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
from gui.contextMenu import ContextMenu
from gui.display import Display
@@ -153,19 +154,22 @@ class ItemView(Display):
# skip the event so the other handlers also get called
event.Skip()
if self.marketBrowser.mode != 'charges':
return
activeFitID = self.mainFrame.getActiveFit()
# if it was not the active fitting that was changed, do not do anything
if activeFitID is not None and activeFitID not in event.fitIDs:
return
items = self.getChargesForActiveFit()
# Handle charges mode
if self.marketBrowser.mode == 'charges':
items = self.getChargesForActiveFit()
# update the UI
self.updateItemStore(items)
self.filterItemStore()
return
# update the UI
self.updateItemStore(items)
self.filterItemStore()
# If "Fits" filter is active, re-filter the current view
if self.marketBrowser.getFitsFilter():
self.filterItemStore()
def updateItemStore(self, items):
self.unfilteredStore = items
@@ -197,13 +201,115 @@ class ItemView(Display):
if btn.userSelected:
selectedMetas.update(sMkt.META_MAP[btn.metaName])
filteredItems = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas)
# Apply slot/fits filters - works IDENTICALLY to meta buttons (filters CURRENT VIEW only)
activeSlotFilters = []
fitsFilterActive = False
for btn in self.marketBrowser.slotButtons:
if btn.userSelected:
if btn.filterType == "fits":
fitsFilterActive = True
elif btn.filterType == "slot":
activeSlotFilters.append(btn.slotType)
# Apply fits filter
if fitsFilterActive:
filteredItems = self._filterByFits(filteredItems)
# Apply slot filters
if activeSlotFilters:
filteredItems = [item for item in filteredItems if Module.calculateSlot(item) in activeSlotFilters]
return filteredItems
def _filterByFits(self, items):
"""Filter items by remaining CPU/PG - filters CURRENT VIEW only"""
fitId = self.mainFrame.getActiveFit()
if fitId is None:
return []
fit = self.sFit.getFit(fitId)
# Get remaining CPU and power grid
cpuOutput = fit.ship.getModifiedItemAttr("cpuOutput")
powerOutput = fit.ship.getModifiedItemAttr("powerOutput")
cpuUsed = fit.cpuUsed
pgUsed = fit.pgUsed
cpuRemaining = cpuOutput - cpuUsed
pgRemaining = powerOutput - pgUsed
# Get remaining calibration (for rigs)
calibrationCapacity = fit.ship.getModifiedItemAttr("upgradeCapacity")
calibrationUsed = fit.calibrationUsed
calibrationRemaining = None
if calibrationCapacity is not None and calibrationCapacity > 0:
calibrationRemaining = calibrationCapacity - calibrationUsed
fittingItems = []
for item in items:
# Check if item is a module (has a slot)
slot = Module.calculateSlot(item)
if slot is None:
continue
# Rigs don't use CPU/power, they use calibration - check rig size and calibration
if slot == FittingSlot.RIG:
# Check if item can fit on the ship
if not fit.canFit(item):
continue
# Check rig size compatibility with ship
shipRigSize = fit.ship.getModifiedItemAttr("rigSize")
itemRigSize = item.attributes.get("rigSize")
if shipRigSize is not None and itemRigSize is not None:
if shipRigSize != itemRigSize.value:
continue
# Check calibration requirement
if calibrationRemaining is not None and calibrationRemaining > 0:
itemCalibration = item.attributes.get("upgradeCost")
if itemCalibration is not None:
itemCalibrationValue = itemCalibration.value
if itemCalibrationValue > calibrationRemaining:
continue
fittingItems.append(item)
continue
# For non-rigs, check CPU and power requirements
itemCpu = item.attributes.get("cpu")
itemPower = item.attributes.get("power")
# Skip items without CPU or power (not modules)
if itemCpu is None and itemPower is None:
continue
# Check CPU requirement
if itemCpu is not None:
itemCpuValue = itemCpu.value
if itemCpuValue > cpuRemaining:
continue
# Check power requirement
if itemPower is not None:
itemPowerValue = itemPower.value
if itemPowerValue > pgRemaining:
continue
# Check if item can fit on the ship (most expensive check, do last)
if not fit.canFit(item):
continue
fittingItems.append(item)
return fittingItems
def setToggles(self):
metaIDs = set()
slotIDs = set()
sMkt = self.sMkt
for item in self.unfilteredStore:
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
slot = Module.calculateSlot(item)
if slot is not None:
slotIDs.add(slot)
for btn in self.marketBrowser.metaButtons:
btn.reset()
@@ -212,6 +318,20 @@ class ItemView(Display):
btn.setMetaAvailable(True)
else:
btn.setMetaAvailable(False)
# Set toggles for slot/fits buttons
for btn in self.marketBrowser.slotButtons:
btn.reset()
if btn.filterType == "fits":
# Fits button is available if there's an active fit
fitId = self.mainFrame.getActiveFit()
btn.setMetaAvailable(fitId is not None)
elif btn.filterType == "slot":
# Slot button is available if items with that slot exist in current view
if btn.slotType in slotIDs:
btn.setMetaAvailable(True)
else:
btn.setMetaAvailable(False)
def scheduleSearch(self, event=None):
self.searchTimer.Stop() # Cancel any pending timers

View File

@@ -130,6 +130,7 @@ class TargetingMiscViewMinimal(StatsView):
("specialPlanetaryCommoditiesHoldCapacity", _t("Planetary goods hold")),
("specialQuafeHoldCapacity", _t("Quafe hold")),
("specialMobileDepotHoldCapacity", _t("Mobile depot hold")),
("specialExpeditionHoldCapacity", _t("Expedition hold")),
))
cargoValues = {
@@ -154,6 +155,7 @@ class TargetingMiscViewMinimal(StatsView):
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity"),
"specialMobileDepotHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMobileDepotHoldCapacity"),
"specialExpeditionHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialExpeditionHoldCapacity"),
}
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),

View File

@@ -106,6 +106,9 @@ class CharacterSelection(wx.Panel):
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills"))
self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (condensed)"))
self.Bind(wx.EVT_MENU, self.exportSkillsCondensed, exportItem)
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (EVEMon)"))
self.Bind(wx.EVT_MENU, self.exportSkillsEveMon, exportItem)
@@ -268,6 +271,15 @@ class CharacterSelection(wx.Panel):
toClipboard(list)
def exportSkillsCondensed(self, evt):
skillsMap = self._buildSkillsTooltipSuperCondensed(self.reqs, skillsMap={})
list = ""
for key in sorted(skillsMap):
list += "%s %d\n" % (key, skillsMap[key][0])
toClipboard(list)
def exportSkillsEveMon(self, evt):
skillsMap = self._buildSkillsTooltipCondensed(self.reqs, skillsMap={})

View File

@@ -54,6 +54,7 @@ class MarketBrowser(wx.Panel):
self.settings = MarketPriceSettings.getInstance()
self.__mode = 'normal'
self.__normalBtnMap = {}
self.__normalSlotBtnMap = {}
self.marketView = MarketTree(self.splitter, self)
self.itemView = ItemView(self.splitter, self)
@@ -64,22 +65,61 @@ class MarketBrowser(wx.Panel):
# Same fix as for search box on macs,
# need some pixels of extra space or everything clips and is ugly
p = wx.Panel(self)
box = wx.BoxSizer(wx.HORIZONTAL)
p.SetSizer(box)
vbox_panel = wx.BoxSizer(wx.VERTICAL)
p.SetSizer(vbox_panel)
vbox.Add(p, 0, wx.EXPAND)
# First row: meta buttons
metaBox = wx.BoxSizer(wx.HORIZONTAL)
vbox_panel.Add(metaBox, 0, wx.EXPAND)
self.metaButtons = []
btn = None
for name in list(self.sMkt.META_MAP.keys()):
btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
setattr(self, name, btn)
box.Add(btn, 1, wx.ALIGN_CENTER)
metaBox.Add(btn, 1, wx.ALIGN_CENTER)
btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton)
btn.metaName = name
self.metaButtons.append(btn)
# Second row: slot/fits filter buttons (BELOW meta buttons)
slotBox = wx.BoxSizer(wx.HORIZONTAL)
vbox_panel.Add(slotBox, 0, wx.EXPAND)
self.slotButtons = []
from eos.const import FittingSlot
# Fits button
fitsBtn = MetaButton(p, wx.ID_ANY, "Fits", style=wx.BU_EXACTFIT)
setattr(self, "fits", fitsBtn)
slotBox.Add(fitsBtn, 1, wx.ALIGN_CENTER)
fitsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
fitsBtn.filterType = "fits"
# Fits button starts deselected (checkbox, off by default)
fitsBtn.setUserSelection(False)
self.slotButtons.append(fitsBtn)
# High, Med, Low, Rig buttons
slotMap = {
FittingSlot.HIGH: "High",
FittingSlot.MED: "Med",
FittingSlot.LOW: "Low",
FittingSlot.RIG: "Rig"
}
for slot, label in slotMap.items():
slotBtn = MetaButton(p, wx.ID_ANY, label, style=wx.BU_EXACTFIT)
setattr(self, "slot_%s" % label.lower(), slotBtn)
slotBox.Add(slotBtn, 1, wx.ALIGN_CENTER)
slotBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
slotBtn.filterType = "slot"
slotBtn.slotType = slot
# Slot buttons start deselected (unlike meta buttons which start selected)
slotBtn.setUserSelection(False)
self.slotButtons.append(slotBtn)
# Make itemview to set toggles according to list contents
self.itemView.setToggles()
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5))
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] * 2 + 10))
def toggleMetaButton(self, event):
"""Process clicks on toggle buttons"""
@@ -100,6 +140,42 @@ class MarketBrowser(wx.Panel):
self.itemView.filterItemStore()
def toggleSlotButton(self, event):
"""Process clicks on slot/fits filter buttons"""
clickedBtn = event.EventObject
# Fits button works as a checkbox (independent toggle)
if clickedBtn.filterType == "fits":
clickedBtn.setUserSelection(clickedBtn.GetValue())
self.itemView.filterItemStore()
return
# Slot buttons (High/Med/Low) work as radio buttons (mutually exclusive)
if wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL:
# Get only slot buttons (not fits)
activeSlotBtns = [btn for btn in self.slotButtons if btn.GetValue() and btn.filterType == "slot"]
if activeSlotBtns:
clickedBtn.setUserSelection(clickedBtn.GetValue())
self.itemView.filterItemStore()
else:
# Do 'nothing' if we're trying to turn last active button off
# Keep button in the same state
clickedBtn.setUserSelection(True)
else:
# Deselect all slot buttons, then select clicked one
for btn in self.slotButtons:
if btn.filterType == "slot":
btn.setUserSelection(btn == clickedBtn)
self.itemView.filterItemStore()
def getFitsFilter(self):
"""Check if Fits button is active"""
for btn in self.slotButtons:
if btn.filterType == "fits" and btn.userSelected:
return True
return False
def jump(self, item):
self.mode = 'normal'
self.marketView.jump(item)
@@ -141,6 +217,9 @@ class MarketBrowser(wx.Panel):
self.__normalBtnMap.clear()
for btn in self.metaButtons:
self.__normalBtnMap[btn] = btn.userSelected
self.__normalSlotBtnMap.clear()
for btn in self.slotButtons:
self.__normalSlotBtnMap[btn] = btn.userSelected
if newMode == 'search':
self.marketView.UnselectAll()
setting = self.settings.get('marketMGSearchMode')
@@ -149,12 +228,16 @@ class MarketBrowser(wx.Panel):
if newMode in ('search', 'recent', 'charges'):
for btn in self.metaButtons:
btn.setUserSelection(True)
# Clear slot button selections when searching (search can return any item type)
for btn in self.slotButtons:
btn.setUserSelection(False)
if newMode == 'normal':
for btn, state in self.__normalBtnMap.items():
btn.setUserSelection(state)
for btn, state in self.__normalSlotBtnMap.items():
btn.setUserSelection(state)
# We turn on all meta buttons permanently
if setting == 2:
for btn in self.metaButtons:
btn.setUserSelection(True)
self.__mode = newMode

View File

@@ -1,22 +1,68 @@
# noinspection PyPackageRequirements
import wx
from logbook import Logger
logger = Logger(__name__)
def toClipboard(text):
clip = wx.TheClipboard
clip.Open()
data = wx.TextDataObject(text)
clip.SetData(data)
clip.Close()
"""
Copy text to clipboard. Explicitly uses CLIPBOARD selection, not PRIMARY.
On X11 systems, wxPython can confuse between PRIMARY and CLIPBOARD selections,
causing "already open" errors. This function ensures we always use CLIPBOARD.
See: https://discuss.wxpython.org/t/wx-theclipboard-pasting-different-content-on-every-second-paste/35361
"""
clipboard = wx.TheClipboard
try:
# Explicitly use CLIPBOARD selection, not PRIMARY selection
# This prevents X11 confusion between the two clipboard types
clipboard.UsePrimarySelection(False)
if clipboard.Open():
try:
data = wx.TextDataObject(text)
clipboard.SetData(data)
return True
finally:
clipboard.Close()
else:
logger.debug("Failed to open clipboard for writing")
return False
except Exception as e:
logger.warning("Error writing to clipboard: {}", e)
return False
def fromClipboard():
clip = wx.TheClipboard
clip.Open()
data = wx.TextDataObject("")
if clip.GetData(data):
clip.Close()
return data.GetText()
else:
clip.Close()
"""
Read text from clipboard. Explicitly uses CLIPBOARD selection, not PRIMARY.
On X11 systems, wxPython can confuse between PRIMARY and CLIPBOARD selections,
causing "already open" errors. This function ensures we always use CLIPBOARD.
See: https://discuss.wxpython.org/t/wx-theclipboard-pasting-different-content-on-every-second-paste/35361
"""
clipboard = wx.TheClipboard
try:
# Explicitly use CLIPBOARD selection, not PRIMARY selection
# This prevents X11 confusion between the two clipboard types
clipboard.UsePrimarySelection(False)
if clipboard.Open():
try:
data = wx.TextDataObject()
if clipboard.GetData(data):
return data.GetText()
else:
logger.debug("Clipboard open but no CLIPBOARD data available")
return None
finally:
clipboard.Close()
else:
logger.debug("Failed to open clipboard for reading")
return None
except Exception as e:
logger.warning("Error reading from clipboard: {}", e)
return None

BIN
imgs/icons/10850@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

BIN
imgs/icons/10850@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
imgs/icons/24566@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
imgs/icons/24566@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

11
pyfa.py
View File

@@ -150,6 +150,17 @@ if __name__ == "__main__":
mf = MainFrame(options.title)
ErrorHandler.SetParent(mf)
# Start ESI token validation, this helps avoid token expiry
try:
from service.esi import Esi
esi = Esi.getInstance()
esi.startTokenValidation()
pyfalog.info("ESI token validation started")
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
pyfalog.warning(f"failed to start ESI token validation thread:\n{e}")
if options.profile_path:
profile_path = os.path.join(options.profile_path, 'pyfa-{}.profile'.format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
pyfalog.debug("Starting pyfa with a profiler, saving to {}".format(profile_path))

9
pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[project]
name = "pyfa"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ruff>=0.14.8",
]

60
release.sh Normal file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
set -e
echo "Figuring out the tag..."
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
if [ -z "$TAG" ]; then
# Get the latest tag
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "v0.0.0")
# Remove 'v' prefix if present
LATEST_TAG=${LATEST_TAG#v}
# Increment the patch version
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
TAG="v${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
# Create a new tag
git tag $TAG
git push origin $TAG
fi
echo "Tag: $TAG"
echo "Building the binary..."
./build.sh
echo "Creating release zip..."
ZIP="pyfa-${TAG}-win.zip"
7z a "${ZIP}" dist/pyfa/*
echo "Creating a release..."
TOKEN="$GITEA_API_KEY"
GITEA="https://git.site.quack-lab.dev"
REPO="dave/pyfa"
# Create a release
RELEASE_RESPONSE=$(curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "'"$TAG"'",
"name": "'"$TAG"'",
"draft": false,
"prerelease": false
}' \
$GITEA/api/v1/repos/$REPO/releases)
# Extract the release ID
echo $RELEASE_RESPONSE
RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
echo "Release ID: $RELEASE_ID"
echo "Uploading the zip..."
curl -X POST \
-H "Authorization: token $TOKEN" \
-F "attachment=@${ZIP}" \
"$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=${ZIP}"
rm "${ZIP}"
echo "Release complete! ${ZIP} uploaded to ${TAG}"

View File

@@ -41,11 +41,15 @@ def main(old, new, groups=True, effects=True, attributes=True, renames=True):
new_cursor = new_db.cursor()
# Force some of the items to make them published
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper",
"Amarr Tactical Destroyer Propulsion Mode",
"Amarr Tactical Destroyer Sharpshooter Mode",
"Amarr Tactical Destroyer Defense Mode")
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName = ?'
FORCEPUB_TYPES = (
"% Propulsion Mode",
"% Sharpshooter Mode",
"% Defense Mode",
"% Primary Mode",
"% Secondary Mode",
"% Tertiary Mode",
)
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName like ?'
for typename in FORCEPUB_TYPES:
old_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
new_cursor.execute(OVERRIDES_TYPEPUB, (typename,))

View File

@@ -25,6 +25,44 @@ pyfalog = Logger(__name__)
_t = wx.GetTranslation
class EsiTokenValidationThread(threading.Thread):
def __init__(self, callback=None):
threading.Thread.__init__(self)
self.name = "EsiTokenValidation"
self.callback = callback
self.running = True
def run(self):
with config.logging_setup.threadbound():
try:
esi = Esi.getInstance()
chars = esi.getSsoCharacters()
for char in chars:
if not self.running:
return
if char.is_token_expired():
pyfalog.info(f"Token expired for {char.characterName}, attempting refresh")
try:
esi.refresh(char)
eos.db.save(char)
pyfalog.info(f"Successfully refreshed token for {char.characterName}")
except Exception as e:
pyfalog.error(f"Failed to refresh token for {char.characterName}: {e}")
else:
pyfalog.debug(f"Token valid for {char.characterName}")
except Exception as e:
pyfalog.error(f"Error validating ESI tokens: {e}")
finally:
if self.callback:
wx.CallAfter(self.callback)
def stop(self):
self.running = False
class Esi(EsiAccess):
_instance = None
@@ -194,3 +232,9 @@ class Esi(EsiAccess):
pyfalog.debug("Handling SSO login with: {0}", message)
self.handleLogin(message['code'])
def startTokenValidation(self):
pyfalog.debug("Starting ESI token validation thread")
tokenValidationThread = EsiTokenValidationThread()
tokenValidationThread.daemon = True
tokenValidationThread.start()

View File

@@ -327,6 +327,8 @@ class Market:
"Sidewinder" : self.les_grp, # AT20 prize
"Cobra" : self.les_grp, # AT20 prize
"Python" : self.les_grp, # AT20 prize
"Skua" : self.les_grp, # AT21 prize
"Anhinga" : self.les_grp, # AT21 prize
}
self.ITEMS_FORCEGROUP_R = self.__makeRevDict(self.ITEMS_FORCEGROUP)

View File

@@ -49167,5 +49167,51 @@
"published": 1,
"stackable": 1,
"unitID": 105
},
"6054": {
"attributeID": 6054,
"categoryID": 37,
"dataType": 5,
"defaultValue": 0.0,
"displayWhenZero": 0,
"highIsGood": 1,
"name": "AnhingaLargeMissilePowerFittingBonus",
"published": 0,
"stackable": 1
},
"6055": {
"attributeID": 6055,
"categoryID": 37,
"dataType": 5,
"defaultValue": 0.0,
"displayWhenZero": 0,
"highIsGood": 1,
"name": "AnhingaLargeMissileCpuFittingBonus",
"published": 0,
"stackable": 1
},
"6057": {
"attributeID": 6057,
"categoryID": 2,
"dataType": 5,
"defaultValue": 1.0,
"description": "",
"displayWhenZero": 0,
"highIsGood": 0,
"name": "modeShieldRechargePostDiv",
"published": 0,
"stackable": 1
},
"6062": {
"attributeID": 6062,
"categoryID": 51,
"dataType": 5,
"defaultValue": 0.0,
"description": "Role Bonus for Perseverance Ice Mining Critical Hit Role bonus",
"displayWhenZero": 0,
"highIsGood": 1,
"name": "shipRoleBonusPerseveranceIceMiningCriticalHitChance",
"published": 0,
"stackable": 1
}
}

View File

@@ -58317,6 +58317,7 @@
],
"propulsionChance": 0,
"published": 0,
"rangeAttributeID": 54,
"rangeChance": 0
},
"6216": {
@@ -99669,6 +99670,56 @@
"published": 0,
"rangeChance": 0
},
"12758": {
"description_de": "Automatically generated effect",
"description_en-us": "Automatically generated effect",
"description_es": "Automatically generated effect",
"description_fr": "Automatically generated effect",
"description_it": "Automatically generated effect",
"description_ja": "Automatically generated effect",
"description_ko": "Automatically generated effect",
"description_ru": "Automatically generated effect",
"description_zh": "Automatically generated effect",
"descriptionID": 1022818,
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12758,
"effectName": "shipRoleBonusAnhingaLargeMissilePowerFittingBonus",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 1245,
"modifiedAttributeID": 30,
"modifyingAttributeID": 6054,
"operation": 0
},
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 506,
"modifiedAttributeID": 30,
"modifyingAttributeID": 6054,
"operation": 0
},
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 508,
"modifiedAttributeID": 30,
"modifyingAttributeID": 6054,
"operation": 0
}
],
"propulsionChance": 0,
"published": 0,
"rangeAttributeID": 54,
"rangeChance": 0
},
"12759": {
"disallowAutoRepeat": 0,
"effectCategory": 4,
@@ -99769,5 +99820,285 @@
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12764": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12764,
"effectName": "shipRoleBonusAnhingaLargeMissileCpuFittingBonus",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 1245,
"modifiedAttributeID": 50,
"modifyingAttributeID": 6055,
"operation": 0
},
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 506,
"modifiedAttributeID": 50,
"modifyingAttributeID": 6055,
"operation": 0
},
{
"domain": "shipID",
"func": "LocationGroupModifier",
"groupID": 508,
"modifiedAttributeID": 50,
"modifyingAttributeID": 6055,
"operation": 0
}
],
"propulsionChance": 0,
"published": 0,
"rangeAttributeID": 54,
"rangeChance": 0
},
"12765": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12765,
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionVelocityMB",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 653,
"modifyingAttributeID": 490,
"operation": 6,
"skillTypeID": 3325
},
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 653,
"modifyingAttributeID": 490,
"operation": 6,
"skillTypeID": 3326
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12766": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12766,
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionRadiusCBC1",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 654,
"modifyingAttributeID": 743,
"operation": 6,
"skillTypeID": 3326
},
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 654,
"modifyingAttributeID": 743,
"operation": 6,
"skillTypeID": 3325
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12767": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12767,
"effectName": "tacticalBonusSkuaDefensiveShieldRechargeRate",
"electronicChance": 0,
"guid": "",
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "ItemModifier",
"modifiedAttributeID": 479,
"modifyingAttributeID": 6057,
"operation": 5
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12771": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12771,
"effectName": "shipRoleBonusPerseveranceIceMiningCriticalHitChanceBonus",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 5967,
"modifyingAttributeID": 6062,
"operation": 6,
"skillTypeID": 16281
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12772": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12772,
"effectName": "shipIceMiningCriticalHitChanceBonusOreDestroyer1",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 5967,
"modifyingAttributeID": 5820,
"operation": 6,
"skillTypeID": 16281
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12773": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12773,
"effectName": "shipIceMiningCriticalHitYieldBonusOreDestroyer2",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 5969,
"modifyingAttributeID": 5821,
"operation": 6,
"skillTypeID": 16281
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12774": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12774,
"effectName": "shipIceMiningRangeBonusOreDestroyer3",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 54,
"modifyingAttributeID": 5822,
"operation": 6,
"skillTypeID": 16281
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12777": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12777,
"effectName": "roleBonusCDLinksPGCPUReductionSkua",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 30,
"modifyingAttributeID": 2064,
"operation": 6,
"skillTypeID": 3348
},
{
"domain": "shipID",
"func": "LocationRequiredSkillModifier",
"modifiedAttributeID": 50,
"modifyingAttributeID": 2064,
"operation": 6,
"skillTypeID": 3348
}
],
"propulsionChance": 0,
"published": 0,
"rangeChance": 0
},
"12790": {
"disallowAutoRepeat": 0,
"effectCategory": 0,
"effectID": 12790,
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionVelocityCBC2",
"electronicChance": 0,
"isAssistance": 0,
"isOffensive": 0,
"isWarpSafe": 0,
"modifierInfo": [
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 653,
"modifyingAttributeID": 745,
"operation": 6,
"skillTypeID": 3325
},
{
"domain": "charID",
"func": "OwnerRequiredSkillModifier",
"modifiedAttributeID": 653,
"modifyingAttributeID": 745,
"operation": 6,
"skillTypeID": 3326
}
],
"propulsionChance": 0,
"published": 0,
"rangeAttributeID": 54,
"rangeChance": 0
}
}

View File

@@ -29731,7 +29731,7 @@
"groupName_ja": "突入ポッドランチャー",
"groupName_ko": "침투 포드 런처",
"groupName_ru": "Пусковая установка внедряющихся капсул",
"groupName_zh": "分裂者级逃生舱发射器",
"groupName_zh": "突破者级逃生舱发射器",
"groupNameID": 726805,
"published": 1,
"useBasePrice": 0
@@ -29750,7 +29750,7 @@
"groupName_ja": "突入ポッド「SCARAB」",
"groupName_ko": "스캐럽 침투 포드",
"groupName_ru": "Внедряющиеся капсулы «Скарабей»",
"groupName_zh": "圣甲虫分裂者级逃生舱",
"groupName_zh": "圣甲虫突破者级逃生舱",
"groupNameID": 726814,
"published": 1,
"useBasePrice": 0
@@ -30182,14 +30182,14 @@
"categoryID": 2,
"fittableNonSingleton": 0,
"groupID": 4918,
"groupName_de": "Phasenasteroiden",
"groupName_en-us": "Phased Asteroids",
"groupName_es": "Asteroides fásicos",
"groupName_fr": "Astéroïdes phasiques",
"groupName_it": "Phased Asteroids",
"groupName_de": "Phasenasteroid",
"groupName_en-us": "Phased Asteroid",
"groupName_es": "Asteroide fásico",
"groupName_fr": "Astéroïde phasique",
"groupName_it": "Phased Asteroid",
"groupName_ja": "位相偏移アステロイド",
"groupName_ko": "위상 소행성",
"groupName_ru": "Фазовые астероиды",
"groupName_ru": "Фазовый астероид",
"groupName_zh": "相位小行星",
"groupNameID": 1019374,
"published": 0,
@@ -30366,6 +30366,63 @@
"published": 0,
"useBasePrice": 0
},
"4949": {
"anchorable": 0,
"anchored": 0,
"categoryID": 11,
"fittableNonSingleton": 0,
"groupID": 4949,
"groupName_de": "Interbus-Yoiul-LADs",
"groupName_en-us": "InterBus Yoiul LADs",
"groupName_es": "MAL de Yoiul de InterBus",
"groupName_fr": "LUTINs de Yoiul d'InterBus",
"groupName_it": "InterBus Yoiul LADs",
"groupName_ja": "インターバス・ヨイウルLAD",
"groupName_ko": "인터버스 요이얼 LAD",
"groupName_ru": "Йольские ГАДы консорциума «ИнтерБас»",
"groupName_zh": "星际捷运尤尔节自动化物流配送舰",
"groupNameID": 1025585,
"published": 0,
"useBasePrice": 0
},
"4972": {
"anchorable": 0,
"anchored": 0,
"categoryID": 17,
"fittableNonSingleton": 0,
"groupID": 4972,
"groupName_de": "Verity-Kryo-Technologie",
"groupName_en-us": "Verity Cryo Tech",
"groupName_es": "Tecnología criogénica de Verity",
"groupName_fr": "Cryotechnologie de Verity",
"groupName_it": "Verity Cryo Tech",
"groupName_ja": "ヴェリティ・クライオテック",
"groupName_ko": "베리티 크라이오 장비",
"groupName_ru": "Криотехнология «Подлинных улучшений»",
"groupName_zh": "维里蒂低温技术",
"groupNameID": 1028940,
"published": 1,
"useBasePrice": 1
},
"4975": {
"anchorable": 0,
"anchored": 0,
"categoryID": 11,
"fittableNonSingleton": 0,
"groupID": 4975,
"groupName_de": "Irregular Capital Industrial Ship",
"groupName_en-us": "Irregular Capital Industrial Ship",
"groupName_es": "Irregular Capital Industrial Ship",
"groupName_fr": "Irregular Capital Industrial Ship",
"groupName_it": "Irregular Capital Industrial Ship",
"groupName_ja": "Irregular Capital Industrial Ship",
"groupName_ko": "Irregular Capital Industrial Ship",
"groupName_ru": "Irregular Capital Industrial Ship",
"groupName_zh": "Irregular Capital Industrial Ship",
"groupNameID": 1030693,
"published": 0,
"useBasePrice": 0
},
"350858": {
"anchorable": 0,
"anchored": 0,

View File

@@ -14686,5 +14686,131 @@
},
"27266": {
"iconFile": "res:/UI/Texture/Icons/Modules/fleetBoost_MiningCrit.png"
},
"27267": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_racial_ama_05_128.png"
},
"27268": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_racial_ama_05_128.png"
},
"27269": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_racial_ama_05_128.png"
},
"27270": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_ornament_01_128.png"
},
"27271": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_ornament_01_128.png"
},
"27272": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_art_deco_01_2K_128.png"
},
"27273": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_10_128.png"
},
"27274": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_10_128.png"
},
"27275": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_08_128.png"
},
"27276": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_08_128.png"
},
"27277": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_05_128.png"
},
"27278": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_05_128.png"
},
"27279": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_16_128.png"
},
"27280": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_16_128.png"
},
"27281": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_12_128.png"
},
"27282": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_12_128.png"
},
"27283": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_07_128.png"
},
"27284": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_07_128.png"
},
"27285": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_11_128.png"
},
"27286": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_11_128.png"
},
"27287": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_organic_10_128.png"
},
"27288": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_09_128.png"
},
"27289": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_06_128.png"
},
"27290": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_14_128.png"
},
"27291": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/patterns/cosm_winter_14_128.png"
},
"27292": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_turquoise_gloss_000_060_100_cs180.png"
},
"27293": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_malachite_highgloss_000_100_010_cs180.png"
},
"27294": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_orange_matt_000_100_010_cs180.png"
},
"27295": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_emerald_brushed_000_060_100.png"
},
"27296": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_emerald_brushed_000_060_100.png"
},
"27297": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_royal_polished_000_080_100.png"
},
"27298": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_royal_polished_000_080_100.png"
},
"27299": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_emerald_polished_000_100_010.png"
},
"27300": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_emerald_polished_000_100_010.png"
},
"27301": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_azure_gloss_000_030_100.png"
},
"27302": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_azure_gloss_000_030_100.png"
},
"27303": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_azure_highgloss_000_100_001.png"
},
"27304": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_azure_highgloss_000_100_001.png"
},
"27305": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_rosered_satin_000_100_050.png"
},
"27306": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_rosered_satin_000_100_050.png"
},
"27307": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_royal_metallic_000_100_100.png"
},
"27309": {
"iconFile": "res:/UI/Texture/classes/Cosmetics/Ship/materials/cosm_brass_metallic_013_051_089.png"
}
}

View File

@@ -45856,7 +45856,7 @@
"name_ja": "突入ポッド",
"name_ko": "침투 포드",
"name_ru": "Внедряющиеся капсулы",
"name_zh": "分裂者级逃生舱",
"name_zh": "突破者级逃生舱",
"nameID": 871476,
"parentGroupID": 11
},
@@ -45881,7 +45881,7 @@
"name_ja": "突入ポッドランチャー",
"name_ko": "침투 포드 런처",
"name_ru": "Пусковая установка внедряющихся капсул",
"name_zh": "分裂者级逃生舱发射器",
"name_zh": "突破者级逃生舱发射器",
"nameID": 871478,
"parentGroupID": 10
},

View File

@@ -11511,7 +11511,7 @@
"17940": {
"3380": 5,
"3410": 3,
"89241": 3
"32918": 3
},
"17975": {
"16281": 1
@@ -30076,6 +30076,12 @@
"3436": 1,
"9955": 5
},
"89807": {
"33096": 1
},
"89808": {
"35680": 1
},
"90037": {
"11584": 3
},
@@ -30249,8 +30255,86 @@
"43703": 1,
"60515": 1
},
"90665": {
"3386": 1
"90669": {
"3405": 1
},
"90670": {
"3405": 1
},
"90671": {
"3405": 1
},
"90672": {
"3405": 1
},
"90673": {
"3405": 1
},
"90674": {
"3405": 1
},
"90675": {
"3405": 1
},
"90676": {
"3405": 1
},
"90677": {
"3405": 1
},
"90678": {
"3405": 1
},
"90679": {
"3405": 1
},
"90680": {
"3405": 1
},
"90682": {
"3405": 1
},
"90683": {
"3405": 1
},
"90684": {
"3405": 1
},
"90685": {
"3405": 1
},
"90686": {
"3405": 1
},
"90687": {
"3405": 1
},
"90688": {
"3405": 1
},
"90689": {
"3405": 1
},
"90690": {
"3405": 1
},
"90691": {
"3405": 1
},
"90692": {
"3405": 1
},
"90693": {
"3405": 1
},
"90694": {
"3402": 1
},
"90695": {
"3402": 1
},
"90696": {
"3402": 1
},
"90727": {
"3386": 3
@@ -30263,5 +30347,18 @@
"90733": {
"3348": 1,
"22536": 1
},
"91044": {
"21718": 1
},
"91045": {
"21718": 1
},
"91046": {
"21718": 1
},
"91174": {
"16281": 1,
"89241": 2
}
}

View File

@@ -827996,7 +827996,7 @@
},
{
"attributeID": 184,
"value": 89241.0
"value": 32918.0
},
{
"attributeID": 275,

View File

@@ -878783,6 +878783,10 @@
"attributeID": 1298,
"value": 1283.0
},
{
"attributeID": 1302,
"value": 91174.0
},
{
"attributeID": 1768,
"value": 11346.0
@@ -878873,6 +878877,10 @@
"attributeID": 1298,
"value": 1283.0
},
{
"attributeID": 1302,
"value": 91174.0
},
{
"attributeID": 1768,
"value": 11346.0
@@ -878963,6 +878971,10 @@
"attributeID": 1298,
"value": 1283.0
},
{
"attributeID": 1302,
"value": 91174.0
},
{
"attributeID": 1692,
"value": 4.0
@@ -886541,6 +886553,10 @@
"attributeID": 1299,
"value": 1534.0
},
{
"attributeID": 1303,
"value": 89808.0
},
{
"attributeID": 1544,
"value": 1.0
@@ -958018,6 +958034,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -958188,6 +958208,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -958528,6 +958552,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -958698,6 +958726,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -970503,6 +970535,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -970685,6 +970721,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -970867,6 +970907,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1303,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0
@@ -971049,6 +971093,10 @@
"attributeID": 1301,
"value": 540.0
},
{
"attributeID": 1302,
"value": 89808.0
},
{
"attributeID": 1795,
"value": 60000.0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -168393,7 +168393,7 @@
"wreckTypeID": 41699
},
"42125": {
"basePrice": 119829000000.0,
"basePrice": 20000000000.0,
"capacity": 1750.0,
"certificateTemplate": 86,
"description_de": "<i>\"Die Föderation glaubt immer noch, sie könne mit der Zukunft der Menschen ihre Spiele treiben. So, wie sie auch mit dem Lebenswerk meines Vaters gespielt hat.\n\n\n\nIch werde ihnen zeigen, wie man dieses Spiel richtig spielt. Sie haben einen Mann zerstört, der die Wissenschaft und Anwendung von Neuralboostern revolutioniert hätte.\n\n\n\nIch werde sie zerstören, indem ich ihnen zeige, wie mächtig diese Wissenschaft sein kann. Es wird keinen Frieden, kein Erbarmen geben. Ich schwöre Rache, im Namen und in Erinnerung Igil Sarpatis.\"</i>\n\n\n\n- V. Salvador Sarpati, im Interview mit Alton Haveri von Scope",
@@ -168442,7 +168442,7 @@
"wreckTypeID": 72817
},
"42126": {
"basePrice": 672620000000.0,
"basePrice": 60000000000.0,
"capacity": 18000.0,
"certificateTemplate": 244,
"description_de": "<i>\"Die Kaperung der Molyneux hat mir gezeigt, dass Kapselpiloten herausragende Dinge leisten können, wenn sie Blut gerochen haben. Eine Zeit lang war ich mir nicht sicher, ob wir entkommen würden.\n\n\n\nNicht wegen der Regierungspolizei. Nein, die waren der reinste Witz. Aber die Kapselpiloten hätten mich fast erwischt. Das Witzige an der Sache ist, dass sie einander fast so erbittert bekämpft haben, wie sie versuchten mich zu fassen.\n\n\n\nEinige haben wir sogar geholfen. Wissen Sie, der Pfad der Zerstörung, den wir hinterlassen haben, ging fast ausschließlich auf sie zurück. Ich denke, es wäre sicher amüsant, ihnen ein paar mehr Spielzeuge zu geben, Sie nicht?\"</i>\n\n\n\n- V. Salvador Sarpati, im Interview mit Alton Haveri von Scope",
@@ -171548,7 +171548,7 @@
"volume": 1.0
},
"42241": {
"basePrice": 253642000000.0,
"basePrice": 60000000000.0,
"capacity": 12400.0,
"certificateTemplate": 249,
"description_de": "<i>\"Molok, vom falschen Glauben als 'Der Täuscher' verhöhnt, war wahrhaftiger als jeder der sogenannten 'Echten Amarr', die den korrupten Thronbeschmutzern Gefolgschaft leisteten, es sich je erhoffen konnte. Er war es, der die wahre Natur des Universums begriffen hatte. Es gibt nur eine Wahrheit: Macht ist alles; die nackte, gnadenlose Kraft in der Hand jener, die den Willen haben, nach ihr zu greifen. Wir trinken das Blut der Mächtigen und der Reinen, denn um zu überleben und fortzuschreiten, müssen wir uns an ihrer Energie laben und sie unser eigen machen. Das ist die Lektion von Molok. Sein Scheitern ist nicht von Belang, seine Erkenntnis der großen Wahrheit ist alles, was zählt.\"</i>\n\n\n\n- Omir Sarikusa, Meditationen des Blutroten Kelchs",
@@ -178117,6 +178117,7 @@
"descriptionID": 522185,
"groupID": 1770,
"iconID": 21687,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 1,
@@ -178153,6 +178154,7 @@
"descriptionID": 522191,
"groupID": 1770,
"iconID": 21691,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 1,
@@ -178225,6 +178227,7 @@
"descriptionID": 522212,
"groupID": 1770,
"iconID": 21700,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 1,
@@ -178261,6 +178264,7 @@
"descriptionID": 522216,
"groupID": 1770,
"iconID": 21704,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 1,
@@ -199677,6 +199681,7 @@
"descriptionID": 522187,
"groupID": 1770,
"iconID": 21687,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 2,
@@ -199714,6 +199719,7 @@
"descriptionID": 522194,
"groupID": 1770,
"iconID": 21691,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 2,
@@ -199751,6 +199757,7 @@
"descriptionID": 522214,
"groupID": 1770,
"iconID": 21700,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 2,
@@ -199788,6 +199795,7 @@
"descriptionID": 522226,
"groupID": 1770,
"iconID": 21704,
"isDynamicType": 0,
"marketGroupID": 1633,
"mass": 0.0,
"metaGroupID": 2,
@@ -238958,7 +238966,7 @@
"volume": 0.01
},
"45649": {
"basePrice": 254538000000.0,
"basePrice": 60000000000.0,
"capacity": 13750.0,
"certificateTemplate": 247,
"description_de": "„Wir sind also die Neunte Megacorporation. Wir sind es schon lange Zeit und wir sind ein wichtiger Teil der ganzen Masche. Der Staat der Caldari wurde damals schal und unflexibel, mit all diesem „Heiian“-Bockmist. Glaubt irgendwer, dass sich die Großen Acht auch nur einen Fedofurz darum scheren? Das ist Holoreel-Mindflood. Nur für die Trottel. Die Proleten, die nur Ausreden suchen, den Großen Mega-Daddys nicht im Wege zu stehen. Und die Kapselpiloten, die „Ehre sei dem Staat“ kleffen. Sie sind die allerschlimmsten. All diese Macht und sie verbeugen sich vor Mami Staat und Daddy Mega. Das macht mich krank.\n\n\n\n„Was? Achso, die Neunte Mega. Was ich meine? Also, die Großen Acht, ja? Sie teilen den Staat unter sich auf. Sie stecken die Köpfe zusammen, bestimmen wer was kriegt, und geben dem Ganzen einen netten Anstrich mit ein bisschen Wettbewerb hier und ein wenig Krieg da. Das ist alles nur Theater. Sie sind vertikal integrierte Megacorporations, die komplette Sektoren der Wirtschaft beherrschen, weil sie sich alle an denselben Plan halten. Wir aber sind der Anti-Plan. Wir sind die neunte Mega, weil sich schließlich ja auch jemand um die Kriminalität kümmern muss, oder? Das sind wir: die Megacorporation des Verbrechens!“ \n\n\n\n Korako „The Rabbit“ Kosakami, Interview mit Ret Gloriaxx von der Sendung „Scopes Galaktische Stunde“",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -697,6 +697,10 @@
{
"level": 3,
"typeID": 90727
},
{
"level": 2,
"typeID": 91017
}
]
},
@@ -1398,6 +1402,10 @@
{
"level": 3,
"typeID": 90727
},
{
"level": 2,
"typeID": 91017
}
]
},
@@ -2099,6 +2107,10 @@
{
"level": 3,
"typeID": 90727
},
{
"level": 2,
"typeID": 91017
}
]
},
@@ -2800,6 +2812,10 @@
{
"level": 3,
"typeID": 90727
},
{
"level": 2,
"typeID": 91017
}
]
}

View File

@@ -70,7 +70,7 @@
"displayNameID": 517442,
"itemModifiers": [
{
"dogmaAttributeID": 37
"dogmaAttributeID": 3171
}
],
"locationGroupModifiers": [],
@@ -5261,5 +5261,135 @@
],
"operationName": "PostPercent",
"showOutputValueInUI": "ShowNormal"
},
"2518": {
"aggregateMode": "Minimum",
"developerDescription": "Sisters of EVE Metaliminal Storm Bonus 5 - Winter Nexus Storm",
"displayName_de": "Thermalresistenz-Bonus (unbeständiger Eissturm)",
"displayName_en-us": "Thermal Resistance Bonus (Volatile Ice Storm)",
"displayName_es": "Bonificación de resistencia térmica (tormenta de hielo volátil)",
"displayName_fr": "Bonus de résistance thermique (tempête de glace volatile)",
"displayName_it": "Thermal Resistance Bonus (Volatile Ice Storm)",
"displayName_ja": "サーマルレジスタンスボーナス(揮発性アイスストーム)",
"displayName_ko": "열 저항력 보너스(불안정한 얼음 폭풍)",
"displayName_ru": "Бонус к сопротивляемости термальному урону (нестабильная ледяная буря)",
"displayName_zh": "热能抗性加成(不稳定冰风暴)",
"displayNameID": 1030721,
"itemModifiers": [
{
"dogmaAttributeID": 110
},
{
"dogmaAttributeID": 270
},
{
"dogmaAttributeID": 274
}
],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PostPercent",
"showOutputValueInUI": "ShowInverted"
},
"2519": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 21",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2520": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 22",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2521": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 23",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2522": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 24",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2523": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 25",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2524": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 27",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2525": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 28",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2526": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 26",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2527": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 29",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
},
"2528": {
"aggregateMode": "Maximum",
"developerDescription": "Sisters of EVE Landmark Bonus 30",
"itemModifiers": [],
"locationGroupModifiers": [],
"locationModifiers": [],
"locationRequiredSkillModifiers": [],
"operationName": "PreAssignment",
"showOutputValueInUI": "ShowNormal"
}
}

View File

@@ -1,10 +1,10 @@
[
{
"field_name": "client_build",
"field_value": 3103065
"field_value": 3137986
},
{
"field_name": "dump_time",
"field_value": 1763465723
"field_value": 1765458681
}
]

File diff suppressed because it is too large Load Diff

40
uv.lock generated Normal file
View File

@@ -0,0 +1,40 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "pyfa"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "ruff", specifier = ">=0.14.8" }]
[[package]]
name = "ruff"
version = "0.14.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" },
{ url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" },
{ url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" },
{ url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" },
{ url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" },
{ url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" },
{ url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" },
{ url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" },
{ url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" },
{ url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" },
{ url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" },
{ url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" },
{ url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" },
{ url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" },
{ url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" },
{ url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" },
{ url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" },
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
]

View File

@@ -1 +1 @@
version: v2.65.0
version: v2.65.2