]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/monsters/sv_monsters.qc
Merge branch 'master' into Mario/monsters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / monsters / sv_monsters.qc
index 469dd7b9cd7d3ec41d609dc48def8cacb2825928..f693ee4513456d0f84ae3905af6d8671ed9da95e 100644 (file)
@@ -35,7 +35,8 @@ void monster_dropitem(entity this, entity attacker)
                return;
 
        vector org = CENTER_OR_VIEWOFS(this);
-       entity e = new(droppedweapon); // use weapon handling to remove it on touch
+       entity e = spawn();
+       Item_SetLoot(e, true);
        e.spawnfunc_checked = true;
 
        e.monster_loot = this.monster_loot;
@@ -48,8 +49,6 @@ void monster_dropitem(entity this, entity attacker)
                e.noalign = true;
                StartItem(e, e.monster_loot);
                e.gravity = 1;
-               set_movetype(e, MOVETYPE_TOSS);
-               e.reset = SUB_Remove;
                setorigin(e, org);
                e.velocity = randomvec() * 175 + '0 0 325';
                e.item_spawnshieldtime = time + 0.7;
@@ -83,6 +82,7 @@ bool Monster_ValidTarget(entity this, entity targ)
        || (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)
        || (targ.items & IT_INVISIBILITY)
        || (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
        || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || targ.health <= 0 || this.health <= 0))
@@ -100,7 +100,7 @@ bool Monster_ValidTarget(entity this, entity 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
@@ -125,21 +125,27 @@ entity Monster_FindTarget(entity this)
        vector my_center = CENTER_OR_VIEWOFS(this);
 
        // find the closest acceptable target to pass to
-       FOREACH_ENTITY_RADIUS(this.origin, this.target_range, it.monster_attack,
+       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))
+                       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;
@@ -166,6 +172,11 @@ void monster_setupcolors(entity this)
                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)
@@ -173,6 +184,8 @@ void monster_changeteam(entity this, int newteam)
        if(!teamplay) { return; }
 
        this.team = newteam;
+       if(!this.monster_attack)
+               IL_PUSH(g_monster_targets, this);
        this.monster_attack = true; // new team, activate attacking
        monster_setupcolors(this);
 
@@ -245,7 +258,7 @@ void Monster_Sound_Precache(string f)
        {
                if(tokenize_console(s) != 3)
                {
-                       LOG_TRACE("Invalid sound info line: ", s);
+                       //LOG_DEBUG("Invalid sound info line: ", s); // probably a comment, no need to spam warnings
                        continue;
                }
                PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
@@ -274,7 +287,7 @@ void Monster_Sounds_Precache(entity this)
 
 void Monster_Sounds_Clear(entity this)
 {
-#define _MSOUND(m) if(this.monstersound_##m) { strunzone(this.monstersound_##m); this.monstersound_##m = string_null; }
+#define _MSOUND(m) strfree(this.monstersound_##m);
        ALLMONSTERSOUNDS
 #undef _MSOUND
 }
@@ -299,7 +312,7 @@ bool Monster_Sounds_Load(entity this, string f, int first)
        float fh = fopen(f, FILE_READ);
        if(fh < 0)
        {
-               LOG_TRACE("Monster sound file not found: ", f);
+               //LOG_DEBUG("Monster sound file not found: ", f); // no biggie, monster has no sounds, let's not spam it
                return false;
        }
        while((s = fgets(fh)))
@@ -309,9 +322,7 @@ bool Monster_Sounds_Load(entity this, string f, int first)
                field = Monster_Sound_SampleField(argv(0));
                if(GetMonsterSoundSampleField_notFound)
                        continue;
-               if (this.(field))
-                       strunzone(this.(field));
-               this.(field) = strzone(strcat(argv(1), " ", argv(2)));
+               strcpy(this.(field), strcat(argv(1), " ", argv(2)));
        }
        fclose(fh);
        return true;
@@ -335,7 +346,11 @@ void Monster_Sound(entity this, .string samplefield, float sound_delay, bool del
        if(delaytoo)
        if(time < this.msound_delay)
                return; // too early
-       GlobalSound_string(this, this.(samplefield), chan, VOL_BASE, VOICETYPE_PLAYERSOUND);
+       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;
 }
@@ -361,7 +376,7 @@ bool Monster_Attack_Melee(entity this, entity targ, float damg, vector anim, flo
        traceline(this.origin + this.view_ofs, this.origin + v_forward * er, 0, this);
 
        if(trace_ent.takedamage)
-               Damage(trace_ent, this, this, damg * MONSTER_SKILLMOD(this), deathtype, trace_ent.origin, normalize(trace_ent.origin - this.origin));
+               Damage(trace_ent, this, this, damg * MONSTER_SKILLMOD(this), deathtype, DMG_NOWEP, trace_ent.origin, normalize(trace_ent.origin - this.origin));
 
        return true;
 }
@@ -565,7 +580,7 @@ vector Monster_Move_Target(entity this, entity targ)
                        || ((trace_fraction < 1) && (trace_ent != this.enemy)))
                {
                        this.enemy = NULL;
-                       this.pass_distance = 0;
+                       //this.pass_distance = 0;
                }
 
                if(this.enemy)
@@ -656,12 +671,13 @@ vector Monster_Move_Target(entity this, entity targ)
 
 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));
+       //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;
+       vector targpos = to;
+#if 0
        if(current_height) // make sure we can actually do this arcing path
        {
                targpos = (to + ('0 0 1' * current_height));
@@ -677,6 +693,7 @@ void Monster_CalculateVelocity(entity this, vector to, vector from, float turnra
                }
        }
        else { targpos = to; }
+#endif
 
        //this.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 
@@ -697,52 +714,11 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
        if(this.target2 && this.target2 != "" && this.goalentity.targetname != this.target2)
                this.goalentity = find(NULL, targetname, this.target2);
 
-       if(STAT(FROZEN, this) == 2)
+       if(STAT(FROZEN, this))
        {
-               this.revive_progress = bound(0, this.revive_progress + this.ticrate * this.revive_speed, 1);
-               this.health = max(1, this.revive_progress * this.max_health);
-               this.iceblock.alpha = bound(0.2, 1 - this.revive_progress, 1);
-
-               if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
-
                movelib_brake_simple(this, stpspeed);
                setanim(this, this.anim_idle, true, false, false);
-
-               this.enemy = NULL;
-               this.nextthink = time + this.ticrate;
-
-               if(this.revive_progress >= 1)
-                       Unfreeze(this);
-
-               return;
-       }
-       else if(STAT(FROZEN, this) == 3)
-       {
-               this.revive_progress = bound(0, this.revive_progress - this.ticrate * this.revive_speed, 1);
-               this.health = max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * this.revive_progress );
-
-               if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
-
-               movelib_brake_simple(this, stpspeed);
-               setanim(this, this.anim_idle, true, false, false);
-
-               this.enemy = NULL;
-               this.nextthink = time + this.ticrate;
-
-               if(this.health < 1)
-               {
-                       Unfreeze(this);
-                       this.health = 0;
-                       if(this.event_damage)
-                               this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, this.origin, '0 0 0');
-               }
-
-               else if ( this.revive_progress <= 0 )
-                       Unfreeze(this);
-
-               return;
+               return; // no physics while frozen!
        }
 
        if(this.flags & FL_SWIM)
@@ -753,7 +729,7 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
                        {
                                this.last_trace = time + 0.4;
 
-                               Damage (this, NULL, NULL, 2, DEATH_DROWN.m_id, this.origin, '0 0 0');
+                               Damage (this, NULL, NULL, 2, DEATH_DROWN.m_id, DMG_NOWEP, this.origin, '0 0 0');
                                this.angles = '90 90 0';
                                if(random() < 0.5)
                                {
@@ -806,27 +782,6 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
        if(DIFF_TEAM(this.monster_follow, this))
                this.monster_follow = NULL;
 
-       if(time >= this.last_enemycheck)
-       {
-               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
-                               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);
-                       }
-               }
-
-               this.last_enemycheck = time + 1; // check for enemies every second
-       }
-
        if(this.state == MONSTER_ATTACK_RANGED && IS_ONGROUND(this))
        {
                this.state = 0;
@@ -891,9 +846,6 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
                turny = bound(turny * -1, shortangle_f(real_angle.y, this.angles.y), turny);
                this.angles_y += turny;
        }
-
-       .entity weaponentity = weaponentities[0]; // TODO?
-       Monster_Attack_Check(this, this.enemy, weaponentity);
 }
 
 void Monster_Remove(entity this)
@@ -964,7 +916,7 @@ void Monster_Reset(entity this)
        this.moveto = this.origin;
 }
 
-void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
        this.health -= damage;
 
@@ -1004,7 +956,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
 
        if(IS_PLAYER(attacker))
        if(autocvar_g_monsters_score_spawned || !((this.spawnflags & MONSTERFLAG_SPAWNED) || (this.spawnflags & MONSTERFLAG_RESPAWNED)))
-               PlayerScore_Add(attacker, SP_SCORE, +autocvar_g_monsters_score_kill);
+               GameRules_scoring_add(attacker, SCORE, +autocvar_g_monsters_score_kill);
 
        if(gibbed)
        {
@@ -1043,7 +995,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
        }
 }
 
-void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
        if((this.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL.m_id && !ITEM_DAMAGE_NEEDKILL(deathtype))
                return;
@@ -1205,6 +1157,64 @@ void Monster_Anim(entity this)
        */
 }
 
+void Monster_Frozen_Think(entity this)
+{
+       if(STAT(FROZEN, this) == 2)
+       {
+               STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
+               this.health = max(1, STAT(REVIVE_PROGRESS, this) * this.max_health);
+               this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
+
+               if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
+                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+
+               if(STAT(REVIVE_PROGRESS, this) >= 1)
+                       Unfreeze(this);
+       }
+       else if(STAT(FROZEN, this) == 3)
+       {
+               STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
+               this.health = max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+
+               if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
+                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+
+               if(this.health < 1)
+               {
+                       Unfreeze(this);
+                       this.health = 0;
+                       if(this.event_damage)
+                               this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
+               }
+
+               else if ( STAT(REVIVE_PROGRESS, this) <= 0 )
+                       Unfreeze(this);
+       }
+       // otherwise, no revival!
+
+       this.enemy = NULL; // TODO: save enemy, and attack when revived?
+}
+
+void Monster_Enemy_Check(entity 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);
+               }
+       }
+}
+
 void Monster_Think(entity this)
 {
        setthink(this, Monster_Think);
@@ -1212,14 +1222,27 @@ void Monster_Think(entity this)
 
        if(this.monster_lifetime && time >= this.monster_lifetime)
        {
-               Damage(this, this, this, this.health + this.max_health, DEATH_KILL.m_id, this.origin, this.origin);
+               Damage(this, this, this, this.health + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
                return;
        }
 
+       if(STAT(FROZEN, this))
+               Monster_Frozen_Think(this);
+       else if(time >= this.last_enemycheck)
+       {
+               Monster_Enemy_Check(this);
+               this.last_enemycheck = time + 1; // check for enemies every second
+       }
+
        Monster mon = Monsters_from(this.monsterid);
        if(mon.mr_think(mon, this))
+       {
                Monster_Move(this, this.speed2, this.speed, this.stopspeed);
 
+               .entity weaponentity = weaponentities[0]; // TODO?
+               Monster_Attack_Check(this, this.enemy, weaponentity);
+       }
+
        Monster_Anim(this);
 
        CSQCMODEL_AUTOUPDATE(this);
@@ -1261,7 +1284,11 @@ bool Monster_Spawn_Setup(entity this)
        Monster_Sounds_Update(this);
 
        if(teamplay)
+       {
+               if(!this.monster_attack)
+                       IL_PUSH(g_monster_targets, this);
                this.monster_attack = true; // we can have monster enemies in team games
+       }
 
        Monster_Sound(this, monstersound_spawn, 0, false, CH_VOICE);
 
@@ -1355,7 +1382,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        this.candrop                    = true;
        this.view_ofs                   = '0 0 0.7' * (this.maxs_z * 0.5);
        this.oldtarget2                 = this.target2;
-       this.pass_distance              = 0;
+       //this.pass_distance            = 0;
        this.deadflag                   = DEAD_NO;
        this.spawn_time                 = time;
        this.gravity                    = 1;
@@ -1370,7 +1397,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        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)
@@ -1379,15 +1406,8 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
                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);