}
}
+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!
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;
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);
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;
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;
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))
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;
}
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;
{
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)
{
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)
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)
{
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);
}
}
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);
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_");
// 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;
// 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;
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
{
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();