// WingsEmu // // Developed by NosWings Team using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FamilyServer.Logs; using FamilyServer.Managers; using Foundatio.AsyncEx; using PhoenixLib.DAL.Redis.Locks; using PhoenixLib.ServiceBus; using Plugin.FamilyImpl.Messages; using WingsAPI.Communication; using WingsAPI.Communication.Families; using WingsAPI.Data.Families; using WingsAPI.Packets.Enums.Families; using WingsEmu.Game.Families; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Families; namespace FamilyServer.Services { public class FamilyService : IFamilyService { private readonly IExpirableLockService _expirableLockService; private readonly FamilyLogManager _familyLogManager; private readonly FamilyManager _familyManager; private readonly FamilyMembershipManager _familyMembershipManager; private readonly IMessagePublisher _messagePublisherChangedFaction; private readonly IMessagePublisher _messagePublisherCreated; private readonly IMessagePublisher _messagePublisherDisbandedFamily; private readonly IMessagePublisher _messagePublisherFamilyUpdate; private readonly IMessagePublisher _messagePublisherMemberAdded; private readonly IMessagePublisher _messagePublisherMemberRemoved; private readonly IMessagePublisher _messagePublisherMemberUpdate; private readonly AsyncReaderWriterLock _readerWriter = new(); public FamilyService(FamilyManager familyManager, IMessagePublisher messagePublisherMemberAdded, IMessagePublisher messagePublisherCreated, IMessagePublisher messagePublisherMemberUpdate, IMessagePublisher messagePublisherMemberRemoved, FamilyLogManager familyLogManager, FamilyMembershipManager familyMembershipManager, IMessagePublisher messagePublisherFamilyUpdate, IMessagePublisher messagePublisherChangedFaction, IMessagePublisher messagePublisherDisbandedFamily, IExpirableLockService expirableLockService) { _familyManager = familyManager; _messagePublisherMemberAdded = messagePublisherMemberAdded; _messagePublisherCreated = messagePublisherCreated; _messagePublisherMemberUpdate = messagePublisherMemberUpdate; _messagePublisherMemberRemoved = messagePublisherMemberRemoved; _familyLogManager = familyLogManager; _familyMembershipManager = familyMembershipManager; _messagePublisherFamilyUpdate = messagePublisherFamilyUpdate; _messagePublisherChangedFaction = messagePublisherChangedFaction; _messagePublisherDisbandedFamily = messagePublisherDisbandedFamily; _expirableLockService = expirableLockService; } public async ValueTask CreateFamilyAsync(FamilyCreateRequest req) { using IDisposable writerLock = await _readerWriter.WriterLockAsync(); { if (await _familyManager.GetFamilyByNameAsync(req.Name) != null) { return new FamilyCreateResponse { Status = FamilyCreateResponseType.NAME_ALREADY_TAKEN }; } FamilyDTO familyDto = await _familyManager.AddFamilyAsync(new FamilyDTO { Name = req.Name, Level = req.Level, Faction = req.Faction, Upgrades = new FamilyUpgradeDto(), Missions = CreateBasicFamilyMissions(), Achievements = CreateBasicAchievements() }); foreach (FamilyMembershipDto member in req.Members) { member.FamilyId = familyDto.Id; } await _familyMembershipManager.AddFamilyMembershipsAsync(req.Members); await _messagePublisherCreated.PublishAsync(new FamilyCreatedMessage { FamilyName = req.Name }); return new FamilyCreateResponse { Status = FamilyCreateResponseType.SUCCESS, Family = familyDto }; } } public async ValueTask DisbandFamilyAsync(FamilyDisbandRequest request) { IReadOnlyCollection memberships = await _familyMembershipManager.GetFamilyMembershipsByFamilyIdAsync(request.FamilyId); if (memberships == null) { return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } foreach (FamilyMembershipDto membership in memberships) { await _familyMembershipManager.RemoveFamilyMembershipByCharAndFamIdAsync(membership); } await _familyManager.RemoveFamilyByIdAsync(request.FamilyId); await _messagePublisherDisbandedFamily.PublishAsync(new FamilyDisbandMessage { FamilyId = request.FamilyId }); return new BasicRpcResponse { ResponseType = RpcResponseType.SUCCESS }; } public async ValueTask ChangeAuthorityByIdAsync(FamilyChangeAuthorityRequest request) { if (request.FamilyMembers.Count <= 0) { return new EmptyResponse(); } var list = new List(); foreach (FamilyChangeContainer container in request.FamilyMembers) { FamilyMembershipDto membership = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(container.CharacterId); if (membership == null) { continue; } membership.Authority = container.RequestedFamilyAuthority; list.Add(membership); } await _familyMembershipManager.SaveFamilyMembershipsAsync(list); await _messagePublisherMemberUpdate.PublishAsync(new FamilyMemberUpdateMessage { UpdatedMembers = list, ChangedInfoMemberUpdate = ChangedInfoMemberUpdate.Authority }); return new EmptyResponse(); } public async ValueTask ChangeFactionByIdAsync(FamilyChangeFactionRequest request) { FactionType newFaction = request.NewFaction; FamilyDTO familyDto = await _familyManager.GetFamilyByFamilyIdAsync(request.FamilyId); if (familyDto == null) { return new FamilyChangeFactionResponse { Status = FamilyChangeFactionResponseType.GENERIC_ERROR }; } if (familyDto.Faction == (byte)newFaction) { return new FamilyChangeFactionResponse { Status = FamilyChangeFactionResponseType.ALREADY_THAT_FACTION }; } if (!await _expirableLockService.TryAddTemporaryLockAsync($"game:locks:family:{familyDto.Id}:change-faction", DateTime.UtcNow.Date.AddDays(1))) { return new FamilyChangeFactionResponse { Status = FamilyChangeFactionResponseType.UNDER_COOLDOWN }; } familyDto.Faction = (byte)newFaction; await _familyManager.AddFamilyAsync(familyDto); await _messagePublisherChangedFaction.PublishAsync(new FamilyChangeFactionMessage { FamilyId = request.FamilyId, NewFaction = newFaction }); return new FamilyChangeFactionResponse { Status = FamilyChangeFactionResponseType.SUCCESS }; } public async ValueTask ChangeTitleByIdAsync(FamilyChangeTitleRequest request) { FamilyMembershipDto membership = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(request.CharacterId); if (membership == null) { return new EmptyResponse(); } membership.Title = request.RequestedFamilyTitle; await _familyMembershipManager.SaveFamilyMembershipAsync(membership); await _messagePublisherMemberUpdate.PublishAsync(new FamilyMemberUpdateMessage { UpdatedMembers = new List { membership }, ChangedInfoMemberUpdate = ChangedInfoMemberUpdate.Authority }); return new EmptyResponse(); } public async ValueTask TryAddFamilyUpgrade(FamilyUpgradeRequest request) { FamilyDTO family = await _familyManager.GetFamilyByFamilyIdAsync(request.FamilyId); if (family == null) { return new FamilyUpgradeResponse { ResponseType = FamilyUpgradeAddResponseType.GENERIC_SERVER_ERROR }; } FamilyUpgradeType familyUpgrade = request.FamilyUpgradeType; short value = request.Value; family.Upgrades ??= new FamilyUpgradeDto(); family.Upgrades.UpgradesBought ??= new HashSet(); if (family.Upgrades.UpgradesBought.Contains(request.UpgradeId)) { return new FamilyUpgradeResponse { ResponseType = FamilyUpgradeAddResponseType.UPGRADE_ALREADY_UNLOCKED }; } family.Upgrades.UpgradesBought.Add(request.UpgradeId); family.Upgrades.UpgradeValues ??= new Dictionary(); family.Upgrades.UpgradeValues[familyUpgrade] = value; FamilyDTO familyDto = await _familyManager.SaveFamilyAsync(family); await _messagePublisherFamilyUpdate.PublishAsync(new FamilyUpdateMessage { ChangedInfoFamilyUpdate = ChangedInfoFamilyUpdate.Upgrades, Families = new[] { familyDto } }); return new FamilyUpgradeResponse { ResponseType = FamilyUpgradeAddResponseType.SUCCESS }; } public async ValueTask AddMemberToFamilyAsync(FamilyAddMemberRequest request) { await _familyMembershipManager.AddFamilyMembershipAsync(request.Member); await _messagePublisherMemberAdded.PublishAsync(new FamilyMemberAddedMessage { AddedMember = request.Member, Nickname = request.Nickname, SenderId = request.SenderId }); return new EmptyResponse(); } public async ValueTask MemberDisconnectedAsync(FamilyMemberDisconnectedRequest request) { FamilyMembershipDto member = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(request.CharacterId); if (member == null) { return new EmptyResponse(); } member.LastOnlineDate = request.DisconnectionTime; await _familyMembershipManager.SaveFamilyMembershipAsync(member); return new EmptyResponse(); } public async ValueTask RemoveMemberToFamilyAsync(FamilyRemoveMemberRequest request) { FamilyMembershipDto membership = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(request.CharacterId); if (membership == null || membership.FamilyId != request.FamilyId) { return new EmptyResponse(); } await _familyMembershipManager.RemoveFamilyMembershipByCharAndFamIdAsync(membership); await _messagePublisherMemberRemoved.PublishAsync(new FamilyMemberRemovedMessage { CharacterId = request.CharacterId, FamilyId = request.FamilyId }); return new EmptyResponse(); } public async ValueTask RemoveMemberByCharIdAsync(FamilyRemoveMemberByCharIdRequest request) { FamilyMembershipDto membership = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(request.CharacterId); if (membership == null) { return new BasicRpcResponse { ResponseType = RpcResponseType.GENERIC_SERVER_ERROR }; } await _familyMembershipManager.RemoveFamilyMembershipByCharAndFamIdAsync(membership); await _messagePublisherMemberRemoved.PublishAsync(new FamilyMemberRemovedMessage { CharacterId = request.CharacterId, FamilyId = membership.FamilyId }); return new BasicRpcResponse { ResponseType = RpcResponseType.SUCCESS }; } public async ValueTask GetFamilyByIdAsync(FamilyIdRequest req) { FamilyDTO family = await _familyManager.GetFamilyByFamilyIdAsync(req.FamilyId); List members = await _familyMembershipManager.GetFamilyMembershipsByFamilyIdAsync(req.FamilyId); List logs = await _familyLogManager.GetFamilyLogsByFamilyId(req.FamilyId); await RemoveLevelAchievements(family); DateTime resetDate = DateTime.UtcNow.Date; if (family.Missions?.Missions != null) { foreach (FamilyMissionDto mission in family.Missions.Missions.Values) { // progressType => 1 = Completed // progressType => 2 = InProgress bool hasCompleted = mission.CompletionDate.HasValue; if (!hasCompleted) { continue; } if (mission.CompletionDate.Value.Date >= resetDate) { continue; } mission.CompletionDate = null; mission.Count = 0; } } return new FamilyIdResponse { Family = family, Members = members, Logs = logs }; } public async ValueTask GetFamilyMembersByFamilyId(FamilyIdRequest req) { List members = await _familyMembershipManager.GetFamilyMembershipsByFamilyIdAsync(req.FamilyId); return new FamilyListMembersResponse { Members = members }; } public async ValueTask GetMembershipByCharacterIdAsync(MembershipRequest req) { FamilyMembershipDto membership = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(req.CharacterId); return new MembershipResponse { Membership = membership }; } public async ValueTask CanPerformTodayMessageAsync(MembershipTodayRequest req) { long characterId = req.CharacterId; string characterName = req.CharacterName; FamilyMembershipDto familyMember = await _familyMembershipManager.GetFamilyMembershipByCharacterIdAsync(characterId); if (familyMember == null) { return new MembershipTodayResponse { CanPerformAction = false }; } if (!await _expirableLockService.TryAddTemporaryLockAsync($"game:locks:family:{familyMember.FamilyId}:{characterId}:quote-of-the-day", DateTime.UtcNow.Date.AddDays(1))) { return new MembershipTodayResponse { CanPerformAction = false }; } return new MembershipTodayResponse { CanPerformAction = true }; } public async ValueTask UpdateFamilySettingsAsync(FamilySettingsRequest request) { FamilyDTO familyDto = await _familyManager.GetFamilyByFamilyIdAsync(request.FamilyId); if (familyDto == null) { return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } byte value = request.Value; switch (request.Authority) { case FamilyAuthority.Keeper: switch (request.FamilyActionType) { case FamilyActionType.SendInvite: familyDto.AssistantCanInvite = value == 1; break; case FamilyActionType.Notice: familyDto.AssistantCanNotice = value == 1; break; case FamilyActionType.FamilyShout: familyDto.AssistantCanShout = value == 1; break; case FamilyActionType.FamilyWarehouseHistory: familyDto.AssistantCanGetHistory = value == 1; break; case FamilyActionType.FamilyWarehouse: if (!Enum.TryParse(value.ToString(), out FamilyWarehouseAuthorityType authorityType)) { return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } familyDto.AssistantWarehouseAuthorityType = authorityType; break; default: return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } break; case FamilyAuthority.Member: switch (request.FamilyActionType) { case FamilyActionType.SendInvite: // Member History familyDto.MemberCanGetHistory = value == 1; break; case FamilyActionType.Notice: // Member Warehouse Authority if (!Enum.TryParse(value.ToString(), out FamilyWarehouseAuthorityType authorityType)) { return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } familyDto.MemberWarehouseAuthorityType = authorityType; break; default: return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } break; default: return new BasicRpcResponse { ResponseType = RpcResponseType.UNKNOWN_ERROR }; } FamilyDTO family = await _familyManager.SaveFamilyAsync(familyDto); await _messagePublisherFamilyUpdate.PublishAsync(new FamilyUpdateMessage { ChangedInfoFamilyUpdate = ChangedInfoFamilyUpdate.Settings, Families = new[] { family } }); return new BasicRpcResponse { ResponseType = RpcResponseType.SUCCESS }; } public async ValueTask ResetFamilyMissions() { DateTime resetDate = DateTime.UtcNow.Date; List families = _familyManager.GetFamiliesInMemory(); HashSet toUpdate = new(); foreach (FamilyDTO family in families.Where(x => x.Missions?.Missions != null)) { foreach (FamilyMissionDto mission in family.Missions.Missions.Values) { // progressType => 1 = Completed // progressType => 2 = InProgress bool hasCompleted = mission.CompletionDate.HasValue; if (!hasCompleted) { continue; } if (mission.CompletionDate.Value.Date >= resetDate) { continue; } mission.CompletionDate = null; mission.Count = 0; if (toUpdate.Contains(family)) { continue; } toUpdate.Add(family); } } await _messagePublisherFamilyUpdate.PublishAsync(new FamilyUpdateMessage { Families = toUpdate, ChangedInfoFamilyUpdate = ChangedInfoFamilyUpdate.AchievementsAndMissions }); return new EmptyResponse(); } private FamilyAchievementsDto CreateBasicAchievements() { FamilyAchievementsDto newAchievements = new() { Achievements = new Dictionary(), Progress = new Dictionary() }; Dictionary progress = newAchievements.Progress; progress[(short)FamilyAchievementsVnum.FAMILY_LEVEL_2_UNLOCKED] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.FAMILY_LEVEL_2_UNLOCKED }; progress[(short)FamilyAchievementsVnum.ENTER_20_QUOTES_OF_THE_DAY] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.ENTER_20_QUOTES_OF_THE_DAY }; progress[(short)FamilyAchievementsVnum.DEFEAT_ANY_ACT4_DUNGEON_10_TIMES] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.DEFEAT_ANY_ACT4_DUNGEON_10_TIMES }; progress[(short)FamilyAchievementsVnum.DEFEAT_MORCOS_ACT4_DUNGEON_1_TIME] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.DEFEAT_MORCOS_ACT4_DUNGEON_1_TIME }; progress[(short)FamilyAchievementsVnum.DEFEAT_HATUS_ACT4_DUNGEON_1_TIME] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.DEFEAT_HATUS_ACT4_DUNGEON_1_TIME }; progress[(short)FamilyAchievementsVnum.DEFEAT_CALVINAS_ACT4_DUNGEON_1_TIME] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.DEFEAT_CALVINAS_ACT4_DUNGEON_1_TIME }; progress[(short)FamilyAchievementsVnum.DEFEAT_BERIOS_ACT4_DUNGEON_1_TIME] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.DEFEAT_BERIOS_ACT4_DUNGEON_1_TIME }; progress[(short)FamilyAchievementsVnum.COMPLETE_10_RAINBOW_BATTLE] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.COMPLETE_10_RAINBOW_BATTLE }; progress[(short)FamilyAchievementsVnum.WIN_5_RAINBOW_BATTLE] = new FamilyAchievementProgressDto { Id = (short)FamilyAchievementsVnum.WIN_5_RAINBOW_BATTLE }; return newAchievements; } private FamilyMissionsDto CreateBasicFamilyMissions() { FamilyMissionsDto newFamilyMissions = new() { Missions = new Dictionary() }; Dictionary missions = newFamilyMissions.Missions; missions[(int)FamilyMissionVnums.DAILY_DEFEAT_5_CUBY_RAID] = new FamilyMissionDto { Id = (int)FamilyMissionVnums.DAILY_DEFEAT_5_CUBY_RAID }; missions[(int)FamilyMissionVnums.DAILY_DEFEAT_5_GINSENG_RAID] = new FamilyMissionDto { Id = (int)FamilyMissionVnums.DAILY_DEFEAT_5_GINSENG_RAID }; missions[(int)FamilyMissionVnums.DAILY_DEFEAT_5_CASTRA_RAID] = new FamilyMissionDto { Id = (int)FamilyMissionVnums.DAILY_DEFEAT_5_CASTRA_RAID }; missions[(int)FamilyMissionVnums.DAILY_DEFEAT_5_GIANT_SPIDER_RAID] = new FamilyMissionDto { Id = (int)FamilyMissionVnums.DAILY_DEFEAT_5_GIANT_SPIDER_RAID }; missions[(int)FamilyMissionVnums.DAILY_DEFEAT_10_INSTANT_BATTLES] = new FamilyMissionDto { Id = (int)FamilyMissionVnums.DAILY_DEFEAT_10_INSTANT_BATTLES }; return newFamilyMissions; } private async Task RemoveLevelAchievements(FamilyDTO family) { if (family == null) { return; } if (family.Level <= 1) { return; } if (family.Achievements?.Achievements == null) { return; } byte familyLevel = family.Level; short secondLevelFamily = (short)FamilyAchievementsVnum.FAMILY_LEVEL_2_UNLOCKED - 1; bool isFirst = true; bool addNewProgress = true; var toRemove = new List(); for (int i = 0; i < 20; i++) { if (isFirst) { isFirst = false; } else { secondLevelFamily++; } if (!family.Achievements.Achievements.ContainsKey(secondLevelFamily)) { continue; } if (familyLevel > i) { continue; } if (addNewProgress) { addNewProgress = false; family.Achievements.Progress[secondLevelFamily] = new FamilyAchievementProgressDto { Id = secondLevelFamily }; } toRemove.Add(secondLevelFamily); } foreach (int remove in toRemove) { family.Achievements.Achievements.Remove(remove); } await _familyManager.SaveFamilyAsync(family); } } }