Compare commits

...

74 Commits

Author SHA1 Message Date
DarkPhoenix
5db97ea773 Bump version 2020-04-16 22:46:45 +03:00
DarkPhoenix
1758e4f320 Rework fix for projection, so that it does not recalculate projected fits too early 2020-04-16 22:25:10 +03:00
DarkPhoenix
1a897c0419 Pass search results as set of item IDs 2020-04-16 15:13:32 +03:00
DarkPhoenix
32db3e3179 Create scoped gamedata sessions 2020-04-16 15:01:00 +03:00
DarkPhoenix
d830a8957a Bump version 2020-04-15 16:54:24 +03:00
DarkPhoenix
652ea48223 Accept 0 free slots for guns which have already been fitted 2020-04-15 16:53:03 +03:00
DarkPhoenix
8c25b2b8f5 Make sure it's impossible to add extra cap boosters via dragging too 2020-04-15 15:40:54 +03:00
DarkPhoenix
db4c56be8e Bump version 2020-04-15 15:27:19 +03:00
DarkPhoenix
f3bcffe2f9 Implement fax cap booster limit 2020-04-15 15:23:49 +03:00
DarkPhoenix
bc5786d099 Update static data to 1706308 2020-04-15 14:50:55 +03:00
DarkPhoenix
5959fe5daf Ignore case when searching for implant sets to allow some inconsistencies on CCP side 2020-04-14 15:28:40 +03:00
DarkPhoenix
649d338bb1 Split implants and boosters in EFT and multibuy exports 2020-04-14 11:36:08 +03:00
DarkPhoenix
dcb058a718 Update existing character if character with the same name exists 2020-04-13 12:52:02 +03:00
DarkPhoenix
1772bb5e7f Merge branch 'master' into singularity 2020-04-13 12:45:25 +03:00
Anton Vorobyov
30bd0adb06 Merge pull request #2159 from soro/auto_create_char
Regarding #2045: automatically create a character for a new sso character
2020-04-12 22:49:01 +03:00
DarkPhoenix
44dfcf771c Ensure projected fits get recalculated, since they get checkStates'd anyway 2020-04-11 02:26:17 +03:00
DarkPhoenix
a1f8a7a930 Fix escaping in regex search 2020-04-11 01:33:53 +03:00
Soeren Roerden
b22887dfad automatically create a character for a new sso character and add button to retrieve all chars directly linked to existing sso characters 2020-04-10 21:12:38 +02:00
DarkPhoenix
28137fa3f4 Remove excessive space 2020-04-10 13:11:04 +03:00
DarkPhoenix
9cbdc6055d Add search timer to attribute overrides to prevent hangs 2020-04-10 11:52:57 +03:00
DarkPhoenix
fc93c61fcf Add more entries 2020-04-10 11:31:17 +03:00
DarkPhoenix
3fa2e7ebd1 More additions to jargon 2020-04-10 06:07:33 +03:00
DarkPhoenix
818628da0c Finalize new jargon dictionary 2020-04-10 05:53:24 +03:00
DarkPhoenix
adf90a8263 Split basic and regex search functions 2020-04-10 00:57:41 +03:00
DarkPhoenix
362923ac64 Overhaul jargon dictionary (some entries are still missing) 2020-04-09 23:29:51 +03:00
DarkPhoenix
7d73838ce1 Change the way user definitions are used 2020-04-08 14:06:49 +03:00
DarkPhoenix
b3278ca9ec Tokenize regexp requests taking into consideration regexp context 2020-04-08 13:24:48 +03:00
DarkPhoenix
5707914ad5 Make use of regex for search 2020-04-08 03:17:25 +03:00
DarkPhoenix
9b697b24d8 Implement regexp function for gamedata 2020-04-08 02:34:28 +03:00
DarkPhoenix
098b088da6 Bump version 2020-04-07 21:39:24 +03:00
DarkPhoenix
14d62f31e1 Add conversions for names 2020-04-07 21:37:40 +03:00
DarkPhoenix
e7b1f55d08 Update static data to 1702461 2020-04-07 20:59:07 +03:00
DarkPhoenix
8f501896a1 Give separate axis selector and axis label names to make it clearer what the graphs do 2020-04-07 20:37:28 +03:00
DarkPhoenix
befcb9b874 Un-hide and un-fuck bumping graphs 2020-04-07 20:14:06 +03:00
DarkPhoenix
c70afa9a4c Hide experimental bumping graphs 2020-04-07 18:25:40 +03:00
DarkPhoenix
029c7dd4c2 Recalculate fit when overrides are toggled 2020-04-07 18:05:43 +03:00
DarkPhoenix
1f83dba1ac Add missing hidden imports to platform-specific specifications 2020-04-07 18:00:17 +03:00
DarkPhoenix
6eae3405fd Add hidden import for newer version of setuptools 2020-04-07 17:39:24 +03:00
DarkPhoenix
c0e1d9e4de Attempt to use newer version of setuptools 2020-04-07 17:09:59 +03:00
DarkPhoenix
7c0bd7aa88 Leave only setuptools override 2020-04-07 16:41:28 +03:00
DarkPhoenix
394583584c Remove some of hardcoded libraries for MacOS X build 2020-04-07 15:08:30 +03:00
DarkPhoenix
4112e2aa6b Debugging Mac OS X build, step 1 2020-04-07 14:17:55 +03:00
DarkPhoenix
aa19e0da72 Add bunch of experimental graphs related to bumping 2020-04-07 13:46:28 +03:00
DarkPhoenix
bba9be1598 Add momentum graphs 2020-04-07 10:50:12 +03:00
DarkPhoenix
17998916b4 Bump version 2020-04-06 22:24:06 +03:00
DarkPhoenix
543089bcd9 Add context menu support for predefined implant sets 2020-04-06 22:20:28 +03:00
DarkPhoenix
7f35c78a65 Add data about implant sets to eve.db 2020-04-06 21:01:20 +03:00
DarkPhoenix
b25798dd83 Update effects 2020-04-06 20:14:19 +03:00
DarkPhoenix
744b9ff78a Updated staticdata to 1701385 2020-04-06 19:19:06 +03:00
DarkPhoenix
8dc1457ebb Change the way zip file is generated 2020-04-06 15:09:06 +03:00
DarkPhoenix
982ad54fab Merge branch 'Metallicow-optimize-images' 2020-04-06 15:29:10 +03:00
DarkPhoenix
912192cd7d Merge branch 'optimize-images' of https://github.com/Metallicow/Pyfa into Metallicow-optimize-images 2020-04-06 15:26:32 +03:00
DarkPhoenix
74cd6d48da Show propmpt to creade damage patterns when there're none 2020-04-06 11:14:21 +03:00
DarkPhoenix
3467a7fe3f Update static data to 1684558 2020-03-11 17:01:34 +03:00
DarkPhoenix
eb269a05ed Bump version 2020-03-10 19:39:22 +03:00
DarkPhoenix
c63cf4b3b0 Add cap booster conversions 2020-03-10 19:19:13 +03:00
DarkPhoenix
11ed94454d Update staticdata to 1683031 2020-03-10 19:07:42 +03:00
DarkPhoenix
8df07645da Show RR power for uncharged ancillary RRs 2020-03-06 00:50:18 +03:00
DarkPhoenix
5666fdd250 Bump version 2020-02-27 15:24:45 +03:00
DarkPhoenix
0b55ae40fc Update staticdata to 1675961 2020-02-27 15:09:54 +03:00
DarkPhoenix
3726e84697 Add dbuffcollections (just to have overview of changed fleet buffs when stuff changes) 2020-02-20 13:31:18 +03:00
DarkPhoenix
a0c4341102 Fix ECM range rigs affecting ECM bursts with penalty 2020-02-17 23:42:48 +03:00
Metallicow
cd6b1038e8 optimize all pngs 2020-02-13 14:58:27 -06:00
DarkPhoenix
10583fd506 Update fighter additions tab icon 2020-02-13 17:25:40 +03:00
DarkPhoenix
713694be56 Bump version 2020-02-13 17:00:58 +03:00
DarkPhoenix
f50293cf77 Revert to wxpython 4.0.6 once again (see issue 2136) 2020-02-13 16:57:23 +03:00
DarkPhoenix
7c88fa477f Bump version 2020-02-13 12:20:03 +03:00
DarkPhoenix
f8df540fad Use newer wxpython version once again 2020-02-13 12:19:37 +03:00
DarkPhoenix
61a01805cc Reset locale and remove resolution data from all the images (wxpython phoenix issue #1515) 2020-02-13 12:14:26 +03:00
Metallicow
a93915cf04 trim trailing whitespace 2020-02-12 16:14:12 -06:00
DarkPhoenix
0f74c97fbf Temporarily revert python version to 3.7 2020-02-12 18:31:44 +03:00
DarkPhoenix
29713b69dc Fix the hack 2020-02-12 18:25:17 +03:00
DarkPhoenix
cc3c2cb9c8 Add hack to build wx on windows 2020-02-12 18:22:31 +03:00
DarkPhoenix
b36a3959da Revert wxpython version to 4.0.6 2020-02-12 18:18:18 +03:00
3371 changed files with 30787 additions and 2871 deletions

View File

@@ -3,7 +3,7 @@ clone_depth: 400
environment:
matrix:
- PYTHON: "C:\\Python38-x64"
- PYTHON: "C:\\Python37-x64"
# Should be enabled only for build process debugging
# init:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
@@ -40,6 +40,8 @@ install:
# pip will build them from source using the MSVC compiler matching the
# target Python version and architecture
- ps: echo("Install pip requirements:")
# This one is needed to build wxpython 4.0.6 on windows
- cmd: "python -m pip install pathlib2"
- cmd: "python -m pip install -r requirements.txt"
- cmd: "python -m pip install PyInstaller==3.6"
@@ -63,7 +65,7 @@ build_script:
after_build:
- ps: "ls \"./\""
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*.*"
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*"
test_script:
# Ha... we're just building

View File

@@ -23,6 +23,7 @@ import functools
import itertools
import json
import os
import re
import sqlite3
import sys
@@ -458,6 +459,38 @@ def update_db():
if itemReplacements is not None:
item.replacements = ','.join('{}'.format(tid) for tid in sorted(itemReplacements))
def processImplantSets(eveTypesData):
print('composing implant sets')
# Includes only implants which can be considered part of sets, not all implants
implant_groups = (300, 1730)
specials = {'Genolution': ('Genolution Core Augmentation', r'CA-\d+')}
implantSets = {}
for row in eveTypesData:
if not row.get('published'):
continue
if row.get('groupID') not in implant_groups:
continue
typeName = row.get('typeName', '')
# Regular sets matching
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
if m:
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
# Special set matching
for setHandle, (setName, implantPattern) in specials.items():
pattern = '(?P<set>{}) (?P<implant>{})'.format(setName, implantPattern)
m = re.match(pattern, typeName)
if m:
implantSets.setdefault((None, setHandle), set()).add(row['typeID'])
break
data = []
for (gradeName, setName), implants in implantSets.items():
if len(implants) < 2:
continue
implants = ','.join('{}'.format(tid) for tid in sorted(implants))
row = {'setName': setName, 'gradeName': gradeName, 'implants': implants}
data.append(row)
_addRows(data, eos.gamedata.ImplantSet)
eveTypesData = processEveTypes()
eveGroupsData = processEveGroups()
processEveCategories()
@@ -476,6 +509,7 @@ def update_db():
eos.db.gamedata_session.flush()
processReqSkills(eveTypesData)
processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData)
processImplantSets(eveTypesData)
# Add schema version to prevent further updates
metadata_schema_version = eos.gamedata.MetaData()

View File

@@ -30,7 +30,8 @@ added_files = [
import_these = [
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
'pkg_resources.py2_warn' # issue 2156
]
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")

View File

@@ -29,7 +29,8 @@ added_files = [
import_these = [
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
'pkg_resources.py2_warn' # issue 2156
]
# Walk directories that do dynamic importing

View File

@@ -17,24 +17,37 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import re
import threading
from sqlalchemy import MetaData, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import MetaData, create_engine, event
from sqlalchemy.orm import sessionmaker, scoped_session
from . import migration
from eos import config
from logbook import Logger
pyfalog = Logger(__name__)
pyfalog.info("Initializing database")
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
class ReadOnlyException(Exception):
pass
def re_fn(expr, item):
try:
reg = re.compile(expr, re.IGNORECASE)
except (SystemExit, KeyboardInterrupt):
raise
except:
return False
return reg.search(item) is not None
pyfalog.debug('Initializing gamedata')
gamedata_connectionstring = config.gamedata_connectionstring
if callable(gamedata_connectionstring):
@@ -42,9 +55,23 @@ if callable(gamedata_connectionstring):
else:
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
@event.listens_for(gamedata_engine, 'connect')
def create_functions(dbapi_connection, connection_record):
dbapi_connection.create_function('regexp', 2, re_fn)
gamedata_meta = MetaData()
gamedata_meta.bind = gamedata_engine
gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)()
GamedataSession = scoped_session(sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False))
gamedata_session = GamedataSession()
gamedata_sessions = {}
def get_gamedata_session():
thread_id = threading.get_ident()
if thread_id not in gamedata_sessions:
gamedata_sessions[thread_id] = GamedataSession()
return gamedata_sessions[thread_id]
pyfalog.debug('Getting gamedata version')
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
@@ -84,7 +111,7 @@ sd_lock = threading.RLock()
pyfalog.debug('Importing gamedata DB scheme')
# Import all the definitions for all our database stuff
# noinspection PyPep8
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes, implantSet
pyfalog.debug('Importing saveddata DB scheme')
# noinspection PyPep8
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \

View File

@@ -1,2 +1,2 @@
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
"item", "marketGroup", "metaGroup", "unit", "alphaClones"]
"item", "marketGroup", "metaGroup", "unit", "alphaClones", "implantSet"]

View File

@@ -0,0 +1,33 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, String, Integer, Table
from sqlalchemy.orm import mapper, synonym
from eos.db import gamedata_meta
from eos.gamedata import ImplantSet
implant_set_table = Table("implantsets", gamedata_meta,
Column("setID", Integer, primary_key=True),
Column("setName", String),
Column("gradeName", String),
Column("implants", String))
mapper(ImplantSet, implant_set_table,
properties={"ID": synonym("setID")})

View File

@@ -22,11 +22,11 @@ from sqlalchemy.orm import aliased, exc, join
from sqlalchemy.sql import and_, or_, select
import eos.config
from eos.db import gamedata_session
from eos.db import get_gamedata_session
from eos.db.gamedata.item import items_table
from eos.db.gamedata.group import groups_table
from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup, ImplantSet
cache = {}
configVal = getattr(eos.config, "gamedataCache", None)
@@ -64,7 +64,7 @@ else:
return deco
def sqlizeString(line):
def sqlizeNormalString(line):
# Escape backslashes first, as they will be as escape symbol in queries
# Then escape percent and underscore signs
# Finally, replace generic wildcards with sql-style wildcards
@@ -79,29 +79,39 @@ itemNameMap = {}
def getItem(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
item = gamedata_session.query(Item).get(lookfor)
item = get_gamedata_session().query(Item).get(lookfor)
else:
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
elif isinstance(lookfor, str):
if lookfor in itemNameMap:
id = itemNameMap[lookfor]
if eager is None:
item = gamedata_session.query(Item).get(id)
item = get_gamedata_session().query(Item).get(id)
else:
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
else:
# Item names are unique, so we can use first() instead of one()
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
if item is not None:
itemNameMap[lookfor] = item.ID
else:
raise TypeError("Need integer or string as argument")
return item
@cachedQuery(1, "itemIDs")
def getItems(itemIDs, eager=None):
if not isinstance(itemIDs, (tuple, list, set)) or not all(isinstance(t, int) for t in itemIDs):
raise TypeError("Need iterable of integers as argument")
if eager is None:
items = get_gamedata_session().query(Item).filter(Item.ID.in_(itemIDs)).all()
else:
items = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID.in_(itemIDs)).all()
return items
def getMutaplasmid(lookfor, eager=None):
if isinstance(lookfor, int):
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
item = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
else:
raise TypeError("Need integer as argument")
return item
@@ -109,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
# A lot of this is described in more detail in #1597
item = gamedata_session.query(Item).get(lookfor)
item = get_gamedata_session().query(Item).get(lookfor)
base = getItem(baseItemID)
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
@@ -125,7 +135,7 @@ def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
# we want to generate a completely new object to work with
gamedata_session.expunge(item)
get_gamedata_session().expunge(item)
return item
@@ -148,7 +158,7 @@ def getItems(lookfor, eager=None):
if len(toGet) > 0:
# Get items that aren't currently cached, and store them in the cache
items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all()
items = get_gamedata_session().query(Item).filter(Item.ID.in_(toGet)).all()
for item in items:
cache[(item.ID, None)] = item
results += items
@@ -162,9 +172,9 @@ def getItems(lookfor, eager=None):
def getAlphaClone(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
item = gamedata_session.query(AlphaClone).get(lookfor)
item = get_gamedata_session().query(AlphaClone).get(lookfor)
else:
item = gamedata_session.query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
item = get_gamedata_session().query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
else:
raise TypeError("Need integer as argument")
return item
@@ -172,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
def getAlphaCloneList(eager=None):
eager = processEager(eager)
clones = gamedata_session.query(AlphaClone).options(*eager).all()
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
return clones
@@ -183,19 +193,19 @@ groupNameMap = {}
def getGroup(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
group = gamedata_session.query(Group).get(lookfor)
group = get_gamedata_session().query(Group).get(lookfor)
else:
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
elif isinstance(lookfor, str):
if lookfor in groupNameMap:
id = groupNameMap[lookfor]
if eager is None:
group = gamedata_session.query(Group).get(id)
group = get_gamedata_session().query(Group).get(id)
else:
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
else:
# Group names are unique, so we can use first() instead of one()
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
if group is not None:
groupNameMap[lookfor] = group.ID
else:
@@ -210,21 +220,21 @@ categoryNameMap = {}
def getCategory(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
category = gamedata_session.query(Category).get(lookfor)
category = get_gamedata_session().query(Category).get(lookfor)
else:
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
Category.ID == lookfor).first()
elif isinstance(lookfor, str):
if lookfor in categoryNameMap:
id = categoryNameMap[lookfor]
if eager is None:
category = gamedata_session.query(Category).get(id)
category = get_gamedata_session().query(Category).get(id)
else:
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
Category.ID == id).first()
else:
# Category names are unique, so we can use first() instead of one()
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
Category.name == lookfor).first()
if category is not None:
categoryNameMap[lookfor] = category.ID
@@ -240,21 +250,21 @@ metaGroupNameMap = {}
def getMetaGroup(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
else:
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
MetaGroup.ID == lookfor).first()
elif isinstance(lookfor, str):
if lookfor in metaGroupNameMap:
id = metaGroupNameMap[lookfor]
if eager is None:
metaGroup = gamedata_session.query(MetaGroup).get(id)
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
else:
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
MetaGroup.ID == id).first()
else:
# MetaGroup names are unique, so we can use first() instead of one()
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
MetaGroup.name == lookfor).first()
if metaGroup is not None:
metaGroupNameMap[lookfor] = metaGroup.ID
@@ -264,16 +274,16 @@ def getMetaGroup(lookfor, eager=None):
def getMetaGroups():
return gamedata_session.query(MetaGroup).all()
return get_gamedata_session().query(MetaGroup).all()
@cachedQuery(1, "lookfor")
def getMarketGroup(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
else:
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
MarketGroup.ID == lookfor).first()
else:
raise TypeError("Need integer as argument")
@@ -285,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
addedIds = set(rootNodeIds)
while addedIds:
allIds.update(addedIds)
addedIds = {mg.ID for mg in gamedata_session.query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
addedIds = {mg.ID for mg in get_gamedata_session().query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
return allIds
@@ -299,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
raise TypeError("Need integer or string as argument")
filter = processWhere(filter, where)
return gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
return get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
filter).all()
@@ -314,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
if not hasattr(join, "__iter__"):
join = (join,)
items = gamedata_session.query(Item).options(*processEager(eager)).join(*join)
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
for token in nameLike.split(' '):
token_safe = "%{0}%".format(sqlizeString(token))
token_safe = "%{0}%".format(sqlizeNormalString(token))
if where is not None:
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
else:
@@ -325,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
return items
@cachedQuery(3, "tokens", "where", "join")
def searchItemsRegex(tokens, where=None, join=None, eager=None):
if not isinstance(tokens, (tuple, list)) or not all(isinstance(t, str) for t in tokens):
raise TypeError("Need tuple or list of strings as argument")
if join is None:
join = tuple()
if not hasattr(join, "__iter__"):
join = (join,)
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
for token in tokens:
if where is not None:
items = items.filter(and_(Item.name.op('regexp')(token), where))
else:
items = items.filter(Item.name.op('regexp')(token))
items = items.limit(100).all()
return items
@cachedQuery(3, "where", "nameLike", "join")
def searchSkills(nameLike, where=None, eager=None):
if not isinstance(nameLike, str):
raise TypeError("Need string as argument")
items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category)
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category)
for token in nameLike.split(' '):
token_safe = "%{0}%".format(sqlizeString(token))
token_safe = "%{0}%".format(sqlizeNormalString(token))
if where is not None:
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
else:
@@ -352,7 +383,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
filter = processWhere(itemfilter, where)
vars = gamedata_session.query(Item).options(*processEager(eager)).filter(filter).all()
vars = get_gamedata_session().query(Item).options(*processEager(eager)).filter(filter).all()
if vars:
return vars
@@ -360,7 +391,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
filter = processWhere(itemfilter, where)
joinon = items_table.c.groupID == groups_table.c.groupID
vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
vars = get_gamedata_session().query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
filter).all()
return vars
@@ -375,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
else:
raise TypeError("Need integer or string as argument")
try:
result = gamedata_session.query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
result = get_gamedata_session().query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
except exc.NoResultFound:
result = None
return result
@@ -384,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
@cachedQuery(1, "field")
def getMetaData(field):
if isinstance(field, str):
data = gamedata_session.query(MetaData).get(field)
data = get_gamedata_session().query(MetaData).get(field)
else:
raise TypeError("Need string as argument")
return data
@@ -403,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
from_obj=[join(Attribute, Item)])
result = gamedata_session.execute(q).fetchall()
result = get_gamedata_session().execute(q).fetchall()
return result
def getAbyssalTypes():
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
return set([r.resultingTypeID for r in get_gamedata_session().query(DynamicItem.resultingTypeID).distinct()])
@cachedQuery(1, "itemID")
@@ -416,11 +447,17 @@ def getDynamicItem(itemID, eager=None):
try:
if isinstance(itemID, int):
if eager is None:
result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
result = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == itemID).one()
else:
result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
result = get_gamedata_session().query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
else:
raise TypeError("Need integer as argument")
except exc.NoResultFound:
result = None
return result
@cachedQuery(1, "lookfor")
def getAllImplantSets():
implantSets = get_gamedata_session().query(ImplantSet).all()
return implantSets

View File

@@ -6,16 +6,16 @@ Allow use of floats in damage pattern values
tmpTable = """
CREATE TABLE "damagePatternsTemp" (
"ID" INTEGER NOT NULL,
"name" VARCHAR,
"emAmount" FLOAT,
"thermalAmount" FLOAT,
"kineticAmount" FLOAT,
"explosiveAmount" FLOAT,
"ownerID" INTEGER,
"created" DATETIME,
"modified" DATETIME,
PRIMARY KEY ("ID"),
"ID" INTEGER NOT NULL,
"name" VARCHAR,
"emAmount" FLOAT,
"thermalAmount" FLOAT,
"kineticAmount" FLOAT,
"explosiveAmount" FLOAT,
"ownerID" INTEGER,
"created" DATETIME,
"modified" DATETIME,
PRIMARY KEY ("ID"),
FOREIGN KEY("ownerID") REFERENCES users ("ID")
)
"""

View File

@@ -0,0 +1,44 @@
"""
Migration 37
- Capacitor Booster tiericide
"""
CONVERSIONS = {
4959: ( # 'Seed' Micro Capacitor Booster I
4957, # Micro Brief Capacitor Overcharge I
4961, # Micro Tapered Capacitor Infusion I
4955, # Micro F-RX Prototype Capacitor Boost
3556, # Micro Capacitor Booster I
3558, # Micro Capacitor Booster II
15774, # Ammatar Navy Micro Capacitor Booster
14180, # Dark Blood Micro Capacitor Booster
14182, # True Sansha Micro Capacitor Booster
15782, # Imperial Navy Micro Capacitor Booster
),
5011: ( # Small F-RX Compact Capacitor Booster
5009, # Small Brief Capacitor Overcharge I
5013, # Small Tapered Capacitor Infusion I
5007, # Small F-RX Prototype Capacitor Boost
),
4833: ( # Medium F-RX Compact Capacitor Booster
4831, # Medium Brief Capacitor Overcharge I
4835, # Medium Tapered Capacitor Infusion I
4829, # Medium F-RX Prototype Capacitor Boost
),
5051: ( # Heavy F-RX Compact Capacitor Booster
5049, # Heavy Brief Capacitor Overcharge I
5053, # Heavy Tapered Capacitor Infusion I
5047, # Heavy F-RX Prototype Capacitor Boost
)
}
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))

View File

@@ -218,7 +218,7 @@ class Effect48(BaseEffect):
powerBooster
Used by:
Modules from group: Capacitor Booster (59 of 59)
Modules from group: Capacitor Booster (41 of 41)
"""
type = 'active'
@@ -989,7 +989,7 @@ class Effect290(BaseEffect):
sharpshooterRangeSkillBonusPostPercentMaxRangeLocationShipModulesRequiringGunnery
Used by:
Implants named like: Frentix Booster (4 of 4)
Implants named like: Frentix Booster (9 of 9)
Implants named like: Zainou 'Deadeye' Sharpshooter ST (6 of 6)
Skill: Sharpshooter
"""
@@ -1008,7 +1008,7 @@ class Effect298(BaseEffect):
surgicalStrikeFalloffBonusPostPercentFalloffLocationShipModulesRequiringGunnery
Used by:
Implants named like: Sooth Sayer Booster (4 of 4)
Implants named like: Sooth Sayer Booster (9 of 9)
Implants named like: Zainou 'Deadeye' Trajectory Analysis TA (6 of 6)
Skill: Trajectory Analysis
"""
@@ -1742,7 +1742,7 @@ class Effect596(BaseEffect):
ammoInfluenceRange
Used by:
Items from category: Charge (590 of 954)
Items from category: Charge (590 of 955)
"""
type = 'passive'
@@ -1757,7 +1757,7 @@ class Effect598(BaseEffect):
ammoSpeedMultiplier
Used by:
Charges from group: Festival Charges (28 of 28)
Charges from group: Festival Charges (29 of 29)
Charges from group: Interdiction Probe (2 of 2)
Charges from group: Structure Festival Charges (2 of 2)
Special Edition Assetss from group: Festival Charges Expired (4 of 4)
@@ -2328,7 +2328,7 @@ class Effect804(BaseEffect):
ammoInfluenceCapNeed
Used by:
Items from category: Charge (496 of 954)
Items from category: Charge (496 of 955)
"""
type = 'passive'
@@ -3694,7 +3694,7 @@ class Effect1185(BaseEffect):
structureStealthEmitterArraySigDecrease
Used by:
Implants named like: X Instinct Booster (4 of 4)
Implants named like: X Instinct Booster (9 of 9)
Implants named like: grade Halo (15 of 18)
"""
@@ -5977,6 +5977,7 @@ class Effect2019(BaseEffect):
repairDroneShieldBonusBonus
Used by:
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
Modules named like: Drone Repair Augmentor (8 of 8)
Skill: Repair Drone Operation
"""
@@ -5986,7 +5987,7 @@ class Effect2019(BaseEffect):
@staticmethod
def handler(fit, container, context, projectionRange, **kwargs):
level = container.level if 'skill' in context else 1
penalized = False if 'skill' in context else True
penalized = False if 'skill' in context or 'implant' in context else True
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == 'Logistic Drone',
'shieldBonus', container.getModifiedItemAttr('damageHP') * level,
stackingPenalties=penalized, **kwargs)
@@ -5997,6 +5998,7 @@ class Effect2020(BaseEffect):
repairDroneArmorDamageAmountBonus
Used by:
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
Modules named like: Drone Repair Augmentor (8 of 8)
Skill: Repair Drone Operation
"""
@@ -6006,7 +6008,7 @@ class Effect2020(BaseEffect):
@staticmethod
def handler(fit, container, context, projectionRange, **kwargs):
level = container.level if 'skill' in context else 1
penalized = False if 'skill' in context else True
penalized = False if 'skill' in context or 'implant' in context else True
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == 'Logistic Drone',
'armorDamageAmount', container.getModifiedItemAttr('damageHP') * level,
stackingPenalties=penalized, **kwargs)
@@ -6964,7 +6966,7 @@ class Effect2432(BaseEffect):
Used by:
Implants named like: Inherent Implants 'Squire' Capacitor Management EM (6 of 6)
Implants named like: Mindflood Booster (4 of 4)
Implants named like: Mindflood Booster (9 of 9)
Modules named like: Semiconductor Memory Cell (8 of 8)
Implant: Antipharmakon Aeolis
Implant: Genolution Core Augmentation CA-1
@@ -7148,8 +7150,7 @@ class Effect2491(BaseEffect):
def handler(fit, container, context, projectionRange, **kwargs):
level = container.level if 'skill' in context else 1
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Burst Jammer',
'ecmBurstRange', container.getModifiedItemAttr('rangeSkillBonus') * level,
stackingPenalties=False if 'skill' in context else True, **kwargs)
'ecmBurstRange', container.getModifiedItemAttr('rangeSkillBonus') * level, **kwargs)
class Effect2492(BaseEffect):
@@ -7821,7 +7822,9 @@ class Effect2735(BaseEffect):
boosterArmorHpPenalty
Used by:
Implants named like: Booster (12 of 35)
Implants named like: Improved Booster (4 of 8)
Implants named like: Standard Booster (4 of 8)
Implants named like: Strong Booster (4 of 8)
"""
attr = 'boosterArmorHPPenalty'
@@ -7838,9 +7841,15 @@ class Effect2736(BaseEffect):
boosterArmorRepairAmountPenalty
Used by:
Implants named like: Drop Booster (3 of 4)
Implants named like: Mindflood Booster (3 of 4)
Implants named like: Sooth Sayer Booster (3 of 4)
Implant: Improved Drop Booster
Implant: Improved Mindflood Booster
Implant: Improved Sooth Sayer Booster
Implant: Standard Drop Booster
Implant: Standard Mindflood Booster
Implant: Standard Sooth Sayer Booster
Implant: Strong Drop Booster
Implant: Strong Mindflood Booster
Implant: Strong Sooth Sayer Booster
"""
attr = 'boosterArmorRepairAmountPenalty'
@@ -7858,7 +7867,9 @@ class Effect2737(BaseEffect):
boosterShieldCapacityPenalty
Used by:
Implants from group: Booster (12 of 67)
Implants named like: Improved Booster (4 of 8)
Implants named like: Standard Booster (4 of 8)
Implants named like: Strong Booster (4 of 8)
"""
attr = 'boosterShieldCapacityPenalty'
@@ -7875,9 +7886,15 @@ class Effect2739(BaseEffect):
boosterTurretOptimalRangePenalty
Used by:
Implants named like: Blue Pill Booster (3 of 5)
Implants named like: Mindflood Booster (3 of 4)
Implants named like: Sooth Sayer Booster (3 of 4)
Implant: Improved Blue Pill Booster
Implant: Improved Mindflood Booster
Implant: Improved Sooth Sayer Booster
Implant: Standard Blue Pill Booster
Implant: Standard Mindflood Booster
Implant: Standard Sooth Sayer Booster
Implant: Strong Blue Pill Booster
Implant: Strong Mindflood Booster
Implant: Strong Sooth Sayer Booster
"""
attr = 'boosterTurretOptimalRangePenalty'
@@ -7895,8 +7912,12 @@ class Effect2741(BaseEffect):
boosterTurretFalloffPenalty
Used by:
Implants named like: Drop Booster (3 of 4)
Implants named like: X Instinct Booster (3 of 4)
Implant: Improved Drop Booster
Implant: Improved X-Instinct Booster
Implant: Standard Drop Booster
Implant: Standard X-Instinct Booster
Implant: Strong Drop Booster
Implant: Strong X-Instinct Booster
"""
attr = 'boosterTurretFalloffPenalty'
@@ -7914,8 +7935,12 @@ class Effect2745(BaseEffect):
boosterCapacitorCapacityPenalty
Used by:
Implants named like: Blue Pill Booster (3 of 5)
Implants named like: Exile Booster (3 of 4)
Implant: Improved Blue Pill Booster
Implant: Improved Exile Booster
Implant: Standard Blue Pill Booster
Implant: Standard Exile Booster
Implant: Strong Blue Pill Booster
Implant: Strong Exile Booster
"""
attr = 'boosterCapacitorCapacityPenalty'
@@ -7932,8 +7957,10 @@ class Effect2746(BaseEffect):
boosterMaxVelocityPenalty
Used by:
Implants named like: Crash Booster (3 of 4)
Items from market group: Implants & Boosters > Booster > Booster Slot 02 (9 of 13)
Implant: Improved Crash Booster
Implant: Standard Crash Booster
Implant: Strong Crash Booster
"""
attr = 'boosterMaxVelocityPenalty'
@@ -7950,8 +7977,12 @@ class Effect2747(BaseEffect):
boosterTurretTrackingPenalty
Used by:
Implants named like: Exile Booster (3 of 4)
Implants named like: Frentix Booster (3 of 4)
Implant: Improved Exile Booster
Implant: Improved Frentix Booster
Implant: Standard Exile Booster
Implant: Standard Frentix Booster
Implant: Strong Exile Booster
Implant: Strong Frentix Booster
"""
attr = 'boosterTurretTrackingPenalty'
@@ -7969,8 +8000,12 @@ class Effect2748(BaseEffect):
boosterMissileVelocityPenalty
Used by:
Implants named like: Crash Booster (3 of 4)
Implants named like: X Instinct Booster (3 of 4)
Implant: Improved Crash Booster
Implant: Improved X-Instinct Booster
Implant: Standard Crash Booster
Implant: Standard X-Instinct Booster
Implant: Strong Crash Booster
Implant: Strong X-Instinct Booster
"""
attr = 'boosterMissileVelocityPenalty'
@@ -7988,7 +8023,9 @@ class Effect2749(BaseEffect):
boosterMissileExplosionVelocityPenalty
Used by:
Implants named like: Blue Pill Booster (3 of 5)
Implant: Improved Blue Pill Booster
Implant: Standard Blue Pill Booster
Implant: Strong Blue Pill Booster
"""
attr = 'boosterAOEVelocityPenalty'
@@ -8143,8 +8180,12 @@ class Effect2791(BaseEffect):
boosterMissileExplosionCloudPenaltyFixed
Used by:
Implants named like: Exile Booster (3 of 4)
Implants named like: Mindflood Booster (3 of 4)
Implant: Improved Exile Booster
Implant: Improved Mindflood Booster
Implant: Standard Exile Booster
Implant: Standard Mindflood Booster
Implant: Strong Exile Booster
Implant: Strong Mindflood Booster
"""
attr = 'boosterMissileAOECloudPenalty'
@@ -8436,7 +8477,7 @@ class Effect2847(BaseEffect):
trackingSpeedBonusPassiveRequiringGunneryTrackingSpeedBonus
Used by:
Implants named like: Drop Booster (4 of 4)
Implants named like: Drop Booster (9 of 9)
Implants named like: Eifyr and Co. 'Gunslinger' Motion Prediction MR (6 of 6)
Implant: Antipharmakon Iokira
Implant: Ogdin's Eye Coordination Enhancer
@@ -9189,7 +9230,7 @@ class Effect3002(BaseEffect):
Used by:
Modules from group: Ancillary Remote Shield Booster (4 of 4)
Modules from group: Capacitor Booster (59 of 59)
Modules from group: Capacitor Booster (41 of 41)
Modules from group: Energy Neutralizer (54 of 54)
Modules from group: Energy Nosferatu (54 of 54)
Modules from group: Hull Repair Unit (25 of 25)
@@ -9303,9 +9344,9 @@ class Effect3029(BaseEffect):
overloadSelfEmHardeningBonus
Used by:
Modules named like: Anti EM Shield Hardener (21 of 21)
Variations of module: Anti-EM Shield Hardener I (20 of 20)
Variations of module: Armor EM Hardener I (39 of 39)
Variations of module: EM Ward Field I (19 of 19)
Module: Civilian EM Ward Field
"""
type = 'overheat'
@@ -9320,9 +9361,9 @@ class Effect3030(BaseEffect):
overloadSelfThermalHardeningBonus
Used by:
Variations of module: Anti-Thermal Shield Hardener I (20 of 20)
Variations of module: Armor Thermal Hardener I (39 of 39)
Variations of module: Thermal Dissipation Field I (19 of 19)
Module: Civilian Thermal Dissipation Field
Module: Civilian Anti-Thermal Shield Hardener
"""
type = 'overheat'
@@ -9337,9 +9378,9 @@ class Effect3031(BaseEffect):
overloadSelfExplosiveHardeningBonus
Used by:
Variations of module: Anti-Explosive Shield Hardener I (20 of 20)
Variations of module: Armor Explosive Hardener I (39 of 39)
Variations of module: Explosive Deflection Field I (19 of 19)
Module: Civilian Explosive Deflection Field
Module: Civilian Anti-Explosive Shield Hardener
"""
type = 'overheat'
@@ -9354,9 +9395,9 @@ class Effect3032(BaseEffect):
overloadSelfKineticHardeningBonus
Used by:
Modules named like: Anti Kinetic Shield Hardener (21 of 21)
Variations of module: Anti-Kinetic Shield Hardener I (20 of 20)
Variations of module: Armor Kinetic Hardener I (39 of 39)
Variations of module: Kinetic Deflection Field I (19 of 19)
Module: Civilian Kinetic Deflection Field
"""
type = 'overheat'
@@ -9372,7 +9413,7 @@ class Effect3035(BaseEffect):
Used by:
Modules named like: Capital Flex Hardener (9 of 9)
Variations of module: Adaptive Invulnerability Field I (17 of 17)
Variations of module: Adaptive Invulnerability Shield Hardener I (18 of 18)
"""
type = 'overheat'
@@ -16714,7 +16755,7 @@ class Effect4951(BaseEffect):
Used by:
Implants named like: Agency 'Hardshell' TB Dose (4 of 4)
Implants named like: Blue Pill Booster (5 of 5)
Implants named like: Blue Pill Booster (10 of 10)
Implant: Antipharmakon Thureo
"""
@@ -16768,9 +16809,15 @@ class Effect4970(BaseEffect):
boosterShieldBoostAmountPenaltyShieldSkills
Used by:
Implants named like: Crash Booster (3 of 4)
Implants named like: Frentix Booster (3 of 4)
Implants named like: Mindflood Booster (3 of 4)
Implant: Improved Crash Booster
Implant: Improved Frentix Booster
Implant: Improved Mindflood Booster
Implant: Standard Crash Booster
Implant: Standard Frentix Booster
Implant: Standard Mindflood Booster
Implant: Strong Crash Booster
Implant: Strong Frentix Booster
Implant: Strong Mindflood Booster
"""
attr = 'boosterShieldBoostAmountPenalty'
@@ -16872,7 +16919,7 @@ class Effect4989(BaseEffect):
missileSkillAoeCloudSizeBonusAllIncludingCapitals
Used by:
Implants named like: Crash Booster (4 of 4)
Implants named like: Crash Booster (9 of 9)
"""
type = 'passive'
@@ -18631,7 +18678,7 @@ class Effect5230(BaseEffect):
Used by:
Modules from group: Flex Shield Hardener (5 of 5)
Modules from group: Shield Hardener (97 of 97)
Modules from group: Shield Hardener (102 of 102)
"""
type = 'active'
@@ -19797,7 +19844,7 @@ class Effect5364(BaseEffect):
Used by:
Implants named like: Agency 'Hardshell' TB Dose (4 of 4)
Implants named like: Exile Booster (4 of 4)
Implants named like: Exile Booster (9 of 9)
Implant: Antipharmakon Kosybo
"""
@@ -22035,6 +22082,7 @@ class Effect5769(BaseEffect):
repairDroneHullBonusBonus
Used by:
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
Modules named like: Drone Repair Augmentor (8 of 8)
Skill: Repair Drone Operation
"""
@@ -29064,10 +29112,6 @@ class Effect6582(BaseEffect):
mod.item.requiresSkill('Capital Precursor Weapon'),
'damageMultiplier', src.getModifiedItemAttr('siegeTurretDamageBonus'), **kwargs)
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill('Motion Prediction'),
'damageMultiplier', src.getModifiedItemAttr('siegeHAWTurretDamageBonus'),
stackingPenalties=True, **kwargs)
# Missiles
for type in ('kinetic', 'thermal', 'explosive', 'em'):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('XL Torpedoes') or
@@ -29079,7 +29123,7 @@ class Effect6582(BaseEffect):
mod.item.requiresSkill('XL Cruise Missiles'),
'speed', src.getModifiedItemAttr('siegeLauncherROFBonus'), **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Target Navigation Prediction'),
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Rapid Torpedo',
'speed', src.getModifiedItemAttr('siegeHAWMissileROFBonus'),
stackingPenalties=True, penaltyGroup='postPercent', **kwargs)
@@ -30461,6 +30505,7 @@ class Effect6664(BaseEffect):
skillBonusDroneSharpshooting
Used by:
Implants named like: Black Market 'Valdimar' Drone Sharpshooting DS (3 of 3)
Skill: Drone Sharpshooting
"""
@@ -30469,16 +30514,18 @@ class Effect6664(BaseEffect):
@staticmethod
def handler(fit, src, context, projectionRange, **kwargs):
lvl = src.level if 'skill' in context else 1
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill('Drones'), 'maxRange',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityMissilesRange',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'),
'fighterAbilityAttackTurretRangeOptimal',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'),
'fighterAbilityAttackMissileRangeOptimal',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.drones.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Drones'), 'maxRange',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityMissilesRange',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityAttackTurretRangeOptimal',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
fit.fighters.filteredItemBoost(
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityAttackMissileRangeOptimal',
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
class Effect6665(BaseEffect):
@@ -30486,6 +30533,7 @@ class Effect6665(BaseEffect):
skillBonusDroneDurability
Used by:
Implants named like: Black Market 'Valdimar' Drone Durability DD (3 of 3)
Skill: Drone Durability
"""
@@ -30509,6 +30557,7 @@ class Effect6667(BaseEffect):
skillBonusDroneNavigation
Used by:
Implants named like: Black Market 'Valdimar' Drone Navigation DN (3 of 3)
Skill: Drone Navigation
"""
@@ -35664,7 +35713,7 @@ class Effect7177(BaseEffect):
skillBonusDroneDurabilityNotFighters
Used by:
Implants from group: Cyber Drones (4 of 4)
Implants named like: Drone Tuner (4 of 4)
"""
type = 'passive'
@@ -36258,3 +36307,109 @@ class Effect8018(BaseEffect):
def handler(fit, implant, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Shield Emission Systems') or mod.item.requiresSkill('Remote Armor Repair Systems'),
'duration', implant.getModifiedItemAttr('remoteRepDurationBonus'), **kwargs)
class Effect8021(BaseEffect):
"""
hydraSetBonus
Used by:
Implants named like: grade Hydra (18 of 18)
"""
runTime = 'early'
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
for attr in ('hydraDroneTrackingBonus', 'hydraDroneRangeBonus', 'hydraMissileFlightTimeBonus', 'hydraMissileExplosionVelocityBonus'):
fit.appliedImplants.filteredItemMultiply(
lambda implant: implant.item.requiresSkill('Cybernetics'),
attr, implant.getModifiedItemAttr('implantSetHydra'), **kwargs)
class Effect8023(BaseEffect):
"""
hydraDroneTrackingEffect
Used by:
Implants named like: grade Hydra (15 of 18)
"""
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
fit.drones.filteredItemBoost(
lambda drone: drone.item.requiresSkill('Drones'),
'trackingSpeed', implant.getModifiedItemAttr('hydraDroneTrackingBonus'), **kwargs)
class Effect8024(BaseEffect):
"""
hydraDroneRangeEffect
Used by:
Implants named like: grade Hydra (15 of 18)
"""
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
for attr in ('maxRange', 'falloff'):
fit.drones.filteredItemBoost(
lambda drone: drone.item.requiresSkill('Drones'),
attr, implant.getModifiedItemAttr('hydraDroneRangeBonus'), **kwargs)
class Effect8025(BaseEffect):
"""
hydraMissileFlightTimeEffect
Used by:
Implants named like: grade Hydra (15 of 18)
"""
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
'explosionDelay', implant.getModifiedItemAttr('hydraMissileFlightTimeBonus'), **kwargs)
class Effect8026(BaseEffect):
"""
hydraMissileExplosionVelocityEffect
Used by:
Implants named like: grade Hydra (15 of 18)
"""
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
'aoeVelocity', implant.getModifiedItemAttr('hydraMissileExplosionVelocityBonus'), **kwargs)
class Effect8029(BaseEffect):
"""
roleBonus7CapBoosterGroupRestriction
Used by:
Ships from group: Force Auxiliary (6 of 6)
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
for attr in ('maxGroupOnline', 'maxGroupFitted'):
fit.modules.filteredItemForce(
lambda mod: mod.item.group.name == 'Capacitor Booster',
attr, ship.getModifiedItemAttr('shipBonusRole7'), **kwargs)

View File

@@ -723,5 +723,15 @@ class Unit(EqBase):
return value
class Traits(EqBase):
pass
class ImplantSet(EqBase):
@property
def fullName(self):
if not self.gradeName:
return self.setName
return '{} {}'.format(self.gradeName, self.setName)

View File

@@ -576,11 +576,15 @@ class Fit:
if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
self.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
"Shield Emission Systems"), "capacitorNeed", value)
lambda mod: mod.item.requiresSkill("Shield Operation") or
mod.item.requiresSkill("Shield Emission Systems") or
mod.item.requiresSkill("Capital Shield Emission Systems"),
"capacitorNeed", value)
self.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
"Shield Emission Systems"), "duration", value)
lambda mod: mod.item.requiresSkill("Shield Operation") or
mod.item.requiresSkill("Shield Emission Systems") or
mod.item.requiresSkill("Capital Shield Emission Systems"),
"duration", value)
if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP
self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True)
@@ -590,12 +594,16 @@ class Fit:
self.ship.boostItemAttr("armor%sDamageResonance" % damageType, value, stackingPenalties=True)
if warfareBuffID == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
mod.item.requiresSkill("Repair Systems"),
"capacitorNeed", value)
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
mod.item.requiresSkill("Repair Systems"),
"duration", value)
self.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
mod.item.requiresSkill("Repair Systems") or
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
"capacitorNeed", value)
self.modules.filteredItemBoost(
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
mod.item.requiresSkill("Repair Systems") or
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
"duration", value)
if warfareBuffID == 15: # Armor Burst: Armor Reinforcement: Armor HP
self.ship.boostItemAttr("armorHP", value, stackingPenalties=True)

View File

@@ -696,7 +696,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# Check this only if we're told to do so
if hardpointLimit:
if fit.getHardpointsFree(self.hardpoint) < 1:
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
return False
return True

View File

@@ -26,11 +26,12 @@ VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle'
class YDef:
def __init__(self, handle, unit, label, selectorLabel=None):
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
self.handle = handle
self.unit = unit
self.label = label
self._selectorLabel = selectorLabel
self.hidden = hidden
@property
def selectorLabel(self):
@@ -53,12 +54,13 @@ class YDef:
class XDef:
def __init__(self, handle, unit, label, mainInput, selectorLabel=None):
def __init__(self, handle, unit, label, mainInput, selectorLabel=None, hidden=False):
self.handle = handle
self.unit = unit
self.label = label
self.mainInput = mainInput
self._selectorLabel = selectorLabel
self.hidden = hidden
@property
def selectorLabel(self):

View File

@@ -31,8 +31,8 @@ from .getter import (
class FitDamageStatsGraph(FitGraph):
def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._timeCache = TimeCache()
self._projectedCache = ProjectedDataCache()

View File

@@ -23,24 +23,6 @@ import math
from graphs.data.base import SmoothPointGetter
class Time2SpeedGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxSpeed': src.getMaxVelocity(),
'mass': src.item.ship.getModifiedItemAttr('mass'),
'agility': src.item.ship.getModifiedItemAttr('agility')}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
maxSpeed = commonData['maxSpeed']
mass = commonData['mass']
agility = commonData['agility']
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
return speed
class Time2DistanceGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, src, tgt):
@@ -60,3 +42,62 @@ class Time2DistanceGetter(SmoothPointGetter):
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
distance = distance_t - distance_0
return distance
class Time2SpeedGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxSpeed': src.getMaxVelocity(),
'mass': src.item.ship.getModifiedItemAttr('mass'),
'agility': src.item.ship.getModifiedItemAttr('agility')}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
maxSpeed = commonData['maxSpeed']
mass = commonData['mass']
agility = commonData['agility']
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
return speed
class Time2MomentumGetter(Time2SpeedGetter):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
mass = commonData['mass']
speed = Time2SpeedGetter._calculatePoint(
self, x=x, miscParams=miscParams,
src=src, tgt=tgt, commonData=commonData)
momentum = speed * mass
return momentum
class Time2BumpSpeedGetter(Time2SpeedGetter):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
bumperMass = commonData['mass'] / 10 ** 6
bumperSpeed = Time2SpeedGetter._calculatePoint(
self, x=x, miscParams=miscParams,
src=src, tgt=tgt, commonData=commonData)
tgtMass = miscParams['tgtMass'] / 10 ** 6
tgtSpeed = (2 * bumperSpeed * bumperMass) / (bumperMass + tgtMass)
return tgtSpeed
class Time2BumpDistanceGetter(Time2BumpSpeedGetter):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
tgtMass = miscParams['tgtMass'] / 10 ** 6
tgtInertia = miscParams['tgtInertia']
tgtSpeed = Time2BumpSpeedGetter._calculatePoint(
self, x=x, miscParams=miscParams,
src=src, tgt=tgt, commonData=commonData)
tgtDistance = tgtSpeed * tgtMass * tgtInertia
return tgtDistance

View File

@@ -19,7 +19,7 @@
from graphs.data.base import FitGraph, XDef, YDef, Input
from .getter import Time2SpeedGetter, Time2DistanceGetter
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
class FitMobilityGraph(FitGraph):
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
yDefs = [
YDef(handle='speed', unit='m/s', label='Speed'),
YDef(handle='distance', unit='km', label='Distance')]
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
YDef(handle='distance', unit='km', label='Distance'),
YDef(handle='momentum', unit='Gkg⋅m/s', label='Momentum'),
YDef(handle='bumpSpeed', unit='m/s', label='Target speed', selectorLabel='Bump speed'),
YDef(handle='bumpDistance', unit='km', label='Target distance traveled', selectorLabel='Bump distance')]
inputs = [
Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30)),
# Default values in target fields correspond to a random carrier/fax
Input(handle='tgtMass', unit='Mkg', label='Target mass', iconID=76, defaultValue=1300, defaultRange=(100, 2500), conditions=[(None, ('bumpSpeed', 'm/s')), (None, ('bumpDistance', 'km'))], secondaryTooltip='Defined in millions of kilograms'),
Input(handle='tgtInertia', unit=None, label='Target inertia factor', iconID=1401, defaultValue=0.015, defaultRange=(0.03, 0.1), conditions=[(None, ('bumpDistance', 'km'))], secondaryTooltip='Inertia Modifier attribute value of the target ship')]
srcExtraCols = ('Speed', 'Agility')
# Calculation stuff
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
_getters = {
('time', 'speed'): Time2SpeedGetter,
('time', 'distance'): Time2DistanceGetter}
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
('time', 'distance'): Time2DistanceGetter,
('time', 'momentum'): Time2MomentumGetter,
('time', 'bumpSpeed'): Time2BumpSpeedGetter,
('time', 'bumpDistance'): Time2BumpDistanceGetter}
_denormalizers = {
('distance', 'km'): lambda v, src, tgt: v / 1000,
('momentum', 'Gkg⋅m/s'): lambda v, src, tgt: v / 10 ** 9,
('bumpDistance', 'km'): lambda v, src, tgt: v / 1000}

View File

@@ -26,8 +26,8 @@ from .getter import Distance2RpsGetter, Distance2RepAmountGetter, Time2RpsGetter
class FitRemoteRepsGraph(FitGraph):
def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._timeCache = TimeCache()
def _clearInternalCache(self, reason, extraData):

View File

@@ -27,6 +27,10 @@ from .getter import (
class FitShieldRegenGraph(FitGraph):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
# UI stuff
internalName = 'shieldRegenGraph'
name = 'Shield Regeneration'
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective

View File

@@ -26,8 +26,8 @@ from .getter import AU_METERS, Distance2TimeGetter
class FitWarpTimeGraph(FitGraph):
def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._subspeedCache = SubwarpSpeedCache()
def _clearInternalCache(self, reason, extraData):

View File

@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
self.ySubSelection.Clear()
for yDef in view.yDefs:
if yDef.hidden and not self.graphFrame.includeHidden:
continue
self.ySubSelection.Append(self.formatLabel(yDef, selector=True), yDef)
self.ySubSelection.Enable(len(view.yDefs) > 1)
self.ySubSelection.SetSelection(selectedY)
self.xSubSelection.Clear()
for xDef in view.xDefs:
if xDef.hidden and not self.graphFrame.includeHidden:
continue
self.xSubSelection.Append(self.formatLabel(xDef, selector=True), xDef)
self.xSubSelection.Enable(len(view.xDefs) > 1)
self.xSubSelection.SetSelection(selectedX)

View File

@@ -50,6 +50,7 @@ class GraphFrame(AuxiliaryFrame):
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.includeHidden = includeHidden
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
@@ -74,7 +75,7 @@ class GraphFrame(AuxiliaryFrame):
# Setup - graph selector
for view in FitGraph.views:
if view.hidden and not includeHidden:
if view.hidden and not self.includeHidden:
continue
self.graphSelection.Append(view.name, view())
self.graphSelection.SetSelection(0)

View File

@@ -316,7 +316,7 @@ class ImplantDisplay(d.Display):
implants.append(implant)
return implants
def addImplantSet(self, impSet):
def addImplants(self, implants):
self.mainFrame.command.Submit(cmd.GuiAddImplantSetCommand(
fitID=self.mainFrame.getActiveFit(),
itemIDs=[i.itemID for i in impSet.implants]))
itemIDs=[i.itemID for i in implants]))

View File

@@ -2,17 +2,19 @@
import wx
from gui.contextMenu import ContextMenuUnconditional
from service.implantSet import ImplantSets as s_ImplantSets
from service.market import Market
from service.implantSet import ImplantSets as UserImplantSets
from service.precalcImplantSet import PrecalcedImplantSets
class ImplantSetApply(ContextMenuUnconditional):
def display(self, callingWindow, srcContext):
sIS = s_ImplantSets.getInstance()
implantSets = sIS.getImplantSetList()
self.userImplantSets = UserImplantSets.getInstance().getImplantSetList()
self.structedImplantSets = PrecalcedImplantSets.getStructuredSets()
if len(implantSets) == 0:
if len(self.userImplantSets) == 0 and len(self.structedImplantSets) == 0:
return False
return srcContext in ("implantItemMisc", "implantEditor")
@@ -20,34 +22,87 @@ class ImplantSetApply(ContextMenuUnconditional):
def getText(self, callingWindow, context):
return "Apply Implant Set"
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
m = wx.Menu()
bindmenu = rootMenu if "wxMSW" in wx.PlatformInfo else m
def _addSeparator(self, m, text):
id_ = ContextMenuUnconditional.nextID()
m.Append(id_, '%s' % text)
m.Enable(id_, False)
sIS = s_ImplantSets.getInstance()
implantSets = sIS.getImplantSetList()
def _addSet(self, parentMenu, profile, name):
id = ContextMenuUnconditional.nextID()
self.eventSetMap[id] = profile
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
return menuItem
def _addCategory(self, parentMenu, name):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
return menuItem
def _gradeSorter(self, item):
order = ['low-grade', 'mid-grade', 'high-grade']
try:
pos = order.index(item.lower())
except IndexError:
pos = len(order)
return pos, item
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
msw = "wxMSW" in wx.PlatformInfo
menu_lvl1 = wx.Menu()
self.context = context
self.callingWindow = callingWindow
self.idmap = {}
self.eventSetMap = {}
for set in sorted(implantSets, key=lambda i: i.name):
id = ContextMenuUnconditional.nextID()
mitem = wx.MenuItem(rootMenu, id, set.name)
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
self.idmap[id] = set
m.Append(mitem)
# Auto-generated sets
for setName in sorted(self.structedImplantSets):
setData = self.structedImplantSets[setName]
if len(setData) == 1:
for implantIDs in setData.values():
menuitem_lvl1 = self._addSet(rootMenu, implantIDs, setName)
menu_lvl1.Append(menuitem_lvl1)
else:
menuitem_lvl1 = self._addCategory(rootMenu, setName)
menu_lvl2 = wx.Menu()
for gradeName in sorted(setData, key=self._gradeSorter):
implantIDs = setData[gradeName]
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantIDs, gradeName)
menu_lvl2.Append(menuitem_lvl2)
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
menuitem_lvl1.SetSubMenu(menu_lvl2)
menu_lvl1.Append(menuitem_lvl1)
return m
# Separator
if self.userImplantSets and self.structedImplantSets:
menu_lvl1.AppendSeparator()
# Saved sets
if self.userImplantSets:
menuitem_lvl1 = self._addCategory(rootMenu, 'Saved Sets')
menu_lvl2 = wx.Menu()
for implantSet in sorted(self.userImplantSets, key=lambda i: i.name):
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantSet, implantSet.name)
menu_lvl2.Append(menuitem_lvl2)
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
menuitem_lvl1.SetSubMenu(menu_lvl2)
menu_lvl1.Append(menuitem_lvl1)
menu_lvl1.Bind(wx.EVT_MENU, self.handleSelection)
return menu_lvl1
def handleSelection(self, event):
impSet = self.idmap.get(event.Id, None)
impSet = self.eventSetMap.get(event.Id, None)
if impSet is None:
event.Skip()
return
self.callingWindow.addImplantSet(impSet)
if isinstance(impSet, str):
implants = PrecalcedImplantSets.stringToImplants(impSet)
else:
implants = impSet.implants
self.callingWindow.addImplants(implants)
ImplantSetApply.register()

View File

@@ -26,7 +26,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
return 'Add Target Profile'
def handleProfileAdd(self, event):
profile = self.profileEventMap.get(event.Id, False)
profile = self.eventProfileMap.get(event.Id, False)
if profile is False:
event.Skip()
return
@@ -34,7 +34,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
def _addProfile(self, parentMenu, profile, name):
id = ContextMenuUnconditional.nextID()
self.profileEventMap[id] = profile
self.eventProfileMap[id] = profile
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
return menuItem
@@ -51,7 +51,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
profiles.sort(key=lambda p: smartSort(p.fullName))
self.profileEventMap = {}
self.eventProfileMap = {}
items = (OrderedDict(), OrderedDict())
for profile in profiles:
container = items

View File

@@ -1,15 +1,16 @@
import wx
from logbook import Logger
from eos.saveddata.module import Module
import gui.builtinMarketBrowser.pfSearchBox as SBox
from config import slotColourMap
from eos.saveddata.module import Module
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
from gui.contextMenu import ContextMenu
from gui.display import Display
from gui.utils.staticHelpers import DragDropHelper
from service.attribute import Attribute
from service.fit import Fit
from config import slotColourMap
from service.market import Market
pyfalog = Logger(__name__)
@@ -170,8 +171,8 @@ class ItemView(Display):
def scheduleSearch(self, event=None):
self.searchTimer.Stop() # Cancel any pending timers
search = self.marketBrowser.search.GetLineText(0)
# Make sure we do not count wildcard as search symbol
realsearch = search.replace("*", "")
# Make sure we do not count wildcards as search symbol
realsearch = search.replace('*', '').replace('?', '')
# Re-select market group if search query has zero length
if len(realsearch) == 0:
self.selectionMade('search')
@@ -193,10 +194,11 @@ class ItemView(Display):
self.setToggles()
self.filterItemStore()
def populateSearch(self, items):
def populateSearch(self, itemIDs):
# If we're no longer searching, dump the results
if self.marketBrowser.mode != 'search':
return
items = Market.getItems(itemIDs)
self.updateItemStore(items)
self.setToggles()
self.filterItemStore()

View File

@@ -25,7 +25,7 @@ class PFGeneralPref(PreferenceView):
self.stTitle.Wrap(-1)
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
helpCursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW)
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)

View File

@@ -86,8 +86,8 @@ class NavigationPanel(SFItem.SFBrowserItem):
def OnScheduleSearch(self, event):
search = self.BrowserSearchBox.GetValue()
# Make sure we do not count wildcard as search symbol
realsearch = search.replace("*", "")
# Make sure we do not count wildcards as search symbol
realsearch = search.replace('*', '').replace('?', '')
minChars = 1 if isStringCjk(realsearch) else 3
if len(realsearch) >= minChars:
self.lastSearch = search

View File

@@ -608,6 +608,18 @@ class Miscellanea(ViewColumn):
shield_hp = stuff.getModifiedItemAttr("shieldBonus", 0)
hp = max(stuff_hp, armor_hp * cycles, capacitor_hp * cycles, shield_hp * cycles, 0)
nonChargedMap = {
"Ancillary Remote Armor Repairer": ("armor", "Armor repaired per second"),
"Ancillary Remote Shield Booster": ("shield", "Shield transferred per second")}
if not cycles and itemGroup in nonChargedMap:
rps = stuff.getRemoteReps(ignoreState=True)
rps = getattr(rps, nonChargedMap[itemGroup][0])
if not rps:
return "", None
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = nonChargedMap[itemGroup][1]
return text, tooltip
if not hp or not cycleTime or not cycles:
return "", None

View File

@@ -302,8 +302,8 @@ class ItemView(d.Display):
sMkt = Market.getInstance()
search = self.searchBox.GetLineText(0)
# Make sure we do not count wildcard as search symbol
realsearch = search.replace("*", "")
# Make sure we do not count wildcards as search symbol
realsearch = search.replace('*', '').replace('?', '')
# Show nothing if query is too short
if len(realsearch) < 3:
self.clearSearch()
@@ -311,12 +311,12 @@ class ItemView(d.Display):
sMkt.searchItems(search, self.populateSearch, 'implants')
def populateSearch(self, items):
def populateSearch(self, itemIDs):
if not self.IsShown():
self.parent.availableImplantsTree.Hide()
self.Show()
self.parent.Layout()
items = Market.getItems(itemIDs)
items = [i for i in items if i.group.name != 'Booster']
self.items = sorted(list(items), key=lambda i: i.name)

View File

@@ -729,12 +729,12 @@ class ImplantEditorView(BaseImplantEditorView):
sChar.removeImplant(char.ID, implant)
def addImplantSet(self, impSet):
def addImplants(self, implants):
charEditor = self.Parent.Parent
char = charEditor.entityEditor.getActiveEntity()
sChar = Character.getInstance()
for implant in impSet.implants:
for implant in implants:
sChar.addImplant(char.ID, implant.item.ID)
wx.PostEvent(charEditor, GE.CharChanged())
@@ -833,7 +833,7 @@ class APIView(wx.Panel):
def fetchSkills(self, evt):
sChar = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
sChar.apiFetch(char.ID, self.__fetchCallback)
sChar.apiFetch(char.ID, APIView.fetchCallback)
def addCharacter(self, event):
sEsi = Esi.getInstance()
@@ -899,7 +899,8 @@ class APIView(wx.Panel):
if event is not None:
event.Skip()
def __fetchCallback(self, e=None):
@staticmethod
def fetchCallback(e=None):
if e:
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
exc_type, exc_value, exc_trace = e

View File

@@ -5,11 +5,14 @@ import requests
import wx
from logbook import Logger
import config
import gui.globalEvents as GE
from eos.db import getItem
from eos.saveddata.cargo import Cargo
from gui.auxFrame import AuxiliaryFrame
from gui.display import Display
from gui.characterEditor import APIView
from service.character import Character
from service.esi import Esi
from service.esiAccess import APIException
from service.fit import Fit
@@ -338,10 +341,14 @@ class SsoCharacterMgmt(AuxiliaryFrame):
self.deleteBtn = wx.Button(self, wx.ID_ANY, "Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0)
btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5)
self.fetchSkillsBtn = wx.Button(self, wx.ID_ANY, "Fetch Skills", wx.DefaultPosition, wx.DefaultSize, 0)
btnSizer.Add(self.fetchSkillsBtn, 0, wx.ALL | wx.EXPAND, 5)
mainSizer.Add(btnSizer, 0, wx.EXPAND, 5)
self.addBtn.Bind(wx.EVT_BUTTON, self.addChar)
self.deleteBtn.Bind(wx.EVT_BUTTON, self.delChar)
self.fetchSkillsBtn.Bind(wx.EVT_BUTTON, self.fetchSkills)
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoLogin)
self.Bind(wx.EVT_CLOSE, self.OnClose)
@@ -355,6 +362,13 @@ class SsoCharacterMgmt(AuxiliaryFrame):
def ssoLogin(self, event):
self.popCharList()
sChar = Character.getInstance()
# Update existing pyfa character, if it doesn't exist - create new
char = sChar.getCharacter(event.character.characterName)
if char is None:
char = sChar.new(event.character.characterName)
char.setSsoCharacter(event.character, config.getClientSecret())
sChar.apiFetch(char.ID, APIView.fetchCallback)
event.Skip()
def kbEvent(self, event):
@@ -381,6 +395,18 @@ class SsoCharacterMgmt(AuxiliaryFrame):
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
def fetchSkills(self, event):
sEsi = Esi.getInstance()
chars = sEsi.getSsoCharacters()
for ssoChar in chars:
if not ssoChar.characters:
char = Character.new(ssoChar.characterName)
char.setSsoCharacter(ssoChar, config.getClientSecret())
for char in ssoChar.characters:
sChar = Character.getInstance()
sChar.apiFetch(char.ID, APIView.fetchCallback)
def addChar(self, event):
try:
sEsi = Esi.getInstance()

View File

@@ -40,9 +40,6 @@ class CalcAddLocalModuleCommand(wx.Command):
position=fit.modules.index(oldMod),
newModInfo=self.newModInfo)
return self.subsystemCmd.Do()
if not newMod.fits(fit):
pyfalog.warning('Module does not fit')
return False
fit.modules.append(newMod)
if newMod not in fit.modules:
pyfalog.warning('Failed to append to list')
@@ -52,6 +49,13 @@ class CalcAddLocalModuleCommand(wx.Command):
# relationship via .owner attribute, which is handled by SQLAlchemy
eos.db.flush()
sFit.recalc(fit)
# fits() sometimes relies on recalculated on-item attributes, such as fax cap
# booster limitation, so we have to check it after recalculating and remove the
# module if the check has failed
if not newMod.fits(fit):
pyfalog.warning('Module does not fit')
self.Undo()
return False
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
return True

View File

@@ -39,7 +39,17 @@ class CalcReplaceLocalModuleCommand(wx.Command):
if newMod.slot != oldMod.slot:
return False
# Dummy it out in case the next bit fails
fit.modules.free(self.position)
fit.modules.replace(self.position, newMod)
if newMod not in fit.modules:
pyfalog.warning('Failed to replace in list')
self.Undo()
return False
if self.recalc:
# Need to flush because checkStates sometimes relies on module->fit
# relationship via .owner attribute, which is handled by SQLAlchemy
eos.db.flush()
sFit.recalc(fit)
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
if not self.ignoreRestrictions and not newMod.fits(fit):
pyfalog.warning('Module does not fit')
self.Undo()
@@ -52,17 +62,6 @@ class CalcReplaceLocalModuleCommand(wx.Command):
pyfalog.warning('Invalid charge')
self.Undo()
return False
fit.modules.replace(self.position, newMod)
if newMod not in fit.modules:
pyfalog.warning('Failed to replace in list')
self.Undo()
return False
if self.recalc:
# Need to flush because checkStates sometimes relies on module->fit
# relationship via .owner attribute, which is handled by SQLAlchemy
eos.db.flush()
sFit.recalc(fit)
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
return True
def Undo(self):

View File

@@ -678,8 +678,8 @@ class MainFrame(wx.Frame):
def toggleOverrides(self, event):
ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled
wx.PostEvent(self, GE.FitChanged(fitIDs=(self.getActiveFit(),)))
changedFitIDs = Fit.getInstance().processOverrideToggle()
wx.PostEvent(self, GE.FitChanged(fitIDs=changedFitIDs))
menu = self.GetMenuBar()
menu.SetLabel(menu.toggleOverridesId,
"&Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "&Turn Overrides On")

View File

@@ -67,7 +67,8 @@ class DmgPatternEntityEditor(EntityEditor):
def getEntitiesFromContext(self):
sDP = DamagePattern.getInstance()
choices = sorted(sDP.getUserDamagePatternList(), key=lambda p: p.rawName)
return [c for c in choices if c.rawName != "Selected Ammo"]
choices = [c for c in choices if c.rawName != "Selected Ammo"]
return choices
def DoNew(self, name):
sDP = DamagePattern.getInstance()
@@ -183,6 +184,10 @@ class DmgPatternEditor(AuxiliaryFrame):
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
if not self.entityEditor.checkEntitiesExist():
self.Close()
return
self.Layout()
bsize = self.GetBestSize()
self.SetSize((-1, bsize.height))
@@ -232,6 +237,11 @@ class DmgPatternEditor(AuxiliaryFrame):
self.entityEditor.btnDelete.Enable()
def patternChanged(self, event=None):
if not self.entityEditor.checkEntitiesExist():
self.Close()
return
p = self.entityEditor.getActiveEntity()
if p is None:

View File

@@ -13,6 +13,7 @@ from eos.db.gamedata.queries import getAttributeInfo, getItem
from gui.auxFrame import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.marketBrowser import SearchBox
from service.fit import Fit
from service.market import Market
@@ -170,12 +171,15 @@ class ItemView(d.Display):
d.Display.__init__(self, parent)
self.activeItems = []
self.searchTimer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.scheduleSearch, self.searchTimer)
self.searchBox = parent.Parent.Parent.searchBox
# Bind search actions
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
self.searchBox.Bind(SBox.EVT_TEXT, self.delaySearch)
self.update(Market.getInstance().getItemsWithOverrides())
@@ -188,12 +192,17 @@ class ItemView(d.Display):
if updateDisplay:
self.update(Market.getInstance().getItemsWithOverrides())
def delaySearch(self, evt):
sFit = Fit.getInstance()
self.searchTimer.Stop()
self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True)
def scheduleSearch(self, event=None):
sMkt = Market.getInstance()
search = self.searchBox.GetLineText(0)
# Make sure we do not count wildcard as search symbol
realsearch = search.replace("*", "")
# Make sure we do not count wildcards as search symbol
realsearch = search.replace('*', '').replace('?', '')
# Show nothing if query is too short
if len(realsearch) < 3:
self.clearSearch()
@@ -218,7 +227,8 @@ class ItemView(d.Display):
return not isFittable, catname, mktgrpid, parentname, metatab, metalvl, item.name
def populateSearch(self, items):
def populateSearch(self, itemIDs):
items = Market.getItems(itemIDs)
self.update(items)
def populate(self, items):

View File

@@ -25,12 +25,12 @@ from collections import OrderedDict
import wx
from logbook import Logger
import gui.mainFrame
import gui.globalEvents as GE
import gui.mainFrame
from gui.auxFrame import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
from gui.utils.clipboard import toClipboard, fromClipboard
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
from gui.utils.clipboard import fromClipboard, toClipboard
from gui.utils.inputs import FloatBox, InputValidator, strToFloat
from service.fit import Fit
from service.targetProfile import TargetProfile

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

After

Width:  |  Height:  |  Size: 72 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 B

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 345 B

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