blob: c59e648af973eba688c68684f57c559982d597df [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
zesstra42fd8bf2016-06-27 22:04:14 +020024// Letzter Spielerkontakt. -1, wenn der MNPC inaktiv zuhause rumsteht
Zesstra0def98f2019-06-28 19:13:44 +020025nosave int meet_last_player;
zesstra42fd8bf2016-06-27 22:04:14 +020026
27static void mnpc_create()
28{
29 if (PO && member(inherit_list(PO), "/std/room.c")!=-1)
30 SetProp(MNPC_HOME, object_name(PO));
31 else if (PL && ENV(PL))
32 SetProp(MNPC_HOME, object_name(ENV(PL)));
33 else
34 SetProp(MNPC_HOME, MNPC_DFLT_HOME);
35 SetProp(P_MNPC, 1);
36 SetProp(MNPC_AREA, ({}));
37 SetProp(MNPC_DELAY, MNPC_DFLT_DELAY);
38 SetProp(MNPC_FUNC, 0);
39 SetProp(MNPC_RANDOM, 0);
40 SetProp(MNPC_WALK_TIME, MNPC_DFLT_WALK);
41 SetProp(MNPC_FLAGS, 0);
42 SetProp(P_ENABLE_IN_ATTACK_OUT, 1);
43 meet_last_player=time();
44}
45
46protected void RegisterWalk()
47{
Zesstra260613d2019-12-09 21:02:58 +010048 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM)) < MAX_MASTER_TIME)
Zesstra0def98f2019-06-28 19:13:44 +020049 {
50 if (!WALK_MASTER->Registration())
51 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY),
52 QueryProp(MNPC_RANDOM));
53 }
zesstra42fd8bf2016-06-27 22:04:14 +020054 else
Zesstra0def98f2019-06-28 19:13:44 +020055 {
Zesstrafa7abe62021-04-09 14:26:26 +020056 if (find_call_out("Walk") == -1)
57 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
Zesstra0def98f2019-06-28 19:13:44 +020058 }
zesstra42fd8bf2016-06-27 22:04:14 +020059}
60
61// Can be used to manually restart the MNPC from a different object even if
Zesstra0def98f2019-06-28 19:13:44 +020062// the MNPC had no player contact.
zesstra42fd8bf2016-06-27 22:04:14 +020063public int RestartWalk()
64{
65 int flags = QueryProp(MNPC_FLAGS);
66 // Falls nicht laufend, wird gar nichts gemacht.
67 if (flags & MNPC_WALK)
68 {
69 //Spielerkontakt simulieren
70 meet_last_player=time();
71 // Falls MNPC noch registriert ist oder noch einen Callout auf Walk hat,
72 // muss nichts weiter gemacht werden.
73 if (WALK_MASTER->Registration()
Zesstrafa7abe62021-04-09 14:26:26 +020074 || find_call_out("Walk") > -1)
zesstra42fd8bf2016-06-27 22:04:14 +020075 return -1;
76 // ansonsten MNPC registrieren, falls geeignet.
Zesstra260613d2019-12-09 21:02:58 +010077 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))<MAX_MASTER_TIME)
zesstra42fd8bf2016-06-27 22:04:14 +020078 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY), QueryProp(MNPC_RANDOM));
79 // und mit kurzer Verzoegerung einmal laufen. (ja, absicht, hier
80 // MNPC_DELAY zu nutzen - denn solange dauert das Walk vom Master
81 // mindestens.)
Zesstrafa7abe62021-04-09 14:26:26 +020082 call_out("Walk",1+random( min(QueryProp(MNPC_DELAY)-1,8) ));
zesstra42fd8bf2016-06-27 22:04:14 +020083 return 1;
84 }
85 return 0;
86}
87
88protected void Stop(int movehome)
89{
90 if (WALK_MASTER->Registration())
91 WALK_MASTER->RemoveWalker();
Zesstrafa7abe62021-04-09 14:26:26 +020092 else if (find_call_out("Walk")!=-1)
93 remove_call_out("Walk");
zesstra42fd8bf2016-06-27 22:04:14 +020094 if (movehome)
95 {
96 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
97 meet_last_player=-1;
98 }
99}
100
101static int _set_mnpc_flags(int flags)
102{
103 if (flags & MNPC_WALK)
104 {
105 if (!QueryProp(MNPC_HOME))
106 raise_error("unknown MNPC_HOME\n");
Zesstra0def98f2019-06-28 19:13:44 +0200107 // RegisterWalk prueft, ob der MNPC schon angemeldet ist.
zesstra42fd8bf2016-06-27 22:04:14 +0200108 RegisterWalk();
109 }
110 // else nicht von Bedeutung, da in Walk() das flag getestet wird
111 if (flags & MNPC_FOLLOW_PLAYER)
112 {
113 if (!QueryProp(MNPC_PURSUER))
114 { // wurde dieses Flag neu eingeschaltet?
115 if (environment())
116 { // Verfolgung aufnehmen...
117 object *pursuer = filter(all_inventory(ENV()), #'interactive);
118 filter_objects(pursuer, "AddPursuer", ME);
119 SetProp(MNPC_PURSUER, pursuer);
120 }
121 else
122 SetProp(MNPC_PURSUER, ({}));
123 }
124 }
125 else if (pointerp(QueryProp(MNPC_PURSUER)))
126 { // wird derzeit irgendwer verfolgt?
127 // alle Verfolgungen abbrechen...
128 filter_objects(QueryProp(MNPC_PURSUER)-({ 0 }), "RemovePursuer", ME);
129 SetProp(MNPC_PURSUER, 0); // Speicher freigeben...
130 }
131 else
132 SetProp(MNPC_PURSUER, 0);
133
134 // nur livings koennen command_me nutzen...
135 if (!living(ME))
136 flags |= MNPC_DIRECT_MOVE;
137
138 return Set(MNPC_FLAGS, flags, F_VALUE);
139}
140
141static void mnpc_InsertEnemy(object enemy)
142{
143 if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) &&
144 (member(QueryProp(MNPC_PURSUER), PL)==-1))
145 {
146 PL->AddPursuer(ME);
147 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
148 }
149}
150
151static void mnpc_reset()
152{
153 int flags = QueryProp(MNPC_FLAGS);
154 // meet_last_player < 0 zeigt an, dass der MNPC schon zuhause ist.
155 if (meet_last_player < 0
156 || !(flags & MNPC_WALK)
157 || (flags & MNPC_NO_MOVE_HOME))
158 return;
159
160 // Lange keinen Spielerkontakt und kein Spieler im Raum: nach Hause gehen.
161 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
162 && environment() && !sizeof(filter(
163 all_inventory(environment()), #'query_once_interactive)))
164 {
165 // Abschalten und Heimgehen und dort warten.
166 Stop(1);
167 }
168}
169
170static int _query_mnpc_last_meet()
171{ return meet_last_player; }
172
173static void mnpc_init()
174{
175 if (interactive(PL))
176 {
Zesstra0def98f2019-06-28 19:13:44 +0200177 // Wenn noetig, Wandern wieder aufnehmen.
zesstra42fd8bf2016-06-27 22:04:14 +0200178 if (meet_last_player<=0)
179 {
180 RegisterWalk();
181 }
Bugfixca4dfd32016-12-22 18:22:42 +0100182 if ( ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER) &&
183 (member(QueryProp(MNPC_PURSUER), PL)==-1)) ||
184 ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) && IsEnemy(PL)))
zesstra42fd8bf2016-06-27 22:04:14 +0200185 {
186 PL->AddPursuer(ME);
187 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
188 }
189 meet_last_player=time();
190 }
191 else
192 {
193 // Wenn der reinkommende auch ein MNPC_LAST_MEET groesser 0 hat, ist es
194 // ein MNPC, der noch laeuft. Wenn wir nicht laufen, laufen wir los.
195 // In diesem und auch im anderen Fall uebernehmen wir aber mal seinen
196 // letzten Spielerkontakt, denn der ist juenger als unserer.
197 int lm = PL->QueryProp(MNPC_LAST_MEET);
198 if (meet_last_player<=0 && lm>0)
199 {
200 RegisterWalk();
201 meet_last_player=lm;
202 }
203 else if (meet_last_player<lm)
204 meet_last_player=lm;
205 }
206}
207
208static void mnpc_move()
209{
210 if (environment() && (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER))
211 {
212 object *liv = QueryProp(MNPC_PURSUER) & all_inventory(environment());
213 filter_objects(QueryProp(MNPC_PURSUER)-liv-({ 0 }), "RemovePursuer", ME);
214 SetProp(MNPC_PURSUER, liv);
215 }
216 if (QueryProp(MNPC_FUNC))
Zesstrabddbe3d2016-07-06 20:47:43 +0200217 call_other(ME, QueryProp(MNPC_FUNC));
zesstra42fd8bf2016-06-27 22:04:14 +0200218}
219
220static int PreventEnter(string file)
221// darf der Raum betreten werden?
222{
223 string *area;
224
225 if (!sizeof(area=QueryProp(MNPC_AREA)))
226 return 0; // Raum darf betreten werden
227 else
228 {
229 int i;
230 status exactmatch;
231 exactmatch=QueryProp(MNPC_FLAGS) & MNPC_EXACT_AREA_MATCH;
232 if ((i=strstr(file, "#"))!=-1) file=file[0..i-1];
233 for (i=sizeof(area)-1; i>=0; i--)
234 {
235 if (exactmatch)
236 {
237 //exakter Vergleich, kein Substringvergleich gewuenscht
238 if (file==area[i])
239 return 0; //betreten
240 }
241 else
242 {
243 if (strstr(file, area[i])==0)
244 return 0; // Raum betreten
245 }
246 }
247 return 1; // Raum darf nicht betreten werden
248 }
249}
250
251static int mnpc_PreventFollow(object dest)
252{
253 if (dest && PreventEnter(object_name(dest)))
254 return 2;
255 return 0;
256}
257
258// Bewegungssimulation (Bewegungsmeldung) fuer bewegende non-livings
Zesstra77f2caf2021-04-09 19:14:07 +0200259protected int direct_move(object|string dest, int method, string direction)
zesstra42fd8bf2016-06-27 22:04:14 +0200260{
261 int res, para, tmp;
262 string textout, textin, *mout, vc, fn;
Zesstraa0c5dff2021-05-07 20:44:32 +0200263 object oldenv;
zesstra42fd8bf2016-06-27 22:04:14 +0200264
265 if (living(ME))
266 return call_other(ME, "move", dest, method);
267 else
268 {
269 oldenv = environment();
270 para=QueryProp(P_PARA);
271 if ((para>0) && stringp(dest))
272 {
273 fn=dest+"^"+para;
274 if (find_object(fn) || (file_size(fn+".c")>0))
275 dest=fn;
276 else if (file_size(vc=implode(explode(fn,"/")[0..<2],"/")
277 +"/virtual_compiler.c")>0)
278 {
Zesstra1c7da1d2019-10-24 23:01:15 +0200279 // wenn ein VC existiert, prüfen ob dieser ParaObjecte unterstuetzt
zesstra42fd8bf2016-06-27 22:04:14 +0200280 // wenn ja, dann testen ob sich Raum laden laesst...
bugfixaf2be4f2020-03-22 19:13:07 +0100281 if ((!catch(tmp=call_other(vc,"NoParaObjects")) && (!tmp)) &&
zesstra42fd8bf2016-06-27 22:04:14 +0200282 (!catch(call_other( fn, "???" ))))
283 dest=fn;
284 }
285 }
286
bugfixaf2be4f2020-03-22 19:13:07 +0100287 res = call_other(ME, "move", dest, M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200288
Bugfixdf8ed432021-01-23 20:46:24 +0100289 if (oldenv==environment() || living(this_object()))
zesstra42fd8bf2016-06-27 22:04:14 +0200290 return res;
291
292 // als erstes die Meldung fuer das Verlassen des Raumes...
293 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100294 textout = QueryProp(P_MMSGOUT) || QueryProp(P_MSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200295 else
296 {
bugfixaf2be4f2020-03-22 19:13:07 +0100297 mout = explode( QueryProp(P_MSGOUT) || "", "#" );
298 textout = mout[0] || QueryProp(P_MMSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200299 }
300
301 if (stringp(textout))
302 {
303 if ( !sizeof(direction) )
304 direction = 0;
305
Bugfixb4f10a62021-01-23 19:51:03 +0100306 send_room(oldenv,
zesstra42fd8bf2016-06-27 22:04:14 +0200307 Name( WER, 2 ) + " " + textout +
308 (direction ? " " + direction : "") +
Bugfixb4f10a62021-01-23 19:51:03 +0100309 (sizeof(mout) > 1 ? mout[1] : "") + ".",
310 MT_LOOK,
311 MA_MOVE_OUT,
312 0,
313 ({}),
314 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200315 }
316
Zesstra1c7da1d2019-10-24 23:01:15 +0200317 // nun die Meldung für das "Betreten" des Raumes...
zesstra42fd8bf2016-06-27 22:04:14 +0200318
319 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100320 textin = QueryProp(P_MMSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200321 else
bugfixaf2be4f2020-03-22 19:13:07 +0100322 textin = QueryProp(P_MSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200323
324 if (stringp(textin))
325 {
Bugfixb4f10a62021-01-23 19:51:03 +0100326 send_room(environment(this_object()),
327 capitalize(name( WER, 0 )) + " " + textin + ".",
328 MT_LOOK,
329 MA_MOVE_IN,
330 0,
331 ({this_object()}),
332 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200333 }
334 }
335 return res;
336}
337
Zesstra77f2caf2021-04-09 19:14:07 +0200338// Sammelt verfuegbare und prinzipiell benutzbare Ausgaenge
339// Standardverhalten beruecktsichtigt P_EXITS und ob special Exits benutzt
340// werden duerfen.
341// Aber ueberladene Varianten koennten voellig andere Quellen fuer Ausgaenge
342// (z.B. eigene Props von Magiern) beruecksichtigen.
343struct exit_s *PresentExits(mapping exits=0)
344{
345 if (!mappingp(exits))
346 exits = (environment()->Query(P_EXITS, F_VALUE));
347 // 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.
368struct exit_s SelectExit(struct exit_s *exitlist=0)
369{
370 if (!exitlist)
371 exitlist = PresentExits();
372 // Bei normalen Ausgaengen per PreventEnter() pruefen, ob der Zielraum OK
373 // ist. Special Exits koennen nicht geprueft und sind OK. (In der Liste
374 // sind bei originalem PresentExits() nur special exits, wenn fuer den
375 // MNPC erlaubt.)
376 exitlist = filter(exitlist, function int (struct exit_s ex)
377 {
378 return ex.special || !PreventEnter(ex.room);
379 });
380
381 // Und zufaellig einen aussuchen
382 if (sizeof(exitlist))
383 return exitlist[random(sizeof(exitlist))];
384 return 0;
385}
386
zesstra42fd8bf2016-06-27 22:04:14 +0200387int Walk()
388{
Zesstrafa7abe62021-04-09 14:26:26 +0200389 if (!environment())
390 {
391 // darf eigentlich nicht vorkommen.
392 raise_error("MNPC ohne Environment.\n");
393 }
394
395 int flags=QueryProp(MNPC_FLAGS);
396 if (!(flags & MNPC_WALK))
397 return 0;
398
Zesstra77f2caf2021-04-09 19:14:07 +0200399 // ggf. neuen Callout eintragen, bevor irgendwas anderes gemacht wird.
Zesstrafa7abe62021-04-09 14:26:26 +0200400 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))>=MAX_MASTER_TIME)
401 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
402
Zesstra77f2caf2021-04-09 19:14:07 +0200403 // Im Kampf ggf. temporaer nicht weitergehen.
Zesstrafa7abe62021-04-09 14:26:26 +0200404 if ((flags & MNPC_NO_WALK_IN_FIGHT) && InFight())
405 {
406 meet_last_player=time();
407 return 1;
408 }
409
Zesstra77f2caf2021-04-09 19:14:07 +0200410 // MNPC anhalten, wenn lange kein Spielerkontakt. Vom WALK_MASTER abmelden.
Zesstrafa7abe62021-04-09 14:26:26 +0200411 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
412 && !sizeof(filter(all_inventory(environment()),
413 #'query_once_interactive))
414 )
415 {
416 // anhalten und ggf. auch direkt nach Hause gehen.
417 Stop(flags & MNPC_GO_HOME_WHEN_STOPPED);
418 return 0;
419 }
420
Zesstra77f2caf2021-04-09 19:14:07 +0200421 // Zielausgang ermitteln
422 struct exit_s ex = SelectExit();
zesstra42fd8bf2016-06-27 22:04:14 +0200423
Zesstra77f2caf2021-04-09 19:14:07 +0200424 // Im direct mode wird die Bewegung selbst implementiert -> YNMV!
zesstra42fd8bf2016-06-27 22:04:14 +0200425 if (flags & MNPC_DIRECT_MOVE)
426 {
Zesstra77f2caf2021-04-09 19:14:07 +0200427 // in diesem Fall sind keine SEs benutzbar, aber die werden von
428 // PresentExit() dann auch nicht geliefert.
429 if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200430 {
Zesstraa0c5dff2021-05-07 20:44:32 +0200431 direct_move(ex.room, M_GO, "nach "+capitalize(ex.msg||ex.cmd));
zesstra42fd8bf2016-06-27 22:04:14 +0200432 }
433 else
434 {
Zesstra77f2caf2021-04-09 19:14:07 +0200435 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
436 // dort gehts ja vielleicht weiter.
437 direct_move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK, 0);
438 }
zesstra42fd8bf2016-06-27 22:04:14 +0200439 }
Zesstra77f2caf2021-04-09 19:14:07 +0200440 else if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200441 {
Zesstra77f2caf2021-04-09 19:14:07 +0200442 // Dies ist der Normalfall: Ausgang per Kommando benutzen
443 command(ex.cmd);
zesstra42fd8bf2016-06-27 22:04:14 +0200444 }
445 else
446 {
Zesstra77f2caf2021-04-09 19:14:07 +0200447 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
448 // dort gehts ja vielleicht weiter.
449 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200450 }
zesstra42fd8bf2016-06-27 22:04:14 +0200451
452 return 1;
453}