feat: add new and continue game dialogs

This commit is contained in:
Rodrigo Verdiani 2025-12-28 23:27:41 -03:00
parent eb5e6c6db9
commit 37ca39119f
8 changed files with 352 additions and 52 deletions

View File

@ -21,8 +21,6 @@ set(IMGUI_SOURCES
${BINDINGS_DIR}/dcimgui.cpp ${BINDINGS_DIR}/dcimgui.cpp
${BINDINGS_DIR}/dcimgui_impl_sdl3.cpp ${BINDINGS_DIR}/dcimgui_impl_sdl3.cpp
${BINDINGS_DIR}/dcimgui_impl_sdlrenderer3.cpp ${BINDINGS_DIR}/dcimgui_impl_sdlrenderer3.cpp
gui.c
gui.h
) )
add_executable(tibia add_executable(tibia
@ -45,7 +43,10 @@ add_executable(tibia
objects.c objects.c
objects.h objects.h
network.c network.c
network.h) network.h
gui.c
gui.h
)
target_include_directories(tibia PRIVATE target_include_directories(tibia PRIVATE
${IMGUI_DIR} ${IMGUI_DIR}

197
gui.c
View File

@ -14,6 +14,14 @@
#include "window.h" #include "window.h"
#include "config.h" #include "config.h"
#define BASE_BODY_SPRITE_ID 155
#define BASE_HEAD_SPRITE_ID 176
#define BASE_LEGS_SPRITE_ID 158
#define BASE_SHOES_SPRITE_ID 161
static SDL_Texture *cachedCharPreview = NULL;
Character_t character = {};
void Gui_Init(Gui_t *gui) { void Gui_Init(Gui_t *gui) {
ImGui_CreateContext(NULL); ImGui_CreateContext(NULL);
@ -44,12 +52,12 @@ void Gui_StartRender() {
ImGui_NewFrame(); ImGui_NewFrame();
} }
void Gui_Render(Gui_t *gui, Network_t *network, ConfigParams_t *configParams) { void Gui_Render(Gui_t *gui, const Objects_t *objects, Network_t *network, ConfigParams_t *configParams) {
Gui_RenderMainMenu(gui); Gui_RenderMainMenu(gui);
Gui_RenderStatusBar(gui); Gui_RenderStatusBar(gui);
Gui_RenderPreferences_Dialog(gui, configParams); Gui_RenderPreferences_Dialog(gui, configParams);
Gui_RenderNewGame_Dialog(gui, configParams, network); Gui_RenderNewGame_Dialog(gui, objects, configParams, network);
Gui_RenderJourneyOnward_Dialog(gui, configParams, network); Gui_RenderJourneyOnward_Dialog(gui, configParams, network);
} }
@ -64,6 +72,7 @@ void Gui_Shutdown() {
cImGui_ImplSDLRenderer3_Shutdown(); cImGui_ImplSDLRenderer3_Shutdown();
cImGui_ImplSDL3_Shutdown(); cImGui_ImplSDL3_Shutdown();
ImGui_DestroyContext(NULL); ImGui_DestroyContext(NULL);
SDL_DestroyTexture(cachedCharPreview);
} }
void Gui_RenderMainMenu(Gui_t *gui) { void Gui_RenderMainMenu(Gui_t *gui) {
@ -276,11 +285,146 @@ void Gui_RenderPreferences_Dialog(Gui_t *gui, ConfigParams_t *configParams) {
ImGui_End(); ImGui_End();
} }
void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Network_t *network) { void Gui_RenderCustomizerRow(const char *label, uint8_t *value) {
if (ImGui_BeginTable(label, 3, ImGuiTableFlags_SizingFixedFit)) {
ImGui_TableSetupColumnEx("Left", ImGuiTableColumnFlags_WidthFixed, 20.0f, 0);
ImGui_TableSetupColumnEx("Text", ImGuiTableColumnFlags_WidthFixed, 45.0f, 0);
ImGui_TableSetupColumnEx("Right", ImGuiTableColumnFlags_WidthFixed, 20.0f, 0);
ImGui_TableNextRow();
ImGui_TableSetColumnIndex(0);
if (ImGui_ButtonEx("<", (ImVec2){20, 20})) {
(*value)--;
SDL_DestroyTexture(cachedCharPreview);
cachedCharPreview = NULL;
}
ImGui_TableSetColumnIndex(1);
const float currentX = ImGui_GetCursorPosX();
const float textWidth = ImGui_CalcTextSize(label).x;
ImGui_SetCursorPosX(currentX + (45.0f - textWidth) * 0.5f);
ImGui_Text("%s", label);
ImGui_TableSetColumnIndex(2);
if (ImGui_ButtonEx(">", (ImVec2){20, 20})) {
(*value)++;
SDL_DestroyTexture(cachedCharPreview);
cachedCharPreview = NULL;
}
ImGui_EndTable();
}
}
SDL_Texture *Gui_CombineCharacterTextures(SDL_Texture *bodyTexture, SDL_Texture *headTexture, SDL_Texture *legsTexture,
SDL_Texture *shoesTexture) {
SDL_Renderer *renderer = Window_GetRenderer();
SDL_Texture *target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, SPRITE_SIZE, SPRITE_SIZE);
SDL_SetTextureBlendMode(target, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(target, SDL_SCALEMODE_NEAREST);
SDL_SetRenderTarget(renderer, target);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
// Draw layers in order
SDL_RenderTexture(renderer, shoesTexture, NULL, NULL);
SDL_RenderTexture(renderer, legsTexture, NULL, NULL);
SDL_RenderTexture(renderer, bodyTexture, NULL, NULL);
// The head texture needs to be offset
// This is hardcoded in the original client
SDL_RenderTexture(renderer, headTexture, NULL, &(SDL_FRect){1.0f, -14.0f, SPRITE_SIZE, SPRITE_SIZE});
SDL_SetRenderTarget(renderer, NULL);
return target;
}
void Gui_RenderCharacterCustomizer(const Objects_t *objects) {
if (ImGui_BeginTable("CharCustomizerMain", 2, ImGuiTableFlags_SizingFixedFit)) {
ImGui_TableSetupColumnEx("Preview", ImGuiTableColumnFlags_WidthFixed, SPRITE_SIZE * 2, 0);
ImGui_TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch);
ImGui_TableNextRow();
// Character Preview
ImGui_TableSetColumnIndex(0);
const float frameHeight = ImGui_GetFrameHeight();
const float spacing = ImGui_GetStyle()->ItemSpacing.y;
const float controlsTotalHeight = frameHeight * 4.0f + spacing * 3.0f;
const float offset = (controlsTotalHeight - SPRITE_SIZE * 2) * 0.5f;
if (offset > 0.0f) {
ImGui_SetCursorPosY(ImGui_GetCursorPosY() + offset);
}
if (cachedCharPreview == NULL) {
SDL_Texture *bodyTexture = Objects_GetSpriteTexture(objects, 155 + character.body);
SDL_Texture *headTexture = Objects_GetSpriteTexture(objects, 176 + character.head);
SDL_Texture *legsTexture = Objects_GetSpriteTexture(objects, 158 + character.legs);
SDL_Texture *shoesTexture = Objects_GetSpriteTexture(objects, 161 + character.shoes);
cachedCharPreview = Gui_CombineCharacterTextures(bodyTexture, headTexture, legsTexture, shoesTexture);
}
ImGui_Image((ImTextureRef){NULL, (ImTextureID) cachedCharPreview}, (ImVec2){SPRITE_SIZE * 2, SPRITE_SIZE * 2});
ImGui_TableSetColumnIndex(1);
ImGui_SetCursorPosX(ImGui_GetCursorPosX() + 10.0f);
ImGui_BeginGroup();
ImGui_PushID("Head");
Gui_RenderCustomizerRow("Head", &character.head);
ImGui_PopID();
ImGui_PushID("Body");
Gui_RenderCustomizerRow("Body", &character.body);
ImGui_PopID();
ImGui_PushID("Legs");
Gui_RenderCustomizerRow("Legs", &character.legs);
ImGui_PopID();
ImGui_PushID("Shoes");
Gui_RenderCustomizerRow("Shoes", &character.shoes);
ImGui_PopID();
ImGui_EndGroup();
ImGui_EndTable();
}
}
uint16_t Gui_PackCharacter() {
// Ensure indices are within 4-bit range
character.head &= 0x0F;
character.body &= 0x0F;
character.legs &= 0x0F;
character.shoes &= 0x0F;
uint16_t packed = 0;
packed |= character.head << 12;
packed |= character.body << 8;
packed |= character.legs << 4;
packed |= character.shoes;
return packed;
}
void Gui_RenderNewGame_Dialog(Gui_t *gui, const Objects_t *objects, const ConfigParams_t *configParams, Network_t *network) {
if (!gui->isNewGameDialogOpen) { if (!gui->isNewGameDialogOpen) {
return; return;
} }
static int sex = 1;
static char realName[50] = "";
static char email[50] = "";
static char location[50] = "";
const ImGuiViewport *viewport = ImGui_GetMainViewport(); const ImGuiViewport *viewport = ImGui_GetMainViewport();
const ImVec2 viewportCenter = { const ImVec2 viewportCenter = {
viewport->Pos.x + viewport->Size.x * 0.5f, viewport->Pos.x + viewport->Size.x * 0.5f,
@ -309,7 +453,6 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
ImGui_Text("Real Name:"); ImGui_Text("Real Name:");
ImGui_TableSetColumnIndex(1); ImGui_TableSetColumnIndex(1);
ImGui_SetNextItemWidth(-FLT_MIN); ImGui_SetNextItemWidth(-FLT_MIN);
static char realName[64] = "";
ImGui_InputText("##RealName", realName, sizeof(realName), 0); ImGui_InputText("##RealName", realName, sizeof(realName), 0);
// Location // Location
@ -319,7 +462,6 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
ImGui_Text("Location:"); ImGui_Text("Location:");
ImGui_TableSetColumnIndex(1); ImGui_TableSetColumnIndex(1);
ImGui_SetNextItemWidth(-FLT_MIN); ImGui_SetNextItemWidth(-FLT_MIN);
static char location[64] = "";
ImGui_InputText("##Location", location, sizeof(location), 0); ImGui_InputText("##Location", location, sizeof(location), 0);
// E-Mail // E-Mail
@ -329,7 +471,6 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
ImGui_Text("E-Mail:"); ImGui_Text("E-Mail:");
ImGui_TableSetColumnIndex(1); ImGui_TableSetColumnIndex(1);
ImGui_SetNextItemWidth(-FLT_MIN); ImGui_SetNextItemWidth(-FLT_MIN);
static char email[64] = "";
ImGui_InputText("##Email", email, sizeof(email), 0); ImGui_InputText("##Email", email, sizeof(email), 0);
ImGui_EndTable(); ImGui_EndTable();
@ -339,7 +480,24 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
// "Create Character" // "Create Character"
if (ImGui_ButtonEx("Create Character", (ImVec2){-FLT_MIN, 0})) { if (ImGui_ButtonEx("Create Character", (ImVec2){-FLT_MIN, 0})) {
// TODO: verify is both passwords match
uint8_t buffer[30 + 30 + 1 + 2 + 1 + 50 + 50 + 50] = {0};
strncpy((char *) buffer, configParams->lastAccount, 30);
strncpy((char *) buffer + 30, configParams->lastPassword, 30);
buffer[30 + 30] = (uint8_t) sex;
const uint16_t packedCharacter = Gui_PackCharacter();
memcpy(buffer + 30 + 30 + 1, &packedCharacter, 2);
strncpy((char *) buffer + 30 + 30 + 1 + 2 + 1, realName, 50);
strncpy((char *) buffer + 30 + 30 + 1 + 2 + 1 + 50, location, 50);
strncpy((char *) buffer + 30 + 30 + 1 + 2 + 1 + 50 + 50, email, 50);
Network_ConnectAndReportStatus(network, configParams, gui); Network_ConnectAndReportStatus(network, configParams, gui);
Network_SendPacket(network, HANDLER_LOGIN_OR_CREATE_CHAR, 0x00, buffer, 30 + 30 + 1 + 2 + 1 + 50 + 50 + 50);
gui->isNewGameDialogOpen = false;
} }
ImGui_Dummy((ImVec2){0.0f, 10.0f}); // Spacing between buttons ImGui_Dummy((ImVec2){0.0f, 10.0f}); // Spacing between buttons
@ -383,10 +541,9 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
ImGui_AlignTextToFramePadding(); ImGui_AlignTextToFramePadding();
ImGui_Text("Your Sex:"); ImGui_Text("Your Sex:");
ImGui_TableSetColumnIndex(1); ImGui_TableSetColumnIndex(1);
static int sex = 0; ImGui_RadioButtonIntPtr("male", &sex, 1);
ImGui_RadioButtonIntPtr("male", &sex, 0);
ImGui_SameLine(); ImGui_SameLine();
ImGui_RadioButtonIntPtr("female", &sex, 1); ImGui_RadioButtonIntPtr("female", &sex, 0);
// Enter Password // Enter Password
ImGui_TableNextRow(); ImGui_TableNextRow();
@ -413,20 +570,7 @@ void Gui_RenderNewGame_Dialog(Gui_t *gui, const ConfigParams_t* configParams, Ne
ImGui_TableSetColumnIndex(1); ImGui_TableSetColumnIndex(1);
// Calculate where the cursor is to draw the rectangle Gui_RenderCharacterCustomizer(objects);
const ImVec2 p0 = ImGui_GetCursorScreenPos();
float placeholderHeight = 90.0f; // Roughly matching the input area height
ImVec2 p1 = { p0.x + 160.0f, p0.y + placeholderHeight };
// Draw Gray Rectangle
ImDrawList* draw_list = ImGui_GetWindowDrawList();
ImDrawList_AddRectFilled(draw_list,p0, p1, IM_COL32(200, 200, 200, 255));
ImDrawList_AddRectFilled(draw_list, p0, p1, IM_COL32(100, 100, 100, 255));
// Add text "Placeholder" inside it just for clarity
ImGui_SetCursorPosY(ImGui_GetCursorPosY() + placeholderHeight * 0.4f);
ImGui_SetCursorPosX(ImGui_GetCursorPosX() + 40.0f);
ImGui_TextColored((ImVec4){0.3f,0.3f,0.3f,1.0f}, "Character\nControls");
ImGui_EndTable(); ImGui_EndTable();
} }
@ -498,7 +642,14 @@ void Gui_RenderJourneyOnward_Dialog(Gui_t *gui, ConfigParams_t *configParams, Ne
// Let's Go // Let's Go
if (ImGui_ButtonEx("Let's Go", (ImVec2){-FLT_MIN, 0})) { if (ImGui_ButtonEx("Let's Go", (ImVec2){-FLT_MIN, 0})) {
gui->isJourneyOnwardDialogOpen = false; gui->isJourneyOnwardDialogOpen = false;
uint8_t buffer[60] = {0};
strncpy((char *) buffer, configParams->lastAccount, 30);
strncpy((char *) buffer + 30, configParams->lastPassword, 30);
Network_ConnectAndReportStatus(network, configParams, gui); Network_ConnectAndReportStatus(network, configParams, gui);
Network_SendPacket(network, HANDLER_LOGIN_OR_CREATE_CHAR, 0x01, buffer, 60);
} }
ImGui_EndTable(); ImGui_EndTable();

12
gui.h
View File

@ -9,6 +9,14 @@
#include "config.h" #include "config.h"
#include "network.h" #include "network.h"
#include "objects.h"
typedef struct Character {
uint8_t head;
uint8_t body;
uint8_t legs;
uint8_t shoes;
} Character_t;
typedef struct Gui { typedef struct Gui {
bool isPreferencesDialogOpen; bool isPreferencesDialogOpen;
@ -20,7 +28,7 @@ typedef struct Gui {
void Gui_Init(Gui_t* gui); void Gui_Init(Gui_t* gui);
void Gui_ProcessEvent(const SDL_Event* event); void Gui_ProcessEvent(const SDL_Event* event);
void Gui_StartRender(); void Gui_StartRender();
void Gui_Render(Gui_t* gui, Network_t* network, ConfigParams_t* configParams); void Gui_Render(Gui_t* gui, const Objects_t* objects, Network_t* network, ConfigParams_t* configParams);
void Gui_FinishRender(); void Gui_FinishRender();
void Gui_Shutdown(); void Gui_Shutdown();
@ -28,7 +36,7 @@ void Gui_RenderMainMenu(Gui_t* gui);
void Gui_RenderStatusBar(const Gui_t* gui); void Gui_RenderStatusBar(const Gui_t* gui);
void Gui_RenderPreferences_Dialog(Gui_t* gui, ConfigParams_t* configParams); void Gui_RenderPreferences_Dialog(Gui_t* gui, ConfigParams_t* configParams);
void Gui_RenderNewGame_Dialog(Gui_t* gui, const ConfigParams_t* configParams, Network_t* network); void Gui_RenderNewGame_Dialog(Gui_t* gui, const Objects_t* objects, const ConfigParams_t* configParams, Network_t* network);
void Gui_RenderJourneyOnward_Dialog(Gui_t* gui, ConfigParams_t* configParams, Network_t* network); void Gui_RenderJourneyOnward_Dialog(Gui_t* gui, ConfigParams_t* configParams, Network_t* network);
void Gui_UpdateStatusBar(Gui_t* gui, const char* message); void Gui_UpdateStatusBar(Gui_t* gui, const char* message);

View File

@ -83,3 +83,58 @@ void Network_ConnectAndReportStatus(Network_t *network, const ConfigParams_t *co
Gui_UpdateStatusBar(gui, message); Gui_UpdateStatusBar(gui, message);
} }
void Network_Send(NET_StreamSocket* socket, uint8_t* packetBuf, const uint16_t packetLen) {
const uint16_t payloadLength = packetLen - 2;
*(uint16_t*) packetBuf = payloadLength;
if (!NET_WriteToStreamSocket(socket, packetBuf, packetLen)) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Network_Send: Failed to send packet.");
}
}
void Network_HandleLoginOrCreateChar(const uint16_t opcode, uint8_t* packetBuf, uint16_t* packetLen, const void* data, const int dataLength) {
// Copy the opcode
packetBuf[*packetLen] = (uint8_t) opcode;
*packetLen += 1;
// Write unk0
const uint16_t unk0 = 1;
memcpy(&packetBuf[*packetLen], &unk0, 2);
*packetLen += 2;
// Write unk1
const uint16_t unk1 = 'g';
memcpy(&packetBuf[*packetLen], &unk1, 2);
*packetLen += 2;
memcpy(&packetBuf[*packetLen], data, dataLength);
*packetLen += dataLength;
}
void Network_SendPacket(const Network_t* network, const PACKET_HANDLER handler, const uint16_t opcode, void* data, const int dataLength) {
if (network->socket == NULL) {
return;
}
uint8_t packetBuf[1050];
uint16_t packetLen = 2;
// TODO: need this?
memset(packetBuf, 0, 1049);
// Write handler to buffer at offset 2
memcpy(&packetBuf[packetLen], &handler, 2);
packetLen += 2;
switch (handler) {
case HANDLER_LOGIN_OR_CREATE_CHAR:
Network_HandleLoginOrCreateChar(opcode, packetBuf, &packetLen, data, dataLength);
break;
default:
break;
}
Network_Send(network->socket, packetBuf, packetLen);
}

View File

@ -15,9 +15,15 @@ typedef struct Network {
NET_StreamSocket* socket; NET_StreamSocket* socket;
} Network_t; } Network_t;
typedef enum PacketHandler {
HANDLER_LOGIN_OR_CREATE_CHAR = 0
} PACKET_HANDLER;
bool Network_Init(Network_t* network); bool Network_Init(Network_t* network);
void Network_Shutdown(const Network_t* network); void Network_Shutdown(const Network_t* network);
void Network_ConnectAndReportStatus(Network_t* network, const ConfigParams_t* configParams, const Gui_t* gui); void Network_ConnectAndReportStatus(Network_t* network, const ConfigParams_t* configParams, const Gui_t* gui);
void Network_SendPacket(const Network_t* network, PACKET_HANDLER handler, uint16_t opcode, void* data, int dataLength);
#endif //TIBIA_NETWORK_H #endif //TIBIA_NETWORK_H

115
objects.c
View File

@ -3,6 +3,7 @@
// //
#include "objects.h" #include "objects.h"
#include "window.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -11,8 +12,6 @@
#include <SDL3/SDL_messagebox.h> #include <SDL3/SDL_messagebox.h>
bool Objects_Init(Objects_t *objects) { bool Objects_Init(Objects_t *objects) {
memset(objects->spriteTable, 0, sizeof(objects->spriteTable));
for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { for (int i = 0; i < THING_DATA_POOL_SIZE; i++) {
objects->thingDataPool[i] = malloc(1536); objects->thingDataPool[i] = malloc(1536);
@ -114,13 +113,15 @@ bool Objects_LoadData(Objects_t *objects) {
} }
bool Objects_LoadSprites(Objects_t *objects) { bool Objects_LoadSprites(Objects_t *objects) {
SDL_Renderer* renderer = Window_GetRenderer();
SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites."); SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites.");
// Free existing sprites to prevent memory leaks // Free existing sprites to prevent memory leaks
for (int i = 0; i < SPRITE_TABLE_SIZE; i++) { for (int i = 0; i < MAX_SPRITES; i++) {
if (objects->spriteTable[i] != NULL) { if (objects->spriteTextures[i] != NULL) {
free(objects->spriteTable[i]); SDL_DestroyTexture(objects->spriteTextures[i]);
objects->spriteTable[i] = NULL; objects->spriteTextures[i] = NULL;
} }
} }
@ -156,36 +157,34 @@ bool Objects_LoadSprites(Objects_t *objects) {
} }
// Ensure ID is within bounds // Ensure ID is within bounds
if (spriteID >= SPRITE_TABLE_SIZE) { if (spriteID >= MAX_SPRITES) {
SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: Sprite ID %d exceeds table size (%d). Skipping.", SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: Sprite ID %d exceeds table size (%d). Skipping.",
spriteID, SPRITE_TABLE_SIZE); spriteID, MAX_SPRITES);
// Skip the data bytes so we don't desync the file stream // Skip the data bytes so we don't desync the file stream
SDL_SeekIO(file, spriteSize - 2, SDL_IO_SEEK_CUR); SDL_SeekIO(file, spriteSize - 2, SDL_IO_SEEK_CUR);
continue; continue;
} }
const uint16_t bytesToRead = spriteSize - 2;
if (bytesToRead <= 0) {
continue;
}
uint8_t *spriteBuffer = malloc(spriteSize); uint8_t *spriteBuffer = malloc(spriteSize);
if (!spriteBuffer) { if (!spriteBuffer) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: cannot allocate memory"); SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: cannot allocate memory");
break; break;
} }
objects->spriteTable[spriteID] = spriteBuffer; if (SDL_ReadIO(file, spriteBuffer, bytesToRead) != bytesToRead) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read full sprite data");
// Store size in the first 2 bytes of the buffer break;
*(uint16_t *) spriteBuffer = spriteSize;
// Read data
// Read (Size - 2) bytes into Buffer + 2
const size_t bytesToRead = spriteSize - 2;
if (bytesToRead > 0) {
if (SDL_ReadIO(file, spriteBuffer + 2, bytesToRead) != bytesToRead) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read full sprite data");
break;
}
} }
objects->spriteTextures[spriteID] = Objects_TextureFromRaw(renderer, spriteBuffer, bytesToRead);
free(spriteBuffer);
spritesLoaded++; spritesLoaded++;
} }
@ -199,4 +198,78 @@ void Objects_Destroy(const Objects_t *objects) {
for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { for (int i = 0; i < THING_DATA_POOL_SIZE; i++) {
free(objects->thingDataPool[i]); free(objects->thingDataPool[i]);
} }
for (int i = 0; i < MAX_SPRITES; i++) {
SDL_DestroyTexture(objects->spriteTextures[i]);
}
}
SDL_Texture * Objects_GetSpriteTexture(const Objects_t* objects, const uint16_t spriteID) {
if (spriteID < 0 || spriteID >= MAX_SPRITES) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_GetSpriteTexture: sprite ID out of range (ID: %d).", spriteID);
return NULL;
}
return objects->spriteTextures[spriteID];
}
SDL_Texture * Objects_TextureFromRaw(SDL_Renderer *renderer, const uint8_t *rawData, const uint16_t size) {
if (!rawData) {
return NULL;
}
SDL_Surface* surface = SDL_CreateSurface(SPRITE_SIZE, SPRITE_SIZE, SDL_PIXELFORMAT_ARGB8888);
if (!surface) {
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_TextureFromRaw: couldn't create surface");
return NULL;
}
SDL_LockSurface(surface);
uint32_t* pixels = surface->pixels;
memset(pixels, 0, SPRITE_SIZE * SPRITE_SIZE * 4);
int currentPixel = 0;
int readOffset = 0;
while (readOffset < size && currentPixel < 1024) {
const uint16_t transparentRaw = *(uint16_t*) (rawData + readOffset);
readOffset += 2;
const uint16_t transparentBytes = transparentRaw % 0x5A0;
const uint16_t skipPixels = transparentBytes / 3;
currentPixel += skipPixels;
const uint16_t colorBytes = *(uint16_t*)(rawData + readOffset);
readOffset += 2;
const uint16_t drawPixels = colorBytes / 3;
for (int i = 0; i < drawPixels; i++) {
if (currentPixel >= 1024) {
break;
}
const uint8_t b = rawData[readOffset++];
const uint8_t g = rawData[readOffset++];
const uint8_t r = rawData[readOffset++];
// Flip image
const int x = currentPixel % SPRITE_SIZE;
const int y = currentPixel / SPRITE_SIZE;
const int flippedY = 31 - y;
pixels[flippedY * SPRITE_SIZE + x] = SDL_MapRGBA(SDL_GetPixelFormatDetails(surface->format), NULL, r, g, b, 255);
currentPixel++;
}
}
SDL_UnlockSurface(surface);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
SDL_DestroySurface(surface);
return texture;
} }

View File

@ -5,12 +5,15 @@
#ifndef TIBIA_OBJECTS_H #ifndef TIBIA_OBJECTS_H
#define TIBIA_OBJECTS_H #define TIBIA_OBJECTS_H
#define SPRITE_TABLE_SIZE 500 #define MAX_SPRITES 500
#define THING_DATA_POOL_SIZE 256 #define THING_DATA_POOL_SIZE 256
#define ACTIVE_OBJECT_LIST_SIZE 256 #define ACTIVE_OBJECT_LIST_SIZE 256
#define SPRITE_SIZE 32
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL3/SDL_render.h>
#pragma pack(push, 1) #pragma pack(push, 1)
typedef struct { typedef struct {
@ -22,7 +25,7 @@ typedef struct {
typedef struct Objects { typedef struct Objects {
// TODO: not sure about the name/purpose of these. // TODO: not sure about the name/purpose of these.
void* spriteTable[SPRITE_TABLE_SIZE]; SDL_Texture* spriteTextures[MAX_SPRITES];
void* thingDataPool[THING_DATA_POOL_SIZE]; void* thingDataPool[THING_DATA_POOL_SIZE];
void* activeObjectList[ACTIVE_OBJECT_LIST_SIZE]; void* activeObjectList[ACTIVE_OBJECT_LIST_SIZE];
} Objects_t; } Objects_t;
@ -32,4 +35,7 @@ bool Objects_LoadData(Objects_t* objects);
bool Objects_LoadSprites(Objects_t* objects); bool Objects_LoadSprites(Objects_t* objects);
void Objects_Destroy(const Objects_t* objects); void Objects_Destroy(const Objects_t* objects);
SDL_Texture* Objects_GetSpriteTexture(const Objects_t* objects, uint16_t spriteID);
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, const uint8_t* rawData, uint16_t size);
#endif //TIBIA_OBJECTS_H #endif //TIBIA_OBJECTS_H

View File

@ -14,7 +14,7 @@ void Render_Frame(App_t* app) {
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
Render_MainWindowBackground(app); Render_MainWindowBackground(app);
Gui_Render(&app->gui, &app->network, &app->configParams); Gui_Render(&app->gui, &app->graphics, &app->network, &app->configParams);
Gui_FinishRender(); Gui_FinishRender();
SDL_RenderPresent(renderer); SDL_RenderPresent(renderer);