Clients/Client2018/content/internal/AppShell/Modules/Shell/AvatarEditorView.lua

517 lines
16 KiB
Lua

-------------- CONSTANTS --------------
local STICK_ROTATION_MULTIPLIER = 3
local THUMBSTICK_DEADZONE = 0.2
-------------- SERVICES --------------
local ContextActionService = game:GetService("ContextActionService")
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CoreGui = game:GetService('CoreGui')
------------ MODULES -------------------
local GuiRoot = CoreGui:FindFirstChild('RobloxGui')
local Modules = GuiRoot:FindFirstChild('Modules')
local AppState = require(Modules.LuaApp.Legacy.AvatarEditor.AppState)
local SetConsoleMenuLevel = require(Modules.LuaApp.Actions.SetConsoleMenuLevel)
local ResetCategory = require(Modules.LuaApp.Actions.ResetCategory)
local ToggleAvatarType = require(Modules.LuaApp.Actions.ToggleAvatarType)
local ToggleAvatarEditorFullView = require(Modules.LuaApp.Actions.ToggleAvatarEditorFullView)
local CreateCharacterManager = require(Modules.LuaApp.Legacy.AvatarEditor.CharacterManager)
local CreatePageManager = require(Modules.LuaApp.Legacy.AvatarEditor.PageManagerConsole)
local CreateCategoryMenu = require(Modules.LuaApp.Legacy.AvatarEditor.CategoryMenuConsole)
local CreateTabList = require(Modules.LuaApp.Legacy.AvatarEditor.TabListConsole)
local ConsoleButtonIndicators = require(Modules.LuaApp.Legacy.AvatarEditor.ConsoleButtonIndicators)
local LayoutInfo = require(Modules.LuaApp.Legacy.AvatarEditor.LayoutInfoConsole)
local Flags = require(Modules.LuaApp.Legacy.AvatarEditor.Flags)
local XboxAppState = require(Modules.Shell.AppState)
----------- UTILITIES --------------
local Utilities = require(Modules.LuaApp.Legacy.AvatarEditor.Utilities)
local TableUtilities = require(Modules.LuaApp.TableUtilities)
------------ SHELL MODULES -------------------
local ShellModules = script.Parent
local CameraManager = require(ShellModules:FindFirstChild('CameraManager'))
local EventHub = require(ShellModules:FindFirstChild('EventHub'))
local LoadingWidget = require(ShellModules:FindFirstChild('LoadingWidget'))
-------------- FFLAGS --------------
local SoundManager = require(ShellModules:FindFirstChild('SoundManager'))
------------ VARIABLES -------------------
local characterTemplates = {
CharacterR6 = ReplicatedStorage:WaitForChild('CharacterR6');
CharacterR15 = ReplicatedStorage:WaitForChild('CharacterR15');
CharacterR15New = ReplicatedStorage:WaitForChild('CharacterR15New');
}
local cameraTweenerObject = {
tweenCamera = function(newCFrame, newFOV)
CameraManager:UpdateAvatarEditorCamera(newCFrame, newFOV)
end
}
----------- CLASS DECLARATION --------------
local function createAvatarEditorView()
AppState:Init()
local this = {}
local storeChangedCn = nil
local scrollingFrameCn = nil
local avatarEditorLoader = nil
local savedSelectedGuiObject = nil
local selectedGuiObjectEnterFullView = nil
local toggleButtonDebounce = true
local toggleViewDebounce = true
local showCount = 0
local inShow = false
local inFocus = false
local lastCharacterSaveTime = nil
local characterUpdated = false
local characterEquipped = false
local savingAvatarMutex = false
local rotationInfo = {
rotation = 0;
lastRotation = 0;
delta = 0;
}
local Container = Utilities.create'ScrollingFrame'
{
Name = 'AvatarEditorContainer';
Size = UDim2.new(1, 0, 1, 0);
CanvasSize = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
ScrollingEnabled = false;
Selectable = false;
Visible = true;
BorderSizePixel = 0;
ScrollBarThickness = 0;
}
local BackgroundOverlay = Utilities.create'ImageLabel'
{
Name = 'BackgroundOverlay';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = true;
Image = 'rbxasset://textures/ui/Shell/AvatarEditor/graphic/gr-background overlay merge.png';
ZIndex = LayoutInfo.BackgroundLayer;
Parent = Container;
}
local frame = Utilities.create'Frame'
{
Position = UDim2.new(0, 0, 0, 270);
Size = UDim2.new(1, 0, 1, 0);
Name = "PageFrame";
BackgroundTransparency = 1;
ClipsDescendants = true;
Visible = true;
Selectable = false;
ZIndex = LayoutInfo.BasicLayer;
Parent = Container;
}
local scrollingFrame = Utilities.create'ScrollingFrame'
{
AnchorPoint = Vector2.new(1, 0);
Position = UDim2.new(1, -99, 0, 0);
Size = UDim2.new(0, 491, 1, -270);
Name = "ScrollingFrame";
BackgroundTransparency = 1;
ClipsDescendants = true;
Visible = true;
ScrollingEnabled = false;
ScrollBarThickness = 0;
Selectable = false;
ZIndex = LayoutInfo.BasicLayer;
Parent = frame;
}
ConsoleButtonIndicators.init(Container)
local warningFrame = Utilities.create'Frame'
{
AnchorPoint = Vector2.new(0.5, 0);
Position = UDim2.new(0.5, 0, 0, 0);
Size = UDim2.new(0, LayoutInfo.WarningMaxLength, 1, 0);
Name = "WarningWidget";
BackgroundTransparency = 1;
Visible = true;
Selectable = false;
ZIndex = LayoutInfo.IndicatorLayer;
Parent = Container;
}
local CameraController = require(Modules.LuaApp.Legacy.AvatarEditor.CameraController)(
cameraTweenerObject, LayoutInfo.CameraCenterScreenPosition)
local characterManager = CreateCharacterManager(
{
get = Utilities.httpGet;
post = Utilities.httpPost;
},
characterTemplates,
1
)
local WarningWidget = require(Modules.LuaApp.Legacy.AvatarEditor.WarningWidget)(warningFrame, characterManager, true)
local PageManager = CreatePageManager(
XboxAppState.store:getState().RobloxUser.rbxuid,
scrollingFrame,
characterManager
)
characterManager:setUpdateCameraCallback(CameraController.updateCamera)
local CategoryMenu = CreateCategoryMenu(Container)
local TabList = CreateTabList(Container, PageManager)
local function initStickRotation()
local gamepadInput = Vector2.new(0, 0)
ContextActionService:UnbindCoreAction("StickRotation")
ContextActionService:BindCoreAction("StickRotation", function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Change then
gamepadInput = inputObject.Position or gamepadInput
gamepadInput = Vector2.new(gamepadInput.X, gamepadInput.Y)
if math.abs(gamepadInput.X) > THUMBSTICK_DEADZONE then
rotationInfo.delta = STICK_ROTATION_MULTIPLIER * gamepadInput.X
else
rotationInfo.delta = 0
end
end
end,
false, Enum.KeyCode.Thumbstick2)
end
local function initKeyControls()
toggleButtonDebounce = true
toggleViewDebounce = true
ContextActionService:UnbindCoreAction("KeyControls")
ContextActionService:BindCoreAction("KeyControls", function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
if inputObject.KeyCode == Enum.KeyCode.ButtonSelect and not AppState.Store:getState().FullView then
if toggleButtonDebounce then
toggleButtonDebounce = false
SoundManager:Play('ButtonPress')
AppState.Store:dispatch(ToggleAvatarType())
toggleButtonDebounce = true
end
end
if inputObject.KeyCode == Enum.KeyCode.ButtonR3 then
if toggleViewDebounce then
toggleViewDebounce = false
SoundManager:Play('ScreenChange')
AppState.Store:dispatch(ToggleAvatarEditorFullView())
toggleViewDebounce = true
end
end
end
end,
false, Enum.KeyCode.ButtonR3, Enum.KeyCode.ButtonSelect)
end
local function initMenuActions()
ContextActionService:UnbindCoreAction("AvatarEditorMenu")
ContextActionService:BindCoreAction("AvatarEditorMenu", function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
if inputObject.KeyCode == Enum.KeyCode.ButtonB then
if AppState.Store:getState().ConsoleMenuLevel > LayoutInfo.ConsoleMenuLevel.CategoryMenu then
local currentMenuLevel = AppState.Store:getState().ConsoleMenuLevel
SoundManager:Play('PopUp')
AppState.Store:dispatch(SetConsoleMenuLevel(currentMenuLevel - 1))
else
-- Back to avatar page in AppShell
AppState.Store:dispatch(ResetCategory())
AppState.Store:dispatch(SetConsoleMenuLevel(LayoutInfo.ConsoleMenuLevel.None))
return Enum.ContextActionResult.Pass
end
end
elseif inputState == Enum.UserInputState.Begin then
if inputObject.KeyCode == Enum.KeyCode.ButtonL2 then
if AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.CategoryMenu then
CategoryMenu:SelectPreviousPage()
elseif AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.TabList then
TabList:SelectPreviousPage()
elseif AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.AssetsPage then
PageManager:SelectPreviousPage()
end
elseif inputObject.KeyCode == Enum.KeyCode.ButtonR2 then
if AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.CategoryMenu then
CategoryMenu:SelectNextPage()
elseif AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.TabList then
TabList:SelectNextPage()
elseif AppState.Store:getState().ConsoleMenuLevel == LayoutInfo.ConsoleMenuLevel.AssetsPage then
PageManager:SelectNextPage()
end
end
end
end,
false, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonL2, Enum.KeyCode.ButtonR2)
end
local function updateFullViewCoreAction(fullView)
ContextActionService:UnbindCoreAction("FullView")
if fullView == true then
ContextActionService:BindCoreAction("FullView",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
if inputObject.KeyCode == Enum.KeyCode.ButtonB then
SoundManager:Play('ScreenChange')
AppState.Store:dispatch(ToggleAvatarEditorFullView())
end
return Enum.ContextActionResult.Sink
end
end,
false, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonA, Enum.KeyCode.ButtonL2, Enum.KeyCode.ButtonR2)
BackgroundOverlay.Visible = false
elseif fullView == false then
BackgroundOverlay.Visible = true
end
end
local function FocusAll()
initStickRotation()
initKeyControls()
initMenuActions()
CameraController:Focus()
CategoryMenu:Focus()
TabList:Focus()
PageManager:Focus()
WarningWidget:Focus()
if not savedSelectedGuiObject and not AppState.Store:getState().FullView then
AppState.Store:dispatch(SetConsoleMenuLevel(LayoutInfo.ConsoleMenuLevel.CategoryMenu))
elseif savedSelectedGuiObject then
Utilities.setSelectedCoreObject(savedSelectedGuiObject)
end
updateFullViewCoreAction(AppState.Store:getState().FullView)
storeChangedCn = AppState.Store.Changed:Connect(function(newState, oldState)
if newState.FullView ~= oldState.FullView then
updateFullViewCoreAction(newState.FullView)
if newState.FullView == true then
local selectedCoreObject = Utilities.getSelectedCoreObject()
if selectedCoreObject then
selectedGuiObjectEnterFullView = selectedCoreObject
end
Utilities.setSelectedCoreObject(nil)
else
if selectedGuiObjectEnterFullView then
Utilities.setSelectedCoreObject(selectedGuiObjectEnterFullView)
end
end
end
if not characterEquipped or not characterUpdated then
local didUpdate = false
local didEquip = false
if newState.Character.AvatarType ~= oldState.Character.AvatarType then
didUpdate = true
end
if newState.Character.Assets ~= oldState.Character.Assets then
for assetType, assetList in pairs(oldState.Character.Assets) do
if not newState.Character.Assets[assetType] and assetList then
if next(assetList) ~= nil then
didUpdate = true
didEquip = true
break
end
end
end
if not didUpdate or not didEquip then
for assetType, _ in pairs(newState.Character.Assets) do
if newState.Character.Assets[assetType] ~= oldState.Character.Assets[assetType] then
local addTheseAssets = TableUtilities.ListDifference(
newState.Character.Assets[assetType] or {},
oldState.Character.Assets[assetType] or {})
local removeTheseAssets =
TableUtilities.ListDifference(oldState.Character.Assets[assetType] or {},
newState.Character.Assets[assetType] or {})
if next(addTheseAssets) ~= nil or next(removeTheseAssets) ~= nil then
didUpdate = true
didEquip = true
break
end
end
end
end
end
if newState.Character.BodyColors ~= oldState.Character.BodyColors then
local differentBodyColors =
TableUtilities.TableDifference(newState.Character.BodyColors, oldState.Character.BodyColors)
if next(differentBodyColors) ~= nil then
didUpdate = true
end
end
if newState.Character.Scales ~= oldState.Character.Scales then
local differentScales =
TableUtilities.TableDifference(newState.Character.Scales, oldState.Character.Scales)
if next(differentScales) ~= nil then
didUpdate = true
end
end
if not characterEquipped then
characterEquipped = didEquip
end
if not characterUpdated then
characterUpdated = didUpdate
end
end
end)
scrollingFrameCn = scrollingFrame.Changed:connect(
function(prop)
if prop == 'CanvasPosition' then
PageManager:updateListContent(scrollingFrame.CanvasPosition.Y)
end
end
)
end
local function RemoveFocusAll()
CameraController:RemoveFocus()
PageManager:RemoveFocus()
CategoryMenu:RemoveFocus()
TabList:RemoveFocus()
WarningWidget:RemoveFocus()
Utilities.disconnectEvent(storeChangedCn)
Utilities.disconnectEvent(scrollingFrameCn)
ContextActionService:UnbindCoreAction("FullView")
ContextActionService:UnbindCoreAction("AvatarEditorMenu")
ContextActionService:UnbindCoreAction("StickRotation")
ContextActionService:UnbindCoreAction("KeyControls")
end
function this:Show(parent)
local startCount = showCount
inShow = true
characterUpdated = false
characterEquipped = false
local prevAvatarEditorLoader = avatarEditorLoader
Utilities.fastSpawn(function()
if startCount == showCount then
avatarEditorLoader = LoadingWidget(
{Parent = parent},
{function()
if prevAvatarEditorLoader then
prevAvatarEditorLoader:AwaitFinished()
prevAvatarEditorLoader:Cleanup()
end
if startCount == showCount then
characterManager.initFromServer()
characterManager.show()
end
end}
)
avatarEditorLoader:AwaitFinished()
if startCount == showCount then
avatarEditorLoader:Cleanup()
avatarEditorLoader = nil
end
end
if startCount == showCount then
Container.Parent = parent
if inFocus then
FocusAll()
end
while startCount == showCount do
local deltaT = Utilities.renderWait()
if startCount == showCount then
rotationInfo.rotation = rotationInfo.rotation + deltaT * rotationInfo.delta
characterManager.setRotation(rotationInfo.rotation)
if not lastCharacterSaveTime or tick() - lastCharacterSaveTime > 5 then
characterManager.saveToServer(false)
lastCharacterSaveTime = tick()
end
end
end
end
end)
end
function this:Hide()
inShow = false
showCount = showCount + 1
characterManager.hide()
savedSelectedGuiObject = nil
selectedGuiObjectEnterFullView = nil
Container.Parent = nil
if not avatarEditorLoader then
RemoveFocusAll()
CategoryMenu:Hide()
WarningWidget:Hide()
local savedCharacterUpdated = characterUpdated
local savedCharacterEquipped = characterEquipped
spawn(function()
while savingAvatarMutex do
wait()
end
savingAvatarMutex = true
characterManager.saveToServer(true)
savingAvatarMutex = false
if savedCharacterUpdated then
Utilities.fastSpawn(function()
EventHub:dispatchEvent(EventHub.Notifications["CharacterUpdated"])
end)
end
if savedCharacterEquipped then
Utilities.fastSpawn(function()
EventHub:dispatchEvent(EventHub.Notifications["CharacterEquipped"], AppState.Store:getState().Character.Assets, savedCharacterUpdated)
end)
end
end)
end
end
--Rebind actions, make them in the right order
function this:Focus()
inFocus = true
if inShow and not avatarEditorLoader then
FocusAll()
end
end
function this:RemoveFocus()
inFocus = false
if inShow and not avatarEditorLoader then
savedSelectedGuiObject = Utilities.getSelectedCoreObject()
RemoveFocusAll()
end
end
function this:Destruct()
this:RemoveFocus()
this:Hide()
Container:Destroy()
characterManager.destroy()
AppState:Destruct()
end
return this
end
return createAvatarEditorView