blob: 07bc31801e90ab165ad5079336598e79deeb4f65 [file] [log] [blame]
Zesstra6b5ac892019-11-26 21:28:16 +01001#pragma save_types,strong_types,rtt_checks
zesstra42fd8bf2016-06-27 22:04:14 +02002
3inherit "/std/living/moving";
4
5#include <moving.h>
6#include <defines.h>
7#include <properties.h>
8#include "/p/service/padreic/mnpc/mnpc.h"
9#define NEED_PROTOTYPES
10#include <living/combat.h>
11#undef NEED_PROTOTYPES
12#include <combat.h>
13
14#define ENV environment
15#define PO previous_object()
16
Zesstra77f2caf2021-04-09 19:14:07 +020017struct exit_s {
18 string cmd; // Kommando fuer Benutzung
19 string|closure room; // Pfad oder ausfzufuehrende Closure
20 string msg; // Bewegungsrichtung beim Benutzen
21 int special; // == 1 fuer special exits
22};
23
Bugfix851697f2022-07-07 14:18:02 +020024// Letzter Spielerkontakt. Beim Laden auf Ladezeit faken.
25// 0, wenn der MNPC inaktiv rumsteht
26// -1, wenn der MNPC inaktiv zuhause rumsteht
27nosave int meet_last_player = time();
zesstra42fd8bf2016-06-27 22:04:14 +020028
29static void mnpc_create()
30{
31 if (PO && member(inherit_list(PO), "/std/room.c")!=-1)
32 SetProp(MNPC_HOME, object_name(PO));
zesstra42fd8bf2016-06-27 22:04:14 +020033 SetProp(P_MNPC, 1);
34 SetProp(MNPC_AREA, ({}));
35 SetProp(MNPC_DELAY, MNPC_DFLT_DELAY);
36 SetProp(MNPC_FUNC, 0);
37 SetProp(MNPC_RANDOM, 0);
38 SetProp(MNPC_WALK_TIME, MNPC_DFLT_WALK);
39 SetProp(MNPC_FLAGS, 0);
40 SetProp(P_ENABLE_IN_ATTACK_OUT, 1);
zesstra42fd8bf2016-06-27 22:04:14 +020041}
42
43protected void RegisterWalk()
44{
Zesstra260613d2019-12-09 21:02:58 +010045 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM)) < MAX_MASTER_TIME)
Zesstra0def98f2019-06-28 19:13:44 +020046 {
47 if (!WALK_MASTER->Registration())
48 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY),
49 QueryProp(MNPC_RANDOM));
50 }
zesstra42fd8bf2016-06-27 22:04:14 +020051 else
Zesstra0def98f2019-06-28 19:13:44 +020052 {
Zesstrafa7abe62021-04-09 14:26:26 +020053 if (find_call_out("Walk") == -1)
54 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
Zesstra0def98f2019-06-28 19:13:44 +020055 }
zesstra42fd8bf2016-06-27 22:04:14 +020056}
57
58// Can be used to manually restart the MNPC from a different object even if
Zesstra0def98f2019-06-28 19:13:44 +020059// the MNPC had no player contact.
zesstra42fd8bf2016-06-27 22:04:14 +020060public int RestartWalk()
61{
62 int flags = QueryProp(MNPC_FLAGS);
63 // Falls nicht laufend, wird gar nichts gemacht.
64 if (flags & MNPC_WALK)
65 {
66 //Spielerkontakt simulieren
67 meet_last_player=time();
68 // Falls MNPC noch registriert ist oder noch einen Callout auf Walk hat,
69 // muss nichts weiter gemacht werden.
70 if (WALK_MASTER->Registration()
Zesstrafa7abe62021-04-09 14:26:26 +020071 || find_call_out("Walk") > -1)
zesstra42fd8bf2016-06-27 22:04:14 +020072 return -1;
73 // ansonsten MNPC registrieren, falls geeignet.
Zesstra260613d2019-12-09 21:02:58 +010074 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))<MAX_MASTER_TIME)
zesstra42fd8bf2016-06-27 22:04:14 +020075 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY), QueryProp(MNPC_RANDOM));
76 // und mit kurzer Verzoegerung einmal laufen. (ja, absicht, hier
77 // MNPC_DELAY zu nutzen - denn solange dauert das Walk vom Master
78 // mindestens.)
Zesstrafa7abe62021-04-09 14:26:26 +020079 call_out("Walk",1+random( min(QueryProp(MNPC_DELAY)-1,8) ));
zesstra42fd8bf2016-06-27 22:04:14 +020080 return 1;
81 }
82 return 0;
83}
84
85protected void Stop(int movehome)
86{
87 if (WALK_MASTER->Registration())
88 WALK_MASTER->RemoveWalker();
Zesstrafa7abe62021-04-09 14:26:26 +020089 else if (find_call_out("Walk")!=-1)
90 remove_call_out("Walk");
zesstra42fd8bf2016-06-27 22:04:14 +020091 if (movehome)
92 {
93 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
94 meet_last_player=-1;
95 }
Bugfix851697f2022-07-07 14:18:02 +020096 else
97 {
98 meet_last_player = 0;
99 }
zesstra42fd8bf2016-06-27 22:04:14 +0200100}
101
102static int _set_mnpc_flags(int flags)
103{
104 if (flags & MNPC_WALK)
105 {
106 if (!QueryProp(MNPC_HOME))
107 raise_error("unknown MNPC_HOME\n");
Zesstra0def98f2019-06-28 19:13:44 +0200108 // RegisterWalk prueft, ob der MNPC schon angemeldet ist.
zesstra42fd8bf2016-06-27 22:04:14 +0200109 RegisterWalk();
110 }
111 // else nicht von Bedeutung, da in Walk() das flag getestet wird
112 if (flags & MNPC_FOLLOW_PLAYER)
113 {
114 if (!QueryProp(MNPC_PURSUER))
115 { // wurde dieses Flag neu eingeschaltet?
116 if (environment())
117 { // Verfolgung aufnehmen...
118 object *pursuer = filter(all_inventory(ENV()), #'interactive);
119 filter_objects(pursuer, "AddPursuer", ME);
120 SetProp(MNPC_PURSUER, pursuer);
121 }
122 else
123 SetProp(MNPC_PURSUER, ({}));
124 }
125 }
126 else if (pointerp(QueryProp(MNPC_PURSUER)))
127 { // wird derzeit irgendwer verfolgt?
128 // alle Verfolgungen abbrechen...
129 filter_objects(QueryProp(MNPC_PURSUER)-({ 0 }), "RemovePursuer", ME);
130 SetProp(MNPC_PURSUER, 0); // Speicher freigeben...
131 }
132 else
133 SetProp(MNPC_PURSUER, 0);
134
135 // nur livings koennen command_me nutzen...
136 if (!living(ME))
137 flags |= MNPC_DIRECT_MOVE;
138
139 return Set(MNPC_FLAGS, flags, F_VALUE);
140}
141
142static void mnpc_InsertEnemy(object enemy)
143{
144 if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) &&
145 (member(QueryProp(MNPC_PURSUER), PL)==-1))
146 {
147 PL->AddPursuer(ME);
148 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
149 }
150}
151
152static void mnpc_reset()
153{
154 int flags = QueryProp(MNPC_FLAGS);
Bugfix851697f2022-07-07 14:18:02 +0200155 // meet_last_player == -1 zeigt an, dass der MNPC schon zuhause ist.
156 if (meet_last_player == -1
zesstra42fd8bf2016-06-27 22:04:14 +0200157 || !(flags & MNPC_WALK)
158 || (flags & MNPC_NO_MOVE_HOME))
159 return;
160
161 // Lange keinen Spielerkontakt und kein Spieler im Raum: nach Hause gehen.
162 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
163 && environment() && !sizeof(filter(
164 all_inventory(environment()), #'query_once_interactive)))
165 {
166 // Abschalten und Heimgehen und dort warten.
167 Stop(1);
168 }
169}
170
171static int _query_mnpc_last_meet()
172{ return meet_last_player; }
173
174static void mnpc_init()
175{
176 if (interactive(PL))
177 {
Zesstra0def98f2019-06-28 19:13:44 +0200178 // Wenn noetig, Wandern wieder aufnehmen.
zesstra42fd8bf2016-06-27 22:04:14 +0200179 if (meet_last_player<=0)
180 {
181 RegisterWalk();
182 }
Bugfixca4dfd32016-12-22 18:22:42 +0100183 if ( ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER) &&
184 (member(QueryProp(MNPC_PURSUER), PL)==-1)) ||
185 ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) && IsEnemy(PL)))
zesstra42fd8bf2016-06-27 22:04:14 +0200186 {
187 PL->AddPursuer(ME);
188 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
189 }
190 meet_last_player=time();
191 }
192 else
193 {
194 // Wenn der reinkommende auch ein MNPC_LAST_MEET groesser 0 hat, ist es
195 // ein MNPC, der noch laeuft. Wenn wir nicht laufen, laufen wir los.
196 // In diesem und auch im anderen Fall uebernehmen wir aber mal seinen
197 // letzten Spielerkontakt, denn der ist juenger als unserer.
198 int lm = PL->QueryProp(MNPC_LAST_MEET);
199 if (meet_last_player<=0 && lm>0)
200 {
201 RegisterWalk();
202 meet_last_player=lm;
203 }
204 else if (meet_last_player<lm)
205 meet_last_player=lm;
206 }
207}
208
209static void mnpc_move()
210{
211 if (environment() && (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER))
212 {
213 object *liv = QueryProp(MNPC_PURSUER) & all_inventory(environment());
214 filter_objects(QueryProp(MNPC_PURSUER)-liv-({ 0 }), "RemovePursuer", ME);
215 SetProp(MNPC_PURSUER, liv);
216 }
217 if (QueryProp(MNPC_FUNC))
Zesstrabddbe3d2016-07-06 20:47:43 +0200218 call_other(ME, QueryProp(MNPC_FUNC));
zesstra42fd8bf2016-06-27 22:04:14 +0200219}
220
221static int PreventEnter(string file)
222// darf der Raum betreten werden?
223{
224 string *area;
225
226 if (!sizeof(area=QueryProp(MNPC_AREA)))
227 return 0; // Raum darf betreten werden
228 else
229 {
230 int i;
231 status exactmatch;
232 exactmatch=QueryProp(MNPC_FLAGS) & MNPC_EXACT_AREA_MATCH;
233 if ((i=strstr(file, "#"))!=-1) file=file[0..i-1];
234 for (i=sizeof(area)-1; i>=0; i--)
235 {
236 if (exactmatch)
237 {
238 //exakter Vergleich, kein Substringvergleich gewuenscht
239 if (file==area[i])
240 return 0; //betreten
241 }
242 else
243 {
244 if (strstr(file, area[i])==0)
245 return 0; // Raum betreten
246 }
247 }
248 return 1; // Raum darf nicht betreten werden
249 }
250}
251
252static int mnpc_PreventFollow(object dest)
253{
254 if (dest && PreventEnter(object_name(dest)))
255 return 2;
256 return 0;
257}
258
259// Bewegungssimulation (Bewegungsmeldung) fuer bewegende non-livings
Zesstra77f2caf2021-04-09 19:14:07 +0200260protected int direct_move(object|string dest, int method, string direction)
zesstra42fd8bf2016-06-27 22:04:14 +0200261{
262 int res, para, tmp;
263 string textout, textin, *mout, vc, fn;
Zesstraa0c5dff2021-05-07 20:44:32 +0200264 object oldenv;
zesstra42fd8bf2016-06-27 22:04:14 +0200265
266 if (living(ME))
267 return call_other(ME, "move", dest, method);
268 else
269 {
270 oldenv = environment();
271 para=QueryProp(P_PARA);
272 if ((para>0) && stringp(dest))
273 {
274 fn=dest+"^"+para;
275 if (find_object(fn) || (file_size(fn+".c")>0))
276 dest=fn;
277 else if (file_size(vc=implode(explode(fn,"/")[0..<2],"/")
278 +"/virtual_compiler.c")>0)
279 {
Zesstra1c7da1d2019-10-24 23:01:15 +0200280 // wenn ein VC existiert, prüfen ob dieser ParaObjecte unterstuetzt
zesstra42fd8bf2016-06-27 22:04:14 +0200281 // wenn ja, dann testen ob sich Raum laden laesst...
bugfixaf2be4f2020-03-22 19:13:07 +0100282 if ((!catch(tmp=call_other(vc,"NoParaObjects")) && (!tmp)) &&
zesstra42fd8bf2016-06-27 22:04:14 +0200283 (!catch(call_other( fn, "???" ))))
284 dest=fn;
285 }
286 }
287
bugfixaf2be4f2020-03-22 19:13:07 +0100288 res = call_other(ME, "move", dest, M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200289
Bugfixdf8ed432021-01-23 20:46:24 +0100290 if (oldenv==environment() || living(this_object()))
zesstra42fd8bf2016-06-27 22:04:14 +0200291 return res;
292
293 // als erstes die Meldung fuer das Verlassen des Raumes...
294 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100295 textout = QueryProp(P_MMSGOUT) || QueryProp(P_MSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200296 else
297 {
bugfixaf2be4f2020-03-22 19:13:07 +0100298 mout = explode( QueryProp(P_MSGOUT) || "", "#" );
299 textout = mout[0] || QueryProp(P_MMSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200300 }
301
302 if (stringp(textout))
303 {
304 if ( !sizeof(direction) )
305 direction = 0;
306
Bugfixb4f10a62021-01-23 19:51:03 +0100307 send_room(oldenv,
zesstra42fd8bf2016-06-27 22:04:14 +0200308 Name( WER, 2 ) + " " + textout +
309 (direction ? " " + direction : "") +
Bugfixb4f10a62021-01-23 19:51:03 +0100310 (sizeof(mout) > 1 ? mout[1] : "") + ".",
311 MT_LOOK,
312 MA_MOVE_OUT,
313 0,
314 ({}),
315 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200316 }
317
Zesstra1c7da1d2019-10-24 23:01:15 +0200318 // nun die Meldung für das "Betreten" des Raumes...
zesstra42fd8bf2016-06-27 22:04:14 +0200319
320 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100321 textin = QueryProp(P_MMSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200322 else
bugfixaf2be4f2020-03-22 19:13:07 +0100323 textin = QueryProp(P_MSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200324
325 if (stringp(textin))
326 {
Bugfixb4f10a62021-01-23 19:51:03 +0100327 send_room(environment(this_object()),
328 capitalize(name( WER, 0 )) + " " + textin + ".",
329 MT_LOOK,
330 MA_MOVE_IN,
331 0,
332 ({this_object()}),
333 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200334 }
335 }
336 return res;
337}
338
Zesstra77f2caf2021-04-09 19:14:07 +0200339// Sammelt verfuegbare und prinzipiell benutzbare Ausgaenge
340// Standardverhalten beruecktsichtigt P_EXITS und ob special Exits benutzt
341// werden duerfen.
342// Aber ueberladene Varianten koennten voellig andere Quellen fuer Ausgaenge
343// (z.B. eigene Props von Magiern) beruecksichtigen.
Zesstra2ee342c2021-05-08 12:16:20 +0200344struct exit_s *PresentExits(mapping exits=environment()->Query(P_EXITS,
345 F_VALUE))
Zesstra77f2caf2021-04-09 19:14:07 +0200346{
Zesstra77f2caf2021-04-09 19:14:07 +0200347 // Aufgrund der MNPC_FLAGS bestimmen ob nur normale Ausgaenge genutzt
348 // werden duerfen
349 int flags=QueryProp(MNPC_FLAGS);
350 flags = !(flags & MNPC_DIRECT_MOVE) && !(flags & MNPC_ONLY_EXITS);
351 // flags ist jetzt nur noch ja/nein bzgl. Special Exits
352
353 return m_values(map(exits,
354 function struct exit_s (string cmd, <string|closure>* arr)
355 {
356 if (flags || !closurep(arr[0]))
357 return (<exit_s> cmd:cmd, room:arr[0], msg:arr[1],
358 special:closurep(arr[0]));
359 return 0;
360 }
361 )) - ({0}); // 0 aufgrund nicht benutzbarer SEs
362}
363
364// Prueft die Ausgaenge auf Benutzbarkeit durch diesen NPC - standardmaessig
365// durch Aufruf von PreventEnter() und liefert einen zufaelligen benutzbaren
366// Ausgang.
367// Kann Ueberladen werden, um eine andere Art der Auswahl zu bewerkstelligen.
Zesstra2ee342c2021-05-08 12:16:20 +0200368struct exit_s SelectExit(struct exit_s *exitlist=PresentExits())
Zesstra77f2caf2021-04-09 19:14:07 +0200369{
Zesstra77f2caf2021-04-09 19:14:07 +0200370 // Bei normalen Ausgaengen per PreventEnter() pruefen, ob der Zielraum OK
371 // ist. Special Exits koennen nicht geprueft und sind OK. (In der Liste
372 // sind bei originalem PresentExits() nur special exits, wenn fuer den
373 // MNPC erlaubt.)
374 exitlist = filter(exitlist, function int (struct exit_s ex)
375 {
376 return ex.special || !PreventEnter(ex.room);
377 });
378
379 // Und zufaellig einen aussuchen
380 if (sizeof(exitlist))
381 return exitlist[random(sizeof(exitlist))];
382 return 0;
383}
384
zesstra42fd8bf2016-06-27 22:04:14 +0200385int Walk()
386{
Zesstrafa7abe62021-04-09 14:26:26 +0200387 if (!environment())
388 {
389 // darf eigentlich nicht vorkommen.
390 raise_error("MNPC ohne Environment.\n");
391 }
392
393 int flags=QueryProp(MNPC_FLAGS);
394 if (!(flags & MNPC_WALK))
395 return 0;
396
Zesstra77f2caf2021-04-09 19:14:07 +0200397 // ggf. neuen Callout eintragen, bevor irgendwas anderes gemacht wird.
Zesstrafa7abe62021-04-09 14:26:26 +0200398 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))>=MAX_MASTER_TIME)
399 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
400
Zesstra77f2caf2021-04-09 19:14:07 +0200401 // Im Kampf ggf. temporaer nicht weitergehen.
Zesstrafa7abe62021-04-09 14:26:26 +0200402 if ((flags & MNPC_NO_WALK_IN_FIGHT) && InFight())
403 {
404 meet_last_player=time();
405 return 1;
406 }
407
Zesstra77f2caf2021-04-09 19:14:07 +0200408 // MNPC anhalten, wenn lange kein Spielerkontakt. Vom WALK_MASTER abmelden.
Zesstrafa7abe62021-04-09 14:26:26 +0200409 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
410 && !sizeof(filter(all_inventory(environment()),
411 #'query_once_interactive))
412 )
413 {
414 // anhalten und ggf. auch direkt nach Hause gehen.
415 Stop(flags & MNPC_GO_HOME_WHEN_STOPPED);
416 return 0;
417 }
418
Zesstra77f2caf2021-04-09 19:14:07 +0200419 // Zielausgang ermitteln
420 struct exit_s ex = SelectExit();
zesstra42fd8bf2016-06-27 22:04:14 +0200421
Zesstra77f2caf2021-04-09 19:14:07 +0200422 // Im direct mode wird die Bewegung selbst implementiert -> YNMV!
zesstra42fd8bf2016-06-27 22:04:14 +0200423 if (flags & MNPC_DIRECT_MOVE)
424 {
Zesstra77f2caf2021-04-09 19:14:07 +0200425 // in diesem Fall sind keine SEs benutzbar, aber die werden von
426 // PresentExit() dann auch nicht geliefert.
427 if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200428 {
Zesstraa0c5dff2021-05-07 20:44:32 +0200429 direct_move(ex.room, M_GO, "nach "+capitalize(ex.msg||ex.cmd));
zesstra42fd8bf2016-06-27 22:04:14 +0200430 }
431 else
432 {
Zesstra77f2caf2021-04-09 19:14:07 +0200433 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
434 // dort gehts ja vielleicht weiter.
435 direct_move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK, 0);
436 }
zesstra42fd8bf2016-06-27 22:04:14 +0200437 }
Zesstra77f2caf2021-04-09 19:14:07 +0200438 else if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200439 {
Zesstra77f2caf2021-04-09 19:14:07 +0200440 // Dies ist der Normalfall: Ausgang per Kommando benutzen
441 command(ex.cmd);
zesstra42fd8bf2016-06-27 22:04:14 +0200442 }
443 else
444 {
Zesstra77f2caf2021-04-09 19:14:07 +0200445 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
446 // dort gehts ja vielleicht weiter.
447 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200448 }
zesstra42fd8bf2016-06-27 22:04:14 +0200449
450 return 1;
451}