Skip to content

Commit 1b7e582

Browse files
authored
Add support for donating guild items (#76)
1 parent ab83d20 commit 1b7e582

13 files changed

Lines changed: 338 additions & 2 deletions

src/Fragment.NetSlum.Networking/Constants/OpCodes.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,12 @@ public enum OpCodes : ushort
329329
DataPurchaseGuildShopItem = 0x770C,
330330
DataPurchaseGuildShopItemResponse = 0x770D,
331331
DataGuildDonateItem = 0x7702,
332+
DataGuildItemPriceResponse = 0x7704,
333+
DataAddItemToGuildResponse = 0x7705,
332334
DataGuildGetDonationSettings = 0x7879,
335+
DataGuildDonationSettingsResponse = 0x787a,
336+
DataUnknown787b = 0x787b,
337+
DataUnknown787cResponse = 0x787c,
333338
DataGuildUpdateItemPricingAvailability = 0x7703,
334339
DataUpdateGuildShopItem = 0x7712,
335340
DataUpdateGuildShopItemResponse = 0x7713,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Fragment.NetSlum.Persistence.Entities;
2+
3+
namespace Fragment.NetSlum.Networking.Contexts;
4+
5+
/// <summary>
6+
/// Stores contextual information about actions happening in a guild shop for a session
7+
/// </summary>
8+
public class GuildShopContextAccessor
9+
{
10+
public record GuildShopItemDonation(uint ToGuildId, uint ItemId, ushort Quantity);
11+
12+
public struct GuildShopContext
13+
{
14+
public GuildShopItemDonation? Donation { get; set; }
15+
}
16+
17+
public GuildShopContext Current = new();
18+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Buffers.Binary;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Fragment.NetSlum.Networking.Attributes;
5+
using Fragment.NetSlum.Networking.Constants;
6+
using Fragment.NetSlum.Networking.Contexts;
7+
using Fragment.NetSlum.Networking.Objects;
8+
using Fragment.NetSlum.Networking.Packets.Response.Guilds;
9+
using Fragment.NetSlum.Networking.Sessions;
10+
using Fragment.NetSlum.Persistence;
11+
using Microsoft.EntityFrameworkCore;
12+
using Microsoft.Extensions.Logging;
13+
14+
namespace Fragment.NetSlum.Networking.Packets.Request.Guilds;
15+
16+
[FragmentPacket(MessageType.Data, OpCodes.DataGuildDonateItem)]
17+
public class DonateItemToGuildRequest : BaseRequest
18+
{
19+
private readonly FragmentContext _database;
20+
private readonly GuildShopContextAccessor _guildShopContextAccessor;
21+
private readonly ILogger<DonateItemToGuildRequest> _logger;
22+
23+
public DonateItemToGuildRequest(FragmentContext database, GuildShopContextAccessor guildShopContextAccessor, ILogger<DonateItemToGuildRequest> logger)
24+
{
25+
_database = database;
26+
_guildShopContextAccessor = guildShopContextAccessor;
27+
_logger = logger;
28+
}
29+
30+
public override async ValueTask<ICollection<FragmentMessage>> GetResponse(FragmentTcpSession session, FragmentMessage request)
31+
{
32+
var guildId = BinaryPrimitives.ReadUInt16BigEndian(request.Data.Span[..2]);
33+
var guildItemId = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[2..6]);
34+
var itemQuantity = BinaryPrimitives.ReadUInt16BigEndian(request.Data.Span[6..8]);
35+
36+
var guildItem = await _database.GuildShopItems
37+
.AsNoTracking()
38+
.FirstOrDefaultAsync(i => i.ItemId == guildItemId && i.GuildId == guildId);
39+
40+
if (guildItem == null)
41+
{
42+
_logger.LogWarning("Player {PlayerId}({PlayerName}) attempted to donate unknown item {ItemId} to guild {GuildId}", session.CharacterId, session.CharacterInfo!.CharacterName, guildItemId, session.GuildId);
43+
return SingleMessageAsync(new GuildItemPriceResponse()
44+
.Build());
45+
}
46+
47+
_guildShopContextAccessor.Current.Donation = new GuildShopContextAccessor.GuildShopItemDonation(guildId, guildItemId, itemQuantity);
48+
49+
_logger.LogInformation("Player {PlayerId}({PlayerName}) is donating item {ItemId} to guild {GuildId}", session.CharacterId, session.CharacterInfo!.CharacterName, guildItemId, session.GuildId);
50+
51+
return SingleMessageAsync(new GuildItemPriceResponse()
52+
.SetGeneralPrice(guildItem.Price)
53+
.SetMemberPrice(guildItem.MemberPrice)
54+
.Build());
55+
}
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using Fragment.NetSlum.Networking.Attributes;
4+
using Fragment.NetSlum.Networking.Constants;
5+
using Fragment.NetSlum.Networking.Objects;
6+
using Fragment.NetSlum.Networking.Packets.Response.Guilds;
7+
using Fragment.NetSlum.Networking.Sessions;
8+
using Fragment.NetSlum.Persistence;
9+
using Microsoft.EntityFrameworkCore;
10+
using Microsoft.Extensions.Logging;
11+
12+
namespace Fragment.NetSlum.Networking.Packets.Request.Guilds;
13+
14+
[FragmentPacket(MessageType.Data, OpCodes.DataGuildGetDonationSettings)]
15+
public class GetGuildDonationSettingsRequest : BaseRequest
16+
{
17+
private readonly FragmentContext _database;
18+
private readonly ILogger<GetGuildDonationSettingsRequest> _logger;
19+
20+
public GetGuildDonationSettingsRequest(FragmentContext database, ILogger<GetGuildDonationSettingsRequest> logger)
21+
{
22+
_database = database;
23+
_logger = logger;
24+
}
25+
public override async ValueTask<ICollection<FragmentMessage>> GetResponse(FragmentTcpSession session, FragmentMessage request)
26+
{
27+
var guild = await _database.Guilds
28+
.AsNoTracking()
29+
.FirstOrDefaultAsync(g => g.Id == session.GuildId);
30+
31+
var isGuildMaster = guild != null && guild.LeaderId == session.GuildId;
32+
33+
_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);
34+
35+
return SingleMessageAsync(new GuildDonationSettingsResponse()
36+
.SetIsGuildMaster(isGuildMaster)
37+
.Build());
38+
}
39+
}

src/Fragment.NetSlum.Networking/Packets/Request/Guilds/GetGuildShopItemCatalogRequest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public override ValueTask<ICollection<FragmentMessage>> GetResponse(FragmentTcpS
2929
{
3030
var reader = new SpanReader(request.Data.Span);
3131
var categoryId = reader.ReadUInt16();
32+
33+
// The old database classified items as the category ID + item ID. So we need to make this backwards compatible with that...
34+
reader.Skip(-2);
3235
var itemId = reader.ReadUInt32();
3336

3437
_logger.LogDebug("GetGuildShopItemCatalogRequest: categoryId={CategoryId}, itemId={ItemId}", categoryId, itemId);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Fragment.NetSlum.Core.Buffers;
2+
using Fragment.NetSlum.Networking.Constants;
3+
using Fragment.NetSlum.Networking.Objects;
4+
5+
namespace Fragment.NetSlum.Networking.Packets.Response.Guilds;
6+
7+
public class AddItemToGuildInventoryResponse : BaseResponse
8+
{
9+
private readonly ushort _quantityAdded;
10+
11+
public AddItemToGuildInventoryResponse(ushort quantityAdded)
12+
{
13+
_quantityAdded = quantityAdded;
14+
}
15+
16+
public override FragmentMessage Build()
17+
{
18+
var writer = new MemoryWriter(sizeof(ushort));
19+
writer.Write(_quantityAdded);
20+
21+
return new FragmentMessage
22+
{
23+
MessageType = MessageType.Data,
24+
DataPacketType = OpCodes.DataAddItemToGuildResponse,
25+
Data = writer.Buffer,
26+
};
27+
}
28+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Fragment.NetSlum.Core.Buffers;
2+
using Fragment.NetSlum.Networking.Constants;
3+
using Fragment.NetSlum.Networking.Objects;
4+
5+
namespace Fragment.NetSlum.Networking.Packets.Response.Guilds;
6+
7+
public class GuildDonationSettingsResponse : BaseResponse
8+
{
9+
private bool _isGuildMaster = false;
10+
11+
public GuildDonationSettingsResponse SetIsGuildMaster(bool isGuildMaster)
12+
{
13+
_isGuildMaster = isGuildMaster;
14+
15+
return this;
16+
}
17+
18+
public override FragmentMessage Build()
19+
{
20+
var writer = new MemoryWriter(sizeof(uint) * 2);
21+
22+
if (_isGuildMaster)
23+
{
24+
writer.Write(1u);
25+
writer.Write(1u);
26+
}
27+
28+
return new FragmentMessage
29+
{
30+
MessageType = MessageType.Data,
31+
DataPacketType = OpCodes.DataGuildDonationSettingsResponse,
32+
Data = writer.Buffer,
33+
};
34+
}
35+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Fragment.NetSlum.Core.Buffers;
2+
using Fragment.NetSlum.Networking.Constants;
3+
using Fragment.NetSlum.Networking.Objects;
4+
5+
namespace Fragment.NetSlum.Networking.Packets.Response.Guilds;
6+
7+
public class GuildItemPriceResponse : BaseResponse
8+
{
9+
private uint _generalPrice;
10+
private uint _memberPrice;
11+
12+
public GuildItemPriceResponse SetGeneralPrice(uint generalPrice)
13+
{
14+
_generalPrice = generalPrice;
15+
16+
return this;
17+
}
18+
19+
public GuildItemPriceResponse SetMemberPrice(uint memberPrice)
20+
{
21+
_memberPrice = memberPrice;
22+
23+
return this;
24+
}
25+
26+
public override FragmentMessage Build()
27+
{
28+
var writer = new MemoryWriter(sizeof(uint) * 2);
29+
30+
writer.Write(_generalPrice);
31+
writer.Write(_memberPrice);
32+
33+
return new FragmentMessage
34+
{
35+
MessageType = MessageType.Data,
36+
DataPacketType = OpCodes.DataGuildItemPriceResponse,
37+
Data = writer.Buffer,
38+
};
39+
}
40+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using Fragment.NetSlum.Networking.Attributes;
4+
using Fragment.NetSlum.Networking.Constants;
5+
using Fragment.NetSlum.Networking.Objects;
6+
using Fragment.NetSlum.Networking.Packets.Request;
7+
using Fragment.NetSlum.Networking.Packets.Response.Misc;
8+
using Fragment.NetSlum.Networking.Sessions;
9+
10+
namespace Fragment.NetSlum.Networking.Packets.Response.Guilds;
11+
12+
/// <summary>
13+
/// This packet is called after successfully donating an item to a guild shop. Unsure of what its actual usage is for.
14+
/// </summary>
15+
[FragmentPacket(MessageType.Data, OpCodes.DataUnknown787b)]
16+
public class Unknown787bRequest : BaseRequest
17+
{
18+
public override ValueTask<ICollection<FragmentMessage>> GetResponse(FragmentTcpSession session, FragmentMessage request)
19+
{
20+
return SingleMessage(new UnknownResponse(OpCodes.DataUnknown787cResponse).Build());
21+
}
22+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Buffers.Binary;
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
using Fragment.NetSlum.Networking.Attributes;
6+
using Fragment.NetSlum.Networking.Constants;
7+
using Fragment.NetSlum.Networking.Contexts;
8+
using Fragment.NetSlum.Networking.Objects;
9+
using Fragment.NetSlum.Networking.Packets.Request;
10+
using Fragment.NetSlum.Networking.Sessions;
11+
using Fragment.NetSlum.Persistence;
12+
using Fragment.NetSlum.Persistence.Entities;
13+
using Microsoft.EntityFrameworkCore;
14+
using Microsoft.Extensions.Logging;
15+
16+
namespace Fragment.NetSlum.Networking.Packets.Response.Guilds;
17+
18+
[FragmentPacket(MessageType.Data, OpCodes.DataGuildUpdateItemPricingAvailability)]
19+
public class UpdateGuildItemPricingAvailabilityRequest : BaseRequest
20+
{
21+
private readonly FragmentContext _database;
22+
private readonly GuildShopContextAccessor _guildShopContextAccessor;
23+
private readonly ILogger<UpdateGuildItemPricingAvailabilityRequest> _logger;
24+
25+
public UpdateGuildItemPricingAvailabilityRequest(FragmentContext database, GuildShopContextAccessor guildShopContextAccessor,
26+
ILogger<UpdateGuildItemPricingAvailabilityRequest> logger)
27+
{
28+
_database = database;
29+
_guildShopContextAccessor = guildShopContextAccessor;
30+
_logger = logger;
31+
}
32+
33+
public override async ValueTask<ICollection<FragmentMessage>> GetResponse(FragmentTcpSession session, FragmentMessage request)
34+
{
35+
var generalPrice = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[..4]);
36+
var memberPrice = BinaryPrimitives.ReadUInt32BigEndian(request.Data.Span[4..8]);
37+
var isGeneral = Convert.ToBoolean(request.Data.Span[8]);
38+
var isMember = Convert.ToBoolean(request.Data.Span[9]);
39+
40+
var guild = await _database.Guilds
41+
.AsNoTracking()
42+
.FirstOrDefaultAsync(g => g.Id == session.GuildId);
43+
44+
var isGuildMaster = guild != null && guild.LeaderId == session.GuildId;
45+
46+
var donatedItem = _guildShopContextAccessor.Current.Donation;
47+
48+
if (donatedItem == null)
49+
{
50+
_logger.LogWarning("Guild pricing availability was requested, but session does not have a donation in progress. Sending 0 quantity response");
51+
return SingleMessageAsync(new AddItemToGuildInventoryResponse(0).Build());
52+
}
53+
54+
var guildItem = await _database.GuildShopItems
55+
.FirstOrDefaultAsync(i => i.ItemId == donatedItem.ItemId && i.GuildId == donatedItem.ToGuildId);
56+
57+
if (guildItem == null)
58+
{
59+
_logger.LogWarning("While processing pricing availability request, guild item {GuildItemId} does not exist. Creating new entry", donatedItem.ItemId);
60+
guildItem = new GuildShopItem
61+
{
62+
ItemId = (int)donatedItem.ItemId,
63+
GuildId = session.GuildId,
64+
AvailableForGeneral = false,
65+
AvailableForMember = false,
66+
};
67+
}
68+
69+
guildItem.Quantity += donatedItem.Quantity;
70+
71+
if (isGuildMaster)
72+
{
73+
guildItem.Price = generalPrice;
74+
guildItem.MemberPrice = memberPrice;
75+
guildItem.AvailableForGeneral = isGeneral;
76+
guildItem.AvailableForMember = isMember;
77+
}
78+
79+
_database.GuildShopItems.Update(guildItem);
80+
81+
await _database.SaveChangesAsync();
82+
83+
// Ensure we reset/remove the donation from the current context.
84+
_guildShopContextAccessor.Current.Donation = null;
85+
_logger.LogWarning("Player {PlayerId} ({PlayerName}) successfully donated {ItemQuantity} items with ID of {GuildItemId} to guild {GuildId}",
86+
session.CharacterId, session.CharacterInfo!.CharacterName, donatedItem.Quantity, donatedItem.ItemId, session.GuildId);
87+
88+
return SingleMessageAsync(new AddItemToGuildInventoryResponse(donatedItem.Quantity).Build());
89+
}
90+
}

0 commit comments

Comments
 (0)