]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Mon, 29 Jun 2020 12:39:56 +0000 (22:39 +1000)
committerMario <mario.mario@y7mail.com>
Mon, 29 Jun 2020 12:39:56 +0000 (22:39 +1000)
1  2 
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/mapinfo.qh
qcsrc/common/models/all.inc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/autocvars.qh
qcsrc/server/command/common.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh

index dea0b1b0c507776efe37bed86c035b4f20e3117d,99456bd3ed03d129d2fb2ffc741e380e612f66ec..07e71132191d098057077661618b0f72ad0cf9a6
  .int lodmodelindex0;
  .int lodmodelindex1;
  .int lodmodelindex2;
 -void CSQCPlayer_LOD_Apply(entity this)
 +void CSQCPlayer_LOD_Apply(entity this, bool isplayer)
  {
 +      int detailreduction = ((isplayer) ? autocvar_cl_playerdetailreduction : autocvar_cl_modeldetailreduction);
 +
        // LOD model loading
        if(this.lodmodelindex0 != this.modelindex)
        {
        }
  
        // apply LOD
 -      if(autocvar_cl_playerdetailreduction <= 0)
 +      if(detailreduction <= 0)
        {
 -              if(autocvar_cl_playerdetailreduction <= -2)
 +              if(detailreduction <= -2)
                        this.modelindex = this.lodmodelindex2;
 -              else if(autocvar_cl_playerdetailreduction <= -1)
 +              else if(detailreduction <= -1)
                        this.modelindex = this.lodmodelindex1;
                else
                        this.modelindex = this.lodmodelindex0;
        }
        else
        {
 -              float distance = vlen(this.origin - view_origin);
 -              float f = (distance * current_viewzoom + 100.0) * autocvar_cl_playerdetailreduction;
 +              float distance = vlen(((isplayer) ? this.origin : NearestPointOnBox(this, view_origin)) - view_origin); // TODO: perhaps it should just use NearestPointOnBox all the time, player hitbox can potentially be huge
 +              float f = (distance * current_viewzoom + 100.0) * detailreduction;
                f *= 1.0 / bound(0.01, view_quality, 1);
                if(f > autocvar_cl_loddistance2)
                        this.modelindex = this.lodmodelindex2;
@@@ -284,6 -282,13 +284,13 @@@ void CSQCPlayer_ModelAppearance_Apply(e
  
        LABEL(skipforcemodels)
  
+       if((this.csqcmodel_effects & CSQCMODEL_EF_RESPAWNGHOST) && !autocvar_cl_respawn_ghosts_keepcolors)
+       {
+               this.glowmod = '0 0 0';
+               this.colormap = 0;
+               return;
+       }
        // GLOWMOD AND DEATH FADING
        if(this.colormap > 0)
                this.glowmod = colormapPaletteColor(((this.colormap >= 1024) ? this.colormap : entcs_GetClientColors(this.colormap - 1)) & 0x0F, true) * 2;
@@@ -615,7 -620,7 +622,7 @@@ void CSQCModel_Hook_PreDraw(entity this
        if((this.isplayermodel & ISPLAYER_MODEL) && this.drawmask) // this checks if it's a player MODEL!
        {
                CSQCPlayer_ModelAppearance_Apply(this, (this.isplayermodel & ISPLAYER_LOCAL));
 -              CSQCPlayer_LOD_Apply(this);
 +              CSQCPlayer_LOD_Apply(this, true);
  
                if(!isplayer)
                {
                        }
                }
        }
 +      else
 +              CSQCPlayer_LOD_Apply(this, false);
  
        CSQCModel_AutoTagIndex_Apply(this);
  
index ecdcd436d8f388cb1395937cd5ed1183c86f387c,fcdac111e401aedbbd01c54a14a82bf294179385..4b10f31bf08da0eaa70eeb033815cbe2990c8ec6
@@@ -48,7 -48,8 +48,8 @@@ void Projectile_DrawTrail(entity this, 
        if (this.traileffect)
        {
                particles_alphamin = particles_alphamax = particles_fade = sqrt(this.alpha);
-               boxparticles(particleeffectnum(Effects_from(this.traileffect)), this, from, to, this.velocity, this.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
+               entity eff = REGISTRY_GET(Effects, this.traileffect);
+               boxparticles(particleeffectnum(eff), this, from, to, this.velocity, this.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
        }
  }
  
@@@ -322,7 -323,7 +323,7 @@@ NET_HANDLE(ENT_CLIENT_PROJECTILE, bool 
                        HANDLE(SEEKER)             this.traileffect = EFFECT_SEEKER_TRAIL.m_id; break;
  
                        HANDLE(MAGE_SPIKE)         this.traileffect = EFFECT_TR_VORESPIKE.m_id; break;
 -                      HANDLE(SHAMBLER_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
 +                      HANDLE(GOLEM_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
  
                        HANDLE(RAPTORBOMB)         this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
                        HANDLE(RAPTORBOMBLET)      this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
                                this.bouncefactor = WEP_CVAR(mortar, bouncefactor);
                                this.bouncestop = WEP_CVAR(mortar, bouncestop);
                                break;
 -                      case PROJECTILE_SHAMBLER_LIGHTNING:
 +                      case PROJECTILE_GOLEM_LIGHTNING:
                                this.mins = '-8 -8 -8';
                                this.maxs = '8 8 8';
                                this.scale = 2.5;
diff --combined qcsrc/common/mapinfo.qh
index 26765660f3b5aff45bda7b671f37e09c5abc6022,4d598e88caa2346378b9295fdd7dc36c7ec59dbc..fa957ef87c9b9f7bac1ccb6fb307c3868fe44d53
@@@ -101,9 -101,10 +101,10 @@@ CLASS(Gametype, Object
  ENDCLASS(Gametype)
  
  REGISTRY(Gametypes, 24)
- #define Gametypes_from(i) _Gametypes_from(i, NULL)
  REGISTER_REGISTRY(Gametypes)
  REGISTRY_CHECK(Gametypes)
+ REGISTRY_DEFINE_GET(Gametypes, NULL)
  #define REGISTER_GAMETYPE(NAME, inst) REGISTER(Gametypes, MAPINFO_TYPE, NAME, m_id, inst)
  
  #define IS_GAMETYPE(NAME) (MapInfo_LoadedGametype == MAPINFO_TYPE_##NAME)
@@@ -556,11 -557,14 +557,11 @@@ REGISTER_GAMETYPE(KEEPAWAY, NEW(Keepawa
  CLASS(Invasion, Gametype)
      INIT(Invasion)
      {
 -        this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,true,"","pointlimit=50 teams=0 type=0",_("Survive against waves of monsters"));
 +        this.gametype_init(this, _("Invasion"),"inv","g_invasion",false,true,"","pointlimit=50 type=0",_("Survive against waves of monsters"));
      }
      METHOD(Invasion, m_parse_mapinfo, bool(string k, string v))
      {
          switch (k) {
 -            case "teams":
 -                cvar_set("g_invasion_teams", v);
 -                return true;
              case "type":
                  cvar_set("g_invasion_type", v);
                  return true;
index cfe7a010fe9ebbbf44b2181fbdf5e1aed5998777,fe5c7b93d47ac40803901a18c847c4fa45b3e389..0af124eb892cfb75f1b648c7dfafcc39ca4d858b
@@@ -104,7 -104,7 +104,7 @@@ MODEL(PROJECTILE_FLAC
  MODEL(PROJECTILE_SEEKER,                "models/tagrocket.md3");
  
  MODEL(PROJECTILE_MAGE_SPIKE,            "models/ebomb.mdl");
 -MODEL(PROJECTILE_SHAMBLER_LIGHTNING,    "models/ebomb.mdl");
 +MODEL(PROJECTILE_GOLEM_LIGHTNING,     "models/ebomb.mdl");
  
  MODEL(PROJECTILE_RAPTORBOMB,            "models/vehicles/clusterbomb.md3");
  MODEL(PROJECTILE_RAPTORBOMBLET,         "models/vehicles/bomblet.md3");
@@@ -147,7 -147,7 +147,7 @@@ MODEL(GIB_ROBO_7
  MODEL(GIB_ROBO_8,                       "models/gibs/robo8.md3");
  Model MDL_GIB_ROBO_RANDOM() {
      int i = floor(random() * 8);
-     return Models_from(MDL_GIB_ROBO_1.m_id + i);
+     return REGISTRY_GET(Models, MDL_GIB_ROBO_1.m_id + i);
  }
  
  MODEL(CASING_SHELL,                     "models/casing_shell.mdl");
@@@ -350,7 -350,7 +350,7 @@@ MODEL(9
  MODEL(10,                               "models/sprites/10.spr32");
  Model MDL_NUM(int i) {
      if ((i >= 0 && i <= 10))
-         return Models_from(MDL_0.m_id + i);
+         return REGISTRY_GET(Models, MDL_0.m_id + i);
      return MDL_Null;
  }
  
index 319b699a706372cc3aca360df4259d50dfac907c,4c2b5ff269a601b1ff502f3977f97af5552aa532..bdd8e5159a774480ccdd786ff388610be819f74c
@@@ -55,21 -55,6 +55,21 @@@ void monster_dropitem(entity this, enti
        }
  }
  
 +bool monster_facing(entity this, entity targ)
 +{
 +      // relies on target having an origin
 +      makevectors(this.angles);
 +      vector targ_org = targ.origin, my_org = this.origin;
 +      if(autocvar_g_monsters_target_infront_2d)
 +      {
 +              targ_org = vec2(targ_org);
 +              my_org = vec2(my_org);
 +      }
 +      float dot = normalize(targ_org - my_org) * v_forward;
 +
 +      return !(dot <= autocvar_g_monsters_target_infront_range);
 +}
 +
  void monster_makevectors(entity this, entity targ)
  {
        if(IS_MONSTER(this))
  // Target handling
  // ===============
  
 -bool Monster_ValidTarget(entity this, entity targ)
 +bool Monster_ValidTarget(entity this, entity targ, bool skipfacing)
  {
        // ensure we're not checking nonexistent monster/target
        if(!this || !targ) { return false; }
  
        if((targ == this)
 -      || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen
 -      || (IS_VEHICLE(targ) && !((REGISTRY_GET(Monsters, this.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
        || (time < game_starttime) // monsters do nothing before match has started
        || (targ.takedamage == DAMAGE_NO)
        || (game_stopped)
        || (this.monster_follow == targ || targ.monster_follow == this)
        || (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET))
        || (!autocvar_g_monsters_typefrag && PHYS_INPUT_BUTTON_CHAT(targ))
-       || (IS_VEHICLE(targ) && !((Monsters_from(this.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
++      || (IS_VEHICLE(targ) && !((REGISTRY_GET(Monsters, this.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
        || (SAME_TEAM(targ, this))
        || (STAT(FROZEN, targ))
        || (targ.alpha != 0 && targ.alpha < 0.5)
 +      || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen
        || (MUTATOR_CALLHOOK(MonsterValidTarget, this, targ))
        )
        {
        }
  
        vector targ_origin = ((targ.absmin + targ.absmax) * 0.5);
 -      traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this);
 +      traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this); // TODO: maybe we can rely a bit on PVS data instead?
  
        if(trace_fraction < 1 && trace_ent != targ)
                return false; // solid
  
 -      if(autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT))
 +      if(!skipfacing && (autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT)))
        if(this.enemy != targ)
        {
 -              makevectors (this.angles);
 -              float dot = normalize (targ.origin - this.origin) * v_forward;
 -
 -              if(dot <= autocvar_g_monsters_target_infront_range) { return false; }
 +              if(!monster_facing(this, targ))
 +                      return false;
        }
  
        return true; // this target is valid!
@@@ -137,27 -124,21 +137,27 @@@ entity Monster_FindTarget(entity this
        vector my_center = CENTER_OR_VIEWOFS(this);
  
        // find the closest acceptable target to pass to
 -      IL_EACH(g_monster_targets, it.monster_attack && vdist(it.origin - this.origin, <, this.target_range),
 +      IL_EACH(g_monster_targets, it.monster_attack,
        {
 -              if(Monster_ValidTarget(this, it))
 -              {
 -                      // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
 -                      vector targ_center = CENTER_OR_VIEWOFS(it);
 +              float trange = this.target_range;
 +              if(PHYS_INPUT_BUTTON_CROUCH(it))
 +                      trange *= 0.75; // TODO cvar this
 +              vector theirmid = (it.absmin + it.absmax) * 0.5;
 +              if(vdist(theirmid - this.origin, >, trange))
 +                      continue;
 +              if(!Monster_ValidTarget(this, it, false))
 +                      continue;
  
 -                      if(closest_target)
 -                      {
 -                              vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 -                              if(vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
 -                                      { closest_target = it; }
 -                      }
 -                      else { closest_target = it; }
 +              // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
 +              vector targ_center = CENTER_OR_VIEWOFS(it);
 +
 +              if(closest_target)
 +              {
 +                      vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 +                      if(vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
 +                              { closest_target = it; }
                }
 +              else { closest_target = it; }
        });
  
        return closest_target;
@@@ -172,23 -153,18 +172,23 @@@ void monster_setupcolors(entity this
        else
        {
                if(this.monster_skill <= MONSTER_SKILL_EASY)
 -                      this.colormap = 1029;
 +                      this.colormap = 1126;
                else if(this.monster_skill <= MONSTER_SKILL_MEDIUM)
 -                      this.colormap = 1027;
 +                      this.colormap = 1075;
                else if(this.monster_skill <= MONSTER_SKILL_HARD)
 -                      this.colormap = 1038;
 +                      this.colormap = 1228;
                else if(this.monster_skill <= MONSTER_SKILL_INSANE)
 -                      this.colormap = 1028;
 +                      this.colormap = 1092;
                else if(this.monster_skill <= MONSTER_SKILL_NIGHTMARE)
 -                      this.colormap = 1032;
 +                      this.colormap = 1160;
                else
                        this.colormap = 1024;
        }
 +
 +      if(this.colormap > 0)
 +              this.glowmod = colormapPaletteColor(this.colormap & 0x0F, false);
 +      else
 +              this.glowmod = '1 1 1';
  }
  
  void monster_changeteam(entity this, int newteam)
  .void(entity) monster_delayedfunc;
  void Monster_Delay_Action(entity this)
  {
 -      if(Monster_ValidTarget(this.owner, this.owner.enemy))
 +      // TODO: maybe do check for facing here
 +      if(Monster_ValidTarget(this.owner, this.owner.enemy, false))
        {
                monster_makevectors(this.owner, this.owner.enemy);
                this.monster_delayedfunc(this.owner);
@@@ -285,7 -260,7 +285,7 @@@ void Monster_Sound_Precache(string f
  
  void Monster_Sounds_Precache(entity this)
  {
-       string m = (Monsters_from(this.monsterid)).m_model.model_str();
+       string m = (REGISTRY_GET(Monsters, this.monsterid)).m_model.model_str();
        float globhandle, n, i;
        string f;
  
@@@ -366,6 -341,7 +366,6 @@@ void Monster_Sound(entity this, .strin
        string sample = this.(samplefield);
        if (sample != "") sample = GlobalSound_sample(sample, random());
        float myscale = ((this.scale) ? this.scale : 1); // safety net
 -      // TODO: change volume depending on size too?
        sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, 100 / myscale, 0);
  
        this.msound_delay = time + sound_delay;
@@@ -446,7 -422,6 +446,7 @@@ void Monster_Attack_Check(entity this, 
        if((!this || !targ)
        || (!this.monster_attackfunc)
        || (time < this.attack_finished_single[slot])
 +      || ((autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT)) && !monster_facing(this, targ))
        ) { return; }
  
        if(vdist(targ.origin - this.origin, <=, this.attack_range))
@@@ -490,16 -465,18 +490,16 @@@ void Monster_UpdateModel(entity this
        this.anim_die2   = animfixfps(this, '9 1 0.01', '0 0 0');*/
  
        // then get the real values
-       Monster mon = Monsters_from(this.monsterid);
+       Monster mon = REGISTRY_GET(Monsters, this.monsterid);
        mon.mr_anim(mon, this);
  }
  
  void Monster_Touch(entity this, entity toucher)
  {
 -      if(toucher == NULL) { return; }
 +      if(!toucher) { return; }
  
 -      if(toucher.monster_attack)
 -      if(this.enemy != toucher)
 -      if(!IS_MONSTER(toucher))
 -      if(Monster_ValidTarget(this, toucher))
 +      if(toucher.monster_attack && this.enemy != toucher && !IS_MONSTER(toucher) && time >= this.spawn_time)
 +      if(Monster_ValidTarget(this, toucher, true))
                this.enemy = toucher;
  }
  
@@@ -572,9 -549,10 +572,9 @@@ void Monster_Dead_Fade(entity this
  
  void Monster_Use(entity this, entity actor, entity trigger)
  {
 -      if(Monster_ValidTarget(this, actor)) { this.enemy = actor; }
 +      if(Monster_ValidTarget(this, actor, true)) { this.enemy = actor; }
  }
  
 -.float pass_distance;
  vector Monster_Move_Target(entity this, entity targ)
  {
        // enemy is always preferred target
                WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
  
                // cases where the enemy may have changed their state (don't need to check everything here)
 -              if((!this.enemy)
 -                      || (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
 +              if(    (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
                        || (STAT(FROZEN, this.enemy))
                        || (this.enemy.flags & FL_NOTARGET)
                        || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
                        || (this.enemy.takedamage == DAMAGE_NO)
                        || (vdist(this.origin - targ_origin, >, this.target_range))
 -                      || ((trace_fraction < 1) && (trace_ent != this.enemy)))
 +                      || ((trace_fraction < 1) && (trace_ent != this.enemy))
 +                      )
                {
                        this.enemy = NULL;
 -                      //this.pass_distance = 0;
                }
  
                if(this.enemy)
        }
  }
  
 -void Monster_CalculateVelocity(entity this, vector to, vector from, float turnrate, float movespeed)
 -{
 -      //float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
 -      //float initial_height = 0; //min(50, (targ_distance * tanh(20)));
 -      //float current_height = (initial_height * min(1, (this.pass_distance) ? (current_distance / this.pass_distance) : current_distance));
 -      //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
 -
 -      vector targpos = to;
 -#if 0
 -      if(current_height) // make sure we can actually do this arcing path
 -      {
 -              targpos = (to + ('0 0 1' * current_height));
 -              WarpZone_TraceLine(this.origin, targpos, MOVE_NOMONSTERS, this);
 -              if(trace_fraction < 1)
 -              {
 -                      //print("normal arc line failed, trying to find new pos...");
 -                      WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, this);
 -                      targpos = (trace_endpos + '0 0 -10');
 -                      WarpZone_TraceLine(this.origin, targpos, MOVE_NOMONSTERS, this);
 -                      if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
 -                      /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
 -              }
 -      }
 -      else { targpos = to; }
 -#endif
 -
 -      //this.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 -
 -      vector desired_direction = normalize(targpos - from);
 -      if(turnrate) { this.velocity = (normalize(normalize(this.velocity) + (desired_direction * 50)) * movespeed); }
 -      else { this.velocity = (desired_direction * movespeed); }
 -
 -      //this.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
 -      //this.angles = vectoangles(this.velocity);
 -}
 -
  .entity draggedby;
  
  void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
        if(!(this.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(this.flags & FL_SWIM))
                this.moveto_z = this.origin_z;
  
 -      if(vdist(this.origin - this.moveto, >, 100))
 +      fixedmakevectors(this.angles);
 +      float vz = this.velocity_z;
 +
 +      if(!turret_closetotarget(this, this.moveto, 16))
        {
                bool do_run = (this.enemy || this.monster_moveto);
 -              if(IS_ONGROUND(this) || ((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
 -                      Monster_CalculateVelocity(this, this.moveto, this.origin, true, ((do_run) ? runspeed : walkspeed));
 +              movelib_move_simple(this, v_forward, ((do_run) ? runspeed : walkspeed), 0.4);
  
 -              if(time > this.pain_finished && time > this.anim_finished) // TODO: use anim_finished instead!?
 +              if(time > this.pain_finished && time > this.anim_finished)
                if(!this.state)
                {
                        if(vdist(this.velocity, >, 10))
                        setanim(this, this.anim_idle, true, false, false);
        }
  
 +      if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
 +              this.velocity_z = vz;
 +
        this.steerto = steerlib_attract2(this, ((this.monster_face) ? this.monster_face : this.moveto), 0.5, 500, 0.95);
  
        vector real_angle = vectoangles(this.steerto) - this.angles;
@@@ -857,9 -867,6 +857,9 @@@ void Monster_Dead_Think(entity this
  {
        this.nextthink = time + this.ticrate;
  
 +      Monster mon = Monsters_from(this.monsterid);
 +      mon.mr_deadthink(mon, this);
 +
        if(this.monster_lifetime != 0)
        if(time >= this.monster_lifetime)
        {
@@@ -964,14 -971,13 +964,14 @@@ void Monster_Dead(entity this, entity a
        this.state                      = 0;
        this.attack_finished_single[0] = 0;
        this.effects = 0;
 +      this.dphitcontentsmask &= ~DPCONTENTS_BODY;
  
        if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
                this.velocity = '0 0 0';
  
        CSQCModel_UnlinkEntity(this);
  
-       Monster mon = Monsters_from(this.monsterid);
+       Monster mon = REGISTRY_GET(Monsters, this.monsterid);
        mon.mr_death(mon, this);
  
        if(this.candrop && this.weapon)
@@@ -1002,7 -1008,7 +1002,7 @@@ void Monster_Damage(entity this, entit
        float take = v.x;
        //float save = v.y;
  
-       Monster mon = Monsters_from(this.monsterid);
+       Monster mon = REGISTRY_GET(Monsters, this.monsterid);
        take = mon.mr_pain(mon, this, take, attacker, deathtype);
  
        if(take)
@@@ -1160,7 -1166,8 +1160,8 @@@ void Monster_Frozen_Think(entity this
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
                SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
-               this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
+               if (this.iceblock)
+                       this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
  
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
                        WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
  
  void Monster_Enemy_Check(entity this)
  {
 -      if(!this.enemy)
 +      if(this.enemy)
 +              return;
 +
 +      this.enemy = Monster_FindTarget(this);
 +      if(this.enemy)
        {
 -              this.enemy = Monster_FindTarget(this);
 -              if(this.enemy)
 -              {
 -                      WarpZone_RefSys_Copy(this.enemy, this);
 -                      WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
 -                      // update move target immediately?
 -                      this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
 -                      this.monster_moveto = '0 0 0';
 -                      this.monster_face = '0 0 0';
 -
 -                      //this.pass_distance = vlen((('1 0 0' * this.enemy.origin_x) + ('0 1 0' * this.enemy.origin_y)) - (('1 0 0' *  this.origin_x) + ('0 1 0' *  this.origin_y)));
 -                      Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
 -              }
 +              WarpZone_RefSys_Copy(this.enemy, this);
 +              WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
 +              // update move target immediately?
 +              this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
 +              this.monster_moveto = '0 0 0';
 +              this.monster_face = '0 0 0';
 +
 +              Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
        }
  }
  
@@@ -1228,7 -1236,7 +1229,7 @@@ void Monster_Think(entity this
                this.last_enemycheck = time + 1; // check for enemies every second
        }
  
-       Monster mon = Monsters_from(this.monsterid);
+       Monster mon = REGISTRY_GET(Monsters, this.monsterid);
        if(mon.mr_think(mon, this))
        {
                Monster_Move(this, this.speed2, this.speed, this.stopspeed);
  
  bool Monster_Spawn_Setup(entity this)
  {
-       Monster mon = Monsters_from(this.monsterid);
+       Monster mon = REGISTRY_GET(Monsters, this.monsterid);
        mon.mr_setup(mon, this);
  
        // ensure some basic needs are met
  
        this.max_health = GetResource(this, RES_HEALTH);
        this.pain_finished = this.nextthink;
 +      this.last_enemycheck = this.spawn_time + random(); // slight delay
  
        if(IS_PLAYER(this.monster_follow))
                this.effects |= EF_DIMLIGHT;
  bool Monster_Spawn(entity this, bool check_appear, int mon_id)
  {
        // setup the basic required properties for a monster
-       entity mon = Monsters_from(mon_id);
+       entity mon = REGISTRY_GET(Monsters, mon_id);
        if(!mon.monsterid) { return false; } // invalid monster
  
        if(!autocvar_g_monsters) { Monster_Remove(this); return false; }
        else
                setmodel(this, mon.m_model);
  
 +      if(!this.monster_name || this.monster_name == "")
 +              this.monster_name = mon.monster_name;
 +
        this.flags                              = FL_MONSTER;
        this.classname                  = "monster";
        this.takedamage                 = DAMAGE_AIM;
        this.reset                              = Monster_Reset;
        this.netname                    = mon.netname;
        this.monster_attackfunc = mon.monster_attackfunc;
 -      this.monster_name               = mon.monster_name;
        this.candrop                    = true;
 -      this.view_ofs                   = '0 0 0.7' * (this.maxs_z * 0.5);
        this.oldtarget2                 = this.target2;
 -      //this.pass_distance            = 0;
        this.deadflag                   = DEAD_NO;
        this.spawn_time                 = time;
        this.gravity                    = 1;
        if(autocvar_g_nodepthtestplayers) { this.effects |= EF_NODEPTHTEST; }
        if(mon.spawnflags & MONSTER_TYPE_SWIM) { this.flags |= FL_SWIM; }
  
 -      if(autocvar_g_playerclip_collisions)
 +      if(autocvar_g_monsters_playerclip_collisions)
                this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
  
        if(mon.spawnflags & MONSTER_TYPE_FLY)
                set_movetype(this, MOVETYPE_FLY);
        }
  
 -      if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
 -      {
 -              if(mon.spawnflags & MONSTER_SIZE_BROKEN)
 -                      this.scale *= 1.3;
 -
 -              if(mon.spawnflags & MONSTER_SIZE_QUAKE)
 -              if(autocvar_g_monsters_quake_resize)
 -                      this.scale *= 1.3;
 -      }
 +      if((mon.spawnflags & MONSTER_SIZE_QUAKE) && autocvar_g_monsters_quake_resize && !(this.spawnflags & MONSTERFLAG_RESPAWNED))
 +              this.scale *= 1.3;
  
        setsize(this, mon.m_mins * this.scale, mon.m_maxs * this.scale);
 +      this.view_ofs                   = '0 0 0.7' * (this.maxs_z * 0.5);
  
        this.ticrate = bound(sys_frametime, ((!this.ticrate) ? autocvar_g_monsters_think_delay : this.ticrate), 60);
  
index 5121c7836347e40e6afdd5f5406338a729d39a1d,c0a8c6b2b54e416017cf39149ee976f55e769e90..b9b1e98343e34dbaeaeca4ecaf48066051fd33ed
@@@ -17,7 -17,7 +17,7 @@@ float GL_CheckExtension(string ext
  
  float GL_Have_TextureCompression()
  {
-       return (GL_CheckExtension("GL_EXT_texture_compression_s3tc") && GL_CheckExtension("GL_ARB_texture_compression"));
+       return GL_CheckExtension("GL_EXT_texture_compression_s3tc");
  }
  
  .entity parent, firstChild, nextSibling;
@@@ -682,13 -682,13 +682,13 @@@ float updateCompression(
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
 -      /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
  
  // hidden gametypes come last so indexing always works correctly
  #define HIDDEN_GAMETYPES \
        GAMETYPE(MAPINFO_TYPE_RACE) \
        GAMETYPE(MAPINFO_TYPE_CTS) \
 +      GAMETYPE(MAPINFO_TYPE_INVASION) \
        /**/
  
  Gametype GameType_GetID(int cnt)
@@@ -830,9 -830,9 +830,9 @@@ void CheckSendCvars(entity me, string c
  {
        if(me.sendCvars)
        {
-               LOG_INFOF("Sending cvar: %s -> %s", cvarnamestring, cvar_string(cvarnamestring));
                if(gamestatus & (GAME_CONNECTED | GAME_ISSERVER))
                {
+                       LOG_INFOF("Sending cvar: %s -> %s", cvarnamestring, cvar_string(cvarnamestring));
                        cmd(sprintf("\nsendcvar %s\n", cvarnamestring));
                }
        }
index 1242b2c3f19b5720eced598d137c69bd9dd125bc,99077dcfe7b237fc8e1c5e59f888e6d666c4feb5..cbeb6a72e554f36cab8dbe3770a843ca84f290e2
@@@ -201,7 -201,9 +201,9 @@@ int autocvar_g_respawn_delay_large_coun
  float autocvar_g_respawn_delay_max;
  bool autocvar_g_respawn_delay_forced;
  bool autocvar_g_respawn_ghosts;
- float autocvar_g_respawn_ghosts_maxtime;
+ float autocvar_g_respawn_ghosts_alpha = 1;
+ float autocvar_g_respawn_ghosts_fadetime = 1.5;
+ float autocvar_g_respawn_ghosts_time = 4.5;
  float autocvar_g_respawn_ghosts_speed;
  int autocvar_g_respawn_waves;
  string autocvar_g_shootfromfixedorigin;
@@@ -395,13 -397,11 +397,13 @@@ float autocvar_g_monsters_damageforcesc
  float autocvar_g_monsters_target_range;
  bool autocvar_g_monsters_target_infront;
  float autocvar_g_monsters_target_infront_range = 0.3;
 +bool autocvar_g_monsters_target_infront_2d = true;
  float autocvar_g_monsters_attack_range;
  int autocvar_g_monsters_score_kill;
  int autocvar_g_monsters_score_spawned;
  bool autocvar_g_monsters_typefrag;
  bool autocvar_g_monsters_owners;
 +bool autocvar_g_monsters_playerclip_collisions;
  float autocvar_g_monsters_miniboss_chance;
  float autocvar_g_monsters_miniboss_healthboost;
  float autocvar_g_monsters_drop_time;
index 5b39fc20b3e6b0d0405d400d0808b6b62bfd17df,f791446eb5872a8b0a6fb37807d430bc9aa594d0..f21d715d6f2f58a8e6fd2e4be55e72b1451ff846
@@@ -354,7 -354,7 +354,7 @@@ void CommonCommand_editmob(int request
                                        string mon_oldname = mon.monster_name;
  
                                        mon.monster_name = argument;
 -                                      if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null);
 +                                      if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null); // TODO: the new name is never actually sent to CSQC!
                                        print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
                                        return;
                                }
@@@ -475,7 -475,7 +475,7 @@@ void CommonCommand_info(int request, en
        {
                case CMD_REQUEST_COMMAND:
                {
-                       string command = builtin_cvar_string(strcat("sv_info_", argv(1)));
+                       string command = cvar_string(strcat("sv_info_", argv(1)));
  
                        if (command) wordwrap_sprint(caller, command, 1000);
                        else print_to(caller, "ERROR: unsupported info command");
diff --combined qcsrc/server/g_world.qc
index fd7d5062cd12b13bdaf0fcd34a6cfa1eaf6e55b1,67049e73b24261fee11aacd7e1a66791496d2bd7..d8eba631ba68fe7132670f821b2b6e9aafdf8916
@@@ -269,6 -269,7 +269,6 @@@ void cvar_changes_init(
                BADCVAR("g_duel_not_dm_maps");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
 -              BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
                BADCVAR("g_jailbreak_teams");
                BADCVAR("g_buffs_randomize");
                BADCVAR("g_buffs_randomize_teamplay");
                BADCVAR("g_campcheck_distance");
+               BADCVAR("g_chatsounds");
                BADCVAR("g_ca_point_leadlimit");
                BADCVAR("g_ca_point_limit");
                BADCVAR("g_ctf_captimerecord_always");
                BADCVAR("g_forced_respawn");
                BADCVAR("g_freezetag_point_leadlimit");
                BADCVAR("g_freezetag_point_limit");
+               BADCVAR("g_glowtrails");
                BADCVAR("g_hats");
+               BADCVAR("g_casings");
                BADCVAR("g_invasion_point_limit");
                BADCVAR("g_jump_grunt");
+               BADCVAR("g_keepaway_ballcarrier_effects");
+               BADCVAR("g_keepawayball_effects");
                BADCVAR("g_keyhunt_point_leadlimit");
                BADCVAR("g_nexball_goalleadlimit");
                BADCVAR("g_new_toys_autoreplace");
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
+               BADCVAR("sv_radio");
                BADCVAR("sv_stepheight");
                BADCVAR("sv_timeout");
                BADCVAR("sv_weapons_modeloverride");
                BADCVAR("w_prop_interval");
+               BADPREFIX("chat_");
                BADPREFIX("crypto_");
                BADPREFIX("gameversion_");
                BADPREFIX("g_chat_");
@@@ -567,9 -575,6 +574,6 @@@ spawnfunc(__init_dedicated_server
        // handler for _init/_init map (only for dedicated server initialization)
  
        world_initialized = -1; // don't complain
-       cvar = cvar_normal;
-       cvar_string = cvar_string_normal;
-       cvar_set = cvar_set_normal;
  
        delete_fn = remove_unsafely;
  
@@@ -679,8 -684,8 +683,8 @@@ spawnfunc(worldspawn
                        // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only
                        // restore csqc_progname too
                        string expect = "csprogs.dat";
-                       wantrestart = cvar_string_normal("csqc_progname") != expect;
-                       cvar_set_normal("csqc_progname", expect);
+                       wantrestart = cvar_string("csqc_progname") != expect;
+                       cvar_set("csqc_progname", expect);
                }
                else
                {
                        // This always works; fall back to it if a versioned csprogs.dat is suddenly missing
                        string select = "csprogs.dat";
                        if (fexists(pk3csprogs)) select = pk3csprogs;
-                       if (cvar_string_normal("csqc_progname") != select)
+                       if (cvar_string("csqc_progname") != select)
                        {
-                               cvar_set_normal("csqc_progname", select);
+                               cvar_set("csqc_progname", select);
                                wantrestart = true;
                        }
                        // Check for updates on startup
                                        string newprogs = sprintf("progs-%s.dat", switchversion);
                                        if (fexists(newprogs))
                                        {
-                                               cvar_set_normal("sv_progs", newprogs);
+                                               cvar_set("sv_progs", newprogs);
                                                wantrestart = true;
                                        }
                                        string newcsprogs = sprintf("csprogs-%s.dat", switchversion);
                                        if (fexists(newcsprogs))
                                        {
-                                               cvar_set_normal("csqc_progname", newcsprogs);
+                                               cvar_set("csqc_progname", newcsprogs);
                                                wantrestart = true;
                                        }
                                }
                }
        }
  
-       cvar = cvar_normal;
-       cvar_string = cvar_string_normal;
-       cvar_set = cvar_set_normal;
        if(world_already_spawned)
                error("world already spawned - you may have EXACTLY ONE worldspawn!");
        world_already_spawned = true;
@@@ -1742,16 -1743,25 +1742,25 @@@ float WinningCondition_Scores(float lim
  
        if(MUTATOR_CALLHOOK(Scores_CountFragsRemaining))
        {
-               float fragsleft = FLOAT_MAX, leadingfragsleft = FLOAT_MAX;
-               if (limit)
-                       fragsleft = limit - WinningConditionHelper_topscore;
-               if (leadlimit)
-                       leadingfragsleft = WinningConditionHelper_secondscore + leadlimit - WinningConditionHelper_topscore;
-               if (limit && leadlimit && autocvar_leadlimit_and_fraglimit)
-                       fragsleft = max(fragsleft, leadingfragsleft);
+               float fragsleft;
+               if (checkrules_suddendeathend && time >= checkrules_suddendeathend)
+               {
+                       fragsleft = 1;
+               }
                else
-                       fragsleft = min(fragsleft, leadingfragsleft);
+               {
+                       fragsleft = FLOAT_MAX;
+                       float leadingfragsleft = FLOAT_MAX;
+                       if (limit)
+                               fragsleft = limit - WinningConditionHelper_topscore;
+                       if (leadlimit)
+                               leadingfragsleft = WinningConditionHelper_secondscore + leadlimit - WinningConditionHelper_topscore;
+                       if (limit && leadlimit && autocvar_leadlimit_and_fraglimit)
+                               fragsleft = max(fragsleft, leadingfragsleft);
+                       else
+                               fragsleft = min(fragsleft, leadingfragsleft);
+               }
  
                if (fragsleft_last != fragsleft) // do not announce same remaining frags multiple times
                {
@@@ -2203,6 -2213,11 +2212,11 @@@ void Shutdown(
        if(world_initialized > 0)
        {
                world_initialized = 0;
+               // if a timeout is active, reset the slowmo value to normal
+               if(timeout_status == TIMEOUT_ACTIVE)
+                       cvar_set("slowmo", ftos(orig_slowmo));
                LOG_TRACE("Saving persistent data...");
                Ban_SaveBans();
  
index 804165cd93b523f40994afebcafec03323038830,d4d5d349449163c529ab6b5223ec7056dcb5814b..35b2d0cea8ed6445cbd92ade371474111faff072
@@@ -243,7 -243,7 +243,7 @@@ string WeaponNameFromWeaponentity(entit
                return wepent.m_weapon.m_name;
        else if(wepent.m_switchweapon != WEP_Null)
                return wepent.m_switchweapon.m_name;
-       return "none"; //Weapons_from(wepent.cnt).m_name;
+       return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name;
  }
  
  string formatmessage(entity this, string msg)
@@@ -719,7 -719,7 +719,7 @@@ void readplayerstartcvars(
                for (int j = 0; j < t; ++j)
                {
                        s = argv(j);
-                       Weapon wep = Weapons_fromstr(s);
+                       Weapon wep = Weapon_from_name(s);
                        if(wep != WEP_Null)
                        {
                                g_weaponarena_weapons |= (wep.m_wepset);
@@@ -1263,7 -1263,7 +1263,7 @@@ string uid2name(string myuid
        return s;
  }
  
 -bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
 +bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
  {
      float m = e.dphitcontentsmask;
      e.dphitcontentsmask = goodcontents | badcontents;
  
                // rule 4: we must "see" some spawnpoint or item
            entity sp = NULL;
 -          IL_EACH(g_spawnpoints, checkpvs(mstart, it),
 +          if(frompos)
            {
 -              if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 -              {
 -                      sp = it;
 -                      break;
 -              }
 -          });
 +              if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 +                      sp = e;
 +          }
 +          if(!sp)
 +          {
 +                  IL_EACH(g_spawnpoints, checkpvs(mstart, it),
 +                  {
 +                      if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 +                      {
 +                              sp = it;
 +                              break;
 +                      }
 +                  });
 +              }
                if(!sp)
                {
                        int items_checked = 0;
      return false;
  }
  
 -float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
 +bool MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
  {
 -      return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
 +      return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false);
  }
  
  void write_recordmarker(entity pl, float tstart, float dt)
index 3cddc88f4a95388995cd181ed898aa0206fd4cce,35fc07beac4b5c51c81fed7ca02caeee37a33928..949e0e997513108b80259c148140ddfa7476a8ee
  #include <common/mapinfo.qh>
  #include <common/turrets/all.qh>
  
- #if 1
- #define cvar_string_normal builtin_cvar_string
- #define cvar_normal builtin_cvar
- #else
- string cvar_string_normal(string n)
- {
-       if (!(cvar_type(n) & CVAR_TYPEFLAG_EXISTS))
-               backtrace(strcat("Attempt to access undefined cvar: ", n));
-       return builtin_cvar_string(n);
- }
- float cvar_normal(string n)
- {
-       return stof(cvar_string_normal(n));
- }
- #endif
- #define cvar_set_normal builtin_cvar_set
  .vector dropped_origin;
  
  entity eliminatedPlayers;
@@@ -92,9 -74,9 +74,9 @@@ float LostMovetypeFollow(entity ent)
  
  string uid2name(string myuid);
  
 -bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance);
 +bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos);
  
 -float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance);
 +bool MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance);
  
  string NearestLocation(vector p);