]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/portals.qc
if someone teleports, recharge the portal's health
[divverent/nexuiz.git] / data / qcsrc / server / portals.qc
1 .vector portal_transform;
2 .vector portal_safe_origin;
3 .float portal_wants_to_vanish;
4 .float portal_activatetime;
5
6 .entity portal_in, portal_out;
7
8 vector Portal_Transform_Apply(vector transform, vector v)
9 {
10         fixedmakevectors(transform);
11         return v_forward * v_x
12              + v_right   * (-v_y)
13                  + v_up      * v_z;
14 }
15
16 vector Portal_Transform_Multiply(vector t1, vector t2)
17 {
18         vector m_forward, m_up;
19         fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
20         m_forward = Portal_Transform_Apply(t1, m_forward);
21         m_up = Portal_Transform_Apply(t1, m_up);
22         return vectoangles2(m_forward, m_up);
23 }
24
25 vector Portal_Transform_Invert(vector transform)
26 {
27         vector i_forward, i_up;
28         fixedmakevectors(transform);
29         // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
30         // but these are orthogonal unit vectors!
31         // so to invert, we can simply vectoangles the TRANSPOSED matrix
32         // TODO is this always -transform?
33         i_forward_x = v_forward_x;
34         i_forward_y = -v_right_x;
35         i_forward_z = v_up_x;
36         i_up_x = v_forward_z;
37         i_up_y = -v_right_z;
38         i_up_z = v_up_z;
39 #ifdef DEBUG
40         vector v;
41         v = vectoangles2(i_forward, i_up);
42         print("Transform: ", vtos(transform), "\n");
43         print("Inverted: ", vtos(v), "\n");
44         print("Verify: ", vtos(Portal_Transform_Multiply(v, transform)), "\n");
45         fixedmakevectors(Portal_Transform_Multiply(v, transform));
46         print("Verify: ", vtos(v_forward), "\n");
47         print("Verify: ", vtos(v_right), "\n");
48         print("Verify: ", vtos(v_up), "\n");
49 #endif
50         return vectoangles2(i_forward, i_up);
51 }
52
53 vector Portal_Transform_TurnDirection(vector transform)
54 {
55         // turn 180 degrees around v_up
56         // changes in-direction to out-direction
57         fixedmakevectors(transform);
58         return vectoangles2(-1 * v_forward, 1 * v_up);
59 }
60
61 vector Portal_Transform_Divide(vector to_transform, vector from_transform)
62 {
63         return Portal_Transform_Multiply(to_transform, Portal_Transform_Invert(from_transform));
64 }
65
66 float PlayerEdgeDistance(entity p, vector v)
67 {
68         vector vbest;
69
70         if(v_x < 0) vbest_x = p.mins_x; else vbest_x = p.maxs_x;
71         if(v_y < 0) vbest_y = p.mins_y; else vbest_y = p.maxs_y;
72         if(v_z < 0) vbest_z = p.mins_z; else vbest_z = p.maxs_z;
73
74         return vbest * v;
75 }
76
77 void Portal_TeleportPlayer(entity teleporter, entity player)
78 {
79         vector from, to, safe, step, transform, ang, newvel;
80         float planeshift;
81         from = teleporter.origin;
82         to = teleporter.enemy.origin;
83         transform = teleporter.portal_transform;
84
85         to = to + Portal_Transform_Apply(teleporter.portal_transform, player.origin - from);
86         // this now is INSIDE the plane... can't use that
87         
88         // shift it out
89         fixedmakevectors(teleporter.enemy.angles);
90
91         // first shift it ON the plane if needed
92         planeshift = ((teleporter.enemy.origin - to) * v_forward) + PlayerEdgeDistance(player, v_forward);
93         if(planeshift > 0)
94                 to += v_forward * planeshift;
95         else
96                 print("no planeshift?\n");
97
98         safe = teleporter.enemy.portal_safe_origin; // a valid player origin
99         step = to + ((safe - to) * v_forward) * v_forward;
100         tracebox(safe, PL_MIN, PL_MAX, step, MOVE_NOMONSTERS, player);
101         if(trace_startsolid)
102         {
103                 bprint("'safe' teleport location is not safe!\n");
104                 // FAIL TODO why does this happen?
105                 return;
106         }
107         safe = trace_endpos;
108         tracebox(safe, PL_MIN, PL_MAX, to, MOVE_NOMONSTERS, player);
109         if(trace_startsolid)
110                 error("trace_endpos in solid!");
111         to = trace_endpos;
112
113         // ang_x stuff works around weird quake angles
114         if(player.classname == "player")
115         {
116                 ang = player.v_angle;
117                 ang_x = -ang_x;
118                 ang = Portal_Transform_Multiply(transform, ang);
119                 ang_z = player.angles_z;
120         }
121         else
122         {
123                 ang = player.angles;
124                 ang_x = -ang_x;
125                 ang = Portal_Transform_Multiply(transform, player.angles);
126         }
127         ang_x = -ang_x;
128
129         newvel = Portal_Transform_Apply(transform, player.velocity);
130
131         if(player.flagcarried)
132                 DropFlag(player.flagcarried);
133         TeleportPlayer(teleporter, player, to, ang, newvel, teleporter.enemy.absmin, teleporter.enemy.absmax);
134
135         // reset fade counter
136         teleporter.portal_wants_to_vanish = 0;
137         teleporter.fade_time = time + 15;
138         teleporter.enemy.health = 300;
139 }
140
141 float Portal_FindSafeOrigin(entity portal)
142 {
143         vector o;
144         o = portal.origin;
145         portal.mins = PL_MIN - '8 8 8';
146         portal.maxs = PL_MAX + '8 8 8';
147         fixedmakevectors(portal.angles);
148         portal.origin += 16 * v_forward;
149         if(!move_out_of_solid(portal))
150         {
151 #ifdef DEBUG
152                 print("NO SAFE ORIGIN\n");
153 #endif
154                 return 0;
155         }
156         portal.portal_safe_origin = portal.origin;
157         setorigin(portal, o);
158         return 1;
159 }
160
161 void Portal_Touch()
162 {
163         if(other.classname == "porto")
164                 return;
165         if(time < self.portal_activatetime)
166                 if(other == self.owner)
167                 {
168                         self.portal_activatetime = time + 0.1;
169                         return;
170                 }
171         fixedmakevectors(self.angles);
172         if((other.origin - self.origin) * v_forward < 0)
173                 return;
174         if(other.mins_x < PL_MIN_x || other.mins_y < PL_MIN_y || other.mins_z < PL_MIN_z
175         || other.maxs_x > PL_MAX_x || other.maxs_y > PL_MAX_y || other.maxs_z > PL_MAX_z)
176         {
177                 // can't teleport this
178                 return;
179         }
180         Portal_TeleportPlayer(self, other);
181 }
182
183 void Portal_MakeBrokenPortal(entity portal)
184 {
185         portal.solid = SOLID_NOT;
186         portal.touch = SUB_Null;
187         portal.effects = 0;
188         //portal.colormod = '1 1 1';
189         portal.nextthink = 0;
190         portal.takedamage = DAMAGE_NO;
191 }
192
193 void Portal_MakeWaitingPortal(entity portal)
194 {
195         portal.solid = SOLID_NOT;
196         portal.touch = SUB_Null;
197         portal.effects = EF_ADDITIVE;
198         portal.colormod = '1 1 1';
199         portal.nextthink = 0;
200         portal.takedamage = DAMAGE_YES;
201 }
202
203 void Portal_MakeInPortal(entity portal)
204 {
205         portal.solid = SOLID_TRIGGER;
206         portal.touch = Portal_Touch;
207         portal.effects = EF_RED;
208         portal.colormod = '1 0 0';
209         portal.nextthink = time;
210         portal.takedamage = DAMAGE_NO;
211 }
212
213 void Portal_MakeOutPortal(entity portal)
214 {
215         portal.solid = SOLID_NOT;
216         portal.touch = SUB_Null;
217         portal.effects = EF_STARDUST | EF_BLUE;
218         portal.colormod = '0 0 1';
219         portal.nextthink = 0;
220         portal.takedamage = DAMAGE_YES;
221 }
222
223 void Portal_Disconnect(entity teleporter, entity destination)
224 {
225         teleporter.enemy = world;
226         destination.enemy = world;
227         Portal_MakeBrokenPortal(teleporter);
228         Portal_MakeBrokenPortal(destination);
229 }
230
231 void Portal_Connect(entity teleporter, entity destination)
232 {
233         teleporter.portal_transform = Portal_Transform_Divide(Portal_Transform_TurnDirection(destination.angles), teleporter.angles);
234
235 #ifdef DEBUG
236         {
237                 // let's verify the transform
238                 vector in_f, in_r, in_u;
239                 vector out_f, out_r, out_u;
240                 fixedmakevectors(teleporter.angles);
241                 in_f = v_forward;
242                 in_r = v_right;
243                 in_u = v_up;
244                 print("teleporter: ", vtos(in_f), " ", vtos(in_r), " ", vtos(in_u), "\n");
245                 fixedmakevectors(destination.angles);
246                 out_f = v_forward;
247                 out_r = v_right;
248                 out_u = v_up;
249                 print("dest: ", vtos(out_f), " ", vtos(out_r), " ", vtos(out_u), "\n");
250                 // INTENDED TRANSFORM:
251                 //   in_f -> -out_f
252                 //   in_r -> -out_r
253                 //   in_u -> +out_u
254                 print("FORWARD: ", vtos(in_f), " -> ", vtos(Portal_Transform_Apply(teleporter.portal_transform, in_f)), ", should be", vtos(-1 * out_f), "\n");
255                 print("RIGHT: ", vtos(in_r), " -> ", vtos(Portal_Transform_Apply(teleporter.portal_transform, in_r)), ", should be", vtos(-1 * out_r), "\n");
256                 print("UP: ", vtos(in_u), " -> ", vtos(Portal_Transform_Apply(teleporter.portal_transform, in_u)), ", should be", vtos(out_u), "\n");
257
258                 te_lightning3(world, teleporter.origin, teleporter.origin + in_r * 1000);
259                 te_lightning3(world, destination.origin, destination.origin + out_r * 1000);
260         }
261 #endif
262
263         teleporter.enemy = destination;
264         destination.enemy = teleporter;
265         Portal_MakeInPortal(teleporter);
266         Portal_MakeOutPortal(destination);
267         teleporter.fade_time = time + 15;
268         destination.fade_time = time + 15;
269         teleporter.portal_wants_to_vanish = 0;
270         destination.portal_wants_to_vanish = 0;
271 }
272
273 void Portal_Remove(entity portal, float killed)
274 {
275         entity e;
276         e = portal.enemy;
277
278         if(e)
279         {
280                 Portal_Disconnect(portal, e);
281                 Portal_Remove(e, killed);
282         }
283
284         if(portal == portal.owner.portal_in)
285                 portal.owner.portal_in = world;
286         if(portal == portal.owner.portal_out)
287                 portal.owner.portal_out = world;
288         portal.owner = world;
289
290         // makes the portal vanish
291         if(killed)
292         {
293                 fixedmakevectors(portal.angles);
294                 pointparticles(particleeffectnum("rocket_explode"), portal.origin + v_forward * 16, v_forward * 1024, 4);
295                 remove(portal);
296         }
297         else
298         {
299                 Portal_MakeBrokenPortal(portal);
300                 SUB_SetFade(portal, time, 0.5);
301         }
302 }
303
304 void Portal_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
305 {
306         if(deathtype == DEATH_TELEFRAG)
307                 return;
308         self.health -= damage;
309         if(self.health < 0)
310         {
311                 Portal_Remove(self, 1);
312         }
313 }
314
315 void Portal_Think()
316 {
317         entity e, o;
318
319         if(self.solid != SOLID_TRIGGER)
320                 error("Portal_Think called for a portal that should not be thinking");
321
322         o = self.owner;
323         self.solid = SOLID_BBOX;
324         self.owner = world;
325         FOR_EACH_PLAYER(e)
326         {
327                 if(time < self.portal_activatetime)
328                         if(e == o)
329                                 continue;
330                 // if e would hit the portal in a frame...
331                 // already teleport him
332                 tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 2 * frametime, MOVE_NORMAL, e);
333                 if(trace_ent == self)
334                         Portal_TeleportPlayer(self, e);
335         }
336         self.solid = SOLID_TRIGGER;
337         self.owner = o;
338
339         self.nextthink = time;
340
341         if(time > self.fade_time)
342                 Portal_Remove(self, 0);
343 }
344
345
346 // cleanup:
347 //   when creating in-portal:
348 //     disconnect
349 //     clear existing out-portal
350 //     make existing in-portal an out-portal and connect
351 //     set as in-portal
352 //   when creating out-portal:
353 //     disconnect
354 //     clear existing out-portal
355 //     set as out-portal
356 //   when player dies:
357 //     disconnect portals
358 //     clear both portals
359 //   after timeout of in-portal:
360 //     disconnect portals
361 //     clear both portals
362 //   TODO: ensure only one portal shot at once
363 float Portal_SetInPortal(entity own, entity portal)
364 {
365         if(own.portal_out)
366         {
367                 if(own.portal_in)
368                         Portal_Disconnect(own.portal_in, own.portal_out);
369                 Portal_Remove(own.portal_out, 0);
370         }
371         if(own.portal_in)
372                 own.portal_out = own.portal_in;
373         own.portal_in = portal;
374         if(own.portal_out)
375                 Portal_Connect(own.portal_in, own.portal_out);
376         return 2;
377 }
378 float Portal_SetOutPortal(entity own, entity portal)
379 {
380         if(!own.portal_in)
381                 return 0;
382         if(own.portal_out)
383         {
384                 Portal_Disconnect(own.portal_in, own.portal_out);
385                 Portal_Remove(own.portal_out, 0);
386         }
387         own.portal_out = portal;
388         Portal_Connect(own.portal_in, own.portal_out);
389         return 1;
390 }
391 void Portal_ClearAll(entity own)
392 {
393         if(own.portal_in)
394                 Portal_Remove(own.portal_in, 0);
395         if(own.portal_out)
396                 Portal_Remove(own.portal_out, 0);
397 }
398 float Portal_VerifyPortal(entity own, vector org, vector ang)
399 {
400         fixedmakevectors(ang);
401         if(!CheckWireframeBox(own, org - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward))
402                 return 0;
403         return 1;
404 }
405
406 entity Portal_Spawn(entity own, vector org, vector ang)
407 {
408         entity portal;
409         portal = spawn();
410         portal.classname = "portal";
411         portal.owner = own;
412         portal.origin = org;
413         portal.angles = ang;
414         portal.think = Portal_Think;
415         portal.nextthink = 0;
416         portal.fade_time = time + 15;
417         portal.portal_activatetime = time + 0.1;
418         portal.event_damage = Portal_Damage;
419         portal.health = 300;
420         setmodel(portal, "models/portal.md3");
421
422         if(!Portal_FindSafeOrigin(portal))
423         {
424                 remove(portal);
425                 return world;
426         }
427
428         setsize(portal, '-48 -48 -48', '48 48 48');
429         Portal_MakeWaitingPortal(portal);
430
431         return portal;
432 }
433
434 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
435 {
436         entity portal;
437         vector ang;
438         vector org;
439
440 #ifdef DEBUG
441         {
442                 vector a, b;
443                 a = randomvec();
444                 a = '0 0 -1';
445                 a = normalize(a);
446                 b = randomvec();
447                 b = '1 0 0';
448                 b = normalize(b - (b * a) * a);
449                 print("f/u = ", vtos(a), " ", vtos(b), "\n");
450                 a = vectoangles2(a, b);
451                 print("ang = ", vtos(a), "\n");
452                 fixedmakevectors(a);
453                 print("f/u = ", vtos(v_forward), " ", vtos(v_up), "\n");
454         }
455 #endif
456
457         if(trace_ent.movetype == MOVETYPE_WALK)
458         {
459                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
460                 trace_plane_normal = '0 0 1';
461                 dir = -1 * dir; // create telefrag portals the other way round
462         }
463
464         org = trace_endpos;
465         ang = vectoangles2(trace_plane_normal, dir);
466         fixedmakevectors(ang);
467
468         if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || !Portal_VerifyPortal(own, org, ang))
469         {
470                 // cannot create a portal here
471                 // clear all to make sure
472                 Portal_ClearAll(own);
473                 return 0;
474         }
475
476         portal = Portal_Spawn(own, org, ang);
477         if(!portal)
478         {
479                 Portal_ClearAll(own);
480                 return 0;
481         }
482
483         Portal_SetInPortal(own, portal);
484
485         return 1;
486 }
487
488 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
489 {
490         entity portal;
491         vector ang;
492         vector org;
493
494         if(trace_ent.movetype == MOVETYPE_WALK)
495         {
496                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
497                 trace_plane_normal = '0 0 1';
498                 dir = -1 * dir; // create telefrag portals the other way round
499         }
500
501         org = trace_endpos;
502         ang = vectoangles2(trace_plane_normal, dir);
503         fixedmakevectors(ang);
504
505         if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || !Portal_VerifyPortal(own, org, ang))
506         {
507                 // cannot create a portal here
508                 // clear all to make sure
509                 Portal_ClearAll(own);
510                 return 0;
511         }
512
513         portal = Portal_Spawn(own, org, ang);
514         if(!portal)
515         {
516                 Portal_ClearAll(own);
517                 return 0;
518         }
519
520         Portal_SetOutPortal(own, portal);
521
522         return 1;
523 }