Просмотр исходного кода

Many changes:
- [CLI/GUI] Linux installer for CLI and GUI. The application now uses a per-user database, and the GUI uses the executable in the path.
- [CLI/GUI] Fixed damage calculation.
- [CLI] Command 'player' implemented to show player info.
- [CLI] Command 'gui' implemented to be used in conjunction with the GUI.
- [CLI] Rune efficiency and max efficiency calculated during update.
- [CLI] Functionality for database creation and initialization in the user directory.
- [CLI] Adjusted the sizes for arrays of excluded units and teams in the optimizer.
- [CLI] Prevented a segfault when many teams or units were excluded due to GCC optimization.
- [CLI] Fixed a bug where accuracy was incorreclty calculated for units with > 100 crit rate during optimization.
- [CLI] Fixed a bug where the function generating a new ID for a team didn't return any value.
- [CLI] Fixed the query for team deletion.
- [CLI] Feedback where the command unit doesn't find units.
- [GUI] Fixed rune detection in optimizer form.
- [GUI] Implemented min stat buttons 'Clear All' and 'Reset All'
- [GUI] Fixed min stat sliders and textboxes to make them user frendlier.
- [GUI] Fixed a bug in the result giving an error when selecting empty lines.
- [GUI] Fixed a bug where the result list couldn't be paginated when optimizing after paginating.
- [GUI] Single click to select elements in lists.
- [GUI] Status message when there is no data in the database.

Iñigo Valentin 4 лет назад
Родитель
Сommit
7b71485d16

+ 21 - 10
src/Makefile → Makefile

@@ -16,25 +16,30 @@ else
 	INC=
 endif
 
-SRC=RuneOptimizer/RuneOptimizer.c
+SRC=src/RuneOptimizer/RuneOptimizer.c
 CC=gcc
 CFLAGS=
 OPTS_DEBUG=-Wall -g
 OPTS_TEST=
 OPTS=-O3
 LIBS=-lsqlite3 -lm -ljson-c
-OUT=../RuneOptimizer
-OUT_DEBUG=../RuneOptimizer_DEBUG
-TEST_SRC=test/RuneOptimizerTest.c
-TEST_SRC_UNIT=test/RuneOptimizerUnitTest.c
-TEST_SRC_INTEGRATION=test/RuneOptimizerIntegrationTest.c
-TEST_SRC_E2E=test/RuneOptimizerE2ETest.c
-TEST_OUT_UNIT=test/RuneOptimizerUnitTest
-TEST_OUT_INTEGRATION=test/RuneOptimizerIntegrationTest
-TEST_OUT_E2E=test/RuneOptimizerE2ETest
+OUT=bin/RuneOptimizer
+OUT_DEBUG=bin/RuneOptimizer_DEBUG
+TEST_SRC=src/test/RuneOptimizerTest.c
+TEST_SRC_UNIT=src/test/RuneOptimizerUnitTest.c
+TEST_SRC_INTEGRATION=src/test/RuneOptimizerIntegrationTest.c
+TEST_SRC_E2E=srctest/RuneOptimizerE2ETest.c
+TEST_OUT_UNIT=bin/RuneOptimizerUnitTest
+TEST_OUT_INTEGRATION=bin/RuneOptimizerIntegrationTest
+TEST_OUT_E2E=bin/RuneOptimizerE2ETest
 #TEST_FUNCTION=-nostartfiles -Wl,-emain_test
 TEST_FUNCTION=
 
+INSTALL_DIR=/usr/local/bin/
+GUI_INSTALL_DIR=/usr/share/
+GUI_EXECUTABLE_SRC=/usr/share/RuneOptimizerGUI/RuneOptimizer.py
+GUI_EXECUTABLE_DST=/usr/local/bin/RuneOptimizerGUI
+
 default :
 	@$(CC) -o $(OUT)$(EXTENSION) $(SRC) $(INC) $(LIBS) $(OPTS)
 	@echo Compiled executable $(OUT)$(EXTENSION)
@@ -50,6 +55,12 @@ test : default
 	@echo Compiled testing executable $(TEST_OUT_INTEGRATION)$(EXTENSION)
 	@$(CC) $(OPTS_TEST) -o $(TEST_OUT_E2E)$(EXTENSION) $(TEST_SRC_E2E) $(INC) $(LIBS) $(TEST_FUNCTION)
 	@echo Compiled testing executable $(TEST_OUT_E2E)$(EXTENSION)
+install :
+	@mkdir -p $(INSTALL_DIR)
+	@cp $(OUT)$(EXTENSION) $(INSTALL_DIR)
+	@cp -rf RuneOptimizerGUI $(GUI_INSTALL_DIR)
+	-@rm $(GUI_EXECUTABLE_DST)
+	@ln -s $(GUI_EXECUTABLE_SRC) $(GUI_EXECUTABLE_DST)
 clean :
 # TODO: Delete database?
 	@$(RM) $(OUT)$(EXTENSION)

+ 12 - 10
RuneOptimizerGUI/RuneOptimizer.py

@@ -28,6 +28,7 @@ from subprocess import Popen, PIPE
 import json
 import os
 import pipes
+import appdirs
 from types import SimpleNamespace
 from time import sleep
 
@@ -84,10 +85,10 @@ stat_names = [
 
 # Rune set names, indexed with Com2Us values.
 set_names = [
-  "NULL",    "ENERGY",  "GUARD",         "SWIFT",   "BLADE",    "RAGE",
-  "FOCUS",   "ENDURE",  "FATAL",         "NULL",    "DESPAIR",  "VAMPIRE",
-  "NULL",    "VIOLENT", "NEMESIS",       "WILL",    "SHIELD",   "REVENGE",
-  "DESTROY", "FIGHT",   "DETERMINATION", "ENHANCE", "ACCURACY", "TOLERANCE"
+  "NULL",    "ENERGY",  "GUARD",    "SWIFT",   "BLADE",    "RAGE",     #  0- 5
+  "FOCUS",   "ENDURE",  "FATAL",    "NULL",    "DESPAIR",  "VAMPIRE",  #  6-11
+  "NULL",    "VIOLENT", "NEMESIS",  "WILL",    "SHIELD",   "REVENGE",  # 12-17
+  "DESTROY", "FIGHT",   "DETERMIN", "ENHANCE", "ACCURACY", "TOLERANCE" # 18-23
 ]
 
 
@@ -100,17 +101,18 @@ if __name__ == '__main__':
     """
 
     #Get CLI app version
-    print("EXEC PATH: " + executable_path)
+    # TODO: Change executable name for windows
+    #if os.name == 'nt':
     app_info["app_version"] = subprocess.check_output(
-      [
-        executable_path,
-        'version'
-      ]
+      ['RuneOptimizer', 'version']
     )
 
+    # TODO: Test for executable to exist
+    # TODO: Test for database to exist
+
     # Open the database
     conn = sqlite3.connect(
-      os.path.dirname(os.path.realpath(__file__)) + '/../data.sqlite'
+      appdirs.user_data_dir("RuneOptimizer") + "/data.sqlite"
     )
 
     # Start the GUI

+ 2 - 1
RuneOptimizerGUI/classes/DialogUpdateJson.py

@@ -223,7 +223,8 @@ class DialogUpdateJson(wx.Dialog):
         self.progressSizer.ShowItems(True)
         self.messageSizer.ShowItems(True)
         self.buttonSizer.ShowItems(False)
-        command = executable_path + " update "
+        # TODO: Executable name for windows
+        command = "RuneOptimizer update "
         command += pipes.quote(self.fileSelector.GetPath())
         if self.starsCheck.GetValue():
             command += " --six-stars"

+ 81 - 43
RuneOptimizerGUI/classes/PanelOptimizer.py

@@ -82,8 +82,12 @@ class PanelOptimizer(wx.Panel):
         Called at process end, rediresct to the result view.
     minStatChangeBySlider(event)
         Changes text when a slider is changed.
-    minStatChangeByTExt(event)
+    minStatChangeByText(event)
         Changes the slider when the text is changed.
+    adaptStats(event)
+        Sets all stats requeriments to the unit current values.
+    resetsStats(event)
+        Sets all stats requeriments to the unit base values.
 
     """
 
@@ -344,59 +348,70 @@ class PanelOptimizer(wx.Panel):
             )
         self.minStatTextList = [
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 0), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 0), size=(65, 25), name="tx0",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 25), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 25), size=(65, 25), name="tx1",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 50), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 50), size=(65, 25), name="tx2",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 75), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 75), size=(65, 25), name="tx3",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 100), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 100), size=(65, 25), name="tx4",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 125), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 125), size=(65, 25), name="tx5",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 150), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 150), size=(65, 25), name="tx6",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 175), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 175), size=(65, 25), name="tx7",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 200), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 200), size=(65, 25), name="tx8",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             ),
             wx.TextCtrl(
-              minStatBox, id=wx.ID_ANY, value="", pos=(155, 225), size=(65, 25),
+              parent=minStatBox, id=wx.ID_ANY, value="",
+              pos=(155, 225), size=(65, 25), name="tx9",
               style=wx.TE_RIGHT|wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
             )
         ]
         for i in range(0, 9):
             self.Bind(
-              wx.EVT_TEXT_ENTER,
+              wx.EVT_TEXT,
               self.minStatChangeByText, self.minStatTextList[i]
             )
         minStatsReset = wx.Button(
           parent=minStatBox, id=wx.ID_ANY, pos=(10, 250),
           size=(100, 20), style=wx.LC_REPORT, label="Reset all"
         )
+        self.Bind(wx.EVT_BUTTON, self.resetStats, minStatsReset)
         minStatsAdapt = wx.Button(
           parent=minStatBox, id=wx.ID_ANY, pos=(120, 250),
           size=(100, 20), style=wx.LC_REPORT, label="Adapt all")
-        # TODO: Add binds
+        self.Bind(wx.EVT_BUTTON, self.adaptStats, minStatsAdapt)
         self.optionsSizer.Add(minStatBox)
 
         # Allowed main stats for even slots
@@ -592,7 +607,7 @@ class PanelOptimizer(wx.Panel):
         """)
         row = cursor.fetchone()
         # First, set sliders max values
-        self.minStatSlidList[0].SetMax(50000)  #HP
+        self.minStatSlidList[0].SetMax(100000)  #HP
         self.minStatSlidList[1].SetMax(5000)   #ATK
         self.minStatSlidList[2].SetMax(5000)   #DEF
         self.minStatSlidList[3].SetMax(500)    #SPD
@@ -600,7 +615,7 @@ class PanelOptimizer(wx.Panel):
         self.minStatSlidList[5].SetMax(500)    #CRD
         self.minStatSlidList[6].SetMax(100)    #RES
         self.minStatSlidList[7].SetMax(85)     #ACC
-        self.minStatSlidList[8].SetMax(250000) #EHP
+        self.minStatSlidList[8].SetMax(500000) #EHP
         self.minStatSlidList[9].SetMax(8000)   #DMG
         self.unitInfoBox.SetLabel(row[17] + " #" + row[16])
         for i in range(0, 8):
@@ -642,21 +657,21 @@ class PanelOptimizer(wx.Panel):
             baseCrr = 100
         baseDmg = math.ceil(
           (baseAtk * (100 - baseCrr) / 100) +
-          ((baseAtk + (baseAtk * baseCrd / 100)) * baseCrr / 100)
+          (baseAtk * (      baseCrr  / 100) * baseCrd / 100)
         )
         self.statGrid.SetCellValue(row=9, col=0, s=str(baseDmg))
         self.minStatSlidList[9].SetMin(baseDmg)
         currentAtk = int(self.statGrid.GetCellValue(row=1, col=1))
         currentCrr = \
-          int(self.statGrid.GetCellValue(row=4, col=0).replace("%", ""))
+          int(self.statGrid.GetCellValue(row=4, col=1).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)
+          (currentAtk * (100 - currentCrr) / 100) +                   # Non-crit
+          (currentAtk * (      currentCrr  / 100) * currentCrd / 100) # Crit
         )
         self.statGrid.SetCellValue(row=9, col=1, s=str(currentDmg))
         self.minStatSlidList[9].SetValue(currentDmg)
@@ -713,6 +728,7 @@ class PanelOptimizer(wx.Panel):
         # Try to infer data to set the default options, reset the rest
         # Except the team list, rune level and storage: never reset those.
         self.statCheckListList[0].SetCheckedItems(())
+        self.statCheckListList[1].SetCheckedItems(())
         for i in range(0, 3):
             if currEvenStats[i] == 1: # HP
                 self.statCheckListList[0].Check(0, True)
@@ -741,10 +757,7 @@ class PanelOptimizer(wx.Panel):
         for i in range(0, 23):
             if j < 3:
                 # If a set of 2 has 4 or 2:
-                if i in [
-                  1, 2, 4, 6, 7, 9, 12, 13, 14, 15,
-                  16, 17, 18, 19, 20, 21, 22, 23
-                ]:
+                if i in [1, 2, 4, 6, 7, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]:
                     if currSets[i] >= 4:
                         currSetList[j] = i - 1
                         currSetList[j + 1] = i - 1
@@ -786,7 +799,8 @@ class PanelOptimizer(wx.Panel):
         # Example call:
         # ../RuneOptimizer optimize 7223811472 -l 15
         #-e rage,blade --stats atk,crr,crd -h 10000 -f 10
-        command = executable_path + " optimize "
+        # TODO: Executable name for windows.
+        command = "RuneOptimizer optimize "
         unitId = self.unitId
         command += unitId
         level = self.levelChoice.GetSelection()
@@ -797,9 +811,8 @@ class PanelOptimizer(wx.Panel):
         else:
             level = "current"
         command += (" --level " + level)
-        #print("    Rune level: " + level)
         sets = ""
-        for i in range (0, 2):
+        for i in range (0, 3):
             selected = self.setChoiceList[i].GetString(
               self.setChoiceList[i].GetSelection()
             ).upper().replace(" ", "");
@@ -845,8 +858,6 @@ class PanelOptimizer(wx.Panel):
             stats = stats[:-1]
             # TODO: ELSE ERROR
         command += (" --stats " + stats)
-        #print("    Main stats: " + stats)
-
         command += (" --min-hp " + str(self.minStatSlidList[0].GetValue()))
         command += (" --min-atk " + str(self.minStatSlidList[1].GetValue()))
         command += (" --min-def " + str(self.minStatSlidList[2].GetValue()))
@@ -938,10 +949,6 @@ class PanelOptimizer(wx.Panel):
 
             jsonText = text
 
-        print('Finished. Result:\n' + jsonText)
-
-        """
-
         # DEBUG: Sample data.
         #self.unitId = "7223811472"
         #json = ""{"result_count":5000,"results":[
@@ -950,7 +957,7 @@ class PanelOptimizer(wx.Panel):
         #    {"id":2,"rating":185,"hp":17730,"atk":2521,"dfc":862,"spd":147,"crr":60,"crd":164,"res":28,"acc":15,"ehp":73704,"dmg":5002,"runes":["21564691276","22920616295","27654723287","25633344349","26207902579","22348038576"]},
         #    {"id":3,"rating":179,"hp":15810,"atk":2431,"dfc":1011,"spd":137,"crr":61,"crd":160,"res":27,"acc":15,"ehp":73968,"dmg":4804,"runes":["21564691276","22920616295","27444728625","22750814840","26670411873","22348038576"]},
         #    {"id":4,"rating":176,"hp":15319,"atk":2530,"dfc":909,"spd":142,"crr":60,"crd":165,"res":28,"acc":15,"ehp":66202,"dmg":5035,"runes":["21564691276","16759622995","27654723287","22750814840","26207902579","22348038576"]}]}
-        #"""
+        #
 
         print ("---- RESULT OUTPUT -------------------------------------------")
         print(jsonText)
@@ -979,7 +986,7 @@ class PanelOptimizer(wx.Panel):
                   caption="Error"
                 )
 
-    def minStatChangeBySlider(self, event):
+    def minStatChangeBySlider(self, event=None):
         """Changes text when a slider is changed.
 
         Doesn't do validation.
@@ -990,13 +997,12 @@ class PanelOptimizer(wx.Panel):
             The event that triggered the call (default is None).
 
         """
-
         slidId = int(event.GetEventObject().GetName().replace("slid", ""))
         self.minStatTextList[slidId].SetValue(
           str(event.GetEventObject().GetValue())
         )
 
-    def minStatChangeByText(self, event):
+    def minStatChangeByText(self, event=None):
         """Changes the slider when the text is changed.
 
         Validates the text value.
@@ -1008,17 +1014,49 @@ class PanelOptimizer(wx.Panel):
 
         """
         textId = int(event.GetEventObject().GetName().replace("tx", ""))
-        if event.GetEventObject().GetValue().isdigit() == False:
+        if event.GetEventObject().GetValue().isdigit() == False and \
+          event.GetEventObject().GetValue() != "":
             event.GetEventObject().SetValue(
               str(self.minStatSlidList[textId].GetValue())
             )
-        value = int(event.GetEventObject().GetValue())
         minValue = self.minStatSlidList[textId].GetMin()
         maxValue = self.minStatSlidList[textId].GetMax()
-        if value < minValue:
+        if event.GetEventObject().GetValue().isdigit():
+            value = int(event.GetEventObject().GetValue())
+        else:
             value = minValue
-            event.GetEventObject().SetValue(str(value))
-        elif value > maxValue:
-            value = maxValue
-            event.GetEventObject().SetValue(str(value))
+        # Unbind scroll event of the slider, set value and rebind.
+        self.Unbind(wx.EVT_SCROLL, self.minStatSlidList[textId])
         self.minStatSlidList[textId].SetValue(value)
+        self.Bind(
+            wx.EVT_SCROLL,
+            self.minStatChangeBySlider, self.minStatSlidList[textId]
+        )
+
+    def adaptStats(self, event=None):
+        """Sets all stats requeriments to the unit current values.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+        for i in range(0, 10):
+             self.minStatSlidList[i].SetValue(self.unitStats[i])
+             self.minStatTextList[i].SetValue(str(self.unitStats[i]))
+
+    def resetStats(self, event=None):
+        """Sets all stats requeriments to the unit base values.
+
+        Parameters
+        ----------
+        event : wxEvent, optional
+            The event that triggered the call (default is None).
+
+        """
+        for i in range(0, 10):
+             self.minStatSlidList[i].SetValue(self.minStatSlidList[i].GetMin())
+             self.minStatTextList[i].SetValue(
+               str(self.minStatSlidList[i].GetMin())
+             )

+ 9 - 3
RuneOptimizerGUI/classes/PanelResults.py

@@ -167,6 +167,7 @@ class PanelResults(wx.Panel):
           numRows=10, numCols=11
         )
         self.resultGrid.EnableEditing(False)
+        self.resultGrid.SetSelectionMode(wx.grid.Grid.GridSelectionModes.SelectRows)
         self.resultGrid.SetDefaultCellAlignment(
           horiz=wx.ALIGN_RIGHT, vert=wx.ALIGN_CENTRE
         )
@@ -616,6 +617,7 @@ class PanelResults(wx.Panel):
           object_hook=lambda d: SimpleNamespace(**d)
         )
         self.totalPages = math.ceil(len(self.data.results) / self.linesPerPage)
+        self.page = 0
         self.printResults()
 
     def pgPrev(self, event):
@@ -817,7 +819,12 @@ class PanelResults(wx.Panel):
         """
 
         selectedLine = self.resultGrid.GetSelectedRows()[0]
-        self.selectedResultIndex = selectedLine + (self.linesPerPage * self.page)
+        self.selectedResultIndex = \
+          selectedLine + (self.linesPerPage * self.page)
+        if self.selectedResultIndex >= len(self.data.results):
+            self.selectedResultIndex = -1;
+            self.detailsSizer.ShowItems(False)
+            return
         self.statGrid.SetCellValue(
           row=0, col=0,
           s=str(self.data.results[self.selectedResultIndex].hp) + " "
@@ -867,7 +874,7 @@ class PanelResults(wx.Panel):
           s=str(self.data.results[self.selectedResultIndex].dfc) + " "
         )
         diff = \
-          self.data.results[self.selectedResultIndex].atk - self.unitStats[2]
+          self.data.results[self.selectedResultIndex].dfc - self.unitStats[2]
         if (diff < 0):
             self.statGrid.SetCellValue(
               row=2, col=1, s="- " + str(abs(diff)) + " "
@@ -1087,7 +1094,6 @@ class PanelResults(wx.Panel):
             self.setLabelList[i].SetLabel(
               set_names[row[2]].ljust(18, " ") + "+" + str(row[3])
             )
-            #print(self.data.results[self.selectedResultIndex].runes[i])
             cursorStats = conn.execute(
               """
                 SELECT

+ 1 - 1
RuneOptimizerGUI/classes/PanelTeams.py

@@ -111,7 +111,7 @@ class PanelTeams(wx.Panel):
         self.teamList.InsertColumn(0, "Name", width=200)
         self.teamList.InsertColumn(1, "Prio.", width=40)
         self.populateTeamList(event=None)
-        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.teamSelected, self.teamList)
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.teamSelected, self.teamList)
 
         createTeamButton = wx.Button(
           parent=self, id=wx.ID_ANY, pos=(50, 500), size=(170, 40),

+ 3 - 3
RuneOptimizerGUI/classes/PanelUnits.py

@@ -127,7 +127,7 @@ class PanelUnits(wx.Panel):
         self.unitList.InsertColumn(0, "Name", width=120)
         self.unitList.InsertColumn(1, "Prio.", width=40)
         self.unitList.InsertColumn(2, "Sto.", width=30)
-        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.unitSelected, self.unitList)
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.unitSelected, self.unitList)
 
         # List filters
         filterBox = wx.StaticBox(
@@ -683,8 +683,8 @@ class PanelUnits(wx.Panel):
         if (currentCrr > 100):
             currentCrr = 100
         currentDmg = math.ceil(
-          (currentAtk * (100 - currentCrr) / 100) +
-          ((currentAtk + (currentAtk * currentCrd / 100)) * currentCrr / 100)
+          (currentAtk * (100 - currentCrr) / 100) +                   # Non-crit
+          (currentAtk * (      currentCrr  / 100) * currentCrd / 100) # Crit
         )
         self.statGrid.SetCellValue(row=9, col=1, s=str(currentDmg))
 

+ 9 - 8
RuneOptimizerGUI/classes/RuneOptimizerFrame.py

@@ -68,12 +68,15 @@ class RuneOptimizerFrame(wx.Frame):
           FROM info
         """)
         row = cursor.fetchone()
-        status = row[1] + ", Lv" + str(row[2]) + " (ID #" +str(row[0]) + ")."
-        status += " Last updated " + str(row[3])
-        if (row[4] == 1):
-            status += ". Modifications applied since."
+        if row is None:
+            status = "No data. Use the update menu."
         else:
-            status += ". No modifications applied since."
+            status = row[1] + ", Lv" + str(row[2]) + " (ID #" +str(row[0]) + ")."
+            status += " Last updated " + str(row[3])
+            if (row[4] == 1):
+                status += ". Modifications applied since."
+            else:
+                status += ". No modifications applied since."
         self.CreateStatusBar()
         self.SetStatusText(status)
 
@@ -168,9 +171,7 @@ class RuneOptimizerFrame(wx.Frame):
                 # do something here
                 if (dlg.updateDone == True and dlg.updateError == False):
                     print("Update succesfull!")
-
-            else:
-                print('Update cancelled')
+                    # TODO: Recalculate status bas message
 
     def updateFromSwdb(self, event):
         """

+ 0 - 3
RuneOptimizerGUI/classes/TabList.py

@@ -78,7 +78,6 @@ class TabList(wx.Listbook):
             if iconPath == "":
                 iconPath += "."
             iconPath += "\\res\\icon\\"
-        print("ICON PATH: " + iconPath)
         il = wx.ImageList(50, 50)
         for ico in [
           "units.png", "teams.png", "optimize.png", "results.png", "info.png"
@@ -127,7 +126,6 @@ class TabList(wx.Listbook):
         #old = event.GetOldSelection()
         #new = event.GetSelection()
         #sel = self.GetSelection()
-        #print('OnPageChanged,  old:%d, new:%d, sel:%d\n' % (old, new, sel))
         event.Skip()
 
     def OnPageChanging(self, event):
@@ -146,5 +144,4 @@ class TabList(wx.Listbook):
         #old = event.GetOldSelection()
         #new = event.GetSelection()
         #sel = self.GetSelection()
-        #print('OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel))
         event.Skip()

+ 17 - 19
src/RuneOptimizer/RuneOptimizer.c

@@ -29,6 +29,8 @@
 #include <unistd.h>
 #include <time.h>
 #include <json-c/json.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include "RuneOptimizer.h"
 #include "error/error.h"
 #include "db/db.c"
@@ -37,6 +39,8 @@
 #include "team/team.c"
 #include "unit/unit.c"
 #include "update/update.c"
+#include "player/player.c"
+#include "gui/gui.c"
 
 int main(int argc, char *argv[]){
     // Parse the arguments to find the command (index 1)
@@ -48,25 +52,9 @@ int main(int argc, char *argv[]){
     }
 
     // Set the global database location
-    int last_path_separator = -1;
-    for(int i = 0; i < strlen(argv[0]); i++){
-        if(argv[0][i] == '/' || argv[0][i] == '\\'){
-            last_path_separator = i + 1;
-        }
-    }
-    if (last_path_separator != -1){
-        strncpy(db_location, argv[0], last_path_separator);
-        db_location[last_path_separator] = '\0';
-        strcat(db_location, "data.sqlite");
-        strncpy(executable_location, argv[0], last_path_separator);
-        
-    }
-    else{
-        db_location[0] = '\0';
-        strcpy(db_location, "");
-        strcpy(executable_location, "");
-    }
-    
+    strcat(db_location, getenv("HOME"));
+    strcat(db_location, "/.local/share/RuneOptimizer/data.sqlite");
+
     int result = SUCCESS;
 
     // Version command
@@ -104,6 +92,16 @@ int main(int argc, char *argv[]){
         return(cmd_unit(argc - 2, argv + 2));
     }
 
+    // Player command.
+    else if (strcmp(argv[1], "player") == 0){
+        return(cmd_player());
+    }
+
+    // GUI command.
+    else if (strcmp(argv[1], "gui") == 0){
+        return(cmd_gui());
+    }
+
     // Any other command is an error
     else{
         fprintf(stderr, "Invalid command specified: %s\n", argv[1]);

+ 0 - 12
src/RuneOptimizer/RuneOptimizer.h

@@ -35,13 +35,6 @@
  */
 #define MAIL "i@inigovalentin.com"
 
-/**
- * Default path to the database.
- * 
- * It's the same directory as the executable.
- */
-#define DEFAULT_DB_PATH "data.sqlite"
-
 /**
  * Mock boolean value TRUE
  */
@@ -225,11 +218,6 @@ sqlite3 *db = NULL;
  */
 char db_location[512];
 
-/**
- * Path to the execcutable.
- */
-char executable_location[512];
-
 /**
  * Number of runes for a given list of runes.
  */

+ 197 - 0
src/RuneOptimizer/db/db.c

@@ -37,7 +37,197 @@ int db_close(){
     return(SUCCESS);
 }
 
+void db_create_directories(){
+    char dir_to_make[512];
+    int last_path_separator = -1;
+    for(int i = 0; i < strlen(db_location); i++){
+        if(db_location[i] == '/' || db_location[i] == '\\'){
+            last_path_separator = i + 1;
+        }
+    }
+    if (last_path_separator != -1){
+        strncpy(dir_to_make, db_location, last_path_separator);
+        dir_to_make[last_path_separator] = '\0';
+
+    }
+    mkdir(dir_to_make, 0777);
+    return;
+}
+
+int db_create_tables(){
+    // Table rune_stats
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS rune_stats("
+        "  rune CHAR(12), slot INT, stat INT, value INT, grind INT, enchant INT"
+        ");",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr,
+          "Unable to create table rune_stats: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_RUNE_STATS;
+    }
+
+    // Table runes
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS runes("
+        "  id CHAR(12),"
+        "  unit CHAR(12),"
+        "  type INT,"
+        "  slot INT,"
+        "  stars INT,"
+        "  level INT,"
+        "  quality INT,"
+        "  efficiency FLOAT,"
+        "  max_efficiency FLOAT,"
+        "  main_stat INT,"
+        "  current_hp_percent INT,"
+        "  current_atk_percent INT,"
+        "  current_def_percent INT,"
+        "  current_hp_flat INT,"
+        "  current_atk_flat INT,"
+        "  current_def_flat INT,"
+        "  current_spd INT,"
+        "  current_crr INT,"
+        "  current_crd INT,"
+        "  current_acc INT,"
+        "  current_res INT,"
+        "  lv12_hp_percent INT,"
+        "  lv12_atk_percent INT,"
+        "  lv12_def_percent INT,"
+        "  lv12_hp_flat INT,"
+        "  lv12_atk_flat INT,"
+        "  lv12_def_flat INT,"
+        "  lv12_spd INT,"
+        "  lv12_crr INT,"
+        "  lv12_crd INT,"
+        "  lv12_acc INT,"
+        "  lv12_res INT,"
+        "  lv15_hp_percent INT,"
+        "  lv15_atk_percent INT,"
+        "  lv15_def_percent INT,"
+        "  lv15_hp_flat INT,"
+        "  lv15_atk_flat INT,"
+        "  lv15_def_flat INT,"
+        "  lv15_spd INT,"
+        "  lv15_crr INT,"
+        "  lv15_crd INT,"
+        "  lv15_acc INT,"
+        "  lv15_res INT"
+        ");",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr, "Unable to create table runes: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_RUNES;
+    }
+
+    // Table units
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS units("
+        "  id CHAR(12),"
+        "  monster INT,"
+        "  name CHAR(128),"
+        "  stars INT,"
+        "  level INT,"
+        "  storage INT,"
+        "  base_hp INT,"
+        "  base_atk INT,"
+        "  base_def INT,"
+        "  base_spd INT,"
+        "  base_crr INT,"
+        "  base_crd INT,"
+        "  base_res INT,"
+        "  base_acc INT,"
+        "  current_hp INT,"
+        "  current_atk INT,"
+        "  current_def INT,"
+        "  current_spd INT,"
+        "  current_crr INT,"
+        "  current_crd INT,"
+        "  current_res INT,"
+        "  current_acc INT"
+        ");",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr,
+          "Unable to create table units: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_UNITS;
+    }
+
+    // Table info
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS info("
+        "  player_id CHAR(12),"
+        "  player_name CHAR(64),"
+        "  player_level INT,"
+        "  ts DATETIME,"
+        "  modified INT"
+        ");",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr,
+          "Unable to create table info: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_INFO;
+    }
+
+    // Tables teams and units_teams
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS "
+        "units_teams(unit CHAR(12), team CHAR(12));",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr,
+          "Unable to create table units_teams: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_UNITS_TEAMS;
+    }
+    if (
+      SUCCESS !=
+      db_execute(
+        "CREATE TABLE IF NOT EXISTS "
+        "teams(id CHAR(12), name CHAR(50), priority INT);",
+        NULL
+      )
+    ){
+        fprintf(
+          stderr,
+          "Unable to create table teams: %s\n", sqlite3_errmsg(db)
+        );
+        return ERROR_DB_CREATE_TEAMS;
+    }
+
+    return SUCCESS;
+}
+
 int db_open(char *path){
+    char exists = TRUE;
+    if (access( db_location, F_OK ) != 0) {
+        exists = FALSE;
+        db_create_directories();
+    }
 
     // Get the path, relative to the program
     char location[512];
@@ -55,6 +245,12 @@ int db_open(char *path){
         db_close();
         return(ERROR_DB_CANT_OPEN);
     }
+
+    // If the database was just created, create the tables
+    if (exists == FALSE){
+        db_create_tables();
+    }
+
     return(SUCCESS);
 }
 
@@ -63,6 +259,7 @@ int db_query(sqlite3_stmt **stmt, char query[], char *parameters[]){
     // If the connection has not been initialized, do it now
     if (NULL == db) db_open(NULL);
 
+
     if (SQLITE_OK != sqlite3_prepare_v2(db, query, -1, stmt, 0)) {
         fprintf(
           stderr, "ERROR executing query '%s': %s\n", query, sqlite3_errmsg(db)

+ 10 - 0
src/RuneOptimizer/db/db.h

@@ -27,6 +27,16 @@
  */
 int db_close();
 
+/**
+ * Ensures that the directory structure for the database exist.
+ */
+void db_create_directories();
+
+/**
+ * Creates all database tables.
+ */
+int db_create_tables();
+
 /**
  * Opens the database connection.
  * 

+ 6 - 0
src/RuneOptimizer/error/error.h

@@ -347,6 +347,12 @@
  */
 #define ERROR_DB_SELECT_RUNE -237
 
+/**
+ * Error indicating that there is no info in the database. The update command
+ * must be run to populate the database.
+ */
+#define ERROR_DB_SELECT_INFO -238
+
 /**
  * Error indicating that a new ID for a team could not be retrieved from the
  * database. This indcates that no teams exists yet, so it's safe to pick 1 for

+ 27 - 0
src/RuneOptimizer/gui/gui.c

@@ -0,0 +1,27 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file gui.c
+ * Implementation of the gui command.
+ */
+
+#include "gui.h"
+
+int cmd_gui(){
+    db_open(NULL);
+}

+ 28 - 0
src/RuneOptimizer/gui/gui.h

@@ -0,0 +1,28 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file gui.h
+ * Declaration of the gui command.
+ */
+
+/**
+ * Makes sure that the db is set up for the gui.
+ *
+ * @return SUCCESS or ERROR_BD_CANT_OPEN if the database cant be created.
+ */
+int cmd_gui();

+ 9 - 0
src/RuneOptimizer/help/help.c

@@ -29,6 +29,15 @@ void cmd_help(){
     printf("  RuneOptimizer [command] [options]\n");
     printf("\n\n  Command: help\n");
     printf("\n    Display this help text and exists. It has no options.\n");
+    printf("\n\n  Command: player\n");
+    printf("\n    Displays info about the player. It takes no options.\n");
+    printf("\n\n  Command: gui\n");
+    printf("\n    Sets up the database to be used by the GUI. No need to run it manually.\n");
+    printf("\n    Usage\n");
+    printf("\n\n  Command: unit\n");
+    printf("\n    View info about units.\n");
+    printf("\n    Usage\n");
+    printf("\n    Usage\n");
     printf("\n\n  Command: update\n");
     printf("\n    Updates the information and builds a database. Usage.\n");
     printf("    RuneOptimizer update [source] [options]\n");

+ 33 - 76
src/RuneOptimizer/optimize/optimize.c

@@ -61,11 +61,8 @@ unsigned short optimize_calculate_dmg(
         crr_capped = 100.0f;
     }
     unsigned short dmg = ceil(
-      (((float) atk) * (100.0f - crr_capped) / 100.0f) +
-      (
-        (((float) atk) + (((float) atk) * ((float) crd) / 100.0f)) *
-        crr_capped / 100.0f
-      )
+      ((float)atk * ((100 - crr_capped) / 100)) +              // Non-crit
+      ((float)atk * (crr_capped / 100) * ((float)crd / 100))   // Crit
     );
     return dmg;
 }
@@ -104,13 +101,16 @@ int cmd_optimize(int argc, char *argv[]){
     min_stats.acc = 1;
     min_stats.ehp = 1;
     min_stats.dmg = 1;
-    char excluded_teams[64][64];
-    char excluded_units[64][64];
-    for (int i = 0; i < 64; i++){
-        excluded_teams[i][0] = '\0';
-        excluded_units[i][0] = '\0';
-    }
 
+    char excluded_teams[128][8];
+    char excluded_units[128][8];
+    for (int i = 0; i < 128; i ++){
+        for (int j = 0; j < 8; j ++){
+            excluded_teams[i][j] = '\0';
+            excluded_units[i][j] = '\0';
+            //(char volatile) excluded_units[i][j] = excluded_teams[i][j];
+        }
+    }
     // Parse arguments from the command line.
     status = optimize_parse_arguments(
       argc, args, id, &requested_level,
@@ -142,7 +142,6 @@ int cmd_optimize(int argc, char *argv[]){
         fprintf(stderr, "Invalid rune set combination.\n");
         return ERROR_INPUT_OPTIMIZE_INCOMPLETE_SETS;
     }
-
     // Create queries for the database.
     char query_even[2500];
     char query_odd[2500];
@@ -258,69 +257,27 @@ int cmd_optimize(int argc, char *argv[]){
         set_count.tolerance = 0;
         for (int i = 1; i < 7; i ++){
             switch (runes[i][index[i]].set){
-                case ENERGY:
-                    set_count.energy ++;
-                    break;
-                case GUARD:
-                    set_count.guard ++;
-                    break;
-                case SWIFT:
-                    set_count.swift ++;
-                    break;
-                case BLADE:
-                    set_count.blade ++;
-                    break;
-                case RAGE:
-                    set_count.rage ++;
-                    break;
-                case FOCUS:
-                    set_count.focus ++;
-                    break;
-                case ENDURE:
-                    set_count.endure ++;
-                    break;
-                case FATAL:
-                    set_count.fatal ++;
-                    break;
-                case DESPAIR:
-                    set_count.despair ++;
-                    break;
-                case VAMPIRE:
-                    set_count.vampire ++;
-                    break;
-                case VIOLENT:
-                    set_count.violent ++;
-                    break;
-                case NEMESIS:
-                    set_count.nemesis ++;
-                    break;
-                case WILL:
-                    set_count.will ++;
-                    break;
-                case SHIELD:
-                    set_count.shield ++;
-                    break;
-                case REVENGE:
-                    set_count.revenge ++;
-                    break;
-                case DESTROY:
-                    set_count.destroy ++;
-                    break;
-                case FIGHT:
-                    set_count.fight ++;
-                    break;
-                case DETERMINATION:
-                    set_count.determination ++;
-                    break;
-                case ENHANCE:
-                    set_count.enhance ++;
-                    break;
-                case ACCURACY:
-                    set_count.accuracy ++;
-                    break;
-                case TOLERANCE:
-                    set_count.tolerance ++;
-                    break;
+                case ENERGY:        set_count.energy ++;        break;
+                case GUARD:         set_count.guard ++;         break;
+                case SWIFT:         set_count.swift ++;         break;
+                case BLADE:         set_count.blade ++;         break;
+                case RAGE:          set_count.rage ++;          break;
+                case FOCUS:         set_count.focus ++;         break;
+                case ENDURE:        set_count.endure ++;        break;
+                case FATAL:         set_count.fatal ++;         break;
+                case DESPAIR:       set_count.despair ++;       break;
+                case VAMPIRE:       set_count.vampire ++;       break;
+                case VIOLENT:       set_count.violent ++;       break;
+                case NEMESIS:       set_count.nemesis ++;       break;
+                case WILL:          set_count.will ++;          break;
+                case SHIELD:        set_count.shield ++;        break;
+                case REVENGE:       set_count.revenge ++;       break;
+                case DESTROY:       set_count.destroy ++;       break;
+                case FIGHT:         set_count.fight ++;         break;
+                case DETERMINATION: set_count.determination ++; break;
+                case ENHANCE:       set_count.enhance ++;       break;
+                case ACCURACY:      set_count.accuracy ++;      break;
+                case TOLERANCE:     set_count.tolerance ++;     break;
             }
         }
 
@@ -409,7 +366,7 @@ int cmd_optimize(int argc, char *argv[]){
             // Cap cappable stats
             if (stats.crr > 100) stats.crr = 100;
             if (stats.res > 100) stats.res = 100;
-            if (stats.crr > 85) stats.acc = 85;
+            if (stats.acc > 85) stats.acc = 85;
 
             // Calculated stats
             stats.ehp = optimize_calculate_ehp(stats.hp, stats.def);

+ 3 - 3
src/RuneOptimizer/optimize/optimize.h

@@ -185,7 +185,7 @@ int optimize_get_runes(
 int optimize_parse_arguments(
   int argc, char argv[][128], char id[64], char *level,
   int *sets, int *stats, struct Stats *min_stats, char *gui,
-  char *storage, char excluded_teams[64][64], char excluded_units[64][64]
+  char *storage, char excluded_teams[128][8], char excluded_units[128][8]
 );
 
 /**
@@ -242,7 +242,7 @@ int optimize_print_gui(Result results[5000], int result_count);
  */
 void optimize_query_for_even_slots(
   char level, int* sets, int* stats,
-  char excluded_teams[64][64], char excluded_units[64][64],
+  char excluded_teams[128][8], char excluded_units[128][8],
   char storage, char unit_id[64], char query[2500]
 );
 
@@ -259,7 +259,7 @@ void optimize_query_for_even_slots(
  */
 void optimize_query_for_odd_slots(
   char level, int* sets,
-  char excluded_teams[64][64], char excluded_units[64][64],
+  char excluded_teams[128][8], char excluded_units[128][8],
   char storage, char unit_id[64], char query[2500]
 );
 

+ 0 - 1
src/RuneOptimizer/optimize/optimize_fetch_unit.c

@@ -32,7 +32,6 @@ int optimize_fetch_unit(char id[64], struct Unit *unit){
       "FROM units WHERE id = ? OR name = ?",
       parameters
     );
-
     // Fetch just one line
     int step = sqlite3_step(stmt_unit);
 

+ 14 - 5
src/RuneOptimizer/optimize/optimize_parse_arguments.c

@@ -23,14 +23,21 @@
 int optimize_parse_arguments(
   int argc, char argv[][128], char id[64], char *level,
   int *sets, int *stats, struct Stats *min_stats, char *gui,
-  char *storage, char excluded_teams[64][64], char excluded_units[64][64]
+  char *storage, char excluded_teams[128][8], char excluded_units[128][8]
 ){
     // Get unit identifier
     if (argc < 1){
         fprintf(stderr, "No unit specified for optimization\n");
         return ERROR_INPUT_OPTIMIZE_NO_UNIT;
     }
-    strncpy(id, argv[0], 64);
+    //strncpy(id, argv[0], 64);
+    if (strlen(argv[0]) > 63){
+        strncpy(id, argv[0], 63);
+        id[63] = '\0';
+    }
+    else{
+        strcpy(id, argv[0]);
+    }
 
     // Assign default values for non-value arguments
     *storage = 0;
@@ -74,7 +81,7 @@ int optimize_parse_arguments(
                 // Separate string by commas
                 int j = 0;
                 // Returns first token
-                char opt_list[64];
+                char opt_list[128];
                 strcpy(opt_list, argv[i + 1]);
                 char *token = strtok(opt_list, ",");
 
@@ -105,11 +112,13 @@ int optimize_parse_arguments(
                 // Separate string by commas
                 int j = 0;
                 // Returns first token
-                char *token = strtok(argv[i + 1], ",");
+                char opt_list[128];
+                strcpy(opt_list, argv[i + 1]);
+                char *token = strtok(opt_list, ",");
 
                 // Keep printing tokens while one of the
                 // delimiters present in the list, or until its complete
-                while (token != NULL && j < 3){
+                while (token != NULL){
                     strcpy(excluded_units[j], token);
                     token = strtok(NULL, ",");
                     j ++;

+ 4 - 4
src/RuneOptimizer/optimize/optimize_query.c

@@ -23,7 +23,7 @@
 
 void optimize_query_for_even_slots(
   char level, int* sets, int* stats,
-  char excluded_teams[64][64], char excluded_units[64][64],
+  char excluded_teams[128][8], char excluded_units[128][8],
   char storage, char unit_id[64], char query[2500]
 ){
     char column[10];
@@ -94,7 +94,7 @@ void optimize_query_for_even_slots(
     strcat(
       query, "' OR unit NOT IN (SELECT unit FROM units_teams WHERE team IN("
     );
-    for (int i = 0; i < 64 && excluded_teams[i][0] != '\0'; i ++){
+    for (int i = 0; i < 128 && excluded_teams[i][0] != '\0'; i ++){
         strcat(query, "'");
         strcat(query, excluded_teams[i]);
         strcat(query, "',");
@@ -104,7 +104,7 @@ void optimize_query_for_even_slots(
     strcat(query, " AND (unit = '' OR unit = '");
     strcat(query, unit_id);
     strcat(query, "' OR unit NOT IN (");
-    for (int i = 0; i < 64 && excluded_units[i][0] != '\0'; i ++){
+    for (int i = 0; i < 128 && excluded_units[i][0] != '\0'; i ++){
         strcat(query, "'");
         strcat(query, excluded_units[i]);
         strcat(query, "',");
@@ -116,7 +116,7 @@ void optimize_query_for_even_slots(
 
 void optimize_query_for_odd_slots(
   char level, int* sets,
-  char excluded_teams[64][64], char excluded_units[64][64],
+  char excluded_teams[128][8], char excluded_units[128][8],
   char storage, char unit_id[64], char query[2500]
 ){
     char column[10];

+ 65 - 0
src/RuneOptimizer/player/player.c

@@ -0,0 +1,65 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file player.c
+ * Implementation of the player command.
+ */
+
+#include "player.h"
+
+int cmd_player(){
+    sqlite3_stmt *stmt;
+    db_query(
+      &stmt,
+      "SELECT player_id, player_name, player_level, ts, modified, "
+      "(SELECT count(id) FROM units) AS total_units, "
+      "(SELECT count(id) FROM units WHERE level = 40) AS units_max, "
+      "(SELECT count(unit) FROM ( "
+      "  SELECT DISTINCT unit FROM runes WHERE unit != '') "
+      ") AS units_with_runes, "
+      "(SELECT count(id) FROM runes) AS total_runes, "
+      "(SELECT count(id) FROM runes WHERE unit != '') AS runes_assigned, "
+      "(SELECT count(id) FROM runes WHERE unit = '') AS runes_unassigned, "
+      "(SELECT count(id) FROM teams) AS total_teams, "
+      "(SELECT count(unit) FROM ( "
+      "  SELECT DISTINCT unit FROM units_teams) "
+      ") AS units_in_teams "
+      "FROM info;",
+      NULL
+    );
+    if (SQLITE_ROW != sqlite3_step(stmt)){
+        fprintf(stderr, "No player info. Use the 'update' command.\n");
+        return(ERROR_DB_SELECT_INFO);
+    }
+    printf("Player name:      %s\n", sqlite3_column_text(stmt, 1));
+    printf("Player ID:        %s\n", sqlite3_column_text(stmt, 0));
+    printf("Level:            %s\n", sqlite3_column_text(stmt, 2));
+    printf("Last updated:     %s\n", sqlite3_column_text(stmt, 3));
+    printf("Modified:        ");
+    if (1 == sqlite3_column_int(stmt, 4)) printf("Yes\n");
+    else printf(" No\n");
+    printf("Total units:      %4d\n", sqlite3_column_int(stmt, 5));
+    printf("Lv.40 units:      %4d\n", sqlite3_column_int(stmt, 6));
+    printf("Units with runes: %4d\n", sqlite3_column_int(stmt, 7));
+    printf("Total runes:      %4d\n", sqlite3_column_int(stmt, 8));
+    printf("Assigned runes:   %4d\n", sqlite3_column_int(stmt, 9));
+    printf("Unassigned runes: %4d\n", sqlite3_column_int(stmt, 10));
+    printf("Total teams:      %4d\n", sqlite3_column_int(stmt, 11));
+    printf("Units in teams:   %4d\n", sqlite3_column_int(stmt, 12));
+    return(SUCCESS);
+}

+ 28 - 0
src/RuneOptimizer/player/player.h

@@ -0,0 +1,28 @@
+/*
+ * This file is part of RuneOptimizer.
+ *
+ * RuneOptimizer 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.
+ *
+ * RuneOptimizer 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
+ * RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file player.h
+ * Declaration of the player command.
+ */
+
+/**
+ * Prints info about the player.
+ *
+ * @return SUCCESS or ERROR_BD_SELECT_INFO if there is no data in the database.
+ */
+int cmd_player();

+ 2 - 1
src/RuneOptimizer/team/team_create.c

@@ -39,7 +39,7 @@ int team_create_get_next_id(){
 
     // Get the new id for the team
     db_query(
-      &stmt_id, "SELECT max(CAST(id AS INTEGER)) + 1 AS id FROM teams;", NULL
+      &stmt_id, "SELECT max(CAST(id AS INTEGER)) + 1 AS id FROM teams", NULL
     );
     if (SQLITE_ROW != sqlite3_step(stmt_id)){
         fprintf(stderr, "Unable to read teams: %s\n", sqlite3_errmsg(db));
@@ -50,6 +50,7 @@ int team_create_get_next_id(){
     // Get all parameters and convert to char.
     new_id = sqlite3_column_int(stmt_id, 0);
     sqlite3_finalize(stmt_id);
+    return(new_id);
 }
 
 int team_create(int argc, char *argv[]){

+ 1 - 1
src/RuneOptimizer/team/team_delete.c

@@ -68,7 +68,7 @@ int team_delete(int argc, char *argv[]){
 
     // Delete from table units_teams.
     if (
-      SUCCESS != db_execute("DELETE FROM units_teams WHERE id = ?", parameters)
+      SUCCESS != db_execute("DELETE FROM units_teams WHERE team = ?", parameters)
     ){
         fprintf(
           stderr, "Unable to delete units in team: %s\n", sqlite3_errmsg(db)

+ 4 - 1
src/RuneOptimizer/unit/unit_list.c

@@ -45,7 +45,7 @@ int unit_list(char *search){
     int units_found = 0;
     int unit_has_runes = FALSE;
     char tmp[32];
-    while (SQLITE_ROW ==sqlite3_step(stmt_units)){
+    while (SQLITE_ROW == sqlite3_step(stmt_units)){
         char unit_name[14];
 
         // Print a separator between units
@@ -318,5 +318,8 @@ int unit_list(char *search){
         }
     }
     sqlite3_finalize(stmt_units);
+    if (units_found == 0){
+        printf("No units found\n");
+    }
     return(units_found);
 }

+ 1 - 1
src/RuneOptimizer/update/update_db_tables.c

@@ -197,7 +197,7 @@ int update_db_tables(unsigned char clear_teams){
           SUCCESS !=
           db_execute(
             "CREATE TABLE IF NOT EXISTS "
-            "teams(unit CHAR(12), name CHAR(50), priority INT);",
+            "teams(id CHAR(12), name CHAR(50), priority INT);",
             NULL
           )
         ){

+ 0 - 5
src/RuneOptimizer/update/update_json_rune.c

@@ -243,11 +243,6 @@ int update_json_rune(json_object *rune_json, char *unit_id){
     float efficiency = 0.0f;
     float max_efficiency = 0.0f;
     update_efficiency(query_parameters[0], &efficiency, &max_efficiency);
-    printf("CALCULATED EFF: %f\n", efficiency);
-    //char eff_c[7];
-    //char max_eff_c[7];
-    //sprintf(eff_c, "%6.2f", efficiency);
-    //sprintf(max_eff_c, "%6.2f", max_efficiency);
     strcpy(query_parameters[2], query_parameters[0]); //ID
     sprintf(query_parameters[0], "%6.2f", efficiency);
     sprintf(query_parameters[1], "%6.2f", max_efficiency);