Jump to content

tsuyoshi_ito

Member
  • Posts

    17
  • Joined

  • Last visited

Everything posted by tsuyoshi_ito

  1. Hello, First of all, thank you for including our sleep-transition fix for stationary NPCs in 3.4.0. We confirmed that `UpdateStationary()` and the guard in `UpdateSleep()` are still there in 3.4.1, and that path no longer warps our NPCs under the floor. Unfortunately, after the September 2026 Rust update **the same symptom came back through a different code path**. The cause this time is not the sleep system but the moment the NPC is created. Your previous fix is intact and still doing its job. ### The symptom Static NPCs from Dynamic Monuments (by Adem) stand under the monument floor, on the terrain. Unlike the previous bug, this happens **regardless of whether a player is nearby**: the NPC is already under the floor from the instant it spawns, without waiting for any sleep transition. ### The cause (a change on the Rust side) Rust's newer navigation agent, `Rust.Ai.Gen2.RustNavMeshAgent`, **synchronizes its own position to the nearest navmesh the moment the component is enabled** (`OnEnable`). Here is the relevant code from the decompiled server `Assembly-CSharp.dll`: public RustNavMeshAgent() { ... _updatePosition = true; // constructor default is true ... } private void OnEnable() { ... if (AI.useUnityNavmesh) // defaults to true { _nextPositionNS = WorldToNavSpace(transform.position); if (SamplePositionPoly(_nextPositionNS, out var hitNS, 10f, out _)) // searches for navmesh within 10 m _nextPositionNS = hitNS.position; } else { ... TryBindNavmesh(); // also samples with a 10 m radius } previousPositionNS = _nextPositionNS; TrySyncWorldPosWithNavPos(); // rewrites transform.position while _updatePosition is true } When the sampled navmesh point is more than 1 m above or below the original position, SamplePositionPoly casts a physics ray (3.5 m) to find the ground under the entity and prefers navmesh within 1 m of that ground. Runtime-spawned monument floors carry no navmesh, so that correction never applies and the terrain navmesh below the floor (within 10 m) is used as-is. This position sync is what NPCPlayer.ServerInit() turns off: NavAgent.updateRotation = false; NavAgent.updatePosition = false; ### Why this affects NpcSpawn `CreateCustomNpc()` (around line 1126 in 3.4.1) activates the NPC **before** spawning it: ScientistNPC scientistNpc = GameManager.server.CreateEntity(config.Prefab, position, Quaternion.identity, false); // created inactive ... customScientist.enableSaving = false; customScientist.gameObject.AwakeFromInstantiate(); // SetActive(true) -> RustNavMeshAgent.OnEnable() runs and moves the NPC under the floor customScientist.Spawn(); // -> ServerInit() sets updatePosition = false, but it is already too late The position is rewritten during AwakeFromInstantiate(), and disabling the sync afterwards in ServerInit() does not restore it. DisableNavAgent() in AddStates() runs even later (Unity's Start), so it cannot help either. This only hurts stationary NPCs. For mobile NPCs the re-projection at spawn time is desirable, since they need to walk on the navmesh anyway. Stationary NPCs, however, are meant to keep the exact position given to SpawnNpc with their NavAgent disabled, so for them the re-projection is both unnecessary and harmful. Steps to reproduce Create a preset with Speed = 0 (or States containing only IdleState + CombatStationaryState). The value of CanSleep does not matter. Spawn any prefab with a walkable floor at runtime and place an NPC on that floor through the SpawnNpc API, with terrain less than 10 m below it. The NPC immediately stands on the terrain under the floor. This happens even with a player standing right next to it — that is the difference from the previous sleep-related bug. If the floor is more than 10 m above the terrain, the problem does not occur, because SamplePositionPoly searches within 10 m. The fix we applied (verified on our server) In CreateCustomNpc(), we set updatePosition to false for stationary NPCs before calling AwakeFromInstantiate(): customScientist.enableSaving = false; customScientist.UpdateStationary(); if (customScientist.IsStationary) { Rust.Ai.Gen2.RustNavMeshAgent navAgent = customScientist.GetComponent<Rust.Ai.Gen2.RustNavMeshAgent>(); if (navAgent != null) navAgent.updatePosition = false; } customScientist.gameObject.AwakeFromInstantiate(); customScientist.Spawn(); UpdateStationary() is the existing public method you added in 3.4.0. It only reads Config, so it is safe to call at this point, and since AddStates() calls the same method there is no second copy of the stationary condition to keep in sync. updatePosition is a value that NPCPlayer.ServerInit() sets to false anyway, so nothing changes for stationary NPCs afterwards — we only set it earlier. Mobile NPCs are untouched and keep the navmesh re-projection at spawn time. If you prefer a different approach, calling SetActive(true) after Spawn(), or disabling the NavAgent for stationary NPCs inside CreateCustomNpc() instead of waiting for AddStates(), would work as well. The key point is that the position sync has to be turned off for stationary NPCs before the RustNavMeshAgent is enabled. After deploying this change on our server, stationary NPCs no longer end up under the floors of runtime-spawned monuments. Patch and how to apply it The attached NpcSpawn-stationary-spawn.patch is a unified diff against NpcSpawn.cs 3.4.1. Put the patch file in the same directory as NpcSpawn.cs and apply it with either of: git apply NpcSpawn-stationary-spawn.patch (inside a git repository) patch -p1 < NpcSpawn-stationary-spawn.patch The change is small (a few lines before AwakeFromInstantiate()), so applying it by hand from the code block above works just as well. In case the attachment does not come through, here is the full patch: diff --git a/NpcSpawn.cs b/NpcSpawn.cs --- a/NpcSpawn.cs +++ b/NpcSpawn.cs @@ -1140,6 +1140,19 @@ namespace Oxide.Plugins customScientist.Config = config; customScientist.Brain = customScientistBrain; customScientist.enableSaving = false; + + // Stationary NPCs: Rust.Ai.Gen2.RustNavMeshAgent.OnEnable() re-projects the entity onto the nearest navmesh + // (10 m radius) and moves the transform while updatePosition is true (its constructor default). + // NPCPlayer.ServerInit() sets it to false, but that runs after activation, so a stationary NPC placed on a + // runtime-spawned structure (no navmesh on its floors) is warped to the terrain below before ServerInit(). + // Disable the sync before activation so the NPC keeps the requested position (mobile NPCs keep vanilla behavior). + customScientist.UpdateStationary(); + if (customScientist.IsStationary) + { + Rust.Ai.Gen2.RustNavMeshAgent navAgent = customScientist.GetComponent<Rust.Ai.Gen2.RustNavMeshAgent>(); + if (navAgent != null) navAgent.updatePosition = false; + } + customScientist.gameObject.AwakeFromInstantiate(); customScientist.Spawn(); NpcSpawn-stationary-spawn.patch.txt
  2. Hi, thank you for the great plugin. I found a localization issue in Chest Stacks v1.4.6 and would like to report it along with a fix. *Issue Player-specific language files (e.g. `oxide/lang/ja/ChestStacks.json`) are never used. All chat messages are always displayed in the server's default language. *Cause `lang.GetMessage()` is called without the player's userId, so the Lang API always falls back to the server default language instead of each player's language. *Fix I modified `GetLangKeyString()` to accept the player and pass `player.UserIDString` to `lang.GetMessage()`, and updated the four call sites accordingly. After this change, each player receives messages in their own client language. I have attached the fixed `ChestStacks.cs` and a diff patch (`ChestStacks-i18n.patch`). It would be great if this could be included in a future release. Thanks! ChestStacks.cs ChestStacks-i18n.patch.txt
      • 1
      • Like
  3. tsuyoshi_ito

    CubeBuild

    Regarding the building limit, could you please add an option to combine CubeBuild blocks with the regular building block count when enforcing the limit? My server has a maximum building block limit. When players place regular building structures, CubeBuild blocks are also counted toward that limit. However, when placing CubeBuild blocks, the number of regular building blocks is ignored. As a result, the limit is not consistently applied. I would appreciate it if you could consider implementing a unified building limit that counts both regular building blocks and CubeBuild blocks. For reference, the building limit plugin I use is EntityLimit: https://umod.org/plugins/entity-limit
  4. Hello, First of all, thank you for developing and maintaining NpcSpawn — we use it on our PvE server through several of your and Adem's plugins. ■How we found it We noticed the issue while using Dynamic Monuments (by Adem). Its "Static NPCs" are stationary guards placed through NpcSpawn on monuments that are spawned dynamically while the server is running. Those NPCs sometimes ended up standing under the monument floor, on the terrain. The trigger was distance: if a player was near the monument when it spawned, the NPCs stayed in place; if nobody was around, they fell under the floor. We investigated and confirmed that Dynamic Monuments places the NPCs at the correct positions — the displacement happens later, inside NpcSpawn's sleep system. So the report below is about NpcSpawn itself and can be reproduced without Dynamic Monuments. ■The issue in NpcSpawn A stationary NPC (Speed = 0, or States containing only IdleState / CombatStationaryState) is spawned with its NavMeshAgent disabled: CustomScientistBrain.AddStates() calls DisableNavAgent(). The NPC then stands exactly where the SpawnNpc API placed it, even if there is no navmesh at that position. This is correct behavior. The problem is in CustomScientistNpc.UpdateSleep() (around line 3632 in 3.3.7). When the sleep state toggles, it always runs one of two movement-related calls, including for stationary NPCs: Falling asleep: SetDestination(HomePosition, 2f, NavigationSpeed.Fast). This goes through BaseNavigator.SetDestination, which re-enables the disabled NavMeshAgent (SetNavMeshEnabled(true) → PlaceOnNavMesh()). Waking up: NavAgent.enabled = true directly. Enabling a NavMeshAgent makes Unity snap it to the nearest navmesh surface. Entities spawned at runtime are not part of the baked navmesh, so for an NPC standing on such a structure the nearest navmesh is the terrain below it — the agent gets warped under the floor. This also explains why the bug looks intermittent: if a player stays within SleepDistance from the moment the NPC spawns, no sleep transition ever happens and the NPC keeps its position. For a stationary NPC both calls are unnecessary: it never moves, so there is nothing to navigate on sleep/wake transitions. ■Steps to reproduce Create a preset with Speed = 0 (or States containing only IdleState + CombatStationaryState), CanSleep = true, SleepDistance = 100. Spawn any prefab with a walkable floor at runtime and place the NPC on top of it via the SpawnNpc API, with no player within 100 m. Wait a few seconds (UpdateTick runs every 2 s), then approach as a player. The NPC is standing on the terrain under the floor. Repeat with a player standing nearby the whole time — the NPC stays in the correct position. ■The fix we applied (verified on our server) We changed only the Sleep region of CustomScientistNpc: Added an IsStationary property using exactly the same condition AddStates() uses for its local isStationary variable. In UpdateSleep(), after Brain.sleeping is updated, return early for stationary NPCs, so neither SetDestination(...) nor NavAgent.enabled = true runs. Brain.sleeping is still updated, so the sleep optimization (the early return in Think()) keeps working for stationary NPCs, and mobile NPCs keep their current behavior. After deploying this change, stationary NPCs no longer fall under runtime-spawned structures on our server. The modified Sleep region: #region Sleep // Same stationary condition as CustomScientistBrain.AddStates() — keep both in sync public bool IsStationary => Config.Speed == 0f || Config.States == null || Config.States.Count == 0 || (Config.States.Contains("IdleState") && Config.States.Contains("CombatStationaryState")) || (Config.States.Contains("IdleState") && Config.States.Count == 1) || (Config.States.Contains("CombatStationaryState") && Config.States.Count == 1); private void UpdateSleep() { if (!Config.CanSleep) return; bool sleep = Query.Server.PlayerGrid.Query(transform.position.x, transform.position.z, Config.SleepDistance, AIBrainSenses.playerQueryResults, x => x.IsPlayer() && !x.IsSleeping()) == 0; if (Brain.sleeping == sleep) return; Brain.sleeping = sleep; // Stationary NPCs must not touch the NavAgent: enabling it snaps them to the nearest // navmesh, which is the terrain under dynamically spawned monuments (no navmesh on floors) if (IsStationary) return; if (Brain.sleeping) SetDestination(HomePosition, 2f, BaseNavigator.NavigationSpeed.Fast); else if (!ActiveCustomNavMesh) NavAgent.enabled = true; } #endregion Sleep ■Patch and how to apply it The attached NpcSpawn-stationary-sleep.patch is a unified diff against NpcSpawn.cs 3.3.7. Put the patch file in the same directory as NpcSpawn.cs and apply it with either of: git apply NpcSpawn-stationary-sleep.patch (inside a git repository) patch -p1 < NpcSpawn-stationary-sleep.patch The change is small (one added property and one early return in UpdateSleep), so applying it by hand from the code block above works just as well. Would you consider including this fix (or an equivalent one) in a future release? Thank you very much! NpcSpawn-stationary-sleep.patch.txt
  5. Thank you as always for providing such a useful plugin. I really appreciate it. I have found an issue with the settings in the following file: oxide/data/Shop/Shops/Default.json Even when I configure the "Buy Limits" for an item, the permissions defined there do not seem to be applied. Could you please make it so that the permissions set in "Buy Limits" are properly recognized and applied? At the moment, I am working around this issue by also setting the same permission under "Discount" so that the permission name is recognized. "Buy Limits (0 - no limit)": { "shop.default": 1, "shop.vip1": 3, "shop.vip2": 5, "shop.vip3": 10 }, "Discount (%)": { "shop.default": 100, "shop.vip1": 100, "shop.vip2": 100, "shop.vip3": 100 },
  6. Even if I specify the purchase quantity as shown below, the purchase quantity is reset when the plugin is reloaded. How can I maintain the purchase quantity even when restarting the plugin? I would like to be able to purchase only the specified quantity during the period leading up to the wipe. "Maximum number of purchases of one vehicle by one player": 2
  7. Can I use LootManager to set the loot dropped by NPCs? If so, please tell me how. In my environment, the NPC drop item item was not displayed in LootManager. I have been using Dynamic Monument since before installing LootManager.

About Us

Codefling is the largest marketplace for plugins, maps, tools, and more, making it easy for customers to discover new content and for creators to monetize their work.

Downloads
3.1m
Total downloads
Customers
12.1k
Customers served
Files Sold
171.2k
Total sales
Payments
3.7m
Processed total
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.