Nimbin[12]?SDL & Graphics / mapcreator / src/character/message.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/character/message.cpp 14.8 KB · 424 lines raw
#include "character/message.hpp"

#include <iostream>

#include "defaults/ui.hpp"
#include "map/map.hpp"
#include "game/game.hpp"
#include "inventory/inventory.hpp"

#define TOP_RIGHT_ICON_BG 180, 180, 180

Messages::Messages() {
    messages.clear();
    messages_data = {
        {MessageKey::MOIN, {10, MessageType::CHARACTER, "I should collect some things.."}},
        {MessageKey::BUILDING_TOOL_REQUIRED, {5, MessageType::CHARACTER, "I need a building tool!"}},
        {MessageKey::PLAYER_EXHAUSTED,       {5, MessageType::CHARACTER, "I am exhausted..."}},
        {MessageKey::DISCOVERED_RESOURCE,    {5, MessageType::CHARACTER, "A new resource!"}},
        {MessageKey::DISCOVERED_VEGETABLE,   {5, MessageType::CHARACTER, "A new vegetable!"}},
        {MessageKey::DISCOVERED_BUILDING,    {5, MessageType::CHARACTER, "Maybe i can build something with it.."}},
        {MessageKey::THIRST_20,     {10, MessageType::CHARACTER, "I should drink something.."}},
        {MessageKey::THIRST_0,      {10, MessageType::CHARACTER, "No water no life.."}},
        {MessageKey::WATER_APPRECIATION, {5, MessageType::CHARACTER, "Hmmm water"}},
        {MessageKey::EAT_APPRECIATION,   {5, MessageType::CHARACTER, "Omnjomnjom"}},
        {MessageKey::CRAFTED_ITEM,  {10, MessageType::CHARACTER, "Whatever i can do with that.."}},
        {MessageKey::ADD_INVENTORY, {10, MessageType::INVENTORY, "Added item: "}},
        {MessageKey::ADD_RESOURCE,  {10, MessageType::RESOURCE,  "Resource "}},
        {MessageKey::ADD_VEGETABLE, {10, MessageType::VEGETABLE, "Vegetable "}},
        {MessageKey::ADD_VEGETABLE_SEED,   {10, MessageType::VEGETABLE_SEED, "Seed "}},
        {MessageKey::NOTHING_TO_INTERACT,  {4, MessageType::CHARACTER, "I cant find anything to interact with.."}},
    };
}

void Messages::Add(MessageKey key, void* type_data) {
    auto base = messages_data.find(key);
    if (base == messages_data.end())
        return;

    Message_s msg = base->second;

    msg.type_data = type_data;

    if (msg.message_type == MessageType::INVENTORY) {
        size_t* id_ptr = static_cast<size_t*>(msg.type_data);
        std::cout << "ADD ID: " << *id_ptr << std::endl;
        messages.emplace(key, msg);
        return;
    }

    if (msg.message_type == MessageType::RESOURCE) {
        ResourceType new_type = *static_cast<ResourceType*>(msg.type_data);
        auto range = messages.equal_range(key);
        for (auto it = range.first; it != range.second; ) {
            if (it->second.message_type == MessageType::RESOURCE) {
                ResourceType old_type = *static_cast<ResourceType*>(it->second.type_data);
                if (old_type == new_type) {
                    it = messages.erase(it);
                    continue;
                }
            }
            ++it;
        }

        messages.emplace(key, msg);
        return;
    }

    if (msg.message_type == MessageType::VEGETABLE) {
        ResourceType new_type = *static_cast<ResourceType*>(msg.type_data);
        auto range = messages.equal_range(key);
        for (auto it = range.first; it != range.second; ) {
            if (it->second.message_type == MessageType::VEGETABLE) {
                ResourceType old_type = *static_cast<ResourceType*>(it->second.type_data);
                if (old_type == new_type) {
                    it = messages.erase(it);
                    continue;
                }
            }
            ++it;
        }

        messages.emplace(key, msg);
        return;
    }

    auto range = messages.equal_range(key);
    for (auto it = range.first; it != range.second; ) {
        it = messages.erase(it);
    }

    messages.emplace(key, msg);
}

void
Messages::Update() {
    for (auto it = messages.begin(); it != messages.end(); ) {
        Message_s &msg = it->second;

        msg.duration -= Default::delta_time;

        if (msg.duration <= 0.0f) {
            /*
            if (msg.message_type == MessageType::VEGETABLE)
                delete static_cast<VegetableData::Type*>(msg.type_data);
            else
            */
            if (msg.message_type == MessageType::RESOURCE)
                delete static_cast<ResourceType*>(msg.type_data);
            else
            if (msg.message_type == MessageType::INVENTORY)
                delete static_cast<size_t*>(msg.type_data);
            else
            if (msg.message_type == MessageType::VEGETABLE)
                delete static_cast<ResourceType*>(msg.type_data);
            it = messages.erase(it);
            continue;
        }
        ++it;
    }
}


void
Messages::Render(SDL_Renderer* renderer, float center_x, float start_y) {
    if (!messages.empty() && Ui::font_tiny) {
        const float row_h = 14.0f;

        // Stable iteration order
        std::vector<MessageKey> keys;
        keys.reserve(messages.size());
        for (const auto &kv : messages)
            keys.push_back(kv.first);


        for (size_t i = 0; i < keys.size(); ) {

            MessageKey key = keys[i];
            auto it = messages.find(key);
            if (it == messages.end()) {
                ++i;
                continue;
            }

            Message_s &msg = it->second;

            if (msg.message_type == MessageType::INVENTORY ||
                msg.message_type == MessageType::RESOURCE  ||
                msg.message_type == MessageType::VEGETABLE) {
                ++i;
                continue;
            }


            // Render message
            SDL_Color col{255, 255, 255, 255};
            SDL_Surface* surf =
                TTF_RenderText_Blended(Ui::font_message, msg.message.c_str(), msg.message.length(), col);
            SDL_Surface* surf_bg =
                TTF_RenderText_Blended(Ui::font_message, msg.message.c_str(), msg.message.length(), {126,126,126,164});

            if (surf && surf_bg) {
                SDL_Texture* tex    = SDL_CreateTextureFromSurface(renderer, surf);
                SDL_Texture* tex_bg = SDL_CreateTextureFromSurface(renderer, surf_bg);
                if (tex && tex_bg) {


                    SDL_FRect dst{
                        Scale(center_x + Map::offset.x) - (surf->w * 0.5f),
                            Scale(start_y + Map::offset.y + 22.0f),
                            (float)surf->w,
                            (float)surf->h
                    };

                    SDL_FRect dst_bg = dst;
                    dst_bg.x -= 1;
                    dst_bg.y -= 1;

                    SDL_RenderTexture(renderer, tex, nullptr, &dst);
                    SDL_RenderTexture(renderer, tex_bg, nullptr, &dst_bg);
                    SDL_DestroyTexture(tex);
                    SDL_DestroyTexture(tex_bg);
                }
                SDL_DestroySurface(surf);
                SDL_DestroySurface(surf_bg);
            }

            start_y -= row_h;
            ++i;
        }
    }
}

void Messages::render_InventoryMessages(Game* game, Inventory* inventory) {
    if (messages.empty() || !Ui::font_menu)
        return;

    const float bar_padding     = 6.0f;
    const float bar_height_min  = 48.0f;
    const float icon_bg_size    = 48.0f;
    const float right_margin    = 10.0f;
    const float spacing         = 6.0f;

    // Collect inventory messages
    std::vector<const Message_s*> inv_msgs;
    inv_msgs.reserve(messages.size());

    for (auto &kv : messages) {
        if ( kv.second.message_type == MessageType::INVENTORY ||
             kv.second.message_type == MessageType::RESOURCE  ||
             kv.second.message_type == MessageType::VEGETABLE ||
             kv.second.message_type == MessageType::VEGETABLE_SEED)
            inv_msgs.push_back(&kv.second);
    }
    if (inv_msgs.empty())
        return;

    // Sort by remaining duration
    std::sort(inv_msgs.begin(), inv_msgs.end(),
            [](const Message_s* a, const Message_s* b){
            return a->duration > b->duration;
            });

    // Measure max text width
    float max_text_w = 0.0f;
    for (const Message_s* msg : inv_msgs) {
        std::string text ;
        if (msg->message_type == MessageType::INVENTORY) {
            size_t id = *static_cast<size_t*>(msg->type_data);
            // std::cout << "IDDD: " << id << " " << inventory->items.size() << " " << inventory->items.front()->id << std::endl;
            InventoryItem* item = inventory->get_ItemById(id);

            if (!item) continue;

            text = "Added item: " + Item::to_String(item->type);
        } else
        if (msg->message_type == MessageType::RESOURCE) {
            ResourceType* rt = static_cast<ResourceType*>(msg->type_data);
            ResourceType type = *rt;
            int amount = inventory->resources[type];
            text = std::to_string(amount);
        }
        else
        if (msg->message_type == MessageType::VEGETABLE) {
            VegetableData::Type* vtype = static_cast<VegetableData::Type*>(msg->type_data);
            auto it = inventory->vegetables.find(*vtype);
            int amount = 0;
            if (it != inventory->vegetables.end())
                amount = it->second.count;
            text = std::to_string(amount);
        }
        else
        if (msg->message_type == MessageType::VEGETABLE_SEED) {
            VegetableData::Type* vtype = static_cast<VegetableData::Type*>(msg->type_data);
            auto it = inventory->seeds.find(*vtype);
            int amount = 0;
            if (it != inventory->seeds.end())
                amount = it->second.count;
            text = std::to_string(amount);
        }

        SDL_Color col{255,255,255,255};
        SDL_Surface* surf = TTF_RenderText_Blended(Ui::font_menu, text.c_str(), text.length(), col);
        if (surf) {
            max_text_w = std::max(max_text_w, static_cast<float>(surf->w));
            SDL_DestroySurface(surf);
        }
    }

    float total_width = max_text_w + bar_padding * 2.0f + icon_bg_size + bar_padding;
    float bar_x = (float)game->win_size.w - total_width - right_margin;
    float y = 10.0f;

    // Render messages
    for (const Message_s* msg : inv_msgs) {
        InventoryItem* item        = nullptr;
        ResourceType* rtype        = nullptr;
        VegetableData::Type* vtype = nullptr;
        bool is_seed               = false;

        std::string text ;
        if (msg->message_type == MessageType::INVENTORY) {
            size_t id = *static_cast<size_t*>(msg->type_data);
            item = inventory->get_ItemById(id);
            if (!item) continue;

            text = "Added item: " + Item::to_String(item->type);
        } else
        if (msg->message_type == MessageType::RESOURCE) {
            rtype = static_cast<ResourceType*>(msg->type_data);
            int amount = inventory->resources[*rtype];
            text = std::to_string(amount);
        }
        else
        if (msg->message_type == MessageType::VEGETABLE) {
            vtype = static_cast<VegetableData::Type*>(msg->type_data);
            auto it = inventory->vegetables.find(*vtype);
            int amount = 0;
            if (it != inventory->vegetables.end())
                amount = it->second.count;
            text = std::to_string(amount);
        }
        else
        if (msg->message_type == MessageType::VEGETABLE_SEED) {
            is_seed = true;
            vtype = static_cast<VegetableData::Type*>(msg->type_data);
            auto it = inventory->seeds.find(*vtype);
            int amount = 0;
            if (it != inventory->seeds.end())
                amount = it->second.count;
            text = std::to_string(amount);
        }

        SDL_Color col{255,255,255,255};
        SDL_Surface* surf = TTF_RenderText_Blended(Ui::font_menu, text.c_str(), text.length(), col);
        if (!surf) continue;

        SDL_Texture* tex = SDL_CreateTextureFromSurface(game->renderer, surf);
        if (!tex) {
            SDL_DestroySurface(surf);
            continue;
        }

        float text_w = surf->w;
        float text_h = surf->h;
        float bar_h = std::max(text_h + bar_padding * 2.0f, bar_height_min);

        SDL_FRect bar_rect{bar_x, y, total_width, bar_h};

        // Bar background
        SDL_SetRenderDrawBlendMode(game->renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(game->renderer, 30, 30, 30, 170);
        SDL_RenderFillRect(game->renderer, &bar_rect);
        SDL_SetRenderDrawColor(game->renderer, 90, 90, 90, 210);
        SDL_RenderRect(game->renderer, &bar_rect);

        // Text
        SDL_FRect dst_text{
            bar_x + bar_padding,
            y + bar_padding + bar_height_min/2 - (text_h*0.75f),
            text_w,
            text_h
        };
        SDL_RenderTexture(game->renderer, tex, nullptr, &dst_text);

        SDL_DestroyTexture(tex);
        SDL_DestroySurface(surf);

        // Icon
        // Determine which payload we have


        // Now select the correct icon source
        bool draw_icon = false;
        SDL_FRect src;

        // ---- INVENTORY ITEM ----
        if (item && item->type != ItemType::NONE) {
            auto it = Icon::item_pos.find(item->type);
            if (it != Icon::item_pos.end()) {
                src = SDL_FRect{
                        it->second.x * 16.0f,
                        it->second.y * 16.0f,
                        16.0f,
                        16.0f
                };
                draw_icon = true;
            }
        }

        // ---- RESOURCE ----
        if (rtype && *rtype != ResourceType::NONE) {
            auto it = Icon::resource_pos.find(*rtype);
            if (it != Icon::resource_pos.end()) {
                src = SDL_FRect{
                        it->second.x * 16.0f,
                        it->second.y * 16.0f,
                        16.0f,
                        16.0f
                };
                draw_icon = true;
            }
        }

        // ---- VEGETABLE ----
        if (vtype && *vtype != VegetableData::Type::NONE) {
            SDL_Point pos = VegetableData::get_IconTilePosByType(*vtype, is_seed);
            src = SDL_FRect{
                    pos.x * 16.0f,
                    pos.y * 16.0f,
                    16.0f,
                    16.0f
            };
            draw_icon = true;
        }

        // ---- DRAW IF AVAILABLE ----
        if (draw_icon) {
            SDL_FRect icon_bg{
                bar_x + total_width - icon_bg_size - bar_padding,
                      y + (bar_h - icon_bg_size) * 0.5f,
                      icon_bg_size,
                      icon_bg_size
            };

            SDL_SetRenderDrawColor(game->renderer, TOP_RIGHT_ICON_BG, 180);
            SDL_RenderFillRect(game->renderer, &icon_bg);

            SDL_SetRenderDrawColor(game->renderer, 0, 0, 0, 255);
            SDL_RenderRect(game->renderer, &icon_bg);

            SDL_FRect dst{
                icon_bg.x,
                    icon_bg.y,
                    icon_bg_size,
                    icon_bg_size
            };

            SDL_RenderTexture(game->renderer, Icon::icons_texture, &src, &dst);
        }


        y += bar_h + spacing;
    }
}