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