+vector trigger_push_velocity_calculatevelocity(entity this, vector org, entity tgt, float speed, float count, entity pushed_entity, bool already_pushed)
+{
+ bool is_playerdir_xy = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_XY);
+ bool is_add_xy = boolean(this.spawnflags & PUSH_VELOCITY_ADD_XY);
+ bool is_playerdir_z = boolean(this.spawnflags & PUSH_VELOCITY_PLAYERDIR_Z);
+ bool is_add_z = boolean(this.spawnflags & PUSH_VELOCITY_ADD_Z);
+ bool is_bidirectional_xy = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_XY);
+ bool is_bidirectional_z = boolean(this.spawnflags & PUSH_VELOCITY_BIDIRECTIONAL_Z);
+ bool is_clamp_negative_adds = boolean(this.spawnflags & PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS);
+
+ vector sdir = normalize(vec2(pushed_entity.velocity));
+ float zdir = pushed_entity.velocity.z;
+ if(zdir != 0) zdir = copysign(1, zdir);
+
+ vector vs_tgt = '0 0 0';
+ float vz_tgt = 0;
+ if (!is_playerdir_xy || !is_playerdir_z)
+ {
+ vector vel_tgt = trigger_push_calculatevelocity(org, tgt, 0, pushed_entity);
+ vs_tgt = vec2(vel_tgt);
+ vz_tgt = vel_tgt.z;
+
+ // bidirectional jump pads do not play nicely with xonotic's jump pad targets
+ if (is_bidirectional_xy)
+ {
+ if (normalize(vs_tgt) * sdir < 0)
+ {
+ vs_tgt *= -1;
+ }
+ }
+
+ if (is_bidirectional_z)
+ {
+ if (signbit(vz_tgt) != signbit(zdir))
+ {
+ vz_tgt *= -1;
+ }
+ }
+ }
+
+ vector vs;
+ if (is_playerdir_xy)
+ {
+ vs = sdir * speed;
+ }
+ else
+ {
+ vs = vs_tgt;
+ }
+
+ float vz;
+ if (is_playerdir_z)
+ {
+ vz = zdir * count;
+ }
+ else
+ {
+ vz = vz_tgt;
+ }
+
+ if (is_add_xy)
+ {
+ vector vs_add = vec2(pushed_entity.velocity);
+ if (already_pushed)
+ {
+ vs = vs_add;
+ }
+ else
+ {
+ vs += vs_add;
+
+ if (is_clamp_negative_adds)
+ {
+ if ((normalize(vs) * sdir) < 0)
+ {
+ vs = '0 0 0';
+ }
+ }
+ }
+ }
+
+ if (is_add_z)
+ {
+ float vz_add = pushed_entity.velocity.z;
+ if (already_pushed)
+ {
+ vz = vz_add;
+ }
+ else
+ {
+ vz += vz_add;
+
+ if (is_clamp_negative_adds)
+ {
+ if (signbit(vz) != signbit(zdir))
+ {
+ vz = 0;
+ }
+ }
+ }
+ }
+
+ return vs + '0 0 1' * vz;
+}
+
+#ifdef SVQC
+void trigger_push_velocity_think(entity this)
+{
+ bool found = false;
+ IL_EACH(g_moveables, it.last_pushed == this,
+ {
+ if(!WarpZoneLib_ExactTrigger_Touch(this, it, false))
+ it.last_pushed = NULL;
+ else
+ found = true;
+ });
+
+ if(found)
+ this.nextthink = time;
+ else
+ setthink(this, func_null);
+}
+#endif
+
+bool jumppad_push(entity this, entity targ, bool is_velocity_pad)