| // MorgenGrauen MUDlib |
| // |
| // spellbook.c -- Grundlegende Funktionen fuer Zaubersprueche |
| // |
| // $Id: spellbook.c 9142 2015-02-04 22:17:29Z Zesstra $ |
| #pragma strict_types |
| #pragma save_types |
| #pragma no_shadow |
| #pragma no_clone |
| #pragma pedantic |
| #pragma range_check |
| |
| //#define NEED_PROTOTYPES |
| inherit "/std/thing"; |
| inherit "/std/restriction_checker"; |
| inherit "/std/player/pklog"; |
| |
| #include <thing/properties.h> |
| #include <properties.h> |
| #include <wizlevels.h> |
| #include <new_skills.h> |
| #include <spellbook.h> |
| #define ME this_object() |
| |
| void create() { |
| seteuid(getuid()); |
| // diese Prop sollte von aussen nicht direkt geaendert werden koennen. |
| Set(P_SB_SPELLS,([]),F_VALUE); |
| Set(P_SB_SPELLS,PROTECTED,F_MODE_AS); |
| |
| ::create(); |
| } |
| |
| mapping _query_sb_spells() { |
| // Kopie liefern, da sonst versehentlich schnell eine Aenderung des |
| // originalen Mappings hier passieren kann. |
| return(deep_copy(Query(P_SB_SPELLS, F_VALUE))); |
| } |
| |
| mapping QuerySpell(string spell) { |
| mapping spi; |
| |
| //Query-Methode umgehen, damit nur ein Spellmapping kopiert werden muss und |
| //nicht das ganzen Mapping mit allen Spells. |
| if (!spell |
| || !(spi=Query(P_SB_SPELLS)) |
| || !(spi=spi[spell])) |
| return 0; |
| return deep_copy(spi); |
| } |
| |
| varargs int |
| AddSpell(string verb, int kosten, mixed ski) { |
| int level; |
| mapping spells; |
| |
| if (!verb || kosten<0) |
| return 0; |
| |
| verb=lower_case(verb); |
| level=(intp(ski))?ski:0; |
| |
| if (!mappingp(ski)) ski=([]); |
| // Zur Sicherheit eine Kopie machen, wer weiss, was da uebergeben wird, bzw. |
| // was der Setzende mit ski hinterher noch macht. |
| else ski=deep_copy(ski); |
| |
| ski=AddSkillMappings(QueryProp(P_GLOBAL_SKILLPROPS),ski); |
| ski+=([SI_SPELLCOST:kosten]); |
| // SI_SPELL-Submapping hinzufuegen, wenn nicht da. |
| if (!member(ski,SI_SPELL)) |
| ski+=([SI_SPELL:([]) ]); |
| // ski[SI_SPELL][SP_NAME] ergaenzen, ggf. aus ski verschieben |
| if (!stringp(ski[SI_SPELL][SP_NAME]) && stringp(ski[SP_NAME])) { |
| ski[SI_SPELL][SP_NAME] = ski[SP_NAME]; |
| m_delete(ski, SP_NAME); |
| } |
| else if (!stringp(ski[SI_SPELL][SP_NAME])) { |
| ski[SI_SPELL][SP_NAME] = verb; |
| } |
| if (stringp(ski[SP_NAME])) { |
| m_delete(ski, SP_NAME); |
| } |
| |
| if (level) |
| ski=AddSkillMappings(ski,([SI_SKILLRESTR_LEARN:([P_LEVEL:level])])); |
| |
| // Abfrage per Query(), um das Mapping als Referenz, nicht als Kopie zu |
| // bekommen, das spart etwas Zeit. |
| if (!(spells=Query(P_SB_SPELLS,F_VALUE))) { |
| spells=([]); |
| SetProp(P_SB_SPELLS,spells); |
| } |
| // Spell setzen... |
| spells[verb]=ski; |
| // Set() nicht noetig, da Query() an spell das Originalmapping geliefert hat |
| // und dieses bereits jetzt geaendert wurde. ;-) |
| //Set(P_SB_SPELLS,spells+([verb:ski]),F_VALUE); |
| |
| // hat wohl geklappt... |
| return(1); |
| } |
| |
| int |
| TryAttackSpell(object victim, int damage, mixed dtypes, |
| mixed is_spell, object caster, mapping sinfo) { |
| mixed no_attack; |
| int nomag; |
| |
| if (no_attack = (mixed)victim->QueryProp(P_NO_ATTACK)) { |
| if (stringp(no_attack)) |
| caster->ReceiveMsg( |
| no_attack, |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| else |
| caster->ReceiveMsg( |
| victim->Name(WER,1)+" laesst sich nicht angreifen!", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| if (victim->QueryProp(P_GHOST)) { |
| caster->ReceiveMsg( |
| victim->Name(WER,1)+" ist ein Geist!", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| |
| damage=(damage*(int)caster->QuerySkillAttribute(SA_DAMAGE))/100; |
| |
| // ggf. Loggen von PK-Versuchen und ggf. ausserdem Angriff abbrechen. |
| // BTW: Aufruf von InsertEnemy() gibt 0 zurueck, wenn der Gegner nicht |
| // eingetragen wurde. Allerdings tut es das auch, wenn der Gegner schon |
| // Feind war. Daher muss noch geprueft werden, ob nach dem InsertEnemy() die |
| // beiden Spieler Feinde sind. Wenn nicht, wird der Spell auch abgebrochen |
| // und eine Meldung ausgegeben. |
| if (query_once_interactive(caster) && query_once_interactive(victim) |
| && CheckPlayerAttack(caster, victim, |
| sprintf("Spellbook: %O, Spell: %O\n", |
| object_name(this_object()), |
| ((mappingp(sinfo) && is_spell) ? sinfo[SP_NAME] : ""))) |
| && !caster->IsEnemy(victim) |
| && !caster->InsertEnemy(victim) |
| ) |
| { |
| tell_object(ME, "Ein goettlicher Einfluss schuetzt Deinen Gegner.\n"); |
| return 0; |
| } |
| |
| nomag=(int)victim->QueryProp(P_NOMAGIC); |
| if ((sinfo[SI_NOMAGIC] < nomag) && |
| nomag*100 > random(100)*(int)caster->QuerySkillAttribute(SA_ENEMY_SAVE)) { |
| printf("%s wehrt Deinen Zauber ab.\n", capitalize((string)victim->name(WER, 1))); |
| return 0; |
| } |
| else { |
| return (int)victim->Defend(damage, dtypes, is_spell, caster); |
| } |
| } |
| |
| varargs int |
| TryDefaultAttackSpell(object victim, object caster, mapping sinfo, |
| mixed si_spell) { |
| object team; |
| int row; |
| |
| if (!si_spell) si_spell=sinfo[SI_SPELL]; |
| // Wenn der Spieler in einem Team ist, die Teamreihen-Boni |
| // beruecksichtigen. Wenn nicht, eben nicht. |
| if (!team=((object)caster->QueryProp(P_TEAM))) |
| return TryAttackSpell(victim, |
| GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster), |
| GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster), |
| si_spell, |
| caster, |
| sinfo); |
| else |
| { |
| row=(int)caster->PresentPosition(); |
| return TryAttackSpell(victim, |
| GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)+ |
| // Nur wenn SI_SKILLDAMAGE_BY_ROW ein mapping ist, |
| // randomisiert addieren |
| (mappingp(sinfo[SI_SKILLDAMAGE_BY_ROW])? |
| random(sinfo[SI_SKILLDAMAGE_BY_ROW][row]):0)+ |
| // Nur wenn das OFFSET davon ein mapping ist, |
| // nicht randomisiert addieren. |
| (mappingp(sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)])? |
| sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)][row] : 0), |
| GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster), |
| si_spell,caster,sinfo); |
| } |
| } |
| |
| #define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y)) |
| int |
| SpellSuccess(object caster, mapping sinfo) { |
| int cval,abil,res; |
| |
| abil=sinfo[SI_SKILLABILITY]; |
| cval=(int)caster->UseSkill(SK_CASTING); |
| res=abil + (SMUL(abil,cval)) / MAX_ABILITY - random(MAX_ABILITY); |
| if (cval && res>MAX_ABILITY) // besonders gut gelungen? |
| caster->LearnSkill(SK_CASTING,1+(res-MAX_ABILITY)/2000); |
| return res; |
| } |
| |
| int |
| CanTrySpell(object caster, mapping sinfo) { |
| if (caster->QueryProp(P_GHOST)) { |
| caster->ReceiveMsg( |
| "Als Geist kannst Du nicht zaubern.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 0; |
| } |
| string res; |
| mapping rmap=sinfo[SI_SKILLRESTR_USE]; |
| if (mappingp(rmap) |
| && (res=check_restrictions(caster,rmap))) |
| { |
| caster->ReceiveMsg(res, MT_NOTIFICATION|MSG_DONT_WRAP, MA_SPELL); |
| return 0; |
| } |
| return 1; |
| } |
| |
| void |
| Learn(object caster, string spell, mapping sinfo) { |
| int val,diff,attval; |
| mapping attr; |
| |
| // gar nicht lernen? |
| if (sinfo[SI_NO_LEARN]) |
| return; |
| |
| // Attribut sollte int sein, wenn nicht anders angegeben |
| if (mappingp(sinfo[SI_LEARN_ATTRIBUTE])) |
| { |
| attr=sinfo[SI_LEARN_ATTRIBUTE]; |
| // Direkt in einem durch berechnen. |
| // Ich gehe davon aus, dass wir nie nie nie |
| // ein weiteres Attribut ins MG einfuegen. |
| // Wir berechnen den Mittelwert |
| attval=((int)caster->QueryAttribute(A_INT)*attr[A_INT]+ |
| (int)caster->QueryAttribute(A_DEX)*attr[A_DEX]+ |
| (int)caster->QueryAttribute(A_STR)*attr[A_STR]+ |
| (int)caster->QueryAttribute(A_CON)*attr[A_CON]) |
| /(attr[A_CON]+attr[A_INT]+attr[A_STR]+attr[A_DEX]); |
| |
| |
| } else { |
| attval=(int)caster->QueryAttribute(A_INT); |
| } |
| |
| |
| val=((attval/2)*GetFValue(SI_SKILLLEARN,sinfo,caster)+ |
| GetOffset(SI_SKILLLEARN,sinfo,caster)); |
| if (!(diff=GetFValueO(SI_DIFFICULTY,sinfo,caster))) |
| diff=GetFValueO(SI_SPELLCOST,sinfo,caster); |
| caster->LearnSkill(spell,val,diff); |
| } |
| |
| void |
| Erfolg(object caster, string spell, mapping sinfo) { |
| object env; |
| if(env=environment(caster)) |
| env->SpellInform(caster,spell,sinfo); |
| } |
| |
| void |
| Misserfolg(object caster, string spell, mapping sinfo) { |
| caster->ReceiveMsg( |
| "Der Zauberspruch ist missglueckt.\n" |
| "Du lernst aus Deinem Fehler.\n", |
| MT_NOTIFICATION|MSG_DONT_WRAP, |
| MA_SPELL); |
| Learn(caster,spell,sinfo); |
| } |
| |
| string |
| SelectSpell(string spell, mapping sinfo) { |
| if (sinfo && sinfo[SI_SKILLFUNC]) |
| return sinfo[SI_SKILLFUNC]; |
| return spell; |
| } |
| |
| varargs void |
| prepare_spell(object caster, string spell, mapping sinfo) { |
| string txt; |
| |
| if (!caster || !spell) return ; |
| if (!mappingp(sinfo) || !stringp(txt=sinfo[SI_PREPARE_MSG])) |
| txt="Du bereitest Dich auf den Spruch ``%s'' vor.\n"; |
| tell_object(caster,sprintf(txt,spell)); |
| } |
| |
| varargs int |
| UseSpell(object caster, string spell, mapping sinfo) { |
| mapping ski,tmp; |
| string spellbook,sname,fname,txt; |
| int res,fat,cost; |
| mixed ps; |
| |
| if (!caster || !spell) |
| return 0; |
| // Spell kann in der Gilde anderen Namen haben |
| sname=SelectSpell(spell,sinfo); |
| if (!(ski=QuerySpell(sname))) // Existiert dieser Spell? |
| return 0; |
| // Gildeneigenschaften sollen Spelleigenschaften ueberschreiben koennen |
| ski=AddSkillMappings(ski,sinfo); |
| // Fuer verschiedene Rassen unterschiedliche Eigenschaften |
| ski=race_modifier(caster,ski); |
| |
| // printf("Spellinfo: %O\n",ski); |
| |
| if (!CanTrySpell(caster, ski)) |
| return 1; |
| |
| if (caster->QueryProp(P_SP) < (cost=GetFValueO(SI_SPELLCOST,ski,caster))) { |
| if(txt=ski[SI_SP_LOW_MSG]) |
| caster->ReceiveMsg( |
| txt, |
| MT_NOTIFICATION, |
| MA_SPELL); |
| else |
| caster->ReceiveMsg( |
| "Du hast zu wenig Zauberpunkte fuer diesen Spruch.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 1; |
| } |
| // printf("cost: %d\n",cost); |
| |
| if (mappingp(ski[SI_X_SPELLFATIGUE])) { |
| // fuer jeden Key die Spellfatigue pruefen, wenn das Mapping hinterher |
| // nicht leer ist, ist man zu erschoepft. |
| tmp = filter(ski[SI_X_SPELLFATIGUE], |
| function int (string key, int val) |
| { return (int)caster->CheckSpellFatigue(key); } ); |
| if (sizeof(tmp)) { |
| caster->ReceiveMsg( |
| ski[SI_TIME_MSG] || |
| "Du bist noch zu erschoepft von Deinem letzten Spruch.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 1; |
| } |
| } |
| else { |
| if (caster->CheckSpellFatigue()) { |
| caster->ReceiveMsg( |
| ski[SI_TIME_MSG] || |
| "Du bist noch zu erschoepft von Deinem letzten Spruch.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 1; |
| } |
| } |
| |
| if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY) && |
| caster->QueryProp(P_ATTACK_BUSY)) { |
| if (txt=ski[SI_ATTACK_BUSY_MSG]) |
| caster->ReceiveMsg( |
| txt, |
| MT_NOTIFICATION, |
| MA_SPELL); |
| else |
| caster->ReceiveMsg( |
| "Du bist schon zu sehr beschaeftigt.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 1; |
| } |
| |
| // Spruchvorbereitung |
| if (pointerp(ps=(mixed)caster->QueryProp(P_PREPARED_SPELL)) // Ausstehender Spruch |
| && sizeof(ps)>=3 && intp(ps[0] && stringp(ps[1]))) { |
| if (ps[1]==spell) { // Dieser Spruch wird noch vorbereitet |
| if (time()<ps[0]) { |
| if (!stringp(txt=ski[SI_PREPARE_BUSY_MSG])) |
| txt="Du bist noch mit der Spruchvorbereitung beschaeftigt.\n"; |
| caster->ReceiveMsg( |
| txt, |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return 1; |
| } |
| } |
| else { // Andere Sprueche brechen die Vorbereitung ab |
| if (!mappingp(tmp=QuerySpell(ps[1])) || // richtige Meldung holen... |
| !stringp(txt=tmp[SI_PREPARE_ABORT_MSG])) |
| txt="Du brichst die Spruchvorbereitung fuer `%s' ab.\n"; |
| printf(txt,ps[1]); |
| if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) { |
| // Spruch braucht vorbereitungszeit |
| caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]})); |
| prepare_spell(caster,spell,ski); |
| return 1; |
| } |
| } |
| } |
| else { |
| if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) { |
| // Spruch braucht vorbereitungszeit |
| caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]})); |
| prepare_spell(caster,spell,ski); |
| return 1; |
| } |
| } |
| if (ps) |
| caster->SetProp(P_PREPARED_SPELL,0); |
| |
| // Funktion kann anderen Namen haben als Spell |
| if (!(fname=sinfo[SI_SKILLFUNC])) |
| fname=sname; |
| |
| if((ski[SI_NOMAGIC] < environment(caster)->QueryProp(P_NOMAGIC)) && |
| random(100) < environment(caster)->QueryProp(P_NOMAGIC)) { |
| if (txt=ski[SI_NOMAGIC_MSG]) |
| caster->ReceiveMsg( |
| txt, |
| MT_NOTIFICATION, |
| MA_SPELL); |
| else |
| caster->ReceiveMsg( |
| "Dein Zauberspruch verpufft im Nichts.", |
| MT_NOTIFICATION, |
| MA_SPELL); |
| res=ABGEWEHRT; |
| } |
| else { |
| // Spruch ausfuehren. |
| res=(int)call_other(this_object(),fname,caster,ski); |
| } |
| if (!res || !caster) |
| return 1; |
| |
| if(res==NICHT_ZUSTAENDIG) |
| return 0; |
| |
| if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY)) |
| { |
| if (!ski[SI_ATTACK_BUSY_AMOUNT]) |
| caster->SetProp(P_ATTACK_BUSY,1); |
| else |
| caster->SetProp(P_ATTACK_BUSY,ski[SI_ATTACK_BUSY_AMOUNT]); |
| } |
| |
| caster->restore_spell_points(-1*cost); |
| |
| if (mappingp(ski[SI_X_SPELLFATIGUE])) { |
| // fuer jeden Key die Spellfatigue setzen. Keys mit Dauer 0 loesen keine |
| // Spellfatigue aus. |
| filter(ski[SI_X_SPELLFATIGUE], |
| function int (string key, int val) |
| { return (int)caster->SetSpellFatigue(val, key); } ); |
| } |
| else { |
| if ((fat=GetFValueO(SI_SPELLFATIGUE,ski,caster))<0) |
| fat=1; |
| caster->SetSpellFatigue(fat); |
| } |
| |
| |
| if (res==ERFOLG) |
| Erfolg(caster,spell,ski); |
| else |
| if (res==MISSERFOLG) |
| Misserfolg(caster,spell,ski); |
| return 1; |
| } |
| |
| object * |
| FindGroup(object pl, int who) { |
| object *res,ob,env,team; |
| int p1,p2; |
| closure qp; |
| |
| res=({}); |
| if (!pl || !(env=environment(pl))) return res; |
| p1 = query_once_interactive(pl) ? 1 : -1; |
| team=(object)pl->QueryProp(P_TEAM); |
| for (ob=first_inventory(env);ob;ob=next_inventory(ob)) { |
| if (!living(ob)) continue; |
| qp=symbol_function("QueryProp",ob); |
| if (pl->IsEnemy(ob)) // Feinde sind immer Gegner |
| p2=-1*p1; |
| else if (objectp(team) && funcall(qp,P_TEAM)==team) |
| p2=p1; // Teammitglieder sind immer auf Seite des Spielers |
| else |
| p2 = (query_once_interactive(ob)||funcall(qp,P_FRIEND)) ? 1 : -1; |
| if (p2>0 && !interactive(ob) && query_once_interactive(ob)) |
| continue; // keine Netztoten. |
| if (funcall(qp,P_GHOST)) |
| continue; |
| if ( who<0 && (funcall(qp,P_NO_ATTACK) || funcall(qp,P_NO_GLOBAL_ATTACK)) ) |
| continue; |
| if (IS_LEARNING(ob) && |
| (funcall(qp,P_INVIS) || (who<0 && !pl->IsEnemy(ob)))) |
| continue; |
| if (p1*p2*who >=0) |
| res+=({ob}); |
| } |
| return res; |
| } |
| |
| object * |
| FindGroupN(object pl, int who, int n) { |
| if (!pl) return ({}); |
| n=(n*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100; |
| if (n<1) n=1; |
| return FindGroup(pl,who)[0..(n-1)]; |
| } |
| |
| object * |
| FindGroupP(object pl, int who, int pr) { |
| object *res,*nres; |
| int i; |
| |
| nres=({}); |
| if (!pl) return nres; |
| pr=(pr*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100; |
| if (pr<0) return nres; |
| res=FindGroup(pl,who); |
| for (i=sizeof(res)-1;i>=0;i--) |
| if (pr>=random(100)) |
| nres+=({res[i]}); |
| return nres; |
| } |
| |
| // Findet feindliche und freundliche GRUPPEN im angegebenen Bereich. |
| varargs mixed |
| FindDistantGroups(object pl, int dist, int dy, int dx) { |
| mapping pos; |
| object ob,enteam,myteam; |
| int p1,min,max,i; |
| mixed *b_rows,x; |
| closure is_enemy, qp; |
| |
| if (!objectp(pl) || !environment(pl)) |
| return ({({}),({})}); |
| if (!dy) dy=100; |
| if (!dx) dx=MAX_TEAM_ROWLEN*100; |
| x=(int)pl->QuerySkillAttribute(SA_EXTENSION); |
| dx=(dx*x)/100;dy=(dy*x)/100; |
| dist=(dist*(int)pl->QuerySkillAttribute(SA_RANGE))/100; |
| min=dist-dy/2; |
| max=dist+dy/2; |
| |
| pos=([]); |
| p1=query_once_interactive(pl) ? 1 : -1; |
| is_enemy=symbol_function("IsEnemy",pl); // zur Beschleunigung |
| myteam=(object)pl->QueryProp(P_TEAM); |
| for (ob=first_inventory(environment(pl));ob;ob=next_inventory(ob)) { |
| if (!living(ob)) continue; |
| qp=symbol_function("QueryProp",ob); // zur Beschleunigung |
| |
| // Zuerst mal die Position feststellen: |
| if (!objectp(enteam=funcall(qp,P_TEAM))) |
| pos[ob]=1; |
| else if (!pos[ob] && mappingp(x=(mapping)ob->PresentTeamPositions())) |
| pos+=x; |
| // PresentTeamPositions wird nur einmal pro Team ausgerechnet, weil |
| // anschliessend jedes anwesende Teammitglied pos[ob]!=0 hat. |
| |
| pos[ob]=(2*pos[ob])-1; |
| // Reihen sollen Abstand 2 haben, auch Reihen 1 und -1 (nach Umrechnung) |
| |
| // Feindliche Reihen an Y-Achse spiegeln, also gegenueber hinstellen: |
| if (funcall(is_enemy,ob)) |
| pos[ob]*=-1; // Ist auf jeden Fall Feind |
| else if (objectp(myteam) && myteam==enteam) |
| ; // Teammitglieder sind immer auf eigener Seite |
| else |
| pos[ob]*=(p1*((int)(query_once_interactive(ob)|| |
| funcall(qp,P_FRIEND))?1:-1)); |
| |
| // Den Spieler auf keinen Fall entfernen |
| if (ob==pl) |
| continue; |
| // Netztote, Geister und unsichtbare Magier nicht beruecksichtigen, |
| // nicht (global) angreifbare Monster und nicht-feindliche Magier |
| // von feindlicher Seite entfernen. |
| if ((!interactive(ob) && query_once_interactive(ob)) // Netztote raus |
| || funcall(qp,P_GHOST) |
| || (IS_LEARNING(ob) && funcall(qp,P_INVIS)) // Bin nicht da :-) |
| || (pos[ob]<0 && (funcall(qp,P_NO_GLOBAL_ATTACK) // Nicht angreifen |
| || funcall(qp,P_NO_ATTACK) |
| || (IS_LEARNING(ob) && !funcall(is_enemy,ob))))) |
| m_delete(pos,ob); |
| } |
| |
| // Reihen aufstellen |
| // Kampfreihe: | 5 4 3 2 1 || 1 2 3 4 5| |
| // Arrays: |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19| |
| // |<----------- Freunde ------->||<--------- Feinde -------->| |
| b_rows=EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY; |
| x=m_indices(pos); |
| for (i=sizeof(x)-1;i>=0;i--) { |
| p1=2*MAX_TEAMROWS-pos[ob=x[i]]; |
| pos[ob]=p1; |
| if (p1>=0 && p1<4*MAX_TEAMROWS) { |
| if (random(100)<50) // Damit gut gemischt wird... |
| b_rows[p1]=b_rows[p1]+({ob}); // zufaellig hinten anfuegen |
| else |
| b_rows[p1]=({ob})+b_rows[p1]; // oder vorne anfuegen. |
| } |
| } |
| |
| // Umrechnung von Min, Max auf Reihen |
| // ... -2: -124..-75, -1:-74..-25, 0:-24..24, 1:25..74 2:75..124 ... |
| // Das muss man machen, wenn man keine vernuenftige Rundungsfunktion hat: |
| if (min<0) min=-1*((25-min)/50); else min=(25+min)/50; |
| if (max<0) max=-1*((25-max)/50); else max=(25+max)/50; |
| // Relativ zur Position des Spielers verschieben: |
| p1=pos[pl];min+=p1;max+=p1; |
| // Umrechnung von Breite auf Anzahl: |
| dx=((dx+50)/100)-1;if (dx<0) dx=0; |
| |
| x=({({}),({})}); |
| for (i=0;i<2*MAX_TEAMROWS;i++) |
| if (i>=min && i<=max) |
| x[1]+=b_rows[i][0..dx]; // Freunde |
| for (i=2*MAX_TEAMROWS+1;i<4*MAX_TEAMROWS;i++) |
| if (i>=min && i<=max) |
| x[0]+=b_rows[i][0..dx]; // Feinde |
| |
| return x; |
| } |
| |
| // Findet feindliche oder freundliche Gruppe (oder Summe beider) im |
| // angegebenen Bereich. |
| varargs object * |
| FindDistantGroup(object pl, int who, int dist, int dy, int dx) { |
| mixed *x; |
| |
| x=FindDistantGroups(pl,dist,dy,dx); |
| if (who<0) |
| return x[0]; |
| else if (who>0) |
| return x[1]; |
| return x[0]+x[1]; |
| } |
| |
| |
| static varargs object |
| find_victim(string wen, object pl) { |
| object victim; |
| |
| if (!pl) return 0; |
| if (!sizeof(wen)) { |
| if (victim = (object)pl->SelectEnemy()) |
| return victim; |
| else |
| return 0; |
| } |
| if (victim = present(wen, environment(pl))) |
| return victim; |
| else if (victim = present(wen, pl)) |
| return victim; |
| else |
| return 0; |
| } |
| varargs object |
| FindVictim(string wen, object pl, string msg) { |
| object vic; |
| |
| if (!(vic=find_victim(wen,pl)) && msg) |
| pl->ReceiveMsg( |
| msg, |
| MT_NOTIFICATION, |
| MA_SPELL); |
| return vic; |
| } |
| varargs object |
| FindLivingVictim(string wen, object pl, string msg) { |
| object vic; |
| |
| if (!(vic=FindVictim(wen,pl,msg))) |
| return 0; |
| if (!living(vic) || vic->QueryProp(P_GHOST)) { |
| printf("%s lebt doch nicht!\n", capitalize((string)vic->name())); |
| return 0; |
| } |
| return vic; |
| } |
| |
| private varargs object |
| DoFindEnemyVictim(string wen, object pl, string msg, |
| mixed func, int min, int max) { |
| object vic; |
| mixed no_attack; |
| int row; |
| |
| if (!(vic=FindLivingVictim(wen,pl,msg))) { |
| if ((stringp(wen) && wen!="") || !objectp(pl)) |
| return 0; |
| if (pointerp(func)) { // Soll einer DIESER Gegner genommen werden? |
| if (!(vic=(object)pl->SelectEnemy(func))) // Dann daraus auswaehlen |
| return 0; |
| } else { |
| if (!stringp(func)) |
| func="SelectEnemy"; |
| if (!(vic=(object)call_other(pl,func,0,min,max))) |
| return 0; |
| } |
| func=0; // kein zweites Mal pruefen. |
| } |
| if (no_attack = (mixed)vic->QueryProp(P_NO_ATTACK)) { |
| if (stringp(no_attack)) |
| pl->ReceiveMsg( |
| no_attack, |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| else |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" laesst sich nicht angreifen.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| if (vic==pl) { |
| pl->ReceiveMsg( |
| "Du koenntest Dir dabei wehtun.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| if (stringp(func)) { |
| switch(func) { |
| case "SelectNearEnemy": |
| if (pl->PresentPosition()>1) { |
| pl->ReceiveMsg( |
| "Du stehst nicht in der ersten Kampfreihe.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| if (vic->PresentPosition()>1) { |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" ist in einer hinteren Kampfreihe.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| break; |
| case "SelectFarEnemy": |
| if (row=(int)vic->PresentPosition()) |
| row--; |
| if (row>=min && row<=max) |
| break; |
| if (row<min) |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" ist zu nahe.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| else if (row>max) |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" ist zu weit weg.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| else |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" ist unerreichbar.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| default:; |
| } |
| } else if (pointerp(func)) { |
| if (member(func,vic)<0) { |
| pl->ReceiveMsg( |
| vic->Name(WER,1)+" ist unerreichbar.", |
| MT_NOTIFICATION, |
| MA_FIGHT); |
| return 0; |
| } |
| } |
| |
| if (!pl->IsEnemy(vic)) // War es bisher kein Feind? |
| pl->Kill(vic); // Dann ist es jetzt einer. |
| return vic; |
| } |
| |
| varargs object |
| FindEnemyVictim(string wen, object pl, string msg) { |
| return DoFindEnemyVictim(wen,pl,msg,"SelectEnemy"); |
| } |
| |
| // Wie FindEnemyVictim, aber nur im Nahkampf erreichbare Feinde werden |
| // gefunden. |
| varargs object |
| FindNearEnemyVictim(string wen, object pl, string msg) { |
| return DoFindEnemyVictim(wen,pl,msg,"SelectNearEnemy"); |
| } |
| |
| // Wie FindEnemyVictim, aber nur Feinde im Bereich der angegebenen Reihen |
| // (min,max) werden gefunden. |
| varargs object |
| FindFarEnemyVictim(string wen, object pl, string msg, |
| int min, int max) { |
| return DoFindEnemyVictim(wen,pl,msg,"SelectFarEnemy",min,max); |
| } |
| |
| // Wie FindEnemyVictim, findet aber nur Feinde in |
| // FindDistantGroup(GEGNER,entfernung,abweichung) |
| varargs object |
| FindDistantEnemyVictim(string wen, object pl, string msg, |
| int dist, int dy) { |
| return DoFindEnemyVictim(wen,pl,msg, |
| FindDistantGroup(pl,-1,dist,dy,10000)); |
| } |
| |
| varargs int |
| TryGlobalAttackSpell(object caster, mapping sinfo, int suc, |
| int damage, mixed dt, mixed is_spell, |
| int dist, int depth, int width) { |
| int i; |
| mixed x,coldam; |
| object *obs,ob; |
| |
| if (!suc) suc=random(sinfo[SI_SKILLABILITY]); |
| if (!dt) dt=GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster); |
| if (!is_spell) is_spell=GetData(SI_SPELL,sinfo,caster); |
| if (!dist) dist=GetRandFValueO(SI_DISTANCE,sinfo,caster); |
| if (!depth) depth=GetRandFValueO(SI_DEPTH,sinfo,caster); |
| if (!width) width=GetRandFValueO(SI_WIDTH,sinfo,caster); |
| |
| if (!depth && width) depth=width; |
| if (!width && depth) width=depth; |
| if (!mappingp(is_spell)) is_spell=([]); |
| is_spell[SP_GLOBAL_ATTACK]=1; |
| |
| x=FindDistantGroups(caster,dist,depth,width); |
| sinfo[SI_NUMBER_ENEMIES]=sizeof(x[0]); |
| sinfo[SI_NUMBER_FRIENDS]=sizeof(x[1]); |
| |
| obs=x[0]; |
| for (i=sizeof(obs)-1;i>=0;i--) |
| if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo)) |
| TryAttackSpell(ob,(damage?random(damage): |
| GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)), |
| dt,is_spell,caster,sinfo); |
| |
| if (!intp(coldam=sinfo[SI_COLLATERAL_DAMAGE]) || !coldam) |
| return 1; |
| obs=x[1]; |
| for (i=sizeof(obs)-1;i>=0;i--) |
| if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo)) |
| ob->reduce_hit_points(((damage?random(damage): |
| GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)) |
| *coldam)/10); |
| // 10 statt 100 ist Absicht, weil reduce_hit_points schon um Faktor |
| // 10 staerker wirkt als Defend. |
| |
| return 1; |
| } |