1 // ============================================================================
2 // File Name: asl_npc_ai_receiver
3 // Generative Agent "Act" Phase placed on Module OnHeartbeat event.
4 // ============================================================================
6 #include "nwnx_redis_lib"
7 #include "nw_i0_generic"
9 void ExecuteFallbackJump(object oNPC, object oHome) {
10 if (GetArea(oNPC) != GetArea(oHome)) {
11 ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_UNSUMMON), oNPC);
12 AssignCommand(oNPC, ActionJumpToObject(oHome));
18 int nResultId = NWNX_Redis_LPOP("llm_to_nwn");
19 string sReplyData = NWNX_Redis_GetResultAsString(nResultId);
22 while (sReplyData != "" && sReplyData != "null" && sReplyData != "(nil)" && nSafety < 10)
25 if (GetStringLength(sReplyData) < 10) break;
27 json jResponse = JsonParse(sReplyData);
28 string sNpcTag = JsonGetString(JsonObjectGet(jResponse, "npc_tag"));
29 string sTargetPlayer = JsonGetString(JsonObjectGet(jResponse, "target_player"));
30 string sReplyText = JsonGetString(JsonObjectGet(jResponse, "reply"));
32 object oNPC = GetObjectByTag(sNpcTag);
33 object oDebugPC = GetFirstPC();
35 if (!GetIsObjectValid(oNPC)) {
36 SendMessageToPC(oDebugPC, "[DEBUG ERROR] Could not find NPC with Tag: " + sNpcTag);
37 nResultId = NWNX_Redis_LPOP("llm_to_nwn");
38 sReplyData = NWNX_Redis_GetResultAsString(nResultId);
42 json jAgentBrain = JsonParse(sReplyText);
45 if (JsonGetType(JsonObjectGet(jAgentBrain, "speech")) == JSON_TYPE_STRING) sSpeech = JsonGetString(JsonObjectGet(jAgentBrain, "speech"));
47 string sEmotion = "NEUTRAL";
48 if (JsonGetType(JsonObjectGet(jAgentBrain, "emotion")) == JSON_TYPE_STRING) sEmotion = GetStringUpperCase(JsonGetString(JsonObjectGet(jAgentBrain, "emotion")));
51 if (JsonGetType(JsonObjectGet(jAgentBrain, "action")) == JSON_TYPE_STRING) sAction = GetStringUpperCase(JsonGetString(JsonObjectGet(jAgentBrain, "action")));
54 if (JsonGetType(JsonObjectGet(jAgentBrain, "action_target")) == JSON_TYPE_STRING) sTarget = JsonGetString(JsonObjectGet(jAgentBrain, "action_target"));
56 string sTargetSpeech = "";
57 if (JsonGetType(JsonObjectGet(jAgentBrain, "target_speech")) == JSON_TYPE_STRING) sTargetSpeech = JsonGetString(JsonObjectGet(jAgentBrain, "target_speech"));
59 if (GetObjectType(oNPC) == OBJECT_TYPE_CREATURE) {
60 if (GetIsInCombat(oNPC) || GetIsObjectValid(GetAttackTarget(oNPC))) {
61 if (sAction != "PEACE" && sAction != "ATTACK") {
62 if (sSpeech != "" && sSpeech != "*grunts quietly*") {
63 AssignCommand(oNPC, SpeakString(sSpeech));
65 nResultId = NWNX_Redis_LPOP("llm_to_nwn");
66 sReplyData = NWNX_Redis_GetResultAsString(nResultId);
72 if (GetObjectType(oNPC) == OBJECT_TYPE_PLACEABLE) {
74 if (sSpeech == "*grunts quietly*") sSpeech = "*The shrine glows faintly but remains silent.*";
75 AssignCommand(oNPC, SpeakString(sSpeech));
76 ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUPER_HEROISM), oNPC);
78 nResultId = NWNX_Redis_LPOP("llm_to_nwn");
79 sReplyData = NWNX_Redis_GetResultAsString(nResultId);
83 SendMessageToPC(oDebugPC, "[DEBUG] " + GetName(oNPC) + " executing: [" + sAction + "] target: [" + sTarget + "]");
84 AssignCommand(oNPC, ClearAllActions(TRUE));
87 object oTalkTarget = GetFirstPC();
88 while (GetIsObjectValid(oTalkTarget)) {
89 if (GetName(oTalkTarget) == sTargetPlayer) {
90 AssignCommand(oNPC, SetFacingPoint(GetPosition(oTalkTarget)));
93 oTalkTarget = GetNextPC();
96 DelayCommand(0.1, AssignCommand(oNPC, SpeakString(sSpeech)));
98 if (sEmotion == "LAUGHING") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_LAUGHING, 1.0, 4.0)));
99 else if (sEmotion == "ANGRY") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_FORCEFUL, 1.0, 4.0)));
100 else if (sEmotion == "PLEADING") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_PLEADING, 1.0, 4.0)));
101 else if (sEmotion == "BOW") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_BOW)));
102 else if (sEmotion == "TAUNT") DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_TAUNT)));
103 else if (sEmotion == "CHEER") {
104 if (Random(2) == 0) DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY1)));
105 else DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING)));
107 else DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_NORMAL, 1.0, 3.0)));
110 if (sAction == "ATTACK") {
111 object oEnemy = OBJECT_INVALID;
114 object oSearch = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i);
115 while (GetIsObjectValid(oSearch) && GetDistanceBetween(oNPC, oSearch) < 20.0f) {
116 if (FindSubString(GetStringLowerCase(GetName(oSearch)), GetStringLowerCase(sTarget)) >= 0) {
121 oSearch = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i);
124 if (!GetIsObjectValid(oEnemy)) oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oNPC, 1, CREATURE_TYPE_IS_ALIVE, TRUE);
125 if (GetIsObjectValid(oEnemy)) {
126 if (GetIsPC(oEnemy)) SetIsTemporaryEnemy(oEnemy, oNPC, TRUE);
127 DelayCommand(0.3, AssignCommand(oNPC, ActionAttack(oEnemy)));
128 DelayCommand(0.4, AssignCommand(oNPC, DetermineCombatRound(oEnemy)));
132 else if (sAction == "PEACE") {
133 object oMyArea = GetArea(oNPC);
134 object oForgive = GetFirstPC();
135 while (GetIsObjectValid(oForgive)) {
136 if (GetArea(oForgive) == oMyArea) ClearPersonalReputation(oForgive, oNPC);
137 oForgive = GetNextPC();
139 AssignCommand(oNPC, ClearAllActions(TRUE));
140 AssignCommand(oNPC, SurrenderToEnemies());
141 DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD)));
144 else if (sAction == "COMMAND") {
145 DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_TALK_FORCEFUL, 1.0, 3.0)));
146 object oVictim = OBJECT_INVALID;
147 if (sTarget != "RETREAT" && sTarget != "DEFEND_ME") {
149 object oSearch = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC, nScan);
150 while (GetIsObjectValid(oSearch) && GetDistanceBetween(oNPC, oSearch) < 30.0f) {
151 if (FindSubString(GetStringLowerCase(GetName(oSearch)), GetStringLowerCase(sTarget)) >= 0) {
156 oSearch = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC, nScan);
160 object oMinion = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, oNPC, i);
161 while (GetIsObjectValid(oMinion) && GetDistanceBetween(oNPC, oMinion) < 30.0f) {
162 if (!GetIsPC(oMinion) && GetFactionEqual(oMinion, oNPC)) {
163 AssignCommand(oMinion, ClearAllActions(TRUE));
164 if (GetIsObjectValid(oVictim)) AssignCommand(oMinion, ActionAttack(oVictim));
165 else if (sTarget == "RETREAT") {
166 object oNearestPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oMinion);
167 AssignCommand(oMinion, ActionMoveAwayFromObject(oNearestPC, TRUE, 40.0f));
169 else if (sTarget == "DEFEND_ME") AssignCommand(oMinion, ActionForceFollowObject(oNPC, 1.5f));
172 oMinion = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, oNPC, i);
176 else if (sAction == "WANDER") DelayCommand(0.2, AssignCommand(oNPC, ActionRandomWalk()));
177 else if (sAction == "PATROL") DelayCommand(0.2, AssignCommand(oNPC, WalkWayPoints()));
178 else if (sAction == "FOLLOW" || sAction == "FOLLOW_NEAREST") {
179 object oTargetPC = GetFirstPC();
181 while (GetIsObjectValid(oTargetPC)) {
182 if (FindSubString(GetStringLowerCase(GetName(oTargetPC)), GetStringLowerCase(sTarget)) >= 0 || GetName(oTargetPC) == sTargetPlayer) {
183 DelayCommand(0.2, AssignCommand(oNPC, ActionForceFollowObject(oTargetPC, 2.0f)));
187 oTargetPC = GetNextPC();
190 object oNearest = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC);
191 if (GetIsObjectValid(oNearest)) DelayCommand(0.2, AssignCommand(oNPC, ActionForceFollowObject(oNearest, 2.0f)));
195 else if (sAction == "REST") {
196 DelayCommand(0.2, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_HEALING_M), oNPC));
197 DelayCommand(0.3, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_LOOPING_SIT_CROSS, 1.0, 12.0)));
198 DelayCommand(0.5, ForceRest(oNPC));
200 else if (sAction == "STEALTH") {
201 DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_STEALTH, TRUE)));
202 DelayCommand(0.5, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_SPASM)));
204 else if (sAction == "SEARCH") {
205 DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_DETECT, TRUE)));
207 else if (sAction == "UNSTEALTH") {
208 DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_STEALTH, FALSE)));
209 DelayCommand(0.2, AssignCommand(oNPC, SetActionMode(oNPC, ACTION_MODE_DETECT, FALSE)));
211 else if (sAction == "GUARD") {
212 DelayCommand(0.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_HEAD_TURN_LEFT)));
213 DelayCommand(3.2, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_HEAD_TURN_RIGHT)));
215 else if (sAction == "GO_TO" && sTarget != "") {
216 object oDest = GetObjectByTag(sTarget);
217 if (GetIsObjectValid(oDest)) DelayCommand(0.2, AssignCommand(oNPC, ActionForceMoveToObject(oDest, FALSE, 1.0f)));
218 else DelayCommand(0.2, AssignCommand(oNPC, ActionRandomWalk()));
221 else if (sAction == "INTERACT" || sAction == "USE_OBJECT") {
222 int bFoundTarget = FALSE;
223 string sTargetLower = GetStringLowerCase(sTarget);
225 if (sTargetLower == "chair" || sTargetLower == "seat") sTargetLower = "stool";
226 else if (sTargetLower == "drink" || sTargetLower == "mug" || sTargetLower == "ale") sTargetLower = "cup";
227 else if (sTargetLower == "food" || sTargetLower == "meal") sTargetLower = "plate";
228 else if (sTargetLower == "fire" || sTargetLower == "hearth") sTargetLower = "fireplace";
230 object oTargetObj = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, 1);
233 while (GetIsObjectValid(oTargetObj) && GetDistanceBetween(oNPC, oTargetObj) < 20.0) {
234 string sObjName = GetStringLowerCase(GetName(oTargetObj));
236 if ((sTargetLower != "" && FindSubString(sObjName, sTargetLower) >= 0) ||
237 (sTargetLower == "" && GetLocalInt(oTargetObj, "NW_INTERACTIVE") == TRUE)) {
240 AssignCommand(oNPC, ActionMoveToObject(oTargetObj, FALSE, 0.5f));
241 AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oTargetObj))));
243 if (FindSubString(sObjName, "cup") >= 0 || FindSubString(sObjName, "wine") >= 0 || FindSubString(sObjName, "keg") >= 0) {
244 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_DRINK)));
245 } else if (FindSubString(sObjName, "stool") >= 0 || FindSubString(sObjName, "bench") >= 0 || FindSubString(sObjName, "chair") >= 0) {
246 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_SIT_CHAIR, 1.0, 9999.0)));
247 } else if (FindSubString(sObjName, "book") >= 0 || FindSubString(sObjName, "shelf") >= 0) {
248 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_READ)));
249 } else if (FindSubString(sObjName, "cook") >= 0 || FindSubString(sObjName, "oven") >= 0 || FindSubString(sObjName, "pot") >= 0) {
250 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 9999.0)));
252 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 3.0)));
257 oTargetObj = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, nNth);
260 if (!bFoundTarget && sAction == "INTERACT" && sTargetLower != "") {
262 object oCreature = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i);
263 while (GetIsObjectValid(oCreature) && GetDistanceBetween(oNPC, oCreature) < 20.0) {
264 if (FindSubString(GetStringLowerCase(GetName(oCreature)), sTargetLower) >= 0) {
265 AssignCommand(oNPC, ActionMoveToObject(oCreature, FALSE, 1.5f));
266 AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oCreature))));
267 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING)));
272 oCreature = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, i);
275 if (!bFoundTarget) AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD));
278 else if (sAction == "CONVERSE") {
280 object oPuppet = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, nScan);
281 object oFoundPuppet = OBJECT_INVALID;
283 while (GetIsObjectValid(oPuppet) && GetDistanceBetween(oNPC, oPuppet) < 20.0f) {
284 if (!GetIsPC(oPuppet)) {
285 if (FindSubString(GetStringLowerCase(GetName(oPuppet)), GetStringLowerCase(sTarget)) >= 0) {
286 oFoundPuppet = oPuppet;
291 oPuppet = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oNPC, nScan);
294 if (GetIsObjectValid(oFoundPuppet)) {
295 AssignCommand(oNPC, ActionDoCommand(SetFacingPoint(GetPosition(oFoundPuppet))));
296 AssignCommand(oFoundPuppet, ClearAllActions(TRUE));
297 AssignCommand(oFoundPuppet, SetFacingPoint(GetPosition(oNPC)));
299 if (sTargetSpeech != "") {
300 DelayCommand(3.5f, AssignCommand(oFoundPuppet, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING)));
301 DelayCommand(4.0f, AssignCommand(oFoundPuppet, SpeakString(sTargetSpeech)));
303 DelayCommand(3.5f, AssignCommand(oFoundPuppet, ActionPlayAnimation(ANIMATION_FIREFORGET_PAUSE_SCRATCH_HEAD)));
308 else if (sAction == "OPEN_STORE") {
309 string sShopTag = GetLocalString(oNPC, "LLM_SHOPTAG");
310 if (sShopTag == "") sShopTag = "STORE_" + GetTag(oNPC);
312 object oStore = GetNearestObjectByTag(sShopTag, oNPC);
313 object oBuyer = OBJECT_INVALID;
314 object oTestPC = GetFirstPC();
315 while(GetIsObjectValid(oTestPC)) {
316 if(GetName(oTestPC) == sTargetPlayer) {
320 oTestPC = GetNextPC();
323 if (!GetIsObjectValid(oBuyer)) oBuyer = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC);
325 if (GetIsObjectValid(oStore) && GetIsObjectValid(oBuyer)) {
326 DelayCommand(1.0f, AssignCommand(oNPC, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING)));
327 DelayCommand(1.5f, OpenStore(oStore, oBuyer));
329 AssignCommand(oNPC, ActionSpeakString("I seem to have lost my wares..."));
333 else if (sAction == "GIVE_QUEST") {
334 object oPlayer = OBJECT_INVALID;
335 object oTestPC = GetFirstPC();
336 while(GetIsObjectValid(oTestPC)) {
337 if(GetName(oTestPC) == sTargetPlayer) {
341 oTestPC = GetNextPC();
344 if (!GetIsObjectValid(oPlayer)) oPlayer = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC);
346 if (GetIsObjectValid(oPlayer)) {
347 DelayCommand(1.0f, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_MAGIC_PROTECTION), oNPC));
348 SendMessageToPC(oPlayer, "[SERVER] " + GetName(oNPC) + " has granted you a new quest objective!");
352 else if (sAction == "RETURN_TO_POST") {
353 object oHome = GetWaypointByTag("WP_" + GetTag(oNPC) + "_HOME");
354 if (GetIsObjectValid(oHome)) {
355 DelayCommand(0.2, AssignCommand(oNPC, ActionForceMoveToObject(oHome, FALSE, 1.0f)));
356 object oBed = GetNearestObjectByTag("Bed", oNPC);
357 if (GetIsObjectValid(oBed) && GetDistanceBetween(oHome, oBed) < 5.0) {
358 AssignCommand(oNPC, ActionDoCommand(ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 9999.0)));
360 AssignCommand(oNPC, ActionDoCommand(SetFacing(GetFacing(oHome))));
362 DelayCommand(20.0, ExecuteFallbackJump(oNPC, oHome));
366 nResultId = NWNX_Redis_LPOP("llm_to_nwn");
367 sReplyData = NWNX_Redis_GetResultAsString(nResultId);