添加支持率

This commit is contained in:
milimoe 2026-05-09 22:59:53 +08:00
parent 187630233b
commit 226159bb53
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
5 changed files with 191 additions and 15 deletions

View File

@ -125,6 +125,16 @@ namespace Oshima.FunGame.WebAPI.Model
/// 支持的投注选项类型(常规包含 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>
@ -208,5 +218,11 @@ namespace Oshima.FunGame.WebAPI.Model
[JsonPropertyName("available_options")]
public string AvailableOptions { get; set; } = "team1_win,team2_win";
[JsonPropertyName("team1_win_odds")]
public decimal? Team1WinOdds { get; set; }
[JsonPropertyName("team2_win_odds")]
public decimal? Team2WinOdds { get; set; }
}
}

View File

@ -147,6 +147,8 @@ namespace Oshima.FunGame.WebAPI.Services
string available = row["available_options"]?.ToString() ?? "[]";
string result = row["result"] != DBNull.Value ? row["result"].ToString() ?? "" : "";
long winner = row["winner"] != DBNull.Value ? Convert.ToInt64(row["winner"]) : 0;
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
string eventName = "";
sql.Parameters["@eid"] = eventId;
@ -172,9 +174,9 @@ namespace Oshima.FunGame.WebAPI.Services
if (status == 0)
{
sb.AppendLine($"可用选项:");
if (available.Contains("team1_win")) sb.AppendLine($" - {t1}胜 (x 2.5)");
if (available.Contains("team2_win")) sb.AppendLine($" - {t2}胜 (x 2.5)");
if (available.Contains("score")) sb.AppendLine($" - 精确比分 (x 3.5)");
if (available.Contains("team1_win")) sb.AppendLine($" - {t1}胜 (x {team1Odds})");
if (available.Contains("team2_win")) sb.AppendLine($" - {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)
@ -308,6 +310,8 @@ namespace Oshima.FunGame.WebAPI.Services
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)
{
long betId = Convert.ToInt64(bet["id"]);
@ -325,7 +329,14 @@ namespace Oshima.FunGame.WebAPI.Services
string note = "未中奖";
if (win)
{
double odds = otype switch { 2 or 3 => 3.5, _ => 2.5 };
double odds = otype switch
{
1 => (double)team1Odds,
2 => (double)team2Odds,
3 => 4,
4 => 3.5,
_ => 2.5
};
payout = (long)(amount * odds);
note = "中奖";
}
@ -559,7 +570,7 @@ namespace Oshima.FunGame.WebAPI.Services
}
// 创建比赛
public static bool CreateMatch(int eventId, string team1Name, string team2Name, string stage, DateTime startTime, DateTime betDeadline, string availableOptions, 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, out string error, out long? newMatchId)
{
error = "";
newMatchId = null;
@ -581,6 +592,22 @@ namespace Oshima.FunGame.WebAPI.Services
.Where(s => s.Length > 0)];
string optionsJson = System.Text.Json.JsonSerializer.Serialize(options);
// 如果比赛包含比分或 MVP 选项,不允许自定义猜胜者赔率
bool hasSpecialOption = options.Contains("score") || options.Contains("mvp");
if ((team1WinOdds.HasValue || team2WinOdds.HasValue) && hasSpecialOption)
{
error = "比赛包含比分或MVP选项时不能自定义猜胜者赔率。";
return false;
}
decimal t1Odds = team1WinOdds ?? 2.50m;
decimal t2Odds = team2WinOdds ?? 2.50m;
if (t1Odds <= 0 || t2Odds <= 0)
{
error = "赔率必须大于0。";
return false;
}
sql.Parameters["@eid"] = eventId;
sql.Parameters["@t1"] = team1Name;
sql.Parameters["@t2"] = team2Name;
@ -588,9 +615,11 @@ namespace Oshima.FunGame.WebAPI.Services
sql.Parameters["@start"] = startTime;
sql.Parameters["@deadline"] = betDeadline;
sql.Parameters["@opts"] = optionsJson;
sql.Parameters["@t1_odds"] = t1Odds;
sql.Parameters["@t2_odds"] = t2Odds;
sql.Execute(@"INSERT INTO csbetting_matches
(event_id, team1_name, team2_name, stage, start_time, bet_deadline, available_options, status)
VALUES (@eid, @t1, @t2, @stage, @start, @deadline, @opts, 0)");
(event_id, team1_name, team2_name, stage, start_time, bet_deadline, available_options, team1_win_odds, team2_win_odds, status)
VALUES (@eid, @t1, @t2, @stage, @start, @deadline, @opts, @t1_odds, @t2_odds, 0)");
if (sql.Success)
{
@ -603,5 +632,48 @@ namespace Oshima.FunGame.WebAPI.Services
error = "数据库连接失败。";
return false;
}
/// <summary>
/// 管理员提前结束竞猜(将未开始比赛标记为进行中)
/// </summary>
public static bool CloseBetting(int matchId, out string message)
{
using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper();
if (sql == null)
{
message = "数据库连接失败。";
return false;
}
UpdateStatuses(sql);
sql.Parameters["@mid"] = matchId;
sql.ExecuteDataSet("SELECT status, team1_name, team2_name FROM csbetting_matches WHERE id = @mid");
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
{
message = "比赛不存在。";
return false;
}
int status = Convert.ToInt32(sql.DataSet.Tables[0].Rows[0]["status"]);
if (status != 0)
{
message = "该比赛已开始或已结束,无需关闭投注。";
return false;
}
sql.Execute("UPDATE csbetting_matches SET status = 1 WHERE id = @mid");
if (sql.Success)
{
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} 的竞猜已提前关闭。";
return true;
}
message = "更新比赛状态失败。";
return false;
}
}
}

View File

@ -60,3 +60,10 @@ CREATE TABLE IF NOT EXISTS `csbetting_matches` (
KEY `idx_start_time` (`start_time`),
CONSTRAINT `fk_matches_event` FOREIGN KEY (`event_id`) REFERENCES `csbetting_events` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CS比赛表';
-- PATCH
-- 为比赛表添加猜胜者赔率字段
ALTER TABLE `csbetting_matches`
ADD COLUMN `team1_win_odds` DECIMAL(5,2) NOT NULL DEFAULT 2.50 COMMENT '队伍1胜赔率' AFTER `available_options`,
ADD COLUMN `team2_win_odds` DECIMAL(5,2) NOT NULL DEFAULT 2.50 COMMENT '队伍2胜赔率' AFTER `team1_win_odds`;

View File

@ -94,7 +94,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;
}
@ -253,7 +253,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
}
if (CSBettingService.CreateMatch(request.EventId, request.Team1Name, request.Team2Name, request.Stage,
request.StartTime, request.BetDeadline, request.AvailableOptions, out string error, out long? newId))
request.StartTime, request.BetDeadline, request.AvailableOptions, request.Team1WinOdds, request.Team2WinOdds, out string error, out long? newId))
{
md.Content = $"比赛创建成功新比赛ID{newId}";
}
@ -269,5 +269,32 @@ namespace Oshima.FunGame.WebAPI.Controllers
return reply;
}
}
[HttpPost("close-betting")]
public BotReply CloseBetting([FromQuery] long uid, [FromQuery] int matchId)
{
MarkdownMessage md = new() { Content = busy };
BotReply reply = new() { Markdown = md };
try
{
if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? admin) || (!admin.IsAdmin && !admin.IsOperator))
{
md.Content = "你没有权限执行此操作。";
return reply;
}
if (CSBettingService.CloseBetting(matchId, out string msg))
{
md.Content = msg;
}
return reply;
}
catch (Exception e)
{
Logger.LogError(e, "CloseBetting 异常");
return reply;
}
}
}
}

View File

@ -143,6 +143,10 @@ namespace Oshima.FunGame.WebAPI.Services
.AppendButtons(2,
Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档"));
}
await SendAsync(e, "CS赛事竞猜", reply);
return true;
}
@ -268,7 +272,8 @@ namespace Oshima.FunGame.WebAPI.Services
// 指令:创建比赛 <赛事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,score
// 示例:创建比赛 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
if (e.Detail.StartsWith("创建比赛"))
{
e.UseNotice = false;
@ -286,7 +291,7 @@ namespace Oshima.FunGame.WebAPI.Services
"格式:创建比赛 <赛事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 可额外添加 score,mvp");
"选项默认 team1_win,team2_win 比分和MVP选项只允许独立添加score,mvp");
return true;
}
@ -319,11 +324,28 @@ namespace Oshima.FunGame.WebAPI.Services
return true;
}
string options = "team1_win,team2_win";
if (parts.Length > 8)
// 解析剩余参数:选项和赔率
List<string> optionParts = [];
decimal? team1Odds = null, team2Odds = null;
for (int i = 8; i < parts.Length; i++)
{
options = string.Join(",", parts[8..]); // 剩余部分视为选项列表
string segment = parts[i];
if (segment.StartsWith("team1_win="))
{
if (decimal.TryParse(segment[11..], out decimal odds1))
team1Odds = odds1;
}
else if (segment.StartsWith("team2_win="))
{
if (decimal.TryParse(segment[11..], out decimal odds2))
team2Odds = odds2;
}
else
{
optionParts.Add(segment);
}
}
string options = optionParts.Count > 0 ? string.Join(",", optionParts) : "team1_win,team2_win";
BotReply reply = BettingController.CreateMatch(new CreateMatchRequest
{
@ -334,12 +356,44 @@ namespace Oshima.FunGame.WebAPI.Services
Stage = stage,
StartTime = startDt,
BetDeadline = deadlineDt,
AvailableOptions = options
AvailableOptions = options,
Team1WinOdds = team1Odds,
Team2WinOdds = team2Odds
});
await SendAsync(e, "创建比赛", reply);
return true;
}
// 指令:关闭投注 <比赛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, "关闭投注", "你没有权限执行此操作。");
return true;
}
string detail = e.Detail
.Replace("关闭投注", "")
.Replace("结束竞猜", "")
.Trim();
if (int.TryParse(detail, out int matchId))
{
BotReply reply = BettingController.CloseBetting(uid, matchId);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
await SendAsync(e, "关闭投注", reply);
}
else
{
await SendAsync(e, "关闭投注", "格式:关闭投注 <比赛ID>");
}
return true;
}
return false;
}
}