diff --git a/game/components.h b/game/components.h index 49269e1..60fbc70 100644 --- a/game/components.h +++ b/game/components.h @@ -191,6 +191,7 @@ typedef struct Unit { f32 maxSpeed; f32 acceleration; f32 deceleration; + EntityType unitType; } Unit; extern ECS_COMPONENT_DECLARE(Unit); typedef struct Building { diff --git a/game/entity_factory.c b/game/entity_factory.c index 1b0e3a2..d3dc5ae 100644 --- a/game/entity_factory.c +++ b/game/entity_factory.c @@ -45,7 +45,8 @@ ecs_entity_t entityCreateWorker(const Position position, Game *game) { ecs_set(ECS, e, Unit, { .acceleration = 80.0f, .maxSpeed = 15.0f, - .deceleration = 0.1f + .deceleration = 0.1f, + .unitType = ENTITY_WORKER }); ecs_set(ECS, e, Worker, { .collectSpeed = 0.8f, diff --git a/game/systems/s_ui.c b/game/systems/s_ui.c index 80d9943..6f32647 100644 --- a/game/systems/s_ui.c +++ b/game/systems/s_ui.c @@ -74,20 +74,33 @@ void drawGameUI(Game *game, f32 dt) { }; i32 numBuildings = sizeof(buildingOrder) / sizeof(*buildingOrder); - BzUINode *menu = NULL; + 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: - menu = 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}, - .margin[BZ_UI_AXIS_Y * 2] = 5.0f * uiGetScale(), - }); - 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 - }); + case INPUT_BUILDING: { for (i32 i = 0; i < numBuildings; i++) { BuildingType buildingType = buildingOrder[i]; Rectangle rec = bzTilesetGetTileRegion(tileset, getBuildingTile(buildingType)); @@ -96,7 +109,6 @@ void drawGameUI(Game *game, f32 dt) { getBuildingSize(buildingType, &sizeX, &sizeY); rec.width *= sizeX; rec.height *= sizeY; - Texture2D tex = tileset->tiles; bool selected = input->building == buildingOrder[i]; PlayerResources *res = &game->playerResources[game->player]; bool canAfford = canAffordBuilding(buildingType, *res); @@ -109,15 +121,61 @@ void drawGameUI(Game *game, f32 dt) { } } break; - case INPUT_SELECTED_UNITS: + } + 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: break; } - bzUIEnd(UI); } diff --git a/game/ui_widgets.c b/game/ui_widgets.c index c76fa82..8dad017 100644 --- a/game/ui_widgets.c +++ b/game/ui_widgets.c @@ -317,3 +317,78 @@ void uiGameBuild(const char *label, Rectangle rec, Texture2D tex, bool canAfford bzUIPopParent(UI); } +bool uiGameUnit(const char *label, i32 count, Rectangle rec, Texture2D tex) { + f32 scl = uiGetScale(); + BzUINode *btn = bzUINodeMake(UI, bzUIKeyFromString(label), &(BzUINodeDesc) { + .flags = BZ_UI_CLICKABLE | BZ_UI_ALIGN_CENTER | BZ_UI_DRAW_BORDER, + .margin[BZ_UI_AXIS_X] = 10.0f * scl, + .margin[BZ_UI_AXIS_Y] = 10.0f * scl, + .margin[BZ_UI_AXIS_Y * 2] = 10.0f * scl, + .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_MAX}, + .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_SUM}, + }); + + BzUIInteraction inter = bzUIGetInteraction(UI, btn); + + Color bgColor = DARKBROWN; + if (inter.hovering) + bgColor = BROWN; + + bzUISetBackgroundStyle(UI, btn, (BzUIBackgroundStyle) { + .roundness = 0.2f, + .active = bgColor, + .normal = bgColor, + .hover = bgColor, + }); + bzUISetBorderStyle(UI, btn, (BzUIBorderStyle) { + .roundness = 0.2f, + .thickness = 5.0f * scl, + .normal = BLACK, + .hover = BLACK, + .active = BLACK, + }); + bzUIPushParent(UI, btn); + bzUISetParentLayout(UI, (BzUILayout) { + .type = BZ_UI_LAYOUT_FLEX_BOX, + .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START + }); + + BzUINode *tileSprite = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { + .flags = BZ_UI_DRAW_SPRITE, + .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_PIXELS, rec.width * 5 * scl}, + .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, rec.height * 5 * scl}, + }); + bzUISetSpriteStyle(UI, tileSprite, (BzUISpriteStyle) { + .texture = tex, + .rec = rec, + .tintNormal = WHITE, + .tintHover = WHITE, + .tintActive = WHITE, + }); + + BzUINode *labelDisplay = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { + .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW, + .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_FIT}, + .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_FIT}, + }); + char buf[128]; + snprintf(buf, sizeof(buf), "%d", count); + bzUISetTextStyle(UI, labelDisplay, (BzUITextStyle) { + .font = getFont(), + .fontSize = 25.0f * scl, + .fontSpacing = 1.0f * scl, + .normal = WHITE, + .hover = WHITE, + .active = WHITE, + .text = buf + }); + bzUISetTextShadowStyle(UI, labelDisplay, (BzUITextShadowStyle) { + .offset = {1.0f * scl, 1.0f * scl}, + .normal = BLACK, + .hover = BLACK, + .active = BLACK + }); + + bzUIPopParent(UI); + return inter.clicked; +} diff --git a/game/ui_widgets.h b/game/ui_widgets.h index 20fe123..e4288f7 100644 --- a/game/ui_widgets.h +++ b/game/ui_widgets.h @@ -28,5 +28,6 @@ void uiSettingsSlider(const char *txt, f32 *value); void uiGameResCount(i32 amount, i32 capacity, Rectangle icon, Texture2D texture); void uiGameBuild(const char *label, Rectangle rec, Texture2D tex, bool canAfford, bool *selected); +bool uiGameUnit(const char *label, i32 count, Rectangle rec, Texture2D tex); #endif //PIXELDEFENSE_UI_WIDGETS_H