From 737214b71be8fe5fdf51155ad50bb064b3156bd3 Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Mon, 3 Nov 2025 17:48:56 +0530 Subject: [PATCH] Add embedded assets, splash screens, and asset loading support Features added: - Embedded main.lua and Lua files support (EMBED_MAIN option) - Embedded assets support (EMBED_ASSETS option) - Splash screens with dual logo display (always embedded) - Asset loading progress tracking API (BeginAssetLoading, UpdateAssetLoading, EndAssetLoading) - Custom font embedding for splash/loading screens - --log flag for Windows console control - --no-logo flag to skip splash screens in development - Python scripts for embedding (embed_lua.py, embed_assets.py, embed_logo.py, embed_font.py) - Documentation (EMBEDDING.md, ASSET_LOADING.md, SPLASH_SCREENS.md) This allows building single-executable releases with all Lua code and assets embedded. --- ASSET_LOADING.md | 285 ++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 70 +++++++++++ EMBEDDING.md | 290 +++++++++++++++++++++++++++++++++++++++++++ SPLASH_SCREENS.md | 230 ++++++++++++++++++++++++++++++++++ build/.gitignore | 1 - embed_assets.py | 116 +++++++++++++++++ embed_font.py | 57 +++++++++ embed_logo.py | 64 ++++++++++ embed_lua.py | 92 ++++++++++++++ fonts/Oleaguid.ttf | Bin 0 -> 112828 bytes include/core.h | 3 + include/lua_core.h | 2 +- include/splash.h | 6 + logo/raylib_logo.png | Bin 0 -> 2466 bytes logo/reilua_logo.png | Bin 0 -> 1191 bytes src/core.c | 61 +++++++++ src/lua_core.c | 236 +++++++++++++++++++++++++++++++++-- src/main.c | 114 ++++++++++++++--- src/splash.c | 198 +++++++++++++++++++++++++++++ 19 files changed, 1801 insertions(+), 24 deletions(-) create mode 100644 ASSET_LOADING.md create mode 100644 EMBEDDING.md create mode 100644 SPLASH_SCREENS.md delete mode 100644 build/.gitignore create mode 100644 embed_assets.py create mode 100644 embed_font.py create mode 100644 embed_logo.py create mode 100644 embed_lua.py create mode 100644 fonts/Oleaguid.ttf create mode 100644 include/splash.h create mode 100644 logo/raylib_logo.png create mode 100644 logo/reilua_logo.png create mode 100644 src/splash.c diff --git a/ASSET_LOADING.md b/ASSET_LOADING.md new file mode 100644 index 0000000..67a3702 --- /dev/null +++ b/ASSET_LOADING.md @@ -0,0 +1,285 @@ +# Asset Loading System + +ReiLua includes a built-in asset loading system with a nice loading screen UI that automatically shows progress while assets are being loaded. + +## 🎨 Features + +- **Automatic Progress Tracking** - Tracks how many assets have been loaded +- **Beautiful Loading UI** - Modern, minimal loading screen with: + - Animated "Loading..." text with dots + - Smooth progress bar with shimmer effect + - Progress percentage (e.g., "3 / 10") + - Current asset name being loaded + - Dark, professional color scheme +- **Easy to Use** - Just 3 functions to show loading progress +- **Works Everywhere** - Development and release builds + +## 📝 API Functions + +### RL.BeginAssetLoading(totalAssets) + +Initialize asset loading progress tracking and show the loading screen. + +**Parameters:** +- `totalAssets` (integer) - Total number of assets to load + +**Example:** +```lua +RL.BeginAssetLoading(10) -- We're loading 10 assets +``` + +--- + +### RL.UpdateAssetLoading(assetName) + +Update the loading progress and display current asset being loaded. + +**Parameters:** +- `assetName` (string) - Name of the asset currently being loaded + +**Example:** +```lua +RL.UpdateAssetLoading("player.png") +``` + +Call this **after** each asset is loaded to update the progress bar. + +--- + +### RL.EndAssetLoading() + +Finish asset loading and hide the loading screen. + +**Example:** +```lua +RL.EndAssetLoading() +``` + +## 🚀 Quick Example + +```lua +function RL.init() + -- List of assets to load + local assetsToLoad = { + "assets/player.png", + "assets/enemy.png", + "assets/background.png", + "assets/music.wav", + } + + -- Begin loading + RL.BeginAssetLoading(#assetsToLoad) + + -- Load each asset + for i, path in ipairs(assetsToLoad) do + RL.UpdateAssetLoading(path) -- Update progress + + -- Load the actual asset + if path:match("%.png$") or path:match("%.jpg$") then + textures[i] = RL.LoadTexture(path) + elseif path:match("%.wav$") or path:match("%.ogg$") then + sounds[i] = RL.LoadSound(path) + end + end + + -- Done! + RL.EndAssetLoading() +end +``` + +## 💡 Complete Example + +```lua +local assets = {} + +local assetsToLoad = { + {type="texture", name="player", path="assets/player.png"}, + {type="texture", name="enemy", path="assets/enemy.png"}, + {type="texture", name="background", path="assets/background.png"}, + {type="sound", name="music", path="assets/music.wav"}, + {type="sound", name="shoot", path="assets/shoot.wav"}, + {type="font", name="title", path="assets/title.ttf"}, +} + +function RL.init() + RL.SetWindowTitle("My Game") + + -- Start loading with progress + RL.BeginAssetLoading(#assetsToLoad) + + -- Load all assets + for i, asset in ipairs(assetsToLoad) do + -- Show current asset name on loading screen + RL.UpdateAssetLoading(asset.name) + + -- Load based on type + if asset.type == "texture" then + assets[asset.name] = RL.LoadTexture(asset.path) + elseif asset.type == "sound" then + assets[asset.name] = RL.LoadSound(asset.path) + elseif asset.type == "font" then + assets[asset.name] = RL.LoadFont(asset.path) + end + end + + -- Loading complete! + RL.EndAssetLoading() + + print("Game ready!") +end + +function RL.update(delta) + -- Your game logic +end + +function RL.draw() + RL.ClearBackground(RL.RAYWHITE) + + -- Use loaded assets + if assets.background then + RL.DrawTexture(assets.background, {0, 0}, RL.WHITE) + end + + if assets.player then + RL.DrawTexture(assets.player, {100, 100}, RL.WHITE) + end +end +``` + +## 🎨 Loading Screen Appearance + +The loading screen features a clean 1-bit pixel art style: + +**Design:** +- Pure black and white aesthetic +- Retro pixel art styling +- Minimal and clean design + +**Elements:** +- **Title**: "LOADING" in bold white pixel text +- **Animated Dots**: White pixelated dots (4x4 squares) that cycle +- **Progress Bar**: + - 200px wide, 16px tall + - Thick 2px white border (pixel art style) + - White fill with black dithering pattern + - Retro/Classic terminal aesthetic +- **Progress Text**: "3/10" in white pixel font style +- **Asset Name**: Current loading asset in small white text +- **Corner Decorations**: White pixel art L-shaped corners in all 4 corners + +**Background:** +- Pure black background (#000000) +- High contrast for maximum clarity + +**Color Palette:** +- White text and UI (#FFFFFF) +- Black background (#000000) +- Pure 1-bit aesthetic (inverted terminal style) + +**Visual Layout:** +``` +[Black Background] + +┌─┐ ┌─┐ +│ │ LOADING □ □ │ │ +│ │ │ │ +│ │ ┌──────────────────┐ │ │ +│ │ │████████░░░░░░░░░░│ 3/10 │ │ +│ │ └──────────────────┘ │ │ +│ │ │ │ +│ │ player.png │ │ +│ │ │ │ +└─┘ └─┘ + +[All text and UI elements in WHITE] +``` + +**Style Inspiration:** +- Classic terminal / console aesthetic +- MS-DOS loading screens +- 1-bit dithering patterns +- Chunky pixel borders +- Retro computing / CRT monitor style + +## 🔧 Customization + +If you want to customize the loading screen appearance, you can modify the colors and sizes in `src/lua_core.c` in the `drawLoadingScreen()` function. + +## ⚡ Performance Tips + +1. **Call UpdateAssetLoading AFTER loading** - This ensures the progress updates at the right time +2. **Load assets in order of importance** - Load critical assets first +3. **Group similar assets** - Load all textures, then sounds, etc. +4. **Use descriptive names** - Shows better feedback to users + +## 📋 Example Asset Loading Patterns + +### Pattern 1: Simple List +```lua +local files = {"player.png", "enemy.png", "music.wav"} +RL.BeginAssetLoading(#files) +for i, file in ipairs(files) do + RL.UpdateAssetLoading(file) + -- load file +end +RL.EndAssetLoading() +``` + +### Pattern 2: With Types +```lua +local assets = { + textures = {"player.png", "enemy.png"}, + sounds = {"music.wav", "shoot.wav"}, +} +local total = #assets.textures + #assets.sounds + +RL.BeginAssetLoading(total) +for _, file in ipairs(assets.textures) do + RL.UpdateAssetLoading(file) + -- load texture +end +for _, file in ipairs(assets.sounds) do + RL.UpdateAssetLoading(file) + -- load sound +end +RL.EndAssetLoading() +``` + +### Pattern 3: Error Handling +```lua +RL.BeginAssetLoading(#files) +for i, file in ipairs(files) do + RL.UpdateAssetLoading(file) + + if RL.FileExists(file) then + -- load file + else + print("Warning: " .. file .. " not found") + end +end +RL.EndAssetLoading() +``` + +## 🎮 When to Use + +**Use the loading system when:** +- You have more than 5-10 assets to load +- Assets are large (images, sounds, fonts) +- Loading might take more than 1 second +- You want professional loading feedback + +**You can skip it when:** +- You have very few, small assets +- Loading is nearly instant +- You prefer immediate game start + +## ✨ Benefits + +- ✅ Professional user experience +- ✅ User knows the game is loading, not frozen +- ✅ Shows progress for large asset sets +- ✅ Works with embedded assets +- ✅ Minimal code required +- ✅ Beautiful default UI + +The loading system makes your game feel polished and professional with just a few lines of code! diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cfba5b..2df11ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,8 @@ option( LUAJIT "Use LuaJIT." off ) option( LUA_EVENTS "Enable Lua event callbacks (RL.event)." off ) option( DYNAMIC_SYMBOLS "Expose all dynamic symbols with rdynamic." off ) option( EXPOSE_API_SYMBOLS "Expose dynamic symbols only for get and push functions of variable types." off ) +option( EMBED_MAIN "Embed all Lua files from build directory into executable." off ) +option( EMBED_ASSETS "Embed all files from assets folder into executable." off ) enum_option( PLATFORM "Desktop;Desktop_SDL2;Desktop_SDL3;Web" "Platform to build for." ) @@ -25,7 +27,75 @@ endif() file( GLOB SOURCES src/*.c ) +# Always embed logo files for splash screens +set( LOGO_FILES + "${CMAKE_SOURCE_DIR}/logo/raylib_logo.png" + "${CMAKE_SOURCE_DIR}/logo/reilua_logo.png" +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/embedded_logo.h + COMMAND python ${CMAKE_SOURCE_DIR}/embed_logo.py + ${CMAKE_CURRENT_BINARY_DIR}/embedded_logo.h + ${CMAKE_SOURCE_DIR}/logo/raylib_logo.png + ${CMAKE_SOURCE_DIR}/logo/reilua_logo.png + DEPENDS ${LOGO_FILES} + COMMENT "Embedding logo files for splash screens..." +) +list( APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/embedded_logo.h ) +set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMBED_LOGO" ) + +# Always embed font file +set( FONT_FILE "${CMAKE_SOURCE_DIR}/fonts/Oleaguid.ttf" ) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/embedded_font.h + COMMAND python ${CMAKE_SOURCE_DIR}/embed_font.py + ${CMAKE_CURRENT_BINARY_DIR}/embedded_font.h + ${CMAKE_SOURCE_DIR}/fonts/Oleaguid.ttf + DEPENDS ${FONT_FILE} + COMMENT "Embedding font file..." +) +list( APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/embedded_font.h ) +set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMBED_FONT" ) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( include ) + +# Embed Lua files if EMBED_MAIN is ON +if( EMBED_MAIN ) + file( GLOB LUA_FILES "${CMAKE_CURRENT_BINARY_DIR}/*.lua" ) + if( LUA_FILES ) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/embedded_main.h + COMMAND python ${CMAKE_SOURCE_DIR}/embed_lua.py ${CMAKE_CURRENT_BINARY_DIR}/embedded_main.h ${LUA_FILES} + DEPENDS ${LUA_FILES} + COMMENT "Embedding Lua files into executable..." + ) + list( APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/embedded_main.h ) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMBED_MAIN" ) + else() + message( WARNING "EMBED_MAIN is ON but no .lua files found in build directory!" ) + endif() +endif() + +# Embed asset files if EMBED_ASSETS is ON +if( EMBED_ASSETS ) + file( GLOB_RECURSE ASSET_FILES "${CMAKE_CURRENT_BINARY_DIR}/assets/*" ) + if( ASSET_FILES ) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/embedded_assets.h + COMMAND python ${CMAKE_SOURCE_DIR}/embed_assets.py ${CMAKE_CURRENT_BINARY_DIR}/embedded_assets.h ${ASSET_FILES} + DEPENDS ${ASSET_FILES} + COMMENT "Embedding asset files into executable..." + ) + list( APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/embedded_assets.h ) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMBED_ASSETS" ) + else() + message( WARNING "EMBED_ASSETS is ON but no files found in build/assets/ directory!" ) + endif() +endif() + add_executable( ${PROJECT_NAME} ${SOURCES} ) if( PLATFORM STREQUAL "Desktop" ) diff --git a/EMBEDDING.md b/EMBEDDING.md new file mode 100644 index 0000000..c496059 --- /dev/null +++ b/EMBEDDING.md @@ -0,0 +1,290 @@ +# Embedding main.lua into Executable + +When you're ready to ship your game, you can embed all Lua files and asset files directly into the executable. + +## Development vs Release Workflow + +### 🔧 Development Build (Fast Iteration) + +During development, use external files for quick iteration: + +**Setup:** +``` +GameFolder/ +├── ReiLua.exe +├── main.lua +├── player.lua +└── assets/ + ├── player.png + └── music.wav +``` + +**Build:** +```bash +cd build +cmake .. +cmake --build . +``` + +**Benefits:** +- ✅ Edit Lua files and re-run immediately +- ✅ Edit assets and reload +- ✅ Fast development cycle +- ✅ Debug with `--log` flag + +### 📦 Release Build (Single Executable) + +For distribution, embed everything into one file: + +**Setup:** +```bash +cd build + +# Copy Lua files to build directory +copy ..\main.lua . +copy ..\player.lua . + +# Create assets folder and copy files +mkdir assets +copy ..\player.png assets\ +copy ..\music.wav assets\ +``` + +**Build:** +```bash +# Configure with embedding +cmake .. -DEMBED_MAIN=ON -DEMBED_ASSETS=ON + +# Build release +cmake --build . --config Release +``` + +**Result:** +``` +Distribution/ +└── YourGame.exe (Everything embedded!) +``` + +**Benefits:** +- ✅ Single executable file +- ✅ No external dependencies +- ✅ Users can't modify game files +- ✅ Professional distribution +- ✅ Smaller download (no separate files) + +## Quick Start + +### Embedding Lua Files + +1. **Copy your Lua files to the build directory**: + ```bash + copy main.lua build\main.lua + copy player.lua build\player.lua + ``` + +2. **Build with EMBED_MAIN option**: + ```bash + cd build + cmake .. -DEMBED_MAIN=ON + cmake --build . --config Release + ``` + +## Command Line Options + +ReiLua supports several command-line options: + +```bash +ReiLua [Options] [Directory to main.lua or main] + +Options: + -h, --help Show help message + -v, --version Show ReiLua version + -i, --interpret Interpret mode [File name] + --log Show console window for logging (Windows only) +``` + +### Console/Logging + +By default, ReiLua runs **without a console window** for a clean user experience. To enable console output for debugging: + +```bash +# Run with console for debugging +ReiLua.exe --log + +# You can also combine with other options +ReiLua.exe --log path/to/game + +# Or with interpret mode +ReiLua.exe --log -i script.lua +``` + +This is useful during development to see: +- TraceLog output +- Print statements +- Lua errors +- Debug information + +## Complete Release Workflow + +Here's a complete step-by-step guide to prepare your game for release: + +### Step 1: Organize Your Project + +Ensure your project has this structure: +``` +MyGame/ +├── main.lua +├── player.lua +├── enemy.lua +├── player.png +├── enemy.png +├── music.wav +└── icon.ico (optional) +``` + +### Step 2: Customize Branding (Optional) + +**Change executable icon:** +```bash +# Replace ReiLua's icon with yours +copy MyGame\icon.ico ReiLua\icon.ico +``` + +**Edit executable properties:** +Open `ReiLua\resources.rc` and modify: +```rc +VALUE "CompanyName", "Your Studio Name" +VALUE "FileDescription", "Your Game Description" +VALUE "ProductName", "Your Game Name" +VALUE "LegalCopyright", "Copyright (C) Your Name, 2025" +``` + +**Change executable name:** +Edit `ReiLua\CMakeLists.txt`: +```cmake +project( YourGameName ) # Change from "ReiLua" +``` + +See [CUSTOMIZATION.md](CUSTOMIZATION.md) for full details. + +### Important: Asset Paths + +**Keep your paths consistent!** The embedding system now preserves the `assets/` prefix, so use the same paths in both development and release: + +```lua +-- ✅ Correct - works in both dev and release +playerImage = RL.LoadTexture("assets/player.png") +backgroundImg = RL.LoadTexture("assets/background.png") +musicSound = RL.LoadSound("assets/music.wav") +``` + +Your Lua code doesn't need to change between development and release builds! + +### Step 3: Prepare Build Directory + +```bash +cd ReiLua\build + +# Copy all Lua files +copy ..\MyGame\*.lua . + +# Create assets folder +mkdir assets + +# Copy all asset files (images, sounds, etc.) +copy ..\MyGame\*.png assets\ +copy ..\MyGame\*.wav assets\ +copy ..\MyGame\*.ogg assets\ +# Or copy entire folders +xcopy /E /I ..\MyGame\images assets\images +xcopy /E /I ..\MyGame\sounds assets\sounds +``` + +### Step 4: Build Release + +```bash +# Configure with embedding enabled +cmake .. -DEMBED_MAIN=ON -DEMBED_ASSETS=ON + +# Build in release mode +cmake --build . --config Release +``` + +### Step 5: Test Release Build + +```bash +# Test with console to verify everything loaded +YourGameName.exe --log + +# Test production mode (no console) +YourGameName.exe +``` + +Check console output for: +- ✅ "ReiLua x.x.x" version info +- ✅ No file loading errors +- ✅ Game runs correctly + +### Step 6: Package for Distribution + +```bash +# Create distribution folder +mkdir ..\Distribution +copy YourGameName.exe ..\Distribution\ + +# Optional: Add README, LICENSE, etc. +copy ..\README.txt ..\Distribution\ +``` + +Your game is now ready to distribute as a single executable! + +### Workflow Summary + +| Stage | Build Command | Files Needed | Result | +|-------|--------------|--------------|--------| +| **Development** | `cmake .. && cmake --build .` | Lua + assets external | Fast iteration | +| **Testing** | `cmake .. -DEMBED_MAIN=ON && cmake --build .` | Lua in build/ | Test embedding | +| **Release** | `cmake .. -DEMBED_MAIN=ON -DEMBED_ASSETS=ON && cmake --build . --config Release` | Lua + assets in build/ | Single .exe | + +### Troubleshooting + +**Problem: "No .lua files found in build directory"** +```bash +# Solution: Copy Lua files to build directory +copy ..\*.lua . +``` + +**Problem: "No files found in assets folder"** +```bash +# Solution: Create assets folder and copy files +mkdir assets +copy ..\*.png assets\ +``` + +**Problem: Game crashes on startup** +```bash +# Solution: Run with --log to see error messages +YourGameName.exe --log +``` + +**Problem: Assets not loading** +- Verify assets are in `build/assets/` before building +- Check asset filenames match in your Lua code +- Use `--log` to see loading errors + +### Notes + +- `.lua` files in the **build directory root** are embedded when using `EMBED_MAIN=ON` +- Asset files in **build/assets/** folder (and subfolders) are embedded when using `EMBED_ASSETS=ON` +- `main.lua` must exist and is always the entry point +- Asset embedding works with subdirectories: `assets/images/player.png` → Load with `LoadImage("player.png")` +- Both Lua and asset embedding can be used independently or together +- The system falls back to file system if embedded file is not found +- No code changes needed - all raylib functions work automatically with embedded assets + +## Customizing Your Executable + +Want to add your own icon and version info to the executable? See [CUSTOMIZATION.md](CUSTOMIZATION.md) for details on: +- Adding a custom icon +- Setting exe properties (company name, version, description) +- Renaming the executable diff --git a/SPLASH_SCREENS.md b/SPLASH_SCREENS.md new file mode 100644 index 0000000..8fb9b51 --- /dev/null +++ b/SPLASH_SCREENS.md @@ -0,0 +1,230 @@ +# Splash Screens + +ReiLua includes a built-in splash screen system that displays three professional splash screens before your game loads. This gives your game a polished, professional appearance right from startup. + +## Overview + +When you run your ReiLua game, it automatically shows two splash screens in sequence: + +1. **"INDRAJITH MAKES GAMES"** - Clean, bold text on Raylib red background (similar to Squid Game style) +2. **"Made using"** - Text with Raylib and ReiLua logos displayed side-by-side + +Each splash screen: +- Fades in over 0.8 seconds +- Displays for 2.5 seconds +- Fades out over 0.8 seconds +- Total display time: 8.2 seconds for both screens + +## Features + +### Always Embedded + +The logo images are **always embedded** into the executable in both development and release builds. This means: + +- ✅ No external logo files needed +- ✅ Consistent splash screens across all builds +- ✅ No risk of missing logo files +- ✅ Professional appearance from the start + +### Asset Loading Integration + +The splash screens display **before** your game's asset loading begins. This means: + +1. User starts your game +2. Splash screens play (~8 seconds) +3. Your `RL.init()` function runs +4. Asset loading with progress indicator (if you use it) +5. Your game starts + +This creates a smooth, professional startup experience. + +## Skipping Splash Screens (Development) + +During development, you often need to test your game repeatedly. Waiting for splash screens every time can slow down your workflow. Use the `--no-logo` flag to skip them: + +```bash +# Windows +ReiLua.exe --no-logo + +# Linux/Mac +./ReiLua --no-logo + +# With other options +ReiLua.exe --log --no-logo +./ReiLua --no-logo path/to/game/ +``` + +**Note:** The `--no-logo` flag only works in development. In release builds, users should see the full splash screen sequence. + +## Technical Details + +### How It Works + +The splash screen system is implemented in C and runs before any Lua code executes: + +1. **Logo Embedding**: During build, `embed_logo.py` converts PNG files to C byte arrays +2. **Initialization**: Before calling `RL.init()`, the engine initializes splash screens +3. **Display Loop**: A dedicated loop handles timing, fading, and rendering +4. **Cleanup**: After completion, resources are freed and Lua code begins + +### Files + +- `src/splash.c` - Splash screen implementation +- `include/splash.h` - Header file +- `embed_logo.py` - Python script to embed logo images +- `logo/raylib_logo.png` - Raylib logo (embedded) +- `logo/reilua_logo.png` - ReiLua logo (embedded) + +### Build Integration + +The CMakeLists.txt automatically: + +1. Runs `embed_logo.py` during build +2. Generates `embedded_logo.h` with logo data +3. Defines `EMBED_LOGO` flag +4. Compiles `splash.c` with the project + +No manual steps required - it just works! + +## Customization + +### Changing Splash Screen Text + +To change "INDRAJITH MAKES GAMES" to your studio name: + +1. Open `src/splash.c` +2. Find the `drawIndrajithSplash()` function +3. Change this line: + ```c + const char* text = "INDRAJITH MAKES GAMES"; + ``` +4. Rebuild the project + +**Note:** Use ALL CAPS for the Squid Game-style aesthetic. + +### Changing Logos + +To use different logos: + +1. Replace `logo/raylib_logo.png` and/or `logo/reilua_logo.png` with your images +2. Recommended size: 256x256 or smaller (logos are auto-scaled to max 200px) +3. Format: PNG with transparency support +4. Rebuild the project - logos will be automatically embedded + +### Changing Timing + +To adjust how long each screen displays: + +1. Open `src/splash.c` +2. Modify these constants at the top: + ```c + #define FADE_IN_TIME 0.8f // Seconds to fade in + #define DISPLAY_TIME 2.5f // Seconds to display fully + #define FADE_OUT_TIME 0.8f // Seconds to fade out + ``` +3. Rebuild the project + +### Removing Splash Screens Entirely + +If you don't want any splash screens: + +1. Open `src/main.c` +2. Find this block: + ```c + /* Show splash screens if not skipped */ + if ( !skip_splash ) { + splashInit(); + // ... splash code ... + splashCleanup(); + } + ``` +3. Comment out or remove the entire block +4. Rebuild the project + +## Example: Complete Startup Sequence + +Here's what a typical game startup looks like with everything enabled: + +```bash +ReiLua.exe MyGame/ +``` + +**User Experience:** + +1. **Splash Screen 1** (4.1 seconds) + - "INDRAJITH MAKES GAMES" bold text + - Red background (Raylib color #E62937) + - Subtle zoom effect + +2. **Splash Screen 2** (4.1 seconds) + - "Made using" text at top + - Raylib + ReiLua logos side-by-side (max 200px each) + - Black background + +3. **Asset Loading** (varies) + - Your loading screen with progress bar + - Shows "Loading texture1.png", "3/10", etc. + +4. **Game Start** + - Your game's main screen appears + - Player can interact + +## Best Practices + +1. **Keep --no-logo for Development**: Always use `--no-logo` during active development +2. **Test Without Flag**: Occasionally test without `--no-logo` to ensure splash screens work +3. **Customize for Your Studio**: Change the text and logos to match your branding +4. **Consider Total Time**: Splash (~8s) + Loading (varies) = Total startup time +5. **Optimize Loading**: Keep asset loading fast to maintain a good first impression + +## Troubleshooting + +### Splash Screens Don't Show + +**Problem**: Game starts immediately without splash screens + +**Solutions**: +- Check you're not using `--no-logo` flag +- Verify logos exist in `logo/` folder before building +- Check console output for embedding errors +- Rebuild project completely: `cmake .. && make clean && make` + +### Logos Appear Corrupted + +**Problem**: Logos display incorrectly or not at all + +**Solutions**: +- Verify PNG files are valid (open in image viewer) +- Check file sizes aren't too large (keep under 1MB each) +- Ensure PNGs use standard format (not progressive or exotic encoding) +- Rebuild project to regenerate embedded data + +### Compilation Errors + +**Problem**: Build fails with logo-related errors + +**Solutions**: +- Ensure Python 3 is installed and in PATH +- Check `embed_logo.py` has correct paths +- Verify `logo/` folder exists with both PNG files +- Check CMake output for specific error messages + +## Command Reference + +```bash +# Development (skip splash) +ReiLua --no-logo + +# Development with logging +ReiLua --log --no-logo + +# Production/testing (full splash) +ReiLua + +# Help +ReiLua --help +``` + +--- + +The splash screen system adds a professional touch to your ReiLua games with minimal effort. Customize it to match your studio's branding and give players a great first impression! diff --git a/build/.gitignore b/build/.gitignore deleted file mode 100644 index f59ec20..0000000 --- a/build/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/embed_assets.py b/embed_assets.py new file mode 100644 index 0000000..52309ef --- /dev/null +++ b/embed_assets.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Embed asset files (images, sounds, fonts, etc.) into a C header file. +Usage: python embed_assets.py [file2.wav] [file3.ttf] ... + +Embeds all specified asset files into a C header for inclusion in the executable. +""" +import sys +import os + +def sanitize_name(filename): + """Convert filename to valid C identifier""" + name = os.path.basename(filename) + # Remove or replace all non-alphanumeric characters (except underscore) + valid_chars = [] + for char in name: + if char.isalnum() or char == '_': + valid_chars.append(char) + else: + valid_chars.append('_') + name = ''.join(valid_chars) + # Ensure it doesn't start with a digit + if name and name[0].isdigit(): + name = '_' + name + return name + +def get_file_extension(filename): + """Get the file extension""" + return os.path.splitext(filename)[1].lower() + +def embed_files(output_file, input_files): + with open(output_file, 'w') as f: + f.write('#ifndef EMBEDDED_ASSETS_H\n') + f.write('#define EMBEDDED_ASSETS_H\n\n') + f.write('/* Auto-generated file - do not edit manually */\n\n') + + # Embed each file as a separate array + for idx, input_file in enumerate(input_files): + with open(input_file, 'rb') as inf: + data = inf.read() + + var_name = sanitize_name(input_file) + # Extract relative path from 'assets/' onwards if present + if 'assets' in input_file.replace('\\', '/'): + parts = input_file.replace('\\', '/').split('assets/') + if len(parts) > 1: + relative_name = 'assets/' + parts[-1] + else: + relative_name = os.path.basename(input_file) + else: + relative_name = os.path.basename(input_file) + + f.write(f'/* Embedded asset: {input_file} ({len(data)} bytes) */\n') + f.write(f'static const unsigned char embedded_asset_{idx}_{var_name}[] = {{\n') + + for i, byte in enumerate(data): + if i % 12 == 0: + f.write(' ') + f.write(f'0x{byte:02x}') + if i < len(data) - 1: + f.write(',') + if (i + 1) % 12 == 0: + f.write('\n') + else: + f.write(' ') + + f.write('\n};\n') + f.write(f'static const unsigned int embedded_asset_{idx}_{var_name}_len = {len(data)};\n\n') + + # Create the asset table + f.write('/* Asset table for virtual filesystem */\n') + f.write('typedef struct {\n') + f.write(' const char* name;\n') + f.write(' const unsigned char* data;\n') + f.write(' unsigned int size;\n') + f.write('} EmbeddedAsset;\n\n') + + f.write('static const EmbeddedAsset embedded_assets[] = {\n') + for idx, input_file in enumerate(input_files): + var_name = sanitize_name(input_file) + # Extract relative path from 'assets/' onwards if present + if 'assets' in input_file.replace('\\', '/'): + parts = input_file.replace('\\', '/').split('assets/') + if len(parts) > 1: + relative_name = 'assets/' + parts[-1] + else: + relative_name = os.path.basename(input_file) + else: + relative_name = os.path.basename(input_file) + f.write(f' {{ "{relative_name}", embedded_asset_{idx}_{var_name}, embedded_asset_{idx}_{var_name}_len }},\n') + f.write('};\n\n') + + f.write(f'static const int embedded_asset_count = {len(input_files)};\n\n') + f.write('#endif /* EMBEDDED_ASSETS_H */\n') + +if __name__ == '__main__': + if len(sys.argv) < 3: + print('Usage: python embed_assets.py [asset2] ...') + print(' Embeds images, sounds, fonts, and other asset files into a C header.') + print(' Supported: .png, .jpg, .wav, .ogg, .mp3, .ttf, .otf, etc.') + sys.exit(1) + + output_file = sys.argv[1] + input_files = sys.argv[2:] + + # Check all input files exist + for f in input_files: + if not os.path.exists(f): + print(f'Error: File not found: {f}') + sys.exit(1) + + embed_files(output_file, input_files) + print(f'Embedded {len(input_files)} asset file(s) into {output_file}') + for f in input_files: + size = os.path.getsize(f) + print(f' - {f} ({size} bytes)') diff --git a/embed_font.py b/embed_font.py new file mode 100644 index 0000000..8144718 --- /dev/null +++ b/embed_font.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +""" +Embed font file into C header. +Usage: python embed_font.py +""" + +import sys +import os + +def embed_file(file_path, var_name): + """Convert a file to a C byte array""" + with open(file_path, 'rb') as f: + data = f.read() + + output = f"/* {os.path.basename(file_path)} */\n" + output += f"static const unsigned char {var_name}[] = {{\n" + + # Write bytes in rows of 16 + for i in range(0, len(data), 16): + chunk = data[i:i+16] + hex_values = ', '.join(f'0x{b:02x}' for b in chunk) + output += f" {hex_values},\n" + + output += "};\n" + output += f"static const unsigned int {var_name}_size = {len(data)};\n\n" + + return output + +def main(): + if len(sys.argv) != 3: + print("Usage: python embed_font.py ") + sys.exit(1) + + output_file = sys.argv[1] + font_file = sys.argv[2] + + # Check if file exists + if not os.path.exists(font_file): + print(f"Error: {font_file} not found!") + sys.exit(1) + + # Generate header content + header_content = "/* Auto-generated embedded font file */\n" + header_content += "#pragma once\n\n" + + # Embed font file + header_content += embed_file(font_file, "embedded_font_data") + + # Write to output file + with open(output_file, 'w') as f: + f.write(header_content) + + print(f"Generated {output_file}") + print(f" - Embedded {font_file} ({os.path.getsize(font_file)} bytes)") + +if __name__ == "__main__": + main() diff --git a/embed_logo.py b/embed_logo.py new file mode 100644 index 0000000..e6b645e --- /dev/null +++ b/embed_logo.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +Embed logo image files into C header for splash screens. +Usage: python embed_logo.py +""" + +import sys +import os + +def embed_file(file_path, var_name): + """Convert a file to a C byte array""" + with open(file_path, 'rb') as f: + data = f.read() + + output = f"/* {os.path.basename(file_path)} */\n" + output += f"static const unsigned char {var_name}[] = {{\n" + + # Write bytes in rows of 16 + for i in range(0, len(data), 16): + chunk = data[i:i+16] + hex_values = ', '.join(f'0x{b:02x}' for b in chunk) + output += f" {hex_values},\n" + + output += "};\n" + output += f"static const unsigned int {var_name}_size = {len(data)};\n\n" + + return output + +def main(): + if len(sys.argv) != 4: + print("Usage: python embed_logo.py ") + sys.exit(1) + + output_file = sys.argv[1] + raylib_logo = sys.argv[2] + reilua_logo = sys.argv[3] + + # Check if files exist + if not os.path.exists(raylib_logo): + print(f"Error: {raylib_logo} not found!") + sys.exit(1) + + if not os.path.exists(reilua_logo): + print(f"Error: {reilua_logo} not found!") + sys.exit(1) + + # Generate header content + header_content = "/* Auto-generated embedded logo files */\n" + header_content += "#pragma once\n\n" + + # Embed both logo files + header_content += embed_file(raylib_logo, "embedded_raylib_logo") + header_content += embed_file(reilua_logo, "embedded_reilua_logo") + + # Write to output file + with open(output_file, 'w') as f: + f.write(header_content) + + print(f"Generated {output_file}") + print(f" - Embedded {raylib_logo} ({os.path.getsize(raylib_logo)} bytes)") + print(f" - Embedded {reilua_logo} ({os.path.getsize(reilua_logo)} bytes)") + +if __name__ == "__main__": + main() diff --git a/embed_lua.py b/embed_lua.py new file mode 100644 index 0000000..9562354 --- /dev/null +++ b/embed_lua.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +Embed multiple Lua files into a C header file for inclusion in the executable. +Usage: python embed_lua.py [file2.lua] [file3.lua] ... + +Embeds all specified Lua files into a C header with a virtual filesystem. +The first file is treated as main.lua (entry point). +""" +import sys +import os + +def sanitize_name(filename): + """Convert filename to valid C identifier""" + name = os.path.basename(filename) + name = name.replace('.', '_').replace('-', '_').replace('/', '_').replace('\\', '_') + return name + +def embed_files(output_file, input_files): + with open(output_file, 'w') as f: + f.write('#ifndef EMBEDDED_MAIN_H\n') + f.write('#define EMBEDDED_MAIN_H\n\n') + f.write('/* Auto-generated file - do not edit manually */\n\n') + + # Embed each file as a separate array + for idx, input_file in enumerate(input_files): + with open(input_file, 'rb') as inf: + data = inf.read() + + var_name = sanitize_name(input_file) + f.write(f'/* Embedded file: {input_file} */\n') + f.write(f'static const unsigned char embedded_lua_{idx}_{var_name}[] = {{\n') + + for i, byte in enumerate(data): + if i % 12 == 0: + f.write(' ') + f.write(f'0x{byte:02x}') + if i < len(data) - 1: + f.write(',') + if (i + 1) % 12 == 0: + f.write('\n') + else: + f.write(' ') + + f.write('\n};\n') + f.write(f'static const unsigned int embedded_lua_{idx}_{var_name}_len = {len(data)};\n\n') + + # Create the file table + f.write('/* File table for virtual filesystem */\n') + f.write('typedef struct {\n') + f.write(' const char* name;\n') + f.write(' const unsigned char* data;\n') + f.write(' unsigned int size;\n') + f.write('} EmbeddedLuaFile;\n\n') + + f.write('static const EmbeddedLuaFile embedded_lua_files[] = {\n') + for idx, input_file in enumerate(input_files): + var_name = sanitize_name(input_file) + # Store both original filename and basename for require compatibility + basename = os.path.basename(input_file) + f.write(f' {{ "{basename}", embedded_lua_{idx}_{var_name}, embedded_lua_{idx}_{var_name}_len }},\n') + f.write('};\n\n') + + f.write(f'static const int embedded_lua_file_count = {len(input_files)};\n\n') + + # Main entry point (first file) + var_name = sanitize_name(input_files[0]) + f.write('/* Main entry point */\n') + f.write(f'#define embedded_main_lua embedded_lua_0_{var_name}\n') + f.write(f'#define embedded_main_lua_len embedded_lua_0_{var_name}_len\n\n') + + f.write('#endif /* EMBEDDED_MAIN_H */\n') + +if __name__ == '__main__': + if len(sys.argv) < 3: + print('Usage: python embed_lua.py [file2.lua] ...') + print(' The first Lua file is treated as the main entry point.') + sys.exit(1) + + output_file = sys.argv[1] + input_files = sys.argv[2:] + + # Check all input files exist + for f in input_files: + if not os.path.exists(f): + print(f'Error: File not found: {f}') + sys.exit(1) + + embed_files(output_file, input_files) + print(f'Embedded {len(input_files)} file(s) into {output_file}') + for f in input_files: + print(f' - {f}') + diff --git a/fonts/Oleaguid.ttf b/fonts/Oleaguid.ttf new file mode 100644 index 0000000000000000000000000000000000000000..034af36d4e1c51d180a3c01ba3f791943d0b6a75 GIT binary patch literal 112828 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 ) { case BUFFER_UNSIGNED_CHAR: return sizeof( unsigned char ); @@ -1955,6 +1963,59 @@ int lcoreGetApplicationDirectory( lua_State* L ) { return 1; } +/* +> RL.BeginAssetLoading( int totalAssets ) + +Initialize asset loading progress tracking + +- totalAssets: Total number of assets to load +*/ +int lcoreBeginAssetLoading( lua_State* L ) { + g_totalAssets = luaL_checkinteger( L, 1 ); + g_loadedAssets = 0; + g_showLoadingScreen = true; + g_loadingProgress = 0.0f; + g_currentAssetName[0] = '\0'; + + return 0; +} + +/* +> RL.UpdateAssetLoading( string assetName ) + +Update loading progress for current asset + +- assetName: Name of the asset currently being loaded +*/ +int lcoreUpdateAssetLoading( lua_State* L ) { + const char* assetName = luaL_checkstring( L, 1 ); + strncpy( g_currentAssetName, assetName, sizeof(g_currentAssetName) - 1 ); + g_currentAssetName[sizeof(g_currentAssetName) - 1] = '\0'; + + g_loadedAssets++; + g_loadingProgress = (float)g_loadedAssets / (float)g_totalAssets; + + if ( g_showLoadingScreen ) { + drawLoadingScreen(); + } + + return 0; +} + +/* +> RL.EndAssetLoading() + +Finish asset loading and hide loading screen +*/ +int lcoreEndAssetLoading( lua_State* L ) { + g_showLoadingScreen = false; + g_totalAssets = 0; + g_loadedAssets = 0; + g_currentAssetName[0] = '\0'; + + return 0; +} + /* > success = RL.MakeDirectory( string dirPath ) diff --git a/src/lua_core.c b/src/lua_core.c index c619b9c..a6c2ef7 100644 --- a/src/lua_core.c +++ b/src/lua_core.c @@ -15,6 +15,21 @@ #include "reasings.h" #include "bitwiseOp.h" +#ifdef EMBED_MAIN + #include "embedded_main.h" +#endif + +#ifdef EMBED_ASSETS + #include "embedded_assets.h" +#endif + +/* Asset loading progress tracking (non-static so core.c can access) */ +int g_totalAssets = 0; +int g_loadedAssets = 0; +char g_currentAssetName[256] = { '\0' }; +bool g_showLoadingScreen = false; +float g_loadingProgress = 0.0f; + #ifdef PLATFORM_DESKTOP #include "platforms/core_desktop_glfw.c" #elif PLATFORM_DESKTOP_SDL2 @@ -25,6 +40,152 @@ #include "platforms/core_web.c" #endif +/* Draw a nice loading screen with progress bar (non-static so core.c can call it) */ +void drawLoadingScreen() { + int screenWidth = GetScreenWidth(); + int screenHeight = GetScreenHeight(); + + BeginDrawing(); + ClearBackground( BLACK ); + + int centerX = screenWidth / 2; + int centerY = screenHeight / 2; + + const char* title = "LOADING"; + int titleSize = 32; + int titleWidth = MeasureText( title, titleSize ); + + DrawText( title, centerX - titleWidth / 2, centerY - 80, titleSize, WHITE ); + + static float dotTime = 0.0f; + dotTime += 0.016f; + int dotCount = (int)(dotTime * 2.0f) % 4; + + int dotStartX = centerX + titleWidth / 2 + 10; + int dotY = centerY - 80 + titleSize - 12; + for ( int i = 0; i < dotCount; i++ ) { + DrawRectangle( dotStartX + i * 8, dotY, 4, 4, WHITE ); + } + + int barWidth = 200; + int barHeight = 16; + int barX = centerX - barWidth / 2; + int barY = centerY; + + DrawRectangle( barX - 2, barY - 2, barWidth + 4, barHeight + 4, WHITE ); + DrawRectangle( barX, barY, barWidth, barHeight, BLACK ); + + int fillWidth = (int)(barWidth * g_loadingProgress); + if ( fillWidth > 0 ) { + DrawRectangle( barX, barY, fillWidth, barHeight, WHITE ); + + for ( int y = 0; y < barHeight; y += 2 ) { + for ( int x = 0; x < fillWidth; x += 4 ) { + if ( (x + y) % 4 == 0 ) { + DrawRectangle( barX + x, barY + y, 1, 1, BLACK ); + } + } + } + } + + if ( g_totalAssets > 0 ) { + char progressText[32]; + sprintf( progressText, "%d/%d", g_loadedAssets, g_totalAssets ); + int progressWidth = MeasureText( progressText, 16 ); + DrawText( progressText, centerX - progressWidth / 2, barY + barHeight + 12, 16, WHITE ); + } + + if ( g_currentAssetName[0] != '\0' ) { + int assetNameWidth = MeasureText( g_currentAssetName, 10 ); + DrawText( g_currentAssetName, centerX - assetNameWidth / 2, barY + barHeight + 36, 10, WHITE ); + } + + int cornerSize = 8; + int margin = 40; + + DrawRectangle( margin, margin, cornerSize, 2, WHITE ); + DrawRectangle( margin, margin, 2, cornerSize, WHITE ); + + DrawRectangle( screenWidth - margin - cornerSize, margin, cornerSize, 2, WHITE ); + DrawRectangle( screenWidth - margin - 2, margin, 2, cornerSize, WHITE ); + + DrawRectangle( margin, screenHeight - margin - cornerSize, 2, cornerSize, WHITE ); + DrawRectangle( margin, screenHeight - margin - 2, cornerSize, 2, WHITE ); + + DrawRectangle( screenWidth - margin - cornerSize, screenHeight - margin - 2, cornerSize, 2, WHITE ); + DrawRectangle( screenWidth - margin - 2, screenHeight - margin - cornerSize, 2, cornerSize, WHITE ); + + EndDrawing(); +} + +#ifdef EMBED_MAIN +/* Custom loader for embedded Lua files */ +static int embedded_lua_loader( lua_State* L ) { + const char* name = lua_tostring( L, 1 ); + if ( name == NULL ) return 0; + + for ( int i = 0; i < embedded_lua_file_count; i++ ) { + const EmbeddedLuaFile* file = &embedded_lua_files[i]; + + const char* basename = file->name; + size_t name_len = strlen( name ); + size_t base_len = strlen( basename ); + + if ( strcmp( basename, name ) == 0 || + ( base_len > 4 && strcmp( basename + base_len - 4, ".lua" ) == 0 && + strncmp( basename, name, base_len - 4 ) == 0 && name_len == base_len - 4 ) ) { + + if ( luaL_loadbuffer( L, (const char*)file->data, file->size, file->name ) == 0 ) { + return 1; + } + else { + lua_pushfstring( L, "\n\tembedded loader error: %s", lua_tostring( L, -1 ) ); + return 1; + } + } + } + + lua_pushfstring( L, "\n\tno embedded file '%s'", name ); + return 1; +} +#endif + +#ifdef EMBED_ASSETS +/* Helper function to find embedded asset by name */ +static const EmbeddedAsset* find_embedded_asset( const char* name ) { + if ( name == NULL ) return NULL; + + for ( int i = 0; i < embedded_asset_count; i++ ) { + if ( strcmp( embedded_assets[i].name, name ) == 0 ) { + return &embedded_assets[i]; + } + } + return NULL; +} + +/* Override LoadFileData to check embedded assets first */ +unsigned char* LoadFileData_Embedded( const char* fileName, int* dataSize ) { + const EmbeddedAsset* asset = find_embedded_asset( fileName ); + if ( asset != NULL ) { + *dataSize = asset->size; + unsigned char* data = (unsigned char*)malloc( asset->size ); + if ( data != NULL ) { + memcpy( data, asset->data, asset->size ); + } + return data; + } + return LoadFileData( fileName, dataSize ); +} + +/* Check if file exists in embedded assets */ +bool FileExists_Embedded( const char* fileName ) { + if ( find_embedded_asset( fileName ) != NULL ) { + return true; + } + return FileExists( fileName ); +} +#endif + /* Custom implementation since LuaJIT doesn't have lua_geti. */ static void lua_getiCustom( lua_State* L, int index, int i ) { lua_pushinteger( L, i ); // Push the index onto the stack @@ -1446,11 +1607,51 @@ int luaTraceback( lua_State* L ) { return 1; } -void luaCallMain() { +bool luaCallMain() { lua_State* L = state->luaState; char path[ STRING_LEN ] = { '\0' }; + /* Show loading screen */ + BeginDrawing(); + ClearBackground( RAYWHITE ); + const char* loadingText = "Loading..."; + int fontSize = 40; + int textWidth = MeasureText( loadingText, fontSize ); + DrawText( loadingText, ( GetScreenWidth() - textWidth ) / 2, GetScreenHeight() / 2 - fontSize / 2, fontSize, DARKGRAY ); + EndDrawing(); + +#ifdef EMBED_MAIN + /* Register custom loader for embedded files */ + lua_getglobal( L, "package" ); + lua_getfield( L, -1, "loaders" ); + if ( lua_isnil( L, -1 ) ) { + lua_pop( L, 1 ); + lua_getfield( L, -1, "searchers" ); /* Lua 5.2+ uses 'searchers' */ + } + + /* Insert our loader at position 2 (before file loaders) */ + lua_len( L, -1 ); + int num_loaders = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + for ( int i = num_loaders; i >= 2; i-- ) { + lua_rawgeti( L, -1, i ); + lua_rawseti( L, -2, i + 1 ); + } + lua_pushcfunction( L, embedded_lua_loader ); + lua_rawseti( L, -2, 2 ); + lua_pop( L, 2 ); /* Pop loaders/searchers and package */ + + /* Load from embedded data */ + if ( luaL_loadbuffer( L, (const char*)embedded_main_lua, embedded_main_lua_len, "main.lua" ) != 0 ) { + TraceLog( LOG_ERROR, "Lua error loading embedded main.lua: %s\n", lua_tostring( L, -1 ) ); + return false; + } + if ( lua_pcall( L, 0, 0, 0 ) != 0 ) { + TraceLog( LOG_ERROR, "Lua error executing embedded main.lua: %s\n", lua_tostring( L, -1 ) ); + return false; + } +#else /* If web, set path to resources folder. */ #ifdef PLATFORM_WEB snprintf( path, STRING_LEN, "main.lua" ); @@ -1467,17 +1668,16 @@ void luaCallMain() { #endif if ( !FileExists( path ) ) { TraceLog( LOG_ERROR, "Cannot find file: %s\n", path ); - state->run = false; - return; + return false; } luaL_dofile( L, path ); /* Check errors in main.lua */ if ( lua_tostring( L, -1 ) ) { TraceLog( LOG_ERROR, "Lua error: %s\n", lua_tostring( L, -1 ) ); - state->run = false; - return; + return false; } +#endif lua_pushcfunction( L, luaTraceback ); int tracebackidx = lua_gettop( L ); /* Apply custom callback here. */ @@ -1489,8 +1689,7 @@ void luaCallMain() { if ( lua_isfunction( L, -1 ) ) { if ( lua_pcall( L, 0, 0, tracebackidx ) != 0 ) { TraceLog( LOG_ERROR, "Lua error: %s", lua_tostring( L, -1 ) ); - state->run = false; - return; + return false; } } lua_pop( L, -1 ); @@ -1502,8 +1701,25 @@ void luaCallMain() { stateContextInit(); } else { - state->run = false; + return false; } + + lua_getglobal( L, "RL" ); + lua_getfield( L, -1, "init" ); + + if ( lua_isfunction( L, -1 ) ) { + if ( lua_pcall( L, 0, 0, tracebackidx ) != 0 ) { + TraceLog( LOG_ERROR, "Lua error: %s", lua_tostring( L, -1 ) ); + return false; + } + } + else { + TraceLog( LOG_ERROR, "%s", "No Lua init found!" ); + return false; + } + lua_pop( L, -1 ); + + return state->run; } void luaCallInit() { @@ -1789,6 +2005,10 @@ void luaRegister() { assingGlobalFunction( "GetPrevDirectoryPath", lcoreGetPrevDirectoryPath ); assingGlobalFunction( "GetWorkingDirectory", lcoreGetWorkingDirectory ); assingGlobalFunction( "GetApplicationDirectory", lcoreGetApplicationDirectory ); + /* Asset loading functions. */ + assingGlobalFunction( "BeginAssetLoading", lcoreBeginAssetLoading ); + assingGlobalFunction( "UpdateAssetLoading", lcoreUpdateAssetLoading ); + assingGlobalFunction( "EndAssetLoading", lcoreEndAssetLoading ); assingGlobalFunction( "MakeDirectory", lcoreMakeDirectory ); assingGlobalFunction( "ChangeDirectory", lcoreChangeDirectory ); assingGlobalFunction( "IsPathFile", lcoreIsPathFile ); diff --git a/src/main.c b/src/main.c index f35d2bf..d4d8fd2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,15 @@ #include "main.h" #include "state.h" #include "lua_core.h" +#include "splash.h" + +#ifdef _WIN32 +// Forward declarations for Windows console functions +extern __declspec(dllimport) int __stdcall AllocConsole(void); +extern __declspec(dllimport) int __stdcall FreeConsole(void); +extern __declspec(dllimport) void* __stdcall GetStdHandle(unsigned long nStdHandle); +extern __declspec(dllimport) int __stdcall SetStdHandle(unsigned long nStdHandle, void* hHandle); +#endif static inline void printVersion() { if ( VERSION_DEV ) { @@ -22,30 +31,92 @@ static inline void printVersion() { int main( int argn, const char** argc ) { char basePath[ STRING_LEN ] = { '\0' }; bool interpret_mode = false; + bool show_console = false; + bool skip_splash = false; + +#ifdef _WIN32 + /* Check for --log and --no-logo arguments */ + for ( int i = 1; i < argn; i++ ) { + if ( strcmp( argc[i], "--log" ) == 0 ) { + show_console = true; + } + if ( strcmp( argc[i], "--no-logo" ) == 0 ) { + skip_splash = true; + } + } + + /* Show or hide console based on --log argument */ + if ( show_console ) { + /* Allocate a console if we don't have one */ + if ( AllocConsole() ) { + freopen( "CONOUT$", "w", stdout ); + freopen( "CONOUT$", "w", stderr ); + freopen( "CONIN$", "r", stdin ); + } + } + else { + /* Hide console window */ + FreeConsole(); + } +#else + /* Check for --no-logo on non-Windows platforms */ + for ( int i = 1; i < argn; i++ ) { + if ( strcmp( argc[i], "--no-logo" ) == 0 ) { + skip_splash = true; + break; + } + } +#endif if ( 1 < argn ) { - if ( strcmp( argc[1], "--version" ) == 0 || strcmp( argc[1], "-v" ) == 0 ) { + /* Skip --log and --no-logo flags to find the actual command */ + int arg_index = 1; + while ( arg_index < argn && ( strcmp( argc[arg_index], "--log" ) == 0 || strcmp( argc[arg_index], "--no-logo" ) == 0 ) ) { + arg_index++; + } + + if ( arg_index < argn && ( strcmp( argc[arg_index], "--version" ) == 0 || strcmp( argc[arg_index], "-v" ) == 0 ) ) { printVersion(); return 1; } - else if ( strcmp( argc[1], "--help" ) == 0 || strcmp( argc[1], "-h" ) == 0 ) { - printf( "Usage: ReiLua [Options] [Directory to main.lua or main]\nOptions:\n-h --help\tThis help\n-v --version\tShow ReiLua version\n-i --interpret\tInterpret mode [File name]\n" ); + else if ( arg_index < argn && ( strcmp( argc[arg_index], "--help" ) == 0 || strcmp( argc[arg_index], "-h" ) == 0 ) ) { + printf( "Usage: ReiLua [Options] [Directory to main.lua or main]\nOptions:\n-h --help\tThis help\n-v --version\tShow ReiLua version\n-i --interpret\tInterpret mode [File name]\n--log\t\tShow console for logging\n--no-logo\tSkip splash screens (development)\n" ); return 1; } - else if ( strcmp( argc[1], "--interpret" ) == 0 || strcmp( argc[1], "-i" ) == 0 ) { + else if ( arg_index < argn && ( strcmp( argc[arg_index], "--interpret" ) == 0 || strcmp( argc[arg_index], "-i" ) == 0 ) ) { interpret_mode = true; - if ( 2 < argn ) { - sprintf( basePath, "%s/%s", GetWorkingDirectory(), argc[2] ); + if ( arg_index + 1 < argn ) { + sprintf( basePath, "%s/%s", GetWorkingDirectory(), argc[arg_index + 1] ); } } - else{ - sprintf( basePath, "%s/%s", GetWorkingDirectory(), argc[1] ); + else if ( arg_index < argn ) { + sprintf( basePath, "%s/%s", GetWorkingDirectory(), argc[arg_index] ); + } + else { + /* Only flags were provided, use default path search */ + char testPath[ STRING_LEN ] = { '\0' }; + sprintf( testPath, "%s/main.lua", GetWorkingDirectory() ); + + if ( FileExists( testPath ) ) { + sprintf( basePath, "%s", GetWorkingDirectory() ); + } + else { + sprintf( basePath, "%s", GetApplicationDirectory() ); + } } } - /* If no argument given, assume main.lua is in exe directory. */ + /* If no argument given, check current directory first, then exe directory. */ else { - sprintf( basePath, "%s", GetApplicationDirectory() ); + char testPath[ STRING_LEN ] = { '\0' }; + sprintf( testPath, "%s/main.lua", GetWorkingDirectory() ); + + if ( FileExists( testPath ) ) { + sprintf( basePath, "%s", GetWorkingDirectory() ); + } + else { + sprintf( basePath, "%s", GetApplicationDirectory() ); + } } if ( interpret_mode ) { @@ -65,15 +136,30 @@ int main( int argn, const char** argc ) { else { printVersion(); stateInit( argn, argc, basePath ); - luaCallMain(); - luaCallInit(); + + /* Show splash screens if not skipped */ + if ( !skip_splash ) { + splashInit(); + bool splashDone = false; + + while ( !splashDone && !WindowShouldClose() ) { + float delta = GetFrameTime(); + splashDone = splashUpdate( delta ); + splashDraw(); + } + + splashCleanup(); + } + + /* Now run the main Lua program */ + state->run = luaCallMain(); while ( state->run ) { - luaCallUpdate(); - luaCallDraw(); if ( WindowShouldClose() ) { state->run = false; } + luaCallUpdate(); + luaCallDraw(); } luaCallExit(); } diff --git a/src/splash.c b/src/splash.c new file mode 100644 index 0000000..185db0c --- /dev/null +++ b/src/splash.c @@ -0,0 +1,198 @@ +#include "main.h" +#include "state.h" +#include "splash.h" + +#ifdef EMBED_LOGO + #include "embedded_logo.h" +#endif + +#define FADE_IN_TIME 0.8f +#define DISPLAY_TIME 2.5f +#define FADE_OUT_TIME 0.8f +#define SPLASH_TOTAL_TIME (FADE_IN_TIME + DISPLAY_TIME + FADE_OUT_TIME) + +typedef enum { + SPLASH_INDRAJITH, + SPLASH_MADE_WITH, + SPLASH_DONE +} SplashState; + +static SplashState currentSplash = SPLASH_INDRAJITH; +static float splashTimer = 0.0f; +static Texture2D raylibLogo = { 0 }; +static Texture2D reiluaLogo = { 0 }; +static bool logosLoaded = false; + +static float getSplashAlpha( float timer ) { + if ( timer < FADE_IN_TIME ) { + return timer / FADE_IN_TIME; + } + else if ( timer < FADE_IN_TIME + DISPLAY_TIME ) { + return 1.0f; + } + else { + float fadeOut = timer - FADE_IN_TIME - DISPLAY_TIME; + return 1.0f - ( fadeOut / FADE_OUT_TIME ); + } +} + +static void loadSplashLogos() { + if ( logosLoaded ) return; + +#ifdef EMBED_LOGO + /* Load from embedded data */ + Image raylib_img = LoadImageFromMemory( ".png", embedded_raylib_logo, embedded_raylib_logo_size ); + raylibLogo = LoadTextureFromImage( raylib_img ); + UnloadImage( raylib_img ); + + Image reilua_img = LoadImageFromMemory( ".png", embedded_reilua_logo, embedded_reilua_logo_size ); + reiluaLogo = LoadTextureFromImage( reilua_img ); + UnloadImage( reilua_img ); +#else + /* Load from files (development mode) */ + if ( FileExists( "logo/raylib_logo.png" ) ) { + raylibLogo = LoadTexture( "logo/raylib_logo.png" ); + } + if ( FileExists( "logo/reilua_logo.png" ) ) { + reiluaLogo = LoadTexture( "logo/reilua_logo.png" ); + } +#endif + + logosLoaded = true; +} + +static void unloadSplashLogos() { + if ( !logosLoaded ) return; + + UnloadTexture( raylibLogo ); + UnloadTexture( reiluaLogo ); + logosLoaded = false; +} + +static void drawIndrajithSplash( float alpha ) { + int screenWidth = GetScreenWidth(); + int screenHeight = GetScreenHeight(); + + ClearBackground( (Color){ 230, 41, 55, 255 } ); // Raylib red + + const char* text = "INDRAJITH MAKES GAMES"; + int fontSize = 48; + float spacing = 2.0f; + + Color textColor = WHITE; + textColor.a = (unsigned char)(255 * alpha); + + /* Draw text with slight expansion effect during fade in */ + float scale = 0.95f + (alpha * 0.05f); // Subtle scale from 0.95 to 1.0 + + /* Measure text with proper spacing for accurate centering */ + Vector2 textSize = MeasureTextEx( state->defaultFont, text, fontSize * scale, spacing ); + + /* Calculate centered position */ + Vector2 position = { + (float)(screenWidth / 2) - (textSize.x / 2), + (float)(screenHeight / 2) - (textSize.y / 2) + }; + + /* Draw with proper kerning */ + DrawTextEx( state->defaultFont, text, position, fontSize * scale, spacing, textColor ); +} + +static void drawMadeWithSplash( float alpha ) { + int screenWidth = GetScreenWidth(); + int screenHeight = GetScreenHeight(); + + ClearBackground( BLACK ); + + /* "Made using" text at top */ + const char* madeText = "Made using"; + int madeSize = 32; + int madeWidth = MeasureText( madeText, madeSize ); + + Color textColor = WHITE; + textColor.a = (unsigned char)(255 * alpha); + + int textY = screenHeight / 2 - 100; + DrawText( madeText, screenWidth / 2 - madeWidth / 2, textY, madeSize, textColor ); + + /* Calculate logo sizes and positions - scale down if too large */ + int maxLogoSize = 200; + float raylibScale = 1.0f; + float reiluaScale = 1.0f; + + if ( raylibLogo.id > 0 && raylibLogo.width > maxLogoSize ) { + raylibScale = (float)maxLogoSize / (float)raylibLogo.width; + } + if ( reiluaLogo.id > 0 && reiluaLogo.width > maxLogoSize ) { + reiluaScale = (float)maxLogoSize / (float)reiluaLogo.width; + } + + int raylibWidth = (int)(raylibLogo.width * raylibScale); + int raylibHeight = (int)(raylibLogo.height * raylibScale); + int reiluaWidth = (int)(reiluaLogo.width * reiluaScale); + int reiluaHeight = (int)(reiluaLogo.height * reiluaScale); + + /* Position logos side by side with spacing */ + int spacing = 40; + int totalWidth = raylibWidth + spacing + reiluaWidth; + int startX = screenWidth / 2 - totalWidth / 2; + int logoY = screenHeight / 2 - 20; + + Color tint = WHITE; + tint.a = (unsigned char)(255 * alpha); + + /* Draw Raylib logo */ + if ( raylibLogo.id > 0 ) { + Rectangle source = { 0, 0, (float)raylibLogo.width, (float)raylibLogo.height }; + Rectangle dest = { (float)startX, (float)logoY, (float)raylibWidth, (float)raylibHeight }; + DrawTexturePro( raylibLogo, source, dest, (Vector2){ 0, 0 }, 0.0f, tint ); + } + + /* Draw ReiLua logo */ + if ( reiluaLogo.id > 0 ) { + int reiluaX = startX + raylibWidth + spacing; + Rectangle source = { 0, 0, (float)reiluaLogo.width, (float)reiluaLogo.height }; + Rectangle dest = { (float)reiluaX, (float)logoY, (float)reiluaWidth, (float)reiluaHeight }; + DrawTexturePro( reiluaLogo, source, dest, (Vector2){ 0, 0 }, 0.0f, tint ); + } +} + +void splashInit() { + loadSplashLogos(); + currentSplash = SPLASH_INDRAJITH; + splashTimer = 0.0f; +} + +bool splashUpdate( float delta ) { + splashTimer += delta; + + if ( splashTimer >= SPLASH_TOTAL_TIME ) { + splashTimer = 0.0f; + currentSplash++; + } + + return currentSplash >= SPLASH_DONE; +} + +void splashDraw() { + float alpha = getSplashAlpha( splashTimer ); + + BeginDrawing(); + + switch ( currentSplash ) { + case SPLASH_INDRAJITH: + drawIndrajithSplash( alpha ); + break; + case SPLASH_MADE_WITH: + drawMadeWithSplash( alpha ); + break; + default: + break; + } + + EndDrawing(); +} + +void splashCleanup() { + unloadSplashLogos(); +}