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