Compare commits
69 Commits
v2.64.1
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
| f03ffa85d8 | |||
| 8d6ae56f33 | |||
| 64e339fb46 | |||
| 29ee808337 | |||
| 72d65e6118 | |||
| 135fdd8812 | |||
| 6bb0938be0 | |||
| 8a37ee810a | |||
| 4d1320161a | |||
| b5d6211ae0 | |||
| fa05cd625f | |||
| d18ebb6dc0 | |||
| f3a89157ca | |||
| 766d45dd17 | |||
| 457bbc0dc3 | |||
| 4ddf1733e4 | |||
| ca2a80cc85 | |||
|
|
c7074f499f | ||
|
|
f6f3a69be4 | ||
|
|
23e09729f7 | ||
|
|
0aca05704f | ||
|
|
b08894e984 | ||
|
|
a1bc8742c9 | ||
|
|
6472cabc05 | ||
|
|
56bb8217d3 | ||
|
|
17f9071317 | ||
|
|
50eda1f4db | ||
|
|
84fbc0a46c | ||
|
|
9551195078 | ||
|
|
f01949d892 | ||
|
|
26b4c05b6f | ||
|
|
6ecab03fd8 | ||
|
|
edc0418d9a | ||
|
|
1d413595b9 | ||
|
|
ce5a593f7b | ||
|
|
faea6a97f0 | ||
|
|
b92913cbf9 | ||
|
|
1af2e7f94b | ||
|
|
dbb61a8a37 | ||
|
|
b12adcae3d | ||
|
|
72567a7155 | ||
|
|
0d4c2551c1 | ||
|
|
ce10aeb55e | ||
|
|
7b14266f0d | ||
|
|
b436f6ec89 | ||
|
|
e7e3f4e626 | ||
|
|
ae8405d132 | ||
|
|
977cf61ff5 | ||
|
|
7cc4f6ec27 | ||
|
|
0e6b9b48f1 | ||
|
|
a00a80b4e4 | ||
|
|
6843373283 | ||
|
|
408da2e344 | ||
|
|
dc8ecae0f1 | ||
|
|
50e6ed516f | ||
|
|
42a6bb92a7 | ||
|
|
521a58d77b | ||
|
|
6546f32971 | ||
|
|
ad3019debe | ||
|
|
fe9fa8b4fe | ||
|
|
92fce5be96 | ||
|
|
d271515060 | ||
|
|
343e52e556 | ||
|
|
7949bc60dc | ||
|
|
bfbd3526cd | ||
|
|
c363f7359d | ||
|
|
3a7ece6a5b | ||
|
|
e0e1c1ce03 | ||
|
|
edec81f4b8 |
@@ -32,7 +32,7 @@ for:
|
|||||||
- sh: export PYFA_VERSION="$(python3 -B scripts/dump_version.py)"
|
- sh: export PYFA_VERSION="$(python3 -B scripts/dump_version.py)"
|
||||||
- sh: mkdir build
|
- sh: mkdir build
|
||||||
# Download packaging tool
|
# 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
|
- sh: chmod +x $APPIMAGE_TOOL
|
||||||
build_script:
|
build_script:
|
||||||
- sh: mkdir -p AppDir/opt/pyfa
|
- sh: mkdir -p AppDir/opt/pyfa
|
||||||
|
|||||||
2
.gitattributes
vendored
@@ -33,4 +33,4 @@ pyfa.py text eol=lf
|
|||||||
*.jpg binary
|
*.jpg binary
|
||||||
*.icns binary
|
*.icns binary
|
||||||
*.ico binary
|
*.ico binary
|
||||||
|
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -126,4 +126,5 @@ gitversion
|
|||||||
/locale/progress.json
|
/locale/progress.json
|
||||||
|
|
||||||
# vscode settings
|
# vscode settings
|
||||||
.vscode
|
.vscode
|
||||||
|
eve.db
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Pyfa-Mod"]
|
||||||
|
path = Pyfa-Mod
|
||||||
|
url = https://github.com/Eivonz/Pyfa-Mod
|
||||||
1
Pyfa-Mod
Submodule
30
build.sh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
cp oleacc* dist/pyfa/
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Build complete! Binary is located at: dist/pyfa/pyfa.exe"
|
||||||
|
echo "You can run it with: dist/pyfa/pyfa.exe"
|
||||||
1206
eos/effects.py
@@ -324,7 +324,7 @@ class ModifiedAttributeDict(MutableMapping):
|
|||||||
cappingAttrKeyCache[key] = cappingKey
|
cappingAttrKeyCache[key] = cappingKey
|
||||||
|
|
||||||
if cappingKey:
|
if cappingKey:
|
||||||
cappingValue = self.original.get(cappingKey, self.__calculateValue(cappingKey))
|
cappingValue = self[cappingKey]
|
||||||
cappingValue = cappingValue.value if hasattr(cappingValue, "value") else cappingValue
|
cappingValue = cappingValue.value if hasattr(cappingValue, "value") else cappingValue
|
||||||
else:
|
else:
|
||||||
cappingValue = None
|
cappingValue = None
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
|||||||
self.__baseVolley = None
|
self.__baseVolley = None
|
||||||
self.__baseRRAmount = None
|
self.__baseRRAmount = None
|
||||||
self.__miningYield = None
|
self.__miningYield = None
|
||||||
self.__miningWaste = None
|
self.__miningDrain = None
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self._item.attributes
|
self.__itemModifiedAttributes.original = self._item.attributes
|
||||||
@@ -240,15 +240,15 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
|||||||
if not ignoreState and self.amountActive <= 0:
|
if not ignoreState and self.amountActive <= 0:
|
||||||
return 0
|
return 0
|
||||||
if self.__miningYield is None:
|
if self.__miningYield is None:
|
||||||
self.__miningYield, self.__miningWaste = self.__calculateMining()
|
self.__miningYield, self.__miningDrain = self.__calculateMining()
|
||||||
return self.__miningYield
|
return self.__miningYield
|
||||||
|
|
||||||
def getMiningWPS(self, ignoreState=False):
|
def getMiningDPS(self, ignoreState=False):
|
||||||
if not ignoreState and self.amountActive <= 0:
|
if not ignoreState and self.amountActive <= 0:
|
||||||
return 0
|
return 0
|
||||||
if self.__miningWaste is None:
|
if self.__miningDrain is None:
|
||||||
self.__miningYield, self.__miningWaste = self.__calculateMining()
|
self.__miningYield, self.__miningDrain = self.__calculateMining()
|
||||||
return self.__miningWaste
|
return self.__miningDrain
|
||||||
|
|
||||||
def __calculateMining(self):
|
def __calculateMining(self):
|
||||||
if self.mines is True:
|
if self.mines is True:
|
||||||
@@ -262,8 +262,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
|||||||
yps = yield_ / (cycleTime / 1000.0)
|
yps = yield_ / (cycleTime / 1000.0)
|
||||||
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
|
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
|
||||||
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
|
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
|
||||||
wps = yps * max(0, min(1, wasteChance / 100)) * wasteMult
|
dps = yps * (1 + max(0, min(1, wasteChance / 100)) * wasteMult)
|
||||||
return yps, wps
|
return yps, dps
|
||||||
else:
|
else:
|
||||||
return 0, 0
|
return 0, 0
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
|
|||||||
self.__baseVolley = None
|
self.__baseVolley = None
|
||||||
self.__baseRRAmount = None
|
self.__baseRRAmount = None
|
||||||
self.__miningYield = None
|
self.__miningYield = None
|
||||||
self.__miningWaste = None
|
self.__miningDrain = None
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.itemModifiedAttributes.clear()
|
self.itemModifiedAttributes.clear()
|
||||||
self.chargeModifiedAttributes.clear()
|
self.chargeModifiedAttributes.clear()
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ class Fit:
|
|||||||
self.__remoteRepMap = {}
|
self.__remoteRepMap = {}
|
||||||
self.__minerYield = None
|
self.__minerYield = None
|
||||||
self.__droneYield = None
|
self.__droneYield = None
|
||||||
self.__minerWaste = None
|
self.__minerDrain = None
|
||||||
self.__droneWaste = None
|
self.__droneDrain = None
|
||||||
self.__droneDps = None
|
self.__droneDps = None
|
||||||
self.__droneVolley = None
|
self.__droneVolley = None
|
||||||
self.__sustainableTank = None
|
self.__sustainableTank = None
|
||||||
@@ -378,11 +378,11 @@ class Fit:
|
|||||||
return self.__minerYield
|
return self.__minerYield
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def minerWaste(self):
|
def minerDrain(self):
|
||||||
if self.__minerWaste is None:
|
if self.__minerDrain is None:
|
||||||
self.calculatemining()
|
self.calculatemining()
|
||||||
|
|
||||||
return self.__minerWaste
|
return self.__minerDrain
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def droneYield(self):
|
def droneYield(self):
|
||||||
@@ -392,19 +392,19 @@ class Fit:
|
|||||||
return self.__droneYield
|
return self.__droneYield
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def droneWaste(self):
|
def droneDrain(self):
|
||||||
if self.__droneWaste is None:
|
if self.__droneDrain is None:
|
||||||
self.calculatemining()
|
self.calculatemining()
|
||||||
|
|
||||||
return self.__droneWaste
|
return self.__droneDrain
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def totalYield(self):
|
def totalYield(self):
|
||||||
return self.droneYield + self.minerYield
|
return self.droneYield + self.minerYield
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def totalWaste(self):
|
def totalDrain(self):
|
||||||
return self.droneWaste + self.minerWaste
|
return self.droneDrain + self.minerDrain
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maxTargets(self):
|
def maxTargets(self):
|
||||||
@@ -518,8 +518,8 @@ class Fit:
|
|||||||
self.__remoteRepMap = {}
|
self.__remoteRepMap = {}
|
||||||
self.__minerYield = None
|
self.__minerYield = None
|
||||||
self.__droneYield = None
|
self.__droneYield = None
|
||||||
self.__minerWaste = None
|
self.__minerDrain = None
|
||||||
self.__droneWaste = None
|
self.__droneDrain = None
|
||||||
self.__effectiveSustainableTank = None
|
self.__effectiveSustainableTank = None
|
||||||
self.__sustainableTank = None
|
self.__sustainableTank = None
|
||||||
self.__droneDps = None
|
self.__droneDps = None
|
||||||
@@ -702,15 +702,12 @@ class Fit:
|
|||||||
mod.item.requiresSkill("High Speed Maneuvering"),
|
mod.item.requiresSkill("High Speed Maneuvering"),
|
||||||
"speedFactor", value, stackingPenalties=True)
|
"speedFactor", value, stackingPenalties=True)
|
||||||
|
|
||||||
if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
|
if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining Range
|
||||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
|
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
|
||||||
mod.item.requiresSkill("Ice Harvesting") or
|
mod.item.requiresSkill("Ice Harvesting") or
|
||||||
mod.item.requiresSkill("Gas Cloud Harvesting"),
|
mod.item.requiresSkill("Gas Cloud Harvesting"),
|
||||||
"maxRange", value, stackingPenalties=True)
|
"maxRange", value, stackingPenalties=True)
|
||||||
|
|
||||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"),
|
|
||||||
"surveyScanRange", value, stackingPenalties=True)
|
|
||||||
|
|
||||||
if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
|
if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
|
||||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
|
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or
|
||||||
mod.item.requiresSkill("Ice Harvesting") or
|
mod.item.requiresSkill("Ice Harvesting") or
|
||||||
@@ -925,6 +922,36 @@ class Fit:
|
|||||||
lambda mod: (mod.item.requiresSkill("Repair Systems")
|
lambda mod: (mod.item.requiresSkill("Repair Systems")
|
||||||
or mod.item.requiresSkill("Capital Repair Systems")),
|
or mod.item.requiresSkill("Capital Repair Systems")),
|
||||||
"armorDamageAmount", value, stackingPenalties=True)
|
"armorDamageAmount", value, stackingPenalties=True)
|
||||||
|
if warfareBuffID == 2464: # Expedition Burst: Probe Strength
|
||||||
|
self.modules.filteredChargeBoost(
|
||||||
|
lambda mod: mod.charge.requiresSkill('Astrometrics'),
|
||||||
|
'baseSensorStrength', value, stackingPenalties=True)
|
||||||
|
if warfareBuffID == 2465: # Expedition Burst: Directional Scanner, Hacking and Salvager Range
|
||||||
|
self.ship.boostItemAttr("maxDirectionalScanRange", value)
|
||||||
|
self.modules.filteredItemBoost(
|
||||||
|
lambda mod: mod.item.group.name in ("Data Miners", "Salvager"), "maxRange", value, stackingPenalties=True)
|
||||||
|
if warfareBuffID == 2466: # Expedition Burst: Maximum Scan Deviation Modifier
|
||||||
|
self.modules.filteredChargeBoost(
|
||||||
|
lambda mod: mod.charge.requiresSkill('Astrometrics'),
|
||||||
|
'baseMaxScanDeviation', value, stackingPenalties=True)
|
||||||
|
if warfareBuffID == 2468: # Expedition Burst: Virus Coherence
|
||||||
|
self.modules.filteredItemIncrease(
|
||||||
|
lambda mod: mod.item.group.name == "Data Miners", "virusCoherence", value)
|
||||||
|
if warfareBuffID == 2474: # Mining burst charges
|
||||||
|
self.ship.forceItemAttr("miningScannerUpgrade", value)
|
||||||
|
if warfareBuffID == 2481: # Expedition Burst: Salvager duration bonus
|
||||||
|
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Salvaging"), "duration", value)
|
||||||
|
if warfareBuffID == 2516: # Mining Burst: Mining Crit Chance
|
||||||
|
self.modules.filteredItemBoost(
|
||||||
|
lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill("Ice Harvesting"),
|
||||||
|
"miningCritChance", value)
|
||||||
|
if warfareBuffID == 2517: # Mining Burst: Mining Residue Chance Reduction
|
||||||
|
self.modules.filteredItemBoost(
|
||||||
|
lambda mod: (
|
||||||
|
mod.item.requiresSkill("Mining")
|
||||||
|
or mod.item.requiresSkill("Ice Harvesting")
|
||||||
|
or mod.item.requiresSkill("Gas Cloud Harvesting")),
|
||||||
|
"miningWasteProbability", value, stackingPenalties=True)
|
||||||
|
|
||||||
del self.commandBonuses[warfareBuffID]
|
del self.commandBonuses[warfareBuffID]
|
||||||
|
|
||||||
@@ -1707,21 +1734,21 @@ class Fit:
|
|||||||
|
|
||||||
def calculatemining(self):
|
def calculatemining(self):
|
||||||
minerYield = 0
|
minerYield = 0
|
||||||
minerWaste = 0
|
minerDrain = 0
|
||||||
droneYield = 0
|
droneYield = 0
|
||||||
droneWaste = 0
|
droneDrain = 0
|
||||||
|
|
||||||
for mod in self.modules:
|
for mod in self.modules:
|
||||||
minerYield += mod.getMiningYPS()
|
minerYield += mod.getMiningYPS()
|
||||||
minerWaste += mod.getMiningWPS()
|
minerDrain += mod.getMiningDPS()
|
||||||
for drone in self.drones:
|
for drone in self.drones:
|
||||||
droneYield += drone.getMiningYPS()
|
droneYield += drone.getMiningYPS()
|
||||||
droneWaste += drone.getMiningWPS()
|
droneDrain += drone.getMiningDPS()
|
||||||
|
|
||||||
self.__minerYield = minerYield
|
self.__minerYield = minerYield
|
||||||
self.__minerWaste = minerWaste
|
self.__minerDrain = minerDrain
|
||||||
self.__droneYield = droneYield
|
self.__droneYield = droneYield
|
||||||
self.__droneWaste = droneWaste
|
self.__droneDrain = droneDrain
|
||||||
|
|
||||||
def calculateWeaponDmgStats(self, spoolOptions):
|
def calculateWeaponDmgStats(self, spoolOptions):
|
||||||
weaponVolley = DmgTypes.default()
|
weaponVolley = DmgTypes.default()
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
|||||||
self.__baseVolley = None
|
self.__baseVolley = None
|
||||||
self.__baseRRAmount = None
|
self.__baseRRAmount = None
|
||||||
self.__miningYield = None
|
self.__miningYield = None
|
||||||
self.__miningWaste = None
|
self.__miningDrain = None
|
||||||
self.__reloadTime = None
|
self.__reloadTime = None
|
||||||
self.__reloadForce = None
|
self.__reloadForce = None
|
||||||
self.__chargeCycles = None
|
self.__chargeCycles = None
|
||||||
@@ -418,17 +418,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
|||||||
if not ignoreState and self.state < FittingModuleState.ACTIVE:
|
if not ignoreState and self.state < FittingModuleState.ACTIVE:
|
||||||
return 0
|
return 0
|
||||||
if self.__miningYield is None:
|
if self.__miningYield is None:
|
||||||
self.__miningYield, self.__miningWaste = self.__calculateMining()
|
self.__miningYield, self.__miningDrain = self.__calculateMining()
|
||||||
return self.__miningYield
|
return self.__miningYield
|
||||||
|
|
||||||
def getMiningWPS(self, ignoreState=False):
|
def getMiningDPS(self, ignoreState=False):
|
||||||
if self.isEmpty:
|
if self.isEmpty:
|
||||||
return 0
|
return 0
|
||||||
if not ignoreState and self.state < FittingModuleState.ACTIVE:
|
if not ignoreState and self.state < FittingModuleState.ACTIVE:
|
||||||
return 0
|
return 0
|
||||||
if self.__miningWaste is None:
|
if self.__miningDrain is None:
|
||||||
self.__miningYield, self.__miningWaste = self.__calculateMining()
|
self.__miningYield, self.__miningDrain = self.__calculateMining()
|
||||||
return self.__miningWaste
|
return self.__miningDrain
|
||||||
|
|
||||||
def __calculateMining(self):
|
def __calculateMining(self):
|
||||||
yield_ = self.getModifiedItemAttr("miningAmount")
|
yield_ = self.getModifiedItemAttr("miningAmount")
|
||||||
@@ -443,8 +443,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
|||||||
yps = 0
|
yps = 0
|
||||||
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
|
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
|
||||||
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
|
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
|
||||||
wps = yps * max(0, min(1, wasteChance / 100)) * wasteMult
|
dps = yps * (1 + max(0, min(1, wasteChance / 100)) * wasteMult)
|
||||||
return yps, wps
|
critChance = self.getModifiedItemAttr("miningCritChance")
|
||||||
|
critBonusMult = self.getModifiedItemAttr("miningCritBonusYield")
|
||||||
|
yps += yps * critChance * critBonusMult
|
||||||
|
return yps, dps
|
||||||
|
|
||||||
def isDealingDamage(self, ignoreState=False):
|
def isDealingDamage(self, ignoreState=False):
|
||||||
volleyParams = self.getVolleyParameters(ignoreState=ignoreState)
|
volleyParams = self.getVolleyParameters(ignoreState=ignoreState)
|
||||||
@@ -894,7 +897,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
|
|||||||
self.__baseVolley = None
|
self.__baseVolley = None
|
||||||
self.__baseRRAmount = None
|
self.__baseRRAmount = None
|
||||||
self.__miningYield = None
|
self.__miningYield = None
|
||||||
self.__miningWaste = None
|
self.__miningDrain = None
|
||||||
self.__reloadTime = None
|
self.__reloadTime = None
|
||||||
self.__reloadForce = None
|
self.__reloadForce = None
|
||||||
self.__chargeCycles = None
|
self.__chargeCycles = None
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
valid Item objects, not the Mode objects. Returns None if not a
|
valid Item objects, not the Mode objects. Returns None if not a
|
||||||
t3 dessy
|
t3 dessy
|
||||||
"""
|
"""
|
||||||
if self.item.group.name != "Tactical Destroyer":
|
if self.item.group.name != "Tactical Destroyer" and self.item.name != "Anhinga":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ class AddCurrentlyOpenFit(ContextMenuUnconditional):
|
|||||||
if isinstance(page, BlankPage):
|
if isinstance(page, BlankPage):
|
||||||
continue
|
continue
|
||||||
fit = sFit.getFit(page.activeFitID, basic=True)
|
fit = sFit.getFit(page.activeFitID, basic=True)
|
||||||
|
if fit is None:
|
||||||
|
continue
|
||||||
id = ContextMenuUnconditional.nextID()
|
id = ContextMenuUnconditional.nextID()
|
||||||
mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name))
|
mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name))
|
||||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
|||||||
('r16', _t('Moon Uncommon')),
|
('r16', _t('Moon Uncommon')),
|
||||||
('r32', _t('Moon Rare')),
|
('r32', _t('Moon Rare')),
|
||||||
('r64', _t('Moon Exceptional')),
|
('r64', _t('Moon Exceptional')),
|
||||||
|
('err', _t('Erratic')),
|
||||||
('misc', _t('Misc'))])
|
('misc', _t('Misc'))])
|
||||||
|
|
||||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class ChangeShipTacticalMode(ContextMenuUnconditional):
|
|||||||
self.modeMap = {
|
self.modeMap = {
|
||||||
'Defense': _t('Defense'),
|
'Defense': _t('Defense'),
|
||||||
'Propulsion': _t('Propulsion'),
|
'Propulsion': _t('Propulsion'),
|
||||||
'Sharpshooter': _t('Sharpshooter')
|
'Sharpshooter': _t('Sharpshooter'),
|
||||||
|
'Primary': _t('Primary'),
|
||||||
|
'Secondary': _t('Secondary'),
|
||||||
|
'Tertiary': _t('Tertiary'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def display(self, callingWindow, srcContext):
|
def display(self, callingWindow, srcContext):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
|||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from config import slotColourMap, slotColourMapDark
|
from config import slotColourMap, slotColourMapDark
|
||||||
from eos.saveddata.module import Module
|
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.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
|
||||||
from gui.contextMenu import ContextMenu
|
from gui.contextMenu import ContextMenu
|
||||||
from gui.display import Display
|
from gui.display import Display
|
||||||
@@ -153,19 +154,22 @@ class ItemView(Display):
|
|||||||
# skip the event so the other handlers also get called
|
# skip the event so the other handlers also get called
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
if self.marketBrowser.mode != 'charges':
|
|
||||||
return
|
|
||||||
|
|
||||||
activeFitID = self.mainFrame.getActiveFit()
|
activeFitID = self.mainFrame.getActiveFit()
|
||||||
# if it was not the active fitting that was changed, do not do anything
|
# 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:
|
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||||
return
|
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
|
# If "Fits" filter is active, re-filter the current view
|
||||||
self.updateItemStore(items)
|
if self.marketBrowser.getFitsFilter():
|
||||||
self.filterItemStore()
|
self.filterItemStore()
|
||||||
|
|
||||||
def updateItemStore(self, items):
|
def updateItemStore(self, items):
|
||||||
self.unfilteredStore = items
|
self.unfilteredStore = items
|
||||||
@@ -197,13 +201,115 @@ class ItemView(Display):
|
|||||||
if btn.userSelected:
|
if btn.userSelected:
|
||||||
selectedMetas.update(sMkt.META_MAP[btn.metaName])
|
selectedMetas.update(sMkt.META_MAP[btn.metaName])
|
||||||
filteredItems = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas)
|
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 and btn.IsEnabled():
|
||||||
|
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
|
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):
|
def setToggles(self):
|
||||||
metaIDs = set()
|
metaIDs = set()
|
||||||
|
slotIDs = set()
|
||||||
sMkt = self.sMkt
|
sMkt = self.sMkt
|
||||||
for item in self.unfilteredStore:
|
for item in self.unfilteredStore:
|
||||||
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
|
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
|
||||||
|
slot = Module.calculateSlot(item)
|
||||||
|
if slot is not None:
|
||||||
|
slotIDs.add(slot)
|
||||||
|
|
||||||
for btn in self.marketBrowser.metaButtons:
|
for btn in self.marketBrowser.metaButtons:
|
||||||
btn.reset()
|
btn.reset()
|
||||||
@@ -212,6 +318,23 @@ class ItemView(Display):
|
|||||||
btn.setMetaAvailable(True)
|
btn.setMetaAvailable(True)
|
||||||
else:
|
else:
|
||||||
btn.setMetaAvailable(False)
|
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()
|
||||||
|
isAvailable = fitId is not None
|
||||||
|
btn.setMetaAvailable(isAvailable)
|
||||||
|
if not isAvailable:
|
||||||
|
btn.setUserSelection(False)
|
||||||
|
elif btn.filterType == "slot":
|
||||||
|
# Slot button is available if items with that slot exist in current view
|
||||||
|
isAvailable = btn.slotType in slotIDs
|
||||||
|
btn.setMetaAvailable(isAvailable)
|
||||||
|
if not isAvailable:
|
||||||
|
btn.setUserSelection(False)
|
||||||
|
|
||||||
def scheduleSearch(self, event=None):
|
def scheduleSearch(self, event=None):
|
||||||
self.searchTimer.Stop() # Cancel any pending timers
|
self.searchTimer.Stop() # Cancel any pending timers
|
||||||
|
|||||||
@@ -130,9 +130,9 @@ class MiningYieldViewFull(StatsView):
|
|||||||
def refreshPanel(self, fit):
|
def refreshPanel(self, fit):
|
||||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
||||||
|
|
||||||
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, lambda: fit.minerWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
|
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, lambda: fit.minerDrain, 3, 0, 0, "{} m\u00B3/s", None),
|
||||||
("labelFullminingyieldDrone", lambda: fit.droneYield, lambda: fit.droneWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
|
("labelFullminingyieldDrone", lambda: fit.droneYield, lambda: fit.droneDrain, 3, 0, 0, "{} m\u00B3/s", None),
|
||||||
("labelFullminingyieldTotal", lambda: fit.totalYield, lambda: fit.totalWaste, 3, 0, 0, "{}{} m\u00B3/s", None))
|
("labelFullminingyieldTotal", lambda: fit.totalYield, lambda: fit.totalDrain, 3, 0, 0, "{} m\u00B3/s", None))
|
||||||
|
|
||||||
def processValue(value):
|
def processValue(value):
|
||||||
value = value() if fit is not None else 0
|
value = value() if fit is not None else 0
|
||||||
@@ -140,23 +140,26 @@ class MiningYieldViewFull(StatsView):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
for labelName, yieldValue, wasteValue, prec, lowest, highest, valueFormat, altFormat in stats:
|
for labelName, yieldValue, drainValue, prec, lowest, highest, valueFormat, altFormat in stats:
|
||||||
label = getattr(self, labelName)
|
label = getattr(self, labelName)
|
||||||
yieldValue = processValue(yieldValue)
|
yieldValue = processValue(yieldValue)
|
||||||
wasteValue = processValue(wasteValue)
|
drainValue = processValue(drainValue)
|
||||||
if self._cachedValues[counter] != (yieldValue, wasteValue):
|
if self._cachedValues[counter] != (yieldValue, drainValue):
|
||||||
|
try:
|
||||||
|
efficiency = '{}%'.format(formatAmount(yieldValue / drainValue * 100, 4, 0, 0))
|
||||||
|
except ZeroDivisionError:
|
||||||
|
efficiency = '0%'
|
||||||
yps = formatAmount(yieldValue, prec, lowest, highest)
|
yps = formatAmount(yieldValue, prec, lowest, highest)
|
||||||
yph = formatAmount(yieldValue * 3600, prec, lowest, highest)
|
yph = formatAmount(yieldValue * 3600, prec, lowest, highest)
|
||||||
wps = formatAmount(wasteValue, prec, lowest, highest)
|
dps = formatAmount(drainValue, prec, lowest, highest)
|
||||||
wph = formatAmount(wasteValue * 3600, prec, lowest, highest)
|
dph = formatAmount(drainValue * 3600, prec, lowest, highest)
|
||||||
wasteSuffix = '\u02b7' if wasteValue > 0 else ''
|
label.SetLabel(valueFormat.format(yps))
|
||||||
label.SetLabel(valueFormat.format(yps, wasteSuffix))
|
|
||||||
tipLines = []
|
tipLines = []
|
||||||
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(yps, yph))
|
tipLines.append("{} m\u00B3 yield per second ({} m\u00B3 per hour)".format(yps, yph))
|
||||||
if wasteValue > 0:
|
tipLines.append("{} m\u00B3 drain per second ({} m\u00B3 per hour)".format(dps, dph))
|
||||||
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(wps, wph))
|
tipLines.append(f'{efficiency} efficiency')
|
||||||
label.SetToolTip(wx.ToolTip('\n'.join(tipLines)))
|
label.SetToolTip(wx.ToolTip('\n'.join(tipLines)))
|
||||||
self._cachedValues[counter] = (yieldValue, wasteValue)
|
self._cachedValues[counter] = (yieldValue, drainValue)
|
||||||
counter += 1
|
counter += 1
|
||||||
self.panel.Layout()
|
self.panel.Layout()
|
||||||
self.headerPanel.Layout()
|
self.headerPanel.Layout()
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ class TargetingMiscViewMinimal(StatsView):
|
|||||||
("specialPlanetaryCommoditiesHoldCapacity", _t("Planetary goods hold")),
|
("specialPlanetaryCommoditiesHoldCapacity", _t("Planetary goods hold")),
|
||||||
("specialQuafeHoldCapacity", _t("Quafe hold")),
|
("specialQuafeHoldCapacity", _t("Quafe hold")),
|
||||||
("specialMobileDepotHoldCapacity", _t("Mobile depot hold")),
|
("specialMobileDepotHoldCapacity", _t("Mobile depot hold")),
|
||||||
|
("specialExpeditionHoldCapacity", _t("Expedition hold")),
|
||||||
))
|
))
|
||||||
|
|
||||||
cargoValues = {
|
cargoValues = {
|
||||||
@@ -154,6 +155,7 @@ class TargetingMiscViewMinimal(StatsView):
|
|||||||
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
|
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
|
||||||
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity"),
|
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity"),
|
||||||
"specialMobileDepotHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMobileDepotHoldCapacity"),
|
"specialMobileDepotHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMobileDepotHoldCapacity"),
|
||||||
|
"specialExpeditionHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialExpeditionHoldCapacity"),
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),
|
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from gui.viewColumn import ViewColumn
|
|||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount
|
||||||
from gui.utils.listFormatter import formatList
|
from gui.utils.listFormatter import formatList
|
||||||
|
from eos.utils.float import floatUnerr
|
||||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||||
import eos.config
|
import eos.config
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ class Miscellanea(ViewColumn):
|
|||||||
tooltip = "Warp core strength modification"
|
tooltip = "Warp core strength modification"
|
||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif (
|
elif (
|
||||||
itemGroup in ("Stasis Web", "Stasis Webifying Drone", "Structure Stasis Webifier") or
|
itemGroup in ("Stasis Web", "Stasis Grappler", "Stasis Webifying Drone", "Structure Stasis Webifier") or
|
||||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEWeb" in item.effects)
|
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEWeb" in item.effects)
|
||||||
):
|
):
|
||||||
speedFactor = stuff.getModifiedItemAttr("speedFactor")
|
speedFactor = stuff.getModifiedItemAttr("speedFactor")
|
||||||
@@ -291,7 +292,7 @@ class Miscellanea(ViewColumn):
|
|||||||
"Gyrostabilizer",
|
"Gyrostabilizer",
|
||||||
"Magnetic Field Stabilizer",
|
"Magnetic Field Stabilizer",
|
||||||
"Heat Sink",
|
"Heat Sink",
|
||||||
"Ballistic Control system",
|
"Ballistic Control System",
|
||||||
"Structure Weapon Upgrade",
|
"Structure Weapon Upgrade",
|
||||||
"Entropic Radiation Sink",
|
"Entropic Radiation Sink",
|
||||||
"Vorton Projector Upgrade"
|
"Vorton Projector Upgrade"
|
||||||
@@ -300,7 +301,7 @@ class Miscellanea(ViewColumn):
|
|||||||
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
|
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
|
||||||
"Magnetic Field Stabilizer": ("damageMultiplier", "speedMultiplier", "Hybrid weapon"),
|
"Magnetic Field Stabilizer": ("damageMultiplier", "speedMultiplier", "Hybrid weapon"),
|
||||||
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
|
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
|
||||||
"Ballistic Control system": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
"Ballistic Control System": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||||
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||||
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon"),
|
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon"),
|
||||||
"Vorton Projector Upgrade": ("damageMultiplier", "speedMultiplier", "Vorton projector")}
|
"Vorton Projector Upgrade": ("damageMultiplier", "speedMultiplier", "Vorton projector")}
|
||||||
@@ -547,18 +548,24 @@ class Miscellanea(ViewColumn):
|
|||||||
if not yps:
|
if not yps:
|
||||||
return "", None
|
return "", None
|
||||||
yph = yps * 3600
|
yph = yps * 3600
|
||||||
wps = stuff.getMiningWPS(ignoreState=True)
|
dps = stuff.getMiningDPS(ignoreState=True)
|
||||||
wph = wps * 3600
|
dph = dps * 3600
|
||||||
|
try:
|
||||||
|
efficiency = yps / dps
|
||||||
|
except ZeroDivisionError:
|
||||||
|
efficiency = 0
|
||||||
textParts = []
|
textParts = []
|
||||||
textParts.append(formatAmount(yps, 3, 0, 3))
|
|
||||||
tipLines = []
|
tipLines = []
|
||||||
|
textParts.append('{} m\u00B3/s'.format(formatAmount(yps, 3, 0, 3)))
|
||||||
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(
|
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(
|
||||||
formatAmount(yps, 3, 0, 3), formatAmount(yph, 3, 0, 3)))
|
formatAmount(yps, 3, 0, 3), formatAmount(yph, 3, 0, 3)))
|
||||||
if wps > 0:
|
tipLines.append("{} m\u00B3 mining drain per second ({} m\u00B3 per hour)".format(
|
||||||
textParts.append(formatAmount(wps, 3, 0, 3))
|
formatAmount(dps, 3, 0, 3), formatAmount(dph, 3, 0, 3)))
|
||||||
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(
|
if floatUnerr(efficiency) != 1:
|
||||||
formatAmount(wps, 3, 0, 3), formatAmount(wph, 3, 0, 3)))
|
eff_text = '{}%'.format(formatAmount(efficiency * 100, 4, 0, 0))
|
||||||
text = '{} m\u00B3/s'.format('+'.join(textParts))
|
textParts.append(eff_text)
|
||||||
|
tipLines.append(f"{eff_text} mining efficiency")
|
||||||
|
text = '{}'.format(' | '.join(textParts))
|
||||||
tooltip = '\n'.join(tipLines)
|
tooltip = '\n'.join(tipLines)
|
||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif itemGroup == "Logistic Drone":
|
elif itemGroup == "Logistic Drone":
|
||||||
@@ -701,7 +708,7 @@ class Miscellanea(ViewColumn):
|
|||||||
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
|
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
|
||||||
)
|
)
|
||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif itemGroup in ("Cargo Scanner", "Ship Scanner", "Survey Scanner"):
|
elif itemGroup in ("Cargo Scanner", "Ship Scanner"):
|
||||||
duration = stuff.getModifiedItemAttr("duration")
|
duration = stuff.getModifiedItemAttr("duration")
|
||||||
if not duration:
|
if not duration:
|
||||||
return "", None
|
return "", None
|
||||||
@@ -766,15 +773,36 @@ class Miscellanea(ViewColumn):
|
|||||||
elif buffId == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
|
elif buffId == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
|
||||||
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
tooltipSections.append("AB/MWD speed increase")
|
tooltipSections.append("AB/MWD speed increase")
|
||||||
elif buffId == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
|
elif buffId == 23: # Mining Burst: Mining Laser Field Enhancement: Mining Range
|
||||||
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
tooltipSections.append("mining/survey module range")
|
tooltipSections.append("mining module range")
|
||||||
elif buffId == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
|
elif buffId == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
|
||||||
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
tooltipSections.append("mining module duration & capacitor use")
|
tooltipSections.append("mining module duration & capacitor use")
|
||||||
elif buffId == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
|
elif buffId == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
|
||||||
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
tooltipSections.append("mining crystal volatility")
|
tooltipSections.append("mining crystal volatility")
|
||||||
|
elif buffId == 2464: # Expedition Burst: Probe Strength
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("scan probe strength")
|
||||||
|
elif buffId == 2465: # Expedition Burst: Directional Scanner, Hacking and Salvager Range
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("dscan, hacking & salvaging range")
|
||||||
|
elif buffId == 2466: # Expedition Burst: Maximum Scan Deviation Modifier
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("scan probe deviation")
|
||||||
|
elif buffId == 2468: # Expedition Burst: Virus Coherence
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}")
|
||||||
|
tooltipSections.append("virus coherence")
|
||||||
|
elif buffId == 2481: # Expedition Burst: Salvager duration bonus
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("salvager cycle time")
|
||||||
|
elif buffId == 2516: # Mining Burst: Mining Crit Chance
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("crit chance")
|
||||||
|
elif buffId == 2517: # Mining Burst: Mining Residue Chance Reduction
|
||||||
|
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
|
||||||
|
tooltipSections.append("waste chance")
|
||||||
if not textSections:
|
if not textSections:
|
||||||
return '', None
|
return '', None
|
||||||
text = ' | '.join(textSections)
|
text = ' | '.join(textSections)
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ class CharacterSelection(wx.Panel):
|
|||||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills"))
|
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills"))
|
||||||
self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
|
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)"))
|
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (EVEMon)"))
|
||||||
self.Bind(wx.EVT_MENU, self.exportSkillsEveMon, exportItem)
|
self.Bind(wx.EVT_MENU, self.exportSkillsEveMon, exportItem)
|
||||||
|
|
||||||
@@ -268,6 +271,15 @@ class CharacterSelection(wx.Panel):
|
|||||||
|
|
||||||
toClipboard(list)
|
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):
|
def exportSkillsEveMon(self, evt):
|
||||||
skillsMap = self._buildSkillsTooltipCondensed(self.reqs, skillsMap={})
|
skillsMap = self._buildSkillsTooltipCondensed(self.reqs, skillsMap={})
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import itertools
|
||||||
import os.path
|
import os.path
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -60,8 +61,12 @@ from gui.shipBrowser import ShipBrowser
|
|||||||
from gui.statsPane import StatsPane
|
from gui.statsPane import StatsPane
|
||||||
from gui.targetProfileEditor import TargetProfileEditor
|
from gui.targetProfileEditor import TargetProfileEditor
|
||||||
from gui.updateDialog import UpdateDialog
|
from gui.updateDialog import UpdateDialog
|
||||||
from gui.utils.clipboard import fromClipboard
|
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||||
from gui.utils.progressHelper import ProgressHelper
|
from gui.utils.progressHelper import ProgressHelper
|
||||||
|
from eos.const import FittingSlot as es_Slot
|
||||||
|
from eos.saveddata.character import Skill
|
||||||
|
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||||
|
from eos.saveddata.module import Module as es_Module
|
||||||
from service.character import Character
|
from service.character import Character
|
||||||
from service.esi import Esi
|
from service.esi import Esi
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
@@ -522,6 +527,8 @@ class MainFrame(wx.Frame):
|
|||||||
self.Bind(wx.EVT_MENU, self.backupToXml, id=menuBar.backupFitsId)
|
self.Bind(wx.EVT_MENU, self.backupToXml, id=menuBar.backupFitsId)
|
||||||
# Export skills needed
|
# Export skills needed
|
||||||
self.Bind(wx.EVT_MENU, self.exportSkillsNeeded, id=menuBar.exportSkillsNeededId)
|
self.Bind(wx.EVT_MENU, self.exportSkillsNeeded, id=menuBar.exportSkillsNeededId)
|
||||||
|
# Copy skills needed
|
||||||
|
self.Bind(wx.EVT_MENU, self.copySkillsNeeded, id=menuBar.copySkillsNeededId)
|
||||||
# Import character
|
# Import character
|
||||||
self.Bind(wx.EVT_MENU, self.importCharacter, id=menuBar.importCharacterId)
|
self.Bind(wx.EVT_MENU, self.importCharacter, id=menuBar.importCharacterId)
|
||||||
# Export HTML
|
# Export HTML
|
||||||
@@ -832,6 +839,60 @@ class MainFrame(wx.Frame):
|
|||||||
self.waitDialog = wx.BusyInfo(_t("Exporting skills needed..."), parent=self)
|
self.waitDialog = wx.BusyInfo(_t("Exporting skills needed..."), parent=self)
|
||||||
sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog)
|
sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog)
|
||||||
|
|
||||||
|
def copySkillsNeeded(self, event):
|
||||||
|
""" Copies skills used by the fit that the character has to clipboard """
|
||||||
|
activeFitID = self.getActiveFit()
|
||||||
|
if activeFitID is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(activeFitID)
|
||||||
|
if fit is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not fit.calculated:
|
||||||
|
fit.calculate()
|
||||||
|
|
||||||
|
char = fit.character
|
||||||
|
skillsMap = {}
|
||||||
|
for thing in itertools.chain(fit.modules, fit.drones, fit.fighters, [fit.ship], fit.appliedImplants, fit.boosters, fit.cargo):
|
||||||
|
self._collectAffectingSkills(thing, char, skillsMap)
|
||||||
|
|
||||||
|
skillsList = ""
|
||||||
|
for skillName in sorted(skillsMap):
|
||||||
|
charLevel = skillsMap[skillName]
|
||||||
|
for level in range(1, charLevel + 1):
|
||||||
|
skillsList += "%s %d\n" % (skillName, level)
|
||||||
|
|
||||||
|
toClipboard(skillsList)
|
||||||
|
|
||||||
|
def _collectAffectingSkills(self, thing, char, skillsMap):
|
||||||
|
""" Collect skills that affect items in the fit that the character has """
|
||||||
|
for attr in ("item", "charge"):
|
||||||
|
if attr == "charge" and isinstance(thing, es_Fighter):
|
||||||
|
continue
|
||||||
|
subThing = getattr(thing, attr, None)
|
||||||
|
if subThing is None:
|
||||||
|
continue
|
||||||
|
if isinstance(thing, es_Fighter) and attr == "charge":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if attr == "charge":
|
||||||
|
cont = getattr(thing, "chargeModifiedAttributes", None)
|
||||||
|
else:
|
||||||
|
cont = getattr(thing, "itemModifiedAttributes", None)
|
||||||
|
|
||||||
|
if cont is not None:
|
||||||
|
for attrName in cont.iterAfflictions():
|
||||||
|
for fit, afflictors in cont.getAfflictions(attrName).items():
|
||||||
|
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||||
|
if isinstance(afflictor, Skill) and afflictor.character == char:
|
||||||
|
skillName = afflictor.item.name
|
||||||
|
if skillName not in skillsMap:
|
||||||
|
skillsMap[skillName] = afflictor.level
|
||||||
|
elif skillsMap[skillName] < afflictor.level:
|
||||||
|
skillsMap[skillName] = afflictor.level
|
||||||
|
|
||||||
def fileImportDialog(self, event):
|
def fileImportDialog(self, event):
|
||||||
"""Handles importing single/multiple EVE XML / EFT cfg fit files"""
|
"""Handles importing single/multiple EVE XML / EFT cfg fit files"""
|
||||||
with wx.FileDialog(
|
with wx.FileDialog(
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class MainMenuBar(wx.MenuBar):
|
|||||||
self.graphFrameId = wx.NewId()
|
self.graphFrameId = wx.NewId()
|
||||||
self.backupFitsId = wx.NewId()
|
self.backupFitsId = wx.NewId()
|
||||||
self.exportSkillsNeededId = wx.NewId()
|
self.exportSkillsNeededId = wx.NewId()
|
||||||
|
self.copySkillsNeededId = wx.NewId()
|
||||||
self.importCharacterId = wx.NewId()
|
self.importCharacterId = wx.NewId()
|
||||||
self.exportHtmlId = wx.NewId()
|
self.exportHtmlId = wx.NewId()
|
||||||
self.wikiId = wx.NewId()
|
self.wikiId = wx.NewId()
|
||||||
@@ -117,6 +118,7 @@ class MainMenuBar(wx.MenuBar):
|
|||||||
characterMenu.AppendSeparator()
|
characterMenu.AppendSeparator()
|
||||||
characterMenu.Append(self.importCharacterId, _t("&Import Character File"), _t("Import characters into pyfa from file"))
|
characterMenu.Append(self.importCharacterId, _t("&Import Character File"), _t("Import characters into pyfa from file"))
|
||||||
characterMenu.Append(self.exportSkillsNeededId, _t("&Export Skills Needed"), _t("Export skills needed for this fitting"))
|
characterMenu.Append(self.exportSkillsNeededId, _t("&Export Skills Needed"), _t("Export skills needed for this fitting"))
|
||||||
|
characterMenu.Append(self.copySkillsNeededId, _t("&Copy Skills Needed"), _t("Copy skills needed for this fitting to clipboard"))
|
||||||
|
|
||||||
characterMenu.AppendSeparator()
|
characterMenu.AppendSeparator()
|
||||||
characterMenu.Append(self.ssoLoginId, _t("&Manage ESI Characters"))
|
characterMenu.Append(self.ssoLoginId, _t("&Manage ESI Characters"))
|
||||||
@@ -178,6 +180,7 @@ class MainMenuBar(wx.MenuBar):
|
|||||||
self.Enable(wx.ID_SAVEAS, enable)
|
self.Enable(wx.ID_SAVEAS, enable)
|
||||||
self.Enable(wx.ID_COPY, enable)
|
self.Enable(wx.ID_COPY, enable)
|
||||||
self.Enable(self.exportSkillsNeededId, enable)
|
self.Enable(self.exportSkillsNeededId, enable)
|
||||||
|
self.Enable(self.copySkillsNeededId, enable)
|
||||||
|
|
||||||
self.refreshUndo()
|
self.refreshUndo()
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class MarketBrowser(wx.Panel):
|
|||||||
self.settings = MarketPriceSettings.getInstance()
|
self.settings = MarketPriceSettings.getInstance()
|
||||||
self.__mode = 'normal'
|
self.__mode = 'normal'
|
||||||
self.__normalBtnMap = {}
|
self.__normalBtnMap = {}
|
||||||
|
self.__normalSlotBtnMap = {}
|
||||||
self.marketView = MarketTree(self.splitter, self)
|
self.marketView = MarketTree(self.splitter, self)
|
||||||
self.itemView = ItemView(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,
|
# Same fix as for search box on macs,
|
||||||
# need some pixels of extra space or everything clips and is ugly
|
# need some pixels of extra space or everything clips and is ugly
|
||||||
p = wx.Panel(self)
|
p = wx.Panel(self)
|
||||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
vbox_panel = wx.BoxSizer(wx.VERTICAL)
|
||||||
p.SetSizer(box)
|
p.SetSizer(vbox_panel)
|
||||||
vbox.Add(p, 0, wx.EXPAND)
|
vbox.Add(p, 0, wx.EXPAND)
|
||||||
|
|
||||||
|
# First row: meta buttons
|
||||||
|
metaBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
vbox_panel.Add(metaBox, 0, wx.EXPAND)
|
||||||
self.metaButtons = []
|
self.metaButtons = []
|
||||||
btn = None
|
btn = None
|
||||||
for name in list(self.sMkt.META_MAP.keys()):
|
for name in list(self.sMkt.META_MAP.keys()):
|
||||||
btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
|
btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
|
||||||
setattr(self, name, btn)
|
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.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton)
|
||||||
btn.metaName = name
|
btn.metaName = name
|
||||||
self.metaButtons.append(btn)
|
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
|
# Make itemview to set toggles according to list contents
|
||||||
self.itemView.setToggles()
|
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):
|
def toggleMetaButton(self, event):
|
||||||
"""Process clicks on toggle buttons"""
|
"""Process clicks on toggle buttons"""
|
||||||
@@ -100,6 +140,21 @@ class MarketBrowser(wx.Panel):
|
|||||||
|
|
||||||
self.itemView.filterItemStore()
|
self.itemView.filterItemStore()
|
||||||
|
|
||||||
|
def toggleSlotButton(self, event):
|
||||||
|
"""Process clicks on slot/fits filter buttons"""
|
||||||
|
clickedBtn = event.EventObject
|
||||||
|
|
||||||
|
# All buttons (Fits, High, Med, Low, Rig) work as checkboxes (independent toggles)
|
||||||
|
clickedBtn.setUserSelection(clickedBtn.GetValue())
|
||||||
|
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):
|
def jump(self, item):
|
||||||
self.mode = 'normal'
|
self.mode = 'normal'
|
||||||
self.marketView.jump(item)
|
self.marketView.jump(item)
|
||||||
@@ -141,6 +196,9 @@ class MarketBrowser(wx.Panel):
|
|||||||
self.__normalBtnMap.clear()
|
self.__normalBtnMap.clear()
|
||||||
for btn in self.metaButtons:
|
for btn in self.metaButtons:
|
||||||
self.__normalBtnMap[btn] = btn.userSelected
|
self.__normalBtnMap[btn] = btn.userSelected
|
||||||
|
self.__normalSlotBtnMap.clear()
|
||||||
|
for btn in self.slotButtons:
|
||||||
|
self.__normalSlotBtnMap[btn] = btn.userSelected
|
||||||
if newMode == 'search':
|
if newMode == 'search':
|
||||||
self.marketView.UnselectAll()
|
self.marketView.UnselectAll()
|
||||||
setting = self.settings.get('marketMGSearchMode')
|
setting = self.settings.get('marketMGSearchMode')
|
||||||
@@ -149,12 +207,16 @@ class MarketBrowser(wx.Panel):
|
|||||||
if newMode in ('search', 'recent', 'charges'):
|
if newMode in ('search', 'recent', 'charges'):
|
||||||
for btn in self.metaButtons:
|
for btn in self.metaButtons:
|
||||||
btn.setUserSelection(True)
|
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':
|
if newMode == 'normal':
|
||||||
for btn, state in self.__normalBtnMap.items():
|
for btn, state in self.__normalBtnMap.items():
|
||||||
btn.setUserSelection(state)
|
btn.setUserSelection(state)
|
||||||
|
for btn, state in self.__normalSlotBtnMap.items():
|
||||||
|
btn.setUserSelection(state)
|
||||||
# We turn on all meta buttons permanently
|
# We turn on all meta buttons permanently
|
||||||
if setting == 2:
|
if setting == 2:
|
||||||
for btn in self.metaButtons:
|
for btn in self.metaButtons:
|
||||||
btn.setUserSelection(True)
|
btn.setUserSelection(True)
|
||||||
self.__mode = newMode
|
self.__mode = newMode
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,68 @@
|
|||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
import wx
|
import wx
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def toClipboard(text):
|
def toClipboard(text):
|
||||||
clip = wx.TheClipboard
|
"""
|
||||||
clip.Open()
|
Copy text to clipboard. Explicitly uses CLIPBOARD selection, not PRIMARY.
|
||||||
data = wx.TextDataObject(text)
|
|
||||||
clip.SetData(data)
|
On X11 systems, wxPython can confuse between PRIMARY and CLIPBOARD selections,
|
||||||
clip.Close()
|
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():
|
def fromClipboard():
|
||||||
clip = wx.TheClipboard
|
"""
|
||||||
clip.Open()
|
Read text from clipboard. Explicitly uses CLIPBOARD selection, not PRIMARY.
|
||||||
data = wx.TextDataObject("")
|
|
||||||
if clip.GetData(data):
|
On X11 systems, wxPython can confuse between PRIMARY and CLIPBOARD selections,
|
||||||
clip.Close()
|
causing "already open" errors. This function ensures we always use CLIPBOARD.
|
||||||
return data.GetText()
|
|
||||||
else:
|
See: https://discuss.wxpython.org/t/wx-theclipboard-pasting-different-content-on-every-second-paste/35361
|
||||||
clip.Close()
|
"""
|
||||||
|
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
|
return None
|
||||||
|
|||||||
BIN
imgs/icons/10850@1x.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
imgs/icons/10850@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
imgs/icons/1546@1x.png
Normal file
|
After Width: | Height: | Size: 767 B |
BIN
imgs/icons/1546@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
imgs/icons/24566@1x.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
imgs/icons/24566@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
imgs/icons/25235@1x.png
Normal file
|
After Width: | Height: | Size: 769 B |
BIN
imgs/icons/25235@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
imgs/icons/25236@1x.png
Normal file
|
After Width: | Height: | Size: 805 B |
BIN
imgs/icons/25236@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25237@1x.png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
imgs/icons/25237@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/25238@1x.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
imgs/icons/25238@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25240@1x.png
Normal file
|
After Width: | Height: | Size: 809 B |
BIN
imgs/icons/25240@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
imgs/icons/25241@1x.png
Normal file
|
After Width: | Height: | Size: 847 B |
BIN
imgs/icons/25241@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25242@1x.png
Normal file
|
After Width: | Height: | Size: 856 B |
BIN
imgs/icons/25242@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/25243@1x.png
Normal file
|
After Width: | Height: | Size: 847 B |
BIN
imgs/icons/25243@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25245@1x.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
imgs/icons/25245@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
imgs/icons/25246@1x.png
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
imgs/icons/25246@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25247@1x.png
Normal file
|
After Width: | Height: | Size: 840 B |
BIN
imgs/icons/25247@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/25248@1x.png
Normal file
|
After Width: | Height: | Size: 838 B |
BIN
imgs/icons/25248@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25250@1x.png
Normal file
|
After Width: | Height: | Size: 821 B |
BIN
imgs/icons/25250@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/25251@1x.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
imgs/icons/25251@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/25252@1x.png
Normal file
|
After Width: | Height: | Size: 839 B |
BIN
imgs/icons/25252@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
imgs/icons/25253@1x.png
Normal file
|
After Width: | Height: | Size: 845 B |
BIN
imgs/icons/25253@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/27053@1x.png
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
imgs/icons/27053@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
imgs/icons/27054@1x.png
Normal file
|
After Width: | Height: | Size: 928 B |
BIN
imgs/icons/27054@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
imgs/icons/27055@1x.png
Normal file
|
After Width: | Height: | Size: 918 B |
BIN
imgs/icons/27055@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
imgs/icons/27056@1x.png
Normal file
|
After Width: | Height: | Size: 912 B |
BIN
imgs/icons/27056@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27058@1x.png
Normal file
|
After Width: | Height: | Size: 598 B |
BIN
imgs/icons/27139@1x.png
Normal file
|
After Width: | Height: | Size: 860 B |
BIN
imgs/icons/27139@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27154@1x.png
Normal file
|
After Width: | Height: | Size: 782 B |
BIN
imgs/icons/27154@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
imgs/icons/27198@1x.png
Normal file
|
After Width: | Height: | Size: 862 B |
BIN
imgs/icons/27198@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27199@1x.png
Normal file
|
After Width: | Height: | Size: 863 B |
BIN
imgs/icons/27199@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27200@1x.png
Normal file
|
After Width: | Height: | Size: 860 B |
BIN
imgs/icons/27200@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27201@1x.png
Normal file
|
After Width: | Height: | Size: 878 B |
BIN
imgs/icons/27201@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
imgs/icons/27202@1x.png
Normal file
|
After Width: | Height: | Size: 888 B |
BIN
imgs/icons/27202@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
imgs/icons/27203@1x.png
Normal file
|
After Width: | Height: | Size: 876 B |
BIN
imgs/icons/27203@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
imgs/icons/27204@1x.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
imgs/icons/27204@2x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
imgs/icons/27205@1x.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
imgs/icons/27205@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/27206@1x.png
Normal file
|
After Width: | Height: | Size: 818 B |
BIN
imgs/icons/27206@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/27207@1x.png
Normal file
|
After Width: | Height: | Size: 817 B |
BIN
imgs/icons/27207@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/icons/27208@1x.png
Normal file
|
After Width: | Height: | Size: 872 B |
BIN
imgs/icons/27208@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
imgs/icons/27209@1x.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
imgs/icons/27209@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
imgs/icons/27210@1x.png
Normal file
|
After Width: | Height: | Size: 875 B |