blob: 3731514d001e05ac323f347ccf12e59d3d528501 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// gmcp.c -- Verwaltung von GMCP im Spielerobjekt
4//
5// $Id$
6
7#pragma strong_types,save_types
8#pragma range_check
9#pragma no_clone
10#pragma no_shadow
11#pragma pedantic
12
13#include <regexp.h>
14#include <telnet.h>
15
16#define NEED_PROTOTYPES
17#include <player/base.h>
18#include <thing/properties.h>
19#include <living/attributes.h>
20#include <player/gmcp.h>
21#include <thing/description.h>
22#include <living/description.h>
23#undef NEED_PROTOTYPES
24
25#include <properties.h>
26#include <new_skills.h>
27#include <rooms.h>
28#include <tls.h>
29
30inherit "/secure/telnetneg-structs.c";
31
32struct gmcp_mod_s {
33 string id; // Name des GMCP-Moduls (z.B. "MG.Char")
34 int version; // Version des aktivierten moduls
35 closure sendcl; // Handler zum Senden (lfun-closure)
36 closure recvcl; // Handler zum Empfangen (lfunc-closure)
37 mixed data; // optional data of the module
38};
39
40nosave mapping gmcpdata;
41/* Struktur:
42 Jedes Modul hat einen Schluessel im Toplevel, worunter ggf. seine Daten
43 abgelegt sind. Die Daten sind eine struct gmcp_mod_s. Der Schluessel ist
44 der Modulname OHNE Version.
45 */
46#define NEED_PROTOTYPES
47#include "/secure/telnetneg.h"
48#undef NEED_PROTOTYPES
49
50//#define __GMCP_DEBUG__ 1
51// Low priority debug messages
52#define GMCP_DEBUG(pre,msg,prio) if (interactive(this_object()) \
53 && gmcpdata) \
54 GMCP_debug(pre,msg,prio);
55// higher priority error messages
56#define GMCPERROR(msg) if (interactive(this_object()) \
57 && gmcpdata) \
58 GMCP_debug("ERROR",msg,10);
59
60
61// **************** API nach Aussen folgt ab hier ********************
62
63// Wird vom Spielerobjekt gerufen, wenn sich Daten am Charakter veraendert
64// haben, die gesendet werden sollten.
65// Dies ist eigentlich nur ein Wrapper, der die Daten an den Handler eines
66// Moduls weitergibt, welches vom Client aktiviert wurde. Hierzu kommen zur
67// Zeit 2 in Frage: MG.Char (bevorzugt) und Char (minimaler Support).
68/*protected*/ int GMCP_Char(mapping data) {
69
70 if (!mappingp(gmcpdata)) return 0;
71
72 // Als erstes schauen, ob der Client MG.Char aktiviert hat.
73 struct gmcp_mod_s mod = gmcpdata["MG.char"];
74 if (structp(mod) && closurep(mod->sendcl))
75 {
76 funcall(mod->sendcl, data);
77 return 1;
78 }
79 // Dann noch das Modul char pruefen. Das ist aber ziemlich eingeschraenkt
80 // und es gibt hoffentlich nicht viele Clients, die es benutzen.
81 // (Aardwolf-Modul)
82 mod = gmcpdata["char"];
83 if (structp(mod) && closurep(mod->sendcl))
84 {
85 funcall(mod->sendcl, data);
86 return 1;
87 }
88 // Dann noch das Modul Char pruefen. Das ist aber ziemlich eingeschraenkt
89 // und es gibt hoffentlich nicht viele Clients, die es benutzen.
90 // (IRE-Modul)
91 mod = gmcpdata["Char"];
92 if (structp(mod) && closurep(mod->sendcl))
93 {
94 funcall(mod->sendcl, data);
95 return 1;
96 }
97 return 0;
98}
99
100/*protected*/ int GMCP_Channel(string msg, string channel, string sender) {
101 if (!mappingp(gmcpdata)) return 0;
102 // comm.channel Modul aktiv?
103 struct gmcp_mod_s mod = gmcpdata["comm.channel"];
104 if (structp(mod) && closurep(mod->sendcl))
105 {
106 funcall(mod->sendcl, (["chan":channel, "player": sender,
107 "msg": msg]) );
108 return 1;
109 }
110 return 0;
111}
112
113/*protected*/ int GMCP_Room() {
114 if (!mappingp(gmcpdata)) return 0;
115 // MG.room Modul aktiv?
116 struct gmcp_mod_s mod = gmcpdata["MG.room"];
117 if (structp(mod) && closurep(mod->sendcl))
118 {
119 funcall(mod->sendcl, 0);
120 return 1;
121 }
122 return 0;
123}
124
125// **************** Ab hier folgen eher die Lowlevel-Dinge ***********
126private void GMCP_debug(string pre, string msg, int prio) {
127 struct gmcp_mod_s mod = gmcpdata["Core"];
128 if (mod && (mod->data)["Debug"] >= prio)
129 tell_object(this_object(), sprintf("GMCP %s: %s\n",pre,msg));
130}
131
132private void GMCP_send(string cmd, mixed data)
133{
134 GMCP_DEBUG("GMCP_send",sprintf("%s %O",cmd,data), 30);
Zesstra9ebed822019-11-27 19:50:17 +0100135 send_telnet_neg_str(
136 to_bytes(({SB, TELOPT_GMCP})) +
137 to_bytes(sprintf("%s %s", cmd, json_serialize(data)),
138 "ASCII//TRANSLIT"), 1);
MG Mud User88f12472016-06-24 23:31:02 +0200139}
140
141private void GMCP_unregister_module(string mod)
142{
143 int version;
144 // Wenn nicht "mod version" Schema, ignorieren
145 if (sscanf(mod, "%s %d", mod, version) != 2)
146 return;
147
148 if (mod=="Core") // darf nicht abgeschaltet werden.
149 return;
150
151 m_delete(gmcpdata, mod);
152}
153
154private void GMCP_register_module(string modname)
155{
156 int version;
157 GMCP_DEBUG("register_module(): trying ... ",modname, 20);
158 // Wenn nicht "mod version" Schema, ignorieren
159 if (sscanf(modname, "%s %d", modname, version) != 2)
160 return;
161
162// GMCP_DEBUG("register_module()",modname + " v" + version);
163
164 // Modul (ggf. mit anderer Version) bereits aktiv?
165 struct gmcp_mod_s mod = gmcpdata[modname];
166 if (structp(mod)) {
167 // wenn gleicher Name und Version, wird nix gemacht, bei anderer Version
168 // wird ein neuer Handler eingetragen und die Daten geloescht.
169 // Wenn nicht-existierende Modul/Version-Kombi angefordert wird, ist das
170 // Modul hinterher aus.
171 if (mod->id == modname && mod->version == version)
172 return;
173 else
174 m_delete(gmcpdata,modname);
175 }
176
177 // Das GMCP-Modul ist nur verfuegbar, wenn es zu der Kombination aus mod und
178 // version einen Handler zum Senden gibt...
179 // Der Handler ist: GMCP_<mod>_v<version>_send, aber in <mod> werden alle
180 // "." durch "_" ersetzt.
181 string replacedname = regreplace(modname, "\\.", "_", RE_GLOBAL);
182 closure sendcl = symbol_function(sprintf("GMCPmod_%s_v%d_send",
183 replacedname, version),
184 this_object());
185 if (!sendcl)
186 return;
187 // Diese Closure darf 0 sein. Dann findet keine Behandlung von vom Client
188 // gesendeten Kommandos statt. Was fuer die meisten Module auch in Ordnung
189 // ist, da sie dem Client keine Kommandos anbieten.
190 closure recvcl = symbol_function(sprintf("GMCPmod_%s_v%d_recv",
191 replacedname, version),
192 this_object());
193
194 GMCP_DEBUG("register_module()",modname+" erfolgreich registriert.",10);
195
196 mod = (<gmcp_mod_s> id: modname, version : version,
197 sendcl : sendcl, recvcl: recvcl, data: ([]) );
198 gmcpdata[modname] = mod;
199
200 // Zum schluss noch den Senden-handler mal rufen, damit der mal alle
201 // verfuegbaren daten sendet.
202 funcall(mod->sendcl, 0);
203}
204
205// Handler fuer das Core Modul von GMCP
206// Gerufen bei Empfang von Kommandos vom Client.
207protected void GMCPmod_Core_v1_recv(string cmd, mixed args)
208{
209 struct gmcp_mod_s mod = gmcpdata["Core"];
210 mapping data = mod->data;
211
212/* if (!mod)
213 {
214 GMCPERROR("Command %s for disabled module ignored.");
215 return;
216 }
217 */
218 GMCP_DEBUG("GMCPmod_Core_v1: ", cmd, 20);
219
220 switch (cmd)
221 {
222 case "Core.Hello":
223 if (mappingp(args))
224 data["Hello"] = (["client": args["client"],
225 "version": args["version"] ]);
226 break;
227 case "Core.Supports.Set":
228 if (pointerp(args))
229 {
230 // Alte Module abschalten/loeschen
231 foreach(string m : data["Supports"])
232 GMCP_unregister_module(m);
233 data["Supports"] = args;
234 // Versuchen, die neuen Module zu registrieren
235 foreach(string m : args)
236 GMCP_register_module(m);
237 }
238 else
239 GMCP_DEBUG("GMCPmod_Core_v1: ",
240 "Data for Core.Supports.Set is no array", 5);
241 break;
242 case "Core.Supports.Add":
243 if (!pointerp(data["Supports"]))
244 data["Supports"] = ({});
245 if (pointerp(args))
246 {
247 foreach(string m: args)
248 GMCP_register_module(m);
249 data["Supports"] += args;
250 }
251 break;
252 case "Core.Supports.Remove":
253 if (!pointerp(data["Supports"]))
254 break;
255 if (pointerp(args))
256 {
257 foreach(string m: args)
258 GMCP_unregister_module(m);
259 data["Supports"] -= args;
260 }
261 break;
262 case "Core.Supports.KeepAlive":
263 break; // this is ignored by us.
264 case "Core.Ping":
265 if (intp(args))
266 data["Ping"] = args;
267 // send a ping back
268 GMCP_send("Core.Ping",0);
269 break;
270 case "Core.Debug":
271 if (intp(args) && args >= 0)
272 data["Debug"] = args;
273 break;
274 default:
275 GMCPERROR(sprintf("Unknown GMCP Core cmd %s with args %O",
276 cmd, args));
277 break;
278 }
279}
280
281// Handler fuer das Core Modul von GMCP
282// Gerufen, wenn Daten zu senden sind.
283protected void GMCPmod_Core_v1_send(mapping data)
284{
Zesstra6d413712019-07-27 19:03:38 +0200285 // Wenn Core registriert wird, wird diese Funktion gerufen und <data> als 0
286 // uebergeben. Wir nutzen das zur Erkennung, dass GMCP aktiviert wurde und
287 // senden die URI fuer das Client-UI-Package.
288 // Bemerkung: ja... Warum zur Hoelle macht Mudlet das so? Es sollte ein
289 // Modul UI definiert werden, was vom Client angefordert wird, anstatt dass
290 // wir auf Verdacht da etwas aus einem nicht-angeforderten Modul rauspusten,
291 // sobald GMCP aktiviert wird.
Zesstra5c155e22019-09-23 21:15:49 +0200292 // Wenn das mal jemand von anderen Clients anmeckert, fliegt es raus.
Zesstra6d413712019-07-27 19:03:38 +0200293 if (!data)
Zesstra040efb72019-07-30 20:23:31 +0200294 {
295 <int|string>* version = (__DIR__"mudlet_gui")->current_version();
296 if (version)
297 {
Zesstra5c155e22019-09-23 21:15:49 +0200298 GMCP_send("Client.GUI",
299 (["version": version[1], "url": version[0]]) );
Zesstra040efb72019-07-30 20:23:31 +0200300 }
301 }
Zesstra6d413712019-07-27 19:03:38 +0200302
303 // Zur Zeit passiert hier weiter nix, spaeter mal Core.Goodbye senden.
MG Mud User88f12472016-06-24 23:31:02 +0200304}
305
306
307// Handler fuer das MG.Char Modul
308// Gerufen bei Empfang von Kommandos vom Client.
309protected void GMCPmod_MG_char_v1_send(mapping data)
310{
311 mapping squeue = m_allocate(5,0);
312 struct gmcp_mod_s mod = gmcpdata["MG.char"];
313 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
314 // leider immer alle senden, nicht nur die geaenderten.
315 if (!mappingp(data))
316 {
317 // Alle verfuegbaren Informationen senden...
318 mod->data = m_allocate(6);
319 m_add(mod->data, "MG.char.base",
320 ([P_NAME: Name(WER),
321 P_GUILD: QueryProp(P_GUILD),
322 P_PRESAY: QueryProp(P_PRESAY), // TODO
323 P_TITLE: QueryProp(P_TITLE),
324 "wizlevel": query_wiz_level(this_object()),
325 P_RACE: QueryProp(P_RACE)]) ); // TODO
326 m_add(mod->data,"MG.char.vitals",
327 ([P_HP: QueryProp(P_HP),
328 P_SP: QueryProp(P_SP),
329 P_POISON: QueryProp(P_POISON) ]) );
330 m_add(mod->data,"MG.char.maxvitals",
331 ([P_MAX_HP: QueryProp(P_MAX_HP),
332 P_MAX_SP: QueryProp(P_MAX_SP),
333 P_MAX_POISON: QueryProp(P_MAX_POISON) ]) );
334 m_add(mod->data,"MG.char.attributes",
335 ([ A_STR: QueryAttribute(A_STR),
336 A_INT: QueryAttribute(A_INT),
337 A_DEX: QueryAttribute(A_DEX),
338 A_CON: QueryAttribute(A_CON) ]) );
339 m_add(mod->data,"MG.char.info",
340 ([P_LEVEL: QueryProp(P_LEVEL),
341 P_GUILD_LEVEL: QueryProp(P_GUILD_LEVEL),
342 P_GUILD_TITLE: QueryProp(P_GUILD_TITLE) ]) );
343 m_add(mod->data,"MG.char.wimpy",
344 ([P_WIMPY: QueryProp(P_WIMPY),
345 P_WIMPY_DIRECTION: QueryProp(P_WIMPY_DIRECTION) ]) );
346 m_add(squeue,"MG.char.base");
347 m_add(squeue,"MG.char.vitals");
348 m_add(squeue,"MG.char.maxvitals");
349 m_add(squeue,"MG.char.attributes");
350 m_add(squeue,"MG.char.info");
351 m_add(squeue,"MG.char.wimpy");
352 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
353 // nur beim Start des Moduls gesendet).
354 GMCP_send("MG.char.infoVars", ([
355 P_LEVEL: "Spielerstufe", P_GUILD_LEVEL: "Gildenstufe",
356 P_GUILD_TITLE: "Gildentitel" ]) );
357 }
358 else
359 {
360 // nur die in data enthaltenen senden.
361 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
362 // muss... *seufz*
363 foreach(string key, mixed val : data)
364 {
365 switch(key)
366 {
367 case P_HP:
368 case P_SP:
369 case P_POISON:
370 (mod->data)["MG.char.vitals"] += ([key: val]);
371 m_add(squeue,"MG.char.vitals");
372 break;
373 case P_MAX_HP:
374 case P_MAX_SP:
375 case P_MAX_POISON:
376 (mod->data)["MG.char.maxvitals"] += ([key: val]);
377 m_add(squeue,"MG.char.maxvitals");
378 break;
379 case P_NAME:
380 (mod->data)["MG.char.base"] += ([key: Name(WER)]);
381 m_add(squeue,"MG.char.base");
382 break;
383 case P_RACE:
384 case P_PRESAY:
385 case P_TITLE:
386 case P_GUILD:
387 (mod->data)["MG.char.base"] += ([key: val]);
388 m_add(squeue,"MG.char.base");
389 break;
390 case A_DEX:
391 case A_STR:
392 case A_CON:
393 case A_INT:
394 (mod->data)["MG.char.attributes"] += ([key: val]);
395 m_add(squeue,"MG.char.attributes");
396 break;
397 case P_LEVEL:
398 case P_GUILD_LEVEL:
399 case P_GUILD_TITLE:
400 (mod->data)["MG.char.info"] += ([key: val]);
401 m_add(squeue,"MG.char.info");
402 break;
403 case P_WIMPY:
404 case P_WIMPY_DIRECTION:
405 (mod->data)["MG.char.wimpy"] += ([key: val]);
406 m_add(squeue,"MG.char.wimpy");
407 break;
408 }
409 }
410 }
411 GMCP_DEBUG("GMCPmod_MG_char_v1_send()",
412 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
413
414 // Jetzt die squeue senden...
415 foreach(string key : squeue)
416 {
417 GMCP_send(key, (mod->data)[key]);
418 }
419}
420
421// Handler fuer das MG.Char Modul
422// Gerufen bei Empfang von Kommandos vom Client.
423protected void GMCPmod_MG_char_v1_recv(string cmd, mixed args)
424{
425 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
426 GMCP_DEBUG("GMCPmod_MG_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
427}
428
429/*
430// Handler fuer das MG.Room Modul von GMCP
431// Gerufen, wenn Daten zu senden sind.
432protected void GMCPmod_MG_Room_v1_send(mapping data)
433{
434}
435
436// Handler fuer das Room Modul von GMCP
437// Gerufen bei Empfang von Kommandos vom Client.
438protected void GMCPmod_MG_Room_v1_recv(string cmd, mixed args)
439{
440 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
441 GMCP_DEBUG("GMCPmod_MG_Room_v1_recv","Client-Kommando ignoriert: "+cmd,20);
442}
443*/
444
445// Recv Handler fuer das comm.channel Modul von GMCP
446// Gerufen bei Empfang von Kommandos vom Client.
447protected void GMCPmod_comm_channel_v1_recv(string cmd, mixed args)
448{
449 GMCP_DEBUG("GMCPmod_comm_channel_v1_recv",
450 "Client-Kommando ignoriert: "+cmd,20);
451}
452
453// Send Handler fuer das comm.channel Modul von GMCP
454protected void GMCPmod_comm_channel_v1_send(mapping data)
455{
456 // Ganz simpel: einfach raussenden...
457 // Core uebergibt beim Einschalten 0 als data. Dieses modul muss aber beim
458 // Eisnchalten nix machen. Also nur ignorieren.
459 if (mappingp(data))
460 GMCP_send("comm.channel", data);
461}
462
463// Recv Handler fuer das MG.room Modul von GMCP
464// Gerufen bei Empfang von Kommandos vom Client.
465protected void GMCPmod_MG_room_v1_recv(string cmd, mixed args)
466{
467 GMCP_DEBUG("GMCPmod_MG_room_v1_recv",
468 "Client-Kommando ignoriert: "+cmd,20);
469}
470
471// Send Handler fuer das comm.channel Modul von GMCP
472protected void GMCPmod_MG_room_v1_send(mapping data)
473{
474 // Bekommt immer 0 als <data> uebergeben und sucht sich die Daten aus dem
475 // Raum zusammen.
476
477 // Baeh. Warum wird das denn ohne Env gerufen. :-(
478 if (!environment())
479 return;
480
481 // Blind gibt es auch per GMCP nix.
482 if (CannotSee(1))
483 return;
484
485 int restr = environment()->QueryProp(P_MAP_RESTRICTIONS);
486
487 if (restr & MR_NOINFO)
488 return; // gar keine info senden.
489
490 // Anmerkung: int_short() waere cool. Dummerweise uebertraegt das auch
491 // sichtbare Ausgange und Objekte. Insofern: geht nicht.
Zesstrad07aaea2018-12-18 22:30:28 +0100492 // Ist das letzte Zeichen kein Satzzeichen einen Punkt anhaengen, sonst nur
493 // den \n.
Zesstraa2af3bb2019-10-28 21:54:05 +0100494 string sh=process_string(environment()->QueryProp(P_INT_SHORT)||".");
Zesstrad07aaea2018-12-18 22:30:28 +0100495 switch(sh[<1])
496 {
Zesstra5c438ee2019-07-29 19:17:21 +0200497 case '.':
498 case '!':
499 case '?':
Zesstrad07aaea2018-12-18 22:30:28 +0100500 break;
501 default:
502 sh+=".";
503 break;
504 }
MG Mud User88f12472016-06-24 23:31:02 +0200505 data = ([
Zesstrad07aaea2018-12-18 22:30:28 +0100506 P_SHORT: sh,
MG Mud User88f12472016-06-24 23:31:02 +0200507 "domain": environment()->QueryProp(P_DOMAIN) || "unbekannt",
508 ]);
509
510 // sichtbare Ausgaenge ausgeben
511 mixed hide = environment()->QueryProp(P_HIDE_EXITS);
512 if (hide && !pointerp(hide))
513 data["exits"] = ({}); // alle verstecken
514 else
515 {
516 // Query() verwenden, damit sowohl normale als auch Special Exits
517 // kommen... Die Summe von beiden wuerde auch gehen, aber dann hat man
518 // zwei unnoetige Filter in den Querymethoden. Hngl.
519 mapping exits = environment()->Query(P_EXITS, F_VALUE) || ([]);
520 if (pointerp(hide))
521 data["exits"] = m_indices(exits) - hide;
522 else
523 data["exits"] = m_indices(exits);
524 }
525
526 if (restr & MR_NOUID)
527 data["id"] = "";
528 else
529 data["id"] = hash(TLS_HASH_MD5, object_name(environment()));
530
531 GMCP_send("MG.room.info", data);
532}
533
534
535// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
536// Gerufen, wenn Daten zu senden sind.
537protected void GMCPmod_char_v1_send(mapping data)
538{
539 mapping squeue = m_allocate(4,0);
540 struct gmcp_mod_s mod = gmcpdata["char"];
541 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
542 // leider immer alle senden, nicht nur die geaenderten.
543 if (!mappingp(data))
544 {
545 // Alle verfuegbaren Informationen senden...
546 mod->data = m_allocate(4);
547 m_add(mod->data, "char.base", (["name": query_real_name(),
548 "race": QueryProp(P_RACE)]) );
549 m_add(mod->data,"char.vitals", (["hp": QueryProp(P_HP),
550 "mana": QueryProp(P_SP)]) );
551 m_add(mod->data,"char.stats", ([ "str": QueryAttribute(A_STR),
552 "int": QueryAttribute(A_INT),
553 "dex": QueryAttribute(A_DEX),
554 "con": QueryAttribute(A_CON) ]) );
555 m_add(mod->data,"char.status", (["level": QueryProp(P_LEVEL) ]) );
556 m_add(squeue,"char.base");
557 m_add(squeue,"char.vitals");
558 m_add(squeue,"char.stats");
559 m_add(squeue,"char.status");
560 }
561 else
562 {
563 // nur die in data enthaltenen senden.
564 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
565 // muss... *seufz*
566 foreach(string key, mixed val : data)
567 {
568 switch(key)
569 {
570 case P_HP:
571 (mod->data)["char.vitals"] += (["hp": val]);
572 m_add(squeue,"char.vitals");
573 break;
574 case P_SP:
575 (mod->data)["char.vitals"] += (["mana": val]);
576 m_add(squeue,"char.vitals");
577 break;
578 case P_NAME:
579 case P_RACE:
580 (mod->data)["char.base"] += ([key: val]);
581 m_add(squeue,"char.base");
582 break;
583 case A_DEX:
584 case A_STR:
585 case A_CON:
586 case A_INT:
587 (mod->data)["char.stats"] += ([key: val]);
588 m_add(squeue,"char.stats");
589 break;
590 case P_LEVEL:
591 (mod->data)["char.status"] += ([key: val]);
592 m_add(squeue,"char.status");
593 break;
594 }
595 }
596 }
597 GMCP_DEBUG("GMCPmod_char_v1_send()",
598 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
599
600 // Jetzt die squeue senden...
601 foreach(string key : squeue)
602 {
603 GMCP_send(key, (mod->data)[key]);
604 }
605}
606
607// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
608// Gerufen bei Empfang von Kommandos vom Client.
609protected void GMCPmod_char_v1_recv(string cmd, mixed data)
610{
611 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
612 GMCP_DEBUG("GMCPmod_char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
613}
614
615
616// Handler fuer das "Char" Modul von GMCP (Modul von IRE)
617// Gerufen, wenn Daten zu senden sind.
618protected void GMCPmod_Char_v1_send(mapping data)
619{
620 mapping squeue = m_allocate(4,0);
621 struct gmcp_mod_s mod = gmcpdata["Char"];
622 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
623 // leider immer alle senden, nicht nur die geaenderten.
624 if (!mappingp(data))
625 {
626 // Alle verfuegbaren Informationen senden...
627 mod->data = m_allocate(4);
628 m_add(mod->data,"Char.Vitals", (["hp": QueryProp(P_HP),
629 "mp": QueryProp(P_SP),
630 "maxhp": QueryProp(P_MAX_HP),
631 "maxmp": QueryProp(P_MAX_SP) ]) );
632 m_add(mod->data,"Char.Status", (["level": QueryProp(P_LEVEL),
633 "guild": QueryProp(P_GUILD) ]) );
634 m_add(squeue,"Char.Vitals");
635 m_add(squeue,"Char.Status");
636 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
637 // nur beim Start des Moduls gesendet).
638 GMCP_send("Char.StatusVars", ([
639 "level": "Spielerstufe", "guild": "Gilde" ]) );
640 }
641 else
642 {
643 // nur die in data enthaltenen senden.
644 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
645 // muss... *seufz*
646 foreach(string key, mixed val : data)
647 {
648 switch(key)
649 {
650 case P_HP:
651 (mod->data)["Char.Vitals"] += (["hp": val]);
652 m_add(squeue,"Char.Vitals");
653 break;
654 case P_SP:
655 (mod->data)["Char.Vitals"] += (["mp": val]);
656 m_add(squeue,"Char.Vitals");
657 break;
658 case P_MAX_HP:
659 (mod->data)["Char.Vitals"] += (["maxhp": val]);
660 m_add(squeue,"Char.Vitals");
661 break;
662 case P_MAX_SP:
663 (mod->data)["Char.Vitals"] += (["maxmp": val]);
664 m_add(squeue,"Char.Vitals");
665 break;
666 case P_LEVEL:
667 case P_GUILD:
668 (mod->data)["Char.Status"] += ([key: val]);
669 m_add(squeue,"Char.Status");
670 break;
671 }
672 }
673 }
674 GMCP_DEBUG("GMCPmod_Char_v1_send()",
675 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
676
677 // Jetzt die squeue senden...
678 foreach(string key : squeue)
679 {
680 GMCP_send(key, (mod->data)[key]);
681 }
682}
683
684// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
685// Gerufen bei Empfang von Kommandos vom Client.
686protected void GMCPmod_Char_v1_recv(string cmd, mixed args)
687{
688 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
689 GMCP_DEBUG("GMCPmod_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
690}
691
692
693// Handler, der von telnetneg.c gerufen wird.
694private void _std_re_handler_gmcp(struct telopt_s opt, int action,
695 int *optargs)
696{
697 switch(action)
698 {
699 case LOCALON:
700 // super!
701 GMCP_DEBUG("recv:", "LOCALON",10);
702 gmcpdata = ([]);
703 opt->data = gmcpdata; // daten auch dort ablegen.
704 // Coremodule in der Version 1 registrieren (es gibt nur eine).
705 GMCP_register_module("Core 1");
706#ifdef __GMCP_DEBUG__
707 GMCPmod_Core_v1_recv("Core.Debug",30);
708#endif
709 break;
710 case LOCALOFF:
711 // alles abschalten und Daten loeschen
712 GMCP_DEBUG("recv:", "LOCALOFF",10);
713 opt->data = 0;
714 gmcpdata = 0;
715 break;
716 case REMOTEON:
717 case REMOTEOFF:
718 // Huch. Auf Clientseite ist GMCP eigentlich nie an. Ignorieren...
719 GMCP_DEBUG("recv:", "Huh? REMOTE state changed?",50);
720 break;
721 case SB:
722 // Der eigentlich interessante Fall... GMCP-Kommandos
723 if (!mappingp(gmcpdata)) return; // GMCP wohl nicht eingeschaltet...
724 string cmd;
725 mixed args;
726 string payload=to_string(optargs);
727 GMCP_DEBUG("recv", payload,10);
728 if (sscanf(payload,"%s %s", cmd, args) != 2) {
729 // ist vermutlich ein Kommando ohne daten (oder Muell)
730 cmd = payload;
731 //args = 0;
732 }
733 else
734 {
735 string err=catch(args = json_parse(args);nolog);
736 if (err)
737 {
738 printf("\nFehler beim Parsen einer GMCP-Nachricht: %s. "
739 "Nachricht war: '%s'\n"
740 "Befehl: '%s', Argument: '%s'\n\n",err,payload,cmd,args||"");
741 return;
742 }
743 }
744 GMCP_DEBUG("recv", sprintf("Command: %s, Data: %O", cmd, args),20);
745
746 string *cmdparts = explode(cmd, ".");
747 struct gmcp_mod_s mod;
748 string modname;
749 // versuch, ein Modul fuer das Kommando zu finden. Anfangen mit der
750 // Annahme, dass bis zum letzten Punkt der Modulname geht und dann
751 // in jedem case einen Punkt kuerzer werdend.
752 switch(sizeof(cmdparts))
753 {
754 case 4:
755 modname = implode(cmdparts[0..2],".");
756 GMCP_DEBUG("trying modname... ", modname, 20 );
757 if (member(gmcpdata, modname)) {
758 mod = gmcpdata[modname];
759 funcall(mod->recvcl, cmd, args);
760 break;
761 }
762 // Fall-through!
763 case 3:
764 modname = implode(cmdparts[0..1],".");
765 GMCP_DEBUG("trying modname... ", modname, 20);
766 if (member(gmcpdata, modname)) {
767 mod = gmcpdata[modname];
768 funcall(mod->recvcl, cmd, args);
769 break;
770 }
771 // Fall-through!
772 case 2:
773 modname = implode(cmdparts[0..0],".");
774 GMCP_DEBUG("trying modname... ", modname, 20);
775 if (member(gmcpdata, modname)) {
776 mod = gmcpdata[modname];
777 funcall(mod->recvcl, cmd, args);
778 break;
779 }
780 // Hier ists jetzt nen Fehler.
781 GMCPERROR(sprintf("Unknown GMCP module for cmd %s",cmd));
782 break;
783 default:
784 // zuviele oder zuwenig . ;-)
785 GMCPERROR(sprintf("Illegal GMCP cmd %s with args %O",
786 cmd, args));
787 break;
788 }
789 // sbdata brauchen wir eigentlich nicht mehr.
790 opt->re_wishes->sbdata = 0;
791 break;
792 } // switch (action)
793}
794
795// wird von base.c nach Konnektierung gerufen.
796// Darf aber erst gerufen werden, wenn das Spielerobjekt fertig initialisiert
797// und eingelesen ist.
798protected void startup_telnet_negs()
799{
800 // evtl. war es ein reconnect, dann steht in gmcp noch alter kram drin. Der
801 // muss weg, koennte ja auch sein, dass der Client (jetzt) kein GMCP
802 // mehr
803 // will.
804 gmcpdata = 0;
805
806 // Hack besonderer Sorte: GMCP soll lokal eingeschaltet sein. Auf
807 // Clientseiten ist es laut Protokoll nicht vorgesehen, daher duerfen
808 // (sollten?) wir kein DO an den Client senden. Wir brauchen aber einen
809 // remote handler, um die Wuensche vom Client zu verarbeiten. Daher erstmal
810 // nur den local handler binden (und gleichzeitig negotiation anstossen) und
811 // dann direkt danach den remote handler auch binden (ohne erneute
812 // negotiation zu starten). Achja und wir nehmen die gleiche Funktion als
813 // Handler fuer remote und lokal.
814 bind_telneg_handler(TELOPT_GMCP, 0, #'_std_re_handler_gmcp, 1);
815 bind_telneg_handler(TELOPT_GMCP, #'_std_re_handler_gmcp,
816 #'_std_re_handler_gmcp, 0);
817}
818