File: game\g_target.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 #include "g_local.h"
   21 
   22 /*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
   23 Fire an origin based temp entity event to the clients.
   24 "style"         type byte
   25 */
   26 void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator)
   27 {
   28         gi.WriteByte (svc_temp_entity);
   29         gi.WriteByte (ent->style);
   30         gi.WritePosition (ent->s.origin);
   31         gi.multicast (ent->s.origin, MULTICAST_PVS);
   32 }
   33 
   34 void SP_target_temp_entity (edict_t *ent)
   35 {
   36         ent->use = Use_Target_Tent;
   37 }
   38 
   39 
   40 //==========================================================
   41 
   42 //==========================================================
   43 
   44 /*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
   45 "noise"         wav file to play
   46 "attenuation"
   47 -1 = none, send to whole level
   48 1 = normal fighting sounds
   49 2 = idle sound level
   50 3 = ambient sound level
   51 "volume"        0.0 to 1.0
   52 
   53 Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.
   54 
   55 Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
   56 Multiple identical looping sounds will just increase volume without any speed cost.
   57 */
   58 void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
   59 {
   60         int             chan;
   61 
   62         if (ent->spawnflags & 3)
   63         {       // looping sound toggles
   64                 if (ent->s.sound)
   65                         ent->s.sound = 0;       // turn it off
   66                 else
   67                         ent->s.sound = ent->noise_index;        // start it
   68         }
   69         else
   70         {       // normal sound
   71                 if (ent->spawnflags & 4)
   72                         chan = CHAN_VOICE|CHAN_RELIABLE;
   73                 else
   74                         chan = CHAN_VOICE;
   75                 // use a positioned_sound, because this entity won't normally be
   76                 // sent to any clients because it is invisible
   77                 gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
   78         }
   79 }
   80 
   81 void SP_target_speaker (edict_t *ent)
   82 {
   83         char    buffer[MAX_QPATH];
   84 
   85         if(!st.noise)
   86         {
   87                 gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
   88                 return;
   89         }
   90         if (!strstr (st.noise, ".wav"))
   91                 Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
   92         else
   93                 strncpy (buffer, st.noise, sizeof(buffer));
   94         ent->noise_index = gi.soundindex (buffer);
   95 
   96         if (!ent->volume)
   97                 ent->volume = 1.0;
   98 
   99         if (!ent->attenuation)
  100                 ent->attenuation = 1.0;
  101         else if (ent->attenuation == -1)        // use -1 so 0 defaults to 1
  102                 ent->attenuation = 0;
  103 
  104         // check for prestarted looping sound
  105         if (ent->spawnflags & 1)
  106                 ent->s.sound = ent->noise_index;
  107 
  108         ent->use = Use_Target_Speaker;
  109 
  110         // must link the entity so we get areas and clusters so
  111         // the server can determine who to send updates to
  112         gi.linkentity (ent);
  113 }
  114 
  115 
  116 //==========================================================
  117 
  118 void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator)
  119 {
  120         if (ent->spawnflags & 1)
  121                 strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
  122         else
  123                 strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
  124 
  125         game.helpchanged++;
  126 }
  127 
  128 /*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
  129 When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
  130 */
  131 void SP_target_help(edict_t *ent)
  132 {
  133         if (deathmatch->value)
  134         {       // auto-remove for deathmatch
  135                 G_FreeEdict (ent);
  136                 return;
  137         }
  138 
  139         if (!ent->message)
  140         {
  141                 gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
  142                 G_FreeEdict (ent);
  143                 return;
  144         }
  145         ent->use = Use_Target_Help;
  146 }
  147 
  148 //==========================================================
  149 
  150 /*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
  151 Counts a secret found.
  152 These are single use targets.
  153 */
  154 void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
  155 {
  156         gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
  157 
  158         level.found_secrets++;
  159 
  160         G_UseTargets (ent, activator);
  161         G_FreeEdict (ent);
  162 }
  163 
  164 void SP_target_secret (edict_t *ent)
  165 {
  166         if (deathmatch->value)
  167         {       // auto-remove for deathmatch
  168                 G_FreeEdict (ent);
  169                 return;
  170         }
  171 
  172         ent->use = use_target_secret;
  173         if (!st.noise)
  174                 st.noise = "misc/secret.wav";
  175         ent->noise_index = gi.soundindex (st.noise);
  176         ent->svflags = SVF_NOCLIENT;
  177         level.total_secrets++;
  178         // map bug hack
  179         if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
  180                 ent->message = "You have found a secret area.";
  181 }
  182 
  183 //==========================================================
  184 
  185 /*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
  186 Counts a goal completed.
  187 These are single use targets.
  188 */
  189 void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
  190 {
  191         gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
  192 
  193         level.found_goals++;
  194 
  195         if (level.found_goals == level.total_goals)
  196                 gi.configstring (CS_CDTRACK, "0");
  197 
  198         G_UseTargets (ent, activator);
  199         G_FreeEdict (ent);
  200 }
  201 
  202 void SP_target_goal (edict_t *ent)
  203 {
  204         if (deathmatch->value)
  205         {       // auto-remove for deathmatch
  206                 G_FreeEdict (ent);
  207                 return;
  208         }
  209 
  210         ent->use = use_target_goal;
  211         if (!st.noise)
  212                 st.noise = "misc/secret.wav";
  213         ent->noise_index = gi.soundindex (st.noise);
  214         ent->svflags = SVF_NOCLIENT;
  215         level.total_goals++;
  216 }
  217 
  218 //==========================================================
  219 
  220 
  221 /*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
  222 Spawns an explosion temporary entity when used.
  223 
  224 "delay"         wait this long before going off
  225 "dmg"           how much radius damage should be done, defaults to 0
  226 */
  227 void target_explosion_explode (edict_t *self)
  228 {
  229         float           save;
  230 
  231         gi.WriteByte (svc_temp_entity);
  232         gi.WriteByte (TE_EXPLOSION1);
  233         gi.WritePosition (self->s.origin);
  234         gi.multicast (self->s.origin, MULTICAST_PHS);
  235 
  236         T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
  237 
  238         save = self->delay;
  239         self->delay = 0;
  240         G_UseTargets (self, self->activator);
  241         self->delay = save;
  242 }
  243 
  244 void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
  245 {
  246         self->activator = activator;
  247 
  248         if (!self->delay)
  249         {
  250                 target_explosion_explode (self);
  251                 return;
  252         }
  253 
  254         self->think = target_explosion_explode;
  255         self->nextthink = level.time + self->delay;
  256 }
  257 
  258 void SP_target_explosion (edict_t *ent)
  259 {
  260         ent->use = use_target_explosion;
  261         ent->svflags = SVF_NOCLIENT;
  262 }
  263 
  264 
  265 //==========================================================
  266 
  267 /*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
  268 Changes level to "map" when fired
  269 */
  270 void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
  271 {
  272         if (level.intermissiontime)
  273                 return;         // already activated
  274 
  275         if (!deathmatch->value && !coop->value)
  276         {
  277                 if (g_edicts[1].health <= 0)
  278                         return;
  279         }
  280 
  281         // if noexit, do a ton of damage to other
  282         if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
  283         {
  284                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
  285                 return;
  286         }
  287 
  288         // if multiplayer, let everyone know who hit the exit
  289         if (deathmatch->value)
  290         {
  291                 if (activator && activator->client)
  292                         gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
  293         }
  294 
  295         // if going to a new unit, clear cross triggers
  296         if (strstr(self->map, "*"))     
  297                 game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
  298 
  299         BeginIntermission (self);
  300 }
  301 
  302 void SP_target_changelevel (edict_t *ent)
  303 {
  304         if (!ent->map)
  305         {
  306                 gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
  307                 G_FreeEdict (ent);
  308                 return;
  309         }
  310 
  311         // ugly hack because *SOMEBODY* screwed up their map
  312    if((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
  313            ent->map = "fact3$secret1";
  314 
  315         ent->use = use_target_changelevel;
  316         ent->svflags = SVF_NOCLIENT;
  317 }
  318 
  319 
  320 //==========================================================
  321 
  322 /*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
  323 Creates a particle splash effect when used.
  324 
  325 Set "sounds" to one of the following:
  326   1) sparks
  327   2) blue water
  328   3) brown water
  329   4) slime
  330   5) lava
  331   6) blood
  332 
  333 "count" how many pixels in the splash
  334 "dmg"   if set, does a radius damage at this location when it splashes
  335                 useful for lava/sparks
  336 */
  337 
  338 void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
  339 {
  340         gi.WriteByte (svc_temp_entity);
  341         gi.WriteByte (TE_SPLASH);
  342         gi.WriteByte (self->count);
  343         gi.WritePosition (self->s.origin);
  344         gi.WriteDir (self->movedir);
  345         gi.WriteByte (self->sounds);
  346         gi.multicast (self->s.origin, MULTICAST_PVS);
  347 
  348         if (self->dmg)
  349                 T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
  350 }
  351 
  352 void SP_target_splash (edict_t *self)
  353 {
  354         self->use = use_target_splash;
  355         G_SetMovedir (self->s.angles, self->movedir);
  356 
  357         if (!self->count)
  358                 self->count = 32;
  359 
  360         self->svflags = SVF_NOCLIENT;
  361 }
  362 
  363 
  364 //==========================================================
  365 
  366 /*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
  367 Set target to the type of entity you want spawned.
  368 Useful for spawning monsters and gibs in the factory levels.
  369 
  370 For monsters:
  371         Set direction to the facing you want it to have.
  372 
  373 For gibs:
  374         Set direction if you want it moving and
  375         speed how fast it should be moving otherwise it
  376         will just be dropped
  377 */
  378 void ED_CallSpawn (edict_t *ent);
  379 
  380 void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
  381 {
  382         edict_t *ent;
  383 
  384         ent = G_Spawn();
  385         ent->classname = self->target;
  386         VectorCopy (self->s.origin, ent->s.origin);
  387         VectorCopy (self->s.angles, ent->s.angles);
  388         ED_CallSpawn (ent);
  389         gi.unlinkentity (ent);
  390         KillBox (ent);
  391         gi.linkentity (ent);
  392         if (self->speed)
  393                 VectorCopy (self->movedir, ent->velocity);
  394 }
  395 
  396 void SP_target_spawner (edict_t *self)
  397 {
  398         self->use = use_target_spawner;
  399         self->svflags = SVF_NOCLIENT;
  400         if (self->speed)
  401         {
  402                 G_SetMovedir (self->s.angles, self->movedir);
  403                 VectorScale (self->movedir, self->speed, self->movedir);
  404         }
  405 }
  406 
  407 //==========================================================
  408 
  409 /*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
  410 Fires a blaster bolt in the set direction when triggered.
  411 
  412 dmg             default is 15
  413 speed   default is 1000
  414 */
  415 
  416 void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
  417 {
  418         int effect;
  419 
  420         if (self->spawnflags & 2)
  421                 effect = 0;
  422         else if (self->spawnflags & 1)
  423                 effect = EF_HYPERBLASTER;
  424         else
  425                 effect = EF_BLASTER;
  426 
  427         fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
  428         gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
  429 }
  430 
  431 void SP_target_blaster (edict_t *self)
  432 {
  433         self->use = use_target_blaster;
  434         G_SetMovedir (self->s.angles, self->movedir);
  435         self->noise_index = gi.soundindex ("weapons/laser2.wav");
  436 
  437         if (!self->dmg)
  438                 self->dmg = 15;
  439         if (!self->speed)
  440                 self->speed = 1000;
  441 
  442         self->svflags = SVF_NOCLIENT;
  443 }
  444 
  445 
  446 //==========================================================
  447 
  448 /*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
  449 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.
  450 */
  451 void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator)
  452 {
  453         game.serverflags |= self->spawnflags;
  454         G_FreeEdict (self);
  455 }
  456 
  457 void SP_target_crosslevel_trigger (edict_t *self)
  458 {
  459         self->svflags = SVF_NOCLIENT;
  460         self->use = trigger_crosslevel_trigger_use;
  461 }
  462 
  463 /*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
  464 Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
  465 killtarget also work.
  466 
  467 "delay"         delay before using targets if the trigger has been activated (default 1)
  468 */
  469 void target_crosslevel_target_think (edict_t *self)
  470 {
  471         if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
  472         {
  473                 G_UseTargets (self, self);
  474                 G_FreeEdict (self);
  475         }
  476 }
  477 
  478 void SP_target_crosslevel_target (edict_t *self)
  479 {
  480         if (! self->delay)
  481                 self->delay = 1;
  482         self->svflags = SVF_NOCLIENT;
  483 
  484         self->think = target_crosslevel_target_think;
  485         self->nextthink = level.time + self->delay;
  486 }
  487 
  488 //==========================================================
  489 
  490 /*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
  491 When triggered, fires a laser.  You can either set a target
  492 or a direction.
  493 */
  494 
  495 void target_laser_think (edict_t *self)
  496 {
  497         edict_t *ignore;
  498         vec3_t  start;
  499         vec3_t  end;
  500         trace_t tr;
  501         vec3_t  point;
  502         vec3_t  last_movedir;
  503         int             count;
  504 
  505         if (self->spawnflags & 0x80000000)
  506                 count = 8;
  507         else
  508                 count = 4;
  509 
  510         if (self->enemy)
  511         {
  512                 VectorCopy (self->movedir, last_movedir);
  513                 VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
  514                 VectorSubtract (point, self->s.origin, self->movedir);
  515                 VectorNormalize (self->movedir);
  516                 if (!VectorCompare(self->movedir, last_movedir))
  517                         self->spawnflags |= 0x80000000;
  518         }
  519 
  520         ignore = self;
  521         VectorCopy (self->s.origin, start);
  522         VectorMA (start, 2048, self->movedir, end);
  523         while(1)
  524         {
  525                 tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
  526 
  527                 if (!tr.ent)
  528                         break;
  529 
  530                 // hurt it if we can
  531                 if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
  532                         T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
  533 
  534                 // if we hit something that's not a monster or player or is immune to lasers, we're done
  535                 if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
  536                 {
  537                         if (self->spawnflags & 0x80000000)
  538                         {
  539                                 self->spawnflags &= ~0x80000000;
  540                                 gi.WriteByte (svc_temp_entity);
  541                                 gi.WriteByte (TE_LASER_SPARKS);
  542                                 gi.WriteByte (count);
  543                                 gi.WritePosition (tr.endpos);
  544                                 gi.WriteDir (tr.plane.normal);
  545                                 gi.WriteByte (self->s.skinnum);
  546                                 gi.multicast (tr.endpos, MULTICAST_PVS);
  547                         }
  548                         break;
  549                 }
  550 
  551                 ignore = tr.ent;
  552                 VectorCopy (tr.endpos, start);
  553         }
  554 
  555         VectorCopy (tr.endpos, self->s.old_origin);
  556 
  557         self->nextthink = level.time + FRAMETIME;
  558 }
  559 
  560 void target_laser_on (edict_t *self)
  561 {
  562         if (!self->activator)
  563                 self->activator = self;
  564         self->spawnflags |= 0x80000001;
  565         self->svflags &= ~SVF_NOCLIENT;
  566         target_laser_think (self);
  567 }
  568 
  569 void target_laser_off (edict_t *self)
  570 {
  571         self->spawnflags &= ~1;
  572         self->svflags |= SVF_NOCLIENT;
  573         self->nextthink = 0;
  574 }
  575 
  576 void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
  577 {
  578         self->activator = activator;
  579         if (self->spawnflags & 1)
  580                 target_laser_off (self);
  581         else
  582                 target_laser_on (self);
  583 }
  584 
  585 void target_laser_start (edict_t *self)
  586 {
  587         edict_t *ent;
  588 
  589         self->movetype = MOVETYPE_NONE;
  590         self->solid = SOLID_NOT;
  591         self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
  592         self->s.modelindex = 1;                 // must be non-zero
  593 
  594         // set the beam diameter
  595         if (self->spawnflags & 64)
  596                 self->s.frame = 16;
  597         else
  598                 self->s.frame = 4;
  599 
  600         // set the color
  601         if (self->spawnflags & 2)
  602                 self->s.skinnum = 0xf2f2f0f0;
  603         else if (self->spawnflags & 4)
  604                 self->s.skinnum = 0xd0d1d2d3;
  605         else if (self->spawnflags & 8)
  606                 self->s.skinnum = 0xf3f3f1f1;
  607         else if (self->spawnflags & 16)
  608                 self->s.skinnum = 0xdcdddedf;
  609         else if (self->spawnflags & 32)
  610                 self->s.skinnum = 0xe0e1e2e3;
  611 
  612         if (!self->enemy)
  613         {
  614                 if (self->target)
  615                 {
  616                         ent = G_Find (NULL, FOFS(targetname), self->target);
  617                         if (!ent)
  618                                 gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
  619                         self->enemy = ent;
  620                 }
  621                 else
  622                 {
  623                         G_SetMovedir (self->s.angles, self->movedir);
  624                 }
  625         }
  626         self->use = target_laser_use;
  627         self->think = target_laser_think;
  628 
  629         if (!self->dmg)
  630                 self->dmg = 1;
  631 
  632         VectorSet (self->mins, -8, -8, -8);
  633         VectorSet (self->maxs, 8, 8, 8);
  634         gi.linkentity (self);
  635 
  636         if (self->spawnflags & 1)
  637                 target_laser_on (self);
  638         else
  639                 target_laser_off (self);
  640 }
  641 
  642 void SP_target_laser (edict_t *self)
  643 {
  644         // let everything else get spawned before we start firing
  645         self->think = target_laser_start;
  646         self->nextthink = level.time + 1;
  647 }
  648 
  649 //==========================================================
  650 
  651 /*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
  652 speed           How many seconds the ramping will take
  653 message         two letters; starting lightlevel and ending lightlevel
  654 */
  655 
  656 void target_lightramp_think (edict_t *self)
  657 {
  658         char    style[2];
  659 
  660         style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
  661         style[1] = 0;
  662         gi.configstring (CS_LIGHTS+self->enemy->style, style);
  663 
  664         if ((level.time - self->timestamp) < self->speed)
  665         {
  666                 self->nextthink = level.time + FRAMETIME;
  667         }
  668         else if (self->spawnflags & 1)
  669         {
  670                 char    temp;
  671 
  672                 temp = self->movedir[0];
  673                 self->movedir[0] = self->movedir[1];
  674                 self->movedir[1] = temp;
  675                 self->movedir[2] *= -1;
  676         }
  677 }
  678 
  679 void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
  680 {
  681         if (!self->enemy)
  682         {
  683                 edict_t         *e;
  684 
  685                 // check all the targets
  686                 e = NULL;
  687                 while (1)
  688                 {
  689                         e = G_Find (e, FOFS(targetname), self->target);
  690                         if (!e)
  691                                 break;
  692                         if (strcmp(e->classname, "light") != 0)
  693                         {
  694                                 gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
  695                                 gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
  696                         }
  697                         else
  698                         {
  699                                 self->enemy = e;
  700                         }
  701                 }
  702 
  703                 if (!self->enemy)
  704                 {
  705                         gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
  706                         G_FreeEdict (self);
  707                         return;
  708                 }
  709         }
  710 
  711         self->timestamp = level.time;
  712         target_lightramp_think (self);
  713 }
  714 
  715 void SP_target_lightramp (edict_t *self)
  716 {
  717         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])
  718         {
  719                 gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
  720                 G_FreeEdict (self);
  721                 return;
  722         }
  723 
  724         if (deathmatch->value)
  725         {
  726                 G_FreeEdict (self);
  727                 return;
  728         }
  729 
  730         if (!self->target)
  731         {
  732                 gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
  733                 G_FreeEdict (self);
  734                 return;
  735         }
  736 
  737         self->svflags |= SVF_NOCLIENT;
  738         self->use = target_lightramp_use;
  739         self->think = target_lightramp_think;
  740 
  741         self->movedir[0] = self->message[0] - 'a';
  742         self->movedir[1] = self->message[1] - 'a';
  743         self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
  744 }
  745 
  746 //==========================================================
  747 
  748 /*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
  749 When triggered, this initiates a level-wide earthquake.
  750 All players and monsters are affected.
  751 "speed"         severity of the quake (default:200)
  752 "count"         duration of the quake (default:5)
  753 */
  754 
  755 void target_earthquake_think (edict_t *self)
  756 {
  757         int             i;
  758         edict_t *e;
  759 
  760         if (self->last_move_time < level.time)
  761         {
  762                 gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
  763                 self->last_move_time = level.time + 0.5;
  764         }
  765 
  766         for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
  767         {
  768                 if (!e->inuse)
  769                         continue;
  770                 if (!e->client)
  771                         continue;
  772                 if (!e->groundentity)
  773                         continue;
  774 
  775                 e->groundentity = NULL;
  776                 e->velocity[0] += crandom()* 150;
  777                 e->velocity[1] += crandom()* 150;
  778                 e->velocity[2] = self->speed * (100.0 / e->mass);
  779         }
  780 
  781         if (level.time < self->timestamp)
  782                 self->nextthink = level.time + FRAMETIME;
  783 }
  784 
  785 void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
  786 {
  787         self->timestamp = level.time + self->count;
  788         self->nextthink = level.time + FRAMETIME;
  789         self->activator = activator;
  790         self->last_move_time = 0;
  791 }
  792 
  793 void SP_target_earthquake (edict_t *self)
  794 {
  795         if (!self->targetname)
  796                 gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
  797 
  798         if (!self->count)
  799                 self->count = 5;
  800 
  801         if (!self->speed)
  802                 self->speed = 200;
  803 
  804         self->svflags |= SVF_NOCLIENT;
  805         self->think = target_earthquake_think;
  806         self->use = target_earthquake_use;
  807 
  808         self->noise_index = gi.soundindex ("world/quake.wav");
  809 }
  810