A downloadable game

========================================================================

       DOCUMENTATIE TEHNICA: OLIMP ROYALE (Spartacus Edition)

========================================================================

1. INFORMATII GENERALE

------------------------------------------------------------------------

* Numele Jocului: Olimp Royale - Spartacus Edition

* Tehnologie utilizata: HTML5, CSS3, JavaScript (ES6+), Framework-ul Phaser 3 (v3.60.0 Arcade Physics)

* Gen: Top-Down 2D Action / Rogue-lite elementar

* Rezolutie ecran: 800 x 600 pixeli (centrat pe ecran)

Echipa de Dezvoltare:

* Ana Serban: Dezvoltare software (Cod), Structura arhitecturala, Documentatie tehnica si Design Grafic.

* Maria Trancioveanu: Design Grafic, Procesare asset-uri vizuale (cadre personaje).

* Alexandra Dimian: Design Grafic, Ilustratii elemente de decor si interfata.

2. CONCEPTUL SI MECANICA JOCULUI

------------------------------------------------------------------------

Jocul transpune jucatorul in Atena anului 430 i.Hr., unde retraieste o batalie mitologica prin ochii a trei zei selectabili din Olimp. Obiectivul principal este supravietuirea in fata valurilor de monstri si infrangerea unui Boss final.

Mecanica de Combat (Rock-Paper-Scissors):

Inamicii normali si Boss-ul au vulnerabilitati elementare stricte. Jucatorul trebuie sa schimbe dinamic arma sau elementul selectat folosind tastele 1, 2, 3 pentru a aplica daune:

Inamic Galben   -> Vulnerabilitate: scut (Tasta 1)    -> Denumire: Oglinda

Inamic Rosu     -> Vulnerabilitate: foc (Tasta 2)     -> Denumire: Foc

Inamic Albastru -> Vulnerabilitate: sulita (Tasta 3)   -> Denumire: Sulita

Regula: Daca inamicul este lovit cu un element gresit, proiectilul este distrus fara a produce daune. Inamicii normali mor dintr-o singura lovitura corecta.

Obstacole practice (Vazele / Amforele):

Pe harta sunt generate static 8 amfore. Acestea actioneaza ca scuturi pentru inamici: daca un proiectil loveste o vaza, acesta este absorbit si distrus. Vazele se distrug la impact si reapar automat dupa un timp de reincarcare de 5 secunde.

3. PERSONAJE (ZEII DIN OLIMP)

------------------------------------------------------------------------

Fiecare zeu selectabil ofera un stil de joc diferit prin modificarea atributelor de baza:

* Zeus: Viteza echilibrata (200), proiectile foarte rapide (800) si rata de foc mare (cooldown mic de 200ms). Are o animatie complexa formata din 23 de cadre.

* Poseidon: Viteza mare de deplasare (260), proiectile lente (400) dar masive ca dimensiune (scalare 2.0). Animatie de mers din 8 cadre.

* Ares: Viteza cea mai mica (180), cooldown mare la atac (1000ms), dar atacurile sale sunt liniare, de tip sabie lunga, capabile sa strapunga si sa distruga vazele fara ca proiectilul sa dispara.

4. STRUCTURA SCENELOR (ARHITECTURA PHASER)

------------------------------------------------------------------------

Jocul este modularizat in 3 scene native Phaser care ruleaza secvential:

Scena 0: IntroScene (Prolog)

* Rol: Introducere narativa si imersie.

* Functionalitate: Ruleaza un text dramatic rand cu rand folosind un sistem recursiv (showNextLine) bazat pe efecte de tranzitie Tween (Fade In / Hold / Fade Out). La final, face blackout pe camera si porneste meniul.

Scena 1: MenuScene (Selectie)

* Rol: Interfata principala si configurare erou.

* Functionalitate: Randeaza fundalul cosmic si genereaza butoanele de selectie in mod dinamic, iterand prin proprietatile obiectului global GODS. Transmite obiectul zeului ales ca parametru de initializare catre scena urmatoare.

Scena 2: GameScene (Jocul propriu-zis)

* Miezul jocului: Gestioneaza motorul de fizica, inputul de la tastatura/mouse, coliziunile, inteligenta artificiala a inamicilor si UI-ul.

* Harta si Coliziuni: Prin functia createRestrictedBlueAreas(), se construiesc pereti invizibili (corpuri fizice statice) peste decorul grafic (temple, munti, cladiri, margini) pentru a impiedica jucatorul sau monstrii sa paraseasca drumurile pavate.

5. INTELIGENTA ARTIFICIALA SI LOGICA DE BOSS

------------------------------------------------------------------------

Inamicii Normali:

* Apar la fiecare 3 secunde in puncte sigure de pe alei (spawnEnemy).

* Daca distanta pana la jucator este mai mare de 200 de pixeli, acestia patruleaza haotic (directie unghiulara aleatorie calculata trigonometric prin cosinus si sinus).

* Daca jucatorul intra in raza de 200 de pixeli, AI-ul comuta pe urmarire agresiva spre coordonatele jucatorului.

Boss-ul Final:

* Se declanseaza fix la atingerea pragului de 20 de inamici invinsi (kills == 20).

* Are o bara dedicata de viata in partea de sus a ecranului (1500 HP).

* Abilitate speciala 1 (Slow): La fiecare 6 secunde, flash-uieste ecranul si incetineste viteza jucatorului cu 25% timp de 2 secunde.

* Abilitate speciala 2 (Adaptabilitate): La fiecare 3 secunde isi schimba aleatoriu vulnerabilitatea elementara si culoarea corespunzatoare.

* Faza a doua (Rage): Cand viata Boss-ului scade sub 750 HP, viteza lui de miscare creste de la 120 la 170.

6. CONTROL SI INTERFATA (USER INPUT)

------------------------------------------------------------------------

* Miscare: Tastele W (Sus), A (Stanga), S (Jos), D (Dreapta). Vectorul de miscare este normalizat pentru a preveni accelerarea rapida pe diagonala.

* Atac: Tasta SPACE sau Click Stanga Mouse (trage in directia cursorului).

* Schimbare elemente: Tastele 1, 2, 3.

* Meniu Ajutor: Butonul HELP (sus, dreapta) opreste complet fizica si jocul prin physics.pause(), afisand ghidul elementelor.

7. MANAGEMENTUL RESURSELOR (OPTIMIZARE)

------------------------------------------------------------------------

* Font Personalizat: Fisierul Spartacus-KVdLp.ttf este incarcat binar direct in memoria cache Phaser si injectat securizat in DOM-ul paginii web prin API-ul nativ FontFace inainte de randarea textelor din UI.

* Sistemul de Damage: La contactul cu inamicii, jucatorul pierde 25 HP si primeste o secunda de invulnerabilitate (flag-ul isHurt), timp in care sprite-ul clipeste prin modificarea opacitatii (setAlpha), prevenind moartea instantanee in acelasi cadru de procesare.


Codul jocului este:


<!DOCTYPE html>

<html lang="ro">

<head>

    <meta charset="UTF-8">

    <title>Olimp Royale</title>

    <!-- Importarea framework-ului Phaser 3 prin CDN -->

    <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>

    

    <style>

        /* Resetarea marginilor și centrarea canvas-ului jocului pe ecran */

        body { 

            margin: 0; 

            background: #000; 

            display: flex; 

            justify-content: center; 

            align-items: center; 

            height: 100vh; 

            overflow: hidden;

        }

    </style>

</head>

<body>

<script>

// ==========================================

// --- CONFIGURAȚII GLOBALE ȘI STATICE ---

// ==========================================

// Obiect ce stochează atributele unice ale fiecărui personaj (zeu) selectabil

const GODS = {

    ZEUS: { name: 'Zeus', color: 0xffff00, speed: 200, bSpeed: 800, bScale: 0.6, cooldown: 200 },

    POSEIDON: { name: 'Poseidon', color: 0x00aaff, speed: 260, bSpeed: 400, bScale: 2.0, cooldown: 500 },

    ARES: { name: 'Ares', color: 0xff4400, speed: 180, bSpeed: 500, bScale: 1.5, cooldown: 1000 }

};

const FONT = 'Spartacus';      // Numele fontului personalizat utilizat în UI

let selectedGod = GODS.ZEUS;   // Reține zeul selectat curent (implicit Zeus)

let kills = 0;                 // Contorizează inamicii învinși

let isBossActive = false;      // Flag pentru a ști dacă bătălia cu Boss-ul a început

// ==========================================

// --- SCENA 0: PROLOG / INTRODUCERE ---

// ==========================================

class IntroScene extends Phaser.Scene {

    constructor() { super('IntroScene'); }

    

    create() {

        // Declanșează povestea după un scurt delay (150ms)

        this.time.delayedCall(150, () => {

            const lines = [

                "Atena, anul 430 i.Hr...",

                "Un batran grec priveste umbrele templului.",

                "In mintea sa, zeii coboara din Olimp...",

                "Povestea lui incepe acum."

            ];

            let currentLine = 0;

            

            // Crearea obiectului text (inițial invizibil prin setAlpha(0))

            let storyText = this.add.text(400, 300, '', { 

                fontFamily: FONT, 

                fontSize: '35px', 

                fill: '#ffd700', 

                align: 'center' 

            }).setOrigin(0.5).setAlpha(0);

            // Funcție recursivă pentru afișarea textului rând cu rând (Fade In -> Hold -> Fade Out)

            const showNextLine = () => {

                if (currentLine < lines.length) {

                    storyText.setText(lines[currentLine]);

                    this.tweens.add({

                        targets: storyText,

                        alpha: 1,

                        duration: 1000,

                        hold: 1500,

                        yoyo: true, // Face fade out automat după ce trece timpul de 'hold'

                        onComplete: () => { 

                            currentLine++; 

                            showNextLine(); 

                        }

                    });

                } else { 

                    // Schimbă scena către meniu după ce s-au terminat replicile

                    this.cameras.main.fadeOut(1000, 0, 0, 0); 

                    this.cameras.main.once('camerafadeoutcomplete', () => this.scene.start('MenuScene')); 

                }

            };

            showNextLine();

        });

    }

}

// ==========================================

// --- SCENA 1: MENIUL PRINCIPAL ---

// ==========================================

class MenuScene extends Phaser.Scene {

    constructor() { super('MenuScene'); }

    

    preload() { 

        // Încărcarea imaginii de fundal pentru meniu

    }

    

    create() {

        this.add.image(400, 300, 'bg_menu').setAlpha(0.5);

        this.add.text(400, 150, 'OLIMP ROYALE', { fontFamily: FONT, fontSize: '64px', fill: '#ffd700' }).setOrigin(0.5);

        

        let y = 320; // Poziția inițială pe verticală a butoanelor

        

        // Generarea dinamică a butoanelor de selecție pe baza obiectului GODS

        Object.values(GODS).forEach(god => {

            this.add.text(400, y, `Alege ${god.name}`, { 

                fontFamily: FONT, fontSize: '32px', fill: '#fff', backgroundColor: '#333', padding: 10 

            })

            .setOrigin(0.5)

            .setInteractive({ useHandCursor: true }) // Permite interacțiunea cu mouse-ul

            .on('pointerdown', () => this.scene.start('GameScene', { god })); // Trimite zeul ales către GameScene

            

            y += 80; // Spațiere între butoane

        });

    }

}

// ==========================================

// --- SCENA 2: JOCUL PROPRIU-ZIS ---

// ==========================================

class GameScene extends Phaser.Scene {

    constructor() { super('GameScene'); }

    // Resetează stările generale când scena repornește sau primește date noi

    init(data) { 

        if (data.god) selectedGod = data.god; 

        kills = 0; 

        isBossActive = false; 

    }

    preload() {

        // Încărcare asset-uri de bază (Jucător temporar, proiectil, hartă și amforă)

        this.load.image('game_map', 'ImaginiCuratateGameJam/AlteChestii/harta.png');

        this.load.image('vase', 'ImaginiCuratateGameJam/AlteChestii/amfora.png');

        

        // Încărcarea fontului ca fișier binar pentru procesare manuală ulterioară

        this.load.binary('spartacusFont', 'spartacus-font/Spartacus-KVdLp.ttf');

        // Încărcare cadre animație pentru Ares

        for (let i = 1; i <= 5; i++) {

            this.load.image(`ares_walk_${i}`, `ImaginiCuratateGameJam/Ares/r1/A${i}.png`);

        }

        // Încărcare cadre animație pentru Poseidon

        this.load.image('poseidon_idle', 'ImaginiCuratateGameJam/Poseidon/r1/PIdle.png');

        this.load.image('poseidon_walk_1', 'ImaginiCuratateGameJam/Poseidon/r1/P8.png');

        this.load.image('poseidon_walk_2', 'ImaginiCuratateGameJam/Poseidon/r1/P9.png');

        this.load.image('poseidon_walk_3', 'ImaginiCuratateGameJam/Poseidon/r1/P11.png');

        this.load.image('poseidon_walk_4', 'ImaginiCuratateGameJam/Poseidon/r1/P12.png');

        this.load.image('poseidon_walk_5', 'ImaginiCuratateGameJam/Poseidon/r1/P13.png');

        this.load.image('poseidon_walk_6', 'ImaginiCuratateGameJam/Poseidon/r1/P15.png');

        this.load.image('poseidon_walk_7', 'ImaginiCuratateGameJam/Poseidon/r1/P14.png');

        this.load.image('poseidon_walk_8', 'ImaginiCuratateGameJam/Poseidon/r1/P10.png');

        // Încărcare cadre animație pentru Zeus (23 de cadre de mișcare)

        for (let i = 1; i <= 23; i++) {

            let frameNum = i < 10 ? '0' + i : i;

            this.load.image(`zeus_move_${frameNum}`, `ImaginiCuratateGameJam/Zeus/Miscare/Z${i}.png`);

        }

    }

    create() {

        // --- PROCESARE ȘI INJECTARE FONT PERSONALIZAT ---

        let fontBuffer = this.cache.binary.get('spartacusFont');

        let newFont = new FontFace(FONT, fontBuffer);

        newFont.load().then((loadedFace) => {

            document.fonts.add(loadedFace);

            if(this.uiText) this.uiText.setFontFamily(FONT);

        }).catch(err => console.log("Eroare incarcare font: ", err));

        // Adăugare fundal hartă

        let background = this.add.image(400, 300, 'game_map');

        background.setDisplaySize(800, 600);

        // --- CONSTRUIRE ANIMAȚII (Ares & Poseidon) ---

        this.anims.create({

            key: 'ares_walk',

            frames: [{ key: 'ares_walk_2' }, { key: 'ares_walk_3' }, { key: 'ares_walk_4' }, { key: 'ares_walk_5' }],

            frameRate: 10,

            repeat: -1

        });

        this.anims.create({

            key: 'poseidon_walk',

            frames: [

                { key: 'poseidon_walk_1' }, { key: 'poseidon_walk_2' }, { key: 'poseidon_walk_3' },

                { key: 'poseidon_walk_4' }, { key: 'poseidon_walk_5' }, { key: 'poseidon_walk_6' },

                { key: 'poseidon_walk_2' }, { key: 'poseidon_walk_7' }, { key: 'poseidon_walk_8' },

                { key: 'poseidon_walk_2' }

            ],

            frameRate: 8,

            repeat: -1

        });

        // --- INIȚIALIZARE VARIABILE DE STARE SCENĂ ---

        this.isGameOver = false; 

        this.isVictory = false; 

        this.isPaused = false; 

        this.lastFired = 0;       // Timestamp pentru controlul ratei de foc

        this.isSlowed = false;     // Control debuff aplicat de Boss

        this.currentElement = 'shield'; // Elementul selectat implicit (Arma activa)

        // Variabile interne pentru animația manuală a lui Zeus

        this.zeusFrameTime = 0;

        this.zeusCurrentFrame = 1;

        // Determinarea texturii inițiale în funcție de zeul ales

        let initialTexture = 'player';

        if (selectedGod.name === 'Zeus') initialTexture = 'zeus_move_01';

        else if (selectedGod.name === 'Ares') initialTexture = 'ares_walk_1';

        else if (selectedGod.name === 'Poseidon') initialTexture = 'poseidon_idle';

        

        // Crearea sprite-ului jucătorului cu proprietăți fizice (Arcade Physics)

        this.player = this.physics.add.sprite(400, 480, initialTexture).setCollideWorldBounds(true);

        

        // --- AJUSTARE HITBOX-URI ȘI SCALE ÎN FUNCȚIE DE SPREE-UL ZEULUI ---

        if (selectedGod.name === 'Zeus') {

            this.player.setScale(0.15); 

            this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.4);

            this.player.body.setOffset(this.player.width * 0.3, this.player.height * 0.3);

        } else if (selectedGod.name === 'Ares') {

            this.player.setScale(0.5); 

            this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.4);

            this.player.body.setOffset(this.player.width * 0.3, this.player.height * 0.3);

        } else if (selectedGod.name === 'Poseidon') {

            this.player.setScale(0.75);

            this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.7);

            this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);

        }

        this.player.health = 100; // Sănătate inițială jucător

        // --- GRUPURI FIZICE ---

        this.enemies = this.physics.add.group(); // Grup dinamic inamici

        this.projectiles = this.physics.add.group(); // Grup dinamic proiectile

        this.destructibles = this.physics.add.staticGroup(); // Grup static amfore (vase)

        this.obstacles = this.add.group(); // Grup obstacole invizibile

        this.physics.world.enable(this.obstacles, Phaser.Physics.Arcade.STATIC_BODY); // Activare corpuri statice

        // Apelare funcție creare pereți invizibili

        this.createRestrictedBlueAreas();

        // Spawn amfore inițiale

        for (let i = 0; i < 8; i++) { this.spawnVase(); }

        // Buton UI de ajutor/pauză

        this.helpBtn = this.add.text(720, 30, ' HELP ', { 

            fontFamily: FONT, fontSize: '25px', fill: '#000', backgroundColor: '#ffd700', padding: 8 

        }).setInteractive({ useHandCursor: true }).setDepth(2000).on('pointerdown', () => this.toggleHelp());

        

        this.setupUI();

        

        // Maparea controalelor de la tastatură

        this.keys = this.input.keyboard.addKeys('W,A,S,D,ONE,TWO,THREE,SPACE');

        

        // --- COLIZIUNI ȘI SUPRAPUNERI (PHYSICS OVERLAPS & COLLIDERS) ---

        this.physics.add.collider(this.player, this.obstacles);

        this.physics.add.collider(this.enemies, this.obstacles);

        this.physics.add.collider(this.projectiles, this.obstacles, this.hitWall, null, this);

        this.physics.add.overlap(this.projectiles, this.enemies, this.handleCombat, null, this);

        this.physics.add.overlap(this.player, this.enemies, this.takeDamage, null, this);

        this.physics.add.overlap(this.projectiles, this.destructibles, this.hitVase, null, this);

        

        // Generator de inamici la fiecare 3 secunde

        this.time.addEvent({ delay: 3000, loop: true, callback: this.spawnEnemy, callbackScope: this });

    }

    // Creează zone invizibile (coliziuni) pe hartă bazate pe elementele grafice din fundal

    createRestrictedBlueAreas() {

        let createWall = (x, y, w, h) => {

            let zone = this.add.zone(x, y, w, h);

            this.physics.add.existing(zone, true); 

            zone.body.immovable = true;

            zone.body.moves = false;

            this.obstacles.add(zone);

        };

        // 1. Zona Templului de Sus + Fortificația Nordică

        createWall(400, 75, 520, 120);   

        createWall(215, 175, 45, 140);   

        createWall(350, 355, 50, 50);

        createWall(460, 360, 50, 50);

        createWall(585, 300, 200, 100);   

        // 2. Fortificația în formă de V din mijloc

        createWall(250, 275, 50, 45);   

        createWall(300, 300, 50, 40);  

        // 3. Masivul muntos din colțul din Dreapta-Sus

        createWall(725, 230, 150, 420);

        // 4. Templul complex din colțul din Stânga-Jos

        createWall(50, 350, 75, 150);

        // 5. Clădirea din Sud și zona adiacentă (Mijloc-Jos)

        createWall(500, 550, 300, 100);

        // 6. Copacii și chiparoșii de pe marginea drumurilor

        createWall(50, 155, 100, 60);     

        createWall(205, 500, 45, 65);    

        // 7. Marginile exterioare ale hărții (bariere)

        createWall(10, 582, 20, 1500);

        createWall(16, 582, 1750, 4);

        createWall(790, 582, 20, 1500);

        createWall(30, 18, 1750, 4);

    }

    // Configurare panouri UI, text și ecrane de Game Over / Victorie

    setupUI() {

        this.uiText = this.add.text(16, 16, '', { fontFamily: FONT, fontSize: '25px', fill: '#ffd700' }).setDepth(100);

        // Bara de viață pentru boss (ascunsă inițial)

        this.bossBarBG = this.add.rectangle(400, 50, 400, 20, 0x333333).setVisible(false).setDepth(150);

        this.bossHealthBar = this.add.rectangle(400, 50, 400, 20, 0xff0000).setVisible(false).setDepth(151);

        this.slowText = this.add.text(400, 100, "TIMPUL SE INCETINESTE...", { fontFamily: FONT, fontSize: '35px', fill: '#00ffff' }).setOrigin(0.5).setVisible(false).setDepth(200);

        // Container UI pentru ecranul de final (Ascuns la început)

        this.endScreen = this.add.container(400, 300).setVisible(false).setDepth(4000);

        this.endScreen.add([

            this.add.rectangle(0, 0, 800, 600, 0x000000, 0.85),

            this.endText = this.add.text(0, -50, "", { fontFamily: FONT, fontSize: '38px', align: 'center', fill: '#fff' }).setOrigin(0.5),

            this.add.text(0, 70, " REINCEARCA ", { fontFamily: FONT, fontSize: '30px', backgroundColor: '#ffd700', fill: '#000', padding: 10 }).setOrigin(0.5).setInteractive().on('pointerdown', () => this.scene.start('MenuScene'))

        ]);

        // Container UI pentru fereastra de ajutor (Manualul eroului)

        this.helpContainer = this.add.container(400, 300).setVisible(false).setDepth(3000);

        const hBg = this.add.rectangle(0, 0, 600, 450, 0x000000, 0.95).setStrokeStyle(3, 0xffd700);

        const hTxt = this.add.text(0, 0, 

            "=== MANUALUL EROULUI ===\n\n" +

            "Inamicul Galben -> [1] OGLINDA\n" +

            "Inamicul Rosu    -> [2] FOC\n" +

            "Inamicul Albastru -> [3] SULITA\n\n" +

            "BOSS apare la 20 kills.\n\n" +

            "WASD: Miscare | Space: Atac", 

            { fontFamily: FONT, fontSize: '25px', fill: '#fff', align: 'center', lineSpacing: 10 }

        ).setOrigin(0.5);

        this.helpContainer.add([hBg, hTxt]);

    }

    // Activează sau dezactivează pauza și manualul de ajutor

    toggleHelp() { 

        if (this.isGameOver || this.isVictory) return; 

        this.isPaused = !this.isPaused; 

        this.helpContainer.setVisible(this.isPaused); 

        if (this.isPaused) this.physics.pause(); else this.physics.resume(); 

    }

    // Bucla principală de actualizare a stării jocului (randată la fiecare cadru)

    update(time, delta) {

        if (this.isGameOver || this.isVictory || this.isPaused) return;

        

        // Aplică încetinirea dacă Boss-ul a activat abilitatea

        let speed = this.isSlowed ? selectedGod.speed * 0.75 : selectedGod.speed;

        

        let moveDirection = new Phaser.Math.Vector2(0, 0);

        let isMoving = false;

        // Citirea tastelor direcționale și întoarcerea vizuală a sprite-ului (FlipX)

        if (this.keys.A.isDown) { moveDirection.x = -1; isMoving = true; this.player.setFlipX(true); } 

        else if (this.keys.D.isDown) { moveDirection.x = 1; isMoving = true; this.player.setFlipX(false); }

        

        if (this.keys.W.isDown) { moveDirection.y = -1; isMoving = true; } 

        else if (this.keys.S.isDown) { moveDirection.y = 1; isMoving = true; }

        // Calcularea vectorului de mișcare normalizat pentru a preveni deplasarea mai rapidă pe diagonală

        if (isMoving) {

            moveDirection.normalize();

            this.player.setVelocityX(moveDirection.x * speed);

            this.player.setVelocityY(moveDirection.y * speed);

        } else {

            this.player.setVelocity(0); // Oprește jucătorul dacă nu apasă nicio tastă

        }

        // --- SISTEM MANAGEMENT ANIMAȚII (Diferit pentru fiecare Zeu) ---

        if (selectedGod.name === 'Zeus') {

            // Zeus nu folosește sistemul standard `.play()`, ci își schimbă texturile manual bazat pe delta time

            if (isMoving) {

                this.zeusFrameTime += delta * 0.75;

                if (this.zeusFrameTime >= 65) { // Schimbă cadrul la fiecare 65ms

                    this.zeusFrameTime = 0;

                    this.zeusCurrentFrame = (this.zeusCurrentFrame % 23) + 1;

                    let frameNum = this.zeusCurrentFrame < 10 ? '0' + this.zeusCurrentFrame : this.zeusCurrentFrame;

                    this.player.setTexture(`zeus_move_${frameNum}`);

                }

            } else {

                this.player.setTexture('zeus_move_01'); // Revine la cadrul static

                this.zeusCurrentFrame = 1;

                this.zeusFrameTime = 0;

            }

        } else if (selectedGod.name === 'Ares') {

            if (isMoving) this.player.play('ares_walk', true);

            else { this.player.stop(); this.player.setTexture('ares_walk_1'); }

        } else if (selectedGod.name === 'Poseidon') {

            if (isMoving) {

                this.player.setScale(0.75);

                this.player.body.setSize(this.player.width * 0.5, this.player.height * 0.8);

                this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);

                this.player.play('poseidon_walk', true);

            } else {

                this.player.stop();

                this.player.setScale(0.75);

                this.player.body.setSize(this.player.width * 0.5, this.player.height * 0.8);

                this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);

                this.player.setTexture('poseidon_idle');

            }

        }

        

        // --- COMANDE SCHIMBARE ARMĂ ȘI ATAC ---

        if (Phaser.Input.Keyboard.JustDown(this.keys.ONE)) this.currentElement = 'shield';

        if (Phaser.Input.Keyboard.JustDown(this.keys.TWO)) this.currentElement = 'fire';

        if (Phaser.Input.Keyboard.JustDown(this.keys.THREE)) this.currentElement = 'spear';

        

        // Executare atac prin Space sau Click Stânga Mouse, ținând cont de Cooldown

        if ((this.keys.SPACE.isDown || this.input.activePointer.isDown) && time > this.lastFired) this.fire(time);

        // --- INTELIGENȚA ARTIFICIALĂ A INAMICILOR (Urmărire / Mișcare Random) ---

        this.enemies.getChildren().forEach(e => {

            if (!e.active) return;

            if (isBossActive && e.isBoss) {

                // Boss-ul urmărește agresiv jucătorul, accelerând când are sub jumătate din viață (750 HP)

                this.physics.moveToObject(e, this.player, e.health < 750 ? 170 : 120);

            } else {

                // Inamicii normali urmăresc jucătorul doar dacă acesta se află la o distanță mai mică de 200 pixeli

                if (Phaser.Math.Distance.Between(this.player.x, this.player.y, e.x, e.y) < 200) {

                    this.physics.moveToObject(e, this.player, 100);

                } else if (time > (e.nextMove || 0) || e.body.velocity.x === 0) {

                    // Dacă jucătorul e departe, merg random în direcții aleatorii calculate la intervale de 2-4 secunde

                    let angle = Phaser.Math.FloatBetween(0, Math.PI * 2);

                    e.setVelocity(Math.cos(angle) * 70, Math.sin(angle) * 70);

                    e.nextMove = time + Phaser.Math.Between(2000, 4000);

                }

            }

        });

        // Actualizare dinamică a dimensiunii barei de viață a Boss-ului

        if (isBossActive && this.bossEntity) this.bossHealthBar.width = 400 * (this.bossEntity.health / 1500);

        

        // Actualizare text UI info

        const names = { shield: 'Oglinda', fire: 'Foc', spear: 'Sulita' };

        this.uiText.setText(`HP: ${this.player.health} | Kills: ${kills}\nArma: ${names[this.currentElement]}`);

    }

    // Funcția care generează un proiectil orientat spre coordonatele cursorului de mouse

    fire(time) {

        let p = this.projectiles.create(this.player.x, this.player.y, 'bullet');

        p.weaponType = this.currentElement; // Stochează tipul elementului pe proiectil

        let target = this.input.activePointer;

        // Ares are o mecanică specială: atacurile sale devin lame lungi/subțiri orientate spre țintă

        if (selectedGod.name === 'Ares') {

            p.setScale(3.5, 0.2);

            p.setRotation(Phaser.Math.Angle.Between(this.player.x, this.player.y, target.worldX, target.worldY));

        } else p.setScale(selectedGod.bScale);

        // Colorează proiectilul în funcție de elementul ales

        p.setTint({ shield: 0xffff00, fire: 0xff0000, spear: 0x0000ff }[p.weaponType]);

        

        // Pune în mișcare proiectilul către țintă cu viteza proprie a zeului ales

        this.physics.moveTo(p, target.worldX, target.worldY, selectedGod.bSpeed);

        

        // Actualizează timestamp-ul pentru următorul atac permis (respectând cooldown-ul)

        this.lastFired = time + selectedGod.cooldown;

    }

    // Distruge proiectilul dacă acesta lovește un perete/zonă restricționată

    hitWall(obj1, obj2) {

        if (obj1 && obj1.weaponType) obj1.destroy(); 

        else if (obj2 && obj2.weaponType) obj2.destroy(); 

    }

    // Gestionarea luptei: Verifică algoritmul de tip Piatră-Foarfecă-Hârtie (Element vs Vulnerabilitate)

    handleCombat(p, e) {

        if (p.weaponType === e.vulnerability) {

            if (isBossActive && e.isBoss) {

                // Cazul în care inamicul lovit este Boss-ul

                e.health -= 50; 

                p.destroy(); 

                this.cameras.main.shake(100, 0.002); // Efect de cutremur pe ecran la impact

                if (e.health <= 0) this.triggerVictory();

            } else { 

                // Cazul inamicilor standard (mor dintr-o lovitură corectă)

                e.destroy(); 

                p.destroy(); 

                kills++; 

                if (kills === 20 && !isBossActive) this.spawnBoss(); 

            }

        } else p.destroy(); // Dacă elementul nu se potrivește, proiectilul se distruge fără daune

    }

    // Curăță variabilele aferente bătăliei cu boss-ul și deschide ecranul de Victorie

    triggerVictory() {

        this.isVictory = true; 

        this.physics.pause();

        if (this.bossColorTimer) this.bossColorTimer.remove();

        if (this.bossSlowTimer) this.bossSlowTimer.remove();

        if (this.bossEntity) { this.bossEntity.destroy(); this.bossEntity = null; }

        isBossActive = false; 

        this.bossBarBG.setVisible(false); 

        this.bossHealthBar.setVisible(false); 

        this.slowText.setVisible(false);

        this.endText.setText("BOSS INVINS!\nLegenda va dainui peste veacuri.");

        this.endScreen.setVisible(true);

    }

    // Instanțierea și configurarea Boss-ului final

    spawnBoss() {

        isBossActive = true;

        this.bossEntity = this.enemies.create(400, 200, 'player');

        this.bossEntity.setScale(5).setTint(0xffffff).setCollideWorldBounds(true).setBounce(1);

        this.bossEntity.body.setSize(20, 20).setOffset(5, 5); 

        this.bossEntity.health = 1500; 

        this.bossEntity.isBoss = true; 

        this.bossEntity.vulnerability = 'shield'; // Vulnerabilitate inițială

        this.bossBarBG.setVisible(true); 

        this.bossHealthBar.setVisible(true);

        

        // Timer buclă: La fiecare 6 secunde, boss-ul încetinește jucătorul timp de 2 secunde (Debuff)

        this.bossSlowTimer = this.time.addEvent({

            delay: 6000, loop: true,

            callback: () => {

                if (!this.bossEntity || this.isGameOver) return;

                this.cameras.main.flash(400, 0, 200, 255, 0.5); 

                this.isSlowed = true; 

                this.slowText.setVisible(true); 

                this.player.setAlpha(0.7); 

                this.time.delayedCall(2000, () => { 

                    this.isSlowed = false; 

                    this.slowText.setVisible(false); 

                    this.player.setAlpha(1); 

                });

            }

        });

        // Timer buclă: La fiecare 3 secunde, boss-ul își schimbă culoarea și elementul la care este vulnerabil

        this.bossColorTimer = this.time.addEvent({ 

            delay: 3000, loop: true, 

            callback: () => {

                if (!this.bossEntity) return;

                this.bossEntity.vulnerability = Phaser.Math.RND.pick(['shield', 'fire', 'spear']);

                this.bossEntity.setTint({ shield: 0xffff00, fire: 0xff0000, spear: 0x0000ff }[this.bossEntity.vulnerability]);

            }

        });

    }

    // Spawnează inamici simpli în mod aleatoriu doar în zone/drumuri sigure (fără pereți)

    spawnEnemy() { 

        if (isBossActive || this.isGameOver || this.isVictory || this.isPaused) return; 

        

        let drumuriSigure = [

            {x: 400, y: 460}, {x: 180, y: 250}, {x: 620, y: 350}, {x: 400, y: 310}  

        ];

        let punctAles = Phaser.Math.RND.pick(drumuriSigure);

        // Creare sprite inamic cu deviație mică de poziție (+/- 15px)

        let e = this.enemies.create(punctAles.x + Phaser.Math.Between(-15, 15), punctAles.y + Phaser.Math.Between(-15, 15), 'player'); 

        

        // Alegerea aleatorie a tipului de inamic (Culoare și Vulnerabilitate atașată)

        let t = Phaser.Math.RND.pick([{w:'shield',c:0xffff00}, {w:'fire',c:0xff0000}, {w:'spear',c:0x0000ff}]); 

        e.setTint(t.c).setCollideWorldBounds(true).setBounce(1).vulnerability = t.w; 

    }

    // Generarea amforelor decorative și distructibile pe hartă

    spawnVase() { 

        let locatiiSigureAmfore = [

            {x: 330, y: 460}, {x: 470, y: 460}, {x: 400, y: 270}, {x: 180, y: 190}, 

            {x: 620, y: 190}, {x: 230, y: 460}, {x: 570, y: 460}, {x: 400, y: 325}  

        ];

        

        // Folosește lungimea curentă a grupului pentru a distribui amforele uniform în vectorul de locații

        let coord = locatiiSigureAmfore[this.destructibles.getLength() % locatiiSigureAmfore.length];

        let v = this.destructibles.create(coord.x, coord.y, 'vase');

        v.setScale(0.35);

        v.body.setSize(v.width * 0.8, v.height * 0.9);

        v.refreshBody(); // Obligatoriu pentru corpurile statice după setScale pentru a realinia hitbox-ul

    }

    

    // Logica distrugerii unei amfore. Se reîntoarce pe hartă (Respawn) după 5 secunde

    hitVase(p, v) { 

        if (selectedGod.name !== 'Ares') p.destroy(); // Proiectilele lui Ares penetrează obiectele distructibile

        v.disableBody(true, true); // O face invizibilă și îi oprește fizica

        this.time.delayedCall(5000, () => { 

            if (!this.isGameOver && !this.isVictory) v.enableBody(true, v.x, v.y, true, true); 

        }); 

    }

    // Gestionarea daunelor încasate de jucător la contactul direct cu inamicii/boss-ul

    takeDamage(p, e) { 

        // Verifică dacă jucătorul nu este deja într-o stare temporară de invulnerabilitate (`isHurt`)

        if (!p.isHurt && !this.isGameOver && !this.isVictory) { 

            p.isHurt = true; 

            p.health -= 25; // Scade viața cu 25 unități

            

            // Feedback vizual diferențiat în funcție de zeul controlat

            if (selectedGod.name === 'Ares' || selectedGod.name === 'Poseidon') {

                this.time.delayedCall(100, () => { p.setAlpha(0.5); });

                this.time.delayedCall(300, () => { p.setAlpha(1); });

            } else if (selectedGod.name !== 'Zeus') {

                p.setTint(0xff0000); // Se colorează în roșu complet

            }

            // Condiție de eșec (Sănătatea ajunge la 0)

            if (p.health <= 0) { 

                this.isGameOver = true; 

                this.endText.setText("VISUL S-A SFARSIT..."); 

                this.endScreen.setVisible(true); 

                this.physics.pause(); 

            } else {

                // Oprirea efectului de avarie și resetarea culorilor după 1 secundă (fereastra de invulnerabilitate)

                this.time.delayedCall(1000, () => { 

                    if (selectedGod.name === 'Ares' || selectedGod.name === 'Poseidon') {

                        p.setAlpha(1);

                    } else if (selectedGod.name !== 'Zeus') {

                        p.clearTint(); 

                        p.setTint(selectedGod.color); 

                    }

                    p.isHurt = false; 

                }); 

            }

        } 

    }

}

// ==========================================

// --- CONFIGURARE ENGINE NATIVĂ ---

// ==========================================

const config = { 

    type: Phaser.AUTO, // Încearcă randare WebGL, altfel face fallback pe Canvas 2D

    width: 800, 

    height: 600, 

    physics: { 

        default: 'arcade',

        arcade: {

            debug: false // Setat pe 'false' pentru a ascunde liniile verzi de debug ale hitbox-urilor

        }

    }, 

    scene: [IntroScene, MenuScene, GameScene] // Înregistrarea scenelor în ordinea rulării

};

// Pornirea propriu-zisă a instanței de joc Phaser

new Phaser.Game(config);

</script>

</body>

</html>

Published 22 days ago
StatusReleased
AuthorDie Coding

Download

Download
OlimpRoyale.zip 283 MB

Install instructions

Pentru a juca jocul, trebuie sa dezarhivati fisierul zip si sa pastrati toate fisierele in folderul in care sunt.

Leave a comment

Log in with itch.io to leave a comment.