Compare commits

...

6 Commits
v4.5 ... main

Author SHA1 Message Date
Kei-Luna
7c41d84a08 Update LICENSE 2026-06-05 06:33:48 +09:00
Kei-Luna
1c9eb7ef14 License 2026-06-05 06:30:16 +09:00
Kei-Luna
3c045a79a9 Update version.txt 2026-05-31 05:42:02 +09:00
Kei-Luna
ecf2446598 Weapon_OneKeyToMax 2026-05-31 05:17:08 +09:00
Kei-Luna
75d840974b Item_Recycle 2026-05-31 05:02:04 +09:00
Kei-Luna
d6f57053dd Update README_jp.md 2026-05-30 17:52:15 +09:00
9 changed files with 577 additions and 32 deletions

View File

@@ -6,6 +6,7 @@ namespace MikuSB.Data.Excel;
public class RecycleExcel : ExcelResource public class RecycleExcel : ExcelResource
{ {
public int ID { get; set; } public int ID { get; set; }
public JToken? RecycleReward { get; set; }
public JToken? RecycleBase { get; set; } public JToken? RecycleBase { get; set; }
public JToken? RecycleRatio { get; set; } public JToken? RecycleRatio { get; set; }

View File

@@ -13,6 +13,7 @@ public class SupportCardExcel : ExcelResource
public uint Icon { get; set; } public uint Icon { get; set; }
public uint ProvideExp { get; set; } public uint ProvideExp { get; set; }
public uint Color { get; set; } public uint Color { get; set; }
[JsonProperty("RecycleID")] public int RecycleID { get; set; }
[JsonProperty("LevelLimitID")] public int LevelLimitId { get; set; } [JsonProperty("LevelLimitID")] public int LevelLimitId { get; set; }
[JsonProperty("AffixPool")] public List<int> AffixPool { get; set; } = []; [JsonProperty("AffixPool")] public List<int> AffixPool { get; set; } = [];
[JsonProperty("AffixCost")] public JToken? AffixCostRaw { get; set; } [JsonProperty("AffixCost")] public JToken? AffixCostRaw { get; set; }

View File

@@ -0,0 +1,316 @@
using MikuSB.Data;
using MikuSB.Data.Excel;
using MikuSB.Database;
using MikuSB.Database.Inventory;
using MikuSB.Enums.Item;
using MikuSB.Proto;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MikuSB.GameServer.Server.CallGS.Handlers.Inventory;
[CallGSApi("Item_Recycle")]
public class Item_Recycle : ICallGSHandler
{
public async Task Handle(Connection connection, string param, ushort seqNo)
{
var player = connection.Player!;
var req = JsonSerializer.Deserialize<ItemRecycleParam>(param);
if (req?.TbItems == null || req.TbItems.Count == 0)
{
await CallGSRouter.SendScript(connection, "Item_Recycle", "{\"sErr\":\"error.BadParam\"}");
return;
}
var config = RecycleConfig.Load();
var itemsToRecycle = new List<(BaseGameItemInfo Item, int RecycleId)>();
foreach (var uniqueId in req.TbItems)
{
BaseGameItemInfo? item = player.InventoryManager.GetWeaponItem((uint)uniqueId)
?? (BaseGameItemInfo?)player.InventoryManager.GetSupportCardItem((uint)uniqueId);
if (item == null)
{
await CallGSRouter.SendScript(connection, "Item_Recycle", "{\"sErr\":\"error.Recycle.ItemNotExists\"}");
return;
}
var recycleId = GetRecycleId(item);
if (recycleId <= 0 || !config.HasConfig(recycleId))
{
await CallGSRouter.SendScript(connection, "Item_Recycle", "{\"sErr\":\"error.Recycle.ItemCanNotRecycle\"}");
return;
}
itemsToRecycle.Add((item, recycleId));
}
var sync = new NtfSyncPlayer();
foreach (var (item, recycleId) in itemsToRecycle)
{
var rewards = config.CalcRewards(item, recycleId);
foreach (var reward in rewards)
await GrantRewardAsync(player, sync, reward);
RemoveItem(player.InventoryManager.InventoryData, item, sync);
}
DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData);
await CallGSRouter.SendScript(connection, "Item_Recycle", "{}", sync);
}
private static int GetRecycleId(BaseGameItemInfo item)
{
if (item.ItemType == ItemTypeEnum.TYPE_WEAPON)
{
var t = GameData.WeaponData.Values.FirstOrDefault(x =>
GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level) == item.TemplateId);
return t?.RecycleID ?? 0;
}
if (item.ItemType == ItemTypeEnum.TYPE_SUPPORT)
{
var t = GameData.SupportCardData.FirstOrDefault(x => x.TemplateId == item.TemplateId);
return t?.RecycleID ?? 0;
}
return 0;
}
private static void RemoveItem(InventoryData inventory, BaseGameItemInfo item, NtfSyncPlayer sync)
{
var removed = item.ToProto();
removed.Count = 0;
sync.Items.Add(removed);
if (item.ItemType == ItemTypeEnum.TYPE_WEAPON)
inventory.Weapons.Remove(item.UniqueId);
else
inventory.SupportCards.Remove(item.UniqueId);
}
private static async Task GrantRewardAsync(GameServer.Game.Player.PlayerInstance player, NtfSyncPlayer sync, IReadOnlyList<uint> reward)
{
if (reward.Count < 5) return;
var itemType = (ItemTypeEnum)reward[0];
var detail = reward[1];
var particular = reward[2];
var level = reward[3];
var count = Math.Max(1u, reward[4]);
switch (itemType)
{
case ItemTypeEnum.TYPE_SUPPLIES:
{
var templateId = (uint)GameResourceTemplateId.FromGdpl(reward[0], detail, particular, level);
if (!GameData.SuppliesData.TryGetValue(templateId, out var supplies)) break;
var item = await player.InventoryManager.AddSuppliesItem(supplies, count, sendPacket: false);
if (item != null) sync.Items.Add(item.ToProto());
break;
}
}
}
}
internal sealed class RecycleConfig
{
private readonly Dictionary<int, RecycleEntry> _entries;
private readonly List<SupplyTemplate> _weaponSupplies;
private readonly List<SupplyTemplate> _supportSupplies;
private readonly Dictionary<int, ulong> _weaponLevelExp;
private readonly Dictionary<int, ulong> _supportLevelExp;
private readonly Dictionary<int, ulong> _weaponLevelExpSsr;
private readonly Dictionary<int, ulong> _supportLevelExpSsr;
private RecycleConfig(
Dictionary<int, RecycleEntry> entries,
List<SupplyTemplate> weaponSupplies,
List<SupplyTemplate> supportSupplies,
Dictionary<int, ulong> weaponLevelExp,
Dictionary<int, ulong> weaponLevelExpSsr,
Dictionary<int, ulong> supportLevelExp,
Dictionary<int, ulong> supportLevelExpSsr)
{
_entries = entries;
_weaponSupplies = weaponSupplies;
_supportSupplies = supportSupplies;
_weaponLevelExp = weaponLevelExp;
_weaponLevelExpSsr = weaponLevelExpSsr;
_supportLevelExp = supportLevelExp;
_supportLevelExpSsr = supportLevelExpSsr;
}
public static RecycleConfig Load()
{
var entries = new Dictionary<int, RecycleEntry>();
foreach (var row in GameData.RecycleData.Values)
{
var fixedRewards = ParseRewards(row.RecycleReward);
var recycleBase = GetUInt(row.RecycleBase);
var recycleRatio = GetDecimal(row.RecycleRatio);
entries[row.ID] = new RecycleEntry(fixedRewards, recycleBase, recycleRatio);
}
var weaponSupplies = new List<SupplyTemplate>();
var supportSupplies = new List<SupplyTemplate>();
foreach (var s in GameData.AllSuppliesData)
{
if (s.ProvideExp == 0) continue;
if (s.Genre == 5 && s.Detail == 2)
weaponSupplies.Add(new SupplyTemplate(s.Genre, s.Detail, s.Particular, s.Level, s.ProvideExp));
else if (s.Genre == 5 && s.Detail == 3)
supportSupplies.Add(new SupplyTemplate(s.Genre, s.Detail, s.Particular, s.Level, s.ProvideExp));
}
weaponSupplies.Sort((a, b) => b.ProvideExp.CompareTo(a.ProvideExp));
supportSupplies.Sort((a, b) => b.ProvideExp.CompareTo(a.ProvideExp));
var weaponLevelExp = BuildLevelExpTable(GameData.UpgradeExpData.Values.Select(x => (x.Lv, x.WeaponNeedExp)));
var weaponLevelExpSsr = BuildLevelExpTable(GameData.UpgradeExpData.Values.Select(x => (x.Lv, x.SSRWeaponNeedExp)));
var supportLevelExp = BuildLevelExpTable(GameData.UpgradeExpData.Values.Select(x => (x.Lv, x.SusNeedExp)));
var supportLevelExpSsr = BuildLevelExpTable(GameData.UpgradeExpData.Values.Select(x => (x.Lv, x.SSRSusNeedExp)));
return new RecycleConfig(entries, weaponSupplies, supportSupplies, weaponLevelExp, weaponLevelExpSsr, supportLevelExp, supportLevelExpSsr);
}
public bool HasConfig(int recycleId) => _entries.ContainsKey(recycleId);
public List<IReadOnlyList<uint>> CalcRewards(BaseGameItemInfo item, int recycleId)
{
if (!_entries.TryGetValue(recycleId, out var entry))
return [];
var rewards = new List<IReadOnlyList<uint>>(entry.FixedRewards);
var expRewards = CalcExpRewards(item, entry);
rewards.AddRange(expRewards);
return rewards;
}
private List<IReadOnlyList<uint>> CalcExpRewards(BaseGameItemInfo item, RecycleEntry entry)
{
if (entry.RecycleRatio == 0) return [];
List<SupplyTemplate> supplies;
Dictionary<int, ulong> levelExp;
if (item.ItemType == ItemTypeEnum.TYPE_WEAPON)
{
supplies = _weaponSupplies;
var color = GetItemColor(item);
levelExp = color == 5 ? _weaponLevelExpSsr : _weaponLevelExp;
}
else if (item.ItemType == ItemTypeEnum.TYPE_SUPPORT)
{
supplies = _supportSupplies;
var color = GetItemColor(item);
levelExp = color == 5 ? _supportLevelExpSsr : _supportLevelExp;
}
else
{
return [];
}
var baseExp = (ulong)entry.RecycleBase;
var levelAccum = levelExp.GetValueOrDefault((int)item.Level);
var totalExp = (ulong)Math.Floor((baseExp + levelAccum + item.Exp) * (double)entry.RecycleRatio);
if (totalExp == 0 || supplies.Count == 0) return [];
var rewards = new List<IReadOnlyList<uint>>();
var remaining = totalExp;
foreach (var supply in supplies)
{
if (remaining == 0) break;
var count = remaining / supply.ProvideExp;
if (count == 0) continue;
remaining -= count * supply.ProvideExp;
rewards.Add([supply.Genre, supply.Detail, supply.Particular, supply.Level, (uint)Math.Min(count, 99999)]);
}
return rewards;
}
private static List<IReadOnlyList<uint>> ParseRewards(JToken? token)
{
if (token == null) return [];
if (token is JArray outerArray)
{
var rewards = new List<IReadOnlyList<uint>>();
foreach (var element in outerArray)
{
if (element is JArray inner && inner.Count >= 4)
{
var reward = inner.Select(x => x.Value<uint>()).ToArray();
if (reward.Length < 5)
reward = [.. reward, 1];
rewards.Add(reward);
}
}
return rewards;
}
return [];
}
private static int GetItemColor(BaseGameItemInfo item)
{
if (item.ItemType == ItemTypeEnum.TYPE_WEAPON)
{
var t = GameData.WeaponData.Values.FirstOrDefault(x =>
GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level) == item.TemplateId);
return t?.Color ?? 0;
}
if (item.ItemType == ItemTypeEnum.TYPE_SUPPORT)
{
var t = GameData.SupportCardData.FirstOrDefault(x => x.TemplateId == item.TemplateId);
return (int)(t?.Color ?? 0);
}
return 0;
}
private static Dictionary<int, ulong> BuildLevelExpTable(IEnumerable<(int Lv, uint NeedExp)> source)
{
var table = new Dictionary<int, ulong>();
ulong accumulated = 0;
foreach (var (lv, needExp) in source.OrderBy(x => x.Lv))
{
table[lv] = accumulated;
accumulated += needExp;
}
return table;
}
private static uint GetUInt(JToken? token) => token?.Type switch
{
JTokenType.Integer => token.Value<uint>(),
JTokenType.Float => (uint)Math.Max(0, token.Value<decimal>()),
JTokenType.String when uint.TryParse(token.Value<string>(), out var r) => r,
_ => 0
};
private static decimal GetDecimal(JToken? token) => token?.Type switch
{
JTokenType.Integer => token.Value<decimal>(),
JTokenType.Float => token.Value<decimal>(),
JTokenType.String when decimal.TryParse(token.Value<string>(), out var r) => r,
_ => 0m
};
}
internal readonly record struct RecycleEntry(
List<IReadOnlyList<uint>> FixedRewards,
uint RecycleBase,
decimal RecycleRatio);
internal readonly record struct SupplyTemplate(uint Genre, uint Detail, uint Particular, uint Level, uint ProvideExp);
internal sealed class ItemRecycleParam
{
[JsonPropertyName("tbItems")]
public List<int> TbItems { get; set; } = [];
}

View File

@@ -0,0 +1,183 @@
using MikuSB.Data;
using MikuSB.Database;
using MikuSB.Database.Inventory;
using MikuSB.Proto;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MikuSB.GameServer.Server.CallGS.Handlers.Weapon;
[CallGSApi("Weapon_OneKeyToMax")]
public class Weapon_OneKeyToMax : ICallGSHandler
{
private const uint MaxBreak = 6;
public async Task Handle(Connection connection, string param, ushort seqNo)
{
var player = connection.Player!;
var req = JsonSerializer.Deserialize<OneKeyToMaxParam>(param);
if (req == null || req.Id <= 0 || req.TbBreakUpgradeMat == null || req.TbBreakUpgradeMat.Count == 0)
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"error.BadParam\"}");
return;
}
var weapon = player.InventoryManager.GetWeaponItem((uint)req.Id);
if (weapon == null)
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"error.BadParam\"}");
return;
}
var config = WeaponUpgradeConfig.Load();
if (!config.TryGetWeaponTemplate(weapon.TemplateId, out var targetTemplate))
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"error.BadParam\"}");
return;
}
var inventory = player.InventoryManager.InventoryData;
var equippedWeaponIds = player.CharacterManager.CharacterData.Characters
.Select(x => x.WeaponUniqueId)
.Where(x => x != 0)
.ToHashSet();
// Validate all materials upfront before making any changes
foreach (var stage in req.TbBreakUpgradeMat)
{
if (stage == null || stage.Count < 3) continue;
var matList = stage[1].Deserialize<List<List<int>>>();
if (matList == null) continue;
foreach (var entry in matList)
{
if (entry == null || entry.Count < 2) continue;
var itemId = (uint)Math.Max(0, entry[0]);
var count = (uint)Math.Max(0, entry[1]);
if (itemId == 0 || count == 0) continue;
if (itemId == weapon.UniqueId)
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"tip.material_not_enough\"}");
return;
}
var material = FindInventoryItem(inventory, itemId);
if (material == null || material.ItemCount < count)
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"tip.material_not_enough\"}");
return;
}
if (material is GameWeaponInfo materialWeapon &&
(materialWeapon.EquipAvatarId != 0 || equippedWeaponIds.Contains(materialWeapon.UniqueId)))
{
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax", "{\"sErr\":\"tip.material_not_enough\"}");
return;
}
}
}
var syncItems = new List<Item>();
var weaponLevel = weapon.Level == 0 ? 1u : weapon.Level;
weapon.Level = weaponLevel;
// Process each break stage
foreach (var stage in req.TbBreakUpgradeMat)
{
if (stage == null || stage.Count < 3) continue;
var matList = stage[1].Deserialize<List<List<int>>>();
var doBreak = stage[2].GetInt32() == 1;
if (matList != null && matList.Count > 0)
{
// Aggregate materials, skip duplicates
var materialsUsed = new Dictionary<uint, uint>();
foreach (var entry in matList)
{
if (entry == null || entry.Count < 2) continue;
var itemId = (uint)Math.Max(0, entry[0]);
var count = (uint)Math.Max(0, entry[1]);
if (itemId == 0 || count == 0) continue;
materialsUsed[itemId] = materialsUsed.GetValueOrDefault(itemId) + count;
}
ulong totalExp = 0;
foreach (var (itemId, count) in materialsUsed)
{
var material = FindInventoryItem(inventory, itemId)!;
if (config.TryGetMaterialGain(material, out var gainExp))
totalExp += gainExp * count;
}
// Consume materials
foreach (var (itemId, count) in materialsUsed)
{
var material = FindInventoryItem(inventory, itemId)!;
material.ItemCount -= count;
if (material.ItemCount == 0)
{
RemoveInventoryItem(inventory, itemId);
var proto = material.ToProto();
proto.Count = 0;
syncItems.Add(proto);
}
else
{
syncItems.Add(material.ToProto());
}
}
// Apply exp to weapon
if (totalExp > 0)
{
var maxLevel = config.GetWeaponMaxLevel(targetTemplate.BreakLimitId, weapon.Break);
var (newLevel, newExp) = config.ApplyWeaponExp(weapon.Level, weapon.Exp, totalExp, targetTemplate.Color, maxLevel);
weapon.Level = newLevel;
weapon.Exp = newExp;
}
}
// Perform break
if (doBreak && weapon.Break < MaxBreak)
weapon.Break++;
}
syncItems.Add(weapon.ToProto());
DatabaseHelper.SaveDatabaseType(inventory);
var finalMaxLevel = config.GetWeaponMaxLevel(targetTemplate.BreakLimitId, weapon.Break);
var bMaxUnlock = finalMaxLevel > 0 && weapon.Level >= finalMaxLevel;
var sync = new NtfSyncPlayer();
sync.Items.AddRange(syncItems);
await CallGSRouter.SendScript(connection, "Weapon_OneKeyToMax",
$"{{\"bMaxUnLock\":{(bMaxUnlock ? "true" : "false")}}}", sync);
}
private static BaseGameItemInfo? FindInventoryItem(InventoryData inventory, uint itemId)
{
if (inventory.Weapons.TryGetValue(itemId, out var weapon)) return weapon;
if (inventory.Skins.TryGetValue(itemId, out var skin)) return skin;
if (inventory.Items.TryGetValue(itemId, out var item)) return item;
return null;
}
private static void RemoveInventoryItem(InventoryData inventory, uint itemId)
{
inventory.Weapons.Remove(itemId);
inventory.Skins.Remove(itemId);
inventory.Items.Remove(itemId);
}
}
internal sealed class OneKeyToMaxParam
{
[JsonPropertyName("Id")]
public int Id { get; set; }
[JsonPropertyName("tbBreakUpgradeMat")]
public List<List<System.Text.Json.JsonElement>> TbBreakUpgradeMat { get; set; } = [];
}

View File

@@ -258,9 +258,9 @@ internal sealed class WeaponUpgradeConfig
var weaponTemplates = GameData.WeaponData.Values.ToDictionary( var weaponTemplates = GameData.WeaponData.Values.ToDictionary(
x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level), x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level),
x => new MaterialTemplate(x.Color, x.ProvideExp, x.ConsumeGold, x.RecycleID, x.BreakLimitID)); x => new MaterialTemplate(x.Color, x.ProvideExp, x.ConsumeGold, x.RecycleID, x.BreakLimitID));
var suppliesTemplates = GameData.SuppliesData.Values.ToDictionary( var suppliesTemplates = GameData.AllSuppliesData
x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level), .GroupBy(x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level))
x => new MaterialTemplate(x.Color, x.ProvideExp, x.ConsumeGold, 0, 0)); .ToDictionary(g => g.Key, g => new MaterialTemplate(g.First().Color, g.First().ProvideExp, g.First().ConsumeGold, 0, 0));
return new WeaponUpgradeConfig(normalExp, ssrExp, breakLimits, recycleById, weaponTemplates, suppliesTemplates); return new WeaponUpgradeConfig(normalExp, ssrExp, breakLimits, recycleById, weaponTemplates, suppliesTemplates);
} }
@@ -271,6 +271,24 @@ internal sealed class WeaponUpgradeConfig
public bool TryGetSuppliesTemplate(ulong templateId, out MaterialTemplate template) => public bool TryGetSuppliesTemplate(ulong templateId, out MaterialTemplate template) =>
_suppliesTemplates.TryGetValue(templateId, out template!); _suppliesTemplates.TryGetValue(templateId, out template!);
public bool TryGetMaterialGain(BaseGameItemInfo item, out ulong exp)
{
exp = 0;
if (TryGetWeaponTemplate(item.TemplateId, out var weaponTemplate))
{
exp = weaponTemplate.ProvideExp;
if (item is GameWeaponInfo weapon && weapon.Level > 1)
exp += GetWeaponRecycleExp(weaponTemplate, weapon.Level);
return true;
}
if (TryGetSuppliesTemplate(item.TemplateId, out var suppliesTemplate))
{
exp = suppliesTemplate.ProvideExp;
return true;
}
return false;
}
public ulong GetWeaponRecycleExp(MaterialTemplate template, uint level) public ulong GetWeaponRecycleExp(MaterialTemplate template, uint level)
{ {
if (template.RecycleId <= 0 || !_recycleById.TryGetValue(template.RecycleId, out var recycle)) if (template.RecycleId <= 0 || !_recycleById.TryGetValue(template.RecycleId, out var recycle))

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Kei-Luna, Naruse and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -87,6 +87,9 @@ dotnet build
- [Naruse](https://github.com/DevilProMT) - [Naruse](https://github.com/DevilProMT)
- [Kei-Luna](https://github.com/Kei-Luna) - [Kei-Luna](https://github.com/Kei-Luna)
## License
This repository is licensed under the [MIT License](LICENSE).
## Notes on use ## Notes on use
This software is intended for research and testing purposes in a local environment. This software is intended for research and testing purposes in a local environment.
It is not intended for unauthorized access to, interference with, or commercial use of official services. It is not intended for unauthorized access to, interference with, or commercial use of official services.

View File

@@ -53,40 +53,42 @@ dotnet build
## 機能一覧 ## 機能一覧
* [x] ログインと基本的なアカウント入場 * [x] ログインシステム
* [x] プレイヤーデータの読み込み * [x] インベントリ
* [x] 所持品の読み込み * [x] 戦闘
* [x] キャラクターの読み込み * [x] キャラクター
* [x] スキンの読み込み * [x] GMメニュー
* [x] 武器の読み込み * [x] 武器
* [x] ロビー表示キャラクターの変更 * [x] 後方支援
* [x] キャラクタースキンの変更 * [x] アポカリプス
* [x] キャラクタースキン形態の変更 * [x] アウクトゥス
* [x] 武器の付け替え * [x] 神格神経
* [x] 武器の強化 * [x] キャラスキン
* [x] プレイヤー名の変更 * [x] 武器スキン
* [x] 現在対応済みロビー状態の基本保存 * [x] ガチャ
* [✓] メイン章のステージ入場と関連フロ * [x] メインストーリーチャプタ
* [✓] デイリーのステージ入場と関連フロー * [x] 社員寮
* [✓] 基本的なプレイヤー設定同期 * [x] ミニゲーム
* [✓] 基本的なプロフィール同期 * [x] 地下清掃
* [✓] イベント関連リクエスト * [x] ニューロンシュミレーション
* [✓] 実績関連リクエスト * [x] 戦術評価
* [✓] 編成関連リクエスト * [x] エピソードストーリー
* [✓] プレビュー関連リクエスト * [x] ルーチン作戦
* [✓] 一部のショップ関連リクエスト * [x] 逆説迷宮
* [ ] 完全な戦闘フロー * [x] ロビー画面の編集
* [ ] ミッション / クエスト進行 * [✓] 惑星開拓
* [ ] ガチャ / 募集システム * [✓] 夢の絵本
* [ ] 完全なショップ挙動 * [ ] ショップ
* [ ] マルチプレイシステム * [ ] マルチプレイシステム
* [ ] 基地 / 宿舎システム * [ ] 名誉紛争
* [ ] クライアント API 全体の対応
## 貢献者 ## 貢献者
- [Naruse](https://github.com/DevilProMT) - [Naruse](https://github.com/DevilProMT)
- [Kei-Luna](https://github.com/Kei-Luna) - [Kei-Luna](https://github.com/Kei-Luna)
## ライセンス
このリポジトリは [MIT License](LICENSE) の下で公開されています。
## 利用上の注意 ## 利用上の注意
本ソフトウェアはローカル環境での研究・検証用途を想定しています。 本ソフトウェアはローカル環境での研究・検証用途を想定しています。

View File

@@ -1 +1 @@
v=4.5 v=4.6