diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py
index c49267050..673f1767f 100644
--- a/eos/db/gamedata/queries.py
+++ b/eos/db/gamedata/queries.py
@@ -23,6 +23,7 @@ from sqlalchemy.sql import and_, or_, select
import eos.config
from eos.db import gamedata_session
from eos.db.gamedata.metaGroup import metatypes_table, items_table
+from eos.db.gamedata.group import groups_table
from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData
@@ -249,7 +250,7 @@ def searchItems(nameLike, where=None, join=None, eager=None):
@cachedQuery(2, "where", "itemids")
-def getVariations(itemids, where=None, eager=None):
+def getVariations(itemids, groupIDs=None, where=None, eager=None):
for itemid in itemids:
if not isinstance(itemid, int):
raise TypeError("All passed item IDs must be integers")
@@ -262,7 +263,17 @@ def getVariations(itemids, where=None, eager=None):
joinon = items_table.c.typeID == metatypes_table.c.typeID
vars = gamedata_session.query(Item).options(*processEager(eager)).join((metatypes_table, joinon)).filter(
filter).all()
- return vars
+
+ if vars:
+ return vars
+ elif groupIDs:
+ itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
+ filter = processWhere(itemfilter, where)
+ joinon = items_table.c.groupID == groups_table.c.groupID
+ vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
+ filter).all()
+
+ return vars
@cachedQuery(1, "attr")
diff --git a/eos/mathUtils.py b/eos/mathUtils.py
deleted file mode 100644
index 845bb4844..000000000
--- a/eos/mathUtils.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# ===============================================================================
-# Copyright (C) 2010 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 .
-# ===============================================================================
-
-from math import floor
-
-
-def floorFloat(value):
- result = int(floor(value))
- return result
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index e332bc14f..aa92ff21a 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -127,6 +127,12 @@ class Fit(object):
self.__capUsed = None
self.__capRecharge = None
self.__calculatedTargets = []
+ self.__remoteReps = {
+ "Armor": None,
+ "Shield": None,
+ "Hull": None,
+ "Capacitor": None,
+ }
self.factorReload = False
self.boostsFits = set()
self.gangBoosts = None
@@ -392,6 +398,9 @@ class Fit(object):
self.ecmProjectedStr = 1
self.commandBonuses = {}
+ for remoterep_type in self.__remoteReps:
+ self.__remoteReps[remoterep_type] = None
+
del self.__calculatedTargets[:]
del self.__extraDrains[:]
@@ -1151,6 +1160,67 @@ class Fit(object):
self.__capStable = True
self.__capState = 100
+ @property
+ def remoteReps(self):
+ force_recalc = False
+ for remote_type in self.__remoteReps:
+ if self.__remoteReps[remote_type] is None:
+ force_recalc = True
+ break
+
+ if force_recalc is False:
+ return self.__remoteReps
+
+ # We are rerunning the recalcs. Explicitly set to 0 to make sure we don't duplicate anything and correctly set all values to 0.
+ for remote_type in self.__remoteReps:
+ self.__remoteReps[remote_type] = 0
+
+ for module in self.modules:
+ # Skip empty and non-Active modules
+ if module.isEmpty or module.state < State.ACTIVE:
+ continue
+
+ # Covert cycleTime to seconds
+ duration = module.cycleTime / 1000
+
+ # Skip modules with no duration.
+ if not duration:
+ continue
+
+ fueledMultiplier = module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
+
+ remote_module_groups = {
+ "Remote Armor Repairer" : "Armor",
+ "Ancillary Remote Armor Repairer": "Armor",
+ "Remote Hull Repairer" : "Hull",
+ "Remote Shield Booster" : "Shield",
+ "Ancillary Remote Shield Booster": "Shield",
+ "Remote Capacitor Transmitter" : "Capacitor",
+ }
+
+ module_group = module.item.group.name
+
+ if module_group in remote_module_groups:
+ remote_type = remote_module_groups[module_group]
+ else:
+ # Module isn't in our list of remote rep modules, bail
+ continue
+
+ if remote_type == "Hull":
+ hp = module.getModifiedItemAttr("structureDamageAmount", 0)
+ elif remote_type == "Armor":
+ hp = module.getModifiedItemAttr("armorDamageAmount", 0)
+ elif remote_type == "Shield":
+ hp = module.getModifiedItemAttr("shieldBonus", 0)
+ elif remote_type == "Capacitor":
+ hp = module.getModifiedItemAttr("powerTransferAmount", 0)
+ else:
+ hp = 0
+
+ self.__remoteReps[remote_type] += (hp * fueledMultiplier) / duration
+
+ return self.__remoteReps
+
@property
def hp(self):
hp = {}
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index 9ea524067..4442cb9b8 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -20,11 +20,11 @@
from logbook import Logger
from sqlalchemy.orm import validates, reconstructor
+from math import floor
import eos.db
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum
-from eos.mathUtils import floorFloat
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.saveddata.citadel import Citadel
@@ -172,7 +172,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if chargeVolume is None or containerCapacity is None:
charges = 0
else:
- charges = floorFloat(float(containerCapacity) / chargeVolume)
+ charges = floor(containerCapacity / chargeVolume)
return charges
@property
@@ -216,9 +216,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def __calculateAmmoShots(self):
if self.charge is not None:
# Set number of cycles before reload is needed
+ # numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
chargeRate = self.getModifiedItemAttr("chargeRate")
numCharges = self.numCharges
- numShots = floorFloat(float(numCharges) / chargeRate)
+ numShots = floor(numCharges / chargeRate)
else:
numShots = None
return numShots
@@ -231,7 +232,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
chance = self.getModifiedChargeAttr("crystalVolatilityChance")
damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
crystals = self.numCharges
- numShots = floorFloat(float(crystals * hp) / (damage * chance))
+ numShots = floor((crystals * hp) / (damage * chance))
else:
# Set 0 (infinite) for permanent crystals like t1 laser crystals
numShots = 0
@@ -665,32 +666,68 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def cycleTime(self):
- reactivation = (self.getModifiedItemAttr("moduleReactivationDelay") or 0)
- # Reactivation time starts counting after end of module cycle
- speed = self.rawCycleTime + reactivation
- if self.charge:
- reload = self.reloadTime
- else:
- reload = 0.0
# Determine if we'll take into account reload time or not
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
- # If reactivation is longer than 10 seconds then module can be reloaded
- # during reactivation time, thus we may ignore reload
- if factorReload and reactivation < reload:
- numShots = self.numShots
- # Time it takes to reload module after end of reactivation time,
- # given that we started when module cycle has just over
- additionalReloadTime = (reload - reactivation)
- # Speed here already takes into consideration reactivation time
- speed = (speed * numShots + additionalReloadTime) / numShots if numShots > 0 else speed
+
+ numShots = self.numShots
+ speed = self.rawCycleTime
+
+ if factorReload and self.charge:
+ raw_reload_time = self.reloadTime
+ else:
+ raw_reload_time = 0.0
+
+ # Module can only fire one shot at a time, think bomb launchers or defender launchers
+ if self.disallowRepeatingAction:
+ if numShots > 1:
+ """
+ The actual mechanics behind this is complex. Behavior will be (for 3 ammo):
+ fire, reactivation delay, fire, reactivation delay, fire, max(reactivation delay, reload)
+ so your effective reload time depends on where you are at in the cycle.
+
+ We can't do that, so instead we'll average it out.
+
+ Currently would apply to bomb launchers and defender missiles
+ """
+ effective_reload_time = ((self.reactivationDelay * numShots) + raw_reload_time) / numShots
+ else:
+ """
+ Applies to MJD/MJFG
+ """
+ effective_reload_time = max(raw_reload_time, self.reactivationDelay, 0)
+ else:
+ """
+ Currently no other modules would have a reactivation delay, so for sanities sake don't try and account for it.
+ Okay, technically cloaks do, but they also have 0 cycle time and cap usage so why do you care?
+ """
+ effective_reload_time = raw_reload_time
+
+ if numShots > 0 and self.charge:
+ speed = (speed * numShots + effective_reload_time) / numShots
return speed
@property
def rawCycleTime(self):
- speed = self.getModifiedItemAttr("speed") or self.getModifiedItemAttr("duration")
+ speed = max(
+ self.getModifiedItemAttr("speed"), # Most weapons
+ self.getModifiedItemAttr("duration"), # Most average modules
+ self.getModifiedItemAttr("durationSensorDampeningBurstProjector"),
+ self.getModifiedItemAttr("durationTargetIlluminationBurstProjector"),
+ self.getModifiedItemAttr("durationECMJammerBurstProjector"),
+ self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector"),
+ 0, # Return 0 if none of the above are valid
+ )
return speed
+ @property
+ def disallowRepeatingAction(self):
+ return self.getModifiedItemAttr("disallowRepeatingAction", 0)
+
+ @property
+ def reactivationDelay(self):
+ return self.getModifiedItemAttr("moduleReactivationDelay", 0)
+
@property
def capUse(self):
capNeed = self.getModifiedItemAttr("capacitorNeed")
diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py
index c6a41c112..bb8631623 100644
--- a/gui/builtinContextMenus/__init__.py
+++ b/gui/builtinContextMenus/__init__.py
@@ -22,4 +22,6 @@ __all__ = [
"metaSwap",
"implantSets",
"fighterAbilities",
+ "cargoAmmo",
+ "droneStack"
]
diff --git a/gui/builtinContextMenus/cargoAmmo.py b/gui/builtinContextMenus/cargoAmmo.py
new file mode 100644
index 000000000..573e4c24f
--- /dev/null
+++ b/gui/builtinContextMenus/cargoAmmo.py
@@ -0,0 +1,35 @@
+from gui.contextMenu import ContextMenu
+import gui.mainFrame
+import service
+import gui.globalEvents as GE
+import wx
+
+
+class CargoAmmo(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
+
+ for selected_item in selection:
+ if selected_item.category.ID in (
+ 8, # Charge
+ ):
+ return True
+
+ def getText(self, itmContext, selection):
+ return "Add {0} to Cargo (x1000)".format(itmContext)
+
+ def activate(self, fullContext, selection, i):
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+
+ typeID = int(selection[0].ID)
+ sFit.addCargo(fitID, typeID, 1000)
+ self.mainFrame.additionsPane.select("Cargo")
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
+
+
+CargoAmmo.register()
diff --git a/gui/builtinContextMenus/droneStack.py b/gui/builtinContextMenus/droneStack.py
new file mode 100644
index 000000000..b7fde1040
--- /dev/null
+++ b/gui/builtinContextMenus/droneStack.py
@@ -0,0 +1,37 @@
+from gui.contextMenu import ContextMenu
+import gui.mainFrame
+import service
+import gui.globalEvents as GE
+import wx
+
+
+class CargoAmmo(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
+
+ for selected_item in selection:
+ if selected_item.category.ID in (
+ 18, # Drones
+ ):
+ return True
+
+ return False
+
+ def getText(self, itmContext, selection):
+ return "Add {0} to Drone Bay (x5)".format(itmContext)
+
+ def activate(self, fullContext, selection, i):
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+
+ typeID = int(selection[0].ID)
+ sFit.addDrone(fitID, typeID, 5)
+ self.mainFrame.additionsPane.select("Drones")
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
+
+
+CargoAmmo.register()
diff --git a/gui/builtinContextMenus/metaSwap.py b/gui/builtinContextMenus/metaSwap.py
index ba3934b3f..f5823b046 100644
--- a/gui/builtinContextMenus/metaSwap.py
+++ b/gui/builtinContextMenus/metaSwap.py
@@ -9,6 +9,11 @@ import gui.mainFrame
import gui.globalEvents as GE
from gui.contextMenu import ContextMenu
from service.settings import ContextMenuSettings
+from eos.saveddata.booster import Booster
+from eos.saveddata.module import Module
+from eos.saveddata.drone import Drone
+from eos.saveddata.fighter import Fighter
+from eos.saveddata.implant import Implant
class MetaSwap(ContextMenu):
@@ -20,7 +25,13 @@ class MetaSwap(ContextMenu):
if not self.settings.get('metaSwap'):
return False
- if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule",):
+ if self.mainFrame.getActiveFit() is None or srcContext not in (
+ "fittingModule",
+ "droneItem",
+ "fighterItem",
+ "boosterItem",
+ "implantItem",
+ ):
return False
# Check if list of variations is same for all of selection
@@ -56,6 +67,17 @@ class MetaSwap(ContextMenu):
def get_metagroup(x):
return x.metaGroup.ID if x.metaGroup is not None else 0
+ def get_boosterrank(x):
+ # If we're returning a lot of items, sort my name
+ if len(self.variations) > 7:
+ return x.name
+ # Sort by booster chance to get some sort of pseudorank.
+ elif 'boosterEffectChance1' in x.attributes:
+ return x.attributes['boosterEffectChance1'].value
+ # the "first" rank (Synth) doesn't have boosterEffectChance1. If we're not pulling back all boosters, return 0 for proper sorting
+ else:
+ return 0
+
m = wx.Menu()
# If on Windows we need to bind out events into the root menu, on other
@@ -67,8 +89,17 @@ class MetaSwap(ContextMenu):
# Sort items by metalevel, and group within that metalevel
items = list(self.variations)
- items.sort(key=get_metalevel)
- items.sort(key=get_metagroup)
+ print context
+ if "implantItem" in context:
+ # sort implants based on name
+ items.sort(key=lambda x: x.name)
+ elif "boosterItem" in context:
+ # boosters don't have meta or anything concrete that we can rank by. Go by chance to inflict side effect
+ items.sort(key=get_boosterrank)
+ else:
+ # sort by group and meta level
+ items.sort(key=get_metalevel)
+ items.sort(key=get_metagroup)
group = None
for item in items:
@@ -78,7 +109,7 @@ class MetaSwap(ContextMenu):
else:
thisgroup = item.metaGroup.name
- if thisgroup != group:
+ if thisgroup != group and context not in ("implantItem", "boosterItem"):
group = thisgroup
id = ContextMenu.nextID()
m.Append(id, u'─ %s ─' % group)
@@ -101,9 +132,56 @@ class MetaSwap(ContextMenu):
fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID)
- for mod in self.selection:
- pos = fit.modules.index(mod)
- sFit.changeModule(fitID, pos, item.ID)
+ for selected_item in self.selection:
+ if isinstance(selected_item, Module):
+ pos = fit.modules.index(selected_item)
+ sFit.changeModule(fitID, pos, item.ID)
+
+ elif isinstance(selected_item, Drone):
+ drone_count = None
+
+ for idx, drone_stack in enumerate(fit.drones):
+ if drone_stack is selected_item:
+ drone_count = drone_stack.amount
+ sFit.removeDrone(fitID, idx, drone_count)
+ break
+
+ if drone_count:
+ sFit.addDrone(fitID, item.ID, drone_count)
+
+ elif isinstance(selected_item, Fighter):
+ fighter_count = None
+
+ for idx, fighter_stack in enumerate(fit.fighters):
+ # Right now fighters always will have max stack size.
+ # Including this for future improvement, so if adjustable
+ # fighter stacks get added we're ready for it.
+ if fighter_stack is selected_item:
+ if fighter_stack.amount > 0:
+ fighter_count = fighter_stack.amount
+ elif fighter_stack.amount == -1:
+ fighter_count = fighter_stack.amountActive
+ else:
+ fighter_count.amount = 0
+
+ sFit.removeFighter(fitID, idx)
+ break
+
+ sFit.addFighter(fitID, item.ID)
+
+ elif isinstance(selected_item, Booster):
+ for idx, booster_stack in enumerate(fit.boosters):
+ if booster_stack is selected_item:
+ sFit.removeBooster(fitID, idx)
+ sFit.addBooster(fitID, item.ID)
+ break
+
+ elif isinstance(selected_item, Implant):
+ for idx, implant_stack in enumerate(fit.implants):
+ if implant_stack is selected_item:
+ sFit.removeImplant(fitID, idx)
+ sFit.addImplant(fitID, item.ID, False)
+ break
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
diff --git a/gui/builtinStatsViews/__init__.py b/gui/builtinStatsViews/__init__.py
index bb116f274..27cc3e1a2 100644
--- a/gui/builtinStatsViews/__init__.py
+++ b/gui/builtinStatsViews/__init__.py
@@ -4,6 +4,7 @@ __all__ = [
"rechargeViewFull",
"firepowerViewFull",
"capacitorViewFull",
+ "outgoingViewFull",
"targetingMiscViewMinimal",
"priceViewFull",
]
diff --git a/gui/builtinStatsViews/outgoingViewFull.py b/gui/builtinStatsViews/outgoingViewFull.py
new file mode 100644
index 000000000..346aacc7a
--- /dev/null
+++ b/gui/builtinStatsViews/outgoingViewFull.py
@@ -0,0 +1,106 @@
+# ===============================================================================
+# Copyright (C) 2014 Alexandros Kosiaris
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# ===============================================================================
+
+# noinspection PyPackageRequirements
+import wx
+from gui.statsView import StatsView
+from gui.bitmapLoader import BitmapLoader
+from gui.utils.numberFormatter import formatAmount
+
+
+class OutgoingViewFull(StatsView):
+ name = "outgoingViewFull"
+
+ def __init__(self, parent):
+ StatsView.__init__(self)
+ self.parent = parent
+ self._cachedValues = []
+
+ def getHeaderText(self, fit):
+ return "Remote Reps"
+
+ def getTextExtentW(self, text):
+ width, height = self.parent.GetTextExtent(text)
+ return width
+
+ def populatePanel(self, contentPanel, headerPanel):
+ contentSizer = contentPanel.GetSizer()
+ parent = self.panel = contentPanel
+ self.headerPanel = headerPanel
+
+ sizerOutgoing = wx.GridSizer(1, 4)
+
+ contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
+
+ counter = 0
+
+ rr_list = [
+ ("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
+ ("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
+ ("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
+ ("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
+ ]
+
+ for outgoingType, label, image, tooltip in rr_list:
+ baseBox = wx.BoxSizer(wx.VERTICAL)
+
+ baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
+
+ if "Capacitor" in outgoingType:
+ lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s")
+ else:
+ lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s")
+
+ lbl.SetToolTip(wx.ToolTip(tooltip))
+
+ setattr(self, "label%s" % outgoingType, lbl)
+
+ baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
+ self._cachedValues.append(0)
+ counter += 1
+
+ sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
+
+ def refreshPanel(self, fit):
+ # If we did anything intresting, we'd update our labels to reflect the new fit's stats here
+
+ stats = [
+ ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None),
+ ]
+
+ counter = 0
+ for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
+ label = getattr(self, labelName)
+ value = value() if fit is not None else 0
+ value = value if value is not None else 0
+ if self._cachedValues[counter] != value:
+ valueStr = formatAmount(value, prec, lowest, highest)
+ label.SetLabel(valueFormat % valueStr)
+ tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
+ label.SetToolTip(wx.ToolTip(tipStr))
+ self._cachedValues[counter] = value
+ counter += 1
+ self.panel.Layout()
+ self.headerPanel.Layout()
+
+
+OutgoingViewFull.register()
diff --git a/gui/builtinStatsViews/outgoingViewMinimal.py b/gui/builtinStatsViews/outgoingViewMinimal.py
new file mode 100644
index 000000000..346aacc7a
--- /dev/null
+++ b/gui/builtinStatsViews/outgoingViewMinimal.py
@@ -0,0 +1,106 @@
+# ===============================================================================
+# Copyright (C) 2014 Alexandros Kosiaris
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# ===============================================================================
+
+# noinspection PyPackageRequirements
+import wx
+from gui.statsView import StatsView
+from gui.bitmapLoader import BitmapLoader
+from gui.utils.numberFormatter import formatAmount
+
+
+class OutgoingViewFull(StatsView):
+ name = "outgoingViewFull"
+
+ def __init__(self, parent):
+ StatsView.__init__(self)
+ self.parent = parent
+ self._cachedValues = []
+
+ def getHeaderText(self, fit):
+ return "Remote Reps"
+
+ def getTextExtentW(self, text):
+ width, height = self.parent.GetTextExtent(text)
+ return width
+
+ def populatePanel(self, contentPanel, headerPanel):
+ contentSizer = contentPanel.GetSizer()
+ parent = self.panel = contentPanel
+ self.headerPanel = headerPanel
+
+ sizerOutgoing = wx.GridSizer(1, 4)
+
+ contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
+
+ counter = 0
+
+ rr_list = [
+ ("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
+ ("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
+ ("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
+ ("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
+ ]
+
+ for outgoingType, label, image, tooltip in rr_list:
+ baseBox = wx.BoxSizer(wx.VERTICAL)
+
+ baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
+
+ if "Capacitor" in outgoingType:
+ lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s")
+ else:
+ lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s")
+
+ lbl.SetToolTip(wx.ToolTip(tooltip))
+
+ setattr(self, "label%s" % outgoingType, lbl)
+
+ baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
+ self._cachedValues.append(0)
+ counter += 1
+
+ sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
+
+ def refreshPanel(self, fit):
+ # If we did anything intresting, we'd update our labels to reflect the new fit's stats here
+
+ stats = [
+ ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None),
+ ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None),
+ ]
+
+ counter = 0
+ for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
+ label = getattr(self, labelName)
+ value = value() if fit is not None else 0
+ value = value if value is not None else 0
+ if self._cachedValues[counter] != value:
+ valueStr = formatAmount(value, prec, lowest, highest)
+ label.SetLabel(valueFormat % valueStr)
+ tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
+ label.SetToolTip(wx.ToolTip(tipStr))
+ self._cachedValues[counter] = value
+ counter += 1
+ self.panel.Layout()
+ self.headerPanel.Layout()
+
+
+OutgoingViewFull.register()
diff --git a/gui/statsPane.py b/gui/statsPane.py
index 59aa0aea0..181d8f9c5 100644
--- a/gui/statsPane.py
+++ b/gui/statsPane.py
@@ -40,6 +40,7 @@ class StatsPane(wx.Panel):
"resistances",
"recharge",
"firepower",
+ "outgoingView",
"capacitor",
"targetingMisc",
"price",
diff --git a/gui/statsView.py b/gui/statsView.py
index 1be2d8b8d..e8b614427 100644
--- a/gui/statsView.py
+++ b/gui/statsView.py
@@ -52,4 +52,5 @@ from gui.builtinStatsViews import ( # noqa: E402, F401
rechargeViewFull,
targetingMiscViewMinimal,
priceViewFull,
+ outgoingViewFull,
)
diff --git a/service/fit.py b/service/fit.py
index 9dd1dff32..9eeb348fe 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -688,7 +688,7 @@ class Fit(object):
self.recalc(fit)
return True
- def addDrone(self, fitID, itemID):
+ def addDrone(self, fitID, itemID, numDronesToAdd=1):
if fitID is None:
return False
@@ -707,7 +707,7 @@ class Fit(object):
fit.drones.append(drone)
else:
return False
- drone.amount += 1
+ drone.amount += numDronesToAdd
eos.db.commit()
self.recalc(fit)
return True
diff --git a/service/market.py b/service/market.py
index 17518363c..e311c94e5 100644
--- a/service/market.py
+++ b/service/market.py
@@ -590,7 +590,40 @@ class Market(object):
parents = set()
# Set-container for variables
variations = set()
+ variations_limiter = set()
for item in items:
+ if item.category.ID == 20: # Implants and Boosters
+ implant_remove_list = set()
+ implant_remove_list.add("Low-Grade ")
+ implant_remove_list.add("Low-grade ")
+ implant_remove_list.add("Mid-Grade ")
+ implant_remove_list.add("Mid-grade ")
+ implant_remove_list.add("High-Grade ")
+ implant_remove_list.add("High-grade ")
+ implant_remove_list.add("Limited ")
+ implant_remove_list.add(" - Advanced")
+ implant_remove_list.add(" - Basic")
+ implant_remove_list.add(" - Elite")
+ implant_remove_list.add(" - Improved")
+ implant_remove_list.add(" - Standard")
+ implant_remove_list.add("Copper ")
+ implant_remove_list.add("Gold ")
+ implant_remove_list.add("Silver ")
+ implant_remove_list.add("Advanced ")
+ implant_remove_list.add("Improved ")
+ implant_remove_list.add("Prototype ")
+ implant_remove_list.add("Standard ")
+ implant_remove_list.add("Strong ")
+ implant_remove_list.add("Synth ")
+
+ for implant_prefix in ("-6", "-7", "-8", "-9", "-10"):
+ for i in range(50):
+ implant_remove_list.add(implant_prefix + str("%02d" % i))
+
+ for text_to_remove in implant_remove_list:
+ if text_to_remove in item.name:
+ variations_limiter.add(item.name.replace(text_to_remove, ""))
+
# Get parent item
if alreadyparent is False:
parent = self.getParentItemByItem(item)
@@ -608,7 +641,16 @@ class Market(object):
variations.update(parents)
# Add all variations of parents to the set
parentids = tuple(item.ID for item in parents)
- variations.update(eos.db.getVariations(parentids))
+ groupids = tuple(item.group.ID for item in parents)
+ variations_list = eos.db.getVariations(parentids, groupids)
+
+ if variations_limiter:
+ for limit in variations_limiter:
+ trimmed_variations_list = [variation_item for variation_item in variations_list if limit in variation_item.name]
+ if trimmed_variations_list:
+ variations_list = trimmed_variations_list
+
+ variations.update(variations_list)
return variations
def getGroupsByCategory(self, cat):
diff --git a/tests/test_modules/eos/test_mathUtils.py b/tests/test_modules/eos/test_mathUtils.py
deleted file mode 100644
index c5cf2b0bb..000000000
--- a/tests/test_modules/eos/test_mathUtils.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from eos.mathUtils import floorFloat
-
-
-def test_floorFloat():
- assert type(floorFloat(1)) is not float
- assert type(floorFloat(1)) is int
- assert type(floorFloat(1.1)) is not float
- assert type(floorFloat(1.1)) is int
- assert floorFloat(1.1) == 1
- assert floorFloat(1.9) == 1
- assert floorFloat(1.5) == 1
- assert floorFloat(-1.5) == -2