// ============================================================================ // File Name: asl_npc_ai_receiver // Generative Agent "Act" Phase placed on Module OnHeartbeat event. // ============================================================================ #include "nwnx_redis" #include "nwnx_redis_lib" #include "nw_i0_generic" void ExecuteFallbackJump(object oNPC, object oHome) { if (GetArea(oNPC) != GetArea(oHome)) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_UNSUMMON), oNPC); AssignCommand(oNPC, ActionJumpToObject(oHome)); } } void main() { int nResultId = NWNX_Redis_LPOP("llm_to_nwn"); string sReplyData = NWNX_Redis_GetResultAsString(nResultId); int nSafety = 0; while (sReplyData != "" && sReplyData != "null" && sReplyData != "(nil)" && nSafety < 10) { nSafety++; if (GetStringLength(sReplyData) < 10) break; json jResponse = JsonParse(sReplyData); string sNpcTag = JsonGetString(JsonObjectGet(jResponse, "npc_tag")); string sTargetPlayer = JsonGetString(JsonObjectGet(jResponse, "target_player")); string sReplyText = JsonGetString(JsonObjectGet(jResponse, "reply")); object oNPC = GetObjectByTag(sNpcTag); object oDebugPC = GetFirstPC(); if (!GetIsObjectValid(oNPC)) { SendMessageToPC(oDebugPC, "[DEBUG ERROR] Could not find NPC with Tag: " + sNpcTag); nResultId = NWNX_Redis_LPOP("llm_to_nwn"); sReplyData = NWNX_Redis_GetResultAsString(nResultId); continue; } json jAgentBrain = JsonParse(sReplyText); string sSpeech = ""; if (JsonGetType(JsonObjectGet(jAgentBrain, "speech")) == JSON_TYPE_STRING) sSpeech = JsonGetString(JsonObjectGet(jAgentBrain, "speech")); string sEmotion = "NEUTRAL"; if (JsonGetType(JsonObjectGet(jAgentBrain, "emotion")) == JSON_TYPE_STRING) sEmotion = GetStringUpperCase(JsonGetString(JsonObjectGet(jAgentBrain, "emotion"))); string sAction = ""; if (JsonGetType(JsonObjectGet(jAgentBrain, "action")) == JSON_TYPE_STRING) sAction = GetStringUpperCase(JsonGetString(JsonObjectGet(jAgentBrain, "action"))); string sTarget = ""; if (JsonGetType(JsonObjectGet(jAgentBrain, "action_target")) == JSON_TYPE_STRING) sTarget = JsonGetString(JsonObjectGet(jAgentBrain, "action_target")); string sTargetSpeech = ""; if (JsonGetType(JsonObjectGet(jAgentBrain, "target_speech")) == JSON_TYPE_STRING) sTargetSpeech = JsonGetString(JsonObjectGet(jAgentBrain, "target_speech")); if (GetObjectType(oNPC) == OBJECT_TYPE_CREATURE) { if (GetIsInCombat(oNPC) || GetIsObjectValid(GetAttackTarget(oNPC))) { if (sAction != "PEACE" && sAction != "ATTACK") { if (sSpeech != "" && sSpeech != "*grunts quietly*") { AssignCommand(oNPC, SpeakString(sSpeech)); } nResultId = NWNX_Redis_LPOP("llm_to_nwn"); sReplyData = NWNX_Redis_GetResultAsString(nResultId); continue; } } } if (GetObjectType(oNPC) == OBJECT_TYPE_PLACEABLE) { if (sSpeech != "") { if (sSpeech == "*grunts quietly*") sSpeech = "*The shrine glows faintly but remains silent.*"; AssignCommand(oNPC, SpeakString(sSpeech)); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUPER_HEROISM), oNPC); } nResultId = NWNX_Redis_LPOP("llm_to_nwn"); sReplyData = NWNX_Redis_GetResultAsString(nResultId); continue; } SendMessageToPC(oDebugPC, "[DEBUG] " + GetName(oNPC) + " executing: [" + sAction + "] target: [" + sTarget + "]"); AssignCommand(oNPC, ClearAllActions(TRUE)); if (sSpeech != "") { object oTalkTarget = GetFirstPC(); while (GetIsObjectValid(oTalkTarget)) { if (GetName(oTalkTarget) == sTargetPlayer) { AssignCommand(oNPC, SetFacingPoint(GetPosition(oTalkTarget))); break; } oTalkTarget = GetNextPC(); } DelayCommand(0.1, AssignCommand(oNPC, SpeakString(sSpeech))); if (sEmotion == "LAUGHING") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_LAUGHING, 1.0, 4.0))); else if (sEmotion == "ANGRY") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_FORCEFUL, 1.0, 4.0))); else if (sEmotion == "PLEADING") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_PLEADING, 1.0, 4.0))); else if (sEmotion == "BOW") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_BOW))); else if (sEmotion == "TAUNT") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_TAUNT))); else if (sEmotion == "CHEER") { if (Random(2) == 0) DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY1))); else DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING))); } else DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_NORMAL, 1.0, 3.0))); } if (sAction == "ATTACK") { object oEnemy = OBJECT_INVALID; if (sTarget != "") { int i = 1; object oSearch = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i); while (GetIsObjectValid(oSearch) && GetDistanceBetween(oNPC, oSearch) < 20.0f) { if (FindSubString(GetStringLowerCase(GetName(oSearch)), GetStringLowerCase(sTarget)) >= 0) { oEnemy = oSearch; break; } i++; oSearch = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i); } } if (!GetIsObjectValid(oEnemy)) oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oNPC, 1, CREATURE_TYPE_IS_ALIVE, TRUE); if (GetIsObjectValid(oEnemy)) { if (GetIsPC(oEnemy)) SetIsTemporaryEnemy(oEnemy, oNPC, TRUE); DelayCommand(0.3, AssignCommand(oNPC, ActionAttack(oEnemy))); DelayCommand(0.4, AssignCommand(oNPC, DetermineCombatRound(oEnemy))); } } else if (sAction == "PEACE") { object oMyArea = GetArea(oNPC); object oForgive = GetFirstPC(); while (GetIsObjectValid(oForgive)) { if (GetArea(oForgive) == oMyArea) ClearPersonalReputation(oForgive, oNPC); oForgive = GetNextPC(); } AssignCommand(oNPC, ClearAllActions(TRUE)); AssignCommand(oNPC, SurrenderToEnemies()); DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD))); } else if (sAction == "COMMAND") { DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_FORCEFUL, 1.0, 3.0))); object oVictim = OBJECT_INVALID; if (sTarget != "RETREAT" && sTarget != "DEFEND_ME") { int nScan = 1; object oSearch = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC, nScan); while (GetIsObjectValid(oSearch) && GetDistanceBetween(oNPC, oSearch) < 30.0f) { if (FindSubString(GetStringLowerCase(GetName(oSearch)), GetStringLowerCase(sTarget)) >= 0) { oVictim = oSearch; break; } nScan++; oSearch = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC, nScan); } } int i = 1; object oMinion = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, oNPC, i); while (GetIsObjectValid(oMinion) && GetDistanceBetween(oNPC, oMinion) < 30.0f) { if (!GetIsPC(oMinion) && GetFactionEqual(oMinion, oNPC)) { AssignCommand(oMinion, ClearAllActions(TRUE)); if (GetIsObjectValid(oVictim)) AssignCommand(oMinion, ActionAttack(oVictim)); else if (sTarget == "RETREAT") { object oNearestPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oMinion); AssignCommand(oMinion, ActionMoveAwayFromObject(oNearestPC, TRUE, 40.0f)); } else if (sTarget == "DEFEND_ME") AssignCommand(oMinion, ActionForceFollowObject(oNPC, 1.5f)); } i++; oMinion = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, oNPC, i); } } else if (sAction == "WANDER") DelayCommand(0.2, AssignCommand(oNPC, ActionRandomWalk())); else if (sAction == "PATROL") DelayCommand(0.2, AssignCommand(oNPC, WalkWayPoints())); else if (sAction == "FOLLOW" || sAction == "FOLLOW_NEAREST") { object oTargetPC = GetFirstPC(); int bFound = FALSE; while (GetIsObjectValid(oTargetPC)) { if (FindSubString(GetStringLowerCase(GetName(oTargetPC)), GetStringLowerCase(sTarget)) >= 0 || GetName(oTargetPC) == sTargetPlayer) { DelayCommand(0.2, AssignCommand(oNPC, ActionForceFollowObject(oTargetPC, 2.0f))); bFound = TRUE; break; } oTargetPC = GetNextPC(); } if (!bFound) { object oNearest = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC); if (GetIsObjectValid(oNearest)) DelayCommand(0.2, AssignCommand(oNPC, ActionForceFollowObject(oNearest, 2.0f))); } } else if (sAction == "REST") { DelayCommand(0.2, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_HEALING_M), oNPC)); DelayCommand(0.3, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_SIT_CROSS, 1.0, 12.0))); DelayCommand(0.5, ForceRest(oNPC)); } else if (sAction == "STEALTH") { DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_STEALTH, TRUE))); DelayCommand(0.5, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_SPASM))); } else if (sAction == "SEARCH") { DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_DETECT, TRUE))); } else if (sAction == "UNSTEALTH") { DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_STEALTH, FALSE))); DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_DETECT, FALSE))); } else if (sAction == "GUARD") { DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_HEAD_TURN_LEFT))); DelayCommand(3.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_HEAD_TURN_RIGHT))); } else if (sAction == "GO_TO" && sTarget != "") { object oDest = GetObjectByTag(sTarget); if (GetIsObjectValid(oDest)) DelayCommand(0.2, AssignCommand(oNPC, ActionForceMoveToObject(oDest, FALSE, 1.0f))); else DelayCommand(0.2, AssignCommand(oNPC, ActionRandomWalk())); } else if (sAction == "INTERACT" || sAction == "USE_OBJECT") { int bFoundTarget = FALSE; string sTargetLower = GetStringLowerCase(sTarget); if (sTargetLower == "chair" || sTargetLower == "seat") sTargetLower = "stool"; else if (sTargetLower == "drink" || sTargetLower == "mug" || sTargetLower == "ale") sTargetLower = "cup"; else if (sTargetLower == "food" || sTargetLower == "meal") sTargetLower = "plate"; else if (sTargetLower == "fire" || sTargetLower == "hearth") sTargetLower = "fireplace"; object oTargetObj = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, 1); int nNth = 1; while (GetIsObjectValid(oTargetObj) && GetDistanceBetween(oNPC, oTargetObj) < 20.0) { string sObjName = GetStringLowerCase(GetName(oTargetObj)); if ((sTargetLower != "" && FindSubString(sObjName, sTargetLower) >= 0) || (sTargetLower == "" && GetLocalInt(oTargetObj, "NW_INTERACTIVE") == TRUE)) { bFoundTarget = TRUE; AssignCommand(oNPC, ActionMoveToObject(oTargetObj, FALSE, 0.5f)); AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oTargetObj)))); if (FindSubString(sObjName, "cup") >= 0 || FindSubString(sObjName, "wine") >= 0 || FindSubString(sObjName, "keg") >= 0) { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK))); } else if (FindSubString(sObjName, "stool") >= 0 || FindSubString(sObjName, "bench") >= 0 || FindSubString(sObjName, "chair") >= 0) { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_SIT_CHAIR, 1.0, 9999.0))); } else if (FindSubString(sObjName, "book") >= 0 || FindSubString(sObjName, "shelf") >= 0) { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_READ))); } else if (FindSubString(sObjName, "cook") >= 0 || FindSubString(sObjName, "oven") >= 0 || FindSubString(sObjName, "pot") >= 0) { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 9999.0))); } else { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 3.0))); } break; } nNth++; oTargetObj = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, nNth); } if (!bFoundTarget && sAction == "INTERACT" && sTargetLower != "") { int i = 1; object oCreature = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i); while (GetIsObjectValid(oCreature) && GetDistanceBetween(oNPC, oCreature) < 20.0) { if (FindSubString(GetStringLowerCase(GetName(oCreature)), sTargetLower) >= 0) { AssignCommand(oNPC, ActionMoveToObject(oCreature, FALSE, 1.5f)); AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oCreature)))); AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING))); bFoundTarget = TRUE; break; } i++; oCreature = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i); } } if (!bFoundTarget) AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD)); } else if (sAction == "CONVERSE") { int nScan = 1; object oPuppet = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, nScan); object oFoundPuppet = OBJECT_INVALID; while (GetIsObjectValid(oPuppet) && GetDistanceBetween(oNPC, oPuppet) < 20.0f) { if (!GetIsPC(oPuppet)) { if (FindSubString(GetStringLowerCase(GetName(oPuppet)), GetStringLowerCase(sTarget)) >= 0) { oFoundPuppet = oPuppet; break; } } nScan++; oPuppet = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, nScan); } if (GetIsObjectValid(oFoundPuppet)) { AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oFoundPuppet)))); AssignCommand(oFoundPuppet, ClearAllActions(TRUE)); AssignCommand(oFoundPuppet, SetFacingPoint(GetPosition(oNPC))); if (sTargetSpeech != "") { DelayCommand(3.5f, AssignCommand(oFoundPuppet, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING))); DelayCommand(4.0f, AssignCommand(oFoundPuppet, SpeakString(sTargetSpeech))); } else { DelayCommand(3.5f, AssignCommand(oFoundPuppet, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD))); } } } else if (sAction == "OPEN_STORE") { string sShopTag = GetLocalString(oNPC, "LLM_SHOPTAG"); if (sShopTag == "") sShopTag = "STORE_" + GetTag(oNPC); object oStore = GetNearestObjectByTag(sShopTag, oNPC); object oBuyer = OBJECT_INVALID; object oTestPC = GetFirstPC(); while(GetIsObjectValid(oTestPC)) { if(GetName(oTestPC) == sTargetPlayer) { oBuyer = oTestPC; break; } oTestPC = GetNextPC(); } if (!GetIsObjectValid(oBuyer)) oBuyer = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC); if (GetIsObjectValid(oStore) && GetIsObjectValid(oBuyer)) { DelayCommand(1.0f, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING))); DelayCommand(1.5f, OpenStore(oStore, oBuyer)); } else { AssignCommand(oNPC, ActionSpeakString("I seem to have lost my wares...")); } } else if (sAction == "GIVE_QUEST") { object oPlayer = OBJECT_INVALID; object oTestPC = GetFirstPC(); while(GetIsObjectValid(oTestPC)) { if(GetName(oTestPC) == sTargetPlayer) { oPlayer = oTestPC; break; } oTestPC = GetNextPC(); } if (!GetIsObjectValid(oPlayer)) oPlayer = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC); if (GetIsObjectValid(oPlayer)) { DelayCommand(1.0f, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_MAGIC_PROTECTION), oNPC)); SendMessageToPC(oPlayer, "[SERVER] " + GetName(oNPC) + " has granted you a new quest objective!"); } } else if (sAction == "RETURN_TO_POST") { object oHome = GetWaypointByTag("WP_" + GetTag(oNPC) + "_HOME"); if (GetIsObjectValid(oHome)) { DelayCommand(0.2, AssignCommand(oNPC, ActionForceMoveToObject(oHome, FALSE, 1.0f))); object oBed = GetNearestObjectByTag("Bed", oNPC); if (GetIsObjectValid(oBed) && GetDistanceBetween(oHome, oBed) < 5.0) { AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 9999.0))); } else { AssignCommand(oNPC, ActionDoCommand(SetFacing(GetFacing(oHome)))); } DelayCommand(20.0, ExecuteFallbackJump(oNPC, oHome)); } } nResultId = NWNX_Redis_LPOP("llm_to_nwn"); sReplyData = NWNX_Redis_GetResultAsString(nResultId); } }