Crossfire Server  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) {
344  stringbuffer_append_string(sb, spell->msg);
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 }
Error, serious thing.
Definition: logger.h:11
Sound-related defines.
#define FLAG_DAMNED
The object is very cursed.
Definition: define.h:305
char * stringbuffer_finish(StringBuffer *sb)
Deallocate the string buffer instance and return the string.
static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell)
Append a terse description of the spell&#39;s name, level, discipline, and paths to a stringbuffer...
Definition: spellbook.cpp:64
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:691
object * check_spell_known(object *op, const char *name)
Checks to see if player knows the spell.
Definition: spell_util.cpp:394
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
void do_forget_spell(object *op, const char *spell)
Erases spell from player&#39;s inventory.
Definition: apply.cpp:187
int rndm(int min, int max)
Returns a number between min and max.
Definition: utils.cpp:162
#define MSG_TYPE_APPLY_FAILURE
Apply OK, but no/bad result.
Definition: newclient.h:640
void register_describe(int ob_type, describe_func method)
Registers the describe method for the given type.
Definition: ob_types.cpp:80
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...) PRINTF_ARGS(6
int16_t grace
Grace.
Definition: living.h:44
#define MSG_TYPE_SPELL
Spell related info.
Definition: newclient.h:428
void esrv_update_item(int flags, object *pl, object *op)
Updates object *op for player *pl.
Definition: main.cpp:359
See Spellbook.
Definition: object.h:208
#define MSG_TYPE_SPELL_INFO
random info about spell, not related to failure/success
Definition: newclient.h:675
#define NDI_BLUE
Actually, it is Dodger Blue.
Definition: newclient.h:251
#define FLAG_BLIND
If set, object cannot see (visually)
Definition: define.h:324
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
#define PREFER_LOW
Definition: define.h:585
void stringbuffer_trim_whitespace(StringBuffer *sb)
Trim trailing whitespace from a stringbuffer.
void change_exp(object *op, int64_t exp, const char *skill_name, int flag)
Changes experience to a player/monster.
Definition: living.cpp:2179
#define FLAG_CONFUSED
Will also be unable to cast spells.
Definition: define.h:299
#define FLAG_BLESSED
Item has a blessing, opposite of cursed/damned.
Definition: define.h:365
uint8_t message_type
Message type to be sent to the client.
Definition: book.h:37
StringBuffer * stringbuffer_new(void)
Create a new string buffer.
#define TRUE
Definition: compat.h:11
#define MAX(x, y)
Definition: compat.h:24
Global type definitions and header inclusions.
struct player * contr
Pointer to the player which control this object.
Definition: object.h:284
StringBuffer * describe_spellpath_attenuation(const char *attenuation, int value, StringBuffer *buf)
Describe the specified path attenuation.
Definition: utils.cpp:507
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Sends message to player(s).
Definition: main.cpp:308
#define MSG_TYPE_APPLY
Applying objects.
Definition: newclient.h:425
#define NDI_NAVY
Definition: newclient.h:248
char method_ret
Define some standard return values for callbacks which don&#39;t need to return any other results...
Definition: ob_methods.h:14
uint32_t update_look
If true, we need to send the look window.
Definition: newserver.h:108
int16_t level
Level of creature or object.
Definition: object.h:361
int16_t sp
Spell points.
Definition: living.h:42
object * identify(object *op)
Identifies an item.
Definition: item.cpp:1446
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&#39;t overwrite ...
Definition: porting.cpp:202
void stringbuffer_append_printf(StringBuffer *sb, const char *format,...)
Append a formatted string to a string buffer instance.
#define METHOD_OK
Definition: ob_methods.h:15
object * env
Pointer to the object which is the environment.
Definition: object.h:301
void stringbuffer_append_string(StringBuffer *sb, const char *str)
Append a string to a string buffer instance.
#define QUERY_FLAG(xyz, p)
Definition: define.h:386
int8_t Wis
Use
Definition: living.h:36
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:637
void query_name(const object *op, char *buf, size_t size)
Describes an item.
Definition: item.cpp:594
#define FLAG_IDENTIFIED
Item is identifiable (e.g.
Definition: define.h:248
socket_struct * socket
Socket information for this player.
Definition: player.h:109
sstring msg
If this is a book/sign/magic mouth/etc.
Definition: object.h:330
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
Struct to store the message_type and message_subtype for signs and books used by the player...
Definition: book.h:36
int is_identified(const object *op)
Return true if the item is identified, either because it is of a type that doesn&#39;t ever need identifi...
Definition: item.cpp:1357
#define UPD_FLAGS
Definition: newclient.h:331
void do_learn_spell(object *op, object *spell, int special_prayer)
Actually makes op learn spell.
Definition: apply.cpp:146
struct Settings settings
Global settings.
Definition: init.cpp:139
void init_type_spellbook(void)
Initializer for the SPELLBOOK object type.
Definition: spellbook.cpp:46
int random_roll(int min, int max, const object *op, int goodbad)
Roll a random number between min and max.
Definition: utils.cpp:42
int get_learn_spell(int stat)
Definition: living.cpp:2377
#define FLAG_WIZ
Object has special privilegies.
Definition: define.h:218
living stats
Str, Con, Dex, etc.
Definition: object.h:378
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
Object type variables.
#define FLAG_CURSED
The object is cursed.
Definition: define.h:304
See Player.
Definition: object.h:112
#define object_decrease_nrof_by_one(xyz)
Definition: compat.h:32
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
sstring name
The name of the object, obviously...
Definition: object.h:319
uint8_t type
PLAYER, BULLET, etc.
Definition: object.h:348
void scroll_failure(object *op, int failure, int power)
op made some mistake with a scroll, this takes care of punishment.
Definition: apply.cpp:1254
#define UPD_NAME
Definition: newclient.h:334
#define MSG_TYPE_APPLY_SUCCESS
Was able to apply object.
Definition: newclient.h:639
object * inv
Pointer to the first object in the inventory.
Definition: object.h:298
#define FLAG_STARTEQUIP
Object was given to player at start.
Definition: define.h:255
sstring skill
Name of the skill this object uses/grants.
Definition: object.h:329
#define NDI_UNIQUE
Print immediately, don&#39;t buffer.
Definition: newclient.h:270
StringBuffer * buf
Definition: readable.cpp:1563
uint8_t message_subtype
Message subtype to be sent to the client.
Definition: book.h:38
Structure containing object statistics.
A buffer that will be expanded as content is added to it.
int8_t Int
Use
Definition: living.h:36
Object type functions and variables.
const readable_message_type * get_readable_message_type(object *readable)
Get the readable type for an object (hopefully book).
Definition: readable.cpp:2062
void query_short_name(const object *op, char *buf, size_t size)
query_short_name(object) is similar to query_name(), but doesn&#39;t contain any information about object...
Definition: item.cpp:518
uint8_t spell_failure_effects
Nasty backlash to spell failures.
Definition: global.h:270
int die_roll(int num, int size, const object *op, int goodbad)
Roll a number of dice (2d3, 4d6).
Definition: utils.cpp:122
#define SOUND_TYPE_SPELL
Definition: newclient.h:350
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:209
static method_ret spellbook_type_apply(object *book, object *applier, int aflags)
Applies a spellbook.
Definition: spellbook.cpp:156
uint32_t path_attuned
Paths the object is attuned to.
Definition: object.h:353
void register_apply(int ob_type, apply_func method)
Registers the apply method for the given type.
Definition: ob_types.cpp:62