// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Base class for worldspawn objects.  This should be subclassed whenever
// a DLL has new game behaviour that needs to be initialized before any other
// entities are created, or before any entity thinks each frame.  Also controls
// spawning of clients.
//

#include "g_local.h"
#include "entity.h"
#include "scriptmaster.h"
#include "worldspawn.h"
#include "surface.h"
#include "console.h"
#include "deadbody.h"
#include "gravpath.h"
#include "earthquake.h"
//###
#include "spritegun.h" // added for spritegun
#include "checkpoints.h"
//###

extern void CreateMissionComputer( void );

WorldPtr world;

//### added some options to the worldspawn
/*****************************************************************************/
/*SINED worldspawn (0 0 0) ? CINEMATIC x BIKESONLY BODIESFADE

   Only used for the world.
   "sky"			environment map name
   "skyaxis"	vector axis for rotating sky
   "skyrotate"	speed of rotation in degrees/second
   "soundtrack" the music file to use
   "cdtrack"	music cd track number
   "gravity"	800 is default gravity
   "message"	text to print at user logon
   "skipthread" thread that is activated to skip this level (if cinematic)

   "watercolor" specifies the view blend color while in water.
   "wateralpha" specifies the alpha of the view blend while in water.
   "lavacolor" specifies the view blend color while in lava.
   "lavaalpha" specifies the alpha of the view blend while in lava.
   "lightcolor" specifies the view blend color while in light.
   "lightalpha" specifies the alpha of the view blend while in light.

   "lavadamage" sets the amount of damage laval should do per waterheight level. Default is 10.

   "cpsounds" sets the script file to get the hoverbike checkpoint sounds from. Default is "global/default_cp_sounds.scr"

   BIKESONLY  all players spawn in hoverbikes and can't get off. If the bike dies, they die.
   BODIESFADE  makes dead player bodies fade shortly after dying. Good for big open maps.
   /*****************************************************************************/

#define CINEMATIC 1
//###
#define BIKESONLY 4
#define BODIESFADE 8
//###

CLASS_DECLARATION( Entity, World, "worldspawn" );

ResponseDef World::Responses[] =
{
	{ NULL, NULL }
};

World::World()
{
	const char      *text;
	str mapname;
	int i;
	Vector water_color;
	Vector lightvolume_color;
	Vector lava_color;
	Event *ev; //### added for spritegun

	world = this;

	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_BSP );

	// world model is always index 1
	edict->s.modelindex = 1;
	model = "*1";

	// Anything that modifies configstrings, or spawns things is ignored when loading savegames
	if ( LoadingSavegame )
	{
		return;
	}

	// inform the client that this is deathmatch, and we should
	// draw deathmatch stats.  This goes around what the CS_STATUSBAR
	// used to be used for since we moved all HUDS to the client.
	if ( deathmatch->value )
	{
		gi.configstring( CS_STATUSBAR, "DEATHMATCH" );
	}
	else
	{
		gi.configstring( CS_STATUSBAR, "SINGLE_PLAYER" );
	}

	//
	// see if the level has a soundtrack associated withit
	//
	text = G_GetSpawnArg( "soundtrack" );
	if ( text )
	{
		gi.configstring( CS_SOUNDTRACK, text );
	}

	text = G_GetSpawnArg( "sky" );
	if ( text )
	{
		gi.configstring( CS_SKY, text );
	}
	else
	{
		gi.configstring( CS_SKY, "sky_" );
	}

	text = G_GetSpawnArg( "skyrotate" );
	gi.configstring( CS_SKYROTATE, text ? text : "0" );

	text = G_GetSpawnArg( "skyaxis" );
	gi.configstring( CS_SKYAXIS, text ? text : "0 0 0" );

	text = G_GetSpawnArg( "cdtrack" );
	gi.configstring( CS_CDTRACK, text ? text : "0" );

	gi.configstring ( CS_MAXCLIENTS, va( "%i", (int)(maxclients->value) ) );

	text = G_GetSpawnArg( "gravity" );
	if ( !text )
	{
		gi.cvar_set( "sv_gravity", "800" );
	}
	else
	{
		gi.cvar_set( "sv_gravity", text );
	}

	// get skipthread
	skipthread = G_GetStringArg( "skipthread" );

	// the world takes blast marks and sparks by default
	flags |= FL_BLASTMARK;
	flags |= FL_SPARKS;

	// Reserve some space for dead bodies
	InitializeBodyQueue();

	//
	// see if this is a cinematic level
	//
	if ( spawnflags & CINEMATIC )
	{
		level.cinematic = true;
	}
	else
	{
		level.cinematic = false;
	}

	level.nextmap = G_GetStringArg( "nextmap" );

	// make some data visible to the server
	text = G_GetSpawnArg( "message" );
	if ( text )
	{
		gi.configstring( CS_NAME, text );
		level.level_name = text;
	}
	else
	{
		level.level_name = level.mapname;
	}

	// Set up script
	text = G_GetSpawnArg( "script" );
	if ( !text )
	{
		// No script specified.  Try using the mapname as the script name
		mapname = "maps/";
		mapname += level.mapname;
		for( i = mapname.length() - 1; i >= 0; i-- )
		{
			if ( mapname[ i ] == '.' )
			{
				mapname[ i ] = 0;
				break;
			}
		}

		mapname += ".scr";

		text = &mapname[ 5 ];

		// If there isn't a script with the same name as the map, then don't try to load script
		if ( gi.LoadFile( mapname.c_str(), NULL, 0 ) == -1 )
		{
			text = NULL;
		}
	}

	if ( text )
	{
		gi.dprintf( "Adding script: '%s'\n", text );
		mapname = va( "maps/%s", text );

		// just set the script, we will start it in G_Spawn
		ScriptLib.SetGameScript( mapname.c_str() );
	}

//###
	// read in sprite file after everything's spawned
	ev = new Event( "spg_load" );
	Spritecontrol.PostEvent( ev, 0.1 );

	// setup checkpoints sounds
	level.cp_sounds_script = G_GetStringArg( "cpsounds", "global/default_cp_sounds.scr" );

	//set this here for when there's no checkpoints
	gi.configstring( CS_CHECKPOINTS, "NONE" );

	// init the checkpoint counter
	level.cp_anyorder = false;
	level.cp_num = 0;

	if(spawnflags & BIKESONLY)
	{
		level.bikesonly = true;
	}
	else
	{
		level.bikesonly = false;
	}

	if(spawnflags & BODIESFADE)
	{
		level.bodiesfade = true;
	}
	else
	{
		level.bodiesfade = false;
	}

	level.lavadamage = G_GetIntArg( "lavadamage", 10 );

	// remove any time limit offset
	reset_timeofs = 0;
//###

	// Set the color for the blends.
	water_color       = G_GetVectorArg( "watercolor",Vector( 0,0,1 ));
	level.water_alpha = G_GetFloatArg( "wateralpha",0.1 );

	lightvolume_color = G_GetVectorArg( "lightcolor",Vector( 1,1,1 ));
	level.lightvolume_alpha = G_GetFloatArg( "lightalpha",0.5 );

	lava_color        = G_GetVectorArg( "lavacolor",Vector( 1.0,0.3,0 ));
	level.lava_alpha  = G_GetFloatArg( "lavaalpha",0.6 );

	level.water_color = water_color;
	level.lightvolume_color = lightvolume_color;
	level.lava_color = lava_color;

	//
	// reset the earthquake
	//
	level.earthquake = 0;
}

TargetList * World::GetTargetList( str &targetname )
{
	TargetList * targetlist;
	int i;

	for( i = 1; i <= targetList.NumObjects(); i++ )
	{
		targetlist = targetList.ObjectAt( i );
		if ( targetname == targetlist->targetname)
			return targetlist;
	}
	targetlist = new TargetList( targetname );
	targetList.AddObject( targetlist );
	return targetlist;
}

void World::AddTargetEntity( str &targetname, Entity * ent )
{
	TargetList * targetlist;

	targetlist = GetTargetList( targetname );
	targetlist->AddEntity( ent );
}

void World::RemoveTargetEntity( str &targetname, Entity * ent )
{
	TargetList * targetlist;

	targetlist = GetTargetList( targetname );
	targetlist->RemoveEntity( ent );
}

Entity * World::GetNextEntity( str &targetname, Entity * ent )
{
	TargetList * targetlist;

	targetlist = GetTargetList( targetname );
	return targetlist->GetNextEntity( ent );
}

//### added so I can trigger stuff easily from code
void World::ActivateTarget( str &targetname )
{
	TargetList * targetlist;
	Event *event;
	Entity *ent;
	int i, num;

	targetlist = GetTargetList( targetname );
	num = targetlist->list.NumObjects();
	for ( i = 1; i <= num; i++ )
	{
		ent = targetlist->list.ObjectAt( i );

		assert( ent );

		event = new Event( EV_Activate );
		event->AddEntity( world );
		ent->ProcessEvent( event );
	}
}
//###

World::~World()
{
	FreeTargetList();
}

void World::FreeTargetList( void )
{
	int i;
	int num;

	num = targetList.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		delete targetList.ObjectAt( i );
	}

	targetList.FreeObjectList();
}

//
// List stuff for targets
//

CLASS_DECLARATION( Class, TargetList, NULL );

ResponseDef TargetList::Responses[] =
{
	{ NULL, NULL }
};

TargetList::TargetList()
{}

TargetList::TargetList( str &tname )
{
	targetname = tname;
}

TargetList::~TargetList()
{}

void TargetList::AddEntity( Entity * ent )
{
	if ( !list.ObjectInList( ent ) )
	{
		list.AddObject( ent );
	}
}

void TargetList::RemoveEntity( Entity * ent )
{
	if ( list.ObjectInList( ent ) )
	{
		list.RemoveObject( ent );
	}
}

Entity * TargetList::GetNextEntity( Entity * ent )
{
	int index;

	index = 0;
	if ( ent )
		index = list.IndexOfObject( ent );
	index++;
	if ( index > list.NumObjects() )
		return NULL;
	else
		return list.ObjectAt( index );
}

