new irc command system, raids
This commit is contained in:
parent
735db02c50
commit
1f697b42d5
|
@ -209,6 +209,9 @@ namespace SCHALE.Common.Database
|
|||
[JsonIgnore]
|
||||
public virtual ICollection<WeaponDB> Weapons { get; }
|
||||
|
||||
[JsonIgnore]
|
||||
public long RaidSeasonId { get; set; } // idk where to store this
|
||||
|
||||
public AccountDB() { }
|
||||
|
||||
public AccountDB(long publisherAccountId)
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SCHALE.Common.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SCHALE.Common.Migrations
|
||||
{
|
||||
[DbContext(typeof(SCHALEContext))]
|
||||
[Migration("20240511074956_Account_RaidSeasonId")]
|
||||
partial class Account_RaidSeasonId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.2")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true)
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.AccountDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<DateTime?>("BirthDay")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CallName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CallNameUpdateTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreateDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("DevId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("Exp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("LastConnectTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("LinkRewardDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("LobbyMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("MemoryLobbyUniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("PublisherAccountId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RaidSeasonId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("RepresentCharacterServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("RetentionDays")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("UnReadMailCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("VIPLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.ToTable("Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.CharacterDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("EquipmentServerIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EquipmentSlotAndDBIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("ExSkillLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("Exp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("ExtraPassiveSkillLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("FavorExp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("FavorRank")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsLocked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("LeaderSkillLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PassiveSkillLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("PotentialStats")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("PublicSkillLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("StarGrade")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("UniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("Characters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.EchelonDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("EchelonNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("EchelonType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ExtensionType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("LeaderServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("MainSlotServerIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SkillCardMulliganCharacterIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SupportSlotServerIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("TSSInteractionServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("UsingFlag")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("Echelons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.EquipmentDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("BoundCharacterServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("Exp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsLocked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("StackCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Tier")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("UniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("Equipment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.ItemDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsLocked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<long>("StackCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("UniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.MissionProgressDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("Complete")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<long>("MissionUniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ProgressParameters")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("StartTime")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("MissionProgresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.Models.AccountTutorial", b =>
|
||||
{
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("TutorialIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("AccountServerId");
|
||||
|
||||
b.ToTable("AccountTutorials");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.Models.GuestAccount", b =>
|
||||
{
|
||||
b.Property<long>("Uid")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Uid"));
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Uid");
|
||||
|
||||
b.ToTable("GuestAccounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.WeaponDB", b =>
|
||||
{
|
||||
b.Property<long>("ServerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
|
||||
|
||||
b.Property<long>("AccountServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("BoundCharacterServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("Exp")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsLocked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("StarGrade")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("UniqueId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("ServerId");
|
||||
|
||||
b.HasIndex("AccountServerId");
|
||||
|
||||
b.ToTable("Weapons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.CharacterDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("Characters")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.EchelonDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("Echelons")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.EquipmentDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("Equipment")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.ItemDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.MissionProgressDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("MissionProgresses")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.WeaponDB", b =>
|
||||
{
|
||||
b.HasOne("SCHALE.Common.Database.AccountDB", "Account")
|
||||
.WithMany("Weapons")
|
||||
.HasForeignKey("AccountServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SCHALE.Common.Database.AccountDB", b =>
|
||||
{
|
||||
b.Navigation("Characters");
|
||||
|
||||
b.Navigation("Echelons");
|
||||
|
||||
b.Navigation("Equipment");
|
||||
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("MissionProgresses");
|
||||
|
||||
b.Navigation("Weapons");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SCHALE.Common.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Account_RaidSeasonId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "RaidSeasonId",
|
||||
table: "Accounts",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RaidSeasonId",
|
||||
table: "Accounts");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -75,6 +75,9 @@ namespace SCHALE.Common.Migrations
|
|||
b.Property<long>("PublisherAccountId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RaidSeasonId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("RepresentCharacterServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
|
|
|
@ -8795,7 +8795,7 @@ namespace SCHALE.Common.NetworkProtocol
|
|||
{
|
||||
get
|
||||
{
|
||||
return NetworkProtocol.Protocol.None;
|
||||
return NetworkProtocol.Protocol.MultiFloorRaid_Sync;
|
||||
}
|
||||
}
|
||||
public List<MultiFloorRaidDB> MultiFloorRaidDBs { get; set; }
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.Common.Database.ModelExtensions;
|
||||
using SCHALE.Common.FlatData;
|
||||
using SCHALE.GameServer.Services.Irc;
|
||||
|
||||
namespace SCHALE.GameServer.Commands
|
||||
{
|
||||
[CommandHandler("character", "Unlock a character or all characters", "character unlock=all")]
|
||||
public class CharacterCommand : Command
|
||||
{
|
||||
[Argument("unlock")]
|
||||
public string? Unlock { get; set; }
|
||||
|
||||
public override void Execute(Dictionary<string, string> args, IrcConnection connection)
|
||||
{
|
||||
base.Execute(args);
|
||||
|
||||
// TODO: finish this
|
||||
if (Unlock is null)
|
||||
{
|
||||
connection.SendChatMessage($"Usage: /character unlock=<all|clear|shipId>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Unlock.Equals("all", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
AddAllCharacters(connection);
|
||||
|
||||
connection.SendChatMessage("All Characters Added!");
|
||||
} else if (Unlock.Equals("clear", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
|
||||
} else if (uint.TryParse(Unlock, out uint characterId))
|
||||
{
|
||||
|
||||
} else
|
||||
{
|
||||
connection.SendChatMessage($"Invalid Character Id: {characterId}");
|
||||
return;
|
||||
}
|
||||
|
||||
connection.Context.SaveChanges();
|
||||
base.NotifySuccess(connection);
|
||||
}
|
||||
|
||||
private void AddAllCharacters(IrcConnection connection)
|
||||
{
|
||||
var account = connection.Account;
|
||||
var context = connection.Context;
|
||||
|
||||
var characterExcel = connection.ExcelTableService.GetTable<CharacterExcelTable>().UnPack().DataList;
|
||||
var allCharacters = characterExcel.Where(x => x.IsPlayable && x.IsPlayableCharacter && x.CollectionVisible && !account.Characters.Any(c => c.UniqueId == x.Id)).Select(x =>
|
||||
{
|
||||
return new CharacterDB()
|
||||
{
|
||||
UniqueId = x.Id,
|
||||
StarGrade = x.MaxStarGrade,
|
||||
Level = 90,
|
||||
Exp = 0,
|
||||
PublicSkillLevel = 10,
|
||||
ExSkillLevel = 5,
|
||||
PassiveSkillLevel = 10,
|
||||
ExtraPassiveSkillLevel = 10,
|
||||
LeaderSkillLevel = 1,
|
||||
FavorRank = 500,
|
||||
IsNew = true,
|
||||
IsLocked = true,
|
||||
PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } },
|
||||
EquipmentServerIds = [0, 0, 0]
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
account.AddCharacters(context, [.. allCharacters]);
|
||||
connection.Context.SaveChanges();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
using SCHALE.GameServer.Services.Irc;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SCHALE.GameServer.Commands
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class CommandHandler : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public string Example { get; }
|
||||
|
||||
public CommandHandler(string commandName, string description, string example)
|
||||
{
|
||||
Name = commandName;
|
||||
Description = description;
|
||||
Example = example;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ArgumentAttribute : Attribute
|
||||
{
|
||||
public string Key { get; }
|
||||
|
||||
public ArgumentAttribute(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CommandUsage
|
||||
{
|
||||
None = 0,
|
||||
Console = 1,
|
||||
User = 2
|
||||
}
|
||||
|
||||
public abstract class Command
|
||||
{
|
||||
public virtual CommandUsage Usage
|
||||
{
|
||||
get
|
||||
{
|
||||
var usage = CommandUsage.None;
|
||||
if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary<string, string>), typeof(IrcConnection)])?.DeclaringType == GetType())
|
||||
usage |= CommandUsage.User;
|
||||
if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary<string, string>)])?.DeclaringType == GetType())
|
||||
usage |= CommandUsage.Console;
|
||||
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
|
||||
readonly Dictionary<string, PropertyInfo> argsProperties;
|
||||
|
||||
public Command()
|
||||
{
|
||||
argsProperties = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Where(x => Attribute.IsDefined(x, typeof(ArgumentAttribute)))
|
||||
.ToDictionary(x => ((ArgumentAttribute)Attribute.GetCustomAttribute(x, typeof(ArgumentAttribute))!).Key, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public virtual void Execute(Dictionary<string, string> args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (argsProperties.TryGetValue(arg.Key, out var prop))
|
||||
prop.SetValue(this, arg.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Execute(Dictionary<string, string> args, IrcConnection connection)
|
||||
{
|
||||
Execute(args);
|
||||
}
|
||||
|
||||
public virtual void NotifySuccess(IrcConnection connection)
|
||||
{
|
||||
connection.SendChatMessage($"{GetType().Name} success! Please relog for it to take effect.");
|
||||
}
|
||||
|
||||
protected T Parse<T>(string? value, T fallback = default!)
|
||||
{
|
||||
var tryParseMethod = typeof(T).GetMethod("TryParse", [typeof(string), typeof(T).MakeByRefType()]);
|
||||
|
||||
if (tryParseMethod != null)
|
||||
{
|
||||
var parameters = new object[] { value!, null! };
|
||||
bool success = (bool)tryParseMethod.Invoke(null, parameters)!;
|
||||
|
||||
if (success)
|
||||
return (T)parameters[1];
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandHandlerFactory
|
||||
{
|
||||
public static readonly List<Command> Commands = new List<Command>();
|
||||
|
||||
static readonly Dictionary<string, Action<Dictionary<string, string>>> commandFunctions;
|
||||
static readonly Dictionary<string, Action<Dictionary<string, string>, IrcConnection>> commandFunctionsConn;
|
||||
private static readonly char[] separator = new[] { ' ' };
|
||||
|
||||
static CommandHandlerFactory()
|
||||
{
|
||||
commandFunctions = new Dictionary<string, Action<Dictionary<string, string>>>(StringComparer.OrdinalIgnoreCase);
|
||||
commandFunctionsConn = new Dictionary<string, Action<Dictionary<string, string>, IrcConnection>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
RegisterCommands(Assembly.GetExecutingAssembly());
|
||||
}
|
||||
|
||||
public static void RegisterCommands(Assembly assembly)
|
||||
{
|
||||
var commandTypes = assembly.GetTypes()
|
||||
.Where(t => Attribute.IsDefined(t, typeof(CommandHandler)) && typeof(Command).IsAssignableFrom(t));
|
||||
|
||||
foreach (var commandType in commandTypes)
|
||||
{
|
||||
var commandAttribute = (CommandHandler?)Attribute.GetCustomAttribute(commandType, typeof(CommandHandler));
|
||||
if (commandAttribute != null)
|
||||
{
|
||||
var commandInstance = (Command)Activator.CreateInstance(commandType)!;
|
||||
|
||||
if (commandInstance.Usage.HasFlag(CommandUsage.Console))
|
||||
commandFunctions[commandAttribute.Name] = commandInstance.Execute;
|
||||
if (commandInstance.Usage.HasFlag(CommandUsage.User))
|
||||
commandFunctionsConn[commandAttribute.Name] = commandInstance.Execute;
|
||||
|
||||
Commands.Add(commandInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleCommand(string commandLine, IrcConnection? connection = null)
|
||||
{
|
||||
var parts = commandLine.Split(separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 0)
|
||||
return;
|
||||
|
||||
var arguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 1; i < parts.Length; i++)
|
||||
{
|
||||
var argParts = parts[i].Split('=', 2);
|
||||
if (argParts.Length == 2)
|
||||
arguments[argParts[0]] = argParts[1];
|
||||
}
|
||||
|
||||
if (connection is not null)
|
||||
{
|
||||
if (!(commandFunctionsConn).TryGetValue(parts[0], out var command))
|
||||
{
|
||||
connection.SendChatMessage($"Unknown command: {parts[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
command(arguments, connection);
|
||||
} else
|
||||
{
|
||||
if (!(commandFunctions).TryGetValue(parts[0], out var command))
|
||||
{
|
||||
connection.SendChatMessage($"Unknown command: {parts[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
command(arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.GameServer.Services.Irc;
|
||||
using Serilog;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace SCHALE.GameServer.Commands
|
||||
{
|
||||
[CommandHandler("help", "Print out all commands with their description and example", "help")]
|
||||
public class HelpCommand : Command
|
||||
{
|
||||
static readonly Dictionary<Type, CommandHandler?> commandAttributes = new Dictionary<Type, CommandHandler?>();
|
||||
|
||||
// doesnt support console yet
|
||||
public override void Execute(Dictionary<string, string> args)
|
||||
{
|
||||
base.Execute(args);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Available Commands: ");
|
||||
foreach (var command in CommandHandlerFactory.Commands.Where(x => x.Usage.HasFlag(CommandUsage.Console)))
|
||||
{
|
||||
if (!commandAttributes.TryGetValue(command.GetType(), out var attr))
|
||||
{
|
||||
attr = command.GetType().GetCustomAttribute(typeof(CommandHandler)) as CommandHandler;
|
||||
commandAttributes[command.GetType()] = attr;
|
||||
}
|
||||
|
||||
if (attr != null)
|
||||
sb.AppendLine($"{attr.Name} - {attr.Description} (Example: {attr.Example})");
|
||||
}
|
||||
|
||||
Console.Write(sb.ToString());
|
||||
}
|
||||
|
||||
public override void Execute(Dictionary<string, string> args, IrcConnection connection)
|
||||
{
|
||||
base.Execute(args);
|
||||
|
||||
connection.SendChatMessage("Available Commands: ");
|
||||
foreach (var command in CommandHandlerFactory.Commands.Where(x => x.Usage.HasFlag(CommandUsage.User)))
|
||||
{
|
||||
if (!commandAttributes.TryGetValue(command.GetType(), out var attr))
|
||||
{
|
||||
attr = command.GetType().GetCustomAttribute(typeof(CommandHandler)) as CommandHandler;
|
||||
commandAttributes[command.GetType()] = attr;
|
||||
}
|
||||
|
||||
if (attr != null)
|
||||
connection.SendChatMessage($"{attr.Name} - {attr.Description} (Example: {attr.Example})");
|
||||
}
|
||||
|
||||
base.NotifySuccess(connection);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -224,6 +224,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
//AddAllEquipment(account);
|
||||
//AddAllItems(account);
|
||||
//AddAllWeapons(account);
|
||||
SetRaidSeason(account, 63);
|
||||
|
||||
return new AccountLoginSyncResponse()
|
||||
{
|
||||
|
@ -327,6 +328,14 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
EquipmentDBs = [.. account.Equipment]
|
||||
},
|
||||
|
||||
ClanLoginResponse = new ClanLoginResponse()
|
||||
{
|
||||
AccountClanMemberDB = new()
|
||||
{
|
||||
AccountId = account.ServerId
|
||||
}
|
||||
},
|
||||
|
||||
FriendCode = "SCHALE",
|
||||
};
|
||||
}
|
||||
|
@ -446,34 +455,6 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
}
|
||||
|
||||
// these will probably be commands
|
||||
private void AddAllCharacters(AccountDB account)
|
||||
{
|
||||
var characterExcel = excelTableService.GetTable<CharacterExcelTable>().UnPack().DataList;
|
||||
var allCharacters = characterExcel.Where(x => x.IsPlayable && x.IsPlayableCharacter && x.CollectionVisible && !account.Characters.Any(c => c.UniqueId == x.Id)).Select(x =>
|
||||
{
|
||||
return new CharacterDB()
|
||||
{
|
||||
UniqueId = x.Id,
|
||||
StarGrade = x.MaxStarGrade,
|
||||
Level = 90,
|
||||
Exp = 0,
|
||||
PublicSkillLevel = 10,
|
||||
ExSkillLevel = 5,
|
||||
PassiveSkillLevel = 10,
|
||||
ExtraPassiveSkillLevel = 10,
|
||||
LeaderSkillLevel = 1,
|
||||
FavorRank = 500,
|
||||
IsNew = true,
|
||||
IsLocked = true,
|
||||
PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } },
|
||||
EquipmentServerIds = [0, 0, 0]
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
account.AddCharacters(context, [.. allCharacters]);
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
private void AddAllEquipment(AccountDB account)
|
||||
{
|
||||
var equipmentExcel = excelTableService.GetTable<EquipmentExcelTable>().UnPack().DataList;
|
||||
|
@ -519,7 +500,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
BoundCharacterServerId = x.ServerId,
|
||||
IsLocked = false,
|
||||
StarGrade = 5,
|
||||
Level = 70
|
||||
Level = 200
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -527,6 +508,11 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
context.SaveChanges();
|
||||
}
|
||||
|
||||
private void SetRaidSeason(AccountDB account, long seasonId)
|
||||
{
|
||||
account.RaidSeasonId = seasonId;
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,73 @@
|
|||
using SCHALE.Common.NetworkProtocol;
|
||||
using SCHALE.Common.Database;
|
||||
using SCHALE.Common.NetworkProtocol;
|
||||
using SCHALE.GameServer.Services;
|
||||
|
||||
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||
{
|
||||
public class Clan : ProtocolHandlerBase
|
||||
{
|
||||
public Clan(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory) { }
|
||||
private readonly ISessionKeyService sessionKeyService;
|
||||
private readonly SCHALEContext context;
|
||||
private readonly ExcelTableService excelTableService;
|
||||
|
||||
public Clan(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory)
|
||||
{
|
||||
sessionKeyService = _sessionKeyService;
|
||||
context = _context;
|
||||
excelTableService = _excelTableService;
|
||||
}
|
||||
|
||||
[ProtocolHandler(Protocol.Clan_Lobby)]
|
||||
public ResponsePacket CheckHandler(ClanLobbyRequest req)
|
||||
{
|
||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||
|
||||
return new ClanLobbyResponse()
|
||||
{
|
||||
IrcConfig = new()
|
||||
{
|
||||
//HostAddress = "10.0.0.149",
|
||||
HostAddress = "192.168.86.35",
|
||||
Port = 6667,
|
||||
Password = ""
|
||||
},
|
||||
AccountClanDB = new()
|
||||
{
|
||||
ClanDBId = 777,
|
||||
ClanName = "Nexon",
|
||||
ClanChannelName = "channel_1",
|
||||
ClanPresidentNickName = "Arona",
|
||||
ClanPresidentRepresentCharacterUniqueId = 10000,
|
||||
ClanNotice = "",
|
||||
ClanMemberCount = 1,
|
||||
},
|
||||
AccountClanMemberDB = new()
|
||||
{
|
||||
AccountId = account.ServerId,
|
||||
AccountLevel = account.Level,
|
||||
ClanDBId = 777,
|
||||
RepresentCharacterUniqueId = 10000,
|
||||
ClanSocialGrade = Common.FlatData.ClanSocialGrade.Member,
|
||||
AccountNickName = account.Nickname
|
||||
},
|
||||
ClanMemberDBs = [
|
||||
new() {
|
||||
AccountId = account.ServerId,
|
||||
AccountLevel = account.Level,
|
||||
ClanDBId = 777,
|
||||
RepresentCharacterUniqueId = 10000,
|
||||
AttendanceCount = 33,
|
||||
ClanSocialGrade = Common.FlatData.ClanSocialGrade.Member,
|
||||
AccountNickName = account.Nickname,
|
||||
AttachmentDB = new() {
|
||||
AccountId = account.ServerId,
|
||||
EmblemUniqueId = 123123
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
[ProtocolHandler(Protocol.Clan_Check)]
|
||||
public ResponsePacket CheckHandler(ClanCheckRequest req)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.Common.NetworkProtocol;
|
||||
using SCHALE.GameServer.Services;
|
||||
|
||||
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||
{
|
||||
public class MultiFloorRaid : ProtocolHandlerBase
|
||||
{
|
||||
private readonly ISessionKeyService sessionKeyService;
|
||||
private readonly SCHALEContext context;
|
||||
private readonly ExcelTableService excelTableService;
|
||||
|
||||
public MultiFloorRaid(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory)
|
||||
{
|
||||
sessionKeyService = _sessionKeyService;
|
||||
context = _context;
|
||||
excelTableService = _excelTableService;
|
||||
}
|
||||
|
||||
[ProtocolHandler(Protocol.MultiFloorRaid_Sync)]
|
||||
public ResponsePacket SyncHandler(MultiFloorRaidSyncRequest req)
|
||||
{
|
||||
return new MultiFloorRaidSyncResponse()
|
||||
{
|
||||
MultiFloorRaidDBs = [
|
||||
new() {
|
||||
SeasonId = 2,
|
||||
ClearBattleFrame = -1
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
[ProtocolHandler(Protocol.MultiFloorRaid_EnterBattle)]
|
||||
public ResponsePacket EnterBattleHandler(MultiFloorRaidEnterBattleRequest req)
|
||||
{
|
||||
return new MultiFloorRaidEnterBattleResponse();
|
||||
}
|
||||
|
||||
[ProtocolHandler(Protocol.MultiFloorRaid_EndBattle)]
|
||||
public ResponsePacket EndBattleHandler(MultiFloorRaidEndBattleRequest req)
|
||||
{
|
||||
return new MultiFloorRaidEndBattleResponse();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.Common.NetworkProtocol;
|
||||
using SCHALE.GameServer.Services;
|
||||
using Serilog;
|
||||
using System.Reflection;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.Common.FlatData;
|
||||
using SCHALE.Common.NetworkProtocol;
|
||||
using SCHALE.GameServer.Services;
|
||||
|
||||
|
@ -20,9 +21,26 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
[ProtocolHandler(Protocol.Raid_Lobby)]
|
||||
public ResponsePacket LobbyHandler(RaidLobbyRequest req)
|
||||
{
|
||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||
|
||||
var raidSeasonInfo = excelTableService.GetTable<RaidSeasonManageExcelTable>().UnPack().DataList;
|
||||
var targetSeason = raidSeasonInfo.FirstOrDefault(x => x.SeasonId == account.RaidSeasonId);
|
||||
|
||||
return new RaidLobbyResponse()
|
||||
{
|
||||
|
||||
SeasonType = RaidSeasonType.Open,
|
||||
RaidLobbyInfoDB = new()
|
||||
{
|
||||
SeasonId = account.RaidSeasonId,
|
||||
Tier = 2,
|
||||
Ranking = 1,
|
||||
BestRankingPoint = 1_000_000,
|
||||
TotalRankingPoint = 10_000_000,
|
||||
ReceiveRewardIds = targetSeason.SeasonRewardId,
|
||||
PlayableHighestDifficulty = new() {
|
||||
{ targetSeason.OpenRaidBossGroup.FirstOrDefault(), Difficulty.Torment }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -38,9 +56,49 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
[ProtocolHandler(Protocol.Raid_CreateBattle)]
|
||||
public ResponsePacket CreateBattleHandller(RaidCreateBattleRequest req)
|
||||
{
|
||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||
|
||||
var raidStageTable = excelTableService.GetTable<RaidStageExcelTable>().UnPack().DataList;
|
||||
var currentRaid = raidStageTable.FirstOrDefault(x => x.Id == req.RaidUniqueId);
|
||||
|
||||
return new RaidCreateBattleResponse()
|
||||
{
|
||||
RaidDB = new()
|
||||
{
|
||||
Owner = new()
|
||||
{
|
||||
AccountId = account.ServerId,
|
||||
AccountName = account.Nickname,
|
||||
},
|
||||
ContentType = ContentType.Raid,
|
||||
UniqueId = req.RaidUniqueId,
|
||||
SeasonId = account.RaidSeasonId,
|
||||
PlayerCount = 1,
|
||||
RaidState = RaidStatus.Playing,
|
||||
IsPractice = req.IsPractice,
|
||||
RaidBossDBs = [
|
||||
new() {
|
||||
ContentType = ContentType.Raid,
|
||||
BossCurrentHP = long.MaxValue
|
||||
}
|
||||
],
|
||||
AccountLevelWhenCreateDB = account.Level,
|
||||
},
|
||||
|
||||
RaidBattleDB = new()
|
||||
{
|
||||
ContentType = ContentType.Raid,
|
||||
RaidUniqueId = req.RaidUniqueId,
|
||||
CurrentBossHP = long.MaxValue,
|
||||
RaidMembers = [
|
||||
new() {
|
||||
AccountId = account.ServerId,
|
||||
AccountName = account.Nickname,
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
AssistCharacterDB = new () { }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -58,7 +116,6 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
{
|
||||
return new RaidEndBattleResponse()
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
Character = new() // hardcoded util proper db
|
||||
{
|
||||
ServerId = req.AccountId,
|
||||
UniqueId = 20007,
|
||||
UniqueId = id,
|
||||
StarGrade = 3,
|
||||
Level = 1,
|
||||
FavorRank = 1,
|
||||
|
@ -120,6 +120,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
|||
|
||||
return new ShopBuyGacha3Response()
|
||||
{
|
||||
GachaResults = gachaResults,
|
||||
UpdateTime = DateTime.UtcNow,
|
||||
GemBonusRemain = long.MaxValue,
|
||||
ConsumedItems = [],
|
||||
|
|
|
@ -6,6 +6,7 @@ using SCHALE.Common.Database;
|
|||
using SCHALE.GameServer.Controllers.Api.ProtocolHandlers;
|
||||
using SCHALE.GameServer.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SCHALE.GameServer.Services.Irc;
|
||||
|
||||
namespace SCHALE.GameServer
|
||||
{
|
||||
|
@ -53,6 +54,7 @@ namespace SCHALE.GameServer
|
|||
builder.Services.AddProtocolHandlerFactory();
|
||||
builder.Services.AddMemorySessionKeyService();
|
||||
builder.Services.AddExcelTableService();
|
||||
builder.Services.AddIrcService();
|
||||
|
||||
// Add all Handler Groups
|
||||
var handlerGroups = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase)));
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
using SCHALE.Common.Database;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SCHALE.GameServer.Services.Irc
|
||||
{
|
||||
public class IrcConnection
|
||||
{
|
||||
public required TcpClient TcpClient { get; set; }
|
||||
|
||||
public required SCHALEContext Context { get; set; }
|
||||
|
||||
public required StreamWriter StreamWriter { get; set; }
|
||||
|
||||
public required ExcelTableService ExcelTableService { get; set; }
|
||||
|
||||
public long AccountServerId { get; set; }
|
||||
|
||||
public string CurrentChannel { get; set; }
|
||||
|
||||
public AccountDB Account { get => Context.Accounts.FirstOrDefault(x => x.ServerId == AccountServerId); }
|
||||
|
||||
public void SendChatMessage(string text)
|
||||
{
|
||||
SendChatMessage(text, "Shiroko", 10010, IrcMessageType.Chat);
|
||||
}
|
||||
|
||||
public void SendChatMessage(string text, string nickname, long pfpCharacterId, IrcMessageType messageType)
|
||||
{
|
||||
var reply = new Reply()
|
||||
{
|
||||
Prefix = "mx_admin_bot!admin@netadmin.example.com",
|
||||
Command = $"PRIVMSG {CurrentChannel}",
|
||||
Trailing = JsonSerializer.Serialize(new IrcMessage()
|
||||
{
|
||||
CharacterId = pfpCharacterId,
|
||||
MessageType = messageType,
|
||||
AccountNickname = nickname,
|
||||
Text = text,
|
||||
SendTicks = DateTimeOffset.Now.Ticks,
|
||||
}, typeof(IrcMessage)),
|
||||
}.ToString();
|
||||
|
||||
StreamWriter.WriteLine(reply);
|
||||
StreamWriter.Flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
using SCHALE.Common.Database;
|
||||
using SCHALE.GameServer.Commands;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SCHALE.GameServer.Services.Irc
|
||||
{
|
||||
public class IrcServer
|
||||
{
|
||||
private ConcurrentDictionary<TcpClient, IrcConnection> clients = new ConcurrentDictionary<TcpClient, IrcConnection>(); // most irc commands doesn't even send over the player uid so imma just use TcpClient as key
|
||||
private ConcurrentDictionary<string, List<long>> channels = new ConcurrentDictionary<string, List<long>>();
|
||||
|
||||
private readonly TcpListener listener;
|
||||
|
||||
private readonly ILogger<IrcService> logger;
|
||||
private readonly SCHALEContext context;
|
||||
private readonly ExcelTableService excelTableService;
|
||||
|
||||
public IrcServer(IPAddress host, int port, ILogger<IrcService> _logger, SCHALEContext _context, ExcelTableService _excelTableService)
|
||||
{
|
||||
logger = _logger;
|
||||
context = _context;
|
||||
excelTableService = _excelTableService;
|
||||
|
||||
listener = new TcpListener(host, port);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
listener.Start();
|
||||
logger.LogDebug("Irc Server Started");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var tcpClient = await listener.AcceptTcpClientAsync();
|
||||
|
||||
_ = HandleMessageAsync(tcpClient);
|
||||
logger.LogDebug("TcpClient is trying to connect...");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(TcpClient tcpClient)
|
||||
{
|
||||
using var reader = new StreamReader(tcpClient.GetStream());
|
||||
using var writer = new StreamWriter(tcpClient.GetStream()) { AutoFlush = true };
|
||||
|
||||
string line;
|
||||
|
||||
while ((line = await reader.ReadLineAsync()) != null)
|
||||
{
|
||||
var splitLine = line.Split(' ', 2);
|
||||
var commandStr = splitLine[0].ToUpper().Trim();
|
||||
var parameters = splitLine.Length > 1 ? splitLine[1] : "";
|
||||
|
||||
if (!Enum.TryParse<IrcCommand>(commandStr, out var command))
|
||||
{
|
||||
command = IrcCommand.UNKNOWN;
|
||||
}
|
||||
|
||||
string result = "";
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case IrcCommand.NICK:
|
||||
result = await HandleNick(parameters);
|
||||
break;
|
||||
case IrcCommand.USER:
|
||||
await HandleUser(parameters, tcpClient, writer);
|
||||
break;
|
||||
case IrcCommand.JOIN:
|
||||
await HandleJoin(parameters, tcpClient);
|
||||
break;
|
||||
case IrcCommand.PRIVMSG:
|
||||
await HandlePrivMsg(parameters, tcpClient);
|
||||
break;
|
||||
case IrcCommand.PING:
|
||||
result = await HandlePing(parameters);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != null || result != "")
|
||||
await writer.WriteLineAsync(result);
|
||||
}
|
||||
|
||||
tcpClient.Close();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
listener.Stop();
|
||||
}
|
||||
|
||||
private async Task<string> HandleNick(string parameters) // welcomes
|
||||
{
|
||||
return new Reply()
|
||||
{
|
||||
Prefix = "server",
|
||||
ReplyCode = ReplyCode.RPL_WELCOME,
|
||||
Trailing = "Welcome to SCHALE!"
|
||||
}.ToString();
|
||||
}
|
||||
|
||||
private async Task HandleUser(string parameters, TcpClient client, StreamWriter writer) // sends over account server id
|
||||
{
|
||||
string[] args = parameters.Split(' ');
|
||||
var user_serverId = long.Parse(args[0].Split("_")[1]);
|
||||
|
||||
clients[client] = new IrcConnection()
|
||||
{
|
||||
AccountServerId = user_serverId,
|
||||
Context = context,
|
||||
TcpClient = client,
|
||||
StreamWriter = writer,
|
||||
ExcelTableService = excelTableService,
|
||||
};
|
||||
|
||||
logger.LogDebug($"User {user_serverId} logged in");
|
||||
}
|
||||
|
||||
private async Task HandleJoin(string parameters, TcpClient client) // sends over channel id
|
||||
{
|
||||
var channel = parameters;
|
||||
|
||||
if (!channels.ContainsKey(channel))
|
||||
{
|
||||
channels[channel] = new List<long>();
|
||||
}
|
||||
|
||||
var userClient = clients[client];
|
||||
|
||||
channels[channel].Add(userClient.AccountServerId);
|
||||
userClient.CurrentChannel = channel;
|
||||
|
||||
logger.LogDebug($"User {userClient.AccountServerId} joined {channel}");
|
||||
}
|
||||
|
||||
private async Task HandlePrivMsg(string parameters, TcpClient client) // player sends msg
|
||||
{
|
||||
string[] args = parameters.Split(' ', 2);
|
||||
|
||||
var channel = args[0];
|
||||
var payloadStr = args[1].TrimStart(':');
|
||||
|
||||
//logger.LogDebug("payload: " + payloadStr);
|
||||
|
||||
var payload = JsonSerializer.Deserialize(payloadStr, typeof(IrcMessage)) as IrcMessage;
|
||||
|
||||
if (payload.Text.StartsWith('/'))
|
||||
{
|
||||
CommandHandlerFactory.HandleCommand(payload.Text.Substring(1), clients[client]);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> HandlePing(string parameters)
|
||||
{
|
||||
return new Reply().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public enum IrcCommand
|
||||
{
|
||||
PASS,
|
||||
NICK,
|
||||
USER,
|
||||
JOIN,
|
||||
PRIVMSG,
|
||||
PING,
|
||||
PART,
|
||||
QUIT,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
public enum IrcMessageType
|
||||
{
|
||||
None,
|
||||
Notice,
|
||||
Sticker,
|
||||
Chat,
|
||||
HistoryCount
|
||||
}
|
||||
|
||||
public class IrcMessage : EventArgs
|
||||
{
|
||||
[JsonPropertyName("MessageType")]
|
||||
public IrcMessageType MessageType { get; set; }
|
||||
|
||||
[JsonPropertyName("CharacterId")]
|
||||
public long CharacterId { get; set; }
|
||||
|
||||
[JsonPropertyName("AccountNickname")]
|
||||
public string AccountNickname { get; set; }
|
||||
|
||||
[JsonPropertyName("StickerId")]
|
||||
public long StickerId { get; }
|
||||
|
||||
[JsonPropertyName("Text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
[JsonPropertyName("SendTicks")]
|
||||
public long SendTicks { get; set; }
|
||||
|
||||
[JsonPropertyName("EmblemId")]
|
||||
public int EmblemId { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Net;
|
||||
using SCHALE.Common.Database;
|
||||
using Serilog;
|
||||
|
||||
namespace SCHALE.GameServer.Services.Irc
|
||||
{
|
||||
public class IrcService : BackgroundService
|
||||
{
|
||||
private IrcServer server;
|
||||
|
||||
public IrcService(ILogger<IrcService> _logger, SCHALEContext _context, ExcelTableService excelTableService)
|
||||
{
|
||||
server = new IrcServer(IPAddress.Any, 6667, _logger, _context, excelTableService);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await server.StartAsync(stoppingToken);
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
server.Stop();
|
||||
await base.StopAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class IrcServiceExtensions
|
||||
{
|
||||
public static void AddIrcService(this IServiceCollection services)
|
||||
{
|
||||
services.AddHostedService<IrcService>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace SCHALE.GameServer.Services.Irc
|
||||
{
|
||||
public class Reply : EventArgs
|
||||
{
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public string Command { get; set; } = string.Empty;
|
||||
|
||||
public ReplyCode ReplyCode { get; set; }
|
||||
|
||||
public List<string> Params { get; set; }
|
||||
|
||||
public string Trailing { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string cmd = Command == string.Empty ? $"{(int)ReplyCode:D3}" : Command;
|
||||
|
||||
return $":{Prefix} {cmd} {Params} :{Trailing}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
namespace SCHALE.GameServer.Services.Irc
|
||||
{
|
||||
public enum ReplyCode
|
||||
{
|
||||
NONE = 0,
|
||||
RPL_NONE = 300,
|
||||
RPL_WELCOME = 1,
|
||||
RPL_YOURHOST = 2,
|
||||
RPL_CREATED = 3,
|
||||
RPL_MYINFO = 4,
|
||||
RPL_MAP = 5,
|
||||
RPL_ENDOFMAP = 7,
|
||||
RPL_MOTDSTART = 375,
|
||||
RPL_MOTD = 372,
|
||||
RPL_MOTDALT = 377,
|
||||
RPL_MOTDALT2 = 378,
|
||||
RPL_MOTDEND = 376,
|
||||
RPL_UMODEIS = 221,
|
||||
RPL_USERHOST = 302,
|
||||
RPL_ISON = 303,
|
||||
RPL_AWAY = 301,
|
||||
RPL_UNAWAY = 305,
|
||||
RPL_NOWAWAY = 306,
|
||||
RPL_WHOISHELPER = 310,
|
||||
RPL_WHOISUSER = 311,
|
||||
RPL_WHOISSERVER = 312,
|
||||
RPL_WHOISOPERATOR = 313,
|
||||
RPL_WHOISIDLE = 317,
|
||||
RPL_ENDOFWHOIS = 318,
|
||||
RPL_WHOISCHANNEL = 319,
|
||||
RPL_WHOWASUSER = 314,
|
||||
RPL_ENDOFWHOWAS = 369,
|
||||
RPL_WHOREPLY = 352,
|
||||
RPL_ENDOFWHO = 315,
|
||||
RPL_USERIPS = 307,
|
||||
RPL_USERIP = 340,
|
||||
RPL_LISTSTART = 321,
|
||||
RPL_LIST = 322,
|
||||
RPL_LISTEND = 323,
|
||||
RPL_LINKS = 364,
|
||||
RPL_ENDOFLINKS = 365,
|
||||
RPL_UNIQOPIS = 325,
|
||||
RPL_CHANNELMODEIS = 324,
|
||||
RPL_CHANNELURL = 328,
|
||||
RPL_CHANNELCREATED = 329,
|
||||
RPL_NOTOPIC = 331,
|
||||
RPL_TOPIC = 332,
|
||||
RPL_TOPICSETBY = 333,
|
||||
RPL_NAMEREPLY = 353,
|
||||
RPL_ENDOFNAMES = 366,
|
||||
RPL_INVITING = 341,
|
||||
RPL_SUMMONING = 342,
|
||||
RPL_INVITELIST = 346,
|
||||
RPL_ENDOFINVITELIST = 357,
|
||||
RPL_EXCEPTLIST = 348,
|
||||
RPL_ENDOFEXCEPTLIST = 349,
|
||||
RPL_BANLIST = 367,
|
||||
RPL_ENDOFBANLIST = 368,
|
||||
RPL_VERSION = 351,
|
||||
RPL_INFO = 371,
|
||||
RPL_ENDOFINFO = 374,
|
||||
RPL_YOUREOPER = 381,
|
||||
RPL_REHASHING = 382,
|
||||
RPL_YOURESERVICE = 383,
|
||||
RPL_TIME = 391,
|
||||
RPL_USERSSTART = 392,
|
||||
RPL_USERS = 393,
|
||||
RPL_ENDOFUSERS = 394,
|
||||
RPL_NOUSERS = 395,
|
||||
RPL_SERVLIST = 234,
|
||||
RPL_SERVLISTEND = 235,
|
||||
RPL_ADMINME = 256,
|
||||
RPL_ADMINLOC1 = 257,
|
||||
RPL_ADMINLOC2 = 258,
|
||||
RPL_ADMINEMAIL = 259,
|
||||
RPL_TRYAGAIN = 263,
|
||||
RPL_TRACELINK = 200,
|
||||
RPL_TRACECONNECTING = 201,
|
||||
RPL_TRACEHANDSHAKE = 202,
|
||||
RPL_TRACEUNKNOWN = 203,
|
||||
RPL_TRACEOPERATOR = 204,
|
||||
RPL_TRACEUSER = 205,
|
||||
RPL_TRACESERVER = 206,
|
||||
RPL_TRACESERVICE = 207,
|
||||
RPL_TRACENEWTYPE = 208,
|
||||
RPL_TRACECLASS = 209,
|
||||
RPL_TRACERECONNECT = 210,
|
||||
RPL_TRACELOG = 261,
|
||||
RPL_TRACEEND = 262,
|
||||
RPL_STATSLINKINFO = 211,
|
||||
RPL_STATSCOMMANDS = 212,
|
||||
RPL_STATSCLINE = 213,
|
||||
RPL_STATSNLINE = 214,
|
||||
RPL_STATSILINE = 215,
|
||||
RPL_STATSKLINE = 216,
|
||||
RplStatsPLine = 217,
|
||||
RplStatsQLine = 222,
|
||||
RplStatsELine = 223,
|
||||
RplStatsDLine = 224,
|
||||
RplStatsLLine = 241,
|
||||
RplStatsuLine = 242,
|
||||
RplStatsoLine = 243,
|
||||
RplStatsHLine = 244,
|
||||
RplStatsGLine = 247,
|
||||
RplStatsULine = 248,
|
||||
RplStatsZLine = 249,
|
||||
RplStatsYLine = 218,
|
||||
RPL_ENDOFSTATS = 219,
|
||||
RPL_STATSUPTIME = 242,
|
||||
RPL_STATSPING = 246,
|
||||
RPL_STATSDLINE = 250,
|
||||
RplGLineList = 280,
|
||||
RplEndOfGLineList = 281,
|
||||
RplSilenceList = 271,
|
||||
RplEndOfSilenceList = 272,
|
||||
RPL_LUSERCLIENT = 251,
|
||||
RPL_LUSEROP = 252,
|
||||
RPL_LUSERUNKNOWN = 253,
|
||||
RPL_LUSERCHANNELS = 254,
|
||||
RPL_LUSERME = 255,
|
||||
RplLUserLocalUser = 265,
|
||||
RplLUserGlobalUser = 266,
|
||||
ERR_NOSUCHNICK = 401,
|
||||
ERR_NOSUCHSERVER = 402,
|
||||
ERR_NOSUCHCHANNEL = 403,
|
||||
ERR_CANNOTSENDTOCHAN = 404,
|
||||
ERR_TOOMANYCHANNELS = 405,
|
||||
ERR_WASNOSUCHNICK = 406,
|
||||
ERR_TOOMANYTARGETS = 407,
|
||||
ERR_NOSUCHSERVICE = 408,
|
||||
ERR_NOORIGIN = 409,
|
||||
ERR_NORECIPIENT = 411,
|
||||
ERR_NOTEXTTOSEND = 412,
|
||||
ERR_NOTOPLEVEL = 413,
|
||||
ERR_WILDTOPLEVEL = 414,
|
||||
ERR_BADMASK = 415,
|
||||
ErrTooMuchInfo = 416,
|
||||
ERR_UNKNOWNCOMMAND = 421,
|
||||
ERR_NOMOTD = 422,
|
||||
ERR_NOADMININFO = 423,
|
||||
ERR_FILEERROR = 424,
|
||||
ERR_NONICKNAMEGIVEN = 431,
|
||||
ERR_ERRONEUSNICKNAME = 432,
|
||||
ERR_NICKNAMEINUSE = 433,
|
||||
ERR_NICKCOLLISION = 436,
|
||||
ERR_UNAVAILRESOURCE = 437,
|
||||
ErrNickTooFast = 438,
|
||||
ErrTargetTooFast = 439,
|
||||
ERR_USERNOTINCHANNEL = 441,
|
||||
ERR_NOTONCHANNEL = 442,
|
||||
ERR_USERONCHANNEL = 443,
|
||||
ERR_NOLOGIN = 444,
|
||||
ERR_SUMMONDISABLED = 445,
|
||||
ERR_USERSDISABLED = 446,
|
||||
ERR_NOTREGISTERED = 451,
|
||||
ERR_NEEDMOREPARAMS = 461,
|
||||
ERR_ALREADYREGISTRED = 462,
|
||||
ERR_NOPERMFORHOST = 463,
|
||||
ERR_PASSWDMISMATCH = 464,
|
||||
ERR_YOUREBANNEDCREEP = 465,
|
||||
ERR_YOUWILLBEBANNED = 466,
|
||||
ERR_KEYSET = 467,
|
||||
ErrServerCanChange = 468,
|
||||
ERR_CHANNELISFULL = 471,
|
||||
ERR_UNKNOWNMODE = 472,
|
||||
ERR_INVITEONLYCHAN = 473,
|
||||
ERR_BANNEDFROMCHAN = 474,
|
||||
ERR_BADCHANNELKEY = 475,
|
||||
ERR_BADCHANMASK = 476,
|
||||
ERR_NOCHANMODES = 477,
|
||||
ERR_BANLISTFULL = 478,
|
||||
ERR_NOPRIVILEGES = 481,
|
||||
ERR_CHANOPRIVSNEEDED = 482,
|
||||
ERR_CANTKILLSERVER = 483,
|
||||
ERR_RESTRICTED = 484,
|
||||
ERR_UNIQOPPRIVSNEEDED = 485,
|
||||
ERR_NOOPERHOST = 491,
|
||||
ERR_UMODEUNKNOWNFLAG = 501,
|
||||
ERR_USERSDONTMATCH = 502,
|
||||
ErrSilenceListFull = 511,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue