Kills: 0
Remaining: 10

Loading Ewe-niverse...

and import * as THREE from 'three'; export class Controls { constructor(domElement) { this.domElement = domElement; this.moveState = { forward: 0, back: 0, left: 0, right: 0 }; this.chargeTriggered = false; this.initEventListeners(); } initEventListeners() { this.domElement.addEventListener('mousedown', (e) => { if (e.button === 0) { if(document.pointerLockElement !== this.domElement) { this.domElement.requestPointerLock(); } else { this.chargeTriggered = true; } } }); document.addEventListener('pointerlockchange', () => {}, false); document.addEventListener('keydown', this.onKeyDown.bind(this)); document.addEventListener('keyup', this.onKeyUp.bind(this)); } onMouseMove(event) { // No longer used for camera control } onKeyDown(event) { if(event.repeat) return; switch (event.code) { case 'KeyW': this.moveState.forward = 1; break; case 'KeyA': this.moveState.left = 1; break; case 'KeyS': this.moveState.back = 1; break; case 'KeyD': this.moveState.right = 1; break; case 'Space': this.chargeTriggered = true; break; } } onKeyUp(event) { switch (event.code) { case 'KeyW': this.moveState.forward = 0; break; case 'KeyA': this.moveState.left = 0; break; case 'KeyS': this.moveState.back = 0; break; case 'KeyD': this.moveState.right = 0; break; } } // Called by game loop to reset one-time triggers resetTriggers() { this.chargeTriggered = false; } } and import * as THREE from 'three'; import { World } from './world.js'; import { Sheep } from './sheep.js'; import { Controls } from './controls.js'; import { Sound } from './sound.js'; export class Game { constructor() { this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.clock = new THREE.Clock(); this.sheeps = []; this.player = null; this.gameOver = false; this.killCount = 0; this.rivalsRemaining = 0; this.gameOverScreen = document.getElementById('game-over-screen'); this.loadingScreen = document.getElementById('loading-screen'); this.killCountEl = document.getElementById('kill-count'); this.sheepRemainingEl = document.getElementById('sheep-remaining'); this.init(); } init() { this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setClearColor(0x87CEEB); // Sky blue this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(this.renderer.domElement); this.scene.fog = new THREE.Fog(0x87CEEB, 100, 500); const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.5); hemiLight.position.set(0, 100, 0); this.scene.add(hemiLight); const dirLight = new THREE.DirectionalLight(0xffffff, 1.5); dirLight.position.set(-50, 50, 25); dirLight.castShadow = true; dirLight.shadow.mapSize.width = 4096; dirLight.shadow.mapSize.height = 4096; dirLight.shadow.camera.left = -150; dirLight.shadow.camera.right = 150; dirLight.shadow.camera.top = 150; dirLight.shadow.camera.bottom = -150; dirLight.shadow.camera.near = 0.5; dirLight.shadow.camera.far = 500; this.scene.add(dirLight); this.world = new World(this.scene); this.player = new Sheep(this.scene, this.world, true); this.sheeps.push(this.player); const NUM_RIVALS = 9; this.rivalsRemaining = NUM_RIVALS; for (let i = 0; i < NUM_RIVALS; i++) { const angle = (i / NUM_RIVALS) * Math.PI * 2; const dist = 50 + Math.random() * 100; const pos = new THREE.Vector3(Math.cos(angle) * dist, 10, Math.sin(angle) * dist); const rival = new Sheep(this.scene, this.world, false, pos); this.sheeps.push(rival); } this.updateUI(); this.controls = new Controls(this.renderer.domElement); this.sounds = { charge: new Sound('charge.mp3'), impact: new Sound('impact.mp3'), }; const startButton = document.createElement('button'); startButton.textContent = "Start Brawling"; this.loadingScreen.appendChild(startButton); startButton.addEventListener('click', () => { this.loadingScreen.style.display = 'none'; this.renderer.domElement.requestPointerLock(); this.startGame(); }, { once: true }); window.addEventListener('resize', this.onWindowResize.bind(this), false); } startGame() { this.animate(); } endGame(playerWon = false) { if(this.gameOver) return; this.gameOver = true; document.exitPointerLock(); const h2 = this.gameOverScreen.querySelector('h2'); const p = this.gameOverScreen.querySelector('p'); if (playerWon) { h2.textContent = "You are the Last Sheep Standing!"; p.textContent = You knocked out ${this.killCount} rival${this.killCount !== 1 ? 's' : ''}.; } else { h2.textContent = "Game Over"; p.textContent = "You've been knocked out!"; } this.gameOverScreen.style.display = 'flex'; } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } updateCamera() { if (!this.player.isAlive) return; const offset = new THREE.Vector3(0, 15, -25); offset.applyQuaternion(this.player.mesh.quaternion); offset.add(this.player.mesh.position); const targetLookAt = this.player.mesh.position.clone().add(new THREE.Vector3(0,2,0)); this.camera.position.lerp(offset, 0.1); this.camera.lookAt(targetLookAt); } checkCollisions() { const deadSheeps = []; for (let i = 0; i < this.sheeps.length; i++) { for (let j = i + 1; j < this.sheeps.length; j++) { const sheepA = this.sheeps[i]; const sheepB = this.sheeps[j]; if (!sheepA.isAlive || !sheepB.isAlive) continue; const distSq = sheepA.mesh.position.distanceToSquared(sheepB.mesh.position); const combinedRadius = sheepA.radius + sheepB.radius; if (distSq < combinedRadius * combinedRadius) { let killer = null; let victim = null; if (sheepA.isCharging && !sheepB.isCharging) { killer = sheepA; victim = sheepB; } else if (sheepB.isCharging && !sheepA.isCharging) { killer = sheepB; victim = sheepA; } if (victim && !deadSheeps.includes(victim)) { victim.knockout(); deadSheeps.push(victim); this.sounds.impact.play(); if (killer.isPlayer) { this.killCount++; } } else { // non-lethal collision const normal = sheepA.mesh.position.clone().sub(sheepB.mesh.position).normalize(); const bounceForce = normal.multiplyScalar(10); sheepA.velocity.add(bounceForce); sheepB.velocity.sub(bounceForce); } } } } if (deadSheeps.length > 0) { this.sheeps = this.sheeps.filter(s => s.isAlive); this.rivalsRemaining -= deadSheeps.filter(s => !s.isPlayer).length; this.updateUI(); } } updateUI() { this.killCountEl.textContent = Kills: ${this.killCount}; this.sheepRemainingEl.textContent = Remaining: ${this.rivalsRemaining + 1}; } animate() { if (this.gameOver) return; requestAnimationFrame(this.animate.bind(this)); const deltaTime = Math.min(this.clock.getDelta(), 0.05); const playerInput = { moveZ: this.controls.moveState.forward - this.controls.moveState.back, turn: this.controls.moveState.right - this.controls.moveState.left, doCharge: this.controls.chargeTriggered, }; if (playerInput.doCharge && this.player.chargeCooldown <=0) { this.sounds.charge.play(); } this.sheeps.forEach(sheep => { sheep.update(deltaTime, this.sheeps, playerInput); }); this.controls.resetTriggers(); this.updateCamera(); this.checkCollisions(); if (!this.player.isAlive) { this.endGame(false); } else if (this.rivalsRemaining <= 0) { this.endGame(true); } if (this.player.isAlive && this.player.mesh.position.y < -20) { this.player.knockout(); this.sounds.impact.play(); this.endGame(false); } this.renderer.render(this.scene, this.camera); } } and import { Game } from './game.js'; window.addEventListener('DOMContentLoaded', () => { new Game(); }); and import * as THREE from 'three'; let sheepIdCounter = 0; export class Sheep { constructor(scene, world, isPlayer = false, position = new THREE.Vector3(0, 2, 0)) { this.id = sheepIdCounter++; this.scene = scene; this.world = world; this.isPlayer = isPlayer; this.velocity = new THREE.Vector3(); this.mass = 20; this.radius = 1.5; this.isCharging = false; this.chargeCooldown = 0; this.onGround = false; this.isAlive = true; // AI state this.aiState = 'wandering'; this.aiStateTimer = Math.random() * 3; this.aiTargetPosition = new THREE.Vector3(); this.createMesh(); this.mesh.position.copy(position); } createMesh() { const sheepGroup = new THREE.Group(); const woolMaterial = new THREE.MeshStandardMaterial({ color: this.isPlayer ? 0xffd700 : 0xffffff, roughness: 1.0 }); // Player is gold const skinMaterial = new THREE.MeshStandardMaterial({ color: 0xffcc99, roughness: 0.5 }); const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); // Body const bodyGeo = new THREE.SphereGeometry(1, 16, 12); const body = new THREE.Mesh(bodyGeo, woolMaterial); body.scale.set(1.2, 1.4, 1.2); body.castShadow = true; sheepGroup.add(body); // Head const head = new THREE.Group(); const headGeo = new THREE.SphereGeometry(0.6, 16, 12); const headMesh = new THREE.Mesh(headGeo, woolMaterial); headMesh.castShadow = true; head.add(headMesh); // Face const faceGeo = new THREE.SphereGeometry(0.4, 16, 8); const faceMesh = new THREE.Mesh(faceGeo, skinMaterial); faceMesh.position.z = 0.4; faceMesh.scale.set(1, 1, 1.3); head.add(faceMesh); // Eyes const eyeGeo = new THREE.SphereGeometry(0.08, 8, 8); const leftEye = new THREE.Mesh(eyeGeo, eyeMaterial); leftEye.position.set(-0.2, 0.2, 0.7); head.add(leftEye); const rightEye = new THREE.Mesh(eyeGeo, eyeMaterial); rightEye.position.set(0.2, 0.2, 0.7); head.add(rightEye); head.position.set(0, 0.5, 1.0); sheepGroup.add(head); // Legs const legGeo = new THREE.CylinderGeometry(0.15, 0.1, 1, 8); const legPositions = [ {x: 0.6, y: -0.8, z: 0.6}, {x: -0.6, y: -0.8, z: 0.6}, {x: 0.6, y: -0.8, z: -0.6}, {x: -0.6, y: -0.8, z: -0.6}, ]; legPositions.forEach(pos => { const leg = new THREE.Mesh(legGeo, skinMaterial); leg.position.set(pos.x, pos.y, pos.z); leg.castShadow = true; sheepGroup.add(leg); }); this.mesh = sheepGroup; this.mesh.scale.set(1.2, 1.2, 1.2); this.scene.add(this.mesh); } charge() { if (this.chargeCooldown > 0 || !this.isAlive) return false; this.isCharging = true; this.chargeCooldown = 2.0; const chargeDirection = new THREE.Vector3(0, 0, 1); chargeDirection.applyQuaternion(this.mesh.quaternion); chargeDirection.y = 0; chargeDirection.normalize(); this.velocity.add(chargeDirection.multiplyScalar(40)); setTimeout(() => { this.isCharging = false; }, 300); return true; } update(deltaTime, sheeps, playerInput = {}) { if (!this.isAlive) return; const GRAVITY = 30.0; const FRICTION = 5.0; const SLIDE_FORCE = 15.0; const ROTATION_SPEED = 3.0; let moveDirection = new THREE.Vector3(); if (this.isPlayer) { const { moveZ, turn, doCharge } = playerInput; if (turn !== 0) { this.mesh.rotateY(-turn * ROTATION_SPEED * deltaTime); } if (moveZ !== 0) { const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion); moveDirection.copy(forward).multiplyScalar(moveZ > 0 ? 1 : -1); } if (doCharge) { if(this.charge()) { // Sounds are handled in game.js } } } else { // AI Logic const aiInput = this.getAiInput(deltaTime, sheeps); if (aiInput.turn !== 0) { this.mesh.rotateY(aiInput.turn * ROTATION_SPEED * deltaTime); } if (aiInput.moveZ !== 0) { const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion); moveDirection.copy(forward).multiplyScalar(aiInput.moveZ); } if (aiInput.doCharge) { this.charge(); } } // Apply gravity this.velocity.y -= GRAVITY * deltaTime; // Apply movement force if (moveDirection.lengthSq() > 0.01 && this.onGround) { this.velocity.addScaledVector(moveDirection, this.mass * 2 * deltaTime); } // Update position based on velocity this.mesh.position.addScaledVector(this.velocity, deltaTime); // Ground collision and sliding const groundHeight = this.world.getHeight(this.mesh.position.x, this.mesh.position.z); const groundNormal = this.world.getNormal(this.mesh.position.x, this.mesh.position.z); if (this.mesh.position.y < groundHeight + this.radius) { this.mesh.position.y = groundHeight + this.radius; this.onGround = true; const slidingForce = new THREE.Vector3().copy(groundNormal); slidingForce.y = 0; this.velocity.addScaledVector(slidingForce, -SLIDE_FORCE * deltaTime); const groundVelocity = new THREE.Vector3(this.velocity.x, 0, this.velocity.z); groundVelocity.multiplyScalar(-FRICTION * deltaTime); this.velocity.add(groundVelocity); this.velocity.y = Math.max(0, this.velocity.y); } else { this.onGround = false; } if (this.chargeCooldown > 0) { this.chargeCooldown -= deltaTime; } } getAiInput(deltaTime, sheeps) { this.aiStateTimer -= deltaTime; const player = sheeps.find(s => s.isPlayer && s.isAlive); let doCharge = false; if (player) { const distToPlayer = this.mesh.position.distanceTo(player.mesh.position); if (distToPlayer < 40 && this.aiState !== 'chasing') { this.aiState = 'chasing'; } else if (distToPlayer >= 40 && this.aiState === 'chasing') { this.aiState = 'wandering'; this.aiStateTimer = 0; } } else if (this.aiState === 'chasing') { this.aiState = 'wandering'; this.aiStateTimer = 0; } if (this.aiStateTimer <= 0) { this.aiState = 'wandering'; this.aiStateTimer = Math.random() * 5 + 3; this.aiTargetPosition.set((Math.random() - 0.5) * 280, 0, (Math.random() - 0.5) * 280); } let targetPos = this.aiTargetPosition; if (this.aiState === 'chasing' && player) { targetPos = player.mesh.position; if (this.chargeCooldown <= 0 && Math.random() < 0.015) { doCharge = true; } } const toTarget = targetPos.clone().sub(this.mesh.position); toTarget.y = 0; let turn = 0; let moveZ = 0; if (toTarget.lengthSq() > 4) { moveZ = 1; const desiredDir = toTarget.normalize(); const forwardDir = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion); const angle = forwardDir.angleTo(desiredDir); if (angle > 0.1) { const cross = forwardDir.cross(desiredDir); turn = cross.y > 0 ? 1 : -1; } } return { moveZ, turn, doCharge }; } knockout() { if (!this.isAlive) return; this.isAlive = false; // Fun little animation const upwardVelocity = 15; const rotationalVelocity = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize().multiplyScalar(10); const animateKnockout = () => { if (!this.mesh) return; this.velocity.y -= 30 * 0.016; // gravity this.mesh.position.y += this.velocity.y * 0.016; this.mesh.rotation.x += rotationalVelocity.x * 0.016; this.mesh.rotation.z += rotationalVelocity.z * 0.016; if(this.mesh.position.y > -50) { requestAnimationFrame(animateKnockout); } else { this.dispose(); } }; this.velocity.y = upwardVelocity; animateKnockout(); } dispose() { if (!this.mesh) return; this.mesh.traverse(child => { if (child.isMesh) { child.geometry.dispose(); if (child.material.isMaterial) { child.material.dispose(); } } }); this.scene.remove(this.mesh); this.mesh = null; } } and export class Sound { constructor(src, loop = false) { if (!Sound.audioContext) { Sound.audioContext = new (window.AudioContext || window.webkitAudioContext)(); } this.gainNode = Sound.audioContext.createGain(); this.gainNode.connect(Sound.audioContext.destination); this.buffer = null; this.source = null; this.loop = loop; this.playing = false; this.isLoaded = false; fetch(src) .then(response => response.arrayBuffer()) .then(data => Sound.audioContext.decodeAudioData(data)) .then(buffer => { this.buffer = buffer; this.isLoaded = true; }) .catch(e => console.error("Error with decoding audio data for " + src, e)); } play() { if (!this.isLoaded || this.playing) return; if (Sound.audioContext.state === 'suspended') { Sound.audioContext.resume(); } this.source = Sound.audioContext.createBufferSource(); this.source.buffer = this.buffer; this.source.loop = this.loop; this.source.connect(this.gainNode); this.source.start(0); this.playing = true; if (!this.loop) { this.source.onended = () => { this.playing = false; }; } } stop() { if (this.source && this.playing) { this.source.stop(); this.playing = false; } } isPlaying() { return this.playing; } } and body { margin: 0; overflow: hidden; font-family: 'Arial', sans-serif; background-color: #000; } canvas { display: block; } #loading-screen, #game-over-screen { position: absolute; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 2em; z-index: 100; text-align: center; } #game-over-screen button, #loading-screen button { margin-top: 20px; padding: 20px 40px; font-size: 0.8em; cursor: pointer; border: none; background: #4CAF50; color: white; border-radius: 10px; text-transform: uppercase; font-weight: bold; } #ui-container { position: absolute; top: 20px; left: 20px; right: 20px; display: flex; justify-content: space-between; color: white; font-size: 2em; text-shadow: 2px 2px 4px rgba(0,0,0,0.7); z-index: 50; pointer-events: none; text-transform: uppercase; font-weight: bold; } and import * as THREE from 'three'; export class World { constructor(scene) { this.scene = scene; this.ground = null; this.raycaster = new THREE.Raycaster(); this.createGround(); this.createTrees(); this.createWater(); } createGround() { const groundSize = 300; const segments = 100; const groundGeometry = new THREE.PlaneGeometry(groundSize, groundSize, segments, segments); const vertices = groundGeometry.attributes.position.array; for (let i = 0; i <= segments; i++) { for (let j = 0; j <= segments; j++) { const index = (i * (segments + 1) + j) * 3; vertices[index + 2] = 0; // Make ground flat } } groundGeometry.attributes.position.needsUpdate = true; groundGeometry.computeVertexNormals(); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x4aa03c, roughness: 1, metalness: 0 }); this.ground = new THREE.Mesh(groundGeometry, groundMaterial); this.ground.rotation.x = -Math.PI / 2; this.ground.receiveShadow = true; this.scene.add(this.ground); } createWater() { const waterGeometry = new THREE.PlaneGeometry(1000, 1000); const waterMaterial = new THREE.MeshStandardMaterial({ color: 0x2266aa, transparent: true, opacity: 0.8, roughness: 0.2 }); const water = new THREE.Mesh(waterGeometry, waterMaterial); water.rotation.x = -Math.PI / 2; water.position.y = -5; this.scene.add(water); } getHeight(x, z) { this.raycaster.set(new THREE.Vector3(x, 100, z), new THREE.Vector3(0, -1, 0)); const intersects = this.raycaster.intersectObject(this.ground); if (intersects.length > 0) { return intersects[0].point.y; } return -Infinity; } getNormal(x, z) { this.raycaster.set(new THREE.Vector3(x, 100, z), new THREE.Vector3(0, -1, 0)); const intersects = this.raycaster.intersectObject(this.ground); if (intersects.length > 0 && intersects[0].face) { return intersects[0].face.normal; } return new THREE.Vector3(0, 1, 0); } createTrees() { const treeGeometry = new THREE.ConeGeometry(2.5, 12, 8); const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 }); const trunkGeometry = new THREE.CylinderGeometry(0.6, 0.6, 5); const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x5B3A29 }); for (let i = 0; i < 50; i++) { const tree = new THREE.Group(); const leaves = new THREE.Mesh(treeGeometry, treeMaterial); leaves.position.y = 6; leaves.castShadow = true; const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial); trunk.castShadow = true; tree.add(leaves); tree.add(trunk); const x = (Math.random() - 0.5) * 280; const z = (Math.random() - 0.5) * 280; const y = this.getHeight(x, z); if (y > -2) { // Only place trees on land tree.position.set(x, y + 2.5, z); this.scene.add(tree); } } } createBarn() { const barn = new THREE.Group(); const baseGeo = new THREE.BoxGeometry(20, 15, 30); const baseMat = new THREE.MeshStandardMaterial({ color: 0xDC143C }); const base = new THREE.Mesh(baseGeo, baseMat); base.castShadow = true; base.receiveShadow = true; barn.add(base); const roofGeo = new THREE.PlaneGeometry(22, 34); const roofMat = new THREE.MeshStandardMaterial({ color: 0x444444, side: THREE.DoubleSide }); const roof = new THREE.Mesh(roofGeo, roofMat); roof.position.y = 7.5; roof.rotation.x = Math.PI / 2; roof.rotation.y = -Math.PI / 4; const barnRoof = new THREE.Group(); const r1 = new THREE.Mesh(new THREE.BoxGeometry(16, 2, 32), new THREE.MeshStandardMaterial({ color: 0x333333 })); r1.position.set(-8, 12, 0); r1.rotation.z = Math.PI / 6; barnRoof.add(r1); const r2 = new THREE.Mesh(new THREE.BoxGeometry(16, 2, 32), new THREE.MeshStandardMaterial({ color: 0x333333 })); r2.position.set(8, 12, 0); r2.rotation.z = -Math.PI / 6; barnRoof.add(r2); barnRoof.traverse(c => { c.castShadow = true; c.receiveShadow = true; }); barn.add(barnRoof); barn.position.set(100, 7.5, -100); this.scene.add(barn); } } merge it all together into one code and ill tell you what to do with it modify it