Merge branch 'issue/1645'
1
.gitignore
vendored
@@ -122,3 +122,4 @@ gitversion
|
||||
/.version
|
||||
*.swp
|
||||
|
||||
*.fsdbinary
|
||||
|
||||
@@ -24,8 +24,8 @@ saveInRoot = False
|
||||
|
||||
# Version data
|
||||
|
||||
version = "2.1.1"
|
||||
tag = "Stable"
|
||||
version = "2.2.0b1"
|
||||
tag = "git"
|
||||
expansionName = "Into the Abyss"
|
||||
expansionVersion = "1.1"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
@@ -78,10 +78,10 @@ sd_lock = threading.RLock()
|
||||
|
||||
# Import all the definitions for all our database stuff
|
||||
# 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
|
||||
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
|
||||
# noinspection PyPep8
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__all__ = ["attribute", "category", "effect", "group", "metaData",
|
||||
"icon", "item", "marketGroup", "metaGroup", "unit", "alphaClones"]
|
||||
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
|
||||
"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 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,
|
||||
Column("value", Float),
|
||||
@@ -38,7 +38,7 @@ attributes_table = Table("dgmattribs", gamedata_meta,
|
||||
Column("published", Boolean),
|
||||
Column("displayName", String),
|
||||
Column("highIsGood", Boolean),
|
||||
Column("iconID", Integer, ForeignKey("icons.iconID")),
|
||||
Column("iconID", Integer),
|
||||
Column("unitID", Integer, ForeignKey("dgmunits.unitID")))
|
||||
|
||||
mapper(Attribute, typeattributes_table,
|
||||
@@ -46,7 +46,6 @@ mapper(Attribute, typeattributes_table,
|
||||
|
||||
mapper(AttributeInfo, attributes_table,
|
||||
properties={
|
||||
"icon" : relation(Icon),
|
||||
"unit" : relation(Unit),
|
||||
"ID" : synonym("attributeID"),
|
||||
"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 eos.db import gamedata_meta
|
||||
from eos.gamedata import Category, Icon
|
||||
from eos.gamedata import Category
|
||||
|
||||
categories_table = Table("invcategories", gamedata_meta,
|
||||
Column("categoryID", Integer, primary_key=True),
|
||||
Column("categoryName", String),
|
||||
Column("description", String),
|
||||
Column("published", Boolean),
|
||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
||||
Column("iconID", Integer))
|
||||
|
||||
mapper(Category, categories_table,
|
||||
properties={
|
||||
"icon" : relation(Icon),
|
||||
"ID" : synonym("categoryID"),
|
||||
"name" : synonym("categoryName"),
|
||||
"description": deferred(categories_table.c.description)
|
||||
|
||||
66
eos/db/gamedata/dynamicAttributes.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# ===============================================================================
|
||||
# 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.orm import relation, mapper, synonym, deferred
|
||||
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
|
||||
|
||||
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,
|
||||
Column("groupID", Integer, primary_key=True),
|
||||
@@ -29,12 +29,11 @@ groups_table = Table("invgroups", gamedata_meta,
|
||||
Column("description", String),
|
||||
Column("published", Boolean),
|
||||
Column("categoryID", Integer, ForeignKey("invcategories.categoryID")),
|
||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
||||
Column("iconID", Integer))
|
||||
|
||||
mapper(Group, groups_table,
|
||||
properties={
|
||||
"category" : relation(Category, backref="groups"),
|
||||
"icon" : relation(Icon),
|
||||
"category" : relation(Category, backref=backref("groups", cascade="all,delete")),
|
||||
"ID" : synonym("groupID"),
|
||||
"name" : synonym("groupName"),
|
||||
"description": deferred(groups_table.c.description)
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
|
||||
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table, Float
|
||||
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 eos.db.gamedata.effect import typeeffects_table
|
||||
|
||||
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,
|
||||
Column("typeID", Integer, primary_key=True),
|
||||
@@ -37,7 +38,8 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("capacity", Float),
|
||||
Column("published", Boolean),
|
||||
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))
|
||||
|
||||
from .metaGroup import metatypes_table # noqa
|
||||
@@ -45,8 +47,7 @@ from .traits import traits_table # noqa
|
||||
|
||||
mapper(Item, items_table,
|
||||
properties={
|
||||
"group" : relation(Group, backref="items"),
|
||||
"icon" : relation(Icon),
|
||||
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
|
||||
"_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')),
|
||||
"metaGroup" : relation(MetaType,
|
||||
@@ -57,7 +58,13 @@ mapper(Item, items_table,
|
||||
"description" : deferred(items_table.c.description),
|
||||
"traits" : relation(Traits,
|
||||
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")
|
||||
|
||||
@@ -21,7 +21,7 @@ from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
|
||||
from sqlalchemy.orm import relation, mapper, synonym, deferred
|
||||
|
||||
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,
|
||||
Column("marketGroupID", Integer, primary_key=True),
|
||||
@@ -30,14 +30,13 @@ marketgroups_table = Table("invmarketgroups", gamedata_meta,
|
||||
Column("hasTypes", Boolean),
|
||||
Column("parentGroupID", Integer,
|
||||
ForeignKey("invmarketgroups.marketGroupID", initially="DEFERRED", deferrable=True)),
|
||||
Column("iconID", Integer, ForeignKey("icons.iconID")))
|
||||
Column("iconID", Integer))
|
||||
|
||||
mapper(MarketGroup, marketgroups_table,
|
||||
properties={
|
||||
"items" : relation(Item, backref="marketGroup"),
|
||||
"parent" : relation(MarketGroup, backref="children",
|
||||
remote_side=[marketgroups_table.c.marketGroupID]),
|
||||
"icon" : relation(Icon),
|
||||
"ID" : synonym("marketGroupID"),
|
||||
"name" : synonym("marketGroupName"),
|
||||
"description": deferred(marketgroups_table.c.description)
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
# 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.inspection import inspect
|
||||
|
||||
import eos.config
|
||||
from eos.db import gamedata_session
|
||||
from eos.db.gamedata.metaGroup import metatypes_table, items_table
|
||||
from eos.db.gamedata.group import groups_table
|
||||
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 = {}
|
||||
configVal = getattr(eos.config, "gamedataCache", None)
|
||||
@@ -97,6 +98,35 @@ def getItem(lookfor, eager=None):
|
||||
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")
|
||||
def getItems(lookfor, eager=None):
|
||||
"""
|
||||
|
||||
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__ = [
|
||||
"character",
|
||||
"fit",
|
||||
"mutator",
|
||||
"module",
|
||||
"user",
|
||||
"skill",
|
||||
|
||||
@@ -18,17 +18,21 @@
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
from sqlalchemy.orm import relation, mapper
|
||||
import datetime
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.saveddata.fit import Fit
|
||||
|
||||
modules_table = Table("modules", saveddata_meta,
|
||||
Column("ID", Integer, primary_key=True),
|
||||
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
|
||||
Column("itemID", Integer, nullable=True),
|
||||
Column("baseItemID", Integer, nullable=True),
|
||||
Column("mutaplasmidID", Integer, nullable=True),
|
||||
Column("dummySlot", Integer, nullable=True, default=None),
|
||||
Column("chargeID", Integer),
|
||||
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"'))
|
||||
|
||||
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,19 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Column, String, Integer, Table
|
||||
from sqlalchemy.orm import mapper, synonym, deferred
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime, Float
|
||||
from sqlalchemy.orm import mapper
|
||||
import datetime
|
||||
|
||||
from eos.db import gamedata_meta
|
||||
from eos.gamedata import Icon
|
||||
from eos.db import saveddata_meta
|
||||
from eos.saveddata.mutator import Mutator
|
||||
|
||||
icons_table = Table("icons", gamedata_meta,
|
||||
Column("iconID", Integer, primary_key=True),
|
||||
Column("description", String),
|
||||
Column("iconFile", String))
|
||||
mutator_table = Table("mutators", saveddata_meta,
|
||||
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, index=True),
|
||||
Column("attrID", Integer, primary_key=True, index=True),
|
||||
Column("value", Float, nullable=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
)
|
||||
|
||||
mapper(Icon, icons_table,
|
||||
properties={
|
||||
"ID" : synonym("iconID"),
|
||||
"description": deferred(icons_table.c.description)
|
||||
})
|
||||
mapper(Mutator, mutator_table)
|
||||
@@ -512,10 +512,17 @@ class Group(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class Icon(EqBase):
|
||||
class DynamicItem(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class DynamicItemAttribute(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class DynamicItemItem(EqBase):
|
||||
pass
|
||||
|
||||
class MarketGroup(EqBase):
|
||||
def __repr__(self):
|
||||
return "MarketGroup(ID={}, name={}, parent={}) at {}".format(
|
||||
|
||||
@@ -33,6 +33,14 @@ class ItemAttrShortcut(object):
|
||||
|
||||
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):
|
||||
def getModifiedChargeAttr(self, key, default=0):
|
||||
@@ -59,8 +67,10 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
self.__modified = {}
|
||||
# Affected by entities
|
||||
self.__affectedBy = {}
|
||||
# Overrides
|
||||
# Overrides (per item)
|
||||
self.__overrides = {}
|
||||
# Mutators (per module)
|
||||
self.__mutators = {}
|
||||
# Dictionaries for various value modification types
|
||||
self.__forced = {}
|
||||
self.__preAssigns = {}
|
||||
@@ -100,6 +110,14 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
def overrides(self, 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):
|
||||
# Check if we have final calculated value
|
||||
key_value = self.__modified.get(key)
|
||||
@@ -128,14 +146,16 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
del self.__intermediary[key]
|
||||
|
||||
def getOriginal(self, key, default=None):
|
||||
val = None
|
||||
if self.overrides_enabled and self.overrides:
|
||||
val = self.overrides.get(key, None)
|
||||
else:
|
||||
val = None
|
||||
val = self.overrides.get(key, val)
|
||||
|
||||
# mutators are overriden by overrides. x_x
|
||||
val = self.mutators.get(key, val)
|
||||
|
||||
if val is None:
|
||||
if self.original:
|
||||
val = self.original.get(key, None)
|
||||
val = self.original.get(key, val)
|
||||
|
||||
if val is None and val != default:
|
||||
val = default
|
||||
|
||||
@@ -27,6 +27,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.enum import Enum
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -74,15 +75,31 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
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"""
|
||||
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:
|
||||
raise ValueError("Passed item is not a Module")
|
||||
|
||||
self.__charge = None
|
||||
self.itemID = item.ID if item is not None else None
|
||||
|
||||
self.projected = False
|
||||
self.state = State.ONLINE
|
||||
self.build()
|
||||
@@ -91,6 +108,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def init(self):
|
||||
"""Initialize a module from the database and validate"""
|
||||
self.__item = None
|
||||
self.__baseItem = None
|
||||
self.__charge = None
|
||||
|
||||
# we need this early if module is invalid and returns early
|
||||
@@ -102,6 +120,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
|
||||
return
|
||||
|
||||
if self.baseItemID:
|
||||
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
|
||||
self.__baseItem = eos.db.getItem(self.baseItemID)
|
||||
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
|
||||
if self.__baseItem is None:
|
||||
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
|
||||
return
|
||||
|
||||
if self.isInvalid:
|
||||
pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
|
||||
return
|
||||
@@ -133,10 +159,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
|
||||
# Instantiate / remove mutators if this is a mutated module
|
||||
if self.__baseItem:
|
||||
for x in self.mutaplasmid.attributes:
|
||||
attr = self.item.attributes[x.name]
|
||||
id = attr.ID
|
||||
if id not in self.mutators: # create the mutator
|
||||
Mutator(self, attr, attr.value)
|
||||
# @todo: remove attributes that are no longer part of the mutaplasmid.
|
||||
|
||||
self.__itemModifiedAttributes.mutators = self.mutators
|
||||
|
||||
if self.__charge:
|
||||
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
||||
self.__chargeModifiedAttributes.overrides = self.__charge.overrides
|
||||
|
||||
|
||||
@classmethod
|
||||
def buildEmpty(cls, slot):
|
||||
empty = Module(None)
|
||||
@@ -162,12 +201,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def isInvalid(self):
|
||||
# todo: validate baseItem as well if it's set.
|
||||
if self.isEmpty:
|
||||
return False
|
||||
return self.__item is None or \
|
||||
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
||||
self.__item.group.name not in self.SYSTEM_GROUPS)
|
||||
|
||||
@property
|
||||
def isMutated(self):
|
||||
return self.baseItemID or self.mutaplasmidID
|
||||
|
||||
@property
|
||||
def numCharges(self):
|
||||
if self.charge is None:
|
||||
@@ -306,6 +350,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def item(self):
|
||||
return self.__item if self.__item != 0 else None
|
||||
|
||||
@property
|
||||
def baseItem(self):
|
||||
return self.__baseItem if self.__baseItem != 0 else None # what?
|
||||
|
||||
@property
|
||||
def mutaplasmid(self):
|
||||
return self.__mutaplasmid if self.__mutaplasmid != 0 else None
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
return self.__charge if self.__charge != 0 else None
|
||||
@@ -572,7 +624,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
for i in range(5):
|
||||
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), 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:
|
||||
continue
|
||||
for singleItem in g.items:
|
||||
|
||||
130
eos/saveddata/mutator.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# ===============================================================================
|
||||
# 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
|
||||
returnVal = min(self.maxValue, max(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 self.dynamicAttribute.min
|
||||
|
||||
@property
|
||||
def maxMod(self):
|
||||
return self.dynamicAttribute.max
|
||||
|
||||
@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
|
||||
|
||||
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:
|
||||
# Rely on name detection because race is not reliable
|
||||
if item.name.lower().startswith(self.item.name.lower()):
|
||||
|
||||
504
gui/attribute_gauge.py
Normal file
@@ -0,0 +1,504 @@
|
||||
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)
|
||||
|
||||
font = wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
|
||||
|
||||
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()
|
||||
@@ -35,7 +35,7 @@ from service.fit import Fit
|
||||
class DummyItem(object):
|
||||
def __init__(self, txt):
|
||||
self.name = txt
|
||||
self.icon = None
|
||||
self.iconID = None
|
||||
|
||||
|
||||
class DummyEntry(object):
|
||||
|
||||
@@ -39,7 +39,7 @@ pyfalog = Logger(__name__)
|
||||
class DummyItem(object):
|
||||
def __init__(self, txt):
|
||||
self.name = txt
|
||||
self.icon = None
|
||||
self.iconID = None
|
||||
|
||||
|
||||
class DummyEntry(object):
|
||||
|
||||
@@ -34,6 +34,7 @@ class BoosterSideEffect(ContextMenu):
|
||||
label = ability.name
|
||||
id = ContextMenu.nextID()
|
||||
self.effectIds[id] = ability
|
||||
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
@@ -117,8 +117,8 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
item = wx.MenuItem(menu, id_, name)
|
||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
||||
item.charge = charge
|
||||
if charge is not None and charge.icon is not None:
|
||||
bitmap = BitmapLoader.getBitmap(charge.icon.iconFile, "icons")
|
||||
if charge is not None and charge.iconID is not None:
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
|
||||
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 = {}
|
||||
sAttr = Attribute.getInstance()
|
||||
for key, attrName in self.propertyAttributeMap.items():
|
||||
iconFile = sAttr.getAttributeInfo(attrName).icon.iconFile
|
||||
iconFile = sAttr.getAttributeInfo(attrName).iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
if bitmap:
|
||||
icons[key] = bitmap
|
||||
|
||||
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
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.icon is not None:
|
||||
iconFile = attrInfo.icon.iconFile
|
||||
if attrInfo.iconID is not None:
|
||||
iconFile = attrInfo.iconID
|
||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
if icon is None:
|
||||
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
|
||||
if self.showRealNames:
|
||||
display = attrName
|
||||
@@ -267,8 +267,8 @@ class ItemAffectedBy(wx.Panel):
|
||||
|
||||
if afflictorType == Ship:
|
||||
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
||||
elif item.icon:
|
||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
||||
elif item.iconID:
|
||||
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
@@ -373,8 +373,8 @@ class ItemAffectedBy(wx.Panel):
|
||||
counter = len(afflictors)
|
||||
if afflictorType == Ship:
|
||||
itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui"))
|
||||
elif item.icon:
|
||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
||||
elif item.iconID:
|
||||
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
@@ -398,17 +398,17 @@ class ItemAffectedBy(wx.Panel):
|
||||
displayName = attrInfo.displayName if attrInfo else ""
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.icon is not None:
|
||||
iconFile = attrInfo.icon.iconFile
|
||||
if attrInfo.iconID is not None:
|
||||
iconFile = attrInfo.iconID
|
||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
if icon is None:
|
||||
icon = BitmapLoader.getBitmap("transparent16x16", "gui")
|
||||
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
|
||||
penalized = ""
|
||||
if '*' in attrModifier:
|
||||
|
||||
@@ -174,7 +174,12 @@ class ItemParams(wx.Panel):
|
||||
info = self.attrInfo.get(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
|
||||
|
||||
val = getattr(att, "value", None)
|
||||
@@ -189,8 +194,8 @@ class ItemParams(wx.Panel):
|
||||
attrName += " ({})".format(info.ID)
|
||||
|
||||
if info:
|
||||
if info.icon is not None:
|
||||
iconFile = info.icon.iconFile
|
||||
if info.iconID is not None:
|
||||
iconFile = info.iconID
|
||||
icon = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
|
||||
if icon is None:
|
||||
@@ -198,9 +203,9 @@ class ItemParams(wx.Panel):
|
||||
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons"))
|
||||
attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons"))
|
||||
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)
|
||||
idNameMap[idCount] = attrName
|
||||
|
||||
@@ -44,8 +44,8 @@ class ItemDependents(wx.Panel):
|
||||
child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId)
|
||||
for item in items:
|
||||
|
||||
if item.icon:
|
||||
bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons")
|
||||
if item.iconID:
|
||||
bitmap = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
|
||||
120
gui/builtinItemStatsViews/itemMutator.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# 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 gui.bitmap_loader import BitmapLoader
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
|
||||
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):
|
||||
slider = AttributeSlider(self, m.baseValue, m.minMod, m.maxMod, not m.highIsGood)
|
||||
slider.SetValue(m.value, 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, "{} {}".format(worse_range[0], m.attribute.unit.displayName))
|
||||
range_low.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
|
||||
|
||||
range_high = wx.StaticText(self, wx.ID_ANY, "{} {}".format(better_range[0], m.attribute.unit.displayName))
|
||||
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.saveBtn = wx.Button(self, wx.ID_ANY, "Save Attributes", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
bSizer.Add(self.saveBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||
|
||||
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
|
||||
sFit = Fit.getInstance()
|
||||
|
||||
sFit.changeMutatedValue(m, value)
|
||||
if self.timer:
|
||||
self.timer.Stop()
|
||||
self.timer = None
|
||||
self.timer = wx.CallLater(1000, self.callLater)
|
||||
|
||||
def callLater(self):
|
||||
self.timer = None
|
||||
print("recalc fit")
|
||||
sFit = Fit.getInstance()
|
||||
sFit.refreshFit(self.activeFit)
|
||||
# todo BUG: if fit is not currently active, this causes the changed fit to show...?
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitID=self.activeFit))
|
||||
|
||||
@@ -23,7 +23,7 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class FitItem(SFItem.SFBrowserItem):
|
||||
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,
|
||||
size=(0, 40), style=0):
|
||||
|
||||
@@ -51,7 +51,7 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
self.deleted = False
|
||||
|
||||
if shipID:
|
||||
self.shipBmp = BitmapLoader.getBitmap(str(shipID), "renders")
|
||||
self.shipBmp = BitmapLoader.getBitmap(str(graphicID), "renders")
|
||||
|
||||
if not self.shipBmp:
|
||||
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
||||
|
||||
@@ -18,7 +18,7 @@ pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
size=(0, 40), style=0):
|
||||
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.shipBmp = None
|
||||
if shipID:
|
||||
self.shipBmp = BitmapLoader.getBitmap(str(shipID), "renders")
|
||||
if graphicID:
|
||||
self.shipBmp = BitmapLoader.getBitmap(str(graphicID), "renders")
|
||||
if not self.shipBmp:
|
||||
self.shipBmp = BitmapLoader.getBitmap("ship_no_image_big", "gui")
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class AmmoIcon(ViewColumn):
|
||||
if stuff.charge is None:
|
||||
return -1
|
||||
else:
|
||||
iconFile = stuff.charge.icon.iconFile if stuff.charge.icon else ""
|
||||
iconFile = stuff.charge.iconID if stuff.charge.iconID else ""
|
||||
if iconFile:
|
||||
return self.fittingView.imageList.GetImageIndex(iconFile, "icons")
|
||||
else:
|
||||
|
||||
@@ -41,7 +41,7 @@ class AttributeDisplay(ViewColumn):
|
||||
iconFile = "pg_small"
|
||||
iconType = "gui"
|
||||
else:
|
||||
iconFile = info.icon.iconFile if info.icon else None
|
||||
iconFile = info.iconID
|
||||
iconType = "icons"
|
||||
if iconFile:
|
||||
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(),
|
||||
"gui")
|
||||
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)
|
||||
return self.loadIconFile(item.icon.iconFile if item.icon else "")
|
||||
return self.loadIconFile(item.iconID)
|
||||
|
||||
def loadIconFile(self, iconFile):
|
||||
if iconFile:
|
||||
|
||||
@@ -40,7 +40,7 @@ class MaxRange(ViewColumn):
|
||||
info = sAttr.getAttributeInfo("maxRange")
|
||||
self.info = info
|
||||
if params["showIcon"]:
|
||||
iconFile = info.icon.iconFile if info.icon else None
|
||||
iconFile = info.iconID
|
||||
if iconFile:
|
||||
self.imageId = fittingView.imageList.GetImageIndex(iconFile, "icons")
|
||||
self.bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
|
||||
@@ -41,7 +41,7 @@ class PropertyDisplay(ViewColumn):
|
||||
iconFile = "pg_small"
|
||||
iconType = "gui"
|
||||
else:
|
||||
iconFile = info.icon.iconFile if info.icon else None
|
||||
iconFile = info.iconID if info.icon else None
|
||||
iconType = "icons"
|
||||
if iconFile:
|
||||
self.imageId = fittingView.imageList.GetImageIndex(iconFile, iconType)
|
||||
|
||||
@@ -156,7 +156,7 @@ class BaseImplantEditorView(wx.Panel):
|
||||
currentMktGrp = sMkt.getMarketGroup(tree.GetItemData(parent))
|
||||
items = sMkt.getItemsByMarketGroup(currentMktGrp)
|
||||
for item in items:
|
||||
iconId = self.addMarketViewImage(item.icon.iconFile)
|
||||
iconId = self.addMarketViewImage(item.iconID)
|
||||
tree.AppendItem(parent, item.name, iconId, data=item)
|
||||
|
||||
tree.SortChildren(parent)
|
||||
|
||||
@@ -208,5 +208,6 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
|
||||
fighterAbilities,
|
||||
boosterSideEffects,
|
||||
commandFits,
|
||||
tabbedFits
|
||||
tabbedFits,
|
||||
mutaplasmids,
|
||||
)
|
||||
|
||||
@@ -34,6 +34,9 @@ from gui.builtinItemStatsViews.itemDependants import ItemDependents
|
||||
from gui.builtinItemStatsViews.itemEffects import ItemEffects
|
||||
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
||||
from gui.builtinItemStatsViews.itemProperties import ItemProperties
|
||||
from gui.builtinItemStatsViews.itemMutator import ItemMutator
|
||||
|
||||
from eos.saveddata.module import Module
|
||||
|
||||
|
||||
class ItemStatsDialog(wx.Dialog):
|
||||
@@ -79,10 +82,8 @@ class ItemStatsDialog(wx.Dialog):
|
||||
item = sMkt.getItem(victim.ID)
|
||||
victim = None
|
||||
self.context = itmContext
|
||||
if item.icon is not None:
|
||||
before, sep, after = item.icon.iconFile.rpartition("_")
|
||||
iconFile = "%s%s%s" % (before, sep, "0%s" % after if len(after) < 2 else after)
|
||||
itemImg = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
if item.iconID is not None:
|
||||
itemImg = BitmapLoader.getBitmap(item.iconID, "icons")
|
||||
if itemImg is not None:
|
||||
self.SetIcon(wx.Icon(itemImg))
|
||||
self.SetTitle("%s: %s%s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name,
|
||||
@@ -163,6 +164,10 @@ class ItemStatsContainer(wx.Panel):
|
||||
self.traits = ItemTraits(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.traits, "Traits")
|
||||
|
||||
if isinstance(stuff, Module) and stuff.isMutated:
|
||||
self.mutator = ItemMutator(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.mutator, "Multiplasmid")
|
||||
|
||||
self.desc = ItemDescription(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.desc, "Description")
|
||||
|
||||
|
||||
@@ -130,8 +130,8 @@ class PyGauge(wx.Window):
|
||||
return self._max_range
|
||||
|
||||
def Animate(self):
|
||||
sFit = Fit.getInstance()
|
||||
if sFit.serviceFittingOptions["enableGaugeAnimation"]:
|
||||
#sFit = Fit.getInstance()
|
||||
if True:
|
||||
if not self._timer:
|
||||
self._timer = wx.Timer(self, self._timer_id)
|
||||
|
||||
@@ -425,6 +425,13 @@ if __name__ == "__main__":
|
||||
gauge.SetFractionDigits(1)
|
||||
box.Add(gauge, 0, wx.ALL, 2)
|
||||
|
||||
gauge = PyGauge(self, font, size=(100, 5))
|
||||
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
|
||||
gauge.SetBarColour(wx.Colour(255, 128, 0))
|
||||
gauge.SetValue(59)
|
||||
gauge.SetFractionDigits(1)
|
||||
box.Add(gauge, 0, wx.ALL, 2)
|
||||
|
||||
self.SetSizer(box)
|
||||
self.Layout()
|
||||
|
||||
|
||||
@@ -236,10 +236,10 @@ class ShipBrowser(wx.Panel):
|
||||
if self.filterShipsWithNoFits:
|
||||
if fits > 0:
|
||||
if filter_:
|
||||
self.lpane.AddWidget(ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, fits), ship.race))
|
||||
self.lpane.AddWidget(ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, fits), ship.race, ship.graphicID))
|
||||
else:
|
||||
if filter_:
|
||||
self.lpane.AddWidget(ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, fits), ship.race))
|
||||
self.lpane.AddWidget(ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, fits), ship.race, ship.graphicID))
|
||||
|
||||
self.raceselect.RebuildRaces(racesList)
|
||||
|
||||
@@ -335,8 +335,8 @@ class ShipBrowser(wx.Panel):
|
||||
|
||||
shipTrait = ship.traits.traitText if (ship.traits is not None) else "" # empty string if no traits
|
||||
|
||||
for ID, name, booster, timestamp, notes in fitList:
|
||||
self.lpane.AddWidget(FitItem(self.lpane, ID, (shipName, shipTrait, name, booster, timestamp, notes), shipID))
|
||||
for ID, name, booster, timestamp, notes, graphicID in fitList:
|
||||
self.lpane.AddWidget(FitItem(self.lpane, ID, (shipName, shipTrait, name, booster, timestamp, notes), shipID, graphicID=graphicID))
|
||||
|
||||
self.lpane.RefreshList()
|
||||
self.lpane.Thaw()
|
||||
@@ -374,7 +374,7 @@ class ShipBrowser(wx.Panel):
|
||||
|
||||
self.lpane.AddWidget(
|
||||
ShipItem(self.lpane, ship.ID, (ship.name, shipTrait, len(sFit.getFitsWithShip(ship.ID))),
|
||||
ship.race))
|
||||
ship.race, ship.graphicID))
|
||||
|
||||
for ID, name, shipID, shipName, booster, timestamp, notes in fitList:
|
||||
ship = sMkt.getItem(shipID)
|
||||
@@ -384,7 +384,7 @@ class ShipBrowser(wx.Panel):
|
||||
|
||||
shipTrait = ship.traits.traitText if (ship.traits is not None) else "" # empty string if no traits
|
||||
|
||||
self.lpane.AddWidget(FitItem(self.lpane, ID, (shipName, shipTrait, name, booster, timestamp, notes), shipID))
|
||||
self.lpane.AddWidget(FitItem(self.lpane, ID, (shipName, shipTrait, name, booster, timestamp, notes), shipID, graphicID=ship.graphicID))
|
||||
if len(ships) == 0 and len(fitList) == 0:
|
||||
self.lpane.AddWidget(PFStaticText(self.lpane, label="No matching results."))
|
||||
self.lpane.RefreshList(doFocus=False)
|
||||
@@ -435,6 +435,7 @@ class ShipBrowser(wx.Panel):
|
||||
fit[4]
|
||||
),
|
||||
shipItem.ID,
|
||||
graphicID=shipItem.graphicID
|
||||
))
|
||||
self.lpane.RefreshList(doFocus=False)
|
||||
self.lpane.Thaw()
|
||||
|
||||
|
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 452 B |
BIN
imgs/icons/10012.png
Normal file
|
After Width: | Height: | Size: 703 B |
BIN
imgs/icons/10013.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
imgs/icons/10014.png
Normal file
|
After Width: | Height: | Size: 677 B |
BIN
imgs/icons/10015.png
Normal file
|
After Width: | Height: | Size: 701 B |
BIN
imgs/icons/10016.png
Normal file
|
After Width: | Height: | Size: 725 B |
BIN
imgs/icons/10017.png
Normal file
|
After Width: | Height: | Size: 814 B |
BIN
imgs/icons/10018.png
Normal file
|
After Width: | Height: | Size: 781 B |
BIN
imgs/icons/10019.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
imgs/icons/10020.png
Normal file
|
After Width: | Height: | Size: 915 B |
BIN
imgs/icons/10021.png
Normal file
|
After Width: | Height: | Size: 898 B |
BIN
imgs/icons/10022.png
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
imgs/icons/10023.png
Normal file
|
After Width: | Height: | Size: 837 B |
BIN
imgs/icons/10024.png
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
imgs/icons/10025.png
Normal file
|
After Width: | Height: | Size: 944 B |
BIN
imgs/icons/10026.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
imgs/icons/10027.png
Normal file
|
After Width: | Height: | Size: 962 B |
BIN
imgs/icons/10028.png
Normal file
|
After Width: | Height: | Size: 696 B |
BIN
imgs/icons/10029.png
Normal file
|
After Width: | Height: | Size: 955 B |
BIN
imgs/icons/10030.png
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
imgs/icons/10031.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
imgs/icons/10032.png
Normal file
|
After Width: | Height: | Size: 814 B |
BIN
imgs/icons/10033.png
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
imgs/icons/10034.png
Normal file
|
After Width: | Height: | Size: 1022 B |
BIN
imgs/icons/10035.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
imgs/icons/10036.png
Normal file
|
After Width: | Height: | Size: 937 B |
BIN
imgs/icons/10037.png
Normal file
|
After Width: | Height: | Size: 814 B |
BIN
imgs/icons/10038.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
imgs/icons/10039.png
Normal file
|
After Width: | Height: | Size: 967 B |
|
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 800 B |
BIN
imgs/icons/10040.png
Normal file
|
After Width: | Height: | Size: 1002 B |
BIN
imgs/icons/10041.png
Normal file
|
After Width: | Height: | Size: 1007 B |
BIN
imgs/icons/10042.png
Normal file
|
After Width: | Height: | Size: 863 B |
BIN
imgs/icons/10043.png
Normal file
|
After Width: | Height: | Size: 774 B |
BIN
imgs/icons/10044.png
Normal file
|
After Width: | Height: | Size: 773 B |
BIN
imgs/icons/10045.png
Normal file
|
After Width: | Height: | Size: 931 B |
BIN
imgs/icons/10046.png
Normal file
|
After Width: | Height: | Size: 831 B |
BIN
imgs/icons/10047.png
Normal file
|
After Width: | Height: | Size: 772 B |
BIN
imgs/icons/10048.png
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
imgs/icons/10049.png
Normal file
|
After Width: | Height: | Size: 862 B |
BIN
imgs/icons/10050.png
Normal file
|
After Width: | Height: | Size: 801 B |
BIN
imgs/icons/10051.png
Normal file
|
After Width: | Height: | Size: 867 B |
BIN
imgs/icons/10052.png
Normal file
|
After Width: | Height: | Size: 907 B |
BIN
imgs/icons/10053.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
imgs/icons/10054.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
imgs/icons/10055.png
Normal file
|
After Width: | Height: | Size: 759 B |
BIN
imgs/icons/10056.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
imgs/icons/10057.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
imgs/icons/10058.png
Normal file
|
After Width: | Height: | Size: 774 B |
BIN
imgs/icons/1007.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
imgs/icons/10071.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
imgs/icons/10073.png
Normal file
|
After Width: | Height: | Size: 827 B |
BIN
imgs/icons/10074.png
Normal file
|
After Width: | Height: | Size: 934 B |
BIN
imgs/icons/10075.png
Normal file
|
After Width: | Height: | Size: 987 B |
BIN
imgs/icons/10076.png
Normal file
|
After Width: | Height: | Size: 781 B |