/* Copyright (C) 1994-2003 Revolution Software Ltd
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /cvsroot/scummvm/scummvm/bs2/mouse.cpp,v 1.9 2003/08/23 14:42:37 fingolfin Exp $
 */

#include "stdafx.h"
#include "driver/driver96.h"
#include "build_display.h"
#include "console.h"
#include "controls.h"
#include "debug.h"
#include "defs.h"
#include "events.h"
#include "icons.h"
#include "interpreter.h"
#include "layers.h"
#include "maketext.h"
#include "mouse.h"	//assure integrety
#include "object.h"
#include "protocol.h"
#include "resman.h"
#include "sound.h"
#include "sword2.h"	// for PauseGame() & UnpauseGame()
//------------------------------------------------------------------------------------
// pointer resource id's

#define CROSHAIR	18
#define EXIT0		788
#define EXIT1		789
#define EXIT2		790
#define EXIT3		791
#define EXIT4		792
#define EXIT5		793
#define EXIT6		794
#define EXIT7		795
#define EXITDOWN	796
#define EXITUP		797
#define MOUTH		787
#define NORMAL		17
#define PICKUP		3099
#define SCROLL_L	1440
#define SCROLL_R	1441
#define USE			3100
//------------------------------------------------------------------------------------
//the mouse list stuff
uint32	cur_mouse;
Mouse_unit mouse_list[TOTAL_mouse_list];

uint32	mouse_touching=0;	//set by Check_mouse_list
uint32	old_mouse_touching=0;

uint32	menu_selected_pos;
uint8	examining_menu_icon=0;

uint32	mouse_pointer_res=0;	// if it's NORMAL_MOUSE_ID (ie. normal pointer) then it's over a floor area (or hidden hot-zone
uint32	mouse_mode=0;	//0 normal in game
							//1 top menu down (bottom!)
							//2 dragging luggage
							//3 system menu chooser (top)
							//4 speech chooser

//Object_mouse	old_mouse_object;	//copy structure from list when moving onto a mouse area

uint32	menu_status;	//0 available - 1 unavailable
uint32	mouse_status;	//human 0 on/1 off

uint32	mouse_mode_locked=0;	//0 not !0 mode cannot be changed from normal mouse to top menu (i.e. when carrying big objects)
uint32	current_luggage_resource=0;

uint32	subject_status;	//0 off	1 on

uint32	old_button=0;	//for the re-click stuff - must be same button you see
uint32	button_click=0;

uint32	pointer_text_bloc_no=0;
uint32	pointerTextSelected=0;

uint32 player_activity_delay=0;	// player activity delay counter

uint32	real_luggage_item=0;	//last minute for pause mode

//------------------------------------------------------------------------------------
/*
#define RD_LEFTBUTTONDOWN			0x01
#define RD_LEFTBUTTONUP				0x02
#define RD_RIGHTBUTTONDOWN			0x04
#define RD_RIGHTBUTTONUP			0x08
*/
//------------------------------------------------------------------------------------
// local function prototypes

//uint8 Check_sprite_pixel( uint32 j );
void CreatePointerText(uint32 TextId, uint32 pointerRes);	// James16jun97
void Monitor_player_activity(void);	// James23july97

void	No_human(void);

//------------------------------------------------------------------------------------
void	Reset_mouse_list(void)	//Tony26Sept96
{
//call at beginning of gameloop

	cur_mouse=1;
}
//------------------------------------------------------------------------------------
void	Mouse_engine(void)	//Tony30Sept96
{

	Monitor_player_activity();	// James23july97

	ClearPointerText();	// James16jun97


	if	(DEAD)	//George is dead ;)
	{
		if	(mouse_mode!=MOUSE_system_menu)
		{
			mouse_mode=MOUSE_system_menu;
			if	(mouse_touching)
			{
//				get off
				old_mouse_touching=0;	//we've moved off
				mouse_touching=0;	//we were on something but not anymore
			}

			Set_mouse(NORMAL_MOUSE_ID);

			Build_system_menu();	//Tony19Mar97
		}
		System_menu();

		return;
	}

		
	if	(mouse_status)	//no human
		return;


	switch(mouse_mode)
	{
		case	MOUSE_normal:	//0 normal in game
			Normal_mouse();
			break;

		case	MOUSE_top:	//1 top menu down
			Top_menu_mouse();
			break;

		case	MOUSE_drag:	//2 dragging luggage
			Drag_mouse();
			break;

		case	MOUSE_system_menu:	//3 in game bottom menu - save/restore, etc?
			System_menu();
			break;

		case	MOUSE_holding:	//4 wait for mouse to move off bottom menu - after speech
			if	(mousey<400)
			{	mouse_mode=MOUSE_normal;
				Zdebug("   releasing");
			}
			break;

		default:
			break;
	}

}
//------------------------------------------------------------------------------------
void	System_menu(void)	//Tony19Mar97
{
	uint32 safe_looping_music_id;
	_mouseEvent	*me;
	int	j,hit;
	uint8	*icon;
	uint32	rv;	// for drivers return value
	int32	pars[2];
	uint32	icon_list[5] =
	{
		OPTIONS_ICON,
		QUIT_ICON,
		SAVE_ICON,
		RESTORE_ICON,
		RESTART_ICON
	};



	if	((mousey>0)&&(!DEAD))	//can't close when player is dead
	{	mouse_mode=MOUSE_normal;	//close menu

//		start the menu coming down
		HideMenu(RDMENU_TOP);

		return;
	}

	me = MouseEvent();	//get mouse event


	if	((me!=NULL)&&(me->buttons&RD_LEFTBUTTONDOWN))	//there's a mouse event to be processed
	{

//		clicked on a top mouse pointer?

			if	((mousex>=24)&&(mousex<640-24)&&(mousey<0))
			{
				hit=(mousex-24)/40;	//which are we over?

				if	((hit==2)&&(DEAD))
					return;	//no save when dead

				if	(hit<5)	//there are 5 system menus
				{	for	(j=0;j<5;j++)	//build them all high in full colour - when on eis clicked on all the rest will grey out
						if	(j!=hit)	//change all others to grey
						{
							icon = res_man.Res_open( icon_list[j] ) + sizeof(_standardHeader);
							SetMenuIcon(RDMENU_TOP, j, icon);
							res_man.Res_close( icon_list[j] );
						}

					//------------------------
					rv = g_sword2->_sound->PauseFx();
					if (rv != RD_OK)
						Zdebug("ERROR: PauseFx() returned %.8x in SystemMenu()", rv);
  					//------------------------
					// NB. Need to keep a safe copy of 'looping_music_id' for savegame
					// & for playing when returning from control panels
					// because control panel music will overwrite it!
					safe_looping_music_id = looping_music_id;
					pars[0] = 221;	// SystemM234 (M234.wav)
					pars[1] = FX_LOOP;
					FN_play_music(pars);
					looping_music_id = safe_looping_music_id;	// restore proper looping_music_id
					//------------------------
  					//------------------------
					// clear the screen & set up the new palette for the menus

					EraseBackBuffer();
					ProcessMenu();	// drivers to redraw menu over this blank screen!
					ResetRenderEngine();
  					//------------------------

					switch(hit)	//call the relevent screen
					{
						case	0:	// options
							Option_control();	// game options
							break;

						case	1:	// quit
							Quit_control();		// quit to windows
							break;

						case	2:	// save
							Save_control();		// save the game
							break;

						case	3:	// restore
							Restore_control();	// restore a game
							break;

						case	4:	// restart
							Restart_control();	// restart the game
							break;

					}
					//------------------------
//					start the menu coming down
					if	(!DEAD)	//not death screen
					{	mouse_mode=MOUSE_normal;	//close menu
						HideMenu(RDMENU_TOP);	//but not when dead
					}
					else
					{	Set_mouse(NORMAL_MOUSE_ID);
						Build_system_menu();	//reset top menu
					}
  					//------------------------
					// clear the screen & restore the location palette

					EraseBackBuffer();
					ProcessMenu();	// drivers to redraw menu over this blank screen!

					//------------------------
					// reset game palette, but not after a successful restore or restart!

					if (this_screen.new_palette != 99)	// see RestoreFromBuffer() in save_rest.cpp
					{
						SetFullPalette(0);	// '0' means put back game screen palette; see Build_display.cpp (James17jun97)
						this_screen.new_palette=0;	//stop the engine fading in the restored screens palette
					}
					else
						this_screen.new_palette=1;

					//------------------------
					rv = g_sword2->_sound->UnpauseFx();
					if (rv != RD_OK)
						Zdebug("ERROR: UnpauseFx() returned %.8x in SystemMenu()", rv);
					//------------------------
					// If there was looping music before coming into the control panels then restart it!
					// NB. This will also start music required when a game has been restored

					if (looping_music_id)
					{
						pars[0] = looping_music_id;
						pars[1] = FX_LOOP;
						FN_play_music(pars);
						// cross-fades into the required music:
						// - either a restored game tune
						// - or music playing prior to entering control panels
					}
					else
						FN_stop_music(NULL);	// stop the control panel music
					//------------------------
				}
			}
	}

}
//------------------------------------------------------------------------------------
void	Drag_mouse(void)	//Tony21Nov96
{
	_mouseEvent	*me;
	uint32	pos;
//	uint32	null_pc=1;	//script 1 is combine script


	if	((mousey<400)&&(!menu_status))
	{	mouse_mode=MOUSE_normal;	//close menu

//		start the menu coming down
		HideMenu(RDMENU_BOTTOM);

		return;
	}


	Mouse_on_off();	//handles cursors and the luggage on/off according to type



//	now do the normal click stuff

	me = MouseEvent();	//get mouse event


//	we only care about left clicks when the mouse is over an object
//	we ignore mouse releases

	if	((me!=NULL)&&(me->buttons&RD_LEFTBUTTONDOWN))	//there's a mouse event to be processed
	{

//		could be clicking on an on screen object or on the top menu which is currently displayed

		if	(mouse_touching)	//mouse is over an on screen object - and we have luggage
		{							//depending on type we'll maybe kill the object_held - like for exits
			if (me->buttons&RD_LEFTBUTTONDOWN)	// set global script variable 'button'
			{
				LEFT_BUTTON  = 1;
				RIGHT_BUTTON = 0;
			}
			else
			{
				LEFT_BUTTON  = 0;
				RIGHT_BUTTON = 1;
			}

			MOUSE_X=(uint32)mousex+this_screen.scroll_offset_x;
			MOUSE_Y=(uint32)mousey+this_screen.scroll_offset_y;	//these might be required by the action script about to be run

			CLICKED_ID = mouse_touching;	// for scripts to know what's been clicked (21jan97). First used for 'room_13_turning_script' in object 'biscuits_13'



			Set_player_action_event(CUR_PLAYER_ID, mouse_touching);	//Tony4Dec96

			//--------------------------------------
			// Write to walkthrough file (zebug0.txt)
			#ifdef _SWORD2_DEBUG
			Zdebug(0,"USED \"%s\" ICON ON %s", FetchObjectName(OBJECT_HELD), FetchObjectName(CLICKED_ID));
			#endif
			//--------------------------------------


			HideMenu(RDMENU_BOTTOM);	//hide menu too
			mouse_mode=MOUSE_normal;	//back to normal menu mode
		}
		else	//better check for combine/cancel	cancel puts us back in Top_menu_mouse mode
		{
			if	((mousex>=24)&&(mousex<640-24))
			{
				pos=(mousex-24)/40;	//which are we over?

				if	(master_menu_list[pos].icon_resource)	//clicked on something - what button?
				{
					mouse_mode=MOUSE_top;	//always back into top menu mode

					Set_luggage(0);	// remove luggage

					if	(pos==menu_selected_pos)	// if we've clicked on the same icon as the one we're dragging
					{
						OBJECT_HELD=0;	// reset first icon
						menu_selected_pos=0;
					}
					else	// combine the 2 icons
					{
//						Zdebug("combine");
						COMBINE_BASE=master_menu_list[pos].icon_resource;	//what we clicked on, not what we're dragging

						Set_player_action_event(CUR_PLAYER_ID, MENU_MASTER_OBJECT);	//Tony4Dec96

						No_human();	// turn off mouse now, to prevent player trying to click elsewhere BUT leave the bottom menu open

						//--------------------------------------
						// Write to walkthrough file (zebug0.txt)
						#ifdef _SWORD2_DEBUG
						Zdebug(0,"USED \"%s\" ICON ON \"%s\" ICON", FetchObjectName(OBJECT_HELD), FetchObjectName(COMBINE_BASE));
						#endif
						//--------------------------------------
					}

					Build_top_menu();	// refresh the menu


//					Zdebug("switch to top mode");
				}
			}
		}
	}


}
//------------------------------------------------------------------------------------
void	Top_menu_mouse(void)	//Tony3Oct96
{
//top menu is down
	_mouseEvent	*me;
	uint32	pos;

	if	((mousey<400)&&(!menu_status))
	{	mouse_mode=MOUSE_normal;	//close menu

//		start the menu coming down
		HideMenu(RDMENU_BOTTOM);

		return;
	}

	me=MouseEvent();	//get mouse event



//	we only care about left clicks when the mouse is over an object
//	we ignore mouse releases

	if	(me!=NULL)	//there's a mouse event to be processed
	{
//		now check if we've clicked on an actual icon

		if	((mousex>=24)&&(mousex<640-24))
		{
			pos=(mousex-24)/40;	//which are we over?

			if	(master_menu_list[pos].icon_resource)	//clicked on something - what button?
			{

				if	(me->buttons&RD_RIGHTBUTTONDOWN)	//right button look
				{
					examining_menu_icon=1;
					OBJECT_HELD = master_menu_list[pos].icon_resource;	//id the object via its graphic
					EXIT_CLICK_ID=0;	// (JEL09oct97) must clear this so next click on exit becomes 1st click again

					//--------------------------------------
					// Write to walkthrough file (zebug0.txt)
					#ifdef _SWORD2_DEBUG
					Zdebug(0,"RIGHT-CLICKED ON \"%s\" ICON", FetchObjectName(OBJECT_HELD));
					#endif
 					//--------------------------------------

					Set_player_action_event(CUR_PLAYER_ID, MENU_MASTER_OBJECT);	//Tony4Dec96

					Build_top_menu();	// refresh the menu
					No_human();	// turn off mouse now, to prevent player trying to click elsewhere BUT leave the bottom menu open
				}
				else	if	(me->buttons&RD_LEFTBUTTONDOWN)	//left button - highlight the object and bung us into drag luggage mode
				{
					menu_selected_pos=pos;	//menu slot we clicked on - derive luggage resource from this in mouse_on_off()
					current_luggage_resource=master_menu_list[pos].luggage_resource;

					mouse_mode=MOUSE_drag;
//					Zdebug("setting OH in top menu");
					OBJECT_HELD = master_menu_list[pos].icon_resource;	//id the object via its graphic
					EXIT_CLICK_ID=0;	// (JEL09oct97) must clear this so next click on exit becomes 1st click again

					Build_top_menu();	// refresh the menu

					Set_luggage(master_menu_list[pos].luggage_resource);

//					Zdebug("switch to drag mode");
				}
			}
		}
	}
}
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
void	Normal_mouse(void)	//Tony30Sept96
{
//the gane is playing and none of the menus are activated - but, we need to check if the top menu is to start
//note, wont have luggage

	_mouseEvent	*me;




	if	((mousey<0)&&(!menu_status)&&(!mouse_mode_locked)&&(!OBJECT_HELD))	//no save in big-object menu lock situation
	{
		mouse_mode=MOUSE_system_menu;

		if	(mouse_touching)
		{
//			get off
			old_mouse_touching=0;	//we've moved off
			mouse_touching=0;	//we were on something but not anymore

		}

//		reset mouse cursor - in case we're between mice
		Set_mouse(NORMAL_MOUSE_ID);

		Build_system_menu();	//Tony19Mar97
	}





	if	((mousey>399)&&(!menu_status)&&(!mouse_mode_locked))
	{


		if	(!OBJECT_HELD)	//why are we testing for this?
		{
			mouse_mode=MOUSE_top;	//bring down top menu
		}
		else
		{
			mouse_mode=MOUSE_drag;
		}

//		if mouse is moving off an object and onto the top menu then do a standard get-off
		if	(mouse_touching)
		{
//			get off

			old_mouse_touching=0;	//we've moved off
			mouse_touching=0;	//we were on something but not anymore

		}

//		reset mouse cursor
		Set_mouse(NORMAL_MOUSE_ID);

//		build menu and start the menu coming down

		Build_top_menu();

		return;
	}


//check also for bringing the bottom menu up




	Mouse_on_off();	//handles



//	now do the normal click stuff

	me = MouseEvent();	//get mouse event

	//-----------------------------------------------------
#ifdef _SWORD2_DEBUG
	if (definingRectangles)
	{
		if (draggingRectangle==0)	// not yet dragging a rectangle, so need click to start
		{
			if ( (me!=NULL) && ((me->buttons&RD_LEFTBUTTONDOWN)||(me->buttons&RD_RIGHTBUTTONDOWN)) )
			{
				rect_x1 = rect_x2 = (uint32)mousex+this_screen.scroll_offset_x;		// set both (x1,y1)
				rect_y1 = rect_y2 = (uint32)mousey+this_screen.scroll_offset_y;	// & (x2,y2) to this point
				draggingRectangle=1;
			}
		}
		else if (draggingRectangle==1)	// currently dragging a rectangle
		{
			if ( (me!=NULL) && ((me->buttons&RD_LEFTBUTTONDOWN)||(me->buttons&RD_RIGHTBUTTONDOWN)) )	// click means reset
			{
				draggingRectangle=2;	// lock rectangle, so you can let go of mouse to type in the coords
			}
			else	// drag rectangle
			{
				rect_x2 = (uint32)mousex+this_screen.scroll_offset_x;
				rect_y2 = (uint32)mousey+this_screen.scroll_offset_y;
			}
		}
		else	// currently locked to avoid knocking out of place while reading off the coords
		{
			if ( (me!=NULL) && ((me->buttons&RD_LEFTBUTTONDOWN)||(me->buttons&RD_RIGHTBUTTONDOWN)) )	// click means reset
			{
				draggingRectangle=0;	// back to start again
			}
		}
	}
	else
#endif	// _SWORD2_DEBUG
	//-----------------------------------------------------
	{

		// we only care about down clicks when the mouse is over an object
		// we ignore mouse releases

		if	((me!=NULL)&&((me->buttons&RD_LEFTBUTTONDOWN)||(me->buttons&RD_RIGHTBUTTONDOWN))&&(mouse_touching))	//there's a mouse event to be processed and the mouse is on something
		{
//			ok, there are no menus about so its nice and simple
//			this is as close to the old advisor_188 script as we get I'm sorry to say.


//			if player is walking or relaxing then those need to terminate correctly


//			otherwise set player run the targets action script
//			or, do a special walk if clicking on the scroll-more icon

//			PLAYER_ACTION = mouse_touching;	//PLAYER_ACTION script variable - whatever catches this must reset to 0 again
													//idle or router-anim will catch it


			if (me->buttons&RD_LEFTBUTTONDOWN)	// set global script variable 'button'
			{
				LEFT_BUTTON  = 1;
				RIGHT_BUTTON = 0;
				button_click=0;	//for re-click
			}
			else
			{
				LEFT_BUTTON  = 0;
				RIGHT_BUTTON = 1;
				button_click=1;	//for re-click
			}

			MOUSE_X=(uint32)mousex+this_screen.scroll_offset_x;
			MOUSE_Y=(uint32)mousey+this_screen.scroll_offset_y;	//these might be required by the action script about to be run


			if	((mouse_touching==EXIT_CLICK_ID)&&(me->buttons&RD_LEFTBUTTONDOWN))	//only left button
			{
//				its the exit double click situation
//				let the existing interaction continue and start fading down - switch the human off too

				FN_no_human(NULL);
				FN_fade_down(NULL);
				EXIT_FADING=1;	//tell the walker

			}
			else	if	((old_button==button_click)&&(mouse_touching==CLICKED_ID)&&(mouse_pointer_res!=NORMAL_MOUSE_ID))
			{	//re-click - do nothing - except on floors
			}
			else	//allow the click
			{
				old_button=button_click;	//for re-click

				CLICKED_ID = mouse_touching;	// for scripts to know what's been clicked (21jan97). First used for 'room_13_turning_script' in object 'biscuits_13'
				EXIT_CLICK_ID=0;	//must clear these two double-click control flags - do it here so reclicks after exit clicks are cleared up
				EXIT_FADING=0;
				Set_player_action_event(CUR_PLAYER_ID, mouse_touching);	//Tony4Dec96

				//--------------------------------------
				// Write to walkthrough file (zebug0.txt)
				#ifdef _SWORD2_DEBUG
				if (OBJECT_HELD)
					Zdebug(0,"USED \"%s\" ICON ON %s", FetchObjectName(OBJECT_HELD), FetchObjectName(CLICKED_ID));
				else if (LEFT_BUTTON)
					Zdebug(0,"LEFT-CLICKED ON %s", FetchObjectName(CLICKED_ID));
				else	// RIGHT BUTTON
					Zdebug(0,"RIGHT-CLICKED ON %s", FetchObjectName(CLICKED_ID));
				#endif
				//--------------------------------------
			}
		}
	}
}
//------------------------------------------------------------------------------------
void	Mouse_on_off(void)	//Tony30Sept96
{
	//this handles the cursor graphic when moving on and off mouse areas
	//it also handles the luggage thingy

	uint32	pointer_type;
	static uint8	mouse_flicked_off=0;


	old_mouse_touching=mouse_touching;


	if	((mousey<0)||(mousey>399))	// don't detect objects that are hidden behind the menu bars (ie. in the scrolled-off areas of the screen)
	{	
		pointer_type=0;
		mouse_touching=0;
	}
	else	
		pointer_type = Check_mouse_list();	// set 'mouse_touching' & return pointer_type




	if	((!mouse_flicked_off)&&(old_mouse_touching==mouse_touching))	//same as previous cycle?
		return;	//yes, so nothing to do		BUT CARRY ON IF MOUSE WAS FLICKED OFF!

	mouse_flicked_off=0;	// can reset this now


	if	((!old_mouse_touching)&&(mouse_touching))	//the cursor has moved onto something
	{

//		make a copy of the object we've moved onto
//		because one day we'll move back off again! (but the list positioning could theoretically have changed)
//		we can only move onto something from being on nothing - we stop the system going from one to another when objects overlap

//		memcpy( &old_mouse_object, &mouse_list[mouse_touching], sizeof(Object_mouse));

		old_mouse_touching=mouse_touching;	//

//		run get on

		if (pointer_type)
		{
			Set_mouse(pointer_type);	// 'pointer_type' holds the resource id of the pointer anim

			if (OBJECT_HELD)			// setup luggage icon
//				Set_luggage(master_menu_list[menu_selected_pos].luggage_resource);
				Set_luggage(current_luggage_resource);

		}
		else
			Con_fatal_error("ERROR: mouse.pointer==0 for object %d (%s) - update logic script!", mouse_touching, FetchObjectName(mouse_touching));
	}
	else	if	((old_mouse_touching)&&(!mouse_touching))	// the cursor has moved off something
	{
		old_mouse_touching=0;		// we've moved off
		Set_mouse(NORMAL_MOUSE_ID);	// reset cursor to normal pointer
		// reset luggage only when necessary
	}
	else	if	((old_mouse_touching)&&(mouse_touching))	// the cursor has moved off something and onto something else
	{														// flip to a blank cursor for a cycle
		mouse_touching=0;			// ignore the new id this cycle - should hit next cycle
		old_mouse_touching=0;		// we've moved off
		Set_mouse(0);			// blank cursor
		mouse_flicked_off=1;		// so we know to set the mouse pointer back to normal if 2nd hot-spot doesn't register because mouse pulled away quickly (onto nothing)
		
		// reset luggage only when necessary
	}
	else	// for when mouse was flicked off for one cycle, but then moved onot nothing before 2nd hot-spot registered
	{
		// both 'old_mouse_touching' & 'mouse_touching' will be zero
		Set_mouse(NORMAL_MOUSE_ID);	// reset cursor to normal pointer
	}

	// possible check for edge of screen more-to-scroll here on large screens

}
//------------------------------------------------------------------------------------
void Set_mouse(uint32 res)	// (4dec96 JEL)
{
	uint8	*icon;
	uint32	len;

	mouse_pointer_res=res;	//high level - whats the mouse - for the engine

	if (res)	// if it's not NULL
	{
		icon = res_man.Res_open( res ) + sizeof(_standardHeader);
		len = res_man.resList[res]->size - sizeof(_standardHeader);

			if (res == NORMAL_MOUSE_ID)	// don't pulse the normal pointer
				SetMouseAnim(icon, len, RDMOUSE_NOFLASH);	// 0 means don't pulse this pointer, just do the regular anim loop
			else
	 			SetMouseAnim(icon, len, RDMOUSE_FLASH);	// 1 mean pulse before starting regular anim loop

		res_man.Res_close( res );
	}
	else
		SetMouseAnim(NULL,0,0);	// blank cursor
}
//------------------------------------------------------------------------------------
void Set_luggage(uint32	res)	//Tony26Nov96
{
	uint8	*icon;
	uint32	len;

	if (res)	// if not NULL
	{
		real_luggage_item=res;

		icon = res_man.Res_open( res ) + sizeof(_standardHeader);
		len = res_man.resList[res]->size - sizeof(_standardHeader);

			SetLuggageAnim(icon, len);

		res_man.Res_close( res );
	}
	else
	{	real_luggage_item=0;
		SetLuggageAnim(NULL, 0);
	}
}
//------------------------------------------------------------------------------------
uint32	Check_mouse_list(void)	//Tony30Sept96
{
	int32	priority=0;
	uint32	j=1;


	if	(cur_mouse>1)
	{
		while(priority<10)	//number of priorities subject to implementation needs
		{
			if ((mouse_list[j].priority==priority) &&	// if the mouse pointer is over this mouse-detection-box
				(mousex+this_screen.scroll_offset_x >= mouse_list[j].x1) &&
				(mousex+this_screen.scroll_offset_x <= mouse_list[j].x2) &&
				(mousey+this_screen.scroll_offset_y >= mouse_list[j].y1) &&
				(mousey+this_screen.scroll_offset_y <= mouse_list[j].y2))
			{

/*
				if (mouse_list[j].anim_resource)	// want to use sprite as a mouse mask, for better accuracy of detection (25oct96 JEL)
				{
					// only works for uncompressed sprite data!!
					// THIS IS NEVER USED IN SWORD2

	   				if (Check_sprite_pixel(j))	// if the mouse is touching a non-zero pixel of the sprite
					{
						mouse_touching=mouse_list[j].id;	// record id
						CreatePointerText(mouse_list[j].pointer_text);	// James16jun97
						return(mouse_list[j].pointer);	//return pointer type
					}
				}
				else	// ok, we're touching the detection-box
*/
				{
					mouse_touching=mouse_list[j].id;	// record id

					// change all COGS pointers to CROSHAIR
					if (mouse_list[j].pointer == USE)
						mouse_list[j].pointer = CROSHAIR;
					CreatePointerText(mouse_list[j].pointer_text, mouse_list[j].pointer);	// James16jun97
					return(mouse_list[j].pointer);	//return pointer type
				}
			}

			j++;	//next
			if	(j==cur_mouse)
			{
				j=0;
				priority++;				//next priority - 0 being the highest, 9 the lowest
			}
		}
	}

	mouse_touching=0;	// touching nothing
	return(0);			// no pointer to return
}
//------------------------------------------------------------------------------------

void CreatePointerText(uint32 textId, uint32 pointerRes)	// James16jun97
{
	uint32	local_text;
	uint32	text_res;
	uint8	*text;
	int16	xOffset, yOffset;	// offsets for pointer text sprite from pointer position
	uint8	justification;

	#define POINTER_TEXT_WIDTH	640		// just in case!
	#define POINTER_TEXT_PEN	184		// white

	if (pointerTextSelected)
	{
		if (textId)
		{
			//-------------------------------------------
			// check what the pointer is, to set offsets correctly for text position

			switch(pointerRes)
			{
				case CROSHAIR:
					yOffset = -7;	// above	(above & to the right of the pointer coordinate)
					xOffset = +10;	// right
					break;

				case EXIT0:
					yOffset = +15;	// below
					xOffset = +20;	// right
					break;

				case EXIT1:
					yOffset = +16;	// below
					xOffset = -10;	// left
					break;

				case EXIT2:
					yOffset = +10;	// below
					xOffset = -22;	// left
					break;

				case EXIT3:
					yOffset = -16;	// above
					xOffset = -10;	// left
					break;

				case EXIT4:
					yOffset = -15;	// above
					xOffset = +15;	// right
					break;

				case EXIT5:
					yOffset = -12;	// above
					xOffset = +10;	// right
					break;

				case EXIT6:
					yOffset = +10;	// below
					xOffset = +25;	// right
					break;

				case EXIT7:
					yOffset = +16;	// below
					xOffset = +20;	// right
					break;

				case EXITDOWN:
					yOffset = -20;	// above
					xOffset = -10;	// left
					break;

				case EXITUP:
					yOffset = +20;	// below
					xOffset = +20;	// right
					break;

				case MOUTH:
					yOffset = -10;	// above
					xOffset = +15;	// right
					break;

				case NORMAL:
					yOffset = -10;	// above
					xOffset = +15;	// right
					break;

				case PICKUP:
					yOffset = -40;	// above
					xOffset = +10;	// right
					break;

				case SCROLL_L:
					yOffset = -20;	// above
					xOffset = +20;	// right
					break;

				case SCROLL_R:
					yOffset = -20;	// above
					xOffset = -20;	// left
					break;

				case USE:
					yOffset = -8;	// above
					xOffset = +20;	// right
					break;

				default:	// shouldn't happen if we cover all the different mouse pointers above
					yOffset = -10;	// above
					xOffset = +10;	// right
			}

   			//-------------------------------------------
			// set up justification for text sprite,
			// based on it's offsets from the pointer position

			// from maketext.h
			//#define NO_JUSTIFICATION				0	// only for debug text, since it doesn't keep text inside the screen margin!
			//#define POSITION_AT_CENTRE_OF_BASE	1	// these all force text inside the screen edge margin when necessary
			//#define POSITION_AT_CENTRE_OF_TOP		2
			//#define POSITION_AT_LEFT_OF_TOP		3
			//#define POSITION_AT_RIGHT_OF_TOP		4
			//#define POSITION_AT_LEFT_OF_BASE		5
			//#define POSITION_AT_RIGHT_OF_BASE		6
			//#define POSITION_AT_LEFT_OF_CENTRE	7
			//#define POSITION_AT_RIGHT_OF_CENTRE	8

			//-----------------------------------------
			if (yOffset < 0)		// if above pointer
			{
				if (xOffset < 0)		// above left
				{
					justification = POSITION_AT_RIGHT_OF_BASE;
				}
				else if (xOffset > 0)	// above right
				{
					justification = POSITION_AT_LEFT_OF_BASE;	// text sprite is justified from it's bottom-left corner
				}
				else	// (xOffset==0)	// above centre
				{
					justification = POSITION_AT_CENTRE_OF_BASE;
				}
			}
			//-----------------------------------------
			else if (yOffset > 0)	// if below pointer
			{
				if (xOffset < 0)		// below left
				{
					justification = POSITION_AT_RIGHT_OF_TOP;
				}
				else if (xOffset > 0)	// below right
				{
					justification = POSITION_AT_LEFT_OF_TOP;
				}
				else	// (xOffset==0)	// below centre
				{
					justification = POSITION_AT_CENTRE_OF_TOP;
				}
			}
			//-----------------------------------------
			else					// if at same y-coord as pointer
			{
				if (xOffset < 0)		// centre left
				{
					justification = POSITION_AT_RIGHT_OF_CENTRE;
				}
				else if (xOffset > 0)	// centre right
				{
					justification = POSITION_AT_LEFT_OF_CENTRE;
				}
				else	// (xOffset==0)	// centre centre
				{
					justification = POSITION_AT_LEFT_OF_CENTRE;		// shouldn't happen anyway!
				}
			}
			//-------------------------------------------

			text_res	= textId/SIZE;		// text resource number
			local_text	= textId&0xffff;	// text line number within the resource

			text = FetchTextLine( res_man.Res_open(text_res), local_text );	// open text file & get the line

			// 'text+2' to skip the first 2 bytes which form the line reference number
			pointer_text_bloc_no = Build_new_block(text+2, mousex+xOffset, mousey+yOffset, POINTER_TEXT_WIDTH, POINTER_TEXT_PEN, RDSPR_TRANS+RDSPR_DISPLAYALIGN, speech_font_id, justification);

			res_man.Res_close(text_res);	// now ok to close the text file
		}
	}
}
//------------------------------------------------------------------------------------
void ClearPointerText(void)	// James16jun97
{
	if (pointer_text_bloc_no)
	{
		Kill_text_bloc(pointer_text_bloc_no);
		pointer_text_bloc_no=0;
	}
}
//------------------------------------------------------------------------------------
/* NOT USED IN SWORD2
uint8 Check_sprite_pixel( uint32 j )	// (25oct96 JEL)
{
	// only works for uncompressed sprite data!!

	uint8 *file;
	uint8 hit;
	_frameHeader *frame_head;
	int16 sprite_x, sprite_y;
	int16 in_sprite_x, in_sprite_y;
	uint8 *sprite_data;


	sprite_x = mouse_list[j].x1;	// sprite coords have been copied to mouse area coords by FN_register_X_frame (X = sort, back or fore)
	sprite_y = mouse_list[j].y1;	// so easier to get them from there than from the anim file again


	file = res_man.Res_open(mouse_list[j].anim_resource);		// open the anim file & point to start of it
	frame_head = FetchFrameHeader(file,mouse_list[j].anim_pc);	// point to frame header of current frame
	sprite_data = (uint8 *)(frame_head + 1);					// point to start of frame data

	in_sprite_x = mousex + this_screen.scroll_offset_x - sprite_x;	// x-coord of mouse from origin at top-left of sprite
	in_sprite_y = mousey + this_screen.scroll_offset_y - sprite_y;	// y-coord of mouse from origin at top-left of sprite

	hit = sprite_data[in_sprite_y * frame_head->width + in_sprite_x];	// hit = value of pixel to which the mouse is pointing

 	res_man.Res_close(mouse_list[j].anim_resource);				// close anim file

	return (hit);	// we are touching the sprite if 'hit' is non-zero
}
*/
//------------------------------------------------------------------------------------
int32 FN_no_human(int32 *params)	//Tony30Sept96
{
//param	none

	MOUSE_AVAILABLE = 0;	// for logic scripts (James21may97)

	ClearPointerText();

	mouse_status=1;	//human/mouse off

	Set_mouse(0);			// blank cursor
	Set_luggage(0);			// blank cursor


//must be normal mouse situation or a largely neutral situation - special menus use No_human
	if	(TALK_FLAG==0)	//dont hide menu in conversations
		HideMenu(RDMENU_BOTTOM);


	if	(mouse_mode==MOUSE_system_menu)
	{
		mouse_mode=MOUSE_normal;	//close menu
		HideMenu(RDMENU_TOP);	//	start the menu coming down
	}


	if (params);


	return(1);	//script continue
}
//------------------------------------------------------------------------------------
void	No_human(void)	//Tony4June97
{
//leaves the menus open
//used by the system when clicking right on a menu item to examine it and
//when combining objects

	MOUSE_AVAILABLE = 0;	// for logic scripts (James21may97)

	mouse_status=1;	//human/mouse off

	Set_mouse(0);			// blank cursor
	Set_luggage(0);			// blank cursor
}
//------------------------------------------------------------------------------------
int32 FN_add_human(int32 *params)	//Tony30Sept96
{
	//param	none
#ifdef _SWORD2_DEBUG
	uint8 black[4]	= {0,0,0,0};
	uint8 white[4]	= {255,255,255,0};
#endif									// ('0' means don't print to console, but console isn't up anyway)



	MOUSE_AVAILABLE = 1;	// for logic scripts (James21may97)


	if	(mouse_status)	//off
	{
		mouse_status=0;	//on
		mouse_touching=1;	//forces engine to choose a cursor
	}

	CLICKED_ID=0;	//clear this to reset no-second-click system

	// this is now done outside the OBJECT_HELD check in case it's set to zero before now! (James 10july97)
	mouse_mode_locked=0;	//unlock the mouse from possible large object lock situtations - see syphon in rm 3

	if	(OBJECT_HELD)	//was dragging something around
	{
		OBJECT_HELD=0;			// need to clear this again

		examining_menu_icon=0;	// and these may also need clearing
		COMBINE_BASE=0;			// - just in case


		Set_luggage(0);			// blank cursor
	}



	if ((mousey>399)&&(mouse_mode!=MOUSE_holding))	// if mouse is over menu area
	{
		mouse_mode=MOUSE_normal;	// VITAL - reset things & rebuild the menu
		Set_mouse(NORMAL_MOUSE_ID);
	}
	else if (mousey>399)
		Set_mouse(NORMAL_MOUSE_ID);


	//----------------------------------------------------------------------------------------------
	// enabled/disabled from console; status printed with on-screen debug info
#ifdef _SWORD2_DEBUG
	if (testingSnR)						// testing logic scripts by simulating an instant Save & Restore
	{
		BS2_SetPalette(0, 1, white, RDPAL_INSTANT);

		Clear_fx_queue();				// stops all fx & clears the queue - eg. when leaving a location
		res_man.Kill_all_objects(0);	// ie. trashing all object resources so they load in fresh & restart their logic scripts

		BS2_SetPalette(0, 1, black, RDPAL_INSTANT);
	}
#endif									// ('0' means don't print to console, but console isn't up anyway)
	//----------------------------------------------------------------------------------------------
	if (params);

	return(1);	//script continue
}
//------------------------------------------------------------------------------------
int32 FN_register_mouse(int32 *params)	//Tony29Oct96
{
//this call would be made from an objects service script 0
//the object would be one with no graphic but with a mouse - i.e. a floor
// or one whose mouse area is manually defined rather than intended to fit sprite shape

//param	0 pointer to Object_mouse or 0 for no write to mouse list

//	Zdebug(1,"cur_mouse = %d", cur_mouse);


   Object_mouse *ob_mouse;

	ob_mouse = (Object_mouse *) params[0];	// param 1 is pointer to mouse structure

	if (ob_mouse->pointer)	// only if 'pointer' isn't NULL
	{
		#ifdef _SWORD2_DEBUG
		if	(cur_mouse==TOTAL_mouse_list)
			Con_fatal_error("ERROR: mouse_list full [%s line %u]",__FILE__,__LINE__);
		#endif

		mouse_list[cur_mouse].x1			= ob_mouse->x1;
		mouse_list[cur_mouse].y1			= ob_mouse->y1;
		mouse_list[cur_mouse].x2			= ob_mouse->x2;
		mouse_list[cur_mouse].y2			= ob_mouse->y2;

		mouse_list[cur_mouse].priority		= ob_mouse->priority;
		mouse_list[cur_mouse].pointer		= ob_mouse->pointer;

		//-----------------------------------------------
		// (James17jun97)
		// check if pointer text field is set due to previous object using this slot (ie. not correct for this one)
		if ((mouse_list[cur_mouse].pointer_text) && (mouse_list[cur_mouse].id != (int32)ID))	// if 'pointer_text' field is set, but the 'id' field isn't same is current id
			mouse_list[cur_mouse].pointer_text=0;												// then we don't want this "left over" pointer text
		//-----------------------------------------------

		mouse_list[cur_mouse].id			= ID;	// get id from system variable 'id' which is correct for current object

		mouse_list[cur_mouse].anim_resource	= 0;	// not using sprite as mask - this is only done from FN_register_frame()
		mouse_list[cur_mouse].anim_pc		= 0;

		//Zdebug("mouse id %d", mouse_list[cur_mouse].id);

		cur_mouse++;
	}

	return(IR_CONT);	//	continue script
}
//------------------------------------------------------------------------------------
// use this in the object's service script prior to registering the mouse area
// ie. before FN_register_mouse or FN_register_frame
// - best if kept at very top of service script

int32 FN_register_pointer_text(int32 *params)	// James16jun97
{
//	param 0 local id of text line to use as pointer text

	#ifdef _SWORD2_DEBUG
	if	(cur_mouse==TOTAL_mouse_list)
		Con_fatal_error("ERROR: mouse_list full [%s line %u]",__FILE__,__LINE__);
	#endif

	mouse_list[cur_mouse].id			= ID;	// current object id - used for checking pointer_text when mouse area registered (in FN_register_mouse & FN_register_frame)
	mouse_list[cur_mouse].pointer_text	= params[0];

	return(IR_CONT);	//	continue script

}
//------------------------------------------------------------------------------------
int32 FN_blank_mouse(int32 *params)	//Tony29Oct96
{
//set mouse to normal pointer - used in speech

//no params


	Set_mouse(0);

	if (params);

	return(1);	//cont
}
//------------------------------------------------------------------------------------
int32 FN_init_floor_mouse(int32 *params)	// James29nov96
{
	// params	0 pointer to object's mouse structure

 	Object_mouse	*ob_mouse = (Object_mouse *) params[0];

	ob_mouse->x1		= 0;
	ob_mouse->y1		= 0;
	ob_mouse->x2		= this_screen.screen_wide-1;
	ob_mouse->y2		= this_screen.screen_deep-1;
	ob_mouse->priority	= 9;				// floor is always lowest priority
	ob_mouse->pointer	= NORMAL_MOUSE_ID;	// normal pointer


	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------
#define SCROLL_MOUSE_WIDTH	20					// James13feb97 (updated by James 25mar97)
//---------------------------------------------------------------------------------------------------------------------
int32 FN_set_scroll_left_mouse(int32 *params)	// James13feb97
{
	// params	0 pointer to object's mouse structure

 	Object_mouse	*ob_mouse = (Object_mouse *) params[0];


	ob_mouse->x1		= 0;
	ob_mouse->y1		= 0;
	ob_mouse->x2		= this_screen.scroll_offset_x + SCROLL_MOUSE_WIDTH;
	ob_mouse->y2		= this_screen.screen_deep-1;
	ob_mouse->priority	= 0;	// highest priority

	if (this_screen.scroll_offset_x > 0)	// if not fully scrolled to the left
		ob_mouse->pointer = SCROLL_LEFT_MOUSE_ID;
	else
		ob_mouse->pointer = 0;	// so the mouse area doesn't get registered


	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32 FN_set_scroll_right_mouse(int32 *params)	// James13feb97
{
	// params	0 pointer to object's mouse structure

 	Object_mouse	*ob_mouse = (Object_mouse *) params[0];


	ob_mouse->x1		= this_screen.scroll_offset_x + screenWide - SCROLL_MOUSE_WIDTH;
	ob_mouse->y1		= 0;
	ob_mouse->x2		= this_screen.screen_wide-1;
	ob_mouse->y2		= this_screen.screen_deep-1;
	ob_mouse->priority	= 0;	// highest priority

	if (this_screen.scroll_offset_x < this_screen.max_scroll_offset_x)	// if not fully scrolled to the right
		ob_mouse->pointer = SCROLL_RIGHT_MOUSE_ID;
	else
		ob_mouse->pointer = 0;	// so the mouse area doesn't get registered


	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32	FN_set_object_held(int32	*params)	//tony19May97
{
//params	0 luggage icon to set

	Set_luggage(params[0]);

	OBJECT_HELD=params[0];
	current_luggage_resource=params[0];

	mouse_mode_locked=1;	//mode locked - no top menu available



	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
// called from speech scripts to remove the chooser bar when it's not appropriate to keep it displayed
int32 FN_remove_chooser(int32 *params)	// James13aug97
{
	HideMenu(RDMENU_BOTTOM);

	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32 FN_disable_menu(int32 *params)	//Tony1Oct96
{
	mouse_mode_locked=1;	//mode locked - no top menu available
	mouse_mode=MOUSE_normal;

	HideMenu(RDMENU_TOP);
	HideMenu(RDMENU_BOTTOM);

	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32	FN_enable_menu(int32	*params)	//tony4June97
{


	mouse_mode_locked=0;	//mode locked - no top menu available

	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32 FN_check_player_activity(int32 *params)	// James23july97
{
//	Used to decide when to trigger music cues described as "no player activity for a while"

//	params 0	threshold delay in seconds, ie. what we want to check the actual delay against

	uint32 threshold = params[0]*12;	// in game cycles

	if (player_activity_delay >= threshold)	// if the actual delay is at or above the given threshold
	{
		player_activity_delay=0;	// reset activity delay counter, now that we've got a positive check
		RESULT=1;
	}
	else
		RESULT=0;

	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
int32 FN_reset_player_activity_delay(int32 *params)	// James23july97
{
// Use if you want to deliberately reset the "no player activity" counter for any reason

//	no params

	player_activity_delay=0;	// reset activity delay counter

	return(IR_CONT);	//	continue script
}
//---------------------------------------------------------------------------------------------------------------------
void Monitor_player_activity(void)	// James23july97
{
	if (CheckForMouseEvents())	// if there is at least one mouse event outstanding
		player_activity_delay=0;	// reset activity delay counter
	else
		player_activity_delay++;	// no. of game cycles since mouse event queue last empty
}
//---------------------------------------------------------------------------------------------------------------------

