Jim's flexible random encounter template.

Discussion in 'Fallout General Modding' started by JimTheDinosaur, Mar 12, 2015.

  1. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    I thought I'd stop dumping everything in my ill-named define.h thread, and make a dedicated thread.

    I came to the conclusion a short while ago that much of my megalomania is outside of the scope of the regular game, and would probably result in breaking it completely. So, I want to try and introduce a lot of that megalomania into the virgin soil that is random encounters.

    But enough about me, what can this do for YOU, the humble, non-megalomanic modder? A lot! I hope! Eventually! Introduce new types of critters, weapons and scenery without having to finnick around with worldmap.txt, that horrible thing. You could also make everything a lot more varied by shuffling up the maps, critters, everything considerably (more on that in a bit).

    Now, what would we lose in the process?

    - as far as I know, there's no way to implement the outdoorsman check. I never was terribly fond of the mechanic (I don't have to run away from rats manually, but just need to click a button), but you might disagree.
    - map type (desert, mountain, etc.) becomes harder to pin down based on the world map, unless you were to go through the effort of micro-allocating everything like in the worldmap.txt.

    Now for my latest trick: constructing map templates with an array constructed from the debug text!

    Code:
    procedure build_rectangle_from_text(variable upper_left, variable array) begin
    variable min,max,tile,north_corner,border_corners,east_corner,west_corner,upper_right,lower_left,lower_right,corners,temp_upper_right;
       variable obj,tile_contents,tile_counter,scenery_array,objs,size;
       size:=get_array(array, len_array(array) - 1);   
       temp_upper_right:=tile_num_in_direction(upper_left, 1, size);
       upper_right:=tile_num_in_direction(temp_upper_right, 5, size/2);
       lower_left:=tile_num_in_direction(upper_left, 2, size);
       lower_right:=tile_num_in_direction(upper_right, 2, size);
       corners:=[upper_left, upper_right, lower_left, lower_right];
       min:=array_min(corners)-50;
       max:=array_max(corners)+50;
       //tile:=min;
       tile_counter:=0;
       while tile <= max and tile_counter < len_array(array) - 1 do begin
          //display_msg("f " + len_array(tile_get_objs(tile, dude_elevation)));
          if tile_in_tile_rect(upper_left, upper_right, lower_left, lower_right, tile) then begin     //upper left, upper right, lower left, lower right, tile
             if array[tile_counter] > 0 then
                create_object(array[tile_counter], tile, dude_elevation);
             tile_counter+=1;
          end
          tile+=1;
       end
    
    
    end
    
    
    procedure get_scenery_in_rectangle(variable upper_left, variable size) begin
       variable min,max,tile,north_corner,border_corners,east_corner,west_corner,upper_right,lower_left,lower_right,corners,temp_upper_right;
       variable obj,tile_contents,tile_counter,scenery_array,objs;
       temp_upper_right:=tile_num_in_direction(upper_left, 1, size);
       upper_right:=tile_num_in_direction(temp_upper_right, 5, size/2);
       lower_left:=tile_num_in_direction(upper_left, 2, size);
       lower_right:=tile_num_in_direction(upper_right, 2, size);
       //create_object(PID_BOOKCASE_2HEX_LEFT_LIGHT, upper_left, dude_elevation);
       //create_object(PID_BOOKCASE_2HEX_LEFT_LIGHT, upper_right, dude_elevation);
       //create_object(PID_BOOKCASE_2HEX_LEFT_LIGHT, lower_left, dude_elevation);
       //create_object(PID_BOOKCASE_2HEX_LEFT_LIGHT, lower_right, dude_elevation);
       corners:=[upper_left, upper_right, lower_left, lower_right];
       min:=array_min(corners)-50;
       max:=array_max(corners)+50;
       tile:=min;
       tile_counter:=0;
       scenery_array:=create_array(0,0);
       while tile <= max do begin
          //display_msg("f " + len_array(tile_get_objs(tile, dude_elevation)));
          if tile_in_tile_rect(upper_left, upper_right, lower_left, lower_right, tile) then begin     //upper left, upper right, lower left, lower right, tile
             objs:=tile_get_objs(tile, dude_elevation);
             tile_contents:=0;
             foreach obj in objs begin
                if (obj_type(obj) == OBJ_TYPE_WALL) or (obj_type(obj) == OBJ_TYPE_SCENERY) or (obj_type(obj) == OBJ_TYPE_TILE) or (obj_type(obj) == OBJ_TYPE_ITEM) then begin
                   display_msg(obj_name(obj) + obj_type(obj));
                   tile_contents:=obj_pid(obj);
                end
             end
             call array_push_no_return(scenery_array, tile_contents);
             //create_object(PID_10MM_PISTOL, tile, dude_elevation);
          end
          tile+=1;
          tile_counter+=1;
       end
       call array_push_no_return(scenery_array, size);    //last value in array denotes size
       call debug_list_comprehensive(scenery_array);
       //debug_msg("" + len_array(scenery_array) + debug_array_str(scenery_array));
    end
    
    What this does is allow you to denote a rectangle on the map you're at (I prefer to use two keystrokes, one for upper left corner, one for size based on distance from upper left corner), within which every piece of scenery and every wall is saved in an array (you can also copy critters of course if you want). When I say saved, I mean it's printed in the debug log through debug_list_comprehensive:

    Code:
    
    
    procedure debug_list_comprehensive(variable arr) begin
       variable i := 0, x:=0, k, s, len;
       len := len_array(arr);
       while x < len do begin
          x+=10;
          s := "";
          while i < x and i < len do begin
             s += arr[i];
             if i < len - 1 then
                s += ", ";
             i++;
          end
          if x == 10 then
             debug_msg("[" + s);
          else if x < len then
             debug_msg(s);
          else
             debug_msg(s + "]");
       end
    end
    Suppose I want to copy this part of a building:



    all I do is make the rectangle around it, then check my debug log where I find it all stored as a lovely array, which I just need to assign to a variable:

    Code:
    test_array:=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 50332280, 50332238, 50332293, 50332236, 50332237, 50332239, 50332292, 
    50332238, 50332234, 50332236, 50332269, 0, 0, 0, 0, 50332270, 0, 
    50332270, 0, 50332270, 0, 50332270, 0, 50332270, 50332162, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 50332177, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 50332178, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 50332288, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 50332177, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 50332178, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 50332273, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    13];
    Now I can place it wherever I want:



    Sadly you can't do this copying with roofs and tiles (and probably never will, right @phobos2077?) because there's no way to place them through scripts, so any maps you piece together this way won't ever look very good (especially where cities are concerned: no curbs and stuff), but it should allow you to do some interesting stuff.


    Next up I'll show something I've been doing with randomized containers along walls that might be interesting as well.

    BTW, I just put up the example script segments to show a bit of where I'm coming from, I'll release proper sources once I've got something finalized.


    edit: hmm, seems I cheered too soon, it appears to work properly only about half the time, with the rest of the time the north facing wall getting garbled up... I need to look into fallout's tile numbering a bit more again.
    edit2: oh right, forgot to account for uneven sizes
     
    Last edited: Mar 12, 2015
    • [Like] [Like] x 2
  2. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    Here's two screenshots of the randomized container placing script I talked about, first set to only one type, namely wallsafes (which as you probably know are pretty annoying to place manually):



    As you can see, the script takes into account (or rather is supposed to take into account) careful spacing, so that windows, wall edges and the like aren't blocked, and that only walls "inside" buildings get populated. Now glorious full random:



    Three and four hex containers like the bookcase over there are annoyingly incapable of properly being inserted for the "right" side wall because of clipping, so I left them out.

    Also, for this last screenshot I implemented the step of even more careful spacing near corners because especially in full random they tend to uglily overlap there, which makes it look a bit more empty for that reason.
     
  3. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    Another update on my map template thing: I fixed two problems with it, namely where upper walls wouldn't build prettily, and where stuff would be built incompletely. The latter problem has to do with there being overlapping objects in many cases, which was a stupid oversight on my part (secret blocking tiles overlapping scenery, etc.); I just made it so a second array stores these secondary objects (maybe there are even tertiary objects? wouldn't know really).

    I absolutely love this thing, I haven't felt this ingenious since my burst cone ;).

    You can make pretty huge templates, even though the result is huge ass arrays in the text editor which you have to copy. Would be fun to make a header file where people can add their own templates.

    Here's an example of rebuilding arroyo in the desert:





    note the chosen one scratching his head in amazement as an entire village appears out of thin air.

    the missing roofs must have him baffled as well.
     
    Last edited: Mar 13, 2015
    • [Like] [Like] x 1
  4. darkf

    darkf Caller of the Void Modder

    73
    Jul 12, 2014
    Neato. When do we get to see cool full procedurally generated maps / random encounters? ;)
     
  5. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    I think I've done all I can do with regards to (the basics of) randomized map construction, so all I need to do now is what really mattered to me, namely critters.

    I hate critter prototypes with a passion, so I'm hoping to completely sidestep them. Ideally every critter in a random encounter would be based on one single prototype, with all their characteristics set through script based on inventory/local vars (e.g. you could set a critter's art fid based on armor equipped, base stats and height of skills (i.e. critter "level" and specialization) through local vars, etc.).

    If simply setting the art fid, and other features (e.g. behavior of fire geckos) through script doesn't work out, then I'd at least want a bare minimum of one prototype per art fid, which of course doesn't matter that much in practice.

    Anyway, main point is that I want more, lots more variation in random encounters. Not just in terms of maps (this actually matters least to me), but in terms of what kind of gameplay there is: not just combat (or a couple of merchants) all the time, but dialogue, different skill applications (doctor, repair, science, stealth, etc.). Different factions on maps which don't automatically fight each other but which you can align with, or have to take effort to set up against each other. Etc., etc. That to me is the interesting part.
     
  6. phobos2077

    phobos2077 Mildly Dipped
    Modder

    596
    Apr 24, 2010
    Amazing stuff. Dude, I really wish people like you will be here when new engine(s) will become available (your ideas could be implemented without much obstacles, just a fair amount of work).

    You should have no trouble changing critter art FID's (that's how NPC appearance mod is working, it just changes fid once after dialog) and stuff like skills, traits, etc. Behavior? Just edit some values in AI.txt and change critter AI packet and team number as you like.

    As to roofs/tile - if anyone could point out where you can change those in the engine, I could add scripting functions for those. Tried once to find them but gave up.
     
    • [Like] [Like] x 1
  7. darkf

    darkf Caller of the Void Modder

    73
    Jul 12, 2014
    I agree, and that could bring some interesting results. You could be involved with different gangs' power struggles and by showing support for some, join their factions; similarly, you could walk in on a fight and be asked to step in, choosing either side (or backstabbing one? :D). There's loads of depth you can add to that.

    Maps, though, are still a problem: it's kind of immersion breaking when you keep seeing the same maps cycled over and over. What I'd like to see is completely procedurally generated maps, based on templates like "caves", "city", "desert", etc., and then filled in randomly (with a lot of variation!) via some algorithms.

    I agree. :) That's part of the reason why I want a new engine: mods will be much more expansive and can modify/hook the engine at their will, so mods like this are a no-brainer instead of having to jump through all sorts of nasty hooks to add behavior.

    Very cool stuff, Jim, I hope I continue to see you working on stuff like this!

    Looks like it's part of the *squares array. Not sure about the indices for it though.
     
    Last edited: Mar 14, 2015
  8. UniversalWolf

    UniversalWolf eaten by a grue.

    Aug 28, 2005
    You come up with some really interesting stuff, Jim. Kudos.
     
  9. Continuum

    Continuum Vault Fossil

    Nov 8, 2006
    How about building maps from blocks, something like original XCOM? I'm not an expert in this matter, but isn't Fallout's scenery visually too complex to run algorithms that will put separately walls, tiles (both roof, and floor), and other stuff in nice looking way? Imagine broken curbs that must fit into broken walls, and that broken walls must fit into broken roof tiles - add blockers at the top it.

    With blocks you could easily specify in what order/how to put them, like:

    - NS road (9 blocks in total) - 3 x road block + buildings: 3 x blocks for west + 3 x blocks for east,
    - EW road (9 blocks in total) - 3 x road block + buildings: 3 x blocks for north + 3 x blocks for south,
    - cross road (9 blocks in total) - 1 x crossroad block + 4 x leading-to-crossroad street + 4 x building blocks in each corner.

    You could create basically unlimited amount of blocks.

    Of course, this is for new engine thing.

    But I think that even with blocks there would be visual glitches here or there.
     
    Last edited: Mar 16, 2015
  10. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    Yeah, I agree mostly @Continuum (though copying blockers isn't really that hard imo). I think my template approach is mostly applicable to "standalone" scenery/buildings that mesh well with desert tiles, such as tents and trees. That's why I really only envisage it as being used to populate desert/mountain maps, which could already be fun.

    edit: also coasts, and I suppose the approach could be applied to caves that are less of the labyrinth type (e.g. ghost farm) as long as you have enough surface area to play with and a homogeneous enough tile surface to work with. That essentially leaves out cities like you noted.
     
    Last edited: Mar 16, 2015
  11. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    I put together a script so you can now save random encounters (at least, every critter, item and scenery object on it). This might be useful if only to get back to your car if you've lost it somewhere without gas ;).

    Code:
    
       #define maps_array_coordinates         0
       #define maps_array_map                 1
       #define maps_array_containers          2
       #define maps_array_total               3
       
       #define maps_array_pid                 0
       #define maps_array_loc                 1
       #define maps_array_elevation           2
       #define maps_array_contents            3
       #define maps_array_hp                  4
       #define maps_array_script              5
       #define maps_array_container_total     6
       
       #define maps_array_quantity            1
       #define maps_array_ammo_pid            2
       #define maps_array_ammo_count          3
       #define maps_array_contents_total      4
       
    procedure map_exit_p_proc begin
       variable map_array, container, container_array;
       map_array:=create_array(maps_array_total,0);      //0: coordinates, 1:map, 2:containers (poorly named: any object that is an item or critter or scenery object)
       map_array[maps_array_coordinates]:=create_array(2,0);
       set_array(get_array(map_array, maps_array_coordinates), 0, get_world_map_x_pos);
       set_array(get_array(map_array, maps_array_coordinates), 1, get_world_map_y_pos);
       map_array[maps_array_map]:=cur_map_index;
       map_array[maps_array_containers]:=create_array(0,0);
       foreach container in list_as_array(LIST_GROUNDITEMS) begin
          container_array:=save_map_containers(container);
          call array_push_no_return(get_array(map_array,  maps_array_containers), container_array);      //we add all the container characteristics to the list of containers on the map
       end
       foreach container in list_as_array(LIST_CRITTERS) begin
          container_array:=save_map_containers(container);
          call array_push_no_return(get_array(map_array,  maps_array_containers), container_array);    
       end
       foreach container in list_as_array(LIST_SCENERY) begin
          container_array:=save_map_containers(container);
          call array_push_no_return(get_array(map_array,  maps_array_containers), container_array);
       end
       call array_push_no_return(saved_maps_array, map_array);     //add the map to the list of saved maps   
       debug_msg("map: "+  map_array[maps_array_map]);    
       debug_msg(debug_array_str(saved_maps_array));
       force_encounter(map_array[maps_array_map]);
    end
    
    
    procedure save_map_containers(variable container) begin
       variable container_array, container_contents, counter, item, item_counter,item_pid,item_ammo_count,item_ammo_pi  d,item_array;
       container_array:=create_array(maps_array_container  _total,0);
       set_array(container_array, maps_array_pid, obj_pid(container));
       set_array(container_array, maps_array_loc, tile_num(container));
       set_array(container_array, maps_array_elevation, elevation(container));
       if obj_type(container) == OBJ_TYPE_CRITTER then
          set_array(container_array, maps_array_hp, current_hp(container));
       set_array(container_array, maps_array_script, get_script(container));
       if (obj_type(container) == OBJ_TYPE_CRITTER and not party_member_obj(obj_pid(container))) or 
          (obj_type(container) == OBJ_TYPE_ITEM and obj_item_subtype(container) == item_type_container) then begin
          container_contents:=create_array(0,0);
          counter:=0;
      		while inven_ptr(container, counter) > 0 do begin
      		   item_array:=create_array(maps_array_contents_total  , 0);
      		   item:=inven_ptr(container, counter);
      		   item_pid:=obj_pid(item);
      		   set_array(item_array, maps_array_pid,item_pid);    //first add container's pid
      		   item_counter:=obj_is_carrying_obj(container, item);      //we're using this one to get all different stacks (different ammo pids for weapons, etc)
      		   set_array(item_array, maps_array_quantity, item_counter);    //then quantity
      		   if obj_item_subtype(item) == item_type_weapon then begin
      		      item_ammo_count:=get_weapon_ammo_count(item);
      		      set_array(item_array, maps_array_ammo_count, item_ammo_count);
      		      item_ammo_pid:=get_weapon_ammo_pid(item);
      		      set_array(item_array, maps_array_ammo_pid, item_ammo_pid);
      		   end      //if it's not a weapon, then these stay empty
    	  	   call array_push_no_return(container_contents, item_array); 
    	  	   counter+=1;
    	  	end
       	if len_array(container_contents) > 0 then
             set_array(container_array, maps_array_contents, container_contents);   //if it's just an container or an empty container, then let it empty
    	end
       return container_array;
    end
    
    
    procedure map_enter_p_proc begin
       variable map_array,pid,loc,contents,item,elev,containers_ar  ray,container_array,script, item_pid,cur_hp,item_array,item_count,container;
       if len_array(saved_maps_array) > 0 then begin
          map_array:=saved_maps_array[0];
          containers_array:=get_array(map_array, maps_array_containers);
          foreach container_array in containers_array begin
             pid:=get_array(container_array,maps_array_pid);
             loc:=get_array(container_array,maps_array_loc);
             elev:=get_array(container_array,maps_array_elevati  on);
             if not tile_contains_obj_pid(loc, elev, pid) then begin     //this is to make sure we're not placing already existing objects (e.g. doors)
                script:=get_array(container_array,maps_array_scrip  t);
                container:=create_object_sid(pid, loc, elev, script);
                contents:=get_array(container_array,maps_array_con  tents);
                foreach item_array in contents begin
                   item_pid:=item_array[maps_array_pid];
                   item_count:=item_array[maps_array_quantity];
                   item:=create_object(item_pid, 0, elev);
                   if obj_item_subtype(item) == item_type_weapon then begin
                      set_weapon_ammo_pid(item, get_array(item_array,maps_array_ammo_pid)); 
                      set_weapon_ammo_count(item, get_array(item_array,maps_array_ammo_count)); 
                   end
                   add_mult_objs_to_inven(container, item, item_count);
                end
                if obj_type(container) == OBJ_TYPE_CRITTER then begin
                   cur_hp:=get_array(container_array,maps_array_hp);
                   if current_hp(container) > cur_hp then
                      critter_heal(container, cur_hp - current_hp(container));
                end
             end
          end
       end
    end
    There's three things I can think of atm that count as limitations: (1) if two teams were fighting when you left, they've stopped fighting when you go back, (2) partial ammo stacks get refilled, (3) local vars don't get saved because you'd need to know exactly how many local vars a critter has.

    Not huge issues I suppose, and I'd bypass them all for my own mod anyway, but some stuff to keep in mind if you should want to use it yourself (you'd also need to adapt it properly so that it saves the array for savegames and checks for the coordinates on the world map of course).

    Here's a demo:

     
    Last edited by a moderator: Jan 9, 2016
  12. Sduibek

    Sduibek Creator of Fallout Fixt Moderator Modder

    Oct 27, 2010
    Don't we already have "new engines"? FOnline for one, I'm pretty sure there's at least 1 other. Are these engines not sufficient?

    Anyway I agree with what they said about this being amazing, naturally. I would love to see Fallout have Diablo(1)-style map generation where random encounters are generated procedurally each time, meaning no two encounters will ever be identical.
     
    Last edited: Jun 18, 2015
  13. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    So I'm trying something out with overriding the existing world map so I can do some fun stuff with it (basically I want to introduce some gameplay into world travel: give you different actions to take, make things like caravans and raiders actually move and be spottable on the WM rather than purely random, etc.). Problem is that the graphical hacks I'm using for it are kind of taxing computing wise, meaning I'm having to use rather large (160*160 pixels) tiles, which transition kind of jarringly:



    Here I'm using a rather sizeable resolution (1280-960) and a bigger reticule, and still you're left a bit disoriented when the break in the tiles occurs. Does anybody know of any tricks developers use to make such transitions less jarring?
     
    Last edited by a moderator: Jan 9, 2016
  14. JimTheDinosaur

    JimTheDinosaur Vault Dweller
    Modder

    736
    Mar 17, 2013
    Hmm.. I tried it out with smaller (40*40) tiles, and it's smoother, of course, but sloooow:



    I'm thinking I just fucked up by including the automatic centering of the vanilla world map, instead, if the panning has to be done manually, it'll be less jarring because it won't sneak up on the player...

    edit: yeah, manual works nicely.
     
    Last edited by a moderator: Jan 9, 2016