Merge branch 'master' into singularity
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -122,3 +122,4 @@ gitversion
|
||||
/.version
|
||||
*.swp
|
||||
|
||||
*.fsdbinary
|
||||
|
||||
@@ -24,10 +24,10 @@ saveInRoot = False
|
||||
|
||||
# Version data
|
||||
|
||||
version = "2.1.0"
|
||||
version = "2.3.1"
|
||||
tag = "Stable"
|
||||
expansionName = "Into the Abyss"
|
||||
expansionVersion = "1.1"
|
||||
expansionName = "YC120.7"
|
||||
expansionVersion = "1.2"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
minItemSearchLength = 3
|
||||
|
||||
@@ -73,4 +73,7 @@ exe = EXE(pyz,
|
||||
app = BUNDLE(exe,
|
||||
name='pyfa.app',
|
||||
icon=icon,
|
||||
bundle_identifier=None)
|
||||
bundle_identifier=None,
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True'
|
||||
})
|
||||
|
||||
13
dist_assets/win/Microsoft.VC90.CRT.manifest
Normal file
13
dist_assets/win/Microsoft.VC90.CRT.manifest
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<noInheritable/>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.VC90.CRT"
|
||||
version="9.0.21022.8"
|
||||
processorArchitecture="x86"
|
||||
publicKeyToken="1fc8b3b9a1e18e3b"/>
|
||||
<file name="MSVCR90.DLL"/>
|
||||
<file name="MSVCM90.DLL"/>
|
||||
<file name="MSVCP90.DLL"/>
|
||||
</assembly>
|
||||
@@ -5,7 +5,7 @@
|
||||
; we do some #ifdef conditionals because automated compilation passes these as arguments
|
||||
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "1.15.0"
|
||||
#define MyAppVersion "2.1.0"
|
||||
#endif
|
||||
#ifndef MyAppExpansion
|
||||
#define MyAppExpansion "Vanguard 1.0"
|
||||
@@ -64,7 +64,7 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
|
||||
|
||||
[Files]
|
||||
Source: "{#MyAppDir}\pyfa.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppDir}\pyfa.exe"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: RemoveFromVirtualStore
|
||||
Source: "{#MyAppDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
@@ -104,6 +104,22 @@ begin
|
||||
FSWbemLocator := Unassigned;
|
||||
end;
|
||||
|
||||
procedure RemoveFromVirtualStore;
|
||||
var
|
||||
VirtualStore,FileName,FilePath:String;
|
||||
DriveChars:Integer;
|
||||
begin
|
||||
VirtualStore:=AddBackslash(ExpandConstant('{localappdata}'))+'VirtualStore';
|
||||
FileName:=ExpandConstant(CurrentFileName);
|
||||
DriveChars:=Length(ExtractFileDrive(FileName));
|
||||
if DriveChars>0 then begin
|
||||
Delete(FileName,1,DriveChars);
|
||||
FileName:=VirtualStore+FileName;
|
||||
FilePath:=ExtractFilePath(FileName);
|
||||
DelTree(FilePath, True, True, True);
|
||||
end;
|
||||
end;
|
||||
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
if(IsAppRunning( 'pyfa.exe' )) then
|
||||
|
||||
25
dist_assets/win/pyfa.exe.manifest
Normal file
25
dist_assets/win/pyfa.exe.manifest
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity name="pyfa" processorArchitecture="x86" type="win32" version="1.0.0.0"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -20,6 +20,8 @@ added_files = [
|
||||
('../../imgs/renders/*.png', 'imgs/renders'),
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../dist_assets/win/pyfa.ico', '.'),
|
||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||
(requests.certs.where(), '.'), # is this needed anymore?
|
||||
('../../eve.db', '.'),
|
||||
('../../README.md', '.'),
|
||||
|
||||
@@ -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)
|
||||
|
||||
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.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,12 @@ 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,36 @@ 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):
|
||||
"""
|
||||
@@ -361,6 +392,10 @@ def directAttributeRequest(itemIDs, attrIDs):
|
||||
return result
|
||||
|
||||
|
||||
def getAbyssalTypes():
|
||||
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
|
||||
|
||||
|
||||
def getRequiredFor(itemID, attrMapping):
|
||||
Attribute1 = aliased(Attribute)
|
||||
Attribute2 = aliased(Attribute)
|
||||
|
||||
@@ -14,7 +14,7 @@ def upgrade(saveddata_engine):
|
||||
"boosters": 2,
|
||||
"cargo": 2,
|
||||
"characters": 2,
|
||||
"crest": 1,
|
||||
# "crest": 1,
|
||||
"damagePatterns": 2,
|
||||
"drones": 2,
|
||||
"fighters": 2,
|
||||
|
||||
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__ = [
|
||||
"character",
|
||||
"fit",
|
||||
"mutator",
|
||||
"module",
|
||||
"user",
|
||||
"skill",
|
||||
|
||||
@@ -56,7 +56,7 @@ fits_table = Table("fits", saveddata_meta,
|
||||
Column("booster", Boolean, nullable=False, index=True, default=0),
|
||||
Column("targetResistsID", ForeignKey("targetResists.ID"), 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("ignoreRestrictions", Boolean, default=0),
|
||||
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.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,18 @@
|
||||
# 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)
|
||||
@@ -13,11 +13,14 @@ type = "active"
|
||||
|
||||
def handler(fit, module, context):
|
||||
damagePattern = fit.damagePattern
|
||||
# pyfalog.debug("==============================")
|
||||
|
||||
static_adaptive_behavior = eos.config.settings['useStaticAdaptiveArmorHardener']
|
||||
|
||||
if (damagePattern.emAmount == damagePattern.thermalAmount == damagePattern.kineticAmount == damagePattern.explosiveAmount) and static_adaptive_behavior:
|
||||
pyfalog.debug("Setting adaptivearmorhardener resists to uniform profile.")
|
||||
# pyfalog.debug("Setting adaptivearmorhardener resists to uniform profile.")
|
||||
for attr in ("armorEmDamageResonance", "armorThermalDamageResonance", "armorKineticDamageResonance", "armorExplosiveDamageResonance"):
|
||||
fit.ship.multiplyItemAttr(attr, module.getModifiedItemAttr(attr), stackingPenalties=True, penaltyGroup="preMul")
|
||||
return
|
||||
|
||||
# Skip if there is no damage pattern. Example: projected ships or fleet boosters
|
||||
@@ -30,7 +33,7 @@ def handler(fit, module, context):
|
||||
damagePattern.kineticAmount * fit.ship.getModifiedItemAttr('armorKineticDamageResonance'),
|
||||
damagePattern.explosiveAmount * fit.ship.getModifiedItemAttr('armorExplosiveDamageResonance'),
|
||||
)
|
||||
# pyfalog.debug("Damage Adjusted for Armor Resists: %f/%f/%f/%f", baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3])
|
||||
# pyfalog.debug("Damage Adjusted for Armor Resists: %f/%f/%f/%f" % (baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3]))
|
||||
|
||||
resistanceShiftAmount = module.getModifiedItemAttr(
|
||||
'resistanceShiftAmount') / 100 # The attribute is in percent and we want a fraction
|
||||
@@ -46,7 +49,7 @@ def handler(fit, module, context):
|
||||
cycleList = []
|
||||
loopStart = -20
|
||||
for num in range(50):
|
||||
# pyfalog.debug("Starting cycle %d.", num)
|
||||
# pyfalog.debug("Starting cycle %d." % num)
|
||||
# The strange order is to emulate the ingame sorting when different types have taken the same amount of damage.
|
||||
# This doesn't take into account stacking penalties. In a few cases fitting a Damage Control causes an inaccurate result.
|
||||
damagePattern_tuples = [
|
||||
@@ -84,7 +87,7 @@ def handler(fit, module, context):
|
||||
RAHResistance[sortedDamagePattern_tuples[1][0]] = sortedDamagePattern_tuples[1][2] + change1
|
||||
RAHResistance[sortedDamagePattern_tuples[2][0]] = sortedDamagePattern_tuples[2][2] + change2
|
||||
RAHResistance[sortedDamagePattern_tuples[3][0]] = sortedDamagePattern_tuples[3][2] + change3
|
||||
# pyfalog.debug("Resistances shifted to %f/%f/%f/%f", RAHResistance[0], RAHResistance[1], RAHResistance[2], RAHResistance[3])
|
||||
# pyfalog.debug("Resistances shifted to %f/%f/%f/%f" % ( RAHResistance[0], RAHResistance[1], RAHResistance[2], RAHResistance[3]))
|
||||
|
||||
# See if the current RAH profile has been encountered before, indicating a loop.
|
||||
for i, val in enumerate(cycleList):
|
||||
@@ -94,16 +97,16 @@ def handler(fit, module, context):
|
||||
abs(RAHResistance[2] - val[2]) <= tolerance and \
|
||||
abs(RAHResistance[3] - val[3]) <= tolerance:
|
||||
loopStart = i
|
||||
# pyfalog.debug("Loop found: %d-%d", loopStart, num)
|
||||
# pyfalog.debug("Loop found: %d-%d" % (loopStart, num))
|
||||
break
|
||||
if loopStart >= 0:
|
||||
break
|
||||
|
||||
cycleList.append(list(RAHResistance))
|
||||
|
||||
if loopStart < 0:
|
||||
pyfalog.error("Reactive Armor Hardener failed to find equilibrium. Damage profile after armor: {0}/{1}/{2}/{3}",
|
||||
baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3])
|
||||
# if loopStart < 0:
|
||||
# pyfalog.error("Reactive Armor Hardener failed to find equilibrium. Damage profile after armor: {0}/{1}/{2}/{3}".format(
|
||||
# baseDamageTaken[0], baseDamageTaken[1], baseDamageTaken[2], baseDamageTaken[3]))
|
||||
|
||||
# Average the profiles in the RAH loop, or the last 20 if it didn't find a loop.
|
||||
loopCycles = cycleList[loopStart:]
|
||||
@@ -117,7 +120,7 @@ def handler(fit, module, context):
|
||||
average[i] = round(average[i] / numCycles, 3)
|
||||
|
||||
# Set the new resistances
|
||||
# pyfalog.debug("Setting new resist profile: %f/%f/%f/%f", average[0], average[1], average[2],average[3])
|
||||
# pyfalog.debug("Setting new resist profile: %f/%f/%f/%f" % ( average[0], average[1], average[2],average[3]))
|
||||
for i, attr in enumerate((
|
||||
'armorEmDamageResonance', 'armorThermalDamageResonance', 'armorKineticDamageResonance',
|
||||
'armorExplosiveDamageResonance')):
|
||||
|
||||
@@ -6,4 +6,4 @@ type = "passive"
|
||||
|
||||
|
||||
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
|
||||
#
|
||||
# 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)
|
||||
# Implant: Antipharmakon Kosybo
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# boosterMaxVelocityPenalty
|
||||
#
|
||||
# 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"
|
||||
|
||||
# User-friendly name for the side effect
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterShieldCapacityPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Booster (12 of 33)
|
||||
# Implants from group: Booster (12 of 66)
|
||||
type = "boosterSideEffect"
|
||||
|
||||
# User-friendly name for the side effect
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# capacitorCapacityBonus
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Capacitor Battery (27 of 27)
|
||||
# Modules from group: Capacitor Battery (30 of 30)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ type = "passive"
|
||||
def handler(fit, container, context):
|
||||
level = container.level if "skill" in context else 1
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill("Drones"),
|
||||
"maxVelocity", container.getModifiedItemAttr("droneMaxVelocityBonus") * level)
|
||||
"maxVelocity", container.getModifiedItemAttr("droneMaxVelocityBonus") * level, stackingPenalties=True)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# energyNosferatuFalloff
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Energy Nosferatu (51 of 51)
|
||||
# Modules from group: Energy Nosferatu (54 of 54)
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
type = "active", "projected"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileSkillWarheadUpgradesEmDamageBonus
|
||||
#
|
||||
# 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
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileSkillWarheadUpgradesExplosiveDamageBonus
|
||||
#
|
||||
# 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
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileSkillWarheadUpgradesKineticDamageBonus
|
||||
#
|
||||
# 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
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileSkillWarheadUpgradesThermalDamageBonus
|
||||
#
|
||||
# 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
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# modifyEnergyWarfareResistance
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Capacitor Battery (27 of 27)
|
||||
# Modules from group: Capacitor Battery (30 of 30)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# 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)
|
||||
# Modules named like: Auxiliary Thrusters (8 of 8)
|
||||
# Implant: Quafe Zero
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Used by:
|
||||
# Modules from group: Capacitor Booster (59 of 59)
|
||||
# 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: Remote Armor Repairer (39 of 39)
|
||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
|
||||
@@ -7,4 +7,4 @@ type = "passive"
|
||||
|
||||
def handler(fit, src, context):
|
||||
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):
|
||||
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"))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Weapon Disruption"), "maxRange",
|
||||
src.getModifiedItemAttr("roleBonus"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shieldBoostAmplifierPassiveBooster
|
||||
#
|
||||
# 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)
|
||||
# Implant: Antipharmakon Thureo
|
||||
type = "passive"
|
||||
|
||||
@@ -6,5 +6,5 @@ type = "passive"
|
||||
|
||||
|
||||
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")
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# shipPDmgBonusMF
|
||||
#
|
||||
# Used by:
|
||||
# Variations of ship: Slasher (3 of 3)
|
||||
# Ship: Cheetah
|
||||
# Ship: Freki
|
||||
# Ship: Republic Fleet Firetail
|
||||
# Ship: Rifter
|
||||
# Ship: Slasher
|
||||
# Ship: Stiletto
|
||||
# Ship: Wolf
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (2 of 2)
|
||||
# Skill: Drone Durability
|
||||
type = "passive"
|
||||
|
||||
|
||||
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",
|
||||
src.getModifiedItemAttr("hullHpBonus") * lvl)
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# skillBonusDroneInterfacing
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (2 of 2)
|
||||
# Skill: Drone Interfacing
|
||||
type = "passive"
|
||||
|
||||
|
||||
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",
|
||||
src.getModifiedItemAttr("damageMultiplierBonus") * lvl)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"),
|
||||
|
||||
@@ -6,7 +6,7 @@ type = "passive"
|
||||
|
||||
|
||||
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",
|
||||
src.getModifiedItemAttr("maxVelocityBonus") * lvl)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "maxVelocity",
|
||||
|
||||
@@ -6,7 +6,7 @@ type = "passive"
|
||||
|
||||
|
||||
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",
|
||||
src.getModifiedItemAttr("rangeSkillBonus") * lvl)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill("Fighters"), "fighterAbilityMissilesRange",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# surgicalStrikeDamageMultiplierBonusPostPercentDamageMultiplierLocationShipModulesRequiringGunnery
|
||||
#
|
||||
# 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)
|
||||
# Implant: Standard Cerebral Accelerator
|
||||
type = "passive"
|
||||
|
||||
@@ -6,5 +6,5 @@ type = "passive"
|
||||
|
||||
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: True, "heatDamage",
|
||||
fit.modules.filteredItemBoost(lambda mod: "heatDamage" in mod.item.attributes, "heatDamage",
|
||||
skill.getModifiedItemAttr("thermodynamicsHeatDamage") * skill.level)
|
||||
|
||||
119
eos/gamedata.py
119
eos/gamedata.py
@@ -208,6 +208,8 @@ class Item(EqBase):
|
||||
|
||||
MOVE_ATTR_INFO = None
|
||||
|
||||
ABYSSAL_TYPES = None
|
||||
|
||||
@classmethod
|
||||
def getMoveAttrInfo(cls):
|
||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||
@@ -463,6 +465,17 @@ class Item(EqBase):
|
||||
|
||||
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):
|
||||
return "Item(ID={}, name={}) at {}".format(
|
||||
self.ID, self.name, hex(id(self))
|
||||
@@ -512,7 +525,15 @@ class Group(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class Icon(EqBase):
|
||||
class DynamicItem(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class DynamicItemAttribute(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class DynamicItemItem(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
@@ -532,7 +553,101 @@ class MetaType(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):
|
||||
|
||||
@@ -141,14 +141,14 @@ class FitDpsGraph(Graph):
|
||||
targetVelocity = data["velocity"]
|
||||
explosionRadius = ability.fighter.getModifiedItemAttr("{}ExplosionRadius".format(prefix))
|
||||
explosionVelocity = ability.fighter.getModifiedItemAttr("{}ExplosionVelocity".format(prefix))
|
||||
damageReductionFactor = ability.fighter.getModifiedItemAttr("{}ReductionFactor".format(prefix))
|
||||
damageReductionFactor = ability.fighter.getModifiedItemAttr("{}ReductionFactor".format(prefix), None)
|
||||
|
||||
# the following conditionals are because CCP can't keep a decent naming convention, as if fighter implementation
|
||||
# wasn't already fucked.
|
||||
if damageReductionFactor is None:
|
||||
damageReductionFactor = ability.fighter.getModifiedItemAttr("{}DamageReductionFactor".format(prefix))
|
||||
|
||||
damageReductionSensitivity = ability.fighter.getModifiedItemAttr("{}ReductionSensitivity".format(prefix))
|
||||
damageReductionSensitivity = ability.fighter.getModifiedItemAttr("{}ReductionSensitivity".format(prefix), None)
|
||||
if damageReductionSensitivity is None:
|
||||
damageReductionSensitivity = ability.fighter.getModifiedItemAttr(
|
||||
"{}DamageReductionSensitivity".format(prefix))
|
||||
|
||||
@@ -33,6 +33,15 @@ 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 +68,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 +111,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 +147,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
|
||||
|
||||
@@ -142,14 +142,8 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
copy = Booster(self.item)
|
||||
copy.active = self.active
|
||||
|
||||
# Legacy booster side effect code, disabling as not currently implemented
|
||||
'''
|
||||
origSideEffects = list(self.iterSideEffects())
|
||||
copySideEffects = list(copy.iterSideEffects())
|
||||
i = 0
|
||||
while i < len(origSideEffects):
|
||||
copySideEffects[i].active = origSideEffects[i].active
|
||||
i += 1
|
||||
'''
|
||||
for sideEffect in self.sideEffects:
|
||||
copyEffect = next(filter(lambda eff: eff.effectID == sideEffect.effectID, copy.sideEffects))
|
||||
copyEffect.active = sideEffect.active
|
||||
|
||||
return copy
|
||||
|
||||
@@ -57,8 +57,15 @@ class Character(object):
|
||||
def init(self):
|
||||
|
||||
self.__skillIdMap = {}
|
||||
|
||||
for skill in self.__skills:
|
||||
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.alphaClone = None
|
||||
@@ -118,9 +125,16 @@ class Character(object):
|
||||
|
||||
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):
|
||||
del self.__skills[:]
|
||||
self.__skillIdMap.clear()
|
||||
self.dirtySkills.clear()
|
||||
|
||||
@property
|
||||
def ro(self):
|
||||
|
||||
@@ -291,6 +291,10 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Fighter(self.item)
|
||||
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
|
||||
|
||||
def fits(self, fit):
|
||||
|
||||
@@ -1016,6 +1016,16 @@ class Fit(object):
|
||||
def getNumSlots(self, type):
|
||||
return self.ship.getModifiedItemAttr(self.slots[type]) or 0
|
||||
|
||||
def getHardpointsFree(self, type):
|
||||
if type == Hardpoint.NONE:
|
||||
return 1
|
||||
elif type == Hardpoint.TURRET:
|
||||
return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(Hardpoint.TURRET)
|
||||
elif type == Hardpoint.MISSILE:
|
||||
return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(Hardpoint.MISSILE)
|
||||
else:
|
||||
raise ValueError("%d is not a valid value for Hardpoint Enum", type)
|
||||
|
||||
@property
|
||||
def calibrationUsed(self):
|
||||
return self.getItemAttrOnlineSum(self.modules, 'upgradeCost')
|
||||
@@ -1570,6 +1580,7 @@ class Fit(object):
|
||||
copy_ship.name = "%s copy" % self.name
|
||||
copy_ship.damagePattern = self.damagePattern
|
||||
copy_ship.targetResists = self.targetResists
|
||||
copy_ship.implantLocation = self.implantLocation
|
||||
copy_ship.notes = self.notes
|
||||
|
||||
toCopy = (
|
||||
@@ -1588,12 +1599,27 @@ class Fit(object):
|
||||
for i in orig:
|
||||
c.append(deepcopy(i))
|
||||
|
||||
for fit in self.projectedFits:
|
||||
copy_ship.__projectedFits[fit.ID] = fit
|
||||
# this bit is required -- see GH issue # 83
|
||||
# this bit is required -- see GH issue # 83
|
||||
def forceUpdateSavedata(fit):
|
||||
eos.db.saveddata_session.flush()
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
from logbook import Logger
|
||||
from copy import deepcopy
|
||||
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
from math import floor
|
||||
@@ -27,6 +28,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__)
|
||||
|
||||
@@ -72,17 +74,33 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Uninteractable Localized Effect Beacon", "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"""
|
||||
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,7 +109,9 @@ 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
|
||||
self.__mutaplasmid = None
|
||||
|
||||
# we need this early if module is invalid and returns early
|
||||
self.__slot = self.dummySlot
|
||||
@@ -102,6 +122,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,6 +161,18 @@ 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
|
||||
@@ -162,11 +202,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)
|
||||
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
|
||||
def numCharges(self):
|
||||
@@ -306,6 +352,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
|
||||
|
||||
@property
|
||||
def mutaplasmid(self):
|
||||
return self.__mutaplasmid
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
return self.__charge if self.__charge != 0 else None
|
||||
@@ -472,7 +526,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if max is not None:
|
||||
current = 0 # if self.owner != fit else -1 # Disabled, see #1278
|
||||
for mod in fit.modules:
|
||||
if mod.item and mod.item.groupID == self.item.groupID:
|
||||
if (mod.item and mod.item.groupID == self.item.groupID and
|
||||
self.modPosition != mod.modPosition):
|
||||
current += 1
|
||||
|
||||
if current >= max:
|
||||
@@ -480,12 +535,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
# Check this only if we're told to do so
|
||||
if hardpointLimit:
|
||||
if self.hardpoint == Hardpoint.TURRET:
|
||||
if fit.ship.getModifiedItemAttr('turretSlotsLeft') - fit.getHardpointsUsed(Hardpoint.TURRET) < 1:
|
||||
return False
|
||||
elif self.hardpoint == Hardpoint.MISSILE:
|
||||
if fit.ship.getModifiedItemAttr('launcherSlotsLeft') - fit.getHardpointsUsed(Hardpoint.MISSILE) < 1:
|
||||
return False
|
||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -575,7 +626,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:
|
||||
@@ -786,9 +837,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if item is None:
|
||||
copy = Module.buildEmpty(self.slot)
|
||||
else:
|
||||
copy = Module(self.item)
|
||||
copy = Module(self.item, self.baseItem, self.mutaplasmid)
|
||||
copy.charge = self.charge
|
||||
copy.state = self.state
|
||||
|
||||
for x in self.mutators.values():
|
||||
Mutator(copy, x.attribute, x.value)
|
||||
|
||||
return copy
|
||||
|
||||
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
|
||||
|
||||
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()):
|
||||
|
||||
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()
|
||||
@@ -32,12 +32,15 @@ logging = Logger(__name__)
|
||||
|
||||
|
||||
class BitmapLoader(object):
|
||||
try:
|
||||
archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r')
|
||||
logging.info("Using zipped image files.")
|
||||
except (IOError, TypeError):
|
||||
logging.info("Using local image files.")
|
||||
archive = None
|
||||
# try:
|
||||
# archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r')
|
||||
# logging.info("Using zipped image files.")
|
||||
# except (IOError, TypeError):
|
||||
# logging.info("Using local image files.")
|
||||
# archive = None
|
||||
|
||||
logging.info("Using local image files.")
|
||||
archive = None
|
||||
|
||||
cached_bitmaps = OrderedDict()
|
||||
dont_use_cached_bitmaps = False
|
||||
@@ -46,7 +49,7 @@ class BitmapLoader(object):
|
||||
@classmethod
|
||||
def getStaticBitmap(cls, name, parent, location):
|
||||
static = wx.StaticBitmap(parent)
|
||||
static.SetBitmap(cls.getBitmap(name, location))
|
||||
static.SetBitmap(cls.getBitmap(name or 0, location))
|
||||
return static
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -118,13 +118,22 @@ class CargoView(d.Display):
|
||||
# Gather module information to get position
|
||||
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 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
|
||||
sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, dstRow)
|
||||
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
|
||||
sFit.removeModule(self.mainFrame.getActiveFit(), module.position)
|
||||
|
||||
@@ -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):
|
||||
@@ -109,7 +109,7 @@ class ProjectedView(d.Display):
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
# Gather module information to get position
|
||||
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))
|
||||
elif data[0] == "market":
|
||||
sFit = Fit.getInstance()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -63,7 +63,7 @@ class ItemStats(ContextMenu):
|
||||
size = wx.DefaultSize
|
||||
pos = wx.DefaultPosition
|
||||
ItemStatsDialog(stuff, fullContext, pos, size, maximized)
|
||||
lastWnd.closeEvent(None)
|
||||
lastWnd.Close()
|
||||
|
||||
else:
|
||||
ItemStatsDialog(stuff, fullContext)
|
||||
|
||||
@@ -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
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()
|
||||
@@ -195,7 +195,7 @@ class WhProjector(ContextMenu):
|
||||
def getLocalizedEnvironments(self):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
grp = sMkt.getGroup("Uninteractable Localized Effect Beacon")
|
||||
grp = sMkt.getGroup("Abyssal Hazards")
|
||||
|
||||
effects = dict()
|
||||
|
||||
|
||||
@@ -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
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:
|
||||
|
||||
@@ -7,8 +7,6 @@ import wx
|
||||
from .helpers import AutoListCtrl
|
||||
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.market import Market
|
||||
from service.attribute import Attribute
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
|
||||
@@ -86,7 +84,8 @@ class ItemParams(wx.Panel):
|
||||
def RefreshValues(self, event):
|
||||
self._fetchValues()
|
||||
self.UpdateList()
|
||||
event.Skip()
|
||||
if event:
|
||||
event.Skip()
|
||||
|
||||
def ToggleViewMode(self, event):
|
||||
self.toggleView *= -1
|
||||
@@ -102,7 +101,7 @@ class ItemParams(wx.Panel):
|
||||
if saveFileDialog.ShowModal() == wx.ID_CANCEL:
|
||||
return # the user hit cancel...
|
||||
|
||||
with open(saveFileDialog.GetPath(), "wb") as exportFile:
|
||||
with open(saveFileDialog.GetPath(), "w") as exportFile:
|
||||
writer = csv.writer(exportFile, delimiter=',')
|
||||
|
||||
writer.writerow(
|
||||
@@ -174,7 +173,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 +193,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 +202,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
|
||||
@@ -210,14 +214,14 @@ class ItemParams(wx.Panel):
|
||||
if self.toggleView != 1:
|
||||
valueUnit = str(value)
|
||||
elif info and info.unit:
|
||||
valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
|
||||
valueUnit = self.FormatValue(*info.unit.TranslateValue(value))
|
||||
else:
|
||||
valueUnit = formatAmount(value, 3, 0, 0)
|
||||
|
||||
if self.toggleView != 1:
|
||||
valueUnitDefault = str(valueDefault)
|
||||
elif info and info.unit:
|
||||
valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name)
|
||||
valueUnitDefault = self.FormatValue(*info.unit.TranslateValue(valueDefault))
|
||||
else:
|
||||
valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
|
||||
|
||||
@@ -232,44 +236,11 @@ class ItemParams(wx.Panel):
|
||||
self.Layout()
|
||||
|
||||
@staticmethod
|
||||
def TranslateValueUnit(value, unitName, unitDisplayName):
|
||||
def itemIDCallback():
|
||||
item = Market.getInstance().getItem(value)
|
||||
return "%s (%d)" % (item.name, value) if item is not None else str(value)
|
||||
|
||||
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])
|
||||
def FormatValue(value, unit):
|
||||
"""Formats a value / unit combination into a string
|
||||
@todo: move this to a more central location, since this is also used in the item mutator panel"""
|
||||
if isinstance(value, (int, float)):
|
||||
fvalue = formatAmount(value, 3, 0, 0)
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -31,3 +31,36 @@ class ItemDescription(wx.Panel):
|
||||
|
||||
mainSizer.Add(self.description, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.Layout()
|
||||
|
||||
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.description.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
|
||||
self.popupMenu = wx.Menu()
|
||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||
self.popupMenu.Append(copyItem)
|
||||
self.popupMenu.Bind(wx.EVT_MENU, self.menuClickHandler, copyItem)
|
||||
|
||||
def onPopupMenu(self, event):
|
||||
self.PopupMenu(self.popupMenu)
|
||||
|
||||
def menuClickHandler(self, event):
|
||||
selectedMenuItem = event.GetId()
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
self.copySelectionToClipboard()
|
||||
# Ctrl + A
|
||||
if keyCode == 65 and event.ControlDown():
|
||||
self.description.SelectAll()
|
||||
|
||||
def copySelectionToClipboard(self):
|
||||
selectedText = self.description.SelectionToText()
|
||||
if selectedText == '': # if no selection, copy all content
|
||||
selectedText = self.description.ToText()
|
||||
if wx.TheClipboard.Open():
|
||||
wx.TheClipboard.SetData(wx.TextDataObject(selectedText))
|
||||
wx.TheClipboard.Close()
|
||||
|
||||
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))
|
||||
@@ -13,5 +13,38 @@ class ItemTraits(wx.Panel):
|
||||
self.traits = wx.html.HtmlWindow(self)
|
||||
self.traits.SetPage(item.traits.traitText)
|
||||
|
||||
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.traits.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
|
||||
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.Layout()
|
||||
|
||||
self.popupMenu = wx.Menu()
|
||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||
self.popupMenu.Append(copyItem)
|
||||
self.popupMenu.Bind(wx.EVT_MENU, self.menuClickHandler, copyItem)
|
||||
|
||||
def onPopupMenu(self, event):
|
||||
self.PopupMenu(self.popupMenu)
|
||||
|
||||
def menuClickHandler(self, event):
|
||||
selectedMenuItem = event.GetId()
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
self.copySelectionToClipboard()
|
||||
# Ctrl + A
|
||||
if keyCode == 65 and event.ControlDown():
|
||||
self.traits.SelectAll()
|
||||
|
||||
def copySelectionToClipboard(self):
|
||||
selectedText = self.traits.SelectionToText()
|
||||
if selectedText == '': # if no selection, copy all content
|
||||
selectedText = self.traits.ToText()
|
||||
if wx.TheClipboard.Open():
|
||||
wx.TheClipboard.SetData(wx.TextDataObject(selectedText))
|
||||
wx.TheClipboard.Close()
|
||||
|
||||
@@ -53,6 +53,7 @@ class MarketTree(wx.TreeCtrl):
|
||||
# And add real market group contents
|
||||
sMkt = self.sMkt
|
||||
currentMktGrp = sMkt.getMarketGroup(self.GetItemData(root), eager="children")
|
||||
|
||||
for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp):
|
||||
# If market should have items but it doesn't, do not show it
|
||||
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
|
||||
|
||||
@@ -38,6 +38,10 @@ class PFGeneralPref(PreferenceView):
|
||||
0)
|
||||
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,
|
||||
wx.DefaultSize, 0)
|
||||
mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5)
|
||||
@@ -119,6 +123,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.sFit = Fit.getInstance()
|
||||
|
||||
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
|
||||
self.cbDefaultCharImplants.SetValue(self.sFit.serviceFittingOptions["useCharacterImplantsByDefault"])
|
||||
self.cbGlobalDmgPattern.SetValue(self.sFit.serviceFittingOptions["useGlobalDamagePattern"])
|
||||
self.cbFitColorSlots.SetValue(self.sFit.serviceFittingOptions["colorFitBySlot"] 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.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
||||
self.cbDefaultCharImplants.Bind(wx.EVT_CHECKBOX, self.OnCBDefaultCharImplantsStateChange)
|
||||
self.cbGlobalDmgPattern.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalDmgPatternStateChange)
|
||||
self.cbFitColorSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalColorBySlot)
|
||||
self.cbRackSlots.Bind(wx.EVT_CHECKBOX, self.onCBGlobalRackSlots)
|
||||
@@ -187,6 +193,10 @@ class PFGeneralPref(PreferenceView):
|
||||
self.sFit.serviceFittingOptions["useGlobalCharacter"] = self.cbGlobalChar.GetValue()
|
||||
event.Skip()
|
||||
|
||||
def OnCBDefaultCharImplantsStateChange(self, event):
|
||||
self.sFit.serviceFittingOptions["useCharacterImplantsByDefault"] = self.cbDefaultCharImplants.GetValue()
|
||||
event.Skip()
|
||||
|
||||
def OnCBGlobalDmgPatternStateChange(self, event):
|
||||
self.sFit.serviceFittingOptions["useGlobalDamagePattern"] = self.cbGlobalDmgPattern.GetValue()
|
||||
event.Skip()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -39,6 +39,7 @@ from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
import gui.utils.fonts as fonts
|
||||
|
||||
import gui.globalEvents as GE
|
||||
|
||||
@@ -148,6 +149,7 @@ class FittingView(d.Display):
|
||||
self.mainFrame.Bind(EVT_FIT_RENAMED, self.fitRenamed)
|
||||
self.mainFrame.Bind(EVT_FIT_REMOVED, self.fitRemoved)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.appendItem)
|
||||
self.font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
|
||||
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
@@ -679,14 +681,27 @@ class FittingView(d.Display):
|
||||
slot = Slot.getValue(slotType)
|
||||
slotMap[slot] = fit.getSlotsFree(slot) < 0
|
||||
|
||||
font = wx.Font(self.GetClassDefaultAttributes().font)
|
||||
|
||||
for i, mod in enumerate(self.mods):
|
||||
self.SetItemBackgroundColour(i, self.GetBackgroundColour())
|
||||
|
||||
# only consider changing color if we're dealing with a Module
|
||||
if type(mod) is Module:
|
||||
if slotMap[mod.slot] or getattr(mod, 'restrictionOverridden', None): # Color too many modules as red
|
||||
hasRestrictionOverriden = getattr(mod, 'restrictionOverridden', None)
|
||||
# If module had broken fitting restrictions but now doesn't,
|
||||
# ensure it is now valid, and remove restrictionOverridden
|
||||
# variable. More in #1519
|
||||
if not fit.ignoreRestrictions and hasRestrictionOverriden:
|
||||
clean = False
|
||||
if mod.fits(fit, False):
|
||||
if not mod.hardpoint:
|
||||
clean = True
|
||||
elif fit.getHardpointsFree(mod.hardpoint) >= 0:
|
||||
clean = True
|
||||
if clean:
|
||||
del mod.restrictionOverridden
|
||||
hasRestrictionOverriden = not hasRestrictionOverriden
|
||||
|
||||
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
|
||||
self.SetItemBackgroundColour(i, wx.Colour(204, 51, 51))
|
||||
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
|
||||
self.SetItemBackgroundColour(i, self.slotColour(mod.slot))
|
||||
@@ -695,11 +710,11 @@ class FittingView(d.Display):
|
||||
if isinstance(mod, Rack) and \
|
||||
sFit.serviceFittingOptions["rackSlots"] and \
|
||||
sFit.serviceFittingOptions["rackLabels"]:
|
||||
font.SetWeight(wx.FONTWEIGHT_BOLD)
|
||||
self.SetItemFont(i, font)
|
||||
self.font.SetWeight(wx.FONTWEIGHT_BOLD)
|
||||
self.SetItemFont(i, self.font)
|
||||
else:
|
||||
font.SetWeight(wx.FONTWEIGHT_NORMAL)
|
||||
self.SetItemFont(i, font)
|
||||
self.font.SetWeight(wx.FONTWEIGHT_NORMAL)
|
||||
self.SetItemFont(i, self.font)
|
||||
|
||||
self.Thaw()
|
||||
self.itemCount = self.GetItemCount()
|
||||
@@ -726,13 +741,12 @@ class FittingView(d.Display):
|
||||
# noinspection PyPropertyAccess
|
||||
def MakeSnapshot(self, maxColumns=1337):
|
||||
if self.FVsnapshot:
|
||||
del self.FVsnapshot
|
||||
self.FVsnapshot = None
|
||||
|
||||
tbmp = wx.Bitmap(16, 16)
|
||||
tdc = wx.MemoryDC()
|
||||
tdc.SelectObject(tbmp)
|
||||
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
|
||||
tdc.SetFont(font)
|
||||
tdc.SetFont(self.font)
|
||||
|
||||
columnsWidths = []
|
||||
for i in range(len(self.DEFAULT_COLS)):
|
||||
@@ -828,7 +842,7 @@ class FittingView(d.Display):
|
||||
mdc.SetBackground(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)))
|
||||
mdc.Clear()
|
||||
|
||||
mdc.SetFont(font)
|
||||
mdc.SetFont(self.font)
|
||||
mdc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
|
||||
|
||||
cx = padding
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -20,6 +20,7 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils import draw
|
||||
from gui.utils import color as color_utils
|
||||
from service.fit import Fit
|
||||
from gui.utils import fonts
|
||||
|
||||
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = 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_back_bitmap = None
|
||||
self.padding = 4
|
||||
self.font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
|
||||
self.font = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||
|
||||
self.tab_img = img
|
||||
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.transp = 0
|
||||
|
||||
hfont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
|
||||
hfont = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||
self.SetFont(hfont)
|
||||
|
||||
tx, ty = self.GetTextExtent(self.title)
|
||||
@@ -1384,7 +1385,7 @@ class PFNotebookPagePreview(wx.Frame):
|
||||
mdc.SetBackground(wx.Brush(color))
|
||||
mdc.Clear()
|
||||
|
||||
font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
|
||||
font = wx.Font(11, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||
mdc.SetFont(font)
|
||||
|
||||
x, y = mdc.GetTextExtent(self.title)
|
||||
|
||||
@@ -208,5 +208,6 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
|
||||
fighterAbilities,
|
||||
boosterSideEffects,
|
||||
commandFits,
|
||||
tabbedFits
|
||||
tabbedFits,
|
||||
mutaplasmids,
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
style=wx.DEFAULT_DIALOG_STYLE)
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "CREST", "MultiBuy"]
|
||||
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy"]
|
||||
copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
|
||||
CopySelectDialog.copyFormatEftImps: "EFT text format",
|
||||
CopySelectDialog.copyFormatXml: "EVE native XML format",
|
||||
|
||||
@@ -157,6 +157,19 @@ class EveFittings(wx.Frame):
|
||||
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):
|
||||
# todo: make this a generate excetpion handler for all calls
|
||||
def __init__(self, parentWindow, ex):
|
||||
@@ -231,6 +244,7 @@ class ExportToEve(wx.Frame):
|
||||
return self.charChoice.GetClientData(selection) if selection is not None else None
|
||||
|
||||
def exportFitting(self, event):
|
||||
sPort = Port.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
self.statusbar.SetStatusText("", 0)
|
||||
@@ -240,27 +254,32 @@ class ExportToEve(wx.Frame):
|
||||
return
|
||||
|
||||
self.statusbar.SetStatusText("Sending request and awaiting response", 1)
|
||||
sEsi = Esi.getInstance()
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
data = sPort.exportESI(sFit.getFit(fitID))
|
||||
res = sEsi.postFitting(self.getActiveCharacter(), data)
|
||||
|
||||
try:
|
||||
|
||||
res.raise_for_status()
|
||||
self.statusbar.SetStatusText("", 0)
|
||||
self.statusbar.SetStatusText("", 1)
|
||||
# try:
|
||||
# text = json.loads(res.text)
|
||||
# self.statusbar.SetStatusText(text['message'], 1)
|
||||
# except ValueError:
|
||||
# pyfalog.warning("Value error on loading JSON.")
|
||||
# self.statusbar.SetStatusText("", 1)
|
||||
self.statusbar.SetStatusText(res.reason, 1)
|
||||
except requests.exceptions.ConnectionError:
|
||||
msg = "Connection error, please check your internet connection"
|
||||
pyfalog.error(msg)
|
||||
self.statusbar.SetStatusText(msg)
|
||||
self.statusbar.SetStatusText("ERROR", 0)
|
||||
self.statusbar.SetStatusText(msg, 1)
|
||||
except ESIExportException as ex:
|
||||
pyfalog.error(ex)
|
||||
self.statusbar.SetStatusText("ERROR", 0)
|
||||
self.statusbar.SetStatusText(ex.args[0], 1)
|
||||
self.statusbar.SetStatusText("{} - {}".format(res.status_code, res.reason), 1)
|
||||
except APIException as ex:
|
||||
ESIExceptionHandler(self, ex)
|
||||
try:
|
||||
ESIExceptionHandler(self, ex)
|
||||
except Exception as ex:
|
||||
self.statusbar.SetStatusText("ERROR", 0)
|
||||
self.statusbar.SetStatusText("{} - {}".format(res.status_code, res.reason), 1)
|
||||
pyfalog.error(ex)
|
||||
|
||||
|
||||
class SsoCharacterMgmt(wx.Dialog):
|
||||
@@ -319,10 +338,12 @@ class SsoCharacterMgmt(wx.Dialog):
|
||||
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
|
||||
|
||||
@staticmethod
|
||||
def addChar(event):
|
||||
sEsi = Esi.getInstance()
|
||||
sEsi.login()
|
||||
def addChar(self, event):
|
||||
try:
|
||||
sEsi = Esi.getInstance()
|
||||
sEsi.login()
|
||||
except Exception as ex:
|
||||
ESIServerExceptionHandler(self, ex)
|
||||
|
||||
def delChar(self, event):
|
||||
item = self.lcCharacters.GetFirstSelected()
|
||||
|
||||
@@ -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,
|
||||
@@ -101,7 +102,7 @@ class ItemStatsDialog(wx.Dialog):
|
||||
if "wxGTK" in wx.PlatformInfo:
|
||||
self.closeBtn = wx.Button(self, wx.ID_ANY, "Close", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.mainSizer.Add(self.closeBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 5)
|
||||
self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent)
|
||||
self.closeBtn.Bind(wx.EVT_BUTTON, (lambda e: self.Close()))
|
||||
|
||||
self.SetSizer(self.mainSizer)
|
||||
|
||||
@@ -146,7 +147,7 @@ class ItemStatsDialog(wx.Dialog):
|
||||
ItemStatsDialog.counter -= 1
|
||||
self.parentWnd.UnregisterStatsWindow(self)
|
||||
|
||||
self.Destroy()
|
||||
event.Skip()
|
||||
|
||||
|
||||
class ItemStatsContainer(wx.Panel):
|
||||
@@ -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, "Mutations")
|
||||
|
||||
self.desc = ItemDescription(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.desc, "Description")
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ except ImportError as e:
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
pyfalog.debug("Done loading mainframe imports")
|
||||
|
||||
|
||||
# dummy panel(no paint no erasebk)
|
||||
class PFPanel(wx.Panel):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -90,7 +90,7 @@ class exportHtmlThread(threading.Thread):
|
||||
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" />
|
||||
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||
<script>
|
||||
// http://stackoverflow.com/questions/32453806/uncaught-securityerror-failed-to-execute-replacestate-on-history-cannot-be
|
||||
//http://stackoverflow.com/questions/32453806/uncaught-securityerror-failed-to-execute-replacestate-on-history-cannot-be
|
||||
$(document).bind('mobileinit',function(){
|
||||
$.mobile.changePage.defaults.changeHash = false;
|
||||
$.mobile.hashListeningEnabled = false;
|
||||
@@ -195,55 +195,51 @@ class exportHtmlThread(threading.Thread):
|
||||
|
||||
if len(fits) > 0:
|
||||
groupFits += len(fits)
|
||||
HTMLship = (
|
||||
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" '
|
||||
'data-corners="false">\n'
|
||||
' <h2>' + ship.name + ' <span class="ui-li-count">' + str(
|
||||
len(fits)) + '</span></h2>\n'
|
||||
' <ul data-role="listview" data-shadow="false" data-inset="true" '
|
||||
'data-corners="false">\n'
|
||||
)
|
||||
|
||||
if len(fits) == 1:
|
||||
for fit in fits:
|
||||
if self.stopRunning:
|
||||
return
|
||||
fit = fits[0]
|
||||
try:
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
HTMLgroup += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + ship.name + ": " + \
|
||||
fit[1] + '</a></li>\n'
|
||||
eftFit = Port.exportEft(getFit(fit[0]))
|
||||
print(eftFit)
|
||||
|
||||
HTMLfit = (
|
||||
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" '
|
||||
'data-corners="false">\n'
|
||||
' <h2>' + fit[1] + '</h2>\n'
|
||||
' <ul data-role="listview" data-shadow="false" data-inset="true" '
|
||||
'data-corners="false">\n'
|
||||
)
|
||||
|
||||
HTMLfit += ' <li><pre>' + eftFit + '\n </pre></li>\n'
|
||||
|
||||
HTMLfit += ' </ul>\n </li>\n'
|
||||
HTMLship += HTMLfit
|
||||
except:
|
||||
pyfalog.warning("Failed to export line")
|
||||
pass
|
||||
continue
|
||||
finally:
|
||||
if self.callback:
|
||||
wx.CallAfter(self.callback, count)
|
||||
count += 1
|
||||
else:
|
||||
# Ship group header
|
||||
HTMLship = (
|
||||
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" data-corners="false">\n'
|
||||
' <h2>' + ship.name + ' <span class="ui-li-count">' + str(
|
||||
len(fits)) + '</span></h2>\n'
|
||||
' <ul data-role="listview" data-shadow="false" data-inset="true" data-corners="false">\n'
|
||||
)
|
||||
|
||||
for fit in fits:
|
||||
if self.stopRunning:
|
||||
return
|
||||
try:
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
print(dnaFit)
|
||||
HTMLship += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + fit[
|
||||
1] + '</a></li>\n'
|
||||
except:
|
||||
pyfalog.warning("Failed to export line")
|
||||
continue
|
||||
finally:
|
||||
if self.callback:
|
||||
wx.CallAfter(self.callback, count)
|
||||
count += 1
|
||||
HTMLgroup += HTMLship + (' </ul>\n'
|
||||
' </li>\n')
|
||||
HTMLgroup += HTMLship + (' </ul>\n'
|
||||
' </li>\n')
|
||||
|
||||
if groupFits > 0:
|
||||
# Market group header
|
||||
HTML += (
|
||||
' <li data-role="collapsible" data-iconpos="right" data-shadow="false" data-corners="false">\n'
|
||||
' <h2>' + group.groupName + ' <span class="ui-li-count">' + str(groupFits) + '</span></h2>\n'
|
||||
' <ul data-role="listview" data-shadow="false" data-inset="true" data-corners="false">\n' + HTMLgroup +
|
||||
' <ul data-role="listview" data-shadow="false" data-inset="true" data-corners="false">\n' +
|
||||
HTMLgroup +
|
||||
' </ul>\n'
|
||||
' </li>'
|
||||
)
|
||||
@@ -279,7 +275,8 @@ class exportHtmlThread(threading.Thread):
|
||||
return
|
||||
try:
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' + ship.name + ': ' + \
|
||||
HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' \
|
||||
+ ship.name + ': ' + \
|
||||
fit[1] + '</a><br> \n'
|
||||
except:
|
||||
pyfalog.error("Failed to export line")
|
||||
|
||||
|
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 452 B |
BIN
imgs/icons/10012.png
Normal file
BIN
imgs/icons/10012.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user