1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 	Copyright (C) 1995 Ronny Wester
5 	Copyright (C) 2003 Jeremy Chin
6 	Copyright (C) 2003-2007 Lucas Martin-King
7 
8 	This program is free software; you can redistribute it and/or modify
9 	it under the terms of the GNU General Public License as published by
10 	the Free Software Foundation; either version 2 of the License, or
11 	(at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	GNU General Public License for more details.
17 
18 	You should have received a copy of the GNU General Public License
19 	along with this program; if not, write to the Free Software
20 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 	This file incorporates work covered by the following copyright and
23 	permission notice:
24 
25 	Copyright (c) 2013-2015, 2018-2021 Cong Xu
26 	All rights reserved.
27 
28 	Redistribution and use in source and binary forms, with or without
29 	modification, are permitted provided that the following conditions are met:
30 
31 	Redistributions of source code must retain the above copyright notice, this
32 	list of conditions and the following disclaimer.
33 	Redistributions in binary form must reproduce the above copyright notice,
34 	this list of conditions and the following disclaimer in the documentation
35 	and/or other materials provided with the distribution.
36 
37 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 	POSSIBILITY OF SUCH DAMAGE.
48 */
49 #include "door.h"
50 
51 #include "gamedata.h"
52 #include "log.h"
53 #include "net_util.h"
54 
GetDoorType(const bool isHorizontal,const int i,const int count)55 static DoorType GetDoorType(
56 	const bool isHorizontal, const int i, const int count)
57 {
58 	if (isHorizontal)
59 	{
60 		if (count == 1)
61 		{
62 			return DOORTYPE_H;
63 		}
64 		else if (i == 0)
65 		{
66 			return DOORTYPE_LEFT;
67 		}
68 		else if (i == count - 1)
69 		{
70 			return DOORTYPE_RIGHT;
71 		}
72 		else
73 		{
74 			return DOORTYPE_HMID;
75 		}
76 	}
77 	else
78 	{
79 		if (count == 1)
80 		{
81 			return DOORTYPE_V;
82 		}
83 		else if (i == 0)
84 		{
85 			return DOORTYPE_TOP;
86 		}
87 		else if (i == count - 1)
88 		{
89 			return DOORTYPE_BOTTOM;
90 		}
91 		else
92 		{
93 			return DOORTYPE_VMID;
94 		}
95 	}
96 }
DoorTypeIsHorizontal(const DoorType type)97 static bool DoorTypeIsHorizontal(const DoorType type)
98 {
99 	return type == DOORTYPE_H || type == DOORTYPE_LEFT ||
100 		   type == DOORTYPE_HMID || type == DOORTYPE_RIGHT;
101 }
102 
103 static bool DoorGroupIsHorizontal(const MapBuilder *mb, const struct vec2i v);
104 static void DoorGetClassName(
105 	char *buf, const TileClass *door, const char *key, const DoorType dType);
106 static int GetDoorCountInGroup(
107 	const MapBuilder *mb, const struct vec2i v, const bool isHorizontal);
108 static TWatch *CreateCloseDoorWatch(
109 	MapBuilder *mb, const struct vec2i v, const bool isHorizontal,
110 	const int doorGroupCount, const char *doorKey);
111 static Trigger *CreateOpenDoorTrigger(
112 	MapBuilder *mb, const struct vec2i v, const bool isHorizontal,
113 	const int doorGroupCount, const int keyFlags);
MapAddDoorGroup(MapBuilder * mb,const struct vec2i v,const int keyFlags)114 struct vec2i MapAddDoorGroup(MapBuilder *mb, const struct vec2i v, const int keyFlags)
115 {
116 	const TileClass *door = MapBuilderGetTile(mb, v);
117 	const bool isHorizontal = DoorGroupIsHorizontal(mb, v);
118 	const int doorGroupCount = GetDoorCountInGroup(mb, v, isHorizontal);
119 	const struct vec2i dv = svec2i(isHorizontal ? 1 : 0, isHorizontal ? 0 : 1);
120 	const struct vec2i dAside = svec2i(dv.y, dv.x);
121 
122 	const char *doorKey;
123 	switch (keyFlags)
124 	{
125 	case FLAGS_KEYCARD_RED:
126 		doorKey = "red";
127 		break;
128 	case FLAGS_KEYCARD_BLUE:
129 		doorKey = "blue";
130 		break;
131 	case FLAGS_KEYCARD_GREEN:
132 		doorKey = "green";
133 		break;
134 	case FLAGS_KEYCARD_YELLOW:
135 		doorKey = "yellow";
136 		break;
137 	default:
138 		doorKey = "normal";
139 		break;
140 	}
141 
142 	// set up the door pics
143 	for (int i = 0; i < doorGroupCount; i++)
144 	{
145 		char doorClassName[CDOGS_FILENAME_MAX];
146 		const DoorType type = GetDoorType(isHorizontal, i, doorGroupCount);
147 		DoorGetClassName(doorClassName, door, doorKey, type);
148 		const TileClass *doorClass = StrTileClass(doorClassName);
149 		DoorGetClassName(doorClassName, door, "open", type);
150 		const TileClass *doorClassOpen = StrTileClass(doorClassName);
151 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
152 		Tile *tile = MapGetTile(mb->Map, vI);
153 		tile->ClassAlt = doorClass;
154 		tile->Class = doorClassOpen;
155 		if (isHorizontal)
156 		{
157 			const struct vec2i vB = svec2i_add(vI, dAside);
158 			Tile *tileB = MapGetTile(mb->Map, vB);
159 			if (!TileCanWalk(MapGetTile(
160 					mb->Map, svec2i(vI.x - dAside.x, vI.y - dAside.y))))
161 			{
162 				LOG(LM_MAP, LL_ERROR,
163 					"map gen error: entrance above should be clear");
164 			}
165 			if (TileCanWalk(tileB))
166 			{
167 				// Change the tile below to shadow, cast by this door
168 				tileB->Class = TileClassesGetMaskedTile(
169 					tileB->Class, tileB->Class->Style, "shadow",
170 					tileB->Class->Mask, tileB->Class->MaskAlt);
171 			}
172 			else
173 			{
174 				LOG(LM_MAP, LL_ERROR,
175 					"map gen error: entrance below should be clear");
176 			}
177 		}
178 	}
179 
180 	TWatch *w =
181 		CreateCloseDoorWatch(mb, v, isHorizontal, doorGroupCount, doorKey);
182 	Trigger *t =
183 		CreateOpenDoorTrigger(mb, v, isHorizontal, doorGroupCount, keyFlags);
184 	// Connect trigger and watch up
185 	Action *a = TriggerAddAction(t);
186 	a->Type = ACTION_ACTIVATEWATCH;
187 	a->u.index = w->index;
188 	a = WatchAddAction(w);
189 	a->Type = ACTION_SETTRIGGER;
190 	a->u.index = t->id;
191 
192 	// Set tiles on and besides doors free
193 	for (int i = 0; i < doorGroupCount; i++)
194 	{
195 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
196 		MapBuilderSetLeaveFree(mb, vI, true);
197 		const struct vec2i vI1 = svec2i_add(vI, dAside);
198 		MapBuilderSetLeaveFree(mb, vI1, true);
199 		const struct vec2i vI2 = svec2i_subtract(vI, dAside);
200 		MapBuilderSetLeaveFree(mb, vI2, true);
201 	}
202 
203 	return isHorizontal ? svec2i(doorGroupCount, 1) : svec2i(1, doorGroupCount);
204 }
DoorGroupIsHorizontal(const MapBuilder * mb,const struct vec2i v)205 static bool DoorGroupIsHorizontal(const MapBuilder *mb, const struct vec2i v)
206 {
207 	const TileClass *tileLeftType =
208 		MapBuilderGetTile(mb, svec2i(v.x - 1, v.y));
209 	const TileClass *tileRightType =
210 		MapBuilderGetTile(mb, svec2i(v.x + 1, v.y));
211 	const TileClass *tileAboveType =
212 		MapBuilderGetTile(mb, svec2i(v.x, v.y - 1));
213 	const TileClass *tileBelowType =
214 		MapBuilderGetTile(mb, svec2i(v.x, v.y + 1));
215 	const bool tileLeftIsDoor =
216 		tileLeftType != NULL && tileLeftType->Type == TILE_CLASS_DOOR;
217 	const bool tileRightIsDoor =
218 		tileRightType != NULL && tileRightType->Type == TILE_CLASS_DOOR;
219 	const bool tileAboveIsDoor =
220 		tileAboveType != NULL && tileAboveType->Type == TILE_CLASS_DOOR;
221 	const bool tileBelowIsDoor =
222 		tileBelowType != NULL && tileBelowType->Type == TILE_CLASS_DOOR;
223 	const bool tileLeftCanWalk = tileLeftType != NULL && tileLeftType->canWalk;
224 	const bool tileRightCanWalk =
225 		tileRightType != NULL && tileRightType->canWalk;
226 	const bool tileAboveCanWalk = tileAboveType != NULL && tileAboveType->canWalk;
227 	const bool tileBelowCanWalk =
228 		tileBelowType != NULL && tileBelowType->canWalk;
229 	// If door is free all around, follow doors
230 	if (tileAboveCanWalk && tileBelowCanWalk && tileLeftCanWalk && tileRightCanWalk)
231 	{
232 		if (tileLeftIsDoor && tileRightIsDoor)
233 		{
234 			return true;
235 		}
236 		if (tileAboveIsDoor && tileBelowIsDoor)
237 		{
238 			return false;
239 		}
240 		return tileLeftIsDoor || tileRightIsDoor;
241 	}
242 	// If door is free both above/below or both left/right
243 	if (tileAboveCanWalk && tileBelowCanWalk)
244 	{
245 		return true;
246 	}
247 	if (tileLeftCanWalk && tileRightCanWalk)
248 	{
249 		return false;
250 	}
251 	// If door is free only one of above/below/left/right
252 	if (tileAboveCanWalk || tileBelowCanWalk)
253 	{
254 		return true;
255 	}
256 	// If there are doors, don't follow them - door layers
257 	if (tileAboveIsDoor || tileBelowIsDoor)
258 	{
259 		return true;
260 	}
261 	return false;
262 }
263 
264 // Count the number of doors that are in the same group as this door
265 // Only check to the right/below
GetDoorCountInGroup(const MapBuilder * mb,const struct vec2i v,const bool isHorizontal)266 static int GetDoorCountInGroup(
267 	const MapBuilder *mb, const struct vec2i v, const bool isHorizontal)
268 {
269 	const struct vec2i dv = svec2i(isHorizontal ? 1 : 0, isHorizontal ? 0 : 1);
270 	int count = 0;
271 	for (struct vec2i vi = v;
272 		 MapIsTileIn(mb->Map, vi) &&
273 		 MapBuilderGetTile(mb, vi)->Type == TILE_CLASS_DOOR;
274 		 vi = svec2i_add(vi, dv))
275 	{
276 		count++;
277 	}
278 	return count;
279 }
280 // Create the watch responsible for closing the door
CreateCloseDoorWatch(MapBuilder * mb,const struct vec2i v,const bool isHorizontal,const int doorGroupCount,const char * doorKey)281 static TWatch *CreateCloseDoorWatch(
282 	MapBuilder *mb, const struct vec2i v, const bool isHorizontal,
283 	const int doorGroupCount, const char *doorKey)
284 {
285 	TWatch *w = WatchNew();
286 	const struct vec2i dv = svec2i(isHorizontal ? 1 : 0, isHorizontal ? 0 : 1);
287 	const struct vec2i dAside = svec2i(dv.y, dv.x);
288 
289 	// The conditions are that the tile above, at and below the doors are empty
290 	for (int i = 0; i < doorGroupCount; i++)
291 	{
292 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
293 
294 		for (int j = -1; j <= 1; j++)
295 		{
296 			WatchAddCondition(
297 				w, CONDITION_TILECLEAR, gCampaign.Setting.DoorOpenTicks,
298 				svec2i_add(vI, svec2i_scale(dAside, (float)j)));
299 		}
300 	}
301 
302 	// Now the actions of the watch once it's triggered
303 	Action *a;
304 
305 	// Deactivate itself
306 	a = WatchAddAction(w);
307 	a->Type = ACTION_DEACTIVATEWATCH;
308 	a->u.index = w->index;
309 	// play close sound at the center of the door group
310 	a = WatchAddAction(w);
311 	a->Type = ACTION_EVENT;
312 	a->u.Event = GameEventNew(GAME_EVENT_SOUND_AT);
313 	strcpy(a->u.Event.u.SoundAt.Sound, "door_close");
314 	a->u.Event.u.SoundAt.Pos = Vec2ToNet(Vec2CenterOfTile(svec2i_add(v, svec2i_scale(dv, (float)doorGroupCount / 2))));
315 
316 	// Close doors
317 	const TileClass *door = MapBuilderGetTile(mb, v);
318 	for (int i = 0; i < doorGroupCount; i++)
319 	{
320 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
321 
322 		a = WatchAddAction(w);
323 		a->Type = ACTION_EVENT;
324 		a->u.Event = GameEventNew(GAME_EVENT_TILE_SET);
325 		a->u.Event.u.TileSet.Pos = Vec2i2Net(vI);
326 		const DoorType type = GetDoorType(isHorizontal, i, doorGroupCount);
327 		DoorGetClassName(
328 			a->u.Event.u.TileSet.ClassName, door, "open", type);
329 
330 		char doorClassName[CDOGS_FILENAME_MAX];
331 		DoorGetClassName(doorClassName, door, doorKey, type);
332 		strcpy(a->u.Event.u.TileSet.ClassAltName, doorClassName);
333 	}
334 
335 	// Add shadows below doors
336 	if (isHorizontal)
337 	{
338 		for (int i = 0; i < doorGroupCount; i++)
339 		{
340 			const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
341 
342 			a = WatchAddAction(w);
343 			a->Type = ACTION_EVENT;
344 			a->u.Event = GameEventNew(GAME_EVENT_TILE_SET);
345 			const struct vec2i vI2 = svec2i(vI.x + dAside.x, vI.y + dAside.y);
346 			a->u.Event.u.TileSet.Pos = Vec2i2Net(vI2);
347 			const TileClass *t = MapBuilderGetTile(mb, vI2);
348 			TileClassGetName(
349 				a->u.Event.u.TileSet.ClassName, t, t->Style, "shadow", t->Mask,
350 				t->MaskAlt);
351 		}
352 	}
353 
354 	return w;
355 }
356 static void TileAddTrigger(Tile *t, Trigger *tr);
CreateOpenDoorTrigger(MapBuilder * mb,const struct vec2i v,const bool isHorizontal,const int doorGroupCount,const int keyFlags)357 static Trigger *CreateOpenDoorTrigger(
358 	MapBuilder *mb, const struct vec2i v, const bool isHorizontal,
359 	const int doorGroupCount, const int keyFlags)
360 {
361 	// All tiles on either side of the door group use the same trigger
362 	const struct vec2i dv = svec2i(isHorizontal ? 1 : 0, isHorizontal ? 0 : 1);
363 	const struct vec2i dAside = svec2i(dv.y, dv.x);
364 	Trigger *t = MapNewTrigger(mb->Map);
365 	t->flags = keyFlags;
366 
367 	// Deactivate itself
368 	Action *a;
369 	a = TriggerAddAction(t);
370 	a->Type = ACTION_CLEARTRIGGER;
371 	a->u.index = t->id;
372 
373 	// Open doors
374 	const TileClass *door = MapBuilderGetTile(mb, v);
375 	for (int i = 0; i < doorGroupCount; i++)
376 	{
377 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
378 		a = TriggerAddAction(t);
379 		a->Type = ACTION_EVENT;
380 		a->u.Event = GameEventNew(GAME_EVENT_TILE_SET);
381 		a->u.Event.u.TileSet.Pos = Vec2i2Net(vI);
382 		const DoorType type = GetDoorType(isHorizontal, i, doorGroupCount);
383 		DoorGetClassName(
384 			a->u.Event.u.TileSet.ClassName, door, "open", type);
385 		if (type == DOORTYPE_TOP || type == DOORTYPE_V)
386 		{
387 			// special door cavity picture
388 			DoorGetClassName(
389 				a->u.Event.u.TileSet.ClassAltName, door, "wall", type);
390 		}
391 	}
392 
393 	// Change tiles below the doors
394 	if (isHorizontal)
395 	{
396 		for (int i = 0; i < doorGroupCount; i++)
397 		{
398 			const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
399 			const struct vec2i vIAside = svec2i_add(vI, dAside);
400 			a = TriggerAddAction(t);
401 			// Remove shadows below doors
402 			a->Type = ACTION_EVENT;
403 			a->u.Event = GameEventNew(GAME_EVENT_TILE_SET);
404 			const TileClass *tc = MapBuilderGetTile(mb, vIAside);
405 			a->u.Event.u.TileSet.Pos = Vec2i2Net(vIAside);
406 			TileClassGetName(
407 				a->u.Event.u.TileSet.ClassName, tc, tc->Style, "normal",
408 				tc->Mask, tc->MaskAlt);
409 		}
410 	}
411 
412 	// Now place the two triggers on the tiles along either side of the doors
413 	for (int i = 0; i < doorGroupCount; i++)
414 	{
415 		const struct vec2i vI = svec2i_add(v, svec2i_scale(dv, (float)i));
416 		const struct vec2i vIA = svec2i_subtract(vI, dAside);
417 		TileAddTrigger(MapGetTile(mb->Map, vIA), t);
418 		const struct vec2i vIB = svec2i_add(vI, dAside);
419 		TileAddTrigger(MapGetTile(mb->Map, vIB), t);
420 	}
421 
422 	/// play sound at the center of the door group
423 	a = TriggerAddAction(t);
424 	a->Type = ACTION_EVENT;
425 	a->u.Event = GameEventNew(GAME_EVENT_SOUND_AT);
426 	strcpy(a->u.Event.u.SoundAt.Sound, "door");
427 	a->u.Event.u.SoundAt.Pos = Vec2ToNet(Vec2CenterOfTile(svec2i_add(v, svec2i_scale(dv, (float)doorGroupCount / 2))));
428 
429 	return t;
430 }
TileAddTrigger(Tile * t,Trigger * tr)431 static void TileAddTrigger(Tile *t, Trigger *tr)
432 {
433 	if (t == NULL) return;
434 	CArrayPushBack(&t->triggers, &tr);
435 }
436 
437 // Get the tile class of a door; if it doesn't exist create it
438 // style: office/dungeon/blast/alien, or custom
439 // key: normal/yellow/green/blue/red/wall/open
DoorGetTypeName(char * buf,const char * key,const DoorType type)440 static void DoorGetTypeName(char *buf, const char *key, const DoorType type)
441 {
442 	const char *typeStr = "";
443 	if (strcmp(key, "wall") == 0)
444 	{
445 		// no change
446 	}
447 	else if (strcmp(key, "open") == 0)
448 	{
449 		typeStr = DoorTypeIsHorizontal(type) ? "_h" : "_v";
450 	}
451 	else
452 	{
453 		switch (type)
454 		{
455 		case DOORTYPE_H:
456 			typeStr = "_h";
457 			break;
458 		case DOORTYPE_LEFT:
459 			typeStr = "_left";
460 			break;
461 		case DOORTYPE_HMID:
462 			typeStr = "_hmid";
463 			break;
464 		case DOORTYPE_RIGHT:
465 			typeStr = "_right";
466 			break;
467 		case DOORTYPE_V:
468 			typeStr = "_v";
469 			break;
470 		case DOORTYPE_TOP:
471 			typeStr = "_top";
472 			break;
473 		case DOORTYPE_VMID:
474 			typeStr = "_vmid";
475 			break;
476 		case DOORTYPE_BOTTOM:
477 			typeStr = "_bottom";
478 			break;
479 		default:
480 			CASSERT(false, "unknown doortype");
481 			break;
482 		}
483 	}
484 	sprintf(buf, "%s%s", key, typeStr);
485 }
DoorGetClassName(char * buf,const TileClass * door,const char * key,const DoorType dType)486 static void DoorGetClassName(
487 	char *buf, const TileClass *door, const char *key, const DoorType dType)
488 {
489 	char type[256];
490 	DoorGetTypeName(type, key, dType);
491 	const color_t mask = strcmp(key, "normal") == 0 ? door->Mask : colorWhite;
492 	const color_t maskAlt = strcmp(key, "normal") == 0 ? door->MaskAlt : colorWhite;
493 	// If the key is "wall", it doesn't include orientation
494 	TileClassGetName(buf, door, door->Style, type, mask, maskAlt);
495 }
DoorAddClass(TileClasses * c,PicManager * pm,const TileClass * base,const char * key,const DoorType type)496 void DoorAddClass(
497 	TileClasses *c, PicManager *pm, const TileClass *base, const char *key,
498 	const DoorType type)
499 {
500 	char buf[CDOGS_FILENAME_MAX];
501 	DoorGetTypeName(buf, key, type);
502 	const color_t mask = strcmp(key, "normal") == 0 ? base->Mask : colorWhite;
503 	const color_t maskAlt = strcmp(key, "normal") == 0 ? base->MaskAlt : colorWhite;
504 	PicManagerGenerateMaskedStylePic(
505 		pm, "door", base->Style, buf, mask, maskAlt, true);
506 	TileClass *t =
507 		TileClassesAdd(c, pm, base, base->Style, buf, mask, maskAlt);
508 	CASSERT(t != NULL, "cannot add door class");
509 	const bool isOpenOrWallCavity =
510 		strcmp(key, "open") == 0 || strcmp(key, "wall") == 0;
511 	t->isOpaque = !isOpenOrWallCavity;
512 	t->canWalk = isOpenOrWallCavity;
513 	t->shootable = !isOpenOrWallCavity;
514 }
515 
516 #define DOORSTYLE_COUNT 4
IntDoorStyle(const int i)517 const char *IntDoorStyle(const int i)
518 {
519 	static const char *doorStyles[] = {"office", "dungeon", "blast", "alien"};
520 	// fix bugs with old campaigns
521 	return doorStyles[abs(i) % DOORSTYLE_COUNT];
522 }
523