Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ae9e0891 | ||
|
|
2ed5dbc3c7 | ||
|
|
bca8ba3114 | ||
|
|
174ac97682 | ||
|
|
2d9e873d42 | ||
|
|
33377357f6 | ||
|
|
3ee0ee7e40 | ||
|
|
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')
|
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
|
||||||
if ROOT_DIR not in sys.path:
|
if ROOT_DIR not in sys.path:
|
||||||
sys.path.insert(0, ROOT_DIR)
|
sys.path.insert(0, ROOT_DIR)
|
||||||
GAMEDATA_SCHEMA_VERSION = 3
|
GAMEDATA_SCHEMA_VERSION = 4
|
||||||
|
|
||||||
|
|
||||||
def db_needs_update():
|
def db_needs_update():
|
||||||
@@ -122,9 +122,13 @@ def update_db():
|
|||||||
if (
|
if (
|
||||||
# Apparently people really want Civilian modules available
|
# Apparently people really want Civilian modules available
|
||||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
(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
|
row['published'] = True
|
||||||
|
# Nearly useless and clutter search results too much
|
||||||
|
elif row['typeName'].startswith('Limited Synth '):
|
||||||
|
row['published'] = False
|
||||||
|
|
||||||
newData = []
|
newData = []
|
||||||
for row in data:
|
for row in data:
|
||||||
@@ -136,11 +140,10 @@ def update_db():
|
|||||||
row['typeID'] in (41549, 41548, 41551, 41550) or
|
row['typeID'] in (41549, 41548, 41551, 41550) or
|
||||||
# Abyssal weather (environment)
|
# Abyssal weather (environment)
|
||||||
row['groupID'] in (
|
row['groupID'] in (
|
||||||
1882,
|
1882,
|
||||||
1975,
|
1975,
|
||||||
1971,
|
1971,
|
||||||
# the "container" for the abyssal environments
|
1983) # the "container" for the abyssal environments
|
||||||
1983)
|
|
||||||
):
|
):
|
||||||
newData.append(row)
|
newData.append(row)
|
||||||
|
|
||||||
@@ -168,16 +171,26 @@ def update_db():
|
|||||||
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||||
newData = []
|
newData = []
|
||||||
for row in eveTypesData:
|
seenKeys = set()
|
||||||
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
|
||||||
if attrName in row:
|
def checkKey(key):
|
||||||
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
if key in seenKeys:
|
||||||
|
return False
|
||||||
|
seenKeys.add(key)
|
||||||
|
return True
|
||||||
|
|
||||||
for typeData in data:
|
for typeData in data:
|
||||||
if typeData['typeID'] not in eveTypeIds:
|
if typeData['typeID'] not in eveTypeIds:
|
||||||
continue
|
continue
|
||||||
for row in typeData.get('dogmaAttributes', ()):
|
for row in typeData.get('dogmaAttributes', ()):
|
||||||
row['typeID'] = typeData['typeID']
|
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)
|
_addRows(newData, eos.gamedata.Attribute)
|
||||||
return newData
|
return newData
|
||||||
|
|
||||||
@@ -315,8 +328,8 @@ def update_db():
|
|||||||
|
|
||||||
def composeReqSkills(raw):
|
def composeReqSkills(raw):
|
||||||
reqSkills = {}
|
reqSkills = {}
|
||||||
for skillTypeID, skillLevels in raw.items():
|
for skillTypeID, skillLevel in raw.items():
|
||||||
reqSkills[int(skillTypeID)] = skillLevels[0]
|
reqSkills[int(skillTypeID)] = skillLevel
|
||||||
return reqSkills
|
return reqSkills
|
||||||
|
|
||||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||||
@@ -472,7 +485,7 @@ def update_db():
|
|||||||
continue
|
continue
|
||||||
typeName = row.get('typeName', '')
|
typeName = row.get('typeName', '')
|
||||||
# Regular sets matching
|
# 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:
|
if m:
|
||||||
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
|
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
|
||||||
# Special set matching
|
# Special set matching
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ added_files = [
|
|||||||
|
|
||||||
import_these = [
|
import_these = [
|
||||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
'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")
|
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ added_files = [
|
|||||||
|
|
||||||
import_these = [
|
import_these = [
|
||||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
'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
|
# Walk directories that do dynamic importing
|
||||||
|
|||||||
@@ -17,24 +17,37 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from sqlalchemy import MetaData, create_engine
|
from sqlalchemy import MetaData, create_engine, event
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
|
||||||
from . import migration
|
from . import migration
|
||||||
from eos import config
|
from eos import config
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
pyfalog.info("Initializing database")
|
pyfalog.info("Initializing database")
|
||||||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
||||||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyException(Exception):
|
class ReadOnlyException(Exception):
|
||||||
pass
|
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')
|
pyfalog.debug('Initializing gamedata')
|
||||||
gamedata_connectionstring = config.gamedata_connectionstring
|
gamedata_connectionstring = config.gamedata_connectionstring
|
||||||
if callable(gamedata_connectionstring):
|
if callable(gamedata_connectionstring):
|
||||||
@@ -42,9 +55,26 @@ if callable(gamedata_connectionstring):
|
|||||||
else:
|
else:
|
||||||
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
|
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 = MetaData()
|
||||||
gamedata_meta.bind = gamedata_engine
|
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')
|
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
|
# 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("description", String),
|
||||||
Column("raceID", Integer),
|
Column("raceID", Integer),
|
||||||
Column("factionID", Integer),
|
Column("factionID", Integer),
|
||||||
Column("volume", Float),
|
|
||||||
Column("mass", Float),
|
|
||||||
Column("capacity", Float),
|
|
||||||
Column("published", Boolean),
|
Column("published", Boolean),
|
||||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||||
Column("iconID", Integer),
|
Column("iconID", Integer),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from sqlalchemy.orm import aliased, exc, join
|
|||||||
from sqlalchemy.sql import and_, or_, select
|
from sqlalchemy.sql import and_, or_, select
|
||||||
|
|
||||||
import eos.config
|
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.item import items_table
|
||||||
from eos.db.gamedata.group import groups_table
|
from eos.db.gamedata.group import groups_table
|
||||||
from eos.db.util import processEager, processWhere
|
from eos.db.util import processEager, processWhere
|
||||||
@@ -64,7 +64,7 @@ else:
|
|||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
def sqlizeString(line):
|
def sqlizeNormalString(line):
|
||||||
# Escape backslashes first, as they will be as escape symbol in queries
|
# Escape backslashes first, as they will be as escape symbol in queries
|
||||||
# Then escape percent and underscore signs
|
# Then escape percent and underscore signs
|
||||||
# Finally, replace generic wildcards with sql-style wildcards
|
# Finally, replace generic wildcards with sql-style wildcards
|
||||||
@@ -79,29 +79,39 @@ itemNameMap = {}
|
|||||||
def getItem(lookfor, eager=None):
|
def getItem(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(Item).get(lookfor)
|
item = get_gamedata_session().query(Item).get(lookfor)
|
||||||
else:
|
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):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in itemNameMap:
|
if lookfor in itemNameMap:
|
||||||
id = itemNameMap[lookfor]
|
id = itemNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(Item).get(id)
|
item = get_gamedata_session().query(Item).get(id)
|
||||||
else:
|
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:
|
else:
|
||||||
# Item names are unique, so we can use first() instead of one()
|
# 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:
|
if item is not None:
|
||||||
itemNameMap[lookfor] = item.ID
|
itemNameMap[lookfor] = item.ID
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return item
|
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):
|
def getMutaplasmid(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
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:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
return item
|
return item
|
||||||
@@ -109,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
|
|||||||
|
|
||||||
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||||
# A lot of this is described in more detail in #1597
|
# 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)
|
base = getItem(baseItemID)
|
||||||
|
|
||||||
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
# 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.
|
# 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,
|
# 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
|
# we want to generate a completely new object to work with
|
||||||
gamedata_session.expunge(item)
|
get_gamedata_session().expunge(item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +158,7 @@ def getItems(lookfor, eager=None):
|
|||||||
|
|
||||||
if len(toGet) > 0:
|
if len(toGet) > 0:
|
||||||
# Get items that aren't currently cached, and store them in the cache
|
# 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:
|
for item in items:
|
||||||
cache[(item.ID, None)] = item
|
cache[(item.ID, None)] = item
|
||||||
results += items
|
results += items
|
||||||
@@ -162,9 +172,9 @@ def getItems(lookfor, eager=None):
|
|||||||
def getAlphaClone(lookfor, eager=None):
|
def getAlphaClone(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(AlphaClone).get(lookfor)
|
item = get_gamedata_session().query(AlphaClone).get(lookfor)
|
||||||
else:
|
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:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
return item
|
return item
|
||||||
@@ -172,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
|
|||||||
|
|
||||||
def getAlphaCloneList(eager=None):
|
def getAlphaCloneList(eager=None):
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
clones = gamedata_session.query(AlphaClone).options(*eager).all()
|
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
|
||||||
return clones
|
return clones
|
||||||
|
|
||||||
|
|
||||||
@@ -183,19 +193,19 @@ groupNameMap = {}
|
|||||||
def getGroup(lookfor, eager=None):
|
def getGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
group = gamedata_session.query(Group).get(lookfor)
|
group = get_gamedata_session().query(Group).get(lookfor)
|
||||||
else:
|
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):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in groupNameMap:
|
if lookfor in groupNameMap:
|
||||||
id = groupNameMap[lookfor]
|
id = groupNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
group = gamedata_session.query(Group).get(id)
|
group = get_gamedata_session().query(Group).get(id)
|
||||||
else:
|
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:
|
else:
|
||||||
# Group names are unique, so we can use first() instead of one()
|
# 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:
|
if group is not None:
|
||||||
groupNameMap[lookfor] = group.ID
|
groupNameMap[lookfor] = group.ID
|
||||||
else:
|
else:
|
||||||
@@ -210,21 +220,21 @@ categoryNameMap = {}
|
|||||||
def getCategory(lookfor, eager=None):
|
def getCategory(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
category = gamedata_session.query(Category).get(lookfor)
|
category = get_gamedata_session().query(Category).get(lookfor)
|
||||||
else:
|
else:
|
||||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||||
Category.ID == lookfor).first()
|
Category.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in categoryNameMap:
|
if lookfor in categoryNameMap:
|
||||||
id = categoryNameMap[lookfor]
|
id = categoryNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
category = gamedata_session.query(Category).get(id)
|
category = get_gamedata_session().query(Category).get(id)
|
||||||
else:
|
else:
|
||||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||||
Category.ID == id).first()
|
Category.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# Category names are unique, so we can use first() instead of one()
|
# 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()
|
Category.name == lookfor).first()
|
||||||
if category is not None:
|
if category is not None:
|
||||||
categoryNameMap[lookfor] = category.ID
|
categoryNameMap[lookfor] = category.ID
|
||||||
@@ -240,21 +250,21 @@ metaGroupNameMap = {}
|
|||||||
def getMetaGroup(lookfor, eager=None):
|
def getMetaGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
|
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
|
||||||
else:
|
else:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||||
MetaGroup.ID == lookfor).first()
|
MetaGroup.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in metaGroupNameMap:
|
if lookfor in metaGroupNameMap:
|
||||||
id = metaGroupNameMap[lookfor]
|
id = metaGroupNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).get(id)
|
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
|
||||||
else:
|
else:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||||
MetaGroup.ID == id).first()
|
MetaGroup.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# MetaGroup names are unique, so we can use first() instead of one()
|
# 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()
|
MetaGroup.name == lookfor).first()
|
||||||
if metaGroup is not None:
|
if metaGroup is not None:
|
||||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||||
@@ -264,16 +274,16 @@ def getMetaGroup(lookfor, eager=None):
|
|||||||
|
|
||||||
|
|
||||||
def getMetaGroups():
|
def getMetaGroups():
|
||||||
return gamedata_session.query(MetaGroup).all()
|
return get_gamedata_session().query(MetaGroup).all()
|
||||||
|
|
||||||
|
|
||||||
@cachedQuery(1, "lookfor")
|
@cachedQuery(1, "lookfor")
|
||||||
def getMarketGroup(lookfor, eager=None):
|
def getMarketGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
|
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
|
||||||
else:
|
else:
|
||||||
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
|
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
|
||||||
MarketGroup.ID == lookfor).first()
|
MarketGroup.ID == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
@@ -285,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
|
|||||||
addedIds = set(rootNodeIds)
|
addedIds = set(rootNodeIds)
|
||||||
while addedIds:
|
while addedIds:
|
||||||
allIds.update(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
|
return allIds
|
||||||
|
|
||||||
|
|
||||||
@@ -299,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
|
|||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
|
|
||||||
filter = processWhere(filter, where)
|
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()
|
filter).all()
|
||||||
|
|
||||||
|
|
||||||
@@ -314,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
|||||||
if not hasattr(join, "__iter__"):
|
if not hasattr(join, "__iter__"):
|
||||||
join = (join,)
|
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(' '):
|
for token in nameLike.split(' '):
|
||||||
token_safe = "%{0}%".format(sqlizeString(token))
|
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
||||||
else:
|
else:
|
||||||
@@ -325,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
|||||||
return items
|
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")
|
@cachedQuery(3, "where", "nameLike", "join")
|
||||||
def searchSkills(nameLike, where=None, eager=None):
|
def searchSkills(nameLike, where=None, eager=None):
|
||||||
if not isinstance(nameLike, str):
|
if not isinstance(nameLike, str):
|
||||||
raise TypeError("Need string as argument")
|
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(' '):
|
for token in nameLike.split(' '):
|
||||||
token_safe = "%{0}%".format(sqlizeString(token))
|
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
||||||
else:
|
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))
|
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||||
filter = processWhere(itemfilter, where)
|
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:
|
if vars:
|
||||||
return 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))
|
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
||||||
filter = processWhere(itemfilter, where)
|
filter = processWhere(itemfilter, where)
|
||||||
joinon = items_table.c.groupID == groups_table.c.groupID
|
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()
|
filter).all()
|
||||||
|
|
||||||
return vars
|
return vars
|
||||||
@@ -375,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
|
|||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
try:
|
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:
|
except exc.NoResultFound:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
@@ -384,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
|
|||||||
@cachedQuery(1, "field")
|
@cachedQuery(1, "field")
|
||||||
def getMetaData(field):
|
def getMetaData(field):
|
||||||
if isinstance(field, str):
|
if isinstance(field, str):
|
||||||
data = gamedata_session.query(MetaData).get(field)
|
data = get_gamedata_session().query(MetaData).get(field)
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need string as argument")
|
raise TypeError("Need string as argument")
|
||||||
return data
|
return data
|
||||||
@@ -403,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
|
|||||||
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
||||||
from_obj=[join(Attribute, Item)])
|
from_obj=[join(Attribute, Item)])
|
||||||
|
|
||||||
result = gamedata_session.execute(q).fetchall()
|
result = get_gamedata_session().execute(q).fetchall()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def getAbyssalTypes():
|
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")
|
@cachedQuery(1, "itemID")
|
||||||
@@ -416,9 +447,9 @@ def getDynamicItem(itemID, eager=None):
|
|||||||
try:
|
try:
|
||||||
if isinstance(itemID, int):
|
if isinstance(itemID, int):
|
||||||
if eager is None:
|
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:
|
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:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
@@ -428,5 +459,5 @@ def getDynamicItem(itemID, eager=None):
|
|||||||
|
|
||||||
@cachedQuery(1, "lookfor")
|
@cachedQuery(1, "lookfor")
|
||||||
def getAllImplantSets():
|
def getAllImplantSets():
|
||||||
implantSets = gamedata_session.query(ImplantSet).all()
|
implantSets = get_gamedata_session().query(ImplantSet).all()
|
||||||
return implantSets
|
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))
|
||||||
@@ -470,7 +470,7 @@ def searchFits(nameLike, where=None, eager=None):
|
|||||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
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
|
return fits
|
||||||
|
|
||||||
|
|||||||
763
eos/effects.py
@@ -146,6 +146,12 @@ class Effect(EqBase):
|
|||||||
|
|
||||||
return self.__effectDef is not None
|
return self.__effectDef is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dealsDamage(self):
|
||||||
|
if not self.__generated:
|
||||||
|
self.__generateHandler()
|
||||||
|
return self.__dealsDamage
|
||||||
|
|
||||||
def isType(self, type):
|
def isType(self, type):
|
||||||
"""
|
"""
|
||||||
Check if this effect is of the passed 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.__handler = getattr(effectDef, "handler", eos.effects.BaseEffect.handler)
|
||||||
self.__runTime = getattr(effectDef, "runTime", "normal")
|
self.__runTime = getattr(effectDef, "runTime", "normal")
|
||||||
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
|
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
|
||||||
|
self.__dealsDamage = effectDef.dealsDamage
|
||||||
effectType = getattr(effectDef, "type", None)
|
effectType = getattr(effectDef, "type", None)
|
||||||
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
|
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
|
||||||
self.__type = effectType
|
self.__type = effectType
|
||||||
@@ -175,6 +182,7 @@ class Effect(EqBase):
|
|||||||
self.__handler = eos.effects.DummyEffect.handler
|
self.__handler = eos.effects.DummyEffect.handler
|
||||||
self.__runTime = "normal"
|
self.__runTime = "normal"
|
||||||
self.__activeByDefault = True
|
self.__activeByDefault = True
|
||||||
|
self.__dealsDamage = False
|
||||||
self.__type = None
|
self.__type = None
|
||||||
pyfalog.debug("ImportError generating handler: {0}", e)
|
pyfalog.debug("ImportError generating handler: {0}", e)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
@@ -182,6 +190,7 @@ class Effect(EqBase):
|
|||||||
self.__handler = eos.effects.DummyEffect.handler
|
self.__handler = eos.effects.DummyEffect.handler
|
||||||
self.__runTime = "normal"
|
self.__runTime = "normal"
|
||||||
self.__activeByDefault = True
|
self.__activeByDefault = True
|
||||||
|
self.__dealsDamage = False
|
||||||
self.__type = None
|
self.__type = None
|
||||||
pyfalog.error("AttributeError generating handler: {0}", e)
|
pyfalog.error("AttributeError generating handler: {0}", e)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
@@ -190,6 +199,7 @@ class Effect(EqBase):
|
|||||||
self.__handler = eos.effects.DummyEffect.handler
|
self.__handler = eos.effects.DummyEffect.handler
|
||||||
self.__runTime = "normal"
|
self.__runTime = "normal"
|
||||||
self.__activeByDefault = True
|
self.__activeByDefault = True
|
||||||
|
self.__dealsDamage = False
|
||||||
self.__type = None
|
self.__type = None
|
||||||
pyfalog.critical("Exception generating handler:")
|
pyfalog.critical("Exception generating handler:")
|
||||||
pyfalog.critical(e)
|
pyfalog.critical(e)
|
||||||
@@ -209,40 +219,13 @@ class Effect(EqBase):
|
|||||||
|
|
||||||
|
|
||||||
class Item(EqBase):
|
class Item(EqBase):
|
||||||
MOVE_ATTRS = (4, # Mass
|
|
||||||
38, # Capacity
|
|
||||||
161) # Volume
|
|
||||||
|
|
||||||
MOVE_ATTR_INFO = None
|
|
||||||
|
|
||||||
ABYSSAL_TYPES = 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
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
self.__race = None
|
self.__race = None
|
||||||
self.__requiredSkills = None
|
self.__requiredSkills = None
|
||||||
self.__requiredFor = None
|
self.__requiredFor = None
|
||||||
self.__moved = False
|
|
||||||
self.__offensive = None
|
self.__offensive = None
|
||||||
self.__assistive = None
|
self.__assistive = None
|
||||||
self.__overrides = None
|
self.__overrides = None
|
||||||
@@ -264,9 +247,6 @@ class Item(EqBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def attributes(self):
|
def attributes(self):
|
||||||
if not self.__moved:
|
|
||||||
self.moveAttrs()
|
|
||||||
|
|
||||||
return self.__attributes
|
return self.__attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -363,7 +343,11 @@ class Item(EqBase):
|
|||||||
if self.__race is None:
|
if self.__race is None:
|
||||||
|
|
||||||
try:
|
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"
|
self.__race = "upwell"
|
||||||
else:
|
else:
|
||||||
self.__race = self.factionMap[self.factionID]
|
self.__race = self.factionMap[self.factionID]
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from logbook import Logger
|
|||||||
|
|
||||||
from sqlalchemy.orm import reconstructor
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
|
from eos.utils.round import roundToPrec
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -56,9 +59,8 @@ class BoosterSideEffect:
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return "{0}% {1}".format(
|
return "{0}% {1}".format(
|
||||||
self.booster.getModifiedItemAttr(self.attr),
|
roundToPrec(self.booster.getModifiedItemAttr(self.attr), 5),
|
||||||
self.__effect.getattr('displayName') or self.__effect.name,
|
self.__effect.getattr('displayName') or self.__effect.name)
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attr(self):
|
def attr(self):
|
||||||
|
|||||||
@@ -364,3 +364,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if self.item.groupID in fitDroneGroupLimits:
|
if self.item.groupID in fitDroneGroupLimits:
|
||||||
return True
|
return True
|
||||||
return False
|
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 False
|
||||||
|
|
||||||
return True
|
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
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from math import log, sqrt
|
from math import floor, log, sqrt
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
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.module import Module
|
||||||
from eos.saveddata.ship import Ship
|
from eos.saveddata.ship import Ship
|
||||||
from eos.saveddata.targetProfile import TargetProfile
|
from eos.saveddata.targetProfile import TargetProfile
|
||||||
|
from eos.utils.float import floatUnerr
|
||||||
from eos.utils.stats import DmgTypes, RRTypes
|
from eos.utils.stats import DmgTypes, RRTypes
|
||||||
|
|
||||||
|
|
||||||
@@ -378,8 +379,9 @@ class Fit:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def maxTargets(self):
|
def maxTargets(self):
|
||||||
return min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
maxTargets = min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
||||||
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
||||||
|
return floor(floatUnerr(maxTargets))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maxTargetRange(self):
|
def maxTargetRange(self):
|
||||||
@@ -1026,6 +1028,16 @@ class Fit:
|
|||||||
if mod.isEmpty:
|
if mod.isEmpty:
|
||||||
del self.modules[i]
|
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
|
@property
|
||||||
def modCount(self):
|
def modCount(self):
|
||||||
x = 0
|
x = 0
|
||||||
@@ -1135,7 +1147,7 @@ class Fit:
|
|||||||
def droneBayUsed(self):
|
def droneBayUsed(self):
|
||||||
amount = 0
|
amount = 0
|
||||||
for d in self.drones:
|
for d in self.drones:
|
||||||
amount += d.item.volume * d.amount
|
amount += d.item.attributes['volume'].value * d.amount
|
||||||
|
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
@@ -1143,7 +1155,7 @@ class Fit:
|
|||||||
def fighterBayUsed(self):
|
def fighterBayUsed(self):
|
||||||
amount = 0
|
amount = 0
|
||||||
for f in self.fighters:
|
for f in self.fighters:
|
||||||
amount += f.item.volume * f.amount
|
amount += f.item.attributes['volume'].value * f.amount
|
||||||
|
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ ProjectedSystem = {
|
|||||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||||
MINING_ATTRIBUTES = ("miningAmount",)
|
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):
|
def __init__(self, item, baseItem=None, mutaplasmid=None):
|
||||||
"""Initialize a module from the program"""
|
"""Initialize a module from the program"""
|
||||||
@@ -214,8 +216,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if charge is None:
|
if charge is None:
|
||||||
charges = 0
|
charges = 0
|
||||||
else:
|
else:
|
||||||
chargeVolume = charge.volume
|
chargeVolume = charge.attributes['volume'].value
|
||||||
containerCapacity = self.item.capacity
|
containerCapacity = self.item.attributes['capacity'].value
|
||||||
if chargeVolume is None or containerCapacity is None:
|
if chargeVolume is None or containerCapacity is None:
|
||||||
charges = 0
|
charges = 0
|
||||||
else:
|
else:
|
||||||
@@ -461,6 +463,20 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
return True
|
return True
|
||||||
return False
|
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):
|
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||||
return {0: DmgTypes(0, 0, 0, 0)}
|
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
|
# Check this only if we're told to do so
|
||||||
if hardpointLimit:
|
if hardpointLimit:
|
||||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -712,6 +728,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
return False
|
return False
|
||||||
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
||||||
return False
|
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:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -778,8 +797,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
# Check sizes, if 'charge size > module volume' it won't fit
|
# Check sizes, if 'charge size > module volume' it won't fit
|
||||||
if charge is None:
|
if charge is None:
|
||||||
return True
|
return True
|
||||||
chargeVolume = charge.volume
|
chargeVolume = charge.attributes['volume'].value
|
||||||
moduleCapacity = self.item.capacity
|
moduleCapacity = self.item.attributes['capacity'].value
|
||||||
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1037,7 +1056,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
elif click == "ctrl":
|
elif click == "ctrl":
|
||||||
state = FittingModuleState.OFFLINE
|
state = FittingModuleState.OFFLINE
|
||||||
else:
|
else:
|
||||||
state = transitionMap[currState]
|
try:
|
||||||
|
state = transitionMap[currState]
|
||||||
|
except KeyError:
|
||||||
|
state = min(transitionMap)
|
||||||
# If passive module tries to transition into online and fails,
|
# If passive module tries to transition into online and fails,
|
||||||
# put it to passive instead
|
# put it to passive instead
|
||||||
if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
|
if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ class Mutator(EqBase):
|
|||||||
@validates("value")
|
@validates("value")
|
||||||
def validator(self, key, val):
|
def validator(self, key, val):
|
||||||
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||||
|
if self.baseValue == 0:
|
||||||
|
return 0
|
||||||
mod = val / self.baseValue
|
mod = val / self.baseValue
|
||||||
|
|
||||||
if self.minMod <= mod <= self.maxMod:
|
if self.minMod <= mod <= self.maxMod:
|
||||||
|
|||||||
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:
|
class YDef:
|
||||||
|
|
||||||
def __init__(self, handle, unit, label, selectorLabel=None):
|
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.label = label
|
self.label = label
|
||||||
self._selectorLabel = selectorLabel
|
self._selectorLabel = selectorLabel
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selectorLabel(self):
|
def selectorLabel(self):
|
||||||
@@ -53,12 +54,13 @@ class YDef:
|
|||||||
|
|
||||||
class XDef:
|
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.handle = handle
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.label = label
|
self.label = label
|
||||||
self.mainInput = mainInput
|
self.mainInput = mainInput
|
||||||
self._selectorLabel = selectorLabel
|
self._selectorLabel = selectorLabel
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selectorLabel(self):
|
def selectorLabel(self):
|
||||||
|
|||||||
@@ -37,7 +37,14 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
|||||||
for mod in src.item.activeModulesIter():
|
for mod in src.item.activeModulesIter():
|
||||||
if not mod.isDealingDamage():
|
if not mod.isDealingDamage():
|
||||||
continue
|
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:
|
if inLockRange:
|
||||||
applicationMap[mod] = getTurretMult(
|
applicationMap[mod] = getTurretMult(
|
||||||
mod=mod,
|
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):
|
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||||
applicationMap[mod] = getLauncherMult(
|
applicationMap[mod] = getLauncherMult(
|
||||||
mod=mod,
|
mod=mod,
|
||||||
src=src,
|
|
||||||
distance=distance,
|
distance=distance,
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
tgtSigRadius=tgtSigRadius)
|
tgtSigRadius=tgtSigRadius)
|
||||||
@@ -151,7 +157,21 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
|
|||||||
return mult
|
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
|
missileMaxRangeData = mod.missileMaxRangeData
|
||||||
if missileMaxRangeData is None:
|
if missileMaxRangeData is None:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ from .getter import (
|
|||||||
|
|
||||||
class FitDamageStatsGraph(FitGraph):
|
class FitDamageStatsGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._timeCache = TimeCache()
|
self._timeCache = TimeCache()
|
||||||
self._projectedCache = ProjectedDataCache()
|
self._projectedCache = ProjectedDataCache()
|
||||||
|
|
||||||
|
|||||||
@@ -23,24 +23,6 @@ import math
|
|||||||
from graphs.data.base import SmoothPointGetter
|
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):
|
class Time2DistanceGetter(SmoothPointGetter):
|
||||||
|
|
||||||
def _getCommonData(self, miscParams, src, tgt):
|
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_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
|
||||||
distance = distance_t - distance_0
|
distance = distance_t - distance_0
|
||||||
return distance
|
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 graphs.data.base import FitGraph, XDef, YDef, Input
|
||||||
from .getter import Time2SpeedGetter, Time2DistanceGetter
|
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
|
||||||
|
|
||||||
|
|
||||||
class FitMobilityGraph(FitGraph):
|
class FitMobilityGraph(FitGraph):
|
||||||
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
|
|||||||
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
||||||
yDefs = [
|
yDefs = [
|
||||||
YDef(handle='speed', unit='m/s', label='Speed'),
|
YDef(handle='speed', unit='m/s', label='Speed'),
|
||||||
YDef(handle='distance', unit='km', label='Distance')]
|
YDef(handle='distance', unit='km', label='Distance'),
|
||||||
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
|
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')
|
srcExtraCols = ('Speed', 'Agility')
|
||||||
|
|
||||||
# Calculation stuff
|
# Calculation stuff
|
||||||
|
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
|
||||||
_getters = {
|
_getters = {
|
||||||
('time', 'speed'): Time2SpeedGetter,
|
('time', 'speed'): Time2SpeedGetter,
|
||||||
('time', 'distance'): Time2DistanceGetter}
|
('time', 'distance'): Time2DistanceGetter,
|
||||||
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
|
('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):
|
class FitRemoteRepsGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._timeCache = TimeCache()
|
self._timeCache = TimeCache()
|
||||||
|
|
||||||
def _clearInternalCache(self, reason, extraData):
|
def _clearInternalCache(self, reason, extraData):
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ from .getter import (
|
|||||||
|
|
||||||
class FitShieldRegenGraph(FitGraph):
|
class FitShieldRegenGraph(FitGraph):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||||
|
|
||||||
# UI stuff
|
# UI stuff
|
||||||
internalName = 'shieldRegenGraph'
|
internalName = 'shieldRegenGraph'
|
||||||
name = 'Shield Regeneration'
|
name = 'Shield Regeneration'
|
||||||
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
|
|||||||
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
|
('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'),
|
('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')}
|
('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):
|
class FitWarpTimeGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._subspeedCache = SubwarpSpeedCache()
|
self._subspeedCache = SubwarpSpeedCache()
|
||||||
|
|
||||||
def _clearInternalCache(self, reason, extraData):
|
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 = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue))
|
||||||
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
||||||
fieldTextBox.SetToolTip(wx.ToolTip(tooltipText))
|
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
|
fieldIcon = None
|
||||||
if inputDef.iconID is not None:
|
if inputDef.iconID is not None:
|
||||||
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
||||||
@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
|
|||||||
|
|
||||||
self.ySubSelection.Clear()
|
self.ySubSelection.Clear()
|
||||||
for yDef in view.yDefs:
|
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.Append(self.formatLabel(yDef, selector=True), yDef)
|
||||||
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
||||||
self.ySubSelection.SetSelection(selectedY)
|
self.ySubSelection.SetSelection(selectedY)
|
||||||
|
|
||||||
self.xSubSelection.Clear()
|
self.xSubSelection.Clear()
|
||||||
for xDef in view.xDefs:
|
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.Append(self.formatLabel(xDef, selector=True), xDef)
|
||||||
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
||||||
self.xSubSelection.SetSelection(selectedX)
|
self.xSubSelection.SetSelection(selectedX)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import gui.globalEvents as GE
|
|||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
from graphs.data.base import FitGraph
|
from graphs.data.base import FitGraph
|
||||||
from graphs.events import RESIST_MODE_CHANGED
|
from graphs.events import RESIST_MODE_CHANGED
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from service.const import GraphCacheCleanupReason
|
from service.const import GraphCacheCleanupReason
|
||||||
from service.settings import GraphSettings
|
from service.settings import GraphSettings
|
||||||
@@ -50,6 +50,7 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
|
|
||||||
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
self.includeHidden = includeHidden
|
||||||
|
|
||||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
|
|
||||||
# Setup - graph selector
|
# Setup - graph selector
|
||||||
for view in FitGraph.views:
|
for view in FitGraph.views:
|
||||||
if view.hidden and not includeHidden:
|
if view.hidden and not self.includeHidden:
|
||||||
continue
|
continue
|
||||||
self.graphSelection.Append(view.name, view())
|
self.graphSelection.Append(view.name, view())
|
||||||
self.graphSelection.SetSelection(0)
|
self.graphSelection.SetSelection(0)
|
||||||
|
|||||||
@@ -22,12 +22,14 @@
|
|||||||
import wx
|
import wx
|
||||||
|
|
||||||
|
|
||||||
class AuxiliaryFrame(wx.Frame):
|
class AuxiliaryMixin:
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None, resizeable=False):
|
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:
|
if resizeable:
|
||||||
baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX
|
baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX
|
||||||
kwargs = {
|
kwargs = {
|
||||||
@@ -53,14 +55,26 @@ class AuxiliaryFrame(wx.Frame):
|
|||||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||||
|
|
||||||
@classmethod
|
@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 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)
|
frame = cls(parent, *args, **kwargs)
|
||||||
cls._instance = frame
|
cls._instance = frame
|
||||||
frame.Show()
|
frame.Show()
|
||||||
else:
|
else:
|
||||||
cls._instance.Raise()
|
cls._instance.Raise()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
def OnSuppressedAction(self, event):
|
def OnSuppressedAction(self, event):
|
||||||
return
|
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.fitCommands as cmd
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from gui.contextMenu import ContextMenu
|
from gui.contextMenu import ContextMenu
|
||||||
|
from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
|
||||||
from gui.utils.staticHelpers import DragDropHelper
|
from gui.utils.staticHelpers import DragDropHelper
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
from service.market import Market
|
from service.market import Market
|
||||||
@@ -58,6 +59,7 @@ class CargoView(d.Display):
|
|||||||
self.lastFitId = None
|
self.lastFitId = None
|
||||||
|
|
||||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
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_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||||
|
|
||||||
@@ -66,6 +68,31 @@ class CargoView(d.Display):
|
|||||||
|
|
||||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
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):
|
def handleListDrag(self, x, y, data):
|
||||||
"""
|
"""
|
||||||
Handles dragging of items from various pyfa displays which support it
|
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
|
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):
|
class DroneViewDrop(wx.DropTarget):
|
||||||
def __init__(self, dropFn, *args, **kwargs):
|
def __init__(self, dropFn, *args, **kwargs):
|
||||||
super(DroneViewDrop, self).__init__(*args, **kwargs)
|
super(DroneViewDrop, self).__init__(*args, **kwargs)
|
||||||
@@ -186,17 +191,13 @@ class DroneView(Display):
|
|||||||
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
||||||
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
||||||
|
|
||||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
@staticmethod
|
||||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
def droneKey(drone):
|
||||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
|
||||||
|
|
||||||
def droneKey(self, drone):
|
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
|
|
||||||
groupName = sMkt.getMarketGroupByItem(drone.item).name
|
groupName = sMkt.getMarketGroupByItem(drone.item).name
|
||||||
|
|
||||||
return (self.DRONE_ORDER.index(groupName),
|
return (DRONE_ORDER.index(groupName), drone.item.name)
|
||||||
drone.item.name)
|
|
||||||
|
|
||||||
def fitChanged(self, event):
|
def fitChanged(self, event):
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ from service.fit import Fit
|
|||||||
from service.market import Market
|
from service.market import Market
|
||||||
|
|
||||||
|
|
||||||
|
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||||
|
|
||||||
|
|
||||||
class FighterViewDrop(wx.DropTarget):
|
class FighterViewDrop(wx.DropTarget):
|
||||||
def __init__(self, dropFn, *args, **kwargs):
|
def __init__(self, dropFn, *args, **kwargs):
|
||||||
super(FighterViewDrop, self).__init__(*args, **kwargs)
|
super(FighterViewDrop, self).__init__(*args, **kwargs)
|
||||||
@@ -250,11 +253,10 @@ class FighterDisplay(d.Display):
|
|||||||
def _merge(src, dst):
|
def _merge(src, dst):
|
||||||
return
|
return
|
||||||
|
|
||||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
@staticmethod
|
||||||
|
def fighterKey(fighter):
|
||||||
def fighterKey(self, fighter):
|
|
||||||
groupName = Market.getInstance().getGroupByItem(fighter.item).name
|
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
|
# Sort support fighters by name, ignore their abilities
|
||||||
if groupName == 'Support Fighter':
|
if groupName == 'Support Fighter':
|
||||||
abilityEffectIDs = ()
|
abilityEffectIDs = ()
|
||||||
|
|||||||
@@ -90,8 +90,6 @@ class ProjectedView(d.Display):
|
|||||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||||
|
|
||||||
self.droneView = gui.builtinAdditionPanes.droneView.DroneView
|
|
||||||
|
|
||||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||||
|
|
||||||
self.SetDropTarget(ProjectedViewDrop(self.handleListDrag))
|
self.SetDropTarget(ProjectedViewDrop(self.handleListDrag))
|
||||||
@@ -119,12 +117,12 @@ class ProjectedView(d.Display):
|
|||||||
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
|
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
|
||||||
elif data[0] == 'market':
|
elif data[0] == 'market':
|
||||||
itemID = int(data[1])
|
itemID = int(data[1])
|
||||||
category = Market.getInstance().getItem(itemID, eager=('group.category')).category.name
|
item = Market.getInstance().getItem(itemID)
|
||||||
if category == 'Module':
|
if item.isModule:
|
||||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=itemID))
|
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))
|
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))
|
self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=itemID))
|
||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
@@ -162,7 +160,7 @@ class ProjectedView(d.Display):
|
|||||||
if item.marketGroup is None:
|
if item.marketGroup is None:
|
||||||
item = item.metaGroup.parent
|
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)
|
drone.item.name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from gui.builtinContextMenus import fitAddCurrentlyOpen
|
|||||||
from gui.builtinContextMenus import envEffectAdd
|
from gui.builtinContextMenus import envEffectAdd
|
||||||
from gui.builtinContextMenus import commandFitAdd
|
from gui.builtinContextMenus import commandFitAdd
|
||||||
from gui.builtinContextMenus.targetProfile import adder
|
from gui.builtinContextMenus.targetProfile import adder
|
||||||
|
from gui.builtinContextMenus import graphFitAmmoPicker
|
||||||
# Often-used item manipulations
|
# Often-used item manipulations
|
||||||
from gui.builtinContextMenus import shipModeChange
|
from gui.builtinContextMenus import shipModeChange
|
||||||
from gui.builtinContextMenus import moduleAmmoChange
|
from gui.builtinContextMenus import moduleAmmoChange
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
from collections import OrderedDict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
@@ -10,6 +11,28 @@ from gui.contextMenu import ContextMenuUnconditional
|
|||||||
from service.market import Market
|
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):
|
class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||||
|
|
||||||
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
|
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
|
||||||
@@ -32,104 +55,71 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
|||||||
def getText(self, callingWindow, itmContext):
|
def getText(self, callingWindow, itmContext):
|
||||||
return "Add Environmental Effect"
|
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):
|
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
|
||||||
|
|
||||||
# Wormholes
|
|
||||||
|
|
||||||
self.idmap = {}
|
self.idmap = {}
|
||||||
sub = wx.Menu()
|
data = self.getData()
|
||||||
|
msw = "wxMSW" in wx.PlatformInfo
|
||||||
|
|
||||||
wormhole_item = wx.MenuItem(sub, wx.ID_ANY, "Wormhole")
|
def makeMenu(data, parentMenu):
|
||||||
wormhole_menu = wx.Menu()
|
menu = wx.Menu()
|
||||||
wormhole_item.SetSubMenu(wormhole_menu)
|
for group_name in data.groups:
|
||||||
sub.Append(wormhole_item)
|
menuItem = self._addGroup(rootMenu if msw else parentMenu, group_name)
|
||||||
|
subMenu = makeMenu(data.groups[group_name], menu)
|
||||||
grouped_data, flat_data = self.getEffectBeacons()
|
menuItem.SetSubMenu(subMenu)
|
||||||
self.buildMenu(grouped_data, flat_data, wormhole_menu, rootMenu, msw)
|
menu.Append(menuItem)
|
||||||
|
for entry in data.items:
|
||||||
# Incursions
|
menuItem = self._addEffect(rootMenu if msw else parentMenu, entry.itemID, entry.shortName)
|
||||||
|
menu.Append(menuItem)
|
||||||
grouped_data, flat_data = self.getEffectBeacons(incursions=True)
|
menu.Bind(wx.EVT_MENU, self.handleSelection)
|
||||||
self.buildMenu(grouped_data, flat_data, sub, rootMenu, msw)
|
return menu
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
sub = makeMenu(data, rootMenu)
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
def handleSelection(self, event):
|
def handleSelection(self, event):
|
||||||
# Skip events ids that aren't mapped
|
# Skip events ids that aren't mapped
|
||||||
|
|
||||||
swObj, swName = self.idmap.get(event.Id, (False, False))
|
swObj = self.idmap.get(event.Id, False)
|
||||||
if not swObj and not swName:
|
if not swObj:
|
||||||
event.Skip()
|
event.Skip()
|
||||||
return
|
return
|
||||||
|
|
||||||
fitID = self.mainFrame.getActiveFit()
|
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['Wormhole'] = self.getEffectBeacons(
|
||||||
|
'Black Hole', 'Cataclysmic Variable', 'Magnetar',
|
||||||
|
'Pulsar', 'Red Giant', 'Wolf Rayet')
|
||||||
|
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()
|
||||||
|
data.groups['Abyssal Weather'] = self.getAbyssalWeather()
|
||||||
|
return data
|
||||||
|
|
||||||
def processFlat(data, root, sub):
|
def getEffectBeacons(self, *groups):
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Get dictionary with wormhole system-wide effects
|
Get dictionary with wormhole system-wide effects
|
||||||
"""
|
"""
|
||||||
|
compacted = len(groups) <= 1
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
|
|
||||||
# todo: rework this
|
|
||||||
# Container for system-wide effects
|
# Container for system-wide effects
|
||||||
grouped = {}
|
data = Group()
|
||||||
|
|
||||||
# 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")
|
|
||||||
|
|
||||||
# Stuff we don't want to see in names
|
# Stuff we don't want to see in names
|
||||||
garbages = ("System Effects", "Effects")
|
garbages = ("System Effects", "Effects")
|
||||||
@@ -140,7 +130,7 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
|||||||
# Cycle through them
|
# Cycle through them
|
||||||
for beacon in sMkt.getItemsByGroup(grp):
|
for beacon in sMkt.getItemsByGroup(grp):
|
||||||
# Check if it belongs to any valid group
|
# Check if it belongs to any valid group
|
||||||
for group in validgroups:
|
for group in groups:
|
||||||
# Check beginning of the name only
|
# Check beginning of the name only
|
||||||
if re.search(group, beacon.name):
|
if re.search(group, beacon.name):
|
||||||
# Get full beacon name
|
# Get full beacon name
|
||||||
@@ -159,64 +149,65 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
|||||||
groupname = re.sub(garbage, "", groupname)
|
groupname = re.sub(garbage, "", groupname)
|
||||||
groupname = re.sub(" {2,}", " ", groupname).strip()
|
groupname = re.sub(" {2,}", " ", groupname).strip()
|
||||||
# Add stuff to dictionary
|
# Add stuff to dictionary
|
||||||
if groupname not in grouped:
|
if compacted:
|
||||||
grouped[groupname] = set()
|
container = data.items
|
||||||
grouped[groupname].add((beacon, beaconname, shortname))
|
else:
|
||||||
|
container = data.groups.setdefault(groupname, Group()).items
|
||||||
|
container.append(Entry(beacon.ID, beaconname, shortname))
|
||||||
# Break loop on 1st result
|
# Break loop on 1st result
|
||||||
break
|
break
|
||||||
|
data.sort()
|
||||||
return grouped, ()
|
return data
|
||||||
|
|
||||||
def getAbyssalWeather(self):
|
def getAbyssalWeather(self):
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
|
data = Group()
|
||||||
|
|
||||||
environments = {x.ID: x for x in sMkt.getGroup("Abyssal Environment").items}
|
environments = {x.ID: x for x in sMkt.getGroup("Abyssal Environment").items}
|
||||||
items = chain(sMkt.getGroup("MassiveEnvironments").items, sMkt.getGroup("Non-Interactable Object").items)
|
items = chain(
|
||||||
|
sMkt.getGroup("MassiveEnvironments").items,
|
||||||
grouped = {}
|
sMkt.getGroup("Non-Interactable Object").items)
|
||||||
flat = set()
|
|
||||||
|
|
||||||
for beacon in items:
|
for beacon in items:
|
||||||
if not beacon.isType('projected'):
|
if not beacon.isType('projected'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
type = self.__class__.abyssal_mapping.get(beacon.name[0:-2], None)
|
type = self.__class__.abyssal_mapping.get(beacon.name[0:-2], None)
|
||||||
type = environments.get(type, None)
|
type = environments.get(type, None)
|
||||||
if type is None:
|
if type is None:
|
||||||
continue
|
continue
|
||||||
|
subdata = data.groups.setdefault(type.name, Group())
|
||||||
if type.name not in grouped:
|
|
||||||
grouped[type.name] = set()
|
|
||||||
|
|
||||||
display_name = "{} {}".format(type.name, beacon.name[-1:])
|
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
|
# 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()
|
sMkt = Market.getInstance()
|
||||||
|
for item in sMkt.getItemsByGroup(sMkt.getGroup('Destructible Effect Beacon')):
|
||||||
grp = sMkt.getGroup("Abyssal Hazards")
|
if not item.isType('projected'):
|
||||||
|
|
||||||
grouped = dict()
|
|
||||||
|
|
||||||
for beacon in grp.items:
|
|
||||||
if not beacon.isType('projected'):
|
|
||||||
continue
|
continue
|
||||||
# Localized effects, currently, have a name like "(size) (type) Cloud"
|
data.items.append(Entry(item.ID, item.name, item.name))
|
||||||
# Until this inevitably changes, do a simple split
|
data.sort()
|
||||||
name_parts = beacon.name.split(" ")
|
return data
|
||||||
|
|
||||||
key = name_parts[1].strip()
|
|
||||||
if key not in grouped:
|
|
||||||
grouped[key] = set()
|
|
||||||
|
|
||||||
grouped[key].add((beacon, beacon.name, beacon.name))
|
|
||||||
|
|
||||||
return grouped, ()
|
|
||||||
|
|
||||||
|
|
||||||
AddEnvironmentEffect.register()
|
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.fitCommands as cmd
|
||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
from eos.const import FittingHardpoint
|
|
||||||
from eos.saveddata.module import Module
|
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.contextMenu import ContextMenuCombined
|
from gui.contextMenu import ContextMenuCombined
|
||||||
from gui.fitCommands.helpers import getSimilarModPositions
|
from gui.fitCommands.helpers import getSimilarModPositions
|
||||||
|
from service.ammo import Ammo
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
from service.market import Market
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeModuleAmmo(ContextMenuCombined):
|
class ChangeModuleAmmo(ContextMenuCombined):
|
||||||
|
|
||||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
|
||||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
# Format: {type ID: set(loadable, charges)}
|
# Format: {type ID: set(loadable, charges)}
|
||||||
self.loadableCharges = {}
|
self.loadableChargesCache = {}
|
||||||
|
|
||||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||||
if srcContext not in ("fittingModule", "projectedModule"):
|
if srcContext not in ('fittingModule', 'projectedModule'):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.mainFrame.getActiveFit() is None:
|
if self.mainFrame.getActiveFit() is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.mainCharges = self.getChargesForMod(mainItem)
|
self.mainCharges = self._getAmmo(mainItem)
|
||||||
if not self.mainCharges:
|
if not self.mainCharges:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -39,186 +34,81 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||||
return "Charge"
|
return 'Charge'
|
||||||
|
|
||||||
def getChargesForMod(self, mod):
|
def _getAmmo(self, mod):
|
||||||
sMkt = Market.getInstance()
|
if mod.itemID is None:
|
||||||
if mod is None or mod.isEmpty:
|
|
||||||
return set()
|
return set()
|
||||||
typeID = mod.item.ID
|
if mod.itemID not in self.loadableChargesCache:
|
||||||
if typeID in self.loadableCharges:
|
self.loadableChargesCache[mod.itemID] = Ammo.getInstance().getModuleFlatAmmo(mod)
|
||||||
return self.loadableCharges[typeID]
|
return self.loadableChargesCache[mod.itemID]
|
||||||
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
|
|
||||||
|
|
||||||
def turretSorter(self, charge):
|
def _addCharge(self, menu, 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):
|
|
||||||
id_ = ContextMenuCombined.nextID()
|
id_ = ContextMenuCombined.nextID()
|
||||||
name = charge.name if charge is not None else "Empty"
|
name = charge.name if charge is not None else 'Empty'
|
||||||
self.chargeIds[id_] = charge
|
self.chargeEventMap[id_] = charge
|
||||||
item = wx.MenuItem(menu, id_, name)
|
item = wx.MenuItem(menu, id_, name)
|
||||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
||||||
item.charge = charge
|
item.charge = charge
|
||||||
if charge is not None and charge.iconID is not None:
|
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:
|
if bitmap is not None:
|
||||||
item.SetBitmap(bitmap)
|
item.SetBitmap(bitmap)
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def addSeperator(m, text):
|
def _addSeparator(m, text):
|
||||||
id_ = ContextMenuCombined.nextID()
|
id_ = ContextMenuCombined.nextID()
|
||||||
m.Append(id_, '─ %s ─' % text)
|
m.Append(id_, '─ %s ─' % text)
|
||||||
m.Enable(id_, False)
|
m.Enable(id_, False)
|
||||||
|
|
||||||
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
||||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
msw = True if 'wxMSW' in wx.PlatformInfo else False
|
||||||
m = wx.Menu()
|
menu = wx.Menu()
|
||||||
self.chargeIds = {}
|
self.chargeEventMap = {}
|
||||||
hardpoint = self.module.hardpoint
|
modType, chargeDict = Ammo.getInstance().getModuleStructuredAmmo(self.module, ammo=self.mainCharges)
|
||||||
moduleName = self.module.item.name
|
if modType == 'ddTurret':
|
||||||
# Make sure we do not consider mining turrets as combat turrets
|
self._addSeparator(menu, 'Long Range')
|
||||||
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
menuItems = []
|
||||||
self.addSeperator(m, "Long Range")
|
for charges in chargeDict.values():
|
||||||
items = []
|
if len(charges) == 1:
|
||||||
range_ = None
|
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
if sub is None and item and base:
|
baseCharge = charges[0]
|
||||||
sub = wx.Menu()
|
menuItem = self._addCharge(rootMenu if msw else menu, baseCharge)
|
||||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
menuItems.append(menuItem)
|
||||||
self.addSeperator(sub, "Less Damage")
|
subMenu = wx.Menu()
|
||||||
item.SetSubMenu(sub)
|
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||||
sub.Append(self.addCharge(rootMenu if msw else sub, base))
|
menuItem.SetSubMenu(subMenu)
|
||||||
|
self._addSeparator(subMenu, 'Less Damage')
|
||||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
for charge in charges:
|
||||||
|
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||||
if sub is not None:
|
self._addSeparator(subMenu, 'More Damage')
|
||||||
self.addSeperator(sub, "More Damage")
|
for menuItem in menuItems:
|
||||||
|
menu.Append(menuItem)
|
||||||
for item in items:
|
self._addSeparator(menu, 'Short Range')
|
||||||
m.Append(item)
|
elif modType == 'ddMissile':
|
||||||
|
menuItems = []
|
||||||
self.addSeperator(m, "Short Range")
|
for chargeCatName, charges in chargeDict.items():
|
||||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
menuItem = wx.MenuItem(menu, wx.ID_ANY, chargeCatName.capitalize())
|
||||||
type_ = None
|
menuItems.append(menuItem)
|
||||||
sub = None
|
subMenu = wx.Menu()
|
||||||
defender = None
|
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||||
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
|
menuItem.SetSubMenu(subMenu)
|
||||||
for charge in chargesSorted:
|
self._addSeparator(subMenu, 'Less Damage')
|
||||||
currType = self.damageInfo(charge)[0]
|
for charge in charges:
|
||||||
|
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||||
if currType != type_ or type_ is None:
|
self._addSeparator(subMenu, 'More Damage')
|
||||||
if sub is not None:
|
for menuItem in menuItems:
|
||||||
self.addSeperator(sub, "More Damage")
|
menu.Append(menuItem)
|
||||||
|
elif modType == 'general':
|
||||||
type_ = currType
|
for charge in chargeDict['general']:
|
||||||
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
|
menu.Append(self._addCharge(rootMenu if msw else menu, charge))
|
||||||
bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
|
menu.Append(self._addCharge(rootMenu if msw else menu, None))
|
||||||
if bitmap is not None:
|
return menu
|
||||||
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
|
|
||||||
|
|
||||||
def handleAmmoSwitch(self, event):
|
def handleAmmoSwitch(self, event):
|
||||||
charge = self.chargeIds.get(event.Id, False)
|
charge = self.chargeEventMap.get(event.Id, False)
|
||||||
if charge is False:
|
if charge is False:
|
||||||
event.Skip()
|
event.Skip()
|
||||||
return
|
return
|
||||||
@@ -254,7 +144,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
|||||||
positions = []
|
positions = []
|
||||||
for position, mod in enumerate(modContainer):
|
for position, mod in enumerate(modContainer):
|
||||||
if mod in self.selection:
|
if mod in self.selection:
|
||||||
modCharges = self.getChargesForMod(mod)
|
modCharges = self._getAmmo(mod)
|
||||||
if modCharges.issubset(self.mainCharges):
|
if modCharges.issubset(self.mainCharges):
|
||||||
positions.append(position)
|
positions.append(position)
|
||||||
self.mainFrame.command.Submit(command(
|
self.mainFrame.command.Submit(command(
|
||||||
|
|||||||
@@ -126,10 +126,13 @@ class AttributeSlider(wx.Panel):
|
|||||||
def SetValue(self, value, post_event=True, affect_modified_flag=True):
|
def SetValue(self, value, post_event=True, affect_modified_flag=True):
|
||||||
self.ctrl.SetValue(value)
|
self.ctrl.SetValue(value)
|
||||||
invert_factor = -1 if self.inverse else 1
|
invert_factor = -1 if self.inverse else 1
|
||||||
if value >= self.base_value:
|
try:
|
||||||
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
if value >= self.base_value:
|
||||||
else:
|
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
||||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 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)
|
self.slider.SetValue(slider_percentage)
|
||||||
if post_event:
|
if post_event:
|
||||||
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage, affect_modified_flag=affect_modified_flag))
|
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.Layout()
|
||||||
|
|
||||||
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
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()
|
self.popupMenu = wx.Menu()
|
||||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||||
@@ -50,7 +50,7 @@ class ItemDescription(wx.Panel):
|
|||||||
if selectedMenuItem == 1: # Copy was chosen
|
if selectedMenuItem == 1: # Copy was chosen
|
||||||
self.copySelectionToClipboard()
|
self.copySelectionToClipboard()
|
||||||
|
|
||||||
def onKeyDown(self, event):
|
def onKeyUp(self, event):
|
||||||
keyCode = event.GetKeyCode()
|
keyCode = event.GetKeyCode()
|
||||||
# Ctrl + C
|
# Ctrl + C
|
||||||
if keyCode == 67 and event.ControlDown():
|
if keyCode == 67 and event.ControlDown():
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ class ItemMutatorList(wx.ScrolledWindow):
|
|||||||
|
|
||||||
first = True
|
first = True
|
||||||
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
|
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||||
|
if m.baseValue == 0:
|
||||||
|
continue
|
||||||
if not first:
|
if not first:
|
||||||
sizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
sizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||||
first = False
|
first = False
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ItemTraits(wx.Panel):
|
|||||||
self.traits.SetPage(item.traits.traitText)
|
self.traits.SetPage(item.traits.traitText)
|
||||||
|
|
||||||
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
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)
|
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
|
||||||
self.Layout()
|
self.Layout()
|
||||||
@@ -32,7 +32,7 @@ class ItemTraits(wx.Panel):
|
|||||||
if selectedMenuItem == 1: # Copy was chosen
|
if selectedMenuItem == 1: # Copy was chosen
|
||||||
self.copySelectionToClipboard()
|
self.copySelectionToClipboard()
|
||||||
|
|
||||||
def onKeyDown(self, event):
|
def onKeyUp(self, event):
|
||||||
keyCode = event.GetKeyCode()
|
keyCode = event.GetKeyCode()
|
||||||
# Ctrl + C
|
# Ctrl + C
|
||||||
if keyCode == 67 and event.ControlDown():
|
if keyCode == 67 and event.ControlDown():
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import wx
|
import wx
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from eos.saveddata.module import Module
|
|
||||||
import gui.builtinMarketBrowser.pfSearchBox as SBox
|
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.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
|
||||||
from gui.contextMenu import ContextMenu
|
from gui.contextMenu import ContextMenu
|
||||||
from gui.display import Display
|
from gui.display import Display
|
||||||
from gui.utils.staticHelpers import DragDropHelper
|
from gui.utils.staticHelpers import DragDropHelper
|
||||||
from service.attribute import Attribute
|
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
from config import slotColourMap
|
from service.market import Market
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -170,8 +171,8 @@ class ItemView(Display):
|
|||||||
def scheduleSearch(self, event=None):
|
def scheduleSearch(self, event=None):
|
||||||
self.searchTimer.Stop() # Cancel any pending timers
|
self.searchTimer.Stop() # Cancel any pending timers
|
||||||
search = self.marketBrowser.search.GetLineText(0)
|
search = self.marketBrowser.search.GetLineText(0)
|
||||||
# Make sure we do not count wildcard as search symbol
|
# Make sure we do not count wildcards as search symbol
|
||||||
realsearch = search.replace("*", "")
|
realsearch = search.replace('*', '').replace('?', '')
|
||||||
# Re-select market group if search query has zero length
|
# Re-select market group if search query has zero length
|
||||||
if len(realsearch) == 0:
|
if len(realsearch) == 0:
|
||||||
self.selectionMade('search')
|
self.selectionMade('search')
|
||||||
@@ -193,30 +194,15 @@ class ItemView(Display):
|
|||||||
self.setToggles()
|
self.setToggles()
|
||||||
self.filterItemStore()
|
self.filterItemStore()
|
||||||
|
|
||||||
def populateSearch(self, items):
|
def populateSearch(self, itemIDs):
|
||||||
# If we're no longer searching, dump the results
|
# If we're no longer searching, dump the results
|
||||||
if self.marketBrowser.mode != 'search':
|
if self.marketBrowser.mode != 'search':
|
||||||
return
|
return
|
||||||
|
items = Market.getItems(itemIDs)
|
||||||
self.updateItemStore(items)
|
self.updateItemStore(items)
|
||||||
self.setToggles()
|
self.setToggles()
|
||||||
self.filterItemStore()
|
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):
|
def contextMenu(self, event):
|
||||||
clickedPos = self.getRowByAbs(event.Position)
|
clickedPos = self.getRowByAbs(event.Position)
|
||||||
self.ensureSelection(clickedPos)
|
self.ensureSelection(clickedPos)
|
||||||
@@ -239,7 +225,7 @@ class ItemView(Display):
|
|||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
# Perform sorting, using item's meta levels besides other stuff
|
# Perform sorting, using item's meta levels besides other stuff
|
||||||
if self.marketBrowser.mode != 'recent':
|
if self.marketBrowser.mode != 'recent':
|
||||||
items.sort(key=self.itemSort)
|
items.sort(key=self.sMkt.itemSort)
|
||||||
# Mark current item list as active
|
# Mark current item list as active
|
||||||
self.active = items
|
self.active = items
|
||||||
# Show them
|
# Show them
|
||||||
@@ -249,12 +235,10 @@ class ItemView(Display):
|
|||||||
if len(items) > 1:
|
if len(items) > 1:
|
||||||
# Re-sort stuff
|
# Re-sort stuff
|
||||||
if self.marketBrowser.mode != 'recent':
|
if self.marketBrowser.mode != 'recent':
|
||||||
items.sort(key=self.itemSort)
|
items.sort(key=self.sMkt.itemSort)
|
||||||
|
|
||||||
for i, item in enumerate(items[:9]):
|
for i, item in enumerate(items[:9]):
|
||||||
# set shortcut info for first 9 modules
|
# set shortcut info for first 9 modules
|
||||||
item.marketShortcut = i + 1
|
item.marketShortcut = i + 1
|
||||||
|
|
||||||
Display.refresh(self, items)
|
Display.refresh(self, items)
|
||||||
|
|
||||||
def columnBackground(self, colItem, item):
|
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 market should have items but it doesn't, do not show it
|
||||||
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
|
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
|
||||||
continue
|
continue
|
||||||
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
|
icon = sMkt.getIconByMarketGroup(childMktGrp)
|
||||||
|
iconId = -1 if icon is None else self.addImage(icon)
|
||||||
try:
|
try:
|
||||||
childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID)
|
childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class PFSearchBox(wx.Window):
|
|||||||
|
|
||||||
def OnKeyPress(self, event):
|
def OnKeyPress(self, event):
|
||||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||||
HandleCtrlBackspace(self.EditBox)
|
HandleCtrlBackspace(self.EditBox)
|
||||||
else:
|
else:
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
|||||||
@@ -35,31 +35,31 @@ class PFGeneralPref(PreferenceView):
|
|||||||
# Database path
|
# Database path
|
||||||
self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, "pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, "pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.stSetUserPath.Wrap(-1)
|
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 = wx.TextCtrl(panel, wx.ID_ANY, config.savePath, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.inputUserPath.SetEditable(False)
|
self.inputUserPath.SetEditable(False)
|
||||||
self.inputUserPath.SetBackgroundColour((200, 200, 200))
|
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
|
# Save DB
|
||||||
self.stFitDB = wx.StaticText(panel, wx.ID_ANY, "Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.stFitDB = wx.StaticText(panel, wx.ID_ANY, "Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.stFitDB.Wrap(-1)
|
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 = wx.TextCtrl(panel, wx.ID_ANY, config.saveDB, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.inputFitDB.SetEditable(False)
|
self.inputFitDB.SetEditable(False)
|
||||||
self.inputFitDB.SetBackgroundColour((200, 200, 200))
|
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
|
# Game Data DB
|
||||||
self.stGameDB = wx.StaticText(panel, wx.ID_ANY, "Game Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.stGameDB = wx.StaticText(panel, wx.ID_ANY, "Game Database:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.stGameDB.Wrap(-1)
|
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 = wx.TextCtrl(panel, wx.ID_ANY, config.gameDB, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.inputGameDB.SetEditable(False)
|
self.inputGameDB.SetEditable(False)
|
||||||
self.inputGameDB.SetBackgroundColour((200, 200, 200))
|
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.SetValue(config.saveInRoot)
|
||||||
self.cbsaveInRoot.Bind(wx.EVT_CHECKBOX, self.onCBsaveInRoot)
|
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 = wx.Button(panel, -1, "Set export destination", pos=(0, 0))
|
||||||
self.fileSelectButton.Bind(wx.EVT_BUTTON, self.selectHTMLExportFilePath)
|
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 = wx.StaticText(panel, wx.ID_ANY, self.desc4, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.stDesc4.Wrap(dlgWidth - 50)
|
self.stDesc4.Wrap(dlgWidth - 50)
|
||||||
|
|||||||
@@ -36,20 +36,20 @@ class PFGeneralPref(PreferenceView):
|
|||||||
# Database path
|
# Database path
|
||||||
self.stLogPath = wx.StaticText(panel, wx.ID_ANY, "Log file location:", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.stLogPath = wx.StaticText(panel, wx.ID_ANY, "Log file location:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.stLogPath.Wrap(-1)
|
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 = wx.TextCtrl(panel, wx.ID_ANY, config.logPath, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.inputLogPath.SetEditable(False)
|
self.inputLogPath.SetEditable(False)
|
||||||
self.inputLogPath.SetBackgroundColour((200, 200, 200))
|
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
|
import requests
|
||||||
self.certPath = wx.StaticText(panel, wx.ID_ANY, "Cert Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.certPath = wx.StaticText(panel, wx.ID_ANY, "Cert Path:", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.certPath .Wrap(-1)
|
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 = wx.TextCtrl(panel, wx.ID_ANY, requests.certs.where(), wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.certPathCtrl.SetEditable(False)
|
self.certPathCtrl.SetEditable(False)
|
||||||
self.certPathCtrl.SetBackgroundColour((200, 200, 200))
|
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
|
# Debug Logging
|
||||||
self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, "Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0)
|
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:",
|
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)
|
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 = wx.Button(panel, wx.ID_ANY, "Dump All Logs", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
self.btnDumpLogs.Bind(wx.EVT_BUTTON, OnDumpLogs)
|
self.btnDumpLogs.Bind(wx.EVT_BUTTON, OnDumpLogs)
|
||||||
mainSizer.Add(self.btnDumpLogs, 0, wx.ALIGN_LEFT, 5)
|
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,
|
self.stPSAutoDetected = wx.StaticText(panel, wx.ID_ANY, "Auto-detected: ", wx.DefaultPosition, wx.DefaultSize,
|
||||||
0)
|
0)
|
||||||
self.stPSAutoDetected.Wrap(-1)
|
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 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
btnSizer.AddStretchSpacer()
|
btnSizer.AddStretchSpacer()
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
|||||||
|
|
||||||
def OnScheduleSearch(self, event):
|
def OnScheduleSearch(self, event):
|
||||||
search = self.BrowserSearchBox.GetValue()
|
search = self.BrowserSearchBox.GetValue()
|
||||||
# Make sure we do not count wildcard as search symbol
|
# Make sure we do not count wildcards as search symbol
|
||||||
realsearch = search.replace("*", "")
|
realsearch = search.replace('*', '').replace('?', '')
|
||||||
minChars = 1 if isStringCjk(realsearch) else 3
|
minChars = 1 if isStringCjk(realsearch) else 3
|
||||||
if len(realsearch) >= minChars:
|
if len(realsearch) >= minChars:
|
||||||
self.lastSearch = search
|
self.lastSearch = search
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ class FirepowerViewFull(StatsView):
|
|||||||
self.headerPanel = headerPanel
|
self.headerPanel = headerPanel
|
||||||
hsizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
hsizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
||||||
self.stEff = wx.StaticText(self.headerPanel, wx.ID_ANY, "( Effective )")
|
self.stEff = wx.StaticText(self.headerPanel, wx.ID_ANY, "( Effective )")
|
||||||
hsizer.Add(self.stEff)
|
hsizer.Insert(0, self.stEff)
|
||||||
# self.headerPanel.GetParent().AddToggleItem(self.stEff)
|
|
||||||
|
|
||||||
panel = "full"
|
panel = "full"
|
||||||
|
|
||||||
@@ -130,9 +129,12 @@ class FirepowerViewFull(StatsView):
|
|||||||
self.panel.GetSizer().Layout()
|
self.panel.GetSizer().Layout()
|
||||||
|
|
||||||
# Remove effective label
|
# Remove effective label
|
||||||
hsizer = self.headerPanel.GetSizer()
|
hsizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
||||||
hsizer.Hide(self.stEff)
|
for i, c in enumerate(hsizer.Children):
|
||||||
# self.stEff.Destroy()
|
if c.GetWindow() is self.stEff:
|
||||||
|
hsizer.Remove(i)
|
||||||
|
self.stEff.Destroy()
|
||||||
|
break
|
||||||
|
|
||||||
# Get the new view
|
# Get the new view
|
||||||
view = StatsView.getView("miningyieldViewFull")(self.parent)
|
view = StatsView.getView("miningyieldViewFull")(self.parent)
|
||||||
|
|||||||
@@ -123,6 +123,15 @@ class Miscellanea(ViewColumn):
|
|||||||
text = ' | '.join(i[0] for i in info)
|
text = ' | '.join(i[0] for i in info)
|
||||||
tooltip = ' and '.join(i[1] for i in info).capitalize()
|
tooltip = ' and '.join(i[1] for i in info).capitalize()
|
||||||
return text, tooltip
|
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":
|
elif itemCategory == "Subsystem":
|
||||||
slots = ("hi", "med", "low")
|
slots = ("hi", "med", "low")
|
||||||
info = []
|
info = []
|
||||||
@@ -133,7 +142,7 @@ class Miscellanea(ViewColumn):
|
|||||||
return "+ " + ", ".join(info), "Slot Modifiers"
|
return "+ " + ", ".join(info), "Slot Modifiers"
|
||||||
elif (
|
elif (
|
||||||
itemGroup in ("Energy Neutralizer", "Structure Energy Neutralizer") or
|
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")
|
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
|
||||||
cycleParams = stuff.getCycleParameters()
|
cycleParams = stuff.getCycleParameters()
|
||||||
@@ -182,7 +191,7 @@ class Miscellanea(ViewColumn):
|
|||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif (
|
elif (
|
||||||
itemGroup in ("Stasis Web", "Stasis Webifying Drone", "Structure Stasis Webifier") or
|
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")
|
speedFactor = stuff.getModifiedItemAttr("speedFactor")
|
||||||
if not speedFactor:
|
if not speedFactor:
|
||||||
@@ -193,7 +202,7 @@ class Miscellanea(ViewColumn):
|
|||||||
elif (
|
elif (
|
||||||
itemGroup == "Target Painter" or
|
itemGroup == "Target Painter" or
|
||||||
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectTargetPainter" in item.effects) 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")
|
sigRadBonus = stuff.getModifiedItemAttr("signatureRadiusBonus")
|
||||||
if not sigRadBonus:
|
if not sigRadBonus:
|
||||||
@@ -204,7 +213,7 @@ class Miscellanea(ViewColumn):
|
|||||||
elif (
|
elif (
|
||||||
itemGroup == "Sensor Dampener" or
|
itemGroup == "Sensor Dampener" or
|
||||||
(itemGroup == "Structure Disruption Battery" and "structureModuleEffectRemoteSensorDampener" in item.effects) 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")
|
lockRangeBonus = stuff.getModifiedItemAttr("maxTargetRangeBonus")
|
||||||
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
|
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
|
||||||
@@ -226,7 +235,7 @@ class Miscellanea(ViewColumn):
|
|||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif (
|
elif (
|
||||||
itemGroup in ("Weapon Disruptor", "Structure Disruption Battery") or
|
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
|
# Weapon disruption now covers both tracking and guidance (missile) disruptors
|
||||||
# First get the attributes for tracking disruptors
|
# First get the attributes for tracking disruptors
|
||||||
@@ -279,7 +288,8 @@ class Miscellanea(ViewColumn):
|
|||||||
"Heat Sink",
|
"Heat Sink",
|
||||||
"Ballistic Control system",
|
"Ballistic Control system",
|
||||||
"Structure Weapon Upgrade",
|
"Structure Weapon Upgrade",
|
||||||
"Entropic Radiation Sink"
|
"Entropic Radiation Sink",
|
||||||
|
"Vorton Projector Upgrade"
|
||||||
):
|
):
|
||||||
attrMap = {
|
attrMap = {
|
||||||
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
|
"Gyrostabilizer": ("damageMultiplier", "speedMultiplier", "Projectile weapon"),
|
||||||
@@ -287,7 +297,8 @@ class Miscellanea(ViewColumn):
|
|||||||
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
|
"Heat Sink": ("damageMultiplier", "speedMultiplier", "Energy weapon"),
|
||||||
"Ballistic Control system": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
"Ballistic Control system": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||||
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
"Structure Weapon Upgrade": ("missileDamageMultiplierBonus", "speedMultiplier", "Missile"),
|
||||||
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon")}
|
"Entropic Radiation Sink": ("damageMultiplier", "speedMultiplier", "Precursor weapon"),
|
||||||
|
"Vorton Projector Upgrade": ("damageMultiplier", "speedMultiplier", "Vorton projector")}
|
||||||
dmgAttr, rofAttr, weaponName = attrMap[itemGroup]
|
dmgAttr, rofAttr, weaponName = attrMap[itemGroup]
|
||||||
dmg = stuff.getModifiedItemAttr(dmgAttr)
|
dmg = stuff.getModifiedItemAttr(dmgAttr)
|
||||||
rof = stuff.getModifiedItemAttr(rofAttr)
|
rof = stuff.getModifiedItemAttr(rofAttr)
|
||||||
@@ -311,8 +322,8 @@ class Miscellanea(ViewColumn):
|
|||||||
tooltip = "Drone DPS boost"
|
tooltip = "Drone DPS boost"
|
||||||
return text, tooltip
|
return text, tooltip
|
||||||
elif (
|
elif (
|
||||||
itemGroup in ("ECM", "Burst Jammer", "Burst Projectors", "Structure ECM Battery") or
|
itemGroup in ("ECM", "Burst Jammer", "Structure ECM Battery") or
|
||||||
(itemGroup == "Structure Burst Projector" and "doomsdayAOEECM" in item.effects)
|
(itemGroup in ("Structure Burst Projector", "Burst Projectors") and "doomsdayAOEECM" in item.effects)
|
||||||
):
|
):
|
||||||
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthBonus")
|
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthBonus")
|
||||||
ladar = stuff.getModifiedItemAttr("scanLadarStrengthBonus")
|
ladar = stuff.getModifiedItemAttr("scanLadarStrengthBonus")
|
||||||
@@ -680,6 +691,13 @@ class Miscellanea(ViewColumn):
|
|||||||
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
|
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
|
||||||
)
|
)
|
||||||
return text, tooltip
|
return text, tooltip
|
||||||
|
elif itemGroup in ("Cargo Scanner", "Ship Scanner", "Survey Scanner"):
|
||||||
|
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:
|
elif stuff.charge is not None:
|
||||||
chargeGroup = stuff.charge.group.name
|
chargeGroup = stuff.charge.group.name
|
||||||
if chargeGroup.endswith("Rocket") or chargeGroup.endswith("Missile") or chargeGroup.endswith("Torpedo"):
|
if chargeGroup.endswith("Rocket") or chargeGroup.endswith("Missile") or chargeGroup.endswith("Torpedo"):
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class FittingView(d.Display):
|
|||||||
self.hoveredRow = None
|
self.hoveredRow = None
|
||||||
self.hoveredColumn = 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_LEFT_DOWN, self.click)
|
||||||
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
||||||
self.Bind(wx.EVT_MIDDLE_DOWN, self.click)
|
self.Bind(wx.EVT_MIDDLE_DOWN, self.click)
|
||||||
@@ -377,23 +377,7 @@ class FittingView(d.Display):
|
|||||||
event.Skip()
|
event.Skip()
|
||||||
return
|
return
|
||||||
batchOp = wx.GetMouseState().GetModifiers() == wx.MOD_ALT and getattr(event, 'allowBatch', None) is not False
|
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.isModule and not batchOp) or item.isSubsystem:
|
||||||
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:
|
|
||||||
self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID))
|
self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID))
|
||||||
elif item.isModule and batchOp:
|
elif item.isModule and batchOp:
|
||||||
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID))
|
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID))
|
||||||
|
|||||||
@@ -302,8 +302,8 @@ class ItemView(d.Display):
|
|||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
|
|
||||||
search = self.searchBox.GetLineText(0)
|
search = self.searchBox.GetLineText(0)
|
||||||
# Make sure we do not count wildcard as search symbol
|
# Make sure we do not count wildcards as search symbol
|
||||||
realsearch = search.replace("*", "")
|
realsearch = search.replace('*', '').replace('?', '')
|
||||||
# Show nothing if query is too short
|
# Show nothing if query is too short
|
||||||
if len(realsearch) < 3:
|
if len(realsearch) < 3:
|
||||||
self.clearSearch()
|
self.clearSearch()
|
||||||
@@ -311,12 +311,12 @@ class ItemView(d.Display):
|
|||||||
|
|
||||||
sMkt.searchItems(search, self.populateSearch, 'implants')
|
sMkt.searchItems(search, self.populateSearch, 'implants')
|
||||||
|
|
||||||
def populateSearch(self, items):
|
def populateSearch(self, itemIDs):
|
||||||
if not self.IsShown():
|
if not self.IsShown():
|
||||||
self.parent.availableImplantsTree.Hide()
|
self.parent.availableImplantsTree.Hide()
|
||||||
self.Show()
|
self.Show()
|
||||||
self.parent.Layout()
|
self.parent.Layout()
|
||||||
|
items = Market.getItems(itemIDs)
|
||||||
items = [i for i in items if i.group.name != 'Booster']
|
items = [i for i in items if i.group.name != 'Booster']
|
||||||
self.items = sorted(list(items), key=lambda i: i.name)
|
self.items = sorted(list(items), key=lambda i: i.name)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from wx.lib.agw.floatspin import FloatSpin
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
|
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
|
||||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||||
@@ -312,6 +312,7 @@ class CharacterEditor(AuxiliaryFrame):
|
|||||||
|
|
||||||
|
|
||||||
class SkillTreeView(wx.Panel):
|
class SkillTreeView(wx.Panel):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
|
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
|
||||||
style=wx.TAB_TRAVERSAL)
|
style=wx.TAB_TRAVERSAL)
|
||||||
@@ -402,7 +403,7 @@ class SkillTreeView(wx.Panel):
|
|||||||
setattr(self, "{}Btn".format(name.lower()), btn)
|
setattr(self, "{}Btn".format(name.lower()), btn)
|
||||||
btn.Enable(True)
|
btn.Enable(True)
|
||||||
btn.SetToolTip("%s skills %s clipboard" % (name, direction))
|
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())))
|
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Skills".format(name.lower())))
|
||||||
|
|
||||||
pmainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)
|
pmainSizer.Add(bSizerButtons, 0, wx.EXPAND, 5)
|
||||||
@@ -611,12 +612,16 @@ class SkillTreeView(wx.Panel):
|
|||||||
|
|
||||||
def spawnMenu(self, event):
|
def spawnMenu(self, event):
|
||||||
item = event.GetItem()
|
item = event.GetItem()
|
||||||
|
itemData = self.skillTreeListCtrl.GetItemData(item)
|
||||||
|
if itemData is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.skillTreeListCtrl.Select(item)
|
self.skillTreeListCtrl.Select(item)
|
||||||
thing = self.skillTreeListCtrl.GetFirstChild(item).IsOk()
|
thing = self.skillTreeListCtrl.GetFirstChild(item).IsOk()
|
||||||
if thing:
|
if thing:
|
||||||
return
|
return
|
||||||
|
|
||||||
id = self.skillTreeListCtrl.GetItemData(item)[1]
|
id = itemData[1]
|
||||||
eveItem = Market.getInstance().getItem(id)
|
eveItem = Market.getInstance().getItem(id)
|
||||||
|
|
||||||
srcContext = "skillItem"
|
srcContext = "skillItem"
|
||||||
@@ -833,7 +838,7 @@ class APIView(wx.Panel):
|
|||||||
def fetchSkills(self, evt):
|
def fetchSkills(self, evt):
|
||||||
sChar = Character.getInstance()
|
sChar = Character.getInstance()
|
||||||
char = self.charEditor.entityEditor.getActiveEntity()
|
char = self.charEditor.entityEditor.getActiveEntity()
|
||||||
sChar.apiFetch(char.ID, self.__fetchCallback)
|
sChar.apiFetch(char.ID, APIView.fetchCallback)
|
||||||
|
|
||||||
def addCharacter(self, event):
|
def addCharacter(self, event):
|
||||||
sEsi = Esi.getInstance()
|
sEsi = Esi.getInstance()
|
||||||
@@ -899,7 +904,8 @@ class APIView(wx.Panel):
|
|||||||
if event is not None:
|
if event is not None:
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
def __fetchCallback(self, e=None):
|
@staticmethod
|
||||||
|
def fetchCallback(e=None):
|
||||||
if e:
|
if e:
|
||||||
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
|
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
|
||||||
exc_type, exc_value, exc_trace = e
|
exc_type, exc_value, exc_trace = e
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class CopySelectDialog(wx.Dialog):
|
|||||||
|
|
||||||
def exportEsi(self, options, callback):
|
def exportEsi(self, options, callback):
|
||||||
fit = getFit(self.mainFrame.getActiveFit())
|
fit = getFit(self.mainFrame.getActiveFit())
|
||||||
Port.exportESI(fit, callback)
|
Port.exportESI(fit, True, callback)
|
||||||
|
|
||||||
def exportXml(self, options, callback):
|
def exportXml(self, options, callback):
|
||||||
fit = getFit(self.mainFrame.getActiveFit())
|
fit = getFit(self.mainFrame.getActiveFit())
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import wx
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.builtinShipBrowser.events import FitSelected
|
from gui.builtinShipBrowser.events import FitSelected
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import wx
|
|||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from service.prereqsCheck import version_block
|
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,
|
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)
|
(-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))
|
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.AppendText("\n")
|
||||||
self.errorTextCtrl.Layout()
|
self.errorTextCtrl.Layout()
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ import requests
|
|||||||
import wx
|
import wx
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
import config
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from eos.db import getItem
|
from eos.db import getItem
|
||||||
from eos.saveddata.cargo import Cargo
|
from eos.saveddata.cargo import Cargo
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.display import Display
|
from gui.display import Display
|
||||||
|
from gui.characterEditor import APIView
|
||||||
|
from service.character import Character
|
||||||
from service.esi import Esi
|
from service.esi import Esi
|
||||||
from service.esiAccess import APIException
|
from service.esiAccess import APIException
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
from service.port import Port
|
from service.port import Port
|
||||||
from service.port.esi import ESIExportException
|
from service.port.esi import ESIExportException
|
||||||
|
from service.settings import EsiSettings
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
@@ -204,7 +208,7 @@ class ExportToEve(AuxiliaryFrame):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition,
|
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
|
self.mainFrame = parent
|
||||||
|
|
||||||
@@ -221,6 +225,11 @@ class ExportToEve(AuxiliaryFrame):
|
|||||||
|
|
||||||
mainSizer.Add(hSizer, 0, wx.EXPAND, 5)
|
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.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting)
|
||||||
|
|
||||||
self.statusbar = wx.StatusBar(self)
|
self.statusbar = wx.StatusBar(self)
|
||||||
@@ -236,6 +245,10 @@ class ExportToEve(AuxiliaryFrame):
|
|||||||
|
|
||||||
self.Center(wx.BOTH)
|
self.Center(wx.BOTH)
|
||||||
|
|
||||||
|
def OnChargeExportChange(self, event):
|
||||||
|
EsiSettings.getInstance().set('exportCharges', self.exportChargesCb.GetValue())
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def updateCharList(self):
|
def updateCharList(self):
|
||||||
sEsi = Esi.getInstance()
|
sEsi = Esi.getInstance()
|
||||||
chars = sEsi.getSsoCharacters()
|
chars = sEsi.getSsoCharacters()
|
||||||
@@ -271,8 +284,9 @@ class ExportToEve(AuxiliaryFrame):
|
|||||||
sEsi = Esi.getInstance()
|
sEsi = Esi.getInstance()
|
||||||
|
|
||||||
sFit = Fit.getInstance()
|
sFit = Fit.getInstance()
|
||||||
|
exportCharges = self.exportChargesCb.GetValue()
|
||||||
try:
|
try:
|
||||||
data = sPort.exportESI(sFit.getFit(fitID))
|
data = sPort.exportESI(sFit.getFit(fitID), exportCharges)
|
||||||
except ESIExportException as e:
|
except ESIExportException as e:
|
||||||
msg = str(e)
|
msg = str(e)
|
||||||
if not msg:
|
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)
|
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)
|
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)
|
btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
mainSizer.Add(btnSizer, 0, wx.EXPAND, 5)
|
mainSizer.Add(btnSizer, 0, wx.EXPAND, 5)
|
||||||
@@ -355,6 +369,16 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
|||||||
|
|
||||||
def ssoLogin(self, event):
|
def ssoLogin(self, event):
|
||||||
self.popCharList()
|
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()
|
event.Skip()
|
||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ class CalcAddLocalModuleCommand(wx.Command):
|
|||||||
position=fit.modules.index(oldMod),
|
position=fit.modules.index(oldMod),
|
||||||
newModInfo=self.newModInfo)
|
newModInfo=self.newModInfo)
|
||||||
return self.subsystemCmd.Do()
|
return self.subsystemCmd.Do()
|
||||||
if not newMod.fits(fit):
|
|
||||||
pyfalog.warning('Module does not fit')
|
|
||||||
return False
|
|
||||||
fit.modules.append(newMod)
|
fit.modules.append(newMod)
|
||||||
if newMod not in fit.modules:
|
if newMod not in fit.modules:
|
||||||
pyfalog.warning('Failed to append to list')
|
pyfalog.warning('Failed to append to list')
|
||||||
@@ -52,6 +49,13 @@ class CalcAddLocalModuleCommand(wx.Command):
|
|||||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||||
eos.db.flush()
|
eos.db.flush()
|
||||||
sFit.recalc(fit)
|
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)
|
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -63,7 +67,7 @@ class CalcAddLocalModuleCommand(wx.Command):
|
|||||||
if self.savedPosition is None:
|
if self.savedPosition is None:
|
||||||
return False
|
return False
|
||||||
from .localRemove import CalcRemoveLocalModulesCommand
|
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():
|
if not cmd.Do():
|
||||||
return False
|
return False
|
||||||
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from logbook import Logger
|
|||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
from eos.const import FittingSlot
|
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
|
from service.fit import Fit
|
||||||
|
|
||||||
|
|
||||||
@@ -12,14 +12,16 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
class CalcRemoveLocalModulesCommand(wx.Command):
|
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')
|
wx.Command.__init__(self, True, 'Remove Module')
|
||||||
self.fitID = fitID
|
self.fitID = fitID
|
||||||
self.positions = positions
|
self.positions = positions
|
||||||
self.recalc = recalc
|
self.recalc = recalc
|
||||||
|
self.clearTail = clearTail
|
||||||
self.savedSubInfos = None
|
self.savedSubInfos = None
|
||||||
self.savedModInfos = None
|
self.savedModInfos = None
|
||||||
self.savedStateCheckChanges = None
|
self.savedStateCheckChanges = None
|
||||||
|
self.savedTail = None
|
||||||
|
|
||||||
def Do(self):
|
def Do(self):
|
||||||
pyfalog.debug('Doing removal of local modules from positions {} on fit {}'.format(self.positions, self.fitID))
|
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:
|
if len(self.savedSubInfos) == 0 and len(self.savedModInfos) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.clearTail:
|
||||||
|
self.savedTail = fit.clearTail()
|
||||||
|
|
||||||
if self.recalc:
|
if self.recalc:
|
||||||
# Need to flush because checkStates sometimes relies on module->fit
|
# Need to flush because checkStates sometimes relies on module->fit
|
||||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||||
@@ -76,6 +81,7 @@ class CalcRemoveLocalModulesCommand(wx.Command):
|
|||||||
if not any(results):
|
if not any(results):
|
||||||
return False
|
return False
|
||||||
restoreCheckedStates(fit, self.savedStateCheckChanges)
|
restoreCheckedStates(fit, self.savedStateCheckChanges)
|
||||||
|
restoreRemovedDummies(fit, self.savedTail)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -39,7 +39,17 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
|||||||
if newMod.slot != oldMod.slot:
|
if newMod.slot != oldMod.slot:
|
||||||
return False
|
return False
|
||||||
# Dummy it out in case the next bit fails
|
# 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):
|
if not self.ignoreRestrictions and not newMod.fits(fit):
|
||||||
pyfalog.warning('Module does not fit')
|
pyfalog.warning('Module does not fit')
|
||||||
self.Undo()
|
self.Undo()
|
||||||
@@ -52,17 +62,6 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
|||||||
pyfalog.warning('Invalid charge')
|
pyfalog.warning('Invalid charge')
|
||||||
self.Undo()
|
self.Undo()
|
||||||
return False
|
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
|
return True
|
||||||
|
|
||||||
def Undo(self):
|
def Undo(self):
|
||||||
|
|||||||
@@ -324,7 +324,8 @@ def activeStateLimit(itemIdentity):
|
|||||||
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
|
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
|
||||||
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
|
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
|
||||||
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
|
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
|
||||||
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively'
|
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively',
|
||||||
|
'cargoScan', 'shipScan', 'surveyScan'
|
||||||
}.intersection(item.effects):
|
}.intersection(item.effects):
|
||||||
return FittingModuleState.ONLINE
|
return FittingModuleState.ONLINE
|
||||||
return FittingModuleState.ACTIVE
|
return FittingModuleState.ACTIVE
|
||||||
@@ -353,6 +354,8 @@ def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
|
|||||||
|
|
||||||
|
|
||||||
def restoreRemovedDummies(fit, dummyInfo):
|
def restoreRemovedDummies(fit, dummyInfo):
|
||||||
|
if dummyInfo is None:
|
||||||
|
return
|
||||||
# Need this to properly undo the case when removal of subsystems removes dummy slots
|
# Need this to properly undo the case when removal of subsystems removes dummy slots
|
||||||
for position in sorted(dummyInfo):
|
for position in sorted(dummyInfo):
|
||||||
slot = dummyInfo[position]
|
slot = dummyInfo[position]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import wx
|
|||||||
import config
|
import config
|
||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
from eos.saveddata.module import Module
|
from eos.saveddata.module import Module
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
||||||
from gui.builtinItemStatsViews.itemAttributes import ItemParams
|
from gui.builtinItemStatsViews.itemAttributes import ItemParams
|
||||||
|
|||||||
@@ -610,7 +610,9 @@ class MainFrame(wx.Frame):
|
|||||||
(wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext),
|
(wx.ACCEL_CTRL, wx.WXK_PAGEDOWN, ctabnext),
|
||||||
(wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev),
|
(wx.ACCEL_CTRL, wx.WXK_PAGEUP, ctabprev),
|
||||||
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
(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
|
# Ctrl/Cmd+# for addition pane selection
|
||||||
@@ -678,8 +680,8 @@ class MainFrame(wx.Frame):
|
|||||||
|
|
||||||
def toggleOverrides(self, event):
|
def toggleOverrides(self, event):
|
||||||
ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled
|
ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled
|
||||||
|
changedFitIDs = Fit.getInstance().processOverrideToggle()
|
||||||
wx.PostEvent(self, GE.FitChanged(fitIDs=(self.getActiveFit(),)))
|
wx.PostEvent(self, GE.FitChanged(fitIDs=changedFitIDs))
|
||||||
menu = self.GetMenuBar()
|
menu = self.GetMenuBar()
|
||||||
menu.SetLabel(menu.toggleOverridesId,
|
menu.SetLabel(menu.toggleOverridesId,
|
||||||
"&Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "&Turn Overrides On")
|
"&Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "&Turn Overrides On")
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ class MainMenuBar(wx.MenuBar):
|
|||||||
fitMenu = wx.Menu()
|
fitMenu = wx.Menu()
|
||||||
self.Append(fitMenu, "Fi&t")
|
self.Append(fitMenu, "Fi&t")
|
||||||
|
|
||||||
fitMenu.Append(wx.ID_UNDO)
|
fitMenu.Append(wx.ID_UNDO, "&Undo\tCTRL+Z", "Undo the most recent action")
|
||||||
fitMenu.Append(wx.ID_REDO)
|
fitMenu.Append(wx.ID_REDO, "&Redo\tCTRL+Y", "Redo the most recent undone action")
|
||||||
|
|
||||||
fitMenu.AppendSeparator()
|
fitMenu.AppendSeparator()
|
||||||
fitMenu.Append(wx.ID_COPY, "&To Clipboard\tCTRL+C", "Export a fit to the clipboard")
|
fitMenu.Append(wx.ID_COPY, "&To Clipboard\tCTRL+C", "Export a fit to the clipboard")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
import wx
|
import wx
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||||
@@ -181,7 +181,7 @@ class DmgPatternEditor(AuxiliaryFrame):
|
|||||||
setattr(self, name, btn)
|
setattr(self, name, btn)
|
||||||
btn.Enable(True)
|
btn.Enable(True)
|
||||||
btn.SetToolTip("%s patterns %s clipboard" % (name, direction))
|
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())))
|
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
|
||||||
|
|
||||||
if not self.entityEditor.checkEntitiesExist():
|
if not self.entityEditor.checkEntitiesExist():
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
|||||||
import gui.display as d
|
import gui.display as d
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from eos.db.gamedata.queries import getAttributeInfo, getItem
|
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.bitmap_loader import BitmapLoader
|
||||||
from gui.marketBrowser import SearchBox
|
from gui.marketBrowser import SearchBox
|
||||||
|
from service.fit import Fit
|
||||||
from service.market import Market
|
from service.market import Market
|
||||||
|
|
||||||
|
|
||||||
@@ -170,12 +171,15 @@ class ItemView(d.Display):
|
|||||||
d.Display.__init__(self, parent)
|
d.Display.__init__(self, parent)
|
||||||
self.activeItems = []
|
self.activeItems = []
|
||||||
|
|
||||||
|
self.searchTimer = wx.Timer(self)
|
||||||
|
self.Bind(wx.EVT_TIMER, self.scheduleSearch, self.searchTimer)
|
||||||
|
|
||||||
self.searchBox = parent.Parent.Parent.searchBox
|
self.searchBox = parent.Parent.Parent.searchBox
|
||||||
# Bind search actions
|
# Bind search actions
|
||||||
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
|
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
|
||||||
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, 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_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())
|
self.update(Market.getInstance().getItemsWithOverrides())
|
||||||
|
|
||||||
@@ -188,12 +192,17 @@ class ItemView(d.Display):
|
|||||||
if updateDisplay:
|
if updateDisplay:
|
||||||
self.update(Market.getInstance().getItemsWithOverrides())
|
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):
|
def scheduleSearch(self, event=None):
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
|
|
||||||
search = self.searchBox.GetLineText(0)
|
search = self.searchBox.GetLineText(0)
|
||||||
# Make sure we do not count wildcard as search symbol
|
# Make sure we do not count wildcards as search symbol
|
||||||
realsearch = search.replace("*", "")
|
realsearch = search.replace('*', '').replace('?', '')
|
||||||
# Show nothing if query is too short
|
# Show nothing if query is too short
|
||||||
if len(realsearch) < 3:
|
if len(realsearch) < 3:
|
||||||
self.clearSearch()
|
self.clearSearch()
|
||||||
@@ -204,21 +213,10 @@ class ItemView(d.Display):
|
|||||||
def itemSort(self, item):
|
def itemSort(self, item):
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
|
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
|
||||||
catname = sMkt.getCategoryByItem(item).name
|
return (not isFittable, *sMkt.itemSort(item))
|
||||||
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, catname, mktgrpid, parentname, metatab, metalvl, item.name
|
def populateSearch(self, itemIDs):
|
||||||
|
items = Market.getItems(itemIDs)
|
||||||
def populateSearch(self, items):
|
|
||||||
self.update(items)
|
self.update(items)
|
||||||
|
|
||||||
def populate(self, items):
|
def populate(self, items):
|
||||||
|
|||||||
@@ -254,7 +254,6 @@ class PyGauge(wx.Window):
|
|||||||
w = rect.width
|
w = rect.width
|
||||||
else:
|
else:
|
||||||
w = rect.width * (float(value) / 100)
|
w = rect.width * (float(value) / 100)
|
||||||
|
|
||||||
r = copy.copy(rect)
|
r = copy.copy(rect)
|
||||||
r.width = w
|
r.width = w
|
||||||
dc.DrawRectangle(r)
|
dc.DrawRectangle(r)
|
||||||
@@ -315,7 +314,8 @@ class PyGauge(wx.Window):
|
|||||||
color,
|
color,
|
||||||
gradient_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
|
# font stuff begins here
|
||||||
dc.SetFont(self.font)
|
dc.SetFont(self.font)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
import wx
|
import wx
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||||
@@ -159,7 +159,7 @@ class ImplantSetEditor(AuxiliaryFrame):
|
|||||||
setattr(self, name, btn)
|
setattr(self, name, btn)
|
||||||
btn.Enable(True)
|
btn.Enable(True)
|
||||||
btn.SetToolTip("%s implant sets %s clipboard" % (name, direction))
|
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)
|
mainSizer.Add(footerSizer, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class ShipBrowser(wx.Panel):
|
|||||||
"amarr", "caldari", "gallente", "minmatar",
|
"amarr", "caldari", "gallente", "minmatar",
|
||||||
"sisters", "ore", "concord",
|
"sisters", "ore", "concord",
|
||||||
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
|
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
|
||||||
"jove", "upwell", "triglavian", None
|
"jove", "triglavian", "upwell", None
|
||||||
]
|
]
|
||||||
|
|
||||||
def raceNameKey(self, ship):
|
def raceNameKey(self, ship):
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class StatsPane(wx.Panel):
|
|||||||
sizer.AddStretchSpacer()
|
sizer.AddStretchSpacer()
|
||||||
# Add menu
|
# Add menu
|
||||||
header_menu = wx.StaticText(tp.GetHeaderPanel(), wx.ID_ANY, "\u2630", size=wx.Size((10, -1)))
|
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_CONTEXT_MENU, handler)
|
||||||
header_menu.Bind(wx.EVT_LEFT_UP, handler)
|
header_menu.Bind(wx.EVT_LEFT_UP, handler)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from logbook import Logger
|
|||||||
|
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
from gui.auxFrame import AuxiliaryFrame
|
from gui.auxWindow import AuxiliaryFrame
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||||
@@ -183,7 +183,7 @@ class TargetProfileEditor(AuxiliaryFrame):
|
|||||||
ttText, unitText = self.ATTRIBUTES[attr]
|
ttText, unitText = self.ATTRIBUTES[attr]
|
||||||
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % attr, "gui"))
|
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % attr, "gui"))
|
||||||
bmp.SetToolTip(wx.ToolTip(ttText))
|
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
|
# set text edit
|
||||||
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
|
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
|
||||||
editBox.SetToolTip(wx.ToolTip(ttText))
|
editBox.SetToolTip(wx.ToolTip(ttText))
|
||||||
@@ -231,7 +231,7 @@ class TargetProfileEditor(AuxiliaryFrame):
|
|||||||
setattr(self, name, btn)
|
setattr(self, name, btn)
|
||||||
btn.Enable(True)
|
btn.Enable(True)
|
||||||
btn.SetToolTip("%s profiles %s clipboard" % (name, direction))
|
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())))
|
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
|
||||||
|
|
||||||
if not self.entityEditor.checkEntitiesExist():
|
if not self.entityEditor.checkEntitiesExist():
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ def DrawFilledBitmap(width, height, color):
|
|||||||
|
|
||||||
|
|
||||||
def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4):
|
def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4):
|
||||||
|
if width == 0 or height == 0:
|
||||||
|
return None
|
||||||
canvas = wx.Bitmap(width, height)
|
canvas = wx.Bitmap(width, height)
|
||||||
|
|
||||||
mdc = wx.MemoryDC()
|
mdc = wx.MemoryDC()
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
|
from eos.utils.round import roundToPrec, roundDec
|
||||||
|
|
||||||
|
|
||||||
def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=False, unitName=None):
|
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:
|
else:
|
||||||
result = "{}{} {}{}".format(sign, mantissa, suffix, unitName)
|
result = "{}{} {}{}".format(sign, mantissa, suffix, unitName)
|
||||||
return result
|
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 |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 834 B After Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.8 KiB |