diff --git a/data/json/itemgroups/tools.json b/data/json/itemgroups/tools.json index bf2b00c4012da..fcdc7a39561b4 100644 --- a/data/json/itemgroups/tools.json +++ b/data/json/itemgroups/tools.json @@ -112,6 +112,7 @@ [ "rope_30", 20 ], [ "flint_steel", 10 ], [ "tinderbox", 10 ], + [ "lifestraw", 4 ], [ "chem_hexamine", 10 ], [ "esbit_stove", 15 ], [ "mess_tin", 10 ] @@ -303,6 +304,7 @@ [ "mess_tin", 5 ], [ "riding_saddle", 3 ], [ "saddlebag", 5 ], + [ "lifestraw", 3 ], [ "acetylene_machine", 2 ] ] }, diff --git a/data/json/items/resources/misc.json b/data/json/items/resources/misc.json index 91985172dbe5f..8c61e16237eb1 100644 --- a/data/json/items/resources/misc.json +++ b/data/json/items/resources/misc.json @@ -40,13 +40,13 @@ "name": "down mattress", "name_plural": "down mattresses", "description": "This is a single, or twin, sized down filled mattress.", - "weight": 20000, + "weight": "6 kg", "volume": "300000 ml", "price": 1000, "material": [ "cotton" ], "symbol": "0", "color": "white", - "use_action": { "type": "deploy_furn", "furn_type": "f_feather_mattress" } + "use_action": { "type": "deploy_furn", "furn_type": "f_down_mattress" } }, { "id": "fuse", diff --git a/data/json/items/tools.json b/data/json/items/tools.json index 19e304070ef25..357e80743061b 100644 --- a/data/json/items/tools.json +++ b/data/json/items/tools.json @@ -1174,6 +1174,26 @@ "use_action": "WATER_PURIFIER", "flags": [ "ALLOWS_REMOTE_USE" ] }, + { + "id": "lifestraw", + "type": "TOOL", + "name": "lifestraw", + "description": "Set the lifestraw in suspect water, let sit for one minute then drink. The two part filtration system will purify the water you drink. Water taken from uncertain sources like a river may be dirty.", + "weight": "104 g", + "volume": "592 ml", + "price": 4000, + "price_postapoc": 75000, + "to_hit": -3, + "bashing": 1, + "material": [ "cotton", "plastic" ], + "symbol": "i", + "looks_like": "advanced_ecig", + "color": "light_cyan", + "initial_charges": 4000, + "max_charges": 4000, + "charges_per_use": 1, + "use_action": { "type": "WATER_PURIFIER", "moves": 70 } + }, { "id": "char_smoker", "type": "TOOL", diff --git a/data/mods/Aftershock/items/afs_weapons.json b/data/mods/Aftershock/items/afs_weapons.json index 374d01649ce9c..4e93090bd6f15 100644 --- a/data/mods/Aftershock/items/afs_weapons.json +++ b/data/mods/Aftershock/items/afs_weapons.json @@ -178,7 +178,9 @@ "reload": 500, "valid_mod_locations": [ [ "accessories", 4 ], [ "sights", 1 ], [ "sling", 1 ], [ "stock", 1 ], [ "underbarrel", 1 ] ], "ammo_effects": [ "LASER", "DRAW_AS_LINE" ], - "magazines": [ [ "battery", [ "heavy_battery_cell", "heavy_plus_battery_cell" ] ] ], + "magazines": [ + [ "battery", [ "heavy_battery_cell", "heavy_plus_battery_cell", "heavy_atomic_battery_cell", "heavy_disposable_cell" ] ] + ], "flags": [ "NEVER_JAMS", "FIRE_20" ] } ] diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index fb4c01c678f76..1dc2e2f80b6ea 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -1131,7 +1131,7 @@ See also VEHICLE_JSON.md "looks_like": "rag", // hint to tilesets if this item has no tile, use the looks_like tile "description" : "Socks. Put 'em on your feet.", // Description of the item "phase" : "solid", // (Optional, default = "solid") What phase it is -"weight" : 350, // Weight of the item in grams. For stackable items (ammo, comestibles) this is the weight per charge. +"weight" : "350 g", // Weight, weight in grams, mg and kg can be used - "50 mg", "5 g" or "5 kg". For stackable items (ammo, comestibles) this is the weight per charge. "volume" : "250 ml", // Volume, volume in ml and L can be used - "50 ml" or "2 L". For stackable items (ammo, comestibles) this is the volume of stack_size charges. "integral_volume" : 0, // Volume added to base item when item is integrated into another (eg. a gunmod integrated to a gun). Volume in ml and L can be used - "50 ml" or "2 L". "integral_weight" : 0, // Weight added to base item when item is integrated into another (eg. a gunmod integrated to a gun) diff --git a/src/game.cpp b/src/game.cpp index 79c4c3936ac2c..10206b72c3bc9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -8471,10 +8471,10 @@ void game::eat( item_location( *menu )( player &p ), int pos ) u.consume( pos ); } else if( u.consume_item( *it ) ) { - if( it->is_food_container() ) { + if( it->is_food_container() || !u.can_consume_as_is( *it ) ) { it->contents.erase( it->contents.begin() ); add_msg( _( "You leave the empty %s." ), it->tname() ); - } else if( u.can_consume_as_is( *it ) ) { + } else { item_loc.remove_item(); } } diff --git a/src/item.cpp b/src/item.cpp index a66c167b1fd8d..49c7d32f98031 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -3845,10 +3845,11 @@ units::mass item::weight( bool include_contents, bool integral ) const } units::mass ret; - if( integral ) { - ret = units::from_gram( get_var( "integral_weight", to_gram( type->integral_weight ) ) ); + std::string local_str_mass = integral ? get_var( "integral_weight" ) : get_var( "weight" ); + if( local_str_mass.empty() ) { + ret = integral ? type->integral_weight : type->weight; } else { - ret = units::from_gram( get_var( "weight", to_gram( type->weight ) ) ); + ret = units::from_milligram( std::stoll( local_str_mass ) ); } if( has_flag( "REDUCED_WEIGHT" ) ) { diff --git a/src/magic.cpp b/src/magic.cpp index b1f77d4639985..4bebf4a0d7fd1 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -1868,6 +1868,7 @@ void fake_spell::load( JsonObject &jo ) void fake_spell::serialize( JsonOut &json ) const { + json.start_object(); json.member( "id", id ); json.member( "hit_self", self ); if( !max_level ) { @@ -1876,6 +1877,7 @@ void fake_spell::serialize( JsonOut &json ) const json.member( "max_level", *max_level ); } json.member( "min_level", level ); + json.end_object(); } void fake_spell::deserialize( JsonIn &jsin ) diff --git a/src/magic_enchantment.cpp b/src/magic_enchantment.cpp index 3fab89e767ba3..912827463fd8e 100644 --- a/src/magic_enchantment.cpp +++ b/src/magic_enchantment.cpp @@ -251,21 +251,11 @@ void enchantment::serialize( JsonOut &jsout ) const jsout.member( "condition", io::enum_to_string( active_conditions.second ) ); if( !hit_you_effect.empty() ) { - jsout.member( "hit_you_effect" ); - jsout.start_array(); - for( const fake_spell &sp : hit_you_effect ) { - sp.serialize( jsout ); - } - jsout.end_array(); + jsout.member( "hit_you_effect", hit_you_effect ); } if( !hit_me_effect.empty() ) { - jsout.member( "hit_me_effect" ); - jsout.start_array(); - for( const fake_spell &sp : hit_me_effect ) { - sp.serialize( jsout ); - } - jsout.end_array(); + jsout.member( "hit_me_effect", hit_me_effect ); } if( !intermittent_activation.empty() ) { diff --git a/tools/dialogue_validator.py b/tools/dialogue_validator.py index 5a3a73e6569a0..263e26db9f9f9 100755 --- a/tools/dialogue_validator.py +++ b/tools/dialogue_validator.py @@ -17,20 +17,25 @@ "TALK_DONE and every topic is reachable from an NPC's" "starting topic. Reports nothing on success.") args.add_argument("dialogue_json", nargs="+", action="store", - help="specify json file or files to validate. Use 'data/json/npcs/* " - "data/json/npcs/*/* data/json/npcs/*/*/*' to validate the " - "dialogue in the vanilla game.") + help="specify json folder to validate. The valdiator will walk the folder's " + "tree and validate all JSON files in it. Use 'data/json/npcs/ " + "to validate the dialogue in the vanilla game.") argsDict = vars(args.parse_args()) def get_dialogue_from_json(): dialogue = [] - for path in argsDict.get("dialogue_json", []): - if path == "data/json/npcs/TALK_TEST.json": - continue - if path.endswith(".json"): - with open(path) as dialogue_file: - dialogue += json.load(dialogue_file) + for arg_path in argsDict.get("dialogue_json", []): + if arg_path.endswith("/"): + arg_path = arg_path[:-1] + for subdir_path, dirnames, filenames in os.walk(arg_path): + for filename in filenames: + path = subdir_path + "/" + filename + if path == "data/json/npcs/TALK_TEST.json": + continue + if path.endswith(".json"): + with open(path) as dialogue_file: + dialogue += json.load(dialogue_file) return dialogue diff --git a/tools/gfx_tools/compose.py b/tools/gfx_tools/compose.py index 2cd5efb69007b..5e2f94e80a578 100644 --- a/tools/gfx_tools/compose.py +++ b/tools/gfx_tools/compose.py @@ -70,9 +70,12 @@ def __init__(self, tileset_dirname): # dict of png absolute numbers to png names self.pngnum_to_pngname = { 0: "null_image" } self.pngnum = 0 + self.referenced_pngnames = [] self.tileset_pathname = tileset_dirname if not tileset_dirname.startswith("gfx/"): self.tileset_pathname = "gfx/" + tileset_dirname + if self.tileset_pathname.endswith("/"): + self.tileset_pathname = self.tileset_pathname[:-1] try: os.stat(self.tileset_pathname) @@ -90,6 +93,19 @@ def __init__(self, tileset_dirname): self.tileset_width = self.tileset_info[0].get("width") self.tileset_height = self.tileset_info[0].get("height") + def convert_a_pngname_to_pngnum(self, sprite_id, entry): + if sprite_id and sprite_id != "no_entry": + new_id = self.pngname_to_pngnum.get(sprite_id, 0) + if new_id: + entry.append(new_id) + if sprite_id not in self.referenced_pngnames: + self.referenced_pngnames.append(sprite_id) + return True + else: + print("sprite id '{}' has no matching PNG file. ".format(sprite_id) + + "It will not be added to tile_config.json") + return False + def convert_pngname_to_pngnum(self, index): new_index = [] if isinstance(index, list): @@ -97,30 +113,22 @@ def convert_pngname_to_pngnum(self, index): if isinstance(pngname, dict): sprite_ids = pngname.get("sprite") valid = False + new_sprites = [] if isinstance(sprite_ids, list): new_sprites = [] for sprite_id in sprite_ids: - if sprite_id != "no_entry": - new_id = self.pngname_to_pngnum.get(sprite_id, 0) - if new_id: - new_sprites.append(new_id) - valid = True + valid |= self.convert_a_pngname_to_pngnum(sprite_id, new_sprites) pngname["sprite"] = new_sprites - elif sprite_ids and sprite_ids != "no_entry": - new_id = self.pngname_to_pngnum.get(sprite_ids, 0) - if new_id: - pngname["sprite"] = new_id - valid = True + else: + valid = self.convert_a_pngname_to_pngnum(sprite_ids, new_sprites) + if valid: + pngname["sprite"] = new_sprites[0] if valid: new_index.append(pngname) - elif pngname != "no_entry": - new_id = self.pngname_to_pngnum.get(pngname, 0) - if new_id: - new_index.append(new_id) - elif index and index != "no_entry": - new_id = self.pngname_to_pngnum.get(index, 0) - if new_id: - new_index.append(new_id) + else: + self.convert_a_pngname_to_pngnum(pngname, new_index) + else: + self.convert_a_pngname_to_pngnum(index, new_index) if new_index and len(new_index) == 1: return new_index[0] return new_index @@ -163,6 +171,13 @@ def convert_tile_entry(self, tile_entry, prefix, is_filler): return tile_entry return None + def verify(self): + for pngname, pngnum in self.pngname_to_pngnum.items(): + if pngnum and pngname not in self.referenced_pngnames: + print("image filename '{}' index '{}'".format(pngname, pngnum) + + " was not used in any tile_config.json entries") + + class TilesheetData(object): def __init__(self, subdir_index, refs): ts_all = refs.tileset_info[subdir_index] @@ -356,3 +371,5 @@ def finalize_merges(self, merge_pngs): } tileset_confpath = refs.tileset_pathname + "/" + "tile_config.json" write_to_json(tileset_confpath, conf_data) + +refs.verify()