From 8eea35c2b53ea00d7c59b1cd8ec8757f319c7885 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Tue, 11 Nov 2025 17:06:24 +0100 Subject: [PATCH 01/21] Implement multiplayer mode with Player 2 controls, including keyboard and joystick support. Update CMake configuration for optimization and SDL2 path adjustments. Enhance .gitignore for build artifacts and temporary files. --- .gitignore | 33 ++++++++ MULTIPLAYER_ANALYSIS.md | 170 ++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 57 +++++++++++--- src/build_simple.bat | 95 ++++++++++++++++++++++ src/data.h | 23 ++++++ src/menu.c | 107 +++++++++++++++++++++++++ src/proto.h | 6 ++ src/seg000.c | 129 ++++++++++++++++++++++++++++++ src/seg003.c | 63 +++++++++++++++ src/seg005.c | 34 +++++++- src/seg006.c | 136 +++++++++++++++++++++++++++++++- src/seg009.c | 61 ++++++++++---- src/types.h | 13 ++- 13 files changed, 891 insertions(+), 36 deletions(-) create mode 100644 MULTIPLAYER_ANALYSIS.md create mode 100644 src/build_simple.bat diff --git a/.gitignore b/.gitignore index 99e79bce..c70bc8ae 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,19 @@ src/*_private.* .idea/ src/.idea/ src/cmake-build*/ + +# Build directories (anywhere in the project) build/ +**/build/ +**/build-*/ +**/cmake-build*/ +**/out/ +**/bin/ +**/obj/ +**/Debug/ +**/Release/ +**/x64/ +**/x86/ # Visual Studio .vs/ @@ -88,3 +100,24 @@ screenshots/ # Configuration file generated by the in-game settings menu *.cfg + +# Temporary build tools and installers (specific files) +7z.exe +vs_buildtools.exe +setup_and_build.ps1 +install_build_tools.ps1 +BUILD_STATUS.md + +# Downloaded dependencies +SDL2-*/ +mingw/ +mingw_temp/ +SDL2_temp/ + +# CMake generated files (additional patterns) +**/CMakeCache.txt +**/CMakeFiles/ +**/cmake_install.cmake +**/Makefile +**/*.cmake +!**/CMakeLists.txt diff --git a/MULTIPLAYER_ANALYSIS.md b/MULTIPLAYER_ANALYSIS.md new file mode 100644 index 00000000..4e86068a --- /dev/null +++ b/MULTIPLAYER_ANALYSIS.md @@ -0,0 +1,170 @@ +# Multiplayer 1v1 Implementation Analysis + +## Complexity Assessment: **MODERATE** (3-4 weeks of development) + +## Current Architecture + +### ✅ What's Already in Place: +1. **Character System**: The game already has `Kid` (player) and `Guard` (opponent) as separate `char_type` structures +2. **Dual Character Processing**: The game loop already processes both characters: + - `play_kid_frame()` - handles Kid + - `play_guard_frame()` - handles Guard (currently AI-controlled) +3. **Input System**: SDL2 already supports multiple gamepads/controllers +4. **Control Functions**: The `control()` function works for any character when `Char` is set appropriately +5. **Sword Fighting**: The combat system already supports two characters fighting + +### ❌ What Needs to Be Added: + +## Implementation Requirements + +### 1. **Input System Extension** (Medium Complexity) +- **Current**: Single set of control variables (`control_x`, `control_y`, `control_shift`, etc.) +- **Needed**: Second set for Player 2 (`control_x_p2`, `control_y_p2`, etc.) +- **Location**: `src/data.h` - add new control variables +- **Files to modify**: + - `src/data.h` - add Player 2 control variables + - `src/seg000.c` - `read_keyb_control()` and `read_joyst_control()` functions + - `src/seg009.c` - `process_events()` for second controller detection + +### 2. **Multiplayer Mode Flag** (Low Complexity) +- **Add**: `is_multiplayer_mode` boolean flag +- **Location**: `src/data.h` +- **Usage**: Check this flag to determine if Guard should use AI or Player 2 input + +### 3. **Guard Control Modification** (Medium Complexity) +- **Current**: `play_guard()` calls `autocontrol_opponent()` (AI) +- **Needed**: In multiplayer mode, call `user_control_p2()` instead +- **Location**: `src/seg006.c` - `play_guard()` function +- **Implementation**: + ```c + if (is_multiplayer_mode) { + user_control_p2(); // Player 2 controls Guard + } else { + autocontrol_opponent(); // AI controls Guard + } + ``` + +### 4. **Player 2 Input Function** (Medium Complexity) +- **Create**: `user_control_p2()` function (similar to `user_control()`) +- **Location**: `src/seg006.c` +- **Logic**: + - Set `Char = Guard` (temporarily) + - Read Player 2 controls into the control variables + - Call `control()` + - Restore `Char` + +### 5. **Initialization** (Low-Medium Complexity) +- **Add**: Function to initialize multiplayer mode +- **Location**: `src/seg003.c` - `init_game()` or new function +- **Tasks**: + - Spawn Kid and Guard facing each other + - Set both characters to active state + - Initialize both with swords drawn (optional) + - Set starting positions + +### 6. **Menu Integration** (Low Complexity) +- **Add**: Menu option to select "Multiplayer 1v1" mode +- **Location**: `src/menu.c` (if menu system is used) or title screen +- **Alternative**: Command-line argument like `prince multiplayer` + +### 7. **Controller Assignment** (Low-Medium Complexity) +- **Detect**: Second controller automatically, or allow manual assignment +- **Fallback**: If only one controller, use keyboard for Player 1, controller for Player 2 +- **Location**: `src/seg009.c` - controller initialization + +## Code Changes Summary + +### Files to Modify: + +1. **src/data.h** + - Add Player 2 control variables + - Add `is_multiplayer_mode` flag + +2. **src/seg000.c** + - Modify `read_user_control()` to read Player 2 input + - Add `read_user_control_p2()` function + +3. **src/seg006.c** + - Modify `play_guard()` to check multiplayer mode + - Add `user_control_p2()` function + +4. **src/seg003.c** + - Add multiplayer initialization function + - Modify `init_game()` or `play_level()` to support multiplayer + +5. **src/seg009.c** + - Enhance controller detection for multiple controllers + - Add keyboard mapping for Player 2 (WASD or separate keys) + +6. **src/config.h** (optional) + - Add `USE_MULTIPLAYER` compile-time flag + +7. **src/proto.h** + - Add function declarations for multiplayer functions + +## Estimated Implementation Steps + +### Phase 1: Core Infrastructure (1 week) +1. Add Player 2 control variables to `data.h` +2. Create `read_user_control_p2()` function +3. Add `is_multiplayer_mode` flag +4. Test input reading for both players + +### Phase 2: Guard Control (1 week) +1. Create `user_control_p2()` function +2. Modify `play_guard()` to use Player 2 input in multiplayer mode +3. Test that Guard responds to Player 2 input + +### Phase 3: Initialization & Setup (1 week) +1. Create multiplayer initialization function +2. Set up starting positions (players facing each other) +3. Add menu/command-line option to start multiplayer +4. Handle controller assignment + +### Phase 4: Polish & Testing (1 week) +1. Test all game mechanics in multiplayer +2. Fix any bugs with collision, sword fighting, etc. +3. Add UI indicators (optional: "Player 1" / "Player 2" labels) +4. Ensure single-player mode is unaffected + +## Potential Challenges + +1. **Input Conflicts**: Need to ensure Player 2 input doesn't interfere with Player 1 +2. **Character State**: The `Char` and `Opp` globals are used throughout - need careful management +3. **Room Transitions**: Both players need to be handled when changing rooms +4. **Win Conditions**: Define what happens when one player dies in multiplayer +5. **Replay System**: May need to handle multiplayer replays differently + +## Advantages of Current Architecture + +✅ **Good News**: +- The game already processes two characters per frame +- Sword fighting system already supports two combatants +- Input system is modular and can be extended +- Character control logic is reusable (`control()` function) +- SDL2 natively supports multiple controllers + +## Complexity Rating Breakdown + +- **Input System**: ⭐⭐⭐ (Medium) - Need to handle two input sources +- **Control Logic**: ⭐⭐ (Low-Medium) - Mostly reusing existing code +- **Initialization**: ⭐⭐ (Low-Medium) - Straightforward setup +- **Testing**: ⭐⭐⭐⭐ (High) - Need to test all game mechanics +- **Integration**: ⭐⭐ (Low-Medium) - Well-isolated changes + +## Recommendation + +**This is very feasible!** The architecture is well-suited for this addition. The main work is: +1. Duplicating the input reading system for Player 2 +2. Replacing AI control with Player 2 input in multiplayer mode +3. Adding initialization and menu options + +The single-player experience can remain completely unchanged by using a compile-time or runtime flag. + +## Next Steps + +Would you like me to: +1. Start implementing the multiplayer mode? +2. Create a detailed implementation plan with code examples? +3. Begin with a specific phase (e.g., input system first)? + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee8da733..622e312c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,18 +1,33 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.5) project(SDLPoP) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c99") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration") +# Add optimization flags for release builds +if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug")) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -ffast-math -funroll-loops") + # Additional optimizations + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fomit-frame-pointer") + # Link-time optimization for better performance (optional, may not work with all toolchains) + # Check if compiler supports LTO before enabling + include(CheckCCompilerFlag) + check_c_compiler_flag("-flto" COMPILER_SUPPORTS_LTO) + if(COMPILER_SUPPORTS_LTO) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto") + endif() +endif() + # have CMake output binaries to the directory that contains the source files set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${SDLPoP_SOURCE_DIR}/..") # SDLPoP requires the SDL2 and SDL2_image development libraries. -# You can pass the SDL2 location to CMake like so: -DSDL2="C:/work/libraries/SDL2-2.0.8" +# You can pass the SDL2 location to CMake like so: -DSDL2="C:/work/libraries/SDL2-2.30.0" # Or alternatively, specify the SDL2 location below: -#set(SDL2 "C:/work/libraries/SDL2-2.0.8") +#set(SDL2 "C:/work/libraries/SDL2-2.30.0") # On macOS, if you used Homebrew to install SDL2, the location may be something like this: @@ -39,18 +54,38 @@ if (WIN32) # If the location of SDL2 is not specified, we will try to guess it. if (NOT(DEFINED SDL2)) cmake_policy(SET CMP0015 NEW) # suppress warning about relative paths - set(SDL2 "../../SDL2-2.0.8") + # Try MSYS2 installation first (common on Windows) + if(EXISTS "C:/msys64/mingw64") + set(SDL2 "C:/msys64/mingw64") + set(USE_MSYS2_SDL2 TRUE) + elseif(EXISTS "../../SDL2-2.30.0") + set(SDL2 "../../SDL2-2.30.0") + set(USE_MSYS2_SDL2 FALSE) + else() + message(FATAL_ERROR "SDL2 not found. Please install SDL2 via MSYS2 (pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image) or set SDL2 variable.") + endif() + else() + set(USE_MSYS2_SDL2 FALSE) endif() - # Automatically detect whether we need the x86 or x64 version of the SDL2 library. - if (CMAKE_SIZEOF_VOID_P EQUAL 8) - set(SDL2_ARCH "x86_64-w64-mingw32") + if(USE_MSYS2_SDL2) + # MSYS2 installation - headers are in include/SDL2 subdirectory + include_directories(${SDL2}/include) + include_directories(${SDL2}/include/SDL2) + link_directories(${SDL2}/lib) else() - set(SDL2_ARCH "i686-w64-mingw32") + # SDL2 development package - use architecture-specific paths + # Automatically detect whether we need the x86 or x64 version of the SDL2 library. + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(SDL2_ARCH "x86_64-w64-mingw32") + else() + set(SDL2_ARCH "i686-w64-mingw32") + endif() + + include_directories(${SDL2}/${SDL2_ARCH}/include) + include_directories(${SDL2}/${SDL2_ARCH}/include/SDL2) + link_directories(${SDL2}/${SDL2_ARCH}/lib) endif() - - include_directories(${SDL2}/${SDL2_ARCH}/include) - link_directories(${SDL2}/${SDL2_ARCH}/lib) endif() set(SOURCE_FILES diff --git a/src/build_simple.bat b/src/build_simple.bat new file mode 100644 index 00000000..4572628c --- /dev/null +++ b/src/build_simple.bat @@ -0,0 +1,95 @@ +@echo off +setlocal +cd /d %~dp0 + +set SDL2=..\SDL2-2.30.0 +set SDL2INC=%SDL2%\include +set SDL2LIB=%SDL2%\lib\x64 +set CPPFLAGS=-I"%SDL2INC%" + +set PATH=C:\msys64\mingw64\bin;%PATH% + +echo Building SDLPoP... +echo. + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -I"%SDL2INC%\..\include" -c main.c -o main.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c data.c -o data.o + +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg000.c -o seg000.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg001.c -o seg001.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg002.c -o seg002.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg003.c -o seg003.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg004.c -o seg004.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg005.c -o seg005.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg006.c -o seg006.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg007.c -o seg007.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg008.c -o seg008.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg009.c -o seg009.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c seqtbl.c -o seqtbl.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c replay.c -o replay.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c options.c -o options.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c lighting.c -o lighting.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c screenshot.c -o screenshot.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c menu.c -o menu.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c midi.c -o midi.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c opl3.c -o opl3.o +if errorlevel 1 goto error + +gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c stb_vorbis.c -o stb_vorbis.o +if errorlevel 1 goto error + +echo. +echo Linking... +gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"%SDL2LIB%" -lSDL2main -lSDL2 -lSDL2_image -lm -o ..\prince.exe +if errorlevel 1 goto error + +echo. +echo Build successful! prince.exe created in parent directory. +goto end + +:error +echo. +echo Build failed! +exit /b 1 + +:end +endlocal + diff --git a/src/data.h b/src/data.h index 5eee25b9..52cbce50 100644 --- a/src/data.h +++ b/src/data.h @@ -248,6 +248,16 @@ extern sbyte control_y; // data:4612 extern sbyte control_x; +// Multiplayer: Player 2 control variables +extern sbyte control_shift_p2; +extern sbyte control_y_p2; +extern sbyte control_x_p2; +extern sbyte control_shift2_p2; +extern sbyte control_forward_p2; +extern sbyte control_backward_p2; +extern sbyte control_up_p2; +extern sbyte control_down_p2; + #ifdef USE_FADE // data:4CCA extern word is_global_fading; @@ -258,6 +268,8 @@ extern palette_fade_type* fade_palette_buffer; extern char_type Kid; // data:295C extern word is_keyboard_mode INIT(= 0); +// Multiplayer mode flag +extern word is_multiplayer_mode INIT(= 0); // data:4E8A extern word is_paused; // data:42D0 @@ -632,8 +644,11 @@ extern SDL_Texture* texture_blurry; extern SDL_Texture* target_texture; extern SDL_GameController* sdl_controller_ INIT( = 0 ); +extern SDL_GameController* sdl_controller_p2_; // Player 2 controller for multiplayer extern SDL_Joystick* sdl_joystick_; // in case our joystick is not compatible with SDL_GameController +extern SDL_Joystick* sdl_joystick_p2_; // Player 2 joystick for multiplayer extern byte using_sdl_joystick_interface; +extern byte using_sdl_joystick_interface_p2; // Player 2 joystick interface flag extern int joy_axis[JOY_AXIS_NUM]; // hor/ver axes for left/right sticks + left and right triggers (in total 6 axes) extern int joy_axis_max[JOY_AXIS_NUM]; // Same as above, but stores the highest value reached between game updates extern int joy_left_stick_states[2]; // horizontal, vertical @@ -951,6 +966,14 @@ extern int key_down INIT(= SDL_SCANCODE_DOWN); extern int key_jump_left INIT(= SDL_SCANCODE_HOME); extern int key_jump_right INIT(= SDL_SCANCODE_PAGEUP); extern int key_action INIT(= SDL_SCANCODE_RSHIFT); +// Multiplayer: Player 2 controls (default: WASD + Space) +extern int key_left_p2 INIT(= SDL_SCANCODE_A); +extern int key_right_p2 INIT(= SDL_SCANCODE_D); +extern int key_up_p2 INIT(= SDL_SCANCODE_W); +extern int key_down_p2 INIT(= SDL_SCANCODE_S); +extern int key_jump_left_p2 INIT(= SDL_SCANCODE_Q); +extern int key_jump_right_p2 INIT(= SDL_SCANCODE_E); +extern int key_action_p2 INIT(= SDL_SCANCODE_SPACE); // menus extern int key_enter INIT(= SDL_SCANCODE_RETURN); extern int key_esc INIT(= SDL_SCANCODE_ESCAPE); diff --git a/src/menu.c b/src/menu.c index 338cf285..2f590f8d 100644 --- a/src/menu.c +++ b/src/menu.c @@ -159,6 +159,7 @@ enum setting_ids { SETTING_ENABLE_FADE, SETTING_ENABLE_FLASH, SETTING_ENABLE_LIGHTING, + SETTING_ENABLE_MULTIPLAYER, SETTING_ENABLE_CHEATS, SETTING_ENABLE_COPYPROT, SETTING_ENABLE_QUICKSAVE, @@ -308,6 +309,14 @@ enum setting_ids { SETTING_KEY_ACTION, SETTING_KEY_ENTER, SETTING_KEY_ESC, + // Multiplayer: Player 2 controls + SETTING_KEY_LEFT_P2, + SETTING_KEY_RIGHT_P2, + SETTING_KEY_UP_P2, + SETTING_KEY_DOWN_P2, + SETTING_KEY_JUMP_LEFT_P2, + SETTING_KEY_JUMP_RIGHT_P2, + SETTING_KEY_ACTION_P2, }; typedef struct setting_type { @@ -409,6 +418,13 @@ setting_type visuals_settings[] = { }; setting_type gameplay_settings[] = { + {.id = SETTING_ENABLE_MULTIPLAYER, .style = SETTING_STYLE_TOGGLE, .linked = &is_multiplayer_mode, + .text = "Multiplayer 1v1 mode", + .explanation = "Enable 1v1 multiplayer mode.\n" + "Player 1 (Kid): Arrow keys + Shift\n" + "Player 2 (Guard): WASD + Space\n" + "Both players start facing each other with swords drawn.\n" + "NOTE: Requires restarting the game to take effect."}, {.id = SETTING_ENABLE_CHEATS, .style = SETTING_STYLE_TOGGLE, .linked = &cheats_enabled, .text = "Enable cheats", .explanation = "Turn cheats on or off."/*"\nAlso, display the CHEATS option on the pause menu."*/}, @@ -1054,6 +1070,35 @@ setting_type controls_settings[] = { .linked = &key_action, .number_type = SETTING_INT, .text = "Action", .explanation = ""}, + // Multiplayer: Player 2 controls + {.id = SETTING_KEY_LEFT_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_left_p2, .number_type = SETTING_INT, + .text = "Player 2: Left", + .explanation = "Left movement key for Player 2 (Guard)."}, + {.id = SETTING_KEY_RIGHT_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_right_p2, .number_type = SETTING_INT, + .text = "Player 2: Right", + .explanation = "Right movement key for Player 2 (Guard)."}, + {.id = SETTING_KEY_UP_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_up_p2, .number_type = SETTING_INT, + .text = "Player 2: Up", + .explanation = "Up movement key for Player 2 (Guard)."}, + {.id = SETTING_KEY_DOWN_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_down_p2, .number_type = SETTING_INT, + .text = "Player 2: Down", + .explanation = "Down movement key for Player 2 (Guard)."}, + {.id = SETTING_KEY_JUMP_LEFT_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_jump_left_p2, .number_type = SETTING_INT, + .text = "Player 2: Jump left", + .explanation = "Jump left key for Player 2 (Guard)."}, + {.id = SETTING_KEY_JUMP_RIGHT_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_jump_right_p2, .number_type = SETTING_INT, + .text = "Player 2: Jump right", + .explanation = "Jump right key for Player 2 (Guard)."}, + {.id = SETTING_KEY_ACTION_P2, .style = SETTING_STYLE_KEY, .required = &is_multiplayer_mode, + .linked = &key_action_p2, .number_type = SETTING_INT, + .text = "Player 2: Action", + .explanation = "Action key for Player 2 (Guard)."}, {.id = SETTING_KEY_ENTER, .style = SETTING_STYLE_KEY, .required = NULL, .linked = &key_enter, .number_type = SETTING_INT, .text = "Enter a menu", @@ -1417,6 +1462,60 @@ void turn_setting_on_off(int setting_id, byte new_state, void* linked) { case SETTING_USE_CUSTOM_OPTIONS: turn_custom_options_on_off(new_state); break; + case SETTING_ENABLE_MULTIPLAYER: + // Set the flag first + if (linked != NULL) { + *(word*)linked = new_state; + } + // Multiplayer mode toggled - NEVER spawn a new Guard, only enable controls on existing Guard + if (new_state) { + extern char_type Guard; + extern char_type Kid; + // Only enable controls if Guard is already active in the same room + // If Guard is not active, multiplayer mode will just wait for a Guard to appear naturally + if (Guard.direction != 0x56 && Guard.room == Kid.room) { // Guard is active and in same room + // Guard is already active - just ensure it's set up for multiplayer + // Make sure Guard has a sword if it doesn't have one + if (Guard.sword != 2) { // sword_2_drawn + Guard.sword = 2; // Give Guard a sword if it doesn't have one + } + // Ensure Guard is alive + Guard.alive = -1; + // Set Guard's HP to match Kid's if it's zero + extern word hitp_max; + extern word guardhp_max; + extern word guardhp_curr; + guardhp_max = hitp_max; + if (guardhp_curr == 0) { + guardhp_curr = guardhp_max; + } + // Ensure Guard is in a controllable frame (stand with sword) + // This is important for movement controls to work + // Note: We use a safer approach that doesn't modify global Char + // to avoid affecting Kid's state + extern void loadshad(void); + extern void saveshad(void); + extern char_type Char; + char_type saved_char = Char; // Save current Char (might be Kid) + loadshad(); + Char = Guard; + // If Guard is not in a stand/walk frame, set it to stand with sword + // Check frame numbers directly (158, 170, 171 are stand frames, 157, 165 are walk frames) + if (Char.frame != 158 && Char.frame != 170 && Char.frame != 171 && + Char.frame != 165 && Char.frame != 157) { + extern const word seqtbl_offsets[]; + // seq_90_en_garde is 90, use numeric value + Char.curr_seq = seqtbl_offsets[90]; // stand active (en garde) + extern void play_seq(void); + play_seq(); // Play sequence to set frame + } + Guard = Char; + saveshad(); + Char = saved_char; // Restore Char to avoid affecting Kid + } + // If Guard is not active, do nothing - let the game spawn Guards naturally + } + break; } } @@ -2321,6 +2420,14 @@ void process_ingame_settings_user_managed(SDL_RWops* rw, rw_process_func_type pr process(key_jump_left ); process(key_jump_right); process(key_action ); + // Multiplayer: Player 2 controls + process(key_left_p2 ); + process(key_right_p2 ); + process(key_up_p2 ); + process(key_down_p2 ); + process(key_jump_left_p2 ); + process(key_jump_right_p2); + process(key_action_p2 ); process(key_enter ); process(key_esc ); } diff --git a/src/proto.h b/src/proto.h index 4af7d682..d09522d9 100644 --- a/src/proto.h +++ b/src/proto.h @@ -187,6 +187,7 @@ void init_game(int level); void play_level(int level_number); void do_startpos(void); void set_start_pos(void); +void init_multiplayer_mode(void); void find_start_level_door(void); void draw_level_first(void); void redraw_screen(int drawing_different_room); @@ -333,6 +334,11 @@ void save_ctrl_1(void); void rest_ctrl_1(void); void clear_saved_ctrl(void); void read_user_control(void); +// Multiplayer: Player 2 input functions +void read_user_control_p2(void); +void read_keyb_control_p2(void); +void read_joyst_control_p2(void); +void user_control_p2(void); int can_grab(void); int wall_type(byte tiletype); int get_tile_above_char(void); diff --git a/src/seg000.c b/src/seg000.c index 17a24718..556a0b98 100644 --- a/src/seg000.c +++ b/src/seg000.c @@ -107,6 +107,10 @@ void pop_main() { show_loading(); set_joy_mode(); + + // Multiplayer: Enable multiplayer mode if requested via command line + is_multiplayer_mode = (check_param("multiplayer") != NULL || check_param("mp") != NULL); + cheats_enabled = check_param("megahit") != NULL; #ifdef USE_DEBUG_CHEATS debug_cheats_enabled = check_param("debug") != NULL; @@ -1807,6 +1811,131 @@ void read_keyb_control() { #endif } +// Multiplayer: Player 2 keyboard controls (configurable keys, default WASD) +void read_keyb_control_p2() { + int key_state; + if (fixes->fix_register_quick_input) { + key_state = KEYSTATE_HELD | KEYSTATE_HELD_NEW; + } else { + key_state = KEYSTATE_HELD; + } + + // Initialize control values + control_y_p2 = CONTROL_RELEASED; + control_x_p2 = CONTROL_RELEASED; + control_shift_p2 = CONTROL_RELEASED; + + // Player 2 uses configurable keys (default: W=up, S=down, A=left, D=right, Space=action) + if (key_states[key_up_p2] & key_state || key_states[key_jump_left_p2] & key_state || key_states[key_jump_right_p2] & key_state) { + control_y_p2 = CONTROL_HELD_UP; + } else if (key_states[key_down_p2] & key_state) { + control_y_p2 = CONTROL_HELD_DOWN; + } + + if (key_states[key_left_p2] & key_state || key_states[key_jump_left_p2] & key_state) { + control_x_p2 = CONTROL_HELD_LEFT; + } else if (key_states[key_right_p2] & key_state || key_states[key_jump_right_p2] & key_state) { + control_x_p2 = CONTROL_HELD_RIGHT; + } + + if (key_states[key_action_p2] & key_state) { + control_shift_p2 = CONTROL_HELD; + } +} + +// Multiplayer: Player 2 joystick controls (second controller) +void read_joyst_control_p2() { + if (sdl_controller_p2_ == NULL && sdl_joystick_p2_ == NULL) { + // No controller for Player 2 - don't clear keyboard controls! + // Keyboard controls are handled by read_keyb_control_p2() + // Only clear joystick-specific controls if we had a controller before + return; + } + + int key_state; + int joy_axis_p2[JOY_AXIS_NUM]; + int joy_left_stick_states_p2[2]; + int joy_right_stick_states_p2[2]; + int joy_button_states_p2[JOYINPUT_NUM]; + + if (fixes->fix_register_quick_input) { + key_state = KEYSTATE_HELD | KEYSTATE_HELD_NEW; + } else { + key_state = KEYSTATE_HELD; + } + + // Read axes from Player 2 controller + if (sdl_controller_p2_ != NULL && !using_sdl_joystick_interface_p2) { + joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTX] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_LEFTX); + joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTY] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_LEFTY); + joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTX] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_RIGHTX); + joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTY] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_RIGHTY); + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = SDL_GameControllerGetAxis(sdl_controller_p2_, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + + // Read buttons + joy_button_states_p2[JOYINPUT_DPAD_LEFT] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_DPAD_LEFT) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_RIGHT] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_UP] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_DPAD_UP) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_DOWN] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_DPAD_DOWN) ? key_state : 0; + joy_button_states_p2[JOYINPUT_X] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_X) ? key_state : 0; + joy_button_states_p2[JOYINPUT_Y] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_Y) ? key_state : 0; + joy_button_states_p2[JOYINPUT_A] = SDL_GameControllerGetButton(sdl_controller_p2_, SDL_CONTROLLER_BUTTON_A) ? key_state : 0; + } else if (sdl_joystick_p2_ != NULL) { + // Use SDL_Joystick interface (fallback for non-GameController devices) + joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTX] = SDL_JoystickGetAxis(sdl_joystick_p2_, 0); + joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTY] = SDL_JoystickGetAxis(sdl_joystick_p2_, 1); + joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTX] = SDL_JoystickGetAxis(sdl_joystick_p2_, 2); + joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTY] = SDL_JoystickGetAxis(sdl_joystick_p2_, 3); + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = SDL_JoystickGetAxis(sdl_joystick_p2_, 4); + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = SDL_JoystickGetAxis(sdl_joystick_p2_, 5); + + // Read buttons (assuming standard mapping) + joy_button_states_p2[JOYINPUT_DPAD_LEFT] = SDL_JoystickGetButton(sdl_joystick_p2_, 11) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_RIGHT] = SDL_JoystickGetButton(sdl_joystick_p2_, 12) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_UP] = SDL_JoystickGetButton(sdl_joystick_p2_, 13) ? key_state : 0; + joy_button_states_p2[JOYINPUT_DPAD_DOWN] = SDL_JoystickGetButton(sdl_joystick_p2_, 14) ? key_state : 0; + joy_button_states_p2[JOYINPUT_X] = SDL_JoystickGetButton(sdl_joystick_p2_, 2) ? key_state : 0; + joy_button_states_p2[JOYINPUT_Y] = SDL_JoystickGetButton(sdl_joystick_p2_, 3) ? key_state : 0; + joy_button_states_p2[JOYINPUT_A] = SDL_JoystickGetButton(sdl_joystick_p2_, 0) ? key_state : 0; + } else { + return; // No controller + } + + // Process stick states + if (joystick_only_horizontal) { + get_joystick_state_hor_only(joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTX], joy_left_stick_states_p2); + get_joystick_state_hor_only(joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTX], joy_right_stick_states_p2); + } else { + get_joystick_state(joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTX], joy_axis_p2[SDL_CONTROLLER_AXIS_LEFTY], joy_left_stick_states_p2); + get_joystick_state(joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTX], joy_axis_p2[SDL_CONTROLLER_AXIS_RIGHTY], joy_right_stick_states_p2); + } + + // Apply stick/button input to Player 2 controls + if (joy_left_stick_states_p2[0] == -1 || joy_right_stick_states_p2[0] == -1 || joy_button_states_p2[JOYINPUT_DPAD_LEFT] & key_state) + control_x_p2 = CONTROL_HELD_LEFT; + else if (joy_left_stick_states_p2[0] == 1 || joy_right_stick_states_p2[0] == 1 || joy_button_states_p2[JOYINPUT_DPAD_RIGHT] & key_state) + control_x_p2 = CONTROL_HELD_RIGHT; + else + control_x_p2 = CONTROL_RELEASED; + + if (joy_left_stick_states_p2[1] == -1 || joy_right_stick_states_p2[1] == -1 || joy_button_states_p2[JOYINPUT_DPAD_UP] & key_state || joy_button_states_p2[JOYINPUT_Y] & key_state) + control_y_p2 = CONTROL_HELD_UP; + else if (joy_left_stick_states_p2[1] == 1 || joy_right_stick_states_p2[1] == 1 || joy_button_states_p2[JOYINPUT_DPAD_DOWN] & key_state || joy_button_states_p2[JOYINPUT_A] & key_state) + control_y_p2 = CONTROL_HELD_DOWN; + else + control_y_p2 = CONTROL_RELEASED; + + if (joy_button_states_p2[JOYINPUT_X] & key_state || + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > 8000 || + joy_axis_p2[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > 8000) + { + control_shift_p2 = CONTROL_HELD; + } else { + control_shift_p2 = CONTROL_RELEASED; + } +} + // We need a version of showmessage() which can detect modifier keys as well, in case someone wants to configure such a key for controls. int showmessage_any_key(char *text,int arg_4,void *arg_0) { word key; diff --git a/src/seg003.c b/src/seg003.c index f2a80b80..af0aa33f 100644 --- a/src/seg003.c +++ b/src/seg003.c @@ -102,7 +102,13 @@ void play_level(int level_number) { Guard.charid = charid_2_guard; Guard.direction = dir_56_none; do_startpos(); + have_sword = /*(level_number != 1)*/ (level_number == 0 || level_number >= custom->have_sword_from_level); + + // Multiplayer: Initialize multiplayer mode if enabled (after have_sword is set) + if (is_multiplayer_mode) { + init_multiplayer_mode(); + } find_start_level_door(); // busy waiting? while (check_sound_playing() && !do_paused()) idle(); @@ -201,6 +207,63 @@ void set_start_pos() { savekid(); } +// Multiplayer: Initialize players facing each other for 1v1 mode +void init_multiplayer_mode() { + // Ensure Guard is active + Guard.charid = charid_2_guard; + Guard.alive = -1; + Guard.room = Kid.room; + + // Position Guard to face Kid (place Guard a few tiles to the right of Kid) + Guard.curr_col = Kid.curr_col + 3; // 3 tiles to the right + if (Guard.curr_col >= SCREEN_TILECOUNTX) { + Guard.curr_col = SCREEN_TILECOUNTX - 1; // Keep within screen bounds + } + Guard.curr_row = Kid.curr_row; + + // Calculate Guard's x position + Guard.x = x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX; + Guard.y = y_land[Guard.curr_row + 1]; + + // Make Guard face left (toward Kid) + Guard.direction = dir_FF_left; + + // Give both characters swords + Kid.sword = sword_2_drawn; + Guard.sword = sword_2_drawn; + + // Set Guard's HP (same as Kid's max HP for fair fight) + guardhp_max = hitp_max; + guardhp_curr = guardhp_max; + + // Initialize Guard's state + Guard.fall_x = 0; + Guard.fall_y = 0; + Guard.action = actions_1_run_jump; + Guard.repeat = 0; + + // Load Guard sprite data and set to stand with sword pose + loadshad(); + Char = Guard; // Set Char to Guard temporarily + seqtbl_offset_char(seq_90_en_garde); // stand active (en garde) + play_seq(); // Play sequence to set frame + Guard = Char; // Restore Guard from Char + saveshad(); + + // Also ensure Kid has sword drawn and is in correct pose + loadkid(); + Char = Kid; + if (Kid.sword == sword_2_drawn) { + seqtbl_offset_char(seq_55_draw_sword); // draw sword (will set to stand with sword) + play_seq(); + } + savekid(); + + // Make Kid face right (toward Guard) + Kid.direction = dir_0_right; + savekid(); +} + // seg003:02E6 void find_start_level_door() { get_room_address(Kid.room); diff --git a/src/seg005.c b/src/seg005.c index 13a09744..ba3eb6ef 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -922,7 +922,18 @@ void run_jump() { // sseg005:0BB5 void back_with_sword() { short frame = Char.frame; - if (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword) { + // Multiplayer: In multiplayer mode, allow movement from any frame for Guard + extern word is_multiplayer_mode; + int can_move = (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword); + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + // In multiplayer, allow movement from any frame (stand, walk, or other) + // This ensures Player 2 can always control the Guard + can_move = 1; // Always allow movement in multiplayer + } else { + // Normal mode: only allow from stand frames + can_move = can_move || (frame == frame_165_walk_with_sword || frame == frame_157_walk_with_sword); + } + if (can_move) { control_backward = CONTROL_IGNORE; // disable automatic repeat seqtbl_offset_char(seq_57_back_with_sword); // back with sword } @@ -931,7 +942,18 @@ void back_with_sword() { // seg005:0BE3 void forward_with_sword() { short frame = Char.frame; - if (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword) { + // Multiplayer: In multiplayer mode, allow movement from any frame for Guard + extern word is_multiplayer_mode; + int can_move = (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword); + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + // In multiplayer, allow movement from any frame (stand, walk, or other) + // This ensures Player 2 can always control the Guard + can_move = 1; // Always allow movement in multiplayer + } else { + // Normal mode: only allow from stand frames + can_move = can_move || (frame == frame_165_walk_with_sword || frame == frame_157_walk_with_sword); + } + if (can_move) { control_forward = CONTROL_IGNORE; // disable automatic repeat if (Char.charid != charid_0_kid) { seqtbl_offset_char(seq_56_guard_forward_with_sword); // forward with sword (Guard) @@ -963,6 +985,14 @@ void draw_sword() { // seg005:0C67 void control_with_sword() { if (Char.action < actions_2_hang_climb) { + // Multiplayer: In multiplayer mode, always allow movement controls for Guard + extern word is_multiplayer_mode; + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + // In multiplayer, Guard can move freely - go directly to swordfight() which handles movement + swordfight(); + return; + } + if (get_tile_at_char() == tiles_11_loose || can_guard_see_kid >= 2) { short distance = char_opp_dist(); if ((word)distance < (word)90) { diff --git a/src/seg006.c b/src/seg006.c index 1a885abd..e8cd879a 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1489,8 +1489,20 @@ void play_guard() { clear_char(); } loc_7A65: - autocontrol_opponent(); - control(); + // Multiplayer: Use Player 2 input instead of AI if in multiplayer mode + if (is_multiplayer_mode) { + // Char is already set to Guard by loadshad_and_opp() in play_guard_frame() + // Disable AI control by clearing autocontrol variables + can_guard_see_kid = 0; // Disable AI seeing Kid + // Read Player 2 input and apply it + read_user_control_p2(); + user_control_p2(); + // Save Char back to Guard (in case user_control_p2 modified it) + Guard = Char; + } else { + autocontrol_opponent(); + control(); + } } } @@ -1591,6 +1603,126 @@ void read_user_control() { } } +// Multiplayer: Player 2 version of read_user_control +void read_user_control_p2() { + // Read keyboard and joystick input for Player 2 first + read_keyb_control_p2(); + read_joyst_control_p2(); + + // Convert control_x_p2 (LEFT/RIGHT) to forward/backward based on Guard's direction + // This is similar to how flip_control_x() works, but we need to do it for Player 2 + // Use Char.direction since Char is set to Guard in play_guard_frame() before this is called + sbyte effective_x_p2 = control_x_p2; + if (Char.direction >= dir_0_right) { + // Guard facing right: right = forward, left = backward + if (control_x_p2 == CONTROL_HELD_RIGHT) { + effective_x_p2 = CONTROL_HELD_FORWARD; + } else if (control_x_p2 == CONTROL_HELD_LEFT) { + effective_x_p2 = CONTROL_HELD_BACKWARD; + } + } else { + // Guard facing left: left = forward, right = backward + if (control_x_p2 == CONTROL_HELD_LEFT) { + effective_x_p2 = CONTROL_HELD_FORWARD; + } else if (control_x_p2 == CONTROL_HELD_RIGHT) { + effective_x_p2 = CONTROL_HELD_BACKWARD; + } + } + + // Process Player 2 controls - set directly based on input + // Initialize to CONTROL_RELEASED first + control_forward_p2 = CONTROL_RELEASED; + control_backward_p2 = CONTROL_RELEASED; + control_up_p2 = CONTROL_RELEASED; + control_down_p2 = CONTROL_RELEASED; + control_shift2_p2 = CONTROL_RELEASED; + + // Set to CONTROL_HELD if corresponding input is active + if (effective_x_p2 == CONTROL_HELD_FORWARD) { + control_forward_p2 = CONTROL_HELD; + } + if (effective_x_p2 == CONTROL_HELD_BACKWARD) { + control_backward_p2 = CONTROL_HELD; + } + if (control_y_p2 == CONTROL_HELD_UP) { + control_up_p2 = CONTROL_HELD; + } + if (control_y_p2 == CONTROL_HELD_DOWN) { + control_down_p2 = CONTROL_HELD; + } + if (control_shift_p2 == CONTROL_HELD) { + control_shift2_p2 = CONTROL_HELD; + } +} + +// Multiplayer: Apply Player 2 input to Guard character +void user_control_p2() { + // IMPORTANT: Save Player 1's control state BEFORE swapping + // This ensures Player 1's controls are preserved and not affecting Guard + sbyte temp_x = control_x; + sbyte temp_y = control_y; + sbyte temp_shift = control_shift; + sbyte temp_forward = control_forward; + sbyte temp_backward = control_backward; + sbyte temp_up = control_up; + sbyte temp_down = control_down; + sbyte temp_shift2 = control_shift2; + + // Convert control_x_p2 (LEFT/RIGHT) to FORWARD/BACKWARD for control() function + // This is the same conversion we did in read_user_control_p2() + sbyte control_x_effective = control_x_p2; + if (Char.direction >= dir_0_right) { + // Guard facing right: right = forward, left = backward + if (control_x_p2 == CONTROL_HELD_RIGHT) { + control_x_effective = CONTROL_HELD_FORWARD; + } else if (control_x_p2 == CONTROL_HELD_LEFT) { + control_x_effective = CONTROL_HELD_BACKWARD; + } + } else { + // Guard facing left: left = forward, right = backward + if (control_x_p2 == CONTROL_HELD_LEFT) { + control_x_effective = CONTROL_HELD_FORWARD; + } else if (control_x_p2 == CONTROL_HELD_RIGHT) { + control_x_effective = CONTROL_HELD_BACKWARD; + } + } + + // IMPORTANT: Clear Player 1's controls first, then set Player 2 controls + // This ensures Player 1's input doesn't leak into Guard control + control_x = CONTROL_RELEASED; + control_y = CONTROL_RELEASED; + control_shift = CONTROL_RELEASED; + control_forward = CONTROL_RELEASED; + control_backward = CONTROL_RELEASED; + control_up = CONTROL_RELEASED; + control_down = CONTROL_RELEASED; + control_shift2 = CONTROL_RELEASED; + + // Now set Player 2 controls as active + control_x = control_x_effective; // Use converted FORWARD/BACKWARD value + control_y = control_y_p2; + control_shift = control_shift_p2; + control_forward = control_forward_p2; + control_backward = control_backward_p2; + control_up = control_up_p2; + control_down = control_down_p2; + control_shift2 = control_shift2_p2; + + // Apply control to Guard (Char should already be set to Guard) + // Note: We don't need to flip_control_x() here because we already converted control_x + control(); + + // Restore Player 1 controls exactly as they were + control_x = temp_x; + control_y = temp_y; + control_shift = temp_shift; + control_forward = temp_forward; + control_backward = temp_backward; + control_up = temp_up; + control_down = temp_down; + control_shift2 = temp_shift2; +} + // seg006:0F55 int can_grab() { // Can char grab curr_tile2 through through_tile? diff --git a/src/seg009.c b/src/seg009.c index a6c496e7..0aa68896 100644 --- a/src/seg009.c +++ b/src/seg009.c @@ -561,8 +561,11 @@ chtab_type* load_sprites_from_file(int resource,int palette_bits, int quit_on_er int n_images = shpl->n_images; size_t alloc_size = sizeof(chtab_type) + sizeof(void *) * n_images; - chtab_type* chtab = (chtab_type*) malloc(alloc_size); - memset(chtab, 0, alloc_size); + chtab_type* chtab = (chtab_type*) calloc(1, alloc_size); // Use calloc instead of malloc+memset + if (chtab == NULL) { + if (quit_on_error) quit(1); + return NULL; + } chtab->n_images = n_images; for (int i = 1; i <= n_images; i++) { SDL_Surface* image = load_image(resource + i, pal_ptr); @@ -683,9 +686,8 @@ void decompress_rle_ud(byte* destination,const byte* source,int dest_length,int // seg009:90FA byte* decompress_lzg_lr(byte* dest,const byte* source,int dest_length) { - byte* window = (byte*) malloc(0x400); - if (window == NULL) return NULL; - memset(window, 0, 0x400); + // Use stack allocation for small window buffer (1024 bytes) to reduce heap fragmentation + byte window[0x400] = {0}; byte* window_pos = window + 0x400 - 0x42; // bx short remaining = dest_length; // cx byte* window_end = window + 0x400; // dx @@ -725,15 +727,13 @@ byte* decompress_lzg_lr(byte* dest,const byte* source,int dest_length) { } } while (remaining); // end: - free(window); return dest; } // seg009:91AD byte* decompress_lzg_ud(byte* dest,const byte* source,int dest_length,int stride,int height) { - byte* window = (byte*) malloc(0x400); - if (window == NULL) return NULL; - memset(window, 0, 0x400); + // Use stack allocation for small window buffer (1024 bytes) to reduce heap fragmentation + byte window[0x400] = {0}; byte* window_pos = window + 0x400 - 0x42; // bx short remaining = height; // cx byte* window_end = window + 0x400; // dx @@ -784,7 +784,6 @@ byte* decompress_lzg_ud(byte* dest,const byte* source,int dest_length,int stride } } while (dest_length); // end: - free(window); return dest; } @@ -846,8 +845,8 @@ image_type* decode_image(image_data_type* image_data, dat_pal_type* palette) { int cmeth = (flags >> 8) & 0x0F; int stride = calc_stride(image_data); int dest_size = stride * height; - byte* dest = (byte*) malloc(dest_size); - memset(dest, 0, dest_size); + byte* dest = (byte*) calloc(1, dest_size); // Use calloc instead of malloc+memset for better performance + if (dest == NULL) return NULL; decompr_img(dest, image_data, dest_size, cmeth, stride); byte* image_8bpp = conv_to_8bpp(dest, width, height, stride, depth); free(dest); dest = NULL; @@ -983,6 +982,29 @@ int set_joy_mode() { } is_keyboard_mode = !is_joyst_mode; + + // Multiplayer: Initialize Player 2 controller if available + sdl_controller_p2_ = NULL; + sdl_joystick_p2_ = NULL; + using_sdl_joystick_interface_p2 = 0; + if (SDL_NumJoysticks() >= 2) { + if (SDL_IsGameController(1)) { + sdl_controller_p2_ = SDL_GameControllerOpen(1); + if (sdl_controller_p2_ == NULL) { + // Try joystick interface as fallback + sdl_joystick_p2_ = SDL_JoystickOpen(1); + if (sdl_joystick_p2_ != NULL) { + using_sdl_joystick_interface_p2 = 1; + } + } + } else { + sdl_joystick_p2_ = SDL_JoystickOpen(1); + if (sdl_joystick_p2_ != NULL) { + using_sdl_joystick_interface_p2 = 1; + } + } + } + return is_joyst_mode; } @@ -1021,7 +1043,16 @@ void set_hc_pal() { // seg009:2446 void flip_not_ega(byte* memory,int height,int stride) { - byte* row_buffer = (byte*) malloc(stride); + // Use stack allocation for row buffer if stride is reasonable (typically <= 320 for 320x200 screen) + // For larger strides, fall back to heap allocation + byte stack_buffer[320]; + byte* row_buffer; + if (stride <= (int)sizeof(stack_buffer)) { + row_buffer = stack_buffer; + } else { + row_buffer = (byte*) malloc(stride); + if (row_buffer == NULL) return; // Early return on allocation failure + } byte* top_ptr; byte* bottom_ptr; bottom_ptr = top_ptr = memory; @@ -1035,7 +1066,9 @@ void flip_not_ega(byte* memory,int height,int stride) { bottom_ptr -= stride; --rem_rows; } while (rem_rows); - free(row_buffer); + if (row_buffer != stack_buffer) { + free(row_buffer); + } } // seg009:19B1 diff --git a/src/types.h b/src/types.h index 393af629..3f414c15 100644 --- a/src/types.h +++ b/src/types.h @@ -24,15 +24,14 @@ The authors of this program may be contacted at https://forum.princed.org #define STB_VORBIS_HEADER_ONLY #include "stb_vorbis.c" -#if !defined(_MSC_VER) -# include -# include -#else -// These headers for SDL seem to be the pkgconfig/meson standard as per the -// latest versions. If the old ones should be used, the ifdef must be used -// to compare versions. +#if defined(_MSC_VER) +// MSVC uses direct includes # include # include +#else +// MinGW/Linux/other - use SDL2 subdirectory (standard for SDL2) +# include +# include #endif #if SDL_BYTEORDER != SDL_LIL_ENDIAN From a88017d0c0a181910e559ceb9909d12348164c04 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Tue, 11 Nov 2025 17:13:19 +0100 Subject: [PATCH 02/21] Refine multiplayer mode setup: Ensure Guards are only activated if already present, reposition them correctly, and manage sword states for both characters. Implement auto-turning for Guards based on distance to opponents. --- src/seg003.c | 62 ++++++++++++++++++++++++++++++++++------------------ src/seg005.c | 11 +++++++++- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/seg003.c b/src/seg003.c index af0aa33f..2e29ef72 100644 --- a/src/seg003.c +++ b/src/seg003.c @@ -208,33 +208,51 @@ void set_start_pos() { } // Multiplayer: Initialize players facing each other for 1v1 mode +// NOTE: This function does NOT spawn enemies - it only sets up existing Guards void init_multiplayer_mode() { - // Ensure Guard is active - Guard.charid = charid_2_guard; + // Only set up if Guard is already active - DO NOT spawn new enemies + // Guard.direction == 0x56 (dir_56_none) means Guard is inactive + if (Guard.direction == 0x56) { + // No Guard active - don't spawn one, just return + // Multiplayer will work when a Guard appears naturally in the level + return; + } + + // Guard is already active - just ensure it's set up for multiplayer Guard.alive = -1; Guard.room = Kid.room; - // Position Guard to face Kid (place Guard a few tiles to the right of Kid) - Guard.curr_col = Kid.curr_col + 3; // 3 tiles to the right - if (Guard.curr_col >= SCREEN_TILECOUNTX) { - Guard.curr_col = SCREEN_TILECOUNTX - 1; // Keep within screen bounds + // Only reposition if Guard is not in the same room as Kid + if (Guard.room != Kid.room) { + Guard.room = Kid.room; + // Position Guard to face Kid (place Guard a few tiles to the right of Kid) + Guard.curr_col = Kid.curr_col + 3; // 3 tiles to the right + if (Guard.curr_col >= SCREEN_TILECOUNTX) { + Guard.curr_col = SCREEN_TILECOUNTX - 1; // Keep within screen bounds + } + Guard.curr_row = Kid.curr_row; + + // Calculate Guard's x position + Guard.x = x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX; + Guard.y = y_land[Guard.curr_row + 1]; + + // Make Guard face left (toward Kid) + Guard.direction = dir_FF_left; } - Guard.curr_row = Kid.curr_row; - - // Calculate Guard's x position - Guard.x = x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX; - Guard.y = y_land[Guard.curr_row + 1]; - // Make Guard face left (toward Kid) - Guard.direction = dir_FF_left; - - // Give both characters swords - Kid.sword = sword_2_drawn; - Guard.sword = sword_2_drawn; + // Give both characters swords if they don't have them + if (Kid.sword != sword_2_drawn) { + Kid.sword = sword_2_drawn; + } + if (Guard.sword != sword_2_drawn) { + Guard.sword = sword_2_drawn; + } // Set Guard's HP (same as Kid's max HP for fair fight) guardhp_max = hitp_max; - guardhp_curr = guardhp_max; + if (guardhp_curr == 0) { + guardhp_curr = guardhp_max; + } // Initialize Guard's state Guard.fall_x = 0; @@ -259,9 +277,11 @@ void init_multiplayer_mode() { } savekid(); - // Make Kid face right (toward Guard) - Kid.direction = dir_0_right; - savekid(); + // Make Kid face right (toward Guard) if they're in the same room + if (Kid.room == Guard.room) { + Kid.direction = dir_0_right; + savekid(); + } } // seg003:02E6 diff --git a/src/seg005.c b/src/seg005.c index ba3eb6ef..7f51ae75 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -985,9 +985,18 @@ void draw_sword() { // seg005:0C67 void control_with_sword() { if (Char.action < actions_2_hang_climb) { - // Multiplayer: In multiplayer mode, always allow movement controls for Guard + // Multiplayer: In multiplayer mode, allow movement controls for Guard but also auto-turn extern word is_multiplayer_mode; if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + // In multiplayer, check distance and auto-turn if needed (like Player 1 does) + short distance = char_opp_dist(); + if (distance < 0) { + // Opponent is behind - turn to face them + if ((word)distance < (word)-4) { + seqtbl_offset_char(seq_60_turn_with_sword); // turn with sword (after switching places) + return; + } + } // In multiplayer, Guard can move freely - go directly to swordfight() which handles movement swordfight(); return; From cbff0c5692ff3bf2f340c11ed2d9be26015eb535 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Tue, 11 Nov 2025 18:55:51 +0100 Subject: [PATCH 03/21] Enhance Player 2 control handling in multiplayer mode: Implement single-step movement logic to prevent continuous movement on key holds, and ensure proper state management for forward and backward controls. --- src/seg005.c | 12 +++++++++++ src/seg006.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/seg005.c b/src/seg005.c index 7f51ae75..0b257553 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -1068,8 +1068,20 @@ void swordfight() { } else if (control_up == CONTROL_HELD) { parry(); } else if (control_forward == CONTROL_HELD) { + // Multiplayer: For Player 2, set to IGNORE immediately to prevent repeat + extern word is_multiplayer_mode; + if (is_multiplayer_mode && charid >= charid_2_guard) { + control_forward = CONTROL_IGNORE; // Prevent continuous movement + control_forward_p2 = CONTROL_IGNORE; + } forward_with_sword(); } else if (control_backward == CONTROL_HELD) { + // Multiplayer: For Player 2, set to IGNORE immediately to prevent repeat + extern word is_multiplayer_mode; + if (is_multiplayer_mode && charid >= charid_2_guard) { + control_backward = CONTROL_IGNORE; // Prevent continuous movement + control_backward_p2 = CONTROL_IGNORE; + } back_with_sword(); } } diff --git a/src/seg006.c b/src/seg006.c index e8cd879a..025689a9 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1630,20 +1630,67 @@ void read_user_control_p2() { } // Process Player 2 controls - set directly based on input + // For single-step movement: Only trigger on NEW key presses (not held keys) + // Save previous state to detect transitions + static sbyte prev_forward_p2 = CONTROL_RELEASED; + static sbyte prev_backward_p2 = CONTROL_RELEASED; + // Initialize to CONTROL_RELEASED first - control_forward_p2 = CONTROL_RELEASED; - control_backward_p2 = CONTROL_RELEASED; control_up_p2 = CONTROL_RELEASED; control_down_p2 = CONTROL_RELEASED; control_shift2_p2 = CONTROL_RELEASED; - // Set to CONTROL_HELD if corresponding input is active + // For forward/backward: Only set to HELD if transitioning from RELEASED (new press) + // This prevents continuous movement when key is held if (effective_x_p2 == CONTROL_HELD_FORWARD) { - control_forward_p2 = CONTROL_HELD; + // Check if this is a new press (previous was RELEASED and current is not IGNORE) + if (prev_forward_p2 == CONTROL_RELEASED && control_forward_p2 != CONTROL_IGNORE) { + // New press - trigger movement + control_forward_p2 = CONTROL_HELD; + } else if (control_forward_p2 == CONTROL_IGNORE) { + // Movement in progress - keep IGNORE (don't change it) + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_forward_p2 = CONTROL_RELEASED; + } + } else { + // Key not pressed + if (control_forward_p2 == CONTROL_IGNORE) { + // Movement was in progress, now key released - reset to RELEASED + control_forward_p2 = CONTROL_RELEASED; + } else { + // Already RELEASED - keep it + control_forward_p2 = CONTROL_RELEASED; + } } + // Update previous state for next frame + prev_forward_p2 = control_forward_p2; + if (effective_x_p2 == CONTROL_HELD_BACKWARD) { - control_backward_p2 = CONTROL_HELD; + // Check if this is a new press (previous was RELEASED and current is not IGNORE) + if (prev_backward_p2 == CONTROL_RELEASED && control_backward_p2 != CONTROL_IGNORE) { + // New press - trigger movement + control_backward_p2 = CONTROL_HELD; + } else if (control_backward_p2 == CONTROL_IGNORE) { + // Movement in progress - keep IGNORE (don't change it) + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_backward_p2 = CONTROL_RELEASED; + } + } else { + // Key not pressed + if (control_backward_p2 == CONTROL_IGNORE) { + // Movement was in progress, now key released - reset to RELEASED + control_backward_p2 = CONTROL_RELEASED; + } else { + // Already RELEASED - keep it + control_backward_p2 = CONTROL_RELEASED; + } } + // Update previous state for next frame + prev_backward_p2 = control_backward_p2; + + // For up/down/action: Always set based on input (no single-step mode) if (control_y_p2 == CONTROL_HELD_UP) { control_up_p2 = CONTROL_HELD; } From 3a8146568e36ca8300d5b350c87a392a7e547e6d Mon Sep 17 00:00:00 2001 From: Dhiego Date: Wed, 12 Nov 2025 00:53:13 +0100 Subject: [PATCH 04/21] Enhance multiplayer controls: Update Player 2's action key from Space to X, improve sword drawing mechanics when sheathed, and refine control state management for smoother gameplay. --- src/build_simple.bat | 10 +++--- src/data.h | 4 +-- src/menu.c | 2 +- src/seg005.c | 20 ++++++++++++ src/seg006.c | 73 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/build_simple.bat b/src/build_simple.bat index 4572628c..aa444a05 100644 --- a/src/build_simple.bat +++ b/src/build_simple.bat @@ -2,9 +2,11 @@ setlocal cd /d %~dp0 -set SDL2=..\SDL2-2.30.0 -set SDL2INC=%SDL2%\include -set SDL2LIB=%SDL2%\lib\x64 +if [%SDL2%]==[] ( + set SDL2=C:\msys64\mingw64 +) +set SDL2INC=%SDL2%\include\SDL2 +set SDL2LIB=%SDL2%\lib set CPPFLAGS=-I"%SDL2INC%" set PATH=C:\msys64\mingw64\bin;%PATH% @@ -78,7 +80,7 @@ if errorlevel 1 goto error echo. echo Linking... -gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"%SDL2LIB%" -lSDL2main -lSDL2 -lSDL2_image -lm -o ..\prince.exe +gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"%SDL2LIB%" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ..\prince.exe if errorlevel 1 goto error echo. diff --git a/src/data.h b/src/data.h index 52cbce50..1860b0dc 100644 --- a/src/data.h +++ b/src/data.h @@ -966,14 +966,14 @@ extern int key_down INIT(= SDL_SCANCODE_DOWN); extern int key_jump_left INIT(= SDL_SCANCODE_HOME); extern int key_jump_right INIT(= SDL_SCANCODE_PAGEUP); extern int key_action INIT(= SDL_SCANCODE_RSHIFT); -// Multiplayer: Player 2 controls (default: WASD + Space) +// Multiplayer: Player 2 controls (default: WASD + F) extern int key_left_p2 INIT(= SDL_SCANCODE_A); extern int key_right_p2 INIT(= SDL_SCANCODE_D); extern int key_up_p2 INIT(= SDL_SCANCODE_W); extern int key_down_p2 INIT(= SDL_SCANCODE_S); extern int key_jump_left_p2 INIT(= SDL_SCANCODE_Q); extern int key_jump_right_p2 INIT(= SDL_SCANCODE_E); -extern int key_action_p2 INIT(= SDL_SCANCODE_SPACE); +extern int key_action_p2 INIT(= SDL_SCANCODE_X); // menus extern int key_enter INIT(= SDL_SCANCODE_RETURN); extern int key_esc INIT(= SDL_SCANCODE_ESCAPE); diff --git a/src/menu.c b/src/menu.c index 2f590f8d..e52cbda4 100644 --- a/src/menu.c +++ b/src/menu.c @@ -422,7 +422,7 @@ setting_type gameplay_settings[] = { .text = "Multiplayer 1v1 mode", .explanation = "Enable 1v1 multiplayer mode.\n" "Player 1 (Kid): Arrow keys + Shift\n" - "Player 2 (Guard): WASD + Space\n" + "Player 2 (Guard): WASD + X\n" "Both players start facing each other with swords drawn.\n" "NOTE: Requires restarting the game to take effect."}, {.id = SETTING_ENABLE_CHEATS, .style = SETTING_STYLE_TOGGLE, .linked = &cheats_enabled, diff --git a/src/seg005.c b/src/seg005.c index 0b257553..b8c12fa6 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -344,6 +344,17 @@ void control_standing() { if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD && check_get_item()) { return; } + // Multiplayer: For Player 2, allow drawing sword with attack button when sheathed + // Check raw input directly (like down+forward does) to bypass state management issues + extern word is_multiplayer_mode; + extern sbyte control_shift_p2; + if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed) { + // Check both the processed state and raw input to ensure it works + if (control_shift == CONTROL_HELD || control_shift2 == CONTROL_HELD || control_shift_p2 == CONTROL_HELD) { + draw_sword(); + return; + } + } if (Char.charid != charid_0_kid && control_down == CONTROL_HELD && control_forward == CONTROL_HELD) { draw_sword(); return; @@ -1053,6 +1064,15 @@ void swordfight() { if (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword) { control_down = CONTROL_IGNORE; // disable automatic repeat Char.sword = sword_0_sheathed; + // Multiplayer: For Player 2, reset attack state when sheathing sword + // This allows re-entering attack stance after sheathing + extern word is_multiplayer_mode; + extern sbyte control_shift2_p2; + if (is_multiplayer_mode && charid >= charid_2_guard) { + control_shift2_p2 = CONTROL_RELEASED; // Reset attack state to allow re-entering attack stance + // Also reset control_shift2 to ensure it's cleared + control_shift2 = CONTROL_RELEASED; + } if (charid == charid_0_kid) { offguard = 1; guard_refrac = 9; diff --git a/src/seg006.c b/src/seg006.c index 025689a9..7a424f66 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1638,7 +1638,11 @@ void read_user_control_p2() { // Initialize to CONTROL_RELEASED first control_up_p2 = CONTROL_RELEASED; control_down_p2 = CONTROL_RELEASED; - control_shift2_p2 = CONTROL_RELEASED; + // Don't reset control_shift2_p2 here if sword is sheathed - let the special handling manage it + extern word is_multiplayer_mode; + if (!(is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed)) { + control_shift2_p2 = CONTROL_RELEASED; + } // For forward/backward: Only set to HELD if transitioning from RELEASED (new press) // This prevents continuous movement when key is held @@ -1690,15 +1694,56 @@ void read_user_control_p2() { // Update previous state for next frame prev_backward_p2 = control_backward_p2; - // For up/down/action: Always set based on input (no single-step mode) + // For up/down: Always set based on input (no single-step mode) if (control_y_p2 == CONTROL_HELD_UP) { control_up_p2 = CONTROL_HELD; } if (control_y_p2 == CONTROL_HELD_DOWN) { control_down_p2 = CONTROL_HELD; } - if (control_shift_p2 == CONTROL_HELD) { - control_shift2_p2 = CONTROL_HELD; + + // For action/attack: Single-step mode (same as forward/backward) + static sbyte prev_shift2_p2 = CONTROL_RELEASED; + extern word is_multiplayer_mode; + // Multiplayer: Special handling when sword is sheathed - allow attack button to draw sword + if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed) { + // When sword is sheathed, reset state and allow attack button to work + prev_shift2_p2 = CONTROL_RELEASED; + if (control_shift_p2 == CONTROL_HELD) { + // Attack button pressed while sword is sheathed - directly set to HELD to draw sword + control_shift2_p2 = CONTROL_HELD; + // Also ensure control_shift_p2 stays HELD so control_shift gets set correctly + } else { + // Attack button not pressed - keep RELEASED + control_shift2_p2 = CONTROL_RELEASED; + } + // Update previous state for next frame + prev_shift2_p2 = control_shift2_p2; + } else { + // Normal attack logic when sword is drawn or not multiplayer + if (control_shift_p2 == CONTROL_HELD) { + // Check if this is a new press (previous was RELEASED and current is not IGNORE) + if (prev_shift2_p2 == CONTROL_RELEASED && control_shift2_p2 != CONTROL_IGNORE) { + // New press - trigger attack + control_shift2_p2 = CONTROL_HELD; + } else if (control_shift2_p2 == CONTROL_IGNORE) { + // Attack in progress - keep IGNORE (don't change it) + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_shift2_p2 = CONTROL_RELEASED; + } + } else { + // Key not pressed + if (control_shift2_p2 == CONTROL_IGNORE) { + // Attack was in progress, now key released - reset to RELEASED + control_shift2_p2 = CONTROL_RELEASED; + } else { + // Already RELEASED - keep it + control_shift2_p2 = CONTROL_RELEASED; + } + } + // Update previous state for next frame + prev_shift2_p2 = control_shift2_p2; } } @@ -1753,7 +1798,15 @@ void user_control_p2() { control_backward = control_backward_p2; control_up = control_up_p2; control_down = control_down_p2; - control_shift2 = control_shift2_p2; + // IMPORTANT: For attack, preserve IGNORE state (same as forward/backward) + // Only set if not IGNORE, or if currently RELEASED + // Multiplayer: When sword is sheathed and attack button is pressed, ensure control_shift2 is set + if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed && control_shift2_p2 == CONTROL_HELD) { + control_shift2 = CONTROL_HELD; // Force set when sword is sheathed and attack pressed + control_shift = CONTROL_HELD; // Also set control_shift to ensure the check in control_standing() works + } else if (control_shift2_p2 != CONTROL_IGNORE || control_shift2 == CONTROL_RELEASED) { + control_shift2 = control_shift2_p2; + } // Apply control to Guard (Char should already be set to Guard) // Note: We don't need to flip_control_x() here because we already converted control_x @@ -2289,6 +2342,16 @@ void add_sword_to_objtable() { // seg006:1827 void control_guard_inactive() { + // Multiplayer: Allow attack button to draw sword when sheathed + extern word is_multiplayer_mode; + extern sbyte control_shift_p2; + if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed) { + // Check attack button (same as down+forward check) + if (control_shift == CONTROL_HELD || control_shift2 == CONTROL_HELD || control_shift_p2 == CONTROL_HELD) { + draw_sword(); + return; + } + } if (Char.frame == frame_166_stand_inactive && control_down == CONTROL_HELD) { if (control_forward == CONTROL_HELD) { draw_sword(); From a6313fa192de6dcc84a94d398fe61e99f3e82a01 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Wed, 12 Nov 2025 01:32:41 +0100 Subject: [PATCH 05/21] Enhance multiplayer sword mechanics: Implement control state management for Player 2 to prevent repeat actions when drawing the sword and during parries. Refine handling of attack button when sword is sheathed to ensure smoother gameplay. --- src/build_and_run.ps1 | 71 +++++++++++++++++++++++++++++++++++++++++++ src/seg005.c | 29 ++++++++++++++++-- src/seg006.c | 49 +++++++++++++++++++++-------- 3 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 src/build_and_run.ps1 diff --git a/src/build_and_run.ps1 b/src/build_and_run.ps1 new file mode 100644 index 00000000..38ae7ff5 --- /dev/null +++ b/src/build_and_run.ps1 @@ -0,0 +1,71 @@ +# Fast incremental build and run script +# Only compiles changed files and launches game once + +$env:Path = "C:\msys64\mingw64\bin;" + $env:Path +$SDL2 = "C:\msys64\mingw64" +$SDL2INC = "$SDL2\include\SDL2" +$SDL2LIB = "$SDL2\lib" + +$ErrorActionPreference = "Stop" + +# Change to script directory (src) +if ($PSScriptRoot) { + Set-Location $PSScriptRoot +} else { + # If run from src directory directly + if (Test-Path "build_and_run.ps1") { + Set-Location $PWD + } +} + +Write-Host "Building SDLPoP (incremental)..." -ForegroundColor Green + +# Compile only changed files (GCC will skip if .o is newer than .c) +$files = @( + "main.c", "data.c", "seg000.c", "seg001.c", "seg002.c", "seg003.c", + "seg004.c", "seg005.c", "seg006.c", "seg007.c", "seg008.c", "seg009.c", + "seqtbl.c", "replay.c", "options.c", "lighting.c", "screenshot.c", + "menu.c", "midi.c", "opl3.c", "stb_vorbis.c" +) + +$compileFlags = @("-Wall", "-std=c99", "-O3", "-ffast-math", "-I$SDL2INC", "-I$SDL2INC\SDL2") +$objectFiles = @() + +foreach ($file in $files) { + $obj = $file -replace '\.c$', '.o' + $objectFiles += $obj + + # Check if we need to compile (file changed or .o doesn't exist) + $cFile = Get-Item $file -ErrorAction SilentlyContinue + $oFile = Get-Item $obj -ErrorAction SilentlyContinue + + if (-not $oFile -or $cFile.LastWriteTime -gt $oFile.LastWriteTime) { + Write-Host " Compiling $file..." -ForegroundColor Yellow + & gcc $compileFlags -c $file -o $obj + if ($LASTEXITCODE -ne 0) { + Write-Host "Build failed!" -ForegroundColor Red + exit 1 + } + } +} + +Write-Host "Linking..." -ForegroundColor Green +$linkFlags = @("-mwindows", "-L$SDL2LIB", "-Wl,--whole-archive", "-lSDL2main", "-Wl,--no-whole-archive", "-lSDL2", "-lSDL2_image", "-lm") +& gcc $objectFiles $linkFlags -o ..\prince.exe + +if ($LASTEXITCODE -ne 0) { + Write-Host "Build failed!" -ForegroundColor Red + exit 1 +} + +Write-Host "`n=== BUILD SUCCESSFUL ===" -ForegroundColor Green + +# Close any existing game instance +Get-Process prince -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Milliseconds 200 + +# Launch game once +Set-Location .. +Write-Host "Running prince.exe...`n" -ForegroundColor Green +Start-Process -FilePath ".\prince.exe" -WorkingDirectory $PWD + diff --git a/src/seg005.c b/src/seg005.c index b8c12fa6..3632395e 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -978,6 +978,12 @@ void forward_with_sword() { void draw_sword() { word seq_id = seq_55_draw_sword; // draw sword control_forward = control_shift2 = release_arrows(); + // Multiplayer: Also set control_shift2_p2 to IGNORE to prevent repeat + extern word is_multiplayer_mode; + extern sbyte control_shift2_p2; + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + control_shift2_p2 = CONTROL_IGNORE; // Prevent repeat for Player 2 + } #ifdef FIX_UNINTENDED_SWORD_STRIKE if (fixes->fix_unintended_sword_strike) { ctrl1_shift2 = CONTROL_IGNORE; // prevent restoring control_shift2 to CONTROL_HELD in rest_ctrl_1() @@ -1128,6 +1134,12 @@ void sword_strike() { return; } control_shift2 = CONTROL_IGNORE; // disable automatic repeat + // Multiplayer: Also set control_shift2_p2 to IGNORE to prevent repeat + extern word is_multiplayer_mode; + extern sbyte control_shift2_p2; + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + control_shift2_p2 = CONTROL_IGNORE; // Prevent repeat for Player 2 + } seqtbl_offset_char(seq_id); } @@ -1145,7 +1157,9 @@ void parry() { char_frame == frame_168_back || // back? char_frame == frame_165_walk_with_sword // walk with sword ) { - if (char_opp_dist() >= 32 && char_charid != charid_0_kid) { + // Multiplayer: Allow parry for Player 2 even when opponent is far away + extern word is_multiplayer_mode; + if (char_opp_dist() >= 32 && char_charid != charid_0_kid && !(is_multiplayer_mode && char_charid >= charid_2_guard)) { back_with_sword(); return; } else if (char_charid == charid_0_kid) { @@ -1163,13 +1177,24 @@ void parry() { } } } else { - if (opp_frame != frame_152_strike_2) return; + // Multiplayer: Allow parry for Player 2 even if opponent is not striking + if (is_multiplayer_mode && char_charid >= charid_2_guard) { + // Allow parry for Player 2 - don't check opponent frame + } else if (opp_frame != frame_152_strike_2) { + return; + } } } else { if (char_frame != frame_167_blocked) return; seq_id = seq_61_parry_after_strike; // parry after striking with sword } control_up = CONTROL_IGNORE; // disable automatic repeat + // Multiplayer: Also set control_up_p2 to IGNORE to prevent repeat + extern word is_multiplayer_mode; + extern sbyte control_up_p2; + if (is_multiplayer_mode && Char.charid >= charid_2_guard) { + control_up_p2 = CONTROL_IGNORE; // Prevent repeat for Player 2 + } seqtbl_offset_char(seq_id); if (do_play_seq) { play_seq(); diff --git a/src/seg006.c b/src/seg006.c index 7a424f66..78487ab6 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1706,16 +1706,29 @@ void read_user_control_p2() { static sbyte prev_shift2_p2 = CONTROL_RELEASED; extern word is_multiplayer_mode; // Multiplayer: Special handling when sword is sheathed - allow attack button to draw sword + // But still use single-step logic to prevent repeat if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed) { - // When sword is sheathed, reset state and allow attack button to work - prev_shift2_p2 = CONTROL_RELEASED; + // When sword is sheathed, use single-step logic to prevent repeat if (control_shift_p2 == CONTROL_HELD) { - // Attack button pressed while sword is sheathed - directly set to HELD to draw sword - control_shift2_p2 = CONTROL_HELD; - // Also ensure control_shift_p2 stays HELD so control_shift gets set correctly + // Check if this is a new press (previous was RELEASED and current is not IGNORE) + if (prev_shift2_p2 == CONTROL_RELEASED && control_shift2_p2 != CONTROL_IGNORE) { + // New press - trigger attack to draw sword + control_shift2_p2 = CONTROL_HELD; + } else if (control_shift2_p2 == CONTROL_IGNORE) { + // Attack in progress - keep IGNORE (don't change it) + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_shift2_p2 = CONTROL_RELEASED; + } } else { - // Attack button not pressed - keep RELEASED - control_shift2_p2 = CONTROL_RELEASED; + // Key not pressed + if (control_shift2_p2 == CONTROL_IGNORE) { + // Attack was in progress, now key released - reset to RELEASED + control_shift2_p2 = CONTROL_RELEASED; + } else { + // Already RELEASED - keep it + control_shift2_p2 = CONTROL_RELEASED; + } } // Update previous state for next frame prev_shift2_p2 = control_shift2_p2; @@ -1799,14 +1812,19 @@ void user_control_p2() { control_up = control_up_p2; control_down = control_down_p2; // IMPORTANT: For attack, preserve IGNORE state (same as forward/backward) - // Only set if not IGNORE, or if currently RELEASED - // Multiplayer: When sword is sheathed and attack button is pressed, ensure control_shift2 is set - if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed && control_shift2_p2 == CONTROL_HELD) { - control_shift2 = CONTROL_HELD; // Force set when sword is sheathed and attack pressed - control_shift = CONTROL_HELD; // Also set control_shift to ensure the check in control_standing() works + // If control_shift2 is already IGNORE (set by sword_strike() or draw_sword()), keep it + // Otherwise, map from control_shift2_p2 + if (control_shift2 == CONTROL_IGNORE) { + // Already IGNORE - keep it and also set control_shift2_p2 to IGNORE to stay in sync + control_shift2_p2 = CONTROL_IGNORE; } else if (control_shift2_p2 != CONTROL_IGNORE || control_shift2 == CONTROL_RELEASED) { control_shift2 = control_shift2_p2; } + // IMPORTANT: For up (parry), preserve IGNORE state (same as attack) + // If control_up is already IGNORE (set by parry()), keep it and sync control_up_p2 + if (control_up == CONTROL_IGNORE) { + control_up_p2 = CONTROL_IGNORE; + } // Apply control to Guard (Char should already be set to Guard) // Note: We don't need to flip_control_x() here because we already converted control_x @@ -2345,10 +2363,15 @@ void control_guard_inactive() { // Multiplayer: Allow attack button to draw sword when sheathed extern word is_multiplayer_mode; extern sbyte control_shift_p2; + extern sbyte control_shift2_p2; if (is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed) { // Check attack button (same as down+forward check) - if (control_shift == CONTROL_HELD || control_shift2 == CONTROL_HELD || control_shift_p2 == CONTROL_HELD) { + // Only trigger if control_shift2_p2 is HELD (single-step logic ensures it's only HELD on new press) + if (control_shift2 == CONTROL_HELD) { draw_sword(); + // Set to IGNORE to prevent repeat (same as draw_sword() does internally) + control_shift2 = CONTROL_IGNORE; + control_shift2_p2 = CONTROL_IGNORE; return; } } From 7ad7afb5f7aa7498643635376d180df227664f9c Mon Sep 17 00:00:00 2001 From: Dhiego Date: Wed, 12 Nov 2025 01:54:19 +0100 Subject: [PATCH 06/21] Refine control state management for Player 2: Enhance handling of control_shift2 to preserve IGNORE state during attack animations and prevent repeat actions. Update logic for transitioning between control states to improve gameplay responsiveness. --- src/seg006.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/seg006.c b/src/seg006.c index 78487ab6..d7a56052 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1638,10 +1638,13 @@ void read_user_control_p2() { // Initialize to CONTROL_RELEASED first control_up_p2 = CONTROL_RELEASED; control_down_p2 = CONTROL_RELEASED; - // Don't reset control_shift2_p2 here if sword is sheathed - let the special handling manage it + // Don't reset control_shift2_p2 here if it's IGNORE (attack in progress) or sword is sheathed extern word is_multiplayer_mode; if (!(is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed)) { - control_shift2_p2 = CONTROL_RELEASED; + // Only reset if not IGNORE (preserve IGNORE state during attack animation) + if (control_shift2_p2 != CONTROL_IGNORE) { + control_shift2_p2 = CONTROL_RELEASED; + } } // For forward/backward: Only set to HELD if transitioning from RELEASED (new press) @@ -1735,28 +1738,33 @@ void read_user_control_p2() { } else { // Normal attack logic when sword is drawn or not multiplayer if (control_shift_p2 == CONTROL_HELD) { - // Check if this is a new press (previous was RELEASED and current is not IGNORE) - if (prev_shift2_p2 == CONTROL_RELEASED && control_shift2_p2 != CONTROL_IGNORE) { + // If already IGNORE, keep it IGNORE (attack animation in progress) + if (control_shift2_p2 == CONTROL_IGNORE) { + // Attack in progress - keep IGNORE (don't change it) + // Keep prev_shift2_p2 as IGNORE to prevent re-triggering while key is held + prev_shift2_p2 = CONTROL_IGNORE; + } else if (prev_shift2_p2 == CONTROL_RELEASED) { // New press - trigger attack control_shift2_p2 = CONTROL_HELD; - } else if (control_shift2_p2 == CONTROL_IGNORE) { - // Attack in progress - keep IGNORE (don't change it) + prev_shift2_p2 = control_shift2_p2; } else { // Key held but already processed - keep RELEASED to prevent repeat control_shift2_p2 = CONTROL_RELEASED; + prev_shift2_p2 = control_shift2_p2; } } else { // Key not pressed if (control_shift2_p2 == CONTROL_IGNORE) { // Attack was in progress, now key released - reset to RELEASED control_shift2_p2 = CONTROL_RELEASED; + prev_shift2_p2 = control_shift2_p2; } else { // Already RELEASED - keep it control_shift2_p2 = CONTROL_RELEASED; + prev_shift2_p2 = control_shift2_p2; } } - // Update previous state for next frame - prev_shift2_p2 = control_shift2_p2; + // Note: prev_shift2_p2 is already updated in each branch above } } @@ -1794,6 +1802,8 @@ void user_control_p2() { // IMPORTANT: Clear Player 1's controls first, then set Player 2 controls // This ensures Player 1's input doesn't leak into Guard control + // BUT: Preserve IGNORE state for attack (control_shift2) to prevent repeat + sbyte saved_shift2 = control_shift2; // Save IGNORE state if present control_x = CONTROL_RELEASED; control_y = CONTROL_RELEASED; control_shift = CONTROL_RELEASED; @@ -1802,6 +1812,10 @@ void user_control_p2() { control_up = CONTROL_RELEASED; control_down = CONTROL_RELEASED; control_shift2 = CONTROL_RELEASED; + // Restore IGNORE state if it was set (from previous frame's sword_strike) + if (saved_shift2 == CONTROL_IGNORE) { + control_shift2 = CONTROL_IGNORE; + } // Now set Player 2 controls as active control_x = control_x_effective; // Use converted FORWARD/BACKWARD value @@ -1812,12 +1826,13 @@ void user_control_p2() { control_up = control_up_p2; control_down = control_down_p2; // IMPORTANT: For attack, preserve IGNORE state (same as forward/backward) - // If control_shift2 is already IGNORE (set by sword_strike() or draw_sword()), keep it - // Otherwise, map from control_shift2_p2 - if (control_shift2 == CONTROL_IGNORE) { - // Already IGNORE - keep it and also set control_shift2_p2 to IGNORE to stay in sync + // Priority: IGNORE state must be preserved to prevent repeat attacks + if (control_shift2 == CONTROL_IGNORE || control_shift2_p2 == CONTROL_IGNORE) { + // Either is IGNORE - set both to IGNORE to prevent repeat + control_shift2 = CONTROL_IGNORE; control_shift2_p2 = CONTROL_IGNORE; - } else if (control_shift2_p2 != CONTROL_IGNORE || control_shift2 == CONTROL_RELEASED) { + } else { + // Neither is IGNORE - map from control_shift2_p2 control_shift2 = control_shift2_p2; } // IMPORTANT: For up (parry), preserve IGNORE state (same as attack) From a857deae780c282a009e6a9810cd34d9924cb9f9 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Wed, 12 Nov 2025 01:57:42 +0100 Subject: [PATCH 07/21] Enhance Player 2 control management in multiplayer mode: Refine handling of control states for parries and sheathing actions to preserve IGNORE state, preventing repeat actions during animations. Improve logic for transitioning between control states to enhance gameplay responsiveness. --- src/seg005.c | 6 +++ src/seg006.c | 106 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/seg005.c b/src/seg005.c index 3632395e..e840bf9c 100644 --- a/src/seg005.c +++ b/src/seg005.c @@ -1069,6 +1069,12 @@ void swordfight() { if (control_down == CONTROL_HELD) { if (frame == frame_158_stand_with_sword || frame == frame_170_stand_with_sword || frame == frame_171_stand_with_sword) { control_down = CONTROL_IGNORE; // disable automatic repeat + // Multiplayer: Also set control_down_p2 to IGNORE to prevent repeat + extern word is_multiplayer_mode; + extern sbyte control_down_p2; + if (is_multiplayer_mode && charid >= charid_2_guard) { + control_down_p2 = CONTROL_IGNORE; // Prevent repeat for Player 2 + } Char.sword = sword_0_sheathed; // Multiplayer: For Player 2, reset attack state when sheathing sword // This allows re-entering attack stance after sheathing diff --git a/src/seg006.c b/src/seg006.c index d7a56052..6c7dbdfa 100644 --- a/src/seg006.c +++ b/src/seg006.c @@ -1635,9 +1635,14 @@ void read_user_control_p2() { static sbyte prev_forward_p2 = CONTROL_RELEASED; static sbyte prev_backward_p2 = CONTROL_RELEASED; - // Initialize to CONTROL_RELEASED first - control_up_p2 = CONTROL_RELEASED; - control_down_p2 = CONTROL_RELEASED; + // Initialize to CONTROL_RELEASED first, but preserve IGNORE state + // Only reset if not IGNORE (preserve IGNORE state during animations) + if (control_up_p2 != CONTROL_IGNORE) { + control_up_p2 = CONTROL_RELEASED; + } + if (control_down_p2 != CONTROL_IGNORE) { + control_down_p2 = CONTROL_RELEASED; + } // Don't reset control_shift2_p2 here if it's IGNORE (attack in progress) or sword is sheathed extern word is_multiplayer_mode; if (!(is_multiplayer_mode && Char.charid >= charid_2_guard && Char.sword == sword_0_sheathed)) { @@ -1697,12 +1702,64 @@ void read_user_control_p2() { // Update previous state for next frame prev_backward_p2 = control_backward_p2; - // For up/down: Always set based on input (no single-step mode) + // For up/down: Single-step mode (same as forward/backward/attack) + static sbyte prev_up_p2 = CONTROL_RELEASED; + static sbyte prev_down_p2 = CONTROL_RELEASED; + + // For up (parry/block): Single-step mode if (control_y_p2 == CONTROL_HELD_UP) { - control_up_p2 = CONTROL_HELD; + // If already IGNORE, keep it IGNORE (parry animation in progress) + if (control_up_p2 == CONTROL_IGNORE) { + // Parry in progress - keep IGNORE (don't change it) + prev_up_p2 = CONTROL_IGNORE; + } else if (prev_up_p2 == CONTROL_RELEASED && control_up_p2 != CONTROL_IGNORE) { + // New press - trigger parry + control_up_p2 = CONTROL_HELD; + prev_up_p2 = control_up_p2; + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_up_p2 = CONTROL_RELEASED; + prev_up_p2 = control_up_p2; + } + } else { + // Key not pressed + if (control_up_p2 == CONTROL_IGNORE) { + // Parry was in progress, now key released - reset to RELEASED + control_up_p2 = CONTROL_RELEASED; + prev_up_p2 = control_up_p2; + } else { + // Already RELEASED - keep it + control_up_p2 = CONTROL_RELEASED; + prev_up_p2 = control_up_p2; + } } + + // For down (sheath sword): Single-step mode if (control_y_p2 == CONTROL_HELD_DOWN) { - control_down_p2 = CONTROL_HELD; + // If already IGNORE, keep it IGNORE (sheath animation in progress) + if (control_down_p2 == CONTROL_IGNORE) { + // Sheath in progress - keep IGNORE (don't change it) + prev_down_p2 = CONTROL_IGNORE; + } else if (prev_down_p2 == CONTROL_RELEASED && control_down_p2 != CONTROL_IGNORE) { + // New press - trigger sheath + control_down_p2 = CONTROL_HELD; + prev_down_p2 = control_down_p2; + } else { + // Key held but already processed - keep RELEASED to prevent repeat + control_down_p2 = CONTROL_RELEASED; + prev_down_p2 = control_down_p2; + } + } else { + // Key not pressed + if (control_down_p2 == CONTROL_IGNORE) { + // Sheath was in progress, now key released - reset to RELEASED + control_down_p2 = CONTROL_RELEASED; + prev_down_p2 = control_down_p2; + } else { + // Already RELEASED - keep it + control_down_p2 = CONTROL_RELEASED; + prev_down_p2 = control_down_p2; + } } // For action/attack: Single-step mode (same as forward/backward) @@ -1802,7 +1859,9 @@ void user_control_p2() { // IMPORTANT: Clear Player 1's controls first, then set Player 2 controls // This ensures Player 1's input doesn't leak into Guard control - // BUT: Preserve IGNORE state for attack (control_shift2) to prevent repeat + // BUT: Preserve IGNORE state for up/down/attack to prevent repeat + sbyte saved_up = control_up; // Save IGNORE state if present + sbyte saved_down = control_down; // Save IGNORE state if present sbyte saved_shift2 = control_shift2; // Save IGNORE state if present control_x = CONTROL_RELEASED; control_y = CONTROL_RELEASED; @@ -1812,7 +1871,13 @@ void user_control_p2() { control_up = CONTROL_RELEASED; control_down = CONTROL_RELEASED; control_shift2 = CONTROL_RELEASED; - // Restore IGNORE state if it was set (from previous frame's sword_strike) + // Restore IGNORE state if it was set (from previous frame's parry/sheath/strike) + if (saved_up == CONTROL_IGNORE) { + control_up = CONTROL_IGNORE; + } + if (saved_down == CONTROL_IGNORE) { + control_down = CONTROL_IGNORE; + } if (saved_shift2 == CONTROL_IGNORE) { control_shift2 = CONTROL_IGNORE; } @@ -1823,8 +1888,24 @@ void user_control_p2() { control_shift = control_shift_p2; control_forward = control_forward_p2; control_backward = control_backward_p2; - control_up = control_up_p2; - control_down = control_down_p2; + // IMPORTANT: For up (parry) and down (sheath), preserve IGNORE state (same as attack) + // Priority: IGNORE state must be preserved to prevent repeat + if (control_up == CONTROL_IGNORE || control_up_p2 == CONTROL_IGNORE) { + // Either is IGNORE - set both to IGNORE to prevent repeat + control_up = CONTROL_IGNORE; + control_up_p2 = CONTROL_IGNORE; + } else { + // Neither is IGNORE - map from control_up_p2 + control_up = control_up_p2; + } + if (control_down == CONTROL_IGNORE || control_down_p2 == CONTROL_IGNORE) { + // Either is IGNORE - set both to IGNORE to prevent repeat + control_down = CONTROL_IGNORE; + control_down_p2 = CONTROL_IGNORE; + } else { + // Neither is IGNORE - map from control_down_p2 + control_down = control_down_p2; + } // IMPORTANT: For attack, preserve IGNORE state (same as forward/backward) // Priority: IGNORE state must be preserved to prevent repeat attacks if (control_shift2 == CONTROL_IGNORE || control_shift2_p2 == CONTROL_IGNORE) { @@ -1835,11 +1916,6 @@ void user_control_p2() { // Neither is IGNORE - map from control_shift2_p2 control_shift2 = control_shift2_p2; } - // IMPORTANT: For up (parry), preserve IGNORE state (same as attack) - // If control_up is already IGNORE (set by parry()), keep it and sync control_up_p2 - if (control_up == CONTROL_IGNORE) { - control_up_p2 = CONTROL_IGNORE; - } // Apply control to Guard (Char should already be set to Guard) // Note: We don't need to flip_control_x() here because we already converted control_x From 41df80a94fe143c930ddc76fc4510a4c693880f7 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 20:46:53 +0100 Subject: [PATCH 08/21] Github workflow pipeline --- .github/workflows/release.yml | 215 ++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..4c5bf8af --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,215 @@ +name: Build Release + +on: + release: + types: [published, created] + push: + tags: + - 'v*' + +jobs: + build-linux: + name: Build Linux AppImage + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + libsdl2-dev \ + libsdl2-image-dev \ + wget \ + file \ + fuse + + - name: Build SDLPoP + working-directory: src + run: | + make clean + make all + + - name: Download linuxdeploy + run: | + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + - name: Create AppDir structure + run: | + mkdir -p AppDir/usr/bin + mkdir -p AppDir/usr/share/applications + mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps + cp prince AppDir/usr/bin/ + cp -r ../data AppDir/usr/bin/ + cp ../SDLPoP.ini AppDir/usr/bin/ 2>/dev/null || echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini + cp ../doc/Readme.txt AppDir/usr/bin/README.txt 2>/dev/null || echo "SDLPoP" > AppDir/usr/bin/README.txt + + - name: Create desktop file + run: | + cat > AppDir/usr/share/applications/sdlpop.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=SDLPoP + Comment=Prince of Persia port + Exec=prince + Icon=sdlpop + Categories=Game; + EOF + + - name: Copy icon (if exists) + run: | + if [ -f data/icon.png ]; then + cp data/icon.png AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png + fi + + - name: Create AppImage + env: + VERSION: ${{ github.ref_name }} + run: | + export VERSION=${VERSION#v} # Remove 'v' prefix if present + export ARCH=x86_64 + ./linuxdeploy-x86_64.AppImage \ + --appdir AppDir \ + --executable AppDir/usr/bin/prince \ + --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ + --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ + --output appimage + mv SDLPoP-*.AppImage SDLPoP-${VERSION}-x86_64.AppImage || mv *.AppImage SDLPoP-${VERSION}-x86_64.AppImage + + - name: Upload AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Linux-AppImage + path: src/SDLPoP-*.AppImage + retention-days: 30 + + build-windows: + name: Build Windows + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install MSYS2 and dependencies + uses: msys2/setup-msys2@v2 + with: + update: true + install: >- + mingw-w64-x86_64-gcc + mingw-w64-x86_64-SDL2 + mingw-w64-x86_64-SDL2_image + make + + - name: Build SDLPoP + shell: msys2 {0} + working-directory: src + run: | + export SDL2=/mingw64 + export SDL2INC=/mingw64/include/SDL2 + export SDL2LIB=/mingw64/lib + export PATH=/mingw64/bin:$PATH + + # Compile all source files + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c main.c -o main.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c data.c -o data.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg000.c -o seg000.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg001.c -o seg001.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg002.c -o seg002.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg003.c -o seg003.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg004.c -o seg004.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg005.c -o seg005.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg006.c -o seg006.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg007.c -o seg007.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg008.c -o seg008.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg009.c -o seg009.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seqtbl.c -o seqtbl.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c replay.c -o replay.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c options.c -o options.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c lighting.c -o lighting.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c screenshot.c -o screenshot.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c menu.c -o menu.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c midi.c -o midi.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c opl3.c -o opl3.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c stb_vorbis.c -o stb_vorbis.o + + # Link + gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + + - name: Create release package + shell: powershell + working-directory: . + run: | + $version = "${{ github.ref_name }}" + $version = $version -replace '^v', '' # Remove 'v' prefix if present + $zipName = "SDLPoP-$version-Windows-x86_64.zip" + + # Create release directory + New-Item -ItemType Directory -Force -Path "release" | Out-Null + + # Copy executable + Copy-Item "prince.exe" -Destination "release/" + + # Copy DLLs + Copy-Item "/mingw64/bin/SDL2.dll" -Destination "release/" + Copy-Item "/mingw64/bin/SDL2_image.dll" -Destination "release/" + + # Copy data directory + Copy-Item "data" -Destination "release/data" -Recurse + + # Copy config file if exists + if (Test-Path "SDLPoP.ini") { + Copy-Item "SDLPoP.ini" -Destination "release/" + } else { + "# SDLPoP Configuration" | Out-File -FilePath "release/SDLPoP.ini" -Encoding utf8 + } + + # Copy README + if (Test-Path "doc/Readme.txt") { + Copy-Item "doc/Readme.txt" -Destination "release/README.txt" + } + + # Create zip + Compress-Archive -Path "release/*" -DestinationPath $zipName -Force + + Write-Host "Created $zipName" + + - name: Upload Windows zip artifact + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Windows-x86_64 + path: SDLPoP-*.zip + retention-days: 30 + + create-release: + name: Create GitHub Release + needs: [build-linux, build-windows] + runs-on: ubuntu-latest + if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') + + steps: + - name: Download Linux artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Linux-AppImage + path: artifacts/linux + + - name: Download Windows artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Windows-x86_64 + path: artifacts/windows + + - name: Upload to Release + uses: softprops/action-gh-release@v1 + with: + files: | + artifacts/linux/* + artifacts/windows/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From 0ada517075d5d8954a973b94364bba85b4f5342c Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 21:03:45 +0100 Subject: [PATCH 09/21] Fix release workflow: only trigger on published releases and fix file paths --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4c5bf8af..ffea5e88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Build Release on: release: - types: [published, created] + types: [published] push: tags: - 'v*' @@ -43,10 +43,10 @@ jobs: mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/share/applications mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps - cp prince AppDir/usr/bin/ - cp -r ../data AppDir/usr/bin/ - cp ../SDLPoP.ini AppDir/usr/bin/ 2>/dev/null || echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini - cp ../doc/Readme.txt AppDir/usr/bin/README.txt 2>/dev/null || echo "SDLPoP" > AppDir/usr/bin/README.txt + cp src/prince AppDir/usr/bin/ + cp -r data AppDir/usr/bin/ + cp SDLPoP.ini AppDir/usr/bin/ 2>/dev/null || echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini + cp doc/Readme.txt AppDir/usr/bin/README.txt 2>/dev/null || echo "SDLPoP" > AppDir/usr/bin/README.txt - name: Create desktop file run: | @@ -84,7 +84,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: SDLPoP-Linux-AppImage - path: src/SDLPoP-*.AppImage + path: SDLPoP-*.AppImage retention-days: 30 build-windows: From 68abe68381f7b526aa7b1f4e2edfe97583d247b0 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 21:08:14 +0100 Subject: [PATCH 10/21] Fix Windows build: use MSYS2 shell for DLL paths --- .github/workflows/release.yml | 44 ++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffea5e88..7a7866d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,42 +141,44 @@ jobs: gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe - name: Create release package - shell: powershell + shell: msys2 {0} working-directory: . run: | - $version = "${{ github.ref_name }}" - $version = $version -replace '^v', '' # Remove 'v' prefix if present - $zipName = "SDLPoP-$version-Windows-x86_64.zip" + version="${{ github.ref_name }}" + version=${version#v} # Remove 'v' prefix if present + zipName="SDLPoP-${version}-Windows-x86_64.zip" # Create release directory - New-Item -ItemType Directory -Force -Path "release" | Out-Null + mkdir -p release # Copy executable - Copy-Item "prince.exe" -Destination "release/" + cp prince.exe release/ - # Copy DLLs - Copy-Item "/mingw64/bin/SDL2.dll" -Destination "release/" - Copy-Item "/mingw64/bin/SDL2_image.dll" -Destination "release/" + # Copy DLLs from MSYS2 installation + cp /mingw64/bin/SDL2.dll release/ + cp /mingw64/bin/SDL2_image.dll release/ # Copy data directory - Copy-Item "data" -Destination "release/data" -Recurse + cp -r data release/ # Copy config file if exists - if (Test-Path "SDLPoP.ini") { - Copy-Item "SDLPoP.ini" -Destination "release/" - } else { - "# SDLPoP Configuration" | Out-File -FilePath "release/SDLPoP.ini" -Encoding utf8 - } + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini release/ + else + echo "# SDLPoP Configuration" > release/SDLPoP.ini + fi # Copy README - if (Test-Path "doc/Readme.txt") { - Copy-Item "doc/Readme.txt" -Destination "release/README.txt" - } + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt release/README.txt + fi - # Create zip - Compress-Archive -Path "release/*" -DestinationPath $zipName -Force + # Create zip using PowerShell (better compression on Windows) + cd release + powershell.exe -Command "Compress-Archive -Path * -DestinationPath ../$zipName -Force" + cd .. - Write-Host "Created $zipName" + echo "Created $zipName" - name: Upload Windows zip artifact uses: actions/upload-artifact@v4 From f402b4fca3fc4027a286f4c5372c1c0ace22e229 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 21:14:37 +0100 Subject: [PATCH 11/21] Add verification steps and fix binary path: prevent build failures with better error handling --- .github/workflows/release.yml | 104 +++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a7866d3..52fe130d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,19 @@ jobs: make clean make all + - name: Verify build output + run: | + if [ ! -f prince ]; then + echo "Error: prince binary not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince binary found" + file prince + - name: Download linuxdeploy run: | wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage @@ -43,10 +56,37 @@ jobs: mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/share/applications mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps - cp src/prince AppDir/usr/bin/ + + # Copy binary (Makefile outputs to ../prince, which is root) + if [ ! -f prince ]; then + echo "Error: prince binary not found" + exit 1 + fi + cp prince AppDir/usr/bin/ + chmod +x AppDir/usr/bin/prince + + # Copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi cp -r data AppDir/usr/bin/ - cp SDLPoP.ini AppDir/usr/bin/ 2>/dev/null || echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini - cp doc/Readme.txt AppDir/usr/bin/README.txt 2>/dev/null || echo "SDLPoP" > AppDir/usr/bin/README.txt + + # Copy config file (optional) + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini AppDir/usr/bin/ + else + echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini + fi + + # Copy README (optional) + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt AppDir/usr/bin/README.txt + else + echo "SDLPoP" > AppDir/usr/bin/README.txt + fi + + echo "✓ AppDir structure created successfully" - name: Create desktop file run: | @@ -140,6 +180,20 @@ jobs: # Link gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + - name: Verify build output + shell: msys2 {0} + run: | + if [ ! -f prince.exe ]; then + echo "Error: prince.exe not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince.exe found" + ls -lh prince.exe + - name: Create release package shell: msys2 {0} working-directory: . @@ -151,26 +205,52 @@ jobs: # Create release directory mkdir -p release - # Copy executable + # Verify and copy executable + if [ ! -f prince.exe ]; then + echo "Error: prince.exe not found" + exit 1 + fi cp prince.exe release/ + echo "✓ Copied prince.exe" - # Copy DLLs from MSYS2 installation + # Verify and copy DLLs from MSYS2 installation + if [ ! -f /mingw64/bin/SDL2.dll ]; then + echo "Error: SDL2.dll not found at /mingw64/bin/SDL2.dll" + echo "Searching for SDL2.dll..." + find /mingw64 -name "SDL2.dll" 2>/dev/null || true + exit 1 + fi + if [ ! -f /mingw64/bin/SDL2_image.dll ]; then + echo "Error: SDL2_image.dll not found at /mingw64/bin/SDL2_image.dll" + echo "Searching for SDL2_image.dll..." + find /mingw64 -name "SDL2_image.dll" 2>/dev/null || true + exit 1 + fi cp /mingw64/bin/SDL2.dll release/ cp /mingw64/bin/SDL2_image.dll release/ + echo "✓ Copied SDL2 DLLs" - # Copy data directory + # Verify and copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi cp -r data release/ + echo "✓ Copied data directory" - # Copy config file if exists + # Copy config file (optional) if [ -f SDLPoP.ini ]; then cp SDLPoP.ini release/ + echo "✓ Copied SDLPoP.ini" else echo "# SDLPoP Configuration" > release/SDLPoP.ini + echo "✓ Created default SDLPoP.ini" fi - # Copy README + # Copy README (optional) if [ -f doc/Readme.txt ]; then cp doc/Readme.txt release/README.txt + echo "✓ Copied README.txt" fi # Create zip using PowerShell (better compression on Windows) @@ -178,7 +258,13 @@ jobs: powershell.exe -Command "Compress-Archive -Path * -DestinationPath ../$zipName -Force" cd .. - echo "Created $zipName" + if [ ! -f "$zipName" ]; then + echo "Error: Failed to create zip file" + exit 1 + fi + + echo "✓ Created $zipName" + ls -lh "$zipName" - name: Upload Windows zip artifact uses: actions/upload-artifact@v4 From 3132a47ab41ef33a6710e4c9b83e9e854e22cc71 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 22:22:48 +0100 Subject: [PATCH 12/21] Fix AppImage rename and Windows zip nesting issues --- .github/workflows/release.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52fe130d..25052c23 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,7 +118,30 @@ jobs: --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ --output appimage - mv SDLPoP-*.AppImage SDLPoP-${VERSION}-x86_64.AppImage || mv *.AppImage SDLPoP-${VERSION}-x86_64.AppImage + + # Find the created AppImage and rename if needed + APPIMAGE=$(ls SDLPoP-*.AppImage 2>/dev/null | head -n 1) + TARGET_NAME="SDLPoP-${VERSION}-x86_64.AppImage" + + if [ -z "$APPIMAGE" ]; then + echo "Error: No AppImage file found" + exit 1 + fi + + if [ "$APPIMAGE" != "$TARGET_NAME" ]; then + echo "Renaming $APPIMAGE to $TARGET_NAME" + mv "$APPIMAGE" "$TARGET_NAME" + else + echo "AppImage already has correct name: $TARGET_NAME" + fi + + # Verify the final file exists + if [ ! -f "$TARGET_NAME" ]; then + echo "Error: Final AppImage file not found: $TARGET_NAME" + exit 1 + fi + echo "✓ Created $TARGET_NAME" + ls -lh "$TARGET_NAME" - name: Upload AppImage artifact uses: actions/upload-artifact@v4 @@ -253,9 +276,9 @@ jobs: echo "✓ Copied README.txt" fi - # Create zip using PowerShell (better compression on Windows) + # Create zip using zip command (avoids PowerShell nesting issues) cd release - powershell.exe -Command "Compress-Archive -Path * -DestinationPath ../$zipName -Force" + zip -r "../$zipName" . -q cd .. if [ ! -f "$zipName" ]; then From 84c0a14bcbae0ff2c13bea45b00f8028db03f6ba Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 22:28:45 +0100 Subject: [PATCH 13/21] Fix Windows build: install zip package in MSYS2 --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25052c23..a7f6cfd3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,6 +167,7 @@ jobs: mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image make + zip - name: Build SDLPoP shell: msys2 {0} From 4883734e4e9d53d3895efa887b5567564b37e3ba Mon Sep 17 00:00:00 2001 From: Dhiego Date: Fri, 14 Nov 2025 23:59:46 +0100 Subject: [PATCH 14/21] Enhance GitHub Actions workflow: Add 32-bit Windows and ARM64 Linux build jobs, update artifact handling, and improve naming conventions for clarity. --- .github/workflows/release.yml | 331 +++++++++++++++++++++++++++++++++- 1 file changed, 323 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7f6cfd3..a1063a0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -151,7 +151,7 @@ jobs: retention-days: 30 build-windows: - name: Build Windows + name: Build Windows x86_64 runs-on: windows-latest steps: @@ -297,31 +297,346 @@ jobs: path: SDLPoP-*.zip retention-days: 30 + build-windows-32: + name: Build Windows x86 (32-bit) + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install MSYS2 and dependencies + uses: msys2/setup-msys2@v2 + with: + update: true + install: >- + mingw-w64-i686-gcc + mingw-w64-i686-SDL2 + mingw-w64-i686-SDL2_image + make + zip + + - name: Build SDLPoP (32-bit) + shell: msys2 {0} + working-directory: src + run: | + export SDL2=/mingw32 + export SDL2INC=/mingw32/include/SDL2 + export SDL2LIB=/mingw32/lib + export PATH=/mingw32/bin:$PATH + + # Compile all source files (32-bit) + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c main.c -o main.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c data.c -o data.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg000.c -o seg000.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg001.c -o seg001.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg002.c -o seg002.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg003.c -o seg003.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg004.c -o seg004.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg005.c -o seg005.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg006.c -o seg006.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg007.c -o seg007.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg008.c -o seg008.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg009.c -o seg009.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seqtbl.c -o seqtbl.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c replay.c -o replay.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c options.c -o options.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c lighting.c -o lighting.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c screenshot.c -o screenshot.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c menu.c -o menu.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c midi.c -o midi.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c opl3.c -o opl3.o + gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c stb_vorbis.c -o stb_vorbis.o + + # Link (32-bit) + gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + + - name: Verify build output (32-bit) + shell: msys2 {0} + run: | + if [ ! -f prince.exe ]; then + echo "Error: prince.exe not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince.exe found" + file prince.exe + ls -lh prince.exe + + - name: Create release package (32-bit) + shell: msys2 {0} + working-directory: . + run: | + version="${{ github.ref_name }}" + version=${version#v} # Remove 'v' prefix if present + zipName="SDLPoP-${version}-Windows-x86.zip" + + # Create release directory + mkdir -p release + + # Verify and copy executable + if [ ! -f prince.exe ]; then + echo "Error: prince.exe not found" + exit 1 + fi + cp prince.exe release/ + echo "✓ Copied prince.exe" + + # Verify and copy DLLs from MSYS2 installation (32-bit) + if [ ! -f /mingw32/bin/SDL2.dll ]; then + echo "Error: SDL2.dll not found at /mingw32/bin/SDL2.dll" + echo "Searching for SDL2.dll..." + find /mingw32 -name "SDL2.dll" 2>/dev/null || true + exit 1 + fi + if [ ! -f /mingw32/bin/SDL2_image.dll ]; then + echo "Error: SDL2_image.dll not found at /mingw32/bin/SDL2_image.dll" + echo "Searching for SDL2_image.dll..." + find /mingw32 -name "SDL2_image.dll" 2>/dev/null || true + exit 1 + fi + cp /mingw32/bin/SDL2.dll release/ + cp /mingw32/bin/SDL2_image.dll release/ + echo "✓ Copied SDL2 DLLs (32-bit)" + + # Verify and copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi + cp -r data release/ + echo "✓ Copied data directory" + + # Copy config file (optional) + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini release/ + echo "✓ Copied SDLPoP.ini" + else + echo "# SDLPoP Configuration" > release/SDLPoP.ini + echo "✓ Created default SDLPoP.ini" + fi + + # Copy README (optional) + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt release/README.txt + echo "✓ Copied README.txt" + fi + + # Create zip using zip command + cd release + zip -r "../$zipName" . -q + cd .. + + if [ ! -f "$zipName" ]; then + echo "Error: Failed to create zip file" + exit 1 + fi + + echo "✓ Created $zipName" + ls -lh "$zipName" + + - name: Upload Windows 32-bit zip artifact + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Windows-x86 + path: SDLPoP-*-Windows-x86.zip + retention-days: 30 + + build-linux-arm64: + name: Build Linux ARM64 + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies and cross-compiler + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + libsdl2-dev:arm64 \ + libsdl2-image-dev:arm64 \ + pkg-config-aarch64-linux-gnu \ + wget \ + file \ + fuse + + - name: Build SDLPoP (ARM64) + working-directory: src + run: | + # Set up cross-compilation paths + export CC=aarch64-linux-gnu-gcc + export PKG_CONFIG=aarch64-linux-gnu-pkg-config + export SDL2_CFLAGS=$(aarch64-linux-gnu-pkg-config --cflags sdl2 SDL2_image) + export SDL2_LIBS=$(aarch64-linux-gnu-pkg-config --libs sdl2 SDL2_image) + + # Compile all source files (ARM64) + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c main.c -o main.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c data.c -o data.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg000.c -o seg000.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg001.c -o seg001.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg002.c -o seg002.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg003.c -o seg003.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg004.c -o seg004.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg005.c -o seg005.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg006.c -o seg006.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg007.c -o seg007.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg008.c -o seg008.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg009.c -o seg009.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seqtbl.c -o seqtbl.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c replay.c -o replay.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c options.c -o options.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c lighting.c -o lighting.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c screenshot.c -o screenshot.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c menu.c -o menu.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c midi.c -o midi.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c opl3.c -o opl3.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c stb_vorbis.c -o stb_vorbis.o + + # Link (ARM64) + $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $SDL2_LIBS -lm -o ../prince + + - name: Verify build output (ARM64) + run: | + if [ ! -f prince ]; then + echo "Error: prince binary not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince binary found" + file prince + # Verify it's ARM64 + if ! file prince | grep -q "ARM aarch64"; then + echo "Warning: Binary might not be ARM64" + fi + + - name: Download linuxdeploy for ARM64 + run: | + # Note: linuxdeploy may not have ARM64 AppImage, so we'll create a tarball instead + echo "Creating ARM64 tarball package" + + - name: Create ARM64 package + run: | + version="${{ github.ref_name }}" + version=${version#v} # Remove 'v' prefix if present + packageName="SDLPoP-${version}-Linux-ARM64.tar.gz" + + # Create package directory + mkdir -p package/usr/bin + mkdir -p package/usr/share/applications + mkdir -p package/usr/share/icons/hicolor/256x256/apps + + # Copy binary + if [ ! -f prince ]; then + echo "Error: prince binary not found" + exit 1 + fi + cp prince package/usr/bin/ + chmod +x package/usr/bin/prince + + # Copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi + cp -r data package/usr/bin/ + + # Copy config file (optional) + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini package/usr/bin/ + else + echo "# SDLPoP Configuration" > package/usr/bin/SDLPoP.ini + fi + + # Copy README (optional) + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt package/usr/bin/README.txt + else + echo "SDLPoP" > package/usr/bin/README.txt + fi + + # Create desktop file + cat > package/usr/share/applications/sdlpop.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=SDLPoP + Comment=Prince of Persia port + Exec=prince + Icon=sdlpop + Categories=Game; + EOF + + # Copy icon (if exists) + if [ -f data/icon.png ]; then + cp data/icon.png package/usr/share/icons/hicolor/256x256/apps/sdlpop.png + fi + + # Create tarball + tar -czf "$packageName" -C package . + + if [ ! -f "$packageName" ]; then + echo "Error: Failed to create tarball" + exit 1 + fi + + echo "✓ Created $packageName" + ls -lh "$packageName" + + - name: Upload ARM64 artifact + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Linux-ARM64 + path: SDLPoP-*-Linux-ARM64.tar.gz + retention-days: 30 + create-release: name: Create GitHub Release - needs: [build-linux, build-windows] + needs: [build-linux, build-windows, build-windows-32, build-linux-arm64] runs-on: ubuntu-latest if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') steps: - - name: Download Linux artifact + - name: Download Linux x86_64 artifact uses: actions/download-artifact@v4 with: name: SDLPoP-Linux-AppImage - path: artifacts/linux + path: artifacts/linux-x86_64 - - name: Download Windows artifact + - name: Download Windows x86_64 artifact uses: actions/download-artifact@v4 with: name: SDLPoP-Windows-x86_64 - path: artifacts/windows + path: artifacts/windows-x86_64 + + - name: Download Windows x86 artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Windows-x86 + path: artifacts/windows-x86 + + - name: Download Linux ARM64 artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Linux-ARM64 + path: artifacts/linux-arm64 - name: Upload to Release uses: softprops/action-gh-release@v1 with: files: | - artifacts/linux/* - artifacts/windows/* + artifacts/linux-x86_64/* + artifacts/windows-x86_64/* + artifacts/windows-x86/* + artifacts/linux-arm64/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ca465c882fa2da506dfce63e172c93eb9a4937c6 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 15 Nov 2025 00:08:11 +0100 Subject: [PATCH 15/21] Enhance GitHub Actions workflow: Add ARM32 and ARM64 build jobs, implement SDL2_image package checks, and improve linking logic for static and dynamic libraries. --- .github/workflows/release.yml | 450 ++++++++++++++++++++++++++++++++-- 1 file changed, 432 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1063a0d..d7cbb742 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -312,10 +312,45 @@ jobs: install: >- mingw-w64-i686-gcc mingw-w64-i686-SDL2 - mingw-w64-i686-SDL2_image make zip + - name: Check available SDL2_image packages + shell: msys2 {0} + run: | + echo "Searching for SDL2_image packages..." + pacman -Ss SDL2_image | grep i686 || echo "No i686 SDL2_image found" + echo "Available SDL packages:" + pacman -Ss SDL2 | grep i686 || echo "No i686 SDL2 packages found" + + - name: Install build dependencies for SDL2_image + shell: msys2 {0} + run: | + pacman -S --noconfirm --needed \ + mingw-w64-i686-autotools \ + mingw-w64-i686-libpng \ + mingw-w64-i686-libjpeg-turbo \ + mingw-w64-i686-libtiff \ + mingw-w64-i686-libwebp \ + wget \ + tar \ + make || echo "Some packages may not be available" + + - name: Build SDL2_image from source (32-bit) + shell: msys2 {0} + run: | + # Download and build SDL2_image for 32-bit if package not available + cd /tmp + wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz + tar -xzf SDL2_image-2.8.2.tar.gz + cd SDL2_image-2.8.2 + export PATH=/mingw32/bin:$PATH + export CC=i686-w64-mingw32-gcc + export PKG_CONFIG_PATH=/mingw32/lib/pkgconfig + ./configure --host=i686-w64-mingw32 --prefix=/mingw32 --with-sdl-prefix=/mingw32 --disable-shared --enable-static + make -j$(nproc) || make + make install || echo "Install may have warnings but should work" + - name: Build SDLPoP (32-bit) shell: msys2 {0} working-directory: src @@ -348,8 +383,14 @@ jobs: gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c opl3.c -o opl3.o gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c stb_vorbis.c -o stb_vorbis.o - # Link (32-bit) - gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + # Link (32-bit) - use static SDL2_image if available + if [ -f "$SDL2LIB/libSDL2_image.a" ]; then + echo "Linking with static SDL2_image" + gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + else + echo "Linking with dynamic SDL2_image" + gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe + fi - name: Verify build output (32-bit) shell: msys2 {0} @@ -392,15 +433,16 @@ jobs: find /mingw32 -name "SDL2.dll" 2>/dev/null || true exit 1 fi - if [ ! -f /mingw32/bin/SDL2_image.dll ]; then - echo "Error: SDL2_image.dll not found at /mingw32/bin/SDL2_image.dll" - echo "Searching for SDL2_image.dll..." - find /mingw32 -name "SDL2_image.dll" 2>/dev/null || true - exit 1 - fi cp /mingw32/bin/SDL2.dll release/ - cp /mingw32/bin/SDL2_image.dll release/ - echo "✓ Copied SDL2 DLLs (32-bit)" + echo "✓ Copied SDL2.dll" + + # SDL2_image might be statically linked, so DLL may not exist + if [ -f /mingw32/bin/SDL2_image.dll ]; then + cp /mingw32/bin/SDL2_image.dll release/ + echo "✓ Copied SDL2_image.dll" + else + echo "Note: SDL2_image is statically linked, no DLL needed" + fi # Verify and copy data directory if [ ! -d data ]; then @@ -460,21 +502,38 @@ jobs: build-essential \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu \ - libsdl2-dev:arm64 \ - libsdl2-image-dev:arm64 \ - pkg-config-aarch64-linux-gnu \ + pkg-config \ wget \ file \ fuse + - name: Build SDL2 from source for ARM64 + run: | + # Download and build SDL2 for ARM64 + cd /tmp + wget -q https://github.com/libsdl-org/SDL/releases/download/release-2.30.0/SDL2-2.30.0.tar.gz + tar -xzf SDL2-2.30.0.tar.gz + cd SDL2-2.30.0 + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static + make -j$(nproc) + sudo make install + + # Download and build SDL2_image for ARM64 + wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz + tar -xzf SDL2_image-2.8.2.tar.gz + cd SDL2_image-2.8.2 + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --with-sdl-prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static + make -j$(nproc) + sudo make install + - name: Build SDLPoP (ARM64) working-directory: src run: | # Set up cross-compilation paths export CC=aarch64-linux-gnu-gcc - export PKG_CONFIG=aarch64-linux-gnu-pkg-config - export SDL2_CFLAGS=$(aarch64-linux-gnu-pkg-config --cflags sdl2 SDL2_image) - export SDL2_LIBS=$(aarch64-linux-gnu-pkg-config --libs sdl2 SDL2_image) + export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig + export SDL2_CFLAGS="-I/usr/aarch64-linux-gnu/include/SDL2" + export SDL2_LIBS="-L/usr/aarch64-linux-gnu/lib -lSDL2 -lSDL2_image" # Compile all source files (ARM64) $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c main.c -o main.o @@ -598,9 +657,350 @@ jobs: path: SDLPoP-*-Linux-ARM64.tar.gz retention-days: 30 + build-linux-arm32: + name: Build Linux ARM 32-bit (armhf) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies and cross-compiler + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + gcc-arm-linux-gnueabihf \ + g++-arm-linux-gnueabihf \ + pkg-config \ + wget \ + file \ + fuse + + - name: Build SDL2 from source for ARM 32-bit + run: | + # Download and build SDL2 for ARM 32-bit (armhf) + cd /tmp + wget -q https://github.com/libsdl-org/SDL/releases/download/release-2.30.0/SDL2-2.30.0.tar.gz + tar -xzf SDL2-2.30.0.tar.gz + cd SDL2-2.30.0 + ./configure --host=arm-linux-gnueabihf --prefix=/usr/arm-linux-gnueabihf --disable-shared --enable-static + make -j$(nproc) + sudo make install + + # Download and build SDL2_image for ARM 32-bit + wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz + tar -xzf SDL2_image-2.8.2.tar.gz + cd SDL2_image-2.8.2 + ./configure --host=arm-linux-gnueabihf --prefix=/usr/arm-linux-gnueabihf --with-sdl-prefix=/usr/arm-linux-gnueabihf --disable-shared --enable-static + make -j$(nproc) + sudo make install + + - name: Build SDLPoP (ARM 32-bit) + working-directory: src + run: | + # Set up cross-compilation paths + export CC=arm-linux-gnueabihf-gcc + export PKG_CONFIG_PATH=/usr/arm-linux-gnueabihf/lib/pkgconfig + export SDL2_CFLAGS="-I/usr/arm-linux-gnueabihf/include/SDL2" + export SDL2_LIBS="-L/usr/arm-linux-gnueabihf/lib -lSDL2 -lSDL2_image" + + # Compile all source files (ARM 32-bit) + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c main.c -o main.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c data.c -o data.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg000.c -o seg000.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg001.c -o seg001.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg002.c -o seg002.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg003.c -o seg003.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg004.c -o seg004.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg005.c -o seg005.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg006.c -o seg006.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg007.c -o seg007.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg008.c -o seg008.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg009.c -o seg009.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seqtbl.c -o seqtbl.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c replay.c -o replay.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c options.c -o options.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c lighting.c -o lighting.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c screenshot.c -o screenshot.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c menu.c -o menu.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c midi.c -o midi.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c opl3.c -o opl3.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c stb_vorbis.c -o stb_vorbis.o + + # Link (ARM 32-bit) + $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $SDL2_LIBS -lm -o ../prince + + - name: Verify build output (ARM 32-bit) + run: | + if [ ! -f prince ]; then + echo "Error: prince binary not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince binary found" + file prince + # Verify it's ARM 32-bit + if ! file prince | grep -q "ARM"; then + echo "Warning: Binary might not be ARM" + fi + + - name: Create ARM 32-bit package + run: | + version="${{ github.ref_name }}" + version=${version#v} # Remove 'v' prefix if present + packageName="SDLPoP-${version}-Linux-ARM32.tar.gz" + + # Create package directory + mkdir -p package/usr/bin + mkdir -p package/usr/share/applications + mkdir -p package/usr/share/icons/hicolor/256x256/apps + + # Copy binary + if [ ! -f prince ]; then + echo "Error: prince binary not found" + exit 1 + fi + cp prince package/usr/bin/ + chmod +x package/usr/bin/prince + + # Copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi + cp -r data package/usr/bin/ + + # Copy config file (optional) + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini package/usr/bin/ + else + echo "# SDLPoP Configuration" > package/usr/bin/SDLPoP.ini + fi + + # Copy README (optional) + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt package/usr/bin/README.txt + else + echo "SDLPoP" > package/usr/bin/README.txt + fi + + # Create desktop file + cat > package/usr/share/applications/sdlpop.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=SDLPoP + Comment=Prince of Persia port + Exec=prince + Icon=sdlpop + Categories=Game; + EOF + + # Copy icon (if exists) + if [ -f data/icon.png ]; then + cp data/icon.png package/usr/share/icons/hicolor/256x256/apps/sdlpop.png + fi + + # Create tarball + tar -czf "$packageName" -C package . + + if [ ! -f "$packageName" ]; then + echo "Error: Failed to create tarball" + exit 1 + fi + + echo "✓ Created $packageName" + ls -lh "$packageName" + + - name: Upload ARM 32-bit artifact + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Linux-ARM32 + path: SDLPoP-*-Linux-ARM32.tar.gz + retention-days: 30 + + build-linux-32: + name: Build Linux AppImage (32-bit) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies and multilib + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + gcc-multilib \ + g++-multilib \ + libsdl2-dev:i386 \ + libsdl2-image-dev:i386 \ + wget \ + file \ + fuse + + - name: Build SDLPoP (32-bit) + working-directory: src + run: | + # Set up 32-bit compilation + export CC="gcc -m32" + export CXX="g++ -m32" + export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig + + # Compile all source files (32-bit) + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c main.c -o main.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c data.c -o data.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg000.c -o seg000.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg001.c -o seg001.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg002.c -o seg002.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg003.c -o seg003.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg004.c -o seg004.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg005.c -o seg005.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg006.c -o seg006.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg007.c -o seg007.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg008.c -o seg008.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg009.c -o seg009.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seqtbl.c -o seqtbl.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c replay.c -o replay.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c options.c -o options.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c lighting.c -o lighting.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c screenshot.c -o screenshot.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c menu.c -o menu.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c midi.c -o midi.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c opl3.c -o opl3.o + $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c stb_vorbis.c -o stb_vorbis.o + + # Link (32-bit) + $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $(pkg-config --libs sdl2 SDL2_image) -lm -o ../prince + + - name: Verify build output (32-bit) + run: | + if [ ! -f prince ]; then + echo "Error: prince binary not found in root directory" + echo "Contents of root directory:" + ls -la + echo "Contents of src directory:" + ls -la src/ + exit 1 + fi + echo "✓ Build successful: prince binary found" + file prince + # Verify it's 32-bit + if ! file prince | grep -q "32-bit"; then + echo "Warning: Binary might not be 32-bit" + fi + + - name: Download linuxdeploy (i686) + run: | + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-i686.AppImage + chmod +x linuxdeploy-i686.AppImage + + - name: Create AppDir structure + run: | + mkdir -p AppDir/usr/bin + mkdir -p AppDir/usr/share/applications + mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps + + # Copy binary + if [ ! -f prince ]; then + echo "Error: prince binary not found" + exit 1 + fi + cp prince AppDir/usr/bin/ + chmod +x AppDir/usr/bin/prince + + # Copy data directory + if [ ! -d data ]; then + echo "Error: data directory not found" + exit 1 + fi + cp -r data AppDir/usr/bin/ + + # Copy config file (optional) + if [ -f SDLPoP.ini ]; then + cp SDLPoP.ini AppDir/usr/bin/ + else + echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini + fi + + # Copy README (optional) + if [ -f doc/Readme.txt ]; then + cp doc/Readme.txt AppDir/usr/bin/README.txt + else + echo "SDLPoP" > AppDir/usr/bin/README.txt + fi + + echo "✓ AppDir structure created successfully" + + - name: Create desktop file + run: | + cat > AppDir/usr/share/applications/sdlpop.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=SDLPoP + Comment=Prince of Persia port + Exec=prince + Icon=sdlpop + Categories=Game; + EOF + + - name: Copy icon (if exists) + run: | + if [ -f data/icon.png ]; then + cp data/icon.png AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png + fi + + - name: Create AppImage (32-bit) + env: + VERSION: ${{ github.ref_name }} + run: | + export VERSION=${VERSION#v} # Remove 'v' prefix if present + export ARCH=i686 + ./linuxdeploy-i686.AppImage \ + --appdir AppDir \ + --executable AppDir/usr/bin/prince \ + --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ + --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ + --output appimage + + # Find the created AppImage and rename if needed + APPIMAGE=$(ls SDLPoP-*.AppImage 2>/dev/null | head -n 1) + TARGET_NAME="SDLPoP-${VERSION}-i686.AppImage" + + if [ -z "$APPIMAGE" ]; then + echo "Error: No AppImage file found" + exit 1 + fi + + if [ "$APPIMAGE" != "$TARGET_NAME" ]; then + echo "Renaming $APPIMAGE to $TARGET_NAME" + mv "$APPIMAGE" "$TARGET_NAME" + else + echo "AppImage already has correct name: $TARGET_NAME" + fi + + # Verify the final file exists + if [ ! -f "$TARGET_NAME" ]; then + echo "Error: Final AppImage file not found: $TARGET_NAME" + exit 1 + fi + echo "✓ Created $TARGET_NAME" + ls -lh "$TARGET_NAME" + + - name: Upload AppImage artifact (32-bit) + uses: actions/upload-artifact@v4 + with: + name: SDLPoP-Linux-AppImage-i686 + path: SDLPoP-*-i686.AppImage + retention-days: 30 + create-release: name: Create GitHub Release - needs: [build-linux, build-windows, build-windows-32, build-linux-arm64] + needs: [build-linux, build-linux-32, build-windows, build-windows-32, build-linux-arm64, build-linux-arm32] runs-on: ubuntu-latest if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') @@ -611,6 +1011,12 @@ jobs: name: SDLPoP-Linux-AppImage path: artifacts/linux-x86_64 + - name: Download Linux i686 artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Linux-AppImage-i686 + path: artifacts/linux-i686 + - name: Download Windows x86_64 artifact uses: actions/download-artifact@v4 with: @@ -629,14 +1035,22 @@ jobs: name: SDLPoP-Linux-ARM64 path: artifacts/linux-arm64 + - name: Download Linux ARM32 artifact + uses: actions/download-artifact@v4 + with: + name: SDLPoP-Linux-ARM32 + path: artifacts/linux-arm32 + - name: Upload to Release uses: softprops/action-gh-release@v1 with: files: | artifacts/linux-x86_64/* + artifacts/linux-i686/* artifacts/windows-x86_64/* artifacts/windows-x86/* artifacts/linux-arm64/* + artifacts/linux-arm32/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 30f9a0e8b397ffb08aaf90953430209fdc772532 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 15 Nov 2025 00:12:25 +0100 Subject: [PATCH 16/21] Fix Windows 32-bit build: Remove non-existent SDL2_image package, build from source instead --- .github/workflows/release.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7cbb742..bc5d62f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -326,6 +326,7 @@ jobs: - name: Install build dependencies for SDL2_image shell: msys2 {0} run: | + # Try to install build dependencies, but don't fail if some are missing pacman -S --noconfirm --needed \ mingw-w64-i686-autotools \ mingw-w64-i686-libpng \ @@ -334,7 +335,8 @@ jobs: mingw-w64-i686-libwebp \ wget \ tar \ - make || echo "Some packages may not be available" + make 2>&1 | grep -v "target not found" || true + echo "Build dependencies installation completed" - name: Build SDL2_image from source (32-bit) shell: msys2 {0} @@ -505,7 +507,12 @@ jobs: pkg-config \ wget \ file \ - fuse + fuse \ + autoconf \ + automake \ + libtool \ + cmake \ + ninja-build - name: Build SDL2 from source for ARM64 run: | @@ -514,7 +521,10 @@ jobs: wget -q https://github.com/libsdl-org/SDL/releases/download/release-2.30.0/SDL2-2.30.0.tar.gz tar -xzf SDL2-2.30.0.tar.gz cd SDL2-2.30.0 - ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static + export CC=aarch64-linux-gnu-gcc + export CXX=aarch64-linux-gnu-g++ + export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --disable-pkg-config make -j$(nproc) sudo make install @@ -522,7 +532,12 @@ jobs: wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz tar -xzf SDL2_image-2.8.2.tar.gz cd SDL2_image-2.8.2 - ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --with-sdl-prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static + export CC=aarch64-linux-gnu-gcc + export CXX=aarch64-linux-gnu-g++ + export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig + export SDL2_CFLAGS="-I/usr/aarch64-linux-gnu/include/SDL2" + export SDL2_LIBS="-L/usr/aarch64-linux-gnu/lib -lSDL2" + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --with-sdl-prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --disable-pkg-config make -j$(nproc) sudo make install From 66559baee38c987dc2780f6eec5509d5b791124d Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 15 Nov 2025 00:20:36 +0100 Subject: [PATCH 17/21] Fix ARM64 and Windows 32-bit builds: Unset PKG_CONFIG to prevent package installation errors --- .github/workflows/release.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc5d62f1..9f60ce51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -523,8 +523,10 @@ jobs: cd SDL2-2.30.0 export CC=aarch64-linux-gnu-gcc export CXX=aarch64-linux-gnu-g++ - export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig - ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --disable-pkg-config + # Unset PKG_CONFIG to prevent it from trying to find packages + unset PKG_CONFIG + unset PKG_CONFIG_PATH + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --without-x --disable-video-x11 make -j$(nproc) sudo make install @@ -534,10 +536,12 @@ jobs: cd SDL2_image-2.8.2 export CC=aarch64-linux-gnu-gcc export CXX=aarch64-linux-gnu-g++ - export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/lib/pkgconfig + # Unset PKG_CONFIG to prevent it from trying to find packages + unset PKG_CONFIG + unset PKG_CONFIG_PATH export SDL2_CFLAGS="-I/usr/aarch64-linux-gnu/include/SDL2" export SDL2_LIBS="-L/usr/aarch64-linux-gnu/lib -lSDL2" - ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --with-sdl-prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --disable-pkg-config + ./configure --host=aarch64-linux-gnu --prefix=/usr/aarch64-linux-gnu --with-sdl-prefix=/usr/aarch64-linux-gnu --disable-shared --enable-static --without-x --disable-png-shared --disable-jpg-shared --disable-tif-shared --disable-webp-shared make -j$(nproc) sudo make install From 5faf22ee75d5e68244f7e5e993596a461da39f83 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 15 Nov 2025 00:28:13 +0100 Subject: [PATCH 18/21] Fix Linux 32-bit and Windows 32-bit builds: Build SDL2 from source for Linux 32-bit, fix tar symlink issue for Windows --- .github/workflows/release.yml | 88 ++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f60ce51..325bfefd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -344,7 +344,8 @@ jobs: # Download and build SDL2_image for 32-bit if package not available cd /tmp wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz - tar -xzf SDL2_image-2.8.2.tar.gz + # Use tar with --no-same-owner to avoid symlink issues on Windows/MSYS2 + tar -xzf SDL2_image-2.8.2.tar.gz --no-same-owner 2>&1 | grep -v "Cannot create symlink" || true cd SDL2_image-2.8.2 export PATH=/mingw32/bin:$PATH export CC=i686-w64-mingw32-gcc @@ -856,11 +857,43 @@ jobs: build-essential \ gcc-multilib \ g++-multilib \ - libsdl2-dev:i386 \ - libsdl2-image-dev:i386 \ wget \ file \ - fuse + fuse \ + autoconf \ + automake \ + libtool \ + cmake \ + ninja-build + + - name: Build SDL2 from source for 32-bit + run: | + # Download and build SDL2 for 32-bit + cd /tmp + wget -q https://github.com/libsdl-org/SDL/releases/download/release-2.30.0/SDL2-2.30.0.tar.gz + tar -xzf SDL2-2.30.0.tar.gz + cd SDL2-2.30.0 + export CC="gcc -m32" + export CXX="g++ -m32" + unset PKG_CONFIG + unset PKG_CONFIG_PATH + ./configure --prefix=/usr/i386-linux-gnu --disable-shared --enable-static --without-x --disable-video-x11 + make -j$(nproc) + sudo make install + + # Download and build SDL2_image for 32-bit + wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz + tar -xzf SDL2_image-2.8.2.tar.gz + cd SDL2_image-2.8.2 + export CC="gcc -m32" + export CXX="g++ -m32" + unset PKG_CONFIG + unset PKG_CONFIG_PATH + export SDL2_CFLAGS="-I/usr/i386-linux-gnu/include/SDL2" + export SDL2_LIBS="-L/usr/i386-linux-gnu/lib -lSDL2" + ./configure --prefix=/usr/i386-linux-gnu --with-sdl-prefix=/usr/i386-linux-gnu --disable-shared --enable-static --without-x --disable-png-shared --disable-jpg-shared --disable-tif-shared --disable-webp-shared + make -j$(nproc) + sudo make install - name: Build SDLPoP (32-bit) working-directory: src @@ -868,33 +901,34 @@ jobs: # Set up 32-bit compilation export CC="gcc -m32" export CXX="g++ -m32" - export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig + export SDL2_CFLAGS="-I/usr/i386-linux-gnu/include/SDL2" + export SDL2_LIBS="-L/usr/i386-linux-gnu/lib -lSDL2 -lSDL2_image" # Compile all source files (32-bit) - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c main.c -o main.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c data.c -o data.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg000.c -o seg000.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg001.c -o seg001.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg002.c -o seg002.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg003.c -o seg003.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg004.c -o seg004.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg005.c -o seg005.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg006.c -o seg006.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg007.c -o seg007.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg008.c -o seg008.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seg009.c -o seg009.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c seqtbl.c -o seqtbl.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c replay.c -o replay.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c options.c -o options.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c lighting.c -o lighting.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c screenshot.c -o screenshot.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c menu.c -o menu.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c midi.c -o midi.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c opl3.c -o opl3.o - $CC -Wall -std=c99 -O3 -ffast-math $(pkg-config --cflags sdl2 SDL2_image) -c stb_vorbis.c -o stb_vorbis.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c main.c -o main.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c data.c -o data.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg000.c -o seg000.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg001.c -o seg001.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg002.c -o seg002.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg003.c -o seg003.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg004.c -o seg004.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg005.c -o seg005.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg006.c -o seg006.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg007.c -o seg007.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg008.c -o seg008.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg009.c -o seg009.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seqtbl.c -o seqtbl.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c replay.c -o replay.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c options.c -o options.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c lighting.c -o lighting.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c screenshot.c -o screenshot.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c menu.c -o menu.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c midi.c -o midi.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c opl3.c -o opl3.o + $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c stb_vorbis.c -o stb_vorbis.o # Link (32-bit) - $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $(pkg-config --libs sdl2 SDL2_image) -lm -o ../prince + $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $SDL2_LIBS -lm -o ../prince - name: Verify build output (32-bit) run: | From 77cff823d2ae2903bbde9e5a5e08e78256414ef0 Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 15 Nov 2025 01:25:47 +0100 Subject: [PATCH 19/21] Remove Windows x86 (32-bit) and Linux AppImage 32-bit build jobs --- .github/workflows/release.yml | 418 +--------------------------------- 1 file changed, 1 insertion(+), 417 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 325bfefd..38f4c619 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -297,199 +297,6 @@ jobs: path: SDLPoP-*.zip retention-days: 30 - build-windows-32: - name: Build Windows x86 (32-bit) - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install MSYS2 and dependencies - uses: msys2/setup-msys2@v2 - with: - update: true - install: >- - mingw-w64-i686-gcc - mingw-w64-i686-SDL2 - make - zip - - - name: Check available SDL2_image packages - shell: msys2 {0} - run: | - echo "Searching for SDL2_image packages..." - pacman -Ss SDL2_image | grep i686 || echo "No i686 SDL2_image found" - echo "Available SDL packages:" - pacman -Ss SDL2 | grep i686 || echo "No i686 SDL2 packages found" - - - name: Install build dependencies for SDL2_image - shell: msys2 {0} - run: | - # Try to install build dependencies, but don't fail if some are missing - pacman -S --noconfirm --needed \ - mingw-w64-i686-autotools \ - mingw-w64-i686-libpng \ - mingw-w64-i686-libjpeg-turbo \ - mingw-w64-i686-libtiff \ - mingw-w64-i686-libwebp \ - wget \ - tar \ - make 2>&1 | grep -v "target not found" || true - echo "Build dependencies installation completed" - - - name: Build SDL2_image from source (32-bit) - shell: msys2 {0} - run: | - # Download and build SDL2_image for 32-bit if package not available - cd /tmp - wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz - # Use tar with --no-same-owner to avoid symlink issues on Windows/MSYS2 - tar -xzf SDL2_image-2.8.2.tar.gz --no-same-owner 2>&1 | grep -v "Cannot create symlink" || true - cd SDL2_image-2.8.2 - export PATH=/mingw32/bin:$PATH - export CC=i686-w64-mingw32-gcc - export PKG_CONFIG_PATH=/mingw32/lib/pkgconfig - ./configure --host=i686-w64-mingw32 --prefix=/mingw32 --with-sdl-prefix=/mingw32 --disable-shared --enable-static - make -j$(nproc) || make - make install || echo "Install may have warnings but should work" - - - name: Build SDLPoP (32-bit) - shell: msys2 {0} - working-directory: src - run: | - export SDL2=/mingw32 - export SDL2INC=/mingw32/include/SDL2 - export SDL2LIB=/mingw32/lib - export PATH=/mingw32/bin:$PATH - - # Compile all source files (32-bit) - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c main.c -o main.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c data.c -o data.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg000.c -o seg000.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg001.c -o seg001.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg002.c -o seg002.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg003.c -o seg003.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg004.c -o seg004.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg005.c -o seg005.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg006.c -o seg006.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg007.c -o seg007.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg008.c -o seg008.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seg009.c -o seg009.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c seqtbl.c -o seqtbl.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c replay.c -o replay.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c options.c -o options.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c lighting.c -o lighting.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c screenshot.c -o screenshot.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c menu.c -o menu.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c midi.c -o midi.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c opl3.c -o opl3.o - gcc -Wall -std=c99 -O3 -ffast-math -I"$SDL2INC" -I"$SDL2INC/../include" -c stb_vorbis.c -o stb_vorbis.o - - # Link (32-bit) - use static SDL2_image if available - if [ -f "$SDL2LIB/libSDL2_image.a" ]; then - echo "Linking with static SDL2_image" - gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe - else - echo "Linking with dynamic SDL2_image" - gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"$SDL2LIB" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ../prince.exe - fi - - - name: Verify build output (32-bit) - shell: msys2 {0} - run: | - if [ ! -f prince.exe ]; then - echo "Error: prince.exe not found in root directory" - echo "Contents of root directory:" - ls -la - echo "Contents of src directory:" - ls -la src/ - exit 1 - fi - echo "✓ Build successful: prince.exe found" - file prince.exe - ls -lh prince.exe - - - name: Create release package (32-bit) - shell: msys2 {0} - working-directory: . - run: | - version="${{ github.ref_name }}" - version=${version#v} # Remove 'v' prefix if present - zipName="SDLPoP-${version}-Windows-x86.zip" - - # Create release directory - mkdir -p release - - # Verify and copy executable - if [ ! -f prince.exe ]; then - echo "Error: prince.exe not found" - exit 1 - fi - cp prince.exe release/ - echo "✓ Copied prince.exe" - - # Verify and copy DLLs from MSYS2 installation (32-bit) - if [ ! -f /mingw32/bin/SDL2.dll ]; then - echo "Error: SDL2.dll not found at /mingw32/bin/SDL2.dll" - echo "Searching for SDL2.dll..." - find /mingw32 -name "SDL2.dll" 2>/dev/null || true - exit 1 - fi - cp /mingw32/bin/SDL2.dll release/ - echo "✓ Copied SDL2.dll" - - # SDL2_image might be statically linked, so DLL may not exist - if [ -f /mingw32/bin/SDL2_image.dll ]; then - cp /mingw32/bin/SDL2_image.dll release/ - echo "✓ Copied SDL2_image.dll" - else - echo "Note: SDL2_image is statically linked, no DLL needed" - fi - - # Verify and copy data directory - if [ ! -d data ]; then - echo "Error: data directory not found" - exit 1 - fi - cp -r data release/ - echo "✓ Copied data directory" - - # Copy config file (optional) - if [ -f SDLPoP.ini ]; then - cp SDLPoP.ini release/ - echo "✓ Copied SDLPoP.ini" - else - echo "# SDLPoP Configuration" > release/SDLPoP.ini - echo "✓ Created default SDLPoP.ini" - fi - - # Copy README (optional) - if [ -f doc/Readme.txt ]; then - cp doc/Readme.txt release/README.txt - echo "✓ Copied README.txt" - fi - - # Create zip using zip command - cd release - zip -r "../$zipName" . -q - cd .. - - if [ ! -f "$zipName" ]; then - echo "Error: Failed to create zip file" - exit 1 - fi - - echo "✓ Created $zipName" - ls -lh "$zipName" - - - name: Upload Windows 32-bit zip artifact - uses: actions/upload-artifact@v4 - with: - name: SDLPoP-Windows-x86 - path: SDLPoP-*-Windows-x86.zip - retention-days: 30 - build-linux-arm64: name: Build Linux ARM64 runs-on: ubuntu-latest @@ -842,218 +649,9 @@ jobs: path: SDLPoP-*-Linux-ARM32.tar.gz retention-days: 30 - build-linux-32: - name: Build Linux AppImage (32-bit) - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies and multilib - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - gcc-multilib \ - g++-multilib \ - wget \ - file \ - fuse \ - autoconf \ - automake \ - libtool \ - cmake \ - ninja-build - - - name: Build SDL2 from source for 32-bit - run: | - # Download and build SDL2 for 32-bit - cd /tmp - wget -q https://github.com/libsdl-org/SDL/releases/download/release-2.30.0/SDL2-2.30.0.tar.gz - tar -xzf SDL2-2.30.0.tar.gz - cd SDL2-2.30.0 - export CC="gcc -m32" - export CXX="g++ -m32" - unset PKG_CONFIG - unset PKG_CONFIG_PATH - ./configure --prefix=/usr/i386-linux-gnu --disable-shared --enable-static --without-x --disable-video-x11 - make -j$(nproc) - sudo make install - - # Download and build SDL2_image for 32-bit - wget -q https://github.com/libsdl-org/SDL_image/releases/download/release-2.8.2/SDL2_image-2.8.2.tar.gz - tar -xzf SDL2_image-2.8.2.tar.gz - cd SDL2_image-2.8.2 - export CC="gcc -m32" - export CXX="g++ -m32" - unset PKG_CONFIG - unset PKG_CONFIG_PATH - export SDL2_CFLAGS="-I/usr/i386-linux-gnu/include/SDL2" - export SDL2_LIBS="-L/usr/i386-linux-gnu/lib -lSDL2" - ./configure --prefix=/usr/i386-linux-gnu --with-sdl-prefix=/usr/i386-linux-gnu --disable-shared --enable-static --without-x --disable-png-shared --disable-jpg-shared --disable-tif-shared --disable-webp-shared - make -j$(nproc) - sudo make install - - - name: Build SDLPoP (32-bit) - working-directory: src - run: | - # Set up 32-bit compilation - export CC="gcc -m32" - export CXX="g++ -m32" - export SDL2_CFLAGS="-I/usr/i386-linux-gnu/include/SDL2" - export SDL2_LIBS="-L/usr/i386-linux-gnu/lib -lSDL2 -lSDL2_image" - - # Compile all source files (32-bit) - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c main.c -o main.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c data.c -o data.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg000.c -o seg000.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg001.c -o seg001.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg002.c -o seg002.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg003.c -o seg003.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg004.c -o seg004.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg005.c -o seg005.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg006.c -o seg006.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg007.c -o seg007.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg008.c -o seg008.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seg009.c -o seg009.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c seqtbl.c -o seqtbl.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c replay.c -o replay.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c options.c -o options.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c lighting.c -o lighting.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c screenshot.c -o screenshot.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c menu.c -o menu.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c midi.c -o midi.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c opl3.c -o opl3.o - $CC -Wall -std=c99 -O3 -ffast-math $SDL2_CFLAGS -c stb_vorbis.c -o stb_vorbis.o - - # Link (32-bit) - $CC main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o $SDL2_LIBS -lm -o ../prince - - - name: Verify build output (32-bit) - run: | - if [ ! -f prince ]; then - echo "Error: prince binary not found in root directory" - echo "Contents of root directory:" - ls -la - echo "Contents of src directory:" - ls -la src/ - exit 1 - fi - echo "✓ Build successful: prince binary found" - file prince - # Verify it's 32-bit - if ! file prince | grep -q "32-bit"; then - echo "Warning: Binary might not be 32-bit" - fi - - - name: Download linuxdeploy (i686) - run: | - wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-i686.AppImage - chmod +x linuxdeploy-i686.AppImage - - - name: Create AppDir structure - run: | - mkdir -p AppDir/usr/bin - mkdir -p AppDir/usr/share/applications - mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps - - # Copy binary - if [ ! -f prince ]; then - echo "Error: prince binary not found" - exit 1 - fi - cp prince AppDir/usr/bin/ - chmod +x AppDir/usr/bin/prince - - # Copy data directory - if [ ! -d data ]; then - echo "Error: data directory not found" - exit 1 - fi - cp -r data AppDir/usr/bin/ - - # Copy config file (optional) - if [ -f SDLPoP.ini ]; then - cp SDLPoP.ini AppDir/usr/bin/ - else - echo "# SDLPoP Configuration" > AppDir/usr/bin/SDLPoP.ini - fi - - # Copy README (optional) - if [ -f doc/Readme.txt ]; then - cp doc/Readme.txt AppDir/usr/bin/README.txt - else - echo "SDLPoP" > AppDir/usr/bin/README.txt - fi - - echo "✓ AppDir structure created successfully" - - - name: Create desktop file - run: | - cat > AppDir/usr/share/applications/sdlpop.desktop << 'EOF' - [Desktop Entry] - Type=Application - Name=SDLPoP - Comment=Prince of Persia port - Exec=prince - Icon=sdlpop - Categories=Game; - EOF - - - name: Copy icon (if exists) - run: | - if [ -f data/icon.png ]; then - cp data/icon.png AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png - fi - - - name: Create AppImage (32-bit) - env: - VERSION: ${{ github.ref_name }} - run: | - export VERSION=${VERSION#v} # Remove 'v' prefix if present - export ARCH=i686 - ./linuxdeploy-i686.AppImage \ - --appdir AppDir \ - --executable AppDir/usr/bin/prince \ - --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ - --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ - --output appimage - - # Find the created AppImage and rename if needed - APPIMAGE=$(ls SDLPoP-*.AppImage 2>/dev/null | head -n 1) - TARGET_NAME="SDLPoP-${VERSION}-i686.AppImage" - - if [ -z "$APPIMAGE" ]; then - echo "Error: No AppImage file found" - exit 1 - fi - - if [ "$APPIMAGE" != "$TARGET_NAME" ]; then - echo "Renaming $APPIMAGE to $TARGET_NAME" - mv "$APPIMAGE" "$TARGET_NAME" - else - echo "AppImage already has correct name: $TARGET_NAME" - fi - - # Verify the final file exists - if [ ! -f "$TARGET_NAME" ]; then - echo "Error: Final AppImage file not found: $TARGET_NAME" - exit 1 - fi - echo "✓ Created $TARGET_NAME" - ls -lh "$TARGET_NAME" - - - name: Upload AppImage artifact (32-bit) - uses: actions/upload-artifact@v4 - with: - name: SDLPoP-Linux-AppImage-i686 - path: SDLPoP-*-i686.AppImage - retention-days: 30 - create-release: name: Create GitHub Release - needs: [build-linux, build-linux-32, build-windows, build-windows-32, build-linux-arm64, build-linux-arm32] + needs: [build-linux, build-windows, build-linux-arm64, build-linux-arm32] runs-on: ubuntu-latest if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') @@ -1064,24 +662,12 @@ jobs: name: SDLPoP-Linux-AppImage path: artifacts/linux-x86_64 - - name: Download Linux i686 artifact - uses: actions/download-artifact@v4 - with: - name: SDLPoP-Linux-AppImage-i686 - path: artifacts/linux-i686 - - name: Download Windows x86_64 artifact uses: actions/download-artifact@v4 with: name: SDLPoP-Windows-x86_64 path: artifacts/windows-x86_64 - - name: Download Windows x86 artifact - uses: actions/download-artifact@v4 - with: - name: SDLPoP-Windows-x86 - path: artifacts/windows-x86 - - name: Download Linux ARM64 artifact uses: actions/download-artifact@v4 with: @@ -1099,9 +685,7 @@ jobs: with: files: | artifacts/linux-x86_64/* - artifacts/linux-i686/* artifacts/windows-x86_64/* - artifacts/windows-x86/* artifacts/linux-arm64/* artifacts/linux-arm32/* env: From f0ac26ad2e78f64c70446e6b035652921ad706af Mon Sep 17 00:00:00 2001 From: Dhiego Date: Tue, 18 Nov 2025 19:06:26 +0100 Subject: [PATCH 20/21] Fix Windows 32-bit build: Update workflow to correctly handle SDL2_image package installation and improve build process. --- .github/workflows/release.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38f4c619..8c7a76a6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,6 +51,11 @@ jobs: wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage + - name: Download linuxdeploy SDL2 plugin + run: | + wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-sdl2/releases/download/continuous/linuxdeploy-plugin-sdl2-x86_64.AppImage + chmod +x linuxdeploy-plugin-sdl2-x86_64.AppImage + - name: Create AppDir structure run: | mkdir -p AppDir/usr/bin @@ -117,6 +122,7 @@ jobs: --executable AppDir/usr/bin/prince \ --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ + --plugin sdl2 \ --output appimage # Find the created AppImage and rename if needed @@ -142,6 +148,15 @@ jobs: fi echo "✓ Created $TARGET_NAME" ls -lh "$TARGET_NAME" + + # Verify that SDL2 libraries are bundled + echo "Verifying bundled libraries..." + ./"$TARGET_NAME" --appimage-extract 2>/dev/null || true + if [ -d "squashfs-root" ]; then + echo "Checking for SDL2 libraries in AppImage..." + find squashfs-root/usr/lib -name "*SDL2*" -type f 2>/dev/null | head -10 || echo "Note: Libraries may be in different location" + rm -rf squashfs-root + fi - name: Upload AppImage artifact uses: actions/upload-artifact@v4 From 0e308d705a850b44eb8fa1b19538c41f5083e48b Mon Sep 17 00:00:00 2001 From: Dhiego Date: Sat, 22 Nov 2025 13:23:58 +0100 Subject: [PATCH 21/21] Enhance release workflow: Add curl for better plugin download handling and improve SDL2 plugin detection logic. Remove obsolete build scripts for PowerShell and batch processing. --- .github/workflows/release.yml | 50 +++++++++++++++--- src/build.bat | 75 --------------------------- src/build_and_run.ps1 | 71 ------------------------- src/build_simple.bat | 97 ----------------------------------- 4 files changed, 44 insertions(+), 249 deletions(-) delete mode 100644 src/build.bat delete mode 100644 src/build_and_run.ps1 delete mode 100644 src/build_simple.bat diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c7a76a6..5908573b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,7 @@ jobs: libsdl2-dev \ libsdl2-image-dev \ wget \ + curl \ file \ fuse @@ -53,8 +54,25 @@ jobs: - name: Download linuxdeploy SDL2 plugin run: | - wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-sdl2/releases/download/continuous/linuxdeploy-plugin-sdl2-x86_64.AppImage - chmod +x linuxdeploy-plugin-sdl2-x86_64.AppImage + # Try to download the plugin with curl (better redirect handling) + # If it fails, we'll use linuxdeploy's built-in SDL2 detection + if curl -L -f -o linuxdeploy-plugin-sdl2-x86_64.AppImage https://github.com/linuxdeploy/linuxdeploy-plugin-sdl2/releases/download/continuous/linuxdeploy-plugin-sdl2-x86_64.AppImage; then + chmod +x linuxdeploy-plugin-sdl2-x86_64.AppImage + echo "✓ SDL2 plugin downloaded successfully" + echo "PLUGIN_AVAILABLE=true" >> $GITHUB_ENV + else + echo "⚠ SDL2 plugin not available at continuous release" + echo "⚠ Trying alternative: latest release..." + # Try latest release instead of continuous + if curl -L -f -o linuxdeploy-plugin-sdl2-x86_64.AppImage https://github.com/linuxdeploy/linuxdeploy-plugin-sdl2/releases/latest/download/linuxdeploy-plugin-sdl2-x86_64.AppImage; then + chmod +x linuxdeploy-plugin-sdl2-x86_64.AppImage + echo "✓ SDL2 plugin downloaded from latest release" + echo "PLUGIN_AVAILABLE=true" >> $GITHUB_ENV + else + echo "⚠ SDL2 plugin not available, linuxdeploy will use built-in SDL2 detection" + echo "PLUGIN_AVAILABLE=false" >> $GITHUB_ENV + fi + fi - name: Create AppDir structure run: | @@ -117,13 +135,33 @@ jobs: run: | export VERSION=${VERSION#v} # Remove 'v' prefix if present export ARCH=x86_64 - ./linuxdeploy-x86_64.AppImage \ + + # Build linuxdeploy command + LINUXDEPLOY_CMD="./linuxdeploy-x86_64.AppImage \ --appdir AppDir \ --executable AppDir/usr/bin/prince \ --desktop-file AppDir/usr/share/applications/sdlpop.desktop \ - --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png \ - --plugin sdl2 \ - --output appimage + --icon-file AppDir/usr/share/icons/hicolor/256x256/apps/sdlpop.png" + + # Add plugin flag only if plugin is available + if [ "$PLUGIN_AVAILABLE" = "true" ] && [ -f linuxdeploy-plugin-sdl2-x86_64.AppImage ]; then + LINUXDEPLOY_CMD="$LINUXDEPLOY_CMD --plugin sdl2" + echo "Using SDL2 plugin" + else + echo "Using linuxdeploy built-in SDL2 detection" + # Manually copy SDL2 libraries if plugin is not available + if [ -f /usr/lib/x86_64-linux-gnu/libSDL2.so ]; then + mkdir -p AppDir/usr/lib + cp /usr/lib/x86_64-linux-gnu/libSDL2*.so* AppDir/usr/lib/ 2>/dev/null || true + cp /usr/lib/x86_64-linux-gnu/libSDL2_image*.so* AppDir/usr/lib/ 2>/dev/null || true + echo "Copied SDL2 libraries manually" + fi + fi + + LINUXDEPLOY_CMD="$LINUXDEPLOY_CMD --output appimage" + + # Execute linuxdeploy + eval $LINUXDEPLOY_CMD # Find the created AppImage and rename if needed APPIMAGE=$(ls SDLPoP-*.AppImage 2>/dev/null | head -n 1) diff --git a/src/build.bat b/src/build.bat deleted file mode 100644 index ca6a598d..00000000 --- a/src/build.bat +++ /dev/null @@ -1,75 +0,0 @@ -@echo off -setlocal -cd %~dp0 - -:: Check that we have access to the MSVC compiler. - -where /q cl -if ERRORLEVEL 1 ( - echo Problem^: the MSVC compiler ^(cl^) cannot not found. - echo The solution is to run vcvarsall.bat, which sets the necessary environment variables. - echo, - echo Example command for VS2017^: - echo call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 - echo, - echo Example command for VS2015^: - echo call "%%VS140COMNTOOLS%%..\..\VC\vcvarsall.bat" x86 - exit /b -) - -:: To override the directory for SDL2 library files, simply set the SDL environment variable in the command shell. -:: (You could do that from the command-line, or from a wrapper script that calls this one.) - -if [%SDL2%]==[] ( - set SDL2=..\..\SDL2-2.0.6 -) - -if not exist %SDL2% ( - echo Problem^: Could not find SDL2 directory. - echo Tried to look here^: %SDL2% - echo, - echo To specify the SDL2 directory, set the SDL2 environment variable. - echo Example command: - echo set "SDL2=C:\work\libraries\SDL2-2.0.6" - exit /b -) - -:: To choose the build configuration, specify either "debug" or "release" as command-line parameter for this build script. - -if [%1]==[debug] goto build_type_debug -if [%1]==[release] goto build_type_release -echo Build type not specified, compiling in release mode... -echo To specify the build type, run as "build.bat debug" or "build.bat release". -goto build_type_release - -:build_type_debug -set BuildTypeCompilerFlags= /MTd /Od /Z7 -set PreprocessorDefinitions= -DDEBUG=1 -goto compile - -:build_type_release -set BuildTypeCompilerFlags= /MT /O2 -set PreprocessorDefinitions= - -:compile -set SourceFiles= main.c data.c seg000.c seg001.c seg002.c seg003.c seg004.c seg005.c seg006.c seg007.c seg008.c seg009.c seqtbl.c replay.c options.c lighting.c screenshot.c menu.c midi.c opl3.c stb_vorbis.c -set CommonCompilerFlags= /nologo /MP /fp:fast /GR- /wd4048 %PreprocessorDefinitions% /I"%SDL2%\include" -set CommonLinkerFlags= /subsystem:windows,5.01 /libpath:"%SDL2%\lib\%VSCMD_ARG_TGT_ARCH%" SDL2main.lib SDL2.lib SDL2_image.lib Shell32.lib icon.res /out:..\prince.exe - -rc /nologo /fo icon.res icon.rc -cl %BuildTypeCompilerFlags% %CommonCompilerFlags% %SourceFiles% /link %CommonLinkerFlags% - -if %ERRORLEVEL% == 0 (goto success) -echo There were errors. -goto cleanup - -:success -echo Output: ..\prince.exe - -:cleanup -if [%1]==[debug] exit /b -del icon.res 2> NUL -del *.obj 2> NUL -del ..\prince.exp 2> NUL -del ..\prince.lib 2> NUL -del ..\prince.pdb 2> NUL diff --git a/src/build_and_run.ps1 b/src/build_and_run.ps1 deleted file mode 100644 index 38ae7ff5..00000000 --- a/src/build_and_run.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -# Fast incremental build and run script -# Only compiles changed files and launches game once - -$env:Path = "C:\msys64\mingw64\bin;" + $env:Path -$SDL2 = "C:\msys64\mingw64" -$SDL2INC = "$SDL2\include\SDL2" -$SDL2LIB = "$SDL2\lib" - -$ErrorActionPreference = "Stop" - -# Change to script directory (src) -if ($PSScriptRoot) { - Set-Location $PSScriptRoot -} else { - # If run from src directory directly - if (Test-Path "build_and_run.ps1") { - Set-Location $PWD - } -} - -Write-Host "Building SDLPoP (incremental)..." -ForegroundColor Green - -# Compile only changed files (GCC will skip if .o is newer than .c) -$files = @( - "main.c", "data.c", "seg000.c", "seg001.c", "seg002.c", "seg003.c", - "seg004.c", "seg005.c", "seg006.c", "seg007.c", "seg008.c", "seg009.c", - "seqtbl.c", "replay.c", "options.c", "lighting.c", "screenshot.c", - "menu.c", "midi.c", "opl3.c", "stb_vorbis.c" -) - -$compileFlags = @("-Wall", "-std=c99", "-O3", "-ffast-math", "-I$SDL2INC", "-I$SDL2INC\SDL2") -$objectFiles = @() - -foreach ($file in $files) { - $obj = $file -replace '\.c$', '.o' - $objectFiles += $obj - - # Check if we need to compile (file changed or .o doesn't exist) - $cFile = Get-Item $file -ErrorAction SilentlyContinue - $oFile = Get-Item $obj -ErrorAction SilentlyContinue - - if (-not $oFile -or $cFile.LastWriteTime -gt $oFile.LastWriteTime) { - Write-Host " Compiling $file..." -ForegroundColor Yellow - & gcc $compileFlags -c $file -o $obj - if ($LASTEXITCODE -ne 0) { - Write-Host "Build failed!" -ForegroundColor Red - exit 1 - } - } -} - -Write-Host "Linking..." -ForegroundColor Green -$linkFlags = @("-mwindows", "-L$SDL2LIB", "-Wl,--whole-archive", "-lSDL2main", "-Wl,--no-whole-archive", "-lSDL2", "-lSDL2_image", "-lm") -& gcc $objectFiles $linkFlags -o ..\prince.exe - -if ($LASTEXITCODE -ne 0) { - Write-Host "Build failed!" -ForegroundColor Red - exit 1 -} - -Write-Host "`n=== BUILD SUCCESSFUL ===" -ForegroundColor Green - -# Close any existing game instance -Get-Process prince -ErrorAction SilentlyContinue | Stop-Process -Force -Start-Sleep -Milliseconds 200 - -# Launch game once -Set-Location .. -Write-Host "Running prince.exe...`n" -ForegroundColor Green -Start-Process -FilePath ".\prince.exe" -WorkingDirectory $PWD - diff --git a/src/build_simple.bat b/src/build_simple.bat deleted file mode 100644 index aa444a05..00000000 --- a/src/build_simple.bat +++ /dev/null @@ -1,97 +0,0 @@ -@echo off -setlocal -cd /d %~dp0 - -if [%SDL2%]==[] ( - set SDL2=C:\msys64\mingw64 -) -set SDL2INC=%SDL2%\include\SDL2 -set SDL2LIB=%SDL2%\lib -set CPPFLAGS=-I"%SDL2INC%" - -set PATH=C:\msys64\mingw64\bin;%PATH% - -echo Building SDLPoP... -echo. - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -I"%SDL2INC%\..\include" -c main.c -o main.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c data.c -o data.o - -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg000.c -o seg000.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg001.c -o seg001.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg002.c -o seg002.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg003.c -o seg003.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg004.c -o seg004.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg005.c -o seg005.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg006.c -o seg006.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg007.c -o seg007.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg008.c -o seg008.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c seg009.c -o seg009.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c seqtbl.c -o seqtbl.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c replay.c -o replay.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c options.c -o options.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c lighting.c -o lighting.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c screenshot.c -o screenshot.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -I"%SDL2INC%\SDL2" -c menu.c -o menu.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c midi.c -o midi.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c opl3.c -o opl3.o -if errorlevel 1 goto error - -gcc -Wall -std=c99 -O3 -ffast-math -I"%SDL2INC%" -c stb_vorbis.c -o stb_vorbis.o -if errorlevel 1 goto error - -echo. -echo Linking... -gcc -mwindows main.o data.o seg000.o seg001.o seg002.o seg003.o seg004.o seg005.o seg006.o seg007.o seg008.o seg009.o seqtbl.o replay.o options.o lighting.o screenshot.o menu.o midi.o opl3.o stb_vorbis.o -L"%SDL2LIB%" -Wl,--whole-archive -lSDL2main -Wl,--no-whole-archive -lSDL2 -lSDL2_image -lm -o ..\prince.exe -if errorlevel 1 goto error - -echo. -echo Build successful! prince.exe created in parent directory. -goto end - -:error -echo. -echo Build failed! -exit /b 1 - -:end -endlocal -