#include "koopatlas/pathmanager.h" #include "koopatlas/core.h" #include "koopatlas/hud.h" #include "koopatlas/player.h" #include void dWMPathManager_c::setup() { isMoving = false; isJumping = false; timer = 0.0; currentPath = 0; reverseThroughPath = false; pathLayer = dScKoopatlas_c::instance->mapData.pathLayer; SpammyReport("setting up PathManager\n"); SaveBlock *save = GetSaveFile()->GetBlock(-1); mustComplainToMapCreator = false; SpammyReport("Unlocking paths\n"); isEnteringLevel = false; unlockPaths(); SpammyReport("done\n"); // Figure out what path node to start at if (dScKoopatlas_c::instance->settings & 0x10000000) { // Start off from a "Change" u8 changeID = (dScKoopatlas_c::instance->settings >> 20) & 0xFF; SpammyReport("entering at Change ID %d\n", changeID); SpammyReport("Path layer: %p\n", pathLayer); SpammyReport("Node count: %d\n", pathLayer->nodeCount); bool found = false; for (int i = 0; i < pathLayer->nodeCount; i++) { dKPNode_s *node = pathLayer->nodes[i]; SpammyReport("Checking node: %p\n", node); if (node->type == dKPNode_s::CHANGE && node->thisID == changeID) { found = true; currentNode = node; SpammyReport("a1 Node: %p\n", node); dKPPath_s *exit = node->getAnyExit(); SpammyReport("a2 Exit: %p\n", exit); startMovementTo(exit); SpammyReport("a3\n"); break; } } if (!found) { currentNode = pathLayer->nodes[0]; mustComplainToMapCreator = true; } } else { SpammyReport("saved path node: %d\n", save->current_path_node); if (save->current_path_node >= pathLayer->nodeCount) { SpammyReport("out of bounds (%d), using node 0\n", pathLayer->nodeCount); currentNode = pathLayer->nodes[0]; } else { currentNode = pathLayer->nodes[save->current_path_node]; SpammyReport("OK %p\n", currentNode); } } for (int i = 0; i < pathLayer->nodeCount; i++) if (pathLayer->nodes[i]->type == dKPNode_s::LEVEL) pathLayer->nodes[i]->setupNodeExtra(); } static u8 *PathAvailabilityData = 0; static u8 *NodeAvailabilityData = 0; dWMPathManager_c::~dWMPathManager_c() { if (PathAvailabilityData && !isEnteringLevel) { delete[] PathAvailabilityData; PathAvailabilityData = 0; delete[] NodeAvailabilityData; NodeAvailabilityData = 0; } } void dWMPathManager_c::unlockPaths() { u8 *oldPathAvData = PathAvailabilityData; PathAvailabilityData = new u8[pathLayer->pathCount]; u8 *oldNodeAvData = NodeAvailabilityData; NodeAvailabilityData = new u8[pathLayer->nodeCount]; SpammyReport("Unlocking paths\n"); // unlock all needed paths for (int i = 0; i < pathLayer->pathCount; i++) { dKPPath_s *path = pathLayer->paths[i]; PathAvailabilityData[i] = path->isAvailable; //SpammyReport("Path %d: %d\n", i, path->isAvailable); // if this path is not "always available", then nuke its alpha path->setLayerAlpha((path->isAvailable == dKPPath_s::ALWAYS_AVAILABLE) ? 255 : 0); } for (int i = 0; i < pathLayer->nodeCount; i++) NodeAvailabilityData[i] = pathLayer->nodes[i]->isUnlocked(); SaveBlock *save = GetSaveFile()->GetBlock(-1); u8 *in = (u8*)dScKoopatlas_c::instance->mapData.data->unlockData; SpammyReport("UNLOCKING PATHS: Unlock data @ %p\n", in); int cmdID = 0; while (*in != 0) { SpammyReport("[%p] Cmd %d: Evaluating condition\n", in, cmdID); // begin processing a block bool value = evaluateUnlockCondition(in, save, 0); SpammyReport("[%p] Cmd %d: Condition evaluated, result: %d\n", in, cmdID, value); //SpammyReport("Unlock condition: %d\n", value); // get what it's supposed to affect // for now we'll assume that it affects one or more paths u8 affectedCount = *(in++); SpammyReport("[%p] Cmd %d: Affects %d path(s)\n", in, cmdID, affectedCount); for (int i = 0; i < affectedCount; i++) { u8 one = *(in++); u8 two = *(in++); u16 pathID = (one << 8) | two; SpammyReport("[%p] Cmd %d: Affected %d: PathID: %d\n", in, cmdID, i, pathID); dKPPath_s *path = pathLayer->paths[pathID]; SpammyReport("[%p] Cmd %d: Affected %d: Path: %p\n", in, cmdID, i, path); path->isAvailable = value ? dKPPath_s::AVAILABLE : dKPPath_s::NOT_AVAILABLE; SpammyReport("[%p] Cmd %d: Affected %d: IsAvailable written\n", in, cmdID, i); PathAvailabilityData[pathID] = value ? dKPPath_s::AVAILABLE : dKPPath_s::NOT_AVAILABLE; SpammyReport("[%p] Cmd %d: Affected %d: AvailabilityData written\n", in, cmdID, i); // NEWLY_AVAILABLE is set later, when that stuff is figured out path->setLayerAlpha(value ? 255 : 0); SpammyReport("[%p] Cmd %d: Affected %d: Layer alpha applied\n", in, cmdID, i); } SpammyReport("[%p] Cmd %d: %d affected path(s) processed\n", in, cmdID, affectedCount); cmdID++; } SpammyReport("UNLOCKING PATHS: All complete @ %p\n", in); // did anything become newly available?! newlyAvailablePaths = 0; newlyAvailableNodes = 0; if (oldPathAvData) { for (int i = 0; i < pathLayer->pathCount; i++) { if ((PathAvailabilityData[i] > 0) && (oldPathAvData[i] == 0)) { dKPPath_s *path = pathLayer->paths[i]; path->isAvailable = dKPPath_s::NEWLY_AVAILABLE; newlyAvailablePaths++; // set this path's alpha to 0, we'll fade it in later path->setLayerAlpha(0); } } delete[] oldPathAvData; // check nodes too for (int i = 0; i < pathLayer->nodeCount; i++) { if ((NodeAvailabilityData[i] > 0) && (oldNodeAvData[i] == 0)) { dKPNode_s *node = pathLayer->nodes[i]; node->isNew = true; newlyAvailableNodes++; } } delete[] oldNodeAvData; } // now set all node alphas for (int i = 0; i < pathLayer->nodeCount; i++) { dKPNode_s *node = pathLayer->nodes[i]; node->setLayerAlpha((node->isUnlocked() & !node->isNew) ? 255 : 0); } // if anything was new, set it as such if (newlyAvailablePaths || newlyAvailableNodes) { countdownToFadeIn = 30; } unlockingAlpha = -1; } bool dWMPathManager_c::evaluateUnlockCondition(u8 *&in, SaveBlock *save, int stack) { SpammyReport("[%p] CondStk:%d begin\n", in, stack); u8 controlByte = *(in++); u8 conditionType = (controlByte >> 6); SpammyReport("[%p] CondStk:%d control byte: %d; condition type: %d\n", in, stack, controlByte, conditionType); if (conditionType == 0) { SpammyReport("[%p] CondStk:%d end, returning CONSTANT 1\n", in, stack); return true; } if (conditionType == 1) { // Simple level bool isSecret = (controlByte & 0x10); u8 worldNumber = controlByte & 0xF; u8 levelNumber = *(in++); SpammyReport("[%p] CondStk:%d level, w:%d l:%d secret:%d\n", in, stack, worldNumber, levelNumber, isSecret); u32 conds = save->GetLevelCondition(worldNumber, levelNumber); SpammyReport("[%p] CondStk:%d returning for level conditions: %d / %x\n", in, stack, conds, conds); if (isSecret) return (conds & COND_SECRET) != 0; else return (conds & COND_NORMAL) != 0; } // Type: 2 = AND, 3 = OR bool isAnd = (conditionType == 2); bool isOr = (conditionType == 3); bool value = isOr ? false : true; u8 termCount = (controlByte & 0x3F) + 1; SpammyReport("[%p] CondStk:%d and:%d or:%d startValue:%d termCount:%d\n", in, stack, isAnd, isOr, value, termCount); for (int i = 0; i < termCount; i++) { bool what = evaluateUnlockCondition(in, save, stack+1); if (isOr) value |= what; else value &= what; } SpammyReport("[%p] CondStk:%d end, returning %d\n", in, stack, value); return value; } void dWMPathManager_c::execute() { int nowPressed = Remocon_GetPressed(GetActiveRemocon()); if (isMoving) { moveThroughPath(); } else { if (nowPressed & WPAD_LEFT && canUseExit(currentNode->leftExit)) startMovementTo(currentNode->leftExit); else if (nowPressed & WPAD_RIGHT && canUseExit(currentNode->rightExit)) startMovementTo(currentNode->rightExit); else if (nowPressed & WPAD_UP && canUseExit(currentNode->upExit)) startMovementTo(currentNode->upExit); else if (nowPressed & WPAD_DOWN && canUseExit(currentNode->downExit)) startMovementTo(currentNode->downExit); else if (nowPressed & WPAD_TWO) activatePoint(); } // handle path fading if (countdownToFadeIn > 0) { countdownToFadeIn--; if (countdownToFadeIn <= 0) { unlockingAlpha = 0; MapSoundPlayer(SoundRelatedClass, SE_SYS_NEW_POINT, 1); } } if (unlockingAlpha != -1) { unlockingAlpha += 3; for (int i = 0; i < pathLayer->pathCount; i++) { dKPPath_s *path = pathLayer->paths[i]; if (path->isAvailable == dKPPath_s::NEWLY_AVAILABLE) path->setLayerAlpha(unlockingAlpha); } for (int i = 0; i < pathLayer->nodeCount; i++) { dKPNode_s *node = pathLayer->nodes[i]; if (node->isNew) node->setLayerAlpha(unlockingAlpha); } if (unlockingAlpha == 255) { // we've reached the end unlockingAlpha = -1; MapSoundPlayer(SoundRelatedClass, SE_SYS_NEW_POINT_END, 1); } } } void dWMPathManager_c::startMovementTo(dKPPath_s *path) { SpammyReport("moving to path %p\n", path); if (!path->isAvailable) { return; } if (currentNode) dWMHud_c::instance->leftNode(); calledEnteredNode = false; SpammyReport("a\n"); isMoving = true; reverseThroughPath = (path->end == currentNode); SpammyReport("b\n"); currentPath = path; SpammyReport("c\n"); // calculate direction of the path short deltaX = path->end->x - path->start->x; short deltaY = path->end->y - path->start->y; SpammyReport("d\n"); u16 direction = (u16)(atan2(deltaX, deltaY) / ((M_PI * 2) / 65536.0)); SpammyReport("e\n"); if (reverseThroughPath) { SpammyReport("e2\n"); direction += 0x8000; } SpammyReport("f\n"); daWMPlayer_c *player = daWMPlayer_c::instance; SpammyReport("g %p\n", player); // Consider adding these as options // wall_walk_l = 60, // wall_walk_r = 61, // hang_walk_l = 65, // hang_walk_r = 66, SpammyReport("h\n"); player->rot.y = direction; player->hasSound = false; player->hasEffect = false; moveSpeed = 3.0f; switch (path->animation) { // Running case 0: player->startAnimation(run, 2.0, 10.0, 0.0); player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_DIRT; break; case 1: player->startAnimation(run, 2.0, 10.0, 0.0); player->hasSound = true; player->hasEffect = true; player->soundName = SE_PLY_FOOTNOTE_CS_SAND; player->effectName = "Wm_mr_foot_sand"; break; case 2: player->startAnimation(run, 2.0, 10.0, 0.0); player->hasSound = true; player->hasEffect = true; player->soundName = SE_PLY_FOOTNOTE_CS_SNOW; player->effectName = "Wm_mr_foot_snow"; break; case 3: player->startAnimation(run, 2.0, 10.0, 0.0); player->hasSound = true; player->hasEffect = true; player->soundName = SE_PLY_FOOTNOTE_CS_WATER; player->effectName = "Wm_mr_foot_water"; break; // Jumping case 4: player->startAnimation(jump, 1.0, 1.0, 0.0); MapSoundPlayer(SoundRelatedClass, SE_PLY_JUMP, 1); isJumping = true; moveSpeed = 2.5f; break; case 5: player->startAnimation(jump, 1.0, 10.0, 0.0); MapSoundPlayer(SoundRelatedClass, SE_PLY_JUMP, 1); isJumping = true; moveSpeed = 2.5f; break; case 6: player->startAnimation(jump, 1.0, 10.0, 0.0); MapSoundPlayer(SoundRelatedClass, SE_PLY_JUMP, 1); isJumping = true; moveSpeed = 2.5f; break; case 7: player->startAnimation(jump, 1.0, 10.0, 0.0); MapSoundPlayer(SoundRelatedClass, SE_PLY_JUMP, 1); isJumping = true; SpawnEffect("Wm_mr_waterwave_out", 0, &player->pos, 0, &player->scale); moveSpeed = 2.0f; break; // Climbing case 8: player->startAnimation(pea_plant, 1.2, 10.0, 0.0); player->rot.y = 0x8000; player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_CS_ROCK_CLIMB; moveSpeed = 1.5f; break; case 9: player->startAnimation(tree_climb, 1.2, 10.0, 0.0); player->rot.y = 0xC000; player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_CS_ROCK_CLIMB; moveSpeed = 1.5f; break; case 10: player->startAnimation(tree_climb, 1.2, 10.0, 0.0); player->rot.y = 0x4000; player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_CS_ROCK_CLIMB; moveSpeed = 1.5f; break; // Others case 12: player->startAnimation(swim_wait, 1.2, 10.0, 0.0); player->hasSound = true; player->hasEffect = true; player->soundName = SE_PLY_SWIM; player->effectName = "Wm_mr_waterswim"; moveSpeed = 2.0f; break; case 13: player->startAnimation(Tjumped, 2.0, 0.0, 0.0); break; case 14: player->startAnimation(b_dash2, 3.0, 10.0, 0.0); player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_DIRT; moveSpeed = 5.0f; break; case 15: player->startAnimation(wait, 2.0, 10.0, 0.0); player->rot.y = 0x0000; MapSoundPlayer(SoundRelatedClass, SE_PLY_DOKAN_IN_OUT, 1); moveSpeed = 1.0f; break; case 16: player->startAnimation(wait, 2.0, 10.0, 0.0); player->rot.y = 0x8000; MapSoundPlayer(SoundRelatedClass, SE_OBJ_DOOR_OPEN, 1); moveSpeed = 0.2f; break; default: SpammyReport("No animtaion?!"); player->startAnimation(run, 2.0, 10.0, 0.0); player->hasSound = true; player->soundName = SE_PLY_FOOTNOTE_DIRT; break; } SpammyReport("i\n"); } void dWMPathManager_c::moveThroughPath() { dKPNode_s *from, *to; from = reverseThroughPath ? currentPath->end : currentPath->start; to = reverseThroughPath ? currentPath->start : currentPath->end; Vec move = (Vec){to->x - from->x, to->y - from->y, 0}; VECNormalize(&move, &move); VECScale(&move, &move, moveSpeed); daWMPlayer_c *player = daWMPlayer_c::instance; if (isJumping) { float ys = (float)from->y; float ye = (float)to->y; float midpoint = (from->y + to->y) / 2; float top, len; if (ys > ye) { len = ys - ye; top = ys - midpoint + 10.0; } else { len = ye - ys; top = ye - midpoint + 10.0; } if (len == 0.0) { len = 2.0; } float a; if (timer > 0.0) { a = -timer; } else { a = timer; } player->jumpOffset = -sin(a * 3.14 / len) * top; timer -= move.y; } player->pos.x += move.x; player->pos.y -= move.y; // what distance is left? if (to->type == dKPNode_s::LEVEL && !calledEnteredNode) { Vec toEndVec = {to->x - player->pos.x, to->y + player->pos.y, 0.0f}; float distToEnd = VECMag(&toEndVec); //OSReport("Distance: %f; To:%d,%d; Player:%f,%f; Diff:%f,%f\n", distToEnd, to->x, to->y, player->pos.x, player->pos.y, toEndVec.x, toEndVec.y); if (distToEnd < 64.0f) { calledEnteredNode = true; dWMHud_c::instance->enteredNode(to); } } // Check if we've reached the end yet if ( ((move.x > 0) ? (player->pos.x >= to->x) : (player->pos.x <= to->x)) && ((move.y > 0) ? (-player->pos.y >= to->y) : (-player->pos.y <= to->y)) ) { currentNode = to; player->pos.x = to->x; player->pos.y = -to->y; isJumping = false; timer = 0.0; SpammyReport("reached path end (%p)\n", to); bool reallyStop = false; if (to->type == dKPNode_s::LEVEL) { // Always stop on levels reallyStop = true; } else if (to->type == dKPNode_s::CHANGE) { // Never stop on entrances reallyStop = false; } else if (to->type == dKPNode_s::PASS_THROUGH) { // If there's only one exit here, then stop even though // it's a passthrough node reallyStop = (to->getAvailableExitCount() == 1); } else { // Quick check: do we *actually* need to stop on this node? // If it's a junction with more than two exits, but only two are open, // take the opposite open one if (to->getExitCount() > 2 && to->getAvailableExitCount() == 2) reallyStop = false; else reallyStop = true; } if (to->type == dKPNode_s::CHANGE) { // Go to another map isMoving = false; SaveBlock *save = GetSaveFile()->GetBlock(-1); SpammyReport("node: %x, %s", to->destMap, to->destMap); save->current_world = dScKoopatlas_c::instance->getIndexForMapName(to->destMap); SpammyReport("Change to map ID %d (%s), entrance ID %d\n", save->current_world, to->destMap, to->foreignID); DoSceneChange(WORLD_MAP, 0x10000000 | (to->foreignID << 20), 0); } else if (reallyStop) { // Stop here player->startAnimation(0, 1.2, 10.0, 0.0); player->hasEffect = false; player->hasSound = false; SpammyReport("stopping here\n"); isMoving = false; SaveBlock *save = GetSaveFile()->GetBlock(-1); save->current_path_node = pathLayer->findNodeID(to); if (!calledEnteredNode) dWMHud_c::instance->enteredNode(); if (to->type == dKPNode_s::LEVEL) { NWRWorld nWorld = NewerWorldForLevelID(to->levelNumber[0], to->levelNumber[1]); if (nWorld != UNKNOWN_WORLD) { save->currentNewerWorld = (u8)nWorld; } } } else { startMovementTo(to->getOppositeAvailableExitTo(currentPath)); SpammyReport("passthrough node, continuing to next path\n"); } } } void dWMPathManager_c::activatePoint() { if (currentNode->type == dKPNode_s::LEVEL) { int w = currentNode->levelNumber[0] - 1; int l = currentNode->levelNumber[1] - 1; if (l == 40) { dWMShop_c::instance->LoadShopForWorld(w); dScKoopatlas_c::instance->state.setState(&dScKoopatlas_c::instance->StateID_ShopWait); return; } if ((l >= 29) && (l <= 36)) { SaveBlock *save = GetSaveFile()->GetBlock(-1); u32 conds = save->GetLevelCondition(w, l); SpammyReport("Toad House Flags: %x", conds); if (conds & 0xFF0) { MapSoundPlayer(SoundRelatedClass, SE_SYS_INVALID, 1); return; } } MapSoundPlayer(SoundRelatedClass, SE_SYS_GAME_START, 1); daWMPlayer_c::instance->startAnimation(170, 1.2, 10.0, 0.0); isEnteringLevel = true; dLevelInfo_c::entry_s *level = dScKoopatlas_c::instance->levelInfo.search(w, l); dScKoopatlas_c::instance->startLevel(level); } } void dWMPathManager_c::unlockAllPaths(char type) { // Unlocks ALL paths, regular and secret if (type == 0) { for (int i = 0; i < pathLayer->pathCount; i++) { dKPPath_s *path = pathLayer->paths[i]; path->isAvailable = true; SaveBlock *save = GetSaveFile()->GetBlock(-1); for (int j = 0; j < 10; j++) { for (int h = 0; h < 0x2A; h++) { save->completions[j][h] = 0x30; } } unlockPaths(); } } // Unlocks ALL paths, regular only if (type == 1) { for (int i = 0; i < pathLayer->pathCount; i++) { dKPPath_s *path = pathLayer->paths[i]; path->isAvailable = true; SaveBlock *save = GetSaveFile()->GetBlock(-1); for (int j = 0; j < 10; j++) { for (int h = 0; h < 0x2A; h++) { save->completions[j][h] = 0x10; } } unlockPaths(); } } // Unlocks current path, regular and secret if (type == 2) { if (currentNode->type == dKPNode_s::LEVEL) { int w = currentNode->levelNumber[0] - 1; int l = currentNode->levelNumber[1] - 1; SaveBlock *save = GetSaveFile()->GetBlock(-1); save->completions[w][l] = 0x30; unlockPaths(); } } // Can't change node models - the price we pay for not using anims // for (int i = 0; i < pathLayer->nodeCount; i++) { // dKPNode_s *node = pathLayer->nodes[i]; // node->setupNodeExtra(); // } }