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