463 lines
17 KiB
C
463 lines
17 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 + 1, 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);
|
|
uiGameBuild(buildingNames[i], rec, tex, canAfford, &selected);
|
|
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;
|
|
uiGameRecruit(label, rec, tex, slot->numRecruiting, progress,
|
|
canAfford, &selected);
|
|
if (selected) {
|
|
i32 res[RES_COUNT] = {0,};
|
|
getEntityCost(slot->entityType, res);
|
|
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);
|
|
}
|
|
|
|
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");
|
|
//loadMap(game, "assets/maps/entity_test.tmj");
|
|
//loadMap(game, "assets/maps/worker_test.tmj");
|
|
//loadMap(game, "assets/maps/battle_test.tmj");
|
|
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("Video");
|
|
uiSettingsCheckbox("Fullscreen", &gameData.options.fullscreen);
|
|
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);
|
|
}
|