// ============================================================================ // File Name: bpc_heartbeat // The "Sense" phase of the Generative AI Loop OnHeartbeat obecjt event // ============================================================================ #include "nwnx_redis" #include "nwnx_redis_lib" void main() { object oNPC = OBJECT_SELF; object oNearestPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC); if (!GetIsObjectValid(oNearestPC) || GetDistanceBetween(oNPC, oNearestPC) > 25.0f) { return; } int nTick = GetLocalInt(oNPC, "LLM_RADAR_TICK") + 1; if (nTick >= 10) nTick = 0; SetLocalInt(oNPC, "LLM_RADAR_TICK", nTick); if (nTick != 0) return; int nMyMaxHP = GetMaxHitPoints(oNPC); int nMyCurHP = GetCurrentHitPoints(oNPC); 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."; } int nStrategy = GetLocalInt(oNPC, "LLM_STRATEGY"); if (nStrategy == 0) nStrategy = 1; string sObservation = "You are observing your surroundings."; if (nStrategy != 3) { sObservation += " " + GetName(oNearestPC) + " is standing nearby."; } string sNearbyNPCs = ""; int nNPCScan = 1; object oNearbyNPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_NOT_PC, oNPC, nNPCScan); while (GetIsObjectValid(oNearbyNPC) && GetDistanceBetween(oNPC, oNearbyNPC) <= 15.0f) { if (oNearbyNPC != oNPC) sNearbyNPCs += GetName(oNearbyNPC) + ", "; nNPCScan++; oNearbyNPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_NOT_PC, oNPC, nNPCScan); } int nPropScan = 1; object oProp = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, nPropScan); while (GetIsObjectValid(oProp) && GetDistanceBetween(oNPC, oProp) < 15.0f && nPropScan <= 3) { if (GetUseableFlag(oProp) || GetLocalInt(oProp, "NW_INTERACTIVE") == TRUE) { sObservation += " You notice a " + GetName(oProp) + " nearby."; break; } nPropScan++; oProp = GetNearestObject(OBJECT_TYPE_PLACEABLE, oNPC, nPropScan); } string sLocationLore = ""; object oArea = GetArea(oNPC); string sAreaLore = GetLocalString(oArea, "LLM_LOCATION_CONTEXT"); if (sAreaLore != "") sLocationLore = sAreaLore; object oWaypoint = GetNearestObjectByTag("WP_LLM_LORE", oNPC); if (GetIsObjectValid(oWaypoint) && GetDistanceBetween(oNPC, oWaypoint) <= 15.0f) { string sWPLore = GetLocalString(oWaypoint, "LLM_LOCATION_CONTEXT"); if (sWPLore != "") sLocationLore += " SPECIFIC SURROUNDINGS: " + sWPLore; } json jData = JsonObject(); jData = JsonObjectSet(jData, "npc_tag", JsonString(GetTag(oNPC))); jData = JsonObjectSet(jData, "target_player", JsonString("Environment")); jData = JsonObjectSet(jData, "message", JsonString(sObservation)); jData = JsonObjectSet(jData, "persona", JsonString(GetLocalString(oNPC, "LLM_PERSONA"))); jData = JsonObjectSet(jData, "profession", JsonString(GetLocalString(oNPC, "LLM_PROFESSION"))); jData = JsonObjectSet(jData, "npc_health", JsonString(sMyHealth)); jData = JsonObjectSet(jData, "llm_strategy", JsonInt(nStrategy)); if (sLocationLore != "") jData = JsonObjectSet(jData, "location_context", JsonString(sLocationLore)); if (sNearbyNPCs != "") jData = JsonObjectSet(jData, "nearby_npcs", JsonString(sNearbyNPCs)); string sQuests = GetLocalString(oNPC, "LLM_QUESTS"); if (sQuests != "") jData = JsonObjectSet(jData, "available_quests", JsonString(sQuests)); string sProps = GetLocalString(oNPC, "LLM_PROPS"); if (sProps != "") jData = JsonObjectSet(jData, "available_props", JsonString(sProps)); NWNX_Redis_RPush("nwn_to_llm", JsonDump(jData)); }