Files
hjha-server/GameModule/Manager/LuckPlayerManager.cs
xiaoou e9616125ce feat: initial commit - HJHA game server full source
6 major server modules (PdkFriendServer/GlobalSever/ServerCore/GameModule/GameNetModule) +
game logic (GameFix/GameDAL/ServerData) +
network layer (NetWorkMessage) +
data layer (ObjectModel) +
utilities (MrWu/Core/Config/CloudAPI/dll) +
adapters (zyxAdapter/base)

.NET 8.0 C# solution, 16 projects, 958 source files
2026-07-07 12:02:15 +08:00

877 lines
31 KiB
C#
Raw 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 System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using MrWu.Debug;
using Newtonsoft.Json;
using Server.Core;
namespace Server
{
//最近 100 场 最大输值的平均值
//N 倍
// 连输 3 场以上 每加1场 增加 %30 发好牌的几率
// 连赢 4 场以上 每加1场 增加 %20 发差牌的几率
// 输的局数 是平均值的 N 倍 拥有%100 发好牌的几率
// 赢的局数 是平均值的 2N 倍 拥有%100 发差牌的几率
// 计算最大输值的 除以赔率 换算成积分
// 当天输的超过 N 倍 x %30 每超过 %1 就增加 1的发好牌几率 封顶 %50
// 当天赢的超过 N 倍 x %50 每超过 %1 就增加 1的发差牌几率 封顶 %50
/// <summary>
/// 幸运玩家管理器
/// </summary>
public partial class LuckPlayerManager : SingleInstance<LuckPlayerManager>
{
/// <summary>
/// 玩法的战绩记录
/// </summary>
private readonly ConcurrentDictionary<string, GamePlayerScoreRecord> _wayGamePlayerScoreRecords =
new ConcurrentDictionary<string, GamePlayerScoreRecord>();
private readonly ConcurrentQueue<string> _wayGamePlayerWayIds = new ConcurrentQueue<string>();
private const string RecordPath = "LuckRecord/WayRecordDataV2.txt";
/// <summary>
/// 15天未玩游戏就过期
/// </summary>
private const long ExpireTime = 15L * 24 * 60 * 60 * 1000;
/// <summary>
/// 统计次数
/// </summary>
private const int StaticsCnt = 100;
/// <summary>
/// 输的平均值倍数
/// </summary>
private float _loseMultConfig = 3f;
private bool isEnable = false;
public event Action<GamePlayerScoreRecord, PlayerScoreRecord> PlayerScoreUpdate;
public event Action<GamePlayerScoreRecord> WayRecordUpdate;
/// <summary>
/// 加载数据
/// </summary>
public void LoadData(float loseMultConfig)
{
isEnable = loseMultConfig >= 1f;
this._loseMultConfig = loseMultConfig;
LoadRecordData();
LoadClubBalanceData();
Debug.Info($"是否启用:{isEnable} {loseMultConfig}");
}
private void LoadRecordData()
{
if (!isEnable)
{
return;
}
if (_wayGamePlayerScoreRecords.Count > 0)
{
Debug.Error("已加载过幸运玩家数据!");
return;
}
_wayGamePlayerScoreRecords.Clear();
try
{
if (File.Exists(RecordPath))
{
string jsonValue = File.ReadAllText(RecordPath);
FriendLuckRecord records = JsonConvert.DeserializeObject<FriendLuckRecord>(jsonValue);
foreach (var record in records.ScoreRecords)
{
//先进行数据初始化
record.PlayerScoreRecords = new ConcurrentDictionary<int, PlayerScoreRecord>();
foreach (var item in record.Records)
{
if (!record.PlayerScoreRecords.TryAdd(item.UserId, item))
{
Debug.Error($"怎么会有重复数据:{item.UserId}");
}
record.PlayerUserIds.Enqueue(item.UserId);
}
if (_wayGamePlayerScoreRecords.TryAdd(record.WayId, record))
{
_wayGamePlayerWayIds.Enqueue(record.WayId);
}
else
{
Debug.Error($"玩法怎么会有重复数据:{record.WayId}");
}
}
}
}
catch (Exception e)
{
Debug.Error($"LoadRecordData error:{e.Message}");
}
}
/// <summary>
/// 保存数据
/// </summary>
public void SaveData()
{
if (!isEnable)
{
return;
}
try
{
FriendLuckRecord records = new FriendLuckRecord();
foreach (var record in _wayGamePlayerScoreRecords.Values)
{
if (record.PlayerScoreRecords != null)
{
record.Records.Clear();
foreach (var item in record.PlayerScoreRecords.Values)
{
record.Records.Add(item);
}
}
records.ScoreRecords.Add(record);
}
string jsonValue = JsonConvert.SerializeObject(records);
FileInfo fileInfo = new FileInfo(RecordPath);
if (!fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
File.WriteAllText(RecordPath, jsonValue);
SaveClubBalanceData();
}
catch (Exception e)
{
Debug.Error($"SaveData error:{e.Message}");
}
}
private ConcurrentQueue<Random> _randoms = new ConcurrentQueue<Random>();
private Random GetRandom()
{
if (_randoms.TryDequeue(out Random random))
{
return random;
}
return new Random();
}
private void ReleaseRandom(Random random)
{
_randoms.Enqueue(random);
}
/// <summary>
/// 获取幸运值索引
/// </summary>
/// <param name="wayId"></param>
/// <param name="userIds"></param>
/// <returns></returns>
public (int, int) GetLuckValueIndex(string wayId, List<int> userIds)
{
int luckIndex = -1;
int badLuckIndex = -1;
if (!isEnable)
{
return (luckIndex, badLuckIndex);
}
List<PlayerScoreRecord> scoreRecords = new List<PlayerScoreRecord>();
if (_wayGamePlayerScoreRecords.TryGetValue(wayId, out GamePlayerScoreRecord record))
{
for (int i = 0; i < userIds.Count; i++)
{
if (userIds[i] <= 0)
{
Debug.Warning("怎么会有Userid 小于0的情况!");
return (luckIndex, badLuckIndex);
}
if (record.PlayerScoreRecords.TryGetValue(userIds[i], out PlayerScoreRecord playerRecord))
{
scoreRecords.Add(playerRecord);
}
}
}
else
{
return (luckIndex, badLuckIndex);
}
Random random = GetRandom();
//先计算连输 连赢
for (int i = 0; i < scoreRecords.Count; i++)
{
//连续赢 增加发差牌概率
if (badLuckIndex < 0 && scoreRecords[i].ContinuousWinCnt > 4) //已经连赢4次以上
{
scoreRecords[i].ContinuousWin_BadWeight = 20 * (scoreRecords[i].ContinuousWinCnt - 4);
if (random.Next(0, 100) < scoreRecords[i].ContinuousWin_BadWeight)
{
badLuckIndex = i;
}
Debug.Info($"连续赢:{i} {badLuckIndex} 触发发差牌概率:{scoreRecords[i].ContinuousWin_BadWeight}");
}
//连续输 增加发好牌的概率
if (luckIndex < 0 && scoreRecords[i].ContinuousLoseCnt > 3) //已经连输3次以上
{
scoreRecords[i].ContinuosLose_LuckWeight = 30 * (scoreRecords[i].ContinuousLoseCnt - 3);
if (random.Next(0, 100) < scoreRecords[i].ContinuosLose_LuckWeight)
{
luckIndex = i;
}
Debug.Info($"连续输:{i} {luckIndex} 触发发好牌概率:{scoreRecords[i].ContinuosLose_LuckWeight}");
}
}
//计算新手保护
if (luckIndex < 0)
{
for (int i = scoreRecords.Count - 1; i >= 0; i--)
{
if (i == luckIndex || i == badLuckIndex)
{
continue;
}
if (scoreRecords[i].NewPlayerCnt > 0)
{
scoreRecords[i].NewPlayerCnt--;
if (random.NextDouble() < 0.2f)
{
luckIndex = i;
Debug.Info($"新手保护发牌:{scoreRecords[i].UserId}");
break;
}
}
}
}
//计算俱乐部平衡
if (BalanceAllConfigs.Count > 0)
{
for (int i = 0; i < scoreRecords.Count; i++)
{
if (luckIndex >= 0 && badLuckIndex >= 0)
{
break;
}
if (i == luckIndex || i == badLuckIndex)
{
continue;
}
ClubPlayerBalanceType clubPlayerBalanceType = GetBalancePlayerType(record.BeiLv,record.WayFightCnt, scoreRecords[i].UserId);
// 不计算平衡
if (clubPlayerBalanceType < ClubPlayerBalanceType.Luck)
{
continue;
}
if (luckIndex < 0 && clubPlayerBalanceType == ClubPlayerBalanceType.Luck)
{
luckIndex = i;
Debug.Info($"俱乐部平衡幸运:{scoreRecords[i].UserId}");
}
if (badLuckIndex < 0 && clubPlayerBalanceType == ClubPlayerBalanceType.BadLuck)
{
badLuckIndex = i;
Debug.Info($"俱乐部平衡倒霉:{scoreRecords[i].UserId}");
}
}
}
//计算这段时间的输赢平衡
if (record.AverageLoseMax < -1)
{
for (int i = scoreRecords.Count - 1; i >= 0; i--)
{
if (i == luckIndex || i == badLuckIndex)
{
continue;
}
if (luckIndex < 0 && scoreRecords[i].NextFightLuck >= 100)
{
luckIndex = i;
scoreRecords[i].NextFightLuck = 0;
Debug.Info($"这个位置输的多,发好牌:{i}");
}
if (badLuckIndex < 0 && scoreRecords[i].NextFightBadLuck >= 100)
{
badLuckIndex = i;
scoreRecords[i].NextFightBadLuck = 0;
Debug.Info($"这个位置赢的多,发差牌:{i}");
}
}
}
//计算一天的输赢平衡
if (record.MaxLoseScore < -1)
{
for (int i = 0; i < scoreRecords.Count; i++)
{
if (i == luckIndex || i == badLuckIndex)
{
continue;
}
float multValue = Math.Abs(scoreRecords[i].DayScore / record.MaxLoseScore);
if (multValue > _loseMultConfig)
{
//计算幸运
if (luckIndex < 0 && scoreRecords[i].DayScore < 0 && multValue > _loseMultConfig)
{
float luckProbability = (multValue / _loseMultConfig - 1f) - 0.3f;
if (luckProbability > 0 && random.NextDouble() < luckProbability)
{
luckIndex = i;
Debug.Info($"按天计算 幸运玩家概率:{i} {luckProbability} {multValue} {_loseMultConfig}");
}
}
if (badLuckIndex < 0 && scoreRecords[i].DayScore > 0 && multValue > _loseMultConfig)
{
float badLuckProbability = (multValue / _loseMultConfig - 1f) - 0.5f;
if (badLuckProbability > 0 && random.NextDouble() < badLuckProbability)
{
badLuckIndex = i;
Debug.Info($"按天计算 倒霉玩家概率:{i} {badLuckProbability} {multValue} {_loseMultConfig}");
}
}
}
}
}
ReleaseRandom(random);
if (luckIndex >= 0 || badLuckIndex >= 0)
{
FriendRoomStatistics.Instance.AddBalance(wayId, luckIndex < 0 ? -1 : scoreRecords[luckIndex].UserId,
badLuckIndex < 0 ? -1 : scoreRecords[badLuckIndex].UserId);
Debug.Info($"幸运玩家:{(luckIndex >= 0 ? scoreRecords[luckIndex].UserId : -1)} {(badLuckIndex >= 0 ? scoreRecords[badLuckIndex].UserId : -1)}");
}
return (luckIndex, badLuckIndex);
}
public void AddOnceFight(string wayId, int userid, int score)
{
if (!isEnable)
{
return;
}
if (!_wayGamePlayerScoreRecords.TryGetValue(wayId, out GamePlayerScoreRecord record))
{
return;
}
PlayerScoreRecord playerScoreRecord = record.PlayerScoreRecords.GetOrAdd(userid,
(key) =>
{
record.PlayerUserIds.Enqueue(key);
Debug.Info($"创建玩家x {userid}");
return new PlayerScoreRecord()
{
UserId = key,
LastEditorTime = TimeInfo.Instance.FrameTime
};
}
);
//连续输赢的统计 小赢小输不算连续
if (score > 5)
{
playerScoreRecord.ContinuousWinCnt++;
playerScoreRecord.ContinuousLoseCnt = 0;
}
if (score < -5)
{
playerScoreRecord.ContinuousLoseCnt++;
playerScoreRecord.ContinuousWinCnt = 0;
}
playerScoreRecord.DayScore += score;
if (score >= 0)
playerScoreRecord.WinFightCnt++;
if (score > 0 && playerScoreRecord.NewPlayerCnt > 0)
{
playerScoreRecord.NewPlayerCnt--;
}
playerScoreRecord.TotalFightCnt++;
//最大输值的平均有值,才开始记录
//记录值
playerScoreRecord.LastEditorTime = TimeInfo.Instance.FrameTime - 5000;
Debug.Info($"单局统计:{record.BeiLv} {record.WayFightCnt} {playerScoreRecord.ToString()}");
AddOnceFightScore(record.BeiLv,record.WayFightCnt, userid, score);
}
/// <summary>
/// 添加积分到记录 所有局数都打完才记录 临时解散的不算
/// </summary>
/// <param name="wayId">玩法Id</param>
/// <param name="beiLv">倍率</param>
/// <param name="fightCnt"> fightCnt </param>
/// <param name="fightScore"> 分值 </param>
public void AddScoreRecord(string wayId, float beiLv, int fightCnt, FightScore[] fightScore)
{
if (!isEnable)
{
return;
}
GamePlayerScoreRecord record = _wayGamePlayerScoreRecords.GetOrAdd(wayId, (key) =>
{
_wayGamePlayerWayIds.Enqueue(wayId);
Debug.Info($"创建 GamePlayerScoreRecord{wayId}");
return new GamePlayerScoreRecord()
{
WayId = wayId,
BeiLv = beiLv,
WayFightCnt = fightCnt,
PlayerScoreRecords = new ConcurrentDictionary<int, PlayerScoreRecord>(),
LastEditorTime = TimeInfo.Instance.FrameTime
};
});
Debug.Info($"_wayGamePlayerScoreRecords Count:{_wayGamePlayerScoreRecords.Count}");
int winMaxScore = 0;
int loseMaxScore = 0;
StringBuilder stringBuilder = new StringBuilder();
foreach (var score in fightScore)
{
if (score.Value > winMaxScore)
{
winMaxScore = (int)score.Value;
}
if (score.Value < loseMaxScore)
{
loseMaxScore = (int)score.Value;
}
}
//最大输值的平均有值,才开始记录
//记录值
foreach (var score in fightScore)
{
stringBuilder.Append($"userid:{score.UserId} value:{score.Value} ");
PlayerScoreRecord playerScoreRecord = record.PlayerScoreRecords.GetOrAdd(score.UserId,
(key) =>
{
record.PlayerUserIds.Enqueue(key);
Debug.Info($"创建玩家- {score.UserId}");
return new PlayerScoreRecord()
{
UserId = key,
NewPlayerCnt = 10, //前10局新手保护 概率是%20 所以相当于最多2次机会
LastEditorTime = TimeInfo.Instance.FrameTime
};
}
);
playerScoreRecord.PingHengFightCnt--;
if (playerScoreRecord.PingHengFightCnt <= 0)
{
Debug.Info($"平衡场次重算:{playerScoreRecord.UserId}");
Random random = GetRandom();
playerScoreRecord.PingHengFightCnt = random.Next(60, 100);
if (playerScoreRecord.DayScore < 0)
{
playerScoreRecord.DayScore /= 2;
}
else
{
playerScoreRecord.DayScore = 0;
}
ReleaseRandom(random);
}
playerScoreRecord.TotalScore += (int)score.Value;
Debug.Info($"添加:{playerScoreRecord.UserId} {score.Value} {playerScoreRecord.TotalScore}");
if (record.AverageLoseMax < -1)
{
//有了参考值才计算 幸运 或者倒霉玩家
playerScoreRecord.Score += (int)score.Value;
playerScoreRecord.NextFightLuck = 0;
playerScoreRecord.NextFightBadLuck = 0;
float multConfigValue = Math.Abs((float)playerScoreRecord.Score / record.AverageLoseMax);
if (playerScoreRecord.Score < 0 && multConfigValue > _loseMultConfig)
{
//给一个幸运的概率
playerScoreRecord.NextFightLuck = 100;
playerScoreRecord.Score = 0;
Debug.Info(
$"触发幸运玩家:{playerScoreRecord.UserId} {playerScoreRecord.Score} {multConfigValue}:{_loseMultConfig}");
}
if (playerScoreRecord.Score > 0 && multConfigValue > 2 * _loseMultConfig)
{
playerScoreRecord.NextFightBadLuck = 100;
playerScoreRecord.Score = 0;
Debug.Info(
$"触发倒霉玩家:{playerScoreRecord.UserId} {playerScoreRecord.Score} {multConfigValue}:{_loseMultConfig}");
}
}
PlayerScoreUpdate?.Invoke(record, playerScoreRecord);
AddSessionFightScore(beiLv,fightCnt,playerScoreRecord.UserId, (int)score.Value,Math.Abs(score.Value - winMaxScore) < 0.01f);
}
record.TotalLoseMax += loseMaxScore;
record.TotalWinMax += winMaxScore;
if (loseMaxScore < record.LoseMax)
{
record.LoseMax = loseMaxScore;
}
if (winMaxScore > record.WinMax)
{
record.WinMax = winMaxScore;
}
record.FightCnt++;
//第一次 只算一半的局数,先把值算出来
if ((record.AverageLoseMax > -1 && record.FightCnt >= StaticsCnt / 2) || record.FightCnt >= StaticsCnt)
{
record.AverageLoseMax = (int)(record.TotalLoseMax / record.FightCnt);
record.MaxLoseScore = (int)(record.AverageLoseMax / beiLv);
record.FightCnt = 0;
record.TotalLoseMax = 0;
record.TotalWinMax = 0;
}
record.LastEditorTime = TimeInfo.Instance.FrameTime;
WayRecordUpdate?.Invoke(record);
Debug.Info($"总局统计: {record.ToString()}");
}
public string GetPlayerInfo(string wayId, int userid)
{
if (_wayGamePlayerScoreRecords.TryGetValue(wayId, out GamePlayerScoreRecord record))
{
if (record.PlayerScoreRecords.TryGetValue(userid, out PlayerScoreRecord playerScoreRecord))
{
return playerScoreRecord.ToString();
}
}
return "none";
}
public void Update()
{
if (!isEnable)
{
return;
}
// 移除不使用的数据
if (_wayGamePlayerWayIds.Count > 0)
{
try
{
if (_wayGamePlayerWayIds.TryDequeue(out string wayid))
{
bool isRemove = true;
if (_wayGamePlayerScoreRecords.TryGetValue(wayid, out GamePlayerScoreRecord record))
{
if (TimeInfo.Instance.FrameTime - record.LastEditorTime > ExpireTime)
{
if (!_wayGamePlayerScoreRecords.TryRemove(wayid, out record))
{
isRemove = false;
}
Debug.Info($"移除 - {wayid}");
}
else
{
// 计算玩家许久未使用的值
if (record.PlayerUserIds.Count > 0)
{
if (record.PlayerUserIds.TryDequeue(out int userid))
{
bool isDel = true;
if (record.PlayerScoreRecords.TryGetValue(userid,
out PlayerScoreRecord playerScoreRecord))
{
if (TimeInfo.Instance.FrameTime - playerScoreRecord.LastEditorTime >
ExpireTime)
{
Debug.Info($"过期移除:{userid}");
if (!record.PlayerScoreRecords.TryRemove(userid, out playerScoreRecord))
{
isDel = false;
}
}
else
{
isDel = false;
}
}
if (!isDel)
{
record.PlayerUserIds.Enqueue(userid);
}
}
}
isRemove = false;
}
}
if (!isRemove)
{
_wayGamePlayerWayIds.Enqueue(wayid);
}
}
}
catch (Exception e)
{
Debug.Error($"LuckPlayerManager Update Error:{e.Message}");
}
}
}
/// <summary>
/// 朋友房幸运玩家积分记录
/// </summary>
public class FriendLuckRecord
{
public List<GamePlayerScoreRecord> ScoreRecords = new List<GamePlayerScoreRecord>();
}
/// <summary>
/// 玩家积分记录
/// </summary>
public class GamePlayerScoreRecord
{
/// <summary>
/// 玩法ID
/// </summary>
public string WayId;
/// <summary>
/// 玩法局数
/// </summary>
public int WayFightCnt;
/// <summary>
/// 每局赢的最大值总和
/// </summary>
public long TotalWinMax;
/// <summary>
/// 记录赢的最大值
/// </summary>
public long WinMax;
/// <summary>
/// 每局输的最大值总和
/// </summary>
public long TotalLoseMax;
/// <summary>
/// 记录输的最大值
/// </summary>
public long LoseMax;
/// <summary>
/// 场次
/// </summary>
public int FightCnt;
/// <summary>
/// 最近一百次的输的平均
/// </summary>
public int AverageLoseMax;
/// <summary>
/// 倍率
/// </summary>
public float BeiLv;
/// <summary>
/// AverageLoseMax / BeiLv
/// </summary>
public int MaxLoseScore;
/// <summary>
/// 玩家积分记录 - 用来序列化
/// </summary>
public List<PlayerScoreRecord> Records = new List<PlayerScoreRecord>();
/// <summary>
/// 上次修改的时间
/// </summary>
public long LastEditorTime;
/// <summary>
/// 玩家的Userids
/// </summary>
[JsonIgnore] public ConcurrentQueue<int> PlayerUserIds = new ConcurrentQueue<int>();
/// <summary>
/// 玩家的积分记录 - 用来计算
/// </summary>
[JsonIgnore] public ConcurrentDictionary<int, PlayerScoreRecord> PlayerScoreRecords = null;
public override string ToString()
{
return
$"wayid:{WayId}, AverageLoseMax:{AverageLoseMax}, fightcnt:{FightCnt}, winmax:{WinMax}, losemax:{LoseMax}, totalwinmax:{TotalWinMax}, totallosemax:{TotalLoseMax}, lasteditortime:{LastEditorTime}";
}
}
/// <summary>
/// 玩家积分记录
/// </summary>
public class PlayerScoreRecord
{
/// <summary>
/// 玩家ID
/// </summary>
public int UserId;
/// <summary>
/// 距上次发牌累计的积分
/// </summary>
public int Score;
/// <summary>
/// 连续赢的次数
/// </summary>
public int ContinuousWinCnt;
/// <summary>
/// 连续输的次数
/// </summary>
public int ContinuousLoseCnt;
/// <summary>
/// 一段时间的分
/// </summary>
public int DayScore;
/// <summary>
/// 平衡 场次
/// </summary>
public int PingHengFightCnt;
/// <summary>
/// 获得的总分
/// </summary>
public int TotalScore;
/// <summary>
/// 赢的局数
/// </summary>
public int WinFightCnt;
/// <summary>
/// 总战斗次数
/// </summary>
public int TotalFightCnt;
/// <summary>
/// 上次修改的时间
/// </summary>
public long LastEditorTime;
/// <summary>
/// 新手保护的局数
/// </summary>
public int NewPlayerCnt;
/// <summary>
/// 下一个大局 有一个幸运的概率
/// </summary>
[JsonIgnore] public int NextFightLuck;
/// <summary>
/// 下一个大局 有一个倒霉的概率
/// </summary>
[JsonIgnore] public int NextFightBadLuck;
/// <summary>
/// 连续赢 输的权重
/// </summary>
[JsonIgnore] public int ContinuousWin_BadWeight;
/// <summary>
/// 连续输 赢的权重
/// </summary>
[JsonIgnore] public int ContinuosLose_LuckWeight;
public override string ToString()
{
return
$"userid:{UserId}, score:{Score}, TotalScore:{TotalScore}, ContinuousWinCnt:{ContinuousWinCnt},ContinuousLoseCnt:{ContinuousLoseCnt},LastEditorTime:{LastEditorTime},DayScore:{DayScore},WinFightCnt:{WinFightCnt},TotalFightCnt:{TotalFightCnt}";
}
}
public struct FightScore
{
/// <summary>
/// 玩家ID
/// </summary>
public int UserId;
/// <summary>
/// 玩家的分数
/// </summary>
public double Value;
}
}
}