Quellcode durchsuchen

UI in Python with wxWidgets. It can generate a valid command for the optimizer.

Iñigo Valentin vor 4 Jahren
Ursprung
Commit
8dd7ee2101

+ 4 - 1
.gitignore

@@ -1,3 +1,6 @@
 data.sqlite
-RuneOptimizer
+./RuneOptimizer
 src/util
+src/RuneOptimizer/RuneOptimizer
+RuneOptimizer
+src/RuneOptimizer/util/

+ 1 - 1
src/Makefile

@@ -3,7 +3,7 @@ CFLAGS=
 LIBS=-lsqlite3
 OUT=../RuneOptimizer
 compile :
-	@$(CC) RuneOptimizer.c $(LIBS) -o $(OUT)
+	@$(CC) RuneOptimizer/RuneOptimizer.c $(LIBS) -o $(OUT)
 	@echo Compiled executable $(OUT)
 #RuneOptimizer: RuneOptimizer.c
 #	$(CC) -o makeRO RuneOptimizer.c

+ 1 - 1
src/RuneOptimizer.c → src/RuneOptimizer/RuneOptimizer.c

@@ -67,7 +67,7 @@ int main(int argc, char *argv[]){
 
 int open_database(){
     // The database location and name is hardcoded in the same directory.
-    int db_status = sqlite3_open("../data.sqlite", &db);
+    int db_status = sqlite3_open("data.sqlite", &db);
     if (db_status != SQLITE_OK) {
         fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
         sqlite3_close(db);

+ 0 - 0
src/RuneOptimizer.h → src/RuneOptimizer/RuneOptimizer.h


+ 560 - 0
src/RuneOptimizerGUI/RuneOptimizer.py

@@ -0,0 +1,560 @@
+#!/usr/bin/env python
+"""
+RuneOptimizer GUI.
+"""
+
+import wx
+import wx.grid
+import sqlite3
+import math
+
+conn = None
+
+stat_names = [
+  "NULL", "HP  ", "HP% ", "ATK ", "ATK%", "DEF ",
+  "DEF%", "NULL", "SPD ", "CRR ", "CRD ", "RES ", "ACC "
+]
+set_names = [
+  "NULL",    "ENERGY",        "GUARD",   "SWIFT",    "BLADE",   "RAGE",
+  "FOCUS",   "ENDURE",        "FATAL",   "NULL",     "DESPAIR", "VAMPIRE",
+  "VIOLENT", "NEMESIS",       "WILL",    "SHIELD",   "REVENGE", "DESTROY",
+  "FIGHT",   "DETERMINATION", "ENHANCE", "ACCURACY", "TOLERANCE"
+]
+
+class RuneOptimizerFrame(wx.Frame):
+    """
+    The main frame
+    """
+    statGrid = None
+    runeList = None
+    minStatSlider = None
+    minStatText = None
+
+    def __init__(self, *args, **kw):
+        global conn
+        # ensure the parent's __init__ is called
+        super(RuneOptimizerFrame, self).__init__(*args, **kw)
+
+        # create a panel in the frame
+        # Sies and pos do nothing here
+        pnl = wx.Panel(self,pos=(500,10), size=(800,800))
+
+        # put some text with a larger bold font on it
+        st = wx.StaticText(pnl, label="Rune Optimizer")
+        font = st.GetFont()
+        font.PointSize += 2
+        font = font.Bold()
+        st.SetFont(font)
+
+        # and create a sizer to manage the layout of child widgets
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(st, wx.SizerFlags().Border(wx.TOP|wx.LEFT, 25))
+        pnl.SetSizer(sizer)
+
+        # create a menu bar
+        self.makeMenuBar()
+
+        # and a status bar
+        self.CreateStatusBar()
+        self.SetStatusText("Status: Updated, no pending changes")
+
+        # Show unit list
+        self.unitList = wx.ListCtrl(parent=pnl, id=-1, pos=(10, 30), size=(190, 650), style=wx.LC_REPORT)
+        self.unitList.InsertColumn(0, "Name", width=120)
+        self.unitList.InsertColumn(1, "Prio.", width=40)
+        self.unitList.InsertColumn(2, "Sto.", width=30)
+
+        # Empty item
+        item = wx.ListItem()
+        item.SetId(0)
+        item.SetText("")
+        item.SetColumn(0)
+        item = wx.ListItem()
+        item.SetId(0)
+        item.SetText("")
+        item.SetColumn(1)
+        item = wx.ListItem()
+        item.SetId(0)
+        item.SetText("")
+        item.SetColumn(2)
+        self.unitList.InsertItem(item)
+
+        # Get units from db
+        cursor = conn.execute("""
+          SELECT
+            id,
+            name,
+            (
+              SELECT cast(total(teams.priority) as int)
+              FROM teams, units_teams
+              WHERE teams.id = units_teams.team AND units_teams.unit = units.id
+            ) as priority
+          FROM units
+          WHERE
+            id IN (SELECT DISTINCT unit FROM runes)
+          ORDER BY priority DESC;
+        """)
+        i = 0
+        for row in cursor:
+            self.unitList.InsertStringItem(i, row[1])
+            self.unitList.SetStringItem(i, 1, str(row[2]))
+            self.unitList.SetStringItem(i, 2, "")
+            self.unitList.SetItemData(i, int(row[0]))
+            i = i + 1
+
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.unitSelected, self.unitList)
+
+        # Stats table
+        self.statGrid = wx.grid.Grid(parent=pnl, id=-1, pos=(210, 30), size=(165, 220))
+        self.statGrid.CreateGrid(numRows=10, numCols=2, selmode=wx.grid.Grid.GridSelectionModes.SelectRowsOrColumns)
+        self.statGrid.SetDefaultCellAlignment(horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE)
+        monospaceFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        self.statGrid.SetDefaultCellFont(monospaceFont)
+        self.statGrid.SetColSize(col=0, width=50)
+        self.statGrid.SetColLabelValue(col=0, value="Base")
+        self.statGrid.SetColSize(col=0, width=50)
+        self.statGrid.SetColLabelValue(col=1, value="Current")
+        self.statGrid.SetRowLabelSize(width=35)
+        self.statGrid.SetColLabelSize(height=20)
+        self.statGrid.SetRowSize(row=0, height=20)
+        self.statGrid.SetRowSize(row=1, height=20)
+        self.statGrid.SetRowSize(row=2, height=20)
+        self.statGrid.SetRowSize(row=3, height=20)
+        self.statGrid.SetRowSize(row=4, height=20)
+        self.statGrid.SetRowSize(row=5, height=20)
+        self.statGrid.SetRowSize(row=6, height=20)
+        self.statGrid.SetRowSize(row=7, height=20)
+        self.statGrid.SetRowSize(row=8, height=20)
+        self.statGrid.SetRowSize(row=9, height=20)
+        self.statGrid.SetRowLabelValue(row=0, value=" HP")
+        self.statGrid.SetRowLabelValue(row=1, value="ATK")
+        self.statGrid.SetRowLabelValue(row=2, value="DEF")
+        self.statGrid.SetRowLabelValue(row=3, value="SPD")
+        self.statGrid.SetRowLabelValue(row=4, value="CRR")
+        self.statGrid.SetRowLabelValue(row=5, value="CRD")
+        self.statGrid.SetRowLabelValue(row=6, value="RES")
+        self.statGrid.SetRowLabelValue(row=7, value="ACC")
+        self.statGrid.SetRowLabelValue(row=8, value="EHP")
+        self.statGrid.SetRowLabelValue(row=9, value="DMG")
+
+        #Rune set list
+        runeListBox = [
+          wx.StaticBox(pnl, label="Slot1:",id=-1, pos=(465, 30), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot2:",id=-1, pos=(550, 30), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot3:",id=-1, pos=(550, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot4:",id=-1, pos=(465, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot5:",id=-1, pos=(380, 150), size=(80, 115)),
+          wx.StaticBox(pnl, label="Slot6:",id=-1, pos=(380, 30), size=(80, 115)),
+        ]
+        self.runeList = [
+          wx.StaticText(runeListBox[0], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[1], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[2], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[3], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[4], label="",id=-1, pos=(0, 0), size=(80, 115)),
+          wx.StaticText(runeListBox[5], label="",id=-1, pos=(0, 0), size=(80, 115)),
+        ]
+        monospaceFont.PointSize -= 2
+        for i in range(0, 6):
+            runeListBox[i].SetFont(monospaceFont)
+        monospaceFont.PointSize += 2
+
+        # Create an update button:
+        #btUpdate = wx.Button(parent=pnl, id=-1, label="Update data", pos=(10,10), size=(100,40))
+        #btOptimize = wx.Button(parent=pnl, id=-1, label="Optimize unit", pos=(10,60), size=(100,40))
+
+        # Line to separate optimization parameters
+        wx.StaticLine(parent=pnl, id=-1, pos=(210, 290), size=(650, 3), style=wx.LC_REPORT)
+
+        # Min stats
+        minStatBox = wx.StaticBox(pnl, label="Min. stats:",id=-1, pos=(220, 300), size=(260, 380))
+        wx.StaticText(minStatBox, label="HP",id=-1, pos=(0, 5), size=(30, 25))
+        wx.StaticText(minStatBox, label="ATK",id=-1, pos=(0, 35), size=(30, 25))
+        wx.StaticText(minStatBox, label="DEF",id=-1, pos=(0, 65), size=(30, 25))
+        wx.StaticText(minStatBox, label="SPD",id=-1, pos=(0, 95), size=(30, 25))
+        wx.StaticText(minStatBox, label="CRR",id=-1, pos=(0, 125), size=(30, 25))
+        wx.StaticText(minStatBox, label="CRD",id=-1, pos=(0, 155), size=(30, 25))
+        wx.StaticText(minStatBox, label="RES",id=-1, pos=(0, 185), size=(30, 25))
+        wx.StaticText(minStatBox, label="ACC",id=-1, pos=(0, 215), size=(30, 25))
+        wx.StaticText(minStatBox, label="EHP",id=-1, pos=(0, 245), size=(30, 25))
+        wx.StaticText(minStatBox, label="DMG",id=-1, pos=(0, 275), size=(30, 25))
+        self.minStatSlid = [
+            wx.Slider(minStatBox, id=-1, pos=(30, 0), size=(150, 30), name="slid0"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 30), size=(150, 30), name="slid1"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 60), size=(150, 30), name="slid2"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 90), size=(150, 30), name="slid3"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 120), size=(150, 30), name="slid4"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 150), size=(150, 30), name="slid5"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 180), size=(150, 30), name="slid6"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 210), size=(150, 30), name="slid7"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 240), size=(150, 30), name="slid8"),
+            wx.Slider(minStatBox, id=-1, pos=(30, 270), size=(150, 30), name="slid9")
+        ]
+        for i in range(0, 9):
+            self.minStatSlid[i].SetMin(0)
+            self.minStatSlid[i].SetMax(0)
+            self.minStatSlid[i].SetValue(0)
+            self.Bind(wx.EVT_SCROLL, self.minStatChangeBySlider, self.minStatSlid[i])
+        self.minStatText = [
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 0), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx0"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 30), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx1"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 60), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx2"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 90), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx3"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 120), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx4"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 150), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx5"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 180), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx6"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 210), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx7"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 240), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx8"),
+            wx.TextCtrl(minStatBox, id=-1, value="", pos=(185, 270), size=(65, 25), style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB, name="tx9")
+        ]
+        for i in range(0, 9):
+            self.Bind(wx.EVT_TEXT_ENTER, self.minStatChangeByText, self.minStatText[i])
+        minStatsReset = wx.Button(parent=minStatBox, id=-1, pos=(10, 300), size=(100, 40), style=wx.LC_REPORT, label="Reset all")
+        minStatsAdapt = wx.Button(parent=minStatBox, id=-1, pos=(120, 300), size=(100, 40), style=wx.LC_REPORT, label="Adapt all")
+        # TODO: Add binds
+
+        # Rune sets
+        names = [
+          "",        "ENERGY ", "GUARD  ", "SWIFT  ", "BLADE  ", "RAGE   ",
+          "FOCUS  ", "ENDURE ", "FATAL  ", "DESPAIR", "VAMPIRE", "VIOLENT",
+          "NEMESIS", "WILL   ", "SHIELD ", "REVENGE", "DESTROY", "FIGHT  ",
+          "DETERMI", "ENHANCE", "ACCURAC", "TOLERAN"
+        ]
+        setBox = wx.StaticBox(pnl, label="Rune Sets:",id=-1, pos=(500, 300), size=(330, 65))
+        self.runeSets = [
+            wx.Choice(parent=setBox, id=-1, pos=(5, 0), choices=names),
+            wx.Choice(parent=setBox, id=-1, pos=(110, 0), choices=names),
+            wx.Choice(parent=setBox, id=-1, pos=(215, 0), choices=names)
+        ]
+
+        # Allowed main stats for even slots
+        names = [
+          ["HP  ", "HP% ", "ATK ", "ATK%", "DEF ", "DEF%"],
+          ["SPD ", "CRR ", "CRD ", "RES ", "ACC "]
+        ]
+        statBox = wx.StaticBox(pnl, label="Main stats (2, 4, 6):",id=-1, pos=(500, 380), size=(150, 190))
+        self.stats = [
+          wx.CheckListBox(parent=statBox, id=-1, pos=(5, 5), size=(70, 155), choices=names[0]),
+          wx.CheckListBox(parent=statBox, id=-1, pos=(70, 5), size=(70, 155), choices=names[1])
+        ]
+
+        levelBox = wx.StaticBox(pnl, label="Rune Level:",id=-1, pos=(700, 380), size=(100, 65))
+        self.level = wx.Choice(parent=levelBox, id=-1, pos=(5, 0), choices=["Current", "+ 12", " + 15"])
+
+        # Button to start
+        btOptimize = wx.Button(parent=pnl, id=-1, pos=(700, 480), size=(100, 40), style=wx.LC_REPORT, label="OPTIMIZE")
+        self.Bind(wx.EVT_BUTTON, self.startOptimization, btOptimize)
+
+    def startOptimization(self, event):
+        # Example call:
+        # ../RuneOptimizer optimize 7223811472 -l 15 -e rage,blade --stats atk,crr,crd -h 10000 -f 10
+        command = "RuneOptimizer optimize "
+        print("StartOptimization...")
+        unitId = str(self.unitList.GetItemData(self.unitList.GetFirstSelected()))
+        command += unitId
+        print("    Unit ID: " + unitId)
+        level = self.level.GetSelection()
+        if level == 1:
+            level = "12"
+        elif level == 2:
+            level = "15"
+        else:
+            level = "current"
+        command += (" --level " + level)
+        print("    Rune level: " + level)
+        sets = ""
+        for i in range (0, 2):
+            selected = self.runeSets[i].GetString(self.runeSets[i].GetSelection()).upper().replace(" ", "");
+            for j in range(0, 22):
+                name = set_names[j].upper()
+                if len(name) > 7:
+                    name = name[0:7]
+                #print(selected + " - " + name)
+                if selected == name:
+                    sets += set_names[j].lower() + ","
+        if len(sets) > 0:
+            sets = sets[:-1]
+            # TODO: ELSE ERROR
+        command += (" --sets " + sets)
+        stats = ""
+        selected_stats = self.stats[0].GetCheckedItems() + self.stats[1].GetCheckedItems()
+        for s in self.stats[0].GetCheckedItems():
+            if s == 0:
+                stats += "hpflat,"
+            elif s == 1:
+                stats += "hp,"
+            elif s == 2:
+                stats += "atkflat,"
+            elif s == 3:
+                stats += "atk,"
+            elif s == 4:
+                stats += "defflat,"
+            elif s == 5:
+                stats += "def,"
+        for s in self.stats[1].GetCheckedItems():
+            if s == 0:
+                stats += "spd,"
+            elif s == 1:
+                stats += "crr,"
+            elif s == 2:
+                stats += "crd,"
+            elif s == 3:
+                stats += "res,"
+            elif s == 4:
+                stats += "acc,"
+        if len(stats) > 0:
+            stats = stats[:-1]
+            # TODO: ELSE ERROR
+        command += (" --stats " + stats)
+        print("    Main stats: " + stats)
+
+        command += (" --min-hp " + str(self.minStatSlid[0].GetValue()))
+        command += (" --min-atk " + str(self.minStatSlid[1].GetValue()))
+        command += (" --min-def " + str(self.minStatSlid[2].GetValue()))
+        command += (" --min-spd " + str(self.minStatSlid[3].GetValue()))
+        command += (" --min-crr " + str(self.minStatSlid[4].GetValue()))
+        command += (" --min-crd " + str(self.minStatSlid[5].GetValue()))
+        command += (" --min-res " + str(self.minStatSlid[6].GetValue()))
+        command += (" --min-acc " + str(self.minStatSlid[7].GetValue()))
+        command += (" --min-ehp " + str(self.minStatSlid[8].GetValue()))
+        command += (" --min-dmg " + str(self.minStatSlid[9].GetValue()))
+
+
+        command += (" --gui ")
+        print("Command: " + command)
+
+
+    def minStatChangeBySlider(self, event):
+        slidId = int(event.GetEventObject().GetName().replace("slid", ""))
+        self.minStatText[slidId].SetValue(str(event.GetEventObject().GetValue()))
+
+    def minStatChangeByText(self, event):
+        textId = int(event.GetEventObject().GetName().replace("tx", ""))
+        if event.GetEventObject().GetValue().isdigit() == False:
+            event.GetEventObject().SetValue(str(self.minStatSlid[textId].GetValue()))
+        value = int(event.GetEventObject().GetValue())
+        minValue = self.minStatSlid[textId].GetMin()
+        maxValue = self.minStatSlid[textId].GetMax()
+        if value < minValue:
+            value = minValue
+            event.GetEventObject().SetValue(str(value))
+        elif value > maxValue:
+            value = maxValue
+            event.GetEventObject().SetValue(str(value))
+        self.minStatSlid[textId].SetValue(value)
+
+    def unitSelected(self, event):
+        id = str(event.GetEventObject().GetItemData(event.GetEventObject().GetFirstSelected()))
+        print("Current selection:" + id)
+
+        # First, set sliders max values
+        self.minStatSlid[0].SetMax(50000)  #HP
+        self.minStatSlid[1].SetMax(5000)   #ATK
+        self.minStatSlid[2].SetMax(5000)   #DEF
+        self.minStatSlid[3].SetMax(500)    #SPD
+        self.minStatSlid[4].SetMax(100)    #CRR
+        self.minStatSlid[5].SetMax(500)    #CRD
+        self.minStatSlid[6].SetMax(100)    #RES
+        self.minStatSlid[7].SetMax(85)     #ACC
+        self.minStatSlid[8].SetMax(250000) #EHP
+        self.minStatSlid[9].SetMax(8000)   #DMG
+
+        cursor = conn.execute("""
+          SELECT
+            base_hp,
+            base_atk,
+            base_def,
+            base_spd,
+            base_crr,
+            base_crd,
+            base_res,
+            base_acc,
+            current_hp,
+            current_atk,
+            current_def,
+            current_spd,
+            current_crr,
+            current_crd,
+            current_res,
+            current_acc
+          FROM units
+          WHERE
+            id = """ + id + """;
+        """)
+        row = cursor.fetchone()
+        for i in range(0, 8):
+            value = str(row[i])
+            self.minStatSlid[i].SetMin(int(value))
+            if i > 3:
+                value = value + "%"
+            else:
+                value = value + " "
+            self.statGrid.SetCellValue(row=i, col=0, s=value)
+        for i in range(0, 8):
+            value = str(row[8 + i])
+            self.minStatSlid[i].SetValue(int(value))
+            self.minStatText[i].SetValue(value)
+            if i > 3:
+                value = value + "%"
+            else:
+                value = value + " "
+            self.statGrid.SetCellValue(row=i, col=1, s=value)
+        # Galculate EHP and DMG
+        baseHp = int(self.statGrid.GetCellValue(row=0, col=0))
+        baseDef = int(self.statGrid.GetCellValue(row=2, col=0).replace("%", ""))
+        baseEhp = math.ceil((((baseDef * 3.5) + 1140) * baseHp) / 1000)
+        self.statGrid.SetCellValue(row=8, col=0, s=str(baseEhp))
+        self.minStatSlid[8].SetMin(baseEhp)
+        currentHp = int(self.statGrid.GetCellValue(row=0, col=1))
+        currentDef = int(self.statGrid.GetCellValue(row=2, col=1).replace("%", ""))
+        currentEhp = math.ceil((((currentDef * 3.5) + 1140) * currentHp) / 1000)
+        self.statGrid.SetCellValue(row=8, col=1, s=str(currentEhp))
+        self.minStatSlid[8].SetValue(currentEhp)
+        self.minStatText[8].SetValue(str(currentEhp))
+        baseAtk = int(self.statGrid.GetCellValue(row=1, col=0))
+        baseCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
+        baseCrd = int(self.statGrid.GetCellValue(row=5, col=0).replace("%", ""))
+        if (baseCrr > 100):
+            # Dont use crit rate over 100
+            baseCrr = 100
+        baseDmg = math.ceil((baseAtk * (100 - baseCrr) / 100) + ((baseAtk + (baseAtk * baseCrd / 100)) * baseCrr / 100));
+        self.statGrid.SetCellValue(row=9, col=0, s=str(baseDmg))
+        self.minStatSlid[9].SetMin(baseDmg)
+        currentAtk = int(self.statGrid.GetCellValue(row=1, col=1))
+        currentCrr = int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
+        currentCrd = int(self.statGrid.GetCellValue(row=5, col=1).replace("%", ""))
+        if (currentCrr > 100):
+            # Dont use crit rate over 100
+            currentCrr = 100
+        currentDmg = math.ceil((currentAtk * (100 - currentCrr) / 100) + ((currentAtk + (currentAtk * currentCrd / 100)) * currentCrr / 100));
+        self.statGrid.SetCellValue(row=9, col=1, s=str(currentDmg))
+        self.minStatSlid[9].SetValue(currentDmg)
+        self.minStatText[9].SetValue(str(currentDmg))
+        cursor = conn.execute("""
+          SELECT
+            id, slot, type
+          FROM runes
+          WHERE
+            unit = """ + id + """
+          ORDER BY slot;
+        """)
+        i = 0
+        for row in cursor:
+            label = ""
+            label = label + set_names[row[2]] + "\n"
+            # Get all stats
+            cursorStats = conn.execute("""
+              SELECT
+                slot, stat, value, grind, enchant
+              FROM rune_stats
+              WHERE
+                rune = """ + row[0] + """
+              ORDER BY slot;
+                """)
+            j = -1
+            for rowStats in cursorStats:
+                while (j != rowStats[0]):
+                    label = label + "\n"
+                    j = j + 1;
+                label = label + stat_names[rowStats[1]] + str(rowStats[2]).rjust(4) + ""
+                if rowStats[3] > 0:
+                    label = label + " +" + str(rowStats[3])
+            self.runeList[i].SetLabel(label)
+            i = i + 1
+
+    def makeMenuBar(self):
+        """
+        A menu bar is composed of menus, which are composed of menu items.
+        This method builds a set of menus and binds handlers to be called
+        when the menu item is selected.
+        """
+
+        updateMenu = wx.Menu()
+        updateJson = updateMenu.Append(
+          -1,
+          "&Update from JSON file\tCtrl-J",
+          "Updates the database from a profile JSON file."
+        );
+        updateSwdb = updateMenu.Append(
+          -1,
+          "&Update from SWDB\tCtrl-W",
+          "Updates the database from data retrieved from a SWDB instance."
+        );
+        updateSwarfarm = updateMenu.Append(
+          -1,
+          "&Update from Sarfarm\tCtrl-F",
+          "Updates the database from data retrieved from Swarfarm."
+        );
+        updateSqlite = updateMenu.Append(
+          -1,
+          "&Update from a sqlite database\tCtrl-Q",
+          "Updates the database from a SWDB sqlite database."
+        );
+
+        # Make a file menu with Hello and Exit items
+        fileMenu = wx.Menu()
+        # The "\t..." syntax defines an accelerator key that also triggers
+        # the same event
+        aboutItem = fileMenu.Append(wx.ID_ABOUT)
+        fileMenu.AppendSeparator()
+        # When using a stock ID we don't need to specify the menu item's
+        # label
+        exitItem = fileMenu.Append(wx.ID_EXIT)
+
+
+
+        # Make the menu bar and add the two menus to it. The '&' defines
+        # that the next letter is the "mnemonic" for the menu item. On the
+        # platforms that support it those letters are underlined and can be
+        # triggered from the keyboard.
+        menuBar = wx.MenuBar()
+        menuBar.Append(fileMenu, "&File")
+        menuBar.Append(updateMenu, "&Update")
+
+        # Give the menu bar to the frame
+        self.SetMenuBar(menuBar)
+
+        # Finally, associate a handler function with the EVT_MENU event for
+        # each of the menu items. That means that when that menu item is
+        # activated then the associated handler function will be called.
+        #self.Bind(wx.EVT_MENU, self.OnHello, helloItem)
+        self.Bind(wx.EVT_MENU, self.closeApp, exitItem)
+        self.Bind(wx.EVT_MENU, self.showAbout, aboutItem)
+        self.Bind(wx.EVT_MENU, self.updateFromJson, updateJson)
+        self.Bind(wx.EVT_MENU, self.updateFromSwdb, updateSwdb)
+        self.Bind(wx.EVT_MENU, self.updateFromSwarfarm, updateSwarfarm)
+        self.Bind(wx.EVT_MENU, self.updateFromSqlite, updateSqlite)
+
+    def closeApp(self, event):
+        """Close the frame, terminating the application."""
+        self.Close(True)
+
+    def showAbout(self, event):
+        """Display an About Dialog"""
+        wx.MessageBox("RuneOptimizerAbout", wx.OK | wx.ICON_INFORMATION)
+
+    def updateFromJson(self, event):
+        self.showUnimplemented(wx.EVT_MENU)
+
+    def updateFromSwdb(self, event):
+        self.showUnimplemented()
+
+    def updateFromSwarfarm(self, event):
+        self.showUnimplemented()
+
+    def updateFromSqlite(self, event):
+        self.showUnimplemented()
+
+    def showUnimplemented(parent, arg2):
+        wx.MessageBox(parent=parent, message="This functionality is not yet implemented", caption="Unimplemented")
+
+
+if __name__ == '__main__':
+    # Connect to the database
+    conn = sqlite3.connect('../../data.sqlite')
+    # When this module is run (not imported) then create the app, the
+    # frame, show it, and start the event loop.
+    app = wx.App()
+    frm = RuneOptimizerFrame(None, title='Rune Optimizer', pos=(100, 100), size=(900, 800))
+    frm.Show()
+    app.MainLoop()