From: Mario Date: Thu, 22 May 2014 17:40:47 +0000 (+1000) Subject: Some updates and fixes to nades, new special bonus nades with different powers X-Git-Tag: xonotic-v0.8.0~162^2~9 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=1182fdc12fe84d3cd9489af075ce1c2e6a88facb;p=xonotic%2Fxonotic-data.pk3dir.git Some updates and fixes to nades, new special bonus nades with different powers --- diff --git a/gamemodes.cfg b/gamemodes.cfg index 30352bba7..1916ddca9 100644 --- a/gamemodes.cfg +++ b/gamemodes.cfg @@ -327,9 +327,12 @@ seta g_freezetag_point_leadlimit -1 "Freeze Tag point lead limit overriding the set g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate" set g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range" set g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him" +set g_freezetag_revive_nade 1 "Enable reviving from own nade explosion" +set g_freezetag_revive_nade_health 40 "Amount of health player has if they revived from their own nade explosion" set g_freezetag_revive_falldamage 0 "Enable reviving from this amount of fall damage" set g_freezetag_revive_falldamage_health 40 "Amount of health player has if they revived from falling" set g_freezetag_round_timelimit 180 "round time limit in seconds" +set g_freezetag_frozen_damage_trigger 1 "if 1, frozen players falling into the void will die instead of teleporting to spawn" set g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with" set g_freezetag_frozen_maxtime 60 "frozen players will be automatically unfrozen after this time in seconds" seta g_freezetag_teams_override 0 diff --git a/gfx/hud/default/nade_bg.tga b/gfx/hud/default/nade_bg.tga new file mode 100644 index 000000000..ffe0e0f6b Binary files /dev/null and b/gfx/hud/default/nade_bg.tga differ diff --git a/gfx/hud/default/nade_nbg.tga b/gfx/hud/default/nade_nbg.tga new file mode 100644 index 000000000..18683746a Binary files /dev/null and b/gfx/hud/default/nade_nbg.tga differ diff --git a/gfx/hud/default/notify_nade.tga b/gfx/hud/default/notify_nade.tga new file mode 100644 index 000000000..78dd0e2af Binary files /dev/null and b/gfx/hud/default/notify_nade.tga differ diff --git a/gfx/hud/default/notify_nade_heal.tga b/gfx/hud/default/notify_nade_heal.tga new file mode 100644 index 000000000..17e240860 Binary files /dev/null and b/gfx/hud/default/notify_nade_heal.tga differ diff --git a/gfx/hud/default/notify_nade_ice.tga b/gfx/hud/default/notify_nade_ice.tga new file mode 100644 index 000000000..f75280888 Binary files /dev/null and b/gfx/hud/default/notify_nade_ice.tga differ diff --git a/gfx/hud/default/notify_nade_napalm.tga b/gfx/hud/default/notify_nade_napalm.tga new file mode 100644 index 000000000..bb84101fa Binary files /dev/null and b/gfx/hud/default/notify_nade_napalm.tga differ diff --git a/mutators.cfg b/mutators.cfg index c762ef9de..d3a979e6b 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -138,10 +138,11 @@ set g_random_gravity_negative 1000 "negative gravity multiplier" // ======= -// nades +// Nades // ======= set g_nades 0 "enable off-hand grenades" set g_nades_spawn 1 "give nades right away when player spawns rather than delaying entire refire" +set g_nades_client_select 0 "allow client side selection of nade type" set g_nades_nade_lifetime 3.5 set g_nades_nade_minforce 400 set g_nades_nade_maxforce 2000 @@ -152,6 +153,72 @@ set g_nades_nade_edgedamage 90 set g_nades_nade_radius 300 set g_nades_nade_force 650 set g_nades_nade_newton_style 0 +set g_nades_nade_type 1 "Type of the off-hand grenade. 1:normal 2:napalm 3:ice 4:translocate 5:spawn 6:heal 7:pokenade" + +seta cl_nade_type 3 +seta cl_pokenade_type "zombie" + +// ------------ +// Nade bonus +// ------------ +// +// How the nade bonus system works: +// Each player has a score counter that is increased by some actions (eg: capping, fragging...) +// Once this counter reaches its maximum, the player will receive a bonus grenade and the score counter resets +// If the player dies all the bonus nades will be lost and the score counter resets +// If g_nades_bonus_score_time is not zero, this score will increase or decrease over time +// +set g_nades_bonus 0 "Enable bonus grenades" +set g_nades_bonus_client_select 0 "Allow client side selection of bonus nade type" +set g_nades_bonus_type 2 "Type of the bonus grenade. 1:normal 2:napalm 3:ice 4:translocate 5:spawn 6:heal 7:pokenade" +set g_nades_bonus_onstrength 1 "Always give bonus grenades to players that have the strength powerup" +set g_nades_bonus_max 3 "Maximum number of bonus grenades" +// Bonus score +set g_nades_bonus_score_max 120 "Score value that will give a bonus nade" +set g_nades_bonus_score_minor 5 "Score given for minor actions (pickups, regular frags etc.)" +set g_nades_bonus_score_low 20 "Score given for frags and unfreezes" +set g_nades_bonus_score_medium 30 "Score given for flag returns and flag carrier kills" +set g_nades_bonus_score_high 60 "Score given for flag captures" +set g_nades_bonus_score_spree 40 "Score given every spree of this many frags" +set g_nades_bonus_score_time -1 "Bonus nade score given per second (negative to have the score decay)" +set g_nades_bonus_score_time_flagcarrier 2 "Bonus nade score given per second as flag carrier (negative to have the score decay)" + +// Napalm (2) +set g_nades_napalm_blast 1 "Whether the napalm grenades also give damage with the usual grenade explosion" +set g_nades_napalm_burntime 0.5 "Time that the fire from napalm will stick to the player" +set g_nades_napalm_selfdamage 1 "Whether the player that tossed the nade can be harmed by its fire" +// Napalm fireballs +set g_nades_napalm_ball_count 6 "Number of fireballs emitted during the explosion" +set g_nades_napalm_ball_spread 500 "Maximum force which the fireballs will have on explosion" +set g_nades_napalm_ball_damageforcescale 4 +set g_nades_napalm_ball_damage 40 +set g_nades_napalm_ball_lifetime 7 +set g_nades_napalm_ball_radius 100 "Distance from the fireball within which you may get burned" +// Napalm Fire fountain +set g_nades_napalm_fountain_lifetime 3 "Time period during which extra fire mines are ejected" +set g_nades_napalm_fountain_delay 0.5 "Delay between emissions by the fountain" +set g_nades_napalm_fountain_damage 50 "Damage caused by the center of the fountain" +set g_nades_napalm_fountain_edgedamage 20 "Damage caused by the edge of the fountain" +set g_nades_napalm_fountain_radius 130 + +// Ice (3) +set g_nades_ice_freeze_time 3 "How long the ice field will last" +set g_nades_ice_health 0 "How much health the player will have after being unfrozen" +set g_nades_ice_explode 0 "Whether the ice nade should explode again once the ice field dissipated" +set g_nades_ice_teamcheck 0 "Don't freeze teammates" + +// Spawn (5) +set g_nades_spawn_count 3 "Number of times player will spawn at their spawn nade explosion location" + +// Heal (6) +set g_nades_heal_time 5 "How long the heling field will last" +set g_nades_heal_rate 30 "Health given per second" +set g_nades_heal_friend 1 "Multiplier of health given to team mates" +set g_nades_heal_foe -2 "Multiplier of health given to enemies" + +// Pokenade (7) +set g_nades_pokenade_monster_lifetime 20 "How long pokenade monster will survive" +set g_nades_pokenade_monster_type "zombie" "Monster to spawn" // ============ diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index 4ea750ce5..820171e75 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -87,6 +87,9 @@ void CSQC_Init(void) registercvar("hud_usecsqc", "1"); registercvar("scoreboard_columns", "default"); + registercvar("cl_nade_type", "3"); + registercvar("cl_pokenade_type", "zombie"); + gametype = 0; // hud_fields uses strunzone on the titles! diff --git a/qcsrc/client/View.qc b/qcsrc/client/View.qc index 2a41ba13c..e7a95c0a6 100644 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@ -1155,15 +1155,14 @@ void CSQC_UpdateView(float w, float h) //else { - if(gametype == MAPINFO_TYPE_FREEZETAG) + if(getstati(STAT_FROZEN)) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, ((getstatf(STAT_REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * getstatf(STAT_REVIVE_PROGRESS)) + ('0 1 1' * getstatf(STAT_REVIVE_PROGRESS) * -1)) : '0.25 0.90 1'), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + else if (getstatf(STAT_HEALING_ORB)>time) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, Nade_Color(NADE_TYPE_HEAL), autocvar_hud_colorflash_alpha*getstatf(STAT_HEALING_ORB_ALPHA), DRAWFLAG_ADDITIVE); + else if(getstatf(STAT_REVIVE_PROGRESS)) { - if(getstati(STAT_FROZEN)) - drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); - if(getstatf(STAT_REVIVE_PROGRESS)) - { - DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); - drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); - } + DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); } if(autocvar_r_letterbox == 0) diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index bb5eb4a68..774d6ebc0 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -886,6 +886,54 @@ string GetAmmoPicture(float i) } } +void DrawNadeScoreBar(vector myPos, vector mySize, vector color) +{ + + HUD_Panel_DrawProgressBar( + myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, + mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, + autocvar_hud_panel_ammo_progressbar_name, + getstatf(STAT_NADE_BONUS_SCORE), 0, 0, color, + autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); + +} + +void DrawAmmoNades(vector myPos, vector mySize, float draw_expanding, float expand_time) +{ + float theAlpha = 1, a, b; + vector nade_color, picpos, numpos; + + nade_color = Nade_Color(getstati(STAT_NADE_BONUS_TYPE)); + + a = getstatf(STAT_NADE_BONUS); + b = getstatf(STAT_NADE_BONUS_SCORE); + + if(autocvar_hud_panel_ammo_iconalign) + { + numpos = myPos; + picpos = myPos + eX * 2 * mySize_y; + } + else + { + numpos = myPos + eX * mySize_y; + picpos = myPos; + } + + DrawNadeScoreBar(myPos, mySize, nade_color); + + if(b > 0 || a > 0) + { + if(autocvar_hud_panel_ammo_text) + drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + + if(draw_expanding) + drawpic_aspect_skin_expanding(picpos, "nade_nbg", '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, expand_time); + + drawpic_aspect_skin(picpos, "nade_bg" , '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + drawpic_aspect_skin(picpos, "nade_nbg" , '1 1 0' * mySize_y, nade_color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + } +} + void DrawAmmoItem(vector myPos, vector mySize, float itemcode, float currently_selected, float infinite_ammo) { float a; @@ -942,6 +990,9 @@ void DrawAmmoItem(vector myPos, vector mySize, float itemcode, float currently_s drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL); } +float nade_prevstatus; +float nade_prevframe; +float nade_statuschange_time; void HUD_Ammo(void) { if(hud != HUD_NORMAL) return; @@ -966,21 +1017,39 @@ void HUD_Ammo(void) mySize -= '2 2 0' * panel_bg_padding; } - const float AMMO_COUNT = 4; float rows = 0, columns, row, column; + float nade_cnt = getstatf(STAT_NADE_BONUS), nade_score = getstatf(STAT_NADE_BONUS_SCORE); + float draw_nades = (nade_cnt > 0 || nade_score > 0), nade_statuschange_elapsedtime; + float total_ammo_count; + vector ammo_size; + float AMMO_COUNT = 4; if (autocvar_hud_panel_ammo_onlycurrent) - ammo_size = mySize; + total_ammo_count = 1; else + total_ammo_count = AMMO_COUNT - 1; // fuel + + if(draw_nades) { - rows = mySize_y/mySize_x; - rows = bound(1, floor((sqrt(4 * (3/1) * rows * AMMO_COUNT + rows * rows) + rows + 0.5) / 2), AMMO_COUNT); - // ^^^ ammo item aspect goes here + ++total_ammo_count; + if (nade_cnt != nade_prevframe) + { + nade_statuschange_time = time; + nade_prevstatus = nade_prevframe; + nade_prevframe = nade_cnt; + } + } + else + nade_prevstatus = nade_prevframe = nade_statuschange_time = 0; - columns = ceil(AMMO_COUNT/rows); + rows = mySize_y/mySize_x; + rows = bound(1, floor((sqrt(4 * (3/1) * rows * (total_ammo_count) + rows * rows) + rows + 0.5) / 2), (total_ammo_count)); + // ^^^ ammo item aspect goes here - ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); - } + columns = ceil((total_ammo_count)/rows); + + ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); + local vector offset = '0 0 0'; // fteqcc sucks float newSize; @@ -1001,6 +1070,9 @@ void HUD_Ammo(void) float i, stat_items, currently_selected, infinite_ammo; infinite_ammo = FALSE; + + row = column = 0; + if (autocvar_hud_panel_ammo_onlycurrent) { if(autocvar__hud_configure) @@ -1021,13 +1093,19 @@ void HUD_Ammo(void) } } } + + ++row; + if(row >= rows) + { + row = 0; + column = column + 1; + } } else { stat_items = getstati(STAT_ITEMS, 0, 24); if (stat_items & IT_UNLIMITED_WEAPON_AMMO) infinite_ammo = TRUE; - row = column = 0; for (i = 0; i < AMMO_COUNT; ++i) { currently_selected = stat_items & GetAmmoItemCode(i); DrawAmmoItem(pos + eX * column * (ammo_size_x + offset_x) + eY * row * (ammo_size_y + offset_y), ammo_size, i, currently_selected, infinite_ammo); @@ -1040,6 +1118,15 @@ void HUD_Ammo(void) } } + if (draw_nades) + { + nade_statuschange_elapsedtime = time - nade_statuschange_time; + + float f = bound(0, nade_statuschange_elapsedtime*2, 1); + + DrawAmmoNades(pos + eX * column * (ammo_size_x + offset_x) + eY * row * (ammo_size_y + offset_y), ammo_size, nade_prevstatus < nade_cnt && nade_cnt != 0 && f < 1, f); + } + draw_endBoldFont(); } diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 1a518c5a6..940f1f932 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -16,6 +16,7 @@ Defs.qc ../common/teams.qh ../common/util.qh +../common/nades.qh ../common/test.qh ../common/counting.qh ../common/items.qh @@ -117,6 +118,8 @@ command/cl_cmd.qc ../common/monsters/monsters.qc +../common/nades.qc + ../warpzonelib/anglestransform.qc ../warpzonelib/mathlib.qc ../warpzonelib/common.qc diff --git a/qcsrc/client/projectile.qc b/qcsrc/client/projectile.qc index 8cdcdf470..9744df8be 100644 --- a/qcsrc/client/projectile.qc +++ b/qcsrc/client/projectile.qc @@ -101,24 +101,16 @@ void Projectile_Draw() case PROJECTILE_GRENADE_BOUNCING: rot = '0 -1000 0'; // sideways break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - rot = self.avelocity; - break; case PROJECTILE_HOOKBOMB: rot = '1000 0 0'; // forward break; default: break; } + + if(Nade_IDFromProjectile(self.cnt) != 0) + rot = self.avelocity; + self.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(self.angles), rot * (t - self.spawntime))); } @@ -136,18 +128,6 @@ void Projectile_Draw() trailorigin = self.origin; switch(self.cnt) { - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - trailorigin += v_up * 4; - break; case PROJECTILE_GRENADE: case PROJECTILE_GRENADE_BOUNCING: trailorigin += v_right * 1 + v_forward * -10; @@ -155,6 +135,10 @@ void Projectile_Draw() default: break; } + + if(Nade_IDFromProjectile(self.cnt) != 0) + trailorigin += v_up * 4; + if(drawn) Projectile_DrawTrail(trailorigin); else @@ -274,6 +258,8 @@ void Ent_Projectile() self.fade_time = 0; self.fade_rate = 0; } + + self.team = ReadByte() - 1; } if(f & 2) @@ -302,6 +288,7 @@ void Ent_Projectile() case PROJECTILE_HOOKBOMB: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_KNIGHTSPIKE"); break; case PROJECTILE_HAGAR: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; case PROJECTILE_HAGAR_BOUNCING: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; + case PROJECTILE_NAPALM_FOUNTAIN: //self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("torch_small"); break; case PROJECTILE_FIREBALL: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("fireball"); break; // particle effect is good enough case PROJECTILE_FIREMINE: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("firemine"); break; // particle effect is good enough case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break; @@ -322,18 +309,8 @@ void Ent_Projectile() case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; - case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break; - case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break; - case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break; - case PROJECTILE_NADE_BLUE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue_burn"); break; - case PROJECTILE_NADE_YELLOW: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow"); break; - case PROJECTILE_NADE_YELLOW_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow_burn"); break; - case PROJECTILE_NADE_PINK: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink"); break; - case PROJECTILE_NADE_PINK_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink_burn"); break; - case PROJECTILE_NADE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade"); break; - case PROJECTILE_NADE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_burn"); break; - default: + if(Nade_IDFromProjectile(self.cnt) != 0) { setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum(Nade_TrailEffect(self.cnt, self.team)); break; } error("Received invalid CSQC projectile, can't work with this!"); break; } @@ -366,17 +343,6 @@ void Ent_Projectile() self.mins = '-3 -3 -3'; self.maxs = '3 3 3'; break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - self.mins = '-3 -3 -3'; - self.maxs = '3 3 3'; - self.move_movetype = MOVETYPE_BOUNCE; - self.move_touch = func_null; - self.scale = 1.5; - self.avelocity = randomvec() * 720; - break; case PROJECTILE_GRENADE_BOUNCING: self.mins = '-3 -3 -3'; self.maxs = '3 3 3'; @@ -385,23 +351,6 @@ void Ent_Projectile() self.move_bounce_factor = g_balance_grenadelauncher_bouncefactor; self.move_bounce_stopspeed = g_balance_grenadelauncher_bouncestop; break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - self.mins = '-16 -16 -16'; - self.maxs = '16 16 16'; - self.move_movetype = MOVETYPE_BOUNCE; - self.move_touch = func_null; - self.scale = 1.5; - self.avelocity = randomvec() * 720; - break; case PROJECTILE_SHAMBLER_LIGHTNING: self.mins = '-8 -8 -8'; self.maxs = '8 8 8'; @@ -432,6 +381,7 @@ void Ent_Projectile() self.move_movetype = MOVETYPE_BOUNCE; self.move_touch = func_null; break; + case PROJECTILE_NAPALM_FOUNTAIN: case PROJECTILE_FIREBALL: loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM); self.mins = '-16 -16 -16'; @@ -488,6 +438,21 @@ void Ent_Projectile() default: break; } + + if(Nade_IDFromProjectile(self.cnt) != 0) + { + self.mins = '-16 -16 -16'; + self.maxs = '16 16 16'; + self.colormod = Nade_Color(Nade_IDFromProjectile(self.cnt)); + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.scale = 1.5; + self.avelocity = randomvec() * 720; + + if(Nade_IDFromProjectile(self.cnt) == NADE_TYPE_TRANSLOCATE) + self.solid = SOLID_TRIGGER; + } + setsize(self, self.mins, self.maxs); } @@ -526,6 +491,7 @@ void Projectile_Precache() precache_model("models/rocket.md3"); precache_model("models/tagrocket.md3"); precache_model("models/tracer.mdl"); + precache_model("models/sphere/sphere.md3"); precache_model("models/weapons/v_ok_grenade.md3"); diff --git a/qcsrc/client/waypointsprites.qc b/qcsrc/client/waypointsprites.qc index 1367501a8..faed3bdb2 100644 --- a/qcsrc/client/waypointsprites.qc +++ b/qcsrc/client/waypointsprites.qc @@ -309,7 +309,7 @@ string spritelookuptext(string s) case "item-shield": return _("Shield"); case "item-fuelregen": return _("Fuel regen"); case "item-jetpack": return _("Jet Pack"); - case "freezetag_frozen": return _("Frozen!"); + case "frozen": return _("Frozen!"); case "tagged-target": return _("Tagged"); case "vehicle": return _("Vehicle"); default: return s; diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index e02fad45f..437afd854 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -101,6 +101,8 @@ const float ENT_CLIENT_TURRET = 40; const float ENT_CLIENT_AUXILIARYXHAIR = 50; const float ENT_CLIENT_VEHICLE = 60; +const float ENT_CLIENT_HEALING_ORB = 80; + const float SPRITERULE_DEFAULT = 0; const float SPRITERULE_TEAMPLAY = 1; @@ -185,6 +187,12 @@ const float STAT_WEAPONS3 = 75; const float STAT_MONSTERS_TOTAL = 76; const float STAT_MONSTERS_KILLED = 77; +const float STAT_NADE_BONUS = 80; +const float STAT_NADE_BONUS_TYPE = 81; +const float STAT_NADE_BONUS_SCORE = 82; +const float STAT_HEALING_ORB = 83; +const float STAT_HEALING_ORB_ALPHA = 84; + // mod stats (1xx) const float STAT_REDALIVE = 100; const float STAT_BLUEALIVE = 101; @@ -360,17 +368,6 @@ const float PROJECTILE_BUMBLE_BEAM = 31; const float PROJECTILE_MAGE_SPIKE = 32; const float PROJECTILE_SHAMBLER_LIGHTNING = 33; -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; diff --git a/qcsrc/common/deathtypes.qh b/qcsrc/common/deathtypes.qh index 1265e7eea..0bdea312f 100644 --- a/qcsrc/common/deathtypes.qh +++ b/qcsrc/common/deathtypes.qh @@ -23,7 +23,11 @@ DEATHTYPE(DEATH_MONSTER_WYVERN, DEATH_SELF_MON_WYVERN, DEATH_MURDER_MONSTER, NORMAL_POS) \ DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP, DEATH_SELF_MON_ZOMBIE_JUMP, DEATH_MURDER_MONSTER, NORMAL_POS) \ DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE, DEATH_SELF_MON_ZOMBIE_MELEE, DEATH_MURDER_MONSTER, DEATH_MONSTER_LAST) \ - DEATHTYPE(DEATH_NADE, DEATH_SELF_NADE, DEATH_MURDER_NADE, NORMAL_POS) \ + DEATHTYPE(DEATH_NADE, DEATH_SELF_NADE, DEATH_MURDER_NADE, NORMAL_POS) \ + DEATHTYPE(DEATH_NADE_NAPALM, DEATH_SELF_NADE_NAPALM, DEATH_MURDER_NADE_NAPALM, NORMAL_POS) \ + DEATHTYPE(DEATH_NADE_ICE, DEATH_SELF_NADE_ICE, DEATH_MURDER_NADE_ICE, NORMAL_POS) \ + DEATHTYPE(DEATH_NADE_ICE_FREEZE, DEATH_SELF_NADE_ICE_FREEZE, DEATH_MURDER_NADE_ICE_FREEZE, NORMAL_POS) \ + DEATHTYPE(DEATH_NADE_HEAL, DEATH_SELF_NADE_HEAL, DEATH_MURDER_NADE_HEAL, NORMAL_POS) \ DEATHTYPE(DEATH_NOAMMO, DEATH_SELF_NOAMMO, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_ROT, DEATH_SELF_ROT, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_SHOOTING_STAR, DEATH_SELF_SHOOTING_STAR, DEATH_MURDER_SHOOTING_STAR, NORMAL_POS) \ diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 2c8ebd547..08e995e2e 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -60,7 +60,7 @@ float friend_needshelp(entity e) return FALSE; if(DIFF_TEAM(e, self) && e != self.monster_owner) return FALSE; - if(e.freezetag_frozen) + if(e.frozen) return FALSE; if(!IS_PLAYER(e)) return ((e.flags & FL_MONSTER) && e.health < e.max_health); diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 927501e65..cc3379ee3 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -104,7 +104,7 @@ float monster_isvalidtarget (entity targ, entity ent) if(SAME_TEAM(targ, ent)) return FALSE; // enemy is on our team - if (targ.freezetag_frozen) + if (targ.frozen) return FALSE; // ignore frozen if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT) @@ -537,6 +537,51 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ entity targ; + if(self.frozen == 2) + { + self.revive_progress = bound(0, self.revive_progress + self.ticrate * self.revive_speed, 1); + self.health = max(1, self.revive_progress * self.max_health); + self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1); + + WaypointSprite_UpdateHealth(self.sprite, self.health); + + movelib_beak_simple(stopspeed); + self.frame = manim_idle; + + self.enemy = world; + self.nextthink = time + self.ticrate; + + if(self.revive_progress >= 1) + Unfreeze(self); + + return; + } + else if(self.frozen == 3) + { + self.revive_progress = bound(0, self.revive_progress - self.ticrate * self.revive_speed, 1); + self.health = max(0, autocvar_g_nades_ice_health + (self.max_health-autocvar_g_nades_ice_health) * self.revive_progress ); + + WaypointSprite_UpdateHealth(self.sprite, self.health); + + movelib_beak_simple(stopspeed); + self.frame = manim_idle; + + self.enemy = world; + self.nextthink = time + self.ticrate; + + if(self.health < 1) + { + Unfreeze(self); + self.health = 0; + self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0'); + } + + else if ( self.revive_progress <= 0 ) + Unfreeze(self); + + return; + } + if(self.flags & FL_SWIM) { if(self.waterlevel < WATERLEVEL_WETFEET) @@ -712,6 +757,9 @@ void monster_remove(entity mon) if(mon.weaponentity) remove(mon.weaponentity); + if(mon.iceblock) + remove(mon.iceblock); + WaypointSprite_Kill(mon.sprite); remove(mon); @@ -762,6 +810,8 @@ void monsters_reset() setorigin(self, self.pos1); self.angles = self.pos2; + Unfreeze(self); // remove any icy remains + self.health = self.max_health; self.velocity = '0 0 0'; self.enemy = world; @@ -797,6 +847,12 @@ void monster_die(entity attacker, float gibbed) self.nextthink = time; self.ltime = time + 5; + if(self.frozen) + { + Unfreeze(self); // remove any icy remains + self.health = 0; // reset by Unfreeze + } + monster_dropitem(); MonsterSound(monstersound_death, 0, FALSE, CH_VOICE); @@ -840,6 +896,9 @@ void monster_die(entity attacker, float gibbed) void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { + if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE) + return; + if(time < self.pain_finished && deathtype != DEATH_KILL) return; diff --git a/qcsrc/common/nades.qc b/qcsrc/common/nades.qc new file mode 100644 index 000000000..03b15525c --- /dev/null +++ b/qcsrc/common/nades.qc @@ -0,0 +1,82 @@ +.float healer_lifetime; +.float healer_radius; + +#ifdef SVQC +float healer_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_HEALING_ORB); + WriteByte(MSG_ENTITY, sf); + + if(sf & 1) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, self.healer_lifetime); + //WriteByte(MSG_ENTITY, self.ltime - time + 1); + WriteShort(MSG_ENTITY, self.healer_radius); + // round time delta to a 1/10th of a second + WriteByte(MSG_ENTITY, (self.ltime - time)*10.0+0.5); + } + + return TRUE; +} +#endif // SVQC + +#ifdef CSQC +.float ltime; +void healer_draw() +{ + float dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) + return; + + self.alpha = (self.ltime - time) / self.healer_lifetime; + self.scale = min((1 - self.alpha)*self.healer_lifetime*4,1)*self.healer_radius; + +} + +void healer_setup() +{ + setmodel(self, "models/ctf/shield.md3"); + + setorigin(self, self.origin); + + float model_radius = self.maxs_x; + vector size = '1 1 1' * self.healer_radius / 2; + setsize(self,-size,size); + self.healer_radius = self.healer_radius/model_radius*0.6; + + self.draw = healer_draw; + self.health = 255; + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + self.drawmask = MASK_NORMAL; + self.scale = 0.01; + self.avelocity = self.move_avelocity = '7 0 11'; + self.colormod = '1 0 0'; + self.renderflags |= RF_ADDITIVE; +} + +void ent_healer() +{ + float sf = ReadByte(); + + if(sf & TNSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.healer_lifetime = ReadByte(); + self.healer_radius = ReadShort(); + self.ltime = time + ReadByte()/10.0; + //self.ltime = time + self.healer_lifetime; + + healer_setup(); + } +} +#endif // CSQC \ No newline at end of file diff --git a/qcsrc/common/nades.qh b/qcsrc/common/nades.qh new file mode 100644 index 000000000..1004e1eca --- /dev/null +++ b/qcsrc/common/nades.qh @@ -0,0 +1,103 @@ +// use slots 70-100 +const float PROJECTILE_NADE = 71; +const float PROJECTILE_NADE_BURN = 72; +const float PROJECTILE_NADE_NAPALM = 73; +const float PROJECTILE_NADE_NAPALM_BURN = 74; +const float PROJECTILE_NAPALM_FOUNTAIN = 75; +const float PROJECTILE_NADE_ICE = 76; +const float PROJECTILE_NADE_ICE_BURN = 77; +const float PROJECTILE_NADE_TRANSLOCATE = 78; +const float PROJECTILE_NADE_SPAWN = 79; +const float PROJECTILE_NADE_HEAL = 80; +const float PROJECTILE_NADE_HEAL_BURN = 81; +const float PROJECTILE_NADE_MONSTER = 82; +const float PROJECTILE_NADE_MONSTER_BURN = 83; + +const float NADE_TYPE_NORMAL = 1; +const float NADE_TYPE_NAPALM = 2; +const float NADE_TYPE_ICE = 3; +const float NADE_TYPE_TRANSLOCATE = 4; +const float NADE_TYPE_SPAWN = 5; +const float NADE_TYPE_HEAL = 6; +const float NADE_TYPE_MONSTER = 7; + +const float NADE_TYPE_LAST = 7; // a check to prevent using higher values & crashing + +vector Nade_Color(float nadeid) +{ + switch(nadeid) + { + case NADE_TYPE_NORMAL: return '1 1 1'; + case NADE_TYPE_NAPALM: return '2 0.5 0'; + case NADE_TYPE_ICE: return '0 0.5 2'; + case NADE_TYPE_TRANSLOCATE: return '1 0.0625 1'; + case NADE_TYPE_SPAWN: return '1 0.9 0.06'; + case NADE_TYPE_HEAL: return '1 0 0'; + case NADE_TYPE_MONSTER: return '1 0.5 0'; + } + + return '0 0 0'; +} + +float Nade_IDFromProjectile(float proj) +{ + switch(proj) + { + case PROJECTILE_NADE: + case PROJECTILE_NADE_BURN: return NADE_TYPE_NORMAL; + case PROJECTILE_NADE_NAPALM: + case PROJECTILE_NADE_NAPALM_BURN: return NADE_TYPE_NAPALM; + case PROJECTILE_NADE_ICE: + case PROJECTILE_NADE_ICE_BURN: return NADE_TYPE_ICE; + case PROJECTILE_NADE_TRANSLOCATE: return NADE_TYPE_TRANSLOCATE; + case PROJECTILE_NADE_SPAWN: return NADE_TYPE_SPAWN; + case PROJECTILE_NADE_HEAL: + case PROJECTILE_NADE_HEAL_BURN: return NADE_TYPE_HEAL; + case PROJECTILE_NADE_MONSTER: + case PROJECTILE_NADE_MONSTER_BURN: return NADE_TYPE_MONSTER; + } + + return 0; +} + +float Nade_ProjectileFromID(float proj, float burn) +{ + switch(proj) + { + case NADE_TYPE_NORMAL: return (burn) ? PROJECTILE_NADE_BURN : PROJECTILE_NADE; + case NADE_TYPE_NAPALM: return (burn) ? PROJECTILE_NADE_NAPALM_BURN : PROJECTILE_NADE_NAPALM; + case NADE_TYPE_ICE: return (burn) ? PROJECTILE_NADE_ICE_BURN : PROJECTILE_NADE_ICE; + case NADE_TYPE_TRANSLOCATE: return PROJECTILE_NADE_TRANSLOCATE; + case NADE_TYPE_SPAWN: return PROJECTILE_NADE_SPAWN; + case NADE_TYPE_HEAL: return (burn) ? PROJECTILE_NADE_HEAL_BURN : PROJECTILE_NADE_HEAL; + case NADE_TYPE_MONSTER: return (burn) ? PROJECTILE_NADE_MONSTER_BURN : PROJECTILE_NADE_MONSTER; + } + + return 0; +} + +string Nade_TrailEffect(float proj, float nade_team) +{ + switch(proj) + { + case PROJECTILE_NADE: return strcat("nade_", Static_Team_ColorName_Lower(nade_team)); + case PROJECTILE_NADE_BURN: return strcat("nade_", Static_Team_ColorName_Lower(nade_team), "_burn"); + case PROJECTILE_NADE_NAPALM: return "TR_ROCKET"; + case PROJECTILE_NADE_NAPALM_BURN: return "spiderbot_rocket_thrust"; + case PROJECTILE_NADE_ICE: return "TR_NEXUIZPLASMA"; + case PROJECTILE_NADE_ICE_BURN: return "wakizashi_rocket_thrust"; + case PROJECTILE_NADE_TRANSLOCATE: return "TR_CRYLINKPLASMA"; + case PROJECTILE_NADE_SPAWN: return "nade_yellow"; + case PROJECTILE_NADE_HEAL: return "nade_red"; + case PROJECTILE_NADE_HEAL_BURN: return "nade_red_burn"; + case PROJECTILE_NADE_MONSTER: return "nade_red"; + case PROJECTILE_NADE_MONSTER_BURN: return "nade_red_burn"; + } + + return ""; +} + +#ifdef CSQC +// misc functions +void ent_healer(); +#endif // CSQC diff --git a/qcsrc/common/notifications.qh b/qcsrc/common/notifications.qh index 003c0fcc1..799efaf03 100644 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@ -360,7 +360,11 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FIRE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s")) \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_LAVA, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_lava", _("^BG%s%s^K1 was cooked by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_MONSTER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was pushed infront of a monster by ^BG%s^K1%s%s"), "") \ - MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_nade", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_NAPALM, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_nade_napalm", _("^BG%s%s^K1 was burned to death by ^BG%s^K1's Napalm Nade%s%s"), _("^BG%s%s^K1 got too close to a napalm explosion%s%s")) \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_ICE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_nade_ice", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Ice Nade%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_ICE_FREEZE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_nade_ice", _("^BG%s%s^K1 was frozen to death by ^BG%s^K1's Ice Nade%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_HEAL, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_nade_heal", _("^BG%s%s^K1 has not been healed by ^BG%s^K1's Healing Nade%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SHOOTING_STAR, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_shootingstar", _("^BG%s%s^K1 was shot into space by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SLIME, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_slime", _("^BG%s%s^K1 was slimed by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SWAMP, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_slime", _("^BG%s%s^K1 was preserved by ^BG%s^K1%s%s"), "") \ @@ -389,7 +393,6 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_SELF_FIRE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 became a bit too crispy%s%s"), _("^BG%s^K1 felt a little hot%s%s")) \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC, 2, 1, "s1 s2loc spree_lost", "s1", "notify_selfkill", _("^BG%s^K1 died%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_LAVA, 2, 1, "s1 s2loc spree_lost", "s1", "notify_lava", _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s")) \ - MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MAGE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was exploded by a Mage%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was smashed by a Shambler%s%s"), "") \ @@ -398,6 +401,11 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_WYVERN, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 joins the Zombies%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was given kung fu lessons by a Zombie%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_nade", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_NAPALM, 2, 1, "s1 s2loc spree_lost", "s1", "notify_nade_napalm", _("^BG%s^K1 was burned to death by their own Napalm Nade%s%s"), _("^BG%s^K1 decided to take a look at the results of their napalm explosion%s%s")) \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_ICE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_nade_ice", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_ICE_FREEZE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_nade_ice", _("^BG%s^K1 was frozen to death by their own Ice Nade%s%s"), _("^BG%s^K1 felt a little chilly%s%s")) \ + MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_HEAL, 2, 1, "s1 s2loc spree_lost", "s1", "notify_nade_heal", _("^BG%s^K1's Healing Nade didn't quite heal them%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NOAMMO, 2, 1, "s1 s2loc spree_lost", "s1", "notify_outofammo", _("^BG%s^K1 died%s%s. What's the point of living without ammo?"), _("^BG%s^K1 ran out of ammo%s%s")) \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_ROT, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 rotted away%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_SHOOTING_STAR, 2, 1, "s1 s2loc spree_lost", "s1", "notify_shootingstar", _("^BG%s^K1 became a shooting star%s%s"), "") \ @@ -432,6 +440,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_FREEZETAG_FREEZE, 2, 0, "s1 s2", "", "", _("^BG%s^K1 was frozen by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED, 2, 0, "s1 s2", "", "", _("^BG%s^K3 was revived by ^BG%s"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_FALL, 1, 0, "s1", "", "", _("^BG%s^K3 was revived by falling"), "") \ + MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_NADE, 1, 0, "s1", "", "", _("^BG%s^K3 was revived by their Nade explosion"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_AUTO_REVIVED, 1, 1, "s1 f1", "", "", _("^BG%s^K3 was automatically revived after %s second(s)"), "") \ MULTITEAM_INFO(1, INFO_ROUND_TEAM_WIN_, 4, 0, 0, "", "", "", _("^TC^TT^BG team wins the round"), "") \ MSG_INFO_NOTIF(1, INFO_ROUND_PLAYER_WIN, 1, 0, "s1", "", "", _("^BG%s^BG wins the round"), "") \ @@ -580,6 +589,7 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_DEATH_MURDER_TYPEFRAGGED_VERBOSE, 1, 4, "spree_cen s1 frag_stats", NO_CPID, "0 0", _("^K1%sYou were typefragged by ^BG%s^BG%s"), _("^K1%sYou were scored against by ^BG%s^K1 while typing^BG%s")) \ MSG_CENTER_NOTIF(1, CENTER_DEATH_MURDER_TYPEFRAG_VERBOSE, 1, 2, "spree_cen s1 frag_ping", NO_CPID, "0 0", _("^K1%sYou typefragged ^BG%s^BG%s"), _("^K1%sYou scored against ^BG%s^K1 while they were typing^BG%s")) \ MSG_CENTER_NOTIF(1, CENTER_NADE_THROW, 0, 0, "", CPID_NADES, "0 0", _("^BGPress ^F2DROPWEAPON^BG again to toss the nade!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_NADE_BONUS, 0, 0, "", CPID_NADES, "0 0", _("^F2You got a ^K1BONUS GRENADE^F2!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_AUTOTEAMCHANGE, 0, 1, "death_team", NO_CPID, "0 0", _("^BGYou have been moved into a different team\nYou are now on: %s"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_BETRAYAL, 0, 0, "", NO_CPID, "0 0", _("^K1Don't shoot your team mates!"), _("^K1Don't go against your team mates!")) \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_CAMP, 0, 0, "", NO_CPID, "0 0", _("^K1Die camper!"), _("^K1Reconsider your tactics, camper!")) \ @@ -592,6 +602,9 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_LAVA, 0, 0, "", NO_CPID, "0 0", _("^K1You couldn't stand the heat!"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_MONSTER, 0, 0, "", NO_CPID, "0 0", _("^K1You were killed by a monster!"), _("^K1You need to watch out for monsters!")) \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE, 0, 0, "", NO_CPID, "0 0", _("^K1You forgot to put the pin back in!"), _("^K1Tastes like chicken!")) \ + MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_NAPALM, 0, 0, "", NO_CPID, "0 0", _("^K1Hanging around a napalm explosion is bad!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_ICE_FREEZE, 0, 0, "", NO_CPID, "0 0", _("^K1You got a little bit too cold!"), _("^K1You felt a little chilly!")) \ + MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_HEAL, 0, 0, "", NO_CPID, "0 0", _("^K1Your Healing Nade is a bit defective"), "") \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NOAMMO, 0, 0, "", NO_CPID, "0 0", _("^K1You were killed for running out of ammo..."), _("^K1You are respawning for running out of ammo...")) \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_ROT, 0, 0, "", NO_CPID, "0 0", _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health")) \ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_SHOOTING_STAR, 0, 0, "", NO_CPID, "0 0", _("^K1You became a shooting star!"), "") \ @@ -619,7 +632,7 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FREEZE, 1, 0, "s1", NO_CPID, "0 0", _("^K3You froze ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FROZEN, 1, 0, "s1", NO_CPID, "0 0", _("^K1You were frozen by ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE, 1, 0, "s1", NO_CPID, "0 0", _("^K3You revived ^BG%s"), "") \ - MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE_FALL, 0, 0, "", NO_CPID, "0 0", _("^K3You revived yourself"), "") \ + MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE_SELF, 0, 0, "", NO_CPID, "0 0", _("^K3You revived yourself"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVED, 1, 0, "s1", NO_CPID, "0 0", _("^K3You were revived by ^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_AUTO_REVIVED, 0, 1, "f1", NO_CPID, "0 0", _("^K3You were automatically revived after %s second(s)"), "") \ MULTITEAM_CENTER(1, CENTER_ROUND_TEAM_WIN_, 4, 0, 0, "", CPID_ROUND, "0 0", _("^TC^TT^BG team wins the round"), "") \ @@ -699,6 +712,10 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_MURDER_LAVA, NO_MSG, INFO_DEATH_MURDER_LAVA, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_MONSTER, NO_MSG, INFO_DEATH_MURDER_MONSTER, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE, NO_MSG, INFO_DEATH_MURDER_NADE, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_NAPALM, NO_MSG, INFO_DEATH_MURDER_NADE_NAPALM, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_ICE, NO_MSG, INFO_DEATH_MURDER_NADE_ICE, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_ICE_FREEZE, NO_MSG, INFO_DEATH_MURDER_NADE_ICE_FREEZE, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_HEAL, NO_MSG, INFO_DEATH_MURDER_NADE_HEAL, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_SHOOTING_STAR, NO_MSG, INFO_DEATH_MURDER_SHOOTING_STAR, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_SLIME, NO_MSG, INFO_DEATH_MURDER_SLIME, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_SWAMP, NO_MSG, INFO_DEATH_MURDER_SWAMP, NO_MSG) \ @@ -736,6 +753,10 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, CENTER_DEATH_SELF_MONSTER) \ MSG_MULTI_NOTIF(1, DEATH_SELF_NADE, NO_MSG, INFO_DEATH_SELF_NADE, CENTER_DEATH_SELF_NADE) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_NAPALM, NO_MSG, INFO_DEATH_SELF_NADE_NAPALM, CENTER_DEATH_SELF_NADE_NAPALM) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_ICE, NO_MSG, INFO_DEATH_SELF_NADE_ICE, CENTER_DEATH_SELF_NADE_ICE_FREEZE) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_ICE_FREEZE, NO_MSG, INFO_DEATH_SELF_NADE_ICE_FREEZE, CENTER_DEATH_SELF_NADE_ICE_FREEZE) \ + MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_HEAL, NO_MSG, INFO_DEATH_SELF_NADE_HEAL, CENTER_DEATH_SELF_NADE_HEAL) \ MSG_MULTI_NOTIF(1, DEATH_SELF_NOAMMO, NO_MSG, INFO_DEATH_SELF_NOAMMO, CENTER_DEATH_SELF_NOAMMO) \ MSG_MULTI_NOTIF(1, DEATH_SELF_ROT, NO_MSG, INFO_DEATH_SELF_ROT, CENTER_DEATH_SELF_ROT) \ MSG_MULTI_NOTIF(1, DEATH_SELF_SHOOTING_STAR, NO_MSG, INFO_DEATH_SELF_SHOOTING_STAR, CENTER_DEATH_SELF_SHOOTING_STAR) \ diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 674c95b14..290277111 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -800,10 +800,13 @@ string autocvar_g_forced_team_otherwise; string autocvar_g_forced_team_pink; string autocvar_g_forced_team_red; string autocvar_g_forced_team_yellow; +float autocvar_g_freezetag_frozen_damage_trigger; float autocvar_g_freezetag_frozen_force; float autocvar_g_freezetag_frozen_maxtime; float autocvar_g_freezetag_revive_falldamage; float autocvar_g_freezetag_revive_falldamage_health; +float autocvar_g_freezetag_revive_nade; +float autocvar_g_freezetag_revive_nade_health; float autocvar_g_freezetag_point_leadlimit; float autocvar_g_freezetag_point_limit; float autocvar_g_freezetag_revive_extra_size; @@ -1105,6 +1108,7 @@ float autocvar_sv_dodging_up_speed; float autocvar_sv_dodging_wall_distance_threshold; float autocvar_sv_dodging_wall_dodging; float autocvar_sv_dodging_frozen; +float autocvar_sv_dodging_frozen_doubletap; float autocvar_sv_doublejump; float autocvar_sv_eventlog; float autocvar_sv_eventlog_console; @@ -1258,6 +1262,8 @@ float autocvar_g_random_gravity_negative; float autocvar_g_random_gravity_delay; float autocvar_g_nades; float autocvar_g_nades_spawn; +float autocvar_g_nades_spawn_count; +float autocvar_g_nades_client_select; float autocvar_g_nades_nade_lifetime; float autocvar_g_nades_nade_minforce; float autocvar_g_nades_nade_maxforce; @@ -1268,6 +1274,44 @@ float autocvar_g_nades_nade_edgedamage; float autocvar_g_nades_nade_radius; float autocvar_g_nades_nade_force; float autocvar_g_nades_nade_newton_style; +float autocvar_g_nades_napalm_ball_count; +float autocvar_g_nades_napalm_ball_spread; +float autocvar_g_nades_napalm_ball_damage; +float autocvar_g_nades_napalm_ball_damageforcescale; +float autocvar_g_nades_napalm_ball_lifetime; +float autocvar_g_nades_napalm_ball_radius; +float autocvar_g_nades_napalm_blast; +float autocvar_g_nades_napalm_fountain_lifetime; +float autocvar_g_nades_napalm_fountain_delay; +float autocvar_g_nades_napalm_fountain_radius; +float autocvar_g_nades_napalm_fountain_damage; +float autocvar_g_nades_napalm_fountain_edgedamage; +float autocvar_g_nades_napalm_burntime; +float autocvar_g_nades_napalm_selfdamage; +float autocvar_g_nades_nade_type; +float autocvar_g_nades_bonus_type; +float autocvar_g_nades_bonus; +float autocvar_g_nades_bonus_onstrength; +float autocvar_g_nades_bonus_client_select; +float autocvar_g_nades_bonus_max; +float autocvar_g_nades_bonus_score_max; +float autocvar_g_nades_bonus_score_time; +float autocvar_g_nades_bonus_score_time_flagcarrier; +float autocvar_g_nades_bonus_score_minor; +float autocvar_g_nades_bonus_score_low; +float autocvar_g_nades_bonus_score_high; +float autocvar_g_nades_bonus_score_medium; +float autocvar_g_nades_bonus_score_spree; +float autocvar_g_nades_ice_freeze_time; +float autocvar_g_nades_ice_health; +float autocvar_g_nades_ice_explode; +float autocvar_g_nades_ice_teamcheck; +float autocvar_g_nades_heal_time; +float autocvar_g_nades_heal_rate; +float autocvar_g_nades_heal_friend; +float autocvar_g_nades_heal_foe; +string autocvar_g_nades_pokenade_monster_type; +//float autocvar_g_nades_pokenade_monster_lifetime; float autocvar_g_campcheck_damage; float autocvar_g_campcheck_distance; float autocvar_g_campcheck_interval; diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc index 3467e2b39..61f4ab5e8 100644 --- a/qcsrc/server/bot/aim.qc +++ b/qcsrc/server/bot/aim.qc @@ -111,7 +111,7 @@ float bot_shouldattack(entity e) return FALSE; } - if(e.freezetag_frozen) + if(e.frozen) return FALSE; // If neither player has ball then don't attack unless the ball is on the diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index d06bc96be..038799df5 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -168,6 +168,8 @@ void PutObserverInServer (void) Portal_ClearAll(self); + Unfreeze(self); + if(self.alivetime) { if(!warmup_stage) @@ -239,6 +241,7 @@ void PutObserverInServer (void) self.angles_z = 0; self.fixangle = TRUE; self.crouch = FALSE; + self.revival_time = 0; setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way self.prevorigin = self.origin; @@ -519,6 +522,7 @@ void PutClientInServer (void) self.punchvector = '0 0 0'; self.oldvelocity = self.velocity; self.fire_endtime = -1; + self.revival_time = 0; entity spawnevent = spawn(); spawnevent.owner = self; @@ -592,6 +596,8 @@ void PutClientInServer (void) activator = world; self = oldself; + Unfreeze(self); + spawn_spot = spot; MUTATOR_CALLHOOK(PlayerSpawn); @@ -941,7 +947,7 @@ void ClientKill (void) { if(gameover) return; if(self.player_blocked) return; - if(self.freezetag_frozen) return; + if(self.frozen) return; ClientKill_TeamChange(0); } @@ -1292,6 +1298,8 @@ void ClientDisconnect (void) Portal_ClearAll(self); + Unfreeze(self); + RemoveGrapplingHook(self); // Here, everything has been done that requires this player to be a client. @@ -1586,6 +1594,7 @@ float CalcRotRegen(float current, float regenstable, float regenfactor, float re void player_regen (void) { if(!MUTATOR_CALLHOOK(PlayerRegen)) + if(!self.frozen) { float minh, mina, maxh, maxa, limith, limita, max_mod, regen_mod, rot_mod, limit_mod; maxh = autocvar_g_balance_health_rotstable; @@ -1731,6 +1740,8 @@ void SpectateCopy(entity spectatee) { self.dmg_inflictor = spectatee.dmg_inflictor; self.v_angle = spectatee.v_angle; self.angles = spectatee.v_angle; + self.frozen = spectatee.frozen; + self.revive_progress = spectatee.revive_progress; if(!self.BUTTON_USE) self.fixangle = TRUE; setorigin(self, spectatee.origin); @@ -1936,6 +1947,7 @@ void LeaveSpectatorMode() if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0) { self.classname = "player"; + nades_RemoveBonus(self); if(autocvar_g_campaign || autocvar_g_balance_teams) { JoinBestTeam(self, FALSE, TRUE); } @@ -2241,6 +2253,30 @@ void PlayerPreThink (void) return; #endif + if(self.frozen == 2) + { + self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1); + self.health = max(1, self.revive_progress * start_health); + self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1); + + if(self.revive_progress >= 1) + Unfreeze(self); + } + else if(self.frozen == 3) + { + self.revive_progress = bound(0, self.revive_progress - frametime * self.revive_speed, 1); + self.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * self.revive_progress ); + + if(self.health < 1) + { + if(self.vehicle) + vehicles_exit(VHEF_RELESE); + self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0'); + } + else if ( self.revive_progress <= 0 ) + Unfreeze(self); + } + MUTATOR_CALLHOOK(PlayerPreThink); if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button @@ -2365,7 +2401,7 @@ void PlayerPreThink (void) do_crouch = 0; if(self.vehicle) do_crouch = 0; - if(self.freezetag_frozen) + if(self.frozen) do_crouch = 0; if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink) do_crouch = 0; diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc index 0d0dd3138..5a0ff5872 100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@ -19,6 +19,9 @@ When you press the jump key */ void PlayerJump (void) { + if(self.frozen) + return; // no jumping in freezetag when frozen + float doublejump = FALSE; player_multijump = doublejump; @@ -789,6 +792,28 @@ void SV_PlayerPhysics() self.stat_sv_airspeedlimit_nonqw *= 0.5; } + if(self.frozen) + { + if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self)) + { + self.movement_x = bound(-5, self.movement_x, 5); + self.movement_y = bound(-5, self.movement_y, 5); + self.movement_z = bound(-5, self.movement_z, 5); + } + else + self.movement = '0 0 0'; + self.disableclientprediction = 1; + + 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; } + } + } + MUTATOR_CALLHOOK(PlayerPhysics); if(self.player_blocked) @@ -990,7 +1015,7 @@ void SV_PlayerPhysics() PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0); } } - else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen) + else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.frozen) { //makevectors(self.v_angle_y * '0 1 0'); makevectors(self.v_angle); diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index 67738c4c6..39f503dc4 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -246,7 +246,7 @@ void player_anim (void) else deadbits = ANIMSTATE_DEAD2; float animbits = deadbits; - if(self.freezetag_frozen) + if(self.frozen) animbits |= ANIMSTATE_FROZEN; if(self.crouch) animbits |= ANIMSTATE_DUCK; @@ -693,6 +693,9 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht // when we get here, player actually dies + Unfreeze(self); // remove any icy remains + self.health = 0; // Unfreeze resets health, so we need to set it back + // clear waypoints WaypointSprite_PlayerDead(); // throw a weapon diff --git a/qcsrc/server/cl_weapons.qc b/qcsrc/server/cl_weapons.qc index 2885e6dc5..540f5b83f 100644 --- a/qcsrc/server/cl_weapons.qc +++ b/qcsrc/server/cl_weapons.qc @@ -330,6 +330,8 @@ void W_ThrowWeapon(vector velo, vector delta, float doreduce) w = self.weapon; if (w == 0) return; // just in case + if(self.frozen) + return; if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon)) return; if(!autocvar_g_weapon_throwable) @@ -358,7 +360,7 @@ float forbidWeaponUse() return 1; if(self.player_blocked) return 1; - if(self.freezetag_frozen) + if(self.frozen) return 1; return 0; } diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index c8c87e096..505dba251 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -283,6 +283,7 @@ void ClientCommand_mobspawn(float request, float argc) else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; } else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; } else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; } + else if(self.frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; } else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; } else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; } else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; } diff --git a/qcsrc/server/csqcprojectile.qc b/qcsrc/server/csqcprojectile.qc index 4d208ebda..3c747bdbb 100644 --- a/qcsrc/server/csqcprojectile.qc +++ b/qcsrc/server/csqcprojectile.qc @@ -48,6 +48,8 @@ float CSQCProjectile_SendEntity(entity to, float sf) WriteByte(MSG_ENTITY, ft); WriteByte(MSG_ENTITY, fr); } + + WriteByte(MSG_ENTITY, self.realowner.team); } if(sf & 2) diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index c32ef7716..d8e35db4a 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -584,7 +584,12 @@ float serverflags; .float player_blocked; -.float freezetag_frozen; +.float frozen; // for freeze attacks +.float revive_progress; +.float revival_time; // time at which player was last revived +.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) +.entity iceblock; +.entity frozen_by; // for ice fields .entity muzzle_flash; .float misc_bulletcounter; // replaces uzi & hlac bullet counter. diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 45319ca07..b5b52a379 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -549,6 +549,86 @@ void Obituary(entity attacker, entity inflictor, entity targ, float deathtype) if(targ.killcount) { targ.killcount = 0; } } +void Ice_Think() +{ + if(!self.owner.frozen || self.owner.iceblock != self) + { + remove(self); + return; + } + setorigin(self, self.owner.origin - '0 0 16'); + self.nextthink = time; +} + +void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint) +{ + if(!IS_PLAYER(targ) && !(targ.flags & FL_MONSTER)) // only specified entities can be freezed + return; + + if(targ.frozen) + return; + + float targ_maxhealth = ((targ.flags & FL_MONSTER) ? targ.max_health : start_health); + + targ.frozen = frozen_type; + targ.revive_progress = ((frozen_type == 3) ? 1 : 0); + targ.health = ((frozen_type == 3) ? targ_maxhealth : 1); + targ.revive_speed = freeze_time; + + entity ice, head; + ice = spawn(); + ice.owner = targ; + ice.classname = "ice"; + ice.scale = targ.scale; + ice.think = Ice_Think; + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + setmodel(ice, "models/ice/ice.md3"); + ice.alpha = 1; + ice.colormod = Team_ColorRGB(targ.team); + ice.glowmod = ice.colormod; + targ.iceblock = ice; + targ.revival_time = 0; + + entity oldself; + oldself = self; + self = ice; + Ice_Think(); + self = oldself; + + RemoveGrapplingHook(targ); + + FOR_EACH_PLAYER(head) + if(head.hook.aiment == targ) + RemoveGrapplingHook(head); + + // add waypoint + if(show_waypoint) + WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1'); +} + +void Unfreeze (entity targ) +{ + if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen + targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health); + + entity head; + targ.frozen = 0; + targ.revive_progress = 0; + targ.revival_time = time; + + WaypointSprite_Kill(targ.waypointsprite_attached); + + FOR_EACH_PLAYER(head) + if(head.hook.aiment == targ) + RemoveGrapplingHook(head); + + // remove the ice block + if(targ.iceblock) + remove(targ.iceblock); + targ.iceblock = world; +} + // these are updated by each Damage call for use in button triggering and such entity damage_targ; entity damage_inflictor; @@ -690,7 +770,63 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float mirrordamage = frag_mirrordamage; force = frag_force; - if (!g_minstagib) + if(targ.frozen) + if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE) + { + if(autocvar_g_freezetag_revive_falldamage > 0) + if(deathtype == DEATH_FALL) + if(damage >= autocvar_g_freezetag_revive_falldamage) + { + Unfreeze(targ); + targ.health = autocvar_g_freezetag_revive_falldamage_health; + pointparticles(particleeffectnum("iceorglass"), targ.origin, '0 0 0', 3); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); + Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } + + damage = 0; + force *= autocvar_g_freezetag_frozen_force; + } + + if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger) + { + pointparticles(particleeffectnum("teleport"), targ.origin, '0 0 0', 1); + + entity oldself = self; + self = targ; + entity spot = SelectSpawnPoint (FALSE); + + if(spot) + { + damage = 0; + self.deadflag = DEAD_NO; + + self.angles = spot.angles; + + self.effects = 0; + self.effects |= EF_TELEPORT_BIT; + + self.angles_z = 0; // never spawn tilted even if the spot says to + self.fixangle = TRUE; // turn this way immediately + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.punchangle = '0 0 0'; + self.punchvector = '0 0 0'; + self.oldvelocity = self.velocity; + + self.spawnorigin = spot.origin; + setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24)); + // don't reset back to last position, even if new position is stuck in solid + self.oldorigin = self.origin; + self.prevorigin = self.origin; + + pointparticles(particleeffectnum("teleport"), self.origin, '0 0 0', 1); + } + + self = oldself; + } + + if(!g_minstagib) { // apply strength multiplier if (attacker.items & IT_STRENGTH) @@ -1200,7 +1336,7 @@ void Fire_ApplyDamage(entity e) e.fire_endtime = 0; // ice stops fire - if(e.freezetag_frozen) + if(e.frozen) e.fire_endtime = 0; t = min(frametime, e.fire_endtime - time); @@ -1217,6 +1353,7 @@ void Fire_ApplyDamage(entity e) e.fire_hitsound = TRUE; if (!IS_INDEPENDENT_PLAYER(e)) + if (!e.frozen) FOR_EACH_PLAYER(other) if(e != other) { if(IS_PLAYER(other)) diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 875a0c3e6..599abdad9 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -794,6 +794,10 @@ void spawnfunc_worldspawn (void) addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load); + // freeze attacks + addstat(STAT_FROZEN, AS_INT, frozen); + addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress); + // g_movementspeed hack addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw); addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed); diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 5dc6f2e39..f3c26699d 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -1303,6 +1303,7 @@ void precache() { // gamemode related things precache_model ("models/misc/chatbubble.spr"); + precache_model("models/ice/ice.md3"); #ifdef TTURRETS_ENABLED if (autocvar_g_turrets) diff --git a/qcsrc/server/mutators/gamemode_ca.qc b/qcsrc/server/mutators/gamemode_ca.qc index 8a6315c42..8b38ceb09 100644 --- a/qcsrc/server/mutators/gamemode_ca.qc +++ b/qcsrc/server/mutators/gamemode_ca.qc @@ -67,12 +67,15 @@ float CA_GetWinnerTeam() #define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams) float CA_CheckWinner() { + entity e; if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); allowed_to_spawn = FALSE; round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + FOR_EACH_PLAYER(e) + nades_Clear(e); return 1; } @@ -95,6 +98,10 @@ float CA_CheckWinner() allowed_to_spawn = FALSE; round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + + FOR_EACH_PLAYER(e) + nades_Clear(e); + return 1; } diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc index 66c7b8715..626e4fed8 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@ -388,6 +388,8 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype) if (!player) { return; } // without someone to give the reward to, we can't possibly cap + nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); + // messages and sounds Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_)); ctf_CaptureRecord(enemy_flag, player); @@ -448,6 +450,8 @@ void ctf_Handle_Return(entity flag, entity player) { PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns + + nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); } TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it @@ -500,6 +504,7 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) // scoring PlayerScore_Add(player, SP_CTF_PICKUPS, 1); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); switch(pickuptype) { case PICKUP_BASE: @@ -791,7 +796,8 @@ void ctf_FlagTouch() } // special touch behaviors - if(toucher.vehicle_flags & VHF_ISVEHICLE) + if(toucher.frozen) { return; } + else if(toucher.vehicle_flags & VHF_ISVEHICLE) { if(autocvar_g_ctf_allow_vehicle_touch) toucher = toucher.owner; // the player is actually the vehicle owner, not other diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc index bfc2601e9..52d0fd492 100644 --- a/qcsrc/server/mutators/gamemode_freezetag.qc +++ b/qcsrc/server/mutators/gamemode_freezetag.qc @@ -1,7 +1,6 @@ .float freezetag_frozen_time; .float freezetag_frozen_timeout; .float freezetag_revive_progress; -.entity freezetag_ice; #define ICE_MAX_ALPHA 1 #define ICE_MIN_ALPHA 0.1 float freezetag_teams; @@ -10,29 +9,18 @@ void freezetag_count_alive_players() { entity e; total_players = redalive = bluealive = yellowalive = pinkalive = 0; - FOR_EACH_PLAYER(e) { - if(e.team == NUM_TEAM_1 && e.health >= 1) - { - ++total_players; - if (!e.freezetag_frozen) ++redalive; - } - else if(e.team == NUM_TEAM_2 && e.health >= 1) - { - ++total_players; - if (!e.freezetag_frozen) ++bluealive; - } - else if(e.team == NUM_TEAM_3 && e.health >= 1) - { - ++total_players; - if (!e.freezetag_frozen) ++yellowalive; - } - else if(e.team == NUM_TEAM_4 && e.health >= 1) + FOR_EACH_PLAYER(e) + { + switch(e.team) { - ++total_players; - if (!e.freezetag_frozen) ++pinkalive; + case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break; } } - FOR_EACH_REALCLIENT(e) { + FOR_EACH_REALCLIENT(e) + { e.redalive_stat = redalive; e.bluealive_stat = bluealive; e.yellowalive_stat = yellowalive; @@ -102,7 +90,7 @@ float freezetag_CheckWinner() FOR_EACH_PLAYER(e) { e.freezetag_frozen_timeout = 0; - e.freezetag_revive_progress = 0; + nades_Clear(e); } round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); return 1; @@ -128,21 +116,12 @@ float freezetag_CheckWinner() FOR_EACH_PLAYER(e) { e.freezetag_frozen_timeout = 0; - e.freezetag_revive_progress = 0; + nades_Clear(e); } round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); return 1; } -// this is needed to allow the player to turn his view around (fixangle can't -// be used to freeze his view, as that also changes the angles), while not -// turning that ice object with the player -void freezetag_Ice_Think() -{ - setorigin(self, self.owner.origin - '0 0 16'); - self.nextthink = time; -} - void freezetag_Add_Score(entity attacker) { if(attacker == self) @@ -163,51 +142,25 @@ void freezetag_Add_Score(entity attacker) void freezetag_Freeze(entity attacker) { - if(self.freezetag_frozen) + if(self.frozen) return; - self.freezetag_frozen = 1; - self.freezetag_frozen_time = time; - self.freezetag_revive_progress = 0; - self.health = 1; + if(autocvar_g_freezetag_frozen_maxtime > 0) self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; - freezetag_count_alive_players(); + Freeze(self, 0, 1, TRUE); - entity ice; - ice = spawn(); - ice.owner = self; - ice.classname = "freezetag_ice"; - ice.think = freezetag_Ice_Think; - ice.nextthink = time; - ice.frame = floor(random() * 21); // ice model has 20 different looking frames - ice.alpha = ICE_MAX_ALPHA; - ice.colormod = Team_ColorRGB(self.team); - ice.glowmod = ice.colormod; - setmodel(ice, "models/ice/ice.md3"); - - self.freezetag_ice = ice; - - RemoveGrapplingHook(self); - - // add waypoint - WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1'); + freezetag_count_alive_players(); freezetag_Add_Score(attacker); } void freezetag_Unfreeze(entity attacker) { - self.freezetag_frozen = 0; self.freezetag_frozen_time = 0; self.freezetag_frozen_timeout = 0; - self.freezetag_revive_progress = 0; - remove(self.freezetag_ice); - self.freezetag_ice = world; - - if(self.waypointsprite_attached) - WaypointSprite_Kill(self.waypointsprite_attached); + Unfreeze(self); } @@ -227,7 +180,7 @@ void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradiu { if ((head != self) && (head.team == self.team)) { - if (head.freezetag_frozen) + if (head.frozen == 1) { distance = vlen(head.origin - org); if (distance > sradius) @@ -259,12 +212,12 @@ void havocbot_role_ft_offense() unfrozen = 0; FOR_EACH_PLAYER(head) { - if ((head.team == self.team) && (!head.freezetag_frozen)) + if ((head.team == self.team) && (head.frozen != 1)) unfrozen++; } // If only one left on team or if role has timed out then start trying to free players. - if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout)) + if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout)) { dprint("changing role to freeing\n"); self.havocbot_role = havocbot_role_ft_freeing; @@ -332,7 +285,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) if(round_handler_IsActive()) if(round_handler_CountdownRunning()) { - if(self.freezetag_frozen) + if(self.frozen) freezetag_Unfreeze(world); freezetag_count_alive_players(); return 1; // let the player die so that he can respawn whenever he wants @@ -344,18 +297,19 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE) { // let the player die, he will be automatically frozen when he respawns - if(!self.freezetag_frozen) + if(self.frozen != 1) { freezetag_Add_Score(frag_attacker); freezetag_count_alive_players(); } else freezetag_Unfreeze(world); // remove ice + self.health = 0; // Unfreeze resets health self.freezetag_frozen_timeout = -2; // freeze on respawn return 1; } - if(self.freezetag_frozen) + if(self.frozen) return 1; freezetag_Freeze(frag_attacker); @@ -375,8 +329,6 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); } - frag_target.health = 1; // "respawn" the player :P - return 1; } @@ -408,8 +360,6 @@ MUTATOR_HOOKFUNCTION(freezetag_reset_map_players) FOR_EACH_PLAYER(self) { self.killcount = 0; - if (self.freezetag_frozen) - freezetag_Unfreeze(world); self.freezetag_frozen_timeout = -1; PutClientInServer(); self.freezetag_frozen_timeout = 0; @@ -432,7 +382,7 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) if(gameover) return 1; - if(self.freezetag_frozen) + if(self.frozen == 1) { // keep health = 1 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; @@ -444,8 +394,9 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) entity o; o = world; - if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) - self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); + //if(self.frozen) + //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) + //self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) n = -1; @@ -453,34 +404,27 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) { vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; n = 0; - FOR_EACH_PLAYER(other) if(self != other) + FOR_EACH_PLAYER(other) + if(self != other) + if(other.frozen == 0) + if(other.deadflag == DEAD_NO) + if(SAME_TEAM(other, self)) + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) { - if(other.freezetag_frozen == 0) - { - if(other.team == self.team) - { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - { - if(!o) - o = other; - if(self.freezetag_frozen) - other.reviving = TRUE; - ++n; - } - } - } + if(!o) + o = other; + if(self.frozen == 1) + other.reviving = TRUE; + ++n; } } - if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us + if(n && self.frozen == 1) // OK, there is at least one teammate reviving us { - self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - if(warmup_stage) - self.health = max(1, self.freezetag_revive_progress * warmup_start_health); - else - self.health = max(1, self.freezetag_revive_progress * start_health); + 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 * ((warmup_stage) ? warmup_start_health : start_health)); - if(self.freezetag_revive_progress >= 1) + if(self.revive_progress >= 1) { freezetag_Unfreeze(self); freezetag_count_alive_players(); @@ -499,6 +443,8 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) { PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); PlayerScore_Add(other, SP_SCORE, +1); + + nades_GiveBonus(other,autocvar_g_nades_bonus_score_low); } } @@ -511,88 +457,24 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) { if(other.reviving) { - other.freezetag_revive_progress = self.freezetag_revive_progress; + other.revive_progress = self.revive_progress; other.reviving = FALSE; } } } - else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset - { - self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); - if(warmup_stage) - self.health = max(1, self.freezetag_revive_progress * warmup_start_health); - else - self.health = max(1, self.freezetag_revive_progress * start_health); - } - else if(!n) + else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset { - self.freezetag_revive_progress = 0; // thawing nobody + self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); + self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); } - - return 1; -} - -MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics) -{ - if(self.freezetag_frozen) + else if(!n && !self.frozen) { - if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self)) - { - self.movement_x = bound(-5, self.movement_x, 5); - self.movement_y = bound(-5, self.movement_y, 5); - self.movement_z = bound(-5, self.movement_z, 5); - } - else - self.movement = '0 0 0'; - - self.disableclientprediction = 1; + self.revive_progress = 0; // thawing nobody } - return 1; -} - -MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate) -{ - if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER) - { - if(autocvar_g_freezetag_revive_falldamage > 0) - if(frag_deathtype == DEATH_FALL) - if(frag_damage >= autocvar_g_freezetag_revive_falldamage) - { - freezetag_Unfreeze(frag_target); - frag_target.health = autocvar_g_freezetag_revive_falldamage_health; - pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname); - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_FALL); - } - frag_damage = 0; - frag_force = frag_force * autocvar_g_freezetag_frozen_force; - } return 1; } -MUTATOR_HOOKFUNCTION(freezetag_PlayerJump) -{ - if(self.freezetag_frozen) - return TRUE; // no jumping in freezetag when frozen - - return FALSE; -} - -MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon) -{ - if (self.freezetag_frozen) - return 1; - return 0; -} - -MUTATOR_HOOKFUNCTION(freezetag_ItemTouch) -{ - if (other.freezetag_frozen) - return MUT_ITEMTOUCH_RETURN; - return MUT_ITEMTOUCH_CONTINUE; -} - MUTATOR_HOOKFUNCTION(freezetag_BotRoles) { if (!self.deadflag) @@ -606,31 +488,14 @@ MUTATOR_HOOKFUNCTION(freezetag_BotRoles) return TRUE; } -MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy) -{ - self.freezetag_frozen = other.freezetag_frozen; - self.freezetag_revive_progress = other.freezetag_revive_progress; - return 0; -} - MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount) { ret_float = freezetag_teams; return 0; } -MUTATOR_HOOKFUNCTION(freezetag_VehicleTouch) -{ - if(other.freezetag_frozen) - return TRUE; - - return FALSE; -} - void freezetag_Initialize() { - precache_model("models/ice/ice.md3"); - freezetag_teams = autocvar_g_freezetag_teams_override; if(freezetag_teams < 2) freezetag_teams = autocvar_g_freezetag_teams; @@ -644,9 +509,6 @@ void freezetag_Initialize() addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); - - addstat(STAT_FROZEN, AS_INT, freezetag_frozen); - addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress); } MUTATOR_DEFINITION(gamemode_freezetag) @@ -658,15 +520,8 @@ MUTATOR_DEFINITION(gamemode_freezetag) MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY); MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST); - MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST); - MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY); - MUTATOR_HOOK(PlayerJump, freezetag_PlayerJump, CBC_ORDER_ANY); - MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY); - MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY); - MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY); MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE); - MUTATOR_HOOK(VehicleTouch, freezetag_VehicleTouch, CBC_ORDER_ANY); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_keepaway.qc b/qcsrc/server/mutators/gamemode_keepaway.qc index ec6ee8cd4..248712f15 100644 --- a/qcsrc/server/mutators/gamemode_keepaway.qc +++ b/qcsrc/server/mutators/gamemode_keepaway.qc @@ -71,6 +71,7 @@ void ka_TouchEvent() // runs any time that the ball comes in contact with someth return; } if(other.deadflag != DEAD_NO) { return; } + if(other.frozen) { return; } if (!IS_PLAYER(other)) { // The ball just touched an object, most likely the world pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1); diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc index 34d87f97b..ab67a4821 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qc +++ b/qcsrc/server/mutators/gamemode_keyhunt.qc @@ -500,6 +500,7 @@ void kh_WinnerTeam(float teem) // runs when a team wins // Samual: Teem?.... TE f = DistributeEvenly_Get(1); kh_Scores_Event(key.owner, key, "capture", f, 0); PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1); + nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); } first = TRUE; diff --git a/qcsrc/server/mutators/gamemode_nexball.qc b/qcsrc/server/mutators/gamemode_nexball.qc index 014d37ec2..8695ca31b 100644 --- a/qcsrc/server/mutators/gamemode_nexball.qc +++ b/qcsrc/server/mutators/gamemode_nexball.qc @@ -288,7 +288,7 @@ void basketball_touch(void) football_touch(); return; } - if(!self.cnt && IS_PLAYER(other) && (other != self.nb_dropper || time > self.nb_droptime + autocvar_g_nexball_delay_collect)) + if(!self.cnt && IS_PLAYER(other) && !other.frozen && (other != self.nb_dropper || time > self.nb_droptime + autocvar_g_nexball_delay_collect)) { if(other.health <= 0) return; diff --git a/qcsrc/server/mutators/mutator_campcheck.qc b/qcsrc/server/mutators/mutator_campcheck.qc index fb20d1cff..2ec584db4 100644 --- a/qcsrc/server/mutators/mutator_campcheck.qc +++ b/qcsrc/server/mutators/mutator_campcheck.qc @@ -25,6 +25,7 @@ MUTATOR_HOOKFUNCTION(campcheck_PlayerThink) { if(IS_PLAYER(self)) if(self.deadflag == DEAD_NO) + if(!self.frozen) if(autocvar_g_campcheck_interval) { vector dist; diff --git a/qcsrc/server/mutators/mutator_dodging.qc b/qcsrc/server/mutators/mutator_dodging.qc index 3f808499a..b26fe1b9f 100644 --- a/qcsrc/server/mutators/mutator_dodging.qc +++ b/qcsrc/server/mutators/mutator_dodging.qc @@ -35,7 +35,7 @@ MUTATOR_HOOKFUNCTION(dodging_PlayerPhysics) { float clean_up_and_do_nothing; float horiz_speed = autocvar_sv_dodging_horiz_speed; - if(self.freezetag_frozen) + if(self.frozen) horiz_speed = autocvar_sv_dodging_horiz_speed_frozen; if (self.deadflag != DEAD_NO) @@ -169,8 +169,9 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { tap_direction_x = 0; tap_direction_y = 0; - float frozen_dodging; - frozen_dodging = (self.freezetag_frozen && autocvar_sv_dodging_frozen); + float frozen_dodging, frozen_no_doubletap; + frozen_dodging = (self.frozen && autocvar_sv_dodging_frozen); + frozen_no_doubletap = (frozen_dodging && !autocvar_sv_dodging_frozen_doubletap); float dodge_detected; if (g_dodging == 0) @@ -188,7 +189,7 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { if (self.movement_x > 0) { // is this a state change? - if (!(self.pressedkeys & KEY_FORWARD) || frozen_dodging) { + if (!(self.pressedkeys & KEY_FORWARD) || frozen_no_doubletap) { if ((time - self.last_FORWARD_KEY_time) < self.cvar_cl_dodging_timeout) { tap_direction_x = 1.0; dodge_detected = 1; @@ -199,7 +200,7 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { if (self.movement_x < 0) { // is this a state change? - if (!(self.pressedkeys & KEY_BACKWARD) || frozen_dodging) { + if (!(self.pressedkeys & KEY_BACKWARD) || frozen_no_doubletap) { tap_direction_x = -1.0; if ((time - self.last_BACKWARD_KEY_time) < self.cvar_cl_dodging_timeout) { dodge_detected = 1; @@ -210,7 +211,7 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { if (self.movement_y > 0) { // is this a state change? - if (!(self.pressedkeys & KEY_RIGHT) || frozen_dodging) { + if (!(self.pressedkeys & KEY_RIGHT) || frozen_no_doubletap) { tap_direction_y = 1.0; if ((time - self.last_RIGHT_KEY_time) < self.cvar_cl_dodging_timeout) { dodge_detected = 1; @@ -221,7 +222,7 @@ MUTATOR_HOOKFUNCTION(dodging_GetPressedKeys) { if (self.movement_y < 0) { // is this a state change? - if (!(self.pressedkeys & KEY_LEFT) || frozen_dodging) { + if (!(self.pressedkeys & KEY_LEFT) || frozen_no_doubletap) { tap_direction_y = -1.0; if ((time - self.last_LEFT_KEY_time) < self.cvar_cl_dodging_timeout) { dodge_detected = 1; diff --git a/qcsrc/server/mutators/mutator_minstagib.qc b/qcsrc/server/mutators/mutator_minstagib.qc index 6cce15211..20811ad50 100644 --- a/qcsrc/server/mutators/mutator_minstagib.qc +++ b/qcsrc/server/mutators/mutator_minstagib.qc @@ -225,7 +225,6 @@ MUTATOR_HOOKFUNCTION(minstagib_SplitHealthArmor) MUTATOR_HOOKFUNCTION(minstagib_ForbidThrowing) { // weapon dropping on death handled by FilterItem - nades_CheckThrow(); return TRUE; } diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc index 2fad60226..03a9e90a2 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,41 +27,519 @@ 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) + { + 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; } + } + + 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, 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); + + 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) ) { - 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; + 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; } +} - CSQCProjectile(_nade, TRUE, p, TRUE); +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, 1); + + //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; + } } - 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); + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); self.takedamage = DAMAGE_NO; - RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + + if(nade_blast) + { + 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); + 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); + } + + 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); } @@ -101,6 +568,9 @@ void nade_beep() void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { + if(self.nade_type == NADE_TYPE_TRANSLOCATE || self.nade_type == NADE_TYPE_SPAWN) + return; + if(DEATH_ISWEAPON(deathtype, WEP_LASER)) return; @@ -113,14 +583,14 @@ void nade_damage(entity inflictor, entity attacker, float damage, float deathtyp if(DEATH_ISWEAPON(deathtype, WEP_UZI)) 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_SHOTGUN)) + if(deathtype & HITTYPE_SECONDARY) { damage = self.max_health * 0.1; - force *= 15; + force *= 10; } + else + damage = self.max_health * 1.1; self.velocity += force; @@ -134,8 +604,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 +617,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 +632,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; @@ -177,12 +651,15 @@ void toss_nade(entity e, vector _velocity, float _time) _nade.velocity = _velocity; else _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, TRUE); - + _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.traileffectnum = 0; _nade.teleportable = TRUE; _nade.pushable = TRUE; _nade.gravity = 1; @@ -190,6 +667,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 = ((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); nade_spawn(_nade); @@ -202,6 +682,51 @@ void toss_nade(entity e, vector _velocity, float _time) e.nade_refire = time + autocvar_g_nades_nade_refire; } +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; + self.alpha = -1; + } + else + { + //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + self.alpha = 1; + } + + return TRUE; +} + void nade_prime() { if(self.nade) @@ -210,29 +735,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() @@ -285,21 +834,52 @@ 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; +} + +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) + { + 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) @@ -322,6 +902,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; } @@ -332,24 +994,110 @@ 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; + + 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_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; } @@ -366,31 +1114,49 @@ MUTATOR_HOOKFUNCTION(nades_BuildMutatorsPrettyString) return FALSE; } +void nades_Initialize() +{ + 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(PlayerSpawn, nades_PlayerSpawn, CBC_ORDER_LAST); + MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_LAST); + MUTATOR_HOOK(PlayerDamage_Calculate, nades_PlayerDamage, CBC_ORDER_FIRST); + 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; diff --git a/qcsrc/server/mutators/mutator_nades.qh b/qcsrc/server/mutators/mutator_nades.qh index 1940f4e05..90c3296bd 100644 --- a/qcsrc/server/mutators/mutator_nades.qh +++ b/qcsrc/server/mutators/mutator_nades.qh @@ -1,5 +1,25 @@ .entity nade; .entity fake_nade; .float nade_refire; +.float bonus_nades; +.float nade_special_time; +.float bonus_nade_score; +.float nade_type; +.string pokenade_type; +.entity nade_damage_target; +.float cvar_cl_nade_type; +.string cvar_cl_pokenade_type; +.float toss_time; +.float stat_healing_orb; +.float stat_healing_orb_alpha; +.float nade_show_particles; -void() nades_CheckThrow; +void toss_nade(entity e, vector _velocity, float _time); + +// Remove nades that are being thrown +void(entity player) nades_Clear; + +// Give a bonus grenade to a player +void(entity player, float score) nades_GiveBonus; +// Remove all bonus nades from a player +void(entity player) nades_RemoveBonus; diff --git a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc index 54df3a97c..2658c4493 100644 --- a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc +++ b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc @@ -56,7 +56,7 @@ MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn) if(team_mate.msnt_timer < time) if(SAME_TEAM(self, team_mate)) if(time > team_mate.spawnshieldtime) // spawn shielding - if(team_mate.freezetag_frozen == 0) + if(team_mate.frozen == 0) if(team_mate != self) { tracebox(team_mate.origin, PL_MIN, PL_MAX, team_mate.origin - '0 0 100', MOVE_WORLDONLY, team_mate); diff --git a/qcsrc/server/mutators/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator_touchexplode.qc index fabf13639..30b01b0da 100644 --- a/qcsrc/server/mutators/mutator_touchexplode.qc +++ b/qcsrc/server/mutators/mutator_touchexplode.qc @@ -19,13 +19,15 @@ void PlayerTouchExplode(entity p1, entity p2) MUTATOR_HOOKFUNCTION(touchexplode_PlayerThink) { if(time > self.touchexplode_time) - if (!gameover) + if(!gameover) + if(!self.frozen) if(IS_PLAYER(self)) if(self.deadflag == DEAD_NO) if (!IS_INDEPENDENT_PLAYER(self)) FOR_EACH_PLAYER(other) if(self != other) { if(time > other.touchexplode_time) + if(!other.frozen) if(other.deadflag == DEAD_NO) if (!IS_INDEPENDENT_PLAYER(other)) if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax)) diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 1ae22e202..a4e431d14 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -15,6 +15,7 @@ sys-post.qh ../common/constants.qh ../common/teams.qh ../common/util.qh +../common/nades.qh ../common/test.qh ../common/counting.qh ../common/items.qh @@ -214,6 +215,8 @@ target_music.qc ../common/items.qc +../common/nades.qc + accuracy.qc ../csqcmodellib/sv_model.qc diff --git a/qcsrc/server/spawnpoints.qh b/qcsrc/server/spawnpoints.qh index 06b484e18..607629e42 100644 --- a/qcsrc/server/spawnpoints.qh +++ b/qcsrc/server/spawnpoints.qh @@ -2,4 +2,4 @@ float spawnpoint_nag; float SpawnEvent_Send(entity to, float sf); entity Spawn_FilterOutBadSpots(entity firstspot, float mindist, float teamcheck); - +entity SelectSpawnPoint (float anypoint); diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 920f738ae..cf974d82b 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -155,7 +155,6 @@ float game_delay; float game_delay_last; float RedirectionThink(); -entity SelectSpawnPoint (float anypoint); void StartFrame (void) { execute_next_frame(); diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index e332e55ed..e325ed845 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -318,6 +318,9 @@ string getwelcomemessage(void) if (g_grappling_hook) s = strcat(s, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n"); + if (cvar("g_nades")) + s = strcat(s, "\n\n^3nades^8 are enabled, press 'g' to use them\n"); + if(cache_lastmutatormsg != autocvar_g_mutatormsg) { if(cache_lastmutatormsg) diff --git a/qcsrc/server/w_electro.qc b/qcsrc/server/w_electro.qc index 0ad23a137..ea2cf8bf0 100644 --- a/qcsrc/server/w_electro.qc +++ b/qcsrc/server/w_electro.qc @@ -263,7 +263,7 @@ void lgbeam_think() return; } - if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen) + if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.frozen) { if(self == owner_player.lgbeam) owner_player.lgbeam = world;