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

324 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using RainOpsMini.Helpers;
using RainOpsMini.Helpers.RCON;
using RainOpsMini.Services;
using RainOpsMini;
using RainOpsMini.Models;
namespace RainOpsMini.Plugins
{
public class RedemptionPlugin : IPlugin, IDisposable
{
public string Name => "积分兑换插件";
public string Description => "允许玩家使用积分兑换VIP或SVIP权限";
public PluginCategory Category => PluginCategory.PointsFunction;
public bool IsEnabled => _config.Enabled;
private RedemptionConfig _config = new RedemptionConfig();
private readonly string _configPath;
public RedemptionPlugin()
{
_configPath = Path.Combine(Environment.CurrentDirectory, "PluginConfig", "RedemptionConfig.json");
LoadConfig();
Program.OnRconMessageReceived += OnRconMessage;
}
public object GetConfig() => _config;
public void UpdateConfig(JsonElement config)
{
try
{
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
_config = JsonSerializer.Deserialize<RedemptionConfig>(config.GetRawText(), options) ?? new RedemptionConfig();
SaveConfig();
}
catch (Exception ex)
{
RainOpsLog.Log($"[RedemptionPlugin] UpdateConfig Error: {ex.Message}");
}
}
public void ResetConfig()
{
_config = new RedemptionConfig();
SaveConfig();
}
private void LoadConfig()
{
try
{
if (File.Exists(_configPath))
{
var json = File.ReadAllText(_configPath);
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
_config = JsonSerializer.Deserialize<RedemptionConfig>(json, options) ?? new RedemptionConfig();
}
else
{
SaveConfig();
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[RedemptionPlugin] LoadConfig Error: {ex.Message}");
}
}
private void SaveConfig()
{
try
{
var dir = Path.GetDirectoryName(_configPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
var options = new JsonSerializerOptions { WriteIndented = true };
var json = JsonSerializer.Serialize(_config, options);
File.WriteAllText(_configPath, json);
}
catch (Exception ex)
{
RainOpsLog.Log($"[RedemptionPlugin] SaveConfig Error: {ex.Message}");
}
}
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();
// List Command
if (content.Equals(_config.ListCommand, StringComparison.OrdinalIgnoreCase))
{
await HandleListCommand(chatMsg.SteamId);
return;
}
// Redeem Command
if (content.StartsWith(_config.RedeemCommand, StringComparison.OrdinalIgnoreCase))
{
string param = content.Substring(_config.RedeemCommand.Length).Trim();
if (int.TryParse(param, out int index))
{
await HandleRedeemCommand(chatMsg.SteamId, chatMsg.Name, index);
}
else if (string.IsNullOrWhiteSpace(param))
{
// If user just types "Redeem", maybe show list? Or help?
// Let's show list for convenience
await HandleListCommand(chatMsg.SteamId);
}
}
}
catch (Exception ex)
{
RainOpsLog.Log($"[RedemptionPlugin] OnRconMessage Error: {ex.Message}");
}
}
private async Task HandleListCommand(string steamId)
{
if (_config.Options == null || _config.Options.Count == 0)
{
await SendWarn(steamId, "当前没有任何可兑换的选项。");
return;
}
var points = await PointsService.GetPointsAsync(steamId);
// 1. 发送标题和积分
await SendWarn(steamId, $"【积分兑换列表】 (当前积分: {points})");
await Task.Delay(200);
// 2. 逐条发送选项
for (int i = 0; i < _config.Options.Count; i++)
{
var opt = _config.Options[i];
await SendWarn(steamId, $"{i + 1}. {opt.Name} - {opt.Cost}积分 ({opt.Description})");
await Task.Delay(200);
}
// 3. 发送操作提示
await SendWarn(steamId, $"请输入 '{_config.RedeemCommand} + 序号' 进行兑换,例如: {_config.RedeemCommand} 1");
}
private async Task HandleRedeemCommand(string steamId, string playerName, int index)
{
if (_config.Options == null || index < 1 || index > _config.Options.Count)
{
await SendWarn(steamId, "无效的兑换序号。");
return;
}
var option = _config.Options[index - 1];
// 1. Check Points
int currentPoints = await PointsService.GetPointsAsync(steamId);
if (currentPoints < option.Cost)
{
await SendWarn(steamId, $"积分不足!需要 {option.Cost} 积分,当前拥有 {currentPoints} 积分。");
return;
}
// 2. Deduct Points
await PointsService.AdjustPointsAsync(steamId, -option.Cost);
// 3. Apply VIP/SVIP
try
{
ApplyVip(steamId, playerName, option);
await SendWarn(steamId, $"兑换成功!消耗 {option.Cost} 积分,获得 {option.Name}。");
RainOpsLog.Log($"[Redemption] Player {playerName}({steamId}) redeemed {option.Name} for {option.Cost} points.");
}
catch (Exception ex)
{
// Refund if failed (optional, but good practice)
await PointsService.AdjustPointsAsync(steamId, option.Cost);
RainOpsLog.Log($"[Redemption] Error applying VIP for {steamId}: {ex.Message}");
await SendWarn(steamId, "兑换发生错误,积分已退回,请联系管理员。");
}
}
private void ApplyVip(string steamId, string playerName, RedemptionOption option)
{
var existingVip = VipStorage.GetBySteamId(steamId);
DateTime? currentVipExpiry = null;
DateTime? currentSvipExpiry = null;
string finalGroup = "";
DateTime finalExpiry = DateTime.MinValue;
// Load existing expiries
if (existingVip != null)
{
currentVipExpiry = existingVip.VipExpiryDate;
currentSvipExpiry = existingVip.SvipExpiryDate;
// Migration/Fallback logic similar to CdkService
if (currentVipExpiry == null && currentSvipExpiry == null)
{
// Legacy support or just simple ExpiryDate
if (existingVip.Group == "SVIP_RainOpsMini") currentSvipExpiry = existingVip.ExpiryDate;
else currentVipExpiry = existingVip.ExpiryDate;
}
}
// Calculate new expiries
bool isSvip = option.Group == "SVIP_RainOpsMini";
DateTime now = DateTime.Now;
if (isSvip)
{
if (currentSvipExpiry.HasValue && currentSvipExpiry.Value > now)
{
currentSvipExpiry = currentSvipExpiry.Value.AddDays(option.DurationDays);
}
else
{
currentSvipExpiry = now.AddDays(option.DurationDays);
}
}
else // VIP
{
if (currentVipExpiry.HasValue && currentVipExpiry.Value > now)
{
currentVipExpiry = currentVipExpiry.Value.AddDays(option.DurationDays);
}
else
{
currentVipExpiry = now.AddDays(option.DurationDays);
}
}
// Determine final group and expiry for the main fields
bool hasActiveSvip = currentSvipExpiry.HasValue && currentSvipExpiry.Value > now;
bool hasActiveVip = currentVipExpiry.HasValue && currentVipExpiry.Value > now;
if (hasActiveSvip)
{
finalGroup = "SVIP_RainOpsMini";
finalExpiry = currentSvipExpiry.Value;
}
else if (hasActiveVip)
{
finalGroup = "VIP_RainOpsMini";
finalExpiry = currentVipExpiry.Value;
}
else
{
// Should not happen if we just added time
finalGroup = option.Group;
finalExpiry = now.AddDays(option.DurationDays);
}
// Save
if (existingVip != null)
{
existingVip.Group = finalGroup;
existingVip.ExpiryDate = finalExpiry;
existingVip.VipExpiryDate = currentVipExpiry;
existingVip.SvipExpiryDate = currentSvipExpiry;
existingVip.Remark = $"积分兑换: {option.Name}";
VipStorage.Update(existingVip); // Should trigger save
}
else
{
var newVip = new VipMember
{
SteamId = steamId,
Name = playerName,
Group = finalGroup,
ExpiryDate = finalExpiry,
VipExpiryDate = currentVipExpiry,
SvipExpiryDate = currentSvipExpiry,
Remark = $"积分兑换: {option.Name}",
CreatedAt = DateTime.Now
};
VipStorage.Add(newVip);
}
// Sync to server config
try
{
string cfgPath = System.IO.Path.Combine(Environment.CurrentDirectory, "SquadGame", "ServerConfig", "Admins.cfg");
VipStorage.SyncToAdminConfig(cfgPath);
}
catch (Exception ex)
{
RainOpsLog.Log($"[Redemption] Sync Admin Config Failed: {ex.Message}");
}
}
private async Task SendWarn(string steamId, string message)
{
if (Program.Client != null && Program.Client.IsConnected)
{
// 防止消息中的双引号破坏RCON命令结构
message = message.Replace("\"", "'");
// 移除换行符
message = message.Replace("\r", "").Replace("\n", " ");
await Program.Client.SendCommandAsync($"AdminWarn \"{steamId}\" \"{message}\"");
}
}
public void Dispose()
{
Program.OnRconMessageReceived -= OnRconMessage;
}
}
}