#include "sv_monsters.qh"
-#include <server/g_subs.qh>
#include <lib/warpzone/common.qh>
#include "../constants.qh"
#include "../teams.qh"
#include "../vehicles/all.qh"
#include <server/campaign.qh>
#include <server/command/_mod.qh>
-#include "../triggers/triggers.qh"
+#include "../mapobjects/triggers.qh"
#include <lib/csqcmodel/sv_model.qh>
#include <server/round_handler.qh>
#include <server/weapons/_mod.qh>
void monsters_setstatus(entity this)
{
- this.stat_monsters_total = monsters_total;
- this.stat_monsters_killed = monsters_killed;
+ STAT(MONSTERS_TOTAL, this) = monsters_total;
+ STAT(MONSTERS_KILLED, this) = monsters_killed;
}
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;
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;
|| (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))
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 && vdist(it.origin - this.origin, <, this.target_range),
{
if(Monster_ValidTarget(this, it))
{
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);
{
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)));
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
}
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)))
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;
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;
}
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;
}
|| ((trace_fraction < 1) && (trace_ent != this.enemy)))
{
this.enemy = NULL;
- this.pass_distance = 0;
+ //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));
+ //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));
}
}
else { targpos = to; }
+#endif
//this.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
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)
{
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)
{
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;
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)
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;
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)
{
}
}
-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;
*/
}
+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);
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);
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);
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;
this.scale *= 1.3;
}
- setsize(this, mon.mins * this.scale, mon.maxs * this.scale);
+ setsize(this, mon.m_mins * this.scale, mon.m_maxs * this.scale);
this.ticrate = bound(sys_frametime, ((!this.ticrate) ? autocvar_g_monsters_think_delay : this.ticrate), 60);