]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/portals.qc
better track ownership of portals; still to fix: player leaving
[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 }
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 }
187
188 void Portal_MakeOutPortal(entity portal)
189 {
190         portal.solid = SOLID_NOT;
191         portal.touch = SUB_Null;
192         portal.effects = EF_STARDUST;
193         portal.colormod = '0 0 1';
194 }
195
196 void Portal_Disconnect(entity teleporter, entity destination)
197 {
198         teleporter.enemy = world;
199         destination.enemy = world;
200         Portal_MakeBrokenPortal(teleporter);
201         Portal_MakeBrokenPortal(destination);
202 }
203
204 void Portal_Connect(entity teleporter, entity destination)
205 {
206         teleporter.portal_transform = Portal_Transform_Divide(Portal_Transform_TurnDirection(destination.mangle), teleporter.mangle);
207         teleporter.enemy = destination;
208         destination.enemy = teleporter;
209         Portal_MakeInPortal(teleporter);
210         Portal_MakeOutPortal(destination);
211         teleporter.fade_time = time + 10;
212         destination.fade_time = time + 10;
213         teleporter.portal_wants_to_vanish = 0;
214         destination.portal_wants_to_vanish = 0;
215 }
216
217 void Portal_Think()
218 {
219         entity e, o;
220         float m;
221
222         if(self.solid == SOLID_TRIGGER)
223         {
224                 m = self.modelindex;
225                 o = self.owner;
226                 self.solid = SOLID_BBOX;
227                 self.owner = world;
228                 self.modelindex = 0;
229                 FOR_EACH_PLAYER(e)
230                 {
231                         if(time < self.portal_activatetime)
232                                 if(e == o)
233                                         continue;
234                         // if e would hit the portal in a frame...
235                         // already teleport him
236                         tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 3 * frametime, MOVE_NORMAL, e);
237                         if(trace_ent == self)
238                                 Portal_TeleportPlayer(self, e);
239                 }
240                 self.solid = SOLID_TRIGGER;
241                 self.owner = o;
242                 self.modelindex = m;
243         }
244
245         self.nextthink = time;
246
247         if(time < self.fade_time)
248                 return;
249
250         self.portal_wants_to_vanish = 1;
251
252         if(self.enemy)
253                 if(!self.enemy.portal_wants_to_vanish)
254                         return;
255
256         SUB_SetFade(self, time + 1, 1);
257         if(self.enemy)
258         {
259                 SUB_SetFade(self.enemy, time + 1, 1);
260                 Portal_Disconnect(self, self.enemy);
261         }
262         if(self.owner.portal_in == self)
263         {
264                 self.owner.portal_in = world;
265                 print("fixed in-portal ownership\n");
266         }
267         if(self.owner.portal_out == self)
268         {
269                 self.owner.portal_out = world;
270                 print("fixed out-portal ownership\n");
271         }
272 }
273
274 void Portal_RequestVanish(entity portal)
275 {
276         entity oldself;
277         oldself = self;
278         self = portal;
279         if(self.enemy)
280         {
281                 self.enemy.portal_wants_to_vanish = 1;
282                 self.enemy.fade_time = time;
283         }
284         self.fade_time = time;
285         Portal_Think();
286         self = oldself;
287 }
288
289 entity Portal_Spawn(entity own, vector org, vector ang)
290 {
291         fixedmakevectors(ang);
292         if(!CheckWireframeBox(org - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 16 * v_forward))
293                 return world;
294
295         entity portal;
296         portal = spawn();
297         portal.classname = "portal";
298         portal.owner = own;
299         portal.origin = org;
300         portal.mangle = ang;
301         ang_x = -ang_x;
302         portal.angles = ang;
303         portal.think = Portal_Think;
304         portal.nextthink = time;
305         portal.fade_time = time + 10;
306         portal.portal_activatetime = time + 0.1;
307         setmodel(portal, "models/portal.md3");
308
309         if(!Portal_FindSafeOrigin(portal))
310         {
311                 remove(portal);
312                 return world;
313         }
314
315         setsize(portal, '-48 -48 -48', '48 48 48');
316
317         return portal;
318 }
319
320 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
321 {
322         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
323                 return 0;
324
325         if(trace_ent.classname == "player")
326         {
327                 print("hit a player, adjusting...\n");
328                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
329                 trace_plane_normal = '0 0 1';
330         }
331
332         if(own.portal_in)
333         {
334                 if(own.portal_out)
335                 {
336                         Portal_Disconnect(own.portal_in, own.portal_out);
337                         Portal_RequestVanish(own.portal_out);
338                 }
339                 own.portal_out = own.portal_in;
340                 own.portal_in = world;
341                 own.portal_out.portal_id = portal_id_val;
342         }
343         else if(own.portal_out)
344         {
345                 print("this DID happen, no idea why (1)\n");
346                 Portal_RequestVanish(own.portal_out);
347                 own.portal_out = world;
348         }
349                 
350         own.portal_in = Portal_Spawn(own, trace_endpos + trace_plane_normal, fixedvectoangles2(trace_plane_normal, dir));
351         if(!own.portal_in)
352         {
353                 if(own.portal_out)
354                 {
355                         Portal_RequestVanish(own.portal_out);
356                         own.portal_out = world;
357                 }
358                 return 0;
359         }
360         own.portal_in.portal_id = portal_id_val;
361
362         if(own.portal_out)
363                 Portal_Connect(own.portal_in, own.portal_out);
364
365         return 1;
366 }
367
368 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
369 {
370         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
371         {
372                 if(own.portal_in.portal_id == portal_id_val)
373                 {
374                         Portal_RequestVanish(own.portal_in);
375                         own.portal_in = world;
376                 }
377                 return 0;
378         }
379
380         if(trace_ent.classname == "player")
381         {
382                 print("hit a player, adjusting...\n");
383                 trace_endpos = trace_ent.origin + '0 0 1' * PL_MIN_z;
384                 trace_plane_normal = '0 0 1';
385                 dir = -1 * dir; // fix orientation
386         }
387
388         if(!own.portal_in)
389                 return 0; // sorry
390
391         if(own.portal_in.portal_id != portal_id_val)
392                 return 0; // we need MATCHING portals
393
394         if(own.portal_out)
395         {
396                 Portal_Disconnect(own.portal_in, own.portal_out);
397                 Portal_RequestVanish(own.portal_out);
398                 own.portal_out = world;
399         }
400                 
401         own.portal_out = Portal_Spawn(own, trace_endpos + trace_plane_normal, fixedvectoangles2(trace_plane_normal, -1 * dir));
402         if(!own.portal_out)
403         {
404                 Portal_RequestVanish(own.portal_in);
405                 own.portal_in = world;
406                 return 0;
407         }
408         own.portal_out.portal_id = portal_id_val;
409
410         Portal_Connect(own.portal_in, own.portal_out);
411
412         return 1;
413 }