Skip to content

Commit

Permalink
Switch mapgen keys from ints to strings
Browse files Browse the repository at this point in the history
Using new facility to split strings into displayed chunks, allow mapgen
to use arbitrary Unicode characters (including combining characters) as
keys.
  • Loading branch information
jbytheway authored and ZhilkinSerg committed Mar 17, 2020
1 parent 6defa75 commit ee98441
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 42 deletions.
87 changes: 49 additions & 38 deletions src/mapgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,20 @@ void jmapgen_place::offset( const point &offset )
y.valmax -= offset.y;
}

map_key::map_key( const std::string &s ) : str( s )
{
if( utf8_width( str ) != 1 ) {
debugmsg( "map key '%s' must be 1 column", str );
}
}

map_key::map_key( const JsonMember &member ) : str( member.name() )
{
if( utf8_width( str ) != 1 ) {
member.throw_error( "format map key must be 1 column" );
}
}

/**
* This is a generic mapgen piece, the template parameter PieceType should be another specific
* type of jmapgen_piece. This class contains a vector of those objects and will chose one of
Expand Down Expand Up @@ -2036,16 +2050,13 @@ void mapgen_palette::load_place_mapings( const JsonObject &jo, const std::string
{
if( jo.has_object( "mapping" ) ) {
for( const JsonMember member : jo.get_object( "mapping" ) ) {
const std::string &key = member.name();
if( key.size() != 1 ) {
member.throw_error( "format map key must be 1 character" );
}
const map_key key( member );
JsonObject sub = member.get_object();
sub.allow_omitted_members();
if( !sub.has_member( member_name ) ) {
continue;
}
auto &vect = format_placings[ key[0] ];
auto &vect = format_placings[ key ];
::load_place_mapings<PieceType>( sub.get_member( member_name ), vect );
}
}
Expand All @@ -2059,11 +2070,8 @@ void mapgen_palette::load_place_mapings( const JsonObject &jo, const std::string
return;
}
for( const JsonMember member : jo.get_object( member_name ) ) {
const std::string &key = member.name();
if( key.size() != 1 ) {
member.throw_error( "format map key must be 1 character" );
}
auto &vect = format_placings[ key[0] ];
const map_key key( member );
auto &vect = format_placings[ key ];
::load_place_mapings<PieceType>( member, vect );
}
}
Expand Down Expand Up @@ -2141,35 +2149,29 @@ mapgen_palette mapgen_palette::load_internal( const JsonObject &jo, const std::s
// "terrain": { "a": "t_grass", "b": "t_lava" }
if( jo.has_member( "terrain" ) ) {
for( const JsonMember member : jo.get_object( "terrain" ) ) {
const std::string &key = member.name();
if( key.size() != 1 ) {
member.throw_error( "format map key must be 1 character" );
}
const map_key key( member );
if( member.test_string() ) {
format_terrain[key[0]] = ter_id( member.get_string() );
format_terrain[key] = ter_id( member.get_string() );
} else {
auto &vect = format_placings[ key[0] ];
auto &vect = format_placings[ key ];
::load_place_mapings<jmapgen_terrain>( member, vect );
if( !vect.empty() ) {
// Dummy entry to signal that this terrain is actually defined, because
// the code below checks that each square on the map has a valid terrain
// defined somehow.
format_terrain[key[0]] = t_null;
format_terrain[key] = t_null;
}
}
}
}

if( jo.has_object( "furniture" ) ) {
for( const JsonMember member : jo.get_object( "furniture" ) ) {
const std::string &key = member.name();
if( key.size() != 1 ) {
member.throw_error( "format map key must be 1 character" );
}
const map_key key( member );
if( member.test_string() ) {
format_furniture[key[0]] = furn_id( member.get_string() );
format_furniture[key] = furn_id( member.get_string() );
} else {
auto &vect = format_placings[ key[0] ];
auto &vect = format_placings[ key ];
::load_place_mapings<jmapgen_furniture>( member, vect );
}
}
Expand Down Expand Up @@ -2307,27 +2309,35 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo )
return false;
}

// mandatory: mapgensize rows of mapgensize character lines, each of which must have a matching key in "terrain",
// unless fill_ter is set
// mandatory: mapgensize rows of mapgensize character lines, each of which must have a
// matching key in "terrain", unless fill_ter is set
// "rows:" [ "aaaajustlikeinmapgen.cpp", "this.must!be!exactly.24!", "and_must_match_terrain_", .... ]
point expected_dim = mapgensize + m_offset;
assert( expected_dim.x >= 0 );
assert( expected_dim.y >= 0 );

parray = jo.get_array( "rows" );
if( static_cast<int>( parray.size() ) < expected_dim.y ) {
parray.throw_error( string_format( "format: rows: must have at least %d rows, not %d",
expected_dim.y, parray.size() ) );
}
for( int c = m_offset.y; c < expected_dim.y; c++ ) {
const auto tmpval = parray.get_string( c );
if( static_cast<int>( tmpval.size() ) < expected_dim.x ) {
parray.throw_error( string_format( "format: row %d must have at least %d columns, not %d",
c + 1, expected_dim.x, tmpval.size() ) );
const std::string row = parray.get_string( c );
std::vector<map_key> row_keys;
for( const std::string &key : utf8_display_split( row ) ) {
row_keys.emplace_back( key );
}
if( row_keys.size() < static_cast<size_t>( expected_dim.x ) ) {
parray.throw_error(
string_format( " format: row %d must have at least %d columns, not %d",
c + 1, expected_dim.x, row_keys.size() ) );
}
for( int i = m_offset.x; i < expected_dim.x; i++ ) {
const point p = point( i, c ) - m_offset;
const int tmpkey = tmpval[i];
const auto iter_ter = format_terrain.find( tmpkey );
const auto iter_furn = format_furniture.find( tmpkey );
const auto fpi = format_placings.find( tmpkey );
const map_key key = row_keys[i];
const auto iter_ter = format_terrain.find( key );
const auto iter_furn = format_furniture.find( key );
const auto fpi = format_placings.find( key );

const bool has_terrain = iter_ter != format_terrain.end();
const bool has_furn = iter_furn != format_furniture.end();
Expand All @@ -2336,18 +2346,19 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo )
if( !has_terrain && !fallback_terrain_exists ) {
parray.throw_error(
string_format( "format: rows: row %d column %d: "
"'%c' is not in 'terrain', and no 'fill_ter' is set!",
c + 1, i + 1, static_cast<char>( tmpkey ) ) );
"'%s' is not in 'terrain', and no 'fill_ter' is set!",
c + 1, i + 1, key.str ) );
}
if( test_mode && !has_terrain && !has_furn && !has_placing && tmpkey != ' ' && tmpkey != '.' ) {
if( test_mode && !has_terrain && !has_furn && !has_placing &&
key.str != " " && key.str != "." ) {
// TODO: Once all the in-tree mods don't report this error,
// it should be changed to happen in regular games (not
// just test_mode) and be non-fatal, so that mappers find
// out about their issues before they PR their changes.
parray.throw_error(
string_format( "format: rows: row %d column %d: "
"'%c' has no terrain, furniture, or other definition",
c + 1, i + 1, static_cast<char>( tmpkey ) ) );
"'%s' has no terrain, furniture, or other definition",
c + 1, i + 1, key.str ) );
}
if( has_terrain ) {
format[ calc_index( p ) ].ter = iter_ter->second;
Expand Down
35 changes: 31 additions & 4 deletions src/mapgen.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,19 +181,46 @@ class jmapgen_place

using palette_id = std::string;

// Strong typedef for strings used as map/palette keys
// Each key should be a UTF-8 string displayed in only one column (i.e.
// utf8_width of 1) but can contain multiple Unicode code points.
class map_key
{
public:
map_key( const std::string & );
map_key( const JsonMember & );

friend bool operator==( const map_key &l, const map_key &r ) {
return l.str == r.str;
}

std::string str;
};

namespace std
{
template<>
struct hash<map_key> {
size_t operator()( const map_key &k ) const noexcept {
return hash<std::string> {}( k.str );
}
};
} // namespace std

class mapgen_palette
{
public:
palette_id id;
/**
* The mapping from character code (key) to a list of things that should be placed. This is
* The mapping from character (key) to a list of things that should be placed. This is
* similar to objects, but it uses key to get the actual position where to place things
* out of the json "bitmap" (which is used to paint the terrain/furniture).
*/
using placing_map = std::map< int, std::vector< shared_ptr_fast<const jmapgen_piece> > >;
using placing_map =
std::unordered_map<map_key, std::vector< shared_ptr_fast<const jmapgen_piece>>>;

std::map<int, ter_id> format_terrain;
std::map<int, furn_id> format_furniture;
std::unordered_map<map_key, ter_id> format_terrain;
std::unordered_map<map_key, furn_id> format_furniture;
placing_map format_placings;

template<typename PieceType>
Expand Down

0 comments on commit ee98441

Please sign in to comment.