462 lines
16 KiB
C
462 lines
16 KiB
C
#include "systems.h"
|
|
#include "game_state.h"
|
|
#include "input.h"
|
|
#include "buildings.h"
|
|
#include "pathfinding.h"
|
|
#include <rlImGui.h>
|
|
#include <raymath.h>
|
|
#include <rlgl.h>
|
|
|
|
Rectangle calculateEntityBounds(Position pos, Size size);
|
|
bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds);
|
|
|
|
ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag);
|
|
|
|
bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag);
|
|
void pickUnits(BzSpatialGrid *entityGrid, Rectangle area);
|
|
|
|
static void iterateSelectedUnits(ecs_query_t *query, void (*fn)(ecs_entity_t entity, Position *pos, Size *size));
|
|
static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size);
|
|
|
|
void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces);
|
|
|
|
void inputPrimaryAction(Game *game, InputState *input) {
|
|
const MouseButton primaryBtn = input->mapping.primaryBtn;
|
|
if (isInputBtnDragged(input, primaryBtn)) {
|
|
Vector2 start = input->mouseDownWorld;
|
|
Vector2 end = input->mouseWorld;
|
|
if (start.x > end.x) {
|
|
f32 tmp = start.x;
|
|
start.x = end.x;
|
|
end.x = tmp;
|
|
}
|
|
if (start.y > end.y) {
|
|
f32 tmp = start.y;
|
|
start.y = end.y;
|
|
end.y = tmp;
|
|
}
|
|
input->pickArea = (Rectangle) {start.x, start.y, end.x - start.x, end.y - start.y};
|
|
pickUnits(game->entityGrid, input->pickArea);
|
|
}
|
|
i32 selectedCount = ecs_query_entity_count(input->queries.selected);
|
|
if (isInputBtnJustDragged(input, primaryBtn) && selectedCount > 0) {
|
|
input->state = INPUT_SELECTED_UNITS;
|
|
} else if (isInputBtnJustUp(input, primaryBtn)) {
|
|
if (pickEntity(game->entityGrid, input->mouseDownWorld, Unit)) {
|
|
input->state = INPUT_SELECTED_UNITS;
|
|
} else if (pickEntity(game->entityGrid, input->mouseDownWorld, Harvestable)) {
|
|
input->state = INPUT_SELECTED_OBJECT;
|
|
} else if (pickEntity(game->entityGrid, input->mouseDownWorld, Buildable)) {
|
|
input->state = INPUT_SELECTED_BUILDING;
|
|
}
|
|
selectedCount = ecs_query_entity_count(input->queries.selected);
|
|
}
|
|
|
|
if (selectedCount == 0)
|
|
input->state = INPUT_NONE;
|
|
}
|
|
void inputUnitAction(Game *game, const InputState *input) {
|
|
ecs_query_t *query = input->queries.selected;
|
|
BzTileMap *map = &game->map;
|
|
const i32 numUnits = ecs_query_entity_count(query);
|
|
BZ_ASSERT(numUnits > 0);
|
|
|
|
const MouseButton actionBtn = input->mapping.secondaryBtn;
|
|
|
|
if (isInputBtnJustDown(input, actionBtn)) {
|
|
// Note: We mustn't use ecs ecs_remove_all since this will also
|
|
// remove ongoing paths that are not part of this query.
|
|
iterateSelectedUnits(input->queries.selected, iterRemovePaths);
|
|
|
|
const Position target = input->mouseWorld;
|
|
|
|
ecs_iter_t it = ecs_query_iter(ECS, query);
|
|
ecs_defer_begin(ECS);
|
|
while (ecs_iter_next(&it)) {
|
|
Position *pos = ecs_field(&it, Position, 1);
|
|
for (i32 i = 0; i < it.count; i++) {
|
|
const ecs_entity_t entity = it.entities[i];
|
|
|
|
Path path = {NULL, 0};
|
|
pathfindAStar(&(PathfindingDesc) {
|
|
.start = pos[i],
|
|
.target = target,
|
|
.map = map,
|
|
.outPath = &path,
|
|
.pool = game->pools.pathData,
|
|
.alloc = &game->stackAlloc
|
|
});
|
|
if (!path.paths) continue;
|
|
ecs_set_ptr(ECS, entity, Path, &path);
|
|
}
|
|
}
|
|
ecs_defer_end(ECS);
|
|
}
|
|
|
|
}
|
|
|
|
void updatePlayerInput() {
|
|
f32 dt = GetFrameTime();
|
|
ImGuiIO *io = igGetIO();
|
|
if (io->WantCaptureMouse || io->WantCaptureKeyboard)
|
|
return;
|
|
|
|
Game *game = ecs_singleton_get_mut(ECS, Game);
|
|
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
|
if (IsKeyDown(KEY_W)) game->camera.target.y -= 5;
|
|
if (IsKeyDown(KEY_S)) game->camera.target.y += 5;
|
|
if (IsKeyDown(KEY_A)) game->camera.target.x -= 5;
|
|
if (IsKeyDown(KEY_D)) game->camera.target.x += 5;
|
|
|
|
if (IsKeyDown(KEY_Q)) game->camera.rotation--;
|
|
if (IsKeyDown(KEY_E)) game->camera.rotation++;
|
|
|
|
|
|
game->camera.zoom += GetMouseWheelMove() * 0.05f;
|
|
BzTileMap *map = &game->map;
|
|
|
|
BzTile tileX = 0, tileY = 0;
|
|
bzTileMapPosToTile(map, input->mouseWorld, &tileX, &tileY);
|
|
|
|
if (IsKeyPressed(input->mapping.backBtn)) {
|
|
ecs_remove_all(ECS, Selected);
|
|
input->state = INPUT_NONE;
|
|
input->building = 0;
|
|
return;
|
|
}
|
|
|
|
const MouseButton primaryBtn = input->mapping.primaryBtn;
|
|
const MouseButton secondaryBtn = input->mapping.secondaryBtn;
|
|
|
|
switch (input->state) {
|
|
case INPUT_NONE: {
|
|
inputPrimaryAction(game, input);
|
|
break;
|
|
}
|
|
case INPUT_BUILDING: {
|
|
BZ_ASSERT(input->building);
|
|
BzTile sizeX = 0, sizeY = 0;
|
|
getBuildingSize(input->building, &sizeX, &sizeY);
|
|
bool canPlace = canPlaceBuilding(&game->map, input->building, tileX, tileY);
|
|
if (canPlace && isInputBtnDown(input, primaryBtn)) {
|
|
placeBuilding(&game->map, input->building, tileX, tileY);
|
|
}
|
|
input->buildingCanPlace = canPlace;
|
|
input->buildingPos = (TilePosition) {tileX, tileY};
|
|
input->buildingSize = (TileSize) {sizeX, sizeY};
|
|
break;
|
|
}
|
|
case INPUT_SELECTED_UNITS: {
|
|
inputPrimaryAction(game, input);
|
|
if (input->state != INPUT_SELECTED_UNITS) break;
|
|
inputUnitAction(game, input);
|
|
|
|
/*
|
|
|
|
i32 selectedCount = ecs_query_entity_count(input->queries.selected);
|
|
if (selectedCount > 1 && isInputBtnJustDragged(input, secondaryBtn)) {
|
|
// TODO: For click it should just move them
|
|
i32 numUnits = selectedCount;
|
|
f32 unitSpacing = 3.5f;
|
|
bzArrayClear(input->unitPositions);
|
|
placeUnits(numUnits, unitSpacing,
|
|
input->mouseDownWorld, input->mouseWorld,
|
|
map, &input->unitPositions);
|
|
|
|
BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);
|
|
i32 unitPosIdx = 0;
|
|
|
|
ecs_defer_begin(ECS);
|
|
ecs_defer_end(ECS);
|
|
|
|
ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected);
|
|
ecs_defer_begin(ECS);
|
|
while (ecs_iter_next(&it)) {
|
|
Position *pos = ecs_field(&it, Position, 1);
|
|
for (i32 i = 0; i < it.count; i++) {
|
|
ecs_entity_t entity = it.entities[i];
|
|
|
|
Position target = bzArrayGet(input->unitPositions, unitPosIdx);
|
|
unitPosIdx++;
|
|
|
|
Path path = {NULL, 0};
|
|
pathfindAStar(&(PathfindingDesc) {
|
|
.start=pos[i],
|
|
.target=target,
|
|
.map=map,
|
|
.outPath=&path,
|
|
.pool=game->pools.pathData,
|
|
.alloc=&game->stackAlloc
|
|
});
|
|
if (!path.paths) continue;
|
|
ecs_set_ptr(ECS, entity, Path, &path);
|
|
|
|
}
|
|
}
|
|
ecs_defer_end(ECS);
|
|
} else if (isInputBtnJustDown(input, secondaryBtn)) {
|
|
ecs_defer_begin(ECS);
|
|
iterateSelectedUnits(input->queries.selected, iterRemovePaths);
|
|
ecs_defer_end(ECS);
|
|
|
|
ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected);
|
|
|
|
ecs_defer_begin(ECS);
|
|
while (ecs_iter_next(&it)) {
|
|
Position *pos = ecs_field(&it, Position, 1);
|
|
for (i32 i = 0; i < it.count; i++) {
|
|
ecs_entity_t entity = it.entities[i];
|
|
ecs_remove(ECS, entity, Path);
|
|
|
|
Path path = {NULL, 0};
|
|
pathfindAStar(&(PathfindingDesc) {
|
|
.start=pos[i],
|
|
.target=input->mouseWorld,
|
|
.map=map,
|
|
.outPath=&path,
|
|
.pool=game->pools.pathData,
|
|
.alloc=&game->stackAlloc
|
|
});
|
|
if (!path.paths) continue;
|
|
ecs_set_ptr(ECS, entity, Path, &path);
|
|
|
|
}
|
|
}
|
|
ecs_defer_end(ECS);
|
|
}
|
|
*/
|
|
break;
|
|
}
|
|
case INPUT_SELECTED_OBJECT: {
|
|
inputPrimaryAction(game, input);
|
|
if (input->state != INPUT_SELECTED_OBJECT) break;
|
|
break;
|
|
}
|
|
case INPUT_SELECTED_BUILDING: {
|
|
inputPrimaryAction(game, input);
|
|
if (input->state != INPUT_SELECTED_BUILDING) break;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void drawPlayerInputUI() {
|
|
Game *game = ecs_singleton_get_mut(ECS, Game);
|
|
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
|
BzTileMap *map = &game->map;
|
|
|
|
const MouseButton primaryBtn = input->mapping.primaryBtn;
|
|
const MouseButton secondaryBtn = input->mapping.secondaryBtn;
|
|
|
|
i32 selectedUnitCount = ecs_count_id(ECS, Selected);
|
|
switch (input->state) {
|
|
case INPUT_NONE:
|
|
if (isInputBtnDragged(input, primaryBtn)) {
|
|
Rectangle area = input->pickArea;
|
|
DrawRectangleLines(area.x, area.y, area.width, area.height, RED);
|
|
}
|
|
break;
|
|
case INPUT_BUILDING: {
|
|
Color placeColor = input->buildingCanPlace ?
|
|
(Color) {0, 255, 0, 200} :
|
|
(Color) {255, 0, 0, 200};
|
|
|
|
BzTile width = game->map.tileWidth;
|
|
BzTile height = game->map.tileHeight;
|
|
DrawRectangleLines(input->buildingPos.x * width,
|
|
input->buildingPos.y * height,
|
|
input->buildingSize.sizeX * width,
|
|
input->buildingSize.sizeY * height, placeColor);
|
|
break;
|
|
}
|
|
case INPUT_SELECTED_UNITS: {
|
|
BZ_ASSERT(selectedUnitCount);
|
|
if (selectedUnitCount > 1 && isInputBtnDragged(input, primaryBtn)) {
|
|
i32 numUnits = selectedUnitCount;
|
|
f32 unitSpacing = 3.5f;
|
|
bzArrayClear(input->unitPositions);
|
|
placeUnits(numUnits, unitSpacing, input->mouseDownWorld, input->mouseWorld,
|
|
map, &input->unitPositions);
|
|
|
|
BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);
|
|
bzArrayFor(input->unitPositions, i) {
|
|
Position pos = bzArrayGet(input->unitPositions, i);
|
|
DrawCircle(pos.x, pos.y, 2.0f, ORANGE);
|
|
}
|
|
}
|
|
Rectangle area = {input->mouseWorld.x, input->mouseWorld.y, 1, 1};
|
|
DrawCircle(area.x, area.y, 4.0f, BLUE);
|
|
|
|
BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid, area.x, area.y, area.width, area.height);
|
|
while (bzSpatialGridQueryNext(&it)) {
|
|
ecs_entity_t entity = 0;
|
|
|
|
BzSpatialGrid *grid = game->entityGrid;
|
|
Vector2 point = input->mouseWorld;
|
|
|
|
|
|
if ((entity = queryEntity(grid, point, Harvestable))) {
|
|
DrawText("Collect resources", point.x, point.y, 10.0f, RED);
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected);
|
|
rlSetLineWidth(2.0f);
|
|
while (ecs_query_next(&it)) {
|
|
Position *pos = ecs_field(&it, Position, 1);
|
|
Size *size = ecs_field(&it, Size, 2);
|
|
for (i32 i = 0; i < it.count; i++) {
|
|
f32 radius = size[i].x;
|
|
if (size[i].y > radius)
|
|
radius = size[i].y;
|
|
radius *= 0.5f;
|
|
DrawCircleLines(pos[i].x, pos[i].y, radius, GREEN);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Rectangle calculateEntityBounds(Position pos, Size size) {
|
|
return (Rectangle) {
|
|
pos.x - size.x * 0.5f,
|
|
pos.y - size.x * 0.5f,
|
|
size.x, size.y
|
|
};
|
|
}
|
|
|
|
bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds) {
|
|
if (!ecs_is_alive(ECS, entity))
|
|
return false;
|
|
const Position *pos = ecs_get(ECS, entity, Position);
|
|
if (!pos)
|
|
return false;
|
|
const Size *size = ecs_get(ECS, entity, Size);
|
|
if (!size)
|
|
return false;
|
|
|
|
if (outPos) {
|
|
*outPos = *pos;
|
|
}
|
|
if (outSize) {
|
|
*outSize = *size;
|
|
}
|
|
if (outBounds) {
|
|
*outBounds = (Rectangle) {
|
|
pos->x - size->x * 0.5f,
|
|
pos->y - size->y * 0.5f,
|
|
size->x, size->y
|
|
};
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) {
|
|
BzSpatialGridIter it = bzSpatialGridIter(entityGrid, point.x, point.y, 0.0f, 0.0f);
|
|
f32 closestDst = INFINITY;
|
|
ecs_entity_t closest = 0;
|
|
while (bzSpatialGridQueryNext(&it)) {
|
|
ecs_entity_t entity = *(ecs_entity_t *) it.data;
|
|
if (!ecs_has_id(ECS, entity, Selectable)) continue;
|
|
if (!ecs_has_id(ECS, entity, tag)) continue;
|
|
Vector2 pos;
|
|
Rectangle bounds;
|
|
if (!getEntityBounds(entity, &pos, NULL, &bounds)) continue;
|
|
|
|
if (!CheckCollisionPointRec(point, bounds)) continue;
|
|
|
|
f32 curDst = Vector2Distance(point, pos);
|
|
if (closestDst > curDst) {
|
|
closestDst = curDst;
|
|
closest = entity;
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) {
|
|
ecs_remove_all(ECS, Selected);
|
|
const ecs_entity_t entity = queryEntity(entityGrid, point, tag);
|
|
if (entity) {
|
|
ecs_add(ECS, entity, Selected);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void pickUnits(BzSpatialGrid *entityGrid, Rectangle area) {
|
|
ecs_remove_all(ECS, Selected);
|
|
BzSpatialGridIter it = bzSpatialGridIter(entityGrid, area.x, area.y, area.width, area.height);
|
|
while (bzSpatialGridQueryNext(&it)) {
|
|
ecs_entity_t entity = *(ecs_entity_t *) it.data;
|
|
if (!ecs_has_id(ECS, entity, Unit)) continue;
|
|
Rectangle bounds;
|
|
if (!getEntityBounds(entity, NULL, NULL, &bounds)) continue;
|
|
|
|
if (!CheckCollisionRecs(area, bounds)) continue;
|
|
ecs_add(ECS, entity, Selected);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static bool isUnitObstructed(f32 x, f32 y, BzTileMap *map) {
|
|
return bzTileMapHasCollision(map, x / map->tileWidth, y / map->tileHeight);
|
|
}
|
|
|
|
static bool canPlaceUnit(Vector2 pos, f32 space, BzTileMap *map) {
|
|
for (i32 y = -1; y <= 1; y++) {
|
|
for (i32 x = -1; x <= 1; x++) {
|
|
if (isUnitObstructed(pos.x + x * space, pos.y + y * space, map))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces) {
|
|
f32 angle = Vector2Angle(start, end);
|
|
|
|
f32 lineLength = Vector2Distance(start, end);
|
|
|
|
Vector2 pos = Vector2Zero();
|
|
pos.x = unitSpacing;
|
|
|
|
for (i32 i = 0; i < numUnits; i++) {
|
|
if (pos.x + unitSpacing * 2.0f > lineLength) {
|
|
pos.x = unitSpacing;
|
|
pos.y += unitSpacing * 2.0f;
|
|
}
|
|
Vector2 unitPos = Vector2Add(start, Vector2Rotate(pos, angle));
|
|
if (!canPlaceUnit(unitPos, 4.0f, map)) {
|
|
i--;
|
|
} else {
|
|
bzArrayPush(*outPlaces, unitPos);
|
|
}
|
|
pos.x += unitSpacing * 2.0f;
|
|
}
|
|
}
|
|
|
|
static void iterateSelectedUnits(ecs_query_t *query, void (*fn)(ecs_entity_t entity, Position *pos, Size *size)) {
|
|
ecs_iter_t it = ecs_query_iter(ECS, query);
|
|
while (ecs_iter_next(&it)) {
|
|
Position *pos = ecs_field(&it, Position, 1);
|
|
Size *size = ecs_field(&it, Size, 2);
|
|
|
|
for (i32 i = 0; i < it.count; i++) {
|
|
ecs_entity_t entity = it.entities[i];
|
|
|
|
fn(entity, pos + i, size + i);
|
|
}
|
|
}
|
|
}
|
|
static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size) {
|
|
ecs_remove(ECS, entity, Path);
|
|
}
|
|
|