Gacha_UpSelect

This commit is contained in:
Kei-Luna
2026-05-19 10:14:22 +09:00
parent 686794a68c
commit def4b8ae68
3 changed files with 187 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ public class GachaExcel : ExcelResource
public uint? ProtectTag { get; set; } public uint? ProtectTag { get; set; }
public uint? ProtectType { get; set; } public uint? ProtectType { get; set; }
public JToken? ProtectCount { get; set; } public JToken? ProtectCount { get; set; }
public uint? UpSelect { get; set; }
public override uint GetId() => ID; public override uint GetId() => ID;
public override void Loaded() => GameData.GachaData[ID] = this; public override void Loaded() => GameData.GachaData[ID] = this;
@@ -41,4 +42,5 @@ public class GachaPoolItem
public List<uint> GDPL { get; set; } = []; public List<uint> GDPL { get; set; } = [];
public int Weight { get; set; } public int Weight { get; set; }
public int? UPTag { get; set; } public int? UPTag { get; set; }
public int? UPSelectTag { get; set; }
} }

View File

@@ -26,6 +26,8 @@ public class Gacha_Launch : ICallGSHandler
private const uint SidAddTimeProb = 2; private const uint SidAddTimeProb = 2;
private const uint SidAddProtectType = 3; private const uint SidAddProtectType = 3;
private const uint SidAddTotalTime = 7; private const uint SidAddTotalTime = 7;
private const int UpSelectIndex = 0;
private const int UpSelectGetFlagIndex = 1;
private static readonly Random Rng = new(); private static readonly Random Rng = new();
public async Task Handle(Connection connection, string param, ushort seqNo) public async Task Handle(Connection connection, string param, ushort seqNo)
@@ -58,6 +60,7 @@ public class Gacha_Launch : ICallGSHandler
} }
var pityState = LoadPityState(player, gachaCfg); var pityState = LoadPityState(player, gachaCfg);
var upSelectState = LoadUpSelectState(player, gachaCfg);
var config = BuildRuntimeConfig(gachaCfg, poolNames); var config = BuildRuntimeConfig(gachaCfg, poolNames);
var awards = new List<List<uint>>(); var awards = new List<List<uint>>();
var tbNew = new List<int>(); var tbNew = new List<int>();
@@ -94,6 +97,18 @@ public class Gacha_Launch : ICallGSHandler
trigger = forceTopUp && item != null && config.UpTarget != null && item.Rarity == config.UpTarget.Rarity; trigger = forceTopUp && item != null && config.UpTarget != null && item.Rarity == config.UpTarget.Rarity;
} }
if (item != null && upSelectState.SelectedItem != null && item.Rarity >= config.TopRarity)
{
bool forceSelected = upSelectState.GuaranteedNext;
bool shouldSelect = forceSelected || Rng.Next(100) < 50;
if (shouldSelect)
{
var selectedItem = FindExactItem(allPoolItems, upSelectState.SelectedItem);
if (selectedItem != null && selectedItem.Rarity >= config.TopRarity)
item = selectedItem;
}
}
if (item == null || item.GDPL.Count < 4) if (item == null || item.GDPL.Count < 4)
{ {
tbTrigger.Add(false); tbTrigger.Add(false);
@@ -109,6 +124,7 @@ public class Gacha_Launch : ICallGSHandler
tbTrigger.Add(trigger); tbTrigger.Add(trigger);
UpdatePityState(pityState, config, item); UpdatePityState(pityState, config, item);
UpdateUpSelectState(upSelectState, config, item);
var itemType = (ItemTypeEnum)g; var itemType = (ItemTypeEnum)g;
switch (itemType) switch (itemType)
@@ -149,6 +165,7 @@ public class Gacha_Launch : ICallGSHandler
} }
SavePityState(player, gachaCfg, pityState, awards.Count, sync); SavePityState(player, gachaCfg, pityState, awards.Count, sync);
SaveUpSelectState(player, gachaCfg, upSelectState, sync);
DatabaseHelper.SaveDatabaseType(player.Data); DatabaseHelper.SaveDatabaseType(player.Data);
DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData);
DatabaseHelper.SaveDatabaseType(player.CharacterManager.CharacterData); DatabaseHelper.SaveDatabaseType(player.CharacterManager.CharacterData);
@@ -187,6 +204,14 @@ public class Gacha_Launch : ICallGSHandler
x.GDPL[2] == gdpl[2] && x.GDPL[2] == gdpl[2] &&
x.GDPL[3] == gdpl[3]); x.GDPL[3] == gdpl[3]);
private static GachaPoolItem? FindExactItem(List<GachaPoolItem> pool, uint[] gdpl) =>
pool.FirstOrDefault(x =>
x.GDPL.Count >= 4 &&
x.GDPL[0] == gdpl[0] &&
x.GDPL[1] == gdpl[1] &&
x.GDPL[2] == gdpl[2] &&
x.GDPL[3] == gdpl[3]);
private static GachaRuntimeConfig BuildRuntimeConfig(GachaExcel gachaCfg, List<string> poolNames) private static GachaRuntimeConfig BuildRuntimeConfig(GachaExcel gachaCfg, List<string> poolNames)
{ {
var allPoolItems = poolNames.SelectMany(name => GameData.GachaPoolData[name]).ToList(); var allPoolItems = poolNames.SelectMany(name => GameData.GachaPoolData[name]).ToList();
@@ -271,6 +296,56 @@ public class Gacha_Launch : ICallGSHandler
SetAttr(player, sync, GachaGid, baseSid + SidAddTotalTime, (uint)(state.PoolTotalTime + drawCount)); SetAttr(player, sync, GachaGid, baseSid + SidAddTotalTime, (uint)(state.PoolTotalTime + drawCount));
} }
private static GachaUpSelectState LoadUpSelectState(PlayerInstance player, GachaExcel gachaCfg)
{
if (gachaCfg.UpSelect != 1)
return new GachaUpSelectState();
var raw = player.Data.StrAttrs.FirstOrDefault(x => x.Gid == GachaSgid && x.Sid == gachaCfg.ID)?.Val;
if (string.IsNullOrWhiteSpace(raw))
return new GachaUpSelectState();
try
{
var state = JArray.Parse(raw);
uint[]? selected = null;
if (state.Count > UpSelectIndex && state[UpSelectIndex] is JArray selectedArray && selectedArray.Count >= 4)
{
selected =
[
selectedArray[0]?.Value<uint>() ?? 0,
selectedArray[1]?.Value<uint>() ?? 0,
selectedArray[2]?.Value<uint>() ?? 0,
selectedArray[3]?.Value<uint>() ?? 0
];
}
return new GachaUpSelectState
{
SelectedItem = selected,
GuaranteedNext = state.Count > UpSelectGetFlagIndex && (state[UpSelectGetFlagIndex]?.Value<int>() ?? 0) == 1,
RawState = state
};
}
catch
{
return new GachaUpSelectState();
}
}
private static void SaveUpSelectState(PlayerInstance player, GachaExcel gachaCfg, GachaUpSelectState state, NtfSyncPlayer sync)
{
if (gachaCfg.UpSelect != 1 || state.RawState == null)
return;
EnsureArraySize(state.RawState, 2);
state.RawState[UpSelectGetFlagIndex] = state.GuaranteedNext ? 1 : 0;
var value = state.RawState.ToString(Newtonsoft.Json.Formatting.None);
player.SetStrAttr(GachaSgid, gachaCfg.ID, value);
sync.CustomStr[player.ToShiftedAttrKey(GachaSgid, gachaCfg.ID)] = value;
}
private static uint GetBaseSid(GachaExcel gachaCfg) private static uint GetBaseSid(GachaExcel gachaCfg)
{ {
if (gachaCfg.ProtectTag.HasValue) if (gachaCfg.ProtectTag.HasValue)
@@ -317,6 +392,27 @@ public class Gacha_Launch : ICallGSHandler
} }
} }
private static void UpdateUpSelectState(GachaUpSelectState state, GachaRuntimeConfig config, GachaPoolItem item)
{
if (state.SelectedItem == null || item.Rarity < config.TopRarity)
return;
state.GuaranteedNext = !MatchesGdpl(item, state.SelectedItem);
}
private static bool MatchesGdpl(GachaPoolItem item, uint[] gdpl) =>
item.GDPL.Count >= 4 &&
item.GDPL[0] == gdpl[0] &&
item.GDPL[1] == gdpl[1] &&
item.GDPL[2] == gdpl[2] &&
item.GDPL[3] == gdpl[3];
private static void EnsureArraySize(JArray state, int size)
{
while (state.Count < size)
state.Add(JValue.CreateNull());
}
private static bool IsFromPool(GachaPoolItem item, PoolRarityRef target) => private static bool IsFromPool(GachaPoolItem item, PoolRarityRef target) =>
item.Rarity == target.Rarity && item.Rarity == target.Rarity &&
GameData.GachaPoolData.TryGetValue(target.PoolName, out var pool) && GameData.GachaPoolData.TryGetValue(target.PoolName, out var pool) &&
@@ -446,4 +542,11 @@ internal sealed class GachaRuntimeConfig
public int TenGuaranteeRarity { get; set; } public int TenGuaranteeRarity { get; set; }
} }
internal sealed class GachaUpSelectState
{
public uint[]? SelectedItem { get; set; }
public bool GuaranteedNext { get; set; }
public JArray? RawState { get; set; } = new();
}
internal sealed record PoolRarityRef(string PoolName, int Rarity); internal sealed record PoolRarityRef(string PoolName, int Rarity);

View File

@@ -0,0 +1,82 @@
using MikuSB.Data;
using MikuSB.Database;
using MikuSB.Proto;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MikuSB.GameServer.Server.CallGS.Handlers.Gacha;
[CallGSApi("Gacha_UpSelect")]
public class Gacha_UpSelect : ICallGSHandler
{
private const uint GachaStrGid = 42;
private const int UpSelectIndex = 0;
private const int UpSelectGetFlagIndex = 1;
private const int UpPickPoolIndex = 2;
public async Task Handle(Connection connection, string param, ushort seqNo)
{
var req = JsonSerializer.Deserialize<GachaUpSelectParam>(param);
var player = connection.Player!;
if (req == null || req.NId == 0 || req.Gdpl == null || req.Gdpl.Count < 4)
{
await CallGSRouter.SendScript(connection, "Gacha_UpSelect", "{\"sErr\":\"error.BadParam\"}");
return;
}
if (!GameData.GachaData.TryGetValue((uint)req.NId, out var gachaCfg) || gachaCfg.UpSelect != 1)
{
await CallGSRouter.SendScript(connection, "Gacha_UpSelect", "{\"sErr\":\"error.BadParam\"}");
return;
}
var valid = (gachaCfg.Pool ?? [])
.Where(GameData.GachaPoolData.ContainsKey)
.SelectMany(name => GameData.GachaPoolData[name])
.Any(item =>
item.UPSelectTag == 1 &&
item.GDPL.Count >= 4 &&
item.GDPL[0] == req.Gdpl[0] &&
item.GDPL[1] == req.Gdpl[1] &&
item.GDPL[2] == req.Gdpl[2] &&
item.GDPL[3] == req.Gdpl[3]);
if (!valid)
{
await CallGSRouter.SendScript(connection, "Gacha_UpSelect", "{\"sErr\":\"error.BadParam\"}");
return;
}
var existing = player.Data.StrAttrs.FirstOrDefault(x => x.Gid == GachaStrGid && x.Sid == (uint)req.NId)?.Val;
var state = string.IsNullOrWhiteSpace(existing) ? new JArray() : JArray.Parse(existing);
EnsureArraySize(state, 3);
state[UpSelectIndex] = new JArray(req.Gdpl);
state[UpSelectGetFlagIndex] = 0;
if (state[UpPickPoolIndex] == null)
state[UpPickPoolIndex] = 0;
player.SetStrAttr(GachaStrGid, (uint)req.NId, state.ToString(Newtonsoft.Json.Formatting.None));
DatabaseHelper.SaveDatabaseType(player.Data);
var sync = new NtfSyncPlayer();
sync.CustomStr[player.ToShiftedAttrKey(GachaStrGid, (uint)req.NId)] = state.ToString(Newtonsoft.Json.Formatting.None);
await CallGSRouter.SendScript(connection, "Gacha_UpSelect", "{}", sync);
}
private static void EnsureArraySize(JArray state, int size)
{
while (state.Count < size)
state.Add(JValue.CreateNull());
}
}
internal sealed class GachaUpSelectParam
{
[JsonPropertyName("nId")]
public int NId { get; set; }
[JsonPropertyName("gdpl")]
public List<uint>? Gdpl { get; set; }
}