Half the handlers were still answering with a bare 0 or -1 even though the whole retcode table is generated. Values on the wire are unchanged, they just read properly now. Same for the item hint reasons. ActionReasonType is a generated enum and we were redeclaring six of its members as private consts in four different files.
591 lines
23 KiB
C#
591 lines
23 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Yuanshen.Common.Data.Excel;
|
|
using Yuanshen.Gameserver.Database;
|
|
using Yuanshen.Gameserver.Network.Session;
|
|
using Yuanshen.Protocol;
|
|
|
|
namespace Yuanshen.Gameserver.Game;
|
|
|
|
internal sealed class GmCommandService
|
|
{
|
|
private const int TeamSize = 4;
|
|
private const uint MoraItemId = 202;
|
|
private const uint MaxConstellation = 6;
|
|
|
|
private readonly Player _player;
|
|
private readonly GameState _state;
|
|
private readonly SceneManager _scene;
|
|
private readonly NetSession _session;
|
|
private readonly PlayerPersistence _persistence;
|
|
private readonly ExcelTableCollection _excel;
|
|
private readonly WorldSpawner _worldSpawner;
|
|
private readonly GachaBannerTable _banners;
|
|
private readonly ILogger _logger;
|
|
|
|
public GmCommandService(Player player, GameState state, SceneManager scene, NetSession session,
|
|
PlayerPersistence persistence, ExcelTableCollection excel, WorldSpawner worldSpawner,
|
|
GachaBannerTable banners, ILogger<GmCommandService> logger)
|
|
{
|
|
_player = player;
|
|
_state = state;
|
|
_scene = scene;
|
|
_session = session;
|
|
_persistence = persistence;
|
|
_excel = excel;
|
|
_worldSpawner = worldSpawner;
|
|
_banners = banners;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async ValueTask<IReadOnlyList<string>> ExecuteAsync(string input)
|
|
{
|
|
string[] parts = input.TrimStart('/').Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
if (parts.Length == 0)
|
|
return Help();
|
|
|
|
string command = parts[0].ToLowerInvariant();
|
|
string[] args = parts[1..];
|
|
|
|
try
|
|
{
|
|
List<string> reply = command switch
|
|
{
|
|
"help" => Help(),
|
|
"tp" => await TeleportAsync(args),
|
|
"spawn" => await SpawnAsync(args),
|
|
"clear" => await ClearAsync(),
|
|
"respawn" => await RespawnAsync(),
|
|
"give" => await GiveAsync(args),
|
|
"heal" => await HealAsync(),
|
|
"level" => await LevelAsync(args),
|
|
"asc" => await AscendAsync(args),
|
|
"talent" => await TalentAsync(args),
|
|
"const" => await ConstellationAsync(args),
|
|
"wlevel" => await WeaponLevelAsync(args),
|
|
"refine" => await RefineAsync(args),
|
|
"wish" => await WishAsync(args),
|
|
"avatar" => await AvatarAsync(args),
|
|
"team" => await TeamAsync(args),
|
|
"monster" => await SpawnAsync(args),
|
|
"trifle" => await GiveAsync(args),
|
|
"kill" => await KillAsync(args),
|
|
"item" => await ItemAsync(args),
|
|
"equip" => await EquipAsync(args),
|
|
"break" => await AscendAsync(args),
|
|
"skill" => await SkillAsync(args),
|
|
"goto" => await TeleportAsync(args),
|
|
"scoin" => await ScoinAsync(args),
|
|
"hcoin" => await HcoinAsync(args),
|
|
"player" => await PlayerAsync(args),
|
|
"quest" => Unsupported("quest"),
|
|
"dungeon" => Unsupported("dungeon"),
|
|
"jump" => Unsupported("jump"),
|
|
_ => new List<string> { $"unknown command /{command} - try /help" }
|
|
};
|
|
|
|
_logger.LogInformation("GM /{command} -> {result}", command, string.Join(" | ", reply));
|
|
return reply;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogWarning("GM /{command} failed: {message}", command, exception.Message);
|
|
return new List<string> { $"/{command} failed: {exception.Message}" };
|
|
}
|
|
}
|
|
|
|
private static List<string> Help() => new()
|
|
{
|
|
"commands: /tp x y z, /spawn id [n], /clear, /respawn, /give id [n], /heal,",
|
|
"/level n, /asc n, /talent n, /const n, /wlevel n, /refine n, /wish [n] (grants fates),",
|
|
"team: avatar add id, team add id, team remove id, team list",
|
|
"mihoyo syntax: item add id [n], equip add id [lvl] [promote], level n, break n,",
|
|
"skill slot n, talent unlock all, goto x y z, scoin n, hcoin n, player level n"
|
|
};
|
|
|
|
private async ValueTask<List<string>> AvatarAsync(string[] args)
|
|
{
|
|
if (args.Length >= 2 && args[0].Equals("add", StringComparison.OrdinalIgnoreCase))
|
|
return await TeamAddAsync(args[1]);
|
|
|
|
return new List<string> { "usage: avatar add avatarId" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> TeamAsync(string[] args)
|
|
{
|
|
if (args.Length == 0)
|
|
return TeamList();
|
|
|
|
string action = args[0].ToLowerInvariant();
|
|
|
|
if (action == "list")
|
|
return TeamList();
|
|
|
|
if (action == "reset")
|
|
return await TeamResetAsync();
|
|
|
|
if (args.Length < 2)
|
|
return new List<string> { "usage: team add|remove avatarId, team list, team reset" };
|
|
|
|
return action switch
|
|
{
|
|
"add" => await TeamAddAsync(args[1]),
|
|
"remove" => await TeamRemoveAsync(args[1]),
|
|
_ => new List<string> { "usage: team add|remove avatarId, team list, team reset" }
|
|
};
|
|
}
|
|
|
|
private List<string> TeamList()
|
|
=> _player.TeamAvatars
|
|
.Select(avatar => $"{avatar.AvatarId} guid={avatar.Guid}")
|
|
.DefaultIfEmpty("team is empty")
|
|
.ToList();
|
|
|
|
private async ValueTask<List<string>> TeamAddAsync(string rawId)
|
|
{
|
|
if (!uint.TryParse(rawId, out uint avatarId))
|
|
return new List<string> { "usage: team add avatarId" };
|
|
|
|
GameAvatar? avatar = _player.Avatars.FirstOrDefault(candidate => candidate.AvatarId == avatarId);
|
|
|
|
if (avatar is null)
|
|
{
|
|
List<string> granted = await GrantAvatarAsync(avatarId, avatarId);
|
|
avatar = _player.Avatars.FirstOrDefault(candidate => candidate.AvatarId == avatarId);
|
|
if (avatar is null)
|
|
return granted;
|
|
}
|
|
|
|
if (_player.TeamAvatarGuids.Contains(avatar.Guid))
|
|
return new List<string> { $"avatar {avatarId} is already in the team" };
|
|
|
|
if (_player.TeamAvatarGuids.Count >= TeamSize)
|
|
return new List<string> { $"team is full, remove one first (max {TeamSize})" };
|
|
|
|
List<ulong> team = _player.TeamAvatarGuids.Append(avatar.Guid).ToList();
|
|
await _scene.SetupTeamAsync(_player.CurrentTeamId, team, _player.CurrentAvatarGuid);
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"added avatar {avatarId} to the team" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> TeamResetAsync()
|
|
{
|
|
_player.ResetTeam();
|
|
await _scene.SetupTeamAsync(_player.CurrentTeamId, _player.TeamAvatarGuids.ToList(), _player.CurrentAvatarGuid);
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { "team reset to the default avatar" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> TeamRemoveAsync(string rawId)
|
|
{
|
|
if (!uint.TryParse(rawId, out uint avatarId))
|
|
return new List<string> { "usage: team remove avatarId" };
|
|
|
|
GameAvatar? avatar = _player.Avatars.FirstOrDefault(candidate => candidate.AvatarId == avatarId);
|
|
if (avatar is null || !_player.TeamAvatarGuids.Contains(avatar.Guid))
|
|
return new List<string> { $"avatar {avatarId} is not in the team" };
|
|
|
|
if (_player.TeamAvatarGuids.Count <= 1)
|
|
return new List<string> { "cannot remove the last team member" };
|
|
|
|
List<ulong> team = _player.TeamAvatarGuids.Where(guid => guid != avatar.Guid).ToList();
|
|
ulong active = _player.CurrentAvatarGuid == avatar.Guid ? team[0] : _player.CurrentAvatarGuid;
|
|
|
|
await _scene.SetupTeamAsync(_player.CurrentTeamId, team, active);
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"removed avatar {avatarId} from the team" };
|
|
}
|
|
|
|
private static List<string> Unsupported(string verb)
|
|
=> new() { $"{verb} is not implemented on this server yet" };
|
|
|
|
private async ValueTask<List<string>> ItemAsync(string[] args)
|
|
{
|
|
if (args.Length >= 2 && args[0].Equals("add", StringComparison.OrdinalIgnoreCase))
|
|
return await GiveAsync(args[1..]);
|
|
|
|
return new List<string> { "usage: item add itemId [count]" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> ScoinAsync(string[] args)
|
|
{
|
|
if (args.Length < 1 || !uint.TryParse(args[0], out uint amount))
|
|
return new List<string> { "usage: scoin amount" };
|
|
|
|
_player.Scoin += amount;
|
|
await _session.NotifyAsync(CmdType.PlayerPropNotify, _player.BuildCurrencyProps());
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"mora = {_player.Scoin}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> HcoinAsync(string[] args)
|
|
{
|
|
if (args.Length < 1 || !uint.TryParse(args[0], out uint amount))
|
|
return new List<string> { "usage: hcoin amount" };
|
|
|
|
_player.Hcoin += amount;
|
|
await _session.NotifyAsync(CmdType.PlayerPropNotify, _player.BuildCurrencyProps());
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"primogems = {_player.Hcoin}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> PlayerAsync(string[] args)
|
|
{
|
|
if (args.Length < 2 || !args[0].Equals("level", StringComparison.OrdinalIgnoreCase)
|
|
|| !uint.TryParse(args[1], out uint level))
|
|
{
|
|
return new List<string> { "usage: player level n" };
|
|
}
|
|
|
|
level = Math.Clamp(level, 1u, 60u);
|
|
|
|
await _session.NotifyAsync(CmdType.PlayerPropNotify, new PlayerPropNotify
|
|
{
|
|
PropMap = { { 4001u, new PropValue { Type = 4001, Ival = level, Val = level } } }
|
|
});
|
|
|
|
return new List<string> { $"player level = {level} (display only, not persisted)" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> SkillAsync(string[] args)
|
|
{
|
|
if (args.Length < 2 || !uint.TryParse(args[1], out uint level))
|
|
return new List<string> { "usage: skill slot level" };
|
|
|
|
return await TalentAsync(new[] { level.ToString() });
|
|
}
|
|
|
|
private async ValueTask<List<string>> EquipAsync(string[] args)
|
|
{
|
|
if (args.Length < 2 || !args[0].Equals("add", StringComparison.OrdinalIgnoreCase)
|
|
|| !uint.TryParse(args[1], out uint itemId))
|
|
{
|
|
return new List<string> { "usage: equip add itemId [level] [promote]" };
|
|
}
|
|
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
if (avatar is null)
|
|
return new List<string> { "no active avatar" };
|
|
|
|
uint level = args.Length > 2 && uint.TryParse(args[2], out uint parsedLevel) ? parsedLevel : 90u;
|
|
uint promote = args.Length > 3 && uint.TryParse(args[3], out uint parsedPromote) ? parsedPromote : 0u;
|
|
|
|
bool isWeapon = _excel.GetTable(ExcelType.Weapon).Items.OfType<WeaponExcel>().Any(weapon => weapon.Id == itemId);
|
|
|
|
if (isWeapon)
|
|
{
|
|
GameWeapon weapon = _player.CreateWeapon(itemId);
|
|
weapon.Level = Math.Clamp(level, 1u, 90u);
|
|
weapon.PromoteLevel = Math.Clamp(promote, 0u, 6u);
|
|
avatar.WeaponGuid = weapon.Guid;
|
|
|
|
await _session.NotifyAsync(CmdType.StoreItemChangeNotify, new StoreItemChangeNotify
|
|
{
|
|
StoreType = StoreType.StorePack,
|
|
ItemList = { weapon.AsItem() }
|
|
});
|
|
|
|
await _session.NotifyAsync(CmdType.AvatarEquipChangeNotify, new AvatarEquipChangeNotify
|
|
{
|
|
AvatarGuid = avatar.Guid,
|
|
EquipType = 6,
|
|
ItemId = weapon.ItemId,
|
|
EquipGuid = weapon.Guid,
|
|
Weapon = weapon.AsSceneWeapon(avatar.WeaponEntityId)
|
|
});
|
|
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"equipped weapon {itemId} lv{weapon.Level} promote {weapon.PromoteLevel}" };
|
|
}
|
|
|
|
GameReliquary? reliquary = _player.FindReliquaryByItemId(itemId);
|
|
if (reliquary is null)
|
|
return new List<string> { $"item {itemId} is neither a known weapon nor a reliquary" };
|
|
|
|
reliquary.Level = Math.Max(level, 1u);
|
|
avatar.ReliquaryGuids[reliquary.EquipType] = reliquary.Guid;
|
|
|
|
await _session.NotifyAsync(CmdType.AvatarEquipChangeNotify, new AvatarEquipChangeNotify
|
|
{
|
|
AvatarGuid = avatar.Guid,
|
|
EquipType = reliquary.EquipType,
|
|
ItemId = reliquary.ItemId,
|
|
EquipGuid = reliquary.Guid,
|
|
Reliquary = reliquary.AsSceneReliquary()
|
|
});
|
|
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { $"equipped reliquary {itemId} lv{reliquary.Level}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> KillAsync(string[] args)
|
|
{
|
|
if (args.Length >= 1 && args[0].Equals("monster", StringComparison.OrdinalIgnoreCase))
|
|
return await ClearAsync();
|
|
|
|
return new List<string> { "usage: kill monster all" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> TeleportAsync(string[] args)
|
|
{
|
|
if (args.Length < 3 || !float.TryParse(args[0], out float x) || !float.TryParse(args[1], out float y) || !float.TryParse(args[2], out float z))
|
|
return new List<string> { "usage: /tp x y z" };
|
|
|
|
await _scene.TeleportAsync(new Vector { X = x, Y = y, Z = z });
|
|
return new List<string> { $"teleported to ({x}, {y}, {z})" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> SpawnAsync(string[] args)
|
|
{
|
|
if (args.Length < 1 || !uint.TryParse(args[0], out uint monsterId))
|
|
return new List<string> { "usage: /spawn monsterId [count]" };
|
|
|
|
uint count = args.Length > 1 && uint.TryParse(args[1], out uint parsed) ? Math.Min(parsed, 50u) : 1u;
|
|
uint spawned = 0;
|
|
|
|
for (uint i = 0; i < count; i++)
|
|
{
|
|
double angle = Math.PI * 2 * i / Math.Max(count, 1u);
|
|
Vector pos = new()
|
|
{
|
|
X = _state.CurrentPos.X + (float)(Math.Cos(angle) * 4),
|
|
Y = _state.CurrentPos.Y,
|
|
Z = _state.CurrentPos.Z + (float)(Math.Sin(angle) * 4)
|
|
};
|
|
|
|
GameMonster monster = _player.SpawnMonster(monsterId, pos, 30, 1000f, 100f, 500f);
|
|
await _scene.AppearMonsterAsync(monster);
|
|
spawned++;
|
|
}
|
|
|
|
return new List<string> { $"spawned {spawned}x monster {monsterId}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> ClearAsync()
|
|
{
|
|
await _scene.ClearMonstersAsync();
|
|
return new List<string> { "cleared all monsters" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> RespawnAsync()
|
|
{
|
|
await _scene.ClearMonstersAsync();
|
|
await _worldSpawner.RespawnAsync(_state.CurrentPos);
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { "respawned all monsters around you" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> GiveAsync(string[] args)
|
|
{
|
|
if (args.Length < 1 || !uint.TryParse(args[0], out uint itemId))
|
|
return new List<string> { "usage: /give itemId [count]" };
|
|
|
|
uint count = args.Length > 1 && uint.TryParse(args[1], out uint parsed) ? Math.Max(parsed, 1u) : 1u;
|
|
|
|
MaterialExcel? material = _excel.GetTable(ExcelType.Material).Items.OfType<MaterialExcel>()
|
|
.FirstOrDefault(candidate => candidate.Id == itemId);
|
|
|
|
if (material is not null && material.GainedAvatarId != 0)
|
|
return await GrantAvatarAsync(material.GainedAvatarId, itemId);
|
|
|
|
bool isWeapon = _excel.GetTable(ExcelType.Weapon).Items.OfType<WeaponExcel>().Any(weapon => weapon.Id == itemId);
|
|
|
|
StoreItemChangeNotify notify = new() { StoreType = StoreType.StorePack };
|
|
|
|
if (isWeapon)
|
|
{
|
|
for (uint i = 0; i < Math.Min(count, 50u); i++)
|
|
notify.ItemList.Add(_player.CreateWeapon(itemId).AsItem());
|
|
}
|
|
else
|
|
{
|
|
_player.GiveMaterial(itemId, count);
|
|
notify.ItemList.Add(new Item
|
|
{
|
|
ItemId = itemId,
|
|
Guid = _player.MaterialGuid(itemId),
|
|
Material = new Material { Count = _player.Materials[itemId] }
|
|
});
|
|
}
|
|
|
|
await _session.NotifyAsync(CmdType.StoreItemChangeNotify, notify);
|
|
|
|
ItemAddHintNotify hint = new() { Reason = (uint)ActionReasonType.ActionReasonTrifle };
|
|
hint.ItemList.Add(new ItemHint { ItemId = itemId, Count = count, IsNew = false });
|
|
await _session.NotifyAsync(CmdType.ItemAddHintNotify, hint);
|
|
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"gave {count}x {itemId}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> GrantAvatarAsync(uint avatarId, uint cardItemId)
|
|
{
|
|
if (_player.Avatars.Any(avatar => avatar.AvatarId == avatarId))
|
|
return new List<string> { $"{cardItemId} is the card for avatar {avatarId}, already owned" };
|
|
|
|
if (!_player.HasAvatarData(avatarId))
|
|
return new List<string> { $"avatar {avatarId} is not in this build" };
|
|
|
|
GameAvatar? granted = _player.GrantAvatar(avatarId);
|
|
if (granted is null)
|
|
return new List<string> { $"avatar {avatarId} could not be created" };
|
|
|
|
await _session.NotifyAsync(CmdType.AvatarAddNotify, new AvatarAddNotify
|
|
{
|
|
Avatar = granted.AsAvatarInfo(),
|
|
IsInTeam = false
|
|
});
|
|
|
|
await _scene.RefreshAvatarDataAsync();
|
|
await _persistence.SaveAsync();
|
|
|
|
List<string> reply = new() { $"granted avatar {avatarId} - add it with: team add {avatarId}" };
|
|
if (!_player.HasAbilities(avatarId))
|
|
reply.Add("warning: no ability data, it will have no skills and may break the UI - use 'team reset' to recover");
|
|
|
|
return reply;
|
|
}
|
|
|
|
private async ValueTask<List<string>> HealAsync()
|
|
{
|
|
await _scene.HealTeamAsync();
|
|
return new List<string> { "team healed" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> LevelAsync(string[] args)
|
|
{
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
if (avatar is null || args.Length < 1 || !uint.TryParse(args[0], out uint level))
|
|
return new List<string> { "usage: /level n" };
|
|
|
|
avatar.Level = Math.Clamp(level, 1u, 90u);
|
|
await _session.NotifyAsync(CmdType.AvatarPropNotify, new AvatarPropNotify
|
|
{
|
|
AvatarGuid = avatar.Guid,
|
|
PropMap = { { 4001u, avatar.Level } }
|
|
});
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"avatar {avatar.AvatarId} level = {avatar.Level}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> AscendAsync(string[] args)
|
|
{
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
if (avatar is null || args.Length < 1 || !uint.TryParse(args[0], out uint promote))
|
|
return new List<string> { "usage: /asc n" };
|
|
|
|
avatar.PromoteLevel = Math.Clamp(promote, 0u, 6u);
|
|
await _session.NotifyAsync(CmdType.AvatarPropNotify, new AvatarPropNotify
|
|
{
|
|
AvatarGuid = avatar.Guid,
|
|
PropMap = { { 1002u, avatar.PromoteLevel } }
|
|
});
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"avatar {avatar.AvatarId} ascension = {avatar.PromoteLevel}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> TalentAsync(string[] args)
|
|
{
|
|
if (args.Length >= 1 && args[0].Equals("unlock", StringComparison.OrdinalIgnoreCase))
|
|
return await ConstellationAsync(new[] { MaxConstellation.ToString() });
|
|
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
if (avatar is null || args.Length < 1 || !uint.TryParse(args[0], out uint talent))
|
|
return new List<string> { "usage: /talent n, or talent unlock all" };
|
|
|
|
avatar.TalentLevel = Math.Clamp(talent, 1u, 15u);
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"avatar {avatar.AvatarId} talents = {avatar.TalentLevel} (reopen character screen to see)" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> ConstellationAsync(string[] args)
|
|
{
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
if (avatar is null || args.Length < 1 || !uint.TryParse(args[0], out uint constellation))
|
|
return new List<string> { "usage: /const n" };
|
|
|
|
avatar.Constellation = Math.Clamp(constellation, 0u, 6u);
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"avatar {avatar.AvatarId} constellation = {avatar.Constellation} (reopen character screen to see)" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> WeaponLevelAsync(string[] args)
|
|
{
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
GameWeapon? weapon = avatar is null ? null : _player.FindWeapon(avatar.WeaponGuid);
|
|
if (weapon is null || args.Length < 1 || !uint.TryParse(args[0], out uint level))
|
|
return new List<string> { "usage: /wlevel n" };
|
|
|
|
weapon.Level = Math.Clamp(level, 1u, 90u);
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"weapon {weapon.ItemId} level = {weapon.Level}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> RefineAsync(string[] args)
|
|
{
|
|
GameAvatar? avatar = _player.ActiveAvatar;
|
|
GameWeapon? weapon = avatar is null ? null : _player.FindWeapon(avatar.WeaponGuid);
|
|
if (weapon is null || args.Length < 1 || !uint.TryParse(args[0], out uint refine))
|
|
return new List<string> { "usage: /refine n" };
|
|
|
|
weapon.Refinement = Math.Clamp(refine, 0u, 4u);
|
|
await _scene.RefreshTeamAsync();
|
|
await _persistence.SaveAsync();
|
|
return new List<string> { $"weapon {weapon.ItemId} refinement = {weapon.Refinement + 1}" };
|
|
}
|
|
|
|
private async ValueTask<List<string>> WishAsync(string[] args)
|
|
{
|
|
uint amount = args.Length > 0 && uint.TryParse(args[0], out uint parsed) ? Math.Clamp(parsed, 1u, 9999u) : 10u;
|
|
|
|
HashSet<uint> costItems = new();
|
|
foreach (GachaBanner banner in _banners.Banners)
|
|
{
|
|
if (banner.Info.CostItemId != 0)
|
|
costItems.Add(banner.Info.CostItemId);
|
|
|
|
if (banner.Info.TenCostItemId != 0)
|
|
costItems.Add(banner.Info.TenCostItemId);
|
|
}
|
|
|
|
if (costItems.Count == 0)
|
|
return new List<string> { "no banners are shipped, nothing to grant" };
|
|
|
|
StoreItemChangeNotify notify = new() { StoreType = StoreType.StorePack };
|
|
List<string> results = new();
|
|
|
|
foreach (uint itemId in costItems.OrderBy(id => id))
|
|
{
|
|
_player.GiveMaterial(itemId, amount);
|
|
uint total = _player.Materials.GetValueOrDefault(itemId);
|
|
|
|
notify.ItemList.Add(new Item
|
|
{
|
|
ItemId = itemId,
|
|
Guid = _player.MaterialGuid(itemId),
|
|
Material = new Material { Count = total }
|
|
});
|
|
|
|
results.Add($"+{amount} x {itemId} (now {total})");
|
|
}
|
|
|
|
await _session.NotifyAsync(CmdType.StoreItemChangeNotify, notify);
|
|
await _persistence.SaveAsync();
|
|
|
|
return new List<string> { "granted wish currency - open the wish page and pull for the real animation" }
|
|
.Concat(results).ToList();
|
|
}
|
|
}
|