文本和算法更新

This commit is contained in:
milimoe 2026-05-11 19:54:25 +08:00
parent 471f897d31
commit 699b80f5a5
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
5 changed files with 327 additions and 358 deletions

View File

@ -2,183 +2,6 @@
namespace Oshima.FunGame.WebAPI.Model
{
/// <summary>
/// 赛事状态
/// </summary>
public enum EventStatus
{
/// <summary>
/// 未开始
/// </summary>
Upcoming,
/// <summary>
/// 进行中
/// </summary>
InProgress,
/// <summary>
/// 已结束
/// </summary>
Completed
}
/// <summary>
/// 比赛状态
/// </summary>
public enum MatchStatus
{
/// <summary>
/// 未开始
/// </summary>
Scheduled,
/// <summary>
/// 正在进行
/// </summary>
Live,
/// <summary>
/// 已结束
/// </summary>
Finished
}
/// <summary>
/// 投注选项类型
/// </summary>
public enum BetOptionType
{
/// <summary>
/// 队伍1胜
/// </summary>
Team1Win,
/// <summary>
/// 队伍2胜
/// </summary>
Team2Win,
/// <summary>
/// 精确比分
/// </summary>
Score,
/// <summary>
/// 赛事MVP
/// </summary>
MVP
}
/// <summary>
/// CS 队伍
/// </summary>
public class CSTeam
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string LogoUrl { get; set; } = "";
public List<string> Players { get; set; } = [];
}
/// <summary>
/// 赛事Event
/// </summary>
public class CSEvent
{
public int Id { get; set; }
public string Name { get; set; } = "";
public EventStatus Status { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public List<CSMatch> Matches { get; set; } = [];
/// <summary>
/// MVP 候选人列表(玩家 OpenId 或游戏内 UID
/// </summary>
public List<long> MVPCandidates { get; set; } = [];
}
/// <summary>
/// 单场比赛
/// </summary>
public class CSMatch
{
public int Id { get; set; }
public int EventId { get; set; }
public int Team1Id { get; set; }
public int Team2Id { get; set; }
public MatchStatus Status { get; set; }
public DateTime StartTime { get; set; }
public string Stage { get; set; } = "";
public string Description { get; set; } = "";
/// <summary>
/// 竞猜截止时间
/// </summary>
public DateTime BetDeadline { get; set; }
/// <summary>
/// 比赛结果(如 “16:14” 或 “2:1”
/// </summary>
///
public string? Result { get; set; }
/// <summary>
/// 获胜方1=Team12=Team20=平局/未定)
/// </summary>
public int Winner { get; set; }
/// <summary>
/// 支持的投注选项类型(常规包含 Team1Win/Team2Win总决赛增加 Score
/// </summary>
public List<BetOptionType> AvailableOptions { get; set; } = [BetOptionType.Team1Win, BetOptionType.Team2Win];
/// <summary>
/// 队伍1胜赔率默认2.5
/// </summary>
public decimal Team1WinOdds { get; set; } = 2.50m;
/// <summary>
/// 队伍2胜赔率默认2.5
/// </summary>
public decimal Team2WinOdds { get; set; } = 2.50m;
}
/// <summary>
/// 用户投注记录
/// </summary>
public class BetRecord
{
public long Id { get; set; }
public long UserId { get; set; }
public int MatchId { get; set; }
public BetOptionType OptionType { get; set; }
/// <summary>
/// 投注选项内容(如 “Team1Win” 或具体比分 “16:14”
/// </summary>
public string OptionValue { get; set; } = "";
/// <summary>
/// 投注金币
/// </summary>
public long Amount { get; set; }
/// <summary>
/// 投注时间
/// </summary>
public DateTime BetTime { get; set; }
/// <summary>
/// 是否已结算
/// </summary>
public bool IsSettled { get; set; }
/// <summary>
/// 结算金额(含本金),未结算为 null
/// </summary>
public long? Payout { get; set; }
/// <summary>
/// 结算备注
/// </summary>
public string? ResultNote { get; set; }
}
public class CreateEventRequest
{
[JsonPropertyName("uid")]
@ -225,6 +48,9 @@ namespace Oshima.FunGame.WebAPI.Model
[JsonPropertyName("team2_win_odds")]
public decimal? Team2WinOdds { get; set; }
[JsonPropertyName("team1_win_probability")]
public decimal? Team1WinProbability { get; set; }
}
public class UpdateMatchRequest
@ -249,5 +75,8 @@ namespace Oshima.FunGame.WebAPI.Model
[JsonPropertyName("description")]
public string? Description { get; set; }
[JsonPropertyName("team1_win_probability")]
public decimal? Team1WinProbability { get; set; }
}
}

View File

@ -233,28 +233,27 @@ namespace Oshima.FunGame.WebAPI.Services
}
sb.AppendLine($"{t1} vs {t2}".CreateCmdInput($"比赛详情 {matchId}"));
sb.AppendLine($"开赛:{start:yyyy/MM/dd HH:mm}");
sb.AppendLine($"竞猜截止:{deadline:yyyy/MM/dd HH:mm}");
sb.AppendLine($"预测截止:{deadline:yyyy/MM/dd HH:mm}");
sb.AppendLine($"状态:{statusStr}");
if (status == 0)
{
sb.AppendLine($"可用选项:");
if (available.Contains("team1_win")) sb.AppendLine($" - 队伍1 {t1} 胜 (x {team1Odds})");
if (available.Contains("team2_win")) sb.AppendLine($" - 队伍2 {t2} 胜 (x {team2Odds})");
if (available.Contains("score")) sb.AppendLine($" - 精确比分 (x 4)");
if (available.Contains("mvp")) sb.AppendLine($" - 赛事MVP (x 3.5)");
}
else if (status == 2)
{
string winnerName = winner switch { 1 => t1, 2 => t2, 3 => result, _ => "待定" };
sb.AppendLine($"胜者:{winnerName}");
if (winner != 3) sb.AppendLine($"结果:{result}");
}
if (!string.IsNullOrWhiteSpace(description))
{
sb.AppendLine($"> 📝 {description}\r\n");
}
if (status == 0) sb.AppendLine($"可用选项:");
else sb.AppendLine($"该比赛已截止预测。");
if (available.Contains("team1_win")) sb.AppendLine($" - 队伍1 {t1} 胜 (x {team1Odds})");
if (available.Contains("team2_win")) sb.AppendLine($" - 队伍2 {t2} 胜 (x {team2Odds})");
if (available.Contains("score")) sb.AppendLine($" - 精确比分 (x 4)");
if (available.Contains("mvp")) sb.AppendLine($" - 赛事MVP (x 3.5)");
if (status == 2)
{
string winnerName = winner switch { 1 => t1, 2 => t2, 3 => result, _ => "待定" };
sb.AppendLine($"胜者:{winnerName}");
if (winner != 3) sb.AppendLine($"结果:{result}");
}
return sb.ToString();
}
return "数据库连接失败。";
@ -277,13 +276,15 @@ namespace Oshima.FunGame.WebAPI.Services
DataRow row = sql.DataSet.Tables[0].Rows[0];
int status = Convert.ToInt32(row["status"]);
DateTime deadline = Convert.ToDateTime(row["bet_deadline"]);
if (status != 0 || DateTime.Now > deadline)
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
if (status == 2 || DateTime.Now > deadline)
{
error = "当前比赛已结束或非投注期。";
error = "当前比赛已结束或非可预测阶段。";
return false;
}
// --- 单场比赛投注上限检查 ---
// --- 单场比赛助力上限检查 ---
long alreadyBet = 0;
long totalBet = amount;
sql.Parameters["@uid"] = uid;
@ -296,44 +297,54 @@ namespace Oshima.FunGame.WebAPI.Services
}
if (totalBet > 5000)
{
error = $"本场比赛你的投注总额不能超过 5000 {General.GameplayEquilibriumConstant.InGameCurrency}(已投 {alreadyBet})。";
error = $"本场比赛你的助力总额不能超过 5000 {General.GameplayEquilibriumConstant.InGameCurrency}(已助力 {alreadyBet})。";
return false;
}
string available = row["available_options"]?.ToString() ?? "[]";
int optionType;
string optionValue = option;
decimal oddsAtBet = 0;
if (option == "team1" && available.Contains("team1_win"))
{
optionType = 1;
oddsAtBet = team1Odds;
}
else if (option == "team2" && available.Contains("team2_win"))
{
optionType = 2;
oddsAtBet = team2Odds;
}
else if (option.StartsWith("score:") && available.Contains("score"))
{
optionType = 3;
optionValue = option.Replace("score:", "").Trim();
oddsAtBet = 4.0m;
}
else if (option.StartsWith("mvp:") && available.Contains("mvp"))
{
optionType = 4;
optionValue = option.Replace("mvp:", "").Trim();
oddsAtBet = 3.5m;
}
else
{
error = "无效的投注选项。";
error = "无效的预测选项。";
return false;
}
// 写入投注记录
// 写入预测记录
sql.Parameters["@uid"] = uid;
sql.Parameters["@mid"] = matchId;
sql.Parameters["@otype"] = optionType;
sql.Parameters["@oval"] = optionValue;
sql.Parameters["@amt"] = amount;
sql.Parameters["@odds"] = oddsAtBet;
sql.Parameters["@time"] = DateTime.Now;
sql.Execute("INSERT INTO csbetting_bet_records (user_id, match_id, option_type, option_value, amount, bet_time) VALUES (@uid, @mid, @otype, @oval, @amt, @time)");
sql.Execute("INSERT INTO csbetting_bet_records (user_id, match_id, option_type, option_value, amount, odds_at_bet, bet_time) VALUES (@uid, @mid, @otype, @oval, @amt, @odds, @time)");
if (!sql.Success)
{
error = "投注记录写入失败。";
error = "预测记录写入失败。";
return false;
}
return true;
@ -347,75 +358,102 @@ namespace Oshima.FunGame.WebAPI.Services
using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper();
if (sql != null)
{
UpdateStatuses(sql);
sql.Parameters["@mid"] = matchId;
sql.ExecuteDataSet("SELECT * FROM csbetting_matches WHERE id = @mid");
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
return "比赛不存在。";
DataRow row = sql.DataSet.Tables[0].Rows[0];
string available = row["available_options"]?.ToString() ?? "[]";
bool isMvp = available.Contains("mvp", StringComparison.CurrentCultureIgnoreCase);
int status = Convert.ToInt32(row["status"]);
if (status == 2)
return "比赛已结算。";
int winTeam = 3;
if (!isMvp)
try
{
if (winner == "team1") winTeam = 1;
else if (winner == "team2") winTeam = 2;
else return "请指定获胜方为 team1 或 team2。MVP 赛事获胜方请直接指定选手 ID。";
}
sql.NewTransaction();
// 更新比赛结果
sql.Parameters["@res"] = result;
sql.Parameters["@win"] = winTeam;
sql.Parameters["@mid"] = matchId;
sql.Execute("UPDATE csbetting_matches SET status=2, result=@res, winner=@win WHERE id=@mid");
UpdateStatuses(sql);
// 获取所有未结算投注
sql.Parameters["@mid"] = matchId;
sql.ExecuteDataSet("SELECT * FROM csbetting_bet_records WHERE match_id=@mid AND is_settled=0");
if (sql.Success && sql.DataSet.Tables[0].Rows.Count > 0)
{
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
foreach (DataRow bet in sql.DataSet.Tables[0].Rows)
sql.Parameters["@mid"] = matchId;
sql.ExecuteDataSet("SELECT * FROM csbetting_matches WHERE id = @mid");
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
return "比赛不存在。";
DataRow row = sql.DataSet.Tables[0].Rows[0];
string available = row["available_options"]?.ToString() ?? "[]";
bool isMvp = available.Contains("mvp", StringComparison.CurrentCultureIgnoreCase);
int status = Convert.ToInt32(row["status"]);
if (status == 2)
return "比赛已结算。";
int winTeam = 3;
if (!isMvp)
{
long betId = Convert.ToInt64(bet["id"]);
int otype = Convert.ToInt32(bet["option_type"]);
string ovalue = bet["option_value"].ToString() ?? "";
long amount = Convert.ToInt64(bet["amount"]);
if (winner == "team1") winTeam = 1;
else if (winner == "team2") winTeam = 2;
else return "请指定获胜方为 team1 或 team2。MVP 赛事获胜方请直接指定选手 ID。";
}
bool win = false;
if (otype == 1 && winTeam == 1) win = true;
else if (otype == 2 && winTeam == 2) win = true;
else if (otype == 3 && ovalue.Replace("", ":").Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true;
else if (otype == 4 && ovalue.Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true;
long payout = 0;
string note = "未中奖";
if (win)
// 更新比赛结果
sql.Parameters["@res"] = result;
sql.Parameters["@win"] = winTeam;
sql.Parameters["@mid"] = matchId;
sql.Execute("UPDATE csbetting_matches SET status=2, result=@res, winner=@win WHERE id=@mid");
if (sql.Success)
{
// 获取所有未结算助力
sql.Parameters["@mid"] = matchId;
sql.ExecuteDataSet("SELECT * FROM csbetting_bet_records WHERE match_id=@mid AND is_settled=0");
if (sql.Success && sql.DataSet.Tables[0].Rows.Count > 0)
{
double odds = otype switch
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
foreach (DataRow bet in sql.DataSet.Tables[0].Rows)
{
1 => (double)team1Odds,
2 => (double)team2Odds,
3 => 4,
4 => 3.5,
_ => 2.5
};
payout = (long)(amount * odds);
note = "中奖";
long betId = Convert.ToInt64(bet["id"]);
int otype = Convert.ToInt32(bet["option_type"]);
string ovalue = bet["option_value"].ToString() ?? "";
long amount = Convert.ToInt64(bet["amount"]);
decimal oddsAtBet = Convert.ToDecimal(bet["odds_at_bet"]);
bool win = false;
if (otype == 1 && winTeam == 1) win = true;
else if (otype == 2 && winTeam == 2) win = true;
else if (otype == 3 && ovalue.Replace("", ":").Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true;
else if (otype == 4 && ovalue.Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true;
long payout = 0;
string note = "未中奖";
if (win)
{
if (oddsAtBet <= 0)
{
oddsAtBet = otype switch
{
1 => team1Odds,
2 => team2Odds,
3 => 4,
4 => 3.5m,
_ => 2.5m
};
}
payout = (long)(amount * oddsAtBet);
note = "中奖";
}
sql.Parameters["@payout"] = payout;
sql.Parameters["@note"] = note;
sql.Parameters["@bid"] = betId;
sql.Execute("UPDATE csbetting_bet_records SET is_settled=1, payout=@payout, result_note=@note WHERE id=@bid");
if (!sql.Success)
{
sql.Rollback();
throw sql.LastException ?? new Milimoe.FunGame.SQLQueryException();
}
}
}
sql.Parameters["@payout"] = payout;
sql.Parameters["@note"] = note;
sql.Parameters["@bid"] = betId;
sql.Execute("UPDATE csbetting_bet_records SET is_settled=1, payout=@payout, result_note=@note WHERE id=@bid");
sql.Commit();
return $"比赛 {matchId} 结算完成。";
}
else
{
sql.Rollback();
throw sql.LastException ?? new Milimoe.FunGame.SQLQueryException();
}
}
return $"比赛 {matchId} 结算完成。";
catch (Exception e)
{
sql.Rollback();
return $"比赛 {matchId} 结算失败,发生异常:{e.Message}";
}
}
return "数据库连接失败。";
}
@ -451,7 +489,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (page > totalPages) page = totalPages;
if (page < 1) page = 1;
if (total == 0)
return ("你还没有任何竞猜记录。", 1);
return ("你还没有任何预测记录。", 1);
}
// 构建分页SQL片段
@ -466,7 +504,8 @@ namespace Oshima.FunGame.WebAPI.Services
sql.Parameters["@uid"] = uid;
sql.ExecuteDataSet($@"
SELECT br.match_id, m.team1_name, m.team2_name, m.status AS match_status, m.start_time,
GROUP_CONCAT(CONCAT(br.option_type, ':', br.option_value, ':', br.amount) ORDER BY br.id SEPARATOR '|') AS details,
MAX(m.team1_win_odds) AS team1_odds, MAX(m.team2_win_odds) AS team2_odds,
GROUP_CONCAT(CONCAT(br.option_type, ':', br.option_value, ':', br.amount, ':', br.odds_at_bet) ORDER BY br.id SEPARATOR '|') AS details,
SUM(br.amount) AS total_amount,
MIN(br.is_settled) AS all_settled,
SUM(CASE WHEN br.is_settled = 1 AND br.payout > 0 AND br.is_claimed = 1 THEN 1 ELSE 0 END) AS claimed_count,
@ -484,17 +523,19 @@ namespace Oshima.FunGame.WebAPI.Services
{limitClause}");
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
return ("你还没有任何竞猜记录。", 1);
return ("你还没有任何预测记录。", 1);
StringBuilder sb = new();
if (mid > 0) sb.Append("你的本场竞猜记录:\r\n> ");
else sb.Append($"我的竞猜{(paged && totalPages > 1 ? $" {page}/{totalPages} " : "")}\r\n> ");
if (mid > 0) sb.Append("你的本场预测记录:\r\n> ");
else sb.Append($"我的预测{(paged && totalPages > 1 ? $" {page}/{totalPages} " : "")}\r\n> ");
foreach (DataRow row in sql.DataSet.Tables[0].Rows)
{
int matchId = Convert.ToInt32(row["match_id"]);
string t1 = row["team1_name"].ToString() ?? "";
string t2 = row["team2_name"].ToString() ?? "";
int matchStatus = Convert.ToInt32(row["match_status"]);
decimal t1Odds = Convert.ToDecimal(row["team1_odds"]);
decimal t2Odds = Convert.ToDecimal(row["team2_odds"]);
long totalAmount = Convert.ToInt64(row["total_amount"]);
long totalPayout = Convert.ToInt64(row["total_payout"]);
int allSettled = Convert.ToInt32(row["all_settled"]);
@ -502,7 +543,7 @@ namespace Oshima.FunGame.WebAPI.Services
int unclaimedCount = Convert.ToInt32(row["unclaimed_count"]);
int lostCount = Convert.ToInt32(row["lost_count"]);
// 解析投注详情
// 解析助力详情
string detailsStr = row["details"].ToString() ?? "";
string[]? parts = detailsStr.Split('|');
List<string> summary = [];
@ -514,6 +555,18 @@ namespace Oshima.FunGame.WebAPI.Services
int otype = int.Parse(items[0]);
string ovalue = items[1];
long oamount = long.Parse(items[2]);
decimal odds = decimal.Parse(items[3]);
if (odds <= 0)
{
odds = otype switch
{
1 => t1Odds,
2 => t2Odds,
3 => 4m,
4 => 3.5m,
_ => 2.5m
};
}
string optStr = otype switch
{
1 => $"{t1}胜",
@ -522,7 +575,7 @@ namespace Oshima.FunGame.WebAPI.Services
4 => $"MVP {ovalue}",
_ => ovalue
};
summary.Add($"{optStr} {oamount}G");
summary.Add($"{optStr} {oamount}G [x {odds}]");
}
}
@ -546,7 +599,7 @@ namespace Oshima.FunGame.WebAPI.Services
string matchLabel = $"{t1} vs {t2}".CreateCmdInput($"比赛详情 {matchId}");
sb.Append($"[比赛{matchId}] {matchLabel} | ");
sb.Append($"投注{totalAmount}G ({detailLine}) | ");
sb.Append($"助力{totalAmount}G ({detailLine}) | ");
sb.Append($"状态:{statusLine}");
if (totalPayout > 0)
sb.Append($" (+{totalPayout}G)");
@ -574,7 +627,7 @@ namespace Oshima.FunGame.WebAPI.Services
// 对于已经过了开始时间但还没有 winner 且状态为 1 的,可保留为进行中;实际上只要 winner 为 null状态应为 1进行中
// 如果有结果但 winner 不为 null管理员应该已经手动结算状态会设为 2这里不做额外修改。
// 安全起见,只更新未开始的,以及当比赛时间已过且无 winner 时自动变成进行中。
// 如果需要自动结束(比如时间过长),可再添加规则,但竞猜系统通常由管理员手动结算结束。
// 如果需要自动结束(比如时间过长),可再添加规则,但预测系统通常由管理员手动结算结束。
// 这里只做基础更新。
}
@ -640,7 +693,7 @@ namespace Oshima.FunGame.WebAPI.Services
}
// 创建比赛
public static bool CreateMatch(int eventId, string team1Name, string team2Name, string stage, DateTime startTime, DateTime betDeadline, string availableOptions, decimal? team1WinOdds, decimal? team2WinOdds, out string error, out long? newMatchId)
public static bool CreateMatch(int eventId, string team1Name, string team2Name, string stage, DateTime startTime, DateTime betDeadline, string availableOptions, decimal? team1WinOdds, decimal? team2WinOdds, decimal? team1WinProbability, out string error, out long? newMatchId)
{
error = "";
newMatchId = null;
@ -670,8 +723,34 @@ namespace Oshima.FunGame.WebAPI.Services
return false;
}
decimal t1Odds = team1WinOdds ?? 2.50m;
decimal t2Odds = team2WinOdds ?? 2.50m;
// 处理默认奖励率或基于胜率计算
decimal t1Odds, t2Odds;
if (team1WinProbability.HasValue)
{
decimal prob = team1WinProbability.Value;
try
{
(t1Odds, t2Odds) = CalculateOdds(prob);
}
catch (Exception e)
{
error = e.Message;
return false;
}
}
else if (team1WinOdds.HasValue && team2WinOdds.HasValue)
{
t1Odds = team1WinOdds.Value;
t2Odds = team2WinOdds.Value;
}
else
{
// 默认双方各 2.0
t1Odds = 2.0m;
t2Odds = 2.0m;
}
// 校验奖励率大于0
if (t1Odds <= 0 || t2Odds <= 0)
{
error = "奖励率必须大于0。";
@ -704,7 +783,7 @@ namespace Oshima.FunGame.WebAPI.Services
}
/// <summary>
/// 管理员提前结束竞猜(将未开始比赛标记为进行中)
/// 管理员提前结束预测(将未开始比赛标记为进行中)
/// </summary>
public static bool CloseBetting(int matchId, out string message)
{
@ -728,7 +807,7 @@ namespace Oshima.FunGame.WebAPI.Services
int status = Convert.ToInt32(sql.DataSet.Tables[0].Rows[0]["status"]);
if (status != 0)
{
message = "该比赛已开始或已结束,无需关闭投注。";
message = "该比赛已开始或已结束,无需关闭预测。";
return false;
}
@ -739,7 +818,7 @@ namespace Oshima.FunGame.WebAPI.Services
string t1 = sql.DataSet.Tables[0].Rows[0]["team1_name"].ToString() ?? "";
string t2 = sql.DataSet.Tables[0].Rows[0]["team2_name"].ToString() ?? "";
string matchlabel = $"{t1} vs {t2}".CreateCmdInput($"比赛详情 {matchId}");
message = $"[比赛{matchId}] {matchlabel} 的竞猜已提前关闭。";
message = $"[比赛{matchId}] {matchlabel} 的预测已提前关闭。";
return true;
}
@ -775,30 +854,53 @@ namespace Oshima.FunGame.WebAPI.Services
string available = sql.DataSet.Tables[0].Rows[0]["available_options"]?.ToString() ?? "[]";
bool hasSpecial = available.Contains("score", StringComparison.OrdinalIgnoreCase) || available.Contains("mvp", StringComparison.OrdinalIgnoreCase);
decimal? newTeam1Odds = request.Team1WinOdds;
decimal? newTeam2Odds = request.Team2WinOdds;
// 校验奖励率逻辑(同创建比赛)
if ((request.Team1WinOdds.HasValue || request.Team2WinOdds.HasValue) && hasSpecial)
if (request.Team1WinProbability.HasValue)
{
error = "比赛包含比分或MVP选项时不能修改猜胜者奖励率。";
return false;
if (hasSpecial)
{
error = "比赛包含比分或MVP选项不能修改猜胜者赔率。";
return false;
}
decimal prob = request.Team1WinProbability.Value;
try
{
(newTeam1Odds, newTeam2Odds) = CalculateOdds(prob);
}
catch (Exception e)
{
error = e.Message;
return false;
}
}
if ((request.Team1WinOdds.HasValue && request.Team1WinOdds <= 0) ||
(request.Team2WinOdds.HasValue && request.Team2WinOdds <= 0))
else
{
error = "奖励率必须大于0。";
return false;
if ((newTeam1Odds.HasValue || newTeam2Odds.HasValue) && hasSpecial)
{
error = "比赛包含比分或MVP选项时不能修改猜胜者奖励率。";
return false;
}
if ((newTeam1Odds.HasValue && newTeam1Odds <= 0) ||
(newTeam2Odds.HasValue && newTeam2Odds <= 0))
{
error = "奖励率必须大于0。";
return false;
}
}
// 构建动态UPDATE
StringBuilder setClause = new();
if (request.Team1WinOdds.HasValue)
if (newTeam1Odds.HasValue)
{
sql.Parameters["@t1od"] = request.Team1WinOdds.Value;
sql.Parameters["@t1od"] = newTeam1Odds.Value;
setClause.Append("team1_win_odds = @t1od, ");
}
if (request.Team2WinOdds.HasValue)
if (newTeam2Odds.HasValue)
{
sql.Parameters["@t2od"] = request.Team2WinOdds.Value;
sql.Parameters["@t2od"] = newTeam2Odds.Value;
setClause.Append("team2_win_odds = @t2od, ");
}
if (request.StartTime.HasValue)
@ -836,5 +938,21 @@ namespace Oshima.FunGame.WebAPI.Services
return true;
}
public static (decimal oddsA, decimal oddsB) CalculateOdds(decimal team1WinProbability, decimal margin = 0.08m)
{
if (team1WinProbability <= 0 || team1WinProbability >= 1)
throw new ArgumentException("胜率必须大于0且小于1。");
if (margin < 0) margin = 0.08m;
decimal probB = 1m - team1WinProbability;
decimal adj = 1m + margin;
decimal oddsA = Math.Round(1m / (team1WinProbability * adj), 2);
decimal oddsB = Math.Round(1m / (probB * adj), 2);
return (oddsA, oddsB);
}
}
}

View File

@ -71,3 +71,12 @@ ALTER TABLE `csbetting_matches`
-- 为比赛表添加描述字段
ALTER TABLE `csbetting_matches`
ADD COLUMN `description` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '比赛描述信息' AFTER `team2_win_odds`;
-- 将队伍赔率的默认值改为 2.00
ALTER TABLE `csbetting_matches`
MODIFY COLUMN `team1_win_odds` DECIMAL(5,2) NOT NULL DEFAULT 2.00 COMMENT '队伍1胜赔率',
MODIFY COLUMN `team2_win_odds` DECIMAL(5,2) NOT NULL DEFAULT 2.00 COMMENT '队伍2胜赔率';
-- 为投注记录表增加投注时的赔率字段,确保结算公平
ALTER TABLE `csbetting_bet_records`
ADD COLUMN `odds_at_bet` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '投注时的赔率' AFTER `amount`;

View File

@ -20,7 +20,6 @@ namespace Oshima.FunGame.WebAPI.Controllers
private ILogger<CSBettingController> Logger { get; set; } = logger;
private const string noSaved = "你还没有创建存档!请发送【创建存档】创建。";
private const string refused = "暂时无法使用此指令。";
private const string busy = "服务器繁忙,请稍后再试。";
// ---------- 查询类(无需锁)----------
@ -61,20 +60,18 @@ namespace Oshima.FunGame.WebAPI.Controllers
[HttpGet("match/{matchId:int}")]
public BotReply GetMatchDetail(int matchId)
{
return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetMatchDetail(matchId, out int status) + (status == 0 ? $"竞猜指令:{"".CreateCmdInput()} <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n👇🏻 点击下方按钮快速竞猜" : "")} };
return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetMatchDetail(matchId, out int status) + (status == 0 ? $"预测指令:{"".CreateCmdInput()} <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n👇🏻 点击下方按钮快速预测" : "")} };
}
[AllowAnonymous]
[HttpGet("mybets/{uid:long}")]
public BotReply GetMyBets(long uid, [FromQuery] int page = 1, [FromQuery] int pageSize = 10)
{
var (content, totalPages) = CSBettingService.GetMyBets(uid, -1, page, pageSize);
KeyboardMessage kb = new();
if (totalPages > 1) kb = new KeyboardMessage().AddPaginationRow("我的竞猜 ", page, totalPages);
if (totalPages > 1) kb = new KeyboardMessage().AddPaginationRow("我的预测 ", page, totalPages);
return new BotReply { Markdown = new MarkdownMessage { Content = content }, Keyboard = kb };
}
[AllowAnonymous]
[HttpGet("mybets/{uid:long}/{mid:long}")]
public BotReply GetMyBets(long uid, long mid)
{
@ -107,7 +104,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
// 校验金额
if (amount < 1000)
{
md.Content = $"最低投注额为 1000 {General.GameplayEquilibriumConstant.InGameCurrency}。";
md.Content = $"最低助力 1000 {General.GameplayEquilibriumConstant.InGameCurrency}。";
FunGameService.SetUserConfigButNotRelease(uid, pc, user);
return reply;
}
@ -122,7 +119,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
{
user.Inventory.Credits -= (int)amount;
FunGameService.SetUserConfigButNotRelease(uid, pc, user);
md.Content = $"投注成功!{amount} {General.GameplayEquilibriumConstant.InGameCurrency}已扣除。";
md.Content = $"{"".CreateCmdInput($" {matchId}")}成功!{amount} {General.GameplayEquilibriumConstant.InGameCurrency}已扣除。";
}
else
{
@ -266,7 +263,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
}
if (CSBettingService.CreateMatch(request.EventId, request.Team1Name, request.Team2Name, request.Stage,
request.StartTime, request.BetDeadline, request.AvailableOptions, request.Team1WinOdds, request.Team2WinOdds, out string error, out long? newId))
request.StartTime, request.BetDeadline, request.AvailableOptions, request.Team1WinOdds, request.Team2WinOdds, request.Team1WinProbability, out string error, out long? newId))
{
md.Content = $"比赛创建成功新比赛ID{newId}";
}

View File

@ -10,43 +10,43 @@ namespace Oshima.FunGame.WebAPI.Services
{
public async Task<bool> HandleCSBettingInput(IBotMessage e, long uid)
{
if (e.Detail == "竞猜帮助")
if (e.Detail == "预测帮助" || e.Detail == "赛事帮助")
{
e.UseNotice = false;
BotReply reply = new()
{
Markdown = new MarkdownMessage
{
Content = "🎮 CS赛事竞猜帮助:\r\n"
Content = "🎮 CS赛事预测帮助:\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看所有赛事\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看所有比赛\r\n"
+ $"✨ {"".CreateCmdInput()} - 创建存档后可竞猜\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看我的投注记录\r\n"
+ $"✨ {"".CreateCmdInput()} - 领取竞猜奖励\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看单场比赛并投注"
+ $"✨ {"".CreateCmdInput()} - 创建存档后可预测\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看我的预测记录\r\n"
+ $"✨ {"".CreateCmdInput()} - 领取预测奖励\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看单场比赛并预测"
},
Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("⚙️ 创建存档", "创建存档"),
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"))
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"))
};
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
return true;
}
// 赛事列表
if (e.Detail.StartsWith("赛事列表"))
if (e.Detail.StartsWith("赛事") || e.Detail.StartsWith("赛事列表"))
{
int page = 1;
string detail = e.Detail.Replace("赛事列表", "").Trim();
string detail = e.Detail.Replace("赛事", "").Replace("赛事列表", "").Trim();
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, "CS赛事预测", reply);
return true;
}
@ -63,8 +63,8 @@ namespace Oshima.FunGame.WebAPI.Services
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"));
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
await SendAsync(e, "比赛赛程", reply);
return true;
}
@ -76,31 +76,31 @@ namespace Oshima.FunGame.WebAPI.Services
{
BotReply reply = BettingController.GetMatchDetail(matchId);
KeyboardMessage kb = new();
// 构建投注键盘(填充“竞猜 <matchId> <选项> ”)
// 构建预测键盘(填充“预测 <matchId> <选项> ”)
if (reply.Markdown?.Content?.Contains("可用选项") ?? false)
{
kb.AppendButtons(2,
Button.CreateCmdButton("⚔️ 队伍1胜", $"竞猜 {matchId} team1 1000", enter: false),
Button.CreateCmdButton("🛡️ 队伍2胜", $"竞猜 {matchId} team2 1000", enter: false),
Button.CreateCmdButton("🎯 精确比分", $"竞猜 {matchId} score:", enter: false),
Button.CreateCmdButton("🏆 MVP", $"竞猜 {matchId} mvp:", enter: false));
Button.CreateCmdButton("⚔️ 队伍1胜", $"预测 {matchId} team1 1000", enter: false),
Button.CreateCmdButton("🛡️ 队伍2胜", $"预测 {matchId} team2 1000", enter: false),
Button.CreateCmdButton("🎯 精确比分", $"预测 {matchId} score:", enter: false),
Button.CreateCmdButton("🏆 MVP", $"预测 {matchId} mvp:", enter: false));
}
kb.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"));
Button.CreateCmdButton("💰 预测领奖", "预测领奖"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
reply.Keyboard = kb;
BotReply reply2 = BettingController.GetMyBets(uid, mid: matchId);
if (reply.Markdown != null && reply.Markdown.Content != null && !(reply2.Markdown?.Content?.Equals("你还没有任何竞猜记录。") ?? true))
if (reply.Markdown != null && reply.Markdown.Content != null && !(reply2.Markdown?.Content?.Equals("你还没有任何预测记录。") ?? true))
{
reply.Markdown.Content += (reply.Markdown.Content.Contains("点击下方按钮快速竞猜") ? "\r\n" : "") + reply2.Markdown.Content;
reply.Markdown.Content += (reply.Markdown.Content.Contains("点击下方按钮快速预测") ? "\r\n" : "") + reply2.Markdown.Content;
}
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
}
else
{
await SendAsync(e, "CS赛事竞猜", "格式:比赛详情 <比赛ID>");
await SendAsync(e, "CS赛事预测", "格式:比赛详情 <比赛ID>");
}
return true;
}
@ -121,9 +121,9 @@ namespace Oshima.FunGame.WebAPI.Services
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖"));
await SendAsync(e, "CS赛事竞猜", reply);
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"));
await SendAsync(e, "CS赛事预测", reply);
}
else
{
@ -137,74 +137,74 @@ namespace Oshima.FunGame.WebAPI.Services
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"),
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"))
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"),
Button.CreateCmdButton("📜 我的预测", "我的预测"))
};
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
}
return true;
}
if (e.Detail.StartsWith("我的竞猜"))
if (e.Detail.StartsWith("我的预测"))
{
int page = 1;
string detail = e.Detail.Replace("我的竞猜", "").Trim();
string detail = e.Detail.Replace("我的预测", "").Trim();
System.Text.RegularExpressions.Match match = GetFirstNumber().Match(detail);
if (match.Success && int.TryParse(match.Value, out int p)) page = p;
BotReply reply = BettingController.GetMyBets(uid, page);
reply.Keyboard ??= new KeyboardMessage();
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖", enter: true),
Button.CreateCmdButton("💰 预测领奖", "预测领奖", enter: true),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"));
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档"));
}
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
return true;
}
if (e.Detail == "竞猜领奖")
if (e.Detail == "预测领奖")
{
BotReply reply = BettingController.ClaimRewards(uid);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档"));
}
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
return true;
}
if (e.Detail.StartsWith("竞猜"))
if (e.Detail.StartsWith("预测"))
{
string detail = e.Detail.Replace("竞猜", "").Trim();
string detail = e.Detail.Replace("预测", "").Trim();
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, "CS赛事预测", $"格式:预测 <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n选项team1 / team2 / score:2:0 / mvp:选手UID");
return true;
}
string option = string.Join(" ", parts[1..^1]).ToLower();
BotReply reply = BettingController.PlaceBet(uid, mid, option, amt);
// 根据控制器返回的消息判断投注结果(简单判断是否包含"成功"
// 根据控制器返回的消息判断预测结果(简单判断是否包含"成功"
bool success = reply.Markdown?.Content?.Contains("成功") ?? false;
KeyboardMessage kb = new();
// 成功与失败通用的按钮
kb.AppendButtons(2,
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助"));
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
@ -215,12 +215,12 @@ namespace Oshima.FunGame.WebAPI.Services
if (success)
{
kb.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("🔄 再次投注", e.Detail, enter: false),
Button.CreateCmdButton("🔄 再次预测", e.Detail, enter: false),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {mid}"));
}
reply.Keyboard = kb;
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
return true;
}
@ -243,10 +243,10 @@ namespace Oshima.FunGame.WebAPI.Services
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("⚙️ 继续结算", "结算比赛 ", enter: false));
await SendAsync(e, "CS赛事竞猜", reply);
await SendAsync(e, "CS赛事预测", reply);
}
else
await SendAsync(e, "CS赛事竞猜", "格式:结算比赛 <比赛ID> winner=team1 result=2:0");
await SendAsync(e, "CS赛事预测", "格式:结算比赛 <比赛ID> winner=team1 result=2:0");
return true;
}
@ -303,10 +303,11 @@ namespace Oshima.FunGame.WebAPI.Services
return true;
}
// 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)]
// 示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55
// 示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win,team2_win
// 示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win=2.5 team2_win=2.5 team1_win,team2_win
// 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <预测截止时间> [选项列表(逗号分隔)]
// 示例(创建默认形式的比赛):创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55
// 示例(提供比赛助力选项):创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win,team2_win
// 示例(提供自定义奖励率):创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win=2.5 team2_win=2.5 team1_win,team2_win
// 示例提供队伍1的胜率自动计算奖励率创建比赛 1 Vitality FaZe Final 2026-05-12 20:00 2026-05-12 19:55 prob=0.6
if (e.Detail.StartsWith("创建比赛"))
{
e.UseNotice = false;
@ -321,7 +322,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (parts.Length < 7) // 最少需要 eventId, team1, team2, stage, start date, start time, deadline date, deadline time
{
await SendAsync(e, "创建比赛",
"格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)]\r\n" +
"格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <预测截止时间> [选项列表(逗号分隔)]\r\n" +
"时间格式yyyy-MM-dd HH:mm开始/截止各占两段)\r\n" +
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55\r\n" +
"选项默认 team1_win,team2_win 比分和MVP选项只允许独立添加score,mvp");
@ -344,7 +345,7 @@ namespace Oshima.FunGame.WebAPI.Services
// 截止时间parts[6] + parts[7] 如果存在)
if (parts.Length < 8)
{
await SendAsync(e, "创建比赛", "投注截止时间需要完整日期和时间示例2026-03-05 13:55");
await SendAsync(e, "创建比赛", "预测截止时间需要完整日期和时间示例2026-03-05 13:55");
return true;
}
string deadlineDate = parts[6];
@ -359,7 +360,7 @@ namespace Oshima.FunGame.WebAPI.Services
// 解析剩余参数:选项和奖励率
List<string> optionParts = [];
decimal? team1Odds = null, team2Odds = null;
decimal? team1Odds = null, team2Odds = null, team1WinProbability = null;
for (int i = 8; i < parts.Length; i++)
{
string segment = parts[i];
@ -373,6 +374,16 @@ namespace Oshima.FunGame.WebAPI.Services
if (decimal.TryParse(segment[11..], out decimal odds2))
team2Odds = odds2;
}
else if (segment.StartsWith("prob="))
{
if (decimal.TryParse(segment[5..], out decimal prob))
team1WinProbability = prob;
else
{
await SendAsync(e, "创建比赛", "胜率值无效应为0~1之间的小数。");
return true;
}
}
else
{
optionParts.Add(segment);
@ -391,25 +402,26 @@ namespace Oshima.FunGame.WebAPI.Services
BetDeadline = deadlineDt,
AvailableOptions = options,
Team1WinOdds = team1Odds,
Team2WinOdds = team2Odds
Team2WinOdds = team2Odds,
Team1WinProbability = team1WinProbability
});
await SendAsync(e, "创建比赛", reply);
return true;
}
// 指令:关闭投注 <比赛ID>
if (e.Detail.StartsWith("关闭投注") || e.Detail.StartsWith("结束竞猜"))
// 指令:关闭预测 <比赛ID>
if (e.Detail.StartsWith("关闭预测") || e.Detail.StartsWith("结束预测"))
{
e.UseNotice = false;
if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator))
{
await SendAsync(e, "关闭投注", "你没有权限执行此操作。");
await SendAsync(e, "关闭预测", "你没有权限执行此操作。");
return true;
}
string detail = e.Detail
.Replace("关闭投注", "")
.Replace("结束竞猜", "")
.Replace("关闭预测", "")
.Replace("结束预测", "")
.Trim();
if (int.TryParse(detail, out int matchId))
{
@ -418,11 +430,11 @@ namespace Oshima.FunGame.WebAPI.Services
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
await SendAsync(e, "关闭投注", reply);
await SendAsync(e, "关闭预测", reply);
}
else
{
await SendAsync(e, "关闭投注", "格式:关闭投注 <比赛ID>");
await SendAsync(e, "关闭预测", "格式:关闭预测 <比赛ID>");
}
return true;
}
@ -475,6 +487,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (decimal.TryParse(val, out decimal t2od)) request.Team2WinOdds = t2od;
else { await SendAsync(e, "修改比赛", "t2od 值无效。"); return true; }
break;
case "prob":
if (decimal.TryParse(val, out decimal prob)) request.Team1WinProbability = prob;
else { await SendAsync(e, "修改比赛", "prob 值无效。"); return true; }
break;
case "st":
if (DateTime.TryParse(val, out DateTime st)) request.StartTime = st;
else { await SendAsync(e, "修改比赛", "开始时间格式错误,请用 yyyy-MM-dd HH:mm。"); return true; }