blob: a14e5352cd9f1f30a2d88f5ffd989b1b1d744f48 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// living/team.c
4//
5// $Id: team.c 9138 2015-02-03 21:46:56Z Zesstra $
6#pragma strong_types
7#pragma save_types
8#pragma range_check
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010
11#define NEED_PROTOTYPES
12
13#include <properties.h>
14#include <thing/properties.h>
15#include <living/combat.h>
16#include <combat.h>
17#include <living/team.h>
18#include <wizlevels.h>
19#include <hook.h>
20
21#define ME this_object()
22#define TP this_player()
23#define PO previous_object()
24#define ENV environment()
25
26private nosave string team_attack_cmd;
27private nosave mapping team_follow_todo;
28private nosave int team_autofollow;
29private nosave object teammove;
30
31void create() {
32 Set(P_TEAM_ATTACK_CMD,-1,F_SET_METHOD);
33 Set(P_TEAM_ATTACK_CMD,PROTECTED,F_MODE_AS);
34 Set(P_TEAM_AUTOFOLLOW,-1,F_SET_METHOD);
35 Set(P_TEAM_AUTOFOLLOW,PROTECTED,F_MODE_AS);
36 teammove=0;
37 offerHook(H_HOOK_TEAMROWCHANGE, 1);
38}
39
40void add_team_commands() {
41 add_action("teamcmd","gruppe");
42 add_action("teamcmd","g");
43 add_action("teamcmd","team");
44}
45
46string _query_team_attack_cmd() {
47 return Set(P_TEAM_ATTACK_CMD,team_attack_cmd);
48}
49
50int _query_team_autofollow() {
51 return Set(P_TEAM_AUTOFOLLOW,team_autofollow);
52}
53
54private int team_help() {
55 // Syntax-Kompatiblitaet (Avalon) ist ganz nett :-)
56 write("\
57(Befehle des Teamleiters sind mit * gekennzeichnet\n\
58\n\
59* team angriff\n\
60 team angriffsbefehl <befehl>\n\
61* team aufnahme <name>\n\
62 team autof[olge] <ein/aus>\n\
63* team autoi[nfo] <ein/aus> [+[lp]] [+[kp]] [sofort]\n\
64* team entlasse <name>\n\
65 team farben lp_rot lp_gelb kp_rot kp_gelb\n\
66 team flucht[reihe] <reihe>\n\
67 team folge <name>\n\
68* team formation <min[-max]> [<min[-max]> ...]\n\
69 team hilfe|?\n\
70 team [info] [sortiert|alphabetisch]\n\
71 team [kampf]reihe <reihe>\n\
72* team leiter[in] <name>\n\
73 team liste\n\
74* team name <gruppenname>\n\
75 team orte [alle]\n\
76 team ruf[e]\n\
77 team uebersicht\n\
78 team verlasse\n");
79 return 1;
80}
81
82object IsTeamLeader() {
83 object team;
84
85 if (!objectp(team=Query(P_TEAM))
86 || team!=Query(P_TEAM_LEADER)
87 || team->Leader()!=ME)
88 return 0;
89 return team;
90}
91
92object *TeamMembers() {
93 object team;
94
95 if (!objectp(team=Query(P_TEAM)))
96 return ({ME});
97 return team->Members();
98}
99
100string TeamPrefix() {
101 object team;
102
103 if (!objectp(team=Query(P_TEAM)))
104 return "";
105 return "["+team->Name()+"] ";
106}
107
108
109private int team_aufnahmewunsch(string arg) {
110 object pl;
111
112 if ((!objectp(pl=find_player(arg)) && !objectp(pl=present(arg,ENV)))
113 || pl->QueryProp(P_INVIS) || environment(pl)!=ENV)
114 return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
115 if (!living(pl))
116 return notify_fail(pl->Name(WER)+" ist etwas zu inaktiv.\n"),0;
117 if (pl==ME)
118 return notify_fail("Du bist eine Person zu wenig fuer ein Team.\n"),0;
119 SetProp(P_TEAM_NEWMEMBER,pl);
120 if (pl->IsTeamLeader()) {
121 write("Du bittest "+pl->name(WEN)+" um Aufnahme ins Team.\n");
122 tell_object(pl,TP->Name(WER)+" bittet Dich um Aufnahme ins Team.\n");
123 } else {
124 write("Du bittest "+pl->name(WEN)+" um Gruendung eines Teams.\n");
125 tell_object(pl,TP->Name(WER)+" bittet Dich um Gruendung eines Teams.\n");
126 }
127 return 1;
128}
129
130private int team_aufnahme(string arg) {
131 object pl,team;
132 int res;
133
134 if ((!objectp(pl=find_player(arg)) && !objectp(pl=present(arg,ENV)))
135 || pl->QueryProp(P_INVIS) || environment(pl)!=ENV)
136 return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
137 if (pl->QueryProp(P_TEAM_NEWMEMBER)!=ME)
138 return notify_fail(pl->Name(WER)+" hat Dich nicht um Aufnahme gebeten.\n"),
139 0;
140 if (pl==ME)
141 return notify_fail("Du bist eine Person zu wenig fuer ein Team.\n"),0;
142 if (!objectp(team=QueryProp(P_TEAM)))
143 team=clone_object(TEAM_OBJECT);
144 res=team->AddMember(pl);
145 if (!sizeof(team->Members()))
146 team->remove();
147 return res;
148}
149
150object IsTeamMove() {
151 if (!objectp(teammove) || (teammove!=Query(P_TEAM)))
152 teammove=0;
153 return teammove;
154}
155
156static void DoTeamAttack(object env, object callbackto) {
157 if (env==ENV && stringp(team_attack_cmd) && !IS_LEARNER(ME)
158 && (interactive(ME) || !query_once_interactive(ME))
159 && objectp(callbackto) && callbackto==Query(P_TEAM)) {
160 teammove=callbackto;
161 command(team_attack_cmd);
162 }
163 if (objectp(callbackto))
164 callbackto->TeamAttackExecuted_Callback(teammove?1:0);
165 teammove=0;
166}
167
168int CallTeamAttack(object env) {
169 if (stringp(team_attack_cmd)
170 && find_call_out("DoTeamAttack")<0
171 && PO
172 && PO==Query(P_TEAM))
173 return call_out("DoTeamAttack",0,env,PO),1;
174 return 0;
175}
176
177static int DoTeamFollow() {
178 string cmd;
179
180 if (!team_autofollow
181 || (!interactive(ME) && query_once_interactive(ME))
182 || IS_LEARNER(ME)
183 || !mappingp(team_follow_todo))
184 return 0;
185 if (!stringp(cmd=team_follow_todo[ENV]))
186 return team_follow_todo=0;
187
188 do {
189 m_delete(team_follow_todo,ENV);
190 tell_object(ME,sprintf("Du folgst Deinem Team mit \"%s\".\n",cmd));
191 command(cmd);
192 } while (get_eval_cost()>900000 && random(1000)>20 && objectp(ME)
193 && stringp(cmd=team_follow_todo[ENV]));
194
195 // Ist Spieler in Umgebung gelandet, fuer die noch ein
196 // Befehl auszufuehren ist?
197 if (!objectp(ME) || !stringp(team_follow_todo[ENV]))
198 return team_follow_todo=0;
199 while (remove_call_out("DoTeamFollow")!=-1) ;
200 call_out("DoTeamFollow",0);
201 return 0;
202}
203
204int CallTeamFollow(object env, string cmd) {
205 if (!team_autofollow
206 || PO!=Query(P_TEAM)
207 || !PO
208 || !objectp(env)
209 || !stringp(cmd))
210 return 0;
211 if (!mappingp(team_follow_todo))
212 team_follow_todo=([]);
213 if (ENV!=env && !team_follow_todo[ENV])
214 return 0;
215 team_follow_todo[env]=cmd;
216 if (find_call_out("DoTeamFollow")<0)
217 call_out("DoTeamFollow",0);
218 return 1;
219}
220
221int ClearTeamFollow() {
222 if (PO!=Query(P_TEAM) || !PO)
223 return 0;
224 team_follow_todo=([]);
225 return 1;
226}
227
228mixed *PresentTeamRows() {
229 object team;
230 mixed *res;
231 int i;
232
233 if (!objectp(team=Query(P_TEAM))) {
234 res=EMPTY_TEAMARRAY;
235 res[0]=({ME});
236 return res;
237 }
238 res=team->PresentRows(ENV);
239 for (i=0;i<MAX_TEAMROWS;i++)
240 if (member(res[i],ME)>=0)
241 return res;
242 res[0]+=({ME});
243 return res;
244}
245
246varargs mixed *PresentEnemyRows(object *here) {
247 mixed *res,*rows;
248 mapping added_teams;
249 int i,j;
250 object ob,team;
251
252 added_teams=([Query(P_TEAM):1]); // Nicht auf eigenes Team hauen
253 res=EMPTY_TEAMARRAY;
254 if (!pointerp(here))
255 here=PresentEnemies();
256 for (i=sizeof(here)-1;i>=0;i--) {
257 if (!objectp(ob=here[i]))
258 continue;
259 if (!objectp(team=ob->QueryProp(P_TEAM))) {
260 res[0]+=({ob});
261 continue;
262 }
263 if (added_teams[team])
264 continue;
265 added_teams[team]=1;
266 rows=team->PresentRows(ENV);
267 for (j=0;j<MAX_TEAMROWS;j++)
268 res[j]+=rows[j];
269 }
270 return res;
271}
272
273varargs object SelectNearEnemy(object *here, int forcefrom) {
274 object ob,en,team;
275 mixed *rows;
276 int *prob,prot,i,r,sz,upsz,sum;
277
278 if (!pointerp(here))
279 here=PresentEnemies();
280 if (!objectp(ob=SelectEnemy(here)))
281 return 0;
282 en=ob->QueryProp(P_TEAM); // Feindliches Team
283 if (objectp(team=Query(P_TEAM))) { // Eigenes Team
284 if (en==team) // Feind im eigenen Team, kein ANDERES Mitglied waehlen.
285 return ob; // Aber auch ausserhalb Reihe 1 draufhauen
286 rows=team->PresentRows(ENV);
287 if (member(rows[0],ME)<0) // Stehe ich in der ersten Reihe?
288 return 0; // Falls nein ist auch kein Gegner nahe.
289 }
290 if (!objectp(en))
291 return ob; // Ist nicht in einem Team, also drauf.
292 rows=en->PresentRows(environment(ob));
293 prob=({1,0,0,0,0});
294 prot=sum=0;
295 for (i=0;i<MAX_TEAMROWS;i++) {
296 if (prot>0) prot--; // Schutzkegel nimmt ab.
297 if (!sz=sizeof(rows[i])) continue; // Gegner in dieser Reihe
298 upsz=sz-prot;if (upsz<0) continue; // Anzahl ungeschuetzter Gegner
299 prob[i]+=(upsz+sum); // Wahrscheinlichkeit += ungeschuetzt
300 sum=prob[i]; // Summe bisheriger Wahrscheinlichkeiten
301 if (sz>prot) prot=sz; // Neuer Schutzkegel
302 }
303 r=random(sum);
304 for (i=0;i<MAX_TEAMROWS;i++)
305 if (r<prob[i])
306 break;
307 if (i>=MAX_TEAMROWS)
308 i=0;
309 if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
310 return en;
311 if (i && objectp(en=SelectEnemy(forcefrom?(here&rows[0]):rows[0])))
312 return en;
313 return ob;
314}
315
316varargs object SelectFarEnemy(object *here, int min, int max, int forcefrom) {
317 mixed *rows;
318 int *prob,i,r,sum;
319 object en;
320
321 if (max<0 || min>=MAX_TEAMROWS || max<min)
322 return 0;
323 if (min<0) min=0;
324 if (max>=MAX_TEAMROWS) max=MAX_TEAMROWS-1;
325 if (!pointerp(here))
326 here=PresentEnemies();
327 rows=PresentEnemyRows(here);
328 prob=({0,0,0,0,0});
329 sum=0;
330 for (i=min;i<=max;i++)
331 sum=prob[i]=sum+sizeof(rows[i])+max-i;
332
333 r=random(sum);
334 for (i=min;i<=max;i++)
335 if (r<prob[i])
336 break;
337 if (i>max)
338 i=min;
339 if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
340 return en;
341 for (i=min;i<=max;i++)
342 if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
343 return en;
344 return 0;
345}
346
347mixed _query_friend() {
348 mixed res;
349
350 if (res=Query(P_FRIEND))
351 return res;
352 if (objectp(res=Query(P_TEAM_ASSOC_MEMBERS))
353 && query_once_interactive(res))
354 return res;
355 return 0;
356}
357
358int DeAssocMember(object npc) {
359 mixed obs;
360 object team;
361
362 if (extern_call() && PO!=npc &&
363 member(({"gilden","spellbooks"}),
364 explode(object_name(PO),"/")[1])<0)
365 return 0;
366 obs=QueryProp(P_TEAM_ASSOC_MEMBERS);
367 if (!pointerp(obs))
368 return 0;
369 obs-=({npc,0});
370 SetProp(P_TEAM_ASSOC_MEMBERS,obs);
371 if (objectp(team=QueryProp(P_TEAM)))
372 team->RemoveAssocMember(ME,npc);
373 return 1;
374}
375
376int AssocMember(object npc) {
377 mixed obs;
378 object team;
379
380 if (extern_call() && PO!=npc &&
381 member(({"gilden","spellbooks"}),
382 explode(object_name(PO),"/")[1])<0)
383 return 0;
384 if (!objectp(npc)
385 || npc->QueryProp(P_TEAM_ASSOC_MEMBERS)
386 || IsEnemy(npc)
387 || npc==ME
388 || query_once_interactive(npc))
389 return 0;
390 obs=QueryProp(P_TEAM_ASSOC_MEMBERS);
391 if (objectp(obs))
392 return 0;
393 if (!pointerp(obs))
394 obs=({});
395 obs=(obs-({npc,0}))+({npc});
396 SetProp(P_TEAM_ASSOC_MEMBERS,obs);
397 npc->SetProp(P_TEAM_ASSOC_MEMBERS,ME);
398 if (objectp(team=QueryProp(P_TEAM)))
399 team->AddAssocMember(ME,npc);
400 return 1;
401}
402
403varargs void InsertEnemyTeam(mixed ens, int rek) {
404 object *obs,ob,eteam,team;
405 int i;
406
407 team=Query(P_TEAM);
408 // Alle Teammitglieder des Gegners sind Feind:
409 if (objectp(ens)) {
410 if (objectp(eteam=ens->QueryProp(P_TEAM))) {
411 if (eteam==team) // feindliches Team = eigenes Team?
412 return; // also nicht alle Teammitglieder gegeneinander hetzen
413 ens=eteam->Members();
414 } else {
415 ens=({ens});
416 }
417 }
418 if (!pointerp(ens))
419 return;
420 ens-=({ME});
421
422 // Interactives sollen keine Interactives durch Team angreifen:
423 if (query_once_interactive(ME)) {
424 for (i=sizeof(ens)-1;i>=0;i--)
425 if (objectp(ob=ens[i]) && environment(ob)==environment()
426 && !query_once_interactive(ob))
427 InsertSingleEnemy(ob);
428 } else {
429 for (i=sizeof(ens)-1;i>=0;i--)
430 if (objectp(ob=ens[i]) && environment(ob)==environment())
431 InsertSingleEnemy(ob);
432 }
433
434 // Alle anderen Teammitglieder Informieren:
435 if (rek || !objectp(team) || !pointerp(obs=team->Members()))
436 return;
437 obs-=({ME});
438 obs-=ens;
439 for (i=sizeof(obs)-1;i>=0;i--)
440 if (objectp(ob=obs[i]))
441 ob->InsertEnemyTeam(ens,1);
442}
443
444int TeamFlee() {
445 object team;
446
447 if (Query(P_TEAM_WIMPY_ROW)<2 || !objectp(team=Query(P_TEAM)))
448 return 0;
449 if (!team->FleeToRow(ME))
450 return 0;
451 if (Query(P_TEAM_LEADER)==team) {
452 if (team_autofollow)
453 tell_object(ME,"Du versuchst zu fliehen, "+
454 "Dein Team folgt Dir nicht mehr.\n");
455 team_autofollow=0;
456 }
457 return 1;
458}
459
460varargs mapping PresentTeamPositions(mixed pres_rows) {
461 mapping res;
462 int i,j;
463 object *obs,ob;
464
465 res=([]);
466 if (!pointerp(pres_rows))
467 pres_rows=PresentTeamRows();
468 for (i=0;i<MAX_TEAMROWS;i++) {
469 obs=pres_rows[i];
470 for (j=sizeof(obs)-1;j>=0;j--)
471 if (objectp(ob=obs[j]) && !res[ob])
472 res[ob]=i+1;
473 }
474 return res;
475}
476
477varargs int PresentPosition(mixed pmap) {
478 object team;
479 int i;
480
481 if (!objectp(team=Query(P_TEAM)))
482 return 1;
483 if (mappingp(pmap))
484 return pmap[ME];
485 if (!pointerp(pmap))
486 pmap=team->PresentRows(ENV);
487 for (i=1;i<MAX_TEAMROWS;i++)
488 if (member(pmap[i],ME)>=0)
489 return i+1;
490 return 1;
491}
492
493#define FILLSTRING " "
494varargs private string center_string(string str, int w) {
495 return (FILLSTRING[0..((w-sizeof(str))/2-1)]+str+FILLSTRING)[0..(w-1)];
496}
497
498private int ShowTeamRows() {
499 int i,j,sz;
500 mixed *pres_rows;
501 object *obs,ob;
502 string str;
503
504 pres_rows=PresentEnemyRows();
505 for (sz=MAX_TEAMROWS-1;sz>=0;sz--)
506 if (sizeof(pres_rows[sz]))
507 break;
508 for (i=sz;i>=0;i--) {
509 obs=pres_rows[i];str="";
510 for (j=sizeof(obs)-1;j>=0;j--)
511 if (objectp(ob=obs[j])) {
512 if (str!="") str+=" / ";
513 str+=ob->Name(RAW);
514 }
515 printf("%d. %s\n",i+1,center_string(str,75));
516 }
517 if (sz>=0)
518 write(" ---------------------------------------------------------------------------\n");
519 pres_rows=PresentTeamRows();
520 for (sz=MAX_TEAMROWS-1;sz>0;sz--)
521 if (sizeof(pres_rows[sz]))
522 break;
523 for (i=0;i<=sz;i++) {
524 obs=pres_rows[i];str="";
525 for (j=sizeof(obs)-1;j>=0;j--)
526 if (objectp(ob=obs[j])) {
527 if (str!="") str+=" / ";
528 str+=ob->Name(RAW);
529 }
530 printf("%d. %s\n",i+1,center_string(str,75));
531 }
532 return 1;
533}
534
535varargs int team_list(string arg) {
536 object *tobs,*obs,tob,ob,ld;
537 string *nms,*tnms,str;
538 int i,j;
539
540 if (!pointerp(tobs=TEAM_MASTER->ListTeamObjects())) return 0;
541 if (arg!="alle") arg=0;
542 tnms=({});
543 for (i=sizeof(tobs)-1;i>=0;i--) {
544 if (!objectp(tob=tobs[i])
545 || !objectp(ld=tob->Leader())
546 || (!query_once_interactive(ld) && !arg)
547 || !pointerp(obs=tob->Members()))
548 continue;
549 nms=({});
550 for (j=sizeof(obs)-1;j>=0;j--) {
551 if (!objectp(ob=obs[j])
552 || (!query_once_interactive(ob) &&!arg))
553 continue;
554 if (!stringp(str=ob->Name(WER))) str="?";
555 if (ob==ld) str+="(*)";
556 nms+=({str});
557 nms=sort_array(nms,#'>);
558 }
559 if (!stringp(str=tob->Name())) str="Team ?";
560 str+=": ";
561 tnms+=({break_string(implode(nms,", "),78,str)});
562 tnms=sort_array(tnms,#'<);
563 }
564 if (sizeof(tnms))
565 tell_object(ME, sprintf("%@s\n", tnms));
566 else
567 tell_object(ME, "Keine Teams gefunden.\n");
568
569 return 1;
570}
571
572varargs int teamcmd(string arg) {
573 string *words,narg;
574 object team;
575
576 if (!arg)
577 arg="";
578 if (!stringp(narg=TP->_unparsed_args()))
579 narg = arg;
580 if (!sizeof(words=explode(arg," ")))
581 return 0;
582
583 if (sizeof(words) > 1) {
584 arg=implode(words[1..]," ");
585 narg = implode(explode(narg, " ")[1..], " ");
586 }
587 else
588 arg = narg = "";
589
590 switch(words[0]) { // Befehle die keine Mitgliedschaft erfordern:
591 case "aufnahme":
592 return team_aufnahme(arg);
593 case "folge":
594 return team_aufnahmewunsch(arg);
595 case "?":
596 case "hilfe":
597 return team_help();
598 case "liste":
599 return team_list(arg);
600 case "uebersicht":
601 return ShowTeamRows();
602 default:;
603 }
604
605 if (!objectp(team=QueryProp(P_TEAM)))
606 return notify_fail("Du bist in keinem Team.\n"),0;
607
608 switch(words[0]) {
609 case "angriffsbefehl":
610 if (narg=="") narg=0;
611 team_attack_cmd=narg;
612 if (stringp(narg))
613 write("Du beginnst den Kampf mit \""+narg+"\"\n");
614 else
615 write("Du hast den Teamangriffsbefehl deaktiviert.\n");
616 break; // NICHT return!
617 case "autofolge":
618 case "autof":
619 if (arg=="ein" || arg=="an") {
620 team_autofollow=1;
621 if (IsTeamLeader())
622 write("Dein Team folgt Dir.\n");
623 else
624 write("Du folgst jetzt dem Teamleiter.\n");
625 } else {
626 team_autofollow=0;
627 if (IsTeamLeader())
628 write("Dein Team folgt Dir nicht mehr.\n");
629 else
630 write("Du folgst jetzt nicht mehr dem Teamleiter.\n");
631 }
632 break; // NICHT return!
633 default: ;
634 }
635 return team->TeamCmd(words[0],narg); // Befehle die Mitgliedschaft erfordern:
636}
637
638varargs void InformRowChange(int from, int to, object caster) {
639
640 if (caster) return; // Fuer den Fall, dass Gildenobjekt==ME ist
641 if (PO!=Query(P_TEAM)) return;
642#if __BOOT_TIME__ < 1281904437
643 mixed gilde = QueryProp(P_GUILD);
644 if (!stringp(gilde)) return;
645 if (!objectp(gilde=find_object("/gilden/"+gilde))) return;
646 gilde->InformRowChange(from,to,ME);
647#endif
648 HookFlow(H_HOOK_TEAMROWCHANGE, ({from,to}) );
649}