Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
4068 lines (3748 sloc) 142 KB
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <fstream>
#include <vector>
#include <sstream>
#include "overmap.h"
#include "rng.h"
#include "line.h"
#include "game.h"
#include "npc.h"
#include <cstring>
#include <ostream>
#include "debug.h"
#include "cursesdef.h"
#include "options.h"
#include "catacharset.h"
#include "overmapbuffer.h"
#include "action.h"
#include "input.h"
#include "json.h"
#include <queue>
#include <bits/basic_string.h>
#include "mapdata.h"
#include "mapgen.h"
#include "uistate.h"
#include "omdata.h"
#define dbg(x) DebugLog((DebugLevel)(x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": "
#define STREETCHANCE 2
#define NUM_FOREST 250
#define TOP_HIWAY_DIST 999
#define MIN_ANT_SIZE 8
#define MAX_ANT_SIZE 20
#define MIN_GOO_SIZE 1
#define MAX_GOO_SIZE 2
#define MIN_RIFT_SIZE 6
#define MAX_RIFT_SIZE 16
#define SETTLE_DICE 2
#define SETTLE_SIDES 2
#define HIVECHANCE 180 //Chance that any given forest will be a hive
#define SWAMPINESS 4 //Affects the size of a swamp
#define SWAMPCHANCE 8500 // Chance that a swamp will spawn instead of forest
enum oter_dir {
oter_dir_north, oter_dir_east, oter_dir_west, oter_dir_south
};
// Here are the global controls for map-extra spawning.
// The %%% line is chance that a given map square will have an extra
// (higher = less likely) and the individual numbers are the
// relative frequencies of each (higher = more likely).
// Adding or deleting map_extras will affect the amount
// of others, so be careful.
map_extras no_extras(0);
// Formatting deviates from standard to make the headers read reliably
// Careful with astyle here, please?
map_extras road_extras(
// %%% HEL MIL SCI BLK DRG SUP PRT MIN CRT FUM 1WY ART
75, 40, 25, 40, 100, 30, 10, 5, 80, 10, 8, 2, 3);
map_extras field_extras(
// %%% HEL MIL SCI BLK DRG SUP PRT MIN CRT FUM 1WY ART
90, 40, 8, 20, 0, 20, 10, 3, 50, 10, 8, 1, 3);
map_extras subway_extras(
// %%% HEL MIL SCI BLK DRG SUP PRT MIN CRT FUM 1WY ART
75, 0, 5, 12, 0, 0, 0, 7, 0, 0, 20, 1, 3);
map_extras build_extras(
// %%% HEL MIL SCI BLK DRG SUP PRT MIN CRT FUM 1WY ART
90, 0, 5, 12, 0, 0, 0, 5, 5, 60, 8, 1, 3);
std::unordered_map<std::string, oter_t> otermap;
std::vector<oter_t> oterlist;
std::unordered_map<std::string, oter_t> obasetermap;
//const regional_settings default_region_settings;
std::unordered_map<std::string, regional_settings> region_settings_map;
std::vector<overmap_special> overmap_specials;
void load_overmap_specials(JsonObject &jo)
{
overmap_special spec;
spec.id = jo.get_string("id");
JsonArray om_array = jo.get_array("overmaps");
while(om_array.has_more()) {
JsonObject om = om_array.next_object();
overmap_special_terrain terrain;
JsonArray point = om.get_array("point");
terrain.p = tripoint(point.get_int(0), point.get_int(1), point.get_int(2));
terrain.terrain = om.get_string("overmap");
terrain.connect = om.get_string("connect", "");
JsonArray flagarray = om.get_array("flags");
while(flagarray.has_more()) {
terrain.flags.insert(flagarray.next_string());
}
spec.terrains.push_back(terrain);
}
JsonArray location_array = jo.get_array("locations");
while(location_array.has_more()) {
spec.locations.push_back(location_array.next_string());
}
JsonArray city_size_array = jo.get_array("city_sizes");
if(city_size_array.has_more()) {
spec.min_city_size = city_size_array.get_int(0);
spec.max_city_size = city_size_array.get_int(1);
}
JsonArray occurrences_array = jo.get_array("occurrences");
if(occurrences_array.has_more()) {
spec.min_occurrences = occurrences_array.get_int(0);
spec.max_occurrences = occurrences_array.get_int(1);
}
JsonArray city_distance_array = jo.get_array("city_distance");
if(city_distance_array.has_more()) {
spec.min_city_distance = city_distance_array.get_int(0);
spec.max_city_distance = city_distance_array.get_int(1);
}
spec.rotatable = jo.get_bool("rotate", false);
spec.unique = jo.get_bool("unique", false);
spec.required = jo.get_bool("required", false);
if(jo.has_object("spawns")) {
JsonObject spawns = jo.get_object("spawns");
spec.spawns.group = spawns.get_string("group");
spec.spawns.min_population = spawns.get_array("population").get_int(0);
spec.spawns.max_population = spawns.get_array("population").get_int(1);
spec.spawns.min_radius = spawns.get_array("radius").get_int(0);
spec.spawns.max_radius = spawns.get_array("radius").get_int(1);
}
JsonArray flag_array = jo.get_array("flags");
while(flag_array.has_more()) {
spec.flags.insert(flag_array.next_string());
}
overmap_specials.push_back(spec);
}
void clear_overmap_specials()
{
overmap_specials.clear();
}
bool has_river(const complex_map_tile &tile)
{
for (const oter_id &ter : tile.tiles) {
if (ter.t().has_flag(is_river)) return true;
}
return false;
}
//checks to see if the given tile is a lab.
bool has_lab(const complex_map_tile &tile, const char *str = "")
{
for (const oter_id &ter : tile.tiles)
{
if (is_lab(ter, str))
return true;
}
return false;
}
//checks to see if the given oter is a lab.
bool is_lab(const oter_id &ter, const char *str = "")
{
if ((strcmp(str, "") == 0 || strcmp(str, "science") == 0) && ter >= "lab" && ter <= "lab_core" ) return true;
if ((strcmp(str, "") == 0 || strcmp(str, "ice") == 0) && ter >= "ice_lab" && ter <= "ice_lab_core" ) return true;
return false;
}
//checks to see if the given tile contains a subway station.
bool is_subway_station(const complex_map_tile &tile)
{
for (const oter_id &ter : tile.tiles)
{
if (is_subway_station(ter))
return true;
}
return false;
}
//checks to see if the given ter is a subway station.
bool is_subway_station(const oter_id &ter)
{
return (ter >= "sub_station_north" && ter <= "sub_station_west");
}
//checks to see if the given tile contains the given otype.
bool is_ot_type(const std::string &otype, const complex_map_tile &tile)
{
for (const oter_id &ter : tile.tiles)
{
if (is_ot_type(otype, ter))
return true;
}
return false;
}
bool is_ot_type(const std::string &otype, const oter_id &oter)
{
const size_t compare_size = otype.size();
if (compare_size > oter.size()) {
return false;
} else {
return std::string(oter).compare(0, compare_size, otype ) == 0;
}
}
bool road_allowed(const oter_id &ter);
//check every piece of a complex tile to see if it disallows roads.
bool road_allowed(const complex_map_tile &tile)
{
for (const oter_id &ter : tile.tiles) {
if (!road_allowed(ter)) return false;
}
return true;
}
//check a particular kind of oter to see if it allows roads.
bool road_allowed(const oter_id &ter)
{
return ter.t().has_flag(allow_road);
}
/*
* Pick an oter_baseid from weightlist and return rotated oter_id
*/
oter_id shop(int dir, oter_weight_list &weightlist )
// todo: rename to something better than 'shop', make it an oter_weight_list method?
{
if ( dir > 3 ) {
debugmsg("Bad rotation of weightlist pick: %d.", dir);
return "";
}
dir = dir % 4;
if (dir < 0) {
dir += 4;
}
const int ret = weightlist.pick();
if ( oterlist[ ret ].rotates == false ) {
return ret;
}
return oterlist[ ret ].directional_peers[dir];
}
oter_id house(int dir, int chance_of_basement)
{
static const oter_id iid_house = oter_id("house_north");
static const oter_id iid_house_base = oter_id("house_base_north");
if (dir < 0) {
dir += 4;
} else if (dir > 3) {
debugmsg("Bad rotation of house: %d.", dir);
return "";
}
return ( one_in( chance_of_basement) ? iid_house : iid_house_base ).t().directional_peers[dir];
}
map_extras &get_extras(const std::string &name)
{
if (name == "field") {
return field_extras;
} else if (name == "road") {
return road_extras;
} else if (name == "subway") {
return subway_extras;
} else if (name == "build") {
return build_extras;
} else {
return no_extras;
}
}
// oter_t specific affirmatives to is_road, set at startup (todo; jsonize)
bool isroad(std::string bstr)
{
if (bstr == "road" || bstr == "road_nesw_manhole" || bstr == "bridge" ||
bstr == "subway" || bstr == "sewer" || bstr == "road_end" ||
bstr == "sewage_treatment_hub" ||
bstr == "sewage_treatment_under" ||
bstr == "rift" || bstr == "hellmouth") {
return true;
}
return false;
}
void load_oter(oter_t &oter)
{
oter.loadid = oterlist.size();
otermap[oter.id] = oter;
oterlist.push_back(oter);
}
void reset_overmap_terrain()
{
otermap.clear();
oterlist.clear();
}
/*
* load mapgen functions from an overmap_terrain json entry
* suffix is for roads/subways/etc which have "_straight", "_curved", "_tee", "_four_way" function mappings
*/
void load_overmap_terrain_mapgens(JsonObject &jo, const std::string id_base,
const std::string suffix = "")
{
const std::string fmapkey(id_base + suffix);
const std::string jsonkey("mapgen" + suffix);
bool default_mapgen = jo.get_bool("default_mapgen", true);
int default_idx = -1;
if ( default_mapgen ) {
if ( mapgen_cfunction_map.find( fmapkey ) != mapgen_cfunction_map.end() ) {
oter_mapgen[fmapkey].push_back( new mapgen_function_builtin( fmapkey ) );
default_idx = oter_mapgen[fmapkey].size() - 1;
}
}
if ( jo.has_array( jsonkey ) ) {
JsonArray ja = jo.get_array( jsonkey );
int c = 0;
while ( ja.has_more() ) {
if ( ja.has_object(c) ) {
JsonObject jio = ja.next_object();
load_mapgen_function( jio, fmapkey, default_idx );
}
c++;
}
}
}
//this function loads an overmap terrain from a JsonObject.
void load_overmap_terrain(JsonObject &jo)
{
oter_t oter;
long syms[4];
oter.id = jo.get_string("id");
oter.name = _(jo.get_string("name").c_str());
oter.rotates = jo.get_bool("rotate", false);
oter.line_drawing = jo.get_bool("line_drawing", false);
if (oter.line_drawing) {
oter.sym = jo.get_int("sym", (int)'%');
} else if (jo.has_array("sym")) {
JsonArray ja = jo.get_array("sym");
for( auto &sym : syms ) {
sym = ja.next_int();
}
oter.sym = syms[0];
} else if (oter.rotates) {
oter.sym = jo.get_int("sym");
for( auto &sym : syms ) {
sym = oter.sym;
}
} else {
oter.sym = jo.get_int("sym");
}
oter.color = color_from_string(jo.get_string("color"));
oter.see_cost = jo.get_int("see_cost");
oter.extras = jo.get_string("extras", "none");
oter.set_flag(known_down, jo.get_bool("known_down", false));
oter.set_flag(known_up, jo.get_bool("known_up", false));
oter.mondensity = jo.get_int("mondensity", 0);
oter.set_flag(has_sidewalk, jo.get_bool("sidewalk", false));
oter.set_flag(allow_road, jo.get_bool("allow_road", false));
std::string id_base = oter.id;
int start_iid = oterlist.size();
oter.id_base = id_base;
oter.loadid_base = start_iid;
oter.directional_peers.clear();
if( jo.has_object( "spawns" ) ) {
JsonObject spawns = jo.get_object( "spawns" );
oter.static_spawns.group = spawns.get_string( "group" );
oter.static_spawns.min_population = spawns.get_array( "population" ).get_int( 0 );
oter.static_spawns.max_population = spawns.get_array( "population" ).get_int( 1 );
oter.static_spawns.chance = spawns.get_int( "chance" );
}
oter.set_flag(is_asphalt, jo.get_bool("asphalt", false));
oter.set_flag(is_subway, jo.get_bool("subway", false));
oter.set_flag(is_sewer, jo.get_bool("sewer", false));
oter.set_flag(is_ants, jo.get_bool("ants", false));
oter.set_flag(is_base_terrain, jo.get_bool("base_terrain", false));
oter.set_flag(is_road, isroad(id_base));
oter.set_flag(is_river, (id_base.compare(0, 5, "river", 5) == 0));
oter.id_mapgen = id_base; // What, another identifier? Whyyy...
if ( ! oter.line_drawing ) { // ...oh
load_overmap_terrain_mapgens(jo, id_base);
}
if (oter.line_drawing) {
// add variants for line drawing
for( int i = start_iid; i < start_iid + 12; i++ ) {
oter.directional_peers.push_back(i);
}
oter.id_mapgen = id_base + "_straight";
load_overmap_terrain_mapgens(jo, id_base, "_straight");
oter.id = id_base + "_ns";
oter.sym = LINE_XOXO;
load_oter(oter);
oter.id = id_base + "_ew";
oter.sym = LINE_OXOX;
load_oter(oter);
oter.id_mapgen = id_base + "_curved";
load_overmap_terrain_mapgens(jo, id_base, "_curved");
oter.id = id_base + "_ne";
oter.sym = LINE_XXOO;
load_oter(oter);
oter.id = id_base + "_es";
oter.sym = LINE_OXXO;
load_oter(oter);
oter.id = id_base + "_sw";
oter.sym = LINE_OOXX;
load_oter(oter);
oter.id = id_base + "_wn";
oter.sym = LINE_XOOX;
load_oter(oter);
oter.id_mapgen = id_base + "_tee";
load_overmap_terrain_mapgens(jo, id_base, "_tee");
oter.id = id_base + "_nes";
oter.sym = LINE_XXXO;
load_oter(oter);
oter.id = id_base + "_new";
oter.sym = LINE_XXOX;
load_oter(oter);
oter.id = id_base + "_nsw";
oter.sym = LINE_XOXX;
load_oter(oter);
oter.id = id_base + "_esw";
oter.sym = LINE_OXXX;
load_oter(oter);
oter.id_mapgen = id_base + "_four_way";
load_overmap_terrain_mapgens(jo, id_base, "_four_way");
oter.id = id_base + "_nesw";
oter.sym = LINE_XXXX;
load_oter(oter);
} else if (oter.rotates) {
// add north/east/south/west variants
for( int i = start_iid; i < start_iid + 5; i++ ) {
oter.directional_peers.push_back(i);
}
oter.id = id_base + "_north";
oter.sym = syms[0];
load_oter(oter);
oter.id = id_base + "_east";
oter.sym = syms[1];
load_oter(oter);
oter.id = id_base + "_south";
oter.sym = syms[2];
load_oter(oter);
oter.id = id_base + "_west";
oter.sym = syms[3];
load_oter(oter);
} else {
oter.directional_peers.push_back(start_iid);
load_oter(oter);
}
}
/*
* Assemble a map of overmap_terrain base ids pointing to first members of oter groups
* We'll do this after json loading so references can be used
*/
void finalize_overmap_terrain( )
{
unsigned c = 0;
for( std::vector<oter_t>::const_iterator it = oterlist.begin(); it != oterlist.end(); ++it ) {
if ( (*it).loadid == (*it).loadid_base ) {
if ( (*it).loadid != c ) { // might as well sanity check while we're here da? da.
debugmsg("ERROR: oterlist[%d]: mismatch with loadid (%d). (id = %s, id_base = %s)",
c, (*it).loadid, (*it).id.c_str(), (*it).id_base.c_str()
);
// aaaaaaaand continue to inevitable crash
}
obasetermap.insert( std::pair<std::string, oter_t>( (*it).id_base, oterlist[c] ) );;
}
c++;
}
// here's another sanity check, yay.
if ( region_settings_map.find("default") == region_settings_map.end() ) {
debugmsg("ERROR: can't find default overmap settings (region_map_settings 'default'),"
" cataclysm pending. And not the fun kind.");
}
for( auto &elem : region_settings_map ) {
elem.second.setup();
}
}
void load_region_settings( JsonObject &jo )
{
regional_settings new_region;
if ( ! jo.read("id", new_region.id) ) {
jo.throw_error("No 'id' field.");
}
bool strict = ( new_region.id == "default" );
if ( ! jo.read("default_oter", new_region.default_oter) && strict ) {
jo.throw_error("default_oter required for default ( though it should probably remain 'field' )");
}
if ( jo.has_object("default_groundcover") ) {
JsonObject jio = jo.get_object("default_groundcover");
new_region.default_groundcover_str = new sid_or_sid("t_grass", 4, "t_dirt");
if ( ! jio.read("primary", new_region.default_groundcover_str->primary_str) ||
! jio.read("secondary", new_region.default_groundcover_str->secondary_str) ||
! jio.read("ratio", new_region.default_groundcover.chance) ) {
jo.throw_error("'default_groundcover' missing one of:\n { \"primary\": \"ter_id\", \"secondary\": \"ter_id\", \"ratio\": (number) }\n");
}
} else if ( strict ) {
jo.throw_error("'default_groundcover' required for 'default'");
}
if ( ! jo.read("num_forests", new_region.num_forests) && strict ) {
jo.throw_error("num_forests required for default");
}
if ( ! jo.read("forest_size_min", new_region.forest_size_min) && strict ) {
jo.throw_error("forest_size_min required for default");
}
if ( ! jo.read("forest_size_max", new_region.forest_size_max) && strict ) {
jo.throw_error("forest_size_max required for default");
}
if ( ! jo.read("house_basement_chance", new_region.house_basement_chance) && strict ) {
jo.throw_error("house_basement_chance required for default");
}
if ( ! jo.read("swamp_maxsize", new_region.swamp_maxsize) && strict ) {
jo.throw_error("swamp_maxsize required for default");
}
if ( ! jo.read("swamp_river_influence", new_region.swamp_river_influence) && strict ) {
jo.throw_error("swamp_river_influence required for default");
}
if ( ! jo.read("swamp_spread_chance", new_region.swamp_spread_chance) && strict ) {
jo.throw_error("swamp_spread_chance required for default");
}
if ( ! jo.has_object("field_coverage") ) {
if ( strict ) {
jo.throw_error("\"field_coverage\": { ... } required for default");
}
} else {
JsonObject pjo = jo.get_object("field_coverage");
double tmpval = 0.0f;
if ( ! pjo.read("percent_coverage", tmpval) ) {
pjo.throw_error("field_coverage: percent_coverage required");
}
new_region.field_coverage.mpercent_coverage = (int)(tmpval * 10000.0);
if ( ! pjo.read("default_ter", new_region.field_coverage.default_ter_str) ) {
pjo.throw_error("field_coverage: default_ter required");
}
tmpval = 0.0f;
if ( pjo.has_object("other") ) {
JsonObject opjo = pjo.get_object("other");
std::set<std::string> keys = opjo.get_member_names();
for( const auto &key : keys ) {
tmpval = 0.0f;
if( key != "//" ) {
if( opjo.read( key, tmpval ) ) {
new_region.field_coverage.percent_str[key] = tmpval;
}
}
}
}
if ( pjo.read("boost_chance", tmpval) && tmpval != 0.0f ) {
new_region.field_coverage.boost_chance = (int)(tmpval * 10000.0);
if ( ! pjo.read("boosted_percent_coverage", tmpval) ) {
pjo.throw_error("boost_chance > 0 requires boosted_percent_coverage");
}
new_region.field_coverage.boosted_mpercent_coverage = (int)(tmpval * 10000.0);
if ( ! pjo.read("boosted_other_percent", tmpval) ) {
pjo.throw_error("boost_chance > 0 requires boosted_other_percent");
}
new_region.field_coverage.boosted_other_mpercent = (int)(tmpval * 10000.0);
if ( pjo.has_object("boosted_other") ) {
JsonObject opjo = pjo.get_object("boosted_other");
std::set<std::string> keys = opjo.get_member_names();
for( const auto &key : keys ) {
tmpval = 0.0f;
if( key != "//" ) {
if( opjo.read( key, tmpval ) ) {
new_region.field_coverage.boosted_percent_str[key] = tmpval;
}
}
}
} else {
pjo.throw_error("boost_chance > 0 requires boosted_other { ... }");
}
}
}
if ( ! jo.has_object("city") ) {
if ( strict ) {
jo.throw_error("\"city\": { ... } required for default");
}
} else {
JsonObject cjo = jo.get_object("city");
if ( ! cjo.read("shop_radius", new_region.city_spec.shop_radius) && strict ) {
jo.throw_error("city: shop_radius required for default");
}
if ( ! cjo.read("park_radius", new_region.city_spec.park_radius) && strict ) {
jo.throw_error("city: park_radius required for default");
}
if ( ! cjo.has_object("shops") && strict ) {
if ( strict ) {
jo.throw_error("city: \"shops\": { ... } required for default");
}
} else {
JsonObject wjo = cjo.get_object("shops");
std::set<std::string> keys = wjo.get_member_names();
for( const auto &key : keys ) {
if( key != "//" ) {
if( wjo.has_int( key ) ) {
new_region.city_spec.shops.add_item( key, wjo.get_int( key ) );
}
}
}
}
if ( ! cjo.has_object("parks") && strict ) {
if ( strict ) {
jo.throw_error("city: \"parks\": { ... } required for default");
}
} else {
JsonObject wjo = cjo.get_object("parks");
std::set<std::string> keys = wjo.get_member_names();
for( const auto &key : keys ) {
if( key != "//" ) {
if( wjo.has_int( key ) ) {
new_region.city_spec.parks.add_item( key, wjo.get_int( key ) );
}
}
}
}
}
region_settings_map[new_region.id] = new_region;
}
void reset_region_settings()
{
region_settings_map.clear();
}
// *** BEGIN overmap FUNCTIONS ***
overmap::overmap(int const x, int const y)
: loc(x, y)
, nullret()
, nullbool(false)
, nullstr("")
{
// STUB: need region map:
// settings = regionmap->calculate_settings( loc );
const std::string rsettings_id = ACTIVE_WORLD_OPTIONS["DEFAULT_REGION"].getValue();
std::unordered_map<std::string, regional_settings>::const_iterator rsit =
region_settings_map.find( rsettings_id );
if ( rsit == region_settings_map.end() ) {
debugmsg("overmap(%d,%d): can't find region '%s'", x, y, rsettings_id.c_str() ); // gonna die now =[
}
settings = rsit->second;
init_layers();
open();
}
overmap::~overmap()
{
}
void overmap::init_layers()
{
for(int z = 0; z < OVERMAP_LAYERS; ++z) {
oter_id default_type = (z < OVERMAP_DEPTH) ? "rock" : (z == OVERMAP_DEPTH) ? settings.default_oter :
"open_air";
for(int i = 0; i < OMAPX; ++i) {
for(int j = 0; j < OMAPY; ++j) {
layer[z].terrain[i][j] = complex_map_tile(default_type);
layer[z].visible[i][j] = false;
layer[z].explored[i][j] = false;
}
}
}
}
complex_map_tile &overmap::ter(const int x, const int y, const int z)
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY || z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
//nullret = "";
return nullret;
}
return layer[z + OVERMAP_DEPTH].terrain[x][y];
}
const complex_map_tile overmap::get_ter(const int x, const int y, const int z) const
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY || z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
return nullret;
}
return layer[z + OVERMAP_DEPTH].terrain[x][y];
}
bool &overmap::seen(int x, int y, int z)
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY || z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
nullbool = false;
return nullbool;
}
return layer[z + OVERMAP_DEPTH].visible[x][y];
}
bool &overmap::explored(int x, int y, int z)
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY || z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
nullbool = false;
return nullbool;
}
return layer[z + OVERMAP_DEPTH].explored[x][y];
}
// this uses om_pos (overmap tiles, aka levxy / 2)
bool overmap::is_safe(int x, int y, int z)
{
// use the monsters_at function of the overmapbuffer, which requires *absolute*
// coordinates. is_safe should be moved to the overmapbuffer, too.
std::vector<mongroup *> mons = overmap_buffer.monsters_at( x + loc.x * OMAPX, y + loc.y * OMAPY, z );
if (mons.empty()) {
return true;
}
for (auto &n : mons) {
if (!n->is_safe()) {
return false;
}
}
return true;
}
bool overmap::is_explored(int const x, int const y, int const z) const
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY || z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
return false;
}
return layer[z + OVERMAP_DEPTH].explored[x][y];
}
bool overmap::has_note(int const x, int const y, int const z) const
{
if (z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
return false;
}
for (auto &i : layer[z + OVERMAP_DEPTH].notes) {
if (i.x == x && i.y == y) {
return true;
}
}
return false;
}
std::string const &overmap::note(int const x, int const y, int const z) const
{
if (z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
return nullstr;
}
for (auto &i : layer[z + OVERMAP_DEPTH].notes) {
if (i.x == x && i.y == y) {
return i.text;
}
}
return nullstr;
}
void overmap::add_note(int const x, int const y, int const z, std::string const &message)
{
if (z < -OVERMAP_DEPTH || z > OVERMAP_HEIGHT) {
debugmsg("Attempting to add not to overmap for blank layer %d", z);
return;
}
for (size_t i = 0; i < layer[z + OVERMAP_DEPTH].notes.size(); i++) {
if (layer[z + OVERMAP_DEPTH].notes[i].x == x && layer[z + OVERMAP_DEPTH].notes[i].y == y) {
if (message.empty()) {
layer[z + OVERMAP_DEPTH].notes.erase(layer[z + OVERMAP_DEPTH].notes.begin() + i);
} else {
layer[z + OVERMAP_DEPTH].notes[i].text = message;
}
return;
}
}
if (message.length() > 0) {
layer[z + OVERMAP_DEPTH].notes.push_back(om_note(x, y, layer[z + OVERMAP_DEPTH].notes.size(),
message));
}
}
extern bool lcmatch(const std::string& text, const std::string& pattern);
//uses the lcmatch function on every kind of tile present, and return true if any of the do.
bool complex_lcmatch (const complex_map_tile &tile, const std::string& pattern) {
for (const oter_id &ter : tile.tiles) {
if (lcmatch(otermap[ter].name, pattern)) return true;
}
return false;
}
std::vector<point> overmap::find_notes(int const z, std::string const &text)
{
std::vector<point> note_locations;
map_layer &this_layer = layer[z + OVERMAP_DEPTH];
for( auto note : this_layer.notes ) {
if( lcmatch( note.text, text ) ) {
note_locations.push_back( point( get_left_border() + note.x, get_top_border() + note.y ) );
}
}
return note_locations;
}
point overmap::display_notes(int z)
{
const overmapbuffer::t_notes_vector notes = overmap_buffer.get_all_notes(z);
WINDOW *w_notes = newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH,
(TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0,
(TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0);
draw_border(w_notes);
const std::string title = _("Notes:");
const std::string back_msg = _("< Prev notes");
const std::string forward_msg = _("Next notes >");
// Number of items to show at one time, 2 rows for border, 2 for title & bottom text
const unsigned maxitems = FULL_SCREEN_HEIGHT - 4;
int ch = '.';
unsigned start = 0;
const int back_len = utf8_width(back_msg.c_str());
bool redraw = true;
point result(-1, -1);
mvwprintz(w_notes, 1, 1, c_ltgray, title.c_str());
do {
if (redraw) {
for (int i = 2; i < FULL_SCREEN_HEIGHT - 1; i++) {
for (int j = 1; j < FULL_SCREEN_WIDTH - 1; j++) {
mvwputch(w_notes, i, j, c_black, ' ');
}
}
for (unsigned i = 0; i < maxitems; i++) {
const unsigned cur_it = start + i;
if (cur_it >= notes.size()) {
continue;
}
// Print letter ('a' <=> cur_it == start)
mvwputch (w_notes, i + 2, 1, c_white, 'a' + i);
mvwprintz(w_notes, i + 2, 3, c_ltgray, "- %s", notes[cur_it].second.c_str());
}
if (start >= maxitems) {
mvwprintw(w_notes, maxitems + 2, 1, back_msg.c_str());
}
if (start + maxitems < notes.size()) {
mvwprintw(w_notes, maxitems + 2, 2 + back_len, forward_msg.c_str());
}
mvwprintz(w_notes, 1, 40, c_white, _("Press letter to center on note"));
mvwprintz(w_notes, FULL_SCREEN_HEIGHT - 2, 40, c_white, _("Spacebar - Return to map "));
wrefresh(w_notes);
redraw = false;
}
ch = getch();
if (ch == '<' && start >= maxitems) {
start -= maxitems;
redraw = true;
} else if (ch == '>' && start + maxitems < notes.size()) {
start += maxitems;
redraw = true;
} else if(ch >= 'a' && ch <= 'z') {
const unsigned chosen = ch - 'a' + start;
if (chosen < notes.size()) {
result = notes[chosen].first;
break; // -> return result
}
}
} while(ch != ' ' && ch != '\n' && ch != KEY_ESCAPE);
delwin(w_notes);
return result;
}
void overmap::generate(const overmap *north, const overmap *east,
const overmap *south, const overmap *west)
{
dbg(D_INFO) << "overmap::generate start...";
std::vector<city> road_points; // cities and roads_out together
std::vector<point> river_start;// West/North endpoints of rivers
std::vector<point> river_end; // East/South endpoints of rivers
// Determine points where rivers & roads should connect w/ adjacent maps
const oter_id river_center("river_center"); // optimized comparison.
if (north != NULL) {
for (int i = 2; i < OMAPX - 2; i++) {
if (has_river(north->get_ter(i, OMAPY - 1, 0))) {
ter(i, 0, 0).set(river_center);
}
if (has_river(north->get_ter(i, OMAPY - 1, 0)) &&
has_river(north->get_ter(i - 1, OMAPY - 1, 0)) &&
has_river(north->get_ter(i + 1, OMAPY - 1, 0))) {
if (river_start.empty() ||
river_start[river_start.size() - 1].x < i - 6) {
river_start.push_back(point(i, 0));
}
}
}
for (auto &i : north->roads_out) {
if (i.y == OMAPY - 1) {
roads_out.push_back(city(i.x, 0, 0));
}
}
}
size_t rivers_from_north = river_start.size();
if (west != NULL) {
for (int i = 2; i < OMAPY - 2; i++) {
if (has_river(west->get_ter(OMAPX - 1, i, 0))) {
ter(0, i, 0).set(river_center);
}
if (has_river(west->get_ter(OMAPX - 1, i, 0)) &&
has_river(west->get_ter(OMAPX - 1, i - 1, 0)) &&
has_river(west->get_ter(OMAPX - 1, i + 1, 0))) {
if (river_start.size() == rivers_from_north ||
river_start[river_start.size() - 1].y < i - 6) {
river_start.push_back(point(0, i));
}
}
}
for (auto &i : west->roads_out) {
if (i.x == OMAPX - 1) {
roads_out.push_back(city(0, i.y, 0));
}
}
}
if (south != NULL) {
for (int i = 2; i < OMAPX - 2; i++) {
if (has_river(south->get_ter(i, 0, 0))) {
ter(i, OMAPY - 1, 0).set(river_center);
}
if (has_river(south->get_ter(i, 0, 0)) &&
has_river(south->get_ter(i - 1, 0, 0)) &&
has_river(south->get_ter(i + 1, 0, 0))) {
if (river_end.empty() ||
river_end[river_end.size() - 1].x < i - 6) {
river_end.push_back(point(i, OMAPY - 1));
}
}
if (south->get_ter(i, 0, 0).has("road_nesw")) {
roads_out.push_back(city(i, OMAPY - 1, 0));
}
}
for (auto &i : south->roads_out) {
if (i.y == 0) {
roads_out.push_back(city(i.x, OMAPY - 1, 0));
}
}
}
size_t rivers_to_south = river_end.size();
if (east != NULL) {
for (int i = 2; i < OMAPY - 2; i++) {
if (has_river(east->get_ter(0, i, 0))) {
ter(OMAPX - 1, i, 0).set(river_center);
}
if (has_river(east->get_ter(0, i, 0)) &&
has_river(east->get_ter(0, i - 1, 0)) &&
has_river(east->get_ter(0, i + 1, 0))) {
if (river_end.size() == rivers_to_south ||
river_end[river_end.size() - 1].y < i - 6) {
river_end.push_back(point(OMAPX - 1, i));
}
}
if (east->get_ter(0, i, 0).has("road_nesw")) {
roads_out.push_back(city(OMAPX - 1, i, 0));
}
}
for (auto &i : east->roads_out) {
if (i.x == 0) {
roads_out.push_back(city(OMAPX - 1, i.y, 0));
}
}
}
// Even up the start and end points of rivers. (difference of 1 is acceptable)
// Also ensure there's at least one of each.
std::vector<point> new_rivers;
if (north == NULL || west == NULL) {
while (river_start.empty() || river_start.size() + 1 < river_end.size()) {
new_rivers.clear();
if (north == NULL) {
new_rivers.push_back( point(rng(10, OMAPX - 11), 0) );
}
if (west == NULL) {
new_rivers.push_back( point(0, rng(10, OMAPY - 11)) );
}
river_start.push_back( new_rivers[rng(0, new_rivers.size() - 1)] );
}
}
if (south == NULL || east == NULL) {
while (river_end.empty() || river_end.size() + 1 < river_start.size()) {
new_rivers.clear();
if (south == NULL) {
new_rivers.push_back( point(rng(10, OMAPX - 11), OMAPY - 1) );
}
if (east == NULL) {
new_rivers.push_back( point(OMAPX - 1, rng(10, OMAPY - 11)) );
}
river_end.push_back( new_rivers[rng(0, new_rivers.size() - 1)] );
}
}
// Now actually place those rivers.
if (river_start.size() > river_end.size() && !river_end.empty()) {
std::vector<point> river_end_copy = river_end;
while (!river_start.empty()) {
int index = rng(0, river_start.size() - 1);
if (!river_end.empty()) {
place_river(river_start[index], river_end[0]);
river_end.erase(river_end.begin());
} else
place_river(river_start[index],
river_end_copy[rng(0, river_end_copy.size() - 1)]);
river_start.erase(river_start.begin() + index);
}
} else if (river_end.size() > river_start.size() && !river_start.empty()) {
std::vector<point> river_start_copy = river_start;
while (!river_end.empty()) {
int index = rng(0, river_end.size() - 1);
if (!river_start.empty()) {
place_river(river_start[0], river_end[index]);
river_start.erase(river_start.begin());
} else
place_river(river_start_copy[rng(0, river_start_copy.size() - 1)],
river_end[index]);
river_end.erase(river_end.begin() + index);
}
} else if (!river_end.empty()) {
if (river_start.size() != river_end.size())
river_start.push_back( point(rng(OMAPX * .25, OMAPX * .75),
rng(OMAPY * .25, OMAPY * .75)));
for (size_t i = 0; i < river_start.size(); i++) {
place_river(river_start[i], river_end[i]);
}
}
// Cities and forests come next.
// These're agnostic of adjacent maps, so it's very simple.
place_cities();
place_forest();
// Ideally we should have at least two exit points for roads, on different sides
if (roads_out.size() < 2) {
std::vector<city> viable_roads;
int tmp;
// Populate viable_roads with one point for each neighborless side.
// Make sure these points don't conflict with rivers.
// TODO: In theory this is a potential infinte loop...
if (north == NULL) {
do {
tmp = rng(10, OMAPX - 11);
} while (has_river(ter(tmp, 0, 0)) || has_river(ter(tmp - 1, 0, 0)) ||
has_river(ter(tmp + 1, 0, 0)) );
viable_roads.push_back(city(tmp, 0, 0));
}
if (east == NULL) {
do {
tmp = rng(10, OMAPY - 11);
} while (has_river(ter(OMAPX - 1, tmp, 0)) || has_river(ter(OMAPX - 1, tmp - 1, 0)) ||
has_river(ter(OMAPX - 1, tmp + 1, 0)));
viable_roads.push_back(city(OMAPX - 1, tmp, 0));
}
if (south == NULL) {
do {
tmp = rng(10, OMAPX - 11);
} while (has_river(ter(tmp, OMAPY - 1, 0)) || has_river(ter(tmp - 1, OMAPY - 1, 0)) ||
has_river(ter(tmp + 1, OMAPY - 1, 0)));
viable_roads.push_back(city(tmp, OMAPY - 1, 0));
}
if (west == NULL) {
do {
tmp = rng(10, OMAPY - 11);
} while (has_river(ter(0, tmp, 0)) || has_river(ter(0, tmp - 1, 0)) ||
has_river(ter(0, tmp + 1, 0)));
viable_roads.push_back(city(0, tmp, 0));
}
while (roads_out.size() < 2 && !viable_roads.empty()) {
tmp = rng(0, viable_roads.size() - 1);
roads_out.push_back(viable_roads[tmp]);
viable_roads.erase(viable_roads.begin() + tmp);
}
}
// Compile our master list of roads; it's less messy if roads_out is first
for (auto &i : roads_out) {
road_points.push_back(i);
}
for (auto &i : cities) {
road_points.push_back(i);
}
// And finally connect them via "highways"
place_hiways(road_points, 0, "road");
place_specials();
// Clean up our roads and rivers
polish(0);
// TODO: there is no reason we can't generate the sublevels in one pass
// for that matter there is no reason we can't as we add the entrance ways either
// Always need at least one sublevel, but how many more
int z = -1;
bool requires_sub = false;
do {
requires_sub = generate_sub(z);
} while(requires_sub && (--z >= -OVERMAP_DEPTH));
// Place the monsters, now that the terrain is laid out
place_mongroups();
place_radios();
dbg(D_INFO) << "overmap::generate done";
}
bool overmap::generate_sub(int const z)
{
bool requires_sub = false;
std::vector<city> subway_points;
std::vector<city> sewer_points;
std::vector<city> ant_points;
std::vector<city> goo_points;
std::vector<city> lab_points;
std::vector<city> ice_lab_points;
std::vector<point> shaft_points;
std::vector<city> mine_points;
// These are so common that it's worth checking first as int.
const oter_id skip_above[5] = {
oter_id("rock"), oter_id("forest"), oter_id("field"),
oter_id("forest_thick"), oter_id("forest_water")
};
for (int i = 0; i < OMAPX; i++) {
for (int j = 0; j < OMAPY; j++) {
complex_map_tile oter_above = ter(i, j, z + 1);
// implicitly skip skip_above oter_ids
bool skipme = false;
for( auto &elem : skip_above ) {
if( oter_above.equals(elem) ) {
skipme = true;
}
}
if (skipme) {
continue;
}
if (oter_above.has("forest_water")) {
ter(i, j, z).add("cavern");
} else if (is_ot_type("house_base", oter_above)) {
ter(i, j, z).add("basement");
} else if (is_ot_type("sub_station", oter_above)) {
ter(i, j, z).add("subway_nesw");
subway_points.push_back(city(i, j, 0));
} else if (oter_above.has("road_nesw_manhole")) {
ter(i, j, z).add("sewer_nesw");
sewer_points.push_back(city(i, j, 0));
} else if (oter_above.has("sewage_treatment")) {
sewer_points.push_back(city(i, j, 0));
} else if (oter_above.has("cave") && z == -1) {
if (one_in(3)) {
ter(i, j, z).add("cave_rat");
requires_sub = true; // rat caves are two level
} else {
ter(i, j, z).add("cave");
}
} else if (oter_above.has("cave_rat") && z == -2) {
ter(i, j, z).add("cave_rat");
} else if (oter_above.has("anthill")) {
int size = rng(MIN_ANT_SIZE, MAX_ANT_SIZE);
ant_points.push_back(city(i, j, size));
add_mon_group(mongroup("GROUP_ANT", i * 2, j * 2, z, size * 1.5, rng(6000, 8000)));
} else if (oter_above.has("slimepit_down")) {
int size = rng(MIN_GOO_SIZE, MAX_GOO_SIZE);
goo_points.push_back(city(i, j, size));
} else if (oter_above.has("lab_core") ||
(z == -1 && oter_above.has("lab_stairs"))) {
lab_points.push_back(city(i, j, rng(1, 5 + z)));
} else if (oter_above.has("lab_stairs")) {
ter(i, j, z).add("lab");
} else if (oter_above.has("ice_lab_core") ||
(z == -1 && oter_above.has("ice_lab_stairs"))) {
ice_lab_points.push_back(city(i, j, rng(1, 5 + z)));
} else if (oter_above.has("ice_lab_stairs")) {
ter(i, j, z).add("ice_lab");
} else if (oter_above.has("mine_entrance")) {
shaft_points.push_back( point(i, j) );
} else if (oter_above.has("mine_shaft") ||
oter_above.has("mine_down") ) {
ter(i, j, z).add("mine");
mine_points.push_back(city(i, j, rng(6 + z, 10 + z)));
// technically not all finales need a sub level,
// but at this point we don't know
requires_sub = true;
} else if (oter_above.has("mine_finale")) {
for (int x = i - 1; x <= i + 1; x++) {
for (int y = j - 1; y <= j + 1; y++) {
ter(x, y, z).add("spiral");
}
}
ter(i, j, z).add("spiral_hub");
add_mon_group(mongroup("GROUP_SPIRAL", i * 2, j * 2, z, 2, 200));
} else if (oter_above.has("silo")) {
if (rng(2, 7) < abs(z) || rng(2, 7) < abs(z)) {
ter(i, j, z).add("silo_finale");
} else {
ter(i, j, z).add("silo");
requires_sub = true;
}
}
}
}
for (auto &i : goo_points) {
requires_sub |= build_slimepit(i.x, i.y, z, i.s);
}
place_hiways(sewer_points, z, "sewer");
polish(z, "sewer");
place_hiways(subway_points, z, "subway");
for (auto &i : subway_points) {
ter(i.x, i.y, z).add("subway_station");
}
for (auto &i : lab_points) {
bool lab = build_lab(i.x, i.y, z, i.s);
requires_sub |= lab;
if (!lab && ter(i.x, i.y, z).has("lab_core")) {
ter(i.x, i.y, z).add("lab");
}
}
for (auto &i : ice_lab_points) {
bool ice_lab = build_ice_lab(i.x, i.y, z, i.s);
requires_sub |= ice_lab;
if (!ice_lab && ter(i.x, i.y, z).has("ice_lab_core")) {
ter(i.x, i.y, z).add("ice_lab");
}
}
for (auto &i : ant_points) {
build_anthill(i.x, i.y, z, i.s);
}
polish(z, "subway");
polish(z, "ants");
for (auto &i : cities) {
if (one_in(3)) {
add_mon_group(mongroup("GROUP_CHUD", i.x * 2, i.y * 2, z, i.s, i.s * 20));
}
if (!one_in(8)) {
add_mon_group(mongroup("GROUP_SEWER", i.x * 2, i.y * 2, z, i.s * 3.5, i.s * 70));
}
}
place_rifts(z);
for (auto &i : mine_points) {
build_mine(i.x, i.y, z, i.s);
}
for (auto &i : shaft_points) {
ter(i.x, i.y, z).add("mine_shaft");
requires_sub = true;
}
return requires_sub;
}
std::vector<point> overmap::find_terrain(const std::string &term, int zlevel)
{
std::vector<point> found;
for (int x = 0; x < OMAPX; x++) {
for (int y = 0; y < OMAPY; y++) {
if (seen(x, y, zlevel) &&
complex_lcmatch(ter(x, y, zlevel), term ) ) {
found.push_back( point( get_left_border() + x, get_top_border() + y) );
}
}
}
return found;
}
int overmap::closest_city(point p)
{
int distance = 999;
size_t ret = -1;
for (size_t i = 0; i < cities.size(); i++) {
int dist = rl_dist(p.x, p.y, cities[i].x, cities[i].y);
if (dist < distance || (dist == distance && cities[i].s < cities[ret].s)) {
ret = i;
distance = dist;
}
}
return ret;
}
point overmap::random_house_in_city(int city_id)
{
if (city_id < 0 || size_t(city_id) >= cities.size()) {
debugmsg("overmap::random_house_in_city(%d) (max %d)", city_id,
cities.size() - 1);
return point(-1, -1);
}
std::vector<point> valid;
int startx = cities[city_id].x - cities[city_id].s;
int endx = cities[city_id].x + cities[city_id].s;
int starty = cities[city_id].y - cities[city_id].s;
int endy = cities[city_id].y + cities[city_id].s;
for (int x = startx; x <= endx; x++) {
for (int y = starty; y <= endy; y++) {
if (check_ot_type("house", x, y, 0)) {
valid.push_back( point(x, y) );
}
}
}
if (valid.empty()) {
return point(-1, -1);
}
return valid[ rng(0, valid.size() - 1) ];
}
int overmap::dist_from_city(point p)
{
int distance = 999;
for (auto &i : cities) {
int dist = rl_dist(p.x, p.y, i.x, i.y);
dist -= i.s;
if (dist < distance) {
distance = dist;
}
}
return distance;
}
void overmap::draw(WINDOW *w, WINDOW *wbar, const tripoint &center,
const tripoint &orig, bool blink, bool show_explored,
input_context *inp_ctxt,
bool debug_monstergroups,
const int iZoneIndex)
{
const int z = center.z;
const int cursx = center.x;
const int cursy = center.y;
const int om_map_width = OVERMAP_WINDOW_WIDTH;
const int om_map_height = OVERMAP_WINDOW_HEIGHT;
// Target of current mission
point target;
bool has_target = false;
if (g->u.active_mission >= 0 && size_t(g->u.active_mission) < g->u.active_missions.size()) {
target = g->find_mission(g->u.active_missions[g->u.active_mission])->target;
has_target = target != overmap::invalid_point;
}
// seen status & terrain of center position
bool csee = false;
oter_id ccur_ter = "";
// used inside the loop
complex_map_tile cur_tile;
complex_map_tile ccur_tile;
oter_id cur_ter = ot_null;
nc_color ter_color;
long ter_sym;
// sight_points is hoisted for speed reasons.
int sight_points = g->u.overmap_sight_range(g->light_level());
std::string sZoneName = "";
tripoint tripointZone = tripoint(-1, -1, -1);
if (iZoneIndex != -1) {
sZoneName = g->u.Zones.vZones[iZoneIndex].getName();
point pOMZone = overmapbuffer::ms_to_omt_copy(g->u.Zones.vZones[iZoneIndex].getCenterPoint());
tripointZone = tripoint(pOMZone.x, pOMZone.y);
}
// If we're debugging monster groups, find the monster group we've selected
const mongroup *mgroup = NULL;
std::vector<mongroup *> mgroups;
if(debug_monstergroups) {
mgroups = overmap_buffer.monsters_at( center.x, center.y, center.z );
for( const auto &mgp : mgroups ) {
mgroup = mgp;
if( mgp->horde ) {
break;
}
}
}
for (int i = 0; i < om_map_width; i++) {
for (int j = 0; j < om_map_height; j++) {
const int omx = cursx + i - (om_map_width / 2);
const int omy = cursy + j - (om_map_height / 2);
const bool see = overmap_buffer.seen(omx, omy, z);
bool los = false;
if (see) {
// Only load terrain if we can actually see it
cur_tile = overmap_buffer.ter(omx, omy, z);
cur_ter = cur_tile.visible();
// Check if location is within player line-of-sight
if (g->u.overmap_los(omx, omy, sight_points)) {
los = true;
}
}
//Check if there is an npc.
const bool npc_here = overmap_buffer.has_npc(omx, omy, z);
// Check for hordes within player line-of-sight
bool horde_here = false;
if (los) {
std::vector<mongroup *> hordes = overmap_buffer.monsters_at(omx, omy, z);
for (const auto &ih : hordes) {
if (ih->horde) {
horde_here = true;
}
}
}
// and a vehicle
const bool veh_here = overmap_buffer.has_vehicle(omx, omy, z);
if (blink && omx == orig.x && omy == orig.y && z == orig.z) {
// Display player pos, should always be visible
ter_color = g->u.symbol_color();
ter_sym = '@';
} else if (blink && has_target && omx == target.x && omy == target.y && z == 0) {
// TODO: mission targets currently have no z-component, are assumed to be on z=0
// Mission target, display always, player should know where it is anyway.
ter_color = c_red;
ter_sym = '*';
} else if (blink && overmap_buffer.has_note(omx, omy, z)) {
// Display notes in all situations, even when not seen
const std::string &note_text = overmap_buffer.note(omx, omy, z);
ter_color = c_yellow;
ter_sym = 'N';
int symbolIndex = note_text.find(':');
int colorIndex = note_text.find(';');
bool symbolFirst = symbolIndex < colorIndex;
if (colorIndex > -1 && symbolIndex > -1) {
if (symbolFirst) {
if (colorIndex > 4) {
colorIndex = -1;
}
if (symbolIndex > 1) {
symbolIndex = -1;
colorIndex = -1;
}
} else {
if (symbolIndex > 4) {
symbolIndex = -1;
}
if (colorIndex > 2) {
colorIndex = -1;
}
}
} else if (colorIndex > 2) {
colorIndex = -1;
} else if (symbolIndex > 1) {
symbolIndex = -1;
}
if (symbolIndex > -1) {
int symbolStart = 0;
if (colorIndex > -1 && !symbolFirst) {
symbolStart = colorIndex + 1;
}
ter_sym = note_text.substr(symbolStart, symbolIndex - symbolStart).c_str()[0];
}
if (colorIndex > -1) {
int colorStart = 0;
if (symbolIndex > -1 && symbolFirst) {
colorStart = symbolIndex + 1;
}
std::string sym = note_text.substr(colorStart, colorIndex - colorStart);
ter_color = get_note_color( sym );
}
} else if (!see) {
// All cases above ignore the seen-status,
ter_color = c_dkgray;
ter_sym = '#';
// All cases below assume that see is true.
} else if (blink && npc_here) {
// Display NPCs only when player can see the location
ter_color = c_pink;
ter_sym = '@';
} else if (blink && horde_here) {
// Display Hordes only when within player line-of-sight
ter_color = c_green;
ter_sym = 'Z';
} else if (blink && veh_here) {
// Display Vehicles only when player can see the location
ter_color = c_cyan;
ter_sym = 'c';
} else {
if (sZoneName != "" && tripointZone.x == omx && tripointZone.y == omy) {
ter_color = c_yellow;
ter_sym = 'Z';
} else {
// Nothing special, but is visible to the player.
if (otermap.find(cur_ter) == otermap.end()) {
debugmsg("Bad ter %s (%d, %d)", cur_ter.c_str(), omx, omy);
ter_color = c_red;
ter_sym = '?';
} else {
// Map tile marked as explored
if (show_explored && overmap_buffer.is_explored(omx, omy, z)) {
ter_color = c_dkgray;
} else {
ter_color = otermap[cur_ter].color;
}
ter_sym = otermap[cur_ter].sym;
}
}
}
// Are we debugging monster groups?
if(blink && debug_monstergroups) {
// Check if this tile is the target of the currently selected group
if(mgroup && mgroup->tx / 2 == omx && mgroup->ty / 2 == omy) {
ter_color = c_red;
ter_sym = 'x';
} else {
const auto &groups = overmap_buffer.monsters_at( omx, omy, center.z );
for( auto &mgp : groups ) {
if( mgp->type == "GROUP_FOREST" ) {
// Don't flood the map with forest creatures.
continue;
}
if( mgp->horde ) {
// Hordes show as +
ter_sym = '+';
break;
} else {
// Regular groups show as -
ter_sym = '-';
}
}
// Set the color only if we encountered an eligible group.
if( ter_sym == '+' || ter_sym == '-' ) {
if( los ) {
ter_color = c_ltblue;
} else {
ter_color = c_blue;
}
}
}
}
if (omx == cursx && omy == cursy) {
csee = see;
ccur_ter = cur_ter;
ccur_tile = cur_tile;
mvwputch_hi(w, j, i, ter_color, ter_sym);
} else {
mvwputch(w, j, i, ter_color, ter_sym);
}
}
}
if (has_target && blink &&
(target.x < cursx - om_map_height / 2 ||
target.x > cursx + om_map_height / 2 ||
target.y < cursy - om_map_width / 2 ||
target.y > cursy + om_map_width / 2)) {
// TODO: mission targets currently have no z-component, are assumed to be on z=0
switch (direction_from(cursx, cursy, target.x, target.y)) {
case NORTH:
mvwputch(w, 0, om_map_width / 2, c_red, '^');
break;
case NORTHEAST:
mvwputch(w, 0, om_map_width - 1, c_red, LINE_OOXX);
break;
case EAST:
mvwputch(w, om_map_height / 2, om_map_width - 1, c_red, '>');
break;
case SOUTHEAST:
mvwputch(w, om_map_height, om_map_width - 1, c_red, LINE_XOOX);
break;
case SOUTH:
mvwputch(w, om_map_height, om_map_height / 2, c_red, 'v');
break;
case SOUTHWEST:
mvwputch(w, om_map_height, 0, c_red, LINE_XXOO);
break;
case WEST:
mvwputch(w, om_map_height / 2, 0, c_red, '<');
break;
case NORTHWEST:
mvwputch(w, 0, 0, c_red, LINE_OXXO);
break;
default:
break; //Do nothing
}
}
std::string note_text = overmap_buffer.note(cursx, cursy, z);
int symbolIndex = note_text.find(':');
int colorIndex = note_text.find(';');
bool symbolFirst = symbolIndex < colorIndex;
if (colorIndex > -1 && symbolIndex > -1) {
if (symbolFirst) {
if (colorIndex > 4) {
colorIndex = -1;
}
if (symbolIndex > 1) {
symbolIndex = -1;
colorIndex = -1;
}
} else {
if (symbolIndex > 4) {
symbolIndex = -1;
}
if (colorIndex > 2) {
colorIndex = -1;
}
}
} else if (colorIndex > 2) {
colorIndex = -1;
} else if (symbolIndex > 1) {
symbolIndex = -1;
}
int erasureLength = 0;
if (symbolIndex > -1 && symbolIndex < 5) {
if (colorIndex > -1 && !symbolFirst) {
erasureLength = symbolIndex;
} else if (colorIndex == -1) {
erasureLength = symbolIndex;
}
}
if (colorIndex > -1 && colorIndex < 5) {
if (symbolIndex > -1 && symbolFirst) {
erasureLength = colorIndex;
} else if (symbolIndex == -1) {
erasureLength = colorIndex;
}
}
if (erasureLength > 0) {
note_text.erase(0, erasureLength + 1);
}
std::vector<std::string> corner_text;
if (!note_text.empty()) {
corner_text.push_back( note_text );
}
const auto npcs = overmap_buffer.get_npcs_near_omt(cursx, cursy, z, 0);
for( const auto &npc : npcs ) {
if( npc->marked_for_death ) {
continue;
}
corner_text.push_back( npc->name );
}
const auto vehicles = overmap_buffer.get_vehicle(cursx, cursy, z);
for( const auto &v : vehicles ) {
corner_text.push_back( v.name );
}
if( !corner_text.empty() ) {
int maxlen = 0;
for( const auto &line : corner_text ) {
maxlen = std::max( maxlen, utf8_width( line.c_str() ) );
}
for( size_t i = 0; i < corner_text.size(); i++ ) {
// clear line, print line, print vertical line at the right side.
mvwprintz( w, i, 0, c_yellow, std::string(maxlen, ' ').c_str() );
mvwprintz( w, i, 0, c_yellow, "%s", corner_text[i].c_str() );
mvwputch( w, i, maxlen, c_white, LINE_XOXO );
}
for (int i = 0; i <= maxlen; i++) {
mvwputch( w, corner_text.size(), i, c_white, LINE_OXOX );
}
mvwputch( w, corner_text.size(), maxlen, c_white, LINE_XOOX );
}
if (sZoneName != "" && tripointZone.x == cursx && tripointZone.y == cursy) {
std::string sTemp = _("Zone:");
sTemp += " " + sZoneName;
const int length = utf8_width(sTemp.c_str());
for (int i = 0; i <= length; i++) {
mvwputch(w, om_map_height-2, i, c_white, LINE_OXOX);
}
mvwprintz(w, om_map_height-1, 0, c_yellow, "%s", sTemp.c_str());
mvwputch(w, om_map_height-2, length, c_white, LINE_OOXX);
mvwputch(w, om_map_height-1, length, c_white, LINE_XOXO);
}
// Draw the vertical line
for (int j = 0; j < TERMY; j++) {
mvwputch(wbar, j, 0, c_white, LINE_XOXO);
}
// Clear the legend
for (int i = 1; i < 55; i++) {
for (int j = 0; j < TERMY; j++) {
mvwputch(wbar, j, i, c_black, ' ');
}
}
// Draw text describing the overmap tile at the cursor position.
if (csee) {
if(!mgroups.empty()) {
int line_number = 1;
for( const auto &mgroup : mgroups ) {
mvwprintz(wbar, line_number++, 3,
c_blue, " Species: %s", mgroup->type.c_str());
mvwprintz(wbar, line_number++, 3,
c_blue, "# monsters: %d", mgroup->population);
if( !mgroup->horde ) {
continue;
}
mvwprintz(wbar, line_number++, 3,
c_blue, " Interest: %d", mgroup->interest);
mvwprintz(wbar, line_number, 3,
c_blue, " Target: %d, %d", mgroup->tx, mgroup->ty);
mvwprintz(wbar, line_number++, 3,
c_red, "x");
}
} else {
for (size_t j = 0; j < ccur_tile.tiles.size(); j++) {
oter_id tile_ter = ccur_tile.tiles[j];
mvwputch(wbar, j + 1, 1, otermap[tile_ter].color, otermap[tile_ter].sym);
//std::vector<std::string> name = foldstring(otermap[tile_ter].name, 25);
//size_t offset = 0;
mvwprintz(wbar, j + 1, 3, otermap[tile_ter].color, "%s: %s", otermap[tile_ter].name.c_str(),
tile_ter.t().id_base.c_str());
//for (size_t i = 0; i < name.size(); i++) {
//if (i > 0) offset += 1;
//}
}
}
} else {
mvwprintz(wbar, 1, 1, c_dkgray, _("# Unexplored"));
}
if (has_target) {
// TODO: mission targets currently have no z-component, are assumed to be on z=0
int distance = rl_dist(orig.x, orig.y, target.x, target.y);
mvwprintz(wbar, 3, 1, c_white, _("Distance to target: %d"), distance);
}
mvwprintz(wbar, 14, 1, c_magenta, _("Use movement keys to pan."));
if (inp_ctxt != NULL) {
mvwprintz(wbar, 15, 1, c_magenta, (inp_ctxt->get_desc("CENTER") +
_(" - Center map on character")).c_str());
mvwprintz(wbar, 16, 1, c_magenta, (inp_ctxt->get_desc("SEARCH") +
_(" - Search")).c_str());
mvwprintz(wbar, 17, 1, c_magenta, (inp_ctxt->get_desc("CREATE_NOTE") +
_(" - Add/Edit a note")).c_str());
mvwprintz(wbar, 18, 1, c_magenta, (inp_ctxt->get_desc("DELETE_NOTE") +
_(" - Delete a note")).c_str());
mvwprintz(wbar, 19, 1, c_magenta, (inp_ctxt->get_desc("LIST_NOTES") +
_(" - List notes")).c_str());
mvwprintz(wbar, 20, 1, c_magenta, (inp_ctxt->get_desc("TOGGLE_BLINKING") +
_(" - Toggle Blinking")).c_str());
mvwprintz(wbar, 21, 1, c_magenta, (inp_ctxt->get_desc("TOGGLE_OVERLAYS") +
_(" - Toggle Overlays")).c_str());
mvwprintz(wbar, 22, 1, c_magenta, (inp_ctxt->get_desc("TOGGLE_EXPLORED") +
_(" - Toggle Explored")).c_str());
mvwprintz(wbar, 23, 1, c_magenta, (inp_ctxt->get_desc("HELP_KEYBINDINGS") +
_(" - Change keys")).c_str());
fold_and_print(wbar, 24, 1, 27, c_magenta, ("m, " + inp_ctxt->get_desc("QUIT") +
_(" - Return to game")).c_str());
}
point omt(cursx, cursy);
const point om = overmapbuffer::omt_to_om_remain(omt);
mvwprintz(wbar, getmaxy(wbar) - 1, 1, c_red,
_("LEVEL %i, %d'%d, %d'%d"), z, om.x, omt.x, om.y, omt.y);
// Done with all drawing!
wrefresh(w);
wrefresh(wbar);
}
tripoint overmap::draw_overmap()
{
return draw_overmap(g->om_global_location());
}
tripoint overmap::draw_overmap(int z)
{
tripoint loc = g->om_global_location();
loc.z = z;
return draw_overmap(loc);
}
//Start drawing the overmap on the screen using the (m)ap command.
tripoint overmap::draw_overmap(const tripoint &orig, bool debug_mongroup, const tripoint &select, const int iZoneIndex)
{
delwin(g->w_omlegend);
g->w_omlegend = newwin(TERMY, 28, 0, TERMX - 28);
delwin(g->w_overmap);
g->w_overmap = newwin(OVERMAP_WINDOW_HEIGHT, OVERMAP_WINDOW_WIDTH, 0, 0);
// Draw black padding space to avoid gap between map and legend
delwin(g->w_blackspace);
g->w_blackspace = newwin(TERMY, TERMX - 28, 0, 0);
mvwputch(g->w_blackspace, 0, 0, c_black, ' ');
wrefresh(g->w_blackspace);
tripoint ret = invalid_tripoint;
tripoint curs(orig);
if (select.x != -1 && select.y != -1 && select.z != -1) {
curs = tripoint(select);
}
// Configure input context for navigating the map.
input_context ictxt("OVERMAP");
ictxt.register_action("ANY_INPUT");
ictxt.register_directions();
ictxt.register_action("CONFIRM");
ictxt.register_action("LEVEL_UP");
ictxt.register_action("LEVEL_DOWN");
ictxt.register_action("HELP_KEYBINDINGS");
// Actions whose keys we want to display.
ictxt.register_action("CENTER");
ictxt.register_action("CREATE_NOTE");
ictxt.register_action("DELETE_NOTE");
ictxt.register_action("SEARCH");
ictxt.register_action("LIST_NOTES");
ictxt.register_action("TOGGLE_BLINKING");
ictxt.register_action("TOGGLE_OVERLAYS");
ictxt.register_action("TOGGLE_EXPLORED");
ictxt.register_action("QUIT");
std::string action;
do {
timeout( BLINK_SPEED );
bool show_explored = uistate.overmap_blinking || uistate.overmap_show_overlays;
draw(g->w_overmap, g->w_omlegend, curs, orig, uistate.overmap_show_overlays, show_explored, &ictxt, debug_mongroup, iZoneIndex);
action = ictxt.handle_input();
timeout(-1);
int dirx, diry;
if (ictxt.get_direction(dirx, diry, action)) {
curs.x += dirx;
curs.y += diry;
} else if (action == "CENTER") {
curs = orig;
} else if (action == "LEVEL_DOWN" && curs.z > -OVERMAP_DEPTH) {
curs.z -= 1;
} else if (action == "LEVEL_UP" && curs.z < OVERMAP_HEIGHT) {
curs.z += 1;
} else if (action == "CONFIRM") {
ret = tripoint(curs.x, curs.y, curs.z);
} else if (action == "QUIT") {
ret = invalid_tripoint;
} else if (action == "CREATE_NOTE") {
std::string color_notes = _("Color codes: ");
for( auto color_pair : get_note_color_names() ) {
// The color index is not translatable, but the name is.
color_notes += string_format( "%s:%s, ", color_pair.first.c_str(),
_(color_pair.second.c_str()) );
}
const std::string old_note = overmap_buffer.note(curs);
const std::string new_note = string_input_popup(
_("Note (X:TEXT for custom symbol, G; for color):"),
45, old_note, color_notes); // 45 char max
if(old_note != new_note) {
overmap_buffer.add_note(curs, new_note);
}
} else if(action == "DELETE_NOTE") {
if (overmap_buffer.has_note(curs) &&
query_yn(_("Really delete note?"))) {
overmap_buffer.delete_note(curs);
}
} else if (action == "LIST_NOTES") {
const point p = display_notes(curs.z);
if (p.x != -1 && p.y != -1) {
curs.x = p.x;
curs.y = p.y;
}
} else if (action == "TOGGLE_BLINKING") {
uistate.overmap_blinking = !uistate.overmap_blinking;
} else if (action == "TOGGLE_OVERLAYS") {
uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
} else if (action == "TOGGLE_EXPLORED") {
overmap_buffer.toggle_explored(curs.x, curs.y, curs.z);
} else if (action == "SEARCH") {
std::string term = string_input_popup(_("Search term:"));
if(term.empty()) {
continue;
}
std::transform( term.begin(), term.end(), term.begin(), tolower );
// This is on purpose only the current overmap, otherwise
// it would contain way to many entries
overmap &om = overmap_buffer.get_om_global(point(curs.x, curs.y));
std::vector<point> locations = om.find_notes(curs.z, term);
std::vector<point> terlist = om.find_terrain(term, curs.z);
locations.insert( locations.end(), terlist.begin(), terlist.end() );
if( locations.empty() ) {
continue;
}
int i = 0;
//Navigate through results
tripoint tmp = curs;
WINDOW *w_search = newwin(13, 27, 3, TERMX - 27);
input_context ctxt("OVERMAP_SEARCH");
ctxt.register_action("NEXT_TAB", _("Next target"));
ctxt.register_action("PREV_TAB", _("Previous target"));
ctxt.register_action("QUIT");
ctxt.register_action("CONFIRM");
ctxt.register_action("HELP_KEYBINDINGS");
ctxt.register_action("ANY_INPUT");
do {
tmp.x = locations[i].x;
tmp.y = locations[i].y;
draw(g->w_overmap, g->w_omlegend, tmp, orig, uistate.overmap_show_overlays, show_explored, NULL);
//Draw search box
draw_border(w_search);
mvwprintz(w_search, 1, 1, c_red, _("Find place:"));
mvwprintz(w_search, 2, 1, c_ltblue, " ");
mvwprintz(w_search, 2, 1, c_ltblue, "%s", term.c_str());
mvwprintz(w_search, 4, 1, c_white, _("'<' '>' Cycle targets."));
mvwprintz(w_search, 10, 1, c_white, _("Enter/Spacebar to select."));
mvwprintz(w_search, 11, 1, c_white, _("q or ESC to return."));
wrefresh(w_search);
timeout( BLINK_SPEED );
action = ctxt.handle_input();
timeout(-1);
if (uistate.overmap_blinking) {
uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
}
if (action == "NEXT_TAB") {
i = (i + 1) % locations.size();
} else if (action == "PREV_TAB") {
i = (i + locations.size() - 1) % locations.size();
} else if (action == "CONFIRM") {
curs = tmp;
}
} while(action != "CONFIRM" && action != "QUIT");
delwin(w_search);
action = "";
} else if (action == "TIMEOUT") {
if (uistate.overmap_blinking) {
uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
}
} else if (action == "ANY_INPUT") {
if (uistate.overmap_blinking) {
uistate.overmap_show_overlays = !uistate.overmap_show_overlays;
}
input_event e = ictxt.get_raw_input();
if(e.type == CATA_INPUT_KEYBOARD && e.get_first_input() == 'm') {
action = "QUIT";
}
}
} while (action != "QUIT" && action != "CONFIRM");
werase(g->w_overmap);
werase(g->w_omlegend);
erase();
g->refresh_all();
return ret;
}
tripoint overmap::find_random_omt( const std::string &omt_base_type ) const
{
std::vector<tripoint> valid;
for( int i = 0; i < OMAPX; i++ ) {
for( int j = 0; j < OMAPY; j++ ) {
for( int k = -OVERMAP_DEPTH; k <= OVERMAP_HEIGHT; k++ ) {
if( get_ter( i, j, k ).visible().t().id_base == omt_base_type ) {
valid.push_back( tripoint( i, j, k ) );
}
}
}
}
if( valid.empty() ) {
return invalid_tripoint;
}
return valid[rng( 0, valid.size() - 1 )];
}
void overmap::process_mongroups()
{
for( auto it = zg.begin(); it != zg.end(); ) {
mongroup &mg = it->second;
if( mg.dying ) {
mg.population *= .8;
mg.radius *= .9;
}
if( mg.population <= 0 ) {
zg.erase( it++ );
} else {
++it;
}
}
}
void mongroup::wander()
{
// TODO: More interesting stuff possible, like looking for nearby shelter.
// What a monster thinks of as shelter is another matter...
tx += rng( -10, 10 );
ty += rng( -10, 10 );
interest = 30;
}
void overmap::move_hordes()
{
// Prevent hordes to be moved twice by putting them in here after moving.
decltype(zg) tmpzg;
//MOVE ZOMBIE GROUPS
for( auto it = zg.begin(); it != zg.end(); ) {
mongroup &mg = it->second;
if( !mg.horde ) {
++it;
continue;
}
if( rng(0, 100) < mg.interest ) {
// TODO: Adjust for monster speed.
// TODO: Handle moving to adjacent overmaps.
if( mg.posx > mg.tx) {
mg.posx--;
}
if( mg.posx < mg.tx) {
mg.posx++;
}
if( mg.posy > mg.ty) {
mg.posy--;
}
if( mg.posy < mg.ty) {
mg.posy++;
}
if( mg.posx == mg.tx && mg.posy == mg.ty ) {
mg.wander();
} else {
mg.dec_interest( 1 );
}
// Erase the group at it's old location, add the group with the new location
tmpzg.insert( std::pair<tripoint, mongroup>( tripoint(mg.posx, mg.posy, mg.posz ), mg ) );
zg.erase( it++ );
}
}
// and now back into the monster group map.
zg.insert( tmpzg.begin(), tmpzg.end() );
}
/**
* @param sig_power - power of signal or max distantion for reaction of zombies
*/
void overmap::signal_hordes( const int x, const int y, const int sig_power)
{
// TODO: Signal adjacent overmaps too. (the 3 nearest ones)
for( auto &elem : zg ) {
mongroup &mg = elem.second;
if( !mg.horde ) {
continue;
}
const int dist = rl_dist( x, y, mg.posx, mg.posy );
if( sig_power <= dist ) {
continue;
}
// TODO: base this in monster attributes, foremost GOODHEARING.
const int d_inter = (sig_power - dist) * 5;
const int roll = rng( 0, mg.interest );
if( roll < d_inter ) {
const int targ_dist = rl_dist( x, y, mg.tx, mg.ty );
// TODO: Base this on targ_dist:dist ratio.
if (targ_dist < 5) {
mg.set_target( (mg.tx + x) / 2, (mg.ty + y) / 2 );
mg.inc_interest( d_inter );
} else {
mg.set_target( x, y );
mg.set_interest( d_inter );
}
}
}
}
void grow_forest_oter_id(complex_map_tile &tile, bool swampy)
{
if (swampy && ( tile.has(ot_field) || tile.has(ot_forest) ) ) {
tile.set(ot_forest_water);
} else if ( tile.has(ot_forest) ) {
tile.set(ot_forest_thick);
} else if ( tile.has(ot_field) ) {
tile.set(ot_forest);
}
}
void overmap::place_forest()
{
for (int i = 0; i < settings.num_forests; i++) {
// forx and fory determine the epicenter of the forest
int forx = rng(0, OMAPX - 1);
int fory = rng(0, OMAPY - 1);
// fors determinds its basic size
int fors = rng(settings.forest_size_min, settings.forest_size_max);
int outer_tries = 1000;
int inner_tries = 1000;
for (auto j = cities.begin(); j != cities.end(); j++) {
inner_tries = 1000;
while (trig_dist(forx, fory, j->x, j->y) - fors / 2 < j->s ) {
// Set forx and fory far enough from cities
forx = rng(0, OMAPX - 1);
fory = rng(0, OMAPY - 1);
// Set fors to determine the size of the forest; usually won't overlap w/ cities
fors = rng(settings.forest_size_min, settings.forest_size_max);
j = cities.begin();
if( 0 == --inner_tries ) {
break;
}
}
if( 0 == --outer_tries || 0 == inner_tries ) {
break;
}
}
if( 0 == outer_tries || 0 == inner_tries ) {
break;
}
int swamps = settings.swamp_maxsize; // How big the swamp may be...
int x = forx;
int y = fory;
// Depending on the size on the forest...
for (int j = 0; j < fors; j++) {
int swamp_chance = 0;
for (int k = -2; k <= 2; k++) {
for (int l = -2; l <= 2; l++) {
if (ter(x + k, y + l, 0).has("forest_water") ||
check_ot_type("river", x + k, y + l, 0)) {
swamp_chance += settings.swamp_river_influence;
}
}
}
bool swampy = false;
if (swamps > 0 && swamp_chance > 0 && !one_in(swamp_chance) &&
(ter(x, y, 0).has("forest") || ter(x, y, 0).has("forest_thick") ||
ter(x, y, 0).has("field") || one_in( settings.swamp_spread_chance ))) {
// ...and make a swamp.
ter(x, y, 0).set("forest_water");
swampy = true;
swamps--;
} else if (swamp_chance == 0) {
swamps = settings.swamp_maxsize;
}
// Place or embiggen forest
for ( int mx = -1; mx < 2; mx++ ) {
for ( int my = -1; my < 2; my++ ) {
grow_forest_oter_id( ter(x + mx, y + my, 0),
( mx == 0 && my == 0 ? false : swampy ) );
}
}
// Random walk our forest
x += rng(-2, 2);
if (x < 0 ) {
x = 0;
}
if (x > OMAPX) {
x = OMAPX;
}
y += rng(-2, 2);
if (y < 0 ) {
y = 0;
}
if (y > OMAPY) {
y = OMAPY;
}
}
}
}
void overmap::place_river(point pa, point pb)
{
int x = pa.x, y = pa.y;
do {
x += rng(-1, 1);
y += rng(-1, 1);
if (x < 0) {
x = 0;
}
if (x > OMAPX - 1) {
x = OMAPX - 1;
}
if (y < 0) {
y = 0;
}
if (y > OMAPY - 1) {
y = OMAPY - 1;
}
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (y + i >= 0 && y + i < OMAPY && x + j >= 0 && x + j < OMAPX) {
ter(x + j, y + i, 0).set("river_center");
}
}
}
if (pb.x > x && (rng(0, int(OMAPX * 1.2) - 1) < pb.x - x ||
(rng(0, int(OMAPX * .2) - 1) > pb.x - x &&
rng(0, int(OMAPY * .2) - 1) > abs(pb.y - y)))) {
x++;
}
if (pb.x < x && (rng(0, int(OMAPX * 1.2) - 1) < x - pb.x ||
(rng(0, int(OMAPX * .2) - 1) > x - pb.x &&
rng(0, int(OMAPY * .2) - 1) > abs(pb.y - y)))) {
x--;
}
if (pb.y > y && (rng(0, int(OMAPY * 1.2) - 1) < pb.y - y ||
(rng(0, int(OMAPY * .2) - 1) > pb.y - y &&
rng(0, int(OMAPX * .2) - 1) > abs(x - pb.x)))) {
y++;
}
if (pb.y < y && (rng(0, int(OMAPY * 1.2) - 1) < y - pb.y ||
(rng(0, int(OMAPY * .2) - 1) > y - pb.y &&
rng(0, int(OMAPX * .2) - 1) > abs(x - pb.x)))) {
y--;
}
x += rng(-1, 1);
y += rng(-1, 1);
if (x < 0) {
x = 0;
}
if (x > OMAPX - 1) {
x = OMAPX - 2;
}
if (y < 0) {
y = 0;
}
if (y > OMAPY - 1) {
y = OMAPY - 1;
}
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
// We don't want our riverbanks touching the edge of the map for many reasons
if ((y + i >= 1 && y + i < OMAPY - 1 && x + j >= 1 && x + j < OMAPX - 1) ||
// UNLESS, of course, that's where the river is headed!
(abs(pb.y - (y + i)) < 4 && abs(pb.x - (x + j)) < 4)) {
ter(x + j, y + i, 0).set("river_center");
}
}
}
} while (pb.x != x || pb.y != y);
}
/*: the root is overmap::place_cities()
20:50 <kevingranade>: which is at overmap.cpp:1355 or so
20:51 <kevingranade>: the key is cs = rng(4, 17), setting the "size" of the city
20:51 <kevingranade>: which is roughly it's radius in overmap tiles
20:52 <kevingranade>: then later overmap::place_mongroups() is called
20:52 <kevingranade>: which creates a mongroup with radius city_size * 2.5 and population city_size * 80
20:53 <kevingranade>: tadaa
spawns happen at... <cue Clue music>
20:56 <kevingranade>: game:pawn_mon() in game.cpp:7380*/
void overmap::place_cities()
{
int NUM_CITIES = dice(4, 4);
int start_dir;
int op_city_size = int(ACTIVE_WORLD_OPTIONS["CITY_SIZE"]);
// Limit number of cities based on average size.
NUM_CITIES = std::min(NUM_CITIES, int(256 / op_city_size * op_city_size));
// Generate a list of random cities in accordance with village/town/city rules.
int village_size = std::max(op_city_size - 2, 1);
int town_min = std::max(op_city_size - 1, 1);
int town_max = op_city_size + 1;
int city_size = op_city_size + 3;
while (cities.size() < size_t(NUM_CITIES)) {
int cx = rng(12, OMAPX - 12);
int cy = rng(12, OMAPY - 12);
int size = dice(town_min, town_max);
if (one_in(6)) {
size = city_size;
} else if (one_in(3)) {
size = village_size;
}
if (ter(cx, cy, 0).equals(settings.default_oter) ) {
ter(cx, cy, 0).add("road_nesw");
city tmp;
tmp.x = cx;
tmp.y = cy;
tmp.s = size;
cities.push_back(tmp);
start_dir = rng(0, 3);
for (int j = 0; j < 4; j++) {
make_road(cx, cy, size, (start_dir + j) % 4, tmp);
}
}
}
}
void overmap::put_buildings(int x, int y, int dir, city town)
{
int ychange = dir % 2, xchange = (dir + 1) % 2;
for (int i = -1; i <= 1; i += 2) {
if ((ter(x + i * xchange, y + i * ychange, 0).equals(settings.default_oter) ) &&
!one_in(STREETCHANCE)) {
if (rng(0, 99) > 80 * trig_dist(x, y, town.x, town.y) / town.s) {
ter(x + i * xchange, y + i * ychange, 0).add(
shop( ((dir % 2) - i) % 4, settings.city_spec.shops ) );
} else {
if (rng(0, 99) > 130 * trig_dist(x, y, town.x, town.y) / town.s) {
ter(x + i * xchange, y + i * ychange, 0).add(
shop( ((dir % 2) - i) % 4, settings.city_spec.parks ) );
} else {
ter(x + i * xchange, y + i * ychange, 0).add(
house( ((dir % 2) - i) % 4, settings.house_basement_chance ) );
}
}
}
}
}
void overmap::make_road(int x, int y, int cs, int dir, city town)
{
int c = cs;
int croad = cs;
int dirx = 0;
int diry = 0;
std::string road;
std::string crossroad;
switch( dir ) {
case 0:
dirx = 0;
diry = -1;
road = "road_ns";
crossroad = "road_ew";
break;
case 1:
dirx = 1;
diry = 0;
road = "road_ew";
crossroad = "road_ns";
break;
case 2:
dirx = 0;
diry = 1;
road = "road_ns";
crossroad = "road_ew";
break;
case 3:
dirx = -1;
diry = 0;
road = "road_ew";
crossroad = "road_ns";
break;
default:
// Out-of-range dir value, bail out.
return;
}
// Grow in the stated direction, sprouting off sub-roads and placing buildings as we go.
while( c > 0 && y > 0 && x > 0 && y < OMAPY - 1 && x < OMAPX - 1 &&
(ter(x + dirx, y + diry, 0).equals(settings.default_oter) || c == cs) ) {
x += dirx;
y += diry;
c--;
ter( x, y, 0 ).add(road.c_str());
// Look for a crossroad or a road ahead, if we find one,
// set current tile to be road_null and c to -1 to prevent further branching.
if( ter( x + dirx, y + diry, 0 ).has(road.c_str()) ||
ter( x + dirx, y + diry, 0 ).has(crossroad.c_str()) ||
// This looks left and right of the current motion of travel.
ter( x + diry, y + dirx, 0 ).has(road.c_str()) ||
ter( x + diry, y + dirx, 0 ).has(crossroad.c_str()) ||
ter( x - diry, y - dirx, 0 ).has(road.c_str()) ||
ter( x - diry, y - dirx, 0 ).has(crossroad.c_str())) {
ter(x, y, 0).add("road_null");
c = -1;
}
put_buildings(x, y, dir, town);
// Look to each side, and branch if the way is clear.
if (c < croad - 1 && c >= 2 && ( ter(x + diry, y + dirx, 0).equals(settings.default_oter) &&
ter(x - diry, y - dirx, 0).equals(settings.default_oter) ) ) {
croad = c;
make_road(x, y, cs - rng(1, 3), (dir + 1) % 4, town);
make_road(x, y, cs - rng(1, 3), (dir + 3) % 4, town);
}
}
// Now we're done growing, if there's a road ahead, add one more road segment to meet it.
if (has_road(x + (2 * dirx) , y + (2 * diry), 0)) {
ter(x + dirx, y + diry, 0).add("road_ns");
}
// If we're big, make a right turn at the edge of town.
// Seems to make little neighborhoods.
cs -= rng(1, 3);
if (cs >= 2 && c == 0) {
int dir2;
if (dir % 2 == 0) {
dir2 = rng(0, 1) * 2 + 1;
} else {
dir2 = rng(0, 1) * 2;
}
make_road(x, y, cs, dir2, town);
if (one_in(5)) {
make_road(x, y, cs, (dir2 + 2) % 4, town);
}
}
}
bool overmap::build_lab(int x, int y, int z, int s)
{
std::vector<point> generated_lab;
ter(x, y, z).add("lab");
for (int n = 0; n <= 1; n++) { // Do it in two passes to allow diagonals
for (int i = 1; i <= s; i++) {
for (int lx = x - i; lx <= x + i; lx++) {
for (int ly = y - i; ly <= y + i; ly++) {
if ((ter(lx - 1, ly, z).has("lab") ||
ter(lx + 1, ly, z).has("lab") ||
ter(lx, ly - 1, z).has("lab") ||
ter(lx, ly + 1, z).has("lab")) && one_in(i)) {
ter(lx, ly, z).add("lab");
generated_lab.push_back(point(lx, ly));
}
}
}
}
}
bool generate_stairs = true;
for( auto &elem : generated_lab ) {
if( ter( elem.x, elem.y, z + 1 ).has("lab_stairs") ) {
generate_stairs = false;
}
}
if (generate_stairs && !generated_lab.empty()) {
int v = rng(0, generated_lab.size() - 1);
point p = generated_lab[v];
ter(p.x, p.y, z + 1).add("lab_stairs");
}
ter(x, y, z).add("lab_core");
int numstairs = 0;
if (s > 0) { // Build stairs going down
while (!one_in(6)) {
int stairx, stairy;
int tries = 0;
do {
stairx = rng(x - s, x + s);
stairy = rng(y - s, y + s);
tries++;
} while (!ter(stairx, stairy, z).has("lab") && tries < 15);
if (tries < 15) {
ter(stairx, stairy, z).add("lab_stairs");
numstairs++;
}
}
}
if (numstairs == 0) { // This is the bottom of the lab; We need a finale
int finalex, finaley;
int tries = 0;
do {
finalex = rng(x - s, x + s);
finaley = rng(y - s, y + s);
tries++;
} while (tries < 15 && !ter(finalex, finaley, z).has("lab")
&& !ter(finalex, finaley, z).has("lab_core"));
ter(finalex, finaley, z).add("lab_finale");
}
return numstairs > 0;
}
bool overmap::build_ice_lab(int x, int y, int z, int s)
{
std::vector<point> generated_ice_lab;
ter(x, y, z).add("ice_lab");
for (int n = 0; n <= 1; n++) { // Do it in two passes to allow diagonals
for (int i = 1; i <= s; i++) {
for (int lx = x - i; lx <= x + i; lx++) {
for (int ly = y - i; ly <= y + i; ly++) {
if ((ter(lx - 1, ly, z).has("ice_lab") ||
ter(lx + 1, ly, z).has("ice_lab") ||
ter(lx, ly - 1, z).has("ice_lab") ||
ter(lx, ly + 1, z).has("ice_lab")) && one_in(i)) {
ter(lx, ly, z).add("ice_lab");
generated_ice_lab.push_back(point(lx, ly));
}
}
}
}
}
bool generate_stairs = true;
for( auto &elem : generated_ice_lab ) {
if( ter( elem.x, elem.y, z + 1 ).has("ice_lab_stairs") ) {
generate_stairs = false;
}
}
if (generate_stairs && !generated_ice_lab.empty()) {
int v = rng(0, generated_ice_lab.size() - 1);
point p = generated_ice_lab[v];
ter(p.x, p.y, z + 1).add("ice_lab_stairs");
}
ter(x, y, z).add("ice_lab_core");
int numstairs = 0;
if (s > 0) { // Build stairs going down
while (!one_in(6)) {
int stairx, stairy;
int tries = 0;
do {
stairx = rng(x - s, x + s);
stairy = rng(y - s, y + s);
tries++;
} while (!ter(stairx, stairy, z).has("ice_lab") && tries < 15);
if (tries < 15) {
ter(stairx, stairy, z).add("ice_lab_stairs");
numstairs++;
}
}
}
if (numstairs == 0) { // This is the bottom of the ice_lab; We need a finale
int finalex, finaley;
int tries = 0;
do {
finalex = rng(x - s, x + s);
finaley = rng(y - s, y + s);
tries++;
} while (tries < 15 && !ter(finalex, finaley, z).has("ice_lab")
&& !ter(finalex, finaley, z).has("ice_lab_core"));
ter(finalex, finaley, z).add("ice_lab_finale");
}
return numstairs > 0;
}
void overmap::build_anthill(int x, int y, int z, int s)
{
build_tunnel(x, y, z, s - rng(0, 3), 0);
build_tunnel(x, y, z, s - rng(0, 3), 1);
build_tunnel(x, y, z, s - rng(0, 3), 2);
build_tunnel(x, y, z, s - rng(0, 3), 3);
std::vector<point> queenpoints;
for (int i = x - s; i <= x + s; i++) {
for (int j = y - s; j <= y + s; j++) {
if (check_ot_type("ants", i, j, z)) {
queenpoints.push_back(point(i, j));
}
}
}
int index = rng(0, queenpoints.size() - 1);
ter(queenpoints[index].x, queenpoints[index].y, z).add("ants_queen");
}
void overmap::build_tunnel(int x, int y, int z, int s, int dir)
{
if (s <= 0) {
return;
}
if (!check_ot_type("ants", x, y, z)) {
ter(x, y, z).add("ants_ns");
}
point next;
switch (dir) {
case 0:
next = point(x , y - 1);
case 1:
next = point(x + 1, y );
case 2:
next = point(x , y + 1);
case 3:
next = point(x - 1, y );
}
if (s == 1) {
next = point(-1, -1);
}
std::vector<point> valid;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (!check_ot_type("ants", i, j, z) && abs(i - x) + abs(j - y) == 1) {
valid.push_back(point(i, j));
}
}
}
for (auto &i : valid) {
if (i.x != next.x || i.y != next.y) {
if (one_in(s * 2)) {
if (one_in(2)) {
ter(i.x, i.y, z).add("ants_food");
} else {
ter(i.x, i.y, z).add("ants_larvae");
}
} else if (one_in(5)) {
int dir2 = 0;
if (i.y == y - 1) {
dir2 = 0;
}
if (i.x == x + 1) {
dir2 = 1;
}
if (i.y == y + 1) {
dir2 = 2;
}
if (i.x == x - 1) {
dir2 = 3;
}
build_tunnel(i.x, i.y, z, s - rng(0, 3), dir2);
}
}
}
build_tunnel(next.x, next.y, z, s - 1, dir);
}
bool overmap::build_slimepit(int x, int y, int z, int s)
{
bool requires_sub = false;
for (int n = 1; n <= s; n++) {
for (int i = x - n; i <= x + n; i++) {
for (int j = y - n; j <= y + n; j++) {
if (rng(1, s * 2) >= n) {
if (one_in(8) && z > -OVERMAP_DEPTH) {
ter(i, j, z).add("slimepit_down");
requires_sub = true;
} else {
ter(i, j, z).add("slimepit");
}
}
}
}
}
return requires_sub;
}
void overmap::build_mine(int x, int y, int z, int s)
{
bool finale = (s <= rng(1, 3));
int built = 0;
if (s < 2) {
s = 2;
}
while (built < s) {
ter(x, y, z).add("mine");
std::vector<point> next;
for (int i = -1; i <= 1; i += 2) {
if (ter(x, y + i, z).equals("rock")) {
next.push_back( point(x, y + i) );
}
if (ter(x + i, y, z).equals("rock")) {
next.push_back( point(x + i, y) );
}
}
if (next.empty()) { // Dead end! Go down!
ter(x, y, z).add((finale ? "mine_finale" : "mine_down"));
return;
}
point p = next[ rng(0, next.size() - 1) ];
x = p.x;
y = p.y;
built++;
}
ter(x, y, z).add((finale ? "mine_finale" : "mine_down"));
}
void overmap::place_rifts(int const z)
{
int num_rifts = rng(0, 2) * rng(0, 2);
std::vector<point> riftline;
if (!one_in(4)) {
num_rifts++;
}
for (int n = 0; n < num_rifts; n++) {
int x = rng(MAX_RIFT_SIZE, OMAPX - MAX_RIFT_SIZE);
int y = rng(MAX_RIFT_SIZE, OMAPY - MAX_RIFT_SIZE);
int xdist = rng(MIN_RIFT_SIZE, MAX_RIFT_SIZE),
ydist = rng(MIN_RIFT_SIZE, MAX_RIFT_SIZE);
// We use rng(0, 10) as the t-value for this Bresenham Line, because by
// repeating this twice, we can get a thick line, and a more interesting rift.
for (int o = 0; o < 3; o++) {
if (xdist > ydist) {
riftline = line_to(x - xdist, y - ydist + o, x + xdist, y + ydist, rng(0, 10));
} else {
riftline = line_to(x - xdist + o, y - ydist, x + xdist, y + ydist, rng(0, 10));
}
for (size_t i = 0; i < riftline.size(); i++) {
if (i == riftline.size() / 2 && !one_in(3)) {
ter(riftline[i].x, riftline[i].y, z).add("hellmouth");
} else {
ter(riftline[i].x, riftline[i].y, z).add("rift");
}
}
}
}
}
void overmap::make_hiway(int x1, int y1, int x2, int y2, int z, const std::string &base)
{
if (x1 == x2 && y1 == y2) {
return;
}
std::priority_queue<node, std::deque<node> > nodes[2];
bool closed[OMAPX][OMAPY] = {{false}};
int open[OMAPX][OMAPY] = {{0}};
int dirs[OMAPX][OMAPY] = {{0}};
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
int i = 0;
int disp = (base == "road") ? 5 : 2;
nodes[i].push(node(x1, y1, 5, 1000));
open[x1][y1] = 1000;
// use A* to find the shortest path from (x1,y1) to (x2,y2)
while (!nodes[i].empty()) {
// get the best-looking node
node mn = nodes[i].top();
nodes[i].pop();
// make sure it's in bounds
if (mn.x >= OMAPX || mn.x < 0 || mn.y >= OMAPY || mn.y < 0) {
continue;
}
// mark it visited
closed[mn.x][mn.y] = true;
// if we've reached the end, draw the path and return
if (mn.x == x2 && mn.y == y2) {
int x = mn.x;
int y = mn.y;
while (x != x1 || y != y1) {
int d = dirs[x][y];
x += dx[d];
y += dy[d];
if (road_allowed(ter(x, y, z))) {
if (has_river(ter(x, y, z))) {
if (d == 1 || d == 3) {
ter(x, y, z).add("bridge_ns");
} else {
ter(x, y, z).add("bridge_ew");
}
} else {
ter(x, y, z).add(base + "_nesw");
}
}
}
return;
}
// otherwise, expand to
for(int d = 0; d < 4; d++) {
int x = mn.x + dx[d];
int y = mn.y + dy[d];
// don't allow:
// * out of bounds
// * already traversed tiles
// * tiles that don't allow roads to cross them (e.g. buildings)
// * corners on rivers
if (x < 1 || x > OMAPX - 2 || y < 1 || y > OMAPY - 2 ||
closed[x][y] || !road_allowed(ter(x, y, z)) ||
(has_river(ter(mn.x, mn.y, z)) && mn.d != d) ||
(has_river(ter(x, y, z)) && mn.d != d) ) {
continue;
}
node cn = node(x, y, d, 0);
// distance to target
cn.p += ((abs(x2 - x) + abs(y2 - y)) / disp);
// prefer existing roads.
cn.p += check_ot_type(base, x, y, z) ? 0 : 3;
// and flat land over bridges
cn.p += !has_river(ter(x, y, z)) ? 0 : 2;
// try not to turn too much
//cn.p += (mn.d == d) ? 0 : 1;
// record direction to shortest path
if (open[x][y] == 0) {
dirs[x][y] = (d + 2) % 4;
open[x][y] = cn.p;
nodes[i].push(cn);
} else if (open[x][y] > cn.p) {
dirs[x][y] = (d + 2) % 4;
open[x][y] = cn.p;
// wizardry
while (nodes[i].top().x != x || nodes[i].top().y != y) {
nodes[1 - i].push(nodes[i].top());
nodes[i].pop();
}
nodes[i].pop();
if (nodes[i].size() > nodes[1 - i].size()) {
i = 1 - i;
}
while (!nodes[i].empty()) {
nodes[1 - i].push(nodes[i].top());
nodes[i].pop();
}
i = 1 - i;
nodes[i].push(cn);
} else {
// a shorter path has already been found
}
}
}
}
void overmap::building_on_hiway(int x, int y, int dir)
{
int xdif = dir * (1 - 2 * rng(0, 1));
int ydif = (1 - dir) * (1 - 2 * rng(0, 1));
int rot = 0;
if (ydif == 1) {
rot = 0;
} else if (xdif == -1) {
rot = 1;
} else if (ydif == -1) {
rot = 2;
} else if (xdif == 1) {
rot = 3;
}
switch (rng(1, 4)) {
case 1:
if (!has_river(ter(x + xdif, y + ydif, 0))) {
ter(x + xdif, y + ydif, 0).add("lab_stairs");
}
break;
case 2:
if (!has_river(ter(x + xdif, y + ydif, 0))) {
ter(x + xdif, y + ydif, 0).add("ice_lab_stairs");
}
break;
case 3:
if (!has_river(ter(x + xdif, y + ydif, 0))) {
ter(x + xdif, y + ydif, 0).add(house(rot, settings.house_basement_chance));
}
break;
case 4:
if (!has_river(ter(x + xdif, y + ydif, 0))) {
ter(x + xdif, y + ydif, 0).add("radio_tower");
}
break;
}
}
void overmap::place_hiways(std::vector<city> cities, int z, const std::string &base)
{
if (cities.size() == 1) {
return;
}
city best;
for (size_t i = 0; i < cities.size(); i++) {
int closest = -1;
for (size_t j = i + 1; j < cities.size(); j++) {
int distance = trig_dist(cities[i].x, cities[i].y, cities[j].x, cities[j].y);
if (distance < closest || closest < 0) {
closest = distance;
best = cities[j];
}
}
if( closest > 0 ) {
make_hiway(cities[i].x, cities[i].y, best.x, best.y, z, base);
}
}
}
// Polish does both good_roads and good_rivers (and any future polishing) in
// a single loop; much more efficient
void overmap::polish(const int z, const std::string &terrain_type)
{
const bool check_all = (terrain_type == "all");
// Main loop--checks roads and rivers that aren't on the borders of the map
for (int x = 0; x < OMAPX; x++) {
for (int y = 0; y < OMAPY; y++) {
if (check_all || check_ot_type(terrain_type, x, y, z)) {
if (check_ot_type("bridge", x, y, z) &&
check_ot_type("bridge", x - 1, y, z) &&
check_ot_type("bridge", x + 1, y, z) &&
check_ot_type("bridge", x, y - 1, z) &&
check_ot_type("bridge", x, y + 1, z)) {
ter(x, y, z).add("road_nesw");
} else if (check_ot_type("subway", x, y, z)) {
good_road("subway", x, y, z);
} else if (check_ot_type("sewer", x, y, z)) {
good_road("sewer", x, y, z);
} else if (check_ot_type("ants", x, y, z)
&& !check_ot_type("ants_queen", x, y, z)
&& !check_ot_type("ants_larvae", x, y, z)
&& !check_ot_type("ants_food", x, y, z)) {
good_road("ants", x, y, z);
} else if (check_ot_type("river", x, y, z)) {
good_river(x, y, z);
// Sometimes a bridge will start at the edge of a river,
// and this looks ugly.
// So, fix it by making that square normal road;
// also taking other road pieces that may be next
// to it into account. A bit of a kludge but it works.
} else if (ter(x, y, z).has("bridge_ns") &&
(!has_river(ter(x - 1, y, z)) ||
!has_river(ter(x + 1, y, z)))) {
good_road("road", x, y, z);
} else if (ter(x, y, z).has("bridge_ew") &&
(!has_river(ter(x, y - 1, z)) ||
!has_river(ter(x, y + 1, z)))) {
good_road("road", x, y, z);
} else if (check_ot_type("road", x, y, z)) {
good_road("road", x, y, z);
}
}
}
}
// Fixes stretches of parallel roads--turns them into two-lane highways
// Note that this fixes 2x2 areas...
// a "tail" of 1x2 parallel roads may be left.
// This can actually be a good thing; it ensures nice connections
// Also, this leaves, say, 3x3 areas of road.
// TODO: fix this? courtyards etc?
for (int y = 0; y < OMAPY - 1; y++) {
for (int x = 0; x < OMAPX - 1; x++) {
if (check_ot_type(terrain_type, x, y, z)) {
if (ter(x, y, z).has("road_nes")
&& ter(x + 1, y, z).has("road_nsw")
&& ter(x, y + 1, z).has("road_nes")
&& ter(x + 1, y + 1, z).has("road_nsw")) {
ter(x, y, z).add("hiway_ns");
ter(x + 1, y, z).add("hiway_ns");
ter(x, y + 1, z).add("hiway_ns");
ter(x + 1, y + 1, z).add("hiway_ns");
} else if (ter(x, y, z).has("road_esw")
&& ter(x + 1, y, z).has("road_esw")
&& ter(x, y + 1, z).has("road_new")
&& ter(x + 1, y + 1, z).has("road_new")) {
ter(x, y, z).add("hiway_ew");
ter(x + 1, y, z).add("hiway_ew");
ter(x, y + 1, z).add("hiway_ew");
ter(x + 1, y + 1, z).add("hiway_ew");
}
}
}
}
}
bool overmap::check_ot_type(const std::string &otype, int x, int y, int z) const
{
const complex_map_tile tile = get_ter(x, y, z);
return is_ot_type(otype, tile);
}
bool overmap::check_ot_type_road(const std::string &otype, int x, int y, int z)
{
const complex_map_tile oter = ter(x, y, z);
if(otype == "road" || otype == "bridge" || otype == "hiway") {
if(is_ot_type("road", oter) || is_ot_type ("bridge", oter) || is_ot_type("hiway", oter)) {
return true;
} else {
return false;
}
}
return is_ot_type(otype, oter);
}
bool overmap::has_road(int x, int y, int z)
{
if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY) {
for (auto &it : roads_out) {
if (abs(it.x - x) + abs(it.y - y) <= 1) {
return true;
}
}
}
return ter(x, y, z).has_flag(is_road);
//oter_t(ter(x, y, z)).is_road;
}
bool overmap::is_road_or_highway(int x, int y, int z)
{
if (has_road(x, y, z) || check_ot_type("hiway", x, y, z)) {
return true;
}
return false;
}
void overmap::good_road(const std::string &base, int x, int y, int z)
{
if (check_ot_type_road(base, x, y - 1, z)) {
if (check_ot_type_road(base, x + 1, y, z)) {
if (check_ot_type_road(base, x, y + 1, z)) {
if (check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_nesw");
} else {
ter(x, y, z).add(base + "_nes");
}
} else {
if (check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_new");
} else {
ter(x, y, z).add(base + "_ne");
}
}
} else {
if (check_ot_type_road(base, x, y + 1, z)) {
if (check_ot_type(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_nsw");
} else {
ter(x, y, z).add(base + "_ns");
}
} else {
if (check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_wn");
} else {
if(base == "road" && (y != OMAPY - 1)) {
ter(x, y, z).add(base + "_end_south");
} else {
ter(x, y, z).add(base + "_ns");
}
}
}
}
} else {
if (check_ot_type_road(base, x + 1, y, z)) {
if (check_ot_type_road(base, x, y + 1, z)) {
if (check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_esw");
} else {
ter(x, y, z).add(base + "_es");
}
} else {
if( check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_ew");
} else {
if(base == "road" && (x != 0)) {
ter(x, y, z).add(base + "_end_west");
} else {
ter(x, y, z).add(base + "_ew");
}
}
}
} else {
if (check_ot_type_road(base, x, y + 1, z)) {
if (check_ot_type_road(base, x - 1, y, z)) {
ter(x, y, z).add(base + "_sw");
} else {
if(base == "road" && (y != 0)) {
ter(x, y, z).add(base + "_end_north");
} else {
ter(x, y, z).add(base + "_ns");
}
}
} else {
if (check_ot_type_road(base, x - 1, y, z)) {
if(base == "road" && (x != OMAPX-1)) {
ter(x, y, z).add(base + "_end_east");
} else {
ter(x, y, z).add(base + "_ew");
}
} else {
// No adjoining roads/etc.
// Happens occasionally, esp. with sewers.
ter(x, y, z).add(base + "_nesw");
}
}
}
}
if (ter(x, y, z).has("road_nesw") && one_in(4)) {
ter(x, y, z).add("road_nesw_manhole");
}
}
void overmap::good_river(int x, int y, int z)
{
if((x == 0) || (x == OMAPX-1)) {
if(!has_river(ter(x, y - 1, z))) {
ter(x, y, z).set("river_north");
} else if(!has_river(ter(x, y + 1, z))) {
ter(x, y, z).set("river_south");
} else {
ter(x, y, z).set("river_center");
}
return;
}
if((y == 0) || (y == OMAPY-1)) {
if(!has_river(ter(x - 1, y, z))) {
ter(x, y, z).set("river_west");
} else if(!has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_east");
} else {
ter(x, y, z).set("river_center");
}
return;
}
if (has_river(ter(x - 1, y, z))) {
if (has_river(ter(x, y - 1, z))) {
if (has_river(ter(x, y + 1, z))) {
if (has_river(ter(x + 1, y, z))) {
// River on N, S, E, W;
// but we might need to take a "bite" out of the corner
if (!has_river(ter(x - 1, y - 1, z))) {
ter(x, y, z).set("river_c_not_nw");
} else if (!has_river(ter(x + 1, y - 1, z))) {
ter(x, y, z).set("river_c_not_ne");
} else if (!has_river(ter(x - 1, y + 1, z))) {
ter(x, y, z).set("river_c_not_sw");
} else if (!has_river(ter(x + 1, y + 1, z))) {
ter(x, y, z).set("river_c_not_se");
} else {
ter(x, y, z).set("river_center");
}
} else {
ter(x, y, z).set("river_east");
}
} else {
if (has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_south");
} else {
ter(x, y, z).set("river_se");
}
}
} else {
if (has_river(ter(x, y + 1, z))) {
if (has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_north");
} else {
ter(x, y, z).set("river_ne");
}
} else {
if (has_river(ter(x + 1, y, z))) { // Means it's swampy
ter(x, y, z).set("forest_water");
}
}
}
} else {
if (has_river(ter(x, y - 1, z))) {
if (has_river(ter(x, y + 1, z))) {
if (has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_west");
} else { // Should never happen
ter(x, y, z).set("forest_water");
}
} else {
if (has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_sw");
} else { // Should never happen
ter(x, y, z).set("forest_water");
}
}
} else {
if (has_river(ter(x, y + 1, z))) {
if (has_river(ter(x + 1, y, z))) {
ter(x, y, z).set("river_nw");
} else { // Should never happen
ter(x, y, z).set("forest_water");
}
} else { // Should never happen
ter(x, y, z).set("forest_water");
}
}
}
}
bool overmap::allowed_terrain(tripoint p, int width, int height, std::list<std::string> allowed)
{
for(int h = 0; h < height; ++h) {
for(int w = 0; w < width; ++w) {
for( auto &elem : allowed ) {
complex_map_tile oter = this->ter(p.x + w, p.y + h, p.z);
if( !is_ot_type( elem, oter ) ) {
return false;
}
}
}
}
return true;
}
// checks the area around the selected point to ensure terrain is valid for special
bool overmap::allowed_terrain(tripoint p, std::list<tripoint> tocheck,
std::list<std::string> allowed, std::list<std::string> disallowed)
{
for( auto t : tocheck ) {
bool passed = false;
for( auto &elem : allowed ) {
complex_map_tile oter = this->ter(p.x + t.x, p.y + t.y, p.z);
if( is_ot_type( elem, oter ) ) {
passed = true;
}
}
// if we are only checking against disallowed types, we don't want this to fail us
if(!passed && allowed.size() > 0) {
return false;
}
for( auto &elem : disallowed ) {
complex_map_tile oter = this->ter(p.x + t.x, p.y + t.y, p.z);
if( is_ot_type( elem, oter ) ) {
return false;
}
}
}
return true;
}
// new x = (x-c.x)*cos() - (y-c.y)*sin() + c.x
// new y = (x-c.x)*sin() + (y-c.y)*cos() + c.y
// r1x = 0*x - 1*y = -1*y, r1y = 1*x + y*0 = x
// r2x = -1*x - 0*y = -1*x , r2y = x*0 + y*-1 = -1*y
// r3x = x*0 - (-1*y) = y, r3y = x*-1 + y*0 = -1*x
// c=0,0, rot90 = (-y, x); rot180 = (-x, y); rot270 = (y, -x)
/*
(0,0)(1,0)(2,0) 90 (0,0)(0,1)(0,2) (-2,0)(-1,0)(0,0)
(0,1)(1,1)(2,1) -> (-1,0)(-1,1)(-1,2) -> (-2,1)(-1,1)(0,1)
(0,2)(1,2)(2,2) (-2,0)(-2,1)(-2,2) (-2,2)(-1,2)(0,2)
*/
inline tripoint rotate_tripoint(tripoint p, int rotations)
{
if(rotations == 1) {
return tripoint(-1 * p.y, p.x, p.z);
} else if(rotations == 2) {
return tripoint(-1 * p.x, -1 * p.y, p.z);
} else if(rotations == 3) {
return tripoint(p.y, -1 * p.x, p.z);
}
return p;
}
// checks around the selected point to see if the special can be placed there
bool overmap::allow_special(tripoint p, overmap_special special, int &rotate)
{
// check if rotation is allowed, and if necessary
rotate = 0;
// check to see if road is nearby, if so, rotate to face road
// if no road && special requires road, return false
// if no road && special does not require it, pick a random rotation
if(special.rotatable) {
// if necessary:
if(check_ot_type("road", p.x + 1, p.y, p.z)) {
// road to right
rotate = 1;
} else if(check_ot_type("road", p.x - 1, p.y, p.z)) {
// road to left
rotate = 3;
} else if(check_ot_type("road", p.x, p.y + 1, p.z)) {
// road to south
rotate = 2;
} else if(check_ot_type("road", p.x, p.y - 1, p.z)) {
// road to north
} else {
if(std::find(special.locations.begin(), special.locations.end(),
"by_hiway") != special.locations.end()) {
return false;
} else {
rotate = rng(0, 3);
}
}
}
// do bounds & connection checking
std::list<tripoint> rotated_points;
for( auto t : special.terrains ) {
tripoint rotated_point = rotate_tripoint(t.p, rotate);
rotated_points.push_back(rotated_point);
tripoint testpoint = tripoint(rotated_point.x + p.x, rotated_point.y + p.y, p.z);
if((testpoint.x >= OMAPX - 1) ||
(testpoint.x < 0) || (testpoint.y < 0) ||
(testpoint.y >= OMAPY - 1)) {
return false;
}
if(t.connect == "road")
{
switch(rotate){
case 0:
testpoint = tripoint(testpoint.x, testpoint.y - 1, testpoint.z);
break;
case 1:
testpoint = tripoint(testpoint.x + 1, testpoint.y, testpoint.z);
break;
case 2:
testpoint = tripoint(testpoint.x, testpoint.y + 1, testpoint.z);
break;
case 3:
testpoint = tripoint(testpoint.x - 1, testpoint.y, testpoint.z);
break;
default:
break;
}
if(!road_allowed(get_ter(testpoint.x, testpoint.y, testpoint.z)))
{
return false;
}
}
}
// then do city range checking
point citypt = point(p.x, p.y);
if(!(special.min_city_distance == -1 || dist_from_city(citypt) >= special.min_city_distance) ||
!(special.max_city_distance == -1 || dist_from_city(citypt) <= special.max_city_distance)) {
return false;
}
// then check location flags
bool passed = false;
for( auto location : special.locations ) {
// check each location, if one returns true, then return true, else return false
// never, always, water, land, forest, wilderness, by_hiway
// false, true, river, !river, forest, forest/field, special
std::list<std::string> allowed_terrains;
std::list<std::string> disallowed_terrains;
if(location == "never") {
return false;
} else if(location == "always") {
return true;
} else if(location == "water") {
allowed_terrains.push_back("river");
} else if(location == "land") {
disallowed_terrains.push_back("river");
disallowed_terrains.push_back("road");
} else if(location == "forest") {
allowed_terrains.push_back("forest");
} else if(location == "wilderness") {
allowed_terrains.push_back("forest");
allowed_terrains.push_back("field");
} else if(location == "by_hiway") {
disallowed_terrains.push_back("road");
}
passed = allowed_terrain(p, rotated_points, allowed_terrains, disallowed_terrains);
if(passed) {
return true;
}
}
return false;
}
// should work essentially the same as previously
// split map into sections, iterate through sections
// iterate through specials, check if special is valid
// pick & place special
void overmap::place_specials()
{
std::map<overmap_special, int> num_placed;
for( auto &overmap_specials_it : overmap_specials ) {
overmap_special special = overmap_specials_it;
if (special.max_occurrences != 100){
num_placed.insert( std::pair<overmap_special, int>( overmap_specials_it,
0 ) ); // normal circumstances
}
else{
if (rand() % 100 <= special.min_occurrences){ //occurance is actually a % chance, so less than 1
num_placed.insert( std::pair<overmap_special, int>(
overmap_specials_it, -1 ) ); // Priority add one in this map
}
else
num_placed.insert( std::pair<overmap_special, int>(
overmap_specials_it, 999 ) ); // Don't add one in this map
}
}
std::vector<point> sectors;
for (int x = 0; x < OMAPX; x += OMSPEC_FREQ) {
for (int y = 0; y < OMAPY; y += OMSPEC_FREQ) {
sectors.push_back(point(x, y));
}
}
while(!sectors.empty()) {
int pick = rng(0, sectors.size() - 1);
int x = sectors.at(pick).x;
int y = sectors.at(pick).y;
sectors.erase(sectors.begin() + pick);
//std::vector<overmap_special> valid_specials;
// second parameter is rotation
std::map<overmap_special, int> valid_specials;
int tries = 0;
tripoint p;
int rotation = 0;
do {
p = tripoint(rng(x, x + OMSPEC_FREQ - 1), rng(y, y + OMSPEC_FREQ - 1), 0);
// dont need to check for edges yet
for( auto special : overmap_specials ) {
std::list<std::string> allowed_terrains;
allowed_terrains.push_back("forest");
if (ACTIVE_WORLD_OPTIONS["CLASSIC_ZOMBIES"] && (special.flags.count("CLASSIC") < 1)) {
continue;
}
if ((num_placed[special] < special.max_occurrences || special.max_occurrences <= 0) &&
allow_special(p, special, rotation)) {
valid_specials[special] = rotation;
}
}
++tries;
} while(valid_specials.empty() && tries < 20);
// selection & placement happens here
std::pair<overmap_special, int> place;
if(!valid_specials.empty()) {
// Place the MUST HAVE ones first, to try and guarantee that they appear
//std::vector<overmap_special> must_place;
std::map<overmap_special, int> must_place;
for( auto &valid_special : valid_specials ) {
place = valid_special;
if(num_placed[place.first] < place.first.min_occurrences) {
must_place.insert(place);
}
}
if (must_place.empty()) {
int selection = rng(0, valid_specials.size() - 1);
//overmap_special special = valid_specials.at(valid_specials.begin() + selection).first;
std::map<overmap_special, int>::iterator it = valid_specials.begin();
std::advance(it, selection);
place = *it;
overmap_special special = place.first;
if (num_placed[special] == -1)
num_placed[special] = 999;//if you build one, never build another. For [x:100] spawn % chance
num_placed[special]++;
place_special(special, p, place.second);
} else {
int selection = rng(0, must_place.size() - 1);
//overmap_special special = must_place.at(must_place.begin() + selection).first;
std::map<overmap_special, int>::iterator it = must_place.begin();
std::advance(it, selection);
place = *it;
overmap_special special = place.first;
if (num_placed[special] == -1)
num_placed[special] = 999;//if you build one, never build another. For [x:100] spawn % chance
num_placed[special]++;
place_special(special, p, place.second);
}
}
}
}
// does the actual placement. should do rotation, but rotation validity should be checked before
// c = center point about rotation
// new x = (x-c.x)*cos() - (y-c.y)*sin() + c.x
// new y = (x-c.x)*sin() + (y-c.y)*cos() + c.y
// c=0,0, rot90 = (-y, x); rot180 = (-x, y); rot270 = (y, -x)
/*
(0,0)(1,0)(2,0) 90 (0,0)(0,1)(0,2) (-2,0)(-1,0)(0,0)
(0,1)(1,1)(2,1) -> (-1,0)(-1,1)(-1,2) -> (-2,1)(-1,1)(0,1)
(0,2)(1,2)(2,2) (-2,0)(-2,1)(-2,2) (-2,2)(-1,2)(0,2)
*/
void overmap::place_special(overmap_special special, tripoint p, int rotation)
{
//std::map<std::string, tripoint> connections;
std::vector<std::pair<std::string, tripoint> > connections;
for(std::list<overmap_special_terrain>::iterator it = special.terrains.begin();
it != special.terrains.end(); ++it) {
overmap_special_terrain terrain = *it;
oter_id id = (oter_id) terrain.terrain;
oter_t t = (oter_t) id;
tripoint rp = rotate_tripoint(terrain.p, rotation);
tripoint location = tripoint(p.x + rp.x, p.y + rp.y, p.z + rp.z);
if(!t.rotates) {
this->ter(location.x, location.y, location.z).add(terrain.terrain);
} else {
this->ter(location.x, location.y, location.z).add(rotate(terrain.terrain, rotation));
}
if(terrain.connect.size() > 0) {
//connections[terrain.connect] = location;
std::pair<std::string, tripoint> connection;
connection.first = terrain.connect;
connection.second = location;
connections.push_back(connection);
}
if(special.flags.count("BLOB") > 0) {
for (int x = -2; x <= 2; x++) {
for (int y = -2; y <= 2; y++) {
if (one_in(1 + abs(x) + abs(y))) {
ter(location.x + x, location.y + y, location.z).add(terrain.terrain);
}
}
}
}
}
for( auto connection : connections ) {
if(connection.first == "road") {
city closest;
int distance = 999;
for( auto c : cities ) {
int dist = rl_dist(connection.second.x, connection.second.y, c.x, c.y);
if (dist < distance) {
closest = c;
distance = dist;
}
}
// generally entrances come out of the top,
// so we want to rotate the road connection with the point.
tripoint conn = connection.second;
switch(rotation)
{
case 0:
conn.y = conn.y - 1;
break;
case 1:
conn.x = conn.x + 1;
break;
case 2:
conn.y = conn.y + 1;
break;
case 3:
conn.x = conn.x - 1;
break;
default:
break;
}
if(ter(conn.x, conn.y, p.z).visible().t().has_flag(allow_road)) {
make_hiway(conn.x, conn.y, closest.x, closest.y, p.z, "road");
} else { // in case the entrance does not come out the top, try wherever possible...
conn = connection.second;
make_hiway(conn.x, conn.y, closest.x, closest.y, p.z, "road");
}
}
}
// place spawns
if(special.spawns.group != "GROUP_NULL") {
overmap_special_spawns spawns = special.spawns;
int pop = rng(spawns.min_population, spawns.max_population);
int rad = rng(spawns.min_radius, spawns.max_radius);
add_mon_group(mongroup(spawns.group, p.x * 2, p.y * 2, p.z, rad, pop));
}
}
oter_id overmap::rotate(const oter_id &oter, int dir)
{
const oter_t &otert = oter;
if (! otert.rotates && dir != 0) {
debugmsg("%s does not rotate.", oter.c_str());
return oter;
}
if (dir < 0) {
dir += 4;
} else if (dir > 3) {
debugmsg("Bad rotation for %s: %d.", oter.c_str(), dir);
return oter;
}
return otert.directional_peers[dir];
}
void overmap::place_mongroups()
{
// Cities are full of zombies
for( auto &elem : cities ) {
if( ACTIVE_WORLD_OPTIONS["WANDER_SPAWNS"] ) {
if( !one_in( 16 ) || elem.s > 5 ) {
mongroup m( "GROUP_ZOMBIE", ( elem.x * 2 ), ( elem.y * 2 ), 0, int( elem.s * 2.5 ),
elem.s * 80 );
// m.set_target( zg.back().posx, zg.back().posy );
m.horde = true;
m.wander();
add_mon_group( m );
}
}
if( !ACTIVE_WORLD_OPTIONS["STATIC_SPAWN"] ) {
add_mon_group( mongroup( "GROUP_ZOMBIE", ( elem.x * 2 ), ( elem.y * 2 ), 0,
int( elem.s * 2.5 ), elem.s * 80 ) );
}
}
if (!ACTIVE_WORLD_OPTIONS["CLASSIC_ZOMBIES"]) {
// Figure out where swamps are, and place swamp monsters
for (int x = 3; x < OMAPX - 3; x += 7) {
for (int y = 3; y < OMAPY - 3; y += 7) {
int swamp_count = 0;
for (int sx = x - 3; sx <= x + 3; sx++) {
for (int sy = y - 3; sy <= y + 3; sy++) {
if (ter(sx, sy, 0).has("forest_water")) {
swamp_count += 2;
}
}
}
if (swamp_count >= 25)
add_mon_group(mongroup("GROUP_SWAMP", x * 2, y * 2, 0, 3,
rng(swamp_count * 8, swamp_count * 25)));
}
}
}
if (!ACTIVE_WORLD_OPTIONS["CLASSIC_ZOMBIES"]) {
// Figure out where rivers are, and place swamp monsters
for (int x = 3; x < OMAPX - 3; x += 7) {
for (int y = 3; y < OMAPY - 3; y += 7) {
int river_count = 0;
for (int sx = x - 3; sx <= x + 3; sx++) {
for (int sy = y - 3; sy <= y + 3; sy++) {
if (has_river(ter(sx, sy, 0))) {
river_count++;
}
}
}
if (river_count >= 25)
add_mon_group(mongroup("GROUP_RIVER", x * 2, y * 2, 0, 3,
rng(river_count * 8, river_count * 25)));
}
}
}
if (!ACTIVE_WORLD_OPTIONS["CLASSIC_ZOMBIES"]) {
// Place the "put me anywhere" groups
int numgroups = rng(0, 3);
for (int i = 0; i < numgroups; i++) {
add_mon_group(mongroup("GROUP_WORM", rng(0, OMAPX * 2 - 1), rng(0, OMAPY * 2 - 1), 0,
rng(20, 40), rng(30, 50)));
}
}
}
int overmap::get_top_border()
{
return loc.y * OMAPY;
}
int overmap::get_left_border()
{
return loc.x * OMAPX;
}
int overmap::get_bottom_border()
{
return get_top_border() + OMAPY;
}
int overmap::get_right_border()
{
return get_left_border() + OMAPX;
}
void overmap::place_radios()
{
std::string message;
for (int i = 0; i < OMAPX; i++) {
for (int j = 0; j < OMAPY; j++) {
if (ter(i, j, 0).has("radio_tower")) {
int choice = rng(0, 2);
switch(choice) {
case 0:
message = string_format(_("This is emergency broadcast station %d%d.\
Please proceed quickly and calmly to your designated evacuation point."), i, j);
radios.push_back(radio_tower(i * 2, j * 2, rng(RADIO_MIN_STRENGTH, RADIO_MAX_STRENGTH), message));
break;
case 1:
radios.push_back(radio_tower(i * 2, j * 2, rng(RADIO_MIN_STRENGTH, RADIO_MAX_STRENGTH),
_("Head West. All survivors, head West. Help is waiting.")));
break;
case 2:
radios.push_back(radio_tower(i * 2, j * 2, rng(RADIO_MIN_STRENGTH, RADIO_MAX_STRENGTH), "",
WEATHER_RADIO));
break;
}
} else if (ter(i, j, 0).has("lmoe")) {
message = string_format(_("This is automated emergency shelter beacon %d%d.\
Supplies, amenities and shelter are stocked."), i, j);
radios.push_back(radio_tower(i * 2, j * 2, rng(RADIO_MIN_STRENGTH, RADIO_MAX_STRENGTH) / 2,
message));
} else if (ter(i, j, 0).has("fema_entrance")) {
message = string_format(_("This is FEMA camp %d%d.\
Supplies are limited, please bring supplemental food, water, and bedding.\
This is FEMA camp %d%d. A designated long-term emergency shelter."), i, j, i, j);
radios.push_back(radio_tower(i * 2, j * 2, rng(RADIO_MIN_STRENGTH, RADIO_MAX_STRENGTH), message));
}
}
}
}
void overmap::open()
{
std::string const plrfilename = overmapbuffer::player_filename(loc.x, loc.y);
std::string const terfilename = overmapbuffer::terrain_filename(loc.x, loc.y);
std::ifstream fin;
fin.open(terfilename.c_str());
if (fin.is_open()) {
unserialize(fin, plrfilename, terfilename);
fin.close();
} else { // No map exists! Prepare neighbors, and generate one.
std::vector<const overmap*> pointers;
// Fetch south and north
for (int i = -1; i <= 1; i += 2) {
pointers.push_back(overmap_buffer.get_existing(loc.x, loc.y+i));
}
// Fetch east and west
for (int i = -1; i <= 1; i += 2) {
pointers.push_back(overmap_buffer.get_existing(loc.x+i, loc.y));
}
// pointers looks like (north, south, west, east)
generate(pointers[0], pointers[3], pointers[1], pointers[2]);
}
}
////////////////
oter_iid ot_null,
ot_crater,
ot_field,
ot_forest,
ot_forest_thick,
ot_forest_water,
ot_river_center;
oter_iid oterfind(const std::string id)
{
if( otermap.find(id) == otermap.end() ) {
debugmsg("Can't find %s", id.c_str());
return 0;
}
return otermap[id].loadid;
}
void set_oter_ids() // fixme constify
{
ot_null = oterfind("");
// NOT required.
ot_crater = oterfind("crater");
ot_field = oterfind("field");
ot_forest = oterfind("forest");
ot_forest_thick = oterfind("forest_thick");
ot_forest_water = oterfind("forest_water");
ot_river_center = oterfind("river_center");
}
//////////////////////////
//// sneaky
// ter(...) = 0;
const unsigned &oter_id::operator=(const int &i)
{
_val = i;
return _val;
}
// ter(...) = "rock"
oter_id::operator std::string() const
{
if ( _val > oterlist.size() ) {
debugmsg("oterlist[%d] > %d", _val, oterlist.size()); // remove me after testing (?)
return 0;
}
return std::string(oterlist[_val].id);
}
// int index = ter(...);
oter_id::operator int() const
{
return _val;
}
// ter(...) != "foobar"
bool oter_id::operator!=(const char *v) const
{
return oterlist[_val].id.compare(v) != 0;
/* hellaciously slow string allocation frenzy -v
std::map<std::string, oter_t>::const_iterator it=otermap.find(v);
return ( it == otermap.end() || it->second.loadid != _val);
*/
}
// ter(...) == "foobar"
bool oter_id::operator==(const char *v) const
{
return t().id.compare(v) == 0;
}
bool oter_id::operator<=(const char *v) const
{
std::unordered_map<std::string, oter_t>::const_iterator it = otermap.find(v);
return ( it == otermap.end() || it->second.loadid <= _val);
}
bool oter_id::operator>=(const char *v) const
{
std::unordered_map<std::string, oter_t>::const_iterator it = otermap.find(v);
return ( it != otermap.end() && it->second.loadid >= _val);
}
// o_id1 != o_id2
bool oter_id::operator!=(const oter_id &v) const
{
return ( _val != v._val );
}
bool oter_id::operator==(const oter_id &v) const
{
return ( _val == v._val );
}
// oter_t( ter(...) ).name // WARNING
oter_id::operator oter_t() const
{
return oterlist[_val];
}
const oter_t &oter_id::t() const
{
return oterlist[_val];
}
// ter(...).size()
size_t oter_id::size() const
{
return oterlist[_val].id.size();
}
// ter(...).find("foo");
int oter_id::find(const std::string &v, const int start, const int end) const
{
(void)start;
(void)end; // TODO?
return oterlist[_val].id.find(v);//, start, end);
}
// ter(...).compare(0, 3, "foo");
int oter_id::compare(size_t pos, size_t len, const char *s, size_t n) const
{
if ( n != 0 ) {
return oterlist[_val].id.compare(pos, len, s, n);
} else {
return oterlist[_val].id.compare(pos, len, s);
}
}
// std::string("river_ne"); oter_id van_location(down_by);
oter_id::oter_id(const std::string &v)
{
std::unordered_map<std::string, oter_t>::const_iterator it = otermap.find(v);
if ( it == otermap.end() ) {
debugmsg("not found: %s", v.c_str());
} else {
_val = it->second.loadid;
}
}
// oter_id b("house_north");
oter_id::oter_id(const char *v)
{
std::unordered_map<std::string, oter_t>::const_iterator it = otermap.find(v);
if ( it == otermap.end() ) {
debugmsg("not found: %s", v);
} else {
_val = it->second.loadid;
}
}
// wprint("%s",ter(...).c_str() );
const char *oter_id::c_str() const
{
return oterlist[_val].id.c_str();
}
void groundcover_extra::setup() // fixme return bool for failure
{
default_ter = terfind( default_ter_str );
ter_furn_id tf_id;
int wtotal = 0;
int btotal = 0;
for ( std::map<std::string, double>::const_iterator it = percent_str.begin();
it != percent_str.end(); ++it ) {
tf_id.ter = t_null;
tf_id.furn = f_null;
if ( it->second < 0.0001 ) {
continue;
}
if ( termap.find( it->first ) != termap.end() ) {
tf_id.ter = termap[ it->first ].loadid;
} else if ( furnmap.find( it->first ) != furnmap.end() ) {
tf_id.furn = furnmap[ it->first ].loadid;
} else {
debugmsg("No clue what '%s' is! No such terrain or furniture", it->first.c_str() );
continue;
}
wtotal += (int)(it->second * 10000.0);
weightlist[ wtotal ] = tf_id;
}
for ( std::map<std::string, double>::const_iterator it = boosted_percent_str.begin();
it != boosted_percent_str.end(); ++it ) {
tf_id.ter = t_null;
tf_id.furn = f_null;
if ( it->second < 0.0001 ) {
continue;
}
if ( termap.find( it->first ) != termap.end() ) {
tf_id.ter = termap[ it->first ].loadid;
} else if ( furnmap.find( it->first ) != furnmap.end() ) {
tf_id.furn = furnmap[ it->first ].loadid;
} else {
debugmsg("No clue what '%s' is! No such terrain or furniture", it->first.c_str() );
continue;
}
btotal += (int)(it->second * 10000.0);
boosted_weightlist[ btotal ] = tf_id;
}
if ( wtotal > 1000000 ) {
debugmsg("plant coverage total exceeds 100%%");
}
if ( btotal > 1000000 ) {
debugmsg("boosted plant coverage total exceeds 100%%");
}
tf_id.furn = f_null;
tf_id.ter = default_ter;
weightlist[ 1000000 ] = tf_id;
boosted_weightlist[ 1000000 ] = tf_id;
percent_str.clear();
boosted_percent_str.clear();
}
ter_furn_id groundcover_extra::pick( bool boosted ) const
{
if ( boosted ) {
return boosted_weightlist.lower_bound( rng( 0, 1000000 ) )->second;
}
return weightlist.lower_bound( rng( 0, 1000000 ) )->second;
}
void regional_settings::setup()
{
if ( default_groundcover_str != NULL ) {
default_groundcover.primary = terfind(default_groundcover_str->primary_str);
default_groundcover.secondary = terfind(default_groundcover_str->secondary_str);
field_coverage.setup();
city_spec.shops.setup();
city_spec.parks.setup();
default_groundcover_str = NULL;
optionsdata.add_value("DEFAULT_REGION", id );
}
}
void overmap::add_mon_group(const mongroup &group)
{
// Monster groups: the old system had large groups (radius > 1),
// the new system transforms them into groups of radius 1, this also
// makes the diffuse setting obsolete (as it only controls how the radius
// is interpreted) - it's only used when adding monster groups with function.
if( group.radius == 1 ) {
zg.insert(std::pair<tripoint, mongroup>( tripoint( group.posx, group.posy, group.posz ), group ) );
return;
}
// diffuse groups use a circular area, non-diffuse groups use a rectangular area
const int rad = std::max<int>( 0, group.radius );
const double total_area = group.diffuse ? std::pow( rad + 1, 2 ) : ( rad * rad * M_PI + 1 );
const double pop = std::max<int>( 1, group.population );
int xpop = 0;
for( int x = -rad; x <= rad; x++ ) {
for( int y = -rad; y <= rad; y++ ) {
const int dist = group.diffuse ? square_dist( x, y, 0, 0 ) : trig_dist( x, y, 0, 0 );
if( dist > rad ) {
continue;
}
// Population on a single submap, *not* a integer
double pop_here;
if( rad == 0 ) {
pop_here = pop;
} else if( group.diffuse ) {
pop_here = pop / total_area;
} else {
// non-diffuse groups are more dense towards the center.
pop_here = ( 1.0 - static_cast<double>( dist ) / rad ) * pop / total_area;
}
if( pop_here > pop || pop_here < 0 ) {
DebugLog( D_ERROR, D_GAME ) << group.type << ": invalid population here: " << pop_here;
}
int p = std::max<int>( 0, std::floor( pop_here ) );
if( pop_here - p != 0 ) {
// in case the population is something like 0.2, randomly add a
// single population unit, this *should* on average give the correct
// total population.
const int mod = 10000 * ( pop_here - p );
if( x_in_y( mod, 10000 ) ) {
p++;
}
}
if( p == 0 ) {
continue;
}
// Exact copy to keep all important values, only change what's needed
// for a single-submap group.
mongroup tmp( group );
tmp.radius = 1;
tmp.posx += x;
tmp.posy += y;
tmp.population = p;
// This *can* create groups outside of the area of this overmap.
// As this function is called during generating the overmap, the
// neighboring overmaps might not have been generated and one can't access
// them through the overmapbuffer as this would trigger generating them.
// This would in turn to lead to a call to this function again.
// To avoid this, the overmapbufer checks the monster groups when loading
// an overmap and moves groups with out-of-bounds position to another overmap.
add_mon_group( tmp );
xpop += tmp.population;
}
}
DebugLog( D_ERROR, D_GAME ) << group.type << ": " << group.population << " => " << xpop;
}
const point overmap::invalid_point = point(INT_MIN, INT_MIN);
const tripoint overmap::invalid_tripoint = tripoint(INT_MIN, INT_MIN, INT_MIN);
//oter_id overmap::nulloter = "";
You can’t perform that action at this time.