using Ananta.Server.ClientData.Client4229938; using Ananta.Server.Configuration; using Ananta.Server.RpcTypes.Client4229938; using Auto = Ananta.Server.RpcTypes.Client4229938.Auto; using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game; namespace Ananta.Server.Protocol.Client4229938; /// /// Builds the large build-4229938 RPC payloads directly from human config + extracted client JSON. /// Nothing here reads generated .bin files: every packet is a normal RpcTypes object serialized by UxSerializer. /// internal static class RuntimePayloadFactory { private static PrivateServerConfig Config => PrivateServerConfigStore.Current; internal static Auto.PlayerClientInfo MinimalPlayerInfo4229938() { // Keep only identity plus the actor-facing roster required to materialize the controlled // spirit. Economy/social/apps/tasks and the unrelated account armory stay absent. var root = new Auto.PlayerClientInfo(); root.InfoLogin.Aid = Config.Client.Aid; root.InfoLogin.Pid = Config.Player.Pid; root.InfoLogin.AccountId = Config.Player.AccountId; root.InfoLogin.Name = Config.Player.DisplayName; root.InfoLogin.Level = 1; root.InfoLogin.Sex = 1; root.InfoLogin.UseSystemName = false; root.InfoLogin.UniverseId = Config.Player.LoginUniverseId; // World-entry cannot advertise a concrete WeaponTemplateId in SyncEnterScene while the // login roster contains zero weapon instances. 4229938 preloads the local actor from the // PlayerInfo spirit/armory stores before AskLoadSceneCompleted; an empty wheel makes that // actor profile internally inconsistent and leaves the client waiting on presentation data. // Keep only the weapon instances actually referenced by the 32 owned spirit wheels. var spirits = ClientConfigRepository.Characters().Select(MinimalActorCharacterSpirit4229938).ToList(); root.InfoSpirit.Spirits = spirits; root.InfoSpirit.ActiveSpirit = Config.Player.InitialSpiritTemplateId; root.InfoSpirit.InfoPokemon.FastFightSquad = [Config.Player.InitialUnitId]; // Unlock all 670 weapons across the entire armory for all characters root.InfoSpirit.InfoArmory.Weapons = CombatCodec.ArmoryWeapons().ToList(); // Unlock all fight styles and martial arts foreach (var styleId in CombatCatalogRepository.UnlockedStyleIds) root.InfoSpirit.InfoFightStyle.FightStyleIsUnLocked[styleId] = true; // Install all phone apps (Camera, Time, Settings, etc.) root.InfoSpirit.InstalledApps = ClientConfigRepository.InstalledPhoneAppIds().ToList(); // Unlock all phone customizations / styles (wallpapers, decorations, pendants) root.InfoSpirit.AvailableSkinParts = Enumerable.Range(1, 200).Select(i => (uint)i).ToList(); root.InfoMinor.Level = 1; root.InfoMinor.MatchInfo.DeviceLevel = 1; root.InfoMinor.MatchInfo.CurLinkDeviceLevel = 1; // Provide the vehicle fleet so the client-side DriveManager recognises available vehicles. if (Config.Gameplay.Vehicles.Enabled && Config.Gameplay.Vehicles.FleetIds.Length > 0) { root.InfoMinor.VehicleInfo = new Auto.PlayerVehicleInfo { UnlockedVehicles = Config.Gameplay.Vehicles.FleetIds.Select(fleetId => new Auto.PlayerVehicleDetail { Id = fleetId, UnlockTime = 1, SuitId = 0, Parts = [], }).ToList(), RequisitionVehicleCount = Config.Gameplay.Vehicles.FleetIds.Length, ParkingVehicleId = 0, }; } // Seed phone contacts so the quick-call summon contacts exist without progression. // The client matches contacts to PhoneContactConfig by phone number: // 142857 = "Call vehicle" (owned-car list -> AskSummonVehicle), // 206337 = "Express Depot" (milk/taxi car). var summonContacts = new (string Remark, string Number)[] { (string.Empty, "142857"), (string.Empty, "206337"), }; foreach (var character in ClientConfigRepository.Characters()) { root.InfoMinor.PlayerPhoneInfo.SpiritPhoneInfos[character.TemplateId] = new Auto.PhoneInfos { ContactList = summonContacts.Select(c => new Auto.PhoneContact { Remark = c.Remark, PhoneNumber = c.Number, }).ToList(), ContactGroupList = [], CallRecordList = [], ContactOutgoingCallTimesDict = new Dictionary(), }; } // Event-condition progress so the quick-call contacts count as unlocked. // Module 3 = UX.Game.EventConditionImplModule.PhoneContact (IL2CPP metadata // declaration order); finished ids 1/2 = PhoneContactUnlockConfig rows for // contacts 111111111 (Call vehicle) / 111111112 (Express Depot). // Without them the client plays LockDialogId (busy) instead of the summon dialog. var phoneProgress = new Auto.ModuleEventProgressInfoBySpirit { EventProgressInfoDict = new Dictionary(), FinishedTemplateIdList = [1, 2], }; var phoneModule = new Auto.ModuleEventProgressInfo { ProgressInfoDict = new Dictionary { [0] = phoneProgress, }, }; foreach (var character in ClientConfigRepository.Characters()) phoneModule.ProgressInfoDict[character.TemplateId] = phoneProgress; root.InfoMinor.ModuleEventProgressInfoDict[3] = phoneModule; // The v7 guide only constrains ActiveSpirit/actor identity; it does not require the rest of // PlayerInfo to be zero. Keep build-local SystemUnlock metadata so the stock 4229938 HUD, // map and input UI do not treat the account as a pre-tutorial profile. No semantic handlers // are re-enabled by publishing these client-owned unlock ids. root.InfoMinor.PlayerInfoGuide.UnlockSystems = ClientConfigRepository.UnlockSystemIds().ToList(); root.InfoMinor.PlayerFashionsInfo = MinimalPlayerFashions4229938(); // Unlock all character poses and interaction actions for (uint i = 1; i <= 200; i++) { root.InfoMinor.PlayerInteractionActionInfo.UnlockActionItemDict[i] = new Auto.PlayerInteractionActionItem { CfgId = i, UnlockTime = 1, ShowRedPoint = false, }; } return root; } internal static Auto.EnterSceneInfo EnterScene(uint switchShowId) { var placement = ClientConfigRepository.EntryPlacement(switchShowId); return EnterScene( Config.World.RaidId, Config.World.SceneInstanceId, Config.World.UniverseId, Config.Player.InitialUnitId, Config.Player.InitialSpiritTemplateId, placement.Position, placement.Facing, switchShowId, isSwitchSpiritShow: true); } internal static Auto.EnterSceneInfo EnterScene( uint raidId, ulong instanceId, uint universeId, ulong unitId, uint templateId, Vec3 position, float facing, uint switchShowId = 0, bool isSwitchSpiritShow = false, uint sectorControlId = 0) => new() { PlayerSessionId = Config.Player.Pid, RaidId = raidId, InstanceId = instanceId, Position = Vector(position), Facing = facing, SceneSpoonNames = [], SceneSpoonMd5s = [], Spirits = [ new Auto.SpiritInitData { Id = unitId, TemplateId = templateId, IsActive = true, // Proven v7 single-CreateHero entry contract: initial actor identity only. // Weapon/fashion/runtime presentation is hydrated only after world-entry finalization. WeaponTemplateId = 0, WeaponSkinId = 0, } ], GridInfo = new Auto.ServerSimpleGridInfo { MinX = 0, MinZ = 0, MaxX = 300000, MaxZ = 300000, }, MatchGameId = 0, SwitchShowId = switchShowId, IsSwitchSpiritShow = isSwitchSpiritShow, UniverseId = universeId, SectorControlId = sectorControlId, LoadingType = new Auto.LoadingTypeInfo { Type = 0, Members = [], Ripple = false, }, LinkSimpleInfo = new Auto.LinkSimpleInfo { Id = 0, Mode = 0, DeviceLevel = 1, Tag = 0, PublicEventId = 0, LastPublicEventTime = 0, HostPid = 0, }, DataLayerIndices = [], Seamless = false, TombIslandIds = [], }; internal static Auto.RaidBattleUnitSpirit ExistingUnitProjection(uint switchShowId) { var placement = ClientConfigRepository.EntryPlacement(switchShowId); return ExistingUnitProjection( Config.Player.InitialUnitId, Config.Player.InitialSpiritTemplateId, placement.Position, placement.Facing); } internal static Auto.RaidBattleUnitSpirit ExistingUnitProjection( ulong unitId, uint templateId, Vec3 position, float facing) { // Initial world-entry uses the same BaseUnit created by native CreateHero. ManagedPid is // presentation/remote-management metadata on this AOI projection and must stay zero; the // actual local authority is established by SyncManagedLogicAgent(unitId, playerPid, 0). var projection = CharacterUnitProjection( unitId, templateId, position, facing, ClientConfigRepository.DefaultFashionIds(templateId)); projection.ManagedPid = 0; return projection; } internal static Auto.RaidBattleUnitSpirit CharacterUnitProjection( uint templateId, Vec3 position, float facing) { var character = ClientConfigRepository.Characters().FirstOrDefault(x => x.TemplateId == templateId) ?? throw new InvalidOperationException($"Character {templateId} is not present in FightSpiritConfig roster."); return CharacterUnitProjection( character.UnitId, character.TemplateId, position, facing, ClientConfigRepository.DefaultFashionIds(templateId)); } internal static GameMethods.SyncSetSpiritFashions CharacterFashions(uint templateId) { var wear = WearFashionsForGameMethods(ClientConfigRepository.DefaultFashionIds(templateId)); return new GameMethods.SyncSetSpiritFashions { spiritId = templateId, source = 0, spiritWearFashionsInfo = wear, }; } internal static GameMethods.AskAllSpiritPanelDataResult MinimalAllSpiritPanelData4229938() => new() { spirits = ClientConfigRepository.Characters().Select(character => new Auto.SpiritPanelData { FightSpiritId = character.TemplateId, MaxHp = 1f, Dam = 0f, DefDeduct = 0f, }).ToList() }; private static Auto.RaidBattleUnitSpirit CharacterUnitProjection( ulong unitId, uint templateId, Vec3 position, float facing, IReadOnlyList fashionIds) { return new Auto.RaidBattleUnitSpirit { SpiritWearFashionsInfo = new Auto.OtherPlayerSpiritWearFashionsInfo { WearFashionColoringInfoDict = new Dictionary(), WearFashionInfoList = fashionIds.Select(id => new Auto.WearFashionInfo { FashionId = id }).ToList(), WearFashionEditInfoList = [], HiddenParts = 0, EditedHiddenParts = 0, }, Begging = false, IsBot = false, Id = unitId, TemplateId = templateId, Position = Vector(position), FacingDirection = facing, OwnerId = Config.Player.Pid, // Build 4229938 uses ManagedPid while constructing the BaseUnit to decide whether // the projected spirit belongs to the local player. Leaving this at zero creates // switch targets as IsMe=false and the stock handoff never completes. ManagedPid = Config.Player.Pid, MoveId = 0, NavTags = 0, GroundData = new Auto.MoveActionGroundData { MoveGroundType = 0, MoveGroundId = 0, MetaInfo = 0, LocalPos = new Auto.UXVector3(), }, }; } private static Auto.SpiritInfo MinimalActorCharacterSpirit4229938(CharacterCatalogEntry character) { var loadout = CombatCatalogRepository.Loadout(character.TemplateId); var spirit = new Auto.SpiritInfo { Id = character.UnitId, TemplateId = character.TemplateId, PossessTime = 1, HpRate = 1f, EverSwitched = character.TemplateId != Config.Player.InitialSpiritTemplateId, Blocked = false, LastUsedWeaponInstanceId = loadout.DefaultWeapon.InstanceId, WeaponSlots = loadout.Slots .Select(weapon => weapon is null ? null! : WeaponData(weapon)) .ToList(), }; // Fight-style metadata is required to interpret the equipped weapon's authored action set. // Do not seed the old server's synthetic SpiritAbilities/urban job/combat graph here. foreach (var (typeId, styleId) in loadout.StyleByType) spirit.SpiritFightStyle.FightStyleInfo[typeId] = styleId; return spirit; } internal const uint InitialAmmoReserve4229938 = 9999u; internal static Auto.PlayerPackItem AmmoPackItem(uint templateId, uint count = InitialAmmoReserve4229938) => new() { UniqueId = 910000000000UL + templateId, TemplateId = templateId, Count = count, IsNew = false, ExpiryTime = 0, RemindState = 0, Quality = 0, Tags = 0, IsBind = false, Components = null, CDFinishTime = 0, }; internal static Auto.WeaponData WeaponData(CombatWeaponDefinition weapon) => new() { TemplateId = weapon.TemplateId, Durability = weapon.InitialClientDurability, InstanceId = weapon.InstanceId, EventId = 0, ReceivedTimeStamp = 0, OperatorFlags = 0, SpecialLabel = null, SceneItemHp = 1f, StackCount = 1, FightStyleId = weapon.Style.Id, MagazineAmmo = weapon.InitialMagazineAmmo, BulletDatas = new Auto.WeaponBulletDatas { BulletId = weapon.BulletId }, BindPid = Config.Player.Pid, IsPlayerLocked = false, }; private static Auto.PlayerFashionsInfo MinimalPlayerFashions4229938() { var result = new Auto.PlayerFashionsInfo { DefaultSpiritIsInitDefaultFashion = true, ColoringCollectionScore = 100, }; foreach (var fashionId in ClientConfigRepository.SelectableFashionIds()) { result.FashionInfoDict[fashionId] = new Auto.FashionInfo { FashionId = fashionId, ExpiredTime = 0, GainTime = 1, Status = 1, ApplyColoringSchemeId = 0, UnlockColoringSlotCount = 10, ColoringSchemeTopNMaxCollectionScore = 100, OwnedCount = 1, }; } foreach (var character in ClientConfigRepository.Characters()) { var defaultIds = ClientConfigRepository.DefaultFashionIds(character.TemplateId); result.SpiritFashionsInfoDict[character.TemplateId] = new Auto.SpiritFashionsInfo { SpiritId = character.TemplateId, SpiritWearFashionsInfo = WearFashions(defaultIds), SpiritPrevWearFashionsInfo = null, FirstGainSuitIdList = [], ActiveClientTryWearSource = 0, UnlockSuitSlotCount = 10, }; } return result; } private static Auto.SpiritWearFashionsInfo WearFashions(IReadOnlyList ids) => new() { FunctionSuitId = 0, WearSourceInfo = new Auto.WearSourceInfo { Source = 0, SourceId = 0 }, IsTryWear = false, WearFashionInfoList = ids.Select(id => new Auto.WearFashionInfo { FashionId = id }).ToList(), WearFashionEditInfoList = [], HiddenParts = 0, EditedHiddenParts = 0, }; internal static GameMethods.SpiritWearFashionsInfo WearFashionsForGameMethods(IReadOnlyList ids) => new() { FunctionSuitId = 0, WearSourceInfo = new GameMethods.WearSourceInfo { Source = 0, SourceId = 0 }, IsTryWear = false, WearFashionInfoList = ids.Select(id => new GameMethods.WearFashionInfo { FashionId = id }).ToList(), WearFashionEditInfoList = [], HiddenParts = 0, EditedHiddenParts = 0, }; private static Auto.UXVector3 Vector(Vec3 value) => new() { X = value.X, Y = value.Y, Z = value.Z }; }