From 8827df138ec3f44023de083502fa34c43075e2f2 Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Mon, 25 Dec 2023 01:30:45 +0100 Subject: [PATCH] Server: WIP Implementation of (weapon) item equipment. One serialization bug and corresponding redscript implementations of equipping the weapon remain --- client/RedscriptModule/src/Cyberverse.reds | 27 +++++++--- .../src/Network/PlayerActionTracker.reds | 2 + client/red4ext/src/NetworkGameSystem.h | 1 + client/red4ext/src/PlayerActionTracker.cpp | 22 +++++++++ client/red4ext/src/PlayerActionTracker.h | 5 ++ .../Clientbound/EMessageTypeClientbound.cs | 3 +- .../Protocol/Clientbound/EquipItemEntity.cs | 18 +++++++ .../Serverbound/EMessageTypeServerbound.cs | 3 +- .../Protocol/Serverbound/PlayerEquipItem.cs | 12 +++++ .../PacketHandling/PlayerPacketHandler.cs | 49 ++++++++++++++++++- server/Native/src/GameServer.cpp | 19 +++++++ .../clientbound/EMessageTypeClientbound.h | 3 +- .../clientbound/WorldPacketsClientBound.h | 14 ++++++ .../serverbound/EMessageTypeServerbound.h | 3 +- .../serverbound/WorldPacketsServerBound.h | 13 +++++ 15 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 server/Managed/NativeLayer/Protocol/Clientbound/EquipItemEntity.cs create mode 100644 server/Managed/NativeLayer/Protocol/Serverbound/PlayerEquipItem.cs diff --git a/client/RedscriptModule/src/Cyberverse.reds b/client/RedscriptModule/src/Cyberverse.reds index 07cdcae..0d4fac4 100644 --- a/client/RedscriptModule/src/Cyberverse.reds +++ b/client/RedscriptModule/src/Cyberverse.reds @@ -79,6 +79,21 @@ protected cb func OnUnmountingEvent(evt: ref) -> Bool { GameInstance.GetNetworkGameSystem().playerActionTracker.OnUnmounting(evt); return result; } + +// replaces the OnWeapoNEquipEvent as we need the counterpart for unequiping anyway? +// protected cb func OnWeaponEquipEvent(evt: ref) -> Bool { +@wrapMethod(PlayerPuppet) +public final func OnItemEquipped(slot: TweakDBID, item: ItemID) -> Void { + let isWeapon = RPGManager.IsItemWeapon(item); + GameInstance.GetNetworkGameSystem().playerActionTracker.OnItemEquipped(slot, item, isWeapon); +} + +@wrapMethod(PlayerPuppet) +public final func OnItemUnequipped(slot: TweakDBID, item: ItemID) -> Void { + let isWeapon = RPGManager.IsItemWeapon(item); + GameInstance.GetNetworkGameSystem().playerActionTracker.OnItemUnequipped(slot, item, isWeapon); +} + @wrapMethod(BaseProjectile) protected cb func OnShoot(eventData: ref) -> Bool { wrappedMethod(eventData); @@ -92,12 +107,12 @@ protected cb func OnShootTarget(eventData: ref) FTLog("Shoot Target"); } -@wrapMethod(GameObject) -protected cb func OnHit(evt: ref) -> Bool { - wrappedMethod(evt); - FTLog("OnHit"); - GameInstance.GetNetworkGameSystem().playerActionTracker.OnHit(this, evt); -} +// @wrapMethod(GameObject) +// protected cb func OnHit(evt: ref) -> Bool { +// wrappedMethod(evt); +// FTLog("OnHit"); +// GameInstance.GetNetworkGameSystem().playerActionTracker.OnHit(this, evt); +// } // @wrapMethod(JumpEvents) // protected cb func OnEnter(stateContext: ref, scriptInterface: ref) -> Void {} diff --git a/client/RedscriptModule/src/Network/PlayerActionTracker.reds b/client/RedscriptModule/src/Network/PlayerActionTracker.reds index ee69225..6b328bd 100644 --- a/client/RedscriptModule/src/Network/PlayerActionTracker.reds +++ b/client/RedscriptModule/src/Network/PlayerActionTracker.reds @@ -6,4 +6,6 @@ public native class PlayerActionTracker { public native func OnHit(gameObject: ref, event: ref); public native func OnMounting(evt: ref); public native func OnUnmounting(evt: ref); + public native func OnItemEquipped(slot: TweakDBID, item: ItemID, isWeapon: Bool); + public native func OnItemUnequipped(slot: TweakDBID, item: ItemID, isWeapon: Bool); } diff --git a/client/red4ext/src/NetworkGameSystem.h b/client/red4ext/src/NetworkGameSystem.h index 634493a..ea65d75 100644 --- a/client/red4ext/src/NetworkGameSystem.h +++ b/client/red4ext/src/NetworkGameSystem.h @@ -88,5 +88,6 @@ RTTI_DEFINE_CLASS(NetworkGameSystem, { template bool NetworkGameSystem::EnqueueMessage(uint8_t channel_id, PlayerActionTracked msg); template bool NetworkGameSystem::EnqueueMessage(uint8_t channel_id, PlayerSpawnCar msg); template bool NetworkGameSystem::EnqueueMessage(uint8_t channel_id, PlayerUnmountCar msg); +template bool NetworkGameSystem::EnqueueMessage(uint8_t channel_id, PlayerEquipItem msg); #endif //NETWORKMANAGERCONTROLLER_H diff --git a/client/red4ext/src/PlayerActionTracker.cpp b/client/red4ext/src/PlayerActionTracker.cpp index 059dc53..23a13c1 100644 --- a/client/red4ext/src/PlayerActionTracker.cpp +++ b/client/red4ext/src/PlayerActionTracker.cpp @@ -138,3 +138,25 @@ void PlayerActionTracker::OnUnmounting(RED4ext::Handle()->EnqueueMessage(0, unmount_car); } + +void PlayerActionTracker::OnItemEquipped(const RED4ext::TweakDBID slot, const RED4ext::ItemID item, const bool isWeapon) +{ + SDK->logger->InfoF(PLUGIN, "Item Equipped at slot %llu with item %llu", slot.value, item.tdbid.value); + PlayerEquipItem player_equip = {}; + player_equip.slot = slot.value; + player_equip.itemId = item.tdbid.value; + player_equip.isWeapon = isWeapon; + player_equip.isUnequipping = false; + Red::GetGameSystem()->EnqueueMessage(0, player_equip); +} + +void PlayerActionTracker::OnItemUnequipped(const RED4ext::TweakDBID slot, const RED4ext::ItemID item, const bool isWeapon) +{ + SDK->logger->InfoF(PLUGIN, "Item Unequipped at slot %llu with item %llu", slot.value, item.tdbid.value); + PlayerEquipItem player_equip = {}; + player_equip.slot = slot.value; + player_equip.itemId = item.tdbid.value; + player_equip.isWeapon = isWeapon; + player_equip.isUnequipping = true; + Red::GetGameSystem()->EnqueueMessage(0, player_equip); +} diff --git a/client/red4ext/src/PlayerActionTracker.h b/client/red4ext/src/PlayerActionTracker.h index fe13b67..9e28173 100644 --- a/client/red4ext/src/PlayerActionTracker.h +++ b/client/red4ext/src/PlayerActionTracker.h @@ -10,6 +10,7 @@ #include "RED4ext/Scripting/Natives/Generated/game/mounting/MountingEvent.hpp" #include "RED4ext/Scripting/Natives/Generated/game/mounting/UnmountingEvent.hpp" #include "RED4ext/Scripting/Natives/Generated/game/projectile/ShootEvent.hpp" +#include "RED4ext/Scripting/Natives/Generated/game/weapon/Object.hpp" class PlayerActionTracker final : public Red::IScriptable { @@ -19,6 +20,8 @@ class PlayerActionTracker final : public Red::IScriptable void OnHit(RED4ext::Handle gameObject, RED4ext::Handle event); void OnMounting(RED4ext::Handle event); void OnUnmounting(RED4ext::Handle event); + void OnItemEquipped(RED4ext::TweakDBID slot, RED4ext::ItemID item, bool isWeapon); + void OnItemUnequipped(RED4ext::TweakDBID slot, RED4ext::ItemID item, bool isWeapon); private: RTTI_IMPL_TYPEINFO(PlayerActionTracker); RTTI_IMPL_ALLOCATOR(); @@ -30,5 +33,7 @@ RTTI_DEFINE_CLASS(PlayerActionTracker, { RTTI_METHOD(OnHit); RTTI_METHOD(OnMounting); RTTI_METHOD(OnUnmounting); + RTTI_METHOD(OnItemEquipped); + RTTI_METHOD(OnItemUnequipped); RTTI_ALIAS("Cyberverse.Network.Managers.PlayerActionTracker"); }); diff --git a/server/Managed/NativeLayer/Protocol/Clientbound/EMessageTypeClientbound.cs b/server/Managed/NativeLayer/Protocol/Clientbound/EMessageTypeClientbound.cs index 55c498e..426e1b6 100644 --- a/server/Managed/NativeLayer/Protocol/Clientbound/EMessageTypeClientbound.cs +++ b/server/Managed/NativeLayer/Protocol/Clientbound/EMessageTypeClientbound.cs @@ -5,5 +5,6 @@ public enum EMessageTypeClientbound: ushort InitAuthResult = 0, SpawnEntity = 1, TeleportEntity = 2, - DestroyEntity = 3 + DestroyEntity = 3, + EquipItemEntity = 4, } \ No newline at end of file diff --git a/server/Managed/NativeLayer/Protocol/Clientbound/EquipItemEntity.cs b/server/Managed/NativeLayer/Protocol/Clientbound/EquipItemEntity.cs new file mode 100644 index 0000000..6edf206 --- /dev/null +++ b/server/Managed/NativeLayer/Protocol/Clientbound/EquipItemEntity.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Cyberverse.Server.NativeLayer.Protocol.Clientbound; + +[StructLayout(LayoutKind.Sequential, Pack = 8)] +public struct EquipItemEntity: IClientBoundPacket +{ + public ulong NetworkedEntityId; + public ulong Slot; + public ulong ItemId; + public bool isWeapon; + public bool isUnequipping; + + public EMessageTypeClientbound GetMessageType() + { + return EMessageTypeClientbound.EquipItemEntity; + } +} \ No newline at end of file diff --git a/server/Managed/NativeLayer/Protocol/Serverbound/EMessageTypeServerbound.cs b/server/Managed/NativeLayer/Protocol/Serverbound/EMessageTypeServerbound.cs index ba82e6e..0188d2c 100644 --- a/server/Managed/NativeLayer/Protocol/Serverbound/EMessageTypeServerbound.cs +++ b/server/Managed/NativeLayer/Protocol/Serverbound/EMessageTypeServerbound.cs @@ -8,5 +8,6 @@ public enum EMessageTypeServerbound: ushort PlayerPositionUpdate = 3, PlayerSpawnCar = 4, PlayerMountCar = 5, // TODO: Implement - PlayerUnmountCar = 6 + PlayerUnmountCar = 6, + PlayerEquipItem = 7 } \ No newline at end of file diff --git a/server/Managed/NativeLayer/Protocol/Serverbound/PlayerEquipItem.cs b/server/Managed/NativeLayer/Protocol/Serverbound/PlayerEquipItem.cs new file mode 100644 index 0000000..b56c0d4 --- /dev/null +++ b/server/Managed/NativeLayer/Protocol/Serverbound/PlayerEquipItem.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Cyberverse.Server.NativeLayer.Protocol.Serverbound; + +[StructLayout(LayoutKind.Sequential, Pack = 8)] +public struct PlayerEquipItem +{ + public ulong slot; + public ulong itemId; + public bool isWeapon; + public bool isUnequipping; // TODO: For some reason this flag is always false. Is this a packing issue because the bools got bitpacked? +} \ No newline at end of file diff --git a/server/Managed/PacketHandling/PlayerPacketHandler.cs b/server/Managed/PacketHandling/PlayerPacketHandler.cs index c580416..348d73f 100644 --- a/server/Managed/PacketHandling/PlayerPacketHandler.cs +++ b/server/Managed/PacketHandling/PlayerPacketHandler.cs @@ -18,6 +18,7 @@ public class PlayerPacketHandler private readonly TypedPacketHandler _playerMoveHandler; private readonly TypedPacketHandler _playerSpawnCarHandler; private readonly TypedPacketHandler _playerUnmountHandler; + private readonly TypedPacketHandler _playerEquipHandler; private EntityTracker? _tracker = null; private PlayerService? _players = null; @@ -27,6 +28,7 @@ public PlayerPacketHandler() _playerMoveHandler = new TypedPacketHandler(HandlePositionUpdate); _playerSpawnCarHandler = new TypedPacketHandler(HandleSpawnCar); _playerUnmountHandler = new TypedPacketHandler(HandleUnmountCar); + _playerEquipHandler = new TypedPacketHandler(HandleEquip); } protected void HandleJoinWorld(GameServer server, EMessageTypeServerbound messageType, byte channelId, uint connectionId, PlayerJoinWorld content) @@ -132,6 +134,50 @@ protected void HandleUnmountCar(GameServer server, EMessageTypeServerbound messa DespawnAllVehiclesForPlayer(server, connectionId); } + private void HandleEquip(GameServer server, EMessageTypeServerbound messageType, byte channelId, + uint connectionId, PlayerEquipItem content) + { + Logger.Info($"Player did (un: {content.isUnequipping})equip an item (slot {content.slot}). Is it a weapon? {content.isWeapon}"); + if (content.isUnequipping) + { + // Try to find existing cars for that owner. + var existingItems = server.EntityService.SpawnedEntities + .Select(x => x.Value) + .Where(x => x.NetworkIdOwner == connectionId) + .Where(x => x.RecordId == content.itemId) + .ToList(); + + // TODO: As long as we don't have NetworkedEntityIds, we need to unequip all of those. + foreach (var item in existingItems) + { + item.NetworkIdOwner = 0; + _tracker!.StopTrackingOf(item); + server.EntityService.RemoveEntity(item.NetworkedEntityId); + } + } + else + { + // TODO: How are we supposed to flag things as items? dedicated maps? But it'll still be entities in the end. + // Also this is relevant when we have our own EquipItemEntity. Right now they are entities in the world. + var playerEntity = server.EntityService.SpawnedEntities + .Select(x => x.Value) + .FirstOrDefault(x => x.NetworkIdOwner == connectionId); + + if (playerEntity == null) + { + return; + } + + var entity = server.EntityService.CreateEntity(content.itemId); + entity.WorldTransform = playerEntity.WorldTransform; // TODO + entity.Yaw = playerEntity.Yaw; // TODO + entity.NetworkIdOwner = connectionId; + entity.IsVehicle = false; + + _tracker!.UpdateTrackingOf(entity); + } + } + public void RegisterOnServer(GameServer server) { _tracker = server.EntityTracker; // TODO: Service registry or even using DI @@ -141,5 +187,6 @@ public void RegisterOnServer(GameServer server) server.AddPacketHandler(EMessageTypeServerbound.PlayerPositionUpdate, _playerMoveHandler.HandlePacket); server.AddPacketHandler(EMessageTypeServerbound.PlayerSpawnCar, _playerSpawnCarHandler.HandlePacket); server.AddPacketHandler(EMessageTypeServerbound.PlayerUnmountCar, _playerUnmountHandler.HandlePacket); + server.AddPacketHandler(EMessageTypeServerbound.PlayerEquipItem, _playerEquipHandler.HandlePacket); } -} \ No newline at end of file +} diff --git a/server/Native/src/GameServer.cpp b/server/Native/src/GameServer.cpp index 17487d1..3d84c23 100644 --- a/server/Native/src/GameServer.cpp +++ b/server/Native/src/GameServer.cpp @@ -322,6 +322,20 @@ void GameServer::PollIncomingMessages() } break; + case ePlayerEquipItem: + { + PlayerEquipItem player_equip_item = {}; + if (zpp::bits::failure(in(player_equip_item))) + { + fprintf(stderr, "Faulty packet: PlayerEquipItem\n"); + pIncomingMsg->Release(); + continue; + } + + AddToRecvQueue(frame.message_type, pIncomingMsg->m_conn, frame.channel_id, player_equip_item); + } + break; + default: printf("Message Type: %d\n", frame.message_type); break; @@ -418,6 +432,11 @@ void GameServer::ProcessSendQueue() } break; + case eEquipItemEntity: + { + EnqueueMessage(val.connectionId, val.channelId, *reinterpret_cast(val.data)); + } + break; default: printf("Unknown messageType: %d\n", val.messageType); diff --git a/shared/protocol/clientbound/EMessageTypeClientbound.h b/shared/protocol/clientbound/EMessageTypeClientbound.h index a0a7d29..7b86d5c 100644 --- a/shared/protocol/clientbound/EMessageTypeClientbound.h +++ b/shared/protocol/clientbound/EMessageTypeClientbound.h @@ -6,5 +6,6 @@ enum EMessageTypeClientbound: uint16_t { EINIT_AUTH_RESULT = 0, eSpawnEntity = 1, eTeleportEntity = 2, - eDestroyEntity = 3 + eDestroyEntity = 3, + eEquipItemEntity = 4, }; \ No newline at end of file diff --git a/shared/protocol/clientbound/WorldPacketsClientBound.h b/shared/protocol/clientbound/WorldPacketsClientBound.h index a089f65..85d8d99 100644 --- a/shared/protocol/clientbound/WorldPacketsClientBound.h +++ b/shared/protocol/clientbound/WorldPacketsClientBound.h @@ -41,3 +41,17 @@ struct TeleportEntity frame.channel_id = 1; // TODO: } }; + +struct EquipItemEntity { + uint64_t networkedEntityId; + uint64_t slot; + uint64_t itemId; + bool isWeapon; + bool isUnequipping; + + inline static void FillMessageFrame(MessageFrame& frame) + { + frame.message_type = eEquipItemEntity; + frame.channel_id = 0; // TODO + } +}; diff --git a/shared/protocol/serverbound/EMessageTypeServerbound.h b/shared/protocol/serverbound/EMessageTypeServerbound.h index 3fc9433..a2279e0 100644 --- a/shared/protocol/serverbound/EMessageTypeServerbound.h +++ b/shared/protocol/serverbound/EMessageTypeServerbound.h @@ -9,5 +9,6 @@ enum EMessageTypeServerbound: uint16_t { ePlayerPositionUpdate = 3, ePlayerSpawnCar = 4, ePlayerMountCar = 5, // TODO: Implement - ePlayerUnmountCar = 6 + ePlayerUnmountCar = 6, + ePlayerEquipItem = 7 }; \ No newline at end of file diff --git a/shared/protocol/serverbound/WorldPacketsServerBound.h b/shared/protocol/serverbound/WorldPacketsServerBound.h index 27e4701..ca68047 100644 --- a/shared/protocol/serverbound/WorldPacketsServerBound.h +++ b/shared/protocol/serverbound/WorldPacketsServerBound.h @@ -67,4 +67,17 @@ struct PlayerUnmountCar { frame.message_type = ePlayerUnmountCar; frame.channel_id = 0; // TODO } +}; + +struct PlayerEquipItem { + uint64_t slot; + uint64_t itemId; + bool isWeapon; + bool isUnequipping; + + inline static void FillMessageFrame(MessageFrame& frame) + { + frame.message_type = ePlayerEquipItem; + frame.channel_id = 0; // TODO + } }; \ No newline at end of file