1 /*
2 ** p_linkedsectors.cpp
3 **
4 ** Linked sector movement
5 **
6 **---------------------------------------------------------------------------
7 ** Copyright 2008 Christoph Oelckers
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 **    notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 **    notice, this list of conditions and the following disclaimer in the
18 **    documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 **    derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35 
36 #include "templates.h"
37 #include "p_local.h"
38 #include "p_lnspec.h"
39 
40 enum
41 {
42 	// The different movement types.
43 	LINK_NONE=0,
44 	LINK_FLOOR=1,
45 	LINK_CEILING=2,
46 	LINK_BOTH=3,
47 	LINK_FLOORMIRRORFLAG=4,
48 	LINK_CEILINGMIRRORFLAG=8,
49 
50 	LINK_FLOORMIRROR=5,
51 	LINK_CEILINGMIRROR=10,
52 	LINK_BOTHMIRROR=15,
53 
54 	LINK_FLOORBITS=5,
55 	LINK_CEILINGBITS=10,
56 
57 	LINK_FLAGMASK = 15
58 
59 };
60 
61 
62 //============================================================================
63 //
64 // Checks whether the other sector is linked to this one
65 // Used to ignore linked sectors in the FindNextLowest/Highest*
66 // functions
67 //
68 // NOTE: After looking at Eternity's code I discovered that this check
69 // is not done for the FindNext* function but instead only for
70 // FindLowestCeilingSurrounding where IMO it makes much less sense
71 // because the most frequent use of this feature is most likely lifts
72 // with a more detailed surface. Needs to be investigated!
73 //
74 //============================================================================
75 
IsLinked(sector_t * other,bool ceiling) const76 bool sector_t::IsLinked(sector_t *other, bool ceiling) const
77 {
78 	extsector_t::linked::plane &scrollplane = ceiling? e->Linked.Ceiling : e->Linked.Floor;
79 	int flag = ceiling? LINK_CEILING : LINK_FLOOR;
80 
81 	for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++)
82 	{
83 		if (other == scrollplane.Sectors[i].Sector && scrollplane.Sectors[i].Type & flag) return true;
84 	}
85 	return false;
86 }
87 
88 
89 //============================================================================
90 //
91 // Helper functions for P_MoveLinkedSectors
92 //
93 //============================================================================
94 
MoveCeiling(sector_t * sector,int crush,fixed_t move)95 static bool MoveCeiling(sector_t *sector, int crush, fixed_t move)
96 {
97 	sector->ceilingplane.ChangeHeight (move);
98 	sector->ChangePlaneTexZ(sector_t::ceiling, move);
99 
100 	if (P_ChangeSector(sector, crush, move, 1, true)) return false;
101 
102 	// Don't let the ceiling go below the floor
103 	if ((sector->ceilingplane.a | sector->ceilingplane.b |
104 		 sector->floorplane.a | sector->floorplane.b) == 0 &&
105 		 sector->GetPlaneTexZ(sector_t::floor)  > sector->GetPlaneTexZ(sector_t::ceiling)) return false;
106 
107 	return true;
108 }
109 
MoveFloor(sector_t * sector,int crush,fixed_t move)110 static bool MoveFloor(sector_t *sector, int crush, fixed_t move)
111 {
112 	sector->floorplane.ChangeHeight (move);
113 	sector->ChangePlaneTexZ(sector_t::floor, move);
114 
115 	if (P_ChangeSector(sector, crush, move, 0, true)) return false;
116 
117 	// Don't let the floor go above the ceiling
118 	if ((sector->ceilingplane.a | sector->ceilingplane.b |
119 		 sector->floorplane.a | sector->floorplane.b) == 0 &&
120 		 sector->GetPlaneTexZ(sector_t::floor) > sector->GetPlaneTexZ(sector_t::ceiling)) return false;
121 
122 	return true;
123 }
124 
125 //============================================================================
126 //
127 // P_MoveLinkedSectors
128 //
129 // Moves all floors/ceilings linked to the control sector
130 // Important: All sectors must complete their movement
131 // even if a previous one already failed.
132 //
133 //============================================================================
134 
P_MoveLinkedSectors(sector_t * sector,int crush,fixed_t move,bool ceiling)135 bool P_MoveLinkedSectors(sector_t *sector, int crush, fixed_t move, bool ceiling)
136 {
137 	extsector_t::linked::plane &scrollplane = ceiling? sector->e->Linked.Ceiling : sector->e->Linked.Floor;
138 	bool ok = true;
139 
140 	for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++)
141 	{
142 		switch(scrollplane.Sectors[i].Type)
143 		{
144 		case LINK_FLOOR:
145 			ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move);
146 			break;
147 
148 		case LINK_CEILING:
149 			ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move);
150 			break;
151 
152 		case LINK_BOTH:
153 			if (move < 0)
154 			{
155 				ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move);
156 				ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move);
157 			}
158 			else
159 			{
160 				ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move);
161 				ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move);
162 			}
163 			break;
164 
165 		case LINK_FLOORMIRROR:
166 			ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move);
167 			break;
168 
169 		case LINK_CEILINGMIRROR:
170 			ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move);
171 			break;
172 
173 		case LINK_BOTHMIRROR:
174 			if (move > 0)
175 			{
176 				ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move);
177 				ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move);
178 			}
179 			else
180 			{
181 				ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move);
182 				ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move);
183 			}
184 			break;
185 
186 		case LINK_FLOOR+LINK_CEILINGMIRROR:
187 			ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move);
188 			ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move);
189 			break;
190 
191 		case LINK_CEILING+LINK_FLOORMIRROR:
192 			ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move);
193 			ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move);
194 			break;
195 
196 		default:
197 			// all other types are invalid and have to be elimintated in the attachment stage
198 			break;
199 		}
200 	}
201 	return ok;
202 }
203 
204 //============================================================================
205 //
206 // P_StartLinkedSectorInterpolations
207 //
208 // Starts interpolators for every sector plane is being changed by moving
209 // this sector
210 //
211 //============================================================================
212 
P_StartLinkedSectorInterpolations(TArray<DInterpolation * > & list,sector_t * sector,bool ceiling)213 void P_StartLinkedSectorInterpolations(TArray<DInterpolation *> &list, sector_t *sector, bool ceiling)
214 {
215 	extsector_t::linked::plane &scrollplane = ceiling? sector->e->Linked.Ceiling : sector->e->Linked.Floor;
216 
217 	for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++)
218 	{
219 		if (scrollplane.Sectors[i].Type & LINK_FLOOR)
220 		{
221 			list.Push(scrollplane.Sectors[i].Sector->SetInterpolation(sector_t::FloorMove, false));
222 		}
223 		if (scrollplane.Sectors[i].Type & LINK_CEILING)
224 		{
225 			list.Push(scrollplane.Sectors[i].Sector->SetInterpolation(sector_t::CeilingMove, false));
226 		}
227 	}
228 }
229 
230 //============================================================================
231 //
232 // AddSingleSector
233 //
234 // Adds a single sector to a scroll plane. Checks for invalid
235 // flag combinations if the sector is already added and removes it if necessary.
236 //
237 //============================================================================
238 
AddSingleSector(extsector_t::linked::plane & scrollplane,sector_t * sector,int movetype)239 static void AddSingleSector(extsector_t::linked::plane &scrollplane, sector_t *sector, int movetype)
240 {
241 	// First we have to check the list if the sector is already in it
242 	// If so the move type must be adjusted
243 
244 	for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++)
245 	{
246 		if (scrollplane.Sectors[i].Sector == sector)
247 		{
248 			if (movetype & LINK_FLOORBITS)
249 			{
250 				scrollplane.Sectors[i].Type &= ~LINK_FLOORBITS;
251 			}
252 
253 			if (movetype & LINK_CEILINGBITS)
254 			{
255 				scrollplane.Sectors[i].Type &= ~LINK_CEILINGBITS;
256 			}
257 
258 			scrollplane.Sectors[i].Type |= movetype;
259 			return;
260 		}
261 	}
262 
263 	// The sector hasn't been attached yet so do it now
264 
265 	FLinkedSector newlink = {sector, movetype};
266 	scrollplane.Sectors.Push(newlink);
267 }
268 
269 //============================================================================
270 //
271 // RemoveTaggedSectors
272 //
273 // Remove all sectors with the given tag from the link list
274 //
275 //============================================================================
276 
RemoveTaggedSectors(extsector_t::linked::plane & scrollplane,int tag)277 static void RemoveTaggedSectors(extsector_t::linked::plane &scrollplane, int tag)
278 {
279 	for(int i = scrollplane.Sectors.Size()-1; i>=0; i--)
280 	{
281 		if (tagManager.SectorHasTag(scrollplane.Sectors[i].Sector, tag))
282 		{
283 			scrollplane.Sectors.Delete(i);
284 		}
285 	}
286 }
287 
288 
289 
290 //============================================================================
291 //
292 // P_AddSectorLink
293 //
294 // Links sector planes to a control sector based on the sector's tag
295 //
296 //============================================================================
297 
P_AddSectorLinks(sector_t * control,int tag,INTBOOL ceiling,int movetype)298 bool P_AddSectorLinks(sector_t *control, int tag, INTBOOL ceiling, int movetype)
299 {
300 	int param = movetype;
301 
302 	// can't be done if the control sector is moving.
303 	if ((ceiling && control->PlaneMoving(sector_t::ceiling)) ||
304 		(!ceiling && control->PlaneMoving(sector_t::floor))) return false;
305 
306 	// Make sure we have only valid combinations
307 	movetype &= LINK_FLAGMASK;
308 	if ((movetype & LINK_FLOORMIRROR) == LINK_FLOORMIRRORFLAG) movetype &= ~LINK_FLOORMIRRORFLAG;
309 	if ((movetype & LINK_CEILINGMIRROR) == LINK_CEILINGMIRRORFLAG) movetype &= ~LINK_CEILINGMIRRORFLAG;
310 
311 	// Don't remove any sector if the parameter is invalid.
312 	// Addition may still be performed based on the given value.
313 	if (param != 0 && movetype == 0) return false;
314 
315 	extsector_t::linked::plane &scrollplane = ceiling? control->e->Linked.Ceiling : control->e->Linked.Floor;
316 
317 	if (movetype > 0)
318 	{
319 		int sec;
320 		FSectorTagIterator itr(tag);
321 		while ((sec = itr.Next()) >= 0)
322 		{
323 			// Don't attach to self!
324 			if (control != &sectors[sec])
325 			{
326 				AddSingleSector(scrollplane, &sectors[sec], movetype);
327 			}
328 		}
329 	}
330 	else
331 	{
332 		RemoveTaggedSectors(scrollplane, tag);
333 	}
334 	return true;
335 }
336 
337 //============================================================================
338 //
339 // P_AddSectorLinksByID
340 //
341 // Links sector planes to a control sector based on a control linedef
342 // touching the sectors. This is the method Eternity uses and is here
343 // mostly so that the Eternity line types can be emulated
344 //
345 //============================================================================
346 
P_AddSectorLinksByID(sector_t * control,int id,INTBOOL ceiling)347 void P_AddSectorLinksByID(sector_t *control, int id, INTBOOL ceiling)
348 {
349 	extsector_t::linked::plane &scrollplane = ceiling? control->e->Linked.Ceiling : control->e->Linked.Floor;
350 
351 	FLineIdIterator itr(id);
352 	int line;
353 	while ((line = itr.Next()) >= 0)
354 	{
355 		line_t *ld = &lines[line];
356 
357 		if (ld->special == Static_Init && ld->args[1] == Init_SectorLink)
358 		{
359 			int movetype = ld->args[3];
360 
361 			// [GZ] Eternity does allow the attached sector to be the control sector,
362 			// this permits elevator effects (ceiling attached to floors), so instead
363 			// of checking whether the two sectors are the same, we prevent a plane
364 			// from being attached to itself. This should be enough to do the trick.
365 			if (ld->frontsector == control)
366 			{
367 				if (ceiling)	movetype &= ~LINK_CEILING;
368 				else			movetype &= ~LINK_FLOOR;
369 			}
370 
371 			// Make sure we have only valid combinations
372 			movetype &= LINK_FLAGMASK;
373 			if ((movetype & LINK_FLOORMIRROR) == LINK_FLOORMIRRORFLAG) movetype &= ~LINK_FLOORMIRRORFLAG;
374 			if ((movetype & LINK_CEILINGMIRROR) == LINK_CEILINGMIRRORFLAG) movetype &= ~LINK_CEILINGMIRRORFLAG;
375 
376 			if (movetype != 0 && ld->frontsector != NULL)//&& ld->frontsector != control) Needs to be allowed!
377 			{
378 				AddSingleSector(scrollplane, ld->frontsector, movetype);
379 			}
380 		}
381 	}
382 }
383