From 610e7d5502c3571e36a9363e4eec1d8801af451b Mon Sep 17 00:00:00 2001 From: Dillon Matchett Date: Fri, 12 Nov 2021 04:02:27 -0400 Subject: [PATCH] Layering items and item variants on specific furniture and terrain (#52632) --- gfx/layering.json | 3 + src/cata_tiles.cpp | 245 +++++++++++++++++++++++++++++++++++++++------ src/cata_tiles.h | 20 ++++ src/item.h | 3 + src/path_info.cpp | 4 + src/path_info.h | 1 + src/submap.h | 5 + 7 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 gfx/layering.json diff --git a/gfx/layering.json b/gfx/layering.json new file mode 100644 index 0000000000000..b70b6673c1f28 --- /dev/null +++ b/gfx/layering.json @@ -0,0 +1,3 @@ +"item_variants": [ + +] diff --git a/src/cata_tiles.cpp b/src/cata_tiles.cpp index 993a6c858da90..67f562c3ad642 100644 --- a/src/cata_tiles.cpp +++ b/src/cata_tiles.cpp @@ -324,10 +324,11 @@ void cata_tiles::reinit() } static void get_tile_information( const std::string &config_path, std::string &json_path, - std::string &tileset_path ) + std::string &tileset_path, std::string &layering_path ) { const std::string default_json = PATH_INFO::defaulttilejson(); const std::string default_tileset = PATH_INFO::defaulttilepng(); + const std::string default_layering = PATH_INFO::defaultlayeringjson(); // Get JSON and TILESET vars from config const auto reader = [&]( std::istream & fin ) { @@ -341,6 +342,10 @@ static void get_tile_information( const std::string &config_path, std::string &j } else if( string_starts_with( sOption, "TILESET" ) ) { fin >> tileset_path; dbg( D_INFO ) << "TILESET path set to [" << tileset_path << "]."; + } else if( string_starts_with( sOption, "LAYERING" ) ) { + fin >> layering_path; + dbg( D_INFO ) << "LAYERING path set to [" << layering_path << "]."; + } else { getline( fin, sOption ); } @@ -350,6 +355,7 @@ static void get_tile_information( const std::string &config_path, std::string &j if( !read_from_file( config_path, reader ) ) { json_path = default_json; tileset_path = default_tileset; + layering_path = default_layering; } if( json_path.empty() ) { @@ -360,6 +366,10 @@ static void get_tile_information( const std::string &config_path, std::string &j tileset_path = default_tileset; dbg( D_INFO ) << "TILESET set to default [" << tileset_path << "]."; } + if( layering_path.empty() ) { + layering_path = default_layering; + dbg( D_INFO ) << "TILESET set to default [" << layering_path << "]."; + } } template @@ -576,25 +586,39 @@ void tileset_cache::loader::load( const std::string &tileset_id, const bool prec const bool pump_events ) { std::string json_conf; + std::string layering; std::string tileset_path; std::string tileset_root; + bool has_layering = true; + const auto tset_iter = TILESETS.find( tileset_id ); if( tset_iter != TILESETS.end() ) { tileset_root = tset_iter->second; dbg( D_INFO ) << '"' << tileset_id << '"' << " tileset: found config file path: " << tileset_root; get_tile_information( tileset_root + '/' + PATH_INFO::tileset_conf(), - json_conf, tileset_path ); + json_conf, tileset_path, layering ); dbg( D_INFO ) << "Current tileset is: " << tileset_id; } else { dbg( D_ERROR ) << "Tileset \"" << tileset_id << "\" from options is invalid"; json_conf = PATH_INFO::defaulttilejson(); tileset_path = PATH_INFO::defaulttilepng(); + layering = PATH_INFO::defaultlayeringjson(); } std::string json_path = tileset_root + '/' + json_conf; std::string img_path = tileset_root + '/' + tileset_path; + std::string layering_path = tileset_root + '/' + layering; + + dbg( D_INFO ) << "Attempting to Load LAYERING file " << layering_path; + cata::ifstream layering_file( fs::u8path( layering_path ), + std::ifstream::in | std::ifstream::binary ); + + if( !layering_file.good() ) { + has_layering = false; + //throw std::runtime_error(std::string("Failed to open layering info json: ") + layering_path); + } dbg( D_INFO ) << "Attempting to Load JSON file " << json_path; cata::ifstream config_file( fs::u8path( json_path ), @@ -701,6 +725,22 @@ void tileset_cache::loader::load( const std::string &tileset_id, const bool prec ensure_default_item_highlight(); ts.tileset_id = tileset_id; + + // set up layering data + if( has_layering ) { + JsonIn layering_json( layering_file ); + JsonObject layer_config = layering_json.get_object(); + layer_config.allow_omitted_members(); + + // "item_variants" section must exist. + if( !layer_config.has_member( "item_variants" ) ) { + layer_config.throw_error( "\"item_variants\" missing" ); + } + + load_layers( layer_config ); + } + + } void tileset_cache::loader::load_internal( const JsonObject &config, @@ -766,6 +806,47 @@ void tileset_cache::loader::load_internal( const JsonObject &config, // also eliminate negative sprite references } +void tileset_cache::loader::load_layers( const JsonObject &config ) +{ + for( const JsonObject item : config.get_array( "item_variants" ) ) { + if( item.has_member( "context" ) && item.has_array( "variants" ) ) { + std::string context; + context = item.get_string( "context" ); + std::vector variants; + for( const JsonObject vars : item.get_array( "variants" ) ) { + if( vars.has_member( "item" ) && vars.has_array( "sprite" ) && vars.has_member( "layer" ) ) { + layer_variant v; + v.item = vars.get_string( "item" ); + + v.layer = vars.get_int( "layer" ); + + int total_weight = 0; + for( const JsonObject sprites : vars.get_array( "sprite" ) ) { + std::string id = sprites.get_string( "id" ); + int weight = sprites.get_int( "weight", 1 ); + v.sprite.emplace( id, weight ); + + total_weight += weight; + } + v.total_weight = total_weight; + variants.push_back( v ); + } else { + config.throw_error( "variants configured incorrectly" ); + } + } + // sort them based on layering so we can draw them correctly + std::sort( variants.begin(), variants.end(), []( const layer_variant & a, + const layer_variant & b ) { + return a.layer < b.layer; + } ); + ts.layer_data.emplace( context, variants ); + } else { + config.throw_error( "layering configured incorrectly" ); + } + } + +} + void tileset_cache::loader::process_variations_after_loading( weighted_int_list> &vs ) { @@ -2976,38 +3057,142 @@ bool cata_tiles::draw_field_or_item( const tripoint &p, const lit_level ll, int mtype_id mon_id; std::string variant; bool hilite = false; + bool drawtop = true; const itype *it_type; - if( it_overridden ) { - it_id = std::get<0>( it_override->second ); - mon_id = std::get<1>( it_override->second ); - hilite = std::get<2>( it_override->second ); - it_type = item::find_type( it_id ); - } else if( !invisible[0] && here.sees_some_items( p, get_player_character() ) ) { - const maptile &tile = here.maptile_at( p ); - const item &itm = tile.get_uppermost_item(); - if( itm.has_itype_variant() ) { - variant = itm.itype_variant().id; + const maptile &tile = here.maptile_at( p ); + + if( !invisible[0] ) { + // start by drawing the layering data if available + // start for checking if layer data is available for the furniture + auto itt = tileset_ptr->layer_data.find( tile.get_furn_t().id.str() ); + if( itt != tileset_ptr->layer_data.end() ) { + + // the furniture has layer info + // go through all the layer variants + for( const layer_variant &layer_var : itt->second ) { + for( const item &i : tile.get_items() ) { + if( i.typeId().str() == layer_var.item ) { + // if an item matches draw it and break + const std::string layer_it_category = i.typeId()->get_item_type_string(); + const lit_level layer_lit = ll; + const bool layer_nv = nv_goggles_activated; + + // get the sprite to draw + // roll should be based on the maptile seed to keep visuals consistent + int roll = i.seed % layer_var.total_weight; + std::string sprite_to_draw; + for( const auto &sprite_list : layer_var.sprite ) { + roll = roll - sprite_list.second; + if( roll < 0 ) { + sprite_to_draw = sprite_list.first; + break; + } + } + + // if we have found info on the item go through and draw its stuff + draw_from_id_string( sprite_to_draw, TILE_CATEGORY::ITEM, layer_it_category, p, 0, + 0, layer_lit, layer_nv, height_3d, 0, "" ); + + + // if the top item is already being layered don't draw it later + if( i.typeId() == tile.get_uppermost_item().typeId() ) { + drawtop = false; + } + + break; + } + } + + } + + } else { + // check if the terrain has data + auto itt = tileset_ptr->layer_data.find( tile.get_ter_t().id.str() ); + if( itt != tileset_ptr->layer_data.end() ) { + + // the furniture has layer info + // go through all the layer variants + for( const layer_variant &layer_var : itt->second ) { + for( const item &i : tile.get_items() ) { + if( i.typeId().str() == layer_var.item ) { + // if an item matches draw it and break + const std::string layer_it_category = i.typeId()->get_item_type_string(); + const lit_level layer_lit = ll; + const bool layer_nv = nv_goggles_activated; + + // get the sprite to draw + // roll should be based on the maptile seed to keep visuals consistent + int roll = i.seed % layer_var.total_weight; + std::string sprite_to_draw; + for( const auto &sprite_list : layer_var.sprite ) { + roll = roll - sprite_list.second; + if( roll < 0 ) { + sprite_to_draw = sprite_list.first; + break; + } + } + + // if we have found info on the item go through and draw its stuff + draw_from_id_string( sprite_to_draw, TILE_CATEGORY::ITEM, layer_it_category, p, 0, + 0, layer_lit, layer_nv, height_3d, 0, "" ); + + + // if the top item is already being layered don't draw it later + if( i.typeId() == tile.get_uppermost_item().typeId() ) { + drawtop = false; + } + + break; + } + } + + } + + } } - const mtype *const mon = itm.get_mtype(); - it_id = itm.typeId(); - mon_id = mon ? mon->id : mtype_id::NULL_ID(); - hilite = tile.get_item_count() > 1; - it_type = itm.type; - } else { - it_type = nullptr; } - if( it_type && !it_id.is_null() ) { - const std::string disp_id = it_id == itype_corpse && mon_id ? - "corpse_" + mon_id.str() : it_id.str(); - const std::string it_category = it_type->get_item_type_string(); - const lit_level lit = it_overridden ? lit_level::LIT : ll; - const bool nv = it_overridden ? false : nv_goggles_activated; - - ret_draw_items = draw_from_id_string( disp_id, TILE_CATEGORY::ITEM, it_category, p, 0, - 0, lit, nv, height_3d, 0, variant ); - if( ret_draw_items && hilite ) { - draw_item_highlight( p ); + + + if( drawtop || it_overridden ) { + if( it_overridden ) { + it_id = std::get<0>( it_override->second ); + mon_id = std::get<1>( it_override->second ); + hilite = std::get<2>( it_override->second ); + it_type = item::find_type( it_id ); + } else if( !invisible[0] && here.sees_some_items( p, get_player_character() ) ) { + const item &itm = tile.get_uppermost_item(); + if( itm.has_itype_variant() ) { + variant = itm.itype_variant().id; + } + const mtype *const mon = itm.get_mtype(); + it_id = itm.typeId(); + mon_id = mon ? mon->id : mtype_id::NULL_ID(); + hilite = tile.get_item_count() > 1; + it_type = itm.type; + } else { + it_type = nullptr; } + if( it_type && !it_id.is_null() ) { + + const std::string disp_id = it_id == itype_corpse && mon_id ? + "corpse_" + mon_id.str() : it_id.str(); + const std::string it_category = it_type->get_item_type_string(); + const lit_level lit = it_overridden ? lit_level::LIT : ll; + const bool nv = it_overridden ? false : nv_goggles_activated; + + + + + ret_draw_items = draw_from_id_string( disp_id, TILE_CATEGORY::ITEM, it_category, p, 0, + 0, lit, nv, height_3d, 0, variant ); + if( ret_draw_items && hilite ) { + draw_item_highlight( p ); + } + } + } + // we may still need to draw the highlight + else if( tile.get_item_count() > 1 && here.sees_some_items( p, get_player_character() ) ) { + draw_item_highlight( p ); } } return ret_draw_field && ret_draw_items; diff --git a/src/cata_tiles.h b/src/cata_tiles.h index 5741156ef4b49..7c682fa576544 100644 --- a/src/cata_tiles.h +++ b/src/cata_tiles.h @@ -107,6 +107,15 @@ class texture } }; +class layer_variant +{ + public: + std::string item; + std::map sprite; + int layer; + int total_weight; +}; + class tileset { private: @@ -136,6 +145,7 @@ class tileset // either variant can be either a `nullptr` or a pointer/reference to the real value (stored inside `tile_ids`) std::unordered_map tile_ids_by_season[season_type::NUM_SEASONS]; + static const texture *get_if_available( const size_t index, const decltype( shadow_tile_values ) &tiles ) { return index < tiles.size() ? & tiles[index] : nullptr; @@ -144,6 +154,9 @@ class tileset friend class tileset_cache; public: + + std::unordered_map> layer_data; + int get_tile_width() const { return tile_width; } @@ -179,6 +192,7 @@ class tileset tile_type &create_tile_type( const std::string &id, tile_type &&new_tile_type ); const tile_type *find_tile_type( const std::string &id ) const; + /** * Looks up tile by id + season suffix AND just raw id * Example: if id == "t_tree_apple" and season == SPRING @@ -284,6 +298,12 @@ class tileset_cache::loader void load_internal( const JsonObject &config, const std::string &tileset_root, const std::string &img_path, bool pump_events ); + /** + * Helper function to load layering data. + * @throw std::exception On any error. + */ + void load_layers( const JsonObject &config ); + public: loader( tileset &ts, const SDL_Renderer_Ptr &r ) : ts( ts ), renderer( r ) { } diff --git a/src/item.h b/src/item.h index fb259229b0811..cd3533368b03f 100644 --- a/src/item.h +++ b/src/item.h @@ -32,6 +32,7 @@ #include "units.h" #include "value_ptr.h" #include "visitable.h" +#include "rng.h" class Character; class Creature; @@ -2580,6 +2581,8 @@ class item : public visitable bool ethereal = false; int wetness = 0; // Turns until this item is completly dry. + int seed = rng( 0, INT_MAX ); // A random seed for layering and other options + // Set when the item / its content changes. Used for worn item with // encumbrance depending on their content. // This not part serialized or compared on purpose! diff --git a/src/path_info.cpp b/src/path_info.cpp index 7b92cc82f9b87..b88bd1e1d58cf 100644 --- a/src/path_info.cpp +++ b/src/path_info.cpp @@ -183,6 +183,10 @@ std::string PATH_INFO::defaulttilejson() { return "tile_config.json"; } +std::string PATH_INFO::defaultlayeringjson() +{ + return "layering.json"; +} std::string PATH_INFO::defaulttilepng() { return "tinytile.png"; diff --git a/src/path_info.h b/src/path_info.h index 81c830fc5d22f..d2b26691018b7 100644 --- a/src/path_info.h +++ b/src/path_info.h @@ -31,6 +31,7 @@ std::string datadir(); std::string debug(); std::string defaultsounddir(); std::string defaulttilejson(); +std::string defaultlayeringjson(); std::string defaulttilepng(); std::string fontdata(); std::string fontdir(); diff --git a/src/submap.h b/src/submap.h index d63dd2380eaf1..c2ea1b39de942 100644 --- a/src/submap.h +++ b/src/submap.h @@ -339,6 +339,11 @@ struct maptile { const item &get_uppermost_item() const { return *std::prev( sm->get_items( pos() ).cend() ); } + + // Gets all items + const cata::colony &get_items() const { + return sm->get_items( pos() ); + } }; #endif // CATA_SRC_SUBMAP_H