diff --git a/.gitignore b/.gitignore index a0fb0136b..f594e8be6 100644 --- a/.gitignore +++ b/.gitignore @@ -427,3 +427,4 @@ DerivedData/ ### Nintendo Switch ### exefs/main +/out/isenseconfig/CPI-Debug diff --git a/CMake/cpigamesh_defs.cmake b/CMake/cpigamesh_defs.cmake new file mode 100644 index 000000000..1d23141af --- /dev/null +++ b/CMake/cpigamesh_defs.cmake @@ -0,0 +1,27 @@ +set(NONET ON) +set(PREFILL_PLAYER_NAME ON) +set(HAS_KBCTRL 1) +set(BINARY_RELEASE ON) +set(LTO ON) +set(DIST ON) +set(DEBUG OFF) +set(ASAN OFF) +set(UBSAN OFF) +set(KBCTRL_BUTTON_DPAD_LEFT SDLK_LEFT) +set(KBCTRL_BUTTON_DPAD_RIGHT SDLK_RIGHT) +set(KBCTRL_BUTTON_DPAD_UP SDLK_UP) +set(KBCTRL_BUTTON_DPAD_DOWN SDLK_DOWN) +set(KBCTRL_BUTTON_X SDLK_i) +set(KBCTRL_BUTTON_Y SDLK_u) +set(KBCTRL_BUTTON_B SDLK_k) +set(KBCTRL_BUTTON_A SDLK_j) +set(KBCTRL_BUTTON_RIGHTSHOULDER SDLK_l) +set(KBCTRL_BUTTON_LEFTSHOULDER SDLK_h) +set(KBCTRL_BUTTON_AXIS_TRIGGERLEFT SDLK_y) +set(KBCTRL_BUTTON_AXIS_TRIGGERRIGHT SDLK_o) +set(KBCTRL_BUTTON_LEFTSTICK SDLK_PAGEUP) +set(KBCTRL_BUTTON_RIGHTSTICK SDLK_PAGEDOWN) +set(KBCTRL_BUTTON_START SDLK_RETURN) +set(KBCTRL_BUTTON_BACK SDLK_SPACE) + +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") diff --git a/CMakeLists.txt b/CMakeLists.txt index 72cb1600c..ed03c6d45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,10 @@ if(RETROFW) include(retrofw_defs) endif() +if(CPIGAMESH) + include(cpigamesh_defs) +endif() + if(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD OR ${CMAKE_SYSTEM_NAME} STREQUAL OpenBSD) set(ASAN OFF) set(UBSAN OFF) diff --git a/Packaging/cpi-gamesh/Devilution.png b/Packaging/cpi-gamesh/Devilution.png new file mode 100644 index 000000000..1bb11fbaf Binary files /dev/null and b/Packaging/cpi-gamesh/Devilution.png differ diff --git a/Packaging/cpi-gamesh/__init__.py b/Packaging/cpi-gamesh/__init__.py new file mode 100644 index 000000000..b6187db3b --- /dev/null +++ b/Packaging/cpi-gamesh/__init__.py @@ -0,0 +1,291 @@ + +import pygame +import validators +import commands +from UI.constants import Width,Height,ICON_TYPES,RUNSYS +from UI.simple_name_space import SimpleNamespace +from UI.page import Page +from UI.label import Label +from UI.icon_item import IconItem +from UI.keys_def import CurKeys, IsKeyMenuOrB, IsKeyStartOrA +from UI.skin_manager import MySkinManager +from UI.lang_manager import MyLangManager + +from libs.DBUS import is_wifi_connected_now,get_wifi_ip +from collections import deque +from enum import Enum +import os +import subprocess +import pipes + +class DevilutionPage(Page): + _FootMsg = ["Nav","Check","Upgrade","Back","Play"] + + _GameName = "devilutionX" + _GamePath = "/home/cpi/games/devilutionX" + _GameExecutable = _GamePath + "/bin/devilutionx" + _GameExecutableRevision = _GameExecutable + ".rev" + _GameBuildScript = _GamePath + "/Packaging/cpi-gamesh/build.sh -t " + pipes.quote(os.path.dirname(os.path.abspath( __file__ ))) + _GamePNG = _GamePath + "/Packaging/cpi-gamesh/Devilution.png" + _DevilutionDiabdatmpq = "/home/cpi/.local/share/diasurgical/devilution/diabdat.mpq" + _DevilutionDiabdatmpqPresent = False + _GameInstalled = False + + _CiteNewUpdate = "Ahh... fresh meat!" + _CiteCheckUpdate = "Lets search the books..." + _CiteWelcome = "Well, what can I do for ya?" + _CiteCompiling = "Stay awhile and listen." + _CiteDone = "You must venture through the portal..." + _CiteFailed = "Game Over. Better luck next time!" + + _GitURL = "https://github.com/danie1kr/devilutionX.git" + _GitBranch = "gameshell" + _GitRevision = "" + + _GameIcon = None + + _Process = None + _Labels = {} + _Coords = {} + + _ListFontObj = MyLangManager.TrFont("varela13") + _URLColor = MySkinManager.GiveColor('URL') + _TextColor = MySkinManager.GiveColor('Text') + + def __init__(self): + Page.__init__(self) + + def InitLabels(self): + + y = 15 + x = 11 + yInc = 19 + xGitRefLabelWidth = 48 + + labels = \ + [["greeting",self._CiteWelcome, self._TextColor, x, y], + ["status", "", self._URLColor, x, y + 72-yInc], + ["comment", "", self._TextColor, x, y + 72], + ["console_out","",self._URLColor, x, y + 72 + yInc], + ["label_rev","GIT Revisions: ", self._TextColor, x, 132], + ["label_git_rev","Source: ", self._TextColor, x, 151], + ["content_git_rev","", self._URLColor, x + xGitRefLabelWidth, 151], + ["label_bin_rev","Bin: ", self._TextColor, x, 170], + ["content_bin_rev","", self._URLColor, x + xGitRefLabelWidth, 170] + ] + + for i in labels: + l = Label() + l.SetCanvasHWND(self._CanvasHWND) + l.Init(i[1],self._ListFontObj) + l.SetColor(i[2]) + self._Labels[ i[0] ] = l + + c = SimpleNamespace() + c.x = i[3] + c.y = i[4] + self._Coords[ i[0] ] = c + + def GitGetRevision(self): + if not os.path.exists(self._GamePath): + return "game not installed" + process = subprocess.Popen("cd " + pipes.quote(self._GamePath) + "; git rev-parse HEAD",stdout=subprocess.PIPE,stderr=subprocess.STDOUT, shell=True) + self._GitRevision = process.communicate()[0].strip() + process.wait() + return self._GitRevision + + def ExectuableGetRevision(self): + try: + with open(self._GameExecutableRevision, 'r') as file: + executableRevsion = file.read().replace('\n', '') + return executableRevsion + except: + return "unknown" + + def InitGameDirectory(self): + try: + os.makedirs(self._GamePath) + except: + pass + self.StartShellProcess("cd " + pipes.quote(self._GamePath) + "; git clone --single-branch --branch " + pipes.quote(self._GitBranch) + " " + pipes.quote(self._GitURL) + " .") + + def CheckDevilutionMPQ(self): + self._DevilutionDiabdatmpqPresent = os.path.isfile(self._DevilutionDiabdatmpq) + + def CheckGameInstalled(self): + self._GameInstalled = os.path.isfile(self._GameExecutable) + + def UpdateFootMsg(self): + if not self._GameInstalled: + self._FootMsg = ["Nav","","Install","Back",""] + self.UpdateLabel("status", "GIT Upgrade") + self.UpdateLabel("comment", "Press X to install") + elif not self._DevilutionDiabdatmpqPresent: + self._FootMsg = ["Nav","","Upgrade","Back","Re-check"] + self.UpdateLabel("status", "Gamefile diabdat.mpq missing") + self.UpdateLabel("comment", "see readme") + else: + self._FootMsg = ["Nav","","Upgrade","Back","Play"] + self.UpdateLabel("status", "Ready") + self.UpdateLabel("comment", self._CiteDone) + + def Init(self): + Page.Init(self) + + if self._Screen != None: + if self._Screen._CanvasHWND != None and self._CanvasHWND == None: + self._HWND = self._Screen._CanvasHWND + self._CanvasHWND = pygame.Surface( (self._Screen._Width,self._Screen._Height) ) + + if os.path.isfile(self._GamePNG): + self._GameIcon = IconItem() + self._GameIcon._ImageName = self._GamePNG + self._GameIcon._MyType = ICON_TYPES["STAT"] + self._GameIcon._Parent = self + self._GameIcon.Adjust(290,70,128,128,0) + + self.InitLabels() + self.CheckDevilutionMPQ() + self.CheckGameInstalled() + self.UpdateFootMsg() + + self.UpdateLabel("content_git_rev", self.GitGetRevision(), 24) + self.UpdateLabel("content_bin_rev", self.ExectuableGetRevision(), 24) + + def UpdateLabel(self, label, msg, maxLen=38): + print(label + ": " + msg) + if len(msg) > maxLen: + m = msg[:maxLen] + "..." + else: + m = msg + self._Labels[label].SetText(m) + + def StartShellProcess(self, cmd): + print("StartShellProcess " + cmd) + proc = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.STDOUT, shell=True) + while(True): + line = proc.stdout.readline() + if line: + self.UpdateLabel("console_out", line.strip(), 48) + self._Screen.Draw() + self._Screen.SwapAndShow() + if line == '' and proc.poll() is not None: + break + self.UpdateLabel("console_out", "") + self._Screen.Draw() + self._Screen.SwapAndShow() + + def GitUpgrade(self): + self.UpdateLabel("status", "GIT Upgrade") + self.UpdateLabel("comment", self._CiteCheckUpdate) + + curRev = "unset" + if not os.path.exists(self._GamePath): + self.InitGameDirectory() + else: + curRev = self.GitGetRevision() + self.StartShellProcess("cd " + pipes.quote(self._GamePath) + "; git pull") + + self._GitRevision = self.GitGetRevision() + self.UpdateLabel("content_git_rev", self._GitRevision, 24) + + if curRev != self._GitRevision: + self.UpdateLabel("comment", self._CiteNewUpdate) + else: + self.UpdateLabel("comment", self._CiteDone) + + self._Screen.Draw() + self._Screen.SwapAndShow() + + def GitExectuableIsGitRevision(self): + return self.GitGetRevision() == self.ExectuableGetRevision() + + def Build(self): + self.UpdateLabel("status", "Building") + self.StartShellProcess(self._GameBuildScript) + + def UpgradeAndBuild(self): + self.GitUpgrade() + self.UpdateLabel("comment", self._CiteCompiling) + self._Screen.Draw() + self._Screen.SwapAndShow() + if not self.GitExectuableIsGitRevision(): + self.Build() + + self.UpdateLabel("content_git_rev", self.GitGetRevision(), 24) + self.UpdateLabel("content_bin_rev", self.ExectuableGetRevision(), 24) + + self.UpdateLabel("status", "Done") + if self.GitExectuableIsGitRevision(): + self.UpdateLabel("comment", self._CiteDone) + else: + self.UpdateLabel("comment", self._CiteFailed) + + self.CheckDevilutionMPQ() + self.CheckGameInstalled() + self.UpdateFootMsg() + + self._Screen.Draw() + self._Screen.SwapAndShow() + + def KeyDown(self,event): + + if IsKeyMenuOrB(event.key): + self.ReturnToUpLevelPage() + self._Screen.Draw() + self._Screen.SwapAndShow() + + if self._DevilutionDiabdatmpqPresent and self._GameInstalled: + if IsKeyStartOrA(event.key): + pygame.event.post( pygame.event.Event(RUNSYS, message=self._GameExecutable)) + if event.key == CurKeys["X"]: + self.UpgradeAndBuild() + elif not self._GameInstalled: + if event.key == CurKeys["X"]: + self.UpgradeAndBuild() + elif not self._DevilutionDiabdatmpqPresent: + if IsKeyStartOrA(event.key): + self.CheckDevilutionMPQ() + self.CheckGameInstalled() + self.UpdateFootMsg() + self._Screen.Draw() + self._Screen.SwapAndShow() + + def Draw(self): + self.ClearCanvas() + + if self._GameIcon != None: + self._GameIcon.Draw() + + for i in self._Labels: + if i in self._Coords: + self._Labels[i].NewCoord( self._Coords[i].x, self._Coords[i].y) + self._Labels[i].Draw() + + if self._HWND != None: + self._HWND.fill(MySkinManager.GiveColor('White')) + self._HWND.blit(self._CanvasHWND,(self._PosX,self._PosY,self._Width, self._Height ) ) + + +class APIOBJ(object): + + _Page = None + def __init__(self): + pass + def Init(self,main_screen): + self._Page = DevilutionPage() + self._Page._Screen = main_screen + self._Page._Name ="devilutionX" + self._Page.Init() + + def API(self,main_screen): + if main_screen !=None: + main_screen.PushPage(self._Page) + main_screen.Draw() + main_screen.SwapAndShow() + +OBJ = APIOBJ() +def Init(main_screen): + OBJ.Init(main_screen) +def API(main_screen): + OBJ.API(main_screen) diff --git a/Packaging/cpi-gamesh/build.sh b/Packaging/cpi-gamesh/build.sh new file mode 100644 index 000000000..bba6b4888 --- /dev/null +++ b/Packaging/cpi-gamesh/build.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Building for target: clockwork pi GameSH" + +declare -r DIR="$(dirname "${BASH_SOURCE[0]}")" +cd "$DIR" +declare -r ABSDIR="$(pwd)" + +usage() { + echo "${BASH_SOURCE[0]} [--target /path/to/devliution/in/gameshell/menu] [--usage]" + exit 1 +} + +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -t|--target) + TARGET="$2" + shift # past argument + shift # past value + ;; + --help|-h|--usage|-u) + usage + shift # past argument + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +install_deps() { + sudo apt install -y cmake libsdl2-ttf-dev libsdl2-mixer-dev +} + +main() { + install_deps + build + install +} + +build() { + mkdir -p ../../build + cd ../../build + rm -f CMakeCache.txt + + local -a defs=(-DCPIGAMESH=ON) + cmake .. ${defs[@]} + make -j $(getconf _NPROCESSORS_ONLN) + cd - +} + +install() { + mkdir -p /home/cpi/games/devilutionX/bin + cp /home/cpi/games/devilutionX/build/devilutionx /home/cpi/games/devilutionX/bin + git rev-parse HEAD > /home/cpi/games/devilutionX/bin/devilutionx.rev + + if [ -z ${TARGET+x} ]; then + local target_dir="25_devilutionX" + else + local target_dir=${TARGET#"/home/cpi/apps/Menu/"} + fi + + local script_dir="/home/cpi/apps/Menu/$target_dir" + + local target_dir_base=`basename "$target_dir"` + local target_dir_dir=`dirname "$target_dir"` + local icon_name="${target_dir_dir}/${target_dir_base#*_}" + + local icon_dir="/home/cpi/launcher/skin/default/Menu/GameShell/${icon_name}.png" + + echo $target_dir + echo $script_dir + echo $target_dir_base + echo $target_dir_dir + echo $icon_name + echo $icon_dir + + mkdir -p $script_dir + cp __init__.py $script_dir + cp Devilution.png $icon_dir +} + +main diff --git a/Packaging/cpi-gamesh/readme.md b/Packaging/cpi-gamesh/readme.md new file mode 100644 index 000000000..80d23ef0c --- /dev/null +++ b/Packaging/cpi-gamesh/readme.md @@ -0,0 +1,42 @@ + +### devilutionX package for ClockworkPi GameShell +For more information about this device see [here](https://www.clockworkpi.com/gameshell). + +## Install devilutionX on the CPi GameShell + +To install devilutionX, just copy the [\_\_init__.py](https://raw.githubusercontent.com/danie1kr/devilutionX/gameshell/Packaging/cpi-gamesh/__init__.py) to a newly created folder under /home/cpi/apps/Menu and run it from the menu. The folder then symbolizes the devilutionX icon. +From this menu, you can press 'X' to clone the git repository for devilutionX and compile the code. Dependencies are installed automatically (cmake and SDL development packages). +Once installed, 'X' pulls the updated code and does the compiling. Note that any changes made locally to the source are reverted before pulling. + +Currently, the code is cloned from [a fork](https://github.com/danie1kr/devilutionX.git), branch "gameshell". + +When the compile is finished and the diabdat.mpq is in place at '/home/cpi/.local/share/diasurgical/devilution/', you can play the game. Enjoy! + +## Play devilutionX on the CPi GameShell + +When the game is compiled and the diabdat.mpq is in place, you can press "A" in the devilutionX menu to play. + +The following key mapping is in place. +Unfortunately, the mapping is not trivial as devilutionX and GameShell use their own not(yet) compatible shift mechanism. +The mapping is based on the standard GameShell keyboard layout, devilutionX uses the Nintento mapping. + +| GameShell Key | Keyboard Key | devilutionX Key | devilutionX Action | +| --------------------- | --------------- | ------------------------- | ------------------ | +| D-Pad | Arrows | D-Pad | move hero | +| Select + D-Pad | Space + Arrows | Back + D-Pad | simulate mouse | +| B | K | A | attack nearby enemies, talk to townspeople and merchants, pickup/place items in the inventory, OK while in main menu | +| A | J | B | select spell, back while in menus | +| Y | I | X | pickup items, open nearby chests and doors, use item in the inventory | +| X | U | Y | cast spell, delete character while in main menu | +| Select | Space | Select | Select | +| Start | Enter | Start | game menu, skip movie | +| LK1 | H | L1 | use health item from belt | +| Start + Left | Enter + Left | Start + Left | character sheet | +| Start + Right | Enter + Right | Start + Right | inventory | +| LK5 | L | R1 | use mana item from belt | +| Start + Down | Enter + Down | Start + Down | automap | +| Select + LK1 | Space + H | Select + L1 | left mouse click | +| Start + X | Enter + U | Start + Y | quest log | +| Start + A | Enter + J | Start + B | spell book | +| Select + LK5 | Space + L | Select + R1 | right mouse click | +| Select + A/B/X/Y | Space + J/K/U/I | Select + A/B/X/Y | hot spell |