diff --git a/Communication/Messages/Incoming/Catalog/GetCatalogIndexEvent.cs b/Communication/Messages/Incoming/Catalog/GetCatalogIndexEvent.cs new file mode 100644 index 0000000..c246719 --- /dev/null +++ b/Communication/Messages/Incoming/Catalog/GetCatalogIndexEvent.cs @@ -0,0 +1,34 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Outgoing.Catalog; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Catalogue; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages.Incoming.Catalog; + +public class GetCatalogIndexEvent : IMessageEvent +{ + private readonly ICatalogueManager _catalogueManager; + private readonly IGameSessionManager _gameSessionManager; + + public GetCatalogIndexEvent(ICatalogueManager catalogueManager, IGameSessionManager gameSessionManager) + { + _catalogueManager = catalogueManager; + _gameSessionManager = gameSessionManager; + } + + public IncomingHeaders Header => IncomingHeaders.GetCatalogIndexEvent; + public async Task HandleAsync(GameSession gameSession, ClientMessage request) + { + if (gameSession.Habbo == null) + { + await _gameSessionManager.CloseAsync("Not logged in", gameSession); + return; + } + + var categories = _catalogueManager.Pages.Values.Where(p => p.Parent is null); + + await gameSession.SendComposerAsync(new CatalogIndexMessageComposer(categories, + request.ReadString() ?? "normal")); + } +} \ No newline at end of file diff --git a/Communication/Messages/Incoming/Catalog/GetCatalogPageEvent.cs b/Communication/Messages/Incoming/Catalog/GetCatalogPageEvent.cs new file mode 100644 index 0000000..2afccf6 --- /dev/null +++ b/Communication/Messages/Incoming/Catalog/GetCatalogPageEvent.cs @@ -0,0 +1,40 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Outgoing.Catalog; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Catalogue; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages.Incoming.Catalog; + +public class GetCatalogPageEvent : IMessageEvent +{ + private readonly ICatalogueManager _catalogueManager; + private readonly IGameSessionManager _gameSessionManager; + + public GetCatalogPageEvent(ICatalogueManager catalogueManager, IGameSessionManager gameSessionManager) + { + _catalogueManager = catalogueManager; + _gameSessionManager = gameSessionManager; + } + + public IncomingHeaders Header => IncomingHeaders.GetCatalogPageEvent; + public async Task HandleAsync(GameSession gameSession, ClientMessage request) + { + if (gameSession.Habbo == null) + { + await _gameSessionManager.CloseAsync("Not logged in", gameSession); + return; + } + + var pageId = request.ReadInt32() ?? -1; + var offerId = request.ReadInt32() ?? 0; + var mode = request.ReadString() ?? "normal"; + + if (!_catalogueManager.Pages.TryGetValue(pageId, out var page)) + { + return; + } + + await gameSession.SendComposerAsync(new CatalogPageMessageComposer(page, offerId, mode)); + } +} \ No newline at end of file diff --git a/Communication/Messages/Incoming/Inventory/Badges/SetActivatedBadgesEvent.cs b/Communication/Messages/Incoming/Inventory/Badges/SetActivatedBadgesEvent.cs index 8324a5a..a790e7b 100644 --- a/Communication/Messages/Incoming/Inventory/Badges/SetActivatedBadgesEvent.cs +++ b/Communication/Messages/Incoming/Inventory/Badges/SetActivatedBadgesEvent.cs @@ -32,7 +32,8 @@ public class SetActivatedBadgesEvent : IMessageEvent return; var wearingBadges = new Collection(); - foreach (var currentBadge in gameSession.Habbo.Badges) + var current = gameSession.Habbo.Badges.Where(b => b.Slot != 0); + foreach (var currentBadge in current) { currentBadge.Slot = 0; } diff --git a/Communication/Messages/Outgoing/Catalog/CatalogIndexMessageComposer.cs b/Communication/Messages/Outgoing/Catalog/CatalogIndexMessageComposer.cs new file mode 100644 index 0000000..861b369 --- /dev/null +++ b/Communication/Messages/Outgoing/Catalog/CatalogIndexMessageComposer.cs @@ -0,0 +1,53 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Catalogue; + +namespace Tiger.Communication.Messages.Outgoing.Catalog; + +public class CatalogIndexMessageComposer : IMessageComposer +{ + private readonly IEnumerable _pages; + private readonly string _mode; + + public CatalogIndexMessageComposer(IEnumerable pages, string mode) + { + _pages = pages; + _mode = mode; + } + + public OutgoingHeaders Header => OutgoingHeaders.CatalogIndexMessageComposer; + public void Compose(ServerMessage message) + { + message.AppendBoolean(true); + message.AppendInt32(0); + message.AppendInt32(-1); + message.AppendString(string.Empty); + message.AppendString(string.Empty); + message.AppendInt32(0); + message.AppendInt32(_pages.Count()); + + foreach (var page in _pages) + { + SerializeNode(page, message); + } + + message.AppendBoolean(false); + message.AppendString(_mode); + } + + private void SerializeNode(CataloguePage page, ServerMessage message) + { + message.AppendBoolean(page.Visible); + message.AppendInt32(page.Icon); + message.AppendInt32(page.Id); + message.AppendString(page.InternalName); + message.AppendString(page.Name); + message.AppendInt32(0); + message.AppendInt32(page.Children.Count); + + foreach (var child in page.Children) + { + SerializeNode(child, message); + } + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/Catalog/CatalogPageMessageComposer.cs b/Communication/Messages/Outgoing/Catalog/CatalogPageMessageComposer.cs new file mode 100644 index 0000000..e7bb3ce --- /dev/null +++ b/Communication/Messages/Outgoing/Catalog/CatalogPageMessageComposer.cs @@ -0,0 +1,44 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Catalogue; + +namespace Tiger.Communication.Messages.Outgoing.Catalog; + +public class CatalogPageMessageComposer : IMessageComposer +{ + private readonly CataloguePage _page; + private readonly int _offerId; + private readonly string _mode; + + public CatalogPageMessageComposer(CataloguePage page, int offerId, string mode) + { + _page = page; + _offerId = offerId; + _mode = mode; + } + + public OutgoingHeaders Header => OutgoingHeaders.CatalogPageMessageComposer; + public void Compose(ServerMessage message) + { + message.AppendInt32(_page.Id); + message.AppendString(_mode); + message.AppendString(_page.Layout); + message.AppendInt32(_page.Images.Count); + + foreach (var image in _page.Images) + { + message.AppendString(image); + } + + message.AppendInt32(_page.Texts.Count); + + foreach (var text in _page.Texts) + { + message.AppendString(text); + } + + message.AppendInt32(0); + message.AppendInt32(_offerId); + message.AppendBoolean(_page.SeasonalCurrency); + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/OutgoingHeaders.cs b/Communication/Messages/Outgoing/OutgoingHeaders.cs index c12a13b..4ac66cb 100644 --- a/Communication/Messages/Outgoing/OutgoingHeaders.cs +++ b/Communication/Messages/Outgoing/OutgoingHeaders.cs @@ -55,7 +55,7 @@ public enum OutgoingHeaders CarryObjectMessageComposer = 1474, CatalogPageExpirationComposer = 2668, CatalogPageMessageComposer = 804, - CatalogPagesListComposer = 1032, + CatalogIndexMessageComposer = 1032, CatalogPageWithEarliestExpiryMessageComposer = 2515, CatalogPublishedMessageComposer = 1866, CategoriesWithVisitorCountComposer = 1455, diff --git a/Game/Catalogue/CatalogueManager.cs b/Game/Catalogue/CatalogueManager.cs new file mode 100644 index 0000000..646db5c --- /dev/null +++ b/Game/Catalogue/CatalogueManager.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using Tiger.Storage; + +namespace Tiger.Game.Catalogue; + +public class CatalogueManager : ICatalogueManager +{ + private readonly IRepository _pagesRepository; + private readonly ILogger _logger; + + public IDictionary Pages { get; private set; } + + public CatalogueManager(IRepository pagesRepository, ILogger logger) + { + _pagesRepository = pagesRepository; + _logger = logger; + Pages = new Dictionary(); + } + + public async Task LoadPagesAsync() + { + Pages = (await _pagesRepository.FindByAsync()).ToDictionary(p => p.Id, p => p); + + _logger.LogInformation("Loaded {Count} catalogue pages", Pages.Count); + } +} \ No newline at end of file diff --git a/Game/Catalogue/CataloguePage.cs b/Game/Catalogue/CataloguePage.cs new file mode 100644 index 0000000..6aae078 --- /dev/null +++ b/Game/Catalogue/CataloguePage.cs @@ -0,0 +1,19 @@ +namespace Tiger.Game.Catalogue; + +public class CataloguePage +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual string InternalName { get; set; } = null!; + public virtual string Layout { get; set; } = null!; + public virtual bool Visible { get; set; } + public virtual bool Enabled { get; set; } + public virtual int Icon { get; set; } + public virtual int MinRank { get; set; } + public virtual CataloguePage? Parent { get; set; } + public virtual IList Children { get; set; } = new List(); + public virtual IList Images { get; set; } = new List(); + public virtual IList Texts { get; set; } = new List(); + public virtual bool SeasonalCurrency { get; set; } + public virtual IList Modes { get; set; } = new List(); +} \ No newline at end of file diff --git a/Game/Catalogue/CataloguePageMap.cs b/Game/Catalogue/CataloguePageMap.cs new file mode 100644 index 0000000..13708b6 --- /dev/null +++ b/Game/Catalogue/CataloguePageMap.cs @@ -0,0 +1,27 @@ +using FluentNHibernate.Mapping; +using Tiger.Storage; + +namespace Tiger.Game.Catalogue; + +public class CataloguePageMap : ClassMap +{ + public CataloguePageMap() + { + Table("catalogue_pages"); + LazyLoad(); + Id(c => c.Id).Column("id").GeneratedBy.Identity(); + Map(c => c.Name).Column("name").Not.Nullable(); + Map(c => c.InternalName).Column("internal_name").Not.Nullable(); + Map(c => c.Layout).Column("layout").Not.Nullable(); + Map(c => c.Visible).Column("visible").Not.Nullable(); + Map(c => c.Enabled).Column("enabled").Not.Nullable(); + Map(c => c.Icon).Column("icon").Not.Nullable(); + Map(c => c.MinRank).Column("min_rank").Not.Nullable(); + References(x => x.Parent).Column("parent_id").Nullable(); + HasMany(x => x.Children).KeyColumn("parent_id").Inverse().Cascade.AllDeleteOrphan(); + Map(c => c.Images).CustomType(); + Map(c => c.Texts).CustomType(); + Map(c => c.SeasonalCurrency).Column("seasonal_currency").Not.Nullable(); + Map(c => c.Modes).CustomType(); + } +} \ No newline at end of file diff --git a/Game/Catalogue/ICatalogueManager.cs b/Game/Catalogue/ICatalogueManager.cs new file mode 100644 index 0000000..dd33833 --- /dev/null +++ b/Game/Catalogue/ICatalogueManager.cs @@ -0,0 +1,7 @@ +namespace Tiger.Game.Catalogue; + +public interface ICatalogueManager +{ + public IDictionary Pages { get; } + Task LoadPagesAsync(); +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index c709b5a..5518313 100644 --- a/Program.cs +++ b/Program.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration.Yaml; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Tiger.Communication.Messages; +using Tiger.Game.Catalogue; using Tiger.Game.Habbos; using Tiger.Game.Settings; using Tiger.Networking.Game; @@ -33,11 +34,13 @@ collection.AddSingleton(); collection.AddScoped(serviceProvider => serviceProvider.GetRequiredService().OpenSession()); collection.AddScoped(typeof(IRepository<>), typeof(Repository<>)); collection.AddSingleton(); +collection.AddSingleton(); collection.RegisterMessageEvents(); var provider = collection.BuildServiceProvider(); await provider.GetRequiredService().ReloadSettingsAsync(); +await provider.GetRequiredService().LoadPagesAsync(); provider.GetRequiredService().Start($"http://{configuration["Network:Game:Ip"]}:{configuration["Network:Game:Port"]}/"); diff --git a/Storage/IRepository.cs b/Storage/IRepository.cs index 5ffadba..59fd72a 100644 --- a/Storage/IRepository.cs +++ b/Storage/IRepository.cs @@ -6,6 +6,9 @@ public interface IRepository { Task FindAsync(object id); Task SaveAsync(T entity); + Task SaveManyAsync(params T[] entities); + Task SaveManyAsync(IEnumerable entities); + // Task FlushAsync(); Task> FindByAsync(Expression>? expression = null); Task FindOneByAsync(Expression> expression); } \ No newline at end of file diff --git a/Storage/Repository.cs b/Storage/Repository.cs index 4070c69..584658c 100644 --- a/Storage/Repository.cs +++ b/Storage/Repository.cs @@ -24,6 +24,26 @@ public class Repository : IRepository where T : class await _session.FlushAsync(); } + public async Task SaveManyAsync(params T[] entities) + { + foreach (var entity in entities) + { + await _session.SaveOrUpdateAsync(entity); + } + + await _session.FlushAsync(); + } + + public async Task SaveManyAsync(IEnumerable entities) + { + foreach (var entity in entities) + { + await _session.SaveOrUpdateAsync(entity); + } + + await _session.FlushAsync(); + } + public async Task> FindByAsync(Expression>? expression) { var query = _session.Query(); diff --git a/Storage/StringListTypeConverter.cs b/Storage/StringListTypeConverter.cs new file mode 100644 index 0000000..2103a40 --- /dev/null +++ b/Storage/StringListTypeConverter.cs @@ -0,0 +1,67 @@ +using System.Data; +using System.Data.Common; +using System.Text.Json; +using NHibernate.Engine; +using NHibernate.SqlTypes; +using NHibernate.UserTypes; + +namespace Tiger.Storage; + +public class StringListTypeConverter : IUserType +{ + public new bool Equals(object x, object y) + { + return object.Equals(x, y); + } + + public int GetHashCode(object x) + { + return x.GetHashCode(); + } + + public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner) + { + var r = rs[names[0]]; + if (r == DBNull.Value) return null!; + var strList = JsonSerializer.Deserialize>(r.ToString() ?? "") ?? new List(); + return strList; + } + + public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session) + { + if (value == null!) + { + ((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value; + } + else + { + var strList = (List)value; + var strValue = JsonSerializer.Serialize(strList); + ((IDataParameter)cmd.Parameters[index]).Value = strValue; + } + } + + public object DeepCopy(object value) + { + return value; + } + + public object Replace(object original, object target, object owner) + { + return original; + } + + public object Assemble(object cached, object owner) + { + return cached; + } + + public object Disassemble(object value) + { + return value; + } + + public SqlType[] SqlTypes => new SqlType[] { SqlTypeFactory.GetString(8000) }; + public Type ReturnedType => typeof(List); + public bool IsMutable => true; +} \ No newline at end of file diff --git a/TigerEmu.csproj b/TigerEmu.csproj index 2fedcd8..e45d168 100644 --- a/TigerEmu.csproj +++ b/TigerEmu.csproj @@ -23,7 +23,7 @@ - PreserveNewest + Always diff --git a/appsettings.yaml b/appsettings.yaml index ed00b40..f25cd1d 100644 --- a/appsettings.yaml +++ b/appsettings.yaml @@ -1,7 +1,7 @@ Database: - Host: 127.0.0.1 + Host: localhost Port: 3306 - Username: root + Username: tiger Password: 123 Database: tiger_db MinPool: 5