Compare commits
1 Commits
main
...
wip/game-r
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bb961448d |
@ -5,7 +5,6 @@ set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
find_package(SDL3 CONFIG REQUIRED)
|
||||
find_package(SDL3_net REQUIRED)
|
||||
|
||||
set(IMGUI_DIR "thirdparty/imgui")
|
||||
set(BINDINGS_DIR "thirdparty/dear_bindings")
|
||||
@ -21,6 +20,8 @@ set(IMGUI_SOURCES
|
||||
${BINDINGS_DIR}/dcimgui.cpp
|
||||
${BINDINGS_DIR}/dcimgui_impl_sdl3.cpp
|
||||
${BINDINGS_DIR}/dcimgui_impl_sdlrenderer3.cpp
|
||||
gui.c
|
||||
gui.h
|
||||
)
|
||||
|
||||
add_executable(tibia
|
||||
@ -41,12 +42,7 @@ add_executable(tibia
|
||||
bitmap.c
|
||||
bitmap.h
|
||||
objects.c
|
||||
objects.h
|
||||
network.c
|
||||
network.h
|
||||
gui.c
|
||||
gui.h
|
||||
)
|
||||
objects.h)
|
||||
|
||||
target_include_directories(tibia PRIVATE
|
||||
${IMGUI_DIR}
|
||||
@ -54,4 +50,4 @@ target_include_directories(tibia PRIVATE
|
||||
${BINDINGS_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(tibia SDL3::SDL3 SDL3_net::SDL3_net)
|
||||
target_link_libraries(tibia SDL3::SDL3)
|
||||
|
||||
14
app.c
14
app.c
@ -2,6 +2,7 @@
|
||||
#include "window.h"
|
||||
#include "render.h"
|
||||
#include "input.h"
|
||||
#include "gui.h"
|
||||
|
||||
void App_Init(App_t *app) {
|
||||
app->isRunning = false;
|
||||
@ -12,8 +13,7 @@ void App_Init(App_t *app) {
|
||||
|
||||
System_QueryMetrics(&app->metrics);
|
||||
|
||||
// TODO: start this window maximized
|
||||
if (!Window_Init(800, 600)) {
|
||||
if (!Window_Init(app->metrics.screenWidth, app->metrics.screenHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -28,14 +28,13 @@ void App_Init(App_t *app) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Objects_Init(&app->graphics)) {
|
||||
if (!Objects_Init(&app->objects)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Network_Init(&app->network)) {
|
||||
return;
|
||||
}
|
||||
Map_LoadSampleData();
|
||||
|
||||
app->isInGame = true;
|
||||
app->isRunning = true;
|
||||
}
|
||||
|
||||
@ -47,8 +46,7 @@ void App_Run(App_t *app) {
|
||||
}
|
||||
|
||||
void App_Shutdown(const App_t* app) {
|
||||
Network_Shutdown(&app->network);
|
||||
Objects_Destroy(&app->graphics);
|
||||
Objects_Destroy(&app->objects);
|
||||
Map_Destroy(&app->map);
|
||||
Bitmap_Destroy(&app->bitmap);
|
||||
Gui_Shutdown();
|
||||
|
||||
5
app.h
5
app.h
@ -9,17 +9,16 @@
|
||||
#include "objects.h"
|
||||
#include "map.h"
|
||||
#include "system.h"
|
||||
#include "network.h"
|
||||
|
||||
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;
|
||||
Network_t network;
|
||||
} App_t;
|
||||
|
||||
void App_Init(App_t* app);
|
||||
|
||||
448
gui.c
448
gui.c
@ -5,8 +5,6 @@
|
||||
#include "gui.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "dcimgui.h"
|
||||
#include "dcimgui_impl_sdl3.h"
|
||||
#include "dcimgui_impl_sdlrenderer3.h"
|
||||
@ -14,14 +12,6 @@
|
||||
#include "window.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) {
|
||||
ImGui_CreateContext(NULL);
|
||||
|
||||
@ -37,9 +27,6 @@ void Gui_Init(Gui_t *gui) {
|
||||
cImGui_ImplSDLRenderer3_Init(renderer);
|
||||
|
||||
gui->isPreferencesDialogOpen = false;
|
||||
gui->isNewGameDialogOpen = false;
|
||||
gui->isJourneyOnwardDialogOpen = false;
|
||||
Gui_UpdateStatusBar(gui, "Welcome to Tibia!");
|
||||
}
|
||||
|
||||
void Gui_ProcessEvent(const SDL_Event *event) {
|
||||
@ -52,13 +39,9 @@ void Gui_StartRender() {
|
||||
ImGui_NewFrame();
|
||||
}
|
||||
|
||||
void Gui_Render(Gui_t *gui, const Objects_t *objects, Network_t *network, ConfigParams_t *configParams) {
|
||||
Gui_RenderMainMenu(gui);
|
||||
Gui_RenderStatusBar(gui);
|
||||
|
||||
void Gui_Render(Gui_t *gui, ConfigParams_t *configParams, const bool inGame) {
|
||||
Gui_RenderMainMenu(gui, inGame);
|
||||
Gui_RenderPreferences_Dialog(gui, configParams);
|
||||
Gui_RenderNewGame_Dialog(gui, objects, configParams, network);
|
||||
Gui_RenderJourneyOnward_Dialog(gui, configParams, network);
|
||||
}
|
||||
|
||||
void Gui_FinishRender() {
|
||||
@ -72,10 +55,9 @@ void Gui_Shutdown() {
|
||||
cImGui_ImplSDLRenderer3_Shutdown();
|
||||
cImGui_ImplSDL3_Shutdown();
|
||||
ImGui_DestroyContext(NULL);
|
||||
SDL_DestroyTexture(cachedCharPreview);
|
||||
}
|
||||
|
||||
void Gui_RenderMainMenu(Gui_t *gui) {
|
||||
void Gui_RenderMainMenu(Gui_t *gui, const bool inGame) {
|
||||
if (!ImGui_BeginMainMenuBar()) {
|
||||
return;
|
||||
}
|
||||
@ -95,27 +77,25 @@ void Gui_RenderMainMenu(Gui_t *gui) {
|
||||
|
||||
if (ImGui_BeginMenu("Game")) {
|
||||
if (ImGui_MenuItem("New Game")) {
|
||||
gui->isNewGameDialogOpen = true;
|
||||
}
|
||||
|
||||
if (ImGui_MenuItem("Journey Onward")) {
|
||||
gui->isJourneyOnwardDialogOpen = true;
|
||||
}
|
||||
|
||||
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();
|
||||
@ -142,39 +122,6 @@ void Gui_RenderMainMenu(Gui_t *gui) {
|
||||
ImGui_EndMainMenuBar();
|
||||
}
|
||||
|
||||
void Gui_RenderStatusBar(const Gui_t *gui) {
|
||||
const ImGuiViewport *viewport = ImGui_GetMainViewport();
|
||||
|
||||
const float height = ImGui_GetFrameHeight();
|
||||
const ImVec2 position = {viewport->Pos.x, viewport->Pos.y + viewport->Size.y - height};
|
||||
const ImVec2 size = {viewport->Size.x, height};
|
||||
|
||||
ImGui_SetNextWindowPosEx(position, ImGuiCond_Always, (ImVec2){0.0f, 0.0f});
|
||||
ImGui_SetNextWindowSize(size, ImGuiCond_Always);
|
||||
|
||||
// Remove rounding, borders and reduce padding
|
||||
ImGui_PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui_PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui_PushStyleVarImVec2(ImGuiStyleVar_WindowPadding, (ImVec2){8.0f, 2.0f});
|
||||
|
||||
// Set background and text colors
|
||||
ImGui_PushStyleColorImVec4(ImGuiCol_WindowBg, (ImVec4){0.85f, 0.85f, 0.85f, 1.0f});
|
||||
ImGui_PushStyleColorImVec4(ImGuiCol_Text, (ImVec4){0.0f, 0.0f, 0.0f, 1.0f});
|
||||
|
||||
const ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNav;
|
||||
|
||||
if (ImGui_Begin("StatusBar", NULL, windowFlags)) {
|
||||
ImGui_Text(gui->statusBarText);
|
||||
}
|
||||
|
||||
ImGui_End();
|
||||
|
||||
ImGui_PopStyleColorEx(2);
|
||||
ImGui_PopStyleVarEx(3);
|
||||
}
|
||||
|
||||
void Gui_RenderPreferences_Dialog(Gui_t *gui, ConfigParams_t *configParams) {
|
||||
if (!gui->isPreferencesDialogOpen) {
|
||||
return;
|
||||
@ -209,7 +156,7 @@ void Gui_RenderPreferences_Dialog(Gui_t *gui, ConfigParams_t *configParams) {
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##Address", configParams->serverAddress, IM_ARRAYSIZE(configParams->serverAddress), 0);
|
||||
|
||||
// Server Port
|
||||
// Server Address
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
@ -284,382 +231,3 @@ void Gui_RenderPreferences_Dialog(Gui_t *gui, ConfigParams_t *configParams) {
|
||||
|
||||
ImGui_End();
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
static int sex = 1;
|
||||
static char realName[50] = "";
|
||||
static char email[50] = "";
|
||||
static char location[50] = "";
|
||||
|
||||
const ImGuiViewport *viewport = ImGui_GetMainViewport();
|
||||
const ImVec2 viewportCenter = {
|
||||
viewport->Pos.x + viewport->Size.x * 0.5f,
|
||||
viewport->Pos.y + viewport->Size.y * 0.5f
|
||||
};
|
||||
ImGui_SetNextWindowPosEx(viewportCenter, ImGuiCond_Appearing, (ImVec2){0.5f, 0.5f});
|
||||
ImGui_SetNextWindowSize((ImVec2){550, 0}, ImGuiCond_Always);
|
||||
|
||||
if (ImGui_Begin("New Game", &gui->isNewGameDialogOpen,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) {
|
||||
if (ImGui_BeginTable("TopLayout", 2, ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui_TableSetupColumnEx("TopInputs", ImGuiTableColumnFlags_WidthStretch, 0.0f, 0);
|
||||
ImGui_TableSetupColumnEx("TopButtons", ImGuiTableColumnFlags_WidthFixed, 140.0f, 0);
|
||||
|
||||
ImGui_TableNextRow();
|
||||
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
if (ImGui_BeginTable("TopForm", 2, ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui_TableSetupColumnEx("Label", ImGuiTableColumnFlags_WidthFixed, 90.0f, 0);
|
||||
ImGui_TableSetupColumn("Input", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
// Real Name
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Real Name:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##RealName", realName, sizeof(realName), 0);
|
||||
|
||||
// Location
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Location:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##Location", location, sizeof(location), 0);
|
||||
|
||||
// E-Mail
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("E-Mail:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##Email", email, sizeof(email), 0);
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
|
||||
// "Create Character"
|
||||
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_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
|
||||
|
||||
// "Oops, Cancel"
|
||||
if (ImGui_ButtonEx("Oops, Cancel", (ImVec2){-FLT_MIN, 0})) {
|
||||
gui->isNewGameDialogOpen = false;
|
||||
}
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
|
||||
ImGui_Spacing();
|
||||
ImGui_Separator();
|
||||
ImGui_Spacing();
|
||||
|
||||
if (ImGui_BeginTable("BottomLayout", 2, ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui_TableSetupColumnEx("BotInputs", ImGuiTableColumnFlags_WidthStretch, 0.0f, 0);
|
||||
ImGui_TableSetupColumnEx("BotVisuals", ImGuiTableColumnFlags_WidthFixed, 180.0f, 0);
|
||||
|
||||
ImGui_TableNextRow();
|
||||
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
if (ImGui_BeginTable("BotForm", 2, ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui_TableSetupColumnEx("Label", ImGuiTableColumnFlags_WidthFixed, 130.0f, 0);
|
||||
ImGui_TableSetupColumn("Input", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
// Your Name
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Your Name:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
static char charName[64] = "";
|
||||
ImGui_InputText("##CharName", charName, sizeof(charName), 0);
|
||||
|
||||
// Your Sex (Radio Buttons)
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Your Sex:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_RadioButtonIntPtr("male", &sex, 1);
|
||||
ImGui_SameLine();
|
||||
ImGui_RadioButtonIntPtr("female", &sex, 0);
|
||||
|
||||
// Enter Password
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Enter Password:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
static char pass1[64] = "";
|
||||
ImGui_InputText("##Pass1", pass1, sizeof(pass1), ImGuiInputTextFlags_Password);
|
||||
|
||||
// Retype Password
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Retype Password:");
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
static char pass2[64] = "";
|
||||
ImGui_InputText("##Pass2", pass2, sizeof(pass2), ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
|
||||
Gui_RenderCharacterCustomizer(objects);
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
}
|
||||
ImGui_End();
|
||||
}
|
||||
|
||||
void Gui_RenderJourneyOnward_Dialog(Gui_t *gui, ConfigParams_t *configParams, Network_t *network) {
|
||||
if (!gui->isJourneyOnwardDialogOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiViewport *viewport = ImGui_GetMainViewport();
|
||||
const ImVec2 viewportCenter = {
|
||||
viewport->Pos.x + viewport->Size.x * 0.5f,
|
||||
viewport->Pos.y + viewport->Size.y * 0.5f
|
||||
};
|
||||
ImGui_SetNextWindowPosEx(viewportCenter, ImGuiCond_Appearing, (ImVec2){0.5f, 0.5f});
|
||||
ImGui_SetNextWindowSize((ImVec2){380, 0}, ImGuiCond_Always);
|
||||
|
||||
if (ImGui_Begin("Journey Onward", &gui->isJourneyOnwardDialogOpen,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) {
|
||||
if (ImGui_BeginTable("Split", 2, ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui_TableSetupColumnEx("Data", ImGuiTableColumnFlags_WidthStretch, 0.0f, 0);
|
||||
ImGui_TableSetupColumnEx("Actions", ImGuiTableColumnFlags_WidthFixed, 100.0f, 0);
|
||||
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
|
||||
ImGui_Text("Player Data:");
|
||||
|
||||
if (ImGui_BeginTable("InputTable", 2, ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui_TableSetupColumnEx("Label", ImGuiTableColumnFlags_WidthFixed, 110.0f, 0);
|
||||
ImGui_TableSetupColumn("Input", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
// Player Name
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Player Name:");
|
||||
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##PName", configParams->lastAccount, IM_ARRAYSIZE(configParams->lastAccount), 0);
|
||||
|
||||
// Player Password
|
||||
ImGui_TableNextRow();
|
||||
ImGui_TableSetColumnIndex(0);
|
||||
ImGui_AlignTextToFramePadding();
|
||||
ImGui_Text("Player Password:");
|
||||
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
ImGui_SetNextItemWidth(-FLT_MIN);
|
||||
ImGui_InputText("##PPass", configParams->lastPassword, IM_ARRAYSIZE(configParams->lastPassword),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
|
||||
ImGui_TableSetColumnIndex(1);
|
||||
|
||||
// Cancel
|
||||
if (ImGui_ButtonEx("Cancel", (ImVec2){-FLT_MIN, 0})) {
|
||||
gui->isJourneyOnwardDialogOpen = false;
|
||||
}
|
||||
|
||||
ImGui_Dummy((ImVec2){0.0f, 10.0f});
|
||||
|
||||
// Let's Go
|
||||
if (ImGui_ButtonEx("Let's Go", (ImVec2){-FLT_MIN, 0})) {
|
||||
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_SendPacket(network, HANDLER_LOGIN_OR_CREATE_CHAR, 0x01, buffer, 60);
|
||||
}
|
||||
|
||||
ImGui_EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui_End();
|
||||
}
|
||||
|
||||
void Gui_UpdateStatusBar(Gui_t *gui, const char *message) {
|
||||
strncpy(gui->statusBarText, message, sizeof(gui->statusBarText) - 1);
|
||||
gui->statusBarText[sizeof(gui->statusBarText) - 1] = '\0';
|
||||
}
|
||||
|
||||
22
gui.h
22
gui.h
@ -8,37 +8,19 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "config.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 {
|
||||
bool isPreferencesDialogOpen;
|
||||
bool isNewGameDialogOpen;
|
||||
bool isJourneyOnwardDialogOpen;
|
||||
char statusBarText[255];
|
||||
} Gui_t;
|
||||
|
||||
void Gui_Init(Gui_t* gui);
|
||||
void Gui_ProcessEvent(const SDL_Event* event);
|
||||
void Gui_StartRender();
|
||||
void Gui_Render(Gui_t* gui, const Objects_t* objects, Network_t* network, 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_RenderStatusBar(const Gui_t* gui);
|
||||
|
||||
void Gui_RenderMainMenu(Gui_t* gui, bool inGame);
|
||||
void Gui_RenderPreferences_Dialog(Gui_t* gui, ConfigParams_t* configParams);
|
||||
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_UpdateStatusBar(Gui_t* gui, const char* message);
|
||||
|
||||
#endif //TIBIA_GUI_H
|
||||
140
network.c
140
network.c
@ -1,140 +0,0 @@
|
||||
//
|
||||
// Created by rov on 12/27/25.
|
||||
//
|
||||
|
||||
#include "network.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_net/SDL_net.h>
|
||||
|
||||
#define NET_TIMEOUT 30000
|
||||
|
||||
typedef enum ConnectionResult {
|
||||
CONN_SUCCESS = 0,
|
||||
CONN_ERR_BAD_HOSTNAME = 1,
|
||||
CONN_ERR_NO_SERVER = 2
|
||||
} CONNECTION_RESULT;
|
||||
|
||||
void Gui_UpdateStatusBar(const Gui_t* gui, const char *message);
|
||||
|
||||
bool Network_Init(Network_t *network) {
|
||||
if (!NET_Init()) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Failed to initialize SDL_net.", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
network->socket = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Network_Shutdown(const Network_t *network) {
|
||||
NET_DestroyStreamSocket(network->socket);
|
||||
NET_Quit();
|
||||
}
|
||||
|
||||
CONNECTION_RESULT ConnectToServer(Network_t *network, const ConfigParams_t *configParams, const Gui_t *gui) {
|
||||
Gui_UpdateStatusBar(gui, "Searching for hostname...");
|
||||
|
||||
NET_Address *netAddress = NET_ResolveHostname(configParams->serverAddress);
|
||||
const NET_Status resolveHostnameStatus = NET_WaitUntilResolved(netAddress, NET_TIMEOUT);
|
||||
if (resolveHostnameStatus != NET_SUCCESS) {
|
||||
NET_UnrefAddress(netAddress);
|
||||
return CONN_ERR_BAD_HOSTNAME;
|
||||
}
|
||||
|
||||
Gui_UpdateStatusBar(gui, "Trying to connect...");
|
||||
|
||||
network->socket = NET_CreateClient(netAddress, configParams->serverPort);
|
||||
const NET_Status createClientStatus = NET_WaitUntilConnected(network->socket, NET_TIMEOUT);
|
||||
if (createClientStatus != NET_SUCCESS) {
|
||||
NET_DestroyStreamSocket(network->socket);
|
||||
network->socket = NULL;
|
||||
|
||||
NET_UnrefAddress(netAddress);
|
||||
return CONN_ERR_NO_SERVER;
|
||||
}
|
||||
|
||||
// TODO: is this okay?
|
||||
NET_UnrefAddress(netAddress);
|
||||
|
||||
return CONN_SUCCESS;
|
||||
}
|
||||
|
||||
void Network_ConnectAndReportStatus(Network_t *network, const ConfigParams_t *configParams, const Gui_t *gui) {
|
||||
if (network->socket != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CONNECTION_RESULT result = ConnectToServer(network, configParams, gui);
|
||||
const char *message = NULL;
|
||||
|
||||
switch (result) {
|
||||
case CONN_SUCCESS:
|
||||
message = "Connection established.";
|
||||
break;
|
||||
case CONN_ERR_BAD_HOSTNAME:
|
||||
message = "Error: IP address or host not found.";
|
||||
break;
|
||||
case CONN_ERR_NO_SERVER:
|
||||
message = "Error: No server running on host.";
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
29
network.h
29
network.h
@ -1,29 +0,0 @@
|
||||
//
|
||||
// Created by rov on 12/27/25.
|
||||
//
|
||||
|
||||
#ifndef TIBIA_NETWORK_H
|
||||
#define TIBIA_NETWORK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL3_net/SDL_net.h>
|
||||
#include "config.h"
|
||||
|
||||
typedef struct Gui Gui_t;
|
||||
|
||||
typedef struct Network {
|
||||
NET_StreamSocket* socket;
|
||||
} Network_t;
|
||||
|
||||
typedef enum PacketHandler {
|
||||
HANDLER_LOGIN_OR_CREATE_CHAR = 0
|
||||
} PACKET_HANDLER;
|
||||
|
||||
bool Network_Init(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_SendPacket(const Network_t* network, PACKET_HANDLER handler, uint16_t opcode, void* data, int dataLength);
|
||||
|
||||
#endif //TIBIA_NETWORK_H
|
||||
103
objects.c
103
objects.c
@ -3,15 +3,15 @@
|
||||
//
|
||||
|
||||
#include "objects.h"
|
||||
#include "window.h"
|
||||
|
||||
#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));
|
||||
|
||||
for (int i = 0; i < THING_DATA_POOL_SIZE; i++) {
|
||||
objects->thingDataPool[i] = malloc(1536);
|
||||
|
||||
@ -21,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");
|
||||
|
||||
@ -113,15 +114,14 @@ 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();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites.");
|
||||
|
||||
// Free existing sprites to prevent memory leaks
|
||||
for (int i = 0; i < MAX_SPRITES; i++) {
|
||||
if (objects->spriteTextures[i] != NULL) {
|
||||
SDL_DestroyTexture(objects->spriteTextures[i]);
|
||||
objects->spriteTextures[i] = NULL;
|
||||
for (int i = 0; i < SPRITE_TABLE_SIZE; i++) {
|
||||
if (objects->spriteTable[i] != NULL) {
|
||||
free(objects->spriteTable[i]);
|
||||
objects->spriteTable[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,34 +157,39 @@ bool Objects_LoadSprites(Objects_t *objects) {
|
||||
}
|
||||
|
||||
// Ensure ID is within bounds
|
||||
if (spriteID >= MAX_SPRITES) {
|
||||
if (spriteID >= SPRITE_TABLE_SIZE) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: Sprite ID %d exceeds table size (%d). Skipping.",
|
||||
spriteID, MAX_SPRITES);
|
||||
spriteID, SPRITE_TABLE_SIZE);
|
||||
|
||||
// Skip the data bytes so we don't desync the file stream
|
||||
SDL_SeekIO(file, spriteSize - 2, SDL_IO_SEEK_CUR);
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint16_t bytesToRead = spriteSize - 2;
|
||||
if (bytesToRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t *spriteBuffer = malloc(spriteSize);
|
||||
if (!spriteBuffer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: cannot allocate memory");
|
||||
break;
|
||||
}
|
||||
|
||||
if (SDL_ReadIO(file, spriteBuffer, bytesToRead) != bytesToRead) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read full sprite data");
|
||||
break;
|
||||
objects->spriteTable[spriteID] = spriteBuffer;
|
||||
|
||||
// Store size in the first 2 bytes of the buffer
|
||||
*(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);
|
||||
objects->spriteTable[spriteID] = spriteBuffer;
|
||||
objects->activeTextures[spriteID] = Objects_TextureFromRaw(renderer, spriteBuffer);
|
||||
|
||||
free(spriteBuffer);
|
||||
spritesLoaded++;
|
||||
}
|
||||
|
||||
@ -199,76 +204,70 @@ void Objects_Destroy(const Objects_t *objects) {
|
||||
free(objects->thingDataPool[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_SPRITES; i++) {
|
||||
SDL_DestroyTexture(objects->spriteTextures[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 < 0 || spriteID >= MAX_SPRITES) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_GetSpriteTexture: sprite ID out of range (ID: %d).", spriteID);
|
||||
SDL_Texture* Objects_GetSpriteTexture(const Objects_t *objects, const uint16_t spriteID) {
|
||||
if (spriteID >= SPRITE_TABLE_SIZE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return objects->spriteTextures[spriteID];
|
||||
return objects->activeTextures[spriteID];
|
||||
}
|
||||
|
||||
SDL_Texture * Objects_TextureFromRaw(SDL_Renderer *renderer, const uint8_t *rawData, const uint16_t size) {
|
||||
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, uint8_t* rawData) {
|
||||
if (!rawData) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Surface* surface = SDL_CreateSurface(SPRITE_SIZE, SPRITE_SIZE, SDL_PIXELFORMAT_ARGB8888);
|
||||
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) {
|
||||
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);
|
||||
|
||||
memset(pixels, 0, 32 * 32 * 4);
|
||||
|
||||
int currentPixel = 0;
|
||||
int readOffset = 0;
|
||||
while (readOffset < size && currentPixel < 1024) {
|
||||
const uint16_t transparentRaw = *(uint16_t*) (rawData + readOffset);
|
||||
while (readOffset < totalSize - 2 && currentPixel < 1024) {
|
||||
const uint16_t transparentRaw = *(uint16_t*)(data + readOffset);
|
||||
readOffset += 2;
|
||||
|
||||
const uint16_t transparentBytes = transparentRaw % 0x5A0;
|
||||
const uint16_t skipPixels = transparentBytes / 3;
|
||||
const int skipPixels = transparentBytes / 3;
|
||||
|
||||
currentPixel += skipPixels;
|
||||
|
||||
const uint16_t colorBytes = *(uint16_t*)(rawData + readOffset);
|
||||
const uint16_t colorBytes = *(uint16_t*)(data + readOffset);
|
||||
readOffset += 2;
|
||||
|
||||
const uint16_t drawPixels = colorBytes / 3;
|
||||
const int 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++];
|
||||
const uint8_t r = data[readOffset++];
|
||||
const uint8_t g = data[readOffset++];
|
||||
const uint8_t b = data[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++;
|
||||
pixels[currentPixel++] = SDL_MapRGBA(SDL_GetPixelFormatDetails(surface->format), NULL, r, g, b, 255);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockSurface(surface);
|
||||
|
||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
SDL_DestroySurface(surface);
|
||||
|
||||
return texture;
|
||||
|
||||
12
objects.h
12
objects.h
@ -5,12 +5,10 @@
|
||||
#ifndef TIBIA_OBJECTS_H
|
||||
#define TIBIA_OBJECTS_H
|
||||
|
||||
#define MAX_SPRITES 500
|
||||
#define SPRITE_TABLE_SIZE 500
|
||||
#define THING_DATA_POOL_SIZE 256
|
||||
#define ACTIVE_OBJECT_LIST_SIZE 256
|
||||
|
||||
#define SPRITE_SIZE 32
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
@ -25,17 +23,19 @@ typedef struct {
|
||||
|
||||
typedef struct Objects {
|
||||
// TODO: not sure about the name/purpose of these.
|
||||
SDL_Texture* spriteTextures[MAX_SPRITES];
|
||||
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);
|
||||
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, const uint8_t* rawData, uint16_t size);
|
||||
|
||||
// TODO: move this somewhere else
|
||||
SDL_Texture* Objects_TextureFromRaw(SDL_Renderer* renderer, uint8_t* rawData);
|
||||
|
||||
#endif //TIBIA_OBJECTS_H
|
||||
104
render.c
104
render.c
@ -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->graphics, &app->network, &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
render.h
6
render.h
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user