blob: ecc1139b9832e80b24fd9d43e22a62dd1682c2a8 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// transport.c -- Basisklasse fuer Schiffe und aehnliche Transporter
4//
5// $Id: transport.c 9400 2015-12-11 21:56:14Z Zesstra $
Zesstra@Morgengrauen2315aa12016-10-30 22:36:26 +01006#pragma strong_types,rtt_checks
MG Mud User88f12472016-06-24 23:31:02 +02007#pragma range_check
8#pragma no_clone
9#pragma pedantic
10
11inherit "/std/thing/moving";
12inherit "/std/room";
13
14#include <properties.h>
15#include <moving.h>
16#include <defines.h>
17#include <language.h>
18#include <transport.h>
19#include <regexp.h>
Zesstra179db0d2016-11-26 13:13:41 +010020#include <hook.h>
21
MG Mud User88f12472016-06-24 23:31:02 +020022
23/* transport.c
24 *
25 * Ueberarbeitete und
26 * erweiterte Version : Tilly@MorgenGrauen, 10.01.02
27 * Basierend auf : transport.c@SilberLand (Woody@SilberLand), 05.12.99
28 * Basierend auf : Hates und Rumatas generisches Transport Objekt
29 * MorgenGrauen 15.02.93
30 */
31
32/*
33 ********************* Variablen *********************
34 */
35
36// TODO: langfristig waer ja private schoen...
37//
38// Datenstruktur von 'route' (bei HP_ROOM)
39// 0 ({string ID, : HP_ROOM
40// 1 string room, : Dateiname Zielraum
41// 2 int stay, : Dauer Haltezeit
42// 3 int next, : Dauer naechste Fahrtzeit
43// 4 string code, : Haltestellenname fuer QueryArrived
44// 5 mixed dest, : Haltestellen-IDs fuer HasRoute (reise nach)
45// 6 mixed deststr }): unbenutzt.
46//
47// Datenstruktur von 'route' (bei HP_MSG, HP_FUN)
48// 0 ({string ID, : HP_MSG
49// 1 string message, : Meldung oder string fun : Funktionsname
50// 2 int next}) : Dauer bis zum naechsten Ereignis
51nosave mixed *route; /* Liste der Haltepunkte. */
52nosave int rpos; /* Momentane Position in obiger Liste. */
53nosave string roomCode; /* Code des aktuellen Raumes (oder 0). */
Zesstra179db0d2016-11-26 13:13:41 +010054nosave int meet_last_player; // Letzter Spielerkontakt
55
56private void unsubscribe_init();
57private int subscribe_init();
MG Mud User88f12472016-06-24 23:31:02 +020058
59/*
60 ********** Management der builtin-properties **********
61 */
62
63string _query_short()
64{
65 if (roomCode) return Query(P_SHORT);
66 return 0;
67}
68
69mixed _query_transparent()
70{
71 if (roomCode) return Query(P_TRANSPARENT);
72 return 0;
73}
74
Zesstra179db0d2016-11-26 13:13:41 +010075static mixed *_set_route(mixed *r) { return route = r; }
76static mixed *_query_route() { return route; }
77static int _query_mnpc_last_meet() { return meet_last_player; }
MG Mud User88f12472016-06-24 23:31:02 +020078
79/*
80 **************** Zugriffsfunktionen ***************
81 */
82
Zesstra179db0d2016-11-26 13:13:41 +010083public void Halt()
MG Mud User88f12472016-06-24 23:31:02 +020084{
Zesstra179db0d2016-11-26 13:13:41 +010085 // stop, but keep rpos counter.
MG Mud User88f12472016-06-24 23:31:02 +020086 while (remove_call_out( "changeHp" )>-1);
87 while (remove_call_out( "disconnect" )>-1);
88}
89
90// Aktualisiert/Setzt die Route im TravelD, wenn erlaubt (d.h. kein
91// P_NO_TRAVELING)
92private void ReportRoute()
93{
94 if(!QueryProp(P_NO_TRAVELING))
95 {
96 mixed tmp = filter(route, function int (mixed arr)
97 {
98 return arr[0] == HP_ROOM;
99 } );
100 string *route = map(tmp, function string (mixed arr)
101 { return arr[1]; }
102 );
103 TRAVELD->AddRoute(object_name(this_object()),route);
104 }
105}
106
Zesstra179db0d2016-11-26 13:13:41 +0100107public varargs void Start(int pos)
MG Mud User88f12472016-06-24 23:31:02 +0200108{
109 Halt();
Zesstra5d2ace02017-06-20 23:21:36 +0200110 // negative pos sind ein Fehler
111 if (pos<0)
112 raise_error(sprintf("Start(): Positionszaehler < 0: %d\n",pos));
113
114 // wenn pos zu gross fuer die Route ist, rpos auf Ende der Route setzen
115 // (i.e. sizeof(route)-1), damit bei der naechsten Bewegung am Anfang der
116 // Route begonnen wird.
117 rpos = min(pos, sizeof(route)-1);
118
MG Mud User88f12472016-06-24 23:31:02 +0200119 call_out("changeHp",0);
120 // Tell TRAVELD our current route
121 ReportRoute();
122}
123
Zesstra179db0d2016-11-26 13:13:41 +0100124// continues the current route at the point we stopped.
125public int Continue()
126{
127 if (find_call_out("changeHp") == -1
128 && find_call_out("disconnect") == -1)
129 {
Zesstra019987a2017-10-24 22:36:08 +0200130 // Nach einer Pause wird die Route am aktuellen Haltepunkt fortgesetzt
131 // (im Regelfall also am Ende der Route). Am Routenende wird auch
132 // geprueft, wann der letzte Spielerkontakt war. Das darf nach einem
133 // Continue() aber nicht passieren, sonst wuerde der Transporter ggf.
134 // sofort wieder anhalten.
135 meet_last_player*=-1; // neg. vorzeichen als Markierung
Zesstra179db0d2016-11-26 13:13:41 +0100136 unsubscribe_init();
137 Start(rpos);
138 return 1;
139 }
140 return 0;
141}
142
143// pauses the transporter temporarily in a way that it continues along its
144// route as soon as a living enters one of the stop points. If that is not
145// possible, we do nothing.
146public int Pause()
147{
148 // ok, stop
149 if (subscribe_init() == 1)
150 {
151 Halt();
152 return 1;
153 }
154 return 0;
155}
156
MG Mud User88f12472016-06-24 23:31:02 +0200157void SetTravelCmds()
158{
159 if (pointerp(QueryProp(P_LEAVECMDS)))
160 AddCmd(QueryProp(P_LEAVECMDS),"GoOutside");
161 if (pointerp(QueryProp(P_ENTERCMDS)))
162 AddCmd(QueryProp(P_ENTERCMDS),"GoInside");
163 if (pointerp(QueryProp(P_TRAVEL_CMDS)))
164 AddCmd(QueryProp(P_TRAVEL_CMDS),"GoInAndOutside");
165 return;
166}
167
168mixed HasRoute(mixed dest)
169{
170 int i,s,z;
171 string str;
172 object ob;
173 mixed harb;
174
175 s = sizeof(route);
176
177 for (i = rpos;i <= rpos+s-1;i++)
178 {
179 if (route[i%s][0] == HP_ROOM)
180 {
181 if (member(route[i%s][5],dest) != -1 &&
182 objectp(ob=load_object(route[i%s][1])) &&
183 pointerp(harb=ob->QueryProp(P_HARBOUR)) &&
184 sizeof(harb))
185 {
186 return ({ route[i%s][1], harb[0] });
187 }
188 }
189 }
190 return 0;
191}
192
193public varargs void AddRoute(string room, int stay, int next,
194 string harbour_desc, string|string* dest_ids, string deststr)
195{
196 // Daten aus dem Zielanleger abfragen.
197 <string|string*>* harbour = room->QueryProp(P_HARBOUR)||({});
198 string* harbour_ids = ({});
199
200 // IDs des Zielanlegers fuer Syntaxpruefung
201 if ( sizeof(harbour)==2 )
202 {
203 if ( pointerp(harbour[1]) )
204 harbour_ids = harbour[1];
205 else
206 harbour_ids = ({harbour[1]});
207 }
208
209 // <dest_ids> in ein Array umwandeln, ist dann ggf. leer
210 if ( !dest_ids )
211 {
212 dest_ids = ({});
213 }
214 if ( stringp(dest_ids) )
215 {
216 dest_ids = ({dest_ids});
217 }
218
219 // explizit angegebene IDs stehen jetzt in <dest_ids>, die IDs des
220 // Zielhafens aus P_HARBOUR werden addiert.
221 dest_ids += harbour_ids;
222
223 // Ist <dest> immer noch leer, versuchen wir, aus <harbour_desc> ein paar
224 // Stichwoerter zu erzeugen, die man als Zielangabe in der Syntax
225 // "reise nach <ziel>" verwenden kann.
226 if ( !sizeof(dest_ids) )
227 {
228 // Grossgeschriebene Begriffe in <harbour_desc> in <dest> eintragen. Dazu:
229 // 1) <code> erstmal so zerschneiden, dass alle ueblichen Satzzeichen
230 // rausfliegen (es gibt Transporter, die sowas in <harbour_desc>
231 // uebergeben).
232 dest_ids = regexplode(harbour_desc, "[(),.;:&\+_ ]",
233 RE_OMIT_DELIM|RE_GLOBAL);
234 // 2a) So filtern, dass nur grossgeschriebene Woerter uebrig bleiben,
235 // von 1) uebriggebliebene Leerstrings gleich mit wegwerfen.
236 // 2b) Ergebnis kleinschreiben, damit die Syntaxpruefung damit arbeiten
237 // kann.
238 dest_ids = map( filter(dest_ids, function int (string key) {
239 return (key!="" && key[0]>='A' && key[0]<='Z');
240 }), #'lower_case);
241 }
242 // Sollte <dest> jetzt immer noch leer sein, wurde an allen drei Stellen
243 // nichts oder nur Muell uebergeben.
244 if ( !sizeof(dest_ids) )
245 {
246 raise_error("Transporterfehlfunktion in AddRoute(): Identifikations"
247 "matrix unzureichend definiert. Transporter unbenutzbar fuer "
248 "Spieler. Bitte mindestens eine Ziel-ID via P_HARBOUR oder als "
249 "Argument to AddRoute().");
250 }
251 route += ({ ({ HP_ROOM, room, stay, next, harbour_desc, dest_ids,
252 deststr }) });
253}
254
255varargs void AddMsg(string msg, int next)
256{
257 route += ({ ({ HP_MSG, msg, next }) });
258}
259
260void AddFun(string fun, int next) { route += ({ ({ HP_FUN, fun, next }) }); }
261
262string QueryArrived() { return roomCode; }
263
264mixed* QueryPosition()
265{
266 return ({ route[rpos][1],route[(rpos+1)<sizeof(route)?(rpos+1):0][1] });
267}
268
269object* QueryPassengers()
270{
271 return filter(all_inventory(),#'query_once_interactive);
272}
273
274varargs string *QueryHarbours(int textflag)
275{
276 string *ret = ({});
277
278 foreach( mixed* entry : route )
279 {
280 if ( entry[0] == HP_ROOM )
281 {
282 if ( textflag )
283 {
284 string *hp_ids = entry[1]->QueryProp(P_HARBOUR)[1];
285 if (pointerp(hp_ids) && sizeof(hp_ids))
286 {
287 string *h = map( explode(hp_ids[0]," "), #'capitalize);
288 ret += ({ implode(h, " ") });
289 }
290 }
291 else
292 {
293 ret += ({ entry[1] });
294 }
295 }
296 }
297 return ret;
298}
299
300// beim zerstoeren sollte auch die route und der Transporter aus dem traveld
301// abgemeldet werden.
302public varargs int remove(int silent)
303{
304 TRAVELD->RemoveTransporter(this_object());
305 return ::remove(silent);
306}
307
308void RemoveRoute()
309{
310 Halt();
311 route = ({ });
312 rpos = 0;
313 TRAVELD->RemoveTransporter(this_object());
314}
315
316varargs int Enter(object who)
317{
318 string *emsg;
319 mixed efail;
320
321 if (!objectp(who)) who = this_player();
322 if (environment(who) == this_object())
323 {
324 tell_object(who,"Da bist Du doch bereits, schon vergessen?\n");
325 return 1;
326 }
327 if (!QueryArrived()) return 0;
328 if (QueryProp(P_MAX_PASSENGERS) &&
329 (sizeof(QueryPassengers()) >= QueryProp(P_MAX_PASSENGERS)))
330 {
331 if (pointerp(efail=QueryProp(P_ENTERFAIL)))
332 {
333 if (sizeof(efail) == 2)
334 tell_room(this_object(),who->Name(WER,2)+" "+process_string(efail[1])+
335 ".\n",({who}));
336 tell_object(who,process_string(efail[0])+".\n");
337 }
338 else if (stringp(efail))
339 tell_object(who,process_string(efail)+".\n");
340 else if (closurep(efail)) funcall(efail);
341 return 1;
342 }
343
344 tell_object(who,"Du betrittst "+name(WEN,1)+".\n");
345 if (pointerp(emsg=QueryProp(P_ENTERMSG)) && sizeof(emsg) == 2)
346 return who->move(this_object(),M_GO,"",process_string(emsg[0]),
347 process_string(emsg[1]));
348 return who->move(this_object(),M_GO,
349 name(WEN,1),"betritt","kommt herein");
350}
351
352varargs int Leave(object who)
353{
354 string *lmsg;
355 mixed lfail;
356
357 if (!objectp(who)) who = this_player();
358 if (environment(who) != this_object())
359 {
360 if (QueryArrived())
361 {
362 tell_object(who,"Dafuer muesstest Du erstmal dort sein.\n");
363 return 1;
364 }
365 return 0;
366 }
367 if (!QueryArrived())
368 {
369 if (lfail=QueryProp(P_LEAVEFAIL))
370 {
371 if (pointerp(lfail) && sizeof(lfail))
372 {
373 if (sizeof(lfail) == 2)
374 tell_room(this_object(),who->Name(WER,2)+" "+process_string(
375 lfail[1])+".\n",({who}));
376 tell_object(who,process_string(lfail[0])+".\n");
377 }
378 else if (stringp(lfail))
379 tell_object(who,process_string(lfail)+".\n");
380 else if (closurep(lfail)) funcall(lfail);
381 return 1;
382 }
383 tell_object(who,"Fehler beim Verlassen des Transporters.\n"
384 "Bitte zustaendigen Magier verstaendigen.\n");
385 return 1;
386 }
387
388 if (who->QueryProp(P_TRAVEL_INFO)) who->SetProp(P_TRAVEL_INFO,0);
389 tell_object(who,"Du verlaesst "+name(WEN,1)+".\n");
390 if (pointerp(lmsg=QueryProp(P_LEAVEMSG)) && sizeof(lmsg) == 2)
391 return who->move(environment(),M_GO,"",process_string(lmsg[0]),
392 process_string(lmsg[1]));
393 return who->move(environment(),M_GO,
394 name(WEN,1),"verlaesst","kommt herein");
395}
396
397/*
398 ****************** Internal Functions ******************
399 */
400
401static int GoInside(string str)
402{
403 _notify_fail("Was moechtest Du denn genau?\n");
404 if (stringp(str) && id(str)) {
405 Enter();
406 return 1;
407 }
408 return 0;
409}
410
411static int GoOutside(string str)
412{
413 _notify_fail("Was moechtest Du denn genau?\n");
414 if (stringp(str) && id(str)) {
415 Leave();
416 return 1;
417 }
418 return 0;
419}
420
421static int GoInAndOutside(string str)
422{
423 string to;
424
425 _notify_fail("Was moechtest Du denn genau?\n");
426 if (!sizeof(str)) return 0;
427 if ((sscanf(str,"auf %s",to) == 1 || sscanf(str,"in %s",to) == 1) && id(to))
428 return Enter(),1;
429 if ((sscanf(str,"von %s",to) == 1 || sscanf(str,"aus %s",to) == 1) && id(to))
430 return Leave(),1;
431 return 0;
432}
433
434protected void create()
435{
436 ::create();
437
438 route = ({});
439
440 SetProp(P_LEAVEFAIL,"Das ist momentan viel zu gefaehrlich");
441 SetProp(P_ENTERFAIL,"Dort ist kein Platz mehr fuer Dich");
442 SetProp(P_TRANSPARENT,1);
443
444 AddId("Transporter");
Zesstra179db0d2016-11-26 13:13:41 +0100445
MG Mud User88f12472016-06-24 23:31:02 +0200446 call_out("SetTravelCmds",1);
447}
448
449static varargs void disconnect(int change, int change_time)
450{
451 object room;
452 mixed *departmsg;
453
454 departmsg = QueryProp(P_DEPARTMSG);
455
456 if ((room = environment()) && pointerp(departmsg))
457 {
458 tell_room(this_object(),process_string(departmsg[0]));
459 tell_room(room,process_string(departmsg[1]));
460 }
461
462 roomCode = 0;
463
464 if (change) call_out("changeHp",change_time);
465}
466
467static varargs void connect(string room, string code)
468{
469 mixed *arrivemsg, *t;
470 object *trav, ob;
471 string *trs, *msgs;
472 int i;
473
474 if (roomCode) disconnect();
475
476 roomCode = code?code:"";
477
478 if (catch(move(room,M_SILENT|M_NOCHECK);publish))
479 {
480 roomCode = 0;
481 return;
482 }
483
484 arrivemsg = QueryProp(P_ARRIVEMSG);
485
486 if (pointerp(arrivemsg))
487 {
488 tell_room(this_object(),process_string(arrivemsg[0]));
489 tell_room(room,process_string(arrivemsg[1]));
490 }
491
492 trav = filter(all_inventory(this_object()),#'living);
493
494 i = sizeof(trav);
495 while(i--)
496 {
497 if (pointerp(t = trav[i]->QueryProp(P_TRAVEL_INFO))&&
498 t[0]==this_object()&&t[2]==room)
499 {
500 if (trav[i]->InFight())
501 tell_object(trav[i],break_string("Du solltest Deinen Kampf "
502 "schnell beenden,denn eigentlich wolltest Du hier "
503 "aussteigen.",78));
504 else
505 Leave(trav[i]);
506 if (environment(trav[i])!=this_object())
507 trav[i]->SetProp(P_TRAVEL_INFO,0);
508 }
509 }
510 trav = filter(all_inventory(find_object(room))-trav,#'living);
511 i=sizeof(trav);
512 while(i--)
513 {
514 if (objectp(trav[i]) && pointerp(t = trav[i]->QueryProp(P_TRAVEL_INFO))&&
515 t[0] == environment(trav[i]) && t[1] == this_object())
516 {
517 if ( trav[i]->InFight() )
518 tell_object(trav[i],
519 break_string("Du solltest Deinen Kampf schnell beenden, denn "
520 "eigentlich wolltest Du mit "+name(WEM,1)+
521 " reisen.",78));
522 else
523 Enter(trav[i]);
524 if (environment(trav[i]) == this_object())
525 {
526 t[0] = this_object();
527 trav[i]->SetProp(P_TRAVEL_INFO,t);
528 }
529 }
530 }
531}
532
533// this object never performs any clean-up, the driver should not call it
534// again.
535int clean_up(int arg) { return 0; }
536
Zesstra179db0d2016-11-26 13:13:41 +0100537public void init()
538{
539 "*"::init();
540 // if we have player contact (even if the player is just in the same
541 // environment), we update the time.
542 if (this_player() && query_once_interactive(this_player()))
543 meet_last_player = time();
544}
545
546// we try to continue our route once some living triggers init.
547private mixed InitHookCallback(object source, int hookid, mixed hookdata)
548{
549 if (hookid == H_HOOK_INIT && previous_object() == source)
550 Continue();
551
552 return ({H_NO_MOD, hookdata});
553}
554
555// subscribes to H_HOOK_INIT in all rooms along the route
556// == 1 for success, < 0 for the number of errors
557private int subscribe_init()
558{
559 // subscribe to the H_HOOK_INIT of all rooms in the route...
560 int no_hook;
561 foreach(mixed* arr : route)
562 {
563 if (arr[0] == HP_ROOM)
564 {
565 if (arr[1]->HRegisterToHook(H_HOOK_INIT, #'InitHookCallback,
566 H_HOOK_LIBPRIO(1), H_LISTENER,
567 0) <= 0)
568 --no_hook; // Count non-success while subscribing
569 }
570 }
571 return no_hook < 0 ? no_hook : 1;
572}
573
574// unsubscribes from all the H_HOOK_INIT.
575private void unsubscribe_init()
576{
577 foreach(mixed* arr : route)
578 {
579 if (arr[0] == HP_ROOM)
580 arr[1]->HUnregisterFromHook(H_HOOK_INIT, #'InitHookCallback);
581 }
582}
583
584private int maybe_pause()
585{
586 // we check for time of last player contact. If too long ago, we pause our
587 // service.
588 if (meet_last_player < time() - 600)
589 {
590 // we don't stop if players currently are in the transporter or in the same
591 // environment (e.g. idling).
592 object *pls = filter(all_inventory(this_object())
593 + all_inventory(environment(this_object())),
594 #'interactive);
595 if (!sizeof(pls))
596 return Pause();
597 }
598 return 0;
599}
600
MG Mud User88f12472016-06-24 23:31:02 +0200601void changeHp()
602{
Zesstrafffd2a82017-10-24 22:03:27 +0200603 // Nicht am Ende der Route? Eins weiter.
604 if (rpos < sizeof(route) - 1)
605 ++rpos;
606 else
MG Mud User88f12472016-06-24 23:31:02 +0200607 {
Zesstra019987a2017-10-24 22:36:08 +0200608 // Routenende
609 // Nach einem expliziten Continue() ist meet_last_player < 0. Dann wird
610 // nicht geprueft, ob wir sofort wieder anhalten. Auch muss dann die Route
611 // nicht uebermittelt werden (hat Start() schon gemacht).
612 if (meet_last_player >= 0)
613 {
614 // TRAVELD die aktuelle Route uebermitteln
615 ReportRoute();
616 // everytime, we pass the end of our route, we check if we should
617 // pause our service.
618 if (maybe_pause())
619 return;
620 }
621 else
622 // Wieder pruefen im naechsten Durchlauf.
623 meet_last_player=abs(meet_last_player);
624
Zesstrafffd2a82017-10-24 22:03:27 +0200625 // wenn keine Pause, wieder zum Anfang der Route bewegen.
626 rpos = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200627 }
Zesstrafffd2a82017-10-24 22:03:27 +0200628
MG Mud User88f12472016-06-24 23:31:02 +0200629 if (route[rpos][0] == HP_MSG)
630 {
631 call_out("changeHp",route[rpos][2]);
632 tell_room(this_object(),route[rpos][1]);
633 }
634 else if (route[rpos][0] == HP_FUN)
635 {
636 call_out("changeHp",route[rpos][2]);
637 call_other(this_object(),route[rpos][1]);
638 }
639 else
640 {
641 call_out("disconnect",route[rpos][2],1,route[rpos][3]);
642 connect(route[rpos][1],route[rpos][4]);
643 }
644}
645