Merge branch 'pyfa-org:master' into master
This commit is contained in:
9
service/conversions/releaseAug2022.py
Normal file
9
service/conversions/releaseAug2022.py
Normal 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",
|
||||
}
|
||||
16
service/conversions/releaseFeb2023.py
Normal file
16
service/conversions/releaseFeb2023.py
Normal 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",
|
||||
}
|
||||
10
service/conversions/releaseSep2023.py
Normal file
10
service/conversions/releaseSep2023.py
Normal 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",
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class Fit:
|
||||
"enableGaugeAnimation": True,
|
||||
"openFitInNew": False,
|
||||
"priceSystem": "Jita",
|
||||
"priceSource": "evemarketer",
|
||||
"priceSource": "fuzzwork market",
|
||||
"showShipBrowserTooltip": True,
|
||||
"marketSearchDelay": 250,
|
||||
"ammoChangeAll": False,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
__all__ = ['evemarketer', 'evepraisal', 'evemarketdata', 'fuzzwork', 'cevemarket']
|
||||
__all__ = ['evetycoon', 'evemarketdata', 'fuzzwork', 'cevemarket']
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user