void MonsterGenerator::load_special_attacks(mtype *m, JsonObject &jo, std::string member) { m->special_attacks.clear(); // make sure we're running with everything cleared if( !jo.has_array( member ) ) { return; } JsonArray outer = jo.get_array(member); while( outer.has_more() ) { if( outer.test_array() ) { JsonArray inner = outer.next_array(); const auto &aname = inner.get_string(0); if ( attack_map.find(aname) != attack_map.end() ) { auto new_entry = mtype_special_attack( attack_map[aname], inner.get_int(1) ); m->special_attacks[aname] = new_entry; m->special_attacks_names.push_back(aname); } else { inner.throw_error("Invalid special_attacks"); } } else if( outer.test_object() ) { set_attack_from_object( outer.next_object(), m->special_attacks, m->special_attacks_names ); } else { outer.throw_error( "array element is neither array nor object." ); } } }
void mtype::add_special_attack( JsonArray inner, const std::string & ) { MonsterGenerator &gen = MonsterGenerator::generator(); const std::string name = inner.get_string( 0 ); const auto iter = gen.attack_map.find( name ); if( iter == gen.attack_map.end() ) { inner.throw_error( "Invalid special_attacks" ); } if( special_attacks.count( name ) > 0 ) { special_attacks.erase( name ); const auto iter = std::find( special_attacks_names.begin(), special_attacks_names.end(), name ); if( iter != special_attacks_names.end() ) { special_attacks_names.erase( iter ); } if( test_mode ) { debugmsg( "%s specifies more than one attack of (sub)type %s, ignoring all but the last", id.c_str(), name.c_str() ); } } auto new_attack = mtype_special_attack( iter->second ); new_attack.actor->cooldown = inner.get_int( 1 ); special_attacks.emplace( name, new_attack ); special_attacks_names.push_back( name ); }
Trait_group_tag trait_group::load_trait_group( JsonIn &stream, const std::string &default_subtype ) { if( stream.test_string() ) { return Trait_group_tag( stream.get_string() ); } else if( stream.test_object() ) { const Trait_group_tag group = get_unique_trait_group_id(); JsonObject jo = stream.get_object(); const std::string subtype = jo.get_string( "subtype", default_subtype ); mutation_branch::load_trait_group( jo, group, subtype ); return group; } else if( stream.test_array() ) { const Trait_group_tag group = get_unique_trait_group_id(); JsonArray jarr = stream.get_array(); if( default_subtype != "collection" && default_subtype != "distribution" ) { jarr.throw_error( "invalid subtype for trait group" ); } mutation_branch::load_trait_group( jarr, group, default_subtype == "collection" ); return group; } else { stream.error( "invalid trait group, must be string (group id) or object/array (the group data)" ); return Trait_group_tag{}; } }
json_item_substitution::substitution::info json_item_substitution::substitution::info::load( JsonArray &arr ) { json_item_substitution::substitution::info ret; ret.new_item = arr.next_string(); if( arr.test_float() && ( ret.ratio = arr.next_float() ) <= 0.0 ) { arr.throw_error( "Ratio must be positive" ); } return ret; }
void mtype::add_special_attack( JsonArray inner ) { MonsterGenerator &gen = MonsterGenerator::generator(); const std::string name = inner.get_string( 0 ); const auto iter = gen.attack_map.find( name ); if( iter == gen.attack_map.end() ) { inner.throw_error( "Invalid special_attacks" ); } special_attacks[name] = mtype_special_attack( iter->second, inner.get_int( 1 ) ); special_attacks_names.push_back( name ); }
void item_comp::load( JsonArray &ja ) { JsonArray comp = ja.next_array(); type = comp.get_string( 0 ); count = comp.get_int( 1 ); // Recoverable is true by default. if( comp.size() > 2 ) { recoverable = comp.get_string( 2 ) == "NO_RECOVER" ? false : true; } if( count <= 0 ) { ja.throw_error( "item count must be a positive number" ); } }
void MonsterGenerator::load_special_defense(mtype *m, JsonObject &jo, std::string member) { if (jo.has_array(member)) { JsonArray jsarr = jo.get_array(member); if ( defense_map.find(jsarr.get_string(0)) != defense_map.end() ) { m->sp_defense = defense_map[jsarr.get_string(0)]; m->def_chance = jsarr.get_int(1); } else { jsarr.throw_error("Invalid special_when_hit"); } } if (m->sp_defense == NULL) { m->sp_defense = defense_map["NONE"]; } }
void tool_comp::load( JsonArray &ja ) { if( ja.test_string() ) { // constructions uses this format: [ "tool", ... ] type = ja.next_string(); count = -1; } else { JsonArray comp = ja.next_array(); type = comp.get_string( 0 ); count = comp.get_int( 1 ); } if( count == 0 ) { ja.throw_error( "tool count must not be 0" ); } // Note: negative count means charges (of the tool) should be consumed }
void json_item_substitution::do_load( JsonObject &jo ) { const bool item_mode = jo.has_string( "item" ); const std::string title = jo.get_string( item_mode ? "item" : "trait" ); auto check_duplicate_item = [&]( const itype_id & it ) { return substitutions.find( it ) != substitutions.end() || std::find_if( bonuses.begin(), bonuses.end(), [&it]( const std::pair<itype_id, trait_requirements> &p ) { return p.first == it; } ) != bonuses.end(); }; if( item_mode && check_duplicate_item( title ) ) { jo.throw_error( "Duplicate definition of item" ); } if( jo.has_array( "bonus" ) ) { if( !item_mode ) { jo.throw_error( "Bonuses can only be used in item mode" ); } JsonArray arr = jo.get_array( "bonus" ); bonuses.emplace_back( title, trait_requirements::load( arr ) ); } else if( !jo.has_array( "sub" ) ) { jo.throw_error( "Missing sub array" ); } JsonArray sub = jo.get_array( "sub" ); while( sub.has_more() ) { JsonArray line = sub.next_array(); substitution s; const itype_id old_it = item_mode ? title : line.next_string(); if( item_mode ) { s.trait_reqs = trait_requirements::load( line ); } else { if( check_duplicate_item( old_it ) ) { line.throw_error( "Duplicate definition of item" ); } s.trait_reqs.present.push_back( trait_id( title ) ); } // Error if the array doesn't have at least one new_item do { s.infos.push_back( substitution::info::load( line ) ); } while( line.has_more() ); substitutions[old_it].push_back( s ); } }
void mtype::add_special_attacks( JsonObject &jo, const std::string &member ) { if( !jo.has_array( member ) ) { return; } JsonArray outer = jo.get_array(member); while( outer.has_more() ) { if( outer.test_array() ) { add_special_attack( outer.next_array() ); } else if( outer.test_object() ) { add_special_attack( outer.next_object() ); } else { outer.throw_error( "array element is neither array nor object." ); } } }
void item_comp::load( JsonArray &ja ) { JsonArray comp = ja.next_array(); type = comp.get_string( 0 ); count = comp.get_int( 1 ); size_t handled = 2; while( comp.size() > handled ) { const std::string &flag = comp.get_string( handled++ ); if( flag == "NO_RECOVER" ) { recoverable = false; } else if( flag == "LIST" ) { requirement = true; } } if( count <= 0 ) { ja.throw_error( "item count must be a positive number" ); } }
void MonsterGenerator::load_special_attacks(mtype *m, JsonObject &jo, std::string member) { m->sp_attack.clear(); // make sure we're running with m->sp_freq.clear(); // everything cleared if (jo.has_array(member)) { JsonArray outer = jo.get_array(member); while (outer.has_more()) { JsonArray inner = outer.next_array(); if ( attack_map.find(inner.get_string(0)) != attack_map.end() ) { m->sp_attack.push_back(attack_map[inner.get_string(0)]); m->sp_freq.push_back(inner.get_int(1)); } else { inner.throw_error("Invalid special_attacks"); } } } if (m->sp_attack.empty()) { m->sp_attack.push_back(attack_map["NONE"]); m->sp_freq.push_back(0); } }
void mtype::load( JsonObject &jo, const std::string &src ) { bool strict = src == "dda"; MonsterGenerator &gen = MonsterGenerator::generator(); // Name and name plural are not translated here, but when needed in // combination with the actual count in `mtype::nname`. mandatory( jo, was_loaded, "name", name ); // default behavior: Assume the regular plural form (appending an āsā) optional( jo, was_loaded, "name_plural", name_plural, name + "s" ); optional( jo, was_loaded, "description", description ); optional( jo, was_loaded, "material", mat, auto_flags_reader<material_id> {} ); optional( jo, was_loaded, "species", species, auto_flags_reader<species_id> {} ); optional( jo, was_loaded, "categories", categories, auto_flags_reader<> {} ); // See monfaction.cpp if( !was_loaded || jo.has_member( "default_faction" ) ) { const auto faction = mfaction_str_id( jo.get_string( "default_faction" ) ); default_faction = monfactions::get_or_add_faction( faction ); } if( !was_loaded || jo.has_member( "symbol" ) ) { sym = jo.get_string( "symbol" ); if( utf8_wrapper( sym ).display_width() != 1 ) { jo.throw_error( "monster symbol should be exactly one console cell width", "symbol" ); } } if( was_loaded && jo.has_member( "copy-from" ) && looks_like.empty() ) { looks_like = jo.get_string( "copy-from" ); } if( jo.has_member( "looks_like" ) ) { looks_like = jo.get_string( "looks_like" ); } assign( jo, "color", color ); const typed_flag_reader<decltype( Creature::size_map )> size_reader{ Creature::size_map, "invalid creature size" }; optional( jo, was_loaded, "size", size, size_reader, MS_MEDIUM ); const typed_flag_reader<decltype( gen.phase_map )> phase_reader{ gen.phase_map, "invalid phase id" }; optional( jo, was_loaded, "phase", phase, phase_reader, SOLID ); assign( jo, "diff", difficulty, strict, 0 ); assign( jo, "hp", hp, strict, 1 ); assign( jo, "speed", speed, strict, 0 ); assign( jo, "aggression", agro, strict, -100, 100 ); assign( jo, "morale", morale, strict ); assign( jo, "attack_cost", attack_cost, strict, 0 ); assign( jo, "melee_skill", melee_skill, strict, 0 ); assign( jo, "melee_dice", melee_dice, strict, 0 ); assign( jo, "melee_dice_sides", melee_sides, strict, 0 ); assign( jo, "dodge", sk_dodge, strict, 0 ); assign( jo, "armor_bash", armor_bash, strict, 0 ); assign( jo, "armor_cut", armor_cut, strict, 0 ); assign( jo, "armor_stab", armor_stab, strict, 0 ); assign( jo, "armor_acid", armor_acid, strict, 0 ); assign( jo, "armor_fire", armor_fire, strict, 0 ); assign( jo, "vision_day", vision_day, strict, 0 ); assign( jo, "vision_night", vision_night, strict, 0 ); optional( jo, was_loaded, "starting_ammo", starting_ammo ); optional( jo, was_loaded, "luminance", luminance, 0 ); optional( jo, was_loaded, "revert_to_itype", revert_to_itype, "" ); optional( jo, was_loaded, "attack_effs", atk_effs, mon_attack_effect_reader{} ); // TODO: make this work with `was_loaded` if( jo.has_array( "melee_damage" ) ) { JsonArray arr = jo.get_array( "melee_damage" ); melee_damage = load_damage_instance( arr ); } else if( jo.has_object( "melee_damage" ) ) { melee_damage = load_damage_instance( jo ); } if( jo.has_int( "melee_cut" ) ) { int bonus_cut = jo.get_int( "melee_cut" ); melee_damage.add_damage( DT_CUT, bonus_cut ); } if( jo.has_member( "death_drops" ) ) { JsonIn &stream = *jo.get_raw( "death_drops" ); death_drops = item_group::load_item_group( stream, "distribution" ); } assign( jo, "harvest", harvest, strict ); const typed_flag_reader<decltype( gen.death_map )> death_reader{ gen.death_map, "invalid monster death function" }; optional( jo, was_loaded, "death_function", dies, death_reader ); if( dies.empty() ) { // TODO: really needed? Is an empty `dies` container not allowed? dies.push_back( mdeath::normal ); } assign( jo, "emit_fields", emit_fields ); if( jo.has_member( "special_when_hit" ) ) { JsonArray jsarr = jo.get_array( "special_when_hit" ); const auto iter = gen.defense_map.find( jsarr.get_string( 0 ) ); if( iter == gen.defense_map.end() ) { jsarr.throw_error( "Invalid monster defense function" ); } sp_defense = iter->second; def_chance = jsarr.get_int( 1 ); } else if( !was_loaded ) { sp_defense = &mdefense::none; def_chance = 0; } if( !was_loaded || jo.has_member( "special_attacks" ) ) { special_attacks.clear(); special_attacks_names.clear(); add_special_attacks( jo, "special_attacks", src ); } else { // Note: special_attacks left as is, new attacks are added to it! // Note: member name prefixes are compatible with those used by generic_typed_reader if( jo.has_object( "extend" ) ) { auto tmp = jo.get_object( "extend" ); add_special_attacks( tmp, "special_attacks", src ); } if( jo.has_object( "delete" ) ) { auto tmp = jo.get_object( "delete" ); remove_special_attacks( tmp, "special_attacks", src ); } } // Disable upgrading when JSON contains `"upgrades": false`, but fallback to the // normal behavior (including error checking) if "upgrades" is not boolean or not `false`. if( jo.has_bool( "upgrades" ) && !jo.get_bool( "upgrades" ) ) { upgrade_group = mongroup_id::NULL_ID(); upgrade_into = mtype_id::NULL_ID(); upgrades = false; } else if( jo.has_member( "upgrades" ) ) { JsonObject up = jo.get_object( "upgrades" ); optional( up, was_loaded, "half_life", half_life, -1 ); optional( up, was_loaded, "age_grow", age_grow, -1 ); optional( up, was_loaded, "into_group", upgrade_group, auto_flags_reader<mongroup_id> {}, mongroup_id::NULL_ID() ); optional( up, was_loaded, "into", upgrade_into, auto_flags_reader<mtype_id> {}, mtype_id::NULL_ID() ); upgrades = true; } //Reproduction if( jo.has_member( "reproduction" ) ) { JsonObject repro = jo.get_object( "reproduction" ); optional( repro, was_loaded, "baby_count", baby_count, -1 ); optional( repro, was_loaded, "baby_timer", baby_timer, -1 ); optional( repro, was_loaded, "baby_monster", baby_monster, auto_flags_reader<mtype_id> {}, mtype_id::NULL_ID() ); optional( repro, was_loaded, "baby_egg", baby_egg, auto_flags_reader<itype_id> {}, "null" ); if( jo.has_member( "baby_flags" ) ) { baby_flags.clear(); JsonArray baby_tags = jo.get_array( "baby_flags" ); while( baby_tags.has_more() ) { baby_flags.push_back( baby_tags.next_string() ); } } reproduces = true; } if( jo.has_member( "biosignature" ) ) { JsonObject biosig = jo.get_object( "biosignature" ); optional( biosig, was_loaded, "biosig_timer", biosig_timer, -1 ); optional( biosig, was_loaded, "biosig_item", biosig_item, auto_flags_reader<itype_id> {}, "null" ); biosignatures = true; } optional( jo, was_loaded, "burn_into", burn_into, auto_flags_reader<mtype_id> {}, mtype_id::NULL_ID() ); const typed_flag_reader<decltype( gen.flag_map )> flag_reader{ gen.flag_map, "invalid monster flag" }; optional( jo, was_loaded, "flags", flags, flag_reader ); // Can't calculate yet - we want all flags first optional( jo, was_loaded, "bash_skill", bash_skill, -1 ); const typed_flag_reader<decltype( gen.trigger_map )> trigger_reader{ gen.trigger_map, "invalid monster trigger" }; optional( jo, was_loaded, "anger_triggers", anger, trigger_reader ); optional( jo, was_loaded, "placate_triggers", placate, trigger_reader ); optional( jo, was_loaded, "fear_triggers", fear, trigger_reader ); if( jo.has_member( "path_settings" ) ) { auto jop = jo.get_object( "path_settings" ); // Here rather than in pathfinding.cpp because we want monster-specific defaults and was_loaded optional( jop, was_loaded, "max_dist", path_settings.max_dist, 0 ); optional( jop, was_loaded, "max_length", path_settings.max_length, -1 ); optional( jop, was_loaded, "bash_strength", path_settings.bash_strength, -1 ); optional( jop, was_loaded, "allow_open_doors", path_settings.allow_open_doors, false ); optional( jop, was_loaded, "avoid_traps", path_settings.avoid_traps, false ); optional( jop, was_loaded, "allow_climb_stairs", path_settings.allow_climb_stairs, true ); } }
void mtype::load( JsonObject &jo ) { MonsterGenerator &gen = MonsterGenerator::generator(); // Name and name plural are not translated here, but when needed in // combination with the actual count in `mtype::nname`. mandatory( jo, was_loaded, "name", name ); // default behaviour: Assume the regular plural form (appending an āsā) optional( jo, was_loaded, "name_plural", name_plural, name + "s" ); mandatory( jo, was_loaded, "description", description, translated_string_reader ); // Have to overwrite the default { "hflesh" } here if( !was_loaded || jo.has_member( "material" ) ) { mat = { jo.get_string( "material" ) }; } optional( jo, was_loaded, "species", species, auto_flags_reader<species_id> {} ); optional( jo, was_loaded, "categories", categories, auto_flags_reader<> {} ); // See monfaction.cpp if( !was_loaded || jo.has_member( "default_faction" ) ) { const auto faction = mfaction_str_id( jo.get_string( "default_faction" ) ); default_faction = monfactions::get_or_add_faction( faction ); } if( !was_loaded || jo.has_member( "symbol" ) ) { sym = jo.get_string( "symbol" ); if( utf8_wrapper( sym ).display_width() != 1 ) { jo.throw_error( "monster symbol should be exactly one console cell width", "symbol" ); } } mandatory( jo, was_loaded, "color", color, color_reader{} ); const typed_flag_reader<decltype( Creature::size_map )> size_reader{ Creature::size_map, "invalid creature size" }; optional( jo, was_loaded, "size", size, size_reader, MS_MEDIUM ); const typed_flag_reader<decltype( gen.phase_map )> phase_reader{ gen.phase_map, "invalid phase id" }; optional( jo, was_loaded, "phase", phase, phase_reader, SOLID ); optional( jo, was_loaded, "diff", difficulty, 0 ); optional( jo, was_loaded, "aggression", agro, 0 ); optional( jo, was_loaded, "morale", morale, 0 ); optional( jo, was_loaded, "speed", speed, 0 ); optional( jo, was_loaded, "attack_cost", attack_cost, 100 ); optional( jo, was_loaded, "melee_skill", melee_skill, 0 ); optional( jo, was_loaded, "melee_dice", melee_dice, 0 ); optional( jo, was_loaded, "melee_dice_sides", melee_sides, 0 ); optional( jo, was_loaded, "melee_cut", melee_cut, 0 ); optional( jo, was_loaded, "dodge", sk_dodge, 0 ); optional( jo, was_loaded, "armor_bash", armor_bash, 0 ); optional( jo, was_loaded, "armor_cut", armor_cut, 0 ); optional( jo, was_loaded, "armor_acid", armor_acid, armor_cut / 2 ); optional( jo, was_loaded, "armor_fire", armor_fire, 0 ); optional( jo, was_loaded, "hp", hp, 0 ); optional( jo, was_loaded, "starting_ammo", starting_ammo ); optional( jo, was_loaded, "luminance", luminance, 0 ); optional( jo, was_loaded, "revert_to_itype", revert_to_itype, "" ); optional( jo, was_loaded, "vision_day", vision_day, 40 ); optional( jo, was_loaded, "vision_night", vision_night, 1 ); optional( jo, was_loaded, "armor_stab", armor_stab, 0.8f * armor_cut ); optional( jo, was_loaded, "attack_effs", atk_effs, mon_attack_effect_reader{} ); if( jo.has_member( "death_drops" ) ) { JsonIn &stream = *jo.get_raw( "death_drops" ); death_drops = item_group::load_item_group( stream, "distribution" ); } const typed_flag_reader<decltype( gen.death_map )> death_reader{ gen.death_map, "invalid monster death function" }; optional( jo, was_loaded, "death_function", dies, death_reader ); if( dies.empty() ) { // TODO: really needed? Is an empty `dies` container not allowed? dies.push_back( mdeath::normal ); } if( jo.has_member( "special_when_hit" ) ) { JsonArray jsarr = jo.get_array( "special_when_hit" ); const auto iter = gen.defense_map.find( jsarr.get_string( 0 ) ); if( iter == gen.defense_map.end() ) { jsarr.throw_error( "Invalid monster defense function" ); } sp_defense = iter->second; def_chance = jsarr.get_int( 1 ); } else if( !was_loaded ) { sp_defense = &mdefense::none; def_chance = 0; } if( !was_loaded || jo.has_member( "special_attacks" ) ) { special_attacks.clear(); special_attacks_names.clear(); add_special_attacks( jo, "special_attacks" ); } else { // Note: special_attacks left as is, new attacks are added to it! // Note: member name prefixes are compatible with those used by generic_typed_reader remove_special_attacks( jo, "remove:special_attacks" ); add_special_attacks( jo, "add:special_attacks" ); } // Disable upgrading when JSON contains `"upgrades": false`, but fallback to the // normal behavior (including error checking) if "upgrades" is not boolean or not `false`. if( jo.has_bool( "upgrades" ) && !jo.get_bool( "upgrades" ) ) { upgrade_group = mongroup_id::NULL_ID; upgrade_into = mtype_id::NULL_ID; upgrades = false; } else if( jo.has_member( "upgrades" ) ) { JsonObject up = jo.get_object( "upgrades" ); optional( up, was_loaded, "half_life", half_life, -1 ); optional( up, was_loaded, "into_group", upgrade_group, auto_flags_reader<mongroup_id> {}, mongroup_id::NULL_ID ); optional( up, was_loaded, "into", upgrade_into, auto_flags_reader<mtype_id> {}, mtype_id::NULL_ID ); upgrades = true; } const typed_flag_reader<decltype( gen.flag_map )> flag_reader{ gen.flag_map, "invalid monster flag" }; optional( jo, was_loaded, "flags", flags, flag_reader ); const typed_flag_reader<decltype( gen.trigger_map )> trigger_reader{ gen.trigger_map, "invalid monster trigger" }; optional( jo, was_loaded, "anger_triggers", anger, trigger_reader ); optional( jo, was_loaded, "placate_triggers", placate, trigger_reader ); optional( jo, was_loaded, "fear_triggers", fear, trigger_reader ); }