blob: 0319f5e24c63d7bd710dd2381195f6b8ffda6973 [file] [log] [blame]
#pragma strong_types,save_types
#pragma no_shadow
// TODO: Morgoth erbt das Teamobjekt: klaeren warum und wieso eigentlich.
//#pragma no_inherit
#pragma pedantic
#include <living/team.h>
#include <properties.h>
#include <language.h>
#include <new_skills.h>
#include <ansi.h>
#include <wizlevels.h>
#define ME this_object()
#define PO previous_object()
#define TP this_player()
#define TI this_interactive()
#define AUTOINF_HP_MINUS 0x01
#define AUTOINF_HP_PLUS 0x02
#define AUTOINF_SP_MINUS 0x04
#define AUTOINF_SP_PLUS 0x08
#define AUTOINF_INSTANT 0x10
private nosave mapping is_member; // Teammitglieder
private nosave object leader; // Teamleiter
private nosave string tname; // Teamname
private nosave mapping wanted_row; // Gewuenschte Reihe
private nosave mapping wimpy_row; // Fluchtreihe
private nosave mapping act_row; // Aktuelle Reihe
private nosave mapping autofollow; // Spieler folgt Teamleiter
private nosave mapping attack_cmd; // Spieler hat Angriffsbefehl
private nosave mapping assoc_mem; // Zugeordnete Mitglieder (Kampf NPCs)
private nosave int *formin,*formax; // Formation
private nosave mixed *rows; // Die Reihen
private nosave int last_reorder; // Letzte Formationspruefung
private nosave mapping h; // Sortier-Score: 125*G.Reihe-HP
private nosave object *att_exec; // Mitglieder, die Attacke ausfuehren.
private nosave object *mis_attacked;// (Ex-)Mitglieder ohne Begruessungsschlag
private nosave mapping mis_init_att;// Fehlende Begruessungsschlaege
private nosave mapping hp_info; // HP, MAX_HP, SP, MAX_SP
private nosave int autoinf_flags;
private nosave mapping autoinf_hp;
private nosave mapping autoinf_sp;
private nosave int autoinf_time;
private nosave string *hist;
private nosave object debugger;
void _set_debug() {
if (!debugger && IS_LEARNER(TI))
debugger=TI;
else if (debugger==TI)
debugger=0;
}
private void debug(string str) {
if (objectp(debugger) && stringp(str))
tell_object(debugger,"#TEAM: "+str);
}
void create() {
autoinf_flags=0;
autoinf_sp=([]);
autoinf_hp=([]);
is_member=([]);
leader=0;
wanted_row=([]);
act_row=([]);
wimpy_row=([]);
autofollow=([]);
attack_cmd=([]);
assoc_mem=([]);
formin=({1,0,0,0,0});
formax=({5,4,3,2,1});
rows=EMPTY_TEAMARRAY;
h=([]);
att_exec=({});
mis_init_att=([]);
mis_attacked=({});
hp_info=([]);
hist=({});
if (object_name(ME)==TEAM_OBJECT)
return;
if (!stringp(tname=TEAM_MASTER->RegisterTeam()))
tname="?";
}
object *Members() {
return (m_indices(is_member)-({0}));
}
object Leader() {
return leader;
}
varargs string name(int casus, int demon) {
if (!stringp(tname))
return "Team ?";
return "Team "+capitalize(tname);
}
varargs string Name(int casus, int demon) {
return name(casus,demon);
}
varargs int remove(int silent) {
if (mappingp(is_member) && sizeof(Members())) // Nur leere Teams removen
return 0;
TEAM_MASTER->UnregisterTeam(); // Teamnamen freigeben.
destruct(ME);
return 1;
}
private void TryRemove() {
if (clonep(ME)
&& (!mappingp(is_member) || !sizeof(Members()))
&& !first_inventory(ME)
&& find_call_out("remove")<0)
call_out("remove",0);
}
int CmpFirstArrayElement(mixed *a, mixed *b) {
return(a[0]<b[0]);
}
varargs private void gtell(string str, string who, int tohist) {
int i;
object *tmembers,rochus;
string prefix,msg;
tmembers=Members();
prefix=sprintf("[%s:%s] ",name(),stringp(who)?who:"");
msg=break_string(str,78,prefix);
for (i=sizeof(tmembers)-1;i>=0;i--)
tell_object(tmembers[i],msg);
if (objectp(rochus=find_player("rochus"))
&& rochus->QueryProp("debug_team"))
tell_object(rochus,msg);
if (tohist)
hist=(hist+({break_string(str+" <"+ctime()[11..15]+">",78,prefix)}))[-100..];
}
int IsMember(object ob) {
return (objectp(ob) && is_member[ob]);
}
int IsInteractiveMember(object ob) {
return (objectp(ob) && is_member[ob] && query_once_interactive(ob));
}
varargs private int *GetHpInfo(object ob, closure cl) {
int *res;
if (!closurep(cl)) cl=symbol_function("QueryProp",ob);
if (!pointerp(res=hp_info[ob]) || sizeof(res)<4)
res=({0,funcall(cl,P_MAX_HP),0,funcall(cl,P_MAX_SP)});
res[0]=funcall(cl,P_HP);
res[2]=funcall(cl,P_SP);
return hp_info[ob]=res;
}
int CompareHp(object a, object b) {
return h[a]>h[b];
}
// Aktualisiert act_row (->wer steht in welcher Reihe).
private void UpdateActRow() {
int i,j,update_hp;
object *new;
mixed aso;
act_row=([]);
rows[0]+=Members();
update_hp=0;
if (!mappingp(h)) {
h=([]);
update_hp=1;
}
for (i=MAX_TEAMROWS-1;i>=0;i--) {
new=({});
foreach(object ob: rows[i]) {
if (objectp(ob) && is_member[ob] && !act_row[ob]) {
act_row[ob]=i+1;
new+=({ob});
if (update_hp) {
if (!objectp(aso=assoc_mem[ob]) || environment(aso)!=environment(ob))
aso=ob;
h[ob]=
1000*wanted_row[aso]
+40*act_row[aso]
-(query_once_interactive(aso)?8:2)*aso->QueryProp(P_HP)
-query_once_interactive(ob);
// NPCs bekommen fast gleichen Wert wie Caster,
// im Zweifelsfalle steht der Caster weiter vorne...
}
}
}
rows[i]=sort_array(new,"CompareHp",ME);
}
}
private void CheckFormation() {
int i,mincap,maxcap,d,num;
mincap=maxcap=0;
if (formin[0]<1)
formin[0]=1;
// erstmal die Wuensche normalisieren/korrigieren auf sinnvolle Werte.
for (i=0;i<MAX_TEAMROWS;i++) {
if (formin[i]<0) formin[i]=0;
if (formax[i]<formin[i]) formax[i]=formin[i];
if (formax[i]>MAX_TEAM_ROWLEN) formax[i]=MAX_TEAM_ROWLEN;
if (formin[i]>formax[i]) formin[i]=formax[i];
mincap+=formin[i]; // Summe der min. je Reihe gewuenschten.
maxcap+=formax[i]; // Summe der max. je Reihe gewuenschten.
}
num=sizeof(Members());
// max. gewuenschte Reihenlaenge verlaengern, wenn die Summe der maximal je
// Reihe gewuenschten kleiner als die Anzahl der Spieler ist. Von vorne
// natuerlich.
d=num-maxcap;
for (i=0;i<MAX_TEAMROWS;i++) {
if (d<=0)
break;
d-=(MAX_TEAM_ROWLEN-formax[i]);
formax[i]=MAX_TEAM_ROWLEN;
if (d<0)
formax[i]+=d; // doch noch was uebrig, wieder anhaengen.
}
// min. gewuenschte Reihenlaenge auf 0 verkuerzen, wenn die Summe der
// minimal je Reihe gewuenschten groesser als die Anzahl der Spieler ist.
// Von hinten natuerlich.
d=mincap-num; //
for (i=MAX_TEAMROWS-1;i>=0;i--) {
if (d<=0)
break;
d-=formin[i];
formin[i]=0;
if (d<0)
formin[i]-=d; // doch noch was uebrig, wieder anhaengen.
}
}
private void MakeFormation() {
// Verlegungsstrategie:
// Richtung Test Verschieben
// 1. -----> a) MAX <- X
// b) MAX X ->
// c) MIN X <- <- <- <-
// 2. <----- a) MIN -> -> -> -> X
// b) MAX <- X
int i,j,d;
last_reorder=time();
UpdateActRow();
CheckFormation();
for (i=0;i<MAX_TEAMROWS;i++) {
d=sizeof(rows[i]);
if (d<formin[i] || d>formax[i])
break;
}
if (i>=MAX_TEAMROWS)
return; // Formation ist noch in Ordnung
for (i=0;i<MAX_TEAMROWS;i++) {
if (sizeof(rows[i])>formax[i]) { // Reihe ist zu voll
if (i>0) {
d=formax[i-1]-sizeof(rows[i-1]);
if (d>0) { // Reihe vorher hat d freie Plaetze
rows[i-1]+=rows[i][0..(d-1)]; // Also d Mitglieder abgeben
rows[i]=rows[i][d..];
}
}
if (i<MAX_TEAMROWS-1 && sizeof(rows[i])>formax[i]) {// Immer noch zu voll
rows[i+1]=rows[i][formax[i]..]+rows[i+1]; // Rest nach hinten.
rows[i]=rows[i][0..(formax[i]-1)];
}
continue; // War zu voll, kann nicht zu leer sein
}
for (j=i+1;j<MAX_TEAMROWS;j++) {
d=formin[i]-sizeof(rows[i]);
if (d<=0) // Ausreichende Anzahl
break; // kein weiteres j noetig
rows[i]+=rows[j][0..(d-1)]; // Sonst Nachschub von hinten holen
rows[j]=rows[j][d..];
}
}
for (i=MAX_TEAMROWS-1;i>0;i--) {
for (j=i-1;j>=0;j--) {
d=formin[i]-sizeof(rows[i]);
if (d<=0) // Ausreichende Anzahl
break; // kein weiteres j noetig
rows[i]+=rows[j][0..(d-1)]; // Sonst Nachschub von vorne holen
rows[j]=rows[j][d..];
}
d=sizeof(rows[i])-formax[i];
if (d>0) {
rows[i-1]+=rows[i][0..(d-1)]; // Ueberschuss nach vorne schieben
rows[i]=rows[i][d..];
}
}
UpdateActRow();
}
private void RemoveFromRow(object ob, int src) {
if (src<0 || src>=MAX_TEAMROWS)
return;
rows[src]-=({ob});
if (sizeof(rows[src])>=formin[src])
return;
// Falls hinten noch Ueberschuss da ist, her damit.
if (src<MAX_TEAMROWS-1 && sizeof(rows[src+1])-1>=formin[src+1]) {
rows[src]+=rows[src+1][0..0];
rows[src+1]=rows[src+1][1..];
return;
}
// Falls vorne noch Ueberschuss da ist, her damit.
if (src>0 && sizeof(rows[src-1])-1>=formin[src-1]) {
rows[src]=rows[src-1][<1..]+rows[src];
rows[src-1]=rows[src-1][0..<2];
}
}
private void AddToRow(object ob, int dest) {
if (dest<0 || dest>=MAX_TEAMROWS)
return;
rows[dest]+=({ob});
if (sizeof(rows[dest])<=formax[dest])
return;
// Falls vorne noch jemand hin kann, dorthin
if (dest>0 && sizeof(rows[dest-1])+1<=formax[dest-1]) {
rows[dest-1]+=rows[dest][0..];
rows[dest]=rows[dest][1..];
return;
}
// Falls hinten noch jemand hin kann, dorthin
if (dest<MAX_TEAMROWS-1 && sizeof(rows[dest+1])+1<=formax[dest+1]) {
// Dest: ({... <3, <2, ob});
rows[dest+1]=rows[dest][<2..<2]+rows[dest+1];
rows[dest]=rows[dest][0..<3]+({ob});
}
}
private void CycleRows(object ob, int src, int dest) {
int i;
if (src<0 || src>=MAX_TEAMROWS || dest<0 || dest>=MAX_TEAMROWS)
return;
rows[src]-=({ob});
if (sizeof(rows[src])<formin[src] || sizeof(rows[dest])>=formax[dest]) {
if (src<dest) { // (<- -X) <- <- <- <- (+X <-)
for (i=src+1;i<=dest;i++) {
rows[i-1]+=rows[i][0..0];
rows[i]=rows[i][1..];
}
} else if (src>dest) { // (-> +X) -> -> -> -> (-X ->)
for (i=src-1;i>=dest;i--) {
rows[i+1]=rows[i][<1..]+rows[i+1];
rows[i]=rows[i][0..<2];
}
}
}
if (src<=dest)
rows[dest]+=({ob});
else
rows[dest]=({ob})+rows[dest];
}
// tauscht zufaellig aus den ersten 4 Reihen aus einer Reihe den letzten mit
// dem ersten aus der Folgereihe, wenn die der Score des vorderen Spieler mehr
// als 10 groesser als der des hinteren Spielers ist.
private void RandomChangeRow() {
int *nums,i;
object p1,p2;
if (!mappingp(h))
UpdateActRow();
for (nums=({0,1,2,3});sizeof(nums);nums-=({i})) {
i=nums[random(sizeof(nums))];
if (!sizeof(rows[i]) || !sizeof(rows[i+1])) continue;
if (!objectp(p1=rows[i][<1]) || !objectp(p2=rows[i+1][0])) continue;
if (wanted_row[p1]<wanted_row[p2]) continue;
if (h[p2]-h[p1]>=-10) continue;
rows[i][<1]=p2;
rows[i+1][0]=p1;
return;
}
}
varargs private string
CountUpNames(object *obs, string zsing, string zplur) {
string res;
object ob;
int i,sz;
res="";
if (!pointerp(obs)) return res;
if (!stringp(zsing)) zsing="";
if (!stringp(zplur)) zplur="";
if (sz=sizeof(obs)) {
for (i=0;i<sz;i++) {
if (i)
res+=((i<sz-1)?", ":" und ");
if (objectp(ob=obs[i]))
res+=ob->name(WER);
}
if (sz>1)
res+=zplur;
else
res+=zsing;
}
return res;
}
static void DoChangeRow(object pl, int dest) {
mapping old_row,ec1,ec2,ecb;
int i;
object *obs,*envs,env;
string *msg,str;
dest--;
CheckFormation();
h=0; // damit HP-Liste geupdated wird.
UpdateActRow();
old_row=deep_copy(act_row);
// welche Objekte bewegen?
obs=({});
if (objectp(pl)) {
obs=({pl});
if (pointerp(assoc_mem[pl]))
obs+=assoc_mem[pl];
} else {
RandomChangeRow();
}
foreach (object ob:obs) {
if (!objectp(ob))
continue;
// Alle assoziierten NPC kriegen die gleiche gewuenschte Reihe wie der
// Spieler.
wanted_row[ob]=wanted_row[pl];
UpdateActRow();
// und dann in die gew. Reihe stopfen.
int src=act_row[ob]-1;
if (dest<0 || dest>=MAX_TEAMROWS)
RemoveFromRow(ob,src);
else if (src<0 || src>=MAX_TEAMROWS)
AddToRow(ob,dest);
else if (src!=dest)
CycleRows(ob,src,dest);
}
MakeFormation();
obs = Members(); // alle Members beruecksichtigen beim Abgleich!
object *changed = allocate(0);
foreach (object ob:obs)
if (objectp(ob) && old_row[ob]!=act_row[ob]) {
ob->InformRowChange(old_row[ob],act_row[ob]);
changed += ({ob});
}
// Ab jetzt nur noch Ausgabe.
if (get_eval_cost()<800000) return; // War schon teuer genug, Lagvermeidung
msg=({});ec1=([]);ec2=([]);ecb=([]);
foreach (object ob:changed) {
tell_object(ob,sprintf("Du bist jetzt in Reihe %d.\n",act_row[ob]));
msg+=({sprintf("%s->%d",ob->Name(WER),act_row[ob])});
if (query_once_interactive(ob) && !interactive(ob)) continue;
if (!objectp(env=environment(ob))) continue;
if (old_row[ob]<=1) {
if (!pointerp(envs=ec1[env])) envs=({});
ec1[env]=envs+({ob});ecb[env]|=1;
}
if (act_row[ob]<=1) {
if (!pointerp(envs=ec2[env])) envs=({});
ec2[env]=envs+({ob});ecb[env]|=2;
}
}
if (sizeof(msg))
gtell(implode(msg,", ")); // Ausgabe an alle Gruppenmitglieder.
m_delete(ecb,find_object("/room/netztot")); // Das gaebe Mega-Lag :-)
envs=m_indices(ecb);obs=Members();
for (i=sizeof(envs)-1;i>=0;i--) {
if (!objectp(env=envs[i])) continue;
str="";
str+=CountUpNames(ec1[env]," tritt zurueck"," treten zurueck");
if (ecb[env]==3) str+=", ";
str+=CountUpNames(ec2[env]," tritt vor"," treten vor");
str+=".\n";
tell_room(env,capitalize(break_string(str,78)),obs);
}
}
void UpdateFormation() {
DoChangeRow(0,0);
}
int SwapRows(object ob1, object ob2) {
int i,r1,r2,p1,p2;
if (!objectp(ob1) || !objectp(ob2) || ob1==ob2)
return 0;
r1=r2=-1;
for (i=0;i<MAX_TEAMROWS;i++) {
if (r1==-1 && (p1=member(rows[i],ob1))>=0)
r1=i;
if (r2==-1 && (p2=member(rows[i],ob2))>=0)
r2=i;
}
if (r1==-1 || r2==-1)
return 0;
if (r1==r2)
return 1;
if (r1<r2) { // Nicht Monster vor Spieler stellen
if (query_once_interactive(ob1) && !interactive(ob2))
return 0;
} else {
if (query_once_interactive(ob2) && !interactive(ob1))
return 0;
}
rows[r1][p1]=ob2;ob2->InformRowChange(r2,r1);
rows[r2][p2]=ob1;ob1->InformRowChange(r1,r2);
gtell(ob1->Name(WER)+" und "+ob2->name(WER)+" tauschen die Plaetze.\n");
return 1;
}
private int ChangeRow(string arg) {
int num;
if (!arg || sscanf(arg,"%d",num)!=1)
return notify_fail("In welche Reihe willst Du denn wechseln?\n"),0;
if (num<1 || num>MAX_TEAMROWS)
return notify_fail("Die Reihenangabe ist ungueltig.\n"),0;
TP->SetProp(P_TEAM_WANTED_ROW,wanted_row[TP]=num);
printf("Du versuchst in Reihe %d zu wechseln.\n",num);
DoChangeRow(TP,num);
return 1;
}
private int ChangeWimpyRow(string arg) {
int num;
if (!arg || sscanf(arg,"%d",num)!=1)
return notify_fail("In welche Reihe willst Du fliehen?\n"),0;
if (num<0 || num>MAX_TEAMROWS)
return notify_fail("Die Reihenangabe ist ungueltig.\n"),0;
TP->SetProp(P_TEAM_WIMPY_ROW,wimpy_row[TP]=num);
if (num>1)
printf("Bei der Flucht wirst Du in Reihe %d wechseln.\n",num);
else
write("Bei der Flucht wirst Du den Raum verlassen.\n");
return 1;
}
mixed *PresentRows(object env) {
int i,j,d,arbeit;
mixed *res;
object *nd,ob;
if (!objectp(env))
env=environment(TP);
if (last_reorder!=time() || !mappingp(h))
UpdateFormation();
res=EMPTY_TEAMARRAY;arbeit=0;nd=({});
for (i=0;i<MAX_TEAMROWS;i++) {
object *new=({});
foreach(ob: rows[i]) {
if (objectp(ob) && is_member[ob] && environment(ob)==env) {
if (query_once_interactive(ob) && !interactive(ob)) {
nd+=({ob});
arbeit=1;
} else {
new+=({ob});
}
} else {
arbeit=1;
}
}
res[i]=new;
}
if (!arbeit)
return res;
for (i=j=0;i<MAX_TEAMROWS;i++) {
if (j<=i) j=i+1;
for (;j<MAX_TEAMROWS;j++) {
d=formin[i]-sizeof(res[i]);
if (d<=0) // Ausreichende Anzahl
break; // kein weiteres j noetig
res[i]+=res[j][0..(d-1)]; // Sonst Nachschub von hinten holen
res[j]=res[j][d..];
}
}
res[MAX_TEAMROWS-1]+=nd; // Netztote bieten keine Deckung, nach hinten.
return res;
}
mapping PresentPositions(mixed pres_rows) {
mapping res=([]);
if (objectp(pres_rows))
pres_rows=PresentRows(pres_rows);
if (!pointerp(pres_rows))
return res;
for (int i=0;i<MAX_TEAMROWS;i++) {
foreach(object ob: pres_rows[i])
if (ob)
res[ob]=i+1;
}
return res;
}
varargs int FleeToRow(object ob) {
int num;
if (!objectp(ob))
ob=TP;
if (!IsMember(ob))
return 0;
h=0; // Reihen bei naechster Abfrage neu sortieren
num=wimpy_row[ob];
if (num<2 || num>MAX_TEAMROWS) // Flucht in 1. Reihe nicht sinnvoll.
return 0;
if (num==wanted_row[ob]) // Ist schonmal nach hinten geflohen.
return 0;
tell_object(ob,sprintf("Du versuchst in Reihe %d zu fliehen.\n",num));
ob->SetProp(P_TEAM_WANTED_ROW,wanted_row[ob]=num);
DoChangeRow(ob,num);
if (PresentPositions(environment(ob))[ob]<=1) // Flucht gescheitert?
return 0;
return 1;
}
static int ChangeFormation(string arg) {
string *words;
int i,min,max;
mapping old_row;
if (arg=="aus")
arg="1-6 0-6 0-6 0-6 0-6";
i=sizeof(words=old_explode(arg," "));
if (i>MAX_TEAMROWS)
i=MAX_TEAMROWS;
for (--i;i>=0;i--) {
if (sscanf(words[i],"%d-%d",min,max)==2) {
formin[i]=min;
formax[i]=max;
} else if (sscanf(words[i],"%d",min)==1) {
formin[i]=formax[i]=min;
}
}
UpdateFormation();
words=({});
for (i=0;i<MAX_TEAMROWS;i++)
words+=({sprintf("%d-%d",formin[i],formax[i])});
gtell("Die Formation ist jetzt "+implode(words," / ")+".\n");
return 1;
}
int Shout(string str) {
if (!str || str=="")
return notify_fail("Was willst Du den anderen Teammitgliedern sagen?\n"),0;
gtell(str,TP->Name(WER),1);
return 1;
}
int Hist(string str) {
int i,anz,maximal;
// non-interactive oder Nicht-Mitglieder sollten die Hist nicht abfragen.
if (!IsInteractiveMember(TP)) return -1;
maximal=sizeof(hist);
if (str && sscanf(str,"%d",anz)==1)
i=maximal-anz;
if (i<0)
i=0;
TP->More(sprintf("%@s",hist[i..maximal])||"");
return 1;
}
private void DoChangeLeader(object ob) {
if (objectp(leader) && leader->QueryProp(P_TEAM_LEADER)==ME)
leader->SetProp(P_TEAM_LEADER,0);
leader=ob;
leader->SetProp(P_TEAM_LEADER,ME);
}
private int ChangeLeader(string arg) {
object ob;
if (stringp(arg) && arg!="") {
if (!objectp(ob=find_player(arg))
&& !objectp(ob=present(arg,environment(TP))))
return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
} else {
ob=TP;
}
if (objectp(leader)
&& TP!=leader
&& (!interactive(TP) || (interactive(leader) && query_idle(leader)<180)))
return notify_fail("Der Teamleiter ist noch aktiv.\n"),0;
if (objectp(leader)
&& query_once_interactive(leader)
&& !query_once_interactive(leader))
return notify_fail("Nur ein Spieler kann das Team leiten.\n"),0;
if (!IsMember(ob))
return notify_fail(ob->Name(WER)+" ist kein Teammitglied.\n"),0;
DoChangeLeader(ob);
gtell(ob->Name(WER)+" leitet jetzt das Team.\n");
return 1;
}
varargs private int CheckSecond(object pl, object *obs) {
mixed x,ip;
mapping second;
object ob;
int i;
if (!objectp(pl))
pl=TP;
if (!query_once_interactive(pl))
return 0;
if (!pointerp(obs))
obs=Members();
obs-=({pl});
second=([]);
for (i=sizeof(obs)-1;i>=0;i--) {
if (!objectp(ob=obs[i]) || !query_once_interactive(ob)) continue;
second[getuid(ob)]=1;
if (stringp(x=ob->QueryProp(P_SECOND)))
second[lower_case(x)]=1;
}
if (second[getuid(pl)] ||
(stringp(x=pl->QueryProp(P_SECOND)) && second[lower_case(x)]))
return 1;
if (!stringp(ip=pl->QueryProp(P_CALLED_FROM_IP)) || ip=="")
return 0;
for (i=sizeof(obs)-1;i>=0;i--) {
if (!objectp(ob=obs[i]) || !query_once_interactive(ob)) continue;
if (ob->QueryProp(P_CALLED_FROM_IP)!=ip) continue;
log_file("rochus/zweitieverdacht",
sprintf("%s %s,%s\n",ctime()[4..15],getuid(pl),getuid(ob)));
break;
}
return 0;
}
static int Allowed(object pl) {
mixed x;
string nm;
if (!objectp(pl))
return notify_fail("WER soll ins Team aufgenommen werden?\n"),0;
if (pl==TP)
nm="Du b";
else
nm=pl->Name(WER)+" ";
if (objectp(x=pl->QueryProp(P_TEAM)) && x!=ME)
return notify_fail(nm+"ist schon in einem anderen Team.\n"),0;
if (pl->QueryGuest())
return notify_fail(nm+"ist ein Gast.\n"),0;
if (pl->QueryProp(P_GHOST))
return notify_fail(nm+"ist ein Geist.\n"),0;
if (CheckSecond(pl))
return notify_fail(nm+"ist Zweitspieler eines Teammitglieds.\n"),0;
if (sizeof(filter(Members(),"IsInteractiveMember",ME))
>=MAX_TEAM_MEMBERS)
return notify_fail("Es sind schon zuviele Spieler im Team.\n"),0;
return 1;
}
private void DoAddMember(object ob) {
closure cl;
if (!IsMember(leader))
DoChangeLeader(ob);
ob->SetProp(P_TEAM,ME);
if (IsMember(ob))
return;
is_member[ob]=1;
cl=symbol_function("QueryProp",ob);
attack_cmd[ob]=funcall(cl,P_TEAM_ATTACK_CMD);
autofollow[ob]=funcall(cl,P_TEAM_AUTOFOLLOW);
wimpy_row[ob]=funcall(cl,P_TEAM_WIMPY_ROW);
wanted_row[ob]=funcall(cl,P_TEAM_WANTED_ROW);
if (!wanted_row[ob]) wanted_row[ob]=1;;
ob->SetProp(P_TEAM_NEWMEMBER,0);
GetHpInfo(ob,cl);
if (query_once_interactive(ob)) ob->AddHpHook(ME);
if (!objectp(assoc_mem[ob]))
gtell(ob->Name(WER)+" wurde ins Team aufgenommen.\n");
DoChangeRow(ob,wanted_row[ob]);
}
int AddAssocMember(object caster, object npc) {
object *obs;
if (extern_call() && PO!=caster)
return 0;
if (!IsMember(caster)
|| !objectp(npc)
|| query_once_interactive(npc)
|| IsMember(npc)
|| objectp(assoc_mem[caster])
|| assoc_mem[npc]
|| caster==npc)
return 0;
assoc_mem[npc]=caster;
DoAddMember(npc);
if (!pointerp(obs=assoc_mem[caster]))
obs=({});
obs=(obs-({npc,0}))+({npc});
assoc_mem[caster]=obs;
return 1;
}
static void AddMemberAndAssocs(object caster) {
object *obs,ob;
int i;
DoAddMember(caster);
if (!pointerp(obs=caster->QueryProp(P_TEAM_ASSOC_MEMBERS)))
return;
for (i=sizeof(obs)-1;i>=0;i--)
if (objectp(ob=obs[i]) && !caster->IsEnemy(ob))
AddAssocMember(caster,ob);
}
int AddMember(object ob) {
if (!Allowed(ob))
return 0;
// Wenn es einen TP gibt, unterliegt er einigen Einschraenkungen.
// Dafuer wird er aber via Aufnahme ins Team auch ggf. Teamleiter, wenn
// noetig.
// TODO: Dieser Code geht davon aus, dass er nur aus einem Kommando
// ausgefuehrt wird und TP der Veranlasser der Aufnahme ist. Dies scheint
// mir unrealistisch. (Zesstra)
if (TP && TP==previous_object()) {
if (!Allowed(TP))
return 0;
if (TP!=ob && CheckSecond(ob,({TP}))) {// Zweitiereglung bei Gruendung.
notify_fail(ob->Name(WER)+" ist Zweitspieler von Dir.\n");
return 0;
}
if (!IsMember(leader))
AddMemberAndAssocs(TP);
if (TP!=leader) {
notify_fail("Nur der Teamleiter kann Mitglieder aufnehmen.\n");
return 0;
}
}
AddMemberAndAssocs(ob);
return 1;
}
private void DoRemoveMember(object ob) {
object *tmembers,caster;
mixed asmem;
int i;
ob->SetProp(P_TEAM,0);
m_delete(is_member,ob);
m_delete(wanted_row,ob);
m_delete(act_row,ob);
m_delete(wimpy_row,ob);
m_delete(autofollow,ob);
m_delete(attack_cmd,ob);
if (objectp(caster=assoc_mem[ob]) && pointerp(asmem=assoc_mem[caster]))
assoc_mem[caster]=asmem-({ob,0});
if (query_once_interactive(ob)) ob->RemoveHpHook(ME);
m_delete(hp_info,ob);
m_delete(autoinf_hp,ob);
m_delete(autoinf_sp,ob);
DoChangeRow(ob,-1);
if (!objectp(assoc_mem[ob])) {
if (ob->QueryProp(P_GHOST)) {
gtell(upper_case(ob->name(WER))+" HAT DAS TEAM VERLASSEN.","Tod");
} else {
tell_object(ob,"Du verlaesst das Team.\n");
gtell(ob->Name(WER)+" hat das Team verlassen.\n");
}
}
m_delete(assoc_mem,ob);
if (IsMember(leader)) // Hat das Team noch einen Leiter?
return;
tmembers=Members();ob=0;
if (i=sizeof(tmembers)) {
ob=tmembers[0];
for (--i;i>=0;i--) {
if (interactive(tmembers[i])) {
ob=tmembers[i];
break;
}
if (query_once_interactive(tmembers[i]))
ob==tmembers[i];
}
DoChangeLeader(ob);
gtell(leader->Name(WER)+" hat die Teamleitung uebernommen.\n");
return;
}
TryRemove();
}
int RemoveAssocMember(object caster, object npc) {
object *obs;
if (extern_call() && PO!=caster)
return 0;
if (!IsMember(caster) || assoc_mem[npc]!=caster)
return 0;
DoRemoveMember(npc);
return 1;
}
static void RemoveMemberAndAssocs(object caster) {
object *obs,ob;
int i;
if (pointerp(obs=caster->QueryProp(P_TEAM_ASSOC_MEMBERS))) {
for (i=sizeof(obs)-1;i>=0;i--)
if (objectp(ob=obs[i]))
RemoveAssocMember(caster,ob);
}
DoRemoveMember(caster);
}
private void RemoveSingles() {
object *obs;
mixed aso;
if (!IsMember(leader)) return;
if (!query_once_interactive(leader)) return; // NPC Team
obs=Members()-({leader});
if (pointerp(aso=assoc_mem[leader]))
obs-=aso;
if (sizeof(obs)) return;
gtell("Das Team loest sich auf.\n");
RemoveMemberAndAssocs(leader);
TryRemove();
}
int RemoveMember(mixed arg) {
object *mems,mem,ob;
int i;
if (objectp(arg)) {
ob=arg;
} else if (stringp(arg) && arg!="") {
mems=Members()-({leader});
for (i=sizeof(mems)-1;i>=0;i--) {
if (objectp(mem=mems[i]) && mem->id(arg)) {
ob=mem;
if (query_once_interactive(ob))
break;
}
}
if (!objectp(ob))
return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
} else {
return 0;
}
if (TP!=leader && TP!=ob && ob!=PO)
return notify_fail("Nur der Teamleiter kann Mitglieder entlassen.\n"),0;
if (!IsMember(ob) && ob->QueryProp(P_TEAM)!=ME)
return notify_fail(ob->Name(WER)+" ist kein Teammitglied.\n"),0;
if (PO!=ob && objectp(assoc_mem[ob]))
return notify_fail(ob->Name(WER)+" gehoert zu "+
assoc_mem[ob]->Name(WEM)+".\n"),0;
RemoveMemberAndAssocs(ob);
RemoveSingles();
return 1;
}
static int ChangeName(string str) {
if (leader && TP!=leader)
return notify_fail("Nur der Teamleiter kann den Namen aendern.\n"),0;
if (!str || str=="")
return 0;
str=str[0..19];
if (!stringp(str=(TEAM_MASTER->RegisterTeam(str))))
return notify_fail("Der Name ist schon vergeben.\n"),0;
tname=str;
if (objectp(TP))
gtell(TP->Name(WER)+" aendert den Teamnamen auf \""+tname+"\".\n");
return 1;
}
void TeamInitAttack() {
object *obs,*ens,*removed,ob,en;
int i,j;
debug(sprintf("mis_init_att: %O\n",mis_init_att));
ens=m_indices(mis_init_att);removed=({});
for (i=sizeof(ens)-1;i>=0;i--) {
if (!objectp(en=ens[i]))
continue;
if (!pointerp(obs=mis_init_att[en]))
continue;
for (j=sizeof(obs)-1;j>=0;j--)
if (!IsMember(ob=obs[j]) || environment(ob)!=environment(en))
obs[j]=0;
obs-=({0});
ob=en->SelectNearEnemy(obs,1);
debug(sprintf("Begruessungsschlag von %O fuer %O\n",en,ob));
if (!objectp(ob))
continue;
en->Attack2(ob); // Begruessungsschlag
removed+=({en}); // Kein Begruessungsschlag mehr von diesem Monster
}
for (i=sizeof(mis_attacked)-1;i>=0;i--)
if (objectp(ob=mis_attacked[i]))
ob->ExecuteMissingAttacks(removed);
// Begruessungsschlaege die ausgefuehrt wurden entfernen
// und nicht ausgefuehrte nachholen
mis_attacked=({});
mis_init_att=([]);
att_exec=({});
}
int InitAttack_Callback(object enemy) {
object *arr;
if (!IsMember(PO) || member(att_exec,PO)<0)
return 0;
if (!pointerp(arr=mis_init_att[enemy]))
arr=({});
mis_init_att[enemy]=arr+({PO});
if (member(mis_attacked,PO)<0)
mis_attacked+=({PO});
return 1;
}
void TeamAttackExecuted_Callback(int success) {
if (!IsMember(PO))
return;
att_exec-=({PO,0});
if (!sizeof(att_exec))
TeamInitAttack();
}
private int StartAttack() {
object *tmembers,ob,env;
int i;
if (TP!=leader || !objectp(env=environment(TP)))
return notify_fail("Nur der Teamleiter kann den Angriff starten.\n"),0;
tmembers=Members();
TeamInitAttack(); // Falls noch Schlaege fehlen....
for (i=sizeof(tmembers)-1;i>=0;i--)
if (objectp(ob=tmembers[i]) && stringp(attack_cmd[ob]))
if (ob->CallTeamAttack(env)) // Angriff wird ausgefuehrt?
att_exec+=({ob}); // Liste der Angreifer
gtell(TP->Name(WER)+" startet den Angriff.\n");
return 1;
}
void StartFollow(object env) {
object *tmembers,ob;
int i;
string cmd,args;
// printf("ld:%O PO:%O TP:%O qv:%O env:%O\n",leader,PO,TP,query_verb(),env);
if (TP!=leader
|| !stringp(cmd=query_verb())
|| !objectp(env)
|| TP!=PO
|| TP->QueryProp(P_GHOST) // Der Befehl war wohl ungesund...
|| TP->IsTeamMove()) // Angriffsbefehl nicht durch verfolge 2 mal...
return;
cmd="\\"+cmd;
if (stringp(args=TP->_unparsed_args()) && args!="")
cmd+=(" "+args);
tmembers=Members()-({leader});
for (i=sizeof(tmembers)-1;i>=0;i--)
if (objectp(ob=tmembers[i]) && autofollow[ob])
if(!query_once_interactive(ob) || env==environment(ob)) // autofolge nur bei anfuehrer im gleichen raum
ob->CallTeamFollow(env,cmd);
}
void ShowTeamInfo() {
int i;
object *tmembers,ob;
string form;
if (!TI || TP!=TI || !IS_LEARNER(TI))
return;
UpdateActRow();
form="";
for (i=0;i<MAX_TEAMROWS;i++)
form+=sprintf(" %d-%d",formin[i],formax[i]);
printf("%s [%O] L: %s F:%s\n",name(),ME,
(objectp(leader)?leader->Name(WER):"?"),form);
tmembers=Members();
for (i=sizeof(tmembers)-1;i>=0;i--) {
ob=tmembers[i];
printf(" %d(%d) %s [%O]\n",
act_row[ob],wanted_row[ob],ob->Name(WER),ob);
}
}
varargs int ShowTeamHP(string arg) {
object *tmembers,ob;
closure qp;
int i,longinf;
mixed *vals,*res,cols,fr,rr,termstr;
mapping real_row;
string nm,inf;
if (arg && arg[0..3]=="lang") {
if (TP!=leader)
return notify_fail("Nur der Teamleiter kann die lange "+
"Uebersicht aufrufen.\n"),0;
longinf=1;
arg=arg[5..];
} else
longinf=0;
real_row=PresentPositions(environment(TP));
tmembers=({});
for (i=0;i<MAX_TEAMROWS;i++)
tmembers+=rows[i];
res=({});
switch(TP->QueryProp(P_TTY)) {
case "ansi":
termstr=({ANSI_RED+ANSI_BOLD,ANSI_YELLOW,ANSI_GREEN,ANSI_NORMAL,
ANSI_UNDERL});
break;
case "vt100":
termstr=({ANSI_BOLD,"","",ANSI_NORMAL,ANSI_UNDERL});
break;
default:
termstr=({"","","","",""});
}
vals=({"",0,0,termstr[3],"",0,0,termstr[3]});
printf(" Name Gilde LV GLV LP (MLP) KP (MKP) Vors. GR AR TR FR A V\n");
if (longinf)
printf(" (Zeile 2) Angriffsbefehl Fluchtrichtung\n");
for (i=sizeof(tmembers)-1;i>=0;i--) {
if (!objectp(ob=tmembers[i])) continue;
qp=symbol_function("QueryProp",ob);
fr=GetHpInfo(ob,qp);
vals[1]=fr[0];vals[2]=fr[1];vals[5]=fr[2];vals[6]=fr[3];
/*
vals[1]=funcall(qp,P_HP);vals[2]=funcall(qp,P_MAX_HP);
vals[5]=funcall(qp,P_SP);vals[6]=funcall(qp,P_MAX_SP);
*/
if (!pointerp(cols=funcall(qp,P_TEAM_COLORS)) || sizeof(cols)<4)
cols=({vals[2]/4,vals[2]/2,vals[6]/4,vals[6]/2});
if (vals[1]<cols[0])
vals[0]=termstr[0];
else if (vals[1]<cols[1])
vals[0]=termstr[1];
else
vals[0]=termstr[2];
if (vals[5]<cols[2])
vals[4]=termstr[0];
else if (vals[5]<cols[3])
vals[4]=termstr[1];
else
vals[4]=termstr[2];
if (intp(fr=wimpy_row[ob]) && fr>1) fr=sprintf("%2d",fr); else fr="--";
if (intp(rr=real_row[ob]) && rr>0) rr=sprintf("%2d",rr); else rr="--";
nm=ob->Name(RAW);
inf=sprintf("%s %-11s%s %-14s %3d %3d %s%3d (%3d)%s %s%3d (%3d)%s %5d %2d %2d %2s %2s %1s %1s\n",
((ob==leader)?(termstr[4]+"*"):" "),
nm[0..10],termstr[3],
capitalize(funcall(qp,P_GUILD)||"")[0..13],
funcall(qp,P_LEVEL),
funcall(qp,P_GUILD_LEVEL),
vals[0],vals[1],vals[2],vals[3],
vals[4],vals[5],vals[6],vals[7],
funcall(qp,P_WIMPY),
wanted_row[ob],act_row[ob],rr,fr,
(attack_cmd[ob]?"X":"-"),
(autofollow[ob]?"X":"-"));
if (longinf) {
if (!stringp(fr=funcall(qp,P_WIMPY_DIRECTION))) fr="";
if (!stringp(rr=attack_cmd[ob])) rr="";
if (fr!="" || rr!="")
inf+=sprintf(" %-42s %-21s\n",rr[0..41],fr[0..20]);
}
switch (arg) {
case "alphabetisch":
res+=({({(query_once_interactive(ob)?"0":"1")+nm,inf})});
break;
case "sortiert":
res+=({({sprintf("%2s %2d %2d %s",rr,act_row[ob],wanted_row[ob],nm),
inf})});
break;
default:
res+=({({i,inf})});
}
}
if (arg && arg!="")
res=sort_array(res,"CmpFirstArrayElement",ME);
for (i=sizeof(res)-1;i>=0;i--)
write(res[i][1]);
return 1;
}
varargs int ShowTeamRooms(string arg) {
object *tmembers,ob;
string s1,s2;
mixed *res;
int i;
tmembers=Members();res=({});
for (i=sizeof(tmembers)-1;i>=0;i--) {
if (!objectp(ob=tmembers[i])) continue;
if (!query_once_interactive(ob) && arg!="alle") continue;
s1=ob->Name(RAW);
if (!objectp(ob=environment(ob))) continue;
if (!stringp(s2=ob->QueryProp(P_INT_SHORT))) s2="";
res+=({({s1,s2})});
}
res=sort_array(res,"CmpFirstArrayElement",ME);
for (i=sizeof(res)-1;i>=0;i--)
printf("%-11s %-66s\n",res[i][0][0..10],res[i][1][0..65]);
return 1;
}
int ChangeColors(string arg) {
int *col;
notify_fail("team farben lp_rot lp_gelb [kp_rot kp_gelb]\n");
if (!arg)
return 0;
col=({0,0,0,0});
if (sscanf(arg,"%d %d %d %d",col[0],col[1],col[2],col[3])!=4) {
if (sscanf(arg,"%d %d",col[0],col[1])!=2)
return 0;
col[2]=col[0];col[3]=col[1];
}
printf("Die Anzeige ist jetzt gelb unter %d LP, rot unter %d LP\n"+
" bzw. gelb unter %d KP, rot unter %d KP.\n",
col[1],col[0],col[3],col[2]);
TP->SetProp(P_TEAM_COLORS,col);
TP->Set(P_TEAM_COLORS,SAVE,F_MODE_AS);
return 1;
}
int ChangeAutoInfo(string arg) {
string *words,txt;
int i,fl;
if (TP!=leader)
return notify_fail("Nur der Teamleiter kann automatisch "+
"informiert werden.\n"),0;
words=old_explode(arg?arg:""," ");fl=0;
for (i=sizeof(words)-1;i>=0;i--) {
switch(words[i]) {
case "aus":
write("Du wirst nicht mehr automatisch informiert.\n");
autoinf_flags=0;
return 1;
case "+kp":
fl|=AUTOINF_SP_PLUS;
case "kp":
fl|=AUTOINF_SP_MINUS;
break;
case "+lp":
fl|=AUTOINF_HP_PLUS;
case "lp":
case "ein":
case "an":
fl|=AUTOINF_HP_MINUS;
break;
case "sofort":
fl|=AUTOINF_INSTANT;
break;
default:
;
}
}
if (!fl)
return notify_fail("WIE moechtest Du automatisch informiert werden?\n"),0;
if (fl==AUTOINF_INSTANT) fl|=AUTOINF_HP_MINUS;
autoinf_flags=fl;
txt="Du wirst"+((fl&AUTOINF_INSTANT)?" sofort ":" ")+"informiert, wenn";
if (fl&(AUTOINF_HP_PLUS|AUTOINF_HP_MINUS)) {
txt+=" die Lebenspunkte eines Teammitglieds";
if (fl&(AUTOINF_HP_PLUS)) txt+=" sich aendern"; else txt+=" fallen";
if (fl&(AUTOINF_SP_PLUS|AUTOINF_SP_MINUS)) txt+=" oder";
}
if (fl&(AUTOINF_SP_PLUS|AUTOINF_SP_MINUS)) {
txt+=" die Konzentrationspunkte";
if (fl&(AUTOINF_SP_PLUS)) txt+=" sich aendern"; else txt+=" fallen";
}
write(break_string(txt+".\n",78));
return 1;
}
private void DoNotifyHpChange() {
object *obs,ob;
string str;
int i;
autoinf_time=time();
str="";
obs=m_indices(autoinf_hp);
for (i=sizeof(obs)-1;i>=0;i--) {
if (!objectp(ob=obs[i])) continue;
if (str!="") str+=", ";
str+=sprintf("%s: %d LP",ob->name(WER),autoinf_hp[ob]);
if (member(autoinf_sp,ob))
str+=sprintf(" / %d KP",autoinf_sp[ob]);
m_delete(autoinf_sp,ob);
}
obs=m_indices(autoinf_sp);
for (i=sizeof(obs)-1;i>=0;i--) {
if (!objectp(ob=obs[i])) continue;
if (str!="") str+=", ";
str+=sprintf("%s: %d KP",ob->name(WER),autoinf_sp[ob]);
}
if (str!="" && IsMember(leader))
tell_object(leader,break_string(capitalize(str)+"\n",78));
autoinf_hp=([]);
autoinf_sp=([]);
}
void NotifyHpChange() {
mixed act,old;
int change;
if (!IsMember(PO) || !pointerp(act=hp_info[PO]))
return;
old=act[0..];
act=GetHpInfo(PO);change=0;
if (!autoinf_flags) return;
if (((autoinf_flags&AUTOINF_HP_MINUS) && act[0]<old[0]) ||
((autoinf_flags&AUTOINF_HP_PLUS) && act[0]>old[0]))
autoinf_hp[PO]=act[0],change=1;
if (((autoinf_flags&AUTOINF_SP_MINUS) && act[2]<old[2]) ||
((autoinf_flags&AUTOINF_SP_PLUS) && act[2]>old[2]))
autoinf_sp[PO]=act[2],change=1;
if (autoinf_time<time() || (change && (autoinf_flags&AUTOINF_INSTANT)))
DoNotifyHpChange();
}
int TeamCmd(string cmd, string arg) {
if (!IsMember(TP)) {
notify_fail("Du bist kein Teammitglied.\n");
if (TP->QueryProp(P_TEAM)==ME)
TP->SetProp(P_TEAM,0);
return 0;
}
switch(cmd) {
case "angriff":
return StartAttack();
case "angriffsbefehl":
if (stringp(arg))
attack_cmd[TP]=arg;
else
m_delete(attack_cmd,arg);
return 1;
case "autofolge":
case "autof":
if (arg=="ein" || arg=="an")
autofollow[TP]=1;
else
m_delete(autofollow,TP);
return 1;
case "autoi":
case "autoinf":
case "autoinfo":
case "autoinform":
return ChangeAutoInfo(arg);
case "entlasse":
return RemoveMember(arg);
case "farben":
return ChangeColors(arg);
case "fluchtreihe":
case "flucht":
return ChangeWimpyRow(arg);
case "formation":
if (TP!=leader)
return notify_fail("Nur der Teamleiter kann die Formation aendern.\n"),0;
return ChangeFormation(arg);
case "hist":
case "history":
return Hist(arg);
case "":
case "info":
return ShowTeamHP(arg);
case "leiter":
case "leiterin":
case "leitung":
return ChangeLeader(arg);
case "name":
return ChangeName(arg);
case "orte":
return ShowTeamRooms(arg);
case "kampfreihe":
case "reihe":
return ChangeRow(arg);
case "ruf":
case "rufe":
return Shout(arg);
case "verlasse":
TP->SetProp(P_TEAM,0);
return RemoveMember(TP);
default:;
}
return 0;
}
void reset() {
RemoveSingles();
TryRemove();
}