1212using Intersect . Framework . Core . GameObjects . PlayerClass ;
1313using Intersect . Framework . Core . GameObjects . Resources ;
1414using Intersect . Framework . Core . GameObjects . Variables ;
15- using Intersect . Framework . Core . Network . Packets . Security ;
1615using Intersect . Framework . Core . Security ;
1716using Intersect . GameObjects ;
1817using Intersect . Models ;
2726using Intersect . Server . General ;
2827using Intersect . Server . Localization ;
2928using Intersect . Server . Maps ;
30- using Intersect . Utilities ;
3129using Microsoft . Extensions . Logging ;
3230using Newtonsoft . Json ;
3331
@@ -53,6 +51,19 @@ public static void ResetMetrics()
5351 SentBytes = 0 ;
5452 }
5553
54+ //Tracks last sent stats per entity so we can skip redundant packet updates.
55+ private sealed record EntityStatsSnapshot ( int [ ] Stats ) ;
56+
57+ private static readonly Dictionary < Guid , EntityStatsSnapshot > _lastEntityStatsSnapshot = new Dictionary < Guid , EntityStatsSnapshot > ( ) ;
58+
59+ // Tracks last sent vitals per entity for map‑wide batch updates.
60+ private sealed record EntityVitalSnapshot ( long [ ] Vitals , long [ ] MaxVitals , long CombatTimeRemaining ) ;
61+
62+ private static readonly Dictionary < Guid , EntityVitalSnapshot > _lastEntityVitalSnapshot = new Dictionary < Guid , EntityVitalSnapshot > ( ) ;
63+
64+ // Tracks last sent equipment item ids per player.
65+ private static readonly Dictionary < Guid , Guid [ ] > _lastPlayerEquipmentSnapshot = new Dictionary < Guid , Guid [ ] > ( ) ;
66+
5667 //PingPacket
5768 public static void SendPing ( Client client , bool request = true )
5869 {
@@ -640,24 +651,28 @@ public static void SendNpcAggressionTo(Player player, Npc npc)
640651 public static void SendEntityLeaveMap ( Entity en , Guid leftMap )
641652 {
642653 SendDataToMapInstance ( leftMap , en . MapInstanceId , new EntityLeftPacket ( en . Id , en . GetEntityType ( ) , en . MapId ) ) ;
654+ ClearEntitySnapshotCache ( en ) ;
643655 }
644656
645657 //EntityLeftPacket
646658 public static void SendEntityLeave ( Entity en )
647659 {
648660 SendDataToProximityOnMapInstance ( en . MapId , en . MapInstanceId , new EntityLeftPacket ( en . Id , en . GetEntityType ( ) , en . MapId ) ) ;
661+ ClearEntitySnapshotCache ( en ) ;
649662 }
650663
651664 //EntityLeftPacket
652665 public static void SendEntityLeaveLayer ( Entity en , Guid mapInstanceId )
653666 {
654667 SendDataToProximityOnMapInstance ( en . MapId , mapInstanceId , new EntityLeftPacket ( en . Id , en . GetEntityType ( ) , en . MapId ) ) ;
668+ ClearEntitySnapshotCache ( en ) ;
655669 }
656670
657671 //EntityLeftPacket
658672 public static void SendEntityLeaveInstanceOfMap ( Entity en , Guid mapId , Guid mapInstanceId )
659673 {
660674 SendDataToProximityOnMapInstance ( mapId , mapInstanceId , new EntityLeftPacket ( en . Id , en . GetEntityType ( ) , en . MapId ) ) ;
675+ ClearEntitySnapshotCache ( en ) ;
661676 }
662677
663678 //EntityLeavePacket
@@ -772,6 +787,16 @@ public static void CacheGameDataPacket()
772787 CachedGameDataPacket = new GameDataPacket ( gameObjects . ToArray ( ) , CustomColors . Json ( ) ) ;
773788 }
774789
790+ private static void ClearEntitySnapshotCache ( Entity en )
791+ {
792+ if ( en != null )
793+ {
794+ _lastEntityStatsSnapshot . Remove ( en . Id ) ;
795+ _lastEntityVitalSnapshot . Remove ( en . Id ) ;
796+ _lastPlayerEquipmentSnapshot . Remove ( en . Id ) ;
797+ }
798+ }
799+
775800 /// <summary>
776801 /// Sends a global chat message to every user online.
777802 /// </summary>
@@ -945,22 +970,56 @@ public static EntityVitalsPacket GenerateEntityVitalsPacket(Entity en)
945970 //EntityVitalsPacket
946971 public static void SendMapEntityVitalUpdate ( MapController map , Entity [ ] entities , Guid mapInstanceId )
947972 {
948- // Generate a list of vitals to send to our users!
973+ if ( map == null || entities == null || entities . Length == 0 )
974+ {
975+ return ;
976+ }
977+
949978 var data = new List < EntityVitalData > ( ) ;
979+
950980 foreach ( var entity in entities )
951981 {
952- data . Add ( new EntityVitalData ( )
982+ if ( entity == null )
953983 {
954- Id = entity . Id ,
955- Type = entity . GetEntityType ( ) ,
956- Vitals = entity . GetVitals ( ) ,
957- MaxVitals = entity . GetMaxVitals ( ) ,
958- CombatTimeRemaining = entity . CombatTimer - Timing . Global . Milliseconds
959- } ) ;
984+ continue ;
985+ }
986+
987+ var vitals = entity . GetVitals ( ) ;
988+ var maxVitals = entity . GetMaxVitals ( ) ;
989+ var combatRemaining = entity . CombatTimer - Timing . Global . Milliseconds ;
990+
991+ var newSnapshot = new EntityVitalSnapshot ( vitals , maxVitals , combatRemaining ) ;
992+
993+ if ( _lastEntityVitalSnapshot . TryGetValue ( entity . Id , out var oldSnapshot ) )
994+ {
995+ if ( oldSnapshot . Vitals . SequenceEqual ( newSnapshot . Vitals ) &&
996+ oldSnapshot . MaxVitals . SequenceEqual ( newSnapshot . MaxVitals ) &&
997+ oldSnapshot . CombatTimeRemaining == newSnapshot . CombatTimeRemaining )
998+ {
999+ // Nothing relevant changed, skip this entity.
1000+ continue ;
1001+ }
1002+ }
1003+
1004+ _lastEntityVitalSnapshot [ entity . Id ] = newSnapshot ;
1005+
1006+ data . Add (
1007+ new EntityVitalData
1008+ {
1009+ Id = entity . Id ,
1010+ Type = entity . GetEntityType ( ) ,
1011+ Vitals = vitals ,
1012+ MaxVitals = maxVitals ,
1013+ CombatTimeRemaining = combatRemaining
1014+ }
1015+ ) ;
9601016 }
9611017
962- // Send the data to the surroundings!
963- SendDataToProximityOnMapInstance ( map . Id , mapInstanceId , new MapEntityVitalsPacket ( map . Id , data . ToArray ( ) ) ) ;
1018+ // Only send if at least one entity actually changed.
1019+ if ( data . Count > 0 )
1020+ {
1021+ SendDataToProximityOnMapInstance ( map . Id , mapInstanceId , new MapEntityVitalsPacket ( map . Id , data . ToArray ( ) ) ) ;
1022+ }
9641023 }
9651024
9661025 public static void SendMapEntityStatusUpdate ( MapController map , Entity [ ] entities , Guid mapInstanceId )
@@ -989,7 +1048,26 @@ public static void SendEntityStats(Entity en)
9891048 return ;
9901049 }
9911050
992- SendDataToProximityOnMapInstance ( en . MapId , en . MapInstanceId , GenerateEntityStatsPacket ( en ) , null , TransmissionMode . Any ) ;
1051+ // Build current stats array (same logic as GenerateEntityStatsPacket)
1052+ var stats = new int [ Enum . GetValues < Stat > ( ) . Length ] ;
1053+ for ( var i = 0 ; i < stats . Length ; i ++ )
1054+ {
1055+ stats [ i ] = en . Stat [ i ] . Value ( ) ;
1056+ }
1057+
1058+ var newSnapshot = new EntityStatsSnapshot ( stats ) ;
1059+
1060+ if ( _lastEntityStatsSnapshot . TryGetValue ( en . Id , out var oldSnapshot ) )
1061+ {
1062+ if ( oldSnapshot . Stats . SequenceEqual ( newSnapshot . Stats ) )
1063+ {
1064+ // No visible stat changes; skip the packet update entirely.
1065+ return ;
1066+ }
1067+ }
1068+
1069+ _lastEntityStatsSnapshot [ en . Id ] = newSnapshot ;
1070+ SendDataToProximityOnMapInstance ( en . MapId , en . MapInstanceId , new EntityStatsPacket ( en . Id , en . GetEntityType ( ) , en . MapId , stats ) , null , TransmissionMode . Any ) ;
9931071 }
9941072
9951073 //EntityVitalsPacket
@@ -1295,6 +1373,36 @@ public static void SendPlayerEquipmentTo(Player forPlayer, Player en)
12951373 //EquipmentPacket
12961374 public static void SendPlayerEquipmentToProximity ( Player en )
12971375 {
1376+ if ( en == null )
1377+ {
1378+ return ;
1379+ }
1380+
1381+ var slots = Options . Instance . Equipment . Slots . Count ;
1382+ var equipment = new Guid [ slots ] ;
1383+
1384+ for ( var i = 0 ; i < slots ; i ++ )
1385+ {
1386+ if ( en . Equipment [ i ] == - 1 || en . Items [ en . Equipment [ i ] ] . ItemId == Guid . Empty )
1387+ {
1388+ equipment [ i ] = Guid . Empty ;
1389+ }
1390+ else
1391+ {
1392+ equipment [ i ] = en . Items [ en . Equipment [ i ] ] . ItemId ;
1393+ }
1394+ }
1395+
1396+ if ( _lastPlayerEquipmentSnapshot . TryGetValue ( en . Id , out var last ) )
1397+ {
1398+ if ( last . Length == equipment . Length && last . SequenceEqual ( equipment ) )
1399+ {
1400+ // Nothing changed, skip entirely.
1401+ return ;
1402+ }
1403+ }
1404+
1405+ _lastPlayerEquipmentSnapshot [ en . Id ] = equipment ;
12981406 SendDataToProximityOnMapInstance ( en . MapId , en . MapInstanceId , GenerateEquipmentPacket ( null , en ) , null , TransmissionMode . Any ) ;
12991407 SendPlayerEquipmentTo ( en , en ) ;
13001408 }
0 commit comments