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