// ============================================================================ // File Name: on_player_chat // Generative Agent "Sense" Phase - Placed on Module OnPlayerChat // ============================================================================ #include "nwnx_redis" #include "nwnx_redis_lib" void main() { object oSpeaker = GetPCChatSpeaker(); string sMessage = GetPCChatMessage(); int nVolume = GetPCChatVolume(); if (nVolume != TALKVOLUME_TALK) return; // ========================================================== // 1. THE PHYSICAL EMOTE SYSTEM (/commands) // ========================================================== string sPhysicalEmote = ""; string sLowerMsg = GetStringLowerCase(sMessage); if (GetStringLeft(sLowerMsg, 1) == "/") { if (sLowerMsg == "/bow") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_FIREFORGET_BOW)); sPhysicalEmote = "[The player physically bows respectfully to you.]"; } else if (sLowerMsg == "/laugh") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_LOOPING_TALK_LAUGHING, 1.0, 3.0)); sPhysicalEmote = "[The player is physically laughing out loud.]"; } else if (sLowerMsg == "/taunt" || sLowerMsg == "/threaten") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_FIREFORGET_TAUNT)); sPhysicalEmote = "[The player steps forward and physically taunts you.]"; } else if (sLowerMsg == "/wave" || sLowerMsg == "/greet") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING)); sPhysicalEmote = "[The player physically waves hello to you.]"; } else if (sLowerMsg == "/cheer") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY1)); sPhysicalEmote = "[The player is cheering and celebrating.]"; } else if (sLowerMsg == "/drunk") { AssignCommand(oSpeaker, ActionPlayAnimation(ANIMATION_LOOPING_PAUSE_DRUNK, 1.0, 6.0)); sPhysicalEmote = "[The player is swaying physically, acting heavily intoxicated.]"; } sMessage = ""; } // ========================================================== // 2. EXTRACT PLAYER BODY LANGUAGE & STATE // ========================================================== string sPlayerState = "Relaxed and unarmed."; object oRightHand = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oSpeaker); object oLeftHand = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oSpeaker); int bArmed = FALSE; if (GetIsObjectValid(oRightHand)) { int nType = GetBaseItemType(oRightHand); if (nType != BASE_ITEM_TORCH && nType != BASE_ITEM_BLANK_POTION && nType != BASE_ITEM_SPELLSCROLL) bArmed = TRUE; } if (GetIsObjectValid(oLeftHand) && GetBaseItemType(oLeftHand) != BASE_ITEM_TORCH) bArmed = TRUE; if (bArmed) sPlayerState += " They have a weapon drawn in their hands!"; if (sPhysicalEmote != "") sPlayerState += " " + sPhysicalEmote; string sWorldState = GetLocalString(GetModule(), "LLM_WORLD_STATE"); // ========================================================== // 3. THE RADIUS SCANNER (Group Conversations) // ========================================================== int nNth = 1; object oTarget = GetNearestObject(OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE, oSpeaker, nNth); while (GetIsObjectValid(oTarget) && GetDistanceBetween(oSpeaker, oTarget) <= 5.0f) { string sPersona = GetLocalString(oTarget, "LLM_PERSONA"); if (sPersona == "") sPersona = GetLocalString(oTarget, "LLM_PROMPT"); if (sPersona != "") { // --- STRATEGY OVERRIDE: Prevent Maestros from reacting to player chat --- int nStrategy = GetLocalInt(oTarget, "LLM_STRATEGY"); if (nStrategy == 0) nStrategy = 1; if (nStrategy == 3) { nNth++; oTarget = GetNearestObject(OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE, oSpeaker, nNth); continue; } // --- COOLDOWN CHECK --- if (GetLocalInt(oTarget, "LLM_CHAT_COOLDOWN") == TRUE) { nNth++; oTarget = GetNearestObject(OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE, oSpeaker, nNth); continue; } SetLocalInt(oTarget, "LLM_CHAT_COOLDOWN", TRUE); DelayCommand(6.0f, SetLocalInt(oTarget, "LLM_CHAT_COOLDOWN", FALSE)); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_HEAD_MIND), oTarget); SendMessageToPC(oSpeaker, "[" + GetName(oTarget) + " is listening...]"); string sLawChaos = "Neutral"; int nLawChaos = GetAlignmentLawChaos(oTarget); if (nLawChaos == ALIGNMENT_LAWFUL) sLawChaos = "Lawful"; else if (nLawChaos == ALIGNMENT_CHAOTIC) sLawChaos = "Chaotic"; string sGoodEvil = "Neutral"; int nGoodEvil = GetAlignmentGoodEvil(oTarget); if (nGoodEvil == ALIGNMENT_GOOD) sGoodEvil = "Good"; else if (nGoodEvil == ALIGNMENT_EVIL) sGoodEvil = "Evil"; string sAlignment = sLawChaos + " " + sGoodEvil; if (sAlignment == "Neutral Neutral") sAlignment = "True Neutral"; string sGender = "Unknown"; int nGender = GetGender(oTarget); if (nGender == GENDER_MALE) sGender = "Male"; else if (nGender == GENDER_FEMALE) sGender = "Female"; string sRace = "Creature"; int nRace = GetRacialType(oTarget); if (nRace == RACIAL_TYPE_HUMAN) sRace = "Human"; else if (nRace == RACIAL_TYPE_ELF) sRace = "Elf"; else if (nRace == RACIAL_TYPE_DWARF) sRace = "Dwarf"; else if (nRace == RACIAL_TYPE_HALFLING) sRace = "Halfling"; else if (nRace == RACIAL_TYPE_GNOME) sRace = "Gnome"; else if (nRace == RACIAL_TYPE_HALFELF) sRace = "Half-Elf"; else if (nRace == RACIAL_TYPE_HALFORC) sRace = "Half-Orc"; int nMyMaxHP = GetMaxHitPoints(oTarget); int nMyCurHP = GetCurrentHitPoints(oTarget); string sMyHealth = "Healthy and uninjured."; if (nMyCurHP < nMyMaxHP) { if (nMyCurHP <= (nMyMaxHP / 4)) sMyHealth = "Critically wounded, bleeding heavily, and near death! I need to REST immediately."; else if (nMyCurHP <= (nMyMaxHP / 2)) sMyHealth = "Injured, bruised, and exhausted."; else sMyHealth = "Slightly hurt, with a few cuts and scratches."; } string sRelationship = "Neutral or Friendly."; if (GetIsEnemy(oSpeaker, oTarget)) { sRelationship = "CRITICAL: THIS PLAYER IS YOUR ENEMY! They have attacked you. You are FURIOUS, AGGRESSIVE, and HOSTILE. Do NOT be polite or friendly!"; } string sLocationLore = ""; object oArea = GetArea(oTarget); string sAreaLore = GetLocalString(oArea, "LLM_LOCATION_CONTEXT"); if (sAreaLore != "") sLocationLore = sAreaLore; object oWaypoint = GetNearestObjectByTag("WP_LLM_LORE", oTarget); if (GetIsObjectValid(oWaypoint) && GetDistanceBetween(oTarget, oWaypoint) <= 15.0f) { string sWPLore = GetLocalString(oWaypoint, "LLM_LOCATION_CONTEXT"); if (sWPLore != "") sLocationLore += " SPECIFIC SURROUNDINGS: " + sWPLore; } string sNearbyNPCs = ""; int nNPCScan = 1; object oRadarNPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_NOT_PC, oTarget, nNPCScan); while (GetIsObjectValid(oRadarNPC) && GetDistanceBetween(oTarget, oRadarNPC) <= 15.0f) { if (oRadarNPC != oTarget) sNearbyNPCs += GetName(oRadarNPC) + ", "; nNPCScan++; oRadarNPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_NOT_PC, oTarget, nNPCScan); } json jData = JsonObject(); jData = JsonObjectSet(jData, "npc_tag", JsonString(GetTag(oTarget))); jData = JsonObjectSet(jData, "target_player", JsonString(GetName(oSpeaker))); jData = JsonObjectSet(jData, "message", JsonString(sMessage)); jData = JsonObjectSet(jData, "persona", JsonString(sPersona)); jData = JsonObjectSet(jData, "profession", JsonString(GetLocalString(oTarget, "LLM_PROFESSION"))); jData = JsonObjectSet(jData, "mood", JsonString(GetLocalString(oTarget, "LLM_MOOD"))); jData = JsonObjectSet(jData, "secret", JsonString(GetLocalString(oTarget, "LLM_SECRET"))); jData = JsonObjectSet(jData, "npc_routine", JsonString(GetLocalString(oTarget, "LLM_ROUTINE"))); jData = JsonObjectSet(jData, "npc_alignment", JsonString(sAlignment)); jData = JsonObjectSet(jData, "npc_gender", JsonString(sGender)); jData = JsonObjectSet(jData, "npc_race", JsonString(sRace)); jData = JsonObjectSet(jData, "player_state", JsonString(sPlayerState)); jData = JsonObjectSet(jData, "npc_health", JsonString(sMyHealth)); jData = JsonObjectSet(jData, "relationship", JsonString(sRelationship)); jData = JsonObjectSet(jData, "llm_strategy", JsonInt(nStrategy)); string sQuests = GetLocalString(oTarget, "LLM_QUESTS"); if (sQuests != "") jData = JsonObjectSet(jData, "available_quests", JsonString(sQuests)); string sProps = GetLocalString(oTarget, "LLM_PROPS"); if (sProps != "") jData = JsonObjectSet(jData, "available_props", JsonString(sProps)); if (sLocationLore != "") jData = JsonObjectSet(jData, "location_context", JsonString(sLocationLore)); if (sWorldState != "") jData = JsonObjectSet(jData, "world_state", JsonString(sWorldState)); if (sNearbyNPCs != "") jData = JsonObjectSet(jData, "nearby_npcs", JsonString(sNearbyNPCs)); NWNX_Redis_RPush("nwn_to_llm", JsonDump(jData)); } nNth++; oTarget = GetNearestObject(OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE, oSpeaker, nNth); } }