Merge remote-tracking branch 'origin/master' into development
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -122,3 +122,4 @@ gitversion
|
|||||||
/.version
|
/.version
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
*.fsdbinary
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ saveInRoot = False
|
|||||||
|
|
||||||
# Version data
|
# Version data
|
||||||
|
|
||||||
version = "2.1.1"
|
version = "2.4.0"
|
||||||
tag = "Stable"
|
tag = "Stable"
|
||||||
expansionName = "Into the Abyss"
|
expansionName = "YC120.8"
|
||||||
expansionVersion = "1.1"
|
expansionVersion = "1.0"
|
||||||
evemonMinVersion = "4081"
|
evemonMinVersion = "4081"
|
||||||
|
|
||||||
minItemSearchLength = 3
|
minItemSearchLength = 3
|
||||||
|
|||||||
@@ -73,4 +73,7 @@ exe = EXE(pyz,
|
|||||||
app = BUNDLE(exe,
|
app = BUNDLE(exe,
|
||||||
name='pyfa.app',
|
name='pyfa.app',
|
||||||
icon=icon,
|
icon=icon,
|
||||||
bundle_identifier=None)
|
bundle_identifier=None,
|
||||||
|
info_plist={
|
||||||
|
'NSHighResolutionCapable': 'True'
|
||||||
|
})
|
||||||
|
|||||||
@@ -78,10 +78,10 @@ sd_lock = threading.RLock()
|
|||||||
|
|
||||||
# Import all the definitions for all our database stuff
|
# Import all the definitions for all our database stuff
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
|
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \
|
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \
|
||||||
miscData, module, override, price, queries, skill, targetResists, user
|
miscData, mutator, module, override, price, queries, skill, targetResists, user
|
||||||
|
|
||||||
# Import queries
|
# Import queries
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
__all__ = ["attribute", "category", "effect", "group", "metaData",
|
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
|
||||||
"icon", "item", "marketGroup", "metaGroup", "unit", "alphaClones"]
|
"item", "marketGroup", "metaGroup", "unit", "alphaClones"]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
|
|||||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.gamedata import Attribute, AttributeInfo, Unit, Icon
|
from eos.gamedata import Attribute, AttributeInfo, Unit
|
||||||
|
|
||||||
typeattributes_table = Table("dgmtypeattribs", gamedata_meta,
|
typeattributes_table = Table("dgmtypeattribs", gamedata_meta,
|
||||||
Column("value", Float),
|
Column("value", Float),
|
||||||
@@ -38,7 +38,7 @@ attributes_table = Table("dgmattribs", gamedata_meta,
|
|||||||
Column("published", Boolean),
|
Column("published", Boolean),
|
||||||
Column("displayName", String),
|
Column("displayName", String),
|
||||||
Column("highIsGood", Boolean),
|
Column("highIsGood", Boolean),
|
||||||
Column("iconID", Integer, ForeignKey("icons.iconID")),
|
Column("iconID", Integer),
|
||||||
Column("unitID", Integer, ForeignKey("dgmunits.unitID")))
|
Column("unitID", Integer, ForeignKey("dgmunits.unitID")))
|
||||||
|
|
||||||
mapper(Attribute, typeattributes_table,
|
mapper(Attribute, typeattributes_table,
|
||||||
@@ -46,7 +46,6 @@ mapper(Attribute, typeattributes_table,
|
|||||||
|
|
||||||
mapper(AttributeInfo, attributes_table,
|
mapper(AttributeInfo, attributes_table,
|
||||||
properties={
|
properties={
|
||||||
"icon" : relation(Icon),
|
|
||||||
"unit" : relation(Unit),
|
"unit" : relation(Unit),
|
||||||
"ID" : synonym("attributeID"),
|
"ID" : synonym("attributeID"),
|
||||||
"name" : synonym("attributeName"),
|
"name" : synonym("attributeName"),
|
||||||
|
|||||||
@@ -21,18 +21,17 @@ from sqlalchemy import Column, String, Integer, ForeignKey, Boolean, Table
|
|||||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.gamedata import Category, Icon
|
from eos.gamedata import Category
|
||||||
|
|
||||||
categories_table = Table("invcategories", gamedata_meta,
|
categories_table = Table("invcategories", gamedata_meta,
|
||||||
Column("categoryID", Integer, primary_key=True),
|
Column("categoryID", Integer, primary_key=True),
|
||||||
Column("categoryName", String),
|
Column("categoryName", String),
|
||||||
Column("description", String),
|
Column("description", String),
|
||||||
Column("published", Boolean),
|
Column("published", Boolean),
|
||||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
Column("iconID", Integer))
|
||||||
|
|
||||||
mapper(Category, categories_table,
|
mapper(Category, categories_table,
|
||||||
properties={
|
properties={
|
||||||
"icon" : relation(Icon),
|
|
||||||
"ID" : synonym("categoryID"),
|
"ID" : synonym("categoryID"),
|
||||||
"name" : synonym("categoryName"),
|
"name" : synonym("categoryName"),
|
||||||
"description": deferred(categories_table.c.description)
|
"description": deferred(categories_table.c.description)
|
||||||
|
|||||||
65
eos/db/gamedata/dynamicAttributes.py
Normal file
65
eos/db/gamedata/dynamicAttributes.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# ===============================================================================
|
||||||
|
# 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, Float, Integer, Table, ForeignKey
|
||||||
|
from sqlalchemy.orm import mapper, relation, synonym
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
|
||||||
|
from eos.db import gamedata_meta
|
||||||
|
from eos.gamedata import DynamicItem, DynamicItemAttribute, DynamicItemItem, Item
|
||||||
|
|
||||||
|
from eos.gamedata import AttributeInfo
|
||||||
|
|
||||||
|
dynamic_table = Table("mutaplasmids", gamedata_meta,
|
||||||
|
Column("typeID", ForeignKey("invtypes.typeID"), primary_key=True, index=True),
|
||||||
|
Column("resultingTypeID", ForeignKey("invtypes.typeID"), primary_key=True))
|
||||||
|
|
||||||
|
dynamicAttributes_table = Table("mutaplasmidAttributes", gamedata_meta,
|
||||||
|
Column("typeID", Integer, ForeignKey("mutaplasmids.typeID"), primary_key=True),
|
||||||
|
Column("attributeID", ForeignKey("dgmattribs.attributeID"), primary_key=True),
|
||||||
|
Column("min", Float),
|
||||||
|
Column("max", Float))
|
||||||
|
|
||||||
|
dynamicApplicable_table = Table("mutaplasmidItems", gamedata_meta,
|
||||||
|
Column("typeID", ForeignKey("mutaplasmids.typeID"), primary_key=True),
|
||||||
|
Column("applicableTypeID", ForeignKey("invtypes.typeID"), primary_key=True),)
|
||||||
|
|
||||||
|
mapper(DynamicItem, dynamic_table, properties={
|
||||||
|
"attributes": relation(DynamicItemAttribute),
|
||||||
|
"item": relation(Item, foreign_keys=[dynamic_table.c.typeID]),
|
||||||
|
"resultingItem": relation(Item, foreign_keys=[dynamic_table.c.resultingTypeID]),
|
||||||
|
"ID": synonym("typeID"),
|
||||||
|
})
|
||||||
|
|
||||||
|
mapper(DynamicItemAttribute, dynamicAttributes_table,
|
||||||
|
properties={"info": relation(AttributeInfo, lazy=False)})
|
||||||
|
|
||||||
|
mapper(DynamicItemItem, dynamicApplicable_table, properties={
|
||||||
|
"mutaplasmid": relation(DynamicItem),
|
||||||
|
})
|
||||||
|
|
||||||
|
DynamicItemAttribute.ID = association_proxy("info", "attributeID")
|
||||||
|
DynamicItemAttribute.name = association_proxy("info", "attributeName")
|
||||||
|
DynamicItemAttribute.description = association_proxy("info", "description")
|
||||||
|
DynamicItemAttribute.published = association_proxy("info", "published")
|
||||||
|
DynamicItemAttribute.displayName = association_proxy("info", "displayName")
|
||||||
|
DynamicItemAttribute.highIsGood = association_proxy("info", "highIsGood")
|
||||||
|
DynamicItemAttribute.iconID = association_proxy("info", "iconID")
|
||||||
|
DynamicItemAttribute.icon = association_proxy("info", "icon")
|
||||||
|
DynamicItemAttribute.unit = association_proxy("info", "unit")
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
|
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
|
||||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.gamedata import Category, Group, Icon
|
from eos.gamedata import Category, Group
|
||||||
|
|
||||||
groups_table = Table("invgroups", gamedata_meta,
|
groups_table = Table("invgroups", gamedata_meta,
|
||||||
Column("groupID", Integer, primary_key=True),
|
Column("groupID", Integer, primary_key=True),
|
||||||
@@ -29,12 +29,11 @@ groups_table = Table("invgroups", gamedata_meta,
|
|||||||
Column("description", String),
|
Column("description", String),
|
||||||
Column("published", Boolean),
|
Column("published", Boolean),
|
||||||
Column("categoryID", Integer, ForeignKey("invcategories.categoryID")),
|
Column("categoryID", Integer, ForeignKey("invcategories.categoryID")),
|
||||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
Column("iconID", Integer))
|
||||||
|
|
||||||
mapper(Group, groups_table,
|
mapper(Group, groups_table,
|
||||||
properties={
|
properties={
|
||||||
"category" : relation(Category, backref="groups"),
|
"category" : relation(Category, backref=backref("groups", cascade="all,delete")),
|
||||||
"icon" : relation(Icon),
|
|
||||||
"ID" : synonym("groupID"),
|
"ID" : synonym("groupID"),
|
||||||
"name" : synonym("groupName"),
|
"name" : synonym("groupName"),
|
||||||
"description": deferred(groups_table.c.description)
|
"description": deferred(groups_table.c.description)
|
||||||
|
|||||||
@@ -19,12 +19,13 @@
|
|||||||
|
|
||||||
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table, Float
|
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table, Float
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
|
||||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
from eos.db.gamedata.effect import typeeffects_table
|
from eos.db.gamedata.effect import typeeffects_table
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.gamedata import Attribute, Effect, Group, Icon, Item, MetaType, Traits
|
from eos.gamedata import Attribute, Effect, Group, Item, MetaType, Traits, DynamicItemItem, DynamicItem
|
||||||
|
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table, dynamic_table
|
||||||
|
|
||||||
items_table = Table("invtypes", gamedata_meta,
|
items_table = Table("invtypes", gamedata_meta,
|
||||||
Column("typeID", Integer, primary_key=True),
|
Column("typeID", Integer, primary_key=True),
|
||||||
@@ -37,7 +38,8 @@ items_table = Table("invtypes", gamedata_meta,
|
|||||||
Column("capacity", 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, ForeignKey("icons.iconID")),
|
Column("iconID", Integer),
|
||||||
|
Column("graphicID", Integer),
|
||||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
|
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
|
||||||
|
|
||||||
from .metaGroup import metatypes_table # noqa
|
from .metaGroup import metatypes_table # noqa
|
||||||
@@ -45,8 +47,7 @@ from .traits import traits_table # noqa
|
|||||||
|
|
||||||
mapper(Item, items_table,
|
mapper(Item, items_table,
|
||||||
properties={
|
properties={
|
||||||
"group" : relation(Group, backref="items"),
|
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
|
||||||
"icon" : relation(Icon),
|
|
||||||
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
|
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
|
||||||
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
|
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
|
||||||
"metaGroup" : relation(MetaType,
|
"metaGroup" : relation(MetaType,
|
||||||
@@ -57,7 +58,12 @@ mapper(Item, items_table,
|
|||||||
"description" : deferred(items_table.c.description),
|
"description" : deferred(items_table.c.description),
|
||||||
"traits" : relation(Traits,
|
"traits" : relation(Traits,
|
||||||
primaryjoin=traits_table.c.typeID == items_table.c.typeID,
|
primaryjoin=traits_table.c.typeID == items_table.c.typeID,
|
||||||
uselist=False)
|
uselist=False),
|
||||||
|
"mutaplasmids": relation(DynamicItem,
|
||||||
|
primaryjoin=dynamicApplicable_table.c.applicableTypeID == items_table.c.typeID,
|
||||||
|
secondaryjoin=dynamicApplicable_table.c.typeID == DynamicItem.typeID,
|
||||||
|
secondary=dynamicApplicable_table,
|
||||||
|
backref="applicableItems")
|
||||||
})
|
})
|
||||||
|
|
||||||
Item.category = association_proxy("group", "category")
|
Item.category = association_proxy("group", "category")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
|
|||||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.gamedata import Icon, Item, MarketGroup
|
from eos.gamedata import Item, MarketGroup
|
||||||
|
|
||||||
marketgroups_table = Table("invmarketgroups", gamedata_meta,
|
marketgroups_table = Table("invmarketgroups", gamedata_meta,
|
||||||
Column("marketGroupID", Integer, primary_key=True),
|
Column("marketGroupID", Integer, primary_key=True),
|
||||||
@@ -30,14 +30,13 @@ marketgroups_table = Table("invmarketgroups", gamedata_meta,
|
|||||||
Column("hasTypes", Boolean),
|
Column("hasTypes", Boolean),
|
||||||
Column("parentGroupID", Integer,
|
Column("parentGroupID", Integer,
|
||||||
ForeignKey("invmarketgroups.marketGroupID", initially="DEFERRED", deferrable=True)),
|
ForeignKey("invmarketgroups.marketGroupID", initially="DEFERRED", deferrable=True)),
|
||||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
Column("iconID", Integer))
|
||||||
|
|
||||||
mapper(MarketGroup, marketgroups_table,
|
mapper(MarketGroup, marketgroups_table,
|
||||||
properties={
|
properties={
|
||||||
"items" : relation(Item, backref="marketGroup"),
|
"items" : relation(Item, backref="marketGroup"),
|
||||||
"parent" : relation(MarketGroup, backref="children",
|
"parent" : relation(MarketGroup, backref="children",
|
||||||
remote_side=[marketgroups_table.c.marketGroupID]),
|
remote_side=[marketgroups_table.c.marketGroupID]),
|
||||||
"icon" : relation(Icon),
|
|
||||||
"ID" : synonym("marketGroupID"),
|
"ID" : synonym("marketGroupID"),
|
||||||
"name" : synonym("marketGroupName"),
|
"name" : synonym("marketGroupName"),
|
||||||
"description": deferred(marketgroups_table.c.description)
|
"description": deferred(marketgroups_table.c.description)
|
||||||
|
|||||||
@@ -17,15 +17,16 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy.orm import join, exc, aliased
|
from sqlalchemy.orm import join, exc, aliased, joinedload, subqueryload
|
||||||
from sqlalchemy.sql import and_, or_, select
|
from sqlalchemy.sql import and_, or_, select
|
||||||
|
from sqlalchemy.inspection import inspect
|
||||||
|
|
||||||
import eos.config
|
import eos.config
|
||||||
from eos.db import gamedata_session
|
from eos.db import gamedata_session
|
||||||
from eos.db.gamedata.metaGroup import metatypes_table, items_table
|
from eos.db.gamedata.metaGroup import metatypes_table, 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
|
||||||
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData
|
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData, DynamicItem
|
||||||
|
|
||||||
cache = {}
|
cache = {}
|
||||||
configVal = getattr(eos.config, "gamedataCache", None)
|
configVal = getattr(eos.config, "gamedataCache", None)
|
||||||
@@ -97,6 +98,36 @@ def getItem(lookfor, eager=None):
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def getMutaplasmid(lookfor, eager=None):
|
||||||
|
if isinstance(lookfor, int):
|
||||||
|
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||||
|
else:
|
||||||
|
raise TypeError("Need integer as argument")
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||||
|
# A lot of this is described in more detail in #1597
|
||||||
|
item = 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.
|
||||||
|
# todo: figure out a way to eagerly load all these via the query...
|
||||||
|
for x in [*inspect(Item).relationships.keys(), 'description']:
|
||||||
|
getattr(item, x)
|
||||||
|
|
||||||
|
# Copy over the attributes from the base, but ise the items attributes when there's an overlap
|
||||||
|
# WARNING: the attribute object still has the old typeID. I don't believe we access this typeID anywhere in the code,
|
||||||
|
# but should keep this in mind for now.
|
||||||
|
item._Item__attributes = {**base.attributes, **item.attributes}
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
@cachedQuery(1, "lookfor")
|
@cachedQuery(1, "lookfor")
|
||||||
def getItems(lookfor, eager=None):
|
def getItems(lookfor, eager=None):
|
||||||
"""
|
"""
|
||||||
@@ -361,6 +392,10 @@ def directAttributeRequest(itemIDs, attrIDs):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def getAbyssalTypes():
|
||||||
|
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
|
||||||
|
|
||||||
|
|
||||||
def getRequiredFor(itemID, attrMapping):
|
def getRequiredFor(itemID, attrMapping):
|
||||||
Attribute1 = aliased(Attribute)
|
Attribute1 = aliased(Attribute)
|
||||||
Attribute2 = aliased(Attribute)
|
Attribute2 = aliased(Attribute)
|
||||||
|
|||||||
18
eos/db/migrations/upgrade28.py
Normal file
18
eos/db/migrations/upgrade28.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
Migration 28
|
||||||
|
|
||||||
|
- adds baseItemID and mutaplasmidID to modules table
|
||||||
|
"""
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT baseItemID FROM modules LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN baseItemID INT;")
|
||||||
|
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT mutaplasmidID FROM modules LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN mutaplasmidID INT;")
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"character",
|
"character",
|
||||||
"fit",
|
"fit",
|
||||||
|
"mutator",
|
||||||
"module",
|
"module",
|
||||||
"user",
|
"user",
|
||||||
"skill",
|
"skill",
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fits_table = Table("fits", saveddata_meta,
|
|||||||
Column("booster", Boolean, nullable=False, index=True, default=0),
|
Column("booster", Boolean, nullable=False, index=True, default=0),
|
||||||
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
||||||
Column("modeID", Integer, nullable=True),
|
Column("modeID", Integer, nullable=True),
|
||||||
Column("implantLocation", Integer, nullable=False, default=ImplantLocation.FIT),
|
Column("implantLocation", Integer, nullable=False),
|
||||||
Column("notes", String, nullable=True),
|
Column("notes", String, nullable=True),
|
||||||
Column("ignoreRestrictions", Boolean, default=0),
|
Column("ignoreRestrictions", Boolean, default=0),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
|
|||||||
@@ -18,17 +18,21 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
|
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
|
||||||
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
from sqlalchemy.orm import relation, mapper
|
from sqlalchemy.orm import relation, mapper
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from eos.db import saveddata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.saveddata.module import Module
|
from eos.saveddata.module import Module
|
||||||
|
from eos.saveddata.mutator import Mutator
|
||||||
from eos.saveddata.fit import Fit
|
from eos.saveddata.fit import Fit
|
||||||
|
|
||||||
modules_table = Table("modules", saveddata_meta,
|
modules_table = Table("modules", saveddata_meta,
|
||||||
Column("ID", Integer, primary_key=True),
|
Column("ID", Integer, primary_key=True),
|
||||||
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
|
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
|
||||||
Column("itemID", Integer, nullable=True),
|
Column("itemID", Integer, nullable=True),
|
||||||
|
Column("baseItemID", Integer, nullable=True),
|
||||||
|
Column("mutaplasmidID", Integer, nullable=True),
|
||||||
Column("dummySlot", Integer, nullable=True, default=None),
|
Column("dummySlot", Integer, nullable=True, default=None),
|
||||||
Column("chargeID", Integer),
|
Column("chargeID", Integer),
|
||||||
Column("state", Integer, CheckConstraint("state >= -1"), CheckConstraint("state <= 2")),
|
Column("state", Integer, CheckConstraint("state >= -1"), CheckConstraint("state <= 2")),
|
||||||
@@ -39,4 +43,12 @@ modules_table = Table("modules", saveddata_meta,
|
|||||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||||
|
|
||||||
mapper(Module, modules_table,
|
mapper(Module, modules_table,
|
||||||
properties={"owner": relation(Fit)})
|
properties={
|
||||||
|
"owner": relation(Fit),
|
||||||
|
"mutators": relation(
|
||||||
|
Mutator,
|
||||||
|
backref="module",
|
||||||
|
cascade="all,delete-orphan",
|
||||||
|
collection_class=attribute_mapped_collection('attrID')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|||||||
@@ -17,19 +17,18 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Column, String, Integer, Table
|
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime, Float
|
||||||
from sqlalchemy.orm import mapper, synonym, deferred
|
from sqlalchemy.orm import mapper
|
||||||
|
import datetime
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.gamedata import Icon
|
from eos.saveddata.mutator import Mutator
|
||||||
|
|
||||||
icons_table = Table("icons", gamedata_meta,
|
mutator_table = Table("mutators", saveddata_meta,
|
||||||
Column("iconID", Integer, primary_key=True),
|
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, index=True),
|
||||||
Column("description", String),
|
Column("attrID", Integer, primary_key=True, index=True),
|
||||||
Column("iconFile", String))
|
Column("value", Float, nullable=False),
|
||||||
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
|
||||||
|
|
||||||
mapper(Icon, icons_table,
|
mapper(Mutator, mutator_table)
|
||||||
properties={
|
|
||||||
"ID" : synonym("iconID"),
|
|
||||||
"description": deferred(icons_table.c.description)
|
|
||||||
})
|
|
||||||
@@ -6,4 +6,4 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.boostItemAttr("agility", module.getModifiedItemAttr("agilityMultiplier"), stackingPenalties=True)
|
fit.ship.boostItemAttr("agility", module.getModifiedItemAttr("agilityBonus"), stackingPenalties=True)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# armorAllRepairSystemsAmountBonusPassive
|
# armorAllRepairSystemsAmountBonusPassive
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Hardshell' TB Dose (3 of 4)
|
# Implants named like: Agency 'Hardshell' TB Dose (4 of 4)
|
||||||
# Implants named like: Exile Booster (4 of 4)
|
# Implants named like: Exile Booster (4 of 4)
|
||||||
# Implant: Antipharmakon Kosybo
|
# Implant: Antipharmakon Kosybo
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# boosterMaxVelocityPenalty
|
# boosterMaxVelocityPenalty
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Booster (12 of 33)
|
# Implants named like: Crash Booster (3 of 4)
|
||||||
|
# Items from market group: Implants & Boosters > Booster > Booster Slot 02 (9 of 13)
|
||||||
type = "boosterSideEffect"
|
type = "boosterSideEffect"
|
||||||
|
|
||||||
# User-friendly name for the side effect
|
# User-friendly name for the side effect
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# boosterShieldCapacityPenalty
|
# boosterShieldCapacityPenalty
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Booster (12 of 33)
|
# Implants from group: Booster (12 of 65)
|
||||||
type = "boosterSideEffect"
|
type = "boosterSideEffect"
|
||||||
|
|
||||||
# User-friendly name for the side effect
|
# User-friendly name for the side effect
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# capacitorCapacityBonus
|
# capacitorCapacityBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Capacitor Battery (27 of 27)
|
# Modules from group: Capacitor Battery (30 of 30)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Black Ops (5 of 5)
|
# Ships from group: Black Ops (5 of 5)
|
||||||
# Ships from group: Blockade Runner (4 of 4)
|
# Ships from group: Blockade Runner (4 of 4)
|
||||||
# Ships from group: Covert Ops (7 of 7)
|
# Ships from group: Covert Ops (8 of 8)
|
||||||
# Ships from group: Expedition Frigate (2 of 2)
|
# Ships from group: Expedition Frigate (2 of 2)
|
||||||
# Ships from group: Force Recon Ship (8 of 8)
|
# Ships from group: Force Recon Ship (9 of 9)
|
||||||
# Ships from group: Stealth Bomber (5 of 5)
|
# Ships from group: Stealth Bomber (5 of 5)
|
||||||
# Ships named like: Stratios (2 of 2)
|
# Ships named like: Stratios (2 of 2)
|
||||||
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
|
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# covertOpsCloakCpuPercentBonus1
|
# covertOpsCloakCpuPercentBonus1
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Covert Ops (5 of 7)
|
# Ships from group: Covert Ops (6 of 8)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
|
|
||||||
|
|||||||
9
eos/effects/covertopswarpresistance.py
Normal file
9
eos/effects/covertopswarpresistance.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# covertOpsWarpResistance
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ships from group: Covert Ops (5 of 8)
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.ship.increaseItemAttr("warpFactor", src.getModifiedItemAttr("eliteBonusCovertOps1"), skill="Covert Ops")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# cynosuralDurationBonus
|
# cynosuralDurationBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Force Recon Ship (7 of 8)
|
# Ships from group: Force Recon Ship (8 of 9)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# cynosuralTheoryConsumptionBonus
|
# cynosuralTheoryConsumptionBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Force Recon Ship (7 of 8)
|
# Ships from group: Force Recon Ship (8 of 9)
|
||||||
# Skill: Cynosural Field Theory
|
# Skill: Cynosural Field Theory
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# eliteBonusCoverOpsScanProbeStrength2
|
# eliteBonusCoverOpsScanProbeStrength2
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Covert Ops (7 of 7)
|
# Ships from group: Covert Ops (8 of 8)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
eos/effects/elitebonusmaxdmgmultibonusadd.py
Normal file
10
eos/effects/elitebonusmaxdmgmultibonusadd.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# eliteBonusMaxDmgMultiBonusAdd
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Hydra
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"), "damageMultiplierBonusMax",
|
||||||
|
src.getModifiedItemAttr("eliteBonusCovertOps3"), skill="Covert Ops")
|
||||||
10
eos/effects/elitebonusreconmaxdmgmultimaxhpt.py
Normal file
10
eos/effects/elitebonusreconmaxdmgmultimaxhpt.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# eliteBonusReconMaxDmgMultiMaxHPT
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Tiamat
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Medium Precursor Weapon"), "damageMultiplierBonusMax",
|
||||||
|
src.getModifiedItemAttr("eliteBonusReconShip3"), skill="Recon Ships")
|
||||||
10
eos/effects/elitebonusreconscanprobestrength2.py
Normal file
10
eos/effects/elitebonusreconscanprobestrength2.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# eliteBonusReconScanProbeStrength2
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Tiamat
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Astrometrics"), "baseSensorStrength",
|
||||||
|
src.getModifiedItemAttr("eliteBonusReconShip2"), skill="Recon Ships")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# energyNosferatuFalloff
|
# energyNosferatuFalloff
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Energy Nosferatu (51 of 51)
|
# Modules from group: Energy Nosferatu (54 of 54)
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||||
|
|
||||||
type = "active", "projected"
|
type = "active", "projected"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# minigameVirusStrengthBonus
|
# minigameVirusStrengthBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Covert Ops (7 of 7)
|
# Ships from group: Covert Ops (7 of 8)
|
||||||
# Ships named like: Stratios (2 of 2)
|
# Ships named like: Stratios (2 of 2)
|
||||||
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
|
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
|
||||||
# Ship: Astero
|
# Ship: Astero
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileSkillWarheadUpgradesEmDamageBonus
|
# missileSkillWarheadUpgradesEmDamageBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Pyrolancea' DB Dose (3 of 4)
|
# Implants named like: Agency 'Pyrolancea' DB Dose (4 of 4)
|
||||||
# Skill: Warhead Upgrades
|
# Skill: Warhead Upgrades
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileSkillWarheadUpgradesExplosiveDamageBonus
|
# missileSkillWarheadUpgradesExplosiveDamageBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Pyrolancea' DB Dose (3 of 4)
|
# Implants named like: Agency 'Pyrolancea' DB Dose (4 of 4)
|
||||||
# Skill: Warhead Upgrades
|
# Skill: Warhead Upgrades
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileSkillWarheadUpgradesKineticDamageBonus
|
# missileSkillWarheadUpgradesKineticDamageBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Pyrolancea' DB Dose (3 of 4)
|
# Implants named like: Agency 'Pyrolancea' DB Dose (4 of 4)
|
||||||
# Skill: Warhead Upgrades
|
# Skill: Warhead Upgrades
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileSkillWarheadUpgradesThermalDamageBonus
|
# missileSkillWarheadUpgradesThermalDamageBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Pyrolancea' DB Dose (3 of 4)
|
# Implants named like: Agency 'Pyrolancea' DB Dose (4 of 4)
|
||||||
# Skill: Warhead Upgrades
|
# Skill: Warhead Upgrades
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# modifyEnergyWarfareResistance
|
# modifyEnergyWarfareResistance
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Capacitor Battery (27 of 27)
|
# Modules from group: Capacitor Battery (30 of 30)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Rig Anchor (4 of 4)
|
# Modules from group: Rig Anchor (4 of 4)
|
||||||
# Implants named like: Agency 'Overclocker' SB Dose (3 of 4)
|
# Implants named like: Agency 'Overclocker' SB Dose (4 of 4)
|
||||||
# Implants named like: grade Snake (16 of 18)
|
# Implants named like: grade Snake (16 of 18)
|
||||||
# Modules named like: Auxiliary Thrusters (8 of 8)
|
# Modules named like: Auxiliary Thrusters (8 of 8)
|
||||||
# Implant: Quafe Zero
|
# Implant: Quafe Zero
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Capacitor Booster (59 of 59)
|
# Modules from group: Capacitor Booster (59 of 59)
|
||||||
# Modules from group: Energy Neutralizer (54 of 54)
|
# Modules from group: Energy Neutralizer (54 of 54)
|
||||||
# Modules from group: Energy Nosferatu (51 of 51)
|
# Modules from group: Energy Nosferatu (54 of 54)
|
||||||
# Modules from group: Hull Repair Unit (25 of 25)
|
# Modules from group: Hull Repair Unit (25 of 25)
|
||||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# reconShipCloakCpuBonus1
|
# reconShipCloakCpuBonus1
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Force Recon Ship (6 of 8)
|
# Ships from group: Force Recon Ship (7 of 9)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
runTime = "early"
|
runTime = "early"
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ type = "passive"
|
|||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Stasis Web", "maxRange",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Stasis Web", "maxRange",
|
||||||
src.getModifiedItemAttr("stasisWebRangeBonus"), stackingPenalties=True)
|
src.getModifiedItemAttr("stasisWebRangeBonus"), stackingPenalties=False)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Weapon Disruption"), "falloff",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Weapon Disruption"), "falloffEffectiveness",
|
||||||
src.getModifiedItemAttr("roleBonus"))
|
src.getModifiedItemAttr("roleBonus"))
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Weapon Disruption"), "maxRange",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Weapon Disruption"), "maxRange",
|
||||||
src.getModifiedItemAttr("roleBonus"))
|
src.getModifiedItemAttr("roleBonus"))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# shieldBoostAmplifierPassiveBooster
|
# shieldBoostAmplifierPassiveBooster
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Hardshell' TB Dose (3 of 4)
|
# 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 (5 of 5)
|
||||||
# Implant: Antipharmakon Thureo
|
# Implant: Antipharmakon Thureo
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
# Ship: Leshak
|
# Ship: Leshak
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# shipbonusPCTDamagePC1
|
# shipbonusPCTDamagePC1
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# shipbonusPCTTrackingPC2
|
# shipbonusPCTTrackingPC2
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
# Ship: Leshak
|
# Ship: Leshak
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
# Ship: Leshak
|
# Ship: Leshak
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
|
# Ship: Hydra
|
||||||
# Ship: Leshak
|
# Ship: Leshak
|
||||||
|
# Ship: Tiamat
|
||||||
# Ship: Vedmak
|
# Ship: Vedmak
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipBonusSurveyProbeExplosionDelaySkillSurveyCovertOps3
|
# shipBonusSurveyProbeExplosionDelaySkillSurveyCovertOps3
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Covert Ops (5 of 7)
|
# Ships from group: Covert Ops (5 of 8)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drone Avionics"),
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Light Drone Operation"),
|
||||||
"thermalDamage", ship.getModifiedItemAttr("shipBonusGF2"), skill="Gallente Frigate")
|
"thermalDamage", ship.getModifiedItemAttr("shipBonusGF2"), skill="Gallente Frigate")
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# shipPDmgBonusMF
|
# shipPDmgBonusMF
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Variations of ship: Slasher (3 of 3)
|
|
||||||
# Ship: Cheetah
|
# Ship: Cheetah
|
||||||
# Ship: Freki
|
# Ship: Freki
|
||||||
# Ship: Republic Fleet Firetail
|
# Ship: Republic Fleet Firetail
|
||||||
# Ship: Rifter
|
# Ship: Rifter
|
||||||
|
# Ship: Slasher
|
||||||
|
# Ship: Stiletto
|
||||||
# Ship: Wolf
|
# Ship: Wolf
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
10
eos/effects/shipprojectilerofmf.py
Normal file
10
eos/effects/shipprojectilerofmf.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# shipProjectileRofMF
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Claw
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Projectile Turret"), "speed",
|
||||||
|
src.getModifiedItemAttr("shipBonusMF"), stackingPenalties=True, skill="Minmatar Frigate")
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
# skillBonusDroneDurability
|
# skillBonusDroneDurability
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Implants from group: Cyber Drones (2 of 2)
|
||||||
# Skill: Drone Durability
|
# Skill: Drone Durability
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
lvl = src.level
|
lvl = src.level if "skill" in context else 1
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "hp",
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "hp",
|
||||||
src.getModifiedItemAttr("hullHpBonus") * lvl)
|
src.getModifiedItemAttr("hullHpBonus") * lvl)
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# skillBonusDroneInterfacing
|
# skillBonusDroneInterfacing
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Implants from group: Cyber Drones (2 of 2)
|
||||||
# Skill: Drone Interfacing
|
# Skill: Drone Interfacing
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
lvl = src.level
|
lvl = src.level if "skill" in context else 1
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "damageMultiplier",
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "damageMultiplier",
|
||||||
src.getModifiedItemAttr("damageMultiplierBonus") * lvl)
|
src.getModifiedItemAttr("damageMultiplierBonus") * lvl)
|
||||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"),
|
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
lvl = src.level
|
lvl = src.level if "skill" in context else 1
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "maxVelocity",
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "maxVelocity",
|
||||||
src.getModifiedItemAttr("maxVelocityBonus") * lvl)
|
src.getModifiedItemAttr("maxVelocityBonus") * lvl)
|
||||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "maxVelocity",
|
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "maxVelocity",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
lvl = src.level
|
lvl = src.level if "skill" in context else 1
|
||||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "maxRange",
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "maxRange",
|
||||||
src.getModifiedItemAttr("rangeSkillBonus") * lvl)
|
src.getModifiedItemAttr("rangeSkillBonus") * lvl)
|
||||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "fighterAbilityMissilesRange",
|
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "fighterAbilityMissilesRange",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# surgicalStrikeDamageMultiplierBonusPostPercentDamageMultiplierLocationShipModulesRequiringGunnery
|
# surgicalStrikeDamageMultiplierBonusPostPercentDamageMultiplierLocationShipModulesRequiringGunnery
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Agency 'Pyrolancea' DB Dose (3 of 4)
|
# Implants named like: Agency 'Pyrolancea' DB Dose (4 of 4)
|
||||||
# Implants named like: Eifyr and Co. 'Gunslinger' Surgical Strike SS (6 of 6)
|
# Implants named like: Eifyr and Co. 'Gunslinger' Surgical Strike SS (6 of 6)
|
||||||
# Implant: Standard Cerebral Accelerator
|
# Implant: Standard Cerebral Accelerator
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
119
eos/gamedata.py
119
eos/gamedata.py
@@ -208,6 +208,8 @@ class Item(EqBase):
|
|||||||
|
|
||||||
MOVE_ATTR_INFO = None
|
MOVE_ATTR_INFO = None
|
||||||
|
|
||||||
|
ABYSSAL_TYPES = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getMoveAttrInfo(cls):
|
def getMoveAttrInfo(cls):
|
||||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||||
@@ -463,6 +465,17 @@ class Item(EqBase):
|
|||||||
|
|
||||||
return self.__price
|
return self.__price
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isAbyssal(self):
|
||||||
|
if Item.ABYSSAL_TYPES is None:
|
||||||
|
Item.getAbyssalYypes()
|
||||||
|
|
||||||
|
return self.ID in Item.ABYSSAL_TYPES
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getAbyssalYypes(cls):
|
||||||
|
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Item(ID={}, name={}) at {}".format(
|
return "Item(ID={}, name={}) at {}".format(
|
||||||
self.ID, self.name, hex(id(self))
|
self.ID, self.name, hex(id(self))
|
||||||
@@ -512,7 +525,15 @@ class Group(EqBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Icon(EqBase):
|
class DynamicItem(EqBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicItemAttribute(EqBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicItemItem(EqBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -532,7 +553,101 @@ class MetaType(EqBase):
|
|||||||
|
|
||||||
|
|
||||||
class Unit(EqBase):
|
class Unit(EqBase):
|
||||||
pass
|
|
||||||
|
def __init__(self):
|
||||||
|
self.name = None
|
||||||
|
self.displayName = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def translations(self):
|
||||||
|
""" This is a mapping of various tweaks that we have to do between the internal representation of an attribute
|
||||||
|
value and the display (for example, 'Millisecond' units have the display name of 's', so we have to convert value
|
||||||
|
from ms to s) """
|
||||||
|
return {
|
||||||
|
"Inverse Absolute Percent": (
|
||||||
|
lambda v: (1 - v) * 100,
|
||||||
|
lambda d: -1 * (d / 100) + 1,
|
||||||
|
lambda u: u),
|
||||||
|
"Inversed Modifier Percent": (
|
||||||
|
lambda v: (1 - v) * 100,
|
||||||
|
lambda d: -1 * (d / 100) + 1,
|
||||||
|
lambda u: u),
|
||||||
|
"Modifier Percent": (
|
||||||
|
lambda v: ("%+.2f" if ((v - 1) * 100) % 1 else "%+d") % ((v - 1) * 100),
|
||||||
|
lambda d: (d / 100) + 1,
|
||||||
|
lambda u: u),
|
||||||
|
"Volume": (
|
||||||
|
lambda v: v,
|
||||||
|
lambda d: d,
|
||||||
|
lambda u: "m³"),
|
||||||
|
"Sizeclass": (
|
||||||
|
lambda v: v,
|
||||||
|
lambda d: d,
|
||||||
|
lambda u: ""),
|
||||||
|
"Absolute Percent": (
|
||||||
|
lambda v: (v * 100),
|
||||||
|
lambda d: d / 100,
|
||||||
|
lambda u: u),
|
||||||
|
"Milliseconds": (
|
||||||
|
lambda v: v / 1000.0,
|
||||||
|
lambda d: d * 1000.0,
|
||||||
|
lambda u: u),
|
||||||
|
"Boolean": (
|
||||||
|
lambda v: "Yes" if v == 1 else "No",
|
||||||
|
lambda d: 1.0 if d == "Yes" else 0.0,
|
||||||
|
lambda u: ""),
|
||||||
|
"typeID": (
|
||||||
|
self.itemIDCallback,
|
||||||
|
None, # we could probably convert these back if we really tried hard enough
|
||||||
|
lambda u: ""),
|
||||||
|
"groupID": (
|
||||||
|
self.groupIDCallback,
|
||||||
|
None,
|
||||||
|
lambda u: ""),
|
||||||
|
"attributeID": (
|
||||||
|
self.attributeIDCallback,
|
||||||
|
None,
|
||||||
|
lambda u: ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def itemIDCallback(v):
|
||||||
|
v = int(v)
|
||||||
|
item = eos.db.getItem(int(v))
|
||||||
|
return "%s (%d)" % (item.name, v) if item is not None else str(v)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def groupIDCallback(v):
|
||||||
|
v = int(v)
|
||||||
|
group = eos.db.getGroup(v)
|
||||||
|
return "%s (%d)" % (group.name, v) if group is not None else str(v)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def attributeIDCallback(v):
|
||||||
|
v = int(v)
|
||||||
|
if not v: # some attributes come through with a value of 0? See #1387
|
||||||
|
return "%d" % (v)
|
||||||
|
attribute = eos.db.getAttributeInfo(v, eager=("unit"))
|
||||||
|
return "%s (%d)" % (attribute.name.capitalize(), v)
|
||||||
|
|
||||||
|
def TranslateValue(self, value):
|
||||||
|
"""Attributes have to be translated certain ways based on their unit (ex: decimals converting to percentages).
|
||||||
|
This allows us to get an easy representation of how the attribute should be printed """
|
||||||
|
|
||||||
|
override = self.translations.get(self.name)
|
||||||
|
if override is not None:
|
||||||
|
return override[0](value), override[2](self.displayName)
|
||||||
|
|
||||||
|
return value, self.displayName
|
||||||
|
|
||||||
|
def ComplicateValue(self, value):
|
||||||
|
"""Takes the display value and turns it back into the internal representation of it"""
|
||||||
|
|
||||||
|
override = self.translations.get(self.name)
|
||||||
|
if override is not None:
|
||||||
|
return override[1](value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Traits(EqBase):
|
class Traits(EqBase):
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ class ItemAttrShortcut(object):
|
|||||||
|
|
||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
|
def getBaseAttrValue(self, key, default=0):
|
||||||
|
'''
|
||||||
|
Gets base value in this order:
|
||||||
|
Mutated value > override value > attribute value
|
||||||
|
'''
|
||||||
|
return_value = self.itemModifiedAttributes.getOriginal(key)
|
||||||
|
|
||||||
|
return return_value or default
|
||||||
|
|
||||||
|
|
||||||
class ChargeAttrShortcut(object):
|
class ChargeAttrShortcut(object):
|
||||||
def getModifiedChargeAttr(self, key, default=0):
|
def getModifiedChargeAttr(self, key, default=0):
|
||||||
@@ -59,8 +68,10 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
self.__modified = {}
|
self.__modified = {}
|
||||||
# Affected by entities
|
# Affected by entities
|
||||||
self.__affectedBy = {}
|
self.__affectedBy = {}
|
||||||
# Overrides
|
# Overrides (per item)
|
||||||
self.__overrides = {}
|
self.__overrides = {}
|
||||||
|
# Mutators (per module)
|
||||||
|
self.__mutators = {}
|
||||||
# Dictionaries for various value modification types
|
# Dictionaries for various value modification types
|
||||||
self.__forced = {}
|
self.__forced = {}
|
||||||
self.__preAssigns = {}
|
self.__preAssigns = {}
|
||||||
@@ -100,6 +111,14 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
def overrides(self, val):
|
def overrides(self, val):
|
||||||
self.__overrides = val
|
self.__overrides = val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mutators(self):
|
||||||
|
return {x.attribute.name: x for x in self.__mutators.values()}
|
||||||
|
|
||||||
|
@mutators.setter
|
||||||
|
def mutators(self, val):
|
||||||
|
self.__mutators = val
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
# Check if we have final calculated value
|
# Check if we have final calculated value
|
||||||
key_value = self.__modified.get(key)
|
key_value = self.__modified.get(key)
|
||||||
@@ -128,14 +147,16 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
del self.__intermediary[key]
|
del self.__intermediary[key]
|
||||||
|
|
||||||
def getOriginal(self, key, default=None):
|
def getOriginal(self, key, default=None):
|
||||||
|
val = None
|
||||||
if self.overrides_enabled and self.overrides:
|
if self.overrides_enabled and self.overrides:
|
||||||
val = self.overrides.get(key, None)
|
val = self.overrides.get(key, val)
|
||||||
else:
|
|
||||||
val = None
|
# mutators are overriden by overrides. x_x
|
||||||
|
val = self.mutators.get(key, val)
|
||||||
|
|
||||||
if val is None:
|
if val is None:
|
||||||
if self.original:
|
if self.original:
|
||||||
val = self.original.get(key, None)
|
val = self.original.get(key, val)
|
||||||
|
|
||||||
if val is None and val != default:
|
if val is None and val != default:
|
||||||
val = default
|
val = default
|
||||||
|
|||||||
@@ -142,14 +142,8 @@ class Booster(HandledItem, ItemAttrShortcut):
|
|||||||
copy = Booster(self.item)
|
copy = Booster(self.item)
|
||||||
copy.active = self.active
|
copy.active = self.active
|
||||||
|
|
||||||
# Legacy booster side effect code, disabling as not currently implemented
|
for sideEffect in self.sideEffects:
|
||||||
'''
|
copyEffect = next(filter(lambda eff: eff.effectID == sideEffect.effectID, copy.sideEffects))
|
||||||
origSideEffects = list(self.iterSideEffects())
|
copyEffect.active = sideEffect.active
|
||||||
copySideEffects = list(copy.iterSideEffects())
|
|
||||||
i = 0
|
|
||||||
while i < len(origSideEffects):
|
|
||||||
copySideEffects[i].active = origSideEffects[i].active
|
|
||||||
i += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
return copy
|
return copy
|
||||||
|
|||||||
@@ -57,8 +57,15 @@ class Character(object):
|
|||||||
def init(self):
|
def init(self):
|
||||||
|
|
||||||
self.__skillIdMap = {}
|
self.__skillIdMap = {}
|
||||||
|
|
||||||
for skill in self.__skills:
|
for skill in self.__skills:
|
||||||
self.__skillIdMap[skill.itemID] = skill
|
self.__skillIdMap[skill.itemID] = skill
|
||||||
|
|
||||||
|
# get a list of skills that the character does no have, and add them (removal of old skills happens in the
|
||||||
|
# Skill loading)
|
||||||
|
for skillID in set(self.getSkillIDMap().keys()).difference(set(self.__skillIdMap.keys())):
|
||||||
|
self.addSkill(Skill(self, skillID, self.defaultLevel))
|
||||||
|
|
||||||
self.dirtySkills = set()
|
self.dirtySkills = set()
|
||||||
|
|
||||||
self.alphaClone = None
|
self.alphaClone = None
|
||||||
@@ -118,6 +125,12 @@ class Character(object):
|
|||||||
|
|
||||||
return all0
|
return all0
|
||||||
|
|
||||||
|
def apiUpdateCharSheet(self, skills, secStatus=0.00):
|
||||||
|
self.clearSkills()
|
||||||
|
for skillRow in skills:
|
||||||
|
self.addSkill(Skill(self, skillRow["typeID"], skillRow["level"]))
|
||||||
|
self.secStatus = float(secStatus)
|
||||||
|
|
||||||
def clearSkills(self):
|
def clearSkills(self):
|
||||||
del self.__skills[:]
|
del self.__skills[:]
|
||||||
self.__skillIdMap.clear()
|
self.__skillIdMap.clear()
|
||||||
|
|||||||
@@ -291,6 +291,10 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
copy = Fighter(self.item)
|
copy = Fighter(self.item)
|
||||||
copy.amount = self.amount
|
copy.amount = self.amount
|
||||||
|
copy.active = self.active
|
||||||
|
for ability in self.abilities:
|
||||||
|
copyAbility = next(filter(lambda a: a.effectID == ability.effectID, copy.abilities))
|
||||||
|
copyAbility.active = ability.active
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def fits(self, fit):
|
def fits(self, fit):
|
||||||
|
|||||||
@@ -1580,6 +1580,7 @@ class Fit(object):
|
|||||||
copy_ship.name = "%s copy" % self.name
|
copy_ship.name = "%s copy" % self.name
|
||||||
copy_ship.damagePattern = self.damagePattern
|
copy_ship.damagePattern = self.damagePattern
|
||||||
copy_ship.targetResists = self.targetResists
|
copy_ship.targetResists = self.targetResists
|
||||||
|
copy_ship.implantLocation = self.implantLocation
|
||||||
copy_ship.notes = self.notes
|
copy_ship.notes = self.notes
|
||||||
|
|
||||||
toCopy = (
|
toCopy = (
|
||||||
@@ -1598,12 +1599,27 @@ class Fit(object):
|
|||||||
for i in orig:
|
for i in orig:
|
||||||
c.append(deepcopy(i))
|
c.append(deepcopy(i))
|
||||||
|
|
||||||
for fit in self.projectedFits:
|
# this bit is required -- see GH issue # 83
|
||||||
copy_ship.__projectedFits[fit.ID] = fit
|
def forceUpdateSavedata(fit):
|
||||||
# this bit is required -- see GH issue # 83
|
|
||||||
eos.db.saveddata_session.flush()
|
eos.db.saveddata_session.flush()
|
||||||
eos.db.saveddata_session.refresh(fit)
|
eos.db.saveddata_session.refresh(fit)
|
||||||
|
|
||||||
|
for fit in self.commandFits:
|
||||||
|
copy_ship.__commandFits[fit.ID] = fit
|
||||||
|
forceUpdateSavedata(fit)
|
||||||
|
copyCommandInfo = fit.getCommandInfo(copy_ship.ID)
|
||||||
|
originalCommandInfo = fit.getCommandInfo(self.ID)
|
||||||
|
copyCommandInfo.active = originalCommandInfo.active
|
||||||
|
forceUpdateSavedata(fit)
|
||||||
|
|
||||||
|
for fit in self.projectedFits:
|
||||||
|
copy_ship.__projectedFits[fit.ID] = fit
|
||||||
|
forceUpdateSavedata(fit)
|
||||||
|
copyProjectionInfo = fit.getProjectionInfo(copy_ship.ID)
|
||||||
|
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
||||||
|
copyProjectionInfo.active = originalProjectionInfo.active
|
||||||
|
forceUpdateSavedata(fit)
|
||||||
|
|
||||||
return copy_ship
|
return copy_ship
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from sqlalchemy.orm import validates, reconstructor
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
from math import floor
|
from math import floor
|
||||||
@@ -27,6 +28,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
|||||||
from eos.enum import Enum
|
from eos.enum import Enum
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||||
from eos.saveddata.citadel import Citadel
|
from eos.saveddata.citadel import Citadel
|
||||||
|
from eos.saveddata.mutator import Mutator
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -74,15 +76,31 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
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")
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item, baseItem=None, mutaplasmid=None):
|
||||||
"""Initialize a module from the program"""
|
"""Initialize a module from the program"""
|
||||||
self.__item = item
|
|
||||||
|
self.itemID = item.ID if item is not None else None
|
||||||
|
self.baseItemID = baseItem.ID if baseItem is not None else None
|
||||||
|
self.mutaplasmidID = mutaplasmid.ID if mutaplasmid is not None else None
|
||||||
|
|
||||||
|
if baseItem is not None:
|
||||||
|
# we're working with a mutated module, need to get abyssal module loaded with the base attributes
|
||||||
|
# Note: there may be a better way of doing this, such as a metho on this classe to convert(mutaplamid). This
|
||||||
|
# will require a bit more research though, considering there has never been a need to "swap" out the item of a Module
|
||||||
|
# before, and there may be assumptions taken with regards to the item never changing (pre-calculated / cached results, for example)
|
||||||
|
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
|
||||||
|
self.__baseItem = baseItem
|
||||||
|
self.__mutaplasmid = mutaplasmid
|
||||||
|
else:
|
||||||
|
self.__item = item
|
||||||
|
self.__baseItem = baseItem
|
||||||
|
self.__mutaplasmid = mutaplasmid
|
||||||
|
|
||||||
if item is not None and self.isInvalid:
|
if item is not None and self.isInvalid:
|
||||||
raise ValueError("Passed item is not a Module")
|
raise ValueError("Passed item is not a Module")
|
||||||
|
|
||||||
self.__charge = None
|
self.__charge = None
|
||||||
self.itemID = item.ID if item is not None else None
|
|
||||||
self.projected = False
|
self.projected = False
|
||||||
self.state = State.ONLINE
|
self.state = State.ONLINE
|
||||||
self.build()
|
self.build()
|
||||||
@@ -91,7 +109,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def init(self):
|
def init(self):
|
||||||
"""Initialize a module from the database and validate"""
|
"""Initialize a module from the database and validate"""
|
||||||
self.__item = None
|
self.__item = None
|
||||||
|
self.__baseItem = None
|
||||||
self.__charge = None
|
self.__charge = None
|
||||||
|
self.__mutaplasmid = None
|
||||||
|
|
||||||
# we need this early if module is invalid and returns early
|
# we need this early if module is invalid and returns early
|
||||||
self.__slot = self.dummySlot
|
self.__slot = self.dummySlot
|
||||||
@@ -102,6 +122,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
|
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.baseItemID:
|
||||||
|
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
|
||||||
|
self.__baseItem = eos.db.getItem(self.baseItemID)
|
||||||
|
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
|
||||||
|
if self.__baseItem is None:
|
||||||
|
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
|
||||||
|
return
|
||||||
|
|
||||||
if self.isInvalid:
|
if self.isInvalid:
|
||||||
pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
|
pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
|
||||||
return
|
return
|
||||||
@@ -133,6 +161,18 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||||
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
||||||
self.__slot = self.__calculateSlot(self.__item)
|
self.__slot = self.__calculateSlot(self.__item)
|
||||||
|
|
||||||
|
# Instantiate / remove mutators if this is a mutated module
|
||||||
|
if self.__baseItem:
|
||||||
|
for x in self.mutaplasmid.attributes:
|
||||||
|
attr = self.item.attributes[x.name]
|
||||||
|
id = attr.ID
|
||||||
|
if id not in self.mutators: # create the mutator
|
||||||
|
Mutator(self, attr, attr.value)
|
||||||
|
# @todo: remove attributes that are no longer part of the mutaplasmid.
|
||||||
|
|
||||||
|
self.__itemModifiedAttributes.mutators = self.mutators
|
||||||
|
|
||||||
if self.__charge:
|
if self.__charge:
|
||||||
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
||||||
self.__chargeModifiedAttributes.overrides = self.__charge.overrides
|
self.__chargeModifiedAttributes.overrides = self.__charge.overrides
|
||||||
@@ -162,11 +202,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def isInvalid(self):
|
def isInvalid(self):
|
||||||
|
# todo: validate baseItem as well if it's set.
|
||||||
if self.isEmpty:
|
if self.isEmpty:
|
||||||
return False
|
return False
|
||||||
return self.__item is None or \
|
return self.__item is None or \
|
||||||
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
||||||
self.__item.group.name not in self.SYSTEM_GROUPS)
|
self.__item.group.name not in self.SYSTEM_GROUPS) or \
|
||||||
|
(self.item.isAbyssal and (not self.baseItemID or not self.mutaplasmidID))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isMutated(self):
|
||||||
|
return self.baseItemID or self.mutaplasmidID
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def numCharges(self):
|
def numCharges(self):
|
||||||
@@ -306,6 +352,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def item(self):
|
def item(self):
|
||||||
return self.__item if self.__item != 0 else None
|
return self.__item if self.__item != 0 else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def baseItem(self):
|
||||||
|
return self.__baseItem
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mutaplasmid(self):
|
||||||
|
return self.__mutaplasmid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def charge(self):
|
def charge(self):
|
||||||
return self.__charge if self.__charge != 0 else None
|
return self.__charge if self.__charge != 0 else None
|
||||||
@@ -572,7 +626,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
for i in range(5):
|
for i in range(5):
|
||||||
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
|
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
|
||||||
if itemChargeGroup is not None:
|
if itemChargeGroup is not None:
|
||||||
g = eos.db.getGroup(int(itemChargeGroup), eager=("items.icon", "items.attributes"))
|
g = eos.db.getGroup(int(itemChargeGroup), eager=("items.attributes"))
|
||||||
if g is None:
|
if g is None:
|
||||||
continue
|
continue
|
||||||
for singleItem in g.items:
|
for singleItem in g.items:
|
||||||
@@ -783,9 +837,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if item is None:
|
if item is None:
|
||||||
copy = Module.buildEmpty(self.slot)
|
copy = Module.buildEmpty(self.slot)
|
||||||
else:
|
else:
|
||||||
copy = Module(self.item)
|
copy = Module(self.item, self.baseItem, self.mutaplasmid)
|
||||||
copy.charge = self.charge
|
copy.charge = self.charge
|
||||||
copy.state = self.state
|
copy.state = self.state
|
||||||
|
|
||||||
|
for x in self.mutators.values():
|
||||||
|
Mutator(copy, x.attribute, x.value)
|
||||||
|
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
131
eos/saveddata/mutator.py
Normal file
131
eos/saveddata/mutator.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# ===============================================================================
|
||||||
|
# Copyright (C) 2015 Ryan Holmes
|
||||||
|
#
|
||||||
|
# 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 logbook import Logger
|
||||||
|
|
||||||
|
from sqlalchemy.orm import validates, reconstructor
|
||||||
|
|
||||||
|
import eos.db
|
||||||
|
from eos.eqBase import EqBase
|
||||||
|
|
||||||
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Mutator(EqBase):
|
||||||
|
""" Mutators are the object that represent an attribute override on the module level, in conjunction with
|
||||||
|
mutaplasmids. Each mutated module, when created, is instantiated with a list of these objects, dictated by the
|
||||||
|
mutaplasmid that is used on the base module.
|
||||||
|
|
||||||
|
A note on the different attributes on this object:
|
||||||
|
* attribute: points to the definition of the attribute from dgmattribs.
|
||||||
|
* baseAttribute: points to the attribute defined for the base item (contains the base value with with to mutate)
|
||||||
|
* dynamicAttribute: points to the Mutaplasmid definition of the attribute, including min/max
|
||||||
|
|
||||||
|
This could probably be cleaned up with smarter relationships, but whatever
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, module, attr, value):
|
||||||
|
# this needs to be above module assignment, as assigning the module will add it to the list and it via
|
||||||
|
# relationship and needs this set 4correctly
|
||||||
|
self.attrID = attr.ID
|
||||||
|
|
||||||
|
self.module = module
|
||||||
|
self.moduleID = module.ID
|
||||||
|
|
||||||
|
self.__attr = attr
|
||||||
|
self.build()
|
||||||
|
self.value = value # must run after the build(), because the validator requires build() to run first
|
||||||
|
|
||||||
|
@reconstructor
|
||||||
|
def init(self):
|
||||||
|
self.__attr = None
|
||||||
|
|
||||||
|
if self.attrID:
|
||||||
|
self.__attr = eos.db.getAttributeInfo(self.attrID)
|
||||||
|
if self.__attr is None:
|
||||||
|
pyfalog.error("Attribute (id: {0}) does not exist", self.attrID)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
self.value = self.value # run the validator (to ensure we catch any changed min/max values might CCP release)
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
# try...except here to catch orphaned mutators. Pretty rare, only happens so far if hacking the database
|
||||||
|
# But put it here to remove the module link if it happens, until a better solution can be developed
|
||||||
|
try:
|
||||||
|
# dynamic attribute links to the Mutaplasmids attribute definition for this mutated definition
|
||||||
|
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
|
||||||
|
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
|
||||||
|
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
|
||||||
|
except:
|
||||||
|
self.module = None
|
||||||
|
|
||||||
|
@validates("value")
|
||||||
|
def validator(self, key, val):
|
||||||
|
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||||
|
mod = val / self.baseValue
|
||||||
|
|
||||||
|
if self.minMod <= mod <= self.maxMod:
|
||||||
|
# sweet, all good
|
||||||
|
returnVal = val
|
||||||
|
else:
|
||||||
|
# need to fudge the numbers a bit. Go with the value closest to base
|
||||||
|
if val >= 0:
|
||||||
|
returnVal = min(self.maxValue, max(self.minValue, val))
|
||||||
|
else:
|
||||||
|
returnVal = max(self.maxValue, min(self.minValue, val))
|
||||||
|
|
||||||
|
return returnVal
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isInvalid(self):
|
||||||
|
# @todo: need to test what happens:
|
||||||
|
# 1) if an attribute is removed from the EVE database
|
||||||
|
# 2) if a mutaplasmid does not have the attribute anymore
|
||||||
|
# 3) if a mutaplasmid does not exist (in eve or on the module's item)
|
||||||
|
# Can remove invalid ones in a SQLAlchemy collection class... eventually
|
||||||
|
return self.__attr is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def highIsGood(self):
|
||||||
|
return self.attribute.highIsGood
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minMod(self):
|
||||||
|
return round(self.dynamicAttribute.min, 3)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def maxMod(self):
|
||||||
|
return round(self.dynamicAttribute.max, 3)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def baseValue(self):
|
||||||
|
return self.baseAttribute.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minValue(self):
|
||||||
|
return self.minMod * self.baseAttribute.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def maxValue(self):
|
||||||
|
return self.maxMod * self.baseAttribute.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attribute(self):
|
||||||
|
return self.__attr
|
||||||
@@ -131,7 +131,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
g = eos.db.getGroup("Ship Modifiers", eager=("items.icon", "items.attributes"))
|
g = eos.db.getGroup("Ship Modifiers", eager=("items.attributes"))
|
||||||
for item in g.items:
|
for item in g.items:
|
||||||
# Rely on name detection because race is not reliable
|
# Rely on name detection because race is not reliable
|
||||||
if item.name.lower().startswith(self.item.name.lower()):
|
if item.name.lower().startswith(self.item.name.lower()):
|
||||||
|
|||||||
503
gui/attribute_gauge.py
Normal file
503
gui/attribute_gauge.py
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
import copy
|
||||||
|
import wx
|
||||||
|
import math
|
||||||
|
|
||||||
|
from gui.utils import color as color_utils
|
||||||
|
from gui.utils import draw, anim_effects
|
||||||
|
from service.fit import Fit
|
||||||
|
|
||||||
|
# todo: clean class up. Took from pyfa gauge, has a bunch of extra shit we don't need
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeGauge(wx.Window):
|
||||||
|
def __init__(self, parent, max_range=100, animate=True, leading_edge=True, edge_on_neutral=True, guide_lines=False, size=(-1, 30), *args,
|
||||||
|
**kargs):
|
||||||
|
|
||||||
|
super().__init__(parent, size=size, *args, **kargs)
|
||||||
|
|
||||||
|
self._size = size
|
||||||
|
|
||||||
|
self.guide_lines = guide_lines
|
||||||
|
|
||||||
|
self._border_colour = wx.BLACK
|
||||||
|
self._bar_colour = None
|
||||||
|
self._bar_gradient = None
|
||||||
|
|
||||||
|
self.leading_edge = leading_edge
|
||||||
|
self.edge_on_neutral = edge_on_neutral
|
||||||
|
|
||||||
|
self._border_padding = 0
|
||||||
|
self._max_range = max_range
|
||||||
|
self._value = 0
|
||||||
|
|
||||||
|
self._fraction_digits = 0
|
||||||
|
|
||||||
|
self._timer_id = wx.NewId()
|
||||||
|
self._timer = None
|
||||||
|
|
||||||
|
self._oldValue = 0
|
||||||
|
|
||||||
|
self._animate = animate
|
||||||
|
self._anim_duration = 500
|
||||||
|
self._anim_step = 0
|
||||||
|
self._period = 20
|
||||||
|
self._anim_value = 0
|
||||||
|
self._anim_direction = 0
|
||||||
|
self.anim_effect = anim_effects.OUT_QUAD
|
||||||
|
|
||||||
|
# transition colors used based on how full (or overfilled) the gauge is.
|
||||||
|
self.transition_colors = [
|
||||||
|
(wx.Colour(191, 191, 191), wx.Colour(96, 191, 0)), # < 0-100%
|
||||||
|
(wx.Colour(191, 167, 96), wx.Colour(255, 191, 0)), # < 100-101%
|
||||||
|
(wx.Colour(255, 191, 0), wx.Colour(255, 128, 0)), # < 101-103%
|
||||||
|
(wx.Colour(255, 128, 0), wx.Colour(255, 0, 0)) # < 103-105%
|
||||||
|
]
|
||||||
|
|
||||||
|
self.goodColor = wx.Colour(96, 191, 0)
|
||||||
|
self.badColor = wx.Colour(255, 64, 0)
|
||||||
|
|
||||||
|
self.gradient_effect = -35
|
||||||
|
|
||||||
|
self._percentage = 0
|
||||||
|
self._old_percentage = 0
|
||||||
|
self._show_remaining = False
|
||||||
|
|
||||||
|
self.SetBackgroundColour(wx.Colour(51, 51, 51))
|
||||||
|
|
||||||
|
self._tooltip = wx.ToolTip("0.00/100.00")
|
||||||
|
self.SetToolTip(self._tooltip)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||||
|
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
||||||
|
self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
|
||||||
|
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
|
||||||
|
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
|
||||||
|
|
||||||
|
def OnEraseBackground(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def OnWindowEnter(self, event):
|
||||||
|
self._show_remaining = True
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def OnWindowLeave(self, event):
|
||||||
|
self._show_remaining = False
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def GetBorderColour(self):
|
||||||
|
return self._border_colour
|
||||||
|
|
||||||
|
def SetBorderColour(self, colour):
|
||||||
|
self._border_colour = colour
|
||||||
|
|
||||||
|
def GetBarColour(self):
|
||||||
|
return self._bar_colour
|
||||||
|
|
||||||
|
def SetBarColour(self, colour):
|
||||||
|
self._bar_colour = colour
|
||||||
|
|
||||||
|
def SetFractionDigits(self, digits):
|
||||||
|
self._fraction_digits = digits
|
||||||
|
|
||||||
|
def GetBarGradient(self):
|
||||||
|
if self._bar_gradient is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._bar_gradient[0]
|
||||||
|
|
||||||
|
def SetBarGradient(self, gradient=None):
|
||||||
|
if gradient is None:
|
||||||
|
self._bar_gradient = None
|
||||||
|
else:
|
||||||
|
if not isinstance(gradient, list):
|
||||||
|
self._bar_gradient = [gradient]
|
||||||
|
else:
|
||||||
|
self._bar_gradient = list(gradient)
|
||||||
|
|
||||||
|
def GetBorderPadding(self):
|
||||||
|
return self._border_padding
|
||||||
|
|
||||||
|
def SetBorderPadding(self, padding):
|
||||||
|
self._border_padding = padding
|
||||||
|
|
||||||
|
def GetRange(self):
|
||||||
|
""" Returns the maximum value of the gauge. """
|
||||||
|
return self._max_range
|
||||||
|
|
||||||
|
def Animate(self):
|
||||||
|
if self._animate:
|
||||||
|
if not self._timer:
|
||||||
|
self._timer = wx.Timer(self, self._timer_id)
|
||||||
|
|
||||||
|
self._anim_step = 0
|
||||||
|
self._timer.Start(self._period)
|
||||||
|
else:
|
||||||
|
self._anim_value = self._percentage
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def SetRange(self, range, reinit=False, animate=True):
|
||||||
|
"""
|
||||||
|
Sets the range of the gauge. The gauge length is its
|
||||||
|
value as a proportion of the range.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._max_range == range:
|
||||||
|
return
|
||||||
|
|
||||||
|
# we cannot have a range of zero (laws of physics, etc), so we set it
|
||||||
|
if range <= 0:
|
||||||
|
self._max_range = 0.01
|
||||||
|
else:
|
||||||
|
self._max_range = range
|
||||||
|
|
||||||
|
if reinit is False:
|
||||||
|
self._old_percentage = self._percentage
|
||||||
|
self._percentage = (self._value / self._max_range) * 100
|
||||||
|
else:
|
||||||
|
self._old_percentage = self._percentage
|
||||||
|
self._percentage = 0
|
||||||
|
self._value = 0
|
||||||
|
|
||||||
|
if animate:
|
||||||
|
self.Animate()
|
||||||
|
|
||||||
|
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range if self._max_range > 0.01 else 0))
|
||||||
|
|
||||||
|
def GetValue(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def SetValue(self, value, animate=True):
|
||||||
|
""" Sets the current position of the gauge. """
|
||||||
|
|
||||||
|
print("=" * 20, self._percentage)
|
||||||
|
if self._value == value:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._old_percentage = self._percentage
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
self._percentage = (self._value / self._max_range) * 100
|
||||||
|
|
||||||
|
if animate:
|
||||||
|
self.Animate()
|
||||||
|
|
||||||
|
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range))
|
||||||
|
|
||||||
|
def SetValueRange(self, value, range, reinit=False):
|
||||||
|
""" Set both value and range of the gauge. """
|
||||||
|
range_ = float(range)
|
||||||
|
|
||||||
|
if range_ <= 0:
|
||||||
|
self._max_range = 0.01
|
||||||
|
else:
|
||||||
|
self._max_range = range_
|
||||||
|
|
||||||
|
value = float(value)
|
||||||
|
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
if reinit is False:
|
||||||
|
self._old_percentage = self._percentage
|
||||||
|
self._percentage = (self._value / self._max_range) * 100
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._old_percentage = self._percentage
|
||||||
|
self._percentage = 0
|
||||||
|
|
||||||
|
self.Animate()
|
||||||
|
self._tooltip.SetTip("%.2f/%.2f" %
|
||||||
|
(self._value, self._max_range if float(self._max_range) > 0.01 else 0))
|
||||||
|
|
||||||
|
def OnPaint(self, event):
|
||||||
|
dc = wx.AutoBufferedPaintDC(self)
|
||||||
|
rect = self.GetClientRect()
|
||||||
|
|
||||||
|
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
|
||||||
|
dc.Clear()
|
||||||
|
|
||||||
|
colour = self.GetBackgroundColour()
|
||||||
|
|
||||||
|
dc.SetBrush(wx.Brush(colour))
|
||||||
|
dc.SetPen(wx.Pen(colour))
|
||||||
|
|
||||||
|
dc.DrawRectangle(rect)
|
||||||
|
|
||||||
|
value = self._percentage
|
||||||
|
|
||||||
|
if self._timer:
|
||||||
|
if self._timer.IsRunning():
|
||||||
|
value = self._anim_value
|
||||||
|
|
||||||
|
if self._border_colour:
|
||||||
|
dc.SetPen(wx.Pen(self.GetBorderColour()))
|
||||||
|
dc.DrawRectangle(rect)
|
||||||
|
pad = 1 + self.GetBorderPadding()
|
||||||
|
rect.Deflate(pad, pad)
|
||||||
|
|
||||||
|
if True:
|
||||||
|
# if we have a bar color set, then we will use this
|
||||||
|
colour = self.goodColor if value >= 0 else self.badColor
|
||||||
|
|
||||||
|
is_even = rect.width % 2 == 0
|
||||||
|
|
||||||
|
# the size of half our available drawing area (since we're only working in halves)
|
||||||
|
half = (rect.width / 2)
|
||||||
|
|
||||||
|
# calculate width of bar as a percentage of half the space
|
||||||
|
w = abs(half * (value / 100))
|
||||||
|
w = min(w, half) # Ensure that we don't overshoot our drawing area
|
||||||
|
w = math.ceil(w) # round up to nearest pixel, this ensures that we don't lose representation for sub pixels
|
||||||
|
|
||||||
|
# print("Percentage: {}\t\t\t\t\tValue: {}\t\t\t\t\tWidth: {}\t\t\t\t\tHalf: {}\t\t\t\t\tRect Width: {}".format(
|
||||||
|
# round(self._percentage, 3), round(value,3), w, half, rect.width))
|
||||||
|
|
||||||
|
# set guide_lines every 10 pixels of the main gauge (not including borders)
|
||||||
|
if self.guide_lines:
|
||||||
|
for x in range(1, 20):
|
||||||
|
dc.SetBrush(wx.Brush(wx.LIGHT_GREY))
|
||||||
|
dc.SetPen(wx.Pen(wx.LIGHT_GREY))
|
||||||
|
dc.DrawRectangle(x * 10, 1, 1, rect.height)
|
||||||
|
|
||||||
|
dc.SetBrush(wx.Brush(colour))
|
||||||
|
dc.SetPen(wx.Pen(colour))
|
||||||
|
|
||||||
|
# If we have an even width, we can simply dedicate the middle-most pixels to both sides
|
||||||
|
# However, if there is an odd width, the middle pixel is shared between the left and right gauge
|
||||||
|
|
||||||
|
if value >= 0:
|
||||||
|
padding = (half if is_even else math.ceil(half - 1)) + 1
|
||||||
|
dc.DrawRectangle(padding, 1, w, rect.height)
|
||||||
|
else:
|
||||||
|
padding = half - w + 1 if is_even else math.ceil(half) - (w - 1)
|
||||||
|
dc.DrawRectangle(padding, 1, w, rect.height)
|
||||||
|
|
||||||
|
if self.leading_edge and (self.edge_on_neutral or value != 0):
|
||||||
|
dc.SetPen(wx.Pen(wx.WHITE))
|
||||||
|
dc.SetBrush(wx.Brush(wx.WHITE))
|
||||||
|
|
||||||
|
if value > 0:
|
||||||
|
dc.DrawRectangle(min(padding + w, rect.width), 1, 1, rect.height)
|
||||||
|
else:
|
||||||
|
dc.DrawRectangle(max(padding - 1, 1), 1, 1, rect.height)
|
||||||
|
|
||||||
|
def OnTimer(self, event):
|
||||||
|
old_value = self._old_percentage
|
||||||
|
value = self._percentage
|
||||||
|
start = 0
|
||||||
|
|
||||||
|
# -1 = left direction, 1 = right direction
|
||||||
|
direction = 1 if old_value < value else -1
|
||||||
|
|
||||||
|
end = direction * (value - old_value)
|
||||||
|
|
||||||
|
self._anim_direction = direction
|
||||||
|
step = self.anim_effect(self._anim_step, start, end, self._anim_duration)
|
||||||
|
|
||||||
|
self._anim_step += self._period
|
||||||
|
|
||||||
|
if self._timer_id == event.GetId():
|
||||||
|
stop_timer = False
|
||||||
|
|
||||||
|
if self._anim_step > self._anim_duration:
|
||||||
|
stop_timer = True
|
||||||
|
|
||||||
|
# add new value to the animation if we haven't reached our goal
|
||||||
|
# otherwise, stop animation
|
||||||
|
if direction == 1:
|
||||||
|
if old_value + step < value:
|
||||||
|
self._anim_value = old_value + step
|
||||||
|
else:
|
||||||
|
stop_timer = True
|
||||||
|
else:
|
||||||
|
if old_value - step > value:
|
||||||
|
self._anim_value = old_value - step
|
||||||
|
else:
|
||||||
|
stop_timer = True
|
||||||
|
|
||||||
|
if stop_timer:
|
||||||
|
self._timer.Stop()
|
||||||
|
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import random
|
||||||
|
|
||||||
|
def frange(x, y, jump):
|
||||||
|
while x < y:
|
||||||
|
yield x
|
||||||
|
x += jump
|
||||||
|
|
||||||
|
class MyPanel(wx.Panel):
|
||||||
|
def __init__(self, parent, size=(500, 500)):
|
||||||
|
wx.Panel.__init__(self, parent, size=size)
|
||||||
|
box = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
self.gauge = gauge = AttributeGauge(self, size=(204, 4))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(100)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge11 = gauge = AttributeGauge(self, size=(204, 6))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(100)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge12 = gauge = AttributeGauge(self, size=(204, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(100)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge13 = gauge = AttributeGauge(self, size=(204, 10))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(100)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.value = wx.StaticText(self, label="Text")
|
||||||
|
box.Add(self.value, 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
|
||||||
|
self.btn = wx.Button(self, label="Toggle Timer")
|
||||||
|
box.Add(self.btn, 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
self.btn.Bind(wx.EVT_BUTTON, self.ToggleTimer)
|
||||||
|
|
||||||
|
self.spinCtrl = wx.SpinCtrl(self, min=-10000, max=10000)
|
||||||
|
box.Add(self.spinCtrl, 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
self.spinCtrl.Bind(wx.EVT_SPINCTRL, self.UpdateValue)
|
||||||
|
|
||||||
|
self.m_staticline2 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||||
|
box.Add(self.m_staticline2, 0, wx.EXPAND, 5)
|
||||||
|
|
||||||
|
self.spinCtrl2 = wx.SpinCtrl(self, min=0, max=10000)
|
||||||
|
box.Add(self.spinCtrl2, 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
self.spinCtrl2.Bind(wx.EVT_SPINCTRL, self.UpdateValue2)
|
||||||
|
|
||||||
|
box.Add(wx.StaticText(self, label="Large Even Pixel Test"), 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
|
||||||
|
guide_lines = False
|
||||||
|
|
||||||
|
self.gauge2 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(204, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(2)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge3 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(204, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(-2)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
box.Add(wx.StaticText(self, label="Large Odd Pixel Test"), 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
|
||||||
|
self.gauge4 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(205, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(2)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge5 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(205, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(-2)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
box.Add(wx.StaticText(self, label="Small Even Pixel Test"), 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
|
||||||
|
self.gauge6 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(100, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(75)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge7 = gauge = AttributeGauge(self, guide_lines=guide_lines, size=(100, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(-75)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
box.Add(wx.StaticText(self, label="Small Odd Pixel Test"), 0, wx.ALL | wx.CENTER, 5)
|
||||||
|
|
||||||
|
self.gauge8 = gauge = AttributeGauge(self, guide_lines=guide_lines, max_range=100, size=(101, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(1)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.gauge9 = gauge = AttributeGauge(self, guide_lines=guide_lines, max_range=100, size=(101, 8))
|
||||||
|
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||||
|
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||||
|
gauge.SetValue(-1)
|
||||||
|
gauge.SetFractionDigits(1)
|
||||||
|
box.Add(gauge, 0, wx.ALL | wx.CENTER, 10)
|
||||||
|
|
||||||
|
self.SetSizer(box)
|
||||||
|
self.Layout()
|
||||||
|
|
||||||
|
self.animTimer = wx.Timer(self, wx.NewId())
|
||||||
|
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
||||||
|
|
||||||
|
self.animTimer.Start(1000)
|
||||||
|
|
||||||
|
def ToggleTimer(self, evt):
|
||||||
|
if self.animTimer.IsRunning:
|
||||||
|
self.animTimer.Stop()
|
||||||
|
else:
|
||||||
|
self.animTimer.Start(1000)
|
||||||
|
|
||||||
|
def UpdateValue(self, event):
|
||||||
|
if self.animTimer.IsRunning:
|
||||||
|
self.animTimer.Stop()
|
||||||
|
num = self.spinCtrl.GetValue()
|
||||||
|
self.gauge.SetValue(num)
|
||||||
|
self.gauge11.SetValue(num)
|
||||||
|
self.gauge12.SetValue(num)
|
||||||
|
self.gauge13.SetValue(num)
|
||||||
|
self.value.SetLabel(str(num))
|
||||||
|
|
||||||
|
def UpdateValue2(self, event):
|
||||||
|
num = self.spinCtrl2.GetValue()
|
||||||
|
self.gauge2.SetValue(num)
|
||||||
|
self.gauge3.SetValue(num * -1)
|
||||||
|
self.gauge4.SetValue(num)
|
||||||
|
self.gauge5.SetValue(num * -1)
|
||||||
|
self.gauge6.SetValue(num)
|
||||||
|
self.gauge7.SetValue(num * -1)
|
||||||
|
self.gauge8.SetValue(num)
|
||||||
|
self.gauge9.SetValue(num * -1)
|
||||||
|
|
||||||
|
def OnTimer(self, evt):
|
||||||
|
num = random.randint(-100, 100)
|
||||||
|
self.gauge.SetValue(num)
|
||||||
|
self.gauge11.SetValue(num)
|
||||||
|
self.gauge12.SetValue(num)
|
||||||
|
self.gauge13.SetValue(num)
|
||||||
|
self.value.SetLabel(str(num))
|
||||||
|
|
||||||
|
class Frame(wx.Frame):
|
||||||
|
def __init__(self, title, size=(500, 800)):
|
||||||
|
wx.Frame.__init__(self, None, title=title, size=size)
|
||||||
|
self.statusbar = self.CreateStatusBar()
|
||||||
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
panel = MyPanel(self, size=size)
|
||||||
|
main_sizer.Add(panel)
|
||||||
|
self.SetSizer(main_sizer)
|
||||||
|
|
||||||
|
app = wx.App(redirect=False) # Error messages go to popup window
|
||||||
|
top = Frame("Test Attribute Bar")
|
||||||
|
top.Show()
|
||||||
|
app.MainLoop()
|
||||||
@@ -49,7 +49,7 @@ class BitmapLoader(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def getStaticBitmap(cls, name, parent, location):
|
def getStaticBitmap(cls, name, parent, location):
|
||||||
static = wx.StaticBitmap(parent)
|
static = wx.StaticBitmap(parent)
|
||||||
static.SetBitmap(cls.getBitmap(name, location))
|
static.SetBitmap(cls.getBitmap(name or 0, location))
|
||||||
return static
|
return static
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -118,13 +118,22 @@ class CargoView(d.Display):
|
|||||||
# Gather module information to get position
|
# Gather module information to get position
|
||||||
module = fit.modules[modIdx]
|
module = fit.modules[modIdx]
|
||||||
|
|
||||||
|
if module.item.isAbyssal:
|
||||||
|
dlg = wx.MessageDialog(self,
|
||||||
|
"Moving this Abyssal module to the cargo will convert it to the base module. Do you wish to proceed?",
|
||||||
|
"Confirm", wx.YES_NO | wx.ICON_QUESTION)
|
||||||
|
result = dlg.ShowModal() == wx.ID_YES
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
if dstRow != -1: # we're swapping with cargo
|
if dstRow != -1: # we're swapping with cargo
|
||||||
if mstate.cmdDown: # if copying, append to cargo
|
if mstate.cmdDown: # if copying, append to cargo
|
||||||
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID)
|
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID if not module.item.isAbyssal else module.baseItemID)
|
||||||
else: # else, move / swap
|
else: # else, move / swap
|
||||||
sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, dstRow)
|
sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, dstRow)
|
||||||
else: # dragging to blank spot, append
|
else: # dragging to blank spot, append
|
||||||
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID)
|
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID if not module.item.isAbyssal else module.baseItemID)
|
||||||
|
|
||||||
if not mstate.cmdDown: # if not copying, remove module
|
if not mstate.cmdDown: # if not copying, remove module
|
||||||
sFit.removeModule(self.mainFrame.getActiveFit(), module.position)
|
sFit.removeModule(self.mainFrame.getActiveFit(), module.position)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from service.fit import Fit
|
|||||||
class DummyItem(object):
|
class DummyItem(object):
|
||||||
def __init__(self, txt):
|
def __init__(self, txt):
|
||||||
self.name = txt
|
self.name = txt
|
||||||
self.icon = None
|
self.iconID = None
|
||||||
|
|
||||||
|
|
||||||
class DummyEntry(object):
|
class DummyEntry(object):
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pyfalog = Logger(__name__)
|
|||||||
class DummyItem(object):
|
class DummyItem(object):
|
||||||
def __init__(self, txt):
|
def __init__(self, txt):
|
||||||
self.name = txt
|
self.name = txt
|
||||||
self.icon = None
|
self.iconID = None
|
||||||
|
|
||||||
|
|
||||||
class DummyEntry(object):
|
class DummyEntry(object):
|
||||||
@@ -109,7 +109,7 @@ class ProjectedView(d.Display):
|
|||||||
dstRow, _ = self.HitTest((x, y))
|
dstRow, _ = self.HitTest((x, y))
|
||||||
# Gather module information to get position
|
# Gather module information to get position
|
||||||
module = fit.modules[int(data[1])]
|
module = fit.modules[int(data[1])]
|
||||||
sFit.project(fit.ID, module.item.ID)
|
sFit.project(fit.ID, module)
|
||||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
|
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
|
||||||
elif data[0] == "market":
|
elif data[0] == "market":
|
||||||
sFit = Fit.getInstance()
|
sFit = Fit.getInstance()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class BoosterSideEffect(ContextMenu):
|
|||||||
label = ability.name
|
label = ability.name
|
||||||
id = ContextMenu.nextID()
|
id = ContextMenu.nextID()
|
||||||
self.effectIds[id] = ability
|
self.effectIds[id] = ability
|
||||||
|
|
||||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
||||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||||
return menuItem
|
return menuItem
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class ItemStats(ContextMenu):
|
|||||||
size = wx.DefaultSize
|
size = wx.DefaultSize
|
||||||
pos = wx.DefaultPosition
|
pos = wx.DefaultPosition
|
||||||
ItemStatsDialog(stuff, fullContext, pos, size, maximized)
|
ItemStatsDialog(stuff, fullContext, pos, size, maximized)
|
||||||
lastWnd.closeEvent(None)
|
lastWnd.Close()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ItemStatsDialog(stuff, fullContext)
|
ItemStatsDialog(stuff, fullContext)
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ class ModuleAmmoPicker(ContextMenu):
|
|||||||
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.icon is not None:
|
if charge is not None and charge.iconID is not None:
|
||||||
bitmap = BitmapLoader.getBitmap(charge.icon.iconFile, "icons")
|
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
|
||||||
if bitmap is not None:
|
if bitmap is not None:
|
||||||
item.SetBitmap(bitmap)
|
item.SetBitmap(bitmap)
|
||||||
|
|
||||||
|
|||||||
78
gui/builtinContextMenus/mutaplasmids.py
Normal file
78
gui/builtinContextMenus/mutaplasmids.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from gui.contextMenu import ContextMenu
|
||||||
|
import gui.mainFrame
|
||||||
|
# noinspection PyPackageRequirements
|
||||||
|
import wx
|
||||||
|
import gui.globalEvents as GE
|
||||||
|
from service.fit import Fit
|
||||||
|
from service.settings import ContextMenuSettings
|
||||||
|
|
||||||
|
|
||||||
|
class MutaplasmidCM(ContextMenu):
|
||||||
|
def __init__(self):
|
||||||
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
self.settings = ContextMenuSettings.getInstance()
|
||||||
|
self.eventIDs = {}
|
||||||
|
|
||||||
|
def display(self, srcContext, selection):
|
||||||
|
|
||||||
|
# if not self.settings.get('ammoPattern'):
|
||||||
|
# return False
|
||||||
|
|
||||||
|
if srcContext not in ("fittingModule") or self.mainFrame.getActiveFit() is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
mod = selection[0]
|
||||||
|
if len(mod.item.mutaplasmids) == 0 and not mod.isMutated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getText(self, itmContext, selection):
|
||||||
|
mod = selection[0]
|
||||||
|
return "Apply Mutaplasmid" if not mod.isMutated else "Revert to {}".format(mod.baseItem.name)
|
||||||
|
|
||||||
|
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||||
|
if selection[0].isMutated:
|
||||||
|
return None
|
||||||
|
|
||||||
|
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||||
|
self.skillIds = {}
|
||||||
|
sub = wx.Menu()
|
||||||
|
|
||||||
|
mod = selection[0]
|
||||||
|
|
||||||
|
menu = rootMenu if msw else sub
|
||||||
|
|
||||||
|
for item in mod.item.mutaplasmids:
|
||||||
|
label = item.item.name
|
||||||
|
id = ContextMenu.nextID()
|
||||||
|
self.eventIDs[id] = (item, mod)
|
||||||
|
skillItem = wx.MenuItem(menu, id, label)
|
||||||
|
menu.Bind(wx.EVT_MENU, self.handleMenu, skillItem)
|
||||||
|
sub.Append(skillItem)
|
||||||
|
|
||||||
|
return sub
|
||||||
|
|
||||||
|
def handleMenu(self, event):
|
||||||
|
mutaplasmid, mod = self.eventIDs[event.Id]
|
||||||
|
fit = self.mainFrame.getActiveFit()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
|
||||||
|
# todo: dev out function to switch module to an abyssal module. Also, maybe open item stats here automatically
|
||||||
|
# with the attribute tab set?
|
||||||
|
sFit.convertMutaplasmid(fit, mod.modPosition, mutaplasmid)
|
||||||
|
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit))
|
||||||
|
|
||||||
|
def activate(self, fullContext, selection, i):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
|
||||||
|
mod = selection[0]
|
||||||
|
sFit.changeModule(fitID, mod.modPosition, mod.baseItemID)
|
||||||
|
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||||
|
|
||||||
|
def getBitmap(self, context, selection):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
MutaplasmidCM.register()
|
||||||
@@ -55,7 +55,7 @@ class FitDpsGraph(Graph):
|
|||||||
icons = {}
|
icons = {}
|
||||||
sAttr = Attribute.getInstance()
|
sAttr = Attribute.getInstance()
|
||||||
for key, attrName in self.propertyAttributeMap.items():
|
for key, attrName in self.propertyAttributeMap.items():
|
||||||
iconFile = sAttr.getAttributeInfo(attrName).icon.iconFile
|
iconFile = sAttr.getAttributeInfo(attrName).iconID
|
||||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||||
if bitmap:
|
if bitmap:
|
||||||
icons[key] = bitmap
|
icons[key] = bitmap
|
||||||
|
|||||||
248
gui/builtinItemStatsViews/attributeSlider.py
Normal file
248
gui/builtinItemStatsViews/attributeSlider.py
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import wx
|
||||||
|
import wx.lib.newevent
|
||||||
|
from gui.attribute_gauge import AttributeGauge
|
||||||
|
|
||||||
|
import eos
|
||||||
|
import eos.db
|
||||||
|
|
||||||
|
_ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeSliderChangeEvent:
|
||||||
|
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
|
||||||
|
self.__obj = obj
|
||||||
|
self.__old = old_value
|
||||||
|
self.__new = new_value
|
||||||
|
self.__old_percent = old_percentage
|
||||||
|
self.__new_percent = new_percentage
|
||||||
|
|
||||||
|
def GetObj(self):
|
||||||
|
return self.__obj
|
||||||
|
|
||||||
|
def GetOldValue(self):
|
||||||
|
return self.__old
|
||||||
|
|
||||||
|
def GetValue(self):
|
||||||
|
return self.__new
|
||||||
|
|
||||||
|
def GetOldPercentage(self):
|
||||||
|
return self.__old_percent
|
||||||
|
|
||||||
|
def GetPercentage(self):
|
||||||
|
return self.__new_percent
|
||||||
|
|
||||||
|
Object = property(GetObj)
|
||||||
|
OldValue = property(GetOldValue)
|
||||||
|
Value = property(GetValue)
|
||||||
|
OldPercentage = property(GetOldPercentage)
|
||||||
|
Percentage = property(GetPercentage)
|
||||||
|
|
||||||
|
|
||||||
|
class ValueChanged(_ValueChanged, AttributeSliderChangeEvent):
|
||||||
|
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
|
||||||
|
_ValueChanged.__init__(self)
|
||||||
|
AttributeSliderChangeEvent.__init__(self, obj, old_value, new_value, old_percentage, new_percentage)
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeSlider(wx.Panel):
|
||||||
|
# Slider which abstracts users values from internal values (because the built in slider does not deal with floats
|
||||||
|
# and the like), based on http://wxpython-users.wxwidgets.narkive.com/ekgBzA7u/anyone-ever-thought-of-a-floating-point-slider
|
||||||
|
|
||||||
|
def __init__(self, parent, baseValue, minMod, maxMod, inverse=False, id=-1):
|
||||||
|
wx.Panel.__init__(self, parent, id=id)
|
||||||
|
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.inverse = inverse
|
||||||
|
|
||||||
|
self.base_value = baseValue
|
||||||
|
|
||||||
|
self.UserMinValue = minMod
|
||||||
|
self.UserMaxValue = maxMod
|
||||||
|
|
||||||
|
# The internal slider basically represents the percentage towards the end of the range. It has to be normalized
|
||||||
|
# in this way, otherwise when we start off with a base, if the range is skewed to one side, the base value won't
|
||||||
|
# be centered. We use a range of -100,100 so that we can depend on the SliderValue to contain the percentage
|
||||||
|
# toward one end
|
||||||
|
|
||||||
|
# Additionally, since we want the slider to be accurate to 3 decimal places, we need to blow out the two ends here
|
||||||
|
# (if we have a slider that needs to land on 66.66% towards the right, it will actually be converted to 66%. Se we need it to support 6,666)
|
||||||
|
|
||||||
|
self.SliderMinValue = -100
|
||||||
|
self.SliderMaxValue = 100
|
||||||
|
self.SliderValue = 0
|
||||||
|
|
||||||
|
range = [(self.UserMinValue * self.base_value), (self.UserMaxValue * self.base_value)]
|
||||||
|
|
||||||
|
self.ctrl = wx.SpinCtrlDouble(self, min=min(range), max=max(range))
|
||||||
|
self.ctrl.SetDigits(3)
|
||||||
|
|
||||||
|
self.ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.UpdateValue)
|
||||||
|
|
||||||
|
self.slider = AttributeGauge(self, size=(-1, 8))
|
||||||
|
|
||||||
|
b = 4
|
||||||
|
vsizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
vsizer1.Add(self.ctrl, 0, wx.LEFT | wx.RIGHT | wx.CENTER, b)
|
||||||
|
vsizer1.Add(self.slider, 0, wx.EXPAND | wx.ALL , b)
|
||||||
|
|
||||||
|
self.SetSizerAndFit(vsizer1)
|
||||||
|
self.parent.SetClientSize((500, vsizer1.GetSize()[1]))
|
||||||
|
|
||||||
|
def UpdateValue(self, evt):
|
||||||
|
self.SetValue(self.ctrl.GetValue())
|
||||||
|
evt.Skip()
|
||||||
|
|
||||||
|
def SetValue(self, value, post_event=True):
|
||||||
|
# todo: check this against values that might be 2.5x and whatnot
|
||||||
|
mod = value / self.base_value
|
||||||
|
self.ctrl.SetValue(value)
|
||||||
|
slider_percentage = 0
|
||||||
|
if mod < 1:
|
||||||
|
modEnd = self.UserMinValue
|
||||||
|
slider_percentage = (1 - mod) / (1 - modEnd) * -100
|
||||||
|
elif mod > 1:
|
||||||
|
modEnd = self.UserMaxValue
|
||||||
|
slider_percentage = ((mod - 1) / (modEnd - 1)) * 100
|
||||||
|
# print(slider_percentage)
|
||||||
|
if self.inverse:
|
||||||
|
slider_percentage *= -1
|
||||||
|
self.slider.SetValue(slider_percentage)
|
||||||
|
if post_event:
|
||||||
|
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage))
|
||||||
|
|
||||||
|
|
||||||
|
class TestAttributeSlider(wx.Frame):
|
||||||
|
|
||||||
|
def __init__(self, parent, id):
|
||||||
|
title = 'Slider...'
|
||||||
|
pos = wx.DefaultPosition
|
||||||
|
size = wx.DefaultSize
|
||||||
|
sty = wx.DEFAULT_FRAME_STYLE
|
||||||
|
wx.Frame.__init__(self, parent, id, title, pos, size, sty)
|
||||||
|
|
||||||
|
self.panel = AttributeSlider(self, -50, 0.8, 1.5, False)
|
||||||
|
self.panel.Bind(EVT_VALUE_CHANGED, self.thing)
|
||||||
|
self.panel.SetValue(-55)
|
||||||
|
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
|
||||||
|
|
||||||
|
def OnCloseWindow(self, event):
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
def thing(self, evt):
|
||||||
|
print("thing")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = wx.App()
|
||||||
|
frame = TestAttributeSlider(None, wx.ID_ANY)
|
||||||
|
frame.Show()
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
|
||||||
|
# class AttributeSliderDEV(wx.Panel):
|
||||||
|
# # Slider which abstracts users values from internal values (because the built in slider does not deal with floats
|
||||||
|
# # and the like), based on http://wxpython-users.wxwidgets.narkive.com/ekgBzA7u/anyone-ever-thought-of-a-floating-point-slider
|
||||||
|
#
|
||||||
|
# def __init__(self, parent, baseValue, minMod, maxMod):
|
||||||
|
# wx.Panel.__init__(self, parent)
|
||||||
|
#
|
||||||
|
# self.parent = parent
|
||||||
|
#
|
||||||
|
# self.base_value = baseValue
|
||||||
|
#
|
||||||
|
# self.UserMinValue = minMod
|
||||||
|
# self.UserMaxValue = maxMod
|
||||||
|
#
|
||||||
|
# # The internal slider basically represents the percentage towards the end of the range. It has to be normalized
|
||||||
|
# # in this way, otherwise when we start off with a base, if the range is skewed to one side, the base value won't
|
||||||
|
# # be centered. We use a range of -100,100 so that we can depend on the SliderValue to contain the percentage
|
||||||
|
# # toward one end
|
||||||
|
#
|
||||||
|
# # Additionally, since we want the slider to be accurate to 3 decimal places, we need to blow out the two ends here
|
||||||
|
# # (if we have a slider that needs to land on 66.66% towards the right, it will actually be converted to 66%. Se we need it to support 6,666)
|
||||||
|
#
|
||||||
|
# self.SliderMinValue = -100_000
|
||||||
|
# self.SliderMaxValue = 100_000
|
||||||
|
# self.SliderValue = 0
|
||||||
|
#
|
||||||
|
# self.statxt1 = wx.StaticText(self, wx.ID_ANY, 'left',
|
||||||
|
# style=wx.ST_NO_AUTORESIZE | wx.ALIGN_LEFT)
|
||||||
|
# self.statxt2 = wx.StaticText(self, wx.ID_ANY, 'middle',
|
||||||
|
# style=wx.ST_NO_AUTORESIZE | wx.ALIGN_CENTRE)
|
||||||
|
# self.statxt3 = wx.StaticText(self, wx.ID_ANY, 'right',
|
||||||
|
# style=wx.ST_NO_AUTORESIZE | wx.ALIGN_RIGHT)
|
||||||
|
#
|
||||||
|
# self.statxt1.SetLabel("{0:.3f}".format(self.UserMinValue * self.base_value))
|
||||||
|
# self.statxt1.SetToolTip("{0:+f}%".format((1-self.UserMinValue)*-100))
|
||||||
|
# self.statxt2.SetLabel("{0:.3f}".format(self.base_value))
|
||||||
|
# self.statxt3.SetLabel("{0:.3f}".format(self.UserMaxValue * self.base_value))
|
||||||
|
# self.statxt3.SetToolTip("{0:+f}%".format((1-self.UserMaxValue)*-100))
|
||||||
|
#
|
||||||
|
# self.slider = wx.Slider(
|
||||||
|
# self, wx.ID_ANY,
|
||||||
|
# self.SliderValue,
|
||||||
|
# self.SliderMinValue,
|
||||||
|
# self.SliderMaxValue,
|
||||||
|
# style=wx.SL_HORIZONTAL)
|
||||||
|
#
|
||||||
|
# self.slider.SetTickFreq((self.SliderMaxValue - self.SliderMinValue) / 15)
|
||||||
|
#
|
||||||
|
# self.slider.Bind(wx.EVT_SCROLL, self.OnScroll)
|
||||||
|
#
|
||||||
|
# b = 20
|
||||||
|
# hsizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
# hsizer1.Add(self.statxt1, 1, wx.RIGHT, b)
|
||||||
|
# hsizer1.Add(self.statxt2, 1, wx.LEFT | wx.RIGHT, b)
|
||||||
|
# hsizer1.Add(self.statxt3, 1, wx.LEFT, b)
|
||||||
|
#
|
||||||
|
# b = 4
|
||||||
|
# vsizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
# vsizer1.Add(hsizer1, 0, wx.EXPAND | wx.ALL, b)
|
||||||
|
# vsizer1.Add(self.slider, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, b)
|
||||||
|
#
|
||||||
|
# self.SetSizerAndFit(vsizer1)
|
||||||
|
# self.parent.SetClientSize((500, vsizer1.GetSize()[1]))
|
||||||
|
#
|
||||||
|
# def OnScroll(self, event):
|
||||||
|
# self.CalculateUserValue()
|
||||||
|
#
|
||||||
|
# def SetValue(self, value):
|
||||||
|
# # todo: check this against values that might be 2.5x and whatnot
|
||||||
|
# mod = value / self.base_value
|
||||||
|
# slider_percentage = 0
|
||||||
|
# if mod < 1:
|
||||||
|
# modEnd = -1 * self.UserMinValue
|
||||||
|
# slider_percentage = (modEnd / mod) * 10_000
|
||||||
|
# elif mod > 1:
|
||||||
|
# modEnd = self.UserMaxValue
|
||||||
|
# slider_percentage = ((mod-1)/(modEnd-1)) * 100_000
|
||||||
|
#
|
||||||
|
# self.slider.SetValue(slider_percentage)
|
||||||
|
# self.CalculateUserValue()
|
||||||
|
#
|
||||||
|
# def CalculateUserValue(self):
|
||||||
|
# self.SliderValue = self.slider.GetValue()
|
||||||
|
#
|
||||||
|
# mod = 1
|
||||||
|
#
|
||||||
|
# # The slider value tells us when mod we're going to use, depending on its sign
|
||||||
|
# if self.SliderValue < 0:
|
||||||
|
# mod = self.UserMinValue
|
||||||
|
# elif self.SliderValue > 0:
|
||||||
|
# mod = self.UserMaxValue
|
||||||
|
#
|
||||||
|
# # Get the slider value percentage as an absolute value
|
||||||
|
# slider_mod = abs(self.SliderValue/1_000) / 100
|
||||||
|
#
|
||||||
|
# # Gets our new mod by use the slider's percentage to determine where in the spectrum it is
|
||||||
|
# new_mod = mod + ((1 - mod) - ((1 - mod) * slider_mod))
|
||||||
|
#
|
||||||
|
# # Modifies our base value, to get out modified value
|
||||||
|
# newValue = new_mod * self.base_value
|
||||||
|
#
|
||||||
|
# if mod == 1:
|
||||||
|
# self.statxt2.SetLabel("{0:.3f}".format(newValue))
|
||||||
|
# else:
|
||||||
|
# self.statxt2.SetLabel("{0:.3f} ({1:+.3f})".format(newValue, newValue - self.base_value, ))
|
||||||
|
# self.statxt2.SetToolTip("{0:+f}%".format(new_mod*100))
|
||||||
@@ -237,16 +237,16 @@ class ItemAffectedBy(wx.Panel):
|
|||||||
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
|
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
|
||||||
|
|
||||||
if attrInfo:
|
if attrInfo:
|
||||||
if attrInfo.icon is not None:
|
if attrInfo.iconID is not None:
|
||||||
iconFile = attrInfo.icon.iconFile
|
iconFile = attrInfo.iconID
|
||||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||||
if icon is None:
|
if icon is None:
|
||||||
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
||||||
attrIcon = self.imageList.Add(icon)
|
attrIcon = self.imageList.Add(icon)
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
|
|
||||||
if self.showRealNames:
|
if self.showRealNames:
|
||||||
display = attrName
|
display = attrName
|
||||||
@@ -267,8 +267,8 @@ class ItemAffectedBy(wx.Panel):
|
|||||||
|
|
||||||
if afflictorType == Ship:
|
if afflictorType == Ship:
|
||||||
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
||||||
elif item.icon:
|
elif item.iconID:
|
||||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||||
else:
|
else:
|
||||||
itemIcon = -1
|
itemIcon = -1
|
||||||
@@ -373,8 +373,8 @@ class ItemAffectedBy(wx.Panel):
|
|||||||
counter = len(afflictors)
|
counter = len(afflictors)
|
||||||
if afflictorType == Ship:
|
if afflictorType == Ship:
|
||||||
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
||||||
elif item.icon:
|
elif item.iconID:
|
||||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||||
else:
|
else:
|
||||||
itemIcon = -1
|
itemIcon = -1
|
||||||
@@ -398,17 +398,17 @@ class ItemAffectedBy(wx.Panel):
|
|||||||
displayName = attrInfo.displayName if attrInfo else ""
|
displayName = attrInfo.displayName if attrInfo else ""
|
||||||
|
|
||||||
if attrInfo:
|
if attrInfo:
|
||||||
if attrInfo.icon is not None:
|
if attrInfo.iconID is not None:
|
||||||
iconFile = attrInfo.icon.iconFile
|
iconFile = attrInfo.iconID
|
||||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||||
if icon is None:
|
if icon is None:
|
||||||
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
||||||
|
|
||||||
attrIcon = self.imageList.Add(icon)
|
attrIcon = self.imageList.Add(icon)
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
|
|
||||||
penalized = ""
|
penalized = ""
|
||||||
if '*' in attrModifier:
|
if '*' in attrModifier:
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import wx
|
|||||||
from .helpers import AutoListCtrl
|
from .helpers import AutoListCtrl
|
||||||
|
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from service.market import Market
|
|
||||||
from service.attribute import Attribute
|
|
||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +84,8 @@ class ItemParams(wx.Panel):
|
|||||||
def RefreshValues(self, event):
|
def RefreshValues(self, event):
|
||||||
self._fetchValues()
|
self._fetchValues()
|
||||||
self.UpdateList()
|
self.UpdateList()
|
||||||
event.Skip()
|
if event:
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def ToggleViewMode(self, event):
|
def ToggleViewMode(self, event):
|
||||||
self.toggleView *= -1
|
self.toggleView *= -1
|
||||||
@@ -174,7 +173,12 @@ class ItemParams(wx.Panel):
|
|||||||
info = self.attrInfo.get(name)
|
info = self.attrInfo.get(name)
|
||||||
att = self.attrValues[name]
|
att = self.attrValues[name]
|
||||||
|
|
||||||
valDefault = getattr(info, "value", None)
|
# If we're working with a stuff object, we should get the original value from our getBaseAttrValue function,
|
||||||
|
# which will return the value with respect to the effective base (with mutators / overrides in place)
|
||||||
|
valDefault = getattr(info, "value", None) # Get default value from attribute
|
||||||
|
if self.stuff is not None:
|
||||||
|
# if it's a stuff, overwrite default (with fallback to current value)
|
||||||
|
valDefault = self.stuff.getBaseAttrValue(name, valDefault)
|
||||||
valueDefault = valDefault if valDefault is not None else att
|
valueDefault = valDefault if valDefault is not None else att
|
||||||
|
|
||||||
val = getattr(att, "value", None)
|
val = getattr(att, "value", None)
|
||||||
@@ -189,8 +193,8 @@ class ItemParams(wx.Panel):
|
|||||||
attrName += " ({})".format(info.ID)
|
attrName += " ({})".format(info.ID)
|
||||||
|
|
||||||
if info:
|
if info:
|
||||||
if info.icon is not None:
|
if info.iconID is not None:
|
||||||
iconFile = info.icon.iconFile
|
iconFile = info.iconID
|
||||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||||
|
|
||||||
if icon is None:
|
if icon is None:
|
||||||
@@ -198,9 +202,9 @@ class ItemParams(wx.Panel):
|
|||||||
|
|
||||||
attrIcon = self.imageList.Add(icon)
|
attrIcon = self.imageList.Add(icon)
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
else:
|
else:
|
||||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||||
|
|
||||||
index = self.paramList.InsertItem(self.paramList.GetItemCount(), attrName, attrIcon)
|
index = self.paramList.InsertItem(self.paramList.GetItemCount(), attrName, attrIcon)
|
||||||
idNameMap[idCount] = attrName
|
idNameMap[idCount] = attrName
|
||||||
@@ -210,14 +214,14 @@ class ItemParams(wx.Panel):
|
|||||||
if self.toggleView != 1:
|
if self.toggleView != 1:
|
||||||
valueUnit = str(value)
|
valueUnit = str(value)
|
||||||
elif info and info.unit:
|
elif info and info.unit:
|
||||||
valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
|
valueUnit = self.FormatValue(*info.unit.TranslateValue(value))
|
||||||
else:
|
else:
|
||||||
valueUnit = formatAmount(value, 3, 0, 0)
|
valueUnit = formatAmount(value, 3, 0, 0)
|
||||||
|
|
||||||
if self.toggleView != 1:
|
if self.toggleView != 1:
|
||||||
valueUnitDefault = str(valueDefault)
|
valueUnitDefault = str(valueDefault)
|
||||||
elif info and info.unit:
|
elif info and info.unit:
|
||||||
valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name)
|
valueUnitDefault = self.FormatValue(*info.unit.TranslateValue(valueDefault))
|
||||||
else:
|
else:
|
||||||
valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
|
valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
|
||||||
|
|
||||||
@@ -232,44 +236,11 @@ class ItemParams(wx.Panel):
|
|||||||
self.Layout()
|
self.Layout()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def TranslateValueUnit(value, unitName, unitDisplayName):
|
def FormatValue(value, unit):
|
||||||
def itemIDCallback():
|
"""Formats a value / unit combination into a string
|
||||||
item = Market.getInstance().getItem(value)
|
@todo: move this to a more central location, since this is also used in the item mutator panel"""
|
||||||
return "%s (%d)" % (item.name, value) if item is not None else str(value)
|
if isinstance(value, (int, float)):
|
||||||
|
fvalue = formatAmount(value, 3, 0, 0)
|
||||||
def groupIDCallback():
|
|
||||||
group = Market.getInstance().getGroup(value)
|
|
||||||
return "%s (%d)" % (group.name, value) if group is not None else str(value)
|
|
||||||
|
|
||||||
def attributeIDCallback():
|
|
||||||
if not value: # some attributes come through with a value of 0? See #1387
|
|
||||||
return "%d" % (value)
|
|
||||||
attribute = Attribute.getInstance().getAttributeInfo(value)
|
|
||||||
return "%s (%d)" % (attribute.name.capitalize(), value)
|
|
||||||
|
|
||||||
trans = {
|
|
||||||
"Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName),
|
|
||||||
"Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName),
|
|
||||||
"Modifier Percent" : (
|
|
||||||
lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName),
|
|
||||||
"Volume" : (lambda: value, "m\u00B3"),
|
|
||||||
"Sizeclass" : (lambda: value, ""),
|
|
||||||
"Absolute Percent" : (lambda: (value * 100), unitName),
|
|
||||||
"Milliseconds" : (lambda: value / 1000.0, unitName),
|
|
||||||
"typeID" : (itemIDCallback, ""),
|
|
||||||
"groupID" : (groupIDCallback, ""),
|
|
||||||
"attributeID" : (attributeIDCallback, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
override = trans.get(unitDisplayName)
|
|
||||||
if override is not None:
|
|
||||||
v = override[0]()
|
|
||||||
if isinstance(v, str):
|
|
||||||
fvalue = v
|
|
||||||
elif isinstance(v, (int, float)):
|
|
||||||
fvalue = formatAmount(v, 3, 0, 0)
|
|
||||||
else:
|
|
||||||
fvalue = v
|
|
||||||
return "%s %s" % (fvalue, override[1])
|
|
||||||
else:
|
else:
|
||||||
return "%s %s" % (formatAmount(value, 3, 0), unitName)
|
fvalue = value
|
||||||
|
return "%s %s" % (fvalue, unit)
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ class ItemDependents(wx.Panel):
|
|||||||
child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId)
|
child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId)
|
||||||
for item in items:
|
for item in items:
|
||||||
|
|
||||||
if item.icon:
|
if item.iconID:
|
||||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||||
else:
|
else:
|
||||||
itemIcon = -1
|
itemIcon = -1
|
||||||
|
|||||||
169
gui/builtinItemStatsViews/itemMutator.py
Normal file
169
gui/builtinItemStatsViews/itemMutator.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# noinspection PyPackageRequirements
|
||||||
|
import wx
|
||||||
|
|
||||||
|
from service.fit import Fit
|
||||||
|
from .attributeSlider import AttributeSlider, EVT_VALUE_CHANGED
|
||||||
|
|
||||||
|
import gui.mainFrame
|
||||||
|
from gui.contextMenu import ContextMenu
|
||||||
|
from .itemAttributes import ItemParams
|
||||||
|
from gui.bitmap_loader import BitmapLoader
|
||||||
|
import gui.globalEvents as GE
|
||||||
|
import random
|
||||||
|
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemMutator(wx.Panel):
|
||||||
|
|
||||||
|
def __init__(self, parent, stuff, item):
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
self.stuff = stuff
|
||||||
|
self.item = item
|
||||||
|
self.timer = None
|
||||||
|
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||||
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
self.goodColor = wx.Colour(96, 191, 0)
|
||||||
|
self.badColor = wx.Colour(255, 64, 0)
|
||||||
|
|
||||||
|
self.event_mapping = {}
|
||||||
|
|
||||||
|
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||||
|
baseValueFormated = m.attribute.unit.TranslateValue(m.baseValue)[0]
|
||||||
|
valueFormated = m.attribute.unit.TranslateValue(m.value)[0]
|
||||||
|
slider = AttributeSlider(self, baseValueFormated, m.minMod, m.maxMod, not m.highIsGood)
|
||||||
|
slider.SetValue(valueFormated, False)
|
||||||
|
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
|
||||||
|
self.event_mapping[slider] = m
|
||||||
|
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
|
||||||
|
# create array for the two ranges
|
||||||
|
min_t = [round(m.minValue, 3), m.minMod, None]
|
||||||
|
max_t = [round(m.maxValue, 3), m.maxMod, None]
|
||||||
|
|
||||||
|
# Then we need to determine if it's better than original, which will be the color
|
||||||
|
min_t[2] = min_t[1] < 1 if not m.highIsGood else 1 < min_t[1]
|
||||||
|
max_t[2] = max_t[1] < 1 if not m.highIsGood else 1 < max_t[1]
|
||||||
|
|
||||||
|
# Lastly, we need to determine which range value is "worse" (left side) or "better" (right side)
|
||||||
|
if (m.highIsGood and min_t[1] > max_t[1]) or (not m.highIsGood and min_t[1] < max_t[1]):
|
||||||
|
better_range = min_t
|
||||||
|
else:
|
||||||
|
better_range = max_t
|
||||||
|
|
||||||
|
if (m.highIsGood and max_t[1] < min_t[1]) or (not m.highIsGood and max_t[1] > min_t[1]):
|
||||||
|
worse_range = max_t
|
||||||
|
else:
|
||||||
|
worse_range = min_t
|
||||||
|
#
|
||||||
|
# print("{}: \nHigh is good: {}".format(m.attribute.displayName, m.attribute.highIsGood))
|
||||||
|
# print("Value {}".format(m.baseValue))
|
||||||
|
#
|
||||||
|
# print(min_t)
|
||||||
|
# print(max_t)
|
||||||
|
# print(better_range)
|
||||||
|
# print(worse_range)
|
||||||
|
|
||||||
|
font = parent.GetFont()
|
||||||
|
font.SetWeight(wx.BOLD)
|
||||||
|
|
||||||
|
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
|
||||||
|
|
||||||
|
displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName)
|
||||||
|
displayName.SetFont(font)
|
||||||
|
|
||||||
|
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
|
||||||
|
|
||||||
|
range_low = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(worse_range[0])))
|
||||||
|
range_low.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
|
||||||
|
|
||||||
|
range_high = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(better_range[0])))
|
||||||
|
range_high.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
|
||||||
|
|
||||||
|
headingSizer.Add(range_low, 0, wx.ALL | wx.EXPAND, 0)
|
||||||
|
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, " ─ "), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
|
||||||
|
headingSizer.Add(range_high, 0, wx.RIGHT | wx.EXPAND, 10)
|
||||||
|
|
||||||
|
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
|
mainSizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10)
|
||||||
|
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
|
mainSizer.AddStretchSpacer()
|
||||||
|
|
||||||
|
self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||||
|
mainSizer.Add(self.m_staticline, 0, wx.EXPAND)
|
||||||
|
|
||||||
|
bSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
|
||||||
|
self.refreshBtn = wx.Button(self, wx.ID_ANY, "Reset defaults", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
|
bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
self.refreshBtn.Bind(wx.EVT_BUTTON, self.resetMutatedValues)
|
||||||
|
|
||||||
|
self.randomBtn = wx.Button(self, wx.ID_ANY, "Random stats", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
|
bSizer.Add(self.randomBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
self.randomBtn.Bind(wx.EVT_BUTTON, self.randomMutatedValues)
|
||||||
|
|
||||||
|
mainSizer.Add(bSizer, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 0)
|
||||||
|
|
||||||
|
self.SetSizer(mainSizer)
|
||||||
|
self.Layout()
|
||||||
|
|
||||||
|
def changeMutatedValue(self, evt):
|
||||||
|
m = self.event_mapping[evt.Object]
|
||||||
|
value = evt.Value
|
||||||
|
value = m.attribute.unit.ComplicateValue(value)
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
|
||||||
|
sFit.changeMutatedValue(m, value)
|
||||||
|
if self.timer:
|
||||||
|
self.timer.Stop()
|
||||||
|
self.timer = None
|
||||||
|
|
||||||
|
for x in self.Parent.Children:
|
||||||
|
if isinstance(x, ItemParams):
|
||||||
|
x.RefreshValues(None)
|
||||||
|
break
|
||||||
|
self.timer = wx.CallLater(1000, self.callLater)
|
||||||
|
|
||||||
|
def resetMutatedValues(self, evt):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
|
||||||
|
for slider, m in self.event_mapping.items():
|
||||||
|
value = sFit.changeMutatedValue(m, m.baseValue)
|
||||||
|
value = m.attribute.unit.TranslateValue(value)[0]
|
||||||
|
slider.SetValue(value)
|
||||||
|
|
||||||
|
evt.Skip()
|
||||||
|
|
||||||
|
def randomMutatedValues(self, evt):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
|
||||||
|
for slider, m in self.event_mapping.items():
|
||||||
|
value = random.uniform(m.minValue, m.maxValue)
|
||||||
|
value = sFit.changeMutatedValue(m, value)
|
||||||
|
value = m.attribute.unit.TranslateValue(value)[0]
|
||||||
|
slider.SetValue(value)
|
||||||
|
|
||||||
|
evt.Skip()
|
||||||
|
|
||||||
|
def callLater(self):
|
||||||
|
self.timer = None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
|
||||||
|
# recalc the fit that this module affects. This is not necessarily the currently active fit
|
||||||
|
sFit.refreshFit(self.activeFit)
|
||||||
|
|
||||||
|
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
activeFit = mainFrame.getActiveFit()
|
||||||
|
|
||||||
|
if activeFit != self.activeFit:
|
||||||
|
# if we're no longer on the fit this module is affecting, simulate a "switch fit" so that the active fit
|
||||||
|
# can be recalculated (if needed)
|
||||||
|
sFit.switchFit(activeFit)
|
||||||
|
|
||||||
|
# Send signal to GUI to update stats with current active fit
|
||||||
|
wx.PostEvent(mainFrame, GE.FitChanged(fitID=activeFit))
|
||||||
@@ -38,6 +38,10 @@ class PFGeneralPref(PreferenceView):
|
|||||||
0)
|
0)
|
||||||
mainSizer.Add(self.cbGlobalChar, 0, wx.ALL | wx.EXPAND, 5)
|
mainSizer.Add(self.cbGlobalChar, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
|
self.cbDefaultCharImplants = wx.CheckBox(panel, wx.ID_ANY, "Use character implants by default for new fits",
|
||||||
|
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
|
mainSizer.Add(self.cbDefaultCharImplants, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
self.cbGlobalDmgPattern = wx.CheckBox(panel, wx.ID_ANY, "Use global damage pattern", wx.DefaultPosition,
|
self.cbGlobalDmgPattern = wx.CheckBox(panel, wx.ID_ANY, "Use global damage pattern", wx.DefaultPosition,
|
||||||
wx.DefaultSize, 0)
|
wx.DefaultSize, 0)
|
||||||
mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5)
|
mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
@@ -119,6 +123,7 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.sFit = Fit.getInstance()
|
self.sFit = Fit.getInstance()
|
||||||
|
|
||||||
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
|
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
|
||||||
|
self.cbDefaultCharImplants.SetValue(self.sFit.serviceFittingOptions["useCharacterImplantsByDefault"])
|
||||||
self.cbGlobalDmgPattern.SetValue(self.sFit.serviceFittingOptions["useGlobalDamagePattern"])
|
self.cbGlobalDmgPattern.SetValue(self.sFit.serviceFittingOptions["useGlobalDamagePattern"])
|
||||||
self.cbFitColorSlots.SetValue(self.sFit.serviceFittingOptions["colorFitBySlot"] or False)
|
self.cbFitColorSlots.SetValue(self.sFit.serviceFittingOptions["colorFitBySlot"] or False)
|
||||||
self.cbRackSlots.SetValue(self.sFit.serviceFittingOptions["rackSlots"] or False)
|
self.cbRackSlots.SetValue(self.sFit.serviceFittingOptions["rackSlots"] or False)
|
||||||
@@ -136,6 +141,7 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.intDelay.SetValue(self.sFit.serviceFittingOptions["marketSearchDelay"])
|
self.intDelay.SetValue(self.sFit.serviceFittingOptions["marketSearchDelay"])
|
||||||
|
|
||||||
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
||||||
|
self.cbDefaultCharImplants.Bind(wx.EVT_CHECKBOX, self.OnCBDefaultCharImplantsStateChange)
|
||||||
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
||||||
self.cbFitColorSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalColorBySlot)
|
self.cbFitColorSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalColorBySlot)
|
||||||
self.cbRackSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackSlots)
|
self.cbRackSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackSlots)
|
||||||
@@ -187,6 +193,10 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.sFit.serviceFittingOptions["useGlobalCharacter"] = self.cbGlobalChar.GetValue()
|
self.sFit.serviceFittingOptions["useGlobalCharacter"] = self.cbGlobalChar.GetValue()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
def OnCBDefaultCharImplantsStateChange(self, event):
|
||||||
|
self.sFit.serviceFittingOptions["useCharacterImplantsByDefault"] = self.cbDefaultCharImplants.GetValue()
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def OnCBGlobalDmgPatternStateChange(self, event):
|
def OnCBGlobalDmgPatternStateChange(self, event):
|
||||||
self.sFit.serviceFittingOptions["useGlobalDamagePattern"] = self.cbGlobalDmgPattern.GetValue()
|
self.sFit.serviceFittingOptions["useGlobalDamagePattern"] = self.cbGlobalDmgPattern.GetValue()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
class FitItem(SFItem.SFBrowserItem):
|
class FitItem(SFItem.SFBrowserItem):
|
||||||
def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cnc's avatar", 0, 0, None), shipID=None,
|
def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cnc's avatar", 0, 0, None), shipID=None,
|
||||||
itemData=None,
|
itemData=None, graphicID=None,
|
||||||
id=wx.ID_ANY, pos=wx.DefaultPosition,
|
id=wx.ID_ANY, pos=wx.DefaultPosition,
|
||||||
size=(0, 40), style=0):
|
size=(0, 40), style=0):
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class FitItem(SFItem.SFBrowserItem):
|
|||||||
self.deleted = False
|
self.deleted = False
|
||||||
|
|
||||||
if shipID:
|
if shipID:
|
||||||
self.shipBmp = BitmapLoader.getBitmap(str(shipID), "renders")
|
self.shipBmp = BitmapLoader.getBitmap(str(graphicID), "renders")
|
||||||
|
|
||||||
if not self.shipBmp:
|
if not self.shipBmp:
|
||||||
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class ShipItem(SFItem.SFBrowserItem):
|
class ShipItem(SFItem.SFBrowserItem):
|
||||||
def __init__(self, parent, shipID=None, shipFittingInfo=("Test", "TestTrait", 2), itemData=None,
|
def __init__(self, parent, shipID=None, shipFittingInfo=("Test", "TestTrait", 2), itemData=None, graphicID=None,
|
||||||
id=wx.ID_ANY, pos=wx.DefaultPosition,
|
id=wx.ID_ANY, pos=wx.DefaultPosition,
|
||||||
size=(0, 40), style=0):
|
size=(0, 40), style=0):
|
||||||
SFItem.SFBrowserItem.__init__(self, parent, size=size)
|
SFItem.SFBrowserItem.__init__(self, parent, size=size)
|
||||||
@@ -36,8 +36,8 @@ class ShipItem(SFItem.SFBrowserItem):
|
|||||||
self.fontSmall = wx.Font(fonts.SMALL, wx.SWISS, wx.NORMAL, wx.NORMAL)
|
self.fontSmall = wx.Font(fonts.SMALL, wx.SWISS, wx.NORMAL, wx.NORMAL)
|
||||||
|
|
||||||
self.shipBmp = None
|
self.shipBmp = None
|
||||||
if shipID:
|
if graphicID:
|
||||||
self.shipBmp = BitmapLoader.getBitmap(str(shipID), "renders")
|
self.shipBmp = BitmapLoader.getBitmap(str(graphicID), "renders")
|
||||||
if not self.shipBmp:
|
if not self.shipBmp:
|
||||||
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class AmmoIcon(ViewColumn):
|
|||||||
if stuff.charge is None:
|
if stuff.charge is None:
|
||||||
return -1
|
return -1
|
||||||
else:
|
else:
|
||||||
iconFile = stuff.charge.icon.iconFile if stuff.charge.icon else ""
|
iconFile = stuff.charge.iconID if stuff.charge.iconID else ""
|
||||||
if iconFile:
|
if iconFile:
|
||||||
return self.fittingView.imageList.GetImageIndex(iconFile, "icons")
|
return self.fittingView.imageList.GetImageIndex(iconFile, "icons")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class AttributeDisplay(ViewColumn):
|
|||||||
iconFile = "pg_small"
|
iconFile = "pg_small"
|
||||||
iconType = "gui"
|
iconType = "gui"
|
||||||
else:
|
else:
|
||||||
iconFile = info.icon.iconFile if info.icon else None
|
iconFile = info.iconID
|
||||||
iconType = "icons"
|
iconType = "icons"
|
||||||
if iconFile:
|
if iconFile:
|
||||||
self.imageId = fittingView.imageList.GetImageIndex(iconFile, iconType)
|
self.imageId = fittingView.imageList.GetImageIndex(iconFile, iconType)
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ class BaseIcon(ViewColumn):
|
|||||||
return self.fittingView.imageList.GetImageIndex("slot_%s_small" % Slot.getName(stuff.slot).lower(),
|
return self.fittingView.imageList.GetImageIndex("slot_%s_small" % Slot.getName(stuff.slot).lower(),
|
||||||
"gui")
|
"gui")
|
||||||
else:
|
else:
|
||||||
return self.loadIconFile(stuff.item.icon.iconFile if stuff.item.icon else "")
|
return self.loadIconFile(stuff.item.iconID or "")
|
||||||
|
|
||||||
item = getattr(stuff, "item", stuff)
|
item = getattr(stuff, "item", stuff)
|
||||||
return self.loadIconFile(item.icon.iconFile if item.icon else "")
|
return self.loadIconFile(item.iconID)
|
||||||
|
|
||||||
def loadIconFile(self, iconFile):
|
def loadIconFile(self, iconFile):
|
||||||
if iconFile:
|
if iconFile:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class MaxRange(ViewColumn):
|
|||||||
info = sAttr.getAttributeInfo("maxRange")
|
info = sAttr.getAttributeInfo("maxRange")
|
||||||
self.info = info
|
self.info = info
|
||||||
if params["showIcon"]:
|
if params["showIcon"]:
|
||||||
iconFile = info.icon.iconFile if info.icon else None
|
iconFile = info.iconID
|
||||||
if iconFile:
|
if iconFile:
|
||||||
self.imageId = fittingView.imageList.GetImageIndex(iconFile, "icons")
|
self.imageId = fittingView.imageList.GetImageIndex(iconFile, "icons")
|
||||||
self.bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
self.bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PropertyDisplay(ViewColumn):
|
|||||||
iconFile = "pg_small"
|
iconFile = "pg_small"
|
||||||
iconType = "gui"
|
iconType = "gui"
|
||||||
else:
|
else:
|
||||||
iconFile = info.icon.iconFile if info.icon else None
|
iconFile = info.iconID if info.icon else None
|
||||||
iconType = "icons"
|
iconType = "icons"
|
||||||
if iconFile:
|
if iconFile:
|
||||||
self.imageId = fittingView.imageList.GetImageIndex(iconFile, iconType)
|
self.imageId = fittingView.imageList.GetImageIndex(iconFile, iconType)
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ class FittingView(d.Display):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if getattr(mod2, "modPosition") is not None:
|
if getattr(mod2, "modPosition") is not None:
|
||||||
if clone and mod2.isEmpty:
|
if clone and mod2.isEmpty and mod1.getModifiedItemAttr("maxGroupFitted", 0) < 1.0:
|
||||||
sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
|
sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
|
||||||
else:
|
else:
|
||||||
sFit.swapModules(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
|
sFit.swapModules(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
|
||||||
@@ -741,7 +741,7 @@ class FittingView(d.Display):
|
|||||||
# noinspection PyPropertyAccess
|
# noinspection PyPropertyAccess
|
||||||
def MakeSnapshot(self, maxColumns=1337):
|
def MakeSnapshot(self, maxColumns=1337):
|
||||||
if self.FVsnapshot:
|
if self.FVsnapshot:
|
||||||
del self.FVsnapshot
|
self.FVsnapshot = None
|
||||||
|
|
||||||
tbmp = wx.Bitmap(16, 16)
|
tbmp = wx.Bitmap(16, 16)
|
||||||
tdc = wx.MemoryDC()
|
tdc = wx.MemoryDC()
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class BaseImplantEditorView(wx.Panel):
|
|||||||
currentMktGrp = sMkt.getMarketGroup(tree.GetItemData(parent))
|
currentMktGrp = sMkt.getMarketGroup(tree.GetItemData(parent))
|
||||||
items = sMkt.getItemsByMarketGroup(currentMktGrp)
|
items = sMkt.getItemsByMarketGroup(currentMktGrp)
|
||||||
for item in items:
|
for item in items:
|
||||||
iconId = self.addMarketViewImage(item.icon.iconFile)
|
iconId = self.addMarketViewImage(item.iconID)
|
||||||
tree.AppendItem(parent, item.name, iconId, data=item)
|
tree.AppendItem(parent, item.name, iconId, data=item)
|
||||||
|
|
||||||
tree.SortChildren(parent)
|
tree.SortChildren(parent)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from gui.bitmap_loader import BitmapLoader
|
|||||||
from gui.utils import draw
|
from gui.utils import draw
|
||||||
from gui.utils import color as color_utils
|
from gui.utils import color as color_utils
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
|
from gui.utils import fonts
|
||||||
|
|
||||||
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
|
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
|
||||||
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
|
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
|
||||||
@@ -357,7 +358,7 @@ class _TabRenderer:
|
|||||||
self.tab_bitmap = None
|
self.tab_bitmap = None
|
||||||
self.tab_back_bitmap = None
|
self.tab_back_bitmap = None
|
||||||
self.padding = 4
|
self.padding = 4
|
||||||
self.font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
self.font = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||||
|
|
||||||
self.tab_img = img
|
self.tab_img = img
|
||||||
self.position = (0, 0) # Not used internally for rendering - helper for tab container
|
self.position = (0, 0) # Not used internally for rendering - helper for tab container
|
||||||
@@ -1322,7 +1323,7 @@ class PFNotebookPagePreview(wx.Frame):
|
|||||||
self.padding = 15
|
self.padding = 15
|
||||||
self.transp = 0
|
self.transp = 0
|
||||||
|
|
||||||
hfont = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
hfont = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||||
self.SetFont(hfont)
|
self.SetFont(hfont)
|
||||||
|
|
||||||
tx, ty = self.GetTextExtent(self.title)
|
tx, ty = self.GetTextExtent(self.title)
|
||||||
@@ -1384,7 +1385,7 @@ class PFNotebookPagePreview(wx.Frame):
|
|||||||
mdc.SetBackground(wx.Brush(color))
|
mdc.SetBackground(wx.Brush(color))
|
||||||
mdc.Clear()
|
mdc.Clear()
|
||||||
|
|
||||||
font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
font = wx.Font(11, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||||
mdc.SetFont(font)
|
mdc.SetFont(font)
|
||||||
|
|
||||||
x, y = mdc.GetTextExtent(self.title)
|
x, y = mdc.GetTextExtent(self.title)
|
||||||
|
|||||||
@@ -208,5 +208,6 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
|
|||||||
fighterAbilities,
|
fighterAbilities,
|
||||||
boosterSideEffects,
|
boosterSideEffects,
|
||||||
commandFits,
|
commandFits,
|
||||||
tabbedFits
|
tabbedFits,
|
||||||
|
mutaplasmids,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,19 +29,21 @@ class CopySelectDialog(wx.Dialog):
|
|||||||
copyFormatDna = 3
|
copyFormatDna = 3
|
||||||
copyFormatEsi = 4
|
copyFormatEsi = 4
|
||||||
copyFormatMultiBuy = 5
|
copyFormatMultiBuy = 5
|
||||||
|
copyFormatEfs = 6
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1),
|
wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1),
|
||||||
style=wx.DEFAULT_DIALOG_STYLE)
|
style=wx.DEFAULT_DIALOG_STYLE)
|
||||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "CREST", "MultiBuy"]
|
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy", "EFS"]
|
||||||
copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
|
copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
|
||||||
CopySelectDialog.copyFormatEftImps: "EFT text format",
|
CopySelectDialog.copyFormatEftImps: "EFT text format",
|
||||||
CopySelectDialog.copyFormatXml: "EVE native XML format",
|
CopySelectDialog.copyFormatXml: "EVE native XML format",
|
||||||
CopySelectDialog.copyFormatDna: "A one-line text format",
|
CopySelectDialog.copyFormatDna: "A one-line text format",
|
||||||
CopySelectDialog.copyFormatEsi: "A JSON format used for EVE CREST",
|
CopySelectDialog.copyFormatEsi: "A JSON format used for ESI",
|
||||||
CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format"}
|
CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format",
|
||||||
|
CopySelectDialog.copyFormatEfs: "JSON data format used by EFS"}
|
||||||
selector = wx.RadioBox(self, wx.ID_ANY, label="Copy to the clipboard using:", choices=copyFormats,
|
selector = wx.RadioBox(self, wx.ID_ANY, label="Copy to the clipboard using:", choices=copyFormats,
|
||||||
style=wx.RA_SPECIFY_ROWS)
|
style=wx.RA_SPECIFY_ROWS)
|
||||||
selector.Bind(wx.EVT_RADIOBOX, self.Selected)
|
selector.Bind(wx.EVT_RADIOBOX, self.Selected)
|
||||||
|
|||||||
@@ -157,6 +157,19 @@ class EveFittings(wx.Frame):
|
|||||||
self.statusbar.SetStatusText(msg)
|
self.statusbar.SetStatusText(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ESIServerExceptionHandler(object):
|
||||||
|
def __init__(self, parentWindow, ex):
|
||||||
|
dlg = wx.MessageDialog(parentWindow,
|
||||||
|
"There was an issue starting up the localized server, try setting "
|
||||||
|
"Login Authentication Method to Manual by going to Preferences -> EVE SS0 -> "
|
||||||
|
"Login Authentication Method. If this doesn't fix the problem please file an "
|
||||||
|
"issue on Github.",
|
||||||
|
"Add Character Error",
|
||||||
|
wx.OK | wx.ICON_ERROR)
|
||||||
|
dlg.ShowModal()
|
||||||
|
pyfalog.error(ex)
|
||||||
|
|
||||||
|
|
||||||
class ESIExceptionHandler(object):
|
class ESIExceptionHandler(object):
|
||||||
# todo: make this a generate excetpion handler for all calls
|
# todo: make this a generate excetpion handler for all calls
|
||||||
def __init__(self, parentWindow, ex):
|
def __init__(self, parentWindow, ex):
|
||||||
@@ -325,10 +338,12 @@ class SsoCharacterMgmt(wx.Dialog):
|
|||||||
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||||
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
|
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
|
||||||
|
|
||||||
@staticmethod
|
def addChar(self, event):
|
||||||
def addChar(event):
|
try:
|
||||||
sEsi = Esi.getInstance()
|
sEsi = Esi.getInstance()
|
||||||
sEsi.login()
|
sEsi.login()
|
||||||
|
except Exception as ex:
|
||||||
|
ESIServerExceptionHandler(self, ex)
|
||||||
|
|
||||||
def delChar(self, event):
|
def delChar(self, event):
|
||||||
item = self.lcCharacters.GetFirstSelected()
|
item = self.lcCharacters.GetFirstSelected()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user