//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/surface.cpp                      $
// $Revision:: 27                                                             $
//   $Author:: Jimdose                                                        $
//     $Date:: 12/18/98 11:02p                                                $
//
// Copyright (C) 1998 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.
//
// $Log:: /Quake 2 Engine/Sin/code/game/surface.cpp                           $
// 
// 27    12/18/98 11:02p Jimdose
// removed include of qcommon.h
// 
// 26    10/20/98 8:26p Markd
// Added Attacker to DamageSurface stuff
// 
// 25    10/15/98 7:33p Markd
// bullet proofed surface not found stuff
// 
// 24    10/15/98 4:37p Markd
// fixed damaged surface stuff
// 
// 23    10/13/98 9:01p Markd
// Fixed bug in TransOn TransOff
// 
// 22    10/09/98 4:54p Markd
// Added threading abilities from surfaces
// 
// 21    10/06/98 9:17p Aldie
// Removed SURF_STRONG
// 
// 20    10/05/98 11:29p Markd
// Put in MadeBreakingSound
// 
// 19    10/05/98 11:01p Jimdose
// Added destructor
// Made Reset free up all surfaces
// 
// 18    9/21/98 4:21p Markd
// Put in archive functions and rewrote all archive routines
// 
// 17    9/19/98 1:56p Markd
// made hard errors event errors or warnings
// 
// 16    9/18/98 8:14p Markd
// rewrote surface system so that surfaces are now damaged by surface name
// instead of by surfinfo
// 
// 15    8/31/98 7:45p Aldie
// Updated surface data structure and removed surfinfo field
// 
// 14    8/24/98 12:45p Markd
// Renamed damage_frame to frame
// 
// 13    8/22/98 12:06a Aldie
// New surface manager implemented
// 
// 12    6/08/98 7:22p Aldie
// Put damage command back in
// 
// 11    5/25/98 2:28p Aldie
// Fixed issues with not loading game dll
// 
// 10    5/24/98 9:00p Jimdose
// made CreateSurface us G_CopyString to allocate a string
// 
// 9     5/24/98 4:48p Jimdose
// Made char *'s const
// 
// 8     5/23/98 12:53p Aldie
// Updated surfaces networking.
// 
// 7     5/13/98 4:47p Aldie
// Update damage surfaces
// 
// 6     5/05/98 5:35p Aldie
// Updated damage surfaces
// 
// 5     5/05/98 3:49p Aldie
// Fixed some prints.
// 
// 4     5/05/98 2:43p Aldie
// Added server side surface states
// 
// 3     3/24/98 4:56p Jimdose
// Fixed bug where surface commands were sent with the wrong syntax
// 
// 2     3/23/98 1:31p Jimdose
// Revamped event and command system
//
// DESCRIPTION:
// Controls surfaces
// 

#include "g_local.h"
#include "listener.h"
#include "surface.h"
#include "misc.h"
#include "specialfx.h"
#include <ctype.h>

CLASS_DECLARATION( Class, Surface, NULL );
ResponseDef Surface::Responses[] =
	{
		{ NULL, NULL }
	};


CLASS_DECLARATION( Listener, SurfaceModifier, NULL );

Event EV_Surface_TransOn( "transOn" );
Event EV_Surface_TransOff( "transOff" );
Event EV_Surface_TransToggle( "transToggle" );
Event EV_Surface_TransMag( "trans_mag" );
Event EV_Surface_Translucence( "translucence" );
Event EV_Surface_Magnitude( "magnitude" );
Event EV_Surface_Frequency( "frequency" );
Event EV_Surface_TransAngle( "trans_angle" );
Event EV_Surface_DamageFrame( "frame" );
Event EV_Surface_Damage( "damage" );

ResponseDef SurfaceModifier::Responses[] =
	{
	   { &EV_Surface_TransOn,					( Response )SurfaceModifier::TranslationOn },
	   { &EV_Surface_TransOff,					( Response )SurfaceModifier::TranslationOff },
	   { &EV_Surface_TransToggle,				( Response )SurfaceModifier::TranslationToggle },
	   { &EV_Surface_TransMag,					( Response )SurfaceModifier::SetTranslationMagnitude },
	   { &EV_Surface_TransAngle,				( Response )SurfaceModifier::SetTranslationAngle },
      { &EV_Surface_Translucence,			( Response )SurfaceModifier::SetTranslucence },
	   { &EV_Surface_Magnitude,				( Response )SurfaceModifier::SetMagnitude },
	   { &EV_Surface_Frequency,				( Response )SurfaceModifier::SetFrequency },
	   { &EV_Surface_DamageFrame,  			( Response )SurfaceModifier::SetDamageFrame },
      { &EV_Surface_Damage,        			( Response )SurfaceModifier::SetDamage },
      { NULL, NULL }
	};

SurfaceModifier surfaceManager;

SurfaceModifier::~SurfaceModifier()
   {
   Reset();
   }

//==========
//AddSurface - to the surfaceManager
//==========
int SurfaceModifier::AddSurface
	(
	Surface *surf
	)
	{
   int num;

	num = surfaceList.AddObject( surf );
   return num;
	}


//=============
//SurfaceExists - returns the number of the surface, 0 if not found.
//=============
int SurfaceModifier::SurfaceExists(const char *surf_name)
   {
   int num,i;
   Surface *p;

	num = surfaceList.NumObjects();
	for( i = 1; i <= num; i++ )
		{
		p = ( Surface * )surfaceList.ObjectAt( i );
      if (!strcmp(p->SurfaceName(),surf_name)) 
         return p->SurfaceNumber();
		}
   return 0;
   }

//=============
//GetSurface - returns the surface, NULL if not found.
//=============
Surface * SurfaceModifier::GetSurface(const char *surf_name)
   {
   int num,i;
   Surface *p;

	num = surfaceList.NumObjects();
	for( i = 1; i <= num; i++ )
		{
		p = ( Surface * )surfaceList.ObjectAt( i );
      if (!strcmp(p->SurfaceName(),surf_name)) 
         return p;
		}
   return NULL;
   }

//=====
//Reset
//=====
void SurfaceModifier::Reset()
   {
   int i;
   int num;

   num = surfaceList.NumObjects();
   for( i = num; i > 0; i-- )
      {
      delete surfaceList.ObjectAt( i );
      }

   surfaceList.FreeObjectList();
   }

//=============
//CreateSurface - Creates a server game surface that can be modified through scripts and events
//=============
void SurfaceModifier::CreateSurface (const char *surf_name, csurface_t *surfinfo)
   {
   csurface_t     *surfptr;
   netsurface_t   *netsurf;
   Surface        *surf;
   int            numframes;

   globals.num_surfaces++;
   surf = new Surface;
   surf->SetDamage( 0 );
   surf->SetState( 0 );
   surf->SetNumber(globals.num_surfaces);
   surf->SetName(surf_name);

   if ( ( surfinfo->flags & SURF_WEAK ) && ( surfinfo->flags & SURF_NORMAL ) )
      surf->SetThreshold( STRONG_DAMAGE_VALUE );
   else if ( surfinfo->flags & SURF_WEAK )
      surf->SetThreshold( WEAK_DAMAGE_VALUE );
   else if ( surfinfo->flags & SURF_NORMAL )
      surf->SetThreshold( NORMAL_DAMAGE_VALUE );

   surfptr = surfinfo;
   numframes = 0;
   while ( surfptr->next )
      {
      numframes++;
      surfptr = surfptr->next;
      if ( numframes > 128 || surfptr == surfinfo )
         {
         gi.dprintf("CreateSurface: Possible infinite animation/damage loop for %s.\n", surf_name );
         break;
         }
      }
   surf->SetNumFrames( numframes );
   surf->SetLightStyle( surfinfo->style );

   AddSurface(surf);
   netsurf = &g_surfaces[globals.num_surfaces];
  
   netsurf->s.name         = G_CopyString( surf_name );
   netsurf->inuse          = true;
   netsurf->s.number       = globals.num_surfaces;
   netsurf->s.groupnumber  = surfinfo->groupnumber;
   netsurf->s.trans_mag    = surfinfo->trans_mag;
   netsurf->s.trans_angle  = surfinfo->trans_angle;
   netsurf->s.translucence = surfinfo->translucence;
   netsurf->s.magnitude    = surfinfo->magnitude;
   netsurf->s.frequency    = surfinfo->frequency;
   netsurf->s.damage_frame = 0;
   if ( surfinfo->flags & SURF_TRANSLATE )
      netsurf->s.trans_state = true;
   else
      netsurf->s.trans_state = false;
   netsurf->s.changed      = true;
   }

//==============
//SetDamageFrame
//==============
void SurfaceModifier::SetDamageFrame (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;
   int            frame;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetDamage", "surface name %s is not found\n", surf_name);
      }
   
   svsurf=&g_surfaces[num];
   frame = ev->GetInteger(2);
   if (svsurf->s.damage_frame != frame)
      {
      svsurf->s.changed = true;
      }
   svsurf->s.damage_frame = frame;
   }

//============
//SetFrequency
//============
void SurfaceModifier::SetFrequency (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetFrequency", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.frequency = ev->GetFloat(2);
   svsurf->s.changed = true;
   }

//============
//SetMagnitude
//============
void SurfaceModifier::SetMagnitude (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetMagnitude", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.magnitude = ev->GetFloat(2);
   svsurf->s.changed = true;
   }

//===============
//SetTranslucence
//===============
void SurfaceModifier::SetTranslucence (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetTranslucence", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.translucence = ev->GetFloat(2);
   svsurf->s.changed = true;
   }

//===================
//SetTranslationAngle
//===================
void SurfaceModifier::SetTranslationAngle (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetDamage", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.trans_angle = ev->GetInteger(2);
   svsurf->s.changed = true;
   }

//=======================
//SetTranslationMagnitude
//=======================
void SurfaceModifier::SetTranslationMagnitude (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetTranslationMagnitude", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.trans_mag = ev->GetInteger(2);
   svsurf->s.changed = true;
   }

//=================
//TranslationToggle
//=================
void SurfaceModifier::TranslationToggle (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::TranslationToggle", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.trans_state = ~svsurf->s.trans_state;
   svsurf->s.changed = true;
   }

//=============
//TranslationOn
//=============
void SurfaceModifier::TranslationOn (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::TranslationOn", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.trans_state = true;
   svsurf->s.changed = true;
   }

//==============
//TranslationOff
//==============
void SurfaceModifier::TranslationOff (Event *ev)
   {
   const char		*surf_name;
   netsurface_t   *svsurf;
   int            num;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::TranslationOff", "surface name %s is not found\n", surf_name);
      }

   svsurf=&g_surfaces[num];
   svsurf->s.trans_state = false;
   svsurf->s.changed = true;
   }

//=========
//SetDamage
//=========
void SurfaceModifier::SetDamage (Event *ev)
   {
   const char		*surf_name;
   int            num;
   int            damage;

   surf_name = ev->GetString(1);
   if (!(num = SurfaceExists(surf_name)))
      {
      ev->Error("SurfaceModifier::SetDamage", "surface name %s is not found\n", surf_name);
      }

   damage = ev->GetInteger(2);
   DoDamage( NULL, surf_name, damage );
   }

//=========
//DoDamage
//=========
void SurfaceModifier::DoDamage ( trace_t * trace, const char * groupname, float damage, Entity * attacker )
   {
   Surface     *surf;
   int			frame;
	int			total_damage;
   int			letter;
   int         state;
   int         currentstate;
   int         numframes;
   int         lightstyle;
   qboolean		dotrigger;
   qboolean    damaged;
   Vector		pos;
	Vector		dir;
   float       surface_damage_threshold;

   surf = GetSurface( groupname );

   if (!surf)
      {
      warning("SurfaceModifier::Damage", "surface name %s is not found\n", groupname);
      return;
      }

   frame = 0;
   if ( trace )
      {
      dir = Vector( trace->dir );
      pos = Vector( trace->endpos ) - dir;
      }

	// let the ai know
	if ( trace )
		{
      MadeBreakingSound( pos, NULL );
		}

   surface_damage_threshold = surf->Threshold();

   // get the current damage of this surface
	total_damage = surf->Damage();
   // see if it has been damaged past the threshold before
   damaged = (total_damage > surface_damage_threshold);
   // get the current state
	currentstate = surf->State();
   // get the number of frames
   numframes = surf->NumFrames();
   // get the light style
   lightstyle = surf->LightStyle();
   // add the damage and the new damage
   total_damage += damage;
   // store the damage back into the surface
   surf->SetDamage( total_damage );
   // init the state to -1 so that it gets put to 0 only if the threshold is exceeded
   state = -1;
   while (total_damage > surface_damage_threshold)
      {
      if (!damaged && trace)
         {
         damaged = true;
         TesselateModel
            (
            NULL,
            TESS_DEFAULT_MIN_SIZE,
            TESS_DEFAULT_MAX_SIZE,
            dir,
            damage,
            TESS_DEFAULT_PERCENT,
            10,
            pos
            );
         SpawnSparks
	         (
            pos,
            dir * -1.0f,
            20
            );
         }

      total_damage -= surface_damage_threshold;
      state++;
      
      if (frame < numframes)
         frame++;
      }
   if ( ( lightstyle >= 0 ) && ( state >= currentstate ) )
      {
      int length;
      int pos;
      int lastvalid;
      qboolean done;

      // get length of name
      length = strlen( groupname );
      lastvalid = 0;
      done = false;
      while ( ( lastvalid < length ) && !done )
         {
         switch( tolower( groupname[ lastvalid ] ) )
            {
            case 'o':
            case 'f':
            case 's':
            case 'q':
            case 'h':
               lastvalid++;
               break;
            default:
               done = true;
               break;
            }
         }
      dotrigger = false;

      if ( currentstate <= lastvalid )
         {
         // copy the current position to a local variable
         pos = state;
         if ( pos >= lastvalid )
            pos = lastvalid - 1;
         letter = tolower( groupname[ pos ] );
         switch( letter )
            {
            case 'o':
               gi.configstring( CS_LIGHTS + lightstyle, "a" );
               state++;
               dotrigger = true;
               break;
            case 'f':
               gi.configstring( CS_LIGHTS + lightstyle, "aanannanaanann" );
               state++;
               dotrigger = true;
               break;
            case 's':
               gi.configstring( CS_LIGHTS + lightstyle, "aaaanaaannaanaaaaan" );
               state++;
               dotrigger = true;
               break;
            case 'q':
               gi.configstring( CS_LIGHTS + lightstyle, "d" );
               state++;
               dotrigger = true;
               break;
            case 'h':
               gi.configstring( CS_LIGHTS + lightstyle, "g" );
               state++;
               dotrigger = true;
               break;
            default:
               break;
            }
         }
      surf->SetState( state );
      if ( dotrigger )
         {
         const char * t;

         t = strchr( groupname, '_' );
         if ( t )
		      {
            Event * event;
            Entity * ent;
		      int n = 0;
            t++;
            // 
            // see if this target exists
            //
		      n = G_FindTarget( n, t );
            if ( n )
               {
		         do
			         {
			         ent = G_GetEntity( n );
		         
			         event = new Event( EV_Activate );
                  event->AddEntity( attacker );
			         ent->ProcessEvent( event );

      		      n = G_FindTarget( n, t );
                  if ( !n )
				         break;
			         }
		         while ( 1 );
               }
            // 
            // otherwise treat it like a thread
            //
            else
               {
               if ( !ExecuteThread( str( t ) ) )
                  {
                  warning( "DoDamage", "%s not found as a script.", t );
                  }
               }
            }
         }
      }
   if ( frame )
      {
      netsurface_t   *svsurf;
      int            num;

      num = surf->SurfaceNumber();
      if ( num )
         {
         svsurf=&g_surfaces[num];
         if (svsurf->s.damage_frame != frame)
            {
            svsurf->s.damage_frame = frame;
            svsurf->s.changed = true;
            }
         }
      }
   }

//=========
//DamageSurface
//=========
void SurfaceModifier::DamageSurface ( trace_t * trace, float damage, Entity * attacker )
   {
   csurface_t  *csurf;

   if ( !trace )
      return;

   csurf = trace->surface;
   // Make sure there is a surface here.
	if ( 
         !csurf || 
         !( csurf->flags & (SURF_WEAK|SURF_NORMAL) )
      )
		{
		return;
		}
   DoDamage( trace, csurf->groupname, damage, attacker );
   }

//==============
//CreateSurfaces
//==============
void CreateSurfaces(csurface_t *surfinfo, int count )
   {
   int i;

   for ( i=0; i<count; i++ )
      {
      if ( !isdigit( surfinfo[ i ].groupname[ 0 ] ) || ( surfinfo[i].flags & SURF_DAMAGE ) )
         {
         if ( !surfaceManager.SurfaceExists( surfinfo[ i ].groupname ) )
            {
            surfaceManager.CreateSurface( surfinfo[ i ].groupname, &surfinfo[ i ] );
            }
         }
      }
   }
