Merge branch 'master' into wx3
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,8 +11,12 @@
|
||||
|
||||
#Patch files
|
||||
*.patch
|
||||
|
||||
#Personal
|
||||
/saveddata
|
||||
saveddata/
|
||||
|
||||
#PyCharm
|
||||
.idea/
|
||||
|
||||
#Pyfa file
|
||||
pyfaFits.html
|
||||
|
||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Pyfa
|
||||
|
||||
Pyfa is a cross-platform desktop fitting application for EVE online that can be used natively on any platform where python and wxwidgets are available.
|
||||
|
||||
It provides many advanced features such as graphs and full calculations of any possible combination of modules, fits, etc.
|
||||
|
||||
Please see the [FAQ](https://github.com/DarkFenX/Pyfa/wiki/FAQ) for answers to common questions / concerns
|
||||
|
||||
#### Links
|
||||
* [Development repository: http://github.com/DarkFenX/Pyfa](http://github.com/DarkFenX/Pyfa)
|
||||
* [XMPP conference:
|
||||
pyfa@conference.jabber.org](pyfa@conference.jabber.org)
|
||||
* [EVE forum thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609)
|
||||
* [EVE Online website](http://www.eveonline.com/)
|
||||
|
||||
@@ -13,15 +13,15 @@ debug = False
|
||||
saveInRoot = False
|
||||
|
||||
# Version data
|
||||
version = "1.6.1"
|
||||
tag = "git"
|
||||
expansionName = "Phoebe"
|
||||
version = "1.7.0"
|
||||
tag = "Stable"
|
||||
expansionName = "Rhea"
|
||||
expansionVersion = "1.0"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
# Database version (int ONLY)
|
||||
# Increment every time we need to flag for user database upgrade/modification
|
||||
dbversion = 1
|
||||
dbversion = 3
|
||||
|
||||
pyfaPath = None
|
||||
savePath = None
|
||||
|
||||
@@ -40,6 +40,15 @@ gamedata_meta = MetaData()
|
||||
gamedata_meta.bind = gamedata_engine
|
||||
gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)()
|
||||
|
||||
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
|
||||
# game db because we haven't reached gamedata_meta.create_all()
|
||||
try:
|
||||
config.gamedata_version = gamedata_session.execute(
|
||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
|
||||
).fetchone()[0]
|
||||
except:
|
||||
config.gamedata_version = None
|
||||
|
||||
saveddata_connectionstring = config.saveddata_connectionstring
|
||||
if saveddata_connectionstring is not None:
|
||||
if callable(saveddata_connectionstring):
|
||||
@@ -65,7 +74,8 @@ from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithS
|
||||
getCharacterList, getPrice, getDamagePatternList, getDamagePattern, \
|
||||
getFitList, getFleetList, getFleet, save, remove, commit, add, \
|
||||
getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \
|
||||
getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists
|
||||
getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists,\
|
||||
clearPrices
|
||||
|
||||
#If using in memory saveddata, you'll want to reflect it so the data structure is good.
|
||||
if config.saveddata_connectionstring == "sqlite:///:memory:":
|
||||
@@ -74,3 +84,4 @@ if config.saveddata_connectionstring == "sqlite:///:memory:":
|
||||
def rollback():
|
||||
with sd_lock:
|
||||
saveddata_session.rollback()
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("typeName", String, index=True),
|
||||
Column("description", String),
|
||||
Column("raceID", Integer),
|
||||
Column("factionID", Integer),
|
||||
Column("volume", Float),
|
||||
Column("mass", Float),
|
||||
Column("capacity", Float),
|
||||
|
||||
@@ -23,7 +23,7 @@ from eos.types import MetaData
|
||||
from eos.db import gamedata_meta
|
||||
|
||||
metadata_table = Table("metadata", gamedata_meta,
|
||||
Column("fieldName", String, primary_key=True),
|
||||
Column("fieldValue", String))
|
||||
Column("field_name", String, primary_key=True),
|
||||
Column("field_value", String))
|
||||
|
||||
mapper(MetaData, metadata_table)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
Migration 1
|
||||
|
||||
- Includes old upgrade paths pre-1.5.0
|
||||
- Alters fits table to introduce target resist attribute
|
||||
- Converts modules based on Oceanus Module Tiericide
|
||||
Some modules have been deleted, which causes pyfa to crash when fits are
|
||||
@@ -84,20 +83,6 @@ CONVERSIONS = {
|
||||
}
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
|
||||
# Update characters schema to include default chars (pre-1.5.0 migration)
|
||||
try:
|
||||
saveddata_engine.execute("SELECT defaultChar, chars FROM characters LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE characters ADD COLUMN defaultChar INTEGER;")
|
||||
saveddata_engine.execute("ALTER TABLE characters ADD COLUMN chars VARCHAR;")
|
||||
|
||||
# Update fits schema to include booster attribute (pre-1.5.0 migration)
|
||||
try:
|
||||
saveddata_engine.execute("SELECT booster FROM fits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN booster BOOLEAN;")
|
||||
|
||||
# Update fits schema to include target resists attribute
|
||||
try:
|
||||
saveddata_engine.execute("SELECT targetResistsID FROM fits LIMIT 1")
|
||||
|
||||
21
eos/db/migrations/upgrade2.py
Normal file
21
eos/db/migrations/upgrade2.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Migration 2
|
||||
|
||||
- Includes old upgrade paths pre-1.5.0. See GH issue #190 for why this is needed
|
||||
"""
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Update characters schema to include default chars
|
||||
try:
|
||||
saveddata_engine.execute("SELECT defaultChar, chars FROM characters LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE characters ADD COLUMN defaultChar INTEGER")
|
||||
saveddata_engine.execute("ALTER TABLE characters ADD COLUMN chars VARCHAR")
|
||||
|
||||
# Update fits schema to include booster attribute
|
||||
try:
|
||||
saveddata_engine.execute("SELECT booster FROM fits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN booster BOOLEAN")
|
||||
13
eos/db/migrations/upgrade3.py
Normal file
13
eos/db/migrations/upgrade3.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Migration 3
|
||||
|
||||
- Adds mode column for fits (t3 dessy)
|
||||
"""
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT mode FROM fits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN modeID INTEGER")
|
||||
@@ -40,7 +40,9 @@ fits_table = Table("fits", saveddata_meta,
|
||||
Column("characterID", ForeignKey("characters.ID"), nullable = True),
|
||||
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
|
||||
Column("booster", Boolean, nullable = False, index = True, default = 0),
|
||||
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True))
|
||||
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
||||
Column("modeID", Integer, nullable=True),
|
||||
)
|
||||
|
||||
projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
|
||||
|
||||
@@ -308,6 +308,12 @@ def getPrice(typeID):
|
||||
raise TypeError("Need integer as argument")
|
||||
return price
|
||||
|
||||
def clearPrices():
|
||||
with sd_lock:
|
||||
deleted_rows = saveddata_session.query(Price).delete()
|
||||
commit()
|
||||
return deleted_rows
|
||||
|
||||
def getMiscData(field):
|
||||
if isinstance(field, basestring):
|
||||
with sd_lock:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# drawbackArmorHP
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Rig Navigation (48 of 68)
|
||||
# Modules from group: Rig Navigation (48 of 64)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.boostItemAttr("armorHP", module.getModifiedItemAttr("drawback"))
|
||||
@@ -1,7 +1,7 @@
|
||||
# drawbackWarpSpeed
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Higgs Anchor I (4 of 4)
|
||||
# Modules from group: Rig Anchor (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.boostItemAttr("warpSpeedMultiplier", module.getModifiedItemAttr("drawback"), stackingPenalties=True)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtMD1ProjDmgBonus
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Naglfar
|
||||
# Ships named like: Naglfar (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Minmatar Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtMD3ProjRoFBonus
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Naglfar
|
||||
# Ships named like: Naglfar (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Minmatar Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtShipBonusHybridDmgG1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Moros
|
||||
# Ships named like: Moros (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Gallente Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtShipBonusHybridRoFG2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Moros
|
||||
# Ships named like: Moros (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Gallente Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtShipBonusLaserCapNeedA1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Revelation
|
||||
# Ships named like: Revelation (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Amarr Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtShipBonusLaserRofA2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Revelation
|
||||
# Ships named like: Revelation (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Amarr Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# dreadnoughtShipBonusShieldResistancesC2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Phoenix
|
||||
# Ships named like: Phoenix (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Caldari Dreadnought").level
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# evasiveManeuveringAgilityBonusPostPercentAgilityShip
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Rig Anchor (4 of 4)
|
||||
# Implants named like: Eifyr and Co. 'Rogue' Evasive Maneuvering EM (6 of 6)
|
||||
# Implants named like: grade Nomad (10 of 12)
|
||||
# Modules named like: Higgs Anchor I (4 of 4)
|
||||
# Modules named like: Low Friction Nozzle Joints (8 of 8)
|
||||
# Implant: Genolution Core Augmentation CA-4
|
||||
# Skill: Evasive Maneuvering
|
||||
|
||||
8
eos/effects/freighteragilitybonus2o2.py
Normal file
8
eos/effects/freighteragilitybonus2o2.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# freighterAgilityBonus2O2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Bowhead
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("ORE Freighter").level
|
||||
fit.ship.boostItemAttr("shipMaintenanceBayCapacity", ship.getModifiedItemAttr("freighterBonusO1")*level)
|
||||
9
eos/effects/freightersmacapacitybonuso1.py
Normal file
9
eos/effects/freightersmacapacitybonuso1.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# freighterSMACapacityBonusO1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Bowhead
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("ORE Freighter").level
|
||||
fit.ship.boostItemAttr("agility", ship.getModifiedItemAttr("freighterBonusO2")*level,
|
||||
stackingPenalties = True)
|
||||
@@ -1,7 +1,7 @@
|
||||
# massReductionBonusPassive
|
||||
#
|
||||
# Used by:
|
||||
# Modules named like: Higgs Anchor I (4 of 4)
|
||||
# Modules from group: Rig Anchor (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.boostItemAttr("mass", module.getModifiedItemAttr("massBonusPercentage"), stackingPenalties=True)
|
||||
|
||||
8
eos/effects/modeagilitypostdiv.py
Normal file
8
eos/effects/modeagilitypostdiv.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# modeAgilityPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Propulsion Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr("agility", 1/module.getModifiedItemAttr("modeAgilityPostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
15
eos/effects/modearmorresonancepostdiv.py
Normal file
15
eos/effects/modearmorresonancepostdiv.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# modeArmorResonancePostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Defense Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
for resType in ("Em", "Explosive", "Kinetic"):
|
||||
fit.ship.multiplyItemAttr("armor{0}DamageResonance".format(resType),
|
||||
1/module.getModifiedItemAttr("mode{0}ResistancePostDiv".format(resType)),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
|
||||
# Thermal != Thermic
|
||||
fit.ship.multiplyItemAttr("armorThermalDamageResonance",
|
||||
1/module.getModifiedItemAttr("modeThermicResistancePostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
9
eos/effects/modesigradiuspostdiv.py
Normal file
9
eos/effects/modesigradiuspostdiv.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# modeSigRadiusPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Defense Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
level = fit.character.getSkill("Minmatar Destroyer").level
|
||||
fit.ship.multiplyItemAttr("signatureRadius", 1/module.getModifiedItemAttr("modeSignatureRadiusPostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
8
eos/effects/modevelocitypostdiv.py
Normal file
8
eos/effects/modevelocitypostdiv.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# modeVelocityPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Propulsion Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr("maxVelocity", 1/module.getModifiedItemAttr("modeVelocityPostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
@@ -1,9 +1,9 @@
|
||||
# navigationVelocityBonusPostPercentMaxVelocityShip
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Rig Anchor (4 of 4)
|
||||
# Implants named like: grade Snake (16 of 18)
|
||||
# Modules named like: Auxiliary Thrusters (8 of 8)
|
||||
# Modules named like: Higgs Anchor I (4 of 4)
|
||||
# Implant: Quafe Zero
|
||||
# Skill: Navigation
|
||||
type = "passive"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# probeLauncherCPUPercentBonusTacticalDestroyer
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Confessor
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Astrometrics"),
|
||||
"cpu", ship.getModifiedItemAttr("roleBonusTacticalDestroyer1"))
|
||||
@@ -1,7 +1,7 @@
|
||||
# roleBonusBulkheadCPU
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Freighter (4 of 4)
|
||||
# Ships from group: Freighter (4 of 5)
|
||||
# Ships from group: Jump Freighter (4 of 4)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipAdvancedSpaceshipCommandAgilityBonus
|
||||
#
|
||||
# Used by:
|
||||
# Items from market group: Ships > Capital Ships (27 of 29)
|
||||
# Items from market group: Ships > Capital Ships (32 of 34)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
skill = fit.character.getSkill("Advanced Spaceship Command")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipBonusDreadCitadelCruiseRofC1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Phoenix
|
||||
# Ships named like: Phoenix (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Caldari Dreadnought").level
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipBonusDreadCitadelTorpRofC1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Phoenix
|
||||
# Ships named like: Phoenix (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Caldari Dreadnought").level
|
||||
|
||||
9
eos/effects/shipheatdamageamarrtacticaldestroyer3.py
Normal file
9
eos/effects/shipheatdamageamarrtacticaldestroyer3.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# shipHeatDamageAmarrTacticalDestroyer3
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Confessor
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Amarr Tactical Destroyer").level
|
||||
fit.modules.filteredItemBoost(lambda mod: True, "heatDamage",
|
||||
ship.getModifiedItemAttr("shipBonusTacticalDestroyerAmarr3") * level)
|
||||
8
eos/effects/shipmodemaxtargetrangepostdiv.py
Normal file
8
eos/effects/shipmodemaxtargetrangepostdiv.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# shipModeMaxTargetRangePostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Sharpshooter Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr("maxTargetRange", 1/module.getModifiedItemAttr("modeMaxTargetRangePostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
8
eos/effects/shipmodescanrespostdiv.py
Normal file
8
eos/effects/shipmodescanrespostdiv.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# shipModeScanResPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Sharpshooter Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr("scanResolution", 1/module.getModifiedItemAttr("modeScanResPostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
8
eos/effects/shipmodescanstrengthpostdiv.py
Normal file
8
eos/effects/shipmodescanstrengthpostdiv.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# shipModeScanStrengthPostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Sharpshooter Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.ship.multiplyItemAttr("scanRadarStrength", 1/module.getModifiedItemAttr("modeRadarStrengthPostDiv"),
|
||||
stackingPenalties = True, penaltyGroup="postDiv")
|
||||
9
eos/effects/shipmodesetoptimalrangepostdiv.py
Normal file
9
eos/effects/shipmodesetoptimalrangepostdiv.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# shipModeSETOptimalRangePostDiv
|
||||
#
|
||||
# Used by:
|
||||
# Module: Amarr Tactical Destroyer Sharpshooter Mode
|
||||
type = "passive"
|
||||
def handler(fit, module, context):
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill("Small Energy Turret"),
|
||||
"maxRange", 1/module.getModifiedItemAttr("modeMaxRangePostDiv"),
|
||||
stackingPenalties=True, penaltyGroup="postDiv")
|
||||
9
eos/effects/shipsetcapneedamarrtacticaldestroyer2.py
Normal file
9
eos/effects/shipsetcapneedamarrtacticaldestroyer2.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# shipSETCapNeedAmarrTacticalDestroyer2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Confessor
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Amarr Tactical Destroyer").level
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Energy Turret"),
|
||||
"capacitorNeed", ship.getModifiedItemAttr("shipBonusTacticalDestroyerAmarr2") * level)
|
||||
9
eos/effects/shipsetdamageamarrtacticaldestroyer1.py
Normal file
9
eos/effects/shipsetdamageamarrtacticaldestroyer1.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# shipSETDamageAmarrTacticalDestroyer1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Confessor
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
level = fit.character.getSkill("Amarr Tactical Destroyer").level
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Energy Turret"),
|
||||
"damageMultiplier", ship.getModifiedItemAttr("shipBonusTacticalDestroyerAmarr1") * level)
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipXLProjectileDamageRole
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Naglfar
|
||||
# Ships named like: Naglfar (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capital Projectile Turret"),
|
||||
|
||||
@@ -28,7 +28,7 @@ import traceback
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
class Effect(EqBase):
|
||||
'''
|
||||
@@ -235,47 +235,63 @@ class Item(EqBase):
|
||||
requiredSkills[item] = skillLvl
|
||||
return self.__requiredSkills
|
||||
|
||||
factionMap = {
|
||||
500001: "caldari",
|
||||
500002: "minmatar",
|
||||
500003: "amarr",
|
||||
500004: "gallente",
|
||||
500005: "jove",
|
||||
500010: "guristas",
|
||||
500011: "angel",
|
||||
500012: "blood",
|
||||
500014: "ore",
|
||||
500016: "sisters",
|
||||
500018: "mordu",
|
||||
500019: "sansha",
|
||||
500020: "serpentis"
|
||||
}
|
||||
|
||||
@property
|
||||
def race(self):
|
||||
if self.__race is None:
|
||||
# Define race map
|
||||
map = {1: "caldari",
|
||||
2: "minmatar",
|
||||
4: "amarr",
|
||||
5: "sansha", # Caldari + Amarr
|
||||
6: "blood", # Minmatar + Amarr
|
||||
8: "gallente",
|
||||
9: "guristas", # Caldari + Gallente
|
||||
10: "angelserp", # Minmatar + Gallente, final race depends on the order of skills
|
||||
12: "sisters", # Amarr + Gallente
|
||||
16: "jove",
|
||||
32: "sansha", # Incrusion Sansha
|
||||
128: "ore"}
|
||||
# Race is None by default
|
||||
race = None
|
||||
# Check primary and secondary required skills' races
|
||||
if race is None:
|
||||
# Currently Assault Frigates skill has raceID set, which is actually
|
||||
# EVE's bug
|
||||
ignoredSkills = ('Assault Frigates',)
|
||||
skills = tuple(filter(lambda i: i.name not in ignoredSkills, self.requiredSkills.keys()))
|
||||
skillRaces = tuple(filter(lambda rid: rid, (s.raceID for s in skills)))
|
||||
if sum(skillRaces) in map:
|
||||
race = map[sum(skillRaces)]
|
||||
if race == "angelserp":
|
||||
if skillRaces == (2, 8):
|
||||
race = "angel"
|
||||
else:
|
||||
race = "serpentis"
|
||||
|
||||
# Rely on item's own raceID as last resort
|
||||
if race is None:
|
||||
race = map.get(self.raceID, None)
|
||||
|
||||
# Store our final value
|
||||
self.__race = race
|
||||
try:
|
||||
self.__race = self.factionMap[self.factionID]
|
||||
# Some ships (like few limited issue ships) do not have factionID set,
|
||||
# thus keep old mechanism for now
|
||||
except KeyError:
|
||||
# Define race map
|
||||
map = {1: "caldari",
|
||||
2: "minmatar",
|
||||
4: "amarr",
|
||||
5: "sansha", # Caldari + Amarr
|
||||
6: "blood", # Minmatar + Amarr
|
||||
8: "gallente",
|
||||
9: "guristas", # Caldari + Gallente
|
||||
10: "angelserp", # Minmatar + Gallente, final race depends on the order of skills
|
||||
12: "sisters", # Amarr + Gallente
|
||||
16: "jove",
|
||||
32: "sansha", # Incrusion Sansha
|
||||
128: "ore"}
|
||||
# Race is None by default
|
||||
race = None
|
||||
# Check primary and secondary required skills' races
|
||||
if race is None:
|
||||
skillRaces = tuple(filter(lambda rid: rid, (s.raceID for s in tuple(self.requiredSkills.keys()))))
|
||||
if sum(skillRaces) in map:
|
||||
race = map[sum(skillRaces)]
|
||||
if race == "angelserp":
|
||||
if skillRaces == (2, 8):
|
||||
race = "angel"
|
||||
else:
|
||||
race = "serpentis"
|
||||
# Rely on item's own raceID as last resort
|
||||
if race is None:
|
||||
race = map.get(self.raceID, None)
|
||||
# Store our final value
|
||||
self.__race = race
|
||||
return self.__race
|
||||
|
||||
|
||||
@property
|
||||
def assistive(self):
|
||||
"""Detects if item can be used as assistance"""
|
||||
@@ -325,9 +341,7 @@ class Item(EqBase):
|
||||
return False
|
||||
|
||||
class MetaData(EqBase):
|
||||
def __init__(self, name, val=None):
|
||||
self.fieldName = name
|
||||
self.fieldValue = val
|
||||
pass
|
||||
|
||||
class EffectInfo(EqBase):
|
||||
pass
|
||||
|
||||
@@ -50,10 +50,13 @@ class FitDpsGraph(Graph):
|
||||
|
||||
for attr, values in ew.iteritems():
|
||||
val = data[attr]
|
||||
for i in xrange(len(values)):
|
||||
bonus = values[i]
|
||||
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
|
||||
data[attr] = val
|
||||
try:
|
||||
for i in xrange(len(values)):
|
||||
bonus = values[i]
|
||||
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
|
||||
data[attr] = val
|
||||
except:
|
||||
pass
|
||||
|
||||
for mod in fit.modules:
|
||||
dps, _ = mod.damageStats(fit.targetResists)
|
||||
@@ -68,9 +71,8 @@ class FitDpsGraph(Graph):
|
||||
if distance <= fit.extraAttributes["droneControlRange"]:
|
||||
for drone in fit.drones:
|
||||
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 0 else self.calculateTurretMultiplier(drone, data)
|
||||
dps, _ = drone.damageStats(fit.targetResists)
|
||||
dps = drone.damageStats(fit.targetResists)
|
||||
total += dps * multiplier
|
||||
|
||||
return total
|
||||
|
||||
def calculateMissileMultiplier(self, mod, data):
|
||||
@@ -87,6 +89,7 @@ class FitDpsGraph(Graph):
|
||||
velocityFactor = (explosionVelocity / explosionRadius * targetSigRad / targetVelocity) ** (log(damageReductionFactor) / log(damageReductionSensitivity))
|
||||
else:
|
||||
velocityFactor = 1
|
||||
|
||||
return min(sigRadiusFactor, velocityFactor, 1)
|
||||
|
||||
def calculateTurretMultiplier(self, mod, data):
|
||||
|
||||
@@ -35,6 +35,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.amount = 0
|
||||
self.amountActive = 0
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__miningyield = None
|
||||
self.projected = False
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
@@ -43,6 +44,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__miningyield = None
|
||||
self.__item = None
|
||||
self.__charge = None
|
||||
@@ -115,6 +117,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
def damageStats(self, targetResists = None):
|
||||
if self.__dps == None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage is True and self.amountActive > 0:
|
||||
if self.hasAmmo:
|
||||
attr = "missileLaunchDuration"
|
||||
@@ -128,11 +132,10 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
volley = sum(map(lambda d: (getter("%sDamage"%d) or 0) * (1-getattr(targetResists, "%sAmount"%d, 0)), self.DAMAGE_TYPES))
|
||||
volley *= self.amountActive
|
||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
self.__volley = volley
|
||||
self.__dps = volley / (cycleTime / 1000.0)
|
||||
else:
|
||||
self.__dps = 0
|
||||
|
||||
return self.__dps
|
||||
return self.__dps, self.__volley
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
@@ -186,6 +189,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -27,12 +27,13 @@ from copy import deepcopy
|
||||
from math import sqrt, log, asinh
|
||||
from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill
|
||||
from eos.saveddata.module import State
|
||||
from eos.saveddata.mode import Mode
|
||||
import time
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
class Fit(object):
|
||||
"""Represents a fitting, with modules, ship, implants, etc."""
|
||||
@@ -66,6 +67,7 @@ class Fit(object):
|
||||
self.gangBoosts = None
|
||||
self.timestamp = time.time()
|
||||
self.ecmProjectedStr = 1
|
||||
self.modeID = None
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
@@ -80,6 +82,7 @@ class Fit(object):
|
||||
self.__minerYield = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__sustainableTank = None
|
||||
self.__effectiveSustainableTank = None
|
||||
@@ -98,6 +101,10 @@ class Fit(object):
|
||||
self.extraAttributes = ModifiedAttributeDict(self)
|
||||
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
|
||||
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
|
||||
if self.ship is not None:
|
||||
self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
|
||||
else:
|
||||
self.mode = None
|
||||
|
||||
@property
|
||||
def targetResists(self):
|
||||
@@ -109,6 +116,7 @@ class Fit(object):
|
||||
self.__weaponDPS = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__droneVolley = None
|
||||
|
||||
@property
|
||||
def damagePattern(self):
|
||||
@@ -120,6 +128,15 @@ class Fit(object):
|
||||
self.__ehp = None
|
||||
self.__effectiveTank = None
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
return self._mode
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode):
|
||||
self._mode = mode
|
||||
self.modeID = mode.item.ID if mode is not None else None
|
||||
|
||||
@property
|
||||
def character(self):
|
||||
return self.__character if self.__character is not None else Character.getAll0()
|
||||
@@ -190,10 +207,21 @@ class Fit(object):
|
||||
|
||||
return self.__droneDPS
|
||||
|
||||
@property
|
||||
def droneVolley(self):
|
||||
if self.__droneVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
|
||||
return self.__droneVolley
|
||||
|
||||
@property
|
||||
def totalDPS(self):
|
||||
return self.droneDPS + self.weaponDPS
|
||||
|
||||
@property
|
||||
def totalVolley(self):
|
||||
return self.droneVolley + self.weaponVolley
|
||||
|
||||
@property
|
||||
def minerYield(self):
|
||||
if self.__minerYield is None:
|
||||
@@ -279,6 +307,7 @@ class Fit(object):
|
||||
self.__effectiveSustainableTank = None
|
||||
self.__sustainableTank = None
|
||||
self.__droneDPS = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__ehp = None
|
||||
self.__calculated = False
|
||||
@@ -355,9 +384,9 @@ class Fit(object):
|
||||
# Avoid adding projected drones and modules when fit is projected onto self
|
||||
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
|
||||
if forceProjected is True:
|
||||
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules)
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules)
|
||||
else:
|
||||
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
self.projectedDrones, self.projectedModules)
|
||||
|
||||
if self.gangBoosts is not None:
|
||||
@@ -480,6 +509,10 @@ class Fit(object):
|
||||
Slot.RIG: "rigSlots",
|
||||
Slot.SUBSYSTEM: "maxSubSystems"}
|
||||
|
||||
if type == Slot.MODE:
|
||||
# Mode slot doesn't really exist, return default 0
|
||||
return 0
|
||||
|
||||
slotsUsed = self.getSlotsUsed(type, countDummies)
|
||||
totalSlots = self.ship.getModifiedItemAttr(slots[type]) or 0
|
||||
return int(totalSlots - slotsUsed)
|
||||
@@ -821,6 +854,7 @@ class Fit(object):
|
||||
weaponDPS = 0
|
||||
droneDPS = 0
|
||||
weaponVolley = 0
|
||||
droneVolley = 0
|
||||
|
||||
for mod in self.modules:
|
||||
dps, volley = mod.damageStats(self.targetResists)
|
||||
@@ -828,11 +862,14 @@ class Fit(object):
|
||||
weaponVolley += volley
|
||||
|
||||
for drone in self.drones:
|
||||
droneDPS += drone.damageStats(self.targetResists)
|
||||
dps, volley = drone.damageStats(self.targetResists)
|
||||
droneDPS += dps
|
||||
droneVolley += volley
|
||||
|
||||
self.__weaponDPS = weaponDPS
|
||||
self.__weaponVolley = weaponVolley
|
||||
self.__droneDPS = droneDPS
|
||||
self.__droneVolley = droneVolley
|
||||
|
||||
@property
|
||||
def fits(self):
|
||||
|
||||
65
eos/saveddata/mode.py
Normal file
65
eos/saveddata/mode.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 eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
|
||||
class Mode(ItemAttrShortcut, HandledItem):
|
||||
|
||||
def __init__(self, item):
|
||||
self.__item = item
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
|
||||
if not isinstance(item, int):
|
||||
self.__buildOriginal()
|
||||
|
||||
def __fetchItemInfo(self):
|
||||
import eos.db
|
||||
self.__item = eos.db.getItem(self.__item)
|
||||
self.__buildOriginal()
|
||||
|
||||
def __buildOriginal(self):
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__item
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
if isinstance(self.__item, int):
|
||||
self.__fetchItemInfo()
|
||||
|
||||
return self.__itemModifiedAttributes
|
||||
|
||||
# @todo: rework to fit only on t3 dessy
|
||||
def fits(self, fit):
|
||||
raise NotImplementedError()
|
||||
|
||||
def clear(self):
|
||||
self.itemModifiedAttributes.clear()
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected = False):
|
||||
if self.item:
|
||||
for effect in self.item.effects.itervalues():
|
||||
if effect.runTime == runTime:
|
||||
effect.handler(fit, self, context = ("module",))
|
||||
@@ -36,6 +36,7 @@ class Slot(Enum):
|
||||
HIGH = 3
|
||||
RIG = 4
|
||||
SUBSYSTEM = 5
|
||||
MODE = 6 # not a real slot, need for pyfa display rack separation
|
||||
|
||||
class Hardpoint(Enum):
|
||||
NONE = 0
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
from eos.saveddata.mode import Mode
|
||||
|
||||
class Ship(ItemAttrShortcut, HandledItem):
|
||||
def __init__(self, item):
|
||||
@@ -65,6 +66,52 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
if effect.runTime == runTime and effect.isType("passive"):
|
||||
effect.handler(fit, self, ("ship",))
|
||||
|
||||
def checkModeItem(self, item):
|
||||
"""
|
||||
Checks if provided item is a valid mode.
|
||||
|
||||
If ship has modes, and current item is not valid, return forced mode
|
||||
else if mode is valid, return Mode
|
||||
else if ship does not have modes, return None
|
||||
|
||||
@todo: rename this
|
||||
"""
|
||||
items = self.getModeItems()
|
||||
|
||||
if items != None:
|
||||
if item == None or item not in items:
|
||||
# We have a tact dessy, but mode is None or not valid. Force new mode
|
||||
return Mode(items[0])
|
||||
elif item in items:
|
||||
# We have a valid mode
|
||||
return Mode(item)
|
||||
return None
|
||||
|
||||
def getModes(self):
|
||||
items = self.getModeItems()
|
||||
return [Mode(item) for item in items] if items else None
|
||||
|
||||
def getModeItems(self):
|
||||
"""
|
||||
Returns a list of valid mode items for ship. Note that this returns the
|
||||
valid Item objects, not the Mode objects. Returns None if not a
|
||||
t3 dessy
|
||||
"""
|
||||
# @todo: is there a better way to determine this that isn't hardcoded groupIDs?
|
||||
if self.item.groupID != 1305:
|
||||
return None
|
||||
|
||||
modeGroupID = 1306
|
||||
import eos.db
|
||||
|
||||
items = []
|
||||
g = eos.db.getGroup(modeGroupID, eager=("items.icon", "items.attributes"))
|
||||
for item in g.items:
|
||||
if item.raceID == self.item.raceID:
|
||||
items.append(item)
|
||||
|
||||
return items
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Ship(self.item)
|
||||
return copy
|
||||
|
||||
@@ -32,6 +32,7 @@ from eos.saveddata.booster import SideEffect
|
||||
from eos.saveddata.booster import Booster
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.mode import Mode
|
||||
from eos.saveddata.fleet import Fleet, Wing, Squad
|
||||
from eos.saveddata.miscData import MiscData
|
||||
import eos.db
|
||||
|
||||
@@ -1,497 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010-2011 Anton Vorobyov
|
||||
#
|
||||
# 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/>.
|
||||
#===============================================================================
|
||||
|
||||
|
||||
'''
|
||||
This script is used to compare two different database versions.
|
||||
It shows removed/changed/new items with list of changed effects,
|
||||
changed attributes and effects which were renamed
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
parser = argparse.ArgumentParser(description="Compare two databases generated from eve dump to find eos-related differences")
|
||||
parser.add_argument("-o", "--old", type=str, required=True, help="path to old cache data dump")
|
||||
parser.add_argument("-n", "--new", type=str, required=True, help="path to new cache data dump")
|
||||
parser.add_argument("-g", "--nogroups", action="store_false", default=True, dest="groups", help="don't show changed groups")
|
||||
parser.add_argument("-e", "--noeffects", action="store_false", default=True, dest="effects", help="don't show list of changed effects")
|
||||
parser.add_argument("-a", "--noattributes", action="store_false", default=True, dest="attributes", help="don't show list of changed attributes")
|
||||
parser.add_argument("-r", "--norenames", action="store_false", default=True, dest="renames", help="don't show list of renamed data")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Open both databases and get their cursors
|
||||
old_db = sqlite3.connect(os.path.expanduser(args.old))
|
||||
old_cursor = old_db.cursor()
|
||||
new_db = sqlite3.connect(os.path.expanduser(args.new))
|
||||
new_cursor = new_db.cursor()
|
||||
|
||||
# Force some of the items to make them published
|
||||
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper")
|
||||
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName = ?'
|
||||
for typename in FORCEPUB_TYPES:
|
||||
old_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
new_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
|
||||
# Initialization of few things used by both changed/renamed effects list
|
||||
effectspath = os.path.join("..", "..", "effects")
|
||||
implemented = set()
|
||||
|
||||
for filename in os.listdir(effectspath):
|
||||
basename, extension = filename.rsplit('.', 1)
|
||||
# Ignore non-py files and exclude implementation-specific 'effect'
|
||||
if extension == "py" and basename not in ("__init__",):
|
||||
implemented.add(basename)
|
||||
|
||||
# Effects' names are used w/o any special symbols by eos
|
||||
stripspec = "[^A-Za-z0-9]"
|
||||
|
||||
# Method to get data if effect is implemented in eos or not
|
||||
def geteffst(effectname):
|
||||
eosname = re.sub(stripspec, "", effectname).lower()
|
||||
if eosname in implemented:
|
||||
impstate = True
|
||||
else:
|
||||
impstate = False
|
||||
return impstate
|
||||
|
||||
def findrenames(ren_dict, query, strip=False):
|
||||
|
||||
old_namedata = {}
|
||||
new_namedata = {}
|
||||
|
||||
for cursor, dictionary in ((old_cursor, old_namedata), (new_cursor, new_namedata)):
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
if strip is True:
|
||||
name = re.sub(stripspec, "", name)
|
||||
dictionary[id] = name
|
||||
|
||||
for id in set(old_namedata.keys()).intersection(new_namedata.keys()):
|
||||
oldname = old_namedata[id]
|
||||
newname = new_namedata[id]
|
||||
if oldname != newname:
|
||||
ren_dict[id] = (oldname, newname)
|
||||
return
|
||||
|
||||
def printrenames(ren_dict, title, implementedtag=False):
|
||||
if len(ren_dict) > 0:
|
||||
print('\nRenamed ' + title + ':')
|
||||
for id in sorted(ren_dict):
|
||||
couple = ren_dict[id]
|
||||
if implementedtag:
|
||||
print("\n[{0}] \"{1}\"\n[{2}] \"{3}\"".format(geteffst(couple[0]), couple[0], geteffst(couple[1]), couple[1]))
|
||||
else:
|
||||
print("\n\"{0}\"\n\"{1}\"".format(couple[0], couple[1]))
|
||||
|
||||
groupcats = {}
|
||||
def getgroupcat(grp):
|
||||
"""Get group category from the new db"""
|
||||
if grp in groupcats:
|
||||
cat = groupcats[grp]
|
||||
else:
|
||||
query = 'SELECT categoryID FROM invgroups WHERE groupID = ?'
|
||||
new_cursor.execute(query, (grp,))
|
||||
cat = 0
|
||||
for row in new_cursor:
|
||||
cat = row[0]
|
||||
groupcats[grp] = cat
|
||||
return cat
|
||||
|
||||
itemnames = {}
|
||||
def getitemname(item):
|
||||
"""Get item name from the new db"""
|
||||
if item in itemnames:
|
||||
name = itemnames[item]
|
||||
else:
|
||||
query = 'SELECT typeName FROM invtypes WHERE typeID = ?'
|
||||
new_cursor.execute(query, (item,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (item,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
itemnames[item] = name
|
||||
return name
|
||||
|
||||
groupnames = {}
|
||||
def getgroupname(grp):
|
||||
"""Get group name from the new db"""
|
||||
if grp in groupnames:
|
||||
name = groupnames[grp]
|
||||
else:
|
||||
query = 'SELECT groupName FROM invgroups WHERE groupID = ?'
|
||||
new_cursor.execute(query, (grp,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (grp,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
groupnames[grp] = name
|
||||
return name
|
||||
|
||||
effectnames = {}
|
||||
def geteffectname(effect):
|
||||
"""Get effect name from the new db"""
|
||||
if effect in effectnames:
|
||||
name = effectnames[effect]
|
||||
else:
|
||||
query = 'SELECT effectName FROM dgmeffects WHERE effectID = ?'
|
||||
new_cursor.execute(query, (effect,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (effect,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
effectnames[effect] = name
|
||||
return name
|
||||
|
||||
attrnames = {}
|
||||
def getattrname(attr):
|
||||
"""Get attribute name from the new db"""
|
||||
if attr in attrnames:
|
||||
name = attrnames[attr]
|
||||
else:
|
||||
query = 'SELECT attributeName FROM dgmattribs WHERE attributeID = ?'
|
||||
new_cursor.execute(query, (attr,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (attr,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
attrnames[attr] = name
|
||||
return name
|
||||
|
||||
# State table
|
||||
S = {"unchanged": 0,
|
||||
"removed": 1,
|
||||
"changed": 2,
|
||||
"added": 3 }
|
||||
|
||||
if args.effects or args.attributes or args.groups:
|
||||
# Format:
|
||||
# Key: item id
|
||||
# Value: [groupID, set(effects), {attribute id : value}]
|
||||
old_itmdata = {}
|
||||
new_itmdata = {}
|
||||
|
||||
for cursor, dictionary in ((old_cursor, old_itmdata), (new_cursor, new_itmdata)):
|
||||
# Compose list of items we're interested in, filtered by category
|
||||
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID INNER JOIN invcategories AS ic ON ig.categoryID = ic.categoryID WHERE it.published = 1 AND ic.categoryName IN ("Ship", "Module", "Charge", "Skill", "Drone", "Implant", "Subsystem")'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
groupID = row[1]
|
||||
# Initialize container for the data for each item with empty stuff besides groupID
|
||||
dictionary[itemid] = [groupID, set(), {}]
|
||||
# Add items filtered by group
|
||||
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID WHERE it.published = 1 AND ig.groupName IN ("Effect Beacon")'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
groupID = row[1]
|
||||
dictionary[itemid] = [groupID, set(), {}]
|
||||
|
||||
if args.effects:
|
||||
# Pull all eff
|
||||
query = 'SELECT it.typeID, de.effectID FROM invtypes AS it INNER JOIN dgmtypeeffects AS dte ON dte.typeID = it.typeID INNER JOIN dgmeffects AS de ON de.effectID = dte.effectID WHERE it.published = 1'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
effectID = row[1]
|
||||
# Process only items we need
|
||||
if itemid in dictionary:
|
||||
# Add effect to the set
|
||||
effectSet = dictionary[itemid][1]
|
||||
effectSet.add(effectID)
|
||||
|
||||
if args.attributes:
|
||||
# Add base attributes to our data
|
||||
query = 'SELECT it.typeID, it.mass, it.capacity, it.volume FROM invtypes AS it'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
if itemid in dictionary:
|
||||
attrdict = dictionary[itemid][2]
|
||||
# Add base attributes: mass (4), capacity (38) and volume (161)
|
||||
attrdict[4] = row[1]
|
||||
attrdict[38] = row[2]
|
||||
attrdict[161] = row[3]
|
||||
|
||||
# Add attribute data for other attributes
|
||||
query = 'SELECT dta.typeID, dta.attributeID, dta.value FROM dgmtypeattribs AS dta'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
if itemid in dictionary:
|
||||
attrid = row[1]
|
||||
attrval = row[2]
|
||||
attrdict = dictionary[itemid][2]
|
||||
if attrid in attrdict:
|
||||
print("Warning: base attribute is described in non-base attribute table")
|
||||
else:
|
||||
attrdict[attrid] = attrval
|
||||
|
||||
# Get set of IDs from both dictionaries
|
||||
items_old = set(old_itmdata.keys())
|
||||
items_new = set(new_itmdata.keys())
|
||||
|
||||
# Format:
|
||||
# Key: item state
|
||||
# Value: {item id: ((group state, old group, new group), {effect state: set(effects)}, {attribute state: {attributeID: (old value, new value)}})}
|
||||
global_itmdata = {}
|
||||
|
||||
# Initialize it
|
||||
for state in S:
|
||||
global_itmdata[S[state]] = {}
|
||||
|
||||
|
||||
# Fill all the data for removed items
|
||||
for item in items_old.difference(items_new):
|
||||
# Set item state to removed
|
||||
state = S["removed"]
|
||||
# Set only old group for item
|
||||
oldgroup = old_itmdata[item][0]
|
||||
groupdata = (S["unchanged"], oldgroup, None)
|
||||
# Set old set of effects and mark all as unchanged
|
||||
effectsdata = {}
|
||||
effectsdata[S["unchanged"]] = set()
|
||||
if args.effects:
|
||||
oldeffects = old_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(oldeffects)
|
||||
# Set old set of attributes and mark all as unchanged
|
||||
attrdata = {}
|
||||
attrdata[S["unchanged"]] = {}
|
||||
if args.attributes:
|
||||
oldattrs = old_itmdata[item][2]
|
||||
for attr in oldattrs:
|
||||
# NULL will mean there's no such attribute in db
|
||||
attrdata[S["unchanged"]][attr] = (oldattrs[attr], "NULL")
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
|
||||
# Now, for added items
|
||||
for item in items_new.difference(items_old):
|
||||
# Set item state to added
|
||||
state = S["added"]
|
||||
# Set only new group for item
|
||||
newgroup = new_itmdata[item][0]
|
||||
groupdata = (S["unchanged"], None, newgroup)
|
||||
# Set new set of effects and mark all as unchanged
|
||||
effectsdata = {}
|
||||
effectsdata[S["unchanged"]] = set()
|
||||
if args.effects:
|
||||
neweffects = new_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(neweffects)
|
||||
# Set new set of attributes and mark all as unchanged
|
||||
attrdata = {}
|
||||
attrdata[S["unchanged"]] = {}
|
||||
if args.attributes:
|
||||
newattrs = new_itmdata[item][2]
|
||||
for attr in newattrs:
|
||||
# NULL will mean there's no such attribute in db
|
||||
attrdata[S["unchanged"]][attr] = ("NULL", newattrs[attr])
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
# Now, check all the items which exist in both databases
|
||||
for item in items_old.intersection(items_new):
|
||||
# Set group data for an item
|
||||
oldgroup = old_itmdata[item][0]
|
||||
newgroup = new_itmdata[item][0]
|
||||
# If we're not asked to compare groups, mark them as unchanged anyway
|
||||
groupdata = (S["changed"] if oldgroup != newgroup and args.groups else S["unchanged"], oldgroup, newgroup)
|
||||
# Fill effects data into appropriate groups
|
||||
effectsdata = {}
|
||||
for state in S:
|
||||
# We do not have changed effects whatsoever
|
||||
if state != "changed":
|
||||
effectsdata[S[state]] = set()
|
||||
if args.effects:
|
||||
oldeffects = old_itmdata[item][1]
|
||||
neweffects = new_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(oldeffects.intersection(neweffects))
|
||||
effectsdata[S["removed"]].update(oldeffects.difference(neweffects))
|
||||
effectsdata[S["added"]].update(neweffects.difference(oldeffects))
|
||||
# Go through all attributes, filling global data dictionary
|
||||
attrdata = {}
|
||||
for state in S:
|
||||
attrdata[S[state]] = {}
|
||||
if args.attributes:
|
||||
oldattrs = old_itmdata[item][2]
|
||||
newattrs = new_itmdata[item][2]
|
||||
for attr in set(oldattrs.keys()).union(newattrs.keys()):
|
||||
# NULL will mean there's no such attribute in db
|
||||
oldattr = oldattrs.get(attr, "NULL")
|
||||
newattr = newattrs.get(attr, "NULL")
|
||||
attrstate = S["unchanged"]
|
||||
if oldattr == "NULL" and newattr != "NULL":
|
||||
attrstate = S["added"]
|
||||
elif oldattr != "NULL" and newattr == "NULL":
|
||||
attrstate = S["removed"]
|
||||
elif oldattr != newattr:
|
||||
attrstate = S["changed"]
|
||||
attrdata[attrstate][attr] = (oldattr, newattr)
|
||||
# Consider item as unchanged by default and set it to change when we see any changes in sub-items
|
||||
state = S["unchanged"]
|
||||
if state == S["unchanged"] and groupdata[0] != S["unchanged"]:
|
||||
state = S["changed"]
|
||||
if state == S["unchanged"] and (len(effectsdata[S["removed"]]) > 0 or len(effectsdata[S["added"]]) > 0):
|
||||
state = S["changed"]
|
||||
if state == S["unchanged"] and (len(attrdata[S["removed"]]) > 0 or len(attrdata[S["changed"]]) > 0 or len(attrdata[S["added"]]) > 0):
|
||||
state = S["changed"]
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
# As eos uses names as unique IDs in lot of places, we have to keep track of name changes
|
||||
if args.renames:
|
||||
ren_effects = {}
|
||||
query = 'SELECT effectID, effectName FROM dgmeffects'
|
||||
findrenames(ren_effects, query, strip = True)
|
||||
|
||||
ren_attributes = {}
|
||||
query = 'SELECT attributeID, attributeName FROM dgmattribs'
|
||||
findrenames(ren_attributes, query)
|
||||
|
||||
ren_categories = {}
|
||||
query = 'SELECT categoryID, categoryName FROM invcategories'
|
||||
findrenames(ren_categories, query)
|
||||
|
||||
ren_groups = {}
|
||||
query = 'SELECT groupID, groupName FROM invgroups'
|
||||
findrenames(ren_groups, query)
|
||||
|
||||
ren_marketgroups = {}
|
||||
query = 'SELECT marketGroupID, marketGroupName FROM invmarketgroups'
|
||||
findrenames(ren_marketgroups, query)
|
||||
|
||||
ren_items = {}
|
||||
query = 'SELECT typeID, typeName FROM invtypes'
|
||||
findrenames(ren_items, query)
|
||||
|
||||
# Get db metadata
|
||||
old_meta = {}
|
||||
new_meta = {}
|
||||
query = 'SELECT fieldName, fieldValue FROM metadata'
|
||||
old_cursor.execute(query)
|
||||
for row in old_cursor:
|
||||
old_meta[row[0]] = row[1]
|
||||
new_cursor.execute(query)
|
||||
for row in new_cursor:
|
||||
new_meta[row[0]] = row[1]
|
||||
|
||||
# Print jobs
|
||||
print("Comparing databases:\n{0}-{1}\n{2}-{3}\n".format(old_meta.get("version"), old_meta.get("release"),
|
||||
new_meta.get("version"), new_meta.get("release")))
|
||||
if args.effects or args.attributes or args.groups:
|
||||
# Print legend only when there're any interesting changes
|
||||
if len(global_itmdata[S["removed"]]) > 0 or len(global_itmdata[S["changed"]]) > 0 or len(global_itmdata[S["added"]]) > 0:
|
||||
genleg = "[+] - new item\n[-] - removed item\n[*] - changed item\n"
|
||||
grpleg = "(x => y) - group changes\n" if args.groups else ""
|
||||
attreffleg = " [+] - effect or attribute has been added to item\n [-] - effect or attribute has been removed from item\n" if args.attributes or args.effects else ""
|
||||
effleg = " [y] - effect is implemented\n [n] - effect is not implemented\n" if args.effects else ""
|
||||
print("{0}{1}{2}{3}\nItems:".format(genleg, grpleg, attreffleg, effleg))
|
||||
|
||||
# Make sure our states are sorted
|
||||
stateorder = sorted(global_itmdata)
|
||||
|
||||
TG = {S["unchanged"]: "+", S["changed"]: "*",
|
||||
S["removed"]: "-",
|
||||
S["added"]: "+"}
|
||||
|
||||
# Cycle through states
|
||||
for itmstate in stateorder:
|
||||
# Skip unchanged items
|
||||
if itmstate == S["unchanged"]:
|
||||
continue
|
||||
items = global_itmdata[itmstate]
|
||||
# Sort by name first
|
||||
itemorder = sorted(items, key=lambda item: getitemname(item))
|
||||
# Then by group id
|
||||
itemorder = sorted(itemorder, key=lambda item: items[item][0][2] or items[item][0][1])
|
||||
# Then by category id
|
||||
itemorder = sorted(itemorder, key=lambda item: getgroupcat(items[item][0][2] or items[item][0][1]))
|
||||
|
||||
for item in itemorder:
|
||||
groupdata = items[item][0]
|
||||
groupstr = " ({0} => {1})".format(getgroupname(groupdata[1]), getgroupname(groupdata[2])) if groupdata[0] == S["changed"] else ""
|
||||
print("\n[{0}] {1}{2}".format(TG[itmstate], getitemname(item), groupstr))
|
||||
|
||||
effdata = items[item][1]
|
||||
for effstate in stateorder:
|
||||
# Skip unchanged effect sets, but always include them for added or removed ships
|
||||
# Also, always skip empty data
|
||||
if (effstate == S["unchanged"] and itmstate not in (S["removed"], S["added"])) or effstate not in effdata:
|
||||
continue
|
||||
effects = effdata[effstate]
|
||||
efforder = sorted(effects, key=lambda eff: geteffectname(eff))
|
||||
for eff in efforder:
|
||||
# Take tag from item if item was added or removed
|
||||
tag = TG[effstate] if itmstate not in (S["removed"], S["added"]) else TG[itmstate]
|
||||
print(" [{0}|{1}] {2}".format(tag, "y" if geteffst(geteffectname(eff)) else "n", geteffectname(eff)))
|
||||
|
||||
attrdata = items[item][2]
|
||||
for attrstate in stateorder:
|
||||
# Skip unchanged and empty attribute sets, also skip attributes display for added and removed items
|
||||
if (attrstate == S["unchanged"] and itmstate != S["added"]) or itmstate in (S["removed"], ) or attrstate not in attrdata:
|
||||
continue
|
||||
attrs = attrdata[attrstate]
|
||||
attrorder = sorted(attrs, key=lambda attr: getattrname(attr))
|
||||
for attr in attrorder:
|
||||
valline = ""
|
||||
if attrs[attr][0] == "NULL" or itmstate == S["added"]:
|
||||
valline = "{0}".format(attrs[attr][1] or 0)
|
||||
elif attrs[attr][1] == "NULL":
|
||||
valline = "{0}".format(attrs[attr][0] or 0)
|
||||
else:
|
||||
valline = "{0} => {1}".format(attrs[attr][0] or 0, attrs[attr][1] or 0)
|
||||
print(" [{0}] {1}: {2}".format(TG[attrstate], getattrname(attr), valline))
|
||||
|
||||
if args.renames:
|
||||
title = 'effects'
|
||||
printrenames(ren_effects, title, implementedtag=True)
|
||||
|
||||
title = 'attributes'
|
||||
printrenames(ren_attributes, title)
|
||||
|
||||
title = 'categories'
|
||||
printrenames(ren_categories, title)
|
||||
|
||||
title = 'groups'
|
||||
printrenames(ren_groups, title)
|
||||
|
||||
title = 'market groups'
|
||||
printrenames(ren_marketgroups, title)
|
||||
|
||||
title = 'items'
|
||||
printrenames(ren_items, title)
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from optparse import OptionParser
|
||||
import os.path
|
||||
import shutil
|
||||
import tempfile
|
||||
import sys
|
||||
import tarfile
|
||||
import datetime
|
||||
import random
|
||||
import string
|
||||
|
||||
class FileStub():
|
||||
def write(self, *args):
|
||||
pass
|
||||
|
||||
def flush(self, *args):
|
||||
pass
|
||||
|
||||
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for x in range(size))
|
||||
|
||||
if __name__ == "__main__":
|
||||
oldstd = sys.stdout
|
||||
parser = OptionParser()
|
||||
parser.add_option("-s", "--skeleton", dest="skeleton", help="Location of skeleton directory")
|
||||
parser.add_option("-b", "--base", dest="base", help="location of the base directory")
|
||||
parser.add_option("-d", "--destination", dest="destination", help="where to copy our archive")
|
||||
parser.add_option("-t", "--static", dest="static", help="directory containing static files")
|
||||
parser.add_option("-q", "--quiet", dest="silent", action="store_true")
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.skeleton is None or options.base is None or options.destination is None:
|
||||
print "Need --skeleton argument as well as --base and --destination argument"
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
if options.silent:
|
||||
sys.stdout = FileStub()
|
||||
|
||||
randomId = id_generator()
|
||||
infoDict = {}
|
||||
skeleton = os.path.expanduser(options.skeleton)
|
||||
info = execfile(os.path.join(skeleton, "info.py"), infoDict)
|
||||
now = datetime.datetime.now()
|
||||
now = "%04d%02d%02d" % (now.year, now.month, now.day)
|
||||
dirName = "nighty-build-%s-%s" % (now, randomId)
|
||||
dst = os.path.join(os.getcwd(), dirName)
|
||||
tmpFile = os.path.join(os.getcwd(), "nighty-build-%s-%s-%s.tar.bz2" % (now, infoDict["os"], randomId))
|
||||
config = os.path.join(skeleton, "config.py")
|
||||
destination = os.path.expanduser(options.destination)
|
||||
|
||||
i = 0
|
||||
gitData = (".git", ".gitignore", ".gitmodules")
|
||||
def loginfo(path, names):
|
||||
global i
|
||||
i += 1
|
||||
if i % 10 == 0:
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
return gitData
|
||||
|
||||
try:
|
||||
print "copying skeleton to ", dst
|
||||
i = 0
|
||||
shutil.copytree(skeleton, dst, ignore=loginfo)
|
||||
print ""
|
||||
|
||||
base = os.path.join(dst, infoDict["base"])
|
||||
print "copying base to ", base
|
||||
|
||||
i = 0
|
||||
for stuff in os.listdir(os.path.expanduser(options.base)):
|
||||
currSource = os.path.join(os.path.expanduser(options.base), stuff)
|
||||
currDest = os.path.join(base, stuff)
|
||||
if stuff in gitData:
|
||||
continue
|
||||
elif os.path.isdir(currSource):
|
||||
shutil.copytree(currSource, currDest, ignore=loginfo)
|
||||
else:
|
||||
shutil.copy2(currSource, currDest)
|
||||
|
||||
print ""
|
||||
if os.path.exists(config):
|
||||
print "adding skeleton config file"
|
||||
shutil.copy2(config, base)
|
||||
|
||||
|
||||
if options.static is not None and os.path.exists(os.path.expanduser(options.static)):
|
||||
print "copying static data to ", os.path.join(base, "staticdata")
|
||||
static = os.path.expanduser(options.static)
|
||||
shutil.copytree(static, os.path.join(base, "staticdata"), ignore=loginfo)
|
||||
|
||||
print "removing development data"
|
||||
paths = []
|
||||
paths.append(os.path.join(base, "eos", "tests"))
|
||||
paths.append(os.path.join(base, "eos", "utils", "scripts"))
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
print path
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
print "copying done, making archive: ", tmpFile
|
||||
archive = tarfile.open(tmpFile, "w:bz2")
|
||||
print "making archive"
|
||||
archive.add(dst, arcname=infoDict["arcname"])
|
||||
print "closing"
|
||||
archive.close()
|
||||
print "copying archive to ", destination
|
||||
shutil.move(tmpFile, destination)
|
||||
except:
|
||||
print "encountered an error"
|
||||
raise
|
||||
finally:
|
||||
print "deleting tmp files"
|
||||
try:
|
||||
shutil.rmtree(dst)
|
||||
os.unlink(tmpFile)
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.stdout = oldstd
|
||||
if os.path.isdir(destination):
|
||||
print os.path.join(destination, os.path.split(tmpFile)[1])
|
||||
else:
|
||||
print destination
|
||||
@@ -25,7 +25,7 @@ import time
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
cachedBitmapsCount = 0
|
||||
cachedBitmaps = OrderedDict()
|
||||
|
||||
@@ -14,5 +14,8 @@ __all__ = [
|
||||
"whProjector",
|
||||
"cargo",
|
||||
"shipJump",
|
||||
"targetResists"
|
||||
#"changeAffectingSkills",
|
||||
"tacticalMode",
|
||||
"targetResists",
|
||||
"priceClear"
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ class AmmoPattern(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
@@ -33,5 +32,4 @@ class AmmoPattern(ContextMenu):
|
||||
def getBitmap(self, context, selection):
|
||||
return None
|
||||
|
||||
|
||||
AmmoPattern.register()
|
||||
|
||||
@@ -10,17 +10,17 @@ import gui.globalEvents as GE
|
||||
class ChangeAffectingSkills(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.sChar = service.Character.getInstance()
|
||||
|
||||
self.sFit = service.Fit.getInstance()
|
||||
fit = self.sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
self.charID = fit.character.ID
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "fittingShip"):
|
||||
return False
|
||||
|
||||
self.sChar = service.Character.getInstance()
|
||||
self.sFit = service.Fit.getInstance()
|
||||
fit = self.sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
self.charID = fit.character.ID
|
||||
|
||||
if self.sChar.getCharName(self.charID) in ("All 0", "All 5"):
|
||||
return False
|
||||
|
||||
@@ -52,9 +52,6 @@ class ChangeAffectingSkills(ContextMenu):
|
||||
def getText(self, itmContext, selection):
|
||||
return "Change %s Skills" % itmContext
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
pass
|
||||
|
||||
def addSkill(self, rootMenu, skill, i):
|
||||
if i < 0:
|
||||
label = "Not Learned"
|
||||
@@ -63,32 +60,31 @@ class ChangeAffectingSkills(ContextMenu):
|
||||
|
||||
id = wx.NewId()
|
||||
self.skillIds[id] = (skill, i)
|
||||
menuItem = wx.MenuItem(rootMenu, id, label, kind=wx.ITEM_CHECK)
|
||||
menuItem = wx.MenuItem(rootMenu, id, label, kind=wx.ITEM_RADIO)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
self.context = context
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.skillIds = {}
|
||||
|
||||
m = wx.Menu()
|
||||
sub = wx.Menu()
|
||||
|
||||
for skill in self.skills:
|
||||
skillItem = wx.MenuItem(m, wx.NewId(), skill.item.name)
|
||||
sub = wx.Menu()
|
||||
skillItem.SetSubMenu(sub)
|
||||
skillItem = wx.MenuItem(sub, wx.NewId(), skill.item.name)
|
||||
grandSub = wx.Menu()
|
||||
skillItem.SetSubMenu(grandSub)
|
||||
if skill.learned:
|
||||
bitmap = bitmapLoader.getBitmap("lvl%s" % skill.level, "icons")
|
||||
if bitmap is not None:
|
||||
skillItem.SetBitmap(bitmap)
|
||||
|
||||
for i in xrange(-1, 6):
|
||||
levelItem = self.addSkill(menu, skill, i)
|
||||
sub.AppendItem(levelItem)
|
||||
levelItem = self.addSkill(rootMenu if msw else grandSub, skill, i)
|
||||
grandSub.AppendItem(levelItem)
|
||||
#@ todo: add check to current level. Need to fix #109 first
|
||||
m.AppendItem(skillItem)
|
||||
sub.AppendItem(skillItem)
|
||||
|
||||
return m
|
||||
return sub
|
||||
|
||||
def handleSkillChange(self, event):
|
||||
skill, level = self.skillIds[event.Id]
|
||||
|
||||
@@ -15,7 +15,7 @@ class DamagePattern(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("resistancesViewFull",) and self.mainFrame.getActiveFit() is not None
|
||||
return srcContext == "resistancesViewFull" and self.mainFrame.getActiveFit() is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
sDP = service.DamagePattern.getInstance()
|
||||
@@ -24,12 +24,11 @@ class DamagePattern(ContextMenu):
|
||||
self.fit = sFit.getFit(fitID)
|
||||
|
||||
self.patterns = sDP.getDamagePatternList()
|
||||
self.patterns.sort( key=lambda p: (p.name not in ["Uniform",
|
||||
"Selected Ammo"], p.name) )
|
||||
self.patterns.sort(key=lambda p: (p.name not in ["Uniform","Selected Ammo"], p.name))
|
||||
|
||||
self.patternIds = {}
|
||||
self.subMenus = OrderedDict()
|
||||
self.singles = []
|
||||
self.singles = []
|
||||
|
||||
# iterate and separate damage patterns based on "[Parent] Child"
|
||||
for pattern in self.patterns:
|
||||
@@ -48,6 +47,51 @@ class DamagePattern(ContextMenu):
|
||||
self.m = map(lambda p: p.name, self.singles) + self.subMenus.keys()
|
||||
return self.m
|
||||
|
||||
def addPattern(self, rootMenu, pattern):
|
||||
id = wx.NewId()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
menuItem = wx.MenuItem(rootMenu, id, name)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
|
||||
|
||||
# set pattern attr to menu item
|
||||
menuItem.pattern = pattern
|
||||
|
||||
# determine active pattern
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
f = sFit.getFit(fitID)
|
||||
dp = f.damagePattern
|
||||
|
||||
if dp == pattern:
|
||||
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
|
||||
menuItem.SetBitmap(bitmap)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch) # this bit is required for some reason
|
||||
|
||||
if self.m[i] not in self.subMenus:
|
||||
# if we're trying to get submenu to something that shouldn't have one,
|
||||
# redirect event of the item to handlePatternSwitch and put pattern in
|
||||
# our patternIds mapping, then return None for no submenu
|
||||
id = pitem.GetId()
|
||||
self.patternIds[id] = self.singles[i]
|
||||
if self.patternIds[id] == self.fit.damagePattern:
|
||||
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
|
||||
pitem.SetBitmap(bitmap)
|
||||
return None
|
||||
|
||||
sub = wx.Menu()
|
||||
|
||||
# Items that have a parent
|
||||
for pattern in self.subMenus[self.m[i]]:
|
||||
sub.AppendItem(self.addPattern(rootMenu if msw else sub, pattern))
|
||||
|
||||
return sub
|
||||
|
||||
def handlePatternSwitch(self, event):
|
||||
pattern = self.patternIds.get(event.Id, False)
|
||||
if pattern is False:
|
||||
@@ -60,50 +104,4 @@ class DamagePattern(ContextMenu):
|
||||
setattr(self.mainFrame,"_activeDmgPattern", pattern)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def addPattern(self, menu, pattern):
|
||||
id = wx.NewId()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
item = wx.MenuItem(menu, id, name)
|
||||
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, item)
|
||||
|
||||
# set pattern attr to menu item
|
||||
item.pattern = pattern
|
||||
|
||||
# determine active pattern
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
f = sFit.getFit(fitID)
|
||||
dp = f.damagePattern
|
||||
|
||||
if dp == pattern:
|
||||
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
|
||||
item.SetBitmap(bitmap)
|
||||
return item
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch) # this bit is required for some reason
|
||||
|
||||
if self.m[i] not in self.subMenus:
|
||||
# if we're trying to get submenu to something that shouldn't have one,
|
||||
# redirect event of the item to handlePatternSwitch and put pattern in
|
||||
# our patternIds mapping, then return None for no submenu
|
||||
id = pitem.GetId()
|
||||
self.patternIds[id] = self.singles[i]
|
||||
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
|
||||
if self.patternIds[id] == self.fit.damagePattern:
|
||||
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
|
||||
pitem.SetBitmap(bitmap)
|
||||
return None
|
||||
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handlePatternSwitch)
|
||||
|
||||
# Items that have a parent
|
||||
for pattern in self.subMenus[self.m[i]]:
|
||||
sub.AppendItem(self.addPattern(sub, pattern))
|
||||
|
||||
return sub
|
||||
|
||||
DamagePattern.register()
|
||||
|
||||
@@ -15,7 +15,6 @@ class ItemRemove(ContextMenu):
|
||||
return "Remove {0} Stack".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
srcContext = fullContext[0]
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
@@ -10,7 +10,7 @@ class FactorReload(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("firepowerViewFull",) and self.mainFrame.getActiveFit() is not None
|
||||
return srcContext == "firepowerViewFull" and self.mainFrame.getActiveFit() is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Factor in Reload Time"
|
||||
|
||||
@@ -9,7 +9,10 @@ class ItemRemove(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("fittingModule", "fittingCharge", "droneItem", "implantItem", "boosterItem", "projectedModule", "projectedCharge",
|
||||
return srcContext in ("fittingModule", "fittingCharge",
|
||||
"droneItem", "implantItem",
|
||||
"boosterItem", "projectedModule",
|
||||
"projectedCharge", "cargoItem",
|
||||
"projectedFit", "projectedDrone")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
@@ -33,6 +36,8 @@ class ItemRemove(ContextMenu):
|
||||
sFit.removeImplant(fitID, fit.implants.index(selection[0]))
|
||||
elif srcContext == "boosterItem":
|
||||
sFit.removeBooster(fitID, fit.boosters.index(selection[0]))
|
||||
elif srcContext == "cargoItem":
|
||||
sFit.removeCargo(fitID, fit.cargo.index(selection[0]))
|
||||
else:
|
||||
sFit.removeProjected(fitID, selection[0])
|
||||
|
||||
|
||||
@@ -9,8 +9,13 @@ class ItemStats(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("marketItemGroup", "marketItemMisc", "fittingModule", "fittingCharge", "fittingShip", "baseShip", "cargoItem",
|
||||
"droneItem", "implantItem", "boosterItem", "skillItem", "projectedModule", "projectedDrone", "projectedCharge")
|
||||
return srcContext in ("marketItemGroup", "marketItemMisc",
|
||||
"fittingModule", "fittingCharge",
|
||||
"fittingShip", "baseShip",
|
||||
"cargoItem", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"skillItem", "projectedModule",
|
||||
"projectedDrone", "projectedCharge")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "{0} Stats".format(itmContext if itmContext is not None else "Item")
|
||||
@@ -33,7 +38,7 @@ class ItemStats(ContextMenu):
|
||||
if mstate.CmdDown():
|
||||
reuse = True
|
||||
|
||||
if self.mainFrame.GetActiveStatsWindow() == None and reuse:
|
||||
if self.mainFrame.GetActiveStatsWindow() is None and reuse:
|
||||
ItemStatsDialog(stuff, fullContext)
|
||||
|
||||
elif reuse:
|
||||
|
||||
@@ -8,19 +8,24 @@ class MarketJump(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
validContexts = ("marketItemMisc", "fittingModule", "fittingCharge", "droneItem", "implantItem",
|
||||
"boosterItem", "projectedModule", "projectedDrone", "projectedCharge", "cargoItem")
|
||||
if not srcContext in validContexts:
|
||||
validContexts = ("marketItemMisc", "fittingModule",
|
||||
"fittingCharge", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"projectedModule", "projectedDrone",
|
||||
"projectedCharge", "cargoItem")
|
||||
|
||||
if not srcContext in validContexts or selection is None or len(selection) < 1:
|
||||
return False
|
||||
|
||||
sMkt = service.Market.getInstance()
|
||||
if selection is None or len(selection) < 1:
|
||||
return False
|
||||
item = getattr(selection[0], "item", selection[0])
|
||||
mktGrp = sMkt.getMarketGroupByItem(item)
|
||||
|
||||
# 1663 is Special Edition Festival Assets, we don't have root group for it
|
||||
if mktGrp is None or mktGrp.ID == 1663:
|
||||
return False
|
||||
doit = not selection[0].isEmpty if srcContext == "fittingModule" else True
|
||||
|
||||
doit = not selection[0].isEmpty if srcContext == "fittingModule" else True
|
||||
return doit
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
@@ -28,7 +33,9 @@ class MarketJump(ContextMenu):
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
srcContext = fullContext[0]
|
||||
if srcContext in ("fittingModule", "droneItem", "implantItem", "boosterItem", "projectedModule", "projectedDrone", "cargoItem"):
|
||||
if srcContext in ("fittingModule", "droneItem", "implantItem",
|
||||
"boosterItem", "projectedModule", "projectedDrone",
|
||||
"cargoItem"):
|
||||
item = selection[0].item
|
||||
elif srcContext in ("fittingCharge", "projectedCharge"):
|
||||
item = selection[0].charge
|
||||
|
||||
@@ -8,6 +8,9 @@ from eos.types import Hardpoint
|
||||
import gui.globalEvents as GE
|
||||
|
||||
class ModuleAmmoPicker(ContextMenu):
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
@@ -19,7 +22,9 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
|
||||
validCharges = None
|
||||
checkedTypes = set()
|
||||
|
||||
for mod in modules:
|
||||
# loop through modules and gather list of valid charges
|
||||
if mod.item.ID in checkedTypes:
|
||||
continue
|
||||
checkedTypes.add(mod.item.ID)
|
||||
@@ -41,10 +46,6 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
def getText(self, itmContext, selection):
|
||||
return "Charge"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
pass
|
||||
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
def turretSorter(self, charge):
|
||||
damage = 0
|
||||
range = (self.module.getModifiedItemAttr("maxRange") or 0) * (charge.getAttribute("weaponRangeMultiplier") or 1)
|
||||
@@ -57,15 +58,14 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
# Take optimal and half falloff as range factor
|
||||
rangeFactor = range + falloff / 2
|
||||
|
||||
return (- rangeFactor, charge.name.rsplit()[-2:], damage, charge.name)
|
||||
return - rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
def missileSorter(self, charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = self.damageInfo(charge)
|
||||
# Find its position in sort list
|
||||
position = self.MISSILE_ORDER.index(chargeDamageType)
|
||||
return (position, totalDamage, charge.name)
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
def damageInfo(self, charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
@@ -90,7 +90,6 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
|
||||
def numericConverter(self, string):
|
||||
return int(string) if string.isdigit() else string
|
||||
|
||||
@@ -103,6 +102,7 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
name = charge.name if charge is not None else "Empty"
|
||||
self.chargeIds[id] = charge
|
||||
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, "pack")
|
||||
@@ -116,11 +116,9 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
m.Append(id, u'─ %s ─' % text)
|
||||
m.Enable(id, False)
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
self.context = context
|
||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
m = wx.Menu()
|
||||
m.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.chargeIds = {}
|
||||
hardpoint = self.module.hardpoint
|
||||
moduleName = self.module.item.name
|
||||
@@ -150,7 +148,7 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
base = charge
|
||||
nameBase = currBase
|
||||
range = currRange
|
||||
item = self.addCharge(m, charge)
|
||||
item = self.addCharge(rootMenu if msw else m, charge)
|
||||
items.append(item)
|
||||
else:
|
||||
if sub is None:
|
||||
@@ -158,9 +156,9 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
sub.AppendItem(self.addCharge(sub, base))
|
||||
sub.AppendItem(self.addCharge(rootMenu if msw else sub, base))
|
||||
|
||||
sub.AppendItem(self.addCharge(sub, charge))
|
||||
sub.AppendItem(self.addCharge(rootMenu if msw else sub, charge))
|
||||
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
@@ -194,20 +192,20 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
m.AppendItem(item)
|
||||
|
||||
if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"):
|
||||
sub.AppendItem(self.addCharge(sub, charge))
|
||||
sub.AppendItem(self.addCharge(rootMenu if msw else sub, charge))
|
||||
else:
|
||||
defender = charge
|
||||
|
||||
if defender is not None:
|
||||
m.AppendItem(self.addCharge(sub, defender))
|
||||
m.AppendItem(self.addCharge(rootMenu if msw else m, defender))
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
else:
|
||||
self.charges.sort(key=self.nameSorter)
|
||||
for charge in self.charges:
|
||||
m.AppendItem(self.addCharge(m, charge))
|
||||
m.AppendItem(self.addCharge(rootMenu if msw else m, charge))
|
||||
|
||||
m.AppendItem(self.addCharge(m, None))
|
||||
m.AppendItem(self.addCharge(rootMenu if msw else m, None))
|
||||
return m
|
||||
|
||||
def handleAmmoSwitch(self, event):
|
||||
|
||||
@@ -33,7 +33,7 @@ class ModuleGlobalAmmoPicker(ModuleAmmoPicker):
|
||||
selectedModule = self.modules[0]
|
||||
allModules = []
|
||||
for mod in fit.modules:
|
||||
if mod.itemID == None:
|
||||
if mod.itemID is None:
|
||||
continue
|
||||
if mod.itemID == selectedModule.itemID:
|
||||
allModules.append(mod)
|
||||
|
||||
@@ -8,7 +8,7 @@ class OpenFit(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return "projectedFit" in srcContext
|
||||
return srcContext == "projectedFit"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Open Fit in New Tab"
|
||||
|
||||
22
gui/builtinContextMenus/priceClear.py
Normal file
22
gui/builtinContextMenus/priceClear.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import wx
|
||||
import gui.globalEvents as GE
|
||||
import service
|
||||
|
||||
class PriceClear(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext == "priceViewFull"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Reset Price Cache"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
sMkt = service.Market.getInstance()
|
||||
sMkt.clearPriceCache()
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit()))
|
||||
|
||||
PriceClear.register()
|
||||
@@ -9,10 +9,7 @@ class ShipJump(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
validContexts = ("fittingShip")
|
||||
if not srcContext in validContexts:
|
||||
return False
|
||||
return True
|
||||
return srcContext == "fittingShip"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Open in Ship Browser"
|
||||
|
||||
60
gui/builtinContextMenus/tacticalMode.py
Normal file
60
gui/builtinContextMenus/tacticalMode.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import wx
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import service
|
||||
import gui.globalEvents as GE
|
||||
|
||||
class TacticalMode(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext != "fittingShip":
|
||||
return False
|
||||
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
self.modes = fit.ship.getModes()
|
||||
self.currMode = fit.mode
|
||||
|
||||
return srcContext == "fittingShip" and self.modes is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Tactical Mode"
|
||||
|
||||
def addMode(self, menu, mode):
|
||||
label = mode.item.name.rsplit()[-2]
|
||||
id = wx.NewId()
|
||||
self.modeIds[id] = mode
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_RADIO)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.modeIds = {}
|
||||
|
||||
sub = wx.Menu()
|
||||
|
||||
for mode in self.modes:
|
||||
menuItem = self.addMode(rootMenu if msw else sub, mode)
|
||||
sub.AppendItem(menuItem)
|
||||
menuItem.Check(self.currMode.item == mode.item)
|
||||
|
||||
return sub
|
||||
|
||||
def handleMode(self, event):
|
||||
item = self.modeIds[event.Id]
|
||||
if item is False or item not in self.modes:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit.setMode(fitID, self.modeIds[event.Id])
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
TacticalMode.register()
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import service
|
||||
@@ -17,21 +15,18 @@ class TargetResists(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ("firepowerViewFull",):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext != "firepowerViewFull":
|
||||
return False
|
||||
|
||||
sTR = service.TargetResists.getInstance()
|
||||
self.patterns = sTR.getTargetResistsList()
|
||||
self.patterns.sort( key=lambda p: (p.name in ["None"], p.name) )
|
||||
self.patterns.sort(key=lambda p: (p.name in ["None"], p.name))
|
||||
|
||||
return len(self.patterns) > 0
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Target Resists"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
pass
|
||||
|
||||
def handleResistSwitch(self, event):
|
||||
pattern = self.patternIds.get(event.Id, False)
|
||||
if pattern is False:
|
||||
@@ -43,12 +38,14 @@ class TargetResists(ContextMenu):
|
||||
sFit.setTargetResists(fitID, pattern)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def addPattern(self, menu, pattern, currBase = None):
|
||||
def addPattern(self, rootMenu, pattern):
|
||||
id = wx.NewId()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
item = wx.MenuItem(menu, id, name)
|
||||
item = wx.MenuItem(rootMenu, id, name)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, item)
|
||||
|
||||
# set pattern attr to menu item
|
||||
item.pattern = pattern
|
||||
|
||||
@@ -63,21 +60,13 @@ class TargetResists(ContextMenu):
|
||||
item.SetBitmap(bitmap)
|
||||
return item
|
||||
|
||||
def addSeperator(self, m, text):
|
||||
id = wx.NewId()
|
||||
m.Append(id, u'─ %s ─' % text)
|
||||
m.Enable(id, False)
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
self.context = context
|
||||
menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
|
||||
m = wx.Menu()
|
||||
m.Bind(wx.EVT_MENU, self.handleResistSwitch)
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.patternIds = {}
|
||||
|
||||
self.subMenus = OrderedDict()
|
||||
self.singles = []
|
||||
|
||||
sub = wx.Menu()
|
||||
for pattern in self.patterns:
|
||||
start, end = pattern.name.find('['), pattern.name.find(']')
|
||||
if start is not -1 and end is not -1:
|
||||
@@ -90,30 +79,30 @@ class TargetResists(ContextMenu):
|
||||
else:
|
||||
self.singles.append(pattern)
|
||||
|
||||
m.AppendItem(self.addPattern(m, None)) # Add reset
|
||||
m.AppendSeparator()
|
||||
sub.AppendItem(self.addPattern(rootMenu if msw else sub, None)) # Add reset
|
||||
sub.AppendSeparator()
|
||||
|
||||
# Single items, no parent
|
||||
for pattern in self.singles:
|
||||
m.AppendItem(self.addPattern(m, pattern))
|
||||
sub.AppendItem(self.addPattern(rootMenu if msw else sub, pattern))
|
||||
|
||||
# Items that have a parent
|
||||
for menuName, patterns in self.subMenus.items():
|
||||
# Create parent item for root menu that is simply name of parent
|
||||
item = wx.MenuItem(menu, wx.NewId(), menuName)
|
||||
item = wx.MenuItem(rootMenu, wx.NewId(), menuName)
|
||||
|
||||
# Create menu for child items
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleResistSwitch)
|
||||
grandSub = wx.Menu()
|
||||
#sub.Bind(wx.EVT_MENU, self.handleResistSwitch)
|
||||
|
||||
# Apply child menu to parent item
|
||||
item.SetSubMenu(sub)
|
||||
item.SetSubMenu(grandSub)
|
||||
|
||||
# Append child items to child menu
|
||||
for pattern in patterns:
|
||||
sub.AppendItem(self.addPattern(sub, pattern))
|
||||
m.AppendItem(item) #finally, append parent item to root menu
|
||||
grandSub.AppendItem(self.addPattern(rootMenu if msw else grandSub, pattern))
|
||||
sub.AppendItem(item) #finally, append parent item to root menu
|
||||
|
||||
return m
|
||||
return sub
|
||||
|
||||
TargetResists.register()
|
||||
|
||||
@@ -14,29 +14,31 @@ class WhProjector(ContextMenu):
|
||||
def getText(self, itmContext, selection):
|
||||
return "Add System Effects"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
pass
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
self.idmap = {}
|
||||
menu.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
m = wx.Menu()
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
sMkt = service.Market.getInstance()
|
||||
effdata = sMkt.getSystemWideEffects()
|
||||
|
||||
self.idmap = {}
|
||||
sub = wx.Menu()
|
||||
|
||||
for swType in sorted(effdata):
|
||||
item = wx.MenuItem(m, wx.ID_ANY, swType)
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
item.SetSubMenu(sub)
|
||||
m.AppendItem(item)
|
||||
subItem = wx.MenuItem(sub, wx.ID_ANY, swType)
|
||||
grandSub = wx.Menu()
|
||||
subItem.SetSubMenu(grandSub)
|
||||
sub.AppendItem(subItem)
|
||||
|
||||
for swData in sorted(effdata[swType], key=lambda tpl: tpl[2]):
|
||||
wxid = wx.NewId()
|
||||
swObj, swName, swClass = swData
|
||||
self.idmap[wxid] = (swObj, swName)
|
||||
subitem = wx.MenuItem(sub, wxid, swClass)
|
||||
sub.AppendItem(subitem)
|
||||
return m
|
||||
|
||||
grandSubItem = wx.MenuItem(grandSub, wxid, swClass)
|
||||
if msw:
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem)
|
||||
else:
|
||||
grandSub.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem)
|
||||
grandSub.AppendItem(grandSubItem)
|
||||
return sub
|
||||
|
||||
def handleSelection(self, event):
|
||||
#Skip events ids that aren't mapped
|
||||
|
||||
@@ -30,6 +30,7 @@ class FirepowerViewFull(StatsView):
|
||||
StatsView.__init__(self)
|
||||
self.parent = parent
|
||||
self._cachedValues = []
|
||||
|
||||
def getHeaderText(self, fit):
|
||||
return "Firepower"
|
||||
|
||||
@@ -44,7 +45,7 @@ class FirepowerViewFull(StatsView):
|
||||
self.headerPanel = headerPanel
|
||||
headerContentSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
hsizer = headerPanel.GetSizer()
|
||||
hsizer.Add(headerContentSizer,0,0,0)
|
||||
hsizer.Add(headerContentSizer, 0, 0, 0)
|
||||
self.stEff = wx.StaticText(headerPanel, wx.ID_ANY, "( Effective )")
|
||||
headerContentSizer.Add(self.stEff)
|
||||
headerPanel.GetParent().AddToggleItem(self.stEff)
|
||||
@@ -54,7 +55,7 @@ class FirepowerViewFull(StatsView):
|
||||
sizerFirepower = wx.FlexGridSizer(1, 4)
|
||||
sizerFirepower.AddGrowableCol(1)
|
||||
|
||||
contentSizer.Add( sizerFirepower, 0, wx.EXPAND, 0)
|
||||
contentSizer.Add(sizerFirepower, 0, wx.EXPAND, 0)
|
||||
|
||||
counter = 0
|
||||
|
||||
@@ -73,10 +74,9 @@ class FirepowerViewFull(StatsView):
|
||||
box.Add(hbox, 1, wx.ALIGN_CENTER)
|
||||
|
||||
lbl = wx.StaticText(parent, wx.ID_ANY, "0.0 DPS")
|
||||
setattr(self, "label%sDps%s" % (panel.capitalize() ,damageType.capitalize()), lbl)
|
||||
setattr(self, "label%sDps%s" % (panel.capitalize(), damageType.capitalize()), lbl)
|
||||
|
||||
hbox.Add(lbl, 0, wx.ALIGN_CENTER)
|
||||
# hbox.Add(wx.StaticText(parent, wx.ID_ANY, " DPS"), 0, wx.ALIGN_CENTER)
|
||||
self._cachedValues.append(0)
|
||||
counter += 1
|
||||
targetSizer = sizerFirepower
|
||||
@@ -143,7 +143,7 @@ class FirepowerViewFull(StatsView):
|
||||
|
||||
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS",None),
|
||||
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
|
||||
("labelFullVolleyTotal", lambda: fit.weaponVolley, 3, 0, 0, "%s", "Volley: %.1f"),
|
||||
("labelFullVolleyTotal", lambda: fit.totalVolley, 3, 0, 0, "%s", "Volley: %.1f"),
|
||||
("labelFullDpsTotal", lambda: fit.totalDPS, 3, 0, 0, "%s", None))
|
||||
# See GH issue #
|
||||
#if fit is not None and fit.totalYield > 0:
|
||||
|
||||
@@ -41,7 +41,7 @@ class PriceViewFull(StatsView):
|
||||
self._cachedFittings = 0
|
||||
self._cachedTotal = 0
|
||||
|
||||
def OnTimer( self, event):
|
||||
def OnTimer(self, event):
|
||||
if self._timerId == event.GetId():
|
||||
if self._timerRuns >= self._timerRunsBeforeUpdate:
|
||||
self._timerRuns = 0
|
||||
@@ -53,11 +53,12 @@ class PriceViewFull(StatsView):
|
||||
if self._timerIdUpdate == event.GetId():
|
||||
self._timerUpdate.Stop()
|
||||
self.labelEMStatus.SetLabel("")
|
||||
|
||||
def getHeaderText(self, fit):
|
||||
return "Price"
|
||||
|
||||
def getTextExtentW(self, text):
|
||||
width, height = self.parent.GetTextExtent( text )
|
||||
width, height = self.parent.GetTextExtent(text)
|
||||
return width
|
||||
|
||||
def populatePanel(self, contentPanel, headerPanel):
|
||||
@@ -65,8 +66,15 @@ class PriceViewFull(StatsView):
|
||||
self.panel = contentPanel
|
||||
self.headerPanel = headerPanel
|
||||
|
||||
headerContentSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
hsizer = headerPanel.GetSizer()
|
||||
hsizer.Add(headerContentSizer, 0, 0, 0)
|
||||
self.labelEMStatus = wx.StaticText(headerPanel, wx.ID_ANY, "")
|
||||
headerContentSizer.Add(self.labelEMStatus)
|
||||
headerPanel.GetParent().AddToggleItem(self.labelEMStatus)
|
||||
|
||||
gridPrice = wx.GridSizer(1, 3)
|
||||
contentSizer.Add( gridPrice, 0, wx.EXPAND | wx.ALL, 0)
|
||||
contentSizer.Add(gridPrice, 0, wx.EXPAND | wx.ALL, 0)
|
||||
for type in ("ship", "fittings", "total"):
|
||||
image = "%sPrice_big" % type if type != "ship" else "ship_big"
|
||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
||||
@@ -86,9 +94,6 @@ class PriceViewFull(StatsView):
|
||||
setattr(self, "labelPrice%s" % type.capitalize(), lbl)
|
||||
hbox.Add(lbl, 0, wx.ALIGN_LEFT)
|
||||
|
||||
# hbox.Add(wx.StaticText(contentPanel, wx.ID_ANY, " ISK"), 0, wx.ALIGN_LEFT)
|
||||
self.labelEMStatus = wx.StaticText(contentPanel, wx.ID_ANY, "")
|
||||
contentSizer.Add(self.labelEMStatus,0)
|
||||
def refreshPanel(self, fit):
|
||||
if fit is not None:
|
||||
self.fit = fit
|
||||
|
||||
@@ -26,7 +26,7 @@ import locale
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
class TargetingMiscViewFull(StatsView):
|
||||
name = "targetingmiscViewFull"
|
||||
|
||||
@@ -42,7 +42,10 @@ class BaseName(ViewColumn):
|
||||
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
|
||||
elif isinstance(stuff, Rack):
|
||||
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
|
||||
return u'─ {} Slots ─'.format(Slot.getName(stuff.slot).capitalize())
|
||||
if stuff.slot == Slot.MODE:
|
||||
return u'─ Tactical Mode ─'
|
||||
else:
|
||||
return u'─ {} Slots ─'.format(Slot.getName(stuff.slot).capitalize())
|
||||
else:
|
||||
return ""
|
||||
elif isinstance(stuff, Module):
|
||||
|
||||
@@ -23,6 +23,7 @@ import service
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
from eos.types import Mode
|
||||
|
||||
class CapacitorUse(ViewColumn):
|
||||
name = "Capacitor Usage"
|
||||
@@ -38,6 +39,9 @@ class CapacitorUse(ViewColumn):
|
||||
|
||||
|
||||
def getText(self, mod):
|
||||
if isinstance(mod, Mode):
|
||||
return ""
|
||||
|
||||
capUse = mod.capUse
|
||||
if capUse:
|
||||
return "%s%s" % ("+" if capUse < 0 else "", (formatAmount(-capUse, 3, 0, 3)))
|
||||
|
||||
@@ -23,6 +23,7 @@ from gui import bitmapLoader
|
||||
import service
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
import wx
|
||||
from eos.types import Mode
|
||||
|
||||
class MaxRange(ViewColumn):
|
||||
name = "Max Range"
|
||||
@@ -51,6 +52,9 @@ class MaxRange(ViewColumn):
|
||||
self.mask |= wx.LIST_MASK_TEXT
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, Mode):
|
||||
return ""
|
||||
|
||||
maxRange = stuff.maxRange if hasattr(stuff, "maxRange") else stuff.getModifiedItemAttr("maxRange")
|
||||
falloff = stuff.falloff
|
||||
if falloff:
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
|
||||
import gui.mainFrame
|
||||
from gui import builtinViewColumns
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
@@ -62,19 +61,30 @@ class Miscellanea(ViewColumn):
|
||||
return (("displayName", bool, False),
|
||||
("showIcon", bool, True))
|
||||
|
||||
|
||||
def __getData(self, stuff):
|
||||
item = stuff.item
|
||||
if item is None:
|
||||
return "", None
|
||||
itemGroup = item.group.name
|
||||
if itemGroup in ("Energy Weapon", "Hybrid Weapon", "Projectile Weapon", "Combat Drone", "Fighter Drone"):
|
||||
itemCategory = item.category.name
|
||||
|
||||
if itemGroup == "Ship Modifiers":
|
||||
return "", None
|
||||
elif itemGroup in ("Energy Weapon", "Hybrid Weapon", "Projectile Weapon", "Combat Drone", "Fighter Drone"):
|
||||
trackingSpeed = stuff.getModifiedItemAttr("trackingSpeed")
|
||||
if not trackingSpeed:
|
||||
return "", None
|
||||
text = "{0}".format(formatAmount(trackingSpeed, 3, 0, 3))
|
||||
tooltip = "Tracking speed"
|
||||
return text, tooltip
|
||||
elif itemCategory == "Subsystem":
|
||||
slots = ("hi", "med", "low")
|
||||
info = []
|
||||
for slot in slots:
|
||||
n = int(stuff.getModifiedItemAttr("%sSlotModifier"%slot))
|
||||
if n > 0:
|
||||
info.append("{0}{1}".format(n, slot[0].upper()))
|
||||
return "+ "+", ".join(info), "Slot Modifiers"
|
||||
elif itemGroup == "Energy Destabilizer":
|
||||
neutAmount = stuff.getModifiedItemAttr("energyDestabilizationAmount")
|
||||
cycleTime = stuff.cycleTime
|
||||
|
||||
@@ -403,6 +403,10 @@ class FittingView(d.Display):
|
||||
self.mods = fit.modules[:]
|
||||
self.mods.sort(key=lambda mod: (slotOrder.index(mod.slot), mod.position))
|
||||
|
||||
# Blanks is a list of indexes that mark non-module positions (such
|
||||
# as Racks and tactical Modes. This allows us to skip over common
|
||||
# module operations such as swapping, removing, copying, etc. that
|
||||
# would otherwise cause complications
|
||||
self.blanks = [] # preliminary markers where blanks will be inserted
|
||||
|
||||
if sFit.serviceFittingOptions["rackSlots"]:
|
||||
@@ -419,6 +423,15 @@ class FittingView(d.Display):
|
||||
for i, (x, slot) in enumerate(self.blanks):
|
||||
self.blanks[i] = x+i # modify blanks with actual index
|
||||
self.mods.insert(x+i, Rack.buildRack(slot))
|
||||
|
||||
if fit.mode:
|
||||
# Modes are special snowflakes and need a little manual loving
|
||||
# We basically append the Mode rack and Mode to the modules
|
||||
# while also marking their positions in the Blanks list
|
||||
self.blanks.append(len(self.mods))
|
||||
self.mods.append(Rack.buildRack(Slot.MODE))
|
||||
self.blanks.append(len(self.mods))
|
||||
self.mods.append(fit.mode)
|
||||
else:
|
||||
self.mods = None
|
||||
|
||||
@@ -457,7 +470,7 @@ class FittingView(d.Display):
|
||||
sel = self.GetFirstSelected()
|
||||
contexts = []
|
||||
|
||||
while sel != -1:
|
||||
while sel != -1 and sel not in self.blanks:
|
||||
mod = self.mods[self.GetItemData(sel)]
|
||||
if not mod.isEmpty:
|
||||
srcContext = "fittingModule"
|
||||
@@ -544,14 +557,17 @@ class FittingView(d.Display):
|
||||
|
||||
font = (self.GetClassDefaultAttributes()).font
|
||||
for i, mod in enumerate(self.mods):
|
||||
if slotMap[mod.slot]:
|
||||
if hasattr(mod,"slot") and slotMap[mod.slot]:
|
||||
self.SetItemBackgroundColour(i, wx.Colour(204, 51, 51))
|
||||
elif sFit.serviceFittingOptions["colorFitBySlot"] and not isinstance(mod, Rack):
|
||||
self.SetItemBackgroundColour(i, self.slotColour(mod.slot))
|
||||
else:
|
||||
self.SetItemBackgroundColour(i, self.GetBackgroundColour())
|
||||
|
||||
if i in self.blanks and sFit.serviceFittingOptions["rackSlots"] and sFit.serviceFittingOptions["rackLabels"]:
|
||||
# Set rack face to bold
|
||||
if isinstance(mod, Rack) and \
|
||||
sFit.serviceFittingOptions["rackSlots"] and \
|
||||
sFit.serviceFittingOptions["rackLabels"]:
|
||||
font.SetWeight(wx.FONTWEIGHT_BOLD)
|
||||
self.SetItemFont(i, font)
|
||||
else:
|
||||
|
||||
@@ -31,13 +31,13 @@ class CharacterSelection(wx.Panel):
|
||||
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.SetSizer(mainSizer)
|
||||
|
||||
mainSizer.Add(wx.StaticText(self, wx.ID_ANY, "Character: "), 0, wx.CENTER | wx.TOP | wx.RIGHT | wx.LEFT, 3)
|
||||
mainSizer.Add(wx.StaticText(self, wx.ID_ANY, "Character: "), 0, wx.CENTER | wx.RIGHT | wx.LEFT, 3)
|
||||
|
||||
# cache current selection to fall back in case we choose to open char editor
|
||||
self.charCache = None
|
||||
|
||||
self.charChoice = wx.Choice(self)
|
||||
mainSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.RIGHT | wx.LEFT, 3)
|
||||
mainSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
|
||||
|
||||
self.refreshCharacterList()
|
||||
|
||||
@@ -56,11 +56,11 @@ class CharacterSelection(wx.Panel):
|
||||
self.btnRefresh.Bind(wx.EVT_BUTTON, self.refreshApi)
|
||||
self.btnRefresh.Enable(False)
|
||||
|
||||
mainSizer.Add(self.btnRefresh, 0, wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.RIGHT | wx.LEFT, 2)
|
||||
mainSizer.Add(self.btnRefresh, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 2)
|
||||
|
||||
self.skillReqsStaticBitmap = wx.StaticBitmap(self)
|
||||
self.skillReqsStaticBitmap.SetBitmap(self.cleanSkills)
|
||||
mainSizer.Add(self.skillReqsStaticBitmap, 0, wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.RIGHT | wx.LEFT, 3)
|
||||
mainSizer.Add(self.skillReqsStaticBitmap, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
|
||||
|
||||
self.Bind(wx.EVT_CHOICE, self.charChanged)
|
||||
self.mainFrame.Bind(GE.CHAR_LIST_UPDATED, self.refreshCharacterList)
|
||||
|
||||
@@ -21,29 +21,47 @@ import wx
|
||||
|
||||
class ContextMenu(object):
|
||||
menus = []
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
ContextMenu.menus.append(cls)
|
||||
|
||||
@classmethod
|
||||
def getMenu(cls, selection, *fullContexts):
|
||||
menu = wx.Menu()
|
||||
menu.info = {}
|
||||
menu.selection = selection
|
||||
"""
|
||||
getMenu returns a menu that is used with wx.PopupMenu.
|
||||
|
||||
selection: provides a list of what was selected. If only 1 item was
|
||||
selected, it's is a 1-item list or tuple. Can also be None for
|
||||
contexts without selection, such as statsPane or projected view
|
||||
fullContexts: a number of tuples of the following tuple:
|
||||
srcContext - context were menu was spawned, eg: projectedFit,
|
||||
cargoItem, etc
|
||||
itemContext - usually the name of the item's category
|
||||
|
||||
eg:
|
||||
(('fittingModule', 'Module'), ('fittingShip', 'Ship'))
|
||||
(('marketItemGroup', 'Implant'),)
|
||||
(('fittingShip', 'Ship'),)
|
||||
"""
|
||||
|
||||
rootMenu = wx.Menu()
|
||||
rootMenu.info = {}
|
||||
rootMenu.selection = (selection,) if not hasattr(selection, "__iter__") else selection
|
||||
empty = True
|
||||
menu.Bind(wx.EVT_MENU, cls.handler)
|
||||
for i, fullContext in enumerate(fullContexts):
|
||||
amount = 0
|
||||
srcContext = fullContext[0]
|
||||
try:
|
||||
itmContext = fullContext[1]
|
||||
itemContext = fullContext[1]
|
||||
except IndexError:
|
||||
itmContext = None
|
||||
itemContext = None
|
||||
for menuHandler in cls.menus:
|
||||
# loop through registered menus
|
||||
m = menuHandler()
|
||||
if m.display(srcContext, selection):
|
||||
amount += 1
|
||||
texts = m.getText(itmContext, selection)
|
||||
texts = m.getText(itemContext, selection)
|
||||
if isinstance(texts, basestring):
|
||||
texts = (texts,)
|
||||
|
||||
@@ -51,54 +69,72 @@ class ContextMenu(object):
|
||||
multiple = not isinstance(bitmap, wx.Bitmap)
|
||||
for it, text in enumerate(texts):
|
||||
id = wx.NewId()
|
||||
item = wx.MenuItem(menu, id, text)
|
||||
menu.info[id] = (m, fullContext, it)
|
||||
rootItem = wx.MenuItem(rootMenu, id, text)
|
||||
rootMenu.info[id] = (m, fullContext, it)
|
||||
|
||||
sub = m.getSubMenu(srcContext, selection, menu, it, item)
|
||||
if sub is not None:
|
||||
item.SetSubMenu(sub)
|
||||
sub = m.getSubMenu(srcContext, selection, rootMenu, it, rootItem)
|
||||
if sub is None:
|
||||
# if there is no sub menu, bind the handler to the rootItem
|
||||
rootMenu.Bind(wx.EVT_MENU, cls.handler, rootItem)
|
||||
else:
|
||||
# If there is a submenu, it is expected that the sub
|
||||
# logic take care of it's own binding. No binding is
|
||||
# done here
|
||||
#
|
||||
# It is important to remember that when binding sub
|
||||
# menu items, bind them to the rootMenu for proper
|
||||
# event handling, eg:
|
||||
# rootMenu.Bind(wx.EVE_MENU, self.handle, menuItem)
|
||||
rootItem.SetSubMenu(sub)
|
||||
|
||||
if bitmap is not None:
|
||||
if multiple:
|
||||
bp = bitmap[it]
|
||||
if bp:
|
||||
item.SetBitmap(bp)
|
||||
rootItem.SetBitmap(bp)
|
||||
else:
|
||||
item.SetBitmap(bitmap)
|
||||
rootItem.SetBitmap(bitmap)
|
||||
|
||||
menu.AppendItem(item)
|
||||
rootMenu.AppendItem(rootItem)
|
||||
|
||||
empty = False
|
||||
|
||||
if amount > 0 and i != len(fullContexts) - 1:
|
||||
menu.AppendSeparator()
|
||||
rootMenu.AppendSeparator()
|
||||
|
||||
return menu if empty is False else None
|
||||
return rootMenu if empty is False else None
|
||||
|
||||
@classmethod
|
||||
def handler(cls, event):
|
||||
menu = event.EventObject
|
||||
stuff = menu.info.get(event.Id)
|
||||
if stuff is not None:
|
||||
m, context, i = stuff
|
||||
menuHandler, context, i = stuff
|
||||
selection = menu.selection
|
||||
if not hasattr(selection, "__iter__"):
|
||||
selection = (selection,)
|
||||
|
||||
m.activate(context, selection, i)
|
||||
menuHandler.activate(context, selection, i)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
def display(self, context, selection):
|
||||
raise NotImplementedError()
|
||||
|
||||
def activate(self, context, selection, i):
|
||||
def activate(self, fullContext, selection, i):
|
||||
return None
|
||||
|
||||
def getSubMenu(self, context, selection, menu, i, pitem):
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
return None
|
||||
|
||||
def getText(self, context, selection):
|
||||
"""
|
||||
getText should be implemented in child classes, and should return either
|
||||
a string that will make up a menu item label or a list of strings which
|
||||
will make numerous menu items.
|
||||
|
||||
These menu items will be added to the root menu
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def getBitmap(self, context, selection):
|
||||
|
||||
@@ -203,6 +203,8 @@ class GraphFrame(wx.Frame):
|
||||
legend.append(fit.name)
|
||||
except:
|
||||
self.SetStatusText("Invalid values in '%s'" % fit.name)
|
||||
self.canvas.draw()
|
||||
return
|
||||
|
||||
if self.legendFix and len(legend) > 0:
|
||||
leg = self.subplot.legend(tuple(legend), "upper right" , shadow = False)
|
||||
|
||||
@@ -22,9 +22,9 @@ import re
|
||||
import gui.mainFrame
|
||||
import bitmapLoader
|
||||
import sys
|
||||
import wx.lib.mixins.listctrl as listmix
|
||||
import wx.lib.mixins.listctrl as listmix
|
||||
import wx.html
|
||||
from eos.types import Ship, Module, Skill, Booster, Implant, Drone
|
||||
from eos.types import Ship, Module, Skill, Booster, Implant, Drone, Mode
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
import service
|
||||
import config
|
||||
@@ -32,7 +32,7 @@ import config
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
class ItemStatsDialog(wx.Dialog):
|
||||
counter = 0
|
||||
@@ -535,7 +535,7 @@ class ItemEffects (wx.Panel):
|
||||
|
||||
|
||||
class ItemAffectedBy (wx.Panel):
|
||||
ORDER = [Ship, Module, Drone, Implant, Booster, Skill]
|
||||
ORDER = [Ship, Mode, Module, Drone, Implant, Booster, Skill]
|
||||
def __init__(self, parent, stuff, item):
|
||||
wx.Panel.__init__ (self, parent)
|
||||
self.stuff = stuff
|
||||
|
||||
@@ -100,7 +100,8 @@ class MainFrame(wx.Frame):
|
||||
return cls.__instance if cls.__instance is not None else MainFrame()
|
||||
|
||||
def __init__(self):
|
||||
wx.Frame.__init__(self, None, wx.ID_ANY, title="pyfa - Python Fitting Assistant")
|
||||
title="pyfa %s%s - Python Fitting Assistant"%(config.version, "" if config.tag.lower() != 'git' else " (git)")
|
||||
wx.Frame.__init__(self, None, wx.ID_ANY, title)
|
||||
|
||||
MainFrame.__instance = self
|
||||
|
||||
@@ -166,7 +167,7 @@ class MainFrame(wx.Frame):
|
||||
self.statsPane = StatsPane(self)
|
||||
cstatsSizer.Add(self.statsPane, 0, wx.EXPAND)
|
||||
|
||||
mainSizer.Add(cstatsSizer, 0 , wx.EXPAND)
|
||||
mainSizer.Add(cstatsSizer, 0, wx.EXPAND)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
|
||||
@@ -214,7 +215,7 @@ class MainFrame(wx.Frame):
|
||||
|
||||
|
||||
def LoadMainFrameAttribs(self):
|
||||
mainFrameDefaultAttribs = {"wnd_width":1000, "wnd_height": 700, "wnd_maximized": False}
|
||||
mainFrameDefaultAttribs = {"wnd_width": 1000, "wnd_height": 680, "wnd_maximized": False}
|
||||
self.mainFrameAttribs = service.SettingsProvider.getInstance().getSettings("pyfaMainWindowAttribs", mainFrameDefaultAttribs)
|
||||
|
||||
if self.mainFrameAttribs["wnd_maximized"]:
|
||||
@@ -289,6 +290,7 @@ class MainFrame(wx.Frame):
|
||||
event.Skip()
|
||||
|
||||
def ShowAboutBox(self, evt):
|
||||
import eos.config
|
||||
info = wx.AboutDialogInfo()
|
||||
info.Name = "pyfa"
|
||||
info.Version = gui.aboutData.versionString
|
||||
@@ -298,7 +300,8 @@ class MainFrame(wx.Frame):
|
||||
"\n\t".join(gui.aboutData.credits) +
|
||||
"\n\nLicenses:\n\t" +
|
||||
"\n\t".join(gui.aboutData.licenses) +
|
||||
"\n\nPython: \t" + sys.version +
|
||||
"\n\nEVE Data: \t" + eos.config.gamedata_version +
|
||||
"\nPython: \t" + sys.version +
|
||||
"\nwxPython: \t" + wx.__version__ +
|
||||
"\nSQLAlchemy: \t" + sqlalchemy.__version__,
|
||||
700, wx.ClientDC(self))
|
||||
|
||||
@@ -708,7 +708,12 @@ class ShipBrowser(wx.Panel):
|
||||
self.raceselect.Show(False)
|
||||
self.Layout()
|
||||
|
||||
RACE_ORDER = ["amarr", "caldari", "gallente", "minmatar", "sisters", "ore", "serpentis", "angel", "blood", "sansha", "guristas", "jove", None]
|
||||
RACE_ORDER = [
|
||||
"amarr", "caldari", "gallente", "minmatar",
|
||||
"sisters", "ore",
|
||||
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
|
||||
"jove", None
|
||||
]
|
||||
|
||||
def raceNameKey(self, ship):
|
||||
return self.RACE_ORDER.index(ship.race), ship.name
|
||||
|
||||
@@ -79,7 +79,7 @@ class StatsPane(wx.Panel):
|
||||
|
||||
mainSizer.Add(tp, 0, wx.EXPAND | wx.LEFT, 3)
|
||||
if i < maxviews - 1:
|
||||
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, style=wx.HORIZONTAL), 0, wx.EXPAND | wx.ALL,2)
|
||||
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, style=wx.HORIZONTAL), 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 2)
|
||||
i+=1
|
||||
tp.OnStateChange(tp.GetBestSize())
|
||||
|
||||
|
||||
BIN
icons/race_mordu_small.png
Normal file
BIN
icons/race_mordu_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 862 B |
48
readme.txt
48
readme.txt
@@ -1,48 +0,0 @@
|
||||
Pyfa is a cross-platform desktop fitting application for EVE online that can be used natively on any platform where python and wxwidgets are available.
|
||||
|
||||
It provides many advanced features such as graphs and full calculations of any possible combination of modules, fits, etc.
|
||||
|
||||
FAQ:
|
||||
Q: What is pyfa?
|
||||
A: Pyfa is the python fitting assistant, its a fitting tool for eve online that allows you to calculate the stats of any possible combination of ships and modules without having to buy them all ingame.
|
||||
|
||||
Q: How do I add fleet bonuses?
|
||||
A: Right click on a fit and click on the position you want to assign it to.
|
||||
|
||||
Q: How do I project a whole fit?
|
||||
A: Drag the fit from the fit browser into the projected effects window.
|
||||
|
||||
Q: How do I add implants on a character instead of a fit?
|
||||
A: This is currently not possible.
|
||||
|
||||
Q: What are all those different export formats in pyfa?
|
||||
A: EFT: the regular format we're all used to, very useful to paste on forums for example.
|
||||
DNA: A one-liner format, useful to paste around on IRC or other places where you can't just paste a whole block of text.
|
||||
XML: The format used by EVE, use this if you want to import your fit(s) into EVE.
|
||||
|
||||
Q: XYZ is calculated wrong! Another tool / EVE tells me so.
|
||||
A: First of all, please check that your implants are setup identically between all your different applications. Afterwards, doublecheck your boosters and skills.
|
||||
If its still wrong afterwards, you might not be using the exact same fit between both, doublecheck that as well.
|
||||
If its still wrong, please hop onto IRC, log issue at github or just leave post at the forums and we can look into it.
|
||||
|
||||
Q: I want to run pyfa from source, what should i install?
|
||||
A: Python 2 - http://www.python.org/
|
||||
wxPython - http://www.wxpython.org/
|
||||
SQLAlchemy - http://www.sqlalchemy.org/
|
||||
dateutil - https://labix.org/python-dateutil
|
||||
Matplotlib - http://matplotlib.sourceforge.net/
|
||||
NumPy - http://numpy.scipy.org/
|
||||
Last 2 are optional, they are required for graphs only.
|
||||
|
||||
A: I can't trust you!
|
||||
Q: If you don't want to trust us, don't. The source code is freely available for anyone to see. So if you have a technical background, you can go check it yourself. If you still don't trust it, well, don't use it.
|
||||
|
||||
|
||||
Development repository:
|
||||
http://github.com/DarkFenX/Pyfa
|
||||
XMPP conference:
|
||||
pyfa@conference.jabber.org
|
||||
EVE forum thread:
|
||||
http://forums.eveonline.com/default.aspx?g=posts&t=247609
|
||||
EVE Online website:
|
||||
http://www.eveonline.com/
|
||||
202
scripts/dist.py
Executable file
202
scripts/dist.py
Executable file
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Script for generating distributables based on platform skeletons.
|
||||
|
||||
User supplies path for pyfa code base, root skeleton directory, and where the
|
||||
builds go. The builds are automatically named depending on the pyfa config
|
||||
values of `version` and `tag`. If it's a Stable release, the naming convention is:
|
||||
pyfa-pyfaversion-expansion-expversion-platform
|
||||
If it is not Stable (tag=git), we determine if the pyfa code base includes the
|
||||
git repo to use as an ID. If not, uses randomly generated 6-character ID. The
|
||||
unstable naming convention:
|
||||
pyfa-YYYMMDD-id-platform
|
||||
|
||||
dist.py can also build the Windows installer provided that it has a path to Inno
|
||||
Setup (and, for generating on non-Windows platforms, that WINE is installed).
|
||||
To build the EXE file, `win` must be included in the platforms to be built.
|
||||
"""
|
||||
|
||||
from optparse import OptionParser
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import datetime
|
||||
import random
|
||||
import string
|
||||
import zipfile
|
||||
from subprocess import call
|
||||
|
||||
class FileStub():
|
||||
def write(self, *args):
|
||||
pass
|
||||
|
||||
def flush(self, *args):
|
||||
pass
|
||||
|
||||
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for x in range(size))
|
||||
|
||||
def zipdir(path, zip):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
zip.write(os.path.join(root, file))
|
||||
|
||||
skels = ['win', 'mac', 'src']
|
||||
iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine
|
||||
|
||||
if __name__ == "__main__":
|
||||
oldstd = sys.stdout
|
||||
parser = OptionParser()
|
||||
parser.add_option("-s", "--skeleton", dest="skeleton", help="Location of Pyfa-skel directory")
|
||||
parser.add_option("-b", "--base", dest="base", help="Location of cleaned read-only base directory")
|
||||
parser.add_option("-d", "--destination", dest="destination", help="Where to copy our distributable")
|
||||
parser.add_option("-p", "--platforms", dest="platforms", help="Comma-separated list of platforms to build", default="win,src,mac")
|
||||
parser.add_option("-t", "--static", dest="static", help="Directory containing static files")
|
||||
parser.add_option("-q", "--quiet", dest="silent", action="store_true")
|
||||
parser.add_option("-w", "--winexe", dest="winexe", action="store_true", help="Build the Windows installer file (needs Inno Setup). Must include 'win' in platform options")
|
||||
parser.add_option("-z", "--zip", dest="zip", action="store_true", help="zip archive instead of tar")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.skeleton is None or options.base is None or options.destination is None:
|
||||
print "Need --skeleton argument as well as --base and --destination argument"
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
if options.silent:
|
||||
sys.stdout = FileStub()
|
||||
|
||||
options.platforms = options.platforms.split(",")
|
||||
|
||||
sys.path.append(options.base)
|
||||
import config as pyfaconfig
|
||||
|
||||
for skel in skels:
|
||||
if skel not in options.platforms:
|
||||
continue
|
||||
|
||||
print "\n======== %s ========"%skel
|
||||
infoDict = {}
|
||||
skeleton = os.path.expanduser(os.path.join(options.skeleton, skel))
|
||||
info = execfile(os.path.join(skeleton, "info.py"), infoDict)
|
||||
dirName = infoDict["arcname"]
|
||||
nowdt = datetime.datetime.now()
|
||||
now = "%04d%02d%02d" % (nowdt.year, nowdt.month, nowdt.day)
|
||||
git = False
|
||||
if pyfaconfig.tag.lower() == "git":
|
||||
try: # if there is a git repo associated with base, use master commit
|
||||
with open(os.path.join(options.base,".git","refs","heads","master"), 'r') as f:
|
||||
id = f.readline()[0:6]
|
||||
git = True
|
||||
except: # else, use custom ID
|
||||
id = id_generator()
|
||||
fileName = "pyfa-%s-%s-%s" % (now, id, infoDict["os"])
|
||||
else:
|
||||
fileName = "pyfa-%s-%s-%s-%s" % (pyfaconfig.version, pyfaconfig.expansionName.lower(), pyfaconfig.expansionVersion, infoDict["os"])
|
||||
|
||||
archiveName = "%s.%s"%(fileName, "zip" if options.zip else "tar.bz2")
|
||||
dst = os.path.join(os.getcwd(), dirName) # tmp directory where files are copied
|
||||
tmpFile = os.path.join(os.getcwd(), archiveName)
|
||||
config = os.path.join(skeleton, "config.py")
|
||||
destination = os.path.expanduser(options.destination)
|
||||
|
||||
i = 0
|
||||
gitData = (".git", ".gitignore", ".gitmodules")
|
||||
def loginfo(path, names):
|
||||
global i
|
||||
i += 1
|
||||
if i % 10 == 0:
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
return gitData
|
||||
|
||||
try:
|
||||
print "Copying skeleton to ", dst
|
||||
i = 0
|
||||
shutil.copytree(skeleton, dst, ignore=loginfo)
|
||||
print
|
||||
base = os.path.join(dst, infoDict["base"])
|
||||
print "Copying base to ", base
|
||||
|
||||
i = 0
|
||||
for stuff in os.listdir(os.path.expanduser(options.base)):
|
||||
currSource = os.path.join(os.path.expanduser(options.base), stuff)
|
||||
currDest = os.path.join(base, stuff)
|
||||
if stuff in gitData:
|
||||
continue
|
||||
elif os.path.isdir(currSource):
|
||||
shutil.copytree(currSource, currDest, ignore=loginfo)
|
||||
else:
|
||||
shutil.copy2(currSource, currDest)
|
||||
|
||||
print
|
||||
|
||||
if os.path.exists(config):
|
||||
print "Adding skeleton config file"
|
||||
shutil.copy2(config, base)
|
||||
|
||||
|
||||
if options.static is not None and os.path.exists(os.path.expanduser(options.static)):
|
||||
print "Copying static data to ", os.path.join(base, "staticdata")
|
||||
static = os.path.expanduser(options.static)
|
||||
shutil.copytree(static, os.path.join(base, "staticdata"), ignore=loginfo)
|
||||
|
||||
print "Copying done, making archive: ", tmpFile
|
||||
|
||||
if options.zip:
|
||||
archive = zipfile.ZipFile(tmpFile, 'w', compression=zipfile.ZIP_DEFLATED)
|
||||
zipdir(dirName, archive)
|
||||
archive.close()
|
||||
else:
|
||||
archive = tarfile.open(tmpFile, "w:bz2")
|
||||
archive.add(dst, arcname=infoDict["arcname"])
|
||||
archive.close()
|
||||
|
||||
print "Moving archive to ", destination
|
||||
shutil.move(tmpFile, destination)
|
||||
|
||||
if "win" in skel and options.winexe:
|
||||
print "Compiling EXE"
|
||||
|
||||
if pyfaconfig.tag.lower() == "git":
|
||||
if git: # if git repo info available, use git commit
|
||||
expansion = "git-%s"%(id)
|
||||
else: # if there is no git repo, use timestamp
|
||||
expansion = now
|
||||
else: # if code is Stable, use expansion name
|
||||
expansion = "%s %s"%(pyfaconfig.expansionName, pyfaconfig.expansionVersion),
|
||||
|
||||
calllist = ["wine"] if 'win' not in sys.platform else []
|
||||
|
||||
call(calllist + [
|
||||
iscc,
|
||||
os.path.join(os.path.dirname(__file__), "pyfa-setup.iss"),
|
||||
"/dMyAppVersion=%s"%(pyfaconfig.version),
|
||||
"/dMyAppExpansion=%s"%(expansion),
|
||||
"/dMyAppDir=%s"%dst,
|
||||
"/dMyOutputDir=%s"%destination,
|
||||
"/dMyOutputFile=%s"%fileName]) #stdout=devnull, stderr=devnull
|
||||
|
||||
print "EXE completed"
|
||||
|
||||
except Exception as e:
|
||||
print "Encountered an error: \n\t", e
|
||||
raise
|
||||
finally:
|
||||
print "Deleting tmp files\n"
|
||||
try:
|
||||
try:
|
||||
shutil.rmtree("dist") # Inno dir
|
||||
except:
|
||||
pass
|
||||
shutil.rmtree(dst)
|
||||
os.unlink(tmpFile)
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.stdout = oldstd
|
||||
if os.path.isdir(destination):
|
||||
print os.path.join(destination, os.path.split(tmpFile)[1])
|
||||
else:
|
||||
print destination
|
||||
@@ -57,11 +57,16 @@ import re
|
||||
import sqlite3
|
||||
from optparse import OptionParser
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
|
||||
# Form list of effects for processing
|
||||
effects_path = os.path.join(script_dir, "..", "eos", "effects")
|
||||
|
||||
usage = "usage: %prog --database=DB [--debug=DEBUG]"
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option("-d", "--database", help="path to eve cache data dump in \
|
||||
sqlite format, default pyfa database path is used if none specified",
|
||||
type="string", default=os.path.join("~", ".pyfa","eve.db"))
|
||||
sqlite format, default to eve database file included in pyfa (../staticdata/eve.db)",
|
||||
type="string", default=os.path.join(script_dir, "..", "staticdata", "eve.db"))
|
||||
parser.add_option("-e", "--effects", help="explicit comma-separated list of \
|
||||
effects to process", type="string", default="")
|
||||
parser.add_option("-r", "--remove", help="remove effect files that are not \
|
||||
@@ -118,7 +123,10 @@ db = sqlite3.connect(os.path.expanduser(options.database))
|
||||
cursor = db.cursor()
|
||||
|
||||
# Force some of the items to make them published
|
||||
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper")
|
||||
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper",
|
||||
"Amarr Tactical Destroyer Propulsion Mode",
|
||||
"Amarr Tactical Destroyer Sharpshooter Mode",
|
||||
"Amarr Tactical Destroyer Defense Mode")
|
||||
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName = ?'
|
||||
for typename in FORCEPUB_TYPES:
|
||||
cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
@@ -379,8 +387,6 @@ for typeid in publishedtypes:
|
||||
(set(), len(typenamesplitted))
|
||||
globalmap_typeid_typenamecombtuple[typeid][0].add(typenamecomb)
|
||||
|
||||
# Form list of effects for processing
|
||||
effects_path = os.path.join("..", "..", "effects")
|
||||
if options.effects:
|
||||
effect_list = options.effects.split(",")
|
||||
else:
|
||||
@@ -5,8 +5,10 @@ import os.path
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
script_dir = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||
|
||||
# Connect to database and set up cursor
|
||||
db = sqlite3.connect(os.path.join("..", "..", "..", "staticdata", "eve.db"))
|
||||
db = sqlite3.connect(os.path.join(script_dir, "..", "staticdata", "eve.db"))
|
||||
cursor = db.cursor()
|
||||
|
||||
# Queries to get raw data
|
||||
510
scripts/itemDiff.py
Executable file
510
scripts/itemDiff.py
Executable file
@@ -0,0 +1,510 @@
|
||||
#!/usr/bin/env python3
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010-2011 Anton Vorobyov
|
||||
#
|
||||
# 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/>.
|
||||
#===============================================================================
|
||||
|
||||
|
||||
'''
|
||||
This script is used to compare two different database versions.
|
||||
It shows removed/changed/new items with list of changed effects,
|
||||
changed attributes and effects which were renamed
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
default_old = os.path.join(script_dir, "..", "staticdata", "eve.db")
|
||||
|
||||
def main(old, new, groups=True, effects=True, attributes=True, renames=True):
|
||||
# Open both databases and get their cursors
|
||||
old_db = sqlite3.connect(os.path.expanduser(old))
|
||||
old_cursor = old_db.cursor()
|
||||
new_db = sqlite3.connect(os.path.expanduser(new))
|
||||
new_cursor = new_db.cursor()
|
||||
|
||||
# Force some of the items to make them published
|
||||
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper",
|
||||
"Amarr Tactical Destroyer Propulsion Mode",
|
||||
"Amarr Tactical Destroyer Sharpshooter Mode",
|
||||
"Amarr Tactical Destroyer Defense Mode")
|
||||
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName = ?'
|
||||
for typename in FORCEPUB_TYPES:
|
||||
old_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
new_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
|
||||
# Initialization of few things used by both changed/renamed effects list
|
||||
script_dir = os.path.dirname(__file__)
|
||||
effectspath = os.path.join(script_dir, "..", "eos", "effects")
|
||||
implemented = set()
|
||||
|
||||
for filename in os.listdir(effectspath):
|
||||
basename, extension = filename.rsplit('.', 1)
|
||||
# Ignore non-py files and exclude implementation-specific 'effect'
|
||||
if extension == "py" and basename not in ("__init__",):
|
||||
implemented.add(basename)
|
||||
|
||||
# Effects' names are used w/o any special symbols by eos
|
||||
stripspec = "[^A-Za-z0-9]"
|
||||
|
||||
# Method to get data if effect is implemented in eos or not
|
||||
def geteffst(effectname):
|
||||
eosname = re.sub(stripspec, "", effectname).lower()
|
||||
if eosname in implemented:
|
||||
impstate = True
|
||||
else:
|
||||
impstate = False
|
||||
return impstate
|
||||
|
||||
def findrenames(ren_dict, query, strip=False):
|
||||
|
||||
old_namedata = {}
|
||||
new_namedata = {}
|
||||
|
||||
for cursor, dictionary in ((old_cursor, old_namedata), (new_cursor, new_namedata)):
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
if strip is True:
|
||||
name = re.sub(stripspec, "", name)
|
||||
dictionary[id] = name
|
||||
|
||||
for id in set(old_namedata.keys()).intersection(new_namedata.keys()):
|
||||
oldname = old_namedata[id]
|
||||
newname = new_namedata[id]
|
||||
if oldname != newname:
|
||||
ren_dict[id] = (oldname, newname)
|
||||
return
|
||||
|
||||
def printrenames(ren_dict, title, implementedtag=False):
|
||||
if len(ren_dict) > 0:
|
||||
print('\nRenamed ' + title + ':')
|
||||
for id in sorted(ren_dict):
|
||||
couple = ren_dict[id]
|
||||
if implementedtag:
|
||||
print("\n[{0}] \"{1}\"\n[{2}] \"{3}\"".format(geteffst(couple[0]), couple[0], geteffst(couple[1]), couple[1]))
|
||||
else:
|
||||
print("\n\"{0}\"\n\"{1}\"".format(couple[0], couple[1]))
|
||||
|
||||
groupcats = {}
|
||||
def getgroupcat(grp):
|
||||
"""Get group category from the new db"""
|
||||
if grp in groupcats:
|
||||
cat = groupcats[grp]
|
||||
else:
|
||||
query = 'SELECT categoryID FROM invgroups WHERE groupID = ?'
|
||||
new_cursor.execute(query, (grp,))
|
||||
cat = 0
|
||||
for row in new_cursor:
|
||||
cat = row[0]
|
||||
groupcats[grp] = cat
|
||||
return cat
|
||||
|
||||
itemnames = {}
|
||||
def getitemname(item):
|
||||
"""Get item name from the new db"""
|
||||
if item in itemnames:
|
||||
name = itemnames[item]
|
||||
else:
|
||||
query = 'SELECT typeName FROM invtypes WHERE typeID = ?'
|
||||
new_cursor.execute(query, (item,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (item,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
itemnames[item] = name
|
||||
return name
|
||||
|
||||
groupnames = {}
|
||||
def getgroupname(grp):
|
||||
"""Get group name from the new db"""
|
||||
if grp in groupnames:
|
||||
name = groupnames[grp]
|
||||
else:
|
||||
query = 'SELECT groupName FROM invgroups WHERE groupID = ?'
|
||||
new_cursor.execute(query, (grp,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (grp,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
groupnames[grp] = name
|
||||
return name
|
||||
|
||||
effectnames = {}
|
||||
def geteffectname(effect):
|
||||
"""Get effect name from the new db"""
|
||||
if effect in effectnames:
|
||||
name = effectnames[effect]
|
||||
else:
|
||||
query = 'SELECT effectName FROM dgmeffects WHERE effectID = ?'
|
||||
new_cursor.execute(query, (effect,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (effect,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
effectnames[effect] = name
|
||||
return name
|
||||
|
||||
attrnames = {}
|
||||
def getattrname(attr):
|
||||
"""Get attribute name from the new db"""
|
||||
if attr in attrnames:
|
||||
name = attrnames[attr]
|
||||
else:
|
||||
query = 'SELECT attributeName FROM dgmattribs WHERE attributeID = ?'
|
||||
new_cursor.execute(query, (attr,))
|
||||
name = ""
|
||||
for row in new_cursor:
|
||||
name = row[0]
|
||||
if not name:
|
||||
old_cursor.execute(query, (attr,))
|
||||
for row in old_cursor:
|
||||
name = row[0]
|
||||
attrnames[attr] = name
|
||||
return name
|
||||
|
||||
# State table
|
||||
S = {"unchanged": 0,
|
||||
"removed": 1,
|
||||
"changed": 2,
|
||||
"added": 3 }
|
||||
|
||||
if effects or attributes or groups:
|
||||
# Format:
|
||||
# Key: item id
|
||||
# Value: [groupID, set(effects), {attribute id : value}]
|
||||
old_itmdata = {}
|
||||
new_itmdata = {}
|
||||
|
||||
for cursor, dictionary in ((old_cursor, old_itmdata), (new_cursor, new_itmdata)):
|
||||
# Compose list of items we're interested in, filtered by category
|
||||
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID INNER JOIN invcategories AS ic ON ig.categoryID = ic.categoryID WHERE it.published = 1 AND ic.categoryName IN ("Ship", "Module", "Charge", "Skill", "Drone", "Implant", "Subsystem")'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
groupID = row[1]
|
||||
# Initialize container for the data for each item with empty stuff besides groupID
|
||||
dictionary[itemid] = [groupID, set(), {}]
|
||||
# Add items filtered by group
|
||||
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID WHERE it.published = 1 AND ig.groupName IN ("Effect Beacon", "Ship Modifiers")'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
groupID = row[1]
|
||||
dictionary[itemid] = [groupID, set(), {}]
|
||||
|
||||
if effects:
|
||||
# Pull all eff
|
||||
query = 'SELECT it.typeID, de.effectID FROM invtypes AS it INNER JOIN dgmtypeeffects AS dte ON dte.typeID = it.typeID INNER JOIN dgmeffects AS de ON de.effectID = dte.effectID WHERE it.published = 1'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
effectID = row[1]
|
||||
# Process only items we need
|
||||
if itemid in dictionary:
|
||||
# Add effect to the set
|
||||
effectSet = dictionary[itemid][1]
|
||||
effectSet.add(effectID)
|
||||
|
||||
if attributes:
|
||||
# Add base attributes to our data
|
||||
query = 'SELECT it.typeID, it.mass, it.capacity, it.volume FROM invtypes AS it'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
if itemid in dictionary:
|
||||
attrdict = dictionary[itemid][2]
|
||||
# Add base attributes: mass (4), capacity (38) and volume (161)
|
||||
attrdict[4] = row[1]
|
||||
attrdict[38] = row[2]
|
||||
attrdict[161] = row[3]
|
||||
|
||||
# Add attribute data for other attributes
|
||||
query = 'SELECT dta.typeID, dta.attributeID, dta.value FROM dgmtypeattribs AS dta'
|
||||
cursor.execute(query)
|
||||
for row in cursor:
|
||||
itemid = row[0]
|
||||
if itemid in dictionary:
|
||||
attrid = row[1]
|
||||
attrval = row[2]
|
||||
attrdict = dictionary[itemid][2]
|
||||
if attrid in attrdict:
|
||||
print("Warning: base attribute is described in non-base attribute table")
|
||||
else:
|
||||
attrdict[attrid] = attrval
|
||||
|
||||
# Get set of IDs from both dictionaries
|
||||
items_old = set(old_itmdata.keys())
|
||||
items_new = set(new_itmdata.keys())
|
||||
|
||||
# Format:
|
||||
# Key: item state
|
||||
# Value: {item id: ((group state, old group, new group), {effect state: set(effects)}, {attribute state: {attributeID: (old value, new value)}})}
|
||||
global_itmdata = {}
|
||||
|
||||
# Initialize it
|
||||
for state in S:
|
||||
global_itmdata[S[state]] = {}
|
||||
|
||||
|
||||
# Fill all the data for removed items
|
||||
for item in items_old.difference(items_new):
|
||||
# Set item state to removed
|
||||
state = S["removed"]
|
||||
# Set only old group for item
|
||||
oldgroup = old_itmdata[item][0]
|
||||
groupdata = (S["unchanged"], oldgroup, None)
|
||||
# Set old set of effects and mark all as unchanged
|
||||
effectsdata = {}
|
||||
effectsdata[S["unchanged"]] = set()
|
||||
if effects:
|
||||
oldeffects = old_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(oldeffects)
|
||||
# Set old set of attributes and mark all as unchanged
|
||||
attrdata = {}
|
||||
attrdata[S["unchanged"]] = {}
|
||||
if attributes:
|
||||
oldattrs = old_itmdata[item][2]
|
||||
for attr in oldattrs:
|
||||
# NULL will mean there's no such attribute in db
|
||||
attrdata[S["unchanged"]][attr] = (oldattrs[attr], "NULL")
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
|
||||
# Now, for added items
|
||||
for item in items_new.difference(items_old):
|
||||
# Set item state to added
|
||||
state = S["added"]
|
||||
# Set only new group for item
|
||||
newgroup = new_itmdata[item][0]
|
||||
groupdata = (S["unchanged"], None, newgroup)
|
||||
# Set new set of effects and mark all as unchanged
|
||||
effectsdata = {}
|
||||
effectsdata[S["unchanged"]] = set()
|
||||
if effects:
|
||||
neweffects = new_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(neweffects)
|
||||
# Set new set of attributes and mark all as unchanged
|
||||
attrdata = {}
|
||||
attrdata[S["unchanged"]] = {}
|
||||
if attributes:
|
||||
newattrs = new_itmdata[item][2]
|
||||
for attr in newattrs:
|
||||
# NULL will mean there's no such attribute in db
|
||||
attrdata[S["unchanged"]][attr] = ("NULL", newattrs[attr])
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
# Now, check all the items which exist in both databases
|
||||
for item in items_old.intersection(items_new):
|
||||
# Set group data for an item
|
||||
oldgroup = old_itmdata[item][0]
|
||||
newgroup = new_itmdata[item][0]
|
||||
# If we're not asked to compare groups, mark them as unchanged anyway
|
||||
groupdata = (S["changed"] if oldgroup != newgroup and groups else S["unchanged"], oldgroup, newgroup)
|
||||
# Fill effects data into appropriate groups
|
||||
effectsdata = {}
|
||||
for state in S:
|
||||
# We do not have changed effects whatsoever
|
||||
if state != "changed":
|
||||
effectsdata[S[state]] = set()
|
||||
if effects:
|
||||
oldeffects = old_itmdata[item][1]
|
||||
neweffects = new_itmdata[item][1]
|
||||
effectsdata[S["unchanged"]].update(oldeffects.intersection(neweffects))
|
||||
effectsdata[S["removed"]].update(oldeffects.difference(neweffects))
|
||||
effectsdata[S["added"]].update(neweffects.difference(oldeffects))
|
||||
# Go through all attributes, filling global data dictionary
|
||||
attrdata = {}
|
||||
for state in S:
|
||||
attrdata[S[state]] = {}
|
||||
if attributes:
|
||||
oldattrs = old_itmdata[item][2]
|
||||
newattrs = new_itmdata[item][2]
|
||||
for attr in set(oldattrs.keys()).union(newattrs.keys()):
|
||||
# NULL will mean there's no such attribute in db
|
||||
oldattr = oldattrs.get(attr, "NULL")
|
||||
newattr = newattrs.get(attr, "NULL")
|
||||
attrstate = S["unchanged"]
|
||||
if oldattr == "NULL" and newattr != "NULL":
|
||||
attrstate = S["added"]
|
||||
elif oldattr != "NULL" and newattr == "NULL":
|
||||
attrstate = S["removed"]
|
||||
elif oldattr != newattr:
|
||||
attrstate = S["changed"]
|
||||
attrdata[attrstate][attr] = (oldattr, newattr)
|
||||
# Consider item as unchanged by default and set it to change when we see any changes in sub-items
|
||||
state = S["unchanged"]
|
||||
if state == S["unchanged"] and groupdata[0] != S["unchanged"]:
|
||||
state = S["changed"]
|
||||
if state == S["unchanged"] and (len(effectsdata[S["removed"]]) > 0 or len(effectsdata[S["added"]]) > 0):
|
||||
state = S["changed"]
|
||||
if state == S["unchanged"] and (len(attrdata[S["removed"]]) > 0 or len(attrdata[S["changed"]]) > 0 or len(attrdata[S["added"]]) > 0):
|
||||
state = S["changed"]
|
||||
# Fill global dictionary with data we've got
|
||||
global_itmdata[state][item] = (groupdata, effectsdata, attrdata)
|
||||
|
||||
# As eos uses names as unique IDs in lot of places, we have to keep track of name changes
|
||||
if renames:
|
||||
ren_effects = {}
|
||||
query = 'SELECT effectID, effectName FROM dgmeffects'
|
||||
findrenames(ren_effects, query, strip = True)
|
||||
|
||||
ren_attributes = {}
|
||||
query = 'SELECT attributeID, attributeName FROM dgmattribs'
|
||||
findrenames(ren_attributes, query)
|
||||
|
||||
ren_categories = {}
|
||||
query = 'SELECT categoryID, categoryName FROM invcategories'
|
||||
findrenames(ren_categories, query)
|
||||
|
||||
ren_groups = {}
|
||||
query = 'SELECT groupID, groupName FROM invgroups'
|
||||
findrenames(ren_groups, query)
|
||||
|
||||
ren_marketgroups = {}
|
||||
query = 'SELECT marketGroupID, marketGroupName FROM invmarketgroups'
|
||||
findrenames(ren_marketgroups, query)
|
||||
|
||||
ren_items = {}
|
||||
query = 'SELECT typeID, typeName FROM invtypes'
|
||||
findrenames(ren_items, query)
|
||||
|
||||
try:
|
||||
# Get db metadata
|
||||
old_meta = {}
|
||||
new_meta = {}
|
||||
query = 'SELECT field_name, field_value FROM metadata WHERE field_name LIKE "client_build"'
|
||||
old_cursor.execute(query)
|
||||
for row in old_cursor:
|
||||
old_meta[row[0]] = row[1]
|
||||
new_cursor.execute(query)
|
||||
for row in new_cursor:
|
||||
new_meta[row[0]] = row[1]
|
||||
except:
|
||||
pass
|
||||
# Print jobs
|
||||
print("Comparing databases:\n{0} -> {1}\n".format(old_meta.get("client_build"), new_meta.get("client_build")))
|
||||
if effects or attributes or groups:
|
||||
# Print legend only when there're any interesting changes
|
||||
if len(global_itmdata[S["removed"]]) > 0 or len(global_itmdata[S["changed"]]) > 0 or len(global_itmdata[S["added"]]) > 0:
|
||||
genleg = "[+] - new item\n[-] - removed item\n[*] - changed item\n"
|
||||
grpleg = "(x => y) - group changes\n" if groups else ""
|
||||
attreffleg = " [+] - effect or attribute has been added to item\n [-] - effect or attribute has been removed from item\n" if attributes or effects else ""
|
||||
effleg = " [y] - effect is implemented\n [n] - effect is not implemented\n" if effects else ""
|
||||
print("{0}{1}{2}{3}\nItems:".format(genleg, grpleg, attreffleg, effleg))
|
||||
|
||||
# Make sure our states are sorted
|
||||
stateorder = sorted(global_itmdata)
|
||||
|
||||
TG = {S["unchanged"]: "+", S["changed"]: "*",
|
||||
S["removed"]: "-",
|
||||
S["added"]: "+"}
|
||||
|
||||
# Cycle through states
|
||||
for itmstate in stateorder:
|
||||
# Skip unchanged items
|
||||
if itmstate == S["unchanged"]:
|
||||
continue
|
||||
items = global_itmdata[itmstate]
|
||||
# Sort by name first
|
||||
itemorder = sorted(items, key=lambda item: getitemname(item))
|
||||
# Then by group id
|
||||
itemorder = sorted(itemorder, key=lambda item: items[item][0][2] or items[item][0][1])
|
||||
# Then by category id
|
||||
itemorder = sorted(itemorder, key=lambda item: getgroupcat(items[item][0][2] or items[item][0][1]))
|
||||
|
||||
for item in itemorder:
|
||||
groupdata = items[item][0]
|
||||
groupstr = " ({0} => {1})".format(getgroupname(groupdata[1]), getgroupname(groupdata[2])) if groupdata[0] == S["changed"] else ""
|
||||
print("\n[{0}] {1}{2}".format(TG[itmstate], getitemname(item), groupstr))
|
||||
|
||||
effdata = items[item][1]
|
||||
for effstate in stateorder:
|
||||
# Skip unchanged effect sets, but always include them for added or removed ships
|
||||
# Also, always skip empty data
|
||||
if (effstate == S["unchanged"] and itmstate not in (S["removed"], S["added"])) or effstate not in effdata:
|
||||
continue
|
||||
effects = effdata[effstate]
|
||||
efforder = sorted(effects, key=lambda eff: geteffectname(eff))
|
||||
for eff in efforder:
|
||||
# Take tag from item if item was added or removed
|
||||
tag = TG[effstate] if itmstate not in (S["removed"], S["added"]) else TG[itmstate]
|
||||
print(" [{0}|{1}] {2}".format(tag, "y" if geteffst(geteffectname(eff)) else "n", geteffectname(eff)))
|
||||
|
||||
attrdata = items[item][2]
|
||||
for attrstate in stateorder:
|
||||
# Skip unchanged and empty attribute sets, also skip attributes display for added and removed items
|
||||
if (attrstate == S["unchanged"] and itmstate != S["added"]) or itmstate in (S["removed"], ) or attrstate not in attrdata:
|
||||
continue
|
||||
attrs = attrdata[attrstate]
|
||||
attrorder = sorted(attrs, key=lambda attr: getattrname(attr))
|
||||
for attr in attrorder:
|
||||
valline = ""
|
||||
if attrs[attr][0] == "NULL" or itmstate == S["added"]:
|
||||
valline = "{0}".format(attrs[attr][1] or 0)
|
||||
elif attrs[attr][1] == "NULL":
|
||||
valline = "{0}".format(attrs[attr][0] or 0)
|
||||
else:
|
||||
valline = "{0} => {1}".format(attrs[attr][0] or 0, attrs[attr][1] or 0)
|
||||
print(" [{0}] {1}: {2}".format(TG[attrstate], getattrname(attr), valline))
|
||||
|
||||
if renames:
|
||||
title = 'effects'
|
||||
printrenames(ren_effects, title, implementedtag=True)
|
||||
|
||||
title = 'attributes'
|
||||
printrenames(ren_attributes, title)
|
||||
|
||||
title = 'categories'
|
||||
printrenames(ren_categories, title)
|
||||
|
||||
title = 'groups'
|
||||
printrenames(ren_groups, title)
|
||||
|
||||
title = 'market groups'
|
||||
printrenames(ren_marketgroups, title)
|
||||
|
||||
title = 'items'
|
||||
printrenames(ren_items, title)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Compare two databases generated from eve dump to find eos-related differences")
|
||||
parser.add_argument("-o", "--old", type=str, help="path to old cache data dump, defaults to current pyfa eve.db", default=default_old)
|
||||
parser.add_argument("-n", "--new", type=str, required=True, help="path to new cache data dump")
|
||||
parser.add_argument("-g", "--nogroups", action="store_false", default=True, dest="groups", help="don't show changed groups")
|
||||
parser.add_argument("-e", "--noeffects", action="store_false", default=True, dest="effects", help="don't show list of changed effects")
|
||||
parser.add_argument("-a", "--noattributes", action="store_false", default=True, dest="attributes", help="don't show list of changed attributes")
|
||||
parser.add_argument("-r", "--norenames", action="store_false", default=True, dest="renames", help="don't show list of renamed data")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.old, args.new, args.groups, args.effects, args.attributes, args.renames)
|
||||
@@ -23,22 +23,18 @@ import sys
|
||||
|
||||
# Add eos root path to sys.path so we can import ourselves
|
||||
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||
sys.path.append(os.path.realpath(os.path.join(path, "..", "..", "..")))
|
||||
sys.path.append(os.path.realpath(os.path.join(path, "..")))
|
||||
|
||||
import json
|
||||
import argparse
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="This scripts dumps effects from an sqlite cache dump to mongo")
|
||||
parser.add_argument("-d", "--db", required=True, type=str, help="The sqlalchemy connectionstring, example: sqlite:///c:/tq.db")
|
||||
parser.add_argument("-j", "--json", required=True, type=str, help="The path to the json dum")
|
||||
args = parser.parse_args()
|
||||
def main(db, json_path):
|
||||
|
||||
jsonPath = os.path.expanduser(args.json)
|
||||
jsonPath = os.path.expanduser(json_path)
|
||||
|
||||
# Import eos.config first and change it
|
||||
import eos.config
|
||||
eos.config.gamedata_connectionstring = args.db
|
||||
eos.config.gamedata_connectionstring = db
|
||||
eos.config.debug = False
|
||||
|
||||
# Now thats done, we can import the eos modules using the config
|
||||
@@ -62,6 +58,7 @@ if __name__ == "__main__":
|
||||
"invmetatypes": eos.gamedata.MetaType,
|
||||
"invtypes": eos.gamedata.Item,
|
||||
"phbtraits": eos.gamedata.Traits,
|
||||
"phbmetadata": eos.gamedata.MetaData,
|
||||
"mapbulk_marketGroups": eos.gamedata.MarketGroup
|
||||
}
|
||||
|
||||
@@ -113,7 +110,7 @@ if __name__ == "__main__":
|
||||
sectionLines.append(headerText)
|
||||
for bonusData in sectionData["bonuses"]:
|
||||
prefix = u"{} ".format(bonusData["number"]) if "number" in bonusData else ""
|
||||
bonusText = u"{}{}".format(prefix, bonusData["text"])
|
||||
bonusText = u"{}{}".format(prefix, bonusData["text"].replace(u"\u00B7", u"\u2022 "))
|
||||
sectionLines.append(bonusText)
|
||||
sectionLine = u"<br />\n".join(sectionLines)
|
||||
return sectionLine
|
||||
@@ -134,6 +131,21 @@ if __name__ == "__main__":
|
||||
newData.append(newRow)
|
||||
return newData
|
||||
|
||||
def convertTypes(typesData):
|
||||
"""
|
||||
Add factionID column to invtypes table.
|
||||
"""
|
||||
factionMap = {}
|
||||
with open(os.path.join(jsonPath, "fsdTypeOverrides.json")) as f:
|
||||
overridesData = json.load(f)
|
||||
for typeID, typeData in overridesData.items():
|
||||
factionID = typeData.get("factionID")
|
||||
if factionID is not None:
|
||||
factionMap[int(typeID)] = factionID
|
||||
for row in typesData:
|
||||
row['factionID'] = factionMap.get(int(row['typeID']))
|
||||
return typesData
|
||||
|
||||
data = {}
|
||||
|
||||
# Dump all data to memory so we can easely cross check ignored rows
|
||||
@@ -144,25 +156,21 @@ if __name__ == "__main__":
|
||||
tableData = convertIcons(tableData)
|
||||
if jsonName == "phbtraits":
|
||||
tableData = convertTraits(tableData)
|
||||
if jsonName == "invtypes":
|
||||
tableData = convertTypes(tableData)
|
||||
data[jsonName] = tableData
|
||||
|
||||
# Do some preprocessing to make our job easier
|
||||
# Set with typeIDs which we will have in our database
|
||||
invTypes = set()
|
||||
for row in data["invtypes"]:
|
||||
if row["published"]:
|
||||
# 1306 - group Ship Modifiers, for items like tactical t3 ship modes
|
||||
if (row["published"] or row['groupID'] == 1306):
|
||||
invTypes.add(row["typeID"])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file == "invtypes" and not row["published"]:
|
||||
if file in ("invtypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in invTypes:
|
||||
return True
|
||||
elif file == "dgmtypeeffects" and not row["typeID"] in invTypes:
|
||||
return True
|
||||
elif file == "dgmtypeattribs" and not row["typeID"] in invTypes:
|
||||
return True
|
||||
elif file == "invmetatypes" and not row["typeID"] in invTypes:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# Loop through each json file and write it away, checking ignored rows
|
||||
@@ -181,6 +189,14 @@ if __name__ == "__main__":
|
||||
|
||||
eos.db.gamedata_session.add(instance)
|
||||
|
||||
eos.db.gamedata_session.commit()
|
||||
eos.db.gamedata_session.commit()
|
||||
|
||||
print("done")
|
||||
print("done")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="This scripts dumps effects from an sqlite cache dump to mongo")
|
||||
parser.add_argument("-d", "--db", required=True, type=str, help="The sqlalchemy connectionstring, example: sqlite:///c:/tq.db")
|
||||
parser.add_argument("-j", "--json", required=True, type=str, help="The path to the json dump")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.db, args.json)
|
||||
94
scripts/prep_data.py
Normal file
94
scripts/prep_data.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Phobos location
|
||||
phb_path = os.path.expanduser("path/to/phobos")
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-e", "--eve", dest="eve_path", help="Location of EVE directory", required=True)
|
||||
parser.add_argument("-c", "--cache", dest="cache_path", help="Location of EVE cache directory. If not specified, an attempt will be make to automatically determine path.")
|
||||
parser.add_argument("-d", "--dump", dest="dump_path", help="Location of Phobos JSON dump directory", required=True)
|
||||
parser.add_argument("-p", "--phobos", dest="phb_path", help="Location of Phobos, defaults to path noted in script", default=phb_path)
|
||||
parser.add_argument("-s", "--singularity", action="store_true", help="Singularity build")
|
||||
parser.add_argument("-j", "--nojson", dest="nojson", action="store_true", help="Skip Phobos JSON data dump.")
|
||||
|
||||
args = parser.parse_args()
|
||||
eve_path = os.path.expanduser(unicode(args.eve_path, sys.getfilesystemencoding()))
|
||||
cache_path = os.path.expanduser(unicode(args.cache_path, sys.getfilesystemencoding())) if args.cache_path else None
|
||||
dump_path = os.path.expanduser(unicode(args.dump_path, sys.getfilesystemencoding()))
|
||||
script_path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
||||
|
||||
### Append Phobos to path
|
||||
sys.path.append(os.path.expanduser(unicode(args.phb_path, sys.getfilesystemencoding())))
|
||||
|
||||
def header(text, subtext=None):
|
||||
print
|
||||
print "* "*30
|
||||
print text.center(60)
|
||||
if subtext:
|
||||
print subtext.center(60)
|
||||
print "* "*30
|
||||
print
|
||||
|
||||
### Data dump
|
||||
if not args.nojson:
|
||||
header("Dumping Phobos Data", dump_path)
|
||||
|
||||
import reverence
|
||||
from flow import FlowManager
|
||||
from miner import *
|
||||
from translator import Translator
|
||||
from writer import *
|
||||
|
||||
rvr = reverence.blue.EVE(eve_path, cachepath=args.cache_path, server="singularity" if args.singularity else "tranquility")
|
||||
|
||||
spickle_miner = StuffedPickleMiner(rvr)
|
||||
trans = Translator(spickle_miner)
|
||||
bulkdata_miner = BulkdataMiner(rvr, trans)
|
||||
|
||||
miners = (
|
||||
MetadataMiner(eve_path),
|
||||
bulkdata_miner,
|
||||
TraitMiner(bulkdata_miner, trans),
|
||||
SqliteMiner(eve_path, trans),
|
||||
CachedCallsMiner(rvr, trans),
|
||||
spickle_miner
|
||||
)
|
||||
|
||||
writers = (
|
||||
JsonWriter(dump_path, indent=2),
|
||||
)
|
||||
|
||||
list = "dgmexpressions,dgmattribs,dgmeffects,dgmtypeattribs,dgmtypeeffects,"\
|
||||
"dgmunits,icons,invcategories,invgroups,invmetagroups,invmetatypes,"\
|
||||
"invtypes,mapbulk_marketGroups,phbmetadata,phbtraits,fsdTypeOverrides"
|
||||
|
||||
FlowManager(miners, writers).run(list, "multi")
|
||||
|
||||
### SQL Convert
|
||||
import jsonToSql
|
||||
|
||||
db_file = os.path.join(dump_path, "eve.db")
|
||||
header("Converting Data to SQL", db_file)
|
||||
|
||||
if os.path.isfile(db_file):
|
||||
os.remove(db_file)
|
||||
|
||||
jsonToSql.main("sqlite:///"+db_file, dump_path)
|
||||
|
||||
### Diff generation
|
||||
import itemDiff
|
||||
diff_file = os.path.join(dump_path, "diff.txt")
|
||||
old_db = os.path.join(script_path, "..", "staticdata", "eve.db")
|
||||
|
||||
header("Generating DIFF", diff_file)
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = open(diff_file, 'w')
|
||||
itemDiff.main(old=old_db, new=db_file)
|
||||
sys.stdout = old_stdout
|
||||
|
||||
print "\nAll done."
|
||||
104
scripts/pyfa-setup.iss
Normal file
104
scripts/pyfa-setup.iss
Normal file
@@ -0,0 +1,104 @@
|
||||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
; Versioning
|
||||
; we do some #ifdef conditionals because automated compilation passes these as arguments
|
||||
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "1.3.0"
|
||||
#endif
|
||||
#ifndef MyAppExpansion
|
||||
#define MyAppExpansion "Crius 1.0"
|
||||
#endif
|
||||
|
||||
; Other config
|
||||
|
||||
#define MyAppName "pyfa"
|
||||
#define MyAppPublisher "pyfa"
|
||||
#define MyAppURL "https://forums.eveonline.com/default.aspx?g=posts&t=247609&p=1"
|
||||
#define MyAppExeName "pyfa.exe"
|
||||
|
||||
#ifndef MyOutputFile
|
||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-'+MyAppExpansion+'-win', " ", "-"))
|
||||
#endif
|
||||
#ifndef MyAppDir
|
||||
#define MyAppDir "pyfa"
|
||||
#endif
|
||||
#ifndef MyOutputDir
|
||||
#define MyOutputDir "dist"
|
||||
#endif
|
||||
|
||||
[Setup]
|
||||
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{3DA39096-C08D-49CD-90E0-1D177F32C8AA}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion} ({#MyAppExpansion})
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={pf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
LicenseFile={#MyAppDir}\gpl.txt
|
||||
OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename={#MyOutputFile}
|
||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
CloseApplications=yes
|
||||
AppReadmeFile=https://github.com/DarkFenX/Pyfa/blob/v{#MyAppVersion}/readme.txt
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
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}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
|
||||
function IsAppRunning(const FileName : string): Boolean;
|
||||
var
|
||||
FSWbemLocator: Variant;
|
||||
FWMIService : Variant;
|
||||
FWbemObjectSet: Variant;
|
||||
begin
|
||||
Result := false;
|
||||
FSWbemLocator := CreateOleObject('WBEMScripting.SWBEMLocator');
|
||||
FWMIService := FSWbemLocator.ConnectServer('', 'root\CIMV2', '', '');
|
||||
FWbemObjectSet := FWMIService.ExecQuery(Format('SELECT Name FROM Win32_Process Where Name="%s"',[FileName]));
|
||||
Result := (FWbemObjectSet.Count > 0);
|
||||
FWbemObjectSet := Unassigned;
|
||||
FWMIService := Unassigned;
|
||||
FSWbemLocator := Unassigned;
|
||||
end;
|
||||
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
if(IsAppRunning( 'pyfa.exe' )) then
|
||||
begin
|
||||
Result := 'Please close pyfa before continuing. When closed, please go back to the previous step and continue.';
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := '';
|
||||
end
|
||||
end;
|
||||
@@ -48,6 +48,8 @@ CONVERSIONS = {
|
||||
"Merlin Wiyrkomi Edition": "Merlin",
|
||||
"Miasmos Amastris Edition": "Miasmos Quafe Ultra Edition",
|
||||
"Miasmos Quafe Ultramarine Edition": "Miasmos Quafe Ultra Edition",
|
||||
"Moros Interbus Edition": "Moros",
|
||||
"Naglfar Justice Edition": "Naglfar",
|
||||
"Nefantar Thrasher": "Thrasher",
|
||||
"Omen Kador Edition": "Omen",
|
||||
"Omen Tash-Murkon Edition": "Omen",
|
||||
@@ -55,6 +57,7 @@ CONVERSIONS = {
|
||||
"Paladin Blood Raider Edition": "Paladin",
|
||||
"Paladin Kador Edition": "Paladin",
|
||||
"Paladin Tash-Murkon Edition": "Paladin",
|
||||
"Phoenix Wiyrkomi Edition": "Phoenix",
|
||||
"Police Pursuit Comet": "Federation Navy Comet",
|
||||
"Prophecy Blood Raiders Edition": "Prophecy",
|
||||
"Punisher Kador Edition": "Punisher",
|
||||
@@ -64,6 +67,7 @@ CONVERSIONS = {
|
||||
"Raven Kaalakiota Edition": "Raven",
|
||||
"Raven Nugoeihuvi Edition": "Raven",
|
||||
"Rattlesnake Victory Edition": "Rattlesnake",
|
||||
"Revelation Sarum Edition": "Revelation",
|
||||
"Rifter Krusual Edition": "Rifter",
|
||||
"Rifter Nefantar Edition": "Rifter",
|
||||
"Rokh Nugoeihuvi Edition": "Rokh",
|
||||
|
||||
@@ -147,6 +147,7 @@ class Fit(object):
|
||||
def newFit(self, shipID, name=None):
|
||||
fit = eos.types.Fit()
|
||||
fit.ship = eos.types.Ship(eos.db.getItem(shipID))
|
||||
fit.mode = fit.ship.checkModeItem(None)
|
||||
fit.name = name if name is not None else "New %s" % fit.ship.item.name
|
||||
fit.damagePattern = self.pattern
|
||||
fit.targetResists = self.targetResists
|
||||
@@ -719,6 +720,16 @@ class Fit(object):
|
||||
|
||||
self.recalc(fit)
|
||||
|
||||
def setMode(self, fitID, mode):
|
||||
if fitID is None:
|
||||
return
|
||||
|
||||
fit = eos.db.getFit(fitID)
|
||||
fit.mode = mode
|
||||
eos.db.commit()
|
||||
|
||||
self.recalc(fit)
|
||||
|
||||
def setAsPattern(self, fitID, ammo):
|
||||
if fitID is None:
|
||||
return
|
||||
|
||||
@@ -32,7 +32,7 @@ import service.conversions as conversions
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
# Event which tells threads dependent on Market that it's initialized
|
||||
mktRdy = threading.Event()
|
||||
@@ -214,7 +214,8 @@ class Market():
|
||||
"Goru's Shuttle": False,
|
||||
"Guristas Shuttle": False,
|
||||
"Mobile Decoy Unit": False, # Seems to be left over test mod for deployables
|
||||
"Tournament Micro Jump Unit": False } # Normally seen only on tournament arenas
|
||||
"Tournament Micro Jump Unit": False} # Normally seen only on tournament arenas
|
||||
|
||||
|
||||
# do not publish ships that we convert
|
||||
for name in conversions.packs['skinnedShips']:
|
||||
@@ -677,6 +678,10 @@ class Market():
|
||||
|
||||
self.priceCache[typeID] = price
|
||||
|
||||
if not price.isValid:
|
||||
# if the price has expired
|
||||
price.price = None
|
||||
|
||||
return price
|
||||
|
||||
def getPricesNow(self, typeIDs):
|
||||
@@ -699,6 +704,10 @@ class Market():
|
||||
|
||||
self.priceWorkerThread.trigger(requests, cb)
|
||||
|
||||
def clearPriceCache(self):
|
||||
self.priceCache.clear()
|
||||
deleted_rows = eos.db.clearPrices()
|
||||
|
||||
def getSystemWideEffects(self):
|
||||
"""
|
||||
Get dictionary with system-wide effects
|
||||
|
||||
@@ -27,7 +27,7 @@ import service
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from gui.utils.compat import OrderedDict
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
FIT_WIN_HEADINGS = ["High power", "Medium power", "Low power", "Rig Slot", "Sub System", "Charges"]
|
||||
EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM]
|
||||
|
||||
Binary file not shown.
BIN
staticdata/icons/ships/34317.png
Normal file
BIN
staticdata/icons/ships/34317.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user