public static void CharacterCreate(ClientMessage message, Session session) { string account = message.Payload.ReadString16L(); if (account != session.Account) { return; } uint id = GuidManager.NewPlayerGuid().Full; CharacterCreateEx(message, session, id); }
private static void CharacterCreateEx(ClientMessage message, Session session) { var characterCreateInfo = new CharacterCreateInfo(); characterCreateInfo.Unpack(message.Payload); // TODO: Check for Banned Name Here //DatabaseManager.Shard.IsCharacterNameBanned(characterCreateInfo.Name, isBanned => //{ // if (!isBanned) // { // SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned); // return; // } //}); // Disable OlthoiPlay characters for now. They're not implemented yet. // FIXME: Restore OlthoiPlay characters when properly handled. if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi || characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Pending); return; } Weenie weenie; if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi && weenie.Type == (int)WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiadmin"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid && weenie.Type == (int)WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidadmin"); } } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi && weenie.Type == (int)WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiplayer"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid && weenie.Type == (int)WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidplayer"); } if (characterCreateInfo.IsSentinel && session.AccessLevel >= AccessLevel.Sentinel) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } if (characterCreateInfo.IsAdmin && session.AccessLevel >= AccessLevel.Developer) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } if (weenie == null) { weenie = DatabaseManager.World.GetCachedWeenie("human"); // Default catch-all } if (weenie == null) // If it is STILL null after the above catchall, the database is missing critical data and cannot continue with character creation. { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown); log.Error("Database does not contain the weenie for human (1). Characters cannot be created until the missing weenie is restored."); return; } // Removes the generic knife and buckler, hidden Javelin, 30 stack of arrows, and 5 stack of coins that are given to all characters // Starter Gear from the JSON file are added to the character later in the CharacterCreateEx() process weenie.WeeniePropertiesCreateList.Clear(); var guid = GuidManager.NewPlayerGuid(); var weenieType = (WeenieType)weenie.Type; // If Database didn't have Sentinel/Admin weenies, alter the weenietype coming in. if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin && weenieType != WeenieType.Admin) { weenieType = WeenieType.Admin; } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy && weenieType != WeenieType.Sentinel) { weenieType = WeenieType.Sentinel; } } var result = PlayerFactory.Create(characterCreateInfo, weenie, guid, session.AccountId, weenieType, out var player); if (result != PlayerFactory.CreateResult.Success || player == null) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Corrupt); return; } DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable => { if (!isAvailable) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse); return; } var possessions = player.GetAllPossessions(); var possessedBiotas = new Collection <(Biota biota, ReaderWriterLockSlim rwLock)>(); foreach (var possession in possessions) { possessedBiotas.Add((possession.Biota, possession.BiotaDatabaseLock)); }
private static void CharacterCreateEx(ClientMessage message, Session session) { var characterCreateInfo = new CharacterCreateInfo(); characterCreateInfo.Unpack(message.Payload); // TODO: Check for Banned Name Here //DatabaseManager.Shard.IsCharacterNameBanned(characterCreateInfo.Name, isBanned => //{ // if (!isBanned) // { // SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned); // return; // } //}); // Disable OlthoiPlay characters for now. They're not implemented yet. // FIXME: Restore OlthoiPlay characters when properly handled. if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi || characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Pending); return; } var cg = DatManager.PortalDat.CharGen; var isAdmin = characterCreateInfo.IsAdmin && (session.AccessLevel >= AccessLevel.Developer); var isEnvoy = characterCreateInfo.IsEnvoy && (session.AccessLevel >= AccessLevel.Sentinel); Weenie weenie; if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi && weenie.Type == (int)WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiadmin"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid && weenie.Type == (int)WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidadmin"); } } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi && weenie.Type == (int)WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiplayer"); } if (characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid && weenie.Type == (int)WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidplayer"); } if (isEnvoy) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } if (isAdmin) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } if (weenie == null) { weenie = DatabaseManager.World.GetCachedWeenie("human"); // Default catch-all } if (weenie == null) // If it is STILL null after the above catchall, the database is missing critical data and cannot continue with character creation. { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown); log.Error($"Database does not contain the weenie for human (1). Characters cannot be created until the missing weenie is restored."); return; } var guid = GuidManager.NewPlayerGuid(); // If Database didn't have Sentinel/Admin weenies, alter the weenietype coming in. if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin && weenie.Type != (int)WeenieType.Admin) { weenie.Type = (int)WeenieType.Admin; } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy && weenie.Type != (int)WeenieType.Sentinel) { weenie.Type = (int)WeenieType.Sentinel; } } var player = new Player(weenie, guid, session); player.SetProperty(PropertyInt.HeritageGroup, (int)characterCreateInfo.Heritage); player.SetProperty(PropertyString.HeritageGroup, cg.HeritageGroups[characterCreateInfo.Heritage].Name); player.SetProperty(PropertyInt.Gender, (int)characterCreateInfo.Gender); player.SetProperty(PropertyString.Sex, characterCreateInfo.Gender == 1 ? "Male" : "Female"); //player.SetProperty(PropertyDataId.Icon, cg.HeritageGroups[characterCreateInfo.Heritage].IconImage); // I don't believe this is used anywhere in the client, but it might be used by a future custom launcher // pull character data from the dat file var sex = cg.HeritageGroups[characterCreateInfo.Heritage].Genders[(int)characterCreateInfo.Gender]; player.SetProperty(PropertyDataId.MotionTable, sex.MotionTable); player.SetProperty(PropertyDataId.SoundTable, sex.SoundTable); player.SetProperty(PropertyDataId.PhysicsEffectTable, sex.PhysicsTable); player.SetProperty(PropertyDataId.Setup, sex.SetupID); player.SetProperty(PropertyDataId.PaletteBase, sex.BasePalette); player.SetProperty(PropertyDataId.CombatTable, sex.CombatTable); // Check the character scale if (sex.Scale != 100u) { player.SetProperty(PropertyFloat.DefaultScale, (sex.Scale / 100f)); // Scale is stored as a percentage } // Get the hair first, because we need to know if you're bald, and that's the name of that tune! var hairstyle = sex.HairStyleList[Convert.ToInt32(characterCreateInfo.Apperance.HairStyle)]; // Certain races (Undead, Tumeroks, Others?) have multiple body styles available. This is controlled via the "hair style". if (hairstyle.AlternateSetup > 0) { player.SetProperty(PropertyDataId.Setup, hairstyle.AlternateSetup); } player.SetProperty(PropertyDataId.EyesTexture, sex.GetEyeTexture(characterCreateInfo.Apperance.Eyes, hairstyle.Bald)); player.SetProperty(PropertyDataId.DefaultEyesTexture, sex.GetDefaultEyeTexture(characterCreateInfo.Apperance.Eyes, hairstyle.Bald)); player.SetProperty(PropertyDataId.NoseTexture, sex.GetNoseTexture(characterCreateInfo.Apperance.Nose)); player.SetProperty(PropertyDataId.DefaultNoseTexture, sex.GetDefaultNoseTexture(characterCreateInfo.Apperance.Nose)); player.SetProperty(PropertyDataId.MouthTexture, sex.GetMouthTexture(characterCreateInfo.Apperance.Mouth)); player.SetProperty(PropertyDataId.DefaultMouthTexture, sex.GetDefaultMouthTexture(characterCreateInfo.Apperance.Mouth)); player.SetProperty(PropertyDataId.HairTexture, sex.GetHairTexture(characterCreateInfo.Apperance.HairStyle)); player.SetProperty(PropertyDataId.DefaultHairTexture, sex.GetDefaultHairTexture(characterCreateInfo.Apperance.HairStyle)); player.SetProperty(PropertyDataId.HeadObject, sex.GetHeadObject(characterCreateInfo.Apperance.HairStyle)); // Skin is stored as PaletteSet (list of Palettes), so we need to read in the set to get the specific palette var skinPalSet = DatManager.PortalDat.ReadFromDat <PaletteSet>(sex.SkinPalSet); player.SetProperty(PropertyDataId.SkinPalette, skinPalSet.GetPaletteID(characterCreateInfo.Apperance.SkinHue)); player.SetProperty(PropertyFloat.Shade, characterCreateInfo.Apperance.SkinHue); // Hair is stored as PaletteSet (list of Palettes), so we need to read in the set to get the specific palette var hairPalSet = DatManager.PortalDat.ReadFromDat <PaletteSet>(sex.HairColorList[Convert.ToInt32(characterCreateInfo.Apperance.HairColor)]); player.SetProperty(PropertyDataId.HairPalette, hairPalSet.GetPaletteID(characterCreateInfo.Apperance.HairHue)); // Eye Color player.SetProperty(PropertyDataId.EyesPalette, sex.EyeColorList[Convert.ToInt32(characterCreateInfo.Apperance.EyeColor)]); if (characterCreateInfo.Apperance.HeadgearStyle < 0xFFFFFFFF) // No headgear is max UINT { var hat = GetClothingObject(sex.GetHeadgearWeenie(characterCreateInfo.Apperance.HeadgearStyle), characterCreateInfo.Apperance.HeadgearColor, characterCreateInfo.Apperance.HeadgearHue); if (hat != null) { player.TryEquipObject(hat, hat.GetProperty(PropertyInt.ValidLocations) ?? 0); } else { CreateIOU(player, sex.GetHeadgearWeenie(characterCreateInfo.Apperance.HeadgearStyle)); } } var shirt = GetClothingObject(sex.GetShirtWeenie(characterCreateInfo.Apperance.ShirtStyle), characterCreateInfo.Apperance.ShirtColor, characterCreateInfo.Apperance.ShirtHue); if (shirt != null) { player.TryEquipObject(shirt, shirt.GetProperty(PropertyInt.ValidLocations) ?? 0); } else { CreateIOU(player, sex.GetShirtWeenie(characterCreateInfo.Apperance.ShirtStyle)); } var pants = GetClothingObject(sex.GetPantsWeenie(characterCreateInfo.Apperance.PantsStyle), characterCreateInfo.Apperance.PantsColor, characterCreateInfo.Apperance.PantsHue); if (pants != null) { player.TryEquipObject(pants, pants.GetProperty(PropertyInt.ValidLocations) ?? 0); } else { CreateIOU(player, sex.GetPantsWeenie(characterCreateInfo.Apperance.PantsStyle)); } var shoes = GetClothingObject(sex.GetFootwearWeenie(characterCreateInfo.Apperance.FootwearStyle), characterCreateInfo.Apperance.FootwearColor, characterCreateInfo.Apperance.FootwearHue); if (shoes != null) { player.TryEquipObject(shoes, shoes.GetProperty(PropertyInt.ValidLocations) ?? 0); } else { CreateIOU(player, sex.GetFootwearWeenie(characterCreateInfo.Apperance.FootwearStyle)); } string templateName = cg.HeritageGroups[characterCreateInfo.Heritage].Templates[characterCreateInfo.TemplateOption].Name; //player.SetProperty(PropertyString.Title, templateName); player.SetProperty(PropertyString.Template, templateName); player.AddTitle(cg.HeritageGroups[characterCreateInfo.Heritage].Templates[characterCreateInfo.TemplateOption].Title, true); // stats uint totalAttributeCredits = cg.HeritageGroups[characterCreateInfo.Heritage].AttributeCredits; uint usedAttributeCredits = 0; player.Strength.StartingValue = ValidateAttributeCredits(characterCreateInfo.StrengthAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Strength.StartingValue; player.Endurance.StartingValue = ValidateAttributeCredits(characterCreateInfo.EnduranceAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Endurance.StartingValue; player.Coordination.StartingValue = ValidateAttributeCredits(characterCreateInfo.CoordinationAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Coordination.StartingValue; player.Quickness.StartingValue = ValidateAttributeCredits(characterCreateInfo.QuicknessAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Quickness.StartingValue; player.Focus.StartingValue = ValidateAttributeCredits(characterCreateInfo.FocusAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Focus.StartingValue; player.Self.StartingValue = ValidateAttributeCredits(characterCreateInfo.SelfAbility, usedAttributeCredits, totalAttributeCredits); usedAttributeCredits += player.Self.StartingValue; // Validate this is equal to actual attribute credits. 330 for all but "Olthoi", which have 60 if (usedAttributeCredits > 330 || ((characterCreateInfo.Heritage == (int)HeritageGroup.Olthoi || characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid) && usedAttributeCredits > 60)) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Corrupt); return; } // data we don't care about //characterCreateInfo.CharacterSlot; //characterCreateInfo.ClassId; // characters start with max vitals player.Health.Current = player.Health.Base; player.Stamina.Current = player.Stamina.Base; player.Mana.Current = player.Mana.Base; // set initial skill credit amount. 52 for all but "Olthoi", which have 68 player.SetProperty(PropertyInt.AvailableSkillCredits, (int)cg.HeritageGroups[characterCreateInfo.Heritage].SkillCredits); for (int i = 0; i < characterCreateInfo.SkillStatuses.Count; i++) { var skill = (Skill)i; var skillCost = skill.GetCost(); var skillStatus = characterCreateInfo.SkillStatuses[i]; if (skillStatus == SkillStatus.Specialized) { player.TrainSkill(skill, skillCost.TrainingCost); player.SpecializeSkill(skill, skillCost.SpecializationCost); player.GetCreatureSkill(skill).InitLevel = 10; } else if (skillStatus == SkillStatus.Trained) { player.TrainSkill(skill, skillCost.TrainingCost); player.GetCreatureSkill(skill).InitLevel = 5; } else if (skillCost != null && skillStatus == SkillStatus.Untrained) { player.UntrainSkill(skill, skillCost.TrainingCost); } } // grant starter items based on skills var starterGearConfig = StarterGearFactory.GetStarterGearConfiguration(); var grantedWeenies = new List <uint>(); foreach (var skillGear in starterGearConfig.Skills) { var charSkill = player.Skills[(Skill)skillGear.SkillId]; if (charSkill.Status == SkillStatus.Trained || charSkill.Status == SkillStatus.Specialized) { foreach (var item in skillGear.Gear) { if (grantedWeenies.Contains(item.WeenieId)) { var existingItem = player.Inventory.Values.FirstOrDefault(i => i.WeenieClassId == item.WeenieId); if (existingItem == null || (existingItem.MaxStackSize ?? 1) <= 1) { continue; } existingItem.StackSize += item.StackSize; continue; } var loot = WorldObjectFactory.CreateNewWorldObject(item.WeenieId); if (loot != null) { if (loot.StackSize.HasValue && loot.MaxStackSize.HasValue) { loot.StackSize = (item.StackSize <= loot.MaxStackSize) ? item.StackSize : loot.MaxStackSize; } } if (loot == null) { CreateIOU(player, item.WeenieId); continue; } if (player.TryAddToInventory(loot)) { grantedWeenies.Add(item.WeenieId); } } var heritageLoot = skillGear.Heritage.FirstOrDefault(sh => sh.HeritageId == characterCreateInfo.Heritage); if (heritageLoot != null) { foreach (var item in heritageLoot.Gear) { if (grantedWeenies.Contains(item.WeenieId)) { var existingItem = player.Inventory.Values.FirstOrDefault(i => i.WeenieClassId == item.WeenieId); if (existingItem == null || (existingItem.MaxStackSize ?? 1) <= 1) { continue; } existingItem.StackSize += item.StackSize; continue; } var loot = WorldObjectFactory.CreateNewWorldObject(item.WeenieId); if (loot != null) { if (loot.StackSize.HasValue && loot.MaxStackSize.HasValue) { loot.StackSize = (item.StackSize <= loot.MaxStackSize) ? item.StackSize : loot.MaxStackSize; } } if (loot == null) { CreateIOU(player, item.WeenieId); continue; } if (player.TryAddToInventory(loot)) { grantedWeenies.Add(item.WeenieId); } } } foreach (var spell in skillGear.Spells) { // Olthoi Spitter is a special case if (characterCreateInfo.Heritage == (int)HeritageGroup.OlthoiAcid) { player.AddKnownSpell(spell.SpellId); // Continue to next spell as Olthoi spells do not have the SpecializedOnly field continue; } if (charSkill.Status == SkillStatus.Trained && spell.SpecializedOnly == false) { player.AddKnownSpell(spell.SpellId); } else if (charSkill.Status == SkillStatus.Specialized) { player.AddKnownSpell(spell.SpellId); } } } } player.Name = characterCreateInfo.Name; //player.SetProperty(PropertyString.DisplayName, characterCreateInfo.Name); // unsure // Index used to determine the starting location uint startArea = characterCreateInfo.StartArea; player.SetProperty(PropertyBool.Attackable, true); player.SetProperty(PropertyFloat.CreationTimestamp, Time.GetTimestamp()); player.SetProperty(PropertyInt.CreationTimestamp, (int)player.GetProperty(PropertyFloat.CreationTimestamp)); player.SetProperty(PropertyString.DateOfBirth, $"{DateTime.UtcNow:dd MMMM yyyy}"); DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable => { if (!isAvailable) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse); return; } // player.SetProperty(PropertyInstanceId.Account, (int)session.Id); var character = new Character(); character.AccountId = session.Id; character.Name = player.GetProperty(PropertyString.Name); character.BiotaId = player.Guid.Full; character.IsDeleted = false; CharacterCreateSetDefaultCharacterOptions(player); player.Location = new Position(cg.StarterAreas[(int)startArea].Locations[0].ObjCellID, cg.StarterAreas[(int)startArea].Locations[0].Frame.Origin.X, cg.StarterAreas[(int)startArea].Locations[0].Frame.Origin.Y, cg.StarterAreas[(int)startArea].Locations[0].Frame.Origin.Z, cg.StarterAreas[(int)startArea].Locations[0].Frame.Orientation.X, cg.StarterAreas[(int)startArea].Locations[0].Frame.Orientation.Y, cg.StarterAreas[(int)startArea].Locations[0].Frame.Orientation.Z, cg.StarterAreas[(int)startArea].Locations[0].Frame.Orientation.W); player.Instantiation = player.Location; player.Sanctuary = player.Location; var possessions = player.GetAllPossessions(); var possessedBiotas = new Collection <Biota>(); foreach (var possession in possessions) { possessedBiotas.Add(possession.Biota); } // We must await here -- DatabaseManager.Shard.AddCharacter(character, player.Biota, possessedBiotas, saveSuccess => { if (!saveSuccess) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown); return; } session.AccountCharacters.Add(character); SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Ok, player.Guid, characterCreateInfo.Name); }); }); }
private static void CharacterCreateEx(ClientMessage message, Session session) { var characterCreateInfo = new CharacterCreateInfo(); characterCreateInfo.Unpack(message.Payload); if (PropertyManager.GetBool("taboo_table").Item&& DatManager.PortalDat.TabooTable.ContainsBadWord(characterCreateInfo.Name.ToLowerInvariant())) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned); return; } if (PropertyManager.GetBool("creature_name_check").Item&& DatabaseManager.World.IsCreatureNameInWorldDatabase(characterCreateInfo.Name)) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameBanned); return; } DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable => { if (!isAvailable) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse); return; } }); // Disable OlthoiPlay characters for now. They're not implemented yet. // FIXME: Restore OlthoiPlay characters when properly handled. if ((characterCreateInfo.Heritage == HeritageGroup.Olthoi || characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid) && !PropertyManager.GetBool("olthoi_play_enabled").Item) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Pending); return; } Weenie weenie; if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == HeritageGroup.Olthoi && weenie.WeenieType == WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiadmin"); } if (characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid && weenie.WeenieType == WeenieType.Admin) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidadmin"); } } else { weenie = DatabaseManager.World.GetCachedWeenie("human"); } if (characterCreateInfo.Heritage == HeritageGroup.Olthoi && weenie.WeenieType == WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiplayer"); } if (characterCreateInfo.Heritage == HeritageGroup.OlthoiAcid && weenie.WeenieType == WeenieType.Creature) { weenie = DatabaseManager.World.GetCachedWeenie("olthoiacidplayer"); } if (characterCreateInfo.IsSentinel && session.AccessLevel >= AccessLevel.Sentinel) { weenie = DatabaseManager.World.GetCachedWeenie("sentinel"); } if (characterCreateInfo.IsAdmin && session.AccessLevel >= AccessLevel.Developer) { weenie = DatabaseManager.World.GetCachedWeenie("admin"); } if (weenie == null) { weenie = DatabaseManager.World.GetCachedWeenie("human"); // Default catch-all } if (weenie == null) // If it is STILL null after the above catchall, the database is missing critical data and cannot continue with character creation. { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.DatabaseDown); log.Error("Database does not contain the weenie for human (1). Characters cannot be created until the missing weenie is restored."); return; } var guid = GuidManager.NewPlayerGuid(); var weenieType = weenie.WeenieType; // If Database didn't have Sentinel/Admin weenies, alter the weenietype coming in. if (ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions) { if (session.AccessLevel >= AccessLevel.Developer && session.AccessLevel <= AccessLevel.Admin && weenieType != WeenieType.Admin) { weenieType = WeenieType.Admin; } else if (session.AccessLevel >= AccessLevel.Sentinel && session.AccessLevel <= AccessLevel.Envoy && weenieType != WeenieType.Sentinel) { weenieType = WeenieType.Sentinel; } } var result = PlayerFactory.Create(characterCreateInfo, weenie, guid, session.AccountId, weenieType, out var player); if (result != PlayerFactory.CreateResult.Success || player == null) { if (result == PlayerFactory.CreateResult.ClientServerSkillsMismatch) { session.Terminate(SessionTerminationReason.ClientOutOfDate, new GameMessageBootAccount(" because your client is not the correct version for this server. Please visit http://play.emu.ac/ to update to latest client")); return; } SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.Corrupt); return; } DatabaseManager.Shard.IsCharacterNameAvailable(characterCreateInfo.Name, isAvailable => { if (!isAvailable) { SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse); return; } var possessions = player.GetAllPossessions(); var possessedBiotas = new Collection <(Biota biota, ReaderWriterLockSlim rwLock)>(); foreach (var possession in possessions) { possessedBiotas.Add((possession.Biota, possession.BiotaDatabaseLock)); }
/// <summary> /// Import or migrate a character. /// </summary> /// <returns>the result</returns> public static ImportAndMigrateResult ImportAndMigrate(PackageMetadata metadata, byte[] importBytes = null) { if ((metadata.PackageType == PackageType.Backup && !TransferConfigManager.Config.AllowImport) || (metadata.PackageType == PackageType.Migrate && !TransferConfigManager.Config.AllowMigrate)) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.OperationNotAllowed }); } metadata.NewCharacterName = metadata.NewCharacterName.Trim(); if (metadata.NewCharacterName.Length < GameConfiguration.CharacterNameMinimumLength || metadata.NewCharacterName.Length > GameConfiguration.CharacterNameMaximumLength) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.NameTooShortOrTooLong }); } if (TransferManagerUtil.StringContainsInvalidChars(GameConfiguration.AllowedCharacterNameCharacters, metadata.NewCharacterName)) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.NameContainsInvalidCharacters }); } bool NameIsGood = false; ManualResetEvent mre = new ManualResetEvent(false); DatabaseManager.Shard.IsCharacterNameAvailable(metadata.NewCharacterName, isAvailable => { NameIsGood = isAvailable; mre.Set(); }); mre.WaitOne(); if (!NameIsGood) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.NameIsUnavailable }); } // TO-DO: restricted and weenie name matching if (DatManager.PortalDat.TabooTable.ContainsBadWord(metadata.NewCharacterName)) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.NameIsNaughty }); } mre = new ManualResetEvent(false); bool slotCheck = false; DatabaseManager.Shard.GetCharacters(metadata.AccountId, false, new Action <List <Character> >((chars) => { slotCheck = chars.Count + 1 <= GameConfiguration.SlotCount; mre.Set(); })); mre.WaitOne(); if (!slotCheck) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.NoCharacterSlotsAvailable }); } string selectedMigrationSourceThumb = null; CharacterMigrationDownloadResponseModel snapshotPack = null; CharacterTransfer xfer = null; if (importBytes == null) { mre = new ManualResetEvent(false); DatabaseManager.Shard.GetCharacterTransfers((xfers) => { xfer = xfers.FirstOrDefault(k => k.Cookie == metadata.Cookie); mre.Set(); }); mre.WaitOne(); if (xfer != null) { // don't fail here, prevents inter-account same-server transfers and name changes // return new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.CookieAlreadyUsed }; } // try to verify we trust the server before downloading package, since migrations cause source server to delete the original char upon download of the package string unverifiedThumbprintsSerialized = null; string nonce = ThreadSafeRandom.NextString(TransferManagerConstants.NonceChars, TransferManagerConstants.NonceLength); try { using (InsecureWebClient iwc = new InsecureWebClient()) { unverifiedThumbprintsSerialized = iwc.DownloadString(metadata.ImportUrl.ToString() + $"api/character/migrationCheck?Cookie={metadata.Cookie}&Nonce={nonce}"); } } catch { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.CannotContactSourceServer }); } SignedMigrationCheckResponseModel signedMigrationCheckResult = null; try { signedMigrationCheckResult = JsonConvert.DeserializeObject <SignedMigrationCheckResponseModel>(unverifiedThumbprintsSerialized, TransferManagerUtil.GetSerializationSettings()); } catch { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.ProtocolError }); } ImportAndMigrateFailiureReason SignedMigrationCheckResultValidationStatus = TransferManagerUtil.Verify(signedMigrationCheckResult, nonce, out selectedMigrationSourceThumb); if (SignedMigrationCheckResultValidationStatus != ImportAndMigrateFailiureReason.None) { return(new ImportAndMigrateResult() { FailReason = SignedMigrationCheckResultValidationStatus }); } if (!signedMigrationCheckResult.Result.Ready) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.MigrationCheckFailed }); } if ((metadata.PackageType == PackageType.Migrate && !TransferConfigManager.Config.AllowMigrationFrom.Any(k => k == signedMigrationCheckResult.Result.Config.MyThumbprint)) || (metadata.PackageType == PackageType.Backup && !TransferConfigManager.Config.AllowImportFrom.Any(k => k == signedMigrationCheckResult.Result.Config.MyThumbprint))) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.UnverifiedSourceServerNotAllowed }); } if (metadata.PackageType == PackageType.Migrate && !signedMigrationCheckResult.Result.Config.AllowMigrate) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.UnverifiedSourceServerDoesntAllowMigrate }); } try { using (InsecureWebClient iwc = new InsecureWebClient()) { string TransferManagerCharacterMigrationDownloadResponseModelSerialized = iwc.DownloadString(metadata.ImportUrl.ToString() + $"api/character/migrationDownload?Cookie={metadata.Cookie}"); snapshotPack = JsonConvert.DeserializeObject <CharacterMigrationDownloadResponseModel>(TransferManagerCharacterMigrationDownloadResponseModelSerialized, TransferManagerUtil.GetSerializationSettings()); } } catch (Exception) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.ProtocolError }); } if (snapshotPack == null) { return(new ImportAndMigrateResult()); } if (!snapshotPack.Success) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.SourceServerRejectedRequest }); } } string tmpFilePath = Path.GetTempFileName(); DirectoryInfo diTmpDirPath = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(tmpFilePath), Path.GetFileNameWithoutExtension(tmpFilePath))); string signerCertPath = Path.Combine(diTmpDirPath.FullName, "signer.crt"); if (importBytes == null) { File.WriteAllBytes(tmpFilePath, snapshotPack.SnapshotPackage); } else { File.WriteAllBytes(tmpFilePath, importBytes); } using (ZipArchive zip = ZipFile.OpenRead(tmpFilePath)) { zip.ExtractToDirectory(diTmpDirPath.FullName); } File.Delete(tmpFilePath); if (!File.Exists(signerCertPath)) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.PackageUnsigned }); } string verifiedSourceThumbprint = string.Empty; using (X509Certificate2 signer = new X509Certificate2(signerCertPath)) { verifiedSourceThumbprint = signer.Thumbprint; // verify that the signer is in the trusted signers list if ((metadata.PackageType == PackageType.Migrate && !TransferConfigManager.Config.AllowMigrationFrom.Any(k => k == verifiedSourceThumbprint)) || (metadata.PackageType == PackageType.Backup && !TransferConfigManager.Config.AllowImportFrom.Any(k => k == verifiedSourceThumbprint))) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.VerifiedSourceServerNotAllowed }); } if (!string.IsNullOrWhiteSpace(selectedMigrationSourceThumb) && verifiedSourceThumbprint != selectedMigrationSourceThumb) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.VerifiedMigrationSourceThumbprintMismatch }); } // verify that the signatures are valid foreach (FileInfo fil in diTmpDirPath.GetFiles("*.json")) { if (!CertificateManager.VerifySignedFile(fil.FullName, signer)) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.CharacterPackageSignatureInvalid }); } } } // deserialize PackageMetadata packInfo = null; List <Biota> snapshot = new List <Biota>(); foreach (FileInfo fil in diTmpDirPath.GetFiles("*.json")) { if (fil.Name == "packinfo.json") { packInfo = JsonConvert.DeserializeObject <PackageMetadata>(File.ReadAllText(fil.FullName), TransferManagerUtil.GetSerializationSettings()); } else { snapshot.Add(JsonConvert.DeserializeObject <Biota>(File.ReadAllText(fil.FullName), TransferManagerUtil.GetSerializationSettings())); } } if (packInfo == null) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.PackInfoNotFound }); } if (packInfo.PackageType != metadata.PackageType) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.WrongPackageType }); } if (importBytes == null) { xfer = null; mre = new ManualResetEvent(false); DatabaseManager.Shard.GetCharacterTransfers((xfers) => { xfer = xfers.FirstOrDefault(k => k.SourceId == packInfo.CharacterId); mre.Set(); }); mre.WaitOne(); if (xfer != null) { return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.CharacterAlreadyPresent }); } } // isolate player biota List <Biota> playerCollection = snapshot.Where(k => ( k.WeenieType == (uint)WeenieType.Admin || k.WeenieType == (uint)WeenieType.Sentinel || k.WeenieType == (uint)WeenieType.Creature ) ).ToList(); if (playerCollection.Count > 1) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.FoundMoreThanOneCharacter }); } if (playerCollection.Count < 1) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.CannotFindCharacter }); } IEnumerable <Biota> possessedBiotas2 = snapshot.Except(playerCollection).ToList(); Biota newCharBiota = playerCollection.First(); uint playerOrigId = newCharBiota.Id; // refactor ObjectGuid guid = GuidManager.NewPlayerGuid(); newCharBiota = TransferManagerUtil.SetGuidAndScrubPKs(newCharBiota, guid.Full); List <BiotaPropertiesString> nameProp = newCharBiota.BiotaPropertiesString.Where(k => k.Type == (ushort)PropertyString.Name).ToList(); if (nameProp.Count != 1) { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.MalformedCharacterData }); } string FormerCharName = nameProp.First().Value; nameProp.First().Value = metadata.NewCharacterName; Collection <(Biota biota, ReaderWriterLockSlim rwLock)> possessedBiotas = new Collection <(Biota biota, ReaderWriterLockSlim rwLock)>(); foreach (Biota possession in possessedBiotas2) { possessedBiotas.Add((TransferManagerUtil.SetGuidAndScrubPKs(possession, GuidManager.NewDynamicGuid().Full), new ReaderWriterLockSlim())); } foreach ((Biota biota, ReaderWriterLockSlim rwLock)item in possessedBiotas) { IEnumerable <BiotaPropertiesIID> instances = item.biota.BiotaPropertiesIID.Where(k => k.Value == playerOrigId); foreach (BiotaPropertiesIID instance in instances) { instance.Value = guid.Full; } } foreach ((Biota biota, ReaderWriterLockSlim rwLock)item in possessedBiotas) { IEnumerable <BiotaPropertiesBookPageData> instances = item.biota.BiotaPropertiesBookPageData.Where(k => k.AuthorId == playerOrigId); foreach (BiotaPropertiesBookPageData instance in instances) { instance.AuthorId = guid.Full; } //TO-DO: scrub other authors? } // build Weenie weenie = DatabaseManager.World.GetCachedWeenie(newCharBiota.WeenieClassId); weenie.Type = newCharBiota.WeenieType; Player newPlayer = new Player(weenie, guid, metadata.AccountId) { Location = new Position(0xD655002C, 126.918549f, 81.756134f, 49.698814f, 0.794878f, 0.000000f, 0.000000f, -0.606769f), // Shoushi starter area Name = metadata.NewCharacterName, }; newPlayer.Character.Name = metadata.NewCharacterName; // insert mre = new ManualResetEvent(false); bool addCharResult = false; DatabaseManager.Shard.AddCharacterInParallel(newCharBiota, newPlayer.BiotaDatabaseLock, possessedBiotas, newPlayer.Character, newPlayer.CharacterDatabaseLock, new Action <bool>((res2) => { addCharResult = res2; mre.Set(); })); mre.WaitOne(); if (addCharResult) { // update server PlayerManager.AddOfflinePlayer(DatabaseManager.Shard.GetBiota(guid.Full)); DatabaseManager.Shard.GetCharacters(metadata.AccountId, false, new Action <List <Character> >((chars) => { Session session = WorldManager.Find(metadata.AccountId); if (session != null) { session.Characters.Add(chars.First(k => k.Id == guid.Full)); } })); DatabaseManager.Shard.SaveCharacterTransfer(new CharacterTransfer() { AccountId = metadata.AccountId, SourceId = packInfo.CharacterId, TransferType = (uint)metadata.PackageType, TransferTime = (ulong)Time.GetUnixTime(), Cookie = metadata.Cookie, SourceBaseUrl = (importBytes == null) ? metadata.ImportUrl.ToString() : null, SourceThumbprint = verifiedSourceThumbprint, TargetId = guid.Full, }, new ReaderWriterLockSlim(), null); } else { Directory.Delete(diTmpDirPath.FullName, true); return(new ImportAndMigrateResult() { FailReason = ImportAndMigrateFailiureReason.AddCharacterFailed }); } // cleanup Directory.Delete(diTmpDirPath.FullName, true); // done return(new ImportAndMigrateResult() { Success = true, NewCharacterName = metadata.NewCharacterName, NewCharacterId = guid.Full }); }