Compare commits

...

107 Commits

Author SHA1 Message Date
DarkPhoenix
ae3d7ade0f Bump version 2021-11-28 23:46:48 +03:00
DarkPhoenix
789df97017 Merge branch 'master' into singularity 2021-11-28 04:04:56 +03:00
DarkPhoenix
7992ba43b2 Change how command burst misc column info is processed 2021-11-28 00:06:15 +03:00
DarkPhoenix
3595e24ad8 Merge branch 'master' into singularity 2021-11-27 15:35:30 +03:00
Anton Vorobyov
6677435474 Merge pull request #2360 from shyadow/master
Add Command Burst effect in Miscellanea Column
2021-11-27 15:18:47 +03:00
DarkPhoenix
1561d9f581 Fix drone group check 2021-11-27 15:12:52 +03:00
DarkPhoenix
1dee8ae648 Bump version 2021-11-27 00:21:16 +03:00
DarkPhoenix
51ea0c4f31 Fix another issue with restrictions 2021-11-27 00:09:43 +03:00
DarkPhoenix
9d8e338136 Implement multi-level selection menu for mining crystals 2021-11-27 00:05:18 +03:00
DarkPhoenix
48818c9709 Fix turret ammo picker 2021-11-26 21:39:08 +03:00
DarkPhoenix
ca356b413b Add mining waste readout to the misc column, and fix more issues related to the attribute fetching change 2021-11-26 21:27:47 +03:00
DarkPhoenix
a9ef5caf2d Fix attribute fetching, which is needed to calculate mining waste of crystal'd strip miners
This potentially can break many things. Some of those are fixed already, but there might be many more...
2021-11-26 20:43:46 +03:00
DarkPhoenix
22be97f901 Calculate waste stats for fits 2021-11-26 17:19:07 +03:00
DarkPhoenix
06f515ec0b Update icons and renders 2021-11-26 02:45:15 +03:00
DarkPhoenix
c878f4c155 Show yield for new gas hoarding modules 2021-11-26 02:42:08 +03:00
DarkPhoenix
5cf433a3eb Add industrial reconfiguration effect 2021-11-26 02:39:25 +03:00
DarkPhoenix
15191e5752 Add new orca & rorqual effects 2021-11-25 23:07:51 +03:00
DarkPhoenix
9d833d9cac Add new barge/exhumer effects 2021-11-25 22:48:33 +03:00
DarkPhoenix
5767c92985 Add more renames 2021-11-25 22:06:42 +03:00
DarkPhoenix
3246e58278 Process attribute renames, update changed effects, remove unused effects 2021-11-25 21:49:23 +03:00
DarkPhoenix
30546c6e45 Update staticdata to 1971319 2021-11-25 21:25:55 +03:00
DarkPhoenix
37fe587d90 Add/fix event booster effects 2021-11-25 21:00:20 +03:00
DarkPhoenix
09b1b50257 Add AT17 prizes to LE market group 2021-11-25 20:43:37 +03:00
DarkPhoenix
91a507eadd Add new module/charge effects 2021-11-25 20:41:54 +03:00
DarkPhoenix
dd92c3742c Add new barge/exhumer effects 2021-11-25 18:54:29 +03:00
DarkPhoenix
181170d7b0 Add new industrial effects 2021-11-25 17:11:12 +03:00
DarkPhoenix
1156f06db2 Add new orca/rorq effects 2021-11-25 16:57:32 +03:00
DarkPhoenix
90baffe267 Add new porpoise effects 2021-11-24 02:53:31 +03:00
DarkPhoenix
100a1fa01a Add support for new cargo types to UI 2021-11-24 02:29:55 +03:00
DarkPhoenix
d8d854da52 Add new endurance effects 2021-11-24 02:23:13 +03:00
DarkPhoenix
418339d000 Add new prospect effects 2021-11-24 01:33:20 +03:00
DarkPhoenix
24a0f4f53e Process renamed items and attributes 2021-11-24 01:01:40 +03:00
DarkPhoenix
94784bf1e0 Remove unused effects and update by which items effects are used 2021-11-23 20:54:03 +03:00
DarkPhoenix
d894df9819 Fix changed effects 2021-11-23 20:32:26 +03:00
DarkPhoenix
a5e1f3be40 Update static data to 1970121 2021-11-23 20:13:49 +03:00
shyadow
dcb6439444 Merge branch 'pyfa-org:master' into master 2021-11-20 17:34:20 +01:00
DarkPhoenix
98fa16d921 Bump version and add renamed mutaplasmid conversion 2021-10-28 14:39:57 +03:00
DarkPhoenix
d5795ed5f5 Update static data to 1957858 2021-10-28 14:35:27 +03:00
DarkPhoenix
fe809ed86b Always use base item attributes in mutator 2021-10-27 11:37:14 +03:00
Ryan Holmes
d09db832c3 Merge branch 'master' into master 2021-10-26 20:37:11 -04:00
Ryan Holmes
de4466b037 Merge pull request #2363 from pyfa-org/sso_v2
Sso v2
2021-10-26 17:22:46 -04:00
blitzmann
8ebf478cf7 fix typo in requirements 2021-10-26 16:50:04 -04:00
Ryan Holmes
530a12ea4f Merge branch 'master' into sso_v2 2021-10-26 12:21:10 -04:00
blitzmann
b94f6e8d0a Merge branch 'sso_v2' of https://github.com/pyfa-org/Pyfa into sso_v2 2021-10-26 12:20:20 -04:00
blitzmann
4b83169070 typo fix 2021-10-26 12:20:11 -04:00
DarkPhoenix
a7da9e20a5 Make new option for new mutated item names 2021-10-26 17:48:18 +03:00
Anton Vorobyov
0020dcdf0a Merge branch 'master' into sso_v2 2021-10-26 17:03:24 +03:00
DarkPhoenix
066f697186 Bump version 2021-10-26 13:06:52 +03:00
DarkPhoenix
b2217bb6cd Use short name only if short mutaplasmid name is available 2021-10-26 12:49:27 +03:00
DarkPhoenix
4669b4813c Use short muta + base names for mutated items 2021-10-26 12:22:25 +03:00
DarkPhoenix
a71701932c Do not store mutaplasmids in commands 2021-10-26 11:59:44 +03:00
DarkPhoenix
1be0d306bb Allow imports of mutated drone groups via ctrl-v and additions panel context menus 2021-10-26 11:29:25 +03:00
DarkPhoenix
85f63e237d Allow imports of separate mutated drones from clipboard 2021-10-26 10:19:10 +03:00
DarkPhoenix
acab787a7b Mark mutated drones unpublished 2021-10-26 09:59:32 +03:00
DarkPhoenix
da50c7dcfc Update icons 2021-10-26 09:55:12 +03:00
DarkPhoenix
b0d3469f01 User short mutaplasmid name in mutation panel 2021-10-26 01:31:09 +03:00
DarkPhoenix
148093f6ad Allow import of fits with mutated drones 2021-10-26 01:16:25 +03:00
DarkPhoenix
fab58bdb57 Implement mutated drone export 2021-10-26 00:44:11 +03:00
DarkPhoenix
34b2fdbd1b Store mutation results on window close for undo/redo purposes 2021-10-25 23:43:21 +03:00
DarkPhoenix
ea7a5b3c70 Implement drone mutation support with some exceptions 2021-10-25 23:34:08 +03:00
DarkPhoenix
056685ded5 Make mutaplasmid names short on menu list 2021-10-25 14:08:06 +03:00
DarkPhoenix
60f9e96b19 Add new event booster effects 2021-10-25 13:36:01 +03:00
DarkPhoenix
341950cb42 Add renames for quafe classic 2021-10-25 01:41:41 +03:00
DarkPhoenix
641e982614 Fix quafe apple stacking penalty & run effect used by script 2021-10-25 01:39:01 +03:00
DarkPhoenix
1e534e087a AIR booster stacking penalty fix 2021-10-25 01:32:28 +03:00
DarkPhoenix
0b7b80d0f6 Update bastion effect 2021-10-25 01:21:13 +03:00
DarkPhoenix
27bd9fc55b Update static data to 1954065 2021-10-25 01:18:36 +03:00
blitzmann
533f6f3b24 Handle API exceptions during fit deletion 2021-10-24 14:45:07 -04:00
blitzmann
c18cbe5e59 Handle server timeouts gracefully 2021-10-24 14:30:52 -04:00
blitzmann
adba55eb7d Ensure that the token error dialog spawns n the three most used API calls that might trigger token refresh: fetch skills, fetch fits, and export fit 2021-10-24 14:07:04 -04:00
blitzmann
03c6f7c894 Update token exception message to include button to manage ESI characters 2021-10-24 13:07:32 -04:00
DarkPhoenix
478b2bfe4c Fix cargo amount change command 2021-10-23 16:05:45 +03:00
DarkPhoenix
7e4d8a3074 Allow to change multiple cargo items at once 2021-10-23 04:05:20 +03:00
DarkPhoenix
8497d1f1bb Change how ECM chance is calculated so that sensor strength links are considered 2021-10-21 16:02:04 +03:00
blitzmann
1b5acc36d3 update callback 2021-10-19 00:15:22 -04:00
blitzmann
2a94dcebf8 Remove various preferences related to SSO, move callback to config 2021-10-19 00:15:10 -04:00
blitzmann
7b9e196ca8 additional error handling 2021-10-19 00:02:53 -04:00
blitzmann
f3f7d688ab Properly handle the "manual" mode of copy and paste auth information 2021-10-18 14:06:36 -04:00
blitzmann
17391f119c No longer looks like esipy, removing comment chunk 2021-10-18 13:12:53 -04:00
blitzmann
abd138a015 Pull endpoints from .well-known route
Add request caching (only for meta calls for now)
Add server list (eventually want to support various servers, for now only TQ)
2021-10-18 13:09:16 -04:00
blitzmann
33aa208513 Token validation and various cleanup 2021-10-17 21:01:30 -04:00
blitzmann
1874cbe0c5 Work on the server to handle the new structure 2021-10-16 16:52:23 -04:00
blitzmann
028fb42e18 Starting some tweaks on SSO 2021-10-16 12:46:25 -04:00
shyadow
6b444bfc63 Change all but first ifs to elifs 2021-10-14 18:28:13 +02:00
shyadow
1753d79bc1 Add Command Burst effect in Miscellanea Column 2021-10-14 13:19:00 +02:00
Ryan Holmes
d9b0d2e72a Update README.md 2021-10-13 23:22:39 -04:00
Ryan Holmes
e0234c343e Update README.md 2021-10-13 23:20:57 -04:00
DarkPhoenix
b5816a312c Bump ESI character endpoint version 2021-10-05 16:32:42 +03:00
Anton Vorobyov
86cc025812 Merge pull request #2347 from chwons/master
fix text color when using a dark system theme
2021-07-13 15:51:06 +03:00
DarkPhoenix
8072f7e618 Bump version 2021-07-13 15:42:46 +03:00
DarkPhoenix
c34b12d806 Fix ewar booster effect 2021-07-13 15:42:33 +03:00
DarkPhoenix
67760fa2ab Update effects 2021-07-13 15:37:54 +03:00
DarkPhoenix
9a28dd17be Update client data to 1918815 2021-07-13 15:04:05 +03:00
chwons
6e4d1876ad Merge branch 'pyfa-org:master' into master 2021-07-08 07:59:50 +09:00
DarkPhoenix
40b0b27176 Run effects used by 2021-07-02 17:46:55 +03:00
DarkPhoenix
b238422f29 Update static data to 1916151 2021-07-02 17:33:30 +03:00
DarkPhoenix
4e20583b81 Update static data to 1914752 2021-06-28 19:54:32 +03:00
DarkPhoenix
f730cdb86a Bump version 2021-06-27 02:34:11 +03:00
DarkPhoenix
e05471fa6f Update effects 2021-06-27 02:33:48 +03:00
DarkPhoenix
45b517e097 Update static data to 1914498 2021-06-27 02:07:46 +03:00
DarkPhoenix
f27274c9e5 Update staticdata to 1913437 2021-06-27 01:44:52 +03:00
chwons
d32a8f0b61 stop testing 2021-06-23 01:59:28 +09:00
chwons
6a01086cf5 Update itemTraits.py 2021-06-23 01:49:27 +09:00
chwons
e0f65c2370 Update itemAttributes.py 2021-06-23 01:48:59 +09:00
chwons
e7c2f4cff7 Update itemDescription.py 2021-06-23 01:47:57 +09:00
chwons
93cce50d00 testing 2021-06-23 01:26:54 +09:00
DarkPhoenix
d1a2cdc586 Bump version 2021-06-20 04:05:51 +03:00
393 changed files with 123973 additions and 37267 deletions

View File

@@ -43,7 +43,9 @@ experimentalFeatures = None
version = None
language = None
API_CLIENT_ID = '095d8cd841ac40b581330919b49fe746'
ESI_CACHE = 'esi_cache'
SSO_CALLBACK = 'https://pyfa-org.github.io/Pyfa/callback'
LOGLEVEL_MAP = {
"critical": CRITICAL,

View File

@@ -27,6 +27,8 @@ import re
import sqlite3
import sys
from sqlalchemy import or_
# todo: need to set the EOS language to en, becasuse this assumes it's being run within an English context
# Need to know what that would do if called from pyfa
@@ -591,7 +593,10 @@ def update_db():
# pyfa, we can do it here as a post-processing step
for attr in eos.db.gamedata_session.query(eos.gamedata.Attribute).filter(eos.gamedata.Attribute.ID == 1367).all():
attr.value = 4.0
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(eos.gamedata.Item.name.like('%abyssal%')).all():
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(or_(
eos.gamedata.Item.name.like('%abyssal%'),
eos.gamedata.Item.name.like('%mutated%')
)).all():
item.published = False
for x in [

View File

@@ -118,7 +118,7 @@ from eos.db.gamedata import alphaClones, attribute, category, effect, group, ite
pyfalog.debug('Importing saveddata DB scheme')
# noinspection PyPep8
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \
miscData, mutator, module, override, price, queries, skill, targetProfile, user
miscData, mutatorMod, mutatorDrone, module, override, price, queries, skill, targetProfile, user
pyfalog.debug('Importing gamedata queries')
# noinspection PyPep8

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, String, Table
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, deferred, mapper, relation, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection

View File

@@ -0,0 +1,18 @@
"""
Migration 45
- Drone mutaplasmid support
"""
import sqlalchemy
def upgrade(saveddata_engine):
try:
saveddata_engine.execute("SELECT baseItemID FROM drones LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE drones ADD COLUMN baseItemID INTEGER;")
try:
saveddata_engine.execute("SELECT mutaplasmidID FROM drones LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE drones ADD COLUMN mutaplasmidID INTEGER;")

View File

@@ -1,7 +1,8 @@
__all__ = [
"character",
"fit",
"mutator",
"mutatorMod",
"mutatorDrone",
"module",
"user",
"skill",

View File

@@ -18,27 +18,35 @@
# ===============================================================================
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
from sqlalchemy.orm import mapper, relation
from sqlalchemy.orm import mapper, relation, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection
import datetime
from eos.db import saveddata_meta
from eos.saveddata.drone import Drone
from eos.saveddata.fit import Fit
from eos.saveddata.mutator import MutatorDrone
drones_table = Table("drones", saveddata_meta,
Column("groupID", Integer, primary_key=True),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
Column("itemID", Integer, nullable=False),
Column("baseItemID", Integer, nullable=True),
Column("mutaplasmidID", Integer, nullable=True),
Column("amount", Integer, nullable=False),
Column("amountActive", Integer, nullable=False),
Column("projected", Boolean, default=False),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
Column("projectionRange", Float, nullable=True)
)
Column("projectionRange", Float, nullable=True))
mapper(Drone, drones_table,
properties={
"owner": relation(Fit)
}
)
"ID": synonym("groupID"),
"owner": relation(Fit),
"mutators": relation(
MutatorDrone,
backref="item",
cascade="all,delete-orphan",
collection_class=attribute_mapped_collection('attrID'))})

View File

@@ -18,14 +18,14 @@
# ===============================================================================
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, CheckConstraint, Boolean, DateTime
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm import relation, mapper
from sqlalchemy.orm.collections import attribute_mapped_collection
import datetime
from eos.db import saveddata_meta
from eos.saveddata.module import Module
from eos.saveddata.mutator import Mutator
from eos.saveddata.fit import Fit
from eos.saveddata.mutator import MutatorModule
modules_table = Table("modules", saveddata_meta,
Column("ID", Integer, primary_key=True),
@@ -45,13 +45,12 @@ modules_table = Table("modules", saveddata_meta,
Column("projectionRange", Float, nullable=True),
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
mapper(Module, modules_table,
properties={
"owner": relation(Fit),
"mutators": relation(
Mutator,
backref="module",
MutatorModule,
backref="item",
cascade="all,delete-orphan",
collection_class=attribute_mapped_collection('attrID')
)
})
collection_class=attribute_mapped_collection('attrID'))})

View File

@@ -23,13 +23,14 @@ from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table
from sqlalchemy.orm import mapper
from eos.db import saveddata_meta
from eos.saveddata.mutator import Mutator
from eos.saveddata.mutator import MutatorDrone
mutator_table = Table("mutators", saveddata_meta,
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, index=True),
Column("attrID", Integer, primary_key=True, index=True),
Column("value", Float, nullable=False),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
mutatorDrones_table = Table(
"mutatorsDrones", saveddata_meta,
Column("groupID", Integer, ForeignKey("drones.groupID"), primary_key=True, index=True),
Column("attrID", Integer, primary_key=True, index=True),
Column("value", Float, nullable=False),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
mapper(Mutator, mutator_table)
mapper(MutatorDrone, mutatorDrones_table)

View File

@@ -0,0 +1,36 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import datetime
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table
from sqlalchemy.orm import mapper
from eos.db import saveddata_meta
from eos.saveddata.mutator import MutatorModule
mutatorMods_table = Table(
"mutators", saveddata_meta,
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, index=True),
Column("attrID", Integer, primary_key=True, index=True),
Column("value", Float, nullable=False),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
mapper(MutatorModule, mutatorMods_table)

File diff suppressed because it is too large Load Diff

View File

@@ -567,7 +567,18 @@ class Group(EqBase):
class DynamicItem(EqBase):
pass
@property
def shortName(self):
name = self.item.customName
keywords = ('Decayed', 'Gravid', 'Unstable', 'Radical')
for kw in keywords:
if name.startswith(f'{kw} '):
name = kw
m = re.match('(?P<mutagrade>\S+) (?P<dronetype>\S+) Drone (?P<mutatype>\S+) Mutaplasmid', name)
if m:
name = '{} {}'.format(m.group('mutagrade'), m.group('mutatype'))
return name
class DynamicItemAttribute(EqBase):

View File

@@ -71,30 +71,30 @@ class ItemAttrShortcut:
def getModifiedItemAttr(self, key, default=0):
return_value = self.itemModifiedAttributes.get(key)
return return_value or default
return return_value if return_value is not None else default
def getModifiedItemAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.itemModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value or default
return return_value if return_value is not None else default
def getItemBaseAttrValue(self, key, default=0):
return_value = self.itemModifiedAttributes.getOriginal(key)
return return_value or default
return return_value if return_value is not None else default
class ChargeAttrShortcut:
def getModifiedChargeAttr(self, key, default=0):
return_value = self.chargeModifiedAttributes.get(key)
return return_value or default
return return_value if return_value is not None else default
def getModifiedChargeAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.chargeModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value or default
return return_value if return_value is not None else default
def getChargeBaseAttrValue(self, key, default=0):
return_value = self.chargeModifiedAttributes.getOriginal(key)
return return_value or default
return return_value if return_value is not None else default
class ModifiedAttributeDict(MutableMapping):

View File

@@ -25,6 +25,8 @@ from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.mutatedMixin import MutatedMixin, MutaError
from eos.saveddata.mutator import MutatorDrone
from eos.utils.cycles import CycleInfo
from eos.utils.default import DEFAULT
from eos.utils.stats import DmgTypes, RRTypes
@@ -33,12 +35,13 @@ from eos.utils.stats import DmgTypes, RRTypes
pyfalog = Logger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, MutatedMixin):
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
def __init__(self, item, baseItem=None, mutaplasmid=None):
"""Initialize a drone from the program"""
self.__item = item
self._item = item
self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid)
if self.isInvalid:
raise ValueError("Passed item is not a Drone")
@@ -53,14 +56,19 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@reconstructor
def init(self):
"""Initialize a drone from the database and validate"""
self.__item = None
self._item = None
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
self._item = eos.db.getItem(self.itemID)
if self._item is None:
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return
try:
self._mutaReconstruct()
except MutaError:
return
if self.isInvalid:
pyfalog.error("Item (id: {0}) is not a Drone", self.itemID)
return
@@ -72,15 +80,18 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__charge = None
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__itemModifiedAttributes.original = self._item.attributes
self.__itemModifiedAttributes.overrides = self._item.overrides
self.__chargeModifiedAttributes = ModifiedAttributeDict()
# pheonix todo: check the attribute itself, not the modified. this will always return 0 now.
self._mutaLoadMutators(mutatorClass=MutatorDrone)
self.__itemModifiedAttributes.mutators = self.mutators
chargeID = self.getModifiedItemAttr("entityMissileTypeID", None)
if chargeID is not None:
if chargeID:
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
@@ -96,11 +107,17 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def isInvalid(self):
return self.__item is None or self.__item.category.name != "Drone"
if self._item is None:
return True
if self._item.category.name != "Drone":
return True
if self._mutaIsInvalid:
return True
return False
@property
def item(self):
return self.__item
return self._item
@property
def charge(self):
@@ -112,8 +129,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
cycleTime = self.getModifiedItemAttr("missileLaunchDuration", 0)
else:
for attr in ("speed", "duration", "durationHighisGood"):
cycleTime = self.getModifiedItemAttr(attr, None)
if cycleTime is not None:
cycleTime = self.getModifiedItemAttr(attr)
if cycleTime:
break
if cycleTime is None:
return 0
@@ -220,26 +237,38 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getCycleParameters(self, reloadOverride=None):
cycleTime = self.cycleTime
if cycleTime == 0:
if not cycleTime:
return None
return CycleInfo(self.cycleTime, 0, math.inf, False)
@property
def miningStats(self):
if self.__miningyield is None:
if self.mines is True and self.amountActive > 0:
getter = self.getModifiedItemAttr
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
def miningYPS(self):
if self.__miningYield is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningYield
return self.__miningyield
@property
def miningWPS(self):
if self.__miningWaste is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningWaste
def __calculateMining(self):
if self.mines is True and self.amountActive > 0:
getter = self.getModifiedItemAttr
cycleParams = self.getCycleParameters()
if cycleParams is None:
yps = 0
else:
cycleTime = cycleParams.averageTime
yield_ = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
yps = yield_ / (cycleTime / 1000.0)
wasteChance = max(0, min(1, self.getModifiedItemAttr("miningWasteProbability")))
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
wps = yps * wasteChance * wasteMult
return yps, wps
else:
return 0, 0
@property
def maxRange(self):
@@ -285,7 +314,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def clear(self):
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.itemModifiedAttributes.clear()
self.chargeModifiedAttributes.clear()
@@ -337,10 +367,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect)
def __deepcopy__(self, memo):
copy = Drone(self.item)
copy = Drone(self.item, self.baseItem, self.mutaplasmid)
copy.amount = self.amount
copy.amountActive = self.amountActive
copy.projectionRange = self.projectionRange
self._mutaApplyMutators(mutatorClass=MutatorDrone, targetInstance=copy)
return copy
def rebase(self, item):
@@ -348,16 +379,17 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
amountActive = self.amountActive
projectionRange = self.projectionRange
Drone.__init__(self, item)
Drone.__init__(self, item, self.baseItem, self.mutaplasmid)
self.amount = amount
self.amountActive = amountActive
self.projectionRange = projectionRange
self._mutaApplyMutators(mutatorClass=MutatorDrone)
def fits(self, fit):
fitDroneGroupLimits = set()
for i in range(1, 3):
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i, None)
if groneGrp is not None:
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i)
if groneGrp:
fitDroneGroupLimits.add(int(groneGrp))
if len(fitDroneGroupLimits) == 0:
return True

View File

@@ -139,9 +139,11 @@ class Fit:
self.__weaponVolleyMap = {}
self.__remoteRepMap = {}
self.__minerYield = None
self.__droneYield = None
self.__minerWaste = None
self.__droneWaste = None
self.__droneDps = None
self.__droneVolley = None
self.__droneYield = None
self.__sustainableTank = None
self.__effectiveSustainableTank = None
self.__effectiveTank = None
@@ -155,7 +157,7 @@ class Fit:
self.factorReload = False
self.boostsFits = set()
self.gangBoosts = None
self.ecmProjectedStr = 1
self.__ecmProjectedList = []
self.commandBonuses = {}
def clearFactorReloadDependentData(self):
@@ -365,21 +367,39 @@ class Fit:
@property
def minerYield(self):
if self.__minerYield is None:
self.calculateMiningStats()
self.calculatemining()
return self.__minerYield
@property
def minerWaste(self):
if self.__minerWaste is None:
self.calculatemining()
return self.__minerWaste
@property
def droneYield(self):
if self.__droneYield is None:
self.calculateMiningStats()
self.calculatemining()
return self.__droneYield
@property
def droneWaste(self):
if self.__droneWaste is None:
self.calculatemining()
return self.__droneWaste
@property
def totalYield(self):
return self.droneYield + self.minerYield
@property
def totalWaste(self):
return self.droneWaste + self.minerWaste
@property
def maxTargets(self):
maxTargets = min(self.extraAttributes["maxTargetsLockedFromSkills"],
@@ -411,7 +431,11 @@ class Fit:
@property
def jamChance(self):
return (1 - self.ecmProjectedStr) * 100
sensors = self.scanStrength
retainLockChance = 1
for jamStr in self.__ecmProjectedList:
retainLockChance *= 1 - min(1, jamStr / sensors)
return (1 - retainLockChance) * 100
@property
def maxSpeed(self):
@@ -487,11 +511,13 @@ class Fit:
self.__weaponVolleyMap = {}
self.__remoteRepMap = {}
self.__minerYield = None
self.__droneYield = None
self.__minerWaste = None
self.__droneWaste = None
self.__effectiveSustainableTank = None
self.__sustainableTank = None
self.__droneDps = None
self.__droneVolley = None
self.__droneYield = None
self.__ehp = None
self.__calculated = False
self.__capStable = None
@@ -499,7 +525,7 @@ class Fit:
self.__capUsed = None
self.__capRecharge = None
self.__savedCapSimData.clear()
self.ecmProjectedStr = 1
self.__ecmProjectedList = []
# self.commandBonuses = {}
del self.__calculatedTargets[:]
@@ -562,6 +588,9 @@ class Fit:
if warfareBuffID not in self.commandBonuses or abs(self.commandBonuses[warfareBuffID][1]) < abs(value):
self.commandBonuses[warfareBuffID] = (runTime, value, module, effect)
def addProjectedEcm(self, strength):
self.__ecmProjectedList.append(strength)
def __runCommandBoosts(self, runTime="normal"):
pyfalog.debug("Applying gang boosts for {0}", repr(self))
for warfareBuffID in list(self.commandBonuses.keys()):
@@ -1620,18 +1649,23 @@ class Fit:
else:
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
def calculateMiningStats(self):
def calculatemining(self):
minerYield = 0
minerWaste = 0
droneYield = 0
droneWaste = 0
for mod in self.modules:
minerYield += mod.miningStats
minerYield += mod.miningYPS
minerWaste += mod.miningWPS
for drone in self.drones:
droneYield += drone.miningStats
droneYield += drone.miningYPS
droneWaste += drone.miningWPS
self.__minerYield = minerYield
self.__minerWaste = minerWaste
self.__droneYield = droneYield
self.__droneWaste = droneWaste
def calculateWeaponDmgStats(self, spoolOptions):
weaponVolley = DmgTypes(0, 0, 0, 0)

View File

@@ -27,7 +27,8 @@ from eos.const import FittingHardpoint, FittingModuleState, FittingSlot
from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.citadel import Citadel
from eos.saveddata.mutator import Mutator
from eos.saveddata.mutatedMixin import MutatedMixin, MutaError
from eos.saveddata.mutator import MutatorModule
from eos.utils.cycles import CycleInfo, CycleSequence
from eos.utils.default import DEFAULT
from eos.utils.float import floatUnerr
@@ -61,7 +62,7 @@ ProjectedSystem = {
}
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, MutatedMixin):
"""An instance of this class represents a module together with its charge and modified attributes"""
MINING_ATTRIBUTES = ("miningAmount",)
SYSTEM_GROUPS = (
@@ -72,21 +73,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""Initialize a module from the program"""
self.itemID = item.ID if item is not None else None
self.baseItemID = baseItem.ID if baseItem is not None else None
self.mutaplasmidID = mutaplasmid.ID if mutaplasmid is not None else None
if baseItem is not None:
# we're working with a mutated module, need to get abyssal module loaded with the base attributes
# Note: there may be a better way of doing this, such as a metho on this classe to convert(mutaplamid). This
# will require a bit more research though, considering there has never been a need to "swap" out the item of a Module
# before, and there may be assumptions taken with regards to the item never changing (pre-calculated / cached results, for example)
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
self.__baseItem = baseItem
self.__mutaplasmid = mutaplasmid
else:
self.__item = item
self.__baseItem = baseItem
self.__mutaplasmid = mutaplasmid
self._item = item
self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid)
if item is not None and self.isInvalid:
raise ValueError("Passed item is not a Module")
@@ -101,27 +90,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@reconstructor
def init(self):
"""Initialize a module from the database and validate"""
self.__item = None
self.__baseItem = None
self._item = None
self.__charge = None
self.__mutaplasmid = None
# we need this early if module is invalid and returns early
self.__slot = self.dummySlot
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
self._item = eos.db.getItem(self.itemID)
if self._item is None:
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return
if self.baseItemID:
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
self.__baseItem = eos.db.getItem(self.baseItemID)
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
if self.__baseItem is None:
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
return
try:
self._mutaReconstruct()
except MutaError:
return
if self.isInvalid:
pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
@@ -140,7 +124,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
@@ -149,21 +134,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
self.__slot = self.dummySlot # defaults to None
if self.__item:
self.__itemModifiedAttributes.original = self.__item.attributes
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__slot = self.calculateSlot(self.__item)
# Instantiate / remove mutators if this is a mutated module
if self.__baseItem:
for x in self.mutaplasmid.attributes:
attr = self.item.attributes[x.name]
id = attr.ID
if id not in self.mutators: # create the mutator
Mutator(self, attr, attr.value)
# @todo: remove attributes that are no longer part of the mutaplasmid.
if self._item:
self.__itemModifiedAttributes.original = self._item.attributes
self.__itemModifiedAttributes.overrides = self._item.overrides
self.__hardpoint = self.__calculateHardpoint(self._item)
self.__slot = self.calculateSlot(self._item)
self._mutaLoadMutators(mutatorClass=MutatorModule)
self.__itemModifiedAttributes.mutators = self.mutators
if self.__charge:
@@ -198,28 +175,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# todo: validate baseItem as well if it's set.
if self.isEmpty:
return False
if self.__item is None:
if self._item is None:
return True
if (
self.__item.category.name not in ("Module", "Subsystem", "Structure Module")
and self.__item.group.name not in self.SYSTEM_GROUPS
self._item.category.name not in ("Module", "Subsystem", "Structure Module")
and self._item.group.name not in self.SYSTEM_GROUPS
):
return True
if (
self.__item.category.name == "Structure Module"
and self.__item.group.name == "Quantum Cores"
self._item.category.name == "Structure Module"
and self._item.group.name == "Quantum Cores"
):
return True
if self.item.isAbyssal and not self.isMutated:
return True
if self.isMutated and not self.__mutaplasmid:
if self._mutaIsInvalid:
return True
return False
@property
def isMutated(self):
return self.baseItemID and self.mutaplasmidID
@property
def numCharges(self):
return self.getNumCharges(self.charge)
@@ -401,8 +372,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def falloff(self):
attrs = ("falloffEffectiveness", "falloff", "shipScanFalloff")
for attr in attrs:
falloff = self.getModifiedItemAttr(attr, None)
if falloff is not None:
falloff = self.getModifiedItemAttr(attr)
if falloff:
return falloff
@property
@@ -419,15 +390,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def item(self):
return self.__item if self.__item != 0 else None
@property
def baseItem(self):
return self.__baseItem
@property
def mutaplasmid(self):
return self.__mutaplasmid
return self._item if self._item != 0 else None
@property
def charge(self):
@@ -448,27 +411,41 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes.clear()
@property
def miningStats(self):
if self.__miningyield is None:
if self.isEmpty:
self.__miningyield = 0
else:
if self.state >= FittingModuleState.ACTIVE:
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
"miningAmount") or 0
if volley:
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
else:
self.__miningyield = 0
def miningYPS(self):
if self.__miningYield is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningYield
return self.__miningyield
@property
def miningWPS(self):
if self.__miningWaste is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningWaste
def __calculateMining(self):
if self.isEmpty:
return 0, 0
if self.state >= FittingModuleState.ACTIVE:
yield_ = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr("miningAmount") or 0
if yield_:
cycleParams = self.getCycleParameters()
if cycleParams is None:
yps = 0
else:
cycleTime = cycleParams.averageTime
yps = yield_ / (cycleTime / 1000.0)
else:
yps = 0
else:
yps = 0
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
if self.charge is not None:
wasteChance += self.getModifiedChargeAttr("specializationCrystalMiningWasteProbabilityBonus", 0)
wasteMult *= self.getModifiedChargeAttr("specializationCrystalMiningWastedVolumeMultiplierBonus", 1)
wasteChance = max(0, min(1, wasteChance))
wps = yps * wasteChance * wasteMult
return yps, wps
def isDealingDamage(self, ignoreState=False):
volleyParams = self.getVolleyParameters(ignoreState=ignoreState)
@@ -718,8 +695,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return False
# Check max group fitted
max = self.getModifiedItemAttr("maxGroupFitted", None)
if max is not None:
max = self.getModifiedItemAttr("maxGroupFitted")
if max:
current = 0 # if self.owner != fit else -1 # Disabled, see #1278
for mod in fit.modules:
if (mod.item and mod.item.groupID == self.item.groupID and
@@ -772,7 +749,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# Check if the local module is over it's max limit; if it's not, we're fine
maxGroupOnline = self.getModifiedItemAttr("maxGroupOnline", None)
maxGroupActive = self.getModifiedItemAttr("maxGroupActive", None)
if maxGroupOnline is None and maxGroupActive is None and projectedOnto is None:
if not maxGroupOnline and not maxGroupActive and projectedOnto is None:
return True
# Following is applicable only to local modules, we do not want to limit projected
@@ -788,11 +765,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
currOnline += 1
if mod.state >= FittingModuleState.ACTIVE:
currActive += 1
if maxGroupOnline is not None and currOnline > maxGroupOnline:
if maxGroupOnline and currOnline > maxGroupOnline:
if maxState is None or maxState > FittingModuleState.OFFLINE:
maxState = FittingModuleState.OFFLINE
break
if maxGroupActive is not None and currActive > maxGroupActive:
if maxGroupActive and currActive > maxGroupActive:
if maxState is None or maxState > FittingModuleState.ONLINE:
maxState = FittingModuleState.ONLINE
return True if maxState is None else maxState
@@ -830,7 +807,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
chargeGroup = charge.groupID
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is None:
if not itemChargeGroup:
continue
if itemChargeGroup == chargeGroup:
return True
@@ -841,7 +818,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
validCharges = set()
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is not None:
if itemChargeGroup:
g = eos.db.getGroup(int(itemChargeGroup), eager="items.attributes")
if g is None:
continue
@@ -903,7 +880,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def clear(self):
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
@@ -1098,9 +1076,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
copy.spoolType = self.spoolType
copy.spoolAmount = self.spoolAmount
copy.projectionRange = self.projectionRange
for x in self.mutators.values():
Mutator(copy, x.attribute, x.value)
self._mutaApplyMutators(mutatorClass=MutatorModule, targetInstance=copy)
return copy
@@ -1118,14 +1094,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.spoolType = spoolType
self.spoolAmount = spoolAmount
self.projectionRange = projectionRange
for x in self.mutators.values():
Mutator(self, x.attribute, x.value)
self._mutaApplyMutators(mutatorClass=MutatorModule)
def __repr__(self):
if self.item:
return "Module(ID={}, name={}) at {}".format(
self.item.ID, self.item.name, hex(id(self))
)
return "Module(ID={}, name={}) at {}".format(self.item.ID, self.item.name, hex(id(self)))
else:
return "EmptyModule() at {}".format(hex(id(self)))

View File

@@ -0,0 +1,105 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import eos.db
from logbook import Logger
pyfalog = Logger(__name__)
class MutaError(Exception):
pass
class MutatedMixin:
@property
def isMutated(self):
return bool(self.baseItemID and self.mutaplasmidID)
@property
def baseItem(self):
return self.__baseItem
@property
def mutaplasmid(self):
return self.__mutaplasmid
@property
def fullName(self):
if self.isMutated:
mutaShortName = self.mutaplasmid.shortName
mutaFullName = self.mutaplasmid.item.customName
# Short name can be unavailable for non-english language
if mutaShortName != mutaFullName:
return f'{self.mutaplasmid.shortName} {self.baseItem.customName}'
return self.item.customName
def _mutaInit(self, baseItem, mutaplasmid):
self.baseItemID = baseItem.ID if baseItem is not None else None
self.mutaplasmidID = mutaplasmid.ID if mutaplasmid is not None else None
if baseItem is not None:
# we're working with a mutated module, need to get abyssal module loaded with the base attributes
# Note: there may be a better way of doing this, such as a metho on this classe to convert(mutaplamid). This
# will require a bit more research though, considering there has never been a need to "swap" out the item of a Module
# before, and there may be assumptions taken with regards to the item never changing (pre-calculated / cached results, for example)
self._item = eos.db.getItemWithBaseItemAttribute(self._item.ID, self.baseItemID)
self.__baseItem = baseItem
self.__mutaplasmid = mutaplasmid
else:
self.__baseItem = None
self.__mutaplasmid = None
def _mutaReconstruct(self):
self.__baseItem = None
self.__mutaplasmid = None
if self.baseItemID:
self._item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
self.__baseItem = eos.db.getItem(self.baseItemID)
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
if self.__baseItem is None:
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
raise MutaError
def _mutaLoadMutators(self, mutatorClass):
# Instantiate / remove mutators if this is a mutated module
if self.__baseItem:
for x in self.mutaplasmid.attributes:
attr = self.item.attributes[x.name]
id = attr.ID
if id not in self.mutators: # create the mutator
mutatorClass(self, attr, attr.value)
# @todo: remove attributes that are no longer part of the mutaplasmid.
@property
def _mutaIsInvalid(self):
if self.item.isAbyssal and not self.isMutated:
return True
if self.isMutated and not self.__mutaplasmid:
return True
return False
def _mutaApplyMutators(self, mutatorClass, targetInstance=None):
if targetInstance is None:
targetInstance = self
for x in self.mutators.values():
mutatorClass(targetInstance, x.attribute, x.value)

View File

@@ -27,10 +27,10 @@ from eos.eqBase import EqBase
pyfalog = Logger(__name__)
class Mutator(EqBase):
""" Mutators are the object that represent an attribute override on the module level, in conjunction with
mutaplasmids. Each mutated module, when created, is instantiated with a list of these objects, dictated by the
mutaplasmid that is used on the base module.
class MutatorBase(EqBase):
""" Mutators are the object that represent an attribute override on the eos item level, in conjunction with
mutaplasmids. Each mutated item, when created, is instantiated with a list of these objects, dictated by the
mutaplasmid that is used on the base item.
A note on the different attributes on this object:
* attribute: points to the definition of the attribute from dgmattribs.
@@ -40,13 +40,13 @@ class Mutator(EqBase):
This could probably be cleaned up with smarter relationships, but whatever
"""
def __init__(self, module, attr, value):
# this needs to be above module assignment, as assigning the module will add it to the list and it via
def __init__(self, item, attr, value):
# this needs to be above item assignment, as assigning the item will add it to the list and it via
# relationship and needs this set 4correctly
self.attrID = attr.ID
self.module = module
self.moduleID = module.ID
self.item = item
self.itemID = item.ID
self.__attr = attr
self.build()
@@ -67,20 +67,20 @@ class Mutator(EqBase):
def build(self):
# try...except here to catch orphaned mutators. Pretty rare, only happens so far if hacking the database
# But put it here to remove the module link if it happens, until a better solution can be developed
# But put it here to remove the eos item link if it happens, until a better solution can be developed
try:
# dynamic attribute links to the Mutaplasmids attribute definition for this mutated definition
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
self.dynamicAttribute = next(a for a in self.item.mutaplasmid.attributes if a.attributeID == self.attrID)
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
self.baseAttribute = self.item.baseItem.attributes[self.dynamicAttribute.name]
except (KeyboardInterrupt, SystemExit):
raise
except:
self.module = None
self.item = None
@validates("value")
def validator(self, key, val):
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
""" Validates values as properly falling within the range of the items' Mutaplasmid """
if self.baseValue == 0:
return 0
mod = val / self.baseValue
@@ -99,7 +99,7 @@ class Mutator(EqBase):
# @todo: need to test what happens:
# 1) if an attribute is removed from the EVE database
# 2) if a mutaplasmid does not have the attribute anymore
# 3) if a mutaplasmid does not exist (in eve or on the module's item)
# 3) if a mutaplasmid does not exist (in eve or on the pyfa item's item)
# Can remove invalid ones in a SQLAlchemy collection class... eventually
return self.__attr is None
@@ -139,3 +139,11 @@ class Mutator(EqBase):
@property
def attribute(self):
return self.__attr
class MutatorModule(MutatorBase):
pass
class MutatorDrone(MutatorBase):
pass

View File

@@ -193,11 +193,8 @@ class DroneView(Display):
@staticmethod
def droneKey(drone):
sMkt = Market.getInstance()
groupName = sMkt.getMarketGroupByItem(drone.item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.item.name)
groupName = Market.getInstance().getMarketGroupByItem(drone.item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
def fitChanged(self, event):
event.Skip()

View File

@@ -26,7 +26,7 @@ from gui.builtinContextMenus import itemAmountChange
from gui.builtinContextMenus import itemProjectionRange
from gui.builtinContextMenus import droneSplitStack
from gui.builtinContextMenus import itemVariationChange
from gui.builtinContextMenus import moduleMutations
from gui.builtinContextMenus import itemMutations
from gui.builtinContextMenus import moduleFill
from gui.builtinContextMenus import moduleMutatedExport
from gui.builtinContextMenus import skillAffectors

View File

@@ -5,7 +5,7 @@ from gui import fitCommands as cmd
from gui.contextMenu import ContextMenuUnconditional
from gui.utils.clipboard import fromClipboard
from service.fit import Fit
from service.port.eft import parseAdditions
from service.port.eft import parseAdditions, importGetMutationData, lineIter
_t = wx.GetTranslation
@@ -41,9 +41,12 @@ class AdditionsImport(ContextMenuUnconditional):
def activate(self, callingWindow, fullContext, i):
text = fromClipboard()
items = parseAdditions(text)
lines = list(lineIter(text))
mutaData = importGetMutationData(lines)
text = '\n'.join(lines)
items = parseAdditions(text, mutaData=mutaData)
filterFunc = self.viewSpecMap[self.srcContext][1]
items = [(i.ID, a) for i, a in items if filterFunc(i)]
items = [(i.ID, a, m) for i, a, m in items if filterFunc(i)]
if not items:
return
command = self.viewSpecMap[self.srcContext][2]

View File

@@ -8,7 +8,7 @@ from eos.saveddata.cargo import Cargo as es_Cargo
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter as es_Fighter
from eos.saveddata.fit import Fit as es_Fit
from gui.contextMenu import ContextMenuSingle
from gui.contextMenu import ContextMenuCombined
from service.fit import Fit
# noinspection PyPackageRequirements
@@ -16,12 +16,12 @@ from service.fit import Fit
_t = wx.GetTranslation
class ChangeItemAmount(ContextMenuSingle):
class ChangeItemAmount(ContextMenuCombined):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext, mainItem):
def display(self, callingWindow, srcContext, mainItem, selection):
if srcContext not in ("droneItem", "projectedDrone", "cargoItem", "projectedFit", "fighterItem", "projectedFighter"):
return False
@@ -30,10 +30,12 @@ class ChangeItemAmount(ContextMenuSingle):
return True
def getText(self, callingWindow, itmContext, mainItem):
def getText(self, callingWindow, itmContext, mainItem, selection):
if isinstance(mainItem, es_Cargo):
return _t("Change Selection Quantity")
return _t("Change {0} Quantity").format(itmContext)
def activate(self, callingWindow, fullContext, mainItem, i):
def activate(self, callingWindow, fullContext, mainItem, selection, i):
fitID = self.mainFrame.getActiveFit()
srcContext = fullContext[0]
if isinstance(mainItem, es_Fit):
@@ -56,8 +58,12 @@ class ChangeItemAmount(ContextMenuSingle):
cleanInput = int(float(re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())))
if isinstance(mainItem, es_Cargo):
self.mainFrame.command.Submit(cmd.GuiChangeCargoAmountCommand(
fitID=fitID, itemID=mainItem.itemID, amount=cleanInput))
itemIDs = []
for cargo in selection:
if cargo in fit.cargo:
itemIDs.append(cargo.itemID)
self.mainFrame.command.Submit(cmd.GuiChangeCargosAmountCommand(
fitID=fitID, itemIDs=itemIDs, amount=cleanInput))
elif isinstance(mainItem, Drone):
if srcContext == "projectedDrone":
self.mainFrame.command.Submit(cmd.GuiChangeProjectedDroneAmountCommand(

View File

@@ -1,16 +1,19 @@
# noinspection PyPackageRequirements
import re
# noinspection PyPackageRequirements
import wx
import gui.mainFrame
from gui.contextMenu import ContextMenuSingle
from gui.fitCommands import GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand
from gui.fitCommands import (
GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand,
GuiConvertMutatedLocalDroneCommand, GuiRevertMutatedLocalDroneCommand)
from service.fit import Fit
_t = wx.GetTranslation
class ChangeModuleMutation(ContextMenuSingle):
class ChangeItemMutation(ContextMenuSingle):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
@@ -18,10 +21,10 @@ class ChangeModuleMutation(ContextMenuSingle):
def display(self, callingWindow, srcContext, mainItem):
if srcContext != "fittingModule" or self.mainFrame.getActiveFit() is None:
if srcContext not in ("fittingModule", "droneItem") or self.mainFrame.getActiveFit() is None:
return False
if mainItem is None or mainItem.isEmpty:
if mainItem is None or getattr(mainItem, 'isEmpty', False):
return False
if len(mainItem.item.mutaplasmids) == 0 and not mainItem.isMutated:
@@ -42,24 +45,27 @@ class ChangeModuleMutation(ContextMenuSingle):
menu = rootMenu if msw else sub
for item in mainItem.item.mutaplasmids:
label = item.item.name
for mutaplasmid in mainItem.item.mutaplasmids:
id = ContextMenuSingle.nextID()
self.eventIDs[id] = (item, mainItem)
skillItem = wx.MenuItem(menu, id, label)
menu.Bind(wx.EVT_MENU, self.handleMenu, skillItem)
sub.Append(skillItem)
self.eventIDs[id] = (mutaplasmid, mainItem)
mItem = wx.MenuItem(menu, id, mutaplasmid.shortName)
menu.Bind(wx.EVT_MENU, self.handleMenu, mItem)
sub.Append(mItem)
return sub
def handleMenu(self, event):
mutaplasmid, mod = self.eventIDs[event.Id]
mutaplasmid, item = self.eventIDs[event.Id]
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
if mod in fit.modules:
position = fit.modules.index(mod)
if item in fit.modules:
position = fit.modules.index(item)
self.mainFrame.command.Submit(GuiConvertMutatedLocalModuleCommand(
fitID=fitID, position=position, mutaplasmid=mutaplasmid))
elif item in fit.drones:
position = fit.drones.index(item)
self.mainFrame.command.Submit(GuiConvertMutatedLocalDroneCommand(
fitID=fitID, position=position, mutaplasmid=mutaplasmid))
def activate(self, callingWindow, fullContext, mainItem, i):
fitID = self.mainFrame.getActiveFit()
@@ -68,9 +74,13 @@ class ChangeModuleMutation(ContextMenuSingle):
position = fit.modules.index(mainItem)
self.mainFrame.command.Submit(GuiRevertMutatedLocalModuleCommand(
fitID=fitID, position=position))
elif mainItem in fit.drones:
position = fit.drones.index(mainItem)
self.mainFrame.command.Submit(GuiRevertMutatedLocalDroneCommand(
fitID=fitID, position=position))
def getBitmap(self, callingWindow, context, mainItem):
return None
ChangeModuleMutation.register()
ChangeItemMutation.register()

View File

@@ -1,5 +1,6 @@
# noinspection PyPackageRequirements
from collections import OrderedDict
# noinspection PyPackageRequirements
import wx
import gui.fitCommands as cmd
@@ -25,8 +26,20 @@ class ChangeModuleAmmo(ContextMenuCombined):
'thermal': _t('Thermal'),
'explosive': _t('Explosive'),
'kinetic': _t('Kinetic'),
'mixed': _t('Mixed')
}
'mixed': _t('Mixed')}
self.oreChargeCatTrans = OrderedDict([
('a1', _t('Asteroid Common')),
('a2', _t('Asteroid Uncommon')),
('a3', _t('Asteroid Rare')),
('a4', _t('Asteroid Premium')),
('a5', _t('Asteroid Abyssal')),
('a6', _t('Asteroid Mercoxit')),
('r4', _t('Moon Ubiquitous')),
('r8', _t('Moon Common')),
('r16', _t('Moon Uncommon')),
('r32', _t('Moon Rare')),
('r64', _t('Moon Exceptional')),
('misc', _t('Misc'))])
def display(self, callingWindow, srcContext, mainItem, selection):
if srcContext not in ('fittingModule', 'projectedModule'):
@@ -115,6 +128,24 @@ class ChangeModuleAmmo(ContextMenuCombined):
self._addSeparator(subMenu, _t('More Damage'))
for menuItem in menuItems:
menu.Append(menuItem)
elif modType == 'miner':
menuItems = []
for catHandle, catLabel in self.oreChargeCatTrans.items():
charges = chargeDict.get(catHandle)
if not charges:
continue
if len(charges) == 1:
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
else:
menuItem = wx.MenuItem(menu, wx.ID_ANY, catLabel)
menuItems.append(menuItem)
subMenu = wx.Menu()
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
menuItem.SetSubMenu(subMenu)
for charge in charges:
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
for menuItem in menuItems:
menu.Append(menuItem)
elif modType == 'general':
for charge in chargeDict['general']:
menu.Append(self._addCharge(rootMenu if msw else menu, charge))

View File

@@ -59,7 +59,8 @@ AttrGroupDict = {
"agility",
"droneCapacity",
"droneBandwidth",
"specialOreHoldCapacity",
"generalMiningHoldCapacity",
"specialIceHoldCapacity",
"specialGasHoldCapacity",
"specialMineralHoldCapacity",
"specialSalvageHoldCapacity",

View File

@@ -194,6 +194,7 @@ class ItemParams(wx.Panel):
attrIcon, attrName, currentVal, baseVal = data
attr_item = self.paramList.AppendItem(parent, attrName)
self.paramList.SetItemTextColour(attr_item, wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
self.paramList.SetItemText(attr_item, currentVal, 1)
if self.stuff is not None:
@@ -222,6 +223,7 @@ class ItemParams(wx.Panel):
heading = data.get("label")
header_item = self.paramList.AppendItem(root, heading)
self.paramList.SetItemTextColour(header_item, wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
for attr in data.get("attributes", []):
# Attribute is a "grouped" attr (eg: damage, sensor strengths, etc). Automatically group these into a child item
if attr in GroupedAttributes:
@@ -232,6 +234,7 @@ class ItemParams(wx.Panel):
# create a child item with the groups label
item = self.paramList.AppendItem(header_item, grouping[1])
self.paramList.SetItemTextColour(item, wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
for attr2 in grouping[0]:
# add each attribute in the group
self.AddAttribute(item, attr2)
@@ -256,6 +259,7 @@ class ItemParams(wx.Panel):
# get all attributes in group
item = self.paramList.AppendItem(root, grouping[1])
self.paramList.SetItemTextColour(item, wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
for attr2 in grouping[0]:
self.AddAttribute(item, attr2)
@@ -351,4 +355,5 @@ class ItemParams(wx.Panel):
fvalue = roundDec(value, digits)
else:
fvalue = value
return "%s %s" % (fvalue, unit)
unitSuffix = f' {unit}' if unit is not None else ''
return f'{fvalue}{unitSuffix}'

View File

@@ -25,9 +25,9 @@ class ItemDescription(wx.Panel):
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
# Strip URLs
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
desc = "<body style='background-color: {}; color: {}'>{}</body>".format(
bgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
fgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
desc = "<body bgcolor='{}' text='{}'>{}</body>".format(
bgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
fgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
desc
)

View File

@@ -18,23 +18,22 @@ _t = wx.GetTranslation
class ItemMutatorPanel(wx.Panel):
def __init__(self, parent, mod):
def __init__(self, parent, stuff):
wx.Panel.__init__(self, parent)
self.stuff = mod
self.stuff = stuff
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
mainSizer = wx.BoxSizer(wx.VERTICAL)
headerSizer = wx.BoxSizer(wx.HORIZONTAL)
headerSizer.AddStretchSpacer()
itemIcon = BitmapLoader.getStaticBitmap(mod.item.iconID, self, "icons")
itemIcon = BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons")
if itemIcon is not None:
headerSizer.Add(itemIcon, 0, 0, 0)
mutaIcon = BitmapLoader.getStaticBitmap(mod.mutaplasmid.item.iconID, self, "icons")
mutaIcon = BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons")
if mutaIcon is not None:
headerSizer.Add(mutaIcon, 0, wx.LEFT, 0)
sourceItemShort = "{} {}".format(mod.mutaplasmid.item.name.split(" ")[0], mod.baseItem.name)
sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
sourceItemText = wx.StaticText(self, wx.ID_ANY, stuff.fullName)
font = parent.GetFont()
font.SetWeight(wx.BOLD)
sourceItemText.SetFont(font)
@@ -43,7 +42,7 @@ class ItemMutatorPanel(wx.Panel):
mainSizer.Add(headerSizer, 0, wx.ALL | wx.EXPAND, 5)
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND, 0)
self.mutaList = ItemMutatorList(self, mod)
self.mutaList = ItemMutatorList(self, stuff)
mainSizer.Add(self.mutaList, 1, wx.EXPAND | wx.ALL, 0)
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND, 0)
@@ -68,13 +67,13 @@ class ItemMutatorPanel(wx.Panel):
class ItemMutatorList(wx.ScrolledWindow):
def __init__(self, parent, mod):
def __init__(self, parent, stuff):
wx.ScrolledWindow.__init__(self, parent)
self.SetScrollRate(0, 15)
self.carryingFitID = gui.mainFrame.MainFrame.getInstance().getActiveFit()
self.initialMutations = {}
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
self.mod = mod
self.stuff = stuff
self.timer = None
self.isModified = False
@@ -91,9 +90,8 @@ class ItemMutatorList(wx.ScrolledWindow):
('Damage Control', 'duration'): True,
('Siege Module', 'siegeLocalLogisticsDurationBonus'): False
}
first = True
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
if m.baseValue == 0:
continue
if not first:
@@ -102,10 +100,10 @@ class ItemMutatorList(wx.ScrolledWindow):
self.initialMutations[m.attrID] = m.value
highIsGood = higOverrides.get((mod.item.group.name, m.attribute.name), m.highIsGood)
highIsGood = higOverrides.get((stuff.item.group.name, m.attribute.name), m.highIsGood)
# Format: [raw value, modifier applied to base raw value, display value]
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue))
range1 = (m.minValue, self._simplifyValue(m, m.minValue))
range2 = (m.maxValue, self._simplifyValue(m, m.maxValue))
# minValue/maxValue do not always correspond to min/max, because these are
# just base value multiplied by minMod/maxMod, and in case base is negative
@@ -148,11 +146,11 @@ class ItemMutatorList(wx.ScrolledWindow):
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
worseVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worseRange[0]), rounding='dec')
worseVal = ItemParams.FormatValue(*self._preformatValue(m, worseRange[0]), rounding='dec')
worseText = wx.StaticText(self, wx.ID_ANY, worseVal)
worseText.SetForegroundColour(badColor)
betterVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(betterRange[0]), rounding='dec')
betterVal = ItemParams.FormatValue(*self._preformatValue(m, betterRange[0]), rounding='dec')
betterText = wx.StaticText(self, wx.ID_ANY, betterVal)
betterText.SetForegroundColour(goodColor)
@@ -163,23 +161,38 @@ class ItemMutatorList(wx.ScrolledWindow):
sizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
slider = AttributeSlider(parent=self,
baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue),
baseValue=self._simplifyValue(m, sliderBaseValue),
minValue=displayMinRange[1],
maxValue=displayMaxRange[1],
inverse=displayMaxRange is worseRange)
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
slider.SetValue(self._simplifyValue(m, m.value), False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m
sizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10)
self.SetSizer(sizer)
def _simplifyValue(self, mutator, value):
if mutator.attribute.unit is None:
return value
return mutator.attribute.unit.SimplifyValue(value)
def _complicateValue(self, mutator, value):
if mutator.attribute.unit is None:
return value
return mutator.attribute.unit.ComplicateValue(value)
def _preformatValue(self, mutator, value):
if mutator.attribute.unit is None:
return value, None
return mutator.attribute.unit.PreformatValue(value)
def changeMutatedValue(self, evt):
if evt.AffectsModifiedFlag:
self.isModified = True
m = self.event_mapping[evt.Object]
value = evt.Value
value = m.attribute.unit.ComplicateValue(value)
value = self._complicateValue(m, value)
sFit = Fit.getInstance()
sFit.changeMutatedValuePrelim(m, value)
@@ -198,7 +211,7 @@ class ItemMutatorList(wx.ScrolledWindow):
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
value = sFit.changeMutatedValuePrelim(m, m.baseValue)
value = m.attribute.unit.SimplifyValue(value)
value = self._simplifyValue(m, value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
@@ -208,7 +221,7 @@ class ItemMutatorList(wx.ScrolledWindow):
for slider, m in self.event_mapping.items():
value = random.uniform(m.minValue, m.maxValue)
value = sFit.changeMutatedValuePrelim(m, value)
value = m.attribute.unit.SimplifyValue(value)
value = self._simplifyValue(m, value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
@@ -218,7 +231,7 @@ class ItemMutatorList(wx.ScrolledWindow):
for slider, m in self.event_mapping.items():
if m.attrID in self.initialMutations:
value = sFit.changeMutatedValuePrelim(m, self.initialMutations[m.attrID])
value = m.attribute.unit.SimplifyValue(value)
value = self._simplifyValue(m, value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
@@ -226,25 +239,34 @@ class ItemMutatorList(wx.ScrolledWindow):
# Submit mutation changes
sFit = Fit.getInstance()
fit = sFit.getFit(self.carryingFitID)
if self.mod in fit.modules:
isCurrentMod = self.stuff in fit.modules
isCurrentDrone = self.stuff in fit.drones
if isCurrentMod or isCurrentDrone:
if self.isModified:
currentMutation = {}
for slider, m in self.event_mapping.items():
# Sliders may have more up-to-date info than mutator in case we changed
# value in slider and without confirming it, decided to close window
value = slider.GetValue()
value = m.attribute.unit.ComplicateValue(value)
value = self._complicateValue(m, value)
if value != m.value:
value = sFit.changeMutatedValuePrelim(m, value)
currentMutation[m.attrID] = value
else:
currentMutation = self.initialMutations
mainFrame = gui.mainFrame.MainFrame.getInstance()
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand(
fitID=self.carryingFitID,
position=fit.modules.index(self.mod),
mutation=currentMutation,
oldMutation=self.initialMutations))
if isCurrentMod:
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand(
fitID=self.carryingFitID,
position=fit.modules.index(self.stuff),
mutation=currentMutation,
oldMutation=self.initialMutations))
elif isCurrentDrone:
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalDroneMutationCommand(
fitID=self.carryingFitID,
position=fit.drones.index(self.stuff),
mutation=currentMutation,
oldMutation=self.initialMutations))
for slider in self.event_mapping:
slider.OnWindowClose()

View File

@@ -13,7 +13,12 @@ class ItemTraits(wx.Panel):
self.SetSizer(mainSizer)
self.traits = wx.html.HtmlWindow(self)
self.traits.SetPage(item.traits.display)
bgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
self.traits.SetPage("<body bgcolor='{}' text='{}'>{}</body>".format(
bgcolor.GetAsString(wx.C2S_HTML_SYNTAX),
fgcolor.GetAsString(wx.C2S_HTML_SYNTAX), item.traits.display))
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
self.traits.Bind(wx.EVT_KEY_UP, self.onKeyUp)

View File

@@ -37,118 +37,19 @@ class PFEsiPref(PreferenceView):
rbSizer = wx.BoxSizer(wx.HORIZONTAL)
self.rbMode = wx.RadioBox(panel, -1, _t("Login Authentication Method"), wx.DefaultPosition, wx.DefaultSize,
[_t('Local Server'), _t('Manual')], 1, wx.RA_SPECIFY_COLS)
self.rbMode.SetItemToolTip(0, _t("This options starts a local webserver that the web application will call back to"
self.rbMode.SetItemToolTip(0, _t("This option starts a local webserver that EVE SSO Server will call back to"
" with information about the character login."))
self.rbMode.SetItemToolTip(1, _t("This option prompts users to copy and paste information from the web application "
"to allow for character login. Use this if having issues with the local server."))
self.rbSsoMode = wx.RadioBox(panel, -1, _t("SSO Mode"), wx.DefaultPosition, wx.DefaultSize,
[_t('pyfa.io'), _t('Custom application')], 1, wx.RA_SPECIFY_COLS)
self.rbSsoMode.SetItemToolTip(0, _t("This options routes SSO Logins through pyfa.io, allowing you to easily login "
"without any configuration. When in doubt, use this option."))
self.rbSsoMode.SetItemToolTip(1, _t("This option goes through EVE SSO directly, but requires more configuration. Use "
"this if pyfa.io is blocked for some reason, or if you do not wish to route data throguh pyfa.io."))
self.rbMode.SetItemToolTip(1, _t("This option prompts users to copy and paste information to allow for"
" character login. Use this if having issues with the local server."))
self.rbMode.SetSelection(self.settings.get('loginMode'))
self.rbSsoMode.SetSelection(self.settings.get('ssoMode'))
rbSizer.Add(self.rbSsoMode, 1, wx.ALL, 5)
rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5)
self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange)
self.rbSsoMode.Bind(wx.EVT_RADIOBOX, self.OnSSOChange)
mainSizer.Add(rbSizer, 1, wx.ALL | wx.EXPAND, 0)
detailsTitle = wx.StaticText(panel, wx.ID_ANY, _t("Custom Application"), wx.DefaultPosition, wx.DefaultSize, 0)
detailsTitle.Wrap(-1)
detailsTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(detailsTitle, 0, wx.ALL, 5)
mainSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0,
wx.EXPAND, 5)
fgAddrSizer = wx.FlexGridSizer(2, 2, 0, 0)
fgAddrSizer.AddGrowableCol(1)
fgAddrSizer.SetFlexibleDirection(wx.BOTH)
fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
self.stSetID = wx.StaticText(panel, wx.ID_ANY, _t("Client ID:"), wx.DefaultPosition, wx.DefaultSize, 0)
self.stSetID.Wrap(-1)
fgAddrSizer.Add(self.stSetID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.inputClientID = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition,
wx.DefaultSize, 0)
fgAddrSizer.Add(self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
self.stSetSecret = wx.StaticText(panel, wx.ID_ANY, _t("Client Secret:"), wx.DefaultPosition, wx.DefaultSize, 0)
self.stSetSecret.Wrap(-1)
fgAddrSizer.Add(self.stSetSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.inputClientSecret = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition,
wx.DefaultSize, 0)
fgAddrSizer.Add(self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
self.inputClientID.Bind(wx.EVT_TEXT, self.OnClientDetailChange)
self.inputClientSecret.Bind(wx.EVT_TEXT, self.OnClientDetailChange)
mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5)
# self.stTimout = wx.StaticText(panel, wx.ID_ANY, "Timeout (seconds):", wx.DefaultPosition, wx.DefaultSize, 0)
# self.stTimout.Wrap(-1)
#
# timeoutSizer.Add(self.stTimout, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
# self.intTimeout = IntCtrl(panel, max=300000, limited=True, value=self.settings.get('timeout'))
# timeoutSizer.Add(self.intTimeout, 0, wx.ALL, 5)
# self.intTimeout.Bind(wx.lib.intctrl.EVT_INT, self.OnTimeoutChange)
#
# mainSizer.Add(timeoutSizer, 0, wx.ALL | wx.EXPAND, 0)
# detailsTitle = wx.StaticText(panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0)
# detailsTitle.Wrap(-1)
# detailsTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
#
# mainSizer.Add(detailsTitle, 0, wx.ALL, 5)
# mainSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0,
# wx.EXPAND, 5)
# fgAddrSizer = wx.FlexGridSizer(2, 2, 0, 0)
# fgAddrSizer.AddGrowableCol(1)
# fgAddrSizer.SetFlexibleDirection(wx.BOTH)
# fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
#
# self.stSetID = wx.StaticText(panel, wx.ID_ANY, "Client ID:", wx.DefaultPosition, wx.DefaultSize, 0)
# self.stSetID.Wrap(-1)
# fgAddrSizer.Add(self.stSetID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
#
# self.inputClientID = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition,
# wx.DefaultSize, 0)
#
# fgAddrSizer.Add(self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
#
# self.stSetSecret = wx.StaticText(panel, wx.ID_ANY, "Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0)
# self.stSetSecret.Wrap(-1)
#
# fgAddrSizer.Add(self.stSetSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
#
# self.inputClientSecret = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition,
# wx.DefaultSize, 0)
#
# fgAddrSizer.Add(self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
#
# self.btnApply = wx.Button(panel, wx.ID_ANY, "Save Client Settings", wx.DefaultPosition, wx.DefaultSize, 0)
# self.btnApply.Bind(wx.EVT_BUTTON, self.OnBtnApply)
#
# mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5)
# mainSizer.Add(self.btnApply, 0, wx.ALIGN_RIGHT, 5)
# self.ToggleProxySettings(self.settings.get('loginMode'))
self.ToggleSSOMode(self.settings.get('ssoMode'))
panel.SetSizer(mainSizer)
panel.Layout()
@@ -158,32 +59,6 @@ class PFEsiPref(PreferenceView):
def OnModeChange(self, event):
self.settings.set('loginMode', event.GetInt())
def OnSSOChange(self, event):
self.settings.set('ssoMode', event.GetInt())
self.ToggleSSOMode(event.GetInt())
def ToggleSSOMode(self, mode):
if mode:
self.stSetID.Enable()
self.inputClientID.Enable()
self.stSetSecret.Enable()
self.inputClientSecret.Enable()
self.rbMode.Disable()
else:
self.stSetID.Disable()
self.inputClientID.Disable()
self.stSetSecret.Disable()
self.inputClientSecret.Disable()
self.rbMode.Enable()
def OnClientDetailChange(self, evt):
self.settings.set('clientID', self.inputClientID.GetValue().strip())
self.settings.set('clientSecret', self.inputClientSecret.GetValue().strip())
# sEsi = Esi.getInstance()
# sEsi.delAllCharacters()
#
def getImage(self):
return BitmapLoader.getBitmap("eve", "gui")

View File

@@ -149,6 +149,14 @@ class PFGeneralPref(PreferenceView):
_t('When disabled, reloads charges just in selected modules. Action can be reversed by holding Ctrl or Alt key while changing charge.')))
mainSizer.Add(self.cbReloadAll, 0, wx.ALL | wx.EXPAND, 5)
self.cbExpMutants = wx.CheckBox(panel, wx.ID_ANY, _t("Include more information in names of mutated items"),
wx.DefaultPosition, wx.DefaultSize, 0)
if "wxGTK" not in wx.PlatformInfo:
self.cbExpMutants.SetCursor(helpCursor)
self.cbExpMutants.SetToolTip(wx.ToolTip(
_t('Use short mutaplasmid name and base item name instead of actual item name. Works if EVE data language is set to English.')))
mainSizer.Add(self.cbExpMutants, 0, wx.ALL | wx.EXPAND, 5)
self.rbAddLabels = wx.RadioBox(panel, -1, _t("Extra info in Additions panel tab names"), wx.DefaultPosition, wx.DefaultSize,
[_t("None"), _t("Quantity of active items"), _t("Quantity of all items")], 1, wx.RA_SPECIFY_COLS)
mainSizer.Add(self.rbAddLabels, 0, wx.EXPAND | wx.TOP | wx.RIGHT | wx.BOTTOM, 10)
@@ -169,6 +177,7 @@ class PFGeneralPref(PreferenceView):
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
self.cbShowShipBrowserTooltip.SetValue(self.sFit.serviceFittingOptions["showShipBrowserTooltip"])
self.cbReloadAll.SetValue(self.sFit.serviceFittingOptions["ammoChangeAll"])
self.cbExpMutants.SetValue(self.sFit.serviceFittingOptions["expandedMutantNames"])
self.rbAddLabels.SetSelection(self.sFit.serviceFittingOptions["additionsLabels"])
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
@@ -184,6 +193,7 @@ class PFGeneralPref(PreferenceView):
self.cbOpenFitInNew.Bind(wx.EVT_CHECKBOX, self.onCBOpenFitInNew)
self.cbShowShipBrowserTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowShipBrowserTooltip)
self.cbReloadAll.Bind(wx.EVT_CHECKBOX, self.onCBReloadAll)
self.cbExpMutants.Bind(wx.EVT_CHECKBOX, self.onCBExpMutants)
self.cbRackLabels.Enable(self.sFit.serviceFittingOptions["rackSlots"] or False)
@@ -266,6 +276,11 @@ class PFGeneralPref(PreferenceView):
def onCBReloadAll(self, event):
self.sFit.serviceFittingOptions["ammoChangeAll"] = self.cbReloadAll.GetValue()
def onCBExpMutants(self, event):
self.sFit.serviceFittingOptions["expandedMutantNames"] = self.cbExpMutants.GetValue()
fitID = self.mainFrame.getActiveFit()
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
def OnAddLabelsChange(self, event):
self.sFit.serviceFittingOptions["additionsLabels"] = event.GetInt()
fitID = self.mainFrame.getActiveFit()

View File

@@ -130,21 +130,33 @@ class MiningYieldViewFull(StatsView):
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, 3, 0, 0, "%s m\u00B3/s", None),
("labelFullminingyieldDrone", lambda: fit.droneYield, 3, 0, 0, "%s m\u00B3/s", None),
("labelFullminingyieldTotal", lambda: fit.totalYield, 3, 0, 0, "%s m\u00B3/s", None))
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, lambda: fit.minerWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
("labelFullminingyieldDrone", lambda: fit.droneYield, lambda: fit.droneWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
("labelFullminingyieldTotal", lambda: fit.totalYield, lambda: fit.totalWaste, 3, 0, 0, "{}{} m\u00B3/s", None))
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
def processValue(value):
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = "Mining Yield per second ({0} per hour)".format(formatAmount(value * 3600, 3, 0, 3))
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
return value
counter = 0
for labelName, yieldValue, wasteValue, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
yieldValue = processValue(yieldValue)
wasteValue = processValue(wasteValue)
if self._cachedValues[counter] != (yieldValue, wasteValue):
yps = formatAmount(yieldValue, prec, lowest, highest)
yph = formatAmount(yieldValue * 3600, prec, lowest, highest)
wps = formatAmount(wasteValue, prec, lowest, highest)
wph = formatAmount(wasteValue * 3600, prec, lowest, highest)
wasteSuffix = '\u02b7' if wasteValue > 0 else ''
label.SetLabel(valueFormat.format(yps, wasteSuffix))
tipLines = []
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(yps, yph))
if wasteValue > 0:
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(wps, wph))
label.SetToolTip(wx.ToolTip('\n'.join(tipLines)))
self._cachedValues[counter] = (yieldValue, wasteValue)
counter += 1
self.panel.Layout()
self.headerPanel.Layout()

View File

@@ -119,10 +119,11 @@ class TargetingMiscViewMinimal(StatsView):
("specialMediumShipHoldCapacity", _t("Medium ship hold")),
("specialLargeShipHoldCapacity", _t("Large ship hold")),
("specialIndustrialShipHoldCapacity", _t("Industrial ship hold")),
("specialOreHoldCapacity", _t("Ore hold")),
("generalMiningHoldCapacity", _t("Mining hold")),
("specialIceHoldCapacity", _t("Ice hold")),
("specialGasHoldCapacity", _t("Gas hold")),
("specialMineralHoldCapacity", _t("Mineral hold")),
("specialMaterialBayCapacity", _t("Material bay")),
("specialGasHoldCapacity", _t("Gas hold")),
("specialSalvageHoldCapacity", _t("Salvage hold")),
("specialCommandCenterHoldCapacity", _t("Command center hold")),
("specialPlanetaryCommoditiesHoldCapacity", _t("Planetary goods hold")),
@@ -139,10 +140,11 @@ class TargetingMiscViewMinimal(StatsView):
"specialMediumShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMediumShipHoldCapacity"),
"specialLargeShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialLargeShipHoldCapacity"),
"specialIndustrialShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialIndustrialShipHoldCapacity"),
"specialOreHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialOreHoldCapacity"),
"generalMiningHoldCapacity": lambda: fit.ship.getModifiedItemAttr("generalMiningHoldCapacity"),
"specialIceHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialIceHoldCapacity"),
"specialGasHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialGasHoldCapacity"),
"specialMineralHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMineralHoldCapacity"),
"specialMaterialBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialMaterialBayCapacity"),
"specialGasHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialGasHoldCapacity"),
"specialSalvageHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSalvageHoldCapacity"),
"specialCommandCenterHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialCommandCenterHoldCapacity"),
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),

View File

@@ -80,7 +80,7 @@ class AttributeDisplay(ViewColumn):
else:
attr = mod.getAttribute(self.info.name)
if attr is None:
if not attr:
return ""
if self.info.name == "volume":

View File

@@ -70,7 +70,10 @@ class BaseName(ViewColumn):
stuff = stuff.item
if isinstance(stuff, Drone):
return "%dx %s" % (stuff.amount, stuff.item.name)
if FitSvc.getInstance().serviceFittingOptions["expandedMutantNames"]:
return "%dx %s" % (stuff.amount, stuff.fullName)
else:
return "%dx %s" % (stuff.amount, stuff.item.name)
elif isinstance(stuff, Fighter):
return "%d/%d %s" % \
(stuff.amount, stuff.getModifiedItemAttr("fighterSquadronMaxSize"), stuff.item.name)
@@ -117,7 +120,10 @@ class BaseName(ViewColumn):
if stuff.isEmpty:
return "%s Slot" % FittingSlot(stuff.slot).name.capitalize()
else:
return stuff.item.customName
if FitSvc.getInstance().serviceFittingOptions["expandedMutantNames"]:
return stuff.fullName
else:
return stuff.item.customName
elif isinstance(stuff, Implant):
return stuff.item.name
elif isinstance(stuff, TargetProfile):

View File

@@ -537,14 +537,24 @@ class Miscellanea(ViewColumn):
text = "{0}m".format(formatAmount(optimalSig, 3, 0, 3))
tooltip = "Optimal signature radius"
return text, tooltip
elif itemGroup in ("Frequency Mining Laser", "Strip Miner", "Mining Laser", "Gas Cloud Harvester", "Mining Drone"):
miningAmount = stuff.getModifiedItemAttr("specialtyMiningAmount") or stuff.getModifiedItemAttr("miningAmount")
cycleTime = getattr(stuff, 'cycleTime', stuff.getModifiedItemAttr("duration"))
if not miningAmount or not cycleTime:
elif itemGroup in ("Frequency Mining Laser", "Strip Miner", "Mining Laser", "Gas Cloud Harvester", "Mining Drone", "Gas Cloud Hoarders"):
yps = stuff.miningYPS
if not yps:
return "", None
minePerSec = (float(miningAmount) * 1000 / cycleTime)
text = "{0} m3/s".format(formatAmount(minePerSec, 3, 0, 3))
tooltip = "Mining Yield per second ({0} per hour)".format(formatAmount(minePerSec * 3600, 3, 0, 3))
yph = yps * 3600
wps = stuff.miningWPS
wph = wps * 3600
textParts = []
textParts.append(formatAmount(yps, 3, 0, 3))
tipLines = []
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(
formatAmount(yps, 3, 0, 3), formatAmount(yph, 3, 0, 3)))
if wps > 0:
textParts.append(formatAmount(wps, 3, 0, 3))
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(
formatAmount(wps, 3, 0, 3), formatAmount(wph, 3, 0, 3)))
text = '{} m3/s'.format('+'.join(textParts))
tooltip = '\n'.join(tipLines)
return text, tooltip
elif itemGroup == "Logistic Drone":
rpsData = stuff.getRemoteReps(ignoreState=True)
@@ -679,7 +689,7 @@ class Miscellanea(ViewColumn):
formatAmount(itemArmorResistanceShiftHardenerKin, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
)
tooltip = "Resistances Shifted to Damage Profile:\n{0}% EM | {1}% Therm | {2}% Kin | {3}% Exp".format(
tooltip = "Resistances shifted to damage profile:\n{0}% EM | {1}% Therm | {2}% Kin | {3}% Exp".format(
formatAmount(itemArmorResistanceShiftHardenerEM, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerTherm, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerKin, 3, 0, 3),
@@ -693,6 +703,80 @@ class Miscellanea(ViewColumn):
text = "{}s".format(formatAmount(duration / 1000, 3, 0, 0))
tooltip = "Scan duration"
return text, tooltip
elif itemGroup == "Command Burst":
textSections = []
tooltipSections = []
buffMap = {}
for seq in (1, 2, 3, 4):
buffId = stuff.getModifiedChargeAttr(f'warfareBuff{seq}ID')
if not buffId:
continue
buffValue = stuff.getModifiedItemAttr(f'warfareBuff{seq}Value')
buffMap[buffId] = buffValue
if buffId == 10: # Shield Burst: Shield Harmonizing: Shield Resistance
# minus buff value because ingame shows positive value
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield resistance")
elif buffId == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield RR duration & capacictor use")
elif buffId == 12: # Shield Burst: Shield Extension: Shield HP
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield HP")
elif buffId == 13: # Armor Burst: Armor Energizing: Armor Resistance
# minus buff value because ingame shows positive value
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor resistance")
elif buffId == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor RR duration & capacitor use")
elif buffId == 15: # Armor Burst: Armor Reinforcement: Armor HP
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor HP")
elif buffId == 16: # Information Burst: Sensor Optimization: Scan Resolution
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("scan resolution")
elif buffId == 26: # Information Burst: Sensor Optimization: Targeting Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("targeting range")
elif buffId == 17: # Information Burst: Electronic Superiority: EWAR Range and Strength
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("electronic warfare modules range & strength")
elif buffId == 18: # Information Burst: Electronic Hardening: Sensor Strength
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("sensor strength")
elif buffId == 19: # Information Burst: Electronic Hardening: RSD/RWD Resistance
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("sensor dampener & weapon disruption resistance")
elif buffId == 20: # Skirmish Burst: Evasive Maneuvers: Signature Radius
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("signature radius")
elif buffId == 60: # Skirmish Burst: Evasive Maneuvers: Agility
# minus the buff value because we want Agility as shown ingame, not inertia modifier
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("agility")
elif buffId == 21: # Skirmish Burst: Interdiction Maneuvers: Tackle Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("warp disruption & stasis web range")
elif buffId == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("AB/MWD speed increase")
elif buffId == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining/survey module range")
elif buffId == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining module duration & capacitor use")
elif buffId == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining crystal volatility")
if not textSections:
return '', None
text = ' | '.join(textSections)
tooltip = '{} bonus'.format(' | '.join(tooltipSections))
if tooltip:
tooltip = tooltip[0].capitalize() + tooltip[1:]
return text, tooltip
elif stuff.charge is not None:
chargeGroup = stuff.charge.group.name
if chargeGroup.endswith("Rocket") or chargeGroup.endswith("Missile") or chargeGroup.endswith("Torpedo"):

View File

@@ -42,6 +42,7 @@ from gui.contextMenu import ContextMenu
from gui.utils.clipboard import fromClipboard, toClipboard
from service.character import Character
from service.esi import Esi
from service.esiAccess import APIException
from service.fit import Fit
from service.market import Market
@@ -888,14 +889,7 @@ class APIView(wx.Panel):
def fetchCallback(e=None):
if e:
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
exc_type, exc_value, exc_trace = e
if config.debug:
exc_value = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace))
pyfalog.warn(exc_value)
wx.MessageBox(
_t("Error fetching skill information"),
_t("Error"), wx.ICON_ERROR | wx.STAY_ON_TOP)
SkillFetchExceptionHandler(e)
else:
wx.MessageBox(
_t("Successfully fetched skills"), _t("Success"), wx.ICON_INFORMATION | wx.STAY_ON_TOP)
@@ -926,3 +920,24 @@ class SecStatusDialog(wx.Dialog):
self.Layout()
self.Center(wx.BOTH)
class SkillFetchExceptionHandler:
def __init__(self, e):
from gui.esiFittings import ESIExceptionHandler
exc_type, exc_value, exc_trace = e
if config.debug:
exc_value = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace))
pyfalog.warn(exc_value)
try:
try:
raise exc_value
except APIException as ex:
pyfalog.error(ex)
ESIExceptionHandler(ex)
except Exception as ex:
pyfalog.error(ex)
wx.MessageBox(
_t("Error fetching skill information"),
_t("Error"), wx.ICON_ERROR | wx.STAY_ON_TOP)

View File

@@ -163,15 +163,9 @@ class CharacterSelection(wx.Panel):
if e is None:
self.refreshCharacterList()
else:
from gui.characterEditor import SkillFetchExceptionHandler
pyfalog.warn("Error fetching skill information for character for refreshAPICallback")
exc_type, exc_value, exc_trace = e
if config.debug:
exc_value = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace))
pyfalog.warn(exc_value)
wx.MessageBox(
_t("Error fetching skill information"),
_t("Error"), wx.ICON_ERROR | wx.STAY_ON_TOP)
SkillFetchExceptionHandler(e)
def charChanged(self, event):
fitID = self.mainFrame.getActiveFit()

View File

@@ -9,6 +9,7 @@ import config
import gui.globalEvents as GE
from eos.db import getItem
from eos.saveddata.cargo import Cargo
import gui.mainFrame
from gui.auxWindow import AuxiliaryFrame
from gui.display import Display
from gui.characterEditor import APIView
@@ -132,7 +133,7 @@ class EveFittings(AuxiliaryFrame):
except APIException as ex:
# Can't do this in a finally because then it obscures the message dialog
del waitDialog # noqa: F821
ESIExceptionHandler(self, ex)
ESIExceptionHandler(ex)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as ex:
@@ -149,6 +150,7 @@ class EveFittings(AuxiliaryFrame):
self.mainFrame._openAfterImport(fits)
def deleteFitting(self, event):
self.statusbar.SetStatusText("")
sEsi = Esi.getInstance()
selection = self.fitView.fitSelection
if not selection:
@@ -164,16 +166,26 @@ class EveFittings(AuxiliaryFrame):
if activeChar is None:
return
try:
sEsi.delFitting(activeChar, data['fitting_id'])
# repopulate the fitting list
self.fitTree.populateSkillTree(self.fittings)
self.fitView.update([])
try:
sEsi.delFitting(activeChar, data['fitting_id'])
# repopulate the fitting list
self.fitTree.populateSkillTree(self.fittings)
self.fitView.update([])
except APIException as ex:
pyfalog.error(ex)
self.statusbar.SetStatusText("Failed to delete fit: ESI error {} received - {}".format(ex.status_code, ex.response["error"]))
try:
ESIExceptionHandler(ex)
except:
# don't need to do anything - we should already have error code in the status
pass
except requests.exceptions.ConnectionError:
msg = _t("Connection error, please check your internet connection")
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
def deleteAllFittings(self, event):
self.statusbar.SetStatusText("")
sEsi = Esi.getInstance()
activeChar = self.getActiveCharacter()
if activeChar is None:
@@ -186,20 +198,30 @@ class EveFittings(AuxiliaryFrame):
) as dlg:
if dlg.ShowModal() == wx.ID_YES:
try:
for fit in self.fittings:
sEsi.delFitting(activeChar, fit['fitting_id'])
anyDeleted = True
try:
for fit in self.fittings:
sEsi.delFitting(activeChar, fit['fitting_id'])
anyDeleted = True
except APIException as ex:
pyfalog.error(ex)
if anyDeleted:
msg = "Some fits were not deleted: ESI error {} received - {}".format(ex.status_code,
ex.response["error"])
else:
msg = "Failed to delete fits: ESI error {} received - {}".format(ex.status_code,
ex.response["error"])
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
try:
ESIExceptionHandler(ex)
except:
# don't need to do anything - we should already have error code in the status
pass
except requests.exceptions.ConnectionError:
msg = "Connection error, please check your internet connection"
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
except APIException as ex:
if anyDeleted:
msg = "Some fits were not deleted: ESI error {} received".format(ex.status_code)
else:
msg = "Failed to delete fits: ESI error {} received".format(ex.status_code)
pyfalog.error(msg)
self.statusbar.SetStatusText(msg)
# repopulate the fitting list
self.fitTree.populateSkillTree(self.fittings)
self.fitView.update([])
@@ -222,15 +244,33 @@ class ESIServerExceptionHandler:
class ESIExceptionHandler:
# todo: make this a generate excetpion handler for all calls
def __init__(self, parentWindow, ex):
if ex.response['error'].startswith('Token is not valid') or ex.response['error'] == 'invalid_token': # todo: this seems messy, figure out a better response
def __init__(self, ex):
# raise ex
if ex.response['error'].startswith('Token is not valid') \
or ex.response['error'] == 'invalid_token' \
or ex.response['error'] == 'invalid_grant': # todo: this seems messy, figure out a better response
pyfalog.error(ex)
with wx.MessageDialog(
parentWindow,
gui.mainFrame.MainFrame.getInstance(),
_t("There was an error validating characters' SSO token. Please try "
"logging into the character again to reset the token."),
_t("Invalid Token"),
wx.OK | wx.ICON_ERROR
wx.OK | wx.ICON_ERROR | wx.CANCEL
) as dlg:
dlg.SetOKLabel("Manage ESI Characters")
ret = dlg.ShowModal()
if ret == wx.ID_OK:
SsoCharacterMgmt.openOne(parent=gui.mainFrame.MainFrame.getInstance())
# todo: spawn manage esi characters
pass
elif ex.response['error'].startswith('Timeout contacting'):
pyfalog.error(ex)
with wx.MessageDialog(
gui.mainFrame.MainFrame.getInstance(),
"HTTP %s: %s\n\n" % (ex.status_code, ex.response['error'])
+ _t("The server took too long to response. Please try again in a moment."),
_t("Timeout"),
wx.OK | wx.ICON_ERROR
) as dlg:
dlg.ShowModal()
else:
@@ -335,9 +375,9 @@ class ExportToEve(AuxiliaryFrame):
pyfalog.warning(msg)
self.statusbar.SetStatusText(msg, 1)
return
res = sEsi.postFitting(activeChar, data)
try:
res = sEsi.postFitting(activeChar, data)
res.raise_for_status()
self.statusbar.SetStatusText("", 0)
self.statusbar.SetStatusText(res.reason, 1)
@@ -346,19 +386,19 @@ class ExportToEve(AuxiliaryFrame):
pyfalog.error(msg)
self.statusbar.SetStatusText(_t("ERROR"), 0)
self.statusbar.SetStatusText(msg, 1)
except ESIExportException as ex:
except APIException as ex:
pyfalog.error(ex)
self.statusbar.SetStatusText(_t("ERROR"), 0)
self.statusbar.SetStatusText("{} - {}".format(res.status_code, res.reason), 1)
except APIException as ex:
self.statusbar.SetStatusText("HTTP {} - {}".format(ex.status_code, ex.response["error"]), 1)
try:
ESIExceptionHandler(self, ex)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as ex:
self.statusbar.SetStatusText(_t("ERROR"), 0)
self.statusbar.SetStatusText("{} - {}".format(res.status_code, res.reason), 1)
pyfalog.error(ex)
ESIExceptionHandler(ex)
except:
# don't need to do anything - we should already get the error in ex.response
pass
except Exception as ex:
self.statusbar.SetStatusText(_t("ERROR"), 0)
self.statusbar.SetStatusText("Unknown error", 1)
pyfalog.error(ex)
class SsoCharacterMgmt(AuxiliaryFrame):

View File

@@ -5,7 +5,7 @@ from .gui.booster.remove import GuiRemoveBoostersCommand
from .gui.booster.sideEffectToggleState import GuiToggleBoosterSideEffectStateCommand
from .gui.booster.toggleStates import GuiToggleBoosterStatesCommand
from .gui.cargo.add import GuiAddCargoCommand
from .gui.cargo.changeAmount import GuiChangeCargoAmountCommand
from .gui.cargo.changeAmount import GuiChangeCargosAmountCommand
from .gui.cargo.changeMetas import GuiChangeCargoMetasCommand
from .gui.cargo.imprt import GuiImportCargosCommand
from .gui.cargo.remove import GuiRemoveCargosCommand
@@ -26,8 +26,12 @@ from .gui.itemsRebase import GuiRebaseItemsCommand
from .gui.localDrone.add import GuiAddLocalDroneCommand
from .gui.localDrone.changeAmount import GuiChangeLocalDroneAmountCommand
from .gui.localDrone.changeMetas import GuiChangeLocalDroneMetasCommand
from .gui.localDrone.changeMutation import GuiChangeLocalDroneMutationCommand
from .gui.localDrone.clone import GuiCloneLocalDroneCommand
from .gui.localDrone.imprt import GuiImportLocalDronesCommand
from .gui.localDrone.mutatedConvert import GuiConvertMutatedLocalDroneCommand
from .gui.localDrone.mutatedImport import GuiImportLocalMutatedDroneCommand
from .gui.localDrone.mutatedRevert import GuiRevertMutatedLocalDroneCommand
from .gui.localDrone.remove import GuiRemoveLocalDronesCommand
from .gui.localDrone.stackSplit import GuiSplitLocalDroneStackCommand
from .gui.localDrone.stacksMerge import GuiMergeLocalDroneStacksCommand

View File

@@ -0,0 +1,51 @@
import wx
from logbook import Logger
from service.fit import Fit
pyfalog = Logger(__name__)
class CalcChangeLocalDroneMutationCommand(wx.Command):
def __init__(self, fitID, position, mutation, oldMutation=None):
wx.Command.__init__(self, True, 'Change Local Drone Mutation')
self.fitID = fitID
self.position = position
self.mutation = mutation
self.savedMutation = oldMutation
def Do(self):
pyfalog.debug('Doing changing of local drone mutation at position {} to {} for fit ID {}'.format(
self.position, self.mutation, self.fitID))
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
drone = fit.drones[self.position]
if not drone.isMutated:
return False
if self.savedMutation is None:
self.savedMutation = {}
for mutator in drone.mutators.values():
self.savedMutation[mutator.attrID] = mutator.value
if self.mutation == self.savedMutation:
return False
for mutator in drone.mutators.values():
if mutator.attrID not in self.mutation:
continue
if mutator.value != self.mutation[mutator.attrID]:
mutator.value = self.mutation[mutator.attrID]
return True
def Undo(self):
pyfalog.debug('Undoing changing of local drone mutation at position {} to {} for fit ID {}'.format(
self.position, self.mutation, self.fitID))
cmd = CalcChangeLocalDroneMutationCommand(
fitID=self.fitID,
position=self.position,
mutation=self.savedMutation)
return cmd.Do()

View File

@@ -10,21 +10,26 @@ from gui.fitCommands.calc.cargo.remove import CalcRemoveCargoCommand
from gui.fitCommands.helpers import CargoInfo, InternalCommandHistory
class GuiChangeCargoAmountCommand(wx.Command):
class GuiChangeCargosAmountCommand(wx.Command):
def __init__(self, fitID, itemID, amount):
def __init__(self, fitID, itemIDs, amount):
wx.Command.__init__(self, True, 'Change Cargo Amount')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.itemID = itemID
self.itemIDs = itemIDs
self.amount = amount
def Do(self):
results = []
if self.amount > 0:
cmd = CalcChangeCargoAmountCommand(fitID=self.fitID, cargoInfo=CargoInfo(itemID=self.itemID, amount=self.amount))
for itemID in self.itemIDs:
cmd = CalcChangeCargoAmountCommand(fitID=self.fitID, cargoInfo=CargoInfo(itemID=itemID, amount=self.amount))
results.append(self.internalHistory.submit(cmd))
else:
cmd = CalcRemoveCargoCommand(fitID=self.fitID, cargoInfo=CargoInfo(itemID=self.itemID, amount=math.inf))
success = self.internalHistory.submit(cmd)
for itemID in self.itemIDs:
cmd = CalcRemoveCargoCommand(fitID=self.fitID, cargoInfo=CargoInfo(itemID=itemID, amount=math.inf))
results.append(self.internalHistory.submit(cmd))
success = any(results)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -14,7 +14,7 @@ class GuiImportCargosCommand(wx.Command):
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.cargos = {}
for itemID, amount in cargos:
for itemID, amount, mutation in cargos:
if itemID not in self.cargos:
self.cargos[itemID] = 0
self.cargos[itemID] += amount

View File

@@ -0,0 +1,44 @@
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.calc.drone.localChangeMutation import CalcChangeLocalDroneMutationCommand
from gui.fitCommands.helpers import InternalCommandHistory
from service.fit import Fit
class GuiChangeLocalDroneMutationCommand(wx.Command):
def __init__(self, fitID, position, mutation, oldMutation=None):
wx.Command.__init__(self, True, 'Change Local Drone Mutation')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.position = position
self.mutation = mutation
self.oldMutation = oldMutation
def Do(self):
cmd = CalcChangeLocalDroneMutationCommand(
fitID=self.fitID,
position=self.position,
mutation=self.mutation,
oldMutation=self.oldMutation)
success = self.internalHistory.submit(cmd)
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -14,15 +14,24 @@ class GuiImportLocalDronesCommand(wx.Command):
wx.Command.__init__(self, True, 'Import Local Drones')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.drones = drones
self.droneInfos = []
for itemID, amount, mutation in drones:
if mutation:
mutaplasmid, attrs = mutation
self.droneInfos.append(DroneInfo(
itemID=mutaplasmid.resultingItem.ID,
amount=amount,
amountActive=0,
baseItemID=itemID,
mutaplasmidID=mutaplasmid.ID,
mutations=attrs))
else:
self.droneInfos.append(DroneInfo(itemID=itemID, amount=amount, amountActive=0))
def Do(self):
results = []
for itemID, amount in self.drones:
cmd = CalcAddLocalDroneCommand(
fitID=self.fitID,
droneInfo=DroneInfo(itemID=itemID, amount=amount, amountActive=0),
forceNewStack=True)
for info in self.droneInfos:
cmd = CalcAddLocalDroneCommand(fitID=self.fitID, droneInfo=info, forceNewStack=True)
results.append(self.internalHistory.submit(cmd))
success = any(results)
eos.db.flush()

View File

@@ -0,0 +1,65 @@
import math
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.calc.drone.localAdd import CalcAddLocalDroneCommand
from gui.fitCommands.calc.drone.localRemove import CalcRemoveLocalDroneCommand
from gui.fitCommands.helpers import DroneInfo, InternalCommandHistory
from service.fit import Fit
class GuiConvertMutatedLocalDroneCommand(wx.Command):
def __init__(self, fitID, position, mutaplasmid):
wx.Command.__init__(self, True, 'Convert Local Drone to Mutated')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.position = position
self.itemID = mutaplasmid.resultingItem.ID
self.mutaplasmidID = mutaplasmid.ID
def Do(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
try:
drone = fit.drones[self.position]
except IndexError:
return False
if drone.isMutated:
return False
info = DroneInfo(
amount=drone.amount,
amountActive=drone.amountActive,
itemID=self.itemID,
baseItemID=drone.item.ID,
mutaplasmidID=self.mutaplasmidID,
mutations={})
cmdRemove = CalcRemoveLocalDroneCommand(
fitID=self.fitID,
position=self.position,
amount=math.inf)
cmdAdd = CalcAddLocalDroneCommand(
fitID=self.fitID,
droneInfo=info,
forceNewStack=True,
ignoreRestrictions=True)
success = self.internalHistory.submitBatch(cmdRemove, cmdAdd)
eos.db.flush()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -0,0 +1,44 @@
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.calc.drone.localAdd import CalcAddLocalDroneCommand
from gui.fitCommands.helpers import InternalCommandHistory, DroneInfo
from service.fit import Fit
class GuiImportLocalMutatedDroneCommand(wx.Command):
def __init__(self, fitID, baseItem, mutaplasmid, mutations, amount):
wx.Command.__init__(self, True, 'Import Local Mutated Drone')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.newDroneInfo = DroneInfo(
amount=amount,
amountActive=0,
itemID=mutaplasmid.resultingItem.ID,
baseItemID=baseItem.ID,
mutaplasmidID=mutaplasmid.ID,
mutations=mutations)
def Do(self):
cmd = CalcAddLocalDroneCommand(fitID=self.fitID, droneInfo=self.newDroneInfo, forceNewStack=True)
success = self.internalHistory.submit(cmd)
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -0,0 +1,60 @@
import math
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.calc.drone.localAdd import CalcAddLocalDroneCommand
from gui.fitCommands.calc.drone.localRemove import CalcRemoveLocalDroneCommand
from gui.fitCommands.helpers import DroneInfo, InternalCommandHistory
from service.fit import Fit
class GuiRevertMutatedLocalDroneCommand(wx.Command):
def __init__(self, fitID, position):
wx.Command.__init__(self, True, 'Revert Local Drone from Mutated')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.position = position
def Do(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
try:
drone = fit.drones[self.position]
except IndexError:
return False
if not drone.isMutated:
return False
info = DroneInfo(
amount=drone.amount,
amountActive=drone.amountActive,
itemID=drone.baseItemID)
cmdRemove = CalcRemoveLocalDroneCommand(
fitID=self.fitID,
position=self.position,
amount=math.inf)
cmdAdd = CalcAddLocalDroneCommand(
fitID=self.fitID,
droneInfo=info,
forceNewStack=True,
ignoreRestrictions=True)
success = self.internalHistory.submitBatch(cmdRemove, cmdAdd)
eos.db.flush()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -14,7 +14,7 @@ class GuiImportLocalFightersCommand(wx.Command):
wx.Command.__init__(self, True, 'Import Local Fighters')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.fighters = fighters
self.fighters = [(i, a) for i, a, m in fighters]
def Do(self):
results = []

View File

@@ -21,7 +21,10 @@ class GuiConvertMutatedLocalModuleCommand(wx.Command):
def Do(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
mod = fit.modules[self.position]
try:
mod = fit.modules[self.position]
except IndexError:
return False
if mod.isEmpty:
return False
if mod.isMutated:

View File

@@ -19,7 +19,10 @@ class GuiRevertMutatedLocalModuleCommand(wx.Command):
def Do(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
mod = fit.modules[self.position]
try:
mod = fit.modules[self.position]
except IndexError:
return False
if mod.isEmpty:
return False
if not mod.isMutated:

View File

@@ -158,8 +158,11 @@ class ModuleInfo:
class DroneInfo:
def __init__(self, itemID, amount, amountActive):
def __init__(self, amount, amountActive, itemID, baseItemID=None, mutaplasmidID=None, mutations=None):
self.itemID = itemID
self.baseItemID = baseItemID
self.mutaplasmidID = mutaplasmidID
self.mutations = mutations
self.amount = amount
self.amountActive = amountActive
@@ -170,22 +173,40 @@ class DroneInfo:
info = cls(
itemID=drone.itemID,
amount=drone.amount,
amountActive=drone.amountActive)
amountActive=drone.amountActive,
baseItemID=drone.baseItemID,
mutaplasmidID=drone.mutaplasmidID,
mutations={m.attrID: m.value for m in drone.mutators.values()})
return info
def toDrone(self):
item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
mkt = Market.getInstance()
item = mkt.getItem(self.itemID, eager=('attributes', 'group.category'))
if self.baseItemID and self.mutaplasmidID:
baseItem = mkt.getItem(self.baseItemID, eager=('attributes', 'group.category'))
mutaplasmid = eos.db.getDynamicItem(self.mutaplasmidID)
else:
baseItem = None
mutaplasmid = None
try:
drone = Drone(item)
drone = Drone(item, baseItem=baseItem, mutaplasmid=mutaplasmid)
except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None
if self.mutations is not None:
for attrID, mutator in drone.mutators.items():
if attrID in self.mutations:
mutator.value = self.mutations[attrID]
drone.amount = self.amount
drone.amountActive = self.amountActive
return drone
def __repr__(self):
return makeReprStr(self, ['itemID', 'amount', 'amountActive'])
return makeReprStr(self, [
'itemID', 'amount', 'amountActive',
'baseItemID', 'mutaplasmidID', 'mutations'])
class FighterInfo:

View File

@@ -22,6 +22,7 @@ import wx
import config
import gui.mainFrame
from eos.saveddata.drone import Drone
from eos.saveddata.module import Module
from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
@@ -165,7 +166,7 @@ class ItemStatsContainer(wx.Panel):
self.traits = ItemTraits(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.traits, _t("Traits"))
if isinstance(stuff, Module) and stuff.isMutated:
if isinstance(stuff, (Module, Drone)) and stuff.isMutated:
self.mutator = ItemMutatorPanel(self.nbContainer, stuff)
self.nbContainer.AddPage(self.mutator, _t("Mutations"))

View File

@@ -762,28 +762,33 @@ class MainFrame(wx.Frame):
if importType == "FittingItem":
baseItem, mutaplasmidItem, mutations = importData[0]
if mutaplasmidItem:
self.command.Submit(cmd.GuiImportLocalMutatedModuleCommand(activeFit, baseItem, mutaplasmidItem, mutations))
if baseItem.isDrone:
self.command.Submit(cmd.GuiImportLocalMutatedDroneCommand(
activeFit, baseItem, mutaplasmidItem, mutations, amount=1))
else:
self.command.Submit(cmd.GuiImportLocalMutatedModuleCommand(
activeFit, baseItem, mutaplasmidItem, mutations))
else:
self.command.Submit(cmd.GuiAddLocalModuleCommand(activeFit, baseItem.ID))
return
if importType == "AdditionsDrones":
if self.command.Submit(cmd.GuiImportLocalDronesCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
if self.command.Submit(cmd.GuiImportLocalDronesCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])):
self.additionsPane.select("Drones")
return
if importType == "AdditionsFighters":
if self.command.Submit(cmd.GuiImportLocalFightersCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
if self.command.Submit(cmd.GuiImportLocalFightersCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])):
self.additionsPane.select("Fighters")
return
if importType == "AdditionsImplants":
if self.command.Submit(cmd.GuiImportImplantsCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
if self.command.Submit(cmd.GuiImportImplantsCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])):
self.additionsPane.select("Implants")
return
if importType == "AdditionsBoosters":
if self.command.Submit(cmd.GuiImportBoostersCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
if self.command.Submit(cmd.GuiImportBoostersCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])):
self.additionsPane.select("Boosters")
return
if importType == "AdditionsCargo":
if self.command.Submit(cmd.GuiImportCargosCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
if self.command.Submit(cmd.GuiImportCargosCommand(activeFit, [(i.ID, a, m) for i, a, m in importData[0]])):
self.additionsPane.select("Cargo")
return
except (KeyboardInterrupt, SystemExit):

View File

@@ -38,7 +38,7 @@ class SsoLogin(wx.Dialog):
from service.esi import Esi
self.sEsi = Esi.getInstance()
uri = self.sEsi.getLoginURI(None)
uri = self.sEsi.get_login_uri(None)
webbrowser.open(uri)
@@ -53,7 +53,7 @@ class SsoLoginServer(wx.Dialog):
self.sEsi = Esi.getInstance()
serverAddr = self.sEsi.startServer(port)
uri = self.sEsi.getLoginURI(serverAddr)
uri = self.sEsi.get_login_uri(serverAddr)
bSizer1 = wx.BoxSizer(wx.VERTICAL)
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Some files were not shown because too many files have changed in this diff Show More