const float STAT_RESPAWN_TIME = 72;
const float STAT_ROUNDSTARTTIME = 73;
- const float STAT_MONSTERS_TOTAL = 73;
- const float STAT_MONSTERS_KILLED = 74;
+ const float STAT_WEAPONS2 = 74;
+ const float STAT_WEAPONS3 = 75;
+
++const float STAT_MONSTERS_TOTAL = 76;
++const float STAT_MONSTERS_KILLED = 77;
+
// mod stats (1xx)
const float STAT_REDALIVE = 100;
const float STAT_BLUEALIVE = 101;
#define VOL_BASEVOICE 1.0
// this sets sounds and other properties of the projectiles in csqc
- float PROJECTILE_ELECTRO = 1;
- float PROJECTILE_ROCKET = 2;
- float PROJECTILE_TAG = 3;
- float PROJECTILE_BULLET = 4;
- float PROJECTILE_CRYLINK = 5;
- float PROJECTILE_ELECTRO_BEAM = 6;
- float PROJECTILE_GRENADE = 7;
- float PROJECTILE_GRENADE_BOUNCING = 8;
- float PROJECTILE_MINE = 9;
- float PROJECTILE_LASER = 10;
- float PROJECTILE_HLAC = 11;
- float PROJECTILE_SEEKER = 12;
- float PROJECTILE_FLAC = 13;
- float PROJECTILE_PORTO_RED = 14;
- float PROJECTILE_PORTO_BLUE = 15;
- float PROJECTILE_HOOKBOMB = 16;
- float PROJECTILE_HAGAR = 17;
- float PROJECTILE_HAGAR_BOUNCING = 18;
- float PROJECTILE_BULLET_GLOWING = 19;
- float PROJECTILE_CRYLINK_BOUNCING = 20;
- float PROJECTILE_FIREBALL = 21;
- float PROJECTILE_FIREMINE = 22;
- float PROJECTILE_BULLET_GLOWING_TRACER = 23;
-
- float PROJECTILE_RAPTORCANNON = 24;
- float PROJECTILE_RAPTORBOMB = 25;
- float PROJECTILE_RAPTORBOMBLET = 26;
- float PROJECTILE_SPIDERROCKET = 27;
- float PROJECTILE_WAKIROCKET = 28;
- float PROJECTILE_WAKICANNON = 29;
-
- float PROJECTILE_BUMBLE_GUN = 30;
- float PROJECTILE_BUMBLE_BEAM = 31;
-
- float PROJECTILE_MAGE_SPIKE = 32;
-
- float PROJECTILE_NADE_RED = 50;
- float PROJECTILE_NADE_RED_BURN = 51;
- float PROJECTILE_NADE_BLUE = 52;
- float PROJECTILE_NADE_BLUE_BURN = 53;
- float PROJECTILE_NADE_YELLOW = 54;
- float PROJECTILE_NADE_YELLOW_BURN = 55;
- float PROJECTILE_NADE_PINK = 56;
- float PROJECTILE_NADE_PINK_BURN = 57;
- float PROJECTILE_NADE = 58;
- float PROJECTILE_NADE_BURN = 59;
-
- float SPECIES_HUMAN = 0;
- float SPECIES_ROBOT_SOLID = 1;
- float SPECIES_ALIEN = 2;
- float SPECIES_ANIMAL = 3;
- float SPECIES_ROBOT_RUSTY = 4;
- float SPECIES_ROBOT_SHINY = 5;
- float SPECIES_RESERVED = 15;
+ const float PROJECTILE_ELECTRO = 1;
+ const float PROJECTILE_ROCKET = 2;
+ const float PROJECTILE_TAG = 3;
+ const float PROJECTILE_BULLET = 4;
+ const float PROJECTILE_CRYLINK = 5;
+ const float PROJECTILE_ELECTRO_BEAM = 6;
+ const float PROJECTILE_GRENADE = 7;
+ const float PROJECTILE_GRENADE_BOUNCING = 8;
+ const float PROJECTILE_MINE = 9;
+ const float PROJECTILE_LASER = 10;
+ const float PROJECTILE_HLAC = 11;
+ const float PROJECTILE_SEEKER = 12;
+ const float PROJECTILE_FLAC = 13;
+ const float PROJECTILE_PORTO_RED = 14;
+ const float PROJECTILE_PORTO_BLUE = 15;
+ const float PROJECTILE_HOOKBOMB = 16;
+ const float PROJECTILE_HAGAR = 17;
+ const float PROJECTILE_HAGAR_BOUNCING = 18;
+ const float PROJECTILE_BULLET_GLOWING = 19;
+ const float PROJECTILE_CRYLINK_BOUNCING = 20;
+ const float PROJECTILE_FIREBALL = 21;
+ const float PROJECTILE_FIREMINE = 22;
+ const float PROJECTILE_BULLET_GLOWING_TRACER = 23;
+
+ const float PROJECTILE_RAPTORCANNON = 24;
+ const float PROJECTILE_RAPTORBOMB = 25;
+ const float PROJECTILE_RAPTORBOMBLET = 26;
+ const float PROJECTILE_SPIDERROCKET = 27;
+ const float PROJECTILE_WAKIROCKET = 28;
+ const float PROJECTILE_WAKICANNON = 29;
+
+ const float PROJECTILE_BUMBLE_GUN = 30;
+ const float PROJECTILE_BUMBLE_BEAM = 31;
+
++float PROJECTILE_MAGE_SPIKE = 32;
++
+ const float PROJECTILE_NADE_RED = 50;
+ const float PROJECTILE_NADE_RED_BURN = 51;
+ const float PROJECTILE_NADE_BLUE = 52;
+ const float PROJECTILE_NADE_BLUE_BURN = 53;
+ const float PROJECTILE_NADE_YELLOW = 54;
+ const float PROJECTILE_NADE_YELLOW_BURN = 55;
+ const float PROJECTILE_NADE_PINK = 56;
+ const float PROJECTILE_NADE_PINK_BURN = 57;
+ const float PROJECTILE_NADE = 58;
+ const float PROJECTILE_NADE_BURN = 59;
+
+ const float SPECIES_HUMAN = 0;
+ const float SPECIES_ROBOT_SOLID = 1;
+ const float SPECIES_ALIEN = 2;
+ const float SPECIES_ANIMAL = 3;
+ const float SPECIES_ROBOT_RUSTY = 4;
+ const float SPECIES_ROBOT_SHINY = 5;
+ const float SPECIES_RESERVED = 15;
#define FRAGS_PLAYER 0
#define FRAGS_SPECTATOR -666
REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30")
#define g_keepaway IS_GAMETYPE(KEEPAWAY)
- float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps
- float MAPINFO_FEATURE_VEHICLES = 2;
- float MAPINFO_FEATURE_TURRETS = 4;
+REGISTER_GAMETYPE(_("Invasion"),invasion,g_invasion,INVASION,"pointlimit=5")
+#define g_invasion IS_GAMETYPE(INVASION)
+
+ const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps
+ const float MAPINFO_FEATURE_VEHICLES = 2;
+ const float MAPINFO_FEATURE_TURRETS = 4;
- float MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
- float MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps
- float MAPINFO_FLAG_FRUSTRATING = 4; // this map is near impossible to play, enable at your own risk
+ const float MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
+ const float MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps
+ const float MAPINFO_FLAG_FRUSTRATING = 4; // this map is near impossible to play, enable at your own risk
float MapInfo_count;
--- /dev/null
- sound(self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTN_NORM);
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ BRUTE,
+/* function */ m_brute,
+/* spawnflags */ 0,
+/* mins,maxs */ '-36 -36 -20', '36 36 50',
+/* model */ "ogre.dpm",
+/* netname */ "brute",
+/* fullname */ _("Brute")
+);
+
+#define BRUTE_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_chainsaw_damage) \
+ MON_ADD_CVAR(monster, attack_uzi_bullets) \
+ MON_ADD_CVAR(monster, attack_uzi_damage) \
+ MON_ADD_CVAR(monster, attack_uzi_force) \
+ MON_ADD_CVAR(monster, attack_uzi_chance) \
+ MON_ADD_CVAR(monster, attack_grenade_damage) \
+ MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
+ MON_ADD_CVAR(monster, attack_grenade_force) \
+ MON_ADD_CVAR(monster, attack_grenade_radius) \
+ MON_ADD_CVAR(monster, attack_grenade_speed) \
+ MON_ADD_CVAR(monster, attack_grenade_speed_up) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+BRUTE_SETTINGS(brute)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float brute_anim_idle = 0;
+const float brute_anim_walk = 1;
+const float brute_anim_run = 2;
+const float brute_anim_pain = 3;
+const float brute_anim_swing = 4;
+const float brute_anim_die = 5;
+
+.float brute_cycles;
+
+void brute_blade()
+{
+ self.brute_cycles += 1;
+ self.angles_y = self.angles_y + random()* 25;
+
+ monster_melee(self.enemy, MON_CVAR(brute, attack_chainsaw_damage), self.attack_range, DEATH_MONSTER_BRUTE_BLADE, TRUE);
+
+ if(self.brute_cycles <= 4)
+ defer(0.2, brute_blade);
+}
+
+void brute_uzi()
+{
+ self.brute_cycles += 1;
+
+ monster_makevectors(self.enemy);
+
- sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
++ sound(self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM);
+ fireBallisticBullet(CENTER_OR_VIEWOFS(self), v_forward, 0.02, 18000, 5, MON_CVAR(brute, attack_uzi_damage), MON_CVAR(brute, attack_uzi_force), DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
+ endFireBallisticBullet();
+
+ if(self.brute_cycles <= MON_CVAR(brute, attack_uzi_bullets))
+ defer(0.1, brute_uzi);
+}
+
+void brute_grenade_explode()
+{
+ pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1);
- sound(self, CH_WEAPON_A, "weapons/grenade_fire.wav", VOL_BASE, ATTN_NORM);
++ sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
+
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+
+ if(self.movetype == MOVETYPE_NONE)
+ self.velocity = self.oldvelocity;
+
+ RadiusDamage (self, self.realowner, MON_CVAR(brute, attack_grenade_damage), MON_CVAR(brute, attack_grenade_edgedamage), MON_CVAR(brute, attack_grenade_radius), world, MON_CVAR(brute, attack_grenade_force), self.projectiledeathtype, other);
+
+ remove (self);
+}
+
+void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if (self.health <= 0)
+ return;
+
+ if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+ return; // g_projectiles_damage says to halt
+
+ self.health = self.health - damage;
+
+ if (self.health <= 0)
+ W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void brute_grenade_touch()
+{
+ PROJECTILE_TOUCH;
+
+ self.use ();
+}
+
+void brute_grenade_think()
+{
+ self.nextthink = time;
+ if (time > self.cnt)
+ {
+ other = world;
+ brute_grenade_explode();
+ return;
+ }
+}
+
+void brute_grenade()
+{
+ entity gren;
+
+ monster_makevectors(self.enemy);
+
++ sound(self, CH_WEAPON_A, "weapons/grenade_fire.wav", VOL_BASE, ATTEN_NORM);
+
+ gren = spawn ();
+ gren.owner = gren.realowner = self;
+ gren.classname = "grenade";
+ gren.bot_dodge = TRUE;
+ gren.bot_dodgerating = MON_CVAR(brute, attack_grenade_damage);
+ gren.movetype = MOVETYPE_BOUNCE;
+ PROJECTILE_MAKETRIGGER(gren);
+ gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
+ setorigin(gren, CENTER_OR_VIEWOFS(self));
+ setsize(gren, '-3 -3 -3', '3 3 3');
+
+ gren.cnt = time + 5;
+ gren.nextthink = time;
+ gren.think = brute_grenade_think;
+ gren.use = brute_grenade_explode;
+ gren.touch = brute_grenade_touch;
+
+ gren.takedamage = DAMAGE_YES;
+ gren.health = 50;
+ gren.damageforcescale = 0;
+ gren.event_damage = brute_grenade_damage;
+ gren.damagedbycontents = TRUE;
+ gren.missile_flags = MIF_SPLASH | MIF_ARC;
+ W_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(brute, attack_grenade_speed), MON_CVAR(brute, attack_grenade_speed_up), 0, 0, FALSE);
+
+ gren.angles = vectoangles (gren.velocity);
+ gren.flags = FL_PROJECTILE;
+
+ CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
+}
+
+float brute_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ self.brute_cycles = 0;
+ monsters_setframe(brute_anim_swing);
+ self.attack_finished_single = time + 1.3;
+ brute_blade();
+
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ self.brute_cycles = 0;
+ if(random() <= MON_CVAR(brute, attack_uzi_chance))
+ {
+ monsters_setframe(brute_anim_pain);
+ self.attack_finished_single = time + 0.8;
+ defer(0.1, brute_uzi);
+ }
+ else
+ {
+ monster_makevectors(self.enemy);
+ brute_grenade();
+ monsters_setframe(brute_anim_pain);
+ self.attack_finished_single = time + 1.2;
+ }
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void spawnfunc_monster_brute()
+{
+ self.classname = "monster_brute";
+
+ self.monster_spawnfunc = spawnfunc_monster_brute;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_BRUTE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_ogre() { spawnfunc_monster_brute(); }
+
+float m_brute(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move(MON_CVAR(brute, speed_run), MON_CVAR(brute, speed_walk), MON_CVAR(brute, speed_stop), brute_anim_run, brute_anim_walk, brute_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ monsters_setframe(brute_anim_die);
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(brute, health);
+
+ self.monster_loot = spawnfunc_item_bullets;
+ self.monster_attackfunc = brute_attack;
+ monsters_setframe(brute_anim_idle);
+ self.weapon = WEP_GRENADE_LAUNCHER;
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(BRUTE_SETTINGS(brute))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_brute(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/ogre.dpm");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ KNIGHT,
+/* function */ m_knight,
+/* spawnflags */ MONSTER_SIZE_BROKEN,
+/* mins,maxs */ '-20 -20 -32', '20 20 41',
+/* model */ "hknight.mdl",
+/* netname */ "knight",
+/* fullname */ _("Knight")
+);
+
+#define KNIGHT_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_melee_damage) \
+ MON_ADD_CVAR(monster, attack_inferno_damage) \
+ MON_ADD_CVAR(monster, attack_inferno_damagetime) \
+ MON_ADD_CVAR(monster, attack_inferno_chance) \
+ MON_ADD_CVAR(monster, attack_fireball_damage) \
+ MON_ADD_CVAR(monster, attack_fireball_edgedamage) \
+ MON_ADD_CVAR(monster, attack_fireball_damagetime) \
+ MON_ADD_CVAR(monster, attack_fireball_force) \
+ MON_ADD_CVAR(monster, attack_fireball_radius) \
+ MON_ADD_CVAR(monster, attack_fireball_chance) \
+ MON_ADD_CVAR(monster, attack_spike_damage) \
+ MON_ADD_CVAR(monster, attack_spike_edgedamage) \
+ MON_ADD_CVAR(monster, attack_spike_force) \
+ MON_ADD_CVAR(monster, attack_spike_radius) \
+ MON_ADD_CVAR(monster, attack_spike_chance) \
+ MON_ADD_CVAR(monster, attack_jump_damage) \
+ MON_ADD_CVAR(monster, attack_jump_distance) \
+ MON_ADD_CVAR(monster, attack_jump_chance) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+KNIGHT_SETTINGS(knight)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float knight_anim_stand = 0;
+const float knight_anim_walk = 1;
+const float knight_anim_run = 2;
+const float knight_anim_pain = 3;
+const float knight_anim_death1 = 4;
+const float knight_anim_death2 = 5;
+const float knight_anim_charge1 = 6;
+const float knight_anim_magic1 = 7;
+const float knight_anim_magic2 = 8;
+const float knight_anim_charge2 = 9;
+const float knight_anim_slice = 10;
+const float knight_anim_smash = 11;
+const float knight_anim_wattack = 12;
+const float knight_anim_magic3 = 13;
+
+.float knight_cycles;
+
+void knight_inferno()
+{
+ if not(self.enemy)
+ return;
+
+ traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
+ if (trace_fraction != 1)
+ return; // not visible
+
+ self.enemy.effects |= EF_MUZZLEFLASH;
- sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
++ sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTEN_NORM);
+
+ if(vlen(self.enemy.origin - self.origin) <= 2000)
+ Fire_AddDamage(self.enemy, self, MON_CVAR(knight, attack_inferno_damage) * monster_skill, MON_CVAR(knight, attack_inferno_damagetime), DEATH_MONSTER_KNIGHT_INFERNO);
+}
+
+void knight_fireball_explode()
+{
+ entity e;
+ if(self)
+ {
+ pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+
+ RadiusDamage(self, self.realowner, MON_CVAR(knight, attack_fireball_damage), MON_CVAR(knight, attack_fireball_edgedamage), MON_CVAR(knight, attack_fireball_force), world, MON_CVAR(knight, attack_fireball_radius), self.projectiledeathtype, world);
+
+ for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= MON_CVAR(knight, attack_inferno_damage))
+ Fire_AddDamage(e, self, 5 * monster_skill, MON_CVAR(knight, attack_fireball_damagetime), self.projectiledeathtype);
+
+ remove(self);
+ }
+}
+
+void knight_fireball_touch()
+{
+ PROJECTILE_TOUCH;
+
+ knight_fireball_explode();
+}
+
+void knight_fireball()
+{
+ entity missile = spawn();
+ vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+ monster_makevectors(self.enemy);
+
+ self.effects |= EF_MUZZLEFLASH;
++ sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTEN_NORM);
+
+ missile.owner = missile.realowner = self;
+ missile.solid = SOLID_TRIGGER;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL;
+ setsize(missile, '-6 -6 -6', '6 6 6');
+ setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+ missile.flags = FL_PROJECTILE;
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.nextthink = time + 5;
+ missile.think = knight_fireball_explode;
+ missile.enemy = self.enemy;
+ missile.touch = knight_fireball_touch;
+ CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+void knight_spike_explode()
+{
+ if(self)
+ {
+ pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
+
+ RadiusDamage (self, self.realowner, MON_CVAR(knight, attack_spike_damage), MON_CVAR(knight, attack_spike_edgedamage), MON_CVAR(knight, attack_spike_force), world, MON_CVAR(knight, attack_spike_radius), DEATH_MONSTER_KNIGHT_SPIKE, other);
+ remove(self);
+ }
+}
+
+void knight_spike_touch()
+{
+ PROJECTILE_TOUCH;
+
+ knight_spike_explode();
+}
+
+void knight_spike()
+{
+ entity missile;
+ vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+ self.effects |= EF_MUZZLEFLASH;
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ missile.solid = SOLID_TRIGGER;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ setsize (missile, '0 0 0', '0 0 0');
+ setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
+ missile.scale = self.scale;
+ missile.flags = FL_PROJECTILE;
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.nextthink = time + 5;
+ missile.think = knight_spike_explode;
+ missile.enemy = self.enemy;
+ missile.touch = knight_spike_touch;
+ CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void knight_spikes()
+{
+ self.knight_cycles += 1;
+ knight_spike();
+
+ if(self.knight_cycles <= 7)
+ defer(0.1, knight_spikes);
+}
+
+float knight_attack_ranged()
+{
+ if not(self.flags & FL_ONGROUND)
+ return FALSE;
+
+ self.knight_cycles = 0;
+
+ RandomSelection_Init();
+ RandomSelection_Add(world, 1, "", MON_CVAR(knight, attack_fireball_chance), 1);
+ RandomSelection_Add(world, 2, "", MON_CVAR(knight, attack_inferno_chance), 1);
+ RandomSelection_Add(world, 3, "", MON_CVAR(knight, attack_spike_chance), 1);
+ if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > MON_CVAR(knight, attack_jump_distance)) ? 1 : MON_CVAR(knight, attack_jump_chance)), 1);
+
+ switch(RandomSelection_chosen_float)
+ {
+ case 1:
+ {
+ monsters_setframe(knight_anim_magic2);
+ self.attack_finished_single = time + 2;
+ defer(0.4, knight_fireball);
+
+ return TRUE;
+ }
+ case 2:
+ {
+ self.attack_finished_single = time + 3;
+ defer(0.5, knight_inferno);
+ return TRUE;
+ }
+ case 3:
+ {
+ monsters_setframe(knight_anim_magic3);
+ self.attack_finished_single = time + 3;
+ defer(0.4, knight_spikes);
+
+ return TRUE;
+ }
+ case 4:
+ {
+ float er = vlen(self.enemy.origin - self.origin);
+
+ if(er >= 400 && er < 1200)
+ if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
+ {
+ self.velocity = findtrajectory_velocity;
+ Damage(self.enemy, self, self, MON_CVAR(knight, attack_jump_damage) * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+ self.attack_finished_single = time + 2;
+ return TRUE;
+ }
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+float knight_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ float anim;
+ if(random() < 0.3)
+ anim = knight_anim_slice;
+ else if(random() < 0.6)
+ anim = knight_anim_smash;
+ else
+ anim = knight_anim_wattack;
+
+ monsters_setframe(anim);
+ self.attack_finished_single = time + 0.7;
+ monster_melee(self.enemy, MON_CVAR(knight, attack_melee_damage), self.attack_range, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
+
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(knight_attack_ranged())
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void spawnfunc_monster_knight()
+{
+ self.classname = "monster_knight";
+
+ self.monster_spawnfunc = spawnfunc_monster_knight;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_KNIGHT, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
+
+float m_knight(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move(MON_CVAR(knight, speed_run), MON_CVAR(knight, speed_walk), MON_CVAR(knight, speed_stop), knight_anim_run, knight_anim_walk, knight_anim_stand);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ float chance = random();
+ monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
+ if(chance < 0.10 || self.spawnflags & MONSTERFLAG_MINIBOSS)
+ if(self.candrop)
+ {
+ self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
+ self.weapon = WEP_FIREBALL;
+ }
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(knight, health);
+
+ self.monster_loot = spawnfunc_item_armor_big;
+ self.monster_attackfunc = knight_attack;
+ monsters_setframe(knight_anim_stand);
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_sound ("player/lava.wav");
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(KNIGHT_SETTINGS(knight))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_knight(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/hknight.mdl");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ MAGE,
+/* function */ m_mage,
+/* spawnflags */ 0,
+/* mins,maxs */ '-36 -36 -24', '36 36 50',
+/* model */ "mage.dpm",
+/* netname */ "mage",
+/* fullname */ _("Mage")
+);
+
+#define MAGE_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_spike_damage) \
+ MON_ADD_CVAR(monster, attack_spike_radius) \
+ MON_ADD_CVAR(monster, attack_spike_delay) \
+ MON_ADD_CVAR(monster, attack_spike_accel) \
+ MON_ADD_CVAR(monster, attack_spike_decel) \
+ MON_ADD_CVAR(monster, attack_spike_turnrate) \
+ MON_ADD_CVAR(monster, attack_spike_speed_max) \
+ MON_ADD_CVAR(monster, attack_spike_smart) \
+ MON_ADD_CVAR(monster, attack_spike_smart_trace_min) \
+ MON_ADD_CVAR(monster, attack_spike_smart_trace_max) \
+ MON_ADD_CVAR(monster, attack_spike_smart_mindist) \
+ MON_ADD_CVAR(monster, attack_melee_damage) \
+ MON_ADD_CVAR(monster, attack_melee_delay) \
+ MON_ADD_CVAR(monster, attack_grenade_damage) \
+ MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
+ MON_ADD_CVAR(monster, attack_grenade_force) \
+ MON_ADD_CVAR(monster, attack_grenade_radius) \
+ MON_ADD_CVAR(monster, attack_grenade_lifetime) \
+ MON_ADD_CVAR(monster, attack_grenade_chance) \
+ MON_ADD_CVAR(monster, attack_grenade_speed) \
+ MON_ADD_CVAR(monster, attack_grenade_speed_up) \
+ MON_ADD_CVAR(monster, heal_self) \
+ MON_ADD_CVAR(monster, heal_allies) \
+ MON_ADD_CVAR(monster, heal_minhealth) \
+ MON_ADD_CVAR(monster, heal_range) \
+ MON_ADD_CVAR(monster, heal_delay) \
+ MON_ADD_CVAR(monster, shield_time) \
+ MON_ADD_CVAR(monster, shield_delay) \
+ MON_ADD_CVAR(monster, shield_blockpercent) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+MAGE_SETTINGS(mage)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float mage_anim_idle = 0;
+const float mage_anim_walk = 1;
+const float mage_anim_attack = 2;
+const float mage_anim_pain = 3;
+const float mage_anim_death = 4;
+const float mage_anim_run = 5;
+
+void() mage_heal;
+void() mage_shield;
+void() mage_shield_die;
+
+float friend_needshelp(entity e)
+{
+ if(e == world)
+ return FALSE;
+ if(e.health <= 0)
+ return FALSE;
+ if(vlen(e.origin - self.origin) > MON_CVAR(mage, heal_range))
+ return FALSE;
+ if(IsDifferentTeam(e, self))
+ return FALSE;
+ if(e.frozen)
+ return FALSE;
+ if(!IS_PLAYER(e))
+ return (e.health < e.max_health);
+ if(e.items & IT_INVINCIBLE)
+ return FALSE;
+
+ switch(self.skin)
+ {
+ case 0: return (e.health < autocvar_g_balance_health_regenstable);
+ case 1: return ((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max));
+ case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
+ case 3: return (e.health > 0);
+ }
+
+ return FALSE;
+}
+
+void mageattack_melee()
+{
+ monster_melee(self.enemy, MON_CVAR(mage, attack_melee_damage), self.attack_range, DEATH_MONSTER_MAGE, TRUE);
+}
+
+void mage_grenade_explode()
+{
+ pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+
- sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
++ sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
+ RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_grenade_damage), MON_CVAR(mage, attack_grenade_edgedamage), MON_CVAR(mage, attack_grenade_radius), world, MON_CVAR(mage, attack_grenade_force), DEATH_MONSTER_MAGE, other);
+ remove(self);
+}
+
+void mage_grenade_touch()
+{
+ if(IS_PLAYER(other))
+ {
+ PROJECTILE_TOUCH;
+ mage_grenade_explode();
+ return;
+ }
+}
+
+void mage_throw_itemgrenade()
+{
+ makevectors(self.angles);
+
+ entity gren = spawn ();
+ gren.owner = gren.realowner = self;
+ gren.classname = "grenade";
+ gren.bot_dodge = FALSE;
+ gren.movetype = MOVETYPE_BOUNCE;
+ gren.solid = SOLID_TRIGGER;
+ gren.projectiledeathtype = DEATH_MONSTER_MAGE;
+ setorigin(gren, CENTER_OR_VIEWOFS(self));
+ setsize(gren, '-64 -64 -64', '64 64 64');
+
+ gren.nextthink = time + MON_CVAR(mage, attack_grenade_lifetime);
+ gren.think = mage_grenade_explode;
+ gren.use = mage_grenade_explode;
+ gren.touch = mage_grenade_touch;
+
+ gren.missile_flags = MIF_SPLASH | MIF_ARC;
+ W_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(mage, attack_grenade_speed), MON_CVAR(mage, attack_grenade_speed_up), 0, 0, FALSE);
+
+ gren.flags = FL_PROJECTILE;
+
+ setmodel(gren, "models/items/g_h50.md3");
+
+ self.attack_finished_single = time + 1.5;
+}
+
+void mage_spike_explode()
+{
+ self.event_damage = func_null;
+
++ sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
+
+ pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+ RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_spike_damage), MON_CVAR(mage, attack_spike_damage) * 0.5, MON_CVAR(mage, attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other);
+
+ remove (self);
+}
+
+void mage_spike_touch()
+{
+ PROJECTILE_TOUCH;
+
+ mage_spike_explode();
+}
+
+// copied from W_Seeker_Think
+void mage_spike_think()
+{
+ entity e;
+ vector desireddir, olddir, newdir, eorg;
+ float turnrate;
+ float dist;
+ float spd;
+
+ if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
+ {
+ self.projectiledeathtype |= HITTYPE_SPLASH;
+ mage_spike_explode();
+ }
+
+ spd = vlen(self.velocity);
+ spd = bound(
+ spd - MON_CVAR(mage, attack_spike_decel) * frametime,
+ MON_CVAR(mage, attack_spike_speed_max),
+ spd + MON_CVAR(mage, attack_spike_accel) * frametime
+ );
+
+ if (self.enemy != world)
+ if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+ self.enemy = world;
+
+ if (self.enemy != world)
+ {
+ e = self.enemy;
+ eorg = 0.5 * (e.absmin + e.absmax);
+ turnrate = MON_CVAR(mage, attack_spike_turnrate); // how fast to turn
+ desireddir = normalize(eorg - self.origin);
+ olddir = normalize(self.velocity); // get my current direction
+ dist = vlen(eorg - self.origin);
+
+ // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+ if (MON_CVAR(mage, attack_spike_smart) && (dist > MON_CVAR(mage, attack_spike_smart_mindist)))
+ {
+ // Is it a better idea (shorter distance) to trace to the target itself?
+ if ( vlen(self.origin + olddir * self.wait) < dist)
+ traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
+ else
+ traceline(self.origin, eorg, FALSE, self);
+
+ // Setup adaptive tracelength
+ self.wait = bound(MON_CVAR(mage, attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = MON_CVAR(mage, attack_spike_smart_trace_max));
+
+ // Calc how important it is that we turn and add this to the desierd (enemy) dir.
+ desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+ }
+
+ newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+ self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+ }
+ else
+ dist = 0;
+
+ ///////////////
+
+ //self.angles = vectoangles(self.velocity); // turn model in the new flight direction
+ self.nextthink = time;// + 0.05; // csqc projectiles
+ UpdateCSQCProjectile(self);
+}
+
+void mage_spike()
+{
+ entity missile;
+ vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+ makevectors(self.angles);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ missile.think = mage_spike_think;
+ missile.ltime = time + 7;
+ missile.nextthink = time;
+ missile.solid = SOLID_BBOX;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.flags = FL_PROJECTILE;
+ setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
+ setsize (missile, '0 0 0', '0 0 0');
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.enemy = self.enemy;
+ missile.touch = mage_spike_touch;
+
+ CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
+}
+
+void mage_heal()
+{
+ entity head;
+ float washealed = FALSE;
+
+ for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
+ {
+ washealed = TRUE;
+ string fx = "";
+ if(IS_PLAYER(head))
+ {
+ switch(self.skin)
+ {
+ case 0:
+ if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), autocvar_g_balance_health_regenstable);
+ fx = "healing_fx";
+ break;
+ case 1:
+ if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
+ if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
+ if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
+ if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
+ fx = "ammoregen_fx";
+ break;
+ case 2:
+ if(head.armorvalue < autocvar_g_balance_armor_regenstable)
+ {
+ head.armorvalue = bound(0, head.armorvalue + MON_CVAR(mage, heal_allies), autocvar_g_balance_armor_regenstable);
+ fx = "armorrepair_fx";
+ }
+ break;
+ case 3:
+ head.health = bound(0, head.health - ((head == self) ? MON_CVAR(mage, heal_self) : MON_CVAR(mage, heal_allies)), autocvar_g_balance_health_regenstable);
+ fx = "rage";
+ break;
+ }
+
+ pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
+ }
+ else
+ {
+ pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+ head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), head.max_health);
+ WaypointSprite_UpdateHealth(head.sprite, head.health);
+ }
+ }
+
+ if(washealed)
+ {
+ monsters_setframe(mage_anim_attack);
+ self.attack_finished_single = time + MON_CVAR(mage, heal_delay);
+ }
+}
+
+void mage_shield_die()
+{
+ if not(self.weaponentity)
+ return; // why would this be called without a shield?
+
+ self.armorvalue = 1;
+
+ remove(self.weaponentity);
+
+ self.weaponentity = world;
+}
+
+void mage_shield()
+{
+ if(self.weaponentity)
+ return; // already have a shield
+
+ entity shield = spawn();
+
+ shield.owner = self;
+ shield.team = self.team;
+ shield.ltime = time + MON_CVAR(mage, shield_time);
+ shield.health = 70;
+ shield.classname = "shield";
+ shield.effects = EF_ADDITIVE;
+ shield.movetype = MOVETYPE_NOCLIP;
+ shield.solid = SOLID_TRIGGER;
+ shield.avelocity = '7 0 11';
+ shield.scale = self.scale * 0.6;
+
+ setattachment(shield, self, "");
+ setmodel(shield, "models/ctf/shield.md3");
+ setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+
+ self.weaponentity = shield;
+
+ self.lastshielded = time + MON_CVAR(mage, shield_delay);
+
+ monsters_setframe(mage_anim_attack);
+ self.attack_finished_single = time + 1;
+
+ self.armorvalue = MON_CVAR(mage, shield_blockpercent) / 100;
+}
+
+float mage_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ monsters_setframe(mage_anim_attack);
+ self.attack_finished_single = time + MON_CVAR(mage, attack_melee_delay);
+ defer(0.2, mageattack_melee);
+
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(random() < MON_CVAR(mage, attack_grenade_chance) / 100)
+ {
+ mage_throw_itemgrenade();
+ return TRUE;
+ }
+
+ monsters_setframe(mage_anim_attack);
+ self.attack_finished_single = time + MON_CVAR(mage, attack_spike_delay);
+ defer(0.2, mage_spike);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void spawnfunc_monster_mage()
+{
+ self.classname = "monster_mage";
+
+ self.monster_spawnfunc = spawnfunc_monster_mage;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+
+float m_mage(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ entity head;
+ float need_help = FALSE;
+
+ FOR_EACH_PLAYER(head)
+ if(friend_needshelp(head))
+ {
+ need_help = TRUE;
+ break; // found 1 player near us who is low on health
+ }
+ if(!need_help)
+ FOR_EACH_MONSTER(head)
+ if(head != self)
+ if(friend_needshelp(head))
+ {
+ need_help = TRUE;
+ break; // found 1 player near us who is low on health
+ }
+
+ if(self.weaponentity)
+ if(time >= self.weaponentity.ltime)
+ mage_shield_die();
+
+ if(self.health < MON_CVAR(mage, heal_minhealth) || need_help)
+ if(time >= self.attack_finished_single)
+ if(random() < 0.5)
+ mage_heal();
+
+ if(self.enemy)
+ if(self.health < self.max_health)
+ if(time >= self.lastshielded)
+ if(random() < 0.5)
+ mage_shield();
+
+ monster_move(MON_CVAR(mage, speed_run), MON_CVAR(mage, speed_walk), MON_CVAR(mage, speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ monsters_setframe(mage_anim_death);
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(mage, health);
+
+ self.monster_loot = spawnfunc_item_health_large;
+ self.monster_attackfunc = mage_attack;
+ monsters_setframe(mage_anim_walk);
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(MAGE_SETTINGS(mage))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_mage(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/mage.dpm");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ SLIME,
+/* function */ m_slime,
+/* spawnflags */ 0,
+/* mins,maxs */ '-16 -16 -24', '16 16 16',
+/* model */ "slime.dpm",
+/* netname */ "slime",
+/* fullname */ _("Slime")
+);
+
+#define SLIME_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_explode_damage) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+SLIME_SETTINGS(slime)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float slime_anim_walk = 0;
+const float slime_anim_idle = 1;
+const float slime_anim_jump = 2;
+const float slime_anim_fly = 3;
+const float slime_anim_die = 4;
+const float slime_anim_pain = 5;
+
+void slime_touch_jump()
+{
+ if(self.health > 0)
+ if(other.health > 0)
+ if(other.takedamage)
+ if(vlen(self.velocity) > 200)
+ {
+ Damage (self, world, world, MON_CVAR(slime, attack_explode_damage), DEATH_MONSTER_SLIME, self.origin, '0 0 0');
+
+ return;
+ }
+
+ if(trace_dphitcontents)
+ {
+ self.touch = MonsterTouch;
+ self.movetype = MOVETYPE_WALK;
+ }
+}
+
+float slime_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ case MONSTER_ATTACK_RANGED:
+ {
+ makevectors(self.angles);
+ if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void slime_explode()
+{
+ RadiusDamage(self, self, MON_CVAR(slime, attack_explode_damage), 15, MON_CVAR(slime, attack_explode_damage) * 0.7, world, 250, DEATH_MONSTER_SLIME, world);
+ pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
++ sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
+
+ setmodel(self, "");
+}
+
+void slime_dead()
+{
+ self.health = -100; // gibbed
+ slime_explode();
+
+ self.deadflag = DEAD_DEAD;
+ self.think = Monster_Fade;
+ self.nextthink = time + 0.1;
+}
+
+void spawnfunc_monster_slime()
+{
+ self.classname = "monster_slime";
+
+ self.monster_spawnfunc = spawnfunc_monster_slime;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_SLIME, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); }
+
+float m_slime(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move(MON_CVAR(slime, speed_run), MON_CVAR(slime, speed_walk), MON_CVAR(slime, speed_stop), slime_anim_walk, slime_anim_walk, slime_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.think = slime_dead;
+ self.nextthink = time;
+ self.event_damage = func_null;
+ self.movetype = MOVETYPE_NONE;
+ self.takedamage = DAMAGE_NO;
+ self.enemy = world;
+ self.health = 0;
+
+ self.SendFlags |= MSF_MOVE | MSF_STATUS;
+
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(slime, health);
+
+ self.monster_loot = spawnfunc_item_rockets;
+ self.monster_attackfunc = slime_attack;
+ monsters_setframe(slime_anim_idle);
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(SLIME_SETTINGS(slime))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_slime(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/slime.dpm");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- sound(self, CH_SHOTS, snd, VOL_BASE, ATTN_NORM);
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ SPIDER,
+/* function */ m_spider,
+/* spawnflags */ 0,
+/* mins,maxs */ '-18 -18 -25', '18 18 30',
+/* model */ "spider.dpm",
+/* netname */ "spider",
+/* fullname */ _("Spider")
+);
+
+#define SPIDER_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_bite_damage) \
+ MON_ADD_CVAR(monster, attack_bite_delay) \
+ MON_ADD_CVAR(monster, attack_web_damagetime) \
+ MON_ADD_CVAR(monster, attack_web_speed) \
+ MON_ADD_CVAR(monster, attack_web_speed_up) \
+ MON_ADD_CVAR(monster, attack_web_delay) \
+ MON_ADD_CVAR(monster, attack_type) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+SPIDER_SETTINGS(spider)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float spider_anim_idle = 0;
+const float spider_anim_walk = 1;
+const float spider_anim_attack = 2;
+const float spider_anim_attack2 = 3;
+
+.float spider_type; // used to switch between fire & ice attacks
+const float SPIDER_TYPE_ICE = 0;
+const float SPIDER_TYPE_FIRE = 1;
+
+void spider_web_explode()
+{
+ entity e;
+ if(self)
+ {
+ float damg = 0, edamg = 0, rad = 1;
+ switch(self.realowner.spider_type)
+ {
+ case SPIDER_TYPE_ICE:
+ rad = 25;
+ pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
+ break;
+ case SPIDER_TYPE_FIRE:
+ pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+ damg = 15;
+ rad = 25;
+ edamg = 6;
+ break;
+ }
+
+ RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway
+
+ for(e = findradius(self.origin, rad); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+ {
+ switch(self.realowner.spider_type)
+ {
+ case SPIDER_TYPE_ICE:
+ Freeze(e, 0.3, 2, FALSE);
+ break;
+ case SPIDER_TYPE_FIRE:
+ Fire_AddDamage(e, self.realowner, 5 * monster_skill, MON_CVAR(spider, attack_web_damagetime), DEATH_MONSTER_SPIDER_FIRE);
+ break;
+ }
+ }
+
+ remove(self);
+ }
+}
+
+void spider_web_touch()
+{
+ PROJECTILE_TOUCH;
+
+ spider_web_explode();
+}
+
+void spider_shootweb(float ptype)
+{
+ float p = 0;
+ string snd = "";
+ switch(ptype)
+ {
+ case SPIDER_TYPE_ICE:
+ p = PROJECTILE_ELECTRO;
+ snd = "weapons/electro_fire2.wav";
+ break;
+ case SPIDER_TYPE_FIRE:
+ p = PROJECTILE_FIREMINE;
+ snd = "weapons/fireball_fire.wav";
+ break;
+ }
+
+ vector fmins = '-4 -4 -4', fmaxs = '4 4 4';
+
+ monster_makevectors(self.enemy);
+
++ sound(self, CH_SHOTS, snd, VOL_BASE, ATTEN_NORM);
+
+ entity proj = spawn ();
+ proj.classname = "plasma";
+ proj.owner = proj.realowner = self;
+ proj.use = spider_web_touch;
+ proj.think = adaptor_think2use_hittype_splash;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = 0;
+ proj.nextthink = time + 5;
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE;
+ setorigin(proj, CENTER_OR_VIEWOFS(self));
+
+ //proj.glow_size = 50;
+ //proj.glow_color = 45;
+ proj.movetype = MOVETYPE_BOUNCE;
+ W_SetupProjectileVelocityEx(proj, v_forward, v_up, MON_CVAR(spider, attack_web_speed), MON_CVAR(spider, attack_web_speed_up), 0, 0, FALSE);
+ proj.touch = spider_web_touch;
+ setsize(proj, fmins, fmaxs);
+ proj.takedamage = DAMAGE_NO;
+ proj.damageforcescale = 0;
+ proj.health = 500;
+ proj.event_damage = func_null;
+ proj.flags = FL_PROJECTILE;
+ proj.damagedbycontents = TRUE;
+
+ proj.bouncefactor = 0.3;
+ proj.bouncestop = 0.05;
+ proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+ CSQCProjectile(proj, TRUE, p, TRUE);
+}
+
+float spider_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ monster_melee(self.enemy, MON_CVAR(spider, attack_bite_damage), self.attack_range, DEATH_MONSTER_SPIDER, TRUE);
+ monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2);
+ self.attack_finished_single = time + MON_CVAR(spider, attack_bite_delay);
+
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(self.enemy.frozen)
+ return FALSE;
+
+ monsters_setframe(spider_anim_attack2);
+ self.attack_finished_single = time + MON_CVAR(spider, attack_web_delay);
+ spider_shootweb(self.spider_type);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void spawnfunc_monster_spider()
+{
+ self.classname = "monster_spider";
+
+ self.monster_spawnfunc = spawnfunc_monster_spider;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+}
+
+float m_spider(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move(MON_CVAR(spider, speed_run), MON_CVAR(spider, speed_walk), MON_CVAR(spider, speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ monsters_setframe(spider_anim_attack);
+ self.angles += '180 0 0';
+ self.SendFlags |= MSF_ANG;
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(spider, health);
+ if not(self.spider_type) self.spider_type = MON_CVAR(spider, attack_type);
+
+ self.monster_loot = spawnfunc_item_health_medium;
+ self.monster_attackfunc = spider_attack;
+ monsters_setframe(spider_anim_idle);
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(SPIDER_SETTINGS(spider))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_spider(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/spider.dpm");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- self.spawnflags &~= MONSTERFLAG_NORESPAWN; // zombies always respawn
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id */ ZOMBIE,
+/* function */ m_zombie,
+/* spawnflags */ 0,
+/* mins,maxs */ '-18 -18 -25', '18 18 47',
+/* model */ "zombie.dpm",
+/* netname */ "zombie",
+/* fullname */ _("Zombie")
+);
+
+#define ZOMBIE_SETTINGS(monster) \
+ MON_ADD_CVAR(monster, health) \
+ MON_ADD_CVAR(monster, attack_melee_damage) \
+ MON_ADD_CVAR(monster, attack_melee_delay) \
+ MON_ADD_CVAR(monster, attack_leap_damage) \
+ MON_ADD_CVAR(monster, attack_leap_force) \
+ MON_ADD_CVAR(monster, attack_leap_speed) \
+ MON_ADD_CVAR(monster, attack_leap_delay) \
+ MON_ADD_CVAR(monster, speed_stop) \
+ MON_ADD_CVAR(monster, speed_run) \
+ MON_ADD_CVAR(monster, speed_walk)
+
+#ifdef SVQC
+ZOMBIE_SETTINGS(zombie)
+#endif // SVQC
+#else
+#ifdef SVQC
+const float zombie_anim_attackleap = 0;
+const float zombie_anim_attackrun1 = 1;
+const float zombie_anim_attackrun2 = 2;
+const float zombie_anim_attackrun3 = 3;
+const float zombie_anim_attackstanding1 = 4;
+const float zombie_anim_attackstanding2 = 5;
+const float zombie_anim_attackstanding3 = 6;
+const float zombie_anim_blockend = 7;
+const float zombie_anim_blockstart = 8;
+const float zombie_anim_deathback1 = 9;
+const float zombie_anim_deathback2 = 10;
+const float zombie_anim_deathback3 = 11;
+const float zombie_anim_deathfront1 = 12;
+const float zombie_anim_deathfront2 = 13;
+const float zombie_anim_deathfront3 = 14;
+const float zombie_anim_deathleft1 = 15;
+const float zombie_anim_deathleft2 = 16;
+const float zombie_anim_deathright1 = 17;
+const float zombie_anim_deathright2 = 18;
+const float zombie_anim_idle = 19;
+const float zombie_anim_painback1 = 20;
+const float zombie_anim_painback2 = 21;
+const float zombie_anim_painfront1 = 22;
+const float zombie_anim_painfront2 = 23;
+const float zombie_anim_runbackwards = 24;
+const float zombie_anim_runbackwardsleft = 25;
+const float zombie_anim_runbackwardsright = 26;
+const float zombie_anim_runforward = 27;
+const float zombie_anim_runforwardleft = 28;
+const float zombie_anim_runforwardright = 29;
+const float zombie_anim_spawn = 30;
+
+void zombie_attack_leap_touch()
+{
+ if (self.health <= 0)
+ return;
+
+ vector angles_face;
+
+ if(other.takedamage)
+ {
+ angles_face = vectoangles(self.moveto - self.origin);
+ angles_face = normalize(angles_face) * MON_CVAR(zombie, attack_leap_force);
+ Damage(other, self, self, MON_CVAR(zombie, attack_leap_damage) * monster_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
+ self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+ }
+
+ if (trace_dphitcontents)
+ self.touch = MonsterTouch;
+}
+
+float zombie_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ float rand = random(), chosen_anim;
+
+ if(rand < 0.33)
+ chosen_anim = zombie_anim_attackstanding1;
+ else if(rand < 0.66)
+ chosen_anim = zombie_anim_attackstanding2;
+ else
+ chosen_anim = zombie_anim_attackstanding3;
+
+ monsters_setframe(chosen_anim);
+
+ self.attack_finished_single = time + MON_CVAR(zombie, attack_melee_delay);
+
+ monster_melee(self.enemy, MON_CVAR(zombie, attack_melee_damage), self.attack_range, DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
+
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ makevectors(self.angles);
+ if(monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * MON_CVAR(zombie, attack_leap_speed) + '0 0 200', MON_CVAR(zombie, attack_leap_delay)))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void spawnfunc_monster_zombie()
+{
+ self.classname = "monster_zombie";
+
+ self.monster_spawnfunc = spawnfunc_monster_zombie;
+
+ self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
+
+ if(Monster_CheckAppearFlags(self))
+ return;
+
+ if not(monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+}
+
+float m_zombie(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move(MON_CVAR(zombie, speed_run), MON_CVAR(zombie, speed_walk), MON_CVAR(zombie, speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if not(self.health) self.health = MON_CVAR(zombie, health);
+
+ if(self.spawnflags & MONSTERFLAG_NORESPAWN)
++ self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
+
+ self.monster_loot = spawnfunc_item_health_medium;
+ self.monster_attackfunc = zombie_attack;
+ monsters_setframe(zombie_anim_spawn);
+ self.spawn_time = time + 2.1;
+ self.spawnshieldtime = self.spawn_time;
+ self.respawntime = 0.2;
+
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_CONFIG:
+ {
+ MON_CONFIG_SETTINGS(ZOMBIE_SETTINGS(zombie))
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_zombie(float req)
+{
+ switch(req)
+ {
+ case MR_DEATH:
+ {
+ // nothing
+ return TRUE;
+ }
+ case MR_INIT:
+ {
+ precache_model ("models/monsters/zombie.dpm");
+ return TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
+// =========================
+// SVQC Monster Properties
+// =========================
+
+
+void M_Item_Touch ()
+{
+ if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO)
+ {
+ Item_Touch();
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+}
+
+void monster_item_spawn()
+{
+ if(self.monster_loot)
+ self.monster_loot();
+
+ self.gravity = 1;
+ self.velocity = randomvec() * 175 + '0 0 325';
+ self.touch = M_Item_Touch;
+
+ SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+}
+
+void monster_dropitem()
+{
+ if(!self.candrop || !self.monster_loot)
+ return;
+
+ vector org = self.origin + ((self.mins + self.maxs) * 0.5);
+ entity e = spawn();
+
+ setorigin(e, org);
+
+ e.monster_loot = self.monster_loot;
+
+ other = e;
+ MUTATOR_CALLHOOK(MonsterDropItem);
+ e = other;
+
+ e.think = monster_item_spawn;
+ e.nextthink = time + 0.3;
+}
+
+void monsters_setframe(float _frame)
+{
+ if(self.frame == _frame)
+ return;
+
+ self.anim_start_time = time;
+ self.frame = _frame;
+ self.SendFlags |= MSF_ANIM;
+}
+
+float monster_isvalidtarget (entity targ, entity ent)
+{
+ if(!targ || !ent)
+ return FALSE; // someone doesn't exist
+
+ if(targ == ent)
+ return FALSE; // don't attack ourselves
+
+ if(time < game_starttime)
+ return FALSE; // monsters do nothing before the match has started
+
+ WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
+
+ if(vlen(targ.origin - ent.origin) >= ent.target_range)
+ return FALSE; // enemy is too far away
+
+ if(trace_ent != targ)
+ return FALSE; // we can't see the enemy
+
+ if(targ.takedamage == DAMAGE_NO)
+ return FALSE; // enemy can't be damaged
+
+ if(targ.items & IT_INVISIBILITY)
+ return FALSE; // enemy is invisible
+
+ if(substring(targ.classname, 0, 10) == "onslaught_")
+ return FALSE; // don't attack onslaught targets
+
+ if(IS_SPEC(targ) || IS_OBSERVER(targ))
+ return FALSE; // enemy is a spectator
+
+ if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
+ return FALSE; // enemy/self is dead
+
+ if(ent.monster_owner == targ)
+ return FALSE; // don't attack our master
+
+ if(targ.monster_owner == ent)
+ return FALSE; // don't attack our pet
+
+ if(targ.flags & FL_NOTARGET)
+ return FALSE; // enemy can't be targeted
+
+ if not(autocvar_g_monsters_typefrag)
+ if(targ.BUTTON_CHAT)
+ return FALSE; // no typefragging!
+
+ if not(IsDifferentTeam(targ, ent))
+ return FALSE; // enemy is on our team
+
+ if(autocvar_g_monsters_target_infront)
+ if(ent.enemy != targ)
+ {
+ float dot;
+
+ makevectors (ent.angles);
+ dot = normalize (targ.origin - ent.origin) * v_forward;
+
+ if(dot <= 0.3)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+entity FindTarget (entity ent)
+{
+ if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+
+ entity head, closest_target = world;
+ head = findradius(ent.origin, ent.target_range);
+
+ while(head) // find the closest acceptable target to pass to
+ {
+ if(monster_isvalidtarget(head, ent))
+ {
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ vector head_center = CENTER_OR_VIEWOFS(head);
+ vector ent_center = CENTER_OR_VIEWOFS(ent);
+
+ //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+ if(closest_target)
+ {
+ vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+ if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+
+ head = head.chain;
+ }
+
+ return closest_target;
+}
+
+void MonsterTouch ()
+{
+ if(other == world)
+ return;
+
+ if(self.enemy != other)
+ if not(other.flags & FL_MONSTER)
+ if(monster_isvalidtarget(other, self))
+ self.enemy = other;
+}
+
+void monster_sound(string msound, float sound_delay, float delaytoo)
+{
+ if(delaytoo && time < self.msound_delay)
+ return; // too early
+
+ if(msound == "")
+ return; // sound doesn't exist
+
- self.flags &~= FL_ONGROUND;
++ sound(self, CHAN_AUTO, msound, VOL_BASE, ATTEN_NORM);
+
+ self.msound_delay = time + sound_delay;
+}
+
+void monster_precachesounds(entity e)
+{
+ precache_sound(e.msound_idle);
+ precache_sound(e.msound_death);
+ precache_sound(e.msound_attack_melee);
+ precache_sound(e.msound_attack_ranged);
+ precache_sound(e.msound_sight);
+ precache_sound(e.msound_pain);
+}
+
+void monster_setupsounds(string mon)
+{
+ if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav"));
+ if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav"));
+ if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav"));
+ if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav"));
+ if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav"));
+ if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav"));
+}
+
+void monster_makevectors(entity e)
+{
+ vector v;
+
+ v = CENTER_OR_VIEWOFS(e);
+ self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+ self.v_angle_x = -self.v_angle_x;
+
+ makevectors(self.v_angle);
+}
+
+float monster_melee(entity targ, float damg, float er, float deathtype, float dostop)
+{
+ float rdmg = damg * random();
+
+ if (self.health <= 0)
+ return FALSE;
+ if (targ == world)
+ return FALSE;
+
+ if(dostop)
+ {
+ self.velocity_x = 0;
+ self.velocity_y = 0;
+ self.state = MONSTER_STATE_ATTACK_MELEE;
+ self.SendFlags |= MSF_MOVE;
+ }
+
+ monster_makevectors(targ);
+
+ traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
+
+ if(trace_ent.takedamage)
+ Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+
+ return TRUE;
+}
+
+void Monster_CheckMinibossFlag ()
+{
+ if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
+ return;
+
+ float chance = random() * 100;
+
+ // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
+ if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
+ {
+ self.health += autocvar_g_monsters_miniboss_healthboost;
+ if not(self.weapon)
+ self.weapon = WEP_NEX;
+ }
+}
+
+float Monster_CanRespawn(entity ent)
+{
+ other = ent;
+ if(MUTATOR_CALLHOOK(MonsterRespawn))
+ return TRUE; // enabled by a mutator
+
+ if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
+ return FALSE;
+
+ if not(autocvar_g_monsters_respawn)
+ return FALSE;
+
+ return TRUE;
+}
+
+void Monster_Fade ()
+{
+ if(Monster_CanRespawn(self))
+ {
+ self.monster_respawned = TRUE;
+ self.think = self.monster_spawnfunc;
+ self.nextthink = time + self.respawntime;
+ self.deadflag = DEAD_RESPAWNING;
+ if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
+ {
+ self.pos1 = self.origin;
+ self.pos2 = self.angles;
+ }
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+ setorigin(self, self.pos1);
+ self.angles = self.pos2;
+ self.health = self.max_health;
+
+ self.SendFlags |= MSF_MOVE;
+ self.SendFlags |= MSF_STATUS;
+ }
+ else
+ SUB_SetFade(self, time + 3, 1);
+}
+
+float Monster_CanJump (vector vel)
+{
+ if(self.state)
+ return FALSE; // already attacking
+ if not(self.flags & FL_ONGROUND)
+ return FALSE; // not on the ground
+ if(self.health <= 0)
+ return FALSE; // called when dead?
+ if(time < self.attack_finished_single)
+ return FALSE; // still attacking
+
+ vector old = self.velocity;
+
+ self.velocity = vel;
+ tracetoss(self, self);
+ self.velocity = old;
+ if (trace_ent != self.enemy)
+ return FALSE;
+
+ return TRUE;
+}
+
+float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
+{
+ if(!Monster_CanJump(vel))
+ return FALSE;
+
+ monsters_setframe(anm);
+ self.state = MONSTER_STATE_ATTACK_LEAP;
+ self.touch = touchfunc;
+ self.origin_z += 1;
+ self.velocity = vel;
- self.spawnflags &~= MONSTERFLAG_APPEAR;
++ self.flags &= ~FL_ONGROUND;
+
+ self.attack_finished_single = time + anim_finished;
+
+ return TRUE;
+}
+
+void monster_checkattack(entity e, entity targ)
+{
+ if(e == world)
+ return;
+ if(targ == world)
+ return;
+
+ if not(e.monster_attackfunc)
+ return;
+
+ if(time < e.attack_finished_single)
+ return;
+
+ if(vlen(targ.origin - e.origin) <= e.attack_range)
+ if(e.monster_attackfunc(MONSTER_ATTACK_MELEE))
+ {
+ monster_sound(e.msound_attack_melee, 0, FALSE);
+ return;
+ }
+
+ if(vlen(targ.origin - e.origin) > e.attack_range)
+ if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
+ {
+ monster_sound(e.msound_attack_ranged, 0, FALSE);
+ return;
+ }
+}
+
+void monster_use ()
+{
+ if (self.enemy)
+ return;
+ if (self.health <= 0)
+ return;
+
+ if(!monster_isvalidtarget(activator, self))
+ return;
+
+ self.enemy = activator;
+}
+
+.float last_trace;
+.float last_enemycheck; // for checking enemy
+vector monster_pickmovetarget(entity targ)
+{
+ // enemy is always preferred target
+ if(self.enemy)
+ {
+ self.monster_movestate = MONSTER_MOVE_ENEMY;
+ self.last_trace = time + 1.2;
+ return self.enemy.origin;
+ }
+
+ switch(self.monster_moveflags)
+ {
+ case MONSTER_MOVE_OWNER:
+ {
+ self.monster_movestate = MONSTER_MOVE_OWNER;
+ self.last_trace = time + 0.3;
+ if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint")
+ return self.monster_owner.origin;
+ }
+ case MONSTER_MOVE_SPAWNLOC:
+ {
+ self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
+ self.last_trace = time + 2;
+ return self.pos1;
+ }
+ case MONSTER_MOVE_NOMOVE:
+ {
+ self.monster_movestate = MONSTER_MOVE_NOMOVE;
+ self.last_trace = time + 2;
+ return self.origin;
+ }
+ default:
+ case MONSTER_MOVE_WANDER:
+ {
+ vector pos;
+ self.monster_movestate = MONSTER_MOVE_WANDER;
+ self.last_trace = time + 2;
+
+ self.angles_y = random() * 500;
+ makevectors(self.angles);
+ pos = self.origin + v_forward * 600;
+
+ if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ {
+ pos_z = random() * 200;
+ if(random() >= 0.5)
+ pos_z *= -1;
+ }
+
+ if(targ)
+ {
+ self.last_trace = time + 0.5;
+ pos = targ.origin;
+ }
+
+ return pos;
+ }
+ }
+}
+
+void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+{
+ fixedmakevectors(self.angles);
+
+ if(self.target2)
+ self.goalentity = find(world, targetname, self.target2);
+
+ entity targ;
+
+ if(self.frozen)
+ {
+ self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+ self.health = max(1, self.max_health * self.revive_progress);
+
+ if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ movelib_beak_simple(stopspeed);
+
+ self.velocity = '0 0 0';
+ self.enemy = world;
+ self.nextthink = time + 0.1;
+
+ if(self.revive_progress >= 1)
+ Unfreeze(self); // wait for next think before attacking
+
+ // don't bother updating angles here?
+ if(self.origin != self.oldorigin)
+ {
+ self.oldorigin = self.origin;
+ self.SendFlags |= MSF_MOVE;
+ }
+
+ return; // no moving while frozen
+ }
+
+ if(self.flags & FL_SWIM)
+ {
+ if(self.waterlevel < WATERLEVEL_WETFEET)
+ {
+ if(time >= self.last_trace)
+ {
+ self.last_trace = time + 0.4;
+
+ Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
+ self.angles = '90 90 0';
+ if(random() < 0.5)
+ {
+ self.velocity_y += random() * 50;
+ self.velocity_x -= random() * 50;
+ }
+ else
+ {
+ self.velocity_y -= random() * 50;
+ self.velocity_x += random() * 50;
+ }
+ self.velocity_z += random() * 150;
+ }
+
+
+ self.movetype = MOVETYPE_BOUNCE;
+ //self.velocity_z = -200;
+
+ self.SendFlags |= MSF_MOVE | MSF_ANG;
+
+ return;
+ }
+ else
+ {
+ self.angles = '0 0 0';
+ self.movetype = MOVETYPE_WALK;
+ }
+ }
+
+ targ = self.goalentity;
+
+ monster_target = targ;
+ monster_speed_run = runspeed;
+ monster_speed_walk = walkspeed;
+
+ if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+ {
+ runspeed = walkspeed = 0;
+ if(time >= self.spawn_time)
+ monsters_setframe(manim_idle);
+ movelib_beak_simple(stopspeed);
+ self.SendFlags |= MSF_MOVE;
+ return;
+ }
+
+ targ = monster_target;
+ runspeed = monster_speed_run;
+ walkspeed = monster_speed_walk;
+
+ if(teamplay)
+ if(autocvar_g_monsters_teams)
+ if(IsDifferentTeam(self.monster_owner, self))
+ self.monster_owner = world;
+
+ if(self.enemy && self.enemy.health < 1)
+ self.enemy = world; // enough!
+
+ if(time >= self.last_enemycheck)
+ {
+ if not(monster_isvalidtarget(self.enemy, self))
+ self.enemy = world;
+
+ if not(self.enemy)
+ {
+ self.enemy = FindTarget(self);
+ if(self.enemy)
+ monster_sound(self.msound_sight, 0, FALSE);
+ }
+
+ self.last_enemycheck = time + 2;
+ }
+
+ if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
+ self.state = 0;
+
+ if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set
+ if(time >= self.last_trace || self.enemy) // update enemy instantly
+ self.moveto = monster_pickmovetarget(targ);
+
+ if not(self.enemy)
+ monster_sound(self.msound_idle, 5, TRUE);
+
+ if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+ self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+
+ if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
+ {
+ self.state = 0;
+ self.touch = MonsterTouch;
+ }
+
+ //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+
+ float turny = 0;
+ vector real_angle = vectoangles(self.steerto) - self.angles;
+
+ if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+ turny = 20;
+
+ if(self.flags & FL_SWIM)
+ turny = vlen(self.angles - self.moveto);
+
+ if(turny)
+ {
+ turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+ self.angles_y += turny;
+ }
+
+ if(self.state == MONSTER_STATE_ATTACK_MELEE)
+ self.moveto = self.origin;
+ else if(self.enemy)
+ self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
+
+ if not(self.flags & FL_FLY || self.flags & FL_SWIM)
+ self.moveto_z = self.origin_z;
+
+ if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ v_forward = normalize(self.moveto - self.origin);
+
+ if(vlen(self.origin - self.moveto) > 64)
+ {
+ if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ else
+ movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ if(time > self.pain_finished)
+ if(time > self.attack_finished_single)
+ if(vlen(self.velocity) > 0)
+ monsters_setframe((self.enemy) ? manim_run : manim_walk);
+ else
+ monsters_setframe(manim_idle);
+ }
+ else
+ {
+ entity e = find(world, targetname, self.target2);
+ if(e.target2)
+ self.target2 = e.target2;
+ else if(e.target)
+ self.target2 = e.target;
+
+ movelib_beak_simple(stopspeed);
+ if(time > self.attack_finished_single)
+ if(time > self.pain_finished)
+ if (vlen(self.velocity) <= 30)
+ monsters_setframe(manim_idle);
+ }
+
+ monster_checkattack(self, self.enemy);
+
+ if(self.angles != self.oldangles)
+ {
+ self.oldangles = self.angles;
+ self.SendFlags |= MSF_ANG;
+ }
+
+ if(self.origin != self.oldorigin)
+ {
+ self.oldorigin = self.origin;
+ self.SendFlags |= MSF_MOVE;
+ }
+}
+
+void monster_dead_think()
+{
+ self.think = monster_dead_think;
+ self.nextthink = time + 0.3; // don't need to update so often now
+
+ self.deadflag = DEAD_DEAD;
+
+ if(time >= self.ltime)
+ {
+ Monster_Fade();
+ return;
+ }
+
+ self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location
+}
+
+void monsters_setstatus()
+{
+ self.stat_monsters_total = monsters_total;
+ self.stat_monsters_killed = monsters_killed;
+}
+
+void Monster_Appear()
+{
+ self.enemy = activator;
- spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME: PLACEHOLDER
++ self.spawnflags &= ~MONSTERFLAG_APPEAR;
+ self.monster_spawnfunc();
+}
+
+float Monster_CheckAppearFlags(entity ent)
+{
+ if not(ent.spawnflags & MONSTERFLAG_APPEAR)
+ return FALSE;
+
+ ent.think = func_null;
+ ent.nextthink = 0;
+ ent.use = Monster_Appear;
+ ent.flags = FL_MONSTER; // set so this monster can get butchered
+
+ return TRUE;
+}
+
+void monsters_reset()
+{
+ setorigin(self, self.pos1);
+ self.angles = self.pos2;
+
+ self.health = self.max_health;
+ self.velocity = '0 0 0';
+ self.enemy = world;
+ self.goalentity = world;
+ self.attack_finished_single = 0;
+ self.moveto = self.origin;
+
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+float monster_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER);
+ WriteByte(MSG_ENTITY, sf);
+ if(sf & MSF_SETUP)
+ {
+ WriteByte(MSG_ENTITY, self.monsterid);
+
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteAngle(MSG_ENTITY, self.angles_x);
+ WriteAngle(MSG_ENTITY, self.angles_y);
+
+ WriteByte(MSG_ENTITY, self.skin);
+ WriteByte(MSG_ENTITY, self.team);
+ }
+
+ if(sf & MSF_ANG)
+ {
+ WriteShort(MSG_ENTITY, rint(self.angles_x));
+ WriteShort(MSG_ENTITY, rint(self.angles_y));
+ }
+
+ if(sf & MSF_MOVE)
+ {
+ WriteShort(MSG_ENTITY, rint(self.origin_x));
+ WriteShort(MSG_ENTITY, rint(self.origin_y));
+ WriteShort(MSG_ENTITY, rint(self.origin_z));
+
+ WriteShort(MSG_ENTITY, rint(self.velocity_x));
+ WriteShort(MSG_ENTITY, rint(self.velocity_y));
+ WriteShort(MSG_ENTITY, rint(self.velocity_z));
+
+ WriteShort(MSG_ENTITY, rint(self.angles_y));
+ }
+
+ if(sf & MSF_ANIM)
+ {
+ WriteCoord(MSG_ENTITY, self.anim_start_time);
+ WriteByte(MSG_ENTITY, self.frame);
+ }
+
+ if(sf & MSF_STATUS)
+ {
+ WriteByte(MSG_ENTITY, self.skin);
+
+ WriteByte(MSG_ENTITY, self.team);
+
+ WriteByte(MSG_ENTITY, self.deadflag);
+
+ if(self.health <= 0)
+ WriteByte(MSG_ENTITY, 0);
+ else
+ WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
+ }
+
+ return TRUE;
+}
+
+void monster_link(void() spawnproc)
+{
+ Net_LinkEntity(self, TRUE, 0, monster_send);
+ self.think = spawnproc;
+ self.nextthink = time;
+}
+
+void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ self.health -= damage;
+
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+
+ if(self.health <= -100) // 100 health until gone?
+ {
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+}
+
+void monster_die()
+{
+ self.think = monster_dead_think;
+ self.nextthink = self.ticrate;
+ self.ltime = time + 5;
+
+ monster_dropitem();
+
+ WaypointSprite_Kill(self.sprite);
+
+ if(self.weaponentity)
+ {
+ remove(self.weaponentity);
+ self.weaponentity = world;
+ }
+
+ monster_sound(self.msound_death, 0, FALSE);
+
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
+ monsters_killed += 1;
+
+ if(self.candrop && self.weapon)
+ W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
+
+ if(IS_CLIENT(self.realowner))
+ self.realowner.monstercount -= 1;
+
+ self.event_damage = monsters_corpse_damage;
+ self.solid = SOLID_CORPSE;
+ self.takedamage = DAMAGE_AIM;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.moveto = self.origin;
+ self.touch = MonsterTouch; // reset incase monster was pouncing
+
+ if not(self.flags & FL_FLY)
+ self.velocity = '0 0 0';
+
+ self.SendFlags |= MSF_MOVE;
+
+ // number of monsters spawned with mobspawn command
+ totalspawned -= 1;
+
+ MON_ACTION(self.monsterid, MR_DEATH);
+}
+
+void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(self.frozen && deathtype != DEATH_KILL)
+ return;
+
+ if(time < self.pain_finished && deathtype != DEATH_KILL)
+ return;
+
+ if(time < self.spawnshieldtime)
+ return;
+
+ if(deathtype != DEATH_KILL)
+ damage *= self.armorvalue;
+
+ if(self.weaponentity && self.weaponentity.classname == "shield")
+ self.weaponentity.health -= damage;
+
+ self.health -= damage;
+
+ if(self.sprite)
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ self.dmg_time = time;
+
+ if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
++ spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
+
+ self.velocity += force * self.damageforcescale;
+
+ if(deathtype != DEATH_DROWN)
+ {
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+ if (damage > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+ if (damage > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+ }
+
+ if(self.health <= 0)
+ {
+ // Update one more time to avoid waypoint fading without emptying healthbar
+ if(self.sprite)
+ WaypointSprite_UpdateHealth(self.sprite, 0);
+
+ if(deathtype == DEATH_KILL)
+ self.candrop = FALSE; // killed by mobkill command
+
+ // TODO: fix this?
+ activator = attacker;
+ other = self.enemy;
+ SUB_UseTargets();
+ self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
+
+ monster_die();
+
+ frag_attacker = attacker;
+ frag_target = self;
+ MUTATOR_CALLHOOK(MonsterDies);
+
+ if(self.health <= -100) // check if we're already gibbed
+ {
+ Violence_GibSplash(self, 1, 0.5, attacker);
+
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+ }
+
+ self.SendFlags |= MSF_STATUS;
+}
+
+void monster_think()
+{
+ self.think = monster_think;
+ self.nextthink = self.ticrate;
+
+ MON_ACTION(self.monsterid, MR_THINK);
+}
+
+void monster_spawn()
+{
+ MON_ACTION(self.monsterid, MR_SETUP);
+
+ if not(self.monster_respawned)
+ Monster_CheckMinibossFlag();
+
+ self.max_health = self.health;
+ self.pain_finished = self.nextthink;
+ self.anim_start_time = time;
+
+ if not(self.noalign)
+ {
+ setorigin(self, self.origin + '0 0 20');
+ tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
+ setorigin(self, trace_endpos);
+ }
+
+ if not(self.monster_respawned)
+ if not(self.skin)
+ self.skin = rint(random() * 4);
+
+ if not(self.attack_range)
+ self.attack_range = autocvar_g_monsters_attack_range;
+
+ self.pos1 = self.origin;
+
+ monster_setupsounds(self.netname);
+
+ monster_precachesounds(self);
+
+ if(teamplay)
+ self.monster_attack = TRUE; // we can have monster enemies in team games
+
+ if(autocvar_g_monsters_healthbars)
+ {
+ WaypointSprite_Spawn(strzone(strdecolorize(self.monster_name)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+
+ monster_sound(self.msound_spawn, 0, FALSE);
+
+ MUTATOR_CALLHOOK(MonsterSpawn);
+
+ self.think = monster_think;
+ self.nextthink = time + self.ticrate;
+
+ self.SendFlags |= MSF_SETUP;
+}
+
+float monster_initialize(float mon_id, float nodrop)
+{
+ if not(autocvar_g_monsters)
+ return FALSE;
+
+ vector min_s, max_s;
+ entity mon = get_monsterinfo(mon_id);
+
+ // support for quake style removing monsters based on skill
+ switch(monster_skill)
+ {
+ case 1: if(self.spawnflags & MONSTERSKILL_NOTEASY) return FALSE; break;
+ case 2: if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) return FALSE; break;
+ case 3: if(self.spawnflags & MONSTERSKILL_NOTHARD) return FALSE; break;
+ case 4: if(self.spawnflags & MONSTERSKILL_NOTINSANE) return FALSE; break;
+ case 5: if(self.spawnflags & MONSTERSKILL_NOTNIGHTMARE) return FALSE; break;
+ }
+
+ if(self.monster_name == "")
+ self.monster_name = M_NAME(mon_id);
+
+ if(self.team && !teamplay)
+ self.team = 0;
+
+ self.flags = FL_MONSTER;
+
+ if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
+ if not(self.monster_respawned)
+ monsters_total += 1;
+
+ min_s = mon.mins;
+ max_s = mon.maxs;
+
+ self.netname = mon.netname;
+
+ setsize(self, min_s, max_s);
+ self.takedamage = DAMAGE_AIM;
+ self.bot_attack = TRUE;
+ self.iscreature = TRUE;
+ self.teleportable = TRUE;
+ self.damagedbycontents = TRUE;
+ self.monsterid = mon_id;
+ self.damageforcescale = 0;
+ self.event_damage = monsters_damage;
+ self.touch = MonsterTouch;
+ self.use = monster_use;
+ self.solid = SOLID_BBOX;
+ self.movetype = MOVETYPE_WALK;
+ self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime;
+ monsters_spawned += 1;
+ self.enemy = world;
+ self.velocity = '0 0 0';
+ self.moveto = self.origin;
+ self.pos2 = self.angles;
+ self.reset = monsters_reset;
+ self.candrop = TRUE;
+ self.view_ofs = '0 0 1' * (self.maxs_z * 0.5);
+ self.oldtarget2 = self.target2;
+ self.deadflag = DEAD_NO;
+ self.noalign = nodrop;
+ self.spawn_time = time;
+ self.gravity = 1;
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+ if(mon.spawnflags & MONSTER_TYPE_SWIM)
+ self.flags |= FL_SWIM;
+
+ if(mon.spawnflags & MONSTER_TYPE_FLY)
+ {
+ self.flags |= FL_FLY;
+ self.movetype = MOVETYPE_FLY;
+ }
+
+ if not(self.scale)
+ self.scale = 1;
+
+ if(mon.spawnflags & MONSTER_SIZE_BROKEN)
+ self.scale = 1.3;
+
+ if not(self.ticrate)
+ self.ticrate = autocvar_g_monsters_think_delay;
+
+ self.ticrate = bound(sys_frametime, self.ticrate, 60);
+
+ if not(self.armorvalue)
+ self.armorvalue = 1; // multiplier
+
+ if not(self.target_range)
+ self.target_range = autocvar_g_monsters_target_range;
+
+ if not(self.respawntime)
+ self.respawntime = autocvar_g_monsters_respawn_delay;
+
+ if not(self.monster_moveflags)
+ self.monster_moveflags = MONSTER_MOVE_WANDER;
+
+ monster_link(monster_spawn);
+
+ return TRUE;
+}
--- /dev/null
- sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTN_NORM);
+#ifdef CSQC
+float generator_precached;
+.float count;
+
+vector randompos(vector m1, vector m2)
+{
+ vector v;
+ m2 = m2 - m1;
+ v_x = m2_x * random() + m1_x;
+ v_y = m2_y * random() + m1_y;
+ v_z = m2_z * random() + m1_z;
+ return v;
+}
+
+void generator_precache()
+{
+ if(generator_precached)
+ return; // already precached
+
+ precache_model("models/onslaught/generator.md3");
+ precache_model("models/onslaught/generator_dead.md3");
+ precache_model("models/onslaught/generator_dmg1.md3");
+ precache_model("models/onslaught/generator_dmg2.md3");
+ precache_model("models/onslaught/generator_dmg3.md3");
+ precache_model("models/onslaught/generator_dmg4.md3");
+ precache_model("models/onslaught/generator_dmg5.md3");
+ precache_model("models/onslaught/generator_dmg6.md3");
+ precache_model("models/onslaught/generator_dmg7.md3");
+ precache_model("models/onslaught/generator_dmg8.md3");
+ precache_model("models/onslaught/generator_dmg9.md3");
+ precache_model("models/onslaught/generator_dead.md3");
+
+ precache_model("models/onslaught/ons_ray.md3");
+ precache_sound("onslaught/shockwave.wav");
+ precache_sound("weapons/grenade_impact.wav");
+ precache_sound("weapons/rocket_impact.wav");
+
+ precache_model("models/onslaught/gen_gib1.md3");
+ precache_model("models/onslaught/gen_gib2.md3");
+ precache_model("models/onslaught/gen_gib3.md3");
+
+ generator_precached = TRUE;
+}
+
+void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce)
+{
+ self.velocity = self.velocity + vforce;
+}
+
+.float giblifetime;
+
+void gib_draw_noburn()
+{
+ if(time >= self.giblifetime)
+ remove(self);
+}
+
+void gib_draw()
+{
+ if(time >= self.move_time)
+ return;
+
+ self.move_time = time + 0.05;
+
+ if(time > self.giblifetime)
+ {
+ remove(self);
+ return;
+ }
+
+ self.alpha -= 0.05;
+
+ if(self.alpha < 0.1)
+ {
+ remove(self);
+ return;
+ }
+
+ if(random()<0.6)
+ pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1);
+}
+
+void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn)
+{
+ entity gib;
+
+ gib = spawn();
+
+ setmodel(gib, smodel);
+ setorigin(gib, v_from);
+ gib.solid = SOLID_CORPSE;
+ gib.move_movetype = MOVETYPE_BOUNCE;
+ gib.movetype = MOVETYPE_BOUNCE;
+ gib.health = 255;
+ gib.move_velocity = v_to;
+ gib.move_origin = v_from;
+ gib.velocity = v_to;
+ gib.alpha = 1;
+ gib.move_time = time;
+ gib.drawmask = MASK_NORMAL;
+ gib.giblifetime = time + f_lifetime;
+
+ if(b_burn)
+ gib.draw = gib_draw;
+ else
+ gib.draw = gib_draw_noburn;
+}
+
+void onslaught_generator_ray_think()
+{
+ self.nextthink = time + 0.05;
+ if(self.count > 10)
+ {
+ self.think = SUB_Remove;
+ return;
+ }
+
+ if(self.count > 5)
+ self.alpha -= 0.1;
+ else
+ self.alpha += 0.1;
+
+ self.scale += 0.2;
+ self.count +=1;
+}
+
+void onslaught_generator_ray_spawn(vector org)
+{
+ entity e;
+ e = spawn();
+ setmodel(e, "models/onslaught/ons_ray.md3");
+ setorigin(e, org);
+ e.angles = randomvec() * 360;
+ e.alpha = 0;
+ e.scale = random() * 5 + 8;
+ e.think = onslaught_generator_ray_think;
+ e.nextthink = time + 0.05;
+}
+
+void generator_draw()
+{
+ if(self.health > 0)
+ return;
+
+ if(time < self.move_time)
+ return;
+ if(self.count <= 0)
+ return;
+
+ vector org;
+ float i;
+
+ // White shockwave
+ if(self.count==40||self.count==20)
+ {
- sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
++ sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM);
+ pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 6);
+ }
+
+ // Throw some gibs
+ if(random() < 0.3)
+ {
+ i = random();
+ if(i < 0.3)
+ ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, TRUE);
+ else if(i > 0.7)
+ ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, TRUE);
+ else
+ ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, TRUE);
+ }
+
+ // Spawn fire balls
+ for(i=0;i < 10;++i)
+ {
+ org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
+ pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1);
+ }
+
+ // Short explosion sound + small explosion
+ if(random() < 0.25)
+ {
+ te_explosion(self.origin);
- sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
++ sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
+ }
+
+ // Particles
+ org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
+ pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
+
+ // rays
+ if(random() > 0.25 )
+ {
+ onslaught_generator_ray_spawn(self.origin);
+ }
+
+ // Final explosion
+ if(self.count==1)
+ {
+ org = self.origin;
+ te_explosion(org);
+ pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
++ sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
+ }
+
+ self.move_time = time + 0.05;
+
+ self.count -= 1;
+}
+
+.float max_health;
+void generator_damage(float hp)
+{
+ if(hp <= 0)
+ setmodel(self, "models/onslaught/generator_dead.md3");
+ else if(hp < self.max_health * 0.10)
+ setmodel(self, "models/onslaught/generator_dmg9.md3");
+ else if(hp < self.max_health * 0.20)
+ setmodel(self, "models/onslaught/generator_dmg8.md3");
+ else if(hp < self.max_health * 0.30)
+ setmodel(self, "models/onslaught/generator_dmg7.md3");
+ else if(hp < self.max_health * 0.40)
+ setmodel(self, "models/onslaught/generator_dmg6.md3");
+ else if(hp < self.max_health * 0.50)
+ setmodel(self, "models/onslaught/generator_dmg5.md3");
+ else if(hp < self.max_health * 0.60)
+ setmodel(self, "models/onslaught/generator_dmg4.md3");
+ else if(hp < self.max_health * 0.70)
+ setmodel(self, "models/onslaught/generator_dmg3.md3");
+ else if(hp < self.max_health * 0.80)
+ setmodel(self, "models/onslaught/generator_dmg2.md3");
+ else if(hp < self.max_health * 0.90)
+ setmodel(self, "models/onslaught/generator_dmg1.md3");
+ else if(hp <= self.max_health || hp >= self.max_health)
+ setmodel(self, "models/onslaught/generator.md3");
+
+ setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+}
+
+void generator_construct()
+{
+ self.netname = "Generator";
+
+ setorigin(self, self.origin);
+ setmodel(self, "models/onslaught/generator.md3");
+ setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+
+ self.move_movetype = MOVETYPE_NOCLIP;
+ self.solid = SOLID_BBOX;
+ self.movetype = MOVETYPE_NOCLIP;
+ self.move_origin = self.origin;
+ self.move_time = time;
+ self.drawmask = MASK_NORMAL;
+ self.alpha = 1;
+ self.draw = generator_draw;
+}
+
+.vector glowmod;
+void generator_changeteam()
+{
+ if(self.team)
+ {
+ self.glowmod = Team_ColorRGB(self.team - 1);
+ self.teamradar_color = Team_ColorRGB(self.team - 1);
+ self.colormap = 1024 + (self.team - 1) * 17;
+ }
+ else
+ {
+ self.colormap = 1024;
+ self.glowmod = '1 1 0';
+ self.teamradar_color = '1 1 0';
+ }
+}
+
+void ent_generator()
+{
+ float sf;
+ sf = ReadByte();
+
+ if(sf & GSF_SETUP)
+ {
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.health = ReadByte();
+ self.max_health = ReadByte();
+ self.count = ReadByte();
+ self.team = ReadByte();
+
+ if not(self.count)
+ self.count = 40;
+
+ generator_changeteam();
+ generator_precache();
+ generator_construct();
+ }
+
+ if(sf & GSF_STATUS)
+ {
+ float _tmp;
+ _tmp = ReadByte();
+ if(_tmp != self.team)
+ {
+ self.team = _tmp;
+ generator_changeteam();
+ }
+
+ _tmp = ReadByte();
+
+ if(_tmp != self.health)
+ generator_damage(_tmp);
+
+ self.health = _tmp;
+ }
+}
+#endif // CSQC
+
+#ifdef SVQC
+float generator_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR);
+ WriteByte(MSG_ENTITY, sf);
+ if(sf & GSF_SETUP)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteByte(MSG_ENTITY, self.health);
+ WriteByte(MSG_ENTITY, self.max_health);
+ WriteByte(MSG_ENTITY, self.count);
+ WriteByte(MSG_ENTITY, self.team);
+ }
+
+ if(sf & GSF_STATUS)
+ {
+ WriteByte(MSG_ENTITY, self.team);
+
+ if(self.health <= 0)
+ WriteByte(MSG_ENTITY, 0);
+ else
+ WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
+ }
+
+ return TRUE;
+}
+
+void generator_link(void() spawnproc)
+{
+ Net_LinkEntity(self, TRUE, 0, generator_send);
+ self.think = spawnproc;
+ self.nextthink = time;
+}
+#endif // SVQC
void ctf_Handle_Return(entity flag, entity player)
{
// messages and sounds
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
- Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
+ if(IS_PLAYER(player))
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
+
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), (player.flags & FL_MONSTER) ? player.monster_name : player.netname);
- sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
+ sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
ctf_EventLog("return", flag.team, player);
// scoring
_vehicle.team = _vehicle.tur_head.team;
- sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM);
+ sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM);
_vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
_vehicle.phase = time + 1;
+ _vehicle.monster_attack = FALSE;
_vehicle.vehicle_exit(eject);