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