]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/portals.qc
fix portal behaviour AGAIN... :P
[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 fixedvectoangles(vector v)
9 {
10         vector a;
11         a = vectoangles(v);
12         a_x = -a_x;
13         return a;
14 }
15
16 vector fixedvectoangles2(vector v, vector w)
17 {
18         vector a;
19         a = vectoangles2(v, w);
20         a_x = -a_x;
21         return a;
22 }
23
24 void fixedmakevectors(vector a)
25 {
26         //a_x = -a_x;
27         makevectors(a);
28 }
29
30 vector Portal_Transform_Apply(vector transform, vector v)
31 {
32         fixedmakevectors(transform);
33         return v_forward * v_x
34              + v_right   * (-v_y)
35                  + v_up      * v_z;
36 }
37
38 vector Portal_Transform_Multiply(vector t1, vector t2)
39 {
40         vector m_forward, m_up;
41         fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
42         m_forward = Portal_Transform_Apply(t1, m_forward);
43         m_up = Portal_Transform_Apply(t1, m_up);
44         return fixedvectoangles2(m_forward, m_up);
45 }
46
47 vector Portal_Transform_Invert(vector transform)
48 {
49         vector i_forward, i_up;
50         fixedmakevectors(transform);
51         // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
52         // but these are orthogonal unit vectors!
53         // so to invert, we can simply vectoangles the TRANSPOSED matrix
54         // TODO is this always -transform?
55         i_forward_x = v_forward_x;
56         i_forward_y = -v_right_x;
57         i_forward_z = v_up_x;
58         i_up_x = v_forward_z;
59         i_up_y = -v_right_z;
60         i_up_z = v_up_z;
61 #ifdef DEBUG
62         vector v;
63         v = fixedvectoangles2(i_forward, i_up);
64         print("Transform: ", vtos(transform), "\n");
65         print("Inverted: ", vtos(v), "\n");
66         print("Verify: ", vtos(Portal_Transform_Multiply(v, transform)), "\n");
67         fixedmakevectors(Portal_Transform_Multiply(v, transform));
68         print("Verify: ", vtos(v_forward), "\n");
69         print("Verify: ", vtos(v_right), "\n");
70         print("Verify: ", vtos(v_up), "\n");
71 #endif
72         return fixedvectoangles2(i_forward, i_up);
73 }
74
75 vector Portal_Transform_TurnDirection(vector transform)
76 {
77         vector t_angles;
78         t_angles_x = -transform_x;
79         t_angles_y = mod(transform_y + 180, 360);
80         t_angles_z = -transform_z;
81         return t_angles;
82 }
83
84 vector Portal_Transform_Divide(vector to_transform, vector from_transform)
85 {
86         return Portal_Transform_Multiply(to_transform, Portal_Transform_Invert(from_transform));
87 }
88
89 void Portal_TeleportPlayer(entity teleporter, entity player)
90 {
91         vector from, to, safe, step, transform, ang;
92         from = teleporter.origin;
93         to = teleporter.enemy.origin;
94         transform = teleporter.portal_transform;
95
96         to = to + Portal_Transform_Apply(teleporter.portal_transform, player.origin - from);
97         // this now is INSIDE the plane... can't use that
98
99         // shift it out
100         fixedmakevectors(teleporter.enemy.mangle);
101         safe = teleporter.enemy.portal_safe_origin; // a valid player origin
102         step = to + ((safe - to) * v_forward) * v_forward;
103         tracebox(safe, PL_MIN, PL_MAX, step, MOVE_NOMONSTERS, player);
104         if(trace_startsolid)
105         {
106                 bprint("'safe' teleport location is not safe!\n");
107                 // FAIL TODO why does this happen?
108                 return;
109         }
110         safe = trace_endpos;
111         tracebox(safe, PL_MIN, PL_MAX, to, MOVE_NOMONSTERS, player);
112         if(trace_startsolid)
113                 error("trace_endpos in solid!");
114         to = trace_endpos;
115
116         if(player.classname == "player")
117         {
118                 ang = Portal_Transform_Multiply(transform, player.v_angle);
119                 ang_z = player.angles_z;
120         }
121         else
122         {
123                 ang = Portal_Transform_Multiply(transform, player.mangle);
124         }
125
126         TeleportPlayer(teleporter, player, to, ang, Portal_Transform_Apply(transform, player.velocity), teleporter.enemy.absmin, teleporter.enemy.absmax);
127
128         // reset fade counter
129         teleporter.portal_wants_to_vanish = 0;
130         teleporter.fade_time = time + 10;
131 }
132
133 float Portal_FindSafeOrigin(entity portal)
134 {
135         vector o;
136         o = portal.origin;
137         portal.mins = PL_MIN - '8 8 8';
138         portal.maxs = PL_MAX + '8 8 8';
139         fixedmakevectors(portal.mangle);
140         portal.origin += 16 * v_forward;
141         if(!move_out_of_solid(portal))
142         {
143                 print("NO SAFE ORIGIN\n");
144                 return 0;
145         }
146         portal.portal_safe_origin = portal.origin;
147         setorigin(portal, o);
148         return 1;
149 }
150
151 void Portal_Touch()
152 {
153         if(other.classname == "porto")
154                 return;
155         if(time < self.portal_activatetime)
156                 if(other == self.owner)
157                 {
158                         self.portal_activatetime = time + 0.1;
159                         return;
160                 }
161         fixedmakevectors(self.mangle);
162         if((other.origin - self.origin) * v_forward < 0)
163                 return;
164         if(other.mins_x < PL_MIN_x || other.mins_y < PL_MIN_y || other.mins_z < PL_MIN_z
165         || other.maxs_x > PL_MAX_x || other.maxs_y > PL_MAX_y || other.maxs_z > PL_MAX_z)
166         {
167                 // can't teleport this
168                 return;
169         }
170         Portal_TeleportPlayer(self, other);
171 }
172
173 void Portal_MakeBrokenPortal(entity portal)
174 {
175         portal.solid = SOLID_NOT;
176         portal.touch = SUB_Null;
177         portal.effects = 0;
178         //portal.colormod = '1 1 1';
179         portal.nextthink = 0;
180 }
181
182 void Portal_MakeWaitingPortal(entity portal)
183 {
184         portal.solid = SOLID_NOT;
185         portal.touch = SUB_Null;
186         portal.effects = EF_ADDITIVE;
187         portal.colormod = '1 1 1';
188         portal.nextthink = 0;
189 }
190
191 void Portal_MakeInPortal(entity portal)
192 {
193         portal.solid = SOLID_TRIGGER;
194         portal.touch = Portal_Touch;
195         portal.effects = EF_RED;
196         portal.colormod = '1 0 0';
197         portal.nextthink = time;
198 }
199
200 void Portal_MakeOutPortal(entity portal)
201 {
202         portal.solid = SOLID_NOT;
203         portal.touch = SUB_Null;
204         portal.effects = EF_STARDUST | EF_BLUE;
205         portal.colormod = '0 0 1';
206         portal.nextthink = 0;
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.mangle), teleporter.mangle);
220         teleporter.enemy = destination;
221         destination.enemy = teleporter;
222         Portal_MakeInPortal(teleporter);
223         Portal_MakeOutPortal(destination);
224         teleporter.fade_time = time + 10;
225         destination.fade_time = time + 10;
226         teleporter.portal_wants_to_vanish = 0;
227         destination.portal_wants_to_vanish = 0;
228 }
229
230 void Portal_Remove(entity portal)
231 {
232         entity e;
233         e = portal.enemy;
234
235         if(e)
236         {
237                 Portal_Disconnect(portal, e);
238                 Portal_Remove(e);
239         }
240
241         if(portal == portal.owner.portal_in)
242                 portal.owner.portal_in = world;
243         if(portal == portal.owner.portal_out)
244                 portal.owner.portal_out = world;
245         portal.owner = world;
246
247         // makes the portal vanish
248         Portal_MakeBrokenPortal(portal);
249         SUB_SetFade(portal, time, 0.5);
250 }
251
252 void Portal_Think()
253 {
254         entity e, o;
255
256         if(self.solid != SOLID_TRIGGER)
257                 error("Portal_Think called for a portal that should not be thinking");
258
259         o = self.owner;
260         self.solid = SOLID_BBOX;
261         self.owner = world;
262         FOR_EACH_PLAYER(e)
263         {
264                 if(time < self.portal_activatetime)
265                         if(e == o)
266                                 continue;
267                 // if e would hit the portal in a frame...
268                 // already teleport him
269                 tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 2 * frametime, MOVE_NORMAL, e);
270                 if(trace_ent == self)
271                         Portal_TeleportPlayer(self, e);
272         }
273         self.solid = SOLID_TRIGGER;
274         self.owner = o;
275
276         self.nextthink = time;
277
278         if(time > self.fade_time)
279                 Portal_Remove(self);
280 }
281
282
283 // cleanup:
284 //   when creating in-portal:
285 //     disconnect
286 //     clear existing out-portal
287 //     make existing in-portal an out-portal and connect
288 //     set as in-portal
289 //   when creating out-portal:
290 //     disconnect
291 //     clear existing out-portal
292 //     set as out-portal
293 //   when player dies:
294 //     disconnect portals
295 //     clear both portals
296 //   after timeout of in-portal:
297 //     disconnect portals
298 //     clear both portals
299 //   TODO: ensure only one portal shot at once
300 float Portal_SetInPortal(entity own, entity portal)
301 {
302         if(own.portal_out)
303                 Portal_Remove(own.portal_out);
304         if(own.portal_in)
305                 own.portal_out = own.portal_in;
306         own.portal_in = portal;
307         if(own.portal_out)
308                 Portal_Connect(own.portal_in, own.portal_out);
309         return 1;
310 }
311 float Portal_SetOutPortal(entity own, entity portal)
312 {
313         if(!own.portal_in)
314                 return 0;
315         if(own.portal_out)
316                 Portal_Remove(own.portal_out);
317         own.portal_out = portal;
318         Portal_Connect(own.portal_in, own.portal_out);
319         return 1;
320 }
321 void Portal_ClearAll(entity own)
322 {
323         if(own.portal_in)
324                 Portal_Remove(own.portal_in);
325         if(own.portal_out)
326                 Portal_Remove(own.portal_out);
327 }
328 float Portal_VerifyPortal(vector org, vector ang)
329 {
330         fixedmakevectors(ang);
331         if(!CheckWireframeBox(org - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 16 * v_forward))
332                 return 0;
333         return 1;
334 }
335
336 entity Portal_Spawn(entity own, vector org, vector ang)
337 {
338         entity portal;
339         portal = spawn();
340         portal.classname = "portal";
341         portal.owner = own;
342         portal.origin = org;
343         portal.mangle = ang;
344         ang_x = -ang_x;
345         portal.angles = ang;
346         portal.think = Portal_Think;
347         portal.nextthink = 0;
348         portal.fade_time = time + 10;
349         portal.portal_activatetime = time + 0.1;
350         setmodel(portal, "models/portal.md3");
351
352         if(!Portal_FindSafeOrigin(portal))
353         {
354                 remove(portal);
355                 return world;
356         }
357
358         setsize(portal, '-48 -48 -48', '48 48 48');
359         Portal_MakeWaitingPortal(portal);
360
361         return portal;
362 }
363
364 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
365 {
366         entity portal;
367         vector ang;
368         vector org;
369
370         if(trace_ent.classname == "player")
371         {
372                 print("hit a player, adjusting...\n");
373                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
374                 trace_plane_normal = '0 0 1';
375         }
376
377         org = trace_endpos;
378         ang = fixedvectoangles2(trace_plane_normal, dir);
379
380         if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || !Portal_VerifyPortal(org, ang))
381         {
382                 // cannot create a portal here
383                 // clear all to make sure
384                 Portal_ClearAll(own);
385                 return 0;
386         }
387
388         portal = Portal_Spawn(own, org, ang);
389         Portal_SetInPortal(own, portal);
390
391         return 1;
392 }
393
394 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
395 {
396         entity portal;
397         vector ang;
398         vector org;
399
400         if(trace_ent.classname == "player")
401         {
402                 print("hit a player, adjusting...\n");
403                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
404                 trace_plane_normal = '0 0 1';
405         }
406         /*
407         else
408                 dir = -1 * dir; // invert the sense of the second portal
409                 // no, better don't, it is weirder IF it is inverted
410         */
411
412         org = trace_endpos;
413         ang = fixedvectoangles2(trace_plane_normal, dir);
414
415         if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || !Portal_VerifyPortal(org, ang))
416         {
417                 // cannot create a portal here
418                 // clear all to make sure
419                 Portal_ClearAll(own);
420                 return 0;
421         }
422
423         portal = Portal_Spawn(own, org, ang);
424         Portal_SetOutPortal(own, portal);
425
426         return 1;
427 }