modify scripts
This commit is contained in:
@ -11,7 +11,8 @@
|
||||
"GUID:c98377141161c7746a178fb5cb1af075",
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"jp.hadashikick.vcontainer"
|
||||
"jp.hadashikick.vcontainer",
|
||||
"GUID:63a57c8b658089e49a173b0f0c4870a7"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.IO;
|
||||
|
||||
namespace WordsToolkit.Scripts.Editor
|
||||
{
|
||||
public class CustomModelPostProcessor : AssetPostprocessor
|
||||
{
|
||||
private static readonly string SOURCE_PATH = "Assets/WordsToolkit/model/custom";
|
||||
private static readonly string TARGET_PATH = "Assets/StreamingAssets/WordConnectGameToolkit/model/custom";
|
||||
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
bool hasCustomModelChanges = false;
|
||||
|
||||
// Check imported assets
|
||||
foreach (string assetPath in importedAssets)
|
||||
{
|
||||
if (IsCustomModelFile(assetPath))
|
||||
{
|
||||
hasCustomModelChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check moved assets
|
||||
if (!hasCustomModelChanges)
|
||||
{
|
||||
foreach (string assetPath in movedAssets)
|
||||
{
|
||||
if (IsCustomModelFile(assetPath))
|
||||
{
|
||||
hasCustomModelChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCustomModelChanges)
|
||||
{
|
||||
CopyCustomModelsToStreamingAssets();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCustomModelFile(string assetPath)
|
||||
{
|
||||
return assetPath.StartsWith(SOURCE_PATH) &&
|
||||
(assetPath.EndsWith(".bin") || assetPath.EndsWith(".json") || assetPath.EndsWith(".txt"));
|
||||
}
|
||||
|
||||
private static void CopyCustomModelsToStreamingAssets()
|
||||
{
|
||||
if (!Directory.Exists(SOURCE_PATH))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create target directory
|
||||
Directory.CreateDirectory(TARGET_PATH);
|
||||
|
||||
// Get all custom model files
|
||||
string[] files = Directory.GetFiles(SOURCE_PATH, "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (string sourceFile in files)
|
||||
{
|
||||
// Skip .meta files
|
||||
if (sourceFile.EndsWith(".meta"))
|
||||
continue;
|
||||
|
||||
// Calculate relative path from source directory
|
||||
string relativePath = Path.GetRelativePath(SOURCE_PATH, sourceFile);
|
||||
string targetFile = Path.Combine(TARGET_PATH, relativePath);
|
||||
|
||||
// Create target subdirectory if needed
|
||||
string targetDir = Path.GetDirectoryName(targetFile);
|
||||
if (!Directory.Exists(targetDir))
|
||||
{
|
||||
Directory.CreateDirectory(targetDir);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Copy file if it doesn't exist or is newer
|
||||
if (!File.Exists(targetFile) || File.GetLastWriteTime(sourceFile) > File.GetLastWriteTime(targetFile))
|
||||
{
|
||||
File.Copy(sourceFile, targetFile, true);
|
||||
Debug.Log($"[CustomModelPostProcessor] Copied {relativePath} to StreamingAssets");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[CustomModelPostProcessor] Failed to copy {sourceFile}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the asset database so Unity sees the new files
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
[MenuItem("WordToolkit/Copy Custom Models to StreamingAssets")]
|
||||
private static void ManualCopyCustomModels()
|
||||
{
|
||||
CopyCustomModelsToStreamingAssets();
|
||||
Debug.Log("[CustomModelPostProcessor] Manual copy completed");
|
||||
}
|
||||
|
||||
[MenuItem("WordToolkit/Clean Custom Models from StreamingAssets")]
|
||||
private static void CleanCustomModelsFromStreamingAssets()
|
||||
{
|
||||
if (Directory.Exists(TARGET_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(TARGET_PATH, true);
|
||||
Debug.Log("[CustomModelPostProcessor] Cleaned custom models from StreamingAssets");
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[CustomModelPostProcessor] Failed to clean StreamingAssets: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a1b585689cce4771abd8ec25020f1a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -23,71 +23,71 @@ namespace WordsToolkit.Scripts.Editor
|
||||
public static string WordConnect = "WordConnect";
|
||||
private static string WordConnectPath = "Assets/WordConnectGameToolkit";
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Shop settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Shop settings")]
|
||||
public static void IAPProducts()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/CoinsShopSettings.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Ads settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Ads settings")]
|
||||
public static void AdsSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/AdsSettings.asset");
|
||||
}
|
||||
|
||||
//DailyBonusSettings
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Daily bonus settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Daily bonus settings")]
|
||||
public static void DailyBonusSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/DailyBonusSettings.asset");
|
||||
}
|
||||
|
||||
//GameSettings
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Game settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Game settings")]
|
||||
public static void GameSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/GameSettings.asset");
|
||||
}
|
||||
|
||||
//SpinSettings
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Spin settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Spin settings")]
|
||||
public static void SpinSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/SpinSettings.asset");
|
||||
}
|
||||
|
||||
//DebugSettings
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Debug settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Debug settings")]
|
||||
public static void DebugSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/DebugSettings.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Crossword config")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Crossword config")]
|
||||
public static void CrosswordConfig()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/CrosswordConfig.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Tutorial settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Tutorial settings")]
|
||||
public static void TutorialSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/TutorialSettings.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Language configuration")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Language configuration")]
|
||||
public static void LanguageConfiguration()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/LanguageConfiguration.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Gift settings")]
|
||||
[MenuItem( nameof(WordConnect) + "/Settings/Gift settings")]
|
||||
public static void GiftSettings()
|
||||
{
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/GiftSettings.asset");
|
||||
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(WordConnectPath + "/Resources/Settings/GiftsSettings.asset");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Scenes/Main scene &1", priority = 0)]
|
||||
[MenuItem( nameof(WordConnect) + "/Scenes/Main scene &1", priority = 0)]
|
||||
public static void MainScene()
|
||||
{
|
||||
EditorSceneManager.OpenScene(WordConnectPath + "/Scenes/main.unity");
|
||||
@ -99,7 +99,7 @@ namespace WordsToolkit.Scripts.Editor
|
||||
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Scenes/Game scene &2")]
|
||||
[MenuItem( nameof(WordConnect) + "/Scenes/Game scene &2")]
|
||||
public static void GameScene()
|
||||
{
|
||||
var stateManager = Object.FindObjectOfType<StateManager>();
|
||||
@ -107,7 +107,7 @@ namespace WordsToolkit.Scripts.Editor
|
||||
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Editor/Tile editor", priority = 1)]
|
||||
[MenuItem( nameof(WordConnect) + "/Editor/Tile editor", priority = 1)]
|
||||
public static void ColorEditor()
|
||||
{
|
||||
string folderPath = WordConnectPath + "/Resources/ColorsTile";
|
||||
@ -130,44 +130,37 @@ namespace WordsToolkit.Scripts.Editor
|
||||
EditorGUIUtility.PingObject(tileAsset);
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Documentation/Main", priority = 2)]
|
||||
[MenuItem( nameof(WordConnect) + "/Documentation/Main", priority = 2)]
|
||||
public static void MainDoc()
|
||||
{
|
||||
Application.OpenURL("https://candy-smith.gitbook.io/main");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Documentation/ADS/Setup ads")]
|
||||
[MenuItem( nameof(WordConnect) + "/Documentation/ADS/Setup ads")]
|
||||
public static void UnityadsDoc()
|
||||
{
|
||||
Application.OpenURL("https://candy-smith.gitbook.io/bubble-shooter-toolkit/tutorials/ads-setup/");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Documentation/Unity IAP (in-apps)")]
|
||||
[MenuItem( nameof(WordConnect) + "/Documentation/Unity IAP (in-apps)")]
|
||||
public static void Inapp()
|
||||
{
|
||||
Application.OpenURL("https://candy-smith.gitbook.io/main/block-puzzle-game-toolkit/setting-up-in-app-purchase-products");
|
||||
}
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/NLP/Training Language Model")]
|
||||
[MenuItem( nameof(WordConnect) + "/NLP/Training Language Model")]
|
||||
public static void TrainingModel()
|
||||
{
|
||||
Application.OpenURL("https://colab.research.google.com/drive/199zNcB3FPfnrD6E7OiwmwCcf27jMnY1b?usp=sharing");
|
||||
}
|
||||
|
||||
|
||||
[MenuItem(nameof(WordConnect) + "/Reset PlayerPrefs &e")]
|
||||
[MenuItem( nameof(WordConnect) + "/Reset PlayerPrefs &e")]
|
||||
private static void ResetPlayerPrefs()
|
||||
{
|
||||
GameDataManager.ClearALlData();
|
||||
PlayerPrefs.DeleteKey("GameState");
|
||||
Debug.Log("PlayerPrefs are reset");
|
||||
}
|
||||
|
||||
// 🔥 新增菜单项,打开 PrefabBatchViewer 窗口
|
||||
[MenuItem(nameof(WordConnect) + "/Settings/Prefab Batch Viewer")]
|
||||
public static void OpenPrefabBatchViewer()
|
||||
{
|
||||
PopupPreview.ShowWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ namespace WordsToolkit.Scripts.Editor
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
CheckDefines("Assets/GoogleMobileAds", "ADMOB");
|
||||
CheckUMPAvailable();
|
||||
CheckDefines("Assets/FacebookSDK", "FACEBOOK");
|
||||
CheckDefines("Assets/PlayFabSDK", "PLAYFAB");
|
||||
CheckDefines("Assets/GameSparks", "GAMESPARKS");
|
||||
@ -41,6 +42,18 @@ namespace WordsToolkit.Scripts.Editor
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckUMPAvailable()
|
||||
{
|
||||
if (( Directory.Exists("Assets/GoogleMobileAds")))
|
||||
{
|
||||
DefineSymbolsUtils.AddSymbol("UMP_AVAILABLE");
|
||||
}
|
||||
else
|
||||
{
|
||||
DefineSymbolsUtils.DeleteSymbol("UMP_AVAILABLE");
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckIronsourceFolder()
|
||||
{
|
||||
var str = "Assets/LevelPlay";
|
||||
|
||||
@ -26,7 +26,6 @@ namespace WordsToolkit.Scripts.Enums
|
||||
LanguageChanged,
|
||||
TileSelected,
|
||||
PurchaseSucceeded,
|
||||
PurchaseFailed,
|
||||
ButtonClicked,
|
||||
WordAnimated,
|
||||
ExtraWordClaimed
|
||||
|
||||
@ -34,6 +34,7 @@ namespace WordsToolkit.Scripts.GUI.Buttons.Boosts
|
||||
|
||||
private CanvasGroup canvasGroup;
|
||||
private bool isActive;
|
||||
private bool isAnimating;
|
||||
|
||||
|
||||
protected override void OnEnable()
|
||||
@ -79,6 +80,12 @@ namespace WordsToolkit.Scripts.GUI.Buttons.Boosts
|
||||
|
||||
protected void OnClick()
|
||||
{
|
||||
// Prevent clicks during animation
|
||||
if (isAnimating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
Refund();
|
||||
@ -105,6 +112,7 @@ namespace WordsToolkit.Scripts.GUI.Buttons.Boosts
|
||||
|
||||
protected virtual void ActivateBoost(bool hideButtons = true)
|
||||
{
|
||||
isAnimating = true;
|
||||
UpdatePriceDisplay();
|
||||
if(hideButtons)
|
||||
buttonViewController.HideOtherButtons(this);
|
||||
@ -117,6 +125,7 @@ namespace WordsToolkit.Scripts.GUI.Buttons.Boosts
|
||||
protected virtual void DeactivateBoost()
|
||||
{
|
||||
isActive = false;
|
||||
isAnimating = false;
|
||||
buttonViewController.ShowButtons();
|
||||
waves.Clear();
|
||||
waves.Stop();
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordsToolkit.Scripts.GUI.Labels
|
||||
{
|
||||
public class IAPDisabler : MonoBehaviour
|
||||
{
|
||||
private void OnEnable()
|
||||
{
|
||||
var gameSettings = Resources.Load<GameSettings>("Settings/GameSettings");
|
||||
if (gameSettings != null && !gameSettings.enableInApps)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9c6b14262f434e07ad593f7b28fbfba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -10,6 +10,7 @@
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using DG.Tweening;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
@ -65,11 +66,7 @@ namespace WordsToolkit.Scripts.GUI
|
||||
|
||||
public void ShowLanguageSelector()
|
||||
{
|
||||
menuManager.CloseAllPopups();
|
||||
menuManager.ShowPopup<LanguageSelectionGame>(null, result =>
|
||||
{
|
||||
UpdateText();
|
||||
});
|
||||
menuManager.ShowPopup<LanguageSelectionGame>(null, result => { UpdateText(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,12 +23,6 @@ namespace WordsToolkit.Scripts.GUI.Tutorials
|
||||
{
|
||||
public class TutorialWordSubstitution : TutorialPopupBase
|
||||
{
|
||||
[Inject]
|
||||
protected LevelManager levelManager;
|
||||
|
||||
[Inject]
|
||||
protected GameManager gameManager;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject hand;
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
private WordSelectionManager wordSelectionManager;
|
||||
private bool isSelected = false;
|
||||
private Color color;
|
||||
private string originalLetter; // Store the original letter for validation
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@ -74,12 +75,13 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
|
||||
public string GetLetter()
|
||||
{
|
||||
return letterText.text;
|
||||
return originalLetter ?? letterText.text;
|
||||
}
|
||||
|
||||
public void SetText(string toString)
|
||||
{
|
||||
letterText.text = toString.ToUpper();
|
||||
originalLetter = toString; // Store the original letter
|
||||
letterText.text = toString.ToUpper(); // Display in uppercase
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
|
||||
@ -523,7 +523,7 @@ namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
if (string.IsNullOrEmpty(word))
|
||||
return false;
|
||||
|
||||
if (!wordValidator.IsWordKnown(word.ToLower(), gameStateManager.CurrentLanguage))
|
||||
if (!wordValidator.IsWordKnown(word, gameStateManager.CurrentLanguage))
|
||||
return false;
|
||||
|
||||
bool wasOpened = false;
|
||||
|
||||
@ -37,7 +37,6 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
private Color[] openColors = new Color[3];
|
||||
|
||||
private bool isSelected = false;
|
||||
private int wordNumber = -1;
|
||||
private bool isOpen = false;
|
||||
|
||||
[Header("Special Item")]
|
||||
@ -321,6 +320,8 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
// Only respond if the tile is selectable and closed
|
||||
if (!isOpen && levelManager != null && levelManager.hammerMode)
|
||||
{
|
||||
// Immediately disable hammer mode to prevent multiple uses
|
||||
levelManager.hammerMode = false;
|
||||
// Instead of opening immediately, play hammer animation first
|
||||
PlayHammerAnimationAndOpen();
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
private float idleTimer = 0f;
|
||||
private Vector2 lastPosition;
|
||||
public Image cursorImage; // Reference to cursor image
|
||||
private bool isCursorVisible = true;
|
||||
private float targetAlpha;
|
||||
private float currentAlpha;
|
||||
|
||||
|
||||
@ -66,7 +66,6 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
private CanvasGroup panelCanvasGroup;
|
||||
|
||||
[Header("UI References")]
|
||||
[SerializeField] private float characterSpacing = 30f; // Spacing between characters
|
||||
public Image backgroundSelectedWord; // Reference to the background image
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI selectedWordText;
|
||||
@ -463,7 +462,7 @@ namespace WordsToolkit.Scripts.Gameplay
|
||||
if (selectedWordText != null)
|
||||
{
|
||||
selectedWordText.color = new Color(selectedWordText.color.r, selectedWordText.color.g, selectedWordText.color.b, 1f);
|
||||
selectedWordText.text = GetSelectedWord();
|
||||
selectedWordText.text = GetSelectedWord().ToUpper();
|
||||
UpdateHorizontalLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,6 @@ namespace WordsToolkit.Scripts.Gameplay.WordValidator
|
||||
if (string.IsNullOrEmpty(word))
|
||||
return false;
|
||||
|
||||
word = word.ToLower();
|
||||
return (modelController != null && modelController.IsWordKnown(word, currentLanguage)) ||
|
||||
(customWordRepository != null && customWordRepository.ContainsWord(word));
|
||||
}
|
||||
|
||||
@ -721,17 +721,27 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
// Handle selection from LevelHierarchyTreeView
|
||||
if (selectedItem != null && selectedItem.type == LevelHierarchyItem.ItemType.Level && selectedItem.levelAsset != null)
|
||||
{
|
||||
// Try to find any available language data in the level
|
||||
string foundLanguage = FindAvailableLanguage(selectedItem.levelAsset);
|
||||
// Use the same language selection logic as the "Open Grid" button
|
||||
// This preserves the user's currently selected language tab
|
||||
string languageCode = LevelEditorUtility.GetLanguageCodeForLevel(selectedItem.levelAsset);
|
||||
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
if (!string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
SetCurrentLevel(selectedItem.levelAsset, foundLanguage);
|
||||
SetCurrentLevel(selectedItem.levelAsset, languageCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the level anyway but with a default language
|
||||
SetCurrentLevel(selectedItem.levelAsset, "en");
|
||||
// Fallback: try to find any available language data in the level
|
||||
string foundLanguage = FindAvailableLanguage(selectedItem.levelAsset);
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
{
|
||||
SetCurrentLevel(selectedItem.levelAsset, foundLanguage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the level anyway but with a default language
|
||||
SetCurrentLevel(selectedItem.levelAsset, "en");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selectedItem == null || selectedItem.type != LevelHierarchyItem.ItemType.Level)
|
||||
@ -767,13 +777,25 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
Level latestLevel = AssetDatabase.LoadAssetAtPath<Level>(path);
|
||||
if (latestLevel != null)
|
||||
{
|
||||
string foundLanguage = FindAvailableLanguage(latestLevel);
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
// Use the same language selection logic as the rest of the system
|
||||
string languageCode = LevelEditorUtility.GetLanguageCodeForLevel(latestLevel);
|
||||
if (!string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
SetCurrentLevel(latestLevel, foundLanguage);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded latest level '{latestLevel.name}' with language '{foundLanguage}' from EditorPrefs");
|
||||
SetCurrentLevel(latestLevel, languageCode);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded latest level '{latestLevel.name}' with language '{languageCode}' from EditorPrefs");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback
|
||||
string foundLanguage = FindAvailableLanguage(latestLevel);
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
{
|
||||
SetCurrentLevel(latestLevel, foundLanguage);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded latest level '{latestLevel.name}' with fallback language '{foundLanguage}' from EditorPrefs");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -784,17 +806,26 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
Level selectedLevel = Selection.activeObject as Level;
|
||||
if (selectedLevel != null)
|
||||
{
|
||||
// Try to find any available language data in the level
|
||||
string foundLanguage = FindAvailableLanguage(selectedLevel);
|
||||
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
// Use the same language selection logic as the rest of the system
|
||||
string languageCode = LevelEditorUtility.GetLanguageCodeForLevel(selectedLevel);
|
||||
if (!string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
SetCurrentLevel(selectedLevel, foundLanguage);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded level '{selectedLevel.name}' with language '{foundLanguage}' from project selection");
|
||||
SetCurrentLevel(selectedLevel, languageCode);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded level '{selectedLevel.name}' with language '{languageCode}' from project selection");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Level '{selectedLevel.name}' has no language data available.");
|
||||
// Fallback
|
||||
string foundLanguage = FindAvailableLanguage(selectedLevel);
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
{
|
||||
SetCurrentLevel(selectedLevel, foundLanguage);
|
||||
Debug.Log($"CrosswordGridWindow: Loaded level '{selectedLevel.name}' with fallback language '{foundLanguage}' from project selection");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Level '{selectedLevel.name}' has no language data available.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -823,23 +854,30 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
private string FindAvailableLanguage(Level level)
|
||||
{
|
||||
if (level == null) return null;
|
||||
|
||||
// Try common language codes in order of preference
|
||||
string[] languageCodes = { "en", "eng", "english", "en-US", "en-GB", "es", "fr", "de", "ru", "zh" };
|
||||
|
||||
foreach (string code in languageCodes)
|
||||
if (level == null || level.languages == null || level.languages.Count == 0)
|
||||
return null;
|
||||
|
||||
// First, try to use the currently selected language tab (same as LevelEditorUtility.GetLanguageCodeForLevel)
|
||||
int selectedTabIndex = EditorPrefs.GetInt("WordsToolkit_SelectedLanguageTab", 0);
|
||||
if (selectedTabIndex >= 0 && selectedTabIndex < level.languages.Count)
|
||||
{
|
||||
var languageData = level.GetLanguageData(code);
|
||||
if (languageData != null)
|
||||
var selectedLanguage = level.languages[selectedTabIndex];
|
||||
if (selectedLanguage != null && !string.IsNullOrEmpty(selectedLanguage.language))
|
||||
{
|
||||
return code;
|
||||
return selectedLanguage.language;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return the first available language
|
||||
for (int i = 0; i < level.languages.Count; i++)
|
||||
{
|
||||
var languageData = level.languages[i];
|
||||
if (languageData != null && !string.IsNullOrEmpty(languageData.language))
|
||||
{
|
||||
return languageData.language;
|
||||
}
|
||||
}
|
||||
|
||||
// If no common languages found, this would require reflection or other means
|
||||
// to get all available languages from the Level object
|
||||
// For now, we'll return null and let the user manually specify
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -851,19 +889,28 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
Level selectedLevel = Selection.activeObject as Level;
|
||||
if (selectedLevel != null)
|
||||
{
|
||||
// Try to find any available language data in the level
|
||||
string foundLanguage = FindAvailableLanguage(selectedLevel);
|
||||
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
// Use the same language selection logic as the rest of the system
|
||||
string languageCode = LevelEditorUtility.GetLanguageCodeForLevel(selectedLevel);
|
||||
if (!string.IsNullOrEmpty(languageCode))
|
||||
{
|
||||
SetCurrentLevel(selectedLevel, foundLanguage);
|
||||
Debug.Log($"Loaded level: {selectedLevel.name} with language: {foundLanguage}");
|
||||
SetCurrentLevel(selectedLevel, languageCode);
|
||||
Debug.Log($"Loaded level: {selectedLevel.name} with language: {languageCode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the level anyway but with a default language
|
||||
SetCurrentLevel(selectedLevel, "en");
|
||||
Debug.LogWarning($"Level '{selectedLevel.name}' has no language data. Using default language 'en'.");
|
||||
// Fallback: try to find any available language data in the level
|
||||
string foundLanguage = FindAvailableLanguage(selectedLevel);
|
||||
if (!string.IsNullOrEmpty(foundLanguage))
|
||||
{
|
||||
SetCurrentLevel(selectedLevel, foundLanguage);
|
||||
Debug.Log($"Loaded level: {selectedLevel.name} with fallback language: {foundLanguage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the level anyway but with a default language
|
||||
SetCurrentLevel(selectedLevel, "en");
|
||||
Debug.LogWarning($"Level '{selectedLevel.name}' has no language data. Using default language 'en'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1023,148 +1070,142 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
if (_currentLevel == null) return;
|
||||
|
||||
var serializedObject = new SerializedObject(_currentLevel);
|
||||
|
||||
|
||||
// Bind letters field (TextField with label)
|
||||
var lettersField = paletteRoot.Q<TextField>("letters-field");
|
||||
if (lettersField != null)
|
||||
var initialValue = languageData.letters ?? "";
|
||||
lettersField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var initialValue = languageData.letters ?? "";
|
||||
lettersField.RegisterValueChangedCallback(evt =>
|
||||
// Don't trigger on initial setup (when previous value is empty and new value matches initial data)
|
||||
if (evt.newValue != evt.previousValue &&
|
||||
!(string.IsNullOrEmpty(evt.previousValue) && evt.newValue == initialValue))
|
||||
{
|
||||
// Don't trigger on initial setup (when previous value is empty and new value matches initial data)
|
||||
if (evt.newValue != evt.previousValue &&
|
||||
!(string.IsNullOrEmpty(evt.previousValue) && evt.newValue == initialValue))
|
||||
// Record undo before changing letters
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
// Record undo before changing letters
|
||||
if (_currentLevel != null)
|
||||
// Ensure grid data is serialized before recording undo
|
||||
var langData = _currentLevel.GetLanguageData(_currentLanguageCode);
|
||||
if (langData?.crosswordData != null)
|
||||
{
|
||||
// Ensure grid data is serialized before recording undo
|
||||
var langData = _currentLevel.GetLanguageData(_currentLanguageCode);
|
||||
if (langData?.crosswordData != null)
|
||||
{
|
||||
langData.crosswordData.SerializeGrid();
|
||||
}
|
||||
|
||||
Undo.RecordObject(_currentLevel, "Change Letters");
|
||||
}
|
||||
|
||||
languageData.letters = evt.newValue;
|
||||
var lettersLength = paletteRoot.Q<IntegerField>("letters-length");
|
||||
if (lettersLength != null)
|
||||
{
|
||||
lettersLength.value = evt.newValue?.Length ?? 0;
|
||||
langData.crosswordData.SerializeGrid();
|
||||
}
|
||||
|
||||
_currentLevel.letters = evt.newValue?.Length ?? 0;
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
|
||||
// Generate new words for the updated letters
|
||||
if (!string.IsNullOrEmpty(evt.newValue))
|
||||
{
|
||||
LevelDataEditor.UpdateAvailableWordsForLevel(_currentLevel);
|
||||
RefreshPreviewData();
|
||||
}
|
||||
|
||||
UpdateGridDisplay();
|
||||
Undo.RecordObject(_currentLevel, "Change Letters");
|
||||
}
|
||||
});
|
||||
lettersField.value = initialValue;
|
||||
}
|
||||
|
||||
languageData.letters = evt.newValue;
|
||||
var lettersLength = paletteRoot.Q<IntegerField>("letters-length");
|
||||
if (lettersLength != null)
|
||||
{
|
||||
lettersLength.value = evt.newValue?.Length ?? 0;
|
||||
}
|
||||
|
||||
_currentLevel.letters = evt.newValue?.Length ?? 0;
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
|
||||
// Generate new words for the updated letters
|
||||
if (!string.IsNullOrEmpty(evt.newValue))
|
||||
{
|
||||
LevelDataEditor.UpdateAvailableWordsForLevel(_currentLevel);
|
||||
RefreshPreviewData();
|
||||
}
|
||||
|
||||
UpdateGridDisplay();
|
||||
}
|
||||
});
|
||||
lettersField.value = initialValue;
|
||||
|
||||
// Bind letters length field
|
||||
var lettersLength = paletteRoot.Q<TextField>("letters-length");
|
||||
if (lettersLength != null)
|
||||
lettersLength.value = _currentLevel.letters.ToString();
|
||||
lettersLength.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
lettersLength.value = _currentLevel.letters.ToString();
|
||||
lettersLength.RegisterValueChangedCallback(evt =>
|
||||
if (int.TryParse(evt.newValue, out int value))
|
||||
{
|
||||
if (int.TryParse(evt.newValue, out int value))
|
||||
{
|
||||
_currentLevel.letters = value;
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_LettersAmount", value);
|
||||
}
|
||||
});
|
||||
}
|
||||
_currentLevel.letters = value;
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_LettersAmount", value);
|
||||
}
|
||||
});
|
||||
|
||||
var generateLettersCheck = paletteRoot.Q<Toggle>("generate-letters-check");
|
||||
generateLettersCheck.value = EditorPrefs.GetBool("WordsToolkit_GenerateLetters", true);
|
||||
generateLettersCheck.RegisterValueChangedCallback(evt => {
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetBool("WordsToolkit_GenerateLetters", evt.newValue);
|
||||
}
|
||||
});
|
||||
// Bind generate button
|
||||
var generateButton = paletteRoot.Q<Button>("generate-button");
|
||||
if (generateButton != null)
|
||||
{
|
||||
generateButton.clicked += () => {
|
||||
if (_currentLevel != null && languageData != null)
|
||||
generateButton.clicked += () => {
|
||||
if (_currentLevel != null && languageData != null)
|
||||
{
|
||||
// Record undo before generating new letters
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
// Record undo before generating new letters
|
||||
if (_currentLevel != null)
|
||||
// Ensure grid data is serialized before recording undo
|
||||
var langData = _currentLevel.GetLanguageData(_currentLanguageCode);
|
||||
if (langData?.crosswordData != null)
|
||||
{
|
||||
// Ensure grid data is serialized before recording undo
|
||||
var langData = _currentLevel.GetLanguageData(_currentLanguageCode);
|
||||
if (langData?.crosswordData != null)
|
||||
{
|
||||
langData.crosswordData.SerializeGrid();
|
||||
}
|
||||
|
||||
Undo.RecordObject(_currentLevel, "Generate Random Letters");
|
||||
langData.crosswordData.SerializeGrid();
|
||||
}
|
||||
|
||||
var modelController = EditorScope.Resolve<IModelController>();
|
||||
string letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, _currentLevel.letters);
|
||||
languageData.letters = letters;
|
||||
|
||||
// Update the letters field in the UI
|
||||
if (lettersField != null) lettersField.value = languageData.letters;
|
||||
if (lettersLength != null) lettersLength.value = (languageData.letters?.Length ?? 0).ToString();
|
||||
|
||||
languageData.words = new string[0];
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
LevelEditorServices.GenerateAvailableWords(_currentLevel, modelController, languageData);
|
||||
UpdateCrossword(_currentLevel, languageData);
|
||||
LevelDataEditor.NotifyWordsListNeedsUpdate(_currentLevel, languageData.language);
|
||||
|
||||
RefreshPreviewData();
|
||||
UpdateLetterPalette();
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
|
||||
|
||||
Undo.RecordObject(_currentLevel, "Generate Random Letters");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var modelController = EditorScope.Resolve<IModelController>();
|
||||
string letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, _currentLevel.letters, generateLettersCheck.value );
|
||||
languageData.letters = letters;
|
||||
|
||||
// Update the letters field in the UI
|
||||
if (lettersField != null) lettersField.value = languageData.letters;
|
||||
if (lettersLength != null) lettersLength.value = (languageData.letters?.Length ?? 0).ToString();
|
||||
|
||||
languageData.words = new string[0];
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
LevelEditorServices.GenerateAvailableWords(_currentLevel, modelController, languageData);
|
||||
UpdateCrossword(_currentLevel, languageData);
|
||||
LevelDataEditor.NotifyWordsListNeedsUpdate(_currentLevel, languageData.language);
|
||||
|
||||
RefreshPreviewData();
|
||||
UpdateLetterPalette();
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Bind grid size controls - columns field
|
||||
var columnsField = paletteRoot.Q<TextField>("columns-field");
|
||||
if (columnsField != null)
|
||||
{
|
||||
columnsField.value = (_previewData?.columns ?? 10).ToString();
|
||||
columnsField.RegisterValueChangedCallback(evt => {
|
||||
if (_previewData != null && int.TryParse(evt.newValue, out int value) && value >= 5 && value <= 50)
|
||||
{
|
||||
_previewData.columns = value;
|
||||
CrosswordPreviewHandler.SavePreviewToLevel(_currentLevel, _currentLanguageCode, _previewData);
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_grid_x", value);
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
}
|
||||
});
|
||||
}
|
||||
columnsField.value = (_previewData?.columns ?? 10).ToString();
|
||||
columnsField.RegisterValueChangedCallback(evt => {
|
||||
if (_previewData != null && int.TryParse(evt.newValue, out int value) && value >= 5 && value <= 50)
|
||||
{
|
||||
_previewData.columns = value;
|
||||
CrosswordPreviewHandler.SavePreviewToLevel(_currentLevel, _currentLanguageCode, _previewData);
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_grid_x", value);
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind grid size controls - rows field
|
||||
var rowsField = paletteRoot.Q<TextField>("rows-field");
|
||||
if (rowsField != null)
|
||||
{
|
||||
rowsField.value = (_previewData?.rows ?? 7).ToString();
|
||||
rowsField.RegisterValueChangedCallback(evt => {
|
||||
if (_previewData != null && int.TryParse(evt.newValue, out int value) && value >= 5 && value <= 50)
|
||||
{
|
||||
_previewData.rows = value;
|
||||
CrosswordPreviewHandler.SavePreviewToLevel(_currentLevel, _currentLanguageCode, _previewData);
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_grid_y", value);
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
}
|
||||
});
|
||||
}
|
||||
rowsField.value = (_previewData?.rows ?? 7).ToString();
|
||||
rowsField.RegisterValueChangedCallback(evt => {
|
||||
if (_previewData != null && int.TryParse(evt.newValue, out int value) && value >= 5 && value <= 50)
|
||||
{
|
||||
_previewData.rows = value;
|
||||
CrosswordPreviewHandler.SavePreviewToLevel(_currentLevel, _currentLanguageCode, _previewData);
|
||||
EditorUtility.SetDirty(_currentLevel);
|
||||
EditorPrefs.SetInt("WordsToolkit_grid_y", value);
|
||||
UpdateGridDisplay();
|
||||
UpdateStatusBar();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind Test Level button
|
||||
var testLevelButton = paletteRoot.Q<Button>("test-level-button");
|
||||
@ -1319,8 +1360,11 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
if (_enableEditing && _currentLevel != null)
|
||||
{
|
||||
var buttonsRow = CreateLetterButtonsRow();
|
||||
buttonsRow.style.alignSelf = Align.FlexEnd;
|
||||
letterButtonsContainer.Add(buttonsRow);
|
||||
if (buttonsRow != null)
|
||||
{
|
||||
buttonsRow.style.alignSelf = Align.FlexEnd;
|
||||
letterButtonsContainer.Add(buttonsRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -616,12 +616,16 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
if (candidate != null && candidate.isValid)
|
||||
{
|
||||
// Check for problematic overlaps (both position overlaps and missing words)
|
||||
bool hasProblematicOverlaps = LevelEditorServices.CheckForOverlappingWords(candidate.placements, wordsToUse);
|
||||
|
||||
// Create a string representation of the grid for uniqueness checking
|
||||
string gridSignature = GridToString(candidate.grid);
|
||||
|
||||
// Check if this grid layout is unique and has a good word count
|
||||
// Check if this grid layout is unique, has a good word count, and no problematic overlaps
|
||||
if (!generatedGrids.Contains(gridSignature) &&
|
||||
candidate.placements.Count > bestWordCount)
|
||||
candidate.placements.Count > bestWordCount &&
|
||||
!hasProblematicOverlaps)
|
||||
{
|
||||
bestVariant = candidate;
|
||||
bestWordCount = candidate.placements.Count;
|
||||
|
||||
@ -1037,6 +1037,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
// Word text field
|
||||
var wordField = new TextField();
|
||||
wordField.name = "word-field";
|
||||
wordField.isReadOnly = true;
|
||||
wordField.style.flexGrow = 1;
|
||||
wordField.style.flexShrink = 1;
|
||||
wordField.style.minWidth = 100;
|
||||
@ -1677,7 +1678,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
if (string.IsNullOrEmpty(letters))
|
||||
{
|
||||
// Generate random letters based on language if needed
|
||||
letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, lettersAmount);
|
||||
letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, lettersAmount, false);
|
||||
lettersProp.stringValue = letters;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
@ -1778,7 +1779,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
private void GenerateWordsForLanguage(Level level, LanguageData languageData, IModelController Controller)
|
||||
{
|
||||
string letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, level.letters);
|
||||
string letters = LevelEditorServices.GenerateRandomLetters(languageData, languageData.wordsAmount, level.letters, false);
|
||||
languageData.letters = letters;
|
||||
LevelEditorServices.GenerateWordsForLanguage(level, languageData, Controller, false);
|
||||
UpdateCrossword(level, languageData);
|
||||
@ -1970,7 +1971,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder("Assets/WordConnectGameToolkit/Resources"))
|
||||
{
|
||||
AssetDatabase.CreateFolder("Assets/WordsToolkit", "Resources");
|
||||
AssetDatabase.CreateFolder("Assets/WordConnectGameToolkit", "Resources");
|
||||
}
|
||||
AssetDatabase.CreateFolder("Assets/WordConnectGameToolkit/Resources", "Settings");
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
$"Processing language: {languageData.language}",
|
||||
(float)processedLanguages / totalLanguages);
|
||||
|
||||
string letters = GenerateRandomLetters(languageData, languageData.wordsAmount, level.letters);
|
||||
string letters = GenerateRandomLetters(languageData, languageData.wordsAmount, level.letters, false);
|
||||
if (!string.IsNullOrEmpty(letters))
|
||||
{
|
||||
languageData.letters = letters;
|
||||
@ -165,10 +165,10 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
if (selectedWords.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"No valid words with {minLettersInWord} to {maxLettersInWord} letters could be generated from '{languageData.letters}' for language {languageData.language}");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"Generated words for language {languageData.language} in level {level.number}");
|
||||
// Shuffle the final list
|
||||
var words = selectedWords.OrderBy(x => UnityEngine.Random.value).ToList();
|
||||
|
||||
@ -192,7 +192,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
return model.GetWordsFromSymbols(languageData.letters, languageData.language);
|
||||
}
|
||||
|
||||
public static string GenerateRandomLetters(LanguageData languageData, int count, int lettersAmount)
|
||||
public static string GenerateRandomLetters(LanguageData languageData, int count, int lettersAmount, bool generateLetters)
|
||||
{
|
||||
var controller = EditorScope.Resolve<IModelController>();
|
||||
var bannedWordsService = EditorScope.Resolve<IBannedWordsService>();
|
||||
@ -204,6 +204,19 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
return "";
|
||||
}
|
||||
|
||||
// 30% chance to generate actual random letters instead of finding optimal words
|
||||
if (generateLetters)
|
||||
{
|
||||
string randomLetters = GenerateActualRandomLetters(languageData.language, lettersAmount);
|
||||
|
||||
// Check if these random letters can generate any words
|
||||
var wordsFromRandomLetters = controller.GetWordsFromSymbols(randomLetters, languageData.language);
|
||||
if (wordsFromRandomLetters != null && wordsFromRandomLetters.Count() > 0)
|
||||
{
|
||||
return randomLetters;
|
||||
}
|
||||
}
|
||||
|
||||
var usedWords = LevelEditorServices.GetUsedWords(languageData.language);
|
||||
|
||||
// Try getting words with specified length
|
||||
@ -212,28 +225,38 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
// If no words found with the specified length, try with other lengths
|
||||
if (words.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"No words with length {lettersAmount} found for language {languageData.language}. Trying other lengths...");
|
||||
// Try with smaller lengths first, then larger
|
||||
for (int offset = 1; offset <= 3; offset++)
|
||||
// Check if model is loaded for this language
|
||||
if (!controller.IsModelLoaded(languageData.language))
|
||||
{
|
||||
// Try smaller length
|
||||
if (lettersAmount - offset > 2)
|
||||
controller.LoadModels();
|
||||
|
||||
// Try again after reloading
|
||||
words = controller.GetWordsWithLength(lettersAmount, languageData.language).Where(w => !usedWords.Contains(w)).ToList();
|
||||
}
|
||||
|
||||
// If still no words found after reloading, try with other lengths
|
||||
if (words.Count == 0)
|
||||
{
|
||||
// Try with smaller lengths first, then larger
|
||||
for (int offset = 1; offset <= 3; offset++)
|
||||
{
|
||||
words = controller.GetWordsWithLength(lettersAmount - offset, languageData.language);
|
||||
// Try smaller length
|
||||
if (lettersAmount - offset > 2)
|
||||
{
|
||||
words = controller.GetWordsWithLength(lettersAmount - offset, languageData.language);
|
||||
if (words.Count > 0) break;
|
||||
}
|
||||
|
||||
// Try larger length
|
||||
words = controller.GetWordsWithLength(lettersAmount + offset, languageData.language);
|
||||
if (words.Count > 0) break;
|
||||
}
|
||||
|
||||
// Try larger length
|
||||
words = controller.GetWordsWithLength(lettersAmount + offset, languageData.language);
|
||||
if (words.Count > 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// If still no words found, use some default letters
|
||||
if (words.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"No suitable words found for language {languageData.language}. Using default letters.");
|
||||
|
||||
// Default letter sets by language
|
||||
Dictionary<string, string> defaultLetters = new Dictionary<string, string>
|
||||
{
|
||||
@ -248,7 +271,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
}
|
||||
else
|
||||
{
|
||||
return "".Substring(0, Mathf.Min(lettersAmount, 26));
|
||||
return new string('a', lettersAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +293,6 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
if (wordsCount >= count)
|
||||
{
|
||||
Debug.Log($"Found optimal word '{words[i]}' that can generate {wordsCount} words (excluding banned words)");
|
||||
return words[i];
|
||||
}
|
||||
|
||||
@ -281,10 +303,73 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"Using best word '{bestWord}' which can generate {maxWordsGenerated} words (excluding banned words)");
|
||||
return bestWord; // Return the word that generated the most derived words
|
||||
}
|
||||
|
||||
private static string GenerateActualRandomLetters(string language, int length)
|
||||
{
|
||||
// Define Latin languages that use vowel/consonant distinction
|
||||
HashSet<string> latinLanguages = new HashSet<string> { "en", "es", "fr", "de", "it", "pt", "nl", "da", "sv", "no" };
|
||||
|
||||
if (latinLanguages.Contains(language))
|
||||
{
|
||||
// For Latin languages, use vowel/consonant distribution
|
||||
string vowels = "aeiou";
|
||||
string consonants = "bcdfghjklmnpqrstvwxyz";
|
||||
|
||||
var result = new List<char>();
|
||||
int vowelCount = Mathf.Max(1, length / 2); // About 1/2 vowels
|
||||
int consonantCount = length - vowelCount;
|
||||
|
||||
// Add vowels
|
||||
for (int i = 0; i < vowelCount; i++)
|
||||
{
|
||||
result.Add(vowels[UnityEngine.Random.Range(0, vowels.Length)]);
|
||||
}
|
||||
|
||||
// Add consonants
|
||||
for (int i = 0; i < consonantCount; i++)
|
||||
{
|
||||
result.Add(consonants[UnityEngine.Random.Range(0, consonants.Length)]);
|
||||
}
|
||||
|
||||
// Shuffle the letters
|
||||
var chars = result.ToArray();
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
int randomIndex = UnityEngine.Random.Range(i, chars.Length);
|
||||
(chars[i], chars[randomIndex]) = (chars[randomIndex], chars[i]);
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-Latin languages, use default letters without vowel distinction
|
||||
Dictionary<string, string> defaultLetters = new Dictionary<string, string>
|
||||
{
|
||||
{ "ru", "оеаинтсрвлкмдпуяыьгзбчйхжшюцщэфъ" },
|
||||
{ "zh", "一二三四五六七八九十百千万亿" },
|
||||
{ "ja", "あいうえおかきくけこさしすせそたちつてとなにぬねの" },
|
||||
{ "ar", "ابتثجحخدذرزسشصضطظعغفقكلمنهوي" }
|
||||
};
|
||||
|
||||
string letters;
|
||||
if (!defaultLetters.TryGetValue(language, out letters))
|
||||
{
|
||||
letters = "abcdefghijklmnopqrstuvwxyz"; // Fallback to Latin alphabet
|
||||
}
|
||||
|
||||
var result = new List<char>();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result.Add(letters[UnityEngine.Random.Range(0, letters.Length)]);
|
||||
}
|
||||
|
||||
return new string(result.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> GetUsedWords(string languageDataLanguage)
|
||||
{
|
||||
// Get all levels in the project
|
||||
@ -503,5 +588,77 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check for problematic overlapping words
|
||||
public static bool CheckForOverlappingWords(List<WordPlacement> placements, string[] originalWords = null)
|
||||
{
|
||||
// Check for words starting at the same position
|
||||
var sameStartGroups = placements
|
||||
.GroupBy(p => p.startPosition)
|
||||
.Where(g => g.Count() > 1)
|
||||
.ToList();
|
||||
|
||||
bool hasPositionOverlaps = sameStartGroups.Count > 0;
|
||||
|
||||
// Check for same-orientation overlaps (words that overlap along their length)
|
||||
bool hasSameOrientationOverlaps = false;
|
||||
for (int i = 0; i < placements.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < placements.Count; j++)
|
||||
{
|
||||
var word1 = placements[i];
|
||||
var word2 = placements[j];
|
||||
|
||||
// Only check words with the same orientation
|
||||
if (word1.isHorizontal == word2.isHorizontal)
|
||||
{
|
||||
if (word1.isHorizontal)
|
||||
{
|
||||
// Both horizontal - check if they're on the same row and overlap
|
||||
if (word1.startPosition.y == word2.startPosition.y)
|
||||
{
|
||||
int word1End = word1.startPosition.x + word1.word.Length - 1;
|
||||
int word2End = word2.startPosition.x + word2.word.Length - 1;
|
||||
|
||||
// Check for overlap
|
||||
bool overlaps = !(word1End < word2.startPosition.x || word2End < word1.startPosition.x);
|
||||
if (overlaps)
|
||||
{
|
||||
hasSameOrientationOverlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Both vertical - check if they're on the same column and overlap
|
||||
if (word1.startPosition.x == word2.startPosition.x)
|
||||
{
|
||||
int word1End = word1.startPosition.y + word1.word.Length - 1;
|
||||
int word2End = word2.startPosition.y + word2.word.Length - 1;
|
||||
|
||||
// Check for overlap
|
||||
bool overlaps = !(word1End < word2.startPosition.y || word2End < word1.startPosition.y);
|
||||
if (overlaps)
|
||||
{
|
||||
hasSameOrientationOverlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasSameOrientationOverlaps) break;
|
||||
}
|
||||
|
||||
// Check if some words are missing from placements (indicating overlaps prevented placement)
|
||||
bool hasMissingWords = false;
|
||||
if (originalWords != null)
|
||||
{
|
||||
hasMissingWords = originalWords.Length > placements.Count;
|
||||
}
|
||||
|
||||
return hasPositionOverlaps || hasSameOrientationOverlaps || hasMissingWords;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,7 @@
|
||||
"references": [
|
||||
"GUID:343deaaf83e0cee4ca978e7df0b80d21",
|
||||
"GUID:d3bf71b33c0c04eb9bc1a8d6513d76bb",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:00dd4a7ac8c24c898083910c81898ecc",
|
||||
"GUID:ac6e78962cfc743b9a5fc5f5a808aa72",
|
||||
"GUID:b25ad8286798741e3b2cc3883283e669",
|
||||
"GUID:75bdbcf23199f4cfb86c610d1d946666"
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Sentis;
|
||||
using Unity.InferenceEngine;
|
||||
using UnityEngine;
|
||||
using ModelAsset = Unity.InferenceEngine.ModelAsset;
|
||||
|
||||
namespace WordsToolkit.Scripts.Levels
|
||||
{
|
||||
|
||||
@ -158,7 +158,17 @@ namespace WordsToolkit.Scripts.Localization
|
||||
|
||||
if (Application.isEditor)
|
||||
{
|
||||
return _debugSettings.TestLanguage;
|
||||
// Use TestLanguageCode from DebugSettings and convert to SystemLanguage
|
||||
try
|
||||
{
|
||||
var cultureInfo = new CultureInfo(_debugSettings.TestLanguageCode);
|
||||
return (SystemLanguage)Enum.Parse(typeof(SystemLanguage), cultureInfo.EnglishName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.LogWarning($"Failed to parse TestLanguageCode '{_debugSettings.TestLanguageCode}'. Using English as fallback.");
|
||||
return SystemLanguage.English;
|
||||
}
|
||||
}
|
||||
|
||||
return Application.systemLanguage;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@ -64,14 +65,24 @@ namespace WordsToolkit.Scripts.Localization
|
||||
SystemLanguage lang;
|
||||
if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.LinuxEditor)
|
||||
{
|
||||
lang = _debugSettings.TestLanguage;
|
||||
// Use TestLanguageCode from DebugSettings and convert to SystemLanguage
|
||||
try
|
||||
{
|
||||
var cultureInfo = new CultureInfo(_debugSettings.TestLanguageCode);
|
||||
lang = (SystemLanguage)Enum.Parse(typeof(SystemLanguage), cultureInfo.EnglishName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.LogWarning($"Failed to parse TestLanguageCode '{_debugSettings.TestLanguageCode}'. Using English as fallback.");
|
||||
lang = SystemLanguage.English;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lang = Application.systemLanguage;
|
||||
}
|
||||
|
||||
return this.Any(i => i.language == lang) ? _debugSettings.TestLanguage : SystemLanguage.English;
|
||||
return this.Any(i => i.language == lang) ? lang : SystemLanguage.English;
|
||||
}
|
||||
|
||||
public IEnumerable<LanguageObject> GetText(DebugSettings _debugSettings)
|
||||
|
||||
@ -3,9 +3,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Unity.Sentis;
|
||||
using Unity.InferenceEngine;
|
||||
using UnityEngine;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.Services;
|
||||
using WordsToolkit.Scripts.Services.BannedWords;
|
||||
@ -36,6 +37,32 @@ namespace WordsToolkit.Scripts.NLP
|
||||
// NOTE: This is now mainly for the old SaveModelBinary method - new architecture uses custom words files
|
||||
private bool protectBinaryFile = false;
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes text by removing diacritics, accents, and converting to lowercase.
|
||||
/// This allows word matching to ignore emphasis marks.
|
||||
/// </summary>
|
||||
private string NormalizeText(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return text;
|
||||
|
||||
text = text.ToLower();
|
||||
|
||||
var normalizedString = text.Normalize(NormalizationForm.FormD);
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
foreach (var c in normalizedString)
|
||||
{
|
||||
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
|
||||
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
|
||||
{
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||
}
|
||||
|
||||
public bool IsModelLoaded(string language = null)
|
||||
{
|
||||
language = language ?? (languageService?.GetCurrentLanguageCode() ?? m_DefaultLanguage);
|
||||
@ -82,6 +109,7 @@ namespace WordsToolkit.Scripts.NLP
|
||||
|
||||
public void LoadModels()
|
||||
{
|
||||
InitializeFromConfiguration();
|
||||
foreach (var languagePair in languageModels)
|
||||
{
|
||||
LoadModelBin(languagePair.Key, languagePair.Value);
|
||||
@ -191,29 +219,62 @@ namespace WordsToolkit.Scripts.NLP
|
||||
LoadCustomWordsFromBinary(language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads bytes from StreamingAssets using UnityWebRequest for Android compatibility
|
||||
/// </summary>
|
||||
private byte[] LoadStreamingAssetBytes(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
using var request = UnityEngine.Networking.UnityWebRequest.Get(path);
|
||||
var operation = request.SendWebRequest();
|
||||
while (!operation.isDone) { }
|
||||
|
||||
if (request.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
|
||||
{
|
||||
return request.downloadHandler.data;
|
||||
}
|
||||
return null;
|
||||
#else
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[ModelController] Exception in LoadStreamingAssetBytes: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads custom words from binary file and adds them to the existing vocabulary.
|
||||
/// Binary file contains ONLY custom words, not the entire model cache.
|
||||
/// </summary>
|
||||
private void LoadCustomWordsFromBinary(string language)
|
||||
{
|
||||
string path = Path.Combine(Application.dataPath, "WordsToolkit", "model",
|
||||
string path = Path.Combine(Application.streamingAssetsPath, "WordConnectGameToolkit", "model",
|
||||
"custom", $"{language}_custom_words.bin");
|
||||
|
||||
if (!File.Exists(path))
|
||||
if (!wordToIndexByLanguage.ContainsKey(language))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wordToIndexByLanguage.ContainsKey(language))
|
||||
byte[] fileData = LoadStreamingAssetBytes(path);
|
||||
if (fileData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.UTF8);
|
||||
using var ms = new MemoryStream(fileData);
|
||||
using var br = new BinaryReader(ms, Encoding.UTF8);
|
||||
|
||||
// Read header
|
||||
if (br.ReadInt32() != 0x43555354) // "CUST" magic number
|
||||
@ -265,9 +326,11 @@ namespace WordsToolkit.Scripts.NLP
|
||||
return;
|
||||
}
|
||||
|
||||
string dir = Path.Combine(Application.dataPath, "WordsToolkit", "model", "custom");
|
||||
string path = Path.Combine(dir, $"{language}_custom_words.bin");
|
||||
Directory.CreateDirectory(dir);
|
||||
// Create StreamingAssets folder structure
|
||||
string streamingAssetsDir = Path.Combine(Application.dataPath, "StreamingAssets");
|
||||
string modelDir = Path.Combine(streamingAssetsDir, "WordConnectGameToolkit", "model", "custom");
|
||||
string path = Path.Combine(modelDir, $"{language}_custom_words.bin");
|
||||
Directory.CreateDirectory(modelDir);
|
||||
|
||||
try
|
||||
{
|
||||
@ -353,7 +416,8 @@ namespace WordsToolkit.Scripts.NLP
|
||||
|
||||
foreach (var pair in wordIndexDict)
|
||||
{
|
||||
wordToIndex[pair.Key] = pair.Value;
|
||||
string normalizedWord = NormalizeText(pair.Key);
|
||||
wordToIndex[normalizedWord] = pair.Value;
|
||||
}
|
||||
|
||||
|
||||
@ -379,6 +443,7 @@ namespace WordsToolkit.Scripts.NLP
|
||||
return null;
|
||||
}
|
||||
|
||||
word = NormalizeText(word);
|
||||
if (!wordToIndexByLanguage[language].ContainsKey(word))
|
||||
{
|
||||
return null;
|
||||
@ -413,7 +478,8 @@ namespace WordsToolkit.Scripts.NLP
|
||||
public bool IsWordKnown(string word, string language = null)
|
||||
{
|
||||
language = language ?? (languageService?.GetCurrentLanguageCode() ?? m_DefaultLanguage);
|
||||
if (bannedWordsService.IsWordBanned(word, language))
|
||||
string normalizedWord = NormalizeText(word);
|
||||
if (bannedWordsService.IsWordBanned(normalizedWord, language))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -451,6 +517,8 @@ namespace WordsToolkit.Scripts.NLP
|
||||
return -1f;
|
||||
}
|
||||
|
||||
word1 = NormalizeText(word1);
|
||||
word2 = NormalizeText(word2);
|
||||
float[] vector1 = GetWordVector(word1, language);
|
||||
float[] vector2 = GetWordVector(word2, language);
|
||||
|
||||
@ -466,10 +534,11 @@ namespace WordsToolkit.Scripts.NLP
|
||||
|
||||
if (!IsModelLoaded(language))
|
||||
{
|
||||
Debug.LogWarning($"[ModelController] AddWord failed – model for '{language}' not loaded.");
|
||||
Debug.LogWarning($"[ModelController] AddWord failed – model for '{language}' not loaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
newWord = NormalizeText(newWord);
|
||||
if (wordToIndexByLanguage[language].ContainsKey(newWord))
|
||||
{
|
||||
Debug.LogWarning($"[ModelController] Word '{newWord}' already exists in vocab.");
|
||||
@ -523,8 +592,8 @@ namespace WordsToolkit.Scripts.NLP
|
||||
Buffer.BlockCopy(oldBuf, 0, newBuf, 0, oldElems * sizeof(float));
|
||||
Buffer.BlockCopy(newVector,0, newBuf, oldElems * sizeof(float), dim * sizeof(float));
|
||||
|
||||
// Sentis requires a non‑generic NativeTensorArrayFromManagedArray
|
||||
// Sentis requires (Array, bytesPerElem, length, channels)
|
||||
// Inference Engine requires a non‑generic NativeTensorArrayFromManagedArray
|
||||
// Inference Engine requires (Array, bytesPerElem, length, channels)
|
||||
// ctor args: (Array data, int srcElementOffset, int srcElementSize, int numDestElement)
|
||||
var newWeights = new NativeTensorArrayFromManagedArray(
|
||||
newBuf, // managed float[]
|
||||
@ -638,7 +707,7 @@ namespace WordsToolkit.Scripts.NLP
|
||||
if (string.IsNullOrEmpty(inputSymbols))
|
||||
return new List<string>();
|
||||
|
||||
inputSymbols = inputSymbols.ToLower();
|
||||
inputSymbols = NormalizeText(inputSymbols);
|
||||
Dictionary<char, int> charCounts = new Dictionary<char, int>();
|
||||
foreach (char c in inputSymbols)
|
||||
{
|
||||
@ -702,7 +771,8 @@ namespace WordsToolkit.Scripts.NLP
|
||||
if (string.IsNullOrEmpty(inputSymbols))
|
||||
return null;
|
||||
|
||||
var symbolSet = new HashSet<char>(inputSymbols.ToLower());
|
||||
inputSymbols = NormalizeText(inputSymbols);
|
||||
var symbolSet = new HashSet<char>(inputSymbols);
|
||||
|
||||
var bestMatches = wordToIndexByLanguage[language].Keys
|
||||
.Select(word => new {
|
||||
@ -855,7 +925,7 @@ namespace WordsToolkit.Scripts.NLP
|
||||
/// <param name="language">Language to clear, or null to clear all</param>
|
||||
public void ClearCustomWordsCache(string language = null)
|
||||
{
|
||||
string customDir = Path.Combine(Application.dataPath, "WordsToolkit", "model", "custom");
|
||||
string customDir = Path.Combine(Application.dataPath, "StreamingAssets", "WordConnectGameToolkit", "model", "custom");
|
||||
|
||||
if (!Directory.Exists(customDir))
|
||||
return;
|
||||
|
||||
@ -11,14 +11,9 @@
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using WordsToolkit.Scripts.Audio;
|
||||
using WordsToolkit.Scripts.Data;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.GUI;
|
||||
using WordsToolkit.Scripts.GUI.Labels;
|
||||
using WordsToolkit.Scripts.Services;
|
||||
using WordsToolkit.Scripts.Services.IAP;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.System;
|
||||
@ -31,6 +26,8 @@ namespace WordsToolkit.Scripts.Popups
|
||||
private CoinsShopSettings shopSettings;
|
||||
public ProductID noAdsProduct;
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI noAdsPriceText;
|
||||
[SerializeField]
|
||||
private AudioClip coinsSound;
|
||||
|
||||
private void OnEnable()
|
||||
@ -49,11 +46,14 @@ namespace WordsToolkit.Scripts.Popups
|
||||
}
|
||||
|
||||
EventManager.GetEvent<string>(EGameEvent.PurchaseSucceeded).Subscribe(PurchaseSucceded);
|
||||
EventManager.GetEvent<(string, string)>(EGameEvent.PurchaseFailed).Subscribe(PurchaseFailed);
|
||||
|
||||
// Update NoAds price display
|
||||
UpdateNoAdsPriceDisplay();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
EventManager.GetEvent<string>(EGameEvent.PurchaseSucceeded).Unsubscribe(PurchaseSucceded);
|
||||
}
|
||||
|
||||
@ -86,44 +86,6 @@ namespace WordsToolkit.Scripts.Popups
|
||||
}
|
||||
}
|
||||
|
||||
private void PurchaseFailed((string, string) info)
|
||||
{
|
||||
var productId = info.Item1;
|
||||
var errorMessage = info.Item2;
|
||||
|
||||
var errorType = IAPErrorHelper.ParseError(errorMessage);
|
||||
Debug.LogWarning($"Purchase failed for product {productId}: {errorMessage}, Error Type: {errorType}");
|
||||
|
||||
switch (errorType)
|
||||
{
|
||||
case IAPErrorType.InvalidProductID:
|
||||
Debug.LogError($"Invalid product ID: {productId}");
|
||||
ShowErrorMessage("Invalid product ID. Please try again later.");
|
||||
break;
|
||||
case IAPErrorType.UserCancelled:
|
||||
Debug.LogWarning("Purchase cancelled by user.");
|
||||
break;
|
||||
case IAPErrorType.DuplicateTransaction:
|
||||
Debug.LogWarning($"Duplicate transaction for product {productId}. This usually means the purchase was already completed.");
|
||||
break;
|
||||
case IAPErrorType.IAPInitFailed:
|
||||
Debug.LogError("IAP initialization failed. Please check your IAP settings.");
|
||||
ShowErrorMessage("IAP initialization failed. Please try again later.");
|
||||
break;
|
||||
default:
|
||||
ShowErrorMessage($"Payment failed: {errorMessage}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void ShowErrorMessage(string message)
|
||||
{
|
||||
var popup = menuManager.ShowPopup<MessagePopup>();
|
||||
if (popup != null)
|
||||
{
|
||||
popup.SetMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void BuyCoins(string id)
|
||||
{
|
||||
// StopInteration();
|
||||
@ -133,5 +95,17 @@ namespace WordsToolkit.Scripts.Popups
|
||||
iapManager.BuyProduct(id);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateNoAdsPriceDisplay()
|
||||
{
|
||||
if (noAdsPriceText != null && noAdsProduct != null)
|
||||
{
|
||||
var localizedPrice = iapManager.GetProductLocalizedPriceString(noAdsProduct.ID);
|
||||
if (!string.IsNullOrEmpty(localizedPrice))
|
||||
{
|
||||
noAdsPriceText.text = localizedPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,8 @@
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.GUI;
|
||||
using WordsToolkit.Scripts.GUI.Buttons;
|
||||
@ -23,11 +25,16 @@ namespace WordsToolkit.Scripts.Popups
|
||||
{
|
||||
public CustomButton removeAdsButton;
|
||||
public ProductID productID;
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI priceText; // Add price display UI element
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
removeAdsButton.onClick.AddListener(RemoveAds);
|
||||
EventManager.GetEvent<string>(EGameEvent.PurchaseSucceeded).Subscribe(PurchaseSucceeded);
|
||||
|
||||
// Set localized price when popup opens
|
||||
UpdatePriceDisplay();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
@ -49,5 +56,17 @@ namespace WordsToolkit.Scripts.Popups
|
||||
{
|
||||
iapManager.BuyProduct(productID.ID);
|
||||
}
|
||||
|
||||
private void UpdatePriceDisplay()
|
||||
{
|
||||
if (priceText != null && productID != null)
|
||||
{
|
||||
var localizedPrice = iapManager.GetProductLocalizedPriceString(productID.ID);
|
||||
if (!string.IsNullOrEmpty(localizedPrice))
|
||||
{
|
||||
priceText.text = localizedPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ using WordsToolkit.Scripts.GUI;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using VContainer;
|
||||
using WordsToolkit.Scripts.GUI.Buttons;
|
||||
using WordsToolkit.Scripts.Services;
|
||||
|
||||
namespace WordsToolkit.Scripts.Popups
|
||||
{
|
||||
@ -26,6 +27,9 @@ namespace WordsToolkit.Scripts.Popups
|
||||
[SerializeField]
|
||||
private CustomButton privacypolicy;
|
||||
|
||||
[SerializeField]
|
||||
private CustomButton googleUMPConsent;
|
||||
|
||||
[SerializeField]
|
||||
private Button restorePurchase;
|
||||
|
||||
@ -38,6 +42,7 @@ namespace WordsToolkit.Scripts.Popups
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
privacypolicy?.onClick.AddListener(PrivacyPolicy);
|
||||
googleUMPConsent?.onClick.AddListener(ReconsiderGoogleUMPConsent);
|
||||
LoadVibrationLevel();
|
||||
vibrationSlider.onValueChanged.AddListener(SaveVibrationLevel);
|
||||
closeButton.onClick.RemoveAllListeners();
|
||||
@ -94,6 +99,14 @@ namespace WordsToolkit.Scripts.Popups
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ReconsiderGoogleUMPConsent()
|
||||
{
|
||||
StopInteration();
|
||||
DisablePause();
|
||||
adsManager.ReconsiderUMPConsent();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void DisablePause()
|
||||
{
|
||||
if (stateManager.CurrentState == EScreenStates.Game)
|
||||
|
||||
@ -22,10 +22,11 @@ namespace WordsToolkit.Scripts.Services.Ads.AdUnits
|
||||
public Action<string> OnShown { get; set; }
|
||||
public Action<string> OnInitialized { get; set; }
|
||||
|
||||
public AdUnit(string placementId, AdReference adReference)
|
||||
public AdUnit(string placementId, AdReference adReference, AdsHandlerBase adsHandler)
|
||||
{
|
||||
PlacementId = placementId;
|
||||
AdReference = adReference;
|
||||
AdsHandler = adsHandler;
|
||||
}
|
||||
|
||||
public AdsHandlerBase AdsHandler { get; set; }
|
||||
|
||||
@ -10,8 +10,14 @@
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
#if IRONSOURCE
|
||||
using Unity.Services.LevelPlay;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
#if UMP_AVAILABLE
|
||||
using GoogleMobileAds.Ump.Api;
|
||||
#endif
|
||||
|
||||
namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
{
|
||||
@ -19,14 +25,42 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
public class IronsourceAdsHandler : AdsHandlerBase
|
||||
{
|
||||
private IAdsListener _listener;
|
||||
#if IRONSOURCE
|
||||
private LevelPlayInterstitialAd _interstitialAd;
|
||||
private LevelPlayRewardedAd _rewardedAd;
|
||||
#endif
|
||||
|
||||
private void Init(string _id)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
IronSource.Agent.setManualLoadRewardedVideo(true);
|
||||
IronSource.Agent.validateIntegration();
|
||||
IronSource.Agent.init(_id);
|
||||
LevelPlay.ValidateIntegration();
|
||||
|
||||
// Set consent for LevelPlay
|
||||
SetConsentStatus();
|
||||
|
||||
// Register initialization events
|
||||
LevelPlay.OnInitSuccess += SdkInitializationCompletedEvent;
|
||||
LevelPlay.OnInitFailed += SdkInitializationFailedEvent;
|
||||
|
||||
LevelPlay.Init(_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SetConsentStatus()
|
||||
{
|
||||
#if IRONSOURCE && UMP_AVAILABLE
|
||||
bool hasConsent = ConsentInformation.CanRequestAds();
|
||||
Debug.Log($"LevelPlay consent status: {hasConsent}");
|
||||
|
||||
// Set consent for GDPR
|
||||
LevelPlay.SetConsent(hasConsent);
|
||||
|
||||
// For CCPA compliance (optional)
|
||||
LevelPlay.SetMetaData("do_not_sell", hasConsent ? "false" : "true");
|
||||
#elif IRONSOURCE
|
||||
// Default to no consent if UMP not available
|
||||
LevelPlay.SetConsent(false);
|
||||
Debug.Log("UMP not available - setting LevelPlay consent to false");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -34,78 +68,121 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
{
|
||||
_listener = listener;
|
||||
Debug.Log(_listener);
|
||||
#if IRONSOURCE
|
||||
//Add Rewarded Video Events
|
||||
IronSourceInterstitialEvents.onAdReadyEvent += OnInterstitialAdReady;
|
||||
IronSourceInterstitialEvents.onAdLoadFailedEvent += InterstitialAdLoadFailedEvent;
|
||||
|
||||
IronSourceRewardedVideoEvents.onAdReadyEvent += OnRewardedVideoAdReady;
|
||||
IronSourceRewardedVideoEvents.onAdLoadFailedEvent += RewardedVideoAdShowFailedEvent;
|
||||
IronSourceRewardedVideoEvents.onAdRewardedEvent += Rewardeded;
|
||||
|
||||
IronSourceEvents.onSdkInitializationCompletedEvent += SdkInitializationCompletedEvent;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if IRONSOURCE
|
||||
private void Rewardeded(IronSourcePlacement obj, IronSourceAdInfo ironSourceAdInfo)
|
||||
private void SdkInitializationCompletedEvent(LevelPlayConfiguration config)
|
||||
{
|
||||
Debug.Log("Ironsource Rewardeded");
|
||||
_listener?.OnAdsShowComplete();
|
||||
}
|
||||
|
||||
private void SdkInitializationCompletedEvent()
|
||||
{
|
||||
Debug.Log("Ironsource SdkInitializationCompletedEvent");
|
||||
Debug.Log("LevelPlay SdkInitializationCompletedEvent");
|
||||
_listener?.OnAdsInitialized();
|
||||
}
|
||||
|
||||
private void InterstitialAdLoadFailedEvent(IronSourceError obj)
|
||||
private void SdkInitializationFailedEvent(LevelPlayInitError error)
|
||||
{
|
||||
Debug.Log("Ironsource InterstitialAdLoadFailedEvent " + obj.getCode() + " " + obj.getDescription());
|
||||
Debug.Log($"LevelPlay SdkInitializationFailedEvent: {error}");
|
||||
_listener?.OnInitFailed();
|
||||
}
|
||||
|
||||
// Interstitial event handlers
|
||||
private void InterstitialAdLoadedEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("LevelPlay OnInterstitialAdReady");
|
||||
_listener?.OnAdsLoaded(adInfo.AdUnitId);
|
||||
}
|
||||
|
||||
private void InterstitialAdLoadFailedEvent(LevelPlayAdError error)
|
||||
{
|
||||
Debug.Log($"LevelPlay InterstitialAdLoadFailedEvent: {error}");
|
||||
_listener?.OnAdsLoadFailed();
|
||||
}
|
||||
|
||||
private void RewardedVideoAdShowFailedEvent(IronSourceError obj)
|
||||
private void InterstitialAdDisplayFailedEvent(LevelPlayAdInfo levelPlayAdInfo, LevelPlayAdError levelPlayAdError)
|
||||
{
|
||||
Debug.Log("1" + obj.getCode());
|
||||
Debug.Log("2" + obj.getDescription());
|
||||
Debug.Log("Ironsource RewardedVideoAdShowFailedEvent " + obj.getCode() + " " + obj.getDescription());
|
||||
Debug.Log(_listener);
|
||||
Debug.Log($"LevelPlay InterstitialAdDisplayFailedEvent: {levelPlayAdError}");
|
||||
_listener?.OnAdsShowFailed();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
|
||||
private void OnRewardedVideoAdReady(IronSourceAdInfo obj)
|
||||
#if LEVELPLAY8
|
||||
private void InterstitialAdDisplayFailedEvent(LevelPlayAdDisplayInfoError obj)
|
||||
{
|
||||
Debug.Log("Ironsource OnRewardedVideoAdReady");
|
||||
_listener?.OnAdsLoaded(obj.instanceId);
|
||||
}
|
||||
|
||||
private void OnInterstitialAdReady(IronSourceAdInfo obj)
|
||||
{
|
||||
Debug.Log("Ironsource OnInterstitialAdReady");
|
||||
_listener?.OnAdsLoaded(obj.instanceId);
|
||||
Debug.Log($"LevelPlay InterstitialAdDisplayFailedEvent: {obj}");
|
||||
_listener?.OnAdsShowFailed();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Rewarded video event handlers
|
||||
private void RewardedAdLoadedEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("LevelPlay OnRewardedVideoAdReady");
|
||||
_listener?.OnAdsLoaded(adInfo.AdUnitId);
|
||||
}
|
||||
|
||||
private void RewardedAdLoadFailedEvent(LevelPlayAdError error)
|
||||
{
|
||||
Debug.Log($"LevelPlay RewardedVideoAdLoadFailedEvent: {error}");
|
||||
_listener?.OnAdsLoadFailed();
|
||||
}
|
||||
|
||||
private void RewardedAdDisplayFailedEvent(LevelPlayAdInfo levelPlayAdInfo, LevelPlayAdError levelPlayAdError)
|
||||
{
|
||||
Debug.Log($"LevelPlay RewardedVideoAdShowFailedEvent: {levelPlayAdError}");
|
||||
_listener?.OnAdsShowFailed();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
|
||||
#if LEVELPLAY8
|
||||
private void RewardedAdDisplayFailedEvent(LevelPlayAdDisplayInfoError obj)
|
||||
{
|
||||
Debug.Log($"LevelPlay RewardedAdDisplayFailedEvent: {obj}");
|
||||
_listener?.OnAdsShowFailed();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
private void RewardedAdRewardedEvent(LevelPlayAdInfo adInfo, LevelPlayReward reward)
|
||||
{
|
||||
Debug.Log("LevelPlay Rewarded");
|
||||
_listener?.OnAdsShowComplete();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
private void InterstitialDisplayedEvent(LevelPlayAdInfo obj)
|
||||
{
|
||||
Debug.Log("LevelPlay InterstitialDisplayedEvent");
|
||||
_listener?.OnAdsShowStart();
|
||||
LevelPlay.SetPauseGame(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
public override void Init(string _id, bool adSettingTestMode, IAdsListener listener)
|
||||
{
|
||||
Debug.Log("Ironsource Init");
|
||||
Debug.Log("LevelPlay Init");
|
||||
Init(_id);
|
||||
Debug.Log("Ironsource SetListener");
|
||||
Debug.Log("LevelPlay SetListener");
|
||||
SetListener(listener);
|
||||
}
|
||||
|
||||
public override void Show(AdUnit adUnit)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Interstitial)
|
||||
if (adUnit.AdReference.adType == EAdType.Interstitial && _interstitialAd != null)
|
||||
{
|
||||
IronSource.Agent.showInterstitial();
|
||||
if (_interstitialAd.IsAdReady())
|
||||
{
|
||||
_interstitialAd.ShowAd();
|
||||
LevelPlay.SetPauseGame(true);
|
||||
}
|
||||
}
|
||||
else if (adUnit.AdReference.adType == EAdType.Rewarded)
|
||||
else if (adUnit.AdReference.adType == EAdType.Rewarded && _rewardedAd != null)
|
||||
{
|
||||
IronSource.Agent.showRewardedVideo();
|
||||
if (_rewardedAd.IsAdReady())
|
||||
{
|
||||
_rewardedAd.ShowAd();
|
||||
LevelPlay.SetPauseGame(true);
|
||||
}
|
||||
}
|
||||
|
||||
_listener?.Show(adUnit);
|
||||
@ -117,26 +194,43 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Interstitial)
|
||||
{
|
||||
IronSource.Agent.loadInterstitial();
|
||||
_interstitialAd = new LevelPlayInterstitialAd(adUnit.PlacementId);
|
||||
|
||||
_interstitialAd.OnAdLoaded += InterstitialAdLoadedEvent;
|
||||
_interstitialAd.OnAdDisplayed += InterstitialDisplayedEvent;
|
||||
_interstitialAd.OnAdLoadFailed += InterstitialAdLoadFailedEvent;
|
||||
_interstitialAd.OnAdDisplayFailed += InterstitialAdDisplayFailedEvent;
|
||||
|
||||
_interstitialAd.LoadAd();
|
||||
}
|
||||
else if (adUnit.AdReference.adType == EAdType.Rewarded)
|
||||
{
|
||||
IronSource.Agent.loadRewardedVideo();
|
||||
_rewardedAd = new LevelPlayRewardedAd(adUnit.PlacementId);
|
||||
|
||||
_rewardedAd.OnAdLoaded += RewardedAdLoadedEvent;
|
||||
_rewardedAd.OnAdLoadFailed += RewardedAdLoadFailedEvent;
|
||||
_rewardedAd.OnAdDisplayFailed += RewardedAdDisplayFailedEvent;
|
||||
_rewardedAd.OnAdRewarded += RewardedAdRewardedEvent;
|
||||
|
||||
_rewardedAd.LoadAd();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public override bool IsAvailable(AdUnit adUnit)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Interstitial)
|
||||
{
|
||||
return IronSource.Agent.isInterstitialReady();
|
||||
return _interstitialAd != null && _interstitialAd.IsAdReady();
|
||||
}
|
||||
|
||||
if (adUnit.AdReference.adType == EAdType.Rewarded)
|
||||
{
|
||||
return IronSource.Agent.isRewardedVideoAvailable();
|
||||
return _rewardedAd != null && _rewardedAd.IsAdReady();
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
#if IRONSOURCE
|
||||
using Unity.Services.LevelPlay;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
|
||||
@ -19,65 +22,100 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
public class IronsourceBannerHandler : AdsHandlerBase
|
||||
{
|
||||
private IAdsListener _listener;
|
||||
#if IRONSOURCE
|
||||
private LevelPlayBannerAd _bannerAd;
|
||||
private LevelPlayBannerAd.Config.Builder configBuilder;
|
||||
#endif
|
||||
|
||||
private void Init(string _id)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
IronSource.Agent.validateIntegration();
|
||||
IronSource.Agent.init(_id);
|
||||
LevelPlay.ValidateIntegration();
|
||||
LevelPlay.Init(_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SetListener(IAdsListener listener)
|
||||
{
|
||||
_listener = listener;
|
||||
#if IRONSOURCE
|
||||
IronSourceBannerEvents.onAdLoadedEvent += BannerAdLoadedEvent;
|
||||
IronSourceBannerEvents.onAdLoadFailedEvent += BannerAdLoadFailedEvent;
|
||||
IronSourceBannerEvents.onAdClickedEvent += BannerAdClickedEvent;
|
||||
IronSourceBannerEvents.onAdScreenPresentedEvent += BannerAdScreenPresentedEvent;
|
||||
IronSourceBannerEvents.onAdScreenDismissedEvent += BannerAdScreenDismissedEvent;
|
||||
IronSourceBannerEvents.onAdLeftApplicationEvent += BannerAdLeftApplicationEvent;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if IRONSOURCE
|
||||
private void BannerAdLoadedEvent(IronSourceAdInfo adInfo)
|
||||
private void BannerAdLoadedEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("IronSource Banner ad loaded");
|
||||
_listener?.OnAdsLoaded(adInfo.instanceId);
|
||||
Debug.Log("LevelPlay Banner ad loaded");
|
||||
_listener?.OnAdsLoaded(adInfo.AdUnitId);
|
||||
}
|
||||
|
||||
private void BannerAdLoadFailedEvent(IronSourceError error)
|
||||
private void BannerAdLoadFailedEvent(LevelPlayAdError error)
|
||||
{
|
||||
Debug.Log($"IronSource Banner ad load failed. Error: {error.getCode()} - {error.getDescription()}");
|
||||
Debug.Log($"LevelPlay Banner ad load failed. Error: {error}");
|
||||
_listener?.OnAdsLoadFailed();
|
||||
}
|
||||
|
||||
private void BannerAdClickedEvent(IronSourceAdInfo adInfo)
|
||||
private void BannerAdClickedEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("IronSource Banner ad clicked");
|
||||
Debug.Log("LevelPlay Banner ad clicked");
|
||||
}
|
||||
|
||||
private void BannerAdScreenPresentedEvent(IronSourceAdInfo adInfo)
|
||||
private void BannerAdDisplayedEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("IronSource Banner ad screen presented");
|
||||
Debug.Log("LevelPlay Banner ad displayed");
|
||||
}
|
||||
|
||||
private void BannerAdScreenDismissedEvent(IronSourceAdInfo adInfo)
|
||||
private void BannerAdDisplayFailedEvent(LevelPlayAdInfo levelPlayAdInfo, LevelPlayAdError levelPlayAdError)
|
||||
{
|
||||
Debug.Log("IronSource Banner ad screen dismissed");
|
||||
Debug.Log($"LevelPlay Banner ad display failed. Error: {levelPlayAdError}");
|
||||
}
|
||||
|
||||
private void BannerAdLeftApplicationEvent(IronSourceAdInfo adInfo)
|
||||
#if LEVELPLAY8
|
||||
private void BannerAdDisplayFailedEvent(LevelPlayAdDisplayInfoError error)
|
||||
{
|
||||
Debug.Log("IronSource Banner ad caused app to leave");
|
||||
Debug.Log($"LevelPlay Banner ad display failed. Error: {error}");
|
||||
}
|
||||
#endif
|
||||
|
||||
private void BannerAdLeftApplicationEvent(LevelPlayAdInfo adInfo)
|
||||
{
|
||||
Debug.Log("LevelPlay Banner ad caused app to leave");
|
||||
}
|
||||
|
||||
private LevelPlayBannerAd GetBannerAd(AdUnit adUnit)
|
||||
{
|
||||
configBuilder ??= new LevelPlayBannerAd.Config.Builder();
|
||||
#if LEVELPLAY9
|
||||
configBuilder.SetSize(LevelPlayAdSize.BANNER);
|
||||
#elif LEVELPLAY8
|
||||
configBuilder.SetSize(com.unity3d.mediation.LevelPlayAdSize.BANNER);
|
||||
#endif
|
||||
#if LEVELPLAY9
|
||||
configBuilder.SetPosition(LevelPlayBannerPosition.BottomCenter);
|
||||
#elif LEVELPLAY8
|
||||
configBuilder.SetPosition(com.unity3d.mediation.LevelPlayBannerPosition.BottomCenter);
|
||||
#endif
|
||||
configBuilder.SetDisplayOnLoad(true);
|
||||
#if UNITY_ANDROID
|
||||
configBuilder.SetRespectSafeArea(true); // Only relevant for Android
|
||||
#endif
|
||||
configBuilder.SetPlacementName("bannerPlacement");
|
||||
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD
|
||||
var bannerConfig = configBuilder.Build();
|
||||
var bannerAd = new LevelPlayBannerAd(adUnit.PlacementId, bannerConfig);
|
||||
|
||||
bannerAd.OnAdLoaded += BannerAdLoadedEvent;
|
||||
bannerAd.OnAdLoadFailed += BannerAdLoadFailedEvent;
|
||||
bannerAd.OnAdClicked += BannerAdClickedEvent;
|
||||
bannerAd.OnAdDisplayed += BannerAdDisplayedEvent;
|
||||
bannerAd.OnAdDisplayFailed += BannerAdDisplayFailedEvent;
|
||||
bannerAd.OnAdLeftApplication += BannerAdLeftApplicationEvent;
|
||||
|
||||
return bannerAd;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void Init(string _id, bool adSettingTestMode, IAdsListener listener)
|
||||
{
|
||||
Debug.Log("IronSource Banner Init");
|
||||
Debug.Log("LevelPlay Banner Init");
|
||||
Init(_id);
|
||||
SetListener(listener);
|
||||
}
|
||||
@ -85,22 +123,23 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
public override void Show(AdUnit adUnit)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Banner)
|
||||
if (_bannerAd == null)
|
||||
{
|
||||
IronSource.Agent.displayBanner();
|
||||
_bannerAd = GetBannerAd(adUnit);
|
||||
}
|
||||
if (adUnit.AdReference.adType == EAdType.Banner && _bannerAd != null)
|
||||
{
|
||||
_bannerAd.ShowAd();
|
||||
_listener?.Show(adUnit);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public override void Load(AdUnit adUnit)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Banner)
|
||||
{
|
||||
IronSourceBannerSize bannerSize = IronSourceBannerSize.BANNER;
|
||||
IronSource.Agent.loadBanner(bannerSize, IronSourceBannerPosition.BOTTOM);
|
||||
}
|
||||
_bannerAd.LoadAd();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -114,9 +153,9 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
public override void Hide(AdUnit adUnit)
|
||||
{
|
||||
#if IRONSOURCE
|
||||
if (adUnit.AdReference.adType == EAdType.Banner)
|
||||
if (adUnit.AdReference.adType == EAdType.Banner && _bannerAd != null)
|
||||
{
|
||||
IronSource.Agent.hideBanner();
|
||||
_bannerAd.HideAd();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 655355652c9a644febf26cd1a5869bf4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "CandySmith.LevelPlay",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:00dd4a7ac8c24c898083910c81898ecc",
|
||||
"GUID:760a4c7888534400e882b82c5b3fba06"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.services.levelplay",
|
||||
"expression": "[0.0,9.0)",
|
||||
"define": "LEVELPLAY8"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.services.levelplay",
|
||||
"expression": "9.0.0",
|
||||
"define": "LEVELPLAY9"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.services.levelplay",
|
||||
"expression": "",
|
||||
"define": "IRONSOURCE"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 537e7a60660d24526a2d6a89f5f68492
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -12,7 +12,9 @@
|
||||
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
|
||||
#if UMP_AVAILABLE
|
||||
using GoogleMobileAds.Ump.Api;
|
||||
#endif
|
||||
namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
{
|
||||
#if UNITY_ADS
|
||||
@ -28,10 +30,33 @@ namespace WordsToolkit.Scripts.Services.Ads.Networks
|
||||
|
||||
public override void Init(string _id, bool adSettingTestMode, IAdsListener listener)
|
||||
{
|
||||
SetConsentStatus();
|
||||
Advertisement.Initialize(_id, adSettingTestMode, this);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void SetConsentStatus()
|
||||
{
|
||||
#if UNITY_ADS && UMP_AVAILABLE
|
||||
bool hasConsent = ConsentInformation.CanRequestAds();
|
||||
Debug.Log($"Unity Ads consent status: {hasConsent}");
|
||||
|
||||
var consentMetaData = new MetaData("gdpr");
|
||||
consentMetaData.Set("consent", hasConsent);
|
||||
Advertisement.SetMetaData(consentMetaData);
|
||||
|
||||
var privacyMetaData = new MetaData("privacy");
|
||||
privacyMetaData.Set("mode", "mixed");
|
||||
Advertisement.SetMetaData(privacyMetaData);
|
||||
#elif UNITY_ADS
|
||||
// Default to no consent if UMP not available
|
||||
var consentMetaData = new MetaData("gdpr");
|
||||
consentMetaData.Set("consent", false);
|
||||
Advertisement.SetMetaData(consentMetaData);
|
||||
Debug.Log("UMP not available - setting Unity Ads consent to false");
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Show(AdUnit adUnit)
|
||||
{
|
||||
Advertisement.Show(adUnit.PlacementId, this);
|
||||
|
||||
@ -12,14 +12,19 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using WordConnectGameToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
using WordsToolkit.Scripts.Services.Ads;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
using WordsToolkit.Scripts.Services.IAP;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.System;
|
||||
#if UMP_AVAILABLE
|
||||
using GoogleMobileAds.Ump.Api;
|
||||
#endif
|
||||
|
||||
namespace WordsToolkit.Scripts.Services
|
||||
{
|
||||
@ -29,6 +34,8 @@ namespace WordsToolkit.Scripts.Services
|
||||
private readonly List<AdUnit> adUnits = new();
|
||||
private readonly Dictionary<AdUnit, IAdLifecycleManager> lifecycleManagers = new();
|
||||
private EPlatforms platforms;
|
||||
private bool consentInfoUpdateInProgress = false;
|
||||
private bool adsInitialized = false;
|
||||
|
||||
[Inject]
|
||||
private GameSettings gameSettings;
|
||||
@ -38,6 +45,8 @@ namespace WordsToolkit.Scripts.Services
|
||||
|
||||
[SerializeField]
|
||||
private ProductID noAdsProduct;
|
||||
private InterstitialSettings interstitialSettings;
|
||||
private AdSetting[] adElements;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@ -45,9 +54,86 @@ namespace WordsToolkit.Scripts.Services
|
||||
{
|
||||
return;
|
||||
}
|
||||
interstitialSettings = Resources.Load<InterstitialSettings>("Settings/AdsInterstitialSettings");
|
||||
adElements = Resources.Load<AdsSettings>("Settings/AdsSettings").adProfiles;
|
||||
StartConsentFlow();
|
||||
}
|
||||
|
||||
private void StartConsentFlow()
|
||||
{
|
||||
if (consentInfoUpdateInProgress) return;
|
||||
|
||||
consentInfoUpdateInProgress = true;
|
||||
|
||||
#if UMP_AVAILABLE && (UNITY_ANDROID || UNITY_IOS)
|
||||
var request = new ConsentRequestParameters();
|
||||
|
||||
if (Debug.isDebugBuild || Application.isEditor)
|
||||
{
|
||||
var debugSettings = new ConsentDebugSettings
|
||||
{
|
||||
DebugGeography = DebugGeography.EEA
|
||||
};
|
||||
|
||||
var testDeviceIds = new List<string>();
|
||||
// testDeviceIds.Add("YOUR-TEST-DEVICE-ID-HERE"); // Uncomment and add your device ID if needed
|
||||
|
||||
if (testDeviceIds.Count > 0)
|
||||
{
|
||||
debugSettings.TestDeviceHashedIds = testDeviceIds;
|
||||
}
|
||||
|
||||
request.ConsentDebugSettings = debugSettings;
|
||||
}
|
||||
|
||||
ConsentInformation.Update(request, OnConsentInfoUpdated);
|
||||
#else
|
||||
InitializeAds();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UMP_AVAILABLE
|
||||
private void OnConsentInfoUpdated(FormError consentError)
|
||||
{
|
||||
consentInfoUpdateInProgress = false;
|
||||
|
||||
if (consentError != null)
|
||||
{
|
||||
Debug.LogError($"Consent info update failed: {consentError}");
|
||||
InitializeAds();
|
||||
return;
|
||||
}
|
||||
|
||||
ConsentForm.LoadAndShowConsentFormIfRequired(OnConsentFormDismissed);
|
||||
}
|
||||
|
||||
private void OnConsentFormDismissed(FormError formError)
|
||||
{
|
||||
if (formError != null)
|
||||
{
|
||||
Debug.LogError($"Consent form error: {formError}");
|
||||
}
|
||||
|
||||
if (ConsentInformation.CanRequestAds())
|
||||
{
|
||||
InitializeAds();
|
||||
RefreshBannerAds();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("User denied consent or consent not available");
|
||||
InitializeAds();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void InitializeAds()
|
||||
{
|
||||
if (adsInitialized) return;
|
||||
adsInitialized = true;
|
||||
|
||||
|
||||
platforms = GetPlatform();
|
||||
var adElements = Resources.Load<AdsSettings>("Settings/AdsSettings").adProfiles;
|
||||
foreach (var t in adElements)
|
||||
{
|
||||
if (t.platforms == platforms && t.enable)
|
||||
@ -60,7 +146,7 @@ namespace WordsToolkit.Scripts.Services
|
||||
adList.Add(t);
|
||||
foreach (var adElement in t.adElements)
|
||||
{
|
||||
var adUnit = new AdUnit(adElement.placementId, adElement.adReference);
|
||||
var adUnit = new AdUnit(adElement.placementId, adElement.adReference, t.adsHandler);
|
||||
var lifecycleManager = new AdLifecycleManager(t.adsHandler);
|
||||
lifecycleManagers[adUnit] = lifecycleManager;
|
||||
adUnits.Add(adUnit);
|
||||
@ -119,26 +205,72 @@ namespace WordsToolkit.Scripts.Services
|
||||
|
||||
private void OnPopupTrigger(Popup popup, bool open)
|
||||
{
|
||||
if (IsNoAdsPurchased())
|
||||
if (IsNoAdsPurchased() || !CanShowAds())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current level number
|
||||
int currentLevel = GameDataManager.GetLevelNum();
|
||||
|
||||
// Check interstitial ads using InterstitialSettings
|
||||
if (interstitialSettings != null && interstitialSettings.interstitials != null)
|
||||
{
|
||||
foreach (var interstitialElement in interstitialSettings.interstitials)
|
||||
{
|
||||
// Check if this interstitial should trigger based on popup
|
||||
if (((open && interstitialElement.showOnOpen) || (!open && interstitialElement.showOnClose))
|
||||
&& popup.GetType() == interstitialElement.popup.GetType())
|
||||
{
|
||||
var adUnit = adUnits.Find(i => i.AdReference == interstitialElement.adReference);
|
||||
if (adUnit == null || !adUnit.IsAvailable())
|
||||
{
|
||||
adUnit?.Load();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check level conditions
|
||||
if (!IsLevelConditionMet(currentLevel, interstitialElement))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find placement ID for frequency tracking
|
||||
string placementId = GetPlacementIdForAdReference(interstitialElement.adReference);
|
||||
if (placementId == null) continue;
|
||||
|
||||
if (!IsFrequencyConditionMet(placementId, interstitialElement.frequency))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
adUnit.Show();
|
||||
adUnit.Load();
|
||||
IncrementAdFrequency(placementId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-interstitial ads (banners, rewarded) using the original logic
|
||||
foreach (var ad in adList)
|
||||
{
|
||||
foreach (var adElement in ad.adElements)
|
||||
{
|
||||
if (adElement.adReference.adType == EAdType.Interstitial)
|
||||
continue; // Skip interstitials as they're handled above
|
||||
|
||||
var adUnit = adUnits.Find(i => i.AdReference == adElement.adReference);
|
||||
if (!lifecycleManagers[adUnit].IsAvailable(adUnit))
|
||||
if (!adUnit.IsAvailable())
|
||||
{
|
||||
lifecycleManagers[adUnit].Load(adUnit);
|
||||
adUnit.Load();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((open && adElement.popup.showOnOpen) || (!open && adElement.popup.showOnClose)) && popup.GetType() == adElement.popup.popup.GetType())
|
||||
{
|
||||
lifecycleManagers[adUnit].Show(adUnit);
|
||||
lifecycleManagers[adUnit].Load(adUnit);
|
||||
adUnit.Show();
|
||||
adUnit.Load();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -152,19 +284,48 @@ namespace WordsToolkit.Scripts.Services
|
||||
|
||||
public void ShowAdByType(AdReference adRef, Action<string> shown)
|
||||
{
|
||||
if (!gameSettings.enableAds)
|
||||
if (!gameSettings.enableAds || !CanShowAds())
|
||||
{
|
||||
shown?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
int currentLevel = GameDataManager.GetLevelNum();
|
||||
|
||||
foreach (var adUnit in adUnits)
|
||||
{
|
||||
if (adUnit.AdReference == adRef && lifecycleManagers[adUnit].IsAvailable(adUnit))
|
||||
if (adUnit.AdReference == adRef && adUnit.IsAvailable())
|
||||
{
|
||||
// Check level conditions for interstitial ads using InterstitialSettings
|
||||
if (adRef.adType == EAdType.Interstitial && interstitialSettings != null)
|
||||
{
|
||||
var interstitialElement = interstitialSettings.interstitials?.FirstOrDefault(i => i.adReference == adRef);
|
||||
if (interstitialElement != null)
|
||||
{
|
||||
if (!IsLevelConditionMet(currentLevel, interstitialElement))
|
||||
{
|
||||
shown?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
string placementId = GetPlacementIdForAdReference(adRef);
|
||||
if (placementId != null)
|
||||
{
|
||||
if (!IsFrequencyConditionMet(placementId, interstitialElement.frequency))
|
||||
{
|
||||
shown?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment frequency counter
|
||||
IncrementAdFrequency(placementId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adUnit.OnShown = shown;
|
||||
lifecycleManagers[adUnit].Show(adUnit);
|
||||
lifecycleManagers[adUnit].Load(adUnit);
|
||||
adUnit.Show();
|
||||
adUnit.Load();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -193,5 +354,70 @@ namespace WordsToolkit.Scripts.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLevelConditionMet(int currentLevel, InterstitialAdElement popupSetting)
|
||||
{
|
||||
return currentLevel >= popupSetting.minLevel && currentLevel <= popupSetting.maxLevel;
|
||||
}
|
||||
|
||||
private bool IsFrequencyConditionMet(string placementId, int frequency)
|
||||
{
|
||||
if (frequency <= 1) return true; // Always show if frequency is 1 or less
|
||||
|
||||
int adShowCount = PlayerPrefs.GetInt($"AdCount_{placementId}", 0);
|
||||
return adShowCount % frequency == 0;
|
||||
}
|
||||
|
||||
private void IncrementAdFrequency(string placementId)
|
||||
{
|
||||
int currentCount = PlayerPrefs.GetInt($"AdCount_{placementId}", 0);
|
||||
PlayerPrefs.SetInt($"AdCount_{placementId}", currentCount + 1);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private string GetPlacementIdForAdReference(AdReference adRef)
|
||||
{
|
||||
foreach (var ad in adList)
|
||||
{
|
||||
foreach (var adElement in ad.adElements)
|
||||
{
|
||||
if (adElement.adReference == adRef)
|
||||
{
|
||||
return adElement.placementId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool CanShowAds()
|
||||
{
|
||||
#if UMP_AVAILABLE
|
||||
return ConsentInformation.CanRequestAds();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void RefreshBannerAds()
|
||||
{
|
||||
if (!CanShowAds() || IsNoAdsPurchased()) return;
|
||||
|
||||
foreach (var adUnit in adUnits)
|
||||
{
|
||||
if (adUnit.AdReference.adType == EAdType.Banner)
|
||||
{
|
||||
lifecycleManagers[adUnit].Show(adUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReconsiderUMPConsent()
|
||||
{
|
||||
#if UMP_AVAILABLE && (UNITY_ANDROID || UNITY_IOS)
|
||||
ConsentInformation.Reset();
|
||||
#endif
|
||||
StartConsentFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,6 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
private IExtensionProvider extensionProvider;
|
||||
|
||||
public static event Action<string> OnSuccessfulPurchase;
|
||||
public static event Action<(string,string)> OnFailedPurchase;
|
||||
public static event Action<bool, List<string>> OnRestorePurchasesFinished;
|
||||
|
||||
public void InitializePurchasing(IEnumerable<(string productId, ProductTypeWrapper.ProductType productType)> products)
|
||||
@ -112,24 +111,20 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
{
|
||||
Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
|
||||
storeController.InitiatePurchase(product);
|
||||
OnFailedPurchase?.Invoke((productId, "Product not found or not available for purchase.")); // debug only
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase {productId}");
|
||||
OnFailedPurchase?.Invoke((productId, "InvalidProductID: product not found or not available for purchase."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("IAPInitFailed: BuyProductID FAIL. Not initialized.");
|
||||
OnFailedPurchase?.Invoke((productId, "IAPInitFailed: Not initialized."));
|
||||
Debug.Log("BuyProductID FAIL. Not initialized.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log("BuyProductID: FAIL. Exception during purchase. " + e);
|
||||
OnFailedPurchase?.Invoke((productId, "Exception during purchase: " + e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,13 +159,11 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
|
||||
{
|
||||
Debug.Log("OnPurchaseFailed: FAIL. Product: " + product.definition.id + " PurchaseFailureDescription: " + failureDescription);
|
||||
OnFailedPurchase?.Invoke((product.definition.id, failureDescription.message));
|
||||
}
|
||||
|
||||
public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
|
||||
{
|
||||
Debug.Log($"OnPurchaseFailed: FAIL. Product: '{i.definition.id}', PurchaseFailureReason: {p}");
|
||||
OnFailedPurchase?.Invoke((i.definition.id, p.ToString()));
|
||||
}
|
||||
|
||||
public void OnInitializeFailed(InitializationFailureReason reason)
|
||||
|
||||
@ -50,18 +50,12 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
IAPController.OnSuccessfulPurchase += purchaseHandler;
|
||||
#endif
|
||||
}
|
||||
public void SubscribeToPurchaseFailedEvent(Action<(string, string)> purchaseHandler)
|
||||
{
|
||||
#if UNITY_PURCHASING
|
||||
IAPController.OnFailedPurchase += purchaseHandler;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UnsubscribeFromPurchaseEvent(Action<string> purchaseHandler)
|
||||
{
|
||||
#if UNITY_PURCHASING
|
||||
#if UNITY_PURCHASING
|
||||
IAPController.OnSuccessfulPurchase -= purchaseHandler;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
public void BuyProduct(string productId)
|
||||
|
||||
@ -13,8 +13,6 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
bool IsProductPurchased(string productId);
|
||||
void RestorePurchases(Action<bool, List<string>> action);
|
||||
void SubscribeToPurchaseEvent(Action<string> purchaseHandler);
|
||||
|
||||
void SubscribeToPurchaseFailedEvent(Action<(string, string)> purchaseHandler);
|
||||
void UnsubscribeFromPurchaseEvent(Action<string> purchaseHandler);
|
||||
}
|
||||
}
|
||||
@ -21,5 +21,6 @@ namespace WordsToolkit.Scripts.Services
|
||||
bool IsRewardedAvailable(AdReference adRef);
|
||||
void RemoveAds();
|
||||
bool IsNoAdsPurchased();
|
||||
void ReconsiderUMPConsent();
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
|
||||
namespace WordsToolkit.Scripts.Settings
|
||||
{
|
||||
@ -36,7 +37,30 @@ namespace WordsToolkit.Scripts.Settings
|
||||
public Key SimulateDuplicate = Key.D;
|
||||
|
||||
[Header("")]
|
||||
[Tooltip("Test language, only for editor")]
|
||||
public SystemLanguage TestLanguage = SystemLanguage.English;
|
||||
[Tooltip("Test language code, only for editor (e.g., 'en', 'fr', 'es')")]
|
||||
[HideInInspector] // Hidden because we use custom editor dropdown
|
||||
public string TestLanguageCode = "en";
|
||||
|
||||
/// <summary>
|
||||
/// Validates if the TestLanguageCode exists in the provided LanguageConfiguration
|
||||
/// </summary>
|
||||
public bool IsValidTestLanguage(LanguageConfiguration config)
|
||||
{
|
||||
if (config == null || string.IsNullOrEmpty(TestLanguageCode))
|
||||
return false;
|
||||
|
||||
return config.GetLanguageInfo(TestLanguageCode) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a valid test language code, falling back to default if current one is invalid
|
||||
/// </summary>
|
||||
public string GetValidTestLanguageCode(LanguageConfiguration config)
|
||||
{
|
||||
if (IsValidTestLanguage(config))
|
||||
return TestLanguageCode;
|
||||
|
||||
return config?.defaultLanguage ?? "en";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,9 @@ using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordsToolkit.Scripts.Settings.Editor
|
||||
namespace WordConnectGameToolkit.Scripts.Settings.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(AdElement))]
|
||||
public class AdElementDrawer : PropertyDrawer
|
||||
@ -45,7 +46,24 @@ namespace WordsToolkit.Scripts.Settings.Editor
|
||||
var adTypeScriptableObject = (AdReference)adTypeScriptableProperty.objectReferenceValue;
|
||||
if (adTypeScriptableObject != null && adTypeScriptableObject.adType == EAdType.Interstitial)
|
||||
{
|
||||
popupField.visible = true;
|
||||
// Add button to open InterstitialSettings for interstitial ads
|
||||
var interstitialButton = new Button(() => {
|
||||
OpenInterstitialSettings();
|
||||
})
|
||||
{
|
||||
text = "Open Interstitial Settings"
|
||||
};
|
||||
interstitialButton.style.marginTop = 5;
|
||||
|
||||
// Show/hide button and popup field based on ad type
|
||||
adTypeScriptableField.RegisterValueChangeCallback(evt =>
|
||||
{
|
||||
UpdateFieldVisibility(adTypeScriptableProperty, popupField, interstitialButton, root);
|
||||
});
|
||||
|
||||
// Initial visibility setup
|
||||
UpdateFieldVisibility(adTypeScriptableProperty, popupField, interstitialButton, root);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -69,6 +87,46 @@ namespace WordsToolkit.Scripts.Settings.Editor
|
||||
// Return the root VisualElement
|
||||
return root;
|
||||
}
|
||||
|
||||
private void UpdateFieldVisibility(SerializedProperty adTypeScriptableProperty, VisualElement popupField, Button interstitialButton, VisualElement root)
|
||||
{
|
||||
var adTypeScriptableObject = (AdReference)adTypeScriptableProperty.objectReferenceValue;
|
||||
|
||||
if (adTypeScriptableObject != null && adTypeScriptableObject.adType == EAdType.Interstitial)
|
||||
{
|
||||
popupField.style.display = DisplayStyle.None; // Hide popup field for interstitials
|
||||
if (!root.Contains(interstitialButton))
|
||||
{
|
||||
root.Add(interstitialButton);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
popupField.style.display = DisplayStyle.Flex; // Show popup field for other ad types
|
||||
if (root.Contains(interstitialButton))
|
||||
{
|
||||
root.Remove(interstitialButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenInterstitialSettings()
|
||||
{
|
||||
// Find InterstitialSettings asset
|
||||
string[] guids = AssetDatabase.FindAssets("t:InterstitialSettings");
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
var interstitialSettings = AssetDatabase.LoadAssetAtPath<InterstitialSettings>(path);
|
||||
Selection.activeObject = interstitialSettings;
|
||||
EditorGUIUtility.PingObject(interstitialSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("InterstitialSettings Not Found",
|
||||
"Could not find InterstitialSettings ScriptableObject in the project.", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -3,8 +3,9 @@ using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordsToolkit.Scripts.Settings.Editor
|
||||
namespace WordConnectGameToolkit.Scripts.Settings.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(AdSetting))]
|
||||
public class AdSettingDrawer : PropertyDrawer
|
||||
@ -18,7 +19,6 @@ namespace WordsToolkit.Scripts.Settings.Editor
|
||||
var foldout = new Foldout { text = property.displayName, value = false };
|
||||
root.Add(foldout);
|
||||
|
||||
// Add fields to the foldout
|
||||
var nameField = new PropertyField(property.FindPropertyRelative("name"), "Name");
|
||||
var enableField = new PropertyField(property.FindPropertyRelative("enable"), "Enable");
|
||||
var testInEditorField = new PropertyField(property.FindPropertyRelative("testInEditor"), "Test In Editor");
|
||||
|
||||
@ -3,11 +3,7 @@
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:d3bf71b33c0c04eb9bc1a8d6513d76bb",
|
||||
"GUID:00dd4a7ac8c24c898083910c81898ecc",
|
||||
"GUID:ac6e78962cfc743b9a5fc5f5a808aa72",
|
||||
"GUID:b25ad8286798741e3b2cc3883283e669",
|
||||
"GUID:75bdbcf23199f4cfb86c610d1d946666",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc"
|
||||
"GUID:00dd4a7ac8c24c898083910c81898ecc"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using System.Linq;
|
||||
|
||||
namespace WordsToolkit.Scripts.Settings.Editor
|
||||
{
|
||||
[CustomEditor(typeof(DebugSettings))]
|
||||
public class DebugSettingsEditor : UnityEditor.Editor
|
||||
{
|
||||
private LanguageConfiguration languageConfig;
|
||||
private string[] availableLanguageCodes;
|
||||
private string[] availableLanguageNames;
|
||||
private int selectedLanguageIndex = 0;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
LoadLanguageConfiguration();
|
||||
}
|
||||
|
||||
void LoadLanguageConfiguration()
|
||||
{
|
||||
// Find LanguageConfiguration asset in the project
|
||||
var guids = AssetDatabase.FindAssets("t:LanguageConfiguration");
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
languageConfig = AssetDatabase.LoadAssetAtPath<LanguageConfiguration>(path);
|
||||
|
||||
if (languageConfig != null && languageConfig.languages != null)
|
||||
{
|
||||
availableLanguageCodes = languageConfig.languages.Select(l => l.code).ToArray();
|
||||
availableLanguageNames = languageConfig.languages.Select(l => $"{l.displayName} ({l.code})").ToArray();
|
||||
|
||||
// Find current selection
|
||||
var debugSettings = (DebugSettings)target;
|
||||
selectedLanguageIndex = global::System.Array.IndexOf(availableLanguageCodes, debugSettings.TestLanguageCode);
|
||||
if (selectedLanguageIndex < 0) selectedLanguageIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var debugSettings = (DebugSettings)target;
|
||||
|
||||
// Refresh language configuration if it's null or changed
|
||||
if (languageConfig == null || availableLanguageCodes == null)
|
||||
{
|
||||
LoadLanguageConfiguration();
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Draw default fields
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("enableHotkeys"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Debug Hotkeys", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("Win"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("Lose"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("Back"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("Restart"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("SimulateDuplicate"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Language Settings", EditorStyles.boldLabel);
|
||||
|
||||
// Custom dropdown for test language
|
||||
if (languageConfig != null && availableLanguageCodes != null && availableLanguageCodes.Length > 0)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
selectedLanguageIndex = EditorGUILayout.Popup("Test Language", selectedLanguageIndex, availableLanguageNames);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
debugSettings.TestLanguageCode = availableLanguageCodes[selectedLanguageIndex];
|
||||
EditorUtility.SetDirty(debugSettings);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to text field if no LanguageConfiguration found
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("TestLanguageCode"), new GUIContent("Test Language Code"));
|
||||
EditorGUILayout.HelpBox("No LanguageConfiguration found. Create one to enable language dropdown.", MessageType.Warning);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c8c4f73793624f5e80582b63c5d77ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
|
||||
namespace WordConnectGameToolkit.Scripts.Settings.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(InterstitialAdElement))]
|
||||
public class InterstitialAdElementDrawer : PropertyDrawer
|
||||
{
|
||||
private Popup[] popupPrefabs;
|
||||
private List<string> popupNames;
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
LoadPopupPrefabs();
|
||||
|
||||
var container = new VisualElement();
|
||||
|
||||
var adReferenceProperty = property.FindPropertyRelative("adReference");
|
||||
var elementNameProperty = property.FindPropertyRelative("elementName");
|
||||
var popupProperty = property.FindPropertyRelative("popup");
|
||||
var showOnOpenProperty = property.FindPropertyRelative("showOnOpen");
|
||||
var showOnCloseProperty = property.FindPropertyRelative("showOnClose");
|
||||
var minLevelProperty = property.FindPropertyRelative("minLevel");
|
||||
var maxLevelProperty = property.FindPropertyRelative("maxLevel");
|
||||
var frequencyProperty = property.FindPropertyRelative("frequency");
|
||||
|
||||
// Update element name based on ad reference
|
||||
UpdateElementName(adReferenceProperty, elementNameProperty);
|
||||
|
||||
// Ad Reference field
|
||||
var adReferenceField = new PropertyField(adReferenceProperty);
|
||||
adReferenceField.RegisterValueChangeCallback(evt =>
|
||||
{
|
||||
UpdateElementName(adReferenceProperty, elementNameProperty);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
container.Add(adReferenceField);
|
||||
|
||||
// Popup dropdown
|
||||
var popupDropdown = new DropdownField("Popup", popupNames, GetPopupIndex(popupProperty.objectReferenceValue as Popup));
|
||||
popupDropdown.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
int selectedIndex = popupNames.IndexOf(evt.newValue);
|
||||
if (selectedIndex == 0)
|
||||
{
|
||||
popupProperty.objectReferenceValue = null;
|
||||
}
|
||||
else if (selectedIndex > 0)
|
||||
{
|
||||
popupProperty.objectReferenceValue = popupPrefabs[selectedIndex - 1];
|
||||
}
|
||||
popupProperty.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
container.Add(popupDropdown);
|
||||
|
||||
// Show options
|
||||
var showOnOpenField = new PropertyField(showOnOpenProperty);
|
||||
container.Add(showOnOpenField);
|
||||
|
||||
var showOnCloseField = new PropertyField(showOnCloseProperty);
|
||||
container.Add(showOnCloseField);
|
||||
|
||||
// Level conditions header
|
||||
var levelHeader = new Label("Level Conditions");
|
||||
levelHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
levelHeader.style.marginTop = 5;
|
||||
container.Add(levelHeader);
|
||||
|
||||
// Level conditions fields
|
||||
var minLevelField = new PropertyField(minLevelProperty);
|
||||
container.Add(minLevelField);
|
||||
|
||||
var maxLevelField = new PropertyField(maxLevelProperty);
|
||||
container.Add(maxLevelField);
|
||||
|
||||
var frequencyField = new PropertyField(frequencyProperty);
|
||||
container.Add(frequencyField);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private void LoadPopupPrefabs()
|
||||
{
|
||||
string[] guids = AssetDatabase.FindAssets("t:Prefab");
|
||||
var popups = guids
|
||||
.Select(guid => AssetDatabase.GUIDToAssetPath(guid))
|
||||
.Select(path => AssetDatabase.LoadAssetAtPath<GameObject>(path))
|
||||
.Where(go => go != null && go.GetComponent<Popup>() != null)
|
||||
.Select(go => go.GetComponent<Popup>())
|
||||
.OrderBy(popup => popup.name)
|
||||
.ToArray();
|
||||
|
||||
popupPrefabs = popups;
|
||||
popupNames = new List<string> { "None (Popup)" };
|
||||
popupNames.AddRange(popups.Select(popup => popup.name));
|
||||
}
|
||||
|
||||
private int GetPopupIndex(Popup popup)
|
||||
{
|
||||
if (popup == null) return 0;
|
||||
|
||||
for (int i = 0; i < popupPrefabs.Length; i++)
|
||||
{
|
||||
if (popupPrefabs[i] == popup)
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void UpdateElementName(SerializedProperty adReferenceProperty, SerializedProperty elementNameProperty)
|
||||
{
|
||||
if (adReferenceProperty.objectReferenceValue != null)
|
||||
{
|
||||
string adRefName = adReferenceProperty.objectReferenceValue.name;
|
||||
elementNameProperty.stringValue = adRefName;
|
||||
}
|
||||
else
|
||||
{
|
||||
elementNameProperty.stringValue = "Unnamed";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 868b5f9431a04f3a81e7484ada3d3f8f
|
||||
timeCreated: 1756307465
|
||||
@ -0,0 +1,65 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordConnectGameToolkit.Scripts.Settings.Editor
|
||||
{
|
||||
[CustomEditor(typeof(InterstitialSettings))]
|
||||
public class InterstitialSettingsEditor : UnityEditor.Editor
|
||||
{
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = new VisualElement();
|
||||
var interstitialSettings = (InterstitialSettings)target;
|
||||
|
||||
// if (interstitialSettings.interstitials == null || interstitialSettings.interstitials.Length == 0)
|
||||
{
|
||||
var helpBox = new HelpBox("InterstitialSettings is empty. Click the button below to populate from AdsSettings.", HelpBoxMessageType.Info);
|
||||
root.Add(helpBox);
|
||||
|
||||
var populateButton = new Button(() =>
|
||||
{
|
||||
var adsSettings = FindAdsSettings();
|
||||
if (adsSettings != null)
|
||||
{
|
||||
interstitialSettings.PopulateFromAdsSettings(adsSettings);
|
||||
EditorUtility.SetDirty(interstitialSettings);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("AdsSettings Not Found",
|
||||
"Could not find AdsSettings ScriptableObject in the project.", "OK");
|
||||
}
|
||||
})
|
||||
{
|
||||
text = "Populate from AdsSettings"
|
||||
};
|
||||
root.Add(populateButton);
|
||||
}
|
||||
CreateDefaultInspector(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void CreateDefaultInspector(VisualElement root)
|
||||
{
|
||||
var interstitialsProperty = serializedObject.FindProperty("interstitials");
|
||||
var interstitialsField = new PropertyField(interstitialsProperty);
|
||||
interstitialsField.Bind(serializedObject);
|
||||
root.Add(interstitialsField);
|
||||
}
|
||||
|
||||
private AdsSettings FindAdsSettings()
|
||||
{
|
||||
string[] guids = AssetDatabase.FindAssets("t:AdsSettings");
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
return AssetDatabase.LoadAssetAtPath<AdsSettings>(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 285dda4a69e24274b3b50bd12857b12b
|
||||
timeCreated: 1756307465
|
||||
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
using WordsToolkit.Scripts.Services.Ads.AdUnits;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordConnectGameToolkit.Scripts.Settings
|
||||
{
|
||||
[CreateAssetMenu(fileName = "InterstitialSettings", menuName = "WordConnectGameToolkit/Settings/InterstitialSettings", order = 1)]
|
||||
public class InterstitialSettings : ScriptableObject
|
||||
{
|
||||
public InterstitialAdElement[] interstitials;
|
||||
|
||||
public void PopulateFromAdsSettings(AdsSettings adsSettings)
|
||||
{
|
||||
if (adsSettings == null) return;
|
||||
|
||||
var interstitialElements = new List<InterstitialAdElement>();
|
||||
|
||||
foreach (var adProfile in adsSettings.adProfiles)
|
||||
{
|
||||
if (!adProfile.enable) continue;
|
||||
|
||||
foreach (var adElement in adProfile.adElements)
|
||||
{
|
||||
if (adElement.adReference != null && adElement.adReference.adType == EAdType.Interstitial)
|
||||
{
|
||||
var interstitialElement = new InterstitialAdElement
|
||||
{
|
||||
elementName = adElement.adReference.name,
|
||||
adReference = adElement.adReference,
|
||||
popup = adElement.popup.popup,
|
||||
showOnOpen = adElement.popup.showOnOpen,
|
||||
showOnClose = adElement.popup.showOnClose,
|
||||
};
|
||||
|
||||
interstitialElements.Add(interstitialElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interstitials = interstitialElements.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class InterstitialAdElement
|
||||
{
|
||||
[HideInInspector]
|
||||
public string elementName;
|
||||
|
||||
public AdReference adReference;
|
||||
|
||||
[Header("Popup that triggers Interstitial ads")]
|
||||
public Popup popup;
|
||||
public bool showOnOpen;
|
||||
public bool showOnClose;
|
||||
[Header("Level Conditions")]
|
||||
public int minLevel = 1;
|
||||
public int maxLevel = 1000;
|
||||
public int frequency = 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44ad2bed4c494715ba8ad21e150b03ee
|
||||
timeCreated: 1756307490
|
||||
@ -103,7 +103,6 @@ namespace WordsToolkit.Scripts.System
|
||||
language = langName;
|
||||
EventManager.GetEvent<string>(EGameEvent.LanguageChanged).Subscribe(LanguageChanged);
|
||||
iapManager.SubscribeToPurchaseEvent(PurchaseSucceeded);
|
||||
iapManager.SubscribeToPurchaseFailedEvent(PurchaseFailed);
|
||||
|
||||
stateManager.OnStateChanged.AddListener((state) => {
|
||||
if (state != EScreenStates.MainMenu)
|
||||
@ -230,11 +229,6 @@ namespace WordsToolkit.Scripts.System
|
||||
{
|
||||
EventManager.GetEvent<string>(EGameEvent.PurchaseSucceeded).Invoke(id);
|
||||
}
|
||||
public void PurchaseFailed((string, string) info)
|
||||
{
|
||||
EventManager.GetEvent<(string, string)>(EGameEvent.PurchaseFailed).Invoke(info);
|
||||
}
|
||||
|
||||
|
||||
public void SetGameMode(EGameMode gameMode)
|
||||
{
|
||||
|
||||
@ -65,7 +65,7 @@ namespace WordsToolkit.Scripts.System.Haptic
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user