From ffd968a26826108ca9fb23d46ed5789260852eb7 Mon Sep 17 00:00:00 2001 From: Ritabrata Das Date: Wed, 9 Sep 2026 21:07:21 +0530 Subject: intial commit --- lib/CMakeLists.txt | 13 +++++ lib/assetManager.cpp | 60 +++++++++++++++++++++ lib/assetManager.hpp | 18 +++++++ lib/button.cpp | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/button.hpp | 32 +++++++++++ lib/dialogue.cpp | 94 ++++++++++++++++++++++++++++++++ lib/dialogue.hpp | 11 ++++ lib/gameState.hpp | 110 +++++++++++++++++++++++++++++++++++++ lib/standard.cpp | 93 ++++++++++++++++++++++++++++++++ lib/standard.hpp | 14 +++++ 10 files changed, 594 insertions(+) create mode 100644 lib/CMakeLists.txt create mode 100644 lib/assetManager.cpp create mode 100644 lib/assetManager.hpp create mode 100644 lib/button.cpp create mode 100644 lib/button.hpp create mode 100644 lib/dialogue.cpp create mode 100644 lib/dialogue.hpp create mode 100644 lib/gameState.hpp create mode 100644 lib/standard.cpp create mode 100644 lib/standard.hpp (limited to 'lib') diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..3758ed0 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,13 @@ +file(GLOB LIB_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cxx" +) + +file(GLOB LIB_HEADERS CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.hh" +) + +target_sources(cirno_day PRIVATE ${LIB_SOURCES} ${LIB_HEADERS}) diff --git a/lib/assetManager.cpp b/lib/assetManager.cpp new file mode 100644 index 0000000..9c60f9c --- /dev/null +++ b/lib/assetManager.cpp @@ -0,0 +1,60 @@ +#include "assetManager.hpp" + +#include +#include + +assetManager::assetManager() = default; + +assetManager::~assetManager() +{ + cleanup(); +} + +void assetManager::init(const std::string& dirPath) +{ + if (!std::filesystem::exists(dirPath) || !std::filesystem::is_directory(dirPath)) { + std::cerr << "assetmanager: directory not found: " << dirPath << std::endl; + return; + } + + for (const auto& entry : std::filesystem::recursive_directory_iterator(dirPath)) { + if (entry.is_regular_file()) { + std::string ext = entry.path().extension().string(); + + if (ext == ".png" || ext == ".jpg" || ext == ".jpeg") { + std::string key = entry.path().stem().string(); + std::string fullPath = entry.path().string(); + + Texture2D tex = LoadTexture(fullPath.c_str()); + + if (tex.id != 0) { + textures[key] = tex; + std::cout << "success loading texture: " << key << std::endl; + } else { + std::cerr << "failed to load texture: " << key << std::endl; + } + } + } + } +} + +Texture2D assetManager::load(const std::string& texName) +{ + const auto tex = textures.find(texName); + if (tex != textures.end()) { + return tex->second; + } + + std::cerr << "asset not found: " << texName << std::endl; + return Texture2D{0}; +} + +void assetManager::cleanup() +{ + for (auto& pair : textures) { + if (pair.second.id != 0) { + UnloadTexture(pair.second); + } + } + textures.clear(); +} diff --git a/lib/assetManager.hpp b/lib/assetManager.hpp new file mode 100644 index 0000000..7b15f5a --- /dev/null +++ b/lib/assetManager.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +class assetManager +{ +private: + std::unordered_map textures; +public: + assetManager(); + ~assetManager(); + + void init(const std::string& dirPath = "../assets"); + Texture2D load(const std::string& texName); + void cleanup(); +}; diff --git a/lib/button.cpp b/lib/button.cpp new file mode 100644 index 0000000..9000b09 --- /dev/null +++ b/lib/button.cpp @@ -0,0 +1,149 @@ +#include "button.hpp" +#include "raylib.h" +#include + +constexpr Color HOVER_COLOR = Color{140, 0, 23, 255}; +constexpr Color FILL_COLOR = Color{148, 35, 67, 255}; +constexpr Color BORDER_COLOR = Color{204, 0, 57, 255}; + +namespace ui { +namespace { + +constexpr const char* kUIFontPath = "../assets/font/font.otf"; +constexpr const char* kUIClickSfxPath = "../assets/audio/sfx/buttonClick.wav"; + +bool g_initialized = false; +bool g_loaded_font = false; +Font g_ui_font = {}; +Sound g_ui_clickSfx ; + +} // namespace + +Font GetActiveFont() { return g_loaded_font ? g_ui_font : GetFontDefault(); } + +void Init() { + if (g_initialized) return; + + if (FileExists(kUIFontPath)) { + g_ui_font = LoadFontEx(kUIFontPath, 256, nullptr, 0); + g_loaded_font = g_ui_font.texture.id != 0; + } + g_ui_clickSfx = LoadSound(kUIClickSfxPath); + + g_initialized = true; +} + +void Shutdown() { + if (!g_initialized) return; + + if (g_loaded_font) { + UnloadFont(g_ui_font); + g_loaded_font = false; + } + + g_initialized = false; +} + +void DrawText(const char* text, int pos_x, int pos_y, int font_size, + Color color) { + if (g_loaded_font) { + DrawTextEx(g_ui_font, text, {static_cast(pos_x), static_cast(pos_y)}, + static_cast(font_size), 1.0f, color); + return; + } + + ::DrawText(text, pos_x, pos_y, font_size, color); +} + +void DrawTextV(const char* text, Vector2 position, int font_size, + Color color) { + if (g_loaded_font) { + DrawTextEx(g_ui_font, text, {static_cast(position.x), static_cast(position.y)}, + static_cast(font_size), 1.0f, color); + return; + } + + ::DrawText(text, position.x, position.y, font_size, color); +} + +void DrawTextRotationV(const char* text, Vector2 position, int font_size, float rotInDeg, Color color) { + if (g_loaded_font) { + DrawTextPro(g_ui_font, text, position, {0,0},rotInDeg,font_size,1.0f,color); + return; + } + DrawTextPro(GetFontDefault(), text, position, {0,0},rotInDeg,font_size,1.0f,color); +} + +void DrawButton(float x, float y, float width, float height, const char* text, + int font_size, const ButtonCallback& on_click, Vector2 mousePos) { + Rectangle bounds = {x, y, width, height}; + const bool is_hovered = CheckCollisionPointRec(mousePos, bounds); + const Color fill_color = is_hovered ? HOVER_COLOR : FILL_COLOR; + + DrawRectangleRounded(bounds, 0.14f, 8, fill_color); + DrawRectangleRoundedLinesEx(bounds, 0.14f, 4, 4.0f, BORDER_COLOR); + + const Font font = GetActiveFont(); + const Vector2 text_size = MeasureTextEx(font, text, static_cast(font_size), 1.0f); + const float text_x = x + (width - text_size.x) * 0.5f; + const float text_y = y + (height - text_size.y) * 0.5f; + ui::DrawText(text, static_cast(text_x), static_cast(text_y), font_size, BLACK); + + if (is_hovered && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { + PlaySound(g_ui_clickSfx); + if (on_click) { + on_click(); + } + } +} + +void DrawButtonNoHover(float x, float y, float width, float height, const char* text, + int font_size, const ButtonCallback& on_click, Vector2 mousePos, Color fillColor) { + Rectangle bounds = {x, y, width, height}; + const bool is_hovered = CheckCollisionPointRec(mousePos, bounds); + + DrawRectangleRounded(bounds, 0.14f, 8, fillColor); + DrawRectangleRoundedLinesEx(bounds, 0.14f, 8, 2.0f, BORDER_COLOR); + + const Font font = GetActiveFont(); + const Vector2 text_size = MeasureTextEx(font, text, static_cast(font_size), 1.0f); + const float text_x = x + (width - text_size.x) * 0.5f; + const float text_y = y + (height - text_size.y) * 0.5f; + ui::DrawText(text, static_cast(text_x), static_cast(text_y), font_size, BLACK); + + if (is_hovered && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { + // auto& asset_manager = assets::AssetManager::Instance(); + // PlaySound(asset_manager.SoundEffect("switch")); + if (on_click) { + on_click(); + } + } +} + + +void DrawTextVClickable(const char* text, Vector2 position, int font_size, + const ButtonCallback& on_click, Vector2 mousePos) { + Vector2 text_size; + if (g_loaded_font) { + text_size = MeasureTextEx(g_ui_font, text, static_cast(font_size), 1.0f); + } else { + // Default Raylib font measurement + text_size.x = static_cast(MeasureText(text, font_size)); + text_size.y = static_cast(font_size); + } + Rectangle bounds = {position.x, position.y, text_size.x, text_size.y}; + const bool is_hovered = CheckCollisionPointRec(mousePos, bounds); + + // Apply hover colors + const Color text_color = is_hovered ? WHITE : YELLOW; + + ui::DrawTextV(text, position, font_size, text_color); + + if (is_hovered && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { + if (on_click) { + on_click(); + } + } +} + +} // namespace ui diff --git a/lib/button.hpp b/lib/button.hpp new file mode 100644 index 0000000..8459cdc --- /dev/null +++ b/lib/button.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "raylib.h" + +namespace ui { + +using ButtonCallback = std::function; + +void Init(); +void Shutdown(); + +void DrawText(const char* text, int pos_x, int pos_y, int font_size, + Color color); + +void DrawTextV(const char* text, Vector2 position, int font_size, + Color color); + +void DrawTextVClickable(const char* text, Vector2 position, int font_size, + const ButtonCallback& on_click, Vector2 mousePos); + +void DrawTextRotationV(const char* text, Vector2 position, int font_size, float rotInDeg, Color color); + +void DrawButton(float x, float y, float width, float height, const char* text, + int font_size, const ButtonCallback& on_click, Vector2 mousePos); + +void DrawButtonNoHover(float x, float y, float width, float height, const char* text, + int font_size, const ButtonCallback& on_click, Vector2 mousePos, Color fillColor = RAYWHITE); + +Font GetActiveFont(); +} // namespace ui diff --git a/lib/dialogue.cpp b/lib/dialogue.cpp new file mode 100644 index 0000000..3c9735b --- /dev/null +++ b/lib/dialogue.cpp @@ -0,0 +1,94 @@ +#include "dialogue.hpp" +#include "button.hpp" +#include "lib/gameState.hpp" +#include + +void SayDialog(GameState &gs, const std::vector &conversation) { + gs.currentDialog = conversation; + gs.currentLineIndex = 0; + gs.visibleChar = 0; + gs.dialogueTimer = 0.0f; + gs.dialogState = gs.currentDialog.empty() ? DIALOGUE_OFF : DIALOGUE_SCROLLING; +} + +// void wait_for(GameState &gs, float time) { +// gs.timeUntilStory = time; +// gs.inStory = false; +// gs.storyPosition += 1; +// } + +void UpdateDialog(GameState &gs) { + if (gs.dialogState == DIALOGUE_OFF || gs.currentDialog.empty()) { + return; + } + + if (gs.dialogState == DIALOGUE_SCROLLING) { + gs.dialogueTimer += GetFrameTime(); + const float speed = gs.currentDialog[gs.currentLineIndex].textSpeed; + if (gs.dialogueTimer >= speed) { + gs.dialogueTimer = 0.0f; + gs.visibleChar += 1; + + if (gs.visibleChar >= (int)(gs.currentDialog[gs.currentLineIndex].text.length())) { + gs.dialogState = DIALOGUE_WAITING; + } + } + + if (IsKeyPressed(KEY_Z) || IsKeyPressed(KEY_SPACE) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + gs.visibleChar = (int)(gs.currentDialog[gs.currentLineIndex].text.length()); + gs.dialogState = DIALOGUE_WAITING; + } + return; + } + + if (gs.dialogState == DIALOGUE_WAITING && (IsKeyPressed(KEY_Z)) || IsKeyPressed(KEY_SPACE) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + gs.currentLineIndex += 1; + gs.visibleChar = 0; + gs.dialogueTimer = 0.0f; + if (gs.currentLineIndex >= (int)(gs.currentDialog.size())) { + gs.dialogState = DIALOGUE_OFF; + gs.currentDialog.clear(); + } else { + gs.dialogState = DIALOGUE_SCROLLING; + } + } +} + +void DrawDialog(GameState &gs) { + gs.curExp = gs.currentDialog[gs.currentLineIndex].curExp; + if (gs.dialogState == DIALOGUE_OFF || gs.currentDialog.empty()) { + return; + } + if (gs.currentLineIndex < 0 || gs.currentLineIndex >= (int)(gs.currentDialog.size())) { + return; + } + + const int screenWidth = VIRTUAL_SCREEN_W; + const int screenHeight =VIRTUAL_SCREEN_H; + Dialogues ¤tLine = gs.currentDialog[gs.currentLineIndex]; + + const int padding = 10; + const int boxWidth = VIRTUAL_SCREEN_W-2*padding; + const int boxHeight = 90; + const int boxX = padding; + const int boxY = screenHeight - boxHeight - 30; + + DrawRectangle(boxX, boxY, boxWidth, boxHeight, Fade(BLACK, 0.8f)); + // DrawRectangleLinesEx({boxX, boxY, boxWidth, boxHeight}, 2, WHITE); + + // DrawTexturePro(currentLine.portrait, srcRect, dstRect, {0.0f, 0.0f}, 0.0f, WHITE); + // DrawRectangleLinesEx(dstRect, 2, RAYWHITE); + + // ui::DrawText(currentLine.characterName.c_str(), boxX + padding, boxY - 15, 18, YELLOW); + + std::string displayString = currentLine.text.substr(0, gs.visibleChar); + ui::DrawText(displayString.c_str(), boxX + padding, boxY + 15, 14, RAYWHITE); + + if (gs.dialogState == DIALOGUE_WAITING && ((int)(GetTime() * 3) % 2 == 0)) { + ui::DrawText(gs.touchScreenMode?"Touch anywhere to proceed":"Press 'Z' to proceed", boxX + padding, boxY + boxHeight - 2*padding, 8, WHITE); + } +} + +bool IsDialogRunning(const GameState &gs) { + return gs.dialogState == DIALOGUE_SCROLLING || gs.dialogState == DIALOGUE_WAITING; +} diff --git a/lib/dialogue.hpp b/lib/dialogue.hpp new file mode 100644 index 0000000..3b53aeb --- /dev/null +++ b/lib/dialogue.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include "gameState.hpp" + +void SayDialog(GameState &gs, const std::vector &conversation); +// void wait_for(GameState &gs, float time); +void UpdateDialog(GameState &gs); +void DrawDialog(GameState &gs); +bool IsDialogRunning(const GameState &gs); diff --git a/lib/gameState.hpp b/lib/gameState.hpp new file mode 100644 index 0000000..5c718f4 --- /dev/null +++ b/lib/gameState.hpp @@ -0,0 +1,110 @@ +#pragma once +#include +#include +#include +#include + +#include "assetManager.hpp" + +constexpr int MAX_USERNAME_LENGTH = 16; +constexpr int VIRTUAL_SCREEN_W = 225; +constexpr int VIRTUAL_SCREEN_H = 400; +constexpr float WEB_RENDER_SCALE = 2.0f; +constexpr Vector2 ZERO_VEC = {0,0}; + +inline Music g_bgMusic; + +enum DialogueState { + DIALOGUE_OFF, + DIALOGUE_SCROLLING, + DIALOGUE_WAITING, +}; + +enum screen { + MAIN_MENU, + GAMEPLAY, +}; + + +enum EXPRESSION { + NEUTRAL, + PISSED, + HURT, + JOY, + LAUGH, + RELAXED, + TIERED, + WOW, +}; + +struct Dialogues +{ + // Texture2D portrait; + std::string characterName; + EXPRESSION curExp; + std::string text; + // Sound scrollSound; + float textSpeed; +}; + +enum POSITION { + INSIDE, + OUTSIDE, + FOREST, + MENU +}; + + +struct GameState { + screen current_screen = MAIN_MENU; // TODO : Change before prod + int gameScore = 0; + int highScore = 0; + bool paused = false; + bool gameOver = false; + /* + * We use a cartesian system that is right handed + * right is posetive x and up is posetive y + * The origin is at the bottom left corner of the + * playable area. + * Max limit is 100, 100 (note that this scale is as percentage) + * x = y wont give a straight line with 45 degree angle + * ^ +y + * | + * | + * |_________ > +x + */ + + /* Dialogue specific */ + DialogueState dialogState = DIALOGUE_OFF; + std::vector currentDialog; + float dialogueTimer = 0.0f; + int currentLineIndex = 0; + int visibleChar = 0; + + short storyStage = 1; + + /* Asset Manager */ + assetManager AssetManager; + + float scale = 1.0f; + float renderScale = 1.0f; + Vector2 currentMousePos = {0,0}; + Font numberFont; + RenderTexture screenTex; + + bool touchScreenMode = false; + + POSITION curPos = MENU; + EXPRESSION curExp = NEUTRAL; + + float timeSinceSceneChange = -1; + POSITION prevPos = INSIDE; + + short flickCount = 0; + short chestCount = 0; + bool flowerOnHead = false; + bool riceCakeEaten = false; + bool frogDialogDone = false; + bool usedTalkButton = false; + std::set explored = {}; +}; diff --git a/lib/standard.cpp b/lib/standard.cpp new file mode 100644 index 0000000..35dfa9f --- /dev/null +++ b/lib/standard.cpp @@ -0,0 +1,93 @@ +#include "standard.hpp" +#include "gameState.hpp" +#include +#include +#include +#include + +constexpr float CARTESIAN_MIN = 0.0f; +constexpr float CARTESIAN_MAX = 100.0f; +constexpr float CARTESIAN_SCALE = 100.0f; + +float maxOfTwo(float a, float b) { + if (a > b) {return a;} + else {return b;} +} + +float minOfTwo(float a, float b) { + if (a < b) {return a;} + else {return b;} +} + +float genRandBw(float min,float max) { + return (((float)rand()/RAND_MAX)*(max-min))+min; +} + +Vector2 rotatePoint(Vector2 point, Vector2 pivot, float angleDegree) { + float rad = angleDegree * DEG2RAD; + float s = sinf(rad); + float c = cosf(rad); + point.x -= pivot.x; + point.y -= pivot.y; + float xNew = point.x * c - point.y * s; + float yNew = point.x * s + point.y * c; + return (Vector2){ xNew + pivot.x, yNew + pivot.y }; +} + +Vector2 C2SProj(Vector2 cartesian_cordinates, Vector4 rectangle_space, Vector2 object_size, + Vector2 absolute_deviation_bias,bool use_clamp) { + /*Cartesian to Screen Projection */ + float y_axis_length = rectangle_space.w - rectangle_space.y; + float x_axis_length = rectangle_space.z - rectangle_space.x; + float usable_width = maxOfTwo(0.0f, x_axis_length - object_size.x); + float usable_height = maxOfTwo(0.0f, y_axis_length - object_size.y); + float normalized_x; + float normalized_y; + if (use_clamp) { + normalized_x = Clamp(cartesian_cordinates.x, CARTESIAN_MIN, CARTESIAN_MAX)/CARTESIAN_SCALE; + normalized_y = Clamp(cartesian_cordinates.y, CARTESIAN_MIN, CARTESIAN_MAX)/CARTESIAN_SCALE; + } else { + normalized_x = cartesian_cordinates.x/CARTESIAN_SCALE; + normalized_y = cartesian_cordinates.y/CARTESIAN_SCALE; + } + float y_pos = (1 - normalized_y)*usable_height; + float x_pos = normalized_x*usable_width; + return {rectangle_space.x + x_pos + absolute_deviation_bias.x, + rectangle_space.y + y_pos + absolute_deviation_bias.y}; +} + +float S2CYaxis(float yValue, Vector4 rectangle_space) { + float y_axis_length = rectangle_space.w - rectangle_space.y; + return ((yValue/y_axis_length) * 100); +} + +float S2CXaxis(float xValue, Vector4 rectangle_space) { + float x_axis_length = rectangle_space.z - rectangle_space.x; + return ((xValue/x_axis_length)*100); +} + +void newGame(GameState& gs){ + gs.gameScore = 0; + gs.paused = false; + gs.gameOver = false; + gs.dialogState = DIALOGUE_OFF; + gs.dialogueTimer = 0.0f; + gs.currentLineIndex = 0; + gs.visibleChar = 0; +} + +bool returnTrueProbPerc(float percentageTrue){ + float i = genRandBw(0, 1); + if (i < (percentageTrue/100)) { + return true; + } + return false; +} + +Vector2 ScreenToVirtual(Vector2 screenPos, Vector2 offset, float scale) +{ + return Vector2{ + (screenPos.x - offset.x) / scale, + (screenPos.y - offset.y) / scale + }; +} diff --git a/lib/standard.hpp b/lib/standard.hpp new file mode 100644 index 0000000..dc2ec75 --- /dev/null +++ b/lib/standard.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "gameState.hpp" +#include + +float maxOfTwo(float a, float b); +float minOfTwo(float a, float b); +float genRandBw(float min, float max); +Vector2 rotatePoint(Vector2 point, Vector2 pivot, float angleDegree); +Vector2 C2SProj(Vector2 cartesian_cordinates, Vector4 rectangle_space, Vector2 object_size = {0, 0}, Vector2 absolute_deviation_bias={0,0},bool use_clamp = true); +float S2CYaxis(float yValue, Vector4 rectangle_space); +float S2CXaxis(float xValue, Vector4 rectangle_space); +void newGame(GameState& gs); +bool returnTrueProbPerc(float percentageTrue); +Vector2 ScreenToVirtual(Vector2 screenPos, Vector2 offset, float scale); -- cgit v1.2.3