turns-00077.parquet:11895
04a34c097e4d9708042a7d4bdegenerate_repetitionAbsentFinal dense release
Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.
04a34c097e4d9708042a7d4b🥰
Hey there! How can I brighten your day today? 😊
334fd5da0485f95421b40125
Goal
Objectif est d'identifier l'origine d'une recette de cuisine et de retourner un code correspondant dans un format JSON structuré.
1. Recherche d’un pays mentionné dans la recette et extraction de son code ISO 3166-1 alpha-2.
2. Détermination de l’origine culinaire du pays si aucun pays n'est explicitement mentionné.
3. Identification d’un continent si aucun pays ne peut être déterminé.
4. Retour du code WWW si l’origine ne peut être définie.
Return Format
Renvoies un objet JSON structuré comme suit :
json
{
"data": "<code>"
}
- Si un pays est mentionné, utilise son code ISO 3166-1 alpha-2 (ex. : FR pour la France).
- Si aucun pays n'est trouvé, renvoie un code de continent parmi :
WAF (Afrique)
WAS (Asie)
WEU (Europe)
WNA (Amérique du Nord)
WSA (Amérique du Sud)
WOC (Océanie)
- Si aucune information ne permet de déterminer une origine, renvoie WWW.
Warnings
- Respecte l’ordre de priorité : pays → continent → code WWW.
- Le code ISO 3166-1 alpha-2 doit être exact et valide si un pays est identifié.
- Le format du JSON doit être strictement respecté et bien formatté.
- Ne fais aucune supposition infondée sur l’origine de la recette.
Context Dump
Données de la recette fournies :
json
{
"title": "GROUPER WITH ASIAN VEGETABLES, STICKY RICE, AND GINGER-SOY BROTH",
"description": null,
"subtitle": null,
"ingredients": [
{
"section": "GROUPER",
"ingredients": [
"4 (6-ounce) black grouper fillets, boneless, skin removed (halibut or red snapper can be substituted)",
"coarse salt, to taste",
"freshly ground black pepper, to taste",
"2 tablespoons hijiki seaweed (arame can be substituted)",
"1/4 cup extra-virgin olive oil",
"1 carrot, peeled and cut into 2-inch matchsticks",
"1/2 red onion, very thinly sliced",
"1 cup green beans, trimmed and cut into 2-inch pieces",
"1 small green papaya, peeled and cut into 2-inch matchsticks (cucumber may be substituted)",
"1 tablespoon finely chopped cilantro, for serving",
"1 tablespoon finely chopped mint, for serving"
]
}
],
"instructions": [
"Warm the ginger-soy broth in a small saucepan over medium heat. Keep warm until ready to serve.",
"Heat 2 tablespoons of olive oil over medium-high heat in a medium skillet. Add the carrots, red onion, and green beans. Cook, stirring constantly, for 3 minutes.",
"With caution, add the stir-fry sauce to the pan and bring to a boil. Remove from the heat.",
"Add the green papaya, stir to coat all of the vegetables with the sauce, and set aside.",
"Heat remaining 2 tablespoons of olive oil over medium-high heat in a medium skillet. Once oil is very hot and begins to simmer, add the fish fillets to the pan using tongs.",
"Cook for 5 minutes on both sides. Remove fillets to a baking sheet and place in oven for an additional 5 to 7 minutes or until very firm to the touch.",
"TO SERVE",
"Divide rice into four bowls.",
"Sprinkle rice with cilantro, mint, and hijiki.",
"Divide stir-fry vegetables and sauce between the bowls.",
"Place fish on top of vegetables and pour ginger-soy sauce over fish. Serve immediately."
],
"author": null
}
```json
{
"data": "WAS"
}
```2b2c5a1f44ce4eb505117ec1yo how can I make a sandbox mode on here to like make a gui panel that goes on screen so u can summon css enemies on anything and can spawn power ups or anything yk
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Adventure Game</title>
<style>
body, html {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
color: #fff;
font-family: Arial, sans-serif;
overflow: hidden;
}
#game-container {
position: relative;
width: 800px;
height: 400px;
background-color: #333;
overflow: hidden;
border: 2px solid #444;
}
#upgrades-menu {
display: none;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
border-radius: 10px;
text-align: center;
}
#upgrade-list {
margin: 20px 0;
}
.upgrade-option {
margin: 10px 0;
font-size: 18px;
}
#start-menu, #game-mode-options, #level-options {
display: none; /* Initially hidden */
text-align: center; /* Center text for buttons */
position: absolute; /* Positioned relative to parent */
top: 50%; /* Start middle of vertical view */
left: 50%; /* Start middle of horizontal view */
transform: translate(-50%, -50%); /* Center it */
z-index: 10; /* Bring to the front */
}
.button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #555;
color: #fff;
border: none;
border-radius: 5px;
}
.navigation-button {
margin: 5px;
}
.split-enemy.split {
border-radius: 0 !important;
}
.boost-drainer-chain {
position: absolute;
border-top: 2px dotted cyan;
pointer-events: none;
z-index: 1000;
height: 0; /* Since it's a horizontal line rotated, height is zero */
}
.boost-drainer {
position: absolute;
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid cyan;
background-color: green; /* start at green */
transition: background-color 0.5s;
z-index: 20;
}
.boost-drainer.green { background-color: green !important; }
.boost-drainer.yellow { background-color: yellow !important; }
.boost-drainer.red { background-color: red !important; }
.boost-laser {
position: absolute;
width: 5px;
height: 120px; /* height of laser, adjust */
background-color: red;
top: 0; /* will be positioned at bottom 0 */
box-shadow: 0 0 10px red;
z-index: 19;
pointer-events: none; /* no interference with input */
}
.split-enemy:not(.split) {
border-radius: 0 !important; /* Make mini splitters squares */
}
.split-enemy:not(.split) {
border-radius: 0 !important;
}
#game-world {
position: absolute;
width: 10000px; /* bigger world - adjust as you wish */
height: 100%;
transition: transform 0.1s linear;
}
#player {
position: absolute;
bottom: 0;
left: 50px;
width: 30px;
height: 30px;
background-color: #4CAF50;
animation: idle 1s infinite alternate;
}
@keyframes idle {
from { transform: translateY(0); }
to { transform: translateY(2px); }
}
@keyframes charging-prep {
0%, 100% { box-shadow: 0 0 5px rgba(255, 0, 0, 0.6); }
50% { box-shadow: 0 0 15px rgba(255, 0, 0, 1); }
}
@keyframes boss-shake {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(-5px, 0); }
50% { transform: translate(5px, 0); }
75% { transform: translate(-5px, 0); }
}
@keyframes charge-trail {
0% { opacity: 0.8; transform: scale(1); }
100% { opacity: 0; transform: scale(2); }
}
@keyframes boss-shield-glow {
0%, 100% { box-shadow: 0 0 10px red; }
50% { box-shadow: 0 0 20px red; }
}
.character, .boss-door, .power-up, .enemy, .virus-drone, .laser-enemy, .obstacle, .bullet, .burst, .boss, #friend, .coin, .flying-enemy, .charging-enemy, .stealth-enemy, .split-enemy, .boost-drainer, .sniper-enemy, .reflective-enemy, .explosive-enemy, .teleporter-enemy, .mourning-enemy, .velocity-enemy, .sticky-enemy, .reflective-enemy {
position: absolute;
}
.character, .king, .stealth-enemy, .split-enemy, .sniper-enemy, .reflective-enemy, .explosive-enemy,
.teleporter-enemy, .mourning-enemy, .velocity-enemy, .sticky-enemy, .reflective-enemy {
border-radius: 50%;
}
.virus-drone {
position: absolute;
width: 30px;
height: 30px;
background-color: green;
border: 3px solid red;
border-radius: 5px;
z-index: 11;
}
.infected {
background-color: #00cc00 !important; // bright green
filter: drop-shadow(0 0 5px lime);
transition: background-color 0.3s;
}
.infected.shielded::after {
content: '';
position: absolute;
top: -5px; left: -5px;
width: calc(100% + 10px);
height: calc(100% + 10px);
border: 2px solid cyan;
border-radius: 5px;
pointer-events: none;
animation: shieldPulse 2s infinite;
}
@keyframes shieldPulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
.energy-blast {
position: absolute;
width: 200px; /* Diameter of the burst */
height: 200px; /* Diameter of the burst */
border-radius: 50%; /* Make it circular */
border: 2px solid rgba(0, 255, 255, 0.8); /* Color and transparency */
opacity: 0.5; /* Semi-transparent */
animation: pulse 0.5s ease-out; /* Animation for the burst */
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(2); /* Expand the burst for the visual effect */
opacity: 0; /* Fade out */
}
}
.charging-prep {
animation: charging-prep 1s infinite alternate;
}
.outlined {
border: 3px solid green; /* Change as necessary */
box-shadow: 0 0 10px green; /* Add glow effect */
}
.boss-shielded {
animation: boss-shield-glow 0.8s infinite alternate;
border: 3px solid red;
}
.charging-active {
box-shadow: 0 0 15px red, 0 0 30px red;
border: 2px solid red;
}
.charge-trail {
position: absolute;
width: 35px;
height: 35px;
background-color: rgba(255, 50, 50, 0.5);
border-radius: 50%;
opacity: 1;
animation: charge-trail 0.4s ease-out;
}
.bullet-trail {
position: absolute;
width: 8px;
height: 4px;
background-color: rgba(255, 215, 0, 0.5);
opacity: 1;
transition: opacity 0.3s ease-out;
}
.laser-line {
pointer-events: none; /* prevent interfering with input */
z-index: 1000;
border-radius: 2px;
transition: background-color 0.2s ease;
}
.infected.shielded::after {
content: '';
position: absolute;
top: -5px; left: -5px;
width: calc(100% + 10px);
height: calc(100% + 10px);
border: 2px solid cyan;
border-radius: 5px;
pointer-events: none;
animation: shieldPulse 2s infinite;
}
.coin {
width: 15px;
height: 15px;
background-color: #FFD700;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.flying-enemy {
width: 30px;
height: 30px;
background-color: #0099FF;
border-radius: 50%;
opacity: 0.9;
}
.charging-enemy {
width: 40px;
height: 40px;
background-color: #FF5500;
animation: charge 1s linear infinite alternate;
border-radius: 50%;
}
.enemy {
width: 25px;
height: 25px;
background-color: #FF5733;
}
.laser-warning-line {
position: absolute;
width: 6px;
background-color: yellow;
opacity: 0.7;
pointer-events: none;
z-index: 9;
}
.laser-beam {
position: absolute;
width: 8px;
background-color: red;
opacity: 0.9;
pointer-events: none;
z-index: 10;
}
.split-enemy {
width: 35px;
height: 35px;
background-color: #FF5733;
}
.laser-enemy {
width: 35px;
height: 35px;
background-color: #FF5733;
}
.obstacle {
width: 30px;
height: 30px;
background-color: #f44336;
}
.lava {
position: absolute;
width: 100%;
bottom: 0;
background-color: red;
z-index: 5;
height: 0;
transition: height 0.1s linear;
}
.power-up {
width: 20px;
height: 20px;
}
.bullet {
width: 10px;
height: 5px;
background-color: #FFD700;
}
.burst {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 69, 0, 0.7);
animation: burst 0.5s ease-out;
}
@keyframes burst {
from { transform: scale(0.5); opacity: 1; }
to { transform: scale(1.5); opacity: 0; }
}
.boss-door {
width: 30px;
height: 60px;
background-color: #FFFF00;
}
.burst {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 69, 0, 0.7);
animation: burst 0.5s ease-out;
}
.boss {
width: 60px;
height: 60px;
background-color: #FF4500;
}
.reflective-enemy {
width: 25px;
height: 25px;
background-color: #FF5733;
border: 3px solid transparent; /* Initial border */
box-shadow: 0 0 10px green; /* Glow effect */
}
.mourning-enemy {
width: 25px;
height: 25px;
background-color: #FF3333;
}
.sniper-enemy {
width: 25px;
height: 25px;
background-color: #FF3333;
}
#score, #lives, #level, #timer, #shop-score {
position: absolute;
top: 10px;
font-size: 16px;
}
#score { left: 10px; }
#lives { right: 10px; }
#level { left: 150px; }
#timer { left: 250px; }
#shop-score { left: 350px; }
#instructions, #cutscene, #level-intro, #level-intro, #boss-fight, #boss-hallway, #game-over, #interact-message {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
flex-direction: column;
font-size: 18px;
z-index: 10;
}
#instructions button, #cutscene button, #level-intro button, #level-intro2 button, #boss-fight button, #game-over button, #boss-hallway button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #555;
color: #fff;
}
#cutscene, #level-intro, #level-intro2, #boss-fight, #boss-hallway, #game-over, #interact-message {
display: none;
}
#intro {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
flex-direction: row;
font-size: 18px;
z-index: 11;
}
#boss-health-bar {
display: none; /* Initially hidden */
position: absolute;
top: 10px; /* Adjust based on your UI */
left: 50%; /* Center it horizontally or adjust as needed */
transform: translateX(-50%); /* Center alignment */
width: 150px; /* Set fixed maximum width for the health bar */
height: 20px; /* Height of the bar */
background-color: #666; /* Background bar color */
border: 1px solid #333; /* Border style */
border-radius: 5px; /* Rounded corners */
overflow: hidden; /* Prevent overflow */
transition: width 0.5s ease; /* Smooth transition */
}
#boss-health {
height: 100%;
background-color: green; /* Initial health segment color */
border-radius: 5px; /* Rounded corners for inner health */
}
#friend {
width: 30px;
height: 30px;
background-color: #00FF00;
display: none;
}
#power-up-message {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
font-size: 24px;
z-index: 12;
display: none;
}
/* New CSS for cool features */
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.flashing {
animation: flash 0.5s infinite;
}
.glow {
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-world">
<div id="player"></div>
</div>
<div id="upgrades-menu" style="display:none;">
<h2>Upgrades</h2>
<button onclick="previousUpgrade()" class="button">Previous</button>
<div id="upgrade-list"></div>
<button onclick="nextUpgrade()" class="button">Next</button>
<button onclick="buyUpgrade()" class="button">Buy Upgrade</button>
<button onclick="viewEquippedUpgrades()" class="button">View Equipped</button> <!-- New button -->
<button onclick="closeUpgrades()" class="button">Close</button>
<div id="message" style="color: red;"></div>
</div>
<div id="score">Score: 0</div>
<div id="shop-score">Shop Score: 0</div>
<div id="lives">Lives: 3</div>
<div id="level">Level: 1</div>
<div id="timer">Timer: 0</div>
<div id="instructions">
<p>Press Arrow Up to Jump</p>
<p>Use Arrow Left/Right to Move</p>
<p>Press Space to Shoot</p>
<p>Press Z for Power-Up</p>
<p>Press E to interact</p>
<button onclick="showGameModeOptions()">Start Game</button> <!-- Start Game Button -->
<button onclick="togglePause()">Pause/Resume</button>
<button onclick="openUpgrades()">Open Upgrades</button>
</div>
<div id="cutscene">
<p>Get ready for the next level!</p>
<button onclick="continueGame()">Continue</button>
</div>
<div id="equipped-upgrades" style="display: none;"> <!-- Initially hidden -->
<h3>Equipped Upgrades:</h3>
<!-- Equipped upgrades will be displayed here -->
</div>
<div id="game-mode-options" style="display: none;">
<button onclick="startGamee()" class="button">Story Mode</button> <!-- Story Mode -->
<button id="endless-mode-btn" onclick="showLevelOptions()" class="button" style="display: none;">Endless Mode</button> <!-- Endless Mode -->
</div>
<div id="level-options" style="display: none;">
<button onclick="startEndlessMode(1)" class="button" >Level 1</button> <!-- Level 1 Button -->
<button onclick="startEndlessMode(2)" class="button">Level 2</button> <!-- Level 2 Button -->
<button onclick="startEndlessMode(3)" class="button">Level 3</button> <!-- Level 3 Button -->
<button onclick="startEndlessMode(4)" class="button">Level 4</button> <!-- Level 4 Button -->
<button onclick="startEndlessMode(5)" class="button">Level 5</button> <!-- Level 4 Button -->
</div>
</div>
<div id="level-intro">
<p id="level-text">Get Ready for Adventure!</p>
<button onclick="startLevel()">Start Level</button>
</div>
<div id="level-intro2">
<p id="level-text">Get Ready for Adventure!</p>
<button onclick="restartGame()">Start Endless mode</button>
</div>
<div id="boss-fight">
<p>Defeat the Boss!</p>
</div>
<div id="game-over">
<p>Game Over!</p>
<button onclick="restartGame()">Restart</button>
</div>
<div id="boss-hallway">
<p>You have entered the boss hallway! Prepare yourself!</p>
<button onclick="startBossFight()">Proceed</button>
</div>
<div id="boss-health-bar">
<div id="boss-health"></div>
</div>
<div id="power-up-message"></div>
<div id="interact-message"></div>
<div id="friend"></div>
</div>
<script>
const gameContainer = document.getElementById('game-container');
const gameWorld = document.getElementById('game-world');
const player = document.getElementById('player');
const scoreDisplay = document.getElementById('score');
const maxUpgrades = 5; // Maximum number of upgrades you can equip
const livesDisplay = document.getElementById('lives');
const levelDisplay = document.getElementById('level');
const timerDisplay = document.getElementById('timer');
const instructions = document.getElementById('instructions');
const cutscene = document.getElementById('cutscene');
const levelIntro = document.getElementById('level-intro');
const levelText = document.getElementById('level-text2');
const levelIntro2 = document.getElementById('level-intro2');
const levelText2 = document.getElementById('level-text');
const bossFightScreen = document.getElementById('boss-fight');
const gameOverScreen = document.getElementById('game-over');
const intro = document.getElementById('intro');
const dialoguePara = document.querySelector('#dialogue p');
const upgradesPerPage = 1; // Number of upgrades to show per page
const bossHallway = document.getElementById('boss-hallway');
const bossHealthBar = document.getElementById('boss-health-bar');
const bossHealthDisplay = document.getElementById('boss-health');
let currentPage = 0; // Tracks current page
let currentGameMode; // Temporary variable to store the current game mode
let currentEndlessLevel = localStorage.getItem('endlessLevel') ? parseInt(localStorage.getItem('endlessLevel')) : 1; // Endless Mode Level Tracking
let storyModeLevel = localStorage.getItem('storyLevel') ? parseInt(localStorage.getItem('storyLevel')) : 1; // Story Mode Level Tracking
let isEndlessMode = false; // Track if currently in Endless Mode
let bulletSpeed = 5; // Initialize bullet speed
// Other variables...
let phaseStartTime = null; // Track the start time of the boss phase
let playerScore = localStorage.getItem('playerScore') ? parseInt(localStorage.getItem('playerScore')) : 0;
const powerUpMessage = document.getElementById('power-up-message');
let upgradesEquipped = []; // Store equipped upgrades
const viruses = [];
const interactMessage = document.getElementById('interact-message');
const friend = document.getElementById('friend');
const obstacleSpawnDistance = 500; // Distance before the right edge
let playerPositionX = 50;
let upgradesPurchased = [];
let boostDrainer = null;
let boostDrainerLaser = null;
let boostDrainerHP = 3;
let boostDrainerState = "moving"; // "moving", "green", "yellow", "red", "firing", "dead"
let boostDrainerAttackTimeout = null;
const boostDrainerAttackXOffset = 250; // how close to player it moves before attack
const boostDrainerSpeed = 1.0; // pixels per frame
let virusDroneHits = 0; // tracks hits on Virus Drone
let playerPositionY = 0;
let playerVelocityX = 0;
let objectSpawnDistance = 300; // Distance from the right edge to spawn new objects
let playerVelocityY = 0;
let removedEnemies = [];
let phaseThreeStarted = false; // Flag to track if Phase Three has started
let phaseThreeStarted1 = false; // Flag to track if Phase Three has started
let phaseThreeStarted3 = false; // Flag to track if Phase Three has started
let bossDefeatedMessageDisplayed = false; // Flag to track if the defeated message has been displayed
let playerMaxSpeed = 5;
const playerJumpStrength = 12;
let jumpCount = 0;
let maxJumps = 1;
let isDoubleJumpEnabled = false;
let isJumping = false;
let isCycling = false; // Lock to prevent concurrent calls
let isPaused = false;
let canShoot = true;
let waveCooldown = false; // Track if a wave is on cooldown
let bossAttacking = false;
let bossAttackPhase = 0;
let gameInterval;
let obstacles = [];
let enemies = [];
const shownPreMessages = {};
let bullets = [];
let powerUps = [];
let boostDrainerChain = null;
let isTeleporting = false; // Track whether teleport is available
let platforms = [];
let bossDoors = [];
let maxPlayerPositionX = 50; // Track furthest progress
let coins = [];
let score = 0;
let isInBossDoor = false;
let lives = 3;
let level = localStorage.getItem('playerLevel') ? parseInt(localStorage.getItem('playerLevel')) : 1;
const gravity = 0.5;
const maxLevels = 5;
let movingLeft = false, movingRight = false;
let currentDirection = 1;
let currentPowerUp = null;
let powerUpTimer = 0;
let gameScrollPosition = 0;
let maxGameScrollPosition = 0; // Holds furthest right camera scroll so far
let leftBoundaryX = 0; // Moving left boundary for player position
const bossScoreThreshold = 1;
const dialogues = [
"Player: Hey friend! What a nice day for an adventure!",
"Friend: Indeed! Maybe we should explore the caves.",
"Player: What's that over there? Is it the King?",
"King: Haha, I've found you! You're coming with me!",
"Friend: Player, help me!"
];
let dialogueIndex = 0;
let boss = null;
let bossHealth = 100;
let bossShielded = true;
let bossPowerObtained = false;
let bossFightActive = false;
const allPowerUpTypes = [
"super-speed", "invincibility", "teleportation",
"reflective-shield", "double-shot", "extra-life",
"score-boost", "mega-jump", "energy-blast", "light-speed",
"blazing-trail", "sonic-wave", "armor", "telekinetic",
"camouflage", "dimension-shift", "energy-drain", "mind-control",
"hologram", "fire", "ice", "hypno"
];
window.addEventListener('keydown', handleKeydown);
window.addEventListener('keyup', handleKeyup);
const upgrades = [
{ name: 'Super Speed', price: 4500, effect: 'Increase speed by 10%', id: 'speedBoost' },
{ name: 'Super bullet', price: 1200, effect: 'Faster bullet speed', id: 'doubleDamage' },
{ name: 'Double jump', price: 7500, effect: 'Double jump', id: 'doubleJump' },
{ name: 'Extra Lives', price: 2500, effect: 'Gain 3 extra lives', id: 'ExtraLives' }, // Add this line for Extra Lives
// Add more upgrades as needed
];
function showEndlessModeButton() {
const endlessBtn = document.getElementById('endless-mode-btn');
if (endlessBtn) {
endlessBtn.style.display = 'inline-block';
endlessBtn.disabled = false;
}
}
// Function to select an upgrade
// Function to select the current upgrade
function selectUpgrade(index) {
selectedUpgrade = upgrades[index]; // Select the upgrade at the given index
}
function updateShopScoreDisplay() {
const shopScoreDisplay = document.getElementById('shop-score');
shopScoreDisplay.textContent = `Shop Score: ${playerScore}`; // Display the total amount
}
// Function to display current selected upgrade details
function displaySelectedUpgrade() {
const upgradeInfo = document.getElementById("upgrade-list");
upgradeInfo.innerHTML += `<br><strong>Selected Upgrade:</strong><br>
${selectedUpgrade.name}<br>
Price: ${selectedUpgrade.price}<br>
Effect: ${selectedUpgrade.effect}<br>`;
}
// Declare the selectedUpgrade at the top
let selectedUpgrade = null;
let currentUpgradeIndex = 0; // Keep track of the currently selected upgrade
// Function to open upgrades
function openUpgrades() {
document.getElementById("instructions").style.display = "none"; // Hide main menu
document.getElementById("upgrades-menu").style.display = "block"; // Show upgrades menu
displayUpgradeOptions(); // Display available upgrades
selectUpgrade(currentUpgradeIndex); // Start with first upgrade selected
}
function closeUpgrades() {
document.getElementById("upgrades-menu").style.display = "none"; // Hide upgrades menu
document.getElementById("instructions").style.display = "flex"; // Show main menu again
}
// Function to buy the selected upgrade
// Function to buy the selected upgrade
// Function to buy the selected upgrade
function showGameModeOptions() {
document.getElementById('instructions').style.display = 'none'; // Hide start button
document.getElementById('game-mode-options').style.display = 'block'; // Show game mode options
}
// Function to show level options for Endless Mode
function showLevelOptions() {
document.getElementById('game-mode-options').style.display = 'none'; // Hide game mode options
document.getElementById('level-options').style.display = 'block'; // Show level buttons
}
function startEndlessMode(chosenLevel) {
console.log('Endless Mode Selected for Level:', chosenLevel); // Debug log
document.getElementById('level-options').style.display = 'none'; // Hide level options
// Set the game mode to Endless
localStorage.setItem('gameMode', 'endless'); // This sets the gameMode in local storage
isEndlessMode = true; // Indicate that we are now in endless mode
currentEndlessLevel = chosenLevel; // Set the chosen level for this session
localStorage.setItem('endlessLevel', currentEndlessLevel); // Save the selected level to local storage
resetGame(); // Reset your game state
switchLevelBackground(); // Set the correct background according to currentEndlessLevel
spawnEndlessEntities(); // Spawn enemies based on the current endless level
checkCollisionWithPlatformsAndObstacles();
gameInterval = setInterval(gameLoop, 1000 / 60); // Start the game loop
}
// Ensure the currentEndlessLevel is used in your functions where needed,
// for instance in the game loop or resetGame function if appropriate
window.addEventListener('load', () => {
loadEquippedUpgrades();
const endlessUnlocked = localStorage.getItem('endlessModeUnlocked') === 'true';
const endlessBtn = document.getElementById('endless-mode-btn');
if (endlessUnlocked) {
// Show endless mode button properly
if (endlessBtn) {
endlessBtn.style.display = 'inline-block'; // Show
endlessBtn.disabled = false;
endlessBtn.style.visibility = 'visible';
endlessBtn.removeAttribute('hidden');
}
} else {
// Hide endless mode button initially
if (endlessBtn) {
endlessBtn.style.display = 'none';
}
}
// Reset temp variables - only if you really intend to reset them on load
currentGameMode = null;
currentEndlessLevel = null;
});
// Store purchased upgrades separately
// Function to buy and equip/unequip upgrades
function buyUpgrade() {
const upgrade = upgrades[currentPage]; // Get the currently displayed upgrade
if (!upgrade) {
return displayMessage("Please select an upgrade.");
}
const score = parseInt(localStorage.getItem('playerScore')) || 0;
const upgradeIndex = upgradesEquipped.indexOf(upgrade.id);
const purchasedIndex = upgradesPurchased.indexOf(upgrade.id);
// If the upgrade is already equipped
if (upgradeIndex > -1) {
// Unequip logic
upgradesEquipped.splice(upgradeIndex, 1);
removeUpgradeEffects(upgrade); // Remove effects of the unequipped upgrade
displayMessage(`${upgrade.name} has been unequipped!`);
} else {
// If the upgrade is already purchased but not equipped
if (purchasedIndex > -1) {
// Just equip the upgrade
upgradesEquipped.push(upgrade.id);
applyUpgrade(upgrade); // Apply effects of the newly equipped upgrade
displayMessage(`${upgrade.name} has been equipped!`);
} else {
// Check if the player has enough score to buy the upgrade
if (score < upgrade.price) {
return displayMessage("You need more score to buy this upgrade.");
}
// Deduct the cost and "purchase" the upgrade
localStorage.setItem('playerScore', score - upgrade.price);
upgradesPurchased.push(upgrade.id); // Mark the upgrade as purchased
upgradesEquipped.push(upgrade.id); // Equip it
applyUpgrade(upgrade); // Apply effects of the newly equipped upgrade
displayMessage(`${upgrade.name} has been equipped!`);
}
}
// Save the equipped upgrades and purchased upgrades to local storage
saveUpgrades();
// Refresh equipped display if needed
updateEquippedDisplay();
// Update the button text or state accordingly
updateButtonText(upgrade.id);
}
function loadEquippedUpgrades() {
const savedUpgradesEquipped = localStorage.getItem('upgradesEquipped');
if (savedUpgradesEquipped) {
upgradesEquipped = JSON.parse(savedUpgradesEquipped); // Update contents instead of reassigning with const
upgradesEquipped.forEach(upgradeId => {
const upgrade = upgrades.find(up => up.id === upgradeId);
if (upgrade) {
applyUpgrade(upgrade); // Apply effects of loaded upgrades
}
});
}
const savedUpgradesPurchased = localStorage.getItem('upgradesPurchased');
if (savedUpgradesPurchased) {
upgradesPurchased = JSON.parse(savedUpgradesPurchased); // Update contents
}
}
// Function to save the upgrades to local storage
function saveUpgrades() {
localStorage.setItem('upgradesEquipped', JSON.stringify(upgradesEquipped));
localStorage.setItem('upgradesPurchased', JSON.stringify(upgradesPurchased));
}
// Function to update the button text based on the equipped state
function updateButtonText(upgradeId) {
const upgrade = upgrades.find(up => up.id === upgradeId);
if (upgrade) {
const button = document.getElementById('upgrade-list'); // Assume the button has this ID
const isEquipped = upgradesEquipped.indexOf(upgradeId) > -1;
button.textContent = isEquipped ? "Unequip" : "Buy Upgrade";
}
}
// Function to remove effects when unequipping an upgrade
function removeUpgradeEffects(upgrade) {
switch (upgrade.id) {
case 'speedBoost':
playerMaxSpeed -= 1.5; // Revert speed increase
break;
case 'doubleDamage':
bulletSpeed -= 7;
break;
case 'doubleJump':
maxJumps = 1
isDoubleJumpEnabled = false; // Enable double jump
console.log('Double Jump Activated!');
break;
case 'ExtraLives': // New case for Extra Lives upgrade
removeLife();
console.log('Extra lives granted! Total lives:', lives); // Log the new lives total
break;
// Add additional cases for removing effects of new upgrades here
}
}
function startGamee() {
localStorage.setItem('gameMode', 'story'); // Set game mode to endless
startGame();
}
// Function to apply effects of an upgrade
function applyUpgrade(upgrade) {
switch (upgrade.id) {
case 'speedBoost':
playerMaxSpeed += 2; // Increase speed
break;
case 'doubleDamage':
bulletSpeed += 7;
break;
case 'doubleJump':
maxJumps = 2; // Allow an additional jump
isDoubleJumpEnabled = true; // Enable double jump
console.log('Double Jump Activated! You can now jump again mid-air.');
break;
case 'ExtraLives': // New case for Extra Lives upgrade
gainExtraLife2();
console.log('Extra lives granted! Total lives:', lives); // Log the new lives total
break;
// Add additional cases for new upgrades here
}
}
// Function to view equipped upgrades
function viewEquippedUpgrades() {
const equippedList = document.getElementById("equipped-upgrades");
// Toggle display of the equipped upgrades
if (equippedList.style.display === "none") {
equippedList.style.display = "block"; // Show the equipped upgrades
updateEquippedDisplay(); // Update the display with currently equipped upgrades
} else {
equippedList.style.display = "none"; // Hide the equipped upgrades
}
}
// Function to update the equipped display
function viewEquippedUpgrades() {
// Open a new blank tab
const equippedWindow = window.open("", "_blank");
// Create the HTML content for the equipped upgrades
let equippedContent = '<html><head><title>Equipped Upgrades</title></head><body>';
equippedContent += '<h3>Equipped Upgrades:</h3>'; // Add header
if (upgradesEquipped.length === 0) {
equippedContent += "<p>No upgrades equipped.</p>"; // Message when no upgrades are equipped
} else {
upgradesEquipped.forEach(id => {
const upgrade = upgrades.find(upg => upg.id === id);
if (upgrade) {
equippedContent += `
<div>
<strong>${upgrade.name}</strong><br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
</div>
<hr>
`;
}
});
}
equippedContent += '</body></html>'; // Close HTML tags
// Write the content to the new window
equippedWindow.document.write(equippedContent);
equippedWindow.document.close(); // Close the document for rendering
}
function updateEquippedDisplay() {
const equippedList = document.getElementById("equipped-upgrades");
equippedList.innerHTML = ""; // Clear the current display
if (upgradesEquipped.length === 0) {
equippedList.innerHTML = "<p>No upgrades equipped.</p>"; // Handle the case where no upgrades are equipped
} else {
upgradesEquipped.forEach(id => {
const upgrade = upgrades.find(upg => upg.id === id);
if (upgrade) {
equippedList.innerHTML += `
<div>
<strong>${upgrade.name}</strong><br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
</div>
<hr>
`;
}
});
}
}
if (upgradesEquipped.includes(selectedUpgrade.id)) {
// Unequip logic
const index = upgradesEquipped.indexOf(selectedUpgrade.id);
if (index > -1) upgradesEquipped.splice(index, 1);
displayMessage(`${selectedUpgrade.name} is now unequipped!`);
} else {
// Equip logic
localStorage.setItem('playerScore', score - selectedUpgrade.price);
upgradesEquipped.push(selectedUpgrade.id);
displayMessage(`${selectedUpgrade.name} has been equipped!`);
}
// Refresh equipped display
updateEquippedDisplay(); // Call it here to update the UI
function updateNavigationButtons(totalUpgrades) {
const prevBtn = document.querySelector('#upgrades-menu button[onclick="previousUpgrade()"]');
const nextBtn = document.querySelector('#upgrades-menu button[onclick="nextUpgrade()"]');
prevBtn.disabled = currentPage === 0; // Disable if on the first page
nextBtn.disabled = (currentPage + 1) * upgradesPerPage >= totalUpgrades; // Disable if on the last page
}
// Function to display messages to the player
function displayMessage(text, seconds = 3) {
bossMessageElement.textContent = text; // example
bossMessageElement.style.display = "block";
setTimeout(() => {
bossMessageElement.style.display = "none";
}, seconds * 1000);
}
function nextUpgrade() {
if (currentPage < upgrades.length - 1) {
currentPage++;
displayUpgradeOptions();
}
}
function previousUpgrade() {
if (currentPage > 0) {
currentPage--;
displayUpgradeOptions();
}
}
function displayUpgradeOptions() {
const upgradeList = document.getElementById("upgrade-list");
const upgrade = upgrades[currentPage];
const equipped = upgradesEquipped.includes(upgrade.id) ? " (Equipped)" : ""; // Check if equipped
upgradeList.innerHTML = `
<strong>${upgrade.name}</strong>${equipped}<br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
`;
// Update navigation buttons
updateNavigationButtons();
}
function handleKeydown(e) {
switch (e.key) {
case 'ArrowLeft':
movingLeft = true;
currentDirection = -1;
break;
case 'ArrowRight':
movingRight = true;
currentDirection = 1;
break;
case 'ArrowUp':
handleJump();
break;
case ' ': // Space bar for shooting
if (canShoot) {
shootBullet(currentDirection);
canShoot = false;
setTimeout(() => canShoot = true, 300);
}
break;
case 'z':
case 'Z': // Power-Up activation
if (currentPowerUp) { // Check if there is a power-up to use
usePowerUp();
} else if (bossPowerObtained) { // Charge if the boss power is obtained and no power-up is active
isCharging = true;
chargeTime = 0;
}
break;
case 'e':
case 'E':
interact(); // Trigger the interact function when 'E' is pressed
break;
}
}
function handleKeyup(e) {
switch (e.key) {
case 'ArrowLeft':
movingLeft = false;
break;
case 'ArrowRight':
movingRight = false;
break;
case 'z':
case 'Z':
if (bossPowerObtained && isCharging) {
isCharging = false;
shootLaserBlast(chargeTime);
}
break;
}
}
function handleJump() {
if (playerPositionY === 0) { // If the player is on the ground, perform a regular jump
playerVelocityY = playerJumpStrength; // Apply jump force
jumpCount = 1; // Set jump count to indicate first jump
isJumping = true; // Set jumping status to true
} else if (jumpCount < maxJumps) { // Allow double jump if jump count allows
doubleJump(); // Call double jump function
}
}
function nextDialogue() {
if (dialogueIndex < dialogues.length - 1) {
dialogueIndex++;
dialoguePara.textContent = dialogues[dialogueIndex];
} else {
intro.style.display = 'none';
startGame();
}
}
function doubleJump() {
if (jumpCount < maxJumps) { // Check if the player can still jump
playerVelocityY = playerJumpStrength; // Apply jump force
jumpCount++; // Increment the jump count
isJumping = true; // Set jumping status to true
}
}
function shootBullet(direction) {
const bullet = createBullet(direction);
bullets.push({ element: bullet, direction });
gameWorld.appendChild(bullet);
if (currentPowerUp === 'double-shot') {
const altBullet = createBullet(direction + 1);
bullets.push({ element: altBullet, direction });
gameWorld.appendChild(altBullet);
}
if (currentPowerUp === 'speed-bullet') {
const sideBullet1 = createBullet(direction + 6);
const sideBullet2 = createBullet(direction - 6);
bullets.push({ element: sideBullet1, direction });
bullets.push({ element: sideBullet2, direction });
gameWorld.appendChild(sideBullet1);
gameWorld.appendChild(sideBullet2);
}
}
function createBurst(x, y) {
const burst = document.createElement('div');
burst.classList.add('burst');
burst.style.left = x + 'px';
burst.style.bottom = y + 'px';
gameWorld.appendChild(burst);
setTimeout(() => burst.remove(), 500);
}
function moveCamera() {
if (!isInBossDoor) {
let proposedScroll = Math.max(0, playerPositionX - (gameContainer.offsetWidth / 2));
if (proposedScroll > maxGameScrollPosition) {
maxGameScrollPosition = proposedScroll;
}
gameScrollPosition = maxGameScrollPosition;
} else {
// Inside boss door, camera follows player freely (no scrolling restrictions)
gameScrollPosition = Math.max(0, playerPositionX - (gameContainer.offsetWidth / 2));
}
gameWorld.style.transform = `translateX(${-gameScrollPosition}px)`;
}
// Function to reset player and possibly generate more obstacles
function resetPlayerPosition() {
playerPositionX = 50; // Reset to starting position
player.style.left = playerPositionX + 'px'; // Ensure the player is positioned correctly
// Optionally, regenerate obstacles or enemies here
regenerateEntities(); // Call a function that generates new obstacles and/or enemies
}
// Function to regenerate entities
function regenerateEntities() {
// Logic to create and append new obstacles/enemies depending on your game mechanics
for (let i = 0; i < 5; i++) { // Change the number to spawn desired quantity
spawnObstacle(); // or spawnEnemy(); as needed
}
}
let summonCooldown = 0;
function summonAllies() {
for (let i = 0; i < 3; i++) {
let ally = createElement('ally', [], parseInt(player.style.left) + (i * 30));
ally.style.bottom = player.style.bottom;
gameWorld.appendChild(ally);
let destroyCount = 0;
let moveInterval = setInterval(() => {
ally.style.left = (parseInt(ally.style.left) + 5) + 'px';
enemies.forEach((enemy, index) => {
if (checkCollision(ally, enemy) && destroyCount < 3) {
enemy.remove();
enemies.splice(index, 1);
destroyCount++;
}
});
if (destroyCount >= 3) {
ally.remove();
clearInterval(moveInterval);
}
}, 50);
setTimeout(() => {
ally.remove();
clearInterval(moveInterval);
}, 5000);
}
}
function createBulletTrail(x, y) {
const trail = document.createElement('div');
trail.classList.add('bullet-trail');
trail.style.left = x + 'px';
trail.style.bottom = y + 'px';
gameWorld.appendChild(trail);
setTimeout(() => {
trail.style.opacity = '0';
setTimeout(() => trail.remove(), 300);
}, 50);
}
function moveBullets() {
bullets.forEach((bulletObj, index) => {
const bullet = bulletObj.element;
const bulletDirection = bulletObj.direction;
// Calculate new position
const newX = parseInt(bullet.style.left) + (bulletDirection * bulletSpeed);
bullet.style.left = newX + 'px';
createBulletTrail(newX, parseInt(bullet.style.bottom));
// Remove if out of bounds
if (newX > gameScrollPosition + gameContainer.offsetWidth || newX < 0) {
bullet.remove();
bullets.splice(index, 1);
} else {
// Check collisions with enemies
for (let enemyIndex = 0; enemyIndex < enemies.length; enemyIndex++) {
const enemy = enemies[enemyIndex];
if (checkCollision(bullet, enemy)) {
if (enemy.classList.contains('reflective-enemy')) {
// Handle reflective enemy bullet collision
handleBulletCollisionWithReflectiveEnemy(enemy, bullet);
bullet.remove();
bullets.splice(index, 1);
break;
} else if (enemy.classList.contains('boost-drainer')) {
// Handle boost drainer bullet collision
if (handleBoostDrainerHit(bulletObj, index)) {
// Bullet handled and removed inside handler
break;
}
} else {
// Handle other enemy hits
handleEnemyHit(enemy, enemyIndex);
createBurst(newX, parseInt(bullet.style.bottom));
bullet.remove();
bullets.splice(index, 1);
break;
}
}
}
checkBossHit();
}
});
}
function handleEnemyHit(enemy, enemyIndex) {
if (enemy.classList.contains('split-enemy')) {
handleSplitterEnemyHit(enemy, enemyIndex);
} else if (enemy.classList.contains('infected')) {
// Existing infected enemy logic...
let shieldHitsLeft = parseInt(enemy.dataset.shieldHitsLeft) || 0;
if (shieldHitsLeft > 0) {
shieldHitsLeft--;
enemy.dataset.shieldHitsLeft = shieldHitsLeft.toString();
if (shieldHitsLeft === 0) enemy.classList.remove('shielded');
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
return;
}
let health = parseInt(enemy.dataset.health) || 1;
health--;
if (health <= 0) {
enemy.remove();
enemies.splice(enemyIndex, 1);
} else {
enemy.dataset.health = health.toString();
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
} else {
// Normal enemy - destroy immediately
enemy.remove();
enemies.splice(enemyIndex, 1);
}
score += 10;
playerScore += 50;
localStorage.setItem('playerScore', playerScore);
updateDisplay();
}
function startGame() {
// Hide game mode options and other screens
document.getElementById("game-mode-options").style.display = "none";
gameOverScreen.style.display = 'none';
instructions.style.display = 'none';
// Retrieve the game mode from local storage
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'story') {
// Logic for starting Story Mode
resetGame(false); // Continue from current Story Mode level
levelIntro.style.display = 'flex'; // Show level intro for Story Mode
} else if (gameMode === 'endless') {
// Logic for starting Endless Mode
if (!localStorage.getItem('endlessLevel')) {
localStorage.setItem('endlessLevel', '1'); // Start Endless level at 1 if not set
}
resetGame(false); // Continue from last saved Endless level
levelIntro.style.display = 'flex'; // Show level intro for Endless Mode
} else {
// Default to Story Mode if no mode is set
localStorage.setItem('gameMode', 'story'); // Default to Story Mode
localStorage.setItem('storyLevel', '2'); // Set initial level for Story Mode
resetGame(true); // Start a new game
levelIntro.style.display = 'flex'; // Show level intro screen
}
}
function startLevel() {
levelIntro.style.display = 'none';
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function resetGame(isNewGame = false) {
lives = upgradesEquipped.includes('ExtraLives') ? 6 : 3; // Set lives based on the Extra Lives upgrade
// Reset player state variables
playerPositionX = 50;
playerPositionY = 0;
playerVelocityX = 0;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
isDoubleJumpEnabled = false;
gameScrollPosition = 0;
clearAllElements(); // Clear all game entities
// Reset the level based on the game mode.
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'endless') {
level = localStorage.getItem('endlessLevel')
? parseInt(localStorage.getItem('endlessLevel')) // Retrieve endless level
: 1; // Start at Level 1 if none found
} else {
level = localStorage.getItem('storyLevel')
? parseInt(localStorage.getItem('storyLevel'))
: 1; // Default for story mode
}
switchLevelBackground(); // Set the background according to the current level
updateDisplay(); // Reflect updates on the UI
}
function updateDisplay() {
if (isEndlessMode) {
// For Endless Mode
levelDisplay.textContent = `Level: ${currentEndlessLevel}`;
} else {
// For Story Mode
levelDisplay.textContent = `Level: ${storyModeLevel}`;
}
scoreDisplay.textContent = `Score: ${score}`;
livesDisplay.textContent = `Lives: ${lives}`;
timerDisplay.textContent = `Timer: ${powerUpTimer}`;
}
function updatePlayerPosition() {
// Calculate velocity based on input
if (movingRight) {
playerVelocityX = playerMaxSpeed;
} else if (movingLeft) {
playerVelocityX = -playerMaxSpeed;
} else {
playerVelocityX = 0;
}
// Tentative new position
let newPlayerX = playerPositionX + playerVelocityX;
if (!isInBossDoor) {
// Moving border and player left clamp applied only outside boss door/fight
let proposedScroll = Math.max(0, newPlayerX - (gameContainer.offsetWidth / 2));
if (proposedScroll > leftBoundaryX) {
leftBoundaryX = proposedScroll;
}
if (newPlayerX < leftBoundaryX) {
// Clamp player so they cannot move backward beyond moving border
newPlayerX = leftBoundaryX;
}
}
// Inside boss door, no restrictions on player X movement
playerPositionX = newPlayerX;
player.style.left = playerPositionX + 'px';
// Jump/gravity logic stays the same...
let onPlatform = checkCollisionWithPlatformsAndObstacles();
platforms.forEach(platform => {
if (checkCollision(player, platform) && playerVelocityY < 0) {
playerPositionY = parseInt(platform.style.bottom) + platform.offsetHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
}
});
if (!onPlatform || isJumping) {
playerVelocityY -= gravity;
playerPositionY += playerVelocityY;
if (playerPositionY <= 0) {
playerPositionY = 0;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
}
} else {
playerVelocityY = 0;
}
player.style.bottom = playerPositionY + 'px';
// Move camera (handles boss door differently)
moveCamera();
}
function spawnEntities() {
if (!isPaused && !bossFightActive) {
if (Math.random() < 0.003) spawnObstacle();
// Spawn enemies based on the current level
if (level === 1) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy();
if (Math.random() < 0.002) spawnObstacle();
} else if (level === 2) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnChargingEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
} else if (level === 3) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnFlyingEnemy(); // Flying enemy option for Level 3
if (Math.random() < 0.001) spawnReflectiveEnemy(); // Reflective enemy option for Level 3
} else if (level === 4) {
if (Math.random() < 0.001) spawnLaserEnemy();
if (Math.random() < 0.002) spawnSplitterEnemy();
} else if (level === 5) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnVirusDrone();
if (Math.random() < 0.001) spawnSplitterEnemy();
}
if (Math.random() < 0.002 && !currentPowerUp) spawnPowerUp();
if (Math.random() < 0.001) spawnPlatform();
if (Math.random() < 0.003) spawnCoin();
// Manage boss door spawning only if in Story Mode
if (localStorage.getItem('gameMode') === 'story') {
if (bossDoors.length === 0 && score >= level * bossScoreThreshold && enemies.length === 0) {
spawnBossDoor();
}
}
}
}
function cleanUpObjects() {
// Remove obstacles that are too far to the left
obstacles = obstacles.filter(obstacle => {
const position = parseInt(obstacle.style.left);
if (position < -obstacle.offsetWidth) {
obstacle.remove(); // Remove from the DOM
return false; // Do not keep this object in the active array
}
return true; // Keep this object in the active array
});
// Similarly for enemies
enemies = enemies.filter(enemy => {
const position = parseInt(enemy.style.left);
if (position < -enemy.offsetWidth) {
enemy.remove(); // Remove from the DOM
return false; // Do not keep this object in the active array
}
return true; // Keep this object in the active array
});
}
function moveElements() {
moveArray(obstacles, 2, 15);
moveArray(enemies, 1, 30);
moveArray(coins, 2, 0);
moveArray(powerUps, 2, 0, true);
moveArray(platforms, 1, 0);
moveFlyingEnemies();
moveSniperEnemies();
moveLaserEnemies();
moveReflectiveEnemies();
moveSplitterEnemies();
moveChargingEnemies();
moveStealthEnemies();
moveVirusDrones();
moveBullets();
moveNewEnemies();
moveBoostDrainer()
}
// Spawn a Laser Enemy (like a sniper — fixed x, y)
function spawnLaserEnemy() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const laserEnemy = createElement('laser-enemy', enemies, leftPos);
laserEnemy.style.bottom = '300px';
laserEnemy.style.width = '30px';
laserEnemy.style.height = '30px';
laserEnemy.style.backgroundColor = 'purple';
laserEnemy.dataset.laserState = 'warning';
laserEnemy.dataset.lastDamageTime = '0';
laserEnemy._warningLine = null;
laserEnemy._laserBeam = null;
// createElement already does append and enemies.push
}
// Call every frame in moveEnemies or gameLoop
function moveLaserEnemies() {
enemies.forEach(enemy => {
if (!enemy.classList.contains('laser-enemy')) return;
if (enemy.dataset.laserState === 'warning') {
if (!enemy._warningLine) {
createWarningLine(enemy);
setTimeout(() => {
enemy.dataset.laserState = 'active';
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
if (enemy._warningLineUpdateInterval) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
}
activateLaserBeam(enemy);
}, 1000);
}
// ⚠️ WARNING LINE DOES NOT DAMAGE PLAYER - NO COLLISION CHECK!
} else if (enemy.dataset.laserState === 'active') {
const lastDamageTime = parseInt(enemy.dataset.lastDamageTime) || 0;
const now = Date.now();
if (enemy._laserBeam && checkCollision(enemy._laserBeam, player)) {
if (now - lastDamageTime > 1000) { // damage cooldown 1s
reduceLives();
enemy.dataset.lastDamageTime = now.toString();
}
}
}
});
}
function spawnObstacle() {
const obstacle = createElement('obstacle', obstacles, gameScrollPosition + gameContainer.offsetWidth);
obstacle.style.bottom = '0px';
}
function spawnEnemy() {
const baseEnemy = createElement('enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
baseEnemy.style.bottom = '0px';
const chance = Math.random();
if (chance < 0.3) {
spawnTeleporterEnemy();
} else if (chance < 0.7) {
spawnVelocityEnemy();
} else if (chance < 0.9) {
spawnStickyEnemy();
}
}
function moveVirusDrones() {
viruses.forEach((virus, vIndex) => {
if (!virus.infectedCount) virus.infectedCount = 0; // init counter
// Pick target enemy for infection
if (!virus.targetEnemy || !enemies.includes(virus.targetEnemy)) {
virus.targetEnemy = null;
if (enemies.length > 0) {
virus.targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
} else {
// No target, fly left and clean up if off-screen
virus.style.left = (parseInt(virus.style.left) - parseInt(virus.dataset.speed)) + 'px';
if(parseInt(virus.style.left) < gameScrollPosition - virus.offsetWidth) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
return;
}
}
const target = virus.targetEnemy;
const virusPosX = parseFloat(virus.style.left) + virus.offsetWidth / 2;
const virusPosY = parseFloat(virus.style.bottom);
const enemyPosX = parseFloat(target.style.left) + target.offsetWidth / 2;
const enemyPosY = parseFloat(target.style.bottom);
const speedH = parseFloat(virus.dataset.speed) || 5;
const speedV = 10;
// Horizontal move gently towards target
if (Math.abs(virusPosX - enemyPosX) > 5) {
if (virusPosX < enemyPosX) {
virus.style.left = (parseFloat(virus.style.left) + speedH) + 'px';
} else {
virus.style.left = (parseFloat(virus.style.left) - speedH) + 'px';
}
// Use smooth vertical cruising instead of forced fixed height (optional)
const cruisingHeight = 350;
let virusPosYCurrent = parseFloat(virus.style.bottom) || cruisingHeight;
if (virusPosYCurrent < cruisingHeight) {
virus.style.bottom = (virusPosYCurrent + speedV) + 'px';
} else if (virusPosYCurrent > cruisingHeight) {
virus.style.bottom = (virusPosYCurrent - speedV) + 'px';
} else {
virus.style.bottom = cruisingHeight + 'px';
}
} else {
// Move vertically down inside enemy for infection smoothly
const targetInsideY = enemyPosY + target.offsetHeight / 2;
if (virusPosY > targetInsideY + 2) {
// Smooth downward movement clamped to not go below targetInsideY + 2
let newBottom = virusPosY - speedV;
if (newBottom < targetInsideY + 2) {
newBottom = targetInsideY + 2;
}
virus.style.bottom = newBottom + 'px';
} else {
infectEnemy(target);
virus.infectedCount++; // Increment infection count
virus.targetEnemy = null; // Reset target
if (virus.infectedCount >= 3) {
// Destroy virus drone after infecting 3 enemies
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
}
}
// Check bullets hitting virus drone - keep your existing logic here as needed
bullets.forEach((bulletObj, bIndex) => {
const bullet = bulletObj.element;
if (checkCollision(virus, bullet)) {
bullet.remove();
bullets.splice(bIndex, 1);
virusDroneHits++;
if (virusDroneHits >= 3) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
enemies.forEach(e => {
if (e.classList.contains('infected')) {
cureEnemy(e);
}
});
}
}
});
// Remove virus drone if off screen left
if (parseFloat(virus.style.left) < gameScrollPosition - virus.offsetWidth) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
});
}
function createWarningLine(enemy) {
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
const warning = document.createElement('div');
warning.classList.add('laser-warning-line');
const width = 6;
const enemyLeft = parseInt(enemy.style.left);
const enemyBottom = parseInt(enemy.style.bottom);
const enemyHeight = enemy.offsetHeight;
const left = enemyLeft + enemy.offsetWidth / 2 - width / 2;
warning.style.position = 'absolute';
warning.style.left = left + 'px';
warning.style.bottom = '0px';
warning.style.width = width + 'px';
warning.style.height = (enemyBottom + enemyHeight) + 'px';
warning.style.backgroundColor = 'yellow';
warning.style.opacity = '0.7';
warning.style.zIndex = '9';
gameWorld.appendChild(warning);
enemy._warningLine = warning;
if (enemy._warningLineUpdateInterval) clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = setInterval(() => {
if (!enemy._warningLine) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
return;
}
const curLeft = parseInt(enemy.style.left);
const curBottom = parseInt(enemy.style.bottom);
const leftPos = curLeft + enemy.offsetWidth / 2 - width / 2;
enemy._warningLine.style.left = leftPos + 'px';
enemy._warningLine.style.height = (curBottom + enemy.offsetHeight) + 'px';
}, 16);
}
function activateLaserBeam(enemy) {
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
if (enemy._warningLineUpdateInterval) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
}
if (enemy._laserBeam) {
enemy._laserBeam.remove();
enemy._laserBeam = null;
}
const laser = document.createElement('div');
laser.classList.add('laser-beam');
const laserWidth = 8;
const enemyLeft = parseInt(enemy.style.left);
const enemyBottom = parseInt(enemy.style.bottom);
const enemyHeight = enemy.offsetHeight;
const left = enemyLeft + enemy.offsetWidth / 2 - laserWidth / 2;
laser.style.position = 'absolute';
laser.style.left = left + 'px';
laser.style.bottom = '0px';
laser.style.width = laserWidth + 'px';
laser.style.height = (enemyBottom + enemyHeight) + 'px';
laser.style.backgroundColor = 'red';
laser.style.opacity = '0.9';
laser.style.zIndex = '10';
gameWorld.appendChild(laser);
enemy._laserBeam = laser;
enemy._laserBeamUpdateInterval = setInterval(() => {
if (!enemy._laserBeam) {
clearInterval(enemy._laserBeamUpdateInterval);
enemy._laserBeamUpdateInterval = null;
return;
}
const curLeft = parseInt(enemy.style.left);
const curBottom = parseInt(enemy.style.bottom);
const leftPos = curLeft + enemy.offsetWidth / 2 - laserWidth / 2;
enemy._laserBeam.style.left = leftPos + 'px';
enemy._laserBeam.style.height = (curBottom + enemy.offsetHeight) + 'px';
}, 16);
setTimeout(() => {
if (enemy._laserBeam) {
enemy._laserBeam.remove();
enemy._laserBeam = null;
}
clearInterval(enemy._laserBeamUpdateInterval);
enemy._laserBeamUpdateInterval = null;
enemy.dataset.laserState = 'cooldown';
setTimeout(() => {
enemy.dataset.laserState = 'warning';
}, 3000);
}, 2000);
}
// Infect Enemy function
// Infect Enemy function - updated for multiple shield hits and faster speed
function infectEnemy(enemy) {
if (enemy.classList.contains('infected')) return; // Already infected
enemy.classList.add('infected', 'shielded');
enemy.dataset.health = '1'; // 1 health after shield
enemy.dataset.shieldHitsLeft = '2'; // 2 shield hits before health
// Store or retrieve base speed
let baseSpeed = parseInt(enemy.dataset.baseSpeed) || 1;
if (!enemy.dataset.baseSpeed) {
baseSpeed = parseInt(enemy.dataset.speed) || 1;
enemy.dataset.baseSpeed = baseSpeed.toString();
}
enemy.dataset.speed = (baseSpeed * 3).toString(); // Speed is tripled
enemy.dataset.powerful = 'true';
// Visual effects
enemy.style.opacity = '0.8';
enemy.style.filter = 'drop-shadow(0 0 5px lime)';
}
// Handle bullet collision with shielded enemy - place this inside your bullet collision code
function handleBulletCollisionWithInfectedEnemy(enemy, bulletIndex) {
if (enemy.classList.contains('infected') && enemy.classList.contains('shielded')) {
let shieldHitsLeft = parseInt(enemy.dataset.shieldHitsLeft) || 0;
shieldHitsLeft--;
// Shield hit logic to decrement shield hits
if (shieldHitsLeft <= 0) {
// Shield broken, now damage health
enemy.dataset.shieldHitsLeft = '0';
// Decrement health by 1 on each bullet after shield is down
let health = parseInt(enemy.dataset.health) || 2; // default infected health 2
health--;
if (health <= 0) {
// Enemy dies
enemy.remove();
enemies = enemies.filter(e => e !== enemy);
} else {
enemy.dataset.health = health.toString();
// Maybe flash enemy to indicate hit
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
} else {
// Shield still active
enemy.dataset.shieldHitsLeft = shieldHitsLeft.toString();
// Flash on shield hit
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
// Remove the bullet after hit
bullets[bulletIndex].element.remove();
bullets.splice(bulletIndex, 1);
return true;
}
return false;
}
// Cure enemy restores normal speed and removes shield
function cureEnemy(enemy) {
enemy.classList.remove('infected', 'shielded');
enemy.style.pointerEvents = 'auto';
enemy.style.opacity = '1';
enemy.style.filter = 'none';
// Reset speed to baseSpeed or default 1
enemy.dataset.speed = enemy.dataset.baseSpeed || '1';
enemy.dataset.shieldHitsLeft = '0';
enemy.dataset.health = '1'; // normal enemies 1 HP
enemy.dataset.powerful = 'false';
// You may reset other enemy attributes here if applied.
}
// Enhance Enemy stats (called on infection)
function enhanceEnemy(enemy) {
// Example: double speed and fire rate if enemy shoots
const speed = parseInt(enemy.dataset.speed) || 1;
enemy.dataset.speed = speed * 2;
// If enemy shoots bullets (like sniper), reduce cooldown or increase damage
if(enemy.dataset.lastShotTime) {
enemy.dataset.shootCooldown = (parseInt(enemy.dataset.shootCooldown) || 3000) / 2;
}
// You can add more attribute enhancements here, e.g. health, damage, size
}
// Normalize Enemy back to default (called on cure)
function normalizeEnemy(enemy) {
const speed = parseInt(enemy.dataset.speed) || 2;
enemy.dataset.speed = speed / 2;
if(enemy.dataset.lastShotTime) {
enemy.dataset.shootCooldown = (parseInt(enemy.dataset.shootCooldown) || 1500) * 2;
}
// Reset any other attributes to normal here
}
function spawnPowerUp() {
const powerUpTypes = allPowerUpTypes;
const type = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)];
const powerUp = createElement('power-up', powerUps, gameScrollPosition + gameContainer.offsetWidth);
powerUp.setAttribute('data-type', type);
powerUp.style.backgroundColor = getPowerUpColor(type);
powerUp.style.bottom = Math.floor(Math.random() * 100) + 'px';
}
function spawnCoin() {
const coin = createElement('coin', coins, gameScrollPosition + gameContainer.offsetWidth);
coin.style.bottom = Math.floor(Math.random() * 50 + 50) + 'px';
}
function getPowerUpColor(type) {
switch (type) {
case 'super-speed': return 'lime';
case 'invincibility': return 'gold';
case 'teleportation': return 'violet';
case 'reflective-shield': return 'aqua';
case 'double-shot': return 'red';
case 'extra-life': return 'lightgreen';
case 'score-boost': return 'coral';
case 'mega-jump': return 'fuchsia';
case 'energy-blast': return 'limegreen'; // Specify colors for valid powers
case 'light-speed': return 'white'; // Define colors for light-speed
case 'blazing-trail': return 'gold'; // Specify for blazing-trail
case 'sonic-wave': return 'blueviolet'; // Specify for sonic-wave
case 'armor': return 'lightgrey'; // Define color for armor
case 'telekinetic': return 'silver'; // Define color for telekinetic
case 'camouflage': return 'darkgreen'; // Define color for camouflage
case 'dimension-shift': return 'black'; // Define color for dimension-shift
case 'energy-drain': return 'red'; // Define color for energy-drain
case 'mind-control': return 'purple'; // Define color for mind-control
case 'hologram': return 'pink'; // Define color for hologram
case 'fire': return 'red'; // Define color for energy-drain
case 'ice': return 'darkblue'; // Define color for energy-drain
case 'hypno': return 'pink'; // Define color for energy-drain
default: return 'white'; // Fallback for any unknown type
}
}
function spawnPlatform() {
const platform = createElement('platform', platforms, gameScrollPosition + gameContainer.offsetWidth);
platform.style.bottom = Math.floor(Math.random() * 200 + 100) + 'px';
}
function spawnBossDoor() {
const bossDoor = createElement('boss-door', bossDoors, gameScrollPosition + gameContainer.offsetWidth + 200);
bossDoor.style.bottom = '0px';
}
function createElement(className, array, leftPosition, type = '') {
const element = document.createElement('div');
element.classList.add(className);
if (type) element.setAttribute('data-type', type);
element.style.left = leftPosition + 'px';
gameWorld.appendChild(element);
array.push(element);
return element;
}
function moveArray(array, speed, scoreValue, isPowerUp = false) {
array.forEach((el, index) => {
el.style.left = (parseInt(el.style.left) - speed) + 'px';
if (parseInt(el.style.left) < gameScrollPosition - el.offsetWidth) {
removeElement(el, array, index, 0);
} else if (checkCollision(player, el)) {
handleCollision(el, array, index, scoreValue, isPowerUp);
}
});
}
function removeElement(el, array, index, scoreValue) {
el.remove();
array.splice(index, 1);
if (scoreValue) {
score += scoreValue;
updateDisplay();
}
}
function handleCollision(el, array, index, scoreValue, isPowerUp = false) {
if (el.classList.contains('coin')) {
score += 5; // Increase score for collecting a coin
updateDisplay(); // Update the display if needed
}
// Handle power-ups
else if (isPowerUp) {
applyPowerUp(el); // Apply power-up effects
}
// Handle boss door interactions
else if (el.classList.contains('boss-door')) {
if (score >= level * bossScoreThreshold) {
interactMessage.textContent = "Press 'E' to enter boss door."; // Prompt for interaction
interactMessage.style.display = checkCollision(player, el) ? 'block' : 'none'; // Show message if player is near
if (checkCollision(player, el) && playerPositionX >= parseInt(el.style.left)) {
openBossHallway(); // Proceed to boss hallway
}
}
}
// Handle enemy and obstacle collisions
else if (el.classList.contains('enemy') || el.classList.contains('obstacle')) {
reduceLives(); // Reduce player's lives on collision with enemies or obstacles
}
// Remove the collided element from the game world
removeElement(el, array, index, scoreValue);
}
function applyPowerUp(el) {
if (currentPowerUp || currentPowerUp === 'boss-power-up') return;
const type = el.getAttribute('data-type');
if (type === 'boss-power-up') {
grantBossPowerUp();
} else {
currentPowerUp = type;
let duration = 10;
activatePowerUp(type);
powerUpTimer = duration;
const powerUpInterval = setInterval(() => {
powerUpTimer--;
if (powerUpTimer <= 0) {
clearInterval(powerUpInterval);
resetPowerUp();
}
updateDisplay();
}, 1000);
}
displayPowerUpMessage(type);
}
function activatePowerUp(type) {
switch (type) {
case 'super-speed':
player.style.backgroundColor = 'lime';
playerMaxSpeed += 8; // Increase speed
break;
case 'invincibility':
player.style.backgroundColor = 'gold';
PlayerInvincibility(); // Implement this function for invincibility
break;
case 'teleportation':
teleportPlayer(); // Implement this function for teleportation
break;
case 'reflective-shield':
activateReflectiveShield(); // Implement the reflective shield functionality
break;
case 'double-shot':
enableDoubleShot(); // Implement functionality for double shot
break;
case 'extra-life':
gainExtraLife(); // Implement functionality for gaining an extra life
break;
case 'score-boost':
boostScore(); // Implement functionality for score boost
break;
case 'mega-jump':
activateMegaJump(); // Implement functionality for mega jump
break;
case 'energy-blast':
activateEnergyBlast(); // Implement functionality for energy blast
break;
case 'light-speed':
player.style.backgroundColor = 'lime';
playerMaxSpeed += 10; // Increase speed
break;
case 'blazing-trail':
activateBlazingTrail(); // Implement functionality for blazing trail
break;
case 'sonic-wave':
activateSonicWave(); // Implement functionality for sonic wave
break;
case 'armor':
activateArmor(); // Implement functionality for armor
break;
case 'telekinetic':
activateTelekinetic(); // Implement functionality for telekinetic
break;
case 'camouflage':
activateCamouflage(); // Implement functionality for camouflage
break;
case 'dimension-shift':
activateDimensionShift(); // Implement functionality for dimension shift
break;
case 'energy-drain':
activateEnergyDrain(); // Implement functionality for energy drain
break;
case 'mind-control':
activateMindControl(); // Implement functionality for mind control
break;
case 'hologram':
activateHologram(); // Implement functionality for hologram
break;
case 'fire':
shootFire();
break;
case 'ice':
shootIce();
break;
case 'hypno':
shootHypno();
break;
default:
// Consider logging or managing invalid power-up requests
console.log(`Unknown power-up type: ${type}`);
break;
}
}
function handlePlayerEnemyCollision() {
enemies.forEach((enemy, index) => {
// Check collision with the player
if (checkCollision(player, enemy)) {
if (enemy.classList.contains('frozen')) {
// If frozen, destroy the enemy
enemy.remove(); // Remove frozen enemy
enemies.splice(index, 1); // Remove from the enemies array
updateDisplay(); // Update any relevant game display elements if needed
} else {
// Normal behavior for non-frozen enemies
reduceLives(); // This would reduce lives for non-frozen collisions
}
}
});
}
function allowPlayerMovement(e) {
switch (e.key) {
case 'ArrowLeft':
playerPositionX -= playerMaxSpeed; // Move left
player.style.left = playerPositionX + 'px';
break;
case 'ArrowRight':
playerPositionX += playerMaxSpeed; // Move right
player.style.left = playerPositionX + 'px';
break;
case ' ': // Space to shoot
shootBullet(currentDirection); // Allow shooting during freeze
break;
}
}
function resetPowerUp() {
currentPowerUp = null; // Also can now be re-assigned
player.style.backgroundColor = '#4CAF50';
if (upgradesEquipped.includes('speedBoost')) {
playerMaxSpeed = 7; // Set the boosted speed or any other value you want
} else {
playerMaxSpeed = 5; // Default speed if the upgrade is not equipped
}
player.style.opacity = '1';
if (upgradesEquipped.includes('doubleJump')) {
isDoubleJumpEnabled = true; // Enable double jump if upgrade is equipped
} else {
isDoubleJumpEnabled = false; // Disable double jump otherwise
}
clearTimeout(timeSlowTimeout);
resetGravity();
}
function displayPowerUpMessage(type) {
powerUpMessage.textContent = `You obtained ${type.replace(/-/g, ' ')}!`;
powerUpMessage.style.display = 'block';
setTimeout(() => powerUpMessage.style.display = 'none', 2000);
}
function grantBossPowerUp() {
bossPowerObtained = true;
powerUpMessage.textContent = "Boss Power-Up Obtained!";
powerUpMessage.style.display = 'block';
setTimeout(() => powerUpMessage.style.display = 'none', 2000);
}
function PlayerInvincibility() {
// Check for collision with enemies
enemies.forEach((enemy) => {
if (checkCollision(player, enemy)) {
if (!isInvincible) { // Only reduce lives if not invincible
if (lives > 1) {
lives--; // Deduct a life when colliding with an enemy
updateDisplay(); // Update the displayed lives
}
}
}
});
// Immediately reset lives to 3 if they drop below that
if (lives < 3) {
lives = 3; // Reset lives to 3 instantly
updateDisplay(); // Update display to show 3 lives
}
}
function usePowerUp() {
if (!currentPowerUp) return;
switch (currentPowerUp) {
case 'ice':
shootIce();
break;
case 'sonic-wave':
activateSonicWave();
break;
case 'fire':
shootFire();
break;
case 'teleportation':
teleportPlayer();
break;
case 'levitate':
useLevitate();
break;
case 'hypno':
shootHypno();
break;
case 'double-shot':
shootDouble();
break;
default:
break;
}
}
function shootIce() {
const iceBullet = createBullet(currentDirection); // Create the ice bullet
iceBullet.style.backgroundColor = 'blue'; // Set bullet color to blue
gameWorld.appendChild(iceBullet);
const iceBulletObj = { element: iceBullet, direction: currentDirection };
animateBullet(iceBulletObj, 5, (target) => {
// Check for collision with enemies
if (target.classList.contains('obstacles')) {
target.classList.add('frozen'); // Mark the enemy as frozen
target.style.backgroundColor = '#00f'; // Change color to blue
target.style.opacity = '0.5'; // Reduce visibility
target.dataset.isFrozen = 'true'; // Mark enemy as frozen
// Set a timer to unfreeze
setTimeout(() => {
if (target.dataset.isFrozen === 'true') {
target.classList.remove('frozen'); // Remove frozen status
target.style.backgroundColor = ''; // Reset color
target.style.opacity = '1'; // Reset visibility
delete target.dataset.isFrozen; // Clear the frozen marker
}
}, 5000); // 5 seconds to unfreeze
}
});
}
function shootFire() {
const fireBullet = createBullet(currentDirection);
fireBullet.style.backgroundColor = 'red';
gameWorld.appendChild(fireBullet);
const fireBulletObj = { element: fireBullet, direction: currentDirection };
animateBullet(fireBulletObj, 5, (target) => {
if (target.classList.contains('obstacle')) {
target.style.transition = 'opacity 0.5s';
target.style.opacity = '0';
setTimeout(() => target.remove(), 500);
}
});
}
function useLevitate() {
player.style.transform = 'translateY(-20px)';
const levitateUp = setTimeout(() => {
player.style.transform = 'translateY(0)';
clearTimeout(levitateUp);
}, 1000);
}
function enableDoubleShot() {
}
function createBullet(direction = 1) {
const bullet = document.createElement('div');
bullet.classList.add('bullet');
bullet.style.backgroundColor = '#FFD700';
bullet.style.left = (playerPositionX + player.offsetWidth / 2 - 5) + 'px';
bullet.style.bottom = (playerPositionY + player.offsetHeight / 2) + 'px';
return bullet;
}
function shootHypno() {
const hypnoBullet = createBullet(currentDirection);
hypnoBullet.style.backgroundColor = 'pink';
gameWorld.appendChild(hypnoBullet);
const hypnoBulletObj = { element: hypnoBullet, direction: currentDirection };
animateBullet(hypnoBulletObj, 5, (target) => {
if (target.classList.contains('enemy') && !target.classList.contains('hypnotized')) {
target.classList.add('hypnotized');
target.style.backgroundColor = 'pink';
hypnotizeEnemy(target);
}
});
}
function shootDouble() {
const bullet1 = createBullet(currentDirection);
const bullet2 = createBullet(currentDirection);
bullets.push({ element: bullet1, direction: currentDirection });
bullets.push({ element: bullet2, direction: currentDirection });
gameWorld.appendChild(bullet1);
gameWorld.appendChild(bullet2);
}
function hypnotizeEnemy(enemy) {
let destroyCount = 0; // Count how many enemies are destroyed by this hypnotized enemy
const moveInterval = setInterval(() => {
let nearestEnemy = findNearestEnemyTo(enemy);
if (!nearestEnemy) {
enemy.remove(); // If no nearest enemy, remove this one
clearInterval(moveInterval); // Stop interval
return;
}
let ex = parseInt(enemy.style.left);
let ey = parseInt(enemy.style.bottom);
let nex = parseInt(nearestEnemy.style.left);
let ney = parseInt(nearestEnemy.style.bottom);
let dx = nex - ex;
let dy = ney - ey;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
destroyCount++;
createBurst(nex, ney); // Create an effect when destroying enemies
nearestEnemy.remove();
enemies = enemies.filter(e => e !== nearestEnemy); // Clean up enemies
if (destroyCount >= 3) { // Define a limit for multiple enemies hypnotized
enemy.remove(); // Remove this hypnotized enemy after doing its effects
clearInterval(moveInterval); // Stop interval
}
} else {
let angle = Math.atan2(dy, dx); // Calculate angle to move towards the nearest enemy
enemy.style.left = (ex + Math.cos(angle) * 2) + 'px'; // Move slightly towards
enemy.style.bottom = (ey + Math.sin(angle) * 2) + 'px';
}
}, 1000 / 60); // Run update at 60 FPS
}
function findNearestEnemyTo(sourceEnemy) {
let minDistance = Infinity;
let closestEnemy = null;
enemies.forEach(enemy => {
if (enemy !== sourceEnemy) {
let distance = calculateDistance(sourceEnemy, enemy);
if (distance < minDistance) {
minDistance = distance;
closestEnemy = enemy;
}
}
});
return closestEnemy;
}
function calculateDistance(el1, el2) {
let el1Rect = el1.getBoundingClientRect();
let el2Rect = el2.getBoundingClientRect();
let dx = (el1Rect.left + el1Rect.width / 2) - (el2Rect.left + el2Rect.width / 2);
let dy = (el1Rect.top + el1Rect.height / 2) - (el2Rect.top + el2Rect.height / 2);
return Math.sqrt(dx * dx + dy * dy);
}
function enableDoubleJump(enable = true) {
isDoubleJumpEnabled = enable;
}
function moveBoostDrainer() {
if (!boostDrainer) return;
// Move left slowly
let currentX = parseInt(boostDrainer.style.left);
boostDrainer.style.left = (currentX - 0.5) + 'px'; // Adjust speed here
// Remove if off-screen left
if (currentX < gameScrollPosition - boostDrainer.offsetWidth) {
removeBoostDrainer();
return;
}
// Update chain line connecting player and boost drainer
if (boostDrainerChain) {
updateChainLine(boostDrainer, boostDrainerChain, player);
}
}
function teleportPlayer() {
if (isTeleporting) return; // Prevent multiple teleports in quick succession
// Set cooldown
isTeleporting = true; // Set to true to indicate teleport in progress
// Calculate teleportation distance
const teleportDistance = 145; // Increase this to a larger value for more noticeable teleport
// Determine player current direction
let teleportOffset = teleportDistance * currentDirection; // Use currentDirection to allow for left (-1) and right (1) teleporting
// Update player position
playerPositionX += teleportOffset; // Move the player in the direction they are facing
playerPositionX = Math.max(0, Math.min(gameWorld.offsetWidth - player.offsetWidth, playerPositionX)); // Ensure player stays in bounds
// Log positions for debugging
console.log('Teleporting to position:', playerPositionX);
player.style.left = playerPositionX + 'px'; // Update player's position on screen
// Set a timeout for the cooldown
setTimeout(() => {
isTeleporting = false; // Reset teleport ability after cooldown
}, 1000); // 1000 milliseconds = 1 second cooldown
}
function toggleGravity() {
const originalGravity = gravity;
gravity = -gravity;
setTimeout(() => {
gravity = originalGravity;
}, 5000);
}
function resetGravity() {
gravity = Math.abs(gravity); // Needs to ensure gravity is declared with let or var
}
function freezeEnemies() {
enemies.forEach(enemy => {
enemy.style.transition = 'opacity 1s';
enemy.style.opacity = '0.2';
enemy.classList.add('frozen-enemy');
});
setTimeout(() => {
enemies.forEach(enemy => {
enemy.style.opacity = '1';
enemy.classList.remove('frozen-enemy');
});
}, 5000);
}
function createClone() {
const clone = createElement('player', [], playerPositionX + 50);
clone.style.bottom = playerPositionY + 'px';
clone.style.backgroundColor = '#00F';
clone.style.border = '2px dashed #FFF';
gameWorld.appendChild(clone);
setTimeout(() => clone.remove(), 5000);
}
function triggerShockwave() {
const shockwaveRadius = 10000;
enemies.forEach(enemy => {
const dist = calculateDistance(player, enemy);
if (dist < shockwaveRadius) {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
enemy.remove();
}
});
}
function absorbEnergy() {
score += 50;
updateDisplay();
}
function shrinkPlayer() {
player.style.transform = 'scale(0.5)';
setTimeout(() => player.style.transform = 'scale(1)', 5000);
}
function growPlayer() {
player.style.transform = 'scale(1.5)';
setTimeout(() => player.style.transform = 'scale(1)', 5000);
}
function healPlayer() {
if (lives < 5) {
lives++;
updateDisplay();
}
}
function activateShield() {
player.style.border = '3px solid purple';
player.style.opacity = '0.6';
lives = Math.min(lives + 1, 7);
updateDisplay();
}
function gainExtraLife() {
lives = Math.min(lives + 1, 7);
updateDisplay();
}
function gainExtraLife2() {
lives = Math.min(lives + 3, 6); // Increase lives by 3, ensuring it doesn't exceed 5
updateDisplay(); // Update the display to reflect the new number of lives
}
function removeLife() {
lives = Math.max(lives - 3, 3); // Decrease lives by 3, ensuring it doesn't fall below 3
updateDisplay(); // Update the display to reflect the new number of lives
}
function boostScore() {
score += 100;
updateDisplay();
}
let consecutiveJumps = 0; // Initialize a variable to track consecutive jumps
const maxJumpCount = 3; // Maximum number of normal jumps before triggering a mega jump
const megaJumpMultiplier = 1.5; // Multiplier for the mega jump
function activateMegaJump() {
if (consecutiveJumps < maxJumpCount) {
// Perform a normal jump if within the max jump limit
playerVelocityY = playerJumpStrength; // Regular jump value
consecutiveJumps++; // Increment consecutive jumps
} else {
// Trigger a mega jump if max jump limit has been reached
playerVelocityY = playerJumpStrength * megaJumpMultiplier; // Apply mega jump
consecutiveJumps = 0; // Reset consecutive jumps to 0 after a mega jump
}
isJumping = true; // Set the jumping status to true
}
// Call this function whenever the player lands to reset the consecutive jumps count
function checkLanding() {
if (playerPositionY <= 0) { // Assuming ground level is at y = 0
consecutiveJumps = 0; // Reset consecutive jumps when landing
}
}
function activateReflectiveShield() {
player.style.border = '3px dashed cyan';
const reflectInterval = setInterval(() => {
bullets.forEach((bullet, index) => {
if (checkCollision(player, bullet.element)) {
bullet.direction *= -1;
}
});
}, 1000 / 60);
setTimeout(() => {
player.style.border = '';
clearInterval(reflectInterval);
}, 5000);
}
function activateRegeneration() {
const regenerationInterval = setInterval(() => {
if (lives < 5) lives++;
updateDisplay();
if (!currentPowerUp || currentPowerUp !== 'regeneration') {
clearInterval(regenerationInterval);
}
}, 2000);
}
function activateMagnet() {
const magnetInterval = setInterval(() => {
coins.forEach(coin => {
const coinX = parseInt(coin.style.left);
const coinY = parseInt(coin.style.bottom);
const dx = playerPositionX - coinX;
const dy = playerPositionY - coinY;
if (Math.sqrt(dx * dx + dy * dy) < 100) {
coin.style.left = (coinX + dx * 0.1) + 'px';
coin.style.bottom = (coinY + dy * 0.1) + 'px';
}
});
if (!currentPowerUp || currentPowerUp !== 'magnet') {
clearInterval(magnetInterval);
}
}, 1000/ 60);
}
let timeSlowTimeout;
function slowDownTime() {
const originalTiming = 1000 / 60;
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, originalTiming * 2);
timeSlowTimeout = setTimeout(() => {
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, originalTiming);
}, 5000);
}
function activateEnergyBlast() {
const blastRadius = 100; // Define the radius of the blast
const burst = document.createElement('div'); // Create a burst element
burst.classList.add('energy-blast'); // Assign a class for styles
burst.style.left = (playerPositionX - blastRadius) + 'px'; // Position the burst to the player's location
burst.style.bottom = (playerPositionY - blastRadius) + 'px'; // Center the burst around the player
gameWorld.appendChild(burst); // Add the burst to the game world
// Set a timeout to remove the burst effect after a duration
setTimeout(() => {
burst.remove(); // Remove the burst after showing it
}, 500); // Keep the burst visible for half a second
// Identify and destroy nearby enemies
let enemiesToRemove = enemies.filter(enemy => calculateDistance(player, enemy) < blastRadius);
enemiesToRemove.forEach(enemy => {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom)); // Create an explosion effect at enemy's location
enemy.remove(); // Remove the enemy from the game
enemies = enemies.filter(e => e !== enemy); // Ensure the enemy is removed from the enemies array
});
updateDisplay(); // Update the display to reflect any changes (like score)
}
function initializeBossHealth() {
bossHealth = level === 3 ? 250 : 100; // Example health values based on level
updateDisplay();
}
function activateBlazingTrail() {
let trailInterval = setInterval(() => {
const trail = document.createElement('div');
trail.className = 'bullet'; // Using the same class for stylistic purposes
trail.style.left = (playerPositionX + player.offsetWidth / 2) + 'px'; // Position trail based on player's location
trail.style.bottom = (playerPositionY + player.offsetHeight / 2) + 'px'; // Center the trail on the player
trail.style.backgroundColor = 'orange'; // Trail color
gameWorld.appendChild(trail); // Add the trail to the game
// Check collision with enemies
enemies.forEach((enemy, enemyIndex) => {
if (checkCollision(trail, enemy)) {
// Create burst effect for visual feedback
const burst = document.createElement('div');
burst.className = 'burst'; // The CSS class for the burst effect
burst.style.left = enemy.style.left; // Position it at the enemy's position
burst.style.bottom = enemy.style.bottom; // Position it at the enemy's position
gameWorld.appendChild(burst);
// Clean up burst after a short duration
setTimeout(() => burst.remove(), 500); // Remove burst after 0.5 seconds
// Remove enemy
enemy.remove(); // Remove the enemy from game
enemies.splice(enemyIndex, 1); // Remove from enemies array to prevent future collisions
}
});
// Remove the trail after a short duration
setTimeout(() => trail.remove(), 500); // Remove trail after 0.5 seconds
}, 100); // The interval for creating the trails
// Automatically clear the interval after a certain time, e.g., 5 seconds
setTimeout(() => {
clearInterval(trailInterval); // Clear the interval for the trail creation
}, 5000);
}
function activateSonicWave() {
if (waveCooldown) return; // Prevent activation if already on cooldown
waveCooldown = true; // Set cooldown to prevent further activation
const wave = document.createElement('div');
wave.className = 'wave'; // Class for wave styling
wave.style.left = (playerPositionX - 50) + 'px'; // Center the wave below the player
wave.style.bottom = (playerPositionY - 50) + 'px'; // Position just below player
wave.style.backgroundColor = 'cyan'; // Wave color
wave.style.width = '100px'; // Width of the wave
wave.style.height = '100px'; // Height of the wave
wave.style.position = 'absolute'; // Ensure proper positioning
gameWorld.appendChild(wave); // Add the wave to the game world
let destroyedBlocks = 0; // Track how many blocks have been destroyed
// Move the wave forward quickly
let waveInterval = setInterval(() => {
wave.style.left = (parseInt(wave.style.left) + 10) + 'px'; // Move wave to the right
// Check for collision with obstacles
obstacles.forEach((block, index) => {
if (checkCollision(wave, block) && destroyedBlocks < 3) { // Destroy only until 3 blocks
// Create burst effect
const burst = document.createElement('div');
burst.className = 'burst';
burst.style.left = block.style.left;
burst.style.bottom = block.style.bottom;
gameWorld.appendChild(burst);
setTimeout(() => burst.remove(), 500); // Remove burst after half a second
block.remove(); // Remove the obstacle
obstacles.splice(index, 1); // Remove from the array
destroyedBlocks++; // Increment the destroyed block count
}
});
// Cleanup if wave goes off screen
if (parseInt(wave.style.left) > gameWorld.offsetWidth) {
clearInterval(waveInterval); // Stop moving the wave if it goes off screen
wave.remove(); // Remove the wave
resetWave(); // Reset block destruction count
return; // Exit the function
}
}, 50); // Adjust speed of the wave as needed
// Automatically remove the wave after a brief time
setTimeout(() => {
clearInterval(waveInterval); // Ensure the interval is cleared after time is up
wave.remove(); // Remove the wave
resetWave(); // Reset properties after the effect finishes
}, 2000); // Duration of the wave effect (2 seconds)
}
// Reset wave properties for future use
function resetWave() {
destroyedBlocks = 0; // Reset the number of destroyed blocks for future waves
waveCooldown = false; // Reset cooldown state
}
function activateArmor() {
player.style.border = '3px solid steelblue';
lives = Math.min(lives + 3, 5);
updateDisplay();
}
function activateTelekinetic() {
const forceInterval = setInterval(() => {
enemies.forEach((enemy, index) => {
const enemyX = parseInt(enemy.style.left) + enemy.offsetWidth / 2; // Get center position
const enemyY = parseInt(enemy.style.bottom) + enemy.offsetHeight / 2; // Get center position
const dx = playerPositionX - enemyX;
const dy = playerPositionY - enemyY;
// Check if the player is within a certain distance (e.g., 100 pixels)
if (Math.sqrt(dx * dx + dy * dy) < 100) {
// Move the enemy upwards slightly
enemy.style.bottom = (enemyY + 2) + 'px'; // Adjust height as necessary for feel
} else if (enemyY > 0) { // Only lower it if above 0
// Bring the enemy back down to ground level smoothly
enemy.style.bottom = (enemyY - 2) + 'px'; // Adjust to bring back down
// Stop exactly at the ground level if necessary
if (parseInt(enemy.style.bottom) < 0) {
enemy.style.bottom = '0px'; // Prevent going below 0
}
}
});
}, 1000 / 60); // Update at 60 FPS
// Optional: Set a timeout for how long telekinesis lasts
setTimeout(() => clearInterval(forceInterval), 5000); // Lasts for 5 seconds
}
function activateCamouflage() {
player.style.opacity = '0.2';
setTimeout(() => player.style.opacity = '1', 5000);
}
// Array to temporarily hold removed enemies
function activateDimensionShift() {
// Change the background color to indicate the shift
gameWorld.style.backgroundColor = 'black';
// Hide and remove all enemies in the game world
enemies.forEach((enemy, index) => {
enemy.style.transition = 'opacity 1s'; // Set transition for fading effect
enemy.style.opacity = '0'; // Start fading the enemy
setTimeout(() => {
enemy.remove(); // Remove from the DOM after fading
enemies.splice(index, 1); // Remove from the enemies array
}, 1000); // Match the timeout to the fade-out duration (1 second)
});
// Hide and remove all obstacles in the game world
obstacles.forEach((obstacle, index) => {
obstacle.style.transition = 'opacity 1s'; // Set transition for fading effect
obstacle.style.opacity = '0'; // Start fading the obstacle
setTimeout(() => {
obstacle.remove(); // Remove from the DOM after fading
obstacles.splice(index, 1); // Remove from the obstacles array
}, 1000); // Match the timeout to the fade-out duration (1 second)
});
// Reset background color after a period
setTimeout(() => {
gameWorld.style.backgroundColor = ''; // Reset the background color after 5 seconds
// Restore removed enemies
removedEnemies.forEach(removedEnemy => {
// Re-create the enemy in the game world
const newEnemy = createElement(removedEnemy.className, enemies, gameScrollPosition + gameContainer.offsetWidth);
newEnemy.style.bottom = '0px'; // Reset position
gameWorld.appendChild(newEnemy); // Append back to game world
});
// Clear out the removedEnemies array for future use
removedEnemies = [];
// If you have obstacles that should be restored, handle that here as well
// You may need to keep track of removed obstacles in a similar way if needed
}, 5000); // Keep the black background for 5 seconds
}
function activateEnergyDrain() {
const drainInterval = setInterval(() => {
if (score > 0) {
score -= 1;
updateDisplay();
}
}, 100);
setTimeout(() => clearInterval(drainInterval), 5000);
}
function activateMindControl() {
const hypnotizeRange = 250; // Set hypnotize range in pixels (you can adjust this)
// Loop through all enemies
enemies.forEach(enemy => {
const enemyX = parseInt(enemy.style.left) + (enemy.offsetWidth / 2); // Get the center of the enemy
const enemyY = parseInt(enemy.style.bottom) + (enemy.offsetHeight / 2); // Get the center of the enemy
const dx = playerPositionX - enemyX; // Calculate horizontal distance
const dy = playerPositionY - enemyY; // Calculate vertical distance
// Check if within the hypnotize range
const distance = Math.sqrt(dx * dx + dy * dy); // Calculate distance to the player
if (distance < hypnotizeRange) {
if (!enemy.classList.contains('hypnotized')) {
enemy.classList.add('hypnotized'); // Mark enemy as hypnotized
enemy.style.backgroundColor = 'violet'; // Change color to indicate hypnotization
// Call the function to handle hypnosis effects
hypnotizeEnemy(enemy);
}
}
});
}
function activateHologram() {
const hologram = document.createElement('div');
hologram.className = 'hologram'; // Use a hologram class for styling
hologram.style.position = 'absolute';
hologram.style.left = `${playerPositionX - 25}px`; // Position the hologram centered relative to player
hologram.style.bottom = `${playerPositionY}px`; // Vertical alignment with player
hologram.style.width = '50px'; // Fixed width for better visibility
hologram.style.height = '50px'; // Fixed height for better visibility
hologram.style.backgroundColor = 'rgba(0, 0, 255, 0.5)'; // Semi-transparent blue color
hologram.style.borderRadius = '50%'; // Round shape
hologram.style.zIndex = '100'; // Ensure the hologram is above other elements
gameWorld.appendChild(hologram); // Add the hologram to the game world
// Pulsate effect
let scale = 5;
const pulseSpeed = 0.05; // Adjust pulsate speed
const maxScale = 1.2; // Maximum scale for pulsate
const minScale = 1; // Minimum scale for pulsate
const pulseInterval = setInterval(() => {
scale += pulseSpeed * Math.sin(Date.now() / 500); // Use sine for smooth pulsating effect
scale = Math.max(minScale, Math.min(scale, maxScale)); // Clamp scale
hologram.style.transform = `scale(${scale})`;
}, 100); // Make it appear smooth
// Behavior to distract nearby enemies
enemies.forEach(enemy => {
const enemyX = parseInt(enemy.style.left) + (enemy.offsetWidth / 2);
const enemyY = parseInt(enemy.style.bottom) + (enemy.offsetHeight / 2);
const dx = playerPositionX - enemyX;
const dy = playerPositionY - enemyY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) { // If enemy is within distraction range
// Smoothly move the enemy away from the hologram
const moveAmount = 50; // The distance to move away
const newLeft = enemyX + (dx > 0 ? moveAmount : -moveAmount); // Move away from hologram
enemy.style.transition = 'left 0.5s'; // Smooth movement transition
enemy.style.left = `${newLeft}px`;
}
});
// Remove the hologram after 5 seconds
setTimeout(() => {
clearInterval(pulseInterval); // Stop the pulsating effect
hologram.remove(); // Remove hologram from the game world
}, 7500);
}
function animateBullet(bulletObj, speed, impactCallback = () => {}) {
const bulletInterval = setInterval(() => {
const bullet = bulletObj.element;
const newLeft = parseInt(bullet.style.left) + (bulletObj.direction * speed);
bullet.style.left = newLeft + 'px';
if (newLeft > gameScrollPosition + gameContainer.offsetWidth || newLeft < 0) {
bullet.remove();
clearInterval(bulletInterval);
return;
}
for (const target of [...obstacles, ...enemies]) {
if (checkCollision(bullet, target)) {
// Handle infected + shielded enemy
if (target.classList.contains('infected') && target.classList.contains('shielded')) {
let shieldHitsLeft = parseInt(target.dataset.shieldHitsLeft) || 2;
shieldHitsLeft--;
if (shieldHitsLeft <= 0) {
// Shield broken
target.dataset.shieldHitsLeft = '0';
target.classList.remove('shielded');
let health = parseInt(target.dataset.health) || 1;
health--;
if (health <= 0) {
target.remove();
enemies = enemies.filter(e => e !== target);
} else {
target.dataset.health = health.toString();
target.style.opacity = '0.5';
setTimeout(() => {
if (target) target.style.opacity = '0.8';
}, 150);
}
} else {
// Shield still active, just decrement and flash
target.dataset.shieldHitsLeft = shieldHitsLeft.toString();
target.style.opacity = '0.5';
setTimeout(() => {
if (target) target.style.opacity = '0.8';
}, 150);
}
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Hypno bullet effect (no instant death)
if (bullet.classList.contains('hypno-bullet') && target.classList.contains('enemy') && !target.classList.contains('hypnotized')) {
target.classList.add('hypnotized');
target.style.backgroundColor = 'pink';
hypnotizeEnemy(target);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Fire bullet effect (fade and remove obstacle)
if (bullet.classList.contains('fire-bullet') && target.classList.contains('obstacle')) {
target.style.transition = 'opacity 0.5s';
target.style.opacity = '0';
setTimeout(() => target.remove(), 500);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Ice bullet effect (freeze, no instant death)
if (bullet.classList.contains('ice-bullet')) {
if (target.classList.contains('obstacle') || target.classList.contains('enemy')) {
target.classList.add('frozen');
target.style.backgroundColor = '#00f';
target.style.opacity = '0.5';
target.dataset.isFrozen = 'true';
setTimeout(() => {
if (target.dataset.isFrozen === 'true') {
target.classList.remove('frozen');
target.style.backgroundColor = '';
target.style.opacity = '1';
delete target.dataset.isFrozen;
}
}, 5000);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
}
// Normal enemies and obstacles: remove on hit
if (target.classList.contains('enemy') || target.classList.contains('obstacle')) {
target.remove();
enemies = enemies.filter(e => e !== target);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Apply other impact effects, if any
impactCallback(target);
bullet.remove();
clearInterval(bulletInterval);
return;
}
}
}, 1000 / 60);
}
function reduceLives() {
if (player.style.border) {
player.style.border = '';
player.style.opacity = '1';
} else {
lives--;
if (lives <= 0) {
endGame(false); // Call endGame if lives are 0
} else {
}
}
updateDisplay();
}
function resetBoss() {
if (boss) {
if (level === 3) {
bossHealth = 150; // Restore boss health to 150 for level 3
} else {
bossHealth = 100; // Restore boss health for other levels
}
updateBossHealth(); // Update the UI to reflect the new health value
}
}
function checkCollision(rect1, rect2) {
const rect1Bounds = rect1.getBoundingClientRect();
const rect2Bounds = rect2.getBoundingClientRect();
return !(rect1Bounds.top > rect2Bounds.bottom || rect1Bounds.bottom < rect2Bounds.top ||
rect1Bounds.left > rect2Bounds.right || rect1Bounds.right < rect2Bounds.left);
}
function togglePause() {
if (isPaused) {
gameInterval = setInterval(gameLoop, 1000 / 60);
isPaused = false;
} else {
clearInterval(gameInterval);
isPaused = true;
}
}
function clearAllElements() {
[obstacles, enemies, bullets.map(b => b.element), powerUps, platforms, bossDoors, coins]
.forEach(arr => {
arr.forEach(el => el.remove());
arr.length = 0;
});
}
function interact() {
if (checkCollision(player, friend)) {
activateFriendDialogue();
}
bossDoors.forEach(door => {
if (checkCollision(player, door)) {
if (score >= level * bossScoreThreshold) {
openBossHallway();
} else {
openBossHallway();
}
}
});
}
function openBossHallway() {
bossHallway.style.display = 'flex';
clearAllElements();
clearInterval(gameInterval);
isInBossDoor = true;
}
function startBossFight() {
bossHallway.style.display = 'none';
isInBossDoor = true; // enable movement restrictions again if desired
triggerBossFight();
gameInterval = setInterval(gameLoop, 1000 / 60); // Ensure the game loop is running for updates
}
function incrementLevel() {
level++; // Increment level
// Check if the level exceeds 3; if so, reset to 1
if (level > 3) {
level = 1; // Reset to Level 1
}
playerMaxSpeed = 5; // Reset speed back to normal
localStorage.setItem('playerLevel', level); // Save updated level in local storage
updateDisplay(); // Update the game display
resetGame(); // Reset game state
}
function switchLevelBackground() {
const backgrounds = ['#333', '#1E90FF', '#228B22', '#a3a3a3','#7208c1'];
gameContainer.style.backgroundColor = backgrounds[(level - 1) % backgrounds.length];
}
function continueGame() {
levelIntro.style.display = 'none';
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function spawnEndlessEntities() {
if (!isPaused && !bossFightActive) {
if (Math.random() < 0.003) spawnObstacle();
// Spawn enemies based on the current level
if (level === 1) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy();
if (Math.random() < 0.002) spawnObstacle();
} else if (level === 2) {
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnChargingEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
} else if (level === 3) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy(); // Flying enemy option for Level 3
if (Math.random() < 0.001) spawnReflectiveEnemy(); // Reflective enemy option for Level 3
} else if (level === 4) {
if (Math.random() < 0.001) spawnLaserEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.002) spawnSplitterEnemy();
} else if (level === 5) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnVirusDrone(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
}
if (Math.random() < 0.002 && !currentPowerUp) spawnPowerUp();
if (Math.random() < 0.001) spawnPlatform();
if (Math.random() < 0.003) spawnCoin();
// Manage boss door spawning only if in Story Mode
if (localStorage.getItem('gameMode') === 'endless') {
if (bossDoors.length === 0 && score >= level * bossScoreThreshold && enemies.length === 0) {
spawnBossDoor();
}
}
}
}
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
const gameMode = localStorage.getItem('gameMode'); // Retrieve the current game mode
if (gameMode === 'endless') {
spawnEndlessEntities(); // Use the endless logic for enemy spawns
switchLevelBackground(); // Set the background based on the endless level
} else {
spawnEntities(); // Handle entity spawning for story mode
}
moveElements();
// Check for collisions between the player and enemies
handlePlayerEnemyCollision();
// Update score display in the shop if applicable
updateShopScoreDisplay();
// Check if the player's score meets the criteria for level transitions
checkScoreForLevelTransition();
updateBoostDrainer();
handlePlayerEnemyCollision(); // Check for collisions
updateDisplay(); // Update the scoreboard, lives, etc.
}
}
function endGame(isWin = false) {
clearInterval(gameInterval);
if (!isWin) {
// Reset boss health if the game is lost
resetBoss();
}
// Display the game over screen with the final score
gameOverScreen.style.display = 'flex';
if (localStorage.getItem('gameMode') === 'story') {
gameOverScreen.innerHTML = `
<p>${isWin ? 'Congratulations! You won!' : 'Game Over!'}</p>
<p>Your current score: ${score}</p>
<button onclick="restartGame2()">Restart</button>
`;
} else if (localStorage.getItem('gameMode') === 'endless') {
gameOverScreen.innerHTML = `
<p>Endless Mode Over!</p>
<p>Your score: ${score}</p>
<button onclick="restartGame()">Restart</button>
`;
}
}
function restartGame() {
gameOverScreen.style.display = 'none';
resetGame();
startLevel();
}
function restartGame2() {
gameOverScreen.style.display = 'none';
resetGame();
startLevel();
}
function triggerBossFight() {
bossFightScreen.style.display = 'flex';
setTimeout(() => {
bossFightScreen.style.display = 'none';
startBossLogic();
}, 2000);
}
function startBossLogic() {
bossFightActive = true;
boss = createElement('boss', [], 600);
boss.style.bottom = '100px';
gameWorld.appendChild(boss);
displayBossHealth();
cycleBossAttacks(); // Start the attack pattern cycle
}
// Helper function for delay(ms)
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Example placeholder for displayMessage function
// You need to implement or adjust this to your UI
// New asynchronous cycleBossAttacks function
// Helper to sleep (wait) for milliseconds
// Display a message for specified seconds
async function displayMessageTimed(text, seconds) {
displayMessage(text, seconds);
await sleep(seconds * 1000);
}
// Displays all pre-attack messages in sequence, then calls and awaits attackFunc
async function preAttackWithMessage(messages, attackFunc) {
for (const msg of messages) {
await displayMessageTimed(msg.text, msg.durationSeconds);
}
await attackFunc();
}
async function cycleBossAttacks() {
if (bossHealth <= 0) {
if (!bossDefeatedMessageDisplayed) {
bossDefeatedMessageDisplayed = true;
await displayMessageTimed("Boss Defeated!", 5);
}
return;
}
if (!phaseStartTime) {
phaseStartTime = Date.now();
bossAttackPhase = 0;
}
bossAttacking = true;
bossShielded = true;
boss.classList.add('boss-shielded');
const maxBossMoves = (level === 1) ? 4 : (level === 2) ? 4 : 6;
switch (level) {
case 1:
switch (bossAttackPhase) {
case 0: await bossEnemyBarrage(); break;
case 1: await bossTargetSlam(); break;
case 2: await bossNewPower(); break;
case 3: await bossExplosionRain(); break;
}
break;
case 2:
switch (bossAttackPhase) {
case 0: await bossPhaseTwoBarrage(); break;
case 1: await bossPhaseTwoTargetSlam(); break;
case 2: await bossPhaseTwoNewPower(); break;
case 3: await bossPhaseTwoExplosionRain(); break;
}
break;
case 3:
if (bossAttackPhase === 0 && (Date.now() - phaseStartTime) < 13000) {
await startPhaseThree();
return;
}
switch (bossAttackPhase) {
case 0: await bossPhaseThree(); break;
case 1: await bossPhaseTwoExplosionRain2(); break;
case 2: await bossPhaseTwoTargetSlam3(); break;
case 3: await bossPhaseTwoNewPower2(); break;
case 4: await bossPhaseTwoTargetSlam2(); break;
case 5: await bossPhase5(); break;
}
break;
case 4:
if (bossAttackPhase === 0 && (Date.now() - phaseStartTime) < 13000) {
await startPhaseFour();
return;
}
switch (bossAttackPhase) {
case 0:
await preAttackWithMessage(
[{ text: "Lets start with something simple!", durationSeconds: 4 }],
EnemyBarrage
);
break;
case 1:
await preAttackWithMessage(
[{ text: "Incredible, a nice worthy foe.", durationSeconds: 3 }],
bossPhaseTwoTargetSlam3
);
break;
case 2:
await preAttackWithMessage(
[{ text: "Well nobody has got this far before, your doing somehow great...", durationSeconds: 4 }],
bossPhase5
);
break;
case 3:
await preAttackWithMessage(
[{ text: "How are you NOT DEAD YET?", durationSeconds: 3 }],
bossPhaseTwoExplosionRain2
);
break;
case 4:
await preAttackWithMessage(
[{ text: "Your actully getting annoying now...", durationSeconds: 3 }],
bossPhaseThree
);
break;
case 5:
await preAttackWithMessage(
[{ text: "Alright let me stop talking now its game on.", durationSeconds: 4 }],
slidingLaserPhase
);
break;
}
break;
case 5:
switch (bossAttackPhase) {
case 0: await bossPhaseFiveAttackA(); break;
case 1: await bossPhaseFiveAttackB(); break;
case 2: await bossPhaseFiveAttackC(); break;
case 3: await bossPhaseFiveAttackE(); break;
case 4: await bossPhaseFiveAttackF(); break;
case 5: await bossPhaseFiveAttackF(); break;
}
break;
default:
// You might want to handle unexpected level values here as well
break;
}
// Increment phase for next attack
bossAttackPhase = (bossAttackPhase + 1) % maxBossMoves;
// Remove shield after 10s and notify player (fire-and-forget, no await needed)
setTimeout(() => {
bossShielded = false;
boss.classList.remove('boss-shielded');
displayMessage("Boss shield is OFF! Attack now!");
}, 10000);
}
async function slidingLaserPhase() {
displayMessage("Sliding laser phase! Jump over the lasers!", 3);
await sleep(3000);
const gameWorld = document.getElementById('game-world');
const gameBounds = gameWorld.getBoundingClientRect();
// Laser parameters
const laserWidth = 150;
const laserHeight = 20;
const initialSpeed = 3;
const maxBounces = 4;
// Create laser elements
function createLaserElement(y, direction) {
const el = document.createElement('div');
el.style.position = 'absolute';
el.style.width = laserWidth + 'px';
el.style.height = laserHeight + 'px';
el.style.backgroundColor = 'red';
el.style.bottom = y + 'px';
el.style.left = (gameBounds.width / 2 - laserWidth / 2) + 'px'; // center horizontally
el.style.borderRadius = '5px';
el.style.zIndex = '100';
gameWorld.appendChild(el);
return {
element: el,
y: y,
speed: initialSpeed,
direction: direction,
bounces: 0,
destroyed: false,
get left() {
return this.element.offsetLeft;
},
set left(val) {
this.element.style.left = val + 'px';
},
get right() {
return this.left + laserWidth;
},
remove() {
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
this.destroyed = true;
}
};
}
// Instantiate two lasers
const lasers = [
createLaserElement(gameBounds.height * 0.3, 1),
createLaserElement(gameBounds.height * 0.5, -1)
];
// Check collision between laser and player
function laserCollidesWithPlayer(laser) {
const laserRect = laser.element.getBoundingClientRect();
const playerRect = player.getBoundingClientRect();
return !(
playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom
);
}
return new Promise(resolve => {
function animate() {
// If both destroyed, end phase
if (lasers.every(l => l.destroyed)) {
displayMessage("Sliding laser phase complete!", 3);
setTimeout(() => {
resolve();
cycleBossAttacks();
}, 3000);
return;
}
lasers.forEach(laser => {
if (laser.destroyed) return;
let newLeft = laser.left + laser.speed * laser.direction;
// Bounce left wall
if (newLeft <= 0) {
newLeft = 0;
laser.direction *= -1;
laser.bounces++;
laser.speed *= 1.3; // speed up
if (laser.bounces >= maxBounces) {
laser.remove();
return;
}
}
// Bounce right wall
if (newLeft + laserWidth >= gameBounds.width) {
newLeft = gameBounds.width - laserWidth;
laser.direction *= -1;
laser.bounces++;
laser.speed *= 1.3;
if (laser.bounces >= maxBounces) {
laser.remove();
return;
}
}
laser.left = newLeft;
// Damage player if colliding
if (laserCollidesWithPlayer(laser)) {
lives = Math.max(0, lives - 1);
displayMessage(`Ouch! Lives left: ${lives}`, 1);
// Optional knockback or player jump boost can be added here
}
});
requestAnimationFrame(animate);
}
animate();
});
}
function bossPhase5() {
const blockWidth = 80;
const blockHeight = 20;
const lavaRiseSpeed = 4; // pixels per tick
const lavaRiseInterval = 100; // ms
const warningDuration = 1000; // 1 second warning before lava rises
const totalPhases = 3;
const obstacleWidth = 30; // Add this line
const obstacleHeight = 30; // Add this line
const platformHeights = [100, 200, 300];
const laserWarningDuration = 1000; // 1 second yellow warning before laser fires
const laserPhaseDuration = 6000; // total laser active time per phase (6 seconds)
const laserBaseInterval = 1000; // starting laser shoot interval
const obstacles = [];
const warningLines = [];
const lasers = [];
const gameWorld = document.getElementById('game-world');
let currentPhase = 0;
let lavaHeight = 0;
let lavaInterval;
let lastDamageTime = 0;
// Save original jump settings
let originalMaxJumps = maxJumps;
let originalDoubleJumpEnabled = isDoubleJumpEnabled;
let laserIntervalId = null;
let laserTimeoutId = null;
// Create or get lava element
let lavaElement = gameWorld.querySelector('.lava');
if (!lavaElement) {
lavaElement = document.createElement('div');
lavaElement.className = 'lava';
Object.assign(lavaElement.style, {
position: 'absolute',
width: '100%',
bottom: '0',
backgroundColor: 'red',
height: '0px',
zIndex: '5',
transition: 'height 0.1s linear',
});
gameWorld.appendChild(lavaElement);
}
// Clear existing platforms and obstacles
function clearPlatformsAndObstacles() {
obstacles.forEach(o => o.remove());
obstacles.length = 0;
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === blockWidth &&
el.offsetHeight === blockHeight
);
blocks.forEach(b => b.remove());
}
// Create a block platform
function spawnBlock(x, y) {
const block = document.createElement('div');
Object.assign(block.style, {
position: 'absolute',
width: blockWidth + 'px',
height: blockHeight + 'px',
backgroundColor: 'blue',
left: x + 'px',
bottom: y + 'px',
zIndex: '10',
});
gameWorld.appendChild(block);
return block;
}
// Create obstacle near block to jump on
function spawnObstacle(x, y) {
const obstacle = document.createElement('div');
obstacle.className = 'obstacle';
Object.assign(obstacle.style, {
position: 'absolute',
width: obstacleWidth + 'px',
height: obstacleHeight + 'px',
backgroundColor: 'orange',
left: x + 'px',
bottom: y + 'px',
zIndex: '10',
});
gameWorld.appendChild(obstacle);
obstacles.push(obstacle);
return obstacle;
}
function spawnPhasePlatforms(phase) {
clearPlatformsAndObstacles();
const yPos = platformHeights[phase];
const xs = [100, 300, 500];
xs.forEach(x => spawnBlock(x, yPos));
xs.forEach(x => spawnObstacle(x + 70, yPos));
}
// Get player’s distance from bottom of gameWorld
function getPlayerBottomOffset() {
const playerRect = player.getBoundingClientRect();
const worldRect = gameWorld.getBoundingClientRect();
return worldRect.bottom - playerRect.bottom;
}
function setTripleJump() {
maxJumps = 3;
isDoubleJumpEnabled = true;
jumpCount = 0;
}
function restoreJumpSettings() {
maxJumps = originalMaxJumps;
isDoubleJumpEnabled = originalDoubleJumpEnabled;
jumpCount = 0;
}
function startLavaPhase() {
displayMessage("Lava is rising soon! Prepare yourself!");
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
setTripleJump();
lavaInterval = setInterval(riseLava, lavaRiseInterval);
}, warningDuration);
}
// Lava rising step
function riseLava() {
const maxLavaHeight = platformHeights[currentPhase];
if (lavaHeight < maxLavaHeight) {
lavaHeight += lavaRiseSpeed;
if (lavaHeight > maxLavaHeight) lavaHeight = maxLavaHeight;
lavaElement.style.height = lavaHeight + 'px';
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
// Remove or comment out this block inside riseLava:
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
const now = Date.now();
if (checkCollision(player, lavaElement)) {
const playerBottom = getPlayerBottomOffset();
if (playerBottom < lavaHeight && (now - lastDamageTime) > 1000) {
reduceLives();
lastDamageTime = now;
}
}
} else {
clearInterval(lavaInterval);
startLaserPhase();
}
}
// Create a warning line (yellow) above a block
function createWarningLine(block) {
const warning = document.createElement('div');
warning.style.position = 'absolute';
warning.style.width = blockWidth + 'px';
warning.style.height = '5px';
warning.style.backgroundColor = 'yellow';
warning.style.left = block.style.left;
warning.style.bottom = (parseInt(block.style.bottom) + blockHeight) + 'px';
warning.style.zIndex = '20';
gameWorld.appendChild(warning);
warningLines.push(warning);
return warning;
}
// Create a laser beam (red) that shoots upwards from a block
function createLaser(block) {
const laser = document.createElement('div');
laser.style.position = 'absolute';
laser.style.width = '50px';
laser.style.height = '200px'; // laser beam height
laser.style.backgroundColor = 'red';
laser.style.left = (parseInt(block.style.left) + blockWidth / 2 - 3) + 'px'; // centered on block
laser.style.bottom = (parseInt(block.style.bottom) + blockHeight) + 'px';
laser.style.zIndex = '15';
gameWorld.appendChild(laser);
lasers.push(laser);
// Remove laser after 0.5s
setTimeout(() => {
laser.remove();
lasers.splice(lasers.indexOf(laser), 1);
}, 500);
return laser;
}
// Remove warning lines
function clearWarningLines() {
warningLines.forEach(w => w.remove());
warningLines.length = 0;
}
// Remove all active lasers
function clearLasers() {
lasers.forEach(l => l.remove());
lasers.length = 0;
}
// Laser phase variables
let laserPhaseTimer = 0;
let currentLaserInterval = laserBaseInterval;
// Laser damage cooldown for player (similar to lava damage)
let laserLastDamageTime = 0;
// Laser Phase logic:
// Every currentLaserInterval ms: choose a random block platform and:
// - Draw a yellow warning line 1 second before laser fires
// - After 1s, fire laser & damage player if in laser zone (once per laser)
function laserTick() {
if (laserPhaseTimer >= laserPhaseDuration) {
// Laser phase ended
clearInterval(laserIntervalId);
clearWarningLines();
clearLasers();
lavaInterval = setInterval(lowerLava, lavaRiseInterval);
return;
}
laserPhaseTimer += currentLaserInterval;
// Get all current blocks (platforms player can jump on)
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === blockWidth &&
el.offsetHeight === blockHeight
);
if (blocks.length === 0) return;
// Pick a random block different from last one if possible
let chosenBlock = blocks[Math.floor(Math.random() * blocks.length)];
// WARNING LINE
const warning = createWarningLine(chosenBlock);
// After 1s fire laser & damage
setTimeout(() => {
warning.remove();
warningLines.splice(warningLines.indexOf(warning), 1);
const laser = createLaser(chosenBlock);
// Check if player is in laser zone vertically and horizontally approx.
const now = Date.now();
const playerLeftPx = parseFloat(player.style.left) || 0;
const playerRightPx = playerLeftPx + player.offsetWidth;
const blockLeft = parseInt(chosenBlock.style.left);
const blockRight = blockLeft + blockWidth;
const laserTop = parseInt(laser.style.bottom) + parseInt(laser.style.height);
const playerBottom = parseFloat(player.style.bottom) || 0;
const playerTop = playerBottom + player.offsetHeight;
// Check horizontal overlap
const horizontalOverlap = playerRightPx > blockLeft && playerLeftPx < blockRight;
// Check vertical overlap with laser beam
const verticalOverlap = playerBottom < laserTop && playerTop > parseInt(laser.style.bottom);
if (horizontalOverlap && verticalOverlap && (now - laserLastDamageTime) > 1000) {
reduceLives();
laserLastDamageTime = now;
}
}, laserWarningDuration);
}
// Lower lava smoothly after laser phase ends
function lowerLava() {
if (lavaHeight > 0) {
lavaHeight -= lavaRiseSpeed;
if (lavaHeight < 0) lavaHeight = 0;
lavaElement.style.height = lavaHeight + 'px';
// Adjust player standing on lava during lowering
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
} else {
clearInterval(lavaInterval);
// Next phase
currentPhase++;
if (currentPhase < totalPhases) {
currentLaserInterval = Math.max(laserBaseInterval - currentPhase * 150, 400); // speeds up laser shooting each phase
laserPhaseTimer = 0;
spawnPhasePlatforms(currentPhase);
startLavaPhase();
} else {
displayMessage("Lava and laser phases complete! Prepare for next boss attack.");
setTimeout(() => {
clearPlatformsAndObstacles();
lavaElement.style.height = '0px';
restoreJumpSettings();
cycleBossAttacks();
}, 2000);
}
}
}
// Start lava rising phases as before
currentPhase = 0;
spawnPhasePlatforms(currentPhase);
displayMessage("Prepare! Lava will start rising soon!");
setTimeout(() => {
startLavaPhase();
}, 1500);
// Overwrite startLavaPhase so that after lava rises fully, laser phase starts
function startLavaPhase() {
displayMessage("Lava is rising soon! Prepare yourself!");
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
setTripleJump();
lavaInterval = setInterval(riseLava, lavaRiseInterval);
}, warningDuration);
}
// Modify riseLava to start laser phase after rising fully
function riseLava() {
const maxLavaHeight = platformHeights[currentPhase];
if (lavaHeight < maxLavaHeight) {
lavaHeight += lavaRiseSpeed;
if (lavaHeight > maxLavaHeight) lavaHeight = maxLavaHeight;
lavaElement.style.height = lavaHeight + 'px';
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
const now = Date.now();
if (checkCollision(player, lavaElement)) {
const playerBottom = getPlayerBottomOffset();
console.log(`Collision! playerBottom=${playerBottom}, lavaHeight=${lavaHeight}`);
if (playerBottom < lavaHeight && (now - lastDamageTime) > 1000) {
console.log("Reducing lives from lava collision");
reduceLives();
lastDamageTime = now;
}
}
} else {
clearInterval(lavaInterval);
displayMessage("Warning! Lasers incoming!");
startLaserPhase();
}
}
// Laser phase start
function startLaserPhase() {
laserPhaseTimer = 0;
laserLastDamageTime = 0;
laserIntervalId = setInterval(laserTick, currentLaserInterval);
// Laser phase ends after fixed duration
laserTimeoutId = setTimeout(() => {
clearInterval(laserIntervalId);
clearWarningLines();
clearLasers();
displayMessage("Lava retreating... Prepare for next rise!");
lavaInterval = setInterval(lowerLava, lavaRiseInterval);
}, laserPhaseDuration);
}
}
function checkCollisionWithPlatformsAndObstacles() {
const playerLeft = playerPositionX;
let playerBottom = playerPositionY;
const playerWidth = player.offsetWidth;
const playerHeight = player.offsetHeight;
let onPlatform = false;
// Check platforms
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === 80 &&
el.offsetHeight === 20
);
for (const block of blocks) {
const blockLeft = parseInt(block.style.left);
const blockBottom = parseInt(block.style.bottom);
const blockRight = blockLeft + block.offsetWidth;
const blockTop = blockBottom + block.offsetHeight;
const horizontalOverlap = playerLeft + playerWidth > blockLeft && playerLeft < blockRight;
const nextPlayerBottom = playerBottom + playerVelocityY;
if (
horizontalOverlap &&
playerBottom >= blockTop &&
nextPlayerBottom <= blockTop &&
playerVelocityY <= 0
) {
playerPositionY = blockTop;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
} else if (
horizontalOverlap &&
playerBottom >= blockTop - 1 &&
playerBottom <= blockTop + 1 &&
playerVelocityY === 0
) {
playerPositionY = blockTop;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
}
}
// Check obstacles with same logic
for (const obstacle of obstacles) {
const obLeft = parseInt(obstacle.style.left);
const obBottom = parseInt(obstacle.style.bottom);
const obRight = obLeft + obstacle.offsetWidth;
const obTop = obBottom + obstacle.offsetHeight;
const horizontalOverlap = playerLeft + playerWidth > obLeft && playerLeft < obRight;
const nextPlayerBottom = playerBottom + playerVelocityY;
if (
horizontalOverlap &&
playerBottom >= obTop &&
nextPlayerBottom <= obTop &&
playerVelocityY <= 0
) {
playerPositionY = obTop;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
} else if (
horizontalOverlap &&
playerBottom >= obTop - 1 &&
playerBottom <= obTop + 1 &&
playerVelocityY === 0
) {
playerPositionY = obTop;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
}
}
return onPlatform;
}
function bossPhaseTwoTargetSlam2() {
const warningLineHeight = 5; // Height of the warning line
const rodWidth = 80; // Width of the laser rod
const rodHeight = 300; // Height of the rod
const rodColor = 'red'; // Color of the rod
const laserSpawnTime = 500; // Time between spawn of lasers
const totalWarnings = 12; // Total number of laser warnings
let warningCount = 0; // Counter for warnings
const warningPositions = []; // Store positions for warnings
function spawnWarningLine() {
// Create a warning line
const warningLine = document.createElement('div');
warningLine.classList.add('warning-line');
warningLine.style.width = '80px'; // Width of the warning line
warningLine.style.height = `${warningLineHeight}px`;
warningLine.style.backgroundColor = 'yellow'; // Set color
warningLine.style.position = 'absolute';
const warningLinePosition = Math.random() * (gameContainer.offsetWidth - 80); // Random horizontal position
warningLine.style.left = `${warningLinePosition}px`; // Set position
warningLine.style.bottom = '50px'; // Slightly above player position
gameWorld.appendChild(warningLine);
// Wait for 0.5 seconds, then spawn the laser rod above
setTimeout(() => {
// Create a rod above the warning line
const rod = document.createElement('div');
rod.classList.add('rod'); // Add a class for styling
rod.style.width = `${rodWidth}px`;
rod.style.height = `${rodHeight}px`;
rod.style.backgroundColor = rodColor;
rod.style.position = 'absolute';
rod.style.left = `${warningLinePosition + 25}px`; // Center the rod above the warning line
rod.style.bottom = '0px'; // Start at ground level
gameWorld.appendChild(rod); // Add rod to the game world
// Damage player if they are still there
if (checkCollision(player, rod)) {
reduceLives(); // Damage the player
}
// Remove the rod after a brief duration
setTimeout(() => {
rod.remove(); // Clean up after the rod
}, 200); // Keep the rod visible for 200 ms
// Remove the warning line
warningLine.remove();
}, 500); // Spawn the rod after 500 milliseconds
}
// Spawn warning lines and lasers at intervals
const spawnWarningsInterval = setInterval(() => {
if (warningCount >= totalWarnings) {
clearInterval(spawnWarningsInterval); // Stop after total warnings
cycleBossAttacks(); // Proceed to the next boss cycle
return; // Exit the function
}
spawnWarningLine(); // Spawn a warning line
warningCount++; // Increase warning count
}, laserSpawnTime); // Spawn a warning every 500 ms
}
function bossPhaseTwoNewPower2() {
const laserCount = 3; // Number of lasers per burst
const laserRadius = 80; // Distance lasers start from boss center (for initial pos)
const laserWidth = 20;
const laserHeight = 20;
const laserColor = 'red';
const healColor = 'green';
const healChance = 0.2;
const maxBursts = 8;
const laserInterval = 2000; // Time between bursts
let burstCount = 0;
const bounceLimit = 1; // Number of bounces before laser is destroyed
// Assume these globals (define globally)
// const boss = document.getElementById('boss');
// const player = document.getElementById('player');
// const gameWorld = document.getElementById('gameWorld');
// Get game world boundaries once
const worldRect = gameWorld.getBoundingClientRect();
function createLasers() {
const bossRect = boss.getBoundingClientRect();
for (let i = 0; i < laserCount; i++) {
const angle = (i * 2 * Math.PI) / laserCount; // spread lasers evenly around circle
// Create laser element
const laser = document.createElement('div');
laser.classList.add('laser');
laser.style.position = 'absolute';
laser.style.width = `${laserWidth}px`;
laser.style.height = `${laserHeight}px`;
laser.style.backgroundColor = laserColor;
laser.style.borderRadius = '2px';
// Initial position roughly at boss position + offset by radius along angle
// Calculate relative to gameWorld top-left (absolute positioning)
const startX = bossRect.left + bossRect.width/2 + Math.cos(angle)*laserRadius - laserWidth/2;
const startY = bossRect.top + bossRect.height/2 + Math.sin(angle)*laserRadius - laserHeight/2;
laser.style.left = `${startX}px`;
laser.style.top = `${startY}px`;
gameWorld.appendChild(laser);
// Velocity vector (pixels per frame)
// Let's say speed = 4px per frame (~60fps, adjust as needed)
const speed = 4;
let vx = Math.cos(angle)*speed;
let vy = Math.sin(angle)*speed;
let bounces = 0;
// Use requestAnimationFrame for smooth animation
function animate() {
if (!laser.parentElement) return; // Laser removed/destroyed
// Current position
let x = parseFloat(laser.style.left);
let y = parseFloat(laser.style.top);
// Move laser
x += vx;
y += vy;
// Check boundaries (bounce off gameWorld edges)
if (x <= 0) {
x = 0;
vx = -vx;
bounces++;
}
if (y <= 0) {
y = 0;
vy = -vy;
bounces++;
}
if (x + laserWidth >= worldRect.width) {
x = worldRect.width - laserWidth;
vx = -vx;
bounces++;
}
if (y + laserHeight >= worldRect.height) {
y = worldRect.height - laserHeight;
vy = -vy;
bounces++;
}
laser.style.left = `${x}px`;
laser.style.top = `${y}px`;
// Collision with player
// Here you can improve detection, currently simple AABB
const playerRect = player.getBoundingClientRect();
const laserRect = laser.getBoundingClientRect();
if (!(playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom)) {
// On collision, heal or damage once:
if (Math.random() < healChance) {
gainExtraLife && gainExtraLife();
} else {
reduceLives();
}
// Remove laser immediately on hit
laser.remove();
return;
}
// Remove laser after 2 bounces
if (bounces > bounceLimit) {
laser.remove();
return;
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}
}
// Interval that runs burstCount times, then stops
const laserBurstInterval = setInterval(() => {
if (burstCount >= maxBursts) {
clearInterval(laserBurstInterval);
cycleBossAttacks && cycleBossAttacks();
return;
}
createLasers();
burstCount++;
}, laserInterval);
}
// Call function to test
// bossPhaseTwoNewPower2();
function startPhaseThree() {
if (phaseThreeStarted) return; // Prevent running it multiple times
phaseThreeStarted = true; // Set flag to true once started
initializeBossHealth(); // Sets the boss health based on the current level
const messages = [
"Player: Oh god what is this",
"Boss: All the moves and powers i had...",
"Boss: But this time your not going to get pass this.",
"Boss: This is your final game",
"Player: alright lets take a bet on that!",
];
let phase3MessageIndex = 0; // Track which message is currently displayed
function displayMessages() {
if (phase3MessageIndex < messages.length) {
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000); // Display the next message after 3 seconds
} else {
// Once messages are done, start the boss attack cycle
cycleBossAttacks(); // Start boss attacks
}
}
displayMessages(); // Start displaying messages
}
// Wait until boss exists, then change color
function changeBossColorBlue() {
if (!boss) {
console.warn("Boss element not ready!");
return;
}
boss.style.backgroundColor = 'blue';
}
function changeBossColorBlue2() {
if (!boss) {
console.warn("Boss element not ready!");
return;
}
boss.style.backgroundColor = 'yellow';
}
// Call it when appropriate, no await needed
function startPhaseFour() {
console.log("startPhaseFour called");
if (typeof phaseThreeStarted === "undefined") {
console.error("phaseThreeStarted is not defined!");
}
if (phaseThreeStarted) {
console.log("Phase 3 already started.");
return;
}
phaseThreeStarted = true;
console.log("Starting Phase Four logic.");
if (typeof initializeBossHealth !== "function") {
console.error("initializeBossHealth is not defined!");
}
initializeBossHealth();
// Set up level-specific behaviors
let messages;
let preMessageFunction = function() {};
let endMessageFunction = function() {};
console.log("Level:", level);
if (level === 4) {
messages = [
"Boss: Well well well...",
"Boss: THIS? is what my brother was talking about!",
"Boss: You look so weak haha..",
"Boss: Well i should introduce myself",
"Boss: Im the strongest cube ever!",
"Player: Well he just told me that already so a big flex ig...",
"Boss: Intersting... you seem somewhat smart.",
"Player: Well knowing this is the last boss fight i will end you!...",
"Boss: Alright lets get started then!",
];
preMessageFunction = changeBossColorBlue;
endMessageFunction = cycleBossAttacks;
} else if (level === 5) {
messages = [
"System: Welcome to your final test.",
"Player: What do you mean?",
"System: To unlock life you must win..",
"Player: Alright lets do it!",
];
preMessageFunction = changeBossColorBlue2;
endMessageFunction = cycleBossAttacks;
} else {
messages = [
"Boss: Well well well...",
"Boss: THIS? is what my brother was talking about!",
"Boss: You look so weak haha..",
"Boss: Well i should introduce myself",
"Boss: Im the strongest cube ever!",
"Player: Well he just told me that already so a big flex ig...",
"Boss: Intersting... you seem somewhat smart.",
"Player: Well knowing this is the last boss fight i will end you!...",
"Boss: Alright lets get started then!",
];
preMessageFunction = changeBossColorBlue;
endMessageFunction = cycleBossAttacks;
}
if (typeof preMessageFunction !== "function") {
console.error("preMessageFunction is not a function!");
}
preMessageFunction();
let phase3MessageIndex = 0;
function displayMessages() {
if (phase3MessageIndex < messages.length) {
if (typeof displayMessage !== "function") {
console.error("displayMessage is not defined!");
}
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000);
} else {
if (typeof endMessageFunction !== "function") {
console.error("endMessageFunction is not a function!");
}
cycleBossAttacks();
}
}
displayMessages();
}
function startPhaseFive() {
if (phaseThreeStarted) return;
phaseThreeStarted = true;
initializeBossHealth();
changeBossColorBlue2();
const messages = [
"System: Welcome to your final test.",
"Player: What do you mean?",
"System: To unlock life you must win..",
"Player: Alright lets do it!",
];
let phase3MessageIndex = 0;
function displayMessages() {
if (phase3MessageIndex < messages.length) {
console.log("Displaying message:", messages[phase3MessageIndex]);
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000);
} else {
console.log("Messages done. Starting boss attacks...");
cycleBossAttacks();
}
}
displayMessages();
}
function bossPhaseTwoTargetSlam() {
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 400; // Faster slam down
const slamHoldDuration = 1200; // Hold slam a bit shorter
const slamDurationUp = 600; // Faster slam up
const slamCooldown = 700; // Faster cooldown between slams
// Reduce lives should be defined globally / externally
// This function just calls it
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
// Trigger additional feedback if needed
}
}
function createBurst(x, y) {
// Your burst effect implementation here
console.log(`Burst at (${x}, ${y})`);
}
// Animate CSS bottom property with transition and Promise-based resolution
function animateBottomPosition(targetBottom, duration) {
return new Promise((resolve) => {
let resolved = false;
function onTransitionEnd(e) {
if (e.propertyName === 'bottom' && !resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
// Safety fallback if transitionend does not fire
setTimeout(() => {
if (!resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}, duration + 50);
});
}
// Get bounding rects for player, boss, and boss parent
function getRects() {
return {
playerRect: player.getBoundingClientRect(),
bossRect: boss.getBoundingClientRect(),
parentRect: boss.parentElement.getBoundingClientRect()
};
}
// Axis-aligned bounding box collision detection
function isCollision(rect1, rect2) {
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
}
// Monitor collision during slam hold, damage max once per second
function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
const hitCooldown = 1000; // ms between allowed hits
let lastHitTimestamp = 0;
const startTime = performance.now();
function check(time) {
if (time - startTime >= duration) {
resolve();
return;
}
const { playerRect, bossRect } = getRects();
if (isCollision(playerRect, bossRect)) {
if (time - lastHitTimestamp > hitCooldown && lives > 0) {
reduceLives();
lastHitTimestamp = time;
shakeScreen(); // Assuming you defined shakeScreen globally
}
}
if (lives <= 0) {
resolve(); // Stop if no lives left
return;
}
requestAnimationFrame(check);
}
requestAnimationFrame(check);
});
}
async function performSlam() {
let slamCount = 0;
while (slamCount < maxSlams) {
// Position boss instantly above player horizontally
const { playerRect, parentRect } = getRects();
const playerCenterX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none';
boss.style.left = `${playerCenterX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
// Wait for next frame so changes apply
await new Promise(resolve => requestAnimationFrame(resolve));
// Shorter hover pause for snappier feel
await new Promise(resolve => setTimeout(resolve, 300));
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerCenterX, groundLevel);
// Hold slam while monitoring collision damage
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
// Cooldown before next slam
await new Promise(resolve => setTimeout(resolve, slamCooldown));
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 5000); // Faster cycle after slam attack
}
}
performSlam();
}
function bossPhaseTwoTargetSlam3() {
let slamCount = 0;
const maxSlams = 6;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 200;
const slamHoldDuration = 1000;
const slamDurationUp = 700;
const slamCooldown = 450;
let slamActive = false;
// assuming boss and player are defined globally or in the outer scope
// e.g. const boss = document.getElementById('boss');
// const player = document.getElementById('player');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function monitorCollisionDuringSlam(duration) {
const startTime = Date.now();
let lastHitTime = 0; // added to throttle reduceLives
return new Promise((resolve) => {
const interval = setInterval(() => {
const now = Date.now();
if (now - startTime >= duration) {
clearInterval(interval);
resolve();
return;
}
if (checkCollision(player, boss)) {
// Only reduce lives if at least 1000ms (1 second) passed since last hit
if (now - lastHitTime >= 1000) {
reduceLives();
console.log("Player hit by the boss slam! Lives Remaining: " + lives);
lastHitTime = now;
}
}
}, 50);
});
}
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(250);
shakeScreen();
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
await monitorCollisionDuringSlam(slamHoldDuration);
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log('Slam attack finished');
resetBossPosition();
setTimeout(cycleBossAttacks, 10000);
}
performSlam();
}
function bossTargetSlam() {
let slamCount = 0;
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 500;
const slamHoldDuration = 1500;
const slamDurationUp = 700;
const slamCooldown = 1000;
// Track if a slam is currently active
let slamActive = false;
// Promise-based sleep
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Collision check: AABB rectangle intersection
function checkCollision(elem1, elem2) {
const r1 = elem1.getBoundingClientRect();
const r2 = elem2.getBoundingClientRect();
return !(
r1.right < r2.left ||
r1.left > r2.right ||
r1.bottom < r2.top ||
r1.top > r2.bottom
);
}
// Placeholder for player's lives remaining, define accordingly
let lives = 3;
// Reduces player's lives safely
reduceLives();
// Shake screen effect placeholder — implement your own or remove
shakeScreen();
// Create burst effect placeholder — implement as needed
function createBurst(x, y) {
console.log(`Burst at (${x}, ${y})`);
}
// Animate bottom position with transition and Promise
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Force reflow to apply transition properly
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
// Monitor collision during slamHoldDuration, but only reduce lives once per second max
async function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
let lastHitTime = 0;
const interval = setInterval(() => {
const now = Date.now();
if (now - lastHitTime >= 1000 && checkCollision(player, boss)) {
reduceLives();
lastHitTime = now;
}
if (now >= startTime + duration) {
clearInterval(interval);
resolve();
}
}, 50);
const startTime = Date.now();
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
// Calculate player and boss parent positions to align boss over player horizontally
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
// Player center X inside boss parent container
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
// Position boss instantly above player at hover height
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(500); // hover delay
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
// Check collision during slam hold, max once per 1000ms
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 10000);
}
}
performSlam();
}
function bossPhaseTwoNewPower() {
let bossSlideCount = 0; // Count how many times the boss has slid side to side
const bossMaxSlides = 5; // Maximum number of slides
let direction = 1; // 1 for right, -1 for left
let canDamagePlayer = true; // Flag to control damage timing
// Set the boss to ground level and an initial position
boss.style.bottom = '0px'; // Set boss to ground level
boss.style.left = '50px'; // Starting position; adjust as needed
// Get the width of the game world for boundary checks
const gameWorldWidth = gameWorld.offsetWidth;
// Interval to move the boss
const slideInterval = setInterval(() => {
// Move the boss
const currentLeft = parseInt(boss.style.left);
const nextLeft = currentLeft + (4 * direction); // Move by 4 pixels
// Check for wall collision
if (nextLeft < 0 || nextLeft + boss.offsetWidth > gameWorldWidth) {
direction *= -1; // Reverse direction upon hitting the wall
bossSlideCount++; // Increment the slide count
// Bounce back the boss a little to ensure it doesn't stay stuck
boss.style.left = (currentLeft + (4 * direction)) + 'px'; // Adjust position slightly after collision
} else {
boss.style.left = nextLeft + 'px'; // Move the boss
}
// Check collision with the player
if (checkCollision(boss, player)) {
// Reduce lives every second when colliding
if (canDamagePlayer) {
reduceLives(); // Damage the player if they collide with the boss
canDamagePlayer = false; // Prevent further damage until the cooldown
setTimeout(() => {
canDamagePlayer = true; // Reset after 1 second
}, 1000);
}
}
// Stop sliding after the specified number of slides and trigger the next boss cycle
if (bossSlideCount >= bossMaxSlides) {
clearInterval(slideInterval); // Stop the sliding movement
resetBossPosition();
cycleBossAttacks(); // Call this function to start the next boss cycle
}
}, 3); // Faster interval for rapid movement
}
function bossPhaseTwoExplosionRain2() {
const blockWidth = 30; // Width of the falling blocks
const blockHeight = 10; // Height of the falling blocks
const blockSpeed = 5; // Speed of block movement (pixels per frame)
const blocks = []; // Array to hold active blocks
const spawnInterval = 500; // Time between block spawns (in milliseconds)
const duration = 15000; // Total duration of the attack (in milliseconds)
const leftRightDuration = 5500; // Move blocks left/right for 3 seconds
const upDownDuration = duration - leftRightDuration; // Then move blocks up/down for 7 seconds
// Keep track of current phase
let phase = 'leftRight'; // 'leftRight' or 'upDown'
let phaseStartTime = performance.now();
// Function to spawn a block for the left/right phase
function spawnBlockLeftRight() {
const block = document.createElement('div');
block.classList.add('explosive-block');
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Spawn from left or right side randomly
const fromLeft = Math.random() < 0.5;
block.dataset.fromLeft = fromLeft; // Store direction in dataset
// Set initial horizontal position (off screen)
block.style.left = fromLeft ? `${-blockWidth}px` : `${window.innerWidth}px`;
// Random vertical position on screen
block.style.bottom = `${Math.random() * (window.innerHeight - blockHeight)}px`;
document.body.appendChild(block);
blocks.push(block);
}
// Function to spawn a block for the up/down phase
function spawnBlockUpDown() {
const block = document.createElement('div');
block.classList.add('explosive-block');
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Spawn from bottom or top randomly
const fromBottom = Math.random() < 0.5;
block.dataset.fromBottom = fromBottom;
// Random horizontal position on the screen
block.style.left = `${Math.random() * (window.innerWidth - blockWidth)}px`;
// Initial vertical position (off screen)
block.style.bottom = fromBottom ? `${-blockHeight}px` : `${window.innerHeight}px`;
document.body.appendChild(block);
blocks.push(block);
}
// Function to update/move the blocks based on current phase
function updateBlocks() {
for (let i = blocks.length - 1; i >= 0; i--) {
const block = blocks[i];
if (!document.body.contains(block)) {
// Remove any blocks that were removed from DOM
blocks.splice(i, 1);
continue;
}
if (phase === 'leftRight') {
// Move horizontally toward center of screen
let currentLeft = parseFloat(block.style.left);
const moveDir = block.dataset.fromLeft === 'true' ? 1 : -1; // left->right is +1, right->left is -1
const newLeft = currentLeft + (blockSpeed * moveDir);
block.style.left = `${newLeft}px`;
// If block moved completely off screen past opposite side, remove it
if (moveDir === 1 && newLeft > window.innerWidth) {
block.remove();
blocks.splice(i, 1);
continue;
}
if (moveDir === -1 && newLeft < -blockWidth) {
block.remove();
blocks.splice(i, 1);
continue;
}
} else if (phase === 'upDown') {
// Move blocks vertically up or down
let currentBottom = parseFloat(block.style.bottom);
const moveDir = block.dataset.fromBottom === 'true' ? 1 : -1; // bottom->top = +1, top->bottom = -1
const newBottom = currentBottom + (blockSpeed * moveDir);
block.style.bottom = `${newBottom}px`;
// Remove if off screen vertically
if (moveDir === 1 && newBottom > window.innerHeight) {
block.remove();
blocks.splice(i, 1);
continue;
}
if (moveDir === -1 && newBottom < -blockHeight) {
block.remove();
blocks.splice(i, 1);
continue;
}
}
// Check collision with player (assuming player is accessible globally)
if (checkCollision(player, block)) {
reduceLives();
block.remove();
blocks.splice(i, 1);
}
}
}
// Interval to spawn blocks depending on the phase
let spawnBlocksInterval = setInterval(() => {
if (phase === 'leftRight') {
spawnBlockLeftRight();
} else if (phase === 'upDown') {
spawnBlockUpDown();
}
}, spawnInterval);
// Animation frame loop to move blocks
function animationLoop() {
const now = performance.now();
// Switch phase after respective time
if (phase === 'leftRight' && now - phaseStartTime >= leftRightDuration) {
phase = 'upDown';
phaseStartTime = now;
// Remove all existing blocks from leftRight phase immediately
blocks.forEach(block => block.remove());
blocks.length = 0;
} else if (phase === 'upDown' && now - phaseStartTime >= upDownDuration) {
// End attack and clean up
clearInterval(spawnBlocksInterval);
blocks.forEach(block => block.remove());
blocks.length = 0;
cycleBossAttacks();
return; // Stop the animation loop
}
updateBlocks();
requestAnimationFrame(animationLoop);
}
requestAnimationFrame(animationLoop);
}
function spawnBoostDrainer() {
if (boostDrainer) return;
const spawnX = gameScrollPosition + gameContainer.offsetWidth + 50;
const spawnY = Math.random() * 200 + 50;
boostDrainer = document.createElement('div');
boostDrainer.classList.add('boost-drainer');
boostDrainer.style.left = spawnX + 'px';
boostDrainer.style.bottom = spawnY + 'px';
gameWorld.appendChild(boostDrainer);
// Create chain element
boostDrainerChain = document.createElement('div');
boostDrainerChain.classList.add('boost-drainer-chain');
gameWorld.appendChild(boostDrainerChain);
boostDrainerHP = 3;
boostDrainerState = "moving"; // Start moving state
}
// Call this inside your main game loop or movement update function
function updateBoostDrainer() {
if (!boostDrainer) return;
const playerX = playerPositionX;
let enemyX = parseInt(boostDrainer.style.left);
switch (boostDrainerState) {
case 'moving':
const targetX = playerX + boostDrainerAttackXOffset;
if (enemyX > targetX) {
enemyX -= boostDrainerSpeed;
boostDrainer.style.left = enemyX + 'px';
} else {
// Reached attack position, start attack cycle
boostDrainerState = 'green';
boostDrainer.style.backgroundColor = 'green'; // Visual green state
boostDrainer.classList.remove('yellow', 'red');
boostDrainerAttackTimeout = setTimeout(() => {
boostDrainerState = 'yellow';
boostDrainer.style.backgroundColor = 'yellow';
boostDrainer.classList.add('yellow');
boostDrainerAttackTimeout = setTimeout(() => {
boostDrainerState = 'red';
boostDrainer.style.backgroundColor = 'red';
boostDrainer.classList.remove('yellow');
boostDrainer.classList.add('red');
fireBoostDrainerLaser(3); // Repeat laser fire 3 times
}, 1000);
}, 1000);
}
break;
// other states can be no movement
default:
break;
}
// Update the chain line only when moving or in states where player and drainer exist
if (boostDrainerChain) {
updateChainLine(boostDrainer, boostDrainerChain, player);
}
}
function fireBoostDrainerLaser(timesLeft) {
if (!boostDrainer || timesLeft <= 0) {
removeBoostDrainer();
return;
}
boostDrainerState = 'firing';
// Create laser div or reuse existing laser if you want smooth transition
if (boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
boostDrainerLaser = document.createElement('div');
boostDrainerLaser.classList.add('boost-laser');
const left = parseInt(boostDrainer.style.left) + boostDrainer.offsetWidth / 2 - 2;
boostDrainerLaser.style.left = left + 'px';
boostDrainerLaser.style.bottom = '0px';
// Laser height can be fixed or adjusted dynamically
boostDrainerLaser.style.height = '120px';
gameWorld.appendChild(boostDrainerLaser);
// Laser stays visible for 0.75s, then disappear, and wait 0.5s before next fire
setTimeout(() => {
if(boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
boostDrainerState = 'red'; // Can reset state or handle as needed
setTimeout(() => {
fireBoostDrainerLaser(timesLeft -1);
}, 500);
}, 750);
}
// Update the dotted chain line between two elements
function updateChainLine(fromEl, lineEl, toEl) {
if (!fromEl || !lineEl || !toEl) return;
// Get bounding rectangles
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
const containerRect = gameWorld.getBoundingClientRect();
// Calculate center point within gameWorld coordinates
// relative to gameWorld's top-left corner BUT we position absolute with bottom coordinate,
// so invert Y relative to container
function centerX(rect) {
return rect.left - containerRect.left + rect.width / 2;
}
function centerYBottom(rect) {
// bottom coordinate = container height - (rect.top - container top + half height)
return gameContainer.offsetHeight - (rect.top - containerRect.top + rect.height / 2);
}
const x1 = centerX(fromRect);
const y1 = centerYBottom(fromRect);
const x2 = centerX(toRect);
const y2 = centerYBottom(toRect);
const dx = x2 - x1;
const dy = y2 - y1;
const length = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
// Adjust the dotted line style here for better positioning
lineEl.style.width = length + 'px';
lineEl.style.left = x1 + 'px';
lineEl.style.bottom = y1 + 'px';
lineEl.style.transformOrigin = 'left center';
lineEl.style.transform = `rotate(${angle}deg)`;
}
// Handle when bullet hits the boost drainer
function handleBoostDrainerHit(bulletObj, bulletIndex) {
if (!boostDrainer) return false;
if (!checkCollision(bulletObj.element, boostDrainer)) return false;
// Remove bullet
bulletObj.element.remove();
bullets.splice(bulletIndex, 1);
// Reduce HP
boostDrainerHP--;
// Flash
boostDrainer.style.opacity = '0.5';
setTimeout(() => {
if (boostDrainer) boostDrainer.style.opacity = '1';
}, 150);
// If dead, remove
if (boostDrainerHP <= 0) {
removeBoostDrainer();
}
return true; // bullet handled
}
// Remove drainer and chain line cleanup
function removeBoostDrainer() {
if (boostDrainer) {
boostDrainer.remove();
boostDrainer = null;
}
if (boostDrainerChain) {
boostDrainerChain.remove();
boostDrainerChain = null;
}
if (boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
if (boostDrainerAttackTimeout) {
clearTimeout(boostDrainerAttackTimeout);
boostDrainerAttackTimeout = null;
}
boostDrainerHP = 0;
boostDrainerState = "dead";
}
function checkBoostDrainerLaserCollision() {
if (!boostDrainerLaser) return;
if (checkCollision(player, boostDrainerLaser)) {
// Hit by laser — destroy enemy & reduce lives
removeBoostDrainer();
reduceLives();
}
}
function bossPhaseTwoExplosionRain() {
const blockWidth = 30; // Width of the falling blocks
const blockHeight = 10; // Height of the falling blocks
const blockSpeed = 5; // Speed of block movement
const blocks = []; // Array to hold active blocks
const spawnInterval = 500; // Time between block spawns (in milliseconds)
const duration = 10000; // Duration of the attack (in milliseconds)
// Function to create and spawn blocks
function spawnBlock() {
const block = document.createElement('div');
block.classList.add('explosive-block'); // Add a class for styling
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Determine random left or right spawn position (off screen)
const side = Math.random() < 0.5 ? -blockWidth : window.innerWidth;
block.style.left = side < 0 ? `${side}px` : `${side - blockWidth}px`;
block.style.bottom = `${Math.random() * window.innerHeight}px`; // Random vertical position
document.body.appendChild(block);
// Move block across the screen
const moveInterval = setInterval(() => {
const currentLeft = parseInt(block.style.left);
block.style.left = `${currentLeft + (side < 0 ? blockSpeed : -blockSpeed)}px`; // Move toward center
// Check if the block is out of bounds
if (currentLeft < -blockWidth || currentLeft > window.innerWidth) {
clearInterval(moveInterval); // Stop moving if out of bounds
block.remove(); // Remove block from DOM
}
// Check collision with player
if (checkCollision(player, block)) {
reduceLives(); // Reduce player's lives
block.remove(); // Remove block on collision
clearInterval(moveInterval); // Stop moving
}
}, 1000 / 60); // Move at 60 FPS
blocks.push(block); // Add the block to the list of active blocks
}
// Start spawning blocks
const spawnBlocksInterval = setInterval(spawnBlock, spawnInterval);
// Stop spawning after the specified duration and clean up
setTimeout(() => {
clearInterval(spawnBlocksInterval);
blocks.forEach(block => block.remove()); // Remove remaining blocks
cycleBossAttacks(); // Move to next boss attack cycle
}, duration);
}
function eded() {
// === Config ===
const CONFIG = {
blockWidth: 80,
blockHeight: 20,
obstacleWidth: 40,
obstacleHeight: 20,
lavaRiseSpeed: 1, // pixels per tick
lavaRiseInterval: 100, // ms
phaseLavaHeight: 100, // lava height per phase
totalPhases: 3,
damageCooldown: 1000 // ms cooldown between damage ticks
};
// === State ===
let currentPhase = 0;
let lavaHeight = 0;
let lavaIntervalId = null;
let lastDamageTime = 0;
// Spawned elements tracking (for cleanup)
const blocks = [];
const obstacles = [];
// Assumes global DOM references:
// - gameWorld: container element for gameplay (should be position: relative or absolute)
// - player: player character element
// - reduceLives(): function that reduces player lives
// - cycleBossAttacks(): function that triggers after phase completes
// === Helper: checkCollision ===
// Simple AABB collision between two elements using getBoundingClientRect
// === Helper: getPlayerBottomOffset ===
// Distance in pixels from player's bottom edge to bottom of the game world container.
function getPlayerBottomOffset() {
const playerRect = player.getBoundingClientRect();
const worldRect = gameWorld.getBoundingClientRect();
return worldRect.bottom - playerRect.bottom; // positive means above bottom
}
// === Spawn functions ===
function createBlock(x) {
const block = document.createElement('div');
block.className = 'block';
Object.assign(block.style, {
position: 'absolute',
width: CONFIG.blockWidth + 'px',
height: CONFIG.blockHeight + 'px',
backgroundColor: 'blue',
left: x + 'px',
bottom: '0',
zIndex: '10',
});
gameWorld.appendChild(block);
blocks.push(block);
return block;
}
function createObstacle(x) {
const obstacle = document.createElement('div');
obstacle.className = 'obstacle';
Object.assign(obstacle.style, {
position: 'absolute',
width: CONFIG.obstacleWidth + 'px',
height: CONFIG.obstacleHeight + 'px',
backgroundColor: 'orange',
left: x + 'px',
bottom: '0',
zIndex: '10',
});
gameWorld.appendChild(obstacle);
obstacles.push(obstacle);
return obstacle;
}
// === Spawning initial fixed blocks ===
function spawnInitialBlocks() {
[100, 300, 500].forEach(xPos => createBlock(xPos));
}
// === Spawning obstacles per phase ===
function spawnPhaseObstacles() {
// Could vary per phase dynamically, for now fixed positions
[150, 350, 550].forEach(xPos => createObstacle(xPos));
}
// === Cleanup elements ===
function cleanupElements() {
blocks.forEach(b => b.remove());
obstacles.forEach(o => o.remove());
blocks.length = 0;
obstacles.length = 0;
// Remove lava if any
const existingLava = gameWorld.querySelector('.lava');
if (existingLava) existingLava.remove();
}
// === Create lava element ===
function createLava() {
const lava = document.createElement('div');
lava.className = 'lava';
Object.assign(lava.style, {
position: 'absolute',
width: '100%',
height: '0',
bottom: '0',
backgroundColor: 'red',
zIndex: '5',
transition: 'height 0.1s linear', // smooth growth per interval
});
gameWorld.appendChild(lava);
return lava;
}
// === Damage player if touching lava with cooldown ===
function tryDamagePlayer() {
const now = Date.now();
if (now - lastDamageTime < CONFIG.damageCooldown) return; // cooldown active
if (checkCollision(player, lavaElement)) {
const playerBottomDist = getPlayerBottomOffset();
if (playerBottomDist < lavaHeight) {
reduceLives();
lastDamageTime = now;
}
}
}
// === Lava rise tick ===
function onLavaRise() {
lavaHeight += CONFIG.lavaRiseSpeed;
lavaElement.style.height = lavaHeight + 'px';
tryDamagePlayer();
// Check if current phase lava fully raised
if (lavaHeight >= (currentPhase + 1) * CONFIG.phaseLavaHeight) {
clearInterval(lavaIntervalId);
lavaIntervalId = null;
currentPhase++;
if (currentPhase < CONFIG.totalPhases) {
// Spawn obstacles for next phase
spawnPhaseObstacles();
// Reset lava after short delay for player repositioning
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
lavaIntervalId = setInterval(onLavaRise, CONFIG.lavaRiseInterval);
}, 1000);
} else {
// Final phase complete, proceed to next boss phase or attacks
cycleBossAttacks();
}
}
}
// === Start phase 5 ===
function startPhase() {
cleanupElements();
// Spawn blocks and initial obstacles
spawnInitialBlocks();
spawnPhaseObstacles();
// Create lava element fresh
lavaElement = createLava();
// Reset state
lavaHeight = 0;
currentPhase = 0;
lastDamageTime = 0;
// Start lava rising loop
lavaIntervalId = setInterval(onLavaRise, CONFIG.lavaRiseInterval);
}
// Launch phase 5
let lavaElement; // define lavaElement here to be accessible in nested functions
startPhase();
}
function bossPhaseTwoTargetSlam() {
let slamCount = 0;
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 500;
const slamHoldDuration = 1500;
const slamDurationUp = 700;
const slamCooldown = 1000;
// Track if a slam is currently active
let slamActive = false;
// Promise-based sleep
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Collision check: AABB rectangle intersection
function checkCollision(elem1, elem2) {
const r1 = elem1.getBoundingClientRect();
const r2 = elem2.getBoundingClientRect();
return !(
r1.right < r2.left ||
r1.left > r2.right ||
r1.bottom < r2.top ||
r1.top > r2.bottom
);
}
// Placeholder for player's lives remaining, define accordingly
let lives = 3;
// Reduces player's lives safely
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
}
}
// Shake screen effect placeholder — implement your own or remove
function shakeScreen() {
// Example simple console log
console.log("Screen shakes!");
}
// Create burst effect placeholder — implement as needed
function createBurst(x, y) {
console.log(`Burst at (${x}, ${y})`);
}
// Animate bottom position with transition and Promise
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Force reflow to apply transition properly
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
// Monitor collision during slamHoldDuration, but only reduce lives once per second max
async function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
let lastHitTime = 0;
const interval = setInterval(() => {
const now = Date.now();
if (now - lastHitTime >= 1000 && checkCollision(player, boss)) {
reduceLives();
lastHitTime = now;
}
if (now >= startTime + duration) {
clearInterval(interval);
resolve();
}
}, 50);
const startTime = Date.now();
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
// Calculate player and boss parent positions to align boss over player horizontally
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
// Player center X inside boss parent container
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
// Position boss instantly above player at hover height
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(500); // hover delay
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
// Check collision during slam hold, max once per 1000ms
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 10000);
}
}
performSlam();
}
// Function to shake the screen
function shakeScreen() {
let shakeEffect = document.createElement('div');
shakeEffect.style.position = "fixed";
shakeEffect.style.top = "0";
shakeEffect.style.left = "0";
shakeEffect.style.width = "100%";
shakeEffect.style.height = "100%";
shakeEffect.style.backgroundColor = "rgba(255, 0, 0, 0.5)"; // Flashing effect
shakeEffect.style.transition = 'transform 0.1s';
document.body.appendChild(shakeEffect);
// Shake effect
const shakeTimes = 15; // Number of shakes
const shakeDistance = 40; // Distance to shake
const shakeInterval = 50; // Time between shakes
for(let i = 0; i < shakeTimes; i++) {
setTimeout(() => {
shakeEffect.style.transform = (i % 2 === 0) ? `translateX(-${shakeDistance}px)` : `translateX(${shakeDistance}px)`;
}, i * shakeInterval);
}
// Reset the screen after shaking
setTimeout(() => {
shakeEffect.style.transform = 'translateX(0)';
}, shakeTimes * shakeInterval);
setTimeout(() => {
shakeEffect.remove(); // Clean up the shake effect
}, shakeTimes * shakeInterval + 100); // Ensure it removes after the reset
}
// Function to reset the boss position to the center of the screen
function resetBossPosition() {
boss.style.left = (gameContainer.offsetWidth / 2 - boss.offsetWidth / 2) + 'px'; // Center the boss
boss.style.bottom = "100px"; // Reset position
}
function bossPhaseTwoBarrage() {
boss.style.animation = "boss-shake 1s infinite alternate"; // Boss shakes
let spawnCount = 0;
let spawnInterval = setInterval(() => {
if (spawnCount >= 15) { // Stops after 10 spawns
clearInterval(spawnInterval);
boss.style.animation = ""; // Stop shaking
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
let enemy = createElement('enemy', enemies, Math.random() * gameContainer.offsetWidth);
enemy.style.bottom = "400px"; // Start from top
let fallSpeed = 12;
let fallInterval = setInterval(() => {
enemy.style.bottom = (parseInt(enemy.style.bottom) - fallSpeed) + "px";
if (parseInt(enemy.style.bottom) <= 0) {
enemy.style.bottom = "0px"; // Stop at ground level
clearInterval(fallInterval);
}
// Check for collision with player
if (checkCollision(player, enemy)) {
reduceLives();
enemy.remove();
clearInterval(fallInterval);
}
}, 50);
spawnCount++;
}, 500); // Spawn every half second
}
function bossEnemyBarrage() {
boss.style.animation = "boss-shake 1s infinite alternate"; // Boss shakes
let spawnCount = 0;
let spawnInterval = setInterval(() => {
if (spawnCount >= 10) { // Stops after 10 spawns
clearInterval(spawnInterval);
boss.style.animation = ""; // Stop shaking
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
let enemy = createElement('enemy', enemies, Math.random() * gameContainer.offsetWidth);
enemy.style.bottom = "400px"; // Start from top
let fallSpeed = 8;
let fallInterval = setInterval(() => {
enemy.style.bottom = (parseInt(enemy.style.bottom) - fallSpeed) + "px";
if (parseInt(enemy.style.bottom) <= 0) {
enemy.style.bottom = "0px"; // Stop at ground level
clearInterval(fallInterval);
}
// Check for collision with player
if (checkCollision(player, enemy)) {
reduceLives();
enemy.remove();
clearInterval(fallInterval);
}
}, 50);
spawnCount++;
}, 500); // Spawn every half second
}
function bossPhaseTwoTargetSlam() {
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 400; // Faster slam down
const slamHoldDuration = 1200; // Hold slam a bit shorter
const slamDurationUp = 600; // Faster slam up
const slamCooldown = 700; // Faster cooldown between slams
// Track if slam is active to avoid overlapping slams
let slamCount = 0;
// Player lives placeholder (should come from your global game state)
// Consider passing lives as param or referencing global store instead
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
// TODO: Trigger player hit animation / feedback here
}
}
// Use Promises to handle CSS transitions cleanly
function animateBottomPosition(targetBottom, duration) {
return new Promise((resolve) => {
let resolved = false;
function onTransitionEnd(e) {
if (e.propertyName === 'bottom' && !resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Apply CSS transition
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
// Safety fallback in case transitionend doesn't fire:
setTimeout(() => {
if (!resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}, duration + 50);
});
}
// Helper to get player and boss rectangles
function getRects() {
return {
playerRect: player.getBoundingClientRect(),
bossRect: boss.getBoundingClientRect(),
parentRect: boss.parentElement.getBoundingClientRect()
};
}
// Check AABB collision between two DOMRect objects
function isCollision(rect1, rect2) {
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
}
// Smoothly monitor collision during hold using requestAnimationFrame for fast, real-time checks
function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
const hitCooldown = 1000; // ms between allowed hits
let lastHitTimestamp = 0;
const startTime = performance.now();
function check(time) {
if (time - startTime >= duration) {
resolve();
return;
}
const { playerRect, bossRect } = getRects();
if (isCollision(playerRect, bossRect)) {
if (time - lastHitTimestamp > hitCooldown && lives > 0) {
reduceLives();
lastHitTimestamp = time;
shakeScreen();
}
}
if (lives <= 0) {
resolve(); // Stop checking if player dead or no lives left
return;
}
requestAnimationFrame(check);
}
requestAnimationFrame(check);
});
}
async function performSlam() {
slamCount = 0;
while (slamCount < maxSlams) {
// Position boss above player horizontally, instantly (no transition)
const { playerRect, parentRect } = getRects();
const playerCenterX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none'; // Remove transition for instant move
boss.style.left = `${playerCenterX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
// Let rendering catch up (a frame or short delay)
await new Promise(resolve => requestAnimationFrame(resolve));
await new Promise(resolve => setTimeout(resolve, 300)); // small hover pause, quicker than before
shakeScreen();
// Slam down animation
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerCenterX, groundLevel);
// Hold slammed position while checking collision multiple times a second, respond quick but not too spammy
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
// Cooldown between slams
await new Promise(resolve => setTimeout(resolve, slamCooldown));
}
console.log("Slam attack finished");
// Reset boss position or continue attack cycles
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
// Reduced delay to chain attacks faster
setTimeout(cycleBossAttacks, 5000);
}
}
performSlam();
}
function bossExplosionRain() {
boss.style.animation = "boss-shake 1s infinite alternate";
let dropCount = 0;
let dropInterval = setInterval(() => {
if (dropCount >= 10) {
clearInterval(dropInterval);
boss.style.animation = "";
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
// Randomize the X position based on the player's position
let playerPositionX = parseInt(player.style.left);
let explosionBlock = document.createElement('div'); // Corrected to use document.createElement
explosionBlock.classList.add('explosive-block'); // Add a class if you want to style it later
explosionBlock.style.position = "absolute"; // Important for positioning
explosionBlock.style.left = (playerPositionX + Math.random() * 30 - 15) + "px"; // Randomize the horizontal position
explosionBlock.style.bottom = "400px"; // Start high
explosionBlock.style.width = "30px"; // Width of the falling block
explosionBlock.style.height = "10px"; // Height of the falling block
explosionBlock.style.backgroundColor = "red"; // Color of the falling block
gameWorld.appendChild(explosionBlock);
let fallSpeed = 5; // Speed of fall
let fallInterval = setInterval(() => {
explosionBlock.style.bottom = (parseInt(explosionBlock.style.bottom) - fallSpeed) + "px";
if (parseInt(explosionBlock.style.bottom) <= 0) {
createBurst(parseInt(explosionBlock.style.left), 0); // Create burst effect
if (checkCollision(explosionBlock, player)) {
reduceLives(); // Reduce lives if hit
}
explosionBlock.remove(); // Remove block once it hits ground
clearInterval(fallInterval);
}
}, 50); // Update every 50ms
dropCount++;
}, 1000); // Drop every second
}
class Laser {
constructor(x, y, direction, length) {
this.x = x;
this.y = y;
this.direction = direction; // 'vertical' or 'horizontal'
this.length = length;
this.element = null;
}
createElement() {
this.element = document.createElement('div');
this.element.style.position = 'absolute';
this.element.style.backgroundColor = 'yellow';
if (this.direction === 'vertical') {
this.element.style.width = '4px';
this.element.style.height = this.length + 'px';
this.element.style.left = this.x + 'px';
this.element.style.bottom = this.y + 'px';
} else {
this.element.style.height = '4px';
this.element.style.width = this.length + 'px';
this.element.style.left = this.x + 'px';
this.element.style.bottom = this.y + 'px';
}
this.element.style.opacity = '0.8';
this.element.style.zIndex = '1000';
this.element.classList.add('laser-beam');
document.getElementById('game-world').appendChild(this.element);
}
activate() {
// Make laser red and fully opaque when activated
if (!this.element) return;
this.element.style.backgroundColor = 'red';
this.element.style.opacity = '1';
}
remove() {
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
this.element = null;
}
}
getBoundingRect() {
return this.element.getBoundingClientRect();
}
}
function isPlayerOnLaser(laser) {
if (!laser.element) return false;
const playerRect = player.getBoundingClientRect();
const laserRect = laser.getBoundingRect();
return !(
playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom
);
}
// Main laser phase boss move function - call it with await in your attack cycle
async function laserPhaseMove() {
displayMessage("Dodge the lasers!", 2);
await sleep(2000); // Wait for player to read the message
const repeats = 4;
const gameWorld = document.getElementById('game-world');
const gameBounds = gameWorld.getBoundingClientRect();
for (let round = 0; round < repeats; round++) {
// Spawn lasers
const lasers = [];
// Number of lasers per round (can tweak for difficulty)
const lasersCount = 5;
for (let i = 0; i < lasersCount; i++) {
const vertical = Math.random() < 0.5;
if (vertical) {
// Random X inside game world boundaries
const xPos = Math.random() * gameBounds.width;
// Y set to bottom of game world container - zero
lasers.push(new Laser(xPos, 0, 'vertical', gameBounds.height));
} else {
// Random Y inside game world height
const yPos = Math.random() * gameBounds.height;
// X at left edge = 0
lasers.push(new Laser(0, yPos, 'horizontal', gameBounds.width));
}
}
// Create laser elements in DOM with warning color (yellow)
lasers.forEach(laser => laser.createElement());
// Warn lasers blinking: toggle opacity several times for 1 second
const warningDurationMs = 1000;
const blinkIntervalMs = 200;
const blinkCount = warningDurationMs / blinkIntervalMs;
for (let b = 0; b < blinkCount; b++) {
lasers.forEach(laser => {
if (!laser.element) return;
laser.element.style.opacity = (b % 2 === 0) ? '0.3' : '0.8';
});
await sleep(blinkIntervalMs);
}
// Set lasers fully visible and red (active)
lasers.forEach(laser => laser.activate());
// Damage phase: 2 seconds, check collisions every 100ms
const damageDurationMs = 2000;
const damageCheckIntervalMs = 100;
let elapsed = 0;
while (elapsed < damageDurationMs) {
lasers.forEach(laser => {
if (isPlayerOnLaser(laser)) {
lives = Math.max(0, lives - 1);
displayMessage(`Hit! Lives: ${lives}`, 1);
// TODO: Add your death/game over logic here if lives <= 0
}
});
await sleep(damageCheckIntervalMs);
elapsed += damageCheckIntervalMs;
}
// Remove all lasers from DOM and clear array
lasers.forEach(laser => laser.remove());
lasers.length = 0;
// Short delay before next round
await sleep(500);
}
displayMessage("Laser phase complete!", 2);
await sleep(2000);
cycleBossAttacks();
}
// Helper sleep function (if not defined)
function getRandomSpawnCount(min = 1, max = 5) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function bossPhaseFiveAttackA() {
displayMessage("Trial 1", 3);
await sleep(3000);
const totalDuration = 25000; // 25 seconds
const spawnInterval = 500;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnObstacle();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackB() {
displayMessage("Trial 2", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 600;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnChargingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSniperEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackC() {
displayMessage("Trial 3", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 550;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnReflectiveEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackD() {
displayMessage("Trial 4", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 400;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnLaserEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSplitterEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnObstacle();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackE() {
displayMessage("Trial 5", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 700;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSniperEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackF() {
displayMessage("Trial 6", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 450;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnVirusDrone();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSplitterEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
function EnemyBarrage() {
const numberOfFlyingEnemies = 3; // Total number of flying enemies to summon
const numberOfChargingEnemies = 5; // Number of charging enemies to spawn
const flyingSpawnDelay = 3000; // Delay between each flying enemy spawn (in milliseconds)
const chargingSpawnDelay = 4000; // Delay between each charging enemy spawn (in milliseconds)
const totalDuration = 15000; // Total time for the flying enemy spawning process (in milliseconds)
// Function to spawn multiple flying enemies
const flyingEnemiesInterval = setInterval(() => {
if (numberOfFlyingEnemies > 0) {
SpawnSplitterEnemy(); // Call to spawn a single flying enemy
numberOfFlyingEnemies--; // Decrement the count
} else {
clearInterval(flyingEnemiesInterval); // Stop spawning when done
}
}, flyingSpawnDelay); // Spawn every 2 seconds
// Function to spawn charging enemies
const chargingEnemiesInterval = setInterval(() => {
if (numberOfChargingEnemies > 0) {
spawnChargingEnemy(); // Call to spawn a charging enemy
numberOfChargingEnemies--; // Decrement the count
} else {
clearInterval(chargingEnemiesInterval); // Stop spawning when done
}
}, chargingSpawnDelay); // Spawn every 3 seconds
// Wait before proceeding to the next boss cycle
setTimeout(() => {
clearInterval(flyingEnemiesInterval); // Clear the spawn loops
clearInterval(chargingEnemiesInterval);
cycleBossAttacks(); // Proceed to the next boss attack
}, totalDuration); // 10 seconds before moving to next attack
}
function bossPhaseThree() {
const numberOfFlyingEnemies = 6; // Total number of flying enemies to summon
const numberOfChargingEnemies = 10; // Number of charging enemies to spawn
const flyingSpawnDelay = 3000; // Delay between each flying enemy spawn (in milliseconds)
const chargingSpawnDelay = 4000; // Delay between each charging enemy spawn (in milliseconds)
const totalDuration = 20000; // Total time for the flying enemy spawning process (in milliseconds)
// Function to spawn multiple flying enemies
const flyingEnemiesInterval = setInterval(() => {
if (numberOfFlyingEnemies > 0) {
spawnFlyingEnemy(); // Call to spawn a single flying enemy
numberOfFlyingEnemies--; // Decrement the count
} else {
clearInterval(flyingEnemiesInterval); // Stop spawning when done
}
}, flyingSpawnDelay); // Spawn every 2 seconds
// Function to spawn charging enemies
const chargingEnemiesInterval = setInterval(() => {
if (numberOfChargingEnemies > 0) {
spawnChargingEnemy(); // Call to spawn a charging enemy
numberOfChargingEnemies--; // Decrement the count
} else {
clearInterval(chargingEnemiesInterval); // Stop spawning when done
}
}, chargingSpawnDelay); // Spawn every 3 seconds
// Wait before proceeding to the next boss cycle
setTimeout(() => {
clearInterval(flyingEnemiesInterval); // Clear the spawn loops
clearInterval(chargingEnemiesInterval);
cycleBossAttacks(); // Proceed to the next boss attack
}, totalDuration); // 10 seconds before moving to next attack
}
function bossNewPower() {
let shotsFired = 0;
const maxShots = 15;
const shotCooldown = 800; // 0.8 seconds between shots
const shootInterval = setInterval(() => {
if (shotsFired >= maxShots) {
clearInterval(shootInterval);
// Introduce a delay before invoking the next attack or phase
setTimeout(() => {
cycleBossAttacks(); // Call back the attack cycle function
}, 2000); // 2 seconds for any cooling down or animation effect
return; // Stop firing after maximum shots
}
// Calculate the boss's and player's positions
const bossX = parseInt(boss.style.left) + boss.offsetWidth / 2; // Middle of the boss
const bossY = parseInt(boss.style.bottom) + 30; // Just above the boss
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Middle of the player
const playerY = parseInt(player.style.bottom); // Bottom of the player
// Calculate the direction vector
const dx = playerX - bossX;
const dy = playerY - bossY;
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 10; // Speed of the projectile
// Create a projectile
const projectile = document.createElement('div');
projectile.classList.add('boss-projectile');
projectile.style.width = '10px';
projectile.style.height = '10px';
projectile.style.backgroundColor = 'orange';
projectile.style.position = 'absolute';
projectile.style.left = `${bossX - 5}px`; // Center it
projectile.style.bottom = `${bossY}px`; // Start just above the boss
gameWorld.appendChild(projectile);
let projectileInterval = setInterval(() => {
// Move projectile towards the player
const moveX = (dx / distance) * velocity; // Calculate x movement
const moveY = (dy / distance) * velocity; // Calculate y movement
projectile.style.left = (parseInt(projectile.style.left) + moveX) + 'px';
projectile.style.bottom = (parseInt(projectile.style.bottom) + moveY) + 'px';
// Check collision
if (checkCollision(player, projectile)) {
reduceLives(); // Reduce player lives on hit
projectile.remove(); // Remove projectile on hit
clearInterval(projectileInterval);
}
// Remove projectile if it goes off-screen
if (parseInt(projectile.style.bottom) > gameContainer.offsetHeight ||
parseInt(projectile.style.left) < 0 ||
parseInt(projectile.style.left) > gameContainer.offsetWidth) {
projectile.remove(); // Remove off-screen
clearInterval(projectileInterval);
}
}, 50); // Update every 50ms
shotsFired++;
}, shotCooldown); // Fire every 0.8 seconds
}
function displayBossHealth() {
bossHealthBar.style.display = 'block';
updateBossHealth();
}
function updateBossHealth() {
// Ensure the health bar width updates according to boss health
const maxHealth = 150; // Define the maximum health for level 3
const currentHealth = Math.max(0, Math.min(bossHealth, maxHealth)); // Ensure health stays within bounds
// Calculate the percentage of current health relative to the maximum health
const healthPercentage = (currentHealth / maxHealth) * 100;
// Update the CSS width of the health bar to reflect the current health
bossHealthBar.style.width = `${healthPercentage}%`;
// Optional: Set a different color based on health status (for visual feedback)
if (currentHealth > maxHealth * 0.5) {
bossHealthBar.style.backgroundColor = 'green'; // Healthy
} else if (currentHealth > maxHealth * 0.2) {
bossHealthBar.style.backgroundColor = 'yellow'; // Caution
} else {
bossHealthBar.style.backgroundColor = 'red'; // Critical health
}
}
function spawnBossEnemies() {
if (bossShielded) {
for (let i = 0; i < 2; i++) {
const enemy = createElement('enemy', enemies, parseInt(boss.style.left) + (i * 50));
enemy.style.bottom = `${Math.random() * 250}px`;
}
}
}
function spawnBossBullets() {
const bullet = createBullet();
bullet.style.left = parseInt(boss.style.left) + 'px';
bullet.style.bottom = parseInt(boss.style.bottom) + 30 + 'px';
bullet.style.backgroundColor = 'orange';
gameWorld.appendChild(bullet);
bullets.push({ element: bullet, direction: -1 });
}
function checkBossHit() {
if (!boss || bossShielded) return; // Stop damaging if boss is shielded
bullets.forEach((bullet, bulletIndex) => {
if (checkCollision(bullet.element, boss)) {
bossHealth -= 2; // Decrement boss health
updateBossHealth();
bullet.element.remove();
bullets.splice(bulletIndex, 1);
// Check if the boss is defeated
if (bossHealth <= 0) {
displayMessage("Boss defeated!");
onBossDefeated(); // Trigger boss defeat logic
}
}
});
}
function onBossDefeated() {
bossHealthBar.style.display = 'none'; // Hide health bar upon defeat
const gameMode = localStorage.getItem('gameMode');
let messages = []; // Array to hold messages
// Create messages based on the level
if (level === 1) {
messages = [
"Player: Take THAT!.",
"Boss: Noooo! You may have won, but I'll see you in the next level!",
"Boss: Until next time.",
"Boss: Prepare yourself for what's to come!"
];
} else if (level === 2) {
messages = [
"Player: Heh to ez!.",
"Boss: You think this is the end? I’ll return!",
"Boss: With my new enemies, you will never win!",
"Boss: Prepare for an even greater challenge in Level 3!",
"Boss: You will need to be stronger to defeat me next time!",
];
} else if (level === 3) {
messages = [
"Boss: How could this happen?!",
"Boss: Well... That was all the attacks I had... and you beated them.",
"Boss: Well now worries because my brother knows about this now.",
"Boss: He will make sure your dead.",
"Player: What do you mean your brother is coming to get me?.",
"Boss: The strongest one in our family known for wiping all advanturers.",
"Player: How come he is the strongest.",
"Boss: he is known for his annoying attacks and summoning things.",
"Boss: He plays around to much with his players.",
"Boss: Good luck because this might be your actual last fight...",
"Player: Ok?...",
];
} else if (level === 4) {
messages = [
"Boss: I wish not to speak with you get out.",
];
} else if (level === 5) {
messages = [
"Trials ended",
"Thank you for playing.",
"That is the end of the game.",
];
}
// Function to display messages sequentially
function displaySequentialMessages(messages, index) {
if (index < messages.length) {
displayMessage(messages[index]); // Display current message
// Set timeout for the next message
setTimeout(() => {
displaySequentialMessages(messages, index + 1); // Move to next message
}, 4500); // 4.5 seconds delay
} else {
// After all messages in level 3, unlock Endless Mode
if (level === 6) {
localStorage.setItem('endlessModeUnlocked', 'true');
displayMessage("Endless Mode And sandbox Unlocked!"); // Optional inform player
showEndlessModeButton();
setTimeout(() => {
setupNextLevel(); // Proceed to the next level setup
}, 3000);
} else {
setupNextLevel(); // Proceed to the next level setup
}
}
}
// Start the message display
displaySequentialMessages(messages, 0);
}
function setupNextLevel() {
// Check if in Endless Mode or Story Mode to manage levels separately
if (isEndlessMode) {
// Increment level for Endless mode
currentEndlessLevel++;
localStorage.setItem('endlessLevel', currentEndlessLevel); // Save updated level
displayMessage(`You've advanced to Endless Level ${currentEndlessLevel}!`);
} else {
// Logic for Story Mode - increment story level
storyModeLevel++;
if (storyModeLevel > 5) {
storyModeLevel = 1; // Reset back to Level 1 if beyond max
}
localStorage.setItem('storyLevel', storyModeLevel); // Save updated level
displayMessage(`You've advanced to Story Level ${storyModeLevel}!`);
}
// Set up the boss door to advance to the next level
const bossDoor = document.createElement('div'); // Create a new div for the boss door
bossDoor.classList.add('boss-door'); // Add class for styling
bossDoor.style.position = 'absolute'; // Positioning for the door
bossDoor.style.left = '600px'; // Specify door's X position
bossDoor.style.bottom = '0px'; // Specify door's Y position (ground level)
bossDoor.style.width = '50px'; // Set width
bossDoor.style.height = '100px'; // Set height
bossDoor.style.backgroundColor = 'gold'; // Set background color (for visibility)
bossDoor.style.border = '2px solid black'; // Optional: add a border
bossDoor.style.boxShadow = '0 0 15px rgba(255, 215, 0, 1)'; // Glow effect
// Append the boss door to the game world
gameWorld.appendChild(bossDoor);
// Inform the player they can interact with the door
displayMessage("Press 'E' to proceed to the next level!");
// Allow player to transition to the next level upon interacting
document.addEventListener('keydown', function handleKeyDown(event) {
if (event.key === 'E' || event.key === 'e') {
displayMessage("Refreshing to Level " + level + "...");
location.reload(); // Refresh the page to start the new level
console.log("Page refreshed for Level " + level + ".");
document.removeEventListener('keydown', handleKeyDown); // Remove the event listener to prevent repeated triggering
}
});
}
function clearAllElements() {
// Clear all elements in the game (elements grouped into arrays for easier management)
[obstacles, enemies, bullets.map(b => b.element), powerUps, platforms, bossDoors, coins].forEach(arr => {
arr.forEach(el => el.remove());
arr.length = 0; // Clear the array
});
}
function SendBackToDoor() {
setTimeout(() => {
player.style.left = "50px";
player.style.bottom = "0px";
setTimeout(() => {
activateBossPower();
level = 2; // Reset to Level 1
resetGame();
startLevel(); // Restart the game
}, 2000);
}, 3000);
}
function activateFriendDialogue() {
interactMessage.style.display = 'none';
displayMessage("Thank you for saving me!");
setTimeout(() => {
displayMessage("I got a secret power for you.");
setTimeout(() => {
grantBossPowerUp();
sendbacktodoor();
friend.style.display = 'none';
}, 3000);
}, 2000);
}
let isCharging = false;
let chargeTime = 0;
function shootLaserBlast(chargeTime) {
let laser = document.createElement('div');
laser.classList.add('laser');
laser.style.left = player.style.left;
laser.style.bottom = player.style.bottom;
laser.style.width = `${chargeTime * 10}px`;
laser.style.height = "10px";
laser.style.backgroundColor = "cyan";
gameWorld.appendChild(laser);
setTimeout(() => {
laser.remove();
enemies.forEach(enemy => {
if (checkCollision(laser, enemy)) {
enemy.remove();
}
});
}, 100);
}
function displayMessage(message) {
powerUpMessage.textContent = message;
powerUpMessage.style.display = 'block';
setTimeout(() => {
powerUpMessage.style.display = 'none';
}, 2000);
}
function checkScoreForLevelTransition() {
localStorage.setItem('playerLevel', level); // Save updated level in local stora
}
function spawnFlyingEnemy() {
const flyingEnemy = createElement('flying-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
flyingEnemy.style.bottom = Math.random() * 150 + 50 + 'px';
}
function moveFlyingEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('flying-enemy')) {
// Move the enemy up and down
let enemyYPosition = parseInt(enemy.style.bottom) || 0; // Get current Y position or default to 0
let direction = enemy.dataset.direction ? parseInt(enemy.dataset.direction) : 1; // Up or down direction
// Update vertical position of the flying enemy
enemyYPosition += direction * 1; // Move 1 pixel per frame
enemy.style.bottom = enemyYPosition + 'px';
// Reverse direction if the flying enemy reaches a certain height
if (enemyYPosition >= 150) { // Maximum height
direction = -1; // Change to downward movement
} else if (enemyYPosition <= 0) { // Minimum height (ground)
direction = 1; // Change to upward movement
}
enemy.dataset.direction = direction; // Store the current direction for the next update
// Move the enemy left
enemy.style.left = (parseInt(enemy.style.left) - 2) + 'px';
// Check shooting
if (enemy.dataset.lastShotTime === undefined || Date.now() - enemy.dataset.lastShotTime > 10000) { // Every 10 seconds
shootBulletAtPlayer(enemy); // Shoot at the player
enemy.dataset.lastShotTime = Date.now(); // Update the last shot time
}
// Boundary checking and collision with the player
if (parseInt(enemy.style.left) < 0) {
removeElement(enemy, enemies, index, 30);
} else if (checkCollision(player, enemy)) {
reduceLives();
removeElement(enemy, enemies, index, 0);
}
}
});
}
// Function to shoot a bullet aimed at the player
function shootBulletAtPlayer(enemy) {
const bullet = document.createElement('div');
bullet.classList.add('bullet'); // Add your bullet class
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'orange'; // Bullet color
bullet.style.position = 'absolute';
// Position the bullet at the enemy's current position
bullet.style.left = (parseInt(enemy.style.left) + 20) + 'px'; // Position it slightly in front of the enemy
bullet.style.bottom = (parseInt(enemy.style.bottom) + 15) + 'px'; // Start just below the enemy
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate angle towards the player
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - (parseInt(bullet.style.bottom));
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Bullet movement
const moveBullet = setInterval(() => {
const moveX = (dx / distance) * velocity;
const moveY = (dy / distance) * velocity;
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check if the bullet hits the player or goes off screen
if (checkCollision(bullet, player)) {
reduceLives(); // Damages the player
bullet.remove(); // Remove bullet
clearInterval(moveBullet); // Stop moving the bullet
}
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBullet); // Stop moving the bullet
}
}, 1000 / 60); // 60 FPS
}
function spawnChargingEnemy() {
const chargingEnemy = createElement('charging-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
chargingEnemy.style.bottom = '0px';
chargingEnemy.setAttribute('data-charge', '0');
chargingEnemy.classList.add('charging-prep'); // Fading in/out red glow effect
}
function createChargeTrail(x, y) {
const trail = document.createElement('div');
trail.classList.add('charge-trail');
trail.style.left = x + 'px';
trail.style.bottom = y + 'px';
gameWorld.appendChild(trail);
setTimeout(() => trail.remove(), 400); // Shorter effect for smoothness
}
function moveChargingEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('charging-enemy')) {
let chargeTime = parseInt(enemy.getAttribute('data-charge'));
if (chargeTime < 60) {
// Charging phase (fading in/out glow)
enemy.setAttribute('data-charge', chargeTime + 1);
} else {
// Fully Charged (Solid Red Outline + Charging Forward)
enemy.classList.remove('charging-prep');
enemy.classList.add('charging-active');
enemy.style.left = (parseInt(enemy.style.left) - 6) + 'px';
// Create a bright red trail
createChargeTrail(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
}
// Check collision with player
if (checkCollision(player, enemy)) {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom)); // Impact explosion
reduceLives();
removeElement(enemy, enemies, index, 0);
}
// Remove if out of bounds
if (parseInt(enemy.style.left) < gameScrollPosition - enemy.offsetWidth) {
removeElement(enemy, enemies, index, 30);
}
}
});
}
function spawnStealthEnemy() {
const stealthEnemy = createElement('stealth-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
stealthEnemy.setAttribute('data-stealth', '0');
stealthEnemy.style.backgroundColor = '#555';
stealthEnemy.style.opacity = '1';
}
function moveStealthEnemies() {
enemies.forEach(en => {
if (en.classList.contains('stealth-enemy')) {
const stealth = parseInt(en.getAttribute('data-stealth'));
if (stealth > 50) {
en.style.opacity = en.style.opacity == '0' ? '1' : '0';
en.setAttribute('data-stealth', '0');
} else {
en.setAttribute('data-stealth', stealth + 1);
}
}
});
}
function moveSniperEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('sniper-enemy')) {
const enemyX = parseInt(enemy.style.left) + enemy.offsetWidth / 2; // Center position
const enemyY = parseInt(enemy.style.bottom) + enemy.offsetHeight; // Height
// Shooting mechanism
if (!enemy.dataset.lastShotTime || Date.now() - enemy.dataset.lastShotTime > 3000) { // Every 3 seconds
shootAtPlayer(enemy);
enemy.dataset.lastShotTime = Date.now(); // Update for next shot
}
// Check distance to player
const distanceToPlayer = Math.sqrt(Math.pow(enemyX - (parseInt(player.style.left) + player.offsetWidth / 2), 2) +
Math.pow(enemyY - parseInt(player.style.bottom), 2));
if (distanceToPlayer < 300) { // Within 300 pixels visibility range
enemy.style.opacity = '1'; // Fully visible
} else {
enemy.style.opacity = '0.5'; // Reduced opacity
}
}
});
}
// Function for the sniper to shoot at the player
function shootAtPlayer(enemy) {
const bullet = document.createElement('div');
bullet.classList.add('bullet'); // Add your class for the bullet
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'red'; // Bullet color
bullet.style.position = 'absolute';
// Position the bullet at the sniper's location
bullet.style.left = (parseInt(enemy.style.left) + 20) + 'px'; // Set slight offset
bullet.style.bottom = (parseInt(enemy.style.bottom) + 15) + 'px'; // Set above the sniper
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate angle towards the player
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - (parseInt(bullet.style.bottom));
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Bullet movement logic
const moveBullet = setInterval(() => {
const moveX = (dx / distance) * velocity; // Calculate x movement direction
const moveY = (dy / distance) * velocity; // Calculate y movement direction
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check for bullet collision with player
if (checkCollision(bullet, player)) {
reduceLives(); // Damage player
bullet.remove(); // Remove bullet
clearInterval(moveBullet); // Stop bullet movement
}
// Remove bullet if it goes off screen
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBullet); // Stop bullet movement
}
}, 1000 / 60); // 60 FPS
}
function spawnSplitterEnemy() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const splitter = createElement('split-enemy', enemies, leftPos);
splitter.style.left = leftPos + 'px';
splitter.style.bottom = '0px';
splitter.classList.add('split'); // Important!
splitter.style.width = '50px';
splitter.style.height = '50px';
}
function moveSplitterEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('split-enemy')) {
const speed = enemy.classList.contains('split') ? 1 : 3; // mini splitters move faster
enemy.style.left = (parseInt(enemy.style.left) - speed) + 'px';
if (parseInt(enemy.style.left) < gameScrollPosition - enemy.offsetWidth) {
removeElement(enemy, enemies, index, 30); // Give some score for killing
} else if (checkCollision(player, enemy)) {
reduceLives();
removeElement(enemy, enemies, index, 0);
}
}
});
}
function handleSplitterEnemyHit(enemy, enemyIndex) {
console.log('Splitter hit triggered for enemy:', enemy);
if (enemy.classList.contains('split')) { // just to be sure
const baseLeft = parseInt(enemy.style.left);
const baseBottom = enemy.style.bottom;
const miniSize = 20; // Increased size from 15 to 20 px
const gap = 10; // Gap between minis (pixels)
for (let i = 0; i < 2; i++) { // Spawn only 2 mini enemies
// Calculate left position for each mini enemy to have gap between them
const miniLeft = baseLeft + i * (miniSize + gap);
const miniEnemy = createElement('split-enemy', enemies, miniLeft);
miniEnemy.style.width = miniSize + 'px';
miniEnemy.style.height = miniSize + 'px';
miniEnemy.classList.remove('split');
miniEnemy.style.bottom = baseBottom; // Appear at the same vertical pos
}
}
removeElement(enemy, enemies, enemyIndex, 0);
}
function handleExplosiveEnemyHit(enemy, enemyIndex) {
removeElement(enemy, enemies, enemyIndex, 0);
enemies.forEach((otherEnemy, index) => {
if (calculateDistance(enemy, otherEnemy) < 50) {
removeElement(otherEnemy, enemies, index, 0);
}
});
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
reduceLives();
}
function spawnSniperEnemy() {
const sniper = createElement('sniper-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
sniper.style.bottom = '200px';
const sniperInterval = setInterval(() => {
if (Math.random() < 0.05 && calculateDistance(player, sniper) < 300) {
const bullet = createBullet();
bullet.style.left = parseInt(sniper.style.left) + 'px';
bullet.style.bottom = parseInt(sniper.style.bottom) + 'px';
bullet.style.backgroundColor = 'red';
gameWorld.appendChild(bullet);
bullets.push({ element: bullet, direction: -1 });
}
}, 1000);
sniper.setAttribute('data-interval', sniperInterval);
}
// Function to spawn the reflective enemy at the bottom
function spawnReflectiveEnemy() {
// Create the reflective enemy element
var reflectiveEnemy = createElement('reflective-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
// Set initial attributes
reflectiveEnemy.setAttribute('data-health', '10'); // Set initial health or reflection strength
reflectiveEnemy.classList.add('outlined'); // Add CSS class for glowing outline
reflectiveEnemy.dataset.reflectiveHits = 0; // Initialize reflection hits tracking
// Position the reflective enemy at the bottom of the game world
reflectiveEnemy.style.bottom = '0px'; // Set to 0 for the bottom position
// Set up shooting capability
reflectiveEnemy.dataset.lastShotTime = Date.now(); // Track when the last shot was fired
// Append the reflective enemy to the game world
gameWorld.appendChild(reflectiveEnemy);
}
// Function to handle shooting bullets at the player
function shootAtPlayer2(reflectiveEnemy) {
const bullet = document.createElement('div');
bullet.classList.add('enemy-bullet'); // Style class for the bullet
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'red'; // Enemy bullet color
bullet.style.position = 'absolute';
// Position the bullet at the enemy's location
bullet.style.left = `${parseInt(reflectiveEnemy.style.left) + 20}px`; // Center the bullet
bullet.style.bottom = `${parseInt(reflectiveEnemy.style.bottom) + 15}px`; // Above the enemy
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate the player’s position
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
// Calculate direction towards the player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - parseInt(bullet.style.bottom);
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Move the bullet towards the player
const moveBulletInterval = setInterval(() => {
const moveX = (dx / distance) * velocity; // Calculate x movement
const moveY = (dy / distance) * velocity; // Calculate y movement
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check for collision with player
if (checkCollision(player, bullet)) {
reduceLives(); // Damage player
bullet.remove(); // Remove bullet
clearInterval(moveBulletInterval); // Stop movement
}
// Remove bullet if it goes off screen
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBulletInterval); // Stop movement
}
}, 1000 / 60); // Move at 60 FPS
}
// Update reflective enemy behavior to shoot at player on hit
function handleBulletCollisionWithReflectiveEnemy(reflectiveEnemy, bullet) {
// Check if the bullet collides with the reflective enemy
if (checkCollision(reflectiveEnemy, bullet)) {
bullet.remove(); // Remove bullet on collision
reflectiveEnemy.dataset.reflectiveHits++; // Track the number of hits
// Call the shoot function after a certain interval
if (Date.now() - reflectiveEnemy.dataset.lastShotTime >= 1) { // Cooldown for 2 seconds between shots
shootAtPlayer2(reflectiveEnemy);
reflectiveEnemy.dataset.lastShotTime = Date.now(); // Reset the shot time
}
}
}
function spawnNewInteractiveEnemies() {
const randomFeature = Math.random();
if (randomFeature < 0.2) {
spawnTeleporterEnemy();
} else if (randomFeature < 0.6) {
spawnVelocityEnemy();
} else if (randomFeature < 0.8) {
spawnStickyEnemy();
} else {
spawnReflectiveEnemy();
}
}
function moveNewEnemies() {
moveTeleporterEnemies();
moveVelocityEnemies();
moveStickyEnemies();
}
// New enemies with creative features
function spawnTeleporterEnemy() {
const teleporterEnemy = createElement('teleporter-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
teleporterEnemy.style.backgroundColor = '#8A2BE2';
teleporterEnemy.style.bottom = '0px';
teleporterEnemy.setAttribute('data-teleport', '0');
}
function moveTeleporterEnemies() {
enemies.forEach(en => {
if (en.classList.contains('teleporter-enemy')) {
const teleport = parseInt(en.getAttribute('data-teleport'));
if (teleport > 100) {
en.style.left = Math.random() * (gameWorld.offsetWidth - en.offsetWidth) + 'px';
en.setAttribute('data-teleport', '0');
} else {
en.setAttribute('data-teleport', teleport + 1);
}
}
});
}
function moveReflectiveEnemies() {
var enemiesToRemove = []; // Array to track enemies to be removed
enemies.forEach(function(en, index) {
if (en.classList.contains('reflective-enemy')) {
// Move the reflective enemy to the left
en.style.left = (parseInt(en.style.left) - 1) + 'px';
// Check for collision with bullets
bullets.forEach(function(bullet, bulletIndex) {
// If the bullet collides with the reflective enemy
if (checkCollision(en, bullet.element)) {
// Reflect the bullet's direction
bullet.direction *= -1;
// Adjust bullet position slightly to avoid getting stuck
bullet.element.style.left = (parseInt(bullet.element.style.left) + (bullet.direction * 5)) + 'px';
// Increment the reflective hits count
en.dataset.reflectiveHits++;
// Optional: handle when you want to remove the enemy after a number of hits, if desired
if (parseInt(en.dataset.reflectiveHits) >= 10) {
enemiesToRemove.push(index); // Mark for removal
}
// Remove the bullet
bullet.element.remove();
bullets.splice(bulletIndex, 1); // Remove bullet from the bullets array
}
});
// Check for collision with the player
if (checkCollision(player, en)) {
reduceLives(); // Reduce lives if player hits the reflective enemy
enemiesToRemove.push(index); // Mark enemy for removal
}
// Remove if off-screen
if (parseInt(en.style.left) < -en.offsetWidth) {
enemiesToRemove.push(index); // Mark for removal
}
}
});
// Remove enemies marked for removal
enemiesToRemove.reverse().forEach(function(index) {
removeElement(enemies[index], enemies, index, 0);
});
}
function spawnVelocityEnemy() {
const velocityEnemy = createElement('velocity-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
velocityEnemy.style.backgroundColor = '#FF4500';
velocityEnemy.style.bottom = Math.random() * 150 + 'px';
velocityEnemy.setAttribute('data-speed', '2');
}
function moveVelocityEnemies() {
enemies.forEach(en => {
if (en.classList.contains('velocity-enemy')) {
const speed = parseInt(en.getAttribute('data-speed'));
en.style.left = (parseInt(en.style.left) - speed) + 'px';
en.style.bottom = (parseInt(en.style.bottom) + (Math.random() - 0.5) * 10) + 'px';
if (parseInt(en.style.left) < gameScrollPosition - en.offsetWidth) {
removeElement(en, enemies, 0, 30);
} else if (checkCollision(player, en)) {
reduceLives();
removeElement(en, enemies, 0, 0);
}
}
});
}
function spawnVirusDrone() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const virus = document.createElement('div');
virus.classList.add('virus-drone');
virus.style.bottom = '380px'; // top of screen (400px height container minus 20 px)
virus.style.left = leftPos + 'px';
virus.dataset.speed = 8;
virus.dataset.activeEnemy = ''; // enemy it’s currently infecting
gameWorld.appendChild(virus);
viruses.push(virus); // store in separate array to manage separately
}
function spawnStickyEnemy() {
const stickyEnemy = createElement('sticky-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
stickyEnemy.style.backgroundColor = '#FFD700';
stickyEnemy.style.bottom = '0px';
}
function moveStickyEnemies() {
enemies.forEach(en => {
if (en.classList.contains('sticky-enemy')) {
en.style.left = (parseInt(en.style.left) - 1) + 'px';
if (checkCollision(player, en)) {
player.style.backgroundColor = '#FFD700';
setTimeout(() => player.style.backgroundColor = '#4CAF50', 2000);
removeElement(en, enemies, 0, 0);
}
}
});
}
</script>
</body>
</html>Hey! To add a **sandbox mode** GUI panel in your existing HTML/JS game that lets you directly summon enemies, spawn power-ups, or anything you want on the screen, you can follow these steps:
---
## 1. Add a Sandbox Mode Toggle Button and Panel to Your HTML
Insert a fixed overlay panel with buttons that trigger spawning various stuff. This will be your sandbox control panel.
Add near the bottom of the `<body>` (or somewhere suitable inside `#game-container`):
```html
<!-- Sandbox Toggle -->
<button id="sandbox-toggle-btn" style="position: fixed; top: 10px; right: 10px; z-index: 1000; padding: 8px 12px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">
Toggle Sandbox
</button>
<!-- Sandbox Panel -->
<div id="sandbox-panel" style="
position: fixed;
top: 50px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;
">
<h3>Sandbox Controls</h3>
<div>
<strong>Spawn Enemies:</strong><br>
<button class="sandbox-spawn" data-type="enemy" style="margin:4px;">Basic</button>
<button class="sandbox-spawn" data-type="flying-enemy" style="margin:4px;">Flying</button>
<button class="sandbox-spawn" data-type="charging-enemy" style="margin:4px;">Charging</button>
<button class="sandbox-spawn" data-type="split-enemy" style="margin:4px;">Splitter</button>
</div>
<div style="margin-top:10px;">
<strong>Spawn Power-ups:</strong><br>
<button class="sandbox-spawn" data-type="super-speed" style="margin:4px;">Super Speed</button>
<button class="sandbox-spawn" data-type="extra-life" style="margin:4px;">Extra Life</button>
<button class="sandbox-spawn" data-type="invincibility" style="margin:4px;">Invincibility</button>
</div>
<div style="margin-top:10px;">
<button id="sandbox-clear" style="background:#a33; color:#fff; padding:6px 10px; border:none; border-radius:5px;">Clear spawned</button>
</div>
</div>
```
---
## 2. Add JavaScript to Handle Sandbox Panel and Spawning
Add this script **below** your existing scripts or inside your existing `<script>` tag:
```js
// Sandbox toggle button + panel
const sandboxToggleBtn = document.getElementById('sandbox-toggle-btn');
const sandboxPanel = document.getElementById('sandbox-panel');
sandboxToggleBtn.addEventListener('click', () => {
if (sandboxPanel.style.display === 'none') {
sandboxPanel.style.display = 'block';
sandboxToggleBtn.textContent = 'Close Sandbox';
} else {
sandboxPanel.style.display = 'none';
sandboxToggleBtn.textContent = 'Toggle Sandbox';
}
});
// Track all sandbox spawned elements in an array for easy clearing later
const sandboxSpawnedElements = [];
// Function to spawn enemy or power-up at a random position in front of the player
function sandboxSpawn(type) {
const spawnX = playerPositionX + gameContainer.offsetWidth / 2 + (Math.random() * 200 - 100);
const spawnY = Math.floor(Math.random() * 150);
if (type === 'enemy') {
const enemy = createElement('enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 'px';
sandboxSpawnedElements.push(enemy);
} else if (type === 'flying-enemy') {
const enemy = createElement('flying-enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 100 + 'px';
sandboxSpawnedElements.push(enemy);
} else if (type === 'charging-enemy') {
const enemy = createElement('charging-enemy', enemies, spawnX);
enemy.style.bottom = '0px';
enemy.setAttribute('data-charge', '0');
enemy.classList.add('charging-prep');
sandboxSpawnedElements.push(enemy);
} else if (type === 'split-enemy') {
const enemy = createElement('split-enemy', enemies, spawnX);
enemy.style.bottom = '0px';
enemy.classList.add('split');
sandboxSpawnedElements.push(enemy);
} else if (allPowerUpTypes.includes(type)) {
const powerUp = createElement('power-up', powerUps, spawnX);
powerUp.setAttribute('data-type', type);
powerUp.style.backgroundColor = getPowerUpColor(type);
powerUp.style.bottom = spawnY + 'px';
sandboxSpawnedElements.push(powerUp);
} else {
console.warn('Unknown sandbox spawn type:', type);
}
}
// Setup sandbox spawn buttons
document.querySelectorAll('.sandbox-spawn').forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.getAttribute('data-type');
sandboxSpawn(type);
});
});
// Clear all sandbox spawned elements
document.getElementById('sandbox-clear').addEventListener('click', () => {
sandboxSpawnedElements.forEach(el => {
// Clean up from their respective arrays
if (enemies.includes(el)) {
enemies.splice(enemies.indexOf(el), 1);
}
if (powerUps.includes(el)) {
powerUps.splice(powerUps.indexOf(el), 1);
}
el.remove();
});
sandboxSpawnedElements.length = 0;
});
```
---
## 3. How It Works
- The **"Toggle Sandbox"** button shows/hides a panel on the right side.
- The panel has buttons to spawn enemies or power-ups instantly in front of the player at random vertical height.
- Spawned elements are tracked so you can clear them all with the "Clear spawned" button.
- It's a simple overlay on top of your existing game without disrupting gameplay.
---
## 4. Optional Enhancements
- Add more enemy/power-up types by adding buttons and extending the `sandboxSpawn` function.
- Add input fields within the panel to specify precise spawn coordinates or properties.
- Style the sandbox panel better or reposition.
- Add cooldown or limits if needed to prevent flooding the screen.
---
By integrating this, you get an on-screen **developer sandbox GUI** that lets you **instantly summon your existing game objects at will** to test powers, enemy combos, or special scenarios.
---
If you'd want, I can help you generate a full minimal example snippet or integrate it deeper into your code! Just let me know.6dcb19f4a054ac73c773aed3no just make ti so when u click sandbox button on game options it brings u to level 1 but does not spawn any enemies but u and the gui
Got it! You want the **Sandbox Mode** button inside your existing **game mode options** panel to:
- Start at **Level 1**,
- Spawn only the **player** (no enemies or obstacles),
- Show a GUI panel on screen that lets you manually summon enemies/power-ups,
- Keep existing functionality intact for other modes.
---
Here's how you can **add that easily** by:
1. **Adding a "Sandbox Mode" button** inside your existing `#game-mode-options` div,
2. **Creating a sandbox GUI panel** (like in the previous answer) that only shows in sandbox mode,
3. **Changing your start/reset logic** to support sandbox (no auto spawns),
4. **Hooking the Sandbox button to setup level and show GUI**.
---
### Step 1: Add Sandbox Mode button in your existing `#game-mode-options`
Modify your HTML for `#game-mode-options`:
```html
<div id="game-mode-options" style="display: none;">
<button onclick="startGamee()" class="button">Story Mode</button>
<button id="endless-mode-btn" onclick="showLevelOptions()" class="button" style="display: none;">Endless Mode</button>
<button onclick="startSandboxMode()" class="button" style="background:#c39; margin-top:10px;">Sandbox Mode</button>
</div>
```
---
### Step 2: Add Sandbox GUI panel HTML (inside `#game-container` or body)
Add this **below your existing controls** but inside or near `#game-container`:
```html
<div id="sandbox-panel" style="
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;
">
<h3>Sandbox Controls</h3>
<div>
<button onclick="sandboxSpawn('enemy')" style="margin:4px;">Spawn Basic Enemy</button><br>
<button onclick="sandboxSpawn('flying-enemy')" style="margin:4px;">Spawn Flying Enemy</button><br>
<button onclick="sandboxSpawn('super-speed')" style="margin:4px;">Spawn Super Speed Power-up</button><br>
<button onclick="sandboxSpawn('extra-life')" style="margin:4px;">Spawn Extra Life</button><br>
<button onclick="sandboxClear()" style="background:#a33; color:#fff; margin-top:10px;">Clear Spawned</button>
</div>
</div>
```
---
### Step 3: Add JavaScript code for Sandbox Mode
Put this inside your existing `<script>` tag or at the bottom before `</body>`:
```js
let isSandboxMode = false;
const sandboxSpawned = [];
function startSandboxMode() {
isSandboxMode = true;
// Hide mode options, upgrades, etc
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
document.getElementById('sandbox-panel').style.display = 'block';
// Clear all enemies/obstacles/powerUps/etc
clearAllElements();
// Reset scores, lives, etc for sandbox
score = 0;
lives = 3;
level = 1;
updateDisplay();
// Reset player position and show player
playerPositionX = 50;
playerPositionY = 0;
playerVelocityX = 0;
playerVelocityY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
// Start game loop for sandbox (no auto spawn)
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function sandboxSpawn(type) {
const spawnX = playerPositionX + 200 + Math.random() * 100;
const spawnY = Math.random() * 150;
if (type === 'enemy') {
const enemy = createElement('enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 'px';
sandboxSpawned.push(enemy);
} else if (type === 'flying-enemy') {
const enemy = createElement('flying-enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 100 + 'px';
sandboxSpawned.push(enemy);
} else if (allPowerUpTypes.includes(type)) {
const powerUp = createElement('power-up', powerUps, spawnX);
powerUp.setAttribute('data-type', type);
powerUp.style.backgroundColor = getPowerUpColor(type);
powerUp.style.bottom = spawnY + 'px';
sandboxSpawned.push(powerUp);
} else {
console.warn('Unknown spawn type: ' + type);
}
}
function sandboxClear() {
sandboxSpawned.forEach(el => {
// Remove from arrays
if (enemies.includes(el)) enemies.splice(enemies.indexOf(el), 1);
if (powerUps.includes(el)) powerUps.splice(powerUps.indexOf(el), 1);
el.remove();
});
sandboxSpawned.length = 0;
}
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
if (!isSandboxMode) {
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'endless') {
spawnEndlessEntities();
switchLevelBackground();
} else {
spawnEntities();
}
} // else sandbox mode disables auto spawn
moveElements();
handlePlayerEnemyCollision();
updateShopScoreDisplay();
checkScoreForLevelTransition();
updateDisplay();
}
}
```
---
### Step 4: Adjust your existing `startGame` minimizing enemy spawn when in sandbox
You can leave your existing `startGame()` as is; the sandbox mode disables auto-spawn in the main loop with the `isSandboxMode` flag.
---
### Summary:
- When you click **Sandbox Mode** button in game options, it:
- Sets `isSandboxMode = true`
- Resets level to 1, player only, no enemies auto spawning
- Shows sandbox GUI panel to *manually spawn* enemies or power-ups
- The main `gameLoop` skips any auto spawns in this mode
- Sandbox GUI buttons call `sandboxSpawn(type)` to create enemies or power-ups anywhere near player
- You can clear the sandbox-added ones with "Clear Spawned" button
---
If you want me to add the full combined code snippet or help you integrate it cleanly just tell me! This approach keeps your existing game logic, uses your existing functions, and makes sandbox mode a separate controlled state.
---
### Bonus: You can also ensure sandbox doesn’t interfere with upgrades or story UI
Add this inside `startSandboxMode()`:
```js
document.getElementById('upgrades-menu').style.display = 'none';
document.getElementById('cutscene').style.display = 'none';
document.getElementById('level-intro').style.display = 'none';
document.getElementById('level-intro2').style.display = 'none';
document.getElementById('boss-fight').style.display = 'none';
document.getElementById('game-over').style.display = 'none';
```
---
**Let me know if you want me to help wiring spawning from sandbox GUI any further!**ff1def3dc063a60fea62cceamake in game loop a new game mode sandbox level is on level 1 make it not spawn any enemies so don't add it there
Sure! To handle a new game mode called **sandbox** at level 1—and not spawn any enemies during the game loop—you just need to add a check in the `gameLoop()` function that skips enemy (and other) auto-spawning when in sandbox mode.
Assuming you add a global variable like:
```js
let isSandboxMode = false; // Set to true when sandbox starts
```
You can then update the `gameLoop()` function to:
```js
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
// Check current game mode and skip spawning if sandbox
if (!isSandboxMode) {
const gameMode = localStorage.getItem('gameMode'); // Or use some variable to track mode
if (gameMode === 'endless') {
spawnEndlessEntities();
switchLevelBackground();
} else {
spawnEntities();
}
}
// For sandbox mode: level fixed at 1, no enemies spawned.
moveElements();
handlePlayerEnemyCollision();
updateShopScoreDisplay();
checkScoreForLevelTransition();
updateDisplay();
}
}
```
---
### How to use `isSandboxMode` for your sandbox:
When starting Sandbox mode (e.g., via a button):
```js
function startSandboxMode() {
isSandboxMode = true;
// Set level to 1 explicitly if needed
level = 1;
localStorage.setItem('gameMode', 'sandbox'); // Optionally
// Hide menus and clear enemies
document.getElementById('game-mode-options').style.display = 'none';
clearAllElements();
// Reset player position and show player
playerPositionX = 50;
playerPositionY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
updateDisplay();
// Show sandbox GUI panel here if you want...
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
```
---
### Summary
- Add `isSandboxMode` global flag
- In `gameLoop()`, **skip all enemy and power-up spawning if `isSandboxMode === true`**
- Set `level = 1` when entering sandbox mode
- Keep the rest of the game running so player can move/shoot/spawn manually via GUI
---
If you want me to write the full modified `gameLoop()` or `startSandboxMode()` functions adapted exactly to your code, just ask!0adeb12eedd73f6143866de4NO BRO ADD A NEW GAMEMODE called sandbox bro if GAMEMODE === sandbox then don't spawn any enemies or nothing
Gotcha! You want a **brand new game mode named "sandbox"**, and when:
```js
localStorage.getItem('gameMode') === 'sandbox'
```
then **NO enemies or obstacles spawn** during the game loop. Here's exactly how to do that in your existing `gameLoop()`:
---
### Modify your existing `gameLoop` function like this:
```js
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'endless') {
spawnEndlessEntities();
switchLevelBackground();
} else if (gameMode === 'story') {
spawnEntities();
} else if (gameMode === 'sandbox') {
// Sandbox mode - NO auto spawning of enemies, obstacles, power-ups, platforms etc.
// Just skip spawning entirely
// Possibly update background or level here if you want
level = 1; // Force level 1 if you want
}
moveElements();
handlePlayerEnemyCollision();
updateShopScoreDisplay();
checkScoreForLevelTransition();
updateDisplay();
}
}
```
---
### How to activate Sandbox mode:
Anywhere you start your game modes, do this for sandbox:
```js
function startSandboxMode() {
localStorage.setItem('gameMode', 'sandbox');
level = 1;
clearAllElements(); // Remove all enemies, obstacles etc.
// Reset player position/velocity if needed
playerPositionX = 50;
playerPositionY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
updateDisplay();
// Hide menus, show sandbox UI if you have one
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('sandbox-panel').style.display = 'block'; // Show sandbox controls
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
```
---
### Add the button in HTML (inside your existing `#game-mode-options`)
```html
<button onclick="startSandboxMode()" class="button" style="background:#c39;">Sandbox Mode</button>
```
---
### Summary:
1. **Add new mode `sandbox`** stored in `localStorage` as usual
2. In the game loop, if mode is `'sandbox'`, **don’t call enemy spawn functions at all**
3. Provide a function `startSandboxMode()` which sets mode and resets game suitably
4. Add UI button wired to `startSandboxMode()` to launch the sandbox
---
That’s the cleanest way to add a separate, no-spawning sandbox mode to your existing codebase exactly as you asked.
---
If you want I can help you generate the exact chunks or integration snippets!aefa1680339fb9fd318c3dfebro it says
SyntaxError: Unexpected token '}'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Adventure Game</title>
<style>
body, html {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
color: #fff;
font-family: Arial, sans-serif;
overflow: hidden;
}
#game-container {
position: relative;
width: 800px;
height: 400px;
background-color: #333;
overflow: hidden;
border: 2px solid #444;
}
#upgrades-menu {
display: none;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
border-radius: 10px;
text-align: center;
}
#upgrade-list {
margin: 20px 0;
}
.upgrade-option {
margin: 10px 0;
font-size: 18px;
}
#start-menu, #game-mode-options, #level-options {
display: none; /* Initially hidden */
text-align: center; /* Center text for buttons */
position: absolute; /* Positioned relative to parent */
top: 50%; /* Start middle of vertical view */
left: 50%; /* Start middle of horizontal view */
transform: translate(-50%, -50%); /* Center it */
z-index: 10; /* Bring to the front */
}
.button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #555;
color: #fff;
border: none;
border-radius: 5px;
}
.navigation-button {
margin: 5px;
}
.split-enemy.split {
border-radius: 0 !important;
}
.boost-drainer-chain {
position: absolute;
border-top: 2px dotted cyan;
pointer-events: none;
z-index: 1000;
height: 0; /* Since it's a horizontal line rotated, height is zero */
}
.boost-drainer {
position: absolute;
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid cyan;
background-color: green; /* start at green */
transition: background-color 0.5s;
z-index: 20;
}
.boost-drainer.green { background-color: green !important; }
.boost-drainer.yellow { background-color: yellow !important; }
.boost-drainer.red { background-color: red !important; }
.boost-laser {
position: absolute;
width: 5px;
height: 120px; /* height of laser, adjust */
background-color: red;
top: 0; /* will be positioned at bottom 0 */
box-shadow: 0 0 10px red;
z-index: 19;
pointer-events: none; /* no interference with input */
}
.split-enemy:not(.split) {
border-radius: 0 !important; /* Make mini splitters squares */
}
.split-enemy:not(.split) {
border-radius: 0 !important;
}
#game-world {
position: absolute;
width: 10000px; /* bigger world - adjust as you wish */
height: 100%;
transition: transform 0.1s linear;
}
#player {
position: absolute;
bottom: 0;
left: 50px;
width: 30px;
height: 30px;
background-color: #4CAF50;
animation: idle 1s infinite alternate;
}
@keyframes idle {
from { transform: translateY(0); }
to { transform: translateY(2px); }
}
@keyframes charging-prep {
0%, 100% { box-shadow: 0 0 5px rgba(255, 0, 0, 0.6); }
50% { box-shadow: 0 0 15px rgba(255, 0, 0, 1); }
}
@keyframes boss-shake {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(-5px, 0); }
50% { transform: translate(5px, 0); }
75% { transform: translate(-5px, 0); }
}
@keyframes charge-trail {
0% { opacity: 0.8; transform: scale(1); }
100% { opacity: 0; transform: scale(2); }
}
@keyframes boss-shield-glow {
0%, 100% { box-shadow: 0 0 10px red; }
50% { box-shadow: 0 0 20px red; }
}
.character, .boss-door, .power-up, .enemy, .virus-drone, .laser-enemy, .obstacle, .bullet, .burst, .boss, #friend, .coin, .flying-enemy, .charging-enemy, .stealth-enemy, .split-enemy, .boost-drainer, .sniper-enemy, .reflective-enemy, .explosive-enemy, .teleporter-enemy, .mourning-enemy, .velocity-enemy, .sticky-enemy, .reflective-enemy {
position: absolute;
}
.character, .king, .stealth-enemy, .split-enemy, .sniper-enemy, .reflective-enemy, .explosive-enemy,
.teleporter-enemy, .mourning-enemy, .velocity-enemy, .sticky-enemy, .reflective-enemy {
border-radius: 50%;
}
.virus-drone {
position: absolute;
width: 30px;
height: 30px;
background-color: green;
border: 3px solid red;
border-radius: 5px;
z-index: 11;
}
.infected {
background-color: #00cc00 !important; // bright green
filter: drop-shadow(0 0 5px lime);
transition: background-color 0.3s;
}
.infected.shielded::after {
content: '';
position: absolute;
top: -5px; left: -5px;
width: calc(100% + 10px);
height: calc(100% + 10px);
border: 2px solid cyan;
border-radius: 5px;
pointer-events: none;
animation: shieldPulse 2s infinite;
}
@keyframes shieldPulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
.energy-blast {
position: absolute;
width: 200px; /* Diameter of the burst */
height: 200px; /* Diameter of the burst */
border-radius: 50%; /* Make it circular */
border: 2px solid rgba(0, 255, 255, 0.8); /* Color and transparency */
opacity: 0.5; /* Semi-transparent */
animation: pulse 0.5s ease-out; /* Animation for the burst */
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(2); /* Expand the burst for the visual effect */
opacity: 0; /* Fade out */
}
}
.charging-prep {
animation: charging-prep 1s infinite alternate;
}
.outlined {
border: 3px solid green; /* Change as necessary */
box-shadow: 0 0 10px green; /* Add glow effect */
}
.boss-shielded {
animation: boss-shield-glow 0.8s infinite alternate;
border: 3px solid red;
}
.charging-active {
box-shadow: 0 0 15px red, 0 0 30px red;
border: 2px solid red;
}
.charge-trail {
position: absolute;
width: 35px;
height: 35px;
background-color: rgba(255, 50, 50, 0.5);
border-radius: 50%;
opacity: 1;
animation: charge-trail 0.4s ease-out;
}
.bullet-trail {
position: absolute;
width: 8px;
height: 4px;
background-color: rgba(255, 215, 0, 0.5);
opacity: 1;
transition: opacity 0.3s ease-out;
}
.laser-line {
pointer-events: none; /* prevent interfering with input */
z-index: 1000;
border-radius: 2px;
transition: background-color 0.2s ease;
}
.infected.shielded::after {
content: '';
position: absolute;
top: -5px; left: -5px;
width: calc(100% + 10px);
height: calc(100% + 10px);
border: 2px solid cyan;
border-radius: 5px;
pointer-events: none;
animation: shieldPulse 2s infinite;
}
.coin {
width: 15px;
height: 15px;
background-color: #FFD700;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.flying-enemy {
width: 30px;
height: 30px;
background-color: #0099FF;
border-radius: 50%;
opacity: 0.9;
}
.charging-enemy {
width: 40px;
height: 40px;
background-color: #FF5500;
animation: charge 1s linear infinite alternate;
border-radius: 50%;
}
.enemy {
width: 25px;
height: 25px;
background-color: #FF5733;
}
.laser-warning-line {
position: absolute;
width: 6px;
background-color: yellow;
opacity: 0.7;
pointer-events: none;
z-index: 9;
}
.laser-beam {
position: absolute;
width: 8px;
background-color: red;
opacity: 0.9;
pointer-events: none;
z-index: 10;
}
.split-enemy {
width: 35px;
height: 35px;
background-color: #FF5733;
}
.laser-enemy {
width: 35px;
height: 35px;
background-color: #FF5733;
}
.obstacle {
width: 30px;
height: 30px;
background-color: #f44336;
}
.lava {
position: absolute;
width: 100%;
bottom: 0;
background-color: red;
z-index: 5;
height: 0;
transition: height 0.1s linear;
}
.power-up {
width: 20px;
height: 20px;
}
.bullet {
width: 10px;
height: 5px;
background-color: #FFD700;
}
.burst {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 69, 0, 0.7);
animation: burst 0.5s ease-out;
}
@keyframes burst {
from { transform: scale(0.5); opacity: 1; }
to { transform: scale(1.5); opacity: 0; }
}
.boss-door {
width: 30px;
height: 60px;
background-color: #FFFF00;
}
.burst {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 69, 0, 0.7);
animation: burst 0.5s ease-out;
}
.boss {
width: 60px;
height: 60px;
background-color: #FF4500;
}
.reflective-enemy {
width: 25px;
height: 25px;
background-color: #FF5733;
border: 3px solid transparent; /* Initial border */
box-shadow: 0 0 10px green; /* Glow effect */
}
.mourning-enemy {
width: 25px;
height: 25px;
background-color: #FF3333;
}
.sniper-enemy {
width: 25px;
height: 25px;
background-color: #FF3333;
}
#score, #lives, #level, #timer, #shop-score {
position: absolute;
top: 10px;
font-size: 16px;
}
#score { left: 10px; }
#lives { right: 10px; }
#level { left: 150px; }
#timer { left: 250px; }
#shop-score { left: 350px; }
#instructions, #cutscene, #level-intro, #level-intro, #boss-fight, #boss-hallway, #game-over, #interact-message {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
flex-direction: column;
font-size: 18px;
z-index: 10;
}
#instructions button, #cutscene button, #level-intro button, #level-intro2 button, #boss-fight button, #game-over button, #boss-hallway button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #555;
color: #fff;
}
#cutscene, #level-intro, #level-intro2, #boss-fight, #boss-hallway, #game-over, #interact-message {
display: none;
}
#intro {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
flex-direction: row;
font-size: 18px;
z-index: 11;
}
#boss-health-bar {
display: none; /* Initially hidden */
position: absolute;
top: 10px; /* Adjust based on your UI */
left: 50%; /* Center it horizontally or adjust as needed */
transform: translateX(-50%); /* Center alignment */
width: 150px; /* Set fixed maximum width for the health bar */
height: 20px; /* Height of the bar */
background-color: #666; /* Background bar color */
border: 1px solid #333; /* Border style */
border-radius: 5px; /* Rounded corners */
overflow: hidden; /* Prevent overflow */
transition: width 0.5s ease; /* Smooth transition */
}
#boss-health {
height: 100%;
background-color: green; /* Initial health segment color */
border-radius: 5px; /* Rounded corners for inner health */
}
#friend {
width: 30px;
height: 30px;
background-color: #00FF00;
display: none;
}
#power-up-message {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
font-size: 24px;
z-index: 12;
display: none;
}
/* New CSS for cool features */
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.flashing {
animation: flash 0.5s infinite;
}
.glow {
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-world">
<div id="player"></div>
</div>
<div id="upgrades-menu" style="display:none;">
<h2>Upgrades</h2>
<button onclick="previousUpgrade()" class="button">Previous</button>
<div id="upgrade-list"></div>
<button onclick="nextUpgrade()" class="button">Next</button>
<button onclick="buyUpgrade()" class="button">Buy Upgrade</button>
<button onclick="viewEquippedUpgrades()" class="button">View Equipped</button> <!-- New button -->
<button onclick="closeUpgrades()" class="button">Close</button>
<div id="message" style="color: red;"></div>
</div>
<div id="sandbox-panel" style="
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;
">
<h3>Sandbox Controls</h3>
<div>
<button onclick="sandboxSpawn('enemy')" style="margin:4px;">Spawn Basic Enemy</button><br>
<button onclick="sandboxSpawn('flying-enemy')" style="margin:4px;">Spawn Flying Enemy</button><br>
<button onclick="sandboxSpawn('super-speed')" style="margin:4px;">Spawn Super Speed Power-up</button><br>
<button onclick="sandboxSpawn('extra-life')" style="margin:4px;">Spawn Extra Life</button><br>
<button onclick="sandboxClear()" style="background:#a33; color:#fff; margin-top:10px;">Clear Spawned</button>
</div>
</div>
<div id="score">Score: 0</div>
<div id="shop-score">Shop Score: 0</div>
<div id="lives">Lives: 3</div>
<div id="level">Level: 1</div>
<div id="timer">Timer: 0</div>
<div id="instructions">
<p>Press Arrow Up to Jump</p>
<p>Use Arrow Left/Right to Move</p>
<p>Press Space to Shoot</p>
<p>Press Z for Power-Up</p>
<p>Press E to interact</p>
<button onclick="showGameModeOptions()">Start Game</button> <!-- Start Game Button -->
<button onclick="togglePause()">Pause/Resume</button>
<button onclick="openUpgrades()">Open Upgrades</button>
</div>
<div id="cutscene">
<p>Get ready for the next level!</p>
<button onclick="continueGame()">Continue</button>
</div>
<div id="equipped-upgrades" style="display: none;"> <!-- Initially hidden -->
<h3>Equipped Upgrades:</h3>
<!-- Equipped upgrades will be displayed here -->
</div>
<div id="game-mode-options" style="display: none;">
<button onclick="startGamee()" class="button">Story Mode</button>
<button id="endless-mode-btn" onclick="showLevelOptions()" class="button" style="display: none;">Endless Mode</button>
<button onclick="startSandboxMode()" class="button" style="background:#c39;">Sandbox Mode</button>
</div>
<div id="level-options" style="display: none;">
<button onclick="startEndlessMode(1)" class="button" >Level 1</button> <!-- Level 1 Button -->
<button onclick="startEndlessMode(2)" class="button">Level 2</button> <!-- Level 2 Button -->
<button onclick="startEndlessMode(3)" class="button">Level 3</button> <!-- Level 3 Button -->
<button onclick="startEndlessMode(4)" class="button">Level 4</button> <!-- Level 4 Button -->
<button onclick="startEndlessMode(5)" class="button">Level 5</button> <!-- Level 4 Button -->
</div>
</div>
<div id="level-intro">
<p id="level-text">Get Ready for Adventure!</p>
<button onclick="startLevel()">Start Level</button>
</div>
<div id="level-intro2">
<p id="level-text">Get Ready for Adventure!</p>
<button onclick="restartGame()">Start Endless mode</button>
</div>
<div id="boss-fight">
<p>Defeat the Boss!</p>
</div>
<div id="game-over">
<p>Game Over!</p>
<button onclick="restartGame()">Restart</button>
</div>
<div id="boss-hallway">
<p>You have entered the boss hallway! Prepare yourself!</p>
<button onclick="startBossFight()">Proceed</button>
</div>
<div id="boss-health-bar">
<div id="boss-health"></div>
</div>
<div id="power-up-message"></div>
<div id="interact-message"></div>
<div id="friend"></div>
</div>
<script>
const gameContainer = document.getElementById('game-container');
const gameWorld = document.getElementById('game-world');
const player = document.getElementById('player');
const scoreDisplay = document.getElementById('score');
const maxUpgrades = 5; // Maximum number of upgrades you can equip
const livesDisplay = document.getElementById('lives');
const levelDisplay = document.getElementById('level');
const timerDisplay = document.getElementById('timer');
const instructions = document.getElementById('instructions');
const cutscene = document.getElementById('cutscene');
const levelIntro = document.getElementById('level-intro');
const levelText = document.getElementById('level-text2');
const levelIntro2 = document.getElementById('level-intro2');
const levelText2 = document.getElementById('level-text');
const bossFightScreen = document.getElementById('boss-fight');
const gameOverScreen = document.getElementById('game-over');
const intro = document.getElementById('intro');
const dialoguePara = document.querySelector('#dialogue p');
const upgradesPerPage = 1; // Number of upgrades to show per page
const bossHallway = document.getElementById('boss-hallway');
const bossHealthBar = document.getElementById('boss-health-bar');
const bossHealthDisplay = document.getElementById('boss-health');
let currentPage = 0; // Tracks current page
let currentGameMode; // Temporary variable to store the current game mode
let currentEndlessLevel = localStorage.getItem('endlessLevel') ? parseInt(localStorage.getItem('endlessLevel')) : 1; // Endless Mode Level Tracking
let storyModeLevel = localStorage.getItem('storyLevel') ? parseInt(localStorage.getItem('storyLevel')) : 1; // Story Mode Level Tracking
let isEndlessMode = false; // Track if currently in Endless Mode
let bulletSpeed = 5; // Initialize bullet speed
// Other variables...
let phaseStartTime = null; // Track the start time of the boss phase
let playerScore = localStorage.getItem('playerScore') ? parseInt(localStorage.getItem('playerScore')) : 0;
const powerUpMessage = document.getElementById('power-up-message');
let upgradesEquipped = []; // Store equipped upgrades
const viruses = [];
const interactMessage = document.getElementById('interact-message');
const friend = document.getElementById('friend');
const obstacleSpawnDistance = 500; // Distance before the right edge
let playerPositionX = 50;
let upgradesPurchased = [];
let boostDrainer = null;
let boostDrainerLaser = null;
let boostDrainerHP = 3;
let boostDrainerState = "moving"; // "moving", "green", "yellow", "red", "firing", "dead"
let boostDrainerAttackTimeout = null;
const boostDrainerAttackXOffset = 250; // how close to player it moves before attack
const boostDrainerSpeed = 1.0; // pixels per frame
let virusDroneHits = 0; // tracks hits on Virus Drone
let playerPositionY = 0;
let playerVelocityX = 0;
let objectSpawnDistance = 300; // Distance from the right edge to spawn new objects
let playerVelocityY = 0;
let removedEnemies = [];
let phaseThreeStarted = false; // Flag to track if Phase Three has started
let phaseThreeStarted1 = false; // Flag to track if Phase Three has started
let phaseThreeStarted3 = false; // Flag to track if Phase Three has started
let bossDefeatedMessageDisplayed = false; // Flag to track if the defeated message has been displayed
let playerMaxSpeed = 5;
const playerJumpStrength = 12;
let jumpCount = 0;
let maxJumps = 1;
let isDoubleJumpEnabled = false;
let isJumping = false;
let isCycling = false; // Lock to prevent concurrent calls
let isPaused = false;
let canShoot = true;
let waveCooldown = false; // Track if a wave is on cooldown
let bossAttacking = false;
let bossAttackPhase = 0;
let gameInterval;
let obstacles = [];
let enemies = [];
const shownPreMessages = {};
let bullets = [];
let isSandboxMode = false;
const sandboxSpawned = [];
let powerUps = [];
let boostDrainerChain = null;
let isTeleporting = false; // Track whether teleport is available
let platforms = [];
let bossDoors = [];
let maxPlayerPositionX = 50; // Track furthest progress
let coins = [];
let score = 0;
let isInBossDoor = false;
let lives = 3;
let level = localStorage.getItem('playerLevel') ? parseInt(localStorage.getItem('playerLevel')) : 1;
const gravity = 0.5;
const maxLevels = 5;
let movingLeft = false, movingRight = false;
let currentDirection = 1;
let currentPowerUp = null;
let powerUpTimer = 0;
let gameScrollPosition = 0;
let maxGameScrollPosition = 0; // Holds furthest right camera scroll so far
let leftBoundaryX = 0; // Moving left boundary for player position
const bossScoreThreshold = 1;
const dialogues = [
"Player: Hey friend! What a nice day for an adventure!",
"Friend: Indeed! Maybe we should explore the caves.",
"Player: What's that over there? Is it the King?",
"King: Haha, I've found you! You're coming with me!",
"Friend: Player, help me!"
];
let dialogueIndex = 0;
let boss = null;
let bossHealth = 100;
let bossShielded = true;
let bossPowerObtained = false;
let bossFightActive = false;
const allPowerUpTypes = [
"super-speed", "invincibility", "teleportation",
"reflective-shield", "double-shot", "extra-life",
"score-boost", "mega-jump", "energy-blast", "light-speed",
"blazing-trail", "sonic-wave", "armor", "telekinetic",
"camouflage", "dimension-shift", "energy-drain", "mind-control",
"hologram", "fire", "ice", "hypno"
];
window.addEventListener('keydown', handleKeydown);
window.addEventListener('keyup', handleKeyup);
const upgrades = [
{ name: 'Super Speed', price: 4500, effect: 'Increase speed by 10%', id: 'speedBoost' },
{ name: 'Super bullet', price: 1200, effect: 'Faster bullet speed', id: 'doubleDamage' },
{ name: 'Double jump', price: 7500, effect: 'Double jump', id: 'doubleJump' },
{ name: 'Extra Lives', price: 2500, effect: 'Gain 3 extra lives', id: 'ExtraLives' }, // Add this line for Extra Lives
// Add more upgrades as needed
];
function showEndlessModeButton() {
const endlessBtn = document.getElementById('endless-mode-btn');
if (endlessBtn) {
endlessBtn.style.display = 'inline-block';
endlessBtn.disabled = false;
}
}
// Function to select an upgrade
// Function to select the current upgrade
function selectUpgrade(index) {
selectedUpgrade = upgrades[index]; // Select the upgrade at the given index
}
function updateShopScoreDisplay() {
const shopScoreDisplay = document.getElementById('shop-score');
shopScoreDisplay.textContent = `Shop Score: ${playerScore}`; // Display the total amount
}
// Function to display current selected upgrade details
function displaySelectedUpgrade() {
const upgradeInfo = document.getElementById("upgrade-list");
upgradeInfo.innerHTML += `<br><strong>Selected Upgrade:</strong><br>
${selectedUpgrade.name}<br>
Price: ${selectedUpgrade.price}<br>
Effect: ${selectedUpgrade.effect}<br>`;
}
// Declare the selectedUpgrade at the top
let selectedUpgrade = null;
let currentUpgradeIndex = 0; // Keep track of the currently selected upgrade
// Function to open upgrades
function openUpgrades() {
document.getElementById("instructions").style.display = "none"; // Hide main menu
document.getElementById("upgrades-menu").style.display = "block"; // Show upgrades menu
displayUpgradeOptions(); // Display available upgrades
selectUpgrade(currentUpgradeIndex); // Start with first upgrade selected
}
function closeUpgrades() {
document.getElementById("upgrades-menu").style.display = "none"; // Hide upgrades menu
document.getElementById("instructions").style.display = "flex"; // Show main menu again
}
// Function to buy the selected upgrade
// Function to buy the selected upgrade
// Function to buy the selected upgrade
function showGameModeOptions() {
document.getElementById('instructions').style.display = 'none'; // Hide start button
document.getElementById('game-mode-options').style.display = 'block'; // Show game mode options
}
// Function to show level options for Endless Mode
function showLevelOptions() {
document.getElementById('game-mode-options').style.display = 'none'; // Hide game mode options
document.getElementById('level-options').style.display = 'block'; // Show level buttons
}
function startEndlessMode(chosenLevel) {
console.log('Endless Mode Selected for Level:', chosenLevel); // Debug log
document.getElementById('level-options').style.display = 'none'; // Hide level options
// Set the game mode to Endless
localStorage.setItem('gameMode', 'endless'); // This sets the gameMode in local storage
isEndlessMode = true; // Indicate that we are now in endless mode
currentEndlessLevel = chosenLevel; // Set the chosen level for this session
localStorage.setItem('endlessLevel', currentEndlessLevel); // Save the selected level to local storage
resetGame(); // Reset your game state
switchLevelBackground(); // Set the correct background according to currentEndlessLevel
spawnEndlessEntities(); // Spawn enemies based on the current endless level
checkCollisionWithPlatformsAndObstacles();
gameInterval = setInterval(gameLoop, 1000 / 60); // Start the game loop
}
// Ensure the currentEndlessLevel is used in your functions where needed,
// for instance in the game loop or resetGame function if appropriate
window.addEventListener('load', () => {
loadEquippedUpgrades();
const endlessUnlocked = localStorage.getItem('endlessModeUnlocked') === 'true';
const endlessBtn = document.getElementById('endless-mode-btn');
if (endlessUnlocked) {
// Show endless mode button properly
if (endlessBtn) {
endlessBtn.style.display = 'inline-block'; // Show
endlessBtn.disabled = false;
endlessBtn.style.visibility = 'visible';
endlessBtn.removeAttribute('hidden');
}
} else {
// Hide endless mode button initially
if (endlessBtn) {
endlessBtn.style.display = 'none';
}
}
// Reset temp variables - only if you really intend to reset them on load
currentGameMode = null;
currentEndlessLevel = null;
});
// Store purchased upgrades separately
// Function to buy and equip/unequip upgrades
function buyUpgrade() {
const upgrade = upgrades[currentPage]; // Get the currently displayed upgrade
if (!upgrade) {
return displayMessage("Please select an upgrade.");
}
const score = parseInt(localStorage.getItem('playerScore')) || 0;
const upgradeIndex = upgradesEquipped.indexOf(upgrade.id);
const purchasedIndex = upgradesPurchased.indexOf(upgrade.id);
// If the upgrade is already equipped
if (upgradeIndex > -1) {
// Unequip logic
upgradesEquipped.splice(upgradeIndex, 1);
removeUpgradeEffects(upgrade); // Remove effects of the unequipped upgrade
displayMessage(`${upgrade.name} has been unequipped!`);
} else {
// If the upgrade is already purchased but not equipped
if (purchasedIndex > -1) {
// Just equip the upgrade
upgradesEquipped.push(upgrade.id);
applyUpgrade(upgrade); // Apply effects of the newly equipped upgrade
displayMessage(`${upgrade.name} has been equipped!`);
} else {
// Check if the player has enough score to buy the upgrade
if (score < upgrade.price) {
return displayMessage("You need more score to buy this upgrade.");
}
// Deduct the cost and "purchase" the upgrade
localStorage.setItem('playerScore', score - upgrade.price);
upgradesPurchased.push(upgrade.id); // Mark the upgrade as purchased
upgradesEquipped.push(upgrade.id); // Equip it
applyUpgrade(upgrade); // Apply effects of the newly equipped upgrade
displayMessage(`${upgrade.name} has been equipped!`);
}
}
// Save the equipped upgrades and purchased upgrades to local storage
saveUpgrades();
// Refresh equipped display if needed
updateEquippedDisplay();
// Update the button text or state accordingly
updateButtonText(upgrade.id);
}
function loadEquippedUpgrades() {
const savedUpgradesEquipped = localStorage.getItem('upgradesEquipped');
if (savedUpgradesEquipped) {
upgradesEquipped = JSON.parse(savedUpgradesEquipped); // Update contents instead of reassigning with const
upgradesEquipped.forEach(upgradeId => {
const upgrade = upgrades.find(up => up.id === upgradeId);
if (upgrade) {
applyUpgrade(upgrade); // Apply effects of loaded upgrades
}
});
}
const savedUpgradesPurchased = localStorage.getItem('upgradesPurchased');
if (savedUpgradesPurchased) {
upgradesPurchased = JSON.parse(savedUpgradesPurchased); // Update contents
}
}
// Function to save the upgrades to local storage
function saveUpgrades() {
localStorage.setItem('upgradesEquipped', JSON.stringify(upgradesEquipped));
localStorage.setItem('upgradesPurchased', JSON.stringify(upgradesPurchased));
}
// Function to update the button text based on the equipped state
function updateButtonText(upgradeId) {
const upgrade = upgrades.find(up => up.id === upgradeId);
if (upgrade) {
const button = document.getElementById('upgrade-list'); // Assume the button has this ID
const isEquipped = upgradesEquipped.indexOf(upgradeId) > -1;
button.textContent = isEquipped ? "Unequip" : "Buy Upgrade";
}
}
// Function to remove effects when unequipping an upgrade
function removeUpgradeEffects(upgrade) {
switch (upgrade.id) {
case 'speedBoost':
playerMaxSpeed -= 1.5; // Revert speed increase
break;
case 'doubleDamage':
bulletSpeed -= 7;
break;
case 'doubleJump':
maxJumps = 1
isDoubleJumpEnabled = false; // Enable double jump
console.log('Double Jump Activated!');
break;
case 'ExtraLives': // New case for Extra Lives upgrade
removeLife();
console.log('Extra lives granted! Total lives:', lives); // Log the new lives total
break;
// Add additional cases for removing effects of new upgrades here
}
}
function startGamee() {
localStorage.setItem('gameMode', 'story'); // Set game mode to endless
startGame();
}
// Function to apply effects of an upgrade
function applyUpgrade(upgrade) {
switch (upgrade.id) {
case 'speedBoost':
playerMaxSpeed += 2; // Increase speed
break;
case 'doubleDamage':
bulletSpeed += 7;
break;
case 'doubleJump':
maxJumps = 2; // Allow an additional jump
isDoubleJumpEnabled = true; // Enable double jump
console.log('Double Jump Activated! You can now jump again mid-air.');
break;
case 'ExtraLives': // New case for Extra Lives upgrade
gainExtraLife2();
console.log('Extra lives granted! Total lives:', lives); // Log the new lives total
break;
// Add additional cases for new upgrades here
}
}
// Function to view equipped upgrades
function viewEquippedUpgrades() {
const equippedList = document.getElementById("equipped-upgrades");
// Toggle display of the equipped upgrades
if (equippedList.style.display === "none") {
equippedList.style.display = "block"; // Show the equipped upgrades
updateEquippedDisplay(); // Update the display with currently equipped upgrades
} else {
equippedList.style.display = "none"; // Hide the equipped upgrades
}
}
// Function to update the equipped display
function viewEquippedUpgrades() {
// Open a new blank tab
const equippedWindow = window.open("", "_blank");
// Create the HTML content for the equipped upgrades
let equippedContent = '<html><head><title>Equipped Upgrades</title></head><body>';
equippedContent += '<h3>Equipped Upgrades:</h3>'; // Add header
if (upgradesEquipped.length === 0) {
equippedContent += "<p>No upgrades equipped.</p>"; // Message when no upgrades are equipped
} else {
upgradesEquipped.forEach(id => {
const upgrade = upgrades.find(upg => upg.id === id);
if (upgrade) {
equippedContent += `
<div>
<strong>${upgrade.name}</strong><br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
</div>
<hr>
`;
}
});
}
equippedContent += '</body></html>'; // Close HTML tags
// Write the content to the new window
equippedWindow.document.write(equippedContent);
equippedWindow.document.close(); // Close the document for rendering
}
function updateEquippedDisplay() {
const equippedList = document.getElementById("equipped-upgrades");
equippedList.innerHTML = ""; // Clear the current display
if (upgradesEquipped.length === 0) {
equippedList.innerHTML = "<p>No upgrades equipped.</p>"; // Handle the case where no upgrades are equipped
} else {
upgradesEquipped.forEach(id => {
const upgrade = upgrades.find(upg => upg.id === id);
if (upgrade) {
equippedList.innerHTML += `
<div>
<strong>${upgrade.name}</strong><br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
</div>
<hr>
`;
}
});
}
}
if (upgradesEquipped.includes(selectedUpgrade.id)) {
// Unequip logic
const index = upgradesEquipped.indexOf(selectedUpgrade.id);
if (index > -1) upgradesEquipped.splice(index, 1);
displayMessage(`${selectedUpgrade.name} is now unequipped!`);
} else {
// Equip logic
localStorage.setItem('playerScore', score - selectedUpgrade.price);
upgradesEquipped.push(selectedUpgrade.id);
displayMessage(`${selectedUpgrade.name} has been equipped!`);
}
// Refresh equipped display
updateEquippedDisplay(); // Call it here to update the UI
function updateNavigationButtons(totalUpgrades) {
const prevBtn = document.querySelector('#upgrades-menu button[onclick="previousUpgrade()"]');
const nextBtn = document.querySelector('#upgrades-menu button[onclick="nextUpgrade()"]');
prevBtn.disabled = currentPage === 0; // Disable if on the first page
nextBtn.disabled = (currentPage + 1) * upgradesPerPage >= totalUpgrades; // Disable if on the last page
}
// Function to display messages to the player
function displayMessage(text, seconds = 3) {
bossMessageElement.textContent = text; // example
bossMessageElement.style.display = "block";
setTimeout(() => {
bossMessageElement.style.display = "none";
}, seconds * 1000);
}
function nextUpgrade() {
if (currentPage < upgrades.length - 1) {
currentPage++;
displayUpgradeOptions();
}
}
function previousUpgrade() {
if (currentPage > 0) {
currentPage--;
displayUpgradeOptions();
}
}
function displayUpgradeOptions() {
const upgradeList = document.getElementById("upgrade-list");
const upgrade = upgrades[currentPage];
const equipped = upgradesEquipped.includes(upgrade.id) ? " (Equipped)" : ""; // Check if equipped
upgradeList.innerHTML = `
<strong>${upgrade.name}</strong>${equipped}<br>
Price: ${upgrade.price}<br>
Effect: ${upgrade.effect}<br>
`;
// Update navigation buttons
updateNavigationButtons();
}
function handleKeydown(e) {
switch (e.key) {
case 'ArrowLeft':
movingLeft = true;
currentDirection = -1;
break;
case 'ArrowRight':
movingRight = true;
currentDirection = 1;
break;
case 'ArrowUp':
handleJump();
break;
case ' ': // Space bar for shooting
if (canShoot) {
shootBullet(currentDirection);
canShoot = false;
setTimeout(() => canShoot = true, 300);
}
break;
case 'z':
case 'Z': // Power-Up activation
if (currentPowerUp) { // Check if there is a power-up to use
usePowerUp();
} else if (bossPowerObtained) { // Charge if the boss power is obtained and no power-up is active
isCharging = true;
chargeTime = 0;
}
break;
case 'e':
case 'E':
interact(); // Trigger the interact function when 'E' is pressed
break;
}
}
function handleKeyup(e) {
switch (e.key) {
case 'ArrowLeft':
movingLeft = false;
break;
case 'ArrowRight':
movingRight = false;
break;
case 'z':
case 'Z':
if (bossPowerObtained && isCharging) {
isCharging = false;
shootLaserBlast(chargeTime);
}
break;
}
}
function handleJump() {
if (playerPositionY === 0) { // If the player is on the ground, perform a regular jump
playerVelocityY = playerJumpStrength; // Apply jump force
jumpCount = 1; // Set jump count to indicate first jump
isJumping = true; // Set jumping status to true
} else if (jumpCount < maxJumps) { // Allow double jump if jump count allows
doubleJump(); // Call double jump function
}
}
function nextDialogue() {
if (dialogueIndex < dialogues.length - 1) {
dialogueIndex++;
dialoguePara.textContent = dialogues[dialogueIndex];
} else {
intro.style.display = 'none';
startGame();
}
}
function startSandboxMode() {
isSandboxMode = true;
// Hide mode options, upgrades, etc
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
document.getElementById('sandbox-panel').style.display = 'block';
// Clear all enemies/obstacles/powerUps/etc
clearAllElements();
// Reset scores, lives, etc for sandbox
score = 0;
lives = 3;
level = 1;
updateDisplay();
// Reset player position and show player
playerPositionX = 50;
playerPositionY = 0;
playerVelocityX = 0;
playerVelocityY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
// Start game loop for sandbox (no auto spawn)
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function sandboxSpawn(type) {
const spawnX = playerPositionX + 200 + Math.random() * 100;
const spawnY = Math.random() * 150;
if (type === 'enemy') {
const enemy = createElement('enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 'px';
sandboxSpawned.push(enemy);
} else if (type === 'flying-enemy') {
const enemy = createElement('flying-enemy', enemies, spawnX);
enemy.style.bottom = spawnY + 100 + 'px';
sandboxSpawned.push(enemy);
} else if (allPowerUpTypes.includes(type)) {
const powerUp = createElement('power-up', powerUps, spawnX);
powerUp.setAttribute('data-type', type);
powerUp.style.backgroundColor = getPowerUpColor(type);
powerUp.style.bottom = spawnY + 'px';
sandboxSpawned.push(powerUp);
} else {
console.warn('Unknown spawn type: ' + type);
}
}
function sandboxClear() {
sandboxSpawned.forEach(el => {
// Remove from arrays
if (enemies.includes(el)) enemies.splice(enemies.indexOf(el), 1);
if (powerUps.includes(el)) powerUps.splice(powerUps.indexOf(el), 1);
el.remove();
});
sandboxSpawned.length = 0;
}
function doubleJump() {
if (jumpCount < maxJumps) { // Check if the player can still jump
playerVelocityY = playerJumpStrength; // Apply jump force
jumpCount++; // Increment the jump count
isJumping = true; // Set jumping status to true
}
}
function shootBullet(direction) {
const bullet = createBullet(direction);
bullets.push({ element: bullet, direction });
gameWorld.appendChild(bullet);
if (currentPowerUp === 'double-shot') {
const altBullet = createBullet(direction + 1);
bullets.push({ element: altBullet, direction });
gameWorld.appendChild(altBullet);
}
if (currentPowerUp === 'speed-bullet') {
const sideBullet1 = createBullet(direction + 6);
const sideBullet2 = createBullet(direction - 6);
bullets.push({ element: sideBullet1, direction });
bullets.push({ element: sideBullet2, direction });
gameWorld.appendChild(sideBullet1);
gameWorld.appendChild(sideBullet2);
}
}
function createBurst(x, y) {
const burst = document.createElement('div');
burst.classList.add('burst');
burst.style.left = x + 'px';
burst.style.bottom = y + 'px';
gameWorld.appendChild(burst);
setTimeout(() => burst.remove(), 500);
}
function moveCamera() {
if (!isInBossDoor) {
let proposedScroll = Math.max(0, playerPositionX - (gameContainer.offsetWidth / 2));
if (proposedScroll > maxGameScrollPosition) {
maxGameScrollPosition = proposedScroll;
}
gameScrollPosition = maxGameScrollPosition;
} else {
// Inside boss door, camera follows player freely (no scrolling restrictions)
gameScrollPosition = Math.max(0, playerPositionX - (gameContainer.offsetWidth / 2));
}
gameWorld.style.transform = `translateX(${-gameScrollPosition}px)`;
}
// Function to reset player and possibly generate more obstacles
function resetPlayerPosition() {
playerPositionX = 50; // Reset to starting position
player.style.left = playerPositionX + 'px'; // Ensure the player is positioned correctly
// Optionally, regenerate obstacles or enemies here
regenerateEntities(); // Call a function that generates new obstacles and/or enemies
}
// Function to regenerate entities
function regenerateEntities() {
// Logic to create and append new obstacles/enemies depending on your game mechanics
for (let i = 0; i < 5; i++) { // Change the number to spawn desired quantity
spawnObstacle(); // or spawnEnemy(); as needed
}
}
let summonCooldown = 0;
function summonAllies() {
for (let i = 0; i < 3; i++) {
let ally = createElement('ally', [], parseInt(player.style.left) + (i * 30));
ally.style.bottom = player.style.bottom;
gameWorld.appendChild(ally);
let destroyCount = 0;
let moveInterval = setInterval(() => {
ally.style.left = (parseInt(ally.style.left) + 5) + 'px';
enemies.forEach((enemy, index) => {
if (checkCollision(ally, enemy) && destroyCount < 3) {
enemy.remove();
enemies.splice(index, 1);
destroyCount++;
}
});
if (destroyCount >= 3) {
ally.remove();
clearInterval(moveInterval);
}
}, 50);
setTimeout(() => {
ally.remove();
clearInterval(moveInterval);
}, 5000);
}
}
function createBulletTrail(x, y) {
const trail = document.createElement('div');
trail.classList.add('bullet-trail');
trail.style.left = x + 'px';
trail.style.bottom = y + 'px';
gameWorld.appendChild(trail);
setTimeout(() => {
trail.style.opacity = '0';
setTimeout(() => trail.remove(), 300);
}, 50);
}
function moveBullets() {
bullets.forEach((bulletObj, index) => {
const bullet = bulletObj.element;
const bulletDirection = bulletObj.direction;
// Calculate new position
const newX = parseInt(bullet.style.left) + (bulletDirection * bulletSpeed);
bullet.style.left = newX + 'px';
createBulletTrail(newX, parseInt(bullet.style.bottom));
// Remove if out of bounds
if (newX > gameScrollPosition + gameContainer.offsetWidth || newX < 0) {
bullet.remove();
bullets.splice(index, 1);
} else {
// Check collisions with enemies
for (let enemyIndex = 0; enemyIndex < enemies.length; enemyIndex++) {
const enemy = enemies[enemyIndex];
if (checkCollision(bullet, enemy)) {
if (enemy.classList.contains('reflective-enemy')) {
// Handle reflective enemy bullet collision
handleBulletCollisionWithReflectiveEnemy(enemy, bullet);
bullet.remove();
bullets.splice(index, 1);
break;
} else if (enemy.classList.contains('boost-drainer')) {
// Handle boost drainer bullet collision
if (handleBoostDrainerHit(bulletObj, index)) {
// Bullet handled and removed inside handler
break;
}
} else {
// Handle other enemy hits
handleEnemyHit(enemy, enemyIndex);
createBurst(newX, parseInt(bullet.style.bottom));
bullet.remove();
bullets.splice(index, 1);
break;
}
}
}
checkBossHit();
}
});
}
function handleEnemyHit(enemy, enemyIndex) {
if (enemy.classList.contains('split-enemy')) {
handleSplitterEnemyHit(enemy, enemyIndex);
} else if (enemy.classList.contains('infected')) {
// Existing infected enemy logic...
let shieldHitsLeft = parseInt(enemy.dataset.shieldHitsLeft) || 0;
if (shieldHitsLeft > 0) {
shieldHitsLeft--;
enemy.dataset.shieldHitsLeft = shieldHitsLeft.toString();
if (shieldHitsLeft === 0) enemy.classList.remove('shielded');
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
return;
}
let health = parseInt(enemy.dataset.health) || 1;
health--;
if (health <= 0) {
enemy.remove();
enemies.splice(enemyIndex, 1);
} else {
enemy.dataset.health = health.toString();
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
} else {
// Normal enemy - destroy immediately
enemy.remove();
enemies.splice(enemyIndex, 1);
}
score += 10;
playerScore += 50;
localStorage.setItem('playerScore', playerScore);
updateDisplay();
}
function startGame() {
// Hide game mode options and other screens
document.getElementById("game-mode-options").style.display = "none";
gameOverScreen.style.display = 'none';
instructions.style.display = 'none';
// Retrieve the game mode from local storage
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'story') {
// Logic for starting Story Mode
resetGame(false); // Continue from current Story Mode level
levelIntro.style.display = 'flex'; // Show level intro for Story Mode
} else if (gameMode === 'endless') {
// Logic for starting Endless Mode
if (!localStorage.getItem('endlessLevel')) {
localStorage.setItem('endlessLevel', '1'); // Start Endless level at 1 if not set
}
resetGame(false); // Continue from last saved Endless level
levelIntro.style.display = 'flex'; // Show level intro for Endless Mode
} else {
// Default to Story Mode if no mode is set
localStorage.setItem('gameMode', 'story'); // Default to Story Mode
localStorage.setItem('storyLevel', '2'); // Set initial level for Story Mode
resetGame(true); // Start a new game
levelIntro.style.display = 'flex'; // Show level intro screen
}
}
function startLevel() {
levelIntro.style.display = 'none';
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function resetGame(isNewGame = false) {
lives = upgradesEquipped.includes('ExtraLives') ? 6 : 3; // Set lives based on the Extra Lives upgrade
// Reset player state variables
playerPositionX = 50;
playerPositionY = 0;
playerVelocityX = 0;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
isDoubleJumpEnabled = false;
gameScrollPosition = 0;
clearAllElements(); // Clear all game entities
// Reset the level based on the game mode.
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'endless') {
level = localStorage.getItem('endlessLevel')
? parseInt(localStorage.getItem('endlessLevel')) // Retrieve endless level
: 1; // Start at Level 1 if none found
} else {
level = localStorage.getItem('storyLevel')
? parseInt(localStorage.getItem('storyLevel'))
: 1; // Default for story mode
}
switchLevelBackground(); // Set the background according to the current level
updateDisplay(); // Reflect updates on the UI
}
function updateDisplay() {
if (isEndlessMode) {
// For Endless Mode
levelDisplay.textContent = `Level: ${currentEndlessLevel}`;
} else {
// For Story Mode
levelDisplay.textContent = `Level: ${storyModeLevel}`;
}
scoreDisplay.textContent = `Score: ${score}`;
livesDisplay.textContent = `Lives: ${lives}`;
timerDisplay.textContent = `Timer: ${powerUpTimer}`;
}
function updatePlayerPosition() {
// Calculate velocity based on input
if (movingRight) {
playerVelocityX = playerMaxSpeed;
} else if (movingLeft) {
playerVelocityX = -playerMaxSpeed;
} else {
playerVelocityX = 0;
}
// Tentative new position
let newPlayerX = playerPositionX + playerVelocityX;
if (!isInBossDoor) {
// Moving border and player left clamp applied only outside boss door/fight
let proposedScroll = Math.max(0, newPlayerX - (gameContainer.offsetWidth / 2));
if (proposedScroll > leftBoundaryX) {
leftBoundaryX = proposedScroll;
}
if (newPlayerX < leftBoundaryX) {
// Clamp player so they cannot move backward beyond moving border
newPlayerX = leftBoundaryX;
}
}
// Inside boss door, no restrictions on player X movement
playerPositionX = newPlayerX;
player.style.left = playerPositionX + 'px';
// Jump/gravity logic stays the same...
let onPlatform = checkCollisionWithPlatformsAndObstacles();
platforms.forEach(platform => {
if (checkCollision(player, platform) && playerVelocityY < 0) {
playerPositionY = parseInt(platform.style.bottom) + platform.offsetHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
}
});
if (!onPlatform || isJumping) {
playerVelocityY -= gravity;
playerPositionY += playerVelocityY;
if (playerPositionY <= 0) {
playerPositionY = 0;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
}
} else {
playerVelocityY = 0;
}
player.style.bottom = playerPositionY + 'px';
// Move camera (handles boss door differently)
moveCamera();
}
function spawnEntities() {
if (!isPaused && !bossFightActive) {
if (Math.random() < 0.003) spawnObstacle();
// Spawn enemies based on the current level
if (level === 1) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy();
if (Math.random() < 0.002) spawnObstacle();
} else if (level === 2) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnChargingEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
} else if (level === 3) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnFlyingEnemy(); // Flying enemy option for Level 3
if (Math.random() < 0.001) spawnReflectiveEnemy(); // Reflective enemy option for Level 3
} else if (level === 4) {
if (Math.random() < 0.001) spawnLaserEnemy();
if (Math.random() < 0.002) spawnSplitterEnemy();
} else if (level === 5) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.002) spawnVirusDrone();
if (Math.random() < 0.001) spawnSplitterEnemy();
}
if (Math.random() < 0.002 && !currentPowerUp) spawnPowerUp();
if (Math.random() < 0.001) spawnPlatform();
if (Math.random() < 0.003) spawnCoin();
// Manage boss door spawning only if in Story Mode
if (localStorage.getItem('gameMode') === 'story') {
if (bossDoors.length === 0 && score >= level * bossScoreThreshold && enemies.length === 0) {
spawnBossDoor();
}
}
}
}
function cleanUpObjects() {
// Remove obstacles that are too far to the left
obstacles = obstacles.filter(obstacle => {
const position = parseInt(obstacle.style.left);
if (position < -obstacle.offsetWidth) {
obstacle.remove(); // Remove from the DOM
return false; // Do not keep this object in the active array
}
return true; // Keep this object in the active array
});
// Similarly for enemies
enemies = enemies.filter(enemy => {
const position = parseInt(enemy.style.left);
if (position < -enemy.offsetWidth) {
enemy.remove(); // Remove from the DOM
return false; // Do not keep this object in the active array
}
return true; // Keep this object in the active array
});
}
function moveElements() {
moveArray(obstacles, 2, 15);
moveArray(enemies, 1, 30);
moveArray(coins, 2, 0);
moveArray(powerUps, 2, 0, true);
moveArray(platforms, 1, 0);
moveFlyingEnemies();
moveSniperEnemies();
moveLaserEnemies();
moveReflectiveEnemies();
moveSplitterEnemies();
moveChargingEnemies();
moveStealthEnemies();
moveVirusDrones();
moveBullets();
moveNewEnemies();
moveBoostDrainer()
}
// Spawn a Laser Enemy (like a sniper — fixed x, y)
function spawnLaserEnemy() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const laserEnemy = createElement('laser-enemy', enemies, leftPos);
laserEnemy.style.bottom = '300px';
laserEnemy.style.width = '30px';
laserEnemy.style.height = '30px';
laserEnemy.style.backgroundColor = 'purple';
laserEnemy.dataset.laserState = 'warning';
laserEnemy.dataset.lastDamageTime = '0';
laserEnemy._warningLine = null;
laserEnemy._laserBeam = null;
// createElement already does append and enemies.push
}
// Call every frame in moveEnemies or gameLoop
function moveLaserEnemies() {
enemies.forEach(enemy => {
if (!enemy.classList.contains('laser-enemy')) return;
if (enemy.dataset.laserState === 'warning') {
if (!enemy._warningLine) {
createWarningLine(enemy);
setTimeout(() => {
enemy.dataset.laserState = 'active';
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
if (enemy._warningLineUpdateInterval) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
}
activateLaserBeam(enemy);
}, 1000);
}
// ⚠️ WARNING LINE DOES NOT DAMAGE PLAYER - NO COLLISION CHECK!
} else if (enemy.dataset.laserState === 'active') {
const lastDamageTime = parseInt(enemy.dataset.lastDamageTime) || 0;
const now = Date.now();
if (enemy._laserBeam && checkCollision(enemy._laserBeam, player)) {
if (now - lastDamageTime > 1000) { // damage cooldown 1s
reduceLives();
enemy.dataset.lastDamageTime = now.toString();
}
}
}
});
}
function spawnObstacle() {
const obstacle = createElement('obstacle', obstacles, gameScrollPosition + gameContainer.offsetWidth);
obstacle.style.bottom = '0px';
}
function spawnEnemy() {
const baseEnemy = createElement('enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
baseEnemy.style.bottom = '0px';
const chance = Math.random();
if (chance < 0.3) {
spawnTeleporterEnemy();
} else if (chance < 0.7) {
spawnVelocityEnemy();
} else if (chance < 0.9) {
spawnStickyEnemy();
}
}
function moveVirusDrones() {
viruses.forEach((virus, vIndex) => {
if (!virus.infectedCount) virus.infectedCount = 0; // init counter
// Pick target enemy for infection
if (!virus.targetEnemy || !enemies.includes(virus.targetEnemy)) {
virus.targetEnemy = null;
if (enemies.length > 0) {
virus.targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
} else {
// No target, fly left and clean up if off-screen
virus.style.left = (parseInt(virus.style.left) - parseInt(virus.dataset.speed)) + 'px';
if(parseInt(virus.style.left) < gameScrollPosition - virus.offsetWidth) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
return;
}
}
const target = virus.targetEnemy;
const virusPosX = parseFloat(virus.style.left) + virus.offsetWidth / 2;
const virusPosY = parseFloat(virus.style.bottom);
const enemyPosX = parseFloat(target.style.left) + target.offsetWidth / 2;
const enemyPosY = parseFloat(target.style.bottom);
const speedH = parseFloat(virus.dataset.speed) || 5;
const speedV = 10;
// Horizontal move gently towards target
if (Math.abs(virusPosX - enemyPosX) > 5) {
if (virusPosX < enemyPosX) {
virus.style.left = (parseFloat(virus.style.left) + speedH) + 'px';
} else {
virus.style.left = (parseFloat(virus.style.left) - speedH) + 'px';
}
// Use smooth vertical cruising instead of forced fixed height (optional)
const cruisingHeight = 350;
let virusPosYCurrent = parseFloat(virus.style.bottom) || cruisingHeight;
if (virusPosYCurrent < cruisingHeight) {
virus.style.bottom = (virusPosYCurrent + speedV) + 'px';
} else if (virusPosYCurrent > cruisingHeight) {
virus.style.bottom = (virusPosYCurrent - speedV) + 'px';
} else {
virus.style.bottom = cruisingHeight + 'px';
}
} else {
// Move vertically down inside enemy for infection smoothly
const targetInsideY = enemyPosY + target.offsetHeight / 2;
if (virusPosY > targetInsideY + 2) {
// Smooth downward movement clamped to not go below targetInsideY + 2
let newBottom = virusPosY - speedV;
if (newBottom < targetInsideY + 2) {
newBottom = targetInsideY + 2;
}
virus.style.bottom = newBottom + 'px';
} else {
infectEnemy(target);
virus.infectedCount++; // Increment infection count
virus.targetEnemy = null; // Reset target
if (virus.infectedCount >= 3) {
// Destroy virus drone after infecting 3 enemies
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
}
}
// Check bullets hitting virus drone - keep your existing logic here as needed
bullets.forEach((bulletObj, bIndex) => {
const bullet = bulletObj.element;
if (checkCollision(virus, bullet)) {
bullet.remove();
bullets.splice(bIndex, 1);
virusDroneHits++;
if (virusDroneHits >= 3) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
enemies.forEach(e => {
if (e.classList.contains('infected')) {
cureEnemy(e);
}
});
}
}
});
// Remove virus drone if off screen left
if (parseFloat(virus.style.left) < gameScrollPosition - virus.offsetWidth) {
virus.remove();
viruses.splice(vIndex, 1);
virusDroneHits = 0;
}
});
}
function createWarningLine(enemy) {
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
const warning = document.createElement('div');
warning.classList.add('laser-warning-line');
const width = 6;
const enemyLeft = parseInt(enemy.style.left);
const enemyBottom = parseInt(enemy.style.bottom);
const enemyHeight = enemy.offsetHeight;
const left = enemyLeft + enemy.offsetWidth / 2 - width / 2;
warning.style.position = 'absolute';
warning.style.left = left + 'px';
warning.style.bottom = '0px';
warning.style.width = width + 'px';
warning.style.height = (enemyBottom + enemyHeight) + 'px';
warning.style.backgroundColor = 'yellow';
warning.style.opacity = '0.7';
warning.style.zIndex = '9';
gameWorld.appendChild(warning);
enemy._warningLine = warning;
if (enemy._warningLineUpdateInterval) clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = setInterval(() => {
if (!enemy._warningLine) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
return;
}
const curLeft = parseInt(enemy.style.left);
const curBottom = parseInt(enemy.style.bottom);
const leftPos = curLeft + enemy.offsetWidth / 2 - width / 2;
enemy._warningLine.style.left = leftPos + 'px';
enemy._warningLine.style.height = (curBottom + enemy.offsetHeight) + 'px';
}, 16);
}
function activateLaserBeam(enemy) {
if (enemy._warningLine) {
enemy._warningLine.remove();
enemy._warningLine = null;
}
if (enemy._warningLineUpdateInterval) {
clearInterval(enemy._warningLineUpdateInterval);
enemy._warningLineUpdateInterval = null;
}
if (enemy._laserBeam) {
enemy._laserBeam.remove();
enemy._laserBeam = null;
}
const laser = document.createElement('div');
laser.classList.add('laser-beam');
const laserWidth = 8;
const enemyLeft = parseInt(enemy.style.left);
const enemyBottom = parseInt(enemy.style.bottom);
const enemyHeight = enemy.offsetHeight;
const left = enemyLeft + enemy.offsetWidth / 2 - laserWidth / 2;
laser.style.position = 'absolute';
laser.style.left = left + 'px';
laser.style.bottom = '0px';
laser.style.width = laserWidth + 'px';
laser.style.height = (enemyBottom + enemyHeight) + 'px';
laser.style.backgroundColor = 'red';
laser.style.opacity = '0.9';
laser.style.zIndex = '10';
gameWorld.appendChild(laser);
enemy._laserBeam = laser;
enemy._laserBeamUpdateInterval = setInterval(() => {
if (!enemy._laserBeam) {
clearInterval(enemy._laserBeamUpdateInterval);
enemy._laserBeamUpdateInterval = null;
return;
}
const curLeft = parseInt(enemy.style.left);
const curBottom = parseInt(enemy.style.bottom);
const leftPos = curLeft + enemy.offsetWidth / 2 - laserWidth / 2;
enemy._laserBeam.style.left = leftPos + 'px';
enemy._laserBeam.style.height = (curBottom + enemy.offsetHeight) + 'px';
}, 16);
setTimeout(() => {
if (enemy._laserBeam) {
enemy._laserBeam.remove();
enemy._laserBeam = null;
}
clearInterval(enemy._laserBeamUpdateInterval);
enemy._laserBeamUpdateInterval = null;
enemy.dataset.laserState = 'cooldown';
setTimeout(() => {
enemy.dataset.laserState = 'warning';
}, 3000);
}, 2000);
}
// Infect Enemy function
// Infect Enemy function - updated for multiple shield hits and faster speed
function infectEnemy(enemy) {
if (enemy.classList.contains('infected')) return; // Already infected
enemy.classList.add('infected', 'shielded');
enemy.dataset.health = '1'; // 1 health after shield
enemy.dataset.shieldHitsLeft = '2'; // 2 shield hits before health
// Store or retrieve base speed
let baseSpeed = parseInt(enemy.dataset.baseSpeed) || 1;
if (!enemy.dataset.baseSpeed) {
baseSpeed = parseInt(enemy.dataset.speed) || 1;
enemy.dataset.baseSpeed = baseSpeed.toString();
}
enemy.dataset.speed = (baseSpeed * 3).toString(); // Speed is tripled
enemy.dataset.powerful = 'true';
// Visual effects
enemy.style.opacity = '0.8';
enemy.style.filter = 'drop-shadow(0 0 5px lime)';
}
// Handle bullet collision with shielded enemy - place this inside your bullet collision code
function handleBulletCollisionWithInfectedEnemy(enemy, bulletIndex) {
if (enemy.classList.contains('infected') && enemy.classList.contains('shielded')) {
let shieldHitsLeft = parseInt(enemy.dataset.shieldHitsLeft) || 0;
shieldHitsLeft--;
// Shield hit logic to decrement shield hits
if (shieldHitsLeft <= 0) {
// Shield broken, now damage health
enemy.dataset.shieldHitsLeft = '0';
// Decrement health by 1 on each bullet after shield is down
let health = parseInt(enemy.dataset.health) || 2; // default infected health 2
health--;
if (health <= 0) {
// Enemy dies
enemy.remove();
enemies = enemies.filter(e => e !== enemy);
} else {
enemy.dataset.health = health.toString();
// Maybe flash enemy to indicate hit
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
} else {
// Shield still active
enemy.dataset.shieldHitsLeft = shieldHitsLeft.toString();
// Flash on shield hit
enemy.style.opacity = '0.5';
setTimeout(() => {
if (enemy) enemy.style.opacity = '0.8';
}, 150);
}
// Remove the bullet after hit
bullets[bulletIndex].element.remove();
bullets.splice(bulletIndex, 1);
return true;
}
return false;
}
// Cure enemy restores normal speed and removes shield
function cureEnemy(enemy) {
enemy.classList.remove('infected', 'shielded');
enemy.style.pointerEvents = 'auto';
enemy.style.opacity = '1';
enemy.style.filter = 'none';
// Reset speed to baseSpeed or default 1
enemy.dataset.speed = enemy.dataset.baseSpeed || '1';
enemy.dataset.shieldHitsLeft = '0';
enemy.dataset.health = '1'; // normal enemies 1 HP
enemy.dataset.powerful = 'false';
// You may reset other enemy attributes here if applied.
}
// Enhance Enemy stats (called on infection)
function enhanceEnemy(enemy) {
// Example: double speed and fire rate if enemy shoots
const speed = parseInt(enemy.dataset.speed) || 1;
enemy.dataset.speed = speed * 2;
// If enemy shoots bullets (like sniper), reduce cooldown or increase damage
if(enemy.dataset.lastShotTime) {
enemy.dataset.shootCooldown = (parseInt(enemy.dataset.shootCooldown) || 3000) / 2;
}
// You can add more attribute enhancements here, e.g. health, damage, size
}
// Normalize Enemy back to default (called on cure)
function normalizeEnemy(enemy) {
const speed = parseInt(enemy.dataset.speed) || 2;
enemy.dataset.speed = speed / 2;
if(enemy.dataset.lastShotTime) {
enemy.dataset.shootCooldown = (parseInt(enemy.dataset.shootCooldown) || 1500) * 2;
}
// Reset any other attributes to normal here
}
function spawnPowerUp() {
const powerUpTypes = allPowerUpTypes;
const type = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)];
const powerUp = createElement('power-up', powerUps, gameScrollPosition + gameContainer.offsetWidth);
powerUp.setAttribute('data-type', type);
powerUp.style.backgroundColor = getPowerUpColor(type);
powerUp.style.bottom = Math.floor(Math.random() * 100) + 'px';
}
function spawnCoin() {
const coin = createElement('coin', coins, gameScrollPosition + gameContainer.offsetWidth);
coin.style.bottom = Math.floor(Math.random() * 50 + 50) + 'px';
}
function getPowerUpColor(type) {
switch (type) {
case 'super-speed': return 'lime';
case 'invincibility': return 'gold';
case 'teleportation': return 'violet';
case 'reflective-shield': return 'aqua';
case 'double-shot': return 'red';
case 'extra-life': return 'lightgreen';
case 'score-boost': return 'coral';
case 'mega-jump': return 'fuchsia';
case 'energy-blast': return 'limegreen'; // Specify colors for valid powers
case 'light-speed': return 'white'; // Define colors for light-speed
case 'blazing-trail': return 'gold'; // Specify for blazing-trail
case 'sonic-wave': return 'blueviolet'; // Specify for sonic-wave
case 'armor': return 'lightgrey'; // Define color for armor
case 'telekinetic': return 'silver'; // Define color for telekinetic
case 'camouflage': return 'darkgreen'; // Define color for camouflage
case 'dimension-shift': return 'black'; // Define color for dimension-shift
case 'energy-drain': return 'red'; // Define color for energy-drain
case 'mind-control': return 'purple'; // Define color for mind-control
case 'hologram': return 'pink'; // Define color for hologram
case 'fire': return 'red'; // Define color for energy-drain
case 'ice': return 'darkblue'; // Define color for energy-drain
case 'hypno': return 'pink'; // Define color for energy-drain
default: return 'white'; // Fallback for any unknown type
}
}
function spawnPlatform() {
const platform = createElement('platform', platforms, gameScrollPosition + gameContainer.offsetWidth);
platform.style.bottom = Math.floor(Math.random() * 200 + 100) + 'px';
}
function spawnBossDoor() {
const bossDoor = createElement('boss-door', bossDoors, gameScrollPosition + gameContainer.offsetWidth + 200);
bossDoor.style.bottom = '0px';
}
function createElement(className, array, leftPosition, type = '') {
const element = document.createElement('div');
element.classList.add(className);
if (type) element.setAttribute('data-type', type);
element.style.left = leftPosition + 'px';
gameWorld.appendChild(element);
array.push(element);
return element;
}
function moveArray(array, speed, scoreValue, isPowerUp = false) {
array.forEach((el, index) => {
el.style.left = (parseInt(el.style.left) - speed) + 'px';
if (parseInt(el.style.left) < gameScrollPosition - el.offsetWidth) {
removeElement(el, array, index, 0);
} else if (checkCollision(player, el)) {
handleCollision(el, array, index, scoreValue, isPowerUp);
}
});
}
function removeElement(el, array, index, scoreValue) {
el.remove();
array.splice(index, 1);
if (scoreValue) {
score += scoreValue;
updateDisplay();
}
}
function handleCollision(el, array, index, scoreValue, isPowerUp = false) {
if (el.classList.contains('coin')) {
score += 5; // Increase score for collecting a coin
updateDisplay(); // Update the display if needed
}
// Handle power-ups
else if (isPowerUp) {
applyPowerUp(el); // Apply power-up effects
}
// Handle boss door interactions
else if (el.classList.contains('boss-door')) {
if (score >= level * bossScoreThreshold) {
interactMessage.textContent = "Press 'E' to enter boss door."; // Prompt for interaction
interactMessage.style.display = checkCollision(player, el) ? 'block' : 'none'; // Show message if player is near
if (checkCollision(player, el) && playerPositionX >= parseInt(el.style.left)) {
openBossHallway(); // Proceed to boss hallway
}
}
}
// Handle enemy and obstacle collisions
else if (el.classList.contains('enemy') || el.classList.contains('obstacle')) {
reduceLives(); // Reduce player's lives on collision with enemies or obstacles
}
// Remove the collided element from the game world
removeElement(el, array, index, scoreValue);
}
function applyPowerUp(el) {
if (currentPowerUp || currentPowerUp === 'boss-power-up') return;
const type = el.getAttribute('data-type');
if (type === 'boss-power-up') {
grantBossPowerUp();
} else {
currentPowerUp = type;
let duration = 10;
activatePowerUp(type);
powerUpTimer = duration;
const powerUpInterval = setInterval(() => {
powerUpTimer--;
if (powerUpTimer <= 0) {
clearInterval(powerUpInterval);
resetPowerUp();
}
updateDisplay();
}, 1000);
}
displayPowerUpMessage(type);
}
function activatePowerUp(type) {
switch (type) {
case 'super-speed':
player.style.backgroundColor = 'lime';
playerMaxSpeed += 8; // Increase speed
break;
case 'invincibility':
player.style.backgroundColor = 'gold';
PlayerInvincibility(); // Implement this function for invincibility
break;
case 'teleportation':
teleportPlayer(); // Implement this function for teleportation
break;
case 'reflective-shield':
activateReflectiveShield(); // Implement the reflective shield functionality
break;
case 'double-shot':
enableDoubleShot(); // Implement functionality for double shot
break;
case 'extra-life':
gainExtraLife(); // Implement functionality for gaining an extra life
break;
case 'score-boost':
boostScore(); // Implement functionality for score boost
break;
case 'mega-jump':
activateMegaJump(); // Implement functionality for mega jump
break;
case 'energy-blast':
activateEnergyBlast(); // Implement functionality for energy blast
break;
case 'light-speed':
player.style.backgroundColor = 'lime';
playerMaxSpeed += 10; // Increase speed
break;
case 'blazing-trail':
activateBlazingTrail(); // Implement functionality for blazing trail
break;
case 'sonic-wave':
activateSonicWave(); // Implement functionality for sonic wave
break;
case 'armor':
activateArmor(); // Implement functionality for armor
break;
case 'telekinetic':
activateTelekinetic(); // Implement functionality for telekinetic
break;
case 'camouflage':
activateCamouflage(); // Implement functionality for camouflage
break;
case 'dimension-shift':
activateDimensionShift(); // Implement functionality for dimension shift
break;
case 'energy-drain':
activateEnergyDrain(); // Implement functionality for energy drain
break;
case 'mind-control':
activateMindControl(); // Implement functionality for mind control
break;
case 'hologram':
activateHologram(); // Implement functionality for hologram
break;
case 'fire':
shootFire();
break;
case 'ice':
shootIce();
break;
case 'hypno':
shootHypno();
break;
default:
// Consider logging or managing invalid power-up requests
console.log(`Unknown power-up type: ${type}`);
break;
}
}
function handlePlayerEnemyCollision() {
enemies.forEach((enemy, index) => {
// Check collision with the player
if (checkCollision(player, enemy)) {
if (enemy.classList.contains('frozen')) {
// If frozen, destroy the enemy
enemy.remove(); // Remove frozen enemy
enemies.splice(index, 1); // Remove from the enemies array
updateDisplay(); // Update any relevant game display elements if needed
} else {
// Normal behavior for non-frozen enemies
reduceLives(); // This would reduce lives for non-frozen collisions
}
}
});
}
function allowPlayerMovement(e) {
switch (e.key) {
case 'ArrowLeft':
playerPositionX -= playerMaxSpeed; // Move left
player.style.left = playerPositionX + 'px';
break;
case 'ArrowRight':
playerPositionX += playerMaxSpeed; // Move right
player.style.left = playerPositionX + 'px';
break;
case ' ': // Space to shoot
shootBullet(currentDirection); // Allow shooting during freeze
break;
}
}
function resetPowerUp() {
currentPowerUp = null; // Also can now be re-assigned
player.style.backgroundColor = '#4CAF50';
if (upgradesEquipped.includes('speedBoost')) {
playerMaxSpeed = 7; // Set the boosted speed or any other value you want
} else {
playerMaxSpeed = 5; // Default speed if the upgrade is not equipped
}
player.style.opacity = '1';
if (upgradesEquipped.includes('doubleJump')) {
isDoubleJumpEnabled = true; // Enable double jump if upgrade is equipped
} else {
isDoubleJumpEnabled = false; // Disable double jump otherwise
}
clearTimeout(timeSlowTimeout);
resetGravity();
}
function displayPowerUpMessage(type) {
powerUpMessage.textContent = `You obtained ${type.replace(/-/g, ' ')}!`;
powerUpMessage.style.display = 'block';
setTimeout(() => powerUpMessage.style.display = 'none', 2000);
}
function grantBossPowerUp() {
bossPowerObtained = true;
powerUpMessage.textContent = "Boss Power-Up Obtained!";
powerUpMessage.style.display = 'block';
setTimeout(() => powerUpMessage.style.display = 'none', 2000);
}
function PlayerInvincibility() {
// Check for collision with enemies
enemies.forEach((enemy) => {
if (checkCollision(player, enemy)) {
if (!isInvincible) { // Only reduce lives if not invincible
if (lives > 1) {
lives--; // Deduct a life when colliding with an enemy
updateDisplay(); // Update the displayed lives
}
}
}
});
// Immediately reset lives to 3 if they drop below that
if (lives < 3) {
lives = 3; // Reset lives to 3 instantly
updateDisplay(); // Update display to show 3 lives
}
}
function usePowerUp() {
if (!currentPowerUp) return;
switch (currentPowerUp) {
case 'ice':
shootIce();
break;
case 'sonic-wave':
activateSonicWave();
break;
case 'fire':
shootFire();
break;
case 'teleportation':
teleportPlayer();
break;
case 'levitate':
useLevitate();
break;
case 'hypno':
shootHypno();
break;
case 'double-shot':
shootDouble();
break;
default:
break;
}
}
function shootIce() {
const iceBullet = createBullet(currentDirection); // Create the ice bullet
iceBullet.style.backgroundColor = 'blue'; // Set bullet color to blue
gameWorld.appendChild(iceBullet);
const iceBulletObj = { element: iceBullet, direction: currentDirection };
animateBullet(iceBulletObj, 5, (target) => {
// Check for collision with enemies
if (target.classList.contains('obstacles')) {
target.classList.add('frozen'); // Mark the enemy as frozen
target.style.backgroundColor = '#00f'; // Change color to blue
target.style.opacity = '0.5'; // Reduce visibility
target.dataset.isFrozen = 'true'; // Mark enemy as frozen
// Set a timer to unfreeze
setTimeout(() => {
if (target.dataset.isFrozen === 'true') {
target.classList.remove('frozen'); // Remove frozen status
target.style.backgroundColor = ''; // Reset color
target.style.opacity = '1'; // Reset visibility
delete target.dataset.isFrozen; // Clear the frozen marker
}
}, 5000); // 5 seconds to unfreeze
}
});
}
function shootFire() {
const fireBullet = createBullet(currentDirection);
fireBullet.style.backgroundColor = 'red';
gameWorld.appendChild(fireBullet);
const fireBulletObj = { element: fireBullet, direction: currentDirection };
animateBullet(fireBulletObj, 5, (target) => {
if (target.classList.contains('obstacle')) {
target.style.transition = 'opacity 0.5s';
target.style.opacity = '0';
setTimeout(() => target.remove(), 500);
}
});
}
function useLevitate() {
player.style.transform = 'translateY(-20px)';
const levitateUp = setTimeout(() => {
player.style.transform = 'translateY(0)';
clearTimeout(levitateUp);
}, 1000);
}
function enableDoubleShot() {
}
function createBullet(direction = 1) {
const bullet = document.createElement('div');
bullet.classList.add('bullet');
bullet.style.backgroundColor = '#FFD700';
bullet.style.left = (playerPositionX + player.offsetWidth / 2 - 5) + 'px';
bullet.style.bottom = (playerPositionY + player.offsetHeight / 2) + 'px';
return bullet;
}
function shootHypno() {
const hypnoBullet = createBullet(currentDirection);
hypnoBullet.style.backgroundColor = 'pink';
gameWorld.appendChild(hypnoBullet);
const hypnoBulletObj = { element: hypnoBullet, direction: currentDirection };
animateBullet(hypnoBulletObj, 5, (target) => {
if (target.classList.contains('enemy') && !target.classList.contains('hypnotized')) {
target.classList.add('hypnotized');
target.style.backgroundColor = 'pink';
hypnotizeEnemy(target);
}
});
}
function shootDouble() {
const bullet1 = createBullet(currentDirection);
const bullet2 = createBullet(currentDirection);
bullets.push({ element: bullet1, direction: currentDirection });
bullets.push({ element: bullet2, direction: currentDirection });
gameWorld.appendChild(bullet1);
gameWorld.appendChild(bullet2);
}
function hypnotizeEnemy(enemy) {
let destroyCount = 0; // Count how many enemies are destroyed by this hypnotized enemy
const moveInterval = setInterval(() => {
let nearestEnemy = findNearestEnemyTo(enemy);
if (!nearestEnemy) {
enemy.remove(); // If no nearest enemy, remove this one
clearInterval(moveInterval); // Stop interval
return;
}
let ex = parseInt(enemy.style.left);
let ey = parseInt(enemy.style.bottom);
let nex = parseInt(nearestEnemy.style.left);
let ney = parseInt(nearestEnemy.style.bottom);
let dx = nex - ex;
let dy = ney - ey;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
destroyCount++;
createBurst(nex, ney); // Create an effect when destroying enemies
nearestEnemy.remove();
enemies = enemies.filter(e => e !== nearestEnemy); // Clean up enemies
if (destroyCount >= 3) { // Define a limit for multiple enemies hypnotized
enemy.remove(); // Remove this hypnotized enemy after doing its effects
clearInterval(moveInterval); // Stop interval
}
} else {
let angle = Math.atan2(dy, dx); // Calculate angle to move towards the nearest enemy
enemy.style.left = (ex + Math.cos(angle) * 2) + 'px'; // Move slightly towards
enemy.style.bottom = (ey + Math.sin(angle) * 2) + 'px';
}
}, 1000 / 60); // Run update at 60 FPS
}
function findNearestEnemyTo(sourceEnemy) {
let minDistance = Infinity;
let closestEnemy = null;
enemies.forEach(enemy => {
if (enemy !== sourceEnemy) {
let distance = calculateDistance(sourceEnemy, enemy);
if (distance < minDistance) {
minDistance = distance;
closestEnemy = enemy;
}
}
});
return closestEnemy;
}
function calculateDistance(el1, el2) {
let el1Rect = el1.getBoundingClientRect();
let el2Rect = el2.getBoundingClientRect();
let dx = (el1Rect.left + el1Rect.width / 2) - (el2Rect.left + el2Rect.width / 2);
let dy = (el1Rect.top + el1Rect.height / 2) - (el2Rect.top + el2Rect.height / 2);
return Math.sqrt(dx * dx + dy * dy);
}
function enableDoubleJump(enable = true) {
isDoubleJumpEnabled = enable;
}
function moveBoostDrainer() {
if (!boostDrainer) return;
// Move left slowly
let currentX = parseInt(boostDrainer.style.left);
boostDrainer.style.left = (currentX - 0.5) + 'px'; // Adjust speed here
// Remove if off-screen left
if (currentX < gameScrollPosition - boostDrainer.offsetWidth) {
removeBoostDrainer();
return;
}
// Update chain line connecting player and boost drainer
if (boostDrainerChain) {
updateChainLine(boostDrainer, boostDrainerChain, player);
}
}
function teleportPlayer() {
if (isTeleporting) return; // Prevent multiple teleports in quick succession
// Set cooldown
isTeleporting = true; // Set to true to indicate teleport in progress
// Calculate teleportation distance
const teleportDistance = 145; // Increase this to a larger value for more noticeable teleport
// Determine player current direction
let teleportOffset = teleportDistance * currentDirection; // Use currentDirection to allow for left (-1) and right (1) teleporting
// Update player position
playerPositionX += teleportOffset; // Move the player in the direction they are facing
playerPositionX = Math.max(0, Math.min(gameWorld.offsetWidth - player.offsetWidth, playerPositionX)); // Ensure player stays in bounds
// Log positions for debugging
console.log('Teleporting to position:', playerPositionX);
player.style.left = playerPositionX + 'px'; // Update player's position on screen
// Set a timeout for the cooldown
setTimeout(() => {
isTeleporting = false; // Reset teleport ability after cooldown
}, 1000); // 1000 milliseconds = 1 second cooldown
}
function toggleGravity() {
const originalGravity = gravity;
gravity = -gravity;
setTimeout(() => {
gravity = originalGravity;
}, 5000);
}
function resetGravity() {
gravity = Math.abs(gravity); // Needs to ensure gravity is declared with let or var
}
function freezeEnemies() {
enemies.forEach(enemy => {
enemy.style.transition = 'opacity 1s';
enemy.style.opacity = '0.2';
enemy.classList.add('frozen-enemy');
});
setTimeout(() => {
enemies.forEach(enemy => {
enemy.style.opacity = '1';
enemy.classList.remove('frozen-enemy');
});
}, 5000);
}
function createClone() {
const clone = createElement('player', [], playerPositionX + 50);
clone.style.bottom = playerPositionY + 'px';
clone.style.backgroundColor = '#00F';
clone.style.border = '2px dashed #FFF';
gameWorld.appendChild(clone);
setTimeout(() => clone.remove(), 5000);
}
function triggerShockwave() {
const shockwaveRadius = 10000;
enemies.forEach(enemy => {
const dist = calculateDistance(player, enemy);
if (dist < shockwaveRadius) {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
enemy.remove();
}
});
}
function absorbEnergy() {
score += 50;
updateDisplay();
}
function shrinkPlayer() {
player.style.transform = 'scale(0.5)';
setTimeout(() => player.style.transform = 'scale(1)', 5000);
}
function growPlayer() {
player.style.transform = 'scale(1.5)';
setTimeout(() => player.style.transform = 'scale(1)', 5000);
}
function healPlayer() {
if (lives < 5) {
lives++;
updateDisplay();
}
}
function activateShield() {
player.style.border = '3px solid purple';
player.style.opacity = '0.6';
lives = Math.min(lives + 1, 7);
updateDisplay();
}
function gainExtraLife() {
lives = Math.min(lives + 1, 7);
updateDisplay();
}
function gainExtraLife2() {
lives = Math.min(lives + 3, 6); // Increase lives by 3, ensuring it doesn't exceed 5
updateDisplay(); // Update the display to reflect the new number of lives
}
function removeLife() {
lives = Math.max(lives - 3, 3); // Decrease lives by 3, ensuring it doesn't fall below 3
updateDisplay(); // Update the display to reflect the new number of lives
}
function boostScore() {
score += 100;
updateDisplay();
}
let consecutiveJumps = 0; // Initialize a variable to track consecutive jumps
const maxJumpCount = 3; // Maximum number of normal jumps before triggering a mega jump
const megaJumpMultiplier = 1.5; // Multiplier for the mega jump
function activateMegaJump() {
if (consecutiveJumps < maxJumpCount) {
// Perform a normal jump if within the max jump limit
playerVelocityY = playerJumpStrength; // Regular jump value
consecutiveJumps++; // Increment consecutive jumps
} else {
// Trigger a mega jump if max jump limit has been reached
playerVelocityY = playerJumpStrength * megaJumpMultiplier; // Apply mega jump
consecutiveJumps = 0; // Reset consecutive jumps to 0 after a mega jump
}
isJumping = true; // Set the jumping status to true
}
// Call this function whenever the player lands to reset the consecutive jumps count
function checkLanding() {
if (playerPositionY <= 0) { // Assuming ground level is at y = 0
consecutiveJumps = 0; // Reset consecutive jumps when landing
}
}
function activateReflectiveShield() {
player.style.border = '3px dashed cyan';
const reflectInterval = setInterval(() => {
bullets.forEach((bullet, index) => {
if (checkCollision(player, bullet.element)) {
bullet.direction *= -1;
}
});
}, 1000 / 60);
setTimeout(() => {
player.style.border = '';
clearInterval(reflectInterval);
}, 5000);
}
function activateRegeneration() {
const regenerationInterval = setInterval(() => {
if (lives < 5) lives++;
updateDisplay();
if (!currentPowerUp || currentPowerUp !== 'regeneration') {
clearInterval(regenerationInterval);
}
}, 2000);
}
function activateMagnet() {
const magnetInterval = setInterval(() => {
coins.forEach(coin => {
const coinX = parseInt(coin.style.left);
const coinY = parseInt(coin.style.bottom);
const dx = playerPositionX - coinX;
const dy = playerPositionY - coinY;
if (Math.sqrt(dx * dx + dy * dy) < 100) {
coin.style.left = (coinX + dx * 0.1) + 'px';
coin.style.bottom = (coinY + dy * 0.1) + 'px';
}
});
if (!currentPowerUp || currentPowerUp !== 'magnet') {
clearInterval(magnetInterval);
}
}, 1000/ 60);
}
let timeSlowTimeout;
function slowDownTime() {
const originalTiming = 1000 / 60;
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, originalTiming * 2);
timeSlowTimeout = setTimeout(() => {
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, originalTiming);
}, 5000);
}
function activateEnergyBlast() {
const blastRadius = 100; // Define the radius of the blast
const burst = document.createElement('div'); // Create a burst element
burst.classList.add('energy-blast'); // Assign a class for styles
burst.style.left = (playerPositionX - blastRadius) + 'px'; // Position the burst to the player's location
burst.style.bottom = (playerPositionY - blastRadius) + 'px'; // Center the burst around the player
gameWorld.appendChild(burst); // Add the burst to the game world
// Set a timeout to remove the burst effect after a duration
setTimeout(() => {
burst.remove(); // Remove the burst after showing it
}, 500); // Keep the burst visible for half a second
// Identify and destroy nearby enemies
let enemiesToRemove = enemies.filter(enemy => calculateDistance(player, enemy) < blastRadius);
enemiesToRemove.forEach(enemy => {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom)); // Create an explosion effect at enemy's location
enemy.remove(); // Remove the enemy from the game
enemies = enemies.filter(e => e !== enemy); // Ensure the enemy is removed from the enemies array
});
updateDisplay(); // Update the display to reflect any changes (like score)
}
function initializeBossHealth() {
bossHealth = level === 3 ? 250 : 100; // Example health values based on level
updateDisplay();
}
function activateBlazingTrail() {
let trailInterval = setInterval(() => {
const trail = document.createElement('div');
trail.className = 'bullet'; // Using the same class for stylistic purposes
trail.style.left = (playerPositionX + player.offsetWidth / 2) + 'px'; // Position trail based on player's location
trail.style.bottom = (playerPositionY + player.offsetHeight / 2) + 'px'; // Center the trail on the player
trail.style.backgroundColor = 'orange'; // Trail color
gameWorld.appendChild(trail); // Add the trail to the game
// Check collision with enemies
enemies.forEach((enemy, enemyIndex) => {
if (checkCollision(trail, enemy)) {
// Create burst effect for visual feedback
const burst = document.createElement('div');
burst.className = 'burst'; // The CSS class for the burst effect
burst.style.left = enemy.style.left; // Position it at the enemy's position
burst.style.bottom = enemy.style.bottom; // Position it at the enemy's position
gameWorld.appendChild(burst);
// Clean up burst after a short duration
setTimeout(() => burst.remove(), 500); // Remove burst after 0.5 seconds
// Remove enemy
enemy.remove(); // Remove the enemy from game
enemies.splice(enemyIndex, 1); // Remove from enemies array to prevent future collisions
}
});
// Remove the trail after a short duration
setTimeout(() => trail.remove(), 500); // Remove trail after 0.5 seconds
}, 100); // The interval for creating the trails
// Automatically clear the interval after a certain time, e.g., 5 seconds
setTimeout(() => {
clearInterval(trailInterval); // Clear the interval for the trail creation
}, 5000);
}
function activateSonicWave() {
if (waveCooldown) return; // Prevent activation if already on cooldown
waveCooldown = true; // Set cooldown to prevent further activation
const wave = document.createElement('div');
wave.className = 'wave'; // Class for wave styling
wave.style.left = (playerPositionX - 50) + 'px'; // Center the wave below the player
wave.style.bottom = (playerPositionY - 50) + 'px'; // Position just below player
wave.style.backgroundColor = 'cyan'; // Wave color
wave.style.width = '100px'; // Width of the wave
wave.style.height = '100px'; // Height of the wave
wave.style.position = 'absolute'; // Ensure proper positioning
gameWorld.appendChild(wave); // Add the wave to the game world
let destroyedBlocks = 0; // Track how many blocks have been destroyed
// Move the wave forward quickly
let waveInterval = setInterval(() => {
wave.style.left = (parseInt(wave.style.left) + 10) + 'px'; // Move wave to the right
// Check for collision with obstacles
obstacles.forEach((block, index) => {
if (checkCollision(wave, block) && destroyedBlocks < 3) { // Destroy only until 3 blocks
// Create burst effect
const burst = document.createElement('div');
burst.className = 'burst';
burst.style.left = block.style.left;
burst.style.bottom = block.style.bottom;
gameWorld.appendChild(burst);
setTimeout(() => burst.remove(), 500); // Remove burst after half a second
block.remove(); // Remove the obstacle
obstacles.splice(index, 1); // Remove from the array
destroyedBlocks++; // Increment the destroyed block count
}
});
// Cleanup if wave goes off screen
if (parseInt(wave.style.left) > gameWorld.offsetWidth) {
clearInterval(waveInterval); // Stop moving the wave if it goes off screen
wave.remove(); // Remove the wave
resetWave(); // Reset block destruction count
return; // Exit the function
}
}, 50); // Adjust speed of the wave as needed
// Automatically remove the wave after a brief time
setTimeout(() => {
clearInterval(waveInterval); // Ensure the interval is cleared after time is up
wave.remove(); // Remove the wave
resetWave(); // Reset properties after the effect finishes
}, 2000); // Duration of the wave effect (2 seconds)
}
// Reset wave properties for future use
function resetWave() {
destroyedBlocks = 0; // Reset the number of destroyed blocks for future waves
waveCooldown = false; // Reset cooldown state
}
function activateArmor() {
player.style.border = '3px solid steelblue';
lives = Math.min(lives + 3, 5);
updateDisplay();
}
function activateTelekinetic() {
const forceInterval = setInterval(() => {
enemies.forEach((enemy, index) => {
const enemyX = parseInt(enemy.style.left) + enemy.offsetWidth / 2; // Get center position
const enemyY = parseInt(enemy.style.bottom) + enemy.offsetHeight / 2; // Get center position
const dx = playerPositionX - enemyX;
const dy = playerPositionY - enemyY;
// Check if the player is within a certain distance (e.g., 100 pixels)
if (Math.sqrt(dx * dx + dy * dy) < 100) {
// Move the enemy upwards slightly
enemy.style.bottom = (enemyY + 2) + 'px'; // Adjust height as necessary for feel
} else if (enemyY > 0) { // Only lower it if above 0
// Bring the enemy back down to ground level smoothly
enemy.style.bottom = (enemyY - 2) + 'px'; // Adjust to bring back down
// Stop exactly at the ground level if necessary
if (parseInt(enemy.style.bottom) < 0) {
enemy.style.bottom = '0px'; // Prevent going below 0
}
}
});
}, 1000 / 60); // Update at 60 FPS
// Optional: Set a timeout for how long telekinesis lasts
setTimeout(() => clearInterval(forceInterval), 5000); // Lasts for 5 seconds
}
function activateCamouflage() {
player.style.opacity = '0.2';
setTimeout(() => player.style.opacity = '1', 5000);
}
// Array to temporarily hold removed enemies
function activateDimensionShift() {
// Change the background color to indicate the shift
gameWorld.style.backgroundColor = 'black';
// Hide and remove all enemies in the game world
enemies.forEach((enemy, index) => {
enemy.style.transition = 'opacity 1s'; // Set transition for fading effect
enemy.style.opacity = '0'; // Start fading the enemy
setTimeout(() => {
enemy.remove(); // Remove from the DOM after fading
enemies.splice(index, 1); // Remove from the enemies array
}, 1000); // Match the timeout to the fade-out duration (1 second)
});
// Hide and remove all obstacles in the game world
obstacles.forEach((obstacle, index) => {
obstacle.style.transition = 'opacity 1s'; // Set transition for fading effect
obstacle.style.opacity = '0'; // Start fading the obstacle
setTimeout(() => {
obstacle.remove(); // Remove from the DOM after fading
obstacles.splice(index, 1); // Remove from the obstacles array
}, 1000); // Match the timeout to the fade-out duration (1 second)
});
// Reset background color after a period
setTimeout(() => {
gameWorld.style.backgroundColor = ''; // Reset the background color after 5 seconds
// Restore removed enemies
removedEnemies.forEach(removedEnemy => {
// Re-create the enemy in the game world
const newEnemy = createElement(removedEnemy.className, enemies, gameScrollPosition + gameContainer.offsetWidth);
newEnemy.style.bottom = '0px'; // Reset position
gameWorld.appendChild(newEnemy); // Append back to game world
});
// Clear out the removedEnemies array for future use
removedEnemies = [];
// If you have obstacles that should be restored, handle that here as well
// You may need to keep track of removed obstacles in a similar way if needed
}, 5000); // Keep the black background for 5 seconds
}
function activateEnergyDrain() {
const drainInterval = setInterval(() => {
if (score > 0) {
score -= 1;
updateDisplay();
}
}, 100);
setTimeout(() => clearInterval(drainInterval), 5000);
}
function activateMindControl() {
const hypnotizeRange = 250; // Set hypnotize range in pixels (you can adjust this)
// Loop through all enemies
enemies.forEach(enemy => {
const enemyX = parseInt(enemy.style.left) + (enemy.offsetWidth / 2); // Get the center of the enemy
const enemyY = parseInt(enemy.style.bottom) + (enemy.offsetHeight / 2); // Get the center of the enemy
const dx = playerPositionX - enemyX; // Calculate horizontal distance
const dy = playerPositionY - enemyY; // Calculate vertical distance
// Check if within the hypnotize range
const distance = Math.sqrt(dx * dx + dy * dy); // Calculate distance to the player
if (distance < hypnotizeRange) {
if (!enemy.classList.contains('hypnotized')) {
enemy.classList.add('hypnotized'); // Mark enemy as hypnotized
enemy.style.backgroundColor = 'violet'; // Change color to indicate hypnotization
// Call the function to handle hypnosis effects
hypnotizeEnemy(enemy);
}
}
});
}
function activateHologram() {
const hologram = document.createElement('div');
hologram.className = 'hologram'; // Use a hologram class for styling
hologram.style.position = 'absolute';
hologram.style.left = `${playerPositionX - 25}px`; // Position the hologram centered relative to player
hologram.style.bottom = `${playerPositionY}px`; // Vertical alignment with player
hologram.style.width = '50px'; // Fixed width for better visibility
hologram.style.height = '50px'; // Fixed height for better visibility
hologram.style.backgroundColor = 'rgba(0, 0, 255, 0.5)'; // Semi-transparent blue color
hologram.style.borderRadius = '50%'; // Round shape
hologram.style.zIndex = '100'; // Ensure the hologram is above other elements
gameWorld.appendChild(hologram); // Add the hologram to the game world
// Pulsate effect
let scale = 5;
const pulseSpeed = 0.05; // Adjust pulsate speed
const maxScale = 1.2; // Maximum scale for pulsate
const minScale = 1; // Minimum scale for pulsate
const pulseInterval = setInterval(() => {
scale += pulseSpeed * Math.sin(Date.now() / 500); // Use sine for smooth pulsating effect
scale = Math.max(minScale, Math.min(scale, maxScale)); // Clamp scale
hologram.style.transform = `scale(${scale})`;
}, 100); // Make it appear smooth
// Behavior to distract nearby enemies
enemies.forEach(enemy => {
const enemyX = parseInt(enemy.style.left) + (enemy.offsetWidth / 2);
const enemyY = parseInt(enemy.style.bottom) + (enemy.offsetHeight / 2);
const dx = playerPositionX - enemyX;
const dy = playerPositionY - enemyY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) { // If enemy is within distraction range
// Smoothly move the enemy away from the hologram
const moveAmount = 50; // The distance to move away
const newLeft = enemyX + (dx > 0 ? moveAmount : -moveAmount); // Move away from hologram
enemy.style.transition = 'left 0.5s'; // Smooth movement transition
enemy.style.left = `${newLeft}px`;
}
});
// Remove the hologram after 5 seconds
setTimeout(() => {
clearInterval(pulseInterval); // Stop the pulsating effect
hologram.remove(); // Remove hologram from the game world
}, 7500);
}
function animateBullet(bulletObj, speed, impactCallback = () => {}) {
const bulletInterval = setInterval(() => {
const bullet = bulletObj.element;
const newLeft = parseInt(bullet.style.left) + (bulletObj.direction * speed);
bullet.style.left = newLeft + 'px';
if (newLeft > gameScrollPosition + gameContainer.offsetWidth || newLeft < 0) {
bullet.remove();
clearInterval(bulletInterval);
return;
}
for (const target of [...obstacles, ...enemies]) {
if (checkCollision(bullet, target)) {
// Handle infected + shielded enemy
if (target.classList.contains('infected') && target.classList.contains('shielded')) {
let shieldHitsLeft = parseInt(target.dataset.shieldHitsLeft) || 2;
shieldHitsLeft--;
if (shieldHitsLeft <= 0) {
// Shield broken
target.dataset.shieldHitsLeft = '0';
target.classList.remove('shielded');
let health = parseInt(target.dataset.health) || 1;
health--;
if (health <= 0) {
target.remove();
enemies = enemies.filter(e => e !== target);
} else {
target.dataset.health = health.toString();
target.style.opacity = '0.5';
setTimeout(() => {
if (target) target.style.opacity = '0.8';
}, 150);
}
} else {
// Shield still active, just decrement and flash
target.dataset.shieldHitsLeft = shieldHitsLeft.toString();
target.style.opacity = '0.5';
setTimeout(() => {
if (target) target.style.opacity = '0.8';
}, 150);
}
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Hypno bullet effect (no instant death)
if (bullet.classList.contains('hypno-bullet') && target.classList.contains('enemy') && !target.classList.contains('hypnotized')) {
target.classList.add('hypnotized');
target.style.backgroundColor = 'pink';
hypnotizeEnemy(target);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Fire bullet effect (fade and remove obstacle)
if (bullet.classList.contains('fire-bullet') && target.classList.contains('obstacle')) {
target.style.transition = 'opacity 0.5s';
target.style.opacity = '0';
setTimeout(() => target.remove(), 500);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Ice bullet effect (freeze, no instant death)
if (bullet.classList.contains('ice-bullet')) {
if (target.classList.contains('obstacle') || target.classList.contains('enemy')) {
target.classList.add('frozen');
target.style.backgroundColor = '#00f';
target.style.opacity = '0.5';
target.dataset.isFrozen = 'true';
setTimeout(() => {
if (target.dataset.isFrozen === 'true') {
target.classList.remove('frozen');
target.style.backgroundColor = '';
target.style.opacity = '1';
delete target.dataset.isFrozen;
}
}, 5000);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
}
// Normal enemies and obstacles: remove on hit
if (target.classList.contains('enemy') || target.classList.contains('obstacle')) {
target.remove();
enemies = enemies.filter(e => e !== target);
createBurst(newLeft, parseInt(bullet.style.bottom));
bullet.remove();
clearInterval(bulletInterval);
return;
}
// Apply other impact effects, if any
impactCallback(target);
bullet.remove();
clearInterval(bulletInterval);
return;
}
}
}, 1000 / 60);
}
function reduceLives() {
if (player.style.border) {
player.style.border = '';
player.style.opacity = '1';
} else {
lives--;
if (lives <= 0) {
endGame(false); // Call endGame if lives are 0
} else {
}
}
updateDisplay();
}
function resetBoss() {
if (boss) {
if (level === 3) {
bossHealth = 150; // Restore boss health to 150 for level 3
} else {
bossHealth = 100; // Restore boss health for other levels
}
updateBossHealth(); // Update the UI to reflect the new health value
}
}
function checkCollision(rect1, rect2) {
const rect1Bounds = rect1.getBoundingClientRect();
const rect2Bounds = rect2.getBoundingClientRect();
return !(rect1Bounds.top > rect2Bounds.bottom || rect1Bounds.bottom < rect2Bounds.top ||
rect1Bounds.left > rect2Bounds.right || rect1Bounds.right < rect2Bounds.left);
}
function togglePause() {
if (isPaused) {
gameInterval = setInterval(gameLoop, 1000 / 60);
isPaused = false;
} else {
clearInterval(gameInterval);
isPaused = true;
}
}
function clearAllElements() {
[obstacles, enemies, bullets.map(b => b.element), powerUps, platforms, bossDoors, coins]
.forEach(arr => {
arr.forEach(el => el.remove());
arr.length = 0;
});
}
function interact() {
if (checkCollision(player, friend)) {
activateFriendDialogue();
}
bossDoors.forEach(door => {
if (checkCollision(player, door)) {
if (score >= level * bossScoreThreshold) {
openBossHallway();
} else {
openBossHallway();
}
}
});
}
function openBossHallway() {
bossHallway.style.display = 'flex';
clearAllElements();
clearInterval(gameInterval);
isInBossDoor = true;
}
function startBossFight() {
bossHallway.style.display = 'none';
isInBossDoor = true; // enable movement restrictions again if desired
triggerBossFight();
gameInterval = setInterval(gameLoop, 1000 / 60); // Ensure the game loop is running for updates
}
function incrementLevel() {
level++; // Increment level
// Check if the level exceeds 3; if so, reset to 1
if (level > 3) {
level = 1; // Reset to Level 1
}
playerMaxSpeed = 5; // Reset speed back to normal
localStorage.setItem('playerLevel', level); // Save updated level in local storage
updateDisplay(); // Update the game display
resetGame(); // Reset game state
}
function switchLevelBackground() {
const backgrounds = ['#333', '#1E90FF', '#228B22', '#a3a3a3','#7208c1'];
gameContainer.style.backgroundColor = backgrounds[(level - 1) % backgrounds.length];
}
function continueGame() {
levelIntro.style.display = 'none';
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function spawnEndlessEntities() {
if (!isPaused && !bossFightActive) {
if (Math.random() < 0.003) spawnObstacle();
// Spawn enemies based on the current level
if (level === 1) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy();
if (Math.random() < 0.002) spawnObstacle();
} else if (level === 2) {
if (Math.random() < 0.002) spawnObstacle();
if (Math.random() < 0.003) spawnChargingEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
} else if (level === 3) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnFlyingEnemy(); // Flying enemy option for Level 3
if (Math.random() < 0.001) spawnReflectiveEnemy(); // Reflective enemy option for Level 3
} else if (level === 4) {
if (Math.random() < 0.001) spawnLaserEnemy(); // Charging enemy option for Level 2
if (Math.random() < 0.002) spawnSplitterEnemy();
} else if (level === 5) {
if (Math.random() < 0.005) spawnEnemy();
if (Math.random() < 0.003) spawnVirusDrone(); // Charging enemy option for Level 2
if (Math.random() < 0.001) spawnSniperEnemy(); // Sniper enemy option for Level 2
}
if (Math.random() < 0.002 && !currentPowerUp) spawnPowerUp();
if (Math.random() < 0.001) spawnPlatform();
if (Math.random() < 0.003) spawnCoin();
// Manage boss door spawning only if in Story Mode
if (localStorage.getItem('gameMode') === 'endless') {
if (bossDoors.length === 0 && score >= level * bossScoreThreshold && enemies.length === 0) {
spawnBossDoor();
}
}
}
}
function startSandboxMode() {
isSandboxMode = true;
// Set level to 1 explicitly if needed
level = 1;
localStorage.setItem('gameMode', 'sandbox'); // Optionally
// Hide menus and clear enemies
document.getElementById('game-mode-options').style.display = 'none';
clearAllElements();
// Reset player position and show player
playerPositionX = 50;
playerPositionY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
updateDisplay();
// Show sandbox GUI panel here if you want...
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
const gameMode = localStorage.getItem('gameMode'); // Retrieve the current game mode
if (gameMode === 'endless') {
spawnEndlessEntities(); // Use the endless logic for enemy spawns
switchLevelBackground(); // Set the background based on the endless level
} else {
spawnEntities(); // Handle entity spawning for story mode
}
} else if (gameMode === 'sandbox') {
// Sandbox mode - NO auto spawning of enemies, obstacles, power-ups, platforms etc.
// Just skip spawning entirely
// Possibly update background or level here if you want
level = 1; // Force level 1 if you want
}
moveElements();
// Check for collisions between the player and enemies
handlePlayerEnemyCollision();
// Update score display in the shop if applicable
updateShopScoreDisplay();
// Check if the player's score meets the criteria for level transitions
checkScoreForLevelTransition();
updateBoostDrainer();
handlePlayerEnemyCollision(); // Check for collisions
updateDisplay(); // Update the scoreboard, lives, etc.
}
}
function endGame(isWin = false) {
clearInterval(gameInterval);
if (!isWin) {
// Reset boss health if the game is lost
resetBoss();
}
// Display the game over screen with the final score
gameOverScreen.style.display = 'flex';
if (localStorage.getItem('gameMode') === 'story') {
gameOverScreen.innerHTML = `
<p>${isWin ? 'Congratulations! You won!' : 'Game Over!'}</p>
<p>Your current score: ${score}</p>
<button onclick="restartGame2()">Restart</button>
`;
} else if (localStorage.getItem('gameMode') === 'endless') {
gameOverScreen.innerHTML = `
<p>Endless Mode Over!</p>
<p>Your score: ${score}</p>
<button onclick="restartGame()">Restart</button>
`;
}
}
function restartGame() {
gameOverScreen.style.display = 'none';
resetGame();
startLevel();
}
function restartGame2() {
gameOverScreen.style.display = 'none';
resetGame();
startLevel();
}
function triggerBossFight() {
bossFightScreen.style.display = 'flex';
setTimeout(() => {
bossFightScreen.style.display = 'none';
startBossLogic();
}, 2000);
}
function startBossLogic() {
bossFightActive = true;
boss = createElement('boss', [], 600);
boss.style.bottom = '100px';
gameWorld.appendChild(boss);
displayBossHealth();
cycleBossAttacks(); // Start the attack pattern cycle
}
// Helper function for delay(ms)
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Example placeholder for displayMessage function
// You need to implement or adjust this to your UI
// New asynchronous cycleBossAttacks function
// Helper to sleep (wait) for milliseconds
// Display a message for specified seconds
async function displayMessageTimed(text, seconds) {
displayMessage(text, seconds);
await sleep(seconds * 1000);
}
// Displays all pre-attack messages in sequence, then calls and awaits attackFunc
async function preAttackWithMessage(messages, attackFunc) {
for (const msg of messages) {
await displayMessageTimed(msg.text, msg.durationSeconds);
}
await attackFunc();
}
async function cycleBossAttacks() {
if (bossHealth <= 0) {
if (!bossDefeatedMessageDisplayed) {
bossDefeatedMessageDisplayed = true;
await displayMessageTimed("Boss Defeated!", 5);
}
return;
}
if (!phaseStartTime) {
phaseStartTime = Date.now();
bossAttackPhase = 0;
}
bossAttacking = true;
bossShielded = true;
boss.classList.add('boss-shielded');
const maxBossMoves = (level === 1) ? 4 : (level === 2) ? 4 : 6;
switch (level) {
case 1:
switch (bossAttackPhase) {
case 0: await bossEnemyBarrage(); break;
case 1: await bossTargetSlam(); break;
case 2: await bossNewPower(); break;
case 3: await bossExplosionRain(); break;
}
break;
case 2:
switch (bossAttackPhase) {
case 0: await bossPhaseTwoBarrage(); break;
case 1: await bossPhaseTwoTargetSlam(); break;
case 2: await bossPhaseTwoNewPower(); break;
case 3: await bossPhaseTwoExplosionRain(); break;
}
break;
case 3:
if (bossAttackPhase === 0 && (Date.now() - phaseStartTime) < 13000) {
await startPhaseThree();
return;
}
switch (bossAttackPhase) {
case 0: await bossPhaseThree(); break;
case 1: await bossPhaseTwoExplosionRain2(); break;
case 2: await bossPhaseTwoTargetSlam3(); break;
case 3: await bossPhaseTwoNewPower2(); break;
case 4: await bossPhaseTwoTargetSlam2(); break;
case 5: await bossPhase5(); break;
}
break;
case 4:
if (bossAttackPhase === 0 && (Date.now() - phaseStartTime) < 13000) {
await startPhaseFour();
return;
}
switch (bossAttackPhase) {
case 0:
await preAttackWithMessage(
[{ text: "Lets start with something simple!", durationSeconds: 4 }],
EnemyBarrage
);
break;
case 1:
await preAttackWithMessage(
[{ text: "Incredible, a nice worthy foe.", durationSeconds: 3 }],
bossPhaseTwoTargetSlam3
);
break;
case 2:
await preAttackWithMessage(
[{ text: "Well nobody has got this far before, your doing somehow great...", durationSeconds: 4 }],
bossPhase5
);
break;
case 3:
await preAttackWithMessage(
[{ text: "How are you NOT DEAD YET?", durationSeconds: 3 }],
bossPhaseTwoExplosionRain2
);
break;
case 4:
await preAttackWithMessage(
[{ text: "Your actully getting annoying now...", durationSeconds: 3 }],
bossPhaseThree
);
break;
case 5:
await preAttackWithMessage(
[{ text: "Alright let me stop talking now its game on.", durationSeconds: 4 }],
slidingLaserPhase
);
break;
}
break;
case 5:
switch (bossAttackPhase) {
case 0: await bossPhaseFiveAttackA(); break;
case 1: await bossPhaseFiveAttackB(); break;
case 2: await bossPhaseFiveAttackC(); break;
case 3: await bossPhaseFiveAttackE(); break;
case 4: await bossPhaseFiveAttackF(); break;
case 5: await bossPhaseFiveAttackF(); break;
}
break;
default:
// You might want to handle unexpected level values here as well
break;
}
// Increment phase for next attack
bossAttackPhase = (bossAttackPhase + 1) % maxBossMoves;
// Remove shield after 10s and notify player (fire-and-forget, no await needed)
setTimeout(() => {
bossShielded = false;
boss.classList.remove('boss-shielded');
displayMessage("Boss shield is OFF! Attack now!");
}, 10000);
}
async function slidingLaserPhase() {
displayMessage("Sliding laser phase! Jump over the lasers!", 3);
await sleep(3000);
const gameWorld = document.getElementById('game-world');
const gameBounds = gameWorld.getBoundingClientRect();
// Laser parameters
const laserWidth = 150;
const laserHeight = 20;
const initialSpeed = 3;
const maxBounces = 4;
// Create laser elements
function createLaserElement(y, direction) {
const el = document.createElement('div');
el.style.position = 'absolute';
el.style.width = laserWidth + 'px';
el.style.height = laserHeight + 'px';
el.style.backgroundColor = 'red';
el.style.bottom = y + 'px';
el.style.left = (gameBounds.width / 2 - laserWidth / 2) + 'px'; // center horizontally
el.style.borderRadius = '5px';
el.style.zIndex = '100';
gameWorld.appendChild(el);
return {
element: el,
y: y,
speed: initialSpeed,
direction: direction,
bounces: 0,
destroyed: false,
get left() {
return this.element.offsetLeft;
},
set left(val) {
this.element.style.left = val + 'px';
},
get right() {
return this.left + laserWidth;
},
remove() {
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
this.destroyed = true;
}
};
}
// Instantiate two lasers
const lasers = [
createLaserElement(gameBounds.height * 0.3, 1),
createLaserElement(gameBounds.height * 0.5, -1)
];
// Check collision between laser and player
function laserCollidesWithPlayer(laser) {
const laserRect = laser.element.getBoundingClientRect();
const playerRect = player.getBoundingClientRect();
return !(
playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom
);
}
return new Promise(resolve => {
function animate() {
// If both destroyed, end phase
if (lasers.every(l => l.destroyed)) {
displayMessage("Sliding laser phase complete!", 3);
setTimeout(() => {
resolve();
cycleBossAttacks();
}, 3000);
return;
}
lasers.forEach(laser => {
if (laser.destroyed) return;
let newLeft = laser.left + laser.speed * laser.direction;
// Bounce left wall
if (newLeft <= 0) {
newLeft = 0;
laser.direction *= -1;
laser.bounces++;
laser.speed *= 1.3; // speed up
if (laser.bounces >= maxBounces) {
laser.remove();
return;
}
}
// Bounce right wall
if (newLeft + laserWidth >= gameBounds.width) {
newLeft = gameBounds.width - laserWidth;
laser.direction *= -1;
laser.bounces++;
laser.speed *= 1.3;
if (laser.bounces >= maxBounces) {
laser.remove();
return;
}
}
laser.left = newLeft;
// Damage player if colliding
if (laserCollidesWithPlayer(laser)) {
lives = Math.max(0, lives - 1);
displayMessage(`Ouch! Lives left: ${lives}`, 1);
// Optional knockback or player jump boost can be added here
}
});
requestAnimationFrame(animate);
}
animate();
});
}
function bossPhase5() {
const blockWidth = 80;
const blockHeight = 20;
const lavaRiseSpeed = 4; // pixels per tick
const lavaRiseInterval = 100; // ms
const warningDuration = 1000; // 1 second warning before lava rises
const totalPhases = 3;
const obstacleWidth = 30; // Add this line
const obstacleHeight = 30; // Add this line
const platformHeights = [100, 200, 300];
const laserWarningDuration = 1000; // 1 second yellow warning before laser fires
const laserPhaseDuration = 6000; // total laser active time per phase (6 seconds)
const laserBaseInterval = 1000; // starting laser shoot interval
const obstacles = [];
const warningLines = [];
const lasers = [];
const gameWorld = document.getElementById('game-world');
let currentPhase = 0;
let lavaHeight = 0;
let lavaInterval;
let lastDamageTime = 0;
// Save original jump settings
let originalMaxJumps = maxJumps;
let originalDoubleJumpEnabled = isDoubleJumpEnabled;
let laserIntervalId = null;
let laserTimeoutId = null;
// Create or get lava element
let lavaElement = gameWorld.querySelector('.lava');
if (!lavaElement) {
lavaElement = document.createElement('div');
lavaElement.className = 'lava';
Object.assign(lavaElement.style, {
position: 'absolute',
width: '100%',
bottom: '0',
backgroundColor: 'red',
height: '0px',
zIndex: '5',
transition: 'height 0.1s linear',
});
gameWorld.appendChild(lavaElement);
}
// Clear existing platforms and obstacles
function clearPlatformsAndObstacles() {
obstacles.forEach(o => o.remove());
obstacles.length = 0;
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === blockWidth &&
el.offsetHeight === blockHeight
);
blocks.forEach(b => b.remove());
}
// Create a block platform
function spawnBlock(x, y) {
const block = document.createElement('div');
Object.assign(block.style, {
position: 'absolute',
width: blockWidth + 'px',
height: blockHeight + 'px',
backgroundColor: 'blue',
left: x + 'px',
bottom: y + 'px',
zIndex: '10',
});
gameWorld.appendChild(block);
return block;
}
// Create obstacle near block to jump on
function spawnObstacle(x, y) {
const obstacle = document.createElement('div');
obstacle.className = 'obstacle';
Object.assign(obstacle.style, {
position: 'absolute',
width: obstacleWidth + 'px',
height: obstacleHeight + 'px',
backgroundColor: 'orange',
left: x + 'px',
bottom: y + 'px',
zIndex: '10',
});
gameWorld.appendChild(obstacle);
obstacles.push(obstacle);
return obstacle;
}
function spawnPhasePlatforms(phase) {
clearPlatformsAndObstacles();
const yPos = platformHeights[phase];
const xs = [100, 300, 500];
xs.forEach(x => spawnBlock(x, yPos));
xs.forEach(x => spawnObstacle(x + 70, yPos));
}
// Get player’s distance from bottom of gameWorld
function getPlayerBottomOffset() {
const playerRect = player.getBoundingClientRect();
const worldRect = gameWorld.getBoundingClientRect();
return worldRect.bottom - playerRect.bottom;
}
function setTripleJump() {
maxJumps = 3;
isDoubleJumpEnabled = true;
jumpCount = 0;
}
function restoreJumpSettings() {
maxJumps = originalMaxJumps;
isDoubleJumpEnabled = originalDoubleJumpEnabled;
jumpCount = 0;
}
function startLavaPhase() {
displayMessage("Lava is rising soon! Prepare yourself!");
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
setTripleJump();
lavaInterval = setInterval(riseLava, lavaRiseInterval);
}, warningDuration);
}
// Lava rising step
function riseLava() {
const maxLavaHeight = platformHeights[currentPhase];
if (lavaHeight < maxLavaHeight) {
lavaHeight += lavaRiseSpeed;
if (lavaHeight > maxLavaHeight) lavaHeight = maxLavaHeight;
lavaElement.style.height = lavaHeight + 'px';
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
// Remove or comment out this block inside riseLava:
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
const now = Date.now();
if (checkCollision(player, lavaElement)) {
const playerBottom = getPlayerBottomOffset();
if (playerBottom < lavaHeight && (now - lastDamageTime) > 1000) {
reduceLives();
lastDamageTime = now;
}
}
} else {
clearInterval(lavaInterval);
startLaserPhase();
}
}
// Create a warning line (yellow) above a block
function createWarningLine(block) {
const warning = document.createElement('div');
warning.style.position = 'absolute';
warning.style.width = blockWidth + 'px';
warning.style.height = '5px';
warning.style.backgroundColor = 'yellow';
warning.style.left = block.style.left;
warning.style.bottom = (parseInt(block.style.bottom) + blockHeight) + 'px';
warning.style.zIndex = '20';
gameWorld.appendChild(warning);
warningLines.push(warning);
return warning;
}
// Create a laser beam (red) that shoots upwards from a block
function createLaser(block) {
const laser = document.createElement('div');
laser.style.position = 'absolute';
laser.style.width = '50px';
laser.style.height = '200px'; // laser beam height
laser.style.backgroundColor = 'red';
laser.style.left = (parseInt(block.style.left) + blockWidth / 2 - 3) + 'px'; // centered on block
laser.style.bottom = (parseInt(block.style.bottom) + blockHeight) + 'px';
laser.style.zIndex = '15';
gameWorld.appendChild(laser);
lasers.push(laser);
// Remove laser after 0.5s
setTimeout(() => {
laser.remove();
lasers.splice(lasers.indexOf(laser), 1);
}, 500);
return laser;
}
// Remove warning lines
function clearWarningLines() {
warningLines.forEach(w => w.remove());
warningLines.length = 0;
}
// Remove all active lasers
function clearLasers() {
lasers.forEach(l => l.remove());
lasers.length = 0;
}
// Laser phase variables
let laserPhaseTimer = 0;
let currentLaserInterval = laserBaseInterval;
// Laser damage cooldown for player (similar to lava damage)
let laserLastDamageTime = 0;
// Laser Phase logic:
// Every currentLaserInterval ms: choose a random block platform and:
// - Draw a yellow warning line 1 second before laser fires
// - After 1s, fire laser & damage player if in laser zone (once per laser)
function laserTick() {
if (laserPhaseTimer >= laserPhaseDuration) {
// Laser phase ended
clearInterval(laserIntervalId);
clearWarningLines();
clearLasers();
lavaInterval = setInterval(lowerLava, lavaRiseInterval);
return;
}
laserPhaseTimer += currentLaserInterval;
// Get all current blocks (platforms player can jump on)
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === blockWidth &&
el.offsetHeight === blockHeight
);
if (blocks.length === 0) return;
// Pick a random block different from last one if possible
let chosenBlock = blocks[Math.floor(Math.random() * blocks.length)];
// WARNING LINE
const warning = createWarningLine(chosenBlock);
// After 1s fire laser & damage
setTimeout(() => {
warning.remove();
warningLines.splice(warningLines.indexOf(warning), 1);
const laser = createLaser(chosenBlock);
// Check if player is in laser zone vertically and horizontally approx.
const now = Date.now();
const playerLeftPx = parseFloat(player.style.left) || 0;
const playerRightPx = playerLeftPx + player.offsetWidth;
const blockLeft = parseInt(chosenBlock.style.left);
const blockRight = blockLeft + blockWidth;
const laserTop = parseInt(laser.style.bottom) + parseInt(laser.style.height);
const playerBottom = parseFloat(player.style.bottom) || 0;
const playerTop = playerBottom + player.offsetHeight;
// Check horizontal overlap
const horizontalOverlap = playerRightPx > blockLeft && playerLeftPx < blockRight;
// Check vertical overlap with laser beam
const verticalOverlap = playerBottom < laserTop && playerTop > parseInt(laser.style.bottom);
if (horizontalOverlap && verticalOverlap && (now - laserLastDamageTime) > 1000) {
reduceLives();
laserLastDamageTime = now;
}
}, laserWarningDuration);
}
// Lower lava smoothly after laser phase ends
function lowerLava() {
if (lavaHeight > 0) {
lavaHeight -= lavaRiseSpeed;
if (lavaHeight < 0) lavaHeight = 0;
lavaElement.style.height = lavaHeight + 'px';
// Adjust player standing on lava during lowering
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
} else {
clearInterval(lavaInterval);
// Next phase
currentPhase++;
if (currentPhase < totalPhases) {
currentLaserInterval = Math.max(laserBaseInterval - currentPhase * 150, 400); // speeds up laser shooting each phase
laserPhaseTimer = 0;
spawnPhasePlatforms(currentPhase);
startLavaPhase();
} else {
displayMessage("Lava and laser phases complete! Prepare for next boss attack.");
setTimeout(() => {
clearPlatformsAndObstacles();
lavaElement.style.height = '0px';
restoreJumpSettings();
cycleBossAttacks();
}, 2000);
}
}
}
// Start lava rising phases as before
currentPhase = 0;
spawnPhasePlatforms(currentPhase);
displayMessage("Prepare! Lava will start rising soon!");
setTimeout(() => {
startLavaPhase();
}, 1500);
// Overwrite startLavaPhase so that after lava rises fully, laser phase starts
function startLavaPhase() {
displayMessage("Lava is rising soon! Prepare yourself!");
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
setTripleJump();
lavaInterval = setInterval(riseLava, lavaRiseInterval);
}, warningDuration);
}
// Modify riseLava to start laser phase after rising fully
function riseLava() {
const maxLavaHeight = platformHeights[currentPhase];
if (lavaHeight < maxLavaHeight) {
lavaHeight += lavaRiseSpeed;
if (lavaHeight > maxLavaHeight) lavaHeight = maxLavaHeight;
lavaElement.style.height = lavaHeight + 'px';
const playerBottomPx = parseFloat(player.style.bottom) || 0;
const playerVelocityDown = playerVelocityY <= 0;
if (playerBottomPx < lavaHeight && playerVelocityDown) {
playerPositionY = lavaHeight;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
player.style.bottom = playerPositionY + 'px';
}
const now = Date.now();
if (checkCollision(player, lavaElement)) {
const playerBottom = getPlayerBottomOffset();
console.log(`Collision! playerBottom=${playerBottom}, lavaHeight=${lavaHeight}`);
if (playerBottom < lavaHeight && (now - lastDamageTime) > 1000) {
console.log("Reducing lives from lava collision");
reduceLives();
lastDamageTime = now;
}
}
} else {
clearInterval(lavaInterval);
displayMessage("Warning! Lasers incoming!");
startLaserPhase();
}
}
// Laser phase start
function startLaserPhase() {
laserPhaseTimer = 0;
laserLastDamageTime = 0;
laserIntervalId = setInterval(laserTick, currentLaserInterval);
// Laser phase ends after fixed duration
laserTimeoutId = setTimeout(() => {
clearInterval(laserIntervalId);
clearWarningLines();
clearLasers();
displayMessage("Lava retreating... Prepare for next rise!");
lavaInterval = setInterval(lowerLava, lavaRiseInterval);
}, laserPhaseDuration);
}
}
function checkCollisionWithPlatformsAndObstacles() {
const playerLeft = playerPositionX;
let playerBottom = playerPositionY;
const playerWidth = player.offsetWidth;
const playerHeight = player.offsetHeight;
let onPlatform = false;
// Check platforms
const blocks = [...gameWorld.querySelectorAll('div')].filter(el =>
el.style.backgroundColor === 'blue' &&
el.offsetWidth === 80 &&
el.offsetHeight === 20
);
for (const block of blocks) {
const blockLeft = parseInt(block.style.left);
const blockBottom = parseInt(block.style.bottom);
const blockRight = blockLeft + block.offsetWidth;
const blockTop = blockBottom + block.offsetHeight;
const horizontalOverlap = playerLeft + playerWidth > blockLeft && playerLeft < blockRight;
const nextPlayerBottom = playerBottom + playerVelocityY;
if (
horizontalOverlap &&
playerBottom >= blockTop &&
nextPlayerBottom <= blockTop &&
playerVelocityY <= 0
) {
playerPositionY = blockTop;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
} else if (
horizontalOverlap &&
playerBottom >= blockTop - 1 &&
playerBottom <= blockTop + 1 &&
playerVelocityY === 0
) {
playerPositionY = blockTop;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
}
}
// Check obstacles with same logic
for (const obstacle of obstacles) {
const obLeft = parseInt(obstacle.style.left);
const obBottom = parseInt(obstacle.style.bottom);
const obRight = obLeft + obstacle.offsetWidth;
const obTop = obBottom + obstacle.offsetHeight;
const horizontalOverlap = playerLeft + playerWidth > obLeft && playerLeft < obRight;
const nextPlayerBottom = playerBottom + playerVelocityY;
if (
horizontalOverlap &&
playerBottom >= obTop &&
nextPlayerBottom <= obTop &&
playerVelocityY <= 0
) {
playerPositionY = obTop;
playerVelocityY = 0;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
} else if (
horizontalOverlap &&
playerBottom >= obTop - 1 &&
playerBottom <= obTop + 1 &&
playerVelocityY === 0
) {
playerPositionY = obTop;
isJumping = false;
jumpCount = 0;
onPlatform = true;
break;
}
}
return onPlatform;
}
function bossPhaseTwoTargetSlam2() {
const warningLineHeight = 5; // Height of the warning line
const rodWidth = 80; // Width of the laser rod
const rodHeight = 300; // Height of the rod
const rodColor = 'red'; // Color of the rod
const laserSpawnTime = 500; // Time between spawn of lasers
const totalWarnings = 12; // Total number of laser warnings
let warningCount = 0; // Counter for warnings
const warningPositions = []; // Store positions for warnings
function spawnWarningLine() {
// Create a warning line
const warningLine = document.createElement('div');
warningLine.classList.add('warning-line');
warningLine.style.width = '80px'; // Width of the warning line
warningLine.style.height = `${warningLineHeight}px`;
warningLine.style.backgroundColor = 'yellow'; // Set color
warningLine.style.position = 'absolute';
const warningLinePosition = Math.random() * (gameContainer.offsetWidth - 80); // Random horizontal position
warningLine.style.left = `${warningLinePosition}px`; // Set position
warningLine.style.bottom = '50px'; // Slightly above player position
gameWorld.appendChild(warningLine);
// Wait for 0.5 seconds, then spawn the laser rod above
setTimeout(() => {
// Create a rod above the warning line
const rod = document.createElement('div');
rod.classList.add('rod'); // Add a class for styling
rod.style.width = `${rodWidth}px`;
rod.style.height = `${rodHeight}px`;
rod.style.backgroundColor = rodColor;
rod.style.position = 'absolute';
rod.style.left = `${warningLinePosition + 25}px`; // Center the rod above the warning line
rod.style.bottom = '0px'; // Start at ground level
gameWorld.appendChild(rod); // Add rod to the game world
// Damage player if they are still there
if (checkCollision(player, rod)) {
reduceLives(); // Damage the player
}
// Remove the rod after a brief duration
setTimeout(() => {
rod.remove(); // Clean up after the rod
}, 200); // Keep the rod visible for 200 ms
// Remove the warning line
warningLine.remove();
}, 500); // Spawn the rod after 500 milliseconds
}
// Spawn warning lines and lasers at intervals
const spawnWarningsInterval = setInterval(() => {
if (warningCount >= totalWarnings) {
clearInterval(spawnWarningsInterval); // Stop after total warnings
cycleBossAttacks(); // Proceed to the next boss cycle
return; // Exit the function
}
spawnWarningLine(); // Spawn a warning line
warningCount++; // Increase warning count
}, laserSpawnTime); // Spawn a warning every 500 ms
}
function bossPhaseTwoNewPower2() {
const laserCount = 3; // Number of lasers per burst
const laserRadius = 80; // Distance lasers start from boss center (for initial pos)
const laserWidth = 20;
const laserHeight = 20;
const laserColor = 'red';
const healColor = 'green';
const healChance = 0.2;
const maxBursts = 8;
const laserInterval = 2000; // Time between bursts
let burstCount = 0;
const bounceLimit = 1; // Number of bounces before laser is destroyed
// Assume these globals (define globally)
// const boss = document.getElementById('boss');
// const player = document.getElementById('player');
// const gameWorld = document.getElementById('gameWorld');
// Get game world boundaries once
const worldRect = gameWorld.getBoundingClientRect();
function createLasers() {
const bossRect = boss.getBoundingClientRect();
for (let i = 0; i < laserCount; i++) {
const angle = (i * 2 * Math.PI) / laserCount; // spread lasers evenly around circle
// Create laser element
const laser = document.createElement('div');
laser.classList.add('laser');
laser.style.position = 'absolute';
laser.style.width = `${laserWidth}px`;
laser.style.height = `${laserHeight}px`;
laser.style.backgroundColor = laserColor;
laser.style.borderRadius = '2px';
// Initial position roughly at boss position + offset by radius along angle
// Calculate relative to gameWorld top-left (absolute positioning)
const startX = bossRect.left + bossRect.width/2 + Math.cos(angle)*laserRadius - laserWidth/2;
const startY = bossRect.top + bossRect.height/2 + Math.sin(angle)*laserRadius - laserHeight/2;
laser.style.left = `${startX}px`;
laser.style.top = `${startY}px`;
gameWorld.appendChild(laser);
// Velocity vector (pixels per frame)
// Let's say speed = 4px per frame (~60fps, adjust as needed)
const speed = 4;
let vx = Math.cos(angle)*speed;
let vy = Math.sin(angle)*speed;
let bounces = 0;
// Use requestAnimationFrame for smooth animation
function animate() {
if (!laser.parentElement) return; // Laser removed/destroyed
// Current position
let x = parseFloat(laser.style.left);
let y = parseFloat(laser.style.top);
// Move laser
x += vx;
y += vy;
// Check boundaries (bounce off gameWorld edges)
if (x <= 0) {
x = 0;
vx = -vx;
bounces++;
}
if (y <= 0) {
y = 0;
vy = -vy;
bounces++;
}
if (x + laserWidth >= worldRect.width) {
x = worldRect.width - laserWidth;
vx = -vx;
bounces++;
}
if (y + laserHeight >= worldRect.height) {
y = worldRect.height - laserHeight;
vy = -vy;
bounces++;
}
laser.style.left = `${x}px`;
laser.style.top = `${y}px`;
// Collision with player
// Here you can improve detection, currently simple AABB
const playerRect = player.getBoundingClientRect();
const laserRect = laser.getBoundingClientRect();
if (!(playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom)) {
// On collision, heal or damage once:
if (Math.random() < healChance) {
gainExtraLife && gainExtraLife();
} else {
reduceLives();
}
// Remove laser immediately on hit
laser.remove();
return;
}
// Remove laser after 2 bounces
if (bounces > bounceLimit) {
laser.remove();
return;
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}
}
// Interval that runs burstCount times, then stops
const laserBurstInterval = setInterval(() => {
if (burstCount >= maxBursts) {
clearInterval(laserBurstInterval);
cycleBossAttacks && cycleBossAttacks();
return;
}
createLasers();
burstCount++;
}, laserInterval);
}
// Call function to test
// bossPhaseTwoNewPower2();
function startPhaseThree() {
if (phaseThreeStarted) return; // Prevent running it multiple times
phaseThreeStarted = true; // Set flag to true once started
initializeBossHealth(); // Sets the boss health based on the current level
const messages = [
"Player: Oh god what is this",
"Boss: All the moves and powers i had...",
"Boss: But this time your not going to get pass this.",
"Boss: This is your final game",
"Player: alright lets take a bet on that!",
];
let phase3MessageIndex = 0; // Track which message is currently displayed
function displayMessages() {
if (phase3MessageIndex < messages.length) {
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000); // Display the next message after 3 seconds
} else {
// Once messages are done, start the boss attack cycle
cycleBossAttacks(); // Start boss attacks
}
}
displayMessages(); // Start displaying messages
}
// Wait until boss exists, then change color
function changeBossColorBlue() {
if (!boss) {
console.warn("Boss element not ready!");
return;
}
boss.style.backgroundColor = 'blue';
}
function changeBossColorBlue2() {
if (!boss) {
console.warn("Boss element not ready!");
return;
}
boss.style.backgroundColor = 'yellow';
}
// Call it when appropriate, no await needed
function startPhaseFour() {
console.log("startPhaseFour called");
if (typeof phaseThreeStarted === "undefined") {
console.error("phaseThreeStarted is not defined!");
}
if (phaseThreeStarted) {
console.log("Phase 3 already started.");
return;
}
phaseThreeStarted = true;
console.log("Starting Phase Four logic.");
if (typeof initializeBossHealth !== "function") {
console.error("initializeBossHealth is not defined!");
}
initializeBossHealth();
// Set up level-specific behaviors
let messages;
let preMessageFunction = function() {};
let endMessageFunction = function() {};
console.log("Level:", level);
if (level === 4) {
messages = [
"Boss: Well well well...",
"Boss: THIS? is what my brother was talking about!",
"Boss: You look so weak haha..",
"Boss: Well i should introduce myself",
"Boss: Im the strongest cube ever!",
"Player: Well he just told me that already so a big flex ig...",
"Boss: Intersting... you seem somewhat smart.",
"Player: Well knowing this is the last boss fight i will end you!...",
"Boss: Alright lets get started then!",
];
preMessageFunction = changeBossColorBlue;
endMessageFunction = cycleBossAttacks;
} else if (level === 5) {
messages = [
"System: Welcome to your final test.",
"Player: What do you mean?",
"System: To unlock life you must win..",
"Player: Alright lets do it!",
];
preMessageFunction = changeBossColorBlue2;
endMessageFunction = cycleBossAttacks;
} else {
messages = [
"Boss: Well well well...",
"Boss: THIS? is what my brother was talking about!",
"Boss: You look so weak haha..",
"Boss: Well i should introduce myself",
"Boss: Im the strongest cube ever!",
"Player: Well he just told me that already so a big flex ig...",
"Boss: Intersting... you seem somewhat smart.",
"Player: Well knowing this is the last boss fight i will end you!...",
"Boss: Alright lets get started then!",
];
preMessageFunction = changeBossColorBlue;
endMessageFunction = cycleBossAttacks;
}
if (typeof preMessageFunction !== "function") {
console.error("preMessageFunction is not a function!");
}
preMessageFunction();
let phase3MessageIndex = 0;
function displayMessages() {
if (phase3MessageIndex < messages.length) {
if (typeof displayMessage !== "function") {
console.error("displayMessage is not defined!");
}
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000);
} else {
if (typeof endMessageFunction !== "function") {
console.error("endMessageFunction is not a function!");
}
cycleBossAttacks();
}
}
displayMessages();
}
function startPhaseFive() {
if (phaseThreeStarted) return;
phaseThreeStarted = true;
initializeBossHealth();
changeBossColorBlue2();
const messages = [
"System: Welcome to your final test.",
"Player: What do you mean?",
"System: To unlock life you must win..",
"Player: Alright lets do it!",
];
let phase3MessageIndex = 0;
function displayMessages() {
if (phase3MessageIndex < messages.length) {
console.log("Displaying message:", messages[phase3MessageIndex]);
displayMessage(messages[phase3MessageIndex]);
phase3MessageIndex++;
setTimeout(displayMessages, 3000);
} else {
console.log("Messages done. Starting boss attacks...");
cycleBossAttacks();
}
}
displayMessages();
}
function bossPhaseTwoTargetSlam() {
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 400; // Faster slam down
const slamHoldDuration = 1200; // Hold slam a bit shorter
const slamDurationUp = 600; // Faster slam up
const slamCooldown = 700; // Faster cooldown between slams
// Reduce lives should be defined globally / externally
// This function just calls it
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
// Trigger additional feedback if needed
}
}
function createBurst(x, y) {
// Your burst effect implementation here
console.log(`Burst at (${x}, ${y})`);
}
// Animate CSS bottom property with transition and Promise-based resolution
function animateBottomPosition(targetBottom, duration) {
return new Promise((resolve) => {
let resolved = false;
function onTransitionEnd(e) {
if (e.propertyName === 'bottom' && !resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
// Safety fallback if transitionend does not fire
setTimeout(() => {
if (!resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}, duration + 50);
});
}
// Get bounding rects for player, boss, and boss parent
function getRects() {
return {
playerRect: player.getBoundingClientRect(),
bossRect: boss.getBoundingClientRect(),
parentRect: boss.parentElement.getBoundingClientRect()
};
}
// Axis-aligned bounding box collision detection
function isCollision(rect1, rect2) {
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
}
// Monitor collision during slam hold, damage max once per second
function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
const hitCooldown = 1000; // ms between allowed hits
let lastHitTimestamp = 0;
const startTime = performance.now();
function check(time) {
if (time - startTime >= duration) {
resolve();
return;
}
const { playerRect, bossRect } = getRects();
if (isCollision(playerRect, bossRect)) {
if (time - lastHitTimestamp > hitCooldown && lives > 0) {
reduceLives();
lastHitTimestamp = time;
shakeScreen(); // Assuming you defined shakeScreen globally
}
}
if (lives <= 0) {
resolve(); // Stop if no lives left
return;
}
requestAnimationFrame(check);
}
requestAnimationFrame(check);
});
}
async function performSlam() {
let slamCount = 0;
while (slamCount < maxSlams) {
// Position boss instantly above player horizontally
const { playerRect, parentRect } = getRects();
const playerCenterX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none';
boss.style.left = `${playerCenterX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
// Wait for next frame so changes apply
await new Promise(resolve => requestAnimationFrame(resolve));
// Shorter hover pause for snappier feel
await new Promise(resolve => setTimeout(resolve, 300));
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerCenterX, groundLevel);
// Hold slam while monitoring collision damage
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
// Cooldown before next slam
await new Promise(resolve => setTimeout(resolve, slamCooldown));
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 5000); // Faster cycle after slam attack
}
}
performSlam();
}
function bossPhaseTwoTargetSlam3() {
let slamCount = 0;
const maxSlams = 6;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 200;
const slamHoldDuration = 1000;
const slamDurationUp = 700;
const slamCooldown = 450;
let slamActive = false;
// assuming boss and player are defined globally or in the outer scope
// e.g. const boss = document.getElementById('boss');
// const player = document.getElementById('player');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function monitorCollisionDuringSlam(duration) {
const startTime = Date.now();
let lastHitTime = 0; // added to throttle reduceLives
return new Promise((resolve) => {
const interval = setInterval(() => {
const now = Date.now();
if (now - startTime >= duration) {
clearInterval(interval);
resolve();
return;
}
if (checkCollision(player, boss)) {
// Only reduce lives if at least 1000ms (1 second) passed since last hit
if (now - lastHitTime >= 1000) {
reduceLives();
console.log("Player hit by the boss slam! Lives Remaining: " + lives);
lastHitTime = now;
}
}
}, 50);
});
}
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(250);
shakeScreen();
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
await monitorCollisionDuringSlam(slamHoldDuration);
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log('Slam attack finished');
resetBossPosition();
setTimeout(cycleBossAttacks, 10000);
}
performSlam();
}
function bossTargetSlam() {
let slamCount = 0;
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 500;
const slamHoldDuration = 1500;
const slamDurationUp = 700;
const slamCooldown = 1000;
// Track if a slam is currently active
let slamActive = false;
// Promise-based sleep
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Collision check: AABB rectangle intersection
function checkCollision(elem1, elem2) {
const r1 = elem1.getBoundingClientRect();
const r2 = elem2.getBoundingClientRect();
return !(
r1.right < r2.left ||
r1.left > r2.right ||
r1.bottom < r2.top ||
r1.top > r2.bottom
);
}
// Placeholder for player's lives remaining, define accordingly
let lives = 3;
// Reduces player's lives safely
reduceLives();
// Shake screen effect placeholder — implement your own or remove
shakeScreen();
// Create burst effect placeholder — implement as needed
function createBurst(x, y) {
console.log(`Burst at (${x}, ${y})`);
}
// Animate bottom position with transition and Promise
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Force reflow to apply transition properly
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
// Monitor collision during slamHoldDuration, but only reduce lives once per second max
async function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
let lastHitTime = 0;
const interval = setInterval(() => {
const now = Date.now();
if (now - lastHitTime >= 1000 && checkCollision(player, boss)) {
reduceLives();
lastHitTime = now;
}
if (now >= startTime + duration) {
clearInterval(interval);
resolve();
}
}, 50);
const startTime = Date.now();
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
// Calculate player and boss parent positions to align boss over player horizontally
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
// Player center X inside boss parent container
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
// Position boss instantly above player at hover height
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(500); // hover delay
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
// Check collision during slam hold, max once per 1000ms
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 10000);
}
}
performSlam();
}
function bossPhaseTwoNewPower() {
let bossSlideCount = 0; // Count how many times the boss has slid side to side
const bossMaxSlides = 5; // Maximum number of slides
let direction = 1; // 1 for right, -1 for left
let canDamagePlayer = true; // Flag to control damage timing
// Set the boss to ground level and an initial position
boss.style.bottom = '0px'; // Set boss to ground level
boss.style.left = '50px'; // Starting position; adjust as needed
// Get the width of the game world for boundary checks
const gameWorldWidth = gameWorld.offsetWidth;
// Interval to move the boss
const slideInterval = setInterval(() => {
// Move the boss
const currentLeft = parseInt(boss.style.left);
const nextLeft = currentLeft + (4 * direction); // Move by 4 pixels
// Check for wall collision
if (nextLeft < 0 || nextLeft + boss.offsetWidth > gameWorldWidth) {
direction *= -1; // Reverse direction upon hitting the wall
bossSlideCount++; // Increment the slide count
// Bounce back the boss a little to ensure it doesn't stay stuck
boss.style.left = (currentLeft + (4 * direction)) + 'px'; // Adjust position slightly after collision
} else {
boss.style.left = nextLeft + 'px'; // Move the boss
}
// Check collision with the player
if (checkCollision(boss, player)) {
// Reduce lives every second when colliding
if (canDamagePlayer) {
reduceLives(); // Damage the player if they collide with the boss
canDamagePlayer = false; // Prevent further damage until the cooldown
setTimeout(() => {
canDamagePlayer = true; // Reset after 1 second
}, 1000);
}
}
// Stop sliding after the specified number of slides and trigger the next boss cycle
if (bossSlideCount >= bossMaxSlides) {
clearInterval(slideInterval); // Stop the sliding movement
resetBossPosition();
cycleBossAttacks(); // Call this function to start the next boss cycle
}
}, 3); // Faster interval for rapid movement
}
function bossPhaseTwoExplosionRain2() {
const blockWidth = 30; // Width of the falling blocks
const blockHeight = 10; // Height of the falling blocks
const blockSpeed = 5; // Speed of block movement (pixels per frame)
const blocks = []; // Array to hold active blocks
const spawnInterval = 500; // Time between block spawns (in milliseconds)
const duration = 15000; // Total duration of the attack (in milliseconds)
const leftRightDuration = 5500; // Move blocks left/right for 3 seconds
const upDownDuration = duration - leftRightDuration; // Then move blocks up/down for 7 seconds
// Keep track of current phase
let phase = 'leftRight'; // 'leftRight' or 'upDown'
let phaseStartTime = performance.now();
// Function to spawn a block for the left/right phase
function spawnBlockLeftRight() {
const block = document.createElement('div');
block.classList.add('explosive-block');
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Spawn from left or right side randomly
const fromLeft = Math.random() < 0.5;
block.dataset.fromLeft = fromLeft; // Store direction in dataset
// Set initial horizontal position (off screen)
block.style.left = fromLeft ? `${-blockWidth}px` : `${window.innerWidth}px`;
// Random vertical position on screen
block.style.bottom = `${Math.random() * (window.innerHeight - blockHeight)}px`;
document.body.appendChild(block);
blocks.push(block);
}
// Function to spawn a block for the up/down phase
function spawnBlockUpDown() {
const block = document.createElement('div');
block.classList.add('explosive-block');
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Spawn from bottom or top randomly
const fromBottom = Math.random() < 0.5;
block.dataset.fromBottom = fromBottom;
// Random horizontal position on the screen
block.style.left = `${Math.random() * (window.innerWidth - blockWidth)}px`;
// Initial vertical position (off screen)
block.style.bottom = fromBottom ? `${-blockHeight}px` : `${window.innerHeight}px`;
document.body.appendChild(block);
blocks.push(block);
}
// Function to update/move the blocks based on current phase
function updateBlocks() {
for (let i = blocks.length - 1; i >= 0; i--) {
const block = blocks[i];
if (!document.body.contains(block)) {
// Remove any blocks that were removed from DOM
blocks.splice(i, 1);
continue;
}
if (phase === 'leftRight') {
// Move horizontally toward center of screen
let currentLeft = parseFloat(block.style.left);
const moveDir = block.dataset.fromLeft === 'true' ? 1 : -1; // left->right is +1, right->left is -1
const newLeft = currentLeft + (blockSpeed * moveDir);
block.style.left = `${newLeft}px`;
// If block moved completely off screen past opposite side, remove it
if (moveDir === 1 && newLeft > window.innerWidth) {
block.remove();
blocks.splice(i, 1);
continue;
}
if (moveDir === -1 && newLeft < -blockWidth) {
block.remove();
blocks.splice(i, 1);
continue;
}
} else if (phase === 'upDown') {
// Move blocks vertically up or down
let currentBottom = parseFloat(block.style.bottom);
const moveDir = block.dataset.fromBottom === 'true' ? 1 : -1; // bottom->top = +1, top->bottom = -1
const newBottom = currentBottom + (blockSpeed * moveDir);
block.style.bottom = `${newBottom}px`;
// Remove if off screen vertically
if (moveDir === 1 && newBottom > window.innerHeight) {
block.remove();
blocks.splice(i, 1);
continue;
}
if (moveDir === -1 && newBottom < -blockHeight) {
block.remove();
blocks.splice(i, 1);
continue;
}
}
// Check collision with player (assuming player is accessible globally)
if (checkCollision(player, block)) {
reduceLives();
block.remove();
blocks.splice(i, 1);
}
}
}
// Interval to spawn blocks depending on the phase
let spawnBlocksInterval = setInterval(() => {
if (phase === 'leftRight') {
spawnBlockLeftRight();
} else if (phase === 'upDown') {
spawnBlockUpDown();
}
}, spawnInterval);
// Animation frame loop to move blocks
function animationLoop() {
const now = performance.now();
// Switch phase after respective time
if (phase === 'leftRight' && now - phaseStartTime >= leftRightDuration) {
phase = 'upDown';
phaseStartTime = now;
// Remove all existing blocks from leftRight phase immediately
blocks.forEach(block => block.remove());
blocks.length = 0;
} else if (phase === 'upDown' && now - phaseStartTime >= upDownDuration) {
// End attack and clean up
clearInterval(spawnBlocksInterval);
blocks.forEach(block => block.remove());
blocks.length = 0;
cycleBossAttacks();
return; // Stop the animation loop
}
updateBlocks();
requestAnimationFrame(animationLoop);
}
requestAnimationFrame(animationLoop);
}
function spawnBoostDrainer() {
if (boostDrainer) return;
const spawnX = gameScrollPosition + gameContainer.offsetWidth + 50;
const spawnY = Math.random() * 200 + 50;
boostDrainer = document.createElement('div');
boostDrainer.classList.add('boost-drainer');
boostDrainer.style.left = spawnX + 'px';
boostDrainer.style.bottom = spawnY + 'px';
gameWorld.appendChild(boostDrainer);
// Create chain element
boostDrainerChain = document.createElement('div');
boostDrainerChain.classList.add('boost-drainer-chain');
gameWorld.appendChild(boostDrainerChain);
boostDrainerHP = 3;
boostDrainerState = "moving"; // Start moving state
}
// Call this inside your main game loop or movement update function
function updateBoostDrainer() {
if (!boostDrainer) return;
const playerX = playerPositionX;
let enemyX = parseInt(boostDrainer.style.left);
switch (boostDrainerState) {
case 'moving':
const targetX = playerX + boostDrainerAttackXOffset;
if (enemyX > targetX) {
enemyX -= boostDrainerSpeed;
boostDrainer.style.left = enemyX + 'px';
} else {
// Reached attack position, start attack cycle
boostDrainerState = 'green';
boostDrainer.style.backgroundColor = 'green'; // Visual green state
boostDrainer.classList.remove('yellow', 'red');
boostDrainerAttackTimeout = setTimeout(() => {
boostDrainerState = 'yellow';
boostDrainer.style.backgroundColor = 'yellow';
boostDrainer.classList.add('yellow');
boostDrainerAttackTimeout = setTimeout(() => {
boostDrainerState = 'red';
boostDrainer.style.backgroundColor = 'red';
boostDrainer.classList.remove('yellow');
boostDrainer.classList.add('red');
fireBoostDrainerLaser(3); // Repeat laser fire 3 times
}, 1000);
}, 1000);
}
break;
// other states can be no movement
default:
break;
}
// Update the chain line only when moving or in states where player and drainer exist
if (boostDrainerChain) {
updateChainLine(boostDrainer, boostDrainerChain, player);
}
}
function fireBoostDrainerLaser(timesLeft) {
if (!boostDrainer || timesLeft <= 0) {
removeBoostDrainer();
return;
}
boostDrainerState = 'firing';
// Create laser div or reuse existing laser if you want smooth transition
if (boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
boostDrainerLaser = document.createElement('div');
boostDrainerLaser.classList.add('boost-laser');
const left = parseInt(boostDrainer.style.left) + boostDrainer.offsetWidth / 2 - 2;
boostDrainerLaser.style.left = left + 'px';
boostDrainerLaser.style.bottom = '0px';
// Laser height can be fixed or adjusted dynamically
boostDrainerLaser.style.height = '120px';
gameWorld.appendChild(boostDrainerLaser);
// Laser stays visible for 0.75s, then disappear, and wait 0.5s before next fire
setTimeout(() => {
if(boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
boostDrainerState = 'red'; // Can reset state or handle as needed
setTimeout(() => {
fireBoostDrainerLaser(timesLeft -1);
}, 500);
}, 750);
}
// Update the dotted chain line between two elements
function updateChainLine(fromEl, lineEl, toEl) {
if (!fromEl || !lineEl || !toEl) return;
// Get bounding rectangles
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
const containerRect = gameWorld.getBoundingClientRect();
// Calculate center point within gameWorld coordinates
// relative to gameWorld's top-left corner BUT we position absolute with bottom coordinate,
// so invert Y relative to container
function centerX(rect) {
return rect.left - containerRect.left + rect.width / 2;
}
function centerYBottom(rect) {
// bottom coordinate = container height - (rect.top - container top + half height)
return gameContainer.offsetHeight - (rect.top - containerRect.top + rect.height / 2);
}
const x1 = centerX(fromRect);
const y1 = centerYBottom(fromRect);
const x2 = centerX(toRect);
const y2 = centerYBottom(toRect);
const dx = x2 - x1;
const dy = y2 - y1;
const length = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
// Adjust the dotted line style here for better positioning
lineEl.style.width = length + 'px';
lineEl.style.left = x1 + 'px';
lineEl.style.bottom = y1 + 'px';
lineEl.style.transformOrigin = 'left center';
lineEl.style.transform = `rotate(${angle}deg)`;
}
// Handle when bullet hits the boost drainer
function handleBoostDrainerHit(bulletObj, bulletIndex) {
if (!boostDrainer) return false;
if (!checkCollision(bulletObj.element, boostDrainer)) return false;
// Remove bullet
bulletObj.element.remove();
bullets.splice(bulletIndex, 1);
// Reduce HP
boostDrainerHP--;
// Flash
boostDrainer.style.opacity = '0.5';
setTimeout(() => {
if (boostDrainer) boostDrainer.style.opacity = '1';
}, 150);
// If dead, remove
if (boostDrainerHP <= 0) {
removeBoostDrainer();
}
return true; // bullet handled
}
// Remove drainer and chain line cleanup
function removeBoostDrainer() {
if (boostDrainer) {
boostDrainer.remove();
boostDrainer = null;
}
if (boostDrainerChain) {
boostDrainerChain.remove();
boostDrainerChain = null;
}
if (boostDrainerLaser) {
boostDrainerLaser.remove();
boostDrainerLaser = null;
}
if (boostDrainerAttackTimeout) {
clearTimeout(boostDrainerAttackTimeout);
boostDrainerAttackTimeout = null;
}
boostDrainerHP = 0;
boostDrainerState = "dead";
}
function checkBoostDrainerLaserCollision() {
if (!boostDrainerLaser) return;
if (checkCollision(player, boostDrainerLaser)) {
// Hit by laser — destroy enemy & reduce lives
removeBoostDrainer();
reduceLives();
}
}
function bossPhaseTwoExplosionRain() {
const blockWidth = 30; // Width of the falling blocks
const blockHeight = 10; // Height of the falling blocks
const blockSpeed = 5; // Speed of block movement
const blocks = []; // Array to hold active blocks
const spawnInterval = 500; // Time between block spawns (in milliseconds)
const duration = 10000; // Duration of the attack (in milliseconds)
// Function to create and spawn blocks
function spawnBlock() {
const block = document.createElement('div');
block.classList.add('explosive-block'); // Add a class for styling
block.style.width = `${blockWidth}px`;
block.style.height = `${blockHeight}px`;
block.style.position = 'absolute';
block.style.backgroundColor = 'red';
// Determine random left or right spawn position (off screen)
const side = Math.random() < 0.5 ? -blockWidth : window.innerWidth;
block.style.left = side < 0 ? `${side}px` : `${side - blockWidth}px`;
block.style.bottom = `${Math.random() * window.innerHeight}px`; // Random vertical position
document.body.appendChild(block);
// Move block across the screen
const moveInterval = setInterval(() => {
const currentLeft = parseInt(block.style.left);
block.style.left = `${currentLeft + (side < 0 ? blockSpeed : -blockSpeed)}px`; // Move toward center
// Check if the block is out of bounds
if (currentLeft < -blockWidth || currentLeft > window.innerWidth) {
clearInterval(moveInterval); // Stop moving if out of bounds
block.remove(); // Remove block from DOM
}
// Check collision with player
if (checkCollision(player, block)) {
reduceLives(); // Reduce player's lives
block.remove(); // Remove block on collision
clearInterval(moveInterval); // Stop moving
}
}, 1000 / 60); // Move at 60 FPS
blocks.push(block); // Add the block to the list of active blocks
}
// Start spawning blocks
const spawnBlocksInterval = setInterval(spawnBlock, spawnInterval);
// Stop spawning after the specified duration and clean up
setTimeout(() => {
clearInterval(spawnBlocksInterval);
blocks.forEach(block => block.remove()); // Remove remaining blocks
cycleBossAttacks(); // Move to next boss attack cycle
}, duration);
}
function eded() {
// === Config ===
const CONFIG = {
blockWidth: 80,
blockHeight: 20,
obstacleWidth: 40,
obstacleHeight: 20,
lavaRiseSpeed: 1, // pixels per tick
lavaRiseInterval: 100, // ms
phaseLavaHeight: 100, // lava height per phase
totalPhases: 3,
damageCooldown: 1000 // ms cooldown between damage ticks
};
// === State ===
let currentPhase = 0;
let lavaHeight = 0;
let lavaIntervalId = null;
let lastDamageTime = 0;
// Spawned elements tracking (for cleanup)
const blocks = [];
const obstacles = [];
// Assumes global DOM references:
// - gameWorld: container element for gameplay (should be position: relative or absolute)
// - player: player character element
// - reduceLives(): function that reduces player lives
// - cycleBossAttacks(): function that triggers after phase completes
// === Helper: checkCollision ===
// Simple AABB collision between two elements using getBoundingClientRect
// === Helper: getPlayerBottomOffset ===
// Distance in pixels from player's bottom edge to bottom of the game world container.
function getPlayerBottomOffset() {
const playerRect = player.getBoundingClientRect();
const worldRect = gameWorld.getBoundingClientRect();
return worldRect.bottom - playerRect.bottom; // positive means above bottom
}
// === Spawn functions ===
function createBlock(x) {
const block = document.createElement('div');
block.className = 'block';
Object.assign(block.style, {
position: 'absolute',
width: CONFIG.blockWidth + 'px',
height: CONFIG.blockHeight + 'px',
backgroundColor: 'blue',
left: x + 'px',
bottom: '0',
zIndex: '10',
});
gameWorld.appendChild(block);
blocks.push(block);
return block;
}
function createObstacle(x) {
const obstacle = document.createElement('div');
obstacle.className = 'obstacle';
Object.assign(obstacle.style, {
position: 'absolute',
width: CONFIG.obstacleWidth + 'px',
height: CONFIG.obstacleHeight + 'px',
backgroundColor: 'orange',
left: x + 'px',
bottom: '0',
zIndex: '10',
});
gameWorld.appendChild(obstacle);
obstacles.push(obstacle);
return obstacle;
}
// === Spawning initial fixed blocks ===
function spawnInitialBlocks() {
[100, 300, 500].forEach(xPos => createBlock(xPos));
}
// === Spawning obstacles per phase ===
function spawnPhaseObstacles() {
// Could vary per phase dynamically, for now fixed positions
[150, 350, 550].forEach(xPos => createObstacle(xPos));
}
// === Cleanup elements ===
function cleanupElements() {
blocks.forEach(b => b.remove());
obstacles.forEach(o => o.remove());
blocks.length = 0;
obstacles.length = 0;
// Remove lava if any
const existingLava = gameWorld.querySelector('.lava');
if (existingLava) existingLava.remove();
}
// === Create lava element ===
function createLava() {
const lava = document.createElement('div');
lava.className = 'lava';
Object.assign(lava.style, {
position: 'absolute',
width: '100%',
height: '0',
bottom: '0',
backgroundColor: 'red',
zIndex: '5',
transition: 'height 0.1s linear', // smooth growth per interval
});
gameWorld.appendChild(lava);
return lava;
}
// === Damage player if touching lava with cooldown ===
function tryDamagePlayer() {
const now = Date.now();
if (now - lastDamageTime < CONFIG.damageCooldown) return; // cooldown active
if (checkCollision(player, lavaElement)) {
const playerBottomDist = getPlayerBottomOffset();
if (playerBottomDist < lavaHeight) {
reduceLives();
lastDamageTime = now;
}
}
}
// === Lava rise tick ===
function onLavaRise() {
lavaHeight += CONFIG.lavaRiseSpeed;
lavaElement.style.height = lavaHeight + 'px';
tryDamagePlayer();
// Check if current phase lava fully raised
if (lavaHeight >= (currentPhase + 1) * CONFIG.phaseLavaHeight) {
clearInterval(lavaIntervalId);
lavaIntervalId = null;
currentPhase++;
if (currentPhase < CONFIG.totalPhases) {
// Spawn obstacles for next phase
spawnPhaseObstacles();
// Reset lava after short delay for player repositioning
setTimeout(() => {
lavaHeight = 0;
lavaElement.style.height = '0px';
lavaIntervalId = setInterval(onLavaRise, CONFIG.lavaRiseInterval);
}, 1000);
} else {
// Final phase complete, proceed to next boss phase or attacks
cycleBossAttacks();
}
}
}
// === Start phase 5 ===
function startPhase() {
cleanupElements();
// Spawn blocks and initial obstacles
spawnInitialBlocks();
spawnPhaseObstacles();
// Create lava element fresh
lavaElement = createLava();
// Reset state
lavaHeight = 0;
currentPhase = 0;
lastDamageTime = 0;
// Start lava rising loop
lavaIntervalId = setInterval(onLavaRise, CONFIG.lavaRiseInterval);
}
// Launch phase 5
let lavaElement; // define lavaElement here to be accessible in nested functions
startPhase();
}
function bossPhaseTwoTargetSlam() {
let slamCount = 0;
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 500;
const slamHoldDuration = 1500;
const slamDurationUp = 700;
const slamCooldown = 1000;
// Track if a slam is currently active
let slamActive = false;
// Promise-based sleep
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Collision check: AABB rectangle intersection
function checkCollision(elem1, elem2) {
const r1 = elem1.getBoundingClientRect();
const r2 = elem2.getBoundingClientRect();
return !(
r1.right < r2.left ||
r1.left > r2.right ||
r1.bottom < r2.top ||
r1.top > r2.bottom
);
}
// Placeholder for player's lives remaining, define accordingly
let lives = 3;
// Reduces player's lives safely
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
}
}
// Shake screen effect placeholder — implement your own or remove
function shakeScreen() {
// Example simple console log
console.log("Screen shakes!");
}
// Create burst effect placeholder — implement as needed
function createBurst(x, y) {
console.log(`Burst at (${x}, ${y})`);
}
// Animate bottom position with transition and Promise
function animateBottomPosition(targetBottom, duration) {
return new Promise(resolve => {
function onTransitionEnd(event) {
if (event.propertyName === 'bottom') {
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Force reflow to apply transition properly
void boss.offsetWidth;
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
});
}
// Monitor collision during slamHoldDuration, but only reduce lives once per second max
async function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
let lastHitTime = 0;
const interval = setInterval(() => {
const now = Date.now();
if (now - lastHitTime >= 1000 && checkCollision(player, boss)) {
reduceLives();
lastHitTime = now;
}
if (now >= startTime + duration) {
clearInterval(interval);
resolve();
}
}, 50);
const startTime = Date.now();
});
}
async function performSlam() {
while (slamCount < maxSlams) {
slamActive = true;
// Calculate player and boss parent positions to align boss over player horizontally
const playerRect = player.getBoundingClientRect();
const parentRect = boss.parentElement.getBoundingClientRect();
// Player center X inside boss parent container
const playerX = playerRect.left - parentRect.left + playerRect.width / 2;
// Position boss instantly above player at hover height
boss.style.transition = 'none';
boss.style.left = `${playerX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
await sleep(500); // hover delay
shakeScreen();
// Slam down
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerX, groundLevel);
// Check collision during slam hold, max once per 1000ms
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
slamActive = false;
await sleep(slamCooldown);
}
console.log("Slam attack finished");
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
setTimeout(cycleBossAttacks, 10000);
}
}
performSlam();
}
// Function to shake the screen
function shakeScreen() {
let shakeEffect = document.createElement('div');
shakeEffect.style.position = "fixed";
shakeEffect.style.top = "0";
shakeEffect.style.left = "0";
shakeEffect.style.width = "100%";
shakeEffect.style.height = "100%";
shakeEffect.style.backgroundColor = "rgba(255, 0, 0, 0.5)"; // Flashing effect
shakeEffect.style.transition = 'transform 0.1s';
document.body.appendChild(shakeEffect);
// Shake effect
const shakeTimes = 15; // Number of shakes
const shakeDistance = 40; // Distance to shake
const shakeInterval = 50; // Time between shakes
for(let i = 0; i < shakeTimes; i++) {
setTimeout(() => {
shakeEffect.style.transform = (i % 2 === 0) ? `translateX(-${shakeDistance}px)` : `translateX(${shakeDistance}px)`;
}, i * shakeInterval);
}
// Reset the screen after shaking
setTimeout(() => {
shakeEffect.style.transform = 'translateX(0)';
}, shakeTimes * shakeInterval);
setTimeout(() => {
shakeEffect.remove(); // Clean up the shake effect
}, shakeTimes * shakeInterval + 100); // Ensure it removes after the reset
}
// Function to reset the boss position to the center of the screen
function resetBossPosition() {
boss.style.left = (gameContainer.offsetWidth / 2 - boss.offsetWidth / 2) + 'px'; // Center the boss
boss.style.bottom = "100px"; // Reset position
}
function bossPhaseTwoBarrage() {
boss.style.animation = "boss-shake 1s infinite alternate"; // Boss shakes
let spawnCount = 0;
let spawnInterval = setInterval(() => {
if (spawnCount >= 15) { // Stops after 10 spawns
clearInterval(spawnInterval);
boss.style.animation = ""; // Stop shaking
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
let enemy = createElement('enemy', enemies, Math.random() * gameContainer.offsetWidth);
enemy.style.bottom = "400px"; // Start from top
let fallSpeed = 12;
let fallInterval = setInterval(() => {
enemy.style.bottom = (parseInt(enemy.style.bottom) - fallSpeed) + "px";
if (parseInt(enemy.style.bottom) <= 0) {
enemy.style.bottom = "0px"; // Stop at ground level
clearInterval(fallInterval);
}
// Check for collision with player
if (checkCollision(player, enemy)) {
reduceLives();
enemy.remove();
clearInterval(fallInterval);
}
}, 50);
spawnCount++;
}, 500); // Spawn every half second
}
function bossEnemyBarrage() {
boss.style.animation = "boss-shake 1s infinite alternate"; // Boss shakes
let spawnCount = 0;
let spawnInterval = setInterval(() => {
if (spawnCount >= 10) { // Stops after 10 spawns
clearInterval(spawnInterval);
boss.style.animation = ""; // Stop shaking
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
let enemy = createElement('enemy', enemies, Math.random() * gameContainer.offsetWidth);
enemy.style.bottom = "400px"; // Start from top
let fallSpeed = 8;
let fallInterval = setInterval(() => {
enemy.style.bottom = (parseInt(enemy.style.bottom) - fallSpeed) + "px";
if (parseInt(enemy.style.bottom) <= 0) {
enemy.style.bottom = "0px"; // Stop at ground level
clearInterval(fallInterval);
}
// Check for collision with player
if (checkCollision(player, enemy)) {
reduceLives();
enemy.remove();
clearInterval(fallInterval);
}
}, 50);
spawnCount++;
}, 500); // Spawn every half second
}
function bossPhaseTwoTargetSlam() {
const maxSlams = 4;
const groundLevel = 0;
const hoverHeight = 100;
const slamDurationDown = 400; // Faster slam down
const slamHoldDuration = 1200; // Hold slam a bit shorter
const slamDurationUp = 600; // Faster slam up
const slamCooldown = 700; // Faster cooldown between slams
// Track if slam is active to avoid overlapping slams
let slamCount = 0;
// Player lives placeholder (should come from your global game state)
// Consider passing lives as param or referencing global store instead
function reduceLives() {
if (lives > 0) {
lives--;
console.log("Player hit by boss slam! Lives Remaining:", lives);
// TODO: Trigger player hit animation / feedback here
}
}
// Use Promises to handle CSS transitions cleanly
function animateBottomPosition(targetBottom, duration) {
return new Promise((resolve) => {
let resolved = false;
function onTransitionEnd(e) {
if (e.propertyName === 'bottom' && !resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}
boss.addEventListener('transitionend', onTransitionEnd);
// Apply CSS transition
boss.style.transition = `bottom ${duration}ms ease-in-out`;
boss.style.bottom = `${targetBottom}px`;
// Safety fallback in case transitionend doesn't fire:
setTimeout(() => {
if (!resolved) {
resolved = true;
boss.removeEventListener('transitionend', onTransitionEnd);
resolve();
}
}, duration + 50);
});
}
// Helper to get player and boss rectangles
function getRects() {
return {
playerRect: player.getBoundingClientRect(),
bossRect: boss.getBoundingClientRect(),
parentRect: boss.parentElement.getBoundingClientRect()
};
}
// Check AABB collision between two DOMRect objects
function isCollision(rect1, rect2) {
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
}
// Smoothly monitor collision during hold using requestAnimationFrame for fast, real-time checks
function monitorCollisionDuringSlam(duration) {
return new Promise((resolve) => {
const hitCooldown = 1000; // ms between allowed hits
let lastHitTimestamp = 0;
const startTime = performance.now();
function check(time) {
if (time - startTime >= duration) {
resolve();
return;
}
const { playerRect, bossRect } = getRects();
if (isCollision(playerRect, bossRect)) {
if (time - lastHitTimestamp > hitCooldown && lives > 0) {
reduceLives();
lastHitTimestamp = time;
shakeScreen();
}
}
if (lives <= 0) {
resolve(); // Stop checking if player dead or no lives left
return;
}
requestAnimationFrame(check);
}
requestAnimationFrame(check);
});
}
async function performSlam() {
slamCount = 0;
while (slamCount < maxSlams) {
// Position boss above player horizontally, instantly (no transition)
const { playerRect, parentRect } = getRects();
const playerCenterX = playerRect.left - parentRect.left + playerRect.width / 2;
boss.style.transition = 'none'; // Remove transition for instant move
boss.style.left = `${playerCenterX - boss.offsetWidth / 2}px`;
boss.style.bottom = `${hoverHeight}px`;
// Let rendering catch up (a frame or short delay)
await new Promise(resolve => requestAnimationFrame(resolve));
await new Promise(resolve => setTimeout(resolve, 300)); // small hover pause, quicker than before
shakeScreen();
// Slam down animation
await animateBottomPosition(groundLevel, slamDurationDown);
createBurst(playerCenterX, groundLevel);
// Hold slammed position while checking collision multiple times a second, respond quick but not too spammy
await monitorCollisionDuringSlam(slamHoldDuration);
// Slam back up
await animateBottomPosition(hoverHeight, slamDurationUp);
slamCount++;
// Cooldown between slams
await new Promise(resolve => setTimeout(resolve, slamCooldown));
}
console.log("Slam attack finished");
// Reset boss position or continue attack cycles
if (typeof resetBossPosition === 'function') {
resetBossPosition();
}
if (typeof cycleBossAttacks === 'function') {
// Reduced delay to chain attacks faster
setTimeout(cycleBossAttacks, 5000);
}
}
performSlam();
}
function bossExplosionRain() {
boss.style.animation = "boss-shake 1s infinite alternate";
let dropCount = 0;
let dropInterval = setInterval(() => {
if (dropCount >= 10) {
clearInterval(dropInterval);
boss.style.animation = "";
setTimeout(cycleBossAttacks, 3000); // Cooldown before next attack
return;
}
// Randomize the X position based on the player's position
let playerPositionX = parseInt(player.style.left);
let explosionBlock = document.createElement('div'); // Corrected to use document.createElement
explosionBlock.classList.add('explosive-block'); // Add a class if you want to style it later
explosionBlock.style.position = "absolute"; // Important for positioning
explosionBlock.style.left = (playerPositionX + Math.random() * 30 - 15) + "px"; // Randomize the horizontal position
explosionBlock.style.bottom = "400px"; // Start high
explosionBlock.style.width = "30px"; // Width of the falling block
explosionBlock.style.height = "10px"; // Height of the falling block
explosionBlock.style.backgroundColor = "red"; // Color of the falling block
gameWorld.appendChild(explosionBlock);
let fallSpeed = 5; // Speed of fall
let fallInterval = setInterval(() => {
explosionBlock.style.bottom = (parseInt(explosionBlock.style.bottom) - fallSpeed) + "px";
if (parseInt(explosionBlock.style.bottom) <= 0) {
createBurst(parseInt(explosionBlock.style.left), 0); // Create burst effect
if (checkCollision(explosionBlock, player)) {
reduceLives(); // Reduce lives if hit
}
explosionBlock.remove(); // Remove block once it hits ground
clearInterval(fallInterval);
}
}, 50); // Update every 50ms
dropCount++;
}, 1000); // Drop every second
}
class Laser {
constructor(x, y, direction, length) {
this.x = x;
this.y = y;
this.direction = direction; // 'vertical' or 'horizontal'
this.length = length;
this.element = null;
}
createElement() {
this.element = document.createElement('div');
this.element.style.position = 'absolute';
this.element.style.backgroundColor = 'yellow';
if (this.direction === 'vertical') {
this.element.style.width = '4px';
this.element.style.height = this.length + 'px';
this.element.style.left = this.x + 'px';
this.element.style.bottom = this.y + 'px';
} else {
this.element.style.height = '4px';
this.element.style.width = this.length + 'px';
this.element.style.left = this.x + 'px';
this.element.style.bottom = this.y + 'px';
}
this.element.style.opacity = '0.8';
this.element.style.zIndex = '1000';
this.element.classList.add('laser-beam');
document.getElementById('game-world').appendChild(this.element);
}
activate() {
// Make laser red and fully opaque when activated
if (!this.element) return;
this.element.style.backgroundColor = 'red';
this.element.style.opacity = '1';
}
remove() {
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
this.element = null;
}
}
getBoundingRect() {
return this.element.getBoundingClientRect();
}
}
function isPlayerOnLaser(laser) {
if (!laser.element) return false;
const playerRect = player.getBoundingClientRect();
const laserRect = laser.getBoundingRect();
return !(
playerRect.right < laserRect.left ||
playerRect.left > laserRect.right ||
playerRect.bottom < laserRect.top ||
playerRect.top > laserRect.bottom
);
}
// Main laser phase boss move function - call it with await in your attack cycle
async function laserPhaseMove() {
displayMessage("Dodge the lasers!", 2);
await sleep(2000); // Wait for player to read the message
const repeats = 4;
const gameWorld = document.getElementById('game-world');
const gameBounds = gameWorld.getBoundingClientRect();
for (let round = 0; round < repeats; round++) {
// Spawn lasers
const lasers = [];
// Number of lasers per round (can tweak for difficulty)
const lasersCount = 5;
for (let i = 0; i < lasersCount; i++) {
const vertical = Math.random() < 0.5;
if (vertical) {
// Random X inside game world boundaries
const xPos = Math.random() * gameBounds.width;
// Y set to bottom of game world container - zero
lasers.push(new Laser(xPos, 0, 'vertical', gameBounds.height));
} else {
// Random Y inside game world height
const yPos = Math.random() * gameBounds.height;
// X at left edge = 0
lasers.push(new Laser(0, yPos, 'horizontal', gameBounds.width));
}
}
// Create laser elements in DOM with warning color (yellow)
lasers.forEach(laser => laser.createElement());
// Warn lasers blinking: toggle opacity several times for 1 second
const warningDurationMs = 1000;
const blinkIntervalMs = 200;
const blinkCount = warningDurationMs / blinkIntervalMs;
for (let b = 0; b < blinkCount; b++) {
lasers.forEach(laser => {
if (!laser.element) return;
laser.element.style.opacity = (b % 2 === 0) ? '0.3' : '0.8';
});
await sleep(blinkIntervalMs);
}
// Set lasers fully visible and red (active)
lasers.forEach(laser => laser.activate());
// Damage phase: 2 seconds, check collisions every 100ms
const damageDurationMs = 2000;
const damageCheckIntervalMs = 100;
let elapsed = 0;
while (elapsed < damageDurationMs) {
lasers.forEach(laser => {
if (isPlayerOnLaser(laser)) {
lives = Math.max(0, lives - 1);
displayMessage(`Hit! Lives: ${lives}`, 1);
// TODO: Add your death/game over logic here if lives <= 0
}
});
await sleep(damageCheckIntervalMs);
elapsed += damageCheckIntervalMs;
}
// Remove all lasers from DOM and clear array
lasers.forEach(laser => laser.remove());
lasers.length = 0;
// Short delay before next round
await sleep(500);
}
displayMessage("Laser phase complete!", 2);
await sleep(2000);
cycleBossAttacks();
}
// Helper sleep function (if not defined)
function getRandomSpawnCount(min = 1, max = 5) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function bossPhaseFiveAttackA() {
displayMessage("Trial 1", 3);
await sleep(3000);
const totalDuration = 25000; // 25 seconds
const spawnInterval = 500;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnObstacle();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackB() {
displayMessage("Trial 2", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 600;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnChargingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSniperEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackC() {
displayMessage("Trial 3", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 550;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnReflectiveEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackD() {
displayMessage("Trial 4", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 400;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnLaserEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSplitterEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnObstacle();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackE() {
displayMessage("Trial 5", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 700;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnFlyingEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSniperEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
async function bossPhaseFiveAttackF() {
displayMessage("Trial 6", 3);
await sleep(3000);
const totalDuration = 25000;
const spawnInterval = 450;
const intervalId = setInterval(() => {
let count;
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnEnemy();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnVirusDrone();
count = getRandomSpawnCount();
for (let i = 0; i < count; i++) spawnSplitterEnemy();
}, spawnInterval);
await new Promise(resolve => {
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, totalDuration);
});
await cycleBossAttacks();
}
function EnemyBarrage() {
const numberOfFlyingEnemies = 3; // Total number of flying enemies to summon
const numberOfChargingEnemies = 5; // Number of charging enemies to spawn
const flyingSpawnDelay = 3000; // Delay between each flying enemy spawn (in milliseconds)
const chargingSpawnDelay = 4000; // Delay between each charging enemy spawn (in milliseconds)
const totalDuration = 15000; // Total time for the flying enemy spawning process (in milliseconds)
// Function to spawn multiple flying enemies
const flyingEnemiesInterval = setInterval(() => {
if (numberOfFlyingEnemies > 0) {
SpawnSplitterEnemy(); // Call to spawn a single flying enemy
numberOfFlyingEnemies--; // Decrement the count
} else {
clearInterval(flyingEnemiesInterval); // Stop spawning when done
}
}, flyingSpawnDelay); // Spawn every 2 seconds
// Function to spawn charging enemies
const chargingEnemiesInterval = setInterval(() => {
if (numberOfChargingEnemies > 0) {
spawnChargingEnemy(); // Call to spawn a charging enemy
numberOfChargingEnemies--; // Decrement the count
} else {
clearInterval(chargingEnemiesInterval); // Stop spawning when done
}
}, chargingSpawnDelay); // Spawn every 3 seconds
// Wait before proceeding to the next boss cycle
setTimeout(() => {
clearInterval(flyingEnemiesInterval); // Clear the spawn loops
clearInterval(chargingEnemiesInterval);
cycleBossAttacks(); // Proceed to the next boss attack
}, totalDuration); // 10 seconds before moving to next attack
}
function bossPhaseThree() {
const numberOfFlyingEnemies = 6; // Total number of flying enemies to summon
const numberOfChargingEnemies = 10; // Number of charging enemies to spawn
const flyingSpawnDelay = 3000; // Delay between each flying enemy spawn (in milliseconds)
const chargingSpawnDelay = 4000; // Delay between each charging enemy spawn (in milliseconds)
const totalDuration = 20000; // Total time for the flying enemy spawning process (in milliseconds)
// Function to spawn multiple flying enemies
const flyingEnemiesInterval = setInterval(() => {
if (numberOfFlyingEnemies > 0) {
spawnFlyingEnemy(); // Call to spawn a single flying enemy
numberOfFlyingEnemies--; // Decrement the count
} else {
clearInterval(flyingEnemiesInterval); // Stop spawning when done
}
}, flyingSpawnDelay); // Spawn every 2 seconds
// Function to spawn charging enemies
const chargingEnemiesInterval = setInterval(() => {
if (numberOfChargingEnemies > 0) {
spawnChargingEnemy(); // Call to spawn a charging enemy
numberOfChargingEnemies--; // Decrement the count
} else {
clearInterval(chargingEnemiesInterval); // Stop spawning when done
}
}, chargingSpawnDelay); // Spawn every 3 seconds
// Wait before proceeding to the next boss cycle
setTimeout(() => {
clearInterval(flyingEnemiesInterval); // Clear the spawn loops
clearInterval(chargingEnemiesInterval);
cycleBossAttacks(); // Proceed to the next boss attack
}, totalDuration); // 10 seconds before moving to next attack
}
function bossNewPower() {
let shotsFired = 0;
const maxShots = 15;
const shotCooldown = 800; // 0.8 seconds between shots
const shootInterval = setInterval(() => {
if (shotsFired >= maxShots) {
clearInterval(shootInterval);
// Introduce a delay before invoking the next attack or phase
setTimeout(() => {
cycleBossAttacks(); // Call back the attack cycle function
}, 2000); // 2 seconds for any cooling down or animation effect
return; // Stop firing after maximum shots
}
// Calculate the boss's and player's positions
const bossX = parseInt(boss.style.left) + boss.offsetWidth / 2; // Middle of the boss
const bossY = parseInt(boss.style.bottom) + 30; // Just above the boss
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Middle of the player
const playerY = parseInt(player.style.bottom); // Bottom of the player
// Calculate the direction vector
const dx = playerX - bossX;
const dy = playerY - bossY;
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 10; // Speed of the projectile
// Create a projectile
const projectile = document.createElement('div');
projectile.classList.add('boss-projectile');
projectile.style.width = '10px';
projectile.style.height = '10px';
projectile.style.backgroundColor = 'orange';
projectile.style.position = 'absolute';
projectile.style.left = `${bossX - 5}px`; // Center it
projectile.style.bottom = `${bossY}px`; // Start just above the boss
gameWorld.appendChild(projectile);
let projectileInterval = setInterval(() => {
// Move projectile towards the player
const moveX = (dx / distance) * velocity; // Calculate x movement
const moveY = (dy / distance) * velocity; // Calculate y movement
projectile.style.left = (parseInt(projectile.style.left) + moveX) + 'px';
projectile.style.bottom = (parseInt(projectile.style.bottom) + moveY) + 'px';
// Check collision
if (checkCollision(player, projectile)) {
reduceLives(); // Reduce player lives on hit
projectile.remove(); // Remove projectile on hit
clearInterval(projectileInterval);
}
// Remove projectile if it goes off-screen
if (parseInt(projectile.style.bottom) > gameContainer.offsetHeight ||
parseInt(projectile.style.left) < 0 ||
parseInt(projectile.style.left) > gameContainer.offsetWidth) {
projectile.remove(); // Remove off-screen
clearInterval(projectileInterval);
}
}, 50); // Update every 50ms
shotsFired++;
}, shotCooldown); // Fire every 0.8 seconds
}
function displayBossHealth() {
bossHealthBar.style.display = 'block';
updateBossHealth();
}
function updateBossHealth() {
// Ensure the health bar width updates according to boss health
const maxHealth = 150; // Define the maximum health for level 3
const currentHealth = Math.max(0, Math.min(bossHealth, maxHealth)); // Ensure health stays within bounds
// Calculate the percentage of current health relative to the maximum health
const healthPercentage = (currentHealth / maxHealth) * 100;
// Update the CSS width of the health bar to reflect the current health
bossHealthBar.style.width = `${healthPercentage}%`;
// Optional: Set a different color based on health status (for visual feedback)
if (currentHealth > maxHealth * 0.5) {
bossHealthBar.style.backgroundColor = 'green'; // Healthy
} else if (currentHealth > maxHealth * 0.2) {
bossHealthBar.style.backgroundColor = 'yellow'; // Caution
} else {
bossHealthBar.style.backgroundColor = 'red'; // Critical health
}
}
function spawnBossEnemies() {
if (bossShielded) {
for (let i = 0; i < 2; i++) {
const enemy = createElement('enemy', enemies, parseInt(boss.style.left) + (i * 50));
enemy.style.bottom = `${Math.random() * 250}px`;
}
}
}
function spawnBossBullets() {
const bullet = createBullet();
bullet.style.left = parseInt(boss.style.left) + 'px';
bullet.style.bottom = parseInt(boss.style.bottom) + 30 + 'px';
bullet.style.backgroundColor = 'orange';
gameWorld.appendChild(bullet);
bullets.push({ element: bullet, direction: -1 });
}
function checkBossHit() {
if (!boss || bossShielded) return; // Stop damaging if boss is shielded
bullets.forEach((bullet, bulletIndex) => {
if (checkCollision(bullet.element, boss)) {
bossHealth -= 2; // Decrement boss health
updateBossHealth();
bullet.element.remove();
bullets.splice(bulletIndex, 1);
// Check if the boss is defeated
if (bossHealth <= 0) {
displayMessage("Boss defeated!");
onBossDefeated(); // Trigger boss defeat logic
}
}
});
}
function onBossDefeated() {
bossHealthBar.style.display = 'none'; // Hide health bar upon defeat
const gameMode = localStorage.getItem('gameMode');
let messages = []; // Array to hold messages
// Create messages based on the level
if (level === 1) {
messages = [
"Player: Take THAT!.",
"Boss: Noooo! You may have won, but I'll see you in the next level!",
"Boss: Until next time.",
"Boss: Prepare yourself for what's to come!"
];
} else if (level === 2) {
messages = [
"Player: Heh to ez!.",
"Boss: You think this is the end? I’ll return!",
"Boss: With my new enemies, you will never win!",
"Boss: Prepare for an even greater challenge in Level 3!",
"Boss: You will need to be stronger to defeat me next time!",
];
} else if (level === 3) {
messages = [
"Boss: How could this happen?!",
"Boss: Well... That was all the attacks I had... and you beated them.",
"Boss: Well now worries because my brother knows about this now.",
"Boss: He will make sure your dead.",
"Player: What do you mean your brother is coming to get me?.",
"Boss: The strongest one in our family known for wiping all advanturers.",
"Player: How come he is the strongest.",
"Boss: he is known for his annoying attacks and summoning things.",
"Boss: He plays around to much with his players.",
"Boss: Good luck because this might be your actual last fight...",
"Player: Ok?...",
];
} else if (level === 4) {
messages = [
"Boss: I wish not to speak with you get out.",
];
} else if (level === 5) {
messages = [
"Trials ended",
"Thank you for playing.",
"That is the end of the game.",
];
}
// Function to display messages sequentially
function displaySequentialMessages(messages, index) {
if (index < messages.length) {
displayMessage(messages[index]); // Display current message
// Set timeout for the next message
setTimeout(() => {
displaySequentialMessages(messages, index + 1); // Move to next message
}, 4500); // 4.5 seconds delay
} else {
// After all messages in level 3, unlock Endless Mode
if (level === 6) {
localStorage.setItem('endlessModeUnlocked', 'true');
displayMessage("Endless Mode And sandbox Unlocked!"); // Optional inform player
showEndlessModeButton();
setTimeout(() => {
setupNextLevel(); // Proceed to the next level setup
}, 3000);
} else {
setupNextLevel(); // Proceed to the next level setup
}
}
}
// Start the message display
displaySequentialMessages(messages, 0);
}
function setupNextLevel() {
// Check if in Endless Mode or Story Mode to manage levels separately
if (isEndlessMode) {
// Increment level for Endless mode
currentEndlessLevel++;
localStorage.setItem('endlessLevel', currentEndlessLevel); // Save updated level
displayMessage(`You've advanced to Endless Level ${currentEndlessLevel}!`);
} else {
// Logic for Story Mode - increment story level
storyModeLevel++;
if (storyModeLevel > 5) {
storyModeLevel = 1; // Reset back to Level 1 if beyond max
}
localStorage.setItem('storyLevel', storyModeLevel); // Save updated level
displayMessage(`You've advanced to Story Level ${storyModeLevel}!`);
}
// Set up the boss door to advance to the next level
const bossDoor = document.createElement('div'); // Create a new div for the boss door
bossDoor.classList.add('boss-door'); // Add class for styling
bossDoor.style.position = 'absolute'; // Positioning for the door
bossDoor.style.left = '600px'; // Specify door's X position
bossDoor.style.bottom = '0px'; // Specify door's Y position (ground level)
bossDoor.style.width = '50px'; // Set width
bossDoor.style.height = '100px'; // Set height
bossDoor.style.backgroundColor = 'gold'; // Set background color (for visibility)
bossDoor.style.border = '2px solid black'; // Optional: add a border
bossDoor.style.boxShadow = '0 0 15px rgba(255, 215, 0, 1)'; // Glow effect
// Append the boss door to the game world
gameWorld.appendChild(bossDoor);
// Inform the player they can interact with the door
displayMessage("Press 'E' to proceed to the next level!");
// Allow player to transition to the next level upon interacting
document.addEventListener('keydown', function handleKeyDown(event) {
if (event.key === 'E' || event.key === 'e') {
displayMessage("Refreshing to Level " + level + "...");
location.reload(); // Refresh the page to start the new level
console.log("Page refreshed for Level " + level + ".");
document.removeEventListener('keydown', handleKeyDown); // Remove the event listener to prevent repeated triggering
}
});
}
function clearAllElements() {
// Clear all elements in the game (elements grouped into arrays for easier management)
[obstacles, enemies, bullets.map(b => b.element), powerUps, platforms, bossDoors, coins].forEach(arr => {
arr.forEach(el => el.remove());
arr.length = 0; // Clear the array
});
}
function SendBackToDoor() {
setTimeout(() => {
player.style.left = "50px";
player.style.bottom = "0px";
setTimeout(() => {
activateBossPower();
level = 2; // Reset to Level 1
resetGame();
startLevel(); // Restart the game
}, 2000);
}, 3000);
}
function activateFriendDialogue() {
interactMessage.style.display = 'none';
displayMessage("Thank you for saving me!");
setTimeout(() => {
displayMessage("I got a secret power for you.");
setTimeout(() => {
grantBossPowerUp();
sendbacktodoor();
friend.style.display = 'none';
}, 3000);
}, 2000);
}
let isCharging = false;
let chargeTime = 0;
function shootLaserBlast(chargeTime) {
let laser = document.createElement('div');
laser.classList.add('laser');
laser.style.left = player.style.left;
laser.style.bottom = player.style.bottom;
laser.style.width = `${chargeTime * 10}px`;
laser.style.height = "10px";
laser.style.backgroundColor = "cyan";
gameWorld.appendChild(laser);
setTimeout(() => {
laser.remove();
enemies.forEach(enemy => {
if (checkCollision(laser, enemy)) {
enemy.remove();
}
});
}, 100);
}
function displayMessage(message) {
powerUpMessage.textContent = message;
powerUpMessage.style.display = 'block';
setTimeout(() => {
powerUpMessage.style.display = 'none';
}, 2000);
}
function checkScoreForLevelTransition() {
localStorage.setItem('playerLevel', level); // Save updated level in local stora
}
function spawnFlyingEnemy() {
const flyingEnemy = createElement('flying-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
flyingEnemy.style.bottom = Math.random() * 150 + 50 + 'px';
}
function moveFlyingEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('flying-enemy')) {
// Move the enemy up and down
let enemyYPosition = parseInt(enemy.style.bottom) || 0; // Get current Y position or default to 0
let direction = enemy.dataset.direction ? parseInt(enemy.dataset.direction) : 1; // Up or down direction
// Update vertical position of the flying enemy
enemyYPosition += direction * 1; // Move 1 pixel per frame
enemy.style.bottom = enemyYPosition + 'px';
// Reverse direction if the flying enemy reaches a certain height
if (enemyYPosition >= 150) { // Maximum height
direction = -1; // Change to downward movement
} else if (enemyYPosition <= 0) { // Minimum height (ground)
direction = 1; // Change to upward movement
}
enemy.dataset.direction = direction; // Store the current direction for the next update
// Move the enemy left
enemy.style.left = (parseInt(enemy.style.left) - 2) + 'px';
// Check shooting
if (enemy.dataset.lastShotTime === undefined || Date.now() - enemy.dataset.lastShotTime > 10000) { // Every 10 seconds
shootBulletAtPlayer(enemy); // Shoot at the player
enemy.dataset.lastShotTime = Date.now(); // Update the last shot time
}
// Boundary checking and collision with the player
if (parseInt(enemy.style.left) < 0) {
removeElement(enemy, enemies, index, 30);
} else if (checkCollision(player, enemy)) {
reduceLives();
removeElement(enemy, enemies, index, 0);
}
}
});
}
// Function to shoot a bullet aimed at the player
function shootBulletAtPlayer(enemy) {
const bullet = document.createElement('div');
bullet.classList.add('bullet'); // Add your bullet class
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'orange'; // Bullet color
bullet.style.position = 'absolute';
// Position the bullet at the enemy's current position
bullet.style.left = (parseInt(enemy.style.left) + 20) + 'px'; // Position it slightly in front of the enemy
bullet.style.bottom = (parseInt(enemy.style.bottom) + 15) + 'px'; // Start just below the enemy
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate angle towards the player
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - (parseInt(bullet.style.bottom));
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Bullet movement
const moveBullet = setInterval(() => {
const moveX = (dx / distance) * velocity;
const moveY = (dy / distance) * velocity;
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check if the bullet hits the player or goes off screen
if (checkCollision(bullet, player)) {
reduceLives(); // Damages the player
bullet.remove(); // Remove bullet
clearInterval(moveBullet); // Stop moving the bullet
}
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBullet); // Stop moving the bullet
}
}, 1000 / 60); // 60 FPS
}
function spawnChargingEnemy() {
const chargingEnemy = createElement('charging-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
chargingEnemy.style.bottom = '0px';
chargingEnemy.setAttribute('data-charge', '0');
chargingEnemy.classList.add('charging-prep'); // Fading in/out red glow effect
}
function createChargeTrail(x, y) {
const trail = document.createElement('div');
trail.classList.add('charge-trail');
trail.style.left = x + 'px';
trail.style.bottom = y + 'px';
gameWorld.appendChild(trail);
setTimeout(() => trail.remove(), 400); // Shorter effect for smoothness
}
function moveChargingEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('charging-enemy')) {
let chargeTime = parseInt(enemy.getAttribute('data-charge'));
if (chargeTime < 60) {
// Charging phase (fading in/out glow)
enemy.setAttribute('data-charge', chargeTime + 1);
} else {
// Fully Charged (Solid Red Outline + Charging Forward)
enemy.classList.remove('charging-prep');
enemy.classList.add('charging-active');
enemy.style.left = (parseInt(enemy.style.left) - 6) + 'px';
// Create a bright red trail
createChargeTrail(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
}
// Check collision with player
if (checkCollision(player, enemy)) {
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom)); // Impact explosion
reduceLives();
removeElement(enemy, enemies, index, 0);
}
// Remove if out of bounds
if (parseInt(enemy.style.left) < gameScrollPosition - enemy.offsetWidth) {
removeElement(enemy, enemies, index, 30);
}
}
});
}
function spawnStealthEnemy() {
const stealthEnemy = createElement('stealth-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
stealthEnemy.setAttribute('data-stealth', '0');
stealthEnemy.style.backgroundColor = '#555';
stealthEnemy.style.opacity = '1';
}
function moveStealthEnemies() {
enemies.forEach(en => {
if (en.classList.contains('stealth-enemy')) {
const stealth = parseInt(en.getAttribute('data-stealth'));
if (stealth > 50) {
en.style.opacity = en.style.opacity == '0' ? '1' : '0';
en.setAttribute('data-stealth', '0');
} else {
en.setAttribute('data-stealth', stealth + 1);
}
}
});
}
function moveSniperEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('sniper-enemy')) {
const enemyX = parseInt(enemy.style.left) + enemy.offsetWidth / 2; // Center position
const enemyY = parseInt(enemy.style.bottom) + enemy.offsetHeight; // Height
// Shooting mechanism
if (!enemy.dataset.lastShotTime || Date.now() - enemy.dataset.lastShotTime > 3000) { // Every 3 seconds
shootAtPlayer(enemy);
enemy.dataset.lastShotTime = Date.now(); // Update for next shot
}
// Check distance to player
const distanceToPlayer = Math.sqrt(Math.pow(enemyX - (parseInt(player.style.left) + player.offsetWidth / 2), 2) +
Math.pow(enemyY - parseInt(player.style.bottom), 2));
if (distanceToPlayer < 300) { // Within 300 pixels visibility range
enemy.style.opacity = '1'; // Fully visible
} else {
enemy.style.opacity = '0.5'; // Reduced opacity
}
}
});
}
// Function for the sniper to shoot at the player
function shootAtPlayer(enemy) {
const bullet = document.createElement('div');
bullet.classList.add('bullet'); // Add your class for the bullet
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'red'; // Bullet color
bullet.style.position = 'absolute';
// Position the bullet at the sniper's location
bullet.style.left = (parseInt(enemy.style.left) + 20) + 'px'; // Set slight offset
bullet.style.bottom = (parseInt(enemy.style.bottom) + 15) + 'px'; // Set above the sniper
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate angle towards the player
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - (parseInt(bullet.style.bottom));
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Bullet movement logic
const moveBullet = setInterval(() => {
const moveX = (dx / distance) * velocity; // Calculate x movement direction
const moveY = (dy / distance) * velocity; // Calculate y movement direction
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check for bullet collision with player
if (checkCollision(bullet, player)) {
reduceLives(); // Damage player
bullet.remove(); // Remove bullet
clearInterval(moveBullet); // Stop bullet movement
}
// Remove bullet if it goes off screen
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBullet); // Stop bullet movement
}
}, 1000 / 60); // 60 FPS
}
function spawnSplitterEnemy() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const splitter = createElement('split-enemy', enemies, leftPos);
splitter.style.left = leftPos + 'px';
splitter.style.bottom = '0px';
splitter.classList.add('split'); // Important!
splitter.style.width = '50px';
splitter.style.height = '50px';
}
function moveSplitterEnemies() {
enemies.forEach((enemy, index) => {
if (enemy.classList.contains('split-enemy')) {
const speed = enemy.classList.contains('split') ? 1 : 3; // mini splitters move faster
enemy.style.left = (parseInt(enemy.style.left) - speed) + 'px';
if (parseInt(enemy.style.left) < gameScrollPosition - enemy.offsetWidth) {
removeElement(enemy, enemies, index, 30); // Give some score for killing
} else if (checkCollision(player, enemy)) {
reduceLives();
removeElement(enemy, enemies, index, 0);
}
}
});
}
function handleSplitterEnemyHit(enemy, enemyIndex) {
console.log('Splitter hit triggered for enemy:', enemy);
if (enemy.classList.contains('split')) { // just to be sure
const baseLeft = parseInt(enemy.style.left);
const baseBottom = enemy.style.bottom;
const miniSize = 20; // Increased size from 15 to 20 px
const gap = 10; // Gap between minis (pixels)
for (let i = 0; i < 2; i++) { // Spawn only 2 mini enemies
// Calculate left position for each mini enemy to have gap between them
const miniLeft = baseLeft + i * (miniSize + gap);
const miniEnemy = createElement('split-enemy', enemies, miniLeft);
miniEnemy.style.width = miniSize + 'px';
miniEnemy.style.height = miniSize + 'px';
miniEnemy.classList.remove('split');
miniEnemy.style.bottom = baseBottom; // Appear at the same vertical pos
}
}
removeElement(enemy, enemies, enemyIndex, 0);
}
function handleExplosiveEnemyHit(enemy, enemyIndex) {
removeElement(enemy, enemies, enemyIndex, 0);
enemies.forEach((otherEnemy, index) => {
if (calculateDistance(enemy, otherEnemy) < 50) {
removeElement(otherEnemy, enemies, index, 0);
}
});
createBurst(parseInt(enemy.style.left), parseInt(enemy.style.bottom));
reduceLives();
}
function spawnSniperEnemy() {
const sniper = createElement('sniper-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
sniper.style.bottom = '200px';
const sniperInterval = setInterval(() => {
if (Math.random() < 0.05 && calculateDistance(player, sniper) < 300) {
const bullet = createBullet();
bullet.style.left = parseInt(sniper.style.left) + 'px';
bullet.style.bottom = parseInt(sniper.style.bottom) + 'px';
bullet.style.backgroundColor = 'red';
gameWorld.appendChild(bullet);
bullets.push({ element: bullet, direction: -1 });
}
}, 1000);
sniper.setAttribute('data-interval', sniperInterval);
}
// Function to spawn the reflective enemy at the bottom
function spawnReflectiveEnemy() {
// Create the reflective enemy element
var reflectiveEnemy = createElement('reflective-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
// Set initial attributes
reflectiveEnemy.setAttribute('data-health', '10'); // Set initial health or reflection strength
reflectiveEnemy.classList.add('outlined'); // Add CSS class for glowing outline
reflectiveEnemy.dataset.reflectiveHits = 0; // Initialize reflection hits tracking
// Position the reflective enemy at the bottom of the game world
reflectiveEnemy.style.bottom = '0px'; // Set to 0 for the bottom position
// Set up shooting capability
reflectiveEnemy.dataset.lastShotTime = Date.now(); // Track when the last shot was fired
// Append the reflective enemy to the game world
gameWorld.appendChild(reflectiveEnemy);
}
// Function to handle shooting bullets at the player
function shootAtPlayer2(reflectiveEnemy) {
const bullet = document.createElement('div');
bullet.classList.add('enemy-bullet'); // Style class for the bullet
bullet.style.width = '10px';
bullet.style.height = '5px';
bullet.style.backgroundColor = 'red'; // Enemy bullet color
bullet.style.position = 'absolute';
// Position the bullet at the enemy's location
bullet.style.left = `${parseInt(reflectiveEnemy.style.left) + 20}px`; // Center the bullet
bullet.style.bottom = `${parseInt(reflectiveEnemy.style.bottom) + 15}px`; // Above the enemy
gameWorld.appendChild(bullet); // Add bullet to the game world
// Calculate the player’s position
const playerX = parseInt(player.style.left) + player.offsetWidth / 2; // Center of player
const playerY = parseInt(player.style.bottom); // Bottom of player
// Calculate direction towards the player
const dx = playerX - (parseInt(bullet.style.left) + bullet.offsetWidth / 2);
const dy = playerY - parseInt(bullet.style.bottom);
const distance = Math.sqrt(dx * dx + dy * dy);
const velocity = 5; // Speed of the bullet
// Move the bullet towards the player
const moveBulletInterval = setInterval(() => {
const moveX = (dx / distance) * velocity; // Calculate x movement
const moveY = (dy / distance) * velocity; // Calculate y movement
bullet.style.left = (parseInt(bullet.style.left) + moveX) + 'px';
bullet.style.bottom = (parseInt(bullet.style.bottom) + moveY) + 'px';
// Check for collision with player
if (checkCollision(player, bullet)) {
reduceLives(); // Damage player
bullet.remove(); // Remove bullet
clearInterval(moveBulletInterval); // Stop movement
}
// Remove bullet if it goes off screen
if (parseInt(bullet.style.left) < 0 || parseInt(bullet.style.left) > gameWorld.offsetWidth || parseInt(bullet.style.bottom) < 0) {
bullet.remove(); // Remove bullet if it goes off screen
clearInterval(moveBulletInterval); // Stop movement
}
}, 1000 / 60); // Move at 60 FPS
}
// Update reflective enemy behavior to shoot at player on hit
function handleBulletCollisionWithReflectiveEnemy(reflectiveEnemy, bullet) {
// Check if the bullet collides with the reflective enemy
if (checkCollision(reflectiveEnemy, bullet)) {
bullet.remove(); // Remove bullet on collision
reflectiveEnemy.dataset.reflectiveHits++; // Track the number of hits
// Call the shoot function after a certain interval
if (Date.now() - reflectiveEnemy.dataset.lastShotTime >= 1) { // Cooldown for 2 seconds between shots
shootAtPlayer2(reflectiveEnemy);
reflectiveEnemy.dataset.lastShotTime = Date.now(); // Reset the shot time
}
}
}
function spawnNewInteractiveEnemies() {
const randomFeature = Math.random();
if (randomFeature < 0.2) {
spawnTeleporterEnemy();
} else if (randomFeature < 0.6) {
spawnVelocityEnemy();
} else if (randomFeature < 0.8) {
spawnStickyEnemy();
} else {
spawnReflectiveEnemy();
}
}
function moveNewEnemies() {
moveTeleporterEnemies();
moveVelocityEnemies();
moveStickyEnemies();
}
// New enemies with creative features
function spawnTeleporterEnemy() {
const teleporterEnemy = createElement('teleporter-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
teleporterEnemy.style.backgroundColor = '#8A2BE2';
teleporterEnemy.style.bottom = '0px';
teleporterEnemy.setAttribute('data-teleport', '0');
}
function moveTeleporterEnemies() {
enemies.forEach(en => {
if (en.classList.contains('teleporter-enemy')) {
const teleport = parseInt(en.getAttribute('data-teleport'));
if (teleport > 100) {
en.style.left = Math.random() * (gameWorld.offsetWidth - en.offsetWidth) + 'px';
en.setAttribute('data-teleport', '0');
} else {
en.setAttribute('data-teleport', teleport + 1);
}
}
});
}
function moveReflectiveEnemies() {
var enemiesToRemove = []; // Array to track enemies to be removed
enemies.forEach(function(en, index) {
if (en.classList.contains('reflective-enemy')) {
// Move the reflective enemy to the left
en.style.left = (parseInt(en.style.left) - 1) + 'px';
// Check for collision with bullets
bullets.forEach(function(bullet, bulletIndex) {
// If the bullet collides with the reflective enemy
if (checkCollision(en, bullet.element)) {
// Reflect the bullet's direction
bullet.direction *= -1;
// Adjust bullet position slightly to avoid getting stuck
bullet.element.style.left = (parseInt(bullet.element.style.left) + (bullet.direction * 5)) + 'px';
// Increment the reflective hits count
en.dataset.reflectiveHits++;
// Optional: handle when you want to remove the enemy after a number of hits, if desired
if (parseInt(en.dataset.reflectiveHits) >= 10) {
enemiesToRemove.push(index); // Mark for removal
}
// Remove the bullet
bullet.element.remove();
bullets.splice(bulletIndex, 1); // Remove bullet from the bullets array
}
});
// Check for collision with the player
if (checkCollision(player, en)) {
reduceLives(); // Reduce lives if player hits the reflective enemy
enemiesToRemove.push(index); // Mark enemy for removal
}
// Remove if off-screen
if (parseInt(en.style.left) < -en.offsetWidth) {
enemiesToRemove.push(index); // Mark for removal
}
}
});
// Remove enemies marked for removal
enemiesToRemove.reverse().forEach(function(index) {
removeElement(enemies[index], enemies, index, 0);
});
}
function spawnVelocityEnemy() {
const velocityEnemy = createElement('velocity-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
velocityEnemy.style.backgroundColor = '#FF4500';
velocityEnemy.style.bottom = Math.random() * 150 + 'px';
velocityEnemy.setAttribute('data-speed', '2');
}
function moveVelocityEnemies() {
enemies.forEach(en => {
if (en.classList.contains('velocity-enemy')) {
const speed = parseInt(en.getAttribute('data-speed'));
en.style.left = (parseInt(en.style.left) - speed) + 'px';
en.style.bottom = (parseInt(en.style.bottom) + (Math.random() - 0.5) * 10) + 'px';
if (parseInt(en.style.left) < gameScrollPosition - en.offsetWidth) {
removeElement(en, enemies, 0, 30);
} else if (checkCollision(player, en)) {
reduceLives();
removeElement(en, enemies, 0, 0);
}
}
});
}
function spawnVirusDrone() {
const leftPos = gameScrollPosition + gameContainer.offsetWidth;
const virus = document.createElement('div');
virus.classList.add('virus-drone');
virus.style.bottom = '380px'; // top of screen (400px height container minus 20 px)
virus.style.left = leftPos + 'px';
virus.dataset.speed = 8;
virus.dataset.activeEnemy = ''; // enemy it’s currently infecting
gameWorld.appendChild(virus);
viruses.push(virus); // store in separate array to manage separately
}
function spawnStickyEnemy() {
const stickyEnemy = createElement('sticky-enemy', enemies, gameScrollPosition + gameContainer.offsetWidth);
stickyEnemy.style.backgroundColor = '#FFD700';
stickyEnemy.style.bottom = '0px';
}
function moveStickyEnemies() {
enemies.forEach(en => {
if (en.classList.contains('sticky-enemy')) {
en.style.left = (parseInt(en.style.left) - 1) + 'px';
if (checkCollision(player, en)) {
player.style.backgroundColor = '#FFD700';
setTimeout(() => player.style.backgroundColor = '#4CAF50', 2000);
removeElement(en, enemies, 0, 0);
}
}
});
}
</script>
</body>
</html>There is a clear **syntax error** in your `gameLoop` function related to unmatched braces and incorrect `if..else` structure. Specifically, this portion:
```js
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
const gameMode = localStorage.getItem('gameMode'); // Retrieve the current game mode
if (gameMode === 'endless') {
spawnEndlessEntities(); // Use the endless logic for enemy spawns
switchLevelBackground(); // Set the background based on the endless level
} else {
spawnEntities(); // Handle entity spawning for story mode
}
} else if (gameMode === 'sandbox') {
// Sandbox mode - NO auto spawning of enemies, obstacles, power-ups, platforms etc.
// Just skip spawning entirely
// Possibly update background or level here if you want
level = 1; // Force level 1 if you want
}
moveElements();
// Check for collisions between the player and enemies
handlePlayerEnemyCollision();
// Update score display in the shop if applicable
updateShopScoreDisplay();
// Check if the player's score meets the criteria for level transitions
checkScoreForLevelTransition();
updateBoostDrainer();
handlePlayerEnemyCollision(); // Check for collisions
updateDisplay(); // Update the scoreboard, lives, etc.
}
}
```
---
### The problem is:
- You close your initial `if (!isPaused)` block *too early* with `}` before `else if (gameMode === 'sandbox')`
- The `else if` is **outside** the `if (!isPaused)` block → this is invalid.
- Your `else if` logically belongs to the `if (gameMode === ...)` inside the `if (!isPaused)`.
---
### Corrected structure:
You want: Inside `if (!isPaused)` block — check the gameMode and do spawns accordingly.
```js
function gameLoop() {
if (!isPaused) {
updatePlayerPosition();
const gameMode = localStorage.getItem('gameMode');
if (gameMode === 'endless') {
spawnEndlessEntities();
switchLevelBackground();
} else if (gameMode === 'story') {
spawnEntities();
} else if (gameMode === 'sandbox') {
// Sandbox mode: No auto spawning
level = 1; // Optional: force level here
}
moveElements();
handlePlayerEnemyCollision();
updateShopScoreDisplay();
checkScoreForLevelTransition();
updateBoostDrainer();
updateDisplay();
}
}
```
---
### Summary:
- Remove the extra closing brace `}` after spawning for `story`—it should be:
```js
if (gameMode === 'endless') {
...
} else if (gameMode === 'story') {
...
} else if (gameMode === 'sandbox') {
...
}
```
without closing the outer `if (!isPaused)` too soon.
---
### Tips for debugging:
- Use a good code editor (VSCode, etc.) with brace matching.
- Use `console.log` at start of `gameLoop` to verify flow.
- Run JS syntax validators or linters.
---
If you want, I can extract your fixed `gameLoop` function code snippet only—just ask!fab9ab462927669393f7a256why wont the UI for sandbox mode show up
If your sandbox mode UI (`#sandbox-panel`) isn’t showing up, here’s a checklist and typical causes along with how to fix them in your situation:
---
### 1. Make sure `#sandbox-panel` is set to `display: block` when sandbox starts
In your `startSandboxMode()` function, you need to **explicitly show** the panel:
```js
function startSandboxMode() {
isSandboxMode = true;
// Hide other UI parts
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
// Show sandbox panel
let sandboxPanel = document.getElementById('sandbox-panel');
sandboxPanel.style.display = 'block';
clearAllElements();
// Reset player position etc...
// ...
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
```
**Check you actually have this line:**
```js
document.getElementById('sandbox-panel').style.display = 'block';
```
---
### 2. Ensure `#sandbox-panel` CSS is NOT overridden elsewhere
Your `<div id="sandbox-panel">` should start hidden by CSS:
```css
#sandbox-panel {
display: none; /* hidden by default */
/* your other styles */
}
```
When showing, the JS sets `display: block`.
If some other CSS or JS is hiding it again, you won’t see the panel.
---
### 3. Verify your button is properly wired to `startSandboxMode()`
Your button in `#game-mode-options`:
```html
<button onclick="startSandboxMode()" class="button" style="background:#c39;">Sandbox Mode</button>
```
Check in browser console:
```js
typeof startSandboxMode
```
Should be `"function"`.
If it’s undefined or error occurs, the function is not defined or not in global scope.
---
### 4. Confirm no JavaScript errors prevent UI updates
- Open browser Developer Tools Console.
- Check if any errors occur.
- Errors can stop script execution before showing the panel.
---
### 5. Try a minimal test - manually show the panel in console
In browser console, try:
```js
document.getElementById('sandbox-panel').style.display = 'block';
```
If it shows, the problem is in your switching logic.
---
### 6. Make sure your `startSandboxMode()` is actually called
Add a debug at the start of `startSandboxMode()`:
```js
function startSandboxMode() {
console.log("Sandbox mode started");
...
}
```
Check that you see the log in console when clicking the button.
---
### 7. Verify no conflicting element covers the panel
Sometimes other `z-index` or overlays block interaction or visibility.
Your `#sandbox-panel` has `z-index: 1000;` which is usually enough.
You can try temporarily giving it `position: fixed` and larger dimensions to isolate.
---
### 8. Verify your HTML structure
Make sure your `#sandbox-panel` is inside (or at least not *outside*) the game container or body and *not* inside elements with `overflow:hidden` that might clip it.
---
## TL;DR
To **fix your sandbox UI not showing**:
- In `startSandboxMode()`, add **`document.getElementById('sandbox-panel').style.display = 'block';`**
- Verify the button calls the function
- Check console for JS errors
- Verify CSS is not forcing `display: none` or hidden via other rules
- Test showing panel manually in browser console
---
If you want, paste your current `startSandboxMode` and `#sandbox-panel` HTML + CSS here, I can review it line-for-line. But usually one of these above is the cause.6e79ab80736ea5ccc146eb91check this
function startSandboxMode() {
isSandboxMode = true;
// Hide mode options, upgrades, etc
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
document.getElementById('sandbox-panel').style.display = 'block';
// Clear all enemies/obstacles/powerUps/etc
clearAllElements();
// Reset scores, lives, etc for sandbox
score = 0;
lives = 3;
level = 1;
updateDisplay();
// Reset player position and show player
playerPositionX = 50;
playerPositionY = 0;
playerVelocityX = 0;
playerVelocityY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
// Start game loop for sandbox (no auto spawn)
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}Your `startSandboxMode` function looks essentially correct. However, here are the key points to check/fix to ensure the sandbox UI actually shows up:
### 1. **Ensure the HTML element with id `sandbox-panel` exists**
Make sure your HTML contains exactly:
```html
<div id="sandbox-panel" style="display:none; /* other styles... */">
<!-- sandbox UI buttons or content -->
</div>
```
The `display: none` in CSS (or inline) hides it by default, so your JS needs to set `.style.display = 'block'` to make it visible.
---
### 2. **Check for any CSS that may override or constrain the panel**
For example:
- Is `#sandbox-panel` inside an element with `overflow: hidden` or other clipping styles?
- Is its `z-index` high enough? (You currently have `z-index: 1000;` which is good)
- Is `position: absolute` or `fixed` properly applied?
Example minimal working CSS:
```css
#sandbox-panel {
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0, 0, 0, 0.85);
color: white;
padding: 15px;
border-radius: 10px;
z-index: 1000;
display: none; /* initially hidden */
}
```
---
### 3. **Verify the function is actually called**
Add a `console.log` at the start of `startSandboxMode()` to ensure the event triggers correctly:
```js
function startSandboxMode() {
console.log("Sandbox mode activated");
...
}
```
If you don't see this message when clicking your sandbox button, your button's `onclick` may be incorrect or the script not loaded.
---
### 4. **Manually test showing panel from browser console**
Open your browser devtools and run:
```js
document.getElementById('sandbox-panel').style.display = 'block';
```
If the UI appears, the issue is in when/how your JS sets display.
---
### 5. **Make sure starting other elements are hidden or shown appropriately**
Your code hides:
```js
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
```
It might be worth also checking:
- Is there any other UI blocking or covering your panel?
- Some overlay with high z-index preventing interaction?
---
### 6. **Ensure that `clearAllElements()` and reset logic don't remove or hide the panel**
Since you call:
```js
clearAllElements();
```
Make sure it does **not** select and remove `#sandbox-panel` from the DOM, or hide it by some other means.
---
### 7. **Verify that your sandbox controls inside `#sandbox-panel` are visible**
Make sure your panel has actual content with reasonable styling and no opacity or display issues.
---
### Summary / Minimal Check
```js
function startSandboxMode() {
console.log("Sandbox mode activated");
isSandboxMode = true;
document.getElementById('sandbox-panel').style.display = 'block';
document.getElementById('game-mode-options').style.display = 'none';
document.getElementById('instructions').style.display = 'none';
document.getElementById('upgrades-menu').style.display = 'none';
clearAllElements();
score = 0;
lives = 3;
level = 1;
updateDisplay();
playerPositionX = 50;
playerPositionY = 0;
player.style.left = playerPositionX + 'px';
player.style.bottom = playerPositionY + 'px';
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, 1000 / 60);
}
```
If after these checks your UI still doesn't appear, try to isolate the problem by:
- Temporarily giving `#sandbox-panel` a big size, bright background, and fixed position.
- Make sure no parent element hides it.
- Confirm no runtime errors in browser console.
---
If you want, I can help you create a minimal working snippet to test the sandbox panel visibility from scratch to debug. Just ask!2f29460f0204b52af8fdea24check this <div id="sandbox-panel" style="display:none;">
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;
">
<h3>Sandbox Controls</h3>
<div>
<button onclick="sandboxSpawn('enemy')" style="margin:4px;">Spawn Basic Enemy</button><br>
<button onclick="sandboxSpawn('flying-enemy')" style="margin:4px;">Spawn Flying Enemy</button><br>
<button onclick="sandboxSpawn('super-speed')" style="margin:4px;">Spawn Super Speed Power-up</button><br>
<button onclick="sandboxSpawn('extra-life')" style="margin:4px;">Spawn Extra Life</button><br>
<button onclick="sandboxClear()" style="background:#a33; color:#fff; margin-top:10px;">Clear Spawned</button>
</div>
</div>The issue is that you have placed **CSS styles inside your `<div>` as text content**, not inside a proper `style` attribute.
Your code currently looks like this:
```html
<div id="sandbox-panel" style="display:none;">
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;
">
<h3>Sandbox Controls</h3>
...
</div>
```
### Problems here:
- You're putting CSS **properties as raw text inside** the `<div>` — this is invalid HTML so the browser just treats that as text inside the div, which is not what you want.
- The `style` attribute only has `display:none;` — the rest isn't included as styles but as HTML text content.
---
### Correct way: put all CSS inside the `style` attribute on the **opening tag** like this:
```html
<div id="sandbox-panel" style="
position: absolute;
top: 10px;
right: 10px;
width: 220px;
background: rgba(0,0,0,0.85);
color: white;
padding: 15px;
border-radius: 10px;
font-family: Arial, sans-serif;
z-index: 1000;
display: none;">
<h3>Sandbox Controls</h3>
<div>
<button onclick="sandboxSpawn('enemy')" style="margin:4px;">Spawn Basic Enemy</button><br>
<button onclick="sandboxSpawn('flying-enemy')" style="margin:4px;">Spawn Flying Enemy</button><br>
<button onclick="sandboxSpawn('super-speed')" style="margin:4px;">Spawn Super Speed Power-up</button><br>
<button onclick="sandboxSpawn('extra-life')" style="margin:4px;">Spawn Extra Life</button><br>
<button onclick="sandboxClear()" style="background:#a33; color:#fff; margin-top:10px;">Clear Spawned</button>
</div>
</div>
```
---
### Summary:
- Remove the CSS lines INSIDE the `<div>` that are just text.
- Wrap all the CSS **inside** the `style=""` attribute on the opening `<div>` tag.
- Make sure to keep `display: none;` initially, and toggle with JS.
After fixing the above, your sandbox panel will render properly and show/hide correctly when your JS sets `display: 'block'`.
---
If you want, I can help you fix your exact snippet or provide a minimal complete example!