Add preliminary (database/gui) fighter support. No effects / calculations.

This commit is contained in:
blitzmann
2016-04-04 22:04:23 -04:00
parent 0769df1e55
commit 3177713447
10 changed files with 598 additions and 5 deletions

View File

@@ -0,0 +1,33 @@
#===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean
from sqlalchemy.orm import mapper
from eos.db import saveddata_meta
from eos.types import Fighter
fighters_table = Table("fighters", saveddata_meta,
Column("groupID", Integer, primary_key=True),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False, index = True),
Column("itemID", Integer, nullable = False),
Column("amount", Integer, nullable = False),
Column("amountActive", Integer, nullable = False),
Column("projected", Boolean, default = False))
mapper(Fighter, fighters_table)

View File

@@ -26,9 +26,10 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
from eos.db import saveddata_meta
from eos.db.saveddata.module import modules_table
from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.fighter import fighters_table
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_table
from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.types import Fit, Module, User, Booster, Drone, Fighter, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.effectHandlerHelpers import *
fits_table = Table("fits", saveddata_meta,
@@ -117,6 +118,12 @@ mapper(Fit, fits_table,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
"_Fit__fighters": relation(
Fighter,
collection_class=HandledDroneCargoList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == False)),
"_Fit__cargo": relation(
Cargo,
collection_class=HandledDroneCargoList,

253
eos/saveddata/fighter.py Normal file
View File

@@ -0,0 +1,253 @@
#===============================================================================
# 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, ChargeAttrShortcut
from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
import eos.db
import logging
logger = logging.getLogger(__name__)
class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
"""Initialize a fighter from the program"""
self.__item = item
print self.__item.category.name
if self.isInvalid:
raise ValueError("Passed item is not a Fighter")
self.itemID = item.ID if item is not None else None
self.amount = 0
self.amountActive = 0
self.projected = False
self.build()
@reconstructor
def init(self):
"""Initialize a fighter from the database and validate"""
self.__item = None
if self.itemID:
self.__item = eos.db.getItem(self.itemID)
if self.__item is None:
logger.error("Item (id: %d) does not exist", self.itemID)
return
if self.isInvalid:
logger.error("Item (id: %d) is not a Fighter", self.itemID)
return
self.build()
def build(self):
""" Build object. Assumes proper and valid item already set """
self.__charge = None
self.__dps = None
self.__volley = None
self.__miningyield = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__chargeModifiedAttributes = ModifiedAttributeDict()
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
if chargeID is not None:
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
self.__chargeModifiedAttributes.overrides = charge.overrides
@property
def itemModifiedAttributes(self):
return self.__itemModifiedAttributes
@property
def chargeModifiedAttributes(self):
return self.__chargeModifiedAttributes
@property
def isInvalid(self):
return self.__item is None or self.__item.category.name != "Fighter"
@property
def item(self):
return self.__item
@property
def charge(self):
return self.__charge
@property
def dealsDamage(self):
for attr in ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage"):
if attr in self.itemModifiedAttributes or attr in self.chargeModifiedAttributes:
return True
@property
def mines(self):
if "miningAmount" in self.itemModifiedAttributes:
return True
@property
def hasAmmo(self):
return self.charge is not None
@property
def dps(self):
return self.damageStats()
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"
getter = self.getModifiedChargeAttr
else:
attr = "speed"
getter = self.getModifiedItemAttr
cycleTime = self.getModifiedItemAttr(attr)
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)
return self.__dps, self.__volley
@property
def miningStats(self):
if self.__miningyield == None:
if self.mines is True and self.amountActive > 0:
attr = "duration"
getter = self.getModifiedItemAttr
cycleTime = self.getModifiedItemAttr(attr)
volley = sum(map(lambda d: getter(d), self.MINING_ATTRIBUTES)) * self.amountActive
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
return self.__miningyield
@property
def maxRange(self):
attrs = ("shieldTransferRange", "powerTransferRange",
"energyDestabilizationRange", "empFieldRange",
"ecmBurstRange", "maxRange")
for attr in attrs:
maxRange = self.getModifiedItemAttr(attr)
if maxRange is not None: return maxRange
if self.charge is not None:
delay = self.getModifiedChargeAttr("explosionDelay")
speed = self.getModifiedChargeAttr("maxVelocity")
if delay is not None and speed is not None:
return delay / 1000.0 * speed
# Had to add this to match the falloff property in modules.py
# Fscking ship scanners. If you find any other falloff attributes,
# Put them in the attrs tuple.
@property
def falloff(self):
attrs = ("falloff", "falloffEffectiveness")
for attr in attrs:
falloff = self.getModifiedItemAttr(attr)
if falloff is not None: return falloff
@validates("ID", "itemID", "chargeID", "amount", "amountActive")
def validator(self, key, val):
map = {"ID": lambda val: isinstance(val, int),
"itemID" : lambda val: isinstance(val, int),
"chargeID" : lambda val: isinstance(val, int),
"amount" : lambda val: isinstance(val, int) and val >= 0,
"amountActive" : lambda val: isinstance(val, int) and val <= self.amount and val >= 0}
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
else: return val
def clear(self):
self.__dps = None
self.__volley = None
self.__miningyield = None
self.itemModifiedAttributes.clear()
self.chargeModifiedAttributes.clear()
def canBeApplied(self, projectedOnto):
"""Check if fighter can engage specific fitting"""
item = self.item
# Do not allow to apply offensive modules on ship with offensive module immunite, with few exceptions
# (all effects which apply instant modification are exception, generally speaking)
if item.offensive and projectedOnto.ship.getModifiedItemAttr("disallowOffensiveModifiers") == 1:
offensiveNonModifiers = set(("energyDestabilizationNew", "leech", "energyNosferatuFalloff", "energyNeutralizerFalloff"))
if not offensiveNonModifiers.intersection(set(item.effects)):
return False
# If assistive modules are not allowed, do not let to apply these altogether
if item.assistive and projectedOnto.ship.getModifiedItemAttr("disallowAssistance") == 1:
return False
else:
return True
def calculateModifiedAttributes(self, fit, runTime, forceProjected = False):
if self.projected or forceProjected:
context = "projected", "fighter"
projected = True
else:
context = ("fighter",)
projected = False
for effect in self.item.effects.itervalues():
if effect.runTime == runTime and \
((projected == True and effect.isType("projected")) or \
projected == False and effect.isType("passive")):
i = 0
while i != self.amountActive:
effect.handler(fit, self, context)
i += 1
if self.charge:
for effect in self.charge.effects.itervalues():
if effect.runTime == runTime:
effect.handler(fit, self, ("fighterCharge",))
def __deepcopy__(self, memo):
copy = Fighter(self.item)
copy.amount = self.amount
copy.amountActive = self.amountActive
return copy
def fits(self, fit):
return True
fitDroneGroupLimits = set()
for i in xrange(1, 3):
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i)
if groneGrp is not None:
fitDroneGroupLimits.add(int(groneGrp))
if len(fitDroneGroupLimits) == 0:
return True
if self.item.groupID in fitDroneGroupLimits:
return True
return False

View File

@@ -53,6 +53,7 @@ class Fit(object):
self.__modules = HandledModuleList()
self.__drones = HandledDroneCargoList()
self.__fighters = HandledDroneCargoList()
self.__cargo = HandledDroneCargoList()
self.__implants = HandledImplantBoosterList()
self.__boosters = HandledImplantBoosterList()
@@ -185,6 +186,10 @@ class Fit(object):
def drones(self):
return self.__drones
@property
def fighters(self):
return self.__fighters
@property
def cargo(self):
return self.__cargo
@@ -365,6 +370,7 @@ class Fit(object):
c = chain(
self.modules,
self.drones,
self.fighters,
self.boosters,
self.implants,
self.projectedDrones,
@@ -482,6 +488,7 @@ class Fit(object):
u = [
(self.character, self.ship),
self.drones,
self.fighters,
self.boosters,
self.appliedImplants,
self.modules
@@ -999,7 +1006,7 @@ class Fit(object):
copy.damagePattern = self.damagePattern
copy.targetResists = self.targetResists
toCopy = ("modules", "drones", "cargo", "implants", "boosters", "projectedModules", "projectedDrones")
toCopy = ("modules", "drones", "fighters", "cargo", "implants", "boosters", "projectedModules", "projectedDrones")
for name in toCopy:
orig = getattr(self, name)
c = getattr(copy, name)

View File

@@ -27,6 +27,7 @@ from eos.saveddata.targetResists import TargetResists
from eos.saveddata.character import Character, Skill
from eos.saveddata.module import Module, State, Slot, Hardpoint, Rack
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.cargo import Cargo
from eos.saveddata.implant import Implant
from eos.saveddata.booster import SideEffect

View File

@@ -21,6 +21,7 @@ import wx
import gui.mainFrame
from gui.boosterView import BoosterView
from gui.droneView import DroneView
from gui.fighterView import FighterView
from gui.cargoView import CargoView
from gui.implantView import ImplantView
from gui.projectedView import ProjectedView
@@ -57,6 +58,7 @@ class AdditionsPane(TogglePanel):
cargoImg = BitmapLoader.getImage("cargo_small", "gui")
self.notebook.AddPage(DroneView(self.notebook), "Drones", tabImage = droneImg, showClose = False)
self.notebook.AddPage(FighterView(self.notebook), "Fighters", tabImage = droneImg, showClose = False)
self.notebook.AddPage(CargoView(self.notebook), "Cargo", tabImage = cargoImg, showClose = False)
self.notebook.AddPage(ImplantView(self.notebook), "Implants", tabImage = implantImg, showClose = False)
self.notebook.AddPage(BoosterView(self.notebook), "Boosters", tabImage = boosterImg, showClose = False)
@@ -68,7 +70,7 @@ class AdditionsPane(TogglePanel):
self.notebook.AddPage(self.gangPage, "Fleet", tabImage = gangImg, showClose = False)
self.notebook.SetSelection(0)
PANES = ["Drones", "Cargo", "Implants", "Boosters", "Projected", "Fleet"]
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Fleet"]
def select(self, name):
self.notebook.SetSelection(self.PANES.index(name))

View File

@@ -16,7 +16,7 @@ class ItemStats(ContextMenu):
"implantItem", "boosterItem",
"skillItem", "projectedModule",
"projectedDrone", "projectedCharge",
"itemStats")
"itemStats", "fighterItem")
def getText(self, itmContext, selection):
return "{0} Stats".format(itmContext if itmContext is not None else "Item")

251
gui/fighterView.py Normal file
View File

@@ -0,0 +1,251 @@
#===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# 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 <http://www.gnu.org/licenses/>.
#===============================================================================
import wx
import service
import gui.globalEvents as GE
import gui.marketBrowser as mb
import gui.display as d
from gui.builtinViewColumns.state import State
from gui.contextMenu import ContextMenu
class FighterViewDrop(wx.PyDropTarget):
def __init__(self, dropFn):
wx.PyDropTarget.__init__(self)
self.dropFn = dropFn
# this is really transferring an EVE itemID
self.dropData = wx.PyTextDataObject()
self.SetDataObject(self.dropData)
def OnData(self, x, y, t):
if self.GetData():
data = self.dropData.GetText().split(':')
self.dropFn(x, y, data)
return t
class FighterView(d.Display):
DEFAULT_COLS = [#"State",
#"Base Icon",
"Base Name",
# "prop:droneDps,droneBandwidth",
#"Max Range",
#"Miscellanea",
#"attr:maxVelocity",
"Price",]
def __init__(self, parent):
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
self.lastFitId = None
self.hoveredRow = None
self.hoveredColumn = None
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
self.mainFrame.Bind(mb.ITEM_SELECTED, self.addItem)
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
self.Bind(wx.EVT_LEFT_DOWN, self.click)
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
if "__WXGTK__" in wx.PlatformInfo:
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
else:
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
self.SetDropTarget(FighterViewDrop(self.handleDragDrop))
def OnLeaveWindow(self, event):
self.SetToolTip(None)
self.hoveredRow = None
self.hoveredColumn = None
event.Skip()
def OnMouseMove(self, event):
row, _, col = self.HitTestSubItem(event.Position)
if row != self.hoveredRow or col != self.hoveredColumn:
if self.ToolTip is not None:
self.SetToolTip(None)
else:
self.hoveredRow = row
self.hoveredColumn = col
if row != -1 and col != -1 and col < len(self.DEFAULT_COLS):
mod = self.fighters[self.GetItemData(row)]
if self.DEFAULT_COLS[col] == "Miscellanea":
tooltip = self.activeColumns[col].getToolTip(mod)
if tooltip is not None:
self.SetToolTipString(tooltip)
else:
self.SetToolTip(None)
else:
self.SetToolTip(None)
else:
self.SetToolTip(None)
event.Skip()
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
row = self.GetFirstSelected()
firstSel = row
if row != -1:
fighter = self.fighters[self.GetItemData(row)]
self.removeFighter(fighter)
event.Skip()
def startDrag(self, event):
row = event.GetIndex()
if row != -1:
data = wx.PyTextDataObject()
data.SetText("fighter:"+str(row))
dropSource = wx.DropSource(self)
dropSource.SetData(data)
res = dropSource.DoDragDrop()
def handleDragDrop(self, x, y, data):
'''
Handles dragging of items from various pyfa displays which support it
data is list with two indices:
data[0] is hard-coded str of originating source
data[1] is typeID or index of data we want to manipulate
'''
if data[0] == "fighter": # we want to merge fighters
srcRow = int(data[1])
dstRow, _ = self.HitTest((x, y))
if srcRow != -1 and dstRow != -1:
self._merge(srcRow, dstRow)
elif data[0] == "market":
wx.PostEvent(self.mainFrame, mb.ItemSelected(itemID=int(data[1])))
def _merge(self, src, dst):
print "merge fighters {} -> {}".format(src, dst)
raise NotImplementedError()
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
if sFit.mergeDrones(fitID, self.drones[src], self.drones[dst]):
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
'''
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
'Heavy Attack Drones', 'Sentry Drones', 'Fighters',
'Fighter Bombers', 'Combat Utility Drones',
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones',
'Light Fighters', 'Heavy Fighters', 'Support Fighters')
def droneKey(self, drone):
sMkt = service.Market.getInstance()
groupName = sMkt.getMarketGroupByItem(drone.item).name
print groupName
return (self.DRONE_ORDER.index(groupName),
drone.item.name)
'''
def fitChanged(self, event):
#Clear list and get out if current fitId is None
if event.fitID is None and self.lastFitId is not None:
self.DeleteAllItems()
self.lastFitId = None
event.Skip()
return
sFit = service.Fit.getInstance()
fit = sFit.getFit(event.fitID)
self.original = fit.fighters if fit is not None else None
self.fighters = stuff = fit.fighters[:] if fit is not None else None
'''
if stuff is not None:
stuff.sort(key=self.droneKey)
'''
if event.fitID != self.lastFitId:
self.lastFitId = event.fitID
item = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_DONTCARE)
if item != -1:
self.EnsureVisible(item)
self.deselectItems()
self.update(stuff)
event.Skip()
def addItem(self, event):
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
trigger = sFit.addFighter(fitID, event.itemID)
if trigger:
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
self.mainFrame.additionsPane.select("Fighters")
event.Skip()
def removeItem(self, event):
row, _ = self.HitTest(event.Position)
if row != -1:
col = self.getColumn(event.Position)
if col != self.getColIndex(State):
fighter = self.fighters[self.GetItemData(row)]
self.removeFighter(fighter)
def removeFighter(self, fighter):
fitID = self.mainFrame.getActiveFit()
sFit = service.Fit.getInstance()
sFit.removeFighter(fitID, self.original.index(fighter))
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
def click(self, event):
event.Skip()
row, _ = self.HitTest(event.Position)
if row != -1:
col = self.getColumn(event.Position)
if col == self.getColIndex(State):
fitID = self.mainFrame.getActiveFit()
sFit = service.Fit.getInstance()
fighter = self.fighters[row]
sFit.toggleFighter(fitID, self.original.index(fighter))
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
def scheduleMenu(self, event):
event.Skip()
if self.getColumn(event.Position) != self.getColIndex(State):
wx.CallAfter(self.spawnMenu)
def spawnMenu(self):
sel = self.GetFirstSelected()
if sel != -1:
fighter = self.fighters[sel]
sMkt = service.Market.getInstance()
sourceContext = "fighterItem"
itemContext = sMkt.getCategoryByItem(fighter.item).name
menu = ContextMenu.getMenu((fighter,), (sourceContext, itemContext))
self.PopupMenu(menu)

View File

@@ -615,6 +615,45 @@ class Fit(object):
self.recalc(fit)
return True
def addFighter(self, fitID, itemID):
if fitID is None:
return False
fit = eos.db.getFit(fitID)
item = eos.db.getItem(itemID, eager=("attributes", "group.category"))
if item.category.name == "Fighter":
fighter = None
'''
for d in fit.fighters.find(item):
if d is not None and d.amountActive == 0 and d.amount < max(5, fit.extraAttributes["maxActiveDrones"]):
drone = d
break
'''
if fighter is None:
fighter = eos.types.Fighter(item)
if fighter.fits(fit) is True:
fit.fighters.append(fighter)
else:
return False
fighter.amount += 1
eos.db.commit()
self.recalc(fit)
return True
else:
return False
def removeFighter(self, fitID, i):
fit = eos.db.getFit(fitID)
f = fit.fighters[i]
f.amount -= 1
if f.amount == 0:
del fit.fighters[i]
eos.db.commit()
self.recalc(fit)
return True
def addDrone(self, fitID, itemID):
if fitID is None:
return False

View File

@@ -318,7 +318,7 @@ class Market():
("faction", frozenset((4, 3))),
("complex", frozenset((6,))),
("officer", frozenset((5,)))])
self.SEARCH_CATEGORIES = ("Drone", "Module", "Subsystem", "Charge", "Implant", "Deployable")
self.SEARCH_CATEGORIES = ("Drone", "Module", "Subsystem", "Charge", "Implant", "Deployable", "Fighter")
self.ROOT_MARKET_GROUPS = (9, # Modules
1111, # Rigs
157, # Drones