Files
squad-rain-ops-mini/Plugins/RedPacketPlugin.cs

491 lines
16 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Hosting;
using RainOpsMini.Helpers;
using RainOpsMini.Helpers.RCON;
using RainOpsMini.Services;
using RainOpsMini;
using RainOpsMini.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace RainOpsMini.Plugins
{
public class RedPacketConfig
{
/// <summary>
/// 是否启用插件
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// 红包过期时间默认300秒5分钟
/// </summary>
public int TimeoutSeconds { get; set; } = 300;
/// <summary>
/// 发红包触发关键字
/// </summary>
public string SendKeyword { get; set; } = "发红包";
/// <summary>
/// 抢红包触发关键字
/// </summary>
public List<string> GrabKeywords { get; set; } = new List<string> { "抢红包", "抢", "qhb", "QHB" };
}
public class RedPacketSession
{
/// <summary>
/// 发送者SteamID
/// </summary>
public string SenderSteamId { get; set; } = "";
/// <summary>
/// 发送者名称
/// </summary>
public string SenderName { get; set; } = "";
/// <summary>
/// 红包总积分
/// </summary>
public int TotalPoints { get; set; }
/// <summary>
/// 红包总个数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 红包开始时间
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// 待领取的红包金额栈
/// </summary>
public ConcurrentStack<int> Packets { get; set; } = new ConcurrentStack<int>();
/// <summary>
/// 已领取红包的玩家记录 (SteamId -> Points)
/// </summary>
public ConcurrentDictionary<string, int> GrabbedPlayers { get; set; } = new ConcurrentDictionary<string, int>();
/// <summary>
/// 剩余红包个数
/// </summary>
public int RemainingCount => Packets.Count;
/// <summary>
/// 剩余积分总额
/// </summary>
public int RemainingPoints => Packets.Sum();
}
public class RedPacketPlugin : BackgroundService, IPlugin
{
public string Name => "红包插件";
public string Description => "玩家可以发送拼手气红包,其他玩家抢红包";
public PluginCategory Category => PluginCategory.PointsFunction;
public bool IsEnabled => _config.Enabled;
private RedPacketConfig _config = new RedPacketConfig();
private RedPacketSession? _currentSession;
private readonly object _sessionLock = new object();
private readonly string _configPath;
/// <summary>
/// 构造函数,初始化配置路径并加载配置
/// </summary>
public RedPacketPlugin()
{
_configPath = Path.Combine(Environment.CurrentDirectory, "PluginConfig", "RedPacketConfig.json");
LoadConfig();
Program.OnRconMessageReceived += OnRconMessage;
}
/// <summary>
/// 获取当前配置
/// </summary>
/// <returns>配置对象</returns>
public object GetConfig() => _config;
/// <summary>
/// 更新配置
/// </summary>
/// <param name="config">JSON配置元素</param>
public void UpdateConfig(JsonElement config)
{
try
{
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
_config = JsonSerializer.Deserialize<RedPacketConfig>(config.GetRawText(), options) ?? new RedPacketConfig();
SaveConfig();
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] UpdateConfig Error: {ex.Message}");
}
}
/// <summary>
/// 重置配置为默认值
/// </summary>
public void ResetConfig()
{
_config = new RedPacketConfig();
SaveConfig();
}
/// <summary>
/// 释放资源
/// </summary>
public override void Dispose()
{
Program.OnRconMessageReceived -= OnRconMessage;
base.Dispose();
}
/// <summary>
/// 后台任务执行入口,定期检查红包过期
/// </summary>
/// <param name="stoppingToken">取消令牌</param>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (_config.Enabled)
{
await CheckTimeout();
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] Timeout Check Error: {ex.Message}");
}
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
/// <summary>
/// 检查红包是否过期
/// </summary>
private async Task CheckTimeout()
{
RedPacketSession? expiredSession = null;
lock (_sessionLock)
{
if (_currentSession != null)
{
if ((DateTime.Now - _currentSession.StartTime).TotalSeconds >= _config.TimeoutSeconds)
{
expiredSession = _currentSession;
_currentSession = null;
}
}
}
if (expiredSession != null)
{
int refundAmount = expiredSession.RemainingPoints;
if (refundAmount > 0)
{
await PointsService.AdjustPointsAsync(expiredSession.SenderSteamId, refundAmount);
string msg = $"红包已过期,剩余 {refundAmount} 积分已退回给发起者 {expiredSession.SenderName}。";
await Broadcast(msg);
RainOpsLog.Log($"[{Name}] Red packet expired. Refunded {refundAmount} to {expiredSession.SenderName} ({expiredSession.SenderSteamId}).");
}
else
{
await Broadcast("红包已过期,已被抢完。");
}
}
}
/// <summary>
/// 处理RCON消息
/// </summary>
/// <param name="msg">RCON消息内容</param>
private async void OnRconMessage(string msg)
{
if (!_config.Enabled) return;
try
{
var chatMsg = RconParser.ParseChatMessage(msg);
if (chatMsg == null) return;
string content = chatMsg.Message.Trim();
// 检查发红包命令
if (content.StartsWith(_config.SendKeyword, StringComparison.OrdinalIgnoreCase))
{
await HandleSendCommand(chatMsg, content);
return;
}
// 检查抢红包命令
if (_config.GrabKeywords.Any(k => string.Equals(content, k, StringComparison.OrdinalIgnoreCase)))
{
await HandleGrabCommand(chatMsg);
return;
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] OnRconMessage Error: {ex.Message}");
}
}
/// <summary>
/// 处理发红包命令
/// </summary>
/// <param name="chatMsg">聊天消息</param>
/// <param name="content">消息内容</param>
private async Task HandleSendCommand(ChatMessage chatMsg, string content)
{
// 格式: 发红包 数量 总积分
var parts = content.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 3)
{
// 可选:提示格式错误?为了不刷屏,用户只输入“发红包”时不提示
return;
}
if (!int.TryParse(parts[1], out int count) || !int.TryParse(parts[2], out int totalPoints))
{
await SendWarn(chatMsg.SteamId, "格式错误!正确格式:发红包 数量 总积分 (例如:发红包 10 100)");
return;
}
if (count <= 0 || totalPoints <= 0)
{
await SendWarn(chatMsg.SteamId, "数量和积分必须大于0");
return;
}
if (totalPoints < count)
{
await SendWarn(chatMsg.SteamId, "总积分不能小于红包数量每人至少1积分");
return;
}
lock (_sessionLock)
{
if (_currentSession != null)
{
SendWarn(chatMsg.SteamId, "当前已有正在进行的红包活动,请稍后再试!").GetAwaiter();
return;
}
}
// 检查余额
int currentPoints = await PointsService.GetPointsAsync(chatMsg.SteamId);
if (currentPoints < totalPoints)
{
await SendWarn(chatMsg.SteamId, $"积分不足!当前积分:{currentPoints}");
return;
}
// 扣除积分
await PointsService.AdjustPointsAsync(chatMsg.SteamId, -totalPoints);
// 生成红包
var packets = GenerateRandomPackets(totalPoints, count);
var session = new RedPacketSession
{
SenderSteamId = chatMsg.SteamId,
SenderName = chatMsg.Name,
TotalPoints = totalPoints,
TotalCount = count,
StartTime = DateTime.Now,
Packets = new ConcurrentStack<int>(packets)
};
lock (_sessionLock)
{
_currentSession = session;
}
RainOpsLog.Log($"[{Name}] {chatMsg.Name} sent red packet: {count} packets, {totalPoints} points.");
await Broadcast($"土豪 {chatMsg.Name} 发出了 {count} 个拼手气红包,总积分 {totalPoints}!输入 '{_config.GrabKeywords[0]}' 立即开抢!");
}
/// <summary>
/// 处理抢红包命令
/// </summary>
/// <param name="chatMsg">聊天消息</param>
private async Task HandleGrabCommand(ChatMessage chatMsg)
{
RedPacketSession? session;
lock (_sessionLock)
{
session = _currentSession;
}
if (session == null)
{
await SendWarn(chatMsg.SteamId, "当前没有可抢的红包!");
return;
}
if (session.GrabbedPlayers.ContainsKey(chatMsg.SteamId))
{
await SendWarn(chatMsg.SteamId, "你已经抢过这个红包了!");
return;
}
if (session.Packets.TryPop(out int points))
{
if (session.GrabbedPlayers.TryAdd(chatMsg.SteamId, points))
{
// 增加积分
int newTotal = await PointsService.AdjustPointsAsync(chatMsg.SteamId, points);
await SendWarn(chatMsg.SteamId, $"抢到了 {points} 积分!当前总积分:{newTotal}");
RainOpsLog.Log($"[{Name}] {chatMsg.Name} grabbed {points} points.");
// 检查是否抢完
if (session.Packets.IsEmpty)
{
FinishSession(session);
}
}
}
else
{
await SendWarn(chatMsg.SteamId, "手慢了,红包已被抢完!");
}
}
/// <summary>
/// 结束红包会话并结算
/// </summary>
/// <param name="session">红包会话对象</param>
private void FinishSession(RedPacketSession session)
{
lock (_sessionLock)
{
if (_currentSession == session)
{
_currentSession = null;
}
}
// 寻找运气王
var luckiest = session.GrabbedPlayers.OrderByDescending(x => x.Value).FirstOrDefault();
// 从缓存获取玩家名称以优化显示
string luckiestName = "未知玩家";
var player = Program.RconCache.Players.FirstOrDefault(p => p.SteamId == luckiest.Key);
if (player != null) luckiestName = player.Name;
Broadcast($"红包已被抢完!运气王是 {luckiestName},抢到了 {luckiest.Value} 积分!").GetAwaiter();
}
/// <summary>
/// 生成随机红包金额
/// </summary>
/// <param name="totalPoints">总积分</param>
/// <param name="count">红包数量</param>
/// <returns>红包金额列表</returns>
private List<int> GenerateRandomPackets(int totalPoints, int count)
{
// 简单逻辑:
// 1. 先给每个红包分配1积分
// 2. 将剩余积分随机分配
int[] result = new int[count];
for (int i = 0; i < count; i++) result[i] = 1;
int remaining = totalPoints - count;
Random rnd = new Random();
for (int i = 0; i < remaining; i++)
{
int index = rnd.Next(count);
result[index]++;
}
// 打乱顺序
return result.OrderBy(x => rnd.Next()).ToList();
}
/// <summary>
/// 发送全服广播
/// </summary>
/// <param name="message">消息内容</param>
private async Task Broadcast(string message)
{
if (Program.Client != null && Program.Client.IsConnected)
{
await Program.Client.SendCommandAsync($"AdminBroadcast \"{message}\"");
}
}
/// <summary>
/// 发送警告消息给特定玩家
/// </summary>
/// <param name="steamId">玩家SteamID</param>
/// <param name="message">消息内容</param>
private async Task SendWarn(string steamId, string message)
{
if (Program.Client != null && Program.Client.IsConnected)
{
await Program.Client.SendCommandAsync($"AdminWarn \"{steamId}\" \"{message}\"");
}
}
/// <summary>
/// 从文件加载配置
/// </summary>
private void LoadConfig()
{
if (File.Exists(_configPath))
{
try
{
string json = File.ReadAllText(_configPath);
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
_config = JsonSerializer.Deserialize<RedPacketConfig>(json, options) ?? new RedPacketConfig();
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] LoadConfig Error: {ex.Message}");
}
}
else
{
SaveConfig();
}
}
/// <summary>
/// 保存配置到文件
/// </summary>
private void SaveConfig()
{
try
{
string dir = Path.GetDirectoryName(_configPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
string json = JsonSerializer.Serialize(_config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_configPath, json);
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] SaveConfig Error: {ex.Message}");
}
}
}
}