blob: ad9485fedcea57a525341b333a23a6d7203a4799 [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);
Zesstra323dc912020-08-28 09:28:30 +0200327 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200328 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);
Zesstra323dc912020-08-28 09:28:30 +0200335
MG Mud User88f12472016-06-24 23:31:02 +0200336 m_add(mod->data, "MG.char.base",
337 ([P_NAME: Name(WER),
338 P_GUILD: QueryProp(P_GUILD),
339 P_PRESAY: QueryProp(P_PRESAY), // TODO
340 P_TITLE: QueryProp(P_TITLE),
341 "wizlevel": query_wiz_level(this_object()),
342 P_RACE: QueryProp(P_RACE)]) ); // TODO
MG Mud User88f12472016-06-24 23:31:02 +0200343 m_add(mod->data,"MG.char.maxvitals",
344 ([P_MAX_HP: QueryProp(P_MAX_HP),
345 P_MAX_SP: QueryProp(P_MAX_SP),
346 P_MAX_POISON: QueryProp(P_MAX_POISON) ]) );
Zesstra323dc912020-08-28 09:28:30 +0200347 // aktuelle Werte fuer LP/KP/Gift
348 mapping d = m_allocate(3);
349 d[P_HP] = QueryProp(P_HP);
350 if (can & CAN_REPORT_SP)
351 d[P_SP] = QueryProp(P_SP);
352 if (can & CAN_REPORT_POISON)
353 d[P_POISON] = QueryProp(P_POISON);
354 m_add(mod->data,"MG.char.vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200355 m_add(mod->data,"MG.char.attributes",
356 ([ A_STR: QueryAttribute(A_STR),
357 A_INT: QueryAttribute(A_INT),
358 A_DEX: QueryAttribute(A_DEX),
359 A_CON: QueryAttribute(A_CON) ]) );
360 m_add(mod->data,"MG.char.info",
361 ([P_LEVEL: QueryProp(P_LEVEL),
362 P_GUILD_LEVEL: QueryProp(P_GUILD_LEVEL),
363 P_GUILD_TITLE: QueryProp(P_GUILD_TITLE) ]) );
Zesstra323dc912020-08-28 09:28:30 +0200364 // Und die Vorsicht
365 d = m_allocate(2);
366 if (can & CAN_REPORT_WIMPY)
367 d[P_WIMPY] = QueryProp(P_WIMPY);
368 if (can & CAN_REPORT_WIMPY_DIR)
369 d[P_WIMPY_DIRECTION] = QueryProp(P_WIMPY_DIRECTION);
370 m_add(mod->data,"MG.char.wimpy", d );
371 if (sizeof(d))
372 m_add(squeue,"MG.char.wimpy");
MG Mud User88f12472016-06-24 23:31:02 +0200373 m_add(squeue,"MG.char.base");
374 m_add(squeue,"MG.char.vitals");
375 m_add(squeue,"MG.char.maxvitals");
376 m_add(squeue,"MG.char.attributes");
377 m_add(squeue,"MG.char.info");
MG Mud User88f12472016-06-24 23:31:02 +0200378 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
379 // nur beim Start des Moduls gesendet).
380 GMCP_send("MG.char.infoVars", ([
381 P_LEVEL: "Spielerstufe", P_GUILD_LEVEL: "Gildenstufe",
382 P_GUILD_TITLE: "Gildentitel" ]) );
383 }
384 else
385 {
386 // nur die in data enthaltenen senden.
387 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
388 // muss... *seufz*
389 foreach(string key, mixed val : data)
390 {
391 switch(key)
392 {
393 case P_HP:
394 case P_SP:
395 case P_POISON:
396 (mod->data)["MG.char.vitals"] += ([key: val]);
397 m_add(squeue,"MG.char.vitals");
398 break;
399 case P_MAX_HP:
400 case P_MAX_SP:
401 case P_MAX_POISON:
402 (mod->data)["MG.char.maxvitals"] += ([key: val]);
403 m_add(squeue,"MG.char.maxvitals");
404 break;
405 case P_NAME:
406 (mod->data)["MG.char.base"] += ([key: Name(WER)]);
407 m_add(squeue,"MG.char.base");
408 break;
409 case P_RACE:
410 case P_PRESAY:
411 case P_TITLE:
412 case P_GUILD:
413 (mod->data)["MG.char.base"] += ([key: val]);
414 m_add(squeue,"MG.char.base");
415 break;
416 case A_DEX:
417 case A_STR:
418 case A_CON:
419 case A_INT:
420 (mod->data)["MG.char.attributes"] += ([key: val]);
421 m_add(squeue,"MG.char.attributes");
422 break;
423 case P_LEVEL:
424 case P_GUILD_LEVEL:
425 case P_GUILD_TITLE:
426 (mod->data)["MG.char.info"] += ([key: val]);
427 m_add(squeue,"MG.char.info");
428 break;
429 case P_WIMPY:
430 case P_WIMPY_DIRECTION:
431 (mod->data)["MG.char.wimpy"] += ([key: val]);
432 m_add(squeue,"MG.char.wimpy");
433 break;
434 }
435 }
436 }
437 GMCP_DEBUG("GMCPmod_MG_char_v1_send()",
438 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
439
440 // Jetzt die squeue senden...
441 foreach(string key : squeue)
442 {
443 GMCP_send(key, (mod->data)[key]);
444 }
445}
446
447// Handler fuer das MG.Char Modul
448// Gerufen bei Empfang von Kommandos vom Client.
449protected void GMCPmod_MG_char_v1_recv(string cmd, mixed args)
450{
451 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
452 GMCP_DEBUG("GMCPmod_MG_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
453}
454
455/*
456// Handler fuer das MG.Room Modul von GMCP
457// Gerufen, wenn Daten zu senden sind.
458protected void GMCPmod_MG_Room_v1_send(mapping data)
459{
460}
461
462// Handler fuer das 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 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
467 GMCP_DEBUG("GMCPmod_MG_Room_v1_recv","Client-Kommando ignoriert: "+cmd,20);
468}
469*/
470
471// Recv Handler fuer das comm.channel Modul von GMCP
472// Gerufen bei Empfang von Kommandos vom Client.
473protected void GMCPmod_comm_channel_v1_recv(string cmd, mixed args)
474{
475 GMCP_DEBUG("GMCPmod_comm_channel_v1_recv",
476 "Client-Kommando ignoriert: "+cmd,20);
477}
478
479// Send Handler fuer das comm.channel Modul von GMCP
480protected void GMCPmod_comm_channel_v1_send(mapping data)
481{
482 // Ganz simpel: einfach raussenden...
483 // Core uebergibt beim Einschalten 0 als data. Dieses modul muss aber beim
484 // Eisnchalten nix machen. Also nur ignorieren.
485 if (mappingp(data))
486 GMCP_send("comm.channel", data);
487}
488
489// Recv Handler fuer das MG.room Modul von GMCP
490// Gerufen bei Empfang von Kommandos vom Client.
491protected void GMCPmod_MG_room_v1_recv(string cmd, mixed args)
492{
493 GMCP_DEBUG("GMCPmod_MG_room_v1_recv",
494 "Client-Kommando ignoriert: "+cmd,20);
495}
496
497// Send Handler fuer das comm.channel Modul von GMCP
498protected void GMCPmod_MG_room_v1_send(mapping data)
499{
500 // Bekommt immer 0 als <data> uebergeben und sucht sich die Daten aus dem
501 // Raum zusammen.
502
503 // Baeh. Warum wird das denn ohne Env gerufen. :-(
504 if (!environment())
505 return;
506
Zesstra323dc912020-08-28 09:28:30 +0200507 // Blind gibt es auch per GMCP keine Short oder weitere Infos.
MG Mud User88f12472016-06-24 23:31:02 +0200508 if (CannotSee(1))
509 return;
510
511 int restr = environment()->QueryProp(P_MAP_RESTRICTIONS);
512
513 if (restr & MR_NOINFO)
514 return; // gar keine info senden.
515
516 // Anmerkung: int_short() waere cool. Dummerweise uebertraegt das auch
517 // sichtbare Ausgange und Objekte. Insofern: geht nicht.
Zesstrad07aaea2018-12-18 22:30:28 +0100518 // Ist das letzte Zeichen kein Satzzeichen einen Punkt anhaengen, sonst nur
519 // den \n.
Zesstra5a4ba112020-08-27 22:27:02 +0200520 // Falls das hier nen Magier ist, darf der auf keinen Fall process_string()
521 // rufen, sonst kann man das Magierobjekt dazu bringen, beliebige
522 // oeffentliche Funktionen im Spiel zu rufen... Und zwar direkt aus der
523 // Magiershell (dem Interactive) heraus.
524 string sh = (IS_LEARNER(ME) ? environment()->QueryProp(P_INT_SHORT) :
525 process_string(environment()->QueryProp(P_INT_SHORT)))
526 || ".";
Zesstrad07aaea2018-12-18 22:30:28 +0100527 switch(sh[<1])
528 {
Zesstra5c438ee2019-07-29 19:17:21 +0200529 case '.':
530 case '!':
531 case '?':
Zesstrad07aaea2018-12-18 22:30:28 +0100532 break;
533 default:
534 sh+=".";
535 break;
536 }
MG Mud User88f12472016-06-24 23:31:02 +0200537 data = ([
Zesstrad07aaea2018-12-18 22:30:28 +0100538 P_SHORT: sh,
MG Mud User88f12472016-06-24 23:31:02 +0200539 "domain": environment()->QueryProp(P_DOMAIN) || "unbekannt",
540 ]);
541
542 // sichtbare Ausgaenge ausgeben
543 mixed hide = environment()->QueryProp(P_HIDE_EXITS);
544 if (hide && !pointerp(hide))
545 data["exits"] = ({}); // alle verstecken
546 else
547 {
548 // Query() verwenden, damit sowohl normale als auch Special Exits
549 // kommen... Die Summe von beiden wuerde auch gehen, aber dann hat man
550 // zwei unnoetige Filter in den Querymethoden. Hngl.
551 mapping exits = environment()->Query(P_EXITS, F_VALUE) || ([]);
552 if (pointerp(hide))
553 data["exits"] = m_indices(exits) - hide;
554 else
555 data["exits"] = m_indices(exits);
556 }
557
558 if (restr & MR_NOUID)
559 data["id"] = "";
560 else
561 data["id"] = hash(TLS_HASH_MD5, object_name(environment()));
562
563 GMCP_send("MG.room.info", data);
564}
565
566
567// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
568// Gerufen, wenn Daten zu senden sind.
569protected void GMCPmod_char_v1_send(mapping data)
570{
571 mapping squeue = m_allocate(4,0);
572 struct gmcp_mod_s mod = gmcpdata["char"];
573 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
574 // leider immer alle senden, nicht nur die geaenderten.
575 if (!mappingp(data))
576 {
Zesstra323dc912020-08-28 09:28:30 +0200577 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200578 // Alle verfuegbaren Informationen senden...
579 mod->data = m_allocate(4);
580 m_add(mod->data, "char.base", (["name": query_real_name(),
581 "race": QueryProp(P_RACE)]) );
Zesstra323dc912020-08-28 09:28:30 +0200582 mapping d = m_allocate(2);
583 d["hp"] = QueryProp(P_HP);
584 if (can & CAN_REPORT_SP)
585 d["mana"] = QueryProp(P_SP);
586 m_add(mod->data,"char.vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200587 m_add(mod->data,"char.stats", ([ "str": QueryAttribute(A_STR),
588 "int": QueryAttribute(A_INT),
589 "dex": QueryAttribute(A_DEX),
590 "con": QueryAttribute(A_CON) ]) );
591 m_add(mod->data,"char.status", (["level": QueryProp(P_LEVEL) ]) );
592 m_add(squeue,"char.base");
593 m_add(squeue,"char.vitals");
594 m_add(squeue,"char.stats");
595 m_add(squeue,"char.status");
596 }
597 else
598 {
599 // nur die in data enthaltenen senden.
600 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
601 // muss... *seufz*
602 foreach(string key, mixed val : data)
603 {
604 switch(key)
605 {
606 case P_HP:
607 (mod->data)["char.vitals"] += (["hp": val]);
608 m_add(squeue,"char.vitals");
609 break;
610 case P_SP:
611 (mod->data)["char.vitals"] += (["mana": val]);
612 m_add(squeue,"char.vitals");
613 break;
614 case P_NAME:
615 case P_RACE:
616 (mod->data)["char.base"] += ([key: val]);
617 m_add(squeue,"char.base");
618 break;
619 case A_DEX:
620 case A_STR:
621 case A_CON:
622 case A_INT:
623 (mod->data)["char.stats"] += ([key: val]);
624 m_add(squeue,"char.stats");
625 break;
626 case P_LEVEL:
627 (mod->data)["char.status"] += ([key: val]);
628 m_add(squeue,"char.status");
629 break;
630 }
631 }
632 }
633 GMCP_DEBUG("GMCPmod_char_v1_send()",
634 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
635
636 // Jetzt die squeue senden...
637 foreach(string key : squeue)
638 {
639 GMCP_send(key, (mod->data)[key]);
640 }
641}
642
643// Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
644// Gerufen bei Empfang von Kommandos vom Client.
645protected void GMCPmod_char_v1_recv(string cmd, mixed data)
646{
647 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
648 GMCP_DEBUG("GMCPmod_char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
649}
650
651
652// Handler fuer das "Char" Modul von GMCP (Modul von IRE)
653// Gerufen, wenn Daten zu senden sind.
654protected void GMCPmod_Char_v1_send(mapping data)
655{
656 mapping squeue = m_allocate(4,0);
657 struct gmcp_mod_s mod = gmcpdata["Char"];
658 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
659 // leider immer alle senden, nicht nur die geaenderten.
660 if (!mappingp(data))
661 {
Zesstra323dc912020-08-28 09:28:30 +0200662 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200663 // Alle verfuegbaren Informationen senden...
664 mod->data = m_allocate(4);
Zesstra323dc912020-08-28 09:28:30 +0200665 mapping d = m_allocate(4);
666 d["hp"] = QueryProp(P_HP);
667 d["maxhp"] = QueryProp(P_MAX_HP);
668 d["maxmp"] = QueryProp(P_MAX_SP);
669 if (can & CAN_REPORT_SP)
670 d["mp"] = QueryProp(P_SP);
671 m_add(mod->data,"Char.Vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200672 m_add(mod->data,"Char.Status", (["level": QueryProp(P_LEVEL),
673 "guild": QueryProp(P_GUILD) ]) );
674 m_add(squeue,"Char.Vitals");
675 m_add(squeue,"Char.Status");
676 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
677 // nur beim Start des Moduls gesendet).
678 GMCP_send("Char.StatusVars", ([
679 "level": "Spielerstufe", "guild": "Gilde" ]) );
680 }
681 else
682 {
683 // nur die in data enthaltenen senden.
684 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
685 // muss... *seufz*
686 foreach(string key, mixed val : data)
687 {
688 switch(key)
689 {
690 case P_HP:
691 (mod->data)["Char.Vitals"] += (["hp": val]);
692 m_add(squeue,"Char.Vitals");
693 break;
694 case P_SP:
695 (mod->data)["Char.Vitals"] += (["mp": val]);
696 m_add(squeue,"Char.Vitals");
697 break;
698 case P_MAX_HP:
699 (mod->data)["Char.Vitals"] += (["maxhp": val]);
700 m_add(squeue,"Char.Vitals");
701 break;
702 case P_MAX_SP:
703 (mod->data)["Char.Vitals"] += (["maxmp": val]);
704 m_add(squeue,"Char.Vitals");
705 break;
706 case P_LEVEL:
707 case P_GUILD:
708 (mod->data)["Char.Status"] += ([key: val]);
709 m_add(squeue,"Char.Status");
710 break;
711 }
712 }
713 }
714 GMCP_DEBUG("GMCPmod_Char_v1_send()",
715 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
716
717 // Jetzt die squeue senden...
718 foreach(string key : squeue)
719 {
720 GMCP_send(key, (mod->data)[key]);
721 }
722}
723
Zesstra323dc912020-08-28 09:28:30 +0200724// Handler fuer das "Char" Modul von GMCP (Modul von IRE)
MG Mud User88f12472016-06-24 23:31:02 +0200725// Gerufen bei Empfang von Kommandos vom Client.
726protected void GMCPmod_Char_v1_recv(string cmd, mixed args)
727{
728 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
729 GMCP_DEBUG("GMCPmod_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
730}
731
732
733// Handler, der von telnetneg.c gerufen wird.
734private void _std_re_handler_gmcp(struct telopt_s opt, int action,
735 int *optargs)
736{
737 switch(action)
738 {
739 case LOCALON:
740 // super!
741 GMCP_DEBUG("recv:", "LOCALON",10);
742 gmcpdata = ([]);
743 opt->data = gmcpdata; // daten auch dort ablegen.
744 // Coremodule in der Version 1 registrieren (es gibt nur eine).
745 GMCP_register_module("Core 1");
746#ifdef __GMCP_DEBUG__
747 GMCPmod_Core_v1_recv("Core.Debug",30);
748#endif
749 break;
750 case LOCALOFF:
751 // alles abschalten und Daten loeschen
752 GMCP_DEBUG("recv:", "LOCALOFF",10);
753 opt->data = 0;
754 gmcpdata = 0;
755 break;
756 case REMOTEON:
757 case REMOTEOFF:
758 // Huch. Auf Clientseite ist GMCP eigentlich nie an. Ignorieren...
759 GMCP_DEBUG("recv:", "Huh? REMOTE state changed?",50);
760 break;
761 case SB:
762 // Der eigentlich interessante Fall... GMCP-Kommandos
763 if (!mappingp(gmcpdata)) return; // GMCP wohl nicht eingeschaltet...
764 string cmd;
765 mixed args;
766 string payload=to_string(optargs);
767 GMCP_DEBUG("recv", payload,10);
768 if (sscanf(payload,"%s %s", cmd, args) != 2) {
769 // ist vermutlich ein Kommando ohne daten (oder Muell)
770 cmd = payload;
771 //args = 0;
772 }
773 else
774 {
775 string err=catch(args = json_parse(args);nolog);
776 if (err)
777 {
778 printf("\nFehler beim Parsen einer GMCP-Nachricht: %s. "
779 "Nachricht war: '%s'\n"
780 "Befehl: '%s', Argument: '%s'\n\n",err,payload,cmd,args||"");
781 return;
782 }
783 }
784 GMCP_DEBUG("recv", sprintf("Command: %s, Data: %O", cmd, args),20);
785
786 string *cmdparts = explode(cmd, ".");
787 struct gmcp_mod_s mod;
788 string modname;
789 // versuch, ein Modul fuer das Kommando zu finden. Anfangen mit der
790 // Annahme, dass bis zum letzten Punkt der Modulname geht und dann
791 // in jedem case einen Punkt kuerzer werdend.
792 switch(sizeof(cmdparts))
793 {
794 case 4:
795 modname = implode(cmdparts[0..2],".");
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 // Fall-through!
803 case 3:
804 modname = implode(cmdparts[0..1],".");
805 GMCP_DEBUG("trying modname... ", modname, 20);
806 if (member(gmcpdata, modname)) {
807 mod = gmcpdata[modname];
808 funcall(mod->recvcl, cmd, args);
809 break;
810 }
811 // Fall-through!
812 case 2:
813 modname = implode(cmdparts[0..0],".");
814 GMCP_DEBUG("trying modname... ", modname, 20);
815 if (member(gmcpdata, modname)) {
816 mod = gmcpdata[modname];
817 funcall(mod->recvcl, cmd, args);
818 break;
819 }
820 // Hier ists jetzt nen Fehler.
821 GMCPERROR(sprintf("Unknown GMCP module for cmd %s",cmd));
822 break;
823 default:
824 // zuviele oder zuwenig . ;-)
825 GMCPERROR(sprintf("Illegal GMCP cmd %s with args %O",
826 cmd, args));
827 break;
828 }
829 // sbdata brauchen wir eigentlich nicht mehr.
830 opt->re_wishes->sbdata = 0;
831 break;
832 } // switch (action)
833}
834
835// wird von base.c nach Konnektierung gerufen.
836// Darf aber erst gerufen werden, wenn das Spielerobjekt fertig initialisiert
837// und eingelesen ist.
838protected void startup_telnet_negs()
839{
840 // evtl. war es ein reconnect, dann steht in gmcp noch alter kram drin. Der
841 // muss weg, koennte ja auch sein, dass der Client (jetzt) kein GMCP
842 // mehr
843 // will.
844 gmcpdata = 0;
845
846 // Hack besonderer Sorte: GMCP soll lokal eingeschaltet sein. Auf
847 // Clientseiten ist es laut Protokoll nicht vorgesehen, daher duerfen
848 // (sollten?) wir kein DO an den Client senden. Wir brauchen aber einen
849 // remote handler, um die Wuensche vom Client zu verarbeiten. Daher erstmal
850 // nur den local handler binden (und gleichzeitig negotiation anstossen) und
851 // dann direkt danach den remote handler auch binden (ohne erneute
852 // negotiation zu starten). Achja und wir nehmen die gleiche Funktion als
853 // Handler fuer remote und lokal.
854 bind_telneg_handler(TELOPT_GMCP, 0, #'_std_re_handler_gmcp, 1);
855 bind_telneg_handler(TELOPT_GMCP, #'_std_re_handler_gmcp,
856 #'_std_re_handler_gmcp, 0);
857}
858