浏览代码

first on gogs

quantumde1 1 周之前
当前提交
20b91a048f
共有 30 个文件被更改,包括 2597 次插入0 次删除
  1. 33 0
      .gitignore
  2. 21 0
      LICENSE
  3. 46 0
      build.bat
  4. 94 0
      build.sh
  5. 13 0
      builder.sh
  6. 7 0
      conf/settings.conf
  7. 50 0
      dub.json
  8. 8 0
      dub.selections.json
  9. 38 0
      hpff/build.sh
  10. 244 0
      hpff/src/lib.c
  11. 3 0
      hpff/src/lib.h
  12. 41 0
      hpff/src/main.c
  13. 二进制
      libhpff.lib
  14. 二进制
      lua53.lib
  15. 二进制
      raylib.lib
  16. 16 0
      readme.md
  17. 37 0
      source/app.d
  18. 200 0
      source/dialogs/dialogbox.d
  19. 79 0
      source/graphics/effects.d
  20. 94 0
      source/graphics/engine.d
  21. 85 0
      source/graphics/gamelogic.d
  22. 349 0
      source/graphics/playback.d
  23. 459 0
      source/scripts/lua.d
  24. 18 0
      source/system/abstraction.d
  25. 32 0
      source/system/cleanup.d
  26. 69 0
      source/system/config.d
  27. 366 0
      source/ui/menu.d
  28. 155 0
      source/variables.d
  29. 40 0
      test.sh
  30. 二进制
      vlc.lib

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+log.txt
+.dub
+docs.json
+__dummy.html
+/himmel-engine
+himmel-engine.exe
+himmel-engine.pdb
+himmel-engine.so
+himmel-engine.dylib
+himmel-engine.dll
+himmel-engine.a
+himmel-engine.lib
+himmel-engine-test-*
+*.exe
+*.pdb
+*.o
+*.obj
+*.lst
+.ldc2_cache
+sky.sh
+a.out
+*.so*
+sky
+*.dylib*
+res/
+/scripts/
+docs/
+./save.txt
+./himmel-silent-hill/
+himmel-silent-hill/
+himmel-soul-hackers/
+./himmel-soul-hackers/
+res

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Arseniy Mesherakov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 46 - 0
build.bat

@@ -0,0 +1,46 @@
+@echo off
+setlocal enabledelayedexpansion
+
+REM Exit immediately if a command exits with a non-zero status
+set "RED=^[[31m"
+set "GREEN=^[[32m"
+set "YELLOW=^[[33m"
+set "BLUE=^[[34m"
+set "RESET=^[[0m"
+
+REM Check and update git submodules
+echo !BLUE![INFO] Checking for git submodules...!RESET!
+git submodule update --init --recursive
+
+REM Change directory to hpff and build
+echo !GREEN![BUILD] Building hpff...!RESET!
+
+REM Check for the --release flag
+set "BUILD_CMD=dub build --force"
+if "%~1"=="--release" (
+    set "BUILD_CMD=dub build --build=release --force"
+)
+
+REM Execute the build command and hide output, but capture errors
+echo !GREEN![BUILD] Building engine...!RESET!
+echo !BLUE![INFO] If no Build complete shown, then engine not built, check log.txt for details.!RESET!
+if exist "log.txt" (
+    echo !YELLOW![WARNING] log file already exists, continue build...!RESET!
+    %BUILD_CMD% > log.txt 2>&1
+) else (
+    echo !YELLOW![WARNING] log file does not exist, creating and continue build...!RESET!
+    echo. > log.txt
+    %BUILD_CMD% > log.txt 2>&1
+)
+
+REM If the build was successful, proceed with further steps
+REM strip ./libplayback.so
+strip libhpff.so
+strip heaven-engine
+echo MADE_BY_QUANTUMDE1_UNDERLEVEL_STUDIOS_2024_ALL_RIGHTS_RESERVED_UNDER_MIT_LICENSE_LMAO >> heaven-engine
+echo #!/bin/sh > sky.bat
+echo exec heaven-engine >> sky.bat
+echo !GREEN![BUILD] Build complete.!RESET!
+echo !GREEN![INFO] All processes completed successfully.!RESET!
+
+endlocal

+ 94 - 0
build.sh

@@ -0,0 +1,94 @@
+#!/bin/sh
+
+set -e  # Exit immediately if a command exits with a non-zero status
+
+RED='\e[31m'
+GREEN='\e[32m'
+YELLOW='\e[33m'
+BLUE='\e[34m'
+RESET='\e[0m'
+
+# Check and update git submodules
+printf "${BLUE}[INFO] Checking for git submodules...${RESET}\n"
+git submodule update --init --recursive
+
+# Change directory to hpff and build
+printf "${GREEN}[BUILD] Building hpff...${RESET}\n"
+cd hpff/
+
+# Check if libhpff.so already exists
+if [ -f "../libhpff.so" ]; then
+    printf "${YELLOW}[WARNING] libhpff.so already built. Skipping build process.${RESET}\n"
+else
+    ./build.sh
+    # Move the built library to the parent directory
+    mv ./libhpff.so ../
+    printf "${GREEN}[SUCCESS] libhpff.so moved to the parent directory.${RESET}\n"
+fi
+
+# Return to the original directory
+cd ..
+
+# Change directory to hpff and build
+printf "${GREEN}[BUILD] Building raylib...${RESET}\n"
+cd raylib/src/
+
+# Check if libhpff.so already exists
+if [ -f "../../libraylib.so" ]; then
+    printf "${YELLOW}[WARNING] libraylib.so already built. Skipping build process.${RESET}\n"
+else
+    make PLATFORM=PLATFORM_DESKTOP RAYLIB_LIBTYPE=SHARED -j $(nproc)
+    # Move the built library to the parent directory
+    mv ./libraylib.so* ../../
+    printf "${GREEN}[SUCCESS] libraylib.so and company moved to the parent directory.${RESET}\n"
+fi
+
+cd ../../
+# Change directory to libplayback/
+#cd libplayback/
+# Check if libplayback.so already exists
+#if [ -f "../libplayback.so" ]; then
+#    printf "${YELLOW}[WARNING] libplayback.so already built. Skipping build process.${RESET}\n"
+#else
+#    # Compile with optimization flags
+#    cc -fPIC -c text.c -O3 -march=native -lraylib -I /opt/local/include -L /opt/local/lib
+#    gcc -shared -o libplayback.so text.o -O3 -march=native -lraylib -I /opt/local/include -L /opt/local/lib
+#    # Move the built library to the parent directory
+#    mv ./libplayback.so ../
+#    printf "${GREEN}[SUCCESS] libplayback.so moved to the parent directory.${RESET}\n"
+#fi
+
+## Return to the original directory
+#cd ..
+
+printf "enter repo address with https:\n"
+read babah
+printf "Done, cloning $babah...\n"
+git clone $babah
+printf "Move data from cloned folder to current folder with engine binary(aka heaven-engine)\n"
+# Check for the --release flag
+if [ "$1" = "--release" ]; then
+    BUILD_CMD="dub build --build=release --force"
+else
+    BUILD_CMD="dub build --force"
+fi
+
+# Execute the build command and hide output, but capture errors
+printf "${GREEN}[BUILD] Building engine...${RESET}\n"
+# check if log file exists
+printf "${BLUE}[INFO] If no Build complete shown, then engine not built, check log.txt for details!${RESET}\n"
+if [ -f "./log.txt" ]; then
+    printf "${YELLOW}[WARNING] log file already exists, continue build...${RESET}\n"
+    $BUILD_CMD &> log.txt
+else
+    printf "${YELLOW}[WARNING] log file does not exist, creating and continue build...${RESET}\n"
+    touch log.txt && $BUILD_CMD &> log.txt
+fi
+
+# If the build was successful, proceed with further steps
+#strip ./libplayback.so
+strip ./libhpff.so
+strip ./heaven-engine
+echo "MADE_BY_QUANTUMDE1_UNDERLEVEL_STUDIOS_2024_ALL_RIGHTS_RESERVED_UNDER_MIT_LICENSE_LMAO" >> ./heaven-engine
+printf "${GREEN}[BUILD] Build complete!${RESET}\n"
+printf "${GREEN}[INFO] All processes completed successfully!${RESET}\n"

+ 13 - 0
builder.sh

@@ -0,0 +1,13 @@
+if [ -x "/usr/bin/hpffutil" ] || [ -x "/bin/hpffutil" ] || [ -x "/usr/local/bin/hpffutil" ]; then
+  echo "hpffutil found"
+else
+  echo "hpffutil not found, install it from https://github.com/quantumde/hpff"
+fi
+
+printf "Packing assets...\n"
+
+hpffutil pack res/bg.bin res/backgrounds/*
+hpffutil pack res/tex.bin res/tex/*
+hpffutil pack res/enemies.bin res/enemies/*
+hpffutil pack res/faces.bin res/faces/*
+hpffutil pack res/data.bin res/data/*

+ 7 - 0
conf/settings.conf

@@ -0,0 +1,7 @@
+SOUND:1
+FORWARD:W
+BACKWARD:S
+LEFT:A
+RIGHT:D
+DIALOG:K
+OPMENU:M

+ 50 - 0
dub.json

@@ -0,0 +1,50 @@
+{
+	"authors": [
+		"quantumde1"
+	],
+	"copyright": "Copyright © 2025, quantumde1",
+	"dependencies": {
+		"bindbc-lua": "~>0.6.1",
+		"raylib-d": "~>5.5.1"
+	},
+	"description": "Engine on Dlang for visual novels",
+	"lflags-linux": [
+		"-rpath=/usr/lib",
+		"-rpath=$$ORIGIN"
+	],
+	"lflags-osx": [
+		"-rpath",
+		"./",
+		"-rpath",
+		"/opt/local/lib"
+	],
+	"lflags-posix": [
+		"-L/usr/lib",
+		"-L.",
+		"-L/opt/local/lib",
+		"-L/usr/local/lib"
+	],
+	"lflags-windows": [
+		"-L./"
+	],
+	"libs-posix": [
+		"raylib",
+		"lua-5.3",
+		"vlc",
+		"hpff"
+	],
+	"libs-windows": [
+		"raylib",
+		"lua53",
+		"libhpff",
+		"vlc"
+	],
+	"license": "MIT License",
+	"name": "himmel-engine",
+	"subConfigurations": {
+		"bindbc-lua": "static"
+	},
+	"versions": [
+		"LUA_53"
+	]
+}

+ 8 - 0
dub.selections.json

@@ -0,0 +1,8 @@
+{
+	"fileVersion": 1,
+	"versions": {
+		"bindbc-loader": "1.1.5",
+		"bindbc-lua": "0.6.1",
+		"raylib-d": "5.5.1"
+	}
+}

+ 38 - 0
hpff/build.sh

@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Variables
+SRC="src/lib.c"
+LIB_NAME="libhpff.so"
+INSTALL_DIR="/usr/local/lib"
+
+# Function to build the shared library
+build() {
+    echo "Building shared library..."
+    gcc -march=native -O3 -fPIC -shared -o "$LIB_NAME" "$SRC"
+    if [ $? -ne 0 ]; then
+        echo "Build failed!"
+        exit 1
+    fi
+    echo "Build successful: $LIB_NAME"
+}
+
+# Function to install the library
+install() {
+    if [ -d /usr/local/lib ]; then
+        echo "Installing to /usr/local/lib"
+        cp "$LIB_NAME" /usr/local/lib
+    else
+        echo "Installing to /usr/lib"
+        cp "$LIB_NAME" /usr/lib
+    fi
+    ldconfig
+    echo "Installation successful."
+}
+
+# Main script logic
+if [ "$1" = "install" ]; then
+    build
+    install
+else
+    build
+fi

+ 244 - 0
hpff/src/lib.c

@@ -0,0 +1,244 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/stat.h>
+
+#define FNAME_LEN 255
+
+void Y(const char *Z, char **A, int B) {
+    FILE *C = fopen(Z, "wb");
+    if (!C) {
+        perror("Error opening output file");
+        return;
+    }
+
+    if (fwrite(&B, sizeof(uint32_t), 1, C) != 1) {
+        perror("Error writing file count");
+        fclose(C);
+        return;
+    }
+
+    for (int D = 0; D < B; D++) {
+        const char *E = A[D];
+        char F[FNAME_LEN];
+        struct stat G;
+
+        if (strncpy(F, E, FNAME_LEN - 1) == NULL) {
+            perror("Error copying file name");
+            continue;
+        }
+        F[FNAME_LEN - 1] = '\0';
+
+        char *H = strrchr(F, '/');
+        if (H) {
+            H++;
+        } else {
+            H = F;
+        }
+
+        if (stat(E, &G) != 0) {
+            perror("Error getting file stats");
+            continue;
+        }
+
+        uint8_t I = strlen(H);
+        if (fwrite(&I, sizeof(uint8_t), 1, C) != 1 || fwrite(H, sizeof(char), I, C) != I) {
+            perror("Error writing file name");
+            continue;
+        }
+
+        uint32_t J = G.st_size;
+        if (fwrite(&J, sizeof(uint32_t), 1, C) != 1) {
+            perror("Error writing file size");
+            continue;
+        }
+
+        FILE *K = fopen(E, "rb");
+        if (!K) {
+            perror("Error opening input file");
+            continue;
+        }
+
+        char *L = malloc(J);
+        if (!L) {
+            perror("Error allocating memory");
+            fclose(K);
+            continue;
+        }
+
+        if (fread(L, sizeof(char), J, K) != J) {
+            perror("Error reading file data");
+            free(L);
+            fclose(K);
+            continue;
+        }
+
+        if (fwrite(L, sizeof(char), J, C) != J) {
+            perror("Error writing file data");
+        }
+
+        free(L);
+        fclose(K);
+    }
+
+    fclose(C);
+    printf("Packed %d files into %s\n", B, Z);
+}
+
+void M(const char *N, const char *O) {
+    FILE *P = fopen(N, "rb");
+    if (!P) {
+        perror("Error opening input file");
+        return;
+    }
+
+    uint32_t Q;
+    if (fread(&Q, sizeof(uint32_t), 1, P) != 1) {
+        perror("Error reading file count");
+        fclose(P);
+        return;
+    }
+
+    for (uint32_t R = 0; R < Q; R++) {
+        uint8_t S;
+        if (fread(&S, sizeof(uint8_t), 1, P) != 1) {
+            perror("Error reading file name length");
+            break;
+        }
+
+        char *T = malloc(S + 1);
+        if (!T) {
+            perror("Error allocating memory");
+            break;
+        }
+
+        if (fread(T, sizeof(char), S, P) != S) {
+            perror("Error reading file name");
+            free(T);
+            break;
+        }
+        T[S] = '\0';
+
+        uint32_t U;
+        if (fread(&U, sizeof(uint32_t), 1, P) != 1) {
+            perror("Error reading file size");
+            free(T);
+            break;
+        }
+
+        char *V = malloc(U);
+        if (!V) {
+            perror("Error allocating memory");
+            free(T);
+            break;
+        }
+
+        if (fread(V, sizeof(char), U, P) != U) {
+            perror("Error reading file data");
+            free(T);
+            free(V);
+            break;
+        }
+
+        char W[FNAME_LEN];
+        if (snprintf(W, sizeof(W), "%s/%s", O, T) >= sizeof(W)) {
+            fprintf(stderr, "Error: file path too long\n");
+            free(T);
+            free(V);
+            break;
+        }
+
+        FILE *X = fopen(W, "wb");
+        if (X) {
+            if (fwrite(V, sizeof(char), U, X) != U) {
+                perror("Error writing file data");
+            }
+            fclose(X);
+        } else {
+            perror("Error opening output file");
+        }
+
+        free(T);
+        free(V);
+    }
+
+    fclose(P);
+    printf("Unpacked %d files to %s\n", Q, O);
+}
+
+char* get_file_data_from_archive(const char *input_file, const char *file_name, uint32_t *file_size_out) {
+    FILE *f = fopen(input_file, "rb");
+    if (!f) {
+        perror("Failed to open input file");
+        return NULL;
+    }
+
+    uint32_t num_files;
+    if (fread(&num_files, sizeof(uint32_t), 1, f) != 1) {
+        perror("Error reading file count");
+        fclose(f);
+        return NULL;
+    }
+
+    for (uint32_t i = 0; i < num_files; i++) {
+        uint8_t name_length;
+        if (fread(&name_length, sizeof(uint8_t), 1, f) != 1) {
+            perror("Error reading file name length");
+            break;
+        }
+
+        char *current_file_name = malloc(name_length + 1);
+        if (!current_file_name) {
+            perror("Error allocating memory");
+            break;
+        }
+
+        if (fread(current_file_name, sizeof(char), name_length, f) != name_length) {
+            perror("Error reading file name");
+            free(current_file_name);
+            break;
+        }
+        current_file_name[name_length] = '\0';
+
+        uint32_t file_size;
+        if (fread(&file_size, sizeof(uint32_t), 1, f) != 1) {
+            perror("Error reading file size");
+            free(current_file_name);
+            break;
+        }
+
+        if (strcmp(current_file_name, file_name) == 0) {
+            char *buffer = malloc(file_size);
+            if (!buffer) {
+                perror("Error allocating memory");
+                free(current_file_name);
+                break;
+            }
+
+            if (fread(buffer, sizeof(char), file_size, f) != file_size) {
+                perror("Error reading file data");
+                free(buffer);
+                free(current_file_name);
+                break;
+            }
+
+            fclose(f);
+            free(current_file_name);
+            *file_size_out = file_size;
+            return buffer;
+        }
+
+        if (fseek(f, file_size, SEEK_CUR) != 0) {
+            perror("Error seeking file data");
+            free(current_file_name);
+            break;
+        }
+
+        free(current_file_name);
+    }
+
+    printf("File '%s' not found in the archive.\n", file_name);
+    fclose(f);
+    return NULL;
+}

+ 3 - 0
hpff/src/lib.h

@@ -0,0 +1,3 @@
+void Y(const char *Z, char **A, int B);
+void M(const char *N, const char *O);
+char* get_file_data_from_archive(const char *input_file, const char *file_name, uint32_t *file_size_out);

+ 41 - 0
hpff/src/main.c

@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include "lib.h"
+
+int main(int argc, char *argv[]) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage:\n");
+        fprintf(stderr, "  To pack files: %s pack <output_file> <file1> <file2> ... <fileN>\n", argv[0]);
+        fprintf(stderr, "  To unpack files: %s unpack <input_file> <output_directory>\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+
+    if (strcmp(argv[1], "pack") == 0) {
+        const char *output_file = argv[2];
+        int num_files = argc - 3;
+        char **file_list = malloc(num_files * sizeof(char *));
+        if (!file_list) {
+            perror("Error allocating memory for file list");
+            return EXIT_FAILURE;
+        }
+
+        for (int i = 0; i < num_files; i++) {
+            file_list[i] = argv[i + 3];
+        }
+
+        Y(output_file, file_list, num_files);
+        free(file_list);
+    } else if (strcmp(argv[1], "unpack") == 0) {
+        const char *input_file = argv[2];
+        const char *output_directory = argv[3];
+        M(input_file, output_directory);
+    } else {
+        fprintf(stderr, "Invalid command. Use 'pack' or 'unpack'.\n");
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}

二进制
libhpff.lib


二进制
lua53.lib


二进制
raylib.lib


+ 16 - 0
readme.md

@@ -0,0 +1,16 @@
+# Himmel Engine
+
+Himmel(equal to Heaven, but in German) is an engine for Visual Novels, written using Dlang and Lua.
+
+## Features
+
+- Multiple characters on screen
+- Backgrounds(can be preloaded and then drawn if needed)
+- Text box with skip functionality and choices
+- Graphics effects(PNG stored in .bin, its called HPFF - Heaven/Himmel Package File Format)
+
+## TODO
+
+- Adding lipsync
+- Adding savestates in-engine
+- Adding backlog and menus

+ 37 - 0
source/app.d

@@ -0,0 +1,37 @@
+// quantumde1 developed software, licensed under MIT license.
+import raylib;
+
+import std.stdio;
+
+//local imports
+import graphics.engine;
+import graphics.playback;
+import variables;
+import std.file;
+import std.string;
+import system.abstraction;
+import system.config;
+import std.conv;
+
+void main(string[] args)
+{
+    validateRaylibBinding();
+    debug {
+        SetTraceLogLevel(0);
+    } else {
+        SetTraceLogLevel(7);
+    }
+    int screenWidth = GetScreenWidth();
+    int screenHeight = GetScreenHeight();
+    luaExec = "scripts/00_script.lua";
+    if (args.length > 1)
+    {
+        writeln("!!!If needed, there is first argument for choosing script to execute.!!!");
+        luaExec = getcwd().to!string ~ "/" ~ args[1];
+        engine_loader("tief blau", screenWidth, screenHeight);
+    }
+    else
+    {
+        engine_loader("tief blau", screenWidth, screenHeight);
+    }
+}

+ 200 - 0
source/dialogs/dialogbox.d

@@ -0,0 +1,200 @@
+module dialogs.dialogbox;
+
+import raylib;
+import std.string;
+import std.stdio;
+import std.conv;
+import std.uni;
+import std.typecons;
+import std.algorithm;
+
+int currentPage = 0;
+float textDisplayProgress = 0.0f;
+bool textFullyDisplayed = false;
+
+void displayDialog(string[] pages, string[] choices, ref int selectedChoice, int choicePage, Font dialogFont, 
+bool *showDialog, float textSpeed) {
+    int pagesLength = cast(int)pages.length;
+    int screenWidth = GetScreenWidth();
+    int screenHeight = GetScreenHeight();
+    
+    DrawRectangle(
+        0,
+        screenHeight - screenHeight / 3,
+        screenWidth,
+        screenHeight / 3,
+        Color(20, 20, 20, 220)
+    );
+    
+    float marginLeft = screenWidth/6.5f;
+    float marginRight = screenWidth/6.5f;
+    float marginTop = screenHeight - screenHeight/3.3f;
+    float textWidth = screenWidth - marginLeft - marginRight;
+    float fontSize = 40.0f;
+    float spacing = 1.0f;
+    
+    string currentText = pages[currentPage];
+    int textLength = cast(int)currentText.length;
+    
+    if (IsKeyPressed(KeyboardKey.KEY_ENTER) && !textFullyDisplayed) {
+        textDisplayProgress = textLength;
+        textFullyDisplayed = true;
+    }
+    else if (IsKeyPressed(KeyboardKey.KEY_ENTER) && textFullyDisplayed) {
+        currentPage += 1;
+        textDisplayProgress = 0.0f;
+        textFullyDisplayed = false;
+    }
+    else if (!textFullyDisplayed) {
+        textDisplayProgress += textSpeed;
+        if (textDisplayProgress >= textLength) {
+            textDisplayProgress = textLength;
+            textFullyDisplayed = true;
+        }
+    }
+    
+    int charsToShow = cast(int)textDisplayProgress;
+    string displayedText = currentText[0 .. min(charsToShow, textLength)];
+    
+    string[] lines;
+    string remainingText = displayedText;
+    
+    while (remainingText.length > 0) {
+        int fitChars = 0;
+        float width = 0.0f;
+        
+        while (fitChars < remainingText.length) {
+            int nextChar = fitChars;
+            while (nextChar < remainingText.length && !isWhite(remainingText[nextChar])) {
+                nextChar++;
+            }
+            
+            string word = remainingText[fitChars..nextChar];
+            float wordWidth = MeasureTextEx(dialogFont, word.toStringz(), fontSize, spacing).x;
+            
+            if (width + wordWidth > textWidth && width > 0) {
+                break;
+            }
+            
+            width += wordWidth;
+            fitChars = nextChar;
+            
+            while (fitChars < remainingText.length && isWhite(remainingText[fitChars])) {
+                width += MeasureTextEx(dialogFont, " ".toStringz(), fontSize, spacing).x;
+                fitChars++;
+            }
+        }
+        
+        if (fitChars == 0) {
+            fitChars = 1;
+        }
+        
+        lines ~= remainingText[0..fitChars];
+        remainingText = remainingText[fitChars..$];
+    }
+    
+    // Draw each line of text
+    float lineHeight = MeasureTextEx(dialogFont, "A", fontSize, spacing).y * 1.4;
+    for (int i = 0; i < lines.length; i++) {
+        DrawTextEx(
+            dialogFont,
+            lines[i].toStringz(),
+            Vector2(marginLeft, marginTop + i * lineHeight),
+            fontSize,
+            spacing,
+            Colors.WHITE
+        );
+    }
+    
+    if (textFullyDisplayed) {
+        drawSnakeAnimation(
+            0,
+            screenHeight - screenHeight / 3,
+            screenWidth,
+            screenHeight / 3
+        );
+    }
+    if (currentPage == choicePage) {
+        
+        // Обработка выбора ответа (вверх/вниз)
+        if (IsKeyPressed(KeyboardKey.KEY_DOWN)) {
+            selectedChoice = cast(int)((selectedChoice + 1) % choices.length);
+        }
+        if (IsKeyPressed(KeyboardKey.KEY_UP)) {
+            selectedChoice = cast(int)((selectedChoice - 1 + choices.length) % choices.length);
+        }
+    
+        for (int i = 0; i < choices.length; i++) {
+            Color color = (i == selectedChoice) ? Colors.YELLOW : Colors.WHITE; 
+            DrawTextEx(
+                dialogFont,
+                toStringz(choices[i]),
+                Vector2(marginLeft, 60 + marginTop + i * 40),
+                fontSize,
+                spacing,
+                color
+            );
+        }
+    }
+    if (currentPage >= pagesLength) {
+        currentPage = 0;
+        textDisplayProgress = 0.0f;
+        textFullyDisplayed = false;
+        pages = [];
+        *showDialog = false;
+        return;
+    }
+}
+
+void drawSnakeAnimation(int rectX, int rectY, int rectWidth, int rectHeight) {
+    static float animTimer = 0.0f;
+    animTimer += GetFrameTime();
+    if (animTimer > 0.1f) animTimer = 0.0f;
+    
+    int animWidth = 300;
+    int animHeight = 100;
+    int animX = rectX + rectWidth - animWidth - 20;
+    int animY = rectY + rectHeight - animHeight - 20;
+    
+    int cubeSize = 5;
+    int cubesInRow = 5;
+    int spacing = 1;
+    
+    int centerX = animX + animWidth/2 - (cubesInRow*cubeSize + (cubesInRow-1)*spacing)/2;
+    int centerY = animY + animHeight/2 - (cubesInRow*cubeSize + (cubesInRow-1)*spacing)/2;
+    
+    for (int y = 0; y < cubesInRow; y++) {
+        for (int x = 0; x < cubesInRow; x++) {
+            DrawRectangle(
+                centerX + x*(cubeSize + spacing),
+                centerY + y*(cubeSize + spacing),
+                cubeSize, cubeSize, Color(50, 50, 50, 255)
+            );
+        }
+    }
+    
+    static int snakePosition = 0;
+    if (animTimer == 0.0f) snakePosition = (snakePosition + 1) % 16;
+    
+    Tuple!(int, int)[] snakePath = [
+        tuple(2, 0), tuple(3, 0), tuple(4, 0), tuple(4, 1),
+        tuple(4, 2), tuple(4, 3), tuple(4, 4), tuple(3, 4),
+        tuple(2, 4), tuple(1, 4), tuple(0, 4), tuple(0, 3),
+        tuple(0, 2), tuple(0, 1), tuple(0, 0), tuple(1, 0)
+    ];
+    
+    for (int i = 0; i < snakePath.length; i++) {
+        int relPos = cast(int)((i + snakePosition) % snakePath.length);
+        int x = snakePath[relPos][0];
+        int y = snakePath[relPos][1];
+        
+        float t = cast(float)i / snakePath.length;
+        Color cubeColor = Color(0, cast(ubyte)(50 + 70 * (1 - t)), 0, 255); 
+        
+        DrawRectangle(
+            centerX + x*(cubeSize + spacing),
+            centerY + y*(cubeSize + spacing),
+            cubeSize, cubeSize, cubeColor
+        );
+    }
+}

+ 79 - 0
source/graphics/effects.d

@@ -0,0 +1,79 @@
+module graphics.effects;
+
+import raylib;
+import std.stdio;
+import variables;
+import std.string;
+import system.abstraction;
+import system.config;
+import std.algorithm;
+import system.abstraction;
+
+int screenWidth;
+int screenHeight;
+
+Texture2D[] loadAnimationFramesUI(const string archivePath, const string animationName)
+{
+    screenWidth = GetScreenWidth();
+    screenHeight = GetScreenHeight();
+    Texture2D[] frames;
+    uint frameIndex = 1;
+    while (true)
+    {
+        string frameFileName = format("%s-%03d.png", animationName, frameIndex);
+        uint image_size;
+        debug debugWriteln(frameFileName);
+        char* image_data = get_file_data_from_archive(toStringz(archivePath),
+                toStringz(frameFileName), &image_size);
+        if (image_data == null)
+        {
+            debug debugWriteln("exiting from load anim UI");
+            break;
+        }
+        Image image = LoadImageFromMemory(".PNG", cast(const(ubyte)*) image_data, image_size);
+        Texture2D texture = LoadTextureFromImage(image);
+        UnloadImage(image);
+        frames ~= texture;
+        debug debugWriteln("Loaded frame for UI ", frameIndex, " - ", frameFileName);
+        frameIndex++;
+    }
+    debug debugWriteln("Frames for ui animations length: ", frames.length);
+    return frames;
+}
+
+void playUIAnimation(Texture2D[] frames)
+{
+    static float frameTime = 0.0f;
+    
+    if (playAnimation) {
+        frameTime += GetFrameTime();
+        
+        while (frameTime >= frameDuration && frameDuration > 0) {
+            frameTime -= frameDuration;
+            currentFrame = cast(int)((currentFrame + 1) % frames.length);
+        }
+
+        int frameWidth = frames[currentFrame].width;
+        int frameHeight = frames[currentFrame].height;
+        
+        DrawTexturePro(
+            frames[currentFrame],
+            Rectangle(0, 0, frameWidth, frameHeight),
+            Rectangle(0, 0, screenWidth, screenHeight),
+            Vector2(0, 0),
+            0,
+            Color(255, 255, 255, 127)
+        );
+    } else {
+        frameTime = 0.0f;
+        currentFrame = 0;
+    }
+}
+
+Sound sfx;
+
+void playSfx(string filename) {
+    debug debugWriteln("Loading & playing SFX");
+    sfx = LoadSound(filename.toStringz());
+    PlaySound(sfx);
+}

+ 94 - 0
source/graphics/engine.d

@@ -0,0 +1,94 @@
+// quantumde1 developed software, licensed under MIT license.
+module graphics.engine;
+
+import raylib;
+import graphics.playback;
+import std.stdio;
+import std.math;
+import std.file;
+import graphics.gamelogic;
+import std.string;
+import std.conv;
+import variables;
+import std.random;
+import std.datetime;
+import ui.menu;
+import std.typecons;
+import system.abstraction;
+import system.config;
+import dialogs.dialogbox;
+import scripts.lua;
+import std.array;
+import system.abstraction;
+import system.cleanup;
+
+void engine_loader(string window_name, int screenWidth, int screenHeight)
+{
+    // Initialization
+    debug debugWriteln("Engine version: ", ver);
+    SetExitKey(0);
+    // Window and Audio Initialization
+    InitWindow(screenWidth, screenHeight, cast(char*) window_name);
+    DisableCursor();
+    ToggleFullscreen();
+    SetTargetFPS(60);
+    textFont = LoadFont("res/font_en.png");
+    // Fade In and Out Effects
+    InitAudioDevice();
+    audioEnabled = systemSettings.sound_state.to!bool;
+    helloScreen();
+    ClearBackground(Colors.BLACK);
+    EndDrawing();
+    camera.target = Vector2(screenWidth/2.0f, screenHeight/2.0f);
+    camera.offset = Vector2(screenWidth/2.0f, screenHeight/2.0f);
+    camera.rotation = 0.0f;
+    camera.zoom = 1.0f;
+    while (true)
+    {
+        switch (currentGameState)
+        {
+        case GameState.MainMenu:
+            debugWriteln("Showing menu.");
+            showMainMenu(currentGameState);
+            break;
+        case GameState.InGame:
+            import core.stdc.stdlib;
+            import core.stdc.time;
+
+            gameInit();
+            while (!WindowShouldClose())
+            {
+                SetExitKey(0);
+                if (luaReload)
+                {
+                    int luaExecutionCode = luaInit(luaExec);
+                    if (luaExecutionCode != EngineExitCodes.EXIT_OK) {
+                        debugWriteln("Engine stops execution according to error code: ", luaExecutionCode);
+                        currentGameState = GameState.Exit;
+                        break;
+                    }
+                    luaReload = false;
+                }
+                luaEventLoop();
+                BeginDrawing();
+                ClearBackground(Colors.BLACK);
+                // main logic
+                // effects logic
+                BeginMode2D(camera);
+                backgroundLogic();
+                effectsLogic();
+                EndMode2D();
+                dialogLogic();
+                EndDrawing();
+            }
+            break;
+        case GameState.Exit:
+            EndDrawing();
+            unloadResourcesLogic();
+            return;
+
+        default:
+            break;
+        }
+    }
+}

+ 85 - 0
source/graphics/gamelogic.d

@@ -0,0 +1,85 @@
+module graphics.gamelogic;
+
+import raylib;
+import bindbc.lua;
+import variables;
+import core.stdc.stdlib;
+import core.stdc.time;
+import graphics.engine;
+import scripts.lua;
+import std.stdio;
+import std.conv;
+import graphics.effects;
+import std.string;
+import std.math;
+import dialogs.dialogbox;
+import system.abstraction;
+import system.config;
+import std.file;
+
+/** 
+ * this module contains game logic, which was removed from engine.d for better readability.
+ */
+ 
+void gameInit()
+{
+    if (WindowShouldClose()) {
+        currentGameState = GameState.Exit;
+    } else {
+        debugWriteln("Game initializing.");
+        systemSettings = loadSettingsFromConfigFile();
+        if (sfxEnabled == false) {
+            UnloadSound(audio.menuMoveSound);
+            UnloadSound(audio.acceptSound);
+            UnloadSound(audio.menuChangeSound);
+            UnloadSound(audio.declineSound);
+            UnloadSound(audio.nonSound);
+        }
+    }
+}
+
+void effectsLogic()
+{
+    UpdateMusicStream(music);
+    if (isCameraMoving) {
+        float delta = GetFrameTime() * cameraMoveSpeed;
+        camera.target.x += (cameraTargetX - camera.target.x) * delta;
+        camera.target.y += (cameraTargetY - camera.target.y) * delta;
+        camera.zoom += (cameraTargetZoom - camera.zoom) * delta;
+
+        if (fabs(camera.target.x - cameraTargetX) < 5.0f &&
+            fabs(camera.target.y - cameraTargetY) < 5.0f &&
+            fabs(camera.zoom - cameraTargetZoom) < 0.5f) {
+            isCameraMoving = false;
+        }
+    }
+    playUIAnimation(framesUI);
+}
+
+void backgroundLogic() {
+    if (neededDraw2D)
+    {
+        DrawTexturePro(backgroundTexture, Rectangle(0, 0, cast(float) backgroundTexture.width, cast(
+                float) backgroundTexture.height), Rectangle(0, 0, cast(float) GetScreenWidth(), cast(
+                float) GetScreenHeight()), Vector2(0, 0), 0.0, Colors.WHITE);
+    }
+    for (int i = 0; i < characterTextures.length; i++)
+    {
+        if (characterTextures[i].drawTexture == true) {
+            float centeredX = characterTextures[i].x - (characterTextures[i].width * characterTextures[i].scale / 2);
+            float centeredY = characterTextures[i].y - (characterTextures[i].height * characterTextures[i].scale / 2);
+            
+            DrawTextureEx(characterTextures[i].texture,
+                        Vector2(centeredX, centeredY), 
+                        0.0, 
+                        characterTextures[i].scale, 
+                        Colors.WHITE);
+        }
+    }
+}
+
+void dialogLogic() {
+    if (showDialog) {
+        displayDialog(messageGlobal, choices, selectedChoice, choicePage, textFont, &showDialog, typingSpeed);
+    }
+}

+ 349 - 0
source/graphics/playback.d

@@ -0,0 +1,349 @@
+// quantumde1 developed software, licensed under MIT license.
+module graphics.playback;
+
+import std.stdio;
+import raylib;
+import raylib.rlgl;
+import core.stdc.stdlib;
+import core.stdc.string;
+import core.thread;
+import variables;
+import core.sync.mutex;
+import std.array;
+import std.file;
+import system.abstraction;
+
+extern (C)
+{
+    struct libvlc_instance_t;
+    struct libvlc_media_t;
+    struct libvlc_media_player_t;
+    struct libvlc_event_manager_t;
+    struct libvlc_event_t;
+    long libvlc_media_player_get_length(libvlc_media_player_t* player);
+    long libvlc_media_player_get_time(libvlc_media_player_t* player);
+    void libvlc_media_player_set_time(libvlc_media_player_t* player, long time);
+    libvlc_instance_t* libvlc_new(int argc, const(char)** argv);
+    void libvlc_release(libvlc_instance_t* instance);
+
+    libvlc_media_t* libvlc_media_new_location(libvlc_instance_t* instance, const(char)* mrl);
+    void libvlc_media_release(libvlc_media_t* media);
+
+    libvlc_media_player_t* libvlc_media_player_new_from_media(libvlc_media_t* media);
+    void libvlc_media_player_release(libvlc_media_player_t* player);
+    void libvlc_media_player_play(libvlc_media_player_t* player);
+    void libvlc_media_player_stop(libvlc_media_player_t* player);
+    int libvlc_media_player_get_state(libvlc_media_player_t* player);
+
+    void libvlc_video_set_callbacks(libvlc_media_player_t* player,
+        void* function(void*, void**),
+        void function(void*, void*, void*),
+        void* function(void*),
+        void* opaque);
+
+    void libvlc_video_set_format(libvlc_media_player_t* player,
+        const(char)* chroma,
+        uint width,
+        uint height,
+        uint pitch);
+
+    void libvlc_video_get_size(libvlc_media_player_t* player,
+        uint num,
+        uint* px,
+        uint* py);
+
+    libvlc_event_manager_t* libvlc_media_player_event_manager(libvlc_media_player_t* player);
+    void libvlc_event_attach(libvlc_event_manager_t* event_manager,
+        int event_type,
+        void function(const(libvlc_event_t)*, void*),
+        void* user_data);
+    int libvlc_event_detach(libvlc_event_manager_t* event_manager,
+        int event_type,
+        void function(const(libvlc_event_t)*, void*),
+        void* user_data);
+}
+
+enum libvlc_state_t
+{
+    libvlc_NothingSpecial = 0,
+    libvlc_Opening,
+    libvlc_Buffering,
+    libvlc_Playing,
+    libvlc_Paused,
+    libvlc_Stopped,
+    libvlc_Ended,
+    libvlc_Error
+}
+
+enum libvlc_event_type_t
+{
+    libvlc_MediaPlayerEndReached = 256
+}
+
+extern (C) void endReachedCallback(const(libvlc_event_t)* event, void* user_data)
+{
+    auto callback = cast(void function(void*)) user_data;
+    callback(user_data);
+}
+
+extern (C) void libvlc_media_player_set_media_player_end_reached_callback(
+    libvlc_media_player_t* player,
+    void function(void*), void* user_data)
+{
+    auto event_manager = libvlc_media_player_event_manager(player);
+    libvlc_event_attach(event_manager, libvlc_event_type_t.libvlc_MediaPlayerEndReached, &endReachedCallback,
+        cast(void*) user_data);
+}
+
+struct Video
+{
+    uint texW, texH;
+    float scale;
+    Mutex mutex;
+    Texture2D texture;
+    ubyte* buffer;
+    bool needUpdate;
+    libvlc_media_player_t* player;
+}
+
+extern (C) void* begin_vlc_rendering(void* data, void** p_pixels)
+{
+    auto video = cast(Video*) data;
+    video.mutex.lock();
+    *p_pixels = video.buffer;
+    return null;
+}
+
+extern (C) void end_vlc_rendering(void* data, void* id, void* p_pixels)
+{
+    auto video = cast(Video*) data;
+    video.needUpdate = true;
+    video.mutex.unlock();
+}
+
+extern (C) void videoEndCallback(void* data)
+{
+    videoFinished = true;
+    debug debugWriteln("Video ended");
+}
+
+Video* add_new_video(libvlc_instance_t* libvlc, const(char)* src, const(char)* protocol)
+{
+    auto video = cast(Video*) malloc(Video.sizeof);
+    if (video is null)
+    {
+        debug debugWriteln("Failed to allocate memory for video.");
+        return null;
+    }
+
+    video.mutex = new Mutex;
+    auto location = cast(char*) malloc(strlen(protocol) + strlen(src) + 3);
+    if (location is null)
+    {
+        debug debugWriteln("Failed to allocate memory for location.");
+        free(video);
+        return null;
+    }
+
+    sprintf(location, "%s://%s", protocol, src);
+    auto media = libvlc_media_new_location(libvlc, location);
+    free(location);
+
+    if (media is null)
+    {
+        debug debugWriteln("Failed to create media.");
+        free(video);
+        return null;
+    }
+
+    video.player = libvlc_media_player_new_from_media(media);
+    libvlc_media_release(media);
+
+    if (video.player is null)
+    {
+        debug debugWriteln("Failed to create media player.");
+        free(video);
+        return null;
+    }
+
+    video.needUpdate = false;
+    video.texW = 0;
+    video.texH = 0;
+    video.buffer = null;
+    video.texture.id = 0;
+
+    libvlc_video_set_callbacks(video.player, &begin_vlc_rendering, &end_vlc_rendering, null, video);
+    libvlc_media_player_set_media_player_end_reached_callback(video.player, &videoEndCallback, video);
+
+    return video;
+}
+
+void cleanup_video(Video* video)
+{
+    if (video is null)
+        return;
+
+    libvlc_media_player_stop(video.player);
+    libvlc_media_player_release(video.player);
+    if (video.texture.id != 0)
+    {
+        UnloadTexture(video.texture);
+    }
+    video.mutex.destroy();
+    if (video.buffer !is null)
+    {
+        MemFree(video.buffer);
+    }
+    free(video);
+}
+
+extern (C) int playVideoInternal(char* argv)
+{
+    const(char)*[] vlcArgs;
+    debug
+    {
+        vlcArgs = [
+            "--verbose=2", "--no-xlib", "--drop-late-frames", "--live-caching=0",
+            "--no-lua"
+        ];
+    }
+    else
+    {
+        vlcArgs = [
+            "--verbose=-1", "--no-xlib", "--drop-late-frames", "--live-caching=0",
+            "--no-lua"
+        ];
+    }
+    auto libvlc = libvlc_new(cast(int) vlcArgs.length, cast(const(char)**) vlcArgs.ptr);
+    if (libvlc is null)
+    {
+        debug debugWriteln("Something went wrong with libvlc init. Turn on DEBUG in conf/build_type.conf at BUILD_TYPE field to get more logs.");
+        videoFinished = true;
+        return 0;
+    }
+
+    Video*[] video_list;
+    auto new_video = add_new_video(libvlc, argv, "file");
+    if (new_video is null)
+    {
+        libvlc_release(libvlc);
+        return 0;
+    }
+
+    video_list ~= new_video;
+    libvlc_media_player_play(new_video.player);
+    debug debugWriteln("Video started playing");
+    while (!WindowShouldClose())
+    {
+        auto player = new_video.player;
+
+        if (libvlc_media_player_get_state(player) == libvlc_state_t.libvlc_Ended)
+        {
+            debug debugWriteln("Video reached end.");
+            videoFinished = true;
+        }
+
+        if (videoFinished)
+        {
+            break;
+        }
+
+
+        BeginDrawing();
+        ClearBackground(Colors.BLACK);
+        UpdateMusicStream(music);
+        foreach (video; video_list)
+        {
+            if (video.buffer is null)
+            {
+                if (libvlc_media_player_get_state(video.player) == libvlc_state_t.libvlc_Playing)
+                {
+                    libvlc_video_get_size(video.player, 0, &video.texW, &video.texH);
+
+                    if (video.texW > 0 && video.texH > 0)
+                    {
+                        float screenWidth = cast(float) GetScreenWidth();
+                        float screenHeight = cast(float) GetScreenHeight();
+                        float videoAspectRatio = cast(float) video.texW / cast(float) video.texH;
+                        float screenAspectRatio = screenWidth / screenHeight;
+
+                        // Calculate scale based on aspect ratio
+                        video.scale = (videoAspectRatio < screenAspectRatio) ?
+                            (screenHeight / cast(float) video.texH) : (
+                                screenWidth / cast(float) video.texW);
+
+                        // Set video format only once
+                        if (video.texture.id == 0)
+                        {
+                            libvlc_video_set_format(video.player, "RV24", video.texW, video.texH, video.texW * 3);
+                            video.mutex.lock();
+
+                            // Load the texture and assign the ID to the texture struct
+                            video.texture.id = rlLoadTexture(null, video.texW, video.texH, PixelFormat
+                                    .PIXELFORMAT_UNCOMPRESSED_R8G8B8, 1);
+                            video.texture.width = video.texW;
+                            video.texture.height = video.texH;
+                            video.texture.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8;
+                            video.texture.mipmaps = 1;
+
+                            // Allocate buffer for video frame
+                            video.buffer = cast(ubyte*) MemAlloc(video.texW * video.texH * 3);
+                            video.needUpdate = false;
+                            video.mutex.unlock();
+                            debug debugWriteln("Video texture initialized");
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if (video.needUpdate)
+                {
+                    video.mutex.lock();
+                    UpdateTexture(video.texture, video.buffer);
+                    video.needUpdate = false;
+                    video.mutex.unlock();
+                }
+
+                Vector2 position = {
+                    (GetScreenWidth() - video.texW * video.scale) * 0.5f, (
+                        GetScreenHeight() - video.texH * video.scale) * 0.5f
+                };
+                DrawTextureEx(video.texture, position, 0, video.scale, Colors.WHITE);
+            }
+        }
+
+        
+        if (IsKeyPressed(KeyboardKey.KEY_ENTER))
+        {
+            foreach (video; video_list)
+            {
+                cleanup_video(video);
+            }
+
+            videoFinished = true;
+            video_list.length = 0;
+            EndDrawing();
+            libvlc_release(libvlc);
+            return 0;
+        }
+
+        EndDrawing();
+    }
+
+    foreach (video; video_list)
+    {
+        cleanup_video(video);
+    }
+    videoFinished = true;
+    video_list.length = 0;
+    libvlc_release(libvlc);
+    return 0;
+}
+
+void playVideo(string filename) {
+    version (Posix)
+        playVideoInternal(cast(char*)(getcwd() ~ "/" ~ filename));
+    version (Windows)
+        playVideoInternal(cast(char*)("/" ~ getcwd() ~ "/" ~ filename));
+}

+ 459 - 0
source/scripts/lua.d

@@ -0,0 +1,459 @@
+// quantumde1 developed software, licensed under BSD-0-Clause license.
+module scripts.lua;
+
+import bindbc.lua;
+import raylib;
+import variables;
+import graphics.effects;
+import std.conv;
+import system.abstraction;
+import system.config;
+import std.string;
+import graphics.engine;
+import graphics.playback;
+import std.file;
+import std.array;
+import std.algorithm;
+
+/* 
+ * This module provides Lua bindings for various engine functionalities.
+ * Functions are built on top of engine built-in functions for execution from scripts.
+ * Not all engine functions usable for scripting are yet implemented.
+*/
+
+string hint;
+
+extern (C) nothrow int luaL_showHint(lua_State* L)
+{
+    hint = "" ~ to!string(luaL_checkstring(L, 1));
+    hintNeeded = true;
+    return 0;
+}
+
+/* text window */
+
+extern (C) nothrow int luaL_dialogBox(lua_State* L)
+{
+    showDialog = true;
+    luaL_checktype(L, 2, LUA_TTABLE);
+    choicePage = cast(int)luaL_checkinteger(L, 4);
+    
+    int textTableLength = cast(int) lua_objlen(L, 2);
+    messageGlobal = new string[](textTableLength); 
+
+    for (int i = 0; i < textTableLength; i++) {
+        lua_rawgeti(L, 2, i + 1);
+        messageGlobal[i] = luaL_checkstring(L, -1).to!string;
+        lua_pop(L, 1);
+    }
+
+    luaL_checktype(L, 5, LUA_TTABLE);
+    int choicesLength = cast(int) lua_objlen(L, 5);
+    choices = new string[choicesLength];
+    for (int i = 0; i < choicesLength; i++)
+    {
+        lua_rawgeti(L, 5, i + 1);
+        choices[i] = luaL_checkstring(L, -1).to!string;
+        lua_pop(L, 1);
+    }
+    if (lua_gettop(L) == 7)
+    {
+        typingSpeed = cast(float) luaL_checknumber(L, 7);
+    }
+
+    return 0;
+}
+
+extern (C) nothrow int luaL_getAnswerValue(lua_State* L)
+{
+    lua_pushinteger(L, selectedChoice);
+    return 1;
+}
+
+extern (C) nothrow int luaL_isDialogExecuted(lua_State *L) {
+    lua_pushboolean(L, showDialog);
+    return 1;
+}
+
+extern (C) nothrow int luaL_dialogAnswerValue(lua_State* L)
+{
+    lua_pushinteger(L, selectedChoice);
+    return 1;
+}
+
+/* background drawing and loading */
+
+extern (C) nothrow int luaL_load2Dbackground(lua_State* L)
+{
+    try
+    {
+        int index = cast(int) luaL_checkinteger(L, 2);
+        //if index too big, extending array
+        if (index >= backgrounds.length)
+        {
+            backgrounds.length = index + 1;
+        }
+
+        // if texture with same Index already loaded, unloading it
+        if (index < backgrounds.length && backgrounds[index].id != 0)
+        {
+            UnloadTexture(backgrounds[index]);
+        }
+        backgrounds[index] = LoadTexture(luaL_checkstring(L, 1));
+    }
+    catch (Exception e)
+    {
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_draw2Dbackground(lua_State* L)
+{
+    try
+    {
+        backgroundTexture = backgrounds[luaL_checkinteger(L, 1)];
+        neededDraw2D = true;
+    }
+    catch (Exception e)
+    {
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_unload2Dbackground(lua_State* L)
+{
+    UnloadTexture(backgrounds[cast(int) luaL_checkinteger(L, 1)]);
+    return 0;
+}
+
+/* character textures */
+
+extern (C) nothrow int luaL_load2Dcharacter(lua_State *L) {
+    try
+    {
+        int count = cast(int) luaL_checkinteger(L, 2);
+
+        if (count >= characterTextures.length)
+        {
+            characterTextures.length = count + 1;
+        }
+        characterTextures[count].texture = LoadTexture(luaL_checkstring(L, 1));
+        characterTextures[count].width = characterTextures[count].texture.width;
+        characterTextures[count].height = characterTextures[count].texture.height;
+        characterTextures[count].drawTexture = false;
+    }
+    catch (Exception e) {
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_draw2Dcharacter(lua_State* L)
+{
+    try {
+        int count = to!int(luaL_checkinteger(L, 4));
+        characterTextures[count].scale = luaL_checknumber(L, 3);
+        characterTextures[count].y = cast(int) luaL_checkinteger(L, 2);
+        characterTextures[count].x = cast(int) luaL_checkinteger(L, 1);
+        characterTextures[count].drawTexture = true;
+        debugWriteln("Count: ", count, " drawTexture cond: ", characterTextures[count].drawTexture);
+    } catch (Exception e) {
+        
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_stopDraw2Dcharacter(lua_State* L)
+{
+    int count = cast(int) luaL_checkinteger(L, 1);
+    characterTextures[count].drawTexture = false;
+    return 0;
+}
+
+extern (C) nothrow int luaL_unload2Dcharacter(lua_State *L) {
+    int count = cast(int) luaL_checkinteger(L, 1);
+    UnloadTexture(characterTextures[count].texture);
+    return 0;
+}
+
+/* music and video */
+
+extern (C) nothrow int luaL_LoadMusic(lua_State* L)
+{
+    try
+    {
+        musicPath = cast(char*) luaL_checkstring(L, 1);
+        music = LoadMusicStream(musicPath);
+    }
+    catch (Exception e)
+    {
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_PlayMusic(lua_State* L)
+{
+    PlayMusicStream(music);
+    return 0;
+}
+
+extern (C) nothrow int luaL_StopMusic(lua_State* L)
+{
+    StopMusicStream(music);
+    return 0;
+}
+
+extern (C) nothrow int luaL_playSfx(lua_State *L) {
+    try {
+    playSfx(to!string(luaL_checkstring(L, 1)));
+    } catch (Exception e) {
+
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_stopSfx(lua_State *L) {
+    StopSound(sfx);
+    return 0;
+}
+
+
+extern (C) nothrow int luaL_playVideo(lua_State* L)
+{
+    try
+    {
+        videoFinished = false;
+        playVideo(luaL_checkstring(L, 1).to!string);
+    }
+    catch (Exception e)
+    {
+    }
+    return 0;
+}
+
+/* ui animations */
+
+extern (C) nothrow int luaL_moveCamera(lua_State *L) {
+    try {
+        float targetX = cast(float) luaL_checknumber(L, 1);
+        float targetY = cast(float) luaL_checknumber(L, 2);
+        float zoom = cast(float) luaL_optnumber(L, 3, 1.0f);
+        float speed = cast(float) luaL_optnumber(L, 4, 5.0f);
+        cameraTargetX = targetX;
+        cameraTargetY = targetY;
+        cameraTargetZoom = zoom;
+        cameraMoveSpeed = speed;
+        isCameraMoving = true;
+    } catch (Exception e) {
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_isCameraMoving(lua_State *L) {
+    lua_pushboolean(L, isCameraMoving);
+    return 1;
+}
+
+extern (C) nothrow int luaL_loadUIAnimation(lua_State *L) {
+    try {
+        framesUI = loadAnimationFramesUI("res/uifx/"~to!string(luaL_checkstring(L, 1)), to!string(luaL_checkstring(L, 2)));
+        if (lua_gettop(L) == 3) {
+            frameDuration = luaL_checknumber(L, 3);
+            debug debugWriteln("frameDuration: ", frameDuration);
+        }
+    } catch (Exception e) {
+
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_playUIAnimation(lua_State *L) {
+    debug debugWriteln("Animation UI start");
+    try {
+        playAnimation = true;
+    } catch (Exception e) {
+
+    }
+    return 0;
+}
+
+extern (C) nothrow int luaL_stopUIAnimation(lua_State *L) {
+    playAnimation = false;
+    debug debugWriteln("Animation UI stop");
+    frameDuration = 0.016f;
+    currentFrame = 0;
+    return 0;
+}
+
+extern (C) nothrow int luaL_unloadUIAnimation(lua_State *L) {
+    try {
+        for (int i = 0; i < framesUI.length; i++) {
+            UnloadTexture(framesUI[i]);
+        }
+    } catch (Exception e) {
+
+    }
+    return 0;
+}
+
+/* system */
+
+extern (C) nothrow int luaL_getScreenWidth(lua_State* L)
+{
+    lua_pushinteger(L, GetScreenWidth());
+    return 1;
+}
+
+extern (C) nothrow int luaL_getScreenHeight(lua_State* L)
+{
+    lua_pushinteger(L, GetScreenHeight());
+    return 1;
+}
+
+extern (C) nothrow int luaL_getUsedLanguage(lua_State* L)
+{
+    lua_pushstring(L, usedLang.toStringz());
+    return 1;
+}
+
+extern (C) nothrow int luaL_2dModeEnable(lua_State* L)
+{
+    neededDraw2D = true;
+    return 0;
+}
+
+extern (C) nothrow int luaL_2dModeDisable(lua_State* L)
+{
+    neededDraw2D = false;
+    return 0;
+}
+
+extern (C) nothrow int luaL_setGameFont(lua_State* L)
+{
+    const char* x = luaL_checkstring(L, 1);
+    debugWriteln("Setting custom font: ", x.to!string);
+    int[512] codepoints = 0;
+    foreach (i; 0 .. 95)
+    {
+        codepoints[i] = 32 + i;
+    }
+    foreach (i; 0 .. 255)
+    {
+        codepoints[96 + i] = 0x400 + i;
+    }
+    textFont = LoadFontEx(x, 40, codepoints.ptr, codepoints.length);
+    return 0;
+}
+
+extern (C) nothrow int luaL_getTime(lua_State* L)
+{
+    lua_pushnumber(L, GetTime());
+    return 1;
+}
+
+extern (C) nothrow int luaL_isKeyPressed(lua_State* L)
+{
+    try
+    {
+        if (IsKeyPressed(cast(int)(luaL_checkinteger(L, 1))))
+        {
+            lua_pushboolean(L, true);
+        }
+        else
+        {
+            lua_pushboolean(L, false);
+        }
+    }
+    catch (Exception e)
+    {
+
+    }
+    return 1;
+}
+
+extern (C) nothrow int luaL_loadScript(lua_State* L)
+{
+    for (int i = cast(int) characterTextures.length; i < characterTextures.length; i++)
+    {
+        UnloadTexture(characterTextures[i].texture);
+    }
+    for (int i = cast(int) backgrounds.length; i < backgrounds.length; i++)
+    {
+        UnloadTexture(backgrounds[i]);
+    }
+    try
+    {
+        luaExec = to!string(luaL_checkstring(L, 1));
+        resetAllScriptValues();
+    }
+    catch (Exception e)
+    {
+    }
+    luaReload = true;
+    return 0;
+}
+
+/* Register functions */
+
+extern (C) nothrow void luaL_loader(lua_State* L)
+{
+    lua_register(L, "dialogBox", &luaL_dialogBox);
+    lua_register(L, "dialogAnswerValue", &luaL_dialogAnswerValue);
+    lua_register(L, "isDialogExecuted", &luaL_isDialogExecuted);
+    lua_register(L, "getAnswerValue", &luaL_getAnswerValue);
+    lua_register(L, "loadAnimationUI", &luaL_loadUIAnimation);
+    lua_register(L, "playAnimationUI", &luaL_playUIAnimation);
+    lua_register(L, "stopAnimationUI", &luaL_stopUIAnimation);
+    lua_register(L, "unloadAnimationUI", &luaL_unloadUIAnimation);
+    lua_register(L, "moveCamera", &luaL_moveCamera);
+    lua_register(L, "isCameraMoving", &luaL_isCameraMoving);
+    lua_register(L, "playVideo", &luaL_playVideo);
+    lua_register(L, "loadMusic", &luaL_LoadMusic);
+    lua_register(L, "playMusic", &luaL_PlayMusic);
+    lua_register(L, "stopMusic", &luaL_StopMusic);
+    lua_register(L, "playSfx", &luaL_playSfx);
+    lua_register(L, "stopSfx", &luaL_stopSfx);
+    lua_register(L, "Begin2D", &luaL_2dModeEnable);
+    lua_register(L, "End2D", &luaL_2dModeDisable);
+    lua_register(L, "load2Dcharacter", &luaL_load2Dcharacter);
+    lua_register(L, "draw2Dcharacter", &luaL_draw2Dcharacter);
+    lua_register(L, "stopDraw2Dcharacter", &luaL_stopDraw2Dcharacter);
+    lua_register(L, "unload2Dcharacter", &luaL_unload2Dcharacter);
+    lua_register(L, "load2Dtexture", &luaL_load2Dbackground);
+    lua_register(L, "draw2Dtexture", &luaL_draw2Dbackground);
+    lua_register(L, "unload2Dtexture", &luaL_unload2Dbackground);
+    lua_register(L, "getTime", &luaL_getTime);
+    lua_register(L, "loadScript", &luaL_loadScript);
+    lua_register(L, "setFont", &luaL_setGameFont);
+    lua_register(L, "getScreenHeight", &luaL_getScreenHeight);
+    lua_register(L, "getScreenWidth", &luaL_getScreenWidth);
+    lua_register(L, "isKeyPressed", &luaL_isKeyPressed);
+    lua_register(L, "getLanguage", &luaL_getUsedLanguage);
+}
+
+int luaInit(string luaExec)
+{
+    debugWriteln("loading Lua");
+    L = luaL_newstate();
+    luaL_openlibs(L);
+    luaL_loader(L);
+    debugWriteln("Executing next Lua file: ", luaExec);
+    if (std.file.exists(luaExec) == false) {
+        debugWriteln("Script file not found! Exiting.");
+        return EngineExitCodes.EXIT_FILE_NOT_FOUND;
+    }
+    if (luaL_dofile(L, toStringz(luaExec)) != LUA_OK) {
+        debugWriteln("Lua error: ", to!string(lua_tostring(L, -1)));
+        return EngineExitCodes.EXIT_SCRIPT_ERROR;
+    }
+    return EngineExitCodes.EXIT_OK;
+}
+
+void luaEventLoop()
+{
+    lua_getglobal(L, "EventLoop");
+    if (lua_pcall(L, 0, 0, 0) != LUA_OK)
+    {
+        debug debugWriteln("Error in EventLoop: ", to!string(lua_tostring(L, -1)));
+    }
+    lua_pop(L, 0);
+}

+ 18 - 0
source/system/abstraction.d

@@ -0,0 +1,18 @@
+module system.abstraction;
+
+import std.stdio;
+
+nothrow void debugWriteln(A...)(A args)
+{
+    debug
+    {
+        try
+        {
+            writeln("INFO: ENGINE: ", args);
+        }
+        catch (Exception e)
+        {
+
+        }
+    }
+}

+ 32 - 0
source/system/cleanup.d

@@ -0,0 +1,32 @@
+module system.cleanup;
+
+import std.stdio;
+import variables;
+import raylib;
+import system.abstraction;
+
+void unloadResourcesLogic()
+{
+    debugWriteln("Exiting. See ya'!");
+    StopMusicStream(music);
+    EndDrawing();
+    if (sfxEnabled) {
+        UnloadSound(audio.menuMoveSound);
+        UnloadSound(audio.acceptSound);
+        UnloadSound(audio.menuChangeSound);
+        UnloadSound(audio.declineSound);
+        UnloadSound(audio.nonSound);
+    }
+    UnloadFont(textFont);
+    for (int i = cast(int) characterTextures.length; i < characterTextures.length; i++)
+    {
+        UnloadTexture(characterTextures[i].texture);
+    }
+    for (int i = cast(int) backgrounds.length; i < backgrounds.length; i++)
+    {
+        UnloadTexture(backgrounds[i]);
+    }
+    UnloadMusicStream(music);
+    CloseAudioDevice();
+    CloseWindow();
+}

+ 69 - 0
source/system/config.d

@@ -0,0 +1,69 @@
+module system.config;
+
+import std.stdio;
+import std.file;
+import std.string;
+import std.range;
+import variables;
+import std.conv;
+import system.abstraction;
+
+nothrow char parseConf(string filename, string type)
+{
+    try
+    {
+        auto file = File(filename);
+        auto config = file.byLineCopy();
+
+        // Create a mapping of types to their corresponding prefixes
+        auto typeMap = [
+            "sound": "SOUND:",
+            "backward": "BACKWARD:",
+            "forward": "FORWARD:",
+            "right": "RIGHT:",
+            "left": "LEFT:",
+            "dialog": "DIALOG:",
+            "opmenu": "OPMENU:"
+        ];
+
+        // Check if the provided type exists in the map
+        if (type in typeMap)
+        {
+            auto prefix = typeMap[type];
+            foreach (line; config)
+            {
+                auto trimmedLine = strip(line);
+                if (trimmedLine.startsWith(prefix))
+                {
+                    auto button = trimmedLine[prefix.length .. $].strip();
+                    debug debugWriteln("Value for ", type, ": ", button);
+                    return button.front.to!char;
+                }
+            }
+        }
+    }
+    catch (Exception e)
+    {
+        try
+        {
+            debugWriteln("Error parsing config: " ~ e.msg);
+        }
+        catch (Exception e)
+        {
+        }
+    }
+    return 'E';
+}
+
+SystemSettings loadSettingsFromConfigFile()
+{
+    return SystemSettings(
+        parseConf("conf/settings.conf", "right"),
+        parseConf("conf/settings.conf", "left"),
+        parseConf("conf/settings.conf", "backward"),
+        parseConf("conf/settings.conf", "forward"),
+        parseConf("conf/settings.conf", "dialog"),
+        parseConf("conf/settings.conf", "opmenu"),
+        parseConf("conf/settings.conf", "sound")
+    );
+}

+ 366 - 0
source/ui/menu.d

@@ -0,0 +1,366 @@
+module ui.menu;
+
+import raylib;
+import variables;
+import std.stdio;
+import system.abstraction;
+import system.config;
+import core.time;
+import core.thread;
+import std.string;
+import graphics.engine;
+import graphics.playback;
+import std.file;
+
+enum
+{
+    MENU_ITEM_START = 0,
+    MENU_ITEM_SOUND = 1,
+    MENU_ITEM_SFX = 2,
+    MENU_ITEM_FULLSCREEN = 3,
+    MENU_ITEM_EXIT = 4,
+
+    FADE_SPEED_IN = 0.02f,
+    FADE_SPEED_OUT = 0.04f,
+    INACTIVITY_TIMEOUT = 20.0f
+}
+
+void fadeEffect(float alpha, bool fadeIn, void delegate(float alpha) renderer)
+{
+    const float FadeIncrement = 0.02f;
+
+    while (fadeIn ? alpha < 2.0f : alpha > 0.0f)
+    {
+        alpha += fadeIn ? FadeIncrement : -FadeIncrement;
+        BeginDrawing();
+        ClearBackground(Colors.BLACK);
+        renderer(alpha);
+        EndDrawing();
+    }
+}
+
+struct MenuState
+{
+    string[] options;
+    int selectedIndex;
+    float fadeAlpha;
+    float inactivityTimer;
+    Texture2D logoTexture;
+    Music menuMusic;
+    bool fromSave;
+    int logoX, logoY;
+}
+
+void renderText(float alpha, immutable(char)* text)
+{
+    DrawTextEx(textFont, text,
+        Vector2(GetScreenWidth() / 2 - MeasureText(text, 40) / 2,
+            GetScreenHeight() / 2), 40, 0, Fade(Colors.WHITE, alpha)
+    );
+}
+
+void renderLogo(float alpha, immutable(char)* name, bool fullscreen)
+{
+    Texture2D companyLogo = LoadTexture(name);
+    if (fullscreen)
+    {
+        DrawTexturePro(companyLogo,
+            Rectangle(0, 0, cast(float) companyLogo.width, cast(float) companyLogo.height),
+            Rectangle(0, 0, cast(float) GetScreenWidth(), cast(float) GetScreenHeight()),
+            Vector2(0, 0), 0.0, Fade(Colors.WHITE, alpha));
+    }
+    else
+    {
+        DrawTexture(companyLogo, GetScreenWidth() / 2, GetScreenHeight() / 2, Colors.WHITE);
+    }
+}
+
+void helloScreen()
+{
+    debug
+    {
+        bool play = false;
+        debugWriteln("hello screen showing");
+        if (play == false)
+        {
+            videoFinished = true;
+            goto debug_lab;
+        }
+    }
+    else
+        {
+        fadeEffect(0.0f, true, (float alpha) {
+            renderText(alpha, "powered by\n\nHimmel Engine");
+        });
+
+        fadeEffect(2.0f, false, (float alpha) {
+            renderText(alpha, "powered by\n\nHimmel Engine");
+        });
+        /*
+        fadeEffect(0.0f, true, (float alpha) {
+            renderLogo(alpha, "atlus_logo.png".toStringz, true);
+        });
+        
+        fadeEffect(fadeAlpha, false, (float alpha) {
+            renderLogo(alpha, "atlus_logo.png".toStringz, true);
+        });
+        */
+        // Play Opening Video
+        BeginDrawing();
+        debug debugWriteln("searching for video");
+        if (std.file.exists(getcwd() ~ "/res/videos/soul_OP.moflex.mp4"))
+        {
+            debug debugWriteln("video found, playing");
+            playVideo("/res/videos/soul_OP.moflex.mp4");
+        }
+        else
+        {
+            debug debugWriteln("video not found, skipping");
+            videoFinished = true;
+        }
+    }
+    debug_lab:
+}
+
+MenuState initMenuState()
+{
+    MenuState state;
+    state.fromSave = std.file.exists(getcwd() ~ "/save.txt");
+    state.options = [
+        "Start Game", 
+        "Sound: On",
+        "SFX: On",
+        "Fullscreen: On",
+        "Exit Game",
+    ];
+
+    if (state.fromSave)
+        state.options[MENU_ITEM_START] = "Continue";
+    if (!audioEnabled)
+        state.options[MENU_ITEM_SOUND] = "Sound: Off";
+
+    state.fadeAlpha = 0.0f;
+    state.inactivityTimer = 0.0f;
+    state.selectedIndex = 0;
+
+    state.logoTexture = LoadTexture("res/data/menu_logo.png");
+    state.logoX = (GetScreenWidth() - state.logoTexture.width) / 2;
+    state.logoY = (GetScreenHeight() - state.logoTexture.height) / 2 - 50;
+
+    state.menuMusic = LoadMusicStream("res/data/menu_music.mp3");
+    if (audioEnabled)
+    {
+        PlayMusicStream(state.menuMusic);
+    }
+    audio.declineSound = LoadSound("res/sfx/10002.wav");
+    audio.acceptSound = LoadSound("res/sfx/10003.wav");
+    audio.menuMoveSound = LoadSound("res/sfx/10004.wav");
+    audio.menuChangeSound = LoadSound("res/sfx/00152.wav");
+    audio.nonSound = LoadSound("res/sfx/00154.wav");
+    return state;
+}
+
+void cleanupMenu(ref MenuState state)
+{
+    UnloadTexture(state.logoTexture);
+    UnloadMusicStream(state.menuMusic);
+}
+
+void drawMenu(ref const MenuState state)
+{
+    BeginDrawing();
+    ClearBackground(Colors.BLACK);
+
+    DrawTextureEx(
+        state.logoTexture,
+        Vector2(state.logoX, state.logoY),
+        0.0f, 1.0f,
+        Fade(Colors.WHITE, state.fadeAlpha)
+    );
+
+    for (int i = 0; i < state.options.length; i++)
+    {
+        Color textColor = (i == state.selectedIndex) ? Colors.LIGHTGRAY : Colors.GRAY;
+        float textWidth = MeasureTextEx(textFont, toStringz(state.options[i]), 30, 0).x;
+        float textX = (GetScreenWidth() - textWidth) / 2;
+        int textY = state.logoY + state.logoTexture.height + 100 + (30 * i);
+
+        DrawTextEx(
+            textFont,
+            toStringz(state.options[i]),
+            Vector2(textX, textY),
+            30, 0,
+            Fade(textColor, state.fadeAlpha)
+        );
+    }
+
+    EndDrawing();
+}
+
+void handleInactivity(ref MenuState state)
+{
+    state.inactivityTimer += GetFrameTime();
+
+    if (state.inactivityTimer >= INACTIVITY_TIMEOUT)
+    {
+        while (state.fadeAlpha > 0.0f)
+        {
+            state.fadeAlpha -= FADE_SPEED_OUT;
+            if (state.fadeAlpha < 0.0f)
+                state.fadeAlpha = 0.0f;
+            drawMenu(state);
+        }
+
+        StopMusicStream(state.menuMusic);
+        playVideo("/res/videos/opening_old.mp4");
+        if (audioEnabled)
+        {
+            PlayMusicStream(state.menuMusic);
+        }
+
+        state.inactivityTimer = 0.0f;
+
+        while (state.fadeAlpha < 1.0f)
+        {
+            state.fadeAlpha += FADE_SPEED_IN;
+            if (state.fadeAlpha > 1.0f)
+                state.fadeAlpha = 1.0f;
+            drawMenu(state);
+        }
+    }
+}
+
+void handleMenuNavigation(ref MenuState state)
+{
+    bool moved = false;
+
+    if (IsKeyPressed(KeyboardKey.KEY_DOWN))
+    {
+        state.selectedIndex = cast(int)((state.selectedIndex + 1) % state.options.length);
+        state.inactivityTimer = 0;
+        moved = true;
+    }
+
+    if (IsKeyPressed(KeyboardKey.KEY_UP))
+    {
+        state.selectedIndex = cast(int)(
+            (state.selectedIndex - 1 + state.options.length) % state.options.length);
+        state.inactivityTimer = 0;
+        moved = true;
+    }
+
+    if (moved && sfxEnabled)
+    {
+        PlaySound(audio.menuMoveSound);
+    }
+}
+
+void handleMenuSettings(ref MenuState state)
+{
+    bool leftPressed = IsKeyPressed(KeyboardKey.KEY_LEFT);
+    bool rightPressed = IsKeyPressed(KeyboardKey.KEY_RIGHT);
+
+    if (!leftPressed && !rightPressed)
+        return;
+
+    state.inactivityTimer = 0;
+
+    if (sfxEnabled) PlaySound(audio.menuChangeSound);
+
+    switch (state.selectedIndex)
+    {
+    case MENU_ITEM_SOUND:
+        audioEnabled = rightPressed ? false : true;
+        state.options[MENU_ITEM_SOUND] = audioEnabled ? "Sound: On" : "Sound: Off";
+
+        if (audioEnabled)
+        {
+            PlayMusicStream(state.menuMusic);
+        }
+        else
+        {
+            StopMusicStream(state.menuMusic);
+        }
+        break;
+    case MENU_ITEM_SFX:
+        sfxEnabled = rightPressed ? false : true;
+        state.options[MENU_ITEM_SFX] = sfxEnabled ? "SFX: On" : "SFX: Off";
+        break;
+
+    case MENU_ITEM_FULLSCREEN:
+        fullscreenEnabled = rightPressed ? false : true;
+        state.options[MENU_ITEM_FULLSCREEN] = fullscreenEnabled ? "Fullscreen: On" : "Fullscreen: Off";
+        if (fullscreenEnabled) {
+            if (!IsWindowFullscreen()) {
+                ToggleFullscreen();
+                HideCursor();
+                
+            }
+        } else if (fullscreenEnabled == false) {
+            if (IsWindowFullscreen()) {
+                ToggleFullscreen();
+                ShowCursor();
+            }
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+void showMainMenu(ref GameState currentGameState)
+{
+    MenuState state = initMenuState();
+
+    while (state.fadeAlpha < 1.0f)
+    {
+        state.fadeAlpha += FADE_SPEED_IN;
+        if (state.fadeAlpha > 1.0f)
+            state.fadeAlpha = 1.0f;
+        drawMenu(state);
+    }
+
+    while (!WindowShouldClose())
+    {
+        UpdateMusicStream(state.menuMusic);
+
+
+        handleInactivity(state);
+        handleMenuNavigation(state);
+        handleMenuSettings(state);
+
+        if (IsKeyPressed(KeyboardKey.KEY_ENTER) ||
+            IsKeyPressed(KeyboardKey.KEY_SPACE))
+        {
+            if (sfxEnabled) PlaySound(audio.acceptSound);
+            switch (state.selectedIndex)
+            {
+            case MENU_ITEM_START:
+                while (state.fadeAlpha > 0.0f)
+                {
+                    state.fadeAlpha -= FADE_SPEED_OUT;
+                    if (state.fadeAlpha < 0.0f)
+                        state.fadeAlpha = 0.0f;
+                    drawMenu(state);
+                }
+
+                cleanupMenu(state);
+                currentGameState = GameState.InGame;
+                debug debugWriteln("getting into game...");
+                return;
+
+            case MENU_ITEM_EXIT:
+                cleanupMenu(state);
+                currentGameState = GameState.Exit;
+                return;
+
+            default:
+                break;
+            }
+        }
+
+        drawMenu(state);
+    }
+
+    cleanupMenu(state);
+}

+ 155 - 0
source/variables.d

@@ -0,0 +1,155 @@
+// quantumde1 developed software, licensed under MIT license.
+module variables;
+
+import std.typecons;
+import raylib;
+import bindbc.lua;
+import system.abstraction;
+
+extern (C) char* get_file_data_from_archive(const char *input_file, const char *file_name, uint *file_size_out);
+
+void resetAllScriptValues() {
+    debugWriteln("Resetting all values!");
+    selectedChoice = 0;
+    characterTextures = [];
+    backgrounds = [];
+}
+
+/* system */
+
+enum EngineExitCodes {
+    EXIT_FILE_NOT_FOUND = 2,
+    EXIT_SCRIPT_ERROR = 3,
+    EXIT_OK = 0,
+}
+
+struct SystemSettings {
+    int sound_state;
+    char right_button;
+    char left_button;
+    char back_button;
+    char forward_button;
+    char dialog_button;
+    char opmenu_button;
+}
+
+struct CharacterTexture {
+    bool drawTexture;
+    float width;
+    float height;
+    float x;
+    float y;
+    Texture2D texture;
+    float scale;
+}
+
+struct InterfaceAudio {
+    Sound menuMoveSound;
+    Sound menuChangeSound;
+    Sound acceptSound;
+    Sound declineSound;
+    Sound nonSound;
+}
+
+enum GameState {
+    MainMenu,
+    InGame,
+    Exit
+}
+
+Camera2D camera;
+
+CharacterTexture[] characterTextures;
+
+SystemSettings systemSettings;
+
+InterfaceAudio audio;
+
+GameState currentGameState;
+
+Font textFont;
+
+Music music;
+
+
+/* booleans */
+
+bool hintNeeded;
+
+bool playAnimation;
+
+bool audioEnabled;
+
+bool sfxEnabled = true;
+
+bool fullscreenEnabled = true;
+
+bool luaReload = true;
+
+bool videoFinished;
+
+bool neededDraw2D;
+
+bool isCameraMoving;
+
+bool neededCharacterDrawing;
+
+bool showDialog;
+
+bool isTextFullyDisplayed;
+
+
+/* strings */
+
+string ver = "1.1.8";
+
+string[] messageGlobal;
+
+string[] choices;
+
+string luaExec;
+
+string usedLang = "english";
+
+char* musicPath;
+
+
+/* floats */
+
+float cameraTargetX = 0;
+
+float cameraTargetY = 0;
+
+float cameraTargetZoom = 1.0f;
+
+float cameraMoveSpeed = 5.0f;
+
+float frameDuration = 0.016f;
+
+float typingSpeed = 0.6f;
+
+
+/* textures */
+
+Texture2D[] backgrounds;
+
+Texture2D[] framesUI;
+
+Texture2D backgroundTexture;
+
+/* integer values */
+
+int button;
+
+int selectedChoice = 0;
+
+int choicePage;
+
+int currentFrame = 0;
+
+int currentChoiceCharIndex = 0;
+
+
+/* lua */
+
+lua_State* L;

+ 40 - 0
test.sh

@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# Проверяем, установлен ли ImageMagick (необходим для работы convert)
+if ! command -v convert &> /dev/null; then
+    echo "Ошибка: ImageMagick не установлен. Установите его для работы скрипта."
+    echo "На Ubuntu/Debian: sudo apt install imagemagick"
+    echo "На CentOS/RHEL: sudo yum install imagemagick"
+    exit 1
+fi
+
+# Проверяем, указана ли папка с изображениями
+if [ -z "$1" ]; then
+    echo "Использование: $0 /путь/к/папке/с/изображениями"
+    exit 1
+fi
+
+# Переходим в указанную папку
+cd "$1" || exit 1
+
+# Счетчик конвертированных файлов
+converted=0
+
+# Обрабатываем все JPG и JPEG файлы
+for file in *.jpg *.jpeg; do
+    # Пропускаем несуществующие файлы (если нет jpg, например)
+    [ -e "$file" ] || continue
+    
+    # Формируем имя PNG файла
+    png_file="${file%.*}.png"
+    
+    echo "Конвертация: $file -> $png_file"
+    
+    # Выполняем конвертацию
+    convert "$file" "$png_file"
+    
+    # Увеличиваем счетчик
+    ((converted++))
+done
+
+echo "Готово! Конвертировано $converted файлов в PNG."

二进制
vlc.lib