1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: p_teleport.cpp 4469 2014-01-03 23:38:29Z dr_sean $
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 // Copyright (C) 1998-2012 by Randy Heit (ZDoom).
8 // Copyright (C) 2006-2014 by The Odamex Team.
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // DESCRIPTION:
21 // Teleportation.
22 //
23 //-----------------------------------------------------------------------------
24
25
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include "doomtype.h"
30 #include "doomdef.h"
31 #include "doomstat.h"
32
33 #include "m_random.h"
34 #include "s_sound.h"
35
36 #include "p_local.h"
37
38
39 // State.
40 #include "r_state.h"
41
42
43 extern void P_CalcHeight (player_t *player);
44
45 // [AM] From ZDoom SVN, modified for use with Odamex and without the
46 // buggy 2.0.x teleport behavior compatibility fix. Thanks to both
47 // Randy and Graf.
SelectTeleDest(int tid,int tag)48 static AActor* SelectTeleDest(int tid, int tag)
49 {
50 AActor* searcher;
51
52 // If tid is non-zero, select a destination from a matching actor at random.
53 // If tag is also non-zero, the selection is restricted to actors in sectors
54 // with a matching tag. If tid is zero and tag is non-zero, then the old Doom
55 // behavior is used instead (return the first teleport dest found in a tagged
56 // sector).
57
58 if (tid != 0)
59 {
60 TThinkerIterator<AActor> iterator;
61 int count = 0;
62 while ((searcher = iterator.Next()))
63 {
64 if (!(searcher->type == MT_TELEPORTMAN || searcher->type == MT_TELEPORTMAN2))
65 continue;
66 if (searcher->tid != tid)
67 continue;
68
69 if (tag == 0 || searcher->subsector->sector->tag == tag)
70 count++;
71 }
72
73 // If teleport dests were not found, the sector tag is ignored for the
74 // following compatibility searches.
75 // Do this only when tag is 0 because this is the only case that was defined in Hexen.
76 if (count == 0)
77 {
78 if (tag == 0)
79 {
80 // Try to find a matching map spot (fixes Hexen MAP10)
81 TThinkerIterator<AActor> it2;
82 searcher = it2.Next();
83 while ((searcher = it2.Next()) != NULL)
84 {
85 if (searcher->type == MT_MAPSPOT || searcher->type == MT_MAPSPOTGRAVITY)
86 break;
87 }
88
89 if (searcher == NULL)
90 {
91 // Try to find a matching non-blocking spot of any type (fixes Caldera MAP13)
92 FActorIterator it3(tid);
93 searcher = it3.Next();
94 while (searcher != NULL && (searcher->flags & MF_SOLID))
95 searcher = it3.Next();
96 return searcher;
97 }
98 }
99 }
100 else
101 {
102 if (count != 1)
103 count = 1 + (P_Random() % count);
104
105 searcher = NULL;
106 while (count > 0)
107 {
108 searcher = iterator.Next();
109 if (!(searcher->type == MT_TELEPORTMAN || searcher->type == MT_TELEPORTMAN2))
110 continue;
111 if (searcher->tid != tid)
112 continue;
113 if (tag == 0 || searcher->subsector->sector->tag == tag)
114 count--;
115 }
116 }
117 return searcher;
118 }
119
120 if (tag != 0)
121 {
122 int secnum = -1;
123
124 while ((secnum = P_FindSectorFromTag(tag, secnum)) >= 0)
125 {
126 // Scanning the snext links of things in the sector will not work, because
127 // TeleportDests have MF_NOSECTOR set. So you have to search *everything*.
128 // If there is more than one sector with a matching tag, then the destination
129 // in the lowest-numbered sector is returned, rather than the earliest placed
130 // teleport destination. This means if 50 sectors have a matching tag and
131 // only the last one has a destination, *every* actor is scanned at least 49
132 // times. Yuck.
133 TThinkerIterator<AActor> it2;
134 while ((searcher = it2.Next()) != NULL)
135 {
136 if (!(searcher->type == MT_TELEPORTMAN || searcher->type == MT_TELEPORTMAN2))
137 continue;
138
139 if (searcher->subsector->sector == sectors + secnum)
140 return searcher;
141 }
142 }
143 }
144
145 return NULL;
146 }
147
148 //
149 // TELEPORTATION
150 //
EV_Teleport(int tid,int tag,int side,AActor * thing)151 BOOL EV_Teleport(int tid, int tag, int side, AActor *thing)
152 {
153 AActor *m;
154 unsigned an;
155 fixed_t oldx;
156 fixed_t oldy;
157 fixed_t oldz;
158 player_t *player;
159
160 // don't teleport missiles
161 if (thing->flags & MF_MISSILE)
162 return false;
163
164 // Don't teleport if hit back of line, so you can get out of teleporter.
165 if (side == 1)
166 return false;
167
168 // [AM] Use modern ZDoom teleport destination selection.
169 m = SelectTeleDest(tid, tag);
170 if (m == NULL)
171 return false;
172
173 // killough 5/12/98: exclude voodoo dolls:
174 player = thing->player;
175 if (player && player->mo != thing)
176 player = NULL;
177
178 oldx = thing->x;
179 oldy = thing->y;
180 oldz = thing->z;
181
182 fixed_t destz = (m->type == MT_TELEPORTMAN) ? P_FloorHeight(m) : m->z;
183
184 if (!P_TeleportMove (thing, m->x, m->y, destz, false))
185 return false;
186
187 // fraggle: this was changed in final doom,
188 // problem between normal doom2 1.9 and final doom
189 // Note that although chex.exe is based on Final Doom,
190 // it does not have this quirk.
191 if (gamemission < pack_tnt || gamemission == chex)
192 thing->z = thing->floorz;
193
194 if (player)
195 player->viewz = thing->z + thing->player->viewheight;
196
197 // spawn teleport fog at source and destination
198 if(serverside && !(player && player->spectator))
199 {
200 S_Sound (new AActor (oldx, oldy, oldz, MT_TFOG), CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
201 an = m->angle >> ANGLETOFINESHIFT;
202 // emit sound at new spot
203 S_Sound (new AActor (m->x+20*finecosine[an], m->y+20*finesine[an], thing->z, MT_TFOG), CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
204 }
205
206 // don't move for a bit
207 if (player && !player->spectator)
208 thing->reactiontime = 18;
209
210 thing->momx = thing->momy = thing->momz = 0;
211 thing->angle = m->angle;
212 thing->pitch = 0;
213
214 return true;
215 }
216
217 // [ML] Original vanilla-style EV_Teleport, based on code from chocolate doom
EV_LineTeleport(line_t * line,int side,AActor * thing)218 BOOL EV_LineTeleport (line_t *line, int side, AActor *thing)
219 {
220 AActor *m;
221 unsigned an;
222 int i;
223 int tag;
224 fixed_t oldx;
225 fixed_t oldy;
226 fixed_t oldz;
227 player_t *player;
228 sector_t* sector;
229 TThinkerIterator<AActor> iterator;
230
231 // don't teleport missiles
232 if (thing->flags & MF_MISSILE)
233 return false;
234
235 // Don't teleport if hit back of line,
236 // so you can get out of teleporter.
237 if (side == 1)
238 return false;
239
240 tag = line->id;
241 // Yeah, cycle through all of them...
242 for (i = 0; i < numsectors; i++)
243 {
244 if (sectors[ i ].tag == tag )
245 {
246 while ( (m = iterator.Next ()) )
247 {
248 // not a teleportman
249 if (m->type != MT_TELEPORTMAN)
250 continue;
251
252 sector = m->subsector->sector;
253 // wrong sector
254 if (sector-sectors != i )
255 continue;
256
257 // killough 5/12/98: exclude voodoo dolls:
258 player = thing->player;
259 if (player && player->mo != thing)
260 player = NULL;
261
262 oldx = thing->x;
263 oldy = thing->y;
264 oldz = thing->z;
265
266 fixed_t destz = (m->type == MT_TELEPORTMAN) ? P_FloorHeight(m) : m->z;
267
268 if (!P_TeleportMove (thing, m->x, m->y, destz, false))
269 return false;
270
271 // fraggle: this was changed in final doom,
272 // problem between normal doom2 1.9 and final doom
273 // Note that although chex.exe is based on Final Doom,
274 // it does not have this quirk.
275 if (gamemission < pack_tnt || gamemission == chex)
276 thing->z = thing->floorz;
277
278 if (player)
279 player->viewz = thing->z + thing->player->viewheight;
280
281 // spawn teleport fog at source and destination
282 if(serverside && !(player && player->spectator))
283 {
284 S_Sound (new AActor (oldx, oldy, oldz, MT_TFOG), CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
285 an = m->angle >> ANGLETOFINESHIFT;
286 // emit sound at new spot
287 S_Sound (new AActor (m->x+20*finecosine[an], m->y+20*finesine[an], thing->z, MT_TFOG), CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
288 }
289
290 // don't move for a bit
291 if (player && !player->spectator)
292 thing->reactiontime = 18;
293
294 thing->momx = thing->momy = thing->momz = 0;
295 thing->angle = m->angle;
296 thing->pitch = 0;
297
298 return true;
299 }
300 }
301 }
302
303 return false;
304 }
305
306
307 //
308 // Silent TELEPORTATION, by Lee Killough
309 // Primarily for rooms-over-rooms etc.
310 // [RH] Changed to find destination by tid rather than sector
311 //
312
EV_SilentTeleport(int tid,line_t * line,int side,AActor * thing)313 BOOL EV_SilentTeleport (int tid, line_t *line, int side, AActor *thing)
314 {
315 AActor *m;
316
317 // don't teleport missiles
318 // Don't teleport if hit back of line,
319 // so you can get out of teleporter.
320 // [RH] This could potentially be called without a source linedef
321
322 if (thing->flags & MF_MISSILE || !line)
323 return false;
324
325 // [AM] TODO: Change this to use SelectTeleDest.
326 if (NULL == (m = AActor::FindGoal (NULL, tid, MT_TELEPORTMAN)))
327 if (NULL == (m = AActor::FindGoal (NULL, tid, MT_TELEPORTMAN2)))
328 return false;
329
330 // Height of thing above ground, in case of mid-air teleports:
331 fixed_t z = thing->z - thing->floorz;
332
333 // Get the angle between the exit thing and source linedef.
334 // Rotate 90 degrees, so that walking perpendicularly across
335 // teleporter linedef causes thing to exit in the direction
336 // indicated by the exit thing.
337 angle_t angle =
338 P_PointToAngle(0, 0, line->dx, line->dy) - m->angle + ANG90;
339
340 // Sine, cosine of angle adjustment
341 fixed_t s = finesine[angle>>ANGLETOFINESHIFT];
342 fixed_t c = finecosine[angle>>ANGLETOFINESHIFT];
343
344 // Momentum of thing crossing teleporter linedef
345 fixed_t momx = thing->momx;
346 fixed_t momy = thing->momy;
347
348 // Whether this is a player, and if so, a pointer to its player_t
349 player_t *player = thing->player;
350
351 // Attempt to teleport, aborting if blocked
352 if (!P_TeleportMove (thing, m->x, m->y, z + m->floorz, false))
353 return 0;
354
355 // Rotate thing according to difference in angles
356 thing->angle += angle;
357
358 // Rotate thing's momentum to come out of exit just like it entered
359 thing->momx = FixedMul(momx, c) - FixedMul(momy, s);
360 thing->momy = FixedMul(momy, c) + FixedMul(momx, s);
361
362 // Adjust player's view, in case there has been a height change
363 // Voodoo dolls are excluded by making sure player->mo == thing.
364 if (player && player->mo == thing)
365 {
366 // Save the current deltaviewheight, used in stepping
367 fixed_t deltaviewheight = player->deltaviewheight;
368
369 // Clear deltaviewheight, since we don't want any changes
370 player->deltaviewheight = 0;
371
372 // Set player's view according to the newly set parameters
373 P_CalcHeight(player);
374
375 // Reset the delta to have the same dynamics as before
376 player->deltaviewheight = deltaviewheight;
377 }
378 return true;
379 }
380
381 //
382 // Silent linedef-based TELEPORTATION, by Lee Killough
383 // Primarily for rooms-over-rooms etc.
384 // This is the complete player-preserving kind of teleporter.
385 // It has advantages over the teleporter with thing exits.
386 //
387
388 // maximum fixed_t units to move object to avoid hiccups
389 #define FUDGEFACTOR 10
390
391 // [RH] Modified to support different source and destination ids.
EV_SilentLineTeleport(line_t * line,int side,AActor * thing,int id,BOOL reverse)392 BOOL EV_SilentLineTeleport (line_t *line, int side, AActor *thing, int id,
393 BOOL reverse)
394 {
395 int i;
396 line_t *l;
397
398 if (thing->flags & MF_MISSILE || !line)
399 return false;
400
401 for (i = -1; (i = P_FindLineFromID (id, i)) >= 0;)
402 if ((l=lines+i) != line && l->backsector)
403 {
404 // Get the thing's position along the source linedef
405 fixed_t pos = abs(line->dx) > abs(line->dy) ?
406 FixedDiv(thing->x - line->v1->x, line->dx) :
407 FixedDiv(thing->y - line->v1->y, line->dy) ;
408
409 // Get the angle between the two linedefs, for rotating
410 // orientation and momentum. Rotate 180 degrees, and flip
411 // the position across the exit linedef, if reversed.
412 angle_t angle = (reverse ? pos = FRACUNIT-pos, 0 : ANG180) +
413 P_PointToAngle(0, 0, l->dx, l->dy) -
414 P_PointToAngle(0, 0, line->dx, line->dy);
415
416 // Interpolate position across the exit linedef
417 fixed_t x = l->v2->x - FixedMul(pos, l->dx);
418 fixed_t y = l->v2->y - FixedMul(pos, l->dy);
419
420 // Sine, cosine of angle adjustment
421 fixed_t s = finesine[angle>>ANGLETOFINESHIFT];
422 fixed_t c = finecosine[angle>>ANGLETOFINESHIFT];
423
424 // Maximum distance thing can be moved away from interpolated
425 // exit, to ensure that it is on the correct side of exit linedef
426 int fudge = FUDGEFACTOR;
427
428 // Whether this is a player, and if so, a pointer to its player_t.
429 // Voodoo dolls are excluded by making sure thing->player->mo==thing.
430 player_t *player = thing->player && thing->player->mo == thing ?
431 thing->player : NULL;
432
433 // Height of thing above ground
434 fixed_t z = thing->z -
435 P_FloorHeight(thing->x, thing->y, sides[line->sidenum[1]].sector) +
436 P_FloorHeight(x, y, sides[l->sidenum[0]].sector);
437
438 // Side to exit the linedef on positionally.
439 //
440 // Notes:
441 //
442 // This flag concerns exit position, not momentum. Due to
443 // roundoff error, the thing can land on either the left or
444 // the right side of the exit linedef, and steps must be
445 // taken to make sure it does not end up on the wrong side.
446 //
447 // Exit momentum is always towards side 1 in a reversed
448 // teleporter, and always towards side 0 otherwise.
449 //
450 // Exiting positionally on side 1 is always safe, as far
451 // as avoiding oscillations and stuck-in-wall problems,
452 // but may not be optimum for non-reversed teleporters.
453 //
454 // Exiting on side 0 can cause oscillations if momentum
455 // is towards side 1, as it is with reversed teleporters.
456 //
457 // Exiting on side 1 slightly improves player viewing
458 // when going down a step on a non-reversed teleporter.
459
460 int side = reverse;
461
462 // Make sure we are on correct side of exit linedef.
463 while (P_PointOnLineSide(x, y, l) != side && --fudge>=0)
464 if (abs(l->dx) > abs(l->dy))
465 y -= ((l->dx < 0) != side ? -1 : 1);
466 else
467 x += ((l->dy < 0) != side ? -1 : 1);
468
469 // Attempt to teleport, aborting if blocked
470 // Adjust z position to be same height above ground as before.
471 // Ground level at the exit is measured as the higher of the
472 // two floor heights at the exit linedef.
473 if (!P_TeleportMove (thing, x, y, z, false))
474 return false;
475
476 // Rotate thing's orientation according to difference in linedef angles
477 thing->angle += angle;
478
479 // Momentum of thing crossing teleporter linedef
480 x = thing->momx;
481 y = thing->momy;
482
483 // Rotate thing's momentum to come out of exit just like it entered
484 thing->momx = FixedMul(x, c) - FixedMul(y, s);
485 thing->momy = FixedMul(y, c) + FixedMul(x, s);
486
487 // Adjust a player's view, in case there has been a height change
488 if (player)
489 {
490 // Save the current deltaviewheight, used in stepping
491 fixed_t deltaviewheight = player->deltaviewheight;
492
493 // Clear deltaviewheight, since we don't want any changes now
494 player->deltaviewheight = 0;
495
496 // Set player's view according to the newly set parameters
497 P_CalcHeight(player);
498
499 // Reset the delta to have the same dynamics as before
500 player->deltaviewheight = deltaviewheight;
501 }
502
503 return true;
504 }
505 return false;
506 }
507
508 VERSION_CONTROL (p_teleport_cpp, "$Id: p_teleport.cpp 4469 2014-01-03 23:38:29Z dr_sean $")
509
510