#include "g_local.h"

/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
Fire an origin based temp entity event to the clients.
"style"         type byte
*/
void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator)
{
        gi.WriteByte (svc_temp_entity);
        gi.WriteByte (ent->style);
        gi.WritePosition (ent->s.origin);
        gi.multicast (ent->s.origin, MULTICAST_PVS);
}

void SP_target_temp_entity (edict_t *ent)
{
        ent->use = Use_Target_Tent;
}


//==========================================================

//==========================================================

/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
"noise"         wav file to play
"attenuation"
-1 = none, send to whole level
1 = normal fighting sounds
2 = idle sound level
3 = ambient sound level
"volume"        0.0 to 1.0

Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.

Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
Multiple identical looping sounds will just increase volume without any speed cost.
*/
void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
{
        int             chan;

        if (ent->spawnflags & 3)
        {       // looping sound toggles
                if (ent->s.sound)
                        ent->s.sound = 0;       // turn it off
                else
                        ent->s.sound = ent->noise_index;        // start it
        }
        else
        {       // normal sound
                if (ent->spawnflags & 4)
                        chan = CHAN_VOICE|CHAN_RELIABLE;
                else
                        chan = CHAN_VOICE;
                // use a positioned_sound, because this entity won't normally be
                // sent to any clients because it is invisible
                gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
        }
}

void SP_target_speaker (edict_t *ent)
{
        char    buffer[MAX_QPATH];

        if(!st.noise)
        {
                gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
                return;
        }
        if (!strstr (st.noise, ".wav"))
                Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
        else
                strncpy (buffer, st.noise, sizeof(buffer));
        ent->noise_index = gi.soundindex (buffer);

        if (!ent->volume)
                ent->volume = 1.0;

        if (!ent->attenuation)
                ent->attenuation = 1.0;
        else if (ent->attenuation == -1)        // use -1 so 0 defaults to 1
                ent->attenuation = 0;

        // check for prestarted looping sound
        if (ent->spawnflags & 1)
                ent->s.sound = ent->noise_index;

        ent->use = Use_Target_Speaker;

        // must link the entity so we get areas and clusters so
        // the server can determine who to send updates to
        gi.linkentity (ent);
}


//==========================================================

void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
{
        if (ent->spawnflags & 1)
                strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
        else
                strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);

        game.helpchanged++;
}

/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
*/
void SP_target_help(edict_t *ent)
{
        if (deathmatch->value)
        {       // auto-remove for deathmatch
                G_FreeEdict (ent);
                return;
        }

        if (!ent->message)
        {
                gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
                G_FreeEdict (ent);
                return;
        }
        ent->use = Use_Target_Help;
}

//==========================================================

/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
Counts a secret found.
These are single use targets.
*/
void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
{
        gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);

        level.found_secrets++;

        G_UseTargets (ent, activator);
        G_FreeEdict (ent);
}

void SP_target_secret (edict_t *ent)
{
        if (deathmatch->value)
        {       // auto-remove for deathmatch
                G_FreeEdict (ent);
                return;
        }

        ent->use = use_target_secret;
        if (!st.noise)
                st.noise = "misc/secret.wav";
        ent->noise_index = gi.soundindex (st.noise);
        ent->svflags = SVF_NOCLIENT;
        level.total_secrets++;
        // map bug hack
        if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
                ent->message = "You have found a secret area.";
}

//==========================================================

/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
Counts a goal completed.
These are single use targets.
*/
void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
{
        gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);

        level.found_goals++;

        if (level.found_goals == level.total_goals)
                gi.configstring (CS_CDTRACK, "0");

        G_UseTargets (ent, activator);
        G_FreeEdict (ent);
}

void SP_target_goal (edict_t *ent)
{
        if (deathmatch->value)
        {       // auto-remove for deathmatch
                G_FreeEdict (ent);
                return;
        }

        ent->use = use_target_goal;
        if (!st.noise)
                st.noise = "misc/secret.wav";
        ent->noise_index = gi.soundindex (st.noise);
        ent->svflags = SVF_NOCLIENT;
        level.total_goals++;
}

//==========================================================


/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
Spawns an explosion temporary entity when used.

"delay"         wait this long before going off
"dmg"           how much radius damage should be done, defaults to 0
*/
void target_explosion_explode (edict_t *self)
{
        float           save;

        gi.WriteByte (svc_temp_entity);
        gi.WriteByte (TE_EXPLOSION1);
        gi.WritePosition (self->s.origin);
        gi.multicast (self->s.origin, MULTICAST_PHS);

        T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);

        save = self->delay;
        self->delay = 0;
        G_UseTargets (self, self->activator);
        self->delay = save;
}

void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
{
        self->activator = activator;

        if (!self->delay)
        {
                target_explosion_explode (self);
                return;
        }

        self->think = target_explosion_explode;
        self->nextthink = level.time + self->delay;
}

void SP_target_explosion (edict_t *ent)
{
        ent->use = use_target_explosion;
        ent->svflags = SVF_NOCLIENT;
}


//==========================================================

/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
Changes level to "map" when fired
*/
void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
{
        if (level.intermissiontime)
                return;         // already activated

        if (!deathmatch->value && !coop->value)
        {
                if (g_edicts[1].health <= 0)
                        return;
        }

        // if noexit, do a ton of damage to other
        if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
        {
                T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
                return;
        }

        // if multiplayer, let everyone know who hit the exit
        if (deathmatch->value)
        {
                if (activator && activator->client)
                        gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
        }

        // if going to a new unit, clear cross triggers
        if (strstr(self->map, "*"))     
                game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);

        BeginIntermission (self);
}

void SP_target_changelevel (edict_t *ent)
{
        if (!ent->map)
        {
                gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
                G_FreeEdict (ent);
                return;
        }

        // ugly hack because *SOMEBODY* screwed up their map
   if((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0))
           ent->map = "fact3$secret1";

        ent->use = use_target_changelevel;
        ent->svflags = SVF_NOCLIENT;
}


//==========================================================

/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
Creates a particle splash effect when used.

Set "sounds" to one of the following:
  1) sparks
  2) blue water
  3) brown water
  4) slime
  5) lava
  6) blood

"count" how many pixels in the splash
"dmg"   if set, does a radius damage at this location when it splashes
                useful for lava/sparks
*/

void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
{
        gi.WriteByte (svc_temp_entity);
        gi.WriteByte (TE_SPLASH);
        gi.WriteByte (self->count);
        gi.WritePosition (self->s.origin);
        gi.WriteDir (self->movedir);
        gi.WriteByte (self->sounds);
        gi.multicast (self->s.origin, MULTICAST_PVS);

        if (self->dmg)
                T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
}

void SP_target_splash (edict_t *self)
{
        self->use = use_target_splash;
        G_SetMovedir (self->s.angles, self->movedir);

        if (!self->count)
                self->count = 32;

        self->svflags = SVF_NOCLIENT;
}


//==========================================================

/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
Set target to the type of entity you want spawned.
Useful for spawning monsters and gibs in the factory levels.

For monsters:
        Set direction to the facing you want it to have.

For gibs:
        Set direction if you want it moving and
        speed how fast it should be moving otherwise it
        will just be dropped
*/
void ED_CallSpawn (edict_t *ent);

void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
{
        edict_t *ent;

        ent = G_Spawn();
        ent->classname = self->target;
        VectorCopy (self->s.origin, ent->s.origin);
        VectorCopy (self->s.angles, ent->s.angles);
        ED_CallSpawn (ent);
        gi.unlinkentity (ent);
        KillBox (ent);
        gi.linkentity (ent);
        if (self->speed)
                VectorCopy (self->movedir, ent->velocity);
}

void SP_target_spawner (edict_t *self)
{
        self->use = use_target_spawner;
        self->svflags = SVF_NOCLIENT;
        if (self->speed)
        {
                G_SetMovedir (self->s.angles, self->movedir);
                VectorScale (self->movedir, self->speed, self->movedir);
        }
}

//==========================================================

/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
Fires a blaster bolt in the set direction when triggered.

dmg             default is 15
speed   default is 1000
*/

void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
{
        int effect;

        if (self->spawnflags & 2)
                effect = 0;
        else if (self->spawnflags & 1)
                effect = EF_HYPERBLASTER;
        else
                effect = EF_BLASTER;

        fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
        gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
}

void SP_target_blaster (edict_t *self)
{
        self->use = use_target_blaster;
        G_SetMovedir (self->s.angles, self->movedir);
        self->noise_index = gi.soundindex ("weapons/laser2.wav");

        if (!self->dmg)
                self->dmg = 15;
        if (!self->speed)
                self->speed = 1000;

        self->svflags = SVF_NOCLIENT;
}


//==========================================================

/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit.  It is OK to check multiple triggers.  Message, delay, target, and killtarget also work.
*/
void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
{
        game.serverflags |= self->spawnflags;
        G_FreeEdict (self);
}

void SP_target_crosslevel_trigger (edict_t *self)
{
        self->svflags = SVF_NOCLIENT;
        self->use = trigger_crosslevel_trigger_use;
}

/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
killtarget also work.

"delay"         delay before using targets if the trigger has been activated (default 1)
*/
void target_crosslevel_target_think (edict_t *self)
{
        if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
        {
                G_UseTargets (self, self);
                G_FreeEdict (self);
        }
}

void SP_target_crosslevel_target (edict_t *self)
{
        if (! self->delay)
                self->delay = 1;
        self->svflags = SVF_NOCLIENT;

        self->think = target_crosslevel_target_think;
        self->nextthink = level.time + self->delay;
}

//==========================================================

/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
When triggered, fires a laser.  You can either set a target
or a direction.
*/

void target_laser_think (edict_t *self)
{
        edict_t *ignore;
        vec3_t  start;
        vec3_t  end;
        trace_t tr;
        vec3_t  point;
        vec3_t  last_movedir;
        int             count;

        if (self->spawnflags & 0x80000000)
                count = 8;
        else
                count = 4;

        if (self->enemy)
        {
                VectorCopy (self->movedir, last_movedir);
                VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
                VectorSubtract (point, self->s.origin, self->movedir);
                VectorNormalize (self->movedir);
                if (!VectorCompare(self->movedir, last_movedir))
                        self->spawnflags |= 0x80000000;
        }

        ignore = self;
        VectorCopy (self->s.origin, start);
        VectorMA (start, 2048, self->movedir, end);
        while(1)
        {
                tr = do_trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);

                if (!tr.ent)
                        break;

                // hurt it if we can
                if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
                        T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);

                // if we hit something that's not a monster or player or is immune to lasers, we're done
                if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
                {
                        if (self->spawnflags & 0x80000000)
                        {
                                self->spawnflags &= ~0x80000000;
                                gi.WriteByte (svc_temp_entity);
                                gi.WriteByte (TE_LASER_SPARKS);
                                gi.WriteByte (count);
                                gi.WritePosition (tr.endpos);
                                gi.WriteDir (tr.plane.normal);
                                gi.WriteByte (self->s.skinnum);
                                gi.multicast (tr.endpos, MULTICAST_PVS);
                        }
                        break;
                }

                ignore = tr.ent;
                VectorCopy (tr.endpos, start);
        }

        VectorCopy (tr.endpos, self->s.old_origin);

        self->nextthink = level.time + FRAMETIME;
}

void target_laser_on (edict_t *self)
{
        if (!self->activator)
                self->activator = self;
        self->spawnflags |= 0x80000001;
        self->svflags &= ~SVF_NOCLIENT;
        target_laser_think (self);
}

void target_laser_off (edict_t *self)
{
        self->spawnflags &= ~1;
        self->svflags |= SVF_NOCLIENT;
        self->nextthink = 0;
}

void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
{
        self->activator = activator;
        if (self->spawnflags & 1)
                target_laser_off (self);
        else
                target_laser_on (self);
}

void target_laser_start (edict_t *self)
{
        edict_t *ent;

        self->movetype = MOVETYPE_NONE;
        self->solid = SOLID_NOT;
        self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
        self->s.modelindex = 1;                 // must be non-zero

        // set the beam diameter
        if (self->spawnflags & 64)
                self->s.frame = 16;
        else
                self->s.frame = 4;

        // set the color
        if (self->spawnflags & 2)
                self->s.skinnum = 0xf2f2f0f0;
        else if (self->spawnflags & 4)
                self->s.skinnum = 0xd0d1d2d3;
        else if (self->spawnflags & 8)
                self->s.skinnum = 0xf3f3f1f1;
        else if (self->spawnflags & 16)
                self->s.skinnum = 0xdcdddedf;
        else if (self->spawnflags & 32)
                self->s.skinnum = 0xe0e1e2e3;

        if (!self->enemy)
        {
                if (self->target)
                {
                        ent = G_Find (NULL, FOFS(targetname), self->target);
                        if (!ent)
                                gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
                        self->enemy = ent;
                }
                else
                {
                        G_SetMovedir (self->s.angles, self->movedir);
                }
        }
        self->use = target_laser_use;
        self->think = target_laser_think;

        if (!self->dmg)
                self->dmg = 1;

        VectorSet (self->mins, -8, -8, -8);
        VectorSet (self->maxs, 8, 8, 8);
        gi.linkentity (self);

        if (self->spawnflags & 1)
                target_laser_on (self);
        else
                target_laser_off (self);
}

void SP_target_laser (edict_t *self)
{
        // let everything else get spawned before we start firing
        self->think = target_laser_start;
        self->nextthink = level.time + 1;
}

//==========================================================

/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
speed           How many seconds the ramping will take
message         two letters; starting lightlevel and ending lightlevel
*/

void target_lightramp_think (edict_t *self)
{
        char    style[2];

        style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
        style[1] = 0;
        gi.configstring (CS_LIGHTS+self->enemy->style, style);

        if ((level.time - self->timestamp) < self->speed)
        {
                self->nextthink = level.time + FRAMETIME;
        }
        else if (self->spawnflags & 1)
        {
                char    temp;

                temp = self->movedir[0];
                self->movedir[0] = self->movedir[1];
                self->movedir[1] = temp;
                self->movedir[2] *= -1;
        }
}

void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
{
        if (!self->enemy)
        {
                edict_t         *e;

                // check all the targets
                e = NULL;
                while (1)
                {
                        e = G_Find (e, FOFS(targetname), self->target);
                        if (!e)
                                break;
                        if (strcmp(e->classname, "light") != 0)
                        {
                                gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
                                gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
                        }
                        else
                        {
                                self->enemy = e;
                        }
                }

                if (!self->enemy)
                {
                        gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
                        G_FreeEdict (self);
                        return;
                }
        }

        self->timestamp = level.time;
        target_lightramp_think (self);
}

void SP_target_lightramp (edict_t *self)
{
        if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
        {
                gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
                G_FreeEdict (self);
                return;
        }

        if (deathmatch->value)
        {
                G_FreeEdict (self);
                return;
        }

        if (!self->target)
        {
                gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
                G_FreeEdict (self);
                return;
        }

        self->svflags |= SVF_NOCLIENT;
        self->use = target_lightramp_use;
        self->think = target_lightramp_think;

        self->movedir[0] = self->message[0] - 'a';
        self->movedir[1] = self->message[1] - 'a';
        self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
}

//==========================================================

/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this initiates a level-wide earthquake.
All players and monsters are affected.
"speed"         severity of the quake (default:200)
"count"         duration of the quake (default:5)
*/

void target_earthquake_think (edict_t *self)
{
        int             i;
        edict_t *e;

        if (self->last_move_time < level.time)
        {
                gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
                self->last_move_time = level.time + 0.5;
        }

        for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
        {
                if (!e->inuse)
                        continue;
                if (!e->client)
                        continue;
                if (!e->groundentity)
                        continue;

                e->groundentity = NULL;
                e->velocity[0] += crandom()* 150;
                e->velocity[1] += crandom()* 150;
                e->velocity[2] = self->speed * (100.0 / e->mass);
        }

        if (level.time < self->timestamp)
                self->nextthink = level.time + FRAMETIME;
}

void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
{
        self->timestamp = level.time + self->count;
        self->nextthink = level.time + FRAMETIME;
        self->activator = activator;
        self->last_move_time = 0;
}

void SP_target_earthquake (edict_t *self)
{
        if (!self->targetname)
                gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));

        if (!self->count)
                self->count = 5;

        if (!self->speed)
                self->speed = 200;

        self->svflags |= SVF_NOCLIENT;
        self->think = target_earthquake_think;
        self->use = target_earthquake_use;

        self->noise_index = gi.soundindex ("world/quake.wav");
}
