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