Crossfire Server, Trunk  R21670
spellbook.c
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(ob_methods *context, object *book, object *applier, int aflags);
39 static void spellbook_type_describe(
40  const ob_methods *context, 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 
52 static const char *const numbers_10[] = {
53  "zero", "ten", "twenty", "thirty", "fourty", "fifty", "sixty", "seventy",
54  "eighty", "ninety"
55 };
56 
58 static const char *const ordinals[] = {
59  "zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh",
60  "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth",
61  "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth",
62  "nineteenth", "twentieth"
63 };
64 
66 static const char *const ordinals_10[] = {
67  "zeroth", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth",
68  "seventieth", "eightieth", "ninetieth"
69 };
70 
82 void stringbuffer_append_ordinal(StringBuffer *sb, const int n) {
83  if (n > 99 || n < 0) {
84  stringbuffer_append_printf(sb, "%d.", n);
85  } else if (n <= 20) {
87  } else if (n % 10 == 0) {
89  } else {
90  stringbuffer_append_printf(sb, "%s%s", ordinals_10[n/10], ordinals[n%10]);
91  }
92 }
93 
94 
108 static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell) {
109  stringbuffer_append_string(sb, "(a ");
111  stringbuffer_append_string(sb, " level ");
112 
113  if (!spell->skill) {
114  /* Can this even happen? */
115  stringbuffer_append_string(sb, "mystery");
116  } else if (spell->stats.grace) {
117  /* Otherwise we get "a second level praying" when it should be "a second
118  * level prayer". */
119  stringbuffer_append_string(sb, "prayer");
120  } else {
121  stringbuffer_append_string(sb, spell->skill);
122  }
123 
124  if (spell->path_attuned) {
125  stringbuffer_append_string(sb, ") ");
126  describe_spellpath_attenuation("paths", spell->path_attuned, sb);
127  } else {
129  }
130 }
131 
151  const ob_methods *context, const object *book, const object *observer,
152  const int use_media_tags, char *buf, size_t size) {
153  if (!is_identified(book)) {
154  /* Without querying the name, spellbooks end up examining
155  * as "That is:", with no name at all
156  * This should tell the player just as little as the inventory view.
157  *
158  * SilverNexus 2020-11-28
159  */
160  query_name(book, buf, size-1);
161  return;
162  }
163 
164  size_t len;
165  /* TODO check if this generates the "of foo" so we don't end up with
166  "spellbook of medium fireball of medium fireball" I think it probably does */
167  common_ob_describe(context, book, observer, use_media_tags, buf, size);
168  len = strlen(buf);
169 
170  const object *spell = book->inv;
171  if (!spell) {
172  snprintf(buf+len, size-len, " (blank)");
173  return;
174  }
175 
179  char *const desc = stringbuffer_finish(sb);
180  safe_strcat(buf, desc, &len, size);
181  free(desc);
182 }
183 
205 static method_ret spellbook_type_apply(ob_methods *context, object *book, object *applier, int aflags) {
206  object *skapplier, *spell, *spell_skill;
207  char level[100];
208 
209  /* Must be applied by a player. */
210  if (applier->type == PLAYER) {
211  if (QUERY_FLAG(applier, FLAG_BLIND) && !QUERY_FLAG(applier, FLAG_WIZ)) {
213  "You are unable to read while blind.");
214  return METHOD_OK;
215  }
216 
217  spell = book->inv;
218  if (!spell) {
219  LOG(llevError, "apply_spellbook: Book %s has no spell in it!\n", book->name);
221  "The spellbook symbols make no sense.");
222  return METHOD_OK;
223  }
224 
225  if (QUERY_FLAG(book, FLAG_CURSED) || QUERY_FLAG(book, FLAG_DAMNED)) {
226  char name[MAX_BUF];
227  /* Player made a mistake, let's shake her/him :) */
228  int failure = -35;
229 
231  failure = -rndm(35, 100);
232  query_name(book, name, MAX_BUF);
234  "The %s was %s!",
235  name, QUERY_FLAG(book, FLAG_DAMNED) ? "damned" : "cursed");
236  scroll_failure(applier, failure, (spell->level+4)*7);
237  if (QUERY_FLAG(book, FLAG_DAMNED)
238  && check_spell_known(applier, spell->name)
239  && die_roll(1, 10, applier, 1) < 2)
240  /* Really unlucky player, better luck next time */
241  do_forget_spell(applier, spell->name);
242  book = object_decrease_nrof_by_one(book);
243  if (book && (!QUERY_FLAG(book, FLAG_IDENTIFIED))) {
244  /* Well, not everything is lost, player now knows the
245  * book is cursed/damned. */
246  book = identify(book);
247  if (book->env)
248  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
249  else
250  applier->contr->socket.update_look = 1;
251  }
252  return METHOD_OK;
253  }
254 
255  /* This section moved before literacy check */
256  if (check_spell_known(applier, spell->name)) {
257  // If we already know the spell, it makes sense we know what the spell is.
258  if (book && (!QUERY_FLAG(book, FLAG_IDENTIFIED))) {
259  book = identify(book);
260  if (book->env)
261  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
262  else
263  applier->contr->socket.update_look = 1;
264  }
265  draw_ext_info_format(NDI_UNIQUE, 0, applier,
267  "You already know the spell %s.\n", spell->name);
268  return METHOD_OK;
269  }
270  /* check they have the right skills to learn the spell in the first place */
271  if (spell->skill) {
272  spell_skill = find_skill_by_name(applier, spell->skill);
273  if (!spell_skill) {
274  draw_ext_info_format(NDI_UNIQUE, 0, applier,
276  "You lack the skill %s to use this spell",
277  spell->skill);
278  return METHOD_OK;
279  }
280 
281  int skill_lev_diff = spell->level - spell_skill->level;
282  if (skill_lev_diff > 0) {
283  if (skill_lev_diff < 2)
285  "The spell described in this book is just beyond your skill in %s.", spell->skill);
286  else if (skill_lev_diff < 3)
288  "The spell described in this book is slightly beyond your skill in %s.", spell->skill);
289  else if (skill_lev_diff < 5)
291  "The spell described in this book is beyond your skill in %s.", spell->skill);
292  else if (skill_lev_diff < 8)
294  "The spell described in this book is quite a bit beyond your skill in %s.", spell->skill);
295  else if (skill_lev_diff < 15)
297  "The spell described in this book is way beyond your skill in %s.", spell->skill);
298  else
300  "The spell described in this book is totally beyond your skill in %s.", spell->skill);
301  return METHOD_OK;
302  }
303  }
304 
305  /* need a literacy skill to learn spells. Also, having a literacy level
306  * lower than the spell will make learning the spell more difficult */
307  skapplier = find_skill_by_name(applier, book->skill);
308  if (!skapplier) {
310  "You can't read! You will need this skill before you can comprehend the ideas written within.");
311  return METHOD_OK;
312  }
313 
314  /* We know the player has all the right skills so check how well they can read. */
315  int read_level;
316  read_level = skapplier->level;
317 
318  /* blessed books are easier to read */
319  if (QUERY_FLAG(book, FLAG_BLESSED))
320  read_level += 5;
321 
322  /* If the players read level is less than 10 levels lower than the spellbook, they can't read it */
323  int lev_diff;
324  lev_diff = spell->level - (read_level+10);
325  if (!QUERY_FLAG(applier, FLAG_WIZ) && lev_diff > 0) {
326  if (lev_diff < 2)
328  "You recognise most of the words but this book is just beyond your comprehension.");
329  else if (lev_diff < 3)
331  "You recognise many of the words but this book is slightly beyond your comprehension.");
332  else if (lev_diff < 5)
334  "You recognise some of the words but this book is slightly beyond your comprehension.");
335  else if (lev_diff < 8)
337  "You recognise some of the words but this book is beyond your comprehension.");
338  else if (lev_diff < 15)
340  "You recognise a few of the words but this book is beyond your comprehension.");
341  else
343  "You recognise a few of the words but this book is totally beyond your comprehension.");
344  return METHOD_OK;
345  }
346 
347  if (!QUERY_FLAG(book, FLAG_IDENTIFIED)) {
348  book = identify(book);
349  if (book->env)
350  esrv_update_item(UPD_FLAGS|UPD_NAME, applier, book);
351  else
352  applier->contr->socket.update_look = 1;
353  spell = book->inv;
354 
355  /* If they hadn't previously IDed the book, they didn't know what
356  * spell it contained, so tell them here.
357  */
360  char *const desc = stringbuffer_finish(sb);
362  "The spellbook contains %s %s.", spell->name, desc);
363  free(desc);
364  }
365 
366  /* Player has the right skills and enough skill to attempt to learn the spell with the logic as follows:
367  *
368  * 1- MU spells use Int to learn, Cleric spells use Wisdom
369  *
370  * 2- The learner's skill level in literacy adjusts the chance
371  * to learn a spell.
372  *
373  * 3 -Automatically fail to learn if you read while confused
374  *
375  * Overall, chances are the same but a player will find having a high
376  * literacy rate very useful! -b.t.
377  */
378  char desc[MAX_BUF];
379  const readable_message_type *msgType = get_readable_message_type(book);
380 
381  if (QUERY_FLAG(applier, FLAG_CONFUSED)) {
383  "In your confused state you flub the wording of the text!");
384  scroll_failure(applier, 0-random_roll(0, spell->level, applier, PREFER_LOW), MAX(spell->stats.sp, spell->stats.grace));
385  } else if (QUERY_FLAG(book, FLAG_STARTEQUIP)
386  || (random_roll(0, 100, applier, PREFER_LOW)-(5*read_level)) < get_learn_spell(spell->stats.grace ? applier->stats.Wis : applier->stats.Int)) {
387  query_short_name(book, desc, sizeof(desc));
389  msgType->message_type, msgType->message_subtype,
390  "You open the %s and start reading.", desc);
391  if (spell->msg != NULL) {
393  stringbuffer_append_string(sb, spell->msg);
395  char *const fluff = stringbuffer_finish(sb);
397  free(fluff);
398  }
400  "You succeed in learning the spell!");
401  do_learn_spell(applier, spell, 0);
402 
403  /* xp gain to literacy for spell learning */
404  if (!QUERY_FLAG(book, FLAG_STARTEQUIP))
405  change_exp(applier, calc_skill_exp(applier, book, skapplier), skapplier->skill, 0);
406  } else {
407  play_sound_player_only(applier->contr, SOUND_TYPE_SPELL, book, 0, "fumble");
409  "You fail to learn the spell.\n");
410  }
412  }
413  return METHOD_OK;
414 }
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...)
Definition: main.c:316
int8_t Int
Definition: living.h:36
#define FLAG_DAMNED
Definition: define.h:318
int64_t calc_skill_exp(const object *who, const object *op, const object *skill)
Definition: skill_util.c:646
object * check_spell_known(object *op, const char *name)
Definition: spell_util.c:433
void do_forget_spell(object *op, const char *spell)
Definition: apply.c:526
#define MSG_TYPE_APPLY_FAILURE
Definition: newclient.h:599
void register_describe(int ob_type, describe_func method)
Definition: ob_types.c:80
void stringbuffer_trim_whitespace(StringBuffer *sb)
Definition: stringbuffer.c:166
#define MSG_TYPE_SPELL
Definition: newclient.h:387
static method_ret spellbook_type_apply(ob_methods *context, object *book, object *applier, int aflags)
Definition: spellbook.c:205
StringBuffer * stringbuffer_new(void)
Definition: stringbuffer.c:57
void esrv_update_item(int flags, object *pl, object *op)
Definition: main.c:343
#define MSG_TYPE_SPELL_INFO
Definition: newclient.h:634
#define NDI_BLUE
Definition: newclient.h:226
socket_struct socket
Definition: player.h:94
#define PREFER_LOW
Definition: define.h:601
#define FLAG_CONFUSED
Definition: define.h:312
#define FLAG_BLESSED
Definition: define.h:378
uint8_t message_type
Definition: book.h:37
#define TRUE
Definition: compat.h:10
uint32_t path_attuned
Definition: object.h:345
#define MAX(x, y)
Definition: compat.h:20
int16_t sp
Definition: living.h:42
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.c:311
static const char *const ordinals_10[]
Definition: spellbook.c:66
#define MSG_TYPE_APPLY
Definition: newclient.h:384
#define NDI_NAVY
Definition: newclient.h:223
char method_ret
Definition: ob_methods.h:14
int rndm(int min, int max)
Definition: utils.c:162
uint32_t update_look
Definition: newserver.h:104
int is_identified(const object *op)
Definition: item.c:1316
void stringbuffer_append_string(StringBuffer *sb, const char *str)
Definition: stringbuffer.c:95
#define METHOD_OK
Definition: ob_methods.h:15
int8_t Wis
Definition: living.h:36
#define snprintf
Definition: win32.h:46
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:596
#define FLAG_IDENTIFIED
Definition: define.h:261
int die_roll(int num, int size, const object *op, int goodbad)
Definition: utils.c:122
static const char *const ordinals[]
Definition: spellbook.c:58
const char * name
Definition: object.h:311
struct obj * env
Definition: object.h:293
#define UPD_FLAGS
Definition: newclient.h:290
void do_learn_spell(object *op, object *spell, int special_prayer)
Definition: apply.c:485
void register_apply(int ob_type, apply_func method)
Definition: ob_types.c:62
struct pl * contr
Definition: object.h:276
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
static void spellbook_type_describe(const ob_methods *context, const object *book, const object *observer, const int use_media_tags, char *buf, const size_t size)
Definition: spellbook.c:150
#define FLAG_WIZ
Definition: define.h:231
void change_exp(object *op, int64_t exp, const char *skill_name, int flag)
Definition: living.c:2162
#define MAX_BUF
Definition: define.h:35
void common_ob_describe(const ob_methods *context, const object *op, const object *observer, int use_media_tags, char *buf, size_t size)
Definition: describe.c:37
const char * skill
Definition: object.h:321
#define FLAG_CURSED
Definition: define.h:317
Definition: object.h:107
void safe_strcat(char *dest, const char *orig, size_t *curlen, size_t maxlen)
Definition: porting.c:346
#define FLAG_BLIND
Definition: define.h:337
#define object_decrease_nrof_by_one(xyz)
Definition: compat.h:28
void play_sound_player_only(player *pl, int8_t sound_type, object *emitter, int dir, const char *action)
Definition: sounds.c:51
int16_t grace
Definition: living.h:44
living stats
Definition: object.h:370
uint8_t type
Definition: object.h:340
struct Settings settings
Definition: init.c:39
void query_short_name(const object *op, char *buf, size_t size)
Definition: item.c:508
void scroll_failure(object *op, int failure, int power)
Definition: apply.c:1578
#define UPD_NAME
Definition: newclient.h:293
#define MSG_TYPE_APPLY_SUCCESS
Definition: newclient.h:598
const char * msg
Definition: object.h:322
#define FLAG_STARTEQUIP
Definition: define.h:268
object * identify(object *op)
Definition: item.c:1386
void stringbuffer_append_printf(StringBuffer *sb, const char *format,...)
Definition: stringbuffer.c:104
StringBuffer * describe_spellpath_attenuation(const char *attenuation, int value, StringBuffer *buf)
Definition: utils.c:534
void stringbuffer_append_ordinal(StringBuffer *sb, const int n)
Definition: spellbook.c:82
struct obj * inv
Definition: object.h:290
const readable_message_type * get_readable_message_type(object *readable)
Definition: readable.c:2233
#define NDI_UNIQUE
Definition: newclient.h:245
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
int get_learn_spell(int stat)
Definition: living.c:2360
uint8_t message_subtype
Definition: book.h:38
void query_name(const object *op, char *buf, size_t size)
Definition: item.c:583
int random_roll(int min, int max, const object *op, int goodbad)
Definition: utils.c:42
static void stringbuffer_append_spelldesc(StringBuffer *sb, const object *spell)
Definition: spellbook.c:108
int16_t level
Definition: object.h:353
uint8_t spell_failure_effects
Definition: global.h:270
#define SOUND_TYPE_SPELL
Definition: newclient.h:309
object * find_skill_by_name(object *who, const char *name)
Definition: skill_util.c:192
char * stringbuffer_finish(StringBuffer *sb)
Definition: stringbuffer.c:76
static const char *const numbers_10[]
Definition: spellbook.c:52
void init_type_spellbook(void)
Definition: spellbook.c:46