Jim's flexible random encounter template.

JimTheDinosaur

Vault Dweller
Modder
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:

Mq4TcHf.png


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:

LCnGEyh.png


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:
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):

IuWEchC.png


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:

ymtFZLt.png


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.
 
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:

dX2ZPsj.png


v5fnU6C.png


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:
Neato. When do we get to see cool full procedurally generated maps / random encounters? ;)
 
Neato. When do we get to see cool full procedurally generated maps / random encounters? ;)

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.
 
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.
 
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.

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.

phobos2077 said:
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).

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!

phobos2077 said:
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.

Looks like it's part of the *squares array. Not sure about the indices for it though.
 
Last edited:
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:
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:
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:
phobos2077 said:
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).
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.

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:
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:
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:
Back
Top