]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/g_triggers.qc
Don't consider grabbing a cheat if sandbox mode is enabled
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_triggers.qc
index c13fb91186aec2c7bc944716da70b35b0e0e2eed..0ab2b423354013da80c2e56215aecd053eebfcca 100644 (file)
@@ -10,7 +10,7 @@ void DelayThink()
        activator = self.enemy;
        SUB_UseTargets ();
        remove(self);
-};
+}
 
 /*
 ==============================
@@ -33,7 +33,7 @@ match (string)self.target and call their .use function
 */
 void SUB_UseTargets()
 {
-       local entity t, stemp, otemp, act;
+       entity t, stemp, otemp, act;
        string s;
        float i;
 
@@ -51,6 +51,9 @@ void SUB_UseTargets()
                t.message = self.message;
                t.killtarget = self.killtarget;
                t.target = self.target;
+               t.target2 = self.target2;
+               t.target3 = self.target3;
+               t.target4 = self.target4;
                return;
        }
 
@@ -85,6 +88,9 @@ void SUB_UseTargets()
        stemp = self;
        otemp = other;
 
+       if(stemp.target_random)
+               RandomSelection_Init();
+
        for(i = 0; i < 4; ++i)
        {
                switch(i)
@@ -100,19 +106,33 @@ void SUB_UseTargets()
                        for(t = world; (t = find(t, targetname, s)); )
                        if(t.use)
                        {
-                               //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
-                               self = t;
-                               other = stemp;
-                               activator = act;
-                               self.use();
+                               if(stemp.target_random)
+                               {
+                                       RandomSelection_Add(t, 0, string_null, 1, 0);
+                               }
+                               else
+                               {
+                                       self = t;
+                                       other = stemp;
+                                       activator = act;
+                                       self.use();
+                               }
                        }
                }
        }
 
+       if(stemp.target_random && RandomSelection_chosen_ent)
+       {
+               self = RandomSelection_chosen_ent;
+               other = stemp;
+               activator = act;
+               self.use();
+       }
+
        activator = act;
        self = stemp;
        other = otemp;
-};
+}
 
 
 //=============================================================================
@@ -129,7 +149,7 @@ void multi_wait()
                self.takedamage = DAMAGE_YES;
                self.solid = SOLID_BBOX;
        }
-};
+}
 
 
 // the trigger was just touched/killed/used
@@ -151,7 +171,7 @@ void multi_trigger()
        }
 
        if (self.noise)
-               sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
+               sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
 
 // don't trigger again until reset
        self.takedamage = DAMAGE_NO;
@@ -174,26 +194,24 @@ void multi_trigger()
                // called wheil C code is looping through area links...
                self.touch = SUB_Null;
        }
-};
+}
 
 void multi_use()
 {
        self.goalentity = other;
        self.enemy = activator;
        multi_trigger();
-};
+}
 
 void multi_touch()
 {
        if not(self.spawnflags & 2)
-       {
                if not(other.iscreature)
                        return;
 
-               if(self.team)
-               if(self.team == other.team)
+       if(self.team)
+               if((self.spawnflags & 4 == 0) == (self.team != other.team))
                        return;
-       }
 
 // if the trigger has an angles field, check player's facing direction
        if (self.movedir != '0 0 0')
@@ -208,7 +226,7 @@ void multi_touch()
        self.enemy = other;
        self.goalentity = other;
        multi_trigger ();
-};
+}
 
 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
@@ -300,7 +318,7 @@ void spawnfunc_trigger_multiple()
                        setorigin (self, self.origin);  // make sure it links into the world
                }
        }
-};
+}
 
 
 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
@@ -320,7 +338,7 @@ void spawnfunc_trigger_once()
 {
        self.wait = -1;
        spawnfunc_trigger_multiple();
-};
+}
 
 //=============================================================================
 
@@ -331,7 +349,7 @@ void spawnfunc_trigger_relay()
 {
        self.use = SUB_UseTargets;
        self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
-};
+}
 
 void delay_use()
 {
@@ -384,7 +402,7 @@ void counter_use()
                centerprint(activator, "Sequence completed!");
        self.enemy = activator;
        multi_trigger ();
-};
+}
 
 void counter_reset()
 {
@@ -408,11 +426,26 @@ void spawnfunc_trigger_counter()
 
        self.use = counter_use;
        self.reset = counter_reset;
-};
+}
+
+void trigger_hurt_use()
+{
+       if(activator.classname == "player")
+               self.enemy = activator;
+       else
+               self.enemy = world; // let's just destroy it, if taking over is too much work
+}
 
 .float triggerhurttime;
 void trigger_hurt_touch()
 {
+       if (self.active != ACTIVE_ACTIVE) 
+               return;
+
+       if(self.team)
+               if((self.spawnflags & 4 == 0) == (self.team != other.team))
+                       return;
+
        // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
        if (other.iscreature)
        {
@@ -421,7 +454,16 @@ void trigger_hurt_touch()
                {
                        EXACTTRIGGER_TOUCH;
                        other.triggerhurttime = time + 1;
-                       Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+
+                       entity own;
+                       own = self.enemy;
+                       if(own.classname != "player")
+                       {
+                               own = self;
+                               self.enemy = world; // I still hate you all
+                       }
+
+                       Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
                }
        }
        else
@@ -442,7 +484,7 @@ void trigger_hurt_touch()
        }
 
        return;
-};
+}
 
 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
 Any object touching this will be hurt
@@ -455,20 +497,24 @@ entity trigger_hurt_first;
 void spawnfunc_trigger_hurt()
 {
        EXACTTRIGGER_INIT;
+       self.active = ACTIVE_ACTIVE;
        self.touch = trigger_hurt_touch;
+       self.use = trigger_hurt_use;
+       self.enemy = world; // I hate you all
        if (!self.dmg)
                self.dmg = 1000;
        if (!self.message)
                self.message = "was in the wrong place";
        if (!self.message2)
                self.message2 = "was thrown into a world of hurt by";
+       // self.message = "someone like %s always gets wrongplaced";
 
        if(!trigger_hurt_first)
                trigger_hurt_first = self;
        if(trigger_hurt_last)
                trigger_hurt_last.trigger_hurt_next = self;
        trigger_hurt_last = self;
-};
+}
 
 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
 {
@@ -492,6 +538,9 @@ float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
 .float triggerhealtime;
 void trigger_heal_touch()
 {
+       if (self.active != ACTIVE_ACTIVE) 
+               return;
+       
        // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
        if (other.iscreature)
        {
@@ -504,15 +553,17 @@ void trigger_heal_touch()
                        if (other.health < self.max_health)
                        {
                                other.health = min(other.health + self.health, self.max_health);
-                               other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
-                               sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
+                               other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
+                               sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
                        }
                }
        }
-};
+}
 
 void spawnfunc_trigger_heal()
 {
+       self.active = ACTIVE_ACTIVE;
+       
        EXACTTRIGGER_INIT;
        self.touch = trigger_heal_touch;
        if (!self.health)
@@ -522,7 +573,7 @@ void spawnfunc_trigger_heal()
        if(self.noise == "")
                self.noise = "misc/mediumhealth.wav";
        precache_sound(self.noise);
-};
+}
 
 
 //////////////////////////////////////////////////////////////
@@ -564,12 +615,12 @@ void trigger_gravity_check_think()
                self.count -= 1;
                self.nextthink = time;
        }
-};
+}
 
 void trigger_gravity_use()
 {
        self.state = !self.state;
-};
+}
 
 void trigger_gravity_touch()
 {
@@ -614,10 +665,10 @@ void trigger_gravity_touch()
        {
                other.gravity = g;
                if(self.noise != "")
-                       sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
+                       sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
                UpdateCSQCProjectile(self.owner);
        }
-};
+}
 
 void spawnfunc_trigger_gravity()
 {
@@ -636,39 +687,142 @@ void spawnfunc_trigger_gravity()
                if(self.spawnflags & 2)
                        self.state = FALSE;
        }
-};
+}
 
 //=============================================================================
 
 // TODO add a way to do looped sounds with sound(); then complete this entity
 .float volume, atten;
-void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
+void target_speaker_use_off();
+void target_speaker_use_activator()
+{
+       if(clienttype(activator) != CLIENTTYPE_REAL)
+               return;
+       string snd;
+       if(substring(self.noise, 0, 1) == "*")
+       {
+               var .string sample;
+               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+               if(GetPlayerSoundSampleField_notFound)
+                       snd = "misc/null.wav";
+               else if(activator.sample == "")
+                       snd = "misc/null.wav";
+               else
+               {
+                       tokenize_console(activator.sample);
+                       float n;
+                       n = stof(argv(1));
+                       if(n > 0)
+                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+                       else
+                               snd = strcat(argv(0), ".wav"); // randomization
+               }
+       }
+       else
+               snd = self.noise;
+       msg_entity = activator;
+       soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
+}
+void target_speaker_use_on()
+{
+       string snd;
+       if(substring(self.noise, 0, 1) == "*")
+       {
+               var .string sample;
+               sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+               if(GetPlayerSoundSampleField_notFound)
+                       snd = "misc/null.wav";
+               else if(activator.sample == "")
+                       snd = "misc/null.wav";
+               else
+               {
+                       tokenize_console(activator.sample);
+                       float n;
+                       n = stof(argv(1));
+                       if(n > 0)
+                               snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+                       else
+                               snd = strcat(argv(0), ".wav"); // randomization
+               }
+       }
+       else
+               snd = self.noise;
+       sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
+       if(self.spawnflags & 3)
+               self.use = target_speaker_use_off;
+}
+void target_speaker_use_off()
+{
+       sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
+       self.use = target_speaker_use_on;
+}
+void target_speaker_reset()
+{
+       if(self.spawnflags & 1) // LOOPED_ON
+       {
+               if(self.use == target_speaker_use_on)
+                       target_speaker_use_on();
+       }
+       else if(self.spawnflags & 2)
+       {
+               if(self.use == target_speaker_use_off)
+                       target_speaker_use_off();
+       }
+}
 
 void spawnfunc_target_speaker()
 {
+       // TODO: "*" prefix to sound file name
+       // TODO: wait and random (just, HOW? random is not a field)
        if(self.noise)
                precache_sound (self.noise);
-       IFTARGETED
+
+       if(!self.atten && !(self.spawnflags & 4))
        {
-               if(!self.atten)
+               IFTARGETED
                        self.atten = ATTN_NORM;
-               else if(self.atten < 0)
-                       self.atten = 0;
-               if(!self.volume)
-                       self.volume = 1;
-               self.use = target_speaker_use;
+               else
+                       self.atten = ATTN_STATIC;
+       }
+       else if(self.atten < 0)
+               self.atten = 0;
+
+       if(!self.volume)
+               self.volume = 1;
+
+       IFTARGETED
+       {
+               if(self.spawnflags & 8) // ACTIVATOR
+                       self.use = target_speaker_use_activator;
+               else if(self.spawnflags & 1) // LOOPED_ON
+               {
+                       target_speaker_use_on();
+                       self.reset = target_speaker_reset;
+               }
+               else if(self.spawnflags & 2) // LOOPED_OFF
+               {
+                       self.use = target_speaker_use_on;
+                       self.reset = target_speaker_reset;
+               }
+               else
+                       self.use = target_speaker_use_on;
+       }
+       else if(self.spawnflags & 1) // LOOPED_ON
+       {
+               ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+               remove(self);
+       }
+       else if(self.spawnflags & 2) // LOOPED_OFF
+       {
+               objerror("This sound entity can never be activated");
        }
        else
        {
-               if(!self.atten)
-                       self.atten = ATTN_STATIC;
-               else if(self.atten < 0)
-                       self.atten = 0;
-               if(!self.volume)
-                       self.volume = 1;
+               // Quake/Nexuiz fallback
                ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+               remove(self);
        }
-};
+}
 
 
 void spawnfunc_func_stardust() {
@@ -871,7 +1025,7 @@ float rainsnow_SendEntity(entity to, float sf)
        WriteShort(MSG_ENTITY, self.count);
        WriteByte(MSG_ENTITY, self.cnt);
        return 1;
-};
+}
 
 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
 This is an invisible area like a trigger, which rain falls inside of.
@@ -908,7 +1062,7 @@ void spawnfunc_func_rain()
        self.Version = 1;
 
        Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
-};
+}
 
 
 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
@@ -946,7 +1100,7 @@ void spawnfunc_func_snow()
        self.Version = 1;
 
        Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
-};
+}
 
 
 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
@@ -1002,6 +1156,8 @@ void misc_laser_think()
 {
        vector o;
        entity oldself;
+       entity hitent;
+       vector hitloc;
 
        self.nextthink = time;
 
@@ -1022,20 +1178,18 @@ void misc_laser_think()
                o = self.origin + v_forward * 32768;
        }
 
-       if(self.dmg)
+       if(self.dmg || self.enemy.target != "")
        {
-               if(self.dmg < 0)
-                       FireRailgunBullet(self.origin, o, 100000, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
-               else
-                       FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, 0, 0, 0, 0, DEATH_HURTTRIGGER);
+               traceline(self.origin, o, MOVE_NORMAL, self);
        }
+       hitent = trace_ent;
+       hitloc = trace_endpos;
 
        if(self.enemy.target != "") // DETECTOR laser
        {
-               traceline(self.origin, o, MOVE_NORMAL, self);
                if(trace_ent.iscreature)
                {
-                       self.pusher = trace_ent;
+                       self.pusher = hitent;
                        if(!self.count)
                        {
                                self.count = 1;
@@ -1061,18 +1215,29 @@ void misc_laser_think()
                        }
                }
        }
+
+       if(self.dmg)
+       {
+               if(self.team)
+                       if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
+                               return;
+               if(hitent.takedamage)
+                       Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
+       }
 }
 
 float laser_SendEntity(entity to, float fl)
 {
        WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
-       fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
+       fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
        if(self.spawnflags & 2)
                fl |= 0x80;
        if(self.alpha)
                fl |= 0x40;
        if(self.scale != 1 || self.modelscale != 1)
                fl |= 0x20;
+       if(self.spawnflags & 4)
+               fl |= 0x10;
        WriteByte(MSG_ENTITY, fl);
        if(fl & 1)
        {
@@ -1092,7 +1257,8 @@ float laser_SendEntity(entity to, float fl)
                        WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
                        WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
                }
-               WriteShort(MSG_ENTITY, self.cnt + 1);
+               if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
+                       WriteShort(MSG_ENTITY, self.cnt + 1);
        }
        if(fl & 2)
        {
@@ -1175,6 +1341,8 @@ void spawnfunc_misc_laser()
                self.scale = 1;
        if(!self.modelscale)
                self.modelscale = 1;
+       else if(self.modelscale < 0)
+               self.modelscale = 0;
        self.think = misc_laser_think;
        self.nextthink = time;
        InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
@@ -1206,24 +1374,10 @@ void trigger_impulse_touch1()
     float pushdeltatime;
     float str;
 
-       // FIXME: Better checking for what to push and not.
-       if not(other.iscreature)
-       if (other.classname != "corpse")
-       if (other.classname != "body")
-       if (other.classname != "gib")
-       if (other.classname != "missile")
-       if (other.classname != "rocket")
-       if (other.classname != "casing")
-       if (other.classname != "grenade")
-       if (other.classname != "plasma")
-       if (other.classname != "plasma_prim")
-       if (other.classname != "plasma_chain")
-       if (other.classname != "droppedweapon")
-       if (other.classname != "nexball_basketball")
-       if (other.classname != "nexball_football")
+       if (self.active != ACTIVE_ACTIVE) 
                return;
 
-       if (other.deadflag && other.iscreature)
+       if (!isPushable(other))
                return;
 
        EXACTTRIGGER_TOUCH;
@@ -1249,7 +1403,8 @@ void trigger_impulse_touch1()
     if(!pushdeltatime) return;
 
     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
-       other.flags &~= FL_ONGROUND;
+    other.flags &~= FL_ONGROUND;
+    UpdateCSQCProjectile(other);
 }
 
 // Directionless (accelerator/decelerator) mode
@@ -1257,24 +1412,10 @@ void trigger_impulse_touch2()
 {
     float pushdeltatime;
 
-       // FIXME: Better checking for what to push and not.
-       if not(other.iscreature)
-       if (other.classname != "corpse")
-       if (other.classname != "body")
-       if (other.classname != "gib")
-       if (other.classname != "missile")
-       if (other.classname != "rocket")
-       if (other.classname != "casing")
-       if (other.classname != "grenade")
-       if (other.classname != "plasma")
-       if (other.classname != "plasma_prim")
-       if (other.classname != "plasma_chain")
-       if (other.classname != "droppedweapon")
-       if (other.classname != "nexball_basketball")
-       if (other.classname != "nexball_football")
+       if (self.active != ACTIVE_ACTIVE) 
                return;
 
-       if (other.deadflag && other.iscreature)
+       if (!isPushable(other))
                return;
 
        EXACTTRIGGER_TOUCH;
@@ -1286,6 +1427,7 @@ void trigger_impulse_touch2()
 
     // div0: ticrate independent, 1 = identity (not 20)
     other.velocity = other.velocity * pow(self.strength, pushdeltatime);
+    UpdateCSQCProjectile(other);
 }
 
 // Spherical (gravity/repulsor) mode
@@ -1294,24 +1436,10 @@ void trigger_impulse_touch3()
     float pushdeltatime;
     float str;
 
-       // FIXME: Better checking for what to push and not.
-       if not(other.iscreature)
-       if (other.classname != "corpse")
-       if (other.classname != "body")
-       if (other.classname != "gib")
-       if (other.classname != "missile")
-       if (other.classname != "rocket")
-       if (other.classname != "casing")
-       if (other.classname != "grenade")
-       if (other.classname != "plasma")
-       if (other.classname != "plasma_prim")
-       if (other.classname != "plasma_chain")
-       if (other.classname != "droppedweapon")
-       if (other.classname != "nexball_basketball")
-       if (other.classname != "nexball_football")
+       if (self.active != ACTIVE_ACTIVE) 
                return;
 
-       if (other.deadflag && other.iscreature)
+       if (!isPushable(other))
                return;
 
        EXACTTRIGGER_TOUCH;
@@ -1333,6 +1461,7 @@ void trigger_impulse_touch3()
         str = self.strength;
 
     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
+    UpdateCSQCProjectile(other);
 }
 
 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
@@ -1355,10 +1484,12 @@ in directional and sperical mode. For damper/accelerator mode this is not nesses
 
 void spawnfunc_trigger_impulse()
 {
+       self.active = ACTIVE_ACTIVE;
+
        EXACTTRIGGER_INIT;
     if(self.radius)
     {
-        if(!self.strength) self.strength = 2000 * cvar("g_triggerimpulse_radial_multiplier");
+        if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
         setorigin(self, self.origin);
         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
         self.touch = trigger_impulse_touch3;
@@ -1367,13 +1498,13 @@ void spawnfunc_trigger_impulse()
     {
         if(self.target)
         {
-            if(!self.strength) self.strength = 950 * cvar("g_triggerimpulse_directional_multiplier");
+            if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
             self.touch = trigger_impulse_touch1;
         }
         else
         {
             if(!self.strength) self.strength = 0.9;
-                       self.strength = pow(self.strength, cvar("g_triggerimpulse_accel_power")) * cvar("g_triggerimpulse_accel_multiplier");
+                       self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
             self.touch = trigger_impulse_touch2;
         }
     }
@@ -1511,7 +1642,7 @@ void spawnfunc_trigger_multivibrator()
        self.state = 0;
        self.use = multivibrator_toggle;
        self.think = multivibrator_send;
-       self.nextthink = time;
+       self.nextthink = max(1, time);
 
        IFTARGETED
                multivibrator_reset();
@@ -1557,6 +1688,7 @@ void follow_init()
                        attach_sameorigin(dst, src, self.message);
                }
 
+               dst.solid = SOLID_NOT; // solid doesn't work with attachment
                remove(self);
        }
        else
@@ -1931,3 +2063,64 @@ void spawnfunc_trigger_magicear()
        // target:
        //   what to trigger
 }
+
+void relay_activators_use()
+{
+       entity trg, os;
+       
+       os = self;
+       
+       for(trg = world; (trg = find(trg, targetname, os.target)); )
+       {
+               self = trg;
+               if (trg.setactive)
+                       trg.setactive(os.cnt);
+               else
+               {
+                       //bprint("Not using setactive\n");
+                       if(os.cnt == ACTIVE_TOGGLE)
+                               if(trg.active == ACTIVE_ACTIVE)
+                                       trg.active = ACTIVE_NOT;
+                               else    
+                                       trg.active = ACTIVE_ACTIVE;
+                       else
+                               trg.active = os.cnt;
+               }               
+       }
+       self = os;
+}
+
+void spawnfunc_relay_activate()
+{
+       self.cnt = ACTIVE_ACTIVE;
+       self.use = relay_activators_use;
+}
+
+void spawnfunc_relay_deactivate()
+{
+       self.cnt = ACTIVE_NOT;
+       self.use = relay_activators_use;        
+}
+
+void spawnfunc_relay_activatetoggle()
+{
+       self.cnt = ACTIVE_TOGGLE;
+       self.use = relay_activators_use;        
+}
+
+.string chmap, gametype;
+void spawnfunc_target_changelevel_use()
+{
+       if(self.gametype != "")
+               MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
+
+       if (self.chmap == "")
+               localcmd("endmatch\n");
+       else
+               localcmd(strcat("changelevel ", self.chmap, "\n"));
+}
+
+void spawnfunc_target_changelevel()
+{
+       self.use = spawnfunc_target_changelevel_use;
+}