/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2001,2007 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992,2007 Frank Tore Johansen
*
* Deliantra 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 3 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, see .
*
* The authors can be reached via e-mail to
*/
#include
#include
#include
#include
#include
#include
#include
/* Want this regardless of rplay. */
#include
/**
* Check if op should abort moving victim because of it's race or slaying.
* Returns 1 if it should abort, returns 0 if it should continue.
*/
int
should_director_abort (object *op, object *victim)
{
int arch_flag, name_flag, race_flag;
/* Get flags to determine what of arch, name, and race should be checked.
* This is stored in subtype, and is a bitmask, the LSB is the arch flag,
* the next is the name flag, and the last is the race flag. Also note,
* if subtype is set to zero, that also goes to defaults of all affecting
* it. Examples:
* subtype 1: only arch
* subtype 3: arch or name
* subtype 5: arch or race
* subtype 7: all three
*/
if (op->subtype)
{
arch_flag = (op->subtype & 1);
name_flag = (op->subtype & 2);
race_flag = (op->subtype & 4);
}
else
{
arch_flag = 1;
name_flag = 1;
race_flag = 1;
}
/* If the director has race set, only affect objects with a arch,
* name or race that matches.
*/
if ((op->race) &&
((!(victim->arch && arch_flag && victim->arch->archname) || op->race != victim->arch->archname)) &&
((!(victim->name && name_flag) || op->race != victim->name)) &&
((!(victim->race && race_flag) || op->race != victim->race)))
return 1;
/* If the director has slaying set, only affect objects where none
* of arch, name, or race match.
*/
if ((op->slaying) && (((victim->arch && arch_flag && victim->arch->archname && op->slaying == victim->arch->archname))) ||
((victim->name && name_flag && op->slaying == victim->name)) ||
((victim->race && race_flag && op->slaying == victim->race)))
return 1;
return 0;
}
/**
* This handles a player dropping money on an altar to identify stuff.
* It'll identify marked item, if none all items up to dropped money.
* Return value: 1 if money was destroyed, 0 if not.
*/
static int
apply_id_altar (object *money, object *altar, object *pl)
{
dynbuf_text buf;
if (!pl || pl->type != PLAYER)
return 0;
/* Check for MONEY type is a special hack - it prevents 'nothing needs
* identifying' from being printed out more than it needs to be.
*/
if (!check_altar_sacrifice (altar, money) || money->type != MONEY)
return 0;
/* if the player has a marked item, identify that if it needs to be
* identified. If it doesn't, then go through the player inventory.
*/
if (object *marked = find_marked_object (pl))
if (!QUERY_FLAG (marked, FLAG_IDENTIFIED) && need_identify (marked))
{
if (operate_altar (altar, &money))
{
identify (marked);
buf.printf ("You have %s.\n\n", long_desc (marked, pl));
if (marked->msg)
buf << "The item has a story:\n\n" << marked->msg << "\n\n";
return !money;
}
}
for (object *id = pl->inv; id; id = id->below)
{
if (!QUERY_FLAG (id, FLAG_IDENTIFIED) && !id->invisible && need_identify (id))
{
if (operate_altar (altar, &money))
{
identify (id);
buf.printf ("You have %s.\n\n", long_desc (id, pl));
if (id->msg)
buf << "The item has a story:\n\n" << id->msg << "\n\n";
/* If no more money, might as well quit now */
if (!money || !check_altar_sacrifice (altar, money))
break;
}
else
{
LOG (llevError, "check_id_altar: Couldn't do sacrifice when we should have been able to\n");
break;
}
}
}
if (buf.empty ())
pl->failmsg ("You have nothing that needs identifying");
else
pl->contr->infobox (MSG_CHANNEL ("identify"), buf);
return !money;
}
/**
* This checks whether the object has a "on_use_yield" field, and if so generated and drops
* matching item.
**/
void
handle_apply_yield (object *tmp)
{
if (shstr yield = tmp->kv (shstr_on_use_yield))
archetype::get (yield)->insert_at (tmp, tmp, INS_BELOW_ORIGINATOR);
}
/**
* Handles applying a potion.
*/
int
apply_potion (object *op, object *tmp)
{
int got_one = 0, i;
object *force = 0, *floor = 0;
floor = GET_MAP_OB (op->map, op->x, op->y);
if (get_map_flags (op->map, NULL, op->x, op->y, NULL, NULL) & P_SAFE)
{
op->failmsg ("Gods prevent you from using this here, it's sacred ground!");
CLEAR_FLAG (tmp, FLAG_APPLIED);
return 0;
}
if (op->type == PLAYER)
if (!QUERY_FLAG (tmp, FLAG_IDENTIFIED))
identify (tmp);
handle_apply_yield (tmp);
/* Potion of restoration - only for players */
if (op->type == PLAYER && (tmp->attacktype & AT_DEPLETE))
{
object *depl;
archetype *at;
if (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))
{
op->drain_stat ();
op->update_stats ();
tmp->decrease ();
return 1;
}
if (!(at = archetype::find (ARCH_DEPLETION)))
{
LOG (llevError, "Could not find archetype depletion\n");
return 0;
}
depl = present_arch_in_ob (at, op);
if (depl)
{
for (i = 0; i < NUM_STATS; i++)
if (depl->stats.stat (i))
op->statusmsg (restore_msg[i]);
depl->destroy ();
op->update_stats ();
}
else
op->statusmsg ("Your potion had no effect.");
tmp->decrease ();
return 1;
}
/* improvement potion - only for players */
if (op->type == PLAYER && (tmp->attacktype & AT_GODPOWER))
{
for (i = 1; i < MIN (11, op->level); i++)
{
if (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))
{
if (op->contr->levhp[i] != 1)
{
op->contr->levhp[i] = 1;
break;
}
if (op->contr->levsp[i] != 1)
{
op->contr->levsp[i] = 1;
break;
}
if (op->contr->levgrace[i] != 1)
{
op->contr->levgrace[i] = 1;
break;
}
}
else
{
if (op->contr->levhp[i] < 9)
{
op->contr->levhp[i] = 9;
break;
}
if (op->contr->levsp[i] < 6)
{
op->contr->levsp[i] = 6;
break;
}
if (op->contr->levgrace[i] < 3)
{
op->contr->levgrace[i] = 3;
break;
}
}
}
/* Just makes checking easier */
if (i < MIN (11, op->level))
got_one = 1;
if (!QUERY_FLAG (tmp, FLAG_CURSED) && !QUERY_FLAG (tmp, FLAG_DAMNED))
{
if (got_one)
{
op->update_stats ();
op->statusmsg ("The Gods smile upon you and remake you "
"a little more in their image. "
"You feel a little more perfect.", NDI_GREEN);
}
else
op->statusmsg ("The potion had no effect - you are already perfect.");
}
else
{ /* cursed potion */
if (got_one)
{
op->update_stats ();
op->failmsg ("The Gods are angry and punish you.");
}
else
op->statusmsg ("You are fortunate that you are so pathetic.", NDI_DK_ORANGE);
}
tmp->decrease ();
return 1;
}
/* A potion that casts a spell. Healing, restore spellpoint (power potion)
* and heroism all fit into this category. Given the spell object code,
* there is no limit to the number of spells that potions can be cast,
* but direction is problematic to try and imbue fireball potions for example.
*/
if (tmp->inv)
{
if (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))
{
object *fball;
op->failmsg ("Yech! Your lungs are on fire!");
/* Explodes a fireball centered at player */
fball = get_archetype (EXPLODING_FIREBALL);
fball->dam_modifier = random_roll (1, op->level, op, PREFER_LOW) / 5 + 1;
fball->stats.maxhp = random_roll (1, op->level, op, PREFER_LOW) / 10 + 2;
fball->x = op->x;
fball->y = op->y;
insert_ob_in_map (fball, op->map, NULL, 0);
}
else
cast_spell (op, tmp, op->facing, tmp->inv, NULL);
tmp->decrease ();
/* if youre dead, no point in doing this... */
if (!QUERY_FLAG (op, FLAG_REMOVED))
op->update_stats ();
return 1;
}
/* Deal with protection potions */
force = NULL;
for (i = 0; i < NROFATTACKS; i++)
{
if (tmp->resist[i])
{
if (!force)
force = get_archetype (FORCE_NAME);
memcpy (force->resist, tmp->resist, sizeof (tmp->resist));
force->type = POTION_EFFECT;
break; /* Only need to find one protection since we copy entire batch */
}
}
/* This is a protection potion */
if (force)
{
/* cursed items last longer */
if (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))
{
force->stats.food *= 10;
for (i = 0; i < NROFATTACKS; i++)
if (force->resist[i] > 0)
force->resist[i] = -force->resist[i]; /* prot => vuln */
}
force->speed_left = -1;
force = insert_ob_in_ob (force, op);
CLEAR_FLAG (tmp, FLAG_APPLIED);
SET_FLAG (force, FLAG_APPLIED);
change_abil (op, force);
tmp->decrease ();
return 1;
}
/* Only thing left are the stat potions */
if (op->type == PLAYER)
{ /* only for players */
if ((QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) && tmp->value != 0)
CLEAR_FLAG (tmp, FLAG_APPLIED);
else
SET_FLAG (tmp, FLAG_APPLIED);
if (!change_abil (op, tmp))
op->statusmsg ("Nothing happened.");
}
/* CLEAR_FLAG is so that if the character has other potions
* that were grouped with the one consumed, his
* stat will not be raised by them. fix_player just clears
* up all the stats.
*/
CLEAR_FLAG (tmp, FLAG_APPLIED);
op->update_stats ();
tmp->decrease ();
return 1;
}
/****************************************************************************
* Weapon improvement code follows
****************************************************************************/
/**
* This returns the sum of nrof of item (arch name).
*/
static int
check_item (object *op, const char *item)
{
int count = 0;
if (!item)
return 0;
for (op = op->below; op; op = op->below)
{
if (strcmp (op->arch->archname, item) == 0)
{
if (!QUERY_FLAG (op, FLAG_CURSED) && !QUERY_FLAG (op, FLAG_DAMNED)
/* Loophole bug? -FD- */ && !QUERY_FLAG (op, FLAG_UNPAID))
{
if (op->nrof == 0) /* this is necessary for artifact sacrifices --FD-- */
count++;
else
count += op->nrof;
}
}
}
return count;
}
/**
* This removes 'nrof' of what item->slaying says to remove.
* op is typically the player, which is only
* really used to determine what space to look at.
* Modified to only eat 'nrof' of objects.
*/
static void
eat_item (object *op, const char *item, uint32 nrof)
{
object *prev;
prev = op;
op = op->below;
while (op)
{
if (strcmp (op->arch->archname, item) == 0)
{
if (op->nrof >= nrof)
{
op->decrease (nrof);
return;
}
else
{
op->decrease (nrof);
nrof -= op->nrof;
}
op = prev;
}
prev = op;
op = op->below;
}
}
/**
* This checks to see of the player (who) is sufficient level to use a weapon
* with improvs improvements (typically last_eat). We take an int here
* instead of the object so that the improvement code can pass along the
* increased value to see if the object is usuable.
* we return 1 (true) if the player can use the weapon.
*/
static int
check_weapon_power (const object *who, int improvs)
{
/* Old code is below (commented out). Basically, since weapons are the only
* object players really have any control to improve, it's a bit harsh to
* require high level in some combat skill, so we just use overall level.
*/
#if 1
if (((who->level / 5) + 5) >= improvs)
return 1;
else
return 0;
#else
int level = 0;
/* The skill system hands out wc and dam bonuses to fighters
* more generously than the old system (see fix_player). Thus
* we need to curtail the power of player enchanted weapons.
* I changed this to 1 improvement per "fighter" level/5 -b.t.
* Note: Nothing should break by allowing this ratio to be different or
* using normal level - it is just a matter of play balance.
*/
if (who->type == PLAYER)
{
object *wc_obj = NULL;
for (wc_obj = who->inv; wc_obj; wc_obj = wc_obj->below)
if (wc_obj->type == SKILL && IS_COMBAT_SKILL (wc_obj->subtype) && wc_obj->level > level)
level = wc_obj->level;
if (!level)
{
LOG (llevError, "Error: Player: %s lacks wc experience object\n", who->name);
level = who->level;
}
}
else
level = who->level;
return (improvs <= ((level / 5) + 5));
#endif
}
/**
* Returns how many items of type improver->slaying there are under op.
* Will display a message if none found, and 1 if improver->slaying is NULL.
*/
static int
check_sacrifice (object *op, const object *improver)
{
int count = 0;
if (improver->slaying)
{
count = check_item (op, improver->slaying);
if (count < 1)
{
op->failmsg (format ("The gods want more %ss", &improver->slaying));
return 0;
}
}
else
count = 1;
return count;
}
/**
* Actually improves the weapon, and tells user.
*/
static int
improve_weapon_stat (object *op, object *improver, object *weapon, sint8 &stat, int sacrifice_count, const char *statname)
{
stat += sacrifice_count;
weapon->last_eat++;
improver->decrease ();
/* So it updates the players stats and the window */
op->update_stats ();
op->statusmsg (format (
"Your sacrifice was accepted.\n"
"Weapon's bonus to %s improved by %d.",
statname, sacrifice_count
));
return 1;
}
/* Types of improvements, hidden in the sp field. */
#define IMPROVE_PREPARE 1
#define IMPROVE_DAMAGE 2
#define IMPROVE_WEIGHT 3
#define IMPROVE_ENCHANT 4
#define IMPROVE_STR 5
#define IMPROVE_DEX 6
#define IMPROVE_CON 7
#define IMPROVE_WIS 8
#define IMPROVE_CHA 9
#define IMPROVE_INT 10
#define IMPROVE_POW 11
/**
* This does the prepare weapon scroll.
* Checks for sacrifice, and so on.
*/
int
prepare_weapon (object *op, object *improver, object *weapon)
{
int sacrifice_count, i;
char buf[MAX_BUF];
if (weapon->level != 0)
{
op->failmsg ("Weapon is already prepared!");
return 0;
}
for (i = 0; i < NROFATTACKS; i++)
if (weapon->resist[i])
break;
/* If we break out, i will be less than nrofattacks, preventing
* improvement of items that already have protections.
*/
if (i < NROFATTACKS || weapon->stats.hp || /* regeneration */
(weapon->stats.sp && weapon->type == WEAPON) || /* sp regeneration */
weapon->stats.exp || /* speed */
weapon->stats.ac) /* AC - only taifu's I think */
{
op->failmsg ("You cannot prepare magic weapons. "
"H");
return 0;
}
sacrifice_count = check_sacrifice (op, improver);
if (sacrifice_count <= 0)
return 0;
weapon->level = isqrt (sacrifice_count);
eat_item (op, improver->slaying, sacrifice_count);
op->statusmsg (format (
"Your sacrifice was accepted."
"Your *%s may be improved %d times.",
&weapon->name, weapon->level
));
sprintf (buf, "%s's %s", &op->name, &weapon->name);
weapon->name = weapon->name_pl = buf;
weapon->nrof = 0; /* prevents preparing n weapons in the same
slot at once! */
improver->decrease ();
weapon->last_eat = 0;
return 1;
}
/**
* Does the dirty job for 'improve weapon' scroll, prepare or add something.
* This is the new improve weapon code.
* Returns 0 if it was not able to work for some reason.
*
* Checks if weapon was prepared, if enough potions on the floor, ...
*
* We are hiding extra information about the weapon in the level and
* last_eat numbers for an object. Hopefully this won't break anything ??
* level == max improve last_eat == current improve
*/
int
improve_weapon (object *op, object *improver, object *weapon)
{
int sacrifice_count, sacrifice_needed = 0;
if (improver->stats.sp == IMPROVE_PREPARE)
return prepare_weapon (op, improver, weapon);
if (weapon->level == 0)
{
op->failmsg ("This weapon has not been prepared. H");
return 0;
}
if (weapon->level == weapon->last_eat && weapon->item_power >= 100)
{
op->failmsg ("This weapon cannot be improved any more.");
return 0;
}
if (QUERY_FLAG (weapon, FLAG_APPLIED) && !check_weapon_power (op, weapon->last_eat + 1))
{
op->failmsg ("Improving the weapon will make it too "
"powerful for you to use. Unready it if you "
"really want to improve it.");
return 0;
}
/* This just increases damage by 5 points, no matter what. No sacrifice
* is needed. Since stats.dam is now a 16 bit value and not 8 bit,
* don't put any maximum value on damage - the limit is how much the
* weapon can be improved.
*/
if (improver->stats.sp == IMPROVE_DAMAGE)
{
weapon->stats.dam += 5;
weapon->weight += 5000; /* 5 KG's */
op->statusmsg (format ("Damage has been increased by 5 to %d.", weapon->stats.dam));
weapon->last_eat++;
weapon->item_power++;
improver->decrease ();
return 1;
}
if (improver->stats.sp == IMPROVE_WEIGHT)
{
/* Reduce weight by 20% */
weapon->weight = (weapon->weight * 8) / 10;
if (weapon->weight < 1)
weapon->weight = 1;
op->statusmsg (format ("Weapon weight reduced to %6.1fkg.", (float) weapon->weight / 1000.0));
weapon->last_eat++;
weapon->item_power++;
improver->decrease ();
return 1;
}
if (improver->stats.sp == IMPROVE_ENCHANT)
{
weapon->magic++;
weapon->last_eat++;
op->statusmsg (format ("Weapon magic increased to %d.", weapon->magic));
improver->decrease ();
weapon->item_power++;
return 1;
}
sacrifice_needed = weapon->stats.Str + weapon->stats.Int + weapon->stats.Dex +
weapon->stats.Pow + weapon->stats.Con + weapon->stats.Cha + weapon->stats.Wis;
if (sacrifice_needed < 1)
sacrifice_needed = 1;
sacrifice_needed *= 2;
sacrifice_count = check_sacrifice (op, improver);
if (sacrifice_count < sacrifice_needed)
{
op->failmsg (format ("You need at least %d %s.", sacrifice_needed, &improver->slaying));
return 0;
}
eat_item (op, improver->slaying, sacrifice_needed);
weapon->item_power++;
switch (improver->stats.sp)
{
case IMPROVE_STR: return improve_weapon_stat (op, improver, weapon, weapon->stats.Str, 1, "strength");
case IMPROVE_DEX: return improve_weapon_stat (op, improver, weapon, weapon->stats.Dex, 1, "dexterity");
case IMPROVE_CON: return improve_weapon_stat (op, improver, weapon, weapon->stats.Con, 1, "constitution");
case IMPROVE_WIS: return improve_weapon_stat (op, improver, weapon, weapon->stats.Wis, 1, "wisdom");
case IMPROVE_CHA: return improve_weapon_stat (op, improver, weapon, weapon->stats.Cha, 1, "charisma");
case IMPROVE_INT: return improve_weapon_stat (op, improver, weapon, weapon->stats.Int, 1, "intelligence");
case IMPROVE_POW: return improve_weapon_stat (op, improver, weapon, weapon->stats.Pow, 1, "power");
default:
op->failmsg ("Unknown improvement type.");
}
LOG (llevError, "improve_weapon: Got to end of function\n");
return 0;
}
/**
* Handles the applying of improve/prepare/enchant weapon scroll.
* Checks a few things (not on a non-magic square, marked weapon, ...),
* then calls improve_weapon to do the dirty work.
*/
int
check_improve_weapon (object *op, object *tmp)
{
object *otmp;
if (op->type != PLAYER)
return 0;
if (!QUERY_FLAG (op, FLAG_WIZCAST) && (get_map_flags (op->map, NULL, op->x, op->y, NULL, NULL) & P_NO_MAGIC))
{
op->failmsg ("Something blocks the magic of the scroll!");
return 0;
}
otmp = find_marked_object (op);
if (!otmp)
{
op->failmsg ("You need to mark a weapon object. H