1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2006      by James Haley
4 // Copyright (C) 2006-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file  p_polyobj.c
11 /// \brief Movable segs like in Hexen, but more flexible
12 ///        due to application of dynamic binary space partitioning theory.
13 
14 // haleyjd: temporary define
15 
16 #include "z_zone.h"
17 
18 #include "doomstat.h"
19 #include "g_game.h"
20 #include "m_bbox.h"
21 #include "m_queue.h"
22 #include "p_maputl.h"
23 #include "p_setup.h"
24 #include "p_tick.h"
25 #include "p_local.h"
26 #include "p_polyobj.h"
27 #include "r_main.h"
28 #include "r_state.h"
29 #include "r_defs.h"
30 
31 /*
32    Theory behind Polyobjects:
33 
34       "The BSP tree hidden surface removal algorithm can easily be
35       extended to allow for dynamic objects. For each frame, start with
36       a BSP tree containing all the static objects in the scene, and
37       reinsert the dynamic objects. While this is straightforward to
38       implement, it can involve substantial computation.
39 
40       "If a dynamic object is separated from each static object by a
41       plane, the dynamic object can be represented as a single point
42       regardless of its complexity. This can dramatically reduce the
43       computation per frame because only one node per dynamic object is
44       inserted into the BSP tree. Compare that to one node for every
45       polygon in the object, and the reason for the savings is obvious.
46       During tree traversal, each point is expanded into the original
47       object...
48 
49       "Inserting a point into the BSP tree is very cheap, because there
50       is only one front/back test at each node. Points are never split,
51       which explains the requirement of separation by a plane. The
52       dynamic object will always be drawn completely in front of the
53       static objects behind it.
54 
55       "...a different front/back test is necessary, because a point
56       doesn't partition three dimesnional (sic) space. The correct
57       front/back test is to simply compare distances to the eye. Once
58       computed, this distance can be cached at the node until the frame
59       is drawn."
60 
61    From http://www.faqs.org/faqs/graphics/bsptree-faq/ (The BSP FAQ)
62 
63    While Hexen had polyobjects, it put severe and artificial limits upon
64    them by keeping them attached to one subsector, and allowing only one
65    per subsector. Neither is necessary, and removing those limitations
66    results in the free-moving polyobjects implemented here. The only
67    true - and unavoidable - restriction is that polyobjects should never
68    overlap with each other or with static walls.
69 
70    The reason that multiple polyobjects per subsector is viable is that
71    with the above assumption that the objects will not overlap, if the
72    center point of one polyobject is closer to the viewer than the center
73    point of another, then the entire polyobject is closer to the viewer.
74    In this way it is possible to impose an order on polyobjects within a
75    subsector, as well as allowing the BSP tree to impose its natural
76    ordering on polyobjects amongst all subsectors.
77 */
78 
79 //
80 // Defines
81 //
82 
83 #define BYTEANGLEMUL     (ANGLE_11hh/8)
84 
85 //
86 // Globals
87 //
88 
89 // The Polyobjects
90 polyobj_t *PolyObjects;
91 INT32 numPolyObjects;
92 
93 // Polyobject Blockmap -- initialized in P_LoadBlockMap
94 polymaplink_t **polyblocklinks;
95 
96 
97 //
98 // Static Data
99 //
100 
101 // Polyobject Blockmap
102 static polymaplink_t *bmap_freelist; // free list of blockmap links
103 
104 
105 //
106 // Static Functions
107 //
108 
Polyobj_bboxAdd(fixed_t * bbox,vertex_t * add)109 FUNCINLINE static ATTRINLINE void Polyobj_bboxAdd(fixed_t *bbox, vertex_t *add)
110 {
111 	bbox[BOXTOP]    += add->y;
112 	bbox[BOXBOTTOM] += add->y;
113 	bbox[BOXLEFT]   += add->x;
114 	bbox[BOXRIGHT]  += add->x;
115 }
116 
Polyobj_bboxSub(fixed_t * bbox,vertex_t * sub)117 FUNCINLINE static ATTRINLINE void Polyobj_bboxSub(fixed_t *bbox, vertex_t *sub)
118 {
119 	bbox[BOXTOP]    -= sub->y;
120 	bbox[BOXBOTTOM] -= sub->y;
121 	bbox[BOXLEFT]   -= sub->x;
122 	bbox[BOXRIGHT]  -= sub->x;
123 }
124 
Polyobj_vecAdd(vertex_t * dst,vertex_t * add)125 FUNCINLINE static ATTRINLINE void Polyobj_vecAdd(vertex_t *dst, vertex_t *add)
126 {
127 	dst->x += add->x;
128 	dst->y += add->y;
129 }
130 
Polyobj_vecSub(vertex_t * dst,vertex_t * sub)131 FUNCINLINE static ATTRINLINE void Polyobj_vecSub(vertex_t *dst, vertex_t *sub)
132 {
133 	dst->x -= sub->x;
134 	dst->y -= sub->y;
135 }
136 
Polyobj_vecSub2(vertex_t * dst,vertex_t * v1,vertex_t * v2)137 FUNCINLINE static ATTRINLINE void Polyobj_vecSub2(vertex_t *dst, vertex_t *v1, vertex_t *v2)
138 {
139 	dst->x = v1->x - v2->x;
140 	dst->y = v1->y - v2->y;
141 }
142 
P_PointInsidePolyobj(polyobj_t * po,fixed_t x,fixed_t y)143 boolean P_PointInsidePolyobj(polyobj_t *po, fixed_t x, fixed_t y)
144 {
145 	size_t i;
146 
147 	for (i = 0; i < po->numLines; i++)
148 	{
149 		if (P_PointOnLineSide(x, y, po->lines[i]) == 0)
150 			return false;
151 	}
152 
153 	return true;
154 }
155 
P_MobjTouchingPolyobj(polyobj_t * po,mobj_t * mo)156 boolean P_MobjTouchingPolyobj(polyobj_t *po, mobj_t *mo)
157 {
158 	fixed_t mbbox[4];
159 	size_t i;
160 
161 	mbbox[BOXTOP] = mo->y + mo->radius;
162 	mbbox[BOXBOTTOM] = mo->y - mo->radius;
163 	mbbox[BOXRIGHT] = mo->x + mo->radius;
164 	mbbox[BOXLEFT] = mo->x - mo->radius;
165 
166 	for (i = 0; i < po->numLines; i++)
167 	{
168 		if (P_BoxOnLineSide(mbbox, po->lines[i]) == -1)
169 			return true;
170 	}
171 
172 	return false;
173 }
174 
P_MobjInsidePolyobj(polyobj_t * po,mobj_t * mo)175 boolean P_MobjInsidePolyobj(polyobj_t *po, mobj_t *mo)
176 {
177 	fixed_t mbbox[4];
178 	size_t i;
179 
180 	mbbox[BOXTOP] = mo->y + mo->radius;
181 	mbbox[BOXBOTTOM] = mo->y - mo->radius;
182 	mbbox[BOXRIGHT] = mo->x + mo->radius;
183 	mbbox[BOXLEFT] = mo->x - mo->radius;
184 
185 	for (i = 0; i < po->numLines; i++)
186 	{
187 		if (P_BoxOnLineSide(mbbox, po->lines[i]) == 0)
188 			return false;
189 	}
190 
191 	return true;
192 }
193 
P_BBoxInsidePolyobj(polyobj_t * po,fixed_t * bbox)194 boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox)
195 {
196 	size_t i;
197 
198 	for (i = 0; i < po->numLines; i++)
199 	{
200 		if (P_BoxOnLineSide(bbox, po->lines[i]) == 0)
201 			return false;
202 	}
203 
204 	return true;
205 }
206 
207 // Gets the polyobject's settings from its first line
208 // args[0] of the first line should be the polyobject's id
Polyobj_GetInfo(polyobj_t * po,line_t * line)209 static void Polyobj_GetInfo(polyobj_t *po, line_t *line)
210 {
211 	po->parent = line->args[1];
212 	if (po->parent == po->id) // do not allow a self-reference
213 		po->parent = -1;
214 
215 	po->translucency = max(min(line->args[2], NUMTRANSMAPS), 0);
216 
217 	po->flags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES|POF_RENDERPLANES;
218 
219 	if (line->args[3] & TMPF_NOINSIDES)
220 		po->flags |= POF_ONESIDE;
221 
222 	if (line->args[3] & TMPF_INTANGIBLE)
223 		po->flags &= ~POF_SOLID;
224 
225 	if (line->args[3] & TMPF_PUSHABLESTOP)
226 		po->flags |= POF_PUSHABLESTOP;
227 
228 	if (line->args[3] & TMPF_INVISIBLEPLANES)
229 		po->flags &= ~POF_RENDERPLANES;
230 
231 	/*if (line->args[3] & TMPF_DONTCLIPPLANES)
232 		po->flags &= ~POF_CLIPPLANES;*/
233 
234 	if (line->args[3] & TMPF_SPLAT)
235 		po->flags |= POF_SPLAT;
236 
237 	if (line->args[3] & TMPF_EXECUTOR) // Has a linedef executor
238 		po->flags |= POF_LDEXEC;
239 
240 	// TODO: support customized damage somehow?
241 	if (line->args[3] & TMPF_CRUSH)
242 		po->damage = 3;
243 
244 	po->triggertag = line->args[4];
245 }
246 
247 // Reallocating array maintenance
248 
249 // Adds a vertex to a polyobject's reallocating vertex arrays, provided
250 // that such a vertex isn't already in the array. Each vertex must only
251 // be translated once during polyobject movement. Keeping track of them
252 // this way results in much more clear and efficient code than what
253 // Hexen used.
Polyobj_addVertex(polyobj_t * po,vertex_t * v)254 static void Polyobj_addVertex(polyobj_t *po, vertex_t *v)
255 {
256 	size_t i;
257 
258 	// First: search the existing vertex pointers for a match. If one is found,
259 	// do not add this vertex again.
260 	for (i = 0; i < po->numVertices; ++i)
261 	{
262 		if (po->vertices[i] == v)
263 			return;
264 	}
265 
266 	// add the vertex to all arrays (translation for origVerts is done later)
267 	if (po->numVertices >= po->numVerticesAlloc)
268 	{
269 		po->numVerticesAlloc = po->numVerticesAlloc ? po->numVerticesAlloc * 2 : 4;
270 		po->vertices =
271 			(vertex_t **)Z_Realloc(po->vertices,
272 			                       po->numVerticesAlloc * sizeof(vertex_t *),
273 			                       PU_LEVEL, NULL);
274 		po->origVerts =
275 			(vertex_t *)Z_Realloc(po->origVerts,
276 			                      po->numVerticesAlloc * sizeof(vertex_t),
277 			                      PU_LEVEL, NULL);
278 
279 		po->tmpVerts =
280 			(vertex_t *)Z_Realloc(po->tmpVerts,
281 			                      po->numVerticesAlloc * sizeof(vertex_t),
282 			                      PU_LEVEL, NULL);
283 	}
284 	po->vertices[po->numVertices] = v;
285 	po->origVerts[po->numVertices] = *v;
286 	po->numVertices++;
287 }
288 
289 // Adds a linedef to a polyobject's reallocating linedefs array, provided
290 // that such a linedef isn't already in the array. Each linedef must only
291 // be adjusted once during polyobject movement. Keeping track of them
292 // this way provides the same benefits as for vertices.
Polyobj_addLine(polyobj_t * po,line_t * l)293 static void Polyobj_addLine(polyobj_t *po, line_t *l)
294 {
295 	size_t i;
296 
297 	// First: search the existing line pointers for a match. If one is found,
298 	// do not add this line again.
299 	for (i = 0; i < po->numLines; ++i)
300 	{
301 		if (po->lines[i] == l)
302 			return;
303 	}
304 
305 	// add the line to the array
306 	if (po->numLines >= po->numLinesAlloc)
307 	{
308 		po->numLinesAlloc = po->numLinesAlloc ? po->numLinesAlloc * 2 : 4;
309 		po->lines = (line_t **)Z_Realloc(po->lines,
310 										po->numLinesAlloc * sizeof(line_t *),
311 										PU_LEVEL, NULL);
312 	}
313 	l->polyobj = po;
314 	po->lines[po->numLines++] = l;
315 }
316 
317 // Adds a single seg to a polyobject's reallocating seg pointer array.
318 // Most polyobjects will have between 4 and 16 segs, so the array size
319 // begins much smaller than usual. Calls Polyobj_addVertex and Polyobj_addLine
320 // to add those respective structures for this seg, as well.
Polyobj_addSeg(polyobj_t * po,seg_t * seg)321 static void Polyobj_addSeg(polyobj_t *po, seg_t *seg)
322 {
323 	if (po->segCount >= po->numSegsAlloc)
324 	{
325 		po->numSegsAlloc = po->numSegsAlloc ? po->numSegsAlloc * 2 : 4;
326 		po->segs = (seg_t **)Z_Realloc(po->segs,
327 										po->numSegsAlloc * sizeof(seg_t *),
328 										PU_LEVEL, NULL);
329 	}
330 
331 	seg->polyseg = po;
332 
333 	po->segs[po->segCount++] = seg;
334 
335 	// possibly add the lines and vertices for this seg. It may be technically
336 	// unnecessary to add the v2 vertex of segs, but this makes sure that even
337 	// erroneously open "explicit" segs will have both vertices added and will
338 	// reduce problems.
339 	Polyobj_addVertex(po, seg->v1);
340 	Polyobj_addVertex(po, seg->v2);
341 	Polyobj_addLine(po, seg->linedef);
342 }
343 
344 // Seg-finding functions
345 
346 // This method adds segs to a polyobject by following segs from vertex to
347 // vertex.  The process stops when the original starting point is reached
348 // or if a particular search ends unexpectedly (ie, the polyobject is not
349 // closed).
Polyobj_findSegs(polyobj_t * po,seg_t * seg)350 static void Polyobj_findSegs(polyobj_t *po, seg_t *seg)
351 {
352 	fixed_t startx, starty;
353 	size_t i;
354 	size_t s;
355 
356 	Polyobj_addSeg(po, seg);
357 
358 	if (!(po->flags & POF_ONESIDE))
359 	{
360 		// Find backfacings
361 		for (s = 0;  s < numsegs; s++)
362 		{
363 			size_t r;
364 
365 			if (segs[s].glseg)
366 				continue;
367 
368 			if (segs[s].linedef != seg->linedef)
369 				continue;
370 
371 			if (segs[s].side != 1)
372 				continue;
373 
374 			for (r = 0; r < po->segCount; r++)
375 			{
376 				if (po->segs[r] == &segs[s])
377 					break;
378 			}
379 
380 			if (r != po->segCount)
381 				continue;
382 
383 			segs[s].dontrenderme = true;
384 
385 			Polyobj_addSeg(po, &segs[s]);
386 		}
387 	}
388 
389 	// on first seg, save the initial vertex
390 	startx = seg->v1->x;
391 	starty = seg->v1->y;
392 
393 	// use goto instead of recursion for maximum efficiency - thanks to lament
394 newseg:
395 
396 	// terminal case: we have reached a seg where v2 is the same as v1 of the
397 	// initial seg
398 	if (seg->v2->x == startx && seg->v2->y == starty)
399 		return;
400 
401 	// search the segs for one whose starting vertex is equal to the current
402 	// seg's ending vertex.
403 	for (i = 0; i < numsegs; ++i)
404 	{
405 		size_t q;
406 
407 		if (segs[i].glseg)
408 			continue;
409 		if (segs[i].side != 0) // needs to be frontfacing
410 			continue;
411 		if (segs[i].v1->x != seg->v2->x)
412 			continue;
413 		if (segs[i].v1->y != seg->v2->y)
414 			continue;
415 
416 		// Make sure you didn't already add this seg...
417 		for (q = 0; q < po->segCount; q++)
418 		{
419 			if (po->segs[q] == &segs[i])
420 				break;
421 		}
422 
423 		if (q != po->segCount)
424 			continue;
425 
426 		// add the new seg and recurse
427 		Polyobj_addSeg(po, &segs[i]);
428 		seg = &segs[i];
429 
430 		if (!(po->flags & POF_ONESIDE))
431 		{
432 			// Find backfacings
433 			for (q = 0; q < numsegs; q++)
434 			{
435 				size_t r;
436 
437 				if (segs[q].glseg)
438 					continue;
439 				if (segs[q].linedef != segs[i].linedef)
440 					continue;
441 				if (segs[q].side != 1)
442 					continue;
443 
444 				for (r = 0; r < po->segCount; r++)
445 				{
446 					if (po->segs[r] == &segs[q])
447 						break;
448 				}
449 
450 				if (r != po->segCount)
451 					continue;
452 
453 				segs[q].dontrenderme = true;
454 				Polyobj_addSeg(po, &segs[q]);
455 			}
456 		}
457 
458 		goto newseg;
459 	}
460 
461 	// error: if we reach here, the seg search never found another seg to
462 	// continue the loop, and thus the polyobject is open. This isn't allowed.
463 	po->isBad = true;
464 	CONS_Debug(DBG_POLYOBJ, "Polyobject %d is not closed\n", po->id);
465 }
466 
467 // Setup functions
468 
Polyobj_spawnPolyObj(INT32 num,mobj_t * spawnSpot,INT32 id)469 static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
470 {
471 	size_t i;
472 	polyobj_t *po = &PolyObjects[num];
473 
474 	// don't spawn a polyobject more than once
475 	if (po->segCount)
476 	{
477 		CONS_Debug(DBG_POLYOBJ, "Polyobj %d has more than one spawn spot", po->id);
478 		return;
479 	}
480 
481 	po->id = id;
482 
483 	// set to default thrust; may be modified by attached thinkers
484 	// TODO: support customized thrust?
485 	po->thrust = FRACUNIT;
486 	po->spawnflags = po->flags = 0;
487 
488 	// Search segs for "line start" special with tag matching this
489 	// polyobject's id number. If found, iterate through segs which
490 	// share common vertices and record them into the polyobject.
491 	for (i = 0; i < numsegs; ++i)
492 	{
493 		seg_t *seg = &segs[i];
494 
495 		if (seg->glseg)
496 			continue;
497 
498 		if (seg->side != 0) // needs to be frontfacing
499 			continue;
500 
501 		if (seg->linedef->special != POLYOBJ_START_LINE)
502 			continue;
503 
504 		if (seg->linedef->args[0] != po->id)
505 			continue;
506 
507 		Polyobj_GetInfo(po, seg->linedef); // apply extra settings if they exist!
508 
509 		// save original flags and translucency to reference later for netgames!
510 		po->spawnflags = po->flags;
511 		po->spawntrans = po->translucency;
512 
513 		Polyobj_findSegs(po, seg);
514 		break;
515 	}
516 
517 	CONS_Debug(DBG_POLYOBJ, "PO ID: %d; Num verts: %s\n", po->id, sizeu1(po->numVertices));
518 
519 	// if an error occurred above, quit processing this object
520 	if (po->isBad)
521 		return;
522 
523 	// make sure array isn't empty
524 	if (po->segCount == 0)
525 	{
526 		po->isBad = true;
527 		CONS_Debug(DBG_POLYOBJ, "Polyobject %d is empty\n", po->id);
528 		return;
529 	}
530 
531 	// set the polyobject's spawn spot
532 	po->spawnSpot.x = spawnSpot->x;
533 	po->spawnSpot.y = spawnSpot->y;
534 
535 	// hash the polyobject by its numeric id
536 	if (Polyobj_GetForNum(po->id))
537 	{
538 		// bad polyobject due to id conflict
539 		po->isBad = true;
540 		CONS_Debug(DBG_POLYOBJ, "Polyobject id conflict: %d\n", id);
541 	}
542 	else
543 	{
544 		INT32 hashkey = po->id % numPolyObjects;
545 		po->next = PolyObjects[hashkey].first;
546 		PolyObjects[hashkey].first = num;
547 	}
548 }
549 
550 static void Polyobj_attachToSubsec(polyobj_t *po);
551 
552 // Translates the polyobject's vertices with respect to the difference between
553 // the anchor and spawn spots. Updates linedef bounding boxes as well.
Polyobj_moveToSpawnSpot(mapthing_t * anchor)554 static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
555 {
556 	polyobj_t *po;
557 	vertex_t  dist, sspot;
558 	size_t i;
559 	mtag_t tag = Tag_FGet(&anchor->tags);
560 
561 	if (!(po = Polyobj_GetForNum(tag)))
562 	{
563 		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", tag);
564 		return;
565 	}
566 
567 	// don't move any bad polyobject that may have gotten through
568 	if (po->isBad)
569 		return;
570 
571 	// don't move any polyobject more than once
572 	if (po->attached)
573 	{
574 		CONS_Debug(DBG_POLYOBJ, "Polyobj %d has more than one anchor\n", po->id);
575 		return;
576 	}
577 
578 	sspot.x = po->spawnSpot.x;
579 	sspot.y = po->spawnSpot.y;
580 
581 	// calculate distance from anchor to spawn spot
582 	dist.x = (anchor->x << FRACBITS) - sspot.x;
583 	dist.y = (anchor->y << FRACBITS) - sspot.y;
584 
585 	// update linedef bounding boxes
586 	for (i = 0; i < po->numLines; ++i)
587 		Polyobj_bboxSub(po->lines[i]->bbox, &dist);
588 
589 	// translate vertices and record original coordinates relative to spawn spot
590 	for (i = 0; i < po->numVertices; ++i)
591 	{
592 		Polyobj_vecSub(po->vertices[i], &dist);
593 
594 		Polyobj_vecSub2(&(po->origVerts[i]), po->vertices[i], &sspot);
595 	}
596 
597 	// attach to subsector
598 	Polyobj_attachToSubsec(po);
599 }
600 
601 // Attaches a polyobject to its appropriate subsector.
Polyobj_attachToSubsec(polyobj_t * po)602 static void Polyobj_attachToSubsec(polyobj_t *po)
603 {
604 	subsector_t  *ss;
605 	fixed_t center_x = 0, center_y = 0;
606 	fixed_t numVertices;
607 	size_t i;
608 
609 	// never attach a bad polyobject
610 	if (po->isBad)
611 		return;
612 
613 	numVertices = (fixed_t)(po->numVertices*FRACUNIT);
614 
615 	for (i = 0; i < po->numVertices; ++i)
616 	{
617 		center_x += FixedDiv(po->vertices[i]->x, numVertices);
618 		center_y += FixedDiv(po->vertices[i]->y, numVertices);
619 	}
620 
621 	po->centerPt.x = center_x;
622 	po->centerPt.y = center_y;
623 
624 	ss = R_PointInSubsector(po->centerPt.x, po->centerPt.y);
625 
626 	M_DLListInsert(&po->link, (mdllistitem_t **)(void *)(&ss->polyList));
627 
628 #ifdef R_LINKEDPORTALS
629 	// set spawnSpot's groupid for correct portal sound behavior
630 	po->spawnSpot.groupid = ss->sector->groupid;
631 #endif
632 
633 	po->attached = true;
634 }
635 
636 // Removes a polyobject from the subsector to which it is attached.
Polyobj_removeFromSubsec(polyobj_t * po)637 static void Polyobj_removeFromSubsec(polyobj_t *po)
638 {
639 	if (po->attached)
640 	{
641 		M_DLListRemove(&po->link);
642 		po->attached = false;
643 	}
644 }
645 
646 // Blockmap Functions
647 
648 // Retrieves a polymaplink object from the free list or creates a new one.
Polyobj_getLink(void)649 static polymaplink_t *Polyobj_getLink(void)
650 {
651 	polymaplink_t *l;
652 
653 	if (bmap_freelist)
654 	{
655 		l = bmap_freelist;
656 		bmap_freelist = (polymaplink_t *)(l->link.next);
657 	}
658 	else
659 	{
660 		l = Z_Malloc(sizeof(*l), PU_LEVEL, NULL);
661 		memset(l, 0, sizeof(*l));
662 	}
663 
664 	return l;
665 }
666 
667 // Puts a polymaplink object into the free list.
Polyobj_putLink(polymaplink_t * l)668 static void Polyobj_putLink(polymaplink_t *l)
669 {
670 	memset(l, 0, sizeof(*l));
671 	l->link.next = (mdllistitem_t *)bmap_freelist;
672 	bmap_freelist = l;
673 }
674 
675 // Inserts a polyobject into the polyobject blockmap. Unlike, mobj_t's,
676 // polyobjects need to be linked into every blockmap cell which their
677 // bounding box intersects. This ensures the accurate level of clipping
678 // which is present with linedefs but absent from most mobj interactions.
Polyobj_linkToBlockmap(polyobj_t * po)679 static void Polyobj_linkToBlockmap(polyobj_t *po)
680 {
681 	fixed_t *blockbox = po->blockbox;
682 	size_t i;
683 	fixed_t x, y;
684 
685 	// never link a bad polyobject or a polyobject already linked
686 	if (po->isBad || po->linked)
687 		return;
688 
689 	// 2/26/06: start line box with values of first vertex, not INT32_MIN/INT32_MAX
690 	blockbox[BOXLEFT]   = blockbox[BOXRIGHT] = po->vertices[0]->x;
691 	blockbox[BOXBOTTOM] = blockbox[BOXTOP]   = po->vertices[0]->y;
692 
693 	// add all vertices to the bounding box
694 	for (i = 1; i < po->numVertices; ++i)
695 		M_AddToBox(blockbox, po->vertices[i]->x, po->vertices[i]->y);
696 
697 	// adjust bounding box relative to blockmap
698 	blockbox[BOXRIGHT]  = (unsigned)(blockbox[BOXRIGHT]  - bmaporgx) >> MAPBLOCKSHIFT;
699 	blockbox[BOXLEFT]   = (unsigned)(blockbox[BOXLEFT]   - bmaporgx) >> MAPBLOCKSHIFT;
700 	blockbox[BOXTOP]    = (unsigned)(blockbox[BOXTOP]    - bmaporgy) >> MAPBLOCKSHIFT;
701 	blockbox[BOXBOTTOM] = (unsigned)(blockbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT;
702 
703 	// link polyobject to every block its bounding box intersects
704 	for (y = blockbox[BOXBOTTOM]; y <= blockbox[BOXTOP]; ++y)
705 	{
706 		for (x = blockbox[BOXLEFT]; x <= blockbox[BOXRIGHT]; ++x)
707 		{
708 			if (!(x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight))
709 			{
710 				polymaplink_t  *l = Polyobj_getLink();
711 
712 				l->po = po;
713 
714 				M_DLListInsert(&l->link,
715 							(mdllistitem_t **)(&polyblocklinks[y*bmapwidth + x]));
716 			}
717 		}
718 	}
719 
720 	po->linked = true;
721 }
722 
723 // Unlinks a polyobject from all blockmap cells it intersects and returns
724 // its polymaplink objects to the free list.
Polyobj_removeFromBlockmap(polyobj_t * po)725 static void Polyobj_removeFromBlockmap(polyobj_t *po)
726 {
727 	polymaplink_t *rover;
728 	fixed_t *blockbox = po->blockbox;
729 	INT32 x, y;
730 
731 	// don't bother trying to unlink one that's not linked
732 	if (!po->linked)
733 		return;
734 
735 	// search all cells the polyobject touches
736 	for (y = blockbox[BOXBOTTOM]; y <= blockbox[BOXTOP]; ++y)
737 	{
738 		for (x = blockbox[BOXLEFT]; x <= blockbox[BOXRIGHT]; ++x)
739 		{
740 			if (!(x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight))
741 			{
742 				rover = polyblocklinks[y * bmapwidth + x];
743 
744 				while (rover && rover->po != po)
745 					rover = (polymaplink_t *)(rover->link.next);
746 
747 				// polyobject not in this cell? go on to next.
748 				if (!rover)
749 					continue;
750 
751 				// remove this link from the blockmap and put it on the freelist
752 				M_DLListRemove(&rover->link);
753 				Polyobj_putLink(rover);
754 			}
755 		}
756 	}
757 
758 	po->linked = false;
759 }
760 
761 // Movement functions
762 
763 // A version of Lee's routine from p_maputl.c that accepts an mobj pointer
764 // argument instead of using tmthing. Returns true if the line isn't contacted
765 // and false otherwise.
Polyobj_untouched(line_t * ld,mobj_t * mo)766 static inline boolean Polyobj_untouched(line_t *ld, mobj_t *mo)
767 {
768 	fixed_t x, y, ptmbbox[4];
769 
770 	return
771 		(ptmbbox[BOXRIGHT]  = (x = mo->x) + mo->radius) <= ld->bbox[BOXLEFT]   ||
772 		(ptmbbox[BOXLEFT]   =           x - mo->radius) >= ld->bbox[BOXRIGHT]  ||
773 		(ptmbbox[BOXTOP]    = (y = mo->y) + mo->radius) <= ld->bbox[BOXBOTTOM] ||
774 		(ptmbbox[BOXBOTTOM] =           y - mo->radius) >= ld->bbox[BOXTOP]    ||
775 		P_BoxOnLineSide(ptmbbox, ld) != -1;
776 }
777 
778 // Inflicts thrust and possibly damage on a thing which has been found to be
779 // blocking the motion of a polyobject. The default thrust amount is only one
780 // unit, but the motion of the polyobject can be used to change this.
Polyobj_pushThing(polyobj_t * po,line_t * line,mobj_t * mo)781 static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
782 {
783 	angle_t lineangle;
784 	fixed_t momx, momy;
785 	vertex_t closest;
786 
787 	// calculate angle of line and subtract 90 degrees to get normal
788 	lineangle = R_PointToAngle2(0, 0, line->dx, line->dy) - ANGLE_90;
789 	lineangle >>= ANGLETOFINESHIFT;
790 	momx = FixedMul(po->thrust, FINECOSINE(lineangle));
791 	momy = FixedMul(po->thrust, FINESINE(lineangle));
792 	mo->momx += momx;
793 	mo->momy += momy;
794 
795 	// Prevent 'sticking'
796 	P_UnsetThingPosition(mo);
797 	P_ClosestPointOnLine(mo->x, mo->y, line, &closest);
798 	mo->x = closest.x + FixedMul(mo->radius, FINECOSINE(lineangle));
799 	mo->y = closest.y + FixedMul(mo->radius, FINESINE(lineangle));
800 	mo->x += momx;
801 	mo->y += momy;
802 	P_SetThingPosition(mo);
803 
804 	// if object doesn't fit at desired location, possibly hurt it
805 	if (po->damage && (mo->flags & MF_SHOOTABLE))
806 	{
807 		P_CheckPosition(mo, mo->x + momx, mo->y + momy);
808 		mo->floorz = tmfloorz;
809 		mo->ceilingz = tmceilingz;
810 		mo->floorrover = tmfloorrover;
811 		mo->ceilingrover = tmceilingrover;
812 	}
813 }
814 
815 // Moves an object resting on top of a polyobject by (x, y). Template function to make alteration easier.
Polyobj_slideThing(mobj_t * mo,fixed_t dx,fixed_t dy)816 static void Polyobj_slideThing(mobj_t *mo, fixed_t dx, fixed_t dy)
817 {
818 	if (mo->player) { // Finally this doesn't suck eggs -fickle
819 		fixed_t cdx, cdy;
820 
821 		cdx = FixedMul(dx, FRACUNIT-CARRYFACTOR);
822 		cdy = FixedMul(dy, FRACUNIT-CARRYFACTOR);
823 
824 		if (mo->player->onconveyor == 1)
825 		{
826 			mo->momx += cdx;
827 			mo->momy += cdy;
828 
829 			// Multiple slides in the same tic, somehow
830 			mo->player->cmomx += cdx;
831 			mo->player->cmomy += cdy;
832 		}
833 		else
834 		{
835 			if (mo->player->onconveyor == 3)
836 			{
837 				mo->momx += cdx - mo->player->cmomx;
838 				mo->momy += cdy - mo->player->cmomy;
839 			}
840 
841 			mo->player->cmomx = cdx;
842 			mo->player->cmomy = cdy;
843 		}
844 
845 		dx = FixedMul(dx, FRACUNIT - mo->friction);
846 		dy = FixedMul(dy, FRACUNIT - mo->friction);
847 
848 		if (mo->player->pflags & PF_SPINNING && (mo->player->rmomx || mo->player->rmomy) && !(mo->player->pflags & PF_STARTDASH)) {
849 #define SPINMULT 5184 // Consider this a substitute for properly calculating FRACUNIT-friction. I'm tired. -Red
850 			dx = FixedMul(dx, SPINMULT);
851 			dy = FixedMul(dy, SPINMULT);
852 #undef SPINMULT
853 		}
854 
855 		mo->momx += dx;
856 		mo->momy += dy;
857 
858 		mo->player->onconveyor = 1;
859 	} else
860 		P_TryMove(mo, mo->x+dx, mo->y+dy, true);
861 }
862 
863 // Causes objects resting on top of the polyobject to 'ride' with its movement.
Polyobj_carryThings(polyobj_t * po,fixed_t dx,fixed_t dy)864 static void Polyobj_carryThings(polyobj_t *po, fixed_t dx, fixed_t dy)
865 {
866 	static INT32 pomovecount = 0;
867 	INT32 x, y;
868 
869 	pomovecount++;
870 
871 	if (!(po->flags & POF_SOLID))
872 		return;
873 
874 	for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
875 	{
876 		for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
877 		{
878 			mobj_t *mo;
879 
880 			if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
881 				continue;
882 
883 			mo = blocklinks[y * bmapwidth + x];
884 
885 			for (; mo; mo = mo->bnext)
886 			{
887 				if (mo->lastlook == pomovecount)
888 					continue;
889 
890 				mo->lastlook = pomovecount;
891 
892 				// Don't scroll objects that aren't affected by gravity
893 				if (mo->flags & MF_NOGRAVITY)
894 					continue;
895 				// (The above check used to only move MF_SOLID objects, but that's inconsistent with conveyor behavior. -Red)
896 
897 				if (mo->flags & MF_NOCLIP)
898 					continue;
899 
900 				if ((mo->eflags & MFE_VERTICALFLIP) && mo->z + mo->height != po->lines[0]->backsector->floorheight)
901 					continue;
902 
903 				if (!(mo->eflags & MFE_VERTICALFLIP) && mo->z != po->lines[0]->backsector->ceilingheight)
904 					continue;
905 
906 				if (!P_MobjInsidePolyobj(po, mo))
907 					continue;
908 
909 				Polyobj_slideThing(mo, dx, dy);
910 			}
911 		}
912 	}
913 }
914 
915 // Checks for things that are in the way of a polyobject line move.
916 // Returns true if something was hit.
Polyobj_clipThings(polyobj_t * po,line_t * line)917 static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
918 {
919 	INT32 hitflags = 0;
920 	fixed_t linebox[4];
921 	INT32 x, y;
922 
923 	if (!(po->flags & POF_SOLID))
924 		return hitflags;
925 
926 	// adjust linedef bounding box to blockmap, extend by MAXRADIUS
927 	linebox[BOXLEFT]   = (unsigned)(line->bbox[BOXLEFT]   - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT;
928 	linebox[BOXRIGHT]  = (unsigned)(line->bbox[BOXRIGHT]  - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT;
929 	linebox[BOXBOTTOM] = (unsigned)(line->bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT;
930 	linebox[BOXTOP]    = (unsigned)(line->bbox[BOXTOP]    - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT;
931 
932 	// check all mobj blockmap cells the line contacts
933 	for (y = linebox[BOXBOTTOM]; y <= linebox[BOXTOP]; ++y)
934 	{
935 		for (x = linebox[BOXLEFT]; x <= linebox[BOXRIGHT]; ++x)
936 		{
937 			if (!(x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight))
938 			{
939 				mobj_t *mo = blocklinks[y * bmapwidth + x];
940 
941 				for (; mo; mo = mo->bnext)
942 				{
943 
944 					// Don't scroll objects that aren't affected by gravity
945 					if (mo->flags & MF_NOGRAVITY)
946 						continue;
947 					// (The above check used to only move MF_SOLID objects, but that's inconsistent with conveyor behavior. -Red)
948 
949 					if (mo->flags & MF_NOCLIP)
950 						continue;
951 
952 					if (mo->z + mo->height <= line->backsector->floorheight)
953 						continue;
954 
955 					if (mo->z >= line->backsector->ceilingheight)
956 						continue;
957 
958 					if (Polyobj_untouched(line, mo))
959 						continue;
960 
961 					if (mo->flags & MF_PUSHABLE && (po->flags & POF_PUSHABLESTOP))
962 						hitflags |= 2;
963 					else
964 						Polyobj_pushThing(po, line, mo);
965 
966 					if (mo->player && (po->lines[0]->backsector->flags & SF_TRIGGERSPECIAL_TOUCH) && !(po->flags & POF_NOSPECIALS))
967 						P_ProcessSpecialSector(mo->player, mo->subsector->sector, po->lines[0]->backsector);
968 
969 					hitflags |= 1;
970 				}
971 			} // end if
972 		} // end for (y)
973 	} // end for (x)
974 
975 	return hitflags;
976 }
977 
978 
979 // Moves a polyobject on the x-y plane.
Polyobj_moveXY(polyobj_t * po,fixed_t x,fixed_t y,boolean checkmobjs)980 boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs)
981 {
982 	size_t i;
983 	vertex_t vec;
984 	INT32 hitflags = 0;
985 
986 	vec.x = x;
987 	vec.y = y;
988 
989 	// don't move bad polyobjects
990 	if (po->isBad)
991 		return false;
992 
993 	// translate vertices
994 	for (i = 0; i < po->numVertices; ++i)
995 		Polyobj_vecAdd(po->vertices[i], &vec);
996 
997 	// translate each line
998 	for (i = 0; i < po->numLines; ++i)
999 		Polyobj_bboxAdd(po->lines[i]->bbox, &vec);
1000 
1001 	if (checkmobjs)
1002 	{
1003 		// check for blocking things (yes, it needs to be done separately)
1004 		for (i = 0; i < po->numLines; ++i)
1005 			hitflags |= Polyobj_clipThings(po, po->lines[i]);
1006 	}
1007 
1008 	if (hitflags & 2)
1009 	{
1010 		// reset vertices
1011 		for (i = 0; i < po->numVertices; ++i)
1012 			Polyobj_vecSub(po->vertices[i], &vec);
1013 
1014 		// reset lines that have been moved
1015 		for (i = 0; i < po->numLines; ++i)
1016 			Polyobj_bboxSub(po->lines[i]->bbox, &vec);
1017 	}
1018 	else
1019 	{
1020 		// translate the spawnSpot as well
1021 		po->spawnSpot.x += vec.x;
1022 		po->spawnSpot.y += vec.y;
1023 
1024 		if (checkmobjs)
1025 			Polyobj_carryThings(po, x, y);
1026 		Polyobj_removeFromBlockmap(po); // unlink it from the blockmap
1027 		Polyobj_removeFromSubsec(po);   // unlink it from its subsector
1028 		Polyobj_linkToBlockmap(po);     // relink to blockmap
1029 		Polyobj_attachToSubsec(po);     // relink to subsector
1030 	}
1031 
1032 	return !(hitflags & 2);
1033 }
1034 
1035 // Rotates a point and then translates it relative to point c.
1036 // The formula for this can be found here:
1037 // http://www.inversereality.org/tutorials/graphics%20programming/2dtransformations.html
1038 // It is, of course, just a vector-matrix multiplication.
Polyobj_rotatePoint(vertex_t * v,const vector2_t * c,angle_t ang)1039 static inline void Polyobj_rotatePoint(vertex_t *v, const vector2_t *c, angle_t ang)
1040 {
1041 	vertex_t tmp = *v;
1042 
1043 	v->x = FixedMul(tmp.x, FINECOSINE(ang)) - FixedMul(tmp.y,   FINESINE(ang));
1044 	v->y = FixedMul(tmp.x,   FINESINE(ang)) + FixedMul(tmp.y, FINECOSINE(ang));
1045 
1046 	v->x += c->x;
1047 	v->y += c->y;
1048 }
1049 
1050 // Taken from P_LoadLineDefs; simply updates the linedef's dx, dy, slopetype,
1051 // and bounding box to be consistent with its vertices.
Polyobj_rotateLine(line_t * ld)1052 static void Polyobj_rotateLine(line_t *ld)
1053 {
1054 	vertex_t *v1, *v2;
1055 
1056 	v1 = ld->v1;
1057 	v2 = ld->v2;
1058 
1059 	// set dx, dy
1060 	ld->dx = v2->x - v1->x;
1061 	ld->dy = v2->y - v1->y;
1062 
1063 	// determine slopetype
1064 	ld->slopetype = !ld->dx ? ST_VERTICAL : !ld->dy ? ST_HORIZONTAL :
1065 			((ld->dy > 0) == (ld->dx > 0)) ? ST_POSITIVE : ST_NEGATIVE;
1066 
1067 	// update bounding box
1068 	if (v1->x < v2->x)
1069 	{
1070 		ld->bbox[BOXLEFT]  = v1->x;
1071 		ld->bbox[BOXRIGHT] = v2->x;
1072 	}
1073 	else
1074 	{
1075 		ld->bbox[BOXLEFT]  = v2->x;
1076 		ld->bbox[BOXRIGHT] = v1->x;
1077 	}
1078 
1079 	if (v1->y < v2->y)
1080 	{
1081 		ld->bbox[BOXBOTTOM] = v1->y;
1082 		ld->bbox[BOXTOP]    = v2->y;
1083 	}
1084 	else
1085 	{
1086 		ld->bbox[BOXBOTTOM] = v2->y;
1087 		ld->bbox[BOXTOP]    = v1->y;
1088 	}
1089 }
1090 
1091 // Causes objects resting on top of the rotating polyobject to 'ride' with its movement.
Polyobj_rotateThings(polyobj_t * po,vector2_t origin,angle_t delta,UINT8 turnthings)1092 static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta, UINT8 turnthings)
1093 {
1094 	static INT32 pomovecount = 10000;
1095 	INT32 x, y;
1096 	angle_t deltafine = (((po->angle + delta) >> ANGLETOFINESHIFT) - (po->angle >> ANGLETOFINESHIFT)) & FINEMASK;
1097 	// This fineshift trickery replaces the old delta>>ANGLETOFINESHIFT; doing it this way avoids loss of precision causing objects to slide off -fickle
1098 
1099 	pomovecount++;
1100 
1101 	if (!(po->flags & POF_SOLID))
1102 		return;
1103 
1104 	for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
1105 	{
1106 		for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
1107 		{
1108 			mobj_t *mo;
1109 
1110 			if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
1111 				continue;
1112 
1113 			mo = blocklinks[y * bmapwidth + x];
1114 
1115 			for (; mo; mo = mo->bnext)
1116 			{
1117 				if (mo->lastlook == pomovecount)
1118 					continue;
1119 
1120 				mo->lastlook = pomovecount;
1121 
1122 				// Don't scroll objects that aren't affected by gravity
1123 				if (mo->flags & MF_NOGRAVITY)
1124 					continue;
1125 				// (The above check used to only move MF_SOLID objects, but that's inconsistent with conveyor behavior. -Red)
1126 
1127 				if (mo->flags & MF_NOCLIP)
1128 					continue;
1129 
1130 				if ((mo->eflags & MFE_VERTICALFLIP) && mo->z + mo->height != po->lines[0]->backsector->floorheight)
1131 					continue;
1132 
1133 				if (!(mo->eflags & MFE_VERTICALFLIP) && mo->z != po->lines[0]->backsector->ceilingheight)
1134 					continue;
1135 
1136 				if (!P_MobjInsidePolyobj(po, mo))
1137 					continue;
1138 
1139 				{
1140 					fixed_t oldxoff, oldyoff, newxoff, newyoff;
1141 					fixed_t c, s;
1142 
1143 					c = FINECOSINE(deltafine);
1144 					s = FINESINE(deltafine);
1145 
1146 					oldxoff = mo->x-origin.x;
1147 					oldyoff = mo->y-origin.y;
1148 
1149 					newxoff = FixedMul(oldxoff, c)-FixedMul(oldyoff, s) - oldxoff;
1150 					newyoff = FixedMul(oldyoff, c)+FixedMul(oldxoff, s) - oldyoff;
1151 
1152 					Polyobj_slideThing(mo, newxoff, newyoff);
1153 
1154 					if (turnthings == 2 || (turnthings == 1 && !mo->player)) {
1155 						mo->angle += delta;
1156 						if (mo->player)
1157 							P_SetPlayerAngle(mo->player, (angle_t)(mo->player->angleturn << 16) + delta);
1158 					}
1159 				}
1160 			}
1161 		}
1162 	}
1163 }
1164 
1165 // Rotates a polyobject around its start point.
Polyobj_rotate(polyobj_t * po,angle_t delta,UINT8 turnthings,boolean checkmobjs)1166 boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
1167 {
1168 	size_t i;
1169 	angle_t angle;
1170 	vector2_t origin;
1171 	INT32 hitflags = 0;
1172 
1173 	// don't move bad polyobjects
1174 	if (po->isBad)
1175 		return false;
1176 
1177 	angle = (po->angle + delta) >> ANGLETOFINESHIFT;
1178 
1179 	// point about which to rotate is the spawn spot
1180 	origin.x = po->spawnSpot.x;
1181 	origin.y = po->spawnSpot.y;
1182 
1183 	// save current positions and rotate all vertices
1184 	for (i = 0; i < po->numVertices; ++i)
1185 	{
1186 		po->tmpVerts[i] = *(po->vertices[i]);
1187 
1188 		// use original pts to rotate to new position
1189 		*(po->vertices[i]) = po->origVerts[i];
1190 
1191 		Polyobj_rotatePoint(po->vertices[i], &origin, angle);
1192 	}
1193 
1194 	// rotate lines
1195 	for (i = 0; i < po->numLines; ++i)
1196 		Polyobj_rotateLine(po->lines[i]);
1197 
1198 	if (checkmobjs)
1199 	{
1200 		// check for blocking things
1201 		for (i = 0; i < po->numLines; ++i)
1202 			hitflags |= Polyobj_clipThings(po, po->lines[i]);
1203 
1204 		Polyobj_rotateThings(po, origin, delta, turnthings);
1205 	}
1206 
1207 	if (hitflags & 2)
1208 	{
1209 		// reset vertices to previous positions
1210 		for (i = 0; i < po->numVertices; ++i)
1211 			*(po->vertices[i]) = po->tmpVerts[i];
1212 
1213 		// reset lines
1214 		for (i = 0; i < po->numLines; ++i)
1215 			Polyobj_rotateLine(po->lines[i]);
1216 	}
1217 	else
1218 	{
1219 		// update seg angles (used only by renderer)
1220 		for (i = 0; i < po->segCount; ++i)
1221 			po->segs[i]->angle += delta;
1222 
1223 		// update polyobject's angle
1224 		po->angle += delta;
1225 
1226 		Polyobj_removeFromBlockmap(po); // unlink it from the blockmap
1227 		Polyobj_removeFromSubsec(po);   // remove from subsector
1228 		Polyobj_linkToBlockmap(po);     // relink to blockmap
1229 		Polyobj_attachToSubsec(po);     // relink to subsector
1230 	}
1231 
1232 	return !(hitflags & 2);
1233 }
1234 
1235 //
1236 // Global Functions
1237 //
1238 
1239 // Retrieves a polyobject by its numeric id using hashing.
1240 // Returns NULL if no such polyobject exists.
Polyobj_GetForNum(INT32 id)1241 polyobj_t *Polyobj_GetForNum(INT32 id)
1242 {
1243 	INT32 curidx  = PolyObjects[id % numPolyObjects].first;
1244 
1245 	while (curidx != numPolyObjects && PolyObjects[curidx].id != id)
1246 		curidx = PolyObjects[curidx].next;
1247 
1248 	return curidx == numPolyObjects ? NULL : &PolyObjects[curidx];
1249 }
1250 
1251 
1252 // Retrieves the parenting polyobject if one exists. Returns NULL
1253 // otherwise.
1254 #if 0 //unused function
1255 static polyobj_t *Polyobj_GetParent(polyobj_t *po)
1256 {
1257 	return (po && po->parent != -1) ? Polyobj_GetForNum(po->parent) : NULL;
1258 }
1259 #endif
1260 
1261 // Iteratively retrieves the children POs of a parent,
1262 // sorta like P_FindSectorSpecialFromTag.
Polyobj_GetChild(polyobj_t * po,INT32 * start)1263 static polyobj_t *Polyobj_GetChild(polyobj_t *po, INT32 *start)
1264 {
1265 	for (; *start < numPolyObjects; (*start)++)
1266 	{
1267 		if (PolyObjects[*start].parent == po->id)
1268 			return &PolyObjects[(*start)++];
1269 	}
1270 
1271 	return NULL;
1272 }
1273 
1274 // structure used to queue up mobj pointers in Polyobj_InitLevel
1275 typedef struct mobjqitem_s
1276 {
1277 	mqueueitem_t mqitem;
1278 	mobj_t *mo;
1279 } mobjqitem_t;
1280 
1281 // Called at the beginning of each map after all other line and thing
1282 // processing is finished.
Polyobj_InitLevel(void)1283 void Polyobj_InitLevel(void)
1284 {
1285 	thinker_t   *th;
1286 	mqueue_t    spawnqueue;
1287 	mqueue_t    anchorqueue;
1288 	mobjqitem_t *qitem;
1289 	INT32 i, numAnchors = 0;
1290 	mobj_t *mo;
1291 
1292 	M_QueueInit(&spawnqueue);
1293 	M_QueueInit(&anchorqueue);
1294 
1295 	// get rid of values from previous level
1296 	// note: as with msecnodes, it is very important to clear out the blockmap
1297 	// node freelist, otherwise it may contain dangling pointers to old objects
1298 	PolyObjects    = NULL;
1299 	numPolyObjects = 0;
1300 	bmap_freelist  = NULL;
1301 
1302 	// run down the thinker list, count the number of spawn points, and save
1303 	// the mobj_t pointers on a queue for use below.
1304 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
1305 	{
1306 		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
1307 			continue;
1308 
1309 		mo = (mobj_t *)th;
1310 
1311 		if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM)
1312 		{
1313 			++numPolyObjects;
1314 
1315 			qitem = malloc(sizeof(mobjqitem_t));
1316 			memset(qitem, 0, sizeof(mobjqitem_t));
1317 			qitem->mo = mo;
1318 			M_QueueInsert(&(qitem->mqitem), &spawnqueue);
1319 		}
1320 		else if (mo->info->doomednum == POLYOBJ_ANCHOR_DOOMEDNUM)
1321 		{
1322 			++numAnchors;
1323 
1324 			qitem = malloc(sizeof(mobjqitem_t));
1325 			memset(qitem, 0, sizeof(mobjqitem_t));
1326 			qitem->mo = mo;
1327 			M_QueueInsert(&(qitem->mqitem), &anchorqueue);
1328 		}
1329 	}
1330 
1331 	if (numPolyObjects)
1332 	{
1333 		// allocate the PolyObjects array
1334 		PolyObjects = Z_Calloc(numPolyObjects * sizeof(polyobj_t),
1335 													PU_LEVEL, NULL);
1336 
1337 		// setup hash fields
1338 		for (i = 0; i < numPolyObjects; ++i)
1339 			PolyObjects[i].first = PolyObjects[i].next = numPolyObjects;
1340 
1341 		// setup polyobjects
1342 		for (i = 0; i < numPolyObjects; ++i)
1343 		{
1344 			qitem = (mobjqitem_t *)M_QueueIterator(&spawnqueue);
1345 
1346 			Polyobj_spawnPolyObj(i, qitem->mo, Tag_FGet(&qitem->mo->spawnpoint->tags));
1347 		}
1348 
1349 		// move polyobjects to spawn points
1350 		for (i = 0; i < numAnchors; ++i)
1351 		{
1352 			qitem = (mobjqitem_t *)M_QueueIterator(&anchorqueue);
1353 
1354 			Polyobj_moveToSpawnSpot((qitem->mo->spawnpoint));
1355 		}
1356 
1357 		// setup polyobject clipping
1358 		for (i = 0; i < numPolyObjects; ++i)
1359 			Polyobj_linkToBlockmap(&PolyObjects[i]);
1360 	}
1361 
1362 #if 0
1363 	// haleyjd 02/22/06: temporary debug
1364 	printf("DEBUG: numPolyObjects = %d\n", numPolyObjects);
1365 	for (i = 0; i < numPolyObjects; ++i)
1366 	{
1367 		INT32 j;
1368 		polyobj_t *po = &PolyObjects[i];
1369 
1370 		printf("polyobj %d:\n", i);
1371 		printf("id = %d, first = %d, next = %d\n", po->id, po->first, po->next);
1372 		printf("segCount = %d, numSegsAlloc = %d\n", po->segCount, po->numSegsAlloc);
1373 		for (j = 0; j < po->segCount; ++j)
1374 			printf("\tseg %d: %p\n", j, po->segs[j]);
1375 		printf("numVertices = %d, numVerticesAlloc = %d\n", po->numVertices, po->numVerticesAlloc);
1376 		for (j = 0; j < po->numVertices; ++j)
1377 		{
1378 			printf("\tvtx %d: (%d, %d) / orig: (%d, %d)\n",
1379 				j, po->vertices[j]->x>>FRACBITS, po->vertices[j]->y>>FRACBITS,
1380 				po->origVerts[j].x>>FRACBITS, po->origVerts[j].y>>FRACBITS);
1381 		}
1382 		printf("numLines = %d, numLinesAlloc = %d\n", po->numLines, po->numLinesAlloc);
1383 		for (j = 0; j < po->numLines; ++j)
1384 			printf("\tline %d: %p\n", j, po->lines[j]);
1385 		printf("spawnSpot = (%d, %d)\n", po->spawnSpot.x >> FRACBITS, po->spawnSpot.y >> FRACBITS);
1386 		printf("centerPt = (%d, %d)\n", po->centerPt.x >> FRACBITS, po->centerPt.y >> FRACBITS);
1387 		printf("attached = %d, linked = %d, validcount = %d, isBad = %d\n",
1388 			po->attached, po->linked, po->validcount, po->isBad);
1389 		printf("blockbox: [%d, %d, %d, %d]\n",
1390 			po->blockbox[BOXLEFT], po->blockbox[BOXRIGHT], po->blockbox[BOXBOTTOM],
1391 			po->blockbox[BOXTOP]);
1392 	}
1393 #endif
1394 
1395 	// done with mobj queues
1396 	M_QueueFree(&spawnqueue);
1397 	M_QueueFree(&anchorqueue);
1398 }
1399 
1400 // Called when a savegame is being loaded. Rotates and translates an
1401 // existing polyobject to its position when the game was saved.
1402 //
1403 // Monster Iestyn 05/04/19: Please do not interact with mobjs! You
1404 // can cause I_Error crashes that way, and all the important mobjs are
1405 // going to be deleted afterwards anyway.
1406 //
Polyobj_MoveOnLoad(polyobj_t * po,angle_t angle,fixed_t x,fixed_t y)1407 void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y)
1408 {
1409 	fixed_t dx, dy;
1410 
1411 	// first, rotate to the saved angle
1412 	Polyobj_rotate(po, angle, false, false);
1413 
1414 	// determine component distances to translate
1415 	dx = x - po->spawnSpot.x;
1416 	dy = y - po->spawnSpot.y;
1417 
1418 	// translate
1419 	Polyobj_moveXY(po, dx, dy, false);
1420 }
1421 
1422 // Thinker Functions
1423 
1424 // Thinker function for PolyObject rotation.
T_PolyObjRotate(polyrotate_t * th)1425 void T_PolyObjRotate(polyrotate_t *th)
1426 {
1427 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1428 
1429 	if (!po)
1430 #ifdef RANGECHECK
1431 		I_Error("T_PolyObjRotate: thinker has invalid id %d\n", th->polyObjNum);
1432 #else
1433 	{
1434 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotate: thinker with invalid id %d removed.\n", th->polyObjNum);
1435 		P_RemoveThinker(&th->thinker);
1436 		return;
1437 	}
1438 #endif
1439 
1440 	// check for displacement due to override and reattach when possible
1441 	if (po->thinker == NULL)
1442 	{
1443 		po->thinker = &th->thinker;
1444 
1445 		// reset polyobject's thrust
1446 		po->thrust = abs(th->speed) >> 8;
1447 		if (po->thrust < FRACUNIT)
1448 			po->thrust = FRACUNIT;
1449 		else if (po->thrust > 4*FRACUNIT)
1450 			po->thrust = 4*FRACUNIT;
1451 	}
1452 
1453 	// rotate by 'speed' angle per frame
1454 	// if distance == -1, this polyobject rotates perpetually
1455 	if (Polyobj_rotate(po, th->speed, th->turnobjs, true) && th->distance != -1)
1456 	{
1457 		INT32 avel = abs(th->speed);
1458 
1459 		// decrement distance by the amount it moved
1460 		th->distance -= avel;
1461 
1462 		// are we at or past the destination?
1463 		if (th->distance <= 0)
1464 		{
1465 			// remove thinker
1466 			if (po->thinker == &th->thinker)
1467 			{
1468 				po->thinker = NULL;
1469 				po->thrust = FRACUNIT;
1470 			}
1471 			P_RemoveThinker(&th->thinker);
1472 
1473 			// TODO: notify scripts
1474 			// TODO: sound sequence stop event
1475 		}
1476 		else if (th->distance < avel)
1477 		{
1478 			// we have less than one multiple of 'speed' left to go,
1479 			// so change the speed so that it doesn't pass the destination
1480 			th->speed = th->speed >= 0 ? th->distance : -th->distance;
1481 		}
1482 	}
1483 }
1484 
1485 // Calculates the speed components from the desired resultant velocity.
Polyobj_componentSpeed(INT32 resVel,INT32 angle,fixed_t * xVel,fixed_t * yVel)1486 FUNCINLINE static ATTRINLINE void Polyobj_componentSpeed(INT32 resVel, INT32 angle,
1487                                             fixed_t *xVel, fixed_t *yVel)
1488 {
1489 	if (angle == 0)
1490 	{
1491 		*xVel = resVel;
1492 		*yVel = 0;
1493 	}
1494 	else if (angle == (INT32)(ANGLE_90>>ANGLETOFINESHIFT))
1495 	{
1496 		*xVel = 0;
1497 		*yVel = resVel;
1498 	}
1499 	else
1500 	{
1501 		*xVel = FixedMul(resVel, FINECOSINE(angle));
1502 		*yVel = FixedMul(resVel,   FINESINE(angle));
1503 	}
1504 }
1505 
T_PolyObjMove(polymove_t * th)1506 void T_PolyObjMove(polymove_t *th)
1507 {
1508 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1509 
1510 	if (!po)
1511 #ifdef RANGECHECK
1512 		I_Error("T_PolyObjMove: thinker has invalid id %d\n", th->polyObjNum);
1513 #else
1514 	{
1515 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjMove: thinker with invalid id %d removed.\n", th->polyObjNum);
1516 		P_RemoveThinker(&th->thinker);
1517 		return;
1518 	}
1519 #endif
1520 
1521 	// check for displacement due to override and reattach when possible
1522 	if (po->thinker == NULL)
1523 	{
1524 		po->thinker = &th->thinker;
1525 
1526 		// reset polyobject's thrust
1527 		po->thrust = abs(th->speed) >> 3;
1528 		if (po->thrust < FRACUNIT)
1529 			po->thrust = FRACUNIT;
1530 		else if (po->thrust > 4*FRACUNIT)
1531 			po->thrust = 4*FRACUNIT;
1532 	}
1533 
1534 	// move the polyobject one step along its movement angle
1535 	if (Polyobj_moveXY(po, th->momx, th->momy, true))
1536 	{
1537 		INT32 avel = abs(th->speed);
1538 
1539 		// decrement distance by the amount it moved
1540 		th->distance -= avel;
1541 
1542 		// are we at or past the destination?
1543 		if (th->distance <= 0)
1544 		{
1545 			// remove thinker
1546 			if (po->thinker == &th->thinker)
1547 			{
1548 				po->thinker = NULL;
1549 				po->thrust = FRACUNIT;
1550 			}
1551 			P_RemoveThinker(&th->thinker);
1552 
1553 			// TODO: notify scripts
1554 			// TODO: sound sequence stop event
1555 		}
1556 		else if (th->distance < avel)
1557 		{
1558 			// we have less than one multiple of 'speed' left to go,
1559 			// so change the speed so that it doesn't pass the destination
1560 			th->speed = th->speed >= 0 ? th->distance : -th->distance;
1561 			Polyobj_componentSpeed(th->speed, th->angle, &th->momx, &th->momy);
1562 		}
1563 	}
1564 }
1565 
T_MovePolyObj(polyobj_t * po,fixed_t distx,fixed_t disty,fixed_t distz)1566 static void T_MovePolyObj(polyobj_t *po, fixed_t distx, fixed_t disty, fixed_t distz)
1567 {
1568 	polyobj_t *child;
1569 	INT32 start;
1570 
1571 	Polyobj_moveXY(po, distx, disty, true);
1572 	// TODO: use T_MovePlane
1573 	po->lines[0]->backsector->floorheight += distz;
1574 	po->lines[0]->backsector->ceilingheight += distz;
1575 	// Sal: Remember to check your sectors!
1576 	// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
1577 	//  updating objects in the front one too just added teleporting to ground bugs
1578 	P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
1579 	// Apply action to mirroring polyobjects as well
1580 	start = 0;
1581 	while ((child = Polyobj_GetChild(po, &start)))
1582 	{
1583 		if (child->isBad)
1584 			continue;
1585 
1586 		Polyobj_moveXY(child, distx, disty, true);
1587 		// TODO: use T_MovePlane
1588 		child->lines[0]->backsector->floorheight += distz;
1589 		child->lines[0]->backsector->ceilingheight += distz;
1590 		P_CheckSector(child->lines[0]->backsector, (boolean)(child->damage));
1591 	}
1592 }
1593 
T_PolyObjWaypoint(polywaypoint_t * th)1594 void T_PolyObjWaypoint(polywaypoint_t *th)
1595 {
1596 	mobj_t *target = NULL;
1597 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1598 	fixed_t speed = th->speed;
1599 
1600 	if (!po)
1601 #ifdef RANGECHECK
1602 		I_Error("T_PolyObjWaypoint: thinker has invalid id %d\n", th->polyObjNum);
1603 #else
1604 	{
1605 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjWaypoint: thinker with invalid id %d removed.", th->polyObjNum);
1606 		P_RemoveThinker(&th->thinker);
1607 		return;
1608 	}
1609 #endif
1610 
1611 	// check for displacement due to override and reattach when possible
1612 	if (!po->thinker)
1613 		po->thinker = &th->thinker;
1614 
1615 	target = waypoints[th->sequence][th->pointnum];
1616 
1617 	if (!target)
1618 	{
1619 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjWaypoint: Unable to find target waypoint!\n");
1620 		return;
1621 	}
1622 
1623 	// Move along the waypoint sequence until speed for the current tic is exhausted
1624 	while (speed > 0)
1625 	{
1626 		mobj_t *waypoint = NULL;
1627 		fixed_t pox, poy, poz;
1628 		fixed_t distx, disty, distz, dist;
1629 
1630 		// Current position of polyobject
1631 		pox = po->centerPt.x;
1632 		poy = po->centerPt.y;
1633 		poz = (po->lines[0]->backsector->floorheight + po->lines[0]->backsector->ceilingheight)/2;
1634 
1635 		// Calculate the distance between the polyobject and the waypoint
1636 		distx = target->x - pox;
1637 		disty = target->y - poy;
1638 		distz = target->z - poz;
1639 		dist = P_AproxDistance(P_AproxDistance(distx, disty), distz);
1640 
1641 		if (dist < 1)
1642 			dist = 1;
1643 
1644 		// Will the polyobject overshoot its target?
1645 		if (speed < dist)
1646 		{
1647 			// No. Move towards waypoint
1648 			fixed_t momx, momy, momz;
1649 
1650 			momx = FixedMul(FixedDiv(target->x - pox, dist), speed);
1651 			momy = FixedMul(FixedDiv(target->y - poy, dist), speed);
1652 			momz = FixedMul(FixedDiv(target->z - poz, dist), speed);
1653 			T_MovePolyObj(po, momx, momy, momz);
1654 			return;
1655 		}
1656 		else
1657 		{
1658 			// Yes. Teleport to waypoint and look for the next one
1659 			T_MovePolyObj(po, distx, disty, distz);
1660 
1661 			if (!th->stophere)
1662 			{
1663 				CONS_Debug(DBG_POLYOBJ, "Looking for next waypoint...\n");
1664 				waypoint = (th->direction == -1) ? P_GetPreviousWaypoint(target, false) : P_GetNextWaypoint(target, false);
1665 
1666 				if (!waypoint && th->returnbehavior == PWR_WRAP) // If specified, wrap waypoints
1667 				{
1668 					if (!th->continuous)
1669 					{
1670 						th->returnbehavior = PWR_STOP;
1671 						th->stophere = true;
1672 					}
1673 
1674 					waypoint = (th->direction == -1) ? P_GetLastWaypoint(th->sequence) : P_GetFirstWaypoint(th->sequence);
1675 				}
1676 				else if (!waypoint && th->returnbehavior == PWR_COMEBACK) // Come back to the start
1677 				{
1678 					th->direction = -th->direction;
1679 
1680 					if (!th->continuous)
1681 						th->returnbehavior = PWR_STOP;
1682 
1683 					waypoint = (th->direction == -1) ? P_GetPreviousWaypoint(target, false) : P_GetNextWaypoint(target, false);
1684 				}
1685 			}
1686 
1687 			if (waypoint)
1688 			{
1689 				CONS_Debug(DBG_POLYOBJ, "Found waypoint (sequence %d, number %d).\n", waypoint->threshold, waypoint->health);
1690 
1691 				target = waypoint;
1692 				th->pointnum = target->health;
1693 
1694 				// Calculate remaining speed
1695 				speed -= dist;
1696 			}
1697 			else
1698 			{
1699 				if (!th->stophere)
1700 					CONS_Debug(DBG_POLYOBJ, "Next waypoint not found!\n");
1701 
1702 				if (po->thinker == &th->thinker)
1703 					po->thinker = NULL;
1704 
1705 				P_RemoveThinker(&th->thinker);
1706 				return;
1707 			}
1708 		}
1709 	}
1710 }
1711 
T_PolyDoorSlide(polyslidedoor_t * th)1712 void T_PolyDoorSlide(polyslidedoor_t *th)
1713 {
1714 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1715 
1716 	if (!po)
1717 #ifdef RANGECHECK
1718 		I_Error("T_PolyDoorSlide: thinker has invalid id %d\n", th->polyObjNum);
1719 #else
1720 	{
1721 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSlide: thinker with invalid id %d removed.\n", th->polyObjNum);
1722 		P_RemoveThinker(&th->thinker);
1723 		return;
1724 	}
1725 #endif
1726 
1727 	// check for displacement due to override and reattach when possible
1728 	if (po->thinker == NULL)
1729 	{
1730 		po->thinker = &th->thinker;
1731 
1732 		// reset polyobject's thrust
1733 		po->thrust = abs(th->speed) >> 3;
1734 		if (po->thrust < FRACUNIT)
1735 			po->thrust = FRACUNIT;
1736 		else if (po->thrust > 4*FRACUNIT)
1737 			po->thrust = 4*FRACUNIT;
1738 	}
1739 
1740 	// count down wait period
1741 	if (th->delayCount)
1742 	{
1743 		if (--th->delayCount == 0)
1744 		{
1745 			; // TODO: start sound sequence event
1746 		}
1747 		return;
1748 	}
1749 
1750 	// move the polyobject one step along its movement angle
1751 	if (Polyobj_moveXY(po, th->momx, th->momy, true))
1752 	{
1753 		INT32 avel = abs(th->speed);
1754 
1755 		// decrement distance by the amount it moved
1756 		th->distance -= avel;
1757 
1758 
1759 		// are we at or past the destination?
1760 		if (th->distance <= 0)
1761 		{
1762 			// does it need to close?
1763 			if (!th->closing)
1764 			{
1765 				th->closing = true;
1766 
1767 				// reset distance and speed
1768 				th->distance = th->initDistance;
1769 				th->speed    = th->initSpeed;
1770 
1771 				// start delay
1772 				th->delayCount = th->delay;
1773 
1774 				// reverse angle
1775 				th->angle = th->revAngle;
1776 
1777 				// reset component speeds
1778 				Polyobj_componentSpeed(th->speed, th->angle, &th->momx, &th->momy);
1779 			}
1780 			else
1781 			{
1782 				// remove thinker
1783 				if (po->thinker == &th->thinker)
1784 				{
1785 					po->thinker = NULL;
1786 					po->thrust = FRACUNIT;
1787 				}
1788 				P_RemoveThinker(&th->thinker);
1789 				// TODO: notify scripts
1790 			}
1791 			// TODO: sound sequence stop event
1792 		}
1793 		else if (th->distance < avel)
1794 		{
1795 			// we have less than one multiple of 'speed' left to go,
1796 			// so change the speed so that it doesn't pass the
1797 			// destination
1798 			th->speed = th->speed >= 0
1799 				? th->distance : -th->distance;
1800 			Polyobj_componentSpeed(th->speed, th->angle,
1801 				&th->momx, &th->momy);
1802 		}
1803 	}
1804 	else if (th->closing && th->distance != th->initDistance)
1805 	{
1806 		// move was blocked, special handling required -- make it reopen
1807 		th->distance = th->initDistance - th->distance;
1808 		th->speed    = th->initSpeed;
1809 		th->angle    = th->initAngle;
1810 		Polyobj_componentSpeed(th->speed, th->angle,
1811 			&th->momx, &th->momy);
1812 		th->closing  = false;
1813 		// TODO: sound sequence start event
1814 	}
1815 }
1816 
T_PolyDoorSwing(polyswingdoor_t * th)1817 void T_PolyDoorSwing(polyswingdoor_t *th)
1818 {
1819 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1820 
1821 	if (!po)
1822 #ifdef RANGECHECK
1823 		I_Error("T_PolyDoorSwing: thinker has invalid id %d\n", th->polyObjNum);
1824 #else
1825 	{
1826 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSwing: thinker with invalid id %d removed.\n", th->polyObjNum);
1827 		P_RemoveThinker(&th->thinker);
1828 		return;
1829 	}
1830 #endif
1831 
1832 	// check for displacement due to override and reattach when possible
1833 	if (po->thinker == NULL)
1834 	{
1835 		po->thinker = &th->thinker;
1836 
1837 		// reset polyobject's thrust
1838 		po->thrust = abs(th->speed) >> 3;
1839 		if (po->thrust < FRACUNIT)
1840 			po->thrust = FRACUNIT;
1841 		else if (po->thrust > 4*FRACUNIT)
1842 			po->thrust = 4*FRACUNIT;
1843 	}
1844 
1845 	// count down wait period
1846 	if (th->delayCount)
1847 	{
1848 		if (--th->delayCount == 0)
1849 		{
1850 			; // TODO: start sound sequence event
1851 		}
1852 		return;
1853 	}
1854 
1855 	// rotate by 'speed' angle per frame
1856 	// if distance == -1, this polyobject rotates perpetually
1857 	if (Polyobj_rotate(po, th->speed, false, true) && th->distance != -1)
1858 	{
1859 		INT32 avel = abs(th->speed);
1860 
1861 		// decrement distance by the amount it moved
1862 		th->distance -= avel;
1863 
1864 		// are we at or past the destination?
1865 		if (th->distance <= 0)
1866 		{
1867 			// does it need to close?
1868 			if (!th->closing)
1869 			{
1870 				th->closing = true;
1871 
1872 				// reset distance and speed
1873 				th->distance =  th->initDistance;
1874 				th->speed    = -th->initSpeed; // reverse speed on close
1875 
1876 				// start delay
1877 				th->delayCount = th->delay;
1878 			}
1879 			else
1880 			{
1881 				// remove thinker
1882 				if (po->thinker == &th->thinker)
1883 				{
1884 					po->thinker = NULL;
1885 					po->thrust = FRACUNIT;
1886 				}
1887 				P_RemoveThinker(&th->thinker);
1888 				// TODO: notify scripts
1889 			}
1890 			// TODO: sound sequence stop event
1891 		}
1892 		else if (th->distance < avel)
1893 		{
1894 			// we have less than one multiple of 'speed' left to go,
1895 			// so change the speed so that it doesn't pass the
1896 			// destination
1897 			th->speed = th->speed >= 0
1898 				? th->distance : -th->distance;
1899 		}
1900 	}
1901 	else if (th->closing && th->distance != th->initDistance)
1902 	{
1903 		// move was blocked, special handling required -- make it reopen
1904 
1905 		th->distance = th->initDistance - th->distance;
1906 		th->speed    = th->initSpeed;
1907 		th->closing  = false;
1908 
1909 		// TODO: sound sequence start event
1910 	}
1911 }
1912 
1913 // Shift a polyobject based on a control sector's heights.
T_PolyObjDisplace(polydisplace_t * th)1914 void T_PolyObjDisplace(polydisplace_t *th)
1915 {
1916 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1917 	fixed_t newheights, delta;
1918 	fixed_t dx, dy;
1919 
1920 	if (!po)
1921 #ifdef RANGECHECK
1922 		I_Error("T_PolyObjDisplace: thinker has invalid id %d\n", th->polyObjNum);
1923 #else
1924 	{
1925 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
1926 		P_RemoveThinker(&th->thinker);
1927 		return;
1928 	}
1929 #endif
1930 
1931 	// check for displacement due to override and reattach when possible
1932 	if (po->thinker == NULL)
1933 	{
1934 		po->thinker = &th->thinker;
1935 
1936 		// reset polyobject's thrust
1937 		po->thrust = FRACUNIT;
1938 	}
1939 
1940 	newheights = th->controlSector->floorheight+th->controlSector->ceilingheight;
1941 	delta = newheights-th->oldHeights;
1942 
1943 	if (!delta)
1944 		return;
1945 
1946 	dx = FixedMul(th->dx, delta);
1947 	dy = FixedMul(th->dy, delta);
1948 
1949 	if (Polyobj_moveXY(po, dx, dy, true))
1950 		th->oldHeights = newheights;
1951 }
1952 
1953 // Rotate a polyobject based on a control sector's heights.
T_PolyObjRotDisplace(polyrotdisplace_t * th)1954 void T_PolyObjRotDisplace(polyrotdisplace_t *th)
1955 {
1956 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
1957 	fixed_t newheights, delta;
1958 	fixed_t rotangle;
1959 
1960 	if (!po)
1961 #ifdef RANGECHECK
1962 		I_Error("T_PolyObjRotDisplace: thinker has invalid id %d\n", th->polyObjNum);
1963 #else
1964 	{
1965 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
1966 		P_RemoveThinker(&th->thinker);
1967 		return;
1968 	}
1969 #endif
1970 
1971 	// check for displacement due to override and reattach when possible
1972 	if (po->thinker == NULL)
1973 	{
1974 		po->thinker = &th->thinker;
1975 
1976 		// reset polyobject's thrust
1977 		po->thrust = FRACUNIT;
1978 	}
1979 
1980 	newheights = th->controlSector->floorheight+th->controlSector->ceilingheight;
1981 	delta = newheights-th->oldHeights;
1982 
1983 	if (!delta)
1984 		return;
1985 
1986 	rotangle = FixedMul(th->rotscale, delta);
1987 
1988 	if (Polyobj_rotate(po, FixedAngle(rotangle), th->turnobjs, true))
1989 		th->oldHeights = newheights;
1990 }
1991 
Polyobj_AngSpeed(INT32 speed)1992 static inline INT32 Polyobj_AngSpeed(INT32 speed)
1993 {
1994 	return (speed*ANG1)>>3; // no FixedAngle()
1995 }
1996 
1997 // Linedef Handlers
1998 
EV_DoPolyObjRotate(polyrotdata_t * prdata)1999 boolean EV_DoPolyObjRotate(polyrotdata_t *prdata)
2000 {
2001 	polyobj_t *po;
2002 	polyobj_t *oldpo;
2003 	polyrotate_t *th;
2004 	INT32 start;
2005 
2006 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
2007 	{
2008 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
2009 		return false;
2010 	}
2011 
2012 	// don't allow line actions to affect bad polyobjects
2013 	if (po->isBad)
2014 		return false;
2015 
2016 	// check for override if this polyobj already has a thinker
2017 	if (po->thinker && !prdata->overRide)
2018 		return false;
2019 
2020 	// create a new thinker
2021 	th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL);
2022 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotate;
2023 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2024 	po->thinker = &th->thinker;
2025 
2026 	// set fields
2027 	th->polyObjNum = prdata->polyObjNum;
2028 
2029 	// use Hexen-style byte angles for speed and distance
2030 	th->speed = Polyobj_AngSpeed(prdata->speed * prdata->direction);
2031 
2032 	if (prdata->distance == 360)    // 360 means perpetual
2033 		th->distance = -1;
2034 	else if (prdata->distance == 0) // 0 means 360 degrees
2035 		th->distance = 0xffffffff - 1;
2036 	else
2037 		th->distance = FixedAngle(prdata->distance*FRACUNIT);
2038 
2039 	// set polyobject's thrust
2040 	po->thrust = abs(th->speed) >> 8;
2041 	if (po->thrust < FRACUNIT)
2042 		po->thrust = FRACUNIT;
2043 	else if (po->thrust > 4*FRACUNIT)
2044 		po->thrust = 4*FRACUNIT;
2045 
2046 	// TODO: start sound sequence event
2047 
2048 	oldpo = po;
2049 
2050 	th->turnobjs = prdata->turnobjs;
2051 
2052 	// apply action to mirroring polyobjects as well
2053 	start = 0;
2054 	while ((po = Polyobj_GetChild(oldpo, &start)))
2055 	{
2056 		prdata->polyObjNum = po->id; // change id to match child polyobject's
2057 		EV_DoPolyObjRotate(prdata);
2058 	}
2059 
2060 	// action was successful
2061 	return true;
2062 }
2063 
EV_DoPolyObjMove(polymovedata_t * pmdata)2064 boolean EV_DoPolyObjMove(polymovedata_t *pmdata)
2065 {
2066 	polyobj_t *po;
2067 	polyobj_t *oldpo;
2068 	polymove_t *th;
2069 	INT32 start;
2070 
2071 	if (!(po = Polyobj_GetForNum(pmdata->polyObjNum)))
2072 	{
2073 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjMove: bad polyobj %d\n", pmdata->polyObjNum);
2074 		return false;
2075 	}
2076 
2077 	// don't allow line actions to affect bad polyobjects
2078 	if (po->isBad)
2079 		return false;
2080 
2081 	// check for override if this polyobj already has a thinker
2082 	if (po->thinker && !pmdata->overRide)
2083 		return false;
2084 
2085 	// create a new thinker
2086 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
2087 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjMove;
2088 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2089 	po->thinker = &th->thinker;
2090 
2091 	// set fields
2092 	th->polyObjNum = pmdata->polyObjNum;
2093 	th->distance   = pmdata->distance;
2094 	th->speed      = pmdata->speed;
2095 	th->angle      = pmdata->angle >> ANGLETOFINESHIFT;
2096 
2097 	// set component speeds
2098 	Polyobj_componentSpeed(th->speed, th->angle, &th->momx, &th->momy);
2099 
2100 	// set polyobject's thrust
2101 	po->thrust = abs(th->speed) >> 3;
2102 	if (po->thrust < FRACUNIT)
2103 		po->thrust = FRACUNIT;
2104 	else if (po->thrust > 4*FRACUNIT)
2105 		po->thrust = 4*FRACUNIT;
2106 
2107 	// TODO: start sound sequence event
2108 
2109 	oldpo = po;
2110 
2111 	// apply action to mirroring polyobjects as well
2112 	start = 0;
2113 	while ((po = Polyobj_GetChild(oldpo, &start)))
2114 	{
2115 		pmdata->polyObjNum = po->id; // change id to match child polyobject's
2116 		EV_DoPolyObjMove(pmdata);
2117 	}
2118 
2119 	// action was successful
2120 	return true;
2121 }
2122 
EV_DoPolyObjWaypoint(polywaypointdata_t * pwdata)2123 boolean EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
2124 {
2125 	polyobj_t *po;
2126 	polywaypoint_t *th;
2127 	mobj_t *first = NULL;
2128 
2129 	if (!(po = Polyobj_GetForNum(pwdata->polyObjNum)))
2130 	{
2131 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: bad polyobj %d\n", pwdata->polyObjNum);
2132 		return false;
2133 	}
2134 
2135 	// don't allow line actions to affect bad polyobjects
2136 	if (po->isBad)
2137 		return false;
2138 
2139 	if (po->thinker) // Don't crowd out another thinker.
2140 		return false;
2141 
2142 	// create a new thinker
2143 	th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL);
2144 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjWaypoint;
2145 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2146 	po->thinker = &th->thinker;
2147 
2148 	// set fields
2149 	th->polyObjNum = pwdata->polyObjNum;
2150 	th->speed = pwdata->speed;
2151 	th->sequence = pwdata->sequence;
2152 	th->direction = (pwdata->flags & PWF_REVERSE) ? -1 : 1;
2153 
2154 	th->returnbehavior = pwdata->returnbehavior;
2155 	if (pwdata->flags & PWF_LOOP)
2156 		th->continuous = true;
2157 	th->stophere = false;
2158 
2159 	// Find the first waypoint we need to use
2160 	first = (th->direction == -1) ? P_GetLastWaypoint(th->sequence) : P_GetFirstWaypoint(th->sequence);
2161 
2162 	if (!first)
2163 	{
2164 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: Missing starting waypoint!\n");
2165 		po->thinker = NULL;
2166 		P_RemoveThinker(&th->thinker);
2167 		return false;
2168 	}
2169 
2170 	// Sanity check: If all waypoints are in the same location,
2171 	// don't allow the movement to be continuous so we don't get stuck in an infinite loop.
2172 	if (th->continuous && P_IsDegeneratedWaypointSequence(th->sequence))
2173 	{
2174 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: All waypoints are in the same location!\n");
2175 		th->continuous = false;
2176 	}
2177 
2178 	th->pointnum = first->health;
2179 
2180 	return true;
2181 }
2182 
Polyobj_doSlideDoor(polyobj_t * po,polydoordata_t * doordata)2183 static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata)
2184 {
2185 	polyslidedoor_t *th;
2186 	polyobj_t *oldpo;
2187 	angle_t angtemp;
2188 	INT32 start;
2189 
2190 	// allocate and add a new slide door thinker
2191 	th = Z_Malloc(sizeof(polyslidedoor_t), PU_LEVSPEC, NULL);
2192 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSlide;
2193 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2194 
2195 	// point the polyobject to this thinker
2196 	po->thinker = &th->thinker;
2197 
2198 	// setup fields of the thinker
2199 	th->polyObjNum = po->id;
2200 	th->closing = false;
2201 	th->delay = doordata->delay;
2202 	th->delayCount = 0;
2203 	th->distance = th->initDistance = doordata->distance;
2204 	th->speed = th->initSpeed = doordata->speed;
2205 
2206 	// haleyjd: do angle reverse calculation in full precision to avoid
2207 	// drift due to ANGLETOFINESHIFT.
2208 	angtemp       = doordata->angle;
2209 	th->angle     = angtemp >> ANGLETOFINESHIFT;
2210 	th->initAngle = th->angle;
2211 	th->revAngle  = (angtemp + ANGLE_180) >> ANGLETOFINESHIFT;
2212 
2213 	Polyobj_componentSpeed(th->speed, th->angle, &th->momx, &th->momy);
2214 
2215 	// set polyobject's thrust
2216 	po->thrust = abs(th->speed) >> 3;
2217 	if (po->thrust < FRACUNIT)
2218 		po->thrust = FRACUNIT;
2219 	else if (po->thrust > 4*FRACUNIT)
2220 		po->thrust = 4*FRACUNIT;
2221 
2222 	// TODO: sound sequence start event
2223 
2224 	oldpo = po;
2225 
2226 	// start action on mirroring polyobjects as well
2227 	start = 0;
2228 	while ((po = Polyobj_GetChild(oldpo, &start)))
2229 		Polyobj_doSlideDoor(po, doordata);
2230 }
2231 
Polyobj_doSwingDoor(polyobj_t * po,polydoordata_t * doordata)2232 static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata)
2233 {
2234 	polyswingdoor_t *th;
2235 	polyobj_t *oldpo;
2236 	INT32 start;
2237 
2238 	// allocate and add a new swing door thinker
2239 	th = Z_Malloc(sizeof(polyswingdoor_t), PU_LEVSPEC, NULL);
2240 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSwing;
2241 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2242 
2243 	// point the polyobject to this thinker
2244 	po->thinker = &th->thinker;
2245 
2246 	// setup fields of the thinker
2247 	th->polyObjNum   = po->id;
2248 	th->closing      = false;
2249 	th->delay        = doordata->delay;
2250 	th->delayCount   = 0;
2251 	th->distance     = th->initDistance = FixedAngle(doordata->distance*FRACUNIT);
2252 	th->speed        = Polyobj_AngSpeed(doordata->speed);
2253 	th->initSpeed    = th->speed;
2254 
2255 	// set polyobject's thrust
2256 	po->thrust = abs(th->speed) >> 3;
2257 	if (po->thrust < FRACUNIT)
2258 		po->thrust = FRACUNIT;
2259 	else if (po->thrust > 4*FRACUNIT)
2260 		po->thrust = 4*FRACUNIT;
2261 
2262 	// TODO: sound sequence start event
2263 
2264 	oldpo = po;
2265 
2266 	// start action on mirroring polyobjects as well
2267 	start = 0;
2268 	while ((po = Polyobj_GetChild(oldpo, &start)))
2269 		Polyobj_doSwingDoor(po, doordata);
2270 }
2271 
EV_DoPolyDoor(polydoordata_t * doordata)2272 boolean EV_DoPolyDoor(polydoordata_t *doordata)
2273 {
2274 	polyobj_t *po;
2275 
2276 	if (!(po = Polyobj_GetForNum(doordata->polyObjNum)))
2277 	{
2278 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyDoor: bad polyobj %d\n", doordata->polyObjNum);
2279 		return false;
2280 	}
2281 
2282 	// don't allow line actions to affect bad polyobjects;
2283 	// polyobject doors don't allow action overrides
2284 	if (po->isBad || po->thinker)
2285 		return false;
2286 
2287 	switch (doordata->doorType)
2288 	{
2289 	case POLY_DOOR_SLIDE:
2290 		Polyobj_doSlideDoor(po, doordata);
2291 		break;
2292 	case POLY_DOOR_SWING:
2293 		Polyobj_doSwingDoor(po, doordata);
2294 		break;
2295 	default:
2296 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyDoor: unknown door type %d", doordata->doorType);
2297 		return false;
2298 	}
2299 
2300 	return true;
2301 }
2302 
EV_DoPolyObjDisplace(polydisplacedata_t * prdata)2303 boolean EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
2304 {
2305 	polyobj_t *po;
2306 	polyobj_t *oldpo;
2307 	polydisplace_t *th;
2308 	INT32 start;
2309 
2310 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
2311 	{
2312 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
2313 		return false;
2314 	}
2315 
2316 	// don't allow line actions to affect bad polyobjects
2317 	if (po->isBad)
2318 		return false;
2319 
2320 	// create a new thinker
2321 	th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL);
2322 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjDisplace;
2323 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2324 	po->thinker = &th->thinker;
2325 
2326 	// set fields
2327 	th->polyObjNum = prdata->polyObjNum;
2328 
2329 	th->controlSector = prdata->controlSector;
2330 	th->oldHeights = th->controlSector->floorheight+th->controlSector->ceilingheight;
2331 
2332 	th->dx = prdata->dx;
2333 	th->dy = prdata->dy;
2334 
2335 	oldpo = po;
2336 
2337 	// apply action to mirroring polyobjects as well
2338 	start = 0;
2339 	while ((po = Polyobj_GetChild(oldpo, &start)))
2340 	{
2341 		prdata->polyObjNum = po->id; // change id to match child polyobject's
2342 		EV_DoPolyObjDisplace(prdata);
2343 	}
2344 
2345 	// action was successful
2346 	return true;
2347 }
2348 
EV_DoPolyObjRotDisplace(polyrotdisplacedata_t * prdata)2349 boolean EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
2350 {
2351 	polyobj_t *po;
2352 	polyobj_t *oldpo;
2353 	polyrotdisplace_t *th;
2354 	INT32 start;
2355 
2356 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
2357 	{
2358 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
2359 		return false;
2360 	}
2361 
2362 	// don't allow line actions to affect bad polyobjects
2363 	if (po->isBad)
2364 		return false;
2365 
2366 	// create a new thinker
2367 	th = Z_Malloc(sizeof(polyrotdisplace_t), PU_LEVSPEC, NULL);
2368 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotDisplace;
2369 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2370 	po->thinker = &th->thinker;
2371 
2372 	// set fields
2373 	th->polyObjNum = prdata->polyObjNum;
2374 
2375 	th->controlSector = prdata->controlSector;
2376 	th->oldHeights = th->controlSector->floorheight+th->controlSector->ceilingheight;
2377 
2378 	th->rotscale = prdata->rotscale;
2379 	th->turnobjs = prdata->turnobjs;
2380 
2381 	oldpo = po;
2382 
2383 	// apply action to mirroring polyobjects as well
2384 	start = 0;
2385 	while ((po = Polyobj_GetChild(oldpo, &start)))
2386 	{
2387 		prdata->polyObjNum = po->id; // change id to match child polyobject's
2388 		EV_DoPolyObjRotDisplace(prdata);
2389 	}
2390 
2391 	// action was successful
2392 	return true;
2393 }
2394 
T_PolyObjFlag(polymove_t * th)2395 void T_PolyObjFlag(polymove_t *th)
2396 {
2397 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
2398 	size_t i;
2399 
2400 	if (!po)
2401 #ifdef RANGECHECK
2402 		I_Error("T_PolyObjFlag: thinker has invalid id %d\n", th->polyObjNum);
2403 #else
2404 	{
2405 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFlag: thinker with invalid id %d removed.\n", th->polyObjNum);
2406 		P_RemoveThinker(&th->thinker);
2407 		return;
2408 	}
2409 #endif
2410 
2411 	// check for displacement due to override and reattach when possible
2412 	if (po->thinker == NULL)
2413 		po->thinker = &th->thinker;
2414 
2415 	// Iterate through polyobject's vertices
2416 	for (i = 0; i < po->numVertices/2; i++)
2417 	{
2418 		vertex_t vec;
2419 		fixed_t sine = FINESINE(th->distance)*th->momx;
2420 
2421 		Polyobj_componentSpeed(sine, th->angle, &vec.x, &vec.y);
2422 
2423 		po->vertices[i]->x = po->tmpVerts[i].x;
2424 		po->vertices[i]->y = po->tmpVerts[i].y;
2425 
2426 		Polyobj_vecAdd(po->vertices[i], &vec);
2427 
2428 		th->distance += th->speed;
2429 		th->distance &= FINEMASK;
2430 	}
2431 
2432 	for (i = 0; i < po->numLines; i++)
2433 		Polyobj_rotateLine(po->lines[i]);
2434 
2435 	Polyobj_removeFromBlockmap(po); // unlink it from the blockmap
2436 	Polyobj_removeFromSubsec(po);   // unlink it from its subsector
2437 	Polyobj_linkToBlockmap(po);     // relink to blockmap
2438 	Polyobj_attachToSubsec(po);     // relink to subsector
2439 }
2440 
EV_DoPolyObjFlag(polyflagdata_t * pfdata)2441 boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
2442 {
2443 	polyobj_t *po;
2444 	polyobj_t *oldpo;
2445 	polymove_t *th;
2446 	size_t i;
2447 	INT32 start;
2448 	mtag_t tag = pfdata->polyObjNum;
2449 
2450 	if (!(po = Polyobj_GetForNum(tag)))
2451 	{
2452 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", tag);
2453 		return false;
2454 	}
2455 
2456 	// don't allow line actions to affect bad polyobjects,
2457 	// polyobject doors don't allow action overrides
2458 	if (po->isBad || po->thinker)
2459 		return false;
2460 
2461 	// Must have even # of vertices
2462 	if (po->numVertices & 1)
2463 	{
2464 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: Polyobject has odd # of vertices!\n");
2465 		return false;
2466 	}
2467 
2468 	// create a new thinker
2469 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
2470 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFlag;
2471 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2472 	po->thinker = &th->thinker;
2473 
2474 	// set fields
2475 	th->polyObjNum = tag;
2476 	th->distance   = 0;
2477 	th->speed      = pfdata->speed;
2478 	th->angle      = pfdata->angle;
2479 	th->momx       = pfdata->momx;
2480 
2481 	// save current positions
2482 	for (i = 0; i < po->numVertices; ++i)
2483 		po->tmpVerts[i] = *(po->vertices[i]);
2484 
2485 	oldpo = po;
2486 
2487 	// apply action to mirroring polyobjects as well
2488 	start = 0;
2489 	while ((po = Polyobj_GetChild(oldpo, &start)))
2490 	{
2491 		pfdata->polyObjNum = po->id;
2492 		EV_DoPolyObjFlag(pfdata);
2493 	}
2494 
2495 	// action was successful
2496 	return true;
2497 }
2498 
T_PolyObjFade(polyfade_t * th)2499 void T_PolyObjFade(polyfade_t *th)
2500 {
2501 	boolean stillfading = false;
2502 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
2503 
2504 	if (!po)
2505 #ifdef RANGECHECK
2506 		I_Error("T_PolyObjFade: thinker has invalid id %d\n", th->polyObjNum);
2507 #else
2508 	{
2509 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFade: thinker with invalid id %d removed.\n", th->polyObjNum);
2510 		P_RemoveThinker(&th->thinker);
2511 		return;
2512 	}
2513 #endif
2514 
2515 	// check for displacement due to override and reattach when possible
2516 	if (po->thinker == NULL)
2517 		po->thinker = &th->thinker;
2518 
2519 	stillfading = th->ticbased ? !(--(th->timer) <= 0)
2520 		: !((th->timer -= th->duration) <= 0);
2521 
2522 	if (th->timer <= 0)
2523 	{
2524 		po->translucency = max(min(th->destvalue, NUMTRANSMAPS), 0);
2525 
2526 		// remove thinker
2527 		if (po->thinker == &th->thinker)
2528 			po->thinker = NULL;
2529 		P_RemoveThinker(&th->thinker);
2530 	}
2531 	else
2532 	{
2533 		INT16 delta = abs(th->destvalue - th->sourcevalue);
2534 		INT32 duration = th->ticbased ? th->duration
2535 			: abs(FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->destvalue)
2536 				- FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->sourcevalue)); // speed-based internal counter duration: delta in 256 scale
2537 		fixed_t factor = min(FixedDiv(duration - th->timer, duration), 1*FRACUNIT);
2538 		if (th->destvalue < th->sourcevalue)
2539 			po->translucency = max(min(po->translucency, th->sourcevalue - (INT16)FixedMul(delta, factor)), th->destvalue);
2540 		else if (th->destvalue > th->sourcevalue)
2541 			po->translucency = min(max(po->translucency, th->sourcevalue + (INT16)FixedMul(delta, factor)), th->destvalue);
2542 	}
2543 
2544 	if (!stillfading)
2545 	{
2546 		// set render flags
2547 		if (po->translucency >= NUMTRANSMAPS) // invisible
2548 			po->flags &= ~POF_RENDERALL;
2549 		else
2550 			po->flags |= (po->spawnflags & POF_RENDERALL);
2551 
2552 		// set collision
2553 		if (th->docollision)
2554 		{
2555 			if (th->destvalue > th->sourcevalue) // faded out
2556 			{
2557 				po->flags &= ~POF_SOLID;
2558 				po->flags |= POF_NOSPECIALS;
2559 			}
2560 			else
2561 			{
2562 				po->flags |= (po->spawnflags & POF_SOLID);
2563 				if (!(po->spawnflags & POF_NOSPECIALS))
2564 					po->flags &= ~POF_NOSPECIALS;
2565 			}
2566 		}
2567 	}
2568 	else
2569 	{
2570 		if (po->translucency >= NUMTRANSMAPS)
2571 			// HACK: OpenGL renders fully opaque when >= NUMTRANSMAPS
2572 			po->translucency = NUMTRANSMAPS-1;
2573 
2574 		po->flags |= (po->spawnflags & POF_RENDERALL);
2575 
2576 		// set collision
2577 		if (th->docollision)
2578 		{
2579 			if (th->doghostfade)
2580 			{
2581 				po->flags &= ~POF_SOLID;
2582 				po->flags |= POF_NOSPECIALS;
2583 			}
2584 			else
2585 			{
2586 				po->flags |= (po->spawnflags & POF_SOLID);
2587 				if (!(po->spawnflags & POF_NOSPECIALS))
2588 					po->flags &= ~POF_NOSPECIALS;
2589 			}
2590 		}
2591 	}
2592 }
2593 
EV_DoPolyObjFade(polyfadedata_t * pfdata)2594 boolean EV_DoPolyObjFade(polyfadedata_t *pfdata)
2595 {
2596 	polyobj_t *po;
2597 	polyobj_t *oldpo;
2598 	polyfade_t *th;
2599 	INT32 start;
2600 
2601 	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
2602 	{
2603 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjFade: bad polyobj %d\n", pfdata->polyObjNum);
2604 		return false;
2605 	}
2606 
2607 	// don't allow line actions to affect bad polyobjects
2608 	if (po->isBad)
2609 		return false;
2610 
2611 	// already equal, nothing to do
2612 	if (po->translucency == pfdata->destvalue)
2613 		return true;
2614 
2615 	if (po->thinker && po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
2616 		P_RemoveThinker(po->thinker);
2617 
2618 	// create a new thinker
2619 	th = Z_Malloc(sizeof(polyfade_t), PU_LEVSPEC, NULL);
2620 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFade;
2621 	P_AddThinker(THINK_POLYOBJ, &th->thinker);
2622 	po->thinker = &th->thinker;
2623 
2624 	// set fields
2625 	th->polyObjNum = pfdata->polyObjNum;
2626 	th->sourcevalue = po->translucency;
2627 	th->destvalue = pfdata->destvalue;
2628 	th->docollision = pfdata->docollision;
2629 	th->doghostfade = pfdata->doghostfade;
2630 
2631 	if (pfdata->ticbased)
2632 	{
2633 		th->ticbased = true;
2634 		th->timer = th->duration = abs(pfdata->speed); // pfdata->speed is duration
2635 	}
2636 	else
2637 	{
2638 		th->ticbased = false;
2639 		th->timer = abs(FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->destvalue)
2640 			- FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->sourcevalue)); // delta converted to 256 scale, use as internal counter
2641 		th->duration = abs(pfdata->speed); // use th->duration as speed decrement
2642 	}
2643 
2644 	oldpo = po;
2645 
2646 	// apply action to mirroring polyobjects as well
2647 	start = 0;
2648 	while ((po = Polyobj_GetChild(oldpo, &start)))
2649 	{
2650 		pfdata->polyObjNum = po->id;
2651 		EV_DoPolyObjFade(pfdata);
2652 	}
2653 
2654 	// action was successful
2655 	return true;
2656 }
2657 
2658 // EOF
2659