]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into DefaultUser/trigger_cleanup
authorMario <mario@smbclan.net>
Wed, 25 Apr 2018 12:26:45 +0000 (22:26 +1000)
committerMario <mario@smbclan.net>
Wed, 25 Apr 2018 12:26:45 +0000 (22:26 +1000)
# Conflicts:
# qcsrc/common/triggers/func/pointparticles.qc
# qcsrc/common/triggers/misc/laser.qc
# qcsrc/common/triggers/target/music.qc

1  2 
qcsrc/common/sounds/sound.qh
qcsrc/common/triggers/func/door.qc
qcsrc/common/triggers/func/pointparticles.qc
qcsrc/common/triggers/misc/laser.qc
qcsrc/common/triggers/misc/teleport_dest.qc
qcsrc/common/triggers/target/music.qc
qcsrc/common/triggers/target/music.qh
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/triggers/triggers.qc
qcsrc/lib/net.qh
qcsrc/lib/spawnfunc.qh

index a22e0e70ee45f81fbb003b5edbcf73b99bacb63f,036c0867013124ede9dce8ccbcc677dd9fa69271..d46ac9eafc7c36cccbeb07c7d302fe9b25f250d3
@@@ -22,7 -22,7 +22,7 @@@ const int CH_PLAYER_SINGLE = 7
  // const int CH_BGM_SINGLE = -8;
  const int CH_BGM_SINGLE = 8;
  const int CH_AMBIENT = -9;
 -// const int CH_AMBIENT_SINGLE = 9;
 +const int CH_AMBIENT_SINGLE = 9;
  
  const float ATTEN_NONE = 0;
  const float ATTEN_MIN = 0.015625;
@@@ -92,40 -92,55 +92,55 @@@ const float VOL_MUFFLED = 0.35
                } \
        } MACRO_END
  
+ string _Sound_fixpath(string base)
+ {
+       if (base == "") return string_null;
+ #ifdef SVQC
+       return strcat(base, ".wav");  // let the client engine decide
+ #else
+ #define extensions(x) \
+       x(wav) \
+       x(ogg) \
+       x(flac) \
+       /**/
+ #define tryext(ext) { \
+               string s = strcat(base, "." #ext); \
+               if (fexists(strcat("sound/", s))) { \
+                       return s; \
+               } \
+       }
+       extensions(tryext);
+       LOG_WARNF("Missing sound: \"%s\"", strcat("sound/", base));
+ #undef tryext
+ #undef extensions
+       return string_null;
+ #endif
+ }
  CLASS(Sound, Object)
        ATTRIB(Sound, m_id, int, 0);
        ATTRIB(Sound, sound_str, string());
+       ATTRIB(Sound, sound_str_, string);
        CONSTRUCTOR(Sound, string() path)
        {
                CONSTRUCT(Sound);
                this.sound_str = path;
        }
-       #define Sound_fixpath(this) _Sound_fixpath((this).sound_str())
-       string _Sound_fixpath(string base)
-       {
-               if (base == "") return string_null;
- #ifdef SVQC
-               return strcat(base, ".wav");  // let the client engine decide
- #else
-               #define extensions(x) \
-                       x(wav) \
-                       x(ogg) \
-                       x(flac) \
-                       /**/
-               #define tryext(ext) { string s = strcat(base, "." #ext); if (fexists(strcat("sound/", s))) return s; }
-               extensions(tryext);
-               LOG_WARNF("Missing sound: \"%s\"", strcat("sound/", base));
-               #undef tryext
-               #undef extensions
-               return string_null;
- #endif
-       }
        METHOD(Sound, sound_precache, void(Sound this))
        {
            TC(Sound, this);
-               string s = Sound_fixpath(this);
+               string s = _Sound_fixpath(this.sound_str());
                if (!s) return;
                profile(sprintf("precache_sound(\"%s\")", s));
                precache_sound(s);
+               strcpy(this.sound_str_, s);
        }
  ENDCLASS(Sound)
+ entity _Sound_fixpath_this;
+ string _Sound_fixpath_cached;
+ #define Sound_fixpath(this) ( \
+       _Sound_fixpath_this = (this), \
+       _Sound_fixpath_cached = _Sound_fixpath_this.sound_str_, \
+       _Sound_fixpath_cached ? _Sound_fixpath_cached : _Sound_fixpath(_Sound_fixpath_this.sound_str()) \
+ )
index d31c5a464b416f56ea268b87238963f67ea4ea14,956114a26b38a95a85d7c2225405eca5b2330bf7..c19041aa0b1b7ad2269597caa1ea93040a26afa6
@@@ -1,5 -1,4 +1,5 @@@
  #include "door.qh"
 +#include "door_rotating.qh"
  /*
  
  Doors are similar to buttons, but can spawn a fat trigger field around them
@@@ -24,10 -23,12 +24,10 @@@ THINK FUNCTION
  
  void door_go_down(entity this);
  void door_go_up(entity this, entity actor, entity trigger);
 -void door_rotating_go_down(entity this);
 -void door_rotating_go_up(entity this, entity oth);
  
  void door_blocked(entity this, entity blocker)
  {
 -      if((this.spawnflags & 8)
 +      if((this.spawnflags & DOOR_CRUSH)
  #ifdef SVQC
                && (blocker.takedamage != DAMAGE_NO)
  #elif defined(CSQC)
                        if (this.wait >= 0)
                        {
                                if (this.state == STATE_DOWN)
-                       if (this.classname == "door")
-                       {
-                               door_go_up (this, NULL, NULL);
-                       } else
-                       {
-                               door_rotating_go_up(this, blocker);
-                       }
+                               {
+                                       if (this.classname == "door")
+                                               door_go_up(this, NULL, NULL);
+                                       else
+                                               door_rotating_go_up(this, blocker);
+                               }
                                else
-                       if (this.classname == "door")
-                       {
-                               door_go_down (this);
-                       } else
-                       {
-                               door_rotating_go_down (this);
-                       }
+                               {
+                                       if (this.classname == "door")
+                                               door_go_down(this);
+                                       else
+                                               door_rotating_go_down(this);
+                               }
                        }
                }
  #ifdef SVQC
@@@ -178,7 -177,7 +176,7 @@@ bool door_check_keys(entity door, entit
        if(!door.itemkeys)
        {
  #ifdef SVQC
 -              play2(player, SND(TALK));
 +              play2(player, door.noise);
                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
  #endif
                return true;
@@@ -239,12 -238,12 +237,12 @@@ void door_fire(entity this, entity acto
                        door_go_up(e, actor, trigger);
                } else {
                        // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
 -                      if ((e.spawnflags & 2) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
 +                      if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
                                e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
                                e.pos2 = '0 0 0' - e.pos2;
                        }
                        // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
 -                      if (!((e.spawnflags & 2) &&  (e.spawnflags & 8) && e.state == STATE_DOWN
 +                      if (!((e.spawnflags & DOOR_ROTATING_BIDIR) &&  (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
                                && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
                        {
                                door_rotating_go_up(e, trigger);
@@@ -264,7 -263,7 +262,7 @@@ void door_use(entity this, entity actor
  
  void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
  {
 -      if(this.spawnflags & DOOR_NOSPLASH)
 +      if(this.spawnflags & NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
        this.health = this.health - damage;
@@@ -314,7 -313,7 +312,7 @@@ void door_touch(entity this, entity tou
  
  void door_generic_plat_blocked(entity this, entity blocker)
  {
 -      if((this.spawnflags & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
 +      if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
  #ifdef SVQC
                Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
  #endif
        }
  }
  
 -void door_rotating_hit_top(entity this)
 -{
 -      if (this.noise1 != "")
 -              _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
 -      this.state = STATE_TOP;
 -      if (this.spawnflags & DOOR_TOGGLE)
 -              return;         // don't come down automatically
 -      setthink(this, door_rotating_go_down);
 -      this.nextthink = this.ltime + this.wait;
 -}
 -
 -void door_rotating_hit_bottom(entity this)
 -{
 -      if (this.noise1 != "")
 -              _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
 -      if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
 -      {
 -              this.pos2 = '0 0 0' - this.pos2;
 -              this.lip = 0;
 -      }
 -      this.state = STATE_BOTTOM;
 -}
 -
 -void door_rotating_go_down(entity this)
 -{
 -      if (this.noise2 != "")
 -              _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
 -      if (this.max_health)
 -      {
 -              this.takedamage = DAMAGE_YES;
 -              this.health = this.max_health;
 -      }
 -
 -      this.state = STATE_DOWN;
 -      SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
 -}
 -
 -void door_rotating_go_up(entity this, entity oth)
 -{
 -      if (this.state == STATE_UP)
 -              return;         // already going up
 -
 -      if (this.state == STATE_TOP)
 -      {       // reset top wait time
 -              this.nextthink = this.ltime + this.wait;
 -              return;
 -      }
 -      if (this.noise2 != "")
 -              _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
 -      this.state = STATE_UP;
 -      SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
 -
 -      string oldmessage;
 -      oldmessage = this.message;
 -      this.message = "";
 -      SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
 -      this.message = oldmessage;
 -}
 -
 -
  /*
  =========================================
  door trigger
@@@ -406,7 -465,7 +404,7 @@@ LinkDoor
  
  entity LinkDoors_nextent(entity cur, entity near, entity pass)
  {
 -      while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
 +      while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
        {
        }
        return cur;
@@@ -439,7 -498,7 +437,7 @@@ void LinkDoors(entity this
  
        if (this.enemy)
                return;         // already linked by another door
 -      if (this.spawnflags & 4)
 +      if (this.spawnflags & DOOR_DONT_LINK)
        {
                this.owner = this.enemy = this;
  
@@@ -629,93 -688,53 +627,93 @@@ void door_reset(entity this
  
  #ifdef SVQC
  
 -// spawnflags require key (for now only func_door)
 -spawnfunc(func_door)
 +// common code for func_door and func_door_rotating spawnfuncs
 +void door_init_shared(entity this)
  {
 -      // Quake 1 keys compatibility
 -      if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
 -              this.itemkeys |= ITEM_KEY_BIT(0);
 -      if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
 -              this.itemkeys |= ITEM_KEY_BIT(1);
 -
 -      SetMovedir(this);
 -
        this.max_health = this.health;
 -      if (!InitMovingBrushTrigger(this))
 -              return;
 -      this.effects |= EF_LOWPRECISION;
 -      this.classname = "door";
  
 +      // unlock sound
        if(this.noise == "")
 +      {
                this.noise = "misc/talk.wav";
 +      }
 +      // door still locked sound
        if(this.noise3 == "")
 +      {
                this.noise3 = "misc/talk.wav";
 +      }
        precache_sound(this.noise);
        precache_sound(this.noise3);
  
 -      setblocked(this, door_blocked);
 -      this.use = door_use;
 -
 -      if(this.dmg && (this.message == ""))
 +      if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
 +      {
                this.message = "was squished";
 -      if(this.dmg && (this.message2 == ""))
 +      }
 +      if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
 +      {
                this.message2 = "was squished by";
 +      }
  
 +      // TODO: other soundpacks
        if (this.sounds > 0)
        {
                this.noise2 = "plats/medplat1.wav";
                this.noise1 = "plats/medplat2.wav";
        }
  
 -      if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
 -      if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
 +      // sound when door stops moving
 +      if(this.noise1 && this.noise1 != "")
 +      {
 +              precache_sound(this.noise1);
 +      }
 +      // sound when door is moving
 +      if(this.noise2 && this.noise2 != "")
 +      {
 +              precache_sound(this.noise2);
 +      }
  
 -      if (!this.speed)
 -              this.speed = 100;
        if (!this.wait)
 +      {
                this.wait = 3;
 +      }
        if (!this.lip)
 +      {
                this.lip = 8;
 +      }
 +
 +      this.state = STATE_BOTTOM;
 +
 +      if (this.health)
 +      {
 +              //this.canteamdamage = true; // TODO
 +              this.takedamage = DAMAGE_YES;
 +              this.event_damage = door_damage;
 +      }
 +
 +      if (this.items)
 +      {
 +              this.wait = -1;
 +      }
 +}
 +
 +// spawnflags require key (for now only func_door)
 +spawnfunc(func_door)
 +{
 +      // Quake 1 keys compatibility
 +      if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
 +              this.itemkeys |= ITEM_KEY_BIT(0);
 +      if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
 +              this.itemkeys |= ITEM_KEY_BIT(1);
 +
 +      SetMovedir(this);
 +
 +      if (!InitMovingBrushTrigger(this))
 +              return;
 +      this.effects |= EF_LOWPRECISION;
 +      this.classname = "door";
 +
 +      setblocked(this, door_blocked);
 +      this.use = door_use;
  
        this.pos1 = this.origin;
        this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
        if (this.spawnflags & DOOR_START_OPEN)
                InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
  
 -      this.state = STATE_BOTTOM;
 +      door_init_shared(this);
  
 -      if (this.health)
 +      if (!this.speed)
        {
 -              //this.canteamdamage = true; // TODO
 -              this.takedamage = DAMAGE_YES;
 -              this.event_damage = door_damage;
 +              this.speed = 100;
        }
  
 -      if (this.items)
 -              this.wait = -1;
 -
        settouch(this, door_touch);
  
  // LinkDoors can't be done until all of the doors have been spawned, so
index 18d3c30e8d774478102c28344bd894bc4681cb6c,e028b4c837d7b20bddc47223b060dcff0ff40c9e..7de5a03ef8171e905ea0d7eff538580ad7e22f5e
@@@ -4,39 -4,39 +4,39 @@@ REGISTER_NET_LINKED(ENT_CLIENT_POINTPAR
  #ifdef SVQC
  // NOTE: also contains func_sparks
  
 -bool pointparticles_SendEntity(entity this, entity to, float fl)
 +bool pointparticles_SendEntity(entity this, entity to, float sendflags)
  {
        WriteHeader(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
  
        // optional features to save space
 -      fl = fl & 0x0F;
 -      if(this.spawnflags & 2)
 -              fl |= 0x10; // absolute count on toggle-on
 +      sendflags = sendflags & 0x0F;
 +      if(this.spawnflags & PARTICLES_IMPULSE)
 +              sendflags |= SF_POINTPARTICLES_IMPULSE; // absolute count on toggle-on
        if(this.movedir != '0 0 0' || this.velocity != '0 0 0')
 -              fl |= 0x20; // 4 bytes - saves CPU
 +              sendflags |= SF_POINTPARTICLES_MOVING; // 4 bytes - saves CPU
        if(this.waterlevel || this.count != 1)
 -              fl |= 0x40; // 4 bytes - obscure features almost never used
 +              sendflags |= SF_POINTPARTICLES_JITTER_AND_COUNT; // 4 bytes - obscure features almost never used
        if(this.mins != '0 0 0' || this.maxs != '0 0 0')
 -              fl |= 0x80; // 14 bytes - saves lots of space
 +              sendflags |= SF_POINTPARTICLES_BOUNDS; // 14 bytes - saves lots of space
  
 -      WriteByte(MSG_ENTITY, fl);
 -      if(fl & 2)
 +      WriteByte(MSG_ENTITY, sendflags);
 +      if(sendflags & SF_TRIGGER_UPDATE)
        {
 -              if(this.state)
 +              if(this.active == ACTIVE_ACTIVE)
                        WriteCoord(MSG_ENTITY, this.impulse);
                else
                        WriteCoord(MSG_ENTITY, 0); // off
        }
 -      if(fl & 4)
 +      if(sendflags & SF_TRIGGER_RESET)
        {
                WriteVector(MSG_ENTITY, this.origin);
        }
 -      if(fl & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                if(this.model != "null")
                {
                        WriteShort(MSG_ENTITY, this.modelindex);
 -                      if(fl & 0x80)
 +                      if(sendflags & SF_POINTPARTICLES_BOUNDS)
                        {
                                WriteVector(MSG_ENTITY, this.mins);
                                WriteVector(MSG_ENTITY, this.maxs);
                else
                {
                        WriteShort(MSG_ENTITY, 0);
 -                      if(fl & 0x80)
 +                      if(sendflags & SF_POINTPARTICLES_BOUNDS)
                        {
                                WriteVector(MSG_ENTITY, this.maxs);
                        }
                }
                WriteShort(MSG_ENTITY, this.cnt);
                WriteString(MSG_ENTITY, this.mdl);
 -              if(fl & 0x20)
 +              if(sendflags & SF_POINTPARTICLES_MOVING)
                {
                        WriteShort(MSG_ENTITY, compressShortVector(this.velocity));
                        WriteShort(MSG_ENTITY, compressShortVector(this.movedir));
                }
 -              if(fl & 0x40)
 +              if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT)
                {
                        WriteShort(MSG_ENTITY, this.waterlevel * 16.0);
                        WriteByte(MSG_ENTITY, this.count * 16.0);
        return 1;
  }
  
 -void pointparticles_use(entity this, entity actor, entity trigger)
 -{
 -      this.state = !this.state;
 -      this.SendFlags |= 2;
 -}
 -
  void pointparticles_think(entity this)
  {
        if(this.origin != this.oldorigin)
        {
 -              this.SendFlags |= 4;
 +              this.SendFlags |= SF_TRIGGER_RESET;
                this.oldorigin = this.origin;
        }
        this.nextthink = time;
  }
  
 -void pointparticles_reset(entity this)
 -{
 -      if(this.spawnflags & 1)
 -              this.state = 1;
 -      else
 -              this.state = 0;
 -}
 -
  spawnfunc(func_pointparticles)
  {
        if(this.model != "") { precache_model(this.model); _setmodel(this, this.model); }
                setsize(this, '0 0 0', this.maxs - this.mins);
        }
        //if(!this.cnt) this.cnt = _particleeffectnum(this.mdl);
 +      this.setactive = generic_netlinked_setactive;
  
 -      Net_LinkEntity(this, (this.spawnflags & 4), 0, pointparticles_SendEntity);
 +      Net_LinkEntity(this, (this.spawnflags & PARTICLES_VISCULLING), 0, pointparticles_SendEntity);
  
        IFTARGETED
        {
 -              this.use = pointparticles_use;
 -              this.reset = pointparticles_reset;
 -              this.reset(this);
 +              // backwards compatibility
 +              this.use = generic_netlinked_legacy_use;
        }
 -      else
 -              this.state = 1;
 +      this.reset = generic_netlinked_reset;
 +      this.reset(this);
        setthink(this, pointparticles_think);
        this.nextthink = time;
  }
  
  spawnfunc(func_sparks)
  {
 -      // this.cnt is the amount of sparks that one burst will spawn
 -      if(this.cnt < 1) {
 -              this.cnt = 25.0; // nice default value
 +      if(this.count < 1) {
 +              this.count = 25.0; // nice default value
        }
  
 -      // this.wait is the probability that a sparkthink will spawn a spark shower
 -      // range: 0 - 1, but 0 makes little sense, so...
 -      if(this.wait < 0.05) {
 -              this.wait = 0.25; // nice default value
 +      if(this.impulse < 0.5) {
 +              this.impulse = 2.5; // nice default value
        }
  
 -      this.count = this.cnt;
        this.mins = '0 0 0';
        this.maxs = '0 0 0';
        this.velocity = '0 0 -1';
        this.mdl = "TE_SPARK";
 -      this.impulse = 10 * this.wait; // by default 2.5/sec
 -      this.wait = 0;
        this.cnt = 0; // use mdl
  
        spawnfunc_func_pointparticles(this);
  .int dphitcontentsmask;
  
  entityclass(PointParticles);
- class(PointParticles) .int cnt; // effect number
- class(PointParticles) .vector velocity; // particle velocity
- class(PointParticles) .float waterlevel; // direction jitter
- class(PointParticles) .int count; // count multiplier
- class(PointParticles) .int impulse; // density
- class(PointParticles) .string noise; // sound
- class(PointParticles) .float atten;
- class(PointParticles) .float volume;
- class(PointParticles) .int absolute; // 1 = count per second is absolute, ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = only spawn at toggle
- class(PointParticles) .vector movedir; // trace direction
- class(PointParticles) .float glow_color; // palette index
+ classfield(PointParticles) .int cnt; // effect number
+ classfield(PointParticles) .vector velocity; // particle velocity
+ classfield(PointParticles) .float waterlevel; // direction jitter
+ classfield(PointParticles) .int count; // count multiplier
+ classfield(PointParticles) .int impulse; // density
+ classfield(PointParticles) .string noise; // sound
+ classfield(PointParticles) .float atten;
+ classfield(PointParticles) .float volume;
 -classfield(PointParticles) .float absolute; // 1 = count per second is absolute, 2 = only spawn at toggle
++classfield(PointParticles) .float absolute; // 1 = count per second is absolute, ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = only spawn at toggle
+ classfield(PointParticles) .vector movedir; // trace direction
+ classfield(PointParticles) .float glow_color; // palette index
  
 +const int ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = 2;
 +
  void Draw_PointParticles(entity this)
  {
        float n, i, fail;
        o = this.origin;
        sz = this.maxs - this.mins;
        n = doBGMScript(this);
 -      if(this.absolute == 2)
 +      if(this.absolute == ABSOLUTE_ONLY_SPAWN_AT_TOGGLE)
        {
                if(n >= 0)
                        n = this.just_toggled ? this.impulse : 0;
  
  void Ent_PointParticles_Remove(entity this)
  {
-       if(this.noise)
-               strunzone(this.noise);
-       this.noise = string_null;
-       if(this.bgmscript)
-               strunzone(this.bgmscript);
-       this.bgmscript = string_null;
-       if(this.mdl)
-               strunzone(this.mdl);
-       this.mdl = string_null;
+     strfree(this.noise);
+     strfree(this.bgmscript);
+     strfree(this.mdl);
  }
  
  NET_HANDLE(ENT_CLIENT_POINTPARTICLES, bool isnew)
  {
        float i;
        vector v;
 -      int f = ReadByte();
 -      if(f & 2)
 +      int sendflags = ReadByte();
 +      if(sendflags & SF_TRIGGER_UPDATE)
        {
                i = ReadCoord(); // density (<0: point, >0: volume)
                if(i && !this.impulse && (this.cnt || this.mdl)) // this.cnt check is so it only happens if the ent already existed
                        this.just_toggled = 1;
                this.impulse = i;
        }
 -      if(f & 4)
 +      if(sendflags & SF_TRIGGER_RESET)
        {
                this.origin = ReadVector();
        }
 -      if(f & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                this.modelindex = ReadShort();
 -              if(f & 0x80)
 +              if(sendflags & SF_POINTPARTICLES_BOUNDS)
                {
                        if(this.modelindex)
                        {
                this.cnt = ReadShort(); // effect number
                this.mdl = strzone(ReadString()); // effect string
  
 -              if(f & 0x20)
 +              if(sendflags & SF_POINTPARTICLES_MOVING)
                {
                        this.velocity = decompressShortVector(ReadShort());
                        this.movedir = decompressShortVector(ReadShort());
                {
                        this.velocity = this.movedir = '0 0 0';
                }
 -              if(f & 0x40)
 +              if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT)
                {
                        this.waterlevel = ReadShort() / 16.0;
                        this.count = ReadByte() / 16.0;
                        this.waterlevel = 0;
                        this.count = 1;
                }
-               if(this.noise)
-                       strunzone(this.noise);
-               if(this.bgmscript)
-                       strunzone(this.bgmscript);
-               this.noise = strzone(ReadString());
+               strcpy(this.noise, ReadString());
                if(this.noise != "")
                {
                        this.atten = ReadByte() / 64.0;
                        this.volume = ReadByte() / 255.0;
                }
-               this.bgmscript = strzone(ReadString());
+               strcpy(this.bgmscript, ReadString());
                if(this.bgmscript != "")
                {
                        this.bgmscriptattack = ReadByte() / 64.0;
  
        return = true;
  
 -      if(f & 2)
 +      if(sendflags & SF_TRIGGER_UPDATE)
        {
                this.absolute = (this.impulse >= 0);
                if(!this.absolute)
                {
                        v = this.maxs - this.mins;
 -                      this.impulse *= -v.x * v.y * v.z / 262144; // relative: particles per 64^3 cube
 +                      this.impulse *= -v.x * v.y * v.z / (64**3); // relative: particles per 64^3 cube
                }
        }
  
 -      if(f & 0x10)
 -              this.absolute = 2;
 +      if(sendflags & SF_POINTPARTICLES_IMPULSE)
 +              this.absolute = ABSOLUTE_ONLY_SPAWN_AT_TOGGLE;
  
        setorigin(this, this.origin);
        setsize(this, this.mins, this.maxs);
index 12b67c3715368b7df38a7897736f1cd3e52d944d,b7bdede728211a77f0df4adf05211f3e11770ab4..df88b750f245dbc7000deccd0a6785bf5ae1a300
@@@ -16,12 -16,12 +16,12 @@@ void misc_laser_aim(entity this
        vector a;
        if(this.enemy)
        {
 -              if(this.spawnflags & 2)
 +              if(this.spawnflags & LASER_FINITE)
                {
                        if(this.enemy.origin != this.mangle)
                        {
                                this.mangle = this.enemy.origin;
 -                              this.SendFlags |= 2;
 +                              this.SendFlags |= SF_LASER_UPDATE_TARGET;
                        }
                }
                else
@@@ -31,7 -31,7 +31,7 @@@
                        if(a != this.mangle)
                        {
                                this.mangle = a;
 -                              this.SendFlags |= 2;
 +                              this.SendFlags |= SF_LASER_UPDATE_TARGET;
                        }
                }
        }
                if(this.angles != this.mangle)
                {
                        this.mangle = this.angles;
 -                      this.SendFlags |= 2;
 +                      this.SendFlags |= SF_LASER_UPDATE_TARGET;
                }
        }
        if(this.origin != this.oldorigin)
        {
 -              this.SendFlags |= 1;
 +              this.SendFlags |= SF_LASER_UPDATE_ORIGIN;
                this.oldorigin = this.origin;
        }
  }
@@@ -65,7 -65,7 +65,7 @@@ void misc_laser_think(entity this
  
        this.nextthink = time;
  
 -      if(!this.state)
 +      if(this.active == ACTIVE_NOT)
                return;
  
        misc_laser_aim(this);
        if(this.enemy)
        {
                o = this.enemy.origin;
 -              if (!(this.spawnflags & 2))
 -                      o = this.origin + normalize(o - this.origin) * 32768;
 +              if (!(this.spawnflags & LASER_FINITE))
 +                      o = this.origin + normalize(o - this.origin) * LASER_BEAM_MAXLENGTH;
        }
        else
        {
                makevectors(this.mangle);
 -              o = this.origin + v_forward * 32768;
 +              o = this.origin + v_forward * LASER_BEAM_MAXLENGTH;
        }
  
        if(this.dmg || this.enemy.target != "")
        if(this.dmg)
        {
                if(this.team)
 -                      if(((this.spawnflags & 8) == 0) == (this.team != hitent.team))
 +                      if(((this.spawnflags & LASER_INVERT_TEAM) == 0) == (this.team != hitent.team))
                                return;
                if(hitent.takedamage)
                        Damage(hitent, this, this, ((this.dmg < 0) ? 100000 : (this.dmg * frametime)), DEATH_HURTTRIGGER.m_id, DMG_NOWEP, hitloc, '0 0 0');
        }
  }
  
 -bool laser_SendEntity(entity this, entity to, float fl)
 +bool laser_SendEntity(entity this, entity to, float sendflags)
  {
        WriteHeader(MSG_ENTITY, ENT_CLIENT_LASER);
 -      fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
 -      if(this.spawnflags & 2)
 -              fl |= 0x80;
 +      sendflags = sendflags & 0x0F; // use that bit to indicate finite length laser
 +      if(this.spawnflags & LASER_FINITE)
 +              sendflags |= SF_LASER_FINITE;
        if(this.alpha)
 -              fl |= 0x40;
 +              sendflags |= SF_LASER_ALPHA;
        if(this.scale != 1 || this.modelscale != 1)
 -              fl |= 0x20;
 -      if(this.spawnflags & 4)
 -              fl |= 0x10;
 -      WriteByte(MSG_ENTITY, fl);
 -      if(fl & 1)
 +              sendflags |= SF_LASER_SCALE;
 +      if(this.spawnflags & LASER_NOTRACE)
 +              sendflags |= SF_LASER_NOTRACE;
 +      WriteByte(MSG_ENTITY, sendflags);
 +      if(sendflags & SF_LASER_UPDATE_ORIGIN)
        {
                WriteVector(MSG_ENTITY, this.origin);
        }
 -      if(fl & 8)
 +      if(sendflags & SF_LASER_UPDATE_EFFECT)
        {
 -              WriteByte(MSG_ENTITY, this.colormod_x * 255.0);
 -              WriteByte(MSG_ENTITY, this.colormod_y * 255.0);
 -              WriteByte(MSG_ENTITY, this.colormod_z * 255.0);
 -              if(fl & 0x40)
 +              WriteByte(MSG_ENTITY, this.beam_color.x * 255.0);
 +              WriteByte(MSG_ENTITY, this.beam_color.y * 255.0);
 +              WriteByte(MSG_ENTITY, this.beam_color.z * 255.0);
 +              if(sendflags & SF_LASER_ALPHA)
                        WriteByte(MSG_ENTITY, this.alpha * 255.0);
 -              if(fl & 0x20)
 +              if(sendflags & SF_LASER_SCALE)
                {
                        WriteByte(MSG_ENTITY, bound(0, this.scale * 16.0, 255));
                        WriteByte(MSG_ENTITY, bound(0, this.modelscale * 16.0, 255));
                }
 -              if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
 -                      WriteShort(MSG_ENTITY, this.cnt + 1);
 +              if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
 +                      WriteShort(MSG_ENTITY, this.cnt);
        }
 -      if(fl & 2)
 +      if(sendflags & SF_LASER_UPDATE_TARGET)
        {
 -              if(fl & 0x80)
 +              if(sendflags & SF_LASER_FINITE)
                {
                        WriteVector(MSG_ENTITY, this.enemy.origin);
                }
                        WriteAngle(MSG_ENTITY, this.mangle_y);
                }
        }
 -      if(fl & 4)
 -              WriteByte(MSG_ENTITY, this.state);
 -      return 1;
 +      if(sendflags & SF_LASER_UPDATE_ACTIVE)
 +              WriteByte(MSG_ENTITY, this.active);
 +      return true;
  }
  
  /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
@@@ -178,41 -178,24 +178,41 @@@ Keys
   spawnfunc_target_position where the laser ends
  "mdl"
   name of beam end effect to use
 -"colormod"
 +"beam_color"
   color of the beam (default: red)
  "dmg"
   damage per second (-1 for a laser that kills immediately)
  */
 -void laser_use(entity this, entity actor, entity trigger)
 +
 +void laser_setactive(entity this, int act)
  {
 -      this.state = !this.state;
 -      this.SendFlags |= 4;
 -      misc_laser_aim(this);
 +      int old_status = this.active;
 +      if(act == ACTIVE_TOGGLE)
 +      {
 +              if(this.active == ACTIVE_ACTIVE)
 +              {
 +                      this.active = ACTIVE_NOT;
 +              }
 +              else
 +              {
 +                      this.active = ACTIVE_ACTIVE;
 +              }
 +      }
 +      else
 +      {
 +              this.active = act;
 +      }
 +
 +      if (this.active != old_status)
 +      {
 +              this.SendFlags |= SF_LASER_UPDATE_ACTIVE;
 +              misc_laser_aim(this);
 +      }
  }
  
 -void laser_reset(entity this)
 +void laser_use(entity this, entity actor, entity trigger)
  {
 -      if(this.spawnflags & 1)
 -              this.state = 1;
 -      else
 -              this.state = 0;
 +      this.setactive(this, ACTIVE_TOGGLE);
  }
  
  spawnfunc(misc_laser)
        if(this.cnt < 0)
                this.cnt = -1;
  
 -      if(this.colormod == '0 0 0')
 +      if(!this.beam_color && this.colormod)
 +      {
 +              LOG_WARN("misc_laser uses legacy field 'colormod', please use 'beam_color' instead");
 +              this.beam_color = this.colormod;
 +      }
 +
 +      if(this.beam_color == '0 0 0')
 +      {
                if(!this.alpha)
 -                      this.colormod = '1 0 0';
 -      if(this.message == "") this.message = "saw the light";
 -      if (this.message2 == "") this.message2 = "was pushed into a laser by";
 -      if(!this.scale) this.scale = 1;
 -      if(!this.modelscale) this.modelscale = 1;
 -      else if(this.modelscale < 0) this.modelscale = 0;
 +                      this.beam_color = '1 0 0';
 +      }
 +
 +      if(this.message == "")
 +      {
 +              this.message = "saw the light";
 +      }
 +      if (this.message2 == "")
 +      {
 +              this.message2 = "was pushed into a laser by";
 +      }
 +      if(!this.scale)
 +      {
 +              this.scale = 1;
 +      }
 +      if(!this.modelscale)
 +      {
 +              this.modelscale = 1;
 +      }
 +      else if(this.modelscale < 0)
 +      {
 +              this.modelscale = 0;
 +      }
        setthink(this, misc_laser_think);
        this.nextthink = time;
        InitializeEntity(this, misc_laser_init, INITPRIO_FINDTARGET);
  
        Net_LinkEntity(this, false, 0, laser_SendEntity);
  
 +      this.setactive = laser_setactive;
 +
        IFTARGETED
        {
 -              this.reset = laser_reset;
 -              this.reset(this);
 +              // backwards compatibility
                this.use = laser_use;
        }
 -      else
 -              this.state = 1;
 +
 +      this.reset = generic_netlinked_reset;
 +      this.reset(this);
  }
  #elif defined(CSQC)
  
  // a laser goes from origin in direction angles
 -// it has color 'colormod'
 +// it has color 'beam_color'
  // and stops when something is in the way
  entityclass(Laser);
- class(Laser) .int cnt; // end effect
- class(Laser) .vector beam_color;
- class(Laser) .int active; // on-off
- class(Laser) .int count; // flags for the laser
- class(Laser) .vector velocity; // laser endpoint if it is FINITE
- class(Laser) .float alpha;
- class(Laser) .float scale; // scaling factor of the thickness
- class(Laser) .float modelscale; // scaling factor of the dlight
+ classfield(Laser) .int cnt; // end effect
+ classfield(Laser) .vector colormod;
+ classfield(Laser) .int state; // on-off
+ classfield(Laser) .int count; // flags for the laser
 -classfield(Laser) .vector velocity;
++classfield(Laser) .vector velocity; // laser endpoint if it is FINITE
+ classfield(Laser) .float alpha;
+ classfield(Laser) .float scale; // scaling factor of the thickness
+ classfield(Laser) .float modelscale; // scaling factor of the dlight
  
  void Draw_Laser(entity this)
  {
 -      if(!this.state)
 +      if(this.active == ACTIVE_NOT)
                return;
        InterpolateOrigin_Do(this);
 -      if(this.count & 0x80)
 +      if(this.count & SF_LASER_FINITE)
        {
 -              if(this.count & 0x10)
 +              if(this.count & SF_LASER_NOTRACE)
                {
                        trace_endpos = this.velocity;
                        trace_dphitq3surfaceflags = 0;
        }
        else
        {
 -              if(this.count & 0x10)
 +              if(this.count & SF_LASER_NOTRACE)
                {
                        makevectors(this.angles);
 -                      trace_endpos = this.origin + v_forward * 1048576;
 +                      trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE;
                        trace_dphitq3surfaceflags = Q3SURFACEFLAG_SKY;
                }
                else
                {
                        makevectors(this.angles);
 -                      traceline(this.origin, this.origin + v_forward * 32768, 0, this);
 +                      traceline(this.origin, this.origin + v_forward * LASER_BEAM_MAXLENGTH, 0, this);
                        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
 -                              trace_endpos = this.origin + v_forward * 1048576;
 +                              trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE;
                }
        }
        if(this.scale != 0)
        {
                if(this.alpha)
                {
 -                      Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.colormod, this.alpha, DRAWFLAG_NORMAL, view_origin);
 +                      Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, this.alpha, DRAWFLAG_NORMAL, view_origin);
                }
                else
                {
 -                      Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.colormod, 0.5, DRAWFLAG_ADDITIVE, view_origin);
 +                      Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, 0.5, DRAWFLAG_ADDITIVE, view_origin);
                }
        }
        if (!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT)))
        {
                if(this.cnt >= 0)
                        __pointparticles(this.cnt, trace_endpos, trace_plane_normal, drawframetime * 1000);
 -              if(this.colormod != '0 0 0' && this.modelscale != 0)
 -                      adddynamiclight(trace_endpos + trace_plane_normal * 1, this.modelscale, this.colormod * 5);
 +              if(this.beam_color != '0 0 0' && this.modelscale != 0)
 +                      adddynamiclight(trace_endpos + trace_plane_normal * 1, this.modelscale, this.beam_color * 5);
        }
  }
  
@@@ -360,43 -317,43 +360,43 @@@ NET_HANDLE(ENT_CLIENT_LASER, bool isnew
        InterpolateOrigin_Undo(this);
  
        // 30 bytes, or 13 bytes for just moving
 -      int f = ReadByte();
 -      this.count = (f & 0xF0);
 +      int sendflags = ReadByte();
 +      this.count = (sendflags & 0xF0);
  
 -      if(this.count & 0x80)
 +      if(this.count & SF_LASER_FINITE)
                this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
        else
                this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
  
 -      if(f & 1)
 +      if(sendflags & SF_LASER_UPDATE_ORIGIN)
        {
                this.origin = ReadVector();
                setorigin(this, this.origin);
        }
 -      if(f & 8)
 +      if(sendflags & SF_LASER_UPDATE_EFFECT)
        {
 -              this.colormod_x = ReadByte() / 255.0;
 -              this.colormod_y = ReadByte() / 255.0;
 -              this.colormod_z = ReadByte() / 255.0;
 -              if(f & 0x40)
 +              this.beam_color.x = ReadByte() / 255.0;
 +              this.beam_color.y = ReadByte() / 255.0;
 +              this.beam_color.z = ReadByte() / 255.0;
 +              if(sendflags & SF_LASER_ALPHA)
                        this.alpha = ReadByte() / 255.0;
                else
                        this.alpha = 0;
 -              this.scale = 2;
 -              this.modelscale = 50;
 -              if(f & 0x20)
 +              this.scale = 2; // NOTE: why 2?
 +              this.modelscale = 50; // NOTE: why 50?
 +              if(sendflags & SF_LASER_SCALE)
                {
                        this.scale *= ReadByte() / 16.0; // beam radius
                        this.modelscale *= ReadByte() / 16.0; // dlight radius
                }
 -              if((f & 0x80) || !(f & 0x10))
 -                      this.cnt = ReadShort() - 1; // effect number
 +              if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE))
 +                      this.cnt = ReadShort(); // effect number
                else
                        this.cnt = 0;
        }
 -      if(f & 2)
 +      if(sendflags & SF_LASER_UPDATE_TARGET)
        {
 -              if(f & 0x80)
 +              if(sendflags & SF_LASER_FINITE)
                {
                        this.velocity = ReadVector();
                }
                        this.angles_y = ReadAngle();
                }
        }
 -      if(f & 4)
 -              this.state = ReadByte();
 +      if(sendflags & SF_LASER_UPDATE_ACTIVE)
 +              this.active = ReadByte();
  
        return = true;
  
index 012412678481c3d0fa16e1bcc8dce779ad74361b,6d32f3c36b3fdef1c41a7a60c8845226c5b201e0..126a20ea26ec08e254c1ee6e9d9473479b084dda
@@@ -3,12 -3,12 +3,12 @@@ REGISTER_NET_LINKED(ENT_CLIENT_TELEPORT
  
  #ifdef SVQC
  
 -bool teleport_dest_send(entity this, entity to, int sf)
 +bool teleport_dest_send(entity this, entity to, int sendflags)
  {
        WriteHeader(MSG_ENTITY, ENT_CLIENT_TELEPORT_DEST);
 -      WriteByte(MSG_ENTITY, sf);
 +      WriteByte(MSG_ENTITY, sendflags);
  
 -      if(sf & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                WriteByte(MSG_ENTITY, this.cnt);
                WriteCoord(MSG_ENTITY, this.speed);
@@@ -26,7 -26,7 +26,7 @@@
  void teleport_dest_link(entity this)
  {
        Net_LinkEntity(this, false, 0, teleport_dest_send);
 -      this.SendFlags |= 1; // update
 +      this.SendFlags |= SF_TRIGGER_INIT;
  }
  
  spawnfunc(info_teleport_destination)
@@@ -57,20 -57,15 +57,15 @@@ spawnfunc(misc_teleporter_dest
  
  void teleport_dest_remove(entity this)
  {
-       //if(this.classname)
-               //strunzone(this.classname);
-       //this.classname = string_null;
-       if(this.targetname)
-               strunzone(this.targetname);
-       this.targetname = string_null;
+     // strfree(this.classname);
+     strfree(this.targetname);
  }
  
  NET_HANDLE(ENT_CLIENT_TELEPORT_DEST, bool isnew)
  {
 -      int sf = ReadByte();
 +      int sendflags = ReadByte();
  
 -      if(sf & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                this.classname = "info_teleport_destination";
                this.cnt = ReadByte();
index 24951b42b3b6a40427f2edb310a9976fbeb008fb,47977f267eff651f01575b1a13fddf01956f9640..5a63872dbd6c46b6742b3534be7b4531c0d38ef2
@@@ -14,10 -14,7 +14,10 @@@ REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_
  #ifdef SVQC
  
  IntrusiveList g_targetmusic_list;
 -STATIC_INIT(g_targetmusic_list) { g_targetmusic_list = IL_NEW(); }
 +STATIC_INIT(g_targetmusic_list)
 +{
 +      g_targetmusic_list = IL_NEW();
 +}
  
  // values:
  //   volume
@@@ -40,10 -37,7 +40,10 @@@ void target_music_sendto(entity this, i
  }
  void target_music_reset(entity this)
  {
 -      if (this.targetname == "") target_music_sendto(this, MSG_ALL, 1);
 +      if (this.targetname == "")
 +      {
 +              target_music_sendto(this, MSG_ALL, true);
 +      }
  }
  void target_music_kill()
  {
@@@ -51,9 -45,9 +51,9 @@@
        {
                it.volume = 0;
          if (it.targetname == "")
 -            target_music_sendto(it, MSG_ALL, 1);
 +            target_music_sendto(it, MSG_ALL, true);
          else
 -            target_music_sendto(it, MSG_ALL, 0);
 +            target_music_sendto(it, MSG_ALL, false);
        });
  }
  void target_music_use(entity this, entity actor, entity trigger)
        if(IS_REAL_CLIENT(actor))
        {
                msg_entity = actor;
 -              target_music_sendto(this, MSG_ONE, 1);
 +              target_music_sendto(this, MSG_ONE, true);
        }
        FOREACH_CLIENT(IS_SPEC(it) && it.enemy == actor, {
                msg_entity = it;
 -              target_music_sendto(this, MSG_ONE, 1);
 +              target_music_sendto(this, MSG_ONE, true);
        });
  }
  spawnfunc(target_music)
                this.volume = 1;
        IL_PUSH(g_targetmusic_list, this);
        if(this.targetname == "")
 -              target_music_sendto(this, MSG_INIT, 1);
 +              target_music_sendto(this, MSG_INIT, true);
        else
 -              target_music_sendto(this, MSG_INIT, 0);
 +              target_music_sendto(this, MSG_INIT, false);
  }
  void TargetMusic_RestoreGame()
  {
        IL_EACH(g_targetmusic_list, true,
        {
                if(it.targetname == "")
 -                      target_music_sendto(it, MSG_INIT, 1);
 +                      target_music_sendto(it, MSG_INIT, true);
                else
 -                      target_music_sendto(it, MSG_INIT, 0);
 +                      target_music_sendto(it, MSG_INIT, false);
        });
  }
  // values:
  //   targetname
  //   fade_time
  // spawnflags:
 -//   1 = START_OFF
 -// when triggered, it is disabled/enabled for everyone
 -bool trigger_music_SendEntity(entity this, entity to, float sf)
 +//   START_DISABLED
 +// can be disabled/enabled for everyone with relays
 +bool trigger_music_SendEntity(entity this, entity to, int sendflags)
  {
        WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC);
 -      sf &= ~0x80;
 -      if(this.cnt)
 -              sf |= 0x80;
 -      WriteByte(MSG_ENTITY, sf);
 -      if(sf & 4)
 +      WriteByte(MSG_ENTITY, sendflags);
 +      if(sendflags & SF_MUSIC_ORIGIN)
        {
                WriteVector(MSG_ENTITY, this.origin);
        }
 -      if(sf & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                if(this.model != "null")
                {
                WriteByte(MSG_ENTITY, this.fade_rate * 16.0);
                WriteString(MSG_ENTITY, this.noise);
        }
 -      return 1;
 +      if(sendflags & SF_TRIGGER_UPDATE)
 +      {
 +              WriteByte(MSG_ENTITY, this.active);
 +      }
 +      return true;
  }
  void trigger_music_reset(entity this)
  {
 -      this.cnt = !(this.spawnflags & 1);
 -      this.SendFlags |= 0x80;
 -}
 -void trigger_music_use(entity this, entity actor, entity trigger)
 -{
 -      this.cnt = !this.cnt;
 -      this.SendFlags |= 0x80;
 +      if(this.spawnflags & START_DISABLED)
 +      {
 +              this.setactive(this, ACTIVE_NOT);
 +      }
 +      else
 +      {
 +              this.setactive(this, ACTIVE_ACTIVE);
 +      }
  }
 +
  spawnfunc(trigger_music)
  {
 -      if(this.model != "") _setmodel(this, this.model);
 -      if(!this.volume) this.volume = 1;
 +      if(this.model != "")
 +      {
 +              _setmodel(this, this.model);
 +      }
 +      if(!this.volume)
 +      {
 +              this.volume = 1;
 +      }
        if(!this.modelindex)
        {
                setorigin(this, this.origin + this.mins);
                setsize(this, '0 0 0', this.maxs - this.mins);
        }
 -      trigger_music_reset(this);
  
 -      this.use = trigger_music_use;
 +      this.setactive = generic_netlinked_setactive;
 +      this.use = generic_netlinked_legacy_use; // backwards compatibility
        this.reset = trigger_music_reset;
 +      this.reset(this);
  
        Net_LinkEntity(this, false, 0, trigger_music_SendEntity);
  }
@@@ -179,14 -163,8 +179,14 @@@ void TargetMusic_Advance(
  {
        // run AFTER all the thinks!
        entity best = music_default;
 -      if (music_target && time < music_target.lifetime) best = music_target;
 -      if (music_trigger) best = music_trigger;
 +      if (music_target && time < music_target.lifetime)
 +      {
 +              best = music_target;
 +      }
 +      if (music_trigger)
 +      {
 +              best = music_trigger;
 +      }
        LL_EACH(TargetMusic_list, it.noise, {
                const float vol0 = (getsoundtime(it, CH_BGM_SINGLE) >= 0) ? it.lastvol : -1;
                if (it == best)
                if (vol != vol0)
                {
                        if(vol0 < 0)
 -                              _sound(it, CH_BGM_SINGLE, it.noise, vol, ATTEN_NONE); // restart
 +                              sound7(it, CH_BGM_SINGLE, it.noise, vol, ATTEN_NONE, 0, BIT(4)); // restart
                        else
 -                              _sound(it, CH_BGM_SINGLE, "", vol, ATTEN_NONE);
 +                              sound7(it, CH_BGM_SINGLE, "", vol, ATTEN_NONE, 0, BIT(4));
                        it.lastvol = vol;
                }
        });
@@@ -237,16 -215,13 +237,13 @@@ void Net_TargetMusic(
        }
        if(e.noise != noi)
        {
-               if(e.noise)
-                       strunzone(e.noise);
-               e.noise = strzone(noi);
+               strcpy(e.noise, noi);
                precache_sound(e.noise);
                _sound(e, CH_BGM_SINGLE, e.noise, 0, ATTEN_NONE);
                if(getsoundtime(e, CH_BGM_SINGLE) < 0)
                {
                        LOG_TRACEF("Cannot initialize sound %s", e.noise);
-                       strunzone(e.noise);
-                       e.noise = string_null;
+                       strfree(e.noise);
                }
        }
        e.volume = vol;
  
  void Ent_TriggerMusic_Think(entity this)
  {
 -      if(WarpZoneLib_BoxTouchesBrush(view_origin, view_origin, this, NULL))
 +      if(this.active == ACTIVE_NOT)
 +      {
 +              return;
 +      }
 +      vector org = (csqcplayer) ? csqcplayer.origin : view_origin;
 +      if(WarpZoneLib_BoxTouchesBrush(org + STAT(PL_MIN), org + STAT(PL_MAX), this, NULL))
        {
                music_trigger = this;
        }
 -      this.nextthink = time;
  }
  
  void Ent_TriggerMusic_Remove(entity this)
  {
-       if(this.noise)
-               strunzone(this.noise);
-       this.noise = string_null;
+     strfree(this.noise);
  }
  
  NET_HANDLE(ENT_CLIENT_TRIGGER_MUSIC, bool isnew)
  {
 -      int f = ReadByte();
 -      if(f & 4)
 +      int sendflags = ReadByte();
 +      if(sendflags & SF_MUSIC_ORIGIN)
        {
                this.origin = ReadVector();
        }
 -      if(f & 1)
 +      if(sendflags & SF_TRIGGER_INIT)
        {
                this.modelindex = ReadShort();
                if(this.modelindex)
                this.fade_time = ReadByte() / 16.0;
                this.fade_rate = ReadByte() / 16.0;
                string s = this.noise;
-               if(this.noise)
-                       strunzone(this.noise);
-               this.noise = strzone(ReadString());
+               strcpy(this.noise, ReadString());
                if(this.noise != s)
                {
                        precache_sound(this.noise);
 -                      _sound(this, CH_BGM_SINGLE, this.noise, 0, ATTEN_NONE);
 +                      sound7(this, CH_BGM_SINGLE, this.noise, 0, ATTEN_NONE, 0, BIT(4));
                        if(getsoundtime(this, CH_BGM_SINGLE) < 0)
                        {
 -                              LOG_TRACEF("Cannot initialize sound %s", this.noise);
 +                              LOG_WARNF("Cannot initialize sound %s", this.noise);
-                               strunzone(this.noise);
-                               this.noise = string_null;
+                               strfree(this.noise);
                        }
                }
        }
 +      if(sendflags & SF_TRIGGER_UPDATE)
 +      {
 +              this.active = ReadByte();
 +      }
  
        setorigin(this, this.origin);
        setsize(this, this.mins, this.maxs);
 -      this.cnt = 1;
 -      setthink(this, Ent_TriggerMusic_Think);
 -      this.nextthink = time;
 +      this.draw = Ent_TriggerMusic_Think;
 +      if(isnew)
 +      {
 +              LL_PUSH(TargetMusic_list, this);
 +              IL_PUSH(g_drawables, this);
 +      }
        return true;
  }
  
index fd8855eafb99563751bc8d2ef7ba3e899427d288,1cb32ea39c180b4c05f0095a3c58535669248084..ccf3f674e6f069394ec256b6a086df3eb1076aee
@@@ -2,8 -2,6 +2,8 @@@
  
  .float lifetime;
  
 +const int SF_MUSIC_ORIGIN = BIT(2);
 +
  #ifdef CSQC
  float music_disabled;
  entity music_default;
@@@ -12,8 -10,8 +12,8 @@@ entity music_trigger
  // FIXME also control bgmvolume here, to not require a target_music for the default track.
  
  entityclass(TargetMusic);
- class(TargetMusic) .int state;
- class(TargetMusic) .float lastvol;
+ classfield(TargetMusic) .int state;
+ classfield(TargetMusic) .float lastvol;
  
  void TargetMusic_Advance();
  
index 198e3bf6ebc4c17426e6685e538f558adc08668b,10a726391c597bdb6b81f97d1596a2690f52f951..f80d36097401e6314d916f9dc5ee3e08c92892b8
@@@ -1,6 -1,7 +1,6 @@@
  #include "jumppads.qh"
  // TODO: split target_push and put it in the target folder
  #ifdef SVQC
 -#include "jumppads.qh"
  #include <common/physics/movetypes/movetypes.qh>
  
  void trigger_push_use(entity this, entity actor, entity trigger)
@@@ -8,7 -9,7 +8,7 @@@
        if(teamplay)
        {
                this.team = actor.team;
 -              this.SendFlags |= 2;
 +              this.SendFlags |= SF_TRIGGER_UPDATE;
        }
  }
  #endif
@@@ -253,7 -254,7 +253,7 @@@ void trigger_push_touch(entity this, en
                return;
  
        if(this.team)
 -              if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher)))
 +              if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, toucher)))
                        return;
  
        EXACTTRIGGER_TOUCH(this, toucher);
@@@ -480,7 -481,7 +480,7 @@@ float trigger_push_send(entity this, en
  
  void trigger_push_updatelink(entity this)
  {
 -      this.SendFlags |= 1;
 +      this.SendFlags |= SF_TRIGGER_INIT;
  }
  
  void trigger_push_link(entity this)
@@@ -576,30 -577,16 +576,30 @@@ void target_push_init2(entity this
        target_push_init(this); // normal push target behaviour can be combined with a legacy pusher?
  }
  
 -spawnfunc(target_push) { target_push_init2(this); }
 -spawnfunc(info_notnull) { target_push_init(this); }
 -spawnfunc(target_position) { target_push_init(this); }
 +spawnfunc(target_push)
 +{
 +      target_push_init2(this);
 +}
 +
 +spawnfunc(info_notnull)
 +{
 +      target_push_init(this);
 +}
 +spawnfunc(target_position)
 +{
 +      target_push_init(this);
 +}
  
  #elif defined(CSQC)
  
  NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew)
  {
        this.classname = "jumppad";
 -      int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
 +      int mytm = ReadByte();
 +      if(mytm)
 +      {
 +              this.team = mytm - 1;
 +      }
        this.spawnflags = ReadInt24_t();
        this.active = ReadByte();
        this.height = ReadCoord();
  
  void target_push_remove(entity this)
  {
-       //if(this.classname)
-               //strunzone(this.classname);
-       //this.classname = string_null;
-       if(this.targetname)
-               strunzone(this.targetname);
-       this.targetname = string_null;
+       // strfree(this.classname);
+       strfree(this.targetname);
  }
  
  NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew)
index 144f6bfb0de01681ce70239f3645b3f0d237b488,0957699b37ad4ef9594e33f930c95b26397030e9..9db38a10b9478a6f9b53272ed21d2dc6e7353dc4
@@@ -21,63 -21,6 +21,63 @@@ void FixSize(entity e
  }
  
  #ifdef SVQC
 +void generic_setactive(entity this, int act)
 +{
 +      if(act == ACTIVE_TOGGLE)
 +      {
 +              if(this.active == ACTIVE_ACTIVE)
 +              {
 +                      this.active = ACTIVE_NOT;
 +              }
 +              else
 +              {
 +                      this.active = ACTIVE_ACTIVE;
 +              }
 +      }
 +      else
 +      {
 +              this.active = act;
 +      }
 +}
 +
 +void generic_netlinked_setactive(entity this, int act)
 +{
 +      int old_status = this.active;
 +      generic_setactive(this, act);
 +
 +      if (this.active != old_status)
 +      {
 +              this.SendFlags |= SF_TRIGGER_UPDATE;
 +      }
 +}
 +
 +void generic_netlinked_reset(entity this)
 +{
 +      IFTARGETED
 +      {
 +              if(this.spawnflags & START_ENABLED)
 +              {
 +                      this.active = ACTIVE_ACTIVE;
 +              }
 +              else
 +              {
 +                      this.active = ACTIVE_NOT;
 +              }
 +      }
 +      else
 +      {
 +              this.active = ACTIVE_ACTIVE;
 +      }
 +
 +      this.SendFlags |= SF_TRIGGER_UPDATE;
 +}
 +
 +// Compatibility with old maps
 +void generic_netlinked_legacy_use(entity this, entity actor, entity trigger)
 +{
 +      LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname);
 +      this.setactive(this, ACTIVE_TOGGLE);
 +}
  
  bool autocvar_g_triggers_debug = true;
  
@@@ -173,12 -116,12 +173,12 @@@ void trigger_common_read(entity this, b
  
        if(withtarget)
        {
-               if(this.target) { strunzone(this.target); }
-               if(this.target2) { strunzone(this.target2); }
-               if(this.target3) { strunzone(this.target3); }
-               if(this.target4) { strunzone(this.target4); }
-               if(this.targetname) { strunzone(this.targetname); }
-               if(this.killtarget) { strunzone(this.killtarget); }
+               strfree(this.target);
+               strfree(this.target2);
+               strfree(this.target3);
+               strfree(this.target4);
+               strfree(this.targetname);
+               strfree(this.killtarget);
  
                int targbits = ReadByte();
  
  
  void trigger_remove_generic(entity this)
  {
-       if(this.target) { strunzone(this.target); }
-       this.target = string_null;
-       if(this.target2) { strunzone(this.target2); }
-       this.target2 = string_null;
-       if(this.target3) { strunzone(this.target3); }
-       this.target3 = string_null;
-       if(this.target4) { strunzone(this.target4); }
-       this.target4 = string_null;
-       if(this.targetname) { strunzone(this.targetname); }
-       this.target = string_null;
-       if(this.killtarget) { strunzone(this.killtarget); }
-       this.killtarget = string_null;
+       strfree(this.target);
+       strfree(this.target2);
+       strfree(this.target3);
+       strfree(this.target4);
+       strfree(this.targetname);
+       strfree(this.killtarget);
  }
  #endif
  
diff --combined qcsrc/lib/net.qh
index a9c28dab3ecbc0b44b9a868a0ebab34bcf1a6410,c1edb5cd3a3600dc60dfd15915509aadd1458513..7b3f581b38f059cd4e4252905ae0dcf243983209
@@@ -50,7 -50,7 +50,7 @@@ STATIC_INIT(RegisterTempEntities_renumb
  
  #ifdef CSQC
        #define REGISTER_NET_LINKED(id) \
-               [[accumulate]] NET_HANDLE(id, bool isnew) \
+               ACCUMULATE NET_HANDLE(id, bool isnew) \
                { \
                        this = __self; \
                        this.sourceLoc = __FILE__ ":" STR(__LINE__); \
@@@ -107,6 -107,7 +107,6 @@@ STATIC_INIT(C2S_Protocol_renumber) { FO
  #ifdef SVQC
        const int MSG_ENTITY = 5;
  
 -      .int Version;  // deprecated, use SendFlags
        .int SendFlags;
  
        IntrusiveList g_uncustomizables;
        {
                if (g_buf == "") return;
                localcmd("\ncmd c2s \"", strreplace("$", "$$", g_buf), "\"\n");
-               strunzone(g_buf);
-               g_buf = string_null;
+               strfree(g_buf);
        }
  #endif
  
@@@ -300,8 -300,7 +299,7 @@@ MACRO_EN
                string s = string_null;
                yenc_single(b, s);
                string tmp = strcat(g_buf, s);
-               if (g_buf) strunzone(g_buf);
-               g_buf = strzone(tmp);
+               strcpy(g_buf, tmp);
        }
        void WriteShort(int to, int b)
        {
diff --combined qcsrc/lib/spawnfunc.qh
index 119d095e24432b54e153523378fae266eec6e2d8,614f7efb1b13c246c1679587d4a6bd405b7e7535..d3198b3ce4c7666bf986564916b9d9678b3dc097
@@@ -19,13 -19,13 +19,13 @@@ noref bool require_spawnfunc_prefix
        }
  
        #define _spawnfunc_checktypes(fld) \
-               if (fieldname == #fld) \
-                       if (!entityfieldassignablefromeditor(i)) LOG_FATALF("Entity field '%s' cannot be whitelisted", fieldname);
+               if (s == #fld) \
+                       if (!entityfieldassignablefromeditor(i)) LOG_FATALF("Entity field '%s' cannot be whitelisted", s);
  #else
        #define _spawnfunc_checktypes(fld)
  #endif
        #define _spawnfunc_check(fld) \
-               if (fieldname == #fld) continue;
+               if (s == #fld) continue;
  
        noref int __spawnfunc_expecting;
        noref entity __spawnfunc_expect;
@@@ -86,7 -86,7 +86,7 @@@
        #define spawnfunc_1(id) spawnfunc_2(id, FIELDS_UNION)
        #define spawnfunc_2(id, whitelist) \
                void __spawnfunc_##id(entity this); \
-               [[accumulate]] void spawnfunc_##id(entity this) \
+               ACCUMULATE void spawnfunc_##id(entity this) \
                { \
                    if (!__spawnfunc_first) { \
                  __spawnfunc_first = true; \
                        if (!this.spawnfunc_checked) { \
                                for (int i = 0, n = numentityfields(); i < n; ++i) { \
                                        string value = getentityfieldstring(i, this); \
-                                       string fieldname = entityfieldname(i); \
+                                       string s = entityfieldname(i); \
                                        whitelist(_spawnfunc_checktypes) \
                                        if (value == "") continue; \
-                                       if (fieldname == "") continue; \
+                                       if (s == "") continue; \
                                        FIELDS_COMMON(_spawnfunc_check) \
                                        whitelist(_spawnfunc_check) \
-                                       LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), #id, fieldname, value); \
+                                       LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), #id, s, value); \
                                } \
                                this.spawnfunc_checked = true; \
                                if (this) { \
                FIELD_SCALAR(fld, bgmscriptsustain) \
                FIELD_SCALAR(fld, bgmscript) \
                FIELD_SCALAR(fld, button0) \
 +              FIELD_SCALAR(fld, chmap) \
                FIELD_SCALAR(fld, cnt) \
                FIELD_SCALAR(fld, colormap) \
                FIELD_SCALAR(fld, count) \
                FIELD_SCALAR(fld, dmg_force) \
                FIELD_SCALAR(fld, dmg_radius) \
                FIELD_SCALAR(fld, effects) \
 +              FIELD_SCALAR(fld, falloff) \
                FIELD_SCALAR(fld, flags) \
                FIELD_SCALAR(fld, fog) \
                FIELD_SCALAR(fld, frags) \
                FIELD_SCALAR(fld, frame) \
 +              FIELD_SCALAR(fld, gametype) \
                FIELD_SCALAR(fld, gametypefilter) \
                FIELD_SCALAR(fld, geomtype) \
                FIELD_SCALAR(fld, gravity) \
                FIELD_SCALAR(fld, noalign) \
                FIELD_SCALAR(fld, noise1) \
                FIELD_SCALAR(fld, noise2) \
 +              FIELD_SCALAR(fld, noise3) \
                FIELD_SCALAR(fld, noise) \
                FIELD_SCALAR(fld, phase) \
                FIELD_SCALAR(fld, platmovetype) \
                FIELD_SCALAR(fld, target_random) \
                FIELD_SCALAR(fld, target_range) \
                FIELD_SCALAR(fld, team) \
 +              FIELD_SCALAR(fld, trigger_reverse) \
                FIELD_SCALAR(fld, turret_scale_health) \
                FIELD_SCALAR(fld, turret_scale_range) \
                FIELD_SCALAR(fld, turret_scale_respawn) \
                FIELD_VEC(fld, absmin) \
                FIELD_VEC(fld, angles) \
                FIELD_VEC(fld, avelocity) \
 +              FIELD_VEC(fld, beam_color)\
                FIELD_VEC(fld, debrisavelocityjitter) \
                FIELD_VEC(fld, debrisvelocity) \
                FIELD_VEC(fld, debrisvelocityjitter) \
                FIELD_VEC(fld, color) \
                FIELD_VEC(fld, mangle) \
                FIELD_VEC(fld, maxs) \
-               FIELD_VEC(fld, maxs) \
                FIELD_VEC(fld, mins) \
                FIELD_VEC(fld, modelscale_vec) \
                FIELD_VEC(fld, velocity) \