void MonsterGroupManager::LoadMonsterGroup(JsonObject &jo) { MonsterGroup g; g.name = mongroup_id( jo.get_string("name") ); g.defaultMonster = mtype_id( jo.get_string("default") ); if (jo.has_array("monsters")) { JsonArray monarr = jo.get_array("monsters"); while (monarr.has_more()) { JsonObject mon = monarr.next_object(); const mtype_id name = mtype_id( mon.get_string("monster") ); int freq = mon.get_int("freq"); int cost = mon.get_int("cost_multiplier"); int pack_min = 1; int pack_max = 1; if(mon.has_member("pack_size")) { JsonArray packarr = mon.get_array("pack_size"); pack_min = packarr.next_int(); pack_max = packarr.next_int(); } int starts = 0; int ends = 0; if(mon.has_member("starts")) { if (ACTIVE_WORLD_OPTIONS["MONSTER_UPGRADE_FACTOR"] > 0) { starts = mon.get_int("starts") * ACTIVE_WORLD_OPTIONS["MONSTER_UPGRADE_FACTOR"]; } else { // Default value if the monster upgrade factor is set to 0.0 - off starts = mon.get_int("starts"); } } if(mon.has_member("ends")) { if (ACTIVE_WORLD_OPTIONS["MONSTER_UPGRADE_FACTOR"] > 0) { ends = mon.get_int("ends") * ACTIVE_WORLD_OPTIONS["MONSTER_UPGRADE_FACTOR"]; } else { // Default value if the monster upgrade factor is set to 0.0 - off ends = mon.get_int("ends"); } } MonsterGroupEntry new_mon_group = MonsterGroupEntry(name, freq, cost, pack_min, pack_max, starts, ends); if(mon.has_member("conditions")) { JsonArray conditions_arr = mon.get_array("conditions"); while(conditions_arr.has_more()) { new_mon_group.conditions.push_back(conditions_arr.next_string()); } } g.monsters.push_back(new_mon_group); } } g.replace_monster_group = jo.get_bool("replace_monster_group", false); g.new_monster_group = mongroup_id( jo.get_string("new_monster_group_id", mongroup_id::NULL_ID.str() ) ); g.monster_group_time = jo.get_int("replacement_time", 0); g.is_safe = jo.get_bool( "is_safe", false ); monsterGroupMap[g.name] = g; }
void MonsterGroupManager::FinalizeMonsterGroups() { const MonsterGenerator &gen = MonsterGenerator::generator(); for( auto &mtid : monster_whitelist ) { if( !gen.has_mtype( mtype_id( mtid ) ) ) { debugmsg( "monster on whitelist %s does not exist", mtid.c_str() ); } } for( auto &mtid : monster_blacklist ) { if( !gen.has_mtype( mtype_id( mtid ) ) ) { debugmsg( "monster on blacklist %s does not exist", mtid.c_str() ); } } for( auto &elem : monsterGroupMap ) { MonsterGroup &mg = elem.second; for(FreqDef::iterator c = mg.monsters.begin(); c != mg.monsters.end(); ) { if(MonsterGroupManager::monster_is_blacklisted( c->name )) { c = mg.monsters.erase(c); } else { ++c; } } if(MonsterGroupManager::monster_is_blacklisted( mg.defaultMonster )) { mg.defaultMonster = NULL_ID; } } }
int turns_to_destination( const std::string &monster_type, const tripoint &start, const tripoint &end ) { monster temp_monster( mtype_id(monster_type), start); // Bypassing game::add_zombie() since it sometimes upgrades the monster instantly. g->critter_tracker->add( temp_monster ); monster &test_monster = g->critter_tracker->find( 0 ); test_monster.set_dest( end ); // Get it riled up. test_monster.anger = 100; test_monster.set_moves( 0 ); const int monster_speed = test_monster.get_speed(); int moves_spent = 0; for( int turn = 0; turn < 1000; ++turn ) { test_monster.mod_moves( monster_speed ); while( test_monster.moves >= 0 ) { int moves_before = test_monster.moves; test_monster.move(); moves_spent += moves_before - test_monster.moves; if( test_monster.pos() == test_monster.move_target() ) { g->remove_zombie( 0 ); return moves_spent; } } } g->remove_zombie( 0 ); // Return an unreasonably high number. return 100000; }
static monster &spawn_test_monster( const std::string &monster_type, const tripoint &start ) { monster temp_monster( mtype_id(monster_type), start); // Bypassing game::add_zombie() since it sometimes upgrades the monster instantly. g->critter_tracker->add( temp_monster ); return g->critter_tracker->find( 0 ); }
item Single_item_creator::create_single(int birthday, RecursionList &rec) const { item tmp; if (type == S_ITEM) { if (id == "corpse") { tmp.make_corpse( mtype_id( "mon_null" ), birthday ); } else { tmp = item(id, birthday); } } else if (type == S_ITEM_GROUP) { if (std::find(rec.begin(), rec.end(), id) != rec.end()) { debugmsg("recursion in item spawn list %s", id.c_str()); return item(null_item_id, birthday); } rec.push_back(id); Item_spawn_data *isd = item_controller->get_group(id); if (isd == NULL) { debugmsg("unknown item spawn list %s", id.c_str()); return item(null_item_id, birthday); } tmp = isd->create_single(birthday, rec); rec.erase( rec.end() - 1 ); } else if (type == S_NONE) { return item(null_item_id, birthday); } if( one_in( 3 ) && tmp.has_flag( "VARSIZE" ) ) { tmp.item_tags.insert( "FIT" ); } if (modifier.get() != NULL) { modifier->modify(tmp); } // TODO: change the spawn lists to contain proper references to containers tmp = tmp.in_its_container(); return tmp; }
void MonsterGroupManager::FinalizeMonsterGroups() { for( auto &mtid : monster_whitelist ) { if( !mtype_id( mtid ).is_valid() ) { debugmsg( "monster on whitelist %s does not exist", mtid.c_str() ); } } for( auto &mtid : monster_blacklist ) { if( !mtype_id( mtid ).is_valid() ) { debugmsg( "monster on blacklist %s does not exist", mtid.c_str() ); } } // If we have the classic zombies option, remove non-conforming monsters if( get_option<bool>( "CLASSIC_ZOMBIES" ) ) { for( auto &elem : monsterGroupMap ) { MonsterGroup &mg = elem.second; for( FreqDef::iterator c = mg.monsters.begin(); c != mg.monsters.end(); ) { // Test mon const mtype &mt = c->name.obj(); if( !( mt.in_category( "CLASSIC" ) || mt.in_category( "WILDLIFE" ) ) ) { c = mg.monsters.erase( c ); } else { ++c; } } const mtype &mt = mg.defaultMonster.obj(); if( !( mt.in_category( "CLASSIC" ) || mt.in_category( "WILDLIFE" ) ) ) { mg.defaultMonster = mtype_id::NULL_ID(); } } } // Further, remove all blacklisted monsters for( auto &elem : monsterGroupMap ) { MonsterGroup &mg = elem.second; for( FreqDef::iterator c = mg.monsters.begin(); c != mg.monsters.end(); ) { if( MonsterGroupManager::monster_is_blacklisted( c->name ) ) { c = mg.monsters.erase( c ); } else { ++c; } } if( MonsterGroupManager::monster_is_blacklisted( mg.defaultMonster ) ) { mg.defaultMonster = mtype_id::NULL_ID(); } } }
// Verify that the named monster has the expected effective speed, which is greatly reduced // due to wasted motion from shambling. // This is an assertion that an average (i.e. no fleet) survivor with no encumbrance // will be able to out-walk (not run, walk) the given monster // if their speed is higher than the monster's speed stat. void check_shamble_speed( const std::string monster_type, const tripoint &destination ) { // Scale the scaling factor based on the ratio of diagonal to cardinal steps. const float slope = (destination.x < destination.y) ? (destination.x / destination.y) : (destination.y / destination.x); const float diagonal_multiplier = 1.0 + (OPTIONS["CIRCLEDIST"] ? (slope * 0.41) : 0.0); const float mon_speed = (float)monster( mtype_id( monster_type ) ).get_speed(); INFO( monster_type << " " << destination ); // Wandering makes things nondeterministic, so look at the distribution rather than a target number. statistics move_stats; for( int i = 0; i < 10; ++i ) { move_stats.add( turns_to_destination( monster_type, {0, 0, 0}, destination ) ); if( ((move_stats.avg() * mon_speed) / (10000.0 * diagonal_multiplier)) == Approx(1.0).epsilon(0.04) ) { break; } } CHECK( ((move_stats.avg() * mon_speed) / (10000.0 * diagonal_multiplier)) == Approx(1.0).epsilon(0.04) ); }
void mission_type::load( JsonObject &jo, const std::string &src ) { const bool strict = src == "dda"; mandatory( jo, was_loaded, "name", name ); mandatory( jo, was_loaded, "difficulty", difficulty ); mandatory( jo, was_loaded, "value", value ); if( jo.has_member( "origins" ) ) { origins.clear(); for( auto &m : jo.get_tags( "origins" ) ) { origins.emplace_back( io::string_to_enum_look_up( io::origin_map, m ) ); } } if( std::any_of( origins.begin(), origins.end(), []( mission_origin origin ) { return origin == ORIGIN_ANY_NPC || origin == ORIGIN_OPENER_NPC || origin == ORIGIN_SECONDARY; } ) ) { auto djo = jo.get_object( "dialogue" ); // TODO: There should be a cleaner way to do it mandatory( djo, was_loaded, "describe", dialogue[ "describe" ] ); mandatory( djo, was_loaded, "offer", dialogue[ "offer" ] ); mandatory( djo, was_loaded, "accepted", dialogue[ "accepted" ] ); mandatory( djo, was_loaded, "rejected", dialogue[ "rejected" ] ); mandatory( djo, was_loaded, "advice", dialogue[ "advice" ] ); mandatory( djo, was_loaded, "inquire", dialogue[ "inquire" ] ); mandatory( djo, was_loaded, "success", dialogue[ "success" ] ); mandatory( djo, was_loaded, "success_lie", dialogue[ "success_lie" ] ); mandatory( djo, was_loaded, "failure", dialogue[ "failure" ] ); } optional( jo, was_loaded, "urgent", urgent ); optional( jo, was_loaded, "item", item_id ); optional( jo, was_loaded, "count", item_count, 1 ); goal = jo.get_enum_value<decltype( goal )>( "goal" ); assign_function( jo, "place", place, tripoint_function_map ); if( jo.has_string( "start" ) ) { assign_function( jo, "start", start, mission_function_map ); } else if( jo.has_member( "start" ) ) { JsonObject j_start = jo.get_object( "start" ); parse_start( j_start ); } assign_function( jo, "end", end, mission_function_map ); assign_function( jo, "fail", fail, mission_function_map ); assign( jo, "deadline_low", deadline_low, false, 1_days ); assign( jo, "deadline_high", deadline_high, false, 1_days ); if( jo.has_member( "followup" ) ) { follow_up = mission_type_id( jo.get_string( "followup" ) ); } if( jo.has_member( "monster_species" ) ) { monster_species = species_id( jo.get_string( "monster_species" ) ); } if( jo.has_member( "monster_type" ) ) { monster_type = mtype_id( jo.get_string( "monster_type" ) ); } if( jo.has_member( "monster_kill_goal" ) ) { monster_kill_goal = jo.get_int( "monster_kill_goal" ); } assign( jo, "destination", target_id, strict ); }
void check_test_overmap_data( const overmap &test_map ) { // Spot-check a bunch of terrain values. // Bottom level, "L 0" in the save REQUIRE(test_map.get_ter(0, 0, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(47, 3, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(48, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(49, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(50, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(51, 3, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 4, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(46, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(47, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(48, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(49, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(50, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(51, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(52, 4, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(179, 179, -10).id() == "empty_rock"); // Level -9, "L 1" in the save REQUIRE(test_map.get_ter(0, 0, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(44, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(46, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(47, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(48, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(49, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(50, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(43, 2, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(44, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(45, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(46, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(47, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(48, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(49, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(50, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(51, 2, -9).id() == "empty_rock"); // Level -3, "L 7" in save REQUIRE(test_map.get_ter(0, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(156, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(157, 0, -3).id() == "temple_stairs"); REQUIRE(test_map.get_ter(158, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(46, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(47, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(48, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(49, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(50, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(133, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(134, 5, -3).id() == "mine"); REQUIRE(test_map.get_ter(135, 5, -3).id() == "empty_rock"); // Ground level REQUIRE(test_map.get_ter(0, 0, 0).id() == "field"); REQUIRE(test_map.get_ter(23, 0, 0).id() == "field"); REQUIRE(test_map.get_ter(24, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(25, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(26, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(27, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(28, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(29, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(30, 0, 0).id() == "forest"); // Sky REQUIRE(test_map.get_ter(0, 0, 1).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 1).id() == "open_air"); REQUIRE(test_map.get_ter(0, 0, 2).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 2).id() == "open_air"); REQUIRE(test_map.get_ter(0, 0, 10).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 10).id() == "open_air"); // Spot-check a few of the monster groups. std::vector<mongroup> expected_groups{ {"GROUP_ANT", {0, 0, -1}, 1, 3, {0, 0, 0}, 0, false, false, false}, {"GROUP_TRIFFID", {0, 132, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {0, 189, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI", {0, 288, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 0, -1}, 1, 3, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 0, 0}, 1, 2, {0, 0, 0}, 0, false, false, false}, {"GROUP_TRIFFID", {1, 137, 0}, 1, 2, {0, 0, 0}, 0, false, false, false}, {"GROUP_WORM", {2, 67, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI_FLOWERS", {2, 150, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI_FLOWERS", {5, 150, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {6, 365, -1}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 8, -2}, 1, 2, {100, 50, 0}, 0, true, false, false}, {"GROUP_GOO", {2, 9, -1}, 1, 4, {25, 75, 0}, 10, false, true, false}, {"GROUP_BEE", {3, 10, 0}, 1, 6, {92, 64, 0}, 20, false, false, true}, {"GROUP_CHUD", {4, 11, -2}, 1, 8, {88, 55, 0}, 30, true, true, false}, {"GROUP_SPIRAL", {5, 12, -1}, 1, 10, {62, 47, 0}, 40, false, true, true}, {"GROUP_RIVER", {6, 13, 0}, 1, 12, {94, 72, 0}, 50, true, false, true}, {"GROUP_SWAMP", {7, 14, -2}, 1, 14, {37, 85, 0}, 60, true, true, true} }; for( auto group : expected_groups ) { REQUIRE(test_map.mongroup_check(group)); } // Only a few cities, so check them all. std::vector<city> expected_cities {{145, 53, 9},{24,60,7},{90,114,2},{108,129,9},{83,26,10}, {140,89,2},{71,33,2},{67,111,2},{97,144,9},{96,166,2}}; REQUIRE(test_map.cities.size() == expected_cities.size()); for( const auto &candidate_city : test_map.cities ) { REQUIRE(std::find(expected_cities.begin(), expected_cities.end(), candidate_city) != expected_cities.end() ); } // Check all the roads too. // Roads are getting size set to 0, but I expect -1. std::vector<city> expected_roads = {{179,126, -1},{136,179, -1}}; REQUIRE(test_map.roads_out.size() == expected_roads.size()); for( const auto &candidate_road : test_map.roads_out ) { REQUIRE(std::find(expected_roads.begin(), expected_roads.end(), candidate_road) != expected_roads.end() ); } // Check the radio towers. std::vector<radio_tower> expected_towers{ {2,42,122,"This is FEMA camp 121. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 121. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {36,300,193,"This is FEMA camp 18150. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 18150. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {56,194,92,"This is automated emergency shelter beacon 2897. Supplies, amenities and shelter are stocked.",MESSAGE_BROADCAST}, {62,208,176,"This is FEMA camp 31104. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 31104. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {64,42,190,"",WEATHER_RADIO},{92,146,100,"",WEATHER_RADIO}, {126,194,112,"This is FEMA camp 6397. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 6397. A designated long-term emergency shelter.", MESSAGE_BROADCAST}, {142,128,114,"This is FEMA camp 7164. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 7164. A designated long-term emergency shelter.", MESSAGE_BROADCAST}, {236,168,115,"",WEATHER_RADIO}, {240,352,95,"This is automated emergency shelter beacon 120176. Supplies, amenities and shelter are stocked.", MESSAGE_BROADCAST}, {244,162,150,"This is emergency broadcast station 12281. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}, {282,48,190,"This is emergency broadcast station 14124. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}, {306,66,90,"This is emergency broadcast station 15333. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}}; REQUIRE(test_map.radios.size() == expected_towers.size()); for( const auto &candidate_tower : test_map.radios ) { REQUIRE(std::find(expected_towers.begin(), expected_towers.end(), candidate_tower) != expected_towers.end() ); } // Spot-check some monsters. std::vector<std::pair<tripoint, monster>> expected_monsters{ {{251, 86, 0},{ mtype_id("mon_zombie"), {140, 23, 0}}}, {{253, 87, 0},{ mtype_id("mon_zombie"), {136, 25, 0}}}, {{259, 95, 0},{ mtype_id("mon_zombie"), {143, 122, 0}}}, {{259, 94, 0},{ mtype_id("mon_zombie"), {139, 109, 0}}}, {{259, 91, 0},{ mtype_id("mon_dog"), {139, 82, 0}}}, {{194, 87, -3},{ mtype_id("mon_irradiated_wanderer_4"), {119, 73, -3}}}, {{194, 87, -3},{ mtype_id("mon_charred_nightmare"), {117, 83, -3}}}, {{142, 96, 0},{ mtype_id("mon_deer"), {16, 109, 0}}}, {{196, 66, -1},{ mtype_id("mon_turret"), {17, 65, -1}}}, {{196, 63, -1},{ mtype_id("mon_broken_cyborg"), {19, 26, -1}}} }; for( auto candidate_monster : expected_monsters ) { REQUIRE(test_map.monster_check(candidate_monster)); } // Check NPCs. They're complicated enough that I'm just going to spot-check some stats. for( const npc *test_npc : test_map.npcs ) { if( test_npc->disp_name() == "Felix Brandon" ) { REQUIRE(test_npc->get_str() == 7); REQUIRE(test_npc->get_dex() == 8); REQUIRE(test_npc->get_int() == 7); REQUIRE(test_npc->get_per() == 10); REQUIRE(test_npc->get_skill_level(skill_id("barter")) == 4); REQUIRE(test_npc->get_skill_level(skill_id("driving")) == 2); REQUIRE(test_npc->get_skill_level(skill_id("firstaid")) == 7); REQUIRE(test_npc->get_skill_level(skill_id("mechanics")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("dodge")) == 3); REQUIRE(test_npc->get_skill_level(skill_id("launcher")) == 3); REQUIRE(test_npc->pos() == tripoint(168, 66, 0)); } else if( test_npc->disp_name() == "Mariann Araujo" ) { REQUIRE(test_npc->get_str() == 11); REQUIRE(test_npc->get_dex() == 9); REQUIRE(test_npc->get_int() == 10); REQUIRE(test_npc->get_per() == 10); REQUIRE(test_npc->get_skill_level(skill_id("barter")) == 4); REQUIRE(test_npc->get_skill_level(skill_id("driving")) == 0); REQUIRE(test_npc->get_skill_level(skill_id("firstaid")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("bashing")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("dodge")) == 4); REQUIRE(test_npc->pos() == tripoint(72, 54, 0)); } else { // Unrecognized NPC, fail. REQUIRE(false); } } }
void MonsterGroupManager::LoadMonsterGroup( JsonObject &jo ) { float mon_upgrade_factor = get_option<float>( "MONSTER_UPGRADE_FACTOR" ); MonsterGroup g; g.name = mongroup_id( jo.get_string( "name" ) ); bool extending = false; //If already a group with that name, add to it instead of overwriting it if( monsterGroupMap.count( g.name ) != 0 && !jo.get_bool( "override", false ) ) { g = monsterGroupMap[g.name]; extending = true; } if( !extending || jo.has_string( "default" ) ) { //Not mandatory to specify default if extending existing group g.defaultMonster = mtype_id( jo.get_string( "default" ) ); } g.is_animal = jo.get_bool( "is_animal", false ); if( jo.has_array( "monsters" ) ) { JsonArray monarr = jo.get_array( "monsters" ); while( monarr.has_more() ) { JsonObject mon = monarr.next_object(); const mtype_id name = mtype_id( mon.get_string( "monster" ) ); int freq = mon.get_int( "freq" ); int cost = mon.get_int( "cost_multiplier" ); int pack_min = 1; int pack_max = 1; if( mon.has_member( "pack_size" ) ) { JsonArray packarr = mon.get_array( "pack_size" ); pack_min = packarr.next_int(); pack_max = packarr.next_int(); } static const time_duration tdfactor = 1_hours; time_duration starts = 0_turns; time_duration ends = 0_turns; if( mon.has_member( "starts" ) ) { starts = tdfactor * mon.get_int( "starts" ) * ( mon_upgrade_factor > 0 ? mon_upgrade_factor : 1 ); } if( mon.has_member( "ends" ) ) { ends = tdfactor * mon.get_int( "ends" ) * ( mon_upgrade_factor > 0 ? mon_upgrade_factor : 1 ); } MonsterGroupEntry new_mon_group = MonsterGroupEntry( name, freq, cost, pack_min, pack_max, starts, ends ); if( mon.has_member( "conditions" ) ) { JsonArray conditions_arr = mon.get_array( "conditions" ); while( conditions_arr.has_more() ) { new_mon_group.conditions.push_back( conditions_arr.next_string() ); } } g.monsters.push_back( new_mon_group ); } } g.replace_monster_group = jo.get_bool( "replace_monster_group", false ); g.new_monster_group = mongroup_id( jo.get_string( "new_monster_group_id", mongroup_id::NULL_ID().str() ) ); assign( jo, "replacement_time", g.monster_group_time, false, 1_days ); g.is_safe = jo.get_bool( "is_safe", false ); g.freq_total = jo.get_int( "freq_total", ( extending ? g.freq_total : 1000 ) ); if( jo.get_bool( "auto_total", false ) ) { //Fit the max size to the sum of all freqs int total = 0; for( MonsterGroupEntry &mon : g.monsters ) { total += mon.frequency; } g.freq_total = total; } monsterGroupMap[g.name] = g; }
bool mission::is_complete( const int _npc_id ) const { if( status == mission_status::success ) { return true; } auto &u = g->u; switch( type->goal ) { case MGOAL_GO_TO: { const tripoint cur_pos = g->u.global_omt_location(); return ( rl_dist( cur_pos, target ) <= 1 ); } break; case MGOAL_GO_TO_TYPE: { const auto cur_ter = overmap_buffer.ter( g->u.global_omt_location() ); return cur_ter == type->target_id; } break; case MGOAL_FIND_ITEM: { inventory tmp_inv = u.crafting_inventory(); // TODO: check for count_by_charges and use appropriate player::has_* function if (!tmp_inv.has_amount(type->item_id, item_count)) { return tmp_inv.has_amount( type->item_id, 1 ) && tmp_inv.has_charges( type->item_id, item_count ); } if( npc_id != -1 && npc_id != _npc_id ) { return false; } } return true; case MGOAL_FIND_ANY_ITEM: return u.has_mission_item( uid ) && ( npc_id == -1 || npc_id == _npc_id ); case MGOAL_FIND_MONSTER: if( npc_id != -1 && npc_id != _npc_id ) { return false; } for( size_t i = 0; i < g->num_zombies(); i++ ) { if( g->zombie( i ).mission_id == uid ) { return true; } } return false; case MGOAL_RECRUIT_NPC: { npc *p = g->find_npc( target_npc_id ); return p != nullptr && p->attitude == NPCATT_FOLLOW; } case MGOAL_RECRUIT_NPC_CLASS: { const auto npcs = overmap_buffer.get_npcs_near_player( 100 ); for( auto & npc : npcs ) { if( npc->myclass == recruit_class && npc->attitude == NPCATT_FOLLOW ) { return true; } } return false; } case MGOAL_FIND_NPC: return npc_id == _npc_id; case MGOAL_ASSASSINATE: return step >= 1; case MGOAL_KILL_MONSTER: return step >= 1; case MGOAL_KILL_MONSTER_TYPE: return g->kill_count( mtype_id( monster_type ) ) >= monster_kill_goal; case MGOAL_COMPUTER_TOGGLE: return step >= 1; default: return false; } return false; }
// We're reading in way too many entities here to mess around with creating sub-objects and // seeking around in them, so we're using the json streaming API. submap *mapbuffer::unserialize_submaps( const tripoint &p ) { // Map the tripoint to the submap quad that stores it. const tripoint om_addr = overmapbuffer::sm_to_omt_copy( p ); const tripoint segment_addr = overmapbuffer::omt_to_seg_copy( om_addr ); std::stringstream quad_path; quad_path << world_generator->active_world->world_path << "/maps/" << segment_addr.x << "." << segment_addr.y << "." << segment_addr.z << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; std::ifstream fin( quad_path.str().c_str() ); if( !fin.is_open() ) { // If it doesn't exist, trigger generating it. return NULL; } JsonIn jsin( fin ); jsin.start_array(); while( !jsin.end_array() ) { std::unique_ptr<submap> sm(new submap()); tripoint submap_coordinates; jsin.start_object(); bool rubpow_update = false; while( !jsin.end_object() ) { std::string submap_member_name = jsin.get_member_name(); if( submap_member_name == "version" ) { if (jsin.get_int() < 22) { rubpow_update = true; } } else if( submap_member_name == "coordinates" ) { jsin.start_array(); int locx = jsin.get_int(); int locy = jsin.get_int(); int locz = jsin.get_int(); jsin.end_array(); submap_coordinates = tripoint( locx, locy, locz ); } else if( submap_member_name == "turn_last_touched" ) { sm->turn_last_touched = jsin.get_int(); } else if( submap_member_name == "temperature" ) { sm->temperature = jsin.get_int(); } else if( submap_member_name == "terrain" ) { // TODO: try block around this to error out if we come up short? jsin.start_array(); // Small duplication here so that the update check is only performed once if (rubpow_update) { std::string ter_string; item rock = item("rock", 0); item chunk = item("steel_chunk", 0); for( int j = 0; j < SEEY; j++ ) { for( int i = 0; i < SEEX; i++ ) { ter_string = jsin.get_string(); if (ter_string == "t_rubble") { sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_rubble" ].loadid; sm->itm[i][j].push_back( rock ); sm->itm[i][j].push_back( rock ); } else if (ter_string == "t_wreckage"){ sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_wreckage" ].loadid; sm->itm[i][j].push_back( chunk ); sm->itm[i][j].push_back( chunk ); } else if (ter_string == "t_ash"){ sm->ter[i][j] = termap[ "t_dirt" ].loadid; sm->frn[i][j] = furnmap[ "f_ash" ].loadid; } else if (ter_string == "t_pwr_sb_support_l"){ sm->ter[i][j] = termap[ "t_support_l" ].loadid; } else if (ter_string == "t_pwr_sb_switchgear_l"){ sm->ter[i][j] = termap[ "t_switchgear_l" ].loadid; } else if (ter_string == "t_pwr_sb_switchgear_s"){ sm->ter[i][j] = termap[ "t_switchgear_s" ].loadid; } else { sm->ter[i][j] = terfind( ter_string ); } } } } else { for( int j = 0; j < SEEY; j++ ) { for( int i = 0; i < SEEX; i++ ) { sm->ter[i][j] = terfind( jsin.get_string() ); } } } jsin.end_array(); } else if( submap_member_name == "radiation" ) { int rad_cell = 0; jsin.start_array(); while( !jsin.end_array() ) { int rad_strength = jsin.get_int(); int rad_num = jsin.get_int(); for( int i = 0; i < rad_num; ++i ) { // A little array trick here, assign to it as a 1D array. // If it's not in bounds we're kinda hosed anyway. sm->set_radiation(0, rad_cell, rad_strength); rad_cell++; } } } else if( submap_member_name == "furniture" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->frn[i][j] = furnmap[ jsin.get_string() ].loadid; jsin.end_array(); } } else if( submap_member_name == "items" ) { jsin.start_array(); while( !jsin.end_array() ) { int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { item tmp; jsin.read( tmp ); if( tmp.is_emissive() ) { sm->update_lum_add(tmp, i, j); } tmp.visit_items([&sm,i,j]( item *it ) { for( auto& e: it->magazine_convert() ) { sm->itm[i][j].push_back( e ); } return VisitResponse::NEXT; } ); sm->itm[i][j].push_back( tmp ); if( tmp.needs_processing() ) { sm->active_items.add( std::prev(sm->itm[i][j].end()), point( i, j ) ); } } } } else if( submap_member_name == "traps" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); // TODO: jsin should support returning an id like jsin.get_id<trap>() sm->trp[i][j] = trap_str_id( jsin.get_string() ); jsin.end_array(); } } else if( submap_member_name == "fields" ) { jsin.start_array(); while( !jsin.end_array() ) { // Coordinates loop int i = jsin.get_int(); int j = jsin.get_int(); jsin.start_array(); while( !jsin.end_array() ) { int type = jsin.get_int(); int density = jsin.get_int(); int age = jsin.get_int(); if (sm->fld[i][j].findField(field_id(type)) == NULL) { sm->field_count++; } sm->fld[i][j].addField(field_id(type), density, age); } } } else if( submap_member_name == "graffiti" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); sm->set_graffiti( i, j, jsin.get_string() ); jsin.end_array(); } } else if(submap_member_name == "cosmetics") { jsin.start_array(); while (!jsin.end_array()) { jsin.start_array(); int i = jsin.get_int(); int j = jsin.get_int(); jsin.read(sm->cosmetics[i][j]); jsin.end_array(); } } else if( submap_member_name == "spawns" ) { jsin.start_array(); while( !jsin.end_array() ) { jsin.start_array(); const mtype_id type = mtype_id( jsin.get_string() ); // TODO: json should know how to read an string_id int count = jsin.get_int(); int i = jsin.get_int(); int j = jsin.get_int(); int faction_id = jsin.get_int(); int mission_id = jsin.get_int(); bool friendly = jsin.get_bool(); std::string name = jsin.get_string(); jsin.end_array(); spawn_point tmp( type, count, i, j, faction_id, mission_id, friendly, name ); sm->spawns.push_back( tmp ); } } else if( submap_member_name == "vehicles" ) { jsin.start_array(); while( !jsin.end_array() ) { vehicle *tmp = new vehicle(); jsin.read( *tmp ); sm->vehicles.push_back( tmp ); } } else if( submap_member_name == "computers" ) { std::string computer_data = jsin.get_string(); sm->comp.load_data( computer_data ); } else if( submap_member_name == "camp" ) { std::string camp_data = jsin.get_string(); sm->camp.load_data( camp_data ); } else { jsin.skip_value(); } } if( !add_submap( submap_coordinates, sm ) ) { debugmsg( "submap %d,%d,%d was already loaded", submap_coordinates.x, submap_coordinates.y, submap_coordinates.z ); } } if( submaps.count( p ) == 0 ) { debugmsg("file %s did not contain the expected submap %d,%d,%d", quad_path.str().c_str(), p.x, p.y, p.z); return NULL; } return submaps[ p ]; }
bool mission::is_complete( const int _npc_id ) const { if( status == mission_status::success ) { return true; } auto &u = g->u; switch( type->goal ) { case MGOAL_GO_TO: { const tripoint cur_pos = g->u.global_omt_location(); return ( rl_dist( cur_pos, target ) <= 1 ); } case MGOAL_GO_TO_TYPE: { const auto cur_ter = overmap_buffer.ter( g->u.global_omt_location() ); return is_ot_type( type->target_id.str(), cur_ter ); } case MGOAL_FIND_ITEM: { inventory tmp_inv = u.crafting_inventory(); // TODO: check for count_by_charges and use appropriate player::has_* function if( !tmp_inv.has_amount( type->item_id, item_count ) ) { return tmp_inv.has_amount( type->item_id, 1 ) && tmp_inv.has_charges( type->item_id, item_count ); } if( npc_id != -1 && npc_id != _npc_id ) { return false; } } return true; case MGOAL_FIND_ANY_ITEM: return u.has_mission_item( uid ) && ( npc_id == -1 || npc_id == _npc_id ); case MGOAL_FIND_MONSTER: if( npc_id != -1 && npc_id != _npc_id ) { return false; } return g->get_creature_if( [&]( const Creature & critter ) { const monster *const mon_ptr = dynamic_cast<const monster *>( &critter ); return mon_ptr && mon_ptr->mission_id == uid; } ); case MGOAL_RECRUIT_NPC: { npc *p = g->find_npc( target_npc_id ); return p != nullptr && p->get_attitude() == NPCATT_FOLLOW; } case MGOAL_RECRUIT_NPC_CLASS: { const auto npcs = overmap_buffer.get_npcs_near_player( 100 ); for( auto &npc : npcs ) { if( npc->myclass == recruit_class && npc->get_attitude() == NPCATT_FOLLOW ) { return true; } } return false; } case MGOAL_FIND_NPC: return npc_id == _npc_id; case MGOAL_ASSASSINATE: return step >= 1; case MGOAL_KILL_MONSTER: return step >= 1; case MGOAL_KILL_MONSTER_TYPE: return g->kill_count( mtype_id( monster_type ) ) >= kill_count_to_reach; case MGOAL_KILL_MONSTER_SPEC: return g->kill_count( monster_species ) >= kill_count_to_reach; case MGOAL_COMPUTER_TOGGLE: return step >= 1; default: return false; } }
void player::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == trait_WEB_WEAVER ) { g->m.add_field( pos(), fd_web, 1 ); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } time_duration time_to_do = 0_turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. time_to_do = 30_minutes; } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { time_to_do = 10_minutes; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity( activity_id( "ACT_BURROW" ), to_moves<int>( time_to_do ), -1, 0 ); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if( mut == trait_SLIMESPAWNER ) { std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if (g->is_empty(dest)) { valid.push_back( dest ); } } // Oops, no room to divide! if( valid.empty() ) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if( monster * const slime = g->summon_mon( mtype_id( "mon_player_blob" ), target ) ) { slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if( mut == trait_NAUSEA || mut == trait_VOMITOUS ) { vomit(); tdata.powered = false; return; } else if( mut == trait_M_FERTILE ) { spores(); tdata.powered = false; return; } else if( mut == trait_M_BLOOM ) { blossoms(); tdata.powered = false; return; } else if( mut == trait_SELFAWARE ) { print_health(); tdata.powered = false; return; } else if( !mdata.spawn_item.empty() ) { item tmpitem( mdata.spawn_item ); i_add_or_drop( tmpitem ); add_msg_if_player( _( mdata.spawn_item_message.c_str() ) ); tdata.powered = false; return; } }
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 MonsterGenerator::check_monster_definitions() const { for( const auto &mon : mon_templates->get_all() ) { if( mon.harvest == "null" && !mon.has_flag( MF_ELECTRONIC ) && mon.id != mtype_id( "mon_null" ) ) { debugmsg( "monster %s has no harvest entry", mon.id.c_str(), mon.harvest.c_str() ); } for( auto &spec : mon.species ) { if( !spec.is_valid() ) { debugmsg( "monster %s has invalid species %s", mon.id.c_str(), spec.c_str() ); } } if( !mon.death_drops.empty() && !item_group::group_is_defined( mon.death_drops ) ) { debugmsg( "monster %s has unknown death drop item group: %s", mon.id.c_str(), mon.death_drops.c_str() ); } for( auto &m : mon.mat ) { if( m.str() == "null" || !m.is_valid() ) { debugmsg( "monster %s has unknown material: %s", mon.id.c_str(), m.c_str() ); } } if( !mon.revert_to_itype.empty() && !item::type_is_defined( mon.revert_to_itype ) ) { debugmsg( "monster %s has unknown revert_to_itype: %s", mon.id.c_str(), mon.revert_to_itype.c_str() ); } for( auto &s : mon.starting_ammo ) { if( !item::type_is_defined( s.first ) ) { debugmsg( "starting ammo %s of monster %s is unknown", s.first.c_str(), mon.id.c_str() ); } } for( auto &e : mon.atk_effs ) { if( !e.id.is_valid() ) { debugmsg( "attack effect %s of monster %s is unknown", e.id.c_str(), mon.id.c_str() ); } } for( const auto &e : mon.emit_fields ) { if( !e.is_valid() ) { debugmsg( "monster %s has invalid emit source %s", mon.id.c_str(), e.c_str() ); } } if( mon.upgrades ) { if( mon.half_life < 0 && mon.age_grow < 0 ) { debugmsg( "half_life %d and age_grow %d (<0) of monster %s is invalid", mon.half_life, mon.age_grow, mon.id.c_str() ); } if( !mon.upgrade_into && !mon.upgrade_group ) { debugmsg( "no into nor into_group defined for monster %s", mon.id.c_str() ); } if( mon.upgrade_into && mon.upgrade_group ) { debugmsg( "both into and into_group defined for monster %s", mon.id.c_str() ); } if( !mon.upgrade_into.is_valid() ) { debugmsg( "upgrade_into %s of monster %s is not a valid monster id", mon.upgrade_into.c_str(), mon.id.c_str() ); } if( !mon.upgrade_group.is_valid() ) { debugmsg( "upgrade_group %s of monster %s is not a valid monster group id", mon.upgrade_group.c_str(), mon.id.c_str() ); } } if( mon.reproduces ) { if( mon.baby_timer < 1 ) { debugmsg( "Time between reproductions (%d) is invalid for %s", mon.baby_timer, mon.id.c_str() ); } if( mon.baby_count < 1 ) { debugmsg( "Number of children (%d) is invalid for %s", mon.baby_count, mon.id.c_str() ); } if( !mon.baby_monster && mon.baby_egg == "null" ) { debugmsg( "No baby or egg defined for monster %s", mon.id.c_str() ); } if( mon.baby_monster && mon.baby_egg != "null" ) { debugmsg( "Both an egg and a live birth baby are defined for %s", mon.id.c_str() ); } if( !mon.baby_monster.is_valid() ) { debugmsg( "baby_monster %s of monster %s is not a valid monster id", mon.baby_monster.c_str(), mon.id.c_str() ); } if( !item::type_is_defined( mon.baby_egg ) ) { debugmsg( "item_id %s of monster %s is not a valid item id", mon.baby_egg.c_str(), mon.id.c_str() ); } } if( mon.biosignatures ) { if( mon.biosig_timer < 1 ) { debugmsg( "Time between biosignature drops (%d) is invalid for %s", mon.biosig_timer, mon.id.c_str() ); } if( mon.biosig_item == "null" ) { debugmsg( "No biosignature drop defined for monster %s", mon.id.c_str() ); } if( !item::type_is_defined( mon.biosig_item ) ) { debugmsg( "item_id %s of monster %s is not a valid item id", mon.biosig_item.c_str(), mon.id.c_str() ); } } } }
void player::activate_mutation( const std::string &mut ) { const auto &mdata = mutation_branch::get( mut ); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == "WEB_WEAVER" ) { g->m.add_field(pos(), fd_web, 1, 0); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } int turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Takes about 100 minutes (not quite two hours) base time. // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. ///\EFFECT_CARPENTRY speeds up burrowing turns = (100000 - 5000 * skillLevel( skill_id( "carpentry" ) )); } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { turns = 18000; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity(ACT_BURROW, turns, -1, 0); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if (mut == "SLIMESPAWNER") { std::vector<tripoint> valid; for (int x = posx() - 1; x <= posx() + 1; x++) { for (int y = posy() - 1; y <= posy() + 1; y++) { tripoint dest(x, y, posz()); if (g->is_empty(dest)) { valid.push_back( dest ); } } } // Oops, no room to divide! if (valid.size() == 0) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if (g->summon_mon(mtype_id( "mon_player_blob" ), target)) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if ((mut == "NAUSEA") || (mut == "VOMITOUS") ){ vomit(); tdata.powered = false; return; } else if (mut == "M_FERTILE"){ spores(); tdata.powered = false; return; } else if (mut == "M_BLOOM"){ blossoms(); tdata.powered = false; return; } else if (mut == "VINES3"){ item newit( "vine_30", calendar::turn ); if (!can_pickVolume(newit.volume())) { //Accounts for result_mult add_msg_if_player(_("You detach a vine but don't have room to carry it, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else if (!can_pickWeight(newit.weight(), !OPTIONS["DANGEROUS_PICKUPS"])) { add_msg_if_player(_("Your freshly-detached vine is too heavy to carry, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else { inv.assign_empty_invlet(newit); newit = i_add(newit); add_msg_if_player(m_info, "%c - %s", newit.invlet == 0 ? ' ' : newit.invlet, newit.tname().c_str()); } tdata.powered = false; return; } else if( mut == "SELFAWARE" ) { print_health(); tdata.powered = false; return; } }