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