feat: add new and continue game dialogs
This commit is contained in:
parent
eb5e6c6db9
commit
37ca39119f
@ -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
197
gui.c
@ -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
12
gui.h
@ -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);
|
||||||
|
|||||||
55
network.c
55
network.c
@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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
115
objects.c
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
10
objects.h
10
objects.h
@ -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
|
||||||
2
render.c
2
render.c
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user