Merge branch 'mutaplasmids'

# Conflicts:
#	eve.db
This commit is contained in:
blitzmann
2018-06-21 00:22:57 -04:00
13 changed files with 235 additions and 84 deletions

View File

@@ -391,6 +391,10 @@ def directAttributeRequest(itemIDs, attrIDs):
return result
def getAbyssalTypes():
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
def getRequiredFor(itemID, attrMapping):
Attribute1 = aliased(Attribute)
Attribute2 = aliased(Attribute)

View File

@@ -208,6 +208,8 @@ class Item(EqBase):
MOVE_ATTR_INFO = None
ABYSSAL_TYPES = None
@classmethod
def getMoveAttrInfo(cls):
info = getattr(cls, "MOVE_ATTR_INFO", None)
@@ -463,6 +465,17 @@ class Item(EqBase):
return self.__price
@property
def isAbyssal(self):
if Item.ABYSSAL_TYPES is None:
Item.getAbyssalYypes()
return self.ID in Item.ABYSSAL_TYPES
@classmethod
def getAbyssalYypes(cls):
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
def __repr__(self):
return "Item(ID={}, name={}) at {}".format(
self.ID, self.name, hex(id(self))
@@ -539,7 +552,101 @@ class MetaType(EqBase):
class Unit(EqBase):
pass
def __init__(self):
self.name = None
self.displayName = None
@property
def translations(self):
""" This is a mapping of various tweaks that we have to do between the internal representation of an attribute
value and the display (for example, 'Millisecond' units have the display name of 's', so we have to convert value
from ms to s) """
return {
"Inverse Absolute Percent": (
lambda v: (1 - v) * 100,
lambda d: -1 * (d / 100) + 1,
lambda u: u),
"Inversed Modifier Percent": (
lambda v: (1 - v) * 100,
lambda d: -1 * (d / 100) + 1,
lambda u: u),
"Modifier Percent": (
lambda v: ("%+.2f" if ((v - 1) * 100) % 1 else "%+d") % ((v - 1) * 100),
lambda d: (d / 100) + 1,
lambda u: u),
"Volume": (
lambda v: v,
lambda d: d,
lambda u: ""),
"Sizeclass": (
lambda v: v,
lambda d: d,
lambda u: ""),
"Absolute Percent": (
lambda v: (v * 100),
lambda d: d / 100,
lambda u: u),
"Milliseconds": (
lambda v: v / 1000.0,
lambda d: d * 1000.0,
lambda u: u),
"Boolean": (
lambda v, u: "Yes" if v == 1 else "No",
lambda d: 1.0 if d == "Yes" else 0.0,
lambda u: ""),
"typeID": (
self.itemIDCallback,
None, # we could probably convert these back if we really tried hard enough
lambda u: ""),
"groupID": (
self.groupIDCallback,
None,
lambda u: ""),
"attributeID": (
self.attributeIDCallback,
None,
lambda u: ""),
}
@staticmethod
def itemIDCallback(v):
v = int(v)
item = eos.db.getItem(int(v))
return "%s (%d)" % (item.name, v) if item is not None else str(v)
@staticmethod
def groupIDCallback(v):
v = int(v)
group = eos.db.getGroup(v)
return "%s (%d)" % (group.name, v) if group is not None else str(v)
@staticmethod
def attributeIDCallback(v):
v = int(v)
if not v: # some attributes come through with a value of 0? See #1387
return "%d" % (v)
attribute = eos.db.getAttributeInfo(v, eager=("unit"))
return "%s (%d)" % (attribute.name.capitalize(), v)
def TranslateValue(self, value):
"""Attributes have to be translated certain ways based on their unit (ex: decimals converting to percentages).
This allows us to get an easy representation of how the attribute should be printed """
override = self.translations.get(self.name)
if override is not None:
return override[0](value), override[2](self.displayName)
return value, self.displayName
def ComplicateValue(self, value):
"""Takes the display value and turns it back into the internal representation of it"""
override = self.translations.get(self.name)
if override is not None:
return override[1](value)
return value
class Traits(EqBase):

View File

@@ -18,6 +18,7 @@
# ===============================================================================
from logbook import Logger
from copy import deepcopy
from sqlalchemy.orm import validates, reconstructor
from math import floor
@@ -110,6 +111,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__item = None
self.__baseItem = None
self.__charge = None
self.__mutaplasmid = None
# we need this early if module is invalid and returns early
self.__slot = self.dummySlot
@@ -206,7 +208,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
return False
return self.__item is None or \
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
self.__item.group.name not in self.SYSTEM_GROUPS)
self.__item.group.name not in self.SYSTEM_GROUPS) or \
(self.item.isAbyssal and (not self.baseItemID or not self.mutaplasmidID) )
@property
def isMutated(self):
@@ -352,11 +355,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def baseItem(self):
return self.__baseItem if self.__baseItem != 0 else None # what?
return self.__baseItem
@property
def mutaplasmid(self):
return self.__mutaplasmid if self.__mutaplasmid != 0 else None
return self.__mutaplasmid
@property
def charge(self):
@@ -835,9 +838,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if item is None:
copy = Module.buildEmpty(self.slot)
else:
copy = Module(self.item)
copy = Module(self.item, self.baseItem, self.mutaplasmid)
copy.charge = self.charge
copy.state = self.state
for x in self.mutators.values():
Mutator(copy, x.attribute, x.value)
return copy
def __repr__(self):

View File

@@ -81,12 +81,15 @@ class Mutator(EqBase):
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
mod = val/self.baseValue
if self.minMod < mod < self.maxMod:
if self.minMod <= mod <= self.maxMod:
# sweet, all good
returnVal = val
else:
# need to fudge the numbers a bit. Go with the value closest to base
returnVal = min(self.maxValue, max(self.minValue, val))
if val >= 0:
returnVal = min(self.maxValue, max(self.minValue, val))
else:
returnVal = max(self.maxValue, min(self.minValue, val))
return returnVal
@@ -105,11 +108,11 @@ class Mutator(EqBase):
@property
def minMod(self):
return self.dynamicAttribute.min
return round(self.dynamicAttribute.min, 3)
@property
def maxMod(self):
return self.dynamicAttribute.max
return round(self.dynamicAttribute.max, 3)
@property
def baseValue(self):

BIN
eve.db

Binary file not shown.

View File

@@ -118,13 +118,22 @@ class CargoView(d.Display):
# Gather module information to get position
module = fit.modules[modIdx]
if module.item.isAbyssal:
dlg = wx.MessageDialog(self,
"Moving this Abyssal module to the cargo will convert it to the base module. Do you wish to proceed?",
"Confirm", wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal() == wx.ID_YES
if not result:
return
if dstRow != -1: # we're swapping with cargo
if mstate.cmdDown: # if copying, append to cargo
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID)
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID if not module.item.isAbyssal else module.baseItemID)
else: # else, move / swap
sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, dstRow)
else: # dragging to blank spot, append
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID)
sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID if not module.item.isAbyssal else module.baseItemID)
if not mstate.cmdDown: # if not copying, remove module
sFit.removeModule(self.mainFrame.getActiveFit(), module.position)

View File

@@ -109,7 +109,7 @@ class ProjectedView(d.Display):
dstRow, _ = self.HitTest((x, y))
# Gather module information to get position
module = fit.modules[int(data[1])]
sFit.project(fit.ID, module.item.ID)
sFit.project(fit.ID, module)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
elif data[0] == "market":
sFit = Fit.getInstance()

View File

@@ -63,7 +63,7 @@ class ItemStats(ContextMenu):
size = wx.DefaultSize
pos = wx.DefaultPosition
ItemStatsDialog(stuff, fullContext, pos, size, maximized)
lastWnd.closeEvent(None)
lastWnd.Close()
else:
ItemStatsDialog(stuff, fullContext)

View File

@@ -104,7 +104,7 @@ class AttributeSlider(wx.Panel):
elif mod > 1:
modEnd = self.UserMaxValue
slider_percentage = ((mod-1)/(modEnd-1)) * 100
print(slider_percentage)
# print(slider_percentage)
if self.inverse:
slider_percentage *= -1
self.slider.SetValue(slider_percentage)

View File

@@ -7,8 +7,6 @@ import wx
from .helpers import AutoListCtrl
from gui.bitmap_loader import BitmapLoader
from service.market import Market
from service.attribute import Attribute
from gui.utils.numberFormatter import formatAmount
@@ -86,7 +84,8 @@ class ItemParams(wx.Panel):
def RefreshValues(self, event):
self._fetchValues()
self.UpdateList()
event.Skip()
if event:
event.Skip()
def ToggleViewMode(self, event):
self.toggleView *= -1
@@ -215,14 +214,14 @@ class ItemParams(wx.Panel):
if self.toggleView != 1:
valueUnit = str(value)
elif info and info.unit:
valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name)
valueUnit = self.FormatValue(*info.unit.TranslateValue(value))
else:
valueUnit = formatAmount(value, 3, 0, 0)
if self.toggleView != 1:
valueUnitDefault = str(valueDefault)
elif info and info.unit:
valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name)
valueUnitDefault = self.FormatValue(*info.unit.TranslateValue(valueDefault))
else:
valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
@@ -237,44 +236,11 @@ class ItemParams(wx.Panel):
self.Layout()
@staticmethod
def TranslateValueUnit(value, unitName, unitDisplayName):
def itemIDCallback():
item = Market.getInstance().getItem(value)
return "%s (%d)" % (item.name, value) if item is not None else str(value)
def groupIDCallback():
group = Market.getInstance().getGroup(value)
return "%s (%d)" % (group.name, value) if group is not None else str(value)
def attributeIDCallback():
if not value: # some attributes come through with a value of 0? See #1387
return "%d" % (value)
attribute = Attribute.getInstance().getAttributeInfo(value)
return "%s (%d)" % (attribute.name.capitalize(), value)
trans = {
"Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName),
"Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName),
"Modifier Percent" : (
lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName),
"Volume" : (lambda: value, "m\u00B3"),
"Sizeclass" : (lambda: value, ""),
"Absolute Percent" : (lambda: (value * 100), unitName),
"Milliseconds" : (lambda: value / 1000.0, unitName),
"typeID" : (itemIDCallback, ""),
"groupID" : (groupIDCallback, ""),
"attributeID" : (attributeIDCallback, "")
}
override = trans.get(unitDisplayName)
if override is not None:
v = override[0]()
if isinstance(v, str):
fvalue = v
elif isinstance(v, (int, float)):
fvalue = formatAmount(v, 3, 0, 0)
else:
fvalue = v
return "%s %s" % (fvalue, override[1])
def FormatValue(value, unit):
"""Formats a value / unit combination into a string
@todo: move this to a more central location, since this is also used in the item mutator panel"""
if isinstance(value, (int, float)):
fvalue = formatAmount(value, 3, 0, 0)
else:
return "%s %s" % (formatAmount(value, 3, 0), unitName)
fvalue = value
return "%s %s" % (fvalue, unit)

View File

@@ -6,9 +6,15 @@ from .attributeSlider import AttributeSlider, EVT_VALUE_CHANGED
import gui.mainFrame
from gui.contextMenu import ContextMenu
from .itemAttributes import ItemParams
from gui.bitmap_loader import BitmapLoader
import gui.globalEvents as GE
import gui.mainFrame
import random
from logbook import Logger
pyfalog = Logger(__name__)
class ItemMutator(wx.Panel):
@@ -26,8 +32,10 @@ class ItemMutator(wx.Panel):
self.event_mapping = {}
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
slider = AttributeSlider(self, m.baseValue, m.minMod, m.maxMod, not m.highIsGood)
slider.SetValue(m.value, False)
baseValueFormated = m.attribute.unit.TranslateValue(m.baseValue)[0]
valueFormated = m.attribute.unit.TranslateValue(m.value)[0]
slider = AttributeSlider(self, baseValueFormated, m.minMod, m.maxMod, not m.highIsGood)
slider.SetValue(valueFormated, False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -50,14 +58,14 @@ class ItemMutator(wx.Panel):
worse_range = max_t
else:
worse_range = min_t
print("{}: \nHigh is good: {}".format(m.attribute.displayName, m.attribute.highIsGood))
print("Value {}".format(m.baseValue))
print(min_t)
print(max_t)
print(better_range)
print(worse_range)
#
# print("{}: \nHigh is good: {}".format(m.attribute.displayName, m.attribute.highIsGood))
# print("Value {}".format(m.baseValue))
#
# print(min_t)
# print(max_t)
# print(better_range)
# print(worse_range)
font = parent.GetFont()
font.SetWeight(wx.BOLD)
@@ -69,14 +77,15 @@ class ItemMutator(wx.Panel):
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
range_low = wx.StaticText(self, wx.ID_ANY, "{} {}".format(worse_range[0], m.attribute.unit.displayName))
range_low = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(worse_range[0])))
range_low.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
range_high = wx.StaticText(self, wx.ID_ANY, "{} {}".format(better_range[0], m.attribute.unit.displayName))
range_high = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(better_range[0])))
range_high.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
headingSizer.Add(range_low, 0, wx.ALL | wx.EXPAND, 0)
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, " "), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, ""), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
headingSizer.Add(range_high, 0, wx.RIGHT | wx.EXPAND, 10)
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
@@ -91,8 +100,13 @@ class ItemMutator(wx.Panel):
bSizer = wx.BoxSizer(wx.HORIZONTAL)
self.saveBtn = wx.Button(self, wx.ID_ANY, "Save Attributes", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer.Add(self.saveBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.refreshBtn = wx.Button(self, wx.ID_ANY, "Reset defaults", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.refreshBtn.Bind(wx.EVT_BUTTON, self.resetMutatedValues)
self.randomBtn = wx.Button(self, wx.ID_ANY, "Random stats", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer.Add(self.randomBtn, 0, wx.ALIGN_CENTER_VERTICAL)
self.randomBtn.Bind(wx.EVT_BUTTON, self.randomMutatedValues)
mainSizer.Add(bSizer, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 0)
@@ -102,19 +116,56 @@ class ItemMutator(wx.Panel):
def changeMutatedValue(self, evt):
m = self.event_mapping[evt.Object]
value = evt.Value
value = m.attribute.unit.ComplicateValue(value)
sFit = Fit.getInstance()
sFit.changeMutatedValue(m, value)
if self.timer:
self.timer.Stop()
self.timer = None
for x in self.Parent.Children:
if isinstance(x, ItemParams):
x.RefreshValues(None)
break
self.timer = wx.CallLater(1000, self.callLater)
def resetMutatedValues(self, evt):
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
value = sFit.changeMutatedValue(m, m.baseValue)
value = m.attribute.unit.TranslateValue(value)[0]
slider.SetValue(value)
evt.Skip()
def randomMutatedValues(self, evt):
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
value = random.uniform(m.minValue, m.maxValue)
value = sFit.changeMutatedValue(m, value)
value = m.attribute.unit.TranslateValue(value)[0]
slider.SetValue(value)
evt.Skip()
def callLater(self):
self.timer = None
print("recalc fit")
sFit = Fit.getInstance()
sFit.refreshFit(self.activeFit)
# todo BUG: if fit is not currently active, this causes the changed fit to show...?
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitID=self.activeFit))
# recalc the fit that this module affects. This is not necessarily the currently active fit
sFit.refreshFit(self.activeFit)
mainFrame = gui.mainFrame.MainFrame.getInstance()
activeFit = mainFrame.getActiveFit()
if activeFit != self.activeFit:
# if we're no longer on the fit this module is affecting, simulate a "switch fit" so that the active fit
# can be recalculated (if needed)
sFit.switchFit(activeFit)
# Send signal to GUI to update stats with current active fit
wx.PostEvent(mainFrame, GE.FitChanged(fitID=activeFit))

View File

@@ -147,7 +147,7 @@ class ItemStatsDialog(wx.Dialog):
ItemStatsDialog.counter -= 1
self.parentWnd.UnregisterStatsWindow(self)
self.Destroy()
event.Skip()
class ItemStatsContainer(wx.Panel):
@@ -166,7 +166,7 @@ class ItemStatsContainer(wx.Panel):
if isinstance(stuff, Module) and stuff.isMutated:
self.mutator = ItemMutator(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.mutator, "Multiplasmid")
self.nbContainer.AddPage(self.mutator, "Mutations")
self.desc = ItemDescription(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.desc, "Description")

View File

@@ -379,7 +379,10 @@ class Fit(object):
thing = eos.db.getItem(thing,
eager=("attributes", "group.category"))
if isinstance(thing, FitType):
if isinstance(thing, es_Module):
thing = copy.deepcopy(thing)
fit.projectedModules.append(thing)
elif isinstance(thing, FitType):
if thing in fit.projectedFits:
return
@@ -522,7 +525,7 @@ class Fit(object):
mutator.value = value
eos.db.commit()
#self.recalc(fit)
return mutator.value
def appendModule(self, fitID, itemID):
pyfalog.debug("Appending module for fit ({0}) using item: {1}", fitID, itemID)
@@ -705,11 +708,12 @@ class Fit(object):
cargo.amount -= 1
if not module.isEmpty: # if module is placeholder, we don't want to convert/add it
for x in fit.cargo.find(module.item):
moduleItem = module.item if not module.item.isAbyssal else module.baseItem
for x in fit.cargo.find(moduleItem ):
x.amount += 1
break
else:
moduleP = es_Cargo(module.item)
moduleP = es_Cargo(moduleItem )
moduleP.amount = 1
fit.cargo.insert(cargoIdx, moduleP)