From 7e8f5caa98a6179662e9fa27396b1b81acc2c777 Mon Sep 17 00:00:00 2001 From: iAmir Date: Sat, 25 Oct 2025 02:47:14 +0330 Subject: [PATCH 1/7] fix aim re-use not clearing states --- Server/Components/NPCs/NPC/npc.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index 595460eb6..a6dbdda0f 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -1153,24 +1153,22 @@ void NPC::aimAt(const Vector3& point, bool shoot, int shootDelay, bool setAngle, return; } - // Set the aiming flag - if (!aiming_) + if (aiming_) { - // Get the shooting start tick - shootUpdateTime_ = lastUpdate_; - reloading_ = false; + stopAim(); } + // Get the shooting start tick + shootUpdateTime_ = lastUpdate_; + reloading_ = false; + // Update aiming data aimOffsetFrom_ = offsetFrom; updateAimData(point, setAngle); // Set keys - if (!aiming_) - { - aiming_ = true; - applyKey(Key::AIM); - } + applyKey(Key::AIM); + aiming_ = true; // Set the shoot delay auto updateRate = npcComponent_->getGeneralNPCUpdateRate(); From 6d9e7cb40daac54d5d58ab380a7ae6b767c0a681 Mon Sep 17 00:00:00 2001 From: iAmir Date: Sat, 25 Oct 2025 02:50:10 +0330 Subject: [PATCH 2/7] stop aiming at player if player leaves --- Server/Components/NPCs/npcs_impl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/Components/NPCs/npcs_impl.cpp b/Server/Components/NPCs/npcs_impl.cpp index 554b72237..e4a7e4796 100644 --- a/Server/Components/NPCs/npcs_impl.cpp +++ b/Server/Components/NPCs/npcs_impl.cpp @@ -187,6 +187,11 @@ void NPCComponent::onPoolEntryDestroyed(IPlayer& player) npc->stopMove(); npc->resetFollowingPlayer(); } + + if (npc->isAiming() && npc->isAimingAtPlayer(player)) + { + npc->stopAim(); + } } } From 1f207adb0ed25923499dd17f52d2457287c9aa6f Mon Sep 17 00:00:00 2001 From: iAmir Date: Sat, 25 Oct 2025 16:32:05 +0330 Subject: [PATCH 3/7] a lot of more NPC natives --- SDK | 2 +- Server/Components/NPCs/NPC/npc.cpp | 18 +++++ Server/Components/NPCs/NPC/npc.hpp | 15 ++-- Server/Components/NPCs/Playback/playback.hpp | 2 - Server/Components/NPCs/npcs_impl.cpp | 2 +- .../Components/Pawn/Scripting/NPC/Natives.cpp | 69 ++++++++++++++++++- 6 files changed, 98 insertions(+), 10 deletions(-) diff --git a/SDK b/SDK index 87ca2d60a..5c0ad89e3 160000 --- a/SDK +++ b/SDK @@ -1 +1 @@ -Subproject commit 87ca2d60acc07972e39440c5ce024b108261008a +Subproject commit 5c0ad89e3d5b4561f1b368da6289403b90abc3b3 diff --git a/Server/Components/NPCs/NPC/npc.cpp b/Server/Components/NPCs/NPC/npc.cpp index a6dbdda0f..d6cc69cd0 100644 --- a/Server/Components/NPCs/NPC/npc.cpp +++ b/Server/Components/NPCs/NPC/npc.cpp @@ -71,6 +71,7 @@ NPC::NPC(NPCComponent* component, IPlayer* playerPtr) , aimOffsetFrom_({ 0.0f, 0.0f, 0.0f }) , aimOffset_({ 0.0f, 0.0f, 0.0f }) , updateAimAngle_(false) + , playerAimingAt_(nullptr) , betweenCheckFlags_(EntityCheckType::None) , hitId_(0) , hitType_(PlayerBulletHitType_None) @@ -507,6 +508,11 @@ bool NPC::isMoving() const return moving_; } +bool NPC::isMovingToPlayer(IPlayer& player) const +{ + return followingPlayer_ != nullptr; +} + void NPC::setSkin(int model) { player_->setSkin(model); @@ -1194,6 +1200,7 @@ void NPC::aimAtPlayer(IPlayer& atPlayer, bool shoot, int shootDelay, bool setAng hitId_ = atPlayer.getID(); hitType_ = PlayerBulletHitType_Player; aimOffset_ = offset; + playerAimingAt_ = &atPlayer; } void NPC::stopAim() @@ -1217,6 +1224,7 @@ void NPC::stopAim() hitType_ = PlayerBulletHitType_None; updateAimAngle_ = false; betweenCheckFlags_ = EntityCheckType::None; + playerAimingAt_ = nullptr; // Reset keys removeKey(Key::AIM); @@ -1876,6 +1884,16 @@ void NPC::updateWeaponState() } } +IPlayer* NPC::getPlayerAimingAt() +{ + return playerAimingAt_; +} + +IPlayer* NPC::getPlayerMovingTo() +{ + return followingPlayer_; +} + void NPC::kill(IPlayer* killer, uint8_t weapon) { if (dead_) diff --git a/Server/Components/NPCs/NPC/npc.hpp b/Server/Components/NPCs/NPC/npc.hpp index c3ab1d8e7..460ce0ef3 100644 --- a/Server/Components/NPCs/NPC/npc.hpp +++ b/Server/Components/NPCs/NPC/npc.hpp @@ -50,6 +50,8 @@ class NPC : public INPC, public PoolIDProvider, public NoCopy bool isMoving() const override; + bool isMovingToPlayer(IPlayer& player) const override; + void setSkin(int model) override; bool isStreamedInForPlayer(const IPlayer& other) const override; @@ -112,6 +114,8 @@ class NPC : public INPC, public PoolIDProvider, public NoCopy PlayerWeaponState getWeaponState() const override; + void setWeaponState(PlayerWeaponState state) override; + void setAmmoInClip(int ammo) override; int getAmmoInClip() const override; @@ -246,16 +250,18 @@ class NPC : public INPC, public PoolIDProvider, public NoCopy void resetSurfingData() override; + void kill(IPlayer* killer, uint8_t weapon) override; + + IPlayer* getPlayerAimingAt() override; + + IPlayer* getPlayerMovingTo() override; + void setAnimation(uint16_t animationId, uint16_t flags); void processPlayback(TimePoint now); - void setWeaponState(PlayerWeaponState state); - void updateWeaponState(); - void kill(IPlayer* killer, uint8_t weapon); - void processDamage(IPlayer* damager, float damage, uint8_t weapon, BodyPart bodyPart, bool handleHealthAndArmour); void updateAim(); @@ -465,6 +471,7 @@ class NPC : public INPC, public PoolIDProvider, public NoCopy Vector3 aimOffsetFrom_; Vector3 aimOffset_; bool updateAimAngle_; + IPlayer* playerAimingAt_; // Weapon raycast/shot checks data EntityCheckType betweenCheckFlags_; diff --git a/Server/Components/NPCs/Playback/playback.hpp b/Server/Components/NPCs/Playback/playback.hpp index 0a05ad1b1..6ba383f21 100644 --- a/Server/Components/NPCs/Playback/playback.hpp +++ b/Server/Components/NPCs/Playback/playback.hpp @@ -27,8 +27,6 @@ struct NPCRecord DynamicArray vehicleData; }; -constexpr int INVALID_RECORD_ID = -1; - class NPC; class NPCRecordManager; diff --git a/Server/Components/NPCs/npcs_impl.cpp b/Server/Components/NPCs/npcs_impl.cpp index e4a7e4796..43a7c7160 100644 --- a/Server/Components/NPCs/npcs_impl.cpp +++ b/Server/Components/NPCs/npcs_impl.cpp @@ -289,7 +289,7 @@ void NPCComponent::destroy(INPC& npc) int NPCComponent::createPath() { NPCPath* path = pathManager_.create(); - return path ? path->getID() : -1; + return path ? path->getID() : INVALID_PATH_ID; } bool NPCComponent::destroyPath(int pathId) diff --git a/Server/Components/Pawn/Scripting/NPC/Natives.cpp b/Server/Components/Pawn/Scripting/NPC/Natives.cpp index 8358e8a9a..0acdcf473 100644 --- a/Server/Components/Pawn/Scripting/NPC/Natives.cpp +++ b/Server/Components/Pawn/Scripting/NPC/Natives.cpp @@ -123,12 +123,27 @@ SCRIPT_API(NPC_IsMoving, bool(INPC& npc)) return npc.isMoving(); } +SCRIPT_API(NPC_IsMovingToPlayer, bool(INPC& npc, IPlayer& player)) +{ + return npc.isMovingToPlayer(player); +} + SCRIPT_API(NPC_SetSkin, bool(INPC& npc, int model)) { npc.setSkin(model); return true; } +SCRIPT_API(NPC_GetSkin, bool(INPC& npc)) +{ + auto player = npc.getPlayer(); + if (player) + { + return player->getSkin(); + } + return -1; +} + SCRIPT_API(NPC_IsStreamedIn, bool(INPC& npc, IPlayer& player)) { return npc.isStreamedInForPlayer(player); @@ -932,7 +947,7 @@ SCRIPT_API(NPC_IsInvulnerable, bool(INPC& npc)) return npc.isInvulnerable(); } -SCRIPT_API(NPC_SetSurfingOffset, bool(INPC& npc, Vector3 offset)) +SCRIPT_API(NPC_SetSurfingOffsets, bool(INPC& npc, Vector3 offset)) { auto data = npc.getSurfingData(); data.offset = offset; @@ -940,7 +955,7 @@ SCRIPT_API(NPC_SetSurfingOffset, bool(INPC& npc, Vector3 offset)) return true; } -SCRIPT_API(NPC_GetSurfingOffset, bool(INPC& npc, Vector3& offset)) +SCRIPT_API(NPC_GetSurfingOffsets, bool(INPC& npc, Vector3& offset)) { auto data = npc.getSurfingData(); offset = data.offset; @@ -1023,3 +1038,53 @@ SCRIPT_API(NPC_ResetSurfingData, bool(INPC& npc)) npc.resetSurfingData(); return true; } + +SCRIPT_API(NPC_IsSpawned, bool(INPC& npc)) +{ + auto player = npc.getPlayer(); + if (player) + { + auto state = player->getState(); + if (state == PlayerState_OnFoot || state == PlayerState_Driver || state == PlayerState_Passenger || state == PlayerState_Spawned) + { + return true; + } + } + return false; +} + +SCRIPT_API(NPC_Kill, bool(INPC& npc, IPlayer* killer, int reason)) +{ + npc.kill(killer, reason); + return true; +} + +SCRIPT_API(NPC_SetVelocity, bool(INPC& npc, Vector3 velocity)) +{ + npc.setVelocity(velocity, true); + return true; +} + +SCRIPT_API(NPC_GetVelocity, bool(INPC& npc, Vector3& velocity)) +{ + velocity = npc.getVelocity(); + return true; +} + +SCRIPT_API(NPC_GetPlayerAimingAt, int(INPC& npc)) +{ + auto player = npc.getPlayerAimingAt(); + return player ? player->getID() : INVALID_PLAYER_ID; +} + +SCRIPT_API(NPC_GetPlayerMovingTo, int(INPC& npc)) +{ + auto player = npc.getPlayerMovingTo(); + return player ? player->getID() : INVALID_PLAYER_ID; +} + +SCRIPT_API(NPC_SetWeaponState, bool(INPC& npc, int weaponState)) +{ + npc.setWeaponState(PlayerWeaponState(weaponState)); + return true; +} From 8289408f9657b6f2b70ba184e5fae90eb1cda4ad Mon Sep 17 00:00:00 2001 From: iAmir Date: Mon, 27 Oct 2025 08:28:30 +0330 Subject: [PATCH 4/7] update sdk to fix formatting --- SDK | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDK b/SDK index 5c0ad89e3..e058e16f9 160000 --- a/SDK +++ b/SDK @@ -1 +1 @@ -Subproject commit 5c0ad89e3d5b4561f1b368da6289403b90abc3b3 +Subproject commit e058e16f9cc6ab4af6fdc2b17f3457823f827e72 From 15ea02f93e8df516ce29cc74b02b0d988fd596b3 Mon Sep 17 00:00:00 2001 From: iAmir Date: Mon, 27 Oct 2025 21:30:21 +0330 Subject: [PATCH 5/7] update capi submodule --- CAPI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAPI b/CAPI index 55afceb0f..cba506bca 160000 --- a/CAPI +++ b/CAPI @@ -1 +1 @@ -Subproject commit 55afceb0fc2c31bf74ab752d76b572ba8c6302ca +Subproject commit cba506bca2be519afae885f52badb99e75f9c408 From ad253af75cc348e0ba0235c2acada13eb66e1435 Mon Sep 17 00:00:00 2001 From: iAmir Date: Mon, 27 Oct 2025 21:45:40 +0330 Subject: [PATCH 6/7] add npc id to npc capi NPC_Create --- CAPI | 2 +- Server/Components/CAPI/Impl/NPCs/APIs.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CAPI b/CAPI index cba506bca..fba112f29 160000 --- a/CAPI +++ b/CAPI @@ -1 +1 @@ -Subproject commit cba506bca2be519afae885f52badb99e75f9c408 +Subproject commit fba112f295c4febbac3fc335e0c25bef1cee0f73 diff --git a/Server/Components/CAPI/Impl/NPCs/APIs.cpp b/Server/Components/CAPI/Impl/NPCs/APIs.cpp index 320962932..1f8973897 100644 --- a/Server/Components/CAPI/Impl/NPCs/APIs.cpp +++ b/Server/Components/CAPI/Impl/NPCs/APIs.cpp @@ -9,7 +9,7 @@ #include "../ComponentManager.hpp" #include -OMP_CAPI(NPC_Create, objectPtr(const char* name)) +OMP_CAPI(NPC_Create, objectPtr(StringCharPtr name, int* id)) { COMPONENT_CHECK_RET(npcs, nullptr); if (name) @@ -17,6 +17,7 @@ OMP_CAPI(NPC_Create, objectPtr(const char* name)) auto npc = npcs->create(name); if (npc) { + *id = npc->getID(); return npc; } } @@ -782,7 +783,7 @@ OMP_CAPI(NPC_GetAnimation, bool(objectPtr npc, int* animationId, float* delta, b return true; } -OMP_CAPI(NPC_ApplyAnimation, bool(objectPtr npc, const char* animlib, const char* animname, float delta, bool loop, bool lockX, bool lockY, bool freeze, int time)) +OMP_CAPI(NPC_ApplyAnimation, bool(objectPtr npc, StringCharPtr animlib, StringCharPtr animname, float delta, bool loop, bool lockX, bool lockY, bool freeze, int time)) { POOL_ENTITY_RET(npcs, INPC, npc, npc_, false); if (!animlib || !animname) @@ -814,7 +815,7 @@ OMP_CAPI(NPC_GetSpecialAction, int(objectPtr npc)) return npc_->getSpecialAction(); } -OMP_CAPI(NPC_StartPlayback, bool(objectPtr npc, const char* recordName, bool autoUnload, float startPosX, float startPosY, float startPosZ, float startRotX, float startRotY, float startRotZ)) +OMP_CAPI(NPC_StartPlayback, bool(objectPtr npc, StringCharPtr recordName, bool autoUnload, float startPosX, float startPosY, float startPosZ, float startRotX, float startRotY, float startRotZ)) { POOL_ENTITY_RET(npcs, INPC, npc, npc_, false); if (!recordName) @@ -856,7 +857,7 @@ OMP_CAPI(NPC_IsPlaybackPaused, bool(objectPtr npc)) return npc_->isPlaybackPaused(); } -OMP_CAPI(NPC_LoadRecord, int(const char* filePath)) +OMP_CAPI(NPC_LoadRecord, int(StringCharPtr filePath)) { COMPONENT_CHECK_RET(npcs, -1); if (filePath) From 5db04b88f8f7ab1498f969348e88ef1e8574eaa7 Mon Sep 17 00:00:00 2001 From: iAmir Date: Tue, 28 Oct 2025 18:56:53 +0330 Subject: [PATCH 7/7] add capi NPC_GetPlayer --- CAPI | 2 +- Server/Components/CAPI/Impl/NPCs/APIs.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CAPI b/CAPI index fba112f29..5ea885b03 160000 --- a/CAPI +++ b/CAPI @@ -1 +1 @@ -Subproject commit fba112f295c4febbac3fc335e0c25bef1cee0f73 +Subproject commit 5ea885b03e2a003725cda0655f33e9595922ddd9 diff --git a/Server/Components/CAPI/Impl/NPCs/APIs.cpp b/Server/Components/CAPI/Impl/NPCs/APIs.cpp index 1f8973897..53c9c0baf 100644 --- a/Server/Components/CAPI/Impl/NPCs/APIs.cpp +++ b/Server/Components/CAPI/Impl/NPCs/APIs.cpp @@ -49,6 +49,12 @@ OMP_CAPI(NPC_IsValid, bool(objectPtr npc)) return npcs->get(npc_->getID()) != nullptr; } +OMP_CAPI(NPC_GetPlayer, objectPtr(objectPtr npc)) +{ + POOL_ENTITY_RET(npcs, INPC, npc, npc_, nullptr); + return npc_->getPlayer(); +} + OMP_CAPI(NPC_Spawn, bool(objectPtr npc)) { POOL_ENTITY_RET(npcs, INPC, npc, npc_, false);