diff --git a/Lib/AeroMessages b/Lib/AeroMessages
index 6712d05e..61de410c 160000
--- a/Lib/AeroMessages
+++ b/Lib/AeroMessages
@@ -1 +1 @@
-Subproject commit 6712d05e5e13516113bb783e63a0725ed0fe9421
+Subproject commit 61de410c85ade361fd6dd20305c932e02302adfb
diff --git a/UdpHosts/GameServer/App.Default.config b/UdpHosts/GameServer/App.Default.config
index e4351436..7f06c118 100644
--- a/UdpHosts/GameServer/App.Default.config
+++ b/UdpHosts/GameServer/App.Default.config
@@ -5,6 +5,7 @@
+
diff --git a/UdpHosts/GameServer/Controllers/Character/BaseController.cs b/UdpHosts/GameServer/Controllers/Character/BaseController.cs
index 0fe453fa..333d0974 100644
--- a/UdpHosts/GameServer/Controllers/Character/BaseController.cs
+++ b/UdpHosts/GameServer/Controllers/Character/BaseController.cs
@@ -12,12 +12,14 @@
using GameServer.Data;
using GameServer.Data.SDB;
using GameServer.Data.SDB.Records.customdata;
+using GameServer.Entities;
using GameServer.Entities.Character;
using GameServer.Entities.Turret;
using GameServer.Entities.Vehicle;
using GameServer.Enums.GSS.Character;
using GameServer.Extensions;
using GameServer.Packets;
+using GameServer.Systems.Encounters;
using Serilog;
using static AeroMessages.GSS.V66.Character.Command.NonDevDebugCommand;
using LoadoutVisualType = AeroMessages.GSS.V66.Character.LoadoutConfig_Visual.LoadoutVisualType;
@@ -371,12 +373,13 @@ public void ExitAttachmentRequest(INetworkClient client, IPlayer player, ulong e
}
var character = player.CharacterEntity;
+ var entity = character.AttachedToEntity;
- if (character.AttachedToEntity is VehicleEntity vehicle)
+ if (entity is VehicleEntity vehicle)
{
vehicle.RemoveOccupant(character);
}
- else if (character.AttachedToEntity is TurretEntity turret)
+ else if (entity is TurretEntity turret)
{
if (turret.Parent is VehicleEntity parentVehicle)
{
@@ -393,6 +396,12 @@ public void ExitAttachmentRequest(INetworkClient client, IPlayer player, ulong e
var response = new ExitingAttachment() { Direction = new Vector3(-0.5f, -0.5f, -0.47f) };
client.NetChannels[ChannelType.ReliableGss].SendMessage(response, character.EntityId);
+
+ if (entity is BaseEntity { Encounter.Instance: IExitAttachmentHandler handler } baseEntity
+ && baseEntity.Encounter.Handles(EncounterComponent.Event.ExitAttachment))
+ {
+ handler.OnExitAttachment(baseEntity, (INetworkPlayer)player);
+ }
}
[MessageID((byte)Commands.SeatChangeRequest)]
diff --git a/UdpHosts/GameServer/Entities/AreaVisualData/AreaVisualDataEntity.cs b/UdpHosts/GameServer/Entities/AreaVisualData/AreaVisualDataEntity.cs
new file mode 100644
index 00000000..e7830799
--- /dev/null
+++ b/UdpHosts/GameServer/Entities/AreaVisualData/AreaVisualDataEntity.cs
@@ -0,0 +1,32 @@
+using AeroMessages.Common;
+using AeroMessages.GSS.V66.AreaVisualData.View;
+
+namespace GameServer.Entities.AreaVisualData;
+
+public sealed class AreaVisualDataEntity : BaseEntity
+{
+ public AreaVisualDataEntity(IShard shard, ulong eid)
+ : base(shard, eid)
+ {
+ AeroEntityId = new EntityId() { Backing = EntityId, ControllerId = Controller.AreaVisualData };
+ InitFields();
+ InitViews();
+ }
+
+ public ObserverView AreaVisualData_ObserverView { get; set; }
+ public ParticleEffectsView AreaVisualData_ParticleEffectsView { get; set; }
+ public MapMarkerView AreaVisualData_MapMarkerView { get; set; }
+ public TinyObjectView AreaVisualData_TinyObjectView { get; set; }
+ public LootObjectView AreaVisualData_LootObjectView { get; set; }
+ public ForceShieldView AreaVisualData_ForceShieldView { get; set; }
+
+ private void InitFields()
+ {
+ }
+
+ private void InitViews()
+ {
+ AreaVisualData_ObserverView = new ObserverView() { PositionProp = Position };
+ AreaVisualData_ParticleEffectsView = new ParticleEffectsView();
+ }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Entities/Character/CharacterEntity.cs b/UdpHosts/GameServer/Entities/Character/CharacterEntity.cs
index 30dd10e4..dd56cb67 100644
--- a/UdpHosts/GameServer/Entities/Character/CharacterEntity.cs
+++ b/UdpHosts/GameServer/Entities/Character/CharacterEntity.cs
@@ -13,6 +13,7 @@
using GameServer.Data.SDB;
using GameServer.Data.SDB.Records.customdata;
using GameServer.Enums;
+using GameServer.Systems.Encounters;
using GameServer.Test;
using GrpcGameServerAPIClient;
using LoadoutVisualType = AeroMessages.GSS.V66.Character.LoadoutConfig_Visual.LoadoutVisualType;
@@ -22,8 +23,11 @@ namespace GameServer.Entities.Character;
///
/// Base Character
///
-public partial class CharacterEntity : BaseAptitudeEntity, IAptitudeTarget
+public sealed partial class CharacterEntity : BaseAptitudeEntity, IAptitudeTarget
{
+ public const byte MaxMapMarkerCount = 64;
+ private MapMarkerState[] MapMarkers = new MapMarkerState[MaxMapMarkerCount];
+
public CharacterEntity(IShard shard, ulong eid)
: base(shard, eid)
{
@@ -1043,6 +1047,51 @@ public void HackClearAllStatusEffects()
Shard.EntityMan.FlushChanges(this);
}
+ public void AddMapMarker(ulong encounterId, PersonalMapMarkerData data)
+ {
+ byte firstFreeIndex = InvalidIndex;
+ for (byte i = 0; i < MaxMapMarkerCount; i++)
+ {
+ if (MapMarkers[i] == null)
+ {
+ firstFreeIndex = i;
+ break;
+ }
+ }
+
+ if (firstFreeIndex == InvalidIndex)
+ {
+ Console.WriteLine("AddMapMarkers but there are too many active map markers!");
+ firstFreeIndex = MaxMapMarkerCount - 1; // Lets not crash
+ }
+
+ var state = new MapMarkerState
+ {
+ EncounterId = data.EncounterId,
+ EncounterMarkerId = data.EncounterMarkerId,
+ };
+
+ MapMarkers[firstFreeIndex] = state;
+
+ SetMapMarker(firstFreeIndex, data);
+ }
+
+ public void RemoveEncounterMapMarkers(ulong encounterId)
+ {
+ for (byte i = 0; i < MapMarkers.Length; i++)
+ {
+ if (MapMarkers[i] == null)
+ {
+ continue;
+ }
+
+ if (MapMarkers[i].EncounterId.Backing == encounterId)
+ {
+ SetMapMarker(i, null);
+ }
+ }
+ }
+
public void SetCombatFlags(CombatFlagsData value)
{
Character_CombatController.CombatFlagsProp = value;
@@ -1551,6 +1600,12 @@ private ulong GetCurrentPermissionsValue()
return result;
}
+ private void SetMapMarker(byte index, PersonalMapMarkerData? data)
+ {
+ Character_MissionAndMarkerController?.GetType().GetProperty($"PersonalMapMarkers_{index}Prop")
+ ?.SetValue(Character_MissionAndMarkerController, data);
+ }
+
public class ActiveStatModifier
{
public StatModifierIdentifier Stat { get; set; }
diff --git a/UdpHosts/GameServer/Entities/EncounterComponent.cs b/UdpHosts/GameServer/Entities/EncounterComponent.cs
index 64213277..14fbdd9c 100644
--- a/UdpHosts/GameServer/Entities/EncounterComponent.cs
+++ b/UdpHosts/GameServer/Entities/EncounterComponent.cs
@@ -1,4 +1,5 @@
using System;
+using GameServer.StaticDB.Records.customdata.Encounters;
using GameServer.Systems.Encounters;
namespace GameServer.Entities;
@@ -11,10 +12,14 @@ public enum Event : uint
Signal = 1 << 0,
Interaction = 1 << 1,
Donation = 1 << 2,
+ ExitAttachment = 1 << 3,
+ Proximity = 1 << 4,
}
public ulong EncounterId { get; set; }
public IEncounter Instance { get; set; }
+ public uint ProximityDistance { get; set; } = 0;
+ public IEncounterDef SpawnDef { get; set; }
public Event Events { get; set; }
public bool Handles(Event type) => Events.HasFlag(type);
public void StartHandling(Event type) => Events |= type;
diff --git a/UdpHosts/GameServer/Entities/Vehicle/VehicleEntity.cs b/UdpHosts/GameServer/Entities/Vehicle/VehicleEntity.cs
index b03fa788..85fc748c 100644
--- a/UdpHosts/GameServer/Entities/Vehicle/VehicleEntity.cs
+++ b/UdpHosts/GameServer/Entities/Vehicle/VehicleEntity.cs
@@ -51,7 +51,7 @@ public class SeatConfig
public byte TurretIndex;
}
-public class VehicleEntity : BaseAptitudeEntity, IAptitudeTarget
+public sealed class VehicleEntity : BaseAptitudeEntity, IAptitudeTarget
{
public VehicleEntity(IShard shard, ulong eid)
: base(shard, eid)
@@ -503,6 +503,11 @@ public bool IsEntitySeated(IEntity entity)
public override bool IsInteractable()
{
+ if (Encounter != null && !Encounter.Handles(EncounterComponent.Event.Interaction))
+ {
+ return false;
+ }
+
foreach (var seat in Occupants.Values)
{
if (seat.Occupant == null && seat.Role != AttachmentRole.None)
diff --git a/UdpHosts/GameServer/Enums/GSS/Character/Commands.cs b/UdpHosts/GameServer/Enums/GSS/Character/Commands.cs
index a4c8c554..005c2f22 100644
--- a/UdpHosts/GameServer/Enums/GSS/Character/Commands.cs
+++ b/UdpHosts/GameServer/Enums/GSS/Character/Commands.cs
@@ -136,7 +136,7 @@ internal enum Commands : byte
RequestTeleport = 210,
RequestFrameLevelReset = 211,
LeaveEncounterParty = 212,
- JoinSquadLeadersAr = 213,
+ JoinSquadLeadersArc = 213,
LeaveArc = 214,
JobLedgerOperation = 215,
SeatChangeRequest = 216,
diff --git a/UdpHosts/GameServer/Enums/GSS/Character/Events.cs b/UdpHosts/GameServer/Enums/GSS/Character/Events.cs
index 858e0274..2ae24a27 100644
--- a/UdpHosts/GameServer/Enums/GSS/Character/Events.cs
+++ b/UdpHosts/GameServer/Enums/GSS/Character/Events.cs
@@ -64,7 +64,7 @@ internal enum Events
ResourceNodeCompletedEvent = 137,
FoundResourceAreas = 138,
GeographicalReportResponse = 139,
- ResourceLocationInfosRespons = 140,
+ ResourceLocationInfosResponse = 140,
UiNamedVariableUpdate = 141,
DuelNotification = 142,
NewUiQuery = 143,
diff --git a/UdpHosts/GameServer/GRPC/GRPCService.cs b/UdpHosts/GameServer/GRPC/GRPCService.cs
index 8a29573e..3519f442 100644
--- a/UdpHosts/GameServer/GRPC/GRPCService.cs
+++ b/UdpHosts/GameServer/GRPC/GRPCService.cs
@@ -1,4 +1,6 @@
+using System;
using System.Collections.Concurrent;
+using System.Threading;
using System.Threading.Tasks;
using GameServer.GRPC.EventHandlers;
using Grpc.Core;
@@ -11,6 +13,7 @@ public static class GRPCService
{
private static GrpcChannel _channel;
private static GameServerAPI.GameServerAPIClient _client;
+ private static AsyncDuplexStreamingCall _stream;
public static void Init(string address)
{
@@ -28,71 +31,73 @@ public static async Task GetCharacterAndBattlefr
return await _client.GetCharacterAndBattleframeVisualsAsync(new CharacterID { ID = characterId });
}
- public static async Task SaveCharacterSessionDataAsync(ulong characterId, uint zoneId, uint outpostId, uint timePlayed)
+ public static async Task SaveCharacterSessionDataAsync(ulong characterId, uint zoneId, uint outpostId, uint timePlayed)
{
- return await _client.SaveCharacterGameSessionDataAsync(new GameSessionData()
- {
- CharacterId = characterId,
- ZoneId = zoneId,
- OutpostId = outpostId,
- TimePlayed = timePlayed
- });
+ var data = new SaveGameSessionData()
+ {
+ CharacterId = characterId, ZoneId = zoneId, OutpostId = outpostId, TimePlayed = timePlayed
+ };
+
+ await SendCommandAsync(new Command() { SaveGameSessionData = data });
}
- public static async Task ListenAsync(ConcurrentDictionary clientMap)
+ public static async Task SendCommandAsync(Command command)
{
- using var listen = _client.Listen(new Empty());
+ await _stream.RequestStream.WriteAsync(command);
+ }
- var reader =
- Task.Run(async () =>
- {
- await foreach (var evt in listen.ResponseStream.ReadAllAsync())
- {
- switch (evt.SubtypeCase)
- {
- case Event.SubtypeOneofCase.ArmyApplicationApproved:
- ArmyEventHandler.HandleEvent(evt.ArmyApplicationApproved, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyApplicationReceived:
- ArmyEventHandler.HandleEvent(evt.ArmyApplicationReceived, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyApplicationRejected:
- ArmyEventHandler.HandleEvent(evt.ArmyApplicationRejected, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyApplicationsUpdated:
- ArmyEventHandler.HandleEvent(evt.ArmyApplicationsUpdated, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyIdChanged:
- ArmyEventHandler.HandleEvent(evt.ArmyIdChanged, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyInfoUpdated:
- ArmyEventHandler.HandleEvent(evt.ArmyInfoUpdated, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyInviteApproved:
- ArmyEventHandler.HandleEvent(evt.ArmyInviteApproved, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyInviteReceived:
- ArmyEventHandler.HandleEvent(evt.ArmyInviteReceived, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyInviteRejected:
- ArmyEventHandler.HandleEvent(evt.ArmyInviteRejected, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyMembersUpdated:
- ArmyEventHandler.HandleEvent(evt.ArmyMembersUpdated, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyRanksUpdated:
- ArmyEventHandler.HandleEvent(evt.ArmyRanksUpdated, clientMap);
- break;
- case Event.SubtypeOneofCase.ArmyTagUpdated:
- ArmyEventHandler.HandleEvent(evt.ArmyTagUpdated, clientMap);
- break;
- case Event.SubtypeOneofCase.CharacterVisualsUpdated:
- CharacterEventHandler.HandleEvent(evt.CharacterVisualsUpdated, clientMap);
- break;
- }
- }
- });
+ public static async Task ListenAsync(ConcurrentDictionary clientMap, CancellationToken ct)
+ {
+ _stream = _client.Stream();
- await reader;
+ await foreach (var evt in _stream.ResponseStream.ReadAllAsync(ct))
+ {
+ Console.WriteLine(evt);
+ switch (evt.SubtypeCase)
+ {
+ case Event.SubtypeOneofCase.ArmyApplicationApproved:
+ ArmyEventHandler.HandleEvent(evt.ArmyApplicationApproved, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyApplicationReceived:
+ ArmyEventHandler.HandleEvent(evt.ArmyApplicationReceived, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyApplicationRejected:
+ ArmyEventHandler.HandleEvent(evt.ArmyApplicationRejected, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyApplicationsUpdated:
+ ArmyEventHandler.HandleEvent(evt.ArmyApplicationsUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyIdChanged:
+ ArmyEventHandler.HandleEvent(evt.ArmyIdChanged, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyInfoUpdated:
+ ArmyEventHandler.HandleEvent(evt.ArmyInfoUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyInviteApproved:
+ ArmyEventHandler.HandleEvent(evt.ArmyInviteApproved, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyInviteReceived:
+ ArmyEventHandler.HandleEvent(evt.ArmyInviteReceived, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyInviteRejected:
+ ArmyEventHandler.HandleEvent(evt.ArmyInviteRejected, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyMembersUpdated:
+ ArmyEventHandler.HandleEvent(evt.ArmyMembersUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyRanksUpdated:
+ ArmyEventHandler.HandleEvent(evt.ArmyRanksUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.ArmyTagUpdated:
+ ArmyEventHandler.HandleEvent(evt.ArmyTagUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.CharacterVisualsUpdated:
+ CharacterEventHandler.HandleEvent(evt.CharacterVisualsUpdated, clientMap);
+ break;
+ case Event.SubtypeOneofCase.None:
+ default:
+ break;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/GRPC/GameServerAPI.proto b/UdpHosts/GameServer/GRPC/GameServerAPI.proto
index c847329d..9ee15212 100644
--- a/UdpHosts/GameServer/GRPC/GameServerAPI.proto
+++ b/UdpHosts/GameServer/GRPC/GameServerAPI.proto
@@ -98,7 +98,11 @@ message CharacterVisualsUpdated {
uint64 CharacterGuid = 1;
CharacterAndBattleframeVisuals CharacterAndBattleframeVisuals = 2;
}
-message Empty {
+message Command {
+ oneof subtype {
+ SaveGameSessionData SaveGameSessionData = 1;
+ SaveLgvRaceFinish SaveLgvRaceFinish = 2;
+ }
}
message Event {
oneof subtype {
@@ -117,12 +121,6 @@ message Event {
CharacterVisualsUpdated CharacterVisualsUpdated = 13;
}
}
-message GameSessionData {
- uint64 CharacterId = 1;
- uint32 ZoneId = 2;
- uint32 OutpostId = 3;
- uint32 TimePlayed = 4;
-}
message PingReq {
.google.protobuf.Timestamp SentTime = 1;
}
@@ -138,6 +136,17 @@ message PlayerBattleframeVisuals {
repeated int32 warpaint_patterns = 5 [packed = false];
repeated int32 visual_overrides = 6 [packed = false];
}
+message SaveGameSessionData {
+ uint64 CharacterId = 1;
+ uint32 ZoneId = 2;
+ uint32 OutpostId = 3;
+ uint32 TimePlayed = 4;
+}
+message SaveLgvRaceFinish {
+ uint64 CharacterGuid = 1;
+ uint32 LeaderboardId = 2;
+ uint64 TimeMs = 3;
+}
message WebColor {
uint32 color = 1;
}
@@ -163,7 +172,6 @@ message WebIdValueColorId {
}
service GameServerAPI {
rpc GetCharacterAndBattleframeVisuals (CharacterID) returns (CharacterAndBattleframeVisuals);
- rpc Listen (Empty) returns (stream Event);
rpc Ping (PingReq) returns (PingResp);
- rpc SaveCharacterGameSessionData (GameSessionData) returns (Empty);
+ rpc Stream (stream Command) returns (stream Event);
}
diff --git a/UdpHosts/GameServer/GameServer.cs b/UdpHosts/GameServer/GameServer.cs
index a78c2622..0eb11ef2 100644
--- a/UdpHosts/GameServer/GameServer.cs
+++ b/UdpHosts/GameServer/GameServer.cs
@@ -12,6 +12,7 @@
using GameServer.Test;
using Serilog;
using Shared.Udp;
+using SDB = FauFau.Formats.StaticDB;
namespace GameServer;
@@ -31,7 +32,7 @@ internal class GameServer : PacketServer
public GameServer(GameServerSettings serverSettings,
ILogger logger,
- StaticDB sdb)
+ SDB sdb)
: base(serverSettings.Port, logger)
{
_clientMap = new ConcurrentDictionary();
@@ -139,12 +140,12 @@ private async Task ListenGrpcAsync(CancellationToken ct)
{
try
{
- await GRPCService.ListenAsync(_clientMap);
+ await GRPCService.ListenAsync(_clientMap, ct);
}
catch (Exception)
{
- Console.WriteLine("Failed to connect to GRPC, retrying in 5 minutes");
- await Task.Delay(TimeSpan.FromMinutes(5), ct);
+ Console.WriteLine("Failed to establish GRPC stream, retrying in 30 seconds");
+ await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
}
}
diff --git a/UdpHosts/GameServer/GameServerModule.cs b/UdpHosts/GameServer/GameServerModule.cs
index 2fd5534e..d4ea27fa 100644
--- a/UdpHosts/GameServer/GameServerModule.cs
+++ b/UdpHosts/GameServer/GameServerModule.cs
@@ -1,10 +1,10 @@
using System;
using System.Configuration;
using Autofac;
-using FauFau.Formats;
using GameServer.Physics.ZoneLoader;
using Serilog;
using Shared.Common;
+using SDB = FauFau.Formats.StaticDB;
namespace GameServer;
@@ -21,7 +21,7 @@ protected override void Load(ContainerBuilder builder)
private static void RegisterTypes(ContainerBuilder builder)
{
builder.RegisterType().SingleInstance();
- builder.RegisterType().SingleInstance();
+ builder.RegisterType().SingleInstance();
builder.RegisterType();
}
@@ -46,6 +46,11 @@ private static void RegisterInstances(ContainerBuilder builder)
settings.StaticDBPath = ConfigurationManager.AppSettings["StaticDBPath"];
}
+ if (ConfigurationManager.AppSettings["ZoneId"] != null)
+ {
+ settings.ZoneId = uint.Parse(ConfigurationManager.AppSettings["ZoneId"]);
+ }
+
if (ConfigurationManager.AppSettings["MapsPath"] != null)
{
settings.MapsPath = ConfigurationManager.AppSettings["MapsPath"];
@@ -101,11 +106,11 @@ private static void RegisterInstances(ContainerBuilder builder)
var settings = ctx.Resolve();
Console.WriteLine($"Opening SDB from {settings.StaticDBPath}");
- StaticDB sdb = new StaticDB();
+ var sdb = new SDB();
sdb.Read(settings.StaticDBPath);
return sdb;
})
- .As().SingleInstance();
+ .As().SingleInstance();
}
}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/IShard.cs b/UdpHosts/GameServer/IShard.cs
index 0f3aab3e..4f7535cd 100644
--- a/UdpHosts/GameServer/IShard.cs
+++ b/UdpHosts/GameServer/IShard.cs
@@ -5,6 +5,7 @@
using GameServer.Entities;
using GameServer.Entities.Outpost;
using GameServer.Physics;
+using GameServer.Systems.Chat;
using GameServer.Systems.Encounters;
using Serilog;
using Shared.Udp;
diff --git a/UdpHosts/GameServer/Shard.cs b/UdpHosts/GameServer/Shard.cs
index b5fb1eef..f0066e10 100644
--- a/UdpHosts/GameServer/Shard.cs
+++ b/UdpHosts/GameServer/Shard.cs
@@ -11,6 +11,7 @@
using GameServer.Entities;
using GameServer.Entities.Outpost;
using GameServer.Physics;
+using GameServer.Systems.Chat;
using GameServer.Systems.Encounters;
using Microsoft.Extensions.Logging;
using Serilog;
diff --git a/UdpHosts/GameServer/StaticDB/CustomDBInterface.cs b/UdpHosts/GameServer/StaticDB/CustomDBInterface.cs
index ef39eee9..1233acea 100644
--- a/UdpHosts/GameServer/StaticDB/CustomDBInterface.cs
+++ b/UdpHosts/GameServer/StaticDB/CustomDBInterface.cs
@@ -1,3 +1,5 @@
+using GameServer.StaticDB.Records.customdata.Encounters;
+
namespace GameServer.Data.SDB;
using System.Collections.Generic;
@@ -154,7 +156,8 @@ public class CustomDBInterface
private static Dictionary> Deployable;
private static Dictionary> Melding;
private static Dictionary> Outpost;
- private static Dictionary> MeldingRepulsor;
+ private static Dictionary> MeldingRepulsor;
+ private static Dictionary> LgvRace;
public static void Init()
{
@@ -310,6 +313,7 @@ public static void Init()
Melding = loader.LoadMelding();
Outpost = loader.LoadOutpost();
MeldingRepulsor = loader.LoadMeldingRepulsor();
+ LgvRace = loader.LoadLgvRace();
}
// aptgss
@@ -461,5 +465,6 @@ public static void Init()
public static Dictionary GetZoneDeployables(uint zoneId) => Deployable.GetValueOrDefault(zoneId);
public static Dictionary GetZoneMeldings(uint zoneId) => Melding.GetValueOrDefault(zoneId);
public static Dictionary GetZoneOutposts(uint zoneId) => Outpost.GetValueOrDefault(zoneId);
- public static Dictionary GetZoneMeldingRepulsors(uint zoneId) => MeldingRepulsor.GetValueOrDefault(zoneId);
+ public static Dictionary GetZoneMeldingRepulsors(uint zoneId) => MeldingRepulsor.GetValueOrDefault(zoneId);
+ public static Dictionary GetZoneLgvRaces(uint zoneId) => LgvRace.GetValueOrDefault(zoneId);
}
diff --git a/UdpHosts/GameServer/StaticDB/CustomData/lgv_race.json b/UdpHosts/GameServer/StaticDB/CustomData/lgv_race.json
new file mode 100644
index 00000000..42f6196d
--- /dev/null
+++ b/UdpHosts/GameServer/StaticDB/CustomData/lgv_race.json
@@ -0,0 +1,262 @@
+[
+ {
+ "id": 1,
+ "zone_id": 448,
+ "comment": "Copacabana - Trans-Hub",
+ "label": 156505,
+ "leaderboard_id": 1,
+ "start_dialog": 11156,
+ "start": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.17295577,
+ "X": 0.002591103,
+ "Y": 0.0435862,
+ "Z": 0.9839613
+ },
+ "position": {
+ "X": -357.18137,
+ "Y": -467.0791,
+ "Z": 418.691528
+ }
+ },
+ "finish": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.1801588,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.9836375
+ },
+ "position": {
+ "X": -1020.095704,
+ "Y": -908.493591,
+ "Z": 430.220825
+ }
+ },
+ "terminal": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.7175571,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.6964997
+ },
+ "position": {
+ "X": -360.3101,
+ "Y": -465.41077,
+ "Z": 418.7193
+ }
+ },
+ "time_limit_ms": 80000,
+ "bonus_time_limit_ms": 50000
+ },
+ {
+ "id": 2,
+ "zone_id": 448,
+ "comment": "Trans-Hub - Sunken Harbor",
+ "label": 141983,
+ "leaderboard_id": 2,
+ "start_dialog": 11387,
+ "start": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.3392906,
+ "X": 0.014921068,
+ "Y": 0.041290212,
+ "Z": 0.9396565
+ },
+ "position": {
+ "X": -1023.555908,
+ "Y": -910.066528,
+ "Z": 429.218689
+ }
+ },
+ "finish": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.50619423,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.8624195
+ },
+ "position": {
+ "X": 627.695129,
+ "Y": -1452.656860,
+ "Z": 411.751312
+ }
+ },
+ "terminal": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.97638285,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.2160475
+ },
+ "position": {
+ "X": -1028.4078,
+ "Y": -907.1568,
+ "Z": 428.6798
+ }
+ },
+ "time_limit_ms": 180000,
+ "bonus_time_limit_ms": 120000
+ },
+ {
+ "id": 3,
+ "zone_id": 448,
+ "comment": "Sunken Harbor - Copacabana",
+ "label": 135929,
+ "leaderboard_id": 3,
+ "start_dialog": 11012,
+ "start": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.7648511,
+ "X": -0.012024249,
+ "Y": -0.010168955,
+ "Z": -0.6440147
+ },
+ "position": {
+ "X": 642.516479,
+ "Y": -1483.968628,
+ "Z": 414.576904
+ }
+ },
+ "finish": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.044414524,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.9990132
+ },
+ "position": {
+ "X": -350.430237,
+ "Y": -421.046906,
+ "Z": 415.034119
+ }
+ },
+ "terminal": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.9999156,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.012992493
+ },
+ "position": {
+ "X": 646.72455,
+ "Y": -1488.7458,
+ "Z": 414.7206
+ }
+ },
+ "time_limit_ms": 160000,
+ "bonus_time_limit_ms": 100000
+ },
+ {
+ "id": 4,
+ "zone_id": 448,
+ "comment": "Copacabana - Thump Dump",
+ "label": 151665,
+ "leaderboard_id": 4,
+ "start_dialog": 11585,
+ "start": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.9566996,
+ "X": 0.071605094,
+ "Y": 0.026196381,
+ "Z": -0.28091335
+ },
+ "position": {
+ "X": -667.258972,
+ "Y": -329.081451,
+ "Z": 442.637207
+ }
+ },
+ "finish": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.6432496,
+ "X": 0.0,
+ "Y": 0.0,
+ "Z": 0.76565653
+ },
+ "position": {
+ "X": -1644.012207,
+ "Y": 1294.188232,
+ "Z": 492.192047
+ }
+ },
+ "terminal": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.31347674,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.94959587
+ },
+ "position": {
+ "X": -667.1653,
+ "Y": -324.08643,
+ "Z": 441.51447
+ }
+ },
+ "time_limit_ms": 180000,
+ "bonus_time_limit_ms": 120000
+ },
+ {
+ "id": 5,
+ "zone_id": 448,
+ "comment": "Thump Dump - Copacabana",
+ "label": 135929,
+ "leaderboard_id": 5,
+ "start_dialog": 11012,
+ "start": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.5659935,
+ "X": -0.03600099,
+ "Y": 0.05213954,
+ "Z": 0.82197124
+ },
+ "position": {
+ "X": -1653.038086,
+ "Y": 1294.078857,
+ "Z": 490.870789
+ }
+ },
+ "finish": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": 0.9220468,
+ "X": 0,
+ "Y": 0,
+ "Z": 0.38707846
+ },
+ "position": {
+ "X": -683.523499,
+ "Y": -322.812042,
+ "Z": 441.364685
+ }
+ },
+ "terminal": {
+ "orientation": {
+ "IsIdentity": false,
+ "W": -0.8908104,
+ "X": 0,
+ "Y": 0,
+ "Z":0.45437527
+ },
+ "position": {
+ "X": -1649.3523,
+ "Y": 1289.0253,
+ "Z": 491.0055
+ }
+ },
+ "time_limit_ms": 180000,
+ "bonus_time_limit_ms": 120000
+ }
+]
\ No newline at end of file
diff --git a/UdpHosts/GameServer/StaticDB/Loaders/CustomDBLoader.cs b/UdpHosts/GameServer/StaticDB/Loaders/CustomDBLoader.cs
index ce644fbb..b8065c81 100644
--- a/UdpHosts/GameServer/StaticDB/Loaders/CustomDBLoader.cs
+++ b/UdpHosts/GameServer/StaticDB/Loaders/CustomDBLoader.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Text.Json;
using GameServer.Data.SDB.Records.customdata;
+using GameServer.StaticDB.Records.customdata.Encounters;
using Shared.Common;
public class CustomDBLoader
@@ -895,9 +896,16 @@ public Dictionary> LoadOutpost()
.ToDictionary(group => group.Key, group => group.ToDictionary(row => row.Id, row => row));
}
- public Dictionary> LoadMeldingRepulsor()
+ public Dictionary> LoadMeldingRepulsor()
{
- return LoadJSON("./StaticDB/CustomData/meldingRepulsor.json")
+ return LoadJSON("./StaticDB/CustomData/meldingRepulsor.json")
+ .GroupBy(row => row.ZoneId)
+ .ToDictionary(group => group.Key, group => group.ToDictionary(row => row.Id, row => row));
+ }
+
+ public Dictionary> LoadLgvRace()
+ {
+ return LoadJSON("./StaticDB/CustomData/lgv_race.json")
.GroupBy(row => row.ZoneId)
.ToDictionary(group => group.Key, group => group.ToDictionary(row => row.Id, row => row));
}
diff --git a/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/IEncounterDef.cs b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/IEncounterDef.cs
new file mode 100644
index 00000000..59b4ef39
--- /dev/null
+++ b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/IEncounterDef.cs
@@ -0,0 +1,5 @@
+namespace GameServer.StaticDB.Records.customdata.Encounters;
+
+public interface IEncounterDef
+{
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/LgvRaceDef.cs b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/LgvRaceDef.cs
new file mode 100644
index 00000000..caf97a99
--- /dev/null
+++ b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/LgvRaceDef.cs
@@ -0,0 +1,23 @@
+using System.Numerics;
+
+namespace GameServer.StaticDB.Records.customdata.Encounters;
+
+public record LgvRaceDef : IEncounterDef
+{
+ public uint Id { get; set; }
+ public uint ZoneId { get; set; }
+ public uint LeaderboardId { get; set; }
+ public uint Label { get; set; }
+ public uint StartDialog { get; set; }
+ public SpawnPose Start { get; set; }
+ public SpawnPose Finish { get; set; }
+ public SpawnPose Terminal { get; set; }
+ public uint TimeLimitMs { get; set; }
+ public uint BonusTimeLimitMs { get; set; }
+}
+
+public record SpawnPose
+{
+ public Vector3 Position { get; set; }
+ public Quaternion Orientation { get; set; }
+}
diff --git a/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsor.cs b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsorDef.cs
similarity index 72%
rename from UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsor.cs
rename to UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsorDef.cs
index ca14fadc..516e8385 100644
--- a/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsor.cs
+++ b/UdpHosts/GameServer/StaticDB/Records/customdata/Encounters/MeldingRepulsorDef.cs
@@ -1,8 +1,9 @@
using System.Numerics;
+using GameServer.Data.SDB.Records.customdata;
-namespace GameServer.Data.SDB.Records.customdata;
+namespace GameServer.StaticDB.Records.customdata.Encounters;
-public record MeldingRepulsor
+public record MeldingRepulsorDef : IEncounterDef
{
public uint Id { get; set; }
public uint ZoneId { get; set; }
diff --git a/UdpHosts/GameServer/Systems/Aptitude/Commands/Interaction/EndInteractionCommand.cs b/UdpHosts/GameServer/Systems/Aptitude/Commands/Interaction/EndInteractionCommand.cs
index 68ff4de2..439bb9d8 100644
--- a/UdpHosts/GameServer/Systems/Aptitude/Commands/Interaction/EndInteractionCommand.cs
+++ b/UdpHosts/GameServer/Systems/Aptitude/Commands/Interaction/EndInteractionCommand.cs
@@ -46,6 +46,11 @@ public bool Execute(Context context)
return true;
}
+ if (hack.Encounter is { SpawnDef: { } spawnData })
+ {
+ context.Shard.EncounterMan.Factory.SpawnEncounter(spawnData, (CharacterEntity)actingEntity);
+ }
+
var abilityId = hack.Interaction.CompletedAbilityId;
if (abilityId != 0)
{
diff --git a/UdpHosts/GameServer/Systems/Aptitude/Commands/Requirement/RequireWeaponArmed.cs b/UdpHosts/GameServer/Systems/Aptitude/Commands/Requirement/RequireWeaponArmedCommand.cs
similarity index 100%
rename from UdpHosts/GameServer/Systems/Aptitude/Commands/Requirement/RequireWeaponArmed.cs
rename to UdpHosts/GameServer/Systems/Aptitude/Commands/Requirement/RequireWeaponArmedCommand.cs
diff --git a/UdpHosts/GameServer/Systems/Chat/ChatCommand.cs b/UdpHosts/GameServer/Systems/Chat/ChatCommand.cs
new file mode 100644
index 00000000..bd608a14
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/ChatCommand.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Numerics;
+
+namespace GameServer.Systems.Chat;
+
+public abstract class ChatCommand
+{
+ public abstract void Execute(string[] parameters, ChatCommandContext context);
+ public virtual void SourceFeedback(string message, ChatCommandContext context)
+ {
+ Console.WriteLine(message);
+ context.SourcePlayer?.SendDebugChat(message);
+ }
+
+ public uint ParseUIntParameter(string value)
+ {
+ if (uint.TryParse(value, out uint result))
+ {
+ return result;
+ }
+ else
+ {
+ Console.WriteLine($"Invalid format: {value}");
+ return 0;
+ }
+ }
+
+ public ulong ParseULongParameter(string value)
+ {
+ if (ulong.TryParse(value, out ulong result))
+ {
+ return result;
+ }
+ else
+ {
+ Console.WriteLine($"Invalid format: {value}");
+ return 0;
+ }
+ }
+
+ public Vector3? ParseVector3Parameters(string[] parameters, int startIndex = 0)
+ {
+ if (startIndex < 0 || startIndex >= parameters.Length)
+ {
+ Console.WriteLine($"Invalid start index: {startIndex}");
+ return null;
+ }
+
+ if (startIndex + 2 < parameters.Length &&
+ float.TryParse(parameters[startIndex], out float x) &&
+ float.TryParse(parameters[startIndex + 1], out float y) &&
+ float.TryParse(parameters[startIndex + 2], out float z))
+ {
+ return new Vector3(x, y, z);
+ }
+ else
+ {
+ Console.WriteLine("Invalid format for Vector3 parameters");
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Chat/ChatCommandAttribute.cs b/UdpHosts/GameServer/Systems/Chat/ChatCommandAttribute.cs
new file mode 100644
index 00000000..4a776102
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/ChatCommandAttribute.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace GameServer.Systems.Chat;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class ChatCommandAttribute : Attribute
+{
+ public ChatCommandAttribute(string description, string usage, params string[] names)
+ {
+ Description = description;
+ Names = names;
+ Usage = usage;
+ }
+
+ public string Description { get; }
+ public string[] Names { get; }
+ public string Usage { get; }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Chat/ChatCommandContext.cs b/UdpHosts/GameServer/Systems/Chat/ChatCommandContext.cs
new file mode 100644
index 00000000..4d318b1a
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/ChatCommandContext.cs
@@ -0,0 +1,8 @@
+namespace GameServer.Systems.Chat;
+
+public class ChatCommandContext
+{
+ public ChatCommandService Service { get; set; }
+ public IShard Shard { get; set; }
+ public INetworkPlayer SourcePlayer { get; set; }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Chat/ChatCommandService.cs b/UdpHosts/GameServer/Systems/Chat/ChatCommandService.cs
new file mode 100644
index 00000000..1ed3f9a1
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/ChatCommandService.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace GameServer.Systems.Chat;
+
+public class ChatCommandService
+{
+ private readonly Dictionary _commandDictionary;
+ private Shard _shard;
+
+ public ChatCommandService(Shard shard)
+ {
+ _shard = shard;
+ _commandDictionary = new Dictionary();
+
+ LoadCommands();
+ }
+
+ public string GetCommandList()
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.AppendLine("Available Commands:");
+
+ foreach (var commandType in _commandDictionary.Values.Distinct())
+ {
+ var command = Activator.CreateInstance(commandType) as ChatCommand;
+ var attribute = commandType.GetCustomAttribute();
+ stringBuilder.AppendLine($"{attribute.Names[0]}: {attribute.Description}\n\tUsage: {attribute.Usage}\n\tAliases: {string.Join(", ", attribute.Names)}");
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ public void ExecuteCommand(string input, INetworkPlayer sourcePlayer)
+ {
+ var (commandName, parameters) = ParseCommand(input);
+
+ if (_commandDictionary.TryGetValue(commandName.ToLower(), out var commandType))
+ {
+ var command = Activator.CreateInstance(commandType) as ChatCommand;
+ command.Execute(parameters, new() { Service = this, Shard = _shard, SourcePlayer = sourcePlayer });
+ }
+ else
+ {
+ Console.WriteLine($"Unknown command: {commandName}");
+ sourcePlayer?.SendDebugChat($"Unknown command: {commandName}");
+ }
+ }
+
+ private (string commandName, string[] parameters) ParseCommand(string input)
+ {
+ var parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+ var commandName = parts[0];
+ var parameters = parts.Skip(1).ToArray();
+
+ return (commandName, parameters);
+ }
+
+ private void LoadCommands()
+ {
+ var commandTypes = Assembly.GetExecutingAssembly().GetTypes()
+ .Where(t => t.IsSubclassOf(typeof(ChatCommand)))
+ .ToList();
+
+ foreach (var commandType in commandTypes)
+ {
+ var attribute = commandType.GetCustomAttribute();
+ if (attribute != null)
+ {
+ foreach (var name in attribute.Names)
+ {
+ _commandDictionary.Add(name.ToLower(), commandType);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Chat/ChatService.cs b/UdpHosts/GameServer/Systems/Chat/ChatService.cs
index 84fda50f..c9cae4af 100644
--- a/UdpHosts/GameServer/Systems/Chat/ChatService.cs
+++ b/UdpHosts/GameServer/Systems/Chat/ChatService.cs
@@ -1,17 +1,14 @@
using System;
using System.Linq;
-using System.Threading;
using AeroMessages.Common;
using AeroMessages.GSS.V66;
-using AeroMessages.GSS.V66.Character;
using AeroMessages.GSS.V66.Character.Command;
-using AeroMessages.GSS.V66.Character.Event;
using AeroMessages.GSS.V66.Generic;
using GameServer.Entities;
using GameServer.Entities.Character;
using GameServer.Enums;
-namespace GameServer;
+namespace GameServer.Systems.Chat;
public class ChatService
{
@@ -24,10 +21,12 @@ public class ChatService
};
private Shard Shard;
+ private ChatCommandService CommandService;
public ChatService(Shard shard)
{
Shard = shard;
+ CommandService = new ChatCommandService(shard);
}
public void CharacterPerformTextChat(INetworkClient client, IEntity entity, PerformTextChat query)
@@ -40,7 +39,13 @@ public void CharacterPerformTextChat(INetworkClient client, IEntity entity, Perf
ChatChannel queryChannel = (ChatChannel)query.Channel;
- if (PublicBroadcastChannels.Contains(queryChannel))
+ var trimmed = query.Message.Trim();
+ if (trimmed.StartsWith('\\'))
+ {
+ CommandService.ExecuteCommand(trimmed[1..], ((CharacterEntity)entity).Player);
+ Console.WriteLine(query.Message);
+ }
+ else if (PublicBroadcastChannels.Contains(queryChannel))
{
SendToAll(query.Message, queryChannel, entity);
}
@@ -73,6 +78,11 @@ public void SendToAll(string message, ChatChannel channel, IEntity sender)
}
}
+ public string GetCommandList()
+ {
+ return CommandService.GetCommandList();
+ }
+
private ChatMessageList PrepareSingleMessage(string message, ChatChannel channel, IEntity sender)
{
var senderId = sender != null ? sender.AeroEntityId : new EntityId() { Backing = Shard.InstanceId };
diff --git a/UdpHosts/GameServer/Systems/Chat/Commands/HelpChatCommand.cs b/UdpHosts/GameServer/Systems/Chat/Commands/HelpChatCommand.cs
new file mode 100644
index 00000000..ab63b0cc
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/Commands/HelpChatCommand.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace GameServer.Systems.Chat.Commands;
+
+[ChatCommand("Print a list of all chat commands", "help", "help", "listcmd", "listcmds", "cmdlist", "cmds")]
+public class HelpChatCommand : ChatCommand
+{
+ public override void Execute(string[] parameters, ChatCommandContext context)
+ {
+ var message = context.Shard.Chat.GetCommandList();
+ if (context.SourcePlayer != null)
+ {
+ context.SourcePlayer.SendDebugLog(message);
+ context.SourcePlayer.SendDebugChat("Command list printed to console");
+ }
+ else
+ {
+ Console.WriteLine(message);
+ }
+ }
+}
diff --git a/UdpHosts/GameServer/Systems/Chat/Commands/LeaderboardChatCommand.cs b/UdpHosts/GameServer/Systems/Chat/Commands/LeaderboardChatCommand.cs
new file mode 100644
index 00000000..b9247a7c
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Chat/Commands/LeaderboardChatCommand.cs
@@ -0,0 +1,22 @@
+using AeroMessages.GSS.V66.Character.Controller;
+
+namespace GameServer.Systems.Chat.Commands;
+
+[ChatCommand("Opens leaderboard", "leaderboard [id]", "leaderboard", "scoreboard")]
+public class LeaderboardChatCommand : ChatCommand
+{
+ public override void Execute(string[] parameters, ChatCommandContext context)
+ {
+ if (parameters.Length != 1)
+ {
+ SourceFeedback("Provide id of the leaderboard you'd like to open", context);
+ return;
+ }
+
+ context.SourcePlayer.CharacterEntity.SetAuthorizedTerminal(
+ new AuthorizedTerminalData
+ {
+ TerminalId = ParseUIntParameter(parameters[0]), TerminalType = 14, TerminalEntityId = 0
+ });
+ }
+}
diff --git a/UdpHosts/GameServer/Systems/Encounters/BaseEncounter.cs b/UdpHosts/GameServer/Systems/Encounters/BaseEncounter.cs
index ef2ca689..5b3e0ee2 100644
--- a/UdpHosts/GameServer/Systems/Encounters/BaseEncounter.cs
+++ b/UdpHosts/GameServer/Systems/Encounters/BaseEncounter.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Aero.Gen;
using AeroMessages.Common;
using AeroMessages.GSS.V66.Generic;
@@ -18,11 +19,17 @@ protected BaseEncounter(IShard shard, ulong entityId, HashSet pa
Participants = participants;
}
+ protected BaseEncounter(IShard shard, ulong entityId, INetworkPlayer soloParticipant)
+ : this(shard, entityId, new HashSet() { soloParticipant })
+ {
+ }
+
public IShard Shard { get; protected set; }
public ulong EntityId { get; }
public EntityId AeroEntityId { get; protected set; }
public HashSet Participants { get; }
+ public INetworkPlayer SoloParticipant => Participants.Single();
public virtual IAeroEncounter View => null;
public virtual void OnUpdate(ulong currentTime)
@@ -53,4 +60,12 @@ protected void PlayDialog(uint id)
p.NetChannels[ChannelType.ReliableGss].SendMessage(msg, p.CharacterEntity.EntityId);
}
}
+
+ protected void RewardWithResource(uint resourceId, uint quantity)
+ {
+ foreach (var p in Participants)
+ {
+ p.Inventory.AddResource(resourceId, quantity);
+ }
+ }
}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Encounters/EncounterManager.cs b/UdpHosts/GameServer/Systems/Encounters/EncounterManager.cs
index 55d0b9f6..6cf25bdf 100644
--- a/UdpHosts/GameServer/Systems/Encounters/EncounterManager.cs
+++ b/UdpHosts/GameServer/Systems/Encounters/EncounterManager.cs
@@ -17,7 +17,9 @@ namespace GameServer.Systems.Encounters;
public class EncounterManager
{
- private const ulong _updateFlushIntervalMs = 20;
+ public Factory Factory;
+
+ private const ulong _updateFlushIntervalMs = 40;
private const ulong _lifetimeCheckIntervalMs = 1000;
private readonly Shard Shard;
@@ -25,6 +27,7 @@ public class EncounterManager
private ulong _lastLifetimeCheck = 0;
private bool _hasSpawnedZoneEncounters = false;
+ private Dictionary EntitiesToCheckProximity = new Dictionary();
private Dictionary UiQueries = new Dictionary();
private HashSet EncountersToUpdate = new HashSet();
private ConcurrentDictionary LifetimeByEncounter = new ConcurrentDictionary();
@@ -32,6 +35,7 @@ public class EncounterManager
public EncounterManager(Shard shard)
{
Shard = shard;
+ Factory = new Factory(shard);
}
public void SendUiQuery(NewUiQuery uiQuery, INetworkPlayer target, IEncounter encounter)
@@ -81,7 +85,14 @@ public void SpawnZoneEncounters(uint zoneId)
foreach (var entry in CustomDBInterface.GetZoneMeldingRepulsors(zoneId))
{
var guid = Shard.GetNextGuid((byte)Controller.Encounter);
- Shard.Encounters.Add(guid, new MeldingRepulsor(Shard, guid, new HashSet(), entry.Value));
+ Add(guid, new MeldingRepulsor(Shard, guid, new HashSet(), entry.Value));
+ }
+
+ foreach (var entry in CustomDBInterface.GetZoneLgvRaces(zoneId))
+ {
+ var t = entry.Value.Terminal;
+ var terminal = Shard.EntityMan.SpawnDeployable(820, t.Position, t.Orientation);
+ terminal.Encounter = new EncounterComponent() { SpawnDef = entry.Value, Events = EncounterComponent.Event.Interaction };
}
}
@@ -103,6 +114,11 @@ public void StopUpdatingEncounter(IEncounter encounter)
EncountersToUpdate.Remove(encounter);
}
+ public void AddCheckingOfProximity(BaseEntity entity, IProximityHandler encounter)
+ {
+ EntitiesToCheckProximity.Add(entity, encounter);
+ }
+
public void Tick(double deltaTime, ulong currentTime, CancellationToken ct)
{
if (!_hasSpawnedZoneEncounters && currentTime != 0)
@@ -126,6 +142,22 @@ public void Tick(double deltaTime, ulong currentTime, CancellationToken ct)
// FlushChanges(encounter);
}
+
+ foreach (var (entity, encounter) in EntitiesToCheckProximity)
+ {
+ foreach (var p in encounter.Participants)
+ {
+ if (!Shard.EntityMan.HasScopedInEntity(entity.EntityId, p))
+ {
+ continue;
+ }
+
+ if (Vector3.Distance(entity.Position, p.CharacterEntity.Position) < entity.Encounter.ProximityDistance)
+ {
+ encounter.OnProximity(entity, p);
+ }
+ }
+ }
}
if (currentTime > _lastLifetimeCheck + _lifetimeCheckIntervalMs)
diff --git a/UdpHosts/GameServer/Systems/Encounters/Encounters/LgvRace.cs b/UdpHosts/GameServer/Systems/Encounters/Encounters/LgvRace.cs
new file mode 100644
index 00000000..f37222ed
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Encounters/Encounters/LgvRace.cs
@@ -0,0 +1,244 @@
+using System.Threading;
+using AeroMessages.Common;
+using AeroMessages.GSS.V66.AreaVisualData;
+using AeroMessages.GSS.V66.Character;
+using AeroMessages.GSS.V66.Character.Controller;
+using AeroMessages.GSS.V66.Character.Event;
+using AeroMessages.GSS.V66.Generic.Event.EncounterView;
+using GameServer.Entities;
+using GameServer.Entities.AreaVisualData;
+using GameServer.GRPC;
+using GameServer.StaticDB.Records.customdata.Encounters;
+using GrpcGameServerAPIClient;
+using Command = GrpcGameServerAPIClient.Command;
+using SinCardTimer = AeroMessages.GSS.V66.Timer;
+
+namespace GameServer.Systems.Encounters.Encounters;
+
+public class LgvRace : BaseEncounter, IExitAttachmentHandler, IProximityHandler, ICanTimeout
+{
+ private const ushort _terromotoCobra = 48;
+ private const uint _crystite = 10;
+ private const uint _mustangDoubloon = 77324;
+ private const uint _lgvRace = 183671;
+ private const uint _finalTime = 152919;
+ private const uint _pfxFinishLine = 212597;
+ private const ushort _mapMarkerRace = 26;
+
+ private ulong startTime;
+ private uint leaderboardId;
+ private uint bonusTimeMs;
+ private AreaVisualDataEntity finishLine;
+
+ public LgvRace(IShard shard, ulong entityId, INetworkPlayer soloParticipant, LgvRaceDef data)
+ : base(shard, entityId, soloParticipant)
+ {
+ startTime = Shard.CurrentTimeLong;
+ Shard.EncounterMan.SetRemainingLifetime(this, data.TimeLimitMs);
+
+ leaderboardId = data.LeaderboardId;
+
+ var vehicle = Shard.EntityMan.SpawnVehicle(
+ _terromotoCobra,
+ data.Start.Position,
+ data.Start.Orientation,
+ SoloParticipant.CharacterEntity,
+ true);
+ vehicle.Encounter = new EncounterComponent()
+ {
+ EncounterId = entityId, Instance = this, Events = EncounterComponent.Event.ExitAttachment
+ };
+
+ View = new HudTimerView()
+ {
+ hudtimer_labelProp = data.Label,
+ hudtimer_timerProp = new SinCardTimer()
+ {
+ Micro = (startTime + data.TimeLimitMs) * 1000,
+ State = SinCardTimer.TimerState.CountingDown,
+ },
+ };
+
+ PlayDialog(data.StartDialog);
+
+ bonusTimeMs = data.BonusTimeLimitMs;
+ var bonusTimeDialog = (data.TimeLimitMs - data.BonusTimeLimitMs) switch
+ {
+ 30_000 => 11155u,
+ 45_000 => 11060u,
+ 60_000 => 11133u,
+ _ => 0u,
+ };
+
+ var timer = new Timer(state =>
+ {
+ if (shard.Encounters.ContainsKey(EntityId))
+ {
+ PlayDialog(bonusTimeDialog);
+ }
+
+ ((Timer)state)?.Dispose();
+ });
+ timer.Change(30_000, Timeout.Infinite);
+
+ finishLine = Shard.EntityMan.SpawnAreaVisualData(data.Finish.Position, new ScopingComponent() { Range = 150 });
+ finishLine.AreaVisualData_ParticleEffectsView.ParticleEffects_0Prop = new ParticleEffect()
+ {
+ PfxEntityId = AeroEntityId,
+ PfxAssetId = _pfxFinishLine,
+ Position = data.Finish.Position,
+ Rotation = data.Finish.Orientation,
+ Unk9 = 1,
+ Unk10 = 1,
+ Scale = 0.7f,
+ HaveUnk4 = 0,
+ HaveUnk12 = 0,
+ };
+ finishLine.Encounter = new EncounterComponent()
+ {
+ EncounterId = entityId,
+ Instance = this,
+ Events = EncounterComponent.Event.Proximity,
+ ProximityDistance = 3,
+ };
+ Shard.EncounterMan.AddCheckingOfProximity(finishLine, this);
+
+ SoloParticipant.CharacterEntity.AddMapMarker(
+ EntityId,
+ new PersonalMapMarkerData()
+ {
+ EncounterId = AeroEntityId,
+ EncounterMarkerId =
+ new EntityId()
+ {
+ Backing = AeroEntityId.Backing,
+ ControllerId = Controller.Generic,
+ },
+ TitleId = data.Label,
+ HasDuration = 0,
+ Position = data.Finish.Position,
+ MarkerType = _mapMarkerRace,
+ });
+ }
+
+ public override HudTimerView View { get; }
+
+ public void OnExitAttachment(BaseEntity targetEntity, INetworkPlayer player)
+ {
+ OnFailure();
+ }
+
+ public void OnProximity(BaseEntity sourceEntity, INetworkPlayer player)
+ {
+ if (player != SoloParticipant)
+ {
+ return;
+ }
+
+ OnSuccess();
+ }
+
+ public void OnTimeOut()
+ {
+ var dialogs = new uint[] { 11015, 11587, 19278 };
+ PlayDialog(dialogs[Rng.Next(dialogs.Length)]);
+
+ OnFailure();
+ }
+
+ public override void OnSuccess()
+ {
+ RemoveEntities();
+
+ uint exp;
+
+ var time = Shard.CurrentTimeLong - startTime;
+
+ if (time < bonusTimeMs)
+ {
+ exp = 5000;
+ PlayDialog(11014);
+ RewardWithResource(_mustangDoubloon, 1);
+ }
+ else
+ {
+ exp = 3500;
+ PlayDialog(11586);
+ }
+
+ var crystite = (uint)Rng.Next(70, 90);
+
+ RewardWithResource(_crystite, crystite);
+
+ var msg = new DisplayRewards()
+ {
+ ResourceTargetId = new EntityId() { Backing = 0 },
+ Experience = exp,
+ Reputations = new ReputationInfo[] { },
+ Rewards1 = new RewardInfoData[]
+ {
+ new RewardInfoData()
+ {
+ Unk = 0,
+ Boosted = 0,
+ SdbId = _crystite,
+ Quantity = (ushort)crystite,
+ Quality = 0,
+ Module1 = 0,
+ Module2 = 0,
+ },
+ new RewardInfoData()
+ {
+ Boosted = 0,
+ SdbId = _mustangDoubloon,
+ Quantity = 1,
+ Quality = 0,
+ Unk = 0,
+ Module1 = 0,
+ Module2 = 0
+ }
+ },
+ Rewards2 = new RewardInfoData[] { },
+ Stats = new StatInfo[]
+ {
+ new StatInfo()
+ {
+ NameId = _finalTime, Type = StatInfo.StatType.Time, Value = time / 1000f, Unk3 = string.Empty,
+ }
+ },
+ Unk1 = 0,
+ IndexId = 1,
+ DisplayQuality = 0,
+ ScreenType = 0,
+ ResourceTargetType = 0,
+ TitleTextId = _lgvRace,
+ };
+
+ SoloParticipant.NetChannels[ChannelType.ReliableGss].SendMessage(msg, SoloParticipant.CharacterEntity.EntityId);
+
+ _ = GRPCService.SendCommandAsync(new Command()
+ {
+ SaveLgvRaceFinish = new SaveLgvRaceFinish()
+ {
+ CharacterGuid = SoloParticipant.CharacterId + 0xFE,
+ LeaderboardId = leaderboardId,
+ TimeMs = time,
+ }
+ });
+
+ base.OnSuccess();
+ }
+
+ public override void OnFailure()
+ {
+ RemoveEntities();
+
+ base.OnFailure();
+ }
+
+ private void RemoveEntities()
+ {
+ Shard.EntityMan.Remove(finishLine);
+ SoloParticipant.CharacterEntity.RemoveEncounterMapMarkers(EntityId);
+ }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Encounters/Encounters/MeldingRepulsor.cs b/UdpHosts/GameServer/Systems/Encounters/Encounters/MeldingRepulsor.cs
index 4609106a..a7c71083 100644
--- a/UdpHosts/GameServer/Systems/Encounters/Encounters/MeldingRepulsor.cs
+++ b/UdpHosts/GameServer/Systems/Encounters/Encounters/MeldingRepulsor.cs
@@ -10,6 +10,7 @@
using GameServer.Entities.Character;
using GameServer.Entities.Deployable;
using GameServer.Entities.Melding;
+using GameServer.StaticDB.Records.customdata.Encounters;
namespace GameServer.Systems.Encounters.Encounters;
@@ -35,14 +36,14 @@ public class MeldingRepulsor : BaseEncounter, IInteractionHandler, IDonationHand
private ulong StartTime = 0;
private uint MeldedCrystite = 0;
- public MeldingRepulsor(IShard shard, ulong entityId, HashSet participants, Data.SDB.Records.customdata.MeldingRepulsor repulsor)
+ public MeldingRepulsor(IShard shard, ulong entityId, HashSet participants, MeldingRepulsorDef repulsorDef)
: base(shard, entityId, participants)
{
- var r = repulsor.Repulsor;
+ var r = repulsorDef.Repulsor;
_repulsor = Shard.EntityMan.SpawnDeployable(r.Type, r.Position, r.Orientation);
_repulsor.Encounter = new EncounterComponent { EncounterId = entityId, Instance = this, Events = EncounterComponent.Event.Signal };
- var t = repulsor.Terminal;
+ var t = repulsorDef.Terminal;
_terminal = Shard.EntityMan.SpawnDeployable(t.Type, t.Position, t.Orientation);
_terminal.Encounter = new EncounterComponent() { EncounterId = entityId, Instance = this, Events = EncounterComponent.Event.Interaction };
@@ -56,13 +57,13 @@ public MeldingRepulsor(IShard shard, ulong entityId, HashSet par
};
_melding = (MeldingEntity)Shard.Entities.Values.First(e => e is MeldingEntity meldingEntity
- && meldingEntity.PerimiterSetName == repulsor.PerimiterSetName);
+ && meldingEntity.PerimiterSetName == repulsorDef.PerimiterSetName);
var adp = _melding.Melding_ObserverView.ActiveDataProp;
- _controlPointIndex = repulsor.MeldingPosition.ControlPointIndex;
+ _controlPointIndex = repulsorDef.MeldingPosition.ControlPointIndex;
- _startPosition = repulsor.MeldingPosition.Position;
+ _startPosition = repulsorDef.MeldingPosition.Position;
_endPosition = adp.ControlPoints_1[_controlPointIndex];
adp.ControlPoints_1[_controlPointIndex] = _startPosition;
diff --git a/UdpHosts/GameServer/Systems/Encounters/Factory.cs b/UdpHosts/GameServer/Systems/Encounters/Factory.cs
new file mode 100644
index 00000000..1b1ec58e
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Encounters/Factory.cs
@@ -0,0 +1,21 @@
+using AeroMessages.Common;
+using GameServer.Entities.Character;
+using GameServer.StaticDB.Records.customdata.Encounters;
+using LgvRace = GameServer.Systems.Encounters.Encounters.LgvRace;
+
+namespace GameServer.Systems.Encounters;
+
+public class Factory(Shard shard)
+{
+ public void SpawnEncounter(IEncounterDef def, CharacterEntity initiator)
+ {
+ var guid = shard.GetNextGuid((byte)Controller.Encounter);
+
+ switch (def)
+ {
+ case LgvRaceDef lgvRace:
+ shard.EncounterMan.Add(guid, new LgvRace(shard, guid, initiator.Player, lgvRace));
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/Encounters/IEncounterHandlers.cs b/UdpHosts/GameServer/Systems/Encounters/IEncounterHandlers.cs
index 1cf7e1cb..83ce8f22 100644
--- a/UdpHosts/GameServer/Systems/Encounters/IEncounterHandlers.cs
+++ b/UdpHosts/GameServer/Systems/Encounters/IEncounterHandlers.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using AeroMessages.GSS.V66.Character.Command;
using GameServer.Entities;
@@ -12,3 +13,14 @@ public interface IDonationHandler
{
void OnDonation(UiQueryResponse response, INetworkPlayer player);
}
+
+public interface IProximityHandler
+{
+ public HashSet Participants { get; }
+ void OnProximity(BaseEntity sourceEntity, INetworkPlayer player);
+}
+
+public interface IExitAttachmentHandler
+{
+ void OnExitAttachment(BaseEntity targetEntity, INetworkPlayer player);
+}
diff --git a/UdpHosts/GameServer/Systems/Encounters/MapMarkerState.cs b/UdpHosts/GameServer/Systems/Encounters/MapMarkerState.cs
new file mode 100644
index 00000000..a26f0541
--- /dev/null
+++ b/UdpHosts/GameServer/Systems/Encounters/MapMarkerState.cs
@@ -0,0 +1,9 @@
+using AeroMessages.Common;
+
+namespace GameServer.Systems.Encounters;
+
+public class MapMarkerState
+{
+ public EntityId EncounterId;
+ public EntityId EncounterMarkerId;
+}
\ No newline at end of file
diff --git a/UdpHosts/GameServer/Systems/EntityManager/EntityManager.cs b/UdpHosts/GameServer/Systems/EntityManager/EntityManager.cs
index 83ad96cd..bed63654 100644
--- a/UdpHosts/GameServer/Systems/EntityManager/EntityManager.cs
+++ b/UdpHosts/GameServer/Systems/EntityManager/EntityManager.cs
@@ -20,6 +20,7 @@
using GameServer.Data.SDB.Records.aptfs;
using GameServer.Data.SDB.Records.customdata;
using GameServer.Entities;
+using GameServer.Entities.AreaVisualData;
using GameServer.Entities.Carryable;
using GameServer.Entities.Character;
using GameServer.Entities.Deployable;
@@ -63,9 +64,12 @@ public EntityManager(Shard shard)
public int GetNumberOfScopedEntities(IPlayer player)
{
- return ScopedPlayersByEntity.Values
- .Where((set) => set.Contains(player))
- .Count();
+ return ScopedPlayersByEntity.Values.Count(set => set.Contains(player));
+ }
+
+ public bool HasScopedInEntity(ulong entityId, INetworkPlayer player)
+ {
+ return ScopedPlayersByEntity.TryGetValue(entityId, out var players) && players.Contains(player);
}
public CharacterEntity SpawnCharacter(uint typeId, Vector3 position)
@@ -106,23 +110,19 @@ public VehicleEntity SpawnVehicle(ushort typeId, Vector3 position, Quaternion or
{
vehicleEntity.SetOwner(owner as BaseEntity);
- if (owner.GetType() == typeof(CharacterEntity))
+ if (owner is CharacterEntity { IsPlayerControlled: true } character)
{
- var character = owner as CharacterEntity;
- if (character.IsPlayerControlled)
- {
- vehicleEntity.SetOwningPlayer(character.Player);
- }
- }
-
- if (autoMount)
- {
- // TODO:
+ vehicleEntity.SetOwningPlayer(character.Player);
}
}
Add(vehicleEntity.EntityId, vehicleEntity);
+ if (owner is CharacterEntity c && autoMount)
+ {
+ vehicleEntity.AddOccupant(c);
+ }
+
if (vehicleEntity.SpawnAbility != 0)
{
Shard.Abilities.HandleActivateAbility(Shard, vehicleEntity, vehicleEntity.SpawnAbility, Shard.CurrentTime, new AptitudeTargets());
@@ -229,6 +229,16 @@ public MeldingEntity SpawnMelding(string perimiterSetName, ActiveDataStruct acti
return meldingEntity;
}
+ public AreaVisualDataEntity SpawnAreaVisualData(Vector3 position, ScopingComponent scoping)
+ {
+ var areaVisualData = new AreaVisualDataEntity(Shard, Shard.GetNextGuid())
+ {
+ Scoping = scoping, Position = position,
+ };
+ Add(areaVisualData.EntityId, areaVisualData);
+ return areaVisualData;
+ }
+
public OutpostEntity SpawnOutpost(Outpost outpost)
{
var outpostEntity = new OutpostEntity(Shard, Shard.GetNextGuid(), outpost);
@@ -693,9 +703,105 @@ public void KeyframeRequest(INetworkClient client, IPlayer player, IEntity entit
}
break;
+ case AreaVisualDataEntity areaVisualData:
+ switch (typecode)
+ {
+ case Enums.GSS.Controllers.AreaVisualData_ObserverView:
+ if (areaVisualData.AreaVisualData_ObserverView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_ObserverView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_ObserverView, entity.EntityId);
+ }
+ }
+
+ break;
+ case Enums.GSS.Controllers.AreaVisualData_ParticleEffectsView:
+ if (areaVisualData.AreaVisualData_ParticleEffectsView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_ParticleEffectsView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_ParticleEffectsView, entity.EntityId);
+ }
+ }
- // TODO: AreaVisualData
- // --
+ break;
+ case Enums.GSS.Controllers.AreaVisualData_MapMarkerView:
+ if (areaVisualData.AreaVisualData_MapMarkerView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_MapMarkerView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_MapMarkerView, entity.EntityId);
+ }
+ }
+
+ break;
+ case Enums.GSS.Controllers.AreaVisualData_TinyObjectView:
+ if (areaVisualData.AreaVisualData_TinyObjectView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_TinyObjectView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_TinyObjectView, entity.EntityId);
+ }
+ }
+
+ break;
+ case Enums.GSS.Controllers.AreaVisualData_LootObjectView:
+ if (areaVisualData.AreaVisualData_LootObjectView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_LootObjectView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_LootObjectView, entity.EntityId);
+ }
+ }
+
+ break;
+ case Enums.GSS.Controllers.AreaVisualData_ForceShieldView:
+ if (areaVisualData.AreaVisualData_ForceShieldView != null)
+ {
+ uint ourChecksum = areaVisualData.AreaVisualData_ForceShieldView.SerializeToChecksum();
+ if (clientChecksum == ourChecksum)
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendChecksum(entity.EntityId, typecode, clientChecksum);
+ }
+ else
+ {
+ client.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(areaVisualData.AreaVisualData_ForceShieldView, entity.EntityId);
+ }
+ }
+
+ break;
+ default:
+ Console.WriteLine($"Unhandled KeyframeRequest for {typecode}");
+ break;
+ }
+
+ break;
case VehicleEntity vehicle:
bool isVehicleController = vehicle.IsPlayerControlled && vehicle.ControllingPlayer == player;
switch (typecode)
@@ -1108,6 +1214,44 @@ public void ScopeIn(INetworkPlayer player, IEntity entity)
player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(observer, entity.EntityId);
}
}
+ else if (entity is AreaVisualDataEntity avd)
+ {
+ var observer = avd.AreaVisualData_ObserverView;
+ if (observer != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(observer, entity.EntityId);
+ }
+
+ var particleEffects = avd.AreaVisualData_ParticleEffectsView;
+ if (particleEffects != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(particleEffects, entity.EntityId);
+ }
+
+ var mapMarker = avd.AreaVisualData_MapMarkerView;
+ if (mapMarker != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(mapMarker, entity.EntityId);
+ }
+
+ var tinyObject = avd.AreaVisualData_TinyObjectView;
+ if (tinyObject != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(tinyObject, entity.EntityId);
+ }
+
+ var lootObject = avd.AreaVisualData_LootObjectView;
+ if (lootObject != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(lootObject, entity.EntityId);
+ }
+
+ var forceShield = avd.AreaVisualData_ForceShieldView;
+ if (forceShield != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewKeyframe(forceShield, entity.EntityId);
+ }
+ }
}
public void ScopeOut(INetworkPlayer player, IEntity entity)
@@ -1332,6 +1476,44 @@ public void ScopeOut(INetworkPlayer player, IEntity entity)
player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(observer, entity.EntityId);
}
}
+ else if (entity is AreaVisualDataEntity avd)
+ {
+ var observer = avd.AreaVisualData_ObserverView;
+ if (observer != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(observer, entity.EntityId);
+ }
+
+ var particleEffects = avd.AreaVisualData_ParticleEffectsView;
+ if (particleEffects != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(particleEffects, entity.EntityId);
+ }
+
+ var mapMarker = avd.AreaVisualData_MapMarkerView;
+ if (mapMarker != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(mapMarker, entity.EntityId);
+ }
+
+ var tinyObject = avd.AreaVisualData_TinyObjectView;
+ if (tinyObject != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(tinyObject, entity.EntityId);
+ }
+
+ var lootObject = avd.AreaVisualData_LootObjectView;
+ if (lootObject != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(lootObject, entity.EntityId);
+ }
+
+ var forceShield = avd.AreaVisualData_ForceShieldView;
+ if (forceShield != null)
+ {
+ player.NetChannels[ChannelType.ReliableGss].SendViewScopeOut(forceShield, entity.EntityId);
+ }
+ }
}
public void RemoveControllers(INetworkPlayer player, IEntity entity)
@@ -1446,6 +1628,15 @@ public void FlushChanges(IEntity entity)
var thumper = entity as ThumperEntity;
FlushViewChangesToEveryone(thumper.ResourceNode_ObserverView, thumper.EntityId);
}
+ else if (entity is AreaVisualDataEntity avd)
+ {
+ FlushViewChangesToEveryone(avd.AreaVisualData_ObserverView, avd.EntityId);
+ FlushViewChangesToEveryone(avd.AreaVisualData_ParticleEffectsView, avd.EntityId);
+ FlushViewChangesToEveryone(avd.AreaVisualData_MapMarkerView, avd.EntityId);
+ FlushViewChangesToEveryone(avd.AreaVisualData_TinyObjectView, avd.EntityId);
+ FlushViewChangesToEveryone(avd.AreaVisualData_LootObjectView, avd.EntityId);
+ FlushViewChangesToEveryone(avd.AreaVisualData_ForceShieldView, avd.EntityId);
+ }
}
public void FlushViewChangesToPlayer(TPacket view, ulong entityId, INetworkPlayer player)