Files
PixelDefense/game/systems/s_entity.c

387 lines
13 KiB
C

#include "systems.h"
#include "../game_state.h"
#include "../input.h"
#include "../pathfinding.h"
#include "../entity_factory.h"
#include "../utils.h"
#include <math.h>
#include <raymath.h>
bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game) {
const Vector2 *pPath = ecs_get(ECS, entity, Position);
BZ_ASSERT(pPath);
const Vector2 start = *pPath;
Path path = {NULL, 0};
const bool foundPath = pathfindAStar(&(PathfindingDesc) {
.start = start,
.target = target,
.map = &game->map,
.outPath = &path,
.pool = game->pools.pathData,
.alloc = &game->stackAlloc,
});
if (foundPath) {
BZ_ASSERT(path.paths);
ecs_set_ptr(ECS, entity, Path, &path);
return true;
}
return false;
}
void entityPathRemove(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
for (i32 i = 0; i < it->count; i++) {
ecs_entity_t entity = it->entities[i];
ecs_remove(ECS, entity, TargetPosition);
}
}
void updateTextureOwnerTile(ecs_iter_t *it) {
TextureRegion *tex = ecs_field(it, TextureRegion, 1);
Owner *owner = ecs_field(it, Owner, 2);
for (i32 i = 0; i < it->count; i++) {
Rectangle rec = tex[i].rec;
BzTileID base = ((int)rec.y / 16) * 256 + ((int) rec.x / 16);
Vector2 offset = getTileOffset(base, owner[i].player);
tex[i].rec.x += offset.x;
tex[i].rec.y += offset.y;
}
}
void buildingAddPopCapacity(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
AddPopCapacity *addPop = ecs_field(it, AddPopCapacity, 1);
for (i32 i = 0; i < it->count; i++) {
Player player = ecs_get(ECS, it->entities[i], Owner)->player;
PlayerResources *res = &game->playerResources[player];
res->popCapacity += addPop[i].amount;
}
}
void buildingRemovePopCapacity(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
AddPopCapacity *addPop = ecs_field(it, AddPopCapacity, 1);
for (i32 i = 0; i < it->count; i++) {
Player player = ecs_get(ECS, it->entities[i], Owner)->player;
PlayerResources *res = &game->playerResources[player];
res->popCapacity -= addPop[i].amount;
}
}
void entityConsumePopCapacity(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
ConsumePopCapacity *pop = ecs_field(it, ConsumePopCapacity, 1);
for (i32 i = 0; i < it->count; i++) {
Player player = ecs_get(ECS, it->entities[i], Owner)->player;
PlayerResources *res = &game->playerResources[player];
res->pop += pop[i].amount;
}
}
void entityUnConsumePopCapacity(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
ConsumePopCapacity *pop = ecs_field(it, ConsumePopCapacity, 1);
for (i32 i = 0; i < it->count; i++) {
Player player = ecs_get(ECS, it->entities[i], Owner)->player;
PlayerResources *res = &game->playerResources[player];
res->pop -= pop[i].amount;
}
}
void entityUpdateSpatialID(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
Position *position = ecs_field(it, Position, 1);
HitBox *hitbox = ecs_field(it, HitBox, 2);
Velocity *velocity = ecs_field(it, Velocity, 3);
SpatialGridID *id = ecs_field(it, SpatialGridID, 4);
BZ_UNUSED(velocity);
for (i32 i = 0; i < it->count; i++) {
HitBox hb = entityTransformHitBox(position[i], hitbox[i]);
bzSpatialGridUpdate(game->entityGrid, id[i], hb.x, hb.y, hb.width, hb.height);
}
}
void entityUpdateKinematic(ecs_iter_t *it) {
Position *position = ecs_field(it, Position, 1);
Velocity *velocity = ecs_field(it, Velocity, 2);
Steering *steering = ecs_field(it, Steering, 3);
Unit *unit = ecs_field(it, Unit, 4);
f32 dt = it->delta_time;
for (i32 i = 0; i < it->count; i++) {
steering[i] = Vector2Normalize(steering[i]);
// velocity += steering * dt
Vector2 accel = Vector2Scale(steering[i], dt);
accel = Vector2Scale(accel, unit->acceleration);
velocity[i] = Vector2Add(velocity[i], accel);
// Apply deceleration
if (Vector2LengthSqr(steering[i]) == 0) {
// velocity *= (1.0 - decel)
velocity[i] = Vector2Scale(velocity[i], 1.0 - unit->deceleration);
}
// Reset steering
steering[i] = Vector2Zero();
// Check for speeding and clip
const f32 maxSpeed = unit->maxSpeed;
if (Vector2Length(velocity[i]) > maxSpeed) {
velocity[i] = Vector2Normalize(velocity[i]);
velocity[i] = Vector2Scale(velocity[i], maxSpeed);
}
// position += velocity * dt
position[i] = Vector2Add(position[i], Vector2Scale(velocity[i], dt));
}
}
void entityUpdate(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
Position *position = ecs_field(it, Position, 1);
HitBox *hitbox = ecs_field(it, HitBox, 2);
Velocity *velocity = ecs_field(it, Velocity, 3);
Unit *unit = ecs_field(it, Unit, 4);
Owner *owner = ecs_field(it, Owner, 5);
SpatialGridID *spatialID = ecs_field(it, SpatialGridID, 6);
f32 dt = it->delta_time;
for (i32 i = 0; i < it->count; i++) {
// Attack thingies
unit[i].attackElapsed += dt;
bool canAttack = unit[i].attackElapsed > unit[i].attackCooldown;
// Only update "stationary" entities
bool stationary = Vector2Length(velocity[i]) <= 0.2f;
Rectangle bounds = entityTransformHitBox(position[i], hitbox[i]);
BzSpatialGridIter spatialIt = bzSpatialGridIter(game->entityGrid, bounds.x, bounds.y,
bounds.width, bounds.height);
Vector2 dir = Vector2Zero();
f32 slowDown = 0.0f;
while (bzSpatialGridQueryNext(&spatialIt)) {
ecs_entity_t other = *(ecs_entity_t *) spatialIt.data;
if (other == it->entities[i])
continue;
Position otherPos;
Rectangle otherBounds;
if (!entityGetHitBox(other, &otherPos, &otherBounds))
continue;
if (!CheckCollisionRecs(bounds, otherBounds)) {
continue;
}
// Attack update
if (canAttack && ecs_has(ECS, other, Health) && ecs_has(ECS, other, Owner)) {
Health *otherHealth = ecs_get_mut(ECS, other, Health);
Player otherPlayer = ecs_get(ECS, other, Owner)->player;
if (otherPlayer != owner[i].player) {
Rectangle collisionRec = GetCollisionRec(bounds, otherBounds);
f32 percentageCovered = (collisionRec.width * collisionRec.height) / (bounds.width * bounds.height);
f32 dealDmg = randFloatRange(unit[i].minDamage, unit[i].maxDamage);
f32 multiplier = 1.0f + percentageCovered;
multiplier = Clamp(multiplier, 0.8f, 1.6f);
dealDmg *= multiplier;
damageEvent(other, (DamageEvent) {
.amount = dealDmg
});
canAttack = false;
unit[i].attackElapsed = 0.0f;
}
}
// Physics update
slowDown += 0.1f;
Position dif = Vector2Subtract(otherPos, position[i]);
dir = Vector2Add(dir, dif);
}
if (stationary) {
dir = Vector2Normalize(dir);
dir = Vector2Scale(dir, 4000 * dt);
velocity[i] = Vector2Subtract(velocity[i], dir);
}
slowDown = BZ_MIN(slowDown, 0.65f);
if (!stationary && slowDown > 0.0f) {
velocity[i] = Vector2Scale(velocity[i], 1 - slowDown);
}
}
}
void entityMoveToTarget(ecs_iter_t *it) {
Position *position = ecs_field(it, Position, 1);
Velocity *velocity = ecs_field(it, Velocity, 2);
TargetPosition *targetPos = ecs_field(it, TargetPosition, 3);
Steering *steering = ecs_field(it, Steering, 4);
for (i32 i = 0; i < it->count; i++) {
Position target = targetPos[i];
steering[i] = Vector2Subtract(target, position[i]);
f32 dst = Vector2LengthSqr(steering[i]);
if (dst < 2.0f) {
// Arrived
ecs_remove(ECS, it->entities[i], TargetPosition);
}
}
}
void entityFollowPath(ecs_iter_t *it) {
const Game *game = ecs_singleton_get(ECS, Game);
Path *path = ecs_field(it, Path, 1);
for (i32 i = 0; i < it->count; i++) {
const ecs_entity_t entity = it->entities[i];
if (!ecs_has(ECS, entity, TargetPosition)) {
if (path[i].curWaypoint >= path[i].paths->numWaypoints) {
path[i].curWaypoint = 0;
PathData *oldPath = path[i].paths;
bzObjectPoolRelease(game->pools.pathData, oldPath);
path[i].paths = path[i].paths->next;
if (!path[i].paths) ecs_remove(ECS, it->entities[i], Path);
}
if (path[i].paths) {
TargetPosition target = path[i].paths->waypoints[path[i].curWaypoint];
path[i].curWaypoint++;
ecs_set_ptr(ECS, entity, TargetPosition, &target);
}
}
}
}
void updateBuildingRecruitment(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
Owner *owner = ecs_field(it, Owner, 1);
Building *building = ecs_field(it, Building, 2);
BuildingRecruitInfo *info = ecs_field(it, BuildingRecruitInfo, 3);
f32 dt = GetFrameTime();
for (i32 i = 0; i < it->count; i++) {
Player player = owner[i].player;
Vector2 placePos = {
(building[i].pos.x + building[i].size.x * 0.5f) * 16.0f,
(building[i].pos.y + building->size.y + 0.5f) * 16.0f
};
placePos.x += GetRandomValue(-building[i].size.x, building[i].size.x);
placePos.y += GetRandomValue(-4, 4);
for (i32 slotIdx = 0; slotIdx < info[i].numSlots; i++) {
BuildingRecruitSlot *slot = &info[i].slots[slotIdx];
if (slot->numRecruiting <= 0) continue;
slot->elapsed += dt;
if (slot->elapsed >= slot->recruitTime) {
slot->elapsed = 0;
PlayerResources *playerRes = &game->playerResources[player];
playerRes->pop--;
entityRecruit(slot->entityType, placePos, player, game);
slot->numRecruiting--;
}
}
}
}
void renderHealthBar(ecs_iter_t *it) {
Position *pos = ecs_field(it, Position, 1);
HitBox *hitbox = ecs_field(it, HitBox, 2);
Health *health = ecs_field(it, Health, 3);
for (i32 i = 0; i < it->count; i++) {
HitBox hb = entityTransformHitBox(pos[i], hitbox[i]);
const f32 HP_WIDTH = 10.0f;
const f32 HP_HEIGHT = 1.8f;
const f32 HP_OFFSET = 2.0f;
const Color BG_COLOR = {0, 0, 0, 60};
const Color HP_COLOR = {255, 0, 0, 220};
const f32 PADDING = 0.15f;
Vector2 hpPos = {
hb.x + (hb.width - HP_WIDTH) * 0.5f,
hb.y - HP_OFFSET - HP_HEIGHT
};
Vector2 size = {
HP_WIDTH, HP_HEIGHT
};
f32 hpPercent = health[i].hp / health[i].startHP;
Vector2 hpSize = {
HP_WIDTH * hpPercent - PADDING * 2.0f,
HP_HEIGHT - PADDING * 2.0f
};
DrawRectangleV(hpPos, size, BG_COLOR);
hpPos.x += PADDING;
hpPos.y += PADDING;
DrawRectangleV(hpPos, hpSize, HP_COLOR);
}
}
void renderColliders(ecs_iter_t *it) {
Position *pos = ecs_field(it, Position, 1);
HitBox *hitbox = ecs_field(it, HitBox, 2);
for (i32 i = 0; i < it->count; i++) {
HitBox hb = entityTransformHitBox(pos[i], hitbox[i]);
DrawRectangleLinesEx(hb, 1.0f, RED);
}
}
void renderOrientationDirection(ecs_iter_t *it) {
Position *pos = ecs_field(it, Position, 1);
Orientation *orientation = ecs_field(it, Orientation, 2);
for (i32 i = 0; i < it->count; i++) {
Vector2 v = {6.0f, 0.0f};
v = Vector2Rotate(v, orientation[i]);
v = Vector2Add(v, pos[i]);
DrawLine(pos[i].x, pos[i].y, v.x, v.y, RED);
}
}
void renderDebugPath(ecs_iter_t *it) {
Path *path = ecs_field(it, Path, 1);
for (i32 i = 0; i < it->count; i++) {
PathData *pathData = path[i].paths;
bool first = true;
while (pathData) {
for (i32 iPath = 0; iPath < pathData->numWaypoints; iPath++) {
Color color = RED;
if (first && iPath < path[i].curWaypoint)
color = GREEN;
else if (first && iPath == path[i].curWaypoint)
color = ORANGE;
color.a = 180;
Position pos = pathData->waypoints[iPath];
DrawCircle(pos.x, pos.y, 3, color);
}
first = false;
pathData = pathData->next;
}
}
}