diff --git a/INSTALL.md b/INSTALL.md
index d18f38d..b37dd3b 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -32,3 +32,6 @@ To create a package, use the *package* target from cmake invocation: `cmake --bu
## Releasing
Update NEWS.yaml to add a new version and the changelog.
Then, run `./tools/create_release.sh -v 3.1.5` to update all the files necessary for the release.
+
+## Audio sounds
+The ogg sounds have been created using LMMS DAW. You can install it on your OS using your package manager. To generate the ogg files in command line, run `lmms render input.mmpz -f ogg -b 160 -o output.ogg`
diff --git a/NEWS.yaml b/NEWS.yaml
index f60c0a5..24c76bc 100644
--- a/NEWS.yaml
+++ b/NEWS.yaml
@@ -1,4 +1,9 @@
---
+Version: 4.0.0
+Date: 2026-xx-xx
+Description:
+ - New audio theme (menu and in game music) created by zabidenhtf (Mykyta Polishyk)
+---
Version: 3.1.6
Date: 2025-12-24
Description:
diff --git a/README.md b/README.md
index 0b4ccad..6d3fb17 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,8 @@ You drive a toy wooden train on many levels and you must collect all the wagons
German, Italian, Japanese, Korean, Portuguese, Russian, Slovak,
Spanish, Swedish, Polish, Turkish, Hungarian, Dutch.
- Colorful animated wood engine.
-- 50 levels in this first version
-- 3 beautiful musics and many sound effects.
+- 50 levels in this first version.
+- 2 themes with 3 beautiful pieces of music and many sound effects.
## Screenshots

@@ -21,7 +21,7 @@ Main menu
Gameplay
## Building
-Information on how to compile Li-Ri is available in the INSTALL file.
+Information on how to compile Li-Ri is available in the INSTALL.md file.
## Thanks
* Christian H. and Adrian F. for the German correction.
@@ -43,14 +43,16 @@ Information on how to compile Li-Ri is available in the INSTALL file.
-------------
-Copyright (c) 2023
+Copyright (c) 2023-2026
Johnny Jazeix: port to SDL2 + android + cmake
+Copyright (c) 2026
+Polishyk Mykyta "zabidentwfan@ukr.net": musics ("menu\_zabiden.ogg", "ingame1\_zabiden.ogg", "ingame2\_zabiden.ogg")
+
Copyright (c) 2006
Dominique Roux-Serret: design & programming & graphics & website.
-Maf464: musics
-Copyright (c) 2006 (for "menu.mod", "ingame1.xm", "ingame2.xm") MAF464 (email charcosset.b@free.fr; website http://maf464.free.fr). This music licensed under GPL license. See COPYING for details
+Copyright (c) 2006 Maf464 (email charcosset.b@free.fr; website http://maf464.free.fr): ("menu\_maf.ogg", "ingame1\_maf.ogg", "ingame2\_maf.ogg")
[
// for SDL_GetError
#include // for SDL_LogInfo, SDL_LOG_CATEGORY_APPLICATION
#include
+#include
#include "audio.h"
#include "utils.h"
@@ -51,7 +52,7 @@ bool Audio::Init()
{
char PathFile[512];
- if (Mix_OpenAudio(22050, AUDIO_S16, 1, 1024)) {
+ if (Mix_OpenAudio(44100, AUDIO_S16, 1, 1024)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Enable to init Sound card: %s", SDL_GetError());
return false;
}
@@ -97,10 +98,6 @@ bool Audio::Init()
Utils::GetPath(PathFile);
Sound[sLive] = Mix_LoadWAV(PathFile);
- strcpy(PathFile, "Sounds/menu.mod");
- Utils::GetPath(PathFile);
- Music = Mix_LoadMUS(PathFile);
-
return true;
}
@@ -108,7 +105,7 @@ bool Audio::Init()
/*********************************************************************/
void Audio::LoadMusic(int Num)
{
- char Provi[512] = "Sounds/ingame1.xm";
+ char Provi[512];
if (!N) {
return;
@@ -118,18 +115,32 @@ void Audio::LoadMusic(int Num)
if (Music) {
PauseMusic(true);
- Mix_HaltMusic(); // Stops the music
+ Mix_HaltMusic();
Mix_FreeMusic(Music);
Music = nullptr;
}
- if (Num == 0) { // if menu music
- strcpy(Provi, "Sounds/menu.mod");
+ if (Num == 0) { // menu music
+ switch (Pref.AudioTheme) {
+ case mMaf:
+ strcpy(Provi, "Sounds/menu_maf.mod");
+ break;
+ case mZabiden:
+ strcpy(Provi, "Sounds/menu_zabiden.ogg");
+ break;
+ }
Utils::GetPath(Provi);
Music = Mix_LoadMUS(Provi);
}
- else {
- Provi[13] = (char)(Num) + '0';
+ else { // in game music
+ switch (Pref.AudioTheme) {
+ case mMaf:
+ sprintf(Provi, "Sounds/ingame%d_maf.xm", Num);
+ break;
+ case mZabiden:
+ sprintf(Provi, "Sounds/ingame%d_zabiden.ogg", Num);
+ break;
+ }
Utils::GetPath(Provi);
Music = Mix_LoadMUS(Provi);
}
diff --git a/src/main.cc b/src/main.cc
index 49d5fab..5d07995 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -126,7 +126,9 @@ int main(int narg, char *argv[])
exit(-1);
}
- audio.PlayMusic();
+ // Start background music
+ audio.LoadMusic(0);
+
Mouse mouse { audio, screen };
mouse.InitStart();
diff --git a/src/menu.cc b/src/menu.cc
index cfc79b1..4338f9c 100644
--- a/src/menu.cc
+++ b/src/menu.cc
@@ -359,7 +359,7 @@ void Menu::InitMain_Options()
// Set background image and build display
Sprites[background_menu].Draw(400, 300, 0, Sprites[fmenu].Image[0]);
Sprites[gmenu].Draw(400, 300, 0, Sprites[fmenu].Image[0]);
- Sprites[keys].Draw(610, 455, 0, Sprites[fmenu].Image[0]);
+ Sprites[keys].Draw(690, 505, 0, Sprites[fmenu].Image[0]);
AddButton(0, sound, 140, 110);
AddButton(1, music, 160, 200);
@@ -372,46 +372,57 @@ void Menu::InitMain_Options()
AddButton(3, earth, 180, 400);
+ // audio theme
+ Sprites[arrows].Draw(370, 420, 1, Sprites[fmenu].Image[0]);
+ Sprites[arrows].Draw(620, 420, 4, Sprites[fmenu].Image[0]);
+
+ Menu_Py[4].StartX = 370 - Sprites[arrows].Dim[0].L / 2;
+ Menu_Py[4].StartY = 420 - Sprites[arrows].Dim[0].H / 2;
+ Menu_Py[4].EndX = 620 + Sprites[arrows].Dim[0].L / 2;
+ Menu_Py[4].EndY = 420 + Sprites[arrows].Dim[0].H / 2;
+ Menu_Py[4].Py = 4;
+ Menu_Py[4].Valid = true;
+
// Center text left
CenterM = 120 + Sprites[T_menu].Dim[0].L / 2;
DrawText(CenterM, 490, T_menu, Sprites[fmenu].Image[0]);
- AddButton(4, T_menu, CenterM, 490);
+ AddButton(5, T_menu, CenterM, 490);
// Sound buttons
Sprites[arrows].Draw(250, 110, 1, Sprites[fmenu].Image[0]);
Sprites[arrows].Draw(700, 110, 4, Sprites[fmenu].Image[0]);
- Menu_Py[5].StartX = 230;
- Menu_Py[5].StartY = 70;
- Menu_Py[5].EndX = 475;
- Menu_Py[5].EndY = 145;
- Menu_Py[5].Py = 5;
- Menu_Py[5].Valid = true;
-
- Menu_Py[6].StartX = 476;
+ Menu_Py[6].StartX = 230;
Menu_Py[6].StartY = 70;
- Menu_Py[6].EndX = 720;
+ Menu_Py[6].EndX = 475;
Menu_Py[6].EndY = 145;
Menu_Py[6].Py = 6;
Menu_Py[6].Valid = true;
- // Music buttons
- Sprites[arrows].Draw(250, 200, 1, Sprites[fmenu].Image[0]);
- Sprites[arrows].Draw(700, 200, 4, Sprites[fmenu].Image[0]);
- Menu_Py[7].StartX = 230;
- Menu_Py[7].StartY = 155;
- Menu_Py[7].EndX = 475;
- Menu_Py[7].EndY = 245;
+ Menu_Py[7].StartX = 476;
+ Menu_Py[7].StartY = 70;
+ Menu_Py[7].EndX = 720;
+ Menu_Py[7].EndY = 145;
Menu_Py[7].Py = 7;
Menu_Py[7].Valid = true;
- Menu_Py[8].StartX = 476;
+ // Music buttons
+ Sprites[arrows].Draw(250, 200, 1, Sprites[fmenu].Image[0]);
+ Sprites[arrows].Draw(700, 200, 4, Sprites[fmenu].Image[0]);
+ Menu_Py[8].StartX = 230;
Menu_Py[8].StartY = 155;
- Menu_Py[8].EndX = 720;
+ Menu_Py[8].EndX = 475;
Menu_Py[8].EndY = 245;
Menu_Py[8].Py = 8;
Menu_Py[8].Valid = true;
- Menu_Py[9].StartX = -1;
+ Menu_Py[9].StartX = 476;
+ Menu_Py[9].StartY = 155;
+ Menu_Py[9].EndX = 720;
+ Menu_Py[9].EndY = 245;
+ Menu_Py[9].Py = 9;
+ Menu_Py[9].Valid = true;
+
+ Menu_Py[10].StartX = -1;
}
/*** Options menu management ***/
@@ -421,7 +432,7 @@ eMenu Menu::SDLMain_Options()
int i, N;
int NumSp;
- PyE = 4;
+ PyE = 5;
// Fetch events
do {
m_screen.CleanSpriteAndScreen(fmenu);
@@ -451,12 +462,19 @@ eMenu Menu::SDLMain_Options()
if (Pref.FullScreen == false) {
Pref.FullScreen = true;
ChangeVideo();
- PyE = 2;
}
break;
+ case 4:
+ Pref.AudioTheme = eAudioTheme(Pref.AudioTheme - 1);
+ if (Pref.AudioTheme < 0) {
+ Pref.AudioTheme = mZabiden;
+ }
+ m_audio.LoadMusic(0);
+ PyE = 4;
+ break;
case 0:
- case 5: // Lowers sound effects volume
- case 6:
+ case 6: // lowers sounds effects volume
+ case 7:
Pref.Volume -= SDL_MIX_MAXVOLUME / 10.0;
if (Pref.Volume < 0) {
Pref.Volume = 0;
@@ -465,8 +483,8 @@ eMenu Menu::SDLMain_Options()
m_audio.Play(sLive);
break;
case 1:
- case 7: // Lowers music volume
- case 8:
+ case 8: // lowers music volume
+ case 9:
Pref.VolumeM -= SDL_MIX_MAXVOLUME / 10.0;
if (Pref.VolumeM < 0) {
Pref.VolumeM = 0;
@@ -484,9 +502,17 @@ eMenu Menu::SDLMain_Options()
PyE = 2;
}
break;
+ case 4:
+ Pref.AudioTheme = eAudioTheme(Pref.AudioTheme + 1);
+ if (Pref.AudioTheme > mZabiden) {
+ Pref.AudioTheme = mMaf;
+ }
+ m_audio.LoadMusic(0);
+ PyE = 4;
+ break;
case 0:
- case 5:
case 6:
+ case 7:
Pref.Volume += SDL_MIX_MAXVOLUME / 10.0;
if (Pref.Volume > SDL_MIX_MAXVOLUME) {
Pref.Volume = SDL_MIX_MAXVOLUME;
@@ -495,8 +521,8 @@ eMenu Menu::SDLMain_Options()
m_audio.Play(sLive);
break;
case 1:
- case 7:
case 8:
+ case 9:
Pref.VolumeM += SDL_MIX_MAXVOLUME / 10.0;
if (Pref.VolumeM > SDL_MIX_MAXVOLUME) {
Pref.VolumeM = SDL_MIX_MAXVOLUME;
@@ -508,12 +534,12 @@ eMenu Menu::SDLMain_Options()
case SDLK_UP:
PyE--;
if (PyE < 0) {
- PyE = 4;
+ PyE = 5;
}
break;
case SDLK_DOWN:
PyE++;
- if (PyE >= 5) {
+ if (PyE >= 6) {
PyE = 0;
}
break;
@@ -538,7 +564,15 @@ eMenu Menu::SDLMain_Options()
SDLMain_Language();
PyE = 3;
break;
- case 5: // Lower sound effects volume
+ case 4: // Audio theme
+ Pref.AudioTheme = eAudioTheme(Pref.AudioTheme + 1);
+ if (Pref.AudioTheme > mZabiden) {
+ Pref.AudioTheme = mMaf;
+ }
+ m_audio.LoadMusic(0);
+ PyE = 4;
+ break;
+ case 6: // lower sounds effects volume
Pref.Volume -= SDL_MIX_MAXVOLUME / 10.0;
if (Pref.Volume < 0) {
Pref.Volume = 0;
@@ -546,7 +580,7 @@ eMenu Menu::SDLMain_Options()
m_audio.DoVolume();
m_audio.Play(sLive);
break;
- case 6:
+ case 7:
Pref.Volume += SDL_MIX_MAXVOLUME / 10.0;
if (Pref.Volume > SDL_MIX_MAXVOLUME) {
Pref.Volume = SDL_MIX_MAXVOLUME;
@@ -554,14 +588,14 @@ eMenu Menu::SDLMain_Options()
m_audio.DoVolume();
m_audio.Play(sLive);
break;
- case 7: // Lower music volume
+ case 8: // lower music volume
Pref.VolumeM -= SDL_MIX_MAXVOLUME / 10.0;
if (Pref.VolumeM < 0) {
Pref.VolumeM = 0;
}
m_audio.DoVolume();
break;
- case 8:
+ case 9:
Pref.VolumeM += SDL_MIX_MAXVOLUME / 10.0;
if (Pref.VolumeM > SDL_MIX_MAXVOLUME) {
Pref.VolumeM = SDL_MIX_MAXVOLUME;
@@ -596,6 +630,16 @@ eMenu Menu::SDLMain_Options()
m_screen.PrintSprite(arrows, 4, 450, 300);
}
+ switch (Pref.AudioTheme) {
+ case mMaf:
+ DrawString(400, 420, "MAF 464", Sprites[fmenu].Image[0]);
+ break;
+ case mZabiden:
+ DrawString(400, 420, "ZABIDEN", Sprites[fmenu].Image[0]);
+ break;
+ }
+ DrawString(350, 370, "Audio Theme", Sprites[fmenu].Image[0]);
+
NumSp = (currentTime / 30) % 25;
m_screen.PrintSprite(sound, NumSp, 150, 110);
NumSp = (currentTime / 30) % 25;
@@ -629,12 +673,10 @@ eMenu Menu::SDLMain_Options()
Print_Main(180);
break;
case 4:
- Print_Main(CenterM);
+ Print_Main(490);
break;
case 5:
- PyE = 0;
- Print_Main();
- PyE = 5;
+ Print_Main(CenterM);
break;
case 6:
PyE = 0;
@@ -642,7 +684,7 @@ eMenu Menu::SDLMain_Options()
PyE = 6;
break;
case 7:
- PyE = 1;
+ PyE = 0;
Print_Main();
PyE = 7;
break;
@@ -651,6 +693,11 @@ eMenu Menu::SDLMain_Options()
Print_Main();
PyE = 8;
break;
+ case 9:
+ PyE = 1;
+ Print_Main();
+ PyE = 9;
+ break;
default:
Print_Main();
}
diff --git a/src/preference.h b/src/preference.h
index 7087b6c..b3253a2 100644
--- a/src/preference.h
+++ b/src/preference.h
@@ -99,6 +99,11 @@ struct sOldPreference
struct sScore Sco[8]; // Store scores
};
+enum eAudioTheme {
+ mMaf = 0, // original Ri-Li soundtrack
+ mZabiden = 1 // updated soundtrack
+};
+
struct sNewPreference
{
e_Difficulty Difficulty { Normal }; // current game difficulty
@@ -115,6 +120,7 @@ struct sNewPreference
float Volume { (float)SDL_MIX_MAXVOLUME }; // audio volume
float VolumeM { (float)SDL_MIX_MAXVOLUME }; // music volume
struct sScore Sco[8]; // store scores
+ eAudioTheme AudioTheme { mZabiden }; // which audio theme to use
int HumanRightsQuiz { 1 }; // enable the human rights questions at the end of a level
};
diff --git a/src/sprite.cc b/src/sprite.cc
index 407cd4e..452fec3 100644
--- a/src/sprite.cc
+++ b/src/sprite.cc
@@ -285,7 +285,7 @@ void DrawNumber(int x, int y, int Number, SDL_Texture *Background)
/*** Display a string ***/
/************************/
-void DrawString(int x, int y, char *Text, SDL_Texture *background)
+void DrawString(int x, int y, const char *Text, SDL_Texture *background)
{
int i = 0;
int Le;
diff --git a/src/sprite.h b/src/sprite.h
index 58ac834..4ffcb86 100644
--- a/src/sprite.h
+++ b/src/sprite.h
@@ -181,7 +181,7 @@ int StringLength(char *Text); // Returns the length in pixels of a string.
bool CharExist(char C); // Checks if a character exists
void DrawNumber(int x, int y, int Number, SDL_Texture *Background = nullptr); // Displays a number
-void DrawString(int x, int y, char *Text, SDL_Texture *Background = nullptr); // Displays a string
+void DrawString(int x, int y, const char *Text, SDL_Texture *Background = nullptr); // Displays a string
void DrawText(int x, int y, e_Sprite Text, SDL_Texture *Background = nullptr); // Displays a text in the language
diff --git a/src/utils.cc b/src/utils.cc
index 27b6b65..20250f2 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -274,6 +274,10 @@ bool Utils::LoadPref()
if (pv) {
Pref.VolumeM = std::stof(pv);
}
+ pv = ini.GetValue("main", "audioTheme");
+ if (pv) {
+ Pref.AudioTheme = eAudioTheme(std::stoi(pv));
+ }
pv = ini.GetValue("main", "humanRightsQuiz");
if (pv) {
Pref.HumanRightsQuiz = std::stoi(pv);
@@ -347,6 +351,7 @@ void Utils::SavePref()
ini.SetValue("main", "audioVolume", std::to_string(Pref.Volume).c_str());
ini.SetValue("main", "musicVolume", std::to_string(Pref.VolumeM).c_str());
ini.SetValue("main", "humanRightsQuiz", std::to_string(Pref.HumanRightsQuiz).c_str());
+ ini.SetValue("main", "audioTheme", std::to_string((int)Pref.AudioTheme).c_str());
ini.SetValue("easy", "maxLevel", std::to_string(Pref.LevelMax[0]).c_str());
ini.SetValue("normal", "maxLevel", std::to_string(Pref.LevelMax[1]).c_str());
ini.SetValue("difficult", "maxLevel", std::to_string(Pref.LevelMax[2]).c_str());