/**
 * This is the basic ritual effect, it does all
 * the checks for targets etc. at each ritual stage,
 * and prints the messages.
 * Original by Pinkfish for the wizard's guild.
 * Rewritten by Deutha for the new magic (wizards and witches) skill tree.
 * Rewritten by Olorin for the priests rituals
 * Stats based resistance added by Sandoz
 * Skills based resistance added by Sandoz
 * Alignment bonus added by Sandoz
 */
#include "skillgroups.h"
#include <deity.h>
#include <effect.h>
#include <tasks.h>
#define NUMBER sizeof( ritual[ stage ] ) - 1
int fixed_time,
    learn_lvl,
    gp_cost,
    power_level,
    pk_checked,
    directed,
    silent,       /* not influenced by silence */
    casting_time, /* average time in seconds between each part of the ritual */
    *resist;
string name,
       info,
       ritual_type,
       skill_used,
       resistance_skill,
       resistance_stat,
       all_needed,
       *sym_needed,
       *sym_consumed,
       *consumables,
       *needed;
mixed *ritual;
int query_fixed_time() { return fixed_time; }
void set_fixed_time( int number ) { fixed_time = number; }
int query_learn_lvl() { return learn_lvl; }
void set_learn_lvl( int number ) { learn_lvl = number; }
int query_silent() { return silent; }
void set_silent( int number ) { silent = number; }
int query_pk_checked() { return pk_checked; }
void set_pk_checked( int number ) { pk_checked = number; }
int query_gp_cost( object caster, object *targets ) { return gp_cost; }
void set_gp_cost( int number ) { gp_cost = number; }
int query_power_level( object caster, object *targets ) { return power_level; }
void set_power_level( int number ) { power_level = number; }
int query_directed() { return directed; }
void set_directed( int number ) { directed = number; }
int query_no_move() { return ( directed & NO_MOVE ); }
int query_casting_time() { return casting_time; }
void set_casting_time( int number ) { casting_time = number; }
int *query_resist() { return resist; }
void set_resist( int *numbers ) { resist = numbers; }
string query_name() { return name; }
void set_name( string word ) { name = word; }
string query_info() { return info; }
void set_info( string word ) { info = word; }
string query_ritual_type() { return ritual_type; }
void set_ritual_type( string word ) { ritual_type = word; }
string query_skill_used() { return skill_used; }
void set_skill_used( string word ) { skill_used = word; }
string query_resistance_skill() { return resistance_skill; }
void set_resistance_skill( string word ) { resistance_skill = word; }
string query_resistance_stat() { return resistance_stat; }
void set_resistance_stat( string word ) { resistance_stat = word; }
string *query_consumables() { return consumables; }
void set_consumables( string *words ) { consumables = words; }
string *query_needed() { return needed; }
void set_needed( string *words ) { needed = words; }
string query_all_needed() { return all_needed; }
void set_all_needed( string words ) { all_needed = words; }
string *query_symbols() { return sym_needed; }
void set_symbols( string *words ) { sym_needed = words; }
string *query_symbols_consumed() { return sym_consumed; }
void set_symbols_consumed( string *words ) { sym_consumed = words; }
mixed *query_ritual() { return ritual; }
void set_ritual( mixed *args ) { ritual = args; }
/*
 * directed:     +1 for non-living, +2 for living, +4 for self, +8 for multiple
 * casting_time: average time in seconds between stages of casting
 * skill_used:   skill for powering outcome
 * resist:       ({ div, normal, player, agressive })
 */
/* example of ritual array:
 * ({
 *    ({ [first part]
 *       ({ [first possibility]
 *          message to caster when caster is a target,
 *          message to caster when caster is not a target,
 *          message to room excluding all targets and caster,
 *          message to targets excluding caster,
 *       }),
 *       ({ [other possibilities...]
 *          etc.
 *       }),
 *    }),
 *    ({ [other parts]
 *       etc.
 *    })
 * })
 */
/** @ignore yes */
void create() {
    ritual = ({ });
    needed = ({ });
    consumables = ({ });
    sym_needed = ({ });
    sym_consumed = ({ });
    fixed_time = 1;
    seteuid("Ritual");
    TO->setup();
} /* create() */
/** @ignore yes */
int check_components( object caster ) {
    object item;
    foreach( item in consumables )
      if( !present( item, caster ) )
          return 0;
    foreach( item in needed )
      if( !present( item, caster ) )
          return 0;
    return 1;
} /* check_components() */
/** @ignore yes */
int check_symbols( object caster, string *symbols ) {
    object *inv;
    string deity, sym;
    if( sizeof( symbols ) ) {
        deity = (string)caster->query_deity();
        // All the holy stuff the player has.
        inv = filter( INV(caster), (: $1->query_property( $2 ) :), deity );
        if( !sizeof( inv ) )
            return 0;
        foreach( sym in symbols ) {
            if( sym[0] == '#' ) {
                if( !call_other( TO, sym[1..], caster ) )
                    return 0;
                continue;
            }
            if( !sizeof( filter( inv, (: $1->query_property($2) :), sym ) ) )
                return 0;
        }
    }
    return 1;
} /* check_symbols() */
/** @ignore yes */
string get_actual_skill( object caster, object *targets, string base ) {
    if( undefinedp( SKILLGROUP[ base ] ) )
        return ( base );
    if( !sizeof( targets ) )
        return ( base );
    if( sizeof( targets ) > 1 )
        return base + ( member_array( "area", SKILLGROUP[ base ] ) == -1 ?
                        "" : ".area" );
    if( targets[ 0 ] == caster )
        return base + ( member_array( "self", SKILLGROUP[ base ] ) == -1 ?
                        "" : ".self" );
    if( member_array( "target", SKILLGROUP[ base ] ) != -1 )
        return base + ".target";
    return ( base );
} /* get_actual_skill */
/**
 * This method will return a filtered array of valid targets.
 * It will print a message to the caster if a particular
 * target is invalid.
 * @param caster the caster
 * @param targets the targets to test
 * @param words the string arguments passed into the ritual
 * @return the array of valid targets if any, or 0 if there are none
 */
varargs object *check_target( object caster, object *targets, string words ) {
    object *pk_prevented, target;
    targets = filter( targets, (: ENV($1) == ENV($2) ||
                                  ENV($1) == $2 :), caster );
    if( directed ) {
        if( directed & NOTARGET )
            return ({ });
        if( !sizeof( targets ) ) {
            tell_object( caster, ( !words || words == "" ?
                "You can only perform "+name+" at or on something." :
                "There is no "+words+" to perform "+name+" on." )+"\n");
            return 0;
        }
        if( !( directed & MULTIPLE ) && sizeof( targets ) > 1 ) {
            tell_object( caster, "You cannot perform "+name+" on multiple "
                                 "targets.\n");
            return 0;
        }
        if( !( directed & SELF ) ) {
            if( member_array( caster, targets ) != -1 ) {
                tell_object( caster, "You cannot perform "+name+" on "
                    "yourself.\n");
                return 0;
            }
        }
        if( !( directed & LIVING ) ) {
            foreach( target in targets )
              if( living( target ) ) {
                  tell_object( caster, "You cannot perform "+name+" on "
                      "living targets.\n" );
                  return 0;
              }
        }
        if( !( directed & NONLIVING ) )
            foreach( target in targets ) {
              if( !living( target ) ) {
                  tell_object( caster, "You cannot perform "+name+" on "
                      "non-living targets.\n" );
                  return 0;
              }
              if( userp( target ) && !interactive( target ) ) {
                  tell_object( caster, "You cannot perform "+name+" on "
                      "net-dead players.\n" );
                  return 0;
              }
        }
        if( !( directed & GHOST ) ) {
            targets = filter( targets, (: !$1->query_property("dead") :),
                caster );
            if( !sizeof( targets ) ) {
                tell_object( caster, "You cannot perform "+name+" on "
                    "ghosts.\n");
                return 0;
            }
        }
        if( directed & CHECKED ) {
            targets = filter( targets, (: TO->ritual_check_target( $2, $1 ) :),
                              caster );
            if( !sizeof( targets ) )
                return 0;
        }
        if( pk_checked ) {
            pk_prevented = filter( targets, (: pk_check( $1, $2 ) :), caster );
            pk_prevented -= ({ caster });
            targets -= pk_prevented;
            if( sizeof( pk_prevented ) )
                tell_object( caster, "Something prevents you from affecting "+
                    query_multiple_short( pk_prevented ) + ".\n" );
            if( !sizeof( targets ) )
                return 0;
        }
    }
    return targets;
} /* check_target() */
/**
 * This method is called upon by the cast command,
 * and it will initiate the performing of the ritual.
 * @param args the string arguments passed into the ritual
 * @param targets the targets to perform the ritual on
 * @param using the components to use (currently not used)
 */
int cast_spell( string args, object *targets, object *using ) {
    int time;
    object *things, caster;
    string deity;
    caster = TP;
    deity = (string)caster->query_deity();
    if( !deity || deity == "" )
        return notify_fail( "%^RED%^You have no deity!%^RESET%^\n"
                            "Please bugreport where you got this ritual.\n" );
    if( !DEITY_H->query_deity( deity ) )
        return notify_fail( "%^RED%^Your deity is invalid!%^RESET%^\n"
                            "Please contact a creator immediately.\n" );
    if( caster->query_casting_ritual() )
        return notify_fail( "You are already performing a ritual!\n");
    if( caster->query_casting_spell() )
        return notify_fail( "You cannot perform "+name+" while casting a "+
                            "spell!\n");
    if( !silent && caster->query_silenced() )
        return notify_fail( "You cannot perform "+name+" ritual when you "
                            "are silenced!\n");
    if( directed && !( directed & NOTARGET ) ) {
        sscanf( args, "at %s", args );
        sscanf( args, "on %s", args );
        if( !things = check_target( caster, targets, args ) )
            return 1;
        time = 2 * sizeof( things ) - 1;
    } else {
        time = 1;
        things = ({ });
    }
    if( !check_components( caster ) || !check_symbols( caster, sym_needed ) ||
        !check_symbols( caster, sym_consumed ) )
        return notify_fail("You need "+add_a( all_needed ? all_needed :
            query_multiple_short( needed+consumables+sym_needed+sym_consumed ) )+
            " to perform "+name+".\n");
    if( (int)DEITY_H->query_valid_al( deity, (int)caster->query_al() ) == -1 )
        return notify_fail( "It seems you are of too good alignment "
                            "for "+ CAP( deity )+" to perform "+name+".\n");
    if( (int)DEITY_H->query_valid_al( deity, (int)caster->query_al() ) == 1 )
        return notify_fail( "It seems you are of too evil alignment "
                            "for "+ CAP( deity )+" to perform "+name+".\n");
    if( !TASKER->point_tasker( caster, "faith", time * gp_cost ) )
        return notify_fail( "You do not have enough spiritual strength to "
                            "perform "+name+".\n");
    caster->dest_hide_shadow();
    time = casting_time * ( fixed_time ? 1 : time );
    caster->add_effect( file_name(TO), ({ 0, time, things, 0 }) );
    return 1;
} /* cast_spell() */
/** @ignore yes */
int consume_components( object caster ) {
    object *things, thing;
    if( sizeof( consumables ) ) {
        things = ({ });
        foreach( string item in consumables ) {
            if( !thing = present( item, caster ) ) {
                tell_object( caster, "You seem to have misplaced "+item+"!\n");
                return 0;
            }
            things += ({ thing });
        }
    }
    if( sizeof( things ) )
        things->dest_me();
    return 1;
} /* consume_components() */
/** @ignore yes */
int consume_symbols( object caster ) {
    string deity, symbol;
    object *things, *inv, *tmp;
    if( !sizeof( sym_consumed ) )
        return 1;
    deity = (string)caster->query_deity();
    /* all the holy stuff the player has */
    inv = filter( INV(caster), (: $1->query_property($2) :), deity );
    if( !sizeof( inv ) ) {
        tell_object( caster, "You seem to have misplaced some of the "
            "components needed to perform "+name+"!\n");
        return 0;
    }
    things = ({ });
    foreach( symbol in sym_consumed ) {
        if( symbol[0..0] == "#" ) {
            // not desting the special stuff here.
            // will do it from ritual_succeeded instead.
            if( !call_other( TO, symbol[1..99], caster ) ) {
                tell_object( caster, "You seem to have misplaced some of "
                    "the components needed to perform "+name+"!\n");
                return 0;
            }
            continue;
        }
        tmp = filter( inv, (: $1->query_property($2) :), symbol );
        if( !sizeof( tmp ) ) {
            tell_object( caster, "You seem to have misplaced some of the "
                "components needed to perform "+name+"!\n");
            return 0;
        }
        things += ({ tmp[0] });
    }
    if( sizeof( things ) )
        things->dest_me();
    return 1;
} /* consume_symbols() */
string expand_ritual_message( string message, object caster, object *things,
                              int to_whom ) {
    object target;
    mixed stuff;
    string ob_name, ob_poss, ob_pron, ob_obje;
    string part1, verb, part2, deity, deity_poss;
    // to_whom is -2 for caster, -1 for room and 0, 1, ... for targets
    if( member_array( caster, things ) != -1 ) {
        switch( to_whom ) {
          case -2 :
            ob_name = query_multiple_short( things - ({ caster }) +
                  ({ "yourself" }), "the" );
            switch( sizeof( things ) ) {
              case 1 :
                ob_poss = "your";
                ob_pron = "you";
                ob_obje = "yourself";
              break;
              case 2 :
                target = ( things - ({ caster }) )[ 0 ];
                ob_poss = (string)target->HIS+" and your";
                ob_pron = (string)target->HE+" and you";
                ob_obje = (string)target->HIM+" and yourself";
              break;
              default :
                ob_poss = "their and your";
                ob_pron = "they and you";
                ob_obje = "them and yourself";
            }
          break;
          case -1 :
            stuff = ({ (string)caster->HIM+"self" });
            stuff += things - ({ caster });
            ob_name = query_multiple_short( stuff, "one" );
            if( sizeof( things ) > 2 ) {
                ob_poss = "their";
                ob_pron = "they";
                ob_obje = "them";
            } else {
                ob_poss = (string)caster->HIS;
                ob_pron = (string)caster->HE;
                ob_obje = (string)caster->HIM;
            }
          break;
          default :
            stuff = ({ (string)caster->HIM+"self" });
            stuff += delete( things, to_whom, 1 ) - ({ caster });
            stuff += ({ "you" });
            ob_name = query_multiple_short( stuff, "one" );
            switch( sizeof( things ) ) {
              case 1 :
                ob_poss = "your";
                ob_pron = "you";
                ob_obje = "yourself";
              break;
              case 2 :
                ob_poss = (string)caster->HIS+" and your";
                ob_pron = (string)caster->HE+" and you";
                ob_obje = (string)caster->HIM+" and yourself";
              break;
              default :
                ob_poss = "their and your";
                ob_pron = "they and you";
                ob_obje = "them and yourself";
            }
        }
    } else {
        switch( to_whom ) {
          case -2 :
            if( !sizeof( things ) )
                break;
            ob_name = query_multiple_short( things, "the" );
            if( sizeof( things ) > 2 ) {
                ob_poss = "their";
                ob_pron = "they";
                ob_obje = "them";
            } else {
                ob_poss = (string)things[ 0 ]->HIS;
                ob_pron = (string)things[ 0 ]->HE;
                ob_obje = (string)things[ 0 ]->HIM;
            }
          break;
          case -1 :
            if( !sizeof( things ) )
                break;
            ob_name = query_multiple_short( things, "one" );
            if( sizeof( things ) > 2 ) {
                ob_poss = "their";
                ob_pron = "they";
                ob_obje = "them";
            } else {
                ob_poss = (string)things[ 0 ]->HIS;
                ob_pron = (string)things[ 0 ]->HE;
                ob_obje = (string)things[ 0 ]->HIM;
            }
          break;
          default :
            if( !sizeof( things ) )
                break;
            ob_name = query_multiple_short( delete( things, to_whom, 1 ) +
                                            ({ "you" }), "one" );
            switch( sizeof( things ) ) {
              case 1 :
                ob_poss = "your";
                ob_pron = "you";
                ob_obje = "yourself";
              break;
              case 2 :
                target = delete( things, to_whom, 1 )[ 0 ];
                ob_poss = (string)target->HIS+" and your";
                ob_pron = (string)target->HE+" and you";
                ob_obje = (string)target->HIM+" and yourself";
              break;
              default :
                ob_poss = "their and your";
                ob_pron = "they and you";
                ob_obje = "them and yourself";
            }
        }
    }
    if( deity = (string)caster->query_deity() )
        deity_poss = DEITY_H->query_possessive( deity );
    if( caster )
        message = replace( message, ({
          "$tp_name$", (string)caster->one_short(),
          "$tp_poss$", (string)caster->HIS,
          "$tp_pron$", (string)caster->HE,
          "$tp_obje$", (string)caster->HIM,
          }) );
    stuff = "";
    while( sscanf( message, "%s %s$s%s", part1, verb, part2 ) == 3 ) {
        stuff += part1+" $V$0="+pluralize( verb )+","+verb+"$V$";
        message = part2;
    }
    stuff += message;
    return CAP( replace( stuff, ({
           "$ob_name$", ob_name,
           "$ob_poss$", ob_poss,
           "$ob_pron$", ob_pron,
           "$ob_obje$", ob_obje,
           "$god_poss$", deity_poss,
           "$god$", deity,
           "$r_name$", name }) ) );
} /* expand_ritual_message() */
/** @ignore yes */
void beginning( object caster, mixed *args, int id ) {
    caster->submit_ee("ritual_stage", 0, EE_ONCE );
} /* beginning() */
void ritual_stage( object caster, mixed *args, int id ) {
    int i, amount, stage, time;
    string message, *messages;
    object *things;
    stage = args[ 0 ];
    time = args[ 1 ];
    things = args[ 2 ];
    if( caster->query_property( "dead" ) ) {
        caster->submit_ee( 0, time, EE_REMOVE );
        return;
    }
#ifdef DEBUG
    tell_creator("sandoz", "Args[0] (stage) : %O\nArgs[1] (time) : %O\n"
                           "Args[2] (things) : %O\nArgs[3] : %O\n",
                            args[0], args[1], args[2], args[3] );
#endif
    if( !check_components( caster ) || !check_symbols( caster, sym_needed ) ||
        !check_symbols( caster, sym_consumed ) ) {
        tell_object( caster, "You seem to have misplaced some of the things "
            "needed to perform "+name+".  You need "+add_a( all_needed ?
            all_needed : query_multiple_short( needed+consumables+sym_needed+
            sym_consumed ) )+".\n");
        caster->set_arg_of( (int)caster->sid_to_enum( id ),
            ({ -1 - stage, time, ({ }), args[ 3 ] }) );
        caster->submit_ee( 0, 0, EE_REMOVE );
        return;
    }
    if( amount = sizeof( things ) ) {
        things = filter( things, (: $1 && ENV($1) == $2 || ENV($1) == $3 :),
            caster, ENV(caster) );
        if( !sizeof( things ) ) {
            tell_object( caster, "You seem to have lost "+ ( amount > 1 ?
                "all of your targets" : "your target" ) +"!\n");
            caster->set_arg_of( (int)caster->sid_to_enum( id ),
                ({ -1 - stage, time, ({ }), args[ 3 ] }) );
            caster->submit_ee( 0, 0, EE_REMOVE );
            return;
        }
        switch( amount - sizeof( things ) ) {
          case 0 :
          break;
          case 1 :
            tell_object( caster, "You seem to have lost one of your "+
                "targets!\n" );
          break;
          default :
            tell_object( caster, "You seem to have lost some of your "+
                "targets!\n" );
        }
    }
    if( amount = sizeof( things ) ) {
        if( !( directed & GHOST ) ) {
            things = filter( things, (: !$1->query_property("dead") :) );
            if( !sizeof( things ) ) {
                tell_object( caster, ( amount > 1 ? "All of your targets seem" :
                    "Your target seems")+" to have died!\n");
                caster->set_arg_of( (int)caster->sid_to_enum( id ),
                    ({ -1 - stage, time, ({ }), args[ 3 ] }) );
                caster->submit_ee( 0, 0, EE_REMOVE );
                return;
            }
            switch( amount - sizeof( things ) ) {
              case 0 :
              break;
              case 1 :
                tell_object( caster, "One of your targets seems to have "
                    "died!\n");
              break;
              default :
                tell_object( caster, "Some of your targets seem to have "
                    "died!\n");
            }
        } else {
            things = filter( things, (: $1->query_property("dead") :) );
            if( !sizeof( things ) ) {
                tell_object( caster, ( amount > 1 ?
                    "All of your targets seem" : "Your target seems")+" to "
                    "have come to life!\n");
                caster->set_arg_of( (int)caster->sid_to_enum( id ),
                    ({ -1 - stage, time, ({ }), args[ 3 ] }) );
                caster->submit_ee( 0, 0, EE_REMOVE );
                return;
            }
            switch( amount - sizeof( things ) ) {
              case 0 :
              break;
              case 1 :
                tell_object( caster, "One of your targets seems to have "
                    "come to life!\n");
              break;
              default :
                tell_object( caster, "Some of your targets seem to have "
                    "come to life!\n");
            }
        }
    }
    messages = ritual[ stage ][ random( NUMBER ) ];
    tell_object( caster, expand_ritual_message(
        messages[ ( member_array( caster, things ) == -1 ) ],
        caster, things, -2 ) );
    tell_room( ENV( caster ), expand_ritual_message( messages[ 2 ],
        caster, things, -1 ), things + ({ caster }) );
    message = messages[ 3 ];
    for( i = 0; i < sizeof( things ); i++ )
      if( things[ i ] != caster )
          tell_object( things[ i ], expand_ritual_message( message,
                caster, things, i ) );
    stage++;
    caster->set_arg_of( (int)caster->sid_to_enum( id ),
          ({ stage, time, things, amount }) );
    if( stage == sizeof( ritual ) )
        caster->submit_ee( 0, time, EE_REMOVE );
    else
        caster->submit_ee( "ritual_stage",
                ({ time / 2, ( 3 * time ) / 2 }), EE_ONCE );
} /* ritual_stage() */
/** @ignore yes */
void end( object caster, mixed *args, int id ) {
    int bonus, resistance, al_range, al_offset, al_bonus, difficulty, tmp;
    object *things, thing;
    string actual_skill, *skills, deity;
    class task_class_result tasker_result;
    things = args[ 2 ];
    actual_skill = get_actual_skill( caster, things, skill_used );
    bonus = (int)caster->query_skill_bonus( actual_skill );
    if( caster->query_property("dead") ) {
        TO->ritual_aborted( caster, things, bonus, args[0] );
        return;
    }
    deity = (string)caster->query_deity();
    if( tmp = sizeof( things ) ) {
        things = filter( things, (: $1 && ENV($1) == $2 || ENV($1) == $3 :),
            caster, ENV(caster) );
        if( args[0] > -1 ) {
            if( !sizeof( things ) ) {
                tell_object( caster, "You seem to have lost "+( tmp > 1 ?
                    "all of your targets" : "your target")+"!\n");
                args[0] = -1 - args[0];
            } else {
                switch( tmp - sizeof( things ) ) {
                  case 0 :
                  break;
                  case 1 :
                    tell_object( caster, "You seem to have lost one of your "
                        "targets!\n" );
                  break;
                  default :
                    tell_object( caster, "You seem to have lost some of your "
                        "targets!\n" );
                }
            }
        }
    }
    if( tmp = sizeof( things ) ) {
        if( !( directed & GHOST ) ) {
            things = filter( things, (: !$1->query_property("dead") :) );
            if( args[0] > -1 ) {
                if( !sizeof( things ) ) {
                    tell_object( caster, ( tmp > 1 ? "All of your targets seem" :
                        "Your target seems")+" to have died!\n");
                    args[0] = -1 - args[0];
                } else {
                    switch( tmp - sizeof( things ) ) {
                      case 0 :
                      break;
                      case 1 :
                        tell_object( caster, "One of your targets seems to "
                            "have died!\n");
                      break;
                      default :
                        tell_object( caster, "Some of your targets seem to "
                            "have died!\n");
                    }
                }
            }
        } else {
            things = filter( things, (: $1->query_property("dead") :) );
            if( args[0] > -1 ) {
                if( !sizeof( things ) ) {
                    tell_object( caster, ( tmp > 1 ? "All of your targets "
                        "seem" : "Your target seems")+" to have come to "
                        "life!\n");
                    args[0] = -1 - args[0];
                } else {
                    switch( tmp - sizeof( things ) ) {
                      case 0 :
                      break;
                      case 1 :
                        tell_object( caster, "One of your targets seems to "
                            "have come to life!\n");
                      break;
                      default :
                        tell_object( caster, "Some of your targets seem to "
                            "have come to life!\n");
                    }
                }
            }
        }
    }
    // This will handle the stop command.
    if( !args[ 0 ] )
        args[ 0 ] = -1;
    if( args[ 0 ] < 0 ) {
        args[ 0 ] = -1 - args[ 0 ];
        TO->ritual_aborted( caster, things, bonus, args[ 0 ] );
        return;
    }
    if( !check_components( caster ) ) {
        tell_object( caster, "You seem to have misplaced some of the things "
            "needed to perform "+name+".  You need "+add_a( all_needed ?
            all_needed : query_multiple_short( needed+consumables+sym_needed+
            sym_consumed ) )+".\n");
        return;
    }
    // Consume consumables.
    if( !consume_symbols( caster ) || !consume_components( caster ) )
        return;
    /*
     * Here the alignment bonus calculation starts.
     * It checks the preferred alignment and the total
     * alignment range for a particular god.
     */
    /* The range between maximum and minimum alignment of a god. */
    al_range = ABS( ( DEITY_H->query_al_lower( deity ) -
                      DEITY_H->query_al_upper( deity ) ) );
    al_offset = ( (int)caster->query_al() -
                  DEITY_H->query_al_middle( deity ) ) / ( al_range / 120 );
    /* This calculates the bonus (or as it may be, penalty) to add to
     * the difficulty of the ritual.
     * -30 if the caster if out of alignment.
     * +30 if the caster is in the preferred alignment.
     */
    al_bonus = 30 - ABS(al_offset);
    difficulty = power_level - al_bonus;
    if( difficulty < 5 )
        difficulty = 5;
    tasker_result = new(class task_class_result);
    tasker_result = TASKER->perform_task( caster, actual_skill,
                                          difficulty, TM_RITUAL, 1 );
    switch( tasker_result->result ) {
      case AWARD :
        skills = explode( actual_skill, "." );
        tell_object( caster, "%^YELLOW%^"+({
            "You realise something new about "+GRPDESC[skills[2]]+
            ( living( things[0] ) ? TRGTDESC[skills[3]][0] :
            TRGTDESC[skills[3]][1] )+".",
            "You find yourself more capable of "+GRPDESC[skills[2]]+
            ( living( things[0] ) ? TRGTDESC[skills[3]][0] :
            TRGTDESC[skills[3]][1] )+".",
            "You feel stronger in the art of "+GRPDESC[skills[2]]+
            ( living( things[0] ) ? TRGTDESC[skills[3]][0] :
            TRGTDESC[skills[3]][1] )+".",
            })[random(2)]+ "\n%^RESET%^");
      case SUCCEED :
        /* ritual cast successfully, now calculate resistance */
        if( !resist ) {
            XP_H->handle_xp( caster, gp_cost, 1 );
            TO->ritual_succeeded( caster, things, bonus );
        } else {
            foreach( thing in things ) {
              /* stats based resistance */
              if( resistance_stat ) {
                  switch( resistance_stat ) {
                    case "con":
                      resistance += ( 4 + random( 4 ) +
                                    (int)thing->query_con() ) * 4;
                    break;
                    case "str":
                      resistance += ( 4 + random( 4 ) +
                                    (int)thing->query_str() ) * 4;
                    break;
                    case "dex":
                      resistance += ( 4 + random( 4 ) +
                                    (int)thing->query_dex() ) * 4;
                    break;
                    case "int":
                      resistance += ( 4 + random( 4 ) +
                                    (int)thing->query_int() ) * 4;
                    break;
                    case "wis":
                      resistance += ( 4 + random( 4 ) +
                                    (int)thing->query_wis() ) * 4;
                    break;
                    default:
                  }
              }
              /* skill based resistance */
              if( resistance_skill ) {
                  resistance +=
                      (int)thing->query_skill_bonus( resistance_skill ) /
                      resist[ 0 ];
              } else {
                  /* level based resistance */
                  if( userp( thing ) ) {
                      resistance += (int)thing->query_level() *
                          resist[ 2 ] / resist[ 0 ];
                  } else {
                      resistance += (int)thing->query_level() *
                          resist[ 1 + 2 * thing->query_aggressive() ] /
                          resist[ 0 ];
                  }
              }
          }
          resistance -= al_bonus;
          tasker_result = new( class task_class_result );
          tasker_result = TASKER->perform_task( caster, actual_skill,
                                                resistance, TM_RITUAL, 1 );
          switch( tasker_result->result ) {
            case BARF :
            break;
            case AWARD :
              skills = explode( actual_skill, "." );
              tell_object( caster, "%^YELLOW%^"+({
                  "You realise something new about breaking through "+
                  "people's resistance against "+GRPDESC[ skills[ 2 ] ]+
                  "them.",
                  "You find yourself more capable of breaking through "
                  "people's resistance against "+GRPDESC[ skills[ 2 ] ]+
                  "them.",
                  "You feel stronger in the art of breaking through "
                  "people's resistance against "+GRPDESC[ skills[ 2 ] ]+
                  "them."})[random(2)]+"\n%^RESET%^");
            case SUCCEED :
              XP_H->handle_xp( caster, gp_cost, 1 );
              TO->ritual_succeeded( caster, things, bonus );
            break;
            default :
              XP_H->handle_xp( caster, gp_cost, 0 );
              TO->ritual_resisted( caster, things, bonus );
          }
        }
      break;
      default :
        XP_H->handle_xp( caster, gp_cost, 0 );
        TO->ritual_failed( caster, things, bonus );
    }
} /* end() */
void ritual_aborted( object caster, object *targets, int bonus, int stage ) {
    tell_object( caster, (string)caster->query_deity()+" loses "
        "interest in your unfinished ritual.\n" );
} /* ritual_aborted() */
void ritual_failed( object caster, object *targets, int bonus ) {
    tell_object( caster, "You get the impression that "+
        (string)caster->query_deity()+" ignores your feeble attempts.\n" );
} /* ritual_failed() */
void ritual_resisted( object caster, object *targets, int bonus ) {
    /* default resisted ritual message */
    tell_object( caster, "Your target"+( sizeof(targets) > 1 ? "s" : "" )+
        " resisted the ritual.\n");
} /* ritual_resisted() */
string help() {
    return( replace( info, ({ "$name$", query_name() }) ) );
} /* help() */
/** @ignore yes */
string query_classification() { return "faith.ritual."+ritual_type; }
/** @ignore yes */
string query_shadow_ob() { return SHADOWS+"basic_ritual"; }
/** @ignore yes */
int query_indefinite() { return 1; }
/** @ignore yes */
int query_faith_ritual() { return 1; }