server-master/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/Event/Algorithm/GenerateExperienceEventHandler.cs
2026-02-10 18:21:30 +01:00

514 lines
No EOL
18 KiB
C#

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Events;
using WingsEmu.DTOs.Maps;
using WingsEmu.Game._enum;
using WingsEmu.Game._i18n;
using WingsEmu.Game.Algorithm;
using WingsEmu.Game.Algorithm.Events;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Characters.Events;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Groups;
using WingsEmu.Game.Items;
using WingsEmu.Game.Managers;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Mates;
using WingsEmu.Game.Mates.Events;
using WingsEmu.Game.TimeSpaces;
using WingsEmu.Packets.Enums;
using WingsEmu.Packets.Enums.Character;
using WingsEmu.Packets.Enums.Chat;
using WingsEmu.Plugins.BasicImplementations.Algorithms;
namespace WingsEmu.Plugins.BasicImplementations.Event.Algorithm;
public class GenerateExperienceEventHandler : IAsyncEventProcessor<GenerateExperienceEvent>
{
private readonly ICharacterAlgorithm _characterAlgorithm;
private readonly IGameLanguageService _gameLanguageService;
private readonly IServerManager _serverManager;
private readonly ITimeSpaceManager _timeSpaceManager;
public GenerateExperienceEventHandler(ICharacterAlgorithm characterAlgorithm, IServerManager serverManager, IGameLanguageService gameLanguageService, ITimeSpaceManager timeSpaceManager)
{
_characterAlgorithm = characterAlgorithm;
_serverManager = serverManager;
_gameLanguageService = gameLanguageService;
_timeSpaceManager = timeSpaceManager;
}
public async Task HandleAsync(GenerateExperienceEvent e, CancellationToken cancellation)
{
IPlayerEntity character = e.Character;
IMonsterEntity monsterEntity = e.MonsterEntity;
long? monsterOwnerId = e.MonsterOwnerId;
if (monsterEntity.IsAlive())
{
return;
}
if (monsterEntity.MapInstance.MapInstanceType == MapInstanceType.TimeSpaceInstance)
{
TimeSpaceParty timeSpace = _timeSpaceManager.GetTimeSpaceByMapInstanceId(monsterEntity.MapInstance.Id);
if (timeSpace == null)
{
return;
}
if (timeSpace.IsEasyMode)
{
return;
}
}
if (!character.IsInGroup())
{
if (!character.IsAlive())
{
return;
}
ExperienceInfo singleExpInfo = new()
{
BeforeCalculationPlayerLevel = character.Level
};
ExcessExperience getExtraExp = character.GetMoreExperience(_serverManager);
getExtraExp.ExperienceInfo = singleExpInfo;
bool decreaseXp = monsterOwnerId.HasValue && monsterOwnerId.Value != character.Id;
await ProcessExperience(character, getExtraExp, monsterEntity, decreaseXp, decreaseXp);
return;
}
bool decrease = true;
PlayerGroup group = character.GetGroup();
if (monsterOwnerId.HasValue)
{
if (group.Members.Any(entity => entity.Id == monsterOwnerId.Value))
{
decrease = false;
}
}
ExperienceInfo experienceInfo = new()
{
MembersLevel = (short)group.Members.Where(x => x.MapInstance?.Id == character.MapInstance?.Id).Sum(x => x.Level),
MembersOnMap = (byte)group.Members.Count(x => x.MapInstance?.Id == character.MapInstance?.Id)
};
foreach (IPlayerEntity member in character.GetGroup().Members)
{
if (!member.IsAlive())
{
continue;
}
if (member.MapInstance?.Id != character.MapInstance?.Id)
{
continue;
}
ExcessExperience memberExtraXp = member.GetMoreExperience(_serverManager);
experienceInfo.BeforeCalculationPlayerLevel = member.Level;
memberExtraXp.ExperienceInfo = experienceInfo;
if (monsterOwnerId.HasValue)
{
await ProcessExperience(member, memberExtraXp, monsterEntity, decrease, member.Id == character.Id);
continue;
}
await ProcessExperience(member, memberExtraXp, monsterEntity);
}
}
private async Task ProcessExperience(IPlayerEntity character, ExcessExperience getExtraExp, IMonsterEntity monsterEntity, bool decreaseXp = false, bool showMessage = false)
{
await ProcessExp(character, LevelType.Level, getExtraExp, monsterEntity, decreaseXp);
await ProcessExp(character, LevelType.JobLevel, getExtraExp, monsterEntity, decreaseXp);
await ProcessExp(character, LevelType.SpJobLevel, getExtraExp, monsterEntity, decreaseXp);
await ProcessExp(character, LevelType.Heroic, getExtraExp, monsterEntity, decreaseXp);
await ProcessExp(character, LevelType.LevelMate, getExtraExp, monsterEntity, decreaseXp);
await ProcessExp(character, LevelType.Fairy, getExtraExp, monsterEntity, decreaseXp);
if (decreaseXp && showMessage && character.MapInstance.MapInstanceType == MapInstanceType.BaseMapInstance &&
!character.MapInstance.HasMapFlag(MapFlags.ACT_4)) // yes, MapInstanceType, not MapFlag
{
character.Session.SendChatMessage(_gameLanguageService.GetLanguage(GameDialogKey.INTERACTION_CHATMESSAGE_XP_NOT_FIRST_HIT, character.Session.UserLanguage), ChatMessageColorType.Yellow);
}
}
private async Task ProcessExp(IPlayerEntity character, LevelType level, ExcessExperience getExtraExp, IMonsterEntity monsterEntity, bool decreaseXp = false)
{
long experience = 0;
int monsterXp = monsterEntity.Xp;
int monsterJobXp = monsterEntity.JobXp;
switch (level)
{
case LevelType.Level:
experience = (long)(Math.Floor(Math.Floor(Math.Ceiling(monsterXp * getExtraExp.Mates) * getExtraExp.Level) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo)) *
getExtraExp.LowLevel);
break;
case LevelType.JobLevel:
experience = (long)(Math.Floor(Math.Floor(monsterJobXp * getExtraExp.JobLevel) * GetPenalty(character, true, monsterEntity, getExtraExp.ExperienceInfo)) * getExtraExp.LowJob);
break;
case LevelType.SpJobLevel:
experience = (long)(Math.Floor(Math.Floor(monsterJobXp * getExtraExp.JobSpLevel) * GetPenalty(character, true, monsterEntity, getExtraExp.ExperienceInfo)) * getExtraExp.LowJobSp);
break;
case LevelType.Heroic:
experience = (long)(Math.Floor(
Math.Floor(Math.Ceiling(monsterXp * getExtraExp.Mates) * getExtraExp.HeroLevel) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo)) * getExtraExp.LowLevel);
break;
case LevelType.LevelMate:
if (!character.MateComponent.GetMates().Any(x => x.IsTeamMember))
{
break;
}
foreach (IMateEntity mate in character.MateComponent.TeamMembers())
{
if (!mate.IsAlive())
{
continue;
}
IMateEntity anotherMate = character.MateComponent.GetTeamMember(x => x.MateType != mate.MateType);
switch (mate.MateType)
{
case MateType.Partner:
if (anotherMate != null && anotherMate.IsAlive())
{
experience = (long)Math.Floor(Math.Floor(monsterXp * 0.2025 * getExtraExp.PartnerLevel) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo));
}
else
{
experience = (long)Math.Floor(Math.Floor(monsterXp * 0.1875 * getExtraExp.PartnerLevel) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo));
}
break;
case MateType.Pet:
if (anotherMate != null && anotherMate.IsAlive())
{
experience = (long)Math.Floor(Math.Floor(monsterXp * 0.0675 * getExtraExp.MatesLevel) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo));
}
else
{
experience = (long)Math.Floor(Math.Floor(monsterXp * 0.055 * getExtraExp.MatesLevel) * GetPenalty(character, false, monsterEntity, getExtraExp.ExperienceInfo));
}
break;
}
await character.Session.EmitEventAsync(new MateProcessExperienceEvent(mate, experience));
}
break;
}
if (decreaseXp)
{
experience /= 3;
}
if (monsterEntity.MapInstance.MapInstanceType == MapInstanceType.EventGameInstance && level != LevelType.Fairy)
{
experience /= 10;
}
if (experience <= 0 && level != LevelType.Fairy)
{
return;
}
await ProcessFinalExperience(character, level, experience, monsterEntity);
}
private async Task ProcessFinalExperience(IPlayerEntity character, LevelType level, long experience, IMonsterEntity monsterEntity)
{
if (monsterEntity.SummonerId != 0 && monsterEntity.SummonerType == VisualType.Player)
{
return;
}
long neededExperienceToLevel;
switch (level)
{
case LevelType.Level:
if (character.Level >= _serverManager.MaxLevel)
{
return;
}
neededExperienceToLevel = _characterAlgorithm.GetLevelXp(character.Level);
if (character.Level <= 20)
{
// every 10% generate full HP/MP
if ((int)(character.LevelXp / (neededExperienceToLevel / 10)) < (int)((character.LevelXp + experience) / (neededExperienceToLevel / 10)))
{
character.Hp = character.MaxHp;
character.Mp = character.MaxMp;
character.Session.RefreshStat();
character.Session.SendEffect(EffectType.ShinyStars);
}
}
character.LevelXp += experience;
if (character.LevelXp < neededExperienceToLevel)
{
break;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.Level
});
break;
case LevelType.JobLevel:
if (character.Class == ClassType.Adventurer)
{
if (character.JobLevel > 19)
{
break;
}
neededExperienceToLevel = _characterAlgorithm.GetJobXp(character.JobLevel, true);
character.JobLevelXp += experience;
if (character.JobLevelXp < neededExperienceToLevel)
{
break;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.JobLevel
});
break;
}
if (character.JobLevel >= _serverManager.MaxJobLevel)
{
break;
}
neededExperienceToLevel = _characterAlgorithm.GetJobXp(character.JobLevel);
character.JobLevelXp += experience;
if (character.JobLevelXp < neededExperienceToLevel)
{
break;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.JobLevel
});
break;
case LevelType.SpJobLevel:
if (character.Specialist == null || !character.UseSp)
{
break;
}
if (character.Specialist.SpLevel >= _serverManager.MaxSpLevel)
{
break;
}
neededExperienceToLevel = _characterAlgorithm.GetSpecialistJobXp(character.Specialist.SpLevel, character.Specialist.IsFunSpecialist());
character.Specialist.Xp += experience;
if (character.Specialist.Xp < neededExperienceToLevel)
{
break;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.SpJobLevel,
ItemVnum = character.Specialist.ItemVNum
});
break;
case LevelType.Heroic:
if (monsterEntity.MapInstance != null && (!monsterEntity.MapInstance.HasMapFlag(MapFlags.ACT_6_1) || monsterEntity.MapInstance.HasMapFlag(MapFlags.ACT_6_2)))
{
break;
}
if (monsterEntity.VesselMonster)
{
break;
}
if (character.HeroLevel == 0)
{
break;
}
if (character.HeroLevel >= _serverManager.MaxHeroLevel)
{
break;
}
neededExperienceToLevel = _characterAlgorithm.GetHeroLevelXp(character.HeroLevel);
character.HeroXp += experience;
if (character.HeroXp < neededExperienceToLevel)
{
break;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.Heroic
});
break;
case LevelType.Fairy:
GameItemInstance fairy = character.Fairy;
if (fairy == null)
{
return;
}
if (fairy.ElementRate + fairy.GameItem.ElementRate >= fairy.GameItem.MaxElementRate || character.Level > monsterEntity.Level + 15)
{
return;
}
fairy.Xp += (int)(_serverManager.FairyXpRate *
(1 + character.BCardComponent.GetAllBCardsInformation(BCardType.FairyXPIncrease, (byte)AdditionalTypes.FairyXPIncrease.IncreaseFairyXPPoints, character.Level).firstData * 0.01));
int fairyXp = _characterAlgorithm.GetFairyXp((short)(fairy.ElementRate + fairy.GameItem.ElementRate));
if (fairy.Xp < fairyXp)
{
return;
}
await character.Session.EmitEventAsync(new LevelUpEvent
{
LevelType = LevelType.Fairy,
ItemVnum = fairy.ItemVNum
});
break;
}
character.Session.RefreshLevel(_characterAlgorithm);
}
private double GetPenalty(IPlayerEntity character, bool isJob, IMonsterEntity monsterEntity, ExperienceInfo experienceInfo)
{
int actHeroPenalty = character.MapInstance.HasMapFlag(MapFlags.ACT_6_1) ? 5 : 0;
bool isInGroup = character.IsInGroup();
double levelPenalty = 1;
double jobPenalty = 1;
double monsterPenalty;
int difference = character.Level - monsterEntity.Level;
if (difference <= 5 + actHeroPenalty)
{
monsterPenalty = 1;
}
else if (difference == (6 + actHeroPenalty))
{
monsterPenalty = 0.9;
}
else if (difference == (7 + actHeroPenalty))
{
monsterPenalty = 0.7;
}
else if (difference == (8 + actHeroPenalty))
{
monsterPenalty = 0.5;
}
else if (difference == (9 + actHeroPenalty))
{
monsterPenalty = 0.3;
}
else
{
monsterPenalty = 0.1;
}
int groupMembers = experienceInfo.MembersOnMap ?? 1;
if (isInGroup && experienceInfo.MembersOnMap != null)
{
byte membersOnTheSameMap = experienceInfo.MembersOnMap.Value;
switch (membersOnTheSameMap)
{
case 2:
levelPenalty = 69.0 / 120.0;
jobPenalty = 75.0 / 120.0;
break;
case 3:
levelPenalty = 52.0 / 120.0;
jobPenalty = 60.0 / 120.0;
break;
}
}
double groupLevel = experienceInfo.MembersLevel ?? experienceInfo.BeforeCalculationPlayerLevel;
if (!isJob)
{
return levelPenalty / (groupLevel / (experienceInfo.BeforeCalculationPlayerLevel * groupMembers)) * monsterPenalty;
}
if (monsterEntity.Level >= 70)
{
monsterPenalty = 1;
}
return jobPenalty / (groupLevel / (experienceInfo.BeforeCalculationPlayerLevel * groupMembers)) * monsterPenalty;
}
}
public class ExcessExperience
{
public ExcessExperience(double level, double jobLevel, double jobSpLevel, double heroLevel, double matesLevel, double partnerLevel, double lowLevel, double lowJob, double lowJobSp, double mates)
{
Level = level;
JobLevel = jobLevel;
JobSpLevel = jobSpLevel;
HeroLevel = heroLevel;
MatesLevel = matesLevel;
PartnerLevel = partnerLevel;
LowLevel = lowLevel;
LowJob = lowJob;
LowJobSp = lowJobSp;
Mates = mates;
}
public double Level { get; }
public double JobLevel { get; }
public double JobSpLevel { get; }
public double HeroLevel { get; }
public double MatesLevel { get; }
public double PartnerLevel { get; }
public double LowLevel { get; }
public double LowJob { get; }
public double LowJobSp { get; }
public double Mates { get; }
public ExperienceInfo ExperienceInfo { get; set; }
}
public struct ExperienceInfo
{
public byte BeforeCalculationPlayerLevel { get; set; }
public short? MembersLevel { get; init; }
public byte? MembersOnMap { get; init; }
}