1 /*
2 ** p_things.cpp
3 ** ACS-accessible thing utilities
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2007 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #include "doomtype.h"
36 #include "p_local.h"
37 #include "info.h"
38 #include "s_sound.h"
39 #include "tables.h"
40 #include "doomstat.h"
41 #include "m_random.h"
42 #include "c_console.h"
43 #include "c_dispatch.h"
44 #include "a_sharedglobal.h"
45 #include "gi.h"
46 #include "templates.h"
47 #include "g_level.h"
48 #include "v_text.h"
49 #include "i_system.h"
50 
51 // Set of spawnable things for the Thing_Spawn and Thing_Projectile specials.
52 FClassMap SpawnableThings;
53 
54 static FRandom pr_leadtarget ("LeadTarget");
55 
P_Thing_Spawn(int tid,AActor * source,int type,angle_t angle,bool fog,int newtid)56 bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, int newtid)
57 {
58 	int rtn = 0;
59 	const PClass *kind;
60 	AActor *spot, *mobj;
61 	FActorIterator iterator (tid);
62 
63 	kind = P_GetSpawnableType(type);
64 
65 	if (kind == NULL)
66 		return false;
67 
68 	// Handle decorate replacements.
69 	kind = kind->GetReplacement();
70 
71 	if ((GetDefaultByType (kind)->flags3 & MF3_ISMONSTER) &&
72 		((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS)))
73 		return false;
74 
75 	if (tid == 0)
76 	{
77 		spot = source;
78 	}
79 	else
80 	{
81 		spot = iterator.Next();
82 	}
83 	while (spot != NULL)
84 	{
85 		mobj = Spawn (kind, spot->Pos(), ALLOW_REPLACE);
86 
87 		if (mobj != NULL)
88 		{
89 			ActorFlags2 oldFlags2 = mobj->flags2;
90 			mobj->flags2 |= MF2_PASSMOBJ;
91 			if (P_TestMobjLocation (mobj))
92 			{
93 				rtn++;
94 				mobj->angle = (angle != ANGLE_MAX ? angle : spot->angle);
95 				if (fog)
96 				{
97 					P_SpawnTeleportFog(mobj, spot->X(), spot->Y(), spot->Z() + TELEFOGHEIGHT, false, true);
98 				}
99 				if (mobj->flags & MF_SPECIAL)
100 					mobj->flags |= MF_DROPPED;	// Don't respawn
101 				mobj->tid = newtid;
102 				mobj->AddToHash ();
103 				mobj->flags2 = oldFlags2;
104 			}
105 			else
106 			{
107 				// If this is a monster, subtract it from the total monster
108 				// count, because it already added to it during spawning.
109 				mobj->ClearCounters();
110 				mobj->Destroy ();
111 			}
112 		}
113 		spot = iterator.Next();
114 	}
115 
116 	return rtn != 0;
117 }
118 
119 // [BC] Added
120 // [RH] Fixed
121 
P_MoveThing(AActor * source,fixed_t x,fixed_t y,fixed_t z,bool fog)122 bool P_MoveThing(AActor *source, fixed_t x, fixed_t y, fixed_t z, bool fog)
123 {
124 	fixed_t oldx, oldy, oldz;
125 
126 	oldx = source->X();
127 	oldy = source->Y();
128 	oldz = source->Z();
129 
130 	source->SetOrigin (x, y, z, false);
131 	if (P_TestMobjLocation (source))
132 	{
133 		if (fog)
134 		{
135 			P_SpawnTeleportFog(source, x, y, z, false, true);
136 			P_SpawnTeleportFog(source, oldx, oldy, oldz, true, true);
137 		}
138 		source->PrevX = x;
139 		source->PrevY = y;
140 		source->PrevZ = z;
141 		if (source == players[consoleplayer].camera)
142 		{
143 			R_ResetViewInterpolation();
144 		}
145 		return true;
146 	}
147 	else
148 	{
149 		source->SetOrigin (oldx, oldy, oldz, false);
150 		return false;
151 	}
152 }
153 
P_Thing_Move(int tid,AActor * source,int mapspot,bool fog)154 bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog)
155 {
156 	AActor *target;
157 
158 	if (tid != 0)
159 	{
160 		FActorIterator iterator1(tid);
161 		source = iterator1.Next();
162 	}
163 	FActorIterator iterator2 (mapspot);
164 	target = iterator2.Next ();
165 
166 	if (source != NULL && target != NULL)
167 	{
168 		return P_MoveThing(source, target->X(), target->Y(), target->Z(), fog);
169 	}
170 	return false;
171 }
172 
P_Thing_Projectile(int tid,AActor * source,int type,const char * type_name,angle_t angle,fixed_t speed,fixed_t vspeed,int dest,AActor * forcedest,int gravity,int newtid,bool leadTarget)173 bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_name, angle_t angle,
174 	fixed_t speed, fixed_t vspeed, int dest, AActor *forcedest, int gravity, int newtid,
175 	bool leadTarget)
176 {
177 	int rtn = 0;
178 	const PClass *kind;
179 	AActor *spot, *mobj, *targ = forcedest;
180 	FActorIterator iterator (tid);
181 	double fspeed = speed;
182 	int defflags3;
183 
184 	if (type_name == NULL)
185 	{
186 		kind = P_GetSpawnableType(type);
187 	}
188 	else
189 	{
190 		kind = PClass::FindClass(type_name);
191 	}
192 	if (kind == NULL || kind->ActorInfo == NULL)
193 	{
194 		return false;
195 	}
196 
197 	// Handle decorate replacements.
198 	kind = kind->GetReplacement();
199 
200 	defflags3 = GetDefaultByType (kind)->flags3;
201 	if ((defflags3 & MF3_ISMONSTER) &&
202 		((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS)))
203 		return false;
204 
205 	if (tid == 0)
206 	{
207 		spot = source;
208 	}
209 	else
210 	{
211 		spot = iterator.Next();
212 	}
213 	while (spot != NULL)
214 	{
215 		FActorIterator tit (dest);
216 
217 		if (dest == 0 || (targ = tit.Next()))
218 		{
219 			do
220 			{
221 				fixed_t z = spot->Z();
222 				if (defflags3 & MF3_FLOORHUGGER)
223 				{
224 					z = ONFLOORZ;
225 				}
226 				else if (defflags3 & MF3_CEILINGHUGGER)
227 				{
228 					z = ONCEILINGZ;
229 				}
230 				else if (z != ONFLOORZ)
231 				{
232 					z -= spot->floorclip;
233 				}
234 				mobj = Spawn (kind, spot->X(), spot->Y(), z, ALLOW_REPLACE);
235 
236 				if (mobj)
237 				{
238 					mobj->tid = newtid;
239 					mobj->AddToHash ();
240 					P_PlaySpawnSound(mobj, spot);
241 					if (gravity)
242 					{
243 						mobj->flags &= ~MF_NOGRAVITY;
244 						if (!(mobj->flags3 & MF3_ISMONSTER) && gravity == 1)
245 						{
246 							mobj->gravity = FRACUNIT/8;
247 						}
248 					}
249 					else
250 					{
251 						mobj->flags |= MF_NOGRAVITY;
252 					}
253 					mobj->target = spot;
254 
255 					if (targ != NULL)
256 					{
257 						fixedvec3 vect = mobj->Vec3To(targ);
258 						vect.z += targ->height / 2;
259 						TVector3<double> aim(vect.x, vect.y, vect.z);
260 
261 						if (leadTarget && speed > 0 && (targ->velx | targ->vely | targ->velz))
262 						{
263 							// Aiming at the target's position some time in the future
264 							// is basically just an application of the law of sines:
265 							//     a/sin(A) = b/sin(B)
266 							// Thanks to all those on the notgod phorum for helping me
267 							// with the math. I don't think I would have thought of using
268 							// trig alone had I been left to solve it by myself.
269 
270 							TVector3<double> tvel(targ->velx, targ->vely, targ->velz);
271 							if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3)
272 							{ // If the target is subject to gravity and not underwater,
273 							  // assume that it isn't moving vertically. Thanks to gravity,
274 							  // even if we did consider the vertical component of the target's
275 							  // velocity, we would still miss more often than not.
276 								tvel.Z = 0.0;
277 								if ((targ->velx | targ->vely) == 0)
278 								{
279 									goto nolead;
280 								}
281 							}
282 							double dist = aim.Length();
283 							double targspeed = tvel.Length();
284 							double ydotx = -aim | tvel;
285 							double a = acos (clamp (ydotx / targspeed / dist, -1.0, 1.0));
286 							double multiplier = double(pr_leadtarget.Random2())*0.1/255+1.1;
287 							double sinb = -clamp (targspeed*multiplier * sin(a) / fspeed, -1.0, 1.0);
288 
289 							// Use the cross product of two of the triangle's sides to get a
290 							// rotation vector.
291 							TVector3<double> rv(tvel ^ aim);
292 							// The vector must be normalized.
293 							rv.MakeUnit();
294 							// Now combine the rotation vector with angle b to get a rotation matrix.
295 							TMatrix3x3<double> rm(rv, cos(asin(sinb)), sinb);
296 							// And multiply the original aim vector with the matrix to get a
297 							// new aim vector that leads the target.
298 							TVector3<double> aimvec = rm * aim;
299 							// And make the projectile follow that vector at the desired speed.
300 							double aimscale = fspeed / dist;
301 							mobj->velx = fixed_t (aimvec[0] * aimscale);
302 							mobj->vely = fixed_t (aimvec[1] * aimscale);
303 							mobj->velz = fixed_t (aimvec[2] * aimscale);
304 							mobj->angle = R_PointToAngle2 (0, 0, mobj->velx, mobj->vely);
305 						}
306 						else
307 						{
308 nolead:
309 							mobj->angle = mobj->AngleTo(targ);
310 							aim.Resize (fspeed);
311 							mobj->velx = fixed_t(aim[0]);
312 							mobj->vely = fixed_t(aim[1]);
313 							mobj->velz = fixed_t(aim[2]);
314 						}
315 						if (mobj->flags2 & MF2_SEEKERMISSILE)
316 						{
317 							mobj->tracer = targ;
318 						}
319 					}
320 					else
321 					{
322 						mobj->angle = angle;
323 						mobj->velx = FixedMul (speed, finecosine[angle>>ANGLETOFINESHIFT]);
324 						mobj->vely = FixedMul (speed, finesine[angle>>ANGLETOFINESHIFT]);
325 						mobj->velz = vspeed;
326 					}
327 					// Set the missile's speed to reflect the speed it was spawned at.
328 					if (mobj->flags & MF_MISSILE)
329 					{
330 						mobj->Speed = fixed_t (sqrt (double(mobj->velx)*mobj->velx + double(mobj->vely)*mobj->vely + double(mobj->velz)*mobj->velz));
331 					}
332 					// Hugger missiles don't have any vertical velocity
333 					if (mobj->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))
334 					{
335 						mobj->velz = 0;
336 					}
337 					if (mobj->flags & MF_SPECIAL)
338 					{
339 						mobj->flags |= MF_DROPPED;
340 					}
341 					if (mobj->flags & MF_MISSILE)
342 					{
343 						if (P_CheckMissileSpawn (mobj, spot->radius))
344 						{
345 							rtn = true;
346 						}
347 					}
348 					else if (!P_TestMobjLocation (mobj))
349 					{
350 						// If this is a monster, subtract it from the total monster
351 						// count, because it already added to it during spawning.
352 						mobj->ClearCounters();
353 						mobj->Destroy ();
354 					}
355 					else
356 					{
357 						// It spawned fine.
358 						rtn = 1;
359 					}
360 				}
361 			} while (dest != 0 && (targ = tit.Next()));
362 		}
363 		spot = iterator.Next();
364 	}
365 
366 	return rtn != 0;
367 }
368 
P_Thing_Damage(int tid,AActor * whofor0,int amount,FName type)369 int P_Thing_Damage (int tid, AActor *whofor0, int amount, FName type)
370 {
371 	FActorIterator iterator (tid);
372 	int count = 0;
373 	AActor *actor;
374 
375 	actor = (tid == 0 ? whofor0 : iterator.Next());
376 	while (actor)
377 	{
378 		AActor *next = tid == 0 ? NULL : iterator.Next ();
379 		if (actor->flags & MF_SHOOTABLE)
380 		{
381 			if (amount > 0)
382 			{
383 				P_DamageMobj (actor, NULL, whofor0, amount, type);
384 			}
385 			else if (actor->health < actor->SpawnHealth())
386 			{
387 				actor->health -= amount;
388 				if (actor->health > actor->SpawnHealth())
389 				{
390 					actor->health = actor->SpawnHealth();
391 				}
392 				if (actor->player != NULL)
393 				{
394 					actor->player->health = actor->health;
395 				}
396 			}
397 			count++;
398 		}
399 		actor = next;
400 	}
401 	return count;
402 }
403 
P_RemoveThing(AActor * actor)404 void P_RemoveThing(AActor * actor)
405 {
406 	// Don't remove live players.
407 	if (actor->player == NULL || actor != actor->player->mo)
408 	{
409 		// Don't also remove owned inventory items
410 		if (actor->IsKindOf(RUNTIME_CLASS(AInventory)) && static_cast<AInventory*>(actor)->Owner != NULL) return;
411 
412 		// be friendly to the level statistics. ;)
413 		actor->ClearCounters();
414 		actor->Destroy ();
415 	}
416 
417 }
418 
P_Thing_Raise(AActor * thing,AActor * raiser)419 bool P_Thing_Raise(AActor *thing, AActor *raiser)
420 {
421 	FState * RaiseState = thing->GetRaiseState();
422 	if (RaiseState == NULL)
423 	{
424 		return true;	// monster doesn't have a raise state
425 	}
426 
427 	AActor *info = thing->GetDefault ();
428 
429 	thing->velx = thing->vely = 0;
430 
431 	// [RH] Check against real height and radius
432 	fixed_t oldheight = thing->height;
433 	fixed_t oldradius = thing->radius;
434 	ActorFlags oldflags = thing->flags;
435 
436 	thing->flags |= MF_SOLID;
437 	thing->height = info->height;	// [RH] Use real height
438 	thing->radius = info->radius;	// [RH] Use real radius
439 	if (!P_CheckPosition (thing, thing->Pos()))
440 	{
441 		thing->flags = oldflags;
442 		thing->radius = oldradius;
443 		thing->height = oldheight;
444 		return false;
445 	}
446 
447 
448 	S_Sound (thing, CHAN_BODY, "vile/raise", 1, ATTN_IDLE);
449 
450 	thing->Revive();
451 
452 	if (raiser != NULL)
453 	{
454 		// Let's copy the friendliness of the one who raised it.
455 		thing->CopyFriendliness(raiser, false);
456 	}
457 
458 	thing->SetState (RaiseState);
459 	return true;
460 }
461 
P_Thing_CanRaise(AActor * thing)462 bool P_Thing_CanRaise(AActor *thing)
463 {
464 	FState * RaiseState = thing->GetRaiseState();
465 	if (RaiseState == NULL)
466 	{
467 		return false;
468 	}
469 
470 	AActor *info = thing->GetDefault();
471 
472 	// Check against real height and radius
473 	ActorFlags oldflags = thing->flags;
474 	fixed_t oldheight = thing->height;
475 	fixed_t oldradius = thing->radius;
476 
477 	thing->flags |= MF_SOLID;
478 	thing->height = info->height;
479 	thing->radius = info->radius;
480 
481 	bool check = P_CheckPosition (thing, thing->Pos());
482 
483 	// Restore checked properties
484 	thing->flags = oldflags;
485 	thing->radius = oldradius;
486 	thing->height = oldheight;
487 
488 	if (!check)
489 	{
490 		return false;
491 	}
492 
493 	return true;
494 }
495 
P_Thing_SetVelocity(AActor * actor,fixed_t vx,fixed_t vy,fixed_t vz,bool add,bool setbob)496 void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool add, bool setbob)
497 {
498 	if (actor != NULL)
499 	{
500 		if (!add)
501 		{
502 			actor->velx = actor->vely = actor->velz = 0;
503 			if (actor->player != NULL) actor->player->velx = actor->player->vely = 0;
504 		}
505 		actor->velx += vx;
506 		actor->vely += vy;
507 		actor->velz += vz;
508 		if (setbob && actor->player != NULL)
509 		{
510 			actor->player->velx += vx;
511 			actor->player->vely += vy;
512 		}
513 	}
514 }
515 
P_GetSpawnableType(int spawnnum)516 const PClass *P_GetSpawnableType(int spawnnum)
517 {
518 	if (spawnnum < 0)
519 	{ // A named arg from a UDMF map
520 		FName spawnname = FName(ENamedName(-spawnnum));
521 		if (spawnname.IsValidName())
522 		{
523 			return PClass::FindClass(spawnname);
524 		}
525 	}
526 	else
527 	{ // A numbered arg from a Hexen or UDMF map
528 		const PClass **type = SpawnableThings.CheckKey(spawnnum);
529 		if (type != NULL)
530 		{
531 			return *type;
532 		}
533 	}
534 	return NULL;
535 }
536 
537 struct MapinfoSpawnItem
538 {
539 	FName classname;	// DECORATE is read after MAPINFO so we do not have the actual classes available here yet.
540 	// These are for error reporting. We must store the file information because it's no longer available when these items get resolved.
541 	FString filename;
542 	int linenum;
543 };
544 
545 typedef TMap<int, MapinfoSpawnItem> SpawnMap;
546 static SpawnMap SpawnablesFromMapinfo;
547 static SpawnMap ConversationIDsFromMapinfo;
548 
SpawnableSort(const void * a,const void * b)549 static int STACK_ARGS SpawnableSort(const void *a, const void *b)
550 {
551 	return (*((FClassMap::Pair **)a))->Key - (*((FClassMap::Pair **)b))->Key;
552 }
553 
DumpClassMap(FClassMap & themap)554 static void DumpClassMap(FClassMap &themap)
555 {
556 	FClassMap::Iterator it(themap);
557 	FClassMap::Pair *pair, **allpairs;
558 	int i = 0;
559 
560 	// Sort into numerical order, since their arrangement in the map can
561 	// be in an unspecified order.
562 	allpairs = new FClassMap::Pair *[themap.CountUsed()];
563 	while (it.NextPair(pair))
564 	{
565 		allpairs[i++] = pair;
566 	}
567 	qsort(allpairs, i, sizeof(*allpairs), SpawnableSort);
568 	for (int j = 0; j < i; ++j)
569 	{
570 		pair = allpairs[j];
571 		Printf ("%d %s\n", pair->Key, pair->Value->TypeName.GetChars());
572 	}
573 	delete[] allpairs;
574 }
575 
CCMD(dumpspawnables)576 CCMD(dumpspawnables)
577 {
578 	DumpClassMap(SpawnableThings);
579 }
580 
CCMD(dumpconversationids)581 CCMD (dumpconversationids)
582 {
583 	DumpClassMap(StrifeTypes);
584 }
585 
586 
ParseSpawnMap(FScanner & sc,SpawnMap & themap,const char * descript)587 static void ParseSpawnMap(FScanner &sc, SpawnMap & themap, const char *descript)
588 {
589 	TMap<int, bool> defined;
590 	int error = 0;
591 
592 	MapinfoSpawnItem editem;
593 
594 	editem.filename = sc.ScriptName;
595 
596 	while (true)
597 	{
598 		if (sc.CheckString("}")) return;
599 		else if (sc.CheckNumber())
600 		{
601 			int ednum = sc.Number;
602 			sc.MustGetStringName("=");
603 			sc.MustGetString();
604 
605 			bool *def = defined.CheckKey(ednum);
606 			if (def != NULL)
607 			{
608 				sc.ScriptMessage("%s %d defined more than once", descript, ednum);
609 				error++;
610 			}
611 			else if (ednum < 0)
612 			{
613 				sc.ScriptMessage("%s must be positive, got %d", descript, ednum);
614 				error++;
615 			}
616 			defined[ednum] = true;
617 			editem.classname = sc.String;
618 
619 			themap.Insert(ednum, editem);
620 		}
621 		else
622 		{
623 			sc.ScriptError("Number expected");
624 		}
625 	}
626 	if (error > 0)
627 	{
628 		sc.ScriptError("%d errors encountered in %s definition", error, descript);
629 	}
630 }
631 
ParseSpawnNums()632 void FMapInfoParser::ParseSpawnNums()
633 {
634 	ParseOpenBrace();
635 	ParseSpawnMap(sc, SpawnablesFromMapinfo, "Spawn number");
636 }
637 
ParseConversationIDs()638 void FMapInfoParser::ParseConversationIDs()
639 {
640 	ParseOpenBrace();
641 	ParseSpawnMap(sc, ConversationIDsFromMapinfo, "Conversation ID");
642 }
643 
644 
InitClassMap(FClassMap & themap,SpawnMap & thedata)645 void InitClassMap(FClassMap &themap, SpawnMap &thedata)
646 {
647 	themap.Clear();
648 	SpawnMap::Iterator it(thedata);
649 	SpawnMap::Pair *pair;
650 	int error = 0;
651 
652 	while (it.NextPair(pair))
653 	{
654 		const PClass *cls = NULL;
655 		if (pair->Value.classname != NAME_None)
656 		{
657 			cls = PClass::FindClass(pair->Value.classname);
658 			if (cls == NULL)
659 			{
660 				Printf(TEXTCOLOR_RED "Script error, \"%s\" line %d:\nUnknown actor class %s\n",
661 					pair->Value.filename.GetChars(), pair->Value.linenum, pair->Value.classname.GetChars());
662 				error++;
663 			}
664 			themap.Insert(pair->Key, cls);
665 		}
666 		else
667 		{
668 			themap.Remove(pair->Key);
669 		}
670 	}
671 	if (error > 0)
672 	{
673 		I_Error("%d unknown actor classes found", error);
674 	}
675 	thedata.Clear();	// we do not need this any longer
676 }
677 
InitSpawnablesFromMapinfo()678 void InitSpawnablesFromMapinfo()
679 {
680 	InitClassMap(SpawnableThings, SpawnablesFromMapinfo);
681 	InitClassMap(StrifeTypes, ConversationIDsFromMapinfo);
682 }
683 
684 
P_Thing_Warp(AActor * caller,AActor * reference,fixed_t xofs,fixed_t yofs,fixed_t zofs,angle_t angle,int flags,fixed_t heightoffset,fixed_t radiusoffset,angle_t pitch)685 int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t zofs, angle_t angle, int flags, fixed_t heightoffset, fixed_t radiusoffset, angle_t pitch)
686 {
687 	if (flags & WARPF_MOVEPTR)
688 	{
689 		AActor *temp = reference;
690 		reference = caller;
691 		caller = temp;
692 	}
693 
694 	fixedvec3 old = caller->Pos();
695 	zofs += FixedMul(reference->height, heightoffset);
696 
697 
698 	if (!(flags & WARPF_ABSOLUTEANGLE))
699 	{
700 		angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle;
701 	}
702 
703 	const fixed_t rad = FixedMul(radiusoffset, reference->radius);
704 	const angle_t fineangle = angle >> ANGLETOFINESHIFT;
705 
706 	if (!(flags & WARPF_ABSOLUTEPOSITION))
707 	{
708 		if (!(flags & WARPF_ABSOLUTEOFFSET))
709 		{
710 			fixed_t xofs1 = xofs;
711 
712 			// (borrowed from A_SpawnItemEx, assumed workable)
713 			// in relative mode negative y values mean 'left' and positive ones mean 'right'
714 			// This is the inverse orientation of the absolute mode!
715 
716 			xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]);
717 			yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]);
718 		}
719 
720 		if (flags & WARPF_TOFLOOR)
721 		{
722 			// set correct xy
723 			// now the caller's floorz should be appropriate for the assigned xy-position
724 			// assigning position again with.
725 			// extra unlink, link and environment calculation
726 			caller->SetOrigin(reference->Vec3Offset(
727 				xofs + FixedMul(rad, finecosine[fineangle]),
728 				yofs + FixedMul(rad, finesine[fineangle]),
729 				0), true);
730 			caller->SetZ(caller->floorz + zofs);
731 		}
732 		else
733 		{
734 			caller->SetOrigin(reference->Vec3Offset(
735 				 xofs + FixedMul(rad, finecosine[fineangle]),
736 				 yofs + FixedMul(rad, finesine[fineangle]),
737 				 zofs), true);
738 		}
739 	}
740 	else // [MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's.
741 	{
742 		if (flags & WARPF_TOFLOOR)
743 		{
744 			caller->SetOrigin(xofs + FixedMul(rad, finecosine[fineangle]), yofs + FixedMul(rad, finesine[fineangle]), zofs);
745 			caller->SetZ(caller->floorz + zofs);
746 		}
747 		else
748 		{
749 			caller->SetOrigin(xofs + FixedMul(rad, finecosine[fineangle]), yofs + FixedMul(rad, finesine[fineangle]), zofs);
750 		}
751 	}
752 
753 	if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller))
754 	{
755 		if (flags & WARPF_TESTONLY)
756 		{
757 			caller->SetOrigin(old, true);
758 		}
759 		else
760 		{
761 			caller->angle = angle;
762 
763 			if (flags & WARPF_COPYPITCH)
764 				caller->SetPitch(reference->pitch, false);
765 
766 			if (pitch)
767 				caller->SetPitch(caller->pitch + pitch, false);
768 
769 			if (flags & WARPF_COPYVELOCITY)
770 			{
771 				caller->velx = reference->velx;
772 				caller->vely = reference->vely;
773 				caller->velz = reference->velz;
774 			}
775 			if (flags & WARPF_STOP)
776 			{
777 				caller->velx = 0;
778 				caller->vely = 0;
779 				caller->velz = 0;
780 			}
781 
782 			if (flags & WARPF_WARPINTERPOLATION)
783 			{
784 				caller->PrevX += caller->X() - old.x;
785 				caller->PrevY += caller->Y() - old.y;
786 				caller->PrevZ += caller->Z() - old.z;
787 			}
788 			else if (flags & WARPF_COPYINTERPOLATION)
789 			{
790 				caller->PrevX = caller->X() + reference->PrevX - reference->X();
791 				caller->PrevY = caller->Y() + reference->PrevY - reference->Y();
792 				caller->PrevZ = caller->Z() + reference->PrevZ - reference->Z();
793 			}
794 			else if (!(flags & WARPF_INTERPOLATE))
795 			{
796 				caller->PrevX = caller->X();
797 				caller->PrevY = caller->Y();
798 				caller->PrevZ = caller->Z();
799 			}
800 			if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB))
801 			{
802 				caller->AddZ(reference->GetBobOffset());
803 			}
804 		}
805 		return true;
806 	}
807 	caller->SetOrigin(old, true);
808 	return false;
809 }