diff options
author | UbitUmarov | 2019-03-28 00:02:24 +0000 |
---|---|---|
committer | UbitUmarov | 2019-03-28 00:02:24 +0000 |
commit | 1847a42a861d6a0f575c56f566b947dfb21c1f03 (patch) | |
tree | 5874efa262c9d8b651d35ef77230e67c8f14b58e | |
parent | try another way (diff) | |
download | opensim-SC-1847a42a861d6a0f575c56f566b947dfb21c1f03.zip opensim-SC-1847a42a861d6a0f575c56f566b947dfb21c1f03.tar.gz opensim-SC-1847a42a861d6a0f575c56f566b947dfb21c1f03.tar.bz2 opensim-SC-1847a42a861d6a0f575c56f566b947dfb21c1f03.tar.xz |
changes on teleports v7
3 files changed, 84 insertions, 93 deletions
diff --git a/OpenSim/Framework/ChildAgentDataUpdate.cs b/OpenSim/Framework/ChildAgentDataUpdate.cs index ee5007a..2d00296 100644 --- a/OpenSim/Framework/ChildAgentDataUpdate.cs +++ b/OpenSim/Framework/ChildAgentDataUpdate.cs | |||
@@ -398,7 +398,8 @@ namespace OpenSim.Framework | |||
398 | // Scripted | 398 | // Scripted |
399 | public ControllerData[] Controllers; | 399 | public ControllerData[] Controllers; |
400 | 400 | ||
401 | public string CallbackURI; | 401 | public string CallbackURI; // to remove |
402 | public string NewCallbackURI; | ||
402 | 403 | ||
403 | // These two must have the same Count | 404 | // These two must have the same Count |
404 | public List<ISceneObject> AttachmentObjects; | 405 | public List<ISceneObject> AttachmentObjects; |
@@ -528,6 +529,9 @@ namespace OpenSim.Framework | |||
528 | if ((CallbackURI != null) && (!CallbackURI.Equals(""))) | 529 | if ((CallbackURI != null) && (!CallbackURI.Equals(""))) |
529 | args["callback_uri"] = OSD.FromString(CallbackURI); | 530 | args["callback_uri"] = OSD.FromString(CallbackURI); |
530 | 531 | ||
532 | if ((NewCallbackURI != null) && (!NewCallbackURI.Equals(""))) | ||
533 | args["cb_uri"] = OSD.FromString(NewCallbackURI); | ||
534 | |||
531 | // Attachment objects for fatpack messages | 535 | // Attachment objects for fatpack messages |
532 | if (AttachmentObjects != null) | 536 | if (AttachmentObjects != null) |
533 | { | 537 | { |
@@ -811,12 +815,7 @@ namespace OpenSim.Framework | |||
811 | } | 815 | } |
812 | // end of code to remove | 816 | // end of code to remove |
813 | } | 817 | } |
814 | /* moved above | 818 | |
815 | if (args.ContainsKey("packed_appearance") && (args["packed_appearance"]).Type == OSDType.Map) | ||
816 | Appearance = new AvatarAppearance((OSDMap)args["packed_appearance"]); | ||
817 | else | ||
818 | m_log.WarnFormat("[CHILDAGENTDATAUPDATE] No packed appearance"); | ||
819 | */ | ||
820 | if ((args["controllers"] != null) && (args["controllers"]).Type == OSDType.Array) | 819 | if ((args["controllers"] != null) && (args["controllers"]).Type == OSDType.Array) |
821 | { | 820 | { |
822 | OSDArray controls = (OSDArray)(args["controllers"]); | 821 | OSDArray controls = (OSDArray)(args["controllers"]); |
@@ -834,6 +833,9 @@ namespace OpenSim.Framework | |||
834 | if (args["callback_uri"] != null) | 833 | if (args["callback_uri"] != null) |
835 | CallbackURI = args["callback_uri"].AsString(); | 834 | CallbackURI = args["callback_uri"].AsString(); |
836 | 835 | ||
836 | if (args["cb_uri"] != null) | ||
837 | NewCallbackURI = args["cb_uri"].AsString(); | ||
838 | |||
837 | // Attachment objects | 839 | // Attachment objects |
838 | if (args["attach_objects"] != null && args["attach_objects"].Type == OSDType.Array) | 840 | if (args["attach_objects"] != null && args["attach_objects"].Type == OSDType.Array) |
839 | { | 841 | { |
diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 9471c90..09b0dd6 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs | |||
@@ -54,15 +54,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
54 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 54 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
55 | private static readonly string LogHeader = "[ENTITY TRANSFER MODULE]"; | 55 | private static readonly string LogHeader = "[ENTITY TRANSFER MODULE]"; |
56 | 56 | ||
57 | public const int DefaultMaxTransferDistance = 4095; | ||
58 | public const bool WaitForAgentArrivedAtDestinationDefault = true; | 57 | public const bool WaitForAgentArrivedAtDestinationDefault = true; |
59 | 58 | ||
60 | /// <summary> | 59 | /// <summary> |
61 | /// The maximum distance, in standard region units (256m) that an agent is allowed to transfer. | ||
62 | /// </summary> | ||
63 | public int MaxTransferDistance { get; set; } | ||
64 | |||
65 | /// <summary> | ||
66 | /// If true then on a teleport, the source region waits for a callback from the destination region. If | 60 | /// If true then on a teleport, the source region waits for a callback from the destination region. If |
67 | /// a callback fails to arrive within a set time then the user is pulled back into the source region. | 61 | /// a callback fails to arrive within a set time then the user is pulled back into the source region. |
68 | /// </summary> | 62 | /// </summary> |
@@ -227,11 +221,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
227 | WaitForAgentArrivedAtDestination | 221 | WaitForAgentArrivedAtDestination |
228 | = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault); | 222 | = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault); |
229 | 223 | ||
230 | MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance); | ||
231 | } | ||
232 | else | ||
233 | { | ||
234 | MaxTransferDistance = DefaultMaxTransferDistance; | ||
235 | } | 224 | } |
236 | 225 | ||
237 | m_entityTransferStateMachine = new EntityTransferStateMachine(this); | 226 | m_entityTransferStateMachine = new EntityTransferStateMachine(this); |
@@ -640,29 +629,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
640 | } | 629 | } |
641 | 630 | ||
642 | /// <summary> | 631 | /// <summary> |
643 | /// Determines whether this instance is within the max transfer distance. | ||
644 | /// </summary> | ||
645 | /// <param name="sourceRegion"></param> | ||
646 | /// <param name="destRegion"></param> | ||
647 | /// <returns> | ||
648 | /// <c>true</c> if this instance is within max transfer distance; otherwise, <c>false</c>. | ||
649 | /// </returns> | ||
650 | private bool IsWithinMaxTeleportDistance(RegionInfo sourceRegion, GridRegion destRegion) | ||
651 | { | ||
652 | if(MaxTransferDistance == 0) | ||
653 | return true; | ||
654 | |||
655 | // m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Source co-ords are x={0} y={1}", curRegionX, curRegionY); | ||
656 | // | ||
657 | // m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Final dest is x={0} y={1} {2}@{3}", | ||
658 | // destRegionX, destRegionY, finalDestination.RegionID, finalDestination.ServerURI); | ||
659 | |||
660 | // Insanely, RegionLoc on RegionInfo is the 256m map co-ord whilst GridRegion.RegionLoc is the raw meters position. | ||
661 | return Math.Abs(sourceRegion.RegionLocX - destRegion.RegionCoordX) <= MaxTransferDistance | ||
662 | && Math.Abs(sourceRegion.RegionLocY - destRegion.RegionCoordY) <= MaxTransferDistance; | ||
663 | } | ||
664 | |||
665 | /// <summary> | ||
666 | /// Wraps DoTeleportInternal() and manages the transfer state. | 632 | /// Wraps DoTeleportInternal() and manages the transfer state. |
667 | /// </summary> | 633 | /// </summary> |
668 | public void DoTeleport( | 634 | public void DoTeleport( |
@@ -722,18 +688,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
722 | 688 | ||
723 | RegionInfo sourceRegion = sp.Scene.RegionInfo; | 689 | RegionInfo sourceRegion = sp.Scene.RegionInfo; |
724 | 690 | ||
725 | if (!IsWithinMaxTeleportDistance(sourceRegion, finalDestination)) | ||
726 | { | ||
727 | sp.ControllingClient.SendTeleportFailed( | ||
728 | string.Format( | ||
729 | "Can't teleport to {0} ({1},{2}) from {3} ({4},{5}), destination is more than {6} regions way", | ||
730 | finalDestination.RegionName, finalDestination.RegionCoordX, finalDestination.RegionCoordY, | ||
731 | sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY, | ||
732 | MaxTransferDistance)); | ||
733 | |||
734 | return; | ||
735 | } | ||
736 | |||
737 | ulong destinationHandle = finalDestination.RegionHandle; | 691 | ulong destinationHandle = finalDestination.RegionHandle; |
738 | 692 | ||
739 | // Let's do DNS resolution only once in this process, please! | 693 | // Let's do DNS resolution only once in this process, please! |
@@ -1175,7 +1129,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
1175 | 1129 | ||
1176 | agent.SenderWantsToWaitForRoot = true; | 1130 | agent.SenderWantsToWaitForRoot = true; |
1177 | 1131 | ||
1178 | //SetCallbackURL(agent, sp.Scene.RegionInfo); | 1132 | if(!sp.IsInLocalTransit) |
1133 | SetNewCallbackURL(agent, sp.Scene.RegionInfo); | ||
1179 | 1134 | ||
1180 | // Reset the do not close flag. This must be done before the destination opens child connections (here | 1135 | // Reset the do not close flag. This must be done before the destination opens child connections (here |
1181 | // triggered by UpdateAgent) to avoid race conditions. However, we also want to reset it as late as possible | 1136 | // triggered by UpdateAgent) to avoid race conditions. However, we also want to reset it as late as possible |
@@ -1224,25 +1179,29 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
1224 | sp.closeAllChildAgents(); | 1179 | sp.closeAllChildAgents(); |
1225 | else | 1180 | else |
1226 | sp.CloseChildAgents(childRegionsToClose); | 1181 | sp.CloseChildAgents(childRegionsToClose); |
1182 | } | ||
1227 | 1183 | ||
1228 | // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone | 1184 | // if far jump we do need to close anyways |
1229 | // goes by HG hook | 1185 | if (NeedsClosing(reg, OutSideViewRange)) |
1230 | if (NeedsClosing(reg, OutSideViewRange)) | 1186 | { |
1187 | int count = 60; | ||
1188 | do | ||
1231 | { | 1189 | { |
1232 | if (!sp.Scene.IncomingPreCloseClient(sp)) | 1190 | Thread.Sleep(250); |
1233 | { | 1191 | if(sp.IsDeleted) |
1234 | sp.IsInTransit = false; | ||
1235 | return; | 1192 | return; |
1236 | } | 1193 | } while (--count > 0); |
1237 | 1194 | ||
1238 | // viewers and target region take extra time to process the tp | 1195 | if (!sp.IsDeleted) |
1239 | Thread.Sleep(15000); | 1196 | { |
1240 | m_log.DebugFormat( | 1197 | m_log.DebugFormat( |
1241 | "[ENTITY TRANSFER MODULE]: Closing agent {0} in {1} after teleport", sp.Name, Scene.Name); | 1198 | "[ENTITY TRANSFER MODULE]: Closing agent {0} in {1} after teleport timeout", sp.Name, Scene.Name); |
1242 | sp.Scene.CloseAgent(sp.UUID, false); | 1199 | sp.Scene.CloseAgent(sp.UUID, false); |
1243 | } | 1200 | } |
1244 | sp.IsInTransit = false; | 1201 | return; |
1245 | } | 1202 | } |
1203 | // otherwise keep child | ||
1204 | sp.IsInTransit = false; | ||
1246 | } | 1205 | } |
1247 | 1206 | ||
1248 | /// <summary> | 1207 | /// <summary> |
@@ -1313,6 +1272,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
1313 | { | 1272 | { |
1314 | agent.CallbackURI = region.ServerURI + "agent/" + agent.AgentID.ToString() + "/" + region.RegionID.ToString() + "/release/"; | 1273 | agent.CallbackURI = region.ServerURI + "agent/" + agent.AgentID.ToString() + "/" + region.RegionID.ToString() + "/release/"; |
1315 | 1274 | ||
1275 | //m_log.DebugFormat( | ||
1276 | // "[ENTITY TRANSFER MODULE]: Set release callback URL to {0} in {1}", | ||
1277 | // agent.CallbackURI, region.RegionName); | ||
1278 | } | ||
1279 | |||
1280 | protected virtual void SetNewCallbackURL(AgentData agent, RegionInfo region) | ||
1281 | { | ||
1282 | agent.NewCallbackURI = region.ServerURI + "agent/" + agent.AgentID.ToString() + "/" + region.RegionID.ToString() + "/release/"; | ||
1283 | |||
1316 | m_log.DebugFormat( | 1284 | m_log.DebugFormat( |
1317 | "[ENTITY TRANSFER MODULE]: Set release callback URL to {0} in {1}", | 1285 | "[ENTITY TRANSFER MODULE]: Set release callback URL to {0} in {1}", |
1318 | agent.CallbackURI, region.RegionName); | 1286 | agent.CallbackURI, region.RegionName); |
@@ -2488,7 +2456,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | |||
2488 | 2456 | ||
2489 | public void AgentArrivedAtDestination(UUID id) | 2457 | public void AgentArrivedAtDestination(UUID id) |
2490 | { | 2458 | { |
2491 | m_entityTransferStateMachine.SetAgentArrivedAtDestination(id); | 2459 | ScenePresence sp = Scene.GetScenePresence(id); |
2460 | if(sp == null || sp.IsDeleted || !sp.IsInTransit) | ||
2461 | return; | ||
2462 | |||
2463 | Scene.CloseAgent(sp.UUID, false); | ||
2464 | m_entityTransferStateMachine.ResetFromTransit(id); // this needs cleanup | ||
2465 | //m_entityTransferStateMachine.SetAgentArrivedAtDestination(id); | ||
2492 | } | 2466 | } |
2493 | 2467 | ||
2494 | #endregion | 2468 | #endregion |
diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 2145fcd..a95036c 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs | |||
@@ -457,9 +457,10 @@ namespace OpenSim.Region.Framework.Scenes | |||
457 | #region For teleports and crossings callbacks | 457 | #region For teleports and crossings callbacks |
458 | 458 | ||
459 | /// <summary> | 459 | /// <summary> |
460 | /// In the V1 teleport protocol, the destination simulator sends ReleaseAgent to this address. | 460 | /// the destination simulator sends ReleaseAgent to this address, for very long range tps, HG. |
461 | /// </summary> | 461 | /// </summary> |
462 | private string m_callbackURI; | 462 | private string m_callbackURI; // to remove with v1 support |
463 | private string m_newCallbackURI; | ||
463 | 464 | ||
464 | /// <summary> | 465 | /// <summary> |
465 | /// Records the region from which this presence originated, if not from login. | 466 | /// Records the region from which this presence originated, if not from login. |
@@ -2155,28 +2156,6 @@ namespace OpenSim.Region.Framework.Scenes | |||
2155 | m_log.DebugFormat("[CompleteMovement]: Missing COF for {0} is {1}", client.AgentId, COF); | 2156 | m_log.DebugFormat("[CompleteMovement]: Missing COF for {0} is {1}", client.AgentId, COF); |
2156 | } | 2157 | } |
2157 | 2158 | ||
2158 | if (!string.IsNullOrEmpty(m_callbackURI)) | ||
2159 | { | ||
2160 | // We cannot sleep here since this would hold up the inbound packet processing thread, as | ||
2161 | // CompleteMovement() is executed synchronously. However, it might be better to delay the release | ||
2162 | // here until we know for sure that the agent is active in this region. Sending AgentMovementComplete | ||
2163 | // is not enough for Imprudence clients - there appears to be a small delay (<200ms, <500ms) until they regard this | ||
2164 | // region as the current region, meaning that a close sent before then will fail the teleport. | ||
2165 | // System.Threading.Thread.Sleep(2000); | ||
2166 | |||
2167 | m_log.DebugFormat( | ||
2168 | "[SCENE PRESENCE]: Releasing {0} {1} with callback to {2}", | ||
2169 | client.Name, client.AgentId, m_callbackURI); | ||
2170 | |||
2171 | UUID originID; | ||
2172 | |||
2173 | lock (m_originRegionIDAccessLock) | ||
2174 | originID = m_originRegionID; | ||
2175 | |||
2176 | Scene.SimulationService.ReleaseAgent(originID, UUID, m_callbackURI); | ||
2177 | m_callbackURI = null; | ||
2178 | //m_log.DebugFormat("[CompleteMovement] ReleaseAgent: {0}ms", Util.EnvironmentTickCountSubtract(ts)); | ||
2179 | } | ||
2180 | } | 2159 | } |
2181 | // Tell the client that we're totally ready | 2160 | // Tell the client that we're totally ready |
2182 | ControllingClient.SendRegionHandshake(); | 2161 | ControllingClient.SendRegionHandshake(); |
@@ -2381,6 +2360,37 @@ namespace OpenSim.Region.Framework.Scenes | |||
2381 | 2360 | ||
2382 | //m_log.DebugFormat("[CompleteMovement] SendInitialDataToMe: {0}ms", Util.EnvironmentTickCountSubtract(ts)); | 2361 | //m_log.DebugFormat("[CompleteMovement] SendInitialDataToMe: {0}ms", Util.EnvironmentTickCountSubtract(ts)); |
2383 | 2362 | ||
2363 | if (!string.IsNullOrEmpty(m_callbackURI)) | ||
2364 | { | ||
2365 | m_log.DebugFormat( | ||
2366 | "[SCENE PRESENCE]: Releasing {0} {1} with old callback to {2}", | ||
2367 | client.Name, client.AgentId, m_callbackURI); | ||
2368 | |||
2369 | UUID originID; | ||
2370 | |||
2371 | lock (m_originRegionIDAccessLock) | ||
2372 | originID = m_originRegionID; | ||
2373 | |||
2374 | Scene.SimulationService.ReleaseAgent(originID, UUID, m_callbackURI); | ||
2375 | m_callbackURI = null; | ||
2376 | //m_log.DebugFormat("[CompleteMovement] ReleaseAgent: {0}ms", Util.EnvironmentTickCountSubtract(ts)); | ||
2377 | } | ||
2378 | else if (!string.IsNullOrEmpty(m_newCallbackURI)) | ||
2379 | { | ||
2380 | m_log.DebugFormat( | ||
2381 | "[SCENE PRESENCE]: Releasing {0} {1} with callback to {2}", | ||
2382 | client.Name, client.AgentId, m_newCallbackURI); | ||
2383 | |||
2384 | UUID originID; | ||
2385 | |||
2386 | lock (m_originRegionIDAccessLock) | ||
2387 | originID = m_originRegionID; | ||
2388 | |||
2389 | Scene.SimulationService.ReleaseAgent(originID, UUID, m_newCallbackURI); | ||
2390 | m_newCallbackURI = null; | ||
2391 | //m_log.DebugFormat("[CompleteMovement] ReleaseAgent: {0}ms", Util.EnvironmentTickCountSubtract(ts)); | ||
2392 | } | ||
2393 | |||
2384 | if (openChildAgents) | 2394 | if (openChildAgents) |
2385 | { | 2395 | { |
2386 | IFriendsModule friendsModule = m_scene.RequestModuleInterface<IFriendsModule>(); | 2396 | IFriendsModule friendsModule = m_scene.RequestModuleInterface<IFriendsModule>(); |
@@ -4589,12 +4599,15 @@ namespace OpenSim.Region.Framework.Scenes | |||
4589 | byebyeRegions.Add(handle); | 4599 | byebyeRegions.Add(handle); |
4590 | else if(handle == curRegionHandle) | 4600 | else if(handle == curRegionHandle) |
4591 | { | 4601 | { |
4602 | continue; | ||
4603 | /* | ||
4592 | RegionInfo curreg = m_scene.RegionInfo; | 4604 | RegionInfo curreg = m_scene.RegionInfo; |
4593 | if (Util.IsOutsideView(255, curreg.RegionLocX, newRegionX, curreg.RegionLocY, newRegionY, | 4605 | if (Util.IsOutsideView(255, curreg.RegionLocX, newRegionX, curreg.RegionLocY, newRegionY, |
4594 | (int)curreg.RegionSizeX, (int)curreg.RegionSizeX, newRegionSizeX, newRegionSizeY)) | 4606 | (int)curreg.RegionSizeX, (int)curreg.RegionSizeX, newRegionSizeX, newRegionSizeY)) |
4595 | { | 4607 | { |
4596 | byebyeRegions.Add(handle); | 4608 | byebyeRegions.Add(handle); |
4597 | } | 4609 | } |
4610 | */ | ||
4598 | } | 4611 | } |
4599 | else | 4612 | else |
4600 | { | 4613 | { |
@@ -4774,6 +4787,7 @@ namespace OpenSim.Region.Framework.Scenes | |||
4774 | public void CopyTo(AgentData cAgent, bool isCrossUpdate) | 4787 | public void CopyTo(AgentData cAgent, bool isCrossUpdate) |
4775 | { | 4788 | { |
4776 | cAgent.CallbackURI = m_callbackURI; | 4789 | cAgent.CallbackURI = m_callbackURI; |
4790 | cAgent.NewCallbackURI = m_newCallbackURI; | ||
4777 | 4791 | ||
4778 | cAgent.AgentID = UUID; | 4792 | cAgent.AgentID = UUID; |
4779 | cAgent.RegionID = Scene.RegionInfo.RegionID; | 4793 | cAgent.RegionID = Scene.RegionInfo.RegionID; |
@@ -4860,9 +4874,10 @@ namespace OpenSim.Region.Framework.Scenes | |||
4860 | private void CopyFrom(AgentData cAgent) | 4874 | private void CopyFrom(AgentData cAgent) |
4861 | { | 4875 | { |
4862 | m_callbackURI = cAgent.CallbackURI; | 4876 | m_callbackURI = cAgent.CallbackURI; |
4863 | // m_log.DebugFormat( | 4877 | m_newCallbackURI = cAgent.NewCallbackURI; |
4864 | // "[SCENE PRESENCE]: Set callback for {0} in {1} to {2} in CopyFrom()", | 4878 | // m_log.DebugFormat( |
4865 | // Name, m_scene.RegionInfo.RegionName, m_callbackURI); | 4879 | // "[SCENE PRESENCE]: Set callback for {0} in {1} to {2} in CopyFrom()", |
4880 | // Name, m_scene.RegionInfo.RegionName, m_callbackURI); | ||
4866 | 4881 | ||
4867 | GodController.SetState(cAgent.GodData); | 4882 | GodController.SetState(cAgent.GodData); |
4868 | 4883 | ||