Xonotic
porto.qc
Go to the documentation of this file.
1 #include "porto.qh"
2 
3 #ifdef CSQC
4 STATIC_INIT(Porto)
5 {
6  entity e = new_pure(porto);
7  e.draw = Porto_Draw;
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;
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;
47  {
48  n += 1;
49  continue;
50  }
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
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  {
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  setsize(this, '-16 -16 0', '16 16 48');
122  setorigin(this, this.origin + trace_plane_normal);
123  if(move_out_of_solid(this))
124  {
125  this.flags = FL_ITEM;
126  IL_PUSH(g_items, this);
127  this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
128  tracetoss(this, this);
129  if(vdist(trace_endpos - this.realowner.origin, <, 128))
130  {
131  .entity weaponentity = this.weaponentity_fld;
132  W_ThrowNewWeapon(this.realowner, WEP_PORTO.m_id, 0, this.origin, this.velocity, weaponentity);
133  Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_FAILED);
134  }
135  }
136  }
137  delete(this);
138 }
139 
140 void W_Porto_Remove(entity p)
141 {
142  if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
143  {
144  W_Porto_Fail(p.porto_current, 1);
145  }
146 }
147 
148 void W_Porto_Think(entity this)
149 {
150  trace_plane_normal = '0 0 0';
151  if(this.realowner.playerid != this.playerid)
152  delete(this);
153  else
154  W_Porto_Fail(this, 0);
155 }
156 
157 void W_Porto_Touch(entity this, entity toucher)
158 {
159  vector norm;
160 
161  // do not use PROJECTILE_TOUCH here
162  // FIXME but DO handle warpzones!
163 
164  if(toucher.classname == "portal")
165  return; // handled by the portal
166 
167  norm = trace_plane_normal;
168  if(trace_ent.iscreature)
169  {
170  // TODO: why not use entity size?
171  traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_CONST.z, MOVE_WORLDONLY, this);
172  if(trace_fraction >= 1)
173  return;
174  if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
175  return;
176  if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
177  return;
178  }
179 
180  if(this.realowner.playerid != this.playerid)
181  {
182  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
183  delete(this);
184  }
185  else if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) || (trace_dphitcontents & DPCONTENTS_PLAYERCLIP))
186  {
187  spamsound(this, CH_SHOTS, SND_PORTO_BOUNCE, VOL_BASE, ATTEN_NORM);
188  // just reflect
191  }
192  else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
193  {
194  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
195  W_Porto_Fail(this, 0);
196  if(this.cnt < 0)
198  }
199  else if(this.cnt == 0)
200  {
201  // in-portal only
203  {
204  sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
205  trace_plane_normal = norm;
206  Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
207  W_Porto_Success(this);
208  }
209  else
210  {
211  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
212  trace_plane_normal = norm;
213  W_Porto_Fail(this, 0);
214  }
215  }
216  else if(this.cnt == 1)
217  {
218  // out-portal only
220  {
221  sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
222  trace_plane_normal = norm;
223  Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
224  W_Porto_Success(this);
225  }
226  else
227  {
228  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
229  trace_plane_normal = norm;
230  W_Porto_Fail(this, 0);
231  }
232  }
233  else if(this.effects & EF_RED)
234  {
235  this.effects += EF_BLUE - EF_RED;
237  {
238  sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
239  trace_plane_normal = norm;
240  Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
241  this.right_vector = this.right_vector - 2 * trace_plane_normal * (this.right_vector * norm);
242  this.angles = vectoangles(this.velocity - 2 * trace_plane_normal * (this.velocity * norm));
243  CSQCProjectile(this, true, PROJECTILE_PORTO_BLUE, true); // change type
244  }
245  else
246  {
247  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
248  trace_plane_normal = norm;
250  W_Porto_Fail(this, 0);
251  }
252  }
253  else
254  {
255  if(this.realowner.portal_in.portal_id == this.portal_id)
256  {
258  {
259  sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
260  trace_plane_normal = norm;
261  Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
262  W_Porto_Success(this);
263  }
264  else
265  {
266  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
268  W_Porto_Fail(this, 0);
269  }
270  }
271  else
272  {
273  sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
275  W_Porto_Fail(this, 0);
276  }
277  }
278 }
279 
280 void W_Porto_Attack(Weapon thiswep, entity actor, .entity weaponentity, float type)
281 {
282  entity gren;
283 
284  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
285  // always shoot from the eye
287  w_shotorg = actor.origin + actor.view_ofs + ((w_shotorg - actor.origin - actor.view_ofs) * v_forward) * v_forward;
288 
289  //Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
290 
291  gren = new(porto);
292  gren.weaponentity_fld = weaponentity;
293  gren.cnt = type;
294  gren.owner = gren.realowner = actor;
295  gren.playerid = actor.playerid;
296  gren.bot_dodge = true;
297  gren.bot_dodgerating = 200;
300  gren.effects = EF_RED;
301  gren.scale = 4;
302  setorigin(gren, w_shotorg);
303  setsize(gren, '0 0 0', '0 0 0');
304 
305  gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime);
306  setthink(gren, W_Porto_Think);
307  settouch(gren, W_Porto_Touch);
308 
309  // TODO: handle as mutator effect
310  if(StatusEffects_active(STATUSEFFECT_Strength, actor))
311  W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0);
312  else
313  W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed), 0);
314 
315  gren.angles = vectoangles(gren.velocity);
316  gren.flags = FL_PROJECTILE;
317  IL_PUSH(g_projectiles, gren);
318  IL_PUSH(g_bot_dodge, gren);
319 
320  gren.portal_id = time;
321  actor.porto_current = gren;
322  gren.playerid = actor.playerid;
323  fixedmakevectors(fixedvectoangles(gren.velocity));
324  gren.right_vector = v_right;
325 
326  gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
327 
328  if(type > 0)
329  CSQCProjectile(gren, true, PROJECTILE_PORTO_BLUE, true);
330  else
331  CSQCProjectile(gren, true, PROJECTILE_PORTO_RED, true);
332 
333  MUTATOR_CALLHOOK(EditProjectile, actor, gren);
334 }
335 
336 METHOD(PortoLaunch, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
337 {
338  PHYS_INPUT_BUTTON_ATCK(actor) = false;
339  PHYS_INPUT_BUTTON_ATCK2(actor) = false;
340  if(!WEP_CVAR(porto, secondary))
341  if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false))
342  PHYS_INPUT_BUTTON_ATCK(actor) = true;
343 }
344 METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
345 {
346  if(WEP_CVAR(porto, secondary))
347  {
348  if(fire & 1)
349  if(!actor.porto_current)
350  if(!actor.porto_forbidden)
351  if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
352  {
353  W_Porto_Attack(thiswep, actor, weaponentity, 0);
354  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
355  }
356 
357  if(fire & 2)
358  if(!actor.porto_current)
359  if(!actor.porto_forbidden)
360  if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(porto, refire)))
361  {
362  W_Porto_Attack(thiswep, actor, weaponentity, 1);
363  weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(porto, animtime), w_ready);
364  }
365  }
366  else
367  {
368  if(actor.(weaponentity).porto_v_angle_held)
369  {
370  if(!(fire & 2))
371  actor.(weaponentity).porto_v_angle_held = 0;
372  }
373  else
374  {
375  if(fire & 2)
376  {
377  actor.(weaponentity).porto_v_angle = actor.v_angle;
378  actor.(weaponentity).porto_v_angle_held = 1;
379  }
380  }
381  if(actor.(weaponentity).porto_v_angle_held)
382  makevectors(actor.(weaponentity).porto_v_angle); // override the previously set angles
383 
384  if(fire & 1)
385  if(!actor.porto_current)
386  if(!actor.porto_forbidden)
387  if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(porto, refire)))
388  {
389  W_Porto_Attack(thiswep, actor, weaponentity, -1);
390  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready);
391  }
392  }
393 }
394 METHOD(PortoLaunch, wr_checkammo1, bool(entity thiswep, entity this, .entity weaponentity))
395 {
396  // always allow infinite ammo
397  return true;
398 }
399 METHOD(PortoLaunch, wr_checkammo2, bool(entity thiswep, entity this, .entity weaponentity))
400 {
401  // always allow infinite ammo
402  return true;
403 }
404 METHOD(PortoLaunch, wr_resetplayer, void(entity thiswep, entity actor))
405 {
406  actor.porto_current = NULL;
407 }
408 #endif
409 #ifdef CSQC
410 METHOD(PortoLaunch, wr_impacteffect, void(entity this, entity actor)) {
411  LOG_WARN("Since when does Porto send DamageInfo?");
412 }
413 #endif
const int PROJECTILE_PORTO_RED
Definition: projectiles.qh:15
#define WEP_CVAR_PRI(wepname, name)
Definition: all.qh:300
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
#define WEPSET(id)
Definition: all.qh:37
#define W_SetupProjVelocity_Basic(ent, pspeed, pspread)
Definition: tracing.qh:48
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity)
#define WEP_CVAR_SEC(wepname, name)
Definition: all.qh:301
#define LOG_WARN(...)
Definition: log.qh:66
#define fixedvectoangles(a)
float speed
Definition: subs.qh:41
vector w_shotorg
Definition: tracing.qh:18
#define PROJECTILE_MAKETRIGGER(e)
Definition: common.qh:29
#define REGISTER_MUTATOR(id, dependence)
Definition: base.qh:263
float trace_dphitq3surfaceflags
vector warpzone_save_view_angles
Definition: client.qh:9
entity viewmodels[MAX_WEAPONSLOTS]
Definition: view.qh:104
float trace_dphitcontents
entity() spawn
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
float MOVETYPE_BOUNCEMISSILE
Definition: progsdefs.qc:257
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
vector view_origin
Definition: main.qh:93
float intermission
Definition: csprogsdefs.qc:148
#define WEP_CVAR(wepname, name)
Definition: all.qh:299
#define STATIC_INIT(func)
during worldspawn
Definition: static.qh:32
vector w_shotdir
Definition: tracing.qh:19
float DPCONTENTS_PLAYERCLIP
entity weaponentity_fld
Definition: weaponsystem.qh:27
float Q3SURFACEFLAG_SLICK
vector view_forward
Definition: main.qh:93
origin
Definition: ent_cs.qc:114
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
void CSQCProjectile(entity e, float clientanimate, int type, float docull)
float FL_ITEM
Definition: progsdefs.qc:239
#define move_out_of_solid(e)
Definition: common.qh:110
float effects
Definition: csprogsdefs.qc:111
IntrusiveList g_bot_dodge
Definition: api.qh:150
entity trace_ent
Definition: csprogsdefs.qc:40
const int PROJECTILE_PORTO_BLUE
Definition: projectiles.qh:16
vector view_angles
Definition: csprogsdefs.qc:150
vector view_up
Definition: main.qh:93
float lifetime
Definition: powerups.qc:23
float cnt
Definition: powerups.qc:24
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
const int CH_WEAPON_A
Definition: sound.qh:7
const float EF_BLUE
Definition: csprogsdefs.qc:301
vector v_up
Definition: csprogsdefs.qc:31
const int MAX_WEAPONSLOTS
Definition: weapon.qh:13
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo,.entity weaponentity)
Definition: throwing.qc:40
#define WEP_CVAR_BOTH(wepname, isprimary, name)
Definition: all.qh:302
IntrusiveList g_items
Definition: items.qh:126
ERASEABLE vector reflect(vector dir, vector norm)
Definition: vector.qh:133
#define NULL
Definition: post.qh:17
void Portal_ClearWithID(entity own, float id)
Definition: portals.qc:615
float DPCONTENTS_SOLID
const float DRAWFLAG_NORMAL
Definition: csprogsdefs.qc:317
const float VOL_BASE
Definition: sound.qh:36
vector trace_endpos
Definition: csprogsdefs.qc:37
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition: tracing.qh:33
float Q3SURFACEFLAG_NOIMPACT
#define IS_DEAD(s)
Definition: utils.qh:26
const float ATTEN_NORM
Definition: sound.qh:30
const int CH_SHOTS
Definition: sound.qh:14
vector(float skel, float bonenum) _skel_get_boneabs_hidden
IntrusiveList g_projectiles
Definition: common.qh:46
float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
Definition: portals.qc:690
const float EF_RED
Definition: csprogsdefs.qc:307
float spectatee_status
Definition: main.qh:166
float DPCONTENTS_BODY
float flags
Definition: csprogsdefs.qc:129
float portal_id
Definition: portals.qh:6
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
Definition: jumppads.qc:31
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
vector v_right
Definition: csprogsdefs.qc:31
entity realowner
Definition: common.qh:25
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
setorigin(ent, v)
#define setthink(e, f)
void Portal_ClearAll_PortalsOnly(entity own)
Definition: portals.qc:580
vector trace_plane_normal
Definition: csprogsdefs.qc:38
vector angles
Definition: csprogsdefs.qc:104
#define MUTATOR_HOOKFUNCTION(...)
Definition: base.qh:310
int autocvar_chase_active
Definition: view.qh:17
#define sound(e, c, s, v, a)
Definition: sound.qh:52
float MOVE_WORLDONLY
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition: weapon.qh:41
float time
Definition: csprogsdefs.qc:16
float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
Definition: portals.qc:670
vector velocity
Definition: csprogsdefs.qc:103
vector right_vector
Definition: portals.qc:110
int m_id
Definition: weapon.qh:42
int dir
Definition: impulse.qc:89
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
IntrusiveList g_drawables
Definition: main.qh:77
#define fixedmakevectors
void set_movetype(entity this, int mt)
#define IS_PLAYER(v)
Definition: utils.qh:9
vector v_forward
Definition: csprogsdefs.qc:31