using System.Numerics; using log4net; using ACE.Common; using ACE.Entity; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Entity.Models; using ACE.Server.Entity; using ACE.Server.Entity.Actions; using ACE.Server.Managers; using ACE.Server.Network.GameEvent.Events; using ACE.Server.Network.GameMessages.Messages; namespace ACE.Server.WorldObjects { public partial class Portal : WorldObject { private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// /// A new biota be created taking all of its values from weenie. /// public Portal(Weenie weenie, ObjectGuid guid) : base(weenie, guid) { SetEphemeralValues(); } /// /// Restore a WorldObject from the database. /// public Portal(Biota biota) : base(biota) { SetEphemeralValues(); } protected void SetEphemeralValues() { ObjectDescriptionFlags |= ObjectDescriptionFlag.Portal; ActivationResponse |= ActivationResponse.Use; UpdatePortalDestination(Destination); } public override bool EnterWorld() { var success = base.EnterWorld(); if (!success) { log.Error($"{Name} ({Guid}) failed to spawn @ {Location?.ToLOCString()}"); return false; } if (RelativeDestination != null && Location != null && Destination == null) { var relativeDestination = new Position(Location); relativeDestination.Pos += new Vector3(RelativeDestination.PositionX, RelativeDestination.PositionY, RelativeDestination.PositionZ); relativeDestination.Rotation = new Quaternion(RelativeDestination.RotationX, relativeDestination.RotationY, relativeDestination.RotationZ, relativeDestination.RotationW); relativeDestination.LandblockId = new LandblockId(relativeDestination.GetCell()); UpdatePortalDestination(relativeDestination); } return true; } public void UpdatePortalDestination(Position destination) { Destination = destination; if (PortalShowDestination ?? true) { AppraisalPortalDestination = Name; if (Destination != null) { var destCoords = Destination.GetMapCoordStr(); if (destCoords != null) AppraisalPortalDestination += $" ({destCoords})."; } } } public override void SetLinkProperties(WorldObject wo) { if (wo.IsLinkSpot) SetPosition(PositionType.Destination, new Position(wo.Location)); } public bool IsGateway { get => WeenieClassId == 1955; } //public override void OnActivate(WorldObject activator) //{ // if (activator is Creature creature) // EmoteManager.OnUse(creature); // base.OnActivate(activator); //} public virtual void OnCollideObject(Player player) { OnActivate(player); } public override void OnCastSpell(WorldObject activator) { if (SpellDID.HasValue) base.OnCastSpell(activator); else ActOnUse(activator); } /// /// If a player tries to use 2 portals in under this amount of time, /// they receive an error message /// private const float minTimeSinceLastPortal = 3.5f; public override ActivationResult CheckUseRequirements(WorldObject activator) { if (!(activator is Player player)) return new ActivationResult(false); if (player.Teleporting) return new ActivationResult(false); if (Destination == null) { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"Portal destination for portal ID {WeenieClassId} not yet implemented!", ChatMessageType.System)); return new ActivationResult(false); } if (player.LastPortalTeleportTimestamp != null) { var currentTime = Time.GetUnixTime(); var timeSinceLastPortal = currentTime - player.LastPortalTeleportTimestamp.Value; if (timeSinceLastPortal < minTimeSinceLastPortal) { // prevent message spam if (player.LastPortalTeleportTimestampError != null) { var timeSinceLastPortalError = currentTime - player.LastPortalTeleportTimestampError.Value; if (timeSinceLastPortalError < minTimeSinceLastPortal) return new ActivationResult(false); } player.LastPortalTeleportTimestampError = currentTime; return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouHaveBeenTeleportedTooRecently)); } } if (player.PKTimerActive && !PortalIgnoresPkAttackTimer) { return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouHaveBeenInPKBattleTooRecently)); } if (!player.IgnorePortalRestrictions) { if (player.Level < MinLevel) { // You are not powerful enough to interact with that portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouAreNotPowerfulEnoughToUsePortal)); } if (player.Level > MaxLevel && MaxLevel != 0 && PropertyManager.GetBool("use_portal_max_level_requirement").Item) { // You are too powerful to interact with that portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouAreTooPowerfulToUsePortal)); } //var playerPkLevel = player.PkLevel; //if (PropertyManager.GetBool("pk_server").Item) // playerPkLevel = PKLevel.PK; //else if (PropertyManager.GetBool("pkl_server").Item) // playerPkLevel = PKLevel.PKLite; if (PortalRestrictions == PortalBitmask.Undef) { // Players may not interact with that portal. return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.PlayersMayNotUsePortal)); } if (PortalRestrictions.HasFlag(PortalBitmask.NoPk) && player.PlayerKillerStatus == PlayerKillerStatus.PK) { // Player killers may not interact with that portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.PKsMayNotUsePortal)); } if (PortalRestrictions.HasFlag(PortalBitmask.NoPKLite) && player.PlayerKillerStatus == PlayerKillerStatus.PKLite) { // Lite Player Killers may not interact with that portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.PKLiteMayNotUsePortal)); } if (PortalRestrictions.HasFlag(PortalBitmask.NoNPK) && player.PlayerKillerStatus == PlayerKillerStatus.NPK) { // Non-player killers may not interact with that portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.NonPKsMayNotUsePortal)); } if (PortalRestrictions.HasFlag(PortalBitmask.OnlyOlthoiPCs) && !player.IsOlthoiPlayer) { // Only Olthoi may pass through this portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.OnlyOlthoiMayUsePortal)); } if ((PortalRestrictions.HasFlag(PortalBitmask.NoOlthoiPCs) || IsGateway) && player.IsOlthoiPlayer) { // Olthoi may not pass through this portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.OlthoiMayNotUsePortal)); } if (PortalRestrictions.HasFlag(PortalBitmask.NoVitae) && player.HasVitae) { // You may not pass through this portal while Vitae weakens you! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouMayNotUsePortalWithVitae)); } if (PortalRestrictions.HasFlag(PortalBitmask.NoNewAccounts) && !player.Account15Days) { // This character must be two weeks old or have been created on an account at least two weeks old to use this portal! return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouMustBeTwoWeeksOldToUsePortal)); } if (player.AccountRequirements < AccountRequirements) { // You must purchase Asheron's Call -- Throne of Destiny to use this portal. return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.MustPurchaseThroneOfDestinyToUsePortal)); } if ((AdvocateQuest ?? false) && !player.IsAdvocate) { // You must be an Advocate to interact with that portal. return new ActivationResult(new GameEventWeenieError(player.Session, WeenieError.YouMustBeAnAdvocateToUsePortal)); } } if (QuestRestriction != null && !player.IgnorePortalRestrictions) { var hasQuest = player.QuestManager.HasQuest(QuestRestriction); var canSolve = player.QuestManager.CanSolve(QuestRestriction); var success = hasQuest && !canSolve; if (!success) { player.QuestManager.HandlePortalQuestError(QuestRestriction); return new ActivationResult(false); } } // handle quest initial flagging if (Quest != null) { EmoteManager.OnQuest(player); } return new ActivationResult(true); } public override void ActOnUse(WorldObject activator) { var player = activator as Player; if (player == null) return; #if DEBUG // player.Session.Network.EnqueueSend(new GameMessageSystemChat("Portal sending player to destination", ChatMessageType.System)); #endif var portalDest = new Position(Destination); AdjustDungeon(portalDest); WorldManager.ThreadSafeTeleport(player, portalDest, new ActionEventDelegate(() => { // If the portal just used is able to be recalled to, // save the destination coordinates to the LastPortal character position save table if (!NoRecall) player.LastPortalDID = OriginalPortal == null ? WeenieClassId : OriginalPortal; // if walking through a summoned portal EmoteManager.OnPortal(player); player.SendWeenieError(WeenieError.ITeleported); }), true); } } }