RuneHive-Game
Loading...
Searching...
No Matches
com.runehive.content.ai.AIDialogueHandler Class Reference
Inheritance diagram for com.runehive.content.ai.AIDialogueHandler:
Collaboration diagram for com.runehive.content.ai.AIDialogueHandler:

Public Member Functions

 AIDialogueHandler (Npc npc)
void sendDialogues (DialogueFactory factory)
 Sends a player a dialogue.

Static Public Member Functions

static void handleAICommand (Player player, String message)
 Process AI command input (called from command handler)
Static Public Member Functions inherited from com.runehive.content.dialogue.Dialogue
static final boolean isDialogueButton (int button)
 Checks if the button triggered is an optional dialogue button.

Private Member Functions

void processGandalfAIMessage (Player player, Npc npc, String message)
String sanitizeInput (String input)
void streamGandalfResponse (Player player, Npc npc, String response)
String[] wrapTextToLines (String text, int maxCharsPerLine)

Static Private Member Functions

static Npc findNearbyNpc (Player player, int npcId, int targetX, int targetY, int maxDistance)
 Find an NPC by ID near specific coordinates.

Private Attributes

final Npc npc

Static Private Attributes

static final int GANDALF_AI_ID = 2108
static final Logger logger = LoggerFactory.getLogger(AIDialogueHandler.class)

Additional Inherited Members

Static Public Attributes inherited from com.runehive.content.dialogue.Dialogue
static final ImmutableList< Integer > DIALOGUE_BUTTONS = ImmutableList.of(2461, 2471, 2482, 2462, 2472, 2483, 2473, 2484, 2485, 2494, 2495, 2496, 2497, 2498)
 The action buttons responsible for dialogues.

Detailed Description

Definition at line 17 of file AIDialogueHandler.java.

Constructor & Destructor Documentation

◆ AIDialogueHandler()

com.runehive.content.ai.AIDialogueHandler.AIDialogueHandler ( Npc npc)

Definition at line 24 of file AIDialogueHandler.java.

24 {
25 this.npc = npc;
26 }

References npc.

Referenced by handleAICommand(), and streamGandalfResponse().

Here is the caller graph for this function:

Member Function Documentation

◆ findNearbyNpc()

Npc com.runehive.content.ai.AIDialogueHandler.findNearbyNpc ( Player player,
int npcId,
int targetX,
int targetY,
int maxDistance )
staticprivate

Find an NPC by ID near specific coordinates.

Definition at line 101 of file AIDialogueHandler.java.

101 {
102 for (Npc npc : World.getNpcs()) {
103 if (npc.id == npcId && npc.getPosition().getHeight() == player.getPosition().getHeight()) {
104 int distX = Math.abs(npc.getPosition().getX() - targetX);
105 int distY = Math.abs(npc.getPosition().getY() - targetY);
106 if (distX <= maxDistance && distY <= maxDistance) {
107 return npc;
108 }
109 }
110 }
111 return null;
112 }

References com.runehive.game.world.position.Position.getHeight(), com.runehive.game.world.World.getNpcs(), com.runehive.game.world.entity.Entity.getPosition(), and npc.

Referenced by handleAICommand().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ handleAICommand()

void com.runehive.content.ai.AIDialogueHandler.handleAICommand ( Player player,
String message )
static

Process AI command input (called from command handler)

Definition at line 53 of file AIDialogueHandler.java.

53 {
54 int npcSpawnX = 1684;
55 int npcSpawnY = 3746;
56 int maxDistance = 1;
57
58 if (Math.abs(player.getPosition().getX() - npcSpawnX) > maxDistance ||
59 Math.abs(player.getPosition().getY() - npcSpawnY) > maxDistance) {
60 player.message("You must be near the Wise Old Man to use AI chat.");
61 return;
62 }
63
64 if (!LazyAIManager.isInitialized()) {
65 player.message("AI services are not initialized. Click the Wise Old Man first.");
66 return;
67 }
68
69 // Find the actual spawned Wise Old Man NPC near the spawn coordinates
70 Npc gandalf = findNearbyNpc(player, GANDALF_AI_ID, npcSpawnX, npcSpawnY, maxDistance);
71
72 if (gandalf == null) {
73 player.message("Could not find the Wise Old Man NPC. Please stand closer.");
74 return;
75 }
76
77 // ::gpt must use the exact same choreography as Talk-to on NPC 2108
78 // Teleport P to exact position facing the NPC; LOCK P.
79 com.runehive.game.world.position.Position npcPos = gandalf.getPosition();
80 com.runehive.game.world.position.Position playerTarget = new com.runehive.game.world.position.Position(npcPos.getX() - 1, npcPos.getY(), npcPos.getHeight());
81 player.move(playerTarget);
82 player.face(gandalf);
83 player.locking.lock();
84
85 // Initialize dialogue camera - player speaks first (with delay to prevent misalignment)
86 player.dialogueCamNpc = gandalf;
87 player.dialogueCamMode = com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.Mode.OFF;
88 player.dialogueCamDelayTicks = 2; // delay 2 ticks before camera activates
89 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.boostSwap(player, 8);
90
91 // Mark that player has received instructions and is now using the command
92 LazyAIManager.markInstructionsReceived(player.getUsername());
93
94 String sanitizedInput = new AIDialogueHandler(gandalf).sanitizeInput(message);
95 new AIDialogueHandler(gandalf).processGandalfAIMessage(player, gandalf, sanitizedInput);
96 }
int getHeight()
Gets the height coordinate, or height.
Definition Position.java:51
int getY()
Gets the absolute y coordinate.
Definition Position.java:46
int getX()
Gets the absolute x coordinate.
Definition Position.java:41

References AIDialogueHandler(), com.runehive.game.world.entity.mob.Mob.face(), findNearbyNpc(), GANDALF_AI_ID, com.runehive.game.world.position.Position.getHeight(), com.runehive.game.world.entity.Entity.getPosition(), com.runehive.game.world.entity.mob.player.Player.getUsername(), com.runehive.game.world.position.Position.getX(), com.runehive.game.world.position.Position.getY(), com.runehive.content.ai.LazyAIManager.isInitialized(), com.runehive.game.world.entity.mob.Locking.lock, com.runehive.game.world.entity.mob.Mob.locking, com.runehive.content.ai.LazyAIManager.markInstructionsReceived(), com.runehive.game.world.entity.mob.player.Player.message(), com.runehive.game.world.entity.mob.Mob.move(), and com.runehive.game.world.position.Position.Position().

Here is the call graph for this function:

◆ processGandalfAIMessage()

void com.runehive.content.ai.AIDialogueHandler.processGandalfAIMessage ( Player player,
Npc npc,
String message )
private

Definition at line 122 of file AIDialogueHandler.java.

122 {
123 // Spawn chair at NPC position (facing WEST)
124 Position chairPos = npc.getPosition().copy();
125 CustomGameObject chair = new CustomGameObject(1096, chairPos, ObjectDirection.WEST, com.runehive.game.world.object.ObjectType.GENERAL_PROP);
126 chair.register();
127
128 // Start thinking animation and speak "Hmmmmm" for 3 ticks
129 // Treat as advancing into NPC turn (flip camera behind NPC and speed up the turn slightly).
130 player.dialogueCamMode = com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.Mode.BEHIND_NPC;
131 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.boostSwap(player, 8);
132 npc.animate(new com.runehive.game.Animation(4079));
133 npc.speak("Hmmmmm");
134
135 logger.info("Processing Gandalf AI request from {} (length: {})",
136 player.getUsername(), message.length());
137
138 LazyAIManager.getOpenAIService()
139 .processPlayerMessage(player.getUsername(), message, npc.id)
140 .thenAccept(response -> {
141 World.schedule(new Task(3) {
142 @Override
143 public void execute() {
144 if (player == null || !player.isRegistered()) {
145 logger.warn("Player {} is no longer online, skipping response",
146 player != null ? player.getUsername() : "null");
147 chair.unregister();
148 cancel();
149 return;
150 }
151
152 try {
153 // Remove chair and reset animation after 3 ticks
154 chair.unregister();
155 npc.animate(new com.runehive.game.Animation(-1));
156 player.locking.lock();
157
158 if (response != null && !response.isEmpty()) {
159 streamGandalfResponse(player, npc, response);
160 } else {
161 player.locking.unlock();
162 player.dialogueFactory
163 .sendNpcChat(npc.id, Expression.SAD,
164 "The mystical connection faltered.",
165 "Perhaps try again with a different query.")
166 .onAction(() -> {
167 player.locking.unlock();
168 player.interfaceManager.close();
169 })
170 .execute();
171 }
172 } catch (Exception e) {
173 logger.error("Exception while processing AI response for {}", player.getUsername(), e);
174 player.locking.unlock();
175 }
176 cancel();
177 }
178 });
179 })
180 .exceptionally(throwable -> {
181 logger.error("Error in Gandalf AI processing", throwable);
182 World.schedule(new Task(1) {
183 @Override
184 public void execute() {
185 if (player == null || !player.isRegistered()) {
186 cancel();
187 return;
188 }
189
190 try {
191 player.locking.unlock();
192 player.dialogueFactory
193 .sendNpcChat(npc.id, Expression.ANGRY,
194 "The AI spirits are restless.",
195 "Please try again in a moment.")
196 .onAction(() -> {
197 player.interfaceManager.close();
198 })
199 .execute();
200 } catch (Exception e) {
201 logger.error("Exception in error handler for {}", player.getUsername(), e);
202 player.locking.unlock();
203 }
204 cancel();
205 }
206 });
207 return null;
208 });
209 }

References com.runehive.content.dialogue.Expression.ANGRY, com.runehive.game.world.entity.mob.player.InterfaceManager.close(), com.runehive.content.ai.LazyAIManager.getOpenAIService(), com.runehive.game.world.entity.mob.player.Player.getUsername(), com.runehive.game.world.entity.mob.player.Player.interfaceManager, com.runehive.game.world.entity.Entity.isRegistered(), com.runehive.game.world.entity.mob.Locking.lock, com.runehive.game.world.entity.mob.Mob.locking, logger, npc, com.runehive.content.ai.OpenAIService.processPlayerMessage(), com.runehive.game.world.object.CustomGameObject.register(), com.runehive.content.dialogue.Expression.SAD, com.runehive.game.world.World.schedule(), streamGandalfResponse(), com.runehive.game.world.entity.mob.Locking.unlock(), com.runehive.game.world.object.CustomGameObject.unregister(), and com.runehive.game.world.object.ObjectDirection.WEST.

Here is the call graph for this function:

◆ sanitizeInput()

String com.runehive.content.ai.AIDialogueHandler.sanitizeInput ( String input)
private

Definition at line 114 of file AIDialogueHandler.java.

114 {
115 if (input == null) {
116 return "";
117 }
118 String cleaned = input.trim().replaceAll("[<>]", "");
119 return cleaned.substring(0, Math.min(cleaned.length(), 500));
120 }

◆ sendDialogues()

void com.runehive.content.ai.AIDialogueHandler.sendDialogues ( DialogueFactory factory)

Sends a player a dialogue.

Parameters
factoryThe factory for this dialogue.

Reimplemented from com.runehive.content.dialogue.Dialogue.

Definition at line 29 of file AIDialogueHandler.java.

29 {
30 Player player = factory.getPlayer();
31 player.locking.lock();
32
33 // Ensure director knows the NPC and start with delay to prevent coordinate misalignment
34 player.dialogueCamNpc = npc; // 2108
35 player.dialogueCamMode = com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.Mode.OFF;
36 player.dialogueCamDelayTicks = 2; // delay 2 ticks before camera activates
37 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.boostSwap(player, 8);
38
39 factory.sendNpcChat(npc.id, Expression.HAPPY,
40 "What would you like to ask?",
41 "Type in chat: ::gpt <your message>")
42 .onAction(() -> {
43 player.locking.unlock();
44 player.interfaceManager.close();
45 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.requestReset(player);
46 })
47 .execute();
48 }

References com.runehive.game.world.entity.mob.player.InterfaceManager.close(), com.runehive.content.dialogue.DialogueFactory.getPlayer(), com.runehive.content.dialogue.Expression.HAPPY, com.runehive.game.world.entity.mob.player.Player.interfaceManager, com.runehive.game.world.entity.mob.Locking.lock, com.runehive.game.world.entity.mob.Mob.locking, npc, com.runehive.content.dialogue.DialogueFactory.onAction(), com.runehive.content.dialogue.DialogueFactory.sendNpcChat(), and com.runehive.game.world.entity.mob.Locking.unlock().

Here is the call graph for this function:

◆ streamGandalfResponse()

void com.runehive.content.ai.AIDialogueHandler.streamGandalfResponse ( Player player,
Npc npc,
String response )
private

Definition at line 211 of file AIDialogueHandler.java.

211 {
212 logger.info("[TTS-SERVER] ========================================");
213 logger.info("[TTS-SERVER] Sending Gandalf response to player: {}", player.getUsername());
214 logger.info("[TTS-SERVER] Full response: {}", response);
215
216 String[] lines = wrapTextToLines(response, 50);
217 logger.info("[TTS-SERVER] Wrapped into {} lines", lines.length);
218 for (int i = 0; i < lines.length; i++) {
219 logger.info("[TTS-SERVER] Line {}: {}", i+1, lines[i]);
220 }
221
222 DialogueFactory factory = player.dialogueFactory;
223
224 // Set camera to behind NPC for AI response
225 player.dialogueCamMode = com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.Mode.BEHIND_NPC;
226
227 int linesPerPage = 3;
228 for (int i = 0; i < lines.length; i += linesPerPage) {
229 int endIndex = Math.min(i + linesPerPage, lines.length);
230 String[] pageLines = java.util.Arrays.copyOfRange(lines, i, endIndex);
231
232 logger.info("[TTS-SERVER] Sending dialogue page {}: {} lines", (i/linesPerPage)+1, pageLines.length);
233
234 factory.sendNpcChat(npc.id, Expression.HAPPY, pageLines)
235 .onAction(() -> {
236 String combinedText = String.join(" ", pageLines);
237 npc.speak(combinedText);
238 });
239 }
240 logger.info("[TTS-SERVER] ========================================");
241
242 factory.sendOption(
243 "Ask another question", () -> {
244 // ASK ANOTHER QUESTION: Immediately reset camera & unlock
245 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.requestReset(player);
246 player.locking.unlock();
247 player.dialogueFactory.sendDialogue(new AIDialogueHandler(npc)).execute();
248 },
249 "No further questions", () -> {
250 // OPT OUT: Rewind to BEHIND_PLAYER (buttery swap), wait 4 ticks, then overhead line + wave, then unlock and reset
251 player.interfaceManager.close(); // Close interface first
252 player.dialogueCamMode = com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.Mode.BEHIND_PLAYER;
253 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.boostSwap(player, 6);
254
255 // Wait 5 ticks for camera to reposition behind player, then player speaks
256 World.schedule(new Task(5) {
257 @Override
258 public void execute() {
259 // Player overhead text
260 player.speak("No, that's all, thank you.");
261
262 // 1 tick later, NPC responds with overhead + wave
263 World.schedule(new Task(1) {
264 @Override
265 public void execute() {
266 npc.speak("Come back again, anytime!");
267 npc.animate(863);
268
269 // After overhead line + animation, unlock & reset camera
270 World.schedule(new Task(3) {
271 @Override
272 public void execute() {
273 LazyAIManager.clearPlayerConsent(player.getUsername());
274 player.locking.unlock();
275 com.runehive.game.world.entity.mob.player.camera.DialogueCameraDirector.requestReset(player);
276 cancel();
277 }
278 });
279 cancel();
280 }
281 });
282 cancel();
283 }
284 });
285 }
286 );
287
288 factory.execute();
289 }

References AIDialogueHandler(), com.runehive.content.ai.LazyAIManager.clearPlayerConsent(), com.runehive.game.world.entity.mob.player.InterfaceManager.close(), com.runehive.game.world.entity.mob.player.Player.dialogueFactory, com.runehive.content.dialogue.DialogueFactory.execute(), com.runehive.game.world.entity.mob.player.Player.getUsername(), com.runehive.content.dialogue.Expression.HAPPY, com.runehive.game.world.entity.mob.player.Player.interfaceManager, com.runehive.game.world.entity.mob.Mob.locking, logger, npc, com.runehive.content.dialogue.DialogueFactory.onAction(), com.runehive.content.dialogue.DialogueFactory.player, com.runehive.game.world.World.schedule(), com.runehive.content.dialogue.DialogueFactory.sendDialogue(), com.runehive.content.dialogue.DialogueFactory.sendNpcChat(), com.runehive.content.dialogue.DialogueFactory.sendOption(), com.runehive.game.world.entity.mob.Mob.speak(), com.runehive.game.world.entity.mob.Locking.unlock(), and wrapTextToLines().

Referenced by processGandalfAIMessage().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wrapTextToLines()

String[] com.runehive.content.ai.AIDialogueHandler.wrapTextToLines ( String text,
int maxCharsPerLine )
private

Definition at line 291 of file AIDialogueHandler.java.

291 {
292 String[] words = text.split(" ");
293 StringBuilder current = new StringBuilder();
294 java.util.List<String> lines = new java.util.ArrayList<>();
295
296 for (String word : words) {
297 if (current.length() + word.length() + 1 > maxCharsPerLine) {
298 if (current.length() > 0) {
299 lines.add(current.toString().trim());
300 current = new StringBuilder(word);
301 } else {
302 lines.add(word);
303 }
304 } else {
305 if (current.length() > 0) {
306 current.append(" ");
307 }
308 current.append(word);
309 }
310 }
311
312 if (current.length() > 0) {
313 lines.add(current.toString().trim());
314 }
315
316 return lines.toArray(new String[0]);
317 }

Referenced by streamGandalfResponse().

Here is the caller graph for this function:

Member Data Documentation

◆ GANDALF_AI_ID

final int com.runehive.content.ai.AIDialogueHandler.GANDALF_AI_ID = 2108
staticprivate

Definition at line 20 of file AIDialogueHandler.java.

Referenced by handleAICommand().

◆ logger

final Logger com.runehive.content.ai.AIDialogueHandler.logger = LoggerFactory.getLogger(AIDialogueHandler.class)
staticprivate

Definition at line 19 of file AIDialogueHandler.java.

Referenced by processGandalfAIMessage(), and streamGandalfResponse().

◆ npc

final Npc com.runehive.content.ai.AIDialogueHandler.npc
private

The documentation for this class was generated from the following file: