void MonsterGenerator::load_monster(JsonObject &jo) { // id std::string mid; if (jo.has_member("id")) { mid = jo.get_string("id"); if (mon_templates.count(mid) > 0) { delete mon_templates[mid]; } mtype *newmon = new mtype; newmon->id = mid; newmon->name = jo.get_string("name").c_str(); if(jo.has_member("name_plural")) { newmon->name_plural = jo.get_string("name_plural"); } else { // default behaviour: Assume the regular plural form (appending an “s”) newmon->name_plural = newmon->name + "s"; } newmon->description = _(jo.get_string("description").c_str()); newmon->mat = jo.get_string("material"); newmon->species = jo.get_tags("species"); newmon->categories = jo.get_tags("categories"); newmon->sym = jo.get_string("symbol"); if( utf8_wrapper( newmon->sym ).display_width() != 1 ) { jo.throw_error( "monster symbol should be exactly one console cell width", "symbol" ); } newmon->color = color_from_string(jo.get_string("color")); newmon->size = get_from_string(jo.get_string("size", "MEDIUM"), size_map, MS_MEDIUM); newmon->phase = get_from_string(jo.get_string("phase", "SOLID"), phase_map, SOLID); newmon->difficulty = jo.get_int("diff", 0); newmon->agro = jo.get_int("aggression", 0); newmon->morale = jo.get_int("morale", 0); newmon->speed = jo.get_int("speed", 0); newmon->melee_skill = jo.get_int("melee_skill", 0); newmon->melee_dice = jo.get_int("melee_dice", 0); newmon->melee_sides = jo.get_int("melee_dice_sides", 0); newmon->melee_cut = jo.get_int("melee_cut", 0); newmon->sk_dodge = jo.get_int("dodge", 0); newmon->armor_bash = jo.get_int("armor_bash", 0); newmon->armor_cut = jo.get_int("armor_cut", 0); newmon->hp = jo.get_int("hp", 0); jo.read("starting_ammo", newmon->starting_ammo); newmon->luminance = jo.get_float("luminance", 0); newmon->revert_to_itype = jo.get_string( "revert_to_itype", "" ); if (jo.has_array("attack_effs")) { JsonArray jsarr = jo.get_array("attack_effs"); while (jsarr.has_more()) { JsonObject e = jsarr.next_object(); mon_effect_data new_eff(e.get_string("id", "null"), e.get_int("duration", 0), body_parts[e.get_string("bp", "NUM_BP")], e.get_bool("permanent", false), e.get_int("chance", 100)); newmon->atk_effs.push_back(new_eff); } } if (jo.has_string("death_drops")) { newmon->death_drops = jo.get_string("death_drops"); } else if (jo.has_object("death_drops")) { JsonObject death_frop_json = jo.get_object("death_drops"); // Make up a group name, should be unique (include the monster id), newmon->death_drops = newmon->id + "_death_drops_auto"; const std::string subtype = death_frop_json.get_string("subtype", "distribution"); // and load the entry as a standard item group using the made up name. item_group::load_item_group(death_frop_json, newmon->death_drops, subtype); } else if (jo.has_member("death_drops")) { jo.throw_error("invalid type, must be string or object", "death_drops"); } newmon->dies = get_death_functions(jo, "death_function"); load_special_defense(newmon, jo, "special_when_hit"); load_special_attacks(newmon, jo, "special_attacks"); std::set<std::string> flags, anger_trig, placate_trig, fear_trig; flags = jo.get_tags("flags"); anger_trig = jo.get_tags("anger_triggers"); placate_trig = jo.get_tags("placate_triggers"); fear_trig = jo.get_tags("fear_triggers"); newmon->flags = get_set_from_tags(flags, flag_map, MF_NULL); newmon->anger = get_set_from_tags(anger_trig, trigger_map, MTRIG_NULL); newmon->fear = get_set_from_tags(fear_trig, trigger_map, MTRIG_NULL); newmon->placate = get_set_from_tags(placate_trig, trigger_map, MTRIG_NULL); mon_templates[mid] = newmon; } }
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 ); // TODO: allow adding/removing specific entries if `was_loaded` is true if( jo.has_array( "attack_effs" ) ) { JsonArray jsarr = jo.get_array( "attack_effs" ); while( jsarr.has_more() ) { JsonObject e = jsarr.next_object(); mon_effect_data new_eff( efftype_id( e.get_string( "id" ) ), e.get_int( "duration", 0 ), get_body_part_token( e.get_string( "bp", "NUM_BP" ) ), e.get_bool( "permanent", false ), e.get_int( "chance", 100 ) ); atk_effs.push_back( new_eff ); } } 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 ); } // TODO: allow overriding/adding/removing those if `was_loaded` is true gen.load_special_defense( this, jo, "special_when_hit" ); gen.load_special_attacks( this, jo, "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 ); }
/** * Generate and refresh output */ void uimenu::show() { if (!started) { setup(); } werase(window); draw_border(window, border_color); if( !title.empty() ) { mvwprintz(window, 0, 1, border_color, "< "); wprintz( window, title_color, title ); wprintz(window, border_color, " >"); } std::string padspaces = std::string(w_width - 2 - pad_left - pad_right, ' '); const int text_lines = textformatted.size(); int estart = 1; if( !textformatted.empty() ) { for ( int i = 0; i < text_lines; i++ ) { trim_and_print( window, 1 + i, 2, getmaxx( window ) - 4, text_color, textformatted[i] ); } mvwputch(window, text_lines + 1, 0, border_color, LINE_XXXO); for ( int i = 1; i < w_width - 1; ++i) { mvwputch(window, text_lines + 1, i, border_color, LINE_OXOX); } mvwputch(window, text_lines + 1, w_width - 1, border_color, LINE_XOXX); estart += text_lines + 1; // +1 for the horizontal line. } calcStartPos( vshift, fselected, vmax, fentries.size() ); for ( int fei = vshift, si = 0; si < vmax; fei++, si++ ) { if ( fei < (int)fentries.size() ) { int ei = fentries [ fei ]; nc_color co = ( ei == selected ? hilight_color : ( entries[ ei ].enabled || entries[ei].force_color ? entries[ ei ].text_color : disabled_color ) ); if ( hilight_full ) { mvwprintz( window, estart + si, pad_left + 1, co, padspaces ); } if( entries[ ei ].hotkey >= 33 && entries[ ei ].hotkey < 126 ) { const nc_color hotkey_co = ei == selected ? hilight_color : hotkey_color; mvwprintz( window, estart + si, pad_left + 2, entries[ ei ].enabled ? hotkey_co : co, "%c", entries[ ei ].hotkey ); } if( padspaces.size() > 3 ) { // padspaces's length indicates the maximal width of the entry, it is used above to // activate the highlighting, it is used to override previous text there, but in both // cases printing starts at pad_left+1, here it starts at pad_left+4, so 3 cells less // to be used. const auto entry = utf8_wrapper( ei == selected ? remove_color_tags( entries[ ei ].txt ) : entries[ ei ].txt ); trim_and_print( window, estart + si, pad_left + 4, max_entry_len, co, "%s", entry.c_str() ); if( max_column_len && !entries[ ei ].ctxt.empty() ) { const auto centry = utf8_wrapper( ei == selected ? remove_color_tags( entries[ ei ].ctxt ) : entries[ ei ].ctxt ); trim_and_print( window, estart + si, getmaxx( window ) - max_column_len - 2, max_column_len, co, "%s", centry.c_str() ); } } mvwzstr menu_entry_extra_text = entries[ei].extratxt; if ( !menu_entry_extra_text.txt.empty() ) { mvwprintz( window, estart + si, pad_left + 1 + menu_entry_extra_text.left, menu_entry_extra_text.color, menu_entry_extra_text.txt ); } if ( menu_entry_extra_text.sym != 0 ) { mvwputch ( window, estart + si, pad_left + 1 + menu_entry_extra_text.left, menu_entry_extra_text.color, menu_entry_extra_text.sym ); } if ( callback != NULL && ei == selected ) { callback->select(ei, this); } } else { mvwprintz( window, estart + si, pad_left + 1, c_light_gray, padspaces ); } } if ( desc_enabled ) { // draw border mvwputch(window, w_height - desc_lines - 2, 0, border_color, LINE_XXXO); for ( int i = 1; i < w_width - 1; ++i) { mvwputch(window, w_height - desc_lines - 2, i, border_color, LINE_OXOX); } mvwputch(window, w_height - desc_lines - 2, w_width - 1, border_color, LINE_XOXX); // clear previous desc the ugly way for ( int y = desc_lines + 1; y > 1; --y ) { for ( int x = 2; x < w_width - 2; ++x) { mvwputch(window, w_height - y, x, text_color, " "); } } if( static_cast<size_t>( selected ) < entries.size() ){ fold_and_print( window, w_height - desc_lines - 1, 2, w_width - 4, text_color, entries[selected].desc ); } } if ( !filter.empty() ) { mvwprintz( window, w_height - 1, 2, border_color, "< %s >", filter.c_str() ); mvwprintz( window, w_height - 1, 4, text_color, filter ); } apply_scrollbar(); this->refresh(true); }
void MonsterGenerator::load_monster(JsonObject &jo) { const mtype_id mid = mtype_id( jo.get_string("id") ); if (mon_templates.count(mid) > 0) { delete mon_templates[mid]; } mtype *newmon = new mtype; newmon->id = mid; newmon->name = jo.get_string("name").c_str(); if(jo.has_member("name_plural")) { newmon->name_plural = jo.get_string("name_plural"); } else { // default behaviour: Assume the regular plural form (appending an “s”) newmon->name_plural = newmon->name + "s"; } newmon->description = _(jo.get_string("description").c_str()); // Have to overwrite the default { "hflesh" } here newmon->mat = { jo.get_string("material") }; for( auto &s : jo.get_tags( "species" ) ) { newmon->species.insert( species_id( s ) ); } newmon->categories = jo.get_tags("categories"); // See monfaction.cpp newmon->default_faction = monfactions::get_or_add_faction( mfaction_str_id( jo.get_string("default_faction") ) ); newmon->sym = jo.get_string("symbol"); if( utf8_wrapper( newmon->sym ).display_width() != 1 ) { jo.throw_error( "monster symbol should be exactly one console cell width", "symbol" ); } newmon->color = color_from_string(jo.get_string("color")); newmon->size = get_from_string(jo.get_string("size", "MEDIUM"), Creature::size_map, MS_MEDIUM); newmon->phase = get_from_string(jo.get_string("phase", "SOLID"), phase_map, SOLID); newmon->difficulty = jo.get_int("diff", 0); newmon->agro = jo.get_int("aggression", 0); newmon->morale = jo.get_int("morale", 0); newmon->speed = jo.get_int("speed", 0); newmon->attack_cost = jo.get_int("attack_cost", 100); newmon->melee_skill = jo.get_int("melee_skill", 0); newmon->melee_dice = jo.get_int("melee_dice", 0); newmon->melee_sides = jo.get_int("melee_dice_sides", 0); newmon->melee_cut = jo.get_int("melee_cut", 0); newmon->sk_dodge = jo.get_int("dodge", 0); newmon->armor_bash = jo.get_int("armor_bash", 0); newmon->armor_cut = jo.get_int("armor_cut", 0); newmon->hp = jo.get_int("hp", 0); jo.read("starting_ammo", newmon->starting_ammo); newmon->luminance = jo.get_float("luminance", 0); newmon->revert_to_itype = jo.get_string( "revert_to_itype", "" ); newmon->vision_day = jo.get_int("vision_day", 40); newmon->vision_night = jo.get_int("vision_night", 1); if (jo.has_array("attack_effs")) { JsonArray jsarr = jo.get_array("attack_effs"); while (jsarr.has_more()) { JsonObject e = jsarr.next_object(); mon_effect_data new_eff(e.get_string("id", "null"), e.get_int("duration", 0), get_body_part_token( e.get_string("bp", "NUM_BP") ), e.get_bool("permanent", false), e.get_int("chance", 100)); newmon->atk_effs.push_back(new_eff); } } if( jo.has_member( "death_drops" ) ) { JsonIn& stream = *jo.get_raw( "death_drops" ); newmon->death_drops = item_group::load_item_group( stream, "distribution" ); } newmon->dies = get_death_functions(jo, "death_function"); load_special_defense(newmon, jo, "special_when_hit"); load_special_attacks(newmon, jo, "special_attacks"); if (jo.has_member("upgrades")) { JsonObject upgrades = jo.get_object("upgrades"); newmon->half_life = upgrades.get_int("half_life", -1); newmon->upgrade_group = mongroup_id( upgrades.get_string("into_group", mongroup_id::NULL_ID.str() ) ); newmon->upgrade_into = mtype_id( upgrades.get_string("into", mtype_id::NULL_ID.str() ) ); newmon->upgrades = true; } std::set<std::string> flags, anger_trig, placate_trig, fear_trig; flags = jo.get_tags("flags"); anger_trig = jo.get_tags("anger_triggers"); placate_trig = jo.get_tags("placate_triggers"); fear_trig = jo.get_tags("fear_triggers"); newmon->flags = get_set_from_tags(flags, flag_map, MF_NULL); newmon->anger = get_set_from_tags(anger_trig, trigger_map, MTRIG_NULL); newmon->fear = get_set_from_tags(fear_trig, trigger_map, MTRIG_NULL); newmon->placate = get_set_from_tags(placate_trig, trigger_map, MTRIG_NULL); mon_templates[mid] = newmon; }
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 ); 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" ); } } 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, "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{} ); // 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" ); } 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; } 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 ); 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 ); }
// Pick up items at (posx, posy). void Pickup::pick_up(int posx, int posy, int min) { int veh_root_part = 0; int cargo_part = -1; vehicle *veh = g->m.veh_at (posx, posy, veh_root_part); bool from_vehicle = false; if( min != -1 ) { cargo_part = interact_with_vehicle( veh, posx, posy, veh_root_part ); from_vehicle = cargo_part >= 0; if( cargo_part == -2 ) { // -2 indicates that we already interacted with the vehicle. return; } } if (g->m.has_flag("SEALED", posx, posy)) { return; } //min == -1 is Autopickup if (!g->u.can_pickup(min != -1)) { // no message on autopickup (-1) return; } if( !from_vehicle ) { bool isEmpty = (g->m.i_at(posx, posy).empty()); // Hide the pickup window if this is a toilet and there's nothing here // but water. if ((!isEmpty) && g->m.furn(posx, posy) == f_toilet) { isEmpty = true; for (size_t i = 0; isEmpty && i < g->m.i_at(posx, posy).size(); i++) { if (g->m.i_at(posx, posy)[i].typeId() != "water") { isEmpty = false; } } } if (isEmpty && (min != -1 || !OPTIONS["AUTO_PICKUP_ADJACENT"] )) { return; } } // which items are we grabbing? std::vector<item> here = (from_vehicle) ? veh->parts[cargo_part].items : g->m.i_at(posx, posy); if (min == -1) { if (g->checkZone("NO_AUTO_PICKUP", posx, posy)) { here.clear(); } // Recursively pick up adjacent items if that option is on. if( OPTIONS["AUTO_PICKUP_ADJACENT"] && g->u.posx == posx && g->u.posy == posy ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { point apos = direction_XY( elem ); apos.x += posx; apos.y += posy; if( g->m.has_flag( "SEALED", apos.x, apos.y ) ) { continue; } if( g->checkZone( "NO_AUTO_PICKUP", apos.x, apos.y ) ) { continue; } pick_up( apos.x, apos.y, min ); } } } // Not many items, just grab them if ((int)here.size() <= min && min != -1) { g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = point( posx - g->u.xpos(), posy - g->u.ypos() ); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } if(min != -1) { // don't bother if we're just autopickup-ing g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy(g->w_messages) - 3; int itemsH = std::min(25, TERMY / 2); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if(maxmaxitems > TERMY - minleftover) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<bool> getitem; getitem.resize(here.size(), false); int maxitems = here.size(); maxitems = (maxitems < minmaxitems ? minmaxitems : (maxitems > maxmaxitems ? maxmaxitems : maxitems )); int itemcount = 0; std::map<int, unsigned int> pickup_count; // Count of how many we'll pick up from each stack if (min == -1) { //Auto Pickup, select matching items if( !select_autopickup_items( here, getitem) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx(g->w_messages); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx(g->w_messages); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; WINDOW *w_pickup = newwin(pickupH, pickupW, pickupY, pickupX); WINDOW *w_item_info = newwin(itemsH, itemsW, itemsY, itemsX); WINDOW_PTR w_pickupptr( w_pickup ); WINDOW_PTR w_item_infoptr( w_item_info ); int ch = ' '; int start = 0, cur_it; int new_weight = g->u.weight_carried(), new_volume = g->u.volume_carried(); bool update = true; mvwprintw(w_pickup, 0, 0, _("PICK UP")); int selected = 0; int last_selected = -1; if(g->was_fullscreen) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { static const std::string pickup_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;"; int idx = -1; for (int i = 1; i < pickupH; i++) { mvwprintw(w_pickup, i, 0, " "); } if (ch >= '0' && ch <= '9') { ch = (char)ch - '0'; itemcount *= 10; itemcount += ch; } else if ((ch == '<' || ch == KEY_PPAGE) && start > 0) { start -= maxitems; selected = start; mvwprintw(w_pickup, maxitems + 2, 0, " "); } else if ((ch == '>' || ch == KEY_NPAGE) && start + maxitems < (int)here.size()) { start += maxitems; selected = start; mvwprintw(w_pickup, maxitems + 2, pickupH, " "); } else if ( ch == KEY_UP ) { selected--; if ( selected < 0 ) { selected = here.size() - 1; start = (int)( here.size() / maxitems ) * maxitems; if (start >= (int)here.size()) { start -= maxitems; } } else if ( selected < start ) { start -= maxitems; } } else if ( ch == KEY_DOWN ) { selected++; if ( selected >= (int)here.size() ) { selected = 0; start = 0; } else if ( selected >= start + maxitems ) { start += maxitems; } } else if ( selected >= 0 && ( ( ch == KEY_RIGHT && !getitem[selected]) || ( ch == KEY_LEFT && getitem[selected] ) ) ) { idx = selected; } else if ( ch == '`' ) { std::string ext = string_input_popup( _("Enter 2 letters (case sensitive):"), 3, "", "", "", 2); if(ext.size() == 2) { int p1 = pickup_chars.find(ext.at(0)); int p2 = pickup_chars.find(ext.at(1)); if ( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else { idx = pickup_chars.find(ch); } if( idx >= 0 && idx < (int)here.size()) { if( getitem[idx] ) { if( pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges ) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight -= temp.weight(); new_volume -= temp.volume(); } else { new_weight -= here[idx].weight(); new_volume -= here[idx].volume(); } } if (itemcount != 0 || pickup_count[idx] == 0) { if (itemcount >= here[idx].charges || !here[idx].count_by_charges()) { // Ignore the count if we pickup the whole stack anyway // or something that is not counted by charges (tools) itemcount = 0; } pickup_count[idx] = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[idx] = ( ch == KEY_RIGHT ? true : ( ch == KEY_LEFT ? false : !getitem[idx] ) ); if ( ch != KEY_RIGHT && ch != KEY_LEFT) { selected = idx; start = (int)( idx / maxitems ) * maxitems; } if (getitem[idx]) { if (pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight += temp.weight(); new_volume += temp.volume(); } else { new_weight += here[idx].weight(); new_volume += here[idx].volume(); } } else { pickup_count[idx] = 0; } update = true; } if ( selected != last_selected ) { last_selected = selected; werase(w_item_info); if ( selected >= 0 && selected <= (int)here.size() - 1 ) { std::vector<iteminfo> vThisItem, vDummy; here[selected].info(true, &vThisItem); draw_item_info(w_item_info, "", vThisItem, vDummy, 0, true, true); } draw_border(w_item_info); const auto name = utf8_wrapper( here[selected].display_name() ).shorten( itemsW - 8 ); mvwprintz(w_item_info, 0, 2, c_white, "< %s >", name.c_str()); wrefresh(w_item_info); } if (ch == ',') { int count = 0; for (size_t i = 0; i < here.size(); i++) { if (getitem[i]) { count++; } else { new_weight += here[i].weight(); new_volume += here[i].volume(); } getitem[i] = true; } if (count == (int)here.size()) { for (size_t i = 0; i < here.size(); i++) { getitem[i] = false; } new_weight = g->u.weight_carried(); new_volume = g->u.volume_carried(); } update = true; } for (cur_it = start; cur_it < start + maxitems; cur_it++) { mvwprintw(w_pickup, 1 + (cur_it % maxitems), 0, " "); if (cur_it < (int)here.size()) { nc_color icolor = here[cur_it].color(&g->u); if (cur_it == selected) { icolor = hilite(icolor); } if (cur_it < (int)pickup_chars.size() ) { mvwputch(w_pickup, 1 + (cur_it % maxitems), 0, icolor, char(pickup_chars[cur_it])); } else { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz(w_pickup, 1 + (cur_it % maxitems), 0, icolor, "`%c%c", char(pickup_chars[p1]), char(pickup_chars[p2])); } if (getitem[cur_it]) { if (pickup_count[cur_it] == 0) { wprintz(w_pickup, c_ltblue, " + "); } else { wprintz(w_pickup, c_ltblue, " # "); } } else { wprintw(w_pickup, " - "); } wprintz(w_pickup, icolor, "%s", here[cur_it].display_name().c_str()); } } int pw = pickupW; const char *unmark = _("[left] Unmark"); const char *scroll = _("[up/dn] Scroll"); const char *mark = _("[right] Mark"); mvwprintw(w_pickup, maxitems + 1, 0, unmark); mvwprintw(w_pickup, maxitems + 1, (pw - strlen(scroll)) / 2, scroll); mvwprintw(w_pickup, maxitems + 1, pw - strlen(mark), mark); const char *prev = _("[pgup] Prev"); const char *all = _("[,] All"); const char *next = _("[pgdn] Next"); if (start > 0) { mvwprintw(w_pickup, maxitems + 2, 0, prev); } mvwprintw(w_pickup, maxitems + 2, (pw - strlen(all)) / 2, all); if (cur_it < (int)here.size()) { mvwprintw(w_pickup, maxitems + 2, pw - strlen(next), next); } if (update) { // Update weight & volume information update = false; for (int i = 9; i < pickupW; ++i) { mvwaddch(w_pickup, 0, i, ' '); } mvwprintz(w_pickup, 0, 9, (new_weight >= g->u.weight_capacity() ? c_red : c_white), _("Wgt %.1f"), g->u.convert_weight(new_weight)); wprintz(w_pickup, c_white, "/%.1f", g->u.convert_weight(g->u.weight_capacity())); mvwprintz(w_pickup, 0, 24, (new_volume > g->u.volume_capacity() - 2 ? c_red : c_white), _("Vol %d"), new_volume); wprintz(w_pickup, c_white, "/%d", g->u.volume_capacity() - 2); } wrefresh(w_pickup); ch = (int)getch(); } while (ch != ' ' && ch != '\n' && ch != KEY_ESCAPE); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection ) { item_selected = true; } } if( ch != '\n' || !item_selected ) { w_pickupptr.reset(); w_item_infoptr.reset(); add_msg(_("Never mind.")); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = point( posx - g->u.xpos(), posy - g->u.ypos() ); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } for (size_t i = 0; i < here.size(); i++) { if( getitem[i] ) { g->u.activity.values.push_back( i ); g->u.activity.values.push_back( pickup_count[i] ); } } g->reenter_fullscreen(); }
/* * Generate and refresh output */ void uimenu::show() { if (!started) { setup(); } std::string padspaces = std::string(w_width - 2 - pad_left - pad_right, ' '); const int text_lines = textformatted.size(); for ( int i = 0; i < text_lines; i++ ) { trim_and_print(window, 1 + i, 2, getmaxx(window) - 4, text_color, "%s", textformatted[i].c_str()); } mvwputch(window, text_lines + 1, 0, border_color, LINE_XXXO); for ( int i = 1; i < w_width - 1; ++i) { mvwputch(window, text_lines + 1, i, border_color, LINE_OXOX); } mvwputch(window, text_lines + 1, w_width - 1, border_color, LINE_XOXX); int estart = text_lines + 2; calcStartPos( vshift, fselected, vmax, fentries.size() ); for ( int fei = vshift, si = 0; si < vmax; fei++, si++ ) { if ( fei < (int)fentries.size() ) { int ei = fentries [ fei ]; nc_color co = ( ei == selected ? hilight_color : ( entries[ ei ].enabled ? entries[ ei ].text_color : disabled_color ) ); if ( hilight_full ) { mvwprintz(window, estart + si, pad_left + 1, co , "%s", padspaces.c_str()); } if(entries[ ei ].enabled && entries[ ei ].hotkey >= 33 && entries[ ei ].hotkey < 126 ) { mvwprintz( window, estart + si, pad_left + 2, ( ei == selected ) ? hilight_color : hotkey_color , "%c", entries[ ei ].hotkey ); } if( padspaces.size() > 3 ) { // padspaces's length indicates the maximal width of the entry, it is used above to // activate the highlighting, it is used to override previous text there, but in both // cases printeing starts at pad_left+1, here it starts at pad_left+4, so 3 cells less // to be used. const auto entry = utf8_wrapper( entries[ ei ].txt ); trim_and_print( window, estart + si, pad_left + 4, w_width - 2 - pad_left - pad_right, co, "%s", entry.c_str() ); } if ( !entries[ei].extratxt.txt.empty() ) { mvwprintz( window, estart + si, pad_left + 1 + entries[ ei ].extratxt.left, entries[ ei ].extratxt.color, "%s", entries[ ei ].extratxt.txt.c_str() ); } if ( entries[ei].extratxt.sym != 0 ) { mvwputch ( window, estart + si, pad_left + 1 + entries[ ei ].extratxt.left, entries[ ei ].extratxt.color, entries[ ei ].extratxt.sym ); } if ( callback != NULL && ei == selected ) { callback->select(ei, this); } } else { mvwprintz(window, estart + si, pad_left + 1, c_ltgray , "%s", padspaces.c_str()); } } if ( desc_enabled ) { // draw border mvwputch(window, w_height - desc_lines - 2, 0, border_color, LINE_XXXO); for ( int i = 1; i < w_width - 1; ++i) { mvwputch(window, w_height - desc_lines - 2, i, border_color, LINE_OXOX); } mvwputch(window, w_height - desc_lines - 2, w_width - 1, border_color, LINE_XOXX); // clear previous desc the ugly way for ( int y = desc_lines + 1; y > 1; --y ) { for ( int x = 2; x < w_width - 2; ++x) { mvwputch(window, w_height - y, x, text_color, " "); } } // draw description fold_and_print(window, w_height - desc_lines - 1, 2, w_width - 4, text_color, entries[selected].desc.c_str()); } if ( !filter.empty() ) { mvwprintz( window, w_height - 1, 2, border_color, "< %s >", filter.c_str() ); mvwprintz( window, w_height - 1, 4, text_color, "%s", filter.c_str() ); } apply_scrollbar(); this->refresh(true); }