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