using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic; using VRC.Core; using VRC.SDKBase; namespace VRCSDK2 { #if UNITY_EDITOR public class RuntimeBlueprintCreation : RuntimeAPICreation { public GameObject waitingPanel; public GameObject blueprintPanel; public GameObject errorPanel; public Text titleText; public InputField blueprintName; public InputField blueprintDescription; public RawImage bpImage; public Image liveBpImage; public Toggle shouldUpdateImageToggle; public Toggle contentSex; public Toggle contentViolence; public Toggle contentGore; public Toggle contentOther; public Toggle developerAvatar; public Toggle sharePrivate; public Toggle sharePublic; public Toggle tagFallback; public UnityEngine.UI.Button uploadButton; private ApiAvatar apiAvatar; new void Start() { if (!Application.isEditor || !Application.isPlaying) return; base.Start(); var desc = pipelineManager.GetComponent(); desc.PositionPortraitCamera(imageCapture.shotCamera.transform); Application.runInBackground = true; UnityEngine.XR.XRSettings.enabled = false; uploadButton.onClick.AddListener(SetupUpload); shouldUpdateImageToggle.onValueChanged.AddListener(ToggleUpdateImage); Login(); } void LoginErrorCallback(string obj) { VRC.Core.Logger.LogError("Could not log in - " + obj, DebugLevel.Always); blueprintPanel.SetActive(false); errorPanel.SetActive(true); } void Login() { if (!ApiCredentials.Load()) LoginErrorCallback("Not logged in"); else APIUser.InitialFetchCurrentUser( delegate (ApiModelContainer c) { pipelineManager.user = c.Model as APIUser; ApiAvatar av = new ApiAvatar() { id = pipelineManager.blueprintId }; av.Get(false, (c2) => { VRC.Core.Logger.Log("Updating an existing avatar.", DebugLevel.API); apiAvatar = c2.Model as ApiAvatar; pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId); SetupUI(); }, (c2) => { VRC.Core.Logger.Log("Creating a new avatar.", DebugLevel.API); apiAvatar = new ApiAvatar(); apiAvatar.id = pipelineManager.blueprintId; pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId); SetupUI(); }); }, (c) => { LoginErrorCallback(c.Error); }); } void SetupUI() { if (!ValidateAssetBundleBlueprintID(apiAvatar.id)) { blueprintPanel.SetActive(false); errorPanel.SetActive(true); OnSDKPipelineError("The asset bundle is out of date. Please rebuild the scene using 'New Build'.", "The blueprint ID in the scene does not match the id in the asset bundle."); return; } if (APIUser.Exists(pipelineManager.user)) { waitingPanel.SetActive(false); blueprintPanel.SetActive(true); errorPanel.SetActive(false); if (isUpdate) { // bp update if (apiAvatar.authorId == pipelineManager.user.id) { titleText.text = "Update Avatar"; // apiAvatar = pipelineManager.user.GetBlueprint(pipelineManager.blueprintId) as ApiAvatar; blueprintName.text = apiAvatar.name; contentSex.isOn = apiAvatar.tags.Contains("content_sex"); contentViolence.isOn = apiAvatar.tags.Contains("content_violence"); contentGore.isOn = apiAvatar.tags.Contains("content_gore"); contentOther.isOn = apiAvatar.tags.Contains("content_other"); developerAvatar.isOn = apiAvatar.tags.Contains("developer"); sharePrivate.isOn = apiAvatar.releaseStatus.Contains("private"); sharePublic.isOn = apiAvatar.releaseStatus.Contains("public"); tagFallback.isOn = apiAvatar.tags.Contains("author_quest_fallback"); tagFallback.transform.parent.gameObject.SetActive(true); switch (pipelineManager.fallbackStatus) { case PipelineManager.FallbackStatus.Valid: #if UNITY_ANDROID tagFallback.interactable = true; tagFallback.GetComponentInChildren().text = "Use for Fallback"; #else tagFallback.interactable = false; tagFallback.GetComponentInChildren().text = "Use for Fallback (change only with Android upload)"; #endif break; case PipelineManager.FallbackStatus.InvalidPerformance: case PipelineManager.FallbackStatus.InvalidRig: tagFallback.isOn = false; // need to remove tag on this upload, the updated version is not up-to-spec tagFallback.interactable = false; tagFallback.GetComponentInChildren().text = "Use for Fallback (avatar not valid, tag will be cleared)"; break; } blueprintDescription.text = apiAvatar.description; shouldUpdateImageToggle.interactable = true; shouldUpdateImageToggle.isOn = false; liveBpImage.enabled = false; bpImage.enabled = true; ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, (Texture2D obj) => bpImage.texture = obj, null); } else // user does not own apiAvatar id associated with descriptor { Debug.LogErrorFormat("{0} is not an owner of {1}", apiAvatar.authorId, pipelineManager.user.id); blueprintPanel.SetActive(false); errorPanel.SetActive(true); } } else { titleText.text = "New Avatar"; shouldUpdateImageToggle.interactable = false; shouldUpdateImageToggle.isOn = true; liveBpImage.enabled = true; bpImage.enabled = false; tagFallback.isOn = false; // Janky fix for an avatar's blueprint image not showing up the very first time you press publish in a project until you resize the window // can remove if we fix the underlying issue or move publishing out of Play Mode string firstTimeResize = $"{Application.identifier}-firstTimeResize"; if (!PlayerPrefs.HasKey(firstTimeResize)) { GameViewMethods.ResizeGameView(); PlayerPrefs.SetInt(firstTimeResize, 1); } tagFallback.transform.parent.gameObject.SetActive(true); switch (pipelineManager.fallbackStatus) { case PipelineManager.FallbackStatus.Valid: #if UNITY_ANDROID tagFallback.interactable = true; tagFallback.GetComponentInChildren().text = "Use for Fallback"; #else tagFallback.interactable = false; tagFallback.GetComponentInChildren().text = "Use for Fallback (change only with Android upload)"; #endif break; case PipelineManager.FallbackStatus.InvalidPerformance: case PipelineManager.FallbackStatus.InvalidRig: tagFallback.transform.parent.gameObject.SetActive(true); tagFallback.interactable = false; tagFallback.GetComponentInChildren().text = "Use for Fallback (avatar not valid, tag will be cleared)"; break; } } } else { waitingPanel.SetActive(true); blueprintPanel.SetActive(false); errorPanel.SetActive(false); } if (APIUser.CurrentUser != null && APIUser.CurrentUser.hasSuperPowers) developerAvatar.gameObject.SetActive(true); else developerAvatar.gameObject.SetActive(false); } public void SetupUpload() { uploadTitle = "Preparing For Upload"; isUploading = true; string abPath = UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath"); string unityPackagePath = UnityEditor.EditorPrefs.GetString("VRC_exportedUnityPackagePath"); UnityEditor.EditorPrefs.SetBool("VRCSDK2_scene_changed", true); UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sex", contentSex.isOn); UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_violence", contentViolence.isOn); UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_gore", contentGore.isOn); UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_other", contentOther.isOn); if (string.IsNullOrEmpty(apiAvatar.id)) { pipelineManager.AssignId(); apiAvatar.id = pipelineManager.blueprintId; } string avatarId = apiAvatar.id; int version = isUpdate ? apiAvatar.version + 1 : 1; PrepareVRCPathForS3(abPath, avatarId, version, ApiAvatar.VERSION); if (!string.IsNullOrEmpty(unityPackagePath) && System.IO.File.Exists(unityPackagePath)) { VRC.Core.Logger.Log("Found unity package path. Preparing to upload!", DebugLevel.All); PrepareUnityPackageForS3(unityPackagePath, avatarId, version, ApiAvatar.VERSION); } StartCoroutine(UploadNew()); } IEnumerator UploadNew() { bool caughtInvalidInput = false; if (!ValidateNameInput(blueprintName)) caughtInvalidInput = true; if (caughtInvalidInput) yield break; VRC.Core.Logger.Log("Starting upload", DebugLevel.Always); // upload unity package if (!string.IsNullOrEmpty(uploadUnityPackagePath)) { yield return StartCoroutine(UploadFile(uploadUnityPackagePath, isUpdate ? apiAvatar.unityPackageUrl : "", GetFriendlyAvatarFileName("Unity package"), "Unity package", delegate (string fileUrl) { cloudFrontUnityPackageUrl = fileUrl; } )); } // upload asset bundle if (!string.IsNullOrEmpty(uploadVrcPath)) { yield return StartCoroutine(UploadFile(uploadVrcPath, isUpdate ? apiAvatar.assetUrl : "", GetFriendlyAvatarFileName("Asset bundle"), "Asset bundle", delegate (string fileUrl) { cloudFrontAssetUrl = fileUrl; } )); } if (isUpdate) yield return StartCoroutine(UpdateBlueprint()); else yield return StartCoroutine(CreateBlueprint()); OnSDKPipelineComplete(); } private string GetFriendlyAvatarFileName(string type) { return "Avatar - " + blueprintName.text + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion + "_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl(); } List BuildTags() { var tags = new List(); if (contentSex.isOn) tags.Add("content_sex"); if (contentViolence.isOn) tags.Add("content_violence"); if (contentGore.isOn) tags.Add("content_gore"); if (contentOther.isOn) tags.Add("content_other"); if (APIUser.CurrentUser.hasSuperPowers) { if (developerAvatar.isOn) tags.Add("developer"); } if (tagFallback.isOn) tags.Add("author_quest_fallback"); return tags; } protected override IEnumerator CreateBlueprint() { yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image"))); ApiAvatar avatar = new ApiAvatar { id = pipelineManager.blueprintId, authorName = pipelineManager.user.displayName, authorId = pipelineManager.user.id, name = blueprintName.text, imageUrl = cloudFrontImageUrl, assetUrl = cloudFrontAssetUrl, description = blueprintDescription.text, tags = BuildTags(), unityPackageUrl = cloudFrontUnityPackageUrl, releaseStatus = sharePublic.isOn ? "public" : "private" }; bool doneUploading = false; bool wasError = false; avatar.Post( (c) => { ApiAvatar savedBP = (ApiAvatar)c.Model; pipelineManager.blueprintId = savedBP.id; UnityEditor.EditorPrefs.SetString("blueprintID-" + pipelineManager.GetInstanceID().ToString(), savedBP.id); AnalyticsSDK.AvatarUploaded(savedBP, false); doneUploading = true; }, (c) => { Debug.LogError(c.Error); SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f); doneUploading = true; wasError = true; }); while (!doneUploading) yield return null; if (wasError) yield return new WaitUntil(() => UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Error saving blueprint.", "Okay")); } protected override IEnumerator UpdateBlueprint() { bool doneUploading = false; apiAvatar.name = blueprintName.text; apiAvatar.description = blueprintDescription.text; apiAvatar.assetUrl = cloudFrontAssetUrl; apiAvatar.releaseStatus = sharePublic.isOn ? "public" : "private"; apiAvatar.tags = BuildTags(); apiAvatar.unityPackageUrl = cloudFrontUnityPackageUrl; if (shouldUpdateImageToggle.isOn) { yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image"))); apiAvatar.imageUrl = cloudFrontImageUrl; } SetUploadProgress("Saving Avatar", "Almost finished!!", 0.8f); apiAvatar.Save( (c) => { AnalyticsSDK.AvatarUploaded(apiAvatar, true); doneUploading = true; }, (c) => { Debug.LogError(c.Error); SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f); doneUploading = true; }); while (!doneUploading) yield return null; } void ToggleUpdateImage(bool isOn) { if (isOn) { bpImage.enabled = false; liveBpImage.enabled = true; } else { bpImage.enabled = true; liveBpImage.enabled = false; ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, obj => bpImage.texture = obj, null); } } void OnDestroy() { UnityEditor.EditorUtility.ClearProgressBar(); UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath"); } } #endif }