1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
4 // Copyright (C) 2012-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 m_cond.c
11 /// \brief Unlockable condition system for SRB2 version 2.1
12
13 #include "m_cond.h"
14 #include "doomstat.h"
15 #include "z_zone.h"
16
17 #include "hu_stuff.h" // CEcho
18 #include "v_video.h" // video flags
19
20 #include "g_game.h" // record info
21 #include "r_skins.h" // numskins
22 #include "r_draw.h" // R_GetColorByName
23
24 // Map triggers for linedef executors
25 // 32 triggers, one bit each
26 UINT32 unlocktriggers;
27
28 // The meat of this system lies in condition sets
29 conditionset_t conditionSets[MAXCONDITIONSETS];
30
31 // Emblem locations
32 emblem_t emblemlocations[MAXEMBLEMS];
33
34 // Extra emblems
35 extraemblem_t extraemblems[MAXEXTRAEMBLEMS];
36
37 // Unlockables
38 unlockable_t unlockables[MAXUNLOCKABLES];
39
40 // Number of emblems and extra emblems
41 INT32 numemblems = 0;
42 INT32 numextraemblems = 0;
43
M_AddRawCondition(UINT8 set,UINT8 id,conditiontype_t c,INT32 r,INT16 x1,INT16 x2)44 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
45 {
46 condition_t *cond;
47 UINT32 num, wnum;
48
49 I_Assert(set && set <= MAXCONDITIONSETS);
50
51 wnum = conditionSets[set - 1].numconditions;
52 num = ++conditionSets[set - 1].numconditions;
53
54 conditionSets[set - 1].condition = Z_Realloc(conditionSets[set - 1].condition, sizeof(condition_t)*num, PU_STATIC, 0);
55
56 cond = conditionSets[set - 1].condition;
57
58 cond[wnum].id = id;
59 cond[wnum].type = c;
60 cond[wnum].requirement = r;
61 cond[wnum].extrainfo1 = x1;
62 cond[wnum].extrainfo2 = x2;
63 }
64
M_ClearConditionSet(UINT8 set)65 void M_ClearConditionSet(UINT8 set)
66 {
67 if (conditionSets[set - 1].numconditions)
68 {
69 Z_Free(conditionSets[set - 1].condition);
70 conditionSets[set - 1].condition = NULL;
71 conditionSets[set - 1].numconditions = 0;
72 }
73 conditionSets[set - 1].achieved = false;
74 }
75
76 // Clear ALL secrets.
M_ClearSecrets(void)77 void M_ClearSecrets(void)
78 {
79 INT32 i;
80
81 memset(mapvisited, 0, sizeof(mapvisited));
82
83 for (i = 0; i < MAXEMBLEMS; ++i)
84 emblemlocations[i].collected = false;
85 for (i = 0; i < MAXEXTRAEMBLEMS; ++i)
86 extraemblems[i].collected = false;
87 for (i = 0; i < MAXUNLOCKABLES; ++i)
88 unlockables[i].unlocked = false;
89 for (i = 0; i < MAXCONDITIONSETS; ++i)
90 conditionSets[i].achieved = false;
91
92 timesBeaten = timesBeatenWithEmeralds = timesBeatenUltimate = 0;
93
94 // Re-unlock any always unlocked things
95 M_SilentUpdateUnlockablesAndEmblems();
96 }
97
98 // ----------------------
99 // Condition set checking
100 // ----------------------
M_CheckCondition(condition_t * cn)101 static UINT8 M_CheckCondition(condition_t *cn)
102 {
103 switch (cn->type)
104 {
105 case UC_PLAYTIME: // Requires total playing time >= x
106 return (totalplaytime >= (unsigned)cn->requirement);
107 case UC_GAMECLEAR: // Requires game beaten >= x times
108 return (timesBeaten >= (unsigned)cn->requirement);
109 case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times
110 return (timesBeatenWithEmeralds >= (unsigned)cn->requirement);
111 case UC_ULTIMATECLEAR: // Requires game beaten on ultimate >= x times (in other words, never)
112 return (timesBeatenUltimate >= (unsigned)cn->requirement);
113 case UC_OVERALLSCORE: // Requires overall score >= x
114 return (M_GotHighEnoughScore(cn->requirement));
115 case UC_OVERALLTIME: // Requires overall time <= x
116 return (M_GotLowEnoughTime(cn->requirement));
117 case UC_OVERALLRINGS: // Requires overall rings >= x
118 return (M_GotHighEnoughRings(cn->requirement));
119 case UC_MAPVISITED: // Requires map x to be visited
120 return ((mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED);
121 case UC_MAPBEATEN: // Requires map x to be beaten
122 return ((mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN);
123 case UC_MAPALLEMERALDS: // Requires map x to be beaten with all emeralds in possession
124 return ((mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS);
125 case UC_MAPULTIMATE: // Requires map x to be beaten on ultimate
126 return ((mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE);
127 case UC_MAPPERFECT: // Requires map x to be beaten with a perfect bonus
128 return ((mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT);
129 case UC_MAPSCORE: // Requires score on map >= x
130 return (G_GetBestScore(cn->extrainfo1) >= (unsigned)cn->requirement);
131 case UC_MAPTIME: // Requires time on map <= x
132 return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
133 case UC_MAPRINGS: // Requires rings on map >= x
134 return (G_GetBestRings(cn->extrainfo1) >= cn->requirement);
135 case UC_NIGHTSSCORE:
136 return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2) >= (unsigned)cn->requirement);
137 case UC_NIGHTSTIME:
138 return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2) <= (unsigned)cn->requirement);
139 case UC_NIGHTSGRADE:
140 return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2) >= cn->requirement);
141 case UC_TRIGGER: // requires map trigger set
142 return !!(unlocktriggers & (1 << cn->requirement));
143 case UC_TOTALEMBLEMS: // Requires number of emblems >= x
144 return (M_GotEnoughEmblems(cn->requirement));
145 case UC_EMBLEM: // Requires emblem x to be obtained
146 return emblemlocations[cn->requirement-1].collected;
147 case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained
148 return extraemblems[cn->requirement-1].collected;
149 case UC_CONDITIONSET: // requires condition set x to already be achieved
150 return M_Achieved(cn->requirement-1);
151 }
152 return false;
153 }
154
M_CheckConditionSet(conditionset_t * c)155 static UINT8 M_CheckConditionSet(conditionset_t *c)
156 {
157 UINT32 i;
158 UINT32 lastID = 0;
159 condition_t *cn;
160 UINT8 achievedSoFar = true;
161
162 for (i = 0; i < c->numconditions; ++i)
163 {
164 cn = &c->condition[i];
165
166 // If the ID is changed and all previous statements of the same ID were true
167 // then this condition has been successfully achieved
168 if (lastID && lastID != cn->id && achievedSoFar)
169 return true;
170
171 // Skip future conditions with the same ID if one fails, for obvious reasons
172 else if (lastID && lastID == cn->id && !achievedSoFar)
173 continue;
174
175 lastID = cn->id;
176 achievedSoFar = M_CheckCondition(cn);
177 }
178
179 return achievedSoFar;
180 }
181
M_CheckUnlockConditions(void)182 void M_CheckUnlockConditions(void)
183 {
184 INT32 i;
185 conditionset_t *c;
186
187 for (i = 0; i < MAXCONDITIONSETS; ++i)
188 {
189 c = &conditionSets[i];
190 if (!c->numconditions || c->achieved)
191 continue;
192
193 c->achieved = (M_CheckConditionSet(c));
194 }
195 }
196
M_UpdateUnlockablesAndExtraEmblems(void)197 UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
198 {
199 INT32 i;
200 char cechoText[992] = "";
201 UINT8 cechoLines = 0;
202
203 if (modifiedgame && !savemoddata)
204 return false;
205
206 M_CheckUnlockConditions();
207
208 // Go through extra emblems
209 for (i = 0; i < numextraemblems; ++i)
210 {
211 if (extraemblems[i].collected || !extraemblems[i].conditionset)
212 continue;
213 if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
214 {
215 strcat(cechoText, va(M_GetText("Got \"%s\" emblem!\\"), extraemblems[i].name));
216 ++cechoLines;
217 }
218 }
219
220 // Fun part: if any of those unlocked we need to go through the
221 // unlock conditions AGAIN just in case an emblem reward was reached
222 if (cechoLines)
223 M_CheckUnlockConditions();
224
225 // Go through unlockables
226 for (i = 0; i < MAXUNLOCKABLES; ++i)
227 {
228 if (unlockables[i].unlocked || !unlockables[i].conditionset)
229 continue;
230 if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false)
231 {
232 if (unlockables[i].nocecho)
233 continue;
234 strcat(cechoText, va(M_GetText("\"%s\" unlocked!\\"), unlockables[i].name));
235 ++cechoLines;
236 }
237 }
238
239 // Announce
240 if (cechoLines)
241 {
242 char slashed[1024] = "";
243 for (i = 0; (i < 19) && (i < 24 - cechoLines); ++i)
244 slashed[i] = '\\';
245 slashed[i] = 0;
246
247 strcat(slashed, cechoText);
248
249 HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
250 HU_SetCEchoDuration(6);
251 HU_DoCEcho(slashed);
252 return true;
253 }
254 return false;
255 }
256
257 // Used when loading gamedata to make sure all unlocks are synched with conditions
M_SilentUpdateUnlockablesAndEmblems(void)258 void M_SilentUpdateUnlockablesAndEmblems(void)
259 {
260 INT32 i;
261 boolean checkAgain = false;
262
263 // Just in case they aren't to sync
264 M_CheckUnlockConditions();
265 M_CheckLevelEmblems();
266
267 // Go through extra emblems
268 for (i = 0; i < numextraemblems; ++i)
269 {
270 if (extraemblems[i].collected || !extraemblems[i].conditionset)
271 continue;
272 if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
273 checkAgain = true;
274 }
275
276 // check again if extra emblems unlocked, blah blah, etc
277 if (checkAgain)
278 M_CheckUnlockConditions();
279
280 // Go through unlockables
281 for (i = 0; i < MAXUNLOCKABLES; ++i)
282 {
283 if (unlockables[i].unlocked || !unlockables[i].conditionset)
284 continue;
285 unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1);
286 }
287
288 players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
289 }
290
291 // Emblem unlocking shit
M_CheckLevelEmblems(void)292 UINT8 M_CheckLevelEmblems(void)
293 {
294 INT32 i;
295 INT32 valToReach;
296 INT16 levelnum;
297 UINT8 res;
298 UINT8 somethingUnlocked = 0;
299
300 // Update Score, Time, Rings emblems
301 for (i = 0; i < numemblems; ++i)
302 {
303 if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected)
304 continue;
305
306 levelnum = emblemlocations[i].level;
307 valToReach = emblemlocations[i].var;
308
309 switch (emblemlocations[i].type)
310 {
311 case ET_SCORE: // Requires score on map >= x
312 res = (G_GetBestScore(levelnum) >= (unsigned)valToReach);
313 break;
314 case ET_TIME: // Requires time on map <= x
315 res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
316 break;
317 case ET_RINGS: // Requires rings on map >= x
318 res = (G_GetBestRings(levelnum) >= valToReach);
319 break;
320 case ET_NGRADE: // Requires NiGHTS grade on map >= x
321 res = (G_GetBestNightsGrade(levelnum, 0) >= valToReach);
322 break;
323 case ET_NTIME: // Requires NiGHTS time on map <= x
324 res = (G_GetBestNightsTime(levelnum, 0) <= (unsigned)valToReach);
325 break;
326 default: // unreachable but shuts the compiler up.
327 continue;
328 }
329
330 emblemlocations[i].collected = res;
331 if (res)
332 ++somethingUnlocked;
333 }
334 return somethingUnlocked;
335 }
336
M_CompletionEmblems(void)337 UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
338 {
339 INT32 i;
340 INT32 embtype;
341 INT16 levelnum;
342 UINT8 res;
343 UINT8 somethingUnlocked = 0;
344 UINT8 flags;
345
346 for (i = 0; i < numemblems; ++i)
347 {
348 if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected)
349 continue;
350
351 levelnum = emblemlocations[i].level;
352 embtype = emblemlocations[i].var;
353 flags = MV_BEATEN;
354
355 if (embtype & ME_ALLEMERALDS)
356 flags |= MV_ALLEMERALDS;
357
358 if (embtype & ME_ULTIMATE)
359 flags |= MV_ULTIMATE;
360
361 if (embtype & ME_PERFECT)
362 flags |= MV_PERFECT;
363
364 res = ((mapvisited[levelnum - 1] & flags) == flags);
365
366 emblemlocations[i].collected = res;
367 if (res)
368 ++somethingUnlocked;
369 }
370 return somethingUnlocked;
371 }
372
373 // -------------------
374 // Quick unlock checks
375 // -------------------
M_AnySecretUnlocked(void)376 UINT8 M_AnySecretUnlocked(void)
377 {
378 INT32 i;
379 for (i = 0; i < MAXUNLOCKABLES; ++i)
380 {
381 if (!unlockables[i].nocecho && unlockables[i].unlocked)
382 return true;
383 }
384 return false;
385 }
386
M_SecretUnlocked(INT32 type)387 UINT8 M_SecretUnlocked(INT32 type)
388 {
389 INT32 i;
390 for (i = 0; i < MAXUNLOCKABLES; ++i)
391 {
392 if (unlockables[i].type == type && unlockables[i].unlocked)
393 return true;
394 }
395 return false;
396 }
397
M_MapLocked(INT32 mapnum)398 UINT8 M_MapLocked(INT32 mapnum)
399 {
400 if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0)
401 return false;
402 if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked)
403 return true;
404 return false;
405 }
406
M_CountEmblems(void)407 INT32 M_CountEmblems(void)
408 {
409 INT32 found = 0, i;
410 for (i = 0; i < numemblems; ++i)
411 {
412 if (emblemlocations[i].collected)
413 found++;
414 }
415 for (i = 0; i < numextraemblems; ++i)
416 {
417 if (extraemblems[i].collected)
418 found++;
419 }
420 return found;
421 }
422
423 // --------------------------------------
424 // Quick functions for calculating things
425 // --------------------------------------
426
427 // Theoretically faster than using M_CountEmblems()
428 // Stops when it reaches the target number of emblems.
M_GotEnoughEmblems(INT32 number)429 UINT8 M_GotEnoughEmblems(INT32 number)
430 {
431 INT32 i, gottenemblems = 0;
432 for (i = 0; i < numemblems; ++i)
433 {
434 if (emblemlocations[i].collected)
435 if (++gottenemblems >= number) return true;
436 }
437 for (i = 0; i < numextraemblems; ++i)
438 {
439 if (extraemblems[i].collected)
440 if (++gottenemblems >= number) return true;
441 }
442 return false;
443 }
444
M_GotHighEnoughScore(INT32 tscore)445 UINT8 M_GotHighEnoughScore(INT32 tscore)
446 {
447 INT32 mscore = 0;
448 INT32 i;
449
450 for (i = 0; i < NUMMAPS; ++i)
451 {
452 if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
453 continue;
454 if (!mainrecords[i])
455 continue;
456
457 if ((mscore += mainrecords[i]->score) > tscore)
458 return true;
459 }
460 return false;
461 }
462
M_GotLowEnoughTime(INT32 tictime)463 UINT8 M_GotLowEnoughTime(INT32 tictime)
464 {
465 INT32 curtics = 0;
466 INT32 i;
467
468 for (i = 0; i < NUMMAPS; ++i)
469 {
470 if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
471 continue;
472
473 if (!mainrecords[i] || !mainrecords[i]->time)
474 return false;
475 else if ((curtics += mainrecords[i]->time) > tictime)
476 return false;
477 }
478 return true;
479 }
480
M_GotHighEnoughRings(INT32 trings)481 UINT8 M_GotHighEnoughRings(INT32 trings)
482 {
483 INT32 mrings = 0;
484 INT32 i;
485
486 for (i = 0; i < NUMMAPS; ++i)
487 {
488 if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
489 continue;
490 if (!mainrecords[i])
491 continue;
492
493 if ((mrings += mainrecords[i]->rings) > trings)
494 return true;
495 }
496 return false;
497 }
498
499 // ----------------
500 // Misc Emblem shit
501 // ----------------
502
503 // Returns pointer to an emblem if an emblem exists for that level.
504 // Pass -1 mapnum to continue from last emblem.
505 // NULL if not found.
506 // note that this goes in reverse!!
M_GetLevelEmblems(INT32 mapnum)507 emblem_t *M_GetLevelEmblems(INT32 mapnum)
508 {
509 static INT32 map = -1;
510 static INT32 i = -1;
511
512 if (mapnum > 0)
513 {
514 map = mapnum;
515 i = numemblems;
516 }
517
518 while (--i >= 0)
519 {
520 if (emblemlocations[i].level == map)
521 return &emblemlocations[i];
522 }
523 return NULL;
524 }
525
M_GetEmblemColor(emblem_t * em)526 skincolornum_t M_GetEmblemColor(emblem_t *em)
527 {
528 if (!em || em->color >= numskincolors)
529 return SKINCOLOR_NONE;
530 return em->color;
531 }
532
M_GetEmblemPatch(emblem_t * em,boolean big)533 const char *M_GetEmblemPatch(emblem_t *em, boolean big)
534 {
535 static char pnamebuf[7];
536
537 if (!big)
538 strcpy(pnamebuf, "GOTITn");
539 else
540 strcpy(pnamebuf, "EMBMn0");
541
542 I_Assert(em->sprite >= 'A' && em->sprite <= 'Z');
543
544 if (!big)
545 pnamebuf[5] = em->sprite;
546 else
547 pnamebuf[4] = em->sprite;
548
549 return pnamebuf;
550 }
551
M_GetExtraEmblemColor(extraemblem_t * em)552 skincolornum_t M_GetExtraEmblemColor(extraemblem_t *em)
553 {
554 if (!em || em->color >= numskincolors)
555 return SKINCOLOR_NONE;
556 return em->color;
557 }
558
M_GetExtraEmblemPatch(extraemblem_t * em,boolean big)559 const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big)
560 {
561 static char pnamebuf[7];
562
563 if (!big)
564 strcpy(pnamebuf, "GOTITn");
565 else
566 strcpy(pnamebuf, "EMBMn0");
567
568 I_Assert(em->sprite >= 'A' && em->sprite <= 'Z');
569
570 if (!big)
571 pnamebuf[5] = em->sprite;
572 else
573 pnamebuf[4] = em->sprite;
574
575 return pnamebuf;
576 }
577