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