function RegisterLevel(short_alias, name, path, resource_path, mode, parent_level_name)
	if not levels then
		levels = {}
	end
	if not levels_index_map then
		levels_index_map = {}
	end

	assert(not levels[name])

	levels[name]                    = {}
	levels[name].path               = path
	levels[name].resource_path      = resource_path
	levels[name].mode               = mode
	levels[name].parent_level_name  = parent_level_name
	levels[name].short_alias        = short_alias
	if IsPreloadManagerEnabled() then
	    levels[name].resources          = GResourcePreloadManager:ParseFolder(resource_path)
	end

	levels_index_map[short_alias] = name
end
-------------------------------------------------------------------------------

function GetLevelPath(name)
	assert(name)
	return levels[name].path
end
-------------------------------------------------------------------------------

function GetLevelResourcePath(name)
	assert(name)
	return levels[name].resource_path
end
-------------------------------------------------------------------------------

function IsLevelCollectorsEdition(name)
    assert(levels[name])
	return levels[name].collectors_edition
end
-------------------------------------------------------------------------------

do
    local qt = 'quest'
    local ho = 'hidden_object'
    local mg = 'mini_game'
    
    function IsLevelQuest(name)
        assert(levels[name])
        return levels[name].level_type==qt
    end

    function IsLevelHiddenObject(name)
        assert(levels[name])
        return levels[name].level_type==ho
    end

    function IsLevelMiniGame(name)
        assert(levels[name])
        return levels[name].level_type==mg
    end
end
-------------------------------------------------------------------------------

function IsSceneVisited(scene_name)
    return HasGameState(SCENE_VISITED, scene_name)
end
-------------------------------------------------------------------------------

function IsSceneClosed(scene_name)
    return HasGameState(SCENE_CLOSED, scene_name)
end
-------------------------------------------------------------------------------

function SetSceneClosed(scene_name)
    if not IsSceneClosed(scene_name) then
        AddGameState(SCENE_CLOSED, scene_name)
    end
end
-------------------------------------------------------------------------------

function ReopenScene(scene_name)
	if IsSceneClosed(scene_name) then
		RemoveGameState(SCENE_CLOSED, scene_name)
		
		local level = GetCurrentLevel()
		
		for trigger_name, linkInfo in pairs(level.linked_scenes) do
		    if linkInfo.level_name == scene_name then
		        MarkLinkZone(trigger_name, false)
		    end
		end
	end
end
-------------------------------------------------------------------------------

function IsMiniGameCompleted(location)
    return HasGameState(MG_COMPLETED, location)
end
-------------------------------------------------------------------------------

function CompleteMinigame(time, callback) 
    local level = GetCurrentLevel()
    time = time or 0
    
    if callback == nil then
        callback =  function()
                        StartLevel(level.parent_level)
                    end
    end
    
    AddUniqueGameState(SCENE_CLOSED, level.name)
	AddUniqueGameState(MG_COMPLETED, level.name)
	
    if level.find_toolMG then
        FindTool(level.find_toolMG, true)
    end
	
    level.timer:Add(callback, time)
end
-------------------------------------------------------------------------------

function IsDebugLocalization()
    if debuglocalization or GetGameManager():IsDebugTasksMode() then
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------
function IsCollectorsEdition()
	return IS_COLLECTORS_EDITION
end
-------------------------------------------------------------------------------
function IsSampleEdition()
	return IS_SAMPLE_EDITION
end
-------------------------------------------------------------------------------
function IsDemoMode()
	return IS_DEMO_EDITION
end
-------------------------------------------------------------------------------

function IsSubwayMode()
    local CurrPlayer = GetGameManager():GetCurrPlayer()
    if CurrPlayer and CurrPlayer.name == 'ChEaTeR94' then
        return true
    end
    
    return false
end

-------------------------------------------------------------------------------
function IsHOScene(levelName)
	assert(levels and levels[levelName] and "Specified level hasn't been registered !")
	local sceneAlias = levels[levelName].short_alias
	return string.match(sceneAlias, "^%d%d*ho")
end

-------------------------------------------------------------------------------
function IsMinigameScene(levelName)
	assert(levels and levels[levelName] and "Specified level hasn't been registered !")
	local sceneAlias = levels[levelName].short_alias
	return string.match(sceneAlias, "^%d%d*mg%d*")
end
-------------------------------------------------------------------------------

function GetZoomZoneOfObject(objectName)
    local level = GetCurrentLevel()
    
    if level.zones then
	    for _, zoom in pairs(level.zones) do
		    if zoom:HasObject(objectName) then
			    return zoom
		    end
	    end
	end
	return nil
end
-------------------------------------------------------------------------------

function RegisterWrongApplyDialog(tool_name, area_name, dialog_name)
    local level = GetCurrentLevel()
    
    if level.wrong_apply_dialogs[dialog_name] == nil then
        level.wrong_apply_dialogs[dialog_name] = {}
    end
    
    local dialog_table = level.wrong_apply_dialogs[dialog_name]
    
    ----
    
    local add_tool_function =   function(area_table, tool)
                                    if tool ~= nil then
                                        if type(tool) == 'table' then
                                            for j, curr_tool in ipairs(tool) do
                                                table.insert(area_table, curr_tool)
                                            end
                                        else
                                            table.insert(area_table, tool)
                                        end
                                    else
                                        table.insert(area_table, '_ANY_TOOL_')
                                    end
                                end
    
    ----
    
    if type(area_name) == 'table' then
        for i, area in ipairs(area_name) do
            
            if dialog_table[area] == nil then
                dialog_table[area] = {};
            end
            
            add_tool_function(dialog_table[area], tool_name)
        end
    else
        if dialog_table[area_name] == nil then
            dialog_table[area_name] = {}
        end
            
        add_tool_function(dialog_table[area_name], tool_name)
    end
end
-------------------------------------------------------------------------------

function GetWrongApplyDialog(tool_name, area_name)
    for dialog, dialog_table in pairs(GetCurrentLevel().wrong_apply_dialogs) do
        local area_table = dialog_table[area_name]
        
        if area_table ~= nil then
            local any_tool_key_exist = false;
            
            for i, tool in ipairs(area_table) do
                if tool == '_ANY_TOOL_' then
                    any_tool_key_exist = true
                    
                elseif tool == tool_name then
                    return dialog
                end
            end
            
            if any_tool_key_exist then
                return dialog;
            end
        end
    end
    
    return nil
end
-------------------------------------------------------------------------------

function OnCollectObjectFound(e)
    ShowBlackBar('H_ROSE_FIND_04')
    
    
    local count = e.sender:GetFoundObjectsCount()
    if count == 1 then
    --    ShowBlackBar('H_ROSE_FIND_00')
        
        NotebookPushNote('Note_02_Rose')
    else
        --TODO: optimize
    --    ShowCollectObjectDialog()
    end
end
-------------------------------------------------------------------------------

--TODO: optimize
function ShowCollectObjectDialog()
    local count = GetCurrentLevel().collectManager:GetFoundObjectsCount() or 0
    local t     = GetString('H_ROSE_FIND_01')..tostring(count)..GetString('H_ROSE_FIND_02')
    
    ShowBlackBar(t, true)
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- ACTIVE-OBJECTS HELPERS -----------------------------------------------------
-------------------------------------------------------------------------------

function IsObjectClicked(obj_name, level_name)
    if not level_name then
        level_name = GetCurrentLevel().name
    end

    local state_value = level_name..'('..obj_name..')'
    return HasGameState(OBJECT_CLICKED, state_value)
end
-------------------------------------------------------------------------------

function RegisterActiveObject(obj_name, on_click, multi_use, cursor_name)
    cursor_name = cursor_name or 'CursorActive'
    
    local bObjectClicked = IsObjectClicked(obj_name)
    
    if multi_use or not bObjectClicked then
        local level = GetCurrentLevel()
        
        level.active_objects[obj_name] = {}
        level.active_objects[obj_name].on_click  = on_click        
        level.active_objects[obj_name].multi_use = multi_use
        level.custom_cursors[obj_name] = cursor_name
    end
    
    if bObjectClicked and on_click then
        local event  = {}
        event.save   = true
        event.target = obj_name
        
        on_click(event)
    end
end
-------------------------------------------------------------------------------

function UnregisterActiveObject(obj_name)
    local level = GetCurrentLevel()
    level.active_objects[obj_name] = nil
    level.custom_cursors[obj_name] = ''
    
    if play_field:HasObject(obj_name) then
        play_field:IgnoreOnTestPoint(obj_name)
    end
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- SWITCH-OBJECTS HELPERS -----------------------------------------------------
-------------------------------------------------------------------------------
function RegisterSwitchObject(triggers_name, on_change, objects1, objects2, time)
    time = time or 0

    local multy_triggers = type(triggers_name) == 'table'
    local state
    --------------
    
    if multy_triggers then
        assert(table.getn(triggers_name) == 2)
        
        state = GetCurrentLevel().name..'('..triggers_name[1]..')'
        Object(triggers_name[2]):SetVisible(false)
    else
        state = GetCurrentLevel().name..'('..triggers_name..')'
    end
    --------------
    
    local function change_opasity(objects, opacity, time)
        if objects == nil then
            return
        end
        
        local obj_type = type(objects)
        
        if obj_type == 'table' then
            for _, obj_name in ipairs(objects) do
                ObjectOpacity(obj_name, opacity, time)
            end
        elseif obj_type == 'string' then
            ObjectOpacity(objects, opacity, time)
        else
            objects:ChangeOpacity(objects:GetOpacity(), opacity, time)
        end
    end
    --------------
    
    local function on_click(e)
        e.time = (e.save) and 0 or time
        
        if e.save then
            if HasGameState(TRIGGER_OBJECT_SETED, state) then
                change_opasity(objects1, 0, 0)
                change_opasity(objects2, 1, 0)
                e.set = true
            else
                change_opasity(objects1, 1, 0)
                change_opasity(objects2, 0, 0)
                e.set = false
            end
            
            if multy_triggers then
                Object(triggers_name[2]):SetVisible(e.set)
                Object(triggers_name[1]):SetVisible(not e.set)
            end
        else
            if HasGameState(TRIGGER_OBJECT_SETED, state) then
                RemoveGameState(TRIGGER_OBJECT_SETED, state)
                change_opasity(objects1, 1, e.time)
                change_opasity(objects2, 0, e.time)
                e.set = false
            else
                AddGameState(TRIGGER_OBJECT_SETED, state)
                change_opasity(objects1, 0, e.time)
                change_opasity(objects2, 1, e.time)
                e.set = true
            end
            
            if multy_triggers then
                local showId = (e.set) and 2 or 1
                local hideId = (e.set) and 1 or 2
                
                Object(triggers_name[hideId]):SetVisible(false)
                GetCurrentLevel().timer:Add(function()
                                                Object(triggers_name[showId]):SetVisible(true)
                                            end,
                                            e.time)
            else
                Object(triggers_name):SetVisible(false)
                GetCurrentLevel().timer:Add(function()
                                                Object(triggers_name):SetVisible(true)
                                            end,
                                            e.time)
            end
        end
        
        on_change(e)
    end
    --------------
    
    do
        local event  = {}
        event.save   = true
        event.target = (multy_triggers) and triggers_name[1] or triggers_name
        
        on_click(event)
    end
    --------------
    
    if multy_triggers then
        RegisterActiveObject(triggers_name[1], on_click, true)
        RegisterActiveObject(triggers_name[2], on_click, true)
    else
        RegisterActiveObject(triggers_name, on_click, true)
    end
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- MONOLOG HELPERS ------------------------------------------------------------
-------------------------------------------------------------------------------

EMonologAliasOrder = {
    SIMPLE = 1,
    RANDOM = 2,
    LOOP   = 3
}

function RegisterMonologObject(obj_name, text_alias, alias_order, ignore_highlight_on_click)
    alias_order = alias_order or EMonologAliasOrder.SIMPLE
    
	local function on_click(event)
        if not event.save then
            local text_alias_type = type(text_alias)
             
            if text_alias_type == 'table' then
                local aliasCount = #text_alias
                local textKey    = nil;
	            
                if alias_order == EMonologAliasOrder.RANDOM then
                    local index = math.random(aliasCount)
                    textKey     = text_alias[index]
                    
                elseif alias_order == EMonologAliasOrder.LOOP then
                    local index = 0;
                    local state = string.upper('MONOLOG_LOOP_STEP_'..GetCurrentLevel().name..'_'..event.target)
                    
                    if HasGameState(state) then
                        index = tonumber( GetGameState(state):Get(0) )
                    end
                    
                    index   = (index < aliasCount) and (index + 1) or 1
                    textKey = text_alias[index];
                    
                    SetGameState(state, tostring(index))
                    
                elseif alias_order == EMonologAliasOrder.SIMPLE then
                    for _, _textKey in ipairs(text_alias) do
                        textKey = _textKey
                        if not HasGameState(COMICS_SHOWN, textKey) then
                            break;
                        end
                    end
                end
	            
	            if textKey then
                    ShowBlackBar(textKey)
                end
                
            elseif text_alias_type == 'string' then
                ShowBlackBar(text_alias)
            else
                assert(false and 'text_alias must be a string or table')
            end
        end
    end
                        
	RegisterActiveObject(obj_name, on_click, true, 'CursorGlass')
	
	if ignore_highlight_on_click then
        GetCurrentLevel().ignore_on_highlight[obj_name] = true
    end
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- LINK SCENE HELPERS ---------------------------------------------------------
-------------------------------------------------------------------------------

function MarkLinkZone(trigger_name, is_closed)
    local level    = GetCurrentLevel()
    local linkInfo = level.linked_scenes[trigger_name]
    
    if not is_closed then
        
        if linkInfo.condition and not linkInfo.condition() then
            level.custom_cursors[trigger_name] = 'CursorGlass'
        else
            level.custom_cursors[trigger_name] = linkInfo.cursor_name
        end
        
        if linkInfo.mark_with_ps and not play_field:GetObject(trigger_name):HasParticleSystem() then
            local ParticleSystem = play_field:GetObject(trigger_name):AddParticleSystem('data/ParticleSystem/ObjectHighlight.psi', 
                                                                                        'ZoneHighlightParticle', 
                                                                                        true)
            ParticleSystem:AffectedByOpacity(true)
        end
        
        if linkInfo.highlight_obj then
            local function play_at_random(obj)
                local len = obj:GetAnimation():GetFrames()
                local rnd = math.random(len)
                obj:GetAnimation():SetFrame(rnd)
            end
            
            if type(linkInfo.highlight_obj) == 'table' then
                for i, v in pairs(linkInfo.highlight_obj) do
                    play_field:GetObject(v):Show()
                    play_at_random(play_field:GetObject(v))
                end
            else
                play_field:GetObject(linkInfo.highlight_obj):Show()
                play_at_random(play_field:GetObject(linkInfo.highlight_obj))
            end
        end
    else
        level.custom_cursors[trigger_name] = ''
        play_field:GetObject(trigger_name):ResetParticleSystem()
        
        if linkInfo.highlight_obj then
            if type(linkInfo.highlight_obj) == 'table' then
                for i, v in pairs(linkInfo.highlight_obj) do
                    play_field:GetObject(v):Hide()
                end
            else
                play_field:GetObject(linkInfo.highlight_obj):Hide()
            end
        end
    end
end
-------------------------------------------------------------------------------

function LinkScene(trigger_name, cursor_name, level_name, highlight_obj, camera_target_pos, mark_with_ps, condition, on_condition_fail)
    assert(play_field:HasObject(trigger_name) and "Trigger object doesn't exist in this location's playfield!");
    
    local level = GetCurrentLevel()
    
    local linkInfo             = {}
    linkInfo.cursor_name       = cursor_name
    linkInfo.level_name        = level_name
    linkInfo.camera_target_pos = camera_target_pos
    linkInfo.mark_with_ps      = mark_with_ps
    linkInfo.highlight_obj     = highlight_obj
    linkInfo.condition         = condition
    linkInfo.on_condition_fail = on_condition_fail
    
    level.linked_scenes[trigger_name]       = linkInfo
    level.ignore_on_highlight[trigger_name] = true
    
    if highlight_obj then
        if type(highlight_obj) == 'table' then
            for i, v in pairs(highlight_obj) do
                Helpers.AttachInPlace(play_field:GetObject(v), play_field:GetObject(trigger_name))
                play_field:IgnoreOnTestPoint(v)
            end
        else
            Helpers.AttachInPlace(play_field:GetObject(highlight_obj), play_field:GetObject(trigger_name))
            play_field:IgnoreOnTestPoint(highlight_obj)
        end
    end

    local is_closed = IsSceneClosed(level_name)
    MarkLinkZone(trigger_name, is_closed)
    
    if IsPreloadManagerEnabled() and not is_closed then
        local level_info = levels[level_name]
        if level_info then
            GResourcePreloadManager:PreloadTextures(level_info.resource_path, level_info.resources)
        end
    end
end
-------------------------------------------------------------------------------

function StartLevel(level_name, camera_target_pos_not_used)
    if not bLoadLevelBlocked then
        bLoadLevelBlocked = true
        
        local fade_delay = (camera_target_pos_not_used) and FADE_SCENE_TIME or 0
        local level      = GetCurrentLevel()
        
        if play_field then
		    active_zones:PopAll()
			level.locker:AddRef() -- to prevent zooms opening etc
            
            level.timer:Add(function()
								DestroyCurrentLevel()
								load_level(level_name, levels[level_name].path)
							end, fade_delay)

            --Hack for quick hiding of the monolog background
			if level.prev_comics then
                local b2 = play_field:GetLevelScreen():GetPanel():GetObject('black_bar')
                b2:ChangeOpacity(b2:GetOpacity(), 0, fade_delay)
            end
            
			if camera_target_pos_not_used then
				ZoomPlayFieldIn(2, camera_target_pos_not_used, fade_delay)
			end
        else
            local load_next_level = "load_level('"
                                    ..level_name
                                    .."', '"
                                    ..levels[level_name].path
                                    .."')"
            GTimerDoString(load_next_level, fade_delay)
		end
    end
end
---------------------------------------------------------------------------------
----------------------------------------------------------------------------------

function ZoomPlayField(scale, zoomPoint, duration)
	assert(play_field ~= nil and scale ~= nil)

	if zoomPoint.x > SCR_W - SCR_W / 2. / scale then
		zoomPoint.x = SCR_W - SCR_W / 2. / scale
	end
	if zoomPoint.x < SCR_W / 2. / scale then
		zoomPoint.x = SCR_W / 2. / scale
	end
	if zoomPoint.y > SCR_H - SCR_H / 2. / scale then
		zoomPoint.y = SCR_H - SCR_H / 2. / scale
	end
	if zoomPoint.y < SCR_H / 2. / scale then
		zoomPoint.y = SCR_H / 2. / scale
	end

	zoomPoint.x = SCR_W / 2. - zoomPoint.x * scale
	zoomPoint.y = SCR_H / 2. - zoomPoint.y * scale

	local af = CreateAnimationEffector();
	af:SetDestroysOnEnd(true)
	af:SetKey(_TRACK_X, 0, play_field:GetX())
	af:SetKey(_TRACK_Y, 0, play_field:GetY())
	af:SetKey(_TRACK_S, 0, play_field:GetScale())
	af:SetKey(_TRACK_X, duration, zoomPoint.x)
	af:SetKey(_TRACK_Y, duration, zoomPoint.y)
	af:SetKey(_TRACK_S, duration, scale)
	af:AttachTo(play_field)
	af:Play()
end
-------------------------------------------------------------------------------

function ZoomPlayFieldIn(scale, zoomPoint, duration)
	assert(play_field:GetScale() == 1.0);
	ZoomPlayField(scale, zoomPoint, duration);
end
-------------------------------------------------------------------------------

function ZoomPlayFieldOut(duration)
	local pScale = play_field:GetScale();
	ZoomPlayField(1.0, {play_field:GetX()/(1.0 - pScale), play_field:GetY()/(1.0 - pScale)}, duration);
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- BIND DOOR OBJECT HELPERS ---------------------------------------------------
-------------------------------------------------------------------------------

function BindDoorObject(trigger_name, door_object, open_sound, close_sound)
    local linkInfo = GetCurrentLevel().linked_scenes[trigger_name]
    
	linkInfo.door_object = door_object
	linkInfo.open_sound  = open_sound
	linkInfo.close_sound = close_sound
	
	door_object:SetOpacity(0)
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- QUEST-OBJECTS HELPERS ------------------------------------------------------
-------------------------------------------------------------------------------

function RegisterQuestObjects(...)
    local level = GetCurrentLevel()
    
	for i, v in ipairs({...}) do
		level.quest_objects[v] = true
	end
end
-------------------------------------------------------------------------------

function FocusOnObject(objectName, dx,dy, scaleFactor, angle)
	dx = dx or 0
	dy = dy or 0
	scaleFactor =  scaleFactor or 1.0
	angle =  angle or 0

	assert(play_field)
	assert(objectName and play_field:HasObject(objectName) and "Name of non-existent object was specified !")

	fadeObjectCount = (not fadeObjectCount and 0 or fadeObjectCount + 1)

	local object = play_field:GetObject(objectName)
	local pos = {x = object:GetScrX(), y = object:GetScrY(), z = 300}
	local opacityRange = {0.8, 0.0}
	local scaleRange = {14.0 * scaleFactor, 25.0 * scaleFactor}

    local name_focus_object = 'fadeObject_'..tostring(fadeObjectCount)

	local anim = CreateAnimation('data/Texture/Common/focus.png',0,0,128,128,1,0)
	anim:SetHotSpot(64,64)
	anim:SetBlendMode(2)
	anim:SetColor('0xFFFFFFFF')
	local fadeObj = play_field:CreateObject(name_focus_object, anim, pos.x + dx, pos.y + dy, pos.z, angle * M_PI/180, scaleRange[1],scaleRange[1])
	play_field:IgnoreOnTestPoint(name_focus_object)

	assert(fadeObj)
	do
		local fadeDuration = 3.0
		local fadeDelayPercent = 0.4

		local eff = CreateAnimationEffector()
		eff:SetDestroysOnEnd(true)
		eff:AttachTo(fadeObj)
		eff:SetKey(_TRACK_O, 0, 							0.0)
		eff:SetKey(_TRACK_O, fadeDelayPercent/5*fadeDuration,opacityRange[1])
		eff:SetKey(_TRACK_O, fadeDelayPercent*fadeDuration, opacityRange[1])
		eff:SetKey(_TRACK_O, fadeDuration,					opacityRange[2])
		eff:SetKey(_TRACK_SX,0,								scaleRange[1])
		eff:SetKey(_TRACK_SY,0,								scaleRange[1])
		eff:SetKey(_TRACK_SX,fadeDelayPercent*fadeDuration, scaleRange[1])
		eff:SetKey(_TRACK_SY,fadeDelayPercent*fadeDuration, scaleRange[1])
		eff:SetKey(_TRACK_SX,fadeDuration,					scaleRange[2])
		eff:SetKey(_TRACK_SY,fadeDuration,					scaleRange[2])
		eff:Play()
	end
end
-------------------------------------------------------------------------------

function SetForcedObject(objectName)
    local level = GetCurrentLevel()
    
    if level.forcedObject ~= objectName then
        level.forcedObject = objectName
        
        if objectName then
            level.mouseBlocker:Push()
        else
            level.mouseBlocker:Pop()
        end
    end
end
-------------------------------------------------------------------------------

function GetForcedObject()
    return GetCurrentLevel().forcedObject
end
-------------------------------------------------------------------------------

function FocusOnPos(x, y, angle, scaleX, scaleY)
    assert(play_field)
    angle = angle or 0

	local pos   = {
                    x = x or SCR_W/2,
                    y = y or SCR_H/2,
                    z = 300
                  }
    local scale = {
                    x = scaleX or 25,
                    y = scaleY or 25
                  }
                
    fadeObjectCount      = (not fadeObjectCount and 0 or fadeObjectCount + 1)
	local opacityRange   = {0.8, 0.0}
    local fadeObjectName = 'fadeObject_'..tostring(fadeObjectCount)
    local fadeObject

    do
        local anim = CreateAnimation('data/Texture/Common/focus.png',0,0,128,128,1,0)
        anim:SetHotSpot(64,64)
        anim:SetBlendMode(2)
        anim:SetColor('0xFFFFFFFF')
        fadeObject = play_field:CreateObject(fadeObjectName, anim, pos.x, pos.y, pos.z, angle, scale.x, scale.y)
        play_field:IgnoreOnTestPoint(fadeObjectName)
    end
    
	do
		local fadeDuration     = 3.0
		local fadeDelayPercent = 0.4

		local eff = CreateAnimationEffector()
		eff:SetDestroysOnEnd(true)
		eff:AttachTo(fadeObject)
		eff:SetKey(_TRACK_O, 0, 							  0.0)
		eff:SetKey(_TRACK_O, fadeDelayPercent/5*fadeDuration, opacityRange[1])
		eff:SetKey(_TRACK_O, fadeDelayPercent*fadeDuration,   opacityRange[1])
		eff:SetKey(_TRACK_O, fadeDuration,					  opacityRange[2])
		eff:Play()
	end
end
-------------------------------------------------------------------------------

function CorrectAnimation(obj_name, color, opacity, is_visible, ignore_on_test_point)
    if ignore_on_test_point == nil then
        ignore_on_test_point = false
    end

    if play_field and play_field:HasObject(obj_name) then
        local obj = play_field:GetObject(obj_name)
        
        obj:GetAnimation():SetColor(color)
        obj:SetOpacity(opacity)
        obj:SetVisible(is_visible)
        
        if ignore_on_test_point and not play_field:IsIgnoredOnTestPoint(obj_name) then
            play_field:IgnoreOnTestPoint(obj_name)
        end
    end
end
-------------------------------------------------------------------------------

function SetCutSceneMode(time, callback)
    local level = GetCurrentLevel()
    local border_bottom
    local border_top
    
    -- Create Border object
    if play_field:HasObject('border_bottom') then
        border_bottom = Object('border_bottom')
    else
        local anim = CreateAnimation('data/Texture/Common/cutSceneBar.png',0,0,1,80,1,0)
	    anim:SetHotSpot(0,40)
	    anim:SetBlendMode(2)
	    anim:SetColor('0xCCFFFFFF')
	    border_bottom = play_field:CreateObject('border_bottom',anim,0,728,200,0,SCR_W,1)
    end
    
    -- Create top object
    if play_field:HasObject('border_top') then
        border_top = Object('border_top')
    else
        local anim = CreateAnimation('data/Texture/Common/cutSceneBar.png',0,0,1,80,1,0)
	    anim:SetHotSpot(0,40)
	    anim:SetBlendMode(2)
	    anim:SetColor('0xCCFFFFFF')
	    border_top = play_field:CreateObject('border_top',anim,0,40,200,0,SCR_W,-1)
    end
    
    local move_height = 64
    local move_time = 0.75
    
    level.prev_panel_mode = level:GetPanelLuaObject():GetMode()
    level:GetPanelLuaObject():SetMode(EPanelMode.PM_CLEAN)
    level.highlighting_disabled = true
    level.mouseBlocker:Push()
    
    border_top:SetOpacity(1)
	border_bottom:SetOpacity(1)

    border_top:SetY(play_field:GetObject('border_top'):GetY() - move_height)
	border_bottom:SetY(play_field:GetObject('border_bottom'):GetY() + move_height)
	border_top:Move(0, move_height, move_time)
    border_bottom:Move(0, -move_height, move_time)
    
    level.timer:Add(function()
                        level:GetPanelLuaObject():SetMode(level.prev_panel_mode)
                        level.prev_panel_mode = nil
                        level.highlighting_disabled = false
                        
                        border_top:SetOpacity(1)
                        border_bottom:SetOpacity(1)

                        border_top:SetY(    play_field:GetObject('border_top'   ):GetY() - move_height)
                        border_bottom:SetY( play_field:GetObject('border_bottom'):GetY() + move_height)
                        border_top:Move(    0, -move_height, move_time)
                        border_bottom:Move( 0,  move_height, move_time)
                        
                        level.timer:Add(    function()
                                                level.mouseBlocker:Pop()
                                                
                                                if callback then
                                                    callback()
                                                end
                                            end,
                                            move_time)
                    end,
                    time)  
end
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- HIGLIGHT OBJECTS -----------------------------------------------------------
-------------------------------------------------------------------------------

function IsValidHighlightCandidate(obj_name)
    local level = GetCurrentLevel()
    
    local result =  play_field:HasObject(obj_name) and
		            play_field:GetObject(obj_name):IsVisible() and
		            (play_field:GetObject(obj_name):GetScrOpacity() > 0. or IsInNotVisibleZoom(obj_name)) and
		            not play_field:IsIgnoredOnTestPoint(obj_name) and
		            level.ignore_on_highlight[obj_name] ~= true
    
    return result
end
-------------------------------------------------------------------------------

function HighlightObject(obj_name, effect_time, is_quest_object, scale_koef)
	if GetCurrentLevel().highlighting_disabled or not IsRegularPlayMode() then
		return
	end
	
	scale_koef = (scale_koef == nil) and 1 or scale_koef
	effect_time = effect_time or 1.3

	local obj
	if is_quest_object then
		obj = play_field:GetObject('quest_object_highlight')
	else
		obj = play_field:GetObject('object_highlight')
	end
	

	local target = play_field:GetObject(obj_name)

	obj:SetScrXY(target:GetScrX(), target:GetScrY())
	obj:SetScrZ(target:GetScrZ() + 0.1)
	
	if target:IsVisible() then
	    obj:Show()
    else
        obj:Hide()
    end
	
	if is_quest_object then
        local af = CreateAnimationEffector()
        af:SetDestroysOnEnd(true)
        af:AttachTo(obj)
        af:SetMode(_ANIM_NOLOOP)
        af:SetKey(_TRACK_O, effect_time * 0.,   0.)
        af:SetKey(_TRACK_A, effect_time * 0.,   0.)
        af:SetKey(_TRACK_S, effect_time * 0.,   0.3 * scale_koef)
        af:SetKey(_TRACK_O, effect_time * 0.05, 1.)
        af:SetKey(_TRACK_S, effect_time * 0.25, 1. * scale_koef)
        af:SetKey(_TRACK_O, effect_time * 0.45, 1.)
        af:SetKey(_TRACK_A, effect_time * 0.45, 35. * M_PI / 180.)
        af:SetKey(_TRACK_S, effect_time * 0.45, 1.4 * scale_koef)
        af:SetKey(_TRACK_O, effect_time * 0.70, 1.)
        af:SetKey(_TRACK_A, effect_time * 1.,   50. * M_PI / 180.)
        af:SetKey(_TRACK_S, effect_time * 1.,   1. * scale_koef)
        af:SetKey(_TRACK_O, effect_time * 1.,   0.)
        af:Play()
    else
        local length = GetAnimationLength( obj:GetAnimation() )
        obj:GetAnimation():Play()
        obj:SetScale(scale_koef)
        obj:SetOpacity(1)
        GetCurrentLevel().timer:Add(function()
                                        obj:SetOpacity(0)
                                    end, length)
    end
end
-------------------------------------------------------------------------------

function HighlightRandomObject()
    local level      = GetCurrentLevel()
    local candidates = {}
    
	level.object_highlight_delay = level.object_highlight_period
	
	if  level.mode ~= "hidden_object" and
	    level.mode ~= "hidden_picture"
    then
		for i, v in pairs(level.tools) do
			if v.trigger_name then
				table.insert(candidates, v.trigger_name)
			end
		end
	end

	for i, v in pairs(level.custom_cursors) do
		if v ~= nil and v ~= '' then
			table.insert(candidates, i)
		end
	end
	
	if level.drag_info and level.drag_objects[level.drag_info.obj_name] then
	    local target_area = level.drag_objects[level.drag_info.obj_name].target_area
	    
	    if target_area ~= nil then
            if type(target_area) == 'string' then
                table.insert(candidates, target_area)
            else
                for i,target in pairs(target_area) do
                    table.insert(candidates, target)
                end
            end
        end
	end
	
	if level.additional_highlight then
	    for i, v in pairs(level.additional_highlight) do
	        table.insert(candidates, i)
	    end
	end

	local candidates_copy = candidates
	candidates = {}
	for i, v in ipairs(candidates_copy) do
		if IsValidHighlightCandidate(v) then
		    if IsInActiveZone(v) and level.quest_objects[v] ~= true then
		        table.insert(candidates, v)
		    end
		end
	end
	candidates_copy = nil
	
	--GetCurrentLevel().last_highlight_candidates = candidates
	table.shuffle(candidates)

	if #candidates > 0 then
		local effect_time = 1.3
		if effect_time > level.object_highlight_period then
		    effect_time = level.object_highlight_period
		end
		local candidate_name
		for _, v in pairs(candidates) do
		    candidate_name = v
		    break
		end
		
		HighlightObject(candidate_name, effect_time)
	end
end
-------------------------------------------------------------------------------

function HighlightRandomQuestObject()
    local level = GetCurrentLevel()
    
	level.quest_object_highlight_delay = level.quest_object_highlight_period

	local candidates = {}
	for i, v in pairs(level.quest_objects) do
		if v == true and IsValidHighlightCandidate(i) then
			table.insert(candidates, i)
		end
	end

	if table.getn(candidates) > 0 then
		local target_index = math.random(1, table.getn(candidates))
		local effect_time  = 1.3
		
		if effect_time > level.quest_object_highlight_period then
		    effect_time = level.quest_object_highlight_period
		end
		HighlightObject(candidates[target_index], effect_time, true)
	end
end
-------------------------------------------------------------------------------

function HighlightRandomHOAction()
    local level = GetCurrentLevel()
    
    assert(level.mode == 'hidden_object' and "Not HO level?")
    
	level.ho_item_highlight_delay = level.ho_item_highlight_period
				
	if not level.action_areas then
		level.action_areas = {}
		level.highlited_areas = {}
		
		for object in play_field:GetObjects() do
			if level.active_objects[object:GetName()] or level.drag_objects[object:GetName()] then
				if level.drag_objects[object:GetName()] then
					table.insert(level.action_areas, Object(level.drag_objects[object:GetName()].get_area))
				else
					table.insert(level.action_areas, object)
				end
			end
		end
	end
				
	local candidates = {}
	for object in each(level.action_areas) do
		if object:IsVisible() and object:GetOpacity() > 0 and not table.has_value(level.highlited_areas, object:GetName()) then
			table.insert(candidates, object)
		end
	end
	
	if #candidates ~= 0 then
		local index = math.random(1, table.getn(candidates))
		table.insert(level.highlited_areas, candidates[index]:GetName())
		candidates[index]:Highlight()
		if #candidates == 1 and #level.highlited_areas ~= 0 then
			level.highlited_areas = {}
		end
	end
end
-------------------------------------------------------------------------------
