server-1.12/types/food/food.c

390 lines
15 KiB
C

/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2007 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 food.c
* The implementation of the Food class of objects.
*/
#include <global.h>
#include <ob_methods.h>
#include <ob_types.h>
#include <sounds.h>
#include <sproto.h>
#include <math.h>
static method_ret food_type_apply(ob_methods *context, object *food, object *applier, int aflags);
static void eat_special_food(object *who, object *food);
static int dragon_eat_flesh(object *op, object *meal);
/**
* Initializer for the food object type.
*/
void init_type_food(void) {
register_apply(FOOD, food_type_apply);
register_apply(DRINK, food_type_apply);
register_apply(FLESH, food_type_apply);
}
/**
* Handles applying food.
* If player is applying, takes care of messages and dragon special food.
* @param context The method context
* @param food The food to apply
* @param applier The object attempting to apply the food
* @param aflags Special flags (always apply/unapply)
* @return METHOD_OK unless failure for some reason.
*/
static method_ret food_type_apply(ob_methods *context, object *food, object *applier, int aflags) {
int capacity_remaining;
if (QUERY_FLAG(food, FLAG_NO_PICK)) {
draw_ext_info_format(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE, "You can't %s that!", NULL, food->type == DRINK ? "drink" : "eat");
return METHOD_OK;
}
if (applier->type != PLAYER)
applier->stats.hp = applier->stats.maxhp;
else {
/* check if this is a dragon (player), eating some flesh */
if (food->type == FLESH && is_dragon_pl(applier))
dragon_eat_flesh(applier, food);
/* Check for old wraith player, give them the feeding skill */
else if (is_old_wraith_pl(applier)) {
object *skill = give_skill_by_name(applier, "wraith feed");
if (skill) {
char buf[MAX_BUF];
SET_FLAG(skill, FLAG_CAN_USE_SKILL);
link_player_skills(applier);
snprintf(buf, sizeof(buf), "You have been dead for too long to taste %s, ", food->name);
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE,
buf, NULL);
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE,
"and seem to have obtained a taste for living flesh.", NULL);
} else
LOG(llevError, "wraith feed skill not found\n");
/* Wraith player gets no food from eating. */
} else if (is_wraith_pl(applier)) {
char buf[MAX_BUF];
snprintf(buf, sizeof(buf), "You can no longer taste %s, and do not feel less hungry after %s it.", food->name, food->type == DRINK ? "drinking" : "eating");
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE,
buf, NULL);
/* usual case - not a wraith or a dgaron: */
} else {
if (applier->stats.food+food->stats.food > 999) {
if (food->type == FOOD || food->type == FLESH)
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE,
"You feel full, but what a waste of food!", NULL);
else
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_FAILURE,
"Most of the drink goes down your face not your throat!", NULL);
}
if (!QUERY_FLAG(food, FLAG_CURSED)) {
char buf[MAX_BUF];
if (!is_dragon_pl(applier)) {
/* eating message for normal players*/
if (food->type == DRINK)
snprintf(buf, sizeof(buf), "Ahhh...that %s tasted good.", food->name);
else
snprintf(buf, sizeof(buf), "The %s tasted %s", food->name, food->type == FLESH ? "terrible!" : "good.");
} else {
/* eating message for dragon players*/
snprintf(buf, sizeof(buf), "The %s tasted terrible!", food->name);
}
draw_ext_info(NDI_UNIQUE, 0, applier, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS,
buf, NULL);
capacity_remaining = 999-applier->stats.food;
applier->stats.food += food->stats.food;
if (capacity_remaining < food->stats.food)
applier->stats.hp += capacity_remaining/50;
else
applier->stats.hp += food->stats.food/50;
if (applier->stats.hp > applier->stats.maxhp)
applier->stats.hp = applier->stats.maxhp;
if (applier->stats.food > 999)
applier->stats.food = 999;
}
/* special food hack -b.t. */
if (food->title || QUERY_FLAG(food, FLAG_CURSED))
eat_special_food(applier, food);
}
}
handle_apply_yield(food);
decrease_ob(food);
return METHOD_OK;
}
/**
* Handles player eating food that temporarily changes status
* (resistances, stats).
* This used to call cast_change_attr(), but
* that doesn't work with the new spell code. Since we know what
* the food changes, just grab a force and use that instead.
*
* @param who
* living eating food.
* @param food
* eaten food.
*/
static void eat_special_food(object *who, object *food) {
object *force;
int i, did_one = 0;
sint8 k;
force = create_archetype(FORCE_NAME);
for (i = 0; i < NUM_STATS; i++) {
k = get_attr_value(&food->stats, i);
if (k) {
set_attr_value(&force->stats, i, k);
did_one = 1;
}
}
/* check if we can protect the eater */
for (i = 0; i < NROFATTACKS; i++) {
if (food->resist[i] > 0) {
force->resist[i] = food->resist[i]/2;
did_one = 1;
}
}
if (did_one) {
force->speed = 0.1;
update_ob_speed(force);
/* bigger morsel of food = longer effect time */
force->stats.food = food->stats.food/5;
SET_FLAG(force, FLAG_IS_USED_UP);
SET_FLAG(force, FLAG_APPLIED);
change_abil(who, force);
insert_ob_in_ob(force, who);
} else {
free_object(force);
}
/* check for hp, sp change */
if (food->stats.hp != 0 && !is_wraith_pl(who)) {
if (QUERY_FLAG(food, FLAG_CURSED)) {
strcpy(who->contr->killer, food->name);
hit_player(who, food->stats.hp, food, AT_POISON, 1);
draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_CURSED,
"Eck!...that was poisonous!", NULL);
} else {
if (food->stats.hp > 0)
draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS,
"You begin to feel better.", NULL);
else
draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_CURSED,
"Eck!...that was poisonous!", NULL);
who->stats.hp += food->stats.hp;
}
}
if (food->stats.sp != 0) {
if (QUERY_FLAG(food, FLAG_CURSED)) {
draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_CURSED,
"You are drained of mana!", NULL);
who->stats.sp -= food->stats.sp;
if (who->stats.sp < 0)
who->stats.sp = 0;
} else {
draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS,
"You feel a rush of magical energy!", NULL);
who->stats.sp += food->stats.sp;
/* place limit on max sp from food? */
}
}
fix_object(who);
}
/**
* A dragon is eating some flesh. If the flesh contains resistances,
* there is a chance for the dragon's skin to get improved.
*
* @param op
* object (dragon player) eating the flesh.
* @param meal
* flesh item, getting chewed in dragon's mouth.
* @return
* 1 if meal was eaten, 0 else.
* @note
* meal's nrof isn't decreased, caller is responsible for that.
*/
static int dragon_eat_flesh(object *op, object *meal) {
object *skin = NULL; /* pointer to dragon skin force*/
object *abil = NULL; /* pointer to dragon ability force*/
object *tmp = NULL; /* tmp. object */
char buf[MAX_BUF]; /* tmp. string buffer */
double chance; /* improvement-chance of one resist type */
double totalchance = 1; /* total chance of gaining one resistance */
double bonus = 0; /* level bonus (improvement is easier at lowlevel) */
double mbonus = 0; /* monster bonus */
int atnr_winner[NROFATTACKS]; /* winning candidates for resistance improvement */
int winners = 0; /* number of winners */
int i; /* index */
/* let's make sure and doublecheck the parameters */
if (meal->type != FLESH || !is_dragon_pl(op))
return 0;
/* now grab the 'dragon_skin'- and 'dragon_ability'-forces
* from the player's inventory
*/
for (tmp = op->inv; tmp != NULL; tmp = tmp->below) {
if (tmp->type == FORCE) {
if (strcmp(tmp->arch->name, "dragon_skin_force") == 0)
skin = tmp;
else if (strcmp(tmp->arch->name, "dragon_ability_force") == 0)
abil = tmp;
}
}
/* if either skin or ability are missing, this is an old player
* which is not to be considered a dragon -> bail out
*/
if (skin == NULL || abil == NULL)
return 0;
/* now start by filling stomache and health, according to food-value */
if ((999-op->stats.food) < meal->stats.food)
op->stats.hp += (999-op->stats.food)/50;
else
op->stats.hp += meal->stats.food/50;
if (op->stats.hp > op->stats.maxhp)
op->stats.hp = op->stats.maxhp;
op->stats.food = MIN(999, op->stats.food+meal->stats.food);
/*LOG(llevDebug, "-> player: %d, flesh: %d\n", op->level, meal->level);*/
/* on to the interesting part: chances for adding resistance */
for (i = 0; i < NROFATTACKS; i++) {
if (meal->resist[i] > 0 && atnr_is_dragon_enabled(i)) {
/* got positive resistance, now calculate improvement chance (0-100) */
/* this bonus makes resistance increase easier at lower levels */
bonus = (settings.max_level-op->level)*30./((double)settings.max_level);
if (i == abil->stats.exp)
bonus += 5; /* additional bonus for resistance of ability-focus */
/* monster bonus increases with level, because high-level
* flesh is too rare
*/
mbonus = op->level*20./((double)settings.max_level);
chance = (((double)MIN(op->level+bonus, meal->level+bonus+mbonus))*100./((double)settings.max_level))-skin->resist[i];
if (chance >= 0.)
chance += 1.;
else
chance = (chance < -12) ? 0. : 1./pow(2., -chance);
/* chance is proportional to amount of resistance (max. 50) */
chance *= ((double)(MIN(meal->resist[i], 50)))/50.;
/* doubled chance for resistance of ability-focus */
if (i == abil->stats.exp)
chance = MIN(100., chance*2.);
/* now make the throw and save all winners (Don't insert luck bonus here!) */
if (RANDOM()%10000 < (int)(chance*100)) {
atnr_winner[winners] = i;
winners++;
}
if (chance >= 0.01)
totalchance *= 1-chance/100;
/*LOG(llevDebug, " %s: bonus %.1f, chance %.1f\n", attacks[i], bonus, chance);*/
}
}
/* inverse totalchance as until now we have the failure-chance */
totalchance = 100-totalchance*100;
/* print message according to totalchance */
if (totalchance > 50.)
snprintf(buf, sizeof(buf), "Hmm! The %s tasted delicious!", meal->name);
else if (totalchance > 10.)
snprintf(buf, sizeof(buf), "The %s tasted very good.", meal->name);
else if (totalchance > 1.)
snprintf(buf, sizeof(buf), "The %s tasted good.", meal->name);
else if (totalchance > 0.1)
snprintf(buf, sizeof(buf), "The %s tasted bland.", meal->name);
else if (totalchance >= 0.01)
snprintf(buf, sizeof(buf), "The %s had a boring taste.", meal->name);
else if (meal->last_eat > 0 && atnr_is_dragon_enabled(meal->last_eat))
snprintf(buf, sizeof(buf), "The %s tasted strange.", meal->name);
else
snprintf(buf, sizeof(buf), "The %s had no taste.", meal->name);
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_APPLY, MSG_TYPE_APPLY_SUCCESS,
buf, NULL);
/* now choose a winner if we have any */
i = -1;
if (winners > 0)
i = atnr_winner[RANDOM()%winners];
if (i >= 0 && i < NROFATTACKS && skin->resist[i] < 95) {
/* resistance increased! */
skin->resist[i]++;
fix_object(op);
draw_ext_info_format(NDI_UNIQUE|NDI_RED, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_PROTECTION_GAIN,
"Your skin is now more resistant to %s!",
"Your skin is now more resistant to %s!",
change_resist_msg[i]);
}
/* if this flesh contains a new ability focus, we mark it
* into the ability_force and it will take effect on next level
*/
if (meal->last_eat > 0
&& atnr_is_dragon_enabled(meal->last_eat)
&& meal->last_eat != abil->last_eat) {
abil->last_eat = meal->last_eat; /* write:last_eat <new attnr focus> */
if (meal->last_eat != abil->stats.exp) {
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_RACE,
"Your metabolism prepares to focus on %s!",
"Your metabolism prepares to focus on %s!",
change_resist_msg[meal->last_eat]);
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_RACE,
"The change will happen at level %d",
"The change will happen at level %d",
abil->level+1);
} else {
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_RACE,
"Your metabolism will continue to focus on %s.",
"Your metabolism will continue to focus on %s.",
change_resist_msg[meal->last_eat]);
abil->last_eat = 0;
}
}
return 1;
}