]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Juhu/velocity_pads
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 2 Jun 2023 13:43:25 +0000 (15:43 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 2 Jun 2023 13:54:21 +0000 (15:54 +0200)
make trigger_push_velocity code compatible with the latest ExactTrigger changes

1  2 
qcsrc/common/mapobjects/trigger/jumppads.qc
qcsrc/common/mapobjects/trigger/jumppads.qh
qcsrc/common/util.qc

index 8c7ca3e9c48894d54c250854f0260208283d6cf4,0ff3ba0a99c58d05de97458666ba703825b9d998..1f91619ed61c9ebb08037841daff4f3d27082af7
@@@ -14,7 -14,6 +14,7 @@@ void trigger_push_use(entity this, enti
  #endif
  
  REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH)
 +REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH_VELOCITY)
  REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH)
  
  /*
@@@ -40,6 -39,14 +40,14 @@@ vector trigger_push_calculatevelocity(v
        if(pushed_entity && pushed_entity.gravity)
                grav *= pushed_entity.gravity;
  
+       // Q3 has frametime-dependent gravity, but its trigger_push velocity calculation doesn't account for that.
+       // This discrepancy can be simulated accurately which ensures that all entities will arrive
+       // where they would in Q3 with gravity 800 at 125fps, even if entity-specific gravity is applied.
+       // This can be hard-coded because we don't support the Q3 world.gravity field at this time.
+       // See physicsCPMA.cfg for maths and test results.
+       if (Q3COMPAT_COMMON)
+               grav /= 750/800; // exact float, unlike 800/750
        zdist = torg.z - org.z;
        sdist = vlen(torg - org - zdist * '0 0 1');
        sdir = normalize(torg - org - zdist * '0 0 1');
        return sdir * vs + '0 0 1' * vz;
  }
  
 -bool jumppad_push(entity this, entity targ)
 +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;
 +}
 +
 +bool jumppad_push(entity this, entity targ, bool is_velocity_pad)
  {
        if (!isPushable(targ))
                return false;
  
        vector org = targ.origin;
  
-       if(STAT(Q3COMPAT))
-       {
-               org.z += targ.mins_z;
-               org.z += 1; // off by 1!
-       }
+       if(Q3COMPAT_COMMON || this.spawnflags & PUSH_STATIC)
+               org = (this.absmin + this.absmax) * 0.5;
  
 +      bool already_pushed = false;
 +      if(is_velocity_pad) // remember velocity jump pads
 +      {
 +              if(this == targ.last_pushed || (targ.last_pushed && !STAT(Q3COMPAT, targ))) // if q3compat is active overwrite last stored jump pad, otherwise ignore
 +              {
 +                      already_pushed = true;
 +              }
 +              else
 +              {
 +                      targ.last_pushed = this; // may be briefly out of sync between client and server if client prediction is toggled
 +              }
 +      }
 +
        if(this.enemy)
        {
 -              targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
 +              if(!is_velocity_pad)
 +              {
 +                      targ.velocity = trigger_push_calculatevelocity(org, this.enemy, this.height, targ);
 +              }
 +              else
 +              {
 +                      targ.velocity = trigger_push_velocity_calculatevelocity(this, org, this.enemy, this.speed, this.count, targ, already_pushed);
 +              }
        }
        else if(this.target && this.target != "")
        {
                        else
                                RandomSelection_AddEnt(e, 1, 1);
                }
 -              targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
 +              if(!is_velocity_pad)
 +              {
 +                      targ.velocity = trigger_push_calculatevelocity(org, RandomSelection_chosen_ent, this.height, targ);
 +              }
 +              else
 +              {
 +                      targ.velocity = trigger_push_velocity_calculatevelocity(this, org, RandomSelection_chosen_ent, this.speed, this.count, targ, already_pushed);
 +              }
        }
        else
        {
 -              targ.velocity = this.movedir;
 +              if(!is_velocity_pad)
 +              {
 +                      targ.velocity = this.movedir;
 +              }
 +              else
 +              {
 +#ifdef SVQC
 +                      objerror (this, "Jumppad with no target");
 +#endif
 +                      return false;
 +              }
        }
  
 -      UNSET_ONGROUND(targ);
 +      if(!is_velocity_pad) UNSET_ONGROUND(targ);
  
  #ifdef CSQC
        if (targ.flags & FL_PROJECTILE)
  
                // prevent sound spam when a player hits the jumppad more than once
                // or when a dead player gets stuck in the jumppad for some reason
 -              if(this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
 +              if(!already_pushed && this.pushltime < time && !(IS_DEAD(targ) && targ.velocity == '0 0 0'))
                {
                        // flash when activated
                        Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1);
@@@ -411,7 -273,7 +416,7 @@@ void trigger_push_touch(entity this, en
  
        EXACTTRIGGER_TOUCH(this, toucher);
  
 -      noref bool success = jumppad_push(this, toucher);
 +      noref bool success = jumppad_push(this, toucher, false);
  
  #ifdef SVQC
        if (success && (this.spawnflags & PUSH_ONCE))
  #endif
  }
  
 +void trigger_push_velocity_touch(entity this, entity toucher)
 +{
 +      if (this.active == ACTIVE_NOT)
 +              return;
 +
 +      if(this.team && DIFF_TEAM(this, toucher))
 +              return;
 +
 +      EXACTTRIGGER_TOUCH(this, toucher);
 +
 +      jumppad_push(this, toucher, true);
 +}
 +
  #ifdef SVQC
  void trigger_push_link(entity this);
  void trigger_push_updatelink(entity this);
@@@ -731,21 -580,6 +736,21 @@@ float trigger_push_send(entity this, en
        return true;
  }
  
 +float trigger_push_velocity_send(entity this, entity to, float sf)
 +{
 +      WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH_VELOCITY);
 +
 +      WriteByte(MSG_ENTITY, this.team);
 +      WriteInt24_t(MSG_ENTITY, this.spawnflags);
 +      WriteByte(MSG_ENTITY, this.active);
 +      WriteCoord(MSG_ENTITY, this.speed);
 +      WriteCoord(MSG_ENTITY, this.count);
 +
 +      trigger_common_write(this, true);
 +
 +      return true;
 +}
 +
  void trigger_push_updatelink(entity this)
  {
        this.SendFlags |= SF_TRIGGER_INIT;
@@@ -756,11 -590,6 +761,11 @@@ void trigger_push_link(entity this
        trigger_link(this, trigger_push_send);
  }
  
 +void trigger_push_velocity_link(entity this)
 +{
 +      trigger_link(this, trigger_push_velocity_send);
 +}
 +
  /*
   * ENTITY PARAMETERS:
   *
@@@ -777,8 -606,8 +782,8 @@@ spawnfunc(trigger_push
  {
        SetMovedir(this);
  
-       trigger_init(this);
+       EXACTTRIGGER_INIT;
+       BITSET_ASSIGN(this.effects, EF_NODEPTHTEST);
        this.active = ACTIVE_ACTIVE;
        this.use = trigger_push_use;
        settouch(this, trigger_push_touch);
        InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET);
  }
  
-       trigger_init(this);
 +/*
 + * ENTITY PARAMETERS:
 + *
 + *   target:  this points to the target_position to which the player will jump.
 + *   speed:   XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
 + *   count:   Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
 + */
 +spawnfunc(trigger_push_velocity)
 +{
++      EXACTTRIGGER_INIT;
++      BITSET_ASSIGN(this.effects, EF_NODEPTHTEST);
 +
 +      this.active = ACTIVE_ACTIVE;
 +      this.use = trigger_push_use;
 +      settouch(this, trigger_push_velocity_touch);
 +
 +      // normal push setup
 +      if (!this.noise)
 +              this.noise = "misc/jumppad.wav";
 +      precache_sound (this.noise);
 +
 +      trigger_push_velocity_link(this); // link it now
 +}
 +
  
  bool target_push_send(entity this, entity to, float sf)
  {
@@@ -842,7 -648,7 +848,7 @@@ void target_push_use(entity this, entit
        if(trigger.classname == "trigger_push" || trigger == this)
                return; // WTF, why is this a thing
  
 -      jumppad_push(this, actor);
 +      jumppad_push(this, actor, false);
  }
  
  void target_push_link(entity this)
@@@ -904,24 -710,6 +910,24 @@@ NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, boo
        return true;
  }
  
 +NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH_VELOCITY, bool isnew)
 +{
 +      int mytm = ReadByte(); if(mytm) { this.team = mytm - 1; }
 +      this.spawnflags = ReadInt24_t();
 +      this.active = ReadByte();
 +      this.speed = ReadCoord();
 +      this.count = ReadCoord();
 +
 +      trigger_common_read(this, true);
 +
 +      this.entremove = trigger_remove_generic;
 +      this.solid = SOLID_TRIGGER;
 +      settouch(this, trigger_push_velocity_touch);
 +      this.move_time = time;
 +
 +      return true;
 +}
 +
  void target_push_remove(entity this)
  {
        // strfree(this.classname);
index 7b3a9caede6f53af7634cc303c45de328a16ed0f,c994bc61085b5971ee3c32aa7ea19e9b8d81e56a..7146cc52cb057a3d4d12f19c5664017d0aa71a8f
@@@ -3,15 -3,8 +3,16 @@@
  
  const int PUSH_ONCE = BIT(0); // legacy, deactivate with relay instead
  const int PUSH_SILENT = BIT(1); // not used?
+ const int PUSH_STATIC = BIT(12); // xonotic-only, Q3 already behaves like this by default
  
 +#define PUSH_VELOCITY_PLAYERDIR_XY        BIT(0)
 +#define PUSH_VELOCITY_ADD_XY              BIT(1)
 +#define PUSH_VELOCITY_PLAYERDIR_Z         BIT(2)
 +#define PUSH_VELOCITY_ADD_Z               BIT(3)
 +#define PUSH_VELOCITY_BIDIRECTIONAL_XY    BIT(4)
 +#define PUSH_VELOCITY_BIDIRECTIONAL_Z     BIT(5)
 +#define PUSH_VELOCITY_CLAMP_NEGATIVE_ADDS BIT(6)
 +
  IntrusiveList g_jumppads;
  STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); }
  
@@@ -19,8 -12,6 +20,8 @@@
  .bool istypefrag;
  .float height;
  
 +.entity last_pushed;
 +
  const int NUM_JUMPPADSUSED = 3;
  .float jumppadcount;
  .entity jumppadsused[NUM_JUMPPADSUSED];
@@@ -53,7 -44,7 +54,7 @@@ bool trigger_push_test(entity this, ent
  void trigger_push_findtarget(entity this);
  
  /*
 - * ENTITY PARAMETERS:
 + * ENTITY PARAMETERS trigger_push:
   *
   *   target:  target of jump
   *   height:  the absolute value is the height of the highest point of the jump
   *            values to target a point on the ceiling.
   *   movedir: if target is not set, this * speed * 10 is the velocity to be reached.
   */
 +
 +/*
 + * ENTITY PARAMETERS trigger_push_velocity:
 + *
 + *   target:  this points to the target_position to which the player will jump.
 + *   speed:   XY speed for player-directional velocity pads - either sets or adds to the player's horizontal velocity.
 + *   count:   Z speed for player-directional velocity pads - either sets or adds to the player's vertical velocity.
 + */
  #ifdef SVQC
  spawnfunc(trigger_push);
 +spawnfunc(trigger_push_velocity);
  
  spawnfunc(target_push);
  spawnfunc(info_notnull);
diff --combined qcsrc/common/util.qc
index 368ffd73fe3133726205c7bf609ec23314af6318,df54e3536b10acdf149037ddacc8b6c4c133da12..08bd71298b6e70e2fa2f638d8ad8270fe1c6f5f4
@@@ -372,7 -372,7 +372,7 @@@ void depthfirst(entity start, .entity u
  }
  
  #ifdef GAMEQC
- string ScoreString(int pFlags, float pValue)
+ string ScoreString(int pFlags, float pValue, int rounds_played)
  {
        string valstr;
        float l;
                valstr = (pValue < 256 ? count_ordinal(pValue) : _("N/A"));
        else if(pFlags & SFL_TIME)
                valstr = TIME_ENCODED_TOSTRING(pValue, true);
+       else if (rounds_played)
+               valstr = sprintf("%.1f", pValue / rounds_played);
        else
                valstr = ftos(pValue);
  
@@@ -1675,21 -1677,6 +1677,21 @@@ void Skeleton_SetBones(entity e
  string to_execute_next_frame;
  void execute_next_frame()
  {
-               if(WarpZoneLib_ExactTrigger_Touch(it.last_pushed, it))
 +#ifdef SVQC
 +      IL_EACH(g_moveables, it.last_pushed,
 +      {
-       if(csqcplayer.last_pushed && WarpZoneLib_ExactTrigger_Touch(csqcplayer.last_pushed, csqcplayer))
++              if(!WarpZoneLib_ExactTrigger_Touch(it.last_pushed, it, false))
 +              {
 +                      it.last_pushed = NULL;
 +              }
 +      });
 +#elif defined(CSQC)
++      if(csqcplayer.last_pushed && !WarpZoneLib_ExactTrigger_Touch(csqcplayer.last_pushed, csqcplayer, false))
 +      {
 +              csqcplayer.last_pushed = NULL;
 +      }
 +#endif
 +
        if(to_execute_next_frame)
        {
                localcmd("\n", to_execute_next_frame, "\n");