]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/t_plats.qc
Merge remote-tracking branch 'origin/master' into samual/notification_rewrite
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_plats.qc
index e0d1ca6f55882e3dc9954c08986f07cece7e0e30..0b9da88faf2b97fc524f472364bc2ae4f6dabc7f 100644 (file)
@@ -31,8 +31,8 @@ float PLAT_LOW_TRIGGER = 1;
 
 void plat_spawn_inside_trigger()
 {
-       local entity trigger;
-       local vector tmin, tmax;
+       entity trigger;
+       vector tmin, tmax;
 
        trigger = spawn();
        trigger.touch = plat_center_touch;
@@ -57,8 +57,18 @@ void plat_spawn_inside_trigger()
                tmax_y = tmin_y + 1;
        }
 
-       setsize (trigger, tmin, tmax);
-};
+       if(tmin_x < tmax_x)
+               if(tmin_y < tmax_y)
+                       if(tmin_z < tmax_z)
+                       {
+                               setsize (trigger, tmin, tmax);
+                               return;
+                       }
+
+       // otherwise, something is fishy...
+       remove(trigger);
+       objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
+}
 
 void plat_hit_top()
 {
@@ -66,27 +76,27 @@ void plat_hit_top()
        self.state = 1;
        self.think = plat_go_down;
        self.nextthink = self.ltime + 3;
-};
+}
 
 void plat_hit_bottom()
 {
        sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
        self.state = 2;
-};
+}
 
 void plat_go_down()
 {
        sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
        self.state = 3;
        SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
-};
+}
 
 void plat_go_up()
 {
        sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
        self.state = 4;
        SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
-};
+}
 
 void plat_center_touch()
 {
@@ -101,7 +111,7 @@ void plat_center_touch()
                plat_go_up ();
        else if (self.state == 1)
                self.nextthink = self.ltime + 1;        // delay going down
-};
+}
 
 void plat_outside_touch()
 {
@@ -114,14 +124,14 @@ void plat_outside_touch()
        self = self.enemy;
        if (self.state == 1)
                plat_go_down ();
-};
+}
 
 void plat_trigger_use()
 {
        if (self.think)
                return;         // already activated
        plat_go_down();
-};
+}
 
 
 void plat_crush()
@@ -144,15 +154,15 @@ void plat_crush()
        // plat state already had changed
        // this isn't a bug per se!
     }
-};
+}
 
 void plat_use()
 {
-       self.use = SUB_Null;
+       self.use = func_null;
        if (self.state != 4)
                objerror ("plat_use: not in up state");
        plat_go_down();
-};
+}
 
 .string sound1, sound2;
 
@@ -172,23 +182,18 @@ void plat_reset()
        }
 }
 
-void spawnfunc_path_corner() { };
+void spawnfunc_path_corner() { }
 void spawnfunc_func_plat()
 {
-       if (!self.t_length)
-               self.t_length = 80;
-       if (!self.t_width)
-               self.t_width = 10;
-
        if (self.sounds == 0)
                self.sounds = 2;
 
     if(self.spawnflags & 4)
         self.dmg = 10000;
 
-    if(self.dmg && (!self.message))
+    if(self.dmg && (self.message == ""))
                self.message = "was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
 
        if (self.sounds == 1)
@@ -231,16 +236,20 @@ void spawnfunc_func_plat()
 
        if (!self.speed)
                self.speed = 150;
+       if (!self.lip)
+               self.lip = 16;
+       if (!self.height)
+               self.height = self.size_z - self.lip;
 
        self.pos1 = self.origin;
        self.pos2 = self.origin;
-       self.pos2_z = self.origin_z - self.size_z + 8;
-
-       plat_spawn_inside_trigger ();   // the "start moving" trigger
+       self.pos2_z = self.origin_z - self.height;
 
        self.reset = plat_reset;
        plat_reset();
-};
+
+       plat_spawn_inside_trigger ();   // the "start moving" trigger
+}
 
 
 void() train_next;
@@ -265,15 +274,15 @@ void train_wait()
        SUB_UseTargets();
        self = oldself;
        self.enemy = world;
-};
+}
 
 void train_next()
 {
-       local entity targ;
+       entity targ;
        targ = find(world, targetname, self.target);
        self.enemy = targ;
        self.target = targ.target;
-       if (!self.target)
+       if (self.target == "")
                objerror("train_next: no next target");
        self.wait = targ.wait;
        if (!self.wait)
@@ -286,19 +295,19 @@ void train_next()
 
        if(self.noise != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
-};
+}
 
 void func_train_find()
 {
-       local entity targ;
+       entity targ;
        targ = find(world, targetname, self.target);
        self.target = targ.target;
-       if (!self.target)
+       if (self.target == "")
                objerror("func_train_find: no next target");
        setorigin(self, targ.origin - self.mins);
        self.nextthink = self.ltime + 1;
        self.think = train_next;
-};
+}
 
 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
 Ridable platform, targets spawnfunc_path_corner path to follow.
@@ -310,7 +319,7 @@ void spawnfunc_func_train()
        if (self.noise != "")
                precache_sound(self.noise);
 
-       if (!self.target)
+       if (self.target == "")
                objerror("func_train without a target");
        if (!self.speed)
                self.speed = 100;
@@ -323,16 +332,16 @@ void spawnfunc_func_train()
        InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
 
        self.blocked = generic_plat_blocked;
-       if(self.dmg & (!self.message))
+       if(self.dmg && (self.message == ""))
                self.message = " was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
        if(self.dmg && (!self.dmgtime))
                self.dmgtime = 0.25;
        self.dmgtime2 = time;
 
        // TODO make a reset function for this one
-};
+}
 
 void func_rotating_setactive(float astate)
 {
@@ -386,9 +395,9 @@ void spawnfunc_func_rotating()
        
        self.pos1 = self.avelocity;
     
-    if(self.dmg & (!self.message))
+    if(self.dmg && (self.message == ""))
         self.message = " was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
 
 
@@ -405,15 +414,15 @@ void spawnfunc_func_rotating()
 
        // wait for targets to spawn
        self.nextthink = self.ltime + 999999999;
-       self.think = SUB_Null;
+       self.think = SUB_NullThink; // for PushMove
 
        // TODO make a reset function for this one
-};
+}
 
 .float height;
 void func_bobbing_controller_think()
 {
-       local vector v;
+       vector v;
        self.nextthink = time + 0.1;
        
        if not (self.owner.active == ACTIVE_ACTIVE)
@@ -428,7 +437,7 @@ void func_bobbing_controller_think()
        if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
                // * 10 so it will arrive in 0.1 sec
                self.owner.velocity = (v - self.owner.origin) * 10;
-};
+}
 
 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
 Brush model that moves back and forth on one axis (default Z).
@@ -441,7 +450,7 @@ dmgtime : See above.
 */
 void spawnfunc_func_bobbing()
 {
-       local entity controller;
+       entity controller;
        if (self.noise != "")
        {
                precache_sound(self.noise);
@@ -460,9 +469,9 @@ void spawnfunc_func_bobbing()
 
        // damage when blocked
        self.blocked = generic_plat_blocked;
-       if(self.dmg & (!self.message))
+       if(self.dmg && (self.message == ""))
                self.message = " was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
        if(self.dmg && (!self.dmgtime))
                self.dmgtime = 0.25;
@@ -486,18 +495,18 @@ void spawnfunc_func_bobbing()
        controller.nextthink = time + 1;
        controller.think = func_bobbing_controller_think;
        self.nextthink = self.ltime + 999999999;
-       self.think = SUB_Null;
+       self.think = SUB_NullThink; // for PushMove
 
        // Savage: Reduce bandwith, critical on e.g. nexdm02
        self.effects |= EF_LOWPRECISION;
 
        // TODO make a reset function for this one
-};
+}
 
 .float freq;
 void func_pendulum_controller_think()
 {
-       local float v;
+       float v;
        self.nextthink = time + 0.1;
 
        if not (self.owner.active == ACTIVE_ACTIVE)
@@ -514,11 +523,11 @@ void func_pendulum_controller_think()
                // * 10 so it will arrive in 0.1 sec
                self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
        }
-};
+}
 
 void spawnfunc_func_pendulum()
 {
-       local entity controller;
+       entity controller;
        if (self.noise != "")
        {
                precache_sound(self.noise);
@@ -533,9 +542,9 @@ void spawnfunc_func_pendulum()
                self.speed = 30;
        // not initializing self.dmg to 2, to allow damageless pendulum
 
-       if(self.dmg & (!self.message))
+       if(self.dmg && (self.message == ""))
                self.message = " was squished";
-       if(self.dmg && (!self.message2))
+       if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
        if(self.dmg && (!self.dmgtime))
                self.dmgtime = 0.25;
@@ -563,12 +572,12 @@ void spawnfunc_func_pendulum()
        controller.nextthink = time + 1;
        controller.think = func_pendulum_controller_think;
        self.nextthink = self.ltime + 999999999;
-       self.think = SUB_Null;
+       self.think = SUB_NullThink; // for PushMove
 
        //self.effects |= EF_LOWPRECISION;
 
        // TODO make a reset function for this one
-};
+}
 
 // button and multiple button
 
@@ -583,12 +592,12 @@ void button_wait()
        activator = self.enemy;
        SUB_UseTargets();
        self.frame = 1;                 // use alternate textures
-};
+}
 
 void button_done()
 {
        self.state = STATE_BOTTOM;
-};
+}
 
 void button_return()
 {
@@ -597,13 +606,13 @@ void button_return()
        self.frame = 0;                 // use normal textures
        if (self.health)
                self.takedamage = DAMAGE_YES;   // can be shot again
-};
+}
 
 
 void button_blocked()
 {
        // do nothing, just don't come all the way back out
-};
+}
 
 
 void button_fire()
@@ -619,7 +628,7 @@ void button_fire()
 
        self.state = STATE_UP;
        SUB_CalcMove (self.pos2, self.speed, button_wait);
-};
+}
 
 void button_reset()
 {
@@ -644,7 +653,7 @@ void button_use()
 
        self.enemy = activator;
        button_fire ();
-};
+}
 
 void button_touch()
 {
@@ -663,7 +672,7 @@ void button_touch()
        if (other.owner)
                self.enemy = other.owner;
        button_fire ();
-};
+}
 
 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
@@ -681,7 +690,7 @@ void button_damage(entity inflictor, entity attacker, float damage, float deatht
                self.enemy = damage_attacker;
                button_fire ();
        }
-};
+}
 
 
 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
@@ -738,7 +747,7 @@ void spawnfunc_func_button()
     self.flags |= FL_NOTARGET;
 
        button_reset();
-};
+}
 
 
 float DOOR_START_OPEN = 1;
@@ -820,7 +829,7 @@ void door_blocked()
                        door_go_down ();
        }
 */
-};
+}
 
 
 void door_hit_top()
@@ -838,14 +847,14 @@ void door_hit_top()
                self.think = door_rotating_go_down;
        }
        self.nextthink = self.ltime + self.wait;
-};
+}
 
 void door_hit_bottom()
 {
        if (self.noise1 != "")
                sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
        self.state = STATE_BOTTOM;
-};
+}
 
 void door_go_down()
 {
@@ -859,7 +868,7 @@ void door_go_down()
 
        self.state = STATE_DOWN;
        SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
-};
+}
 
 void door_go_up()
 {
@@ -882,7 +891,7 @@ void door_go_up()
        self.message = "";
        SUB_UseTargets();
        self.message = oldmessage;
-};
+}
 
 
 
@@ -903,53 +912,45 @@ float door_check_keys(void) {
        else
                door = self;
        
-       if (door.spawnflags & (SPAWNFLAGS_GOLD_KEY | SPAWNFLAGS_SILVER_KEY)) {
-               // this door require a key
-               // only a player can have a key
-               if (other.classname != "player")
-                       return FALSE;
-               
-               // check gold key
-               if (self.owner.spawnflags & SPAWNFLAGS_GOLD_KEY) {
-                       if (!(other.itemkeys & KEYS_GOLD_KEY)) {
-                               if (other.key_door_messagetime <= time) {
-                                       play2(other, "misc/talk.wav");
-                                       centerprint(other, "You don't have the gold key!");
-                                       other.key_door_messagetime = time + 2;
-                               }
-                               return FALSE;
-                       } else {
-                               self.owner.spawnflags &~= SPAWNFLAGS_GOLD_KEY;
-                       }
+       // no key needed
+       if not(door.itemkeys)
+               return TRUE;
+
+       // this door require a key
+       // only a player can have a key
+       if (other.classname != "player")
+               return FALSE;
+       
+       if (item_keys_usekey(door, other)) {
+               // some keys were used
+               if (other.key_door_messagetime <= time) {
+                       play2(other, "misc/talk.wav");
+                       centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
                }
-                       
-               // check silver key
-               if (self.owner.spawnflags & SPAWNFLAGS_SILVER_KEY) {
-                       if (!(other.itemkeys & KEYS_SILVER_KEY)) {
-                               if (other.key_door_messagetime <= time) {
-                                       play2(other, "misc/talk.wav");
-                                       centerprint(other, "You don't have the silver key!");
-                                       other.key_door_messagetime = time + 2;
-                               }
-                               return FALSE;
-                       } else {
-                               self.owner.spawnflags &~= SPAWNFLAGS_SILVER_KEY;
-                       }
+       } else {
+               // no keys were used
+               if (other.key_door_messagetime <= time) {
+                       play2(other, "misc/talk.wav");
+                       centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
+                       other.key_door_messagetime = time + 2;
                }
-               
+       }
+
+       if (door.itemkeys) {
                // door is now unlocked
                play2(other, "misc/talk.wav");
                centerprint(other, "Door unlocked!");
-       }
-       
-       return TRUE;
+               return TRUE;
+       } else
+               return FALSE;
 }
 
 
 void door_fire()
 {
-       local entity    oself;
-       local entity    starte;
+       entity  oself;
+       entity  starte;
 
        if (self.owner != self)
                objerror ("door_fire: self.owner != self");
@@ -1003,12 +1004,12 @@ void door_fire()
                self = self.enemy;
        } while ( (self != starte) && (self != world) );
        self = oself;
-};
+}
 
 
 void door_use()
 {
-       local entity oself;
+       entity oself;
 
        //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
        
@@ -1019,7 +1020,7 @@ void door_use()
                door_fire ();
                self = oself;
        }
-};
+}
 
 
 void door_trigger_touch()
@@ -1041,18 +1042,18 @@ void door_trigger_touch()
 
        self = self.owner;
        door_use ();
-};
+}
 
 
 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
-       local entity oself;
+       entity oself;
        if(self.spawnflags & DOOR_NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
        self.health = self.health - damage;
        
-       if (self.spawnflags & SPAWNFLAGS_GOLD_KEY || self.spawnflags & SPAWNFLAGS_SILVER_KEY) {
+       if (self.itemkeys) {
                // don't allow opening doors through damage if keys are required
                return;
        }
@@ -1066,7 +1067,7 @@ void door_damage(entity inflictor, entity attacker, float damage, float deathtyp
                door_use ();
                self = oself;
        }
-};
+}
 
 
 /*
@@ -1091,7 +1092,7 @@ void door_touch()
                        centerprint (other, self.owner.message);
                play2(other, "misc/talk.wav");
        }
-};
+}
 
 
 void door_generic_plat_blocked()
@@ -1131,7 +1132,7 @@ void door_generic_plat_blocked()
                        door_rotating_go_down ();
        }
 */
-};
+}
 
 
 void door_rotating_hit_top()
@@ -1143,7 +1144,7 @@ void door_rotating_hit_top()
                return;         // don't come down automatically
        self.think = door_rotating_go_down;
        self.nextthink = self.ltime + self.wait;
-};
+}
 
 void door_rotating_hit_bottom()
 {
@@ -1155,7 +1156,7 @@ void door_rotating_hit_bottom()
                self.lip = 0;
        }
        self.state = STATE_BOTTOM;
-};
+}
 
 void door_rotating_go_down()
 {
@@ -1169,7 +1170,7 @@ void door_rotating_go_down()
 
        self.state = STATE_DOWN;
        SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
-};
+}
 
 void door_rotating_go_up()
 {
@@ -1191,7 +1192,7 @@ void door_rotating_go_up()
        self.message = "";
        SUB_UseTargets();
        self.message = oldmessage;
-};
+}
 
 
 
@@ -1207,8 +1208,8 @@ SPAWNING FUNCTIONS
 
 entity spawn_field(vector fmins, vector fmaxs)
 {
-       local entity    trigger;
-       local   vector  t1, t2;
+       entity  trigger;
+       vector  t1, t2;
 
        trigger = spawn();
        trigger.classname = "doortriggerfield";
@@ -1221,26 +1222,34 @@ entity spawn_field(vector fmins, vector fmaxs)
        t2 = fmaxs;
        setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
        return (trigger);
-};
+}
 
 
-float EntitiesTouching(entity e1, entity e2)
+entity LinkDoors_nextent(entity cur, entity near, entity pass)
 {
-       if (e1.absmin_x > e2.absmax_x)
+       while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
+       {
+       }
+       return cur;
+}
+
+float LinkDoors_isconnected(entity e1, entity e2, entity pass)
+{
+       float DELTA = 4;
+       if (e1.absmin_x > e2.absmax_x + DELTA)
                return FALSE;
-       if (e1.absmin_y > e2.absmax_y)
+       if (e1.absmin_y > e2.absmax_y + DELTA)
                return FALSE;
-       if (e1.absmin_z > e2.absmax_z)
+       if (e1.absmin_z > e2.absmax_z + DELTA)
                return FALSE;
-       if (e1.absmax_x < e2.absmin_x)
+       if (e2.absmin_x > e1.absmax_x + DELTA)
                return FALSE;
-       if (e1.absmax_y < e2.absmin_y)
+       if (e2.absmin_y > e1.absmax_y + DELTA)
                return FALSE;
-       if (e1.absmax_z < e2.absmin_z)
+       if (e2.absmin_z > e1.absmax_z + DELTA)
                return FALSE;
        return TRUE;
-};
-
+}
 
 /*
 =============
@@ -1251,8 +1260,8 @@ LinkDoors
 */
 void LinkDoors()
 {
-       local entity    t, starte;
-       local vector    cmins, cmaxs;
+       entity  t;
+       vector  cmins, cmaxs;
 
        if (self.enemy)
                return;         // already linked by another door
@@ -1271,69 +1280,71 @@ void LinkDoors()
                return;         // don't want to link this door
        }
 
-       cmins = self.absmin;
-       cmaxs = self.absmax;
-
-       starte = self;
-       t = self;
+       FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
 
-       do
+       // set owner, and make a loop of the chain
+       dprint("LinkDoors: linking doors:");
+       for(t = self; ; t = t.enemy)
        {
-               self.owner = starte;                    // master door
-
-               if (self.health)
-                       starte.health = self.health;
-               IFTARGETED
-                       starte.targetname = self.targetname;
-               if (self.message != "")
-                       starte.message = self.message;
-
-               t = find(t, classname, self.classname);
-               if (!t)
+               dprint(" ", etos(t));
+               t.owner = self;
+               if(t.enemy == world)
                {
-                       self.enemy = starte;            // make the chain a loop
-
-               // shootable, or triggered doors just needed the owner/enemy links,
-               // they don't spawn a field
-
-                       self = self.owner;
+                       t.enemy = self;
+                       break;
+               }
+       }
+       dprint("\n");
 
-                       if (self.health)
-                               return;
-                       IFTARGETED
-                               return;
-                       if (self.items)
-                               return;
+       // collect health, targetname, message, size
+       cmins = self.absmin;
+       cmaxs = self.absmax;
+       for(t = self; ; t = t.enemy)
+       {
+               if(t.health && !self.health)
+                       self.health = t.health;
+               if((t.targetname != "") && (self.targetname == ""))
+                       self.targetname = t.targetname;
+               if((t.message != "") && (self.message == ""))
+                       self.message = t.message;
+               if (t.absmin_x < cmins_x)
+                       cmins_x = t.absmin_x;
+               if (t.absmin_y < cmins_y)
+                       cmins_y = t.absmin_y;
+               if (t.absmin_z < cmins_z)
+                       cmins_z = t.absmin_z;
+               if (t.absmax_x > cmaxs_x)
+                       cmaxs_x = t.absmax_x;
+               if (t.absmax_y > cmaxs_y)
+                       cmaxs_y = t.absmax_y;
+               if (t.absmax_z > cmaxs_z)
+                       cmaxs_z = t.absmax_z;
+               if(t.enemy == self)
+                       break;
+       }
 
-                       self.owner.trigger_field = spawn_field(cmins, cmaxs);
+       // distribute health, targetname, message
+       for(t = self; t; t = t.enemy)
+       {
+               t.health = self.health;
+               t.targetname = self.targetname;
+               t.message = self.message;
+               if(t.enemy == self)
+                       break;
+       }
 
-                       return;
-               }
+       // shootable, or triggered doors just needed the owner/enemy links,
+       // they don't spawn a field
 
-               if (EntitiesTouching(self,t))
-               {
-                       if (t.enemy)
-                               objerror ("cross connected doors");
-
-                       self.enemy = t;
-                       self = t;
-
-                       if (t.absmin_x < cmins_x)
-                               cmins_x = t.absmin_x;
-                       if (t.absmin_y < cmins_y)
-                               cmins_y = t.absmin_y;
-                       if (t.absmin_z < cmins_z)
-                               cmins_z = t.absmin_z;
-                       if (t.absmax_x > cmaxs_x)
-                               cmaxs_x = t.absmax_x;
-                       if (t.absmax_y > cmaxs_y)
-                               cmaxs_y = t.absmax_y;
-                       if (t.absmax_z > cmaxs_z)
-                               cmaxs_z = t.absmax_z;
-               }
-       } while (1 );
+       if (self.health)
+               return;
+       IFTARGETED
+               return;
+       if (self.items)
+               return;
 
-};
+       self.trigger_field = spawn_field(cmins, cmaxs);
+}
 
 
 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
@@ -1377,15 +1388,21 @@ void door_reset()
        setorigin(self, self.pos1);
        self.velocity = '0 0 0';
        self.state = STATE_BOTTOM;
-       self.think = SUB_Null;
+       self.think = func_null;
+       self.nextthink = 0;
 }
 
+// spawnflags require key (for now only func_door)
+#define SPAWNFLAGS_GOLD_KEY 8
+#define SPAWNFLAGS_SILVER_KEY 16
 void spawnfunc_func_door()
 {
-       //dprint("spawnfunc_func_door() spawnflags=", ftos(self.spawnflags));
-       //dprint(", gold_key=", ftos(self.spawnflags & SPAWNFLAGS_GOLD_KEY));
-       //dprint(", silver_key=", ftos(self.spawnflags & SPAWNFLAGS_SILVER_KEY), "\n");
-
+       // Quake 1 keys compatibility
+       if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(0);
+       if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
+               self.itemkeys |= ITEM_KEY_BIT(1);
+               
        //if (!self.deathtype) // map makers can override this
        //      self.deathtype = " got in the way";
        SetMovedir ();
@@ -1403,9 +1420,9 @@ void spawnfunc_func_door()
        // if(self.spawnflags & 8)
        //      self.dmg = 10000;
 
-    if(self.dmg && (!self.message))
+    if(self.dmg && (self.message == ""))
                self.message = "was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
 
        if (self.sounds > 0)
@@ -1449,7 +1466,7 @@ void spawnfunc_func_door()
        InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
 
        self.reset = door_reset;
-};
+}
 
 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
 if two doors touch, they are assumed to be connected and operate as a unit.
@@ -1484,7 +1501,8 @@ void door_rotating_reset()
        self.angles = self.pos1;
        self.avelocity = '0 0 0';
        self.state = STATE_BOTTOM;
-       self.think = SUB_Null;
+       self.think = func_null;
+       self.nextthink = 0;
 }
 
 void door_rotating_init_startopen()
@@ -1528,9 +1546,9 @@ void spawnfunc_func_door_rotating()
     if(self.spawnflags & 8)
         self.dmg = 10000;
 
-    if(self.dmg && (!self.message))
+    if(self.dmg && (self.message == ""))
                self.message = "was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
 
     if (self.sounds > 0)
@@ -1573,7 +1591,7 @@ void spawnfunc_func_door_rotating()
        InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
 
        self.reset = door_rotating_reset;
-};
+}
 
 /*
 =============================================================================
@@ -1597,10 +1615,9 @@ float SECRET_1ST_DOWN = 4;               // 1st move is down from arrow
 float SECRET_NO_SHOOT = 8;             // only opened by trigger
 float SECRET_YES_SHOOT = 16;   // shootable even if targeted
 
-
 void fd_secret_use()
 {
-       local float temp;
+       float temp;
        string message_save;
 
        self.health = 10000;
@@ -1646,7 +1663,12 @@ void fd_secret_use()
        SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
        if (self.noise2 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
-};
+}
+
+void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       fd_secret_use();
+}
 
 // Wait after first movement...
 void fd_secret_move1()
@@ -1655,7 +1677,7 @@ void fd_secret_move1()
        self.think = fd_secret_move2;
        if (self.noise3 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
-};
+}
 
 // Start moving sideways w/sound...
 void fd_secret_move2()
@@ -1663,7 +1685,7 @@ void fd_secret_move2()
        if (self.noise2 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
        SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
-};
+}
 
 // Wait here until time to go back...
 void fd_secret_move3()
@@ -1675,7 +1697,7 @@ void fd_secret_move3()
                self.nextthink = self.ltime + self.wait;
                self.think = fd_secret_move4;
        }
-};
+}
 
 // Move backward...
 void fd_secret_move4()
@@ -1683,7 +1705,7 @@ void fd_secret_move4()
        if (self.noise2 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
        SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
-};
+}
 
 // Wait 1 second...
 void fd_secret_move5()
@@ -1692,14 +1714,14 @@ void fd_secret_move5()
        self.think = fd_secret_move6;
        if (self.noise3 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
-};
+}
 
 void fd_secret_move6()
 {
        if (self.noise2 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
        SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
-};
+}
 
 void fd_secret_done()
 {
@@ -1711,7 +1733,7 @@ void fd_secret_done()
        }
        if (self.noise3 != "")
                sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
-};
+}
 
 void secret_blocked()
 {
@@ -1719,7 +1741,7 @@ void secret_blocked()
                return;
        self.attack_finished_single = time + 0.5;
        //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-};
+}
 
 /*
 ==============
@@ -1743,7 +1765,7 @@ void secret_touch()
                        centerprint (other, self.message);
                play2(other, "misc/talk.wav");
        }
-};
+}
 
 void secret_reset()
 {
@@ -1753,7 +1775,8 @@ void secret_reset()
                self.takedamage = DAMAGE_YES;
        }
        setorigin(self, self.oldorigin);
-       self.think = SUB_Null;
+       self.think = func_null;
+       self.nextthink = 0;
 }
 
 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
@@ -1803,7 +1826,7 @@ void spawnfunc_func_door_secret()
        {
                self.health = 10000;
                self.takedamage = DAMAGE_YES;
-               self.event_damage = fd_secret_use;
+               self.event_damage = fd_secret_damage;
        }
        self.oldorigin = self.origin;
        if (!self.wait)
@@ -1811,7 +1834,7 @@ void spawnfunc_func_door_secret()
 
        self.reset = secret_reset;
        secret_reset();
-};
+}
 
 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
@@ -1826,7 +1849,7 @@ dmgtime: See above.
 
 void func_fourier_controller_think()
 {
-       local vector v;
+       vector v;
        float n, i, t;
 
        self.nextthink = time + 0.1;
@@ -1851,11 +1874,11 @@ void func_fourier_controller_think()
        if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
                // * 10 so it will arrive in 0.1 sec
                self.owner.velocity = (v - self.owner.origin) * 10;
-};
+}
 
 void spawnfunc_func_fourier()
 {
-       local entity controller;
+       entity controller;
        if (self.noise != "")
        {
                precache_sound(self.noise);
@@ -1870,9 +1893,9 @@ void spawnfunc_func_fourier()
        self.cnt = 360 / self.speed;
 
        self.blocked = generic_plat_blocked;
-       if(self.dmg & (!self.message))
+       if(self.dmg && (self.message == ""))
                self.message = " was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message2 == ""))
                self.message2 = "was squished by";
        if(self.dmg && (!self.dmgtime))
                self.dmgtime = 0.25;
@@ -1893,13 +1916,13 @@ void spawnfunc_func_fourier()
        controller.nextthink = time + 1;
        controller.think = func_fourier_controller_think;
        self.nextthink = self.ltime + 999999999;
-       self.think = SUB_Null;
+       self.think = SUB_NullThink; // for PushMove
 
        // Savage: Reduce bandwith, critical on e.g. nexdm02
        self.effects |= EF_LOWPRECISION;
 
        // TODO make a reset function for this one
-};
+}
 
 // reusing some fields havocbots declared
 .entity wp00, wp01, wp02, wp03;
@@ -1990,9 +2013,9 @@ void func_vectormamamam_findtarget()
        if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
                objerror("No reference entity found, so there is nothing to move. Aborting.");
 
-       self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
+       self.destvec = self.origin - func_vectormamamam_origin(self, 0);
 
-       local entity controller;
+       entity controller;
        controller = spawn();
        controller.classname = "func_vectormamamam_controller";
        controller.owner = self;
@@ -2033,9 +2056,9 @@ void spawnfunc_func_vectormamamam()
                self.target4normal = normalize(self.target4normal);
 
        self.blocked = generic_plat_blocked;
-       if(self.dmg & (!self.message))
+       if(self.dmg && (self.message == ""))
                self.message = " was squished";
-    if(self.dmg && (!self.message2))
+    if(self.dmg && (self.message == ""))
                self.message2 = "was squished by";
        if(self.dmg && (!self.dmgtime))
                self.dmgtime = 0.25;
@@ -2049,7 +2072,7 @@ void spawnfunc_func_vectormamamam()
 
        // wait for targets to spawn
        self.nextthink = self.ltime + 999999999;
-       self.think = SUB_Null;
+       self.think = SUB_NullThink; // for PushMove
 
        // Savage: Reduce bandwith, critical on e.g. nexdm02
        self.effects |= EF_LOWPRECISION;
@@ -2058,3 +2081,91 @@ void spawnfunc_func_vectormamamam()
 
        InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
 }
+
+void conveyor_think()
+{
+       entity e;
+
+       // set myself as current conveyor where possible
+       for(e = world; (e = findentity(e, conveyor, self)); )
+               e.conveyor = world;
+
+       if(self.state)
+       {
+               for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+                       if(!e.conveyor.state)
+                               if(isPushable(e))
+                               {
+                                       vector emin = e.absmin;
+                                       vector emax = e.absmax;
+                                       if(self.solid == SOLID_BSP)
+                                       {
+                                               emin -= '1 1 1';
+                                               emax += '1 1 1';
+                                       }
+                                       if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+                                               if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+                                                       e.conveyor = self;
+                               }
+
+               for(e = world; (e = findentity(e, conveyor, self)); )
+               {
+                       if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
+                               continue; // done in SV_PlayerPhysics
+
+                       setorigin(e, e.origin + self.movedir * sys_frametime);
+                       move_out_of_solid(e);
+                       UpdateCSQCProjectile(e);
+                       /*
+                       // stupid conveyor code
+                       tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
+                       if(trace_fraction > 0)
+                               setorigin(e, trace_endpos);
+                       */
+               }
+       }
+
+       self.nextthink = time;
+}
+
+void conveyor_use()
+{
+       self.state = !self.state;
+}
+
+void conveyor_reset()
+{
+       self.state = (self.spawnflags & 1);
+}
+
+void conveyor_init()
+{
+       if (!self.speed)
+               self.speed = 200;
+       self.movedir = self.movedir * self.speed;
+       self.think = conveyor_think;
+       self.nextthink = time;
+       IFTARGETED
+       {
+               self.use = conveyor_use;
+               self.reset = conveyor_reset;
+               conveyor_reset();
+       }
+       else
+               self.state = 1;
+}
+
+void spawnfunc_trigger_conveyor()
+{
+       SetMovedir();
+       EXACTTRIGGER_INIT;
+       conveyor_init();
+}
+
+void spawnfunc_func_conveyor()
+{
+       SetMovedir();
+       InitMovingBrushTrigger();
+       self.movetype = MOVETYPE_NONE;
+       conveyor_init();
+}