/*

   NPC Armor Appearance mod (Lite version)

   A cut-down version of the mod included in sfall 4.x modders pack. Used to replace the scripted part of B-Team mod.
   Appropriate graphics are required for this mod to work.
   Can be adopted to any mod by adjusting armor PIDs, allowed weapon anim codes, NPC PIDs and NPC FIDs in INI file.

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

   Requires sfall 3.8.29 or higher

   version 1.4

*/

#include "main.h"

#define IS_ARMOR(item)   (obj_type(item) == OBJ_TYPE_ITEM and obj_item_subtype(item) == item_type_armor)
#define IS_WEAPON(item)  (obj_type(item) == OBJ_TYPE_ITEM and obj_item_subtype(item) == item_type_weapon)

#define FID_OBJ_CRITTER  (0x01000000)

procedure update_armor_apperance;
procedure canuseweapon_handler;

variable
   modIni := "npcarmor.ini",
   defaultFids,
   armorPidMap, // maps armor PID to it's "type" - leather armor, metal, power armor, etc.
   npcMap,
   altWeapon,
   unWieldWeapon;

/*
   Returns the FID value set in the config settings for party member armor: Jacket, Leather, etc.
*/
procedure check_armor_change(variable critter, variable armor, variable isWield) begin
   variable npc, armorType, fid;
   npc := npcMap[obj_pid(critter)];
   if (npc) then begin
      if (isWield == false) then begin
         // display_msg("No armor fid: " + npc["Default"]);
         return npc["Default"];
      end else if (armor) then begin
         armorType := armorPidMap[obj_pid(armor)];
         fid := npc[armorType] or defaultFids[armorType];
         if (fid == 0 or fid == -1) then fid := npc["Default"];
         // display_msg("Change fid: " + fid + ", npc: " + npc[armorType] + ", default:" + defaultFids[armorType]);
         return fid;
      end
   end
   return -1; // engine default
end

/*
   Checks if a party member can use the offered weapon
*/
procedure check_weapon_change(variable critter, variable weapon, variable isWield) begin
   variable npc, newWeaponAnim, weaponAnimList, i;
   npc := npcMap[obj_pid(critter)];
   if (npc) then begin
      if (isWield) then begin
         newWeaponAnim := get_proto_data(obj_pid(weapon), PROTO_WP_ANIM);
         if newWeaponAnim then begin // anim code 0 - none/unarmed
            weaponAnimList := npc["WeaponAnims"];
            if (weaponAnimList != 0) then begin
               foreach (i in string_split(weaponAnimList, ",")) begin
                  if (newWeaponAnim == atoi(i)) then return -1; // can use (engine default)
               end
            end
            return 0; // can't use
         end
      end else begin
         unWieldWeapon := obj_pid(weapon);
      end
   end
   return -1; // engine default
end

/*
   Finds the first weapon that can be used by the party member in the inventory
*/
procedure search_alt_weapon(variable critter) begin
   variable obj, res, i := 0;
   obj := inven_ptr(critter, 0);
   while (obj) do begin
      if (obj_item_subtype(obj) == item_type_weapon and weapon_is_unusable(obj) == false) then begin
         if (unWieldWeapon == 0 or unWieldWeapon != obj_pid(obj)) then begin
            res := check_weapon_change(critter, obj, 1);
            if (res == -1) then begin
               altWeapon := obj;
               wield_obj_critter(critter, obj);
               altWeapon := 0;
               break;
            end
         end
      end
      i++;
      obj := inven_ptr(critter, i);
   end
   unWieldWeapon := 0;
end

// for NPCs when they change armor/weapon themselves
procedure invenwield_handler begin
   variable critter, item, fid, slot, isWield, canWield;
   critter := get_sfall_arg;
   item    := get_sfall_arg;
   slot    := get_sfall_arg;
   isWield := get_sfall_arg;

   if (/*critter and*/ item and slot == INVEN_TYPE_WORN) then begin
      fid := check_armor_change(critter, item, isWield);
      if (fid != -1) then begin
         if art_exists(fid) then begin
            art_change_fid_num(critter, fid);
         end else begin
            debug_msg("[Error] NPC armor mod: Invenwield missing FID: " + fid);
         end
      end
   end else if (/*critter and*/ item and slot == INVEN_TYPE_RIGHT_HAND) then begin
      if (altWeapon == item) then return;
      canWield := check_weapon_change(critter, item, isWield);
      set_sfall_return(canWield);
      if (canWield != -1) then begin
         call search_alt_weapon(critter); // wield weapon by script
      end
   end
end

// when changing armor from inventory while controlling other NPCs
procedure adjustfid_handler begin
   variable fid, armor, weapAnim, currFid, newFid;
   if (dude_obj != real_dude_obj) then begin
      currFid := get_sfall_arg;
      if ((currFid bwand 0x0F000000) != FID_OBJ_CRITTER) then return;
      armor := critter_inven_obj(dude_obj, INVEN_TYPE_WORN);
      fid := check_armor_change(dude_obj, armor, armor != 0);
      if (fid != -1) then begin
         weapAnim := currFid bwand 0xF000;
         newFid := fid bwand 0xFFFF0FFF bwor weapAnim;
         if art_exists(newFid) then begin
            set_sfall_arg(0, newFid);
            set_sfall_return(newFid);
         end else begin
            debug_msg("[Error] NPC armor mod: Adjust FID missing: " + newFid);
         end
      end
   end
end

// when changing weapon from inventory while controlling other NPCs
procedure inventorymove_handler begin
   variable slot, item, canWield;
   slot := get_sfall_arg;
   item := get_sfall_arg;
   if (dude_obj != real_dude_obj) then begin
      if ((slot == INVEN_TYPE_RIGHT_HAND or slot == INVEN_TYPE_LEFT_HAND) and IS_WEAPON(item)) then begin
         canWield := check_weapon_change(dude_obj, item, item != 0);
         set_sfall_return(canWield);
      end
   end
end

procedure start begin
   variable sect, sects, armorTypes, armorType, npc, pid, pids, i;
   if game_loaded then begin
      if (sfall_ver_major >= 4 or sfall_ver_build < 29) then return;

      armorTypes := get_ini_section(modIni, "ArmorTypes");
      armorPidMap := create_array_map;
      foreach (armorType: pids in armorTypes) begin
         pids := string_split(pids, ",");
         foreach (pid in pids) begin
            armorPidMap[atoi(pid)] := armorType;
         end
      end

      defaultFids := get_ini_section(modIni, "Default");
      fix_array(defaultFids);
      foreach (armorType: i in defaultFids) begin
         defaultFids[armorType] := atoi(i);
      end

      npcMap := create_array_map;

      i := 1;
      sect := get_ini_section(modIni, ""+i);
      while (sect.PID) do begin
         npc := create_array_map;
         npc["WeaponAnims"] := sect["WeaponAnims"];
         npc["Default"] := atoi(sect["Default"]);
         foreach (armorType: pids in armorTypes) begin
            if (sect[armorType]) then begin
               npc[armorType] := atoi(sect[armorType]);
            end
         end
         npcMap[atoi(sect.PID)] := npc;

         i += 1;
         sect := get_ini_section(modIni, ""+i);
      end

      call update_armor_apperance;

      register_hook_proc(HOOK_INVENWIELD, invenwield_handler);
      register_hook_proc(HOOK_ADJUSTFID, adjustfid_handler);
      register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler);
      register_hook_proc_spec(HOOK_CANUSEWEAPON, canuseweapon_handler);

      debug_msg("NPC armor appearance mod: Done.");
   end
end

procedure update_armor_apperance begin
   variable npc, arItem, fid;
   foreach (npc in party_member_list_critters) begin
      if (npc == dude_obj) then continue;
      arItem := critter_inven_obj(npc, INVEN_TYPE_WORN);
      if (arItem) then begin
         fid := check_armor_change(npc, arItem, true);
         if (fid == -1 or art_exists(fid) == false) then continue;
         art_change_fid_num(npc, fid);
      end
   end
end

// prevent NPCs from equipping "unusable" weapons - for 3.8.40+
procedure canuseweapon_handler begin
   variable critter, canUse;
   critter := get_sfall_arg;
   if (critter != dude_obj and get_sfall_arg_at(3)) then begin
      canUse := check_weapon_change(critter, get_sfall_arg, true);
      if (canUse == false) then begin
         set_sfall_arg(3, 0);
         set_sfall_return(0); // overrides the result of engine function. Any non-zero value allows using the weapon
      end
   end
end
