about summary refs log blame commit diff stats
path: root/html/rogue/js/world.js
blob: ad25e9daadb314ab3b202fa3cda4e6a7f0020c92 (plain) (tree)
1
2
3
4
5
6
7
8


                            



                                         
                                       


                                         

  

                                                   
            
                    

                    
                         



                            
                    







                            






















                            





















                          



















                           





                                                  
                                               









                                                 
                  
                                   

          





                                                 
                   





                                                 









                                                 




                                                









                                                






                                                









                                                





























                                         
                                                 
                    
                                     

          
                                               









                                                 
                   
                                   




                                                 




                                                




                                                






                                                




                                                
         





















                                                                                     

   

                                               


                                                                            



                                                           


                               



                              

      
                             








                                                    














































                                                                                                       

























                                                         




















































                                                                                    

























































                                                                  
// Define world object types
const WORLD_OBJECTS = {
    PLATFORM: 'platform',
    FIR_BACKGROUND: 'fir_background',
    FIR_FOREGROUND: 'fir_foreground',
    MAPLE_BACKGROUND: 'maple_background',
    MAPLE_FOREGROUND: 'maple_foreground',
    ROCK_BACKGROUND: 'rock_background',
    ROCK_FOREGROUND: 'rock_foreground',
    GRASS_BACKGROUND: 'grass_background',
    GRASS_FOREGROUND: 'grass_foreground'
};

// Separate configurations for different tree types
const FIR_TYPES = {
    SMALL: {
        type: 'fir',
        width: 80,
        height: 120,
        canopyOffset: 40,
        canopyColor: '#2a4',
        trunkColor: '#531'
    },
    LARGE: {
        type: 'fir',
        width: 120,
        height: 200,
        canopyOffset: 60,
        canopyColor: '#1a4',
        trunkColor: '#420'
    }
};

const MAPLE_TYPES = {
    SMALL: {
        type: 'maple',
        width: 100,
        height: 130,
        trunkHeight: 60,
        canopyRadius: 35,
        leafClusters: 5,
        canopyColor: '#3a2',
        trunkColor: '#642'
    },
    LARGE: {
        type: 'maple',
        width: 160,
        height: 200,
        trunkHeight: 85,
        canopyRadius: 55,
        leafClusters: 7,
        canopyColor: '#2a2',
        trunkColor: '#531'
    }
};

// Rock configurations
const ROCK_TYPES = {
    SMALL: {
        width: 40,
        height: 30,
        color: '#666',
        highlights: '#888'
    },
    MEDIUM: {
        width: 70,
        height: 45,
        color: '#555',
        highlights: '#777'
    },
    LARGE: {
        width: 100,
        height: 60,
        color: '#444',
        highlights: '#666'
    }
};

// Add grass configurations
const GRASS_TYPES = {
    TALL: {
        type: 'grass',
        width: 30,
        height: 40,
        blades: 5,
        color: '#3a5',
        shadowColor: '#294'
    },
    SHORT: {
        type: 'grass',
        width: 20,
        height: 25,
        blades: 4,
        color: '#3b6',
        shadowColor: '#2a5'
    }
};

// Create a world with platforms, trees, and rocks
const createWorld = () => ({
    groundHeight: 12,
    // Separate arrays for different layers
    backgroundTrees: [
        {
            type: WORLD_OBJECTS.FIR_BACKGROUND,
            x: -400,
            config: FIR_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.MAPLE_BACKGROUND,
            x: -250,
            config: MAPLE_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.FIR_BACKGROUND,
            x: 50,
            config: FIR_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.MAPLE_BACKGROUND,
            x: 250,
            config: MAPLE_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.FIR_BACKGROUND,
            x: 500,
            config: FIR_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.MAPLE_BACKGROUND,
            x: 650,
            config: MAPLE_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.FIR_BACKGROUND,
            x: 900,
            config: FIR_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.MAPLE_BACKGROUND,
            x: 1100,
            config: MAPLE_TYPES.LARGE
        }
    ],
    backgroundRocks: [
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: -300,
            config: ROCK_TYPES.MEDIUM
        },
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: -100,
            config: ROCK_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: 150,
            config: ROCK_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: 400,
            config: ROCK_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: 750,
            config: ROCK_TYPES.MEDIUM
        },
        {
            type: WORLD_OBJECTS.ROCK_BACKGROUND,
            x: 1000,
            config: ROCK_TYPES.SMALL
        }
    ],
    platforms: [
        {
            type: WORLD_OBJECTS.PLATFORM,
            x: 300,
            y: 300,
            width: 200,
            height: 20,
            color: '#484'
        },
        {
            type: WORLD_OBJECTS.PLATFORM,
            x: 600,
            y: 200,
            width: 200,
            height: 20,
            color: '#484'
        },
        {
            type: WORLD_OBJECTS.PLATFORM,
            x: -200,
            y: 250,
            width: 200,
            height: 20,
            color: '#484'
        }
    ],
    foregroundTrees: [
        {
            type: WORLD_OBJECTS.MAPLE_FOREGROUND,
            x: -150,
            config: MAPLE_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.FIR_FOREGROUND,
            x: 200,
            config: FIR_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.MAPLE_FOREGROUND,
            x: 450,
            config: MAPLE_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.FIR_FOREGROUND,
            x: 800,
            config: FIR_TYPES.LARGE
        },
        {
            type: WORLD_OBJECTS.MAPLE_FOREGROUND,
            x: 1200,
            config: MAPLE_TYPES.SMALL
        }
    ],
    foregroundRocks: [
        {
            type: WORLD_OBJECTS.ROCK_FOREGROUND,
            x: 0,
            config: ROCK_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.ROCK_FOREGROUND,
            x: 300,
            config: ROCK_TYPES.MEDIUM
        },
        {
            type: WORLD_OBJECTS.ROCK_FOREGROUND,
            x: 700,
            config: ROCK_TYPES.SMALL
        },
        {
            type: WORLD_OBJECTS.ROCK_FOREGROUND,
            x: 950,
            config: ROCK_TYPES.LARGE
        }
    ],
    backgroundGrass: [
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -350, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -180, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 100, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 320, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 580, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 750, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 950, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1050, config: GRASS_TYPES.SHORT }
    ],
    foregroundGrass: [
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -280, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -50, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 150, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 420, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 680, config: GRASS_TYPES.TALL },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 880, config: GRASS_TYPES.SHORT },
        { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1150, config: GRASS_TYPES.TALL }
    ],
    // Track grass animation states
    grassStates: {}
});

// Rename current tree render function
const renderFirTree = (ctx, tree, groundY) => {
    const { x, config } = tree;
    const { width, height, canopyOffset, trunkColor, canopyColor } = config;
    
    // Calculate trunk dimensions
    const trunkWidth = width/3;
    const trunkHeight = height - (height - canopyOffset)/2;
    
    // Draw trunk
    ctx.fillStyle = trunkColor;
    ctx.fillRect(
        x - trunkWidth/2,
        groundY - trunkHeight,
        trunkWidth,
        trunkHeight
    );
    
    // Draw triangular canopy
    ctx.fillStyle = canopyColor;
    ctx.beginPath();
    ctx.moveTo(x - width/2, groundY - canopyOffset);
    ctx.lineTo(x + width/2, groundY - canopyOffset);
    ctx.lineTo(x, groundY - height);
    ctx.closePath();
    ctx.fill();
};

// Add new maple tree render function
const renderMapleTree = (ctx, tree, groundY) => {
    const { x, config } = tree;
    const { 
        width, height, trunkHeight, canopyRadius,
        leafClusters, trunkColor, canopyColor 
    } = config;
    
    // Draw trunk
    const trunkWidth = width/4;
    ctx.fillStyle = trunkColor;
    ctx.fillRect(
        x - trunkWidth/2,
        groundY - trunkHeight,
        trunkWidth,
        trunkHeight
    );
    
    // Draw leaf clusters as overlapping circles
    ctx.fillStyle = canopyColor;
    
    // Center cluster
    ctx.beginPath();
    ctx.arc(x, groundY - trunkHeight - canopyRadius, canopyRadius, 0, Math.PI * 2);
    ctx.fill();
    
    // Surrounding clusters
    for (let i = 0; i < leafClusters; i++) {
        const angle = (i / leafClusters) * Math.PI * 2;
        const clusterX = x + Math.cos(angle) * (canopyRadius * 0.8);
        const clusterY = groundY - trunkHeight - canopyRadius + Math.sin(angle) * (canopyRadius * 0.8);
        
        ctx.beginPath();
        ctx.arc(clusterX, clusterY, canopyRadius * 0.9, 0, Math.PI * 2);
        ctx.fill();
    }
};

// Main tree render function that handles both types
const renderTree = (ctx, tree, groundY) => {
    if (tree.config.type === 'fir') {
        renderFirTree(ctx, tree, groundY);
    } else if (tree.config.type === 'maple') {
        renderMapleTree(ctx, tree, groundY);
    }
};

const renderRock = (ctx, rock, groundY) => {
    const { x, config } = rock;
    const { width, height, color, highlights } = config;
    
    // Draw main rock shape (slightly irregular pentagon)
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(x - width/2, groundY);
    ctx.lineTo(x - width/2 + width/6, groundY - height);
    ctx.lineTo(x + width/3, groundY - height);
    ctx.lineTo(x + width/2, groundY - height/2);
    ctx.lineTo(x + width/2, groundY);
    ctx.closePath();
    ctx.fill();

    // Add highlights
    ctx.fillStyle = highlights;
    ctx.beginPath();
    ctx.moveTo(x - width/4, groundY - height);
    ctx.lineTo(x, groundY - height);
    ctx.lineTo(x + width/6, groundY - height/2);
    ctx.lineTo(x - width/6, groundY - height/2);
    ctx.closePath();
    ctx.fill();
};

// Add grass rendering function
const renderGrass = (ctx, grass, groundY, time) => {
    const { x, config } = grass;
    const { width, height, blades, color, shadowColor } = config;
    
    // Get or initialize grass state
    const stateKey = `grass_${x}`;
    if (!window.gameState.world.grassStates[stateKey]) {
        window.gameState.world.grassStates[stateKey] = {
            rustleAmount: 0,
            rustleDecay: 0.95
        };
    }
    const state = window.gameState.world.grassStates[stateKey];
    
    // Check for player interaction
    const player = window.gameState.player;
    const interactionDistance = width;
    const distanceToPlayer = Math.abs(player.x + player.width/2 - x);
    
    if (distanceToPlayer < interactionDistance) {
        state.rustleAmount = Math.min(1, state.rustleAmount + 0.3);
    } else {
        state.rustleAmount *= state.rustleDecay;
    }

    // Draw each blade of grass
    for (let i = 0; i < blades; i++) {
        const bladeX = x + (i - blades/2) * (width/blades);
        const bladeWidth = width / blades * 0.7;
        
        // Calculate blade curve based on rustle and time
        const rustleOffset = Math.sin(time/300 + i) * 5 * state.rustleAmount;
        const baseWave = Math.sin(time/1000 + i) * 2; // Gentle wave motion
        
        // Draw blade
        ctx.fillStyle = i % 2 === 0 ? color : shadowColor;
        ctx.beginPath();
        ctx.moveTo(bladeX, groundY);
        
        // Control points for quadratic curve
        const cp1x = bladeX + rustleOffset + baseWave;
        const cp1y = groundY - height/2;
        const cp2x = bladeX + rustleOffset * 1.5 + baseWave;
        const cp2y = groundY - height;
        
        // Draw curved blade
        ctx.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y);
        ctx.quadraticCurveTo(cp1x + bladeWidth, cp1y, bladeX + bladeWidth, groundY);
        ctx.fill();
    }
};

// Collision detection helper
const checkCollision = (player, platform) => {
    return player.x < platform.x + platform.width &&
           player.x + player.width > platform.x &&
           player.y < platform.y + platform.height &&
           player.y + player.height > platform.y;
};

// World physics helper
const handleWorldCollisions = (player, world) => {
    let onGround = false;
    
    // Check ground collision first
    const groundY = window.innerHeight - world.groundHeight;
    if (player.y + player.height > groundY) {
        player.y = groundY - player.height;
        player.velocityY = 0;
        onGround = true;
    }
    
    // Then check platform collisions
    for (const platform of world.platforms) {
        if (checkCollision(player, platform)) {
            // Calculate overlap on each axis
            const overlapX = Math.min(
                player.x + player.width - platform.x,
                platform.x + platform.width - player.x
            );
            const overlapY = Math.min(
                player.y + player.height - platform.y,
                platform.y + platform.height - player.y
            );

            // Resolve collision on the axis with smallest overlap
            if (overlapX < overlapY) {
                // Horizontal collision
                if (player.x < platform.x) {
                    player.x = platform.x - player.width;
                } else {
                    player.x = platform.x + platform.width;
                }
                player.velocityX = 0;
            } else {
                // Vertical collision
                if (player.y < platform.y) {
                    player.y = platform.y - player.height;
                    player.velocityY = 0;
                    onGround = true;
                } else {
                    player.y = platform.y + platform.height;
                    player.velocityY = 0;
                }
            }
        }
    }
    
    return { ...player, jumping: !onGround };
};