/**
* *sob* This is, to my knowledge, the 4th complete or major re-write
* of the magic_spell object that I have done. And this time, it's
* starting with nothing due to formatting this machine. *sigh*
* This one better damn well work RIGHT.
* @author Shiannar
* @revision 10/05/04 Changed the way that consumables work a tad, to
* get consumables working right. - Shiannar.
*/
#include <magic.h>
#include <tasks.h>
#include <effect.h>
#include <obj_parser.h>
mixed *ritual;
int gp_cost;
int time;
mapping consumed;
mapping required;
string name;
string help;
string spell_type;
/* Function Prototypes */
void real_end_spell(object, mixed *, int, int);
string query_name();
int query_time();
void create() {
//Not initialising ritual mapping because if this inherit is ever used in
//a spell that doesn't have a ritual mapping, it doesn't deserve to work.
consumed = ([ ]);
required = ([ ]);
seteuid("Spell");
TO->setup();
}
/** Sets the ritual array up. The ritual array is the
* array of steps that a spell will take.
*/
void set_ritual(mixed *_ritual) {
if(!arrayp(_ritual) || !sizeof(_ritual)) return;
ritual = _ritual;
} /* set_ritual() */
/**
* Returns the ritual array.
* @see set_ritual
*/
mixed *query_ritual() {
return ritual;
} /* query_ritual() */
/**
* Checks to see if the spell can be cast, and if so, casts it.
* Should never be called directly.
*/
int query_cast_spell(object caster, string arg, object *_targets, object *_using) {
object *targets;
mixed *args;
/* Check to see if the caster is already casting a spell. */
if(caster->query_casting_spell()) {
tell_object(caster, "You are already casting a spell, you cannot "
"cast "+query_name()+".\n");
return 1;
}
/* See if the caster can cast the spell on their chosen targets.
* By default, spells can't be cast on anything. */
targets = TO->check_targets(caster, _targets);
if(!targets) return 0;
/* Check to see if everything else has been specified right.
check_args should be overloaded to do things. */
args = ({ 1, targets, _using, 0, arg });
if(!args = TO->check_args(caster, args)) {
return 0;
}
/* Is there _anything_ else we should check for? */
if(!TO->check_else(caster, args)) {
return 1;
}
/* Does the caster have enough guild points? */
if(!TASKER->point_tasker(caster, "magic",
to_int(gp_cost * TO->scale_gp_cost(caster, args)))) {
tell_object(caster, "You cannot summon enough magical energy to "
"cast "+query_name()+".\n");
return 1;
}
/* For all intents and purposes, they can now start casting. */
caster->add_effect(file_name(TO), args);
return 1;
} /* query_cast_spell() */
/**
* @ignore yes
*/
void beginning(object caster, mixed *args, int id) {
mixed *targets;
targets = args[ARG_TARGETS];
if(!sizeof(targets)) {
tell_object(caster, "You prepare to cast "+query_name()+".\n");
}
else {
if(member_array(caster, targets) != -1) {
targets -= ({ caster });
targets = targets + ({ "yourself" });
}
tell_object(caster, "You prepare to cast "+query_name()+" on "+
query_multiple_short(args[ARG_TARGETS])+".\n");
}
caster->submit_ee("spell_stage",
to_int(time * TO->scale_time(caster, args)),
EE_ONCE);
} /* beginning() */
/**
* @ignore yes
*/
void spell_stage(object caster, mixed *args, int id) {
object *targets;
int difficulty;
class task_class_result tasker_result;
/* Make sure all targets are still agreeable according to the spell */
targets = TO->check_targets(caster, args[ARG_TARGETS]);
if(!targets) {
TO->end_spell(caster, args, END_ABORT, id);
}
else if(sizeof(targets) < sizeof(args[ARG_TARGETS])) {
if(!sizeof(targets)) {
tell_object(caster, "You have lost all of your targets.\n");
TO->end_spell(caster, args, END_ABORT, id);
}
if(sizeof(args[ARG_TARGETS]) - sizeof(targets) > 1) {
tell_object(caster, "You have lost some of your targets.\n");
}
else {
tell_object(caster, "You have lost one of your targets.\n");
}
}
args[ARG_TARGETS] = targets;
/* Caster has all required components? */
if(!TO->check_components(caster, args[ARG_STAGE], args)) {
TO->end_spell(caster, args, END_ABORT, id);
return;
}
/* Check to see if the spell has anything else to do before
attempting this stage. */
if(function_exists("fail_stage_"+query_num(args[ARG_STAGE]), TO, 1)) {
if(call_other(TO, "fail_stage_"+query_num(args[ARG_STAGE]), caster,
args)) {
TO->end_spell(caster, args, END_FAIL_HANDLED, id);
}
else {
// TO->end_spell(caster, args, END_FAIL, id);
}
}
/* Work out difficulty for the stage, perform stage. */
difficulty = to_int( RITUAL_DIFF(args[ARG_STAGE]) *
TO->scale_difficulty(caster, args) );
tasker_result = TASKER->perform_task(caster,
RITUAL_SKILL(args[ARG_STAGE]),
difficulty,
TM_SPELL, 1);
switch(tasker_result->result) {
case AWARD: {
tell_object(caster, "%^YELLOW%^"+({
"Your concentrated mind becomes aware of another facet of "+
RITUAL_TM_SKILL(args[ARG_STAGE]),
"While contemplating "+RITUAL_TM_SKILL(args[ARG_STAGE])+", you "
"obtain a deeper understanding",
"A more practical application of "+RITUAL_TM_SKILL(args[ARG_STAGE])+
" suddenly becomes apparent",
"An obscure theory relating to "+RITUAL_TM_SKILL(args[ARG_STAGE])+
" becomes suddenly clearer"})[random(4)]+"!%^RESET%^\n");
break;
}
case FAIL: {
MAGIC_H->write_messages(caster, targets,
RITUAL_FAIL_MESSAGES(args[ARG_STAGE]));
if(!function_exists("fail_stage_"+query_num(args[ARG_STAGE]), TO, 1)) {
TO->end_spell(caster, args, END_FAIL, id);
}
else {
call_other("fail_stage_"+query_num(args[ARG_STAGE]), TO);
}
return;
}
}
MAGIC_H->write_messages(caster, targets,
RITUAL_MESSAGES(args[ARG_STAGE]));
/* Update args */
args[ARG_BONUS] += tasker_result->degree;
args[ARG_STAGE] += 1;
caster->set_arg_of(caster->sid_to_enum(id), args);
if(args[ARG_STAGE] > RITUAL_STAGES) {
TO->end_spell(caster, args, END_SUCCEED, id);
return;
}
/* Submit effect event for next stage. */
caster->submit_ee("spell_stage",
to_int(time * TO->scale_time(caster, args)),
EE_ONCE);
} /* spell_stage() */
/**
* Checks to see if valid arguments for the spell have
* been entered. Should be overloaded by inheriting
* objects to perform proper checking.
* @arg caster The person casting the spell
* @arg args Array of arguments for the spell, accessed through the defines in magic.h
* @return The new array of arguments, or 0 for failure due to incorrect args
*/
mixed *check_args(object caster, mixed *args) {
return args;
} /* check_args() */
/**
* Checks to see if the spell should fail due to any other reasons.
* Suppresses error messages, thus custom ones must be provided
* by inheriting objects. Should be overloaded if needed.
* @arg caster The person casting the spell
* @arg args Array of arguments for the spell, accessed through the defines in magic.h
* @return 0 to halt the spell, 1 to continue
*/
int check_else(object caster, mixed *args) {
return 1;
} /* check_else() */
/**
* Checks to see if all targets are valid. Called once per
* spell stage, before the spell stage takes place. Should
* be overloaded for any targeted spells.
* @arg caster The person casting the spell
* @arg targets Object array of all current targets
* @return Object array of all new targets
*/
object *check_targets(object caster, object *targets) {
if(sizeof(targets)) {
tell_object(caster, "You cannot cast "+query_name()+" at anything.\n");
return 0;
}
return ({ });
} /* check_targets() */
/**
* Scaling function. Overload to scale GP cost to cast a spell
* if so desired.
* @arg caster The person casting the spell
* @arg args The args for the spell, see magic.h
* @return Float percentage of GP cost, default 1.0
*/
float scale_gp_cost(object caster, mixed *args) {
return 1.0;
} /* scale_gp_cost() */
/**
* Scaling function. Overload to scale the time it takes to cast
* a spell if you so desire.
* @arg caster The person casting the spell
* @arg args The args for the spell, see magic.h
* @return Float percentage of normal speed, for default see code.
*/
float scale_time(object caster, mixed *args) {
int bnus;
int ntime;
if(TO->query_spell_type()) {
bnus += caster->query_skill_bonus(TO->query_spell_type());
}
if(bnus < 0) bnus = 0;
switch(bnus) {
case 400..599 : {
ntime = 2;
break;
}
case 300..399 : {
ntime = 3;
break;
}
case 200..299 : {
ntime = 4;
break;
}
case 150..199 : {
ntime = 5;
break;
}
case 100..149 : {
ntime = 6;
break;
}
case 50..99 : {
ntime = 7;
break;
}
case 20..49 : {
ntime = 8;
break;
}
case 0..19 : {
ntime = 9;
break;
}
default : {
ntime = 1;
}
}
return 1.0 + random(to_float(ntime)/2);
} /* scale_time() */
/**
* Scaling function. Overload to scale difficulty of stages according
* to any factors you may desire. For example, you could make a spell
* more difficult if there are more targets.
* @arg caster The person casting the spell
* @arg args The spell args, see magic.h
* @return Float percentage of difficulty, default 1.0
*/
float scale_difficulty(object caster, mixed *args) {
return 1.0;
} /* scale_difficulty() */
/**
* @ignore yes
*/
void end_spell(object caster, mixed *args, int end_type, int id) {
args[ARG_STAGE] = end_type;
caster->set_arg_of(caster->sid_to_enum(id), args);
caster->submit_ee(0, 1, EE_REMOVE);
} /* end_spell() */
/**
* @ignore yes
*/
void end(object caster, mixed *args, int id) {
int bonus;
bonus = args[ARG_BONUS] / RITUAL_STAGES;
if(args[ARG_STAGE] == END_ABORT) { //No, I'm not proud of this hack,
//But it's the prettiest option.
tell_object(caster, "You hastily release "+query_name()+"!\n");
TO->spell_aborted(caster, args, bonus);
return;
}
if(args[ARG_STAGE] == END_SUCCEED) {
XP_H->handle_xp(caster, to_int(gp_cost*TO->scale_gp_cost(caster, args)), 1);
TO->spell_succeeded(caster, args, bonus);
}
if(args[ARG_STAGE] == END_FAIL_HANDLED) {
XP_H->handle_xp(caster, to_int((gp_cost*TO->scale_gp_cost(caster, args)/2)), 0);
}
if(args[ARG_STAGE] == END_FAIL) {
XP_H->handle_xp(caster, to_int(gp_cost*TO->scale_gp_cost(caster, args)), 0);
TO->spell_failed(caster, args, bonus);
}
} /* end() */
/**
* @return The name of the spell
*/
string query_name() {
return name;
} /* query_name() */
/**
* @arg nname The new name of the spell
*/
void set_name(string nname) {
name = nname;
} /* set_name() */
/**
* @return The raw time in seconds per spell stage, I think.
* @see set_time, scale_time
*/
int query_time() {
return time;
} /* query_time() */
/**
* @arg ttime The new raw time in seconds per stage.
* @see query_time, scale_time
*/
void set_time(int ttime) {
time = ttime;
} /* set_time() */
/**
* @arg gp The raw gp cost to cast the spell.
* @see query_gp, scale_gp_cost
*/
void set_gp(int gp) {
gp_cost = gp;
} /* set_gp() */
/**
* @return The raw gp cost to cast the spell.
* @see set_gp, scale_gp_cost
*/
int query_gp() {
return gp_cost;
} /* query_gp() */
/**
* @arg n_type String type of spell, eg "magic.spells.misc"
* @see query_spell_type
*/
void set_spell_type(string n_type) {
spell_type = n_type;
} /* set_spell_type() */
/**
* @return String skill used primarily in spell.
* @see set_spell_type
*/
string query_spell_type() {
return spell_type;
} /* query_spell_type() */
/**
* @arg _help The string help for the spell.
*/
void set_help(string _help) {
help = _help;
} /* set_help() */
/**
* @ignore yes
*/
string help() {
return help;
} /* help() */
/**
* This function sets the items consumed by the spell, and the stages they're consumed in.
* @param cconsumed the entire mapping of items consumed in the format:
* ([
* (int)stage : ({ (string)id of the first item, (string)id of the second item}),
* ])
*/
void set_consumed(mapping cconsumed) {
if(!mappingp(cconsumed)) return;
consumed = cconsumed;
} /* set_consumed() */
/**
* This function sets the items required by the spell, and the stages they're required for.
* NB: Items that are required are not consumed! Use set_consumed for items that will
* be consumed by the spell.
* @param rrequired the entire mapping of items required in the format:
* ([
* (int)stage : ({ (string)id of the first item, (string)id of the second item}),
* ])
*/
void set_required(mapping rrequired) {
if(!mappingp(rrequired)) return;
required = rrequired;
} /* set_required() */
/**
* @ignore yes
*/
int check_components(object caster, int stage, mixed *args) {
object *dest;
string *missing;
string thing;
class obj_match result;
dest = ({});
missing = ({});
//Handle consumed items.
if(consumed[stage] && arrayp(consumed[stage])) {
foreach(thing in consumed[stage]) {
result = match_objects_in_environments(thing, caster);
if(result->result != OBJ_PARSER_SUCCESS) {
missing += ({thing});
}
else {
dest += result->objects;
}
}
}
dest->move("/room/rubbish");
//Handle required, non-consumed items.
if(required[stage] && arrayp(required[stage])) {
foreach(thing in required[stage]) {
result = match_objects_in_environments(thing, caster);
if(result->result != OBJ_PARSER_SUCCESS) {
missing += ({thing});
}
}
}
if(sizeof(missing)) {
tell_object(caster, "To continue casting "+query_name()+" you require "+
query_multiple_short(map(missing, (:add_a($1):)))+".\n");
return 0;
}
return 1;
} /* check_components() */
/**
* @ignore yes
*/
int query_is_spell() { return 1; } /* query_is_spell() */
/**
* @ignore yes
*/
string query_classification() {
if(!TO->query_spell_type()) {
return "magic.spell";
}
return "magic.spell."+
explode(TO->query_spell_type(), ".")[sizeof(explode(TO->query_spell_type(), "."))-1];
} /* query_classification() */
/**
* @ignore yes
*/
string query_shadow_ob() { return "/std/shadows/magic/magic_spell"; } /* query_shadow_ob() */