You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
6.1 KiB
214 lines
6.1 KiB
#include "worldbuilder.hpp"
|
|
#include "rand.hpp"
|
|
#include <fmt/core.h>
|
|
#include <iostream>
|
|
#include "components.hpp"
|
|
#include "inventory.hpp"
|
|
#include "rituals.hpp"
|
|
#include "maze.hpp"
|
|
|
|
using namespace fmt;
|
|
using namespace components;
|
|
|
|
void WorldBuilder::stylize_rooms() {
|
|
auto& tiles = $map.tiles();
|
|
|
|
for(auto& room : $map.rooms()) {
|
|
for(matrix::box it{tiles, room.x, room.y, room.width+1, room.height+1}; it.next();) {
|
|
if(tiles[it.y][it.x] == 1) {
|
|
tiles[it.y][it.x] = 2;
|
|
} else if(tiles[it.y][it.x] == 0) {
|
|
tiles[it.y][it.x] = 6;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WorldBuilder::generate_map() {
|
|
maze::Builder maze($map);
|
|
|
|
maze.hunt_and_kill();
|
|
maze.init();
|
|
|
|
maze.randomize_rooms();
|
|
|
|
if($map.width() > 20) {
|
|
maze.inner_box(4, 2);
|
|
}
|
|
|
|
maze.hunt_and_kill();
|
|
|
|
$map.enclose();
|
|
$map.init_tiles();
|
|
|
|
stylize_rooms();
|
|
}
|
|
|
|
bool WorldBuilder::find_open_spot(Point& pos_out) {
|
|
size_t i = 0;
|
|
|
|
// horribly bad but I need to place things _somewhere_ so just fan out
|
|
for(i = 2; i < $map.width(); i++) {
|
|
// rando_rect starts at the top/left corner not center
|
|
for(matrix::rando_box it{$map.walls(), pos_out.x, pos_out.y, i}; it.next();) {
|
|
Point test{size_t(it.x), size_t(it.y)};
|
|
|
|
if($map.can_move(test) && !$collision.occupied(test)) {
|
|
pos_out = test;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
matrix::dump("FAIL PLACE!", $map.walls(), pos_out.x, pos_out.y);
|
|
|
|
dbc::sentinel(fmt::format("failed to place entity in the entire map?: i={}; width={};", i, $map.width()));
|
|
|
|
return false;
|
|
}
|
|
|
|
DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, Point pos) {
|
|
bool found = find_open_spot(pos);
|
|
dbc::check(found, "Failed to find a place for this thing.");
|
|
|
|
auto item = world.entity();
|
|
world.set<Position>(item, {pos.x, pos.y});
|
|
|
|
if(entity_data["inventory_count"] > 0) {
|
|
world.set<InventoryItem>(item, {entity_data["inventory_count"], entity_data});
|
|
}
|
|
|
|
if(entity_data.contains("components")) {
|
|
components::configure_entity($components, world, item, entity_data["components"]);
|
|
}
|
|
|
|
$collision.insert(pos, item);
|
|
|
|
return item;
|
|
}
|
|
|
|
DinkyECS::Entity WorldBuilder::configure_entity_in_room(DinkyECS::World &world, json &entity_data, int in_room) {
|
|
Point pos_out;
|
|
bool placed = $map.place_entity(in_room, pos_out);
|
|
dbc::check(placed, "failed to randomly place item in room");
|
|
auto entity = configure_entity_in_map(world, entity_data, pos_out);
|
|
return entity;
|
|
}
|
|
|
|
|
|
inline json &select_entity_type(GameConfig &config, json &gen_config) {
|
|
int enemy_test = Random::uniform<int>(0,100);
|
|
int device_test = Random::uniform<int>(0, 100);
|
|
|
|
if(enemy_test < gen_config["enemy_probability"]) {
|
|
return config.enemies.json();
|
|
} else if(device_test < gen_config["device_probability"]) {
|
|
return config.devices.json();
|
|
} else {
|
|
return config.items.json();
|
|
}
|
|
}
|
|
|
|
inline json& random_entity_data(GameConfig& config, json& gen_config) {
|
|
json& entity_db = select_entity_type(config, gen_config);
|
|
|
|
std::vector<std::string> keys;
|
|
for(auto& el : entity_db.items()) {
|
|
auto& data = el.value();
|
|
|
|
if(data["placement"] == nullptr) {
|
|
keys.push_back(el.key());
|
|
}
|
|
}
|
|
|
|
int rand_entity = Random::uniform<int>(0, keys.size() - 1);
|
|
std::string key = keys[rand_entity];
|
|
// BUG: this may crash if PLAYER_TILE isn't first
|
|
return entity_db[key];
|
|
}
|
|
|
|
void WorldBuilder::randomize_entities(DinkyECS::World &world, GameConfig &config) {
|
|
auto& gen_config = config.game["worldgen"];
|
|
|
|
for(int room_num = $map.room_count() - 1; room_num > 0; room_num--) {
|
|
// pass that to the config as it'll be a generic json
|
|
auto& entity_data = random_entity_data(config, gen_config);
|
|
configure_entity_in_room(world, entity_data, room_num);
|
|
}
|
|
|
|
for(auto& at : $map.$dead_ends) {
|
|
auto& entity_data = random_entity_data(config, gen_config);
|
|
configure_entity_in_map(world, entity_data, at);
|
|
}
|
|
}
|
|
|
|
void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) {
|
|
auto& device_config = config.devices.json();
|
|
auto entity_data = device_config["STAIRS_DOWN"];
|
|
|
|
auto at_end = $map.$dead_ends.back();
|
|
configure_entity_in_map(world, entity_data, at_end);
|
|
}
|
|
|
|
void WorldBuilder::configure_starting_items(DinkyECS::World &world) {
|
|
auto& blanket = world.get_the<ritual::Blanket>();
|
|
Config config("assets/rituals.json");
|
|
|
|
for(auto& el : config["starting_junk"]) {
|
|
ritual::JunkItem name = el;
|
|
blanket.add(name);
|
|
};
|
|
}
|
|
|
|
void WorldBuilder::place_entities(DinkyECS::World &world) {
|
|
auto &config = world.get_the<GameConfig>();
|
|
// configure a player as a fact of the world
|
|
Position player_pos{0,0};
|
|
|
|
if(world.has_the<Player>()) {
|
|
auto& player = world.get_the<Player>();
|
|
|
|
// first get a guess from the map
|
|
bool placed = $map.place_entity(0, player_pos.location);
|
|
dbc::check(placed, "map.place_entity failed to position player");
|
|
|
|
// then use the collision map to place the player safely
|
|
placed = find_open_spot(player_pos.location);
|
|
dbc::check(placed, "WorldBuild.find_open_spot also failed to position player");
|
|
|
|
world.set<Position>(player.entity, player_pos);
|
|
} else {
|
|
auto player_data = config.enemies["PLAYER_TILE"];
|
|
auto player_ent = configure_entity_in_room(world, player_data, 0);
|
|
|
|
player_pos = world.get<Position>(player_ent);
|
|
|
|
// configure player in the world
|
|
Player player{player_ent};
|
|
world.set_the<Player>(player);
|
|
world.set_the<ritual::Belt>({});
|
|
world.set_the<ritual::Blanket>({});
|
|
configure_starting_items(world);
|
|
world.set<Inventory>(player.entity, {5});
|
|
world.make_constant(player.entity);
|
|
}
|
|
|
|
dbc::check(player_pos.location.x != 0 && player_pos.location.y != 0,
|
|
"failed to place the player correctly");
|
|
|
|
// make a dead zone around the player
|
|
auto& player = world.get_the<Player>();
|
|
for(matrix::box it{$map.walls(), player_pos.location.x, player_pos.location.y, 2};
|
|
it.next();)
|
|
{
|
|
$collision.insert({it.x, it.y}, player.entity);
|
|
}
|
|
|
|
randomize_entities(world, config);
|
|
place_stairs(world, config);
|
|
}
|
|
|
|
void WorldBuilder::generate(DinkyECS::World &world) {
|
|
generate_map();
|
|
place_entities(world);
|
|
}
|
|
|