]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Mon, 18 Nov 2019 10:12:08 +0000 (20:12 +1000)
committerMario <mario.mario@y7mail.com>
Mon, 18 Nov 2019 10:12:08 +0000 (20:12 +1000)
# Conflicts:
# qcsrc/common/monsters/monster/shambler.qc
# qcsrc/common/monsters/monster/wyvern.qc
# qcsrc/common/monsters/sv_monsters.qc

1  2 
qcsrc/client/csqcmodel_hooks.qc
qcsrc/common/mapinfo.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications/all.inc
qcsrc/server/autocvars.qh
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc

index b28c7c52ddba97a2e76fba7a85b6bac2aa16b330,1272758f76cee3de642322d9ce6ffb2419660e7a..87e577c9cea454869acfc3b34ea9ab65110014b6
  .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;
@@@ -542,8 -540,11 +542,11 @@@ void CSQCModel_Effects_Apply(entity thi
                tref = EFFECT_TR_BLOOD.m_id;
        if(this.csqcmodel_modelflags & MF_ROTATE)
        {
+               // This will be hard to replace with MAKE_VECTORS because it's called as part of the predraw function
+               // as documented in csprogs.h in the engine. The globals can then be read in many places in the engine.
+               // However MF_ROTATE is currently only used in one place - might be possible to get rid of it entirely.
                this.renderflags |= RF_USEAXIS;
-               MAKEVECTORS(makevectors, this.angles + '0 100 0' * fmod(time, 3.6), v_forward, v_right, v_up);
+               makevectors(this.angles + '0 100 0' * fmod(time, 3.6));
        }
        if(this.csqcmodel_modelflags & MF_TRACER)
                tref = EFFECT_TR_WIZSPIKE.m_id;
@@@ -616,7 -617,7 +619,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);
  
diff --combined qcsrc/common/mapinfo.qh
index 02ae96586c1dc45f4db78c5ff2ba7b9c756c5623,b669ba1f7b63aa2a8c74ab7bc7e1f91caaed1de3..26765660f3b5aff45bda7b671f37e09c5abc6022
@@@ -124,7 -124,7 +124,7 @@@ REGISTER_GAMETYPE(DEATHMATCH, NEW(Death
  CLASS(LastManStanding, Gametype)
      INIT(LastManStanding)
      {
-         this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,true,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
+         this.gametype_init(this, _("Last Man Standing"),"lms","g_lms",false,true,"","timelimit=20 lives=5 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
      }
      METHOD(LastManStanding, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
      {
@@@ -302,7 -302,7 +302,7 @@@ void HUD_Mod_CA(vector pos, vector mySi
  CLASS(ClanArena, Gametype)
      INIT(ClanArena)
      {
-         this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
+         this.gametype_init(this, _("Clan Arena"),"ca","g_ca",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=6",_("Kill all enemy teammates to win the round"));
      }
      METHOD(ClanArena, m_parse_mapinfo, bool(string k, string v))
      {
@@@ -497,7 -497,7 +497,7 @@@ REGISTER_GAMETYPE(NEXBALL, NEW(NexBall)
  CLASS(FreezeTag, Gametype)
      INIT(FreezeTag)
      {
-         this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
+         this.gametype_init(this, _("Freeze Tag"),"ft","g_freezetag",true,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=6",_("Kill enemies to freeze them, stand next to frozen teammates to revive them; freeze all enemies to win"));
      }
      METHOD(FreezeTag, m_parse_mapinfo, bool(string k, string v))
      {
@@@ -556,11 -556,14 +556,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;
@@@ -669,6 -672,8 +669,8 @@@ void MapInfo_Cache_Destroy(); // disabl
  void MapInfo_Cache_Create(); // enable caching
  void MapInfo_Cache_Invalidate(); // delete cache if any, but keep enabled
  
+ void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse);
  void MapInfo_ClearTemps(); // call this when done with mapinfo for this frame
  
  void MapInfo_Shutdown(); // call this in the shutdown handler
index 0dd5c0c3006ae93465b7568070240e1ca93c95c5,1cba349ed96e39fe5baa594b4f447b04d7af8651..57465865ccdca6b2edc4349081a4238550b6f3a0
@@@ -12,6 -12,7 +12,7 @@@ METHOD(MageSpike, wr_think, void(MageSp
      if (!IS_PLAYER(actor) || weapon_prepareattack(thiswep, actor, weaponentity, false, 0.2)) {
          if (!actor.target_range) actor.target_range = autocvar_g_monsters_target_range;
          actor.enemy = Monster_FindTarget(actor);
+         monster_makevectors(actor, actor.enemy);
          W_SetupShot_Dir(actor, weaponentity, v_forward, false, 0, SND_MageSpike_FIRE, CH_WEAPON_B, 0, DEATH_MONSTER_MAGE.m_id);
        if (!IS_PLAYER(actor)) w_shotdir = normalize((actor.enemy.origin + '0 0 10') - actor.origin);
          M_Mage_Attack_Spike(actor, w_shotdir);
@@@ -46,22 -47,16 +47,22 @@@ float autocvar_g_monster_mage_attack_sp
  float autocvar_g_monster_mage_attack_spike_delay;
  float autocvar_g_monster_mage_attack_spike_accel;
  float autocvar_g_monster_mage_attack_spike_decel;
 +float autocvar_g_monster_mage_attack_spike_chance = 0.45;
  float autocvar_g_monster_mage_attack_spike_turnrate;
  float autocvar_g_monster_mage_attack_spike_speed_max;
  float autocvar_g_monster_mage_attack_spike_smart;
  float autocvar_g_monster_mage_attack_spike_smart_trace_min;
  float autocvar_g_monster_mage_attack_spike_smart_trace_max;
  float autocvar_g_monster_mage_attack_spike_smart_mindist;
 +float autocvar_g_monster_mage_attack_push_chance = 0.7;
  float autocvar_g_monster_mage_attack_push_damage;
  float autocvar_g_monster_mage_attack_push_radius;
  float autocvar_g_monster_mage_attack_push_delay;
  float autocvar_g_monster_mage_attack_push_force;
 +float autocvar_g_monster_mage_attack_teleport_chance = 0.2;
 +float autocvar_g_monster_mage_attack_teleport_delay = 2;
 +float autocvar_g_monster_mage_attack_teleport_random = 0.4;
 +float autocvar_g_monster_mage_attack_teleport_random_range = 1200;
  float autocvar_g_monster_mage_heal_self;
  float autocvar_g_monster_mage_heal_allies;
  float autocvar_g_monster_mage_heal_minhealth;
@@@ -230,7 -225,7 +231,7 @@@ void M_Mage_Attack_Spike(entity this, v
  
  void M_Mage_Defend_Heal(entity this)
  {
 -      float washealed = false;
 +      bool washealed = false;
  
        FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_mage_heal_range, M_Mage_Defend_Heal_Check(this, it),
        {
                                }
                                case 1:
                                {
 -                                      if(GetResource(this, RES_CELLS)) GiveResourceWithLimit(it, RES_CELLS, 1, g_pickup_cells_max);
 -                                      if(GetResource(this, RES_PLASMA)) GiveResourceWithLimit(it, RES_PLASMA, 1, g_pickup_plasma_max);
 -                                      if(GetResource(this, RES_ROCKETS)) GiveResourceWithLimit(it, RES_ROCKETS, 1, g_pickup_rockets_max);
 -                                      if(GetResource(this, RES_SHELLS)) GiveResourceWithLimit(it, RES_SHELLS, 2, g_pickup_shells_max);
 -                                      if(GetResource(this, RES_BULLETS)) GiveResourceWithLimit(it, RES_BULLETS, 5, g_pickup_nails_max);
 +                                      if(GetResource(it, RES_CELLS)) GiveResourceWithLimit(it, RES_CELLS, 1, g_pickup_cells_max);
 +                                      if(GetResource(it, RES_PLASMA)) GiveResourceWithLimit(it, RES_PLASMA, 1, g_pickup_plasma_max);
 +                                      if(GetResource(it, RES_ROCKETS)) GiveResourceWithLimit(it, RES_ROCKETS, 1, g_pickup_rockets_max);
 +                                      if(GetResource(it, RES_SHELLS)) GiveResourceWithLimit(it, RES_SHELLS, 2, g_pickup_shells_max);
 +                                      if(GetResource(it, RES_BULLETS)) GiveResourceWithLimit(it, RES_BULLETS, 5, g_pickup_nails_max);
                                        // TODO: fuel?
                                        fx = EFFECT_AMMO_REGEN;
                                        break;
  
        if(washealed)
        {
 -              setanim(this, this.anim_shoot, true, true, true);
 +              setanim(this, this.anim_melee, true, true, true);
                this.attack_finished_single[0] = time + (autocvar_g_monster_mage_heal_delay);
 +              this.state = MONSTER_ATTACK_MELEE;
                this.anim_finished = time + 1.5;
        }
  }
@@@ -298,10 -292,8 +299,10 @@@ void M_Mage_Attack_Push(entity this
                                                NULL, NULL, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE.m_id, DMG_NOWEP, this.enemy);
        Send_Effect(EFFECT_TE_EXPLOSION, this.origin, '0 0 0', 1);
  
 -      setanim(this, this.anim_shoot, true, true, true);
 +      setanim(this, this.anim_duckjump, true, true, true);
        this.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_push_delay);
 +      this.anim_finished = time + 1;
 +      this.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
  }
  
  void M_Mage_Attack_Teleport(entity this, entity targ)
        if(!targ) return;
        if(vdist(targ.origin - this.origin, >, 1500)) return;
  
 +      if(autocvar_g_monster_mage_attack_teleport_random && random() <= autocvar_g_monster_mage_attack_teleport_random)
 +      {
 +              vector oldpos = this.origin;
 +              vector extrasize = '1 1 1' * autocvar_g_monster_mage_attack_teleport_random_range;
 +              if(MoveToRandomLocationWithinBounds(this, this.absmin - extrasize, this.absmax + extrasize,
 +                                                                                      DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, 
 +                                                                                      Q3SURFACEFLAG_SKY, 10, 64, 256, true))
 +              {
 +                      vector a = vectoangles(targ.origin - this.origin);
 +                      this.angles = '0 1 0' * a.y;
 +                      this.fixangle = true;
 +                      Send_Effect(EFFECT_SPAWN_NEUTRAL, oldpos, '0 0 0', 1);
 +                      Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
 +                      this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
 +                      return;
 +              }
 +      }
 +
 +      if(!IS_ONGROUND(targ)) return;
 +
        makevectors(targ.angles);
 -      tracebox(targ.origin + ((v_forward * -1) * 200), this.mins, this.maxs, this.origin, MOVE_NOMONSTERS, this);
 +      tracebox(CENTER_OR_VIEWOFS(targ), this.mins, this.maxs, CENTER_OR_VIEWOFS(targ) + ((v_forward * -1) * 200), MOVE_NOMONSTERS, this);
  
        if(trace_fraction < 1)
                return;
  
 -      vector newpos = targ.origin + ((v_forward * -1) * 200);
 +      vector newpos = trace_endpos;
  
        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
        Send_Effect(EFFECT_SPAWN_NEUTRAL, newpos, '0 0 0', 1);
        this.fixangle = true;
        this.velocity *= 0.5;
  
 -      this.attack_finished_single[0] = time + 0.2;
 +      this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
  }
  
  void M_Mage_Defend_Shield_Remove(entity this)
@@@ -365,7 -337,7 +366,7 @@@ void M_Mage_Defend_Shield(entity this
        SetResourceExplicit(this, RES_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
        this.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
        setanim(this, this.anim_shoot, true, true, true);
 -      this.attack_finished_single[0] = time + 1;
 +      this.attack_finished_single[0] = time + 1; // give just a short cooldown on attacking
        this.anim_finished = time + 1;
  }
  
@@@ -375,7 -347,7 +376,7 @@@ bool M_Mage_Attack(int attack_type, ent
        {
                case MONSTER_ATTACK_MELEE:
                {
 -                      if(random() <= 0.7)
 +                      if(random() <= autocvar_g_monster_mage_attack_push_chance)
                        {
                                Weapon wep = WEP_MAGE_SPIKE;
  
                }
                case MONSTER_ATTACK_RANGED:
                {
 -                      if(!actor.mage_spike)
 +                      if(random() <= autocvar_g_monster_mage_attack_teleport_chance)
                        {
 -                              if(random() <= 0.4)
 -                              {
 -                                      OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
 -                                      off.offhand_think(off, actor, true);
 -                                      return true;
 -                              }
 -                              else
 -                              {
 -                                      setanim(actor, actor.anim_shoot, true, true, true);
 -                                      actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
 -                                      actor.anim_finished = time + 1;
 -                                      Weapon wep = WEP_MAGE_SPIKE;
 -                                      wep.wr_think(wep, actor, weaponentity, 1);
 -                                      return true;
 -                              }
 +                              OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
 +                              actor.OffhandMageTeleport_key_pressed = 0;
 +                              off.offhand_think(off, actor, 1);
 +                              return true;
                        }
 -
 -                      if(actor.mage_spike)
 +                      else if(!actor.mage_spike && random() <= autocvar_g_monster_mage_attack_spike_chance)
 +                      {
 +                              setanim(actor, actor.anim_shoot, true, true, true);
 +                              actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
 +                              actor.anim_finished = time + 1;
 +                              actor.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
 +                              Weapon wep = WEP_MAGE_SPIKE;
 +                              wep.wr_think(wep, actor, weaponentity, 1);
                                return true;
 -                      else
 -                              return false;
 +                      }
 +
 +                      return false;
                }
        }
  
@@@ -471,7 -447,7 +472,7 @@@ METHOD(Mage, mr_pain, float(Mage this, 
  METHOD(Mage, mr_death, bool(Mage this, entity actor))
  {
      TC(Mage, this);
 -    setanim(actor, actor.anim_die1, false, true, true);
 +    setanim(actor, ((random() > 0.5) ? actor.anim_die2 : actor.anim_die1), false, true, true);
      return true;
  }
  
@@@ -481,22 -457,12 +482,22 @@@ METHOD(Mage, mr_anim, bool(Mage this, e
  {
      TC(Mage, this);
      vector none = '0 0 0';
 -    actor.anim_die1 = animfixfps(actor, '4 1 0.5', none); // 2 seconds
 -    actor.anim_walk = animfixfps(actor, '1 1 1', none);
      actor.anim_idle = animfixfps(actor, '0 1 1', none);
 -    actor.anim_pain1 = animfixfps(actor, '3 1 2', none); // 0.5 seconds
 +    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 +    actor.anim_run = animfixfps(actor, '1 1 1', none);
      actor.anim_shoot = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
 -    actor.anim_run = animfixfps(actor, '5 1 1', none);
 +    actor.anim_duckjump = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
 +    actor.anim_melee = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire1 = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire2 = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire3 = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    actor.anim_pain1 = animfixfps(actor, '6 1 2', none); // 0.5 seconds
 +    actor.anim_pain2 = animfixfps(actor, '7 1 2', none); // 0.5 seconds
 +    //actor.anim_pain3 = animfixfps(actor, '8 1 2', none); // 0.5 seconds
 +    actor.anim_die1 = animfixfps(actor, '9 1 0.5', none); // 2 seconds
 +    actor.anim_die2 = animfixfps(actor, '10 1 0.5', none); // 2 seconds
 +    //actor.anim_dead1 = animfixfps(actor, '11 1 0.5', none); // 2 seconds
 +    //actor.anim_dead2 = animfixfps(actor, '12 1 0.5', none); // 2 seconds
      return true;
  }
  #endif
index 8d20edf82c097f290658d43a22c04ad68e47749e,b684af15255e66efc70d9580e34f90c3dea825f4..7d021f4c267f5de5d73c86b166852ce98b23cb64
@@@ -10,7 -10,6 +10,7 @@@ float autocvar_g_monster_spider_attack_
  float autocvar_g_monster_spider_attack_web_speed;
  float autocvar_g_monster_spider_attack_web_speed_up;
  float autocvar_g_monster_spider_attack_web_delay;
 +float autocvar_g_monster_spider_attack_web_range = 800;
  
  float autocvar_g_monster_spider_attack_bite_damage;
  float autocvar_g_monster_spider_attack_bite_delay;
@@@ -59,15 -58,12 +59,15 @@@ METHOD(SpiderAttack, wr_think, void(Spi
      TC(SpiderAttack, thiswep);
      bool isPlayer = IS_PLAYER(actor);
      if (fire & 1)
 -    if ((!isPlayer && time >= actor.spider_web_delay) || weapon_prepareattack(thiswep, actor, weaponentity, false, autocvar_g_monster_spider_attack_web_delay)) {
 +    if ((!isPlayer && time >= actor.spider_web_delay) || (isPlayer && weapon_prepareattack(thiswep, actor, weaponentity, false, autocvar_g_monster_spider_attack_web_delay))) {
                if (!isPlayer) {
 -                      actor.spider_web_delay = time + 3;
 +                      actor.spider_web_delay = time + autocvar_g_monster_spider_attack_web_delay;
                        setanim(actor, actor.anim_shoot, true, true, true);
 -                      actor.attack_finished_single[0] = time + (autocvar_g_monster_spider_attack_web_delay);
 -                      actor.anim_finished = time + 1;
 +                      if(actor.animstate_endtime > time)
 +                              actor.anim_finished = actor.animstate_endtime;
 +                      else
 +                              actor.anim_finished = time + 1;
 +                      actor.attack_finished_single[0] = actor.anim_finished + 0.2;
                }
          if (isPlayer) actor.enemy = Monster_FindTarget(actor);
          W_SetupShot_Dir(actor, weaponentity, v_forward, false, 0, SND_SpiderAttack_FIRE, CH_WEAPON_B, 0, DEATH_MONSTER_SPIDER.m_id);
@@@ -132,8 -128,6 +132,6 @@@ void adaptor_think2use_hittype_splash(e
  
  void M_Spider_Attack_Web(entity this)
  {
-       monster_makevectors(this, this.enemy);
        sound(this, CH_SHOTS, SND_ELECTRO_FIRE2, VOL_BASE, ATTEN_NORM);
  
        entity proj = new(plasma);
@@@ -182,11 -176,8 +180,11 @@@ bool M_Spider_Attack(int attack_type, e
                }
                case MONSTER_ATTACK_RANGED:
                {
 -                      wep.wr_think(wep, actor, weaponentity, 1);
 -                      return true;
 +                      if(vdist(actor.enemy.origin - actor.origin, <=, autocvar_g_monster_spider_attack_web_range))
 +                      {
 +                              wep.wr_think(wep, actor, weaponentity, 1);
 +                              return true;
 +                      }
                }
        }
  
@@@ -206,15 -197,14 +204,15 @@@ METHOD(Spider, mr_think, bool(Spider th
  METHOD(Spider, mr_pain, float(Spider this, entity actor, float damage_take, entity attacker, float deathtype))
  {
      TC(Spider, this);
 +    setanim(actor, ((random() > 0.5) ? actor.anim_pain2 : actor.anim_pain1), true, true, false);
 +    actor.pain_finished = actor.animstate_endtime;
      return damage_take;
  }
  
  METHOD(Spider, mr_death, bool(Spider this, entity actor))
  {
      TC(Spider, this);
 -    setanim(actor, actor.anim_melee, false, true, true);
 -    actor.angles_x = 180;
 +    setanim(actor, ((random() > 0.5) ? actor.anim_die2 : actor.anim_die1), false, true, true);
      return true;
  }
  #endif
@@@ -223,25 -213,11 +221,25 @@@ METHOD(Spider, mr_anim, bool(Spider thi
  {
      TC(Spider, this);
      vector none = '0 0 0';
 -    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 -    actor.anim_idle = animfixfps(actor, '0 1 1', none);
 -    actor.anim_melee = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
 -    actor.anim_shoot = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
 -    actor.anim_run = animfixfps(actor, '1 1 1', none);
 +    actor.anim_melee = animfixfps(actor, '0 1 5', none); // analyze models and set framerate
 +    actor.anim_die1 = animfixfps(actor, '1 1 1', none);
 +    actor.anim_die2 = animfixfps(actor, '2 1 1', none);
 +    actor.anim_shoot = animfixfps(actor, '3 1 1', none);
 +    //actor.anim_fire2 = animfixfps(actor, '4 1 1', none);
 +    actor.anim_idle = animfixfps(actor, '5 1 1', none);
 +    //actor.anim_sight = animfixfps(actor, '6 1 1', none);
 +    actor.anim_pain1 = animfixfps(actor, '7 1 1', none);
 +    actor.anim_pain2 = animfixfps(actor, '8 1 1', none);
 +    //actor.anim_pain3 = animfixfps(actor, '9 1 1', none);
 +    actor.anim_walk = animfixfps(actor, '10 1 1', none);
 +    actor.anim_run = animfixfps(actor, '10 1 1', none); // temp?
 +    //actor.anim_forwardright = animfixfps(actor, '11 1 1', none);
 +    //actor.anim_walkright = animfixfps(actor, '12 1 1', none);
 +    //actor.anim_walkbackright = animfixfps(actor, '13 1 1', none);
 +    //actor.anim_walkback = animfixfps(actor, '14 1 1', none);
 +    //actor.anim_walkbackleft = animfixfps(actor, '15 1 1', none);
 +    //actor.anim_walkleft = animfixfps(actor, '16 1 1', none);
 +    //actor.anim_forwardleft = animfixfps(actor, '17 1 1', none);
      return true;
  }
  #endif
index 69d446f53ef52d87250070ebd686b8a2b4807762,d07669af0ea1861edcb230d86cc27cb151b5d8d7..48867793b122d120ae9dbdac3f7505b7d84b8751
@@@ -19,7 -19,10 +19,10 @@@ METHOD(WyvernAttack, wr_think, void(Wyv
      if (fire & 1)
      if (time > actor.attack_finished_single[0] || weapon_prepareattack(thiswep, actor, weaponentity, false, 1.2)) {
          if (IS_PLAYER(actor)) W_SetupShot_Dir(actor, weaponentity, v_forward, false, 0, SND_WyvernAttack_FIRE, CH_WEAPON_B, 0, DEATH_MONSTER_WYVERN.m_id);
-               if (IS_MONSTER(actor)) monster_makevectors(actor, actor.enemy);
+               if (IS_MONSTER(actor)) {
+                       actor.attack_finished_single[0] = time + 1.2;
+                       actor.anim_finished = time + 1.2;
+               }
  
                entity missile = spawn();
                missile.owner = missile.realowner = actor;
@@@ -68,9 -71,9 +71,9 @@@ void M_Wyvern_Attack_Fireball_Explode(e
        entity own = this.realowner;
  
        RadiusDamage(this, own, autocvar_g_monster_wyvern_attack_fireball_damage, autocvar_g_monster_wyvern_attack_fireball_edgedamage, autocvar_g_monster_wyvern_attack_fireball_force,
 -                                              NULL, NULL, autocvar_g_monster_wyvern_attack_fireball_radius, this.projectiledeathtype, DMG_NOWEP, NULL);
 +                                              own, NULL, autocvar_g_monster_wyvern_attack_fireball_radius, this.projectiledeathtype, DMG_NOWEP, NULL);
  
 -      FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_wyvern_attack_fireball_radius, it.takedamage == DAMAGE_AIM,
 +      FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_wyvern_attack_fireball_radius, it.takedamage == DAMAGE_AIM && it != own,
        {
                Fire_AddDamage(it, own, 5 * MONSTER_SKILLMOD(own), autocvar_g_monster_wyvern_attack_fireball_damagetime, this.projectiledeathtype);
        });
@@@ -85,15 -88,6 +88,15 @@@ void M_Wyvern_Attack_Fireball_Touch(ent
        M_Wyvern_Attack_Fireball_Explode(this);
  }
  
 +void M_Wyvern_Attack_Fireball(entity this)
 +{
 +      w_shotdir = normalize((this.enemy.origin + '0 0 10') - this.origin);
 +      Weapon wep = WEP_WYVERN_ATTACK;
 +      // TODO
 +      .entity weaponentity = weaponentities[0];
 +      wep.wr_think(wep, this, weaponentity, 1);
 +}
 +
  bool M_Wyvern_Attack(int attack_type, entity actor, entity targ, .entity weaponentity)
  {
        switch(attack_type)
                case MONSTER_ATTACK_MELEE:
                case MONSTER_ATTACK_RANGED:
                {
 -                      w_shotdir = normalize((actor.enemy.origin + '0 0 10') - actor.origin);
 -                      Weapon wep = WEP_WYVERN_ATTACK;
 -                      wep.wr_think(wep, actor, weaponentity, 1);
 +                      Monster_Delay(actor, 0, 1, M_Wyvern_Attack_Fireball);
 +                      //actor.anim_finished = time + 1.2;
 +                      setanim(actor, actor.anim_shoot, false, true, true);
 +                      if(actor.animstate_endtime > time)
 +                              actor.anim_finished = actor.animstate_endtime;
 +                      else
 +                              actor.anim_finished = time + 1.2;
 +                      actor.attack_finished_single[0] = actor.anim_finished + 0.2;
                        return true;
                }
        }
@@@ -126,14 -115,6 +129,14 @@@ METHOD(Wyvern, mr_think, bool(Wyvern th
      return true;
  }
  
 +METHOD(Wyvern, mr_deadthink, bool(Wyvern this, entity actor))
 +{
 +    TC(Wyvern, this);
 +    if(IS_ONGROUND(actor))
 +      setanim(actor, actor.anim_die2, true, false, false);
 +    return true;
 +}
 +
  METHOD(Wyvern, mr_pain, float(Wyvern this, entity actor, float damage_take, entity attacker, float deathtype))
  {
      TC(Wyvern, this);
@@@ -157,15 -138,12 +160,15 @@@ METHOD(Wyvern, mr_anim, bool(Wyvern thi
  {
      TC(Wyvern, this);
      vector none = '0 0 0';
 -    actor.anim_die1 = animfixfps(actor, '4 1 0.5', none); // 2 seconds
 -    actor.anim_walk = animfixfps(actor, '1 1 1', none);
      actor.anim_idle = animfixfps(actor, '0 1 1', none);
 +    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 +    actor.anim_run = animfixfps(actor, '2 1 1', none);
      actor.anim_pain1 = animfixfps(actor, '3 1 2', none); // 0.5 seconds
 -    actor.anim_shoot = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
 -    actor.anim_run = animfixfps(actor, '1 1 1', none);
 +    actor.anim_pain2 = animfixfps(actor, '4 1 2', none); // 0.5 seconds
 +    actor.anim_melee = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    actor.anim_shoot = animfixfps(actor, '6 1 5', none); // analyze models and set framerate
 +    actor.anim_die1 = animfixfps(actor, '7 1 0.5', none); // 2 seconds
 +    actor.anim_die2 = animfixfps(actor, '8 1 0.5', none); // 2 seconds
      return true;
  }
  #endif
index 990f318873abf743c7f4d4b4aab3cc0982d34851,4f2139cc170a56c78d49548f47b360e8ca8e78b3..319b699a706372cc3aca360df4259d50dfac907c
@@@ -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) && !((Monsters_from(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
        || (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)) { this.monster_delayedfunc(this.owner); }
++      if(Monster_ValidTarget(this.owner, this.owner.enemy, false))
+       {
+               monster_makevectors(this.owner, this.owner.enemy);
+               this.monster_delayedfunc(this.owner);
+       }
  
        if(this.cnt > 1)
        {
@@@ -362,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;
@@@ -383,8 -363,6 +387,6 @@@ bool Monster_Attack_Melee(entity this, 
        else
                this.attack_finished_single[0] = this.anim_finished = time + animtime;
  
-       monster_makevectors(this, targ);
        traceline(this.origin + this.view_ofs, this.origin + v_forward * er, 0, this);
  
        if(trace_ent.takedamage)
@@@ -444,11 -422,11 +446,12 @@@ 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))
        {
+               monster_makevectors(this, targ);
                int attack_success = this.monster_attackfunc(MONSTER_ATTACK_MELEE, this, targ, weaponentity);
                if(attack_success == 1)
                        Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
  
        if(vdist(targ.origin - this.origin, >, this.attack_range))
        {
+               monster_makevectors(this, targ);
                int attack_success = this.monster_attackfunc(MONSTER_ATTACK_RANGED, this, targ, weaponentity);
                if(attack_success == 1)
                        Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
@@@ -492,10 -471,12 +496,10 @@@ void Monster_UpdateModel(entity 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;
  }
  
@@@ -568,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;
@@@ -853,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)
        {
@@@ -960,7 -971,6 +964,7 @@@ 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';
@@@ -1188,20 -1198,21 +1192,20 @@@ void Monster_Frozen_Think(entity this
  
  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);
        }
  }
  
@@@ -1263,7 -1274,6 +1267,7 @@@ bool Monster_Spawn_Setup(entity this
  
        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;
@@@ -1343,9 -1353,6 +1347,9 @@@ bool Monster_Spawn(entity this, bool ch
        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 5a03092a960ad03a159e00ef471c3f4399a7d297,036a8b5e3c74e6391cf1aadec15251ca8b871eb9..87b01637379659b55dff35448231cb106085b219
      MSG_INFO_NOTIF(DEATH_SELF_GENERIC,                      N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 died%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_LAVA,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s"))
      MSG_INFO_NOTIF(DEATH_SELF_MON_MAGE,                     N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was exploded by a Mage%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,            N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,           N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Shambler%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,             N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,               N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Golem%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Golem%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,                N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Golem%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_SPIDER,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was bitten by a Spider%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_WYVERN,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 joins the Zombies%s%s"), "")
      MULTITEAM_INFO(NEXBALL_RETURN_HELD,                     N_CONSOLE,  0, 0, "", "",           "",     _("^BGThe ^TC^TT^BG team held the ball for too long"), "", NAME)
  
      MSG_INFO_NOTIF(ONSLAUGHT_CAPTURE,                       N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^BG%s^BG captured %s^BG control point"), "")
+     MSG_INFO_NOTIF(ONSLAUGHT_CAPTURE_NONAME,                N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^BG captured a control point"), "")
      MULTITEAM_INFO(ONSLAUGHT_CPDESTROYED,                   N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^TC^TT^BG team %s^BG control point has been destroyed by %s"), "", NAME)
+     MULTITEAM_INFO(ONSLAUGHT_CPDESTROYED_NONAME,            N_CONSOLE,  1, 0, "s1", "",         "",     _("^TC^TT^BG team control point has been destroyed by %s"), "", NAME)
      MULTITEAM_INFO(ONSLAUGHT_GENDESTROYED,                  N_CONSOLE,  0, 0, "", "",           "",     _("^TC^TT^BG generator has been destroyed"), "", GENERATOR)
      MULTITEAM_INFO(ONSLAUGHT_GENDESTROYED_OVERTIME,         N_CONSOLE,  0, 0, "", "",           "",     _("^TC^TT^BG generator spontaneously combusted due to overtime!"), "", GENERATOR)
  
      MSG_CENTER_NOTIF(DOOR_LOCKED_ALSONEED,              N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^BGYou also need %s^BG!"), "")
      MSG_CENTER_NOTIF(DOOR_UNLOCKED,                     N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDoor unlocked!"), "")
  
-     MSG_CENTER_NOTIF(EXTRALIVES,                        N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^F2You picked up some extra lives"), "")
+     MSG_CENTER_NOTIF(EXTRALIVES,                        N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^F2Extra lives taken: ^K1%s"), "")
  
      MSG_CENTER_NOTIF(FREEZETAG_REVIVE,                  N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K3You revived ^BG%s"), "")
      MSG_CENTER_NOTIF(FREEZETAG_REVIVE_SELF,             N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K3You revived yourself"), "")
      MSG_CENTER_NOTIF(NIX_NEWWEAPON,                     N_ENABLE,    0, 1, "item_wepname",   CPID_NIX,               "0 0",  _("^F2Active weapon: ^F1%s"), "")
  
      MSG_CENTER_NOTIF(ONS_CAPTURE,                       N_ENABLE,    1, 0, "s1",             CPID_ONSLAUGHT,         "0 0",  _("^BGYou captured %s^BG control point"), "")
+     MSG_CENTER_NOTIF(ONS_CAPTURE_NONAME,                N_ENABLE,    0, 0, "",               CPID_ONSLAUGHT,         "0 0",  _("^BGYou captured a control point"), "")
      MULTITEAM_CENTER(ONS_CAPTURE_TEAM,                  N_ENABLE,    1, 0, "s1",             CPID_ONSLAUGHT,         "0 0",  _("^TC^TT^BG team captured %s^BG control point"), "", NAME)
+     MULTITEAM_CENTER(ONS_CAPTURE_TEAM_NONAME,           N_ENABLE,    0, 0, "",               CPID_ONSLAUGHT,         "0 0",  _("^TC^TT^BG team captured a control point"), "", NAME)
      MSG_CENTER_NOTIF(ONS_CONTROLPOINT_SHIELDED,         N_ENABLE,    0, 0, "",               CPID_ONS_CAPSHIELD,     "0 0",  _("^BGThis control point currently cannot be captured"), "")
      MSG_CENTER_NOTIF(ONS_GENERATOR_SHIELDED,            N_ENABLE,    0, 0, "",               CPID_ONS_CAPSHIELD,     "0 0",  _("^BGThe enemy generator cannot be destroyed yet\n^F2Capture some control points to unshield it"), "")
      MULTITEAM_CENTER(ONS_NOTSHIELDED,                   N_ENABLE,    0, 0, "",               CPID_ONSLAUGHT,         "0 0",  _("^BGThe ^TCenemy^BG generator is no longer shielded!"), "", NAME)
  
      MSG_CENTER_NOTIF(RACE_FINISHLAP,                    N_ENABLE,    0, 0, "",               CPID_RACE_FINISHLAP,    "0 0",  _("^F2The race is over, finish your lap!"), "")
  
-     MSG_CENTER_NOTIF(SECONDARY_NODAMAGE,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGSecondary fire inflicts no damage!"), "")
      MSG_CENTER_NOTIF(SEQUENCE_COMPLETED,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGSequence completed!"), "")
      MSG_CENTER_NOTIF(SEQUENCE_COUNTER,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGThere are more to go..."), "")
      MSG_CENTER_NOTIF(SEQUENCE_COUNTER_FEWMORE,          N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^BGOnly %s^BG more to go..."), "")
      MSG_MULTI_NOTIF(DEATH_SELF_GENERIC,                 N_ENABLE,  NULL,           INFO_DEATH_SELF_GENERIC,                CENTER_DEATH_SELF_GENERIC)
      MSG_MULTI_NOTIF(DEATH_SELF_LAVA,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_LAVA,                   CENTER_DEATH_SELF_LAVA)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_MAGE,                N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_MAGE,               CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,       N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_CLAW,      CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,      N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_SMASH,     CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,        N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_ZAP,       CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,          N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_CLAW,         CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_SMASH,        CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,           N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_ZAP,          CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_SPIDER,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SPIDER,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_WYVERN,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_WYVERN,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_ZOMBIE_JUMP,        CENTER_DEATH_SELF_MONSTER)
index 28a74cd708d1e205fd0b6eea464adf2f67374264,a783d72314a7a1c571014ce6b5cbe70d2d0ac440..35c7a2d6d65f86d28b408c9081e6618424b84578
@@@ -169,6 -169,7 +169,7 @@@ float autocvar_g_maxspeed
  bool autocvar_g_mirrordamage_onlyweapons;
  
  float autocvar_g_movement_highspeed = 1;
+ bool autocvar_g_movement_highspeed_q3_compat = 0;
  string autocvar_g_mutatormsg;
  //float autocvar_g_nick_flood_penalty;
  int autocvar_g_nick_flood_penalty_red;
@@@ -290,6 -291,7 +291,7 @@@ string autocvar_sv_defaultplayermodel_p
  string autocvar_sv_defaultplayermodel_red;
  string autocvar_sv_defaultplayermodel_yellow;
  int autocvar_sv_defaultplayerskin;
+ bool autocvar_sv_doors_always_open;
  bool autocvar_sv_doublejump;
  bool autocvar_sv_eventlog;
  bool autocvar_sv_eventlog_console;
@@@ -300,7 -302,7 +302,7 @@@ string autocvar_sv_eventlog_files_names
  bool autocvar_sv_eventlog_files_timestamps;
  float autocvar_sv_friction_on_land;
  var float autocvar_sv_friction_slick = 0.5;
- float autocvar_sv_gameplayfix_q2airaccelerate;
+ float autocvar_sv_gameplayfix_q2airaccelerate = 1;
  int autocvar_sv_gentle;
  #define autocvar_sv_gravity cvar("sv_gravity")
  string autocvar_sv_intermission_cdtrack;
@@@ -374,8 -376,8 +376,8 @@@ float autocvar_timelimit_overtime
  int autocvar_timelimit_overtimes;
  float autocvar_timelimit_suddendeath;
  #define autocvar_utf8_enable cvar("utf8_enable")
float autocvar_sv_gameplayfix_gravityunaffectedbyticrate;
- bool autocvar_sv_gameplayfix_upwardvelocityclearsongroundflag;
bool autocvar_sv_gameplayfix_gravityunaffectedbyticrate = true;
+ bool autocvar_sv_gameplayfix_upwardvelocityclearsongroundflag = true;
  float autocvar_g_trueaim_minrange;
  float autocvar_g_grab_range;
  int autocvar_g_max_info_autoscreenshot;
@@@ -390,13 -392,11 +392,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;
diff --combined qcsrc/server/g_world.qc
index 2bda678b80c81827becb20d99b4740e6cb0e0872,0c44a95f17bf5385372fb106e1dd4e37114930ee..166c7b43f939440ecbfac704b32170c9835869f0
@@@ -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("sv_damagetext");
                BADCVAR("sv_db_saveasdump");
                BADCVAR("sv_intermission_cdtrack");
+               BADCVAR("sv_mapchange_delay");
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
@@@ -2064,7 -2066,7 +2065,7 @@@ string GotoMap(string m
                return "Map switch will happen after scoreboard.";
  }
  
- bool autocvar_sv_gameplayfix_multiplethinksperframe;
+ bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
  void RunThink(entity this)
  {
        // don't let things stay in the past.
  }
  
  bool autocvar_sv_freezenonclients;
- bool autocvar_sv_gameplayfix_delayprojectiles;
+ bool autocvar_sv_gameplayfix_delayprojectiles = false;
  void Physics_Frame()
  {
        if(autocvar_sv_freezenonclients)
index a2ed8cdda7d76f477b9c0cc0c9c4b49523aa38d2,ded47a6387f97920ce32f2fa41851a7b8ec32dea..4c5bef5979bf0841657b24cb4797afb827aae898
@@@ -715,7 -715,7 +715,7 @@@ void readplayerstartcvars(
        {
                g_weapon_stay = 0; // incompatible
                start_weapons = g_weaponarena_weapons;
-               start_items |= IT_UNLIMITED_AMMO;
+               start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
        }
        else
        {
        if(!cvar("g_use_ammunition"))
                start_items |= IT_UNLIMITED_AMMO;
  
-       if(start_items & IT_UNLIMITED_WEAPON_AMMO)
+       if(start_items & IT_UNLIMITED_AMMO)
        {
                start_ammo_shells = 999;
                start_ammo_nails = 999;
@@@ -1257,7 -1257,7 +1257,7 @@@ string uid2name(string myuid) 
        return s;
  }
  
 -float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float 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, i;
      vector start, org, delta, end, enddown, mstart;
  
        // 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)