1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: po_man.cpp 4542 2014-02-09 17:39:42Z dr_sean $
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 // Copyright (C) 2006-2014 by The Odamex Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // DESCRIPTION:
20 // PO_MAN.C : Heretic 2 : Raven Software, Corp.
21 //
22 //-----------------------------------------------------------------------------
23
24 // HEADER FILES ------------------------------------------------------------
25
26 #include "doomdef.h"
27 #include "p_local.h"
28 #include "r_local.h"
29 #include "i_system.h"
30 #include "z_zone.h"
31 #include "w_wad.h"
32 #include "m_swap.h"
33 #include "m_bbox.h"
34 #include "tables.h"
35 #include "s_sndseq.h"
36
37 // MACROS ------------------------------------------------------------------
38
39 #define PO_MAXPOLYSEGS 64
40
41 // TYPES -------------------------------------------------------------------
42
43 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
44
45 BOOL PO_MovePolyobj (int num, int x, int y);
46 BOOL PO_RotatePolyobj (int num, angle_t angle);
47 void PO_Init (void);
48
49 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
50
51 static polyobj_t *GetPolyobj (int polyNum);
52 static int GetPolyobjMirror (int poly);
53 static void UpdateSegBBox (seg_t *seg);
54 static void RotatePt (int an, fixed_t *x, fixed_t *y, fixed_t startSpotX,
55 fixed_t startSpotY);
56 static void UnLinkPolyobj (polyobj_t *po);
57 static void LinkPolyobj (polyobj_t *po);
58 static BOOL CheckMobjBlocking (seg_t *seg, polyobj_t *po);
59 static void InitBlockMap (void);
60 static void IterFindPolySegs (int x, int y, seg_t **segList);
61 static void SpawnPolyobj (int index, int tag, BOOL crush);
62 static void TranslateToStartSpot (int tag, int originX, int originY);
63 static void DoMovePolyobj (polyobj_t *po, int x, int y);
64
65 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
66
67 extern seg_t *segs;
68
69 // PUBLIC DATA DEFINITIONS -------------------------------------------------
70
71 polyblock_t **PolyBlockMap;
72 polyobj_t *polyobjs; // list of all poly-objects on the level
73 int po_NumPolyobjs;
74 polyspawns_t *polyspawns; // [RH] Let P_SpawnMapThings() find our thingies for us
75
76 // PRIVATE DATA DEFINITIONS ------------------------------------------------
77
78 static int PolySegCount;
79 static fixed_t PolyStartX;
80 static fixed_t PolyStartY;
81
82 // CODE --------------------------------------------------------------------
83
IMPLEMENT_SERIAL(DPolyAction,DThinker)84 IMPLEMENT_SERIAL (DPolyAction, DThinker)
85 IMPLEMENT_SERIAL (DRotatePoly, DPolyAction)
86 IMPLEMENT_SERIAL (DMovePoly, DPolyAction)
87 IMPLEMENT_SERIAL (DPolyDoor, DMovePoly)
88
89 DPolyAction::DPolyAction ()
90 {
91 }
92
Serialize(FArchive & arc)93 void DPolyAction::Serialize (FArchive &arc)
94 {
95 Super::Serialize (arc);
96 if (arc.IsStoring ())
97 arc << m_PolyObj << m_Speed << m_Dist;
98 else
99 arc >> m_PolyObj >> m_Speed >> m_Dist;
100 }
101
DPolyAction(int polyNum)102 DPolyAction::DPolyAction (int polyNum)
103 {
104 m_PolyObj = polyNum;
105 m_Speed = 0;
106 m_Dist = 0;
107 }
108
DRotatePoly()109 DRotatePoly::DRotatePoly ()
110 {
111 }
112
Serialize(FArchive & arc)113 void DRotatePoly::Serialize (FArchive &arc)
114 {
115 Super::Serialize (arc);
116 }
117
DRotatePoly(int polyNum)118 DRotatePoly::DRotatePoly (int polyNum)
119 : Super (polyNum)
120 {
121 }
122
DMovePoly()123 DMovePoly::DMovePoly ()
124 {
125 }
126
Serialize(FArchive & arc)127 void DMovePoly::Serialize (FArchive &arc)
128 {
129 Super::Serialize (arc);
130 if (arc.IsStoring ())
131 arc << m_Angle << m_xSpeed << m_ySpeed;
132 else
133 arc >> m_Angle >> m_xSpeed >> m_ySpeed;
134 }
135
DMovePoly(int polyNum)136 DMovePoly::DMovePoly (int polyNum)
137 : Super (polyNum)
138 {
139 m_Angle = 0;
140 m_xSpeed = 0;
141 m_ySpeed = 0;
142 }
143
DPolyDoor()144 DPolyDoor::DPolyDoor ()
145 {
146 }
147
Serialize(FArchive & arc)148 void DPolyDoor::Serialize (FArchive &arc)
149 {
150 Super::Serialize (arc);
151 if (arc.IsStoring ())
152 arc << m_Direction << m_TotalDist << m_Tics << m_WaitTics << m_Type << m_Close;
153 else
154 arc >> m_Direction >> m_TotalDist >> m_Tics >> m_WaitTics >> m_Type >> m_Close;
155 }
156
DPolyDoor(int polyNum,podoortype_t type)157 DPolyDoor::DPolyDoor (int polyNum, podoortype_t type)
158 : Super (polyNum)
159 {
160 m_Type = type;
161 m_Direction = 0;
162 m_TotalDist = 0;
163 m_Tics = 0;
164 m_WaitTics = 0;
165 m_Close = false;
166 }
167
168 // ===== Polyobj Event Code =====
169
170
171 //
172 // T_RotatePoly
173 //
RunThink()174 void DRotatePoly::RunThink ()
175 {
176 if (PO_RotatePolyobj (m_PolyObj, m_Speed))
177 {
178 int absSpeed = abs (m_Speed);
179
180 if (m_Dist == -1)
181 { // perpetual polyobj
182 return;
183 }
184 m_Dist -= absSpeed;
185 if (m_Dist <= 0)
186 {
187 polyobj_t *poly = GetPolyobj (m_PolyObj);
188 if (poly->specialdata == this)
189 {
190 poly->specialdata = NULL;
191 }
192 SN_StopSequence (poly);
193 Destroy ();
194 }
195 else if (m_Dist < absSpeed)
196 {
197 m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1);
198 }
199 }
200 }
201
202 //
203 // EV_RotatePoly
204 //
EV_RotatePoly(line_t * line,int polyNum,int speed,int byteAngle,int direction,BOOL overRide)205 BOOL EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle,
206 int direction, BOOL overRide)
207 {
208 int mirror;
209 DRotatePoly *pe;
210 polyobj_t *poly;
211
212 if ( (poly = GetPolyobj(polyNum)) )
213 {
214 if (poly->specialdata && !overRide)
215 { // poly is already moving
216 return false;
217 }
218 }
219 else
220 {
221 I_Error("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum);
222 }
223 pe = new DRotatePoly (polyNum);
224 if (byteAngle)
225 {
226 if (byteAngle == 255)
227 {
228 pe->m_Dist = ~0;
229 }
230 else
231 {
232 pe->m_Dist = byteAngle*(ANG90/64); // Angle
233 }
234 }
235 else
236 {
237 pe->m_Dist = ANG360-1;
238 }
239 pe->m_Speed = (speed*direction*(ANG90/64))>>3;
240 poly->specialdata = pe;
241 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
242
243 while ( (mirror = GetPolyobjMirror( polyNum)) )
244 {
245 poly = GetPolyobj(mirror);
246 if (poly && poly->specialdata && !overRide)
247 { // mirroring poly is already in motion
248 break;
249 }
250 pe = new DRotatePoly (mirror);
251 poly->specialdata = pe;
252 if (byteAngle)
253 {
254 if (byteAngle == 255)
255 {
256 pe->m_Dist = ~0;
257 }
258 else
259 {
260 pe->m_Dist = byteAngle*(ANG90/64); // Angle
261 }
262 }
263 else
264 {
265 pe->m_Dist = ANG360-1;
266 }
267 if( (poly = GetPolyobj(polyNum)) )
268 {
269 poly->specialdata = pe;
270 }
271 else
272 {
273 I_Error ("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum);
274 }
275 direction = -direction;
276 pe->m_Speed = (speed*direction*(ANG90/64))>>3;
277 polyNum = mirror;
278 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
279 }
280 return true;
281 }
282
283 //
284 // T_MovePoly
285 //
RunThink()286 void DMovePoly::RunThink ()
287 {
288 polyobj_t *poly;
289
290 if (PO_MovePolyobj (m_PolyObj, m_xSpeed, m_ySpeed))
291 {
292 int absSpeed = abs (m_Speed);
293 m_Dist -= absSpeed;
294 if (m_Dist <= 0)
295 {
296 poly = GetPolyobj (m_PolyObj);
297 if (poly->specialdata == this)
298 {
299 poly->specialdata = NULL;
300 }
301 SN_StopSequence (poly);
302 Destroy ();
303 }
304 else if (m_Dist < absSpeed)
305 {
306 m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1);
307 m_xSpeed = FixedMul (m_Speed, finecosine[m_Angle]);
308 m_ySpeed = FixedMul (m_Speed, finesine[m_Angle]);
309 }
310 }
311 }
312
313 //
314 // EV_MovePoly
315 //
EV_MovePoly(line_t * line,int polyNum,int speed,angle_t angle,fixed_t dist,BOOL overRide)316 BOOL EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle,
317 fixed_t dist, BOOL overRide)
318 {
319 int mirror;
320 DMovePoly *pe;
321 polyobj_t *poly;
322 angle_t an;
323
324 if ( (poly = GetPolyobj(polyNum)) )
325 {
326 if (poly->specialdata && !overRide)
327 { // poly is already moving
328 return false;
329 }
330 }
331 else
332 {
333 I_Error("EV_MovePoly: Invalid polyobj num: %d\n", polyNum);
334 }
335 pe = new DMovePoly (polyNum);
336 pe->m_Dist = dist; // Distance
337 pe->m_Speed = speed;
338 poly->specialdata = pe;
339
340 an = angle;
341
342 pe->m_Angle = an>>ANGLETOFINESHIFT;
343 pe->m_xSpeed = FixedMul (pe->m_Speed, finecosine[pe->m_Angle]);
344 pe->m_ySpeed = FixedMul (pe->m_Speed, finesine[pe->m_Angle]);
345 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
346
347 while ( (mirror = GetPolyobjMirror(polyNum)) )
348 {
349 poly = GetPolyobj(mirror);
350 if (poly && poly->specialdata && !overRide)
351 { // mirroring poly is already in motion
352 break;
353 }
354 pe = new DMovePoly (mirror);
355 poly->specialdata = pe;
356 pe->m_Dist = dist; // Distance
357 pe->m_Speed = speed;
358 an = an+ANG180; // reverse the angle
359 pe->m_Angle = an>>ANGLETOFINESHIFT;
360 pe->m_xSpeed = FixedMul (pe->m_Speed, finecosine[pe->m_Angle]);
361 pe->m_ySpeed = FixedMul (pe->m_Speed, finesine[pe->m_Angle]);
362 polyNum = mirror;
363 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
364 }
365 return true;
366 }
367
368
369 //
370 // T_PolyDoor
371 //
RunThink()372 void DPolyDoor::RunThink ()
373 {
374 int absSpeed;
375 polyobj_t *poly;
376
377 if (m_Tics)
378 {
379 if (!--m_Tics)
380 {
381 poly = GetPolyobj (m_PolyObj);
382 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
383 }
384 return;
385 }
386 switch (m_Type)
387 {
388 case PODOOR_SLIDE:
389 if (m_Dist <= 0 || PO_MovePolyobj (m_PolyObj, m_xSpeed, m_ySpeed))
390 {
391 absSpeed = abs (m_Speed);
392 m_Dist -= absSpeed;
393 if (m_Dist <= 0)
394 {
395 poly = GetPolyobj (m_PolyObj);
396 SN_StopSequence (poly);
397 if (!m_Close)
398 {
399 m_Dist = m_TotalDist;
400 m_Close = true;
401 m_Tics = m_WaitTics;
402 m_Direction = (ANG360>>ANGLETOFINESHIFT)-
403 m_Direction;
404 m_xSpeed = -m_xSpeed;
405 m_ySpeed = -m_ySpeed;
406 }
407 else
408 {
409 if (poly->specialdata == this)
410 {
411 poly->specialdata = NULL;
412 }
413 Destroy ();
414 }
415 }
416 }
417 else
418 {
419 poly = GetPolyobj (m_PolyObj);
420 if (poly->crush || !m_Close)
421 { // continue moving if the poly is a crusher, or is opening
422 return;
423 }
424 else
425 { // open back up
426 m_Dist = m_TotalDist - m_Dist;
427 m_Direction = (ANG360>>ANGLETOFINESHIFT)-
428 m_Direction;
429 m_xSpeed = -m_xSpeed;
430 m_ySpeed = -m_ySpeed;
431 m_Close = false;
432 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
433 }
434 }
435 break;
436 case PODOOR_SWING:
437 if (PO_RotatePolyobj (m_PolyObj, m_Speed))
438 {
439 absSpeed = abs (m_Speed);
440 if (m_Dist == -1)
441 { // perpetual polyobj
442 return;
443 }
444 m_Dist -= absSpeed;
445 if (m_Dist <= 0)
446 {
447 poly = GetPolyobj (m_PolyObj);
448 SN_StopSequence (poly);
449 if (!m_Close)
450 {
451 m_Dist = m_TotalDist;
452 m_Close = true;
453 m_Tics = m_WaitTics;
454 m_Speed = -m_Speed;
455 }
456 else
457 {
458 if (poly->specialdata == this)
459 {
460 poly->specialdata = NULL;
461 }
462 Destroy ();
463 }
464 }
465 }
466 else
467 {
468 poly = GetPolyobj (m_PolyObj);
469 if(poly->crush || !m_Close)
470 { // continue moving if the poly is a crusher, or is opening
471 return;
472 }
473 else
474 { // open back up and rewait
475 m_Dist = m_TotalDist - m_Dist;
476 m_Speed = -m_Speed;
477 m_Close = false;
478 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
479 }
480 }
481 break;
482 default:
483 break;
484 }
485 }
486
487 //
488 // EV_OpenPolyDoor
489 //
EV_OpenPolyDoor(line_t * line,int polyNum,int speed,angle_t angle,int delay,int distance,podoortype_t type)490 BOOL EV_OpenPolyDoor (line_t *line, int polyNum, int speed, angle_t angle,
491 int delay, int distance, podoortype_t type)
492 {
493 int mirror;
494 DPolyDoor *pd;
495 polyobj_t *poly;
496
497 if( (poly = GetPolyobj(polyNum)) )
498 {
499 if (poly->specialdata)
500 { // poly is already moving
501 return false;
502 }
503 }
504 else
505 {
506 I_Error("EV_OpenPolyDoor: Invalid polyobj num: %d\n", polyNum);
507 }
508 pd = new DPolyDoor (polyNum, type);
509 if (type == PODOOR_SLIDE)
510 {
511 pd->m_WaitTics = delay;
512 pd->m_Speed = speed;
513 pd->m_Dist = pd->m_TotalDist = distance; // Distance
514 pd->m_Direction = angle >> ANGLETOFINESHIFT;
515 pd->m_xSpeed = FixedMul (pd->m_Speed, finecosine[pd->m_Direction]);
516 pd->m_ySpeed = FixedMul (pd->m_Speed, finesine[pd->m_Direction]);
517 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
518 }
519 else if (type == PODOOR_SWING)
520 {
521 pd->m_WaitTics = delay;
522 pd->m_Direction = 1; // ADD: PODO'OR_SWINGL, PODOOR_SWINGR
523 pd->m_Speed = (speed*pd->m_Direction*(ANG90/64))>>3;
524 pd->m_Dist = pd->m_TotalDist = angle;
525 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
526 }
527
528 poly->specialdata = pd;
529
530 while ( (mirror = GetPolyobjMirror (polyNum)) )
531 {
532 poly = GetPolyobj (mirror);
533 if (poly && poly->specialdata)
534 { // mirroring poly is already in motion
535 break;
536 }
537 pd = new DPolyDoor (mirror, type);
538 poly->specialdata = pd;
539 if (type == PODOOR_SLIDE)
540 {
541 pd->m_WaitTics = delay;
542 pd->m_Speed = speed;
543 pd->m_Dist = pd->m_TotalDist = distance; // Distance
544 pd->m_Direction = (angle + ANG180) >> ANGLETOFINESHIFT; // reverse the angle
545 pd->m_xSpeed = FixedMul (pd->m_Speed, finecosine[pd->m_Direction]);
546 pd->m_ySpeed = FixedMul (pd->m_Speed, finesine[pd->m_Direction]);
547 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
548 }
549 else if (type == PODOOR_SWING)
550 {
551 pd->m_WaitTics = delay;
552 pd->m_Direction = -1; // ADD: same as above
553 pd->m_Speed = (speed*pd->m_Direction*(ANG90/64))>>3;
554 pd->m_Dist = pd->m_TotalDist = angle;
555 SN_StartSequence (poly, poly->seqType, SEQ_DOOR);
556 }
557 polyNum = mirror;
558 }
559 return true;
560 }
561
562 // ===== Higher Level Poly Interface code =====
563
564
565 //
566 // GetPolyobj
567 //
GetPolyobj(int polyNum)568 static polyobj_t *GetPolyobj (int polyNum)
569 {
570 int i;
571
572 for (i = 0; i < po_NumPolyobjs; i++)
573 {
574 if (polyobjs[i].tag == polyNum)
575 {
576 return &polyobjs[i];
577 }
578 }
579 return NULL;
580 }
581
582 //
583 // GetPolyobjMirror
584 //
GetPolyobjMirror(int poly)585 static int GetPolyobjMirror(int poly)
586 {
587 int i;
588
589 for (i = 0; i < po_NumPolyobjs; i++)
590 {
591 if (polyobjs[i].tag == poly)
592 {
593 return (*polyobjs[i].segs)->linedef->args[1];
594 }
595 }
596 return 0;
597 }
598
599 //
600 // ThrustMobj
601 //
ThrustMobj(AActor * actor,seg_t * seg,polyobj_t * po)602 void ThrustMobj (AActor *actor, seg_t *seg, polyobj_t *po)
603 {
604 int thrustAngle;
605 int thrustX;
606 int thrustY;
607 DPolyAction *pe;
608
609 int force;
610
611 if (!(actor->flags&MF_SHOOTABLE) && !actor->player)
612 {
613 return;
614 }
615 thrustAngle = (seg->angle-ANG90)>>ANGLETOFINESHIFT;
616
617 pe = static_cast<DPolyAction *>(po->specialdata);
618 if (pe)
619 {
620 if (pe->IsKindOf (RUNTIME_CLASS (DRotatePoly)))
621 {
622 force = pe->m_Speed >> 8;
623 }
624 else
625 {
626 force = pe->m_Speed >> 3;
627 }
628 if (force < FRACUNIT)
629 {
630 force = FRACUNIT;
631 }
632 else if (force > 4*FRACUNIT)
633 {
634 force = 4*FRACUNIT;
635 }
636 }
637 else
638 {
639 force = FRACUNIT;
640 }
641
642 thrustX = FixedMul (force, finecosine[thrustAngle]);
643 thrustY = FixedMul (force, finesine[thrustAngle]);
644 actor->momx += thrustX;
645 actor->momy += thrustY;
646 if (po->crush)
647 {
648 if (!P_CheckPosition (actor, actor->x + thrustX, actor->y + thrustY))
649 {
650 P_DamageMobj (actor, NULL, NULL, 3, MOD_CRUSH);
651 }
652 }
653 }
654
655
656 //
657 // UpdateSegBBox
658 //
UpdateSegBBox(seg_t * seg)659 static void UpdateSegBBox (seg_t *seg)
660 {
661 line_t *line;
662
663 line = seg->linedef;
664
665 if (seg->v1->x < seg->v2->x)
666 {
667 line->bbox[BOXLEFT] = seg->v1->x;
668 line->bbox[BOXRIGHT] = seg->v2->x;
669 }
670 else
671 {
672 line->bbox[BOXLEFT] = seg->v2->x;
673 line->bbox[BOXRIGHT] = seg->v1->x;
674 }
675 if (seg->v1->y < seg->v2->y)
676 {
677 line->bbox[BOXBOTTOM] = seg->v1->y;
678 line->bbox[BOXTOP] = seg->v2->y;
679 }
680 else
681 {
682 line->bbox[BOXBOTTOM] = seg->v2->y;
683 line->bbox[BOXTOP] = seg->v1->y;
684 }
685
686 // Update the line's slopetype
687 line->dx = line->v2->x - line->v1->x;
688 line->dy = line->v2->y - line->v1->y;
689 if (!line->dx)
690 {
691 line->slopetype = ST_VERTICAL;
692 }
693 else if (!line->dy)
694 {
695 line->slopetype = ST_HORIZONTAL;
696 }
697 else
698 {
699 if (FixedDiv(line->dy, line->dx) > 0)
700 {
701 line->slopetype = ST_POSITIVE;
702 }
703 else
704 {
705 line->slopetype = ST_NEGATIVE;
706 }
707 }
708 }
709
710
711 //
712 // PO_MovePolyobj
713 //
PO_MovePolyobj(int num,int x,int y)714 BOOL PO_MovePolyobj (int num, int x, int y)
715 {
716 int count;
717 seg_t **segList;
718 polyobj_t *po;
719 bool blocked;
720
721 if (!(po = GetPolyobj (num)))
722 {
723 I_Error ("PO_MovePolyobj: Invalid polyobj number: %d\n", num);
724 }
725
726 UnLinkPolyobj (po);
727 DoMovePolyobj (po, x, y);
728
729 segList = po->segs;
730 blocked = false;
731 for (count = po->numsegs; count; count--, segList++)
732 {
733 if (CheckMobjBlocking(*segList, po))
734 {
735 blocked = true;
736 break;
737 }
738 }
739 if (blocked)
740 {
741 DoMovePolyobj (po, -x, -y);
742 LinkPolyobj(po);
743 return false;
744 }
745 po->startSpot[0] += x;
746 po->startSpot[1] += y;
747 LinkPolyobj (po);
748 return true;
749 }
750
751 //
752 // DoMovePolyobj
753 //
DoMovePolyobj(polyobj_t * po,int x,int y)754 void DoMovePolyobj (polyobj_t *po, int x, int y)
755 {
756 int count;
757 seg_t **segList;
758 seg_t **veryTempSeg;
759 vertex_t *prevPts;
760
761 segList = po->segs;
762 prevPts = po->prevPts;
763
764 validcount++;
765 for (count = po->numsegs; count; count--, segList++, prevPts++)
766 {
767 line_t *linedef = (*segList)->linedef;
768 if (linedef->validcount != validcount)
769 {
770 linedef->bbox[BOXTOP] += y;
771 linedef->bbox[BOXBOTTOM] += y;
772 linedef->bbox[BOXLEFT] += x;
773 linedef->bbox[BOXRIGHT] += x;
774 //if (linedef->sidenum[0] != -1)
775 // ADecal::MoveChain (sides[linedef->sidenum[0]].BoundActors, x, y);
776 //if (linedef->sidenum[1] != -1)
777 // ADecal::MoveChain (sides[linedef->sidenum[1]].BoundActors, x, y);
778 linedef->validcount = validcount;
779 }
780 for (veryTempSeg = po->segs; veryTempSeg != segList;
781 veryTempSeg++)
782 {
783 if ((*veryTempSeg)->v1 == (*segList)->v1)
784 {
785 break;
786 }
787 }
788 if (veryTempSeg == segList)
789 {
790 (*segList)->v1->x += x;
791 (*segList)->v1->y += y;
792 }
793 (*prevPts).x += x; // previous points are unique for each seg
794 (*prevPts).y += y;
795 }
796 }
797
798 //
799 // RotatePt
800 //
RotatePt(int an,fixed_t * x,fixed_t * y,fixed_t startSpotX,fixed_t startSpotY)801 static void RotatePt (int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_t startSpotY)
802 {
803 fixed_t tr_x, tr_y;
804 fixed_t gxt, gyt;
805
806 tr_x = *x;
807 tr_y = *y;
808
809 gxt = FixedMul(tr_x, finecosine[an]);
810 gyt = FixedMul(tr_y, finesine[an]);
811 *x = (gxt-gyt)+startSpotX;
812
813 gxt = FixedMul(tr_x, finesine[an]);
814 gyt = FixedMul(tr_y, finecosine[an]);
815 *y = (gyt+gxt)+startSpotY;
816 }
817
818 //
819 // PO_RotatePolyobj
820 //
PO_RotatePolyobj(int num,angle_t angle)821 BOOL PO_RotatePolyobj (int num, angle_t angle)
822 {
823 int count;
824 seg_t **segList;
825 vertex_t *originalPts;
826 vertex_t *prevPts;
827 int an;
828 polyobj_t *po;
829 BOOL blocked;
830
831 if(!(po = GetPolyobj(num)))
832 {
833 I_Error("PO_RotatePolyobj: Invalid polyobj number: %d\n", num);
834 }
835 an = (po->angle+angle)>>ANGLETOFINESHIFT;
836
837 UnLinkPolyobj(po);
838
839 segList = po->segs;
840 originalPts = po->originalPts;
841 prevPts = po->prevPts;
842
843 for(count = po->numsegs; count; count--, segList++, originalPts++,
844 prevPts++)
845 {
846 prevPts->x = (*segList)->v1->x;
847 prevPts->y = (*segList)->v1->y;
848 (*segList)->v1->x = originalPts->x;
849 (*segList)->v1->y = originalPts->y;
850 RotatePt (an, &(*segList)->v1->x, &(*segList)->v1->y, po->startSpot[0],
851 po->startSpot[1]);
852 }
853 segList = po->segs;
854 blocked = false;
855 validcount++;
856 for (count = po->numsegs; count; count--, segList++)
857 {
858 if (CheckMobjBlocking(*segList, po))
859 {
860 blocked = true;
861 }
862 if ((*segList)->linedef->validcount != validcount)
863 {
864 UpdateSegBBox(*segList);
865 line_t *line = (*segList)->linedef;
866 //if (line->sidenum[0] != -1)
867 // ADecal::FixForSide (&sides[line->sidenum[0]]);
868 //if (line->sidenum[1] != -1)
869 // ADecal::FixForSide (&sides[line->sidenum[1]]);
870 line->validcount = validcount;
871 }
872 (*segList)->angle += angle;
873 }
874 if (blocked)
875 {
876 segList = po->segs;
877 prevPts = po->prevPts;
878 for (count = po->numsegs; count; count--, segList++, prevPts++)
879 {
880 (*segList)->v1->x = prevPts->x;
881 (*segList)->v1->y = prevPts->y;
882 }
883 segList = po->segs;
884 validcount++;
885 for (count = po->numsegs; count; count--, segList++, prevPts++)
886 {
887 if ((*segList)->linedef->validcount != validcount)
888 {
889 UpdateSegBBox(*segList);
890 line_t *line = (*segList)->linedef;
891 //if (line->sidenum[0] != -1)
892 // ADecal::FixForSide (&sides[line->sidenum[0]]);
893 //if (line->sidenum[1] != -1)
894 // ADecal::FixForSide (&sides[line->sidenum[1]]);
895 line->validcount = validcount;
896 }
897 (*segList)->angle -= angle;
898 }
899 LinkPolyobj(po);
900 return false;
901 }
902 po->angle += angle;
903 LinkPolyobj(po);
904 return true;
905 }
906
907 //
908 // UnLinkPolyobj
909 //
UnLinkPolyobj(polyobj_t * po)910 static void UnLinkPolyobj (polyobj_t *po)
911 {
912 polyblock_t *link;
913 int i, j;
914 int index;
915
916 // remove the polyobj from each blockmap section
917 for(j = po->bbox[BOXBOTTOM]; j <= po->bbox[BOXTOP]; j++)
918 {
919 index = j*bmapwidth;
920 for(i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++)
921 {
922 if(i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight)
923 {
924 link = PolyBlockMap[index+i];
925 while(link != NULL && link->polyobj != po)
926 {
927 link = link->next;
928 }
929 if(link == NULL)
930 { // polyobj not located in the link cell
931 continue;
932 }
933 link->polyobj = NULL;
934 }
935 }
936 }
937 }
938
939 //
940 // LinkPolyobj
941 //
LinkPolyobj(polyobj_t * po)942 static void LinkPolyobj (polyobj_t *po)
943 {
944 int leftX, rightX;
945 int topY, bottomY;
946 seg_t **tempSeg;
947 polyblock_t **link;
948 polyblock_t *tempLink;
949 int i, j;
950
951 // calculate the polyobj bbox
952 tempSeg = po->segs;
953 rightX = leftX = (*tempSeg)->v1->x;
954 topY = bottomY = (*tempSeg)->v1->y;
955
956 for(i = 0; i < po->numsegs; i++, tempSeg++)
957 {
958 if((*tempSeg)->v1->x > rightX)
959 {
960 rightX = (*tempSeg)->v1->x;
961 }
962 if((*tempSeg)->v1->x < leftX)
963 {
964 leftX = (*tempSeg)->v1->x;
965 }
966 if((*tempSeg)->v1->y > topY)
967 {
968 topY = (*tempSeg)->v1->y;
969 }
970 if((*tempSeg)->v1->y < bottomY)
971 {
972 bottomY = (*tempSeg)->v1->y;
973 }
974 }
975 po->bbox[BOXRIGHT] = (rightX-bmaporgx)>>MAPBLOCKSHIFT;
976 po->bbox[BOXLEFT] = (leftX-bmaporgx)>>MAPBLOCKSHIFT;
977 po->bbox[BOXTOP] = (topY-bmaporgy)>>MAPBLOCKSHIFT;
978 po->bbox[BOXBOTTOM] = (bottomY-bmaporgy)>>MAPBLOCKSHIFT;
979 // add the polyobj to each blockmap section
980 for(j = po->bbox[BOXBOTTOM]*bmapwidth; j <= po->bbox[BOXTOP]*bmapwidth;
981 j += bmapwidth)
982 {
983 for(i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++)
984 {
985 if(i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight*bmapwidth)
986 {
987 link = &PolyBlockMap[j+i];
988 if(!(*link))
989 { // Create a new link at the current block cell
990 *link = (polyblock_t *)Z_Malloc(sizeof(polyblock_t), PU_LEVEL, 0);
991 (*link)->next = NULL;
992 (*link)->prev = NULL;
993 (*link)->polyobj = po;
994 continue;
995 }
996 else
997 {
998 tempLink = *link;
999 while(tempLink->next != NULL && tempLink->polyobj != NULL)
1000 {
1001 tempLink = tempLink->next;
1002 }
1003 }
1004 if(tempLink->polyobj == NULL)
1005 {
1006 tempLink->polyobj = po;
1007 continue;
1008 }
1009 else
1010 {
1011 tempLink->next = (polyblock_t *)Z_Malloc (sizeof(polyblock_t),
1012 PU_LEVEL, 0);
1013 tempLink->next->next = NULL;
1014 tempLink->next->prev = tempLink;
1015 tempLink->next->polyobj = po;
1016 }
1017 }
1018 // else, don't link the polyobj, since it's off the map
1019 }
1020 }
1021 }
1022
1023 //
1024 // CheckMobjBlocking
1025 //
CheckMobjBlocking(seg_t * seg,polyobj_t * po)1026 static BOOL CheckMobjBlocking (seg_t *seg, polyobj_t *po)
1027 {
1028 AActor *mobj;
1029 int i, j;
1030 int left, right, top, bottom;
1031 fixed_t tmbbox[4];
1032 line_t *ld;
1033 BOOL blocked;
1034
1035 ld = seg->linedef;
1036
1037 top = (ld->bbox[BOXTOP]-bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT;
1038 bottom = (ld->bbox[BOXBOTTOM]-bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT;
1039 left = (ld->bbox[BOXLEFT]-bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT;
1040 right = (ld->bbox[BOXRIGHT]-bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT;
1041
1042 blocked = false;
1043
1044 bottom = bottom < 0 ? 0 : bottom;
1045 bottom = bottom >= bmapheight ? bmapheight-1 : bottom;
1046 top = top < 0 ? 0 : top;
1047 top = top >= bmapheight ? bmapheight-1 : top;
1048 left = left < 0 ? 0 : left;
1049 left = left >= bmapwidth ? bmapwidth-1 : left;
1050 right = right < 0 ? 0 : right;
1051 right = right >= bmapwidth ? bmapwidth-1 : right;
1052
1053 for (j = bottom*bmapwidth; j <= top*bmapwidth; j += bmapwidth)
1054 {
1055 for (i = left; i <= right; i++)
1056 {
1057 for (mobj = blocklinks[j+i]; mobj; mobj = mobj->bmapnode.Next(i, j/bmapwidth))
1058 {
1059 if ((mobj->flags&MF_SOLID) && !(mobj->flags&MF_NOCLIP))
1060 {
1061 tmbbox[BOXTOP] = mobj->y+mobj->radius;
1062 tmbbox[BOXBOTTOM] = mobj->y-mobj->radius;
1063 tmbbox[BOXLEFT] = mobj->x-mobj->radius;
1064 tmbbox[BOXRIGHT] = mobj->x+mobj->radius;
1065
1066 if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
1067 || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
1068 || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
1069 || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
1070 {
1071 continue;
1072 }
1073 if (P_BoxOnLineSide(tmbbox, ld) != -1)
1074 {
1075 continue;
1076 }
1077 ThrustMobj (mobj, seg, po);
1078 blocked = true;
1079 }
1080 }
1081 }
1082 }
1083 return blocked;
1084 }
1085
1086 //
1087 // InitBlockMap
1088 //
InitBlockMap(void)1089 static void InitBlockMap (void)
1090 {
1091 int i;
1092
1093 PolyBlockMap = (polyblock_t **)Z_Malloc (bmapwidth*bmapheight*sizeof(polyblock_t *),
1094 PU_LEVEL, 0);
1095 memset (PolyBlockMap, 0, bmapwidth*bmapheight*sizeof(polyblock_t *));
1096
1097 for (i = 0; i < po_NumPolyobjs; i++)
1098 {
1099 LinkPolyobj(&polyobjs[i]);
1100 }
1101 }
1102
1103 //
1104 // IterFindPolySegs
1105 //
1106 // Passing NULL for segList will cause IterFindPolySegs to
1107 // count the number of segs in the polyobj
IterFindPolySegs(int x,int y,seg_t ** segList)1108 static void IterFindPolySegs (int x, int y, seg_t **segList)
1109 {
1110 int i;
1111
1112 if (x == PolyStartX && y == PolyStartY)
1113 {
1114 return;
1115 }
1116 for (i = 0; i < numsegs; i++)
1117 {
1118 if (segs[i].v1->x == x && segs[i].v1->y == y)
1119 {
1120 if(!segList)
1121 {
1122 PolySegCount++;
1123 }
1124 else
1125 {
1126 *segList++ = &segs[i];
1127 }
1128 IterFindPolySegs (segs[i].v2->x, segs[i].v2->y, segList);
1129 return;
1130 }
1131 }
1132 I_Error ("IterFindPolySegs: Non-closed Polyobj located.\n");
1133 }
1134
1135 //
1136 // SpawnPolyobj
1137 //
SpawnPolyobj(int index,int tag,BOOL crush)1138 static void SpawnPolyobj (int index, int tag, BOOL crush)
1139 {
1140 int i;
1141 int j;
1142 int psIndex;
1143 int psIndexOld;
1144 seg_t *polySegList[PO_MAXPOLYSEGS];
1145
1146 for (i = 0; i < numsegs; i++)
1147 {
1148 if (segs[i].linedef->special == PO_LINE_START &&
1149 segs[i].linedef->args[0] == tag)
1150 {
1151 if (polyobjs[index].segs)
1152 {
1153 I_Error ("SpawnPolyobj: Polyobj %d already spawned.\n", tag);
1154 }
1155 segs[i].linedef->special = 0;
1156 segs[i].linedef->args[0] = 0;
1157 PolySegCount = 1;
1158 PolyStartX = segs[i].v1->x;
1159 PolyStartY = segs[i].v1->y;
1160 IterFindPolySegs(segs[i].v2->x, segs[i].v2->y, NULL);
1161
1162 polyobjs[index].numsegs = PolySegCount;
1163 polyobjs[index].segs = (seg_t **)Z_Malloc (PolySegCount*sizeof(seg_t *),
1164 PU_LEVEL, 0);
1165 polyobjs[index].segs[0] = &segs[i]; // insert the first seg
1166 IterFindPolySegs (segs[i].v2->x, segs[i].v2->y,
1167 polyobjs[index].segs+1);
1168 polyobjs[index].crush = crush;
1169 polyobjs[index].tag = tag;
1170 polyobjs[index].seqType = segs[i].linedef->args[2];
1171 if (polyobjs[index].seqType < 0 || polyobjs[index].seqType > 63)
1172 {
1173 polyobjs[index].seqType = 0;
1174 }
1175 break;
1176 }
1177 }
1178 if (!polyobjs[index].segs)
1179 { // didn't find a polyobj through PO_LINE_START
1180 psIndex = 0;
1181 polyobjs[index].numsegs = 0;
1182 for (j = 1; j < PO_MAXPOLYSEGS; j++)
1183 {
1184 psIndexOld = psIndex;
1185 for (i = 0; i < numsegs; i++)
1186 {
1187 if (segs[i].linedef->special == PO_LINE_EXPLICIT &&
1188 segs[i].linedef->args[0] == tag)
1189 {
1190 if (!segs[i].linedef->args[1])
1191 {
1192 I_Error ("SpawnPolyobj: Explicit line missing order number (probably %d) in poly %d.\n",
1193 j+1, tag);
1194 }
1195 if (segs[i].linedef->args[1] == j)
1196 {
1197 polySegList[psIndex] = &segs[i];
1198 polyobjs[index].numsegs++;
1199 psIndex++;
1200 if (psIndex > PO_MAXPOLYSEGS)
1201 {
1202 I_Error ("SpawnPolyobj: psIndex > PO_MAXPOLYSEGS\n");
1203 }
1204 }
1205 }
1206 }
1207 // Clear out any specials for these segs...we cannot clear them out
1208 // in the above loop, since we aren't guaranteed one seg per
1209 // linedef.
1210 for (i = 0; i < numsegs; i++)
1211 {
1212 if (segs[i].linedef->special == PO_LINE_EXPLICIT &&
1213 segs[i].linedef->args[0] == tag && segs[i].linedef->args[1] == j)
1214 {
1215 segs[i].linedef->special = 0;
1216 segs[i].linedef->args[0] = 0;
1217 }
1218 }
1219 if (psIndex == psIndexOld)
1220 { // Check if an explicit line order has been skipped
1221 // A line has been skipped if there are any more explicit
1222 // lines with the current tag value
1223 for (i = 0; i < numsegs; i++)
1224 {
1225 if(segs[i].linedef->special == PO_LINE_EXPLICIT &&
1226 segs[i].linedef->args[0] == tag)
1227 {
1228 I_Error ("SpawnPolyobj: Missing explicit line %d for poly %d\n",
1229 j, tag);
1230 }
1231 }
1232 }
1233 }
1234 if (polyobjs[index].numsegs)
1235 {
1236 PolySegCount = polyobjs[index].numsegs; // PolySegCount used globally
1237 polyobjs[index].crush = crush;
1238 polyobjs[index].tag = tag;
1239 polyobjs[index].segs = (seg_t **)Z_Malloc (polyobjs[index].numsegs
1240 *sizeof(seg_t *), PU_LEVEL, 0);
1241 for (i = 0; i < polyobjs[index].numsegs; i++)
1242 {
1243 polyobjs[index].segs[i] = polySegList[i];
1244 }
1245 polyobjs[index].seqType = (*polyobjs[index].segs)->linedef->args[3];
1246 // Next, change the polyobj's first line to point to a mirror
1247 // if it exists
1248 (*polyobjs[index].segs)->linedef->args[1] =
1249 (*polyobjs[index].segs)->linedef->args[2];
1250 }
1251 else
1252 I_Error ("SpawnPolyobj: Poly %d does not exist\n", tag);
1253 }
1254 }
1255
1256 //
1257 // TranslateToStartSpot
1258 //
TranslateToStartSpot(int tag,int originX,int originY)1259 static void TranslateToStartSpot (int tag, int originX, int originY)
1260 {
1261 seg_t **tempSeg;
1262 seg_t **veryTempSeg;
1263 vertex_t *tempPt;
1264 subsector_t *sub;
1265 polyobj_t *po;
1266 int deltaX;
1267 int deltaY;
1268 vertex_t avg; // used to find a polyobj's center, and hence subsector
1269 int i;
1270
1271 po = NULL;
1272 for (i = 0; i < po_NumPolyobjs; i++)
1273 {
1274 if (polyobjs[i].tag == tag)
1275 {
1276 po = &polyobjs[i];
1277 break;
1278 }
1279 }
1280 if (po == NULL)
1281 { // didn't match the tag with a polyobj tag
1282 I_Error("TranslateToStartSpot: Unable to match polyobj tag: %d\n",
1283 tag);
1284 }
1285 if (po->segs == NULL)
1286 {
1287 I_Error ("TranslateToStartSpot: Anchor point located without a StartSpot point: %d\n", tag);
1288 }
1289 po->originalPts = (vertex_t *)Z_Malloc(po->numsegs*sizeof(vertex_t), PU_LEVEL, 0);
1290 po->prevPts = (vertex_t *)Z_Malloc(po->numsegs*sizeof(vertex_t), PU_LEVEL, 0);
1291 deltaX = originX-po->startSpot[0];
1292 deltaY = originY-po->startSpot[1];
1293
1294 tempSeg = po->segs;
1295 tempPt = po->originalPts;
1296 avg.x = 0;
1297 avg.y = 0;
1298
1299 validcount++;
1300 for (i = 0; i < po->numsegs; i++, tempSeg++, tempPt++)
1301 {
1302 if ((*tempSeg)->linedef->validcount != validcount)
1303 {
1304 (*tempSeg)->linedef->bbox[BOXTOP] -= deltaY;
1305 (*tempSeg)->linedef->bbox[BOXBOTTOM] -= deltaY;
1306 (*tempSeg)->linedef->bbox[BOXLEFT] -= deltaX;
1307 (*tempSeg)->linedef->bbox[BOXRIGHT] -= deltaX;
1308 (*tempSeg)->linedef->validcount = validcount;
1309 }
1310 for (veryTempSeg = po->segs; veryTempSeg != tempSeg; veryTempSeg++)
1311 {
1312 if((*veryTempSeg)->v1 == (*tempSeg)->v1)
1313 {
1314 break;
1315 }
1316 }
1317 if (veryTempSeg == tempSeg)
1318 { // the point hasn't been translated, yet
1319 (*tempSeg)->v1->x -= deltaX;
1320 (*tempSeg)->v1->y -= deltaY;
1321 }
1322 avg.x += (*tempSeg)->v1->x>>FRACBITS;
1323 avg.y += (*tempSeg)->v1->y>>FRACBITS;
1324 // the original Pts are based off the startSpot Pt, and are
1325 // unique to each seg, not each linedef
1326 tempPt->x = (*tempSeg)->v1->x-po->startSpot[0];
1327 tempPt->y = (*tempSeg)->v1->y-po->startSpot[1];
1328 }
1329 avg.x /= po->numsegs;
1330 avg.y /= po->numsegs;
1331 sub = P_PointInSubsector (avg.x<<FRACBITS, avg.y<<FRACBITS);
1332 if (sub->poly != NULL)
1333 {
1334 I_Error ("PO_TranslateToStartSpot: Multiple polyobjs in a single subsector.\n");
1335 }
1336 sub->poly = po;
1337 }
1338
1339 //
1340 // PO_Init
1341 //
PO_Init(void)1342 void PO_Init (void)
1343 {
1344 // [RH] Hexen found the polyobject-related things by reloading the map's
1345 // THINGS lump here and scanning through it. I have P_SpawnMapThing()
1346 // record those things instead, so that in here, we simply need to
1347 // look at the polyspawns list.
1348 polyspawns_t *polyspawn, **prev;
1349 int polyIndex;
1350
1351 polyobjs = (polyobj_t *)Z_Malloc (po_NumPolyobjs*sizeof(polyobj_t), PU_LEVEL, 0);
1352 memset (polyobjs, 0, po_NumPolyobjs*sizeof(polyobj_t));
1353
1354 polyIndex = 0; // index polyobj number
1355 // Find the startSpot points, and spawn each polyobj
1356 for (polyspawn = polyspawns, prev = &polyspawns; polyspawn;)
1357 {
1358 // 9301 (3001) = no crush, 9302 (3002) = crushing
1359 if (polyspawn->type == PO_SPAWN_TYPE || polyspawn->type == PO_SPAWNCRUSH_TYPE)
1360 { // Polyobj StartSpot Pt.
1361 polyobjs[polyIndex].startSpot[0] = polyspawn->x;
1362 polyobjs[polyIndex].startSpot[1] = polyspawn->y;
1363 SpawnPolyobj(polyIndex, polyspawn->angle, (polyspawn->type == PO_SPAWNCRUSH_TYPE));
1364 polyIndex++;
1365 *prev = polyspawn->next;
1366 delete polyspawn;
1367 polyspawn = *prev;
1368 } else {
1369 prev = &polyspawn->next;
1370 polyspawn = polyspawn->next;
1371 }
1372 }
1373 for (polyspawn = polyspawns; polyspawn;)
1374 {
1375 polyspawns_t *next = polyspawn->next;
1376 if (polyspawn->type == PO_ANCHOR_TYPE)
1377 { // Polyobj Anchor Pt.
1378 TranslateToStartSpot (polyspawn->angle, polyspawn->x, polyspawn->y);
1379 }
1380 delete polyspawn;
1381 polyspawn = next;
1382 }
1383 polyspawns = NULL;
1384
1385 // check for a startspot without an anchor point
1386 for (polyIndex = 0; polyIndex < po_NumPolyobjs; polyIndex++)
1387 {
1388 if (!polyobjs[polyIndex].originalPts)
1389 {
1390 I_Error ("PO_Init: StartSpot located without an Anchor point: %d\n",
1391 polyobjs[polyIndex].tag);
1392 }
1393 }
1394 InitBlockMap();
1395 }
1396
1397 //
1398 // PO_Busy
1399 //
PO_Busy(int polyobj)1400 BOOL PO_Busy (int polyobj)
1401 {
1402 polyobj_t *poly;
1403
1404 poly = GetPolyobj (polyobj);
1405 if (!poly->specialdata)
1406 {
1407 return false;
1408 }
1409 else
1410 {
1411 return true;
1412 }
1413 }
1414
1415 VERSION_CONTROL (po_man_cpp, "$Id: po_man.cpp 4542 2014-02-09 17:39:42Z dr_sean $")
1416