diff --git a/OshimaServers/Model/CSBettingModels.cs b/OshimaServers/Model/CSBettingModels.cs
index 110c300..d64aaaa 100644
--- a/OshimaServers/Model/CSBettingModels.cs
+++ b/OshimaServers/Model/CSBettingModels.cs
@@ -2,183 +2,6 @@
namespace Oshima.FunGame.WebAPI.Model
{
- ///
- /// 赛事状态
- ///
- public enum EventStatus
- {
- ///
- /// 未开始
- ///
- Upcoming,
- ///
- /// 进行中
- ///
- InProgress,
- ///
- /// 已结束
- ///
- Completed
- }
-
- ///
- /// 比赛状态
- ///
- public enum MatchStatus
- {
- ///
- /// 未开始
- ///
- Scheduled,
- ///
- /// 正在进行
- ///
- Live,
- ///
- /// 已结束
- ///
- Finished
- }
-
- ///
- /// 投注选项类型
- ///
- public enum BetOptionType
- {
- ///
- /// 队伍1胜
- ///
- Team1Win,
- ///
- /// 队伍2胜
- ///
- Team2Win,
- ///
- /// 精确比分
- ///
- Score,
- ///
- /// 赛事MVP
- ///
- MVP
- }
-
- ///
- /// CS 队伍
- ///
- public class CSTeam
- {
- public int Id { get; set; }
- public string Name { get; set; } = "";
- public string LogoUrl { get; set; } = "";
- public List Players { get; set; } = [];
- }
-
- ///
- /// 赛事(Event)
- ///
- 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 Matches { get; set; } = [];
-
- ///
- /// MVP 候选人列表(玩家 OpenId 或游戏内 UID)
- ///
- public List MVPCandidates { get; set; } = [];
- }
-
- ///
- /// 单场比赛
- ///
- 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; } = "";
-
- ///
- /// 竞猜截止时间
- ///
- public DateTime BetDeadline { get; set; }
-
- ///
- /// 比赛结果(如 “16:14” 或 “2:1”)
- ///
- ///
- public string? Result { get; set; }
-
- ///
- /// 获胜方(1=Team1,2=Team2,0=平局/未定)
- ///
- public int Winner { get; set; }
-
- ///
- /// 支持的投注选项类型(常规包含 Team1Win/Team2Win,总决赛增加 Score)
- ///
- public List AvailableOptions { get; set; } = [BetOptionType.Team1Win, BetOptionType.Team2Win];
-
- ///
- /// 队伍1胜赔率,默认2.5
- ///
- public decimal Team1WinOdds { get; set; } = 2.50m;
-
- ///
- /// 队伍2胜赔率,默认2.5
- ///
- public decimal Team2WinOdds { get; set; } = 2.50m;
- }
-
- ///
- /// 用户投注记录
- ///
- public class BetRecord
- {
- public long Id { get; set; }
- public long UserId { get; set; }
- public int MatchId { get; set; }
- public BetOptionType OptionType { get; set; }
-
- ///
- /// 投注选项内容(如 “Team1Win” 或具体比分 “16:14”)
- ///
- public string OptionValue { get; set; } = "";
-
- ///
- /// 投注金币
- ///
- public long Amount { get; set; }
-
- ///
- /// 投注时间
- ///
- public DateTime BetTime { get; set; }
-
- ///
- /// 是否已结算
- ///
- public bool IsSettled { get; set; }
-
- ///
- /// 结算金额(含本金),未结算为 null
- ///
- public long? Payout { get; set; }
-
- ///
- /// 结算备注
- ///
- 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; }
}
}
\ No newline at end of file
diff --git a/OshimaServers/Service/CSBettingService.cs b/OshimaServers/Service/CSBettingService.cs
index 4701545..6668e65 100644
--- a/OshimaServers/Service/CSBettingService.cs
+++ b/OshimaServers/Service/CSBettingService.cs
@@ -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 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
}
///
- /// 管理员提前结束竞猜(将未开始比赛标记为进行中)
+ /// 管理员提前结束预测(将未开始比赛标记为进行中)
///
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);
+ }
}
}
diff --git a/OshimaWebAPI/Constant/CSBetting.sql b/OshimaWebAPI/Constant/CSBetting.sql
index 63cc425..440d487 100644
--- a/OshimaWebAPI/Constant/CSBetting.sql
+++ b/OshimaWebAPI/Constant/CSBetting.sql
@@ -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`;
diff --git a/OshimaWebAPI/Controllers/CSBettingController.cs b/OshimaWebAPI/Controllers/CSBettingController.cs
index 2e30bca..ac37947 100644
--- a/OshimaWebAPI/Controllers/CSBettingController.cs
+++ b/OshimaWebAPI/Controllers/CSBettingController.cs
@@ -20,7 +20,6 @@ namespace Oshima.FunGame.WebAPI.Controllers
private ILogger 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}";
}
diff --git a/OshimaWebAPI/Services/CSBettingInputHandler.cs b/OshimaWebAPI/Services/CSBettingInputHandler.cs
index 3eb859d..96ecc9a 100644
--- a/OshimaWebAPI/Services/CSBettingInputHandler.cs
+++ b/OshimaWebAPI/Services/CSBettingInputHandler.cs
@@ -10,43 +10,43 @@ namespace Oshima.FunGame.WebAPI.Services
{
public async Task 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();
- // 构建投注键盘(填充“竞猜 <选项> ”)
+ // 构建预测键盘(填充“预测 <选项> ”)
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 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; }