diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index e769effd02ea8..f2b022134a298 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -1234,6 +1234,13 @@ "name": "Teleport to cursor", "bindings": [ { "input_method": "keyboard_any", "key": "d" } ] }, + { + "type": "keybinding", + "id": "MODIFY_HORDE", + "category": "OVERMAP", + "name": "Modify existing horde or place new one", + "bindings": [ { "input_method": "keyboard_any", "key": "p" } ] + }, { "type": "keybinding", "name": "View missions", diff --git a/src/debug_menu.h b/src/debug_menu.h index 24bcfb2c54413..9d8e3bbe6bd94 100644 --- a/src/debug_menu.h +++ b/src/debug_menu.h @@ -12,6 +12,7 @@ class Character; class Creature; +struct mongroup; struct tripoint; template struct enum_traits; @@ -121,6 +122,8 @@ void wishitem( Character *you = nullptr ); void wishitem( Character *you, const tripoint & ); void wishitem( Character *you, const tripoint_bub_ms & ); void wishmonster( const std::optional &p ); +void wishmonstergroup( tripoint_abs_omt &loc ); +void wishmonstergroup_mon_selection( mongroup &group ); void wishmutate( Character *you ); void wishbionics( Character *you ); /* diff --git a/src/mongroup.cpp b/src/mongroup.cpp index dea2a253025ef..ced65049c4b2f 100644 --- a/src/mongroup.cpp +++ b/src/mongroup.cpp @@ -449,6 +449,11 @@ void MonsterGroupManager::FinalizeMonsterGroups() } } +std::map &MonsterGroupManager::Get_all_Groups() +{ + return MonsterGroupManager::monsterGroupMap; +} + void MonsterGroupManager::LoadMonsterGroup( const JsonObject &jo ) { float mon_upgrade_factor = get_option( "MONSTER_UPGRADE_FACTOR" ); diff --git a/src/mongroup.h b/src/mongroup.h index 5855582abb8d2..d99ccb7f4e756 100644 --- a/src/mongroup.h +++ b/src/mongroup.h @@ -224,6 +224,9 @@ class MonsterGroupManager static bool is_animal( const mongroup_id &group ); + // Public getter for private monsterGroupMap, do not use if you don't know what you're doing. + static std::map &Get_all_Groups(); + private: static std::map monsterGroupMap; using t_string_set = std::set; diff --git a/src/overmap.cpp b/src/overmap.cpp index 07d72f010e8b4..8a7788a3554b1 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -7553,6 +7553,23 @@ void overmap::debug_force_add_group( const mongroup &group ) add_mon_group( group, 1 ); } +std::vector> overmap::debug_unsafe_get_groups_at( + tripoint_abs_omt &loc ) +{ + point_abs_om overmap; + tripoint_om_omt omt_within_overmap; + std::tie( overmap, omt_within_overmap ) = project_remain( loc ); + tripoint_om_sm om_sm_pos = project_to( omt_within_overmap ); + + std::vector> groups_at; + for( std::pair &pair : zg ) { + if( pair.first == om_sm_pos ) { + groups_at.emplace_back( pair.second ); + } + } + return groups_at; +} + void overmap::add_mon_group( const mongroup &group ) { zg.emplace( group.rel_pos(), group ); diff --git a/src/overmap.h b/src/overmap.h index d24af49002727..75c669ef910a2 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -540,6 +540,7 @@ class overmap // DEBUG ONLY! void debug_force_add_group( const mongroup &group ); + std::vector> debug_unsafe_get_groups_at( tripoint_abs_omt &loc ); private: /** * Iterate over the overmap and place the quota of specials. diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 313a015dab552..389c7eb773807 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -35,6 +35,7 @@ #include "cuboid_rectangle.h" #include "cursesdef.h" #include "display.h" +#include "debug_menu.h" #include "game.h" #include "game_constants.h" #include "game_ui.h" @@ -1151,6 +1152,7 @@ static void draw_om_sidebar( ui_adaptor &ui, print_hint( "PLACE_SPECIAL", c_light_blue ); print_hint( "SET_SPECIAL_ARGS", c_light_blue ); print_hint( "LONG_TELEPORT", c_light_blue ); + print_hint( "MODIFY_HORDE", c_light_blue ); ++y; } @@ -1627,6 +1629,92 @@ static void set_special_args( tripoint_abs_omt &curs ) *maybe_args = args; } +static void modify_horde_func( tripoint_abs_omt &curs ) +{ + overmap &map_at_cursor = overmap_buffer.get( project_to( curs ).xy() ); + std::vector> hordes = + map_at_cursor.debug_unsafe_get_groups_at( curs ); + if( hordes.empty() ) { + if( !query_yn( _( "No hordes there. Would you like to make a new horde?" ) ) ) { + return; + } else { + debug_menu::wishmonstergroup( curs ); + return; + } + } + uilist horde_list; + int entry_num = 0; + for( auto &horde_wrapper : hordes ) { + mongroup &mg = horde_wrapper.get(); + // We do some special handling here to provide the information in as simple a way as possible + // emulates horde behavior + int displayed_monster_num = mg.monsters.empty() ? mg.population : mg.monsters.size(); + std::string horde_description = string_format( _( "group(type: %s) with %d monsters" ), + mg.type.c_str(), displayed_monster_num ); + horde_list.addentry( entry_num, true, -1, horde_description ); + } + horde_list.query(); + int &selected_group = horde_list.ret; + if( selected_group < 0 || static_cast( selected_group ) > hordes.size() ) { + return; + } + mongroup &chosen_group = hordes[selected_group]; + + uilist smenu; + smenu.addentry( 0, true, 'I', _( "Set horde's interest (in %%)" ) ); + smenu.addentry( 1, true, 'D', _( "Set horde's destination" ) ); + smenu.addentry( 2, true, 'P', _( "Modify horde's population" ) ); + smenu.addentry( 3, true, 'M', _( "Add a new monster to the horde" ) ); + smenu.addentry( 4, true, 'B', _( "Set horde behavior" ) ); + smenu.addentry( 5, true, 'T', _( "Set horde's boolean values" ) ); + smenu.addentry( 6, true, 'A', _( "Add another horde to this location" ) ); + smenu.query(); + int new_value = 0; + tripoint_abs_omt horde_destination = tripoint_abs_omt_zero; + switch( smenu.ret ) { + case 0: + query_int( new_value, _( "Set interest to what value? Currently %d" ), chosen_group.interest ); + chosen_group.set_interest( new_value ); + break; + case 1: + popup( _( "Select a target destination for the horde." ) ); + horde_destination = ui::omap::choose_point( true ); + if( horde_destination == overmap::invalid_tripoint || horde_destination == tripoint_abs_omt_zero ) { + break; + } + chosen_group.target = project_to( horde_destination ).xy(); + break; + case 2: + query_int( new_value, _( "Set population to what value? Currently %d" ), chosen_group.population ); + chosen_group.population = new_value; + break; + case 3: + debug_menu::wishmonstergroup_mon_selection( chosen_group ); + break; + case 4: + // Screw it we hardcode a popup, if you really want to use this you're welcome to improve it + popup( _( "Set behavior to which enum value? Currently %d. \nAccepted values:\n0 = none,\n1 = city,\n2=roam,\n3=nemesis" ), + static_cast( chosen_group.behaviour ) ); + query_int( new_value, "" ); + chosen_group.behaviour = static_cast( new_value ); + break; + case 5: + // One day we'll be able to simply convert booleans to strings... + chosen_group.horde = query_yn( + _( "Set group's \"horde\" value to true? (Select no to set to false) \nCurrently %s" ), + chosen_group.horde ? _( "true" ) : _( "false" ) ); + chosen_group.dying = query_yn( + _( "Set group's \"dying\" value to true? (Select no to set to false) \nCurrently %s" ), + chosen_group.dying ? _( "true" ) : _( "false" ) ); + break; + case 6: + debug_menu::wishmonstergroup( curs ); + break; + default: + break; + } +} + static std::vector get_overmap_path_to( const tripoint_abs_omt &dest, bool driving ) { @@ -1807,6 +1895,7 @@ static tripoint_abs_omt display() ictxt.register_action( "PLACE_SPECIAL" ); ictxt.register_action( "SET_SPECIAL_ARGS" ); ictxt.register_action( "LONG_TELEPORT" ); + ictxt.register_action( "MODIFY_HORDE" ); } ictxt.register_action( "QUIT" ); std::string action; @@ -2012,6 +2101,9 @@ static tripoint_abs_omt display() g->place_player_overmap( curs ); add_msg( _( "You teleport to submap %s." ), curs.to_string() ); action = "QUIT"; + } else if( action == "MODIFY_HORDE" ) { + modify_horde_func( curs ); + action = "QUIT"; } else if( action == "MISSIONS" ) { g->list_missions(); } @@ -2279,6 +2371,7 @@ void ui::omap::display() { g->overmap_data = overmap_ui::overmap_draw_data_t(); //reset data g->overmap_data.origin_pos = get_player_character().global_omt_location(); + g->overmap_data.debug_editor = debug_mode; // always display debug editor if game is in debug mode overmap_ui::display(); } diff --git a/src/wish.cpp b/src/wish.cpp index a687deb4468c8..0a689826028ca 100644 --- a/src/wish.cpp +++ b/src/wish.cpp @@ -29,7 +29,10 @@ #include "item_factory.h" #include "itype.h" #include "localized_comparator.h" +#include "overmap.h" +#include "overmapbuffer.h" #include "map.h" +#include "mongroup.h" #include "monster.h" #include "monstergenerator.h" #include "mtype.h" @@ -49,6 +52,8 @@ static const efftype_id effect_pet( "pet" ); +static const mongroup_id GROUP_ZOMBIE( "GROUP_ZOMBIE" ); + class ui_adaptor; class wish_mutate_callback: public uilist_callback @@ -713,26 +718,97 @@ class wish_monster_callback: public uilist_callback ~wish_monster_callback() override = default; }; +static void setup_wishmonster( uilist &pick_a_monster, std::vector &mtypes ) +{ + pick_a_monster.desired_bounds = { -1.0, -1.0, 1.0, 1.0 }; + pick_a_monster.selected = uistate.wishmonster_selected; + int i = 0; + for( const mtype &montype : MonsterGenerator::generator().get_all_mtypes() ) { + pick_a_monster.addentry( i, true, 0, montype.nname() ); + pick_a_monster.entries[i].extratxt.txt = montype.sym; + pick_a_monster.entries[i].extratxt.color = montype.color; + pick_a_monster.entries[i].extratxt.left = 1; + ++i; + mtypes.push_back( &montype ); + } +} + +void debug_menu::wishmonstergroup( tripoint_abs_omt &loc ) +{ + bool population_group = query_yn( + _( "Is this a population-based horde, based on an existing monster group? Select \"No\" to manually assign monsters." ) ); + mongroup new_group; + // Just in case, we manually set some values + new_group.abs_pos = project_to( loc ); + new_group.target = new_group.abs_pos.xy(); + new_group.population = 1; // overwritten if population_group + new_group.horde = true; + new_group.type = GROUP_ZOMBIE; // overwritten if population_group + if( population_group ) { + uilist type_selection; + std::vector possible_groups; + int i = 0; + for( auto &group_pair : MonsterGroupManager::Get_all_Groups() ) { + possible_groups.emplace_back( group_pair.first ); + type_selection.addentry( i, true, -1, group_pair.first.c_str() ); + i++; + } + type_selection.query(); + int &selected = type_selection.ret; + if( selected < 0 || static_cast( selected ) >= possible_groups.size() ) { + return; + } + const mongroup_id selected_group( possible_groups[selected] ); + new_group.type = selected_group; + int new_value = 0; + query_int( new_value, _( "Set population to what value? Currently %d" ), new_group.population ); + overmap &there = overmap_buffer.get( project_to( loc ).xy() ); + there.debug_force_add_group( new_group ); + return; // Don't go to adding individual monsters, they'll override the values we just set + } + + + wishmonstergroup_mon_selection( new_group ); + overmap &there = overmap_buffer.get( project_to( loc ).xy() ); + there.debug_force_add_group( new_group ); +} + +void debug_menu::wishmonstergroup_mon_selection( mongroup &group ) +{ + + std::vector mtypes; + uilist new_mon_selection; + setup_wishmonster( new_mon_selection, mtypes ); + wish_monster_callback cb( mtypes ); + new_mon_selection.callback = &cb; + new_mon_selection.query(); + int &selected = new_mon_selection.ret; + if( selected < 0 || static_cast( selected ) >= mtypes.size() ) { + return; + } + const mtype_id &mon_type = mtypes[ selected ]->id; + for( int i = 0; i < cb.group; i++ ) { + monster new_mon( mon_type ); + if( cb.friendly ) { + new_mon.friendly = -1; + new_mon.add_effect( effect_pet, 1_turns, true ); + } + if( cb.hallucination ) { + new_mon.hallucination = true; + } + group.monsters.emplace_back( new_mon ); + } +} + void debug_menu::wishmonster( const std::optional &p ) { std::vector mtypes; uilist wmenu; - wmenu.desired_bounds = { -1.0, -1.0, 1.0, 1.0 }; - wmenu.selected = uistate.wishmonster_selected; + setup_wishmonster( wmenu, mtypes ); wish_monster_callback cb( mtypes ); wmenu.callback = &cb; - int i = 0; - for( const mtype &montype : MonsterGenerator::generator().get_all_mtypes() ) { - wmenu.addentry( i, true, 0, montype.nname() ); - wmenu.entries[i].extratxt.txt = montype.sym; - wmenu.entries[i].extratxt.color = montype.color; - wmenu.entries[i].extratxt.left = 1; - ++i; - mtypes.push_back( &montype ); - } - do { wmenu.query(); if( wmenu.ret >= 0 ) {