From 9dfd61885b223e09b28ba85e80ef9e1875964c75 Mon Sep 17 00:00:00 2001 From: milimoe Date: Tue, 12 May 2026 22:31:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96+=E6=96=B0=E5=8A=9F=E8=83=BD+?= =?UTF-8?q?BUG=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OshimaModules/Regions/OshimaRegion.cs | 5 + OshimaModules/Regions/Players.cs | 40 +++ OshimaServers/Model/CSBettingModels.cs | 129 +++++++++ OshimaServers/Service/CSBettingService.cs | 67 ++++- OshimaWebAPI/Constant/CSBetting.sql | 1 - OshimaWebAPI/Controllers/BettingController.cs | 267 ++++++++++++++++++ .../Controllers/CSBettingController.cs | 2 +- OshimaWebAPI/Controllers/FunGameController.cs | 73 +++++ .../Services/CSBettingInputHandler.cs | 26 +- OshimaWebAPI/Services/RainBOTService.cs | 57 ++++ 10 files changed, 638 insertions(+), 29 deletions(-) create mode 100644 OshimaWebAPI/Controllers/BettingController.cs diff --git a/OshimaModules/Regions/OshimaRegion.cs b/OshimaModules/Regions/OshimaRegion.cs index 993a1ec..4dda1a2 100644 --- a/OshimaModules/Regions/OshimaRegion.cs +++ b/OshimaModules/Regions/OshimaRegion.cs @@ -43,6 +43,11 @@ namespace Oshima.FunGame.OshimaModules.Regions } + public virtual bool AddGoodsToStore(string storeName, List goodsList, bool addToNextRefreshGoods = true) + { + return false; + } + public override string ToString() { StringBuilder builder = new(); diff --git a/OshimaModules/Regions/Players.cs b/OshimaModules/Regions/Players.cs index 32048e6..c230fa2 100644 --- a/OshimaModules/Regions/Players.cs +++ b/OshimaModules/Regions/Players.cs @@ -187,6 +187,46 @@ namespace Oshima.FunGame.OshimaModules.Regions storeTemplate.SaveConfig(); } + public override bool AddGoodsToStore(string storeName, List goodsList, bool addToNextRefreshGoods = true) + { + EntityModuleConfig storeTemplate = new("stores", "dokyo"); + storeTemplate.LoadConfig(); + + Store? store = storeTemplate.Get(storeName); + store ??= storeName switch + { + "dokyo_forge" => CreateNewForgeStore(), + "dokyo_horseracing" => CreateNewHorseRacingStore(), + "dokyo_cooperative" => CreateNewCooperativeStore(), + _ => null + }; + + if (store is null) + { + return false; + } + + long maxKey = store.Goods.Count > 0 ? store.Goods.Keys.Max() : 0; + long newKey = maxKey + 1; + + foreach (Goods goods in goodsList) + { + goods.Id = newKey; + store.Goods[newKey] = goods; + + if (addToNextRefreshGoods) + { + store.NextRefreshGoods[newKey] = goods; + } + + newKey++; + } + + storeTemplate.Add(storeName, store); + storeTemplate.SaveConfig(); + return true; + } + private static Store CreateNewForgeStore() { Store store = new("锻造积分商店") diff --git a/OshimaServers/Model/CSBettingModels.cs b/OshimaServers/Model/CSBettingModels.cs index 4e645e8..43b0dfe 100644 --- a/OshimaServers/Model/CSBettingModels.cs +++ b/OshimaServers/Model/CSBettingModels.cs @@ -81,5 +81,134 @@ namespace Oshima.FunGame.WebAPI.Model [JsonPropertyName("result")] public string? Result { get; set; } + + [JsonPropertyName("stage")] + public string? Stage { get; set; } + } + + public class BettingEvent + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("status")] + public int Status { get; set; } // 0=未开始 1=进行中 2=已结束 + + [JsonPropertyName("start_time")] + public DateTime StartTime { get; set; } + + [JsonPropertyName("end_time")] + public DateTime EndTime { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime UpdatedAt { get; set; } + } + + public class BettingMatch + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("event_id")] + public int EventId { get; set; } + + [JsonPropertyName("stage")] + public string? Stage { get; set; } + + [JsonPropertyName("team1_name")] + public string Team1Name { get; set; } = ""; + + [JsonPropertyName("team1_logo")] + public string? Team1Logo { get; set; } + + [JsonPropertyName("team2_name")] + public string Team2Name { get; set; } = ""; + + [JsonPropertyName("team2_logo")] + public string? Team2Logo { get; set; } + + [JsonPropertyName("status")] + public int Status { get; set; } // 0=未开始 1=进行中 2=已结束 + + [JsonPropertyName("start_time")] + public DateTime StartTime { get; set; } + + [JsonPropertyName("bet_deadline")] + public DateTime BetDeadline { get; set; } + + [JsonPropertyName("result")] + public string? Result { get; set; } + + [JsonPropertyName("winner")] + public int? Winner { get; set; } // 1=team1, 2=team2, null=未定 + + [JsonPropertyName("available_options")] + public string AvailableOptions { get; set; } = "[]"; // JSON 数组字符串 + + [JsonPropertyName("team1_win_odds")] + public decimal Team1WinOdds { get; set; } + + [JsonPropertyName("team2_win_odds")] + public decimal Team2WinOdds { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime UpdatedAt { get; set; } + } + + public class BettingBetRecord + { + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("user_id")] + public long UserId { get; set; } + + [JsonPropertyName("match_id")] + public int MatchId { get; set; } + + [JsonPropertyName("option_type")] + public int OptionType { get; set; } // 1=team1胜 2=team2胜 3=精确比分 4=MVP + + [JsonPropertyName("option_value")] + public string OptionValue { get; set; } = ""; + + [JsonPropertyName("amount")] + public long Amount { get; set; } + + [JsonPropertyName("odds_at_bet")] + public decimal OddsAtBet { get; set; } + + [JsonPropertyName("bet_time")] + public DateTime BetTime { get; set; } + + [JsonPropertyName("is_settled")] + public bool IsSettled { get; set; } + + [JsonPropertyName("payout")] + public long? Payout { get; set; } + + [JsonPropertyName("is_claimed")] + public bool IsClaimed { get; set; } + + [JsonPropertyName("result_note")] + public string? ResultNote { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime UpdatedAt { get; set; } } } \ No newline at end of file diff --git a/OshimaServers/Service/CSBettingService.cs b/OshimaServers/Service/CSBettingService.cs index cc0d86c..977cb69 100644 --- a/OshimaServers/Service/CSBettingService.cs +++ b/OshimaServers/Service/CSBettingService.cs @@ -151,7 +151,7 @@ namespace Oshima.FunGame.WebAPI.Services sql.ExecuteDataSet($@" SELECT m.id, m.team1_name, m.team2_name, m.status, m.start_time, m.stage, - e.name AS event_name, e.id AS event_id + e.name AS event_name, e.id AS event_id, m.result FROM csbetting_matches m LEFT JOIN csbetting_events e ON m.event_id = e.id ORDER BY @@ -176,8 +176,9 @@ namespace Oshima.FunGame.WebAPI.Services string stage = row["stage"]?.ToString() ?? ""; string eventName = row["event_name"]?.ToString() ?? ""; long eventId = Convert.ToInt64(row["event_id"]); + string result = row["result"] != DBNull.Value ? row["result"].ToString() ?? "" : ""; - string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" }; + string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => $"已结束 | {result}", _ => "未知" }; string matchLabel = $"{t1} vs {t2}".CreateCmdInput($"比赛详情 {id}"); sb.Append($"[{id}] {matchLabel}"); @@ -222,6 +223,22 @@ namespace Oshima.FunGame.WebAPI.Services eventName = rowEvent["name"].ToString() ?? ""; } + // 查询各选项统计 + sql.Parameters["@mid"] = matchId; + DataSet stats = sql.ExecuteDataSet(@"SELECT option_type, + COUNT(*) AS totalRecord, SUM(amount) AS totalAmount + FROM csbetting_bet_records WHERE match_id = @mid GROUP BY option_type"); + + // 将统计存入字典便于查找 + Dictionary statDict = []; + if (sql.Success && stats.Tables[0].Rows.Count > 0) + { + foreach (DataRow srow in stats.Tables[0].Rows) + { + statDict[Convert.ToInt32(srow["option_type"])] = (Convert.ToInt32(srow["totalRecord"]), Convert.ToInt64(srow["totalAmount"])); + } + } + string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" }; StringBuilder sb = new(); sb.AppendLine($"比赛 #{matchId}"); @@ -241,27 +258,44 @@ namespace Oshima.FunGame.WebAPI.Services sb.AppendLine($"> 📝 {description}\r\n"); } - if (status == 0) sb.AppendLine($"可用选项:"); + DateTime now = DateTime.Now; + bool canBet = (status == 0 || status == 1) && now < deadline; + if (canBet) sb.AppendLine($"可用选项:"); else sb.AppendLine($"该比赛已截止预测。"); + + string GetStatString(int opt) + { + if (statDict.TryGetValue(opt, out var stat) && stat.Item1 > 0) + { + return $" 👥 {stat.Item1} 🔥 {stat.Item2}"; + } + return ""; + }; + + string statText = ""; if (available.Contains("team1_win")) { - sb.AppendLine($" - {t1} 胜 (x {team1Odds})"); - if (status == 0) kb.AppendButtons(2, Button.CreateCmdButton($"⚔️ {t1} 胜", $"预测 {matchId} team1 1000", enter: false)); + statText = GetStatString(1); + sb.AppendLine($" - {t1} 胜 (x {team1Odds}){statText}"); + if (canBet) kb.AppendButtons(2, Button.CreateCmdButton($"⚔️ {t1} 胜", $"预测 {matchId} team1 1000", enter: false)); } if (available.Contains("team2_win")) { - sb.AppendLine($" - {t2} 胜 (x {team2Odds})"); - if (status == 0) kb.AppendButtons(2, Button.CreateCmdButton($"🛡️ {t2} 胜", $"预测 {matchId} team2 1000", enter: false)); + statText = GetStatString(2); + sb.AppendLine($" - {t2} 胜 (x {team2Odds}){statText}"); + if (canBet) kb.AppendButtons(2, Button.CreateCmdButton($"🛡️ {t2} 胜", $"预测 {matchId} team2 1000", enter: false)); } if (available.Contains("score")) { - sb.AppendLine($" - 精确比分 (x 4)"); - if (status == 0) kb.AppendButtons(2, Button.CreateCmdButton("🎯 精确比分", $"预测 {matchId} score:", enter: false)); + statText = GetStatString(3); + sb.AppendLine($" - 精确比分 (x 4){statText}"); + if (canBet) kb.AppendButtons(2, Button.CreateCmdButton("🎯 精确比分", $"预测 {matchId} score:", enter: false)); } if (available.Contains("mvp")) { - sb.AppendLine($" - 赛事MVP (x 3.5)"); - if (status == 0) kb.AppendButtons(2, Button.CreateCmdButton("🏆 MVP", $"预测 {matchId} mvp:", enter: false)); + statText = GetStatString(4); + sb.AppendLine($" - 赛事MVP (x 3.5){statText}"); + if (canBet) kb.AppendButtons(2, Button.CreateCmdButton("🏆 MVP", $"预测 {matchId} mvp:", enter: false)); } if (status == 2) { @@ -270,7 +304,7 @@ namespace Oshima.FunGame.WebAPI.Services if (winner != 3) sb.AppendLine($"结果:{result}"); } - if (status == 0) + if (canBet) { sb.AppendLine($"预测指令:{"预测".CreateCmdInput()} <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n👇🏻 点击下方按钮快速预测"); } @@ -316,9 +350,9 @@ namespace Oshima.FunGame.WebAPI.Services alreadyBet = Convert.ToInt64(sql.DataSet.Tables[0].Rows[0]["total"] ?? 0L); totalBet += alreadyBet; } - if (totalBet > 5000) + if (totalBet > 10000) { - error = $"本场比赛你的助力总额不能超过 5000 {General.GameplayEquilibriumConstant.InGameCurrency}(已助力 {alreadyBet})。"; + error = $"本场比赛你的助力总额不能超过 10000 {General.GameplayEquilibriumConstant.InGameCurrency}(已助力 {alreadyBet})。"; return false; } @@ -919,6 +953,11 @@ namespace Oshima.FunGame.WebAPI.Services sql.Parameters["@result"] = (object?)request.Result ?? ""; setClause.Append("result = @result, "); } + if (request.Stage != null) + { + sql.Parameters["@stage"] = (object?)request.Stage ?? ""; + setClause.Append("stage = @stage, "); + } if (setClause.Length == 0) { diff --git a/OshimaWebAPI/Constant/CSBetting.sql b/OshimaWebAPI/Constant/CSBetting.sql index 440d487..2cd81f2 100644 --- a/OshimaWebAPI/Constant/CSBetting.sql +++ b/OshimaWebAPI/Constant/CSBetting.sql @@ -28,7 +28,6 @@ CREATE TABLE IF NOT EXISTS `csbetting_events` ( `status` tinyint NOT NULL DEFAULT '0' COMMENT '赛事状态:0=未开始,1=进行中,2=已结束', `start_time` datetime NOT NULL COMMENT '赛事开始时间', `end_time` datetime NOT NULL COMMENT '赛事结束时间', - `mvp_candidates` json DEFAULT NULL COMMENT 'MVP候选人UID列表,如 [1001, 1002]', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), diff --git a/OshimaWebAPI/Controllers/BettingController.cs b/OshimaWebAPI/Controllers/BettingController.cs new file mode 100644 index 0000000..ded42c4 --- /dev/null +++ b/OshimaWebAPI/Controllers/BettingController.cs @@ -0,0 +1,267 @@ +using System.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Milimoe.FunGame.Core.Api.Utility; +using Oshima.FunGame.WebAPI.Model; + +namespace Oshima.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class BettingController : ControllerBase + { + /// + /// 获取赛事列表(分页 + 可选状态过滤) + /// + [AllowAnonymous] + [HttpGet("events")] + public ActionResult> GetEvents( + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] int? status = null) + { + using var sql = Factory.OpenFactory.GetSQLHelper(); + if (sql == null) return StatusCode(500, "数据库连接失败"); + sql.ClearParametersAfterExecute = false; + + // 动态条件 + string where = status.HasValue ? "WHERE status = @status" : ""; + if (status.HasValue) sql.Parameters["@status"] = status.Value; + + // 总数 + sql.ExecuteDataSet($"SELECT COUNT(*) FROM csbetting_events {where}"); + int total = sql.Success ? Convert.ToInt32(sql.DataSet.Tables[0].Rows[0][0]) : 0; + int totalPages = (int)Math.Ceiling(total / (double)pageSize); + if (page > totalPages) page = totalPages; + if (page < 1) page = 1; + + int offset = (page - 1) * pageSize; + sql.ExecuteDataSet($@" + SELECT * FROM csbetting_events {where} + ORDER BY start_time DESC + LIMIT {pageSize} OFFSET {offset}"); + + if (!sql.Success || sql.DataSet.Tables.Count == 0) + return Ok(new { data = Array.Empty(), page, totalPages, total }); + + var list = new List(); + foreach (DataRow row in sql.DataSet.Tables[0].Rows) + { + list.Add(MapEvent(row)); + } + + return Ok(new { data = list, page, totalPages, total }); + } + + /// + /// 获取单个赛事详情 + /// + [AllowAnonymous] + [HttpGet("events/{id:int}")] + public ActionResult GetEvent(int id) + { + using var sql = Factory.OpenFactory.GetSQLHelper(); + if (sql == null) return StatusCode(500, "数据库连接失败"); + sql.ClearParametersAfterExecute = false; + + sql.Parameters["@id"] = id; + DataRow? row = sql.ExecuteDataRow("SELECT * FROM csbetting_events WHERE id = @id"); + if (row == null) return NotFound(); + + return Ok(MapEvent(row)); + } + + /// + /// 获取比赛列表(分页 + 可选过滤) + /// + [AllowAnonymous] + [HttpGet("matches")] + public ActionResult> GetMatches( + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] int? eventId = null, + [FromQuery] int? status = null) + { + using var sql = Factory.OpenFactory.GetSQLHelper(); + if (sql == null) return StatusCode(500, "数据库连接失败"); + sql.ClearParametersAfterExecute = false; + + var conditions = new List(); + if (eventId.HasValue) + { + conditions.Add("event_id = @eventId"); + sql.Parameters["@eventId"] = eventId.Value; + } + if (status.HasValue) + { + conditions.Add("status = @status"); + sql.Parameters["@status"] = status.Value; + } + string where = conditions.Count > 0 ? "WHERE " + string.Join(" AND ", conditions) : ""; + + sql.ExecuteDataSet($"SELECT COUNT(*) FROM csbetting_matches {where}"); + int total = sql.Success ? Convert.ToInt32(sql.DataSet.Tables[0].Rows[0][0]) : 0; + int totalPages = (int)Math.Ceiling(total / (double)pageSize); + if (page > totalPages) page = totalPages; + if (page < 1) page = 1; + + int offset = (page - 1) * pageSize; + sql.ExecuteDataSet($@" + SELECT * FROM csbetting_matches {where} + ORDER BY start_time DESC + LIMIT {pageSize} OFFSET {offset}"); + + if (!sql.Success || sql.DataSet.Tables.Count == 0) + return Ok(new { data = Array.Empty(), page, totalPages, total }); + + var list = new List(); + foreach (DataRow row in sql.DataSet.Tables[0].Rows) + { + list.Add(MapMatch(row)); + } + + return Ok(new { data = list, page, totalPages, total }); + } + + /// + /// 获取单个比赛详情 + /// + [AllowAnonymous] + [HttpGet("matches/{id:int}")] + public ActionResult GetMatch(int id) + { + using var sql = Factory.OpenFactory.GetSQLHelper(); + if (sql == null) return StatusCode(500, "数据库连接失败"); + sql.ClearParametersAfterExecute = false; + + sql.Parameters["@id"] = id; + DataRow? row = sql.ExecuteDataRow("SELECT * FROM csbetting_matches WHERE id = @id"); + if (row == null) return NotFound(); + + return Ok(MapMatch(row)); + } + + /// + /// 获取投注记录列表(管理员专用) + /// + [Authorize(AuthenticationSchemes = "CustomBearer")] + [HttpGet("bets")] + public ActionResult> GetBets( + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] long? userId = null, + [FromQuery] int? matchId = null, + [FromQuery] bool? isSettled = null) + { + // 简易管理员检查(可替换为实际鉴权) + if (!User.IsInRole("Admin") && !User.IsInRole("Operator")) + return Forbid(); + + using var sql = Factory.OpenFactory.GetSQLHelper(); + if (sql == null) return StatusCode(500, "数据库连接失败"); + sql.ClearParametersAfterExecute = false; + + var conditions = new List(); + if (userId.HasValue) + { + conditions.Add("user_id = @uid"); + sql.Parameters["@uid"] = userId.Value; + } + if (matchId.HasValue) + { + conditions.Add("match_id = @mid"); + sql.Parameters["@mid"] = matchId.Value; + } + if (isSettled.HasValue) + { + conditions.Add("is_settled = @settled"); + sql.Parameters["@settled"] = isSettled.Value ? 1 : 0; + } + string where = conditions.Count > 0 ? "WHERE " + string.Join(" AND ", conditions) : ""; + + sql.ExecuteDataSet($"SELECT COUNT(*) FROM csbetting_bet_records {where}"); + int total = sql.Success ? Convert.ToInt32(sql.DataSet.Tables[0].Rows[0][0]) : 0; + int totalPages = (int)Math.Ceiling(total / (double)pageSize); + if (page > totalPages) page = totalPages; + if (page < 1) page = 1; + + int offset = (page - 1) * pageSize; + sql.ExecuteDataSet($@" + SELECT * FROM csbetting_bet_records {where} + ORDER BY bet_time DESC + LIMIT {pageSize} OFFSET {offset}"); + + if (!sql.Success || sql.DataSet.Tables.Count == 0) + return Ok(new { data = Array.Empty(), page, totalPages, total }); + + var list = new List(); + foreach (DataRow row in sql.DataSet.Tables[0].Rows) + { + list.Add(MapBetRecord(row)); + } + + return Ok(new { data = list, page, totalPages, total }); + } + + // ---------- 映射辅助方法 ---------- + private static BettingEvent MapEvent(DataRow row) + { + return new BettingEvent + { + Id = Convert.ToInt32(row["id"]), + Name = row["name"].ToString() ?? "", + Status = Convert.ToInt32(row["status"]), + StartTime = Convert.ToDateTime(row["start_time"]), + EndTime = Convert.ToDateTime(row["end_time"]), + CreatedAt = Convert.ToDateTime(row["created_at"]), + UpdatedAt = Convert.ToDateTime(row["updated_at"]) + }; + } + + private static BettingMatch MapMatch(DataRow row) + { + return new BettingMatch + { + Id = Convert.ToInt32(row["id"]), + EventId = Convert.ToInt32(row["event_id"]), + Stage = row["stage"]?.ToString(), + Team1Name = row["team1_name"].ToString() ?? "", + Team1Logo = row["team1_logo"]?.ToString(), + Team2Name = row["team2_name"].ToString() ?? "", + Team2Logo = row["team2_logo"]?.ToString(), + Status = Convert.ToInt32(row["status"]), + StartTime = Convert.ToDateTime(row["start_time"]), + BetDeadline = Convert.ToDateTime(row["bet_deadline"]), + Result = row["result"]?.ToString(), + Winner = row["winner"] != DBNull.Value ? Convert.ToInt32(row["winner"]) : null, + AvailableOptions = row["available_options"].ToString() ?? "[]", + Team1WinOdds = Convert.ToDecimal(row["team1_win_odds"]), + Team2WinOdds = Convert.ToDecimal(row["team2_win_odds"]), + Description = row["description"]?.ToString(), + CreatedAt = Convert.ToDateTime(row["created_at"]), + UpdatedAt = Convert.ToDateTime(row["updated_at"]) + }; + } + + private static BettingBetRecord MapBetRecord(DataRow row) + { + return new BettingBetRecord + { + Id = Convert.ToInt64(row["id"]), + UserId = Convert.ToInt64(row["user_id"]), + MatchId = Convert.ToInt32(row["match_id"]), + OptionType = Convert.ToInt32(row["option_type"]), + OptionValue = row["option_value"].ToString() ?? "", + Amount = Convert.ToInt64(row["amount"]), + OddsAtBet = Convert.ToDecimal(row["odds_at_bet"]), + BetTime = Convert.ToDateTime(row["bet_time"]), + IsSettled = Convert.ToInt32(row["is_settled"]) == 1, + Payout = row["payout"] != DBNull.Value ? Convert.ToInt64(row["payout"]) : null, + IsClaimed = Convert.ToInt32(row["is_claimed"]) == 1, + ResultNote = row["result_note"]?.ToString(), + CreatedAt = Convert.ToDateTime(row["created_at"]), + UpdatedAt = Convert.ToDateTime(row["updated_at"]) + }; + } + } +} diff --git a/OshimaWebAPI/Controllers/CSBettingController.cs b/OshimaWebAPI/Controllers/CSBettingController.cs index e895d46..95b03e0 100644 --- a/OshimaWebAPI/Controllers/CSBettingController.cs +++ b/OshimaWebAPI/Controllers/CSBettingController.cs @@ -162,7 +162,7 @@ namespace Oshima.FunGame.WebAPI.Controllers long total = CSBettingService.ClaimRewards(uid); if (total > 0) { - user.Inventory.Credits += (int)total; + user.Inventory.Credits += total; FunGameService.SetUserConfigButNotRelease(uid, pc, user); md.Content = $"领取成功!获得 {total} {General.GameplayEquilibriumConstant.InGameCurrency}。"; } diff --git a/OshimaWebAPI/Controllers/FunGameController.cs b/OshimaWebAPI/Controllers/FunGameController.cs index 33c87f4..035ee77 100644 --- a/OshimaWebAPI/Controllers/FunGameController.cs +++ b/OshimaWebAPI/Controllers/FunGameController.cs @@ -8577,6 +8577,79 @@ namespace Oshima.FunGame.WebAPI.Controllers } } + [HttpPost("systemstoreaddgoods")] + public BotReply SystemStoreAddGoods([FromQuery] long? uid = null, [FromQuery] long region = 0, [FromQuery] string storeName = "", [FromQuery] string name = "", [FromQuery] double price = 0, [FromQuery] int stock = -1, [FromQuery] int quota = 0, [FromQuery] bool addToNextRefreshGoods = true) + { + long userid = uid ?? Convert.ToInt64("10" + Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 11)); + + PluginConfig pc = FunGameService.GetUserConfig(userid, out _); + + if (pc.Count > 0) + { + User user = FunGameService.GetUser(pc); + + string msg = ""; + if (user.IsAdmin) + { + if (FunGameConstant.PlayerRegions.FirstOrDefault(r => r.Id == region) is OshimaRegion or) + { + if (FunGameConstant.Items.FirstOrDefault(i => i.Name == name) is Item item) + { + Item newItem = item.Copy(); + Goods g = new() + { + Name = newItem.Name, + Description = newItem.Description, + Stock = stock, + Quota = quota, + }; + g.Items.Add(newItem); + if (price == 0) + { + (int min, int max) = (0, 0); + if (FunGameConstant.PriceRanges.TryGetValue(item.QualityType, out (int Min, int Max) range)) + { + (min, max) = (range.Min, range.Max); + } + price = Random.Shared.Next(min, max); + } + newItem.Price = price; + g.SetPrice(General.GameplayEquilibriumConstant.InGameCurrency, price); + + if (or.AddGoodsToStore(storeName, [g], addToNextRefreshGoods)) + { + msg = $"添加成功,请查看商店!"; + } + else + { + msg = $"添加失败,商店可能不存在。"; + } + } + else + { + msg = $"目标物品不存在,请重新输入。"; + } + } + else + { + msg = $"未知地区,请重新输入。{"世界地图".CreateCmdInput()}"; + } + } + else + { + msg = $"你没有权限使用此指令!"; + } + + FunGameService.SetUserConfigAndReleaseSemaphoreSlim(userid, pc, user); + return msg; + } + else + { + FunGameService.ReleaseUserSemaphoreSlim(userid); + return noSaved; + } + } + [HttpPost("forgeitemcreate")] public BotReply ForgeItem_Create([FromQuery] long uid = -1, [FromBody] Dictionary? materials = null) { diff --git a/OshimaWebAPI/Services/CSBettingInputHandler.cs b/OshimaWebAPI/Services/CSBettingInputHandler.cs index eb3cccf..c6611fa 100644 --- a/OshimaWebAPI/Services/CSBettingInputHandler.cs +++ b/OshimaWebAPI/Services/CSBettingInputHandler.cs @@ -18,7 +18,7 @@ namespace Oshima.FunGame.WebAPI.Services { Markdown = new MarkdownMessage { - Content = "🎮 CS赛事预测帮助:\r\n" + Content = "🎮 赛事预测系统帮助:\r\n" + $"✨ {"赛事列表".CreateCmdInput()} - 查看所有赛事\r\n" + $"✨ {"比赛列表".CreateCmdInput()} - 查看所有比赛\r\n" + $"✨ {"创建存档".CreateCmdInput()} - 创建存档后可预测\r\n" @@ -35,7 +35,7 @@ namespace Oshima.FunGame.WebAPI.Services Button.CreateCmdButton("💰 预测领奖", "预测领奖"), Button.CreateCmdButton("❓ 预测帮助", "预测帮助")) }; - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); return true; } @@ -75,11 +75,11 @@ namespace Oshima.FunGame.WebAPI.Services { reply.Markdown.Content += "\r\n" + reply2.Markdown.Content; } - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); } else { - await SendAsync(e, "CS赛事预测", "格式:比赛详情 <比赛ID>"); + await SendAsync(e, "赛事预测", "格式:比赛详情 <比赛ID>"); } return true; } @@ -102,7 +102,7 @@ namespace Oshima.FunGame.WebAPI.Services Button.CreateCmdButton("📅 比赛列表", "比赛列表"), Button.CreateCmdButton("📜 我的预测", "我的预测"), Button.CreateCmdButton("💰 预测领奖", "预测领奖")); - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); } else { @@ -119,7 +119,7 @@ namespace Oshima.FunGame.WebAPI.Services Button.CreateCmdButton("❓ 预测帮助", "预测帮助"), Button.CreateCmdButton("📜 我的预测", "我的预测")) }; - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); } return true; } @@ -132,7 +132,7 @@ namespace Oshima.FunGame.WebAPI.Services System.Text.RegularExpressions.Match match = GetFirstNumber().Match(detail); if (match.Success && int.TryParse(match.Value, out int p)) page = p; BotReply reply = BettingController.GetEventsOverview(page); - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); return true; } @@ -153,7 +153,7 @@ namespace Oshima.FunGame.WebAPI.Services { reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档")); } - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); return true; } @@ -169,7 +169,7 @@ namespace Oshima.FunGame.WebAPI.Services { reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档")); } - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); return true; } @@ -179,7 +179,7 @@ namespace Oshima.FunGame.WebAPI.Services string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 3 || !int.TryParse(parts[0], out int mid) || !long.TryParse(parts[^1], out long amt)) { - await SendAsync(e, "CS赛事预测", $"格式:预测 <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n选项:team1 / team2 / score:2:0 / mvp:选手UID"); + await SendAsync(e, "赛事预测", $"格式:预测 <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n选项:team1 / team2 / score:2:0 / mvp:选手UID"); return true; } string option = string.Join(" ", parts[1..^1]).ToLower(); @@ -211,7 +211,7 @@ namespace Oshima.FunGame.WebAPI.Services } reply.Keyboard = kb; - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); return true; } @@ -235,10 +235,10 @@ namespace Oshima.FunGame.WebAPI.Services Button.CreateCmdButton("📅 比赛列表", "比赛列表"), Button.CreateCmdButton("⚙️ 继续结算", "结算比赛 ", enter: false), Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {mid}")); - await SendAsync(e, "CS赛事预测", reply); + await SendAsync(e, "赛事预测", reply); } else - await SendAsync(e, "CS赛事预测", "格式:结算比赛 <比赛ID> winner=team1 result=2:0"); + await SendAsync(e, "赛事预测", "格式:结算比赛 <比赛ID> winner=team1 result=2:0"); return true; } diff --git a/OshimaWebAPI/Services/RainBOTService.cs b/OshimaWebAPI/Services/RainBOTService.cs index 04ee789..714a25c 100644 --- a/OshimaWebAPI/Services/RainBOTService.cs +++ b/OshimaWebAPI/Services/RainBOTService.cs @@ -2144,6 +2144,63 @@ namespace Oshima.FunGame.WebAPI.Services return result; } + if (e.Detail.StartsWith("添加商品", StringComparison.CurrentCultureIgnoreCase)) + { + string detail = e.Detail["添加商品".Length..].Trim(); + + // 匹配必需参数:地区ID(数字),商店名称(可带双引号),物品名称(可带双引号),以及剩余可选参数部分 + string pattern = @"^(?\d+)\s+(?(""[^""]*""|\S+))\s+(?(""[^""]*""|\S+))\s*(?.*)$"; + Match match = Regex.Match(detail, pattern); + + if (match.Success) + { + long region = long.Parse(match.Groups["region"].Value); + string storeName = match.Groups["storeName"].Value.Trim('"'); + string itemName = match.Groups["itemName"].Value.Trim('"'); + string optionsStr = match.Groups["options"].Value.Trim(); + + // 默认值 + double price = 0; + int stock = -1; + int quota = 0; + bool addToNextRefreshGoods = true; + + // 解析可选键值对 + if (!string.IsNullOrEmpty(optionsStr)) + { + string optionPattern = @"(?\w+)=(?[^\s]+)"; + foreach (Match opt in Regex.Matches(optionsStr, optionPattern)) + { + string key = opt.Groups["key"].Value.ToLower(); + string val = opt.Groups["value"].Value; + switch (key) + { + case "price": + _ = double.TryParse(val, out price); + break; + case "stock": + _ = int.TryParse(val, out stock); + break; + case "quota": + _ = int.TryParse(val, out quota); + break; + case "refresh": + // 支持 是/否 或 true/false 或 1/0 + addToNextRefreshGoods = val == "1" || + bool.TryParse(val, out bool b) && b || + val.Equals("是", StringComparison.OrdinalIgnoreCase) || + val.Equals("yes", StringComparison.OrdinalIgnoreCase); + break; + } + } + } + + BotReply reply = Controller.SystemStoreAddGoods(uid, region, storeName, itemName, price, stock, quota, addToNextRefreshGoods); + await SendAsync(e, "添加商品", reply); + } + return result; + } + if (e.Detail.StartsWith("查地区", StringComparison.CurrentCultureIgnoreCase) || e.Detail.StartsWith("查询地区", StringComparison.CurrentCultureIgnoreCase)) { string detail = e.Detail.Replace("查地区", "").Replace("查询地区", "").Trim();