Compare commits

...

1 Commits

Author SHA1 Message Date
3bb961448d feat(render): add game rendering 2025-12-27 18:36:03 -03:00
8 changed files with 205 additions and 17 deletions

7
app.c
View File

@ -28,10 +28,13 @@ void App_Init(App_t *app) {
return;
}
if (!Objects_Init(&app->graphics)) {
if (!Objects_Init(&app->objects)) {
return;
}
Map_LoadSampleData();
app->isInGame = true;
app->isRunning = true;
}
@ -43,7 +46,7 @@ void App_Run(App_t *app) {
}
void App_Shutdown(const App_t* app) {
Objects_Destroy(&app->graphics);
Objects_Destroy(&app->objects);
Map_Destroy(&app->map);
Bitmap_Destroy(&app->bitmap);
Gui_Shutdown();

3
app.h
View File

@ -12,11 +12,12 @@
typedef struct App {
bool isRunning;
bool isInGame;
SystemMetrics_t metrics;
ConfigParams_t configParams;
Map_t map;
Bitmap_t bitmap;
Objects_t graphics;
Objects_t objects;
Gui_t gui;
} App_t;

14
gui.c
View File

@ -39,8 +39,8 @@ void Gui_StartRender() {
ImGui_NewFrame();
}
void Gui_Render(Gui_t *gui, ConfigParams_t *configParams) {
Gui_RenderMainMenu(gui);
void Gui_Render(Gui_t *gui, ConfigParams_t *configParams, const bool inGame) {
Gui_RenderMainMenu(gui, inGame);
Gui_RenderPreferences_Dialog(gui, configParams);
}
@ -57,7 +57,7 @@ void Gui_Shutdown() {
ImGui_DestroyContext(NULL);
}
void Gui_RenderMainMenu(Gui_t *gui) {
void Gui_RenderMainMenu(Gui_t *gui, const bool inGame) {
if (!ImGui_BeginMainMenuBar()) {
return;
}
@ -82,20 +82,20 @@ void Gui_RenderMainMenu(Gui_t *gui) {
if (ImGui_MenuItem("Journey Onward")) {
}
if (ImGui_MenuItem("End Game")) {
if (ImGui_MenuItemEx("End Game", NULL, NULL, inGame)) {
}
ImGui_EndMenu();
}
if (ImGui_BeginMenu("Info")) {
if (ImGui_MenuItem("Change Data")) {
if (ImGui_MenuItemEx("Change Data", NULL, NULL, inGame)) {
}
if (ImGui_MenuItem("Userlist")) {
if (ImGui_MenuItemEx("Userlist", NULL, NULL, inGame)) {
}
if (ImGui_MenuItem("Comments")) {
if (ImGui_MenuItemEx("Comments", NULL, NULL, inGame)) {
}
ImGui_EndMenu();

4
gui.h
View File

@ -16,11 +16,11 @@ typedef struct Gui {
void Gui_Init(Gui_t* gui);
void Gui_ProcessEvent(const SDL_Event* event);
void Gui_StartRender();
void Gui_Render(Gui_t* gui, ConfigParams_t* configParams);
void Gui_Render(Gui_t* gui, ConfigParams_t* configParams, bool inGame);
void Gui_FinishRender();
void Gui_Shutdown();
void Gui_RenderMainMenu(Gui_t* gui);
void Gui_RenderMainMenu(Gui_t* gui, bool inGame);
void Gui_RenderPreferences_Dialog(Gui_t* gui, ConfigParams_t* configParams);
#endif //TIBIA_GUI_H

View File

@ -6,9 +6,8 @@
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL_iostream.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_messagebox.h>
#include <SDL3/SDL.h>
#include "window.h"
bool Objects_Init(Objects_t *objects) {
memset(objects->spriteTable, 0, sizeof(objects->spriteTable));
@ -22,6 +21,7 @@ bool Objects_Init(Objects_t *objects) {
}
memset(objects->activeObjectList, 0, sizeof(objects->activeObjectList));
memset(objects->activeTextures, 0, sizeof(objects->activeTextures));
SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_Init: memory allocated");
@ -115,6 +115,7 @@ bool Objects_LoadData(Objects_t *objects) {
bool Objects_LoadSprites(Objects_t *objects) {
SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites.");
SDL_Renderer* renderer = Window_GetRenderer();
// Free existing sprites to prevent memory leaks
for (int i = 0; i < SPRITE_TABLE_SIZE; i++) {
@ -186,6 +187,9 @@ bool Objects_LoadSprites(Objects_t *objects) {
}
}
objects->spriteTable[spriteID] = spriteBuffer;
objects->activeTextures[spriteID] = Objects_TextureFromRaw(renderer, spriteBuffer);
spritesLoaded++;
}
@ -199,4 +203,72 @@ void Objects_Destroy(const Objects_t *objects) {
for (int i = 0; i < THING_DATA_POOL_SIZE; i++) {
free(objects->thingDataPool[i]);
}
for (int i = 0; i < ACTIVE_OBJECT_LIST_SIZE; i++) {
if (objects->activeTextures[i]) {
SDL_DestroyTexture(objects->activeTextures[i]);
}
}
}
SDL_Texture* Objects_GetSpriteTexture(const Objects_t *objects, const uint16_t spriteID) {
if (spriteID >= SPRITE_TABLE_SIZE) {
return NULL;
}
return objects->activeTextures[spriteID];
}
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, uint8_t* rawData) {
if (!rawData) {
return NULL;
}
const uint16_t totalSize = *(uint16_t*) rawData;
uint8_t* data = rawData + 2;
int readOffset = 0;
SDL_Surface* surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_ARGB8888);
if (!surface) {
return NULL;
}
SDL_LockSurface(surface);
uint32_t* pixels = surface->pixels;
memset(pixels, 0, 32 * 32 * 4);
int currentPixel = 0;
while (readOffset < totalSize - 2 && currentPixel < 1024) {
const uint16_t transparentRaw = *(uint16_t*)(data + readOffset);
readOffset += 2;
const uint16_t transparentBytes = transparentRaw % 0x5A0;
const int skipPixels = transparentBytes / 3;
currentPixel += skipPixels;
const uint16_t colorBytes = *(uint16_t*)(data + readOffset);
readOffset += 2;
const int drawPixels = colorBytes / 3;
for (int i = 0; i < drawPixels; i++) {
if (currentPixel >= 1024) {
break;
}
const uint8_t r = data[readOffset++];
const uint8_t g = data[readOffset++];
const uint8_t b = data[readOffset++];
pixels[currentPixel++] = SDL_MapRGBA(SDL_GetPixelFormatDetails(surface->format), NULL, r, g, b, 255);
}
}
SDL_UnlockSurface(surface);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
return texture;
}

View File

@ -11,6 +11,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL_render.h>
#pragma pack(push, 1)
typedef struct {
@ -25,11 +26,16 @@ typedef struct Objects {
void* spriteTable[SPRITE_TABLE_SIZE];
void* thingDataPool[THING_DATA_POOL_SIZE];
void* activeObjectList[ACTIVE_OBJECT_LIST_SIZE];
SDL_Texture* activeTextures[SPRITE_TABLE_SIZE];
} Objects_t;
bool Objects_Init(Objects_t* objects);
bool Objects_LoadData(Objects_t* objects);
bool Objects_LoadSprites(Objects_t* objects);
void Objects_Destroy(const Objects_t* objects);
SDL_Texture* Objects_GetSpriteTexture(const Objects_t* objects, uint16_t spriteID);
// TODO: move this somewhere else
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, uint8_t* rawData);
#endif //TIBIA_OBJECTS_H

104
render.c
View File

@ -6,6 +6,48 @@
#include "window.h"
#include "gui.h"
#define MAP_WIDTH_TILES 15
#define MAP_HEIGHT_TILES 11
#define TILE_SIZE 32
typedef struct {
uint16_t groundSpriteID;
} MapTile;
MapTile g_ClientMapData[MAP_WIDTH_TILES][MAP_HEIGHT_TILES];
void Map_LoadSampleData() {
SDL_Log("Map_LoadSampleData: Populating dummy map...");
// These IDs are guesses for Tibia 1.03 based on standard asset order.
// If these show up as items (like swords/apples), try swapping them.
// Usually: 0-100 contains basic ground tiles.
uint16_t ID_GRASS = 45;
uint16_t ID_DIRT = 40;
uint16_t ID_STONE = 50;
for (int x = 0; x < MAP_WIDTH_TILES; x++) {
for (int y = 0; y < MAP_HEIGHT_TILES; y++) {
// 1. Default to Grass
uint16_t tileID = ID_GRASS;
// 2. Create a Checkerboard pattern
if ((x + y) % 2 == 0) {
tileID = ID_DIRT;
}
// 3. Create a Stone path in the middle row
if (y == 5) {
tileID = ID_STONE;
}
// 4. Assign to the map
g_ClientMapData[x][y].groundSpriteID = x * MAP_HEIGHT_TILES + y;
}
}
}
void Render_Frame(App_t* app) {
Gui_StartRender();
@ -14,7 +56,9 @@ void Render_Frame(App_t* app) {
SDL_RenderClear(renderer);
Render_MainWindowBackground(app);
Gui_Render(&app->gui, &app->configParams);
Render_GameView(app);
Gui_Render(&app->gui, &app->configParams, app->isInGame);
Gui_FinishRender();
SDL_RenderPresent(renderer);
@ -29,7 +73,7 @@ void Render_MainWindowBackground(const App_t *app) {
return;
}
const int TILE_SIZE = 128;
// const int TILE_SIZE = 128;
// Calculate how many tiles we need to cover the screen
const int tilesX = app->metrics.screenWidth / TILE_SIZE + 1;
@ -48,3 +92,59 @@ void Render_MainWindowBackground(const App_t *app) {
}
}
}
void Render_GameView(const App_t *app) {
if (!app->isInGame) {
Render_TitleScreen(app);
return;
}
Render_Game(app);
}
void Render_TitleScreen(const App_t *app) {
// TODO: properly calculate the window sizes and spacings
SDL_Renderer *renderer = Window_GetRenderer();
int winW, winH;
SDL_GetRenderOutputSize(renderer, &winW, &winH);
const SDL_FRect destRect = {
0.0f, 30.0f,
(float)winW, (float)winH - 150.0f
};
SDL_RenderTexture(renderer, app->bitmap.tibiaTexture, NULL, &destRect);
}
void Render_Game(const App_t *app) {
SDL_Renderer *renderer = Window_GetRenderer();
for (int x = 0; x < MAP_WIDTH_TILES; x++) {
for (int y = 0; y < MAP_HEIGHT_TILES; y++) {
const MapTile* tile = &g_ClientMapData[x][y];
if (tile->groundSpriteID == 0) {
continue;
}
const int screenX = x * TILE_SIZE;
const int screenY = y * TILE_SIZE;
SDL_Texture* texture = Objects_GetSpriteTexture(&app->objects, tile->groundSpriteID);
if (texture) {
SDL_FRect destRect = {
(float)screenX,
(float)screenY,
(float)TILE_SIZE,
(float)TILE_SIZE
};
SDL_RenderTexture(renderer, texture, NULL, &destRect);
} else {
SDL_FRect destRect = { (float)screenX, (float)screenY, 32.0f, 32.0f };
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Green for ground
SDL_RenderFillRect(renderer, &destRect);
}
}
}
}

View File

@ -8,8 +8,14 @@
#include "app.h"
void Map_LoadSampleData();
void Render_Frame(App_t* app);
void Render_MainWindowBackground(const App_t* app);
void Render_GameView(const App_t* app);
void Render_TitleScreen(const App_t* app);
void Render_Game(const App_t* app);
void Render_DrawWindowFrame(SDL_Renderer* renderer, int x, int y, int w, int h);