From 6e43d5ac56c4e1f81506a5fae92f60ef67a734c8 Mon Sep 17 00:00:00 2001
From: RenechCDDA <84619419+RenechCDDA@users.noreply.github.com>
Date: Sat, 8 Jun 2024 19:54:06 -0400
Subject: [PATCH] ...Move settings around between your followers. Why not

---
 src/npctalk_rules.cpp | 207 ++++++++++++++++++++++++++++++++++++++++++
 src/npctalk_rules.h   |   1 +
 2 files changed, 208 insertions(+)

diff --git a/src/npctalk_rules.cpp b/src/npctalk_rules.cpp
index 94b5b196e05b3..0e4f31ea128ff 100644
--- a/src/npctalk_rules.cpp
+++ b/src/npctalk_rules.cpp
@@ -16,9 +16,11 @@
 
 #include "cata_imgui.h"
 #include "dialogue.h"
+#include "game.h"
 #include "npctalk.h"
 #include "ui_manager.h"
 #include "imgui/imgui.h"
+#include "imgui/imgui_internal.h"
 
 static std::map<cbm_recharge_rule, std::string> recharge_map = {
     {cbm_recharge_rule::CBM_RECHARGE_ALL, "<ally_rule_cbm_recharge_all_text>" },
@@ -121,6 +123,193 @@ void follower_rules_ui_impl::print_hotkey( input_event &hotkey )
     ImGui::SameLine();
 }
 
+void follower_rules_ui_impl::rules_transfer_popup( bool &exporting_rules, bool &still_in_popup )
+{
+    std::string toggle_label;
+    if( exporting_rules ) {
+        draw_colored_text( string_format( _( "Exporting rules from <color_white>%s</color>" ),
+                                          guy->disp_name() ), c_blue );
+        //~Label for a button used to copy NPC follower rules to another NPC
+        toggle_label = _( "Export" );
+    } else {
+        draw_colored_text( string_format( _( "Importing rules to <color_white>%s</color>" ),
+                                          guy->disp_name() ), c_yellow );
+        //~Label for a button used to copy NPC follower rules from another NPC
+        toggle_label = _( "Import" );
+    }
+    // Not ideal but leaves plenty of room for even verbose languages. Really anywhere top-right-ish is fine
+    ImGui::SameLine( ImGui::GetWindowWidth() * 0.7 );
+    if( ImGui::Button( _( "Return to rules settings" ) ) ) {
+        still_in_popup = false;
+        return;
+    }
+    if( ImGui::Button( _( "Toggle export/import" ) ) ) {
+        exporting_rules = !exporting_rules;
+    }
+    ImGui::NewLine();
+    draw_colored_text( string_format(
+                           _( "Individual settings are colored if they already match to %s." ),
+                           guy->disp_name() ) );
+    if( ImGui::BeginTable( "##SETTINGS_SWAP_TABLE", 8, ImGuiTableFlags_None,
+                           ImVec2( window_width, window_height ) ) ) {
+        // Missions selection is purposefully thinner than the description, it has less to convey.
+        ImGui::TableSetupColumn( _( "Name" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "Behaviors" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "Aiming" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "Engagement" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "CBM recharge" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "CBM reserve" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "Pickup list" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableSetupColumn( _( "ALL" ), ImGuiTableColumnFlags_WidthStretch,
+                                 window_width / 8 );
+        ImGui::TableHeadersRow();
+        int button_number = 0;
+        for( npc &role_model : g->all_npcs() ) {
+            if( &role_model == guy ) {
+                continue; // No copying settings to/from yourself
+            }
+            if( role_model.is_player_ally() ) {
+                bool do_flags_overrides = false;
+                bool do_aim = false;
+                bool do_engagement = false;
+                bool do_cbm_recharge = false;
+                bool do_cbm_reserve = false;
+                bool do_pickup = false;
+                ImGui::TableNextRow();
+                ImGui::TableNextColumn();
+                draw_colored_text( role_model.disp_name() );
+                /*Flags and overrides*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.flags == guy->rules.flags ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_flags_overrides = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*Aim rule*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.aim == guy->rules.aim ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_aim = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*Engagement rule*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.engagement == guy->rules.engagement ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_engagement = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*CBM recharge rule*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.cbm_recharge == guy->rules.cbm_recharge ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_cbm_recharge = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*CBM reserve rule*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.cbm_reserve == guy->rules.cbm_reserve ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_cbm_reserve = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*Pickup whitelist rule*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                // This is not correct if both have rules and both have *different* rules, but comparison is expensive
+                if( role_model.rules.pickup_whitelist->empty() == guy->rules.pickup_whitelist->empty() ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_pickup = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*The ALL button*/
+                ImGui::TableNextColumn();
+                ImGui::PushID( button_number );
+                button_number++;
+                if( role_model.rules.flags == guy->rules.flags &&
+                    role_model.rules.aim == guy->rules.aim &&
+                    role_model.rules.engagement == guy->rules.engagement &&
+                    role_model.rules.cbm_recharge == guy->rules.cbm_recharge &&
+                    role_model.rules.cbm_reserve == guy->rules.cbm_reserve &&
+                    role_model.rules.pickup_whitelist->empty() == guy->rules.pickup_whitelist->empty() ) {
+                    ImGui::PushStyleColor( ImGuiCol_Button, c_green );
+                }
+                if( ImGui::Button( toggle_label.c_str() ) ) {
+                    do_flags_overrides = true;
+                    do_aim = true;
+                    do_engagement = true;
+                    do_cbm_recharge = true;
+                    do_cbm_reserve = true;
+                    do_pickup = true;
+                }
+                ImGui::PopID();
+                ImGui::PopStyleColor();
+                /*The great swap*/
+                // To make this slightly easier...
+                npc *guy_with_same = exporting_rules ? guy : &role_model;
+                npc *guy_with_new = exporting_rules ? &role_model : guy;
+                if( do_flags_overrides ) {
+                    guy_with_new->rules.flags = guy_with_same->rules.flags;
+                    guy_with_new->rules.overrides = guy_with_same->rules.overrides;
+                    guy_with_new->rules.override_enable = guy_with_same->rules.override_enable;
+                }
+                if( do_aim ) {
+                    guy_with_new->rules.aim = guy_with_same->rules.aim;
+                }
+                if( do_engagement ) {
+                    guy_with_new->rules.engagement = guy_with_same->rules.engagement;
+                }
+                if( do_cbm_recharge ) {
+                    guy_with_new->rules.cbm_recharge = guy_with_same->rules.cbm_recharge;
+                }
+                if( do_cbm_reserve ) {
+                    guy_with_new->rules.cbm_reserve = guy_with_same->rules.cbm_reserve;
+                }
+                if( do_pickup ) {
+                    guy_with_new->rules.pickup_whitelist = guy_with_same->rules.pickup_whitelist;
+                }
+            }
+        }
+        ImGui::EndTable();
+    }
+}
+
 void follower_rules_ui_impl::draw_controls()
 {
     if( !guy ) {
@@ -131,6 +320,13 @@ void follower_rules_ui_impl::draw_controls()
 
     ImGui::SetWindowSize( ImVec2( window_width, window_height ), ImGuiCond_Once );
 
+    static bool exporting_rules = false;
+    static bool in_popup = false;
+    if( in_popup ) {
+        rules_transfer_popup( exporting_rules, in_popup );
+        return;
+    }
+
     ImGui::InvisibleButton( "TOP_OF_WINDOW_KB_SCROLL_SELECTABLE", ImVec2() );
 
     const hotkey_queue &hotkeys = hotkey_queue::alphabets();
@@ -142,6 +338,17 @@ void follower_rules_ui_impl::draw_controls()
     draw_colored_text( _( "Hotkey:" ) );
     ImGui::NewLine();
 
+    if( ImGui::Button( _( "Import settings from follower" ) ) ) {
+        exporting_rules = false;
+        in_popup = true;
+        return;
+    }
+    if( ImGui::Button( _( "Export settings to follower" ) ) ) {
+        exporting_rules = true;
+        in_popup = true;
+        return;
+    }
+
     print_hotkey( assigned_hotkey );
     if( ImGui::Button( _( "Default ALL" ) ) || pressed_key == assigned_hotkey ) {
         ImGui::SetKeyboardFocusHere( -1 );
diff --git a/src/npctalk_rules.h b/src/npctalk_rules.h
index 43f2a00ef446a..841ab7144a38d 100644
--- a/src/npctalk_rules.h
+++ b/src/npctalk_rules.h
@@ -44,6 +44,7 @@ class follower_rules_ui_impl : public cataimgui::window
         npc *guy = nullptr;
         std::string get_parsed( std::string initial_string );
         void print_hotkey( input_event &hotkey );
+        void rules_transfer_popup( bool &exporting_rules, bool &still_in_popup );
 
         size_t window_width = str_width_to_pixels( TERMX ) / 2;
         size_t window_height = str_height_to_pixels( TERMY ) / 2;