Compare commits

...

10 commits

55 changed files with 401 additions and 319 deletions

View file

@ -37,7 +37,7 @@ tracy_build:
meson compile -j 10 -C builddir meson compile -j 10 -C builddir
test: build test: build
./builddir/runtests -d yes "[systems-engine]" ./builddir/runtests -d yes
run: build test run: build test
ifeq '$(OS)' 'Windows_NT' ifeq '$(OS)' 'Windows_NT'
@ -48,7 +48,7 @@ else
endif endif
debug: build debug: build
gdb --nx -x .gdbinit --ex run --args builddir/runtests "[systems-engine]" gdb --nx -x .gdbinit --ex run --args builddir/runtests
debug_run: build debug_run: build
gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster
@ -60,7 +60,7 @@ clean:
meson compile --clean -C builddir meson compile --clean -C builddir
debug_test: build debug_test: build
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests "[systems-engine]" gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests
win_installer: win_installer:
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'

View file

@ -65,7 +65,7 @@
"close": [] "close": []
} }
}, },
"rat_with_sword": { "spider_bot": {
"sheet": { "sheet": {
"frames": 1, "frames": 1,
"frame_width": 256, "frame_width": 256,
@ -98,14 +98,14 @@
"close": [] "close": []
} }
}, },
"female_hand": { "hands_sword_attack": {
"sheet": { "sheet": {
"frames": 3, "frames": 3,
"frame_width": 900, "frame_width": 512,
"frame_height": 600 "frame_height": 341
}, },
"sequences": { "sequences": {
"idle": {"frames": [0, 1, 2], "durations": [10, 10, 20] } "idle": {"frames": [0, 1, 2], "durations": [25, 5, 20] }
}, },
"transforms": { "transforms": {
"basic": { "basic": {
@ -115,7 +115,7 @@
"max_y": 1.0, "max_y": 1.0,
"flipped": false, "flipped": false,
"scaled": false, "scaled": false,
"toggled": true, "toggled": false,
"looped": false, "looped": false,
"relative": false, "relative": false,
"easing": "none", "easing": "none",

View file

@ -1,62 +1,29 @@
{ {
"sounds": { "sounds": {
"Sword_Hit_1": "assets/sounds/Creature_Sounds-Sword_Hit_1.ogg",
"Evil_Eye_Sound_1": "assets/sounds/Creature_Sounds-Evil_Eye_Sound_1.ogg",
"Evil_Eye_Sound_2": "assets/sounds/Creature_Sounds-Evil_Eye_Sound_2.ogg",
"Giant_Voice_1": "assets/sounds/Creature_Sounds-Giant_Voice_1.ogg",
"Medium_Rat": "assets/sounds/Creature_Sounds-Medium_Rat.ogg",
"Ranger_1": "assets/sounds/Creature_Sounds-Ranger_1.ogg",
"Small_Rat": "assets/sounds/Creature_Sounds-Small_Rat.ogg",
"Spider_1": "assets/sounds/Creature_Sounds-Spider_1.ogg",
"Spider_2": "assets/sounds/Creature_Sounds-Spider_2.ogg",
"Sword_Hit_1": "assets/sounds/Creature_Sounds-Sword_Hit_1.ogg",
"Sword_Hit_2": "assets/sounds/Creature_Sounds-Sword_Hit_2.ogg",
"walk": "assets/sounds/Creature_Sounds-Walk.ogg",
"Creature_Death_1": "assets/sounds/Creature_Sounds-Creature_Death_1.ogg",
"Humanoid_Death_1": "assets/sounds/Creature_Sounds-Humanoid_Death_1.ogg",
"Marmot_Scream_1": "assets/sounds/Creature_Sounds-Marmot_Scream_1.ogg",
"blank": "assets/sounds/blank.ogg", "blank": "assets/sounds/blank.ogg",
"pickup": "assets/sounds/pickup.ogg", "pickup": "assets/sounds/pickup.ogg",
"ambient_1": "assets/sounds/ambient_1.ogg",
"ui_click": "assets/sounds/ui_click.ogg", "ui_click": "assets/sounds/ui_click.ogg",
"ui_hover": "assets/sounds/ui_hover.ogg", "ui_hover": "assets/sounds/ui_hover.ogg",
"punch_cartoony": "assets/sounds/punch_cartoony.ogg", "walk": "assets/sounds/walk.ogg"
"electric_shock_01": "assets/sounds/electric_shock_01.ogg",
"fireball_01": "assets/sounds/fireball_01.ogg",
"hp_status_80": "assets/sounds/hp_status_80.ogg",
"hp_status_60": "assets/sounds/hp_status_60.ogg",
"hp_status_30": "assets/sounds/hp_status_30.ogg",
"hp_status_10": "assets/sounds/hp_status_10.ogg",
"hp_status_00": "assets/sounds/hp_status_00.ogg"
}, },
"sprites": { "sprites": {
"rat_with_sword": "spider_bot":
{"path": "assets/sprites/rat_with_sword.png", {"path": "assets/sprites/spider_bot.png",
"frame_width": 256, "frame_width": 256,
"frame_height": 256 "frame_height": 256
}, },
"torch_crappy": "repair_kit":
{"path": "assets/items/torch_crappy.png", {"path": "assets/items/repair_kit.png",
"frame_width": 256, "frame_width": 256,
"frame_height": 256 "frame_height": 256
}, },
"torch_horizontal_floor": "sword_1":
{"path": "assets/items/torch_horizontal_floor.png", {"path": "assets/items/sword_1_sprite.png",
"frame_width": 256, "frame_width": 256,
"frame_height": 256 "frame_height": 256
}, },
"peasant_girl": "ladder_down":
{"path": "assets/sprites/peasant_girl_2.png", {"path": "assets/sprites/ladder_down.png",
"frame_width": 256,
"frame_height": 256
},
"healing_potion_small":
{"path": "assets/items/healing_potion_small.png",
"frame_width": 256,
"frame_height": 256
},
"well_down":
{"path": "assets/sprites/well_down.png",
"frame_width": 256, "frame_width": 256,
"frame_height": 256 "frame_height": 256
}, },
@ -75,13 +42,8 @@
"frame_width": 256, "frame_width": 256,
"frame_height": 256 "frame_height": 256
}, },
"peasant_girl": "hands_sword_attack":
{"path": "assets/sprites/peasant_girl_2.png", {"path": "assets/hands/hands_sword_attack.png",
"frame_width": 256,
"frame_height": 256
},
"female_hand":
{"path": "assets/hands/female_hand.png",
"frame_width": 900, "frame_width": 900,
"frame_height": 600 "frame_height": 600
} }
@ -109,9 +71,9 @@
"border_px": 1, "border_px": 1,
"text_size": 20, "text_size": 20,
"label_size": 20, "label_size": 20,
"font_file_name": "assets/text.otf" "font_file_name": "assets/text.ttf"
}, },
"player": { "player": {
"hands": "female_hand" "hands": "hands_sword_attack"
} }
} }

View file

@ -14,7 +14,7 @@
{"_type": "Device", {"_type": "Device",
"config": {}, "config": {},
"events": ["STAIRS_DOWN"]}, "events": ["STAIRS_DOWN"]},
{"_type": "Sprite", "name": "well_down", "width": 256, "height": 256, "scale": 1.0} {"_type": "Sprite", "name": "ladder_down", "width": 256, "height": 256, "scale": 1.0}
] ]
}, },
"DEAD_BODY_LOOTABLE": { "DEAD_BODY_LOOTABLE": {

BIN
assets/doors/door_plain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -6,7 +6,7 @@
"foreground": "enemies/fg:player", "foreground": "enemies/fg:player",
"background": "color:transparent" "background": "color:transparent"
}, },
{"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false}, {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "Collision", "has": true}, {"_type": "Collision", "has": true},
{"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"}, {"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"},
@ -14,19 +14,18 @@
{"_type": "LightSource", "strength": 35, "radius": 2.0} {"_type": "LightSource", "strength": 35, "radius": 2.0}
] ]
}, },
"RAT_GIANT": { "SPIDER_BOT": {
"components": [ "components": [
{"_type": "Tile", "display": 2220, {"_type": "Tile", "display": 2220,
"foreground": "enemies/fg:rat_giant", "foreground": "enemies/fg:rat_giant",
"background": "color:transparent" "background": "color:transparent"
}, },
{"_type": "Combat", "hp": 50, "max_hp": 50, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false}, {"_type": "Combat", "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false},
{"_type": "Collision", "has": true}, {"_type": "Collision", "has": true},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}, {"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": false}, {"_type": "Personality", "hearing_distance": 5, "tough": false},
{"_type": "Sprite", "name": "rat_with_sword", "scale": 1.0}, {"_type": "Sprite", "name": "spider_bot", "scale": 1.0}
{"_type": "Sound", "attack": "Small_Rat", "death": "Creature_Death_1"}
] ]
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,12 +1,12 @@
{ {
"healing_potion_small": "repair_kit":
{"path": "assets/icons/healing_potion_small.png", {"path": "assets/icons/repair_kit.png",
"frame_width": 96, "frame_width": 96,
"frame_height": 96 "frame_height": 96
}, },
"torch_horizontal_floor": "sword_1":
{"path": "assets/icons/torch_horizontal_floor.png", {"path": "assets/icons/sword_1_icon.png",
"frame_width": 96, "frame_width": 300,
"frame_height": 96 "frame_height": 100
} }
} }

BIN
assets/icons/repair_kit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,23 +1,8 @@
{ {
"TORCH_BAD": { "REPAIR_KIT": {
"id": "TORCH_BAD", "id": "REPAIR_KIT",
"name": "Crappy Torch", "name": "Robot Repair Kit",
"description": "A torch that barely lights the way. You wonder if it'd be better to not see the person who murders you.", "description": "A healing item for robots.",
"inventory_count": 1,
"components": [
{"_type": "LightSource", "strength": 50, "radius": 2.5},
{"_type": "Tile", "display": 3848,
"foreground": "items/fg:flame",
"background": "color:transparent"
},
{"_type": "Sprite", "name": "torch_horizontal_floor", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"POTION_HEALING_SMALL": {
"id": "POTION_HEALING_SMALL",
"name": "Small Healing Potion",
"description": "A small healing potion.",
"inventory_count": 1, "inventory_count": 1,
"components": [ "components": [
{"_type": "Tile", "display": 1003, {"_type": "Tile", "display": 1003,
@ -25,7 +10,21 @@
"background": "color:transparent" "background": "color:transparent"
}, },
{"_type": "Curative", "hp": 20}, {"_type": "Curative", "hp": 20},
{"_type": "Sprite", "name": "healing_potion_small", "width": 256, "height": 256, "scale": 1.0}, {"_type": "Sprite", "name": "repair_kit", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"SWORD_1": {
"id": "SWORD_1",
"name": "Robot Repair Kit",
"description": "A healing item for robots.",
"inventory_count": 1,
"components": [
{"_type": "Tile", "display": 1004,
"foreground": "items/fg:potion",
"background": "color:transparent"
},
{"_type": "Sprite", "name": "sword_1", "width": 300, "height": 100, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"} {"_type": "Sound", "attack": "pickup", "death": "blank"}
] ]
} }

BIN
assets/items/repair_kit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/map_tiles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -5,17 +5,17 @@
}, },
"gui/theme": { "gui/theme": {
"black": [0, 0, 0, 255], "black": [0, 0, 0, 255],
"dark_dark": [10, 10, 10, 255], "dark_dark": [22, 10, 7, 255],
"dark_mid": [30, 30, 30, 255], "dark_mid": [53, 25, 18, 255],
"dark_light": [60, 60, 60, 255], "dark_light": [91, 42, 31, 255],
"mid": [100, 100, 100, 255], "mid": [142, 65, 48, 255],
"light_dark": [150, 150, 150, 255], "light_dark": [193, 89, 65, 255],
"light_mid": [200, 200, 200, 255], "light_mid": [255, 117, 86, 255],
"light_light": [230, 230, 230, 255], "light_light": [255, 194, 181, 255],
"white": [255, 255, 255, 255], "white": [255, 255, 255, 255],
"fill_color": "gui/theme:dark_mid", "fill_color": [28, 29, 33, 255],
"text_color": "gui/theme:light_light", "text_color": [209, 209, 209, 255],
"bg_color": "gui/theme:mid", "bg_color": "gui/theme:dark_light",
"border_color": "gui/theme:dark_dark", "border_color": "gui/theme:dark_dark",
"bg_color_dark": "gui/theme:black" "bg_color_dark": "gui/theme:black"
}, },

BIN
assets/sounds/blank.ogg Normal file

Binary file not shown.

BIN
assets/sounds/pickup.ogg Normal file

Binary file not shown.

BIN
assets/sounds/ui_click.ogg Normal file

Binary file not shown.

BIN
assets/sounds/ui_hover.ogg Normal file

Binary file not shown.

BIN
assets/sounds/walk.ogg Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
assets/text.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -8,9 +8,30 @@ namespace components {
if(attack) { if(attack) {
my_dmg = Random::uniform<int>(1, damage); my_dmg = Random::uniform<int>(1, damage);
target.hp -= my_dmg; target.hit_limb(my_dmg);
} }
return my_dmg; return my_dmg;
} }
void Combat::hit_limb(int my_dmg) {
body_parts["head"] -= my_dmg;
}
bool Combat::is_dead() {
return body_parts["head"] < 0;
}
bool Combat::almost_dead() {
return body_parts["head"] < 20;
}
bool Combat::can_heal() {
return body_parts["head"] < 50;
}
void Combat::apply_healing(Curative& cure) {
int new_hp = body_parts["head"] + cure.hp;
body_parts["head"] = std::min(new_hp, 50);
}
} }

View file

@ -83,10 +83,6 @@ namespace components {
std::string ai_goal_name; std::string ai_goal_name;
}; };
struct Curative {
int hp = 10;
};
struct Sprite { struct Sprite {
string name; string name;
float scale; float scale;
@ -106,12 +102,17 @@ namespace components {
std::vector<std::array<std::string, 4>> beats; std::vector<std::array<std::string, 4>> beats;
}; };
struct Curative {
int hp = 10;
};
struct Combat { struct Combat {
int hp;
int max_hp;
int ap_delta; int ap_delta;
int max_ap; int max_ap;
int damage; int damage;
std::unordered_map<std::string, int> body_parts{
{"head", 50},
};
// everyone starts at 0 but ap_delta is added each round // everyone starts at 0 but ap_delta is added each round
int ap = 0; int ap = 0;
@ -120,6 +121,11 @@ namespace components {
bool dead = false; bool dead = false;
int attack(Combat &target); int attack(Combat &target);
void hit_limb(int my_dmg);
bool is_dead();
bool almost_dead();
bool can_heal();
void apply_healing(Curative& cure);
}; };
struct LightSource { struct LightSource {
@ -156,7 +162,7 @@ namespace components {
ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name); ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name);
ENROLL_COMPONENT(Personality, hearing_distance, tough); ENROLL_COMPONENT(Personality, hearing_distance, tough);
ENROLL_COMPONENT(Motion, dx, dy, random); ENROLL_COMPONENT(Motion, dx, dy, random);
ENROLL_COMPONENT(Combat, hp, max_hp, ap_delta, max_ap, damage, dead); ENROLL_COMPONENT(Combat, ap_delta, max_ap, damage, dead);
ENROLL_COMPONENT(Device, config, events); ENROLL_COMPONENT(Device, config, events);
ENROLL_COMPONENT(Storyboard, image, audio, layout, beats); ENROLL_COMPONENT(Storyboard, image, audio, layout, beats);
ENROLL_COMPONENT(Sound, attack, death); ENROLL_COMPONENT(Sound, attack, death);

82
src/game/registry.hpp Normal file
View file

@ -0,0 +1,82 @@
#pragma once
#include <functional>
#include "game/components.hpp"
namespace System {
using MovingFunc = std::function<void(components::Position& move_to)>;
using CombatFunc = std::function<void(int attack_id)>;
using RenderFunc = std::function<void()>;
using UpdateFunc = std::function<void()>;
using UseItemFunc = std::function<void(const std::string& slot_name)>;
using PickupFunc = std::function<void()>;
struct Registry {
std::vector<MovingFunc> $moving;
std::vector<CombatFunc> $combat;
std::vector<RenderFunc> $render;
std::vector<UpdateFunc> $update;
std::vector<UseItemFunc> $use_item;
std::vector<PickupFunc> $pickup;
void addMoving(MovingFunc action) {
$moving.emplace_back(action);
}
void addCombat(CombatFunc action) {
$combat.emplace_back(action);
}
void addRender(RenderFunc action) {
$render.emplace_back(action);
}
void addUpdate(UpdateFunc action) {
$update.emplace_back(action);
}
void addUseItem(UseItemFunc action) {
$use_item.emplace_back(action);
}
void addPickup(PickupFunc action) {
$pickup.emplace_back(action);
}
void runMoving(components::Position move_to) {
for(auto func : $moving) {
func(move_to);
}
}
void runCombat(int attack_id) {
for(auto func : $combat) {
func(attack_id);
}
}
void runRender() {
for(auto func : $render) {
func();
}
}
void runUpdate() {
for(auto func : $update) {
func();
}
}
void runUseItem(const std::string& slot_name) {
for(auto func : $use_item) {
func(slot_name);
}
}
void runPickup() {
for(auto func : $pickup) {
func();
}
}
};
}

View file

@ -167,15 +167,14 @@ void System::distribute_loot(Position target_pos) {
auto loot_entity = world.entity(); auto loot_entity = world.entity();
if(inventory_count > 0) { if(inventory_count > 0) {
auto& entity_data = config.devices["DEAD_BODY_LOOTABLE"]; dbc::log("!!!!!!!!!!!!!!!! ============= LOOTING BODIES NOT READY");
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}); // NOTE: refer to the code in raycaster for this
} else {
// this creates a dead body on the ground // this creates a dead body on the ground
auto& entity_data = config.devices["DEAD_BODY"]; auto& entity_data = config.devices["DEAD_BODY"];
components::configure_entity(world, loot_entity, entity_data["components"]); components::configure_entity(world, loot_entity, entity_data["components"]);
}
set_position(world, *level.collision, loot_entity, target_pos); set_position(world, *level.collision, loot_entity, target_pos);
level.world->send<game::Event>(game::Event::ENTITY_SPAWN, loot_entity, {}); level.world->send<game::Event>(game::Event::ENTITY_SPAWN, loot_entity, {});
@ -189,7 +188,7 @@ void System::death() {
world.query<Combat>([&](auto ent, auto &combat) { world.query<Combat>([&](auto ent, auto &combat) {
// bring out yer dead // bring out yer dead
if(combat.hp <= 0 && !combat.dead) { if(combat.is_dead() && !combat.dead) {
combat.dead = true; combat.dead = true;
if(ent != player.entity) { if(ent != player.entity) {
// we won't change out the player's components later // we won't change out the player's components later
@ -197,7 +196,7 @@ void System::death() {
} }
// we need to send this event for everything that dies // we need to send this event for everything that dies
world.send<game::Event>(game::Event::DEATH, ent, {}); world.send<game::Event>(game::Event::DEATH, ent, {});
} else if(float(combat.hp) / float(combat.max_hp) < 0.5f) { } else if(combat.almost_dead()) {
// if enemies are below 50% health they are marked with bad health // if enemies are below 50% health they are marked with bad health
if(world.has<ai::EntityAI>(ent)) { if(world.has<ai::EntityAI>(ent)) {
auto& enemy_ai = world.get<ai::EntityAI>(ent); auto& enemy_ai = world.get<ai::EntityAI>(ent);
@ -474,34 +473,23 @@ bool System::inventory_occupied(Entity container_id, const std::string& name) {
return inventory.has(name); return inventory.has(name);
} }
bool System::use_item(const string& slot_name) { void System::use_item(const string& slot_name) {
auto& level = GameDB::current_level(); auto& level = GameDB::current_level();
auto& world = *level.world; auto& world = *level.world;
auto& inventory = world.get<inventory::Model>(level.player); auto& inventory = world.get<inventory::Model>(level.player);
auto& player_combat = world.get<Combat>(level.player); auto& player_combat = world.get<Combat>(level.player);
if(player_combat.hp >= player_combat.max_hp) return false; if(!player_combat.can_heal()) return;
if(!inventory.has(slot_name)) return false; if(!inventory.has(slot_name)) return;
auto what = inventory.get(slot_name); auto what = inventory.get(slot_name);
if(auto curative = world.get_if<Curative>(what)) { if(auto curative = world.get_if<Curative>(what)) {
inventory.remove(what); inventory.remove(what);
player_combat.apply_healing(*curative);
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); world.remove<Curative>(what);
return true;
} else { } else {
dbc::log($F("no usable item at {}", what)); dbc::log($F("no usable item at {}", what));
return false;
} }
} }
@ -541,3 +529,19 @@ void System::clear_attack() {
void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) { void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) {
} }
void System::init(Registry& reg) {
reg.addRender(System::clear_attack);
reg.addUseItem(System::use_item);
reg.addMoving(System::move_player);
reg.addCombat(System::combat);
reg.addPickup(System::pickup);
reg.addUpdate(System::generate_paths);
reg.addUpdate(System::enemy_ai_initialize);
reg.addUpdate(System::enemy_pathing);
reg.addUpdate(System::motion);
reg.addUpdate(System::collision);
reg.addUpdate(System::lighting);
reg.addUpdate(System::death);
}

View file

@ -5,8 +5,10 @@
#include "algos/spatialmap.hpp" #include "algos/spatialmap.hpp"
#include "game/level.hpp" #include "game/level.hpp"
#include "events.hpp" #include "events.hpp"
#include "game/registry.hpp"
namespace System { namespace System {
using namespace components; using namespace components;
using namespace DinkyECS; using namespace DinkyECS;
using std::string, matrix::Matrix; using std::string, matrix::Matrix;
@ -32,15 +34,15 @@ namespace System {
void pickup(); void pickup();
// BUG: these might need to go somewhere else....
bool place_in_container(Entity cont_id, const string& name, Entity world_entity); bool place_in_container(Entity cont_id, const string& name, Entity world_entity);
void remove_from_container(Entity cont_id, const std::string& name); void remove_from_container(Entity cont_id, const std::string& name);
void remove_from_world(Entity entity); void remove_from_world(Entity entity);
void inventory_swap(Entity container_id, const std::string& a_name, const std::string &b_name); void inventory_swap(Entity container_id, const std::string& a_name, const std::string &b_name);
bool inventory_occupied(Entity container_id, const std::string& name); bool inventory_occupied(Entity container_id, const std::string& name);
void set_position(DinkyECS::World& world, SpatialMap& collision, Entity entity, Position pos); void set_position(DinkyECS::World& world, SpatialMap& collision, Entity entity, Position pos);
bool use_item(const std::string& slot_name); void use_item(const std::string& slot_name);
game::Event shortest_rotate(Point player_at, Point aiming_at, Point turning_to); game::Event shortest_rotate(Point player_at, Point aiming_at, Point turning_to);
@ -67,4 +69,6 @@ namespace System {
void clear_attack(); void clear_attack();
void spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy); void spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy);
void init(Registry& reg);
} }

View file

@ -199,15 +199,14 @@ void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) {
void WorldBuilder::configure_starting_items(DinkyECS::World &world) { void WorldBuilder::configure_starting_items(DinkyECS::World &world) {
auto& player = world.get_the<Player>(); auto& player = world.get_the<Player>();
auto torch_id = System::spawn_item(world, "TORCH_BAD");
auto &inventory = world.get<inventory::Model>(player.entity); auto &inventory = world.get<inventory::Model>(player.entity);
inventory.add("hand_r", torch_id);
world.make_constant(torch_id);
auto healing = System::spawn_item(world, "POTION_HEALING_SMALL"); auto healing = System::spawn_item(world, "REPAIR_KIT");
inventory.add("pocket_l", healing); inventory.add("inv0", healing);
auto sword = System::spawn_item(world, "SWORD_1");
inventory.add("hand_main", sword);
world.make_constant(healing); world.make_constant(healing);
} }

View file

@ -121,7 +121,7 @@ inline void set_scale_position(sf::Sprite& sprite, sf::Vector2f& position, sf::V
} }
void Raycaster::sprite_casting() { void Raycaster::sprite_casting(sf::RenderTarget& target) {
auto& lights = $level.lights->lighting(); auto& lights = $level.lights->lighting();
auto world = $level.world; auto world = $level.world;
$level.collision->distance_sorted($sprite_order, {(size_t)$pos_x, (size_t)$pos_y}, RENDER_DISTANCE); $level.collision->distance_sorted($sprite_order, {(size_t)$pos_x, (size_t)$pos_y}, RENDER_DISTANCE);
@ -212,7 +212,6 @@ void Raycaster::sprite_casting() {
sf::Vector2f scale{sprite_scale_w, sprite_scale_h}; sf::Vector2f scale{sprite_scale_w, sprite_scale_h};
sf::Vector2f position{x + origin.x * scale.x, y + origin.y * scale.y}; sf::Vector2f position{x + origin.x * scale.x, y + origin.y * scale.y};
sf::IntRect in_texture{ {tex_x, tex_y}, {tex_render_width, texture_height}}; sf::IntRect in_texture{ {tex_x, tex_y}, {tex_render_width, texture_height}};
shared_ptr<sf::Shader> effect = System::sprite_effect(rec.entity); shared_ptr<sf::Shader> effect = System::sprite_effect(rec.entity);
if(effect) { if(effect) {
@ -231,7 +230,7 @@ void Raycaster::sprite_casting() {
set_scale_position(*sf_sprite, position, scale, in_texture, origin); set_scale_position(*sf_sprite, position, scale, in_texture, origin);
} }
$sprites_to_render.emplace_back(sf_sprite, effect); target.draw(*sf_sprite, effect.get());
} }
} }
} }
@ -438,16 +437,12 @@ void Raycaster::update() {
draw_ceiling_floor(); draw_ceiling_floor();
cast_rays(); cast_rays();
draw_pixel_buffer(); draw_pixel_buffer();
sprite_casting();
} }
// BUG: if target is a rendertarget then I can say it has to be the viewport only, then I can get rid of $screen_pos_x/y // BUG: if target is a rendertarget then I can say it has to be the viewport only, then I can get rid of $screen_pos_x/y
void Raycaster::render(sf::RenderTarget& target) { void Raycaster::render(sf::RenderTarget& target) {
target.draw($view_sprite); target.draw($view_sprite);
sprite_casting(target);
for(auto [sprite, effect] : $sprites_to_render) {
target.draw(*sprite, effect.get());
}
} }
void Raycaster::update_sprite(DinkyECS::Entity ent, components::Sprite& sprite) { void Raycaster::update_sprite(DinkyECS::Entity ent, components::Sprite& sprite) {

View file

@ -21,7 +21,7 @@ struct CameraLOL {
double target_plane_y = 0.0; double target_plane_y = 0.0;
}; };
using SpriteRender = std::pair<std::shared_ptr<sf::Sprite>, std::shared_ptr<sf::Shader>>; using SpriteRender = std::pair<sf::Sprite, std::shared_ptr<sf::Shader>>;
struct Raycaster { struct Raycaster {
sf::Texture $view_texture; sf::Texture $view_texture;
@ -64,7 +64,7 @@ struct Raycaster {
void cast_rays(); void cast_rays();
void draw_ceiling_floor(); void draw_ceiling_floor();
void draw_pixel_buffer(); void draw_pixel_buffer();
void sprite_casting(); void sprite_casting(sf::RenderTarget& target);
void update(); void update();
void render(sf::RenderTarget& target); void render(sf::RenderTarget& target);

61
src/gui/body_ui.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "gui/body_ui.hpp"
#include "game/components.hpp"
#include <guecs/ui.hpp>
#include "algos/rand.hpp"
#include <fmt/xchar.h>
#include "gui/guecstra.hpp"
#include "game/systems.hpp"
#include "game/inventory.hpp"
#include "game/level.hpp"
namespace gui {
using namespace guecs;
using std::any, std::any_cast, std::string, std::make_any;
void BodyUI::init(size_t x, size_t y, size_t width, size_t height) {
$gui.position(x, y, width, height);
$gui.layout(
"[head]"
"[chest]"
"[right_arm]"
"[left_arm]"
"[stomach]"
"[left_leg]"
"[right_leg]");
$gui.set<Background>($gui.MAIN, {$gui.$parser, });
for(auto& [name, cell] : $gui.cells()) {
auto gui_id = $gui.entity(name);
$gui.set<Text>(gui_id, {guecs::to_wstring(name)});
$gui.set<Meter>(gui_id, {1.0f, THEME.DARK_MID, {}});
}
$gui.init();
update();
}
bool BodyUI::mouse(float x, float y, guecs::Modifiers mods) {
return $gui.mouse(x, y, mods);
}
void BodyUI::update() {
auto world = GameDB::current_world();
auto& player = world->get_the<components::Player>();
auto& player_combat = world->get<components::Combat>(player.entity);
for(auto& [key, value] : player_combat.body_parts) {
auto gui_id = $gui.entity(key);
if(auto meter = $gui.get_if<Meter>(gui_id)) {
meter->percent = float(value) / 50.0;
}
}
}
void BodyUI::render(sf::RenderWindow &window) {
$gui.render(window);
// $gui.debug_layout(window);
}
}

18
src/gui/body_ui.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "constants.hpp"
#include <deque>
#include "graphics/textures.hpp"
#include <guecs/ui.hpp>
#include "gui/guecstra.hpp"
namespace gui {
class BodyUI {
public:
guecs::UI $gui{};
void init(size_t x, size_t y, size_t width, size_t height);
void render(sf::RenderWindow &window);
void update();
bool mouse(float x, float y, guecs::Modifiers mods);
};
}

View file

@ -19,7 +19,7 @@ namespace gui {
"[spawn1|spawn2|spawn3]" "[spawn1|spawn2|spawn3]"
"[spawn4|spawn5|spawn6]"); "[spawn4|spawn5|spawn6]");
add_spawn_button("RAT_GIANT", "rat_with_sword", "spawn4"); // add_spawn_button("RAT_GIANT", "rat_with_sword", "spawn4");
$gui.init(); $gui.init();
} }
@ -48,7 +48,6 @@ namespace gui {
auto map = level.map; auto map = level.map;
std::wstring stats = $F(L"STATS\n" std::wstring stats = $F(L"STATS\n"
L"HP: {}\n"
L"mean:{:>8.5}\n" L"mean:{:>8.5}\n"
L"sdev: {:>8.5}\n" L"sdev: {:>8.5}\n"
L"min: {:>8.5}\n" L"min: {:>8.5}\n"
@ -58,7 +57,7 @@ namespace gui {
L"VSync? {}\n" L"VSync? {}\n"
L"FR Limit: {}\n" L"FR Limit: {}\n"
L"Debug? {}\n\n", L"Debug? {}\n\n",
player_combat.hp, $stats.mean(), $stats.stddev(), $stats.min, $stats.mean(), $stats.stddev(), $stats.min,
$stats.max, $stats.n, level.index, map->width(), map->height(), $stats.max, $stats.n, level.index, map->width(), map->height(),
VSYNC, FRAME_LIMIT, DEBUG_BUILD); VSYNC, FRAME_LIMIT, DEBUG_BUILD);
@ -74,9 +73,6 @@ namespace gui {
if(active) { if(active) {
auto& level = GameDB::current_level(); auto& level = GameDB::current_level();
// it's on now, enable things // it's on now, enable things
auto player = level.world->get_the<components::Player>();
auto& player_combat = level.world->get<components::Combat>(player.entity);
player_combat.hp = player_combat.max_hp;
$gui.show_text("debug_text", L"STATS"); $gui.show_text("debug_text", L"STATS");
} else { } else {
// it's off now, close it // it's off now, close it

View file

@ -58,7 +58,7 @@ namespace gui {
void FSM::MOVING(Event ) { void FSM::MOVING(Event ) {
// this should be an optional that returns a point // this should be an optional that returns a point
if(auto move_to = $main_ui.play_move()) { if(auto move_to = $main_ui.play_move()) {
System::move_player(*move_to); $systems.runMoving(*move_to);
run_systems(); run_systems();
$main_ui.dirty(); $main_ui.dirty();
state(State::IDLE); state(State::IDLE);
@ -70,7 +70,7 @@ namespace gui {
switch(ev) { switch(ev) {
case TICK: { case TICK: {
dbc::log("!!!!!! FIX System::combat(0) doesn't use any weapons, only first"); dbc::log("!!!!!! FIX System::combat(0) doesn't use any weapons, only first");
System::combat(0); $systems.runCombat(0);
run_systems(); run_systems();
state(State::IN_COMBAT); state(State::IN_COMBAT);
} break; } break;
@ -79,7 +79,7 @@ namespace gui {
break; break;
case ATTACK: { case ATTACK: {
int attack_id = std::any_cast<int>(data); int attack_id = std::any_cast<int>(data);
System::combat(attack_id); $systems.runCombat(attack_id);
run_systems(); run_systems();
} break; } break;
default: default:
@ -174,10 +174,8 @@ namespace gui {
case USE_ITEM: { case USE_ITEM: {
auto gui_id = std::any_cast<guecs::Entity>(data); auto gui_id = std::any_cast<guecs::Entity>(data);
auto& slot_name = $status_ui.$gui.name_for(gui_id); auto& slot_name = $status_ui.$gui.name_for(gui_id);
$systems.runUseItem(slot_name);
if(System::use_item(slot_name)) {
$status_ui.update(); $status_ui.update();
}
} break; } break;
case MOUSE_CLICK: case MOUSE_CLICK:
mouse_action(guecs::NO_MODS); mouse_action(guecs::NO_MODS);
@ -186,7 +184,7 @@ namespace gui {
mouse_action({1 << guecs::ModBit::hover}); mouse_action({1 << guecs::ModBit::hover});
} break; } break;
case AIM_CLICK: case AIM_CLICK:
System::pickup(); $systems.runPickup();
break; break;
default: default:
break; // ignore everything else break; // ignore everything else
@ -312,9 +310,6 @@ namespace gui {
$debug_ui.debug(); $debug_ui.debug();
shaders::reload(); shaders::reload();
break; break;
case KEY::O:
autowalking = true;
break;
case KEY::L: case KEY::L:
// This will go away as soon as containers work // This will go away as soon as containers work
$loot_ui.set_target($loot_ui.$temp_loot); $loot_ui.set_target($loot_ui.$temp_loot);
@ -360,10 +355,8 @@ namespace gui {
} }
void FSM::render() { void FSM::render() {
$window.clear();
// this clears any attack animations, like fire // this clears any attack animations, like fire
System::clear_attack(); $systems.runRender();
// BUG: this is the render for this class, and where I add an update // BUG: this is the render for this class, and where I add an update
draw_gui(); draw_gui();
@ -371,13 +364,7 @@ namespace gui {
} }
void FSM::run_systems() { void FSM::run_systems() {
System::generate_paths(); $systems.runUpdate();
System::enemy_ai_initialize();
System::enemy_pathing();
System::motion();
System::collision();
System::lighting();
System::death();
} }
bool FSM::active() { bool FSM::active() {

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "constants.hpp" #include "constants.hpp"
#include "game/registry.hpp"
#include "algos/simplefsm.hpp" #include "algos/simplefsm.hpp"
#include "gui/debug_ui.hpp" #include "gui/debug_ui.hpp"
#include "gui/main_ui.hpp" #include "gui/main_ui.hpp"
@ -35,12 +36,11 @@ namespace gui {
LootUI $loot_ui; LootUI $loot_ui;
gui::routing::Router $router; gui::routing::Router $router;
DNDLoot $dnd_loot; DNDLoot $dnd_loot;
System::Registry $systems;
FSM(); FSM();
void event(game::Event ev, std::any data={}); void event(game::Event ev, std::any data={});
void autowalk();
void start_autowalk(double rot_speed);
void START(game::Event ev); void START(game::Event ev);
void MOVING(game::Event ev); void MOVING(game::Event ev);

View file

@ -78,6 +78,7 @@ namespace gui {
dbc::check(world->has<components::Sprite>(item), dbc::check(world->has<components::Sprite>(item),
"item in inventory UI doesn't exist in world. New level?"); "item in inventory UI doesn't exist in world. New level?");
auto& sprite = world->get<components::Sprite>(item); auto& sprite = world->get<components::Sprite>(item);
fmt::println("!!!!!!!!!!! trying to load {}", sprite.name);
$gui.set_init<guecs::Icon>(id, {sprite.name}); $gui.set_init<guecs::Icon>(id, {sprite.name});
guecs::GrabSource grabber{ guecs::GrabSource grabber{

View file

@ -17,9 +17,11 @@ namespace gui {
$window.setFramerateLimit(FRAME_LIMIT); $window.setFramerateLimit(FRAME_LIMIT);
auto config = settings::get("config"); auto config = settings::get("config");
$hand = textures::get_sprite(config["player"]["hands"]); $hand = textures::get_sprite(config["player"]["hands"]);
$hand_anim = animation::load("assets/animation.json", config["player"]["hands"]); $hand_anim = animation::load("assets/animation.json", config["player"]["hands"]);
int width = $hand_anim.sheet.frame_width;
int height = $hand_anim.sheet.frame_height;
$hand.sprite->setPosition({float(RAY_VIEW_X + (RAY_VIEW_WIDTH - width) / 2), float(RAY_VIEW_HEIGHT - height)});
} }
void MainUI::dirty() { void MainUI::dirty() {
@ -148,7 +150,6 @@ namespace gui {
if($hand_anim.playing) { if($hand_anim.playing) {
$hand_anim.update(); $hand_anim.update();
$hand_anim.apply(*$hand.sprite); $hand_anim.apply(*$hand.sprite);
$hand.sprite->setPosition({RAY_VIEW_X, RAY_VIEW_Y});
$window.draw(*$hand.sprite); $window.draw(*$hand.sprite);
} }
} }

View file

@ -18,12 +18,13 @@ namespace gui {
$gui.init(); $gui.init();
} }
inline void make_clickable_area(guecs::UI &gui, const std::string &name) { inline void make_clickable_area(guecs::UI &gui, const std::string name) {
auto area = gui.entity(name); auto area = gui.entity(name);
gui.set<Clickable>(area, { gui.set<Clickable>(area, {
[&](auto) { [=](auto) {
auto world = GameDB::current_world(); auto world = GameDB::current_world();
fmt::println("CLICK {}", name);
world->send<game::Event>(game::Event::AIM_CLICK, area, {}); world->send<game::Event>(game::Event::AIM_CLICK, area, {});
} }
}); });

View file

@ -16,10 +16,14 @@ namespace gui {
{ {
$gui.position(x, y, width, height); $gui.position(x, y, width, height);
$gui.layout( $gui.layout(
"[=slot1]" "[*%(100, 300)body_ui]"
"[=slot2]" "[_]"
"[=hand_r]" "[_]"
"[=pocket_l]"); "[=inv0|=inv1|=inv2]"
"[=inv3|=inv4|=inv5]"
"[=inv6|=inv7|=inv8]"
"[=hand_main]"
"[=hand_off]");
} }
void StatusUI::init() { void StatusUI::init() {
@ -28,9 +32,13 @@ namespace gui {
for(auto& [name, cell] : $gui.cells()) { for(auto& [name, cell] : $gui.cells()) {
auto gui_id = $gui.entity(name); auto gui_id = $gui.entity(name);
$gui.set<Rectangle>(gui_id, {});
$gui.set<Text>(gui_id, {guecs::to_wstring(name)}); $gui.set<Text>(gui_id, {guecs::to_wstring(name)});
if(name.starts_with("body_")) {
auto& cell = $gui.cell_for(name);
$body_ui.init(cell.x, cell.y, cell.w, cell.h);
} else {
$gui.set<Rectangle>(gui_id, {});
$gui.set<Clickable>(gui_id, { $gui.set<Clickable>(gui_id, {
guecs::make_action(gui_id, game::Event::INV_SELECT, {gui_id}) guecs::make_action(gui_id, game::Event::INV_SELECT, {gui_id})
}); });
@ -40,6 +48,7 @@ namespace gui {
} }
}); });
} }
}
$gui.init(); $gui.init();
update(); update();
@ -54,6 +63,8 @@ namespace gui {
auto player = world->get_the<components::Player>(); auto player = world->get_the<components::Player>();
auto& inventory = world->get<inventory::Model>(player.entity); auto& inventory = world->get<inventory::Model>(player.entity);
$body_ui.update();
for(const auto& [slot, cell] : $gui.cells()) { for(const auto& [slot, cell] : $gui.cells()) {
if(inventory.has(slot)) { if(inventory.has(slot)) {
@ -79,6 +90,7 @@ namespace gui {
void StatusUI::render(sf::RenderWindow &window) { void StatusUI::render(sf::RenderWindow &window) {
$gui.render(window); $gui.render(window);
$body_ui.render(window);
// $gui.debug_layout(window); // $gui.debug_layout(window);
} }

View file

@ -4,11 +4,13 @@
#include "graphics/textures.hpp" #include "graphics/textures.hpp"
#include <guecs/ui.hpp> #include <guecs/ui.hpp>
#include "gui/guecstra.hpp" #include "gui/guecstra.hpp"
#include "gui/body_ui.hpp"
namespace gui { namespace gui {
class StatusUI { class StatusUI {
public: public:
guecs::UI $gui; guecs::UI $gui{};
BodyUI $body_ui{};
explicit StatusUI(size_t x, size_t y, size_t width, size_t height); explicit StatusUI(size_t x, size_t y, size_t width, size_t height);

View file

@ -7,6 +7,7 @@
#include "gui/backend.hpp" #include "gui/backend.hpp"
#include "game/level.hpp" #include "game/level.hpp"
#include "graphics/camera.hpp" #include "graphics/camera.hpp"
#include "game/systems.hpp"
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
try { try {
@ -21,6 +22,7 @@ int main(int argc, char* argv[]) {
sound::mute(true); sound::mute(true);
gui::FSM main; gui::FSM main;
System::init(main.$systems);
main.event(game::Event::START); main.event(game::Event::START);
sound::play("ambient_1", true); sound::play("ambient_1", true);

View file

@ -19,6 +19,7 @@ sources = files(
'gui/status_ui.cpp', 'gui/status_ui.cpp',
'gui/main_ui.cpp', 'gui/main_ui.cpp',
'gui/overlay_ui.cpp', 'gui/overlay_ui.cpp',
'gui/body_ui.cpp',
# graphics # graphics
'graphics/animation.cpp', 'graphics/animation.cpp',

View file

@ -25,20 +25,17 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
DinkyECS::Entity host = 0; DinkyECS::Entity host = 0;
ai::EntityAI host_ai("Host::actions", host_start, host_goal); ai::EntityAI host_ai("Host::actions", host_start, host_goal);
components::Combat host_combat{ components::Combat host_combat{.ap_delta=6, .max_ap=12, .damage=20};
.hp=100, .max_hp=100, .ap_delta=6, .max_ap=12, .damage=20};
battle.add_enemy({host, &host_ai, &host_combat, true}); battle.add_enemy({host, &host_ai, &host_combat, true});
DinkyECS::Entity axe_ranger = 1; DinkyECS::Entity axe_ranger = 1;
ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal);
components::Combat axe_combat{ components::Combat axe_combat{.ap_delta=8, .max_ap=12, .damage=20};
.hp=20, .max_hp=20, .ap_delta=8, .max_ap=12, .damage=20};
battle.add_enemy({axe_ranger, &axe_ai, &axe_combat}); battle.add_enemy({axe_ranger, &axe_ai, &axe_combat});
DinkyECS::Entity rat = 2; DinkyECS::Entity rat = 2;
ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal); ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal);
components::Combat rat_combat{ components::Combat rat_combat{.ap_delta=2, .max_ap=10, .damage=10};
.hp=10, .max_hp=10, .ap_delta=2, .max_ap=10, .damage=10};
battle.add_enemy({rat, &rat_ai, &rat_combat}); battle.add_enemy({rat, &rat_ai, &rat_combat});
battle.set_all("enemy_found", true); battle.set_all("enemy_found", true);
@ -50,8 +47,8 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
battle.set(host, "have_healing", false); battle.set(host, "have_healing", false);
battle.set(host, "tough_personality", false); battle.set(host, "tough_personality", false);
while(host_combat.hp > 0) { while(!host_combat.is_dead()) {
battle.set(host, "health_good", host_combat.hp > 20); battle.set(host, "health_good", host_combat.almost_dead());
battle.player_request("use_healing"); battle.player_request("use_healing");
battle.player_request("kill_enemy"); battle.player_request("kill_enemy");
@ -62,11 +59,6 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
while(auto act = battle.next()) { while(auto act = battle.next()) {
auto& [enemy, wants_to, cost, enemy_state] = *act; auto& [enemy, wants_to, cost, enemy_state] = *act;
// fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap",
// enemy.entity, wants_to,
// cost, enemy.combat->hp,
// enemy.combat->ap);
switch(enemy_state) { switch(enemy_state) {
case BattleHostState::agree: case BattleHostState::agree:
// fmt::println("HOST and PLAYER requests match {}, doing it.", wants_to); // fmt::println("HOST and PLAYER requests match {}, doing it.", wants_to);

View file

@ -10,12 +10,12 @@ using namespace components;
TEST_CASE("test the loot ui", "[loot]") { TEST_CASE("test the loot ui", "[loot]") {
auto items = settings::get("assets/items.json"); auto items = settings::get("assets/items.json");
DinkyECS::World world; DinkyECS::World world;
auto torch = world.entity(); auto repair_kit = world.entity();
auto& data = items["TORCH_BAD"]; auto& data = items["REPAIR_KIT"];
components::init(); components::init();
components::configure_entity(world, torch, data["components"]); components::configure_entity(world, repair_kit, data["components"]);
auto& torch_sprite = world.get<Sprite>(torch); auto& repair_kit_sprite = world.get<Sprite>(repair_kit);
REQUIRE(torch_sprite.name == "torch_horizontal_floor"); REQUIRE(repair_kit_sprite.name == "repair_kit");
} }

View file

@ -11,14 +11,8 @@ TEST_CASE("color palette test", "[color-palette]") {
// confirm it's idempotent // confirm it's idempotent
palette::init(); palette::init();
sf::Color expect{10, 10, 10, 255};
auto gui_text = palette::get("gui/theme:dark_dark"); auto gui_text = palette::get("gui/theme:dark_dark");
REQUIRE(gui_text == expect); REQUIRE(gui_text.r > 0);
REQUIRE(gui_text.g > 0);
gui_text = palette::get("gui/theme", "mid"); REQUIRE(gui_text.b > 0);
REQUIRE(gui_text != expect);
expect = {100, 100, 100, 255};
REQUIRE(gui_text == expect);
} }

View file

@ -3,7 +3,9 @@
#include "game/systems.hpp" #include "game/systems.hpp"
#include <cmath> #include <cmath>
#include <numbers> #include <numbers>
#include "game/registry.hpp"
using components::Position;
TEST_CASE("figure out best rotation direction", "[systems-rotate]") { TEST_CASE("figure out best rotation direction", "[systems-rotate]") {
Matrix map = matrix::make(3, 3); Matrix map = matrix::make(3, 3);
@ -35,104 +37,45 @@ TEST_CASE("figure out best rotation direction", "[systems-rotate]") {
} }
} }
using MovingFunc = std::function<void()>;
using CombatFunc = std::function<void(bool)>;
using RenderFunc = std::function<void()>;
using UpdateFunc = std::function<void()>;
using UseFunc = std::function<void()>;
using PickupFunc = std::function<void()>;
struct Engine { void test_moving(Position& move_to) {
std::vector<MovingFunc> $moving; fmt::println("MOVING: {},{}", move_to.location.x, move_to.location.y);
std::vector<CombatFunc> $combat;
std::vector<RenderFunc> $render;
std::vector<UpdateFunc> $update;
std::vector<UseFunc> $use;
std::vector<PickupFunc> $pickup;
void addMoving(MovingFunc action) {
$moving.emplace_back(action);
}
void addCombat(CombatFunc action) {
$combat.emplace_back(action);
}
void addRender(RenderFunc action) {
$render.emplace_back(action);
}
void addUpdate(UpdateFunc action) {
$update.emplace_back(action);
}
void addUse(UseFunc action) {
$use.emplace_back(action);
}
void addPickup(PickupFunc action) {
$pickup.emplace_back(action);
}
void runMoving() {
for(auto func : $moving) {
func();
}
}
void runCombat(bool attr) {
for(auto func : $combat) {
func(attr);
}
}
void runRender() {
for(auto func : $render) {
func();
}
}
void runUpdate() {
for(auto func : $update) {
func();
}
}
void runUse() {
for(auto func : $use) {
func();
}
}
void runPickup() {
for(auto func : $pickup) {
func();
}
}
};
void test_system_1() {
fmt::println("TEST 1");
} }
void test_combat(bool what) { void test_combat(int attack_id) {
fmt::println("TEST 2: {}", what); fmt::println("ATTACK: {}", attack_id);
}
void test_render() {
fmt::println("RENDER");
}
void test_update() {
fmt::println("UPDATE");
}
void test_use_item(const std::string& slot_name) {
fmt::println("USE ITEM {}", slot_name);
}
void test_pickup() {
fmt::println("PICKUP");
} }
TEST_CASE("new system running engine thing", "[systems-engine]") { TEST_CASE("new system running engine thing", "[systems-engine]") {
Engine systems; System::Registry systems;
systems.addMoving(test_system_1); systems.addMoving(test_moving);
systems.addCombat(test_combat); systems.addCombat(test_combat);
systems.addRender(test_system_1); systems.addRender(test_render);
systems.addUpdate(test_system_1); systems.addUpdate(test_update);
systems.addUse(test_system_1); systems.addUseItem(test_use_item);
systems.addPickup(test_system_1); systems.addPickup(test_pickup);
systems.runCombat(false); systems.runCombat(1);
systems.runMoving(); systems.runMoving({1,1});
systems.runRender(); systems.runRender();
systems.runUpdate(); systems.runUpdate();
systems.runUse(); systems.runUseItem("hand_l");
systems.runPickup(); systems.runPickup();
} }

View file

@ -11,13 +11,13 @@ TEST_CASE("test texture management", "[textures]") {
components::init(); components::init();
textures::init(); textures::init();
auto spider = textures::get_sprite("rat_with_sword"); auto spider = textures::get_sprite("spider_bot");
REQUIRE(spider.sprite != nullptr); REQUIRE(spider.sprite != nullptr);
REQUIRE(spider.texture != nullptr); REQUIRE(spider.texture != nullptr);
REQUIRE(spider.frame_size.x == TEXTURE_WIDTH); REQUIRE(spider.frame_size.x == TEXTURE_WIDTH);
REQUIRE(spider.frame_size.y == TEXTURE_HEIGHT); REQUIRE(spider.frame_size.y == TEXTURE_HEIGHT);
auto image = textures::load_image("assets/sprites/rat_with_sword.png"); auto image = textures::load_image("assets/sprites/spider_bot.png");
size_t floor_tile = textures::get_id("floor_tile"); size_t floor_tile = textures::get_id("floor_tile");
size_t gray_stone = textures::get_id("door_plain"); size_t gray_stone = textures::get_id("door_plain");