1#include "sv_instagib.qh"
2
3//int autocvar_g_instagib_ammo_drop;
4bool autocvar_g_instagib_ammo_convert_cells;
5bool autocvar_g_instagib_ammo_convert_rockets;
6bool autocvar_g_instagib_ammo_convert_shells;
7bool autocvar_g_instagib_ammo_convert_bullets;
8int autocvar_g_instagib_extralives;
9float autocvar_g_instagib_speed_highspeed;
10
11#include <server/client.qh>
12
13#include <common/items/_mod.qh>
14
15REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball);
16
17spawnfunc(item_minst_cells)
18{
19	if (!g_instagib) { delete(this); return; }
20	StartItem(this, ITEM_VaporizerCells);
21}
22
23void instagib_invisibility(entity this)
24{
25	this.strength_finished = autocvar_g_balance_powerup_strength_time;
26	StartItem(this, ITEM_Invisibility);
27}
28
29void instagib_extralife(entity this)
30{
31	StartItem(this, ITEM_ExtraLife);
32}
33
34void instagib_speed(entity this)
35{
36	this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
37	StartItem(this, ITEM_Speed);
38}
39
40.float instagib_nextthink;
41.float instagib_needammo;
42void instagib_stop_countdown(entity e)
43{
44	if (!e.instagib_needammo)
45		return;
46	Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER, CPID_INSTAGIB_FINDAMMO);
47	e.instagib_needammo = false;
48}
49void instagib_ammocheck(entity this)
50{
51	if(time < this.instagib_nextthink)
52		return;
53	if(!IS_PLAYER(this))
54		return; // not a player
55
56	if(IS_DEAD(this) || game_stopped)
57		instagib_stop_countdown(this);
58	else if (this.ammo_cells > 0 || (this.items & IT_UNLIMITED_WEAPON_AMMO) || (this.flags & FL_GODMODE))
59		instagib_stop_countdown(this);
60	else if(autocvar_g_rm && autocvar_g_rm_laser)
61	{
62		if(!this.instagib_needammo)
63		{
64			Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE);
65			this.instagib_needammo = true;
66		}
67	}
68	else
69	{
70		this.instagib_needammo = true;
71		if (this.health <= 5)
72		{
73			Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
74			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_INSTAGIB_TERMINATED);
75		}
76		else if (this.health <= 10)
77		{
78			Damage(this, this, this, 5, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
79			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_1);
80		}
81		else if (this.health <= 20)
82		{
83			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
84			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_2);
85		}
86		else if (this.health <= 30)
87		{
88			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
89			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_3);
90		}
91		else if (this.health <= 40)
92		{
93			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
94			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_4);
95		}
96		else if (this.health <= 50)
97		{
98			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
99			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_5);
100		}
101		else if (this.health <= 60)
102		{
103			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
104			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_6);
105		}
106		else if (this.health <= 70)
107		{
108			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
109			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_7);
110		}
111		else if (this.health <= 80)
112		{
113			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
114			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_8);
115		}
116		else if (this.health <= 90)
117		{
118			Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_INSTAGIB_FINDAMMO);
119			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
120			Send_Notification(NOTIF_ONE, this, MSG_ANNCE, ANNCE_NUM_9);
121		}
122		else
123		{
124			Send_Notification(NOTIF_ONE_ONLY, this, MSG_MULTI, MULTI_INSTAGIB_FINDAMMO);
125			Damage(this, this, this, 10, DEATH_NOAMMO.m_id, this.origin, '0 0 0');
126		}
127	}
128	this.instagib_nextthink = time + 1;
129}
130
131MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd)
132{
133	FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(instagib_stop_countdown(it)));
134}
135
136MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
137{
138	entity item = M_ARGV(1, entity);
139
140	item.monster_loot = ITEM_VaporizerCells;
141}
142
143MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterSpawn)
144{
145	entity mon = M_ARGV(0, entity);
146
147	// always refill ammo
148	if(mon.monsterid == MON_MAGE.monsterid)
149		mon.skin = 1;
150}
151
152MUTATOR_HOOKFUNCTION(mutator_instagib, BotShouldAttack)
153{
154	entity targ = M_ARGV(1, entity);
155
156	if (targ.items & ITEM_Invisibility.m_itemid)
157		return true;
158}
159
160MUTATOR_HOOKFUNCTION(mutator_instagib, MakePlayerObserver)
161{
162	entity player = M_ARGV(0, entity);
163
164	instagib_stop_countdown(player);
165}
166
167MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerSpawn)
168{
169	entity player = M_ARGV(0, entity);
170
171	player.effects |= EF_FULLBRIGHT;
172}
173
174MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPreThink)
175{
176	entity player = M_ARGV(0, entity);
177
178	instagib_ammocheck(player);
179}
180
181MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerRegen)
182{
183	// no regeneration in instagib
184	return true;
185}
186
187MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPowerups)
188{
189	entity player = M_ARGV(0, entity);
190
191	if (!(player.effects & EF_FULLBRIGHT))
192		player.effects |= EF_FULLBRIGHT;
193
194	if (player.items & ITEM_Invisibility.m_itemid)
195	{
196		play_countdown(player, player.strength_finished, SND_POWEROFF);
197		if (time > player.strength_finished)
198		{
199			player.alpha = default_player_alpha;
200			player.exteriorweaponentity.alpha = default_weapon_alpha;
201			player.items &= ~ITEM_Invisibility.m_itemid;
202			Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY);
203		}
204	}
205	else
206	{
207		if (time < player.strength_finished)
208		{
209			player.alpha = autocvar_g_instagib_invis_alpha;
210			player.exteriorweaponentity.alpha = autocvar_g_instagib_invis_alpha;
211			player.items |= ITEM_Invisibility.m_itemid;
212			Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_INVISIBILITY, player.netname);
213			Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_INVISIBILITY);
214		}
215	}
216
217	if (player.items & ITEM_Speed.m_itemid)
218	{
219		play_countdown(player, player.invincible_finished, SND_POWEROFF);
220		if (time > player.invincible_finished)
221		{
222			player.items &= ~ITEM_Speed.m_itemid;
223			Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERDOWN_SPEED);
224		}
225	}
226	else
227	{
228		if (time < player.invincible_finished)
229		{
230			player.items |= ITEM_Speed.m_itemid;
231			Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SPEED, player.netname);
232			Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_POWERUP_SPEED);
233		}
234	}
235}
236
237.float stat_sv_maxspeed;
238
239MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerPhysics)
240{
241	entity player = M_ARGV(0, entity);
242
243	if(player.items & ITEM_Speed.m_itemid)
244		player.stat_sv_maxspeed = player.stat_sv_maxspeed * autocvar_g_instagib_speed_highspeed;
245}
246
247MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDamage_SplitHealthArmor)
248{
249	M_ARGV(4, float) = M_ARGV(7, float); // take = damage
250	M_ARGV(5, float) = 0; // save
251}
252
253MUTATOR_HOOKFUNCTION(mutator_instagib, ForbidThrowCurrentWeapon)
254{
255	// weapon dropping on death handled by FilterItem
256	return true;
257}
258
259MUTATOR_HOOKFUNCTION(mutator_instagib, Damage_Calculate)
260{
261	entity frag_attacker = M_ARGV(1, entity);
262	entity frag_target = M_ARGV(2, entity);
263	float frag_deathtype = M_ARGV(3, float);
264	float frag_damage = M_ARGV(4, float);
265	float frag_mirrordamage = M_ARGV(5, float);
266	vector frag_force = M_ARGV(6, vector);
267
268	if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
269		frag_damage = 0;
270
271	if(IS_PLAYER(frag_target))
272	{
273		if(frag_deathtype == DEATH_FALL.m_id)
274			frag_damage = 0; // never count fall damage
275
276		if(!autocvar_g_instagib_damagedbycontents)
277		switch(DEATH_ENT(frag_deathtype))
278		{
279			case DEATH_DROWN:
280			case DEATH_SLIME:
281			case DEATH_LAVA:
282				frag_damage = 0;
283				break;
284		}
285
286		if(IS_PLAYER(frag_attacker))
287		if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
288		{
289			if(!autocvar_g_instagib_friendlypush && SAME_TEAM(frag_target, frag_attacker))
290				frag_force = '0 0 0';
291
292			if(frag_target.armorvalue)
293			{
294				frag_target.armorvalue -= 1;
295				frag_damage = 0;
296				frag_target.damage_dealt += 1;
297				frag_attacker.damage_dealt += 1;
298				Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_target.armorvalue);
299			}
300		}
301
302		if(IS_PLAYER(frag_attacker) && DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
303		{
304			if(frag_deathtype & HITTYPE_SECONDARY)
305			{
306				if(!autocvar_g_instagib_blaster_keepdamage || frag_attacker == frag_target)
307				{
308					frag_damage = 0;
309					if(!autocvar_g_instagib_mirrordamage)
310						frag_mirrordamage = 0; // never do mirror damage on enemies
311				}
312
313				if(frag_target != frag_attacker)
314				{
315					if(frag_damage <= 0 && frag_target.health > 0) { Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); }
316					if(!autocvar_g_instagib_blaster_keepforce)
317						frag_force = '0 0 0';
318				}
319			}
320		}
321	}
322
323	if(!autocvar_g_instagib_mirrordamage) // only apply the taking lives hack if we don't want to support real damage mirroring
324	if(IS_PLAYER(frag_attacker))
325	if(frag_mirrordamage > 0)
326	{
327		// just lose extra LIVES, don't kill the player for mirror damage
328		if(frag_attacker.armorvalue > 0)
329		{
330			frag_attacker.armorvalue -= 1;
331			Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_INSTAGIB_LIVES_REMAINING, frag_attacker.armorvalue);
332			frag_attacker.damage_dealt += frag_mirrordamage;
333		}
334		frag_mirrordamage = 0;
335	}
336
337	if(frag_target.alpha && frag_target.alpha < 1)
338	if(IS_PLAYER(frag_target))
339		yoda = 1;
340
341	M_ARGV(4, float) = frag_damage;
342	M_ARGV(5, float) = frag_mirrordamage;
343	M_ARGV(6, vector) = frag_force;
344}
345
346MUTATOR_HOOKFUNCTION(mutator_instagib, SetStartItems)
347{
348	start_health       = warmup_start_health       = 100;
349	start_armorvalue   = warmup_start_armorvalue   = 0;
350
351	start_ammo_shells  = warmup_start_ammo_shells  = 0;
352	start_ammo_nails   = warmup_start_ammo_nails   = 0;
353	start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_instagib_ammo_start");
354	start_ammo_plasma  = warmup_start_ammo_plasma  = 0;
355	start_ammo_rockets = warmup_start_ammo_rockets = 0;
356	//start_ammo_fuel    = warmup_start_ammo_fuel    = 0;
357
358	start_weapons = warmup_start_weapons = WEPSET(VAPORIZER);
359	start_items |= IT_UNLIMITED_SUPERWEAPONS;
360}
361
362MUTATOR_HOOKFUNCTION(mutator_instagib, SetWeaponArena)
363{
364	// turn weapon arena off
365	M_ARGV(0, string) = "off";
366}
367
368void replace_with_insta_cells(entity item)
369{
370	entity e = spawn();
371	setorigin(e, item.origin);
372	e.noalign = item.noalign;
373	e.cnt = item.cnt;
374	e.team = item.team;
375	e.spawnfunc_checked = true;
376	spawnfunc_item_minst_cells(e);
377}
378
379MUTATOR_HOOKFUNCTION(mutator_instagib, FilterItem)
380{
381	entity item = M_ARGV(0, entity);
382
383	if(item.classname == "item_cells")
384	{
385		if(autocvar_g_instagib_ammo_convert_cells)
386		{
387			replace_with_insta_cells(item);
388		}
389		return true;
390	}
391	else if(item.classname == "item_rockets")
392	{
393		if(autocvar_g_instagib_ammo_convert_rockets)
394		{
395			replace_with_insta_cells(item);
396		}
397		return true;
398	}
399	else if(item.classname == "item_shells")
400	{
401		if(autocvar_g_instagib_ammo_convert_shells)
402		{
403			replace_with_insta_cells(item);
404		}
405		return true;
406	}
407	else if(item.classname == "item_bullets")
408	{
409		if(autocvar_g_instagib_ammo_convert_bullets)
410		{
411			replace_with_insta_cells(item);
412		}
413		return true;
414	}
415
416	if(item.weapon == WEP_VAPORIZER.m_id && item.classname == "droppedweapon")
417	{
418		item.ammo_cells = autocvar_g_instagib_ammo_drop;
419		return false;
420	}
421
422	if(item.weapon == WEP_DEVASTATOR.m_id || item.weapon == WEP_VORTEX.m_id)
423	{
424		replace_with_insta_cells(item);
425		return true;
426	}
427
428	if(item.flags & FL_POWERUP)
429		return false;
430
431	if(item.ammo_cells > autocvar_g_instagib_ammo_drop && item.classname != "item_minst_cells")
432		item.ammo_cells = autocvar_g_instagib_ammo_drop;
433
434	if(item.ammo_cells && !item.weapon)
435		return false;
436
437	return true;
438}
439
440MUTATOR_HOOKFUNCTION(mutator_instagib, CustomizeWaypoint)
441{
442	entity wp = M_ARGV(0, entity);
443	entity player = M_ARGV(1, entity);
444
445	entity e = WaypointSprite_getviewentity(player);
446
447	// if you have the invisibility powerup, sprites ALWAYS are restricted to your team
448	// but only apply this to real players, not to spectators
449	if((wp.owner.flags & FL_CLIENT) && (wp.owner.items & ITEM_Invisibility.m_itemid) && (e == player))
450	if(DIFF_TEAM(wp.owner, e))
451		return true;
452}
453
454MUTATOR_HOOKFUNCTION(mutator_instagib, PlayerDies)
455{
456	float frag_deathtype = M_ARGV(3, float);
457
458	if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))
459		M_ARGV(4, float) = 1000; // always gib if it was a vaporizer death
460}
461
462MUTATOR_HOOKFUNCTION(mutator_instagib, ItemTouch)
463{
464	entity item = M_ARGV(0, entity);
465	entity toucher = M_ARGV(1, entity);
466
467	if(item.ammo_cells)
468	{
469		// play some cool sounds ;)
470		if (IS_CLIENT(toucher))
471		{
472			if(toucher.health <= 5)
473				Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_LASTSECOND);
474			else if(toucher.health < 50)
475				Send_Notification(NOTIF_ONE, toucher, MSG_ANNCE, ANNCE_INSTAGIB_NARROWLY);
476		}
477
478		if(toucher.health < 100)
479			toucher.health = 100;
480
481		return MUT_ITEMTOUCH_CONTINUE;
482	}
483
484	if(item.itemdef == ITEM_ExtraLife)
485	{
486		toucher.armorvalue = bound(toucher.armorvalue, 999, toucher.armorvalue + autocvar_g_instagib_extralives);
487		Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
488		return MUT_ITEMTOUCH_PICKUP;
489	}
490
491	return MUT_ITEMTOUCH_CONTINUE;
492}
493
494MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn)
495{
496	if (!autocvar_g_powerups) { return; }
497	entity ent = M_ARGV(0, entity);
498	// Can't use .itemdef here
499	if (!(ent.classname == "item_strength" || ent.classname == "item_invincible" || ent.classname == "item_health_mega"))
500		return;
501
502	entity e = spawn();
503
504	float r = random();
505	if (r < 0.3)
506		setthink(e, instagib_invisibility);
507	else if (r < 0.6)
508		setthink(e, instagib_extralife);
509	else
510		setthink(e, instagib_speed);
511
512	e.nextthink = time + 0.1;
513	e.spawnflags = ent.spawnflags;
514	e.noalign = ent.noalign;
515	setorigin(e, ent.origin);
516
517	return true;
518}
519
520MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString)
521{
522	M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib");
523}
524
525MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsPrettyString)
526{
527	M_ARGV(0, string) = strcat(M_ARGV(0, string), ", instagib");
528}
529
530MUTATOR_HOOKFUNCTION(mutator_instagib, SetModname)
531{
532	M_ARGV(0, string) = "InstaGib";
533	return true;
534}
535