870 lines
34 KiB
Lua
870 lines
34 KiB
Lua
---------------------------------------------
|
|
-- Base class for R2 components / features --
|
|
---------------------------------------------
|
|
|
|
-- NB : throughout the file 'this' is used instead of the lua 'self' to denote the fact
|
|
-- that method are not called on the class definition, but on the instances of this class
|
|
|
|
baseClass = -- not local here because of the implementation ...
|
|
{
|
|
----------------------
|
|
-- CLASS PROPERTIES --
|
|
----------------------
|
|
|
|
BaseClass = "", -- Name of the base class
|
|
Version = 0,
|
|
Name = "BaseClass", -- Name of the class
|
|
BuildPropertySheet = true, -- True if objects of this class should be editable by using a generic property sheet
|
|
-- Setting to 'true' will cause the framework to create the property sheet ui at launch
|
|
Menu="", -- ui path of the contextual menu for this class
|
|
--
|
|
DisplayerUI = "", -- name of the C++ class that displays this object in the ui
|
|
DisplayerUIParams = "", -- parameters passed to the ui displayer when it is created
|
|
--
|
|
DisplayerProperties = "R2::CDisplayerLua", -- 'lua' type of displayer takes the name of a lua function that must return the class of the displayer
|
|
DisplayerPropertiesParams = "propertySheetDisplayer", -- name of the function that build the displayer that update the property sheet.
|
|
--
|
|
TreeIcon="", -- icon to be displayed in the tree (or a method returning it)
|
|
PermanentTreeIcon="", -- icon to be displayed in the tree if in permanent content (or a method returning it)
|
|
SelectBarType = "", -- type in select bar
|
|
-- rollout header for the generic property sheet (a string giving the xml code for the header)
|
|
PropertySheetHeader = nil,
|
|
|
|
-------------------
|
|
-- CLASS METHODS --
|
|
-------------------
|
|
ClassMethods = {},
|
|
|
|
--------------------------
|
|
-- INSTANCES PROPERTIES --
|
|
--------------------------
|
|
-- * Unlike class properties above, they are created in each instance of the class. They are directly accessible in the objects.
|
|
-- * They are C++ objects exposed through the metatable mechanism
|
|
-- * They are *READ-ONLY*. They should be modified by network commands like r2.requestSetNode and the like
|
|
-- To store client side transcient values, use the native property 'User' (detailed below) instead
|
|
-- TODO complete doc (available types, widgets ...)
|
|
Prop =
|
|
{
|
|
{Name="InstanceId", Type="String", WidgetStyle="StaticText", Category="Advanced", Visible=false },
|
|
},
|
|
|
|
|
|
-- 'VIRTUAL' PROPERTIES (NOT IMPLEMENTED)
|
|
-- Not really properties of this class, but 'shortcuts' to other, real properties
|
|
-- The virtual properties are use by the property sheet to display properties than are jnot directly in the object,
|
|
-- but that can be found in another place. (son object for example, or object that require an indirection)
|
|
-- each virtual prop should provide a 'getPath' function which takes the InstanceId of the instance in which it is contained as its parameter
|
|
-- It should return a couple : realOwnerInstanceId, realPropName (arrays not supported yet)
|
|
-- an observer is placed on the real property to take its changes in account in the property sheet
|
|
-- IMPORTANT: the getPath function will be called each frame to see if observers should be removed
|
|
-- and placed on another target
|
|
|
|
-- Example : indirection to fictive string property 'Toto' in the Scenario
|
|
--VirtualProp =
|
|
--{
|
|
-- {Name="InstanceId", Type="String", WidgetStyle="StaticText", Category="Advanced", Visible=true,
|
|
-- getPath = function()
|
|
-- return r2.Scenario.InstanceId, "Toto"
|
|
-- end,
|
|
-- },
|
|
--},
|
|
|
|
---------------------------------
|
|
-- NATIVE / SPECIAL PROPERTIES --
|
|
---------------------------------
|
|
|
|
-- They are implemented in C++ (thanks to the lua metatables)
|
|
-- Parent : (R/O) : The direct parent of this object. If parent object is in a table, returns the table
|
|
-- ParentInstance : (R/O) : Next parent of this object with an InstanceId. In object is contained in a property that
|
|
-- is a table, the object containing the table will be returned, not the table
|
|
-- IndexInParent : (R/O) : If parent is a table, then return index into it, -1 otherwise
|
|
-- User : Read/Write lua table attached to the object. The 'User' field itself is R/O This is the place to store client side
|
|
-- edition variables.
|
|
-- Size : (R/O) : If object is a table, returns its size, nil otherwise
|
|
-- DisplayerUI : (R/O) : for instances : returns reference to the ui displayer. May be nil.
|
|
-- In the case where the field 'DisplayerUI' of this class definition id 'R2::CDisplayerLua', it would return
|
|
-- the displayer object created by a call to the function defined in 'DisplayerUIParams' (in this class definition)
|
|
-- DisplayerVisual : (R/O) : Same as DisplayerUI but for 'in scene' displayer
|
|
-- DisplayerProperties : (R/O) : Same as DisplayerUI but for the properties displayer
|
|
-- Selectable : (R/W) : default is true. When false, the object can't be selected in the scene. This flag is local to the instance (ancestor state not inherited)
|
|
-- SelectableFromRoot : (R/O) : True if this object and also its ancestor are selectable
|
|
|
|
------------
|
|
-- EVENTS --
|
|
------------
|
|
-- Events that are sent to the displayers, are also sent to instances that hold those displayers :
|
|
-- By default they are not handled
|
|
-- To handle, one shouldd redefine :
|
|
-- function baseClass.onCreate(this)
|
|
-- function baseClass.onErase(this)
|
|
-- etc ...
|
|
-- see r2_ui_displayers.lua for details
|
|
|
|
}
|
|
|
|
---------------------
|
|
-- GENERAL METHODS --
|
|
---------------------
|
|
|
|
-- Methods are defined in the class definition and are nevertheless callable on instances as follow :
|
|
-- instance:methodName(params ...)
|
|
-- In the class, the method would be defined as follow:
|
|
-- methodName = function(this, param1, param2 ...) ... some code ... end
|
|
-- 'this' will be filled at runtime by a reference on the instance on which the method is called.
|
|
-- Method calling is possible thanks to the metamethod mechanism (in this case it is implemented in C++)
|
|
-- Calling a method is in fact equivalent to doing the following :
|
|
-- r2:getClass(instance).methodName(instance, param1, param2 ..)
|
|
|
|
|
|
---------------------------------------
|
|
-- TYPE / CLASS / PROPERTIES METHODS --
|
|
---------------------------------------
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- return a reference to the class of this object
|
|
function baseClass.getClass(this)
|
|
return r2:getClass(this)
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get description of a property (as found in the 'Prop' table of the class definition) from its name
|
|
function baseClass.getPropDesc(this, propName)
|
|
return this:getClass().NameToProp[propName]
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- return name of the parent class
|
|
function baseClass.getClassName(this)
|
|
return this.Class
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- test if object is of the given class (or derived from the class)
|
|
-- param 'class' should be a string identifying the class
|
|
function baseClass.isKindOf(this, className)
|
|
|
|
assert( type(this) == "userdata")
|
|
local currClass = this:getClass()
|
|
while currClass do
|
|
if currClass.Name == className then
|
|
return true
|
|
end
|
|
currClass = r2.Classes[currClass.BaseClass]
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- return a 'this' of the base class
|
|
-- Use this to access a method defined in a base class from a derived class
|
|
--
|
|
-- example : this:delegate():doThis() -- Call the doThis function in the parent class
|
|
--
|
|
-- Expected behavior is the same than with C++ :
|
|
-- Call from a delegated pointer is static
|
|
-- any further call is the call chain is polymorphic
|
|
-- Calls to delegate can be chained
|
|
-- NB : this function shouldn't be redefined in derived classes (the delegation mechanism uses a pointer on this function)
|
|
--function baseClass.delegate(this)
|
|
-- return __baseClassImpl.delegate(this) -- implementation defined in "r2_base_class_private.lua"
|
|
--end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Get actual C++ "this" for this object. Because of the delegation mechanism, this may be a raw C++ object
|
|
-- or a lua table that performs the delegation
|
|
-- OBSOLETE, TO REMOVE
|
|
function baseClass.getRawThis(this)
|
|
-- return __baseClassImpl.getRawThis(this) -- implementation defined in "r2_base_class_private.lua"
|
|
return this
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- compare current "this", with another "this" pointer
|
|
-- This should be the standard way to compare instance in memory because 'this' may sometime be a userdata
|
|
-- (raw C++ pointer to internal C++ object), or a table (delegated 'this' pointer)
|
|
-- OBSOLETE, TO REMOVE
|
|
function baseClass.isSameObjectThan(this, other)
|
|
--if this:isKindOf("Act") then
|
|
-- breakPoint()
|
|
--end
|
|
--return this:getRawThis() == other:getRawThis()
|
|
return this == other
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Helper : Return world position (that is, absolute position). By default, object deriving from the base class have no position
|
|
function baseClass.getWorldPos()
|
|
return { x = 0, y = 0, z = 0 }
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- When adding content, pionneer have a limited budget. This method gives the 'cost' of this object (0 by default)
|
|
--function baseClass.getUsedQuota(this)
|
|
-- return 0
|
|
--end
|
|
|
|
-------------------
|
|
-- SCENARIO COST --
|
|
-------------------
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- See wether this element has a cost in the scenario
|
|
function baseClass.hasScenarioCost(this)
|
|
return false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get local cost cached in object
|
|
function baseClass.getLocalCost(this)
|
|
return defaulting(this.User.Cost, 0)
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- set local cost in object
|
|
function baseClass.setLocalCost(this, cost)
|
|
this.User.Cost = cost
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get local static cost cached in object
|
|
function baseClass.getLocalStaticCost(this)
|
|
return defaulting(this.User.StaticCost, 0)
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- set local static cost in object
|
|
function baseClass.setLocalStaticCost(this, cost)
|
|
this.User.StaticCost = cost
|
|
end
|
|
|
|
|
|
|
|
function baseClass.getStaticObjectCost(this)
|
|
return 0
|
|
end
|
|
|
|
function baseClass.getAiCost(this)
|
|
return 0
|
|
end
|
|
|
|
----------------------
|
|
-- OBJECT HIERARCHY --
|
|
----------------------
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- append all sub-content that is "kind of" 'kind' to 'destTable'
|
|
-- NB : this is very SLOW!!! please use iterators instead (see r2:enumInstances)
|
|
function baseClass.appendInstancesByType(this, destTable, kind)
|
|
assert(type(kind) == "string")
|
|
if this.CompareClass and this.CompareClass == true then
|
|
if this.Class == kind then
|
|
if destTable == nil then
|
|
dumpCallStack(1)
|
|
end
|
|
table.insert(destTable, this:getRawThis())
|
|
end
|
|
elseif this:isKindOf(kind) then
|
|
if destTable == nil then
|
|
dumpCallStack(1)
|
|
end
|
|
table.insert(destTable, this:getRawThis())
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Append all instances rooted at this object (including this one)
|
|
|
|
function baseClass.getSons(this, destTable)
|
|
r2:exploreInstanceTree(this, destTable)
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Search first ancestor of the wanted kind (that is of class 'className' or a derived class)
|
|
function baseClass.getParentOfKind(this, className)
|
|
local parent = this.ParentInstance
|
|
while parent do
|
|
assert(parent.isKindOf)
|
|
if parent:isKindOf(className) then return parent end
|
|
parent = parent.ParentInstance
|
|
end
|
|
return nil
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Search parent until an act is found
|
|
function baseClass.getParentAct(this)
|
|
return this:getParentOfKind("Act")
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Search parent until a scenario is found
|
|
function baseClass.getParentScenario(this)
|
|
return this:getParentOfKind("Scenario")
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- See if hits object is inserted in the default feature (that is : single npcs, bot objects, roads etc. with no enclosing group or feature)
|
|
function baseClass.isInDefaultFeature(this)
|
|
return this.ParentInstance:isKindOf('DefaultFeature')
|
|
end
|
|
|
|
|
|
--------------------------
|
|
-- UI METHODS / DISPLAY --
|
|
--------------------------
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Called by the contextual menu/toolbar when the 'delete' option is chosen by the user
|
|
-- on THIS client
|
|
-- This is the place to perform additionnal deletion tasks
|
|
-- Example : a vertex may want to delete its containing region when there are 2 vertices left only
|
|
-- default -> just call 'r2.requestEraseNode'
|
|
function baseClass.onDelete(this)
|
|
if this.User.DeleteInProgress == true then return end
|
|
this.User.DeleteInProgress = true
|
|
this:setDeleteActionName()
|
|
this:selectNext()
|
|
r2.requestEraseNode(this.InstanceId, "", -1)
|
|
r2.requestEndAction()
|
|
end
|
|
|
|
-- helper : add "delete : name_of_the_thing_being_deleted" in the action historic as the name of the delete action that is about
|
|
-- to be done
|
|
function baseClass.setDeleteActionName(this)
|
|
r2.requestNewAction(concatUCString(i18n.get("uiR2EDDeleteAction"), this:getDisplayName()))
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Test wether the user can delete this object
|
|
function baseClass.isDeletable(this)
|
|
if this.Deletable and this.Deletable == 0 then return false end
|
|
return true
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- called when the instance is selected (default is no op)
|
|
function baseClass.onSelect(this)
|
|
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Tell if object can be selected as next object if a predecessor object
|
|
-- has been selected in the parent list
|
|
function baseClass.isNextSelectable(this)
|
|
return false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get next selectable object (or nil else)
|
|
function baseClass.getNextSelectableObject(this)
|
|
local startIndex = this.IndexInParent
|
|
if type(startIndex) ~= "number" then return nil end
|
|
local currIndex = startIndex
|
|
while true do
|
|
currIndex = currIndex + 1
|
|
if currIndex == this.Parent.Size then
|
|
currIndex = 0
|
|
end
|
|
local instance = this.Parent[currIndex]
|
|
if currIndex == startIndex then break end
|
|
if instance ~= nil then
|
|
local firstSon = instance:getFirstSelectableSon()
|
|
if firstSon ~= nil and firstSon:isNextSelectable() then
|
|
return firstSon
|
|
elseif instance.Selectable and instance:isNextSelectable() then
|
|
return instance
|
|
end
|
|
end
|
|
end
|
|
if this.ParentInstance:isKindOf("DefaultFeature") then
|
|
return this.ParentInstance:getNextSelectableObject()
|
|
end
|
|
return nil
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- select object next to this one, if there's one
|
|
function baseClass.selectNext(this)
|
|
local nextSelection = this
|
|
while 1 do
|
|
nextSelection = nextSelection:getNextSelectableObject()
|
|
if not nextSelection or nextSelection == this then
|
|
r2:setSelectedInstanceId("")
|
|
return
|
|
end
|
|
if nextSelection then
|
|
-- should not be frozen or hiden
|
|
if (not nextSelection.DisplayerVisual) or (nextSelection.DisplayerVisual.DisplayMode ~= 1 and nextSelection.DisplayerVisual.DisplayMode ~= 2) then
|
|
r2:setSelectedInstanceId(nextSelection.InstanceId)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- if an object is not selectable if may nevertheless contain selectable object, the first one is returned by this method
|
|
function baseClass.getFirstSelectableSon(this)
|
|
return nil
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get select bar type
|
|
function baseClass.getSelectBarType(this)
|
|
return r2:evalProp(this:getClass().SelectBarType, this, "")
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get name of tree icon
|
|
function baseClass.getTreeIcon(this)
|
|
return r2:evalProp(this:getClass().TreeIcon, this, "")
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get name of tree icon
|
|
function baseClass.getPermanentTreeIcon(this)
|
|
return r2:evalProp(this:getClass().PermanentTreeIcon, this, "")
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get name of tree icon according to permanent or current act
|
|
function baseClass.getContextualTreeIcon(this)
|
|
if this:getParentAct() and this:getParentAct():isBaseAct() then
|
|
return this:getPermanentTreeIcon()
|
|
else
|
|
return this:getTreeIcon()
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get name of permanent statut icon
|
|
function baseClass.getPermanentStatutIcon(this)
|
|
return ""
|
|
end
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get name of icon to be displayed in the slect bar
|
|
function baseClass.getSelectBarIcon(this)
|
|
return this:getContextualTreeIcon()
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Get the display name (in i18n format). This name will be displayed in the property sheet or inthe instance tree
|
|
function baseClass.getDisplayName(this)
|
|
local displayName = ucstring()
|
|
if this.Name ~= nil and this.Name ~= "" then
|
|
displayName:fromUtf8(this.Name)
|
|
else
|
|
return i18n.get("uiR2EDNoName")
|
|
-- local className = this.Class
|
|
-- -- tmp patch
|
|
-- if this:isKindOf("Npc") then
|
|
-- if this:isBotObject() then
|
|
-- className = "Bot object"
|
|
-- end
|
|
-- end
|
|
-- className = className .. " : " .. this.InstanceId
|
|
-- displayName:fromUtf8(className)
|
|
end
|
|
return displayName
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Get the base name for instance name generation (should return a ucstring)
|
|
function baseClass.getBaseName(this)
|
|
return ucstring("")
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- return true if this instance can by displayed as a button in the select bar
|
|
function baseClass.displayInSelectBar(this)
|
|
return true
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get first parent that is selectable in the select bar
|
|
function baseClass.getFirstSelectBarParent(this)
|
|
local curr = this.ParentInstance
|
|
while curr and not curr:displayInSelectBar() do
|
|
curr = curr.ParentInstance
|
|
end
|
|
return curr
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- search the first son that could be inserted in the select bar
|
|
-- default is to look recursively in the 'son select bar container'
|
|
function baseClass.getFirstSelectBarSon(this)
|
|
local sons = this:getSelectBarSons()
|
|
if not sons then return nil end
|
|
for k, v in specPairs(sons) do
|
|
if v:displayInSelectBar() then
|
|
return v
|
|
end
|
|
local firstSelectBarSon = v:getFirstSelectBarSon()
|
|
if firstSelectBarSon ~= nil then
|
|
return firstSelectBarSon
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- test if object can have sons than are displayable in the select bar
|
|
function baseClass.canHaveSelectBarSons(this)
|
|
return false;
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- return the default container that may contain object displayable in the select bar
|
|
function baseClass.getSelectBarSons()
|
|
return nil
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- called by the select bar when it displays its menu. Additionnal can be added there
|
|
function baseClass.completeSelectBarMenu(rootMenu)
|
|
-- no-op
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- The following method is called when the default ui displayer wants to know where to attach an object in the instance tree
|
|
-- Default behaviour is to return the tree node of the parent object when one is found
|
|
function baseClass.getParentTreeNode(this)
|
|
parent = this.ParentInstance
|
|
while parent ~= nil do
|
|
if parent.User.TreeNodes ~= nil then
|
|
return parent.User.TreeNodes
|
|
end
|
|
parent = parent.ParentInstance
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------
|
|
-- Helper function for features : return the feature parent tree node in their act
|
|
function baseClass.getFeatureParentTreeNode(this)
|
|
|
|
--return this:getParentAct():getContentTreeNodes("macro_components")
|
|
return this:getParentAct():getContentTreeNodes()
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------------------
|
|
-- TODO: test if the object can be exported (true by default)
|
|
function baseClass.isExportable(this)
|
|
return true
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- This method is called by the C++ when the contextual menu is about to be displayed
|
|
function baseClass.onSetupMenu(this)
|
|
local class = r2:getClass(this)
|
|
local menuName = class.Menu
|
|
if menuName == nil then return end
|
|
local menu = getUI(menuName)
|
|
-- setup menu entries to select parents
|
|
--for i = 1,8 do
|
|
-- menu["selectParent" .. tostring(i)].active = false
|
|
--end
|
|
-- local parent = this.ParentInstance
|
|
-- for i = 1,9 do
|
|
-- if parent == nil or parent.Parent == nil then break end
|
|
-- menu["selectParent" .. tostring(i)].active = true
|
|
-- menu["selectParent" .. tostring(i)].uc_hardtext = i18n.get("uimR2EDSelectParent") + (parent.InstanceId .. " (" .. parent.Class .. ")")
|
|
-- --debugInfo(colorTag(0, 255, 255) .. tostring(i))
|
|
-- parent = parent.ParentInstance
|
|
-- end
|
|
-- -- setup cut & paste entries
|
|
-- local cuttedSelection = r2:getCuttedSelection()
|
|
-- if cuttedSelection and this.accept ~= nil then
|
|
-- local canPaste = this:accept(cuttedSelection)
|
|
-- debugInfo("canPaste = " .. tostring(canPaste))
|
|
-- menu.paste.grayed = not canPaste
|
|
-- else
|
|
-- menu.paste.grayed = true
|
|
-- end
|
|
-- debug options
|
|
local extDebug = config.R2EDExtendedDebug == 1
|
|
menu.dump_lua_table.active = extDebug
|
|
menu.inspect_lua_table.active = extDebug
|
|
menu.translateFeatures.active = extDebug
|
|
menu.dump_dialogs_as_text.active = extDebug
|
|
menu.update_dialogs_from_text.active = extDebug
|
|
|
|
menu.cut.active = extDebug
|
|
menu.paste.active = extDebug
|
|
|
|
|
|
|
|
r2.ContextualCommands:setupMenu(this, menu)
|
|
|
|
|
|
|
|
-- delete entries for dynamic content
|
|
-- local menuRoot = menu:getRootMenu()
|
|
-- local startLine = menuRoot:getLineFromId("dynamic_content_start")
|
|
-- local endLine = menuRoot:getLineFromId("dynamic_content_end")
|
|
-- assert(startLine ~= -1 and endLine ~= -1)
|
|
-- for lineToDel = endLine - 1, startLine + 1, -1 do
|
|
-- menuRoot:removeLine(lineToDel)
|
|
-- end
|
|
-- retrieve dynamic commands
|
|
-- local commands = this:getAvailableCommands()
|
|
-- local currentLine = startLine + 1
|
|
-- local commandIndex = 1
|
|
-- for commandIndex, command in commands do
|
|
-- menuRoot:addLineAtIndex(currentLine, i18n.get(command.TextId), "lua", "", "dyn_command_" .. tostring(commandIndex))
|
|
-- if there's a bitmap, build a group with the buitmap in it, and add to menu
|
|
-- if command.ButtonBitmap and command.ButtonBitmap ~= "" then
|
|
-- local menuButton = createGroupInstance("r2_menu_button", "", { bitmap = command.ButtonBitmap, })
|
|
-- if menuButton then
|
|
-- menuRoot:setUserGroupLeft(currentLine, menuButton)
|
|
-- end
|
|
-- end
|
|
--currentLine = currentLine + 1
|
|
--end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Show the property window for this instance
|
|
-- (react to the event 'show properties' triggered in the ui, by contextual menu or toolbar)
|
|
function baseClass.onShowProperties(this)
|
|
-- for now a global (see r2_ui_property_sheet.lua)
|
|
r2:showProperties(this)
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Return list of currently available commands to launch on that instance.
|
|
-- such commands are displayed in the contextual toolbar or in the contextual menu.
|
|
-- Returned value should be an array (starting at index 1) with commands of the following format :
|
|
--
|
|
-- { DoCommand = function(instance) ..., -- code to execute when the command is triggered (by menu or toolbar)
|
|
-- -- Because the function takes 'instance' as a parameter, it may be
|
|
-- -- either a global function or a method of this class
|
|
-- Id = "", -- Id of the action. The action "r2ed_context_command" defined in actions.xml
|
|
-- -- will search for this id when a key is pressed to find the good action
|
|
-- TextId = "...", -- Text id for entry menu & toolbar tooltip
|
|
-- ButtonBitmap = "filename.tga", -- Name of the button to display in the toolbar, nil
|
|
-- -- or "" if the command should not appear in the contextual toolbar
|
|
-- Separator = "true", -- optionnal, false by default : specify if there should be a separator
|
|
-- -- between this button and previous buttons
|
|
-- ShowInMenu = false, -- false if the entry shouldn't be displayed in the menu
|
|
-- IsActivity = false -- true if event is an activity
|
|
-- }
|
|
--
|
|
-- 'getAvailableCommands' should be called by derived class, possibly adding their
|
|
-- own commands
|
|
--
|
|
-- See also : 'buildCommand'
|
|
|
|
function baseClass.getAvailableCommands(this, dest)
|
|
if this.ParentInstance:isKindOf("UserComponentHolder") then
|
|
table.insert(dest, this:buildCommand(this.onRemoveFromUserComponent, "removeFromUserComponent", "uimR2EDRemoveFromUserComponent", ""))
|
|
end
|
|
if this:isDeletable() and this.User.DeleteInProgress ~= true then
|
|
table.insert(dest, this:buildCommand(this.onDelete, "delete", "uimR2EDMenuDelete", "r2_toolbar_delete.tga"))
|
|
end
|
|
if this:getClass().BuildPropertySheet then
|
|
table.insert(dest, this:buildCommand(this.onShowProperties, "properties", "uimR2EDMenuProperties", "r2_toolbar_properties.tga", true))
|
|
end
|
|
|
|
if this:isKindOf("NpcCustom") then
|
|
table.insert(dest, this:buildCommand(this.customizeLook, "customize_look", "uiR2EDCustomizeLook", "r2_toolbar_customize_look.tga", false))
|
|
end
|
|
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Build a single command entry to be used by 'getAvailableCommands'
|
|
-- A command entry translates into a button in the contextual toolbar
|
|
function baseClass.buildCommand(this, command, id, textId, bitmap, separator, showInMenu)
|
|
if showInMenu == nil then showInMenu = true end
|
|
return
|
|
{
|
|
DoCommand = command,
|
|
TextId = textId,
|
|
Id = id,
|
|
ButtonBitmap = bitmap,
|
|
Separator = separator,
|
|
ShowInMenu = showInMenu,
|
|
IsActivity = false
|
|
}
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- same as 'buildCommand', but for activities
|
|
function baseClass.buildActivityCommand(this, command, id, textId, bitmap, separator, showInMenu)
|
|
local result = this:buildCommand(command, id, textId, bitmap, separator, showInMenu)
|
|
result.IsActivity = true
|
|
return result
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Special, class method (not instance method) for dev : returns a table containing all infos on which the xml generic property sheet depends
|
|
-- When this table is modified, then the xml property sheet will be rebuild for this class (when 'resetEditor'
|
|
-- is called for example.
|
|
function baseClass.ClassMethods.getGenericPropertySheetCacheInfos(this)
|
|
local infos = {}
|
|
infos.Prop = this.Prop -- if one of the properties change, then must rebuild the property sheet
|
|
infos.PropertySheetHeader = this.PropertySheetHeader -- if the xml header change, then must rebuild the sheet, too
|
|
return infos
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- get list of command for display in the mini toolbar
|
|
function baseClass.getAvailableMiniCommands(this, result)
|
|
-- OBSOLETE
|
|
-- table.insert(result, this:buildCommand(this.editDialogs, "edit_dialogs", "uiR2EDEditDialogs", "r2_icon_dialog_mini.tga"))
|
|
-- table.insert(result, this:buildCommand(this.editActions, "edit_actions", "uiR2EDEditActions", "r2_icon_action_mini.tga"))
|
|
-- table.insert(result, this:buildCommand(this.editReactions, "edit_reactions", "uiR2EDEditReactions", "r2_icon_reaction_mini.tga"))
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Return true if sequences can be edited on that object
|
|
function baseClass.isSequencable(this)
|
|
return false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- For sequencable object only (baseClass.isSequencable) : return the lookup string for a verb from an activity name
|
|
-- Indeed, an activity may have different name depending on who performs it
|
|
-- for example, the "Feed In Zone" activity will be name "work" for a worker kitin instead of "feed" for a carnivore
|
|
function baseClass.getActivityVerbLookupName(this, activityName)
|
|
return activityName
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- is the object global to the scenario ? The select bar will call this to force the good update
|
|
-- for global objects that are selectable (plot items ...)
|
|
function baseClass.isGlobalObject(this)
|
|
return false
|
|
end
|
|
|
|
|
|
|
|
function baseClass.onRemoveFromUserComponent(this)
|
|
r2_core.CurrentHolderId = this.ParentInstance.InstanceId
|
|
r2_core:removeUserComponentElement(this.InstanceId)
|
|
end
|
|
|
|
-------------
|
|
-- REF IDS --
|
|
-------------
|
|
|
|
-- Set the value of a refId inside this object. (empty string to delete)
|
|
-- This will push a new action name & call r2.requestNode
|
|
function baseClass.setRefIdValue(this, refIdName, targetId)
|
|
local name = this:getDisplayName()
|
|
local refIdUCName = r2:getPropertyTranslation(this:getClass().NameToProp[refIdName])
|
|
if targetId == "" then
|
|
r2.requestNewAction(concatUCString(i18n.get("uiR2EDRemovingTargetAction"), name,
|
|
i18n.get("uiR2EDAddingReferenceSeparator"), refIdname))
|
|
else
|
|
local targetName = r2:getInstanceFromId(targetId):getDisplayName()
|
|
r2.requestNewAction(concatUCString(i18n.get("uiR2EDAddingReferenceAction"), name,
|
|
i18n.get("uiR2EDAddingReferenceSeparator"), refIdUCName,
|
|
i18n.get("uiR2EDAddingReferenceToAction"), targetName))
|
|
end
|
|
r2.requestSetNode(this.InstanceId, refIdName, targetId)
|
|
|
|
end
|
|
|
|
|
|
---------------------------
|
|
-- COPY / PASTE HANDLING --
|
|
---------------------------
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- see if this object support copy (false by default)
|
|
function baseClass.isCopyable(this)
|
|
return false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Create a canonical copy of this object, this copy can be used by subsequent calls to 'newCopy'
|
|
function baseClass.copy(this)
|
|
-- implementation in "r2_base_class_private.lua"
|
|
end
|
|
|
|
|
|
-- Create a new copy from a canonical copy
|
|
-- New instance ids are generated
|
|
-- Default behavior is to remove all external dependencies and to rename
|
|
-- internal dependencies
|
|
-- The result can be used as input to 'paste' & 'ghostPaste'
|
|
function baseClass.newCopy(canonicalCopy)
|
|
-- implementation in "r2_base_class_private.lua"
|
|
end
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
-- Paste the current clipboard
|
|
-- not really a method here, because 'src' id a lua table (should be the content of the clipboard ...) that can be used with
|
|
-- a r2.request.. command.
|
|
-- - this function should copy the result in a suitable place (maybe into current selection, or at global scope)
|
|
-- NB : if newPlace is not true, then the result should be past at the coordinates found in src
|
|
-- - It should check that there is some room in the scenario before doing the copy
|
|
function baseClass.paste(src, newPlace, srcInstanceId)
|
|
if r2:getLeftQuota() <= 0 then
|
|
r2:makeRoomMsg()
|
|
return
|
|
end
|
|
end
|
|
|
|
function baseClass.pasteGhost(src)
|
|
|
|
if r2:getLeftQuota() <= 0 then
|
|
r2:makeRoomMsg()
|
|
return
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- TMP TMP : move events test
|
|
|
|
--
|
|
function baseClass.onTargetInstancePreHrcMove(this, targetAttr, targetIndexInArray)
|
|
debugInfo(string.format("instance: pre hrc move : %s", targetAttr))
|
|
end
|
|
--
|
|
function baseClass.onTargetInstancePostHrcMove(this, targetAttr, targetIndexInArray)
|
|
debugInfo(string.format("instance : post hrc move : %s", targetAttr))
|
|
end
|
|
|
|
|
|
|
|
-- IMPLEMENTATION
|
|
r2.doFile("r2_base_class_private.lua")
|
|
|
|
|
|
r2.registerComponent(baseClass)
|
|
|
|
baseClass = nil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|