/*

   NPC Combat Control

   Previously was part of sfall itself, now a separate mod.
   Features:
   - allows you to take control of your party members or other NPCs during combat
   - configurable list of perk IDs for perks being inherited from the player (optional, not all perks can work)
   - allows the player to gain a positive reputation when killing NPCs while controlling other critters (optional)
   - a notification box to display the name of the controlled critter above the interface bar (optional)

   NOTE: this script requires compiler from sfall modderspack with -s option
   (short circuit evaluation)

   version 1.4

*/

#include "..\headers\global.h"
#include "..\headers\critrpid.h"
#include "main.h"

#ifndef DEBUG
#define DEBUGMSG(x)
#else
#define DEBUGMSG(x)  debug_msg(x);
#endif

procedure start;
procedure AllowControl(variable pid);
procedure SetLight(variable critter, variable int, variable dist);
procedure CombatTurn_Handler;
procedure GameModeChange_Handler;
procedure InventoryMove_Handler;
procedure InvenWield_Handler;
procedure SetGlobalVar_Handler;

variable
   controlMode,
   pidList, perksList, addedPerkLevels,
   lightInt, lightDist, npcControl,
   displayName, displayNameColor, isShowTag,
   inControl := false,
   dudeLightInt, dudeLightDist;


// returns non-zero for a controlled NPC
procedure AllowControl(variable pid) begin
   if (pidList) then begin
      return scan_array(pidList, pid bwand 0xFFFFFF) != -1;
   end
   return party_member_obj(pid);
end

procedure SetLight(variable critter, variable lInt, variable lDist) begin
   if (lInt) then lInt := round((lInt / 65536.0) * 100); // calc percent of intensity
   obj_set_light_level(critter, lInt, lDist); // TODO: make sfall obj_set_light_level function
end

procedure CombatTurn_Handler begin
   variable
      status := get_sfall_arg,
      critter := get_sfall_arg,
      pid, i, perkID, level;

   DEBUGMSG("Combat Turn: " + status + ", by "  + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg)

   pid := obj_pid(critter);
   if (pid == PID_PLAYER and inControl == false) then return;

   if (pid != PID_PLAYER and controlMode != 1 and AllowControl(pid) == false) then begin
      DEBUGMSG("Skip control.")

      if (npcControl) then begin
         npcControl := 0;
         set_dude_obj(real_dude_obj);
         call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light
         move_to(dude_obj, dude_tile, dude_elevation);

         DEBUGMSG("Set dude after NPC control.")
      end
      return;
   end

   if (status == 1) then begin
      set_dude_obj(critter);

      DEBUGMSG("Take control of: " + obj_name(critter))

      if (critter != real_dude_obj) then begin
         if (npcControl == 0) then begin
            dudeLightDist := get_object_data(real_dude_obj, OBJ_DATA_LIGHT_DISTANCE);
            dudeLightInt  := get_object_data(real_dude_obj, OBJ_DATA_LIGHT_INTENSITY);
            obj_set_light_level(real_dude_obj, 0, 0); // dude off light
         end
         npcControl := critter;

         lightDist := get_object_data(critter, OBJ_DATA_LIGHT_DISTANCE);
         lightInt  := get_object_data(critter, OBJ_DATA_LIGHT_INTENSITY);

         // set perks (only work with 4.1.8+)
         foreach (i: perkID in perksList) begin
            addedPerkLevels[i] := 0;

            level := has_trait(TRAIT_PERK, real_dude_obj, perkID);
            if (level) then begin
               addedPerkLevels[i] := level;
               critter_add_trait(critter, TRAIT_PERK, perkID, level);
            end
         end
         // special handling if dude has Jinxed trait/perk
         if (has_trait(TRAIT_TRAIT, real_dude_obj, TRAIT_jinxed) or has_trait(TRAIT_PERK, real_dude_obj, PERK_jinxed_perk)) then begin
            critter_add_trait(critter, TRAIT_PERK, PERK_jinxed_perk, 1);
         end
         intface_redraw;

         obj_set_light_level(critter, 100, 4);
         inControl := true;
      end else if (npcControl) then begin
         call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light
         npcControl := 0; // for dude control
         intface_redraw;
      end

      if (inControl) then begin
         // check preference setting _combat_highlight
         if (read_byte(0x56D38C)) then set_outline(critter, OUTLINE_GREY); // TODO: replace read_byte with normal function

         // center the screen on the controlled critter/dude and remove roof tiles
         move_to(dude_obj, dude_tile, dude_elevation);

         if (displayName and critter != real_dude_obj) then begin
            set_iface_tag_text(displayName, obj_name(critter), displayNameColor);
            show_iface_tag(displayName);
            isShowTag := true;
         end
      end
   end else begin
      if (npcControl) then begin
         call SetLight(critter, lightInt, lightDist); // restore light for controlled NPC
      end
      if (isShowTag) then begin
         hide_iface_tag(displayName);
         isShowTag := false;
      end

      // remove perks before switching control (only work with 4.1.8+)
      if (dude_obj != real_dude_obj) then begin
         DEBUGMSG("Remove perks after NPC control.")
         foreach (i: perkID in perksList) begin
            level := addedPerkLevels[i]; //has_trait(TRAIT_PERK, real_dude_obj, perkID);
            if (level) then critter_rm_trait(critter, TRAIT_PERK, perkID, level);
         end
         // special handling for Jinxed
         critter_rm_trait(critter, TRAIT_PERK, PERK_jinxed_perk, -1);
      end

      if (status < 0) then begin
         set_dude_obj(real_dude_obj);
         call SetLight(dude_obj, dudeLightInt, dudeLightDist);
      end
   end
end

procedure GameModeChange_Handler begin
   if (inControl and (get_game_mode bwand COMBAT) == 0) then begin
      inControl := false;
      npcControl := 0;
      move_to(dude_obj, dude_tile, dude_elevation);
      DEBUGMSG("Move to dude after NPC control.")
   end
end

procedure InventoryMove_Handler begin
   if (npcControl and get_sfall_arg == 3) then begin // armor slot
      if (obj_pid(dude_obj) == PID_MARCUS or proto_data(obj_pid(dude_obj), cr_body_type) != CR_BODY_BIPED) then begin
         display_msg(message_str_game(GAME_MSG_PROTO, 675));
         set_sfall_return(true);
      end
   end
end

procedure InvenWield_Handler begin
   variable
      critter := get_sfall_arg,
      item  := get_sfall_arg,
      slot  := get_sfall_arg,
      event := get_sfall_arg;

   // Fix weapon duplication when equipping in combat
   if (event and slot == INVEN_TYPE_RIGHT_HAND and AllowControl(obj_pid(critter))) then begin
      set_flags(item, get_flags(item) bwand bwnot(FLAG_LEFT_HAND));
   end
end

procedure SetGlobalVar_Handler begin
   if (npcControl and get_sfall_arg == GVAR_PLAYER_REPUTATION and obj_pid(dude_obj) != PID_PLAYER) then begin
      variable value := global_var(GVAR_PLAYER_REPUTATION);
      if (get_sfall_arg > value) then begin
         set_sfall_arg(1, value);
         set_sfall_return(value);
      end
   end
end

procedure start begin
   if game_loaded then begin
      if (sfall_ver_major < 4) then return;

      controlMode := GetConfig("CombatControl", "Mode", 0);
      if (controlMode >= 3) then begin
         if (controlMode == 3) then metarule3(999, 0, 0, 0); // enable feature: order party members to attack a specified target
         controlMode := 0;
      end else if (controlMode == 1 and GetIniConfig("Debugging", "Enable", 0, "ddraw.ini") == 0) then begin
         controlMode := 2;
      end

      if (controlMode > 0) then begin
         displayName := GetConfig("CombatControl", "DisplayName", 0);

         if (displayName) then begin
            variable nameTag := add_iface_tag;
            debug_msg("NPC control - added new box: " + nameTag);
            if (nameTag <= 0) then begin // box added?
               variable maxBoxCount := get_ini_setting("ddraw.ini|Misc|BoxBarCount");
               if (maxBoxCount < 0) then maxBoxCount := 9;
               else maxBoxCount += 4;
               if (displayName < 5 or displayName > maxBoxCount) then displayName := 0;
            end else begin
               displayName := nameTag;
            end
            displayNameColor := GetConfig("CombatControl", "DisplayNameColor", 0);
         end

         pidList := GetConfigListInt("CombatControl", "PIDList");
         if (len_array(pidList) > 0) then
            fix_array(pidList);
         else
            pidList := 0;

         perksList := GetConfigListInt("CombatControl", "PerksList");
         fix_array(perksList);

         addedPerkLevels := create_array_list(len_array(perksList));

         register_hook_proc(HOOK_COMBATTURN, CombatTurn_Handler);
         register_hook_proc(HOOK_GAMEMODECHANGE, GameModeChange_Handler);
         register_hook_proc(HOOK_INVENTORYMOVE, InventoryMove_Handler);
         register_hook_proc(HOOK_INVENWIELD, InvenWield_Handler);

         if (GetConfig("CombatControl", "GainReputation", 0) == 0) then begin
             register_hook_proc(HOOK_SETGLOBALVAR, SetGlobalVar_Handler);
         end
      end else begin
         exit;
      end
   end
end
