Skip to content

Commit 34b226d

Browse files
committed
Start implementing in-game UI for level chains
1 parent 7ac264e commit 34b226d

3 files changed

Lines changed: 289 additions & 16 deletions

File tree

Rayman2APMod/src/connector.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define MESSAGE_TYPE_MASK_CHECK 11
1515

1616
#define LEVEL_COUNT 56
17+
#define MAX_LEVEL_NAME_LENGTH 32
1718
#define MAX_LENGTH 32
1819
#define CHAIN_COUNT 21
1920

Rayman2APMod/src/mod.c

Lines changed: 279 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,40 @@ void MOD_ChangeLevel(const char* szLevelName, ACP_tdxBool bSaveGame) {
686686
GAM_fn_vAskToChangeLevel(szLevelName, bSaveGame);
687687
}
688688

689+
/** Counts a series of lum collectables. */
690+
int CountCollectibleLums(int* values, int* superValues, int superLums, int count) {
691+
HIE_tdstSuperObject* pGlobal = HIE_fn_p_stFindObjectByName("global");
692+
int total = 0;
693+
if (pGlobal) {
694+
for (int i = 0; i < (count - superLums * 5); i++) {
695+
if (AI_fn_bGetBooleanInArray(pGlobal, 42, values[i])) {
696+
total++;
697+
}
698+
}
699+
for (int i = 0; i < superLums; i++) {
700+
if (AI_fn_bGetBooleanInArray(pGlobal, 42, superValues[i])) {
701+
total += 5;
702+
}
703+
}
704+
}
705+
return total;
706+
}
707+
708+
/** Counts a series of cage collectables. */
709+
int CountCollectibleCages(int* values, int count) {
710+
HIE_tdstSuperObject* pGlobal = HIE_fn_p_stFindObjectByName("global");
711+
int total = 0;
712+
if (pGlobal) {
713+
for (int i = 0; i < count; i++) {
714+
if (AI_fn_bGetBooleanInArray(pGlobal, 42, values[i])) {
715+
total++;
716+
}
717+
}
718+
719+
}
720+
return total;
721+
}
722+
689723
/** Sets the value of a DSG variable. */
690724
BOOL AI_fn_bSetDsgVar(HIE_tdstSuperObject* p_stSuperObj, unsigned char ucDsgVarId, void** p_pValue_In) {
691725
if (!HIE_M_bSuperObjectIsActor(p_stSuperObj))
@@ -1197,6 +1231,87 @@ void MOD_ToggleDeathLink() {
11971231
}
11981232
}
11991233

1234+
/** Crawls through the level chains and appends to the output list. */
1235+
void CrawlLevelInfo(int chainId, int currentLevel, LevelInfo** info, int* length, int depth) {
1236+
// Determine the level to place here
1237+
int chainLength = MOD_LevelChainsLengths[chainId];
1238+
if (currentLevel < 0 || currentLevel >= chainLength) return;
1239+
1240+
// Extend the length and array with this new level
1241+
*length = *length + 1;
1242+
LevelInfo* tmp = realloc(*info, *length * sizeof(LevelInfo));
1243+
if (!tmp) return;
1244+
*info = tmp;
1245+
1246+
// Prepare the level data for this level
1247+
LevelInfo* level = &(*info)[*length - 1];
1248+
memset(level, 0, sizeof(LevelInfo));
1249+
1250+
level->depth = depth;
1251+
1252+
// Determine the level in this spot
1253+
int levelId = MOD_LevelChainContents[chainId][currentLevel];
1254+
char* levelName = MOD_LevelIds[levelId];
1255+
1256+
// Determine the lums/cages of this level
1257+
if (compareStringCaseInsensitive(levelName, "Learn_30") == 0) {
1258+
strcpy(level->name, "Fairy Glade 1");
1259+
level->lumsMax = 9;
1260+
level->cagesMax = 2;
1261+
level->lums = CountCollectibleLums((int[]) { 6, 7, 8, 12 }, (int[]) { 1 }, 1, level->lumsMax);
1262+
level->cages = CountCollectibleCages((int[]) { 842, 843 }, level->cagesMax);
1263+
} else if (compareStringCaseInsensitive(levelName, "learn_31") == 0) {
1264+
strcpy(level->name, "Fairy Glade 2");
1265+
level->lumsMax = 1;
1266+
level->cagesMax = 0;
1267+
level->lums = CountCollectibleLums((int[]) { 11 }, (int[]) { 0 }, 0, level->lumsMax);
1268+
level->cages = CountCollectibleCages((int[]) { 0 }, level->cagesMax);
1269+
} else if (compareStringCaseInsensitive(levelName, "Learn_32") == 0) {
1270+
strcpy(level->name, "Fairy Glade - Revisit");
1271+
level->lumsMax = 2;
1272+
level->cagesMax = 1;
1273+
level->lums = CountCollectibleLums((int[]) { 9, 10 }, (int[]) { 0 }, 0, level->lumsMax);
1274+
level->cages = CountCollectibleCages((int[]) { 844 }, level->cagesMax);
1275+
} else if (compareStringCaseInsensitive(levelName, "bast_20") == 0) {
1276+
strcpy(level->name, "Fairy Glade 3");
1277+
level->lumsMax = 17;
1278+
level->cagesMax = 2;
1279+
level->lums = CountCollectibleLums((int[]) { 27, 26, 25, 29, 28, 23, 24 }, (int[]) { 13, 18 }, 2, level->lumsMax);
1280+
level->cages = CountCollectibleCages((int[]) { 845, 846 }, level->cagesMax);
1281+
} else if (compareStringCaseInsensitive(levelName, "bast_22") == 0) {
1282+
strcpy(level->name, "Fairy Glade 4");
1283+
level->lumsMax = 4;
1284+
level->cagesMax = 0;
1285+
level->lums = CountCollectibleLums((int[]) { 31, 30, 32, 33 }, (int[]) { 0 }, 0, level->lumsMax);
1286+
level->cages = CountCollectibleCages((int[]) { 0 }, level->cagesMax);
1287+
} else if (compareStringCaseInsensitive(levelName, "learn_60") == 0) {
1288+
strcpy(level->name, "Fairy Glade 5");
1289+
level->lumsMax = 17;
1290+
level->cagesMax = 2;
1291+
level->lums = CountCollectibleLums((int[]) { 34, 35, 36, 37, 48, 49, 50, 38, 42, 45, 44, 43, 39, 46, 47, 41, 40 }, (int[]) { 0 }, 0, level->lumsMax);
1292+
level->cages = CountCollectibleCages((int[]) { 847, 848 }, level->cagesMax);
1293+
} else if (compareStringCaseInsensitive(levelName, "cask_10") == 0) {
1294+
strcpy(level->name, "Echoing Caves 1");
1295+
level->lumsMax = 15;
1296+
level->cagesMax = 2;
1297+
level->lums = CountCollectibleLums((int[]) { 421, 422, 423, 430, 431, 432, 428, 429, 424, 425, 426, 427, 433, 434, 435 }, (int[]) {0}, 0, level->lumsMax);
1298+
level->cages = CountCollectibleCages((int[]) {887, 888}, level->cagesMax);
1299+
} else {
1300+
strcpy(level->name, levelName);
1301+
level->lumsMax = 0;
1302+
level->cagesMax = 0;
1303+
}
1304+
1305+
// Crawl for the next level, if this is Sanc of Stone and Fire or Echoing Caves we include the revisit area!
1306+
// The other three have their own portals which show their contents.
1307+
if (compareStringCaseInsensitive(levelName, "cask_10") == 0) {
1308+
CrawlLevelInfo(CHAIN_FAIRY_REVISIT, 0, info, length, depth + 1);
1309+
} else if (compareStringCaseInsensitive(levelName, "plum_00") == 0) {
1310+
CrawlLevelInfo(CHAIN_SIDE_TEMPLE, 0, info, length, depth + 1);
1311+
}
1312+
CrawlLevelInfo(chainId, currentLevel + 1, info, length, depth);
1313+
}
1314+
12001315
/** Draws text to the screen with the recent screen text and progression statistics. */
12011316
void CALLBACK MOD_vTextCallback(SPTXT_tdstTextInfo* pInfo) {
12021317
// Determine the current time
@@ -1225,22 +1340,170 @@ void CALLBACK MOD_vTextCallback(SPTXT_tdstTextInfo* pInfo) {
12251340
SPTXT_vPrint(screen);
12261341
}
12271342

1228-
// Draw the current Archipelago progression to the bottom in the hall of doors or on the pause screen
1229-
const char* szLevelName = GAM_fn_p_szGetLevelName();
1230-
if (MOD_Connected && (compareStringCaseInsensitive(szLevelName, "mapmonde") == 0 || *AI_g_bInGameMenu)) {
1231-
pInfo->xSize = 6;
1232-
pInfo->bRightAlign = TRUE;
1233-
long lineHeight = SPTXT_fn_lGetCharHeight(pInfo->xSize);
1234-
1235-
pInfo->X = 995;
1236-
pInfo->Y = 990 - 4 * lineHeight;
1237-
SPTXT_vPrintFmtLine("/o200:Archipelago Received");
1238-
pInfo->Y = 990 - 3 * lineHeight;
1239-
SPTXT_vPrintFmtLine("/o400:Lums /o0:%d of 1000/o400:, Cages /o0:%d of 80", MOD_Lums, MOD_Cages);
1240-
pInfo->Y = 990 - 2 * lineHeight;
1241-
SPTXT_vPrintFmtLine("/o400:Masks /o0:%d of 4/o400:, Power /o0:%d of 2", MOD_Masks, MOD_Upgrades);
1242-
pInfo->Y = 990 - lineHeight;
1243-
SPTXT_vPrintFmtLine("/o400:Elixir %s/o400:, Knowledge %s", MOD_Elixir ? "/o0:Yes" : "/o200:No", MOD_Knowledge ? "/o0:Yes" : "/o200:No");
1343+
if (MOD_Connected) {
1344+
// Draw the current Archipelago progression to the bottom in the hall of doors or on the pause screen
1345+
const char* szLevelName = GAM_fn_p_szGetLevelName();
1346+
const auto inMapMonde = compareStringCaseInsensitive(szLevelName, "mapmonde") == 0;
1347+
if (inMapMonde || *AI_g_bInGameMenu) {
1348+
pInfo->xSize = 6;
1349+
pInfo->bRightAlign = TRUE;
1350+
long lineHeight = SPTXT_fn_lGetCharHeight(pInfo->xSize);
1351+
1352+
pInfo->X = 995;
1353+
pInfo->Y = 990 - 4 * lineHeight;
1354+
SPTXT_vPrintFmtLine("/o200:Archipelago Received");
1355+
SPTXT_vPrintFmtLine("/o400:Lums /o0:%d of 1000/o400:, Cages /o0:%d of 80", MOD_Lums, MOD_Cages);
1356+
SPTXT_vPrintFmtLine("/o400:Masks /o0:%d of 4/o400:, Power /o0:%d of 2", MOD_Masks, MOD_Upgrades);
1357+
SPTXT_vPrintFmtLine("/o400:Elixir %s/o400:, Knowledge %s", MOD_Elixir ? "/o0:Yes" : "/o200:No", MOD_Knowledge ? "/o0:Yes" : "/o200:No");
1358+
}
1359+
1360+
// When hovering over a portal in mapmonde we show information on the level chain inside
1361+
if (inMapMonde) {
1362+
HIE_tdstSuperObject* pLums = HIE_fn_p_stFindObjectByName("YAM_Lums_I1");
1363+
HIE_tdstSuperObject* pGlobal = HIE_fn_p_stFindObjectByName("global");
1364+
if (pLums && pGlobal) {
1365+
// This variable stores the fade time on the UI box, so 255
1366+
// means that we've been at a portal for enough time to show
1367+
// the UI.
1368+
int* p_stInt;
1369+
AI_fn_bGetDsgVar(pLums, 19, NULL, &p_stInt);
1370+
if (*p_stInt == 255) {
1371+
// Load the currently highlighted portal into a variable
1372+
HIE_tdstSuperObject** pPastille;
1373+
AI_fn_bGetDsgVar(pLums, 5, NULL, &pPastille);
1374+
1375+
if (*pPastille) {
1376+
// Dsg var 2 on portals stores their level offset relative to 960
1377+
int* p_stLevelId;
1378+
AI_fn_bGetDsgVar(*pPastille, 2, NULL, &p_stLevelId);
1379+
1380+
// Draw information based on the chain
1381+
int chainId = -1;
1382+
int nextLevelValue = 960;
1383+
int portal = 1;
1384+
1385+
// Determine which chain this is and what the next level is for checking if this was completed
1386+
if (*p_stLevelId == 1) {
1387+
chainId = CHAIN_FAIRY_GLADE;
1388+
nextLevelValue += 4;
1389+
} else if (*p_stLevelId == 4) {
1390+
chainId = CHAIN_MARSHES;
1391+
nextLevelValue += 7;
1392+
portal = 2;
1393+
} else if (*p_stLevelId == 6) {
1394+
chainId = CHAIN_COBD;
1395+
nextLevelValue = 1123; // We show info when you have the Elixir of Life!
1396+
portal = 3;
1397+
} else if (*p_stLevelId == 7) {
1398+
chainId = CHAIN_BAYOU;
1399+
nextLevelValue += 10;
1400+
portal = 4;
1401+
} else if (*p_stLevelId == 9) {
1402+
chainId = CHAIN_WALK_LIFE;
1403+
nextLevelValue += 0; // Walk is always visible!
1404+
portal = 5;
1405+
} else if (*p_stLevelId == 10) {
1406+
chainId = CHAIN_SANC_WATER;
1407+
nextLevelValue += 12;
1408+
portal = 6;
1409+
} else if (*p_stLevelId == 12) {
1410+
chainId = CHAIN_MENHIR;
1411+
nextLevelValue += 16;
1412+
portal = 7;
1413+
} else if (*p_stLevelId == 16) {
1414+
chainId = CHAIN_CANOPY;
1415+
nextLevelValue += 19;
1416+
portal = 8;
1417+
} else if (*p_stLevelId == 19) {
1418+
chainId = CHAIN_WHALE;
1419+
nextLevelValue += 21;
1420+
portal = 9;
1421+
} else if (*p_stLevelId == 21) {
1422+
chainId = CHAIN_SANC_STONE;
1423+
nextLevelValue += 15;
1424+
portal = 10;
1425+
} else if (*p_stLevelId == 15) {
1426+
chainId = CHAIN_ECHOING;
1427+
nextLevelValue += 25;
1428+
portal = 11;
1429+
} else if (*p_stLevelId == 25) {
1430+
chainId = CHAIN_PRECIPICE;
1431+
nextLevelValue += 28;
1432+
portal = 12;
1433+
} else if (*p_stLevelId == 28) {
1434+
chainId = CHAIN_TOP;
1435+
nextLevelValue += 30;
1436+
portal = 13;
1437+
} else if (*p_stLevelId == 32) {
1438+
chainId = CHAIN_WALK_POWER;
1439+
nextLevelValue += 0; // Walk is always visible!
1440+
portal = 14;
1441+
} else if (*p_stLevelId == 30) {
1442+
chainId = CHAIN_SANC_ROCK;
1443+
nextLevelValue += 33;
1444+
portal = 15;
1445+
} else if (*p_stLevelId == 33) {
1446+
chainId = CHAIN_BENEATH;
1447+
nextLevelValue += 47;
1448+
portal = 16;
1449+
} else if (*p_stLevelId == 47) {
1450+
chainId = CHAIN_TOMB;
1451+
nextLevelValue += 40;
1452+
portal = 17;
1453+
} else if (*p_stLevelId == 40) {
1454+
chainId = CHAIN_IRON_MOUNT;
1455+
nextLevelValue += 42;
1456+
portal = 18;
1457+
} else if (*p_stLevelId == 42) {
1458+
chainId = CHAIN_PRISON;
1459+
nextLevelValue += 0; // Prison Ship is always visible because we have no reliable checks and it's the last level anyway so you can process of elimination it.
1460+
portal = 19;
1461+
}
1462+
1463+
if (chainId != -1) {
1464+
pInfo->xSize = 7;
1465+
pInfo->bRightAlign = FALSE;
1466+
pInfo->X = 15;
1467+
1468+
long lineHeight = SPTXT_fn_lGetCharHeight(pInfo->xSize);
1469+
ACP_tdxBool isCompleted = AI_fn_bGetBooleanInArray(pGlobal, 42, nextLevelValue);
1470+
1471+
if (!isCompleted) {
1472+
int lines = 1;
1473+
pInfo->Y = 550 - lines * lineHeight;
1474+
SPTXT_vPrintFmtLine("/o200:Unlock the next level to reveal rooms!");
1475+
} else {
1476+
// Determine the level contents!
1477+
LevelInfo* levelInfo = NULL;
1478+
int length = 0;
1479+
CrawlLevelInfo(chainId, 0, &levelInfo, &length, 0);
1480+
1481+
// Draw the level info in a list, start with a header showing portal number and room count
1482+
long spacer = lineHeight - 2;
1483+
pInfo->Y = 320;
1484+
SPTXT_vPrintFmtLine("/o200:Portal nr. %d", portal);
1485+
pInfo->X = 22;
1486+
SPTXT_vPrintFmtLine("/o0:%d rooms", length);
1487+
pInfo->Y = pInfo->Y + spacer;
1488+
1489+
for (int i = 0; i < length; i++) {
1490+
LevelInfo info = levelInfo[i];
1491+
pInfo->X = 15 + info.depth * 10;
1492+
SPTXT_vPrintFmtLine("/o400:%s", info.name);
1493+
pInfo->X = 22 + info.depth * 10;
1494+
if (info.cagesMax > 0) {
1495+
SPTXT_vPrintFmtLine("/o400:Lums /o0:%d of %d/o400:", info.lums, info.lumsMax);
1496+
} else {
1497+
SPTXT_vPrintFmtLine("/o400:Lums /o0:%d of %d/o400:, Cages /o0:%d of %d", info.lums, info.lumsMax, info.cages, info.cagesMax);
1498+
}
1499+
pInfo->Y = pInfo->Y + spacer;
1500+
}
1501+
}
1502+
}
1503+
}
1504+
}
1505+
}
1506+
}
12441507
}
12451508
SPTXT_vResetTextInfo(pInfo);
12461509
}

Rayman2APMod/src/mod.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
#include "connector.h"
44
#include "bitset.h"
55

6+
typedef struct LevelInfo {
7+
char name[MAX_LEVEL_NAME_LENGTH];
8+
int lums;
9+
int lumsMax;
10+
int cages;
11+
int cagesMax;
12+
int depth;
13+
} LevelInfo;
14+
615
int compareStringCaseInsensitive(char const* a, char const* b);
716

817
void MOD_EngineTick();

0 commit comments

Comments
 (0)