Compare commits
217 Commits
v2.19.0
...
v2.25.1dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a317dab9a8 | ||
|
|
db6d8b93e9 | ||
|
|
7d585c1a62 | ||
|
|
c45e84470e | ||
|
|
ab3b40e136 | ||
|
|
b566a8bfa6 | ||
|
|
ccb395d592 | ||
|
|
3d039724c9 | ||
|
|
d0e7e7eed5 | ||
|
|
a387bc8d09 | ||
|
|
94344bd432 | ||
|
|
a976fb33f0 | ||
|
|
f850fdf0d5 | ||
|
|
0fcbedba45 | ||
|
|
5707bddacd | ||
|
|
8ed9257dfa | ||
|
|
056ae590cf | ||
|
|
7733fd38c2 | ||
|
|
fef78c971f | ||
|
|
e52ceacdb9 | ||
|
|
85b2d7af8d | ||
|
|
3e658a31bb | ||
|
|
0d2a4d4d44 | ||
|
|
bc2cdcdea7 | ||
|
|
30ad554cc0 | ||
|
|
4a85fd5d1b | ||
|
|
4d8dbe74bd | ||
|
|
016f2b44ff | ||
|
|
676794baed | ||
|
|
ca488089fd | ||
|
|
96f9b9a719 | ||
|
|
508572e08b | ||
|
|
f91e0b2e23 | ||
|
|
93ae9e0891 | ||
|
|
2ed5dbc3c7 | ||
|
|
bca8ba3114 | ||
|
|
174ac97682 | ||
|
|
2d9e873d42 | ||
|
|
33377357f6 | ||
|
|
3ee0ee7e40 | ||
|
|
2f02747b29 | ||
|
|
78b176a135 | ||
|
|
52063beea9 | ||
|
|
03a55d94e9 | ||
|
|
db256f57d1 | ||
|
|
8de6d78be0 | ||
|
|
902a00d37d | ||
|
|
523cb1467e | ||
|
|
f75de70d79 | ||
|
|
4b635f4d21 | ||
|
|
63632e09b3 | ||
|
|
df78eb5781 | ||
|
|
c1a5828d6b | ||
|
|
5377210b89 | ||
|
|
1936255f2c | ||
|
|
b3e5763cfc | ||
|
|
7442f315c9 | ||
|
|
433c9555bf | ||
|
|
d1345fc71e | ||
|
|
31cae0e54b | ||
|
|
b2cc3ae600 | ||
|
|
62d1d6a06d | ||
|
|
83fa567321 | ||
|
|
9b315b5870 | ||
|
|
3094fd32fc | ||
|
|
c3f1824a84 | ||
|
|
b558ae3810 | ||
|
|
c38f05902a | ||
|
|
181e1e1e30 | ||
|
|
3fc26254ae | ||
|
|
ca8822c455 | ||
|
|
4c55d32086 | ||
|
|
c13817708e | ||
|
|
39cab4be72 | ||
|
|
f80473d073 | ||
|
|
0d9be1b174 | ||
|
|
83b0b3ff0e | ||
|
|
c4b490907d | ||
|
|
f89fe4d5d7 | ||
|
|
81dced03bf | ||
|
|
27d3bea8d8 | ||
|
|
7eaeea325d | ||
|
|
9695526352 | ||
|
|
4f35bc38db | ||
|
|
d9c7c1a0fd | ||
|
|
a94e66e5ed | ||
|
|
e9c41c6439 | ||
|
|
b24ceb95f5 | ||
|
|
6ba6e0f69b | ||
|
|
9aca49e539 | ||
|
|
01f1cdb175 | ||
|
|
c1356906bb | ||
|
|
8229537e5f | ||
|
|
6cfa2161ec | ||
|
|
fa3cf46786 | ||
|
|
ac40dfd018 | ||
|
|
4c32d559bb | ||
|
|
02ee12586f | ||
|
|
496f15a83e | ||
|
|
acf6af8f95 | ||
|
|
225b00b9ff | ||
|
|
7f2b7415ea | ||
|
|
d1d9ae4dac | ||
|
|
ac7ccfb6a3 | ||
|
|
05c2c41762 | ||
|
|
10f5133282 | ||
|
|
42658a8167 | ||
|
|
6dbf38dbb4 | ||
|
|
64306e2366 | ||
|
|
f259df85cd | ||
|
|
1090589cb2 | ||
|
|
29e09fcdda | ||
|
|
36fd4aeaff | ||
|
|
c46a59e3b1 | ||
|
|
8a3a21972a | ||
|
|
6839669e04 | ||
|
|
5645e20505 | ||
|
|
15fe8f6261 | ||
|
|
855fafa94d | ||
|
|
4e10335ae7 | ||
|
|
21ea9ce579 | ||
|
|
ea07bbf4f9 | ||
|
|
8eed6fbe21 | ||
|
|
0859f2fbe9 | ||
|
|
71ba33edeb | ||
|
|
ce80d92b35 | ||
|
|
f17cf9b736 | ||
|
|
98579c774b | ||
|
|
509fa279e7 | ||
|
|
091ee87761 | ||
|
|
c0c20cc92e | ||
|
|
1341f7bca1 | ||
|
|
fe93db1d4b | ||
|
|
5db97ea773 | ||
|
|
1758e4f320 | ||
|
|
1a897c0419 | ||
|
|
32db3e3179 | ||
|
|
d830a8957a | ||
|
|
652ea48223 | ||
|
|
8c25b2b8f5 | ||
|
|
db4c56be8e | ||
|
|
f3bcffe2f9 | ||
|
|
bc5786d099 | ||
|
|
5959fe5daf | ||
|
|
649d338bb1 | ||
|
|
dcb058a718 | ||
|
|
1772bb5e7f | ||
|
|
30bd0adb06 | ||
|
|
44dfcf771c | ||
|
|
a1f8a7a930 | ||
|
|
b22887dfad | ||
|
|
28137fa3f4 | ||
|
|
9cbdc6055d | ||
|
|
fc93c61fcf | ||
|
|
3fa2e7ebd1 | ||
|
|
818628da0c | ||
|
|
adf90a8263 | ||
|
|
362923ac64 | ||
|
|
7d73838ce1 | ||
|
|
b3278ca9ec | ||
|
|
5707914ad5 | ||
|
|
9b697b24d8 | ||
|
|
098b088da6 | ||
|
|
14d62f31e1 | ||
|
|
e7b1f55d08 | ||
|
|
8f501896a1 | ||
|
|
befcb9b874 | ||
|
|
c70afa9a4c | ||
|
|
029c7dd4c2 | ||
|
|
1f83dba1ac | ||
|
|
6eae3405fd | ||
|
|
c0e1d9e4de | ||
|
|
7c0bd7aa88 | ||
|
|
394583584c | ||
|
|
4112e2aa6b | ||
|
|
aa19e0da72 | ||
|
|
bba9be1598 | ||
|
|
aec5a46452 | ||
|
|
588f9a4490 | ||
|
|
4ba948c6f9 | ||
|
|
eacc97160b | ||
|
|
78423f67dd | ||
|
|
61e32d0b1b | ||
|
|
bf04d26a7b | ||
|
|
2217aff5ab | ||
|
|
400d0aaa22 | ||
|
|
bfc928934c | ||
|
|
395c17270e | ||
|
|
1b40467775 | ||
|
|
27e3e53868 | ||
|
|
00b9884c68 | ||
|
|
b05bd04801 | ||
|
|
7ba5585b83 | ||
|
|
9386ba3fb9 | ||
|
|
1321e70035 | ||
|
|
e2d943b0b0 | ||
|
|
424b769ba9 | ||
|
|
3671f10c9c | ||
|
|
935ecd0ea7 | ||
|
|
92e15ece72 | ||
|
|
a5c1875a29 | ||
|
|
9146c0f2c6 | ||
|
|
a917207e07 | ||
|
|
c21b92945f | ||
|
|
049bd10797 | ||
|
|
b486276167 | ||
|
|
e4df215e47 | ||
|
|
33cb332978 | ||
|
|
7c8f4d51bb | ||
|
|
66bf7b3dc6 | ||
|
|
8bb1d43d0c | ||
|
|
3d70ca941c | ||
|
|
04a1e95730 | ||
|
|
9fddb64ef9 | ||
|
|
e883035120 | ||
|
|
4d2da8e52e | ||
|
|
c1df335f08 |
43
db_update.py
@@ -33,7 +33,7 @@ DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
|
||||
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
GAMEDATA_SCHEMA_VERSION = 3
|
||||
GAMEDATA_SCHEMA_VERSION = 4
|
||||
|
||||
|
||||
def db_needs_update():
|
||||
@@ -122,9 +122,13 @@ def update_db():
|
||||
if (
|
||||
# Apparently people really want Civilian modules available
|
||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
||||
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor')
|
||||
row['typeName'] == 'Capsule' or
|
||||
row['groupID'] == 4033 # destructible effect beacons
|
||||
):
|
||||
row['published'] = True
|
||||
# Nearly useless and clutter search results too much
|
||||
elif row['typeName'].startswith('Limited Synth '):
|
||||
row['published'] = False
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
@@ -136,11 +140,10 @@ def update_db():
|
||||
row['typeID'] in (41549, 41548, 41551, 41550) or
|
||||
# Abyssal weather (environment)
|
||||
row['groupID'] in (
|
||||
1882,
|
||||
1975,
|
||||
1971,
|
||||
# the "container" for the abyssal environments
|
||||
1983)
|
||||
1882,
|
||||
1975,
|
||||
1971,
|
||||
1983) # the "container" for the abyssal environments
|
||||
):
|
||||
newData.append(row)
|
||||
|
||||
@@ -168,16 +171,26 @@ def update_db():
|
||||
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||
newData = []
|
||||
for row in eveTypesData:
|
||||
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
||||
if attrName in row:
|
||||
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
||||
seenKeys = set()
|
||||
|
||||
def checkKey(key):
|
||||
if key in seenKeys:
|
||||
return False
|
||||
seenKeys.add(key)
|
||||
return True
|
||||
|
||||
for typeData in data:
|
||||
if typeData['typeID'] not in eveTypeIds:
|
||||
continue
|
||||
for row in typeData.get('dogmaAttributes', ()):
|
||||
row['typeID'] = typeData['typeID']
|
||||
newData.append(row)
|
||||
if checkKey((row['typeID'], row['attributeID'])):
|
||||
newData.append(row)
|
||||
for row in eveTypesData:
|
||||
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
||||
if attrName in row and checkKey((row['typeID'], attrId)):
|
||||
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
||||
|
||||
_addRows(newData, eos.gamedata.Attribute)
|
||||
return newData
|
||||
|
||||
@@ -315,8 +328,8 @@ def update_db():
|
||||
|
||||
def composeReqSkills(raw):
|
||||
reqSkills = {}
|
||||
for skillTypeID, skillLevels in raw.items():
|
||||
reqSkills[int(skillTypeID)] = skillLevels[0]
|
||||
for skillTypeID, skillLevel in raw.items():
|
||||
reqSkills[int(skillTypeID)] = skillLevel
|
||||
return reqSkills
|
||||
|
||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||
@@ -472,7 +485,7 @@ def update_db():
|
||||
continue
|
||||
typeName = row.get('typeName', '')
|
||||
# Regular sets matching
|
||||
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName)
|
||||
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
|
||||
if m:
|
||||
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
|
||||
# Special set matching
|
||||
|
||||
@@ -30,7 +30,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'pkg_resources.py2_warn' # issue 2156
|
||||
]
|
||||
|
||||
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||
|
||||
@@ -29,7 +29,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'pkg_resources.py2_warn' # issue 2156
|
||||
]
|
||||
|
||||
# Walk directories that do dynamic importing
|
||||
|
||||
@@ -17,24 +17,37 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import MetaData, create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
|
||||
from . import migration
|
||||
from eos import config
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
pyfalog.info("Initializing database")
|
||||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
||||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
||||
|
||||
|
||||
class ReadOnlyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def re_fn(expr, item):
|
||||
try:
|
||||
reg = re.compile(expr, re.IGNORECASE)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
return reg.search(item) is not None
|
||||
|
||||
|
||||
pyfalog.debug('Initializing gamedata')
|
||||
gamedata_connectionstring = config.gamedata_connectionstring
|
||||
if callable(gamedata_connectionstring):
|
||||
@@ -42,9 +55,26 @@ if callable(gamedata_connectionstring):
|
||||
else:
|
||||
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
|
||||
|
||||
|
||||
@event.listens_for(gamedata_engine, 'connect')
|
||||
def create_functions(dbapi_connection, connection_record):
|
||||
dbapi_connection.create_function('regexp', 2, re_fn)
|
||||
|
||||
|
||||
gamedata_meta = MetaData()
|
||||
gamedata_meta.bind = gamedata_engine
|
||||
gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)()
|
||||
GamedataSession = scoped_session(sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False))
|
||||
gamedata_session = GamedataSession()
|
||||
|
||||
gamedata_sessions = {threading.get_ident(): gamedata_session}
|
||||
|
||||
|
||||
def get_gamedata_session():
|
||||
thread_id = threading.get_ident()
|
||||
if thread_id not in gamedata_sessions:
|
||||
gamedata_sessions[thread_id] = GamedataSession()
|
||||
return gamedata_sessions[thread_id]
|
||||
|
||||
|
||||
pyfalog.debug('Getting gamedata version')
|
||||
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
|
||||
|
||||
@@ -33,9 +33,6 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("description", String),
|
||||
Column("raceID", Integer),
|
||||
Column("factionID", Integer),
|
||||
Column("volume", Float),
|
||||
Column("mass", Float),
|
||||
Column("capacity", Float),
|
||||
Column("published", Boolean),
|
||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||
Column("iconID", Integer),
|
||||
|
||||
@@ -22,7 +22,7 @@ from sqlalchemy.orm import aliased, exc, join
|
||||
from sqlalchemy.sql import and_, or_, select
|
||||
|
||||
import eos.config
|
||||
from eos.db import gamedata_session
|
||||
from eos.db import get_gamedata_session
|
||||
from eos.db.gamedata.item import items_table
|
||||
from eos.db.gamedata.group import groups_table
|
||||
from eos.db.util import processEager, processWhere
|
||||
@@ -64,7 +64,7 @@ else:
|
||||
return deco
|
||||
|
||||
|
||||
def sqlizeString(line):
|
||||
def sqlizeNormalString(line):
|
||||
# Escape backslashes first, as they will be as escape symbol in queries
|
||||
# Then escape percent and underscore signs
|
||||
# Finally, replace generic wildcards with sql-style wildcards
|
||||
@@ -79,29 +79,39 @@ itemNameMap = {}
|
||||
def getItem(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(lookfor)
|
||||
item = get_gamedata_session().query(Item).get(lookfor)
|
||||
else:
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in itemNameMap:
|
||||
id = itemNameMap[lookfor]
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(id)
|
||||
item = get_gamedata_session().query(Item).get(id)
|
||||
else:
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
||||
else:
|
||||
# Item names are unique, so we can use first() instead of one()
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
||||
if item is not None:
|
||||
itemNameMap[lookfor] = item.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return item
|
||||
|
||||
@cachedQuery(1, "itemIDs")
|
||||
def getItems(itemIDs, eager=None):
|
||||
if not isinstance(itemIDs, (tuple, list, set)) or not all(isinstance(t, int) for t in itemIDs):
|
||||
raise TypeError("Need iterable of integers as argument")
|
||||
if eager is None:
|
||||
items = get_gamedata_session().query(Item).filter(Item.ID.in_(itemIDs)).all()
|
||||
else:
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID.in_(itemIDs)).all()
|
||||
return items
|
||||
|
||||
|
||||
def getMutaplasmid(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -109,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
|
||||
|
||||
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||
# A lot of this is described in more detail in #1597
|
||||
item = gamedata_session.query(Item).get(lookfor)
|
||||
item = get_gamedata_session().query(Item).get(lookfor)
|
||||
base = getItem(baseItemID)
|
||||
|
||||
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
||||
@@ -125,7 +135,7 @@ def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
|
||||
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
|
||||
# we want to generate a completely new object to work with
|
||||
gamedata_session.expunge(item)
|
||||
get_gamedata_session().expunge(item)
|
||||
return item
|
||||
|
||||
|
||||
@@ -148,7 +158,7 @@ def getItems(lookfor, eager=None):
|
||||
|
||||
if len(toGet) > 0:
|
||||
# Get items that aren't currently cached, and store them in the cache
|
||||
items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all()
|
||||
items = get_gamedata_session().query(Item).filter(Item.ID.in_(toGet)).all()
|
||||
for item in items:
|
||||
cache[(item.ID, None)] = item
|
||||
results += items
|
||||
@@ -162,9 +172,9 @@ def getItems(lookfor, eager=None):
|
||||
def getAlphaClone(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(AlphaClone).get(lookfor)
|
||||
item = get_gamedata_session().query(AlphaClone).get(lookfor)
|
||||
else:
|
||||
item = gamedata_session.query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -172,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
|
||||
|
||||
def getAlphaCloneList(eager=None):
|
||||
eager = processEager(eager)
|
||||
clones = gamedata_session.query(AlphaClone).options(*eager).all()
|
||||
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
|
||||
return clones
|
||||
|
||||
|
||||
@@ -183,19 +193,19 @@ groupNameMap = {}
|
||||
def getGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(lookfor)
|
||||
group = get_gamedata_session().query(Group).get(lookfor)
|
||||
else:
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in groupNameMap:
|
||||
id = groupNameMap[lookfor]
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(id)
|
||||
group = get_gamedata_session().query(Group).get(id)
|
||||
else:
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
||||
else:
|
||||
# Group names are unique, so we can use first() instead of one()
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
||||
if group is not None:
|
||||
groupNameMap[lookfor] = group.ID
|
||||
else:
|
||||
@@ -210,21 +220,21 @@ categoryNameMap = {}
|
||||
def getCategory(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(lookfor)
|
||||
category = get_gamedata_session().query(Category).get(lookfor)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in categoryNameMap:
|
||||
id = categoryNameMap[lookfor]
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(id)
|
||||
category = get_gamedata_session().query(Category).get(id)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == id).first()
|
||||
else:
|
||||
# Category names are unique, so we can use first() instead of one()
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.name == lookfor).first()
|
||||
if category is not None:
|
||||
categoryNameMap[lookfor] = category.ID
|
||||
@@ -240,21 +250,21 @@ metaGroupNameMap = {}
|
||||
def getMetaGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in metaGroupNameMap:
|
||||
id = metaGroupNameMap[lookfor]
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(id)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == id).first()
|
||||
else:
|
||||
# MetaGroup names are unique, so we can use first() instead of one()
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.name == lookfor).first()
|
||||
if metaGroup is not None:
|
||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||
@@ -264,16 +274,16 @@ def getMetaGroup(lookfor, eager=None):
|
||||
|
||||
|
||||
def getMetaGroups():
|
||||
return gamedata_session.query(MetaGroup).all()
|
||||
return get_gamedata_session().query(MetaGroup).all()
|
||||
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getMarketGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
|
||||
else:
|
||||
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
|
||||
MarketGroup.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
@@ -285,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
|
||||
addedIds = set(rootNodeIds)
|
||||
while addedIds:
|
||||
allIds.update(addedIds)
|
||||
addedIds = {mg.ID for mg in gamedata_session.query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||
addedIds = {mg.ID for mg in get_gamedata_session().query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||
return allIds
|
||||
|
||||
|
||||
@@ -299,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
|
||||
raise TypeError("Need integer or string as argument")
|
||||
|
||||
filter = processWhere(filter, where)
|
||||
return gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
||||
return get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
||||
filter).all()
|
||||
|
||||
|
||||
@@ -314,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
if not hasattr(join, "__iter__"):
|
||||
join = (join,)
|
||||
|
||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(*join)
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||
for token in nameLike.split(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
||||
else:
|
||||
@@ -325,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
return items
|
||||
|
||||
|
||||
@cachedQuery(3, "tokens", "where", "join")
|
||||
def searchItemsRegex(tokens, where=None, join=None, eager=None):
|
||||
if not isinstance(tokens, (tuple, list)) or not all(isinstance(t, str) for t in tokens):
|
||||
raise TypeError("Need tuple or list of strings as argument")
|
||||
|
||||
if join is None:
|
||||
join = tuple()
|
||||
|
||||
if not hasattr(join, "__iter__"):
|
||||
join = (join,)
|
||||
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||
for token in tokens:
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.op('regexp')(token), where))
|
||||
else:
|
||||
items = items.filter(Item.name.op('regexp')(token))
|
||||
items = items.limit(100).all()
|
||||
return items
|
||||
|
||||
|
||||
@cachedQuery(3, "where", "nameLike", "join")
|
||||
def searchSkills(nameLike, where=None, eager=None):
|
||||
if not isinstance(nameLike, str):
|
||||
raise TypeError("Need string as argument")
|
||||
|
||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
||||
for token in nameLike.split(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
||||
else:
|
||||
@@ -352,7 +383,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
|
||||
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||
filter = processWhere(itemfilter, where)
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
vars = get_gamedata_session().query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
|
||||
if vars:
|
||||
return vars
|
||||
@@ -360,7 +391,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
||||
filter = processWhere(itemfilter, where)
|
||||
joinon = items_table.c.groupID == groups_table.c.groupID
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
||||
vars = get_gamedata_session().query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
||||
filter).all()
|
||||
|
||||
return vars
|
||||
@@ -375,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
try:
|
||||
result = gamedata_session.query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
||||
result = get_gamedata_session().query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
||||
except exc.NoResultFound:
|
||||
result = None
|
||||
return result
|
||||
@@ -384,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
@cachedQuery(1, "field")
|
||||
def getMetaData(field):
|
||||
if isinstance(field, str):
|
||||
data = gamedata_session.query(MetaData).get(field)
|
||||
data = get_gamedata_session().query(MetaData).get(field)
|
||||
else:
|
||||
raise TypeError("Need string as argument")
|
||||
return data
|
||||
@@ -403,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
|
||||
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
||||
from_obj=[join(Attribute, Item)])
|
||||
|
||||
result = gamedata_session.execute(q).fetchall()
|
||||
result = get_gamedata_session().execute(q).fetchall()
|
||||
return result
|
||||
|
||||
|
||||
def getAbyssalTypes():
|
||||
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
|
||||
return set([r.resultingTypeID for r in get_gamedata_session().query(DynamicItem.resultingTypeID).distinct()])
|
||||
|
||||
|
||||
@cachedQuery(1, "itemID")
|
||||
@@ -416,9 +447,9 @@ def getDynamicItem(itemID, eager=None):
|
||||
try:
|
||||
if isinstance(itemID, int):
|
||||
if eager is None:
|
||||
result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
||||
result = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
||||
else:
|
||||
result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
||||
result = get_gamedata_session().query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
except exc.NoResultFound:
|
||||
@@ -428,5 +459,5 @@ def getDynamicItem(itemID, eager=None):
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getAllImplantSets():
|
||||
implantSets = gamedata_session.query(ImplantSet).all()
|
||||
implantSets = get_gamedata_session().query(ImplantSet).all()
|
||||
return implantSets
|
||||
|
||||
42
eos/db/migrations/upgrade38.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Migration 38
|
||||
|
||||
- Armor hardener tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16357: ( # Experimental Enduring EM Armor Hardener I
|
||||
16353, # Upgraded Armor EM Hardener I
|
||||
),
|
||||
16365: ( # Experimental Enduring Explosive Armor Hardener I
|
||||
16361, # Upgraded Armor Explosive Hardener I
|
||||
),
|
||||
16373: ( # Experimental Enduring Kinetic Armor Hardener I
|
||||
16369, # Upgraded Armor Kinetic Hardener I
|
||||
),
|
||||
16381: ( # Experimental Enduring Thermal Armor Hardener I
|
||||
16377, # Upgraded Armor Thermal Hardener I
|
||||
),
|
||||
16359: ( # Prototype Compact EM Armor Hardener I
|
||||
16355, # Limited Armor EM Hardener I
|
||||
),
|
||||
16367: ( # Prototype Compact Explosive Armor Hardener I
|
||||
16363, # Limited Armor Explosive Hardener I
|
||||
),
|
||||
16375: ( # Prototype Compact Kinetic Armor Hardener I
|
||||
16371, # Limited Armor Kinetic Hardener I
|
||||
),
|
||||
16383: ( # Prototype Compact Thermal Armor Hardener I
|
||||
16379, # Limited Armor Thermal Hardener I
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
34
eos/db/migrations/upgrade39.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Migration 39
|
||||
|
||||
- Shield amplifier tiericide
|
||||
- CCP getting rid of DB TDs due to exploits
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
1798: ( # 'Basic' EM Shield Amplifier
|
||||
9562, # Supplemental EM Ward Amplifier
|
||||
),
|
||||
1804: ( # 'Basic' Explosive Shield Amplifier
|
||||
9574, # Supplemental Explosive Deflection Amplifier
|
||||
),
|
||||
1802: ( # 'Basic' Kinetic Shield Amplifier
|
||||
9570, # Supplemental Kinetic Deflection Amplifier
|
||||
),
|
||||
1800: ( # 'Basic' Thermal Shield Amplifier
|
||||
9566, # Supplemental Thermal Dissipation Amplifier
|
||||
),
|
||||
22933: ( # 'Investor' Tracking Disruptor I
|
||||
32416, # Dark Blood Tracking Disruptor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
18
eos/db/migrations/upgrade40.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Migration 40
|
||||
|
||||
Imports all item conversions since Migration 28 and runs them against module.baseItemID. This column seems to have been
|
||||
forgotten about since it's been added.
|
||||
|
||||
"""
|
||||
from .upgrade36 import CONVERSIONS as u36
|
||||
from .upgrade37 import CONVERSIONS as u37
|
||||
from .upgrade38 import CONVERSIONS as u38
|
||||
from .upgrade39 import CONVERSIONS as u39
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
for conversions in [u36, u37, u38, u39]:
|
||||
for replacement_item, list in conversions.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
50
eos/db/migrations/upgrade41.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Migration 41
|
||||
|
||||
- Resistance plating tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16345: ( # Upgraded Layered Coating I
|
||||
16347, # Limited Layered Plating I
|
||||
16349, # 'Scarab' Layered Plating I
|
||||
16351, # 'Grail' Layered Plating I
|
||||
),
|
||||
16305: ( # Upgraded Multispectrum Coating I
|
||||
16307, # Limited Adaptive Nano Plating I
|
||||
16309, # 'Collateral' Adaptive Nano Plating I
|
||||
16311, # 'Refuge' Adaptive Nano Plating I
|
||||
),
|
||||
16329: ( # Upgraded EM Coating I
|
||||
16331, # Limited EM Plating I
|
||||
16333, # 'Contour' EM Plating I
|
||||
16335, # 'Spiegel' EM Plating I
|
||||
),
|
||||
16321: ( # Upgraded Explosive Coating I
|
||||
16323, # Limited Explosive Plating I
|
||||
16325, # Experimental Explosive Plating I
|
||||
16319, # 'Aegis' Explosive Plating I
|
||||
),
|
||||
16313: ( # Upgraded Kinetic Coating I
|
||||
16315, # Limited Kinetic Plating I
|
||||
16317, # Experimental Kinetic Plating I
|
||||
16327, # 'Element' Kinetic Plating I
|
||||
),
|
||||
16337: ( # Upgraded Thermal Coating I
|
||||
16339, # Limited Thermal Plating I
|
||||
16341, # Experimental Thermal Plating I
|
||||
16343, # Prototype Thermal Plating I
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
50
eos/db/migrations/upgrade42.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Migration 42
|
||||
|
||||
- Resistance membrane tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16391: ( # Compact Multispectrum Energized Membrane
|
||||
16389, # Experimental Energized Adaptive Nano Membrane I
|
||||
16387, # Limited Energized Adaptive Nano Membrane I
|
||||
16385, # Upgraded Energized Adaptive Nano Membrane I
|
||||
),
|
||||
16423: ( # Compact Layered Energized Membrane
|
||||
16421, # Experimental Energized Armor Layering Membrane I
|
||||
16419, # Limited Energized Armor Layering Membrane I
|
||||
16417, # Upgraded Energized Armor Layering Membrane I
|
||||
),
|
||||
16415: ( # Compact EM Energized Membrane
|
||||
16413, # Experimental Energized EM Membrane I
|
||||
16411, # Limited Energized EM Membrane I
|
||||
16409, # Upgraded Energized EM Membrane I
|
||||
),
|
||||
16407: ( # Compact Explosive Energized Membrane
|
||||
16405, # Experimental Energized Explosive Membrane I
|
||||
16403, # Limited Energized Explosive Membrane I
|
||||
16401, # Upgraded Energized Explosive Membrane I
|
||||
),
|
||||
16399: ( # Compact Kinetic Energized Membrane
|
||||
16397, # Experimental Energized Kinetic Membrane I
|
||||
16395, # Limited Energized Kinetic Membrane I
|
||||
16393, # Upgraded Energized Kinetic Membrane I
|
||||
),
|
||||
16431: ( # Compact Thermal Energized Membrane
|
||||
16429, # Experimental Energized Thermal Membrane I
|
||||
16427, # Limited Energized Thermal Membrane I
|
||||
16425, # Upgraded Energized Thermal Membrane I
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
@@ -470,7 +470,7 @@ def searchFits(nameLike, where=None, eager=None):
|
||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).limit(100).all())
|
||||
|
||||
return fits
|
||||
|
||||
|
||||
1122
eos/effects.py
@@ -146,6 +146,12 @@ class Effect(EqBase):
|
||||
|
||||
return self.__effectDef is not None
|
||||
|
||||
@property
|
||||
def dealsDamage(self):
|
||||
if not self.__generated:
|
||||
self.__generateHandler()
|
||||
return self.__dealsDamage
|
||||
|
||||
def isType(self, type):
|
||||
"""
|
||||
Check if this effect is of the passed type
|
||||
@@ -167,6 +173,7 @@ class Effect(EqBase):
|
||||
self.__handler = getattr(effectDef, "handler", eos.effects.BaseEffect.handler)
|
||||
self.__runTime = getattr(effectDef, "runTime", "normal")
|
||||
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
|
||||
self.__dealsDamage = effectDef.dealsDamage
|
||||
effectType = getattr(effectDef, "type", None)
|
||||
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
|
||||
self.__type = effectType
|
||||
@@ -175,6 +182,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.debug("ImportError generating handler: {0}", e)
|
||||
except AttributeError as e:
|
||||
@@ -182,6 +190,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.error("AttributeError generating handler: {0}", e)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
@@ -190,6 +199,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.critical("Exception generating handler:")
|
||||
pyfalog.critical(e)
|
||||
@@ -209,40 +219,13 @@ class Effect(EqBase):
|
||||
|
||||
|
||||
class Item(EqBase):
|
||||
MOVE_ATTRS = (4, # Mass
|
||||
38, # Capacity
|
||||
161) # Volume
|
||||
|
||||
MOVE_ATTR_INFO = None
|
||||
|
||||
ABYSSAL_TYPES = None
|
||||
|
||||
@classmethod
|
||||
def getMoveAttrInfo(cls):
|
||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||
if info is None:
|
||||
cls.MOVE_ATTR_INFO = info = []
|
||||
for id in cls.MOVE_ATTRS:
|
||||
info.append(eos.db.getAttributeInfo(id))
|
||||
|
||||
return info
|
||||
|
||||
def moveAttrs(self):
|
||||
self.__moved = True
|
||||
for info in self.getMoveAttrInfo():
|
||||
val = getattr(self, info.name, 0)
|
||||
if val != 0:
|
||||
attr = Attribute()
|
||||
attr.info = info
|
||||
attr.value = val
|
||||
self.__attributes[info.name] = attr
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__race = None
|
||||
self.__requiredSkills = None
|
||||
self.__requiredFor = None
|
||||
self.__moved = False
|
||||
self.__offensive = None
|
||||
self.__assistive = None
|
||||
self.__overrides = None
|
||||
@@ -264,9 +247,6 @@ class Item(EqBase):
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
if not self.__moved:
|
||||
self.moveAttrs()
|
||||
|
||||
return self.__attributes
|
||||
|
||||
@property
|
||||
@@ -363,7 +343,11 @@ class Item(EqBase):
|
||||
if self.__race is None:
|
||||
|
||||
try:
|
||||
if self.category.categoryName == 'Structure':
|
||||
if (
|
||||
self.category.categoryName == 'Structure' or
|
||||
# Here until CCP puts their shit together
|
||||
self.name in ("Thunderchild", "Stormbringer", "Skybreaker")
|
||||
):
|
||||
self.__race = "upwell"
|
||||
else:
|
||||
self.__race = self.factionMap[self.factionID]
|
||||
|
||||
@@ -21,6 +21,9 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.round import roundToPrec
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@@ -56,9 +59,8 @@ class BoosterSideEffect:
|
||||
@property
|
||||
def name(self):
|
||||
return "{0}% {1}".format(
|
||||
self.booster.getModifiedItemAttr(self.attr),
|
||||
self.__effect.getattr('displayName') or self.__effect.name,
|
||||
)
|
||||
roundToPrec(self.booster.getModifiedItemAttr(self.attr), 5),
|
||||
self.__effect.getattr('displayName') or self.__effect.name)
|
||||
|
||||
@property
|
||||
def attr(self):
|
||||
|
||||
@@ -364,3 +364,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.item.groupID in fitDroneGroupLimits:
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (ignoreState or self.amountActive > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -441,3 +441,15 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def canDealDamage(self, ignoreState=False, ignoreAbilityState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
if not self.active and not ignoreState:
|
||||
return False
|
||||
for ability in self.abilities:
|
||||
if not ability.active and not ignoreAbilityState:
|
||||
continue
|
||||
if ability.effect.dealsDamage:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -21,7 +21,7 @@ import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import log, sqrt
|
||||
from math import floor, log, sqrt
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
@@ -39,6 +39,7 @@ from eos.saveddata.damagePattern import DamagePattern
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
|
||||
|
||||
@@ -378,8 +379,9 @@ class Fit:
|
||||
|
||||
@property
|
||||
def maxTargets(self):
|
||||
return min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
||||
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
||||
maxTargets = min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
||||
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
||||
return floor(floatUnerr(maxTargets))
|
||||
|
||||
@property
|
||||
def maxTargetRange(self):
|
||||
@@ -750,6 +752,8 @@ class Fit:
|
||||
|
||||
if warfareBuffID == 79: # AOE_Beacon_bioluminescence_cloud
|
||||
self.ship.boostItemAttr("signatureRadius", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"signatureRadius", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 80: # AOE_Beacon_caustic_cloud_local_repair
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
|
||||
@@ -772,7 +776,11 @@ class Fit:
|
||||
if warfareBuffID == 90: # Weather_electric_storm_EM_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}EmDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}EmDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("emDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"emDamageResonance", value) #for hull
|
||||
|
||||
if warfareBuffID == 92: # Weather_electric_storm_capacitor_recharge_bonus
|
||||
self.ship.boostItemAttr("rechargeRate", value, stackingPenalties=True)
|
||||
@@ -780,32 +788,54 @@ class Fit:
|
||||
if warfareBuffID == 93: # Weather_xenon_gas_explosive_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}ExplosiveDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}ExplosiveDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("explosiveDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"explosiveDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 94: # Weather_xenon_gas_shield_hp_bonus
|
||||
self.ship.boostItemAttr("shieldCapacity", value) # for hull
|
||||
self.ship.boostItemAttr("shieldCapacity", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"shieldCapacity", value)
|
||||
|
||||
if warfareBuffID == 95: # Weather_infernal_thermal_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}ThermalDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}ThermalDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("thermalDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"thermalDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 96: # Weather_infernal_armor_hp_bonus
|
||||
self.ship.boostItemAttr("armorHP", value) # for hull
|
||||
self.ship.boostItemAttr("armorHP", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"armorHP", value)
|
||||
|
||||
if warfareBuffID == 97: # Weather_darkness_turret_range_penalty
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"maxRange", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"maxRange", value, stackingPenalties=True)
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"falloff", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"falloff", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 98: # Weather_darkness_velocity_bonus
|
||||
self.ship.boostItemAttr("maxVelocity", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"maxVelocity", value)
|
||||
|
||||
if warfareBuffID == 99: # Weather_caustic_toxin_kinetic_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}KineticDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}KineticDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("kineticDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"kineticDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 100: # Weather_caustic_toxin_scan_resolution_bonus
|
||||
self.ship.boostItemAttr("scanResolution", value, stackingPenalties=True)
|
||||
@@ -1026,6 +1056,16 @@ class Fit:
|
||||
if mod.isEmpty:
|
||||
del self.modules[i]
|
||||
|
||||
def clearTail(self):
|
||||
tailPositions = {}
|
||||
for mod in reversed(self.modules):
|
||||
if not mod.isEmpty:
|
||||
break
|
||||
tailPositions[self.modules.index(mod)] = mod.slot
|
||||
for pos in sorted(tailPositions, reverse=True):
|
||||
self.modules.remove(self.modules[pos])
|
||||
return tailPositions
|
||||
|
||||
@property
|
||||
def modCount(self):
|
||||
x = 0
|
||||
@@ -1135,7 +1175,7 @@ class Fit:
|
||||
def droneBayUsed(self):
|
||||
amount = 0
|
||||
for d in self.drones:
|
||||
amount += d.item.volume * d.amount
|
||||
amount += d.item.attributes['volume'].value * d.amount
|
||||
|
||||
return amount
|
||||
|
||||
@@ -1143,7 +1183,7 @@ class Fit:
|
||||
def fighterBayUsed(self):
|
||||
amount = 0
|
||||
for f in self.fighters:
|
||||
amount += f.item.volume * f.amount
|
||||
amount += f.item.attributes['volume'].value * f.amount
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@ ProjectedSystem = {
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
||||
SYSTEM_GROUPS = (
|
||||
"Effect Beacon", "MassiveEnvironments", "Abyssal Hazards",
|
||||
"Non-Interactable Object", "Destructible Effect Beacon")
|
||||
|
||||
def __init__(self, item, baseItem=None, mutaplasmid=None):
|
||||
"""Initialize a module from the program"""
|
||||
@@ -214,8 +216,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if charge is None:
|
||||
charges = 0
|
||||
else:
|
||||
chargeVolume = charge.volume
|
||||
containerCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
containerCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
else:
|
||||
@@ -461,6 +463,20 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.isEmpty:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (
|
||||
ignoreState or
|
||||
effect.isType('offline') or
|
||||
(effect.isType('passive') and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType('active') and self.state >= FittingModuleState.ACTIVE) or
|
||||
(effect.isType('overheat') and self.state >= FittingModuleState.OVERHEATED)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
@@ -696,7 +712,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
# Check this only if we're told to do so
|
||||
if hardpointLimit:
|
||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
||||
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -712,6 +728,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
||||
return False
|
||||
# Some destructible effect beacons contain active effects, hardcap those at online state
|
||||
elif state > FittingModuleState.ONLINE and self.slot == FittingSlot.SYSTEM:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -778,8 +797,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# Check sizes, if 'charge size > module volume' it won't fit
|
||||
if charge is None:
|
||||
return True
|
||||
chargeVolume = charge.volume
|
||||
moduleCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
moduleCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
||||
return False
|
||||
|
||||
@@ -1037,7 +1056,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
elif click == "ctrl":
|
||||
state = FittingModuleState.OFFLINE
|
||||
else:
|
||||
state = transitionMap[currState]
|
||||
try:
|
||||
state = transitionMap[currState]
|
||||
except KeyError:
|
||||
state = min(transitionMap)
|
||||
# If passive module tries to transition into online and fails,
|
||||
# put it to passive instead
|
||||
if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
|
||||
|
||||
@@ -81,6 +81,8 @@ class Mutator(EqBase):
|
||||
@validates("value")
|
||||
def validator(self, key, val):
|
||||
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||
if self.baseValue == 0:
|
||||
return 0
|
||||
mod = val / self.baseValue
|
||||
|
||||
if self.minMod <= mod <= self.maxMod:
|
||||
|
||||
@@ -86,7 +86,7 @@ BUILTINS = OrderedDict([
|
||||
# Source: ticket #2067
|
||||
(-52, ('[NPC][Invasion]Invading Precursor Entities', 0.422, 0.367, 0.453, 0.411)),
|
||||
(-53, ('[NPC][Invasion]Retaliating Amarr Entities', 0.360, 0.310, 0.441, 0.602)),
|
||||
(-54, ('[NPC][Invasion]Retaliating Caldari Entities', 0.287, 0.610, 0.487, 0.401)),
|
||||
(-54, ('[NPC][Invasion]Retaliating Caldari Entities', 0.303, 0.610, 0.487, 0.401)),
|
||||
(-55, ('[NPC][Invasion]Retaliating Gallente Entities', 0.383, 0.414, 0.578, 0.513)),
|
||||
(-56, ('[NPC][Invasion]Retaliating Minmatar Entities', 0.620, 0.422, 0.355, 0.399)),
|
||||
(-57, ('[NPC][Abyssal][Dark Matter All Tiers]Drones', 0.439, 0.522, 0.529, 0.435)),
|
||||
|
||||
27
eos/utils/round.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import math
|
||||
|
||||
|
||||
def roundToPrec(val, prec, nsValue=None):
|
||||
"""
|
||||
nsValue: custom value which should be used to determine normalization shift
|
||||
"""
|
||||
# We're not rounding integers anyway
|
||||
# Also make sure that we do not ask to calculate logarithm of zero
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||
# But we don't want to round integers
|
||||
if roundFactor < 0:
|
||||
roundFactor = 0
|
||||
# Do actual rounding
|
||||
val = round(val, roundFactor)
|
||||
# Make sure numbers with .0 part designating float don't get through
|
||||
if int(val) == val:
|
||||
val = int(val)
|
||||
return val
|
||||
|
||||
|
||||
def roundDec(val, prec):
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
return round(val, prec)
|
||||
@@ -26,11 +26,12 @@ VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle'
|
||||
|
||||
class YDef:
|
||||
|
||||
def __init__(self, handle, unit, label, selectorLabel=None):
|
||||
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
|
||||
self.handle = handle
|
||||
self.unit = unit
|
||||
self.label = label
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
@@ -53,12 +54,13 @@ class YDef:
|
||||
|
||||
class XDef:
|
||||
|
||||
def __init__(self, handle, unit, label, mainInput, selectorLabel=None):
|
||||
def __init__(self, handle, unit, label, mainInput, selectorLabel=None, hidden=False):
|
||||
self.handle = handle
|
||||
self.unit = unit
|
||||
self.label = label
|
||||
self.mainInput = mainInput
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
|
||||
@@ -37,7 +37,14 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
||||
if "ChainLightning" in mod.item.effects:
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getVortonMult(
|
||||
mod=mod,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
elif mod.hardpoint == FittingHardpoint.TURRET:
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
@@ -56,7 +63,6 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
@@ -151,7 +157,21 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
|
||||
return mult
|
||||
|
||||
|
||||
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
|
||||
def getVortonMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||
rangeFactor = calculateRangeFactor(
|
||||
mod.getModifiedItemAttr('maxRange'),
|
||||
0,
|
||||
distance)
|
||||
applicationFactor = _calcMissileFactor(
|
||||
atkEr=mod.getModifiedItemAttr('aoeCloudSize'),
|
||||
atkEv=mod.getModifiedItemAttr('aoeVelocity'),
|
||||
atkDrf=mod.getModifiedItemAttr('aoeDamageReductionFactor'),
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
return rangeFactor * applicationFactor
|
||||
|
||||
|
||||
def getLauncherMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||
missileMaxRangeData = mod.missileMaxRangeData
|
||||
if missileMaxRangeData is None:
|
||||
return 0
|
||||
|
||||
@@ -31,8 +31,8 @@ from .getter import (
|
||||
|
||||
class FitDamageStatsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
self._projectedCache = ProjectedDataCache()
|
||||
|
||||
|
||||
@@ -23,24 +23,6 @@ import math
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
class Time2SpeedGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
return {
|
||||
'maxSpeed': src.getMaxVelocity(),
|
||||
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
||||
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
time = x
|
||||
maxSpeed = commonData['maxSpeed']
|
||||
mass = commonData['mass']
|
||||
agility = commonData['agility']
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||
return speed
|
||||
|
||||
|
||||
class Time2DistanceGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
@@ -60,3 +42,62 @@ class Time2DistanceGetter(SmoothPointGetter):
|
||||
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
|
||||
distance = distance_t - distance_0
|
||||
return distance
|
||||
|
||||
|
||||
class Time2SpeedGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
return {
|
||||
'maxSpeed': src.getMaxVelocity(),
|
||||
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
||||
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
time = x
|
||||
maxSpeed = commonData['maxSpeed']
|
||||
mass = commonData['mass']
|
||||
agility = commonData['agility']
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||
return speed
|
||||
|
||||
|
||||
class Time2MomentumGetter(Time2SpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
mass = commonData['mass']
|
||||
speed = Time2SpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
momentum = speed * mass
|
||||
return momentum
|
||||
|
||||
|
||||
class Time2BumpSpeedGetter(Time2SpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||
bumperMass = commonData['mass'] / 10 ** 6
|
||||
bumperSpeed = Time2SpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||
tgtSpeed = (2 * bumperSpeed * bumperMass) / (bumperMass + tgtMass)
|
||||
return tgtSpeed
|
||||
|
||||
|
||||
class Time2BumpDistanceGetter(Time2BumpSpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||
tgtInertia = miscParams['tgtInertia']
|
||||
tgtSpeed = Time2BumpSpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
tgtDistance = tgtSpeed * tgtMass * tgtInertia
|
||||
return tgtDistance
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
|
||||
from graphs.data.base import FitGraph, XDef, YDef, Input
|
||||
from .getter import Time2SpeedGetter, Time2DistanceGetter
|
||||
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
|
||||
|
||||
|
||||
class FitMobilityGraph(FitGraph):
|
||||
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
|
||||
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
||||
yDefs = [
|
||||
YDef(handle='speed', unit='m/s', label='Speed'),
|
||||
YDef(handle='distance', unit='km', label='Distance')]
|
||||
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
|
||||
YDef(handle='distance', unit='km', label='Distance'),
|
||||
YDef(handle='momentum', unit='Gkg⋅m/s', label='Momentum'),
|
||||
YDef(handle='bumpSpeed', unit='m/s', label='Target speed', selectorLabel='Bump speed'),
|
||||
YDef(handle='bumpDistance', unit='km', label='Target distance traveled', selectorLabel='Bump distance')]
|
||||
inputs = [
|
||||
Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30)),
|
||||
# Default values in target fields correspond to a random carrier/fax
|
||||
Input(handle='tgtMass', unit='Mkg', label='Target mass', iconID=76, defaultValue=1300, defaultRange=(100, 2500), conditions=[(None, ('bumpSpeed', 'm/s')), (None, ('bumpDistance', 'km'))], secondaryTooltip='Defined in millions of kilograms'),
|
||||
Input(handle='tgtInertia', unit=None, label='Target inertia factor', iconID=1401, defaultValue=0.015, defaultRange=(0.03, 0.1), conditions=[(None, ('bumpDistance', 'km'))], secondaryTooltip='Inertia Modifier attribute value of the target ship')]
|
||||
srcExtraCols = ('Speed', 'Agility')
|
||||
|
||||
# Calculation stuff
|
||||
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
|
||||
_getters = {
|
||||
('time', 'speed'): Time2SpeedGetter,
|
||||
('time', 'distance'): Time2DistanceGetter}
|
||||
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
|
||||
('time', 'distance'): Time2DistanceGetter,
|
||||
('time', 'momentum'): Time2MomentumGetter,
|
||||
('time', 'bumpSpeed'): Time2BumpSpeedGetter,
|
||||
('time', 'bumpDistance'): Time2BumpDistanceGetter}
|
||||
_denormalizers = {
|
||||
('distance', 'km'): lambda v, src, tgt: v / 1000,
|
||||
('momentum', 'Gkg⋅m/s'): lambda v, src, tgt: v / 10 ** 9,
|
||||
('bumpDistance', 'km'): lambda v, src, tgt: v / 1000}
|
||||
|
||||
@@ -26,8 +26,8 @@ from .getter import Distance2RpsGetter, Distance2RepAmountGetter, Time2RpsGetter
|
||||
|
||||
class FitRemoteRepsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -27,6 +27,10 @@ from .getter import (
|
||||
|
||||
class FitShieldRegenGraph(FitGraph):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||
|
||||
# UI stuff
|
||||
internalName = 'shieldRegenGraph'
|
||||
name = 'Shield Regeneration'
|
||||
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
|
||||
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
|
||||
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
|
||||
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||
|
||||
@@ -26,8 +26,8 @@ from .getter import AU_METERS, Distance2TimeGetter
|
||||
|
||||
class FitWarpTimeGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._subspeedCache = SubwarpSpeedCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -235,7 +235,7 @@ class GraphControlPanel(wx.Panel):
|
||||
fieldTextBox = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue))
|
||||
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
||||
fieldTextBox.SetToolTip(wx.ToolTip(tooltipText))
|
||||
fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5)
|
||||
fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.RIGHT, 5)
|
||||
fieldIcon = None
|
||||
if inputDef.iconID is not None:
|
||||
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
||||
@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
|
||||
|
||||
self.ySubSelection.Clear()
|
||||
for yDef in view.yDefs:
|
||||
if yDef.hidden and not self.graphFrame.includeHidden:
|
||||
continue
|
||||
self.ySubSelection.Append(self.formatLabel(yDef, selector=True), yDef)
|
||||
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
||||
self.ySubSelection.SetSelection(selectedY)
|
||||
|
||||
self.xSubSelection.Clear()
|
||||
for xDef in view.xDefs:
|
||||
if xDef.hidden and not self.graphFrame.includeHidden:
|
||||
continue
|
||||
self.xSubSelection.Append(self.formatLabel(xDef, selector=True), xDef)
|
||||
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
||||
self.xSubSelection.SetSelection(selectedX)
|
||||
|
||||
@@ -27,7 +27,7 @@ import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from graphs.data.base import FitGraph
|
||||
from graphs.events import RESIST_MODE_CHANGED
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from service.settings import GraphSettings
|
||||
@@ -50,6 +50,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.includeHidden = includeHidden
|
||||
|
||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||
|
||||
@@ -74,7 +75,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
# Setup - graph selector
|
||||
for view in FitGraph.views:
|
||||
if view.hidden and not includeHidden:
|
||||
if view.hidden and not self.includeHidden:
|
||||
continue
|
||||
self.graphSelection.Append(view.name, view())
|
||||
self.graphSelection.SetSelection(0)
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
import wx
|
||||
|
||||
|
||||
class AuxiliaryFrame(wx.Frame):
|
||||
class AuxiliaryMixin:
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None, resizeable=False):
|
||||
baseStyle = wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU
|
||||
baseStyle = wx.FRAME_NO_TASKBAR | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU
|
||||
if parent is not None:
|
||||
baseStyle = baseStyle | wx.FRAME_FLOAT_ON_PARENT
|
||||
if resizeable:
|
||||
baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX
|
||||
kwargs = {
|
||||
@@ -53,14 +55,26 @@ class AuxiliaryFrame(wx.Frame):
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
@classmethod
|
||||
def openOne(cls, parent, *args, **kwargs):
|
||||
def openOne(cls, parent, *args, forceReopen=False, **kwargs):
|
||||
"""If window is open and alive - raise it, open otherwise"""
|
||||
if not cls._instance:
|
||||
if not cls._instance or forceReopen:
|
||||
if cls._instance:
|
||||
cls._instance.Close()
|
||||
frame = cls(parent, *args, **kwargs)
|
||||
cls._instance = frame
|
||||
frame.Show()
|
||||
else:
|
||||
cls._instance.Raise()
|
||||
return cls._instance
|
||||
|
||||
|
||||
def OnSuppressedAction(self, event):
|
||||
return
|
||||
|
||||
|
||||
class AuxiliaryFrame(AuxiliaryMixin, wx.Frame):
|
||||
pass
|
||||
|
||||
|
||||
class AuxiliaryDialog(AuxiliaryMixin, wx.Dialog):
|
||||
pass
|
||||
@@ -24,6 +24,7 @@ import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
@@ -58,6 +59,7 @@ class CargoView(d.Display):
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
@@ -66,6 +68,31 @@ class CargoView(d.Display):
|
||||
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
def addItem(self, event):
|
||||
item = Market.getInstance().getItem(event.itemID, eager='group')
|
||||
if item is None or not item.isCharge:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
|
||||
if not fit:
|
||||
event.Skip()
|
||||
return
|
||||
modifiers = wx.GetMouseState().GetModifiers()
|
||||
amount = 1
|
||||
if modifiers == wx.MOD_CONTROL:
|
||||
amount = 10
|
||||
elif modifiers == wx.MOD_ALT:
|
||||
amount = 100
|
||||
elif modifiers == wx.MOD_CONTROL | wx.MOD_ALT:
|
||||
amount = 1000
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(
|
||||
fitID=fitID, itemID=item.ID, amount=amount))
|
||||
self.mainFrame.additionsPane.select('Cargo')
|
||||
event.Skip()
|
||||
|
||||
def handleListDrag(self, x, y, data):
|
||||
"""
|
||||
Handles dragging of items from various pyfa displays which support it
|
||||
|
||||
@@ -36,6 +36,11 @@ import gui.fitCommands as cmd
|
||||
from gui.fitCommands.helpers import droneStackLimit
|
||||
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
|
||||
class DroneViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(DroneViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -186,17 +191,13 @@ class DroneView(Display):
|
||||
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
||||
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
def droneKey(self, drone):
|
||||
@staticmethod
|
||||
def droneKey(drone):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
groupName = sMkt.getMarketGroupByItem(drone.item).name
|
||||
|
||||
return (self.DRONE_ORDER.index(groupName),
|
||||
drone.item.name)
|
||||
return (DRONE_ORDER.index(groupName), drone.item.name)
|
||||
|
||||
def fitChanged(self, event):
|
||||
event.Skip()
|
||||
|
||||
@@ -34,6 +34,9 @@ from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
|
||||
class FighterViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(FighterViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -250,11 +253,10 @@ class FighterDisplay(d.Display):
|
||||
def _merge(src, dst):
|
||||
return
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
def fighterKey(self, fighter):
|
||||
@staticmethod
|
||||
def fighterKey(fighter):
|
||||
groupName = Market.getInstance().getGroupByItem(fighter.item).name
|
||||
orderPos = self.FIGHTER_ORDER.index(groupName)
|
||||
orderPos = FIGHTER_ORDER.index(groupName)
|
||||
# Sort support fighters by name, ignore their abilities
|
||||
if groupName == 'Support Fighter':
|
||||
abilityEffectIDs = ()
|
||||
|
||||
@@ -90,8 +90,6 @@ class ProjectedView(d.Display):
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.droneView = gui.builtinAdditionPanes.droneView.DroneView
|
||||
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.SetDropTarget(ProjectedViewDrop(self.handleListDrag))
|
||||
@@ -119,12 +117,12 @@ class ProjectedView(d.Display):
|
||||
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
|
||||
elif data[0] == 'market':
|
||||
itemID = int(data[1])
|
||||
category = Market.getInstance().getItem(itemID, eager=('group.category')).category.name
|
||||
if category == 'Module':
|
||||
item = Market.getInstance().getItem(itemID)
|
||||
if item.isModule:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Drone':
|
||||
elif item.isDrone:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedDroneCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Fighter':
|
||||
elif item.isFighter:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=itemID))
|
||||
|
||||
def kbEvent(self, event):
|
||||
@@ -162,7 +160,7 @@ class ProjectedView(d.Display):
|
||||
if item.marketGroup is None:
|
||||
item = item.metaGroup.parent
|
||||
|
||||
return (self.droneView.DRONE_ORDER.index(item.marketGroup.name),
|
||||
return (gui.builtinAdditionPanes.droneView.DRONE_ORDER.index(item.marketGroup.name),
|
||||
drone.item.name)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -6,6 +6,7 @@ from gui.builtinContextMenus import fitAddCurrentlyOpen
|
||||
from gui.builtinContextMenus import envEffectAdd
|
||||
from gui.builtinContextMenus import commandFitAdd
|
||||
from gui.builtinContextMenus.targetProfile import adder
|
||||
from gui.builtinContextMenus import graphFitAmmoPicker
|
||||
# Often-used item manipulations
|
||||
from gui.builtinContextMenus import shipModeChange
|
||||
from gui.builtinContextMenus import moduleAmmoChange
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -10,6 +11,28 @@ from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class Group:
|
||||
|
||||
def __init__(self):
|
||||
self.groups = OrderedDict()
|
||||
self.items = []
|
||||
|
||||
def sort(self):
|
||||
self.groups = OrderedDict((k, self.groups[k]) for k in sorted(self.groups))
|
||||
for group in self.groups.values():
|
||||
group.sort()
|
||||
self.items.sort(key=lambda e: e.shortName)
|
||||
|
||||
|
||||
class Entry:
|
||||
|
||||
def __init__(self, itemID, name, shortName):
|
||||
self.itemID = itemID
|
||||
self.name = name
|
||||
self.shortName = shortName
|
||||
|
||||
|
||||
|
||||
class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
|
||||
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
|
||||
@@ -32,107 +55,78 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return "Add Environmental Effect"
|
||||
|
||||
def _addGroup(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _addEffect(self, parentMenu, typeID, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.idmap[id] = typeID
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
|
||||
# Wormholes
|
||||
|
||||
self.idmap = {}
|
||||
sub = wx.Menu()
|
||||
data = self.getData()
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
|
||||
wormhole_item = wx.MenuItem(sub, wx.ID_ANY, "Wormhole")
|
||||
wormhole_menu = wx.Menu()
|
||||
wormhole_item.SetSubMenu(wormhole_menu)
|
||||
sub.Append(wormhole_item)
|
||||
|
||||
grouped_data, flat_data = self.getEffectBeacons()
|
||||
self.buildMenu(grouped_data, flat_data, wormhole_menu, rootMenu, msw)
|
||||
|
||||
# Incursions
|
||||
|
||||
grouped_data, flat_data = self.getEffectBeacons(incursions=True)
|
||||
self.buildMenu(grouped_data, flat_data, sub, rootMenu, msw)
|
||||
|
||||
# Abyssal Weather
|
||||
|
||||
abyssal_item = wx.MenuItem(sub, wx.ID_ANY, "Abyssal Weather")
|
||||
abyssal_menu = wx.Menu()
|
||||
abyssal_item.SetSubMenu(abyssal_menu)
|
||||
sub.Append(abyssal_item)
|
||||
|
||||
grouped_data, flat_data = self.getAbyssalWeather()
|
||||
self.buildMenu(grouped_data, flat_data, abyssal_menu, rootMenu, msw)
|
||||
|
||||
# Localized Weather
|
||||
|
||||
local_item = wx.MenuItem(sub, wx.ID_ANY, "Localized")
|
||||
local_menu = wx.Menu()
|
||||
local_item.SetSubMenu(local_menu)
|
||||
sub.Append(local_item)
|
||||
|
||||
grouped_data, flat_data = self.getLocalizedEnvironments()
|
||||
self.buildMenu(grouped_data, flat_data, local_menu, rootMenu, msw)
|
||||
def makeMenu(data, parentMenu):
|
||||
menu = wx.Menu()
|
||||
for group_name in data.groups:
|
||||
menuItem = self._addGroup(rootMenu if msw else parentMenu, group_name)
|
||||
subMenu = makeMenu(data.groups[group_name], menu)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
menu.Append(menuItem)
|
||||
for entry in data.items:
|
||||
menuItem = self._addEffect(rootMenu if msw else parentMenu, entry.itemID, entry.shortName)
|
||||
menu.Append(menuItem)
|
||||
menu.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
return menu
|
||||
|
||||
sub = makeMenu(data, rootMenu)
|
||||
return sub
|
||||
|
||||
def handleSelection(self, event):
|
||||
# Skip events ids that aren't mapped
|
||||
|
||||
swObj, swName = self.idmap.get(event.Id, (False, False))
|
||||
if not swObj and not swName:
|
||||
swObj = self.idmap.get(event.Id, False)
|
||||
if not swObj:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID, swObj.ID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID, swObj))
|
||||
|
||||
def buildMenu(self, grouped_data, flat_data, local_menu, rootMenu, msw):
|
||||
def getData(self):
|
||||
data = Group()
|
||||
data.groups['Metaliminal Storm'] = self.getEffectBeacons(
|
||||
'Electrical', 'Exotic', 'Gamma', 'Plasma',
|
||||
extra_garbage=('Metaliminal', 'Storm', 'Matter', 'Ray', 'Firestorm'))
|
||||
data.groups['Wormhole'] = self.getEffectBeacons(
|
||||
'Black Hole', 'Cataclysmic Variable', 'Magnetar',
|
||||
'Pulsar', 'Red Giant', 'Wolf Rayet')
|
||||
data.groups['Abyssal Weather'] = self.getAbyssalWeather()
|
||||
data.groups['Sansha Incursion'] = self.getEffectBeacons('Sansha Incursion')
|
||||
data.groups['Triglavian Invasion'] = self.getEffectBeacons('Triglavian Invasion')
|
||||
data.groups['Triglavian Invasion'].groups['Destructible Beacons'] = self.getDestructibleBeacons()
|
||||
return data
|
||||
|
||||
def processFlat(data, root, sub):
|
||||
for swData in sorted(data, key=lambda tpl: tpl[2]):
|
||||
wxid = ContextMenuUnconditional.nextID()
|
||||
swObj, swName, swClass = swData
|
||||
self.idmap[wxid] = (swObj, swName)
|
||||
subItem = wx.MenuItem(sub, wxid, swClass)
|
||||
if msw:
|
||||
root.Bind(wx.EVT_MENU, self.handleSelection, subItem)
|
||||
else:
|
||||
sub.Bind(wx.EVT_MENU, self.handleSelection, subItem)
|
||||
sub.Append(subItem)
|
||||
|
||||
for swType in sorted(grouped_data):
|
||||
subItem = wx.MenuItem(local_menu, wx.ID_ANY, swType)
|
||||
grandSub = wx.Menu()
|
||||
subItem.SetSubMenu(grandSub)
|
||||
local_menu.Append(subItem)
|
||||
processFlat(grouped_data[swType], rootMenu, grandSub)
|
||||
|
||||
processFlat(flat_data, rootMenu, local_menu)
|
||||
|
||||
def getEffectBeacons(self, incursions=False):
|
||||
def getEffectBeacons(self, *groups, extra_garbage=()):
|
||||
"""
|
||||
Get dictionary with wormhole system-wide effects
|
||||
Get dictionary with system-wide effects
|
||||
"""
|
||||
compacted = len(groups) <= 1
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
# todo: rework this
|
||||
# Container for system-wide effects
|
||||
grouped = {}
|
||||
|
||||
# Expressions for matching when detecting effects we're looking for
|
||||
if incursions:
|
||||
validgroups = ("Sansha Incursion",
|
||||
"Triglavian Invasion")
|
||||
else:
|
||||
validgroups = ("Black Hole",
|
||||
"Cataclysmic Variable",
|
||||
"Magnetar",
|
||||
"Pulsar",
|
||||
"Red Giant",
|
||||
"Wolf Rayet")
|
||||
data = Group()
|
||||
|
||||
# Stuff we don't want to see in names
|
||||
garbages = ("System Effects", "Effects")
|
||||
garbages = ["System Effects", "Effects"]
|
||||
garbages.extend(extra_garbage)
|
||||
|
||||
# Get group with all the system-wide beacons
|
||||
grp = sMkt.getGroup("Effect Beacon")
|
||||
@@ -140,7 +134,7 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
# Cycle through them
|
||||
for beacon in sMkt.getItemsByGroup(grp):
|
||||
# Check if it belongs to any valid group
|
||||
for group in validgroups:
|
||||
for group in groups:
|
||||
# Check beginning of the name only
|
||||
if re.search(group, beacon.name):
|
||||
# Get full beacon name
|
||||
@@ -159,64 +153,65 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
groupname = re.sub(garbage, "", groupname)
|
||||
groupname = re.sub(" {2,}", " ", groupname).strip()
|
||||
# Add stuff to dictionary
|
||||
if groupname not in grouped:
|
||||
grouped[groupname] = set()
|
||||
grouped[groupname].add((beacon, beaconname, shortname))
|
||||
if compacted:
|
||||
container = data.items
|
||||
else:
|
||||
container = data.groups.setdefault(groupname, Group()).items
|
||||
container.append(Entry(beacon.ID, beaconname, shortname))
|
||||
# Break loop on 1st result
|
||||
break
|
||||
|
||||
return grouped, ()
|
||||
data.sort()
|
||||
return data
|
||||
|
||||
def getAbyssalWeather(self):
|
||||
sMkt = Market.getInstance()
|
||||
data = Group()
|
||||
|
||||
environments = {x.ID: x for x in sMkt.getGroup("Abyssal Environment").items}
|
||||
items = chain(sMkt.getGroup("MassiveEnvironments").items, sMkt.getGroup("Non-Interactable Object").items)
|
||||
|
||||
grouped = {}
|
||||
flat = set()
|
||||
|
||||
items = chain(
|
||||
sMkt.getGroup("MassiveEnvironments").items,
|
||||
sMkt.getGroup("Non-Interactable Object").items)
|
||||
for beacon in items:
|
||||
if not beacon.isType('projected'):
|
||||
continue
|
||||
|
||||
type = self.__class__.abyssal_mapping.get(beacon.name[0:-2], None)
|
||||
type = environments.get(type, None)
|
||||
if type is None:
|
||||
continue
|
||||
|
||||
if type.name not in grouped:
|
||||
grouped[type.name] = set()
|
||||
|
||||
subdata = data.groups.setdefault(type.name, Group())
|
||||
display_name = "{} {}".format(type.name, beacon.name[-1:])
|
||||
grouped[type.name].add((beacon, display_name, display_name))
|
||||
subdata.items.append(Entry(beacon.ID, display_name, display_name))
|
||||
data.sort()
|
||||
|
||||
# Localized abyssal hazards
|
||||
items = sMkt.getGroup("Abyssal Hazards").items
|
||||
if items:
|
||||
subdata = data.groups.setdefault('Localized', Group())
|
||||
for beacon in sMkt.getGroup("Abyssal Hazards").items:
|
||||
if not beacon.isType('projected'):
|
||||
continue
|
||||
# Localized effects, currently, have a name like "(size) (type) Cloud"
|
||||
# Until this inevitably changes, do a simple split
|
||||
name_parts = beacon.name.split(" ")
|
||||
|
||||
key = name_parts[1].strip()
|
||||
subsubdata = subdata.groups.setdefault(key, Group())
|
||||
subsubdata.items.append(Entry(beacon.ID, beacon.name, beacon.name))
|
||||
subdata.sort()
|
||||
|
||||
# PVP weather
|
||||
flat.add((sMkt.getItem(49766), 'PvP Weather', 'PvP Weather'))
|
||||
data.items.append(Entry(49766, 'PvP Weather', 'PvP Weather'))
|
||||
|
||||
return grouped, flat
|
||||
return data
|
||||
|
||||
def getLocalizedEnvironments(self):
|
||||
def getDestructibleBeacons(self):
|
||||
data = Group()
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
grp = sMkt.getGroup("Abyssal Hazards")
|
||||
|
||||
grouped = dict()
|
||||
|
||||
for beacon in grp.items:
|
||||
if not beacon.isType('projected'):
|
||||
for item in sMkt.getItemsByGroup(sMkt.getGroup('Destructible Effect Beacon')):
|
||||
if not item.isType('projected'):
|
||||
continue
|
||||
# Localized effects, currently, have a name like "(size) (type) Cloud"
|
||||
# Until this inevitably changes, do a simple split
|
||||
name_parts = beacon.name.split(" ")
|
||||
|
||||
key = name_parts[1].strip()
|
||||
if key not in grouped:
|
||||
grouped[key] = set()
|
||||
|
||||
grouped[key].add((beacon, beacon.name, beacon.name))
|
||||
|
||||
return grouped, ()
|
||||
|
||||
data.items.append(Entry(item.ID, item.name, item.name))
|
||||
data.sort()
|
||||
return data
|
||||
|
||||
AddEnvironmentEffect.register()
|
||||
|
||||
241
gui/builtinContextMenus/graphFitAmmoPicker.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.auxWindow import AuxiliaryDialog
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.ammo import Ammo
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class GraphFitAmmoPicker(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if srcContext != 'graphFitList':
|
||||
return False
|
||||
if mainItem is None or not mainItem.isFit:
|
||||
return False
|
||||
if callingWindow.graphFrame.getView().internalName != 'dmgStatsGraph':
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem):
|
||||
return 'Plot with Different Ammo...'
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, i):
|
||||
AmmoPickerFrame.openOne(callingWindow, mainItem.item, forceReopen=True)
|
||||
|
||||
|
||||
# GraphFitAmmoPicker.register()
|
||||
|
||||
|
||||
class AmmoPickerFrame(AuxiliaryDialog):
|
||||
|
||||
def __init__(self, parent, fit):
|
||||
super().__init__(parent, title='Choose Different Ammo', style=wx.DEFAULT_DIALOG_STYLE, resizeable=True)
|
||||
padding = 5
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
contents = AmmoPickerContents(self, fit)
|
||||
mainSizer.Add(contents, 1, wx.EXPAND | wx.ALL, padding)
|
||||
|
||||
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
||||
if buttonSizer:
|
||||
mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, padding)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
|
||||
contW, contH = contents.GetVirtualSize()
|
||||
bestW = contW + padding * 2
|
||||
bestH = contH + padding * 2
|
||||
if buttonSizer:
|
||||
# Yeah right... whatever
|
||||
buttW, buttH = buttonSizer.GetSize()
|
||||
bestW = max(bestW, buttW + padding * 2)
|
||||
bestH += buttH + padding * 2
|
||||
bestW = min(1000, bestW)
|
||||
bestH = min(700, bestH)
|
||||
self.SetSize(bestW, bestH)
|
||||
self.SetMinSize(wx.Size(int(bestW * 0.7), int(bestH * 0.7)))
|
||||
self.CenterOnParent()
|
||||
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
|
||||
|
||||
def kbEvent(self, event):
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
|
||||
class AmmoPickerContents(wx.ScrolledCanvas):
|
||||
|
||||
indent = 15
|
||||
|
||||
def __init__(self, parent, fit):
|
||||
wx.ScrolledCanvas.__init__(self, parent)
|
||||
self.SetScrollRate(0, 15)
|
||||
|
||||
mods = self.getMods(fit)
|
||||
drones = self.getDrones(fit)
|
||||
fighters = self.getFighters(fit)
|
||||
self.rbLabelMap = {}
|
||||
self.rbCheckboxMap = {}
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
moduleSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(moduleSizer, 0, wx.ALL, 0)
|
||||
|
||||
self.droneSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(self.droneSizer, 0, wx.ALL, 0)
|
||||
|
||||
fighterSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(fighterSizer, 0, wx.ALL, 0)
|
||||
|
||||
firstRadio = True
|
||||
|
||||
for modInfo, modAmmo in mods:
|
||||
text = '\n'.join('{}x {}'.format(amount, item.name) for item, amount in modInfo)
|
||||
modRb = self.addRadioButton(moduleSizer, text, firstRadio)
|
||||
firstRadio = False
|
||||
# Get actual module, as ammo getters need it
|
||||
mod = next((m for m in fit.modules if m.itemID == next(iter(modInfo))[0].ID), None)
|
||||
_, ammoTree = Ammo.getInstance().getModuleStructuredAmmo(mod)
|
||||
if len(ammoTree) == 1:
|
||||
for ammoCatName, ammos in ammoTree.items():
|
||||
for ammo in ammos:
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
||||
else:
|
||||
for ammoCatName, ammos in ammoTree.items():
|
||||
if len(ammos) == 1:
|
||||
ammo = next(iter(ammos))
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
||||
else:
|
||||
self.addLabel(moduleSizer, '{}:'.format(ammoCatName), modRb, indentLvl=1)
|
||||
for ammo in ammos:
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=2)
|
||||
if drones:
|
||||
droneRb = self.addRadioButton(self.droneSizer, 'Drones', firstRadio)
|
||||
from gui.builtinAdditionPanes.droneView import DroneView
|
||||
for drone in sorted(drones, key=DroneView.droneKey):
|
||||
self.addCheckbox(self.droneSizer, '{}x {}'.format(drone.amount, drone.item.name), droneRb, indentLvl=1)
|
||||
addBtn = wx.Button(self, wx.ID_ANY, '+', style=wx.BU_EXACTFIT)
|
||||
addBtn.Bind(wx.EVT_BUTTON, self.OnDroneGroupAdd)
|
||||
mainSizer.Add(addBtn, 0, wx.LEFT, self.indent)
|
||||
if fighters:
|
||||
fighterRb = self.addRadioButton(fighterSizer, 'Fighters', firstRadio)
|
||||
from gui.builtinAdditionPanes.fighterView import FighterDisplay
|
||||
for fighter in sorted(fighters, key=FighterDisplay.fighterKey):
|
||||
self.addCheckbox(fighterSizer, '{}x {}'.format(fighter.amount, fighter.item.name), fighterRb, indentLvl=1)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
self.refreshStatus()
|
||||
|
||||
def addRadioButton(self, sizer, text, firstRadio=False):
|
||||
if firstRadio:
|
||||
rb = wx.RadioButton(self, wx.ID_ANY, text, style=wx.RB_GROUP)
|
||||
rb.SetValue(True)
|
||||
else:
|
||||
rb = wx.RadioButton(self, wx.ID_ANY, text)
|
||||
rb.SetValue(False)
|
||||
rb.Bind(wx.EVT_RADIOBUTTON, self.rbSelected)
|
||||
sizer.Add(rb, 0, wx.EXPAND | wx.ALL, 0)
|
||||
return rb
|
||||
|
||||
def addCheckbox(self, sizer, text, currentRb, indentLvl=0):
|
||||
cb = wx.CheckBox(self, -1, text)
|
||||
sizer.Add(cb, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
||||
if currentRb is not None:
|
||||
self.rbCheckboxMap.setdefault(currentRb, []).append(cb)
|
||||
|
||||
def addLabel(self, sizer, text, currentRb, indentLvl=0):
|
||||
text = text[0].capitalize() + text[1:]
|
||||
label = wx.StaticText(self, wx.ID_ANY, text)
|
||||
sizer.Add(label, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
||||
if currentRb is not None:
|
||||
self.rbLabelMap.setdefault(currentRb, []).append(label)
|
||||
|
||||
def getMods(self, fit):
|
||||
sMkt = Market.getInstance()
|
||||
sAmmo = Ammo.getInstance()
|
||||
loadableChargesCache = {}
|
||||
# Modules, format: {frozenset(ammo): {item: count}}
|
||||
modsPrelim = {}
|
||||
if fit is not None:
|
||||
for mod in fit.modules:
|
||||
if not mod.canDealDamage():
|
||||
continue
|
||||
typeID = mod.item.ID
|
||||
if typeID not in loadableChargesCache:
|
||||
loadableChargesCache[typeID] = sAmmo.getModuleFlatAmmo(mod)
|
||||
charges = loadableChargesCache[typeID]
|
||||
# We're not interested in modules which contain no charges
|
||||
if charges:
|
||||
data = modsPrelim.setdefault(frozenset(charges), {})
|
||||
if mod.item not in data:
|
||||
data[mod.item] = 0
|
||||
data[mod.item] += 1
|
||||
# Format: [([(item, count), ...], frozenset(ammo)), ...]
|
||||
modsFinal = []
|
||||
for charges, itemCounts in modsPrelim.items():
|
||||
modsFinal.append((
|
||||
# Sort items within group
|
||||
sorted(itemCounts.items(), key=lambda i: sMkt.itemSort(i[0], reverseMktGrp=True), reverse=True),
|
||||
charges))
|
||||
# Sort item groups
|
||||
modsFinal.sort(key=lambda i: sMkt.itemSort(i[0][0][0], reverseMktGrp=True), reverse=True)
|
||||
return modsFinal
|
||||
|
||||
def getDrones(self, fit):
|
||||
drones = []
|
||||
if fit is not None:
|
||||
for drone in fit.drones:
|
||||
if drone.item is None:
|
||||
continue
|
||||
# Drones are our "ammo", so we want to pick even those which are inactive
|
||||
if drone.canDealDamage(ignoreState=True):
|
||||
drones.append(drone)
|
||||
continue
|
||||
if {'remoteWebifierEntity', 'remoteTargetPaintEntity'}.intersection(drone.item.effects):
|
||||
drones.append(drone)
|
||||
continue
|
||||
return drones
|
||||
|
||||
def getFighters(self, fit):
|
||||
fighters = []
|
||||
if fit is not None:
|
||||
for fighter in fit.fighters:
|
||||
if fighter.item is None:
|
||||
continue
|
||||
# Fighters are our "ammo" as well
|
||||
if fighter.canDealDamage(ignoreState=True):
|
||||
fighters.append(fighter)
|
||||
continue
|
||||
for ability in fighter.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||
fighters.append(fighter)
|
||||
break
|
||||
return fighters
|
||||
|
||||
def OnDroneGroupAdd(self, event):
|
||||
event.Skip()
|
||||
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
label = wx.StaticText()
|
||||
self.droneSizer.Add(sizer, 0, wx.EXPAND | wx.LEFT, self.indent)
|
||||
|
||||
def refreshStatus(self):
|
||||
for map in (self.rbLabelMap, self.rbCheckboxMap):
|
||||
for rb, items in map.items():
|
||||
for item in items:
|
||||
item.Enable(rb.GetValue())
|
||||
|
||||
def rbSelected(self, event):
|
||||
event.Skip()
|
||||
self.refreshStatus()
|
||||
@@ -3,33 +3,28 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions
|
||||
from service.ammo import Ammo
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class ChangeModuleAmmo(ContextMenuCombined):
|
||||
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
# Format: {type ID: set(loadable, charges)}
|
||||
self.loadableCharges = {}
|
||||
self.loadableChargesCache = {}
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if srcContext not in ("fittingModule", "projectedModule"):
|
||||
if srcContext not in ('fittingModule', 'projectedModule'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
self.mainCharges = self.getChargesForMod(mainItem)
|
||||
self.mainCharges = self._getAmmo(mainItem)
|
||||
if not self.mainCharges:
|
||||
return False
|
||||
|
||||
@@ -39,186 +34,81 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return "Charge"
|
||||
return 'Charge'
|
||||
|
||||
def getChargesForMod(self, mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
def _getAmmo(self, mod):
|
||||
if mod.itemID is None:
|
||||
return set()
|
||||
typeID = mod.item.ID
|
||||
if typeID in self.loadableCharges:
|
||||
return self.loadableCharges[typeID]
|
||||
chargeSet = self.loadableCharges.setdefault(typeID, set())
|
||||
# Do not try to grab it for modes which can also be passed as part of selection
|
||||
if isinstance(mod, Module):
|
||||
for charge in mod.getValidCharges():
|
||||
if sMkt.getPublicityByItem(charge):
|
||||
chargeSet.add(charge)
|
||||
return chargeSet
|
||||
if mod.itemID not in self.loadableChargesCache:
|
||||
self.loadableChargesCache[mod.itemID] = Ammo.getInstance().getModuleFlatAmmo(mod)
|
||||
return self.loadableChargesCache[mod.itemID]
|
||||
|
||||
def turretSorter(self, charge):
|
||||
damage = 0
|
||||
range_ = (self.module.item.getAttribute("maxRange")) * \
|
||||
(charge.getAttribute("weaponRangeMultiplier") or 1)
|
||||
falloff = (self.module.item.getAttribute("falloff") or 0) * \
|
||||
(charge.getAttribute("fallofMultiplier") or 1)
|
||||
for type_ in self.DAMAGE_TYPES:
|
||||
d = charge.getAttribute("%sDamage" % type_)
|
||||
if d > 0:
|
||||
damage += d
|
||||
|
||||
# Take optimal and falloff as range factor
|
||||
rangeFactor = range_ + falloff
|
||||
|
||||
return - rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
def missileSorter(self, charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = self.damageInfo(charge)
|
||||
# Find its position in sort list
|
||||
position = self.MISSILE_ORDER.index(chargeDamageType)
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
def damageInfo(self, charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
damageMap = {}
|
||||
totalDamage = 0
|
||||
# Fill them with the data about charge
|
||||
for damageType in self.DAMAGE_TYPES:
|
||||
currentDamage = charge.getAttribute("{0}Damage".format(damageType)) or 0
|
||||
damageMap[damageType] = currentDamage
|
||||
totalDamage += currentDamage
|
||||
# Detect type of ammo
|
||||
chargeDamageType = None
|
||||
for damageType in damageMap:
|
||||
# If all damage belongs to certain type purely, set appropriate
|
||||
# ammoType
|
||||
if damageMap[damageType] == totalDamage:
|
||||
chargeDamageType = damageType
|
||||
break
|
||||
# Else consider ammo as mixed damage
|
||||
if chargeDamageType is None:
|
||||
chargeDamageType = "mixed"
|
||||
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
@staticmethod
|
||||
def numericConverter(string):
|
||||
return int(string) if string.isdigit() else string
|
||||
|
||||
def nameSorter(self, charge):
|
||||
parts = charge.name.split(" ")
|
||||
return list(map(self.numericConverter, parts))
|
||||
|
||||
def addCharge(self, menu, charge):
|
||||
def _addCharge(self, menu, charge):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
name = charge.name if charge is not None else "Empty"
|
||||
self.chargeIds[id_] = charge
|
||||
name = charge.name if charge is not None else 'Empty'
|
||||
self.chargeEventMap[id_] = charge
|
||||
item = wx.MenuItem(menu, id_, name)
|
||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
||||
item.charge = charge
|
||||
if charge is not None and charge.iconID is not None:
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, 'icons')
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def addSeperator(m, text):
|
||||
def _addSeparator(m, text):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
m = wx.Menu()
|
||||
self.chargeIds = {}
|
||||
hardpoint = self.module.hardpoint
|
||||
moduleName = self.module.item.name
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
||||
self.addSeperator(m, "Long Range")
|
||||
items = []
|
||||
range_ = None
|
||||
nameBase = None
|
||||
sub = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.turretSorter)
|
||||
for charge in chargesSorted:
|
||||
if "civilian" in charge.name.lower():
|
||||
continue
|
||||
currBase = charge.name.rsplit()[-2:]
|
||||
currRange = charge.getAttribute("weaponRangeMultiplier")
|
||||
if nameBase is None or range_ != currRange or nameBase != currBase:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
sub = None
|
||||
base = charge
|
||||
nameBase = currBase
|
||||
range_ = currRange
|
||||
item = self.addCharge(rootMenu if msw else m, charge)
|
||||
items.append(item)
|
||||
msw = True if 'wxMSW' in wx.PlatformInfo else False
|
||||
menu = wx.Menu()
|
||||
self.chargeEventMap = {}
|
||||
modType, chargeDict = Ammo.getInstance().getModuleStructuredAmmo(self.module, ammo=self.mainCharges)
|
||||
if modType == 'ddTurret':
|
||||
self._addSeparator(menu, 'Long Range')
|
||||
menuItems = []
|
||||
for charges in chargeDict.values():
|
||||
if len(charges) == 1:
|
||||
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
|
||||
else:
|
||||
if sub is None and item and base:
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, base))
|
||||
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
for item in items:
|
||||
m.Append(item)
|
||||
|
||||
self.addSeperator(m, "Short Range")
|
||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
type_ = None
|
||||
sub = None
|
||||
defender = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
|
||||
for charge in chargesSorted:
|
||||
currType = self.damageInfo(charge)[0]
|
||||
|
||||
if currType != type_ or type_ is None:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
type_ = currType
|
||||
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
|
||||
bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
m.Append(item)
|
||||
|
||||
if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"):
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
else:
|
||||
defender = charge
|
||||
|
||||
if defender is not None:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, defender))
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
else:
|
||||
chargesSorted = sorted(self.mainCharges, key=self.nameSorter)
|
||||
for charge in chargesSorted:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, charge))
|
||||
|
||||
m.Append(self.addCharge(rootMenu if msw else m, None))
|
||||
return m
|
||||
baseCharge = charges[0]
|
||||
menuItem = self._addCharge(rootMenu if msw else menu, baseCharge)
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self._addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||
self._addSeparator(subMenu, 'More Damage')
|
||||
for menuItem in menuItems:
|
||||
menu.Append(menuItem)
|
||||
self._addSeparator(menu, 'Short Range')
|
||||
elif modType == 'ddMissile':
|
||||
menuItems = []
|
||||
for chargeCatName, charges in chargeDict.items():
|
||||
menuItem = wx.MenuItem(menu, wx.ID_ANY, chargeCatName.capitalize())
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self._addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||
self._addSeparator(subMenu, 'More Damage')
|
||||
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))
|
||||
menu.Append(self._addCharge(rootMenu if msw else menu, None))
|
||||
return menu
|
||||
|
||||
def handleAmmoSwitch(self, event):
|
||||
charge = self.chargeIds.get(event.Id, False)
|
||||
charge = self.chargeEventMap.get(event.Id, False)
|
||||
if charge is False:
|
||||
event.Skip()
|
||||
return
|
||||
@@ -254,7 +144,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
positions = []
|
||||
for position, mod in enumerate(modContainer):
|
||||
if mod in self.selection:
|
||||
modCharges = self.getChargesForMod(mod)
|
||||
modCharges = self._getAmmo(mod)
|
||||
if modCharges.issubset(self.mainCharges):
|
||||
positions.append(position)
|
||||
self.mainFrame.command.Submit(command(
|
||||
|
||||
@@ -126,10 +126,13 @@ class AttributeSlider(wx.Panel):
|
||||
def SetValue(self, value, post_event=True, affect_modified_flag=True):
|
||||
self.ctrl.SetValue(value)
|
||||
invert_factor = -1 if self.inverse else 1
|
||||
if value >= self.base_value:
|
||||
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
||||
else:
|
||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
|
||||
try:
|
||||
if value >= self.base_value:
|
||||
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
||||
else:
|
||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
|
||||
except ZeroDivisionError:
|
||||
slider_percentage = 0
|
||||
self.slider.SetValue(slider_percentage)
|
||||
if post_event:
|
||||
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage, affect_modified_flag=affect_modified_flag))
|
||||
|
||||
@@ -35,7 +35,7 @@ class ItemDescription(wx.Panel):
|
||||
self.Layout()
|
||||
|
||||
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.description.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.description.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
self.popupMenu = wx.Menu()
|
||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||
@@ -50,7 +50,7 @@ class ItemDescription(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -93,6 +93,8 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
|
||||
first = True
|
||||
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||
if m.baseValue == 0:
|
||||
continue
|
||||
if not first:
|
||||
sizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||
first = False
|
||||
|
||||
@@ -14,7 +14,7 @@ class ItemTraits(wx.Panel):
|
||||
self.traits.SetPage(item.traits.traitText)
|
||||
|
||||
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.traits.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.traits.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.Layout()
|
||||
@@ -32,7 +32,7 @@ class ItemTraits(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from eos.saveddata.module import Module
|
||||
import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
from config import slotColourMap
|
||||
from eos.saveddata.module import Module
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.display import Display
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.attribute import Attribute
|
||||
from service.fit import Fit
|
||||
from config import slotColourMap
|
||||
from service.market import Market
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -170,8 +171,8 @@ class ItemView(Display):
|
||||
def scheduleSearch(self, event=None):
|
||||
self.searchTimer.Stop() # Cancel any pending timers
|
||||
search = self.marketBrowser.search.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Re-select market group if search query has zero length
|
||||
if len(realsearch) == 0:
|
||||
self.selectionMade('search')
|
||||
@@ -193,30 +194,15 @@ class ItemView(Display):
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
# If we're no longer searching, dump the results
|
||||
if self.marketBrowser.mode != 'search':
|
||||
return
|
||||
items = Market.getItems(itemIDs)
|
||||
self.updateItemStore(items)
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def itemSort(self, item):
|
||||
sMkt = self.sMkt
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
def contextMenu(self, event):
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
self.ensureSelection(clickedPos)
|
||||
@@ -239,7 +225,7 @@ class ItemView(Display):
|
||||
self.unselectAll()
|
||||
# Perform sorting, using item's meta levels besides other stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
# Mark current item list as active
|
||||
self.active = items
|
||||
# Show them
|
||||
@@ -249,12 +235,10 @@ class ItemView(Display):
|
||||
if len(items) > 1:
|
||||
# Re-sort stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
for i, item in enumerate(items[:9]):
|
||||
# set shortcut info for first 9 modules
|
||||
item.marketShortcut = i + 1
|
||||
|
||||
Display.refresh(self, items)
|
||||
|
||||
def columnBackground(self, colItem, item):
|
||||
|
||||
@@ -60,7 +60,8 @@ class MarketTree(wx.TreeCtrl):
|
||||
# If market should have items but it doesn't, do not show it
|
||||
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
|
||||
continue
|
||||
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
|
||||
icon = sMkt.getIconByMarketGroup(childMktGrp)
|
||||
iconId = -1 if icon is None else self.addImage(icon)
|
||||
try:
|
||||
childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
|
||||
@@ -86,7 +86,7 @@ class PFSearchBox(wx.Window):
|
||||
|
||||
def OnKeyPress(self, event):
|
||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
|
||||
@@ -35,31 +35,31 @@ class PFGeneralPref(PreferenceView):
|
||||
# Database path
|
||||
self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, "pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stSetUserPath.Wrap(-1)
|
||||
mainSizer.Add(self.stSetUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stSetUserPath, 0, wx.ALL, 5)
|
||||
self.inputUserPath = wx.TextCtrl(panel, wx.ID_ANY, config.savePath, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.inputUserPath.SetEditable(False)
|
||||
self.inputUserPath.SetBackgroundColour((200, 200, 200))
|
||||
mainSizer.Add(self.inputUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
|
||||
mainSizer.Add(self.inputUserPath, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
# Save DB
|
||||
self.stFitDB = wx.StaticText(panel, wx.ID_ANY, "Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stFitDB.Wrap(-1)
|
||||
mainSizer.Add(self.stFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stFitDB, 0, wx.ALL, 5)
|
||||
|
||||
self.inputFitDB = wx.TextCtrl(panel, wx.ID_ANY, config.saveDB, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.inputFitDB.SetEditable(False)
|
||||
self.inputFitDB.SetBackgroundColour((200, 200, 200))
|
||||
mainSizer.Add(self.inputFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
|
||||
mainSizer.Add(self.inputFitDB, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
# Game Data DB
|
||||
self.stGameDB = wx.StaticText(panel, wx.ID_ANY, "Game Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stGameDB.Wrap(-1)
|
||||
mainSizer.Add(self.stGameDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stGameDB, 0, wx.ALL, 5)
|
||||
|
||||
self.inputGameDB = wx.TextCtrl(panel, wx.ID_ANY, config.gameDB, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.inputGameDB.SetEditable(False)
|
||||
self.inputGameDB.SetBackgroundColour((200, 200, 200))
|
||||
mainSizer.Add(self.inputGameDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
|
||||
mainSizer.Add(self.inputGameDB, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
self.cbsaveInRoot.SetValue(config.saveInRoot)
|
||||
self.cbsaveInRoot.Bind(wx.EVT_CHECKBOX, self.onCBsaveInRoot)
|
||||
|
||||
@@ -50,7 +50,7 @@ class PFHTMLExportPref(PreferenceView):
|
||||
|
||||
self.fileSelectButton = wx.Button(panel, -1, "Set export destination", pos=(0, 0))
|
||||
self.fileSelectButton.Bind(wx.EVT_BUTTON, self.selectHTMLExportFilePath)
|
||||
mainSizer.Add(self.fileSelectButton, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.fileSelectButton, 0, wx.ALL, 5)
|
||||
|
||||
self.stDesc4 = wx.StaticText(panel, wx.ID_ANY, self.desc4, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stDesc4.Wrap(dlgWidth - 50)
|
||||
|
||||
@@ -36,20 +36,20 @@ class PFGeneralPref(PreferenceView):
|
||||
# Database path
|
||||
self.stLogPath = wx.StaticText(panel, wx.ID_ANY, "Log file location:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stLogPath.Wrap(-1)
|
||||
mainSizer.Add(self.stLogPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stLogPath, 0, wx.ALL, 5)
|
||||
self.inputLogPath = wx.TextCtrl(panel, wx.ID_ANY, config.logPath, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.inputLogPath.SetEditable(False)
|
||||
self.inputLogPath.SetBackgroundColour((200, 200, 200))
|
||||
mainSizer.Add(self.inputLogPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
|
||||
mainSizer.Add(self.inputLogPath, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
import requests
|
||||
self.certPath = wx.StaticText(panel, wx.ID_ANY, "Cert Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.certPath .Wrap(-1)
|
||||
mainSizer.Add(self.certPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.certPath, 0, wx.ALL, 5)
|
||||
self.certPathCtrl = wx.TextCtrl(panel, wx.ID_ANY, requests.certs.where(), wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.certPathCtrl.SetEditable(False)
|
||||
self.certPathCtrl.SetBackgroundColour((200, 200, 200))
|
||||
mainSizer.Add(self.certPathCtrl, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
|
||||
mainSizer.Add(self.certPathCtrl, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
# Debug Logging
|
||||
self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, "Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
@@ -57,7 +57,7 @@ class PFGeneralPref(PreferenceView):
|
||||
|
||||
self.stDumpLogs = wx.StaticText(panel, wx.ID_ANY, "Pressing this button will cause all logs in memory to write to the log file:",
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
mainSizer.Add(self.stDumpLogs, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stDumpLogs, 0, wx.ALL, 5)
|
||||
self.btnDumpLogs = wx.Button(panel, wx.ID_ANY, "Dump All Logs", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.btnDumpLogs.Bind(wx.EVT_BUTTON, OnDumpLogs)
|
||||
mainSizer.Add(self.btnDumpLogs, 0, wx.ALIGN_LEFT, 5)
|
||||
|
||||
@@ -135,7 +135,7 @@ class PFNetworkPref(PreferenceView):
|
||||
self.stPSAutoDetected = wx.StaticText(panel, wx.ID_ANY, "Auto-detected: ", wx.DefaultPosition, wx.DefaultSize,
|
||||
0)
|
||||
self.stPSAutoDetected.Wrap(-1)
|
||||
mainSizer.Add(self.stPSAutoDetected, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
mainSizer.Add(self.stPSAutoDetected, 0, wx.ALL, 5)
|
||||
|
||||
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
btnSizer.AddStretchSpacer()
|
||||
|
||||
@@ -86,8 +86,8 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
|
||||
def OnScheduleSearch(self, event):
|
||||
search = self.BrowserSearchBox.GetValue()
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
minChars = 1 if isStringCjk(realsearch) else 3
|
||||
if len(realsearch) >= minChars:
|
||||
self.lastSearch = search
|
||||
|
||||
@@ -51,8 +51,7 @@ class FirepowerViewFull(StatsView):
|
||||
self.headerPanel = headerPanel
|
||||
hsizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
||||
self.stEff = wx.StaticText(self.headerPanel, wx.ID_ANY, "( Effective )")
|
||||
hsizer.Add(self.stEff)
|
||||
# self.headerPanel.GetParent().AddToggleItem(self.stEff)
|
||||
hsizer.Insert(0, self.stEff)
|
||||
|
||||
panel = "full"
|
||||
|
||||
@@ -130,9 +129,12 @@ class FirepowerViewFull(StatsView):
|
||||
self.panel.GetSizer().Layout()
|
||||
|
||||
# Remove effective label
|
||||
hsizer = self.headerPanel.GetSizer()
|
||||
hsizer.Hide(self.stEff)
|
||||
# self.stEff.Destroy()
|
||||
hsizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
||||
for i, c in enumerate(hsizer.Children):
|
||||
if c.GetWindow() is self.stEff:
|
||||
hsizer.Remove(i)
|
||||
self.stEff.Destroy()
|
||||
break
|
||||
|
||||
# Get the new view
|
||||
view = StatsView.getView("miningyieldViewFull")(self.parent)
|
||||
|
||||
@@ -123,6 +123,15 @@ class Miscellanea(ViewColumn):
|
||||
text = ' | '.join(i[0] for i in info)
|
||||
tooltip = ' and '.join(i[1] for i in info).capitalize()
|
||||
return text, tooltip
|
||||
elif itemGroup == "Vorton Projector":
|
||||
cloudSize = stuff.getModifiedItemAttr("aoeCloudSize")
|
||||
aoeVelocity = stuff.getModifiedItemAttr("aoeVelocity")
|
||||
if not cloudSize or not aoeVelocity:
|
||||
return "", None
|
||||
text = "{0}{1} | {2}{3}".format(formatAmount(cloudSize, 3, 0, 3), "m",
|
||||
formatAmount(aoeVelocity, 3, 0, 3), "m/s")
|
||||
tooltip = "Explosion radius and explosion velocity"
|
||||
return text, tooltip
|
||||
elif itemCategory == "Subsystem":
|
||||
slots = ("hi", "med", "low")
|
||||
info = []
|
||||
@@ -133,7 +142,7 @@ class Miscellanea(ViewColumn):
|
||||
return "+ " + ", ".join(info), "Slot Modifiers"
|
||||
elif (
|
||||
itemGroup in ("Energy Neutralizer", "Structure Energy Neutralizer") or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOENeut" in item.effects)
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOENeut" in item.effects)
|
||||
):
|
||||
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
cycleParams = stuff.getCycleParameters()
|
||||
@@ -182,7 +191,7 @@ class Miscellanea(ViewColumn):
|
||||
return text, tooltip
|
||||
elif (
|
||||
itemGroup in ("Stasis Web", "Stasis Webifying Drone", "Structure Stasis Webifier") or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOEWeb" in item.effects)
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEWeb" in item.effects)
|
||||
):
|
||||
speedFactor = stuff.getModifiedItemAttr("speedFactor")
|
||||
if not speedFactor:
|
||||
@@ -193,7 +202,7 @@ class Miscellanea(ViewColumn):
|
||||
elif (
|
||||
itemGroup == "Target Painter" or
|
||||
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectTargetPainter" in item.effects) or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOEPaint" in item.effects)
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEPaint" in item.effects)
|
||||
):
|
||||
sigRadBonus = stuff.getModifiedItemAttr("signatureRadiusBonus")
|
||||
if not sigRadBonus:
|
||||
@@ -204,7 +213,7 @@ class Miscellanea(ViewColumn):
|
||||
elif (
|
||||
itemGroup == "Sensor Dampener" or
|
||||
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectRemoteSensorDampener" in item.effects) or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOEDamp" in item.effects)
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEDamp" in item.effects)
|
||||
):
|
||||
lockRangeBonus = stuff.getModifiedItemAttr("maxTargetRangeBonus")
|
||||
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
|
||||
@@ -226,7 +235,7 @@ class Miscellanea(ViewColumn):
|
||||
return text, tooltip
|
||||
elif (
|
||||
itemGroup in ("Weapon Disruptor", "Structure Disruption Battery") or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOETrack" in item.effects)
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOETrack" in item.effects)
|
||||
):
|
||||
# Weapon disruption now covers both tracking and guidance (missile) disruptors
|
||||
# First get the attributes for tracking disruptors
|
||||
@@ -279,7 +288,8 @@ class Miscellanea(ViewColumn):
|
||||
"Heat Sink",
|
||||
"Ballistic Control system",
|
||||
"Structure Weapon Upgrade",
|
||||
"Entropic Radiation Sink"
|
||||
"Entropic Radiation Sink",
|
||||
"Vorton Projector Upgrade"
|
||||
):
|
||||
attrMap = {
|
||||
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
|
||||
@@ -287,7 +297,8 @@ class Miscellanea(ViewColumn):
|
||||
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
|
||||
"Ballistic Control system": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon")}
|
||||
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon"),
|
||||
"Vorton Projector Upgrade": ("damageMultiplier", "speedMultiplier", "Vorton projector")}
|
||||
dmgAttr, rofAttr, weaponName = attrMap[itemGroup]
|
||||
dmg = stuff.getModifiedItemAttr(dmgAttr)
|
||||
rof = stuff.getModifiedItemAttr(rofAttr)
|
||||
@@ -311,8 +322,8 @@ class Miscellanea(ViewColumn):
|
||||
tooltip = "Drone DPS boost"
|
||||
return text, tooltip
|
||||
elif (
|
||||
itemGroup in ("ECM", "Burst Jammer", "Burst Projectors", "Structure ECM Battery") or
|
||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOEECM" in item.effects)
|
||||
itemGroup in ("ECM", "Burst Jammer", "Structure ECM Battery") or
|
||||
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEECM" in item.effects)
|
||||
):
|
||||
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthBonus")
|
||||
ladar = stuff.getModifiedItemAttr("scanLadarStrengthBonus")
|
||||
@@ -680,6 +691,13 @@ class Miscellanea(ViewColumn):
|
||||
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
|
||||
)
|
||||
return text, tooltip
|
||||
elif itemGroup in ("Cargo Scanner", "Ship Scanner", "Survey Scanner"):
|
||||
duration = stuff.getModifiedItemAttr("duration")
|
||||
if not duration:
|
||||
return "", None
|
||||
text = "{}s".format(formatAmount(duration / 1000, 3, 0, 0))
|
||||
tooltip = "Scan duration"
|
||||
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"):
|
||||
|
||||
@@ -166,7 +166,7 @@ class FittingView(d.Display):
|
||||
self.hoveredRow = None
|
||||
self.hoveredColumn = None
|
||||
|
||||
self.Bind(wx.EVT_KEY_DOWN, self.kbEvent)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_MIDDLE_DOWN, self.click)
|
||||
@@ -377,23 +377,7 @@ class FittingView(d.Display):
|
||||
event.Skip()
|
||||
return
|
||||
batchOp = wx.GetMouseState().GetModifiers() == wx.MOD_ALT and getattr(event, 'allowBatch', None) is not False
|
||||
# If we've selected ammo, then apply to the selected module(s)
|
||||
if item.isCharge:
|
||||
positions = []
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if batchOp:
|
||||
for position, mod in enumerate(fit.modules):
|
||||
if isinstance(mod, Module) and not mod.isEmpty:
|
||||
positions.append(position)
|
||||
else:
|
||||
for mod in self.getSelectedMods():
|
||||
if mod.isEmpty or mod not in fit.modules:
|
||||
continue
|
||||
positions.append(fit.modules.index(mod))
|
||||
if len(positions) > 0:
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalModuleChargesCommand(
|
||||
fitID=fitID, positions=positions, chargeItemID=itemID))
|
||||
elif (item.isModule and not batchOp) or item.isSubsystem:
|
||||
if (item.isModule and not batchOp) or item.isSubsystem:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID))
|
||||
elif item.isModule and batchOp:
|
||||
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID))
|
||||
@@ -578,7 +562,10 @@ class FittingView(d.Display):
|
||||
|
||||
if sFit.serviceFittingOptions["rackSlots"]:
|
||||
# flag to know when to add blanks, based on previous slot
|
||||
slotDivider = None if sFit.serviceFittingOptions["rackLabels"] else self.mods[0].slot
|
||||
if sFit.serviceFittingOptions["rackLabels"] or len(self.mods) == 0:
|
||||
slotDivider = None
|
||||
else:
|
||||
slotDivider = self.mods[0].slot
|
||||
|
||||
# first loop finds where slot dividers must go before modifying self.mods
|
||||
for i, mod in enumerate(self.mods):
|
||||
|
||||
@@ -302,8 +302,8 @@ class ItemView(d.Display):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
search = self.searchBox.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Show nothing if query is too short
|
||||
if len(realsearch) < 3:
|
||||
self.clearSearch()
|
||||
@@ -311,12 +311,12 @@ class ItemView(d.Display):
|
||||
|
||||
sMkt.searchItems(search, self.populateSearch, 'implants')
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
if not self.IsShown():
|
||||
self.parent.availableImplantsTree.Hide()
|
||||
self.Show()
|
||||
self.parent.Layout()
|
||||
|
||||
items = Market.getItems(itemIDs)
|
||||
items = [i for i in items if i.group.name != 'Booster']
|
||||
self.items = sorted(list(items), key=lambda i: i.name)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from wx.lib.agw.floatspin import FloatSpin
|
||||
|
||||
import config
|
||||
import gui.globalEvents as GE
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
|
||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||
@@ -312,6 +312,7 @@ class CharacterEditor(AuxiliaryFrame):
|
||||
|
||||
|
||||
class SkillTreeView(wx.Panel):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
|
||||
style=wx.TAB_TRAVERSAL)
|
||||
@@ -402,7 +403,7 @@ class SkillTreeView(wx.Panel):
|
||||
setattr(self, "{}Btn".format(name.lower()), btn)
|
||||
btn.Enable(True)
|
||||
btn.SetToolTip("%s skills %s clipboard" % (name, direction))
|
||||
bSizerButtons.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT | wx.ALL, 5)
|
||||
bSizerButtons.Add(btn, 0, wx.ALL, 5)
|
||||
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Skills".format(name.lower())))
|
||||
|
||||
pmainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)
|
||||
@@ -611,12 +612,16 @@ class SkillTreeView(wx.Panel):
|
||||
|
||||
def spawnMenu(self, event):
|
||||
item = event.GetItem()
|
||||
itemData = self.skillTreeListCtrl.GetItemData(item)
|
||||
if itemData is None:
|
||||
return
|
||||
|
||||
self.skillTreeListCtrl.Select(item)
|
||||
thing = self.skillTreeListCtrl.GetFirstChild(item).IsOk()
|
||||
if thing:
|
||||
return
|
||||
|
||||
id = self.skillTreeListCtrl.GetItemData(item)[1]
|
||||
id = itemData[1]
|
||||
eveItem = Market.getInstance().getItem(id)
|
||||
|
||||
srcContext = "skillItem"
|
||||
@@ -833,7 +838,7 @@ class APIView(wx.Panel):
|
||||
def fetchSkills(self, evt):
|
||||
sChar = Character.getInstance()
|
||||
char = self.charEditor.entityEditor.getActiveEntity()
|
||||
sChar.apiFetch(char.ID, self.__fetchCallback)
|
||||
sChar.apiFetch(char.ID, APIView.fetchCallback)
|
||||
|
||||
def addCharacter(self, event):
|
||||
sEsi = Esi.getInstance()
|
||||
@@ -899,7 +904,8 @@ class APIView(wx.Panel):
|
||||
if event is not None:
|
||||
event.Skip()
|
||||
|
||||
def __fetchCallback(self, e=None):
|
||||
@staticmethod
|
||||
def fetchCallback(e=None):
|
||||
if e:
|
||||
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
|
||||
exc_type, exc_value, exc_trace = e
|
||||
|
||||
@@ -402,6 +402,10 @@ class _TabRenderer:
|
||||
width = max(width, self.min_width)
|
||||
height = max(height, self.min_height)
|
||||
|
||||
cur_width, cur_height = self.tab_size
|
||||
if (width == cur_width) and (height == cur_height):
|
||||
return
|
||||
|
||||
self.tab_size = (width, height)
|
||||
self.InitTab()
|
||||
|
||||
@@ -975,9 +979,6 @@ class _TabsContainer(wx.Panel):
|
||||
sel_tab = self.tabs.index(tab)
|
||||
self.Parent.SetSelection(sel_tab)
|
||||
|
||||
wx.PostEvent(self.Parent, PageChanged(self.tabs.index(old_sel_tab),
|
||||
self.tabs.index(tab)))
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -177,7 +177,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
|
||||
def exportEsi(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportESI(fit, callback)
|
||||
Port.exportESI(fit, True, callback)
|
||||
|
||||
def exportXml(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
@@ -26,7 +26,7 @@ import wx
|
||||
from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.builtinShipBrowser.events import FitSelected
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import wx
|
||||
from logbook import Logger
|
||||
|
||||
import config
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from service.prereqsCheck import version_block
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class ErrorFrame(AuxiliaryFrame):
|
||||
self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, version + version_block.strip(), wx.DefaultPosition,
|
||||
(-1, 400), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP)
|
||||
self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL))
|
||||
mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5)
|
||||
mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.ALL, 5)
|
||||
self.errorTextCtrl.AppendText("\n")
|
||||
self.errorTextCtrl.Layout()
|
||||
|
||||
|
||||
@@ -5,16 +5,20 @@ import requests
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import config
|
||||
import gui.globalEvents as GE
|
||||
from eos.db import getItem
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.display import Display
|
||||
from gui.characterEditor import APIView
|
||||
from service.character import Character
|
||||
from service.esi import Esi
|
||||
from service.esiAccess import APIException
|
||||
from service.fit import Fit
|
||||
from service.port import Port
|
||||
from service.port.esi import ESIExportException
|
||||
from service.settings import EsiSettings
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -204,7 +208,7 @@ class ExportToEve(AuxiliaryFrame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition,
|
||||
size=wx.Size(400, 120) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 100), resizeable=True)
|
||||
size=wx.Size(400, 140) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 115), resizeable=True)
|
||||
|
||||
self.mainFrame = parent
|
||||
|
||||
@@ -221,6 +225,11 @@ class ExportToEve(AuxiliaryFrame):
|
||||
|
||||
mainSizer.Add(hSizer, 0, wx.EXPAND, 5)
|
||||
|
||||
self.exportChargesCb = wx.CheckBox(self, wx.ID_ANY, 'Export Loaded Charges', wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.exportChargesCb.SetValue(EsiSettings.getInstance().get('exportCharges'))
|
||||
self.exportChargesCb.Bind(wx.EVT_CHECKBOX, self.OnChargeExportChange)
|
||||
mainSizer.Add(self.exportChargesCb, 0, 0, 5)
|
||||
|
||||
self.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting)
|
||||
|
||||
self.statusbar = wx.StatusBar(self)
|
||||
@@ -236,6 +245,10 @@ class ExportToEve(AuxiliaryFrame):
|
||||
|
||||
self.Center(wx.BOTH)
|
||||
|
||||
def OnChargeExportChange(self, event):
|
||||
EsiSettings.getInstance().set('exportCharges', self.exportChargesCb.GetValue())
|
||||
event.Skip()
|
||||
|
||||
def updateCharList(self):
|
||||
sEsi = Esi.getInstance()
|
||||
chars = sEsi.getSsoCharacters()
|
||||
@@ -271,8 +284,9 @@ class ExportToEve(AuxiliaryFrame):
|
||||
sEsi = Esi.getInstance()
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
exportCharges = self.exportChargesCb.GetValue()
|
||||
try:
|
||||
data = sPort.exportESI(sFit.getFit(fitID))
|
||||
data = sPort.exportESI(sFit.getFit(fitID), exportCharges)
|
||||
except ESIExportException as e:
|
||||
msg = str(e)
|
||||
if not msg:
|
||||
@@ -335,7 +349,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
self.addBtn = wx.Button(self, wx.ID_ANY, "Add Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.addBtn, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
self.deleteBtn = wx.Button(self, wx.ID_ANY, "Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.deleteBtn = wx.Button(self, wx.ID_ANY, "Remove Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
mainSizer.Add(btnSizer, 0, wx.EXPAND, 5)
|
||||
@@ -355,6 +369,16 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
|
||||
def ssoLogin(self, event):
|
||||
self.popCharList()
|
||||
sChar = Character.getInstance()
|
||||
# Update existing pyfa character, if it doesn't exist - create new
|
||||
char = sChar.getCharacter(event.character.characterName)
|
||||
newChar = False
|
||||
if char is None:
|
||||
char = sChar.new(event.character.characterName)
|
||||
newChar = True
|
||||
char.setSsoCharacter(event.character, config.getClientSecret())
|
||||
sChar.apiFetch(char.ID, APIView.fetchCallback)
|
||||
wx.PostEvent(self.mainFrame, GE.CharListUpdated())
|
||||
event.Skip()
|
||||
|
||||
def kbEvent(self, event):
|
||||
|
||||
@@ -40,9 +40,6 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
position=fit.modules.index(oldMod),
|
||||
newModInfo=self.newModInfo)
|
||||
return self.subsystemCmd.Do()
|
||||
if not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
return False
|
||||
fit.modules.append(newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to append to list')
|
||||
@@ -52,6 +49,13 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
# fits() sometimes relies on recalculated on-item attributes, such as fax cap
|
||||
# booster limitation, so we have to check it after recalculating and remove the
|
||||
# module if the check has failed
|
||||
if not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
self.Undo()
|
||||
return False
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
return True
|
||||
|
||||
@@ -63,7 +67,7 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
if self.savedPosition is None:
|
||||
return False
|
||||
from .localRemove import CalcRemoveLocalModulesCommand
|
||||
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.savedPosition], recalc=False)
|
||||
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.savedPosition], recalc=False, clearTail=True)
|
||||
if not cmd.Do():
|
||||
return False
|
||||
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
||||
|
||||
@@ -3,7 +3,7 @@ from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
from eos.const import FittingSlot
|
||||
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates
|
||||
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates, restoreRemovedDummies
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, positions, recalc=True):
|
||||
def __init__(self, fitID, positions, recalc=True, clearTail=False):
|
||||
wx.Command.__init__(self, True, 'Remove Module')
|
||||
self.fitID = fitID
|
||||
self.positions = positions
|
||||
self.recalc = recalc
|
||||
self.clearTail = clearTail
|
||||
self.savedSubInfos = None
|
||||
self.savedModInfos = None
|
||||
self.savedStateCheckChanges = None
|
||||
self.savedTail = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing removal of local modules from positions {} on fit {}'.format(self.positions, self.fitID))
|
||||
@@ -40,6 +42,9 @@ class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
if len(self.savedSubInfos) == 0 and len(self.savedModInfos) == 0:
|
||||
return False
|
||||
|
||||
if self.clearTail:
|
||||
self.savedTail = fit.clearTail()
|
||||
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
@@ -76,6 +81,7 @@ class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
if not any(results):
|
||||
return False
|
||||
restoreCheckedStates(fit, self.savedStateCheckChanges)
|
||||
restoreRemovedDummies(fit, self.savedTail)
|
||||
return True
|
||||
|
||||
@property
|
||||
|
||||
@@ -39,7 +39,17 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
||||
if newMod.slot != oldMod.slot:
|
||||
return False
|
||||
# Dummy it out in case the next bit fails
|
||||
fit.modules.free(self.position)
|
||||
fit.modules.replace(self.position, newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to replace in list')
|
||||
self.Undo()
|
||||
return False
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
if not self.ignoreRestrictions and not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
self.Undo()
|
||||
@@ -52,17 +62,6 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
||||
pyfalog.warning('Invalid charge')
|
||||
self.Undo()
|
||||
return False
|
||||
fit.modules.replace(self.position, newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to replace in list')
|
||||
self.Undo()
|
||||
return False
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
|
||||
@@ -324,7 +324,8 @@ def activeStateLimit(itemIdentity):
|
||||
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
|
||||
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
|
||||
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
|
||||
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively'
|
||||
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively',
|
||||
'cargoScan', 'shipScan', 'surveyScan'
|
||||
}.intersection(item.effects):
|
||||
return FittingModuleState.ONLINE
|
||||
return FittingModuleState.ACTIVE
|
||||
@@ -353,6 +354,8 @@ def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
|
||||
|
||||
|
||||
def restoreRemovedDummies(fit, dummyInfo):
|
||||
if dummyInfo is None:
|
||||
return
|
||||
# Need this to properly undo the case when removal of subsystems removes dummy slots
|
||||
for position in sorted(dummyInfo):
|
||||
slot = dummyInfo[position]
|
||||
|
||||
@@ -23,7 +23,7 @@ import wx
|
||||
import config
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.module import Module
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
||||
from gui.builtinItemStatsViews.itemAttributes import ItemParams
|
||||
|
||||
@@ -110,8 +110,6 @@ class OpenFitsThread(threading.Thread):
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
time.sleep(0.5) # Give GUI some time to finish drawing
|
||||
|
||||
# `startup` tells FitSpawner that we are loading fits are startup, and
|
||||
# has 3 values:
|
||||
# False = Set as default in FitSpawner itself, never set here
|
||||
@@ -610,7 +608,9 @@ class MainFrame(wx.Frame):
|
||||
(wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext),
|
||||
(wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev),
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev)
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev),
|
||||
|
||||
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, ord("Z"), wx.ID_REDO)
|
||||
]
|
||||
|
||||
# Ctrl/Cmd+# for addition pane selection
|
||||
@@ -678,8 +678,8 @@ class MainFrame(wx.Frame):
|
||||
|
||||
def toggleOverrides(self, event):
|
||||
ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled
|
||||
|
||||
wx.PostEvent(self, GE.FitChanged(fitIDs=(self.getActiveFit(),)))
|
||||
changedFitIDs = Fit.getInstance().processOverrideToggle()
|
||||
wx.PostEvent(self, GE.FitChanged(fitIDs=changedFitIDs))
|
||||
menu = self.GetMenuBar()
|
||||
menu.SetLabel(menu.toggleOverridesId,
|
||||
"&Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "&Turn Overrides On")
|
||||
|
||||
@@ -80,8 +80,8 @@ class MainMenuBar(wx.MenuBar):
|
||||
fitMenu = wx.Menu()
|
||||
self.Append(fitMenu, "Fi&t")
|
||||
|
||||
fitMenu.Append(wx.ID_UNDO)
|
||||
fitMenu.Append(wx.ID_REDO)
|
||||
fitMenu.Append(wx.ID_UNDO, "&Undo\tCTRL+Z", "Undo the most recent action")
|
||||
fitMenu.Append(wx.ID_REDO, "&Redo\tCTRL+Y", "Redo the most recent undone action")
|
||||
|
||||
fitMenu.AppendSeparator()
|
||||
fitMenu.Append(wx.ID_COPY, "&To Clipboard\tCTRL+C", "Export a fit to the clipboard")
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
@@ -181,7 +181,7 @@ class DmgPatternEditor(AuxiliaryFrame):
|
||||
setattr(self, name, btn)
|
||||
btn.Enable(True)
|
||||
btn.SetToolTip("%s patterns %s clipboard" % (name, direction))
|
||||
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
|
||||
footerSizer.Add(btn, 0)
|
||||
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
|
||||
|
||||
if not self.entityEditor.checkEntitiesExist():
|
||||
|
||||
@@ -10,9 +10,10 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
import gui.display as d
|
||||
import gui.globalEvents as GE
|
||||
from eos.db.gamedata.queries import getAttributeInfo, getItem
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.marketBrowser import SearchBox
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
@@ -170,12 +171,15 @@ class ItemView(d.Display):
|
||||
d.Display.__init__(self, parent)
|
||||
self.activeItems = []
|
||||
|
||||
self.searchTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.scheduleSearch, self.searchTimer)
|
||||
|
||||
self.searchBox = parent.Parent.Parent.searchBox
|
||||
# Bind search actions
|
||||
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.delaySearch)
|
||||
|
||||
self.update(Market.getInstance().getItemsWithOverrides())
|
||||
|
||||
@@ -188,12 +192,17 @@ class ItemView(d.Display):
|
||||
if updateDisplay:
|
||||
self.update(Market.getInstance().getItemsWithOverrides())
|
||||
|
||||
def delaySearch(self, evt):
|
||||
sFit = Fit.getInstance()
|
||||
self.searchTimer.Stop()
|
||||
self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True)
|
||||
|
||||
def scheduleSearch(self, event=None):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
search = self.searchBox.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Show nothing if query is too short
|
||||
if len(realsearch) < 3:
|
||||
self.clearSearch()
|
||||
@@ -204,21 +213,10 @@ class ItemView(d.Display):
|
||||
def itemSort(self, item):
|
||||
sMkt = Market.getInstance()
|
||||
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
return (not isFittable, *sMkt.itemSort(item))
|
||||
|
||||
return not isFittable, catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
items = Market.getItems(itemIDs)
|
||||
self.update(items)
|
||||
|
||||
def populate(self, items):
|
||||
|
||||
@@ -254,7 +254,6 @@ class PyGauge(wx.Window):
|
||||
w = rect.width
|
||||
else:
|
||||
w = rect.width * (float(value) / 100)
|
||||
|
||||
r = copy.copy(rect)
|
||||
r.width = w
|
||||
dc.DrawRectangle(r)
|
||||
@@ -315,7 +314,8 @@ class PyGauge(wx.Window):
|
||||
color,
|
||||
gradient_color
|
||||
)
|
||||
dc.DrawBitmap(gradient_bitmap, r.left, r.top)
|
||||
if gradient_bitmap is not None:
|
||||
dc.DrawBitmap(gradient_bitmap, r.left, r.top)
|
||||
|
||||
# font stuff begins here
|
||||
dc.SetFont(self.font)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
@@ -159,7 +159,7 @@ class ImplantSetEditor(AuxiliaryFrame):
|
||||
setattr(self, name, btn)
|
||||
btn.Enable(True)
|
||||
btn.SetToolTip("%s implant sets %s clipboard" % (name, direction))
|
||||
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
|
||||
footerSizer.Add(btn, 0)
|
||||
|
||||
mainSizer.Add(footerSizer, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ class ShipBrowser(wx.Panel):
|
||||
"amarr", "caldari", "gallente", "minmatar",
|
||||
"sisters", "ore", "concord",
|
||||
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
|
||||
"jove", "upwell", "triglavian", None
|
||||
"jove", "triglavian", "upwell", None
|
||||
]
|
||||
|
||||
def raceNameKey(self, ship):
|
||||
|
||||
@@ -148,7 +148,7 @@ class StatsPane(wx.Panel):
|
||||
sizer.AddStretchSpacer()
|
||||
# Add menu
|
||||
header_menu = wx.StaticText(tp.GetHeaderPanel(), wx.ID_ANY, "\u2630", size=wx.Size((10, -1)))
|
||||
sizer.Add(header_menu , 0, wx.EXPAND | wx.RIGHT | wx.ALIGN_RIGHT, 5)
|
||||
sizer.Add(header_menu , 0, wx.EXPAND | wx.RIGHT, 5)
|
||||
|
||||
header_menu.Bind(wx.EVT_CONTEXT_MENU, handler)
|
||||
header_menu.Bind(wx.EVT_LEFT_UP, handler)
|
||||
|
||||
@@ -27,7 +27,7 @@ from logbook import Logger
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
@@ -183,7 +183,7 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
ttText, unitText = self.ATTRIBUTES[attr]
|
||||
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % attr, "gui"))
|
||||
bmp.SetToolTip(wx.ToolTip(ttText))
|
||||
miscAttrSizer.Add(bmp, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, leftPad)
|
||||
miscAttrSizer.Add(bmp, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, leftPad)
|
||||
# set text edit
|
||||
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
|
||||
editBox.SetToolTip(wx.ToolTip(ttText))
|
||||
@@ -231,7 +231,7 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
setattr(self, name, btn)
|
||||
btn.Enable(True)
|
||||
btn.SetToolTip("%s profiles %s clipboard" % (name, direction))
|
||||
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
|
||||
footerSizer.Add(btn, 0)
|
||||
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
|
||||
|
||||
if not self.entityEditor.checkEntitiesExist():
|
||||
|
||||
@@ -35,6 +35,8 @@ def DrawFilledBitmap(width, height, color):
|
||||
|
||||
|
||||
def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4):
|
||||
if width == 0 or height == 0:
|
||||
return None
|
||||
canvas = wx.Bitmap(width, height)
|
||||
|
||||
mdc = wx.MemoryDC()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import math
|
||||
|
||||
from eos.utils.round import roundToPrec, roundDec
|
||||
|
||||
|
||||
def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=False, unitName=None):
|
||||
"""
|
||||
@@ -97,29 +99,3 @@ def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=Fal
|
||||
else:
|
||||
result = "{}{} {}{}".format(sign, mantissa, suffix, unitName)
|
||||
return result
|
||||
|
||||
|
||||
def roundToPrec(val, prec, nsValue=None):
|
||||
"""
|
||||
nsValue: custom value which should be used to determine normalization shift
|
||||
"""
|
||||
# We're not rounding integers anyway
|
||||
# Also make sure that we do not ask to calculate logarithm of zero
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||
# But we don't want to round integers
|
||||
if roundFactor < 0:
|
||||
roundFactor = 0
|
||||
# Do actual rounding
|
||||
val = round(val, roundFactor)
|
||||
# Make sure numbers with .0 part designating float don't get through
|
||||
if int(val) == val:
|
||||
val = int(val)
|
||||
return val
|
||||
|
||||
|
||||
def roundDec(val, prec):
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
return round(val, prec)
|
||||
|
||||
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 452 B |
|
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 677 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 725 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 738 B After Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 683 B After Width: | Height: | Size: 781 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 915 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 898 B |