blob: 43c57650feb6086fac893d6561f35c88ab02e893 [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));
zesstra42fd8bf2016-06-27 22:04:14 +020031 SetProp(P_MNPC, 1);
32 SetProp(MNPC_AREA, ({}));
33 SetProp(MNPC_DELAY, MNPC_DFLT_DELAY);
34 SetProp(MNPC_FUNC, 0);
35 SetProp(MNPC_RANDOM, 0);
36 SetProp(MNPC_WALK_TIME, MNPC_DFLT_WALK);
37 SetProp(MNPC_FLAGS, 0);
38 SetProp(P_ENABLE_IN_ATTACK_OUT, 1);
39 meet_last_player=time();
40}
41
42protected void RegisterWalk()
43{
Zesstra260613d2019-12-09 21:02:58 +010044 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM)) < MAX_MASTER_TIME)
Zesstra0def98f2019-06-28 19:13:44 +020045 {
46 if (!WALK_MASTER->Registration())
47 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY),
48 QueryProp(MNPC_RANDOM));
49 }
zesstra42fd8bf2016-06-27 22:04:14 +020050 else
Zesstra0def98f2019-06-28 19:13:44 +020051 {
Zesstrafa7abe62021-04-09 14:26:26 +020052 if (find_call_out("Walk") == -1)
53 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
Zesstra0def98f2019-06-28 19:13:44 +020054 }
zesstra42fd8bf2016-06-27 22:04:14 +020055}
56
57// Can be used to manually restart the MNPC from a different object even if
Zesstra0def98f2019-06-28 19:13:44 +020058// the MNPC had no player contact.
zesstra42fd8bf2016-06-27 22:04:14 +020059public int RestartWalk()
60{
61 int flags = QueryProp(MNPC_FLAGS);
62 // Falls nicht laufend, wird gar nichts gemacht.
63 if (flags & MNPC_WALK)
64 {
65 //Spielerkontakt simulieren
66 meet_last_player=time();
67 // Falls MNPC noch registriert ist oder noch einen Callout auf Walk hat,
68 // muss nichts weiter gemacht werden.
69 if (WALK_MASTER->Registration()
Zesstrafa7abe62021-04-09 14:26:26 +020070 || find_call_out("Walk") > -1)
zesstra42fd8bf2016-06-27 22:04:14 +020071 return -1;
72 // ansonsten MNPC registrieren, falls geeignet.
Zesstra260613d2019-12-09 21:02:58 +010073 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))<MAX_MASTER_TIME)
zesstra42fd8bf2016-06-27 22:04:14 +020074 WALK_MASTER->RegisterWalker(QueryProp(MNPC_DELAY), QueryProp(MNPC_RANDOM));
75 // und mit kurzer Verzoegerung einmal laufen. (ja, absicht, hier
76 // MNPC_DELAY zu nutzen - denn solange dauert das Walk vom Master
77 // mindestens.)
Zesstrafa7abe62021-04-09 14:26:26 +020078 call_out("Walk",1+random( min(QueryProp(MNPC_DELAY)-1,8) ));
zesstra42fd8bf2016-06-27 22:04:14 +020079 return 1;
80 }
81 return 0;
82}
83
84protected void Stop(int movehome)
85{
86 if (WALK_MASTER->Registration())
87 WALK_MASTER->RemoveWalker();
Zesstrafa7abe62021-04-09 14:26:26 +020088 else if (find_call_out("Walk")!=-1)
89 remove_call_out("Walk");
zesstra42fd8bf2016-06-27 22:04:14 +020090 if (movehome)
91 {
92 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
93 meet_last_player=-1;
94 }
95}
96
97static int _set_mnpc_flags(int flags)
98{
99 if (flags & MNPC_WALK)
100 {
101 if (!QueryProp(MNPC_HOME))
102 raise_error("unknown MNPC_HOME\n");
Zesstra0def98f2019-06-28 19:13:44 +0200103 // RegisterWalk prueft, ob der MNPC schon angemeldet ist.
zesstra42fd8bf2016-06-27 22:04:14 +0200104 RegisterWalk();
105 }
106 // else nicht von Bedeutung, da in Walk() das flag getestet wird
107 if (flags & MNPC_FOLLOW_PLAYER)
108 {
109 if (!QueryProp(MNPC_PURSUER))
110 { // wurde dieses Flag neu eingeschaltet?
111 if (environment())
112 { // Verfolgung aufnehmen...
113 object *pursuer = filter(all_inventory(ENV()), #'interactive);
114 filter_objects(pursuer, "AddPursuer", ME);
115 SetProp(MNPC_PURSUER, pursuer);
116 }
117 else
118 SetProp(MNPC_PURSUER, ({}));
119 }
120 }
121 else if (pointerp(QueryProp(MNPC_PURSUER)))
122 { // wird derzeit irgendwer verfolgt?
123 // alle Verfolgungen abbrechen...
124 filter_objects(QueryProp(MNPC_PURSUER)-({ 0 }), "RemovePursuer", ME);
125 SetProp(MNPC_PURSUER, 0); // Speicher freigeben...
126 }
127 else
128 SetProp(MNPC_PURSUER, 0);
129
130 // nur livings koennen command_me nutzen...
131 if (!living(ME))
132 flags |= MNPC_DIRECT_MOVE;
133
134 return Set(MNPC_FLAGS, flags, F_VALUE);
135}
136
137static void mnpc_InsertEnemy(object enemy)
138{
139 if ( (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) &&
140 (member(QueryProp(MNPC_PURSUER), PL)==-1))
141 {
142 PL->AddPursuer(ME);
143 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
144 }
145}
146
147static void mnpc_reset()
148{
149 int flags = QueryProp(MNPC_FLAGS);
150 // meet_last_player < 0 zeigt an, dass der MNPC schon zuhause ist.
151 if (meet_last_player < 0
152 || !(flags & MNPC_WALK)
153 || (flags & MNPC_NO_MOVE_HOME))
154 return;
155
156 // Lange keinen Spielerkontakt und kein Spieler im Raum: nach Hause gehen.
157 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
158 && environment() && !sizeof(filter(
159 all_inventory(environment()), #'query_once_interactive)))
160 {
161 // Abschalten und Heimgehen und dort warten.
162 Stop(1);
163 }
164}
165
166static int _query_mnpc_last_meet()
167{ return meet_last_player; }
168
169static void mnpc_init()
170{
171 if (interactive(PL))
172 {
Zesstra0def98f2019-06-28 19:13:44 +0200173 // Wenn noetig, Wandern wieder aufnehmen.
zesstra42fd8bf2016-06-27 22:04:14 +0200174 if (meet_last_player<=0)
175 {
176 RegisterWalk();
177 }
Bugfixca4dfd32016-12-22 18:22:42 +0100178 if ( ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER) &&
179 (member(QueryProp(MNPC_PURSUER), PL)==-1)) ||
180 ((QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_ENEMY) && IsEnemy(PL)))
zesstra42fd8bf2016-06-27 22:04:14 +0200181 {
182 PL->AddPursuer(ME);
183 SetProp(MNPC_PURSUER, QueryProp(MNPC_PURSUER)+({ PL }));
184 }
185 meet_last_player=time();
186 }
187 else
188 {
189 // Wenn der reinkommende auch ein MNPC_LAST_MEET groesser 0 hat, ist es
190 // ein MNPC, der noch laeuft. Wenn wir nicht laufen, laufen wir los.
191 // In diesem und auch im anderen Fall uebernehmen wir aber mal seinen
192 // letzten Spielerkontakt, denn der ist juenger als unserer.
193 int lm = PL->QueryProp(MNPC_LAST_MEET);
194 if (meet_last_player<=0 && lm>0)
195 {
196 RegisterWalk();
197 meet_last_player=lm;
198 }
199 else if (meet_last_player<lm)
200 meet_last_player=lm;
201 }
202}
203
204static void mnpc_move()
205{
206 if (environment() && (QueryProp(MNPC_FLAGS) & MNPC_FOLLOW_PLAYER))
207 {
208 object *liv = QueryProp(MNPC_PURSUER) & all_inventory(environment());
209 filter_objects(QueryProp(MNPC_PURSUER)-liv-({ 0 }), "RemovePursuer", ME);
210 SetProp(MNPC_PURSUER, liv);
211 }
212 if (QueryProp(MNPC_FUNC))
Zesstrabddbe3d2016-07-06 20:47:43 +0200213 call_other(ME, QueryProp(MNPC_FUNC));
zesstra42fd8bf2016-06-27 22:04:14 +0200214}
215
216static int PreventEnter(string file)
217// darf der Raum betreten werden?
218{
219 string *area;
220
221 if (!sizeof(area=QueryProp(MNPC_AREA)))
222 return 0; // Raum darf betreten werden
223 else
224 {
225 int i;
226 status exactmatch;
227 exactmatch=QueryProp(MNPC_FLAGS) & MNPC_EXACT_AREA_MATCH;
228 if ((i=strstr(file, "#"))!=-1) file=file[0..i-1];
229 for (i=sizeof(area)-1; i>=0; i--)
230 {
231 if (exactmatch)
232 {
233 //exakter Vergleich, kein Substringvergleich gewuenscht
234 if (file==area[i])
235 return 0; //betreten
236 }
237 else
238 {
239 if (strstr(file, area[i])==0)
240 return 0; // Raum betreten
241 }
242 }
243 return 1; // Raum darf nicht betreten werden
244 }
245}
246
247static int mnpc_PreventFollow(object dest)
248{
249 if (dest && PreventEnter(object_name(dest)))
250 return 2;
251 return 0;
252}
253
254// Bewegungssimulation (Bewegungsmeldung) fuer bewegende non-livings
Zesstra77f2caf2021-04-09 19:14:07 +0200255protected int direct_move(object|string dest, int method, string direction)
zesstra42fd8bf2016-06-27 22:04:14 +0200256{
257 int res, para, tmp;
258 string textout, textin, *mout, vc, fn;
Zesstraa0c5dff2021-05-07 20:44:32 +0200259 object oldenv;
zesstra42fd8bf2016-06-27 22:04:14 +0200260
261 if (living(ME))
262 return call_other(ME, "move", dest, method);
263 else
264 {
265 oldenv = environment();
266 para=QueryProp(P_PARA);
267 if ((para>0) && stringp(dest))
268 {
269 fn=dest+"^"+para;
270 if (find_object(fn) || (file_size(fn+".c")>0))
271 dest=fn;
272 else if (file_size(vc=implode(explode(fn,"/")[0..<2],"/")
273 +"/virtual_compiler.c")>0)
274 {
Zesstra1c7da1d2019-10-24 23:01:15 +0200275 // wenn ein VC existiert, prüfen ob dieser ParaObjecte unterstuetzt
zesstra42fd8bf2016-06-27 22:04:14 +0200276 // wenn ja, dann testen ob sich Raum laden laesst...
bugfixaf2be4f2020-03-22 19:13:07 +0100277 if ((!catch(tmp=call_other(vc,"NoParaObjects")) && (!tmp)) &&
zesstra42fd8bf2016-06-27 22:04:14 +0200278 (!catch(call_other( fn, "???" ))))
279 dest=fn;
280 }
281 }
282
bugfixaf2be4f2020-03-22 19:13:07 +0100283 res = call_other(ME, "move", dest, M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200284
Bugfixdf8ed432021-01-23 20:46:24 +0100285 if (oldenv==environment() || living(this_object()))
zesstra42fd8bf2016-06-27 22:04:14 +0200286 return res;
287
288 // als erstes die Meldung fuer das Verlassen des Raumes...
289 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100290 textout = QueryProp(P_MMSGOUT) || QueryProp(P_MSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200291 else
292 {
bugfixaf2be4f2020-03-22 19:13:07 +0100293 mout = explode( QueryProp(P_MSGOUT) || "", "#" );
294 textout = mout[0] || QueryProp(P_MMSGOUT);
zesstra42fd8bf2016-06-27 22:04:14 +0200295 }
296
297 if (stringp(textout))
298 {
299 if ( !sizeof(direction) )
300 direction = 0;
301
Bugfixb4f10a62021-01-23 19:51:03 +0100302 send_room(oldenv,
zesstra42fd8bf2016-06-27 22:04:14 +0200303 Name( WER, 2 ) + " " + textout +
304 (direction ? " " + direction : "") +
Bugfixb4f10a62021-01-23 19:51:03 +0100305 (sizeof(mout) > 1 ? mout[1] : "") + ".",
306 MT_LOOK,
307 MA_MOVE_OUT,
308 0,
309 ({}),
310 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200311 }
312
Zesstra1c7da1d2019-10-24 23:01:15 +0200313 // nun die Meldung für das "Betreten" des Raumes...
zesstra42fd8bf2016-06-27 22:04:14 +0200314
315 if ( method & M_TPORT )
bugfixaf2be4f2020-03-22 19:13:07 +0100316 textin = QueryProp(P_MMSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200317 else
bugfixaf2be4f2020-03-22 19:13:07 +0100318 textin = QueryProp(P_MSGIN);
zesstra42fd8bf2016-06-27 22:04:14 +0200319
320 if (stringp(textin))
321 {
Bugfixb4f10a62021-01-23 19:51:03 +0100322 send_room(environment(this_object()),
323 capitalize(name( WER, 0 )) + " " + textin + ".",
324 MT_LOOK,
325 MA_MOVE_IN,
326 0,
327 ({this_object()}),
328 this_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200329 }
330 }
331 return res;
332}
333
Zesstra77f2caf2021-04-09 19:14:07 +0200334// Sammelt verfuegbare und prinzipiell benutzbare Ausgaenge
335// Standardverhalten beruecktsichtigt P_EXITS und ob special Exits benutzt
336// werden duerfen.
337// Aber ueberladene Varianten koennten voellig andere Quellen fuer Ausgaenge
338// (z.B. eigene Props von Magiern) beruecksichtigen.
Zesstra2ee342c2021-05-08 12:16:20 +0200339struct exit_s *PresentExits(mapping exits=environment()->Query(P_EXITS,
340 F_VALUE))
Zesstra77f2caf2021-04-09 19:14:07 +0200341{
Zesstra77f2caf2021-04-09 19:14:07 +0200342 // Aufgrund der MNPC_FLAGS bestimmen ob nur normale Ausgaenge genutzt
343 // werden duerfen
344 int flags=QueryProp(MNPC_FLAGS);
345 flags = !(flags & MNPC_DIRECT_MOVE) && !(flags & MNPC_ONLY_EXITS);
346 // flags ist jetzt nur noch ja/nein bzgl. Special Exits
347
348 return m_values(map(exits,
349 function struct exit_s (string cmd, <string|closure>* arr)
350 {
351 if (flags || !closurep(arr[0]))
352 return (<exit_s> cmd:cmd, room:arr[0], msg:arr[1],
353 special:closurep(arr[0]));
354 return 0;
355 }
356 )) - ({0}); // 0 aufgrund nicht benutzbarer SEs
357}
358
359// Prueft die Ausgaenge auf Benutzbarkeit durch diesen NPC - standardmaessig
360// durch Aufruf von PreventEnter() und liefert einen zufaelligen benutzbaren
361// Ausgang.
362// Kann Ueberladen werden, um eine andere Art der Auswahl zu bewerkstelligen.
Zesstra2ee342c2021-05-08 12:16:20 +0200363struct exit_s SelectExit(struct exit_s *exitlist=PresentExits())
Zesstra77f2caf2021-04-09 19:14:07 +0200364{
Zesstra77f2caf2021-04-09 19:14:07 +0200365 // Bei normalen Ausgaengen per PreventEnter() pruefen, ob der Zielraum OK
366 // ist. Special Exits koennen nicht geprueft und sind OK. (In der Liste
367 // sind bei originalem PresentExits() nur special exits, wenn fuer den
368 // MNPC erlaubt.)
369 exitlist = filter(exitlist, function int (struct exit_s ex)
370 {
371 return ex.special || !PreventEnter(ex.room);
372 });
373
374 // Und zufaellig einen aussuchen
375 if (sizeof(exitlist))
376 return exitlist[random(sizeof(exitlist))];
377 return 0;
378}
379
zesstra42fd8bf2016-06-27 22:04:14 +0200380int Walk()
381{
Zesstrafa7abe62021-04-09 14:26:26 +0200382 if (!environment())
383 {
384 // darf eigentlich nicht vorkommen.
385 raise_error("MNPC ohne Environment.\n");
386 }
387
388 int flags=QueryProp(MNPC_FLAGS);
389 if (!(flags & MNPC_WALK))
390 return 0;
391
Zesstra77f2caf2021-04-09 19:14:07 +0200392 // ggf. neuen Callout eintragen, bevor irgendwas anderes gemacht wird.
Zesstrafa7abe62021-04-09 14:26:26 +0200393 if ((QueryProp(MNPC_DELAY)+QueryProp(MNPC_RANDOM))>=MAX_MASTER_TIME)
394 call_out("Walk", QueryProp(MNPC_DELAY)+random(QueryProp(MNPC_RANDOM)));
395
Zesstra77f2caf2021-04-09 19:14:07 +0200396 // Im Kampf ggf. temporaer nicht weitergehen.
Zesstrafa7abe62021-04-09 14:26:26 +0200397 if ((flags & MNPC_NO_WALK_IN_FIGHT) && InFight())
398 {
399 meet_last_player=time();
400 return 1;
401 }
402
Zesstra77f2caf2021-04-09 19:14:07 +0200403 // MNPC anhalten, wenn lange kein Spielerkontakt. Vom WALK_MASTER abmelden.
Zesstrafa7abe62021-04-09 14:26:26 +0200404 if (QueryProp(MNPC_WALK_TIME)+meet_last_player < time()
405 && !sizeof(filter(all_inventory(environment()),
406 #'query_once_interactive))
407 )
408 {
409 // anhalten und ggf. auch direkt nach Hause gehen.
410 Stop(flags & MNPC_GO_HOME_WHEN_STOPPED);
411 return 0;
412 }
413
Zesstra77f2caf2021-04-09 19:14:07 +0200414 // Zielausgang ermitteln
415 struct exit_s ex = SelectExit();
zesstra42fd8bf2016-06-27 22:04:14 +0200416
Zesstra77f2caf2021-04-09 19:14:07 +0200417 // Im direct mode wird die Bewegung selbst implementiert -> YNMV!
zesstra42fd8bf2016-06-27 22:04:14 +0200418 if (flags & MNPC_DIRECT_MOVE)
419 {
Zesstra77f2caf2021-04-09 19:14:07 +0200420 // in diesem Fall sind keine SEs benutzbar, aber die werden von
421 // PresentExit() dann auch nicht geliefert.
422 if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200423 {
Zesstraa0c5dff2021-05-07 20:44:32 +0200424 direct_move(ex.room, M_GO, "nach "+capitalize(ex.msg||ex.cmd));
zesstra42fd8bf2016-06-27 22:04:14 +0200425 }
426 else
427 {
Zesstra77f2caf2021-04-09 19:14:07 +0200428 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
429 // dort gehts ja vielleicht weiter.
430 direct_move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK, 0);
431 }
zesstra42fd8bf2016-06-27 22:04:14 +0200432 }
Zesstra77f2caf2021-04-09 19:14:07 +0200433 else if (ex)
zesstra42fd8bf2016-06-27 22:04:14 +0200434 {
Zesstra77f2caf2021-04-09 19:14:07 +0200435 // Dies ist der Normalfall: Ausgang per Kommando benutzen
436 command(ex.cmd);
zesstra42fd8bf2016-06-27 22:04:14 +0200437 }
438 else
439 {
Zesstra77f2caf2021-04-09 19:14:07 +0200440 // Hngl. Ohne Ausgaenge nach Hause gehen... aber nicht anhalten, von
441 // dort gehts ja vielleicht weiter.
442 move(QueryProp(MNPC_HOME), M_TPORT|M_NOCHECK);
zesstra42fd8bf2016-06-27 22:04:14 +0200443 }
zesstra42fd8bf2016-06-27 22:04:14 +0200444
445 return 1;
446}