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