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