First cut of pulling out the relevant parts of my original game to make a little framework.
This commit is contained in:
commit
6a0c9e8d46
177 changed files with 18197 additions and 0 deletions
656
src/game/systems.cpp
Normal file
656
src/game/systems.cpp
Normal file
|
|
@ -0,0 +1,656 @@
|
|||
#include "game/systems.hpp"
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include "algos/rand.hpp"
|
||||
#include "algos/spatialmap.hpp"
|
||||
#include "dbc.hpp"
|
||||
#include "graphics/lights.hpp"
|
||||
#include "events.hpp"
|
||||
#include "game/sound.hpp"
|
||||
#include "ai/ai.hpp"
|
||||
#include "ai/ai_debug.hpp"
|
||||
#include "algos/shiterator.hpp"
|
||||
#include "combat/battle.hpp"
|
||||
#include <iostream>
|
||||
#include "graphics/shaders.hpp"
|
||||
#include "graphics/textures.hpp"
|
||||
#include "game/inventory.hpp"
|
||||
#include "game/level.hpp"
|
||||
#include "events.hpp"
|
||||
#include "graphics/animation.hpp"
|
||||
|
||||
using std::string;
|
||||
using namespace fmt;
|
||||
using namespace combat;
|
||||
using namespace components;
|
||||
using namespace DinkyECS;
|
||||
using lighting::LightSource;
|
||||
|
||||
void System::set_position(World& world, SpatialMap& collision, Entity entity, Position pos) {
|
||||
world.set<Position>(entity, pos);
|
||||
bool has_collision = world.has<Collision>(entity);
|
||||
collision.insert(pos.location, entity, has_collision);
|
||||
}
|
||||
|
||||
void System::lighting() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& light = *level.lights;
|
||||
auto& world = *level.world;
|
||||
auto& map = *level.map;
|
||||
|
||||
light.reset_light();
|
||||
|
||||
world.query<Position>([&](auto, auto &position) {
|
||||
light.set_light_target(position.location);
|
||||
});
|
||||
|
||||
light.path_light(map.walls());
|
||||
|
||||
world.query<Position, LightSource>([&](auto ent, auto &position, auto &lightsource) {
|
||||
light.render_light(lightsource, position.location);
|
||||
|
||||
if(ent == level.player) {
|
||||
light.update_fow(position.location, lightsource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void System::generate_paths() {
|
||||
auto& level = GameDB::current_level();
|
||||
const auto &player_pos = GameDB::player_position();
|
||||
|
||||
level.map->set_target(player_pos.location);
|
||||
level.map->make_paths();
|
||||
}
|
||||
|
||||
void System::enemy_ai_initialize() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& map = *level.map;
|
||||
|
||||
world.query<Position, EnemyConfig>([&](const auto ent, auto& pos, auto& config) {
|
||||
if(world.has<ai::EntityAI>(ent)) {
|
||||
auto& enemy = world.get<ai::EntityAI>(ent);
|
||||
auto& personality = world.get<Personality>(ent);
|
||||
|
||||
enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance);
|
||||
enemy.update();
|
||||
} else {
|
||||
auto ai_start = ai::load_state(config.ai_start_name);
|
||||
auto ai_goal = ai::load_state(config.ai_goal_name);
|
||||
|
||||
ai::EntityAI enemy(config.ai_script, ai_start, ai_goal);
|
||||
auto& personality = world.get<Personality>(ent);
|
||||
|
||||
enemy.set_state("tough_personality", personality.tough);
|
||||
enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance);
|
||||
enemy.update();
|
||||
|
||||
world.set<ai::EntityAI>(ent, enemy);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void System::enemy_pathing() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& map = *level.map;
|
||||
const auto &player_pos = GameDB::player_position();
|
||||
|
||||
world.query<Position, Motion>([&](auto ent, auto &position, auto &motion) {
|
||||
if(ent != level.player) {
|
||||
auto& enemy_ai = world.get<ai::EntityAI>(ent);
|
||||
Point out = position.location; // copy
|
||||
bool found_path = false;
|
||||
|
||||
if(enemy_ai.wants_to("find_enemy")) {
|
||||
found_path = map.random_walk(out, motion.random, PATHING_TOWARD);
|
||||
} else if(enemy_ai.wants_to("run_away")) {
|
||||
found_path = map.random_walk(out, motion.random, PATHING_AWAY);
|
||||
} else {
|
||||
motion = {0,0};
|
||||
return; // enemy doesn't want to move
|
||||
}
|
||||
|
||||
enemy_ai.set_state("cant_move", !found_path);
|
||||
enemy_ai.update();
|
||||
|
||||
motion = { int(out.x - position.location.x), int(out.y - position.location.y)};
|
||||
}
|
||||
});
|
||||
|
||||
map.clear_target(player_pos.location);
|
||||
}
|
||||
|
||||
|
||||
void System::motion() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto world = level.world;
|
||||
auto map = level.map;
|
||||
auto collider = level.collision;
|
||||
|
||||
world->query<Position, Motion>(
|
||||
[&](auto ent, auto &position, auto &motion) {
|
||||
// skip enemies that aren't moving
|
||||
if(motion.dx == 0 && motion.dy == 0) return;
|
||||
|
||||
Point move_to = {
|
||||
position.location.x + motion.dx,
|
||||
position.location.y + motion.dy
|
||||
};
|
||||
|
||||
motion = {0,0}; // clear it after getting it
|
||||
|
||||
dbc::check(map->can_move(move_to), "Enemy pathing failed, move_to is wall.");
|
||||
|
||||
bool cant_move = collider->occupied(move_to);
|
||||
|
||||
if(auto enemy_ai = world->get_if<ai::EntityAI>(ent)) {
|
||||
enemy_ai->set_state("cant_move", cant_move);
|
||||
}
|
||||
|
||||
// it's a wall, skip
|
||||
if(cant_move) return;
|
||||
|
||||
// all good, do the move
|
||||
collider->move(position.location, move_to, ent);
|
||||
position.location = move_to;
|
||||
});
|
||||
}
|
||||
|
||||
void System::distribute_loot(Position target_pos) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& config = world.get_the<GameConfig>();
|
||||
int inventory_count = Random::uniform(0, 3);
|
||||
auto loot_entity = world.entity();
|
||||
|
||||
if(inventory_count > 0) {
|
||||
auto& entity_data = config.devices["DEAD_BODY_LOOTABLE"];
|
||||
components::configure_entity(world, loot_entity, entity_data["components"]);
|
||||
// BUG: inventory_count here isn't really used to remove it
|
||||
world.set<InventoryItem>(loot_entity, {inventory_count, entity_data});
|
||||
} else {
|
||||
// this creates a dead body on the ground
|
||||
auto& entity_data = config.devices["DEAD_BODY"];
|
||||
components::configure_entity(world, loot_entity, entity_data["components"]);
|
||||
}
|
||||
|
||||
set_position(world, *level.collision, loot_entity, target_pos);
|
||||
level.world->send<game::Event>(game::Event::ENTITY_SPAWN, loot_entity, {});
|
||||
}
|
||||
|
||||
void System::death() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto player = world.get_the<Player>();
|
||||
std::vector<Entity> dead_things;
|
||||
|
||||
world.query<Combat>([&](auto ent, auto &combat) {
|
||||
// bring out yer dead
|
||||
if(combat.hp <= 0 && !combat.dead) {
|
||||
combat.dead = true;
|
||||
if(ent != player.entity) {
|
||||
// we won't change out the player's components later
|
||||
dead_things.push_back(ent);
|
||||
}
|
||||
// we need to send this event for everything that dies
|
||||
world.send<game::Event>(game::Event::DEATH, ent, {});
|
||||
} else if(float(combat.hp) / float(combat.max_hp) < 0.5f) {
|
||||
// if enemies are below 50% health they are marked with bad health
|
||||
if(world.has<ai::EntityAI>(ent)) {
|
||||
auto& enemy_ai = world.get<ai::EntityAI>(ent);
|
||||
enemy_ai.set_state("health_good", false);
|
||||
enemy_ai.update();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// this goes through everything that died and changes them to a gravestone
|
||||
for(auto ent : dead_things) {
|
||||
if(auto snd = world.get_if<Sound>(ent)) {
|
||||
sound::stop(snd->attack);
|
||||
sound::play(snd->death);
|
||||
}
|
||||
|
||||
auto pos = world.get<Position>(ent);
|
||||
|
||||
// need to remove _after_ getting the position
|
||||
level.collision->remove(pos.location, ent);
|
||||
|
||||
// distribute_loot is then responsible for putting something there
|
||||
System::distribute_loot(pos);
|
||||
|
||||
world.destroy(ent);
|
||||
}
|
||||
}
|
||||
|
||||
void System::combat(int attack_id) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& collider = *level.collision;
|
||||
auto& world = *level.world;
|
||||
const auto& player_pos = GameDB::player_position();
|
||||
auto& player_combat = world.get<Combat>(level.player);
|
||||
auto& player_ai = world.get<ai::EntityAI>(level.player);
|
||||
|
||||
// this is guaranteed to not return the given position
|
||||
auto [found, nearby] = collider.neighbors(player_pos.location);
|
||||
combat::BattleEngine battle;
|
||||
|
||||
if(found) {
|
||||
for(auto entity : nearby) {
|
||||
if(world.has<ai::EntityAI>(entity)) {
|
||||
auto& enemy_ai = world.get<ai::EntityAI>(entity);
|
||||
auto& enemy_combat = world.get<Combat>(entity);
|
||||
battle.add_enemy({entity, &enemy_ai, &enemy_combat});
|
||||
}
|
||||
}
|
||||
|
||||
battle.add_enemy({level.player, &player_ai, &player_combat, true});
|
||||
battle.set_all("enemy_found", true);
|
||||
battle.set_all("in_combat", true);
|
||||
battle.player_request("kill_enemy");
|
||||
battle.ap_refresh();
|
||||
battle.plan();
|
||||
}
|
||||
|
||||
// battle.dump();
|
||||
|
||||
while(auto act = battle.next()) {
|
||||
auto [enemy, enemy_action, cost, host_state] = *act;
|
||||
|
||||
// player shouldn't hit theirself
|
||||
if(host_state != BattleHostState::not_host) continue;
|
||||
|
||||
components::CombatResult result {
|
||||
.attacker=enemy.entity,
|
||||
.host_state=host_state,
|
||||
.player_did=player_combat.attack(*enemy.combat),
|
||||
.enemy_did=0
|
||||
};
|
||||
|
||||
if(result.player_did > 0) {
|
||||
spawn_attack(world, attack_id, enemy.entity);
|
||||
}
|
||||
|
||||
if(enemy_action == "kill_enemy") {
|
||||
result.enemy_did = enemy.combat->attack(player_combat);
|
||||
animation::animate_entity(world, enemy.entity);
|
||||
}
|
||||
|
||||
world.send<game::Event>(game::Event::COMBAT, enemy.entity, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void System::collision() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& collider = *level.collision;
|
||||
auto& world = *level.world;
|
||||
const auto& player_pos = GameDB::player_position();
|
||||
|
||||
// this is guaranteed to not return the given position
|
||||
auto [found, nearby] = collider.neighbors(player_pos.location);
|
||||
int combat_count = 0;
|
||||
|
||||
// AI: I think also this would a possible place to run AI decisions
|
||||
for(auto entity : nearby) {
|
||||
if(world.has<Combat>(entity)) {
|
||||
auto combat = world.get<Combat>(entity);
|
||||
if(!combat.dead) {
|
||||
combat_count++;
|
||||
world.send<game::Event>(game::Event::COMBAT_START, entity, entity);
|
||||
}
|
||||
} else {
|
||||
dbc::log($F("UNKNOWN COLLISION TYPE {}", entity));
|
||||
}
|
||||
}
|
||||
|
||||
if(combat_count == 0) {
|
||||
// BUG: this is probably how we get stuck in combat
|
||||
world.send<game::Event>(game::Event::NO_NEIGHBORS, level.player, level.player);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This isn't for destroying something, but just removing it
|
||||
* from the world for say, putting into a container or inventory.
|
||||
*/
|
||||
void System::remove_from_world(Entity entity) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& item_pos = level.world->get<Position>(entity);
|
||||
level.collision->remove(item_pos.location, entity);
|
||||
// if you don't do this you get the bug that you can pickup
|
||||
// an item and it'll also be in your inventory
|
||||
level.world->remove<Position>(entity);
|
||||
}
|
||||
|
||||
void System::pickup() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& collision = *level.collision;
|
||||
auto pos = GameDB::player_position();
|
||||
|
||||
if(!collision.something_there(pos.aiming_at)) return;
|
||||
|
||||
auto entity = level.collision->find(pos.aiming_at, [&](auto data) -> bool {
|
||||
return (world.has<InventoryItem>(data.entity) ||
|
||||
world.has<Device>(data.entity));
|
||||
});
|
||||
|
||||
if(entity == DinkyECS::NONE) {
|
||||
dbc::log("no inventory or devices there");
|
||||
return;
|
||||
}
|
||||
|
||||
// use spatial find to find an item with inventory...
|
||||
if(world.has<InventoryItem>(entity)) {
|
||||
// NOTE: this might need to be a separate system so that people can leave stuff alone
|
||||
remove_from_world(entity);
|
||||
// NOTE: chests are different from say a torch, maybe 2 events or the
|
||||
// GUI figures out which it is, then when you click either pick it up
|
||||
// and move it or show the loot container UI
|
||||
world.send<game::Event>(game::Event::LOOT_ITEM, entity, entity);
|
||||
} else if(world.has<Device>(entity)) {
|
||||
System::device(world, level.player, entity);
|
||||
} else {
|
||||
dbc::log("BUG: is this a bug in pickup?!");
|
||||
}
|
||||
}
|
||||
|
||||
void System::device(World &world, Entity actor, Entity item) {
|
||||
auto& device = world.get<Device>(item);
|
||||
dbc::log($F("entity {} INTERACTED WITH DEVICE {}", actor, item));
|
||||
|
||||
for(auto event : device.events) {
|
||||
if(event == "STAIRS_DOWN") {
|
||||
world.send<game::Event>(game::Event::STAIRS_DOWN, actor, device);
|
||||
} else if(event == "STAIRS_UP") {
|
||||
world.send<game::Event>(game::Event::STAIRS_UP, actor, device);
|
||||
} else if(event == "TRAP") {
|
||||
world.send<game::Event>(game::Event::TRAP, actor, device);
|
||||
} else if(event == "LOOT_CONTAINER") {
|
||||
world.send<game::Event>(game::Event::LOOT_CONTAINER, actor, device);
|
||||
} else {
|
||||
dbc::log($F(
|
||||
"INVALID EVENT {} for device {}",
|
||||
event, (std::string)device.config["name"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::move_player(Position move_to) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto old_pos = level.world->get<Position>(level.player);
|
||||
|
||||
level.world->set<Position>(level.player, move_to);
|
||||
level.collision->move(old_pos.location, move_to.location, level.player);
|
||||
}
|
||||
|
||||
|
||||
void System::player_status() {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& combat = level.world->get<Combat>(level.player);
|
||||
float percent = float(combat.hp) / float(combat.max_hp);
|
||||
|
||||
if(percent > 0.8) {
|
||||
sound::play("hp_status_80");
|
||||
} else if(percent > 0.6) {
|
||||
sound::play("hp_status_60");
|
||||
} else if(percent > 0.3) {
|
||||
sound::play("hp_status_30");
|
||||
} else if(percent > 0.1) {
|
||||
sound::play("hp_status_10");
|
||||
} else {
|
||||
sound::play("hp_status_00");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<sf::Shader> System::sprite_effect(Entity entity) {
|
||||
auto world = GameDB::current_world();
|
||||
if(auto se = world->get_if<SpriteEffect>(entity)) {
|
||||
if(se->frames > 0) {
|
||||
se->frames--;
|
||||
return se->effect;
|
||||
} else {
|
||||
world->remove<SpriteEffect>(entity);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Entity System::spawn_item(World& world, const std::string& name) {
|
||||
auto& config = world.get_the<GameConfig>().items;
|
||||
auto& item_config = config[name];
|
||||
auto item_id = world.entity();
|
||||
world.set<InventoryItem>(item_id, {1, item_config});
|
||||
components::configure_entity(world, item_id, item_config["components"]);
|
||||
|
||||
return item_id;
|
||||
}
|
||||
|
||||
void System::drop_item(Entity item) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& map = *level.map;
|
||||
auto player_pos = GameDB::player_position();
|
||||
|
||||
dbc::check(map.can_move(player_pos.location), "impossible, the player can't be in a wall");
|
||||
|
||||
Position drop_spot = {player_pos.aiming_at.x, player_pos.aiming_at.y};
|
||||
|
||||
// if they're aiming at a wall then drop at their feet
|
||||
if(!map.can_move(drop_spot.location)) drop_spot = player_pos;
|
||||
|
||||
set_position(world, *level.collision, item, drop_spot);
|
||||
|
||||
level.world->not_constant(item);
|
||||
level.world->send<game::Event>(game::Event::ENTITY_SPAWN, item, {});
|
||||
}
|
||||
|
||||
// NOTE: I think pickup and this need to be different
|
||||
bool System::place_in_container(Entity cont_id, const std::string& name, Entity world_entity) {
|
||||
auto world = GameDB::current_world();
|
||||
auto& container = world->get<inventory::Model>(cont_id);
|
||||
|
||||
if(container.has(world_entity)) {
|
||||
fmt::println("container {} already has entity {}, skip", cont_id, world_entity);
|
||||
// NOTE: I think this would be a move?!
|
||||
return false;
|
||||
} else if(container.has(name)) {
|
||||
// this is an already occupied slot
|
||||
fmt::println("container {} already has SLOT {}, skip", cont_id, name);
|
||||
return false;
|
||||
} else {
|
||||
// this should only apply to the player's inventory
|
||||
fmt::println("adding {} entity to loot with name {}", world_entity, name);
|
||||
container.add(name, world_entity);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void System::remove_from_container(Entity cont_id, const std::string& slot_id) {
|
||||
auto world = GameDB::current_world();
|
||||
auto& container = world->get<inventory::Model>(cont_id);
|
||||
auto entity = container.get(slot_id);
|
||||
container.remove(entity);
|
||||
}
|
||||
|
||||
|
||||
void System::inventory_swap(Entity container_id, const std::string& a_name, const std::string &b_name) {
|
||||
auto& level = GameDB::current_level();
|
||||
dbc::check(a_name != b_name, "Attempt to inventory swap the same slot, you should check this and avoid calling me.");
|
||||
|
||||
auto& inventory = level.world->get<inventory::Model>(container_id);
|
||||
|
||||
auto a_ent = inventory.get(a_name);
|
||||
auto b_ent = inventory.get(b_name);
|
||||
inventory.swap(a_ent, b_ent);
|
||||
}
|
||||
|
||||
bool System::inventory_occupied(Entity container_id, const std::string& name) {
|
||||
auto world = GameDB::current_world();
|
||||
auto& inventory = world->get<inventory::Model>(container_id);
|
||||
return inventory.has(name);
|
||||
}
|
||||
|
||||
|
||||
void System::draw_map(Matrix& grid, EntityGrid& entity_map) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
Map &map = *level.map;
|
||||
Matrix &fow = level.lights->$fow;
|
||||
size_t view_x = matrix::width(grid) - 1;
|
||||
size_t view_y = matrix::height(grid) - 1;
|
||||
|
||||
entity_map.clear();
|
||||
|
||||
auto player_pos = world.get<Position>(level.player).location;
|
||||
Point cam_orig = map.center_camera(player_pos, view_x, view_y);
|
||||
auto &tiles = map.tiles();
|
||||
auto &tile_set = textures::get_map_tile_set();
|
||||
|
||||
/* I'm doing double tid->wchar_t conversion here, maybe just
|
||||
* render the tids into the grid then let someone else do this. */
|
||||
|
||||
// first fill it with the map cells
|
||||
for(shiterator::each_cell_t it{grid}; it.next();) {
|
||||
size_t tile_y = size_t(it.y) + cam_orig.y;
|
||||
size_t tile_x = size_t(it.x) + cam_orig.x;
|
||||
|
||||
if(matrix::inbounds(tiles, tile_x, tile_y) && fow[tile_y][tile_x]) {
|
||||
size_t tid = tiles[tile_y][tile_x];
|
||||
grid[it.y][it.x] = tile_set[tid];
|
||||
} else {
|
||||
grid[it.y][it.x] = L' ';
|
||||
}
|
||||
}
|
||||
|
||||
// then get the enemy/item/device tiles and fill those in
|
||||
world.query<Position, Tile>([&](auto, auto &pos, auto &entity_glyph) {
|
||||
// BUG: don't I have a within bounds macro somewhere?
|
||||
if(pos.location.x >= cam_orig.x
|
||||
&& pos.location.x <= cam_orig.x + view_x
|
||||
&& pos.location.y >= cam_orig.y
|
||||
&& pos.location.y <= cam_orig.y + view_y)
|
||||
{
|
||||
if(fow[pos.location.y][pos.location.x]) {
|
||||
Point view_pos = map.map_to_camera(pos.location, cam_orig);
|
||||
entity_map.insert_or_assign(view_pos, entity_glyph.display);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void System::render_map(Matrix& tiles, EntityGrid& entity_map, sf::RenderTexture& render, int compass_dir, wchar_t player_display) {
|
||||
sf::Vector2i tile_sprite_dim{MAP_TILE_DIM,MAP_TILE_DIM};
|
||||
unsigned int width = matrix::width(tiles);
|
||||
unsigned int height = matrix::height(tiles);
|
||||
sf::Vector2u dim{width * tile_sprite_dim.x, height * tile_sprite_dim.y};
|
||||
auto render_size = render.getSize();
|
||||
|
||||
if(render_size.x != width || render_size.y != height) {
|
||||
bool worked = render.resize(dim);
|
||||
dbc::check(worked, "Failed to resize map render target.");
|
||||
}
|
||||
|
||||
render.clear({0,0,0,255});
|
||||
|
||||
for(matrix::each_row it{tiles}; it.next();) {
|
||||
wchar_t display = tiles[it.y][it.x];
|
||||
if(display == L' ') continue; // skip for now
|
||||
auto& sprite = textures::get_map_sprite(display);
|
||||
sprite.setPosition({float(it.x * tile_sprite_dim.x), float(it.y * tile_sprite_dim.y)});
|
||||
render.draw(sprite);
|
||||
}
|
||||
|
||||
for(auto [point, display] : entity_map) {
|
||||
auto& sprite = textures::get_map_sprite(display);
|
||||
|
||||
if(display == player_display) {
|
||||
sf::Vector2f center{float(tile_sprite_dim.x / 2), float(tile_sprite_dim.y / 2)};
|
||||
float degrees = (((compass_dir * 45) + PLAYER_SPRITE_DIR_CORRECTION) % 360);
|
||||
|
||||
sprite.setOrigin(center);
|
||||
sprite.setRotation(sf::degrees(degrees));
|
||||
sprite.setPosition({float(point.x * tile_sprite_dim.x) + center.x, float(point.y * tile_sprite_dim.y) + center.y});
|
||||
} else {
|
||||
sprite.setPosition({float(point.x * tile_sprite_dim.x), float(point.y * tile_sprite_dim.y)});
|
||||
}
|
||||
|
||||
render.draw(sprite);
|
||||
}
|
||||
|
||||
render.display();
|
||||
}
|
||||
|
||||
bool System::use_item(const string& slot_name) {
|
||||
auto& level = GameDB::current_level();
|
||||
auto& world = *level.world;
|
||||
auto& inventory = world.get<inventory::Model>(level.player);
|
||||
auto& player_combat = world.get<Combat>(level.player);
|
||||
|
||||
if(player_combat.hp >= player_combat.max_hp) return false;
|
||||
if(!inventory.has(slot_name)) return false;
|
||||
|
||||
auto what = inventory.get(slot_name);
|
||||
|
||||
if(auto curative = world.get_if<Curative>(what)) {
|
||||
inventory.remove(what);
|
||||
|
||||
player_combat.hp += curative->hp;
|
||||
|
||||
if(player_combat.hp > player_combat.max_hp) {
|
||||
player_combat.hp = player_combat.max_hp;
|
||||
}
|
||||
|
||||
dbc::log($F("player health now {}",
|
||||
player_combat.hp));
|
||||
|
||||
world.remove<Curative>(what);
|
||||
return true;
|
||||
} else {
|
||||
dbc::log($F("no usable item at {}", what));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
game::Event System::shortest_rotate(Point player_at, Point aiming_at, Point target) {
|
||||
dbc::check(aiming_at != target, "you're already pointing there.");
|
||||
dbc::check(player_at != target, "you can't turn on yourself");
|
||||
|
||||
float target_dx = float(player_at.x) - float(target.x);
|
||||
float target_dy = float(player_at.y) - float(target.y);
|
||||
float aiming_dx = float(player_at.x) - float(aiming_at.x);
|
||||
float aiming_dy = float(player_at.y) - float(aiming_at.y);
|
||||
|
||||
float target_angle = atan2(-target_dy, target_dx) * (180.0 / std::numbers::pi);
|
||||
float aiming_angle = atan2(-aiming_dy, aiming_dx) * (180.0 / std::numbers::pi);
|
||||
|
||||
float diff = target_angle - aiming_angle;
|
||||
double normalized = fmod(diff + 360.0, 360.0);
|
||||
|
||||
return normalized < 180.0 ? game::Event::ROTATE_LEFT : game::Event::ROTATE_RIGHT;
|
||||
}
|
||||
|
||||
void System::clear_attack() {
|
||||
auto world = GameDB::current_world();
|
||||
std::vector<Entity> dead_anim;
|
||||
|
||||
world->query<animation::Animation, Temporary>([&](auto ent, auto& anim, auto&) {
|
||||
if(!anim.playing) dead_anim.push_back(ent);
|
||||
});
|
||||
|
||||
for(auto ent : dead_anim) {
|
||||
world->remove<Sprite>(ent);
|
||||
world->remove<animation::Animation>(ent);
|
||||
world->remove<SpriteEffect>(ent);
|
||||
remove_from_world(ent);
|
||||
}
|
||||
}
|
||||
|
||||
void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) {
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue