Fallout 2 mod (Solved) How do I supply the player with sharpened poles during earlygame? I need ideas

Discussion in 'Fallout General Modding' started by Velizar, Aug 8, 2021.

  1. Velizar

    Velizar First time out of the vault

    35
    Jul 17, 2019
    I'm working on a mod which adds new weapons and I really want to add a harpoon gun which is available from the start of the game and shoots sharpened poles and spears. The problem is that they're very scarce in the original game. It will become obsolete slightly after the Den, so I only need to solve the problem until then.

    One of the goals of the mod is to leave all the existing game maps unchanged if at all possible. This achieves compatibility with other mods and also simplicity.

    I could use Mr. Fixit to make them craftable, but then the player would need to find the materials, and I can't think of anything available in the early game which is a reasonable material for either a spear or a sharpened pole.

    I would love it if there was a way to add it to the list of restocked items to ALL auto-restocking vendors as long as the player's level is 1-6, but I don't think SSL has the necessary metaprogramming utilities - vendors restock using check_restock_item, but I'm not aware of a way to add a hook which gets called whenever that gets called. I definitely don't want my mod to look at individual vendors or their boxes - I strongly prefer to automatically recognize them. Maybe someone more advanced knows of a way to do this? Edit: Maybe I can do something with the generic storage box?

    I could add a new location next to Arroyo with a pole/spear vendor, but I would hate to do this because it's such a clunky and forced solution just for a single weapon - I would rather not have the harpoon gun at all.

    Any better ideas on how to do this?


    Edit: Now that I thought I can't solve it without help and wrote up my problem, I came up with a solution which might work... :roll:
    Here's my idea: add a global procedure which triggers on HOOK_REMOVEINVENOBJ, and when someone is moving their inventory to generic_temp_box, then they're recognized as a vendor so we say that they'll restock sharpened poles and spears once every 5 days, and we add some to their inventory right now. Problems:
    1. This is not in sync with how often they restock their other items, but I think that's ok.
    Also the restock starts ticking down whenever they first swap their inventory, not whenever you enter the map, but I think that's ok too.
    2. I'm assuming that every vendor swaps their inventory with generic_temp_box, is that true? Is that true even in most mods which aren't wild total conversions?
    Edit2: Nope, the above won't work because point 2 is false - most vendors in the game don't use the temp box and use only one box instead.
     
    Last edited: Aug 8, 2021
  2. NovaRain

    NovaRain Casual Modder Modder Moderator

    Mar 10, 2007
    The vendor restock is not some unique process, just a macro consists of various functions. It is checked/run when the player enters a map, so you can try running your additional restock in map_enter_p_proc procedure from a global script.
     
  3. Velizar

    Velizar First time out of the vault

    35
    Jul 17, 2019
    The problem with this is that my script will need to know which NPCs are vendors or which containers are NPC inventories, and I don't want to hardcode that.
     
  4. NovaRain

    NovaRain Casual Modder Modder Moderator

    Mar 10, 2007
    You can read the list of vendor data from an INI file, so people can edit the file to adapt the mod to other TCs without recompiling the script itself.
     
  5. Velizar

    Velizar First time out of the vault

    35
    Jul 17, 2019
    I wrote a scipt which automatically detects vendors. It relies on the fact that they receive items from a container on dialog start, as there are hooks for both moving an item and starting a dialog. There are a few small caveats explained in the comment at the beginning of the file, but overall I'm very happy with it.

    I tested it in Arroyo, Klamath, and Den, and all seems to be working. The only mistake it made was that it didn't detect Mom as a merchant.

    Before writing it I looked at all existing scripts which do an inventory move, and in theory it looked like this approach would work for the vast majority.

    Another reason why I wanted a solution like this is because it allows me to seamlessly add any new types of ammo to the game, and I had some ideas for earlygame weapons which need lategame ammo.
    Code:
    /*******************************************************************************
            Name: Global vendor restock
            Description: Enables restocking of all vendors with custom items
            Imperfections:
              - Restocking happens on opening a dialog, rather than on entering the map
              - This script has their own restock cycle, independent from the NPC's
              - Vendor detection can make mistakes. Must satisfy all of the following:
                * On the first conversation with the NPC, it must "swap inventory"
                    immediately before the dialog opens. No game time can pass
                    from inventory swap until dialog opening
                * Inventory swap means the NPC must receive items from a container
                * The NPC must be a critter and with bartering enabled when the swap occurs
                * At least two different item types must be transferred to its inventory
              False positives include NPCs which don't restock and more.
              False negatives include NPCs which don't immediately swap inventory and more.
              Both of those are acceptable if you want the items to be findable,
                and you don't mind them ending up in (very few) extra NPC inventories.
    *******************************************************************************/
    
    #define NAME                    SCRIPT_GL_H_RSTCK
    
    /* Include Files */
    #include "DEFINE.H"
    #include "COMMAND.h"
    #include "sfall.h"
    #include "define_extra.h"
    
    /* Defines */
    
    #define RECEIVED_ONE_ITEM  (1)
    #define CONFIRMED_MERCHANT (2)
    #define NOT_MERCHANT       (3)
    
    /* Script Procedures */
    procedure start;
    procedure on_game_mode_change;
    procedure on_remove_inventory_object;
    procedure check_npc_for_restock(variable obj);
    
    /* Local Variables which are saved. All Local Variables need to be prepended by LVAR_ */
    
    /*
       Imported variables from the Map scripts. These should only be pointers and variables
       that need not be saved. If a variable Needs to be saved, make it a map variable (MVAR_)
    */
    
    /* Local variables which do not need to be saved between map changes */
    
    // map
    // key: critter PID
    // value: the first time when we saw the NPC get an item from a container
    //        or RECEIVED_ONE_ITEM or CONFIRMED_MERCHANT or NOT_MERCHANT
    variable critterMerchantStatus;
    
    // map
    // key: merchant PID
    // value: the next time it must be restocked by our custom script
    variable merchantNextRestock;
    
    procedure start begin
       if game_loaded then begin
          critterMerchantStatus := load_array("critterMerchantStatus");
          merchantNextRestock := load_array("merchantNextRestock");
          if (critterMerchantStatus == 0) then
             critterMerchantStatus := {};
          if (merchantNextRestock == 0) then
             merchantNextRestock := {};
          save_array("critterMerchantStatus", critterMerchantStatus);
          save_array("merchantNextRestock", merchantNextRestock);
          register_hook_proc(HOOK_GAMEMODECHANGE, on_game_mode_change);
          register_hook_proc(HOOK_REMOVEINVENOBJ, on_remove_inventory_object);
       end
    end
    
    procedure on_game_mode_change begin
       if (get_game_mode == DIALOG) then begin // just entered a dialog
          variable pid = obj_pid(dialog_obj);
          // NPC didn't receive 2+ item types from a container or isn't barterable with
          if (critterMerchantStatus[pid] == 0) then
             critterMerchantStatus[pid] = NOT_MERCHANT;
          else if (critterMerchantStatus[pid] != CONFIRMED_MERCHANT) then begin
             if (game_time == critterMerchantStatus[pid]) then
                critterMerchantStatus[pid] = CONFIRMED_MERCHANT;
             else
                critterMerchantStatus[pid] = NOT_MERCHANT;
          end
          if (critterMerchantStatus[pid] == CONFIRMED_MERCHANT) then
             call check_npc_for_restock(dialog_obj);
       end
    end
    
    procedure on_remove_inventory_object begin
       variable srcObj := get_sfall_arg_at(0),
                removeReason := get_sfall_arg_at(3),
                dstObj := get_sfall_arg_at(4),
                dstPid := obj_pid(dstObj),
                srcAContainer := (obj_item_subtype(srcObj) == item_type_container),
                dstNotAnItem := (obj_item_subtype(dstObj) == -1),
                // Note: would return false for dude_obj
                dstBarterableWith := (CFLG_BARTER == (CFLG_BARTER bwand get_proto_data(dstPid, PROTO_CR_FLAGS)));
       if (removeReason == RMOBJ_CONTAINER and srcAContainer and dstNotAnItem and dstBarterableWith) then begin
          if (critterMerchantStatus[dstPid] == 0) then
             critterMerchantStatus[dstPid] := RECEIVED_ONE_ITEM;
          else if (critterMerchantStatus[dstPid] == RECEIVED_ONE_ITEM) then
             critterMerchantStatus[dstPid] := game_time;
       end
    end
    
    // rewritten check_restock_item using variable who_obj in place of self_obj
    procedure check_restock_item0(variable who_obj, variable the_item, variable min_amt, variable max_amt, variable res_perc) begin
       restock_amt := random(min_amt, max_amt);
       if (obj_is_carrying_obj_pid(who_obj, the_item) < restock_amt) then begin
          if (res_perc >= random(1,100)) then begin
             stock_pid_qty(who_obj, the_item, restock_amt)
          end
       end else begin
          stock_pid_qty(who_obj, the_item, restock_amt)
       end
    end
    
    procedure check_npc_for_restock(variable npc_obj) begin
       variable pid = obj_pid(npc_obj);
       if (merchantNextRestock[pid] < game_time) then begin
          variable stockProbability := 50;
          if (get_sfall_global_int("h_1stNpc") == 0) then begin
             set_sfall_global("h_1stNpc", 1);
             stockProbability := 100;
          end
          if (get_pc_stat(PCSTAT_level) >= 7) then begin
             stockProbability := 0;
          end
          set_self(npc_obj);
          call check_restock_item0(npc_obj, PID_SHARPENED_POLE, 10, 20, stockProbability);
          merchantNextRestock[pid] := (random(2, 4) * ONE_GAME_DAY) + game_time;
       end
    end
    
     
    Last edited: Aug 8, 2021
  6. Mr.Stalin

    Mr.Stalin Mildly Dipped

    529
    Oct 29, 2015
    please more detail, why not detected, the hook doesn't run?
     
  7. Velizar

    Velizar First time out of the vault

    35
    Jul 17, 2019
    The hook is probably fine.

    I haven't investigated, but by design my script can't detect every merchant correctly (it has both false positives and false negatives), since there was no way to do it better. So the error was most likely from my script.

    My assumption was that probably Mom doesn't get a container inventory transferred to her in all dialog branches, or that the container inventory was empty or had only 1 item, but I decided it didn't matter much because unfixable errors were expected and are tolerated.

    If you're curious then I could investigate further to find out exactly why she isn't being recognized.