blob: e93d2d74db6a8249f3cd873e58bb81fb062ba965 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// transport.c -- Basisklasse fuer Schiffe und aehnliche Transporter
//
// $Id: transport.c 9400 2015-12-11 21:56:14Z Zesstra $
#pragma strong_types,rtt_checks
#pragma range_check
#pragma no_clone
inherit "/std/thing/moving";
inherit "/std/room";
#include <properties.h>
#include <moving.h>
#include <defines.h>
#include <language.h>
#include <transport.h>
#include <regexp.h>
#include <hook.h>
#include <break_string.h>
/* transport.c
*
* Ueberarbeitete und
* erweiterte Version : Tilly@MorgenGrauen, 10.01.02
* Basierend auf : transport.c@SilberLand (Woody@SilberLand), 05.12.99
* Basierend auf : Hates und Rumatas generisches Transport Objekt
* MorgenGrauen 15.02.93
*/
/*
********************* Variablen *********************
*/
// TODO: langfristig waer ja private schoen...
//
// Datenstruktur von 'route' (bei HP_ROOM)
// 0 ({string ID, : HP_ROOM
// 1 string room, : Dateiname Zielraum
// 2 int stay, : Dauer Haltezeit
// 3 int next, : Dauer naechste Fahrtzeit
// 4 string code, : Haltestellenname fuer QueryArrived
// 5 mixed dest, : Haltestellen-IDs fuer HasRoute (reise nach)
// 6 mixed deststr }): unbenutzt.
//
// Datenstruktur von 'route' (bei HP_MSG, HP_FUN)
// 0 ({string ID, : HP_MSG
// 1 string message, : Meldung oder string fun : Funktionsname
// 2 int next}) : Dauer bis zum naechsten Ereignis
nosave mixed *route; /* Liste der Haltepunkte. */
nosave int rpos; /* Momentane Position in obiger Liste. */
nosave string roomCode; /* Code des aktuellen Raumes (oder 0). */
// Letzter Spielerkontakt. Das muss != 0 sein (sonst funktioniert der
// Mechanismus zum Fortsetzen der Route nach einer Pause nicht ordentlich,
// daher wird es auf 1 initialisiert.
nosave int meet_last_player = 1;
// Dauer der Route
nosave int route_time;
private void unsubscribe_init();
private int subscribe_init();
void changeHp();
/*
********** Management der builtin-properties **********
*/
string _query_short()
{
if (roomCode) return Query(P_SHORT, F_VALUE);
return 0;
}
// Waehrend der Reise echt unsichtbar und nicht-interagierbar machen, ausser
// fuer PL innerhalb des Transporters.
// Ansonsten koennten z.B. Spieler, die im Hafen einen angelegten Transporter
// betrachten, ihn dadurch als Referenzobjekt haben und Details am
// unsichtbaren, reisenden Transporter betrachten.
visible int _query_invis()
{
if(!roomCode &&
(!PL || (environment(PL) != ME && query_verb() != "reise")))
{
return 1;
}
return Query(P_INVIS, F_VALUE);
}
mixed _query_transparent()
{
if (roomCode) return Query(P_TRANSPARENT);
return 0;
}
static mixed *_set_route(mixed *r) { return route = r; }
static mixed *_query_route() { return route; }
static int _query_mnpc_last_meet() { return meet_last_player; }
/*
**************** Zugriffsfunktionen ***************
*/
public void Halt()
{
// stop, but keep rpos counter.
while (remove_call_out( "changeHp" )>-1);
while (remove_call_out( "disconnect" )>-1);
}
// Aktualisiert/Setzt die Route im TravelD, wenn erlaubt (d.h. kein
// P_NO_TRAVELING)
private void ReportRoute()
{
if(!QueryProp(P_NO_TRAVELING))
{
mixed tmp = filter(route, function int (mixed arr)
{
return arr[0] == HP_ROOM;
} );
string *route = map(tmp, function string (mixed arr)
{ return arr[1]; }
);
TRAVELD->AddRoute(object_name(this_object()),route);
}
}
public varargs void Start(int pos)
{
Halt();
// negative pos sind ein Fehler
if (pos<0)
raise_error(sprintf("Start(): Positionszaehler < 0: %d\n",pos));
// wenn pos zu gross fuer die Route ist, rpos auf Ende der Route setzen
// (i.e. sizeof(route)-1), damit bei der naechsten Bewegung am Anfang der
// Route begonnen wird.
rpos = min(pos, sizeof(route)-1);
// Tell TRAVELD our current route
ReportRoute();
// changeHp() inkrementiert zu Beginn rpos um 1. D.h. damit wir keinen
// Haltepunkt ueberspringen, muss dieses vorweg kompensiert werden. Da dies
// wiederum den Transporter aber ggf. buggen laesst (rpos<0), darf das
// changeHp() hier nicht asynchron per call_out gerufen werden.
--rpos;
changeHp();
}
// continues the current route at the point we stopped.
public int Continue()
{
if (find_call_out("changeHp") == -1
&& find_call_out("disconnect") == -1)
{
// Nach einer Pause wird die Route am aktuellen Haltepunkt fortgesetzt
// (im Regelfall also am Ende der Route). Am Routenende wird auch
// geprueft, wann der letzte Spielerkontakt war. Das darf nach einem
// Continue() aber nicht passieren, sonst wuerde der Transporter ggf.
// sofort wieder anhalten.
meet_last_player*=-1; // neg. vorzeichen als Markierung
unsubscribe_init();
Start(rpos);
return 1;
}
return 0;
}
// pauses the transporter temporarily in a way that it continues along its
// route as soon as a living enters one of the stop points. If that is not
// possible, we do nothing.
public int Pause()
{
// ok, stop
if (subscribe_init() == 1)
{
Halt();
return 1;
}
return 0;
}
void SetTravelCmds()
{
if (pointerp(QueryProp(P_LEAVECMDS)))
AddCmd(QueryProp(P_LEAVECMDS),"GoOutside");
if (pointerp(QueryProp(P_ENTERCMDS)))
AddCmd(QueryProp(P_ENTERCMDS),"GoInside");
if (pointerp(QueryProp(P_TRAVEL_CMDS)))
AddCmd(QueryProp(P_TRAVEL_CMDS),"GoInAndOutside");
return;
}
mixed HasRoute(mixed dest)
{
int i,s;
object ob;
mixed harb;
s = sizeof(route);
for (i = rpos;i <= rpos+s-1;i++)
{
if (route[i%s][0] == HP_ROOM)
{
if (member(route[i%s][5],dest) != -1 &&
objectp(ob=load_object(route[i%s][1])) &&
pointerp(harb=ob->QueryProp(P_HARBOUR)) &&
sizeof(harb))
{
return ({ route[i%s][1], harb[0] });
}
}
}
return 0;
}
public varargs void AddRoute(string room, int stay, int next,
string harbour_desc, string|string* dest_ids, string deststr)
{
// Daten aus dem Zielanleger abfragen.
<string|string*>* harbour = room->QueryProp(P_HARBOUR)||({});
string* harbour_ids = ({});
// IDs des Zielanlegers fuer Syntaxpruefung
if ( sizeof(harbour)==2 )
{
if ( pointerp(harbour[1]) )
harbour_ids = harbour[1];
else
harbour_ids = ({harbour[1]});
}
// <dest_ids> in ein Array umwandeln, ist dann ggf. leer
if ( !dest_ids )
{
dest_ids = ({});
}
if ( stringp(dest_ids) )
{
dest_ids = ({dest_ids});
}
// explizit angegebene IDs stehen jetzt in <dest_ids>, die IDs des
// Zielhafens aus P_HARBOUR werden addiert.
dest_ids += harbour_ids;
// Ist <dest> immer noch leer, versuchen wir, aus <harbour_desc> ein paar
// Stichwoerter zu erzeugen, die man als Zielangabe in der Syntax
// "reise nach <ziel>" verwenden kann.
if ( !sizeof(dest_ids) )
{
// Grossgeschriebene Begriffe in <harbour_desc> in <dest> eintragen. Dazu:
// 1) <code> erstmal so zerschneiden, dass alle ueblichen Satzzeichen
// rausfliegen (es gibt Transporter, die sowas in <harbour_desc>
// uebergeben).
dest_ids = regexplode(harbour_desc, "[(),.;:&\+_ ]",
RE_OMIT_DELIM|RE_GLOBAL);
// 2a) So filtern, dass nur grossgeschriebene Woerter uebrig bleiben,
// von 1) uebriggebliebene Leerstrings gleich mit wegwerfen.
// 2b) Ergebnis kleinschreiben, damit die Syntaxpruefung damit arbeiten
// kann.
dest_ids = map( filter(dest_ids, function int (string key) {
return (key!="" && key[0]>='A' && key[0]<='Z');
}), #'lower_case);
}
// Sollte <dest> jetzt immer noch leer sein, wurde an allen drei Stellen
// nichts oder nur Muell uebergeben.
if ( !sizeof(dest_ids) )
{
raise_error("Transporterfehlfunktion in AddRoute(): Identifikations"
"matrix unzureichend definiert. Transporter unbenutzbar fuer "
"Spieler. Bitte mindestens eine Ziel-ID via P_HARBOUR oder als "
"Argument to AddRoute().");
}
route += ({ ({ HP_ROOM, room, stay, next, harbour_desc, dest_ids,
deststr }) });
route_time += stay + next;
}
varargs void AddMsg(string msg, int next)
{
route += ({ ({ HP_MSG, msg, next }) });
route_time += next;
}
void AddFun(string fun, int next)
{
route += ({ ({ HP_FUN, fun, next }) });
route_time += next;
}
string QueryArrived() { return roomCode; }
mixed* QueryPosition()
{
return ({ route[rpos][1],route[(rpos+1)<sizeof(route)?(rpos+1):0][1] });
}
object* QueryPassengers()
{
return filter(all_inventory(),#'query_once_interactive);
}
varargs string *QueryHarbours(int textflag)
{
string *ret = ({});
foreach( mixed* entry : route )
{
if ( entry[0] == HP_ROOM )
{
if ( textflag )
{
string *hp_ids = entry[1]->QueryProp(P_HARBOUR)[1];
if (pointerp(hp_ids) && sizeof(hp_ids))
{
string *h = map( explode(hp_ids[0]," "), #'capitalize);
ret += ({ implode(h, " ") });
}
}
else
{
ret += ({ entry[1] });
}
}
}
return ret;
}
// beim zerstoeren sollte auch die route und der Transporter aus dem traveld
// abgemeldet werden.
public varargs int remove(int silent)
{
TRAVELD->RemoveTransporter(this_object());
return ::remove(silent);
}
void RemoveRoute()
{
Halt();
route = ({ });
rpos = 0;
route_time = 0;
TRAVELD->RemoveTransporter(this_object());
}
varargs int Enter(object who)
{
string *emsg;
mixed efail;
if (!objectp(who)) who = this_player();
if (environment(who) == this_object())
{
tell_object(who,"Da bist Du doch bereits, schon vergessen?\n");
return 1;
}
if (!QueryArrived()) return 0;
if (QueryProp(P_MAX_PASSENGERS) &&
(sizeof(QueryPassengers()) >= QueryProp(P_MAX_PASSENGERS)))
{
if (pointerp(efail=QueryProp(P_ENTERFAIL)))
{
if (sizeof(efail) == 2)
tell_room(this_object(),who->Name(WER,2)+" "+process_string(efail[1])+
".\n",({who}));
tell_object(who,process_string(efail[0])+".\n");
}
else if (stringp(efail))
tell_object(who,process_string(efail)+".\n");
else if (closurep(efail)) funcall(efail);
return 1;
}
tell_object(who,"Du betrittst "+name(WEN,1)+".\n");
if (pointerp(emsg=QueryProp(P_ENTERMSG)) && sizeof(emsg) == 2)
return who->move(this_object(),M_GO,"",process_string(emsg[0]),
process_string(emsg[1]));
return who->move(this_object(),M_GO,
name(WEN,1),"betritt","kommt herein");
}
varargs int Leave(object who)
{
string *lmsg;
mixed lfail;
if (!objectp(who)) who = this_player();
if (environment(who) != this_object())
{
if (QueryArrived())
{
tell_object(who,"Dafuer muesstest Du erstmal dort sein.\n");
return 1;
}
return 0;
}
if (!QueryArrived())
{
if (lfail=QueryProp(P_LEAVEFAIL))
{
if (pointerp(lfail) && sizeof(lfail))
{
if (sizeof(lfail) == 2)
tell_room(this_object(),who->Name(WER,2)+" "+process_string(
lfail[1])+".\n",({who}));
tell_object(who,process_string(lfail[0])+".\n");
}
else if (stringp(lfail))
tell_object(who,process_string(lfail)+".\n");
else if (closurep(lfail)) funcall(lfail);
return 1;
}
tell_object(who,"Fehler beim Verlassen des Transporters.\n"
"Bitte zustaendigen Magier verstaendigen.\n");
return 1;
}
if (who->QueryProp(P_TRAVEL_INFO)) who->SetProp(P_TRAVEL_INFO,0);
tell_object(who,"Du verlaesst "+name(WEN,1)+".\n");
if (pointerp(lmsg=QueryProp(P_LEAVEMSG)) && sizeof(lmsg) == 2)
return who->move(environment(),M_GO,"",process_string(lmsg[0]),
process_string(lmsg[1]));
return who->move(environment(),M_GO,
name(WEN,1),"verlaesst","kommt herein");
}
/*
****************** Internal Functions ******************
*/
static int GoInside(string str)
{
_notify_fail("Was moechtest Du denn genau?\n");
if (stringp(str) && id(str)) {
Enter();
return 1;
}
return 0;
}
static int GoOutside(string str)
{
_notify_fail("Was moechtest Du denn genau?\n");
if (stringp(str) && id(str)) {
Leave();
return 1;
}
return 0;
}
static int GoInAndOutside(string str)
{
string to;
_notify_fail("Was moechtest Du denn genau?\n");
if (!sizeof(str)) return 0;
if ((sscanf(str,"auf %s",to) == 1 || sscanf(str,"in %s",to) == 1) && id(to))
return Enter(),1;
if ((sscanf(str,"von %s",to) == 1 || sscanf(str,"aus %s",to) == 1) && id(to))
return Leave(),1;
return 0;
}
protected void create()
{
::create();
route = ({});
SetProp(P_LEAVEFAIL,"Das ist momentan viel zu gefaehrlich");
SetProp(P_ENTERFAIL,"Dort ist kein Platz mehr fuer Dich");
SetProp(P_TRANSPARENT,1);
AddId("Transporter");
call_out("SetTravelCmds",1);
}
static varargs void disconnect(int change, int change_time)
{
string* departmsg = ({string*})QueryProp(P_DEPARTMSG);
string departmsg_me = regreplace(departmsg[0], "@WO", QueryArrived(), 1);
string departmsg_env = regreplace(departmsg[1], "@WO", QueryArrived(), 1);
send_room(this_object(),
break_string(departmsg_me, 78, 0, BS_LEAVE_MY_LFS),
MT_LOOK|MT_LISTEN);
if (objectp(environment()))
{
send_room(environment(),
break_string(departmsg_env, 78, 0, BS_LEAVE_MY_LFS),
MT_LOOK|MT_LISTEN, MA_MOVE_OUT, 0, ({this_object()}));
}
roomCode = 0;
if (change) call_out("changeHp",change_time);
}
static varargs void connect(string room, string code)
{
mixed *t;
object *trav, ob;
string *trs, *msgs;
int i;
if (roomCode) disconnect();
roomCode = code?code:"";
if (catch(move(room,M_SILENT|M_NOCHECK);publish))
{
roomCode = 0;
return;
}
string* arrivemsg = ({string*})QueryProp(P_ARRIVEMSG);
string arrivemsg_me = regreplace(arrivemsg[0], "@WO", QueryArrived(), 1);
string arrivemsg_env = regreplace(arrivemsg[1], "@WO", QueryArrived(), 1);
send_room(this_object(),
break_string(arrivemsg_me, 78, 0, BS_LEAVE_MY_LFS),
MT_LOOK|MT_LISTEN);
send_room(room,
break_string(arrivemsg_env, 78, 0, BS_LEAVE_MY_LFS),
MT_LOOK|MT_LISTEN, MA_MOVE_IN, 0, ({this_object()}));
trav = filter(all_inventory(this_object()),#'living);
i = sizeof(trav);
while(i--)
{
if (pointerp(t = trav[i]->QueryProp(P_TRAVEL_INFO))&&
t[0]==this_object()&&t[2]==room)
{
if (trav[i]->InFight())
tell_object(trav[i],break_string("Du solltest Deinen Kampf "
"schnell beenden,denn eigentlich wolltest Du hier "
"aussteigen.",78));
else
Leave(trav[i]);
if (environment(trav[i])!=this_object())
trav[i]->SetProp(P_TRAVEL_INFO,0);
}
}
trav = filter(all_inventory(find_object(room))-trav,#'living);
i=sizeof(trav);
while(i--)
{
if (objectp(trav[i]) && pointerp(t = trav[i]->QueryProp(P_TRAVEL_INFO))&&
t[0] == environment(trav[i]) && t[1] == this_object())
{
if ( trav[i]->InFight() )
tell_object(trav[i],
break_string("Du solltest Deinen Kampf schnell beenden, denn "
"eigentlich wolltest Du mit "+name(WEM,1)+
" reisen.",78));
else
Enter(trav[i]);
if (environment(trav[i]) == this_object())
{
t[0] = this_object();
trav[i]->SetProp(P_TRAVEL_INFO,t);
}
}
}
}
// this object never performs any clean-up, the driver should not call it
// again.
int clean_up(int arg) { return 0; }
public varargs void init(object origin)
{
"*"::init(origin);
// if we have player contact (even if the player is just in the same
// environment), we update the time.
if (this_player() && query_once_interactive(this_player()))
{
meet_last_player = time();
// Wenn jemand in uns ist, auch falls noetig die Route fortsetzen,
// denn wir haben natuerlich nicht H_HOOK_INIT in uns selbst abonniert.
// Aber auch nur, wenn nicht jemand schon nen zeitverzögertes
// Continue per Hook getriggert hat.
if(environment(PL)==ME && find_call_out(#'Continue)==-1)
Continue();
}
}
// we try to continue our route once some living triggers init.
private mixed InitHookCallback(object source, int hookid, mixed hookdata)
{
if (hookid == H_HOOK_INIT && previous_object() == source &&
find_call_out(#'Continue)==-1)
call_out(#'Continue, 0);
return ({H_NO_MOD, hookdata});
}
// subscribes to H_HOOK_INIT in all rooms along the route
// == 1 for success, < -1 if not (at least one hook failed, all registration
// were already subscribed).
private int subscribe_init()
{
// subscribe to the H_HOOK_INIT of all rooms in the route...
foreach(mixed* arr : route)
{
if (arr[0] == HP_ROOM)
{
int res = arr[1]->HRegisterToHook(H_HOOK_INIT, #'InitHookCallback,
H_HOOK_LIBPRIO(1), H_LISTENER,
0);
// 1 == Erfolg, -3 == bereits registriert
if(res != 1 && res != -3)
{
// von allen H_HOOK_INIT wieder abmelden...
unsubscribe_init();
return -1;
}
}
}
return 1;
}
// unsubscribes from all the H_HOOK_INIT.
private void unsubscribe_init()
{
foreach(mixed* arr : route)
{
if (arr[0] == HP_ROOM)
arr[1]->HUnregisterFromHook(H_HOOK_INIT, #'InitHookCallback);
}
}
private int maybe_pause()
{
// we check for time of last player contact. If we did not meet any players
// for 2 round-trips, we try to pause.
if (meet_last_player < time() - (2*route_time) )
{
// we don't stop if players are currently at one of our stops
foreach(mixed* arr : route)
{
if (arr[0] == HP_ROOM)
{
object room = find_object(arr[1]);
if(room &&
sizeof(filter(all_inventory(room), #'interactive)))
return 0; // no pause
}
}
// and we don't stop if players currently are in the transporter.
if (sizeof(filter(all_inventory(this_object()), #'interactive)))
return 0;
// ok, pause machen
return Pause();
}
return 0;
}
void changeHp()
{
// Nicht am Ende der Route? Eins weiter.
if (rpos < sizeof(route) - 1)
++rpos;
else
{
// Routenende
// Nach einem expliziten Continue() ist meet_last_player < 0. Dann wird
// nicht geprueft, ob wir sofort wieder anhalten. Auch muss dann die Route
// nicht uebermittelt werden (hat Start() schon gemacht).
if (meet_last_player >= 0)
{
// TRAVELD die aktuelle Route uebermitteln
ReportRoute();
// everytime, we pass the end of our route, we check if we should
// pause our service.
if (maybe_pause())
return;
}
else
// Wieder pruefen im naechsten Durchlauf.
meet_last_player=abs(meet_last_player);
// wenn keine Pause, wieder zum Anfang der Route bewegen.
rpos = 0;
}
if (route[rpos][0] == HP_MSG)
{
call_out("changeHp",route[rpos][2]);
tell_room(this_object(),route[rpos][1]);
}
else if (route[rpos][0] == HP_FUN)
{
call_out("changeHp",route[rpos][2]);
call_other(this_object(),route[rpos][1]);
}
else
{
call_out("disconnect",route[rpos][2],1,route[rpos][3]);
connect(route[rpos][1],route[rpos][4]);
}
}