Merge branch 'pyfa-org:master' into master

This commit is contained in:
正汰
2024-01-12 11:00:13 +08:00
committed by GitHub
263 changed files with 455210 additions and 113977 deletions

View File

@@ -0,0 +1,9 @@
"""
Conversion pack for August 2022 release
"""
CONVERSIONS = {
# Renamed items
"Basic Reactor Control Unit": "'Basic' Reactor Control Unit",
"Basic Co-Processor": "'Basic' Co-Processor",
}

View File

@@ -0,0 +1,16 @@
"""
Conversion pack for February 2023 release
"""
CONVERSIONS = {
# Renamed items
"Restrained Interdiction Nullifier": "Enduring Interdiction Nullifier",
"Synthetic Hull Conversion Inertia Stabilizers": "Synthetic Hull Conversion Inertial Stabilizers",
"Tobias's Modified Torpedo Launcher": "Tobias' Modified Torpedo Launcher",
"Vepas's Modified Torpedo Launcher": "Vepas' Modified Torpedo Launcher",
"Vepas's Modified Kinetic Shield Hardener": "Vepas' Modified Kinetic Shield Hardener",
"Vepas's Modified EM Shield Hardener": "Vepas' Modified EM Shield Hardener",
"Vepas's Modified Explosive Shield Hardener": "Vepas' Modified Explosive Shield Hardener",
"Vepas's Modified Thermal Shield Hardener": "Vepas' Modified Thermal Shield Hardener",
"Vepas's Modified Multispectrum Shield Hardener": "Vepas' Modified Multispectrum Shield Hardener",
}

View File

@@ -0,0 +1,10 @@
"""
Actually renamed somewhere during summer, but updated only in september
"""
CONVERSIONS = {
# Renamed items
"Atgeir Explosive Disruptive Lance": "'Atgeir' Explosive Disruptive Lance",
"Steel Yari Kinetic Disruptive Lance": "'Steel Yari' Kinetic Disruptive Lance",
"Sarissa Thermal Disruptive Lance": "'Sarissa' Thermal Disruptive Lance",
}

View File

@@ -88,7 +88,7 @@ class Fit:
"enableGaugeAnimation": True,
"openFitInNew": False,
"priceSystem": "Jita",
"priceSource": "evemarketer",
"priceSource": "fuzzwork market",
"showShipBrowserTooltip": True,
"marketSearchDelay": 250,
"ammoChangeAll": False,

View File

@@ -263,6 +263,9 @@ bcs:
bcu:
- 'bcu'
- 'ballistic control system'
vts:
- 'vts'
- 'vorton tuning system'
tc:
- 'tc'
- '(?<!remote )tracking computer'
@@ -976,6 +979,9 @@ cdfe:
cp:
- 'cp'
- 'command processor'
ats:
- 'ats'
- 'auto targeting system'
# Implants
lg:

View File

@@ -35,6 +35,7 @@ from eos.gamedata import Category as types_Category, Group as types_Group, Item
from service import conversions
from service.jargon import JargonLoader
from service.settings import SettingsProvider
from utils.cjk import isStringCjk
pyfalog = Logger(__name__)
_t = wx.GetTranslation
@@ -152,7 +153,11 @@ class SearchWorkerThread(threading.Thread):
requestTokens = self.jargonLoader.get_jargon().apply(requestTokens)
all_results = set()
if len(' '.join(requestTokens)) >= config.minItemSearchLength:
joinedTokens = ' '.join(requestTokens)
if (
(isStringCjk(joinedTokens) and len(joinedTokens) >= config.minItemSearchLengthCjk)
or len(joinedTokens) >= config.minItemSearchLength
):
for filter_ in filters:
filtered_results = eos.db.searchItemsRegex(
requestTokens, where=filter_,
@@ -314,6 +319,11 @@ class Market:
"Raiju" : self.les_grp, # AT17 prize
"Laelaps" : self.les_grp, # AT17 prize
"Boobook" : self.les_grp, # 19th EVE anniversary gift
"Geri" : self.les_grp, # AT18 prize
"Bestla" : self.les_grp, # AT18 prize
"Metamorphosis" : self.les_grp, # Seems to be anniversary gift
"Shapash" : self.les_grp, # AT19 prize
"Cybele" : self.les_grp, # AT19 prize
}
self.ITEMS_FORCEGROUP_R = self.__makeRevDict(self.ITEMS_FORCEGROUP)

View File

@@ -1 +1 @@
__all__ = ['evemarketer', 'evepraisal', 'evemarketdata', 'fuzzwork', 'cevemarket']
__all__ = ['evetycoon', 'evemarketdata', 'fuzzwork', 'cevemarket']

View File

@@ -1,75 +0,0 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from xml.dom import minidom
from logbook import Logger
from eos.saveddata.price import PriceStatus
from service.network import Network
from service.price import Price
pyfalog = Logger(__name__)
class EveMarketer:
name = 'evemarketer'
group = 'tranquility'
def __init__(self, priceMap, system, fetchTimeout):
# Try selected system first
self.fetchPrices(priceMap, max(2 * fetchTimeout / 3, 2), system)
# If price was not available - try globally
if priceMap:
self.fetchPrices(priceMap, max(fetchTimeout / 3, 2))
@staticmethod
def fetchPrices(priceMap, fetchTimeout, system=None):
params = {'typeid': {typeID for typeID in priceMap}}
if system is not None:
params['usesystem'] = system
baseurl = 'https://api.evemarketer.com/ec/marketstat'
network = Network.getInstance()
data = network.get(url=baseurl, type=network.PRICES, params=params, timeout=fetchTimeout)
xml = minidom.parseString(data.text)
types = xml.getElementsByTagName('marketstat').item(0).getElementsByTagName('type')
# Cycle through all types we've got from request
for type_ in types:
# Get data out of each typeID details tree
typeID = int(type_.getAttribute('id'))
sell = type_.getElementsByTagName('sell').item(0)
# If price data wasn't there, skip the item
try:
percprice = float(sell.getElementsByTagName('percentile').item(0).firstChild.data)
except (TypeError, ValueError):
pyfalog.warning('Failed to get price for: {0}', type_)
continue
# Price is 0 if evemarketer has info on this item, but it is not available
# for current scope limit. If we provided scope limit - make sure to skip
# such items to check globally, and do not skip if requested globally
if percprice == 0 and system is not None:
continue
priceMap[typeID].update(PriceStatus.fetchSuccess, percprice)
del priceMap[typeID]
Price.register(EveMarketer)

View File

@@ -17,7 +17,6 @@
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from logbook import Logger
from eos.saveddata.price import PriceStatus
@@ -26,58 +25,41 @@ from service.price import Price
pyfalog = Logger(__name__)
systemAliases = {
None: 'universe',
30000142: 'jita',
30002187: 'amarr',
30002659: 'dodixie',
30002510: 'rens',
30002053: 'hek'}
locations = {
30000142: (10000002, 60003760), # Jita 4-4 CNAP
30002187: (10000043, 60008494), # Amarr VIII
30002659: (10000032, 60011866), # Dodixie
30002510: (10000030, 60004588), # Rens
30002053: (10000042, 60005686)} # Hek
class EvePraisal:
class EveTycoon:
name = 'evepraisal'
name = 'evetycoon'
group = 'tranquility'
def __init__(self, priceMap, system, fetchTimeout):
# Try selected system first
self.fetchPrices(priceMap, max(2 * fetchTimeout / 3, 2), system)
# If price was not available - try globally
if priceMap:
self.fetchPrices(priceMap, max(fetchTimeout / 3, 2))
@staticmethod
def fetchPrices(priceMap, fetchTimeout, system=None):
if system not in systemAliases:
return
jsonData = {
'market_name': systemAliases[system],
'items': [{'type_id': typeID} for typeID in priceMap]}
baseurl = 'https://evepraisal.com/appraisal/structured.json'
# Default to jita when system is not found
regionID, stationID = locations.get(system, locations[30000142])
baseurl = 'https://evetycoon.com/api/v1/market/stats'
network = Network.getInstance()
resp = network.post(baseurl, network.PRICES, jsonData=jsonData, timeout=fetchTimeout)
data = resp.json()
try:
itemsData = data['appraisal']['items']
except (KeyError, TypeError):
return
# Cycle through all types we've got from request
for itemData in itemsData:
try:
typeID = int(itemData['typeID'])
price = itemData['prices']['sell']['min']
orderCount = itemData['prices']['sell']['order_count']
except (KeyError, TypeError):
for typeID in tuple(priceMap):
url = f'{baseurl}/{regionID}/{typeID}'
resp = network.get(url=url, params={'locationId': stationID}, type=network.PRICES, timeout=fetchTimeout)
if resp.status_code != 200:
continue
# evepraisal returns 0 if price data doesn't even exist for the item
price = resp.json()['sellAvgFivePercent']
# Price is 0 - no data
if price == 0:
continue
# evepraisal seems to provide price for some items despite having no orders up
if orderCount < 1:
continue
priceMap[typeID].update(PriceStatus.fetchSuccess, price)
del priceMap[typeID]
Price.register(EvePraisal)
Price.register(EveTycoon)

View File

@@ -28,7 +28,7 @@ pyfaVersion = getVersion()
class EfsPort:
wepTestSet = {}
version = 0.05
version = 0.06
@staticmethod
def attrDirectMap(values, target, source):
@@ -207,6 +207,9 @@ class EfsPort:
]:
stats["type"] = "Remote Armor Repairer"
EfsPort.attrDirectMap(["armorDamageAmount"], stats, mod)
elif mod.item.group.name in ["Remote Capacitor Transmitter"]:
stats["type"] = "Remote Capacitor Transmitter"
EfsPort.attrDirectMap(["powerTransferAmount"], stats, mod)
elif mod.item.group.name == "Warp Scrambler":
stats["type"] = "Warp Scrambler"
EfsPort.attrDirectMap(["activationBlockedStrenght", "warpScrambleStrength"], stats, mod)
@@ -243,7 +246,10 @@ class EfsPort:
pyfalog.error("Projected module {0} lacks efs export implementation".format(mod.item.typeName))
if mod.getModifiedItemAttr("maxRange", None) is None:
pyfalog.error("Projected module {0} has no maxRange".format(mod.item.typeName))
stats["optimal"] = mod.getModifiedItemAttr("maxRange", maxRangeDefault)
# Burst jammer maxRange is 0 if the value is retrieved using mod.getModifiedItemAttr("maxRange")
# Despite it is correct, it still pulls 0.0.
stats["optimal"] = mod.getModifiedItemAttr("maxRange", maxRangeDefault) if mod.item.group.name != "Burst Jammer" else mod.maxRange
stats["falloff"] = mod.getModifiedItemAttr("falloffEffectiveness", falloffDefault)
EfsPort.attrDirectMap(["duration", "capacitorNeed"], stats, mod)
projections.append(stats)

View File

@@ -165,16 +165,25 @@ def exportModules(modules, options, mutaData=None):
def exportDrones(drones, exportMutants=True, mutaData=None, standAlone=True):
# Same as in drone additions panel
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
def getDroneName(drone):
if drone.isMutated:
return drone.baseItem.typeName
return drone.item.typeName
def droneSorter(drone):
groupName = Market.getInstance().getMarketGroupByItem(drone.item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
if mutaData is None:
mutaData = MutationExportData()
sections = []
droneLines = []
for drone in sorted(drones, key=getDroneName):
for drone in sorted(drones, key=droneSorter):
if drone.isMutated and exportMutants:
mutaData.mutants[mutaData.reference] = drone
mutationSuffix = ' [{}]'.format(mutaData.reference)
@@ -190,8 +199,15 @@ def exportDrones(drones, exportMutants=True, mutaData=None, standAlone=True):
def exportFighters(fighters):
# Same as in drone additions panel
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
def fighterSorter(fighter):
groupName = Market.getInstance().getGroupByItem(fighter.item).name
return (FIGHTER_ORDER.index(groupName), fighter.item.typeName)
fighterLines = []
for fighter in sorted(fighters, key=lambda f: f.item.typeName):
for fighter in sorted(fighters, key=fighterSorter):
fighterLines.append('{} x{}'.format(fighter.item.typeName, fighter.amount))
return '\n'.join(fighterLines)

View File

@@ -56,7 +56,7 @@ INV_FLAG_DRONEBAY = 87
INV_FLAG_FIGHTER = 158
def exportESI(ofit, exportCharges, callback):
def exportESI(ofit, exportCharges, exportImplants, exportBoosters, callback):
# A few notes:
# max fit name length is 50 characters
# Most keys are created simply because they are required, but bogus data is okay
@@ -133,6 +133,22 @@ def exportESI(ofit, exportCharges, callback):
item['type_id'] = fighter.item.ID
fit['items'].append(item)
if exportImplants:
for implant in ofit.implants:
item = nested_dict()
item['flag'] = INV_FLAG_CARGOBAY
item['quantity'] = 1
item['type_id'] = implant.item.ID
fit['items'].append(item)
if exportBoosters:
for booster in ofit.boosters:
item = nested_dict()
item['flag'] = INV_FLAG_CARGOBAY
item['quantity'] = 1
item['type_id'] = booster.item.ID
fit['items'].append(item)
if len(fit['items']) == 0:
raise ESIExportException("Cannot export fitting: module list cannot be empty.")

View File

@@ -323,8 +323,8 @@ class Port:
return importESI(string)
@staticmethod
def exportESI(fit, exportCharges, callback=None):
return exportESI(fit, exportCharges, callback=callback)
def exportESI(fit, exportCharges, exportImplants, exportBoosters, callback=None):
return exportESI(fit, exportCharges, exportImplants, exportBoosters, callback=callback)
# XML-related methods
@staticmethod

View File

@@ -36,7 +36,7 @@ def tankSection(fit):
ehp.append(sum(ehp))
ehpStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehp]
resists = {tankType: [1 - fit.ship.getModifiedItemAttr(s) for s in resonanceNames[tankType]] for tankType in tankTypes}
ehpAgainstDamageType = [sum(pattern.calculateEhp(fit).values()) for pattern in damagePatterns]
ehpAgainstDamageType = [sum(pattern.calculateEhp(fit.ship).values()) for pattern in damagePatterns]
ehpAgainstDamageTypeStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehpAgainstDamageType]
# not used for now. maybe will be improved later

View File

@@ -276,4 +276,4 @@ class PriceWorkerThread(threading.Thread):
# Import market sources only to initialize price source modules, they register on their own
from service.marketSources import evemarketer, evemarketdata, evepraisal, fuzzwork, cevemarket # noqa: E402
from service.marketSources import evemarketdata, fuzzwork, cevemarket, evetycoon # noqa: E402

View File

@@ -222,9 +222,9 @@ class NetworkSettings:
if prefix not in proxydict:
continue
proxyline = proxydict[prefix]
proto = "{0}://".format(prefix)
if proxyline[:len(proto)] == proto:
proxyline = proxyline[len(proto):]
proto_pos = proxyline.find('://')
if proto_pos != -1:
proxyline = proxyline[proto_pos+3:]
# sometimes proxyline contains "user:password@" section before proxy address
# remove it if present, so later split by ":" works
if '@' in proxyline:
@@ -376,6 +376,8 @@ class EsiSettings:
"timeout": 60,
"server": "Tranquility",
"exportCharges": True,
"exportImplants": True,
"exportBoosters": True,
"enforceJwtExpiration": True
}
@@ -560,7 +562,7 @@ class LocaleSettings:
with open(os.path.join(config.pyfaPath, 'locale', 'progress.json'), "r") as f:
self.progress_data = json.load(f)
except FileNotFoundError:
self.progress_data = None
self.progress_data = {}
@classmethod
def getInstance(cls):
@@ -569,14 +571,14 @@ class LocaleSettings:
return cls._instance
def get_progress(self, lang):
if self.progress_data is None:
if not self.progress_data:
return None
if lang == self.defaults['locale']:
return None
return self.progress_data[lang]
return self.progress_data.get(lang)
@classmethod
def supported_langauges(cls):
def supported_languages(cls):
"""Requires the application to be initialized, otherwise wx.Translation isn't set."""
pyfalog.info(f'using "{config.CATALOG}" to fetch languages, relatively base path "{os.getcwd()}"')
return {x: wx.Locale.FindLanguageInfo(x) for x in wx.Translations.Get().GetAvailableTranslations(config.CATALOG)}
@@ -591,6 +593,6 @@ class LocaleSettings:
return val if val != self.defaults['eos_locale'] else self.settings['locale'].split("_")[0]
def set(self, key, value):
if key == 'locale' and value not in self.supported_langauges():
if key == 'locale' and value not in self.supported_languages():
self.settings[key] = self.DEFAULT
self.settings[key] = value