forked from chainsaw_mcginny/RoosterReWrite
131 changed files with 54590 additions and 1 deletions
@ -0,0 +1,727 @@ |
|||
# THIS COG WOULD NOT BE POSSIBLE WITHOUT ME STEALING HALF OF PYFAS CODEBASE |
|||
# SERIOUSLY THEY DID THE HARD WORK I JUST DISCORDED IT HAVE MERCY |
|||
# https://github.com/pyfa-org/Pyfa |
|||
import logging |
|||
import aiohttp |
|||
import re |
|||
from enum import IntEnum |
|||
import eos |
|||
import eos.db |
|||
from eos.gamedata import * |
|||
from eos.db.gamedata.queries import getDynamicItem |
|||
from eos.saveddata.booster import Booster |
|||
from eos.saveddata.implant import Implant |
|||
from eos.saveddata.cargo import Cargo |
|||
from eos.saveddata.fighter import Fighter |
|||
from eos.saveddata.drone import Drone |
|||
from eos.saveddata.ship import Ship |
|||
from eos.saveddata.citadel import Citadel |
|||
from eos.saveddata.module import Module |
|||
from eos.saveddata.damagePattern import DamagePattern |
|||
from eos.saveddata.fit import Fit |
|||
from eos.const import FittingModuleState |
|||
from eos.const import FittingSlot |
|||
import eos.db.gamedata.queries as queries |
|||
from eos.db.gamedata.queries import getAttributeInfo |
|||
import eos.db.saveddata.queries as s_queries |
|||
|
|||
from discord.ext import commands |
|||
from discord.embeds import Embed |
|||
|
|||
log = logging.getLogger(__name__) |
|||
|
|||
|
|||
class PortEftOptions(IntEnum): |
|||
""" |
|||
Contains different options for eft-export |
|||
""" |
|||
|
|||
IMPLANTS = 1 |
|||
MUTATIONS = 2 |
|||
LOADED_CHARGES = 3 |
|||
|
|||
|
|||
EFT_OPTIONS = ( |
|||
( |
|||
PortEftOptions.LOADED_CHARGES, |
|||
"Loaded Charges", |
|||
"Export charges loaded into modules", |
|||
True, |
|||
), |
|||
( |
|||
PortEftOptions.MUTATIONS, |
|||
"Mutated Attributes", |
|||
"Export mutated modules' stats", |
|||
True, |
|||
), |
|||
( |
|||
PortEftOptions.IMPLANTS, |
|||
"Implants && Boosters", |
|||
"Export implants and boosters", |
|||
True, |
|||
), |
|||
) |
|||
|
|||
MODULE_CATS = ("Module", "Subsystem", "Structure Module") |
|||
SLOT_ORDER = ( |
|||
FittingSlot.LOW, |
|||
FittingSlot.MED, |
|||
FittingSlot.HIGH, |
|||
FittingSlot.RIG, |
|||
FittingSlot.SUBSYSTEM, |
|||
FittingSlot.SERVICE, |
|||
) |
|||
OFFLINE_SUFFIX = "/OFFLINE" |
|||
|
|||
|
|||
class EFT(commands.Cog): |
|||
def __init__(self, bot): |
|||
self.bot = bot |
|||
self.eftregex = re.compile("\[.+?, .+]") |
|||
|
|||
@commands.Cog.listener() |
|||
async def on_message(self, message): |
|||
# force fits to be shown in code blocks, its just good form :sun: |
|||
if not message.content.startswith("```"): |
|||
return |
|||
else: |
|||
potentialfit = message.content.strip("```").strip() |
|||
|
|||
fit = getfit(potentialfit) |
|||
|
|||
if not fit: |
|||
print("no fit found") |
|||
return |
|||
await message.channel.trigger_typing() |
|||
# do some calculatin on the ship with an all v toonie |
|||
fit.calculateModifiedAttributes() |
|||
fit.calculateDroneDmgStats() |
|||
fit.calculateWeaponDmgStats(spoolOptions=None) |
|||
|
|||
# omni damage pattern needed for tank stats like res and ehp |
|||
fit.damagePattern = DamagePattern() |
|||
|
|||
# price check |
|||
# im not a big fan of making a new session here, but i dont see an easy way to get ahold of our normal method |
|||
async with aiohttp.ClientSession() as session: |
|||
async with session.post( |
|||
"https://evepraisal.com/appraisal", |
|||
data={"market": "jita", "raw_textarea": potentialfit}, |
|||
) as resp: |
|||
if resp.status != 200: |
|||
price = -1 |
|||
else: |
|||
app_id = resp.headers["X-Appraisal-Id"] |
|||
app_url = f"https://evepraisal.com/a/{app_id}.json" |
|||
async with session.get(app_url) as resp: |
|||
if resp.status != 200: |
|||
price = -1 |
|||
else: |
|||
price = await resp.json() |
|||
price = price["totals"]["sell"] |
|||
|
|||
embed = Embed(title=f"{fit.ship.name} - {fit.name}") |
|||
embed.set_thumbnail( |
|||
url=f"https://image.eveonline.com/Render/{fit.shipID}_128.png" |
|||
) |
|||
embed.add_field(name="Raw HP", value=f"{round(sum(fit.hp.values())):,}") |
|||
embed.add_field(name="EHP (omni)", value=f"{round(sum(fit.ehp.values())):,}") |
|||
embed.add_field( |
|||
name="DPS(estimate)/Volley", |
|||
value=f"{fit.getTotalDps().total:.2f}/{fit.getTotalVolley().total:,.2f}", |
|||
) |
|||
if fit.drones: |
|||
embed.set_footer( |
|||
text=f"*Dps calcs are using {fit.activeDrones} {queries.getItem(fit.drones[-1].item.ID).name} drones" |
|||
) |
|||
if fit.capStable: |
|||
embed.add_field(name="Capacitor", value="Stable!") |
|||
else: |
|||
embed.add_field(name="Cap length", value=f"{fit.capState:.2f} seconds") |
|||
embed.add_field(name="Align", value=f"{fit.alignTime:.2f} Seconds") |
|||
embed.add_field(name="Total Price", value=f"{price:,.2f} isk") |
|||
|
|||
# active reps, also includes normal shield regen, so maybe check for that before using this |
|||
# embed.add_field(name="Active Tank", value=f"{sum(fit.effectiveSustainableTank.values()):.2f} EHP/s") |
|||
|
|||
# i really really want to include the resistance profiles, but its a lot of info and will be spammy |
|||
# for tankType in ("shield", "armor", "hull"): |
|||
# for damageType in ("em", "thermal", "kinetic", "explosive"): |
|||
# if fit is not None: |
|||
# resonanceType = tankType if tankType != "hull" else "" |
|||
# resonance = "%s%sDamageResonance" % (resonanceType, damageType.capitalize()) |
|||
# resonance = resonance[0].lower() + resonance[1:] |
|||
# resonance = (1 - fit.ship.getModifiedItemAttr(resonance)) * 100 |
|||
# else: |
|||
# resonance = 0 |
|||
# embed.add_field(name=tankType, value=f"{damageType.capitalize()} {resonance.__round__(2)}") |
|||
return await message.channel.send(embed=embed) |
|||
|
|||
|
|||
### |
|||
# most everything past this point has been ripped straight from pyfa/eos with minor modification |
|||
# i am but a script kiddie and not very good at this |
|||
# https://github.com/pyfa-org/Pyfa |
|||
def getfit(potentialfit): |
|||
lines = _importPrepare(potentialfit) |
|||
try: |
|||
fit = _importCreateFit(lines) |
|||
except EftImportError as e: |
|||
return |
|||
fit.character = s_queries.Character.getAll5() |
|||
aFit = AbstractFit() |
|||
aFit.mutations = _importGetMutationData(lines) |
|||
|
|||
nameChars = "[^,/\[\]]" # Characters which are allowed to be used in name |
|||
stubPattern = "^\[.+?\]$" |
|||
modulePattern = "^(?P<typeName>{0}+?)(,\s*(?P<chargeName>{0}+?))?(?P<offline>\s*{1})?(\s*\[(?P<mutation>\d+?)\])?$".format( |
|||
nameChars, OFFLINE_SUFFIX |
|||
) |
|||
droneCargoPattern = "^(?P<typeName>{}+?) x(?P<amount>\d+?)$".format(nameChars) |
|||
|
|||
sections = [] |
|||
for section in _importSectionIter(lines): |
|||
for line in section.lines: |
|||
# Stub line |
|||
if re.match(stubPattern, line): |
|||
section.itemSpecs.append(None) |
|||
continue |
|||
# Items with quantity specifier |
|||
m = re.match(droneCargoPattern, line) |
|||
if m: |
|||
try: |
|||
itemSpec = MultiItemSpec(m.group("typeName")) |
|||
# Items which cannot be fetched are considered as stubs |
|||
except EftImportError: |
|||
section.itemSpecs.append(None) |
|||
else: |
|||
itemSpec.amount = int(m.group("amount")) |
|||
section.itemSpecs.append(itemSpec) |
|||
continue |
|||
# All other items |
|||
m = re.match(modulePattern, line) |
|||
if m: |
|||
try: |
|||
itemSpec = RegularItemSpec( |
|||
m.group("typeName"), chargeName=m.group("chargeName") |
|||
) |
|||
# Items which cannot be fetched are considered as stubs |
|||
except EftImportError: |
|||
section.itemSpecs.append(None) |
|||
else: |
|||
if m.group("offline"): |
|||
itemSpec.offline = True |
|||
if m.group("mutation"): |
|||
itemSpec.mutationIdx = int(m.group("mutation")) |
|||
section.itemSpecs.append(itemSpec) |
|||
continue |
|||
_clearTail(section.itemSpecs) |
|||
sections.append(section) |
|||
|
|||
hasDroneBay = any(s.isDroneBay for s in sections) |
|||
hasFighterBay = any(s.isFighterBay for s in sections) |
|||
for section in sections: |
|||
if section.isModuleRack: |
|||
aFit.addModules(section.itemSpecs) |
|||
elif section.isImplantRack: |
|||
for itemSpec in section.itemSpecs: |
|||
aFit.addImplant(itemSpec) |
|||
elif section.isDroneBay: |
|||
for itemSpec in section.itemSpecs: |
|||
aFit.addDrone(itemSpec) |
|||
elif section.isFighterBay: |
|||
for itemSpec in section.itemSpecs: |
|||
aFit.addFighter(itemSpec) |
|||
elif section.isCargoHold: |
|||
for itemSpec in section.itemSpecs: |
|||
aFit.addCargo(itemSpec) |
|||
# Mix between different kinds of item specs (can happen when some |
|||
# blank lines are removed) |
|||
else: |
|||
for itemSpec in section.itemSpecs: |
|||
if itemSpec is None: |
|||
continue |
|||
if itemSpec.isModule: |
|||
aFit.addModule(itemSpec) |
|||
elif itemSpec.isImplant: |
|||
aFit.addImplant(itemSpec) |
|||
elif itemSpec.isDrone and not hasDroneBay: |
|||
aFit.addDrone(itemSpec) |
|||
elif itemSpec.isFighter and not hasFighterBay: |
|||
aFit.addFighter(itemSpec) |
|||
elif itemSpec.isCargo: |
|||
aFit.addCargo(itemSpec) |
|||
|
|||
# Subsystems first because they modify slot amount |
|||
for i, m in enumerate(aFit.subsystems): |
|||
if m is None: |
|||
dummy = Module.buildEmpty(aFit.getSlotByContainer(aFit.subsystems)) |
|||
dummy.owner = fit |
|||
fit.modules.replaceRackPosition(i, dummy) |
|||
elif m.fits(fit): |
|||
m.owner = fit |
|||
fit.modules.replaceRackPosition(i, m) |
|||
# Other stuff |
|||
for modRack in ( |
|||
aFit.rigs, |
|||
aFit.services, |
|||
aFit.modulesHigh, |
|||
aFit.modulesMed, |
|||
aFit.modulesLow, |
|||
): |
|||
for i, m in enumerate(modRack): |
|||
if m is None: |
|||
dummy = Module.buildEmpty(aFit.getSlotByContainer(modRack)) |
|||
dummy.owner = fit |
|||
fit.modules.replaceRackPosition(i, dummy) |
|||
elif m.fits(fit): |
|||
m.owner = fit |
|||
if not m.isValidState(m.state): |
|||
logging.warning( |
|||
"service.port.eft.importEft: module {} cannot have state {}", |
|||
m, |
|||
m.state, |
|||
) |
|||
fit.modules.replaceRackPosition(i, m) |
|||
for implant in aFit.implants: |
|||
fit.implants.append(implant) |
|||
for booster in aFit.boosters: |
|||
fit.boosters.append(booster) |
|||
for drone in aFit.drones.values(): |
|||
fit.drones.append(drone) |
|||
for fighter in aFit.fighters: |
|||
fit.fighters.append(fighter) |
|||
for cargo in aFit.cargo.values(): |
|||
fit.cargo.append(cargo) |
|||
|
|||
# ugly ugly ugly hack to try to activate the last block of drones we have in the drone bay |
|||
# probably can be improved a lot by filtering out ewar and searching for highest deeps |
|||
try: |
|||
print(drone.item.getShortName()) |
|||
has = drone.amount |
|||
canuse = fit.getReleaseLimitForDrone(drone.item) |
|||
if has < canuse: |
|||
drone.amountActive = has |
|||
else: |
|||
drone.amountActive = canuse |
|||
except UnboundLocalError: |
|||
pass |
|||
|
|||
return fit |
|||
|
|||
|
|||
class EftImportError(Exception): |
|||
"""Exception class emitted and consumed by EFT importer internally.""" |
|||
|
|||
... |
|||
|
|||
|
|||
def _importPrepare(lines): |
|||
lines = lines.splitlines() |
|||
for i in range(len(lines)): |
|||
lines[i] = lines[i].strip() |
|||
while lines and not lines[0]: |
|||
del lines[0] |
|||
while lines and not lines[-1]: |
|||
del lines[-1] |
|||
return lines |
|||
|
|||
|
|||
def _importSectionIter(lines): |
|||
section = Section() |
|||
for line in lines: |
|||
if not line: |
|||
if section.lines: |
|||
yield section |
|||
section = Section() |
|||
else: |
|||
section.lines.append(line) |
|||
if section.lines: |
|||
yield section |
|||
|
|||
|
|||
def _clearTail(lst): |
|||
while lst and lst[-1] is None: |
|||
del lst[-1] |
|||
|
|||
|
|||
def _importCreateFit(lines): |
|||
"""Create fit and set top-level entity (ship or citadel).""" |
|||
fit = Fit() |
|||
header = lines.pop(0) |
|||
m = re.match("\[(?P<shipType>[\w\s]+),\s*(?P<fitName>.+)\]", header) |
|||
if not m: |
|||
logging.warning("service.port.eft.importEft: corrupted fit header") |
|||
raise EftImportError |
|||
shipType = m.group("shipType").strip() |
|||
fitName = m.group("fitName").strip() |
|||
try: |
|||
ship = queries.getItem(shipType) |
|||
try: |
|||
fit.ship = Ship(ship) |
|||
except ValueError: |
|||
fit.ship = Citadel(ship) |
|||
fit.name = fitName |
|||
except: |
|||
logging.warning( |
|||
"service.port.eft.importEft: exception caught when parsing header" |
|||
) |
|||
raise EftImportError |
|||
return fit |
|||
|
|||
|
|||
def activeStateLimit(itemIdentity): |
|||
item = eos.db.getItem(itemIdentity.ID) |
|||
if { |
|||
"moduleBonusAssaultDamageControl", |
|||
"moduleBonusIndustrialInvulnerability", |
|||
"microJumpDrive", |
|||
"microJumpPortalDrive", |
|||
}.intersection(item.effects): |
|||
return FittingModuleState.ONLINE |
|||
return FittingModuleState.ACTIVE |
|||
|
|||
|
|||
def parseMutant(lines): |
|||
# Fetch base item type |
|||
try: |
|||
baseItemName = lines[0] |
|||
except IndexError: |
|||
return None |
|||
baseItem = queries.getItem(baseItemName.strip()) |
|||
if baseItem is None: |
|||
return None, None, {} |
|||
# Fetch mutaplasmid item type and actual item |
|||
try: |
|||
mutaplasmidName = lines[1] |
|||
except IndexError: |
|||
return baseItem, None, {} |
|||
mutaplasmidItem = queries.getItem(mutaplasmidName.strip()) |
|||
if mutaplasmidItem is None: |
|||
return baseItem, None, {} |
|||
mutaplasmidItem = getDynamicItem(mutaplasmidItem.ID) |
|||
# Process mutated attribute values |
|||
try: |
|||
mutationsLine = lines[2] |
|||
except IndexError: |
|||
return baseItem, mutaplasmidItem, {} |
|||
mutations = {} |
|||
pairs = [p.strip() for p in mutationsLine.split(",")] |
|||
for pair in pairs: |
|||
try: |
|||
attrName, value = pair.split(" ") |
|||
except ValueError: |
|||
continue |
|||
try: |
|||
value = float(value) |
|||
except (ValueError, TypeError): |
|||
continue |
|||
attrInfo = getAttributeInfo(attrName.strip()) |
|||
if attrInfo is None: |
|||
continue |
|||
mutations[attrInfo.ID] = value |
|||
return baseItem, mutaplasmidItem, mutations |
|||
|
|||
|
|||
mutantHeaderPattern = re.compile("^\[(?P<ref>\d+)\](?P<tail>.*)") |
|||
|
|||
|
|||
def _importGetMutationData(lines): |
|||
data = {} |
|||
# Format: {ref: [lines]} |
|||
mutaLinesMap = {} |
|||
currentMutaRef = None |
|||
currentMutaLines = [] |
|||
consumedIndices = set() |
|||
|
|||
def completeMutaLines(): |
|||
if currentMutaRef is not None and currentMutaLines: |
|||
mutaLinesMap[currentMutaRef] = currentMutaLines |
|||
|
|||
for i, line in enumerate(lines): |
|||
m = mutantHeaderPattern.match(line) |
|||
# Start and reset at header line |
|||
if m: |
|||
completeMutaLines() |
|||
currentMutaRef = int(m.group("ref")) |
|||
currentMutaLines = [] |
|||
currentMutaLines.append(m.group("tail")) |
|||
consumedIndices.add(i) |
|||
# Reset at blank line |
|||
elif not line: |
|||
completeMutaLines() |
|||
currentMutaRef = None |
|||
currentMutaLines = [] |
|||
elif currentMutaRef is not None: |
|||
currentMutaLines.append(line) |
|||
consumedIndices.add(i) |
|||
else: |
|||
completeMutaLines() |
|||
# Clear mutant info from source |
|||
for i in sorted(consumedIndices, reverse=True): |
|||
del lines[i] |
|||
# Run parsing |
|||
data = {} |
|||
for ref, mutaLines in mutaLinesMap.items(): |
|||
_, mutaType, mutaAttrs = parseMutant(mutaLines) |
|||
data[ref] = (mutaType, mutaAttrs) |
|||
return data |
|||
|
|||
|
|||
class AbstractFit: |
|||
def __init__(self): |
|||
# Modules |
|||
self.modulesHigh = [] |
|||
self.modulesMed = [] |
|||
self.modulesLow = [] |
|||
self.rigs = [] |
|||
self.subsystems = [] |
|||
self.services = [] |
|||
# Non-modules |
|||
self.implants = [] |
|||
self.boosters = [] |
|||
self.drones = {} # Format: {item: Drone} |
|||
self.fighters = [] |
|||
self.cargo = {} # Format: {item: Cargo} |
|||
# Other stuff |
|||
self.mutations = ( |
|||
{} |
|||
) # Format: {reference: (mutaplamid item, {attr ID: attr value})} |
|||
|
|||
@property |
|||
def __slotContainerMap(self): |
|||
return { |
|||
FittingSlot.HIGH: self.modulesHigh, |
|||
FittingSlot.MED: self.modulesMed, |
|||
FittingSlot.LOW: self.modulesLow, |
|||
FittingSlot.RIG: self.rigs, |
|||
FittingSlot.SUBSYSTEM: self.subsystems, |
|||
FittingSlot.SERVICE: self.services, |
|||
} |
|||
|
|||
def getContainerBySlot(self, slotType): |
|||
return self.__slotContainerMap.get(slotType) |
|||
|
|||
def getSlotByContainer(self, container): |
|||
slotType = None |
|||
for k, v in self.__slotContainerMap.items(): |
|||
if v is container: |
|||
slotType = k |
|||
break |
|||
return slotType |
|||
|
|||
def addModules(self, itemSpecs): |
|||
modules = [] |
|||
slotTypes = set() |
|||
for itemSpec in itemSpecs: |
|||
if itemSpec is None: |
|||
modules.append(None) |
|||
continue |
|||
m = self.__makeModule(itemSpec) |
|||
if m is None: |
|||
modules.append(None) |
|||
continue |
|||
modules.append(m) |
|||
slotTypes.add(m.slot) |
|||
_clearTail(modules) |
|||
# If all the modules have same slot type, put them to appropriate |
|||
# container with stubs |
|||
if len(slotTypes) == 1: |
|||
slotType = tuple(slotTypes)[0] |
|||
self.getContainerBySlot(slotType).extend(modules) |
|||
# Otherwise, put just modules |
|||
else: |
|||
for m in modules: |
|||
if m is None: |
|||
continue |
|||
self.getContainerBySlot(m.slot).append(m) |
|||
|
|||
def addModule(self, itemSpec): |
|||
if itemSpec is None: |
|||
return |
|||
m = self.__makeModule(itemSpec) |
|||
if m is not None: |
|||
self.getContainerBySlot(m.slot).append(m) |
|||
|
|||
def __makeModule(self, itemSpec): |
|||
# Mutate item if needed |
|||
m = None |
|||
if itemSpec.mutationIdx in self.mutations: |
|||
mutaItem, mutaAttrs = self.mutations[itemSpec.mutationIdx] |
|||
mutaplasmid = getDynamicItem(mutaItem.ID) |
|||
if mutaplasmid: |
|||
try: |
|||
m = Module(mutaplasmid.resultingItem, itemSpec.item, mutaplasmid) |
|||
except ValueError: |
|||
pass |
|||
else: |
|||
for attrID, mutator in m.mutators.items(): |
|||
if attrID in mutaAttrs: |
|||
mutator.value = mutaAttrs[attrID] |
|||
# If we still don't have item (item is not mutated or we |
|||
# failed to construct mutated item), try to make regular item |
|||
if m is None: |
|||
try: |
|||
m = Module(itemSpec.item) |
|||
except ValueError: |
|||
return None |
|||
|
|||
if itemSpec.charge is not None and m.isValidCharge(itemSpec.charge): |
|||
m.charge = itemSpec.charge |
|||
if itemSpec.offline and m.isValidState(FittingModuleState.OFFLINE): |
|||
m.state = FittingModuleState.OFFLINE |
|||
elif m.isValidState(FittingModuleState.ACTIVE): |
|||
m.state = activeStateLimit(m.item) |
|||
return m |
|||
|
|||
def addImplant(self, itemSpec): |
|||
if itemSpec is None: |
|||
return |
|||
if "implantness" in itemSpec.item.attributes: |
|||
self.implants.append(Implant(itemSpec.item)) |
|||
elif "boosterness" in itemSpec.item.attributes: |
|||
self.boosters.append(Booster(itemSpec.item)) |
|||
else: |
|||
pyfalog.error("Failed to import implant: {}", itemSpec.typeName) |
|||
|
|||
def addDrone(self, itemSpec): |
|||
if itemSpec is None: |
|||
return |
|||
if itemSpec.item not in self.drones: |
|||
self.drones[itemSpec.item] = Drone(itemSpec.item) |
|||
self.drones[itemSpec.item].amount += itemSpec.amount |
|||
|
|||
def addFighter(self, itemSpec): |
|||
if itemSpec is None: |
|||
return |
|||
fighter = Fighter(itemSpec.item) |
|||
fighter.amount = itemSpec.amount |
|||
self.fighters.append(fighter) |
|||
|
|||
def addCargo(self, itemSpec): |
|||
if itemSpec is None: |
|||
return |
|||
if itemSpec.item not in self.cargo: |
|||
self.cargo[itemSpec.item] = Cargo(itemSpec.item) |
|||
self.cargo[itemSpec.item].amount += itemSpec.amount |
|||
|
|||
|
|||
class Section: |
|||
def __init__(self): |
|||
self.lines = [] |
|||
self.itemSpecs = [] |
|||
self.__itemDataCats = None |
|||
|
|||
@property |
|||
def itemDataCats(self): |
|||
if self.__itemDataCats is None: |
|||
cats = set() |
|||
for itemSpec in self.itemSpecs: |
|||
if itemSpec is None: |
|||
continue |
|||
cats.add(itemSpec.item.category.name) |
|||
self.__itemDataCats = tuple(sorted(cats)) |
|||
return self.__itemDataCats |
|||
|
|||
@property |
|||
def isModuleRack(self): |
|||
return all(i is None or i.isModule for i in self.itemSpecs) |
|||
|
|||
@property |
|||
def isImplantRack(self): |
|||
return all(i is not None and i.isImplant for i in self.itemSpecs) |
|||
|
|||
@property |
|||
def isDroneBay(self): |
|||
return all(i is not None and i.isDrone for i in self.itemSpecs) |
|||
|
|||
@property |
|||
def isFighterBay(self): |
|||
return all(i is not None and i.isFighter for i in self.itemSpecs) |
|||
|
|||
@property |
|||
def isCargoHold(self): |
|||
return ( |
|||
all(i is not None and i.isCargo for i in self.itemSpecs) |
|||
and not self.isDroneBay |
|||
and not self.isFighterBay |
|||
) |
|||
|
|||
|
|||
class BaseItemSpec: |
|||
def __init__(self, typeName): |
|||
item = queries.getItem(typeName) |
|||
if item is None: |
|||
raise EftImportError |
|||
self.typeName = typeName |
|||
self.item = item |
|||
|
|||
@property |
|||
def isModule(self): |
|||
return False |
|||
|
|||
@property |
|||
def isImplant(self): |
|||
return False |
|||
|
|||
@property |
|||
def isDrone(self): |
|||
return False |
|||
|
|||
@property |
|||
def isFighter(self): |
|||
return False |
|||
|
|||
@property |
|||
def isCargo(self): |
|||
return False |
|||
|
|||
|
|||
class RegularItemSpec(BaseItemSpec): |
|||
def __init__(self, typeName, chargeName=None): |
|||
super().__init__(typeName) |
|||
self.charge = self.__fetchCharge(chargeName) |
|||
self.offline = False |
|||
self.mutationIdx = None |
|||
|
|||
def __fetchCharge(self, chargeName): |
|||
if chargeName: |
|||
charge = queries.getItem(chargeName) |
|||
if not charge or charge.category.name != "Charge": |
|||
charge = None |
|||
else: |
|||
charge = None |
|||
return charge |
|||
|
|||
@property |
|||
def isModule(self): |
|||
return self.item.category.name in MODULE_CATS |
|||
|
|||
@property |
|||
def isImplant(self): |
|||
return self.item.category.name == "Implant" and ( |
|||
"implantness" in self.item.attributes |
|||
or "boosterness" in self.item.attributes |
|||
) |
|||
|
|||
|
|||
class MultiItemSpec(BaseItemSpec): |
|||
def __init__(self, typeName): |
|||
super().__init__(typeName) |
|||
self.amount = 0 |
|||
|
|||
@property |
|||
def isDrone(self): |
|||
return self.item.category.name == "Drone" |
|||
|
|||
@property |
|||
def isFighter(self): |
|||
return self.item.category.name == "Fighter" |
|||
|
|||
@property |
|||
def isCargo(self): |
|||
return True |
|||
|
|||
|
|||
def setup(bot): |
|||
bot.add_cog(EFT(bot)) |
@ -0,0 +1,2 @@ |
|||
version = "0.2.3" |
|||
tag = "git" |
@ -0,0 +1,210 @@ |
|||
import heapq |
|||
import time |
|||
from math import sqrt, exp |
|||
from functools import reduce |
|||
|
|||
DAY = 24 * 60 * 60 * 1000 |
|||
|
|||
|
|||
def lcm(a, b): |
|||
n = a * b |
|||
while b: |
|||
a, b = b, a % b |
|||
return n / a |
|||
|
|||
|
|||
class CapSimulator(object): |
|||
"""Entity's EVE Capacitor Simulator""" |
|||
|
|||
def __init__(self): |
|||
# simulator defaults (change in instance, not here) |
|||
|
|||
self.capacitorCapacity = 100 |
|||
self.capacitorRecharge = 1000 |
|||
|
|||
# max simulated time. |
|||
self.t_max = DAY |
|||
|
|||
# take reloads into account? |
|||
self.reload = False |
|||
|
|||
# stagger activations of identical modules? |
|||
self.stagger = False |
|||
|
|||
# scale activation duration and capNeed to values that ease the |
|||
# calculation at the cost of accuracy? |
|||
self.scale = False |
|||
|
|||
# millisecond resolutions for scaling |
|||
self.scale_resolutions = (100, 50, 25, 10) |
|||
|
|||
# relevant decimal digits of capacitor for LCM period optimization |
|||
self.stability_precision = 1 |
|||
|
|||
def scale_activation(self, duration, capNeed): |
|||
for res in self.scale_resolutions: |
|||
mod = duration % res |
|||
if mod: |
|||
if mod > res / 2.0: |
|||
mod = res - mod |
|||
else: |
|||
mod = -mod |
|||
|
|||
if abs(mod) <= duration / 100.0: |
|||
# only adjust if the adjustment is less than 1% |
|||
duration += mod |
|||
capNeed += float(mod) / duration * capNeed |
|||
break |
|||
|
|||
return duration, capNeed |
|||
|
|||
def init(self, modules): |
|||
"""prepare modules. a list of (duration, capNeed, clipSize, disableStagger) tuples is |
|||
expected, with clipSize 0 if the module has infinite ammo. |
|||
""" |
|||
self.modules = modules |
|||
|
|||
def reset(self): |
|||
"""Reset the simulator state""" |
|||
self.state = [] |
|||
mods = {} |
|||
period = 1 |
|||
disable_period = False |
|||
|
|||
# Loop over modules, clearing clipSize if applicable, and group modules based on attributes |
|||
for (duration, capNeed, clipSize, disableStagger, reloadTime) in self.modules: |
|||
if self.scale: |
|||
duration, capNeed = self.scale_activation(duration, capNeed) |
|||
|
|||
# set clipSize to infinite if reloads are disabled unless it's |
|||
# a cap booster module. |
|||
if not self.reload and capNeed > 0: |
|||
clipSize = 0 |
|||
reloadTime = 0 |
|||
|
|||
# Group modules based on their properties |
|||
if (duration, capNeed, clipSize, disableStagger, reloadTime) in mods: |
|||
mods[(duration, capNeed, clipSize, disableStagger, reloadTime)] += 1 |
|||
else: |
|||
mods[(duration, capNeed, clipSize, disableStagger, reloadTime)] = 1 |
|||
|
|||
# Loop over grouped modules, configure staggering and push to the simulation state |
|||
for (duration, capNeed, clipSize, disableStagger, reloadTime), amount in mods.items(): |
|||
if self.stagger and not disableStagger: |
|||
if clipSize == 0: |
|||
duration = int(duration / amount) |
|||
else: |
|||
stagger_amount = (duration * clipSize + reloadTime) / (amount * clipSize) |
|||
for i in range(1, amount): |
|||
heapq.heappush(self.state, |
|||
[i * stagger_amount, duration, |
|||
capNeed, 0, clipSize, reloadTime]) |
|||
else: |
|||
capNeed *= amount |
|||
|
|||
period = lcm(period, duration) |
|||
|
|||
# period optimization doesn't work when reloads are active. |
|||
if clipSize: |
|||
disable_period = True |
|||
|
|||
heapq.heappush(self.state, [0, duration, capNeed, 0, clipSize, reloadTime]) |
|||
|
|||
if disable_period: |
|||
self.period = self.t_max |
|||
else: |
|||
self.period = period |
|||
|
|||
def run(self): |
|||
"""Run the simulation""" |
|||
|
|||
start = time.time() |
|||
|
|||
self.reset() |
|||
|
|||
push = heapq.heappush |
|||
pop = heapq.heappop |
|||
|
|||
state = self.state |
|||
stability_precision = self.stability_precision |
|||
period = self.period |
|||
|
|||
iterations = 0 |
|||
|
|||
capCapacity = self.capacitorCapacity |
|||
tau = self.capacitorRecharge / 5.0 |
|||
|
|||
cap_wrap = capCapacity # cap value at last period |
|||
cap_lowest = capCapacity # lowest cap value encountered |
|||
cap_lowest_pre = capCapacity # lowest cap value before activations |
|||
cap = capCapacity # current cap value |
|||
t_wrap = self.period # point in time of next period |
|||
|
|||
t_last = 0 |
|||
t_max = self.t_max |
|||
|
|||
while 1: |
|||
activation = pop(state) |
|||
t_now, duration, capNeed, shot, clipSize, reloadTime = activation |
|||
if t_now >= t_max: |
|||
break |
|||
|
|||
cap = ((1.0 + (sqrt(cap / capCapacity) - 1.0) * exp((t_last - t_now) / tau)) ** 2) * capCapacity |
|||
|
|||
if t_now != t_last: |
|||
if cap < cap_lowest_pre: |
|||
cap_lowest_pre = cap |
|||
if t_now == t_wrap: |
|||
# history is repeating itself, so if we have more cap now than last |
|||
# time this happened, it is a stable setup. |
|||
if cap >= cap_wrap: |
|||
break |
|||
cap_wrap = round(cap, stability_precision) |
|||
t_wrap += period |
|||
|
|||
cap -= capNeed |
|||
if cap > capCapacity: |
|||
cap = capCapacity |
|||
|
|||
iterations += 1 |
|||
|
|||
t_last = t_now |
|||
|
|||
if cap < cap_lowest: |
|||
if cap < 0.0: |
|||
break |
|||
cap_lowest = cap |
|||
|
|||
# queue the next activation of this module |
|||
t_now += duration |
|||
shot += 1 |
|||
if clipSize: |
|||
if shot % clipSize == 0: |
|||
shot = 0 |
|||
t_now += reloadTime # include reload time |
|||
activation[0] = t_now |
|||
activation[3] = shot |
|||
|
|||
push(state, activation) |
|||
push(state, activation) |
|||
|
|||
# update instance with relevant results. |
|||
self.t = t_last |
|||
self.iterations = iterations |
|||
|
|||
# calculate EVE's stability value |
|||
try: |
|||
avgDrain = reduce(float.__add__, [x[2] / x[1] for x in self.state], 0.0) |
|||
self.cap_stable_eve = 0.25 * (1.0 + sqrt(-(2.0 * avgDrain * tau - capCapacity) / capCapacity)) ** 2 |
|||
except ValueError: |
|||
self.cap_stable_eve = 0.0 |
|||
|
|||
if cap > 0.0: |
|||
# capacitor low/high water marks |
|||
self.cap_stable_low = cap_lowest |
|||
self.cap_stable_high = cap_lowest_pre |
|||
else: |
|||
self.cap_stable_low = \ |
|||
self.cap_stable_high = 0.0 |
|||
|
|||
self.runtime = time.time() - start |
@ -0,0 +1,32 @@ |
|||
import sys |
|||
from os.path import realpath, join, dirname, abspath |
|||
|
|||
from logbook import Logger |
|||
import os |
|||
|
|||
istravis = os.environ.get('TRAVIS') == 'true' |
|||
pyfalog = Logger(__name__) |
|||
|
|||
debug = False |
|||
gamedataCache = True |
|||
saveddataCache = True |
|||
gamedata_version = "" |
|||
gamedata_date = "" |
|||
gamedata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), ".", "eve.db")) |
|||
pyfalog.debug("Gamedata connection string: {0}", gamedata_connectionstring) |
|||
|
|||
if istravis is True or hasattr(sys, '_called_from_test'): |
|||
# Running in Travis. Run saveddata database in memory. |
|||
saveddata_connectionstring = 'sqlite:///:memory:' |
|||
else: |
|||
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), ".", "saveddata.db")) |
|||
|
|||
pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring) |
|||
settings = { |
|||
"useStaticAdaptiveArmorHardener": False, |
|||
"strictSkillLevels": True, |
|||
"globalDefaultSpoolupPercentage": 1.0 |
|||
} |
|||
|
|||
# Autodetect path, only change if the autodetection bugs out. |
|||
path = dirname(__file__) |
@ -0,0 +1,103 @@ |
|||
# ============================================================================= |
|||
# Copyright (C) 2019 Ryan Holmes |
|||
# |
|||
# 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/>. |
|||
# ============================================================================= |
|||
|
|||
from enum import IntEnum,unique |
|||
|
|||
|
|||
@unique |
|||
class FittingSlot(IntEnum): |
|||
""" |
|||
Contains slots for ship fittings |
|||
""" |
|||
# These are self-explanatory |
|||
LOW = 1 |
|||
MED = 2 |
|||
HIGH = 3 |
|||
RIG = 4 |
|||
SUBSYSTEM = 5 |
|||
# not a real slot, need for pyfa display rack separation |
|||
MODE = 6 |
|||
# system effects. They are projected "modules" and pyfa assumes all modules |
|||
# have a slot. In this case, make one up. |
|||
SYSTEM = 7 |
|||
# used for citadel services |
|||
SERVICE = 8 |
|||
# fighter 'slots'. Just easier to put them here... |
|||
F_LIGHT = 10 |
|||
F_SUPPORT = 11 |
|||
F_HEAVY = 12 |
|||
# fighter 'slots' (for structures) |
|||
FS_LIGHT = 13 |
|||
FS_SUPPORT = 14 |
|||
FS_HEAVY = 15 |
|||
|
|||
|
|||
@unique |
|||
class ImplantLocation(IntEnum): |
|||
""" |
|||
Contains location of the implant |
|||
""" |
|||
FIT = 0 |
|||
CHARACTER = 1 |
|||
|
|||
|
|||
@unique |
|||
class CalcType(IntEnum): |
|||
""" |
|||
Contains location of the calculation |
|||
""" |
|||
LOCAL = 0 |
|||
PROJECTED = 1 |
|||
COMMAND = 2 |
|||
|
|||
|
|||
@unique |
|||
class FittingModuleState(IntEnum): |
|||
""" |
|||
Contains the state of a fitting module |
|||
""" |
|||
OFFLINE = -1 |
|||
ONLINE = 0 |
|||
ACTIVE = 1 |
|||
OVERHEATED = 2 |
|||
|
|||
|
|||
@unique |
|||
class FittingHardpoint(IntEnum): |
|||
""" |
|||
Contains the types of a fitting hardpoint |
|||
""" |
|||
NONE = 0 |
|||
MISSILE = 1 |
|||
TURRET = 2 |
|||
|
|||
|
|||
@unique |
|||
class SpoolType(IntEnum): |
|||
SCALE = 0 # [0..1] |
|||
TIME = 1 # Expressed via time in seconds since spool up started |
|||
CYCLES = 2 # Expressed in amount of cycles since spool up started |
|||
|
|||
|
|||
@unique |
|||
class FitSystemSecurity(IntEnum): |
|||
HISEC = 0 |
|||
LOWSEC = 1 |
|||
NULLSEC = 2 |
|||
WSPACE = 3 |
@ -0,0 +1,101 @@ |
|||
# =============================================================================== |
|||
# 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/>. |
|||
# =============================================================================== |
|||
|
|||
import threading |
|||
|
|||
from sqlalchemy import MetaData, create_engine |
|||
from sqlalchemy.orm import sessionmaker |
|||
|
|||
from . import migration |
|||
from eos import config |
|||
from logbook import Logger |
|||
|
|||
pyfalog = Logger(__name__) |
|||
pyfalog.info("Initializing database") |
|||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring) |
|||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring) |
|||
|
|||
class ReadOnlyException(Exception): |
|||
pass |
|||
|
|||
|
|||
gamedata_connectionstring = config.gamedata_connectionstring |
|||
if callable(gamedata_connectionstring): |
|||
gamedata_engine = create_engine("sqlite://", creator=gamedata_connectionstring, echo=config.debug) |
|||
else: |
|||
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug) |
|||
|
|||
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] |
|||
config.gamedata_date = gamedata_session.execute( |
|||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'dump_time'" |
|||
).fetchone()[0] |
|||
except Exception as e: |
|||
pyfalog.warning("Missing gamedata version.") |
|||
pyfalog.critical(e) |
|||
config.gamedata_version = None |
|||
config.gamedata_date = None |
|||
|
|||
saveddata_connectionstring = config.saveddata_connectionstring |
|||
if saveddata_connectionstring is not None: |
|||
if callable(saveddata_connectionstring): |
|||
saveddata_engine = create_engine(creator=saveddata_connectionstring, echo=config.debug) |
|||
else: |
|||
saveddata_engine = create_engine(saveddata_connectionstring, echo=config.debug) |
|||
|
|||
saveddata_meta = MetaData() |
|||
saveddata_meta.bind = saveddata_engine |
|||
saveddata_session = sessionmaker(bind=saveddata_engine, autoflush=False, expire_on_commit=False)() |
|||
else: |
|||
saveddata_meta = None |
|||
|
|||
# Lock controlling any changes introduced to session |
|||
sd_lock = threading.RLock() |
|||
|
|||
# Import all the definitions for all our database stuff |
|||
# noinspection PyPep8 |
|||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes |
|||
# noinspection PyPep8 |
|||
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \ |
|||
miscData, mutator, module, override, price, queries, skill, targetResists, user |
|||
|
|||
# Import queries |
|||
# noinspection PyPep8 |
|||
from eos.db.gamedata.queries import * |
|||
# noinspection PyPep8 |
|||
from eos.db.saveddata.queries import * |
|||
|
|||
# If using in memory saveddata, you'll want to reflect it so the data structure is good. |
|||
if config.saveddata_connectionstring == "sqlite:///:memory:": |
|||
saveddata_meta.create_all() |
|||
pyfalog.info("Running database out of memory.") |
|||
|
|||
|
|||
def rollback(): |
|||
with sd_lock: |
|||
pyfalog.warning("Session rollback triggered.") |
|||
saveddata_session.rollback() |
@ -0,0 +1,2 @@ |
|||
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes", |
|||
"item", "marketGroup", "metaGroup", "unit", "alphaClones"] |
@ -0,0 +1,50 @@ |
|||
# =============================================================================== |
|||
# 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 Column, String, Integer, Table, ForeignKey |
|||
from sqlalchemy.orm import relation, mapper, synonym |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import AlphaClone, AlphaCloneSkill |
|||
|
|||
alphaclones_table = Table( |
|||
"alphaClones", |
|||
gamedata_meta, |
|||
Column("alphaCloneID", Integer, primary_key=True), |
|||
Column("alphaCloneName", String), |
|||
) |
|||
|
|||
alphacloneskskills_table = Table( |
|||
"alphaCloneSkills", |
|||
gamedata_meta, |
|||
Column("alphaCloneID", Integer, ForeignKey("alphaClones.alphaCloneID"), primary_key=True), |
|||
Column("typeID", Integer, primary_key=True), |
|||
Column("level", Integer), |
|||
) |
|||
|
|||
mapper(AlphaClone, alphaclones_table, |
|||
properties={ |
|||
"ID" : synonym("alphaCloneID"), |
|||
"skills": relation( |
|||
AlphaCloneSkill, |
|||
cascade="all,delete-orphan", |
|||
backref="clone") |
|||
}) |
|||
|
|||
mapper(AlphaCloneSkill, alphacloneskskills_table) |
@ -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 sqlalchemy import Table, Column, Integer, Float, Unicode, ForeignKey, String, Boolean |
|||
from sqlalchemy.ext.associationproxy import association_proxy |
|||
from sqlalchemy.orm import relation, mapper, synonym, deferred |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import Attribute, AttributeInfo, Unit |
|||
|
|||
typeattributes_table = Table("dgmtypeattribs", gamedata_meta, |
|||
Column("value", Float), |
|||
Column("typeID", Integer, ForeignKey("invtypes.typeID"), primary_key=True, index=True), |
|||
Column("attributeID", ForeignKey("dgmattribs.attributeID"), primary_key=True)) |
|||
|
|||
attributes_table = Table("dgmattribs", gamedata_meta, |
|||
Column("attributeID", Integer, primary_key=True), |
|||
Column("attributeName", String), |
|||
Column("defaultValue", Float), |
|||
Column("maxAttributeID", Integer, ForeignKey("dgmattribs.attributeID")), |
|||
Column("description", Unicode), |
|||
Column("published", Boolean), |
|||
Column("displayName", String), |
|||
Column("highIsGood", Boolean), |
|||
Column("iconID", Integer), |
|||
Column("attributeCategory", Integer), |
|||
Column("tooltipDescription", Integer), |
|||
Column("unitID", Integer, ForeignKey("dgmunits.unitID"))) |
|||
|
|||
mapper(Attribute, typeattributes_table, |
|||
properties={"info": relation(AttributeInfo, lazy=False)}) |
|||
|
|||
mapper(AttributeInfo, attributes_table, |
|||
properties={ |
|||
"unit" : relation(Unit), |
|||
"ID" : synonym("attributeID"), |
|||
"name" : synonym("attributeName"), |
|||
"description": deferred(attributes_table.c.description) |
|||
}) |
|||
|
|||
Attribute.ID = association_proxy("info", "attributeID") |
|||
Attribute.name = association_proxy("info", "attributeName") |
|||
Attribute.description = association_proxy("info", "description") |
|||
Attribute.published = association_proxy("info", "published") |
|||
Attribute.displayName = association_proxy("info", "displayName") |
|||
Attribute.highIsGood = association_proxy("info", "highIsGood") |
|||
Attribute.iconID = association_proxy("info", "iconID") |
|||
Attribute.icon = association_proxy("info", "icon") |
|||
Attribute.unit = association_proxy("info", "unit") |
@ -0,0 +1,38 @@ |
|||
# =============================================================================== |
|||
# 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 Boolean, Column, Integer, String, Table |
|||
from sqlalchemy.orm import deferred, mapper, synonym |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import Category |
|||
|
|||
categories_table = Table("invcategories", gamedata_meta, |
|||
Column("categoryID", Integer, primary_key=True), |
|||
Column("categoryName", String), |
|||
Column("description", String), |
|||
Column("published", Boolean), |
|||
Column("iconID", Integer)) |
|||
|
|||
mapper(Category, categories_table, |
|||
properties={ |
|||
"ID" : synonym("categoryID"), |
|||
"name" : synonym("categoryName"), |
|||
"description": deferred(categories_table.c.description) |
|||
}) |
@ -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 sqlalchemy import Column, Float, Integer, Table, ForeignKey |
|||
from sqlalchemy.orm import mapper, relation, synonym |
|||
from sqlalchemy.ext.associationproxy import association_proxy |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import DynamicItem, DynamicItemAttribute, DynamicItemItem, Item |
|||
|
|||
from eos.gamedata import AttributeInfo |
|||
|
|||
dynamic_table = Table("mutaplasmids", gamedata_meta, |
|||
Column("typeID", ForeignKey("invtypes.typeID"), primary_key=True, index=True), |
|||
Column("resultingTypeID", ForeignKey("invtypes.typeID"), primary_key=True)) |
|||
|
|||
dynamicAttributes_table = Table("mutaplasmidAttributes", gamedata_meta, |
|||
Column("typeID", Integer, ForeignKey("mutaplasmids.typeID"), primary_key=True), |
|||
Column("attributeID", ForeignKey("dgmattribs.attributeID"), primary_key=True), |
|||
Column("min", Float), |
|||
Column("max", Float)) |
|||
|
|||
dynamicApplicable_table = Table("mutaplasmidItems", gamedata_meta, |
|||
Column("typeID", ForeignKey("mutaplasmids.typeID"), primary_key=True), |
|||
Column("applicableTypeID", ForeignKey("invtypes.typeID"), primary_key=True),) |
|||
|
|||
mapper(DynamicItem, dynamic_table, properties={ |
|||
"attributes": relation(DynamicItemAttribute), |
|||
"item": relation(Item, foreign_keys=[dynamic_table.c.typeID]), |
|||
"resultingItem": relation(Item, foreign_keys=[dynamic_table.c.resultingTypeID]), |
|||
"ID": synonym("typeID"), |
|||
}) |
|||
|
|||
mapper(DynamicItemAttribute, dynamicAttributes_table, |
|||
properties={"info": relation(AttributeInfo, lazy=False)}) |
|||
|
|||
mapper(DynamicItemItem, dynamicApplicable_table, properties={ |
|||
"mutaplasmid": relation(DynamicItem), |
|||
}) |
|||
|
|||
DynamicItemAttribute.ID = association_proxy("info", "attributeID") |
|||
DynamicItemAttribute.name = association_proxy("info", "attributeName") |
|||
DynamicItemAttribute.description = association_proxy("info", "description") |
|||
DynamicItemAttribute.published = association_proxy("info", "published") |
|||
DynamicItemAttribute.displayName = association_proxy("info", "displayName") |
|||
DynamicItemAttribute.highIsGood = association_proxy("info", "highIsGood") |
|||
DynamicItemAttribute.iconID = association_proxy("info", "iconID") |
|||
DynamicItemAttribute.icon = association_proxy("info", "icon") |
|||
DynamicItemAttribute.unit = association_proxy("info", "unit") |
@ -0,0 +1,46 @@ |
|||
# =============================================================================== |
|||
# 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 Column, String, Integer, Boolean, Table, ForeignKey |
|||
from sqlalchemy.orm import mapper, synonym, deferred |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import Effect, ItemEffect |
|||
|
|||
typeeffects_table = Table("dgmtypeeffects", gamedata_meta, |
|||
Column("typeID", Integer, ForeignKey("invtypes.typeID"), primary_key=True, index=True), |
|||
Column("effectID", Integer, ForeignKey("dgmeffects.effectID"), primary_key=True)) |
|||
|
|||
effects_table = Table("dgmeffects", gamedata_meta, |
|||
Column("effectID", Integer, primary_key=True), |
|||
Column("effectName", String), |
|||
Column("description", String), |
|||
Column("published", Boolean), |
|||
Column("isAssistance", Boolean), |
|||
Column("isOffensive", Boolean), |
|||
Column("resistanceID", Integer)) |
|||
|
|||
mapper(Effect, effects_table, |
|||
properties={ |
|||
"ID" : synonym("effectID"), |
|||
"name" : synonym("effectName"), |
|||
"description": deferred(effects_table.c.description) |
|||
}) |
|||
|
|||
mapper(ItemEffect, typeeffects_table) |
@ -0,0 +1,40 @@ |
|||
# =============================================================================== |
|||
# 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 Column, String, Integer, Boolean, ForeignKey, Table |
|||
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref |
|||
|
|||
from eos.db import gamedata_meta |
|||
from eos.gamedata import Category, Group |
|||
|
|||
groups_table = Table("invgroups", gamedata_meta, |
|||
Column("groupID", Integer, primary_key=True), |
|||
Column("groupName", String), |
|||
Column("description", String), |
|||
Column("published", Boolean), |
|||
Column("categoryID", Integer, ForeignKey("invcategories.categoryID")), |
|||
Column("iconID", Integer)) |
|||
|
|||
|