blob: c128f7f68dbb49939e16cbf77d827aa03d893ec7 [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
Bugfix1da8b7f2024-10-09 19:39:16 +0200102private void check_home()
103{
104 if (!QueryProp(MNPC_HOME))
105 {
106 catch(
107 raise_error("unknown MNPC_HOME\n");
108 publish);
109 // Laufen stoppen, sonst bugt es im reset
110 SetProp(MNPC_FLAGS, 0);
111 }
112}
113
zesstra42fd8bf2016-06-27 22:04:14 +0200114static int _set_mnpc_flags(int flags)
115{
116 if (flags & MNPC_WALK)
117 {
Bugfix1da8b7f2024-10-09 19:39:16 +0200118 // Check verzoegern, damit MNPC_HOME ggf. von aussen gesetzt werden kann
119 call_out(#'check_home, 1);
Zesstra0def98f2019-06-28 19:13:44 +0200120 // RegisterWalk prueft, ob der MNPC schon angemeldet ist.
zesstra42fd8bf2016-06-27 22:04:14 +0200121 RegisterWalk();
122 }
123 // else nicht von Bedeutung, da in Walk() das flag getestet wird
124 if (flags & MNPC_FOLLOW_PLAYER)
125 {
126 if (!QueryProp(MNPC_PURSUER))
127 { // wurde dieses Flag neu eingeschaltet?
128 if (environment())
129 { // Verfolgung aufnehmen...
130 object *pursuer = filter(all_inventory(ENV()), #'interactive);
131 filter_objects(pursuer, "AddPursuer", ME);
132 SetProp(MNPC_PURSUER, pursuer);
133 }
134 else
135 SetProp(MNPC_PURSUER, ({}));
136 }
137 }
138 else if (pointerp(QueryProp(MNPC_PURSUER)))
139 { // wird derzeit irgendwer verfolgt?
140 // alle Verfolgungen abbrechen...
141 filter_objects(QueryProp(MNPC_PURSUER)-({ 0 }), "RemovePursuer", ME);
142 SetProp(MNPC_PURSUER, 0); // Speicher freigeben...
143 }
144 else
145 SetProp(MNPC_PURSUER, 0);
146
147 // nur livings koennen command_me nutzen...
148 if (!living(ME))
149 flags |= MNPC_DIRECT_MOVE;
150
151 return Set(MNPC_FLAGS, flags, F_VALUE);
152}
153
154static void mnpc_InsertEnemy(object enemy)
155{
156 if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) &&
157 (member(QueryProp(MNPC_PURSUER), PL)==-1))
158 {
159 PL->AddPursuer(ME);
160 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
161 }
162}
163
164static void mnpc_reset()
165{
166 int flags = QueryProp(MNPC_FLAGS);
Bugfix851697f2022-07-07 14:18:02 +0200167 // meet_last_player == -1 zeigt an, dass der MNPC schon zuhause ist.
168 if (meet_last_player == -1
zesstra42fd8bf2016-06-27 22:04:14 +0200169 || !(flags & MNPC_WALK)
170 || (flags & MNPC_NO_MOVE_HOME))
171 return;
172
173 // Lange keinen Spielerkontakt und kein Spieler im Raum: nach Hause gehen.
174 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
175 && environment() && !sizeof(filter(
176 all_inventory(environment()), #'query_once_interactive)))
177 {
178 // Abschalten und Heimgehen und dort warten.
179 Stop(1);
180 }
181}
182
183static int _query_mnpc_last_meet()
184{ return meet_last_player; }
185
186static void mnpc_init()
187{
188 if (interactive(PL))
189 {
Zesstra0def98f2019-06-28 19:13:44 +0200190 // Wenn noetig, Wandern wieder aufnehmen.
zesstra42fd8bf2016-06-27 22:04:14 +0200191 if (meet_last_player<=0)
192 {
193 RegisterWalk();
194 }
Bugfixca4dfd32016-12-22 18:22:42 +0100195 if ( ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER) &&
196 (member(QueryProp(MNPC_PURSUER), PL)==-1)) ||
197 ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) && IsEnemy(PL)))
zesstra42fd8bf2016-06-27 22:04:14 +0200198 {
199 PL->AddPursuer(ME);
200 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
201 }
202 meet_last_player=time();
203 }
204 else
205 {
206 // Wenn der reinkommende auch ein MNPC_LAST_MEET groesser 0 hat, ist es
207 // ein MNPC, der noch laeuft. Wenn wir nicht laufen, laufen wir los.
208 // In diesem und auch im anderen Fall uebernehmen wir aber mal seinen
209 // letzten Spielerkontakt, denn der ist juenger als unserer.
210 int lm = PL->QueryProp(MNPC_LAST_MEET);
211 if (meet_last_player<=0 && lm>0)
212 {
213 RegisterWalk();
214 meet_last_player=lm;
215 }
216 else if (meet_last_player<lm)
217 meet_last_player=lm;
218 }
219}
220
221static void mnpc_move()
222{
223 if (environment() && (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER))
224 {
225 object *liv = QueryProp(MNPC_PURSUER) & all_inventory(environment());
226 filter_objects(QueryProp(MNPC_PURSUER)-liv-({ 0 }), "RemovePursuer", ME);
227 SetProp(MNPC_PURSUER, liv);
228 }
229 if (QueryProp(MNPC_FUNC))
Zesstrabddbe3d2016-07-06 20:47:43 +0200230 call_other(ME, QueryProp(MNPC_FUNC));
zesstra42fd8bf2016-06-27 22:04:14 +0200231}
232
233static int PreventEnter(string file)
234// darf der Raum betreten werden?
235{
236 string *area;
237
238 if (!sizeof(area=QueryProp(MNPC_AREA)))
239 return 0; // Raum darf betreten werden
240 else
241 {
242 int i;
243 status exactmatch;
244 exactmatch=QueryProp(MNPC_FLAGS) & MNPC_EXACT_AREA_MATCH;
245 if ((i=strstr(file, "#"))!=-1) file=file[0..i-1];
246 for (i=sizeof(area)-1; i>=0; i--)
247 {
248 if (exactmatch)
249 {
250 //exakter Vergleich, kein Substringvergleich gewuenscht
251 if (file==area[i])
252 return 0; //betreten
253 }
254 else
255 {
256 if (strstr(file, area[i])==0)
257 return 0; // Raum betreten
258 }
259 }
260 return 1; // Raum darf nicht betreten werden
261 }
262}
263
264static int mnpc_PreventFollow(object dest)
265{
266 if (dest && PreventEnter(object_name(dest)))
267 return 2;
268 return 0;
269}
270
271// Bewegungssimulation (Bewegungsmeldung) fuer bewegende non-livings
Zesstra77f2caf2021-04-09 19:14:07 +0200272protected int direct_move(object|string dest, int method, string direction)
zesstra42fd8bf2016-06-27 22:04:14 +0200273{
274 int res, para, tmp;
275 string textout, textin, *mout, vc, fn;
Zesstraa0c5dff2021-05-07 20:44:32 +0200276 object oldenv;
zesstra42fd8bf2016-06-27 22:04:14 +0200277
278 if (living(ME))
279 return call_other(ME, "move", dest, method);
280 else
281 {
282 oldenv = environment();
283 para=QueryProp(P_PARA);
284 if ((para>0) && stringp(dest))
285 {
286 fn=dest+"^"+para;
287 if (find_object(fn) || (file_size(fn+".c")>0))
288 dest=fn;
289 else if (file_size(vc=implode(explode(fn,"/")[0..<2],"/")
290 +"/virtual_compiler.c")>0)
291 {
Zesstra1c7da1d2019-10-24 23:01:15 +0200292 // wenn ein VC existiert, prüfen ob dieser ParaObjecte unterstuetzt
zesstra42fd8bf2016-06-27 22:04:14 +0200293 // wenn ja, dann testen ob sich Raum laden laesst...
bugfixaf2be4f2020-03-22 19:13:07 +0100294 if ((!catch(tmp=call_other(vc,"NoParaObjects")) && (!tmp)) &&
zesstra42fd8bf2016-06-27 22:04:14 +0200295 (!catch(call_other( fn, "???" ))))
296 dest=fn;
297 }
298 }
299
bugfixaf2be4f2020-03-22 19:13:07 +0100300 res = call_other(ME, "move", dest, M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200301
Bugfixdf8ed432021-01-23 20:46:24 +0100302 if (oldenv==environment() || living(this_object()))
zesstra42fd8bf2016-06-27 22:04:14 +0200303 return res;
304
305 // als erstes die Meldung fuer das Verlassen des Raumes...
306 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100307 textout = QueryProp(P_MMSGOUT) || QueryProp(P_MSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200308 else
309 {
bugfixaf2be4f2020-03-22 19:13:07 +0100310 mout = explode( QueryProp(P_MSGOUT) || "", "#" );
311 textout = mout[0] || QueryProp(P_MMSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200312 }
313
314 if (stringp(textout))
315 {
316 if ( !sizeof(direction) )
317 direction = 0;
318
Bugfixb4f10a62021-01-23 19:51:03 +0100319 send_room(oldenv,
zesstra42fd8bf2016-06-27 22:04:14 +0200320 Name( WER, 2 ) + " " + textout +
321 (direction ? " " + direction : "") +
Bugfixb4f10a62021-01-23 19:51:03 +0100322 (sizeof(mout) > 1 ? mout[1] : "") + ".",
323 MT_LOOK,
324 MA_MOVE_OUT,
325 0,
326 ({}),
327 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200328 }
329
Zesstra1c7da1d2019-10-24 23:01:15 +0200330 // nun die Meldung für das "Betreten" des Raumes...
zesstra42fd8bf2016-06-27 22:04:14 +0200331
332 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100333 textin = QueryProp(P_MMSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200334 else
bugfixaf2be4f2020-03-22 19:13:07 +0100335 textin = QueryProp(P_MSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200336
337 if (stringp(textin))
338 {
Bugfixb4f10a62021-01-23 19:51:03 +0100339 send_room(environment(this_object()),
340 capitalize(name( WER, 0 )) + " " + textin + ".",
341 MT_LOOK,
342 MA_MOVE_IN,
343 0,
344 ({this_object()}),
345 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200346 }
347 }
348 return res;
349}
350
Zesstra77f2caf2021-04-09 19:14:07 +0200351// Sammelt verfuegbare und prinzipiell benutzbare Ausgaenge
352// Standardverhalten beruecktsichtigt P_EXITS und ob special Exits benutzt
353// werden duerfen.
354// Aber ueberladene Varianten koennten voellig andere Quellen fuer Ausgaenge
355// (z.B. eigene Props von Magiern) beruecksichtigen.
Zesstra2ee342c2021-05-08 12:16:20 +0200356struct exit_s *PresentExits(mapping exits=environment()->Query(P_EXITS,
357 F_VALUE))
Zesstra77f2caf2021-04-09 19:14:07 +0200358{
Zesstra77f2caf2021-04-09 19:14:07 +0200359 // Aufgrund der MNPC_FLAGS bestimmen ob nur normale Ausgaenge genutzt
360 // werden duerfen
361 int flags=QueryProp(MNPC_FLAGS);
362 flags = !(flags & MNPC_DIRECT_MOVE) && !(flags & MNPC_ONLY_EXITS);
363 // flags ist jetzt nur noch ja/nein bzgl. Special Exits
364
365 return m_values(map(exits,
366 function struct exit_s (string cmd, <string|closure>* arr)
367 {
368 if (flags || !closurep(arr[0]))
369 return (<exit_s> cmd:cmd, room:arr[0], msg:arr[1],
370 special:closurep(arr[0]));
371 return 0;
372 }
373 )) - ({0}); // 0 aufgrund nicht benutzbarer SEs
374}
375
376// Prueft die Ausgaenge auf Benutzbarkeit durch diesen NPC - standardmaessig
377// durch Aufruf von PreventEnter() und liefert einen zufaelligen benutzbaren
378// Ausgang.
379// Kann Ueberladen werden, um eine andere Art der Auswahl zu bewerkstelligen.
Zesstra2ee342c2021-05-08 12:16:20 +0200380struct exit_s SelectExit(struct exit_s *exitlist=PresentExits())
Zesstra77f2caf2021-04-09 19:14:07 +0200381{
Zesstra77f2caf2021-04-09 19:14:07 +0200382 // Bei normalen Ausgaengen per PreventEnter() pruefen, ob der Zielraum OK
383 // ist. Special Exits koennen nicht geprueft und sind OK. (In der Liste
384 // sind bei originalem PresentExits() nur special exits, wenn fuer den
385 // MNPC erlaubt.)
386 exitlist = filter(exitlist, function int (struct exit_s ex)
387 {
388 return ex.special || !PreventEnter(ex.room);
389 });
390
391 // Und zufaellig einen aussuchen
392 if (sizeof(exitlist))
393 return exitlist[random(sizeof(exitlist))];
394 return 0;
395}
396
zesstra42fd8bf2016-06-27 22:04:14 +0200397int Walk()
398{
Zesstrafa7abe62021-04-09 14:26:26 +0200399 if (!environment())
400 {
401 // darf eigentlich nicht vorkommen.
402 raise_error("MNPC ohne Environment.\n");
403 }
404
405 int flags=QueryProp(MNPC_FLAGS);
406 if (!(flags & MNPC_WALK))
407 return 0;
408
Zesstra77f2caf2021-04-09 19:14:07 +0200409 // ggf. neuen Callout eintragen, bevor irgendwas anderes gemacht wird.
Zesstrafa7abe62021-04-09 14:26:26 +0200410 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))>=MAX_MASTER_TIME)
411 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
412
Zesstra77f2caf2021-04-09 19:14:07 +0200413 // Im Kampf ggf. temporaer nicht weitergehen.
Zesstrafa7abe62021-04-09 14:26:26 +0200414 if ((flags & MNPC_NO_WALK_IN_FIGHT) && InFight())
415 {
416 meet_last_player=time();
417 return 1;
418 }
419
Zesstra77f2caf2021-04-09 19:14:07 +0200420 // MNPC anhalten, wenn lange kein Spielerkontakt. Vom WALK_MASTER abmelden.
Zesstrafa7abe62021-04-09 14:26:26 +0200421 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
422 && !sizeof(filter(all_inventory(environment()),
423 #'query_once_interactive))
424 )
425 {
426 // anhalten und ggf. auch direkt nach Hause gehen.
427 Stop(flags & MNPC_GO_HOME_WHEN_STOPPED);
428 return 0;
429 }
430
Zesstra77f2caf2021-04-09 19:14:07 +0200431 // Zielausgang ermitteln
432 struct exit_s ex = SelectExit();
zesstra42fd8bf2016-06-27 22:04:14 +0200433
Zesstra77f2caf2021-04-09 19:14:07 +0200434 // Im direct mode wird die Bewegung selbst implementiert -> YNMV!
zesstra42fd8bf2016-06-27 22:04:14 +0200435 if (flags & MNPC_DIRECT_MOVE)
436 {
Zesstra77f2caf2021-04-09 19:14:07 +0200437 // in diesem Fall sind keine SEs benutzbar, aber die werden von
438 // PresentExit() dann auch nicht geliefert.
439 if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200440 {
Zesstraa0c5dff2021-05-07 20:44:32 +0200441 direct_move(ex.room, M_GO, "nach "+capitalize(ex.msg||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 direct_move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK, 0);
448 }
zesstra42fd8bf2016-06-27 22:04:14 +0200449 }
Zesstra77f2caf2021-04-09 19:14:07 +0200450 else if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200451 {
Zesstra77f2caf2021-04-09 19:14:07 +0200452 // Dies ist der Normalfall: Ausgang per Kommando benutzen
453 command(ex.cmd);
zesstra42fd8bf2016-06-27 22:04:14 +0200454 }
455 else
456 {
Zesstra77f2caf2021-04-09 19:14:07 +0200457 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
458 // dort gehts ja vielleicht weiter.
459 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200460 }
zesstra42fd8bf2016-06-27 22:04:14 +0200461
462 return 1;
463}