make sure leadlimit is >= 0
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / itemstime / itemstime.qc
1 #include "itemstime.qh"
2
3 REGISTER_MUTATOR(itemstime, true);
4
5 REGISTER_NET_TEMP(itemstime)
6
7 #ifdef SVQC
8 void IT_Write(entity e, int i, float f) {
9     if (!IS_REAL_CLIENT(e)) return;
10     msg_entity = e;
11     WriteHeader(MSG_ONE, itemstime);
12     WriteByte(MSG_ONE, i);
13     WriteFloat(MSG_ONE, f);
14 }
15 #endif
16
17 #ifdef CSQC
18 // reserve one more spot for superweapons time
19 float ItemsTime_time[Items_MAX + 1];
20 float ItemsTime_availableTime[Items_MAX + 1];
21 NET_HANDLE(itemstime, bool isNew)
22 {
23     int i = ReadByte();
24     float f = ReadFloat();
25     return = true;
26     ItemsTime_time[i] = f;
27 }
28 #endif
29
30 #ifdef CSQC
31
32 STATIC_INIT(ItemsTime_Init) {
33         FOREACH(Items, true, {
34                 ItemsTime_time[it.m_id] = -1;
35         });
36         ItemsTime_time[Items_MAX] = -1;
37 }
38
39 int autocvar_hud_panel_itemstime = 2;
40 float autocvar_hud_panel_itemstime_dynamicsize = 1;
41 float autocvar_hud_panel_itemstime_ratio = 2;
42 int autocvar_hud_panel_itemstime_iconalign;
43 bool autocvar_hud_panel_itemstime_progressbar = 0;
44 float autocvar_hud_panel_itemstime_progressbar_maxtime = 30;
45 string autocvar_hud_panel_itemstime_progressbar_name = "progressbar";
46 float autocvar_hud_panel_itemstime_progressbar_reduced;
47 bool autocvar_hud_panel_itemstime_hidespawned = 1;
48 bool autocvar_hud_panel_itemstime_hidebig = false;
49 int autocvar_hud_panel_itemstime_text = 1;
50 #define hud_panel_itemstime_hidebig autocvar_hud_panel_itemstime_hidebig
51 #else
52 #define hud_panel_itemstime_hidebig false
53 #endif
54
55 bool Item_ItemsTime_SpectatorOnly(GameItem it)
56 {
57     return (false
58     || it == ITEM_ArmorMega     || (it == ITEM_ArmorBig && !hud_panel_itemstime_hidebig)
59     || it == ITEM_HealthMega    || (it == ITEM_HealthBig && !hud_panel_itemstime_hidebig)
60     );
61 }
62
63 bool Item_ItemsTime_Allow(GameItem it)
64 {
65     return (false
66     || it.instanceOfPowerup
67     || Item_ItemsTime_SpectatorOnly(it)
68     );
69 }
70
71 #ifdef SVQC
72
73 // reserve one more spot for superweapons time
74 float it_times[Items_MAX + 1];
75
76 STATIC_INIT(ItemsTime_Init) {
77         FOREACH(Items, Item_ItemsTime_Allow(it), {
78                 it_times[it.m_id] = -1;
79         });
80         it_times[Items_MAX] = -1;
81 }
82
83 void Item_ItemsTime_ResetTimes()
84 {
85     FOREACH(Items, Item_ItemsTime_Allow(it), {
86         it_times[it.m_id] = (it_times[it.m_id] == -1) ? -1 : 0;
87     });
88     it_times[Items_MAX] = (it_times[Items_MAX] == -1) ? -1 : 0;
89 }
90
91 void Item_ItemsTime_ResetTimesForPlayer(entity e)
92 {
93     FOREACH(Items, Item_ItemsTime_Allow(it), {
94         IT_Write(e, it.m_id, (it_times[it.m_id] == -1) ? -1 : 0);
95     });
96     IT_Write(e, Items_MAX, (it_times[Items_MAX] == -1) ? -1 : 0);
97 }
98
99 void Item_ItemsTime_SetTimesForPlayer(entity e)
100 {
101     FOREACH(Items, Item_ItemsTime_Allow(it), {
102         IT_Write(e, it.m_id, it_times[it.m_id]);
103     });
104     IT_Write(e, Items_MAX, it_times[Items_MAX]);
105 }
106
107 void Item_ItemsTime_SetTime(entity e, float t)
108 {
109     if (!autocvar_sv_itemstime)
110         return;
111
112     GameItem item = e.itemdef;
113     if (item.instanceOfGameItem)
114     {
115                 if (!item.instanceOfWeaponPickup)
116                         it_times[item.m_id] = t;
117                 else if (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS)
118                         it_times[Items_MAX] = t;
119     }
120 }
121
122 void Item_ItemsTime_SetTimesForAllPlayers()
123 {
124     FOREACH_CLIENT(IS_REAL_CLIENT(it) && (warmup_stage || !IS_PLAYER(it) || autocvar_sv_itemstime == 2), { Item_ItemsTime_SetTimesForPlayer(it); });
125 }
126
127 float Item_ItemsTime_UpdateTime(entity e, float t)
128 {
129     bool isavailable = (t == 0);
130     IL_EACH(g_items, it != e,
131     {
132         if(!(it.itemdef == e.itemdef || ((STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS) && (STAT(WEAPONS, it) & WEPSET_SUPERWEAPONS))))
133             continue;
134         if (it.scheduledrespawntime <= time)
135             isavailable = true;
136         else if (t == 0 || it.scheduledrespawntime < t)
137             t = it.scheduledrespawntime;
138     });
139     if (isavailable)
140         t = -t; // let know the client there's another available item
141     return t;
142 }
143
144 MUTATOR_HOOKFUNCTION(itemstime, reset_map_global)
145 {
146     Item_ItemsTime_ResetTimes();
147     // ALL the times need to be reset before .reset()ing each item
148     // since Item_Reset schedules respawn of superweapons and powerups
149     IL_EACH(g_items, it.reset,
150     {
151         Item_ItemsTime_SetTime(it, 0);
152     });
153     Item_ItemsTime_SetTimesForAllPlayers();
154 }
155
156 MUTATOR_HOOKFUNCTION(itemstime, MakePlayerObserver)
157 {
158     entity player = M_ARGV(0, entity);
159
160     Item_ItemsTime_SetTimesForPlayer(player);
161 }
162
163 MUTATOR_HOOKFUNCTION(itemstime, ClientConnect, CBC_ORDER_LAST)
164 {
165     entity player = M_ARGV(0, entity);
166
167         if(IS_PLAYER(player))
168         {
169                 // client became player on connection skipping putObserverInServer step
170                 if (IS_REAL_CLIENT(player))
171                 if (warmup_stage || autocvar_sv_itemstime == 2)
172                         Item_ItemsTime_SetTimesForPlayer(player);
173         }
174 }
175
176 MUTATOR_HOOKFUNCTION(itemstime, PlayerSpawn)
177 {
178     if (warmup_stage || autocvar_sv_itemstime == 2) return;
179     entity player = M_ARGV(0, entity);
180
181     Item_ItemsTime_ResetTimesForPlayer(player);
182 }
183
184 #endif
185
186 #ifdef CSQC
187
188 void DrawItemsTimeItem(vector myPos, vector mySize, float ar, string item_icon, float item_time, bool item_available, float item_availableTime)
189 {
190     float t = 0;
191     vector color = '0 0 0';
192     float picalpha;
193
194     if (autocvar_hud_panel_itemstime_hidespawned == 2)
195         picalpha = 1;
196     else if (item_available)
197     {
198         float BLINK_FACTOR = 0.15;
199         float BLINK_BASE = 0.85;
200         float BLINK_FREQ = 5;
201         picalpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
202     }
203     else
204         picalpha = 0.5;
205     t = floor(item_time - time + 0.999);
206     if (t < 5)
207         color = '0.7 0 0';
208     else if (t < 10)
209         color = '0.7 0.7 0';
210     else
211         color = '1 1 1';
212
213     vector picpos, numpos;
214     if (autocvar_hud_panel_itemstime_iconalign)
215     {
216         numpos = myPos;
217         picpos = myPos + eX * (ar - 1) * mySize_y;
218     }
219     else
220     {
221         numpos = myPos + eX * mySize_y;
222         picpos = myPos;
223     }
224
225     if (t > 0 && autocvar_hud_panel_itemstime_progressbar)
226     {
227         vector p_pos, p_size;
228         if (autocvar_hud_panel_itemstime_progressbar_reduced)
229         {
230             p_pos = numpos;
231             p_size = vec2(((ar - 1)/ar) * mySize.x, mySize.y);
232         }
233         else
234         {
235             p_pos = myPos;
236             p_size = mySize;
237         }
238         HUD_Panel_DrawProgressBar(p_pos, p_size, autocvar_hud_panel_itemstime_progressbar_name, t/autocvar_hud_panel_itemstime_progressbar_maxtime, 0, autocvar_hud_panel_itemstime_iconalign, color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
239     }
240
241     if(autocvar_hud_panel_itemstime_text)
242     {
243         if(t > 0)
244             drawstring_aspect(numpos, ftos(t), vec2(((ar - 1)/ar) * mySize.x, mySize.y), color, panel_fg_alpha, DRAWFLAG_NORMAL);
245         else if(precache_pic("gfx/hud/default/checkmark")) // COMPAT: check if this image exists, as 0.8.1 clients lack it
246             drawpic_aspect_skin(numpos, "checkmark", vec2((ar - 1) * mySize.y, mySize.y), '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL);
247         else // legacy code, if the image is missing just center the icon
248             picpos.x = myPos.x + mySize.x / 2 - mySize.y / 2;
249     }
250     if (item_availableTime)
251         drawpic_aspect_skin_expanding(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL, item_availableTime);
252     drawpic_aspect_skin(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL);
253 }
254
255 float Item_ItemsTime_GetTime(int item)
256 {
257     if(autocvar__hud_configure)
258     {
259         switch(item)
260         {
261             case ITEM_ArmorMega.m_id: return time + 0;
262             case ITEM_HealthMega.m_id: return time + 8;
263             case ITEM_Strength.m_id: return time + 0;
264             case ITEM_Shield.m_id: return time + 4;
265         }
266
267         return -1; // don't show others
268     }
269     else
270         return ItemsTime_time[item];
271 }
272
273 void HUD_ItemsTime()
274 {
275     if (!autocvar__hud_configure)
276     {
277         if (!(
278             (autocvar_hud_panel_itemstime == 1 && spectatee_status != 0)
279         ||      (autocvar_hud_panel_itemstime == 2 && (spectatee_status != 0 || warmup_stage || STAT(ITEMSTIME) == 2))
280             )) { return; }
281     }
282
283     int count = 0;
284     if (autocvar_hud_panel_itemstime_hidespawned == 1)
285     {
286         FOREACH(Items, Item_ItemsTime_Allow(it), {
287             count += (Item_ItemsTime_GetTime(it.m_id) > time || -Item_ItemsTime_GetTime(it.m_id) > time);
288         });
289         count += (Item_ItemsTime_GetTime(Items_MAX) > time || -Item_ItemsTime_GetTime(Items_MAX) > time);
290     }
291     else if (autocvar_hud_panel_itemstime_hidespawned == 2)
292     {
293         FOREACH(Items, Item_ItemsTime_Allow(it), {
294             count += (Item_ItemsTime_GetTime(it.m_id) > time);
295         });
296         count += (Item_ItemsTime_GetTime(Items_MAX) > time);
297     }
298     else
299     {
300         FOREACH(Items, Item_ItemsTime_Allow(it), {
301             count += (Item_ItemsTime_GetTime(it.m_id) != -1);
302         });
303         count += (Item_ItemsTime_GetTime(Items_MAX) != -1);
304     }
305     if (count == 0)
306         return;
307
308     HUD_Panel_LoadCvars();
309
310     vector pos, mySize;
311     pos = panel_pos;
312     mySize = panel_size;
313
314     if (panel_bg_padding)
315     {
316         pos += '1 1 0' * panel_bg_padding;
317         mySize -= '2 2 0' * panel_bg_padding;
318     }
319
320     float rows, columns;
321     float ar = max(2, autocvar_hud_panel_itemstime_ratio) + 1;
322     rows = HUD_GetRowCount(count, mySize, ar);
323     columns = ceil(count/rows);
324
325     vector itemstime_size = vec2(mySize.x / columns, mySize.y / rows);
326
327     vector offset = '0 0 0';
328     float newSize;
329     if (autocvar_hud_panel_itemstime_dynamicsize)
330     {
331         if (autocvar__hud_configure)
332         if (hud_configure_menu_open != 2)
333             HUD_Panel_DrawBg(); // also draw the bg of the entire panel
334
335         // reduce panel to avoid spacing items
336         if (itemstime_size.x / itemstime_size.y < ar)
337         {
338             newSize = rows * itemstime_size.x / ar;
339             pos.y += (mySize.y - newSize) / 2;
340             mySize.y = newSize;
341             itemstime_size.y = mySize.y / rows;
342         }
343         else
344         {
345             newSize = columns * itemstime_size.y * ar;
346             pos.x += (mySize.x - newSize) / 2;
347             mySize.x = newSize;
348             itemstime_size.x = mySize.x / columns;
349         }
350         panel_pos = pos - '1 1 0' * panel_bg_padding;
351         panel_size = mySize + '2 2 0' * panel_bg_padding;
352     }
353     else
354     {
355         if (itemstime_size.x/itemstime_size.y > ar)
356         {
357             newSize = ar * itemstime_size.y;
358             offset.x = itemstime_size.x - newSize;
359             pos.x += offset.x/2;
360             itemstime_size.x = newSize;
361         }
362         else
363         {
364             newSize = 1/ar * itemstime_size.x;
365             offset.y = itemstime_size.y - newSize;
366             pos.y += offset.y/2;
367             itemstime_size.y = newSize;
368         }
369     }
370
371     HUD_Scale_Enable();
372     HUD_Panel_DrawBg();
373
374     float row = 0, column = 0;
375     bool item_available;
376     int id = 0;
377     string icon = "";
378     FOREACH(Items, Item_ItemsTime_Allow(it) && Item_ItemsTime_GetTime(it.m_id) != -1, {
379         id = it.m_id;
380         icon = it.m_icon;
381
382 LABEL(iteration)
383         float item_time = Item_ItemsTime_GetTime(id);
384         if (item_time < -1)
385         {
386             item_available = true;
387             item_time = -item_time;
388         }
389         else
390             item_available = (item_time <= time);
391
392         if (Item_ItemsTime_GetTime(id) >= 0)
393         {
394             if (time <= Item_ItemsTime_GetTime(id))
395                 ItemsTime_availableTime[id] = 0;
396             else if (ItemsTime_availableTime[id] == 0)
397                 ItemsTime_availableTime[id] = time;
398         }
399         else if (ItemsTime_availableTime[id] == 0)
400             ItemsTime_availableTime[id] = time;
401
402         float f = (time - ItemsTime_availableTime[id]) * 2;
403         f = (f > 1) ? 0 : bound(0, f, 1);
404
405         if (autocvar_hud_panel_itemstime_hidespawned == 1)
406             if (!(Item_ItemsTime_GetTime(id) > time || -Item_ItemsTime_GetTime(id) > time))
407                 continue;
408
409         if (autocvar_hud_panel_itemstime_hidespawned == 2)
410             if (!(Item_ItemsTime_GetTime(id) > time))
411                 continue;
412
413         DrawItemsTimeItem(pos + vec2(column * (itemstime_size.x + offset.x), row * (itemstime_size.y + offset.y)), itemstime_size, ar, icon, item_time, item_available, f);
414         ++row;
415         if (row >= rows)
416         {
417             row = 0;
418             column = column + 1;
419         }
420         if(id == Items_MAX) // can happen only in the last fake iteration
421                 break;
422     });
423     // add another fake iteration for superweapons time
424     if(id < Items_MAX && Item_ItemsTime_GetTime(Items_MAX) != -1)
425     {
426                 id = Items_MAX;
427                 icon = "superweapons";
428                 goto iteration;
429     }
430 }
431
432 #endif