Files
PixelDefense/game/systems/s_ui.c

482 lines
18 KiB
C

#include <stdio.h>
#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);
}