mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-08 14:25:33 +08:00
324 lines
12 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|