Crossfire Server, Trunk
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
Definition: book.h:37
PLAYER
@ PLAYER
Definition: object.h:112
global.h
settings
struct Settings settings
Definition: init.cpp:139
FLAG_CONFUSED
#define FLAG_CONFUSED
Definition: define.h:311
llevError
@ llevError
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:51
object::inv
object * inv
Definition: object.h:298
safe_strcat
void safe_strcat(char *dest, const char *orig, size_t *curlen, size_t maxlen)
Definition: porting.cpp:202
FLAG_STARTEQUIP
#define FLAG_STARTEQUIP
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)
Definition: apply.cpp:484
stringbuffer_append_printf
void stringbuffer_append_printf(StringBuffer *sb, const char *format,...)
Definition: stringbuffer.cpp:138
stringbuffer_new
StringBuffer * stringbuffer_new(void)
Definition: stringbuffer.cpp:57
register_apply
void register_apply(int ob_type, apply_func method)
Definition: ob_types.cpp:62
METHOD_OK
#define METHOD_OK
Definition: ob_methods.h:15
PREFER_LOW
#define PREFER_LOW
Definition: define.h:564
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
Settings::spell_failure_effects
uint8_t spell_failure_effects
Definition: global.h:269
play_sound_player_only
void play_sound_player_only(player *pl, int8_t sound_type, object *emitter, int dir, const char *action)
Definition: sounds.cpp:51
FLAG_BLESSED
#define FLAG_BLESSED
Definition: define.h:369
NDI_NAVY
#define NDI_NAVY
Definition: newclient.h:233
scroll_failure
void scroll_failure(object *op, int failure, int power)
Definition: apply.cpp:1601
MSG_TYPE_SPELL_INFO
#define MSG_TYPE_SPELL_INFO
Definition: newclient.h:628
NDI_BLUE
#define NDI_BLUE
Definition: newclient.h:236
object::level
int16_t level
Definition: object.h:361
FLAG_BLIND
#define FLAG_BLIND
Definition: define.h:336
common_ob_describe
void common_ob_describe(const object *op, const object *observer, int use_media_tags, char *buf, size_t size)
Definition: describe.cpp:36
buf
StringBuffer * buf
Definition: readable.cpp:1552
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
Definition: newserver.h:104
init_type_spellbook
void init_type_spellbook(void)
Definition: spellbook.cpp:46
stringbuffer_finish
char * stringbuffer_finish(StringBuffer *sb)
Definition: stringbuffer.cpp:76
object::contr
struct player * contr
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
Definition: newclient.h:592
query_name
void query_name(const object *op, char *buf, size_t size)
Definition: item.cpp:592
calc_skill_exp
int64_t calc_skill_exp(const object *who, const object *op, const object *skill)
Definition: skill_util.cpp:667
query_short_name
void query_short_name(const object *op, char *buf, size_t size)
Definition: item.cpp:517
is_identified
int is_identified(const object *op)
Definition: item.cpp:1352
stringbuffer_append_spelldesc
static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell)
Definition: spellbook.cpp:64
object::type
uint8_t type
Definition: object.h:348
FLAG_DAMNED
#define FLAG_DAMNED
Definition: define.h:317
UPD_FLAGS
#define UPD_FLAGS
Definition: newclient.h:304
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)
Definition: living.cpp:2168
die_roll
int die_roll(int num, int size, const object *op, int goodbad)
Definition: utils.cpp:122
sproto.h
stringbuffer_append_string
void stringbuffer_append_string(StringBuffer *sb, const char *str)
Definition: stringbuffer.cpp:95
readable_message_type::message_subtype
uint8_t message_subtype
Definition: book.h:38
MSG_TYPE_SPELL
#define MSG_TYPE_SPELL
Definition: newclient.h:400
living::Int
int8_t Int
Definition: living.h:36
SOUND_TYPE_SPELL
#define SOUND_TYPE_SPELL
Definition: newclient.h:323
nlohmann::detail::void
j template void())
Definition: json.hpp:4099
find_skill_by_name
object * find_skill_by_name(object *who, const char *name)
Definition: skill_util.cpp:211
MAX_BUF
#define MAX_BUF
Definition: define.h:35
living::Wis
int8_t Wis
Definition: living.h:36
method_ret
char method_ret
Definition: ob_methods.h:14
ob_types.h
sounds.h
FLAG_WIZ
#define FLAG_WIZ
Definition: define.h:231
describe_spellpath_attenuation
StringBuffer * describe_spellpath_attenuation(const char *attenuation, int value, StringBuffer *buf)
Definition: utils.cpp:507
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:251
object::name
sstring name
Definition: object.h:319
get_learn_spell
int get_learn_spell(int stat)
Definition: living.cpp:2366
stringbuffer_trim_whitespace
void stringbuffer_trim_whitespace(StringBuffer *sb)
Definition: stringbuffer.cpp:222
object::env
object * env
Definition: object.h:301
check_spell_known
object * check_spell_known(object *op, const char *name)
Definition: spell_util.cpp:394
object::skill
sstring skill
Definition: object.h:329
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)
Definition: spellbook.cpp:103
esrv_update_item
void esrv_update_item(int flags, object *pl, object *op)
Definition: main.cpp:359
random_roll
int random_roll(int min, int max, const object *op, int goodbad)
Definition: utils.cpp:42
spellbook_type_apply
static method_ret spellbook_type_apply(object *book, object *applier, int aflags)
Definition: spellbook.cpp:156
readable_message_type
Definition: book.h:36
MSG_TYPE_APPLY_FAILURE
#define MSG_TYPE_APPLY_FAILURE
Definition: newclient.h:593
UPD_NAME
#define UPD_NAME
Definition: newclient.h:307
get_readable_message_type
const readable_message_type * get_readable_message_type(object *readable)
Definition: readable.cpp:2039
do_forget_spell
void do_forget_spell(object *op, const char *spell)
Definition: apply.cpp:525
player::socket
socket_struct * socket
Definition: player.h:107
rndm
int rndm(int min, int max)
Definition: utils.cpp:162
ob_methods.h
object::stats
living stats
Definition: object.h:378
StringBuffer
Definition: stringbuffer.cpp:25
TRUE
#define TRUE
Definition: compat.h:11
MSG_TYPE_APPLY
#define MSG_TYPE_APPLY
Definition: newclient.h:397
FLAG_CURSED
#define FLAG_CURSED
Definition: define.h:316
MSG_TYPE_APPLY_ERROR
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:590
living.h
SPELLBOOK
@ SPELLBOOK
Definition: object.h:208
register_describe
void register_describe(int ob_type, describe_func method)
Definition: ob_types.cpp:80
FLAG_IDENTIFIED
#define FLAG_IDENTIFIED
Definition: define.h:261
identify
object * identify(object *op)
Definition: item.cpp:1425