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