blob: eec555ccd394ed317c46ed3cf92fe18e44c9b4ff [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);
Bugfixdb37a562022-02-09 11:30:50 +0100371 if(npc->QueryProp(P_TEAM_ASSOC_MEMBERS) == ME)
372 npc->SetProp(P_TEAM_ASSOC_MEMBERS,0);
MG Mud User88f12472016-06-24 23:31:02 +0200373 if (objectp(team=QueryProp(P_TEAM)))
374 team->RemoveAssocMember(ME,npc);
375 return 1;
376}
377
378int AssocMember(object npc) {
379 mixed obs;
380 object team;
381
382 if (extern_call() && PO!=npc &&
383 member(({"gilden","spellbooks"}),
384 explode(object_name(PO),"/")[1])<0)
385 return 0;
386 if (!objectp(npc)
387 || npc->QueryProp(P_TEAM_ASSOC_MEMBERS)
388 || IsEnemy(npc)
389 || npc==ME
390 || query_once_interactive(npc))
391 return 0;
392 obs=QueryProp(P_TEAM_ASSOC_MEMBERS);
393 if (objectp(obs))
394 return 0;
395 if (!pointerp(obs))
396 obs=({});
397 obs=(obs-({npc,0}))+({npc});
398 SetProp(P_TEAM_ASSOC_MEMBERS,obs);
399 npc->SetProp(P_TEAM_ASSOC_MEMBERS,ME);
400 if (objectp(team=QueryProp(P_TEAM)))
401 team->AddAssocMember(ME,npc);
402 return 1;
403}
404
405varargs void InsertEnemyTeam(mixed ens, int rek) {
406 object *obs,ob,eteam,team;
407 int i;
408
409 team=Query(P_TEAM);
410 // Alle Teammitglieder des Gegners sind Feind:
411 if (objectp(ens)) {
412 if (objectp(eteam=ens->QueryProp(P_TEAM))) {
413 if (eteam==team) // feindliches Team = eigenes Team?
414 return; // also nicht alle Teammitglieder gegeneinander hetzen
415 ens=eteam->Members();
416 } else {
417 ens=({ens});
418 }
419 }
420 if (!pointerp(ens))
421 return;
422 ens-=({ME});
423
424 // Interactives sollen keine Interactives durch Team angreifen:
425 if (query_once_interactive(ME)) {
426 for (i=sizeof(ens)-1;i>=0;i--)
427 if (objectp(ob=ens[i]) && environment(ob)==environment()
428 && !query_once_interactive(ob))
429 InsertSingleEnemy(ob);
430 } else {
431 for (i=sizeof(ens)-1;i>=0;i--)
432 if (objectp(ob=ens[i]) && environment(ob)==environment())
433 InsertSingleEnemy(ob);
434 }
435
436 // Alle anderen Teammitglieder Informieren:
437 if (rek || !objectp(team) || !pointerp(obs=team->Members()))
438 return;
439 obs-=({ME});
440 obs-=ens;
441 for (i=sizeof(obs)-1;i>=0;i--)
442 if (objectp(ob=obs[i]))
443 ob->InsertEnemyTeam(ens,1);
444}
445
446int TeamFlee() {
447 object team;
448
449 if (Query(P_TEAM_WIMPY_ROW)<2 || !objectp(team=Query(P_TEAM)))
450 return 0;
451 if (!team->FleeToRow(ME))
452 return 0;
453 if (Query(P_TEAM_LEADER)==team) {
454 if (team_autofollow)
455 tell_object(ME,"Du versuchst zu fliehen, "+
456 "Dein Team folgt Dir nicht mehr.\n");
457 team_autofollow=0;
458 }
459 return 1;
460}
461
462varargs mapping PresentTeamPositions(mixed pres_rows) {
463 mapping res;
464 int i,j;
465 object *obs,ob;
466
467 res=([]);
468 if (!pointerp(pres_rows))
469 pres_rows=PresentTeamRows();
470 for (i=0;i<MAX_TEAMROWS;i++) {
471 obs=pres_rows[i];
472 for (j=sizeof(obs)-1;j>=0;j--)
473 if (objectp(ob=obs[j]) && !res[ob])
474 res[ob]=i+1;
475 }
476 return res;
477}
478
479varargs int PresentPosition(mixed pmap) {
480 object team;
481 int i;
482
483 if (!objectp(team=Query(P_TEAM)))
484 return 1;
485 if (mappingp(pmap))
486 return pmap[ME];
487 if (!pointerp(pmap))
488 pmap=team->PresentRows(ENV);
489 for (i=1;i<MAX_TEAMROWS;i++)
490 if (member(pmap[i],ME)>=0)
491 return i+1;
492 return 1;
493}
494
495#define FILLSTRING " "
496varargs private string center_string(string str, int w) {
497 return (FILLSTRING[0..((w-sizeof(str))/2-1)]+str+FILLSTRING)[0..(w-1)];
498}
499
500private int ShowTeamRows() {
501 int i,j,sz;
502 mixed *pres_rows;
503 object *obs,ob;
504 string str;
505
506 pres_rows=PresentEnemyRows();
507 for (sz=MAX_TEAMROWS-1;sz>=0;sz--)
508 if (sizeof(pres_rows[sz]))
509 break;
510 for (i=sz;i>=0;i--) {
511 obs=pres_rows[i];str="";
512 for (j=sizeof(obs)-1;j>=0;j--)
513 if (objectp(ob=obs[j])) {
514 if (str!="") str+=" / ";
515 str+=ob->Name(RAW);
516 }
517 printf("%d. %s\n",i+1,center_string(str,75));
518 }
519 if (sz>=0)
520 write(" ---------------------------------------------------------------------------\n");
521 pres_rows=PresentTeamRows();
522 for (sz=MAX_TEAMROWS-1;sz>0;sz--)
523 if (sizeof(pres_rows[sz]))
524 break;
525 for (i=0;i<=sz;i++) {
526 obs=pres_rows[i];str="";
527 for (j=sizeof(obs)-1;j>=0;j--)
528 if (objectp(ob=obs[j])) {
529 if (str!="") str+=" / ";
530 str+=ob->Name(RAW);
531 }
532 printf("%d. %s\n",i+1,center_string(str,75));
533 }
534 return 1;
535}
536
537varargs int team_list(string arg) {
538 object *tobs,*obs,tob,ob,ld;
539 string *nms,*tnms,str;
540 int i,j;
541
542 if (!pointerp(tobs=TEAM_MASTER->ListTeamObjects())) return 0;
543 if (arg!="alle") arg=0;
544 tnms=({});
545 for (i=sizeof(tobs)-1;i>=0;i--) {
546 if (!objectp(tob=tobs[i])
547 || !objectp(ld=tob->Leader())
548 || (!query_once_interactive(ld) && !arg)
549 || !pointerp(obs=tob->Members()))
550 continue;
551 nms=({});
552 for (j=sizeof(obs)-1;j>=0;j--) {
553 if (!objectp(ob=obs[j])
554 || (!query_once_interactive(ob) &&!arg))
555 continue;
556 if (!stringp(str=ob->Name(WER))) str="?";
557 if (ob==ld) str+="(*)";
558 nms+=({str});
559 nms=sort_array(nms,#'>);
560 }
561 if (!stringp(str=tob->Name())) str="Team ?";
562 str+=": ";
563 tnms+=({break_string(implode(nms,", "),78,str)});
564 tnms=sort_array(tnms,#'<);
565 }
566 if (sizeof(tnms))
567 tell_object(ME, sprintf("%@s\n", tnms));
568 else
569 tell_object(ME, "Keine Teams gefunden.\n");
570
571 return 1;
572}
573
574varargs int teamcmd(string arg) {
575 string *words,narg;
576 object team;
577
578 if (!arg)
579 arg="";
580 if (!stringp(narg=TP->_unparsed_args()))
581 narg = arg;
582 if (!sizeof(words=explode(arg," ")))
583 return 0;
584
585 if (sizeof(words) > 1) {
586 arg=implode(words[1..]," ");
587 narg = implode(explode(narg, " ")[1..], " ");
588 }
589 else
590 arg = narg = "";
591
592 switch(words[0]) { // Befehle die keine Mitgliedschaft erfordern:
593 case "aufnahme":
594 return team_aufnahme(arg);
595 case "folge":
596 return team_aufnahmewunsch(arg);
597 case "?":
598 case "hilfe":
599 return team_help();
600 case "liste":
601 return team_list(arg);
602 case "uebersicht":
603 return ShowTeamRows();
604 default:;
605 }
606
607 if (!objectp(team=QueryProp(P_TEAM)))
608 return notify_fail("Du bist in keinem Team.\n"),0;
609
610 switch(words[0]) {
611 case "angriffsbefehl":
612 if (narg=="") narg=0;
613 team_attack_cmd=narg;
614 if (stringp(narg))
615 write("Du beginnst den Kampf mit \""+narg+"\"\n");
616 else
617 write("Du hast den Teamangriffsbefehl deaktiviert.\n");
618 break; // NICHT return!
619 case "autofolge":
620 case "autof":
621 if (arg=="ein" || arg=="an") {
622 team_autofollow=1;
623 if (IsTeamLeader())
624 write("Dein Team folgt Dir.\n");
625 else
626 write("Du folgst jetzt dem Teamleiter.\n");
627 } else {
628 team_autofollow=0;
629 if (IsTeamLeader())
630 write("Dein Team folgt Dir nicht mehr.\n");
631 else
632 write("Du folgst jetzt nicht mehr dem Teamleiter.\n");
633 }
634 break; // NICHT return!
635 default: ;
636 }
637 return team->TeamCmd(words[0],narg); // Befehle die Mitgliedschaft erfordern:
638}
639
640varargs void InformRowChange(int from, int to, object caster) {
641
642 if (caster) return; // Fuer den Fall, dass Gildenobjekt==ME ist
643 if (PO!=Query(P_TEAM)) return;
MG Mud User88f12472016-06-24 23:31:02 +0200644 HookFlow(H_HOOK_TEAMROWCHANGE, ({from,to}) );
645}