/// <summary> /// This method controls the AI for the grappling hook. Note that there is a separate AI run /// for each active hook. There are three states that a hook can be in: /// 1(0f) -- Extending /// 2(1f) -- Retreating /// 3(2f) -- Grappled /// The AI starts at Extending when shot and updates the state of the hook based on certain /// conditions. If the hook has been shot, but has not reached the max grapple distance, the /// ai state stays at Extending. If the hook has reached the max grapple distance, the ai state /// is updated to Retreating. If the hook ever reaches a solid tile that it can grapple to, the /// ai state is updated to Grappled. The AI then continues to check that the block that the /// hook grappled to is still active. If the block is ever removed (e.g. broken), the ai state /// of the hook is updated to Retreating. /// </summary> public override void AI() { Player hookPlayer = Main.player[projectile.owner]; if (hookPlayer.dead || hookPlayer.stoned || hookPlayer.webbed || hookPlayer.frozen) { projectile.Kill(); } else { // Determines distance from player to grapple int newPosition; Vector2 playerLocation = hookPlayer.MountedCenter; Vector2 projectileLocation = new Vector2(projectile.position.X + (float)projectile.width * 0.5f, projectile.position.Y + (float)projectile.height * 0.5f); float xDistance = playerLocation.X - projectileLocation.X; float yDistance = playerLocation.Y - projectileLocation.Y; float grappleDistance = (float)Math.Sqrt((double)(xDistance * xDistance + yDistance * yDistance)); projectile.rotation = (float)Math.Atan2((double)yDistance, (double)xDistance) - 1.57f; // ai state for when grapple is extending if (projectile.ai[0] == 0f) { // Sets grappling hook ai to retreat (1) if the hook is outside of the max grappling range if (grappleDistance > maxRange) { projectile.ai[0] = 1f; } /* If the hook is still within the max grappling range, it checks to see if the grapple has collided * with a tile and, if it has, sets the grappling hook's ai to grappled (2)*/ // Sets position of hook's hitbox Vector2 bottomLeftVector = projectile.Center - new Vector2(5f); Vector2 topRightVector = projectile.Center + new Vector2(5f); Point bottomLeftPoint = (bottomLeftVector - new Vector2(16f)).ToTileCoordinates(); Point topRightPoint = (topRightVector + new Vector2(32f)).ToTileCoordinates(); int grappleLeft2 = bottomLeftPoint.X; int grappleRight2 = topRightPoint.X; int grappleBottom2 = bottomLeftPoint.Y; int grappleTop2 = topRightPoint.Y; // Verifies hook's hitbox position is within map bounds. If not, sets hitbox to map bounds. if (grappleLeft2 < 0) { grappleLeft2 = 0; } if (grappleRight2 > Main.maxTilesX) { grappleRight2 = Main.maxTilesX; } if (grappleBottom2 < 0) { grappleBottom2 = 0; } if (grappleTop2 > Main.maxTilesY) { grappleTop2 = Main.maxTilesY; } /* Iterates through all tiles within hook's hitbox position to check for a solid tile the hook * can grapple to. If there is, the grapple's ai is set to grappled (2)*/ for (int xPos = grappleLeft2; xPos < grappleRight2; xPos = newPosition + 1) { for (int yPos = grappleBottom2; yPos < grappleTop2; yPos = newPosition + 1) { Vector2 tilePosition = default; tilePosition.X = (float)(xPos * 16); tilePosition.Y = (float)(yPos * 16); if (bottomLeftVector.X + 10f > tilePosition.X && bottomLeftVector.X < tilePosition.X + 16f && bottomLeftVector.Y + 10f > tilePosition.Y && bottomLeftVector.Y < tilePosition.Y + 16f && Main.tile[xPos, yPos].nactive() && (Main.tileSolid[Main.tile[xPos, yPos].type] || Main.tile[xPos, yPos].type == 314)) { // Updates player's grapple count if (hookPlayer.grapCount < 10) { hookPlayer.grappling[hookPlayer.grapCount] = projectile.whoAmI; hookPlayer.grapCount += 1; } ShouldKillOldestHook(); // Kills the oldest hook if player currently has max number of hooks grappled WorldGen.KillTile(xPos, yPos, true, true, false); Main.PlaySound(0, xPos * 16, yPos * 16, 1, 1f, 0f); projectile.velocity.X = 0f; projectile.velocity.Y = 0f; projectile.ai[0] = 2f; projectile.position.X = (float)(xPos * 16 + 8 - projectile.width / 2); projectile.position.Y = (float)(yPos * 16 + 8 - projectile.height / 2); projectile.damage = 0; projectile.netUpdate = true; if (Main.myPlayer == projectile.owner) { NetMessage.SendData(13, -1, -1, null, projectile.owner, 0f, 0f, 0f, 0, 0, 0); } break; } newPosition = yPos; } if (projectile.ai[0] == 2f) { break; } newPosition = xPos; } return; } // ai state for when grapple is retreating if (projectile.ai[0] == 1f) { ProjectileLoader.GrappleRetreatSpeed(projectile, hookPlayer, ref retreatSpeed); // Kills the hook once close enough to the player on retreat if (grappleDistance < 24f) { projectile.Kill(); } grappleDistance = retreatSpeed / grappleDistance; xDistance *= grappleDistance; yDistance *= grappleDistance; projectile.velocity.X = xDistance; projectile.velocity.Y = yDistance; } // ai state for when grapple is hooked else if (projectile.ai[0] == 2f) { // Sets position of hook's hitbox int grappleLeft = (int)(projectile.position.X / 16f) - 1; int grappleRight = (int)((projectile.position.X + (float)projectile.width) / 16f) + 2; int grappleBottom = (int)(projectile.position.Y / 16f) - 1; int grappleTop = (int)((projectile.position.Y + (float)projectile.height) / 16f) + 2; // Verifies hook's hitbox position is within map bounds. If not, sets hitbox to map bounds. if (grappleLeft < 0) { grappleLeft = 0; } if (grappleRight > Main.maxTilesX) { grappleRight = Main.maxTilesX; } if (grappleBottom < 0) { grappleBottom = 0; } if (grappleTop > Main.maxTilesY) { grappleTop = Main.maxTilesY; } /* Iterates through all tiles within hook's hitbox position to check for a solid tile the hook * is grappled to. If the hook is no longer grappled to a tile (e.g. if the tile is broken with * a pickaxe), the grapple ai is set to retreat (1)*/ bool notGrappled = true; for (int xPos = grappleLeft; xPos < grappleRight; xPos = newPosition + 1) { for (int yPos = grappleBottom; yPos < grappleTop; yPos = newPosition + 1) { Vector2 tilePosition = default; tilePosition.X = (float)(xPos * 16); tilePosition.Y = (float)(yPos * 16); if (projectile.position.X + (float)(projectile.width / 2) > tilePosition.X && projectile.position.X + (float)(projectile.width / 2) < tilePosition.X + 16f && projectile.position.Y + (float)(projectile.height / 2) > tilePosition.Y && projectile.position.Y + (float)(projectile.height / 2) < tilePosition.Y + 16f && Main.tile[xPos, yPos].nactive() && (Main.tileSolid[Main.tile[xPos, yPos].type] || Main.tile[xPos, yPos].type == 314 || Main.tile[xPos, yPos].type == 5)) { notGrappled = false; } newPosition = yPos; } newPosition = xPos; } // Sets the grapple's ai to retreat (1) if the tile that he hook was grappled to is no longer there if (notGrappled) { projectile.ai[0] = 1f; } else if (hookPlayer.grapCount < 10) { hookPlayer.grappling[hookPlayer.grapCount] = projectile.whoAmI; hookPlayer.grapCount += 1; } KillHookOnJump(hookPlayer); // checks if the player has jumped. On a jump, all hooks are killed (including actively extending hooks) } return; } }