]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/portals.qc
make the minstagib Nex a separate weapon
[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         if(!move_out_of_solid(portal))
140         {
141                 print("NO SAFE ORIGIN\n");
142                 return 0;
143         }
144         portal.portal_safe_origin = portal.origin;
145         setorigin(portal, o);
146         return 1;
147 }
148
149 void Portal_Touch()
150 {
151         if(other.classname == "porto")
152                 return;
153         if(time < self.portal_activatetime)
154                 if(other == self.owner)
155                 {
156                         self.portal_activatetime = time + 0.1;
157                         return;
158                 }
159         fixedmakevectors(self.mangle);
160         if((other.origin - self.origin) * v_forward < 0)
161                 return;
162         if(other.mins_x < PL_MIN_x || other.mins_y < PL_MIN_y || other.mins_z < PL_MIN_z
163         || other.maxs_x > PL_MAX_x || other.maxs_y > PL_MAX_y || other.maxs_z > PL_MAX_z)
164         {
165                 // can't teleport this
166                 return;
167         }
168         Portal_TeleportPlayer(self, other);
169 }
170
171 void Portal_MakeBrokenPortal(entity portal)
172 {
173         portal.solid = SOLID_NOT;
174         portal.touch = SUB_Null;
175         portal.effects = 0;
176         portal.colormod = '1 1 1';
177         portal.nextthink = 0;
178 }
179
180 void Portal_MakeInPortal(entity portal)
181 {
182         portal.solid = SOLID_TRIGGER;
183         portal.touch = Portal_Touch;
184         portal.effects = EF_RED;
185         portal.colormod = '1 0 0';
186         portal.nextthink = time;
187 }
188
189 void Portal_MakeOutPortal(entity portal)
190 {
191         portal.solid = SOLID_NOT;
192         portal.touch = SUB_Null;
193         portal.effects = EF_STARDUST;
194         portal.colormod = '0 0 1';
195         portal.nextthink = 0;
196 }
197
198 void Portal_Disconnect(entity teleporter, entity destination)
199 {
200         teleporter.enemy = world;
201         destination.enemy = world;
202         Portal_MakeBrokenPortal(teleporter);
203         Portal_MakeBrokenPortal(destination);
204 }
205
206 void Portal_Connect(entity teleporter, entity destination)
207 {
208         teleporter.portal_transform = Portal_Transform_Divide(Portal_Transform_TurnDirection(destination.mangle), teleporter.mangle);
209         teleporter.enemy = destination;
210         destination.enemy = teleporter;
211         Portal_MakeInPortal(teleporter);
212         Portal_MakeOutPortal(destination);
213         teleporter.fade_time = time + 10;
214         destination.fade_time = time + 10;
215         teleporter.portal_wants_to_vanish = 0;
216         destination.portal_wants_to_vanish = 0;
217 }
218
219 void Portal_Remove(entity portal)
220 {
221         entity e;
222         e = portal.enemy;
223
224         if(e)
225         {
226                 Portal_Disconnect(portal, e);
227                 Portal_Remove(e);
228         }
229
230         if(portal == portal.owner.portal_in)
231                 portal.owner.portal_in = world;
232         if(portal == portal.owner.portal_out)
233                 portal.owner.portal_out = world;
234         portal.owner = world;
235
236         // makes the portal vanish
237         Portal_MakeBrokenPortal(portal);
238         SUB_SetFade(portal, time, 0.5);
239 }
240
241 void Portal_Think()
242 {
243         entity e, o;
244
245         if(self.solid != SOLID_TRIGGER)
246                 error("Portal_Think called for a portal that should not be thinking");
247
248         o = self.owner;
249         self.solid = SOLID_BBOX;
250         self.owner = world;
251         FOR_EACH_PLAYER(e)
252         {
253                 if(time < self.portal_activatetime)
254                         if(e == o)
255                                 continue;
256                 // if e would hit the portal in a frame...
257                 // already teleport him
258                 tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 3 * frametime, MOVE_NORMAL, e);
259                 if(trace_ent == self)
260                         Portal_TeleportPlayer(self, e);
261         }
262         self.solid = SOLID_TRIGGER;
263         self.owner = o;
264
265         self.nextthink = time;
266
267         if(time > self.fade_time)
268                 Portal_Remove(self);
269 }
270
271
272 // cleanup:
273 //   when creating in-portal:
274 //     disconnect
275 //     clear existing out-portal
276 //     make existing in-portal an out-portal and connect
277 //     set as in-portal
278 //   when creating out-portal:
279 //     disconnect
280 //     clear existing out-portal
281 //     set as out-portal
282 //   when player dies:
283 //     disconnect portals
284 //     clear both portals
285 //   after timeout of in-portal:
286 //     disconnect portals
287 //     clear both portals
288 //   TODO: ensure only one portal shot at once
289 float Portal_SetInPortal(entity own, entity portal)
290 {
291         if(own.portal_out)
292                 Portal_Remove(own.portal_out);
293         if(own.portal_in)
294                 own.portal_out = own.portal_in;
295         own.portal_in = portal;
296         if(own.portal_out)
297                 Portal_Connect(own.portal_in, own.portal_out);
298         return 1;
299 }
300 float Portal_SetOutPortal(entity own, entity portal)
301 {
302         if(!own.portal_in)
303                 return 0;
304         if(own.portal_out)
305                 Portal_Remove(own.portal_out);
306         own.portal_out = portal;
307         Portal_Connect(own.portal_in, own.portal_out);
308         return 1;
309 }
310 void Portal_ClearAll(entity own)
311 {
312         if(own.portal_in)
313                 Portal_Remove(own.portal_in);
314         if(own.portal_out)
315                 Portal_Remove(own.portal_out);
316 }
317 float Portal_VerifyPortalAtTrace(vector ang)
318 {
319         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
320                 return 0;
321         fixedmakevectors(ang);
322         if(!CheckWireframeBox(trace_endpos - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 16 * v_forward))
323                 return 0;
324         return 1;
325 }
326
327 entity Portal_Spawn(entity own, vector org, vector ang)
328 {
329         entity portal;
330         portal = spawn();
331         portal.classname = "portal";
332         portal.owner = own;
333         portal.origin = org;
334         portal.mangle = ang;
335         ang_x = -ang_x;
336         portal.angles = ang;
337         portal.think = Portal_Think;
338         portal.nextthink = time;
339         portal.fade_time = time + 10;
340         portal.portal_activatetime = time + 0.1;
341         setmodel(portal, "models/portal.md3");
342
343         if(!Portal_FindSafeOrigin(portal))
344         {
345                 remove(portal);
346                 return world;
347         }
348
349         setsize(portal, '-48 -48 -48', '48 48 48');
350
351         return portal;
352 }
353
354 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
355 {
356         entity portal;
357         vector ang;
358
359         if(trace_ent.classname == "player")
360         {
361                 print("hit a player, adjusting...\n");
362                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
363                 trace_plane_normal = '0 0 1';
364         }
365
366         if(!Portal_VerifyPortalAtTrace(dir))
367         {
368                 // cannot create a portal here
369                 // clear all to make sure
370                 Portal_ClearAll(own);
371                 return 0;
372         }
373
374         ang = fixedvectoangles2(trace_plane_normal, dir);
375         portal = Portal_Spawn(own, trace_endpos, ang);
376         Portal_SetInPortal(own, portal);
377
378         return 1;
379 }
380
381 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
382 {
383         entity portal;
384         vector ang;
385
386         if(trace_ent.classname == "player")
387         {
388                 print("hit a player, adjusting...\n");
389                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
390                 trace_plane_normal = '0 0 1';
391         }
392         else
393                 dir = -1 * dir; // invert the sense of the second portal
394
395         if(!Portal_VerifyPortalAtTrace(dir))
396         {
397                 // cannot create a portal here
398                 // clear all to make sure
399                 Portal_ClearAll(own);
400                 return 0;
401         }
402
403         ang = fixedvectoangles2(trace_plane_normal, dir);
404         portal = Portal_Spawn(own, trace_endpos, ang);
405         Portal_SetOutPortal(own, portal);
406
407         return 1;
408 }