]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/powerups/sv_powerups.qc
d655ae19a29458e83bd7b52ab75becd9eb6ff7fe
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / powerups / sv_powerups.qc
1 #include "sv_powerups.qh"
2
3 MUTATOR_HOOKFUNCTION(powerups, W_PlayStrengthSound)
4 {
5         entity player = M_ARGV(0, entity);
6
7         if(StatusEffects_active(STATUSEFFECT_Strength, player)
8                 && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
9                 || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
10                 {
11                         sound(player, CH_TRIGGER, SND_STRENGTH_FIRE, VOL_BASE, ATTEN_NORM);
12                         player.prevstrengthsound = time;
13                 }
14         player.prevstrengthsoundattempt = time;
15 }
16
17 MUTATOR_HOOKFUNCTION(powerups, LogDeath_AppendItemCodes)
18 {
19         entity player = M_ARGV(0, entity);
20
21         if(StatusEffects_active(STATUSEFFECT_Strength, player))
22                 M_ARGV(1, string) = strcat(M_ARGV(1, string), "S");
23
24         if(StatusEffects_active(STATUSEFFECT_Shield, player))
25                 M_ARGV(1, string) = strcat(M_ARGV(1, string), "I");
26
27         // TODO: item codes for other powerups?
28 }
29
30 MUTATOR_HOOKFUNCTION(powerups, Damage_Calculate)
31 {
32         entity attacker = M_ARGV(1, entity);
33         entity targ = M_ARGV(2, entity);
34
35         // apply strength multiplier
36         if(StatusEffects_active(STATUSEFFECT_Strength, attacker))
37         {
38                 if(targ == attacker)
39                 {
40                         M_ARGV(4, float) = M_ARGV(4, float) * autocvar_g_balance_powerup_strength_selfdamage;
41                         M_ARGV(6, vector) = M_ARGV(6, vector) * autocvar_g_balance_powerup_strength_selfforce;
42                 }
43                 else
44                 {
45                         M_ARGV(4, float) = M_ARGV(4, float) * autocvar_g_balance_powerup_strength_damage;
46                         M_ARGV(6, vector) = M_ARGV(6, vector) * autocvar_g_balance_powerup_strength_force;
47                 }
48         }
49
50         // apply shield multiplier
51         if(StatusEffects_active(STATUSEFFECT_Shield, targ))
52         {
53                 M_ARGV(4, float) = M_ARGV(4, float) * autocvar_g_balance_powerup_invincible_takedamage;
54                 if (targ != attacker)
55                 {
56                         M_ARGV(6, vector) = M_ARGV(6, vector) * autocvar_g_balance_powerup_invincible_takeforce;
57                 }
58         }
59 }
60
61 MUTATOR_HOOKFUNCTION(powerups, CustomizeWaypoint)
62 {
63         entity wp = M_ARGV(0, entity);
64         entity player = M_ARGV(1, entity);
65
66         entity e = WaypointSprite_getviewentity(player);
67
68         // if you have the invisibility powerup, sprites ALWAYS are restricted to your team
69         // but only apply this to real players, not to spectators
70         if(IS_CLIENT(wp.owner) && (e == player) && DIFF_TEAM(wp.owner, e) && StatusEffects_active(STATUSEFFECT_Invisibility, wp.owner))
71                 return true;
72 }
73
74 MUTATOR_HOOKFUNCTION(powerups, MonsterValidTarget)
75 {
76         entity targ = M_ARGV(1, entity);
77         return StatusEffects_active(STATUSEFFECT_Invisibility, targ);
78 }
79
80 void powerups_DropItem_Think(entity this)
81 {
82         TakeResource(this, RES_HEALTH, 1);
83
84         if(GetResource(this, RES_HEALTH) < 1) {
85                 RemoveItem(this);
86                 return;
87         }
88
89         // Only needed to update if the timer of the powerup is running
90         if(!GetResource(this, RES_ARMOR))
91                 WaypointSprite_UpdateHealth(this.waypointsprite_attached, GetResource(this, RES_HEALTH));
92
93         this.nextthink = time + 1;
94 }
95
96 void powerups_DropItem(entity this, StatusEffects effect, bool freezeTimer)
97 {
98         entity item = Item_DefinitionFromInternalName(effect.netname);
99         float t = StatusEffects_gettime(effect, this);
100         float timeleft = t - time;
101         float maxtime = 0;
102
103         if(timeleft <= 1 || !item)
104                 return;
105         entity e = spawn();
106
107         // If we want the timer to keep running, we enable expiring then use the exact time the powerup will finish at.
108         // If we want the timer to freeze, we disable expiring and we just use the time left of the powerup.
109         // See Item_SetExpiring() below.
110         float finished = (freezeTimer ? timeleft : t);
111
112         // If the timer is frozen, the item will stay on the floor for 20 secs (same as weapons),
113         // otherwise it'll disappear after the timer runs out.
114         float time_to_live = (freezeTimer ? autocvar_g_items_dropped_lifetime : timeleft);
115
116         // TODO: items cannot hold their "item field" yet, so we need to list all the powerups here!
117         switch(item)
118         {
119                 case ITEM_Strength: e.strength_finished = finished; maxtime = autocvar_g_balance_powerup_strength_time; break;
120                 case ITEM_Shield: e.invincible_finished = finished; maxtime = autocvar_g_balance_powerup_invincible_time; break;
121                 case ITEM_Invisibility: e.invisibility_finished = finished; maxtime = autocvar_g_balance_powerup_invincible_time; break;
122                 case ITEM_Speed: e.speed_finished = finished; maxtime = autocvar_g_balance_powerup_speed_time; break;
123         }
124         vector vel = W_CalculateProjectileVelocity(this, this.velocity, v_forward * 750, false);
125         if (!Item_InitializeLoot(e, item.m_canonical_spawnfunc, this.origin, vel, time_to_live))
126                 return;
127
128         e.item_spawnshieldtime = time + 0.5;
129
130         if(!freezeTimer)
131                 Item_SetExpiring(e, true);
132
133         // Use health as time left to live
134         SetResourceExplicit(e, RES_HEALTH, time_to_live);
135
136         // Use armor as timer freezer
137         if(freezeTimer)
138                 SetResourceExplicit(e, RES_ARMOR, 1);
139
140         // Create waypoint displaying time left of the powerup
141         entity wp = WaypointSprite_Spawn(WP_Item, 0, 0, e, '0 0 1' * e.maxs.z, NULL, 0, e, waypointsprite_attached, true, RADARICON_Item);
142         wp.wp_extra = item.m_id;
143         WaypointSprite_UpdateMaxHealth(e.waypointsprite_attached, maxtime);
144         WaypointSprite_UpdateHealth(e.waypointsprite_attached, timeleft);
145
146         // Start dropping its time to live
147         setthink(e, powerups_DropItem_Think);
148         e.nextthink = time + 1;
149 }
150
151 MUTATOR_HOOKFUNCTION(powerups, ItemTouched)
152 {
153         entity e = M_ARGV(0, entity);
154         if(e.waypointsprite_attached)
155                 WaypointSprite_Kill(e.waypointsprite_attached);
156 }
157
158 MUTATOR_HOOKFUNCTION(powerups, PlayerDies)
159 {
160         if(!autocvar_g_powerups_drop_ondeath)
161                 return;
162
163         entity frag_target = M_ARGV(2, entity);
164
165         FOREACH(StatusEffect, it.instanceOfPowerups,
166         {
167                 if(StatusEffects_active(it, frag_target))
168                         powerups_DropItem(frag_target, it, autocvar_g_powerups_drop_ondeath == 2);
169         });
170 }
171
172 MUTATOR_HOOKFUNCTION(powerups, PlayerUseKey)
173 {
174         if(MUTATOR_RETURNVALUE || game_stopped || !autocvar_g_powerups_drop) return;
175
176         entity player = M_ARGV(0, entity);
177
178         FOREACH(StatusEffect, it.instanceOfPowerups,
179         {
180                 if(StatusEffects_active(it, player)) {
181                         powerups_DropItem(player, it, autocvar_g_powerups_drop == 2);
182                         StatusEffects_remove(it, player, STATUSEFFECT_REMOVE_NORMAL);
183                         return true;
184                 }
185         });
186 }
187
188 MUTATOR_HOOKFUNCTION(powerups, PlayerPhysics_UpdateStats)
189 {
190         entity player = M_ARGV(0, entity);
191         // these automatically reset, no need to worry
192
193         if(StatusEffects_active(STATUSEFFECT_Speed, player))
194                 STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_balance_powerup_speed_highspeed;
195 }
196
197 MUTATOR_HOOKFUNCTION(powerups, WeaponRateFactor)
198 {
199         entity player = M_ARGV(1, entity);
200
201         if(StatusEffects_active(STATUSEFFECT_Speed, player))
202                 M_ARGV(0, float) *= autocvar_g_balance_powerup_speed_attackrate;
203 }
204
205 MUTATOR_HOOKFUNCTION(powerups, BuildMutatorsPrettyString)
206 {
207         if(autocvar_g_powerups == 0)
208                 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", No powerups");
209         if(autocvar_g_powerups > 0)
210                 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Powerups");
211 }
212
213 MUTATOR_HOOKFUNCTION(powerups, BotShouldAttack)
214 {
215         entity targ = M_ARGV(1, entity);
216
217         if(StatusEffects_active(STATUSEFFECT_Invisibility, targ))
218                 return true;
219 }
220
221 MUTATOR_HOOKFUNCTION(powerups, BuildMutatorsString)
222 {
223         if(autocvar_g_powerups == 0)
224                 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":no_powerups");
225         if(autocvar_g_powerups > 0)
226                 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":powerups");
227 }