#include "building_factory.h" #include "components.h" #include "entity_factory.h" #include "game_state.h" #include "map_layers.h" #include "systems/systems.h" #include bool canPlaceBuilding(Game *game, BuildingType type, BzTile tileX, BzTile tileY) { i32 sizeX, sizeY; getBuildingSize(type, &sizeX, &sizeY); if (sizeX == 0 || sizeY == 0) return false; if (!canAffordBuilding(type, game->playerResources[game->player])) return false; BzTileMap *map = &game->map; // Ensure that it is within the map if (tileX < 0 || tileX + sizeX > map->width || tileY < 0 || tileY + sizeY > map->height) return false; Rectangle buildArea = {tileX * map->tileWidth, tileY * map->tileHeight, sizeX * map->tileWidth, sizeY * map->tileHeight}; BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid, buildArea.x, buildArea.y, buildArea.width, buildArea.height); while (bzSpatialGridQueryNext(&it)) { ecs_entity_t entity = *(ecs_entity_t *) it.data; Rectangle bounds; if (!entityGetHitBox(entity, NULL, &bounds)) continue; if (CheckCollisionRecs(buildArea, bounds)) return false; } return true; } ecs_entity_t placeBuilding(Game *game, BuildingType type, BzTile posX, BzTile posY, Player player) { if (type <= BUILDING_NONE || type >= BUILDING_COUNT) return 0; i32 sizeX, sizeY; getBuildingSize(type, &sizeX, &sizeY); const i32 tileWidth = game->map.tileWidth; const i32 tileHeight = game->map.tileHeight; // Create entity ecs_entity_t building = entityCreateEmpty(); ecs_add_id(ECS, building, Selectable); ecs_set(ECS, building, Building, { .type = type, .pos = (Vec2i) { posX, posY }, .size = (Vec2i) { sizeX, sizeY } }); Position pos = { .x = posX * tileWidth, .y = posY * tileHeight }; Size size = { .x = sizeX * tileWidth, .y = sizeY * tileHeight, }; pos.y += size.y; HitBox hitbox = { .x = 0.0f, .y = 0.0f, .width = size.x, .height = size.y }; ecs_set_ptr(ECS, building, Position, &pos); ecs_set_ptr(ECS, building, Size, &size); ecs_set_ptr(ECS, building, HitBox, &hitbox); ecs_set(ECS, building, Rotation, {0}); HitBox tHitBox = entityTransformHitBox(pos, hitbox); SpatialGridID gridID = bzSpatialGridInsert(game->entityGrid, &building, tHitBox.x, tHitBox.y, tHitBox.width, tHitBox.height); ecs_set_ptr(ECS, building, SpatialGridID, &gridID); ecs_set(ECS, building, Owner, {player}); BzTileset *tileset = &game->tileset; TextureRegion region = { tileset->tiles, bzTilesetGetTileRegion(tileset, getBuildingTile(type)) }; region.rec.width *= sizeX; region.rec.height *= sizeY; ecs_set_ptr(ECS, building, TextureRegion, ®ion); bool hasCollision = true; Health health = { .startHP = 500.0f, .hp = 500.0f, .lastChanged = 1000.0f, }; switch (type) { case BUILDING_KEEP: game->keepEntity = building; ecs_set(ECS, building, AddPopCapacity, {10}); ecs_set(ECS, building, Storage, { .stores[RES_WOOD] = true, .stores[RES_GOLD] = true, .stores[RES_FOOD] = true, }); ecs_set(ECS, building, BuildingRecruitInfo, { .numSlots = 1, .slots[0] = { .entityType = ENTITY_WORKER, .numRecruiting = 0, .recruitTime = 4.0f, .elapsed = 0.0f, } }); health.startHP = 5000.0f; health.hp = health.startHP; break; case BUILDING_WAREHOUSE: ecs_set(ECS, building, Storage, { .stores[RES_WOOD] = true, .stores[RES_GOLD] = true, .stores[RES_FOOD] = false, }); break; case BUILDING_GRANARY: ecs_set(ECS, building, Storage, { .stores[RES_WOOD] = false, .stores[RES_GOLD] = false, .stores[RES_FOOD] = true, }); break; case BUILDING_BARRACKS: ecs_set(ECS, building, BuildingRecruitInfo, { .numSlots = 2, .slots[0] = { .entityType = ENTITY_SOLDIER, .numRecruiting = 0, .recruitTime = 6.0f, .elapsed = 0.0f, }, .slots[1] = { .entityType = ENTITY_WARRIOR, .numRecruiting = 0, .recruitTime = 15.0f, .elapsed = 0.0f } }); health.startHP = 2000.0f; health.hp = health.startHP; break; case BUILDING_TOWER: { ecs_entity_t mage = entityCreateEmpty(); const Vector2 mageSize = { 8.0f, 16.0f }; Vector2 magePos = { pos.x + (size.x - mageSize.x) * 0.5f, pos.y - size.y * 0.5f }; ecs_set_ptr(ECS, mage, Position, &magePos); ecs_set_ptr(ECS, mage, Size, &mageSize); ecs_set(ECS, mage, Rotation, { 0 }); ecs_set(ECS, mage, TextureRegion, { tileset->tiles, getTextureRect(getEntityTile(ENTITY_MAGE)) }); ecs_set(ECS, mage, Animation, { .entityType = ENTITY_MAGE, .animType = ANIM_IDLE, .sequence = entityGetAnimationSequence(ENTITY_MAGE, ANIM_IDLE), .tileset = tileset, .curFrame = 0, .elapsed = 0.0f }); ecs_set(ECS, mage, DrawLayer, { 1 }); ecs_set(ECS, building, AttachedEntity, {mage}); ecs_set(ECS, building, Tower, { .range = 80.0f, .projMinDamage = 40.0f, .projMaxDamage = 80.0f, .projMinLifespan = 0.8f, .projMaxLifespan = 1.2f, .projSpeed = 100.0f, .projRadius = 4.0f, .projDamageCount = 3, .fireCooldown = 0.8f, .fireElapsed = 0.0f, }); break; } case BUILDING_HOUSE_01: case BUILDING_HOUSE_02: case BUILDING_HOUSE_03: case BUILDING_HOUSE_04: case BUILDING_HOUSE_05: case BUILDING_HOUSE_06: ecs_set(ECS, building, AddPopCapacity, {5}); break; case BUILDING_WHEAT_0: case BUILDING_WHEAT_1: hasCollision = false; ecs_set(ECS, building, Harvestable, { .harvestLimit = 1 }); ecs_set(ECS, building, Resource, {RES_FOOD, INT32_MAX}); health.startHP = 20.0f; health.hp = health.startHP; hasCollision = false; break; default: break; } ecs_set_ptr(ECS, building, Health, &health); if (hasCollision) { bzTileMapSetCollisions(&game->map, true, COLL_LAYER_BUILDINGS, posX, posY, sizeX, sizeY); } return building; } void getBuildingCost(BuildingType type, i32 cost[RES_COUNT]) { for (i32 i = 0; i < RES_COUNT; i++) { cost[i] = 0; } switch (type) { case BUILDING_ARCHERY_RANGE: cost[RES_WOOD] = 400; cost[RES_FOOD] = 400; cost[RES_GOLD] = 200; break; case BUILDING_BARRACKS: cost[RES_WOOD] = 200; cost[RES_FOOD] = 400; cost[RES_GOLD] = 500; break; case BUILDING_GRANARY: cost[RES_WOOD] = 80; break; case BUILDING_HOUSE_01: case BUILDING_HOUSE_02: case BUILDING_HOUSE_03: case BUILDING_HOUSE_04: case BUILDING_HOUSE_05: case BUILDING_HOUSE_06: case BUILDING_HOUSE_07: case BUILDING_HOUSE_08: case BUILDING_HOUSE_09: cost[RES_WOOD] = 50; break; case BUILDING_MARKET: cost[RES_WOOD] = 200; cost[RES_FOOD] = 200; cost[RES_GOLD] = 200; break; case BUILDING_MILL: cost[RES_WOOD] = 200; cost[RES_GOLD] = 50; break; case BUILDING_WAREHOUSE: cost[RES_WOOD] = 100; break; case BUILDING_TOWER: cost[RES_WOOD] = 400; cost[RES_FOOD] = 100; cost[RES_GOLD] = 500; break; case BUILDING_NONE: case BUILDING_KEEP: case BUILDING_COUNT: // NA break; default:; } } bool canAffordBuilding(BuildingType type, PlayerResources res) { i32 needed[RES_COUNT] = {0, }; getBuildingCost(type, needed); if (needed[RES_WOOD] > res.wood) return false; if (needed[RES_FOOD] > res.food) return false; if (needed[RES_GOLD] > res.gold) return false; return true; } Vector2 getPositionNearBuilding(ecs_entity_t building, Vector2 fromPos) { BZ_ASSERT(ecs_is_alive(ECS, building)); BZ_ASSERT(ecs_has(ECS, building, Position)); BZ_ASSERT(ecs_has(ECS, building, HitBox)); Vector2 pos = *ecs_get(ECS, building, Position); HitBox hitbox = *ecs_get(ECS, building, HitBox); Vector2 center = entityGetCenter(pos, hitbox); Vector2 size = {hitbox.width, hitbox.height}; size = Vector2SubtractValue(size, 5.0f); Vector2 dir = Vector2Normalize(Vector2Subtract(fromPos, center)); dir = Vector2Multiply(dir, size); center = Vector2Add(center, dir); return center; }