Fallout 2 mod Scripting Help Needed - object can't read inventory of another object?

Stuporstar

First time out of the vault
Hi there. I'm pretty new to Fallout 2 modding, but I've been modding Morrowind for almost two decades (and am well familiar with how finicky C-based scripting can be). I've managed to figure out most of the tools and basic syntax already, as well as working with the various header macros. Anyway, I've been working on a mini-mod to extend the timer for Arroyo, using the RPU as a base. I've got the basic mini quests and dialogue all working, but after weeks of figuring out this stuff myself, I'm completely stumped on this problem.

Specifically, this line is returning 0 when it shouldn't be:
Code:
Item_Added:=(obj_is_carrying_obj_pid(PID_ARROYO_ELDER,PID_GOLDEN_GECKO_PELT));

I'll add some context. This is the whole bit (everything I'm not currently testing commented out), inside procedure map_update_p_proc:
Code:
   variable Item_Count;
   variable Item_Adjust;
   variable Item_Added;

   if (map_var(MVAR_Hides_Given) > 0) then begin // this is working in the elder's script
//      Item_Count:=self_item_count(PID_GOLDEN_GECKO_PELT);
      Item_Added:=(obj_is_carrying_obj_pid(PID_ARROYO_ELDER,PID_GOLDEN_GECKO_PELT)); // BROKEN!
      give_xp(Item_Added); // using as a temporary debug message I can see in the message box
      set_map_var(MVAR_Hides_Given, 0); // so it doesn't spam
    
//      if (map_var(MVAR_Hides_Given) > Item_Count) then begin // hides have either been given or stolen
//         Item_Added := obj_is_carrying_obj_pid(PID_ARROYO_ELDER,PID_GOLDEN_GECKO_PELT);
//         if (Item_Added > 0) then begin
//            Item_Adjust := rm_mult_objs_from_inven(PID_ARROYO_ELDER, PID_GOLDEN_GECKO_PELT, Item_Added);
//            add_mult_objs_to_inven(self_obj, PID_GOLDEN_GECKO_PELT, Item_Adjust);
//            Number_Days := (Item_Added * 30);
//            inc_global_var_amt(GVAR_ARROYO_RELIEF, Number_Days);
//         end
//         else begin  // hides likely stolen from chest - but it's triggering every time
//            Amount_Stolen := (map_var(MVAR_Hides_Given) - Item_Count);
//            Number_Days := (Amount_Stolen * 30);
//            set_map_var(MVAR_Hides_Given,Item_Count);
//            dec_global_var_amt(GVAR_ARROYO_RELIEF, Number_Days);
//            call stolen_from;
//         end
//      end
   end

What happens is I get a "0 exp" message after I give the pelts to the Elder. I've tried all sorts of things, like making a #define for that variable instead, but it just doesn't work.

I tried testing with this instead:
Code:
   if (map_var(MVAR_Hides_Given) > 0) then begin // this is working in the elder's script
      Item_Count:=self_item_count(PID_GOLDEN_GECKO_PELT);
//      Item_Added:=(obj_is_carrying_obj_pid(PID_ARROYO_ELDER,PID_GOLDEN_GECKO_PELT)); // BROKEN!
      give_xp(Item_Count); // using as a temporary debug message I can see in the message box
      set_map_var(MVAR_Hides_Given, 0); // so it doesn't spam
end

Then in-game I pick the lock, stick some hides in the chest, and then give one to the elder to trigger the map var. I get the right number in the chest in the "x exp" message. So it seems the chest can read its own inventory just fine -- it just refuses to do so with the elder.

I've thought of a few workarounds that involve moving the elder's whole inventory back and forth (and dealing with the UP's anti-stealing bit), which I can try. But would that require making a chest with a unique PID like the trader chests? I'd also just like to know if this (reading other object's inventory) is just something the scripts can't handle or if I'm missing something here.
 
It's "obj_is_carrying_obj_pid(object obj, int pid)" and you passed a PID as an object pointer.
And if you want to see a (debug) message in the message box, just use display_msg(), like
Code:
display_msg("obj_is_carrying_obj_pid: " + Item_Added);
 
Last edited:
Oh, I see! So I use the object_ptr instead of its PID. That's what I was missing. I'd also wrongly assumed you needed to reference a .msg file to use display_msg().

Thanks!

I have another question regarding this mod I'm working on. I noticed the ddraw.ini has lines to change the time for the dream sequences, so is it impossible to change the actual time limit for Arroyo inside the game via. script? I have the sinking sense that all I'm doing by adjusting the game time limit in command.h and global.h is for role-playing flavor and I have to adjust the .ini outside the game.
 
Huh, I'm still doing something ridiculously wrong. I've tried making an object pointer to the Elder using variables and defines in a variety of ways and it's not working.

So I have to ask the very rudimentary question -- how do I make a simple object pointer? All I can find in any scripts are self_obj, dude_obj, party_member_obj, and even prize_figher_obj, but nothing that just says "this critter with this PID."

Looking at merchant box scripts, it looks like I do need to use a workaround by importing an exported variable like a box_obj, because they all use the self_obj pointer with generic_temp_box as a go-between instead of passing inventory directly back and forth.

Edit: I guess maybe it does need the temp box, because just exporting the variable Elder_Ptr from the map script, importing it and having the elder define it in her script, and then closing the dialogue and letting the box directly yank the items out of her inventory freezes the game. Whoops
 
Last edited:
Ok I'm really stumped now. I've been trying to use an exported variable for the container just like the merchant scripts, but the variable keeps returning 0 and not working. I keep getting results like this.
Screenshot (3).png

Here is the simplified script bits on the box:
Code:
export variable Elder_box_obj;
procedure start begin
   variable Elder_box_obj := self_obj;
   display_msg("object is " + Elder_box_obj);
end

And the elder:
Code:
import variable Elder_box_obj;
procedure look_at_p_proc begin
display_msg("object is " + Elder_box_obj);
end

It makes no difference whether I export the variable from the map script instead. This is just me trying to figure out the simplest chain of events, where I export the variable from the box and have it defined as the box at start. Then import it into the Elder's script and have it return the variable number again when you look at her. When called from her script it's always 0, only returning correctly when calling from the box.

I did a second test where I defined the elder in a variable as well, and when called from the container's script it always returns correctly. So this seems to be a script load order problem, with the Elder always loading first.

Edit: Working with that in mind, I decided to try pulling the items from the elder's inventory using the box's script again and finally got it to work without crashing with this:
Code:
if (map_var(MVAR_Hides_Given) > 0) then begin // triggered in Elder's script
      if (map_var(MVAR_Hides_Given) > Item_Adjust) then begin
         Item := obj_carrying_pid_obj(Elder_Ptr,PID_GOLDEN_GECKO_PELT);
         Item_Count := obj_is_carrying_obj_pid(Elder_Ptr,PID_GOLDEN_GECKO_PELT);
         Item_Adjust += rm_mult_objs_from_inven(Elder_Ptr, item, item_count);
         add_mult_objs_to_inven(Elder_box_obj, item, item_count);
   end
Nice and clean, only one imported variable necessary, and no temp box needed. I think I want to save Item_Adjust though, or a separate LVAR to track how many are in the container to detect if the player steals them back? I dunno I'll figure it out.

Edit 2: Working 100% now, with added checks for player sticking the hides in her chest, stealing them back, and so on:
Code:
item_added := obj_is_carrying_obj_pid(Elder_Ptr,PID_GOLDEN_GECKO_PELT);
   item_count := self_item_count(PID_GOLDEN_GECKO_PELT);
   if (item_added) then // item given to elder
      inc_map_var_amt(MVAR_Hides_Given, item_added);
   if (map_var(MVAR_Hides_Given) < item_count) then // item added to chest
      set_map_var(MVAR_Hides_Given, item_count);
   else if ((map_var(MVAR_Hides_Given)) > (local_var(LVAR_Hide_Count))) then begin // hides given
      if (item_added > 0) then begin // given to elder
         item := obj_carrying_pid_obj(Elder_Ptr,PID_GOLDEN_GECKO_PELT);
         item_adjust := rm_mult_objs_from_inven(Elder_Ptr, item, item_added);
         add_mult_objs_to_inven(self_obj, item, item_added);
      end
      else // added to chest
         item_added := (item_count - (local_var(LVAR_Hide_Count)));
      inc_local_var_amt(LVAR_Hide_Count, item_added);
      item_adjust := (item_added * 15);
      inc_global_var_amt(GVAR_ARROYO_RELIEF, item_adjust);
      display_msg("Added to chest " + item_added);
   end
   else if (local_var(LVAR_Hide_Count) > item_count) then begin  // hides likely stolen from chest
      item_adjust := (local_var(LVAR_Hide_Count) - item_count);
      dec_local_var_amt(LVAR_Hide_Count, item_adjust);
      item_adjust := (item_adjust * 15);
      dec_global_var_amt(GVAR_ARROYO_RELIEF, item_adjust);
      set_map_var(MVAR_Hides_Given, item_count); // gecko pelts left in chest
      call stolen_from;
   end
 
Last edited:
I have another basic syntax question I can't find an answer to (probably because it's "no" lol). Can variables handle integer ranges (when not random)? Or do I always have to do something like this?
Code:
if ((global_var(GVAR_Var_Name) > 0) and (global_var(GVAR_Var_Name) < 3)) then
 
You export the variable from the map script and import them into your box script and into the character script. Look again at how Fo2 scripts are doing it and do exactly the same.
 
Hmm, that's the first thing I did though, and it had the script load order problem, which is why I tried simplifying everything. I got it working by making it point to the Elder and having the container do the work. Probably the better way to go anyway since the Elder gets transported to the Enclave. This way there's nothing in Arroyo being called on in her script.

Would exporting the variable from the map script cause trouble when she's moved? Because it's exported from her script right now, since she always seems to load first.

I've got almost everything working in this Arroyo mod now except for being able to alter the times of the Hakunin dream sequences from a script inside the game. Is that even possible?
 
Last edited:
Exporting/importing temp variables only works on the map with the attached map script. If you want the variable to work on the enclave map as well, you will have to export it from that map script as well.
 
Oh no, I don't want it to work on the enclave map. I just worry it might make her script crash when she moves.
 
Oh ok, thanks! How can you tell I've mostly modded Bethsesda games lol

Edit: To finish up this mod, I'm looking at the sfall documentation to figure out how to change the timing of the Hakunin dream sequences. Is get_ini_setting and set_ini_setting in a global script what I'd use to change the ddraw.ini settings by reading the global variable I've made to add time in-game. I'm looking at these settings specifically:
Code:
MovieTimer_artimer1=90
MovieTimer_artimer2=180
MovieTimer_artimer3=270
MovieTimer_artimer4=360
 
Last edited:
There is no script command for changing the timers. You'd have to figure out the byte position in the fo2 exe file and then read/write it via script. But it also requires unsafe scripting to be enabled. Overwriting those settings in the .ini file via script is a bad idea, since it only takes effect if you restart the game.
 
Ah. Does that mean if you manually change MovieTimer_artimer1=90 in the .ini in the middle of a game it wouldn't take effect either? Edit: No, I tested that out with an older install, and you can change it in the middle of a game. You meant restart as in exit the game and run it again, didn't you. I don't think that will be too much of a problem, because you have the chance to extend the timers well before they come up, and the chance of someone marathoning the game that long is low.

But do the movie timers and/or TIME_LIMIT_1/reached_tl_1 (in global.h and command.h) not affect Arroyo being replaced by the destroyed map bridge in a year? Is that what would have to be rewritten in the exe?
 
Last edited:
I don't think that will be too much of a problem, because you have the chance to extend the timers well before they come up
In the past I easily played the game without closing / restarting it and seeing the dream sequences. It's really not impossible.

The time limit stuff in script determines when something happens, it has no influence on when the cutscenes are playing.
 
I guess it's something I could put in the readme along with the installation instructions, regarding the .ini settings.

Yeah, I found the time limit stuff in global.h and command.h are really only called in some Arroyo character's scripts, which is already taken care of in my mod.

I haven't been able to find anything related the Arroyo bridge map change though, which is more crucial than the dream sequences (which can be explained away as premonitions if they still happen to the player lol). The only thing I found is in maps.txt:
Code:
[Map 005]
lookup_name=Arroyo Bridge
map_name=arbridge
music=17arroyo
;ambient_sfx=hmxxxxbg:100
;ambient_sfx=gntlwin1:20, gntlwind:20, dogbark:20, dogbark1:20, gustwind:10, gustwin1:10
ambient_sfx=gntlwin1:25, gntlwind:25, dogbark:20, dogbark1:20, gustwind:5, gustwin1:5
saved=Yes
;destroyed_maps_as=22     ; Which map does this map re-map as when destroyed?
;destroyed_maps_on_var=0  ; Which global script variable determines
                          ;    that an area is destroyed?

But the destroyed Arroyo map is 127, not 022, and the global script variable 0 is player reputation, so that's confusing.
 
Those two lines are commented out as seen on the ; in front of it. The game ignores them.
 
Doh, I didn't notice that. Does that mean it's deep in the .exe and not possible to prevent it happening with a script? (only before you find the GECK)

Edit: Another problem I'm having is it turns out using a global variable in the TIME_LIMIT defines in global.h is returning the global's index number and not its value. It works in scripts, but in the header it comes out as TIME_LIMIT + 791 instead of the value of 30 it was while testing.

Edit2: I figured it out. This works:
Code:
#ifndef Time_Adjust
variable time_increase := 0;
#define Time_Adjust                       if global_var(GVAR_ARROYO_RELIEF) then \
                                             time_increase := global_var(GVAR_ARROYO_RELIEF);
#endif
#ifndef TIME_LIMIT_1
#define TIME_LIMIT_1                        (90 + time_increase)
#endif
#ifndef TIME_LIMIT_2
#define TIME_LIMIT_2                        (180 + time_increase)
#endif
#ifndef TIME_LIMIT_3
#define TIME_LIMIT_3                        (270 + time_increase)
#endif
#ifndef TIME_LIMIT_4
#define TIME_LIMIT_4                        (360 + time_increase)
#endif
But I think it requires calling Time_Adjust in the script before getting the value of the TIME_LIMIT? That's ok.
 
Last edited:
Seems I've been wringing my hands over the Arroyo bridge destruction for nothing. I tested it in a fresh game, set the global variable, walked out of the village, saved and exited, and set the game time a year ahead in F2Explorer. When I reloaded and walked back to Arroyo, it isn't destroyed at all!

I actually saved the village from getting destroyed in a year!!!

Here's the global script I made. I thought about only doing it on game exit, but couldn't figure that out, so decided to use the hook for the esc menu instead. If there's a better way to do it, I'm definitely interested to hear it.
Code:
#include "sfall_headers\sfall.h"
#include "../headers/global.h"

procedure start;

#define artimer1 get_ini_setting("ddraw.ini|misc|MovieTimer_artimer1")
#define artimer2 get_ini_setting("ddraw.ini|misc|MovieTimer_artimer2")
#define artimer3 get_ini_setting("ddraw.ini|misc|MovieTimer_artimer3")
#define artimer4 get_ini_setting("ddraw.ini|misc|MovieTimer_artimer4")

procedure start begin
   if game_loaded then begin
      set_global_script_type(1);
//      set_global_script_repeat(60);
      register_hook_proc_spec(HOOK_GAMEMODECHANGE, start);
   end
   else begin
      variable mode := get_game_mode;
      if (mode andAlso mode != ESCMENU) then return;
      if (mode == ESCMENU) then begin
         Time_Adjust
         if (artimer1 != TIME_LIMIT_1) then
            set_ini_setting("ddraw.ini|misc|MovieTimer_artimer1",TIME_LIMIT_1);
         if (artimer2 != TIME_LIMIT_2) then
            set_ini_setting("ddraw.ini|misc|MovieTimer_artimer2",TIME_LIMIT_2);
         if (artimer3 != TIME_LIMIT_3) then
            set_ini_setting("ddraw.ini|misc|MovieTimer_artimer3",TIME_LIMIT_3);
         if (artimer4 != TIME_LIMIT_4) then
            set_ini_setting("ddraw.ini|misc|MovieTimer_artimer4",TIME_LIMIT_4);
      end
   end
end
 
Last edited:
But what's the point of this? The timers in Fo2 don't influence anything. Arroyo gets destroyed once you find the Geck.
 
Back
Top