feat(nwscript): add 14 missing actions for full K1CP compatibility#95
Open
gnutix wants to merge 13 commits intoKobaltBlu:masterfrom
Open
feat(nwscript): add 14 missing actions for full K1CP compatibility#95gnutix wants to merge 13 commits intoKobaltBlu:masterfrom
gnutix wants to merge 13 commits intoKobaltBlu:masterfrom
Conversation
Returns the NWScript OBJECT_TYPE_* constant for a given object by mapping from the internal ModuleObjectType bitmask to the NWModuleObjectType enum values the scripts expect. Used by 3 K1 Community Patch scripts (k_pman_steam01, k_ptar_spawnkand, k_ptar_spwnkand2) to check if objects are creatures before applying effects or destroying them.
Logs the message to the console. In the original engine this displayed a server message to the player; in KotOR.js single-player context, console logging is the appropriate equivalent. Used by K1CP's cp_inc_debug.nss for debug output.
Sets the nonEquippable flag on a ModuleItem, which is already read from and written to GFF templates (ModuleItem.ts lines 699-700, 870). Used by K1CP's cp_inc_tat.nss to re-enable equipping of items.
When nValue is TRUE, head tracking is disabled (locked) so the creature's head stays fixed during dialog instead of following the speaker. Uses the existing headTrackingEnabled property on ModuleCreature which controls the updateHeadTracking() behavior. Mirrors the pattern of the existing SetLockOrientationInDialog (action 505) which locks body orientation in dialogs. Used by 6 K1CP scripts for Leviathan, Star Forge, and Unknown World dialog cutscenes.
Instant item transfer between objects, mirroring the logic from the existing ActionGiveItem action class. Items given to party members are routed through InventoryManager (shared party inventory); items given to non-party objects use addItem directly. Used by 6 K1CP scripts across Taris, Kashyyyk, Leviathan, Tatooine, and Unknown World for inventory transfers during quest scripts.
…turbItem Adds accessor implementations that read from new properties on ModuleObject (lastInventoryDisturbType, lastInventoryDisturbItem). These properties should be set by the inventory system when firing the OnInventoryDisturbed event, which is defined but not yet fully wired. Used by K1CP's k_pkor_therangen.nss (Korriban therma grenade trap).
Returns the last item acquired from the module's lastItemAcquired property. This property should be set by the item acquisition system before firing the OnItemAcquired module event. Used by K1CP's k_pdan_14b_itmaq.nss (Dantooine diary item detection).
Also fixes the function signature: the stub had empty args but the original engine expects (nNPC: int, oCreature: object). Sets the moduleObject reference on the PartyManager NPC slot so the party system uses the specified creature instance. Used by K1CP's k_pdan_13_area.nss to assign Carth and Bastila's in-world object IDs to their party slots on Dantooine.
Calculates a target point that is fMoveAwayRange meters from oFleeFrom, along the direction from oFleeFrom through the caller, then queues an ActionMoveToPoint to walk/run there. If the caller is already at least fMoveAwayRange away, the action is a no-op. No other open-source KotOR/NWN engine reimplementation (reone, xoreos) has this function implemented, so behavior is derived from the NWScript spec: "the distance we wish the action subject to put between themselves and oFleeFrom." Used by 5 K1CP scripts: Taris NPC movement (cp_tar04_gendjmp, cp_tar04_rukiljp), generic AI flee behavior (k_inc_generic), Ebon Hawk (k_pebo_ud), and Taris vulkar cow (k_ptar_vulcow_ud).
Queues an ActionCastSpell with cheat mode enabled so the conjure/cast animations and visuals play without requiring the caster to actually know the spell. The actual spell effects are not applied by this action — K1CP scripts apply effects separately via ApplyEffectToObject. Uses the same ActionCastSpell infrastructure as ActionCastSpellAtObject (action 48), matching the parameters used by that implementation. Used by 7 K1CP scripts for cutscene force power visuals on Endar Spire, Korriban, Kashyyyk, Leviathan, and Unknown World.
Adds the EffectTrueSeeing effect class following the same pattern as EffectInvisibility and other simple effect types. Registers it in GameEffectFactory (both as a static member and in the EffectFromStruct switch) and exports it from the effects index. The effect grants true seeing, allowing the affected creature to see through stealth/invisibility. The game effect type EffectTrueseeing (0x48) was already defined in the enums. Used by K1CP's cp_lev_awarefix.nss to fix an awareness issue on the Leviathan.
Iterates all creatures within a 10-metre radius of the caller NPC, clears their actions and effects, and sets their faction reputation towards the caller to neutral (50). Skips if the caller is a PC, per the original engine spec. Uses the existing FactionManager.SetFactionReputation and the creature.isHostile/clearAllActions/clearAllEffects methods. Used by 5 K1CP scripts on Taris (Calo/Vulkar) and Kashyyyk (Chuundar/Freyyr/Xor) to end combat for dialog transitions.
Same as SurrenderToEnemies (action 476) but does NOT clear effects on affected creatures, preserving self-applied buffs. This distinction is per the original engine spec. Used by K1CP's k_psta_ud_bastil.nss for a Bastila dialog transition on the Star Forge where she should retain her force powers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hello there!
I'm a chronic kotOR nostalgic (and a web/PHP developer with almost 20 years of experience), and I've often wondered how I could contribute to amazing open source projects around this game, but they are mostly in programming languages I know nothing about, or require extensive modder/games internals knowledge. I only did some mapping/scripting in JK2/JKA, but that was a loooong time ago, so... anyway! :'D
Today I found your project, noticed it was active, and was curious to learn more about it. So I looked around the repo/history/PRs, ran the online playground, and noticed the character creation menu only allowed "the quick path" (no way to define skills, traits and such). Knowing this project was in JS, which felt less intimidating, I thought maybe that was my opportunity to contribute. So I fired up Claude to investigate these menus actions. It quickly got a bit complicated, so I set it aside. But then I got curious if the project was fully compatible with K1CP or not.
As KotOR.js already loads K1CP's modified files automatically when pointed at a patched game directory (Override files, modified .mod archives, patched dialog.tlk, recompiled .ncs scripts all load through the existing resource system), the only missing piece was to make sure all functions used in K1CP's files were implemented in KotOR.js.
Here's how Claude found these 14 missing functions, and then proceeded to implement them :
.nsssource files.cp_*,GN_*,UT_*functions).NWScriptDefK1.ts.I thought this was probably a good candidate for a first PR on such a project ; small, isolated scope. So here it is!
I've re-read every commit attentively, and challenged Claude on every possible topic I could think of (code style convention, type conversion, function signature mismatches, etc), so the quality is "as good as I can get it" with the knowledge I have. This might not look like it, but this PR took me the whole afternoon. 😅
Important disclaimer: I've not tested any of these changes, as I wouldn't know how. It would require saves for a dozen specific game points, which I don't have. And even with those, most of these won't have a visible/lasting effect anyway, or wouldn't crash the game. So they seemed safe enough for me to at least open a PR about them. Below you'll find some patches for more changes that I wasn't so sure were safe.
Each function is a separate commit for easy cherry-picking, in case you like some but not all of them.
Implemented Functions
GetObjectTypeModuleObjectTypebitmask →NWModuleObjectTypeenumSendMessageToPCSetItemNonEquippableModuleItem.nonEquippablepropertySetLockHeadFollowInDialogModuleCreature.headTrackingEnabled(mirrorsSetLockOrientationInDialog)GiveItemActionGiveItemlogic without action queueGetInventoryDisturbType+GetInventoryDisturbItemModuleObject(see limitations)GetModuleItemAcquiredModule(see limitations)SetAvailableNPCIdmoduleObjectonPartyManager.NPCSslot; also fixes arg signature (stub had emptyargs: []but function takes[int, object])ActionMoveAwayFromObjectActionMoveToPointActionCastFakeSpellAtObjectActionCastSpellwith cheat mode (animations only, no spell effects)EffectTrueSeeingEffectTrueSeeingclass + factory registrationSurrenderToEnemiesSurrenderRetainBuffsKnown Limitations
Event wiring not yet connected for some actions
GetInventoryDisturbType,GetInventoryDisturbItem, andGetModuleItemAcquiredhave working accessor implementations, but the properties they read from are never populated by the current event system. This means they will return default values (0 / undefined) at runtime.Why these are still worth including: The accessor plumbing is correct and follows the patterns of other event-based getters (e.g.,
GetLastOpenedBy,GetEnteringObject). The event wiring is a separate concern that touches high-traffic code paths (ModuleObject.addItem(),InventoryManager.addItem()), so I'd prefer to discuss the approach rather than include it silently.Proposed patch: wire up OnInventoryDisturbed in ModuleObject.addItem()
The K1CP script
k_pkor_therangen.nss(Korriban Therangen Obelisk) checks forINVENTORY_DISTURB_TYPE_ADDEDwhen a grenade is placed into the obelisk. This requires firing the event fromaddItem():And set the properties during the existing
retrieveInventory()flow:Concern:
addItem()is called from many places (NWScript actions, store purchases, save game loading, creature inventory init). The original engine likely only fires OnInventoryDisturbed for player-initiated UI actions, not programmatic transfers. This may need a guard or asilentparameter to avoid unintended script triggers during loading.Proposed patch: wire up Mod_OnAcquirItem in InventoryManager
The K1CP script
k_pdan_14b_itmaq.nss(Dantooine diary) expects the module-levelMod_OnAcquirItemevent to fire when the player picks up an item:Concern: Same issue —
InventoryManager.addItem()is called during save game deserialization, store purchases, and scripted item creation, not just player pickup. The event should probably only fire from specific call sites (retrieveInventory,ActionGiveItem,ActionTakeItem), not globally.I'd appreciate guidance on the right approach to implement and test these event-wiring changes, I case you want me to include them.
Let me know if there's anything else I can do.