diff --git a/AppVeyor/dbversion.txt b/AppVeyor/dbversion.txt index b4bd120c95..c10cb0cc5a 100644 --- a/AppVeyor/dbversion.txt +++ b/AppVeyor/dbversion.txt @@ -1 +1 @@ -0.9.277 +0.9.278 diff --git a/Source/ACE.Database/ACE.Database.csproj b/Source/ACE.Database/ACE.Database.csproj index 513e28068f..aa64558e73 100644 --- a/Source/ACE.Database/ACE.Database.csproj +++ b/Source/ACE.Database/ACE.Database.csproj @@ -18,7 +18,7 @@ - + diff --git a/Source/ACE.Server/ACE.Server.csproj b/Source/ACE.Server/ACE.Server.csproj index 9578da1250..56cc0a140e 100644 --- a/Source/ACE.Server/ACE.Server.csproj +++ b/Source/ACE.Server/ACE.Server.csproj @@ -240,6 +240,8 @@ + + diff --git a/Source/ACE.Server/Entity/CorePlating.cs b/Source/ACE.Server/Entity/CorePlating.cs new file mode 100644 index 0000000000..b8f78927ca --- /dev/null +++ b/Source/ACE.Server/Entity/CorePlating.cs @@ -0,0 +1,292 @@ +using System; + +using log4net; + +using ACE.Entity.Enum; +using ACE.Entity.Enum.Properties; +using ACE.Entity.Models; +using ACE.Server.Entity.Actions; +using ACE.Server.Managers; +using ACE.Server.WorldObjects; + +namespace ACE.Server.Entity +{ + public class CorePlating + { + // http://acpedia.org/wiki/Announcements_-_2010/06_-_Shifting_Gears#Gear_Knights + // http://acpedia.org/wiki/Core_Plating_Integrator + // http://acpedia.org/wiki/Core_Plating_Deintegrator + + public const uint CorePlatingIntegrator = 42979; + public const uint CorePlatingDeintegrator = 43022; + + public static bool IsCorePlatingDevice(WorldObject wo) + { + return wo.WeenieClassId == CorePlatingIntegrator || wo.WeenieClassId == CorePlatingDeintegrator; + } + + public static bool IsIntegrator(uint wcid) + { + return wcid == CorePlatingIntegrator; + } + + public static bool IsDeintegrator(uint wcid) + { + return wcid == CorePlatingDeintegrator; + } + + /// + /// The player uses a Core Plating device on a piece of armor or clothing + /// + public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target) + { + //Console.WriteLine($"CorePlating.UseObjectOnTarget({player.Name}, {source.Name}, {target.Name})"); + + if (player.IsBusy) + { + player.SendUseDoneEvent(WeenieError.YoureTooBusy); + return; + } + + var allowCraftInCombat = PropertyManager.GetBool("allow_combat_mode_crafting").Item; + + if (!allowCraftInCombat && player.CombatMode != CombatMode.NonCombat) + { + player.SendUseDoneEvent(WeenieError.YouMustBeInPeaceModeToTrade); + return; + } + + // verify use requirements + var useError = VerifyUseRequirements(player, source, target); + if (useError != WeenieError.None) + { + player.SendUseDoneEvent(useError); + return; + } + + var motionCommand = MotionCommand.ClapHands; + + var actionChain = new ActionChain(); + var nextUseTime = 0.0f; + + player.IsBusy = true; + + if (allowCraftInCombat && player.CombatMode != CombatMode.NonCombat) + { + // Drop out of combat mode. This depends on the server property "allow_combat_mode_craft" being True. + // If not, this action would have aborted due to not being in NonCombat mode. + var stanceTime = player.SetCombatMode(CombatMode.NonCombat); + actionChain.AddDelaySeconds(stanceTime); + + nextUseTime += stanceTime; + } + + var motion = new Motion(player, motionCommand); + var currentStance = player.CurrentMotionState.Stance; // expected to be MotionStance.NonCombat + var clapTime = Physics.Animation.MotionTable.GetAnimationLength(player.MotionTableId, currentStance, motionCommand); + + actionChain.AddAction(player, () => player.SendMotionAsCommands(motionCommand, currentStance)); + actionChain.AddDelaySeconds(clapTime); + + nextUseTime += clapTime; + + actionChain.AddAction(player, () => + { + // re-verify + var useError = VerifyUseRequirements(player, source, target); + if (useError != WeenieError.None) + { + player.SendUseDoneEvent(useError); + return; + } + + if (IsIntegrator(source.WeenieClassId)) + Integrate(player, source, target); + else if (IsDeintegrator(source.WeenieClassId)) + Deintegrate(player, source, target); + else + player.SendUseDoneEvent(WeenieError.CraftGeneralErrorNoUiMsg); + }); + + //player.EnqueueMotion(actionChain, MotionCommand.Ready); + + actionChain.AddAction(player, () => player.IsBusy = false); + + actionChain.EnqueueChain(); + + player.NextUseTime = DateTime.UtcNow.AddSeconds(nextUseTime); + } + + public static WeenieError VerifyUseRequirements(Player player, WorldObject source, WorldObject target) + { + if (source == target) + { + player.SendTransientError($"You can't use the {source.Name} on itself."); + return WeenieError.YouDoNotPassCraftingRequirements; + } + + // ensure both source and target are in player's inventory + if (player.FindObject(source.Guid.Full, Player.SearchLocations.MyInventory) == null) + return WeenieError.YouDoNotPassCraftingRequirements; + + if (player.FindObject(target.Guid.Full, Player.SearchLocations.MyInventory) == null) + return WeenieError.YouDoNotPassCraftingRequirements; + + if (source.WeenieClassId != CorePlatingIntegrator && source.WeenieClassId != CorePlatingDeintegrator) + return WeenieError.YouDoNotPassCraftingRequirements; + + if ((target.ValidLocations & (EquipMask.Clothing | EquipMask.Armor)) == 0) + { + player.SendTransientError($"You can't use the {source.Name} on {target.Name} because it is not a piece of armor or clothing."); + return WeenieError.YouDoNotPassCraftingRequirements; + } + + if (IsIntegrator(source.WeenieClassId)) + { + if (target.GetProperty(PropertyInt.HeritageSpecificArmor) == (int)HeritageGroup.Gearknight) + { + //player.SendTransientError($"This armor has already been integrated into gear plating."); + player.SendMessage($"This armor has already been integrated into gear plating."); + return WeenieError.YouDoNotPassCraftingRequirements; + } + + if (target.GetProperty(PropertyInt.HeritageSpecificArmor) > 0) + { + //player.SendTransientError($"This armor cannot be integrated into gear plating as it is created specifically for another race."); + player.SendMessage($"This armor cannot be integrated into gear plating as it is created specifically for another race."); + return WeenieError.YouDoNotPassCraftingRequirements; + } + } + else if (IsDeintegrator(source.WeenieClassId)) + { + if (target.GetProperty(PropertyInt.HeritageSpecificArmor) != (int)HeritageGroup.Gearknight) + { + //player.SendTransientError($"This armor has not been integrated into gear plating."); + player.SendMessage($"This armor has not been integrated into gear plating."); + return WeenieError.YouDoNotPassCraftingRequirements; + } + } + + return WeenieError.None; + } + + public const uint CorePlatingGearOverlay = 0x06006D70; + + public static void Integrate(Player player, WorldObject source, WorldObject target) + { + player.SendMessage("Your integrator forges the piece into gear plating for a Gear Knight."); + + if (target.IconOverlayId != 0) + target.IconOverlaySecondary = target.IconOverlayId; + + var slotName = ""; + switch (target.ValidLocations) + { + case EquipMask.HeadWear: + slotName = " Helm "; + break; + case EquipMask.ChestWear: + case EquipMask.ChestArmor: + case EquipMask.ChestWear | EquipMask.AbdomenWear | EquipMask.UpperArmWear: + case EquipMask.ChestArmor | EquipMask.AbdomenArmor | EquipMask.UpperArmArmor: + slotName = " Chest "; + break; + case EquipMask.ChestWear | EquipMask.AbdomenWear | EquipMask.UpperArmWear | EquipMask.LowerArmWear: + case EquipMask.ChestArmor | EquipMask.AbdomenArmor | EquipMask.UpperArmArmor | EquipMask.LowerArmArmor: + slotName = " Hauberk "; + break; + // no pcaps for "shirts" found + //case EquipMask.ChestWear | EquipMask.AbdomenWear | EquipMask.UpperArmWear: + //case EquipMask.ChestArmor | EquipMask.AbdomenArmor | EquipMask.UpperArmArmor: + // slotName = " Shirt "; + // break; + case EquipMask.AbdomenWear: + case EquipMask.AbdomenArmor: + slotName = " Girth "; + break; + case EquipMask.UpperArmWear: + case EquipMask.UpperArmArmor: + slotName = " Pauldron "; + break; + case EquipMask.LowerArmWear: + case EquipMask.LowerArmArmor: + slotName = " Bracer "; + break; + case EquipMask.UpperArmWear | EquipMask.LowerArmWear: + case EquipMask.UpperArmArmor | EquipMask.LowerArmArmor: + slotName = " Sleeve "; + break; + case EquipMask.HandWear: + slotName = " Gauntlet "; + break; + case EquipMask.UpperLegWear: + case EquipMask.UpperLegArmor: + slotName = " Tasset "; + break; + case EquipMask.LowerLegWear: + case EquipMask.LowerLegArmor: + slotName = " Greaves "; + break; + case EquipMask.UpperLegWear | EquipMask.LowerLegWear: + case EquipMask.UpperLegArmor | EquipMask.LowerLegArmor: + case EquipMask.AbdomenWear | EquipMask.UpperLegWear | EquipMask.LowerLegWear: + case EquipMask.AbdomenArmor | EquipMask.UpperLegArmor | EquipMask.LowerLegArmor: + slotName = " Leg "; + break; + case EquipMask.FootWear: + case EquipMask.LowerLegWear | EquipMask.FootWear: + slotName = " Solleret "; + break; + // pcap showed boots were called solleret as well + //case EquipMask.LowerLegWear | EquipMask.FootWear: + // slotName = " Boot "; + // break; + case EquipMask.Armor: + case EquipMask.HeadWear | EquipMask.Armor: + slotName = " Body "; + break; + } + + player.UpdateProperty(target, PropertyInt.HeritageSpecificArmor, (int)HeritageGroup.Gearknight); + player.UpdateProperty(target, PropertyDataId.IconOverlay, CorePlatingGearOverlay); + player.UpdateProperty(target, PropertyString.GearPlatingName, $"Core{slotName}Plating"); + player.UpdateProperty(target, PropertyString.Use, "This Aetherium core plating installs into the frame of a Gear Knight to strengthen it."); + + target.SaveBiotaToDatabase(); + + player.SendUseDoneEvent(); + } + + public static void Deintegrate(Player player, WorldObject source, WorldObject target) + { + player.SendMessage("Your deintegrator restores the original form of this piece of gear."); + + player.UpdateProperty(target, PropertyInt.HeritageSpecificArmor, null); + if (target.IconOverlayId == CorePlatingGearOverlay) + player.UpdateProperty(target, PropertyDataId.IconOverlay, target.IconOverlaySecondary ?? null); + player.UpdateProperty(target, PropertyString.GearPlatingName, null); + + var targetWeenie = Database.DatabaseManager.World.GetCachedWeenie(target.WeenieClassId); + + if (targetWeenie != null) + { + var useString = targetWeenie.GetProperty(PropertyString.Use); + if (useString != null) + player.UpdateProperty(target, PropertyString.Use, useString); + else + player.UpdateProperty(target, PropertyString.Use, null); + } + else + player.UpdateProperty(target, PropertyString.Use, null); + + if (target.IconOverlaySecondary != null) + target.IconOverlaySecondary = null; + + target.SaveBiotaToDatabase(); + + player.SendUseDoneEvent(); + } + + private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + } +} diff --git a/Source/ACE.Server/Entity/Fellowship.cs b/Source/ACE.Server/Entity/Fellowship.cs index 4f9383f278..b31140cac7 100644 --- a/Source/ACE.Server/Entity/Fellowship.cs +++ b/Source/ACE.Server/Entity/Fellowship.cs @@ -99,7 +99,7 @@ public void AddFellowshipMember(Player inviter, Player newMember) } } - if (FellowshipMembers.Count == MaxFellows) + if (FellowshipMembers.Count >= MaxFellows) { inviter.Session.Network.EnqueueSend(new GameEventWeenieError(inviter.Session, WeenieError.YourFellowshipIsFull)); return; @@ -148,7 +148,7 @@ public void AddConfirmedMember(Player inviter, Player player, bool response) return; } - if (FellowshipMembers.Count == 9) + if (FellowshipMembers.Count >= MaxFellows) { inviter.Session.Network.EnqueueSend(new GameEventWeenieError(inviter.Session, WeenieError.YourFellowshipIsFull)); return; diff --git a/Source/ACE.Server/Factories/PlayerFactory.cs b/Source/ACE.Server/Factories/PlayerFactory.cs index 8a652f3411..7e9c77c94b 100644 --- a/Source/ACE.Server/Factories/PlayerFactory.cs +++ b/Source/ACE.Server/Factories/PlayerFactory.cs @@ -102,32 +102,35 @@ public static CreateResult Create(CharacterCreateInfo characterCreateInfo, Weeni // skip over this for olthoi, use the weenie defaults if (!player.IsOlthoiPlayer) { - if (characterCreateInfo.Appearance.HeadgearStyle < uint.MaxValue) // No headgear is max UINT + if (player.Heritage != (int)HeritageGroup.Gearknight) // Gear Knights do not get clothing (pcap verified) { - var hat = GetClothingObject(sex.GetHeadgearWeenie(characterCreateInfo.Appearance.HeadgearStyle), characterCreateInfo.Appearance.HeadgearColor, characterCreateInfo.Appearance.HeadgearHue); - if (hat != null) - player.TryEquipObject(hat, hat.ValidLocations ?? 0); + if (characterCreateInfo.Appearance.HeadgearStyle < uint.MaxValue) // No headgear is max UINT + { + var hat = GetClothingObject(sex.GetHeadgearWeenie(characterCreateInfo.Appearance.HeadgearStyle), characterCreateInfo.Appearance.HeadgearColor, characterCreateInfo.Appearance.HeadgearHue); + if (hat != null) + player.TryEquipObject(hat, hat.ValidLocations ?? 0); + else + player.TryAddToInventory(CreateIOU(sex.GetHeadgearWeenie(characterCreateInfo.Appearance.HeadgearStyle))); + } + + var shirt = GetClothingObject(sex.GetShirtWeenie(characterCreateInfo.Appearance.ShirtStyle), characterCreateInfo.Appearance.ShirtColor, characterCreateInfo.Appearance.ShirtHue); + if (shirt != null) + player.TryEquipObject(shirt, shirt.ValidLocations ?? 0); else - player.TryAddToInventory(CreateIOU(sex.GetHeadgearWeenie(characterCreateInfo.Appearance.HeadgearStyle))); - } + player.TryAddToInventory(CreateIOU(sex.GetShirtWeenie(characterCreateInfo.Appearance.ShirtStyle))); - var shirt = GetClothingObject(sex.GetShirtWeenie(characterCreateInfo.Appearance.ShirtStyle), characterCreateInfo.Appearance.ShirtColor, characterCreateInfo.Appearance.ShirtHue); - if (shirt != null) - player.TryEquipObject(shirt, shirt.ValidLocations ?? 0); - else - player.TryAddToInventory(CreateIOU(sex.GetShirtWeenie(characterCreateInfo.Appearance.ShirtStyle))); - - var pants = GetClothingObject(sex.GetPantsWeenie(characterCreateInfo.Appearance.PantsStyle), characterCreateInfo.Appearance.PantsColor, characterCreateInfo.Appearance.PantsHue); - if (pants != null) - player.TryEquipObject(pants, pants.ValidLocations ?? 0); - else - player.TryAddToInventory(CreateIOU(sex.GetPantsWeenie(characterCreateInfo.Appearance.PantsStyle))); - - var shoes = GetClothingObject(sex.GetFootwearWeenie(characterCreateInfo.Appearance.FootwearStyle), characterCreateInfo.Appearance.FootwearColor, characterCreateInfo.Appearance.FootwearHue); - if (shoes != null) - player.TryEquipObject(shoes, shoes.ValidLocations ?? 0); - else - player.TryAddToInventory(CreateIOU(sex.GetFootwearWeenie(characterCreateInfo.Appearance.FootwearStyle))); + var pants = GetClothingObject(sex.GetPantsWeenie(characterCreateInfo.Appearance.PantsStyle), characterCreateInfo.Appearance.PantsColor, characterCreateInfo.Appearance.PantsHue); + if (pants != null) + player.TryEquipObject(pants, pants.ValidLocations ?? 0); + else + player.TryAddToInventory(CreateIOU(sex.GetPantsWeenie(characterCreateInfo.Appearance.PantsStyle))); + + var shoes = GetClothingObject(sex.GetFootwearWeenie(characterCreateInfo.Appearance.FootwearStyle), characterCreateInfo.Appearance.FootwearColor, characterCreateInfo.Appearance.FootwearHue); + if (shoes != null) + player.TryEquipObject(shoes, shoes.ValidLocations ?? 0); + else + player.TryAddToInventory(CreateIOU(sex.GetFootwearWeenie(characterCreateInfo.Appearance.FootwearStyle))); + } string templateName = heritageGroup.Templates[characterCreateInfo.TemplateOption].Name; player.SetProperty(PropertyString.Template, templateName); diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index ebbf595c64..d32b6bd629 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -557,6 +557,7 @@ public static void LoadDefaultProperties() ("fellow_quest_bonus", new Property(false, "if TRUE, applies EvenShare formula to fellowship quest reward XP (300% max bonus, defaults to false in retail)")), ("fix_chest_missing_inventory_window", new Property(false, "Very non-standard fix. This fixes an acclient bug where unlocking a chest, and then quickly opening it before the client has received the Locked=false update from server can result in the chest opening, but with the chest inventory window not displaying. Bug has a higher chance of appearing with more network latency.")), ("gateway_ties_summonable", new Property(true, "if disabled, players cannot summon ties from gateways. defaults to enabled, as in retail")), + ("gearknight_core_plating", new Property(true, "if disabled, Gear Knight players are not required to use core plating devices for armor and clothing. defaults to enabled, as in retail")), ("house_15day_account", new Property(true, "if disabled, houses can be purchased with accounts created less than 15 days old")), ("house_30day_cooldown", new Property(true, "if disabled, houses can be purchased without waiting 30 days between each purchase")), ("house_hook_limit", new Property(true, "if disabled, house hook limits are ignored")), @@ -684,4 +685,4 @@ public static void LoadDefaultProperties() ("server_motd", new Property("", "Server message of the day")) ); } -} \ No newline at end of file +} diff --git a/Source/ACE.Server/ServerBuildInfo_Dynamic.cs b/Source/ACE.Server/ServerBuildInfo_Dynamic.cs index 79fc43e845..59cad9eae1 100644 --- a/Source/ACE.Server/ServerBuildInfo_Dynamic.cs +++ b/Source/ACE.Server/ServerBuildInfo_Dynamic.cs @@ -4,17 +4,17 @@ namespace ACE.Server public static partial class ServerBuildInfo { public static string Branch = "master"; - public static string Commit = "74b57b215c600e480657fc0bbeaeab9f568920d6"; + public static string Commit = "bdb45398fc6682a367bd7d6af2cc1b4d4cb9988a"; - public static string Version = "1.57"; - public static string Build = "4475"; + public static string Version = "1.58"; + public static string Build = "4480"; public static int BuildYear = 2024; public static int BuildMonth = 01; - public static int BuildDay = 11; - public static int BuildHour = 00; - public static int BuildMinute = 58; - public static int BuildSecond = 45; + public static int BuildDay = 20; + public static int BuildHour = 20; + public static int BuildMinute = 06; + public static int BuildSecond = 24; } } diff --git a/Source/ACE.Server/WorldObjects/CraftTool.cs b/Source/ACE.Server/WorldObjects/CraftTool.cs index c3780ae2ea..2185d64acd 100644 --- a/Source/ACE.Server/WorldObjects/CraftTool.cs +++ b/Source/ACE.Server/WorldObjects/CraftTool.cs @@ -42,6 +42,12 @@ public override void HandleActionUseOnTarget(Player player, WorldObject target) return; } + if (CorePlating.IsCorePlatingDevice(this)) + { + CorePlating.UseObjectOnTarget(player, this, target); + return; + } + // fallback on recipe manager base.HandleActionUseOnTarget(player, target); } diff --git a/Source/ACE.Server/WorldObjects/Player.cs b/Source/ACE.Server/WorldObjects/Player.cs index 3c88c29438..343e0cf779 100644 --- a/Source/ACE.Server/WorldObjects/Player.cs +++ b/Source/ACE.Server/WorldObjects/Player.cs @@ -167,6 +167,8 @@ private void SetEphemeralValues() IsOlthoiPlayer = HeritageGroup == HeritageGroup.Olthoi || HeritageGroup == HeritageGroup.OlthoiAcid; + IsGearKnightPlayer = PropertyManager.GetBool("gearknight_core_plating").Item && HeritageGroup == HeritageGroup.Gearknight; + ContainerCapacity = (byte)(7 + AugmentationExtraPackSlot); if (Session != null && AdvocateQuest && IsAdvocate) // Advocate permissions are per character regardless of override diff --git a/Source/ACE.Server/WorldObjects/Player_Inventory.cs b/Source/ACE.Server/WorldObjects/Player_Inventory.cs index e541c89280..e337e022cc 100644 --- a/Source/ACE.Server/WorldObjects/Player_Inventory.cs +++ b/Source/ACE.Server/WorldObjects/Player_Inventory.cs @@ -2061,6 +2061,12 @@ private WeenieError CheckWieldRequirements(WorldObject item) if (heritageSpecificArmor == null || (HeritageGroup)heritageSpecificArmor != HeritageGroup) return WeenieError.HeritageRequiresSpecificArmor; } + else if (IsGearKnightPlayer) + { + if (((item.ValidLocations & (EquipMask.Clothing | EquipMask.Armor)) != 0) + && (heritageSpecificArmor == null || (HeritageGroup)heritageSpecificArmor != HeritageGroup)) + return WeenieError.HeritageRequiresSpecificArmor; + } else { if (heritageSpecificArmor != null && (HeritageGroup)heritageSpecificArmor != HeritageGroup) diff --git a/Source/ACE.Server/WorldObjects/Player_Properties.cs b/Source/ACE.Server/WorldObjects/Player_Properties.cs index 3e8b3aceea..bbee5aeab5 100644 --- a/Source/ACE.Server/WorldObjects/Player_Properties.cs +++ b/Source/ACE.Server/WorldObjects/Player_Properties.cs @@ -74,8 +74,16 @@ public bool IsPlussed get => (Character != null && Character.IsPlussed) || (Session != null && ConfigManager.Config.Server.Accounts.OverrideCharacterPermissions && Session.AccessLevel > AccessLevel.Advocate); } + /// + /// Flag indicates if player is an Olthoi Player + /// public bool IsOlthoiPlayer { get; set; } + /// + /// Flag indicates if player is a Gear Knight and Core Plating server option (gearknight_core_plating) is enforced + /// + public bool IsGearKnightPlayer { get; set; } + public string GodState { diff --git a/Source/ACE.Server/starterGear.json b/Source/ACE.Server/starterGear.json index cf4ea8c465..631b0561de 100644 --- a/Source/ACE.Server/starterGear.json +++ b/Source/ACE.Server/starterGear.json @@ -138,6 +138,16 @@ "weenieId": "43018", "name": "Letter From Home", "stacksize": "1" + }, + { + "weenieId": "42979", + "name": "Core Plating Integrator", + "stacksize": "1" + }, + { + "weenieId": "43022", + "name": "Core Plating Deintegrator", + "stacksize": "1" } ] }, diff --git a/appveyor.yml b/appveyor.yml index baea791b67..cdc977fd28 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.57.{build} +version: 1.58.{build} pull_requests: do_not_increment_build_number: true skip_tags: true