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