Spaces:
Running
Running
Update index.html
Browse files- index.html +115 -14
index.html
CHANGED
@@ -134,11 +134,11 @@
|
|
134 |
</div>
|
135 |
<div id="instructions">
|
136 |
<h2>Welcome to the Isle of Mull Ferry Driving Simulator!</h2>
|
137 |
-
<p>You're driving to catch the ferry to the Isle of Mull in Scotland. Practice safe driving on
|
138 |
|
139 |
<h3>How to Play:</h3>
|
140 |
<ul style="text-align: left;">
|
141 |
-
<li>Drive on the <span class="highlight">
|
142 |
<li>Use <span class="highlight">W/S</span> or <span class="highlight">β/β</span> to accelerate/brake</li>
|
143 |
<li>Use <span class="highlight">A/D</span> or <span class="highlight">β/β</span> to steer</li>
|
144 |
<li>Press <span class="highlight">SPACE</span> or <span class="highlight">J</span> to jump (when driving fast)</li>
|
@@ -569,8 +569,8 @@
|
|
569 |
car.castShadow = true;
|
570 |
const {roadY,roadCurve} = getRoadPropertiesAtZ(worldZPosition);
|
571 |
|
572 |
-
//
|
573 |
-
const laneXOffset = isOncoming ? (roadWidth/4
|
574 |
car.position.set(roadCurve + laneXOffset, 1+roadY, worldZPosition);
|
575 |
car.rotation.y = isOncoming ? Math.PI : 0;
|
576 |
scene.add(car);
|
@@ -579,7 +579,23 @@
|
|
579 |
leftHeadlight.position.set(-0.7,0.3,1.9); car.add(leftHeadlight);
|
580 |
const rightHeadlight = new THREE.Mesh(new THREE.SphereGeometry(0.2,8,8), headlightMat);
|
581 |
rightHeadlight.position.set(0.7,0.3,1.9); car.add(rightHeadlight);
|
582 |
-
aiCars.push({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
583 |
}
|
584 |
|
585 |
function createPassingPlaces() {
|
@@ -806,10 +822,12 @@
|
|
806 |
|
807 |
aiCars.forEach(ai => {
|
808 |
const {roadY:rY, roadCurve:rC} = getRoadPropertiesAtZ(ai.initialPositionZ);
|
809 |
-
|
|
|
810 |
ai.mesh.position.set(rC+lo, 1+rY, ai.initialPositionZ);
|
811 |
ai.mesh.rotation.y = ai.isOncoming ? Math.PI : 0;
|
812 |
-
Object.assign(ai, {waiting:false,honking:false,waitingAtPassingPlace:false,flashingLights:false,isOvertaking:false});
|
|
|
813 |
if(ai.leftHeadlight) ai.leftHeadlight.material.color.setHex(0xFFFFFF);
|
814 |
if(ai.rightHeadlight) ai.rightHeadlight.material.color.setHex(0xFFFFFF);
|
815 |
});
|
@@ -976,12 +994,44 @@
|
|
976 |
function updateAICars(delta) {
|
977 |
carsBehind = [];
|
978 |
aiCars.forEach(ai => {
|
979 |
-
const carM=ai.mesh;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
980 |
const moveDist=curSpd*(ai.isOncoming?-1:1)*delta*2.5; carM.position.z+=moveDist;
|
981 |
const {roadY,roadCurve}=getRoadPropertiesAtZ(carM.position.z);
|
982 |
-
|
983 |
-
|
|
|
|
|
984 |
const tX=roadCurve+tLaneXOff; carM.position.x+=(tX-carM.position.x)*0.1; carM.position.y=1+roadY;
|
|
|
985 |
if(ai.isOncoming){
|
986 |
const dToP=playerCar.position.distanceTo(carM.position);
|
987 |
if(dToP<40&&!ai.waitingAtPassingPlace){
|
@@ -1006,8 +1056,11 @@
|
|
1006 |
if(!ai.isOncoming&&carM.position.z<playerCar.position.z&&carM.position.z>playerCar.position.z-50)carsBehind.push(ai);
|
1007 |
if(Math.abs(carM.position.z-(roadLength/2))>roadLength/2+100){
|
1008 |
const iZ=ai.initialPositionZ; const{roadY:iRY,roadCurve:iRC}=getRoadPropertiesAtZ(iZ);
|
1009 |
-
|
1010 |
-
|
|
|
|
|
|
|
1011 |
}
|
1012 |
});
|
1013 |
updateRearViewMirror();
|
@@ -1017,7 +1070,8 @@
|
|
1017 |
const pZ=playerCar.position.z,pX=playerCar.position.x; const{roadCurve:pRC}=getRoadPropertiesAtZ(pZ);
|
1018 |
for(const pp of passingPlaces){
|
1019 |
if(Math.abs(pZ-pp.position)<pp.length/2){
|
1020 |
-
|
|
|
1021 |
}
|
1022 |
}return false;
|
1023 |
}
|
@@ -1042,7 +1096,54 @@
|
|
1042 |
function checkCollisions() {
|
1043 |
const playerBox = new THREE.Box3().setFromObject(playerCar);
|
1044 |
potholes.forEach(pd=>{if(!pd.hit){const dist=playerCar.position.distanceTo(pd.mesh.position);if(dist<1.5){pd.hit=true;const oSpd=Math.abs(speed);speed*=0.6;decreaseHealth(15,"Hit a pothole!");if(oSpd>20){showMessage("Flat tire!",3);decreaseHealth(25,"Flat Tire!");}}}});
|
1045 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1046 |
}
|
1047 |
|
1048 |
function updateHealthRegen(delta) {
|
|
|
134 |
</div>
|
135 |
<div id="instructions">
|
136 |
<h2>Welcome to the Isle of Mull Ferry Driving Simulator!</h2>
|
137 |
+
<p>You're driving to catch the ferry to the Isle of Mull in Scotland. Practice safe driving on American-style roads.</p>
|
138 |
|
139 |
<h3>How to Play:</h3>
|
140 |
<ul style="text-align: left;">
|
141 |
+
<li>Drive on the <span class="highlight">right side</span> of the road</li>
|
142 |
<li>Use <span class="highlight">W/S</span> or <span class="highlight">β/β</span> to accelerate/brake</li>
|
143 |
<li>Use <span class="highlight">A/D</span> or <span class="highlight">β/β</span> to steer</li>
|
144 |
<li>Press <span class="highlight">SPACE</span> or <span class="highlight">J</span> to jump (when driving fast)</li>
|
|
|
569 |
car.castShadow = true;
|
570 |
const {roadY,roadCurve} = getRoadPropertiesAtZ(worldZPosition);
|
571 |
|
572 |
+
// SWITCHED: For right-side driving, oncoming cars should be on player's left side
|
573 |
+
const laneXOffset = isOncoming ? (-roadWidth/4 - 0.25) : (roadWidth/4 + 0.25);
|
574 |
car.position.set(roadCurve + laneXOffset, 1+roadY, worldZPosition);
|
575 |
car.rotation.y = isOncoming ? Math.PI : 0;
|
576 |
scene.add(car);
|
|
|
579 |
leftHeadlight.position.set(-0.7,0.3,1.9); car.add(leftHeadlight);
|
580 |
const rightHeadlight = new THREE.Mesh(new THREE.SphereGeometry(0.2,8,8), headlightMat);
|
581 |
rightHeadlight.position.set(0.7,0.3,1.9); car.add(rightHeadlight);
|
582 |
+
aiCars.push({
|
583 |
+
mesh:car,
|
584 |
+
speed:isOncoming?8:10,
|
585 |
+
baseSpeed:isOncoming?8:10, // Store original speed
|
586 |
+
isOncoming:isOncoming,
|
587 |
+
honking:false,
|
588 |
+
waiting:false,
|
589 |
+
initialPositionZ:worldZPosition,
|
590 |
+
flashing:false,
|
591 |
+
waitingAtPassingPlace:false,
|
592 |
+
flashingLights:false,
|
593 |
+
leftHeadlight:leftHeadlight,
|
594 |
+
rightHeadlight:rightHeadlight,
|
595 |
+
politeness:Math.random()*0.8+0.2,
|
596 |
+
avoidingPlayer:false, // New property for collision avoidance
|
597 |
+
reactionDistance:30 + Math.random() * 20 // Variable reaction distance
|
598 |
+
});
|
599 |
}
|
600 |
|
601 |
function createPassingPlaces() {
|
|
|
822 |
|
823 |
aiCars.forEach(ai => {
|
824 |
const {roadY:rY, roadCurve:rC} = getRoadPropertiesAtZ(ai.initialPositionZ);
|
825 |
+
// SWITCHED: Updated for right-side driving
|
826 |
+
const lo = ai.isOncoming ? (-roadWidth/4-0.25) : (roadWidth/4+0.25);
|
827 |
ai.mesh.position.set(rC+lo, 1+rY, ai.initialPositionZ);
|
828 |
ai.mesh.rotation.y = ai.isOncoming ? Math.PI : 0;
|
829 |
+
Object.assign(ai, {waiting:false,honking:false,waitingAtPassingPlace:false,flashingLights:false,isOvertaking:false,avoidingPlayer:false});
|
830 |
+
ai.speed = ai.baseSpeed; // Reset to base speed
|
831 |
if(ai.leftHeadlight) ai.leftHeadlight.material.color.setHex(0xFFFFFF);
|
832 |
if(ai.rightHeadlight) ai.rightHeadlight.material.color.setHex(0xFFFFFF);
|
833 |
});
|
|
|
994 |
function updateAICars(delta) {
|
995 |
carsBehind = [];
|
996 |
aiCars.forEach(ai => {
|
997 |
+
const carM=ai.mesh;
|
998 |
+
|
999 |
+
// NEW: Collision avoidance logic
|
1000 |
+
const distToPlayer = playerCar.position.distanceTo(carM.position);
|
1001 |
+
let targetSpeed = ai.baseSpeed;
|
1002 |
+
ai.avoidingPlayer = false;
|
1003 |
+
|
1004 |
+
if (ai.isOncoming) {
|
1005 |
+
// Oncoming cars should slow down when approaching player head-on
|
1006 |
+
if (distToPlayer < ai.reactionDistance &&
|
1007 |
+
Math.abs(carM.position.x - playerCar.position.x) < roadWidth * 0.8) {
|
1008 |
+
ai.avoidingPlayer = true;
|
1009 |
+
const slowdownFactor = Math.max(0.2, distToPlayer / ai.reactionDistance);
|
1010 |
+
targetSpeed = ai.baseSpeed * slowdownFactor;
|
1011 |
+
}
|
1012 |
+
} else {
|
1013 |
+
// Cars behind player should slow down when getting too close
|
1014 |
+
const zDiff = playerCar.position.z - carM.position.z;
|
1015 |
+
if (zDiff > 0 && zDiff < ai.reactionDistance &&
|
1016 |
+
Math.abs(carM.position.x - playerCar.position.x) < roadWidth * 0.6) {
|
1017 |
+
ai.avoidingPlayer = true;
|
1018 |
+
const slowdownFactor = Math.max(0.3, zDiff / ai.reactionDistance);
|
1019 |
+
targetSpeed = Math.min(ai.baseSpeed * slowdownFactor, Math.abs(speed) * 0.8);
|
1020 |
+
}
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
// Smoothly adjust speed towards target
|
1024 |
+
ai.speed += (targetSpeed - ai.speed) * delta * 2;
|
1025 |
+
|
1026 |
+
let curSpd=ai.waiting||ai.waitingAtPassingPlace?0:ai.speed;
|
1027 |
const moveDist=curSpd*(ai.isOncoming?-1:1)*delta*2.5; carM.position.z+=moveDist;
|
1028 |
const {roadY,roadCurve}=getRoadPropertiesAtZ(carM.position.z);
|
1029 |
+
|
1030 |
+
// SWITCHED: Updated for right-side driving
|
1031 |
+
let tLaneXOff=ai.isOncoming?(-roadWidth/4):(-roadWidth/4);
|
1032 |
+
if(ai.isOvertaking)tLaneXOff=ai.isOncoming?(roadWidth/4):(-roadWidth/4);
|
1033 |
const tX=roadCurve+tLaneXOff; carM.position.x+=(tX-carM.position.x)*0.1; carM.position.y=1+roadY;
|
1034 |
+
|
1035 |
if(ai.isOncoming){
|
1036 |
const dToP=playerCar.position.distanceTo(carM.position);
|
1037 |
if(dToP<40&&!ai.waitingAtPassingPlace){
|
|
|
1056 |
if(!ai.isOncoming&&carM.position.z<playerCar.position.z&&carM.position.z>playerCar.position.z-50)carsBehind.push(ai);
|
1057 |
if(Math.abs(carM.position.z-(roadLength/2))>roadLength/2+100){
|
1058 |
const iZ=ai.initialPositionZ; const{roadY:iRY,roadCurve:iRC}=getRoadPropertiesAtZ(iZ);
|
1059 |
+
// SWITCHED: Updated for right-side driving
|
1060 |
+
const lo=ai.isOncoming?(-roadWidth/4-0.25):(roadWidth/4+0.25);
|
1061 |
+
carM.position.set(iRC+lo,1+iRY,iZ);
|
1062 |
+
Object.assign(ai,{waiting:false,waitingAtPassingPlace:false,isOvertaking:false,flashingLights:false,avoidingPlayer:false});
|
1063 |
+
ai.speed = ai.baseSpeed; // Reset speed when respawning
|
1064 |
}
|
1065 |
});
|
1066 |
updateRearViewMirror();
|
|
|
1070 |
const pZ=playerCar.position.z,pX=playerCar.position.x; const{roadCurve:pRC}=getRoadPropertiesAtZ(pZ);
|
1071 |
for(const pp of passingPlaces){
|
1072 |
if(Math.abs(pZ-pp.position)<pp.length/2){
|
1073 |
+
// SWITCHED: Updated for right-side driving - player should pull to the right
|
1074 |
+
if(pp.side===1){if(pX>pRC+roadWidth/4&&Math.abs(speed)<5)return true;}
|
1075 |
}
|
1076 |
}return false;
|
1077 |
}
|
|
|
1096 |
function checkCollisions() {
|
1097 |
const playerBox = new THREE.Box3().setFromObject(playerCar);
|
1098 |
potholes.forEach(pd=>{if(!pd.hit){const dist=playerCar.position.distanceTo(pd.mesh.position);if(dist<1.5){pd.hit=true;const oSpd=Math.abs(speed);speed*=0.6;decreaseHealth(15,"Hit a pothole!");if(oSpd>20){showMessage("Flat tire!",3);decreaseHealth(25,"Flat Tire!");}}}});
|
1099 |
+
|
1100 |
+
aiCars.forEach(aiD=>{
|
1101 |
+
const aiB=new THREE.Box3().setFromObject(aiD.mesh);
|
1102 |
+
if(playerBox.intersectsBox(aiB)){
|
1103 |
+
// NEW: Determine collision type and adjust damage accordingly
|
1104 |
+
let damage = 35; // Base damage
|
1105 |
+
let collisionType = "Collision!";
|
1106 |
+
|
1107 |
+
// Check if it's a rear collision (player hitting AI from behind or AI hitting player from behind)
|
1108 |
+
const playerToAI = aiD.mesh.position.z - playerCar.position.z;
|
1109 |
+
const playerSpeed = Math.abs(speed);
|
1110 |
+
|
1111 |
+
if (Math.abs(playerToAI) > 2) { // Not a side collision
|
1112 |
+
if ((playerToAI > 0 && speed > 0) || (playerToAI < 0 && speed < 0)) {
|
1113 |
+
// Rear collision - much less damage
|
1114 |
+
damage = Math.max(8, Math.floor(damage * 0.3)); // 70% damage reduction
|
1115 |
+
collisionType = "Rear collision!";
|
1116 |
+
}
|
1117 |
+
}
|
1118 |
+
|
1119 |
+
// Speed-based damage adjustment
|
1120 |
+
const speedFactor = Math.max(0.5, playerSpeed / 30);
|
1121 |
+
damage = Math.floor(damage * speedFactor);
|
1122 |
+
|
1123 |
+
decreaseHealth(damage, collisionType);
|
1124 |
+
playerCar.position.z-=Math.sign(speed)*2.5;
|
1125 |
+
speed*=0.1;
|
1126 |
+
aiD.waiting=true;
|
1127 |
+
setTimeout(()=>{if(aiD)aiD.waiting=false;},2500);
|
1128 |
+
}
|
1129 |
+
});
|
1130 |
+
}
|
1131 |
+
|
1132 |
+
function checkLandingBonus(impactForce) {
|
1133 |
+
// Award points for successful jumps and landings
|
1134 |
+
if (jumpSpeed > 0) {
|
1135 |
+
let bonusPoints = 0;
|
1136 |
+
if (airTime > 1.0) bonusPoints += 100;
|
1137 |
+
if (airTime > 2.0) bonusPoints += 200;
|
1138 |
+
if (jumpSpeed > 30) bonusPoints += 150;
|
1139 |
+
if (impactForce < 0.5) bonusPoints += 100; // Smooth landing bonus
|
1140 |
+
|
1141 |
+
if (bonusPoints > 0) {
|
1142 |
+
score += bonusPoints;
|
1143 |
+
document.getElementById('score').textContent = `Score: ${score}`;
|
1144 |
+
showMessage(`Landing bonus: +${bonusPoints}!`, 2);
|
1145 |
+
}
|
1146 |
+
}
|
1147 |
}
|
1148 |
|
1149 |
function updateHealthRegen(delta) {
|