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();