blob: c67c1145c11c798f58fd8e52a331c30a33f3018c [file] [log] [blame]
// 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;
}