X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fmutators%2Fmutator_nades.qc;h=70c4578bd02baea2c19b7185add6ddaade69913b;hb=0a92453278b65a165e83e64a989b5e7de38ccf02;hp=5ab6df75dc720e2d6b385ed665d1cf0ccf9fcfc3;hpb=451c0fbf5473c6acc88bafaa0e9c14e5afd3764e;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc index 5ab6df75d..70c4578bd 100644 --- a/qcsrc/server/mutators/mutator_nades.qc +++ b/qcsrc/server/mutators/mutator_nades.qc @@ -1,31 +1,20 @@ +.entity nade_spawnloc; + void nade_timer_think() { self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10); self.nextthink = time; if(!self.owner || wasfreed(self.owner)) remove(self); - } void nade_burn_spawn(entity _nade) { - float p; - - switch(_nade.realowner.team) - { - case NUM_TEAM_1: p = PROJECTILE_NADE_RED_BURN; break; - case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE_BURN; break; - case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW_BURN; break; - case NUM_TEAM_4: p = PROJECTILE_NADE_PINK_BURN; break; - default: p = PROJECTILE_NADE_BURN; break; - } - - CSQCProjectile(_nade, TRUE, p, TRUE); + CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, TRUE), TRUE); } void nade_spawn(entity _nade) { - float p; entity timer = spawn(); setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3"); setattachment(timer, _nade, ""); @@ -38,47 +27,527 @@ void nade_spawn(entity _nade) timer.owner = _nade; timer.skin = 10; - switch(_nade.realowner.team) + _nade.effects |= EF_LOWPRECISION; + + CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, FALSE), TRUE); +} + +void napalm_damage(float dist, float damage, float edgedamage, float burntime) +{ + entity e; + float d; + vector p; + + if ( damage < 0 ) + return; + + RandomSelection_Init(); + for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain) + if(e.takedamage == DAMAGE_AIM) + if(self.realowner != e || autocvar_g_nades_napalm_selfdamage) + if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self)) + if(!e.frozen) + { + p = e.origin; + p_x += e.mins_x + random() * (e.maxs_x - e.mins_x); + p_y += e.mins_y + random() * (e.maxs_y - e.mins_y); + p_z += e.mins_z + random() * (e.maxs_z - e.mins_z); + d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); + if(d < dist) + { + e.fireball_impactvec = p; + RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); + } + } + if(RandomSelection_chosen_ent) + { + d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); + d = damage + (edgedamage - damage) * (d / dist); + Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); + //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec); + pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); + } +} + + +void napalm_ball_think() +{ + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time > self.pushltime) { - case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break; - case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break; - case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break; - case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break; - default: p = PROJECTILE_NADE; break; + remove(self); + return; } - CSQCProjectile(_nade, TRUE, p, TRUE); + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + } + + self.angles = vectoangles(self.velocity); + + napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, + autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; +} + + +void nade_napalm_ball() +{ + entity proj; + vector kick; + + spamsound(self, CH_SHOTS, "weapons/fireball_fire.wav", VOL_BASE, ATTEN_NORM); + + proj = spawn (); + proj.owner = self.owner; + proj.realowner = self.realowner; + proj.team = self.owner.team; + proj.classname = "grenade"; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; + proj.movetype = MOVETYPE_BOUNCE; + proj.projectiledeathtype = DEATH_NADE_NAPALM; + PROJECTILE_MAKETRIGGER(proj); + setmodel(proj, "null"); + proj.scale = 1;//0.5; + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, self.origin); + proj.think = napalm_ball_think; + proj.nextthink = time; + proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; + proj.effects = EF_LOWPRECISION | EF_FLAME; + + kick_x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick_y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick_z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; + proj.velocity = kick; + + proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + //CSQCProjectile(proj, TRUE, PROJECTILE_NAPALM_FIRE, TRUE); +} + + +void napalm_fountain_think() +{ + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + remove(self); + return; + } + + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + + UpdateCSQCProjectile(self); + } + + napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, + autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; + if(time >= self.nade_special_time) + { + self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; + nade_napalm_ball(); + } +} + +void nade_napalm_boom() +{ + entity fountain; + local float c; + for (c = 0; c < autocvar_g_nades_napalm_ball_count; c ++) + nade_napalm_ball(); + + + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = napalm_fountain_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; + fountain.pushltime = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_NAPALM; + fountain.bot_dodge = TRUE; + fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; + fountain.nade_special_time = time; + setsize(fountain, '-16 -16 -16', '16 16 16'); + CSQCProjectile(fountain, TRUE, PROJECTILE_NAPALM_FOUNTAIN, TRUE); +} + +void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) +{ + frost_target.frozen_by = freezefield.realowner; + pointparticles(particleeffectnum("electro_impact"), frost_target.origin, '0 0 0', 1); + Freeze(frost_target, 1/freeze_time, 3, FALSE); + if(frost_target.ballcarried) + if(g_keepaway) { ka_DropEvent(frost_target); } + else { DropBall(frost_target.ballcarried, frost_target.origin, frost_target.velocity);} + if(frost_target.flagcarried) { ctf_Handle_Throw(frost_target, world, DROP_THROW); } + if(frost_target.nade) { toss_nade(frost_target, '0 0 0', time + 0.05); } + + kh_Key_DropAll(frost_target, FALSE); +} + +void nade_ice_think() +{ + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + if ( autocvar_g_nades_ice_explode ) + { + string expef; + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "nade_red_explode"; break; + case NUM_TEAM_2: expef = "nade_blue_explode"; break; + case NUM_TEAM_3: expef = "nade_yellow_explode"; break; + case NUM_TEAM_4: expef = "nade_pink_explode"; break; + default: expef = "nade_neutral_explode"; break; + } + pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } + remove(self); + return; + } + + + self.nextthink = time+0.1; + + // gaussian + float randomr; + randomr = random(); + randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius; + float randomw; + randomw = random()*M_PI*2; + vector randomp; + randomp_x = randomr*cos(randomw); + randomp_y = randomr*sin(randomw); + randomp_z = 1; + pointparticles(particleeffectnum("electro_muzzleflash"), self.origin + randomp, '0 0 0', 1); + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.7; + + + pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); + pointparticles(particleeffectnum("icefield"), self.origin, '0 0 0', 1); + } + + + float current_freeze_time = self.ltime - time - 0.1; + + entity e; + for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain) + if(e != self) + if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner)) + if(e.takedamage && e.deadflag == DEAD_NO) + if(e.health > 0) + if(!e.revival_time || ((time - e.revival_time) >= 1.5)) + if(!e.frozen) + if(current_freeze_time > 0) + nade_ice_freeze(self, e, current_freeze_time); +} + +void nade_ice_boom() +{ + entity fountain; + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = nade_ice_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_ice_freeze_time; + fountain.pushltime = fountain.wait = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_ICE; + fountain.bot_dodge = FALSE; + setsize(fountain, '-16 -16 -16', '16 16 16'); + fountain.nade_special_time = time+0.3; + fountain.angles = self.angles; + + if ( autocvar_g_nades_ice_explode ) + { + setmodel(fountain, "models/grenademodel.md3"); + entity timer = spawn(); + setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3"); + setattachment(timer, fountain, ""); + timer.classname = "nade_timer"; + timer.colormap = self.colormap; + timer.glowmod = self.glowmod; + timer.think = nade_timer_think; + timer.nextthink = time; + timer.wait = fountain.ltime; + timer.owner = fountain; + timer.skin = 10; + } + else + setmodel(fountain, "null"); +} + +void nade_translocate_boom() +{ + if(self.realowner.vehicle) + return; + + vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins_z - 24); + tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner); + locout = trace_endpos; + + makevectors(self.realowner.angles); + + entity oldself = self; + self = self.realowner; + MUTATOR_CALLHOOK(PortalTeleport); + self.realowner = self; + self = oldself; + + TeleportPlayer(self, self.realowner, locout, self.realowner.mangle, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); +} + +void nade_spawn_boom() +{ + entity spawnloc = spawn(); + setorigin(spawnloc, self.origin); + setsize(spawnloc, self.realowner.mins, self.realowner.maxs); + spawnloc.movetype = MOVETYPE_NONE; + spawnloc.solid = SOLID_NOT; + spawnloc.drawonlytoclient = self.realowner; + spawnloc.effects = EF_STARDUST; + spawnloc.cnt = autocvar_g_nades_spawn_count; + + if(self.realowner.nade_spawnloc) + { + remove(self.realowner.nade_spawnloc); + self.realowner.nade_spawnloc = world; + } + + self.realowner.nade_spawnloc = spawnloc; +} + +void nade_heal_think() +{ + if(time >= self.ltime) + { + remove(self); + return; + } + + self.nextthink = time; + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.25; + self.nade_show_particles = 1; + } + else + self.nade_show_particles = 0; +} + +void nade_heal_touch() +{ + float maxhealth; + float health_factor; + if(IS_PLAYER(other) || (other.flags & FL_MONSTER)) + if(other.deadflag == DEAD_NO) + if(!other.frozen) + { + health_factor = autocvar_g_nades_heal_rate*frametime/2; + if ( other != self.realowner ) + { + if ( SAME_TEAM(other,self) ) + health_factor *= autocvar_g_nades_heal_friend; + else + health_factor *= autocvar_g_nades_heal_foe; + } + if ( health_factor > 0 ) + { + maxhealth = (other.flags & FL_MONSTER) ? other.max_health : g_pickup_healthmega_max; + if ( other.health < maxhealth ) + { + if ( self.nade_show_particles ) + pointparticles(particleeffectnum("healing_fx"), other.origin, '0 0 0', 1); + other.health = min(other.health+health_factor, maxhealth); + } + other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); + } + else if ( health_factor < 0 ) + { + Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL,other.origin,'0 0 0'); + } + + } + + if ( IS_REAL_CLIENT(other) || (other.vehicle_flags & VHF_ISVEHICLE) ) + { + entity show_red = (other.vehicle_flags & VHF_ISVEHICLE) ? other.owner : other; + show_red.stat_healing_orb = time+0.1; + show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime; + } +} + +void nade_heal_boom() +{ + entity healer; + healer = spawn(); + healer.owner = self.owner; + healer.realowner = self.realowner; + setorigin(healer, self.origin); + healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar + healer.ltime = time + healer.healer_lifetime; + healer.team = self.realowner.team; + healer.bot_dodge = FALSE; + healer.solid = SOLID_TRIGGER; + healer.touch = nade_heal_touch; + + setmodel(healer, "models/ctf/shield.md3"); + healer.healer_radius = autocvar_g_nades_nade_radius; + vector size = '1 1 1' * healer.healer_radius / 2; + setsize(healer,-size,size); + + Net_LinkEntity(healer, TRUE, 0, healer_send); + + healer.think = nade_heal_think; + healer.nextthink = time; + healer.SendFlags |= 1; +} + +void nade_monster_boom() +{ + entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, FALSE, FALSE, 1); + + if(autocvar_g_nades_pokenade_monster_lifetime > 0) + e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; + e.monster_skill = MONSTER_SKILL_INSANE; } void nade_boom() { string expef; + float nade_blast = 1; - switch(self.realowner.team) + switch ( self.nade_type ) { - case NUM_TEAM_1: expef = "nade_red_explode"; break; - case NUM_TEAM_2: expef = "nade_blue_explode"; break; - case NUM_TEAM_3: expef = "nade_yellow_explode"; break; - case NUM_TEAM_4: expef = "nade_pink_explode"; break; - default: expef = "nade_explode"; break; + case NADE_TYPE_NAPALM: + nade_blast = autocvar_g_nades_napalm_blast; + expef = "explosion_medium"; + break; + case NADE_TYPE_ICE: + nade_blast = 0; + expef = "electro_combo"; // hookbomb_explode electro_combo bigplasma_impact + break; + case NADE_TYPE_TRANSLOCATE: + nade_blast = 0; + expef = ""; + break; + case NADE_TYPE_MONSTER: + case NADE_TYPE_SPAWN: + nade_blast = 0; + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "spawn_event_red"; break; + case NUM_TEAM_2: expef = "spawn_event_blue"; break; + case NUM_TEAM_3: expef = "spawn_event_yellow"; break; + case NUM_TEAM_4: expef = "spawn_event_pink"; break; + default: expef = "spawn_event_neutral"; break; + } + break; + case NADE_TYPE_HEAL: + nade_blast = 0; + expef = "spawn_event_red"; + break; + + default: + case NADE_TYPE_NORMAL: + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "nade_red_explode"; break; + case NUM_TEAM_2: expef = "nade_blue_explode"; break; + case NUM_TEAM_3: expef = "nade_yellow_explode"; break; + case NUM_TEAM_4: expef = "nade_pink_explode"; break; + default: expef = "nade_neutral_explode"; break; + } } + pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); - Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + if(nade_blast) + { + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } - self.takedamage = DAMAGE_NO; - RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + switch ( self.nade_type ) + { + case NADE_TYPE_NAPALM: nade_napalm_boom(); break; + case NADE_TYPE_ICE: nade_ice_boom(); break; + case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break; + case NADE_TYPE_SPAWN: nade_spawn_boom(); break; + case NADE_TYPE_HEAL: nade_heal_boom(); break; + case NADE_TYPE_MONSTER: nade_monster_boom(); break; + } remove(self); } void nade_touch() { + if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } PROJECTILE_TOUCH; //setsize(self, '-2 -2 -2', '2 2 2'); //UpdateCSQCProjectile(self); @@ -101,26 +570,28 @@ void nade_beep() void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { - if(DEATH_ISWEAPON(deathtype, WEP_LASER)) + if(self.nade_type == NADE_TYPE_TRANSLOCATE || self.nade_type == NADE_TYPE_SPAWN) + return; + + if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) return; - if(DEATH_ISWEAPON(deathtype, WEP_NEX) || DEATH_ISWEAPON(deathtype, WEP_MINSTANEX)) + if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) { force *= 6; damage = self.max_health * 0.55; } - if(DEATH_ISWEAPON(deathtype, WEP_UZI)) + if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN)) damage = self.max_health * 0.1; - if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && !(deathtype & HITTYPE_SECONDARY)) - damage = self.max_health * 1.1; - - if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && (deathtype & HITTYPE_SECONDARY)) + if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && (deathtype & HITTYPE_SECONDARY)) // WEAPONTODO { damage = self.max_health * 0.1; - force *= 15; + force *= 10; } + else + damage = self.max_health * 1.1; self.velocity += force; @@ -134,8 +605,10 @@ void nade_damage(entity inflictor, entity attacker, float damage, float deathtyp self.think = nade_beep; } - self.health -= damage; - self.realowner = attacker; + self.health -= damage; + + if ( self.nade_type != NADE_TYPE_HEAL || IS_PLAYER(attacker) ) + self.realowner = attacker; if(self.health <= 0) W_PrepareExplosionByDamage(attacker, nade_boom); @@ -145,6 +618,9 @@ void nade_damage(entity inflictor, entity attacker, float damage, float deathtyp void toss_nade(entity e, vector _velocity, float _time) { + if(e.nade == world) + return; + entity _nade = e.nade; e.nade = world; @@ -157,10 +633,9 @@ void toss_nade(entity e, vector _velocity, float _time) Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES); - //setorigin(_nade, CENTER_OR_VIEWOFS(e) + (v_right * 10) * -1); setorigin(_nade, w_shotorg + (v_right * 25) * -1); - setmodel(_nade, "models/weapons/v_ok_grenade.md3"); - setattachment(_nade, world, ""); + //setmodel(_nade, "models/weapons/v_ok_grenade.md3"); + //setattachment(_nade, world, ""); PROJECTILE_MAKETRIGGER(_nade); setsize(_nade, '-16 -16 -16', '16 16 16'); _nade.movetype = MOVETYPE_BOUNCE; @@ -169,8 +644,8 @@ void toss_nade(entity e, vector _velocity, float _time) if (trace_startsolid) setorigin(_nade, e.origin); - if(e.crouch) - _nade.velocity = '0 0 -10'; + if(self.v_angle_x >= 70 && self.v_angle_x <= 110) + _nade.velocity = '0 0 100'; else if(autocvar_g_nades_nade_newton_style == 1) _nade.velocity = e.velocity + _velocity; else if(autocvar_g_nades_nade_newton_style == 2) @@ -178,12 +653,15 @@ void toss_nade(entity e, vector _velocity, float _time) else _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, TRUE); - //_nade.solid = SOLID_BBOX; // TODO: remember why this was needed _nade.touch = nade_touch; _nade.health = autocvar_g_nades_nade_health; _nade.max_health = _nade.health; _nade.takedamage = DAMAGE_AIM; _nade.event_damage = nade_damage; + _nade.customizeentityforclient = func_null; + _nade.exteriormodeltoclient = world; + _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + _nade.traileffectnum = 0; _nade.teleportable = TRUE; _nade.pushable = TRUE; _nade.gravity = 1; @@ -191,6 +669,9 @@ void toss_nade(entity e, vector _velocity, float _time) _nade.damagedbycontents = TRUE; _nade.angles = vectoangles(_nade.velocity); _nade.flags = FL_PROJECTILE; + _nade.projectiledeathtype = DEATH_NADE; + _nade.toss_time = time; + _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); nade_spawn(_nade); @@ -201,6 +682,56 @@ void toss_nade(entity e, vector _velocity, float _time) } e.nade_refire = time + autocvar_g_nades_nade_refire; + e.nade_timer = 0; +} + +void nades_GiveBonus(entity player, float score) +{ + if (autocvar_g_nades) + if (autocvar_g_nades_bonus) + if (IS_REAL_CLIENT(player)) + if (IS_PLAYER(player) && player.bonus_nades < autocvar_g_nades_bonus_max) + if (player.frozen == 0) + if (player.deadflag == DEAD_NO) + { + if ( player.bonus_nade_score < 1 ) + player.bonus_nade_score += score/autocvar_g_nades_bonus_score_max; + + if ( player.bonus_nade_score >= 1 ) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); + play2(player,"kh/alarm.wav"); + player.bonus_nades++; + player.bonus_nade_score -= 1; + } + } +} + +void nades_RemoveBonus(entity player) +{ + player.bonus_nades = player.bonus_nade_score = 0; +} + +float nade_customize() +{ + //if(IS_SPEC(other)) { return FALSE; } + if(other == self.realowner || (IS_SPEC(other) && other.enemy == self.realowner)) + { + // somewhat hide the model, but keep the glow + //self.effects = 0; + if(self.traileffectnum) + self.traileffectnum = 0; + self.alpha = -1; + } + else + { + //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + if(!self.traileffectnum) + self.traileffectnum = particleeffectnum(Nade_TrailEffect(Nade_ProjectileFromID(self.nade_type, FALSE), self.team)); + self.alpha = 1; + } + + return TRUE; } void nade_prime() @@ -211,29 +742,53 @@ void nade_prime() if(self.fake_nade) remove(self.fake_nade); - self.nade = spawn(); - setmodel(self.nade, "null"); - setattachment(self.nade, self, "bip01 l hand"); - self.nade.classname = "nade"; - self.nade.realowner = self; - self.nade.colormap = self.colormap; - self.nade.glowmod = self.glowmod; - self.nade.wait = time + autocvar_g_nades_nade_lifetime; - self.nade.lifetime = time; - self.nade.think = nade_beep; - self.nade.nextthink = max(self.nade.wait - 3, time); - self.nade.projectiledeathtype = DEATH_NADE; - - self.fake_nade = spawn(); - setmodel(self.fake_nade, "models/weapons/h_ok_grenade.iqm"); - setattachment(self.fake_nade, self.weaponentity, ""); - self.fake_nade.classname = "fake_nade"; - //self.fake_nade.viewmodelforclient = self; - self.fake_nade.realowner = self.fake_nade.owner = self; - self.fake_nade.colormap = self.colormap; - self.fake_nade.glowmod = self.glowmod; - self.fake_nade.think = SUB_Remove; - self.fake_nade.nextthink = self.nade.wait; + entity n = spawn(), fn = spawn(); + + n.classname = "nade"; + fn.classname = "fake_nade"; + + if(self.items & IT_STRENGTH && autocvar_g_nades_bonus_onstrength) + n.nade_type = self.nade_type; + else if (self.bonus_nades >= 1) + { + n.nade_type = self.nade_type; + n.pokenade_type = self.pokenade_type; + self.bonus_nades -= 1; + } + else + { + n.nade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_nade_type : autocvar_g_nades_nade_type); + n.pokenade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); + } + + n.nade_type = bound(1, n.nade_type, NADE_TYPE_LAST); + + setmodel(n, "models/weapons/v_ok_grenade.md3"); + //setattachment(n, self, "bip01 l hand"); + n.exteriormodeltoclient = self; + n.customizeentityforclient = nade_customize; + n.traileffectnum = particleeffectnum(Nade_TrailEffect(Nade_ProjectileFromID(n.nade_type, FALSE), self.team)); + n.colormod = Nade_Color(n.nade_type); + n.realowner = self; + n.colormap = self.colormap; + n.glowmod = self.glowmod; + n.wait = time + autocvar_g_nades_nade_lifetime; + n.lifetime = time; + n.think = nade_beep; + n.nextthink = max(n.wait - 3, time); + n.projectiledeathtype = DEATH_NADE; + + setmodel(fn, "models/weapons/h_ok_grenade.iqm"); + setattachment(fn, self.weaponentity, ""); + fn.realowner = fn.owner = self; + fn.colormod = Nade_Color(n.nade_type); + fn.colormap = self.colormap; + fn.glowmod = self.glowmod; + fn.think = SUB_Remove; + fn.nextthink = n.wait; + + self.nade = n; + self.fake_nade = fn; } float CanThrowNade() @@ -286,21 +841,55 @@ void nades_CheckThrow() } } +void nades_Clear(entity player) +{ + if(player.nade) + remove(player.nade); + if(player.fake_nade) + remove(player.fake_nade); + + player.nade = player.fake_nade = world; + player.nade_timer = 0; +} + +MUTATOR_HOOKFUNCTION(nades_CheckThrow) +{ + if(MUTATOR_RETURNVALUE) { nades_CheckThrow(); } + return FALSE; +} + MUTATOR_HOOKFUNCTION(nades_VehicleEnter) { - if(other.nade) - toss_nade(other, '0 0 100', max(other.nade.wait, time + 0.05)); + if(vh_player.nade) + toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05)); return FALSE; } MUTATOR_HOOKFUNCTION(nades_PlayerPreThink) { - float key_pressed = ((g_grappling_hook || client_hasweapon(self, WEP_HOOK, FALSE, FALSE) || (weaponsInMap & WEPSET_HOOK)) ? self.button16 : self.BUTTON_HOOK); + if(!IS_PLAYER(self)) { return FALSE; } + + float key_pressed = self.BUTTON_HOOK; + float time_score; + + if(g_grappling_hook || client_hasweapon(self, WEP_HOOK, FALSE, FALSE) || (weaponsInMap & WEPSET_HOOK) || g_jetpack || self.items & IT_JETPACK) + key_pressed = self.button16; // if hook/jetpack is enabled, use an alternate key + + if(self.nade) + { + self.nade_timer = bound(0, (time - self.nade.lifetime) / autocvar_g_nades_nade_lifetime, 1); + //print(sprintf("%d %d\n", self.nade_timer, time - self.nade.lifetime)); + makevectors(self.angles); + self.nade.velocity = self.velocity; + + setorigin(self.nade, self.origin + self.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0); + self.nade.angles_y = self.angles_y; + } if(self.nade) - if(self.nade.wait - 0.1 <= time) - toss_nade(self, '0 0 0', time + 0.05); + if(self.nade.wait - 0.1 <= time) + toss_nade(self, '0 0 0', time + 0.05); if(CanThrowNade()) if(self.nade_refire < time) @@ -323,6 +912,88 @@ MUTATOR_HOOKFUNCTION(nades_PlayerPreThink) } } + if(IS_PLAYER(self)) + { + if ( autocvar_g_nades_bonus && autocvar_g_nades ) + { + entity key; + float key_count = 0; + FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; } + + if(self.flagcarried || self.ballcarried) // this player is important + time_score = autocvar_g_nades_bonus_score_time_flagcarrier; + else + time_score = autocvar_g_nades_bonus_score_time; + + if(key_count) + time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding + + if(autocvar_g_nades_bonus_client_select) + { + self.nade_type = self.cvar_cl_nade_type; + self.pokenade_type = self.cvar_cl_pokenade_type; + } + else + { + self.nade_type = autocvar_g_nades_bonus_type; + self.pokenade_type = autocvar_g_nades_pokenade_monster_type; + } + + self.nade_type = bound(1, self.nade_type, NADE_TYPE_LAST); + + if(self.bonus_nade_score >= 0 && autocvar_g_nades_bonus_score_max) + nades_GiveBonus(self, time_score / autocvar_g_nades_bonus_score_max); + } + else + { + self.bonus_nades = self.bonus_nade_score = 0; + } + } + + float n = 0; + entity o = world; + if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) + n = -1; + else + { + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) if(self != other) + { + if(other.deadflag == DEAD_NO) + if(other.frozen == 0) + if(SAME_TEAM(other, self)) + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; + if(self.frozen == 1) + other.reviving = TRUE; + ++n; + } + } + } + + if(n && self.frozen == 3) // OK, there is at least one teammate reviving us + { + self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + self.health = max(1, self.revive_progress * start_health); + + if(self.revive_progress >= 1) + { + Unfreeze(self); + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); + Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); + } + + FOR_EACH_PLAYER(other) if(other.reviving) + { + other.revive_progress = self.revive_progress; + other.reviving = FALSE; + } + } + return FALSE; } @@ -333,24 +1004,113 @@ MUTATOR_HOOKFUNCTION(nades_PlayerSpawn) else self.nade_refire = time + autocvar_g_nades_nade_refire; + if(autocvar_g_nades_bonus_client_select) + self.nade_type = self.cvar_cl_nade_type; + + self.nade_timer = 0; + + if(self.nade_spawnloc) + { + setorigin(self, self.nade_spawnloc.origin); + self.nade_spawnloc.cnt -= 1; + + if(self.nade_spawnloc.cnt <= 0) + { + remove(self.nade_spawnloc); + self.nade_spawnloc = world; + } + } + return FALSE; } MUTATOR_HOOKFUNCTION(nades_PlayerDies) { - if(self.nade) - toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05)); + if(frag_target.nade) + if(!frag_target.frozen || !autocvar_g_freezetag_revive_nade) + toss_nade(frag_target, '0 0 100', max(frag_target.nade.wait, time + 0.05)); + + float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor); + + if(IS_PLAYER(frag_attacker)) + { + if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target) + nades_RemoveBonus(frag_attacker); + else if(frag_target.flagcarried) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium); + else if(autocvar_g_nades_bonus_score_spree && frag_attacker.killcount > 1) + { + #define SPREE_ITEM(counta,countb,center,normal,gentle) \ + case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; } + switch(frag_attacker.killcount) + { + KILL_SPREE_LIST + default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break; + } + #undef SPREE_ITEM + } + else + nades_GiveBonus(frag_attacker, killcount_bonus); + } + + nades_RemoveBonus(frag_target); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(nades_PlayerDamage) +{ + if(frag_target.frozen) + if(autocvar_g_freezetag_revive_nade) + if(frag_attacker == frag_target) + if(frag_deathtype == DEATH_NADE) + if(time - frag_inflictor.toss_time <= 0.1) + { + Unfreeze(frag_target); + frag_target.health = autocvar_g_freezetag_revive_nade_health; + pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3); + frag_damage = 0; + frag_force = '0 0 0'; + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(nades_MonsterDies) +{ + if(IS_PLAYER(frag_attacker)) + if(DIFF_TEAM(frag_attacker, self)) + if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); return FALSE; } MUTATOR_HOOKFUNCTION(nades_RemovePlayer) { - if(self.nade) - remove(self.nade); + nades_Clear(self); + nades_RemoveBonus(self); + return FALSE; +} - if(self.fake_nade) - remove(self.fake_nade); +MUTATOR_HOOKFUNCTION(nades_SpectateCopy) +{ + self.nade_timer = other.nade_timer; + self.nade_type = other.nade_type; + self.pokenade_type = other.pokenade_type; + self.bonus_nades = other.bonus_nades; + self.bonus_nade_score = other.bonus_nade_score; + self.stat_healing_orb = other.stat_healing_orb; + self.stat_healing_orb_alpha = other.stat_healing_orb_alpha; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(nades_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nade_type, "cl_nade_type"); + GetCvars_handleString(get_cvars_s, get_cvars_f, cvar_cl_pokenade_type, "cl_pokenade_type"); return FALSE; } @@ -367,31 +1127,50 @@ MUTATOR_HOOKFUNCTION(nades_BuildMutatorsPrettyString) return FALSE; } +void nades_Initialize() +{ + addstat(STAT_NADE_TIMER, AS_FLOAT, nade_timer); + addstat(STAT_NADE_BONUS, AS_FLOAT, bonus_nades); + addstat(STAT_NADE_BONUS_TYPE, AS_INT, nade_type); + addstat(STAT_NADE_BONUS_SCORE, AS_FLOAT, bonus_nade_score); + addstat(STAT_HEALING_ORB, AS_FLOAT, stat_healing_orb); + addstat(STAT_HEALING_ORB_ALPHA, AS_FLOAT, stat_healing_orb_alpha); + + precache_model("models/ok_nade_counter/ok_nade_counter.md3"); + precache_model("models/weapons/h_ok_grenade.iqm"); + precache_model("models/weapons/v_ok_grenade.md3"); + precache_model("models/ctf/shield.md3"); + + precache_sound("weapons/rocket_impact.wav"); + precache_sound("weapons/grenade_bounce1.wav"); + precache_sound("weapons/grenade_bounce2.wav"); + precache_sound("weapons/grenade_bounce3.wav"); + precache_sound("weapons/grenade_bounce4.wav"); + precache_sound("weapons/grenade_bounce5.wav"); + precache_sound("weapons/grenade_bounce6.wav"); + precache_sound("overkill/grenadebip.ogg"); +} + MUTATOR_DEFINITION(mutator_nades) { + MUTATOR_HOOK(ForbidThrowCurrentWeapon, nades_CheckThrow, CBC_ORDER_LAST); MUTATOR_HOOK(VehicleEnter, nades_VehicleEnter, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerPreThink, nades_PlayerPreThink, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, nades_PlayerSpawn, CBC_ORDER_ANY); - MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_LAST); + MUTATOR_HOOK(PlayerDamage_Calculate, nades_PlayerDamage, CBC_ORDER_ANY); + MUTATOR_HOOK(MonsterDies, nades_MonsterDies, CBC_ORDER_ANY); MUTATOR_HOOK(MakePlayerObserver, nades_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(ClientDisconnect, nades_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, nades_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(GetCvars, nades_GetCvars, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_global, nades_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(BuildMutatorsString, nades_BuildMutatorsString, CBC_ORDER_ANY); MUTATOR_HOOK(BuildMutatorsPrettyString, nades_BuildMutatorsPrettyString, CBC_ORDER_ANY); MUTATOR_ONADD { - precache_model("models/ok_nade_counter/ok_nade_counter.md3"); - - precache_model("models/weapons/h_ok_grenade.iqm"); - precache_model("models/weapons/v_ok_grenade.md3"); - precache_sound("weapons/rocket_impact.wav"); - precache_sound("weapons/grenade_bounce1.wav"); - precache_sound("weapons/grenade_bounce2.wav"); - precache_sound("weapons/grenade_bounce3.wav"); - precache_sound("weapons/grenade_bounce4.wav"); - precache_sound("weapons/grenade_bounce5.wav"); - precache_sound("weapons/grenade_bounce6.wav"); - precache_sound("overkill/grenadebip.ogg"); + nades_Initialize(); } return FALSE;