command yay!

This commit is contained in:
rfi 2023-11-13 19:10:02 +07:00
parent 21856d353b
commit 6c5aca3c3a
12 changed files with 335 additions and 36 deletions

View File

@ -52,6 +52,9 @@ namespace AscNet.Common.Database
public void AddStage(StageDatum stageData) public void AddStage(StageDatum stageData)
{ {
if (Stages.ContainsKey(stageData.StageId))
Stages[stageData.StageId] = stageData;
else
Stages.Add(stageData.StageId, stageData); Stages.Add(stageData.StageId, stageData);
} }

View File

@ -2452,26 +2452,7 @@ namespace AscNet.Common.MsgPack
[global::MessagePack.MessagePackObject(true)] [global::MessagePack.MessagePackObject(true)]
public class NotifyStageData public class NotifyStageData
{ {
[global::MessagePack.MessagePackObject(true)] public List<StageDatum> StageList { get; set; } = new();
public class NotifyStageDataStage
{
public UInt32 StageId { get; set; }
public Int32 StarsMark { get; set; }
public Boolean Passed { get; set; }
public Int32 PassTimesToday { get; set; }
public Int32 PassTimesTotal { get; set; }
public Int32 BuyCount { get; set; }
public Int32 Score { get; set; }
public UInt32 LastPassTime { get; set; }
public UInt32 RefreshTime { get; set; }
public UInt32 CreateTime { get; set; }
public Int32 BestRecordTime { get; set; }
public Int32 LastRecordTime { get; set; }
public List<UInt32> BestCardIds { get; set; } = new();
public List<UInt32> LastCardIds { get; set; } = new();
}
public List<NotifyStageDataStage> StageList { get; set; } = new();
} }

View File

@ -0,0 +1,132 @@
using System.Reflection;
using System.Text.RegularExpressions;
using AscNet.Logging;
namespace AscNet.GameServer.Commands
{
public abstract class Command
{
protected Session session;
protected string[] args;
public abstract string Help { get; }
/// <summary>
/// Make sure to handle me well...
/// </summary>
/// <param name="session"></param>
/// <param name="args"></param>
/// <exception cref="ArgumentException"></exception>
public Command(Session session, string[] args, bool validate = true)
{
this.session = session;
this.args = args;
string? ret = Validate();
if (ret is not null && validate)
throw new ArgumentException(ret);
}
public string? Validate()
{
List<PropertyInfo> argsProperties = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute(typeof(ArgumentAttribute)) is not null).ToList();
if (argsProperties.Count != args.Length)
return "Invalid args length!";
foreach (var argProp in argsProperties)
{
ArgumentAttribute attr = (ArgumentAttribute)argProp.GetCustomAttribute(typeof(ArgumentAttribute))!;
if (!attr.Pattern.IsMatch(args[attr.Position]))
return $"Argument {argProp.Name} is invalid!";
argProp.SetValue(this, args[attr.Position]);
}
return null;
}
public abstract void Execute();
}
[AttributeUsage(AttributeTargets.Property)]
public class ArgumentAttribute : Attribute
{
public int Position { get; }
public Regex Pattern { get; }
public string? Description { get; }
public ArgumentAttribute(int position, string pattern, string? description = null)
{
Position = position;
Pattern = new(pattern);
Description = description;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class CommandNameAttribute : Attribute
{
public string Name { get; }
public CommandNameAttribute(string name)
{
Name = name;
}
}
[CommandName("help")]
internal class HelpCommand : Command
{
public HelpCommand(Session session, string[] args, bool validate = true) : base(session, args, validate) { }
public override string Help => "Show this help.";
public override void Execute()
{
string helpText = string.Empty;
foreach (var command in CommandFactory.commands.Keys)
{
Command? cmd = CommandFactory.CreateCommand(command, session, args, false);
if (cmd is not null)
helpText += $"{command}\n\t└─{cmd.Help}\n";
}
}
}
public static class CommandFactory
{
public static readonly Dictionary<string, Type> commands = new();
internal static readonly Logger log = new(typeof(CommandFactory), LogLevel.DEBUG, LogLevel.DEBUG);
public static void LoadCommands()
{
log.LogLevelColor[LogLevel.INFO] = ConsoleColor.White;
log.Info("Loading commands...");
IEnumerable<Type> classes = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.GetCustomAttribute<CommandNameAttribute>() is not null
select t;
foreach (var command in classes)
{
CommandNameAttribute nameAttr = command.GetCustomAttribute<CommandNameAttribute>()!;
commands.Add(nameAttr.Name, command);
#if DEBUG
log.Info($"Loaded {nameAttr.Name} command");
#endif
}
log.Info("Finished loading commands");
}
public static Command? CreateCommand(string name, Session session, string[] args, bool validate = true)
{
Type? command = commands.GetValueOrDefault(name);
if (command is null)
return null;
return (Command)Activator.CreateInstance(command, new object[] { session, args, validate })!;
}
}
}

View File

@ -0,0 +1,73 @@
using AscNet.Common.MsgPack;
using AscNet.Table.share.fuben;
namespace AscNet.GameServer.Commands
{
[CommandName("stage")]
internal class StageCommand : Command
{
public StageCommand(Session session, string[] args, bool validate = true) : base(session, args, validate) { }
[Argument(0, @"^[0-9]+$|^all$", "The target stage, value is stage id or 'all'")]
string TargetStage { get; set; } = string.Empty;
public override string Help => "Modify the stage completion status of the account.";
public override void Execute()
{
if (TargetStage == "all")
{
session.stage.Stages.Clear();
foreach (var stageData in StageTableReader.Instance.All)
{
session.stage.Stages.Add(stageData.StageId, new()
{
StageId = stageData.StageId,
StarsMark = 7,
Passed = true,
PassTimesToday = 0,
PassTimesTotal = 1,
BuyCount = 0,
Score = 0,
LastPassTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
RefreshTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
CreateTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
BestRecordTime = 0,
LastRecordTime = 0,
BestCardIds = new List<long> { 1021001 },
LastCardIds = new List<long> { 1021001 }
});
}
session.SendPush(new NotifyStageData() { StageList = session.stage.Stages.Select(x => x.Value).ToList() });
}
else
{
StageTable? stageData = StageTableReader.Instance.FromStageId(int.Parse(TargetStage));
if (stageData is not null && !session.stage.Stages.ContainsKey(stageData.StageId))
{
StageDatum stage = new()
{
StageId = stageData.StageId,
StarsMark = 7,
Passed = true,
PassTimesToday = 0,
PassTimesTotal = 1,
BuyCount = 0,
Score = 0,
LastPassTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
RefreshTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
CreateTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
BestRecordTime = 0,
LastRecordTime = 0,
BestCardIds = new List<long> { 1021001 },
LastCardIds = new List<long> { 1021001 }
};
session.stage.Stages.Add(stageData.StageId, stage);
session.SendPush(new NotifyStageData() { StageList = new() { stage } });
}
}
}
}
}

View File

@ -1,5 +1,6 @@
using AscNet.Common.Database; using AscNet.Common.Database;
using AscNet.Common.MsgPack; using AscNet.Common.MsgPack;
using AscNet.Table.share.fuben;
using AscNet.Table.share.guide; using AscNet.Table.share.guide;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -103,7 +104,7 @@ namespace AscNet.GameServer.Handlers
BaseEquipLoginData = new(), BaseEquipLoginData = new(),
FubenData = new() FubenData = new()
{ {
StageData = session.stage.Stages, StageData = session.stage.Stages.ToDictionary(x => x.Key, x => x.Value),
FubenBaseData = new() FubenBaseData = new()
}, },
FubenMainLineData = new(), FubenMainLineData = new(),
@ -116,7 +117,6 @@ namespace AscNet.GameServer.Handlers
notifyLogin.FashionList.AddRange(session.character.Fashions); notifyLogin.FashionList.AddRange(session.character.Fashions);
#if DEBUG #if DEBUG
// Per account settings flag(?)
notifyLogin.PlayerData.GuideData = GuideGroupTableReader.Instance.All.Select(x => (long)x.Id).ToList(); notifyLogin.PlayerData.GuideData = GuideGroupTableReader.Instance.All.Select(x => (long)x.Id).ToList();
#endif #endif

File diff suppressed because one or more lines are too long

View File

@ -120,7 +120,7 @@ namespace AscNet.GameServer
#endif #endif
} }
log.Info("Finished Loading Packet Handlers"); log.Info("Finished loading packet handlers");
} }
public static RequestPacketHandlerDelegate? GetRequestPacketHandler(string name) public static RequestPacketHandlerDelegate? GetRequestPacketHandler(string name)

View File

@ -19,12 +19,16 @@ namespace AscNet.GameServer
} }
} }
public Server() static Server()
{ {
// TODO: add loglevel based on appsettings // TODO: add loglevel based on appsettings
LogLevel logLevel = LogLevel.DEBUG; LogLevel logLevel = LogLevel.DEBUG;
LogLevel fileLogLevel = LogLevel.DEBUG; LogLevel fileLogLevel = LogLevel.DEBUG;
log = new(typeof(Server), logLevel, fileLogLevel); log = new(typeof(Server), logLevel, fileLogLevel);
}
public Server()
{
listener = new(IPAddress.Parse("0.0.0.0"), Common.Common.config.GameServer.Port); listener = new(IPAddress.Parse("0.0.0.0"), Common.Common.config.GameServer.Port);
} }
@ -54,5 +58,10 @@ namespace AscNet.GameServer
} }
} }
} }
public Session? SessionFromUID(long uid)
{
return Sessions.FirstOrDefault(x => x.Value.player.PlayerData.Id == uid).Value;
}
} }
} }

View File

@ -13,6 +13,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AscNet.Common\AscNet.Common.csproj" /> <ProjectReference Include="..\AscNet.Common\AscNet.Common.csproj" />
<ProjectReference Include="..\AscNet.GameServer\AscNet.GameServer.csproj" />
<ProjectReference Include="..\AscNet.Logging\AscNet.Logging.csproj" /> <ProjectReference Include="..\AscNet.Logging\AscNet.Logging.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,69 @@
using AscNet.Common.Database;
using AscNet.GameServer;
using AscNet.GameServer.Commands;
using AscNet.SDKServer.Models ;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace AscNet.SDKServer.Controllers
{
public class CommandController : IRegisterable
{
public static void Register(WebApplication app)
{
app.MapGet("/api/AscNet/commands", (HttpContext ctx) =>
{
List<object> commands = new();
foreach (var command in CommandFactory.commands.Keys)
{
Command? cmd = CommandFactory.CreateCommand(command, null!, Array.Empty<string>(), false);
if (cmd is not null)
commands.Add(new
{
name = command,
help = cmd.Help
});
}
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { code = 0, msg = "OK", data = commands }));
});
app.MapPost("/api/AscNet/command/{command}", ([FromHeader] string authorization, [FromBody] ExecuteCommandBody body, string command, HttpContext ctx) =>
{
Player? player = Player.FromToken(authorization);
if (player is null)
{
ctx.Response.StatusCode = 401;
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { msg = "Invalid token!" }));
}
Session? session = Server.Instance.SessionFromUID(player.PlayerData.Id);
if (session is null)
{
ctx.Response.StatusCode = 400;
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { msg = "Player is offline!" }));
}
try
{
Command? cmd = CommandFactory.CreateCommand(command, session, body.Args);
if (cmd is null)
{
ctx.Response.StatusCode = 404;
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { msg = "Command does not exists!" }));
}
cmd.Execute();
}
catch (Exception ex)
{
ctx.Response.StatusCode = 400;
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { msg = "Execution error!", err = ex.InnerException?.Message ?? ex.Message }));
}
return ctx.Response.WriteAsync(JsonConvert.SerializeObject(new { msg = "OK" }));
});
}
}
}

View File

@ -0,0 +1,11 @@
using Newtonsoft.Json;
namespace AscNet.SDKServer.Models
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public class ExecuteCommandBody
{
[JsonProperty("args", NullValueHandling = NullValueHandling.Ignore)]
public string[] Args { get; set; }
}
}

File diff suppressed because one or more lines are too long