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

319 lines
10 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.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace RainOpsMini.Plugins
{
public class LotteryConfig
{
/// <summary>
/// 是否启用插件
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// 抽奖触发关键字
/// </summary>
public List<string> Keywords { get; set; } = new List<string> { "抽奖", "cj" };
/// <summary>
/// 积分模式Fixed=固定值Random=随机值)
/// </summary>
public string PointsMode { get; set; } = "Random"; // "Fixed" or "Random"
/// <summary>
/// 固定积分数值
/// </summary>
public int FixedPoints { get; set; } = 100;
/// <summary>
/// 随机积分范围(格式:最小值,最大值)
/// </summary>
public string RandomRange { get; set; } = "10,500"; // "Min,Max"
/// <summary>
/// 抽奖冷却时间(分钟)
/// </summary>
public int CooldownMinutes { get; set; } = 60;
/// <summary>
/// 冷却模式Min=按分钟Day=每日一次)
/// </summary>
public string CooldownType { get; set; } = "Min"; // "Min" or "Day"
/// <summary>
/// 通知模式Warn=警告弹窗Broadcast=全服广播)
/// </summary>
public string NotificationMode { get; set; } = "Warn"; // "Broadcast" or "Warn"
}
public class PlayerLotteryData
{
public string SteamId { get; set; } = "";
public DateTime LastDrawTime { get; set; } = DateTime.MinValue;
}
public class LotteryPlugin : IPlugin, IDisposable
{
public string Name => "抽奖插件";
public string Description => "玩家积分抽奖 (固定/随机模式)";
public PluginCategory Category => PluginCategory.PointsFunction;
public bool IsEnabled => _config.Enabled;
private LotteryConfig _config = new LotteryConfig();
private ConcurrentDictionary<string, PlayerLotteryData> _data = new ConcurrentDictionary<string, PlayerLotteryData>();
private readonly string _dataPath;
/// <summary>
/// 构造函数,初始化数据路径并加载数据
/// </summary>
public LotteryPlugin()
{
_dataPath = Path.Combine(Environment.CurrentDirectory, "PluginConfig", "LotteryData.json");
LoadData();
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<LotteryConfig>(config.GetRawText(), options) ?? new LotteryConfig();
// SaveData(); // Not needed
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] UpdateConfig Error: {ex.Message}");
}
}
/// <summary>
/// 重置配置为默认值
/// </summary>
public void ResetConfig()
{
_config = new LotteryConfig();
}
/// <summary>
/// 释放资源,取消订阅
/// </summary>
public void Dispose()
{
Program.OnRconMessageReceived -= OnRconMessage;
}
/// <summary>
/// 处理RCON消息回调
/// </summary>
/// <param name="msg">消息内容</param>
private async void OnRconMessage(string msg)
{
if (!_config.Enabled) return;
try
{
var chatMsg = RconParser.ParseChatMessage(msg);
if (chatMsg == null) return;
if (IsKeywordMatch(chatMsg.Message))
{
await HandleDraw(chatMsg);
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] OnRconMessage Error: {ex.Message}");
}
}
/// <summary>
/// 检查消息是否匹配关键字
/// </summary>
/// <param name="message">消息内容</param>
/// <returns>是否匹配</returns>
private bool IsKeywordMatch(string message)
{
if (string.IsNullOrWhiteSpace(message)) return false;
foreach (var key in _config.Keywords)
{
if (string.Equals(message, key, StringComparison.OrdinalIgnoreCase)) return true;
string keyPinyin = PinyinHelper.GetInitials(key);
if (string.Equals(message, keyPinyin, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
/// <summary>
/// 处理抽奖逻辑
/// </summary>
/// <param name="chatMsg">聊天消息对象</param>
private async Task HandleDraw(ChatMessage chatMsg)
{
var data = GetOrAddData(chatMsg.SteamId);
// 检查冷却时间
bool canDraw = false;
if (_config.CooldownType.Equals("Day", StringComparison.OrdinalIgnoreCase))
{
if (data.LastDrawTime.Date < DateTime.Now.Date)
{
canDraw = true;
}
}
else // Min
{
if ((DateTime.Now - data.LastDrawTime).TotalMinutes >= _config.CooldownMinutes)
{
canDraw = true;
}
}
if (!canDraw)
{
string waitMsg = _config.CooldownType.Equals("Day", StringComparison.OrdinalIgnoreCase)
? "今天已抽过奖,请明天再来!"
: $"抽奖冷却中,请等待 {(_config.CooldownMinutes - (DateTime.Now - data.LastDrawTime).TotalMinutes):F1} 分钟。";
// 对于冷却警告,通常只警告用户,不广播失败
await Program.Client.SendCommandAsync($"AdminWarn \"{chatMsg.SteamId}\" \"{waitMsg}\"");
return;
}
// 计算积分
int points = 0;
if (_config.PointsMode.Equals("Fixed", StringComparison.OrdinalIgnoreCase))
{
points = _config.FixedPoints;
}
else
{
points = GetRandomPoints(_config.RandomRange);
}
// 应用积分变更
int newTotal = await PointsService.AdjustPointsAsync(chatMsg.SteamId, points);
// 更新冷却时间
data.LastDrawTime = DateTime.Now;
SaveData();
// 发送通知
string successMsg = $"恭喜!抽奖获得 {points} 积分!当前总积分: {newTotal}";
if (_config.NotificationMode.Equals("Broadcast", StringComparison.OrdinalIgnoreCase))
{
await Program.Client.SendCommandAsync($"AdminBroadcast \" {chatMsg.Name} {successMsg}\"");
}
else
{
await Program.Client.SendCommandAsync($"AdminWarn \"{chatMsg.SteamId}\" \"{successMsg}\"");
}
RainOpsLog.Log($"[{Name}] Player {chatMsg.Name} ({chatMsg.SteamId}) drew lottery. +{points}");
}
/// <summary>
/// 获取随机积分
/// </summary>
/// <param name="valConfig">积分范围配置字符串</param>
/// <returns>随机积分值</returns>
private int GetRandomPoints(string valConfig)
{
try
{
var parts = valConfig.Split(',');
if (parts.Length >= 2)
{
int min = int.Parse(parts[0]);
int max = int.Parse(parts[1]);
if (min > max) (min, max) = (max, min);
return new Random().Next(min, max + 1);
}
}
catch
{
RainOpsLog.Log($"[{Name}] Invalid RandomRange config: {valConfig}");
}
return 0;
}
/// <summary>
/// 获取或添加玩家抽奖数据
/// </summary>
/// <param name="steamId">玩家SteamID</param>
/// <returns>抽奖数据对象</returns>
private PlayerLotteryData GetOrAddData(string steamId)
{
return _data.GetOrAdd(steamId, id => new PlayerLotteryData { SteamId = id });
}
/// <summary>
/// 从文件加载数据
/// </summary>
private void LoadData()
{
if (File.Exists(_dataPath))
{
try
{
string json = File.ReadAllText(_dataPath);
var list = JsonSerializer.Deserialize<List<PlayerLotteryData>>(json);
if (list != null)
{
foreach (var item in list)
{
_data[item.SteamId] = item;
}
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] LoadData Error: {ex.Message}");
}
}
}
/// <summary>
/// 保存数据到文件
/// </summary>
private void SaveData()
{
try
{
string dir = Path.GetDirectoryName(_dataPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
string json = JsonSerializer.Serialize(_data.Values, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_dataPath, json);
}
catch (Exception ex)
{
RainOpsLog.Log($"[{Name}] SaveData Error: {ex.Message}");
}
}
}
}