blob: 01c5d8061bea5208c7fd66dba91c02dc16467355 [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{
Zesstra6c3b6812020-05-22 11:54:35 +020070 if (mappingp(gmcpdata) && member(gmcpdata, module))
71 {
72 struct gmcp_mod_s mod = gmcpdata[module];
73 return mod->version;
74 }
75 return 0;
76}
77
MG Mud User88f12472016-06-24 23:31:02 +020078// Wird vom Spielerobjekt gerufen, wenn sich Daten am Charakter veraendert
79// haben, die gesendet werden sollten.
80// Dies ist eigentlich nur ein Wrapper, der die Daten an den Handler eines
81// Moduls weitergibt, welches vom Client aktiviert wurde. Hierzu kommen zur
82// Zeit 2 in Frage: MG.Char (bevorzugt) und Char (minimaler Support).
Zesstra38d062e2020-05-22 11:53:07 +020083protected int GMCP_Char(mapping data) {
MG Mud User88f12472016-06-24 23:31:02 +020084
85 if (!mappingp(gmcpdata)) return 0;
86
87 // Als erstes schauen, ob der Client MG.Char aktiviert hat.
88 struct gmcp_mod_s mod = gmcpdata["MG.char"];
89 if (structp(mod) && closurep(mod->sendcl))
90 {
91 funcall(mod->sendcl, data);
92 return 1;
93 }
94 // Dann noch das Modul char pruefen. Das ist aber ziemlich eingeschraenkt
95 // und es gibt hoffentlich nicht viele Clients, die es benutzen.
96 // (Aardwolf-Modul)
97 mod = gmcpdata["char"];
98 if (structp(mod) && closurep(mod->sendcl))
99 {
100 funcall(mod->sendcl, data);
101 return 1;
102 }
103 // Dann noch das Modul Char pruefen. Das ist aber ziemlich eingeschraenkt
104 // und es gibt hoffentlich nicht viele Clients, die es benutzen.
105 // (IRE-Modul)
106 mod = gmcpdata["Char"];
107 if (structp(mod) && closurep(mod->sendcl))
108 {
109 funcall(mod->sendcl, data);
110 return 1;
111 }
112 return 0;
113}
114
Zesstra38d062e2020-05-22 11:53:07 +0200115protected int GMCP_Channel(string msg, string channel, string sender) {
MG Mud User88f12472016-06-24 23:31:02 +0200116 if (!mappingp(gmcpdata)) return 0;
117 // comm.channel Modul aktiv?
118 struct gmcp_mod_s mod = gmcpdata["comm.channel"];
119 if (structp(mod) && closurep(mod->sendcl))
120 {
121 funcall(mod->sendcl, (["chan":channel, "player": sender,
122 "msg": msg]) );
123 return 1;
124 }
125 return 0;
126}
127
Zesstra38d062e2020-05-22 11:53:07 +0200128protected int GMCP_Room() {
MG Mud User88f12472016-06-24 23:31:02 +0200129 if (!mappingp(gmcpdata)) return 0;
130 // MG.room Modul aktiv?
131 struct gmcp_mod_s mod = gmcpdata["MG.room"];
132 if (structp(mod) && closurep(mod->sendcl))
133 {
134 funcall(mod->sendcl, 0);
135 return 1;
136 }
137 return 0;
138}
139
140// **************** Ab hier folgen eher die Lowlevel-Dinge ***********
141private void GMCP_debug(string pre, string msg, int prio) {
142 struct gmcp_mod_s mod = gmcpdata["Core"];
143 if (mod && (mod->data)["Debug"] >= prio)
144 tell_object(this_object(), sprintf("GMCP %s: %s\n",pre,msg));
145}
146
147private void GMCP_send(string cmd, mixed data)
148{
149 GMCP_DEBUG("GMCP_send",sprintf("%s %O",cmd,data), 30);
Zesstra9ebed822019-11-27 19:50:17 +0100150 send_telnet_neg_str(
151 to_bytes(({SB, TELOPT_GMCP})) +
152 to_bytes(sprintf("%s %s", cmd, json_serialize(data)),
153 "ASCII//TRANSLIT"), 1);
MG Mud User88f12472016-06-24 23:31:02 +0200154}
155
156private void GMCP_unregister_module(string mod)
157{
MG Mud User88f12472016-06-24 23:31:02 +0200158 // Wenn nicht "mod version" Schema, ignorieren
Arathornb3051452021-05-13 21:13:03 +0200159 if (sscanf(mod, "%s %~d", mod) != 2)
MG Mud User88f12472016-06-24 23:31:02 +0200160 return;
161
162 if (mod=="Core") // darf nicht abgeschaltet werden.
163 return;
164
165 m_delete(gmcpdata, mod);
166}
167
168private void GMCP_register_module(string modname)
169{
170 int version;
171 GMCP_DEBUG("register_module(): trying ... ",modname, 20);
172 // Wenn nicht "mod version" Schema, ignorieren
173 if (sscanf(modname, "%s %d", modname, version) != 2)
174 return;
175
176// GMCP_DEBUG("register_module()",modname + " v" + version);
177
178 // Modul (ggf. mit anderer Version) bereits aktiv?
179 struct gmcp_mod_s mod = gmcpdata[modname];
180 if (structp(mod)) {
181 // wenn gleicher Name und Version, wird nix gemacht, bei anderer Version
182 // wird ein neuer Handler eingetragen und die Daten geloescht.
183 // Wenn nicht-existierende Modul/Version-Kombi angefordert wird, ist das
184 // Modul hinterher aus.
185 if (mod->id == modname && mod->version == version)
186 return;
187 else
188 m_delete(gmcpdata,modname);
189 }
190
191 // Das GMCP-Modul ist nur verfuegbar, wenn es zu der Kombination aus mod und
192 // version einen Handler zum Senden gibt...
193 // Der Handler ist: GMCP_<mod>_v<version>_send, aber in <mod> werden alle
194 // "." durch "_" ersetzt.
195 string replacedname = regreplace(modname, "\\.", "_", RE_GLOBAL);
196 closure sendcl = symbol_function(sprintf("GMCPmod_%s_v%d_send",
197 replacedname, version),
198 this_object());
199 if (!sendcl)
200 return;
201 // Diese Closure darf 0 sein. Dann findet keine Behandlung von vom Client
202 // gesendeten Kommandos statt. Was fuer die meisten Module auch in Ordnung
203 // ist, da sie dem Client keine Kommandos anbieten.
204 closure recvcl = symbol_function(sprintf("GMCPmod_%s_v%d_recv",
205 replacedname, version),
206 this_object());
207
208 GMCP_DEBUG("register_module()",modname+" erfolgreich registriert.",10);
209
210 mod = (<gmcp_mod_s> id: modname, version : version,
211 sendcl : sendcl, recvcl: recvcl, data: ([]) );
212 gmcpdata[modname] = mod;
213
214 // Zum schluss noch den Senden-handler mal rufen, damit der mal alle
215 // verfuegbaren daten sendet.
216 funcall(mod->sendcl, 0);
217}
218
Zaphob7dedda22022-08-08 00:50:42 +0200219// Recv-Handler fuer das Core Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200220// Gerufen bei Empfang von Kommandos vom Client.
221protected void GMCPmod_Core_v1_recv(string cmd, mixed args)
222{
223 struct gmcp_mod_s mod = gmcpdata["Core"];
224 mapping data = mod->data;
225
226/* if (!mod)
227 {
228 GMCPERROR("Command %s for disabled module ignored.");
229 return;
230 }
231 */
232 GMCP_DEBUG("GMCPmod_Core_v1: ", cmd, 20);
233
234 switch (cmd)
235 {
236 case "Core.Hello":
237 if (mappingp(args))
238 data["Hello"] = (["client": args["client"],
239 "version": args["version"] ]);
240 break;
241 case "Core.Supports.Set":
242 if (pointerp(args))
243 {
244 // Alte Module abschalten/loeschen
245 foreach(string m : data["Supports"])
246 GMCP_unregister_module(m);
247 data["Supports"] = args;
248 // Versuchen, die neuen Module zu registrieren
249 foreach(string m : args)
250 GMCP_register_module(m);
251 }
252 else
253 GMCP_DEBUG("GMCPmod_Core_v1: ",
254 "Data for Core.Supports.Set is no array", 5);
255 break;
256 case "Core.Supports.Add":
257 if (!pointerp(data["Supports"]))
258 data["Supports"] = ({});
259 if (pointerp(args))
260 {
261 foreach(string m: args)
262 GMCP_register_module(m);
263 data["Supports"] += args;
264 }
265 break;
266 case "Core.Supports.Remove":
267 if (!pointerp(data["Supports"]))
268 break;
269 if (pointerp(args))
270 {
271 foreach(string m: args)
272 GMCP_unregister_module(m);
273 data["Supports"] -= args;
274 }
275 break;
276 case "Core.Supports.KeepAlive":
277 break; // this is ignored by us.
278 case "Core.Ping":
279 if (intp(args))
280 data["Ping"] = args;
281 // send a ping back
282 GMCP_send("Core.Ping",0);
283 break;
284 case "Core.Debug":
285 if (intp(args) && args >= 0)
286 data["Debug"] = args;
287 break;
288 default:
289 GMCPERROR(sprintf("Unknown GMCP Core cmd %s with args %O",
290 cmd, args));
291 break;
292 }
293}
294
Zaphob7dedda22022-08-08 00:50:42 +0200295// Send-Handler fuer das Core Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200296// Gerufen, wenn Daten zu senden sind.
297protected void GMCPmod_Core_v1_send(mapping data)
298{
Zesstrac7723982021-06-10 23:13:16 +0200299 // Zur Zeit passiert hier weiter nix, spaeter mal Core.Goodbye senden.
300}
301
302// Uebermittelt eine Nachricht Client.GUI an den Client, wenn der Spieler das
303// via "telnet client-gui ..." anfordert.
304protected void GMCP_offer_clientgui(string client)
305{
306 if (!GMCP_Status())
307 {
308 tell_object(ME,
309 "Dein Client hat GMCP nicht aktiviert.\n");
310 return;
311 }
312 if (client == "mudlet")
Zesstra040efb72019-07-30 20:23:31 +0200313 {
314 <int|string>* version = (__DIR__"mudlet_gui")->current_version();
315 if (version)
316 {
Zesstrac7723982021-06-10 23:13:16 +0200317 // Don't know, why mudlet wants version value as string...
Zesstra5c155e22019-09-23 21:15:49 +0200318 GMCP_send("Client.GUI",
Zesstrac7723982021-06-10 23:13:16 +0200319 (["version": to_string(version[1]), "url": version[0]]) );
320 tell_object(ME,
321 "Paketdaten wurden an Mudlet geschickt.\n");
322 }
323 else
324 {
325 tell_object(ME,
326 "Zur Zeit ist fuer Mudlet kein GUI-Paket verfuegbar.\n");
327
Zesstra040efb72019-07-30 20:23:31 +0200328 }
329 }
Zesstrac7723982021-06-10 23:13:16 +0200330 else
331 {
332 tell_object(ME,
333 "Fuer diesen Client existiert kein GUI-Paket.\n");
334 }
MG Mud User88f12472016-06-24 23:31:02 +0200335}
336
337
Zaphob7dedda22022-08-08 00:50:42 +0200338// Send-Handler fuer das MG.Char Modul
339// Gerufen, wenn Daten zu senden sind.
MG Mud User88f12472016-06-24 23:31:02 +0200340protected void GMCPmod_MG_char_v1_send(mapping data)
341{
342 mapping squeue = m_allocate(5,0);
Zesstra323dc912020-08-28 09:28:30 +0200343 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200344 struct gmcp_mod_s mod = gmcpdata["MG.char"];
345 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
346 // leider immer alle senden, nicht nur die geaenderten.
347 if (!mappingp(data))
348 {
349 // Alle verfuegbaren Informationen senden...
350 mod->data = m_allocate(6);
Zesstra323dc912020-08-28 09:28:30 +0200351
MG Mud User88f12472016-06-24 23:31:02 +0200352 m_add(mod->data, "MG.char.base",
353 ([P_NAME: Name(WER),
354 P_GUILD: QueryProp(P_GUILD),
355 P_PRESAY: QueryProp(P_PRESAY), // TODO
356 P_TITLE: QueryProp(P_TITLE),
357 "wizlevel": query_wiz_level(this_object()),
358 P_RACE: QueryProp(P_RACE)]) ); // TODO
MG Mud User88f12472016-06-24 23:31:02 +0200359 m_add(mod->data,"MG.char.maxvitals",
360 ([P_MAX_HP: QueryProp(P_MAX_HP),
361 P_MAX_SP: QueryProp(P_MAX_SP),
362 P_MAX_POISON: QueryProp(P_MAX_POISON) ]) );
Zesstra323dc912020-08-28 09:28:30 +0200363 // aktuelle Werte fuer LP/KP/Gift
364 mapping d = m_allocate(3);
365 d[P_HP] = QueryProp(P_HP);
366 if (can & CAN_REPORT_SP)
367 d[P_SP] = QueryProp(P_SP);
368 if (can & CAN_REPORT_POISON)
369 d[P_POISON] = QueryProp(P_POISON);
370 m_add(mod->data,"MG.char.vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200371 m_add(mod->data,"MG.char.attributes",
372 ([ A_STR: QueryAttribute(A_STR),
373 A_INT: QueryAttribute(A_INT),
374 A_DEX: QueryAttribute(A_DEX),
375 A_CON: QueryAttribute(A_CON) ]) );
376 m_add(mod->data,"MG.char.info",
377 ([P_LEVEL: QueryProp(P_LEVEL),
378 P_GUILD_LEVEL: QueryProp(P_GUILD_LEVEL),
379 P_GUILD_TITLE: QueryProp(P_GUILD_TITLE) ]) );
Zesstra323dc912020-08-28 09:28:30 +0200380 // Und die Vorsicht
381 d = m_allocate(2);
382 if (can & CAN_REPORT_WIMPY)
383 d[P_WIMPY] = QueryProp(P_WIMPY);
384 if (can & CAN_REPORT_WIMPY_DIR)
385 d[P_WIMPY_DIRECTION] = QueryProp(P_WIMPY_DIRECTION);
386 m_add(mod->data,"MG.char.wimpy", d );
387 if (sizeof(d))
388 m_add(squeue,"MG.char.wimpy");
MG Mud User88f12472016-06-24 23:31:02 +0200389 m_add(squeue,"MG.char.base");
390 m_add(squeue,"MG.char.vitals");
391 m_add(squeue,"MG.char.maxvitals");
392 m_add(squeue,"MG.char.attributes");
393 m_add(squeue,"MG.char.info");
MG Mud User88f12472016-06-24 23:31:02 +0200394 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
395 // nur beim Start des Moduls gesendet).
396 GMCP_send("MG.char.infoVars", ([
397 P_LEVEL: "Spielerstufe", P_GUILD_LEVEL: "Gildenstufe",
398 P_GUILD_TITLE: "Gildentitel" ]) );
399 }
400 else
401 {
402 // nur die in data enthaltenen senden.
403 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
404 // muss... *seufz*
405 foreach(string key, mixed val : data)
406 {
407 switch(key)
408 {
409 case P_HP:
410 case P_SP:
411 case P_POISON:
412 (mod->data)["MG.char.vitals"] += ([key: val]);
413 m_add(squeue,"MG.char.vitals");
414 break;
415 case P_MAX_HP:
416 case P_MAX_SP:
417 case P_MAX_POISON:
418 (mod->data)["MG.char.maxvitals"] += ([key: val]);
419 m_add(squeue,"MG.char.maxvitals");
420 break;
421 case P_NAME:
422 (mod->data)["MG.char.base"] += ([key: Name(WER)]);
423 m_add(squeue,"MG.char.base");
424 break;
425 case P_RACE:
426 case P_PRESAY:
427 case P_TITLE:
428 case P_GUILD:
429 (mod->data)["MG.char.base"] += ([key: val]);
430 m_add(squeue,"MG.char.base");
431 break;
432 case A_DEX:
433 case A_STR:
434 case A_CON:
435 case A_INT:
436 (mod->data)["MG.char.attributes"] += ([key: val]);
437 m_add(squeue,"MG.char.attributes");
438 break;
439 case P_LEVEL:
440 case P_GUILD_LEVEL:
441 case P_GUILD_TITLE:
442 (mod->data)["MG.char.info"] += ([key: val]);
443 m_add(squeue,"MG.char.info");
444 break;
445 case P_WIMPY:
446 case P_WIMPY_DIRECTION:
447 (mod->data)["MG.char.wimpy"] += ([key: val]);
448 m_add(squeue,"MG.char.wimpy");
449 break;
450 }
451 }
452 }
453 GMCP_DEBUG("GMCPmod_MG_char_v1_send()",
454 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
455
456 // Jetzt die squeue senden...
457 foreach(string key : squeue)
458 {
459 GMCP_send(key, (mod->data)[key]);
460 }
461}
462
Zaphob7dedda22022-08-08 00:50:42 +0200463// Recv-Handler fuer das MG.Char Modul
MG Mud User88f12472016-06-24 23:31:02 +0200464// Gerufen bei Empfang von Kommandos vom Client.
465protected void GMCPmod_MG_char_v1_recv(string cmd, mixed args)
466{
467 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
468 GMCP_DEBUG("GMCPmod_MG_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
469}
470
471/*
Zaphob7dedda22022-08-08 00:50:42 +0200472// Send-Handler fuer das MG.Room Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200473// Gerufen, wenn Daten zu senden sind.
474protected void GMCPmod_MG_Room_v1_send(mapping data)
475{
476}
477
Zaphob7dedda22022-08-08 00:50:42 +0200478// Recv-Handler fuer das Room Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200479// Gerufen bei Empfang von Kommandos vom Client.
480protected void GMCPmod_MG_Room_v1_recv(string cmd, mixed args)
481{
482 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
483 GMCP_DEBUG("GMCPmod_MG_Room_v1_recv","Client-Kommando ignoriert: "+cmd,20);
484}
485*/
486
Zaphob7dedda22022-08-08 00:50:42 +0200487// Recv-Handler fuer das comm.channel Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200488// Gerufen bei Empfang von Kommandos vom Client.
489protected void GMCPmod_comm_channel_v1_recv(string cmd, mixed args)
490{
491 GMCP_DEBUG("GMCPmod_comm_channel_v1_recv",
492 "Client-Kommando ignoriert: "+cmd,20);
493}
494
Zaphob7dedda22022-08-08 00:50:42 +0200495// Send-Handler fuer das comm.channel Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200496protected void GMCPmod_comm_channel_v1_send(mapping data)
497{
498 // Ganz simpel: einfach raussenden...
499 // Core uebergibt beim Einschalten 0 als data. Dieses modul muss aber beim
Zaphob7dedda22022-08-08 00:50:42 +0200500 // Einschalten nix machen. Also nur ignorieren.
MG Mud User88f12472016-06-24 23:31:02 +0200501 if (mappingp(data))
502 GMCP_send("comm.channel", data);
503}
504
Zaphob7dedda22022-08-08 00:50:42 +0200505// Recv-Handler fuer das MG.room Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200506// Gerufen bei Empfang von Kommandos vom Client.
507protected void GMCPmod_MG_room_v1_recv(string cmd, mixed args)
508{
509 GMCP_DEBUG("GMCPmod_MG_room_v1_recv",
510 "Client-Kommando ignoriert: "+cmd,20);
511}
512
Zaphob7dedda22022-08-08 00:50:42 +0200513// Send-Handler fuer das comm.channel Modul von GMCP
MG Mud User88f12472016-06-24 23:31:02 +0200514protected void GMCPmod_MG_room_v1_send(mapping data)
515{
516 // Bekommt immer 0 als <data> uebergeben und sucht sich die Daten aus dem
517 // Raum zusammen.
518
519 // Baeh. Warum wird das denn ohne Env gerufen. :-(
520 if (!environment())
521 return;
522
Zesstra323dc912020-08-28 09:28:30 +0200523 // Blind gibt es auch per GMCP keine Short oder weitere Infos.
MG Mud User88f12472016-06-24 23:31:02 +0200524 if (CannotSee(1))
525 return;
526
527 int restr = environment()->QueryProp(P_MAP_RESTRICTIONS);
528
529 if (restr & MR_NOINFO)
530 return; // gar keine info senden.
531
532 // Anmerkung: int_short() waere cool. Dummerweise uebertraegt das auch
533 // sichtbare Ausgange und Objekte. Insofern: geht nicht.
Zesstrad07aaea2018-12-18 22:30:28 +0100534 // Ist das letzte Zeichen kein Satzzeichen einen Punkt anhaengen, sonst nur
535 // den \n.
Zesstra5a4ba112020-08-27 22:27:02 +0200536 // Falls das hier nen Magier ist, darf der auf keinen Fall process_string()
537 // rufen, sonst kann man das Magierobjekt dazu bringen, beliebige
538 // oeffentliche Funktionen im Spiel zu rufen... Und zwar direkt aus der
539 // Magiershell (dem Interactive) heraus.
540 string sh = (IS_LEARNER(ME) ? environment()->QueryProp(P_INT_SHORT) :
541 process_string(environment()->QueryProp(P_INT_SHORT)))
542 || ".";
Zesstrad07aaea2018-12-18 22:30:28 +0100543 switch(sh[<1])
544 {
Zesstra5c438ee2019-07-29 19:17:21 +0200545 case '.':
546 case '!':
547 case '?':
Zesstrad07aaea2018-12-18 22:30:28 +0100548 break;
549 default:
550 sh+=".";
551 break;
552 }
MG Mud User88f12472016-06-24 23:31:02 +0200553 data = ([
Zesstrad07aaea2018-12-18 22:30:28 +0100554 P_SHORT: sh,
MG Mud User88f12472016-06-24 23:31:02 +0200555 "domain": environment()->QueryProp(P_DOMAIN) || "unbekannt",
556 ]);
557
558 // sichtbare Ausgaenge ausgeben
559 mixed hide = environment()->QueryProp(P_HIDE_EXITS);
560 if (hide && !pointerp(hide))
561 data["exits"] = ({}); // alle verstecken
562 else
563 {
564 // Query() verwenden, damit sowohl normale als auch Special Exits
565 // kommen... Die Summe von beiden wuerde auch gehen, aber dann hat man
566 // zwei unnoetige Filter in den Querymethoden. Hngl.
567 mapping exits = environment()->Query(P_EXITS, F_VALUE) || ([]);
568 if (pointerp(hide))
569 data["exits"] = m_indices(exits) - hide;
570 else
571 data["exits"] = m_indices(exits);
572 }
573
574 if (restr & MR_NOUID)
575 data["id"] = "";
576 else
577 data["id"] = hash(TLS_HASH_MD5, object_name(environment()));
578
579 GMCP_send("MG.room.info", data);
580}
581
582
Zaphob7dedda22022-08-08 00:50:42 +0200583// Send-Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
MG Mud User88f12472016-06-24 23:31:02 +0200584// Gerufen, wenn Daten zu senden sind.
585protected void GMCPmod_char_v1_send(mapping data)
586{
587 mapping squeue = m_allocate(4,0);
588 struct gmcp_mod_s mod = gmcpdata["char"];
589 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
590 // leider immer alle senden, nicht nur die geaenderten.
591 if (!mappingp(data))
592 {
Zesstra323dc912020-08-28 09:28:30 +0200593 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200594 // Alle verfuegbaren Informationen senden...
595 mod->data = m_allocate(4);
596 m_add(mod->data, "char.base", (["name": query_real_name(),
597 "race": QueryProp(P_RACE)]) );
Zesstra323dc912020-08-28 09:28:30 +0200598 mapping d = m_allocate(2);
599 d["hp"] = QueryProp(P_HP);
600 if (can & CAN_REPORT_SP)
601 d["mana"] = QueryProp(P_SP);
602 m_add(mod->data,"char.vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200603 m_add(mod->data,"char.stats", ([ "str": QueryAttribute(A_STR),
604 "int": QueryAttribute(A_INT),
605 "dex": QueryAttribute(A_DEX),
606 "con": QueryAttribute(A_CON) ]) );
607 m_add(mod->data,"char.status", (["level": QueryProp(P_LEVEL) ]) );
608 m_add(squeue,"char.base");
609 m_add(squeue,"char.vitals");
610 m_add(squeue,"char.stats");
611 m_add(squeue,"char.status");
612 }
613 else
614 {
615 // nur die in data enthaltenen senden.
616 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
617 // muss... *seufz*
618 foreach(string key, mixed val : data)
619 {
620 switch(key)
621 {
622 case P_HP:
623 (mod->data)["char.vitals"] += (["hp": val]);
624 m_add(squeue,"char.vitals");
625 break;
626 case P_SP:
627 (mod->data)["char.vitals"] += (["mana": val]);
628 m_add(squeue,"char.vitals");
629 break;
630 case P_NAME:
631 case P_RACE:
632 (mod->data)["char.base"] += ([key: val]);
633 m_add(squeue,"char.base");
634 break;
635 case A_DEX:
636 case A_STR:
637 case A_CON:
638 case A_INT:
639 (mod->data)["char.stats"] += ([key: val]);
640 m_add(squeue,"char.stats");
641 break;
642 case P_LEVEL:
643 (mod->data)["char.status"] += ([key: val]);
644 m_add(squeue,"char.status");
645 break;
646 }
647 }
648 }
649 GMCP_DEBUG("GMCPmod_char_v1_send()",
650 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
651
652 // Jetzt die squeue senden...
653 foreach(string key : squeue)
654 {
655 GMCP_send(key, (mod->data)[key]);
656 }
657}
658
Zaphob7dedda22022-08-08 00:50:42 +0200659// Recv-Handler fuer das "char" Modul von GMCP (Modul von Aardwolf)
MG Mud User88f12472016-06-24 23:31:02 +0200660// Gerufen bei Empfang von Kommandos vom Client.
661protected void GMCPmod_char_v1_recv(string cmd, mixed data)
662{
663 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
664 GMCP_DEBUG("GMCPmod_char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
665}
666
667
Zaphob7dedda22022-08-08 00:50:42 +0200668// Send-Handler fuer das "Char" Modul von GMCP (Modul von IRE)
MG Mud User88f12472016-06-24 23:31:02 +0200669// Gerufen, wenn Daten zu senden sind.
670protected void GMCPmod_Char_v1_send(mapping data)
671{
672 mapping squeue = m_allocate(4,0);
673 struct gmcp_mod_s mod = gmcpdata["Char"];
674 // mod->data fungiert hier auch als Cache der Daten. Die muss man naemlich
675 // leider immer alle senden, nicht nur die geaenderten.
676 if (!mappingp(data))
677 {
Zesstra323dc912020-08-28 09:28:30 +0200678 int can = QueryProp(P_CAN_FLAGS);
MG Mud User88f12472016-06-24 23:31:02 +0200679 // Alle verfuegbaren Informationen senden...
680 mod->data = m_allocate(4);
Zesstra323dc912020-08-28 09:28:30 +0200681 mapping d = m_allocate(4);
682 d["hp"] = QueryProp(P_HP);
683 d["maxhp"] = QueryProp(P_MAX_HP);
684 d["maxmp"] = QueryProp(P_MAX_SP);
685 if (can & CAN_REPORT_SP)
686 d["mp"] = QueryProp(P_SP);
687 m_add(mod->data,"Char.Vitals", d );
MG Mud User88f12472016-06-24 23:31:02 +0200688 m_add(mod->data,"Char.Status", (["level": QueryProp(P_LEVEL),
689 "guild": QueryProp(P_GUILD) ]) );
690 m_add(squeue,"Char.Vitals");
691 m_add(squeue,"Char.Status");
692 // dies wird direkt gesendet, weil es nicht gespeichert werden muss. (wird
693 // nur beim Start des Moduls gesendet).
694 GMCP_send("Char.StatusVars", ([
695 "level": "Spielerstufe", "guild": "Gilde" ]) );
696 }
697 else
698 {
699 // nur die in data enthaltenen senden.
700 // jetzt erstmal alles aus data so sortieren, wie es gesendet werden
701 // muss... *seufz*
702 foreach(string key, mixed val : data)
703 {
704 switch(key)
705 {
706 case P_HP:
707 (mod->data)["Char.Vitals"] += (["hp": val]);
708 m_add(squeue,"Char.Vitals");
709 break;
710 case P_SP:
711 (mod->data)["Char.Vitals"] += (["mp": val]);
712 m_add(squeue,"Char.Vitals");
713 break;
714 case P_MAX_HP:
715 (mod->data)["Char.Vitals"] += (["maxhp": val]);
716 m_add(squeue,"Char.Vitals");
717 break;
718 case P_MAX_SP:
719 (mod->data)["Char.Vitals"] += (["maxmp": val]);
720 m_add(squeue,"Char.Vitals");
721 break;
722 case P_LEVEL:
723 case P_GUILD:
724 (mod->data)["Char.Status"] += ([key: val]);
725 m_add(squeue,"Char.Status");
726 break;
727 }
728 }
729 }
730 GMCP_DEBUG("GMCPmod_Char_v1_send()",
731 sprintf("Data ready: %O, Sendqueue: %O",mod->data, squeue),50);
732
733 // Jetzt die squeue senden...
734 foreach(string key : squeue)
735 {
736 GMCP_send(key, (mod->data)[key]);
737 }
738}
739
Zaphob7dedda22022-08-08 00:50:42 +0200740// Recv-Handler fuer das "Char" Modul von GMCP (Modul von IRE)
MG Mud User88f12472016-06-24 23:31:02 +0200741// Gerufen bei Empfang von Kommandos vom Client.
742protected void GMCPmod_Char_v1_recv(string cmd, mixed args)
743{
744 // dieses Modul bietet dem Client keine Kommandos an, daher ignorieren.
745 GMCP_DEBUG("GMCPmod_Char_v1_recv","Client-Kommando ignoriert: "+cmd,20);
746}
747
748
749// Handler, der von telnetneg.c gerufen wird.
750private void _std_re_handler_gmcp(struct telopt_s opt, int action,
751 int *optargs)
752{
753 switch(action)
754 {
755 case LOCALON:
756 // super!
757 GMCP_DEBUG("recv:", "LOCALON",10);
758 gmcpdata = ([]);
759 opt->data = gmcpdata; // daten auch dort ablegen.
760 // Coremodule in der Version 1 registrieren (es gibt nur eine).
761 GMCP_register_module("Core 1");
762#ifdef __GMCP_DEBUG__
763 GMCPmod_Core_v1_recv("Core.Debug",30);
764#endif
765 break;
766 case LOCALOFF:
767 // alles abschalten und Daten loeschen
768 GMCP_DEBUG("recv:", "LOCALOFF",10);
769 opt->data = 0;
770 gmcpdata = 0;
771 break;
772 case REMOTEON:
773 case REMOTEOFF:
774 // Huch. Auf Clientseite ist GMCP eigentlich nie an. Ignorieren...
775 GMCP_DEBUG("recv:", "Huh? REMOTE state changed?",50);
776 break;
777 case SB:
778 // Der eigentlich interessante Fall... GMCP-Kommandos
779 if (!mappingp(gmcpdata)) return; // GMCP wohl nicht eingeschaltet...
780 string cmd;
781 mixed args;
782 string payload=to_string(optargs);
783 GMCP_DEBUG("recv", payload,10);
784 if (sscanf(payload,"%s %s", cmd, args) != 2) {
785 // ist vermutlich ein Kommando ohne daten (oder Muell)
786 cmd = payload;
787 //args = 0;
788 }
789 else
790 {
791 string err=catch(args = json_parse(args);nolog);
792 if (err)
793 {
794 printf("\nFehler beim Parsen einer GMCP-Nachricht: %s. "
795 "Nachricht war: '%s'\n"
796 "Befehl: '%s', Argument: '%s'\n\n",err,payload,cmd,args||"");
797 return;
798 }
799 }
800 GMCP_DEBUG("recv", sprintf("Command: %s, Data: %O", cmd, args),20);
801
802 string *cmdparts = explode(cmd, ".");
803 struct gmcp_mod_s mod;
804 string modname;
805 // versuch, ein Modul fuer das Kommando zu finden. Anfangen mit der
806 // Annahme, dass bis zum letzten Punkt der Modulname geht und dann
807 // in jedem case einen Punkt kuerzer werdend.
808 switch(sizeof(cmdparts))
809 {
810 case 4:
811 modname = implode(cmdparts[0..2],".");
812 GMCP_DEBUG("trying modname... ", modname, 20 );
813 if (member(gmcpdata, modname)) {
814 mod = gmcpdata[modname];
815 funcall(mod->recvcl, cmd, args);
816 break;
817 }
818 // Fall-through!
819 case 3:
820 modname = implode(cmdparts[0..1],".");
821 GMCP_DEBUG("trying modname... ", modname, 20);
822 if (member(gmcpdata, modname)) {
823 mod = gmcpdata[modname];
824 funcall(mod->recvcl, cmd, args);
825 break;
826 }
827 // Fall-through!
828 case 2:
829 modname = implode(cmdparts[0..0],".");
830 GMCP_DEBUG("trying modname... ", modname, 20);
831 if (member(gmcpdata, modname)) {
832 mod = gmcpdata[modname];
833 funcall(mod->recvcl, cmd, args);
834 break;
835 }
836 // Hier ists jetzt nen Fehler.
837 GMCPERROR(sprintf("Unknown GMCP module for cmd %s",cmd));
838 break;
839 default:
840 // zuviele oder zuwenig . ;-)
841 GMCPERROR(sprintf("Illegal GMCP cmd %s with args %O",
842 cmd, args));
843 break;
844 }
845 // sbdata brauchen wir eigentlich nicht mehr.
846 opt->re_wishes->sbdata = 0;
847 break;
848 } // switch (action)
849}
850
851// wird von base.c nach Konnektierung gerufen.
852// Darf aber erst gerufen werden, wenn das Spielerobjekt fertig initialisiert
853// und eingelesen ist.
854protected void startup_telnet_negs()
855{
856 // evtl. war es ein reconnect, dann steht in gmcp noch alter kram drin. Der
857 // muss weg, koennte ja auch sein, dass der Client (jetzt) kein GMCP
858 // mehr
859 // will.
860 gmcpdata = 0;
861
862 // Hack besonderer Sorte: GMCP soll lokal eingeschaltet sein. Auf
863 // Clientseiten ist es laut Protokoll nicht vorgesehen, daher duerfen
864 // (sollten?) wir kein DO an den Client senden. Wir brauchen aber einen
865 // remote handler, um die Wuensche vom Client zu verarbeiten. Daher erstmal
866 // nur den local handler binden (und gleichzeitig negotiation anstossen) und
867 // dann direkt danach den remote handler auch binden (ohne erneute
868 // negotiation zu starten). Achja und wir nehmen die gleiche Funktion als
869 // Handler fuer remote und lokal.
870 bind_telneg_handler(TELOPT_GMCP, 0, #'_std_re_handler_gmcp, 1);
871 bind_telneg_handler(TELOPT_GMCP, #'_std_re_handler_gmcp,
872 #'_std_re_handler_gmcp, 0);
873}
874