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