From: Mario Date: Fri, 30 Aug 2013 11:04:40 +0000 (+1000) Subject: Merge branch 'master' into Mario/monsters X-Git-Tag: xonotic-v0.8.0~241^2^2~126 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=1f988ad58a9fb5cef7ed843ad839b23dd23f6dc9 Merge branch 'master' into Mario/monsters --- 1f988ad58a9fb5cef7ed843ad839b23dd23f6dc9 diff --cc qcsrc/common/constants.qh index 74f3da0793,b3dafaaf7b..385d3b2e12 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@@ -182,9 -180,9 +182,12 @@@ const float STAT_SECRETS_FOUND = 71 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; @@@ -326,60 -324,58 +329,60 @@@ const float ATTEN_MAX = 3.984375 #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 diff --cc qcsrc/common/mapinfo.qh index f59fd2c243,db08fdf07c..d8b84709b3 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@@ -78,16 -78,13 +78,16 @@@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_ REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30") #define g_keepaway IS_GAMETYPE(KEEPAWAY) +REGISTER_GAMETYPE(_("Invasion"),invasion,g_invasion,INVASION,"pointlimit=5") +#define g_invasion IS_GAMETYPE(INVASION) + - float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps - float MAPINFO_FEATURE_VEHICLES = 2; - float MAPINFO_FEATURE_TURRETS = 4; + 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; diff --cc qcsrc/common/monsters/monster/brute.qc index 2f1d912c17,0000000000..a78813beea mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/brute.qc +++ b/qcsrc/common/monsters/monster/brute.qc @@@ -1,269 -1,0 +1,269 @@@ +#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_WEAPON_A, "weapons/uzi_fire.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_SHOTS, "weapons/grenade_impact.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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/monsters/monster/knight.qc index 433a9d3ee8,0000000000..ca74c3c69e mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/knight.qc +++ b/qcsrc/common/monsters/monster/knight.qc @@@ -1,343 -1,0 +1,343 @@@ +#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.enemy, CHAN_AUTO, "player/lava.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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/monsters/monster/mage.qc index b729b381aa,0000000000..67dba01efa mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@@ -1,490 -1,0 +1,490 @@@ +#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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/monsters/monster/slime.qc index 93897b4804,0000000000..31680a278d mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/slime.qc +++ b/qcsrc/common/monsters/monster/slime.qc @@@ -1,170 -1,0 +1,170 @@@ +#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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/monsters/monster/spider.qc index 7e901af5ac,0000000000..42de2aca49 mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@@ -1,243 -1,0 +1,243 @@@ +#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, ATTN_NORM); ++ 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 diff --cc qcsrc/common/monsters/monster/zombie.qc index 53b392ca2c,0000000000..34586bd245 mode 100644,000000..100644 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@@ -1,195 -1,0 +1,195 @@@ +#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.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 diff --cc qcsrc/common/monsters/sv_monsters.qc index df30e53468,0000000000..4183fe1dd0 mode 100644,000000..100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@@ -1,1056 -1,0 +1,1056 @@@ +// ========================= +// 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 + - sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM); ++ 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.flags &~= FL_ONGROUND; ++ 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; - self.spawnflags &~= MONSTERFLAG_APPEAR; ++ 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, ATTN_NORM); // FIXME: PLACEHOLDER ++ 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; +} diff --cc qcsrc/server/generator.qc index 62fd76e9a6,0000000000..98f5ba4722 mode 100644,000000..100644 --- a/qcsrc/server/generator.qc +++ b/qcsrc/server/generator.qc @@@ -1,355 -1,0 +1,355 @@@ +#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, "onslaught/shockwave.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/grenade_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, ATTN_NORM); ++ 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 diff --cc qcsrc/server/mutators/gamemode_ctf.qc index 988462940c,d2fc1e9936..f09a0c2627 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@@ -451,11 -451,9 +451,11 @@@ void ctf_Handle_Capture(entity flag, en 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 diff --cc qcsrc/server/vehicles/vehicles.qc index 5dfaf0f095,75fa9407e6..7226fe5679 --- a/qcsrc/server/vehicles/vehicles.qc +++ b/qcsrc/server/vehicles/vehicles.qc @@@ -817,10 -810,9 +817,10 @@@ void vehicles_exit(float eject _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);