#include #include "systems.h" #include "../game_state.h" #include "../input.h" #include "../map_init.h" #include "../ui_widgets.h" #include "../building_factory.h" #include "../utils.h" #include "../entity_factory.h" void drawGameUI(Game *game, f32 dt) { // UI i32 width = GetScreenWidth(); i32 height = GetScreenHeight(); bzUIBegin(UI, width, height); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_SPACE_BETWEEN | BZ_UI_FLEX_ALIGN_CENTER }); // top bar BzUINode *topBar = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_AS_PARENT}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_MAX}, }); bzUIPushParent(UI, topBar); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_SPACE_BETWEEN }); Color topBarBG = {0, 0, 0, 50}; bzUISetBackgroundStyle(UI, topBar, (BzUIBackgroundStyle) { .normal = topBarBG, .hover = topBarBG, .active = topBarBG, }); BzUINode *topBarLeft = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_SUM}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_MAX}, }); bzUIPushParent(UI, topBarLeft); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); BzTileset *tileset = &game->tileset; Rectangle woodRec = bzTilesetGetTileRegion(tileset, getEntityTile(ENTITY_WOOD)); Rectangle foodRec = bzTilesetGetTileRegion(tileset, getEntityTile(ENTITY_APPLE)); Rectangle goldRec = bzTilesetGetTileRegion(tileset, getEntityTile(ENTITY_GOLD)); Rectangle popRec = bzTilesetGetTileRegion(tileset, getEntityTile(ENTITY_POP)); PlayerResources resources = game->playerResources[game->player]; uiGameResCount(resources.wood, -1, woodRec, tileset->tiles); uiGameResCount(resources.food, -1, foodRec, tileset->tiles); uiGameResCount(resources.gold, -1, goldRec, tileset->tiles); uiGameResCount(resources.pop, resources.popCapacity, popRec, tileset->tiles); bzUIPopParent(UI); // topBarLeft BzUINode *topBarRight = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_SUM}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_MAX}, }); bzUIPushParent(UI, topBarRight); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); char waveDisplay[32]; if (game->waveInfo.started) { snprintf(waveDisplay, sizeof(waveDisplay), "Wave: %d ", game->waveInfo.number); } else { f32 startingIn = game->waveInfo.data.timeBeforeStart - game->waveInfo.elapsed; i32 min = startingIn / 60.0f; i32 sec = startingIn - (min * 60); snprintf(waveDisplay, sizeof(waveDisplay), "Wave %d starting in: %02d:%02d ", game->waveInfo.number, min, sec); } uiBaseLabel(waveDisplay, game->font, 0.5f, WHITE); bzUIPopParent(UI); // topBarRight bzUIPopParent(UI); // topBar InputState *input = ecs_singleton_get_mut(ECS, InputState); const BuildingType buildingOrder[] = { BUILDING_WHEAT_0, BUILDING_HOUSE_05, BUILDING_GRANARY, BUILDING_WAREHOUSE, BUILDING_MARKET, BUILDING_BARRACKS, BUILDING_TOWER }; const char *buildingNames[] = { "Farm", "House", "Granary", "Warehouse", "Market", "Barracks", "Tower" }; i32 numBuildings = sizeof(buildingOrder) / sizeof(*buildingOrder); BzUINode *menu = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_REL_PARENT, 0.98f}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, 140.0f * uiGetScale()}, .margin[BZ_UI_AXIS_Y + 2] = 10.0f * uiGetScale(), }); bzUISetBackgroundStyle(UI, menu, (BzUIBackgroundStyle) { .normal = DARKBROWN, .hover = DARKBROWN, .active = DARKBROWN, .roundness = 0.2f * uiGetScale() }); bzUISetBorderStyle(UI, menu, (BzUIBorderStyle) { .roundness = 0.2f * uiGetScale(), .thickness = 6.0f * uiGetScale(), .normal = BLACK, .hover = BLACK, .active = BLACK, }); bzUIPushParent(UI, menu); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); Texture2D tex = tileset->tiles; switch (input->state) { case INPUT_NONE: case INPUT_BUILDING: { for (i32 i = 0; i < numBuildings; i++) { BuildingType buildingType = buildingOrder[i]; Rectangle rec = bzTilesetGetTileRegion(tileset, getBuildingTile(buildingType)); // Adjust for size i32 sizeX = 1, sizeY = 1; getBuildingSize(buildingType, &sizeX, &sizeY); rec.width *= sizeX; rec.height *= sizeY; bool selected = input->building == buildingOrder[i]; PlayerResources *res = &game->playerResources[game->player]; bool canAfford = canAffordBuilding(buildingType, *res); bool hovered = false; uiGameBuild(buildingNames[i], rec, tex, canAfford, &selected, &hovered); if (hovered) { input->toolTipCost.active = true; i32 costs[RES_COUNT] = {0}; getBuildingCost(buildingType, costs); bzMemCpy(input->toolTipCost.costs, costs, sizeof(costs)); } if (!canAfford) selected = false; if (selected) { input->building = buildingOrder[i]; input->state = INPUT_BUILDING; } } break; } case INPUT_SELECTED_UNITS: { typedef struct UnitPair { i32 count; Rectangle rec; } UnitPair; UnitPair unitTypes[ENTITY_COUNT] = {0, }; ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); while (ecs_query_next(&it)) { for (i32 i = 0; i < it.count; i++) { ecs_entity_t entity = it.entities[i]; if (!ecs_has(ECS, entity, Unit)) continue; Unit unit = *ecs_get(ECS, entity, Unit); if (unitTypes[unit.unitType].count == 0) { unitTypes[unit.unitType].rec = ecs_get(ECS, entity, TextureRegion)->rec; } unitTypes[unit.unitType].count++; } } i32 filterUnit = -1; for (i32 i = 0; i < ENTITY_COUNT; i++) { i32 count = unitTypes[i].count; if (count == 0) continue; const char *label = getEntityStr(i); if (uiGameUnit(label, count, unitTypes[i].rec, tex)) { filterUnit = i; } } if (filterUnit != -1) { ecs_defer_begin(ECS); it = ecs_query_iter(ECS, input->queries.selected); while (ecs_query_next(&it)) { for (i32 i = 0; i < it.count; i++) { ecs_entity_t entity = it.entities[i]; if (ecs_has(ECS, entity, Unit)) { Unit unit = *ecs_get(ECS, entity, Unit); if (unit.unitType == filterUnit) continue; } ecs_remove(ECS, entity, Selected); } } ecs_defer_end(ECS); } break; } case INPUT_SELECTED_OBJECT: break; case INPUT_SELECTED_BUILDING: { ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); ecs_entity_t buildingEntity = 0; while (ecs_query_next(&it)) { for (i32 i = 0; i < it.count; i++) { buildingEntity = it.entities[i]; } } if (!buildingEntity || !ecs_has(ECS, buildingEntity, Building)) break; Building building = *ecs_get(ECS, buildingEntity, Building); Vector2 placePos = { (building.pos.x + building.size.x * 0.5f) * 16.0f, (building.pos.y + building.size.y + 0.5f) * 16.0f }; placePos.x += GetRandomValue(-16, 16); placePos.y += GetRandomValue(-4, 4); PlayerResources *playerRes = &game->playerResources[game->player]; bool canRecruit = ecs_has(ECS, buildingEntity, BuildingRecruitInfo); if (canRecruit) { BuildingRecruitInfo *info = ecs_get_mut(ECS, buildingEntity, BuildingRecruitInfo); for (i32 i = 0; i < info->numSlots; i++) { BuildingRecruitSlot *slot = &info->slots[i]; const char *label = getEntityStr(slot->entityType); Rectangle rec = getTextureRect(getEntityTile(slot->entityType)); bool selected = false; bool canAfford = canAffordEntity(slot->entityType, *playerRes); canAfford &= playerRes->pop < playerRes->popCapacity; f32 progress = slot->elapsed / slot->recruitTime; if (slot->numRecruiting <= 0) progress = -1.0f; bool hovered = false; uiGameRecruit(label, rec, tex, slot->numRecruiting, progress, canAfford, &selected, &hovered); i32 res[RES_COUNT] = {0,}; getEntityCost(slot->entityType, res); if (hovered) { input->toolTipCost.active = true; bzMemCpy(input->toolTipCost.costs, res, sizeof(res)); } if (selected) { playerRes->pop++; playerRes->food -= res[RES_FOOD]; playerRes->wood -= res[RES_WOOD]; playerRes->gold -= res[RES_GOLD]; slot->numRecruiting++; } } break; } if (building.type == BUILDING_MARKET) { // 50:10 if (uiGameTrade("Buy 50 (-10 gold)##food", foodRec, tex, playerRes->gold >= 10)) { playerRes->food += 50; playerRes->gold -= 10; } // 100:10 if (uiGameTrade("Sell 100 (+10 gold)##food", foodRec, tex, playerRes->food >= 100)) { playerRes->food -= 100; playerRes->gold += 10; } // 50:25 if (uiGameTrade("Buy 50 (-25 gold)##wood", woodRec, tex, playerRes->gold >= 25)) { playerRes->wood += 50; playerRes->gold -= 25; } // 100:25 if (uiGameTrade("Sell 100 (+25 gold)##wood", woodRec, tex, playerRes->wood >= 100)) { playerRes->wood -= 100; playerRes->gold += 25; } } break; } default:; } bzUIEnd(UI); if (input->toolTipCost.active) { uiGameTooltipCost(input->mouse, input->toolTipCost.costs, tex); input->toolTipCost.active = false; } } void drawGameOverUI(Game *game, f32 dt) { i32 width = GetScreenWidth(); i32 height = GetScreenHeight(); bzUIBegin(UI, width, height); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN }); uiPushDivParentPercentage(1.0f, 0.4f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); uiMainMenuLabel("Game Over!"); bzUIPopParent(UI); uiPushDivParentPercentage(1.0f, 0.6f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER }); char gameOverDisplay[32]; snprintf(gameOverDisplay, sizeof(gameOverDisplay), "You survived %d waves.\n\n", game->waveInfo.number); uiBaseLabel(gameOverDisplay, game->font, 0.8f, WHITE); if (uiMainMenuButton("Exit", true)) { setScreen(game, SCREEN_MAIN_MENU); unloadMap(game); loadMap(game, "assets/maps/main_menu_01.tmj", true); } bzUIPopParent(UI); bzUIEnd(UI); } void drawPauseUI(Game *game, f32 dt) { i32 width = GetScreenWidth(); i32 height = GetScreenHeight(); bzUIBegin(UI, width, height); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN }); uiPushDivParentPercentage(1.0f, 0.4f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); uiMainMenuLabel("Paused"); bzUIPopParent(UI); uiPushDivParentPercentage(1.0f, 0.6f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER }); if (uiMainMenuButton("Resume", true)) { setScreen(game, SCREEN_GAME); } if (uiMainMenuButton("Exit", true)) { setScreen(game, SCREEN_MAIN_MENU); unloadMap(game); loadMap(game, "assets/maps/main_menu_01.tmj", true); } bzUIPopParent(UI); bzUIEnd(UI); } void drawMainMenuUI(Game *game, f32 dt) { i32 width = GetScreenWidth(); i32 height = GetScreenHeight(); game->camera.zoom = 3 * uiGetScale(); bzUIBegin(UI, width, height); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN }); uiPushDivParentPercentage(1.0f, 0.4f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); uiMainMenuLabel("Pixel Defense"); bzUIPopParent(UI); uiPushDivParentPercentage(1.0f, 0.6f); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER }); if (uiMainMenuButton("Play", true)) { setScreen(game, SCREEN_GAME); unloadMap(game); //loadMap(game, "assets/maps/tree_test.tmj", false); //loadMap(game, "assets/maps/entity_test.tmj", false); //loadMap(game, "assets/maps/worker_test.tmj", false); loadMap(game, "assets/maps/battle_test.tmj", false); //loadMap(game, "assets/maps/map_01.tmj", false); } if (uiMainMenuButton("Settings", true)) { setScreen(game, SCREEN_SETTINGS); } #ifndef EMSCRIPTEN if (uiMainMenuButton("Exit", true)) { bzGameExit(); } #endif bzUIPopParent(UI); bzUIEnd(UI); } void drawSettingsUI(Game *game, f32 dt) { i32 width = GetScreenWidth(); i32 height = GetScreenHeight(); game->camera.zoom = 3 * uiGetScale(); bzUIBegin(UI, width, height); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); bzUIPushDiv(UI, (BzUISize) { BZ_UI_SIZE_REL_PARENT, 0.8f}, (BzUISize) { BZ_UI_SIZE_REL_PARENT, 0.8f}); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); static GameData gameData = {}; if (game->screenPrevFrame != SCREEN_SETTINGS) gameData = game->gameData; uiSettingsLabel("Game"); //uiSettingsCheckbox("Fullscreen", &gameData.options.fullscreen); uiSettingsCheckbox("Debug Mode", &gameData.options.debugMenu); uiSettingsCheckbox("Limit FPS", &gameData.options.limitFps); //uiSettingsCheckbox("V-Sync", &opts.vsync); uiSettingsLabel("Audio"); uiSettingsSlider("Master: ", &gameData.options.master); uiSettingsSlider("Music: ", &gameData.options.music); uiSettingsSlider("Sound: ", &gameData.options.sound); bzUIPopParent(UI); bzUIPushDiv(UI, (BzUISize) {BZ_UI_SIZE_REL_PARENT, 0.8f}, (BzUISize) {BZ_UI_SIZE_REL_PARENT, 0.2f}); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_JUSTIFY_CENTER | BZ_UI_FLEX_ALIGN_CENTER }); if (uiSettingsButton("Back")) { setScreen(game, SCREEN_MAIN_MENU); } if (uiSettingsButton("Reset")) { gameData = game->gameData; } if (uiSettingsButton("Apply")) { serializeGameData(GAME_DATA_SAVE_PATH, &gameData); game->gameData = gameData; applyOptions(&game->gameData.options); setScreen(game, SCREEN_MAIN_MENU); } bzUIEnd(UI); }