Lyonesse/bin/
Lyonesse/doc/eng/
Lyonesse/doc/ita/
Lyonesse/lib/
Lyonesse/lib/buildings/
Lyonesse/lib/clans/
Lyonesse/lib/data/
Lyonesse/lib/etc/
Lyonesse/lib/house/
Lyonesse/lib/misc/
Lyonesse/lib/plralias/A-E/
Lyonesse/lib/plralias/F-J/
Lyonesse/lib/plralias/K-O/
Lyonesse/lib/plralias/P-T/
Lyonesse/lib/plralias/U-Z/
Lyonesse/lib/plralias/ZZZ/
Lyonesse/lib/plrobjs/A-E/
Lyonesse/lib/plrobjs/F-J/
Lyonesse/lib/plrobjs/K-O/
Lyonesse/lib/plrobjs/P-T/
Lyonesse/lib/plrobjs/U-Z/
Lyonesse/lib/plrobjs/ZZZ/
Lyonesse/lib/plrsave/A-E/
Lyonesse/lib/plrsave/F-J/
Lyonesse/lib/plrsave/K-O/
Lyonesse/lib/plrsave/P-T/
Lyonesse/lib/plrsave/U-Z/
Lyonesse/lib/plrsave/ZZZ/
Lyonesse/lib/ships/
Lyonesse/lib/stables/
Lyonesse/lib/text/help/
Lyonesse/lib/world/
Lyonesse/lib/world/bld/
Lyonesse/lib/world/ship/
Lyonesse/lib/world/shp/
Lyonesse/lib/world/wls/
Lyonesse/lib/world/wls/Life/
Lyonesse/lib/world/wls/Map/
Lyonesse/log/
/**************************************************************************
 * #   #   #   ##   #  #  ###   ##   ##  ###       http://www.lyonesse.it *
 * #    # #   #  #  ## #  #    #    #    #                                *
 * #     #    #  #  # ##  ##    #    #   ##   ## ##  #  #  ##             *
 * #     #    #  #  # ##  #      #    #  #    # # #  #  #  # #            *
 * ###   #     ##   #  #  ###  ##   ##   ###  #   #  ####  ##    Ver. 1.0 *
 *                                                                        *
 * -Based on CircleMud & Smaug-     Copyright (c) 2001-2002 by Mithrandir *
 *                                                                        *
 * ********************************************************************** *
 *                                                                        *
 * File: magic2.c                                                         *
 *                                                                        *
 * - Spellbook system                                                     *
 * - New magic system based upon spell memorization                       *
 * - Study/Copy skill (for acquiring spells from objects)                 *
 * - Enchant skill                                                        *
 *                                                                        *
 **************************************************************************/

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "spells.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "constants.h"

/* external variables */
extern SPELL_INFO_DATA		spell_info[];
extern int			prac_params[4][NUM_CLASSES];


#define SINFO			spell_info[spellnum]
#define LEARNED(ch)		(prac_params[0][(int)GET_CLASS(ch)])


/* ************************************************************** */
/* Spellbooks Code                                                */
/* ************************************************************** */

/* ================================================================ */
/* Low-level functions to create or destroy pages and books         */
/* ================================================================ */

/* instert in a book a new page with a random spells */
void add_rand_book_page(SPELLBOOK *book)
{
	BOOK_PAGE *page;
	int spellnum = number(1, LAST_SPELL);

	CREATE(page, BOOK_PAGE, 1);
	page->spellnum		= spellnum;
	page->status		= number(60, 90);
	page->flags			= PAGE_WRITTEN;
	page->spellname		= str_dup(SINFO.name);

	LINK(page, book->first_page, book->last_page, next, prev);
	book->num_of_pages++;
	book->num_of_spells++;
}

/* initialize a new blank page for a book */
BOOK_PAGE *new_book_page(void)
{
	BOOK_PAGE *page;

	CREATE(page, BOOK_PAGE, 1);
	page->spellnum		= NOTHING;
	page->status		= number(80, 100);
	page->flags			= PAGE_BLANK;
	page->spellname		= NULL;

	return (page);
}

/* remove a page from a book */
void remove_book_page(SPELLBOOK *book, BOOK_PAGE *page)
{
	if ( page->spellname )
		STRFREE(page->spellname);
	UNLINK(page, book->first_page, book->last_page, next, prev);
	DISPOSE(page);
	book->num_of_pages--;
}

/* initialize a new spellbook */
SPELLBOOK *new_spellbook(OBJ_DATA *obj, int type, bool rand)
{
	SPELLBOOK *book;

	CREATE(book, SPELLBOOK, 1);
	book->first_page	= NULL;
	book->last_page		= NULL;
	book->num_of_pages	= 0;
	book->num_of_spells	= 0;
	book->type			= type;

	if ( rand )
	{
		int p;

		for ( p = 0; p < MIN_PAGES; p++ )
			add_rand_book_page( book );
	}

	return (book);
}

/* ================================================================ */
/* Spellbook info                                                   */
/* ================================================================ */

int find_page_cond( int percent )
{
	int num;

	if		(percent >= 100)	num = 0;
	else if (percent > 80)		num = 1;
	else if (percent > 65)		num = 2;
	else if (percent > 40)		num = 3;
	else if (percent > 25)		num = 4;
	else if (percent >  9)		num = 5;
	else if (percent >  0)		num = 6;
	else						num = 7;

	return (num);
}

/* lists all the pages in a spellbook */
void ListSpellsInBook(CHAR_DATA *ch, SPELLBOOK *book, bool dampage)
{
	BOOK_PAGE *page, *next_page = NULL;
	char bbuf[MAX_STRING_LENGTH];
	int pn, cond;

	*bbuf = '\0';

	send_to_char("The spellbook contains the following spells:\r\n", ch);
	for ( pn = 0, page = book->first_page; page; page = next_page, pn++ )
	{
		next_page = page->next;

		/* ruined pages are unreadables */
		if ( page->flags == PAGE_RUINED )
			strcat(bbuf, " - an unreadable page");
		else if ( page->flags == PAGE_BLANK )
		{
			cond = find_page_cond(percentage(page->status, 100));
			sprintf(bbuf+strlen(bbuf), " - a blank page that looks %s",
				obj_cond_table[cond]);
		}
		else
		{
			cond = find_page_cond(percentage(page->status, 100));
			sprintf(bbuf+strlen(bbuf), " - %s %s page that contains: '&b&3%s&0'",
				AN(obj_cond_table[cond]), obj_cond_table[cond], page->spellname);
		}
		strcat(bbuf, ".\r\n");

		/* take a chance to ruin the page */
		if ( dampage && !number(0, 5) )
		{
			page->status--;

			if ( page->status <= 0 )
			{
				remove_book_page(book, page);
				send_to_char("The page crumbles under your fingers.\r\n",ch);
			}
			else if ( page->status < 5 )
				page->flags = PAGE_RUINED;
		}
	}

	page_string(ch->desc, bbuf, 1);
}

/*
 * called from look_read in act.informative.c
 *
 * read the spellbook
 */
void ReadSpellbook(CHAR_DATA *ch, OBJ_DATA *obj, char *argument)
{
	SPELLBOOK *book;

	if ( !obj->special || !OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
		return;

	if ( percentage(GET_OBJ_COND(obj), GET_OBJ_MAXCOND(obj)) < 10 )
	{
		send_to_char("The book is too damaged to be readable.\r\n", ch);
		return;
	}

	book = (SPELLBOOK *) obj->special;

	ch_printf(ch, "It's a spellbook with %d pages over a maximum of %d.\r\n",
		book->num_of_pages, book_types[book->type].max_num_pages);

	ListSpellsInBook(ch, book, TRUE);

	/* take a chance to damage the book */
	check_damage_obj(ch, obj, 4);
}

/* ================================================================ */
/* Spellbook checks                                                 */
/* ================================================================ */

/* check to see if the spellbook contains the spell */
BOOK_PAGE *BookHasSpell(SPELLBOOK *book, int sn)
{
	BOOK_PAGE *page = NULL;

	for ( page = book->first_page; page; page = page->next )
	{
		if ( page->flags == PAGE_RUINED )
			continue;

		if ( page->spellnum == sn )
			break;
	}

	return (page);
}


void MakePageBlank(BOOK_PAGE *page)
{
	if ( !page )
		return;

	if (page->spellname)
		STRFREE(page->spellname);
	page->spellnum	= NOTHING;
	page->flags	= PAGE_BLANK;
}

/* check for a blank page into the spellbook */
int BookHasBlankPage(SPELLBOOK *book)
{
	BOOK_PAGE *page = NULL;
	int count = 0;

	for ( page = book->first_page; page; page = page->next )
	{
		if ( page->flags == PAGE_RUINED )
			continue;

		if ( page->flags == PAGE_BLANK )
			count++;
	}

	return (count);
}

/*
 * recursive search of a spellbook into inventory
 * and carried containers
 *
 * returns SPELLBOOK data of object
 */
SPELLBOOK *FindSpellbook(OBJ_DATA *list, long owner_id)
{
	SPELLBOOK *book = NULL;
	OBJ_DATA *obj;

	for ( obj = list; obj; obj = obj->next_content )
	{
		if ( obj->special && OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) &&
			GET_OBJ_OWNER(obj) == owner_id )
		{
			book = (SPELLBOOK *) obj->special;
			break;
		}

		if ( GET_OBJ_TYPE(obj) == ITEM_CONTAINER )
		{
			if ( (book = FindSpellbook(obj->first_content, owner_id)) )
				break;
		}
	}

	return ( book );
}


/*
 * recursive search of a spellbook into inventory
 * and carried containers
 *
 * returns the object
 */
OBJ_DATA *FindObjSpellbook( OBJ_DATA *list, long owner_id )
{
	OBJ_DATA *obj;

	for ( obj = list; obj; obj = obj->next_content )
	{
		if ( obj->special && OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) &&
			GET_OBJ_OWNER(obj) == owner_id )
			break;

		if ( GET_OBJ_TYPE(obj) == ITEM_CONTAINER )
		{
			if ( (obj = FindObjSpellbook(obj->first_content, owner_id)) )
				break;
		}
	}

	return ( obj );
}


/* add a blank page to the spellbook */
bool AddBlankPage( SPELLBOOK *book )
{
	BOOK_PAGE *page;
	
	if ( book->num_of_pages >= book_types[book->type].max_num_pages )
		return (FALSE);

	page = new_book_page();

	LINK(page, book->first_page, book->last_page, next, prev);
	book->num_of_pages++;

	return (TRUE);
}

/* add the spell to the spellbook */
bool AddSpellToBook(CHAR_DATA *ch, SPELLBOOK *book, int sn)
{
	BOOK_PAGE *page;

	for (page = book->first_page; page; page = page->next)
	{
		if (page->flags == PAGE_RUINED)	continue;
		if (page->flags == PAGE_BLANK)	break;
	}

	if (!page)
	{
		send_to_char("There isn't a blank page to write on.\r\n", ch);
		return (FALSE);
	}

	/* take a chance to ruin the page */
	if ( !number(0, 5) )
	{
		page->status--;
		
		if ( page->status <= 0 )
		{
			remove_book_page(book, page);
			send_to_char("The page crumbles under your fingers.\r\n",ch);
			return (FALSE);
		}
		else if ( page->status < 5 )
		{
			page->flags = PAGE_RUINED;
			return (FALSE);
		}
	}

	page->spellnum		= sn;
	if (page->spellname)
		STRFREE(page->spellname);
	page->spellname		= str_dup(skill_name(sn));
	page->flags			= PAGE_WRITTEN;

	book->num_of_spells++;

	return (TRUE);
}

int AddNewSpell(CHAR_DATA *ch, int spellnum)
{
	SPELLBOOK *sbook;

	if ( !(sbook = FindSpellbook(ch->first_carrying, GET_IDNUM(ch))) )
	{
		send_to_char("You need one of yours spellbook.\r\n", ch);
		return (1);
	}
	
	if ( !BookHasSpell(sbook, spellnum ) )
	{
		if ( !BookHasBlankPage(sbook) )
		{
			if ( !AddBlankPage(sbook) )
			{
				send_to_char("You can't add any more pages to this spellbook.\r\n", ch);
				return (1);
			}
		}
		AddSpellToBook(ch, sbook, spellnum);
	}

	return (0);
}

/*
 * called from do_start() for new sorcerers
 */
void create_new_spellbook(CHAR_DATA *ch, int type)
{
	OBJ_DATA *obj = create_obj();

	GET_OBJ_TYPE(obj)		= ITEM_SPELLBOOK;
	GET_OBJ_COST(obj)		= 1000 * book_types[type].mult;
	GET_OBJ_LEVEL(obj)		= GET_LEVEL(ch);
	GET_OBJ_OWNER(obj)		= GET_IDNUM(ch);
	GET_OBJ_VAL(obj, 0)		= type;
	GET_OBJ_WEIGHT(obj)		= 20 * book_types[type].mult;

	obj->name				= str_dup("book spellbook");
	obj->short_description	= str_dup("a white leather covered book");
	obj->description		= str_dup("A white leather covered book with runes on the front has been left here.");

	SET_BIT(GET_OBJ_EXTRA(obj), ITEM_MAGIC | ITEM_UNIQUE | ITEM_IS_SPELLBOOK | ITEM_NODONATE);
	SET_BIT(GET_OBJ_WEAR(obj), ITEM_WEAR_TAKE);

	obj->special = new_spellbook(obj, type, FALSE);
	obj_to_char(obj, ch);
}

/* immortal command to create a new spellbook */
ACMD(do_newbook)
{
	OBJ_DATA *obj = create_obj();

	GET_OBJ_TYPE(obj)		= ITEM_SPELLBOOK;
	GET_OBJ_COST(obj)		= 1000 * book_types[BOOK_BOOK].mult;
	GET_OBJ_LEVEL(obj)		= GET_LEVEL(ch);
	GET_OBJ_OWNER(obj)		= GET_IDNUM(ch);
	GET_OBJ_VAL(obj, 0)		= BOOK_BOOK;
	GET_OBJ_WEIGHT(obj)		= 20 * book_types[BOOK_BOOK].mult;

	if (obj->name)
		free(obj->name);
	obj->name				= str_dup("book spellbook");

	if (obj->short_description)
		free(obj->short_description);
	obj->short_description	= str_dup("a red leather covered book");

	if (obj->description)
		free(obj->description);
	obj->description		= str_dup("A red leather covered book with runes on the front has been left here.");

	SET_BIT(GET_OBJ_EXTRA(obj), ITEM_MAGIC | ITEM_UNIQUE | ITEM_IS_SPELLBOOK);
	SET_BIT(GET_OBJ_WEAR(obj), ITEM_WEAR_TAKE);

	obj->special = new_spellbook(obj, BOOK_BOOK, TRUE);

	obj_to_char(obj, ch);
	send_to_char("You create a new spellbook out of the blue.\r\n", ch);
}


/* *************************************************************** */
/* Magic System based upon memorized spells and spellbooks         */
/* *************************************************************** */

/* list of memorized spells */
void ListMemSpells(CHAR_DATA *ch)
{
	char buf[MAX_STRING_LENGTH];
	int spellnum;

	if ( IS_NPC(ch) )
		return;

	send_to_char("You have memorized the following spells:\r\n", ch);

	*buf = '\0';
	for (spellnum = 1; spellnum <= MAX_SPELLS; spellnum++)
	{
		if ( MEMORIZED(ch, spellnum) )
			sprintf(buf+strlen(buf), " [%d] '&b&3%s&0'\r\n", 
				MEMORIZED(ch, spellnum), SINFO.name);
	}

	if ( !*buf )
		strcat(buf, "None.\r\n");

	page_string(ch->desc, buf, 1);
}

/* return the maximum number of spell memorizable by ch */
int MaxSpellsCanMem(CHAR_DATA *ch)
{
	int num;

	if ( !ch || IS_NPC(ch) )
		return (0);

	num = (int) GET_LEVEL(ch);
	num += (int) int_app[GET_INT(ch)].memspl;
	
	return (URANGE(1, num, MAX_MEM_SPELLS));
}

/* return the number of spells memorized by ch */
int NumSpellsMem(CHAR_DATA *ch)
{
	register int sn;
	int num;

	if ( !ch || IS_NPC(ch) )
		return (0);

	for ( sn = 1; sn <= MAX_SPELLS; sn++ )
	{
		if ( MEMORIZED(ch, sn) )
			num += MEMORIZED(ch, sn);
	}

	return (num);
}

/*
 * return TRUE if ch can memorize 'num' 'sn' spell
 * FALSE otherwise
 */
bool CanMemSpell(CHAR_DATA *ch, int sn, int num)
{
	if ( !ch || IS_NPC(ch) )
		return (FALSE);

	if ( MEMORIZED(ch, sn) + num <= MAX_SAME_SPELL )
		return (TRUE);

	return (FALSE);
}


/* stop the memorization of a spell */
void stop_memorizing(CHAR_DATA *ch)
{
	if ( !ch || !ch->action || !AFF_FLAGGED(ch, AFF_MEMORIZING) )
		return;

	event_cancel(ch->action);
	REMOVE_BIT(AFF_FLAGS(ch), AFF_MEMORIZING);

	send_to_char("You stop memorizing your spell.\r\n", ch);	
}

/* the memorizing event */
EVENTFUNC(mem_event)
{
	MEM_EVENT *me = (MEM_EVENT *) event_obj;
	CHAR_DATA *ch = me->ch;
	int spellnum = me->spell;
	int tnum = me->num;

	ch_printf(ch, "You have finished memorizing '&b&3%s&0'.\r\n", SINFO.name);
	MEMORIZED(ch, spellnum) += tnum;

	ch->action	= NULL;
	REMOVE_BIT(AFF_FLAGS(ch), AFF_MEMORIZING);
	DISPOSE(event_obj);
	return (0);
}

/* command to memorize a spell */
ACMD(do_memorize)
{
	MEM_EVENT *me;
	SPELLBOOK *book;
	char *s, arg1[MAX_INPUT_LENGTH];
	int spellnum, tnum = 1;

	if ( IS_NPC(ch) )
		return;

	if ( !MEMMING_CLASS(ch) )
	{
		send_to_char("You cannot memorize spells.\r\n", ch);
		return;
	}

	if ( ch->action )
	{
		if ( AFF_FLAGGED(ch, AFF_MEMORIZING) )
			send_to_char("You can memorize only one spell at time.\r\n", ch);
		else
			send_to_char("You are too busy to memorize a spell right now.\r\n", ch);
		return;
	}

	/* get: blank, spell name, target name */
	s = get_spell_name(argument);
	if (s == NULL)
	{
		send_to_char("Usage: memorize 'spellname' <num>\r\n", ch);
		ListMemSpells(ch);
		return;
	}

	argument = strtok(NULL, "\0");
	one_argument(argument, arg1);

	if ( GET_POS(ch) != POS_RESTING )
	{
		send_to_char("You must rest before start memorizing spells.\r\n", ch);
		return;
	}

	spellnum = find_skill_num(s);
	
	if ((spellnum < 1) || (spellnum >= MAX_SPELLS))
	{
		send_to_char("Memorize what?!?\r\n", ch);
		return;
	}

	if ( !(book = FindSpellbook(ch->first_carrying, GET_IDNUM(ch)) ) )
	{
		send_to_char("You need a spellbook to memorize spells.\r\n", ch);
		return;
	}

	if ( !BookHasSpell(book, spellnum) )
	{
		send_to_char("This spellbook doesn't contains that spell.\r\n", ch);
		return;
	}

	if (GET_LEVEL(ch) < SINFO.min_level[(int) GET_CLASS(ch)])
	{
		send_to_char("That spell is beyond your actual power!\r\n", ch);
		return;
	}

	if ( *arg1 && is_number(arg1) )
	{ 
		tnum = atoi(arg1);
			
		if ( tnum < 1 || tnum > MAX_SAME_SPELL )
		{
			ch_printf(ch, "You can memorize the same spell only %d times.\r\n", MAX_SAME_SPELL);
			return;
		}
	}
	
	if ( NumSpellsMem(ch) + tnum >= MaxSpellsCanMem(ch) )
	{
		send_to_char("You've already memorized all the spells you can handle at the moment.\r\n", ch);
		return;
	}

	if ( !CanMemSpell(ch, spellnum, tnum) )
	{
		ch_printf(ch, "You cannot memorize '%s' any further.\r\n", SINFO.name);
		return;
	}
	
	if (!(GET_COND(ch, FULL)) || !(GET_COND(ch, THIRST)))
	{
		send_to_char("You can't concentrate with ", ch);
		if (!GET_COND(ch, FULL))
		{
			if (!GET_COND(ch, THIRST))
				send_to_char("your stomach aching and your throat dry.\r\n", ch);
			else
				send_to_char("your stomach rumbling.\r\n", ch);
		}
		else
			send_to_char("your throat this parched.\r\n", ch);
		return;
	}

	send_to_char("You open your spellbook and begin studying it intently.\r\n", ch);
	act("$n opens $s spellbook and begins studying it intently.", TRUE, ch, NULL, NULL, TO_ROOM);

	if ( ch->in_obj && OBJ_FLAGGED(ch->in_obj, ITEM_FAST_MAGIC) )
	{
		ch_printf(ch, "You immediately memorize '&b&3%s&0'.\r\n", SINFO.name);
		MEMORIZED(ch, spellnum) += tnum;
		return;
	}

	CREATE(me, MEM_EVENT, 1);
	me->ch		= ch;
	me->spell	= spellnum;
	me->num		= tnum;

	ch->action	= event_create(mem_event, me, MEM_DELAY * tnum);
	SET_BIT(AFF_FLAGS(ch), AFF_MEMORIZING);
}

/*
 * called from ACMD(do_forget)
 *
 * return TRUE if *argument is a spell name, FALSE otherwise
 */
bool forget_spell( CHAR_DATA *ch, char *argument )
{
	char *spellname = get_spell_name(argument);
	int sn;

	if ( !spellname )
		return (FALSE);

	sn = find_skill_num(spellname);
	if ( sn < 0 || sn >= MAX_SPELLS )
		return (FALSE);

	if ( !MEMORIZED(ch, sn) )
		send_to_char("You don't have that spell memorized.\r\n", ch);
	else
	{
		ch_printf(ch, "You forget the '&b&3%s&0' spell.\r\n", spellname);
		MEMORIZED(ch, sn) = 0;
	}

	return (TRUE);
}


/* ****************************************************** */
/* Study / Copy functions                                 */
/* ****************************************************** */

void LearnOrImprove(CHAR_DATA *ch, int spellnum)
{
	if (spellnum < 0 || spellnum >= MAX_SPELLS)
		return;

	if (GET_SKILL(ch, spellnum) >= LEARNED(ch))
	{
		send_to_char("You are already learned in that area.\r\n", ch);
		return;
	}

	if ( GET_SKILL(ch, spellnum) > 0 )
	{
		GET_SKILL(ch, spellnum) += number(1, 3);
		GET_SKILL(ch, spellnum) = MIN(GET_SKILL(ch, spellnum), LEARNED(ch));
		ch_printf(ch, "You improve your knowledge of the spell '&b&3%s&0'.\r\n", SINFO.name);
		return;
	}

	if ( MEMMING_CLASS(ch) )
	{
		if (AddNewSpell(ch, spellnum))
			return;
		send_to_char("You carefully copy the spell on you spellbook.\r\n", ch);
		act("$n carefully write something on $s spellbook.", TRUE, ch, NULL, NULL, TO_ROOM);
	}

	if (GET_LEVEL(ch) >= SINFO.min_level[(int) GET_CLASS(ch)])
	{
		SET_SKILL(ch, spellnum, (5 + number(1, (int_app[GET_INT(ch)].learn) / 2)));
		ch_printf(ch, "You have learned the art of '&b&3%s&0'.\r\n", SINFO.name);
	}
	else
		ch_printf(ch, "The '&b&3%s&0' spell is beyond your actual power.\r\n", SINFO.name);
}

/*
 * *POWERFUL*
 *
 * allow to study wands, staves, scrolls, spellbooks
 * to learn theirs spells.
 *
 * FB 03-06-2002 -- players now can study maps
 */
ACMD(do_study)	 /* original study code for Rom 2.4 is by Absalom */
{
	OBJ_DATA *obj;
	BOOK_PAGE *page;
	char *burngone = "$p burns brightly and is gone.";
	char *burnpage = "$p's page glows brightly and the writing disappear.";
	char *s;
	int spellnum;
	
	argument = one_argument(argument, arg);
	
	if (!*arg)
	{
		send_to_char("You can study magical or enchanted objects to acquire new knowledge.\r\n", ch);
		send_to_char(
			"Usage: study <object>\r\n"
			"     : study <spelbook> 'spellname'\r\n"
			"     : study <map obj>\r\n"
			, ch);
		return;
	}
	
	if ( !GET_SKILL(ch, SKILL_STUDY) )
	{
		send_to_char("You have no idea on how to study.\r\n", ch);
		return;
	}

	/* study scrolls, wands and staves */
	if (!(obj = get_obj_in_list_vis_rev(ch, arg, NULL, ch->last_carrying)))
	{
		sprintf(buf, "You don't seem to have %s %s.\r\n", AN(arg), arg);
		send_to_char(buf, ch);
		return;
	}
	
	if (GET_OBJ_TYPE(obj) == ITEM_MAP)
	{
		// sea course map
		if ( GET_OBJ_VAL(obj, 0) == 0 )
		{
			int learn_course(CHAR_DATA *ch, int cnum);
			int ret;
			
			ret = learn_course(ch, GET_OBJ_VAL(obj, 1));
			
			switch (ret)
			{
			case -1:
				log("SYSERR: invalid course vnum in obj.");
				break;
			case 0:
				send_to_char("You already know that course.\r\n", ch);
				break;
			default:
				send_to_char("You learn a new sea course.\r\n", ch);
				break;
			}
		}
		else
		{
			log("SYSERR: unknown map type.");
			send_to_char("This is an unknown map.\r\n", ch);
		}
		return;
	}

	/* list here item type allowed to be studied */
	if (GET_OBJ_TYPE(obj) != ITEM_STAFF  && GET_OBJ_TYPE(obj) != ITEM_WAND &&
	    GET_OBJ_TYPE(obj) != ITEM_SCROLL && GET_OBJ_TYPE(obj) != ITEM_SPELLBOOK)
	{
		send_to_char("You can only study scrolls, wands, staves and spellbooks.\r\n", ch);
		return;
	}

	/* sorcerer check to prevent studying his spellbook */
	if ( MEMMING_CLASS(ch) )
	{
		OBJ_DATA *sbook = FindObjSpellbook(ch->first_carrying, GET_IDNUM(ch));

		if ( (sbook) && sbook == obj )
		{
			send_to_char("Are you stupid??\r\n", ch);
			return;
		}
	}

	separate_obj(obj);

	act("You start studying $p.", TRUE, ch, obj, NULL, TO_CHAR);
	act("$n start studying $p.", TRUE, ch, obj, NULL, TO_ROOM);
	
	if (GET_LEVEL(ch) + 5 < GET_OBJ_LEVEL(obj))
	{
		send_to_char("You cannot glean any knowledge from it.\r\n", ch);
		act(burngone, FALSE, NULL, obj, 0, TO_ROOM);
		extract_obj(obj);
		return;
	}
	
	if (!success(ch, NULL, SKILL_STUDY, 0))
	{
		send_to_char("You fail to extract any knowledge from it.\r\n", ch);
		act(burngone, FALSE, NULL, obj, 0, TO_ROOM);
		extract_obj(obj);
		return;
	}
	
	switch (GET_OBJ_TYPE(obj))
	{
	case ITEM_SCROLL:
		if ( success(ch, NULL, SKILL_READ_MAGIC, 0) )
		{
			int sn;

			for (sn = 1; sn < 4; sn++)
				LearnOrImprove(ch, GET_OBJ_VAL(obj, sn));
		}
		else
			send_to_char("You fail to decifrate the magic writing.\r\n", ch);

		act(burngone, FALSE, NULL, obj, 0, TO_ROOM);
		extract_obj(obj);
		break;

	case ITEM_WAND:
	case ITEM_STAFF:
		LearnOrImprove(ch, GET_OBJ_VAL(obj, 3));
		act(burngone, FALSE, NULL, obj, 0, TO_ROOM);
		extract_obj(obj);
		break;

	case ITEM_SPELLBOOK:
		if ( !obj->special || !OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
		{
			send_to_char("There is nothing to study in that.\r\n", ch);
			return;
		}

		if ( !*argument )
		{
			send_to_char("You must specify which spell of the spellbook you want to study.\r\n", ch);
			ListSpellsInBook(ch, (SPELLBOOK *) obj->special, TRUE);
			return;
		}

		s = get_spell_name(argument);
		if ( s == NULL )
		{
			send_to_char("You must specify which spell of the spellbook you want to study.\r\n", ch);
			ListSpellsInBook(ch, (SPELLBOOK *) obj->special, TRUE);
			return;
		}

		spellnum = find_skill_num(s);

		if ( !(page = BookHasSpell((SPELLBOOK *) obj->special, spellnum)) )
		{
			send_to_char("The spellbook doesn't contains that spell.\r\n", ch);
			return;
		}

		LearnOrImprove(ch, spellnum);

		act(burnpage, FALSE, ch, obj, NULL, TO_CHAR);
		act(burnpage, FALSE, ch, obj, NULL, TO_ROOM);
		MakePageBlank(page);

		break;
	}
}


/*
 * copy a spell from a scroll to the char spellbook
 */
void CopyScroll(CHAR_DATA *ch, OBJ_DATA *scroll, SPELLBOOK *book)
{
	char *burngone = "$p burns brightly and is gone.";
	int sn, spellnum, written = 0;

	if ( GET_OBJ_VAL(scroll, 1) == -1 )
	{
		send_to_char("The scroll doesn't contains any spell.\r\n", ch);
		act(burngone, FALSE, NULL, scroll, 0, TO_ROOM);
		extract_obj(scroll);
		return;
	}

	for (written = 0, sn = 1; sn < 4; sn++)
	{
		if ((spellnum = GET_OBJ_VAL(scroll, sn)) == -1)
			continue;

		if ( BookHasSpell(book, spellnum) )
		{
			ch_printf(ch, "You skip '%s' because your spellbook already contains this spell.\r\n",
				skill_name(spellnum));
			continue;
		}

		/*
		 * the only reason this return false is there's no blank pages on
		 * the spellbook, so we exit here
		 */
		if ( !AddSpellToBook(ch, book, spellnum) )
			break;

		written++;
	}

	if ( written )
	{
		send_to_char("You successfully copy the spells in your spellbook.\r\n", ch);
		act("$n carefully write something on $s spellbook.", TRUE, ch, NULL, NULL, TO_ROOM);
		act(burngone, FALSE, NULL, scroll, 0, TO_ROOM);
		extract_obj(scroll);
	}
}


/*
 * copy a spell from a spellbook to the char spellbook
 */
void CopySpellbook(CHAR_DATA *ch, OBJ_DATA *obj, SPELLBOOK *chbook, char *argument)
{
	SPELLBOOK *book;
	BOOK_PAGE *page;
	char *s, *burngone = "$p's page glows brightly and the writing disappear.";
	int spellnum, written = 0;

	s = get_spell_name(argument);
	if (s == NULL)
	{
		act("Which spell do you want to copy from $p?", TRUE, ch, obj, NULL, TO_CHAR);
		return;
	}

	book = (SPELLBOOK *) obj->special;

	if ( ( spellnum = find_skill_num(s) ) == NOTHING )
	{
		send_to_char("That spell does not exists.\r\n", ch);
		return;
	}

	if ( BookHasSpell(chbook, spellnum) )
	{
		ch_printf(ch, "Your spellbook already contains the '%s' spell.\r\n",
			skill_name(spellnum));
		return;
	}

	if ( !( page = BookHasSpell(book, spellnum) ) )
	{
		act("$p does not contain that spell.", TRUE, ch, obj, NULL, TO_CHAR);
		return;
	}

	if ( AddSpellToBook(ch, chbook, spellnum) )
	{
		send_to_char("You successfully copy the spells in your spellbook.\r\n", ch);
		act("$n carefully write something on $s spellbook.", TRUE, ch, NULL, NULL, TO_ROOM);
		act(burngone, FALSE, ch, obj, NULL, TO_CHAR);
		MakePageBlank(page);

		check_damage_obj(ch, obj, 8);
	}
}


ACMD(do_copy)
{
	SPELLBOOK *book;
	OBJ_DATA *obj;
	char arg1[MAX_INPUT_LENGTH];

	if ( !MEMMING_CLASS(ch) )
	{
		send_to_char("You have no idea on how to copy spells from objects.", ch);
		if ( CASTING_CLASS(ch) )
			send_to_char(" Try studying it.\r\n", ch);
		send_to_char("\r\n", ch);
		return;
	}
		
	argument = one_argument(argument, arg1);

	if ( !(obj = get_obj_in_list_vis_rev(ch, arg1, NULL, ch->last_carrying)) )
	{
		ch_printf(ch, "You don't have %s %s.\r\n", AN(arg1), arg1);
		return;
	}

	if ( GET_OBJ_TYPE(obj) != ITEM_SCROLL && GET_OBJ_TYPE(obj) != ITEM_SPELLBOOK )
	{
		send_to_char("You can copy spells only from scrolls or spellbooks.\r\n", ch);
		return;
	}

	if ( !(book = FindSpellbook(ch->first_carrying, GET_IDNUM(ch))) )
	{
		send_to_char("You don't have a spellbook to copy the spell to.\r\n", ch);
		return;
	}

	if ( !BookHasBlankPage(book) )
	{
		send_to_char("There isn't a blank page on your spellbook to write on.\r\n", ch);
		return;
	}

	if ( !success(ch, NULL, SKILL_READ_MAGIC, 0) )
	{
		send_to_char("You fail to decifrate the magic writing.\r\n", ch);
		return;
	}

	if ( GET_OBJ_TYPE(obj) == ITEM_SCROLL )
		CopyScroll(ch, obj, book);
	else if ( obj->special && OBJ_FLAGGED(obj, ITEM_IS_SPELLBOOK) )
		CopySpellbook(ch, obj, book, argument);
}


/* *************************************************************** */
/* Code for storing and using spells on magic items                */
/* *************************************************************** */

/*
 * if target object has the given spell stored, return
 * a pointer to the obj spell data
 */
OBJ_SPELLS_DATA *ItemHasSpell(OBJ_DATA *obj, int sn)
{
	OBJ_SPELLS_DATA *oSpell;

	if (!obj || !obj->special || !OBJ_FLAGGED(obj, ITEM_HAS_SPELLS))
		return (NULL);

	for (oSpell = (OBJ_SPELLS_DATA *) obj->special; oSpell; oSpell = oSpell->next)
	{
		if ( oSpell->spellnum == sn )
			break;
	}

	return (oSpell);
}

/*
 * search all char equipped items of type ITEM_WORN
 * that can contains the given spell
 */
OBJ_SPELLS_DATA *GetCharItemsSpell(CHAR_DATA *ch, int sn)
{
	OBJ_SPELLS_DATA *oSpell;

	if ( GET_EQ(ch, WEAR_FINGER_R) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_FINGER_R), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_FINGER_R), 2);
		return (oSpell);
	}

	if ( GET_EQ(ch, WEAR_FINGER_L) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_FINGER_L), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_FINGER_L), 2);
		return (oSpell);
	}

	if ( GET_EQ(ch, WEAR_NECK_1) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_NECK_1), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_NECK_1), 2);
		return (oSpell);
	}

	if ( GET_EQ(ch, WEAR_NECK_2) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_NECK_2), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_NECK_2), 2);
		return (oSpell);
	}

	if ( GET_EQ(ch, WEAR_WRIST_R) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_WRIST_R), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_WRIST_R), 2);
		return (oSpell);
	}

	if ( GET_EQ(ch, WEAR_WRIST_L) && (oSpell = ItemHasSpell(GET_EQ(ch, WEAR_WRIST_L), sn)) )
	{
		check_damage_obj(ch, GET_EQ(ch, WEAR_WRIST_L), 2);
		return (oSpell);
	}

	return (NULL);
}

/*
 * *POWERFUL*
 *
 * store a spell into an object.
 *
 * that allow to cast that spell thru the object
 */
ACMD(do_enchant)
{
	OBJ_DATA *obj, *pObj;
	OBJ_SPELLS_DATA *oSpell, *spells_list = NULL;
	char *s, arg1[MAX_INPUT_LENGTH];
	int spellnum;

	if ( !GET_SKILL(ch, SKILL_ENCHANT_ITEMS) )
	{
		send_to_char("You don't know how to enchant an item.\r\n", ch);
		return;
	}

	/* get: blank, spell name, target name */
	s = get_spell_name(argument);
	if (s == NULL)
	{
		send_to_char("Usage: enchant 'spellname' <object>\r\n", ch);
		return;
	}
	argument = strtok(NULL, "\0");

	spellnum = find_skill_num(s);
	if ( spellnum < 0 || spellnum >= MAX_SPELLS )
	{
		send_to_char("That spell doesn't exist.\r\n", ch);
		return;
	}

	one_argument(argument, arg1);

	if ( !*arg1 )
	{
		send_to_char("Usage: enchant 'spellname' <object>\r\n", ch);
		return;
	}

	if ( !(obj = get_obj_in_list_vis_rev(ch, arg1, NULL, ch->last_carrying)) )
	{
		ch_printf(ch, "You don't have %s %s.\r\n", AN(arg1), arg1);
		return;
	}

	if ( GET_OBJ_TYPE(obj) != ITEM_WORN && GET_OBJ_TYPE(obj) != ITEM_WEAPON )
	{
		send_to_char("You can enchant, in different ways, only weapons and jewelry.\r\n", ch);
		return;
	}

	if ( GET_OBJ_TYPE(obj) == ITEM_WEAPON && !IS_SET(SINFO.routines, MAG_DAMAGE))
	{
		send_to_char("A weapon can be enchanted only with offensive spells.\r\n", ch);
		return;
	}

	if ( obj->special )
	{
		if (!OBJ_FLAGGED(obj, ITEM_HAS_SPELLS)) 
		{
			act("$p cannot be enchanted.", TRUE, ch, obj, NULL, TO_CHAR);
			return;
		}

		if ( ItemHasSpell(obj, spellnum) )
		{
			act("$p is already enchanted with that spell.", TRUE, ch, obj, NULL, TO_CHAR);
			return;
		}
	}

	separate_obj(obj);

	if (IS_MORTAL(ch))
	{
		if ( !success(ch, NULL, SKILL_ENCHANT_ITEMS, 0) )
		{
			act("You fail in enchanting $p, and it explodes.", FALSE, ch, obj, NULL, TO_CHAR);
			act("$n casts a spell upon $p, but something goes badly wrong.", FALSE, ch, obj, NULL, TO_ROOM);
			damage(ch, ch, number(20, 50), TYPE_UNDEFINED);
			extract_obj(obj);
			return;
		}
	}
	
	pObj = clone_object(obj);

	/* make the object unique */
	if ( !OBJ_FLAGGED(pObj, ITEM_UNIQUE) )
	{
		/* clone obj increase the copy number of the prototype */
		if (pObj->item_number != NOTHING)
			obj_index[pObj->item_number].number--;
		pObj->item_number	= NOTHING;
		SET_BIT(GET_OBJ_EXTRA(pObj), ITEM_UNIQUE);
	}

	if ( pObj->special )
		spells_list = (OBJ_SPELLS_DATA *) pObj->special;

	CREATE(oSpell, OBJ_SPELLS_DATA, 1);
	oSpell->spellnum	= spellnum;
	oSpell->percent		= MIN(100, number(60, 80) + GET_LEVEL(ch));
	oSpell->level		= MAX(1, GET_LEVEL(ch) - number(1, 4));

	oSpell->next		= spells_list;
	spells_list			= oSpell;

	pObj->special		= spells_list;

	SET_BIT(GET_OBJ_EXTRA(pObj), ITEM_MAGIC | ITEM_HAS_SPELLS);

	act("You enchant $p, that glows white for a moment.", TRUE, ch, pObj, NULL, TO_CHAR);
	act("$n enchants $p, that glows white for a moment.", TRUE, ch, pObj, NULL, TO_ROOM);
	extract_obj(obj);
	obj_to_char(pObj, ch);

	/* TODO
	 * character must have drawbacks for enchanting items...
	 */
}