using System.Collections.Generic; using System.Linq; using UnityRandom = UnityEngine.Random; using SystemRandom = System.Random; using UnityEngine; using System; using asap.core; using cfg; using UnityEngine.Assertions; public static class FtUtils { /// /// Split number into count pieces randomly. /// /// Number to split. /// Number of pieces. /// Result. Meaningless if result if false. /// False if fail to split. public static bool RandomSplit(int number, int count, out List res) { // res = new List(); // if (number < count || number <= 0 || count <= 0) // return false; // if (number == count) // { // res = Enumerable.Repeat(1, count).ToList(); // return true; // } // var baseNumbers = new HashSet { 0 }; // while (baseNumbers.Count < count) // baseNumbers.Add(UnityRandom.Range(1, number - 1)); // var baseList = baseNumbers.OrderBy(e => e).ToList(); // for (int i = 1; i < baseList.Count; i++) // res.Add(baseList[i] - baseList[i - 1]); // res.Add(number - baseList[^1]); // return res.Count == count; res = null; if (number < count || number <= 0 || count <= 0) return false; res = new List(count); // Step 1: initialize all to 1 for (int i = 0; i < count; i++) res.Add(1); int remaining = number - count; // Step 2: distribute remaining randomly for (int i = 0; i < remaining; i++) { int index = UnityRandom.Range(0, count); res[index]++; } return true; } public static bool ScaleByBase(int num, int baseNum, out int res) { res = baseNum; if (num <= 0 || baseNum <= 0) return false; if (num <= baseNum) return true; res = num / baseNum * baseNum; return true; } public static int GetRandomIdxFromWeightList(IEnumerable weightList) { // Check for null or empty list if (weightList == null || !weightList.Any()) { Debug.LogError("[GetRandomIdxFromWeightList]: Weight list is null or empty. Returning 0."); return 0; } var accWeightList = new List(); int tmp = 0; foreach (int w in weightList) { // Check for negative weights if (w < 0) { Debug.LogError($"[GetRandomIdxFromWeightList]: Negative weight detected: {w}. Returning 0."); return 0; } tmp += w; accWeightList.Add(tmp); } // Check for all zero weights if (accWeightList[^1] <= 0) { Debug.LogError("[GetRandomIdxFromWeightList]: Total weight is zero or negative. Returning 0."); return 0; } tmp = UnityRandom.Range(0, accWeightList[^1]); for (int i = 0; i < accWeightList.Count; i++) { if (tmp < accWeightList[i]) return i; } return accWeightList.Count - 1; } public static int GetRandomIdxFromWeightList(IEnumerable weightList, SystemRandom rng) { // Check for null or empty list if (weightList == null || !weightList.Any()) { Debug.LogError("[GetRandomIdxFromWeightList]: Weight list is null or empty. Returning 0."); return 0; } var accWeightList = new List(); int tmp = 0; foreach (int w in weightList) { // Check for negative weights if (w < 0) { Debug.LogError("[GetRandomIdxFromWeightList]: Negative weight detected. Returning 0."); return 0; } tmp += w; accWeightList.Add(tmp); } // Check for all zero weights if (accWeightList[^1] <= 0) { Debug.LogError("[GetRandomIdxFromWeightList]: Total weight is zero or negative. Returning 0."); return 0; } tmp = rng.Next(0, accWeightList[^1]); for (int i = 0; i < accWeightList.Count; i++) { if (tmp < accWeightList[i]) return i; } return accWeightList.Count - 1; } public static int GetRandomIdxFromWeightList(IEnumerable weightList, SystemRandom rng) { // Check for null or empty list if (weightList == null || !weightList.Any()) { Debug.LogError("[GetRandomIdxFromWeightList]: Weight list is null or empty. Returning 0."); return 0; } var accWeightList = new List(); double tmp = 0d; foreach (float w in weightList) { // Check for negative weights if (w < 0) { Debug.LogError("[GetRandomIdxFromWeightList]: Negative weight detected. Returning 0."); return 0; } tmp += w; accWeightList.Add((float)tmp); } // Check for all zero weights if (accWeightList[^1] <= 0) { Debug.LogError("[GetRandomIdxFromWeightList]: Total weight is zero or negative. Returning 0."); return 0; } tmp = rng.NextDouble() * accWeightList[^1]; for (int i = 0; i < accWeightList.Count; i++) { if (tmp < accWeightList[i]) return i; } return accWeightList.Count - 1; } public static T PickRandomItemWithWeight(IEnumerable items, SystemRandom rng, System.Func weightGetter) { var weightList = items.Select((x, i) => weightGetter(i)).ToList(); return items.ElementAt(GetRandomIdxFromWeightList(weightList, rng)); } public static T PickRandomItemWithWeight(IEnumerable items, System.Func weightGetter) { var weightList = items.Select((x, i) => weightGetter(i)).ToList(); return items.ElementAt(GetRandomIdxFromWeightList(weightList)); } public static Vector2 CalculateBezierCurve(float t, Vector2 p0, Vector2 p1, Vector2 p2) { if (t < 0 || t > 1) return Vector2.zero; Vector2 a = Vector2.Lerp(p0, p1, t); Vector2 b = Vector2.Lerp(p1, p2, t); return Vector2.Lerp(a, b, t); } /// /// ("#,##0") Display number with comma, integer part only. /// /// /// public static string GetNumberString(float num) { string str; if (num < 1000) { str = num.ToString("#,##0"); } else if (num < 1000000) { str = (num / 1000f).ToString("#,##0") + "K"; } else if (num < 1000000000) { str = (num / 1000000f).ToString("#,##0") + "M"; } else { str = (num / 1000000000f).ToString("#,##0") + "G"; } return str; } public static int[] BitWisePositionScan(int num) { var res = new List(); int idx = 0; while (num != 0) { if ((num & 1) == 1) res.Add(idx); num >>= 1; idx++; } return res.ToArray(); } public static void ShuffleList(IList list, System.Random rng) { for (int i = list.Count - 1; i > 0; i--) { int j = rng.Next(i + 1); (list[j], list[i]) = (list[i], list[j]); } } /// /// Get a random point in a corn-shaped spread. /// /// In degree. /// Close range. /// Far range. /// In radians. Default is upward vertical, 90 degrees. /// A random delta public static Vector2 GetCornSpread(float spreadHalfAngle, float spreadNearRange, float spreadFarRange, float direction = 90) { var halfAngleRadians = Mathf.Deg2Rad * spreadHalfAngle; var angle = UnityRandom.Range(-halfAngleRadians, halfAngleRadians); angle += Mathf.Deg2Rad * direction; var radius = UnityRandom.Range(spreadNearRange, spreadFarRange); return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius; } /// /// Get Time limitation of Event from EventId. Can throw error, so either catch it or let it jam. /// /// Event id. /// Tuple, (startTime, endTime) public static (DateTime startTime, DateTime endTime) GetEventTimeLimit(int eventId) { var res = GContext.container.Resolve().TbFishingEvent.DataMap.TryGetValue(eventId, out var tableEvent); Assert.IsTrue(res, $"[FtUtils] Event {eventId} not found in fishing event table."); try { var startTime = DateTime.Parse((tableEvent.TimeDefinition as LimitedTime).StartTime); var endTime = DateTime.Parse((tableEvent.TimeDefinition as LimitedTime).EndTime); return (startTime, endTime); } catch (Exception e) { throw e; } } public static int RoundToInt(float num) { return (int)(num + 0.5f); } /// /// Counts the number of set bits (1s) in the binary representation of a positive integer. /// /// The integer to count set bits for. Must be non-negative. /// The number of set bits (1s) in the binary representation of x. /// Thrown when a negative number is passed. public static int PopCount(int x) { if (x < 0) { throw new ArgumentException("PopCount does not support negative numbers", nameof(x)); } int count = 0; while (x != 0) { x &= x - 1; count++; } return count; } public static List ResolveChestContent(int chestItemId, float? inflationRate = null) { var res = new List(); var item = GContext.container.Resolve().TbItem.GetOrDefault(chestItemId); if (item == null) { Debug.LogError($"[FtUtils] Item {chestItemId} not found in item table."); return res; } if (item.Type != 9 || item.SubType != 7) { Debug.LogError($"[FtUtils] Item {chestItemId} is not a grand reward chest item."); return res; } res = GContext.container.Resolve().GetItemDropItemList(item.RedirectID, 1); GContext.container.Resolve().LureInflation(res, inflationRate); return res; } }