diff --git a/src/Fragment.NetSlum.Networking/Constants/OpCodes.cs b/src/Fragment.NetSlum.Networking/Constants/OpCodes.cs index 147595a..4d4c4e9 100644 --- a/src/Fragment.NetSlum.Networking/Constants/OpCodes.cs +++ b/src/Fragment.NetSlum.Networking/Constants/OpCodes.cs @@ -329,7 +329,12 @@ public enum OpCodes : ushort DataPurchaseGuildShopItem = 0x770C, DataPurchaseGuildShopItemResponse = 0x770D, DataGuildDonateItem = 0x7702, + DataGuildItemPriceResponse = 0x7704, + DataAddItemToGuildResponse = 0x7705, DataGuildGetDonationSettings = 0x7879, + DataGuildDonationSettingsResponse = 0x787a, + DataUnknown787b = 0x787b, + DataUnknown787cResponse = 0x787c, DataGuildUpdateItemPricingAvailability = 0x7703, DataUpdateGuildShopItem = 0x7712, DataUpdateGuildShopItemResponse = 0x7713, diff --git a/src/Fragment.NetSlum.Networking/Contexts/GuildShopContextAccessor.cs b/src/Fragment.NetSlum.Networking/Contexts/GuildShopContextAccessor.cs new file mode 100644 index 0000000..4dd1edd --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Contexts/GuildShopContextAccessor.cs @@ -0,0 +1,18 @@ +using Fragment.NetSlum.Persistence.Entities; + +namespace Fragment.NetSlum.Networking.Contexts; + +/// +/// Stores contextual information about actions happening in a guild shop for a session +/// +public class GuildShopContextAccessor +{ + public record GuildShopItemDonation(uint ToGuildId, uint ItemId, ushort Quantity); + + public struct GuildShopContext + { + public GuildShopItemDonation? Donation { get; set; } + } + + public GuildShopContext Current = new(); +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/DonateItemToGuildRequest.cs b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/DonateItemToGuildRequest.cs new file mode 100644 index 0000000..5c10410 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/DonateItemToGuildRequest.cs @@ -0,0 +1,56 @@ +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Threading.Tasks; +using Fragment.NetSlum.Networking.Attributes; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Contexts; +using Fragment.NetSlum.Networking.Objects; +using Fragment.NetSlum.Networking.Packets.Response.Guilds; +using Fragment.NetSlum.Networking.Sessions; +using Fragment.NetSlum.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Fragment.NetSlum.Networking.Packets.Request.Guilds; + +[FragmentPacket(MessageType.Data, OpCodes.DataGuildDonateItem)] +public class DonateItemToGuildRequest : BaseRequest +{ + private readonly FragmentContext _database; + private readonly GuildShopContextAccessor _guildShopContextAccessor; + private readonly ILogger _logger; + + public DonateItemToGuildRequest(FragmentContext database, GuildShopContextAccessor guildShopContextAccessor, ILogger logger) + { + _database = database; + _guildShopContextAccessor = guildShopContextAccessor; + _logger = logger; + } + + public override async ValueTask> GetResponse(FragmentTcpSession session, FragmentMessage request) + { + var guildId = BinaryPrimitives.ReadUInt16BigEndian(request.Data.Span[..2]); + var guildItemId = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[2..6]); + var itemQuantity = BinaryPrimitives.ReadUInt16BigEndian(request.Data.Span[6..8]); + + var guildItem = await _database.GuildShopItems + .AsNoTracking() + .FirstOrDefaultAsync(i => i.ItemId == guildItemId && i.GuildId == guildId); + + if (guildItem == null) + { + _logger.LogWarning("Player {PlayerId}({PlayerName}) attempted to donate unknown item {ItemId} to guild {GuildId}", session.CharacterId, session.CharacterInfo!.CharacterName, guildItemId, session.GuildId); + return SingleMessageAsync(new GuildItemPriceResponse() + .Build()); + } + + _guildShopContextAccessor.Current.Donation = new GuildShopContextAccessor.GuildShopItemDonation(guildId, guildItemId, itemQuantity); + + _logger.LogInformation("Player {PlayerId}({PlayerName}) is donating item {ItemId} to guild {GuildId}", session.CharacterId, session.CharacterInfo!.CharacterName, guildItemId, session.GuildId); + + return SingleMessageAsync(new GuildItemPriceResponse() + .SetGeneralPrice(guildItem.Price) + .SetMemberPrice(guildItem.MemberPrice) + .Build()); + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildDonationSettingsRequest.cs b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildDonationSettingsRequest.cs new file mode 100644 index 0000000..a35c7d9 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildDonationSettingsRequest.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Fragment.NetSlum.Networking.Attributes; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Objects; +using Fragment.NetSlum.Networking.Packets.Response.Guilds; +using Fragment.NetSlum.Networking.Sessions; +using Fragment.NetSlum.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Fragment.NetSlum.Networking.Packets.Request.Guilds; + +[FragmentPacket(MessageType.Data, OpCodes.DataGuildGetDonationSettings)] +public class GetGuildDonationSettingsRequest : BaseRequest +{ + private readonly FragmentContext _database; + private readonly ILogger _logger; + + public GetGuildDonationSettingsRequest(FragmentContext database, ILogger logger) + { + _database = database; + _logger = logger; + } + public override async ValueTask> GetResponse(FragmentTcpSession session, FragmentMessage request) + { + var guild = await _database.Guilds + .AsNoTracking() + .FirstOrDefaultAsync(g => g.Id == session.GuildId); + + var isGuildMaster = guild != null && guild.LeaderId == session.GuildId; + + _logger.LogInformation("Guild donation settings requested for guild ID {GuildId} by player {PlayerId}({PlayerName}). Is Guild Master? {IsGuildMaster}", session.GuildId, session.CharacterId, session.CharacterInfo!.CharacterName, isGuildMaster); + + return SingleMessageAsync(new GuildDonationSettingsResponse() + .SetIsGuildMaster(isGuildMaster) + .Build()); + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildShopItemCatalogRequest.cs b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildShopItemCatalogRequest.cs index 88e5922..f7f890b 100644 --- a/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildShopItemCatalogRequest.cs +++ b/src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildShopItemCatalogRequest.cs @@ -29,6 +29,9 @@ public override ValueTask> GetResponse(FragmentTcpS { var reader = new SpanReader(request.Data.Span); var categoryId = reader.ReadUInt16(); + + // The old database classified items as the category ID + item ID. So we need to make this backwards compatible with that... + reader.Skip(-2); var itemId = reader.ReadUInt32(); _logger.LogDebug("GetGuildShopItemCatalogRequest: categoryId={CategoryId}, itemId={ItemId}", categoryId, itemId); diff --git a/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/AddItemToGuildInventoryResponse.cs b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/AddItemToGuildInventoryResponse.cs new file mode 100644 index 0000000..30d1d51 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/AddItemToGuildInventoryResponse.cs @@ -0,0 +1,28 @@ +using Fragment.NetSlum.Core.Buffers; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Objects; + +namespace Fragment.NetSlum.Networking.Packets.Response.Guilds; + +public class AddItemToGuildInventoryResponse : BaseResponse +{ + private readonly ushort _quantityAdded; + + public AddItemToGuildInventoryResponse(ushort quantityAdded) + { + _quantityAdded = quantityAdded; + } + + public override FragmentMessage Build() + { + var writer = new MemoryWriter(sizeof(ushort)); + writer.Write(_quantityAdded); + + return new FragmentMessage + { + MessageType = MessageType.Data, + DataPacketType = OpCodes.DataAddItemToGuildResponse, + Data = writer.Buffer, + }; + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildDonationSettingsResponse.cs b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildDonationSettingsResponse.cs new file mode 100644 index 0000000..6e817e2 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildDonationSettingsResponse.cs @@ -0,0 +1,35 @@ +using Fragment.NetSlum.Core.Buffers; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Objects; + +namespace Fragment.NetSlum.Networking.Packets.Response.Guilds; + +public class GuildDonationSettingsResponse : BaseResponse +{ + private bool _isGuildMaster = false; + + public GuildDonationSettingsResponse SetIsGuildMaster(bool isGuildMaster) + { + _isGuildMaster = isGuildMaster; + + return this; + } + + public override FragmentMessage Build() + { + var writer = new MemoryWriter(sizeof(uint) * 2); + + if (_isGuildMaster) + { + writer.Write(1u); + writer.Write(1u); + } + + return new FragmentMessage + { + MessageType = MessageType.Data, + DataPacketType = OpCodes.DataGuildDonationSettingsResponse, + Data = writer.Buffer, + }; + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildItemPriceResponse.cs b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildItemPriceResponse.cs new file mode 100644 index 0000000..eb09de3 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/GuildItemPriceResponse.cs @@ -0,0 +1,40 @@ +using Fragment.NetSlum.Core.Buffers; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Objects; + +namespace Fragment.NetSlum.Networking.Packets.Response.Guilds; + +public class GuildItemPriceResponse : BaseResponse +{ + private uint _generalPrice; + private uint _memberPrice; + + public GuildItemPriceResponse SetGeneralPrice(uint generalPrice) + { + _generalPrice = generalPrice; + + return this; + } + + public GuildItemPriceResponse SetMemberPrice(uint memberPrice) + { + _memberPrice = memberPrice; + + return this; + } + + public override FragmentMessage Build() + { + var writer = new MemoryWriter(sizeof(uint) * 2); + + writer.Write(_generalPrice); + writer.Write(_memberPrice); + + return new FragmentMessage + { + MessageType = MessageType.Data, + DataPacketType = OpCodes.DataGuildItemPriceResponse, + Data = writer.Buffer, + }; + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/Unknown787bRequest.cs b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/Unknown787bRequest.cs new file mode 100644 index 0000000..111a6ee --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/Unknown787bRequest.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Fragment.NetSlum.Networking.Attributes; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Objects; +using Fragment.NetSlum.Networking.Packets.Request; +using Fragment.NetSlum.Networking.Packets.Response.Misc; +using Fragment.NetSlum.Networking.Sessions; + +namespace Fragment.NetSlum.Networking.Packets.Response.Guilds; + +/// +/// This packet is called after successfully donating an item to a guild shop. Unsure of what its actual usage is for. +/// +[FragmentPacket(MessageType.Data, OpCodes.DataUnknown787b)] +public class Unknown787bRequest : BaseRequest +{ + public override ValueTask> GetResponse(FragmentTcpSession session, FragmentMessage request) + { + return SingleMessage(new UnknownResponse(OpCodes.DataUnknown787cResponse).Build()); + } +} diff --git a/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/UpdateGuildItemPricingAvailabilityRequest.cs b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/UpdateGuildItemPricingAvailabilityRequest.cs new file mode 100644 index 0000000..76aa8a0 --- /dev/null +++ b/src/Fragment.NetSlum.Networking/Packets/Response/Guilds/UpdateGuildItemPricingAvailabilityRequest.cs @@ -0,0 +1,90 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Threading.Tasks; +using Fragment.NetSlum.Networking.Attributes; +using Fragment.NetSlum.Networking.Constants; +using Fragment.NetSlum.Networking.Contexts; +using Fragment.NetSlum.Networking.Objects; +using Fragment.NetSlum.Networking.Packets.Request; +using Fragment.NetSlum.Networking.Sessions; +using Fragment.NetSlum.Persistence; +using Fragment.NetSlum.Persistence.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Fragment.NetSlum.Networking.Packets.Response.Guilds; + +[FragmentPacket(MessageType.Data, OpCodes.DataGuildUpdateItemPricingAvailability)] +public class UpdateGuildItemPricingAvailabilityRequest : BaseRequest +{ + private readonly FragmentContext _database; + private readonly GuildShopContextAccessor _guildShopContextAccessor; + private readonly ILogger _logger; + + public UpdateGuildItemPricingAvailabilityRequest(FragmentContext database, GuildShopContextAccessor guildShopContextAccessor, + ILogger logger) + { + _database = database; + _guildShopContextAccessor = guildShopContextAccessor; + _logger = logger; + } + + public override async ValueTask> GetResponse(FragmentTcpSession session, FragmentMessage request) + { + var generalPrice = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[..4]); + var memberPrice = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[4..8]); + var isGeneral = Convert.ToBoolean(request.Data.Span[8]); + var isMember = Convert.ToBoolean(request.Data.Span[9]); + + var guild = await _database.Guilds + .AsNoTracking() + .FirstOrDefaultAsync(g => g.Id == session.GuildId); + + var isGuildMaster = guild != null && guild.LeaderId == session.GuildId; + + var donatedItem = _guildShopContextAccessor.Current.Donation; + + if (donatedItem == null) + { + _logger.LogWarning("Guild pricing availability was requested, but session does not have a donation in progress. Sending 0 quantity response"); + return SingleMessageAsync(new AddItemToGuildInventoryResponse(0).Build()); + } + + var guildItem = await _database.GuildShopItems + .FirstOrDefaultAsync(i => i.ItemId == donatedItem.ItemId && i.GuildId == donatedItem.ToGuildId); + + if (guildItem == null) + { + _logger.LogWarning("While processing pricing availability request, guild item {GuildItemId} does not exist. Creating new entry", donatedItem.ItemId); + guildItem = new GuildShopItem + { + ItemId = (int)donatedItem.ItemId, + GuildId = session.GuildId, + AvailableForGeneral = false, + AvailableForMember = false, + }; + } + + guildItem.Quantity += donatedItem.Quantity; + + if (isGuildMaster) + { + guildItem.Price = generalPrice; + guildItem.MemberPrice = memberPrice; + guildItem.AvailableForGeneral = isGeneral; + guildItem.AvailableForMember = isMember; + } + + _database.GuildShopItems.Update(guildItem); + + await _database.SaveChangesAsync(); + + // Ensure we reset/remove the donation from the current context. + _guildShopContextAccessor.Current.Donation = null; + _logger.LogWarning("Player {PlayerId} ({PlayerName}) successfully donated {ItemQuantity} items with ID of {GuildItemId} to guild {GuildId}", + session.CharacterId, session.CharacterInfo!.CharacterName, donatedItem.Quantity, donatedItem.ItemId, session.GuildId); + + return SingleMessageAsync(new AddItemToGuildInventoryResponse(donatedItem.Quantity).Build()); + } +} diff --git a/src/Fragment.NetSlum.Server/Api/Endpoints/News/UpdateMotdEndpoint.cs b/src/Fragment.NetSlum.Server/Api/Endpoints/News/UpdateMotdEndpoint.cs index 84a0371..76b9460 100644 --- a/src/Fragment.NetSlum.Server/Api/Endpoints/News/UpdateMotdEndpoint.cs +++ b/src/Fragment.NetSlum.Server/Api/Endpoints/News/UpdateMotdEndpoint.cs @@ -6,7 +6,6 @@ using Fragment.NetSlum.Persistence; using Fragment.NetSlum.Server.Api.Models; using Fragment.NetSlum.Server.Api.ViewModels; -using Microsoft.AspNetCore.Authentication.JwtBearer; namespace Fragment.NetSlum.Server.Api.Endpoints.News; diff --git a/src/Fragment.NetSlum.Server/Handlers/Character/RegisterCharacterCommandHandler.cs b/src/Fragment.NetSlum.Server/Handlers/Character/RegisterCharacterCommandHandler.cs index 1873368..6b5d413 100644 --- a/src/Fragment.NetSlum.Server/Handlers/Character/RegisterCharacterCommandHandler.cs +++ b/src/Fragment.NetSlum.Server/Handlers/Character/RegisterCharacterCommandHandler.cs @@ -6,7 +6,6 @@ using Fragment.NetSlum.Core.CommandBus.Contracts.Commands; using Fragment.NetSlum.Networking.Commands.Characters; using Fragment.NetSlum.Persistence; -using Fragment.NetSlum.Persistence.Entities; using Fragment.NetSlum.Server.Mappings; using Microsoft.EntityFrameworkCore; diff --git a/src/Fragment.NetSlum.Server/Program.cs b/src/Fragment.NetSlum.Server/Program.cs index 4ff563f..ee4f445 100644 --- a/src/Fragment.NetSlum.Server/Program.cs +++ b/src/Fragment.NetSlum.Server/Program.cs @@ -10,6 +10,7 @@ using FastEndpoints.Swagger; using FastEndpoints.Security; using Fragment.NetSlum.Core.CommandBus; +using Fragment.NetSlum.Networking.Contexts; using Fragment.NetSlum.Networking.Extensions; using Fragment.NetSlum.Networking.Stores; using Fragment.NetSlum.Persistence; @@ -175,6 +176,7 @@ builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); +builder.Services.AddScoped(); var app = builder.Build();