Jim's Improved UI Tools for Modders

JimTheDinosaur

Vault Dweller
Modder
As most modders know, there's a more or less functional way to introduce custom UI elements into FO2, but it's far from ideal. Using the global script below (i.e. compiled as something starting with gl*) you get a couple of advantages:

1: The procedures work fast. As I get into in this thread, regular button procedures are incredibly slow computing wise.
2: You can use single frame functions. Also see the above thread.
3: You see which button is being pressed. In the original, if you had 10 buttons each calling the same procedure, it would be impossible to know which button was calling it. Now you get both the button, and the menu to which it belongs.
4: Elements are all retrievable in a single array. Previously, you'd have to very carefully keep track of which menus and which buttons you were creating so you wouldn't end up trying to delete or select a non-existent one. Now every menu and button can be easily retrieved.
5: Ease of use. Stuff like making buttons into toggles and including sounds are easier.

A couple of things to note:

- I left out the option to change button states based on hover over, which was possible in the original. I didn't do this for reasons of economy (you'd need to check over each menu and button every frame), but you could of course introduce it yourself.
- You still can't (properly) place UI items on top of existing ones (e.g. over the inventory screen). Doing so requires dirty workarounds like constantly rebuilding buttons which results in flickering, but you can still try doing so of course.

Code:
procedure mouseclick;
procedure hexmoveblock;
/*
menu_name(string): the name of the menu you're placing.
x/y_loc: pixel coordinates of upper left corner
x/y_size: horizontal and vertical size in pixels 
menu_gfx: the name of the .pcx file used for the image (e.g. "PCX\\images\\image.pcx").
*/
procedure create_sfall_menu(variable menu_name, variable x_loc, variable y_loc, variable x_size, variable y_size, variable menu_gfx);
/*
menu_name(string): the name of the menu on which you want to place your buttons (note that you can't place buttons without one).
NOTE: in order to find individual buttons, these are all added in the order you create them, with the first item in the list (0) containing menu information, so that the first button numbered 1, the next 2, and so on.
x/y_loc: pixel coordinates of upper left corner, relative to those of the menu.
x/y_size: horizontal and vertical size in pixels 
button_on/off_gfx: the name of the .pcx files used for the button when switched on and off (e.g. "PCX\\buttons\\button_on.pcx").


NOTE: the following four can be left out of the function call, in which case default values (0) are used.
button_type: use sfall_button_type values to set whether you want the ON graphic to replace the OFF graphic on mouse down, release, or over. 
is_toggle: switches the on and off graphics whether the button is clicked
button_state_begin: should the button start in the on (1) or off (0) position 
button_snd: string with the sound effect to be played on click (e.g. "IB2P1XX1"), 0 returns no sound.
*/
procedure create_sfall_button(variable menu_name, variable x_loc, variable y_loc, variable x_size, variable y_size, variable button_on_gfx, variable button_off_gfx, variable button_type:=0, variable is_toggle:=0, variable button_state_begin:=0, variable button_snd:=0);
procedure sfall_button_proc(variable menu, variable button);
procedure check_sfall_buttons(variable sfall_button_type);
procedure sfall_button_update_gfx(variable button, variable button_num, variable x_loc, variable y_loc, variable x_size, variable y_size);
variable mouse_x, mouse_y,sfall_menu_array;


#define get_flag_ten(flags, flag)  ((flags/(10^(flag-1+1))) % 10)
#define set_flag_ten(flags, flag, value)  (flags - ((get_flag_ten(flags, flag))*(10^(flag-1+1))) + (value*(10^(flag-1+1))))



#define sfall_button_x_loc          0
#define sfall_button_y_loc          1      
#define sfall_button_x_size         2
#define sfall_button_y_size         3
#define sfall_button_button_flags   4
#define sfall_button_snd            5
#define sfall_button_on_gfx         6
#define sfall_button_off_gfx        7
#define sfall_button_total          8


#define button_flags_type     0
#define button_flags_toggle   1
#define button_flags_begin_state    2
#define button_flags_cur_state    3


#define sfall_button_type_down         0
#define sfall_button_type_release      1
//#define sfall_button_type_over       2


procedure start begin
  if game_loaded then begin
    register_hook_proc(HOOK_MOUSECLICK, mouseclick);
    register_hook_proc(HOOK_HEXMOVEBLOCKING, hexmoveblock);
  end else begin
  end
end



procedure create_sfall_menu(variable menu_name, variable x_loc, variable y_loc, variable x_size, variable y_size, variable menu_gfx) begin
    variable array:=create_array(4,0);
   if get_array(sfall_menu_array, menu_name) == 0 then begin    //this means that this category doesn't exist yet, so we have to make it
      sfall_menu_array[menu_name]:=create_array(0,0);
   end
   array[sfall_button_x_loc]:=x_loc;
   array[sfall_button_y_loc]:=y_loc;
   array[sfall_button_x_size]:=x_size;
   array[sfall_button_y_size]:=y_size;
   //need the sizes as well so we know where to block movement
   call array_push(get_array(sfall_menu_array, menu_name), array);    //this adds the x/y info as the first entry in the menu array (the subsequent ones are for the buttons)
    createWin(menu_name, x_loc, y_loc, x_size, y_size);
    selectWin(menu_name);
    display(menu_gfx);
   debug_msg("len_array(sfall_menu_array)" + len_array(sfall_menu_array) + "len_array(get_array(sfall_menu_array, menu_name))" + len_array(get_array(sfall_menu_array, menu_name)));
    showWin;
end




procedure create_sfall_button(variable menu_name, variable x_loc, variable y_loc, variable x_size, variable y_size, variable button_on_gfx, variable button_off_gfx, variable button_type:=0, variable is_toggle:=0, variable button_state_begin:=0, variable button_snd:=0) begin
   variable button_flags:=0;
   variable button_gfx, button_name;
   variable array:=create_array(sfall_button_total,0);
   //now we put together all the info for our individual button:
   array[sfall_button_x_loc]:=x_loc;
   array[sfall_button_y_loc]:=y_loc;
   array[sfall_button_x_size]:=x_size;
   array[sfall_button_y_size]:=y_size;
   button_flags:=set_flag_ten(button_flags, button_flags_type, button_type);
   button_flags:=set_flag_ten(button_flags, button_flags_toggle, is_toggle);
   button_flags:=set_flag_ten(button_flags, button_flags_begin_state, button_state_begin); 
   button_flags:=set_flag_ten(button_flags, button_flags_cur_state, button_state_begin);       //current state is the begin state obvs
   array[sfall_button_button_flags]:=button_flags;
   array[sfall_button_snd]:=button_snd;
   array[sfall_button_on_gfx]:=button_on_gfx;
   array[sfall_button_off_gfx]:=button_off_gfx;
   if button_state_begin == 0 then
      button_gfx:=button_off_gfx;
   else
      button_gfx:=button_on_gfx;
   selectWin(menu_name);      //the menu name is the same as that of the menu we've created before
   button_name:="" + len_array(get_array(sfall_menu_array, menu_name)); //thus, the button identifier is the menu_name(?) + the position in the button list (e.g. "menu1", for the first button in "menu", given that menu0 is the menu info)
   debug_msg("button_name " + button_name + " x_loc " + x_loc + " y_loc " + y_loc + " button_gfx " + button_gfx);
   addButton(button_name, x_loc, y_loc, x_size, y_size);
   addButtonGfx(button_name, button_gfx, button_gfx, button_gfx);    //all the button actions are in their default (off) position, we do the shifting elsewhere
   call array_push(get_array(sfall_menu_array, menu_name), array);    //this adds all the info on our individual button to the button category
   debug_msg("array " + debug_array_str(array) + "len_array(sfall_menu_array)" + len_array(sfall_menu_array) + "len_array(get_array(sfall_menu_array, menu_name))" + len_array(get_array(sfall_menu_array, menu_name)));
   showWin;
end








procedure check_sfall_buttons(variable sfall_button_type) begin
   variable menu_x_loc,menu_y_loc,x_loc,y_loc,x_size,y_size,button_type,button_flags,snd, button_toggle, menu, button,menu_name, button_num, button_state_cur, button_state_begin;
   foreach menu in sfall_menu_array begin
      debug_msg("check menu: " + menu + " " + debug_array_str(menu));
      menu_name:=scan_array(sfall_menu_array, menu);      //menu returns the number corresponding to the array, while the way we find the menu on the map is through its name
      selectWin(menu_name);
      foreach button in menu begin
         debug_msg("check button: " + button + " " + debug_array_str(button));
         button_num:=scan_array(menu, button);
         if button_num == 0 then begin //i.e. it's the information for the menu, not the buttons (so to call it button is a bit of a misnomer hmm)
            menu_x_loc:=button[sfall_button_x_loc];
            menu_y_loc:=button[sfall_button_y_loc];
         end else begin    //it's a button
            x_loc:=button[sfall_button_x_loc];
            y_loc:=button[sfall_button_y_loc];
            x_size:=button[sfall_button_x_size];
            y_size:=button[sfall_button_y_size];
            button_flags:=button[sfall_button_button_flags];
            button_type:=get_flag_ten(button_flags, button_flags_type);
            button_toggle:=get_flag_ten(button_flags, button_flags_toggle);
            button_state_cur:=get_flag_ten(button_flags, button_flags_cur_state);
            button_state_begin:=get_flag_ten(button_flags, button_flags_begin_state);
            snd:=button[sfall_button_snd];
            debug_msg("checking button " + button_num + " sfall_button_type " + sfall_button_type + " button_type " + button_type + " button_type " + button_type);
            if mouse_x >= x_loc + menu_x_loc and
               mouse_x <= x_loc + x_size + menu_x_loc and
               mouse_y >= y_loc + menu_y_loc and
               mouse_y <= y_loc + y_size + menu_y_loc then begin    //i.e. the click is within the area of the button
               debug_msg("check found button " + button_num);
               if button_toggle == 1 then begin
                  if button_type == sfall_button_type then  //we should only update the toggle gfx if the proc is also fired
                     call sfall_button_update_gfx(button, button_num, x_loc, y_loc, x_size, y_size);  
               end else begin    //is not a toggle
                  if not (sfall_button_type == sfall_button_type_release and button_state_cur == button_state_begin) then     //this avoids setting the button gfx on when pressing down outside the button and releasing over: do note that the proc will still fire if the button_type is set to release(!)
                     call sfall_button_update_gfx(button, button_num, x_loc, y_loc, x_size, y_size);
               end
               if sfall_button_type == button_type then begin   //the player's action corresponds to the button type (e.g. button is pressed and set to fire proc if button is pressed)
                  if snd != 0 then        //sound should always play on proc fire(or maybe graphics change?)
                     play_sfx(snd);
                  call sfall_button_proc(menu_name, button_num);                 //everything you want happen when this button is pressed you handle here
               end
            end else begin    //button clicked outside area of button
               if sfall_button_type == sfall_button_type_release then begin      //mouse being released outside of button area can cause graphics to fail to reset
                  if button_toggle != 1 then begin    //toggles neatly only change graphical states when also firing procs so ignore
                     if button_state_cur != button_state_begin then     //all (non-toggle) buttons should be at their begin state when releasing
                        call sfall_button_update_gfx(button, button_num, x_loc, y_loc, x_size, y_size);  
                  end
               end
            end
         end  
      end
      showWin; 
   end
end


procedure sfall_button_update_gfx(variable button, variable button_num, variable x_loc, variable y_loc, variable x_size, variable y_size) begin
   variable button_state_cur, button_on_gfx, button_off_gfx, button_gfx, button_flags;
   button_flags:=button[sfall_button_button_flags];
   button_state_cur:=get_flag_ten(button_flags, button_flags_cur_state);
   button_on_gfx:=button[sfall_button_on_gfx];
   button_off_gfx:=button[sfall_button_off_gfx];
   button_state_cur:=(button_state_cur + 1) % 2;
   button_flags:=set_flag_ten(button_flags, button_flags_cur_state, button_state_cur);     //need to update the changed (graphical) state of the button: note that the state being "on" doesn't necessarily mean the proc is fired
   button[sfall_button_button_flags]:=button_flags;
   if button_state_cur == 0 then  //button is off
      button_gfx:=button_off_gfx;     
   else     //button is on
      button_gfx:=button_on_gfx;
   deleteButton("" + button_num);      //need to remove button first before it can be changed
   addButton("" + button_num, x_loc, y_loc, x_size, y_size);    //regardless of button_type, graphics need to change
   addButtonGfx("" + button_num, button_gfx, button_gfx, button_gfx);
end


procedure sfall_button_proc(variable menu_name, variable button_num) begin
   //here's where your magic happens
   //you can find the menu using sfall_menu_array[menu_name] and the button being pressed with menu_name[button_num] 
end


procedure  hexmoveblock begin
   variable menu, menu_data,menu_x_loc,menu_y_loc,menu_x_size,menu_y_size;
    variable attacker:=get_sfall_arg;
    variable tilenumber:=get_sfall_arg;
    variable elevation_is:=get_sfall_arg;
    variable blocking_is:=get_sfall_arg;   //1 for blocking
   set_sfall_return(attacker);
   set_sfall_return(tilenumber);
   set_sfall_return(elevation_is);
    if attacker == dude_obj then begin
      mouse_x:=get_mouse_x;
      mouse_y:=get_mouse_y;
      foreach menu in sfall_menu_array begin
         menu_data:=menu[0];
         menu_x_loc:=menu_data[sfall_button_x_loc];
         menu_y_loc:=menu_data[sfall_button_y_loc];
         menu_x_size:=menu_data[sfall_button_x_size];
         menu_y_size:=menu_data[sfall_button_y_size];
         if mouse_x >= menu_x_loc and
            mouse_x <= menu_x_size + menu_x_loc and
            mouse_y >= menu_y_loc and
            mouse_y <= menu_y_size + menu_y_loc then begin    //i.e. the click is within the area of the button
            blocking_is:=1;   //mouse is over a menu, so stop movement and stop checking
            break;
         end
       end
       set_sfall_return(blocking_is);
   end
end




procedure mouseclick begin
   variable type:=get_sfall_arg;   //event type: 1 - pressed, 0 - released
   variable button:=get_sfall_arg;   //button number (0 - left, 1 - right, up to 7)
   //if in_game_map then begin      //remember to adds checks here for when you want to check for button presses: doing so in, say, inventory might be problematic
      mouse_x:=get_mouse_x;
      mouse_y:=get_mouse_y;
      if button == 0 then begin
         if type == 0 then begin
            call check_sfall_buttons(sfall_button_type_release);
         end else begin
            call check_sfall_buttons(sfall_button_type_down);
         end
      end 
   //end
end
 
Last edited:
Regarding the flickering of graphics over existing ui elements: For my button, I workarounded this with adding the new button graphic into the original interface graphic. Then the new (working) button is placed exactly over the painted button. This way, even if the button disappears due to reloading, it won't flicker as you always see the fake button on the original interface.

Though, it took a lot time to match the colors correctly. In the end, it's a matter of how much time you want to spend on such a tiny thing...
 
Regarding the flickering of graphics over existing ui elements: For my button, I workarounded this with adding the new button graphic into the original interface graphic. Then the new (working) button is placed exactly over the painted button. This way, even if the button disappears due to reloading, it won't flicker as you always see the fake button on the original interface.

Though, it took a lot time to match the colors correctly. In the end, it's a matter of how much time you want to spend on such a tiny thing...

It might even be easier to just force the game not to load the standard UI to begin with and just build it yourself. I mean, all the graphical elements are right there and you could just load the different menus using tap_key's. I don't think there's anything you couldn't do manually yourself... Is it possible to keep the UI from loading atm, @phobos2077?
 
Last edited by a moderator:
Actually, scratch that last question (you could just build a custom UI right on top of the existing one as long as it was the same size or larger and accomplish the exact same thing).

I think another interesting possible workaround that has become possible with the mouseclick hook and is extremely easy to implement is to disable the ui on mouse release (this way the button pressed down animation for the UI is still played), and then reenable it through a repeating script that checks whether the UI is disabled.

So now all you have to do is check for the mouse location of the release and you can implement your very own menus/actions. You'd also have to block out the hotkeys of course.
 
Hi everyone. I apologize if this is not the right thread for my question, but is there a way to completely disable the menu animations? For example when telling npc to stay right here, and the menu would go down the screen and comes up again. And there is the combat setting screen that does the same menu animations.it is very time consuming. Any idea? tia
 
Hi everyone. I apologize if this is not the right thread for my question, but is there a way to completely disable the menu animations? For example when telling npc to stay right here, and the menu would go down the screen and comes up again. And there is the combat setting screen that does the same menu animations.it is very time consuming. Any idea? tia
There are related settings in sfall:
Code:
;Controls the speed of combat panel animations (lower - faster; valid range: 0..65535)
CombatPanelAnimDelay=1000
;Controls the speed of dialog panel animations (lower - faster; valid range: 0..255)
DialogPanelAnimDelay=33
You can just set both to 0.
 
Thank you nova rain, but i can't find those settings in ddraw.ini. Do you mind telling me which file I should be looking at?
 
Last edited:
Given that this thread is now about questions about the UI:

Where can I find the font used to make those big yellow letters used in windows such as the start game screen and the char screen? I mean the letters/symbols used to write "CHAR POINTS" and "OPTIONAL TRAITS" etc. here (only the SPECIAL stat letters are specially made for the screen):

Fallout-2-character-creation-screen.jpg


I tried searching in the interface art folder and using SetFont, but no success... does anybody have an idea? Maybe @.Pixote. or @Lexx?
 
Last edited by a moderator:
If I remember correct, then this is from a font file and not art. Would make the most sense too, as these texts aren't written on the interface graphics and they are available in multiple language versions (you can change the text via files).
 
If I remember correct, then this is from a font file and not art. Would make the most sense too, as these texts aren't written on the interface graphics and they are available in multiple language versions (you can change the text via files).

Ah right, I've found them in FONT3.AAF (FONT4 are a bigger version, I guess for the menu screen and FONT2 is for the skilldex picture names, FONT0 and 1 I have no clue cause I can't view them with fontviewer). I'm guessing it's naïve to think that there's some easy way to print these fonts on a menu the same way you can with FONT0.FON, etc., using set_font and print_rect, right?

edit: hmm, I have at least been able to convert the font to bmp with fonconverter... I guess I'll have to just transfer it all manually.
 
Last edited:
If I remember correct, then this is from a font file and not art. Would make the most sense too, as these texts aren't written on the interface graphics and they are available in multiple language versions (you can change the text via files).

Ah right, I've found them in FONT3.AAF (FONT4 are a bigger version, I guess for the menu screen and FONT2 is for the skilldex picture names, FONT0 and 1 I have no clue cause I can't view them with fontviewer). I'm guessing it's naïve to think that there's some easy way to print these fonts on a menu the same way you can with FONT0.FON, etc., using set_font and print_rect, right?

edit: hmm, I have at least been able to convert the font to bmp with fonconverter... I guess I'll have to just transfer it all manually.

I'm not sure if sfall has any of the functionality currently but it can certainly be added (through hooks to set_font, text_to_buf, etc.)

Funny coincidence, I just figured out that the Skilldex/UI text was rendered using the font files 2 days ago! Need a nice way to render that now. :)

Does the font converter output colored bitmaps, by the way?
 
I'm not sure if sfall has any of the functionality currently but it can certainly be added (through hooks to set_font, text_to_buf, etc.)

I'm fairly sure it doesn't; guess another thing to bother @phobos2077 about again. I'm still wondering why it is that there's no "regular" way of accessing .aaf files... The description for SetFont over at the opcode playground does say it can't be used for .aaf, but I can't seem to be able to properly google search the opcode playground (must be all the russian) to find other possible mentions of it.



Does the font converter output colored bitmaps, by the way?

Yeah, you set the base color and the converter handles all the gradients to black for you.
 
Last edited by a moderator:
I found those settings in the latest sfall download. The one that came with RP 2.3.3 did not have those settings. I will try to replace the it with this newer version to see if that works. Thanks everyone.
 
after I downloaded the newest sfall and replaced both ddraw.dll and ddraw.ini in rp 2.3.3, the game warned me to change it back. I chose to replace only the ddraw.dll and add the above settings that novarain mentioned to the bottom of the old ddraw.ini. Everything works fine now.
 
after I downloaded the newest sfall and replaced both ddraw.dll and ddraw.ini in rp 2.3.3, the game warned me to change it back. I chose to replace only the ddraw.dll and add the above settings that novarain mentioned to the bottom of the old ddraw.ini. Everything works fine now.
You're supposed to only replace the ddraw.dll file, or such problems will occur. You could of course copy some contents from 3.6's ddraw.ini, but I don't think we'll see that added until the next unofficial patch or RP 2.3.4
 
Could this possibly help for restoring the "Days Left: ###" counter in the engine conversion?
 
New version!

Improvements:
- smoother: the system no longer deletes buttons before replacing them, which was jarring. Instead, every change results in the entire menu being rebuilt underneath the existing menu, after which the old one is deleted, making the transition nice and smooth.

Additions:
- player input text boxes: let the player input his own text, then read it and do silly things based on it!
- text lists: you now have a lot of different options for making text buttons with or without ranges of values attached to them. You can then scroll up and down the list, and change the values with left and right (I'm right now using the numpad for that, because I can't figure out for the life of me how to block out the screen movements when using regular direction keys, but it's still not great).

A very brief demo vid:




You can find it all in the sources for my cheat mod.
 
Last edited by a moderator:
Could this possibly help for restoring the "Days Left: ###" counter in the engine conversion?

I have this weird deja vu feeling for some reason :P.

It doesn't really change anything in that regard. It's still theoretically possible but requires constantly rebuilding it whenever the player clicks somewhere else on the pipboy screen, causing it to once again be placed over the image displayed.
 
Could this possibly help for restoring the "Days Left: ###" counter in the engine conversion?

I have this weird deja vu feeling for some reason :P.

It doesn't really change anything in that regard. It's still theoretically possible but requires constantly rebuilding it whenever the player clicks somewhere else on the pipboy screen, causing it to once again be placed over the image displayed.

Well but I meant somewhere else. Like in main UI or something, for example above the Turn/End combat box on the far right.

Remind me if Sfall scripting can hook keyboard input? If so, I'll just make "/?" in addition to displaying the time & date, also show Days Left. Easy enough and makes sense to me.

If the "in main UI" option would work without pains-in-the-ass and the input-hooking is possible, that'd give us three options/methods which I think is more than sufficient :)
- Period updates in the console window (user can pick once a day, once a week, each)
- Main UI display that is constant
- Triggered console message when pressing "/?" on keyboard.
 
Last edited:
Could this possibly help for restoring the "Days Left: ###" counter in the engine conversion?

I have this weird deja vu feeling for some reason :P.

It doesn't really change anything in that regard. It's still theoretically possible but requires constantly rebuilding it whenever the player clicks somewhere else on the pipboy screen, causing it to once again be placed over the image displayed.

Well but I meant somewhere else. Like in main UI or something, for example above the Turn/End combat box on the far right.

Remind me if Sfall scripting can hook keyboard input? If so, I'll just make "/?" in addition to displaying the time & date, also show Days Left. Easy enough and makes sense to me.

If the "in main UI" option would work without pains-in-the-ass and the input-hooking is possible, that'd give us three options/methods which I think is more than sufficient :)
- Period updates in the console window (user can pick once a day, once a week, each)
- Main UI display that is constant
- Triggered console message when pressing "/?" on keyboard.

Oh yes, that's "easy" enough. For an example of keyboard hooking you can check the sources I've just now posted here. The problems as I see them have to do with presentation only. IIRC the days left counter thingy is assymetrical, and given that PCX doesn't allow for transparency, the old graphic is pretty much out. But if you want something barebones, that's easy enough (hell, you could even just put the info up in the combat log, which'd be the easiest by far).
 
Back
Top