2013-07-02 18:21:41 +00:00
/**
ObjectInteractionMenu
Handles the inventory exchange and general interaction between the player and buildings , vehicles etc .
@ author Zapper
*/
local Name = " $Name$ " ;
local Description = " $Description$ " ;
2014-03-31 18:14:08 +00:00
static const InteractionMenu_SideBarSize = 80 ; // in tenth-em
2013-07-02 18:21:41 +00:00
static const InteractionMenu_Contents = 2 ;
static const InteractionMenu_Custom = 4 ;
local current_objects ;
/*
current_menus is an array with two fields
each field contain a proplist with the attributes :
target : the target object , needs to be in current_objects
2015-03-05 17:53:52 +00:00
menu_object : target of the menu ( usually a dummy object ) ( the ID is always 1 ; the menu ' s sidebar has the ID 2 )
2013-07-02 18:21:41 +00:00
menu_id
2015-03-19 15:22:06 +00:00
forced : ( boolean ) Whether the menu was forced - open ( f . e . by an extra - slot object ) and is not necessarily attached to an outside - world object .
Such an object might be removed from the list when the menu is closed .
2013-07-02 18:21:41 +00:00
menus : array with more proplists with the following attributes :
2015-03-19 15:22:06 +00:00
flag : bitwise flag ( needed for content - menus f . e . )
2013-07-02 18:21:41 +00:00
title
decoration : ID of a menu decoration definition
2014-03-26 22:55:32 +00:00
Priority : priority of the menu ( Y position )
BackgroundColor : background color of the menu
2013-07-02 18:21:41 +00:00
callback : function called when an entry is selected , the function is passed the symbol of an entry and the user data
2014-03-26 22:55:32 +00:00
callback_hover : function called when hovering over an entry , the function is passed everything " callback " gets plus the target of the description box menu
2013-07-02 18:21:41 +00:00
callback_target : object to which the callback is made , ususally the target object ( except for contents menus )
menu_object : MenuStyle_Grid object , used to add / remove entries later
entry_index_count : used to generate unique IDs for the entries
2015-03-01 09:27:29 +00:00
entries_callback : ( callback ) function that can be used to retrieve a list of entries for that menu ( at any point - it might also be called later )
This callback should return an array of entries shown in the menu , the entries are proplists with the following attributes :
symbol : icon of the item
2015-03-19 15:22:06 +00:00
extra_data : custom user data ( internal : in case of inventory menus this is a proplist containing some extra data ( f . e . the one object for unstackable objects ) )
2015-03-01 09:27:29 +00:00
text : text shown on the object ( f . e . count in inventory )
2015-02-27 10:23:20 +00:00
custom ( optional ) : completely custom menu entry that is passed to the grid menu - allows for custom design
2013-07-02 18:21:41 +00:00
unique_index : generated from entry_index_count ( not set by user )
2015-03-01 09:27:29 +00:00
entries : last result of the callback function described above
2013-07-02 18:21:41 +00:00
*/
local current_menus ;
2014-03-26 22:55:32 +00:00
/*
current_description_box contains information about the description box at the bottom of the menu :
target : target object of the description box
symbol_target : target object of the symbol sub - box
desc_target : target object of the description sub - box
*/
local current_description_box ;
2013-07-02 18:21:41 +00:00
// this is the ID of the root window that contains the other subwindows (i.e. the menus which contain the sidebars and the interaction-menu)
local current_main_menu_id ;
2015-04-12 18:24:45 +00:00
// This holds the dummy object that is the target of the center column ("move all left/right").
local current_center_column_target ;
// The Clonk who the menu was opened for.
2013-07-02 18:21:41 +00:00
local cursor ;
public func Close ( ) { return RemoveObject ( ) ; }
public func IsContentMenu ( ) { return true ; }
public func Show ( ) { this . Visibility = VIS_Owner ; return true ; }
public func Hide ( ) { this . Visibility = VIS_None ; return true ; }
func Construction ( )
{
current_objects = [ ] ;
current_menus = [ ] ;
2014-03-26 22:55:32 +00:00
current_description_box = { target = nil } ;
2013-07-02 18:21:41 +00:00
}
func Destruction ( )
{
// we need only to remove the top-level menu target of the open menus, since the submenus close due to a clever use of OnClose!
for ( var menu in current_menus )
{
2014-02-28 21:39:14 +00:00
if ( menu & & menu . menu_object )
2013-07-02 18:21:41 +00:00
menu . menu_object - > RemoveObject ( ) ;
}
2015-01-03 14:32:06 +00:00
// remove all remaining contained dummy objects to prevent script warnings about objects in removed containers
var i = ContentsCount ( ) , obj = nil ;
while ( obj = Contents ( - - i ) )
obj - > RemoveObject ( false ) ;
2013-07-02 18:21:41 +00:00
}
// used as a static function
func CreateFor ( object cursor )
{
var obj = CreateObject ( GUI_ObjectInteractionMenu , AbsX ( 0 ) , AbsY ( 0 ) , cursor - > GetOwner ( ) ) ;
obj . Visibility = VIS_Owner ;
obj - > Init ( cursor ) ;
cursor - > SetMenu ( obj ) ;
return obj ;
}
func Init ( object cursor )
{
2014-03-26 22:55:32 +00:00
this . cursor = cursor ;
2015-03-05 17:53:52 +00:00
var checking_effect = AddEffect ( " IntCheckObjects " , cursor , 1 , 10 , this ) ;
// Notify the Clonk. This can be used to create custom entries in the objects list via helper objects. For example the "Your Environment" tab.
// Note that the cursor is NOT informed when the menu is closed again. Helper objects can be attached to the livetime of this menu by, f.e., effects.
cursor - > ~ OnInteractionMenuOpen ( this ) ;
// And then quickly refresh for the very first time. Successive refreshs will be only every 10 frames.
EffectCall ( cursor , checking_effect , " Timer " ) ;
2013-07-02 18:21:41 +00:00
}
func FxIntCheckObjectsStart ( target , effect , temp )
{
if ( temp ) return ;
EffectCall ( target , effect , " Timer " ) ;
}
func FxIntCheckObjectsTimer ( target , effect , timer )
{
2015-03-19 15:22:06 +00:00
var new_objects = FindObjects ( Find_AtPoint ( target - > GetX ( ) , target - > GetY ( ) ) , Find_NoContainer ( ) ,
// Find only vehicles and structures (plus Clonks ("livings") and helper objects). This makes sure that no C4D_Object-containers (extra slot) are shown.
2015-06-07 10:53:59 +00:00
Find_Or ( Find_Category ( C4D_Vehicle ) , Find_Category ( C4D_Structure ) , Find_OCF ( OCF_Alive ) , Find_ActionTarget ( target ) , Find_Func ( " IsContainerEx " ) ) ,
2015-03-19 15:22:06 +00:00
// But these objects still need to either be a container or provide an own interaction menu.
Find_Or ( Find_Func ( " IsContainer " ) , Find_Func ( " HasInteractionMenu " ) ) ) ;
2015-03-05 17:53:52 +00:00
var equal = GetLength ( new_objects ) = = GetLength ( current_objects ) ;
2013-07-02 18:21:41 +00:00
2015-03-05 17:53:52 +00:00
if ( equal )
{
for ( var i = GetLength ( new_objects ) - 1 ; i > = 0 ; - - i )
{
if ( new_objects [ i ] = = current_objects [ i ] ) continue ;
equal = false ;
break ;
}
}
if ( ! equal )
UpdateObjects ( new_objects ) ;
2013-07-02 18:21:41 +00:00
}
// updates the objects shown in the side bar
// if an object which is in the menu on the left or on the right is not in the side bar anymore, another object is selected
func UpdateObjects ( array new_objects )
{
// need to close a menu?
for ( var i = 0 ; i < GetLength ( current_menus ) ; + + i )
{
2015-02-27 10:23:20 +00:00
if ( ! current_menus [ i ] ) continue ; // todo: I don't actually know why this can happen.
2015-03-19 15:22:06 +00:00
if ( current_menus [ i ] . forced ) continue ;
2015-02-27 10:23:20 +00:00
2013-07-02 18:21:41 +00:00
var target = current_menus [ i ] . target ;
var found = false ;
for ( var obj in new_objects )
{
if ( target ! = obj ) continue ;
found = true ;
break ;
}
if ( found ) continue ;
// not found? close!
// sub menus close automatically (and remove their dummy) due to a clever usage of OnClose
current_menus [ i ] . menu_object - > RemoveObject ( ) ;
current_menus [ i ] = nil ;
}
current_objects = new_objects ;
// need to fill an empty menu slot?
for ( var i = 0 ; i < 2 ; + + i )
{
2015-03-05 17:53:52 +00:00
// If the menu already exists, just update the sidebar.
if ( current_menus [ i ] ! = nil )
{
RefreshSidebar ( i ) ;
continue ;
}
2013-07-02 18:21:41 +00:00
// look for next object to fill slot
for ( var obj in current_objects )
{
// but only if the object's menu is not already open
var is_already_open = false ;
for ( var menu in current_menus )
{
2015-02-27 10:23:20 +00:00
if ( ! menu ) continue ; // todo: I don't actually know why that can happen.
2013-07-02 18:21:41 +00:00
if ( menu . target ! = obj ) continue ;
is_already_open = true ;
break ;
}
if ( is_already_open ) continue ;
// use object to create a new menu at that slot
OpenMenuForObject ( obj , i ) ;
break ;
}
}
}
func FxIntCheckObjectsStop ( target , effect , reason , temp )
{
if ( temp ) return ;
if ( this )
this - > RemoveObject ( ) ;
}
2015-04-12 18:24:45 +00:00
/*
This is the entry point .
Create a menu for an object ( usually from current_objects ) and also create everything around it if it ' s the first time a menu is opened .
*/
2015-03-19 15:22:06 +00:00
func OpenMenuForObject ( object obj , int slot , bool forced )
2013-07-02 18:21:41 +00:00
{
2015-03-19 15:22:06 +00:00
forced = forced ? ? false ;
2013-07-02 18:21:41 +00:00
// clean up old menu
var old_menu = current_menus [ slot ] ;
2015-08-07 14:48:28 +00:00
var other_menu = current_menus [ 1 - slot ] ;
2013-07-02 18:21:41 +00:00
if ( old_menu )
2015-08-07 14:48:28 +00:00
{
// Re-enable entry in (other!) sidebar.
if ( other_menu )
{
GuiUpdate ( { Symbol = nil } , current_main_menu_id , 1 + 1 - slot , old_menu . target ) ;
}
// ..and close old menu.
2013-07-02 18:21:41 +00:00
old_menu . menu_object - > RemoveObject ( ) ;
2015-08-07 14:48:28 +00:00
}
2013-07-02 18:21:41 +00:00
current_menus [ slot ] = nil ;
// before creating the sidebar, we have to create a new entry in current_menus, even if it contains not all information
2015-03-19 15:22:06 +00:00
current_menus [ slot ] = { target = obj , forced = forced } ;
2014-03-26 22:55:32 +00:00
// clean up old inventory-check effects that are not needed anymore
var effect_index = 0 , inv_effect = nil ;
while ( inv_effect = GetEffect ( " IntRefreshContentsMenu " , this , effect_index ) )
{
if ( inv_effect . obj ! = current_menus [ 0 ] . target & & inv_effect . obj ! = current_menus [ 1 ] . target )
RemoveEffect ( nil , nil , inv_effect ) ;
else
2015-04-12 18:24:45 +00:00
+ + effect_index ;
2014-03-26 22:55:32 +00:00
}
2015-03-05 17:53:52 +00:00
// Create a menu with all interaction possibilities for an object.
// Always create the side bar AFTER the main menu, so that the target can be copied.
2013-07-02 18:21:41 +00:00
var main = CreateMainMenu ( obj , slot ) ;
2015-03-05 17:53:52 +00:00
// To close the part_menu automatically when the main menu is closed. The sidebar will use this target, too.
2013-07-02 18:21:41 +00:00
current_menus [ slot ] . menu_object = main . Target ;
2015-03-05 17:53:52 +00:00
// Now, the sidebar.
var sidebar = CreateSideBar ( slot ) ;
2013-07-02 18:21:41 +00:00
2014-03-31 18:14:08 +00:00
var sidebar_size_em = ToEmString ( InteractionMenu_SideBarSize ) ;
2015-04-12 18:24:45 +00:00
var part_menu =
2013-07-02 18:21:41 +00:00
{
2014-03-26 22:55:32 +00:00
Left = " 0% " , Right = " 50%-2em " ,
Bottom = " 100%-14em " ,
2013-07-02 18:21:41 +00:00
sidebar = sidebar , main = main ,
2015-03-05 17:53:52 +00:00
Target = current_menus [ slot ] . menu_object ,
ID = 1
2013-07-02 18:21:41 +00:00
} ;
if ( slot = = 1 )
{
2014-03-26 22:55:32 +00:00
part_menu . Left = " 50%+2em " ;
2014-02-17 14:46:36 +00:00
part_menu . Right = " 100% " ;
2013-07-02 18:21:41 +00:00
}
2015-04-12 18:24:45 +00:00
2013-07-02 18:21:41 +00:00
// need to open a completely new menu?
if ( ! current_main_menu_id )
{
2014-03-26 22:55:32 +00:00
if ( ! current_description_box . target )
{
current_description_box . target = CreateDummy ( ) ;
current_description_box . symbol_target = CreateDummy ( ) ;
current_description_box . desc_target = CreateDummy ( ) ;
}
2015-04-12 18:24:45 +00:00
if ( ! current_center_column_target )
current_center_column_target = CreateDummy ( ) ;
var root_menu =
2013-07-02 18:21:41 +00:00
{
2015-03-05 17:53:52 +00:00
_one_part = part_menu ,
2014-03-26 22:55:32 +00:00
Target = this ,
2015-03-01 09:27:29 +00:00
Decoration = GUI_MenuDeco ,
2015-04-04 09:23:25 +00:00
BackgroundColor = RGB ( 0 , 0 , 0 ) ,
2015-04-12 18:24:45 +00:00
center_column =
{
Left = " 50%-2em " ,
Right = " 50%+2em " ,
Top = " 1.75em " ,
Bottom = " 100%-14em " ,
Style = GUI_VerticalLayout ,
move_all_left =
{
Target = current_center_column_target ,
ID = 10 + 0 ,
Right = " 4em " , Bottom = " 6em " ,
Style = GUI_TextHCenter | GUI_TextVCenter ,
Symbol = Icon_MoveItems , GraphicsName = " Left " ,
Tooltip = " " ,
BackgroundColor = { Std = 0 , Hover = RGBa ( 255 , 0 , 0 , 100 ) } ,
OnMouseIn = GuiAction_SetTag ( " Hover " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
OnClick = GuiAction_Call ( this , " OnMoveAllToClicked " , 0 )
} ,
move_all_right =
{
Target = current_center_column_target ,
ID = 10 + 1 ,
Right = " 4em " , Bottom = " 6em " ,
Style = GUI_TextHCenter | GUI_TextVCenter ,
Symbol = Icon_MoveItems ,
Tooltip = " " ,
BackgroundColor = { Std = 0 , Hover = RGBa ( 255 , 0 , 0 , 100 ) } ,
OnMouseIn = GuiAction_SetTag ( " Hover " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
OnClick = GuiAction_Call ( this , " OnMoveAllToClicked " , 1 )
}
} ,
description_box =
2014-03-26 22:55:32 +00:00
{
Top = " 100%-10em " ,
2014-03-31 18:14:08 +00:00
Margin = [ sidebar_size_em , " 0em " ] ,
2015-03-01 09:27:29 +00:00
BackgroundColor = RGB ( 25 , 25 , 25 ) ,
2015-04-12 18:24:45 +00:00
symbol_part =
2014-03-26 22:55:32 +00:00
{
Right = " 10em " ,
Symbol = nil ,
Margin = " 1em " ,
ID = 1 ,
Target = current_description_box . symbol_target
} ,
desc_part =
{
Left = " 10em " ,
Margin = " 1em " ,
ID = 1 ,
Target = current_description_box . target ,
real_contents = // nested one more time so it can dynamically be replaced without messing up the layout
{
ID = 1 ,
Target = current_description_box . desc_target
}
}
}
2013-07-02 18:21:41 +00:00
} ;
2014-10-11 09:51:26 +00:00
current_main_menu_id = GuiOpen ( root_menu ) ;
2013-07-02 18:21:41 +00:00
}
else // menu already exists and only one part has to be added
{
2015-04-12 18:24:45 +00:00
GuiUpdate ( { _update : part_menu } , current_main_menu_id , nil , nil ) ;
2013-07-02 18:21:41 +00:00
}
2015-04-12 18:24:45 +00:00
// Show "put/take all items" buttons if applicable. Also update tooltip.
var show_grab_all = current_menus [ 0 ] & & current_menus [ 1 ] ;
show_grab_all = show_grab_all
2015-06-07 17:04:43 +00:00
& & ( current_menus [ 0 ] . target - > ~ IsContainer ( ) | | current_menus [ 0 ] . target - > ~ IsClonk ( ) | | current_menus [ 0 ] . target - > ~ AllowsGrabAll ( ) )
& & ( current_menus [ 1 ] . target - > ~ IsContainer ( ) | | current_menus [ 1 ] . target - > ~ IsClonk ( ) | | current_menus [ 1 ] . target - > ~ AllowsGrabAll ( ) ) ;
2015-04-12 18:24:45 +00:00
if ( show_grab_all )
{
current_center_column_target . Visibility = VIS_Owner ;
for ( var i = 0 ; i < 2 ; + + i )
GuiUpdate ( { Tooltip : Format ( " $MoveAllTo$ " , current_menus [ i ] . target - > GetName ( ) ) } , current_main_menu_id , 10 + i , current_center_column_target ) ;
}
else
{
current_center_column_target . Visibility = VIS_None ;
}
2015-08-07 14:48:28 +00:00
// Finally disable object for selection in other sidebar, if available.
if ( other_menu )
{
GuiUpdate ( { Symbol = Icon_Cancel } , current_main_menu_id , 1 + 1 - slot , obj ) ;
}
2015-04-12 18:24:45 +00:00
}
// Tries to put all items from the other menu's target into the target of menu menu_id. Returns nil.
public func OnMoveAllToClicked ( int menu_id )
{
// Sanity checks..
for ( var i = 0 ; i < 2 ; + + i )
{
if ( ! current_menus [ i ] | | ! current_menus [ i ] . target )
return ;
2015-06-07 17:04:43 +00:00
if ( ! current_menus [ i ] . target - > ~ IsContainer ( ) & & ! current_menus [ i ] . target - > ~ IsClonk ( ) & & ! current_menus [ i ] . target - > ~ AllowsGrabAll ( ) )
2015-04-12 18:24:45 +00:00
return ;
}
// Take all from the other object and try to put into the target.
var other = current_menus [ 1 - menu_id ] . target ;
var target = current_menus [ menu_id ] . target ;
2015-06-07 17:04:43 +00:00
var contents = [ ] ;
if ( other - > ~ AllowsGrabAll ( ) )
contents = other - > GetGrabAllObjects ( ) ;
else
contents = FindObjects ( Find_Container ( other ) ) ;
2015-04-12 18:24:45 +00:00
var transfered = 0 ;
for ( var obj in contents )
{
// Sanity, can actually happen if an item merges with others during the transfer etc.
if ( ! obj ) continue ;
var collected = target - > Collect ( obj , true ) ;
if ( collected )
+ + transfered ;
}
if ( transfered > 0 )
{
Sound ( " SoftTouch* " , true , nil , GetOwner ( ) ) ;
return ;
}
else
{
Sound ( " BalloonPop " , true , nil , GetOwner ( ) ) ;
return ;
}
2013-07-02 18:21:41 +00:00
}
2014-02-15 17:59:33 +00:00
// generates a proplist that defines a custom GUI that represents a side bar where objects
2013-07-02 18:21:41 +00:00
// to interact with can be selected
func CreateSideBar ( int slot )
{
2015-08-07 14:48:28 +00:00
var other_menu = current_menus [ 1 - slot ] ;
2014-03-31 18:14:08 +00:00
var em_size = ToEmString ( InteractionMenu_SideBarSize ) ;
2013-07-02 18:21:41 +00:00
var sidebar =
{
Priority = 10 ,
2014-03-31 18:14:08 +00:00
Right = em_size ,
2013-07-02 18:21:41 +00:00
Style = GUI_VerticalLayout ,
2015-03-05 17:53:52 +00:00
Target = current_menus [ slot ] . menu_object ,
ID = 2
2013-07-02 18:21:41 +00:00
} ;
if ( slot = = 1 )
{
2014-03-31 18:14:08 +00:00
sidebar . Left = Format ( " 100%% %s " , ToEmString ( - InteractionMenu_SideBarSize ) ) ;
2014-02-17 14:46:36 +00:00
sidebar . Right = " 100% " ;
2013-07-02 18:21:41 +00:00
}
2015-03-19 15:22:06 +00:00
// Now show the current_objects list as entries.
// If there is a forced-open menu, also add it to bottom of sidebar..
var sidebar_items = nil ;
if ( current_menus [ slot ] . forced )
{
sidebar_items = current_objects [ : ] ;
PushBack ( sidebar_items , current_menus [ slot ] . target ) ;
}
else
sidebar_items = current_objects ;
for ( var obj in sidebar_items )
2013-07-02 18:21:41 +00:00
{
var background_color = nil ;
var symbol = { Std = Icon_Menu_RectangleRounded , OnHover = Icon_Menu_RectangleBrightRounded } ;
// figure out whether the object is already selected
// if so, highlight the entry
if ( current_menus [ slot ] . target = = obj )
{
background_color = RGBa ( 255 , 255 , 0 , 10 ) ;
symbol = Icon_Menu_RectangleBrightRounded ;
}
2015-01-03 14:32:06 +00:00
var priority = 10000 - obj . Plane ;
2015-08-07 14:48:28 +00:00
// Cross-out the entry?
var deactivation_symbol = nil ;
if ( other_menu & & other_menu . target = = obj )
deactivation_symbol = Icon_Cancel ;
// Always show Clonk at top.
2015-01-03 14:32:06 +00:00
if ( obj = = cursor ) priority = 1 ;
2013-07-02 18:21:41 +00:00
var entry =
{
2015-08-07 14:48:28 +00:00
// The object is added as the target of the entry, so it can easily be identified later.
// For example, to apply show the grey haze to indicate that it cannot be clicked.
Target = obj ,
2014-03-31 18:14:08 +00:00
Right = em_size , Bottom = em_size ,
2013-07-02 18:21:41 +00:00
Symbol = symbol ,
2015-01-03 14:32:06 +00:00
Priority = priority ,
2013-07-02 18:21:41 +00:00
Style = GUI_TextBottom | GUI_TextHCenter ,
BackgroundColor = background_color ,
2014-02-16 16:18:34 +00:00
OnMouseIn = GuiAction_SetTag ( " OnHover " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
2013-07-02 18:21:41 +00:00
OnClick = GuiAction_Call ( this , " OnSidebarEntrySelected " , { slot = slot , obj = obj } ) ,
Text = obj - > GetName ( ) ,
2015-08-07 14:48:28 +00:00
obj_symbol = { Symbol = obj , Margin = " 0.5em " , Priority = 1 } ,
obj_symbol_deactivated = { Symbol = deactivation_symbol , Margin = " 1.0em " , Priority = 2 , Target = obj , ID = 1 + slot }
2013-07-02 18:21:41 +00:00
} ;
2014-10-23 21:28:06 +00:00
GuiAddSubwindow ( entry , sidebar ) ;
2013-07-02 18:21:41 +00:00
}
return sidebar ;
}
2015-03-05 17:53:52 +00:00
// Updates the sidebar with the current objects (and closes the old one).
func RefreshSidebar ( int slot )
{
if ( ! current_menus [ slot ] ) return ;
// Close old sidebar? This call will just do nothing if there is no sidebar present.
GuiClose ( current_main_menu_id , 2 , current_menus [ slot ] . menu_object ) ;
var sidebar = CreateSideBar ( slot ) ;
GuiUpdate ( { sidebar = sidebar } , current_main_menu_id , 1 , current_menus [ slot ] . menu_object ) ;
}
2013-07-02 18:21:41 +00:00
func OnSidebarEntrySelected ( data , int player , int ID , int subwindowID , object target )
{
if ( ! data . obj ) return ;
// can not open object twice!
for ( var menu in current_menus )
if ( menu . target = = data . obj ) return ;
OpenMenuForObject ( data . obj , data . slot ) ;
}
2015-04-12 18:24:45 +00:00
/*
Generates and creates one side of the menu .
Returns the proplist that will be put into the main menu on the left or right side .
*/
2013-07-02 18:21:41 +00:00
func CreateMainMenu ( object obj , int slot )
{
2015-04-12 18:24:45 +00:00
var container =
2013-07-02 18:21:41 +00:00
{
Target = CreateDummy ( ) ,
Priority = 5 ,
2014-03-31 18:14:08 +00:00
Right = Format ( " 100%% %s " , ToEmString ( - InteractionMenu_SideBarSize ) ) ,
2014-03-26 22:55:32 +00:00
Style = GUI_VerticalLayout ,
2015-03-01 09:27:29 +00:00
BackgroundColor = RGB ( 25 , 25 , 25 )
2013-07-02 18:21:41 +00:00
} ;
if ( slot = = 0 )
{
2014-03-31 18:14:08 +00:00
container . Left = ToEmString ( InteractionMenu_SideBarSize ) ;
2014-02-17 14:46:36 +00:00
container . Right = " 100% " ;
2013-07-02 18:21:41 +00:00
}
2015-03-01 10:20:33 +00:00
// Do virtually nothing if the building is not ready to be interacted with. This can be caused by several things.
var error_message = nil ;
if ( obj - > GetCon ( ) < 100 ) error_message = Format ( " $MsgNotFullyConstructed$ " , obj - > GetName ( ) ) ;
else if ( Hostile ( cursor - > GetOwner ( ) , obj - > GetOwner ( ) ) ) error_message = Format ( " $MsgHostile$ " , obj - > GetName ( ) , GetTaggedPlayerName ( obj - > GetOwner ( ) ) ) ;
if ( error_message )
{
container . Style = GUI_TextVCenter | GUI_TextHCenter ;
container . Text = error_message ;
return container ;
}
2014-03-26 22:55:32 +00:00
var menus = obj - > ~ GetInteractionMenus ( cursor ) ? ? [ ] ;
2013-07-02 18:21:41 +00:00
// get all interaction info from the object and put it into a menu
// contents first
if ( obj - > ~ IsContainer ( ) | | obj - > ~ IsClonk ( ) )
{
2015-04-12 18:24:45 +00:00
var info =
2013-07-02 18:21:41 +00:00
{
flag = InteractionMenu_Contents ,
title = " $Contents$ " ,
entries = [ ] ,
2015-03-01 09:27:29 +00:00
entries_callback = nil ,
2013-07-02 18:21:41 +00:00
callback = " OnContentsSelection " ,
callback_target = this ,
decoration = GUI_MenuDecoInventoryHeader ,
2014-03-26 22:55:32 +00:00
Priority = 10
2013-07-02 18:21:41 +00:00
} ;
PushBack ( menus , info ) ;
}
current_menus [ slot ] . menus = menus ;
// now generate the actual menus from the information-list
for ( var i = 0 ; i < GetLength ( menus ) ; + + i )
{
var menu = menus [ i ] ;
2014-03-26 22:55:32 +00:00
if ( ! menu . flag )
menu . flag = InteractionMenu_Custom ;
2015-03-01 09:27:29 +00:00
if ( menu . entries_callback )
menu . entries = obj - > Call ( menu . entries_callback , obj ) ;
if ( menu . entries = = nil )
{
FatalError ( Format ( " An interaction menu did not return valid entries. %s -> %v() (object %v) " , obj - > GetName ( ) , menu . entries_callback , obj ) ) ;
continue ;
}
2014-02-15 17:59:33 +00:00
menu . menu_object = CreateObject ( MenuStyle_Grid ) ;
2014-03-26 22:55:32 +00:00
2014-02-17 14:46:36 +00:00
menu . menu_object . Top = " +2em " ;
2013-07-02 18:21:41 +00:00
menu . menu_object . Priority = 2 ;
menu . menu_object - > SetPermanent ( ) ;
2014-03-26 22:55:32 +00:00
menu . menu_object - > SetFitChildren ( ) ;
//menu.menu_object.Bottom = "4em";
menu . menu_object - > SetMouseOverCallback ( this , " OnMenuEntryHover " ) ;
2013-07-02 18:21:41 +00:00
for ( var e = 0 ; e < GetLength ( menu . entries ) ; + + e )
{
var entry = menu . entries [ e ] ;
entry . unique_index = + + menu . entry_index_count ;
2015-02-27 10:23:20 +00:00
// This also allows the interaction-menu user to supply a custom entry with custom layout f.e.
menu . menu_object - > AddItem ( entry . symbol , entry . text , entry . unique_index , this , " OnMenuEntrySelected " , { slot = slot , index = i } , entry [ " custom " ] ) ;
2013-07-02 18:21:41 +00:00
}
2015-04-12 18:24:45 +00:00
var all =
2013-07-02 18:21:41 +00:00
{
2014-03-26 22:55:32 +00:00
Priority = menu . Priority ? ? i ,
Style = GUI_FitChildren ,
2013-07-02 18:21:41 +00:00
title_bar =
{
Priority = 1 ,
2014-03-26 22:55:32 +00:00
Style = GUI_TextVCenter | GUI_TextHCenter ,
Bottom = " +1.5em " ,
2013-07-02 18:21:41 +00:00
Text = menu . title ,
2014-03-26 22:55:32 +00:00
BackgroundColor = 0xa0000000 ,
//Decoration = menu.decoration
hline = { Bottom = " 0.1em " , BackgroundColor = RGB ( 100 , 100 , 100 ) }
2013-07-02 18:21:41 +00:00
} ,
2014-03-26 22:55:32 +00:00
Margin = [ nil , nil , nil , " 0.5em " ] ,
real_menu = menu . menu_object ,
spacer = { Left = " 0em " , Right = " 0em " , Bottom = " 6em " } // guarantees a minimum height
2013-07-02 18:21:41 +00:00
} ;
2014-03-26 22:55:32 +00:00
if ( menu . flag = = InteractionMenu_Contents )
all . BackgroundColor = RGB ( 0 , 50 , 0 ) ;
else if ( menu . BackgroundColor )
all . BackgroundColor = menu . BackgroundColor ;
else if ( menu . decoration )
menu . menu_object . BackgroundColor = menu . decoration - > FrameDecorationBackClr ( ) ;
2014-10-23 21:28:06 +00:00
GuiAddSubwindow ( all , container ) ;
2013-07-02 18:21:41 +00:00
}
// add refreshing effects for all of the contents menus
for ( var i = 0 ; i < GetLength ( menus ) ; + + i )
{
if ( ! ( menus [ i ] . flag & InteractionMenu_Contents ) ) continue ;
2015-03-05 17:53:52 +00:00
AddEffect ( " IntRefreshContentsMenu " , this , 1 , 1 , this , nil , obj , slot , i ) ;
2013-07-02 18:21:41 +00:00
}
return container ;
}
2014-03-26 22:55:32 +00:00
func GetEntryInformation ( proplist menu_info , int entry_index )
2013-07-02 18:21:41 +00:00
{
if ( ! current_menus [ menu_info . slot ] ) return ;
var menu ;
if ( ! ( menu = current_menus [ menu_info . slot ] . menus [ menu_info . index ] ) ) return ;
var entry ;
for ( var possible in menu . entries )
{
if ( possible . unique_index ! = entry_index ) continue ;
entry = possible ;
break ;
}
2014-03-26 22:55:32 +00:00
return { menu = menu , entry = entry } ;
}
func OnMenuEntryHover ( proplist menu_info , int entry_index , int player )
{
var info = GetEntryInformation ( menu_info , entry_index ) ;
if ( ! info . entry ) return ;
// update symbol of description box
2014-10-11 09:51:26 +00:00
GuiUpdate ( { Symbol = info . entry . symbol } , current_main_menu_id , 1 , current_description_box . symbol_target ) ;
2014-03-26 22:55:32 +00:00
// and update description itself
// clean up existing description window in case it has been cluttered by sub-windows
2014-10-11 09:51:26 +00:00
GuiClose ( current_main_menu_id , 1 , current_description_box . desc_target ) ;
2014-03-26 22:55:32 +00:00
// now add new subwindow to replace the recently removed one
2014-10-11 09:51:26 +00:00
GuiUpdate ( { new_subwindow = { Target = current_description_box . desc_target , ID = 1 } } , current_main_menu_id , 1 , current_description_box . target ) ;
2014-03-26 22:55:32 +00:00
// default to description of object
if ( ! info . menu . callback_target | | ! info . menu . callback_hover )
2015-03-19 15:22:06 +00:00
{
var text = Format ( " %s:|%s " , info . entry . symbol . Name , info . entry . symbol . Description ) ;
var obj = nil ;
if ( info . entry . extra_data )
obj = info . entry . extra_data . one_object ;
// For contents menus, we can sometimes present additional information about objects.
if ( info . menu . flag = = InteractionMenu_Contents & & obj )
{
var additional = nil ;
if ( obj - > Contents ( ) )
{
additional = " $Contains$ " ;
var i = 0 , count = obj - > ContentsCount ( ) ;
// This currently justs lists contents one after the other.
// Items are not stacked, which should be enough for everything we have ingame right now. If this is filed into the bugtracker at some point, fix here.
for ( ; i < count ; + + i )
{
if ( i > 0 )
additional = Format ( " %s, " , additional ) ;
additional = Format ( " %s%s " , additional , obj - > Contents ( i ) - > GetName ( ) ) ;
}
}
if ( additional ! = nil )
text = Format ( " %s||%s " , text , additional ) ;
}
GuiUpdateText ( text , current_main_menu_id , 1 , current_description_box . desc_target ) ;
}
2014-03-26 22:55:32 +00:00
else
{
info . menu . callback_target - > Call ( info . menu . callback_hover , info . entry . symbol , info . entry . extra_data , current_description_box . desc_target , current_main_menu_id ) ;
}
}
func OnMenuEntrySelected ( proplist menu_info , int entry_index , int player )
{
var info = GetEntryInformation ( menu_info , entry_index ) ;
if ( ! info . entry ) return ;
2013-07-02 18:21:41 +00:00
var callback_target ;
2014-03-26 22:55:32 +00:00
if ( ! ( callback_target = info . menu . callback_target ) ) return ;
2015-03-01 10:30:46 +00:00
if ( ! info . menu . callback ) return ; // The menu can actually decide to handle user interaction itself and not provide a callback.
2015-03-01 10:20:33 +00:00
var result = callback_target - > Call ( info . menu . callback , info . entry . symbol , info . entry . extra_data , cursor ) ;
2013-07-02 18:21:41 +00:00
2015-03-01 10:30:46 +00:00
// todo: trigger refresh for special value of result?
2013-07-02 18:21:41 +00:00
}
2015-04-04 18:37:06 +00:00
private func OnContentsSelection ( symbol , extra_data )
2013-07-02 18:21:41 +00:00
{
var target = current_menus [ extra_data . slot ] . target ;
if ( ! target ) return ;
// no target to swap to?
if ( ! current_menus [ 1 - extra_data . slot ] ) return ;
var other_target = current_menus [ 1 - extra_data . slot ] . target ;
if ( ! other_target ) return ;
2015-03-26 11:24:48 +00:00
var obj = extra_data . one_object ? ? FindObject ( Find_Container ( target ) , Find_ID ( symbol ) ) ;
2013-07-02 18:21:41 +00:00
if ( ! obj ) return ;
2015-04-04 18:37:06 +00:00
// If stackable, always try to grab a full stack.
// Imagine armory with 200 arrows, but not 10 stacks with 20 each but 200 stacks with 1 each.
if ( obj - > ~ IsStackable ( ) )
{
var others = FindObjects ( Find_Container ( target ) , Find_ID ( symbol ) , Find_Exclude ( obj ) ) ;
2015-04-12 18:24:45 +00:00
for ( var other in others )
2015-04-04 18:37:06 +00:00
{
if ( obj - > IsFullStack ( ) ) break ;
other - > TryAddToStack ( obj ) ;
}
}
// More special handling for Stackable items..
2015-03-26 11:24:48 +00:00
var handled = obj - > ~ TryPutInto ( other_target ) ;
if ( handled | | other_target - > Collect ( obj , true ) )
2013-07-02 18:21:41 +00:00
{
Sound ( " SoftTouch* " , true , nil , GetOwner ( ) ) ;
return true ;
}
else
{
Sound ( " BalloonPop " , true , nil , GetOwner ( ) ) ;
return false ;
}
}
func FxIntRefreshContentsMenuStart ( object target , proplist effect , temp , object obj , int slot , int menu_index )
{
if ( temp ) return ;
2014-03-26 22:55:32 +00:00
effect . obj = obj ; // the property (with this name) is externally accessed!
2013-07-02 18:21:41 +00:00
effect . slot = slot ;
effect . menu_index = menu_index ;
effect . last_inventory = [ ] ;
}
func FxIntRefreshContentsMenuTimer ( target , effect , time )
{
if ( ! effect . obj ) return - 1 ;
2015-03-19 15:22:06 +00:00
// Helper object used to track extra-slot objects. When this object is removed, the tracking stops.
var extra_slot_keep_alive = current_menus [ effect . slot ] . menu_object ;
2015-03-05 17:53:52 +00:00
// The fast interval is only used for the very first check to ensure a fast startup.
// It can't be just called instantly though, because the menu might not have been opened yet.
if ( effect . Interval = = 1 ) effect . Interval = 5 ;
2013-07-02 18:21:41 +00:00
var inventory = [ ] ;
var obj , i = 0 ;
while ( obj = effect . obj - > Contents ( i + + ) )
{
var symbol = obj - > GetID ( ) ;
2015-03-19 15:22:06 +00:00
var extra_data = { slot = effect . slot , menu_index = effect . menu_index , one_object = nil /* for unstackable containers */ } ;
2013-07-02 18:21:41 +00:00
// check if already exists (and then stack!)
var found = false ;
2015-03-19 15:22:06 +00:00
// Never stack containers with (different) contents, though.
var is_container = obj - > ~ IsContainer ( ) ;
var has_contents = obj - > ContentsCount ( ) ! = 0 ;
// For extra-slot objects, we should attach a tracking effect to update the UI on changes.
if ( obj - > ~ HasExtraSlot ( ) )
2013-07-02 18:21:41 +00:00
{
2015-03-19 15:22:06 +00:00
var j = 0 , e = nil ;
var found_tracker = false ;
while ( e = GetEffect ( nil , obj , j + + ) )
{
if ( e . keep_alive ! = extra_slot_keep_alive ) continue ;
found_tracker = true ;
break ;
}
if ( ! found_tracker )
{
var e = AddEffect ( " ExtraSlotTracker " , obj , 1 , 30 + Random ( 60 ) , this ) ;
e . keep_alive = extra_slot_keep_alive ;
e . callback_effect = effect ;
}
2013-07-02 18:21:41 +00:00
}
2015-03-26 11:24:48 +00:00
// How many objects are this object?!
var object_amount = obj - > ~ GetStackCount ( ) ? ? 1 ;
2015-03-19 15:22:06 +00:00
// Empty containers can be stacked.
if ( ! ( is_container & & has_contents ) )
{
for ( var inv in inventory )
{
if ( inv . symbol ! = symbol ) continue ;
if ( inv . has_contents ) continue ;
2015-03-26 11:24:48 +00:00
inv . count + = object_amount ;
2015-03-19 15:22:06 +00:00
inv . text = Format ( " %dx " , inv . count ) ;
found = true ;
break ;
}
}
2015-03-26 11:24:48 +00:00
else // Can't stack? Remember object..
extra_data . one_object = obj ;
2015-03-19 15:22:06 +00:00
// Add new!
2013-07-02 18:21:41 +00:00
if ( ! found )
{
2015-03-19 15:22:06 +00:00
// Do we need a custom entry when the object has contents?
var custom = nil ;
if ( is_container )
{
// Use a default grid-menu entry as the base.
custom = MenuStyle_Grid - > MakeEntryProplist ( symbol , nil ) ;
// Pack it into a larger frame to allow for another button below.
// The entries with contents are sorted to the back of the inventory menu. This makes for a nicer layout.
custom = { Right = custom . Right , Bottom = " 8em " , top = custom , Priority = 10000 + obj - > GetValue ( ) } ;
// Then add a little container-symbol (that can be clicked).
2015-04-12 18:24:45 +00:00
custom . bottom =
2015-03-19 15:22:06 +00:00
{
Top = " 4em " ,
BackgroundColor = { Std = 0 , Selected = RGBa ( 255 , 100 , 100 , 100 ) } ,
OnMouseIn = GuiAction_SetTag ( " Selected " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
OnClick = GuiAction_Call ( this , " OnExtraSlotClicked " , { slot = effect . slot , one_object = obj , ID = obj - > GetID ( ) } ) ,
container =
{
Symbol = Chest ,
Priority = 1
}
} ;
// And if the object has contents, show the first one, too.
if ( has_contents )
{
// This icon won't ever be stacked. Remember it for a description.
extra_data . one_object = obj ;
// Add to GUI.
custom . bottom . contents =
{
Symbol = obj - > Contents ( 0 ) - > GetID ( ) ,
Margin = " 0.25em " ,
Priority = 2
} ;
// Possibly add text for stackable items - this is an special exception for the Library_Stackable.
var count = obj - > Contents ( 0 ) - > ~ GetStackCount ( ) ;
count = count ? ? obj - > ContentsCount ( obj - > Contents ( 0 ) - > GetID ( ) ) ;
if ( count > 1 )
{
custom . bottom . contents . Text = Format ( " %dx " , count ) ;
custom . bottom . contents . Style = GUI_TextBottom | GUI_TextRight ;
}
// Also make the chest smaller, so that the contents symbol is not obstructed.
custom . bottom . container . Bottom = " 2em " ;
custom . bottom . container . Left = " 2em " ;
}
}
// Add to menu!
2015-03-26 11:24:48 +00:00
var text = nil ;
if ( object_amount > 1 )
text = Format ( " %dx " , object_amount ) ;
2015-03-19 15:22:06 +00:00
PushBack ( inventory ,
{
symbol = symbol ,
extra_data = extra_data ,
has_contents = ( is_container & & has_contents ) ,
2015-03-26 11:24:48 +00:00
custom = custom ,
count = object_amount ,
text = text
2015-03-19 15:22:06 +00:00
} ) ;
2013-07-02 18:21:41 +00:00
}
}
2014-03-26 22:55:32 +00:00
2013-07-02 18:21:41 +00:00
if ( GetLength ( inventory ) = = GetLength ( effect . last_inventory ) )
{
var same = true ;
2015-04-12 18:24:45 +00:00
for ( var i = GetLength ( inventory ) - 1 ; i > = 0 ; - - i )
2013-07-02 18:21:41 +00:00
{
if ( inventory [ i ] . symbol = = effect . last_inventory [ i ] . symbol
& & inventory [ i ] . text = = effect . last_inventory [ i ] . text ) continue ;
same = false ;
break ;
}
if ( same ) return 1 ;
}
2014-03-27 18:43:38 +00:00
effect . last_inventory = inventory [ : ] ;
2013-07-02 18:21:41 +00:00
DoMenuRefresh ( effect . slot , effect . menu_index , inventory ) ;
return 1 ;
}
2015-03-19 15:22:06 +00:00
func FxExtraSlotTrackerTimer ( object target , proplist effect , int time )
{
if ( ! effect . keep_alive ) return - 1 ;
return 1 ;
}
// This is called by the extra-slot library.
func FxExtraSlotTrackerUpdate ( object target , proplist effect )
{
// Simply overwrite the inventory cache of the IntRefreshContentsMenu effect.
// This will lead to the inventory being upated asap.
if ( effect . callback_effect )
effect . callback_effect . last_inventory = [ ] ;
}
func OnExtraSlotClicked ( proplist extra_data )
{
var menu = current_menus [ extra_data . slot ] ;
if ( ! menu ) return ;
var obj = extra_data . one_object ;
if ( ! obj | | obj - > Contained ( ) ! = menu . target )
{
// Maybe find a similar object? (case: stack of empty bows and one was removed -> user doesn't care which one is displayed)
for ( var o in FindObjects ( Find_Container ( menu . target ) , Find_ID ( extra_data . ID ) ) )
{
obj = o ;
if ( ! obj - > Contents ( ) )
break ;
}
if ( ! obj ) return ;
}
2015-03-26 11:24:48 +00:00
OpenMenuForObject ( obj , extra_data . slot , true ) ;
2015-03-19 15:22:06 +00:00
}
2015-03-01 09:27:29 +00:00
// This function is supposed to be called when the menu already exists (is open) and some sub-menu needs an update.
// Note that the parameter "new_entries" is optional. If not supplied, the /entries_callback/ for the specified menu will be used to fill the menu.
2013-07-02 18:21:41 +00:00
func DoMenuRefresh ( int slot , int menu_index , array new_entries )
{
// go through new_entries and look for differences to currently open menu
// then try to only adjust the existing menu when possible
// the assumption is that ususally only few entries change
var menu = current_menus [ slot ] . menus [ menu_index ] ;
var current_entries = menu . entries ;
2015-03-01 09:27:29 +00:00
if ( ! new_entries & & menu . entries_callback )
new_entries = current_menus [ slot ] . target - > Call ( menu . entries_callback , this . cursor ) ;
2013-07-02 18:21:41 +00:00
2015-03-01 09:27:29 +00:00
// step 0.1: update all items where the symbol and extra_data did not change but other things (f.e. the text)
2014-03-26 22:55:32 +00:00
// this is done to maintain a consistent order that would be shuffled constantly if the entry was removed and re-added at the end
for ( var c = 0 ; c < GetLength ( current_entries ) ; + + c )
{
var old_entry = current_entries [ c ] ;
var found = false ;
var symbol_equal_index = - 1 ;
for ( var ni = 0 ; ni < GetLength ( new_entries ) ; + + ni )
{
var new_entry = new_entries [ ni ] ;
if ( ! EntriesEqual ( new_entry , old_entry ) )
{
2015-03-01 09:27:29 +00:00
if ( ( new_entry . symbol = = old_entry . symbol ) & & ( new_entry . extra_data = = old_entry . extra_data ) )
2014-03-26 22:55:32 +00:00
symbol_equal_index = ni ;
continue ;
}
found = true ;
break ;
}
// if the entry exist just like that, we do not need to do anything
// same, if we don't have anything to replace it with, anyway
if ( found | | symbol_equal_index = = - 1 ) continue ;
// now we can just update the symbol with the new data
var new_entry = new_entries [ symbol_equal_index ] ;
2015-03-01 09:27:29 +00:00
menu . menu_object - > UpdateItem ( new_entry . symbol , new_entry . text , old_entry . unique_index , this , " OnMenuEntrySelected " , { slot = slot , index = menu_index } , new_entry [ " custom " ] , current_main_menu_id ) ;
2014-03-26 22:55:32 +00:00
new_entry . unique_index = old_entry . unique_index ;
// make sure it's not manipulated later on
current_entries [ c ] = nil ;
}
2013-07-02 18:21:41 +00:00
// step 1: remove (close) all current entries that have been removed
for ( var c = 0 ; c < GetLength ( current_entries ) ; + + c )
{
2014-03-26 22:55:32 +00:00
var old_entry = current_entries [ c ] ;
if ( ! old_entry ) continue ;
2013-07-02 18:21:41 +00:00
// check for removal
var removed = true ;
for ( var new_entry in new_entries )
{
2014-03-26 22:55:32 +00:00
if ( ! EntriesEqual ( new_entry , old_entry ) ) continue ;
2013-07-02 18:21:41 +00:00
removed = false ;
break ;
}
if ( removed )
{
2014-03-26 22:55:32 +00:00
menu . menu_object - > RemoveItem ( old_entry . unique_index , current_main_menu_id ) ;
2013-07-02 18:21:41 +00:00
current_entries [ c ] = nil ;
}
}
// step 2: add new entries
for ( var c = 0 ; c < GetLength ( new_entries ) ; + + c )
{
var new_entry = new_entries [ c ] ;
2014-03-26 22:55:32 +00:00
// the entry was already updated before?
if ( new_entry . unique_index ! = nil ) continue ;
2013-07-02 18:21:41 +00:00
var existing = false ;
for ( var old_entry in current_entries )
{
if ( old_entry = = nil ) // might be nil as a result of step 1
continue ;
if ( ! EntriesEqual ( new_entry , old_entry ) ) continue ;
existing = true ;
// fix unique indices for the new array
new_entry . unique_index = old_entry . unique_index ;
break ;
}
if ( existing ) continue ;
new_entry . unique_index = + + menu . entry_index_count ;
2015-03-01 09:27:29 +00:00
menu . menu_object - > AddItem ( new_entry . symbol , new_entry . text , new_entry . unique_index , this , " OnMenuEntrySelected " , { slot = slot , index = menu_index } , new_entry [ " custom " ] , current_main_menu_id ) ;
2013-07-02 18:21:41 +00:00
}
menu . entries = new_entries ;
}
func EntriesEqual ( proplist entry_a , proplist entry_b )
{
return entry_a . symbol = = entry_b . symbol
& & entry_a . text = = entry_b . text
2015-03-01 09:27:29 +00:00
& & entry_a . extra_data = = entry_b . extra_data
& & entry_a . custom = = entry_b . custom ;
2013-07-02 18:21:41 +00:00
}
func CreateDummy ( )
{
var dummy = CreateContents ( Dummy ) ;
dummy . Visibility = VIS_Owner ;
dummy - > SetOwner ( GetOwner ( ) ) ;
return dummy ;
}
func RemoveDummy ( object dummy , int player , int ID , int subwindowID , object target )
{
if ( dummy )
dummy - > RemoveObject ( ) ;
2014-02-14 22:28:18 +00:00
}
2014-03-26 22:55:32 +00:00
// updates the interaction menu for an object iff it is currently shown
2015-03-01 09:27:29 +00:00
func UpdateInteractionMenuFor ( object target , callbacks )
2014-03-26 22:55:32 +00:00
{
for ( var slot = 0 ; slot < GetLength ( current_menus ) ; + + slot )
{
var current_menu = current_menus [ slot ] ;
2015-04-12 18:24:45 +00:00
if ( ! current_menu | | current_menu . target ! = target ) continue ;
2015-03-01 09:27:29 +00:00
if ( ! callbacks ) // do a full refresh
OpenMenuForObject ( target , slot ) ;
else // otherwise selectively update the menus for the callbacks
{
for ( var callback in callbacks )
{
for ( var menu_index = 0 ; menu_index < GetLength ( current_menu . menus ) ; + + menu_index )
{
var menu = current_menu . menus [ menu_index ] ;
if ( menu . entries_callback ! = callback ) continue ;
DoMenuRefresh ( slot , menu_index ) ;
}
}
}
2014-03-26 22:55:32 +00:00
}
}
/*
Updates all interaction menus that are currently attached to an object .
This function can be called at all times , not only when a menu is open , making it more convenient for users , because there is no need to track open menus .
2015-03-01 09:27:29 +00:00
If the / callbacks / parameter is supplied , only menus that use those callbacks are updated . That way , a producer can f . e . only update its " queue " menu .
2014-03-26 22:55:32 +00:00
*/
2015-03-01 09:27:29 +00:00
global func UpdateInteractionMenus ( callbacks )
2014-03-26 22:55:32 +00:00
{
if ( ! this ) return ;
2015-03-01 09:27:29 +00:00
if ( callbacks & & GetType ( callbacks ) ! = C4V_Array ) callbacks = [ callbacks ] ;
2014-03-26 22:55:32 +00:00
for ( var interaction_menu in FindObjects ( Find_ID ( GUI_ObjectInteractionMenu ) ) )
2015-03-01 09:27:29 +00:00
interaction_menu - > UpdateInteractionMenuFor ( this , callbacks ) ;
2014-03-26 22:55:32 +00:00
}