Crossfire Server, Trunk  1.75.0
spellbook.cpp
Go to the documentation of this file.
1 /*
2  CrossFire, A Multiplayer game for X-windows
3 
4  Copyright (C) 2007 Crossfire Development Team
5  Copyright (C) 1992 Frank Tore Johansen
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21  The authors can be reached via e-mail at crossfire-devel@real-time.com
22 */
23 
28 #include <global.h>
29 #include <ob_methods.h>
30 #include <ob_types.h>
31 #include <sounds.h>
32 #include <sproto.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "living.h"
37 
38 static method_ret spellbook_type_apply(object *book, object *applier, int aflags);
39 static void spellbook_type_describe(
40  const object *book, const object *observer,
41  const int use_media_tags, char *buf, const size_t size);
42 
46 void init_type_spellbook(void) {
49 }
50 
64 static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell) {
66 
67  if (!spell->skill) {
68  // Nothing
69  } else if (spell->stats.grace) {
70  /* Otherwise we get "a second level praying" when it should be "a second
71  * level prayer". */
72  stringbuffer_append_string(sb, "prayer");
73  } else {
75  }
76 
77  stringbuffer_append_printf(sb, " level %d", spell->level);
78 
79  if (spell->path_attuned) {
81  describe_spellpath_attenuation("paths", spell->path_attuned, sb);
82  } else {
84  }
85 }
86 
104  const object *book, const object *observer,
105  const int use_media_tags, char *buf, size_t size) {
106  if (!is_identified(book)) {
107  /* Without querying the name, spellbooks end up examining
108  * as "That is:", with no name at all
109  * This should tell the player just as little as the inventory view.
110  *
111  * SilverNexus 2020-11-28
112  */
113  query_name(book, buf, size-1);
114  return;
115  }
116 
117  size_t len;
118  /* TODO check if this generates the "of foo" so we don't end up with
119  "spellbook of medium fireball of medium fireball" I think it probably does */
120  common_ob_describe(book, observer, use_media_tags, buf, size);
121  len = strlen(buf);
122 
123  const object *spell = book->inv;
124  if (!spell) {
125  snprintf(buf+len, size-len, " (blank)");
126  return;
127  }
128 
132  char *const desc = stringbuffer_finish(sb);
133  safe_strcat(buf, desc, &len, size);
134  free(desc);
135 }
136 
156 static method_ret spellbook_type_apply(object *book, object *applier, int aflags) {
157  object *skapplier, *spell, *spell_skill;
158  (void)aflags;
159 
160  /* Must be applied by a player. */
161  if (applier->type == PLAYER) {
162  if (QUERY_FLAG(applier, FLAG_BLIND) && !QUERY_FLAG(applier, FLAG_WIZ)) {
164  "You are unable to read while blind.");
165  return METHOD_OK;
166  }
167 
168  spell = book->inv;
169  if (!spell) {
170  LOG(llevError, "apply_spellbook: Book %s has no spell in it!\n", book->name);
172  "The spellbook symbols make no sense.");
173  return METHOD_OK;
174  }
175 
176  if (QUERY_FLAG(book, FLAG_CURSED) || QUERY_FLAG(book, FLAG_DAMNED)) {
177  char name[MAX_BUF];
178  /* Player made a mistake, let's shake her/him :) */
179  int failure = -35;
180 
182  failure = -rndm(35, 100);
183  query_name(book, name, MAX_BUF);
185  "The %s was %s!",
186  name, QUERY_FLAG(book, FLAG_DAMNED) ? "damned" : "cursed");
187  scroll_failure(applier, failure, (spell->level+4)*7);
188  if (QUERY_FLAG(book, FLAG_DAMNED)
189  && check_spell_known(applier, spell->name)
190  && die_roll(1, 10, applier, 1) < 2)
191  /* Really unlucky player, better luck next time */
192  do_forget_spell(applier, spell->name);
193  book = object_decrease_nrof_by_one(book);
194  if (book && (!QUERY_FLAG(book, FLAG_IDENTIFIED))) {
195  /* Well, not everything is lost, player now knows the
196  * book is cursed/damned. */
197  book = identify(book);
198  if (book->env)
199  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
200  else
201  applier->contr->socket->update_look = 1;
202  }
203  return METHOD_OK;
204  }
205 
206  /* This section moved before literacy check */
207  if (check_spell_known(applier, spell->name)) {
208  // If we already know the spell, it makes sense we know what the spell is.
209  if (book && (!QUERY_FLAG(book, FLAG_IDENTIFIED))) {
210  book = identify(book);
211  if (book->env)
212  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
213  else
214  applier->contr->socket->update_look = 1;
215  }
216  draw_ext_info_format(NDI_UNIQUE, 0, applier,
218  "You already know the spell %s.\n", spell->name);
219  return METHOD_OK;
220  }
221  /* check they have the right skills to learn the spell in the first place */
222  if (spell->skill) {
223  spell_skill = find_skill_by_name(applier, spell->skill);
224  if (!spell_skill) {
225  draw_ext_info_format(NDI_UNIQUE, 0, applier,
227  "You lack the skill %s to use this spell",
228  spell->skill);
229  return METHOD_OK;
230  }
231 
232  int skill_lev_diff = spell->level - spell_skill->level;
233  if (skill_lev_diff > 0) {
234  if (skill_lev_diff < 2)
236  "The spell described in this book is just beyond your skill in %s.", spell->skill);
237  else if (skill_lev_diff < 3)
239  "The spell described in this book is slightly beyond your skill in %s.", spell->skill);
240  else if (skill_lev_diff < 5)
242  "The spell described in this book is beyond your skill in %s.", spell->skill);
243  else if (skill_lev_diff < 8)
245  "The spell described in this book is quite a bit beyond your skill in %s.", spell->skill);
246  else if (skill_lev_diff < 15)
248  "The spell described in this book is way beyond your skill in %s.", spell->skill);
249  else
251  "The spell described in this book is totally beyond your skill in %s.", spell->skill);
252  return METHOD_OK;
253  }
254  }
255 
256  /* need a literacy skill to learn spells. Also, having a literacy level
257  * lower than the spell will make learning the spell more difficult */
258  skapplier = find_skill_by_name(applier, book->skill);
259  if (!skapplier) {
261  "You can't read! You will need this skill before you can comprehend the ideas written within.");
262  return METHOD_OK;
263  }
264 
265  /* We know the player has all the right skills so check how well they can read. */
266  int read_level;
267  read_level = skapplier->level;
268 
269  /* blessed books are easier to read */
270  if (QUERY_FLAG(book, FLAG_BLESSED))
271  read_level += 5;
272 
273  /* If the players read level is less than 10 levels lower than the spellbook, they can't read it */
274  int lev_diff;
275  lev_diff = spell->level - (read_level+10);
276  if (!QUERY_FLAG(applier, FLAG_WIZ) && lev_diff > 0) {
277  if (lev_diff < 2)
279  "You recognise most of the words but this book is just beyond your comprehension.");
280  else if (lev_diff < 3)
282  "You recognise many of the words but this book is slightly beyond your comprehension.");
283  else if (lev_diff < 5)
285  "You recognise some of the words but this book is slightly beyond your comprehension.");
286  else if (lev_diff < 8)
288  "You recognise some of the words but this book is beyond your comprehension.");
289  else if (lev_diff < 15)
291  "You recognise a few of the words but this book is beyond your comprehension.");
292  else
294  "You recognise a few of the words but this book is totally beyond your comprehension.");
295  return METHOD_OK;
296  }
297 
298  if (!QUERY_FLAG(book, FLAG_IDENTIFIED)) {
299  book = identify(book);
300  if (book->env)
301  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
302  else
303  applier->contr->socket->update_look = 1;
304  spell = book->inv;
305 
306  /* If they hadn't previously IDed the book, they didn't know what
307  * spell it contained, so tell them here.
308  */
311  char *const desc = stringbuffer_finish(sb);
313  "The spellbook contains %s %s.", spell->name, desc);
314  free(desc);
315  }
316 
317  /* Player has the right skills and enough skill to attempt to learn the spell with the logic as follows:
318  *
319  * 1- MU spells use Int to learn, Cleric spells use Wisdom
320  *
321  * 2- The learner's skill level in literacy adjusts the chance
322  * to learn a spell.
323  *
324  * 3 -Automatically fail to learn if you read while confused
325  *
326  * Overall, chances are the same but a player will find having a high
327  * literacy rate very useful! -b.t.
328  */
329  char desc[MAX_BUF];
330  const readable_message_type *msgType = get_readable_message_type(book);
331 
332  if (QUERY_FLAG(applier, FLAG_CONFUSED)) {
334  "In your confused state you flub the wording of the text!");
335  scroll_failure(applier, 0-random_roll(0, spell->level, applier, PREFER_LOW), MAX(spell->stats.sp, spell->stats.grace));
336  } else if (QUERY_FLAG(book, FLAG_STARTEQUIP)
337  || (random_roll(0, 100, applier, PREFER_LOW)-(5*read_level)) < get_learn_spell(spell->stats.grace ? applier->stats.Wis : applier->stats.Int)) {
338  query_short_name(book, desc, sizeof(desc));
340  msgType->message_type, msgType->message_subtype,
341  "You open the %s and start reading.", desc);
342  if (spell->msg != NULL) {
346  char *const fluff = stringbuffer_finish(sb);
348  free(fluff);
349  }
351  "You succeed in learning the spell!");
352  do_learn_spell(applier, spell, 0);
353 
354  /* xp gain to literacy for spell learning */
355  if (!QUERY_FLAG(book, FLAG_STARTEQUIP))
356  change_exp(applier, calc_skill_exp(applier, book, skapplier), skapplier->skill, 0);
357  } else {
358  play_sound_player_only(applier->contr, SOUND_TYPE_SPELL, book, 0, "fumble");
360  "You fail to learn the spell.\n");
361  }
363  }
364  return METHOD_OK;
365 }
readable_message_type::message_type
uint8_t message_type
Message type to be sent to the client.
Definition: book.h:37
PLAYER
@ PLAYER
Definition: object.h:112
global.h
settings
struct Settings settings
Server settings.
Definition: init.cpp:139
FLAG_CONFUSED
#define FLAG_CONFUSED
Will also be unable to cast spells.
Definition: define.h:311
llevError
@ llevError
Error, serious thing.
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
object::inv
object * inv
Pointer to the first object in the inventory.
Definition: object.h:298
safe_strcat
void safe_strcat(char *dest, const char *orig, size_t *curlen, size_t maxlen)
Simple function we use below to keep adding to the same string but also make sure we don't overwrite ...
Definition: porting.cpp:202
FLAG_STARTEQUIP
#define FLAG_STARTEQUIP
Object was given to player at start.
Definition: define.h:268
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
do_learn_spell
void do_learn_spell(object *op, object *spell, int special_prayer)
Actually makes op learn spell.
Definition: apply.cpp:484
stringbuffer_append_printf
void stringbuffer_append_printf(StringBuffer *sb, const char *format,...)
Append a formatted string to a string buffer instance.
Definition: stringbuffer.cpp:138
stringbuffer_new
StringBuffer * stringbuffer_new(void)
Create a new string buffer.
Definition: stringbuffer.cpp:57
register_apply
void register_apply(int ob_type, apply_func method)
Registers the apply method for the given type.
Definition: ob_types.cpp:62
METHOD_OK
#define METHOD_OK
Definition: ob_methods.h:15
PREFER_LOW
#define PREFER_LOW
Definition: define.h:558
draw_ext_info_format
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...) PRINTF_ARGS(6
play_sound_player_only
void play_sound_player_only(player *pl, int8_t sound_type, object *emitter, int dir, const char *action)
Plays a sound for specified player only.
Definition: sounds.cpp:51
FLAG_BLESSED
#define FLAG_BLESSED
Item has a blessing, opposite of cursed/damned.
Definition: define.h:369
NDI_NAVY
#define NDI_NAVY
Definition: newclient.h:247
scroll_failure
void scroll_failure(object *op, int failure, int power)
op made some mistake with a scroll, this takes care of punishment.
Definition: apply.cpp:1601
rndm
int rndm(int min, int max)
Returns a number between min and max.
Definition: utils.cpp:162
MSG_TYPE_SPELL_INFO
#define MSG_TYPE_SPELL_INFO
random info about spell, not related to failure/success
Definition: newclient.h:642
spellbook_type_describe
static void spellbook_type_describe(const object *book, const object *observer, const int use_media_tags, char *buf, const size_t size)
Describe a spellbook.
Definition: spellbook.cpp:103
NDI_BLUE
#define NDI_BLUE
Actually, it is Dodger Blue.
Definition: newclient.h:250
object::level
int16_t level
Level of creature or object.
Definition: object.h:361
common_ob_describe
void common_ob_describe(const object *op, const object *observer, int use_media_tags, char *buf, size_t size)
Describes an object, seen by a given observer.
Definition: describe.cpp:36
buf
StringBuffer * buf
Definition: readable.cpp:1565
MAX
#define MAX(x, y)
Definition: compat.h:24
name
Plugin animator file specs[Config] name
Definition: animfiles.txt:4
draw_ext_info
vs only yadda is in because all tags get reset on the next draw_ext_info In the second since it is all in one draw_ext_info
Definition: media-tags.txt:61
socket_struct::update_look
uint32_t update_look
If true, we need to send the look window.
Definition: newserver.h:104
init_type_spellbook
void init_type_spellbook(void)
Initializer for the SPELLBOOK object type.
Definition: spellbook.cpp:46
stringbuffer_finish
char * stringbuffer_finish(StringBuffer *sb)
Deallocate the string buffer instance and return the string.
Definition: stringbuffer.cpp:76
object::contr
struct player * contr
Pointer to the player which control this object.
Definition: object.h:284
object_decrease_nrof_by_one
#define object_decrease_nrof_by_one(xyz)
Definition: compat.h:32
MSG_TYPE_APPLY_SUCCESS
#define MSG_TYPE_APPLY_SUCCESS
Was able to apply object.
Definition: newclient.h:606
query_name
void query_name(const object *op, char *buf, size_t size)
Describes an item.
Definition: item.cpp:588
calc_skill_exp
int64_t calc_skill_exp(const object *who, const object *op, const object *skill)
Calculates amount of experience can be gained for successful use of a skill.
Definition: skill_util.cpp:667
describe_spellpath_attenuation
StringBuffer * describe_spellpath_attenuation(const char *attenuation, int value, StringBuffer *buf)
Describe the specified path attenuation.
Definition: utils.cpp:507
query_short_name
void query_short_name(const object *op, char *buf, size_t size)
query_short_name(object) is similar to query_name(), but doesn't contain any information about object...
Definition: item.cpp:513
FLAG_BLIND
#define FLAG_BLIND
If set, object cannot see (visually)
Definition: define.h:336
is_identified
int is_identified(const object *op)
Return true if the item is identified, either because it is of a type that doesn't ever need identifi...
Definition: item.cpp:1353
stringbuffer_append_spelldesc
static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell)
Append a terse description of the spell's name, level, discipline, and paths to a stringbuffer.
Definition: spellbook.cpp:64
object::type
uint8_t type
PLAYER, BULLET, etc.
Definition: object.h:348
FLAG_DAMNED
#define FLAG_DAMNED
The object is very cursed.
Definition: define.h:317
UPD_FLAGS
#define UPD_FLAGS
Definition: newclient.h:318
spell
with a maximum of six This is not so if you are wearing plate you receive no benefit Armour is additive with all the supplementry forms of which means that it lasts until the next semi permanent spell effect is cast upon the character spell
Definition: tome-of-magic.txt:44
change_exp
void change_exp(object *op, int64_t exp, const char *skill_name, int flag)
Changes experience to a player/monster.
Definition: living.cpp:2179
sproto.h
stringbuffer_append_string
void stringbuffer_append_string(StringBuffer *sb, const char *str)
Append a string to a string buffer instance.
Definition: stringbuffer.cpp:95
readable_message_type::message_subtype
uint8_t message_subtype
Message subtype to be sent to the client.
Definition: book.h:38
MSG_TYPE_SPELL
#define MSG_TYPE_SPELL
Spell related info.
Definition: newclient.h:414
living::Int
int8_t Int
Definition: living.h:36
random_roll
int random_roll(int min, int max, const object *op, int goodbad)
Roll a random number between min and max.
Definition: utils.cpp:42
SOUND_TYPE_SPELL
#define SOUND_TYPE_SPELL
Definition: newclient.h:337
find_skill_by_name
object * find_skill_by_name(object *who, const char *name)
This returns the skill pointer of the given name (the one that accumulates exp, has the level,...
Definition: skill_util.cpp:211
MAX_BUF
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
StringBuffer
A buffer that will be expanded as content is added to it.
Definition: stringbuffer.cpp:25
Settings::spell_failure_effects
uint8_t spell_failure_effects
Nasty backlash to spell failures.
Definition: global.h:271
living::Wis
int8_t Wis
Definition: living.h:36
method_ret
char method_ret
Define some standard return values for callbacks which don't need to return any other results.
Definition: ob_methods.h:14
ob_types.h
sounds.h
FLAG_WIZ
#define FLAG_WIZ
Object has special privilegies.
Definition: define.h:231
NDI_UNIQUE
#define NDI_UNIQUE
Print immediately, don't buffer.
Definition: newclient.h:265
object::name
sstring name
The name of the object, obviously...
Definition: object.h:319
get_learn_spell
int get_learn_spell(int stat)
Definition: living.cpp:2377
stringbuffer_trim_whitespace
void stringbuffer_trim_whitespace(StringBuffer *sb)
Trim trailing whitespace from a stringbuffer.
Definition: stringbuffer.cpp:222
object::env
object * env
Pointer to the object which is the environment.
Definition: object.h:301
check_spell_known
object * check_spell_known(object *op, const char *name)
Checks to see if player knows the spell.
Definition: spell_util.cpp:394
object::skill
sstring skill
Name of the skill this object uses/grants.
Definition: object.h:329
esrv_update_item
void esrv_update_item(int flags, object *pl, object *op)
Updates object *op for player *pl.
Definition: main.cpp:359
spellbook_type_apply
static method_ret spellbook_type_apply(object *book, object *applier, int aflags)
Applies a spellbook.
Definition: spellbook.cpp:156
die_roll
int die_roll(int num, int size, const object *op, int goodbad)
Roll a number of dice (2d3, 4d6).
Definition: utils.cpp:122
readable_message_type
Struct to store the message_type and message_subtype for signs and books used by the player.
Definition: book.h:36
MSG_TYPE_APPLY_FAILURE
#define MSG_TYPE_APPLY_FAILURE
Apply OK, but no/bad result.
Definition: newclient.h:607
UPD_NAME
#define UPD_NAME
Definition: newclient.h:321
get_readable_message_type
const readable_message_type * get_readable_message_type(object *readable)
Get the readable type for an object (hopefully book).
Definition: readable.cpp:2055
do_forget_spell
void do_forget_spell(object *op, const char *spell)
Erases spell from player's inventory.
Definition: apply.cpp:525
player::socket
socket_struct * socket
Socket information for this player.
Definition: player.h:107
ob_methods.h
object::stats
living stats
Str, Con, Dex, etc.
Definition: object.h:378
TRUE
#define TRUE
Definition: compat.h:11
MSG_TYPE_APPLY
#define MSG_TYPE_APPLY
Applying objects.
Definition: newclient.h:411
FLAG_CURSED
#define FLAG_CURSED
The object is cursed.
Definition: define.h:316
MSG_TYPE_APPLY_ERROR
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:604
living.h
SPELLBOOK
@ SPELLBOOK
Definition: object.h:208
register_describe
void register_describe(int ob_type, describe_func method)
Registers the describe method for the given type.
Definition: ob_types.cpp:80
FLAG_IDENTIFIED
#define FLAG_IDENTIFIED
Player knows full info about item.
Definition: define.h:261
identify
object * identify(object *op)
Identifies an item.
Definition: item.cpp:1426