Files
UmaViewer/Assets/Scripts/LiveAutoSelection.cs
T

167 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using UnityEngine;
public static class LiveAutoSelection
{
private sealed class Candidate
{
public CharaEntry Character;
public string CostumeId;
public Sprite Icon;
public int BodyTypeSub;
}
// Existing character, body costume and head costume choices are never replaced.
public static bool FillMissing(UmaViewerMain main, LiveEntry live,
IList<LiveCharacterSelect> slots, Sprite fallbackIcon = null)
{
if (main == null || live == null || slots == null) return false;
bool needsDefaults = false;
var used = new HashSet<int>();
foreach (var slot in slots)
{
if (slot == null) continue;
if (slot.CharaEntry != null && slot.CharaEntry.Id > 0) used.Add(slot.CharaEntry.Id);
if (slot.CharaEntry == null || slot.CharaEntry.Id <= 0 || string.IsNullOrEmpty(slot.CostumeId)) needsDefaults = true;
}
if (!needsDefaults) return true;
var defaults = GetWinningCostumes(main, fallbackIcon);
var singerIds = GetSingerIds(main, live.MusicId);
var singers = new List<Candidate>();
var others = new List<Candidate>();
foreach (var character in main.Characters)
{
if (character == null || character.IsMob || !defaults.TryGetValue(character.Id, out var candidate)) continue;
candidate.Character = character;
(singerIds.Contains(character.Id) ? singers : others).Add(candidate);
}
Shuffle(singers);
Shuffle(others);
foreach (var slot in slots)
{
if (slot == null) continue;
bool missingCharacter = slot.CharaEntry == null || slot.CharaEntry.Id <= 0;
bool missingCostume = string.IsNullOrEmpty(slot.CostumeId);
if (!missingCharacter && !missingCostume) continue;
Candidate selected;
if (missingCharacter)
{
selected = FindCandidate(main, singers, used, slot.CostumeId, true)
?? FindCandidate(main, others, used, slot.CostumeId, true)
?? FindCandidate(main, singers, used, slot.CostumeId, false)
?? FindCandidate(main, others, used, slot.CostumeId, false);
}
else
{
defaults.TryGetValue(slot.CharaEntry.Id, out selected);
if (slot.CharaEntry.IsMob) selected = null;
}
if (selected == null) return false;
if (missingCharacter) slot.CharaEntry = selected.Character;
if (missingCostume) slot.CostumeId = selected.CostumeId;
used.Add(slot.CharaEntry.Id);
if (slot.CharaImage != null)
{
slot.CharaImage.enabled = true;
slot.CharaImage.sprite = slot.CharaEntry.Icon;
}
if (missingCostume && slot.CostumeImage != null)
slot.CostumeImage.sprite = selected.Icon;
}
return true;
}
private static Dictionary<int, Candidate> GetWinningCostumes(UmaViewerMain main, Sprite fallbackIcon)
{
var result = new Dictionary<int, Candidate>();
var rows = UmaDatabaseController.Instance.DressData;
if (rows == null) return result;
foreach (DataRow row in rows)
{
if (Convert.ToInt32(row["costume_type"]) != 1) continue;
int characterId = Convert.ToInt32(row["chara_id"]);
int subtype = Convert.ToInt32(row["body_type_sub"]);
string costumeId = subtype.ToString("D2", CultureInfo.InvariantCulture);
string body = UmaDatabaseController.BodyPath + $"bdy{characterId}_{costumeId}/pfb_bdy{characterId}_{costumeId}";
if (characterId <= 0 || !main.AbList.TryGetValue(body, out var entry) || entry == null) continue;
if (result.TryGetValue(characterId, out var previous) && previous.BodyTypeSub <= subtype) continue;
Sprite icon = fallbackIcon;
foreach (var costume in main.Costumes)
{
if (costume.CharaId != characterId || costume.BodyTypeSub != subtype) continue;
if (costume.Icon != null) icon = costume.Icon;
break;
}
result[characterId] = new Candidate { CostumeId = costumeId, Icon = icon, BodyTypeSub = subtype };
}
return result;
}
private static HashSet<int> GetSingerIds(UmaViewerMain main, int musicId)
{
string song = musicId.ToString("D4", CultureInfo.InvariantCulture);
string prefix = $"sound/l/{song}/snd_bgm_live_{song}_chara_";
var result = new HashSet<int>();
foreach (var sound in main.AbSounds)
{
string name = sound.Name;
if (!name.StartsWith(prefix, StringComparison.Ordinal) || !name.EndsWith("awb", StringComparison.OrdinalIgnoreCase)) continue;
int separator = name.IndexOf('_', prefix.Length);
if (separator < 0 || !name.Substring(separator).StartsWith("_01", StringComparison.Ordinal)) continue;
if (int.TryParse(name.Substring(prefix.Length, separator - prefix.Length), out int characterId))
result.Add(characterId);
}
return result;
}
private static Candidate FindCandidate(UmaViewerMain main, List<Candidate> candidates,
HashSet<int> used, string costumeId, bool distinct)
{
if (candidates.Count == 0) return null;
int start = distinct ? 0 : UnityEngine.Random.Range(0, candidates.Count);
for (int i = 0; i < candidates.Count; i++)
{
var candidate = candidates[(start + i) % candidates.Count];
if (distinct && used.Contains(candidate.Character.Id)) continue;
if (string.IsNullOrEmpty(costumeId) || HasBody(main, candidate.Character, costumeId)) return candidate;
}
return null;
}
private static bool HasBody(UmaViewerMain main, CharaEntry character, string costumeId)
{
if (costumeId.Length < 4)
return main.AbList.ContainsKey(UmaDatabaseController.BodyPath + $"bdy{character.Id}_{costumeId}/pfb_bdy{character.Id}_{costumeId}");
int separator = costumeId.LastIndexOf('_');
if (separator < 0) return false;
var rows = UmaDatabaseController.Instance.CharaData;
if (rows == null) return false;
foreach (var row in rows)
{
if (Convert.ToInt32(row["id"]) != character.Id) continue;
string shortId = costumeId.Substring(0, separator);
string body = UmaDatabaseController.BodyPath + $"bdy{shortId}/pfb_bdy{costumeId}_{row["height"]}_{row["shape"]}_{row["bust"]}";
return main.AbList.ContainsKey(body);
}
return false;
}
private static void Shuffle(List<Candidate> candidates)
{
for (int i = candidates.Count - 1; i > 0; i--)
{
int other = UnityEngine.Random.Range(0, i + 1);
var candidate = candidates[i];
candidates[i] = candidates[other];
candidates[other] = candidate;
}
}
}