diff --git a/Api/Utility/LINQExtension.cs b/Api/Utility/LINQExtension.cs new file mode 100644 index 0000000..1185ef8 --- /dev/null +++ b/Api/Utility/LINQExtension.cs @@ -0,0 +1,17 @@ +namespace Milimoe.FunGame.Core.Api.Utility +{ + public static class LINQExtension + { + public static IEnumerable GetPage(this IEnumerable list, int showPage, int pageSize) + { + return [.. list.Skip((showPage - 1) * pageSize).Take(pageSize)]; + } + + public static int MaxPage(this IEnumerable list, int pageSize) + { + if (pageSize <= 0) pageSize = 1; + int page = (int)Math.Ceiling((double)list.Count() / pageSize); + return page > 0 ? page : 1; + } + } +} diff --git a/Entity/Trade/Market.cs b/Entity/Trade/Market.cs new file mode 100644 index 0000000..79d9864 --- /dev/null +++ b/Entity/Trade/Market.cs @@ -0,0 +1,67 @@ +using Milimoe.FunGame.Core.Interface.Entity; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Entity +{ + public class Market : BaseEntity + { + public string Description { get; set; } = ""; + public DateTime? StartTime { get; set; } = null; + public DateTime? EndTime { get; set; } = null; + public DateTime? StartTimeOfDay { get; set; } = null; + public DateTime? EndTimeOfDay { get; set; } = null; + public Dictionary MarketItems { get; } = []; + + public Market(string name) + { + Name = name; + } + + public override string ToString() + { + return Name; + } + + public void AddItem(User user, Item item, double price, int stock, string name = "") + { + if (MarketItems.Values.FirstOrDefault(m => m.Item.Id == item.Id && m.Item.Name == item.Name && m.Price == price && m.User == user.Id && m.Status == MarketItemState.Listed) is MarketItem marketItem) + { + marketItem.Stock += stock; + marketItem.Item.Price = (marketItem.Item.Price + item.Price) / marketItem.Stock; + } + else + { + long id = MarketItems.Count > 0 ? MarketItems.Keys.Max() + 1 : 1; + if (name.Trim() == "") + { + name = item.Name; + } + marketItem = new() + { + Id = id, + User = user.Id, + Username = user.Username, + Item = item, + Price = price, + Stock = stock, + Name = name + }; + MarketItems.Add(id, marketItem); + } + } + + public void AddItems(User user, Item[] items, double price) + { + for (int index = 0; index < items.Length; index++) + { + Item item = items[index]; + AddItem(user, item, price, 1); + } + } + + public override bool Equals(IBaseEntity? other) + { + return other is Market && other.GetIdName() == GetIdName(); + } + } +} diff --git a/Entity/Trade/MarketItem.cs b/Entity/Trade/MarketItem.cs index 3ba5340..bb2a613 100644 --- a/Entity/Trade/MarketItem.cs +++ b/Entity/Trade/MarketItem.cs @@ -6,13 +6,20 @@ namespace Milimoe.FunGame.Core.Entity { public class MarketItem : BaseEntity { - public User User { get; set; } + public long User { get; set; } = 0; + public string Username { get; set; } = ""; public Item Item { get; set; } public double Price { get; set; } = 0; + public int Stock { get; set; } = 0; public DateTime CreateTime { get; set; } = DateTime.Now; public DateTime? FinishTime { get; set; } = null; public MarketItemState Status { get; set; } = MarketItemState.Listed; - public User? Buyer { get; set; } = null; + public HashSet Buyers { get; set; } = []; + + public override string ToString() + { + return Item.Name; + } public override bool Equals(IBaseEntity? other) { @@ -21,7 +28,6 @@ namespace Milimoe.FunGame.Core.Entity public MarketItem() { - User = Factory.GetUser(); Item = Factory.GetItem(); } } diff --git a/Entity/Trade/Store.cs b/Entity/Trade/Store.cs index d7ba4b9..a99faa8 100644 --- a/Entity/Trade/Store.cs +++ b/Entity/Trade/Store.cs @@ -60,16 +60,40 @@ namespace Milimoe.FunGame.Core.Entity if (StartTimeOfDay.HasValue && EndTimeOfDay.HasValue) { builder.AppendLine($"每日营业时间:{StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)} 至 {EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)}"); - DateTime now = DateTime.Now; - if (StartTimeOfDay.Value > now || EndTimeOfDay.Value < now) builder.AppendLine($"商店现在还未开始营业。"); } else { builder.AppendLine($"[ 24H ] 全天营业"); } + DateTime now = DateTime.Now; + TimeSpan nowTimeOfDay = now.TimeOfDay; + bool isStoreOpen = true; + bool isStoreOpenInDate = true; + if (StartTime.HasValue && StartTime.Value > now || EndTime.HasValue && EndTime.Value < now) + { + isStoreOpen = false; + isStoreOpenInDate = false; + } + if (isStoreOpen && StartTimeOfDay.HasValue && EndTimeOfDay.HasValue) + { + TimeSpan startTimeSpan = StartTimeOfDay.Value.TimeOfDay; + TimeSpan endTimeSpan = EndTimeOfDay.Value.TimeOfDay; + if (startTimeSpan <= endTimeSpan) + { + isStoreOpen = nowTimeOfDay >= startTimeSpan && nowTimeOfDay <= endTimeSpan; + } + else + { + isStoreOpen = nowTimeOfDay >= startTimeSpan || nowTimeOfDay <= endTimeSpan; + } + } + if (!isStoreOpen) + { + builder.AppendLine($"商店现在不在营业时间内。"); + } builder.AppendLine($"☆--- 商品列表 ---☆"); Goods[] goodsValid = [.. Goods.Values.Where(g => !g.ExpireTime.HasValue || g.ExpireTime.Value > DateTime.Now)]; - if (goodsValid.Length == 0) + if (!isStoreOpen || goodsValid.Length == 0) { builder.AppendLine("当前没有商品可供购买,过一段时间再来吧。"); } @@ -79,9 +103,9 @@ namespace Milimoe.FunGame.Core.Entity { builder.AppendLine(goods.ToString(user)); } - builder.AppendLine("提示:使用【商店查看+序号】查看物品详细信息,使用【商店购买+序号】购买物品(指令在 2 分钟内可用)。"); + builder.AppendLine("提示:使用【商店查看+序号】查看商品详细信息,使用【商店购买+序号】购买商品(指令在 2 分钟内可用)。"); } - if (AutoRefresh) + if (isStoreOpenInDate && AutoRefresh) { builder.AppendLine($"商品将在 {NextRefreshDate.ToString(General.GeneralDateTimeFormatChinese)} 刷新。"); } diff --git a/Library/Common/JsonConverter/MarketConverter.cs b/Library/Common/JsonConverter/MarketConverter.cs new file mode 100644 index 0000000..fabdf64 --- /dev/null +++ b/Library/Common/JsonConverter/MarketConverter.cs @@ -0,0 +1,96 @@ +using System.Text.Json; +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Common.Architecture; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Library.Common.JsonConverter +{ + public class MarketConverter : BaseEntityConverter + { + public override Market NewInstance() + { + return new Market("市场"); + } + + public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Market result, Dictionary convertingContext) + { + switch (propertyName) + { + case nameof(Market.Name): + result.Name = reader.GetString() ?? ""; + break; + case nameof(Market.Description): + result.Description = reader.GetString() ?? ""; + break; + case nameof(Market.StartTime): + string dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time)) + { + result.StartTime = time; + } + else + { + result.StartTime = DateTime.MinValue; + } + break; + case nameof(Market.EndTime): + dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time)) + { + result.EndTime = time; + } + else + { + result.EndTime = DateTime.MinValue; + } + break; + case nameof(Market.StartTimeOfDay): + dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time)) + { + result.StartTimeOfDay = time; + } + else + { + result.StartTimeOfDay = DateTime.MinValue; + } + break; + case nameof(Market.EndTimeOfDay): + dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time)) + { + result.EndTimeOfDay = time; + } + else + { + result.EndTimeOfDay = DateTime.MinValue; + } + break; + case nameof(Market.MarketItems): + Dictionary marketItems = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + foreach (long id in marketItems.Keys) + { + result.MarketItems[id] = marketItems[id]; + } + break; + } + } + + public override void Write(Utf8JsonWriter writer, Market value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteString(nameof(Market.Name), value.Name); + writer.WriteString(nameof(Market.Description), value.Description); + if (value.StartTime.HasValue) writer.WriteString(nameof(Market.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat)); + if (value.EndTime.HasValue) writer.WriteString(nameof(Market.EndTime), value.EndTime.Value.ToString(General.GeneralDateTimeFormat)); + if (value.StartTimeOfDay.HasValue) writer.WriteString(nameof(Market.StartTimeOfDay), value.StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormat)); + if (value.EndTimeOfDay.HasValue) writer.WriteString(nameof(Market.EndTimeOfDay), value.EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormat)); + writer.WritePropertyName(nameof(Market.MarketItems)); + JsonSerializer.Serialize(writer, value.MarketItems, options); + + writer.WriteEndObject(); + } + } +} diff --git a/Library/Common/JsonConverter/MarketItemConverter.cs b/Library/Common/JsonConverter/MarketItemConverter.cs new file mode 100644 index 0000000..08c5f01 --- /dev/null +++ b/Library/Common/JsonConverter/MarketItemConverter.cs @@ -0,0 +1,93 @@ +using System.Text.Json; +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Common.Architecture; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Library.Common.JsonConverter +{ + public class MarketItemConverter : BaseEntityConverter + { + public override MarketItem NewInstance() + { + return new MarketItem(); + } + + public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref MarketItem result, Dictionary convertingContext) + { + switch (propertyName) + { + case nameof(MarketItem.Id): + result.Id = reader.GetInt64(); + break; + case nameof(MarketItem.User): + result.User = reader.GetInt64(); + break; + case nameof(MarketItem.Username): + result.Username = reader.GetString() ?? ""; + break; + case nameof(MarketItem.Item): + result.Item = NetworkUtility.JsonDeserialize(ref reader, options) ?? Factory.GetItem(); + break; + case nameof(MarketItem.Stock): + result.Stock = Convert.ToInt32(reader.GetInt64()); + break; + case nameof(MarketItem.Name): + result.Name = reader.GetString() ?? ""; + break; + case nameof(MarketItem.Price): + result.Price = reader.GetDouble(); + break; + case nameof(MarketItem.CreateTime): + string dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time)) + { + result.CreateTime = time; + } + else + { + result.CreateTime = DateTime.MinValue; + } + break; + case nameof(MarketItem.FinishTime): + dateString = reader.GetString() ?? ""; + if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time)) + { + result.FinishTime = time; + } + else + { + result.FinishTime = DateTime.MinValue; + } + break; + case nameof(MarketItem.Status): + result.Status = (MarketItemState)reader.GetInt32(); + break; + case nameof(MarketItem.Buyers): + result.Buyers = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + } + } + + public override void Write(Utf8JsonWriter writer, MarketItem value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber(nameof(MarketItem.Id), value.Id); + writer.WriteNumber(nameof(MarketItem.User), value.User); + writer.WriteString(nameof(MarketItem.Username), value.Username); + writer.WritePropertyName(nameof(MarketItem.Item)); + JsonSerializer.Serialize(writer, value.Item, options); + writer.WriteNumber(nameof(MarketItem.Stock), value.Stock); + writer.WriteString(nameof(MarketItem.Name), value.Name); + writer.WriteNumber(nameof(MarketItem.Price), value.Price); + writer.WriteString(nameof(MarketItem.CreateTime), value.CreateTime.ToString(General.GeneralDateTimeFormat)); + if (value.FinishTime.HasValue) writer.WriteString(nameof(MarketItem.FinishTime), value.FinishTime.Value.ToString(General.GeneralDateTimeFormat)); + writer.WriteNumber(nameof(MarketItem.Status), (int)value.Status); + writer.WritePropertyName(nameof(MarketItem.Buyers)); + JsonSerializer.Serialize(writer, value.Buyers, options); + + writer.WriteEndObject(); + } + } +} diff --git a/Library/Constant/ConstantSet.cs b/Library/Constant/ConstantSet.cs index 2550f58..0668c7e 100644 --- a/Library/Constant/ConstantSet.cs +++ b/Library/Constant/ConstantSet.cs @@ -62,6 +62,16 @@ namespace Milimoe.FunGame.Core.Library.Constant _ => "已过期" }; } + + public static string GetMarketItemStatus(MarketItemState status) + { + return status switch + { + MarketItemState.Delisted => "已下架", + MarketItemState.Purchased => "已售罄", + _ => "已上架" + }; + } } /// diff --git a/Library/SQLScript/Entity/MarketItemsQuery.cs b/Library/SQLScript/Entity/MarketItemsQuery.cs index d58e024..52ddb9a 100644 --- a/Library/SQLScript/Entity/MarketItemsQuery.cs +++ b/Library/SQLScript/Entity/MarketItemsQuery.cs @@ -10,10 +10,11 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity public const string Column_ItemGuid = "ItemGuid"; public const string Column_UserId = "UserId"; public const string Column_Price = "Price"; + public const string Column_Stock = "Stock"; public const string Column_CreateTime = "CreateTime"; public const string Column_FinishTime = "FinishTime"; public const string Column_Status = "Status"; - public const string Column_Buyer = "Buyer"; + public const string Column_Buyers = "Buyers"; public const string Select_MarketItems = $"{Command_Select} {Command_All} {Command_From} {TableName}"; @@ -35,14 +36,15 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity return $"{Select_MarketItems} {Command_Where} {Column_Status} = @Status"; } - public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, MarketItemState state = MarketItemState.Listed) + public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, double Stock, MarketItemState state = MarketItemState.Listed) { SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString(); SQLHelper.Parameters["@UserId"] = UserId; SQLHelper.Parameters["@Price"] = Price; + SQLHelper.Parameters["@Stock"] = Stock; SQLHelper.Parameters["@Status"] = (int)state; - return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Status)"; + return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Stock}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Stock, @Status)"; } public static string Update_MarketItemPrice(SQLHelper SQLHelper, Guid ItemGuid, double Price) @@ -51,6 +53,13 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity SQLHelper.Parameters["@Price"] = Price; return $"{Command_Update} {TableName} {Command_Set} {Column_Price} = @Price {Command_Where} {Column_ItemGuid} = @ItemGuid"; } + + public static string Update_MarketItemStock(SQLHelper SQLHelper, Guid ItemGuid, double Stock) + { + SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString(); + SQLHelper.Parameters["@Stock"] = Stock; + return $"{Command_Update} {TableName} {Command_Set} {Column_Stock} = @Stock {Command_Where} {Column_ItemGuid} = @ItemGuid"; + } public static string Update_MarketItemState(SQLHelper SQLHelper, Guid ItemGuid, MarketItemState state) { @@ -59,12 +68,12 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity return $"{Command_Update} {TableName} {Command_Set} {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid"; } - public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, long Buyer) + public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, string Buyers) { SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString(); - SQLHelper.Parameters["@Buyer"] = Buyer; + SQLHelper.Parameters["@Buyers"] = Buyers; SQLHelper.Parameters["@Status"] = (int)MarketItemState.Purchased; - return $"{Command_Update} {TableName} {Command_Set} {Column_Buyer} = @Buyer, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid"; + return $"{Command_Update} {TableName} {Command_Set} {Column_Buyers} = @Buyers, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid"; } public static string Update_MarketItemFinishTime(SQLHelper SQLHelper, Guid ItemGuid, DateTime FinishTime) diff --git a/Library/SQLScript/PatchScript/mysql/2.0.0.250728.sql b/Library/SQLScript/PatchScript/mysql/2.0.0.250728.sql new file mode 100644 index 0000000..c2f1024 --- /dev/null +++ b/Library/SQLScript/PatchScript/mysql/2.0.0.250728.sql @@ -0,0 +1 @@ +ALTER TABLE your_table_name CHANGE COLUMN Buyer Buyers VARCHAR(255) NOT NULL DEFAULT ''; diff --git a/Library/SQLScript/PatchScript/sqlite/2.0.0.250728.sql b/Library/SQLScript/PatchScript/sqlite/2.0.0.250728.sql new file mode 100644 index 0000000..a8089eb --- /dev/null +++ b/Library/SQLScript/PatchScript/sqlite/2.0.0.250728.sql @@ -0,0 +1,43 @@ +PRAGMA foreign_keys = OFF; + +BEGIN TRANSACTION; + +ALTER TABLE MarketItems RENAME TO _MarketItems_old; + +CREATE TABLE MarketItems ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + ItemGuid TEXT NOT NULL DEFAULT '', + UserId INTEGER NOT NULL DEFAULT 0, + Price REAL NOT NULL DEFAULT 0, + CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')), + FinishTime DATETIME DEFAULT NULL, + Status INTEGER NOT NULL DEFAULT 0, + Buyers TEXT NOT NULL DEFAULT '' +); + +INSERT INTO MarketItems ( + Id, + ItemGuid, + UserId, + Price, + CreateTime, + FinishTime, + Status, + Buyers +) +SELECT + Id, + ItemGuid, + UserId, + Price, + CreateTime, + FinishTime, + Status, + CAST(Buyer AS TEXT) +FROM _MarketItems_old; + +DROP TABLE _MarketItems_old; + +COMMIT; + +PRAGMA foreign_keys = ON; diff --git a/Library/SQLScript/fungame.sql b/Library/SQLScript/fungame.sql index a0cd079..abb3fe2 100644 --- a/Library/SQLScript/fungame.sql +++ b/Library/SQLScript/fungame.sql @@ -95,7 +95,7 @@ CREATE TABLE `MarketItems` ( `CreateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `FinishTime` datetime DEFAULT NULL, `Status` int(10) NOT NULL DEFAULT '0', - `Buyer` bigint(20) NOT NULL DEFAULT '0', + `Buyers` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Library/SQLScript/fungame_sqlite.sql b/Library/SQLScript/fungame_sqlite.sql index 4397135..c79dfb1 100644 --- a/Library/SQLScript/fungame_sqlite.sql +++ b/Library/SQLScript/fungame_sqlite.sql @@ -92,7 +92,7 @@ CREATE TABLE MarketItems ( CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')), FinishTime DATETIME DEFAULT NULL, Status INTEGER NOT NULL DEFAULT 0, - Buyer INTEGER NOT NULL DEFAULT 0 + Buyers TEXT NOT NULL DEFAULT '' ); -- ---------------------------- diff --git a/Service/JsonManager.cs b/Service/JsonManager.cs index af92148..de3c7bd 100644 --- a/Service/JsonManager.cs +++ b/Service/JsonManager.cs @@ -22,7 +22,8 @@ namespace Milimoe.FunGame.Core.Service Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(), new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(), new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(), - new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter(), new RoundRecordConverter(), new ActivityConverter(), new QuestConverter() + new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter(), new RoundRecordConverter(), new ActivityConverter(), new QuestConverter(), + new MarketConverter(), new MarketItemConverter() } };