]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/porto.qc
Transifex autosync
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / porto.qc
1 #include "porto.qh"
2
3 #ifdef CSQC
4 STATIC_INIT(Porto)
5 {
6         entity e = new_pure(porto);
7         e.draw = Porto_Draw;
8         IL_PUSH(g_drawables, e);
9         e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
10 }
11
12 const int polyline_length = 16;
13 .vector polyline[polyline_length];
14 void Porto_Draw(entity this)
15 {
16         if (spectatee_status || intermission == 1 || intermission == 2 || STAT(HEALTH) <= 0 || WEP_CVAR(porto, secondary)) return;
17
18         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
19         {
20                 entity wepent = viewmodels[slot];
21
22                 if (wepent.activeweapon != WEP_PORTO) continue;
23
24                 vector pos = view_origin;
25                 vector dir = view_forward;
26                 makevectors(((autocvar_chase_active) ? warpzone_save_view_angles : view_angles));
27                 pos += v_right * -wepent.movedir.y
28                         +  v_up * wepent.movedir.z;
29
30                 if (wepent.angles_held_status)
31                 {
32                         makevectors(wepent.angles_held);
33                         dir = v_forward;
34                 }
35
36                 wepent.polyline[0] = pos;
37
38                 int portal_number = 0, portal1_idx = 1, portal_max = 2;
39                 int n = 1 + 2;  // 2 lines == 3 points
40                 for (int idx = 0; idx < n && idx < polyline_length - 1; )
41                 {
42                         traceline(pos, pos + 65536 * dir, true, this);
43                         dir = reflect(dir, trace_plane_normal);
44                         pos = trace_endpos;
45                         wepent.polyline[++idx] = pos;
46                         if ((trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) || (trace_dphitcontents & DPCONTENTS_PLAYERCLIP))
47                         {
48                                 n += 1;
49                                 continue;
50                         }
51                         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
52                         {
53                                 n = max(2, idx);
54                                 break;
55                         }
56                         // check size
57                         {
58                                 vector ang = vectoangles2(trace_plane_normal, dir);
59                                 ang.x = -ang.x;
60                                 makevectors(ang);
61                                 if (!CheckWireframeBox(this, pos - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward))
62                                 {
63                                         n = max(2, idx);
64                                         break;
65                                 }
66                         }
67                         portal_number += 1;
68                         if (portal_number >= portal_max) break;
69                         if (portal_number == 1) portal1_idx = idx;
70                 }
71                 for (int idx = 0; idx < n - 1; ++idx)
72                 {
73                         vector p = wepent.polyline[idx], q = wepent.polyline[idx + 1];
74                         if (idx == 0) p -= view_up * 16;  // line from player
75                         vector rgb = (idx < portal1_idx) ? '1 0 0' : '0 0 1';
76                         Draw_CylindricLine(p, q, 4, "", 1, 0, rgb, 0.5, DRAWFLAG_NORMAL, view_origin);
77                 }
78         }
79 }
80 #endif
81
82 #ifdef SVQC
83 #include <common/mapobjects/trigger/jumppads.qh>
84 #include <server/weapons/throwing.qh>
85
86 REGISTER_MUTATOR(porto_ticker, true);
87 MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
88         FOREACH_CLIENT(IS_PLAYER(it), it.porto_forbidden = max(0, it.porto_forbidden - 1));
89 }
90
91 void W_Porto_Success(entity this)
92 {
93         if(this.realowner == NULL)
94         {
95                 objerror(this, "Cannot succeed successfully: no owner\n");
96                 return;
97         }
98
99         this.realowner.porto_current = NULL;
100         delete(this);
101 }
102
103 void W_Porto_Fail(entity this, float failhard)
104 {
105         if(this.realowner == NULL)
106         {
107                 objerror(this, "Cannot fail successfully: no owner\n");
108                 return;
109         }
110
111         // no portals here!
112         if(this.cnt < 0)
113         {
114                 Portal_ClearWithID(this.realowner, this.portal_id);
115         }
116
117         this.realowner.porto_current = NULL;
118
119         if(this.cnt < 0 && !failhard && this.realowner.playerid == this.playerid && !IS_DEAD(this.realowner) && !(STAT(WEAPONS, this.realowner) & WEPSET(PORTO)))
120         {
121                 // FIXME: item properties should be obtained from the registry
122                 setsize(this, ITEM_D_MINS, ITEM_D_MAXS);
123                 setorigin(this, this.origin + trace_plane_normal);
124                 if(nudgeoutofsolid(this))
125                 {
126                         this.flags = FL_ITEM;
127                         IL_PUSH(g_items, this);
128                         this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
129                         tracetoss(this, this);
130                         if(vdist(trace_endpos - this.realowner.origin, <, 128))
131                         {
132                                 .entity weaponentity = this.weaponentity_fld;
133                                 W_ThrowNewWeapon(this.realowner, WEP_PORTO.m_id, 0, this.origin, this.velocity, weaponentity);
134                                 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_FAILED);
135                         }
136                 }
137         }
138         delete(this);
139 }
140
141 void W_Porto_Remove(entity p)
142 {
143         if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
144         {
145                 W_Porto_Fail(p.porto_current, 1);
146         }
147 }
148
149 void W_Porto_Think(entity this)
150 {
151         trace_plane_normal = '0 0 0';
152         if(this.realowner.playerid != this.playerid)
153                 delete(this);
154         else
155                 W_Porto_Fail(this, 0);
156 }
157
158 void W_Porto_Touch(entity this, entity toucher)
159 {
160         vector norm;
161
162         // do not use PROJECTILE_TOUCH here
163         // FIXME but DO handle warpzones!
164
165         if(toucher.classname == "portal")
166                 return; // handled by the portal
167
168         norm = trace_plane_normal;
169         if(trace_ent.iscreature)
170         {
171                 // TODO: why not use entity size?
172                 traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_CONST.z, MOVE_WORLDONLY, this);
173                 if(trace_fraction >= 1)
174                         return;
175                 if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) || (trace_dphitcontents & DPCONTENTS_PLAYERCLIP))
176                         return;
177                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
178                         return;
179         }
180
181         if(this.realowner.playerid != this.playerid)
182         {
183                 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
184                 delete(this);
185         }
186         else if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) || (trace_dphitcontents & DPCONTENTS_PLAYERCLIP))
187         {
188                 spamsound(this, CH_SHOTS, SND_PORTO_BOUNCE, VOL_BASE, ATTEN_NORM);
189                 // just reflect
190                 this.right_vector = this.right_vector - 2 * trace_plane_normal * (this.right_vector * trace_plane_normal);
191                 this.angles = vectoangles(this.velocity - 2 * trace_plane_normal * (this.velocity * trace_plane_normal));
192         }
193         else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
194         {
195                 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
196                 W_Porto_Fail(this, 0);
197                 if(this.cnt < 0)
198                         Portal_ClearAll_PortalsOnly(this.realowner);
199         }
200         else if(this.cnt == 0)
201         {
202                 // in-portal only
203                 if(Portal_SpawnInPortalAtTrace(this.realowner, this.right_vector, this.portal_id))
204                 {
205                         sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
206                         trace_plane_normal = norm;
207                         Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
208                         W_Porto_Success(this);
209                 }
210                 else
211                 {
212                         sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
213                         trace_plane_normal = norm;
214                         W_Porto_Fail(this, 0);
215                 }
216         }
217         else if(this.cnt == 1)
218         {
219                 // out-portal only
220                 if(Portal_SpawnOutPortalAtTrace(this.realowner, this.right_vector, this.portal_id))
221                 {
222                         sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
223                         trace_plane_normal = norm;
224                         Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
225                         W_Porto_Success(this);
226                 }
227                 else
228                 {
229                         sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
230                         trace_plane_normal = norm;
231                         W_Porto_Fail(this, 0);
232                 }
233         }
234         else if(this.effects & EF_RED)
235         {
236                 this.effects += EF_BLUE - EF_RED;
237                 if(Portal_SpawnInPortalAtTrace(this.realowner, this.right_vector, this.portal_id))
238                 {
239                         sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
240                         trace_plane_normal = norm;
241                         Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
242                         this.right_vector = this.right_vector - 2 * trace_plane_normal * (this.right_vector * norm);
243                         this.angles = vectoangles(this.velocity - 2 * trace_plane_normal * (this.velocity * norm));
244                         CSQCProjectile(this, true, PROJECTILE_PORTO_BLUE, true); // change type
245                 }
246                 else
247                 {
248                         sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
249                         trace_plane_normal = norm;
250                         Portal_ClearAll_PortalsOnly(this.realowner);
251                         W_Porto_Fail(this, 0);
252                 }
253         }
254         else
255         {
256                 if(this.realowner.portal_in.portal_id == this.portal_id)
257                 {
258                         if(Portal_SpawnOutPortalAtTrace(this.realowner, this.right_vector, this.portal_id))
259                         {
260                                 sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
261                                 trace_plane_normal = norm;
262                                 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
263                                 W_Porto_Success(this);
264                         }
265                         else
266                         {
267                                 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
268                                 Portal_ClearAll_PortalsOnly(this.realowner);
269                                 W_Porto_Fail(this, 0);
270                         }
271                 }
272                 else
273                 {
274                         sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
275                         Portal_ClearAll_PortalsOnly(this.realowner);
276                         W_Porto_Fail(this, 0);
277                 }
278         }
279 }
280
281 void W_Porto_Attack(Weapon thiswep, entity actor, .entity weaponentity, float type)
282 {
283         entity gren;
284
285         W_SetupShot(actor, weaponentity, false, 4, SND_PORTO_FIRE, CH_WEAPON_A, 0, thiswep.m_id); // TODO: does the deathtype even need to be set here? porto can't hurt people
286         // always shoot from the eye
287         w_shotdir = v_forward;
288         w_shotorg = actor.origin + actor.view_ofs + ((w_shotorg - actor.origin - actor.view_ofs) * v_forward) * v_forward;
289
290         //Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
291
292         gren = new(porto);
293         gren.weaponentity_fld = weaponentity;
294         gren.cnt = type;
295         gren.owner = gren.realowner = actor;
296         gren.playerid = actor.playerid;
297         gren.bot_dodge = true;
298         gren.bot_dodgerating = 200;
299         set_movetype(gren, MOVETYPE_BOUNCEMISSILE);
300         PROJECTILE_MAKETRIGGER(gren);
301         gren.effects = EF_RED;
302         gren.scale = 4;
303         setorigin(gren, w_shotorg);
304         setsize(gren, '0 0 0', '0 0 0');
305
306         gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime);
307         setthink(gren, W_Porto_Think);
308         settouch(gren, W_Porto_Touch);
309
310         // TODO: handle as mutator effect
311         if(StatusEffects_active(STATUSEFFECT_Strength, actor))
312                 W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0);
313         else
314                 W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0);
315
316         gren.angles = vectoangles(gren.velocity);
317         gren.flags = FL_PROJECTILE;
318         IL_PUSH(g_projectiles, gren);
319         IL_PUSH(g_bot_dodge, gren);
320
321         gren.portal_id = time;
322         actor.porto_current = gren;
323         gren.playerid = actor.playerid;
324         fixedmakevectors(fixedvectoangles(gren.velocity));
325         gren.right_vector = v_right;
326
327         gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
328
329         if(type > 0)
330                 CSQCProjectile(gren, true, PROJECTILE_PORTO_BLUE, true);
331         else
332                 CSQCProjectile(gren, true, PROJECTILE_PORTO_RED, true);
333
334         MUTATOR_CALLHOOK(EditProjectile, actor, gren);
335 }
336
337 METHOD(PortoLaunch, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
338 {
339     PHYS_INPUT_BUTTON_ATCK(actor) = false;
340     PHYS_INPUT_BUTTON_ATCK2(actor) = false;
341     if(!WEP_CVAR(porto, secondary))
342         if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false, true))
343             PHYS_INPUT_BUTTON_ATCK(actor) = true;
344 }
345 METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
346 {
347     if(WEP_CVAR(porto, secondary))
348     {
349         if(fire & 1)
350         if(!actor.porto_current)
351         if(!actor.porto_forbidden)
352         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
353         {
354             W_Porto_Attack(thiswep, actor, weaponentity, 0);
355             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
356         }
357
358         if(fire & 2)
359         if(!actor.porto_current)
360         if(!actor.porto_forbidden)
361         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(porto, refire)))
362         {
363             W_Porto_Attack(thiswep, actor, weaponentity, 1);
364             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
365         }
366     }
367     else
368     {
369         if(actor.(weaponentity).porto_v_angle_held)
370         {
371             if(!(fire & 2))
372                 actor.(weaponentity).porto_v_angle_held = 0;
373         }
374         else
375         {
376             if(fire & 2)
377             {
378                 actor.(weaponentity).porto_v_angle = actor.v_angle;
379                 actor.(weaponentity).porto_v_angle_held = 1;
380             }
381         }
382         if(actor.(weaponentity).porto_v_angle_held)
383             makevectors(actor.(weaponentity).porto_v_angle); // override the previously set angles
384
385         if(fire & 1)
386         if(!actor.porto_current)
387         if(!actor.porto_forbidden)
388         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
389         {
390             W_Porto_Attack(thiswep, actor, weaponentity, -1);
391             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
392         }
393     }
394 }
395 METHOD(PortoLaunch, wr_checkammo1, bool(entity thiswep, entity this, .entity weaponentity))
396 {
397     // always allow infinite ammo
398     return true;
399 }
400 METHOD(PortoLaunch, wr_checkammo2, bool(entity thiswep, entity this, .entity weaponentity))
401 {
402     // always allow infinite ammo
403     return true;
404 }
405 METHOD(PortoLaunch, wr_resetplayer, void(entity thiswep, entity actor))
406 {
407     actor.porto_current = NULL;
408 }
409 #endif
410 #ifdef CSQC
411 METHOD(PortoLaunch, wr_impacteffect, void(entity this, entity actor)) {
412     LOG_WARN("Since when does Porto send DamageInfo?");
413 }
414 #endif