server-1.12/server/c_range.c

401 lines
13 KiB
C

/*
* static char *rcsid_c_range_c =
* "$Id: c_range.c 11578 2009-02-23 22:02:27Z lalo $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2006 Mark Wedel & Crossfire Development Team
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The authors can be reached via e-mail at crossfire-devel@real-time.com
*/
/**
* @file
* Range related commands (casting, shooting, throwing, etc.).
*/
#include <global.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#include <spells.h>
#include <skills.h>
#include <newclient.h>
#include <commands.h>
/**
* 'invoke' command, fires a spell immediately.
*
* @param op
* player.
* @param params
* spell.
* @return
* 1 for success, 0 for failure.
*/
int command_invoke(object *op, char *params) {
return command_cast_spell(op, params, 'i');
}
/**
* 'cast' command, prepares a spell for laster casting.
*
* @param op
* player.
* @param params
* spell.
* @return
* 1 for success, 0 for failure.
*/
int command_cast(object *op, char *params) {
return command_cast_spell(op, params, 'c');
}
/**
* Equivalent to command_cast().
*
* @param op
* player.
* @param params
* spell.
* @return
* 1 for success, 0 for failure.
* @todo remove.
*/
int command_prepare(object *op, char *params) {
return command_cast_spell(op, params, 'p');
}
/**
* Shows all spells that op knows.
*
* Given there is more than one skill, we can't supply break
* them down to cleric/wizardry.
*
* @param op
* player wanting to knows her spells.
* @param params
* if supplied, the spell name must match that.
*/
static void show_matching_spells(object *op, char *params) {
object *spell;
char spell_sort[NROFREALSPELLS][MAX_BUF], tmp[MAX_BUF], *cp;
int num_found = 0, i;
/* We go and see what spells the player has. We put them
* into the spell_sort array so that we can sort them -
* we prefix the skill in the name so that the sorting
* works better.
*/
for (spell = op->inv; spell != NULL; spell = spell->below) {
/* If it is a spell, and no params are passed, or they
* match the name, process this spell.
*/
if (spell->type == SPELL
&& (!params || !strncmp(params, spell->name, strlen(params)))) {
if (spell->path_attuned&op->path_denied) {
snprintf(spell_sort[num_found++], sizeof(spell_sort[0]),
"%s:%-22s %3s %3s", spell->skill ? spell->skill : "generic",
spell->name, "den", "den");
} else {
snprintf(spell_sort[num_found++], sizeof(spell_sort[0]),
"%s:%-22s %3d %3d", spell->skill ? spell->skill : "generic",
spell->name, spell->level,
SP_level_spellpoint_cost(op, spell, SPELL_HIGHEST));
}
}
}
if (!num_found) {
/* If a matching string was passed along, now try it without that
* string. It is odd to do something like 'cast trans',
* and it say you have no spells, when really, you do, but just
* nothing that matches.
*/
if (params)
show_matching_spells(op, NULL);
else
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR,
"You know no spells", NULL);
} else {
/* Note in the code below that we make some
* presumptions that there will be a colon in the
* string. given the code above, this is always
* the case.
*/
qsort(spell_sort, num_found, MAX_BUF, (int (*)(const void *, const void *))strcmp);
strcpy(tmp, "asdfg"); /* Dummy string so initial compare fails */
for (i = 0; i < num_found; i++) {
/* Different skill name, so print banner */
if (strncmp(tmp, spell_sort[i], strlen(tmp))) {
strcpy(tmp, spell_sort[i]);
cp = strchr(tmp, ':');
*cp = '\0';
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"\n[fixed]%s spells %.*s <lvl> <sp>",
"\n%s spells %.*s <lvl> <sp>",
tmp, 12-strlen(tmp), " ");
}
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"[fixed]%s",
"%s",
strchr(spell_sort[i], ':')+1);
}
}
}
/**
* Sets up to cast a spell.
*
* Invoke casts a spell immediately, whereas cast just set up the range type.
*
* @param op
* caster.
* @param params
* spell name.
* @param command
* first letter of the spell type (c=cast, i=invoke, p=prepare).
* @return
* 0 if success, 1 for failure.
*/
int command_cast_spell(object *op, char *params, char command) {
int castnow = 0;
char *cp;
object *spob;
if (command == 'i')
castnow = 1;
if (params != NULL) {
tag_t spellnumber = 0;
if ((spellnumber = atoi(params)) != 0)
for (spob = op->inv; spob && spob->count != spellnumber; spob = spob->below)
;
else
spob = lookup_spell_by_name(op, params);
if (spob && spob->type == SPELL) {
/* Now grab any extra data, if there is any. Forward pass
* any 'of' delimiter
*/
if (spellnumber) {
/* if we passed a number, the options start at the second word */
cp = strchr(params, ' ');
if (cp) {
cp++;
if (!strncmp(cp, "of ", 3))
cp += 3;
}
} else if (strlen(params) > strlen(spob->name)) {
cp = params+strlen(spob->name);
*cp = 0;
cp++;
if (!strncmp(cp, "of ", 3))
cp += 3;
} else
cp = NULL;
if (spob->skill && !find_skill_by_name(op, spob->skill)) {
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_MISSING,
"You need the skill %s to cast %s!",
"You need the skill %s to cast %s!",
spob->skill, spob->name);
return 1;
}
/* Remove control of the golem */
if (op->contr->ranges[range_golem] != NULL) {
if (op->contr->golem_count == op->contr->ranges[range_golem]->count) {
remove_friendly_object(op->contr->ranges[range_golem]);
remove_ob(op->contr->ranges[range_golem]);
free_object(op->contr->ranges[range_golem]);
}
op->contr->ranges[range_golem] = NULL;
op->contr->golem_count = 0;
}
if (castnow) {
cast_spell(op, op, op->facing, spob, cp);
} else {
op->contr->ranges[range_magic] = spob;
op->contr->shoottype = range_magic;
if (cp != NULL) {
strncpy(op->contr->spellparam, cp, MAX_BUF);
op->contr->spellparam[MAX_BUF-1] = '\0';
} else {
op->contr->spellparam[0] = '\0';
}
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"You ready the spell %s",
"You ready the spell %s",
spob->name);
}
return 0;
} /* else fall through to below and print spells */
} /* params supplied */
/* We get here if cast was given without options or we could not find
* the requested spell. List all the spells the player knows.
*/
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR,
"Cast what spell? Choose one of:", NULL);
show_matching_spells(op, params);
return 1;
}
/**************************************************************************/
/**
* Check for the validity of a player range.
*
* This function could probably be simplified, eg, everything
* should index into the ranges[] array.
*
* @param op
* player to check.
* @param r
* range to check.
*
* @retval 1
* range specified is legal - that is, the character has an item that is equipped for that range type.
* @retval 0
* no item of that range type that is usable.
*/
int legal_range(object *op, int r) {
switch (r) {
case range_none: /* "Nothing" is always legal */
return 1;
case range_bow:
case range_misc:
case range_magic: /* cast spells */
if (op->contr->ranges[r])
return 1;
else
return 0;
case range_golem: /* Use scrolls */
if (op->contr->ranges[range_golem]
&& op->contr->ranges[range_golem]->count == op->contr->golem_count)
return 1;
else
return 0;
case range_skill:
if (op->chosen_skill)
return 1;
else
return 0;
}
/* No match above, must not be valid */
return 0;
}
/**
* Rotate the selected range attack.
*
* @param op
* player.
* @param k
* '+' selects next range, other values previous range.
*/
void change_spell(object *op, char k) {
char name[MAX_BUF];
do {
op->contr->shoottype += ((k == '+') ? 1 : -1);
if (op->contr->shoottype >= range_size)
op->contr->shoottype = range_none;
else if (op->contr->shoottype <= range_bottom)
op->contr->shoottype = (rangetype)(range_size-1);
} while (!legal_range(op, op->contr->shoottype));
/* Legal range has already checked that we have an appropriate item
* that uses the slot, so we don't need to be too careful about
* checking the status of the object.
*/
switch (op->contr->shoottype) {
case range_none:
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_ERROR,
"No ranged attack chosen.", NULL);
break;
case range_golem:
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"You regain control of your golem.", NULL);
break;
case range_bow:
query_name(op->contr->ranges[range_bow], name, MAX_BUF);
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"Switched to %s and %s.",
"Switched to %s and %s.",
name,
op->contr->ranges[range_bow]->race ? op->contr->ranges[range_bow]->race : "nothing");
break;
case range_magic:
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"Switched to spells (%s).",
"Switched to spells (%s).",
op->contr->ranges[range_magic]->name);
break;
case range_misc:
query_base_name(op->contr->ranges[range_misc], 0, name, MAX_BUF);
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"Switched to %s.",
"Switched to %s.",
name);
break;
case range_skill:
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
"Switched to skill: %s",
"Switched to skill: %s",
op->chosen_skill ? op->chosen_skill->name : "none");
break;
default:
break;
}
}
/**
* 'rotateshoottype' command, switch range attack.
*
* @param op
* player.
* @param params
* arguments to the command.
* @return
* 0.
*/
int command_rotateshoottype(object *op, char *params) {
if (!params)
change_spell(op, '+');
else
change_spell(op, params[0]);
return 0;
}