Merge branch 'master' into wx3

This commit is contained in:
DarkPhoenix
2014-12-12 14:42:25 +03:00
104 changed files with 1847 additions and 1009 deletions

6
.gitignore vendored
View File

@@ -11,8 +11,12 @@
#Patch files
*.patch
#Personal
/saveddata
saveddata/
#PyCharm
.idea/
#Pyfa file
pyfaFits.html

15
README.md Normal file
View 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/)

View File

@@ -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

View File

@@ -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()

View File

@@ -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),

View File

@@ -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)

View File

@@ -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")

View 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")

View 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")

View File

@@ -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),

View File

@@ -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:

View File

@@ -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"))

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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)

View 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)

View File

@@ -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)

View 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")

View 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")

View 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")

View 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")

View File

@@ -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"

View File

@@ -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"))

View File

@@ -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):

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View 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)

View 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")

View 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")

View 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")

View 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")

View 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)

View 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)

View File

@@ -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"),

View File

@@ -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

View File

@@ -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):

View File

@@ -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()

View File

@@ -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
View 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",))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -14,5 +14,8 @@ __all__ = [
"whProjector",
"cargo",
"shipJump",
"targetResists"
#"changeAffectingSkills",
"tacticalMode",
"targetResists",
"priceClear"
]

View File

@@ -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()

View File

@@ -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]

View File

@@ -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()

View File

@@ -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)

View File

@@ -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"

View File

@@ -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])

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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"

View 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()

View File

@@ -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"

View 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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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"

View File

@@ -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):

View File

@@ -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)))

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

View File

@@ -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
View 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

View File

@@ -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:

View File

@@ -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
View 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)

View File

@@ -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
View 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
View 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;

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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.

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