mirror of
https://github.com/ACEmulator/ACE
synced 2026-08-17 12:26:06 -04:00
* Update Player_Location.cs Thanks to Erusun for the report. Stops mansion recall to Apartments. * Update Player_House.cs * Update Player_Location.cs
890 lines
36 KiB
C#
890 lines
36 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using System.Linq;
|
|
|
|
using ACE.Common;
|
|
using ACE.Database;
|
|
using ACE.DatLoader;
|
|
using ACE.DatLoader.FileTypes;
|
|
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.Network.GameEvent.Events;
|
|
using ACE.Server.Network.GameMessages.Messages;
|
|
using ACE.Server.Managers;
|
|
|
|
namespace ACE.Server.WorldObjects
|
|
{
|
|
partial class Player
|
|
{
|
|
private static readonly Position MarketplaceDrop = DatabaseManager.World.GetCachedWeenie("portalmarketplace")?.GetPosition(PositionType.Destination) ?? new Position(0x016C01BC, 49.206f, -31.935f, 0.005f, 0, 0, -0.707107f, 0.707107f);
|
|
|
|
/// <summary>
|
|
/// Teleports the player to position
|
|
/// </summary>
|
|
/// <param name="positionType">PositionType to be teleported to</param>
|
|
/// <returns>true on success (position is set) false otherwise</returns>
|
|
public bool TeleToPosition(PositionType positionType)
|
|
{
|
|
var position = GetPosition(positionType);
|
|
|
|
if (position != null)
|
|
{
|
|
var teleportDest = new Position(position);
|
|
AdjustDungeon(teleportDest);
|
|
|
|
Teleport(teleportDest);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static readonly Motion motionLifestoneRecall = new Motion(MotionStance.NonCombat, MotionCommand.LifestoneRecall);
|
|
|
|
private static readonly Motion motionHouseRecall = new Motion(MotionStance.NonCombat, MotionCommand.HouseRecall);
|
|
|
|
public static float RecallMoveThreshold = 8.0f;
|
|
public static float RecallMoveThresholdSq = RecallMoveThreshold * RecallMoveThreshold;
|
|
|
|
public bool TooBusyToRecall
|
|
{
|
|
get => IsBusy || suicideInProgress; // recalls could be started from portal space?
|
|
}
|
|
|
|
public void HandleActionTeleToHouse()
|
|
{
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCanOnlyRecallToLifestone));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
var house = House ?? GetAccountHouse();
|
|
|
|
if (house == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling home.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.HouseRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
var actionChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
var animLength = DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.HouseRecall);
|
|
actionChain.AddDelaySeconds(animLength);
|
|
IsBusy = true;
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
Teleport(house.SlumLord.Location);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles teleporting a player to the lifestone (/ls or /lifestone command)
|
|
/// </summary>
|
|
public void HandleActionTeleToLifestone()
|
|
{
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
if (Sanctuary == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameMessageSystemChat("Your spirit has not been attuned to a sanctuary location.", ChatMessageType.Broadcast));
|
|
return;
|
|
}
|
|
|
|
// FIXME(ddevec): I should probably make a better interface for this
|
|
UpdateVital(Mana, Mana.Current / 2);
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the lifestone.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.LifestoneRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
ActionChain lifestoneChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
IsBusy = true;
|
|
lifestoneChain.AddDelaySeconds(DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.LifestoneRecall));
|
|
lifestoneChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
Teleport(Sanctuary);
|
|
});
|
|
|
|
lifestoneChain.EnqueueChain();
|
|
}
|
|
|
|
private static readonly Motion motionMarketplaceRecall = new Motion(MotionStance.NonCombat, MotionCommand.MarketplaceRecall);
|
|
|
|
public void HandleActionTeleToMarketPlace()
|
|
{
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCanOnlyRecallToLifestone));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the marketplace.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.MarketplaceRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// TODO: (OptimShi): Actual animation length is longer than in retail. 18.4s
|
|
// float mpAnimationLength = MotionTable.GetAnimationLength((uint)MotionTableId, MotionCommand.MarketplaceRecall);
|
|
// mpChain.AddDelaySeconds(mpAnimationLength);
|
|
ActionChain mpChain = new ActionChain();
|
|
mpChain.AddDelaySeconds(14);
|
|
|
|
// Then do teleport
|
|
IsBusy = true;
|
|
mpChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
Teleport(MarketplaceDrop);
|
|
});
|
|
|
|
// Set the chain to run
|
|
mpChain.EnqueueChain();
|
|
}
|
|
|
|
private static readonly Motion motionAllegianceHometownRecall = new Motion(MotionStance.NonCombat, MotionCommand.AllegianceHometownRecall);
|
|
|
|
public void HandleActionRecallAllegianceHometown()
|
|
{
|
|
//Console.WriteLine($"{Name}.HandleActionRecallAllegianceHometown()");
|
|
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCanOnlyRecallToLifestone));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
// check if player is in an allegiance
|
|
if (!VerifyRecallAllegianceHometown())
|
|
return;
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is going to the Allegiance hometown.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.AllegianceHometownRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
var actionChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
IsBusy = true;
|
|
var animLength = DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.AllegianceHometownRecall);
|
|
actionChain.AddDelaySeconds(animLength);
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
// re-verify
|
|
if (!VerifyRecallAllegianceHometown())
|
|
return;
|
|
|
|
Teleport(Allegiance.Sanctuary);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
private bool VerifyRecallAllegianceHometown()
|
|
{
|
|
if (Allegiance == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance));
|
|
return false;
|
|
}
|
|
|
|
if (Allegiance.Sanctuary == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourAllegianceDoesNotHaveHometown));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalls you to your allegiance's Mansion or Villa
|
|
/// </summary>
|
|
public void HandleActionTeleToMansion()
|
|
{
|
|
//Console.WriteLine($"{Name}.HandleActionTeleToMansion()");
|
|
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCanOnlyRecallToLifestone));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
var allegianceHouse = VerifyTeleToMansion();
|
|
|
|
if (allegianceHouse == null)
|
|
return;
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the Allegiance housing.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.HouseRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
var actionChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
var animLength = DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.HouseRecall);
|
|
actionChain.AddDelaySeconds(animLength);
|
|
|
|
IsBusy = true;
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
// re-verify
|
|
allegianceHouse = VerifyTeleToMansion();
|
|
|
|
if (allegianceHouse == null)
|
|
return;
|
|
|
|
Teleport(allegianceHouse.SlumLord.Location);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
private House VerifyTeleToMansion()
|
|
{
|
|
// check if player is in an allegiance
|
|
if (Allegiance == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance));
|
|
return null;
|
|
}
|
|
|
|
var allegianceHouse = Allegiance.GetHouse();
|
|
|
|
if (allegianceHouse == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchDoesNotOwnAMansionOrVilla));
|
|
return null;
|
|
}
|
|
|
|
if (allegianceHouse.HouseType != HouseType.Villa && allegianceHouse.HouseType != HouseType.Mansion)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchsHouseIsNotAMansionOrVilla));
|
|
return null;
|
|
}
|
|
|
|
// ensure allegiance housing has allegiance permissions enabled
|
|
if (allegianceHouse.MonarchId == null)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchHasClosedTheMansion));
|
|
return null;
|
|
}
|
|
|
|
return allegianceHouse;
|
|
}
|
|
|
|
private static readonly Motion motionPkArenaRecall = new Motion(MotionStance.NonCombat, MotionCommand.PKArenaRecall);
|
|
|
|
private static List<Position> pkArenaLocs = new List<Position>()
|
|
{
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpkarenanew1")?.GetPosition(PositionType.Destination) ?? new Position(0x00660117, 30, -50, 0.005f, 0, 0, 0.000000f, 1.000000f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpkarenanew2")?.GetPosition(PositionType.Destination) ?? new Position(0x00660106, 10, 0, 0.005f, 0, 0, -0.947071f, 0.321023f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpkarenanew3")?.GetPosition(PositionType.Destination) ?? new Position(0x00660103, 30, -30, 0.005f, 0, 0, -0.699713f, 0.714424f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpkarenanew4")?.GetPosition(PositionType.Destination) ?? new Position(0x0066011E, 50, 0, 0.005f, 0, 0, -0.961021f, -0.276474f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpkarenanew5")?.GetPosition(PositionType.Destination) ?? new Position(0x00660127, 60, -30, 0.005f, 0, 0, 0.681639f, 0.731689f)),
|
|
};
|
|
|
|
public void HandleActionTeleToPkArena()
|
|
{
|
|
//Console.WriteLine($"{Name}.HandleActionTeleToPkArena()");
|
|
|
|
if (PlayerKillerStatus != PlayerKillerStatus.PK)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OnlyPKsMayUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is going to the PK Arena.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.PKArenaRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
var actionChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
var animLength = DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.PKArenaRecall);
|
|
actionChain.AddDelaySeconds(animLength);
|
|
|
|
IsBusy = true;
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
var rng = ThreadSafeRandom.Next(0, pkArenaLocs.Count - 1);
|
|
var loc = pkArenaLocs[rng];
|
|
|
|
Teleport(loc);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
private static List<Position> pklArenaLocs = new List<Position>()
|
|
{
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpklarenanew1")?.GetPosition(PositionType.Destination) ?? new Position(0x00670117, 30, -50, 0.005f, 0, 0, 0.000000f, 1.000000f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpklarenanew2")?.GetPosition(PositionType.Destination) ?? new Position(0x00670106, 10, 0, 0.005f, 0, 0, -0.947071f, 0.321023f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpklarenanew3")?.GetPosition(PositionType.Destination) ?? new Position(0x00670103, 30, -30, 0.005f, 0, 0, -0.699713f, 0.714424f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpklarenanew4")?.GetPosition(PositionType.Destination) ?? new Position(0x0067011E, 50, 0, 0.005f, 0, 0, -0.961021f, -0.276474f)),
|
|
new Position(DatabaseManager.World.GetCachedWeenie("portalpklarenanew5")?.GetPosition(PositionType.Destination) ?? new Position(0x00670127, 60, -30, 0.005f, 0, 0, 0.681639f, 0.731689f)),
|
|
};
|
|
|
|
public void HandleActionTeleToPklArena()
|
|
{
|
|
//Console.WriteLine($"{Name}.HandleActionTeleToPkLiteArena()");
|
|
|
|
if (IsOlthoiPlayer)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCanOnlyRecallToLifestone));
|
|
return;
|
|
}
|
|
|
|
if (PlayerKillerStatus != PlayerKillerStatus.PKLite)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OnlyPKLiteMayUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (PKTimerActive)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
|
|
return;
|
|
}
|
|
|
|
if (RecallsDisabled)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
|
|
return;
|
|
}
|
|
|
|
if (TooBusyToRecall)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YoureTooBusy));
|
|
return;
|
|
}
|
|
|
|
if (CombatMode != CombatMode.NonCombat)
|
|
{
|
|
// this should be handled by a different thing, probably a function that forces player into peacemode
|
|
var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
|
|
SetCombatMode(CombatMode.NonCombat);
|
|
Session.Network.EnqueueSend(updateCombatMode);
|
|
}
|
|
|
|
EnqueueBroadcast(new GameMessageSystemChat($"{Name} is going to the PKL Arena.", ChatMessageType.Recall), LocalBroadcastRange, ChatMessageType.Recall);
|
|
|
|
SendMotionAsCommands(MotionCommand.PKArenaRecall, MotionStance.NonCombat);
|
|
|
|
var startPos = new Position(Location);
|
|
|
|
// Wait for animation
|
|
var actionChain = new ActionChain();
|
|
|
|
// Then do teleport
|
|
var animLength = DatManager.PortalDat.ReadFromDat<MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.PKArenaRecall);
|
|
actionChain.AddDelaySeconds(animLength);
|
|
|
|
IsBusy = true;
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
IsBusy = false;
|
|
var endPos = new Position(Location);
|
|
if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
|
|
{
|
|
Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
|
|
return;
|
|
}
|
|
|
|
var rng = ThreadSafeRandom.Next(0, pklArenaLocs.Count - 1);
|
|
var loc = pklArenaLocs[rng];
|
|
|
|
Teleport(loc);
|
|
});
|
|
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
public void SendMotionAsCommands(MotionCommand motionCommand, MotionStance motionStance)
|
|
{
|
|
if (FastTick)
|
|
{
|
|
var actionChain = new ActionChain();
|
|
EnqueueMotionAction(actionChain, new List<MotionCommand>() { motionCommand }, 1.0f, motionStance);
|
|
actionChain.EnqueueChain();
|
|
}
|
|
else
|
|
{
|
|
var motion = new Motion(motionStance, MotionCommand.Ready);
|
|
motion.MotionState.AddCommand(this, motionCommand);
|
|
EnqueueBroadcastMotion(motion);
|
|
}
|
|
}
|
|
|
|
public DateTime LastTeleportTime;
|
|
|
|
/// <summary>
|
|
/// This is not thread-safe. Consider using WorldManager.ThreadSafeTeleport() instead if you're calling this from a multi-threaded subsection.
|
|
/// </summary>
|
|
public void Teleport(Position _newPosition, bool fromPortal = false)
|
|
{
|
|
var newPosition = new Position(_newPosition);
|
|
//newPosition.PositionZ += 0.005f;
|
|
newPosition.PositionZ += 0.005f * (ObjScale ?? 1.0f);
|
|
|
|
//Console.WriteLine($"{Name}.Teleport() - Sending to {newPosition.ToLOCString()}");
|
|
|
|
// Check currentFogColor set for player. If LandblockManager.GlobalFogColor is set, don't bother checking, dungeons didn't clear like this on retail worlds.
|
|
// if not clear, reset to clear before portaling in case portaling to dungeon (no current way to fast check unloaded landblock for IsDungeon or current FogColor)
|
|
// client doesn't respond to any change inside dungeons, and only queues for change if in dungeon, executing change upon next teleport
|
|
// so if we delay teleport long enough to ensure clear arrives before teleport, we don't get fog carrying over into dungeon.
|
|
|
|
if (currentFogColor.HasValue && currentFogColor != EnvironChangeType.Clear && !LandblockManager.GlobalFogColor.HasValue)
|
|
{
|
|
var delayTelport = new ActionChain();
|
|
delayTelport.AddAction(this, () => ClearFogColor());
|
|
delayTelport.AddDelaySeconds(1);
|
|
delayTelport.AddAction(this, () => WorldManager.ThreadSafeTeleport(this, _newPosition));
|
|
|
|
delayTelport.EnqueueChain();
|
|
|
|
return;
|
|
}
|
|
|
|
Teleporting = true;
|
|
LastTeleportTime = DateTime.UtcNow;
|
|
LastTeleportStartTimestamp = Time.GetUnixTime();
|
|
|
|
if (fromPortal)
|
|
LastPortalTeleportTimestamp = LastTeleportStartTimestamp;
|
|
|
|
Session.Network.EnqueueSend(new GameMessagePlayerTeleport(this));
|
|
|
|
// load quickly, but player can load into landblock before server is finished loading
|
|
|
|
// send a "fake" update position to get the client to start loading asap,
|
|
// also might fix some decal bugs
|
|
var prevLoc = Location;
|
|
Location = newPosition;
|
|
SendUpdatePosition();
|
|
Location = prevLoc;
|
|
|
|
DoTeleportPhysicsStateChanges();
|
|
|
|
// force out of hotspots
|
|
PhysicsObj.report_collision_end(true);
|
|
|
|
if (UnderLifestoneProtection)
|
|
LifestoneProtectionDispel();
|
|
|
|
HandlePreTeleportVisibility(newPosition);
|
|
|
|
UpdatePlayerPosition(new Position(newPosition), true);
|
|
}
|
|
|
|
public void DoPreTeleportHide()
|
|
{
|
|
if (Teleporting) return;
|
|
PlayParticleEffect(PlayScript.Hide, Guid);
|
|
}
|
|
|
|
public void DoTeleportPhysicsStateChanges()
|
|
{
|
|
var broadcastUpdate = false;
|
|
|
|
var oldHidden = Hidden.Value;
|
|
var oldIgnore = IgnoreCollisions.Value;
|
|
var oldReport = ReportCollisions.Value;
|
|
|
|
Hidden = true;
|
|
IgnoreCollisions = true;
|
|
ReportCollisions = false;
|
|
|
|
if (Hidden != oldHidden || IgnoreCollisions != oldIgnore || ReportCollisions != oldReport)
|
|
broadcastUpdate = true;
|
|
|
|
if (broadcastUpdate)
|
|
EnqueueBroadcastPhysicsState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevent message spam
|
|
/// </summary>
|
|
public double? LastPortalTeleportTimestampError;
|
|
|
|
public void OnTeleportComplete()
|
|
{
|
|
if (CurrentLandblock != null && !CurrentLandblock.CreateWorldObjectsCompleted)
|
|
{
|
|
// If the critical landblock resources haven't been loaded yet, we keep the player in the pink bubble state
|
|
// We'll check periodically to see when it's safe to let them materialize in
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(0.1);
|
|
actionChain.AddAction(this, OnTeleportComplete);
|
|
actionChain.EnqueueChain();
|
|
return;
|
|
}
|
|
|
|
// set materialize physics state
|
|
// this takes the player from pink bubbles -> fully materialized
|
|
if (CloakStatus != CloakStatus.On)
|
|
ReportCollisions = true;
|
|
|
|
IgnoreCollisions = false;
|
|
Hidden = false;
|
|
Teleporting = false;
|
|
|
|
CheckMonsters();
|
|
CheckHouse();
|
|
|
|
EnqueueBroadcastPhysicsState();
|
|
|
|
// hijacking this for both start/end on portal teleport
|
|
if (LastTeleportStartTimestamp == LastPortalTeleportTimestamp)
|
|
LastPortalTeleportTimestamp = Time.GetUnixTime();
|
|
}
|
|
|
|
public void SendTeleportedViaMagicMessage(WorldObject itemCaster, Spell spell)
|
|
{
|
|
if (itemCaster == null || itemCaster is Gem)
|
|
Session.Network.EnqueueSend(new GameMessageSystemChat($"You have been teleported.", ChatMessageType.Magic));
|
|
else if (this != itemCaster && !(itemCaster is Gem) && !(itemCaster is Switch) && !(itemCaster.GetProperty(PropertyBool.NpcInteractsSilently) ?? false))
|
|
Session.Network.EnqueueSend(new GameMessageSystemChat($"{itemCaster.Name} teleports you with {spell.Name}.", ChatMessageType.Magic));
|
|
//else if (itemCaster is Gem)
|
|
// Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ITeleported));
|
|
}
|
|
|
|
public void NotifyLandblocks()
|
|
{
|
|
// the original implementations of this were done on landblock heartbeat,
|
|
// with checks for players in the current landblock, as well as adjacent outdoor landblocks
|
|
|
|
// for performance reasons, this is being reimplemented in the reverse manner,
|
|
// with players notifying landblocks of their activity
|
|
|
|
// notify current landblock of player activity
|
|
if (CurrentLandblock != null)
|
|
CurrentLandblock?.SetActive();
|
|
}
|
|
|
|
public const float RunFactor = 1.5f;
|
|
|
|
/// <summary>
|
|
/// Returns the amount of time for player to rotate by the # of degrees
|
|
/// from the input angle, using the omega speed from its MotionTable
|
|
/// </summary>
|
|
public override float GetRotateDelay(float angle)
|
|
{
|
|
return base.GetRotateDelay(angle) / RunFactor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A list of landblocks the player cannot relog directly into
|
|
///
|
|
/// If a regular player logs out in one of these landblocks,
|
|
/// they will be transported back to the lifestone when they log back in.
|
|
/// </summary>
|
|
public static HashSet<ushort> NoLog_Landblocks = new HashSet<ushort>()
|
|
{
|
|
// https://asheron.fandom.com/wiki/Special:Search?query=Lifestone+on+Relog%3A+Yes+
|
|
// https://docs.google.com/spreadsheets/d/122xOw3IKCezaTDjC_hggWSVzYJ_9M_zUUtGEXkwNXfs/edit#gid=846612575
|
|
|
|
0x0002, // Viamontian Garrison
|
|
0x0007, // Town Network
|
|
0x0056, // Augmentation Realm Main Level
|
|
0x005F, // Tanada House of Pancakes (Seasonal)
|
|
0x0067, // PKL Arena
|
|
0x006D, // Augmentation Realm Upper Level
|
|
0x007D, // Augmentation Realm Lower Level
|
|
0x00AB, // Derethian Combat Arena
|
|
0x00AC, // Derethian Combat Arena
|
|
0x00C3, // Blighted Putrid Moarsman Tunnels
|
|
0x00D7, // Jester's Prison
|
|
0x00EA, // Mhoire Armory
|
|
0x015D, // Mountain Cavern
|
|
0x027F, // East Fork Dam Hive
|
|
0x03A7, // Mount Elyrii Hive
|
|
0x5764, // Oubliette of Mhoire Castle
|
|
0x634C, // Tainted Grotto
|
|
0x6544, // Greater Battle Dungeon
|
|
0x6651, // Hoshino Tower
|
|
0x7E04, // Thug Hideout
|
|
0x8A04, // Night Club (Seasonal Anniversary)
|
|
0x8B04, // Frozen Wight Lair
|
|
0x9EE5, // Northwatch Castle Black Market
|
|
0xB5F0, // Aerfalle's Sanctum
|
|
0xF92F, // Freebooter Keep Black Market
|
|
0x00B0, // Colosseum Arena One
|
|
0x00B1, // Colosseum Arena Two
|
|
0x00B2, // Colosseum Arena Three
|
|
0x00B3, // Colosseum Arena Four
|
|
0x00B4, // Colosseum Arena Five
|
|
0x00B6, // Colosseum Arena Mini-Bosses
|
|
0x5960, // Gauntlet Arena One (Celestial Hand)
|
|
0x5961, // Gauntlet Arena Two (Celestial Hand)
|
|
0x5962, // Gauntlet Arena One (Eldritch Web)
|
|
0x5963, // Gauntlet Arena Two (Eldritch Web)
|
|
0x5964, // Gauntlet Arena One (Radiant Blood)
|
|
0x5965, // Gauntlet Arena Two (Radiant Blood)
|
|
};
|
|
|
|
/// <summary>
|
|
/// Called when a player first logs in
|
|
/// </summary>
|
|
public static void HandleNoLogLandblock(Biota biota, out bool playerWasMovedFromNoLogLandblock)
|
|
{
|
|
playerWasMovedFromNoLogLandblock = false;
|
|
|
|
if (biota.WeenieType == WeenieType.Sentinel || biota.WeenieType == WeenieType.Admin) return;
|
|
|
|
if (!biota.PropertiesPosition.TryGetValue(PositionType.Location, out var location))
|
|
return;
|
|
|
|
var landblock = (ushort)(location.ObjCellId >> 16);
|
|
|
|
if (!NoLog_Landblocks.Contains(landblock))
|
|
return;
|
|
|
|
if (!biota.PropertiesPosition.TryGetValue(PositionType.Sanctuary, out var lifestone))
|
|
return;
|
|
|
|
location.ObjCellId = lifestone.ObjCellId;
|
|
location.PositionX = lifestone.PositionX;
|
|
location.PositionY = lifestone.PositionY;
|
|
location.PositionZ = lifestone.PositionZ;
|
|
location.RotationX = lifestone.RotationX;
|
|
location.RotationY = lifestone.RotationY;
|
|
location.RotationZ = lifestone.RotationZ;
|
|
location.RotationW = lifestone.RotationW;
|
|
|
|
playerWasMovedFromNoLogLandblock = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|