Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
porto.qc
Go to the documentation of this file.
1#include "porto.qh"
2
4
5#ifdef CSQC
6
8{
9 entity e = new_pure(porto);
10 e.draw = Porto_Draw;
13}
14
15const int polyline_length = 16;
18{
19 if (spectatee_status || intermission == 1 || intermission == 2 || STAT(HEALTH) <= 0 || WEP_CVAR(WEP_PORTO, secondary)) return;
20
21 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
22 {
23 entity wepent = viewmodels[slot];
24
25 if (wepent.activeweapon != WEP_PORTO) continue;
26
27 vector pos = view_origin;
30 pos += v_right * -wepent.movedir.y
31 + v_up * wepent.movedir.z;
32
33 if (wepent.angles_held_status)
34 {
35 makevectors(wepent.angles_held);
36 dir = v_forward;
37 }
38
39 wepent.polyline[0] = pos;
40
41 int portal_number = 0, portal1_idx = 1, portal_max = 2;
42 int n = 1 + 2; // 2 lines == 3 points
43 for (int idx = 0; idx < n && idx < polyline_length - 1; )
44 {
45 traceline(pos, pos + 65536 * dir, true, this);
47 pos = trace_endpos;
48 wepent.polyline[++idx] = pos;
50 {
51 n += 1;
52 continue;
53 }
55 {
56 n = max(2, idx);
57 break;
58 }
59 // check size
60 {
61 vector ang = vectoangles2(trace_plane_normal, dir);
62 ang.x = -ang.x;
64 if (!CheckWireframeBox(this, pos - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward))
65 {
66 n = max(2, idx);
67 break;
68 }
69 }
70 portal_number += 1;
71 if (portal_number >= portal_max) break;
72 if (portal_number == 1) portal1_idx = idx;
73 }
74 for (int idx = 0; idx < n - 1; ++idx)
75 {
76 vector p = wepent.polyline[idx], q = wepent.polyline[idx + 1];
77 if (idx == 0) p -= view_up * 16; // line from player
78 vector rgb = (idx < portal1_idx) ? '1 0 0' : '0 0 1';
79 Draw_CylindricLine(p, q, 4, "", 1, 0, rgb, 0.5, DRAWFLAG_NORMAL, view_origin);
80 }
81 }
82}
83
84#endif
85
86#ifdef SVQC
87
90
91REGISTER_MUTATOR(porto_ticker, true);
92MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
93 FOREACH_CLIENT(IS_PLAYER(it), it.porto_forbidden = max(0, it.porto_forbidden - 1));
94}
95
97{
98 if(this.realowner == NULL)
99 {
100 objerror(this, "Cannot succeed successfully: no owner\n");
101 return;
102 }
103
104 this.realowner.porto_current = NULL;
105 delete(this);
106}
107
108void W_Porto_Fail(entity this, float failhard)
109{
110 if(this.realowner == NULL)
111 {
112 objerror(this, "Cannot fail successfully: no owner\n");
113 return;
114 }
115
116 // no portals here!
117 if(this.cnt < 0)
118 {
120 }
121
122 this.realowner.porto_current = NULL;
123
124 if(this.cnt < 0 && !failhard && this.realowner.playerid == this.playerid && !IS_DEAD(this.realowner) && !(STAT(WEAPONS, this.realowner) & WEPSET(PORTO)))
125 {
126 // FIXME: item properties should be obtained from the registry
127 setsize(this, ITEM_D_MINS, ITEM_D_MAXS);
128 setorigin(this, this.origin + trace_plane_normal);
129 if(nudgeoutofsolid_OrFallback(this))
130 {
131 this.flags = FL_ITEM;
132 IL_PUSH(g_items, this);
133 this.velocity = trigger_push_calculatevelocity(this.origin, this.realowner, 128, this);
134 tracetoss(this, this);
135 if(vdist(trace_endpos - this.realowner.origin, <, 128))
136 {
137 .entity weaponentity = this.weaponentity_fld;
138 W_ThrowNewWeapon(this.realowner, WEP_PORTO.m_id, 0, this.origin, this.velocity, weaponentity);
139 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_FAILED);
140 }
141 }
142 }
143 delete(this);
144}
145
147{
148 if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
149 {
150 W_Porto_Fail(p.porto_current, 1);
151 }
152}
153
155{
156 trace_plane_normal = '0 0 0';
157 if(this.realowner.playerid != this.playerid)
158 delete(this);
159 else
160 W_Porto_Fail(this, 0);
161}
162
164{
165 vector norm;
166
167 // do not use PROJECTILE_TOUCH here
168 // FIXME but DO handle warpzones!
169
170 if(toucher.classname == "portal")
171 return; // handled by the portal
172
173 norm = trace_plane_normal;
174 if(trace_ent.iscreature)
175 {
176 // TODO: why not use entity size?
177 traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_CONST.z, MOVE_WORLDONLY, this);
178 if(trace_fraction >= 1)
179 return;
181 return;
183 return;
184 }
185
186 if(this.realowner.playerid != this.playerid)
187 {
188 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
189 delete(this);
190 }
192 {
193 spamsound(this, CH_SHOTS, SND_PORTO_BOUNCE, VOL_BASE, ATTEN_NORM);
194 // just reflect
197 }
199 {
200 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
201 W_Porto_Fail(this, 0);
202 if(this.cnt < 0)
204 }
205 else if(this.cnt == 0)
206 {
207 // in-portal only
209 {
210 sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
211 trace_plane_normal = norm;
212 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
213 W_Porto_Success(this);
214 }
215 else
216 {
217 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
218 trace_plane_normal = norm;
219 W_Porto_Fail(this, 0);
220 }
221 }
222 else if(this.cnt == 1)
223 {
224 // out-portal only
226 {
227 sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
228 trace_plane_normal = norm;
229 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
230 W_Porto_Success(this);
231 }
232 else
233 {
234 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
235 trace_plane_normal = norm;
236 W_Porto_Fail(this, 0);
237 }
238 }
239 else if(this.effects & EF_RED)
240 {
241 this.effects += EF_BLUE - EF_RED;
243 {
244 sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
245 trace_plane_normal = norm;
246 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN);
247 this.right_vector = this.right_vector - 2 * trace_plane_normal * (this.right_vector * norm);
248 this.angles = vectoangles(this.velocity - 2 * trace_plane_normal * (this.velocity * norm));
249 CSQCProjectile(this, true, PROJECTILE_PORTO_BLUE, true); // change type
250 }
251 else
252 {
253 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
254 trace_plane_normal = norm;
256 W_Porto_Fail(this, 0);
257 }
258 }
259 else
260 {
261 if(this.realowner.portal_in.portal_id == this.portal_id)
262 {
264 {
265 sound(this, CH_SHOTS, SND_PORTO_CREATE, VOL_BASE, ATTEN_NORM);
266 trace_plane_normal = norm;
267 Send_Notification(NOTIF_ONE, this.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT);
268 W_Porto_Success(this);
269 }
270 else
271 {
272 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
274 W_Porto_Fail(this, 0);
275 }
276 }
277 else
278 {
279 sound(this, CH_SHOTS, SND_PORTO_UNSUPPORTED, VOL_BASE, ATTEN_NORM);
281 W_Porto_Fail(this, 0);
282 }
283 }
284}
285
286void W_Porto_Attack(Weapon thiswep, entity actor, .entity weaponentity, float type)
287{
288 entity gren;
289
290 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
291 // always shoot from the eye
293 w_shotorg = actor.origin + actor.view_ofs + ((w_shotorg - actor.origin - actor.view_ofs) * v_forward) * v_forward;
294
295 //Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
296
297 gren = new(porto);
298 gren.weaponentity_fld = weaponentity;
299 gren.cnt = type;
300 gren.owner = gren.realowner = actor;
301 gren.playerid = actor.playerid;
302 gren.bot_dodge = true;
303 gren.bot_dodgerating = 200;
306 gren.effects = EF_RED;
307 gren.scale = 4;
308 setorigin(gren, w_shotorg);
309 setsize(gren, '0 0 0', '0 0 0');
310
311 gren.nextthink = time + WEP_CVAR_BOTH(WEP_PORTO, (type <= 0), lifetime);
312 setthink(gren, W_Porto_Think);
313 settouch(gren, W_Porto_Touch);
314
315 // TODO: handle as mutator effect
316 if(StatusEffects_active(STATUSEFFECT_Strength, actor))
318 else
319 W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(WEP_PORTO, (type <= 0), speed), 0);
320
321 gren.angles = vectoangles(gren.velocity);
322 gren.flags = FL_PROJECTILE;
323 IL_PUSH(g_projectiles, gren);
324 IL_PUSH(g_bot_dodge, gren);
325
326 gren.portal_id = time;
327 actor.porto_current = gren;
328 gren.playerid = actor.playerid;
329 fixedmakevectors(fixedvectoangles(gren.velocity));
330 gren.right_vector = v_right;
331
332 gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
333
334 if(type > 0)
335 CSQCProjectile(gren, true, PROJECTILE_PORTO_BLUE, true);
336 else
337 CSQCProjectile(gren, true, PROJECTILE_PORTO_RED, true);
338
339 MUTATOR_CALLHOOK(EditProjectile, actor, gren);
340}
341
342METHOD(PortoLaunch, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
343{
344 PHYS_INPUT_BUTTON_ATCK(actor) = false;
345 PHYS_INPUT_BUTTON_ATCK2(actor) = false;
346 if(!WEP_CVAR(WEP_PORTO, secondary))
347 if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(WEP_PORTO, speed), 0, WEP_CVAR_PRI(WEP_PORTO, lifetime), false, true))
348 PHYS_INPUT_BUTTON_ATCK(actor) = true;
349}
350
351METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
352{
353 if(WEP_CVAR(WEP_PORTO, secondary))
354 {
355 if(fire & 1)
356 if(!actor.porto_current)
357 if(!actor.porto_forbidden)
358 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_PORTO, refire)))
359 {
360 W_Porto_Attack(thiswep, actor, weaponentity, 0);
361 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_PORTO, animtime), w_ready);
362 }
363
364 if(fire & 2)
365 if(!actor.porto_current)
366 if(!actor.porto_forbidden)
367 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(WEP_PORTO, refire)))
368 {
369 W_Porto_Attack(thiswep, actor, weaponentity, 1);
370 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(WEP_PORTO, animtime), w_ready);
371 }
372 }
373 else
374 {
375 if(actor.(weaponentity).porto_v_angle_held)
376 {
377 if(!(fire & 2))
378 actor.(weaponentity).porto_v_angle_held = 0;
379 }
380 else
381 {
382 if(fire & 2)
383 {
384 actor.(weaponentity).porto_v_angle = actor.v_angle;
385 actor.(weaponentity).porto_v_angle_held = 1;
386 }
387 }
388 if(actor.(weaponentity).porto_v_angle_held)
389 makevectors(actor.(weaponentity).porto_v_angle); // override the previously set angles
390
391 if(fire & 1)
392 if(!actor.porto_current)
393 if(!actor.porto_forbidden)
394 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(WEP_PORTO, refire)))
395 {
396 W_Porto_Attack(thiswep, actor, weaponentity, -1);
397 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(WEP_PORTO, animtime), w_ready);
398 }
399 }
400}
401
402METHOD(PortoLaunch, wr_checkammo1, bool(entity thiswep, entity this, .entity weaponentity))
403{
404 // always allow infinite ammo
405 return true;
406}
407
408METHOD(PortoLaunch, wr_checkammo2, bool(entity thiswep, entity this, .entity weaponentity))
409{
410 // always allow infinite ammo
411 return true;
412}
413
414METHOD(PortoLaunch, wr_resetplayer, void(entity thiswep, entity actor))
415{
416 actor.porto_current = NULL;
417}
418
419#endif
420
421#ifdef CSQC
422
423METHOD(PortoLaunch, wr_impacteffect, void(entity this, entity actor)) {
424 LOG_WARN("Since when does Porto send DamageInfo?");
425}
426
427#endif
428#ifdef MENUQC
429
430METHOD(PortoLaunch, describe, string(PortoLaunch this))
431{
432 TC(PortoLaunch, this);
434 PAR(_("The %s is a unique gun that creates one-way portals between flat surfaces on the map, that immediately teleport players and projectiles."), COLORED_NAME(this));
435 PAR(_("The secondary fire is used to shoot the out-portal, while the primary fire shoots the in-portal."));
436 PAR(_("It doesn't require ammo, but it is destroyed after some time."));
437 PAR(_("The portals will close either after the player who shot them dies or after some time period."));
438 PAR(_("The %s isn't often placed on maps, but if used well it can make for some interesting gameplay."), COLORED_NAME(this));
439 PAR(W_Guide_Keybinds(this));
440 return PAGE_TEXT;
441}
442
443#endif
void fixedmakevectors(vector a)
#define fixedvectoangles
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity, bool shot_accurate)
IntrusiveList g_bot_dodge
Definition api.qh:150
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
#define REGISTER_MUTATOR(...)
Definition base.qh:295
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition weapon.qh:44
int m_id
Definition weapon.qh:45
float lifetime
Definition powerups.qc:23
float cnt
Definition powerups.qc:24
IntrusiveList g_drawables
Definition main.qh:91
vector view_origin
Definition main.qh:109
int spectatee_status
the -1 disables HUD panels before CSQC receives necessary data
Definition main.qh:197
vector view_up
Definition main.qh:109
vector view_forward
Definition main.qh:109
#define COLORED_NAME(this)
Definition color.qh:195
const vector ITEM_D_MAXS
Definition item.qh:83
const vector ITEM_D_MINS
Definition item.qh:82
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:150
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:152
float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
Definition util.qc:582
void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float theAlpha, float drawflag, vector vieworg)
Definition draw.qh:11
const vector PL_MIN_CONST
Definition constants.qh:56
const int FL_PROJECTILE
Definition constants.qh:85
const int FL_ITEM
Definition constants.qh:77
vector v_up
float flags
const float DRAWFLAG_NORMAL
float trace_dphitcontents
entity trace_ent
float DPCONTENTS_SOLID
float Q3SURFACEFLAG_SLICK
const float EF_RED
vector velocity
float DPCONTENTS_BODY
vector view_angles
float effects
float DPCONTENTS_PLAYERCLIP
float time
vector v_right
vector trace_endpos
float MOVE_WORLDONLY
float trace_dphitq3surfaceflags
float intermission
const float EF_BLUE
vector v_forward
vector origin
float trace_fraction
float Q3SURFACEFLAG_NOIMPACT
vector trace_plane_normal
void CSQCProjectile(entity e, float clientanimate, int type, float docull)
float speed
Definition dynlight.qc:9
ent angles
Definition ent_cs.qc:121
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define TC(T, sym)
Definition _all.inc:82
#define STAT(...)
Definition stats.qh:82
vector warpzone_save_view_angles
Definition client.qh:9
#define LOG_WARN(...)
Definition log.qh:61
vector vectoangles(vector v)
float max(float f,...)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_BOUNCEMISSILE
Definition movetypes.qh:140
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define METHOD(cname, name, prototype)
Definition oo.qh:269
float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
Definition portals.qc:691
void Portal_ClearWithID(entity own, float id)
Definition portals.qc:616
void Portal_ClearAll_PortalsOnly(entity own)
Definition portals.qc:581
float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
Definition portals.qc:671
float portal_id
Definition portals.qh:6
vector polyline[polyline_length]
Definition porto.qc:16
void W_Porto_Success(entity this)
Definition porto.qc:96
void W_Porto_Fail(entity this, float failhard)
Definition porto.qc:108
void Porto_Draw(entity this)
Definition porto.qc:17
const int polyline_length
Definition porto.qc:15
void W_Porto_Think(entity this)
Definition porto.qc:154
void W_Porto_Remove(entity p)
Definition porto.qc:146
void W_Porto_Touch(entity this, entity toucher)
Definition porto.qc:163
void W_Porto_Attack(Weapon thiswep, entity actor,.entity weaponentity, float type)
Definition porto.qc:286
float porto_v_angle_held
Definition porto.qh:63
vector porto_v_angle
Definition porto.qh:62
vector right_vector
Definition porto.qh:64
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
#define objerror
Definition pre.qh:8
const int PROJECTILE_PORTO_BLUE
const int PROJECTILE_PORTO_RED
#define setthink(e, f)
vector
Definition self.qh:92
entity entity toucher
Definition self.qh:72
vector vector ang
Definition self.qh:92
#define settouch(e, f)
Definition self.qh:73
int dir
Definition impulse.qc:89
IntrusiveList g_items
Definition items.qh:125
IntrusiveList g_projectiles
Definition common.qh:58
#define PROJECTILE_MAKETRIGGER(e)
Definition common.qh:34
const float VOL_BASE
Definition sound.qh:36
const int CH_SHOTS
Definition sound.qh:14
const int CH_WEAPON_A
Definition sound.qh:7
const float ATTEN_NORM
Definition sound.qh:30
#define sound(e, c, s, v, a)
Definition sound.qh:52
float spamsound(entity e, int chan, Sound samp, float vol, float _atten)
use this one if you might be causing spam (e.g.
Definition all.qc:124
#define STATIC_INIT(func)
during worldspawn
Definition static.qh:32
bool StatusEffects_active(StatusEffect this, entity actor)
float autocvar_g_balance_powerup_strength_force
Definition strength.qh:19
#define PAGE_TEXT
Definition string.qh:643
#define PAR(...)
Adds an individually translatable paragraph to PAGE_TEXT without having to deal with strcat and sprin...
Definition string.qh:649
#define PAGE_TEXT_INIT()
Definition string.qh:642
vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity)
Definition jumppads.qc:32
entity realowner
float W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo,.entity weaponentity)
Definition throwing.qc:22
vector w_shotdir
Definition tracing.qh:20
vector w_shotorg
Definition tracing.qh:19
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition tracing.qh:34
#define W_SetupProjVelocity_Basic(ent, pspeed, pspread)
Definition tracing.qh:49
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector reflect(vector dir, vector norm)
Definition vector.qh:133
int autocvar_chase_active
Definition view.qh:17
entity viewmodels[MAX_WEAPONSLOTS]
Definition view.qh:108
string W_Guide_Keybinds(Weapon wep)
Definition all.qc:836
#define WEP_CVAR_PRI(wep, name)
Definition all.qh:322
#define WEP_CVAR_BOTH(wep, isprimary, name)
Definition all.qh:324
#define WEPSET(id)
Definition all.qh:45
#define WEP_CVAR(wep, name)
Definition all.qh:321
#define WEP_CVAR_SEC(wep, name)
Definition all.qh:323
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
entity weaponentity_fld