309 lines
9.8 KiB
C
309 lines
9.8 KiB
C
#include "building_factory.h"
|
|
|
|
#include "components.h"
|
|
#include "entity_factory.h"
|
|
#include "game_state.h"
|
|
#include "map_layers.h"
|
|
#include "systems/systems.h"
|
|
|
|
#include <raymath.h>
|
|
|
|
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 = 20.0f,
|
|
.projMaxDamage = 50.0f,
|
|
.projMinLifespan = 0.8f,
|
|
.projMaxLifespan = 1.2f,
|
|
.projSpeed = 100.0f,
|
|
.projRadius = 4.0f,
|
|
.projDamageCount = 3,
|
|
.fireCooldown = 0.9f,
|
|
.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] = 40;
|
|
cost[RES_FOOD] = 40;
|
|
cost[RES_GOLD] = 20;
|
|
break;
|
|
case BUILDING_BARRACKS:
|
|
cost[RES_WOOD] = 20;
|
|
cost[RES_FOOD] = 40;
|
|
cost[RES_GOLD] = 50;
|
|
break;
|
|
case BUILDING_GRANARY:
|
|
cost[RES_WOOD] = 40;
|
|
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] = 20;
|
|
cost[RES_FOOD] = 20;
|
|
cost[RES_GOLD] = 20;
|
|
break;
|
|
case BUILDING_MILL:
|
|
cost[RES_WOOD] = 200;
|
|
cost[RES_GOLD] = 50;
|
|
break;
|
|
case BUILDING_WAREHOUSE:
|
|
cost[RES_WOOD] = 60;
|
|
break;
|
|
case BUILDING_TOWER:
|
|
cost[RES_WOOD] = 200;
|
|
cost[RES_FOOD] = 100;
|
|
cost[RES_GOLD] = 100;
|
|
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;
|
|
}
|