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$ " ;
2015-09-06 17:02:31 +00:00
static const InteractionMenu_SideBarSize = 40 ; // in tenth-em
2014-03-31 18:14:08 +00:00
2013-07-02 18:21:41 +00:00
static const InteractionMenu_Contents = 2 ;
static const InteractionMenu_Custom = 4 ;
2015-09-06 14:35:20 +00:00
/*
This contains an array with a proplist for every player number .
The attributes are also always attached to every interaction menu on creation ( menu . minimized = InteractionMenu_Attributes [ plr ] . minimized ; ) .
The following properties are either used or nil :
minimized ( bool ) : whether the player minimized the menu .
A minimized menu does not show some elements ( like the description box ) .
*/
static InteractionMenu_Attributes ;
2013-07-02 18:21:41 +00:00
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
2017-03-05 19:44:41 +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 ) .
2017-03-13 13:56:05 +00:00
The function is called in the object that the menu was opened for and passes the player ' s Clonk as the first argument .
2015-03-01 09:27:29 +00:00
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-10-24 08:44:01 +00:00
fx : ( optional ) effect that gets a " OnMenuOpened(int menu_id, object menu_target, int subwindow_id) " callback once which can be used to update a specific entry only
2017-03-05 19:44:41 +00:00
entries_callback_parameter ( optional ) :
2017-03-13 13:56:05 +00:00
Passed as second argument to entries_callback . Can be used for custom information .
2017-06-26 15:28:40 +00:00
entries_callback_target ( optional ) :
By default the object for which the menu is opened , can be changed for more involved constructions .
2015-03-01 09:27:29 +00:00
entries : last result of the callback function described above
2015-10-24 08:44:01 +00:00
additional properties that are added are :
ID : ( menu ) id of the entry as returned by the menu_object - can be used for updating
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 ; }
2015-10-16 15:07:33 +00:00
// Called when the menu is open and the player clicks outside.
public func OnMouseClick ( ) { return Close ( ) ; }
2013-07-02 18:21:41 +00:00
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 )
2015-11-22 21:37:58 +00:00
{
// Notify the object of the deleted menu.
DoInteractionMenuClosedCallback ( menu . target ) ;
2013-07-02 18:21:41 +00:00
menu . menu_object - > RemoveObject ( ) ;
2015-11-22 21:37:58 +00:00
}
2013-07-02 18:21:41 +00:00
}
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 ) ;
2017-01-22 09:49:58 +00:00
// Remove check objects effect.
if ( cursor )
RemoveEffect ( " IntCheckObjects " , cursor ) ;
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 ;
2015-09-06 14:35:20 +00:00
if ( InteractionMenu_Attributes = = nil )
InteractionMenu_Attributes = [ ] ;
// Transfer some attributes from the player configuration.
if ( GetLength ( InteractionMenu_Attributes ) > cursor - > GetOwner ( ) )
{
var config = InteractionMenu_Attributes [ cursor - > GetOwner ( ) ] ;
obj . minimized = GetProperty ( " minimized " , config ) ? ? false ;
}
else
{
obj . minimized = false ;
}
2013-07-02 18:21:41 +00:00
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
}
2015-09-04 14:53:32 +00:00
func FxIntCheckObjectsStart ( target , effect fx , temp )
2013-07-02 18:21:41 +00:00
{
if ( temp ) return ;
2015-09-04 14:53:32 +00:00
EffectCall ( target , fx , " Timer " ) ;
2013-07-02 18:21:41 +00:00
}
2015-09-04 14:53:32 +00:00
func FxIntCheckObjectsTimer ( target , effect fx )
2013-07-02 18:21:41 +00:00
{
2016-01-14 12:03:18 +00:00
// If contained, leave the search area intact, because otherwise we'd have to pass a "nil" parameter to FindObjects (which needs additional hacks).
// This is a tiny bit slower (because the area AND the container have to be checked), but the usecase of contained Clonks is rare anyway.
var container_restriction = Find_NoContainer ( ) ;
var container = target - > Contained ( ) ;
if ( container )
{
container_restriction = Find_Or ( Find_Container ( container ) , Find_InArray ( [ container ] ) ) ;
}
2016-08-09 22:01:27 +00:00
var new_objects = FindObjects ( Find_AtRect ( target - > GetX ( ) - 5 , target - > GetY ( ) - 10 , 10 , 21 ) , container_restriction , Find_Layer ( target - > GetObjectLayer ( ) ) ,
2015-09-20 15:36:45 +00:00
// Find all containers and objects with a custom menu.
Find_Or ( Find_Func ( " IsContainer " ) , Find_Func ( " HasInteractionMenu " ) ) ,
2016-01-18 03:15:23 +00:00
// Do not show objects with an extra slot though - even if they are containers. They count as items here and can be accessed via the surroundings tab.
Find_Not ( Find_And ( Find_Property ( " Collectible " ) , Find_Func ( " HasExtraSlot " ) ) ) ,
2015-09-20 15:36:45 +00:00
// Show only objects that the player can see.
Find_Func ( " CheckVisibility " , GetOwner ( ) ) ,
2015-09-16 01:13:53 +00:00
// Normally sorted by z-order. But some objects may have a lower priority.
Sort_Reverse ( Sort_Func ( " GetInteractionPriority " , target ) )
) ;
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 ;
2015-09-04 14:53:32 +00:00
// Still existant? Nothing to do!
if ( GetIndexOf ( new_objects , target ) ! = - 1 ) continue ;
2013-07-02 18:21:41 +00:00
// 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 ;
2015-11-30 08:40:53 +00:00
// Notify the target of the now closed menu.
DoInteractionMenuClosedCallback ( target ) ;
2013-07-02 18:21:41 +00:00
}
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
{
2015-11-22 21:37:58 +00:00
// Notify other object of the closed menu.
DoInteractionMenuClosedCallback ( old_menu . target ) ;
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
{
2015-10-14 09:22:58 +00:00
Left = " 0% " , Right = " 50%-3em " ,
2015-09-06 17:02:31 +00:00
Bottom = " 100%-7em " ,
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 )
{
2015-10-14 09:22:58 +00:00
part_menu . Left = " 50%-1em " ;
part_menu . Right = " 100%-2em " ;
2013-07-02 18:21:41 +00:00
}
2015-09-06 14:35:20 +00:00
if ( this . minimized )
{
part_menu . Bottom = nil ; // maximum height
}
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-09-06 14:35:20 +00:00
minimize_button =
{
Bottom = " 100% " ,
2015-10-14 09:22:58 +00:00
Top = " 100% - 2em " ,
Left = " 100% - 2em " ,
Tooltip = " $Minimize$ " ,
Symbol = Icon_Arrow ,
GraphicsName = " Down " ,
BackgroundColor = { Std = nil , OnHover = 0x50ffff00 } ,
2015-09-06 14:35:20 +00:00
OnMouseIn = GuiAction_SetTag ( " OnHover " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
OnClick = GuiAction_Call ( this , " OnToggleMinimizeClicked " )
} ,
2015-04-12 18:24:45 +00:00
center_column =
{
2015-10-14 09:22:58 +00:00
Left = " 50%-3em " ,
Right = " 50%-1em " ,
2015-04-12 18:24:45 +00:00
Top = " 1.75em " ,
2015-09-06 17:02:31 +00:00
Bottom = " 100%-7em " ,
2015-04-12 18:24:45 +00:00
Style = GUI_VerticalLayout ,
move_all_left =
{
Target = current_center_column_target ,
ID = 10 + 0 ,
2015-09-06 17:02:31 +00:00
Right = " 2em " , Bottom = " 3em " ,
2015-04-12 18:24:45 +00:00
Style = GUI_TextHCenter | GUI_TextVCenter ,
Symbol = Icon_MoveItems , GraphicsName = " Left " ,
Tooltip = " " ,
2015-10-14 09:22:58 +00:00
BackgroundColor = { Std = 0 , Hover = 0x50ffff00 } ,
2015-04-12 18:24:45 +00:00
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 ,
2015-09-06 17:02:31 +00:00
Right = " 2em " , Bottom = " 3em " ,
2015-04-12 18:24:45 +00:00
Style = GUI_TextHCenter | GUI_TextVCenter ,
Symbol = Icon_MoveItems ,
Tooltip = " " ,
2015-10-14 09:22:58 +00:00
BackgroundColor = { Std = 0 , Hover = 0x50ffff00 } ,
2015-04-12 18:24:45 +00:00
OnMouseIn = GuiAction_SetTag ( " Hover " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
OnClick = GuiAction_Call ( this , " OnMoveAllToClicked " , 1 )
}
} ,
description_box =
2014-03-26 22:55:32 +00:00
{
2015-09-06 17:02:31 +00:00
Top = " 100%-5em " ,
2015-10-14 09:22:58 +00:00
Right = " 100% - 2em " ,
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
{
2015-09-06 17:02:31 +00:00
Right = " 5em " ,
2014-03-26 22:55:32 +00:00
Symbol = nil ,
2015-09-06 17:02:31 +00:00
Margin = " 0.5em " ,
2014-03-26 22:55:32 +00:00
ID = 1 ,
Target = current_description_box . symbol_target
} ,
desc_part =
{
2015-09-06 17:02:31 +00:00
Left = " 5em " ,
Margin = " 0.5em " ,
2014-03-26 22:55:32 +00:00
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
} ;
2015-09-06 14:35:20 +00:00
2015-10-14 09:22:58 +00:00
// Allow the menu to be closed with a clickable button.
2018-07-24 12:19:26 +00:00
GuiAddCloseButton ( root_menu , this , " Close " ) ;
2015-10-14 09:22:58 +00:00
2015-09-06 14:35:20 +00:00
// Special setup for a minimized menu.
if ( this . minimized )
{
root_menu . Top = " 75% " ;
2015-10-14 09:22:58 +00:00
root_menu . minimize_button . Tooltip = " $Maximize$ " ;
root_menu . minimize_button . GraphicsName = " Up " ;
2015-09-06 14:35:20 +00:00
root_menu . center_column . Bottom = nil ; // full size
root_menu . description_box = nil ;
}
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 ] ;
2015-12-06 20:27:39 +00:00
// Both objects have to be containers.
2015-04-12 18:24:45 +00:00
show_grab_all = show_grab_all
2015-12-06 15:55:48 +00:00
& & ( current_menus [ 0 ] . target - > ~ IsContainer ( ) )
& & ( current_menus [ 1 ] . target - > ~ IsContainer ( ) ) ;
2015-12-06 20:27:39 +00:00
// And neither must disallow interaction.
show_grab_all = show_grab_all
& & ! current_menus [ 0 ] . target - > ~ RejectInteractionMenu ( cursor )
& & ! current_menus [ 1 ] . target - > ~ RejectInteractionMenu ( cursor ) ;
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
2015-10-24 08:44:01 +00:00
// Now tell all user-provided effects for the new menu that the menu is ready.
// Those effects can be used to update only very specific menu entries without triggering a full refresh.
for ( var menu in current_menus [ slot ] . menus )
{
2015-11-01 17:29:26 +00:00
if ( ! menu . entries ) continue ;
2015-10-24 08:44:01 +00:00
for ( var entry in menu . entries )
{
if ( ! entry . fx ) continue ;
EffectCall ( nil , entry . fx , " OnMenuOpened " , current_main_menu_id , entry . ID , menu . menu_object ) ;
}
}
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-11-22 21:37:58 +00:00
// And notify the object of the fresh menu.
DoInteractionMenuOpenedCallback ( obj ) ;
}
private func DoInteractionMenuOpenedCallback ( object obj )
{
if ( ! obj ) return ;
if ( obj . _open_interaction_menus = = nil )
obj . _open_interaction_menus = 0 ;
obj . _open_interaction_menus + = 1 ;
var is_first = obj . _open_interaction_menus = = 1 ;
obj - > ~ OnShownInInteractionMenuStart ( is_first ) ;
}
private func DoInteractionMenuClosedCallback ( object obj )
{
if ( ! obj ) return ;
obj . _open_interaction_menus = Max ( 0 , obj . _open_interaction_menus - 1 ) ;
var is_last = obj . _open_interaction_menus = = 0 ;
obj - > ~ OnShownInInteractionMenuStop ( is_last ) ;
2015-04-12 18:24:45 +00:00
}
2015-09-06 14:35:20 +00:00
// Toggles the menu state between minimized and maximized.
public func OnToggleMinimizeClicked ( )
{
var config = nil ;
if ( GetLength ( InteractionMenu_Attributes ) < = GetOwner ( ) )
{
config = { minimized = false } ;
InteractionMenu_Attributes [ GetOwner ( ) ] = config ;
}
else
{
config = InteractionMenu_Attributes [ GetOwner ( ) ] ;
}
config . minimized = ! ( GetProperty ( " minimized " , config ) ? ? false ) ;
// Reopen with new layout..
2016-10-25 01:05:17 +00:00
var last_cursor = this . cursor ;
2015-09-06 14:35:20 +00:00
RemoveObject ( ) ;
2016-10-25 01:05:17 +00:00
GUI_ObjectInteractionMenu - > CreateFor ( last_cursor ) ;
2015-09-06 14:35:20 +00:00
}
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-12-06 15:55:48 +00:00
if ( ! current_menus [ i ] . target - > ~ IsContainer ( ) )
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-09-24 19:27:52 +00:00
// Get all contents in a separate step in case the object's inventory changes during the transfer.
// Also do not use FindObject(Find_Container(...)), because this way an object can simply overload Contents to return an own collection of items.
2015-06-07 17:04:43 +00:00
var contents = [ ] ;
2015-09-24 19:27:52 +00:00
var index = 0 , obj ;
while ( obj = other - > Contents ( index + + ) ) PushBack ( contents , obj ) ;
2016-04-05 18:49:40 +00:00
var transfered = TransferObjectsFromToSimple ( contents , other , target ) ;
2015-04-12 18:24:45 +00:00
if ( transfered > 0 )
{
2016-04-05 18:59:00 +00:00
PlaySoundTransfer ( ) ;
2015-04-12 18:24:45 +00:00
return ;
}
else
{
2016-04-05 18:59:00 +00:00
PlaySoundError ( ) ;
2015-04-12 18:24:45 +00:00
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 ;
2016-10-08 15:46:39 +00:00
var symbol = { Std = SidebarIconStandard ( ) , OnHover = SidebarIconOnHover ( ) } ;
2013-07-02 18:21:41 +00:00
// 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 ) ;
2016-10-08 15:46:39 +00:00
symbol = SidebarIconSelected ( ) ;
2013-07-02 18:21:41 +00:00
}
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-09-06 17:02:31 +00:00
obj_symbol = { Symbol = obj , Margin = " 0.25em " , Priority = 1 } ,
obj_symbol_deactivated = { Symbol = deactivation_symbol , Margin = " 0.5em " , 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-09-04 14:53:32 +00:00
var big_menu =
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 ) ) ,
2015-09-04 14:53:32 +00:00
container =
{
2015-09-06 17:02:31 +00:00
Top = " 1em " ,
2015-09-04 14:53:32 +00:00
Style = GUI_VerticalLayout ,
BackgroundColor = RGB ( 25 , 25 , 25 ) ,
} ,
headline =
{
2015-09-06 17:02:31 +00:00
Bottom = " 1em " ,
2015-09-04 14:53:32 +00:00
Text = obj - > GetName ( ) ,
Style = GUI_TextHCenter | GUI_TextVCenter ,
BackgroundColor = 0xff000000
}
2013-07-02 18:21:41 +00:00
} ;
2015-09-04 14:53:32 +00:00
var container = big_menu . container ;
2013-07-02 18:21:41 +00:00
if ( slot = = 0 )
{
2015-09-04 14:53:32 +00:00
big_menu . Left = ToEmString ( InteractionMenu_SideBarSize ) ;
big_menu . Right = " 100% " ;
2013-07-02 18:21:41 +00:00
}
2015-03-01 10:20:33 +00:00
2015-12-06 20:27:39 +00:00
// Do virtually nothing if the building/object is not ready to be interacted with. This can be caused by several things.
var error_message = obj - > ~ RejectInteractionMenu ( cursor ) ;
2015-03-01 10:20:33 +00:00
if ( error_message )
{
2015-12-06 20:27:39 +00:00
if ( GetType ( error_message ) ! = C4V_String )
error_message = " $NoInteractionsPossible$ " ;
2015-03-01 10:20:33 +00:00
container . Style = GUI_TextVCenter | GUI_TextHCenter ;
container . Text = error_message ;
2015-11-21 01:59:36 +00:00
current_menus [ slot ] . menus = [ ] ;
2015-09-04 14:53:32 +00:00
return big_menu ;
2015-03-01 10:20:33 +00:00
}
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
2015-12-07 19:52:14 +00:00
if ( obj - > ~ IsContainer ( ) & & ! obj - > ~ RejectContentsMenu ( ) )
2013-07-02 18:21:41 +00:00
{
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 )
2017-03-05 19:44:41 +00:00
{
2014-03-26 22:55:32 +00:00
menu . flag = InteractionMenu_Custom ;
2017-03-05 19:44:41 +00:00
}
2015-03-01 09:27:29 +00:00
if ( menu . entries_callback )
2017-03-05 19:44:41 +00:00
{
2017-06-26 15:28:40 +00:00
var call_from = menu . entries_callback_target ? ? obj ;
menu . entries = call_from - > Call ( menu . entries_callback , cursor , menu . entries_callback_parameter ) ;
2017-03-05 19:44:41 +00:00
}
2015-03-01 09:27:29 +00:00
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 ) ;
2016-11-05 14:34:43 +00:00
if ( menu . flag = = InteractionMenu_Contents )
2017-03-05 19:44:41 +00:00
{
2016-11-05 14:34:43 +00:00
menu . menu_object - > SetTightGridLayout ( ) ;
2017-03-05 19:44:41 +00:00
}
2015-09-06 17:02:31 +00:00
menu . menu_object . Top = " +1em " ;
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 - > 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.
2015-10-24 08:44:01 +00:00
var added_entry = menu . menu_object - > AddItem ( entry . symbol , entry . text , entry . unique_index , this , " OnMenuEntrySelected " , { slot = slot , index = i } , entry [ " custom " ] ) ;
// Remember the menu entry's ID (e.g. for passing it to an update effect after the menu has been opened).
entry . ID = added_entry . ID ;
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 ,
2016-01-18 20:39:54 +00:00
Bottom = " +1em " ,
2013-07-02 18:21:41 +00:00
Text = menu . title ,
2014-03-26 22:55:32 +00:00
BackgroundColor = 0xa0000000 ,
//Decoration = menu.decoration
2015-09-06 17:02:31 +00:00
hline = { Bottom = " 0.05em " , BackgroundColor = RGB ( 100 , 100 , 100 ) }
2013-07-02 18:21:41 +00:00
} ,
2015-09-06 17:02:31 +00:00
Margin = [ nil , nil , nil , " 0.25em " ] ,
2014-03-26 22:55:32 +00:00
real_menu = menu . menu_object ,
2015-09-06 17:02:31 +00:00
spacer = { Left = " 0em " , Right = " 0em " , Bottom = " 3em " } // 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 )
2017-03-05 19:44:41 +00:00
{
2014-03-26 22:55:32 +00:00
all . BackgroundColor = RGB ( 0 , 50 , 0 ) ;
2017-03-05 19:44:41 +00:00
}
2014-03-26 22:55:32 +00:00
else if ( menu . BackgroundColor )
2017-03-05 19:44:41 +00:00
{
all . BackgroundColor = menu . BackgroundColor ;
}
2014-03-26 22:55:32 +00:00
else if ( menu . decoration )
2017-03-05 19:44:41 +00:00
{
2014-03-26 22:55:32 +00:00
menu . menu_object . BackgroundColor = menu . decoration - > FrameDecorationBackClr ( ) ;
2017-03-05 19:44:41 +00:00
}
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 )
{
2017-06-26 15:26:06 +00:00
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
}
2015-09-04 14:53:32 +00:00
return big_menu ;
2013-07-02 18:21:41 +00:00
}
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 ;
2015-10-17 10:09:35 +00:00
if ( ! info . entry . symbol ) return ;
2014-03-26 22:55:32 +00:00
// 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
{
2015-10-17 10:09:35 +00:00
var text = Format ( " %s:|%s " , info . entry . symbol - > GetName ( ) , info . entry . symbol . Description ) ;
2017-05-02 17:11:11 +00:00
// For contents menus, we can sometimes present additional information about objects.
if ( info . menu . flag = = InteractionMenu_Contents )
2015-10-17 10:09:35 +00:00
{
2017-05-02 17:11:11 +00:00
// Get the first valid object of the clicked stack.
var obj = nil ;
if ( info . entry . extra_data & & info . entry . extra_data . objects )
2015-10-17 10:09:35 +00:00
{
2017-05-02 17:11:11 +00:00
for ( var possible in info . entry . extra_data . objects )
{
if ( possible = = nil ) continue ;
obj = possible ;
break ;
}
2015-10-17 10:09:35 +00:00
}
2017-05-02 17:11:11 +00:00
// ..and use that object to fetch some more information.
if ( obj )
2015-03-19 15:22:06 +00:00
{
2017-05-02 17:11:11 +00:00
var additional = nil ;
if ( obj - > Contents ( ) )
2015-03-19 15:22:06 +00:00
{
2017-05-02 17:11:11 +00:00
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 ( ) ) ;
}
2015-03-19 15:22:06 +00:00
}
2017-05-02 17:11:11 +00:00
if ( additional ! = nil )
text = Format ( " %s||%s " , text , additional ) ;
2015-03-19 15:22:06 +00:00
}
}
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.
2018-07-24 12:19:26 +00:00
callback_target - > Call ( info . menu . callback , info . entry . symbol , info . entry . extra_data , cursor ) ;
2013-07-02 18:21:41 +00:00
2018-07-24 12:19:26 +00:00
// todo: trigger refresh for special value of callback 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
{
Prevent script error in ObjectInteractionMenu
ERROR: proplist access: proplist expected, got nil
by: Object(18501)->OnContentsSelection() (Objects.ocd/HUD.ocd/ObjectInteractionMenu.ocd/Script.c:887)
by: Object(18501)->OnMenuEntrySelected({index = 0, slot = 0},2,1) (Objects.ocd/HUD.ocd/ObjectInteractionMenu.ocd/Script.c:880)
by: Object(18911)->DoCall(2,0,Object(18501),0,1) (Objects.ocd/HUD.ocd/MenuStyles.ocd/List.ocd/Script.c:166)
by: Object(18911)->OnClick(0,1,948,2,Object(18911)) (Objects.ocd/HUD.ocd/MenuStyles.ocd/List.ocd/Script.c:175)
2017-12-27 11:50:56 +00:00
if ( ! current_menus [ extra_data . slot ] ) return ;
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-12-06 20:27:39 +00:00
// Only if the object wants to be interacted with (hostility etc.)
if ( other_target - > ~ RejectInteractionMenu ( cursor ) ) return ;
2015-12-06 15:55:48 +00:00
// Allow transfer only into containers.
if ( ! other_target - > ~ IsContainer ( ) )
{
2015-12-16 22:00:17 +00:00
cursor - > ~ PlaySoundDoubt ( true , nil , cursor - > GetOwner ( ) ) ;
2015-12-06 15:55:48 +00:00
return ;
}
2015-10-17 10:09:35 +00:00
var transfer_only_one = GetPlayerControlState ( GetOwner ( ) , CON_ModifierMenu1 ) = = 0 ; // Transfer ONE object of the stack?
2015-10-09 11:14:27 +00:00
var to_transfer = nil ;
2015-10-17 10:09:35 +00:00
if ( transfer_only_one )
2015-10-09 11:14:27 +00:00
{
2015-10-17 10:09:35 +00:00
for ( var possible in extra_data . objects )
2015-10-09 11:14:27 +00:00
{
2015-10-17 10:09:35 +00:00
if ( possible = = nil ) continue ;
to_transfer = [ possible ] ;
break ;
2015-10-09 11:14:27 +00:00
}
}
2015-10-17 10:09:35 +00:00
else
{
2015-10-26 17:44:33 +00:00
to_transfer = extra_data . objects ;
2015-10-17 10:09:35 +00:00
}
2016-04-05 18:49:40 +00:00
var successful_transfers = TransferObjectsFromTo ( to_transfer , target , other_target ) ;
// Did we at least transfer one item?
if ( successful_transfers > 0 )
{
2016-04-05 18:59:00 +00:00
PlaySoundTransfer ( ) ;
2016-04-05 18:49:40 +00:00
return true ;
}
else
{
2016-04-05 18:59:00 +00:00
PlaySoundTransferIncomplete ( ) ;
2016-04-05 18:49:40 +00:00
return false ;
}
}
2016-04-05 18:55:04 +00:00
func TransferObjectsFromToSimple ( array to_transfer , object source , object destination )
2016-04-05 18:49:40 +00:00
{
// Now try transferring each item once.
2016-04-05 18:55:04 +00:00
var successful_transfers = 0 ;
for ( var obj in to_transfer )
2016-04-05 18:49:40 +00:00
{
// Sanity, can actually happen if an item merges with others during the transfer etc.
2016-04-05 18:55:04 +00:00
if ( ! obj | | ! destination ) continue ;
2016-04-05 18:49:40 +00:00
2016-04-05 18:55:04 +00:00
var handled = destination - > Collect ( obj , true ) ;
if ( handled )
+ + successful_transfers ;
2016-04-05 18:49:40 +00:00
}
2016-04-05 18:55:04 +00:00
return successful_transfers ;
2016-04-05 18:49:40 +00:00
}
2016-04-05 18:55:04 +00:00
func TransferObjectsFromTo ( array to_transfer , object source , object destination )
2016-04-05 18:49:40 +00:00
{
2015-10-09 11:14:27 +00:00
var successful_transfers = 0 ;
2015-09-11 19:08:38 +00:00
2015-10-09 11:14:27 +00:00
// Try to transfer all the previously selected items.
for ( var obj in to_transfer )
2015-04-04 18:37:06 +00:00
{
2015-10-09 11:14:27 +00:00
if ( ! obj ) continue ;
2016-01-25 19:21:28 +00:00
// Our target might have disappeared (e.g. a construction site completing after the first item).
2016-04-05 18:55:04 +00:00
if ( ! destination ) break ;
2015-10-09 11:14:27 +00:00
// Does the object not want to leave the other container anyway?
2016-04-05 18:55:04 +00:00
if ( ! obj - > Contained ( ) | | ! obj - > ~ QueryRejectDeparture ( source ) )
2015-04-04 18:37:06 +00:00
{
2016-04-05 19:09:04 +00:00
var handled = false ;
2015-10-09 11:14:27 +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.
2016-03-18 16:20:00 +00:00
// TODO: 200 stacks of 1 arrow would each merge into the stacks that are already in the target
// when they enter the target. For this reason that special case is, imo, not needed here.
2015-10-09 11:14:27 +00:00
if ( obj - > ~ IsStackable ( ) )
2015-09-11 19:08:38 +00:00
{
2016-04-05 18:55:04 +00:00
var others = FindObjects ( Find_Container ( source ) , Find_ID ( obj - > GetID ( ) ) , Find_Exclude ( obj ) ) ;
2015-10-09 11:14:27 +00:00
for ( var other in others )
{
if ( obj - > IsFullStack ( ) ) break ;
other - > TryAddToStack ( obj ) ;
}
2015-09-11 19:08:38 +00:00
}
2015-10-09 11:14:27 +00:00
// More special handling for Stackable items..
2016-04-05 18:55:04 +00:00
handled = obj - > ~ MergeWithStacksIn ( destination ) ;
2015-10-09 11:14:27 +00:00
// Try to normally collect the object otherwise.
2016-04-05 18:55:04 +00:00
if ( ! handled & & destination & & obj )
handled = destination - > Collect ( obj , true ) ;
2016-04-05 19:09:04 +00:00
if ( handled )
successful_transfers + = 1 ;
2015-04-04 18:37:06 +00:00
}
}
2016-04-05 18:49:40 +00:00
return successful_transfers ;
2013-07-02 18:21:41 +00:00
}
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 = [ ] ;
}
2017-06-26 15:26:06 +00:00
func FxIntRefreshContentsMenuTimer ( object target , effect , int time )
2013-07-02 18:21:41 +00:00
{
2017-06-26 15:26:06 +00:00
// Remove effect if menu is gone or menu at slot and index is not a contents menu.
if ( ! effect . obj )
return FX_Execute_Kill ;
if ( ! current_menus [ effect . slot ] | | ! current_menus [ effect . slot ] . menus [ effect . menu_index ] )
return FX_Execute_Kill ;
if ( ! ( current_menus [ effect . slot ] . menus [ effect . menu_index ] . flag & InteractionMenu_Contents ) )
return FX_Execute_Kill ;
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 + + ) )
{
2015-10-17 10:09:35 +00:00
var symbol = obj ;
var extra_data = { slot = effect . slot , menu_index = effect . menu_index , objects = [ ] } ;
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 ( ) ;
// 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 ;
2016-08-09 21:45:48 +00:00
while ( e = GetEffect ( " ExtraSlotTracker " , obj , j + + ) )
2015-03-19 15:22:06 +00:00
{
if ( e . keep_alive ! = extra_slot_keep_alive ) continue ;
found_tracker = true ;
break ;
}
if ( ! found_tracker )
{
2016-08-09 21:45:48 +00:00
var e = AddEffect ( " ExtraSlotTracker " , obj , 1 , 30 + Random ( 60 ) , nil , GetID ( ) ) ;
2015-03-19 15:22:06 +00:00
e . keep_alive = extra_slot_keep_alive ;
e . callback_effect = effect ;
2016-08-09 21:45:48 +00:00
e . obj = effect . obj ;
2015-03-19 15:22:06 +00:00
}
2013-07-02 18:21:41 +00:00
}
2015-03-26 11:24:48 +00:00
// How many objects are this object?!
2018-01-07 16:42:27 +00:00
var object_amount = obj - > ~ GetStackCount ( ) ? ? 1 ;
// Infinite stacks work differently - showing an arbitrary amount would not make sense.
if ( object_amount > 1 & & obj - > ~ IsInfiniteStackCount ( ) )
object_amount = 1 ;
2015-03-19 15:22:06 +00:00
// Empty containers can be stacked.
2015-10-17 10:09:35 +00:00
for ( var inv in inventory )
2015-03-19 15:22:06 +00:00
{
2015-10-17 10:09:35 +00:00
if ( ! inv . extra_data . objects [ 0 ] - > CanBeStackedWith ( obj ) ) continue ;
if ( ! obj - > CanBeStackedWith ( inv . extra_data . objects [ 0 ] ) ) continue ;
inv . count + = object_amount ;
inv . text = Format ( " %dx " , inv . count ) ;
PushBack ( inv . extra_data . objects , obj ) ;
// This object has a custom symbol (because it's a container)? Then the normal text would not be displayed.
if ( inv . custom ! = nil )
2015-03-19 15:22:06 +00:00
{
2015-10-17 10:09:35 +00:00
inv . custom . top . Text = inv . text ;
inv . custom . top . Style = inv . custom . top . Style | GUI_TextRight | GUI_TextBottom ;
2015-03-19 15:22:06 +00:00
}
2015-10-17 10:09:35 +00:00
found = true ;
break ;
2015-03-19 15:22:06 +00:00
}
2015-10-17 10:09:35 +00:00
2015-03-19 15:22:06 +00:00
// Add new!
2013-07-02 18:21:41 +00:00
if ( ! found )
{
2015-10-17 10:09:35 +00:00
PushBack ( extra_data . objects , obj ) ;
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.
2016-11-07 19:09:37 +00:00
// The priority offset makes sure that double-height items are at the front.
custom = { Right = custom . Right , Bottom = " 4em " , top = custom , Priority = - 10000 + obj - > GetValue ( ) } ;
2015-03-19 15:22:06 +00:00
// 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
{
2015-09-06 17:02:31 +00:00
Top = " 2em " ,
2015-03-19 15:22:06 +00:00
BackgroundColor = { Std = 0 , Selected = RGBa ( 255 , 100 , 100 , 100 ) } ,
OnMouseIn = GuiAction_SetTag ( " Selected " ) ,
OnMouseOut = GuiAction_SetTag ( " Std " ) ,
2015-10-17 10:09:35 +00:00
OnClick = GuiAction_Call ( this , " OnExtraSlotClicked " , { slot = effect . slot , objects = extra_data . objects , ID = obj - > GetID ( ) } ) ,
2015-03-19 15:22:06 +00:00
container =
{
Symbol = Chest ,
Priority = 1
}
} ;
// And if the object has contents, show the first one, too.
2015-10-17 10:09:35 +00:00
if ( obj - > ContentsCount ( ) ! = 0 )
2015-03-19 15:22:06 +00:00
{
2015-11-10 16:32:38 +00:00
var first_contents = obj - > Contents ( 0 ) ;
2015-03-19 15:22:06 +00:00
// Add to GUI.
custom . bottom . contents =
{
2015-11-10 16:32:38 +00:00
Symbol = first_contents ,
2015-09-06 17:02:31 +00:00
Margin = " 0.125em " ,
2015-03-19 15:22:06 +00:00
Priority = 2
} ;
// Possibly add text for stackable items - this is an special exception for the Library_Stackable.
2015-11-10 16:32:38 +00:00
var count = first_contents - > ~ GetStackCount ( ) ;
// Infinite stacks display an own overlay.
if ( ( count > 1 ) & & ( first_contents - > ~ IsInfiniteStackCount ( ) ) ) count = nil ;
count = count ? ? obj - > ContentsCount ( first_contents - > GetID ( ) ) ;
2015-03-19 15:22:06 +00:00
if ( count > 1 )
{
custom . bottom . contents . Text = Format ( " %dx " , count ) ;
custom . bottom . contents . Style = GUI_TextBottom | GUI_TextRight ;
}
2015-11-10 16:32:38 +00:00
var overlay = first_contents - > ~ GetInventoryIconOverlay ( ) ;
if ( overlay )
custom . bottom . contents . overlay = overlay ;
2015-03-19 15:22:06 +00:00
// Also make the chest smaller, so that the contents symbol is not obstructed.
2015-09-06 17:02:31 +00:00
custom . bottom . container . Bottom = " 1em " ;
custom . bottom . container . Left = " 1em " ;
2015-03-19 15:22:06 +00:00
}
}
2015-11-07 20:19:45 +00:00
// Enable objects to provide a custom overlay for the icon slot.
// This could e.g. be used by special scenarios or third-party mods.
var overlay = obj - > ~ GetInventoryIconOverlay ( ) ;
if ( overlay ! = nil )
{
if ( ! custom )
{
custom = MenuStyle_Grid - > MakeEntryProplist ( symbol , nil ) ;
custom . Priority = obj - > GetValue ( ) ;
2015-11-22 10:01:56 +00:00
custom . top = { } ;
2015-11-07 20:19:45 +00:00
}
2015-11-22 10:01:56 +00:00
custom . top . _overlay = overlay ;
2015-11-07 20:19:45 +00:00
}
2015-03-19 15:22:06 +00:00
// 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 ,
{
2015-10-17 10:09:35 +00:00
symbol = symbol ,
extra_data = extra_data ,
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
}
}
2016-03-04 07:18:20 +00:00
// Add a contents counter on top.
var contents_count_bar =
{
BackgroundColor = RGBa ( 0 , 0 , 0 , 100 ) ,
Priority = - 1 ,
Bottom = " 1em " ,
text =
{
Priority = 2 ,
Style = GUI_TextRight | GUI_TextVCenter
}
} ;
if ( effect . obj . MaxContentsCount )
{
var count = effect . obj - > ContentsCount ( ) ;
var max = effect . obj . MaxContentsCount ;
contents_count_bar . text . Text = Format ( " <c eeeeee>%3d / %3d</c> " , count , max ) ;
contents_count_bar . bar =
{
Priority = 1 ,
BackgroundColor = RGBa ( 0 , 255 , 0 , 50 ) ,
Right = ToPercentString ( 1000 * count / max , 10 )
} ;
}
else contents_count_bar . text . Text = Format ( " <c eeeeee>%3d</c> " , effect . obj - > ContentsCount ( ) ) ;
PushBack ( inventory , { symbol = nil , text = nil , custom = contents_count_bar } ) ;
2015-10-17 10:09:35 +00:00
// Check if nothing changed. If so, we don't need to update.
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 ;
}
2017-06-26 15:26:06 +00:00
if ( same )
return FX_OK ;
2013-07-02 18:21:41 +00:00
}
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 ) ;
2017-06-26 15:26:06 +00:00
return FX_OK ;
2013-07-02 18:21:41 +00:00
}
2015-03-19 15:22:06 +00:00
func FxExtraSlotTrackerTimer ( object target , proplist effect , int time )
{
2016-08-09 21:45:48 +00:00
if ( ! effect . keep_alive )
return - 1 ;
2015-03-19 15:22:06 +00:00
}
// 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 ] ;
2015-10-04 02:26:10 +00:00
if ( ! menu | | ! menu . target ) return ;
2015-10-17 10:09:35 +00:00
var obj = nil ;
for ( var possible in extra_data . objects )
2015-03-19 15:22:06 +00:00
{
2015-10-17 10:09:35 +00:00
if ( possible = = nil ) continue ;
if ( possible - > Contained ( ) ! = menu . target & & ! menu . target - > ~ IsObjectContained ( possible ) ) continue ;
obj = possible ;
break ;
2015-03-19 15:22:06 +00:00
}
2015-10-17 10:09:35 +00:00
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 )
2017-03-05 19:44:41 +00:00
{
2017-06-26 15:28:40 +00:00
var call_from = menu . entries_callback_target ? ? current_menus [ slot ] . target ;
new_entries = call_from - > Call ( menu . entries_callback , this . cursor , menu . entries_callback_parameter ) ;
2017-03-05 19:44:41 +00:00
}
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 ] ;
2017-12-27 14:42:38 +00:00
if ( ! old_entry ) continue ;
2014-03-26 22:55:32 +00:00
var found = false ;
var symbol_equal_index = - 1 ;
for ( var ni = 0 ; ni < GetLength ( new_entries ) ; + + ni )
{
var new_entry = new_entries [ ni ] ;
2017-12-27 14:42:38 +00:00
if ( ! new_entry ) continue ;
2014-03-26 22:55:32 +00:00
if ( ! EntriesEqual ( new_entry , old_entry ) )
{
2017-12-27 14:42:38 +00:00
// Exception for the inventory menus.. extra_data includes all the found objects, but they are allowed to differ here.
// So we check for equality excluding the objects.
var extra1 = new_entry . extra_data ;
if ( GetType ( extra1 ) = = C4V_PropList ) extra1 = new extra1 { objects = nil } ;
var extra2 = old_entry . extra_data ;
if ( GetType ( extra2 ) = = C4V_PropList ) extra2 = new extra2 { objects = nil } ;
// We also allow the symbols to change as long as the actual ID stays intact.
var symbol1 = new_entry . symbol ;
var symbol2 = old_entry . symbol ;
var symbols_equal = symbol1 = = symbol2 ;
if ( ! symbols_equal & & symbol1 & & symbol2 & & GetType ( symbol1 ) = = C4V_C4Object & & GetType ( symbol2 ) = = C4V_C4Object )
symbols_equal = symbol1 - > ~ GetID ( ) = = symbol2 - > ~ GetID ( ) ;
if ( symbols_equal & & DeepEqual ( extra1 , extra2 ) & & DeepEqual ( new_entry . custom , old_entry . custom ) & & ( new_entry . fx = = old_entry . fx ) )
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 )
{
2015-11-23 19:06:25 +00:00
if ( old_entry . fx )
RemoveEffect ( nil , nil , old_entry . fx ) ;
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-11-23 19:06:25 +00:00
var added_entry = 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 ) ;
new_entry . ID = added_entry . ID ;
2013-07-02 18:21:41 +00:00
2015-11-23 19:06:25 +00:00
if ( new_entry . fx )
{
EffectCall ( nil , new_entry . fx , " OnMenuOpened " , current_main_menu_id , new_entry . ID , menu . menu_object ) ;
}
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
2017-12-27 14:42:38 +00:00
& & DeepEqual ( entry_a . extra_data , entry_b . extra_data )
& & DeepEqual ( 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 ] ;
2017-03-05 19:44:41 +00:00
if ( menu . entries_callback ! = callback )
{
continue ;
}
2015-03-01 09:27:29 +00:00
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
}
2016-04-05 18:59:00 +00:00
// Sounds
func PlaySoundTransfer ( )
{
Sound ( " Hits::SoftTouch* " , true , nil , GetOwner ( ) ) ;
}
func PlaySoundTransferIncomplete ( )
{
Sound ( " Hits::Materials::Wood::DullWoodHit* " , true , nil , GetOwner ( ) ) ;
}
func PlaySoundError ( )
{
Sound ( " Objects::Balloon::Pop " , true , nil , GetOwner ( ) ) ;
}
2016-10-08 15:46:39 +00:00
// Overloadable functions for customization
func SidebarIconStandard ( )
{
return Icon_Menu_RectangleRounded ;
}
func SidebarIconOnHover ( )
{
return Icon_Menu_RectangleBrightRounded ;
}
func SidebarIconSelected ( )
{
return Icon_Menu_RectangleBrightRounded ;
}