Remix of Spedfish — Auto Fish Play 3D
by MegaFlare512759 lines42.5 KB
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Spedfish — Auto Fish Play 3D</title>
<style>
html,body{
margin:0;width:100%;height:100%;overflow:hidden;
font-family:Arial;background:#061522;color:#fff
}
*{box-sizing:border-box}
button{
border:1px solid #38c8ff88;background:#092c43ef;color:#fff;
border-radius:14px;padding:13px 18px;font-weight:800;
touch-action:none
}
.screen{position:fixed;inset:0}
.hide{display:none!important}
#loading{
z-index:50;
background:linear-gradient(135deg,#090909,#252525 45%,#0b0b0b);
display:flex;flex-direction:column;align-items:center;
justify-content:center;transition:opacity 1.5s
}
#loading h1{
font-size:42px;margin:0 0 25px;color:#eee;
text-shadow:2px 2px #111,0 0 18px #ff1730;
letter-spacing:5px
}
.bar{
width:min(78vw,480px);height:18px;
border:3px solid #777;border-radius:8px;
overflow:visible;position:relative;
padding:3px;background:linear-gradient(180deg,#666,#292929);
box-shadow:inset 0 3px 8px #111,0 0 15px #ff1730
}
.fill{
height:100%;width:0;border-radius:4px;
background:linear-gradient(90deg,#ff1028,#ff4a55,#ff1028);
box-shadow:0 0 16px #ff1530
}
.loadtxt{
margin-top:12px;color:#ff9aa4;
text-shadow:0 0 8px #ff1f36
}
.loadparticles{
position:absolute;inset:0;pointer-events:none;overflow:hidden
}
.loadparticles:before,
.loadparticles:after{
content:"✦ • ◆ · ✦ • ◆ · ✦ • ◆";
position:absolute;left:-20%;width:140%;
font-size:18px;letter-spacing:35px;color:#ff253b;
text-shadow:0 0 10px #ff001f;
animation:particles 3s linear infinite;opacity:.75
}
.loadparticles:before{top:42%;animation-duration:2.4s}
.loadparticles:after{
top:54%;animation-duration:3.2s;
animation-direction:reverse;font-size:13px
}
@keyframes particles{
from{transform:translateX(-10%) translateY(0);opacity:.1}
50%{opacity:1}
to{transform:translateX(10%) translateY(-22px);opacity:.05}
}
#fade{
z-index:49;background:#000;opacity:0;
pointer-events:none;transition:opacity 1.5s
}
#lobby{
background:linear-gradient(135deg,#171717,#3a3a3a 45%,#111);
overflow:hidden
}
#lobby:before{
content:"⌁ SYSTEM ONLINE ⌁ // AUTO FISH PLAY // ⌁ SYSTEM ONLINE ⌁";
position:absolute;top:17px;left:0;width:100%;
text-align:center;font-size:11px;color:#ff5963;
letter-spacing:3px;z-index:11
}
#lobby:after{
content:"";position:absolute;inset:10px;
border:4px solid #777;border-radius:16px;
box-shadow:inset 0 0 50px #000,0 0 18px #ff1830;
pointer-events:none;z-index:10
}
#lobby .lobby-bg{
position:absolute;inset:0;
background:
repeating-linear-gradient(0deg,#0000 0 38px,#ffffff08 39px 40px),
linear-gradient(135deg,#4a4a4a,#161616);
border:6px solid #777;
box-shadow:inset 0 0 70px #000
}
#lobby .lobby-bg:after{
content:'◈ ◇ ◈ ◇ ◈ ◇ ◈';
position:absolute;bottom:8px;left:0;width:100%;
text-align:center;font-size:28px;color:#ff2633;
opacity:.28;letter-spacing:12px
}
.logo{
position:absolute;top:42px;width:100%;
text-align:center;font-size:42px;font-weight:1000;
color:#fff;text-shadow:0 0 8px #ff2738,0 0 28px #ff001f;
letter-spacing:4px
}
.logo:before{
content:'◉ CORE // FISHING NETWORK';
display:block;font-size:10px;letter-spacing:5px;
color:#ff5963;margin-bottom:7px
}
.logo:after{
content:'ADVENTURE SYSTEM • v1.0 OFICIAL • ONLINE';
display:block;font-size:11px;letter-spacing:4px;
color:#ff7b82;margin-top:7px
}
.level{
position:absolute;top:75px;left:50%;
transform:translateX(-50%);
padding:9px 20px;border-radius:30px;
background:#09090cf2;border:1px solid #ff304455;
box-shadow:0 0 18px #ff001f22
}
.level b{color:#ff5963}
.coins{
position:absolute;right:20px;top:20px;
background:#09090cf2;border:1px solid #ff304455;
padding:10px 14px;border-radius:14px
}
.coins b{color:#ff5963}
.menu{
position:absolute;top:50%;
transform:translateY(-50%);
display:flex;flex-direction:column;gap:12px;
z-index:12
}
.left{left:20px}
.right{right:20px}
.menu button{
background:linear-gradient(135deg,#21070b,#100b0d);
border:1px solid #ff344866;
box-shadow:0 0 18px #ff001f22,inset 0 0 20px #ff001f0d;
border-radius:12px;position:relative;overflow:hidden
}
.menu button:before{
content:'//';color:#ff3948;margin-right:9px
}
.play{
font-size:28px;padding:24px 35px;
background:linear-gradient(135deg,#ff2838,#8f0613)!important;
border-color:#ff7a82!important;
box-shadow:0 0 30px #ff203366,0 8px 0 #4b050b!important
}
.lobby-scene{
position:absolute;left:50%;top:50%;
transform:translate(-50%,-50%) scale(.82);
width:360px;height:260px;
z-index:3;filter:drop-shadow(0 0 24px #ff1d3044)
}
.lobby-island{
position:absolute;left:50%;top:60%;
transform:translate(-50%,-50%);
width:330px;height:120px;border-radius:50%;
background:radial-gradient(circle,#4c1117,#16080a);
border:2px solid #ff354455;
box-shadow:0 0 35px #ff102522,inset 0 0 30px #ff00151a
}
.lobby-char{
position:absolute;left:50%;top:40%;
transform:translate(-50%,-50%);
width:60px;height:130px
}
.lc-head{
position:absolute;left:10px;top:0;
width:40px;height:40px;background:#e2a071;
border-radius:9px
}
.lc-hair{
position:absolute;left:8px;top:-5px;
width:44px;height:15px;background:#20252c;
border-radius:8px 8px 2px 2px;z-index:2
}
.lc-body{
position:absolute;left:7px;top:40px;
width:46px;height:50px;
background:#168ce0;border-radius:7px
}
.lc-arm{
position:absolute;top:43px;
width:12px;height:43px;
background:#e2a071;border-radius:7px
}
.lca{left:-7px;transform:rotate(12deg)}
.lcb{right:-7px;transform:rotate(-12deg)}
.lc-leg{
position:absolute;top:87px;
width:15px;height:43px;background:#26394a;
border-radius:5px
}
.lcl{left:9px}
.lcr{right:9px}
.lc-rod{
position:absolute;right:-15px;top:35px;
width:4px;height:90px;
background:#9b6b38;transform:rotate(-20deg)
}
.modal{
z-index:40;background:#000b;
display:flex;align-items:center;
justify-content:center;padding:14px
}
.panel{
width:min(950px,96vw);max-height:90vh;
overflow:auto;background:#09263a;
border:1px solid #42cfff55;border-radius:20px;
padding:20px
}
.close{float:right;padding:7px 11px}
.skins{
display:grid;grid-template-columns:repeat(5,1fr);gap:10px
}
.skin{
background:#0d3850;border-radius:14px;
padding:8px;text-align:center
}
.avatar{height:110px;position:relative}
.ah{
position:absolute;left:50%;top:5px;
transform:translateX(-50%);
width:42px;height:42px;background:#e2a071;
border-radius:9px
}
.ab{
position:absolute;left:50%;top:47px;
transform:translateX(-50%);
width:48px;height:49px;border-radius:7px
}
.al{
position:absolute;left:calc(50% - 23px);
top:95px;width:46px;height:13px;
background:#243646
}
.skin button{width:100%;padding:8px;margin-top:7px}
.owned{background:#149553!important}
.setting{margin:25px 0}
.setting label{
display:flex;justify-content:space-between;
margin-bottom:8px
}
input[type=range]{
width:100%;accent-color:#10a9ef
}
#game{background:#000}
#game canvas{
position:absolute;inset:0
}
/* NÍVEL PEQUENO NO JOGO */
.gameLevel{
position:absolute;z-index:7;top:8px;left:50%;
transform:translateX(-50%);
background:#09090cf5;
border:1px solid #ff304455;
box-shadow:0 0 12px #ff001f22;
color:#fff;padding:4px 10px;
border-radius:10px;font-size:12px;
font-weight:900;text-align:center;
min-width:110px
}
.gameLevel b{color:#ff5963}
.xpbar{
width:120px;height:6px;margin-top:3px;
background:#16070a;border:1px solid #ff304455;
border-radius:99px;overflow:hidden
}
.xpfill{
height:100%;width:0;
background:linear-gradient(90deg,#ff1028,#ff5963);
box-shadow:0 0 7px #ff2038;
transition:width .25s
}
.xptext{font-size:7px;opacity:.75}
.hud{
position:absolute;z-index:5;top:10px;left:10px;
background:#03111dcc;padding:10px 13px;
border-radius:13px
}
.hud b{color:#63dcff}
.gamebtn{
position:absolute;z-index:6;
top:10px;right:10px
}
.controls{
position:absolute;z-index:6;inset:0;
pointer-events:none
}
.move{
position:absolute;bottom:16px;left:16px;
display:grid;
grid-template-columns:58px 58px 58px;
grid-template-rows:58px 58px;
gap:5px;pointer-events:auto
}
.move button{padding:0;font-size:22px}
.up{grid-column:2}
.leftb{grid-column:1;grid-row:2}
.down{grid-column:2;grid-row:2}
.rightb{grid-column:3;grid-row:2}
.actions{
position:absolute;right:15px;bottom:20px;
display:flex;gap:8px;pointer-events:auto
}
.cast{
width:82px;height:82px;
border-radius:50%;background:#079fe5
}
.hint{
position:absolute;bottom:5px;left:50%;
transform:translateX(-50%);
font-size:11px;white-space:nowrap
}
.catchMsg{
position:absolute;z-index:20;
left:50%;top:22%;
transform:translateX(-50%);
font-size:34px;font-weight:1000;
background:#09263aee;padding:16px 28px;
border:2px solid #55dcff;border-radius:20px;
box-shadow:0 0 35px #00bfff66
}
.clickCatch{
position:absolute;z-index:21;
left:50%;bottom:18%;
transform:translateX(-50%);
font-size:24px;font-weight:1000;
background:#08a8ee;
border:2px solid #b9f2ff;
border-radius:18px;
padding:16px 30px;
box-shadow:0 8px 25px #0008;
cursor:pointer
}
.shopHint{
pointer-events:auto;
position:absolute;left:50%;top:18%;
transform:translateX(-50%);
background:#0b3650ef;
border:2px solid #42cfff;
border-radius:18px;
padding:12px 22px;
text-align:center;font-weight:900;
box-shadow:0 8px 30px #0008;
cursor:pointer
}
.shopHint small,.sellHint small{
font-weight:600;opacity:.8
}
.sellHint{
pointer-events:auto;
position:absolute;left:50%;top:32%;
transform:translateX(-50%);
background:#174a35ee;
border:2px solid #55ff9b;
border-radius:18px;
padding:12px 22px;
text-align:center;font-weight:900;
box-shadow:0 8px 30px #0008
}
.rods{
display:grid;grid-template-columns:repeat(4,1fr);
gap:12px
}
.rodcard{
background:#0d3850;
border:1px solid #42cfff33;
border-radius:16px;padding:14px;
text-align:center
}
.rodicon{font-size:42px}
.rodcard button{
width:100%;margin-top:10px
}
.rodcard.owned{background:#146d4b}
@media(max-width:700px){
.rods{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:650px){
.logo{font-size:25px;top:40px}
.logo:before{font-size:8px}
.logo:after{font-size:8px}
.skins{grid-template-columns:repeat(2,1fr)}
.left{left:8px}
.right{right:8px}
.menu button{padding:11px}
.coins{top:65px}
}
@media(max-width:850px) and (orientation:landscape){
.logo{top:34px}
.menu{top:55%}
.lobby-scene{transform:translate(-50%,-50%) scale(.82)}
}
@media(orientation:portrait){
#loading:after,#lobby:after{
content:"GIRE O CELULAR DE LADO ↔";
display:flex;align-items:flex-end;
justify-content:center;padding-bottom:18px;
color:#ff5963;font-size:14px;
letter-spacing:2px;text-shadow:0 0 8px #ff1730
}
}
</style>
</head>
<body>
<div id="loading" class="screen">
<div class="loadparticles"></div>
<h1>Spedfish</h1>
<div class="bar">
<div id="fill" class="fill"></div>
</div>
<div id="loadtxt" class="loadtxt">Carregando 0%</div>
</div>
<div id="fade" class="screen"></div>
<div id="lobby" class="screen hide">
<div class="lobby-bg">
<div class="lobby-title">🎣 SPEDFISH</div>
<div class="lobby-sub">UMA AVENTURA COMEÇA AQUI</div>
<div class="lobby-scene">
<div class="lobby-island"></div>
<div class="lobby-char">
<div class="lc-hair"></div>
<div class="lc-head"></div>
<div class="lc-body"></div>
<div class="lc-arm lca"></div>
<div class="lc-arm lcb"></div>
<div class="lc-leg lcl"></div>
<div class="lc-leg lcr"></div>
<div class="lc-rod"></div>
</div>
</div>
</div>
<div class="logo">🎣 SPEDFISH</div>
<div class="level">
Nível <b id="level">1</b>
</div>
<div class="coins">
💰 <b id="lobbyCoins">0</b>
</div>
<div class="menu left">
<button onclick="openSkins()">👕 SKINS</button>
<button onclick="openSettings()">⚙️ CONFIGURAÇÕES</button>
<button onclick="openEvents()">🎉 EVENTOS</button>
</div>
<div class="menu right">
<button class="play" onclick="play()">▶ JOGAR</button>
</div>
</div>
<div id="modal" class="screen modal hide">
<div id="panel" class="panel"></div>
</div>
<div id="game" class="screen hide">
<div id="catchMsg" class="catchMsg hide">
🎣 PESCOUUU!
</div>
<div id="clickCatch" class="clickCatch hide">
CLICAR <b id="clickCount">0</b>/10
</div>
<div class="gameLevel">
⭐ NÍVEL <b id="gameLevel">1</b>
<div class="xpbar">
<div id="xpFill" class="xpfill"></div>
</div>
<div class="xptext" id="xpText">0 / 500 XP</div>
</div>
<div class="hud">
🐟 Peixes: <b id="fish">0</b>
· ⭐ XP: <b id="xpHud">0</b>
· 💰 <b id="coins">0</b>
· 🎣 Isca: <b id="bait">3</b>
</div>
<button class="gamebtn" onclick="backLobby()">
☰ MENU
</button>
<div class="controls">
<div class="move">
<button class="up" data-key="w">▲</button>
<button class="leftb" data-key="a">◀</button>
<button class="down" data-key="s">▼</button>
<button class="rightb" data-key="d">▶</button>
</div>
<div class="actions">
<button onclick="
if(nearShop())openShop('peixes');
else if(nearBoatShop())openBoatShop();
else alert('Chegue perto de uma loja.')
">🏪 LOJA</button>
<button class="cast" onclick="cast()">🎣</button>
</div>
<div id="shopHint" class="shopHint hide"
onclick="
if(nearShop())openShop('peixes');
else if(nearBoatShop())openBoatShop()
">
🏪 LOJA<br>
<small>Toque para abrir</small>
</div>
<div class="hint">
Arraste o dedo na tela para girar a câmera
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
const $=id=>document.getElementById(id);
let coins=0,
fish=0,
bait=3,
level=1,
xp=0;
let fishInv={
kling:0,
park:0,
nickgroud:0
};
let enchInv={
kling:0,
park:0,
nickgroud:0
};
let owned=[0];
let equipped=0;
let quality=75;
let sensitivity=50;
let keys={},
scene,
camera,
renderer,
player,
parts=[],
yaw=0,
pitch=.22,
fishObjs=[],
started=false,
rod=0,
ownedRods=[0],
shopOpen=false,
catching=false,
clicks=0,
fishHeld=null;
function sync(){
level=Math.min(2500,1+Math.floor(xp/500));
['coins','lobbyCoins'].forEach(id=>{
$(id).textContent=coins;
});
$('fish').textContent=fish;
$('bait').textContent=bait;
if($('level'))
$('level').textContent=level;
if($('gameLevel'))
$('gameLevel').textContent=level;
if($('xpHud'))
$('xpHud').textContent=xp;
let cur=xp%500;
let pct=level>=2500
?100
:cur/5;
if($('xpFill'))
$('xpFill').style.width=pct+'%';
if($('xpText'))
$('xpText').textContent=
level>=2500
?'MÁXIMO — 2500'
:cur+' / 500 XP';
}
let startTime=performance.now();
function loading(){
let t=Math.min(
7000,
performance.now()-startTime
);
let v=t/7000*100;
$('fill').style.width=v+'%';
$('loadtxt').textContent=
'Carregando '+Math.floor(v)+'%';
if(t<7000){
requestAnimationFrame(loading);
}else{
setTimeout(()=>{
$('fade').style.opacity=1;
setTimeout(()=>{
$('loading').classList.add('hide');
$('fade').style.opacity=0;
$('lobby').classList.remove('hide');
},3000);
},150);
}
}
requestAnimationFrame(loading);
function show(html){
$('panel').innerHTML=html;
$('modal').classList.remove('hide');
}
function closeModal(){
$('modal').classList.add('hide');
shopOpen=false;
}
function openEvents(){
show(`
<button class="close" onclick="closeModal()">✕</button>
<h2>🎉 Eventos</h2>
<h3>Sem eventos disponíveis</h3>
`);
}
function nearShop(){
return player &&
player.position.distanceTo(
new THREE.Vector3(0,1,-82)
)<11;
}
function nearBoatShop(){
return player &&
player.position.distanceTo(
new THREE.Vector3(78,1,-82)
)<11;
}
function openShop(tab='peixes'){
shopOpen=true;
let h=`
<button class="close"
onclick="closeModal()">✕</button>
<h2>🏪 LOJA</h2>
<p>Escolha uma categoria:</p>
<div style="
display:flex;
gap:10px;
margin:14px 0;
flex-wrap:wrap">
<button onclick="openShop('peixes')" style="flex:1">
🐟 PEIXES
</button>
<button onclick="openShop('varas')" style="flex:1">
🎣 VARAS
</button>
<button onclick="openShop('iscas')" style="flex:1">
🪱 ISCAS
</button>
</div>
`;
if(tab==='iscas'){
let packs=[
[1,1],
[10,2],
[50,10],
[100,30],
[200,40],
[1000,100],
[10000,1000]
];
h+=`
<h3>🪱 Iscas</h3>
<p>
Você começa com <b>3 iscas</b>.
Atualmente: <b>${bait}</b>
</p>
<div class="rods">
`;
packs.forEach(p=>{
h+=`
<div class="rodcard">
<div class="rodicon">🪱</div>
<h3>
${p[0].toLocaleString('pt-BR')}
isca${p[0]===1?'':'s'}
</h3>
<b>
R$ ${p[1].toLocaleString('pt-BR')}
</b>
<button onclick="buyBait(${p[0]},${p[1]})">
COMPRAR
</button>
</div>
`;
});
h+='</div>';
}
else if(tab==='peixes'){
let n=fishInv.kling;
let c=fishInv.park;
let k=fishInv.nickgroud;
let totalMoney=
n*50+
c*100+
k*200+
enchInv.kling*200+
enchInv.park*300+
enchInv.nickgroud*500;
h+=`
<h3>🐟 Peixes</h3>
<div class="rodcard">
<div>
🐟 Kling:
<b>${n}</b>
· R$50 · ✨ R$200
</div>
<div>
🐠 Park:
<b>${c}</b>
· R$100 · ✨ R$300
</div>
<div>
🐡 Nickgroud:
<b>${k}</b>
· R$200 · ✨ R$500
</div>
<hr>
<h3>
✨ Encantados:
${enchInv.kling+
enchInv.park+
enchInv.nickgroud}
· Total:
${fish} peixes
</h3>
<p>
Valor total:
<b>
R$ ${totalMoney.toLocaleString('pt-BR')}
</b>
</p>
<button onclick="sellFish()">
VENDER TUDO
</button>
</div>
`;
}
else{
let rods=[
['Madeira',0,'🎣'],
['Plástico',300,'🪄'],
['Ferro',600,'⚙️'],
['Nikricom',1000,'✨']
];
h+=`
<h3>🎣 Varas</h3>
<div class="rods">
`;
rods.forEach((r,i)=>{
let own=ownedRods.includes(i);
h+=`
<div class="rodcard">
<div class="rodicon">${r[2]}</div>
<h3>${r[0]}</h3>
<b>
${r[1]===0
?'GRÁTIS'
:'R$ '+r[1].toLocaleString('pt-BR')}
</b>
<button
class="${own?'owned':''}"
onclick="buyRod(${i})">
${
own
?(rod===i?'EQUIPADA':'EQUIPAR')
:(r[1]===0?'PEGAR GRÁTIS':'COMPRAR')
}
</button>
</div>
`;
});
h+='</div>';
}
show(h);
}
function buyBait(qty,price){
if(coins<price){
alert('Dinheiro insuficiente.');
return;
}
coins-=price;
bait+=qty;
sync();
openShop('iscas');
}
function buyRod(i){
let prices=[
0,
300,
600,
1000
];
let names=[
'Madeira',
'Plástico',
'Ferro',
'Nikricom'
];
if(ownedRods.includes(i)){
rod=i;
sync();
openShop('varas');
return;
}
if(coins<prices[i]){
alert(
'Você precisa de R$ '+
prices[i].toLocaleString('pt-BR')+'.'
);
return;
}
coins-=prices[i];
ownedRods.push(i);
rod=i;
sync();
openShop('varas');
}
function sellFish(){
let money=
fishInv.kling*50+
fishInv.park*100+
fishInv.nickgroud*200+
enchInv.kling*200+
enchInv.park*300+
enchInv.nickgroud*500;
if(!fish){
alert('Você não tem peixes para vender.');
return;
}
coins+=money;
fish=0;
fishInv={
kling:0,
park:0,
nickgroud:0
};
enchInv={
kling:0,
park:0,
nickgroud:0
};
sync();
alert(
'Peixes vendidos por R$ '+
money.toLocaleString('pt-BR')+'!'
);
openShop('peixes');
}
function openBoatShop(){
shopOpen=true;
let boats=[
['Canoa',7000,'🛶'],
['Barquinho',10000,'🚤'],
['Navio',14000,'🚢']
];
let h=`
<button class="close"
onclick="closeModal()">✕</button>
<h2>🚤 LOJA DE BARCOS</h2>
<p>Escolha seu barco:</p>
<div class="rods">
`;
boats.forEach((b,i)=>{
h+=`
<div class="rodcard">
<div class="rodicon">${b[2]}</div>
<h3>${b[0]}</h3>
<b>
R$ ${b[1].toLocaleString('pt-BR')}
</b>
<button onclick="buyBoat(${i})">
COMPRAR
</button>
</div>
`;
});
h+='</div>';
show(h);
}
function buyBoat(i){
let prices=[
7000,
10000,
14000
];
let names=[
'Canoa',
'Barquinho',
'Navio'
];
if(coins<prices[i]){
alert(
'Você precisa de R$ '+
prices[i].toLocaleString('pt-BR')+
' para comprar '+names[i]+'.'
);
return;
}
coins-=prices[i];
sync();
alert(names[i]+' comprado!');
openBoatShop();
}
function openSettings(){
show(`
<button class="close"
onclick="closeModal()">✕</button>
<h2>⚙️ Configurações</h2>
<div class="setting">
<label>
Qualidade
<b id="qv">${quality}%</b>
</label>
<input
type="range"
min="0"
max="100"
value="${quality}"
oninput="
quality=+this.value;
qv.textContent=this.value+'%';
applyQuality()
"
>
</div>
<div class="setting">
<label>
Sensibilidade
<b id="sv">${sensitivity}%</b>
</label>
<input
type="range"
min="0"
max="100"
value="${sensitivity}"
oninput="
sensitivity=+this.value;
sv.textContent=this.value+'%'
"
>
</div>
<button onclick="closeModal()">
SALVAR
</button>
`);
}
const colors=[
'#168ce0',
'#e44d65',
'#814de0',
'#16a36a',
'#e09b2d',
'#f04aa0',
'#14a6a6',
'#73452b',
'#e7e7e7',
'#222'
];
function avatar(i){
return `
<div class="avatar">
<div class="ah"></div>
<div
class="ab"
style="background:${colors[i%colors.length]}">
</div>
<div class="al"></div>
</div>
`;
}
function openSkins(){
let s=`
<button class="close"
onclick="closeModal()">✕</button>
<h2>👕 Skins</h2>
<p>
20 personagens 3D · preço:
2.000 moedas cada
</p>
<div class="skins">
`;
for(let i=0;i<20;i++){
let own=owned.includes(i);
s+=`
<div class="skin">
${avatar(i)}
<b>Skin ${i+1}</b>
<button
class="${own?'owned':''}"
onclick="chooseSkin(${i})">
${
own
?(equipped===i?'EQUIPADO':'EQUIPAR')
:'2.000 💰'
}
</button>
</div>
`;
}
show(s+'</div>');
}
function chooseSkin(i){
if(!owned.includes(i)){
if(coins<2000){
alert(
'Você precisa de 2.000 moedas.'
);
return;
}
coins-=2000;
owned.push(i);
}
equipped=i;
sync();
openSkins();
if(player)
changeSkin();
}
function play(){
closeModal();
$('lobby').classList.add('hide');
$('game').classList.remove('hide');
if(!started){
started=true;
init();
}else{
sync();
}
}
function backLobby(){
$('game').classList.add('hide');
$('lobby').classList.remove('hide');
}
function mat(c){
return new THREE.MeshStandardMaterial({
color:c
});
}
function cube(s,c){
let m=new THREE.Mesh(
new THREE.BoxGeometry(...s),
mat(c)
);
return m;
}
function makeTree(x,z){
let trunk=cube(
[.8,5,.8],
0x79502e
);
trunk.position.set(
x,2.5,z
);
scene.add(trunk);
for(let i=0;i<4;i++){
let leaf=new THREE.Mesh(
new THREE.ConeGeometry(
2.1,3.5,5
),
mat(0x159b47)
);
leaf.position.set(
x+(i%2?1:-1)*.8,
5.2+(i*.35),
z+(i<2?-.5:.5)
);
leaf.rotation.z=
(i%2?-.35:.35);
scene.add(leaf);
}
}
function makePlayer(){
player=new THREE.Group();
scene.add(player);
let body=cube(
[1.15,1.5,.72],
colors[equipped%colors.length]
);
body.position.y=2;
body.userData.body=true;
player.add(body);
let head=cube(
[.9,.9,.9],
0xe2a071
);
head.position.y=3.2;
player.add(head);
let hair=cube(
[.94,.25,.94],
0x20252c
);
hair.position.y=3.7;
player.add(hair);
for(let side of [-1,1]){
let arm=cube(
[.35,1.25,.4],
0xe2a071
);
arm.position.set(
side*.78,2,0
);
arm.userData.arm=side;
player.add(arm);
parts.push(arm);
let leg=cube(
[.38,1.25,.45],
0x26394a
);
leg.position.set(
side*.3,.72,0
);
leg.userData.leg=side;
player.add(leg);
parts.push(leg);
}
let handRod=cube(
[.12,2.5,.12],
0x9b6b38
);
handRod.position.set(
.95,2.25,.15
);
handRod.rotation.z=-.35;
handRod.userData.handRod=true;
player.add(handRod);
parts.push(handRod);
}
function nearBridge(){
if(!player)
return false;
let x=player.position.x;
let z=player.position.z;
return(
(z<-82&&Math.abs(x)<12)||
(z>82&&Math.abs(x)<12)||
(x>82&&Math.abs(z)<12)||
(x<-82&&Math.abs(z)<12)
);
}
function makeRealFish(
color=0xff3030,
species='Lmabreia',
size=1
){
const g=new THREE.Group();
const bodyScale=
species==='nickgroud'
?1.3
:species==='park'
?1.1
:.98;
const bodyMat=
new THREE.MeshStandardMaterial({
color,
roughness:.25,
metalness:.04
});
const bellyColor=
species==='kling'
?0xffb8b8
:species==='park'
?0xb9d7ff
:0xffd0a0;
const bellyMat=
new THREE.MeshStandardMaterial({
color:bellyColor,
roughness:.32
});
const finMat=
new THREE.MeshStandardMaterial({
color:0xf8f3e8,
transparent:true,
opacity:.82,
roughness:.35,
side:THREE.DoubleSide
});
const darkFinMat=
new THREE.MeshStandardMaterial({
color:0x7c2630,
roughness:.3,
side:THREE.DoubleSide
});
const eyeWhite=
new THREE.MeshStandardMaterial({
color:0xffffff,
roughness:.18
});
const eyeBlack=
new THREE.MeshStandardMaterial({
color:0x090b10,
roughness:.08
});
const body=new THREE.Mesh(
new THREE.SphereGeometry(.62,32,20),
bodyMat
);
body.scale.set(
1.55*bodyScale,
.74*bodyScale,
.52*bodyScale
);
g.add(body);
g.userData.body=body;
const belly=new THREE.Mesh(
new THREE.SphereGeometry(.625,28,16),
bellyMat
);
belly.scale.set(
1.25*bodyScale,
.48*bodyScale,
.525*bodyScale
);
belly.position.set(
.12,
-.12*bodyScale,
0
);
g.add(belly);
const stripeMat=
new THREE.MeshStandardMaterial({
color:0xffffff,
transparent:true,
opacity:.24,
roughness:.45
});
const stripe=new THREE.Mesh(
new THREE.BoxGeometry(
1.65*bodyScale,
.055*bodyScale,
.025*bodyScale
),
stripeMat
);
stripe.position.set(
-.02,
.03*bodyScale,
.505*bodyScale
);
g.add(stripe);
for(let i=0;i<7;i++){
const scale=new THREE.Mesh(
new THREE.SphereGeometry(
.045*bodyScale,
8,6
),
stripeMat
);
scale.scale.z=.25;
scale.position.set(
(i-3)*.32*bodyScale,
.04*bodyScale,
.515*bodyScale
);
g.add(scale);
}
const tail=new THREE.Group();
tail.position.x=
-1.05*bodyScale;
g.add(tail);
g.userData.tail=tail;
const tailTop=new THREE.Mesh(
new THREE.ConeGeometry(
.48*bodyScale,
.82*bodyScale,
5
),
bodyMat
);
tailTop.rotation.z=Math.PI/2;
tailTop.position.x=
-.36*bodyScale;
tailTop.scale.y=1.18;
tail.add(tailTop);
const tailBottom=tailTop.clone();
tailBottom.rotation.z=-Math.PI/2;
tailBottom.position.x=
-.36*bodyScale;
tail.add(tailBottom);
const tailCenter=new THREE.Mesh(
new THREE.SphereGeometry(
.18*bodyScale,12,8
),
bodyMat
);
tailCenter.position.x=
-.1*bodyScale;
tail.add(tailCenter);
const dorsal=new THREE.Mesh(
new THREE.ConeGeometry(
.28*bodyScale,
.7*bodyScale,
4
),
finMat
);
dorsal.position.set(
.02,
.53*bodyScale,
0
);
dorsal.rotation.z=Math.PI;
g.add(dorsal);
g.userData.dorsal=dorsal;
const lower=new THREE.Mesh(
new THREE.ConeGeometry(
.22*bodyScale,
.52*bodyScale,
4
),
finMat
);
lower.position.set(
-.12,
-.48*bodyScale,
0
);
g.add(lower);
g.userData.lower=lower;
for(const z of [-.34,.34]){
const fin=new THREE.Mesh(
new THREE.ConeGeometry(
.2*bodyScale,
.62*bodyScale,
4
),
finMat
);
fin.position.set(
.18*bodyScale,
-.08*bodyScale,
z
);
fin.rotation.x=
z>0?.62:-.62;
fin.rotation.z=Math.PI;
g.add(fin);
}
const gillMat=
new THREE.MeshStandardMaterial({
color:0x7d2028,
roughness:.38
});
const gill=new THREE.Mesh(
new THREE.TorusGeometry(
.18*bodyScale,
.025*bodyScale,
8,12,
Math.PI*.9
),
gillMat
);
gill.position.set(
.48*bodyScale,
.02*bodyScale,
.47*bodyScale
);
gill.rotation.y=Math.PI/2;
g.add(gill);
for(const z of [-.22,.22]){
const eye=new THREE.Group();
eye.position.set(
.79*bodyScale,
.18*bodyScale,
z*1.7
);
g.add(eye);
const white=new THREE.Mesh(
new THREE.SphereGeometry(
.12*bodyScale,16,10
),
eyeWhite
);
eye.add(white);
const pupil=new THREE.Mesh(
new THREE.SphereGeometry(
.07*bodyScale,14,10
),
eyeBlack
);
pupil.position.set(
.095*bodyScale,
0,
z>0
?.045*bodyScale
:-.045*bodyScale
);
eye.add(pupil);
const shine=new THREE.Mesh(
new THREE.SphereGeometry(
.022*bodyScale,8,6
),
new THREE.MeshBasicMaterial({
color:0xffffff
})
);
shine.position.set(
.13*bodyScale,
.035*bodyScale,
z>0
?.065*bodyScale
:-.065*bodyScale
);
eye.add(shine);
}
const mouth=new THREE.Mesh(
new THREE.TorusGeometry(
.14*bodyScale,
.022*bodyScale,
8,16,
Math.PI*.9
),
darkFinMat
);
mouth.position.set(
.93*bodyScale,
-.035*bodyScale,
0
);
mouth.rotation.y=Math.PI/2;
g.add(mouth);
g.scale.setScalar(size);
return g;
}
function fishType(){
let r=Math.random();
let f;
if(r<.60)
f=['kling',50,2,100];
else if(r<.90)
f=['park',100,1,200];
else
f=['nickgroud',120,.6,300];
let enchanted=
Math.random()*100<f[2];
return[
f[0],
enchanted?f[3]:f[1],
enchanted
];
}
function finishCatch(){
player.userData.fishing=false;
player.position.y=1;
player.rotation.x=0;
let f=fishType();
fish++;
xp+=f[1];
fishInv[f[0]]++;
if(f[2])
enchInv[f[0]]++;
$('clickCatch').classList.add('hide');
$('catchMsg').classList.remove('hide');
setTimeout(()=>{
$('catchMsg').classList.add('hide');
},1400);
let col=
f[0]==='kling'
?0x2ed3ff
:f[0]==='park'
?0x7b7cff
:0xffb52e;
let held=makeRealFish(
col,
f[0],
.72
);
held.position.set(
.95,2.25,-.55
);
held.rotation.set(
.05,-.2,-.2
);
held.userData.fishHeld=true;
player.add(held);
fishHeld=held;
let heldRef=held;
setTimeout(()=>{
if(fishHeld===heldRef){
player.remove(heldRef);
fishHeld=null;
}
},3500);
sync();
}
function doCatchClick(){
if(!catching)
return;
clicks++;
$("clickCount").textContent=
clicks;
if(clicks>=10){
catching=false;
finishCatch();
}
}
function changeSkin(){
let body=
player?.children.find(
x=>x.geometry?.parameters?.width===1.15
);
if(body)
body.material.color.set(
colors[equipped%colors.length]
);
}
function init(){
scene=new THREE.Scene();
scene.background=
new THREE.Color(0x75d3e8);
scene.fog=
new THREE.Fog(
0x75d3e8,
55,
145
);
camera=
new THREE.PerspectiveCamera(
65,
innerWidth/innerHeight,
.1,
300
);
renderer=
new THREE.WebGLRenderer({
antialias:true,
powerPreference:'high-performance'
});
renderer.setSize(
innerWidth,
innerHeight
);
game.prepend(
renderer.domElement
);
scene.add(
new THREE.HemisphereLight(
0xbbeeff,
0x19364a,
2
)
);
let sun=
new THREE.DirectionalLight(
0xffffff,
1.8
);
sun.position.set(
25,45,20
);
scene.add(sun);
let island=new THREE.Mesh(
new THREE.CylinderGeometry(
105,117,4,64
),
mat(0xc9aa65)
);
island.position.y=-2;
scene.add(island);
let water=new THREE.Mesh(
new THREE.PlaneGeometry(
400,400
),
new THREE.MeshPhongMaterial({
color:0x0785ad,
transparent:true,
opacity:.85
})
);
water.rotation.x=-Math.PI/2;
water.position.y=-4;
scene.add(water);
[
[-70,-55],
[70,-55],
[-65,55],
[65,55],
[-20,75],
[20,75],
[-82,0],
[82,0],
[-45,-5],
[45,-5],
[-15,-75],
[15,-75]
].forEach(p=>makeTree(...p));
let shop=cube(
[11,5,6],
0xd9c39d
);
shop.position.set(
0,2,-82
);
scene.add(shop);
let boatShop=cube(
[11,5,6],
0x486477
);
boatShop.position.set(
78,2,-82
);
scene.add(boatShop);
let boatRoof=cube(
[12,1,7],
0x273a4a
);
boatRoof.position.set(
78,5,-82
);
scene.add(boatRoof);
let boatDoor=cube(
[2.5,3,.2],
0x10232d
);
boatDoor.position.set(
78,1.5,-78.9
);
scene.add(boatDoor);
let roof=cube(
[12,1,7],
0xd74a42
);
roof.position.set(
0,5,-82
);
scene.add(roof);
let door=cube(
[2.5,3,.2],
0x36596a
);
door.position.set(
0,1.5,-78.9
);
scene.add(door);
let bridge=cube(
[16,1.2,18],
0x70472c
);
bridge.position.set(
0,-.1,-92
);
scene.add(bridge);
let bridge2=bridge.clone();
bridge2.position.set(
92,-.1,0
);
bridge2.rotation.y=Math.PI/2;
scene.add(bridge2);
let bridge3=bridge.clone();
bridge3.position.set(
0,-.1,92
);
scene.add(bridge3);
let bridge4=bridge.clone();
bridge4.position.set(
-92,-.1,0
);
bridge4.rotation.y=Math.PI/2;
scene.add(bridge4);
for(let x of [-7,-3,3,7]){
let post=cube(
[.35,2,.35],
0x4c301e
);
post.position.set(
x,1,-28
);
scene.add(post);
let rail=cube(
[.35,1.4,22],
0x4c301e
);
rail.position.set(
x,1,-28
);
scene.add(rail);
}
let sign=cube(
[5,2,.25],
0x4b2d1b
);
sign.position.set(
0,3,-24
);
scene.add(sign);
makePlayer();
player.position.set(
0,1,7
);
for(let i=0;i<30;i++){
let species=
['kling','park','nickgroud']
[i%3];
let col=
species==='kling'
?0x2ed3ff
:species==='park'
?0x7b7cff
:0xffb52e;
let f=makeRealFish(
col,
species,
.58+Math.random()*.18
);
let a=Math.random()*Math.PI*2;
let r=
108+Math.random()*30;
f.position.set(
Math.cos(a)*r,
-3.45-Math.random()*1.2,
Math.sin(a)*r
);
f.rotation.y=
a+Math.PI;
f.userData.t=
Math.random()*10;
f.userData.baseY=
f.position.y;
f.userData.speed=
.012+Math.random()*.012;
f.userData.phase=
Math.random()*6.28;
f.userData.baseScaleY=
f.scale.y;
scene.add(f);
fishObjs.push(f);
}
setupControls();
applyQuality();
window.onresize=resize;
animate();
}
function applyQuality(){
if(!renderer)
return;
let q=Number(quality);
let tier=
q<=20
?'PÉSSIMA'
:q<=40
?'BOA'
:q<=80
?'MUITO BOA'
:'MUITOOOO BOA';
if($('qv'))
$('qv').textContent=
q+'% — '+tier;
let ratio=
q<=20?.5
:q<=40?.75
:q<=80?1.15
:1.6;
renderer.setPixelRatio(ratio);
if(scene){
if(q<=20){
scene.fog.near=28;
scene.fog.far=80;
}else if(q<=40){
scene.fog.near=40;
scene.fog.far=105;
}else if(q<=80){
scene.fog.near=55;
scene.fog.far=145;
}else{
scene.fog.near=70;
scene.fog.far=190;
}
}
}
function resize(){
camera.aspect=
innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
innerWidth,
innerHeight
);
}
$("clickCatch").onclick=
doCatchClick;
function setupControls(){
document
.querySelectorAll('[data-key]')
.forEach(b=>{
let k=b.dataset.key;
b.onpointerdown=e=>{
e.preventDefault();
keys[k]=1;
};
b.onpointerup=
b.onpointercancel=()=>{
keys[k]=0;
};
});
window.onkeydown=e=>
keys[e.key.toLowerCase()]=1;
window.onkeyup=e=>
keys[e.key.toLowerCase()]=0;
renderer.domElement.style.touchAction=
'none';
let camPointers=
new Map();
renderer.domElement.onpointerdown=e=>{
if(
e.pointerType==='mouse' &&
e.button!==0
)
return;
camPointers.set(
e.pointerId,
{
x:e.clientX,
y:e.clientY
}
);
renderer.domElement
.setPointerCapture?.(
e.pointerId
);
};
renderer.domElement.onpointermove=e=>{
let p=
camPointers.get(
e.pointerId
);
if(!p)
return;
let dx=
e.clientX-p.x;
let dy=
e.clientY-p.y;
p.x=e.clientX;
p.y=e.clientY;
let sens=
.003*
(
.35+
.65*
(sensitivity/100)
);
yaw-=dx*sens;
pitch=Math.max(
-.45,
Math.min(
.9,
pitch-dy*sens
)
);
};
renderer.domElement.onpointerup=e=>
camPointers.delete(
e.pointerId
);
renderer.domElement.onpointercancel=e=>
camPointers.delete(
e.pointerId
);
}
function animate(){
requestAnimationFrame(
animate
);
if(!renderer)
return;
const sp=.13*1.7;
let dx2=0,
dz2=0,
moving=false;
if(keys.w){
dx2-=Math.sin(yaw)*sp;
dz2-=Math.cos(yaw)*sp;
moving=true;
}
if(keys.s){
dx2+=Math.sin(yaw)*sp;
dz2+=Math.cos(yaw)*sp;
moving=true;
}
if(keys.a){
dx2-=Math.cos(yaw)*sp;
dz2+=Math.sin(yaw)*sp;
moving=true;
}
if(keys.d){
dx2+=Math.cos(yaw)*sp;
dz2-=Math.sin(yaw)*sp;
moving=true;
}
if(
moving &&
!player.userData.fishing
){
player.position.x=
Math.max(
-98,
Math.min(
98,
player.position.x+dx2
)
);
player.position.z=
Math.max(
-98,
Math.min(
98,
player.position.z+dz2
)
);
player.rotation.y=
Math.atan2(dx2,dz2);
let t=
performance.now()*.014;
parts.forEach(p=>{
if(p.userData.arm)
p.rotation.x=
Math.sin(t)*
.65*
p.userData.arm;
if(p.userData.leg)
p.rotation.x=
-Math.sin(t)*
.7*
p.userData.leg;
});
}else if(
!player.userData.fishing
){
parts.forEach(p=>
p.rotation.x*=.85
);
}
if(player.userData.fishing){
let ft=
performance.now()-
player.userData.fishingStart;
player.position.y=
ft<3000?.65:.85;
parts.forEach(p=>{
if(p.userData.leg)
p.rotation.x=-.15;
if(p.userData.arm)
p.rotation.x=-1.15;
});
if(ft>3000)
player.rotation.x=-.08;
}
let dist=8;
let target=
new THREE.Vector3(
player.position.x+
Math.sin(yaw)*dist,
player.position.y+
4+
pitch*4,
player.position.z+
Math.cos(yaw)*dist
);
camera.position.lerp(
target,
.22
);
camera.lookAt(
player.position.x,
player.position.y+1.7,
player.position.z
);
if($('shopHint')){
$('shopHint')
.classList.toggle(
'hide',
(!nearShop()&&!nearBoatShop())||
shopOpen
);
}
fishObjs.forEach(f=>{
let tm=
performance.now()*.001;
let ph=
f.userData.phase||0;
f.position.y=
f.userData.baseY+
Math.sin(
tm*2.2+
f.userData.t
)*.2;
f.rotation.y+=
f.userData.speed||.005;
f.rotation.z=
Math.sin(
tm*3.2+ph
)*.045;
f.position.x+=
Math.cos(
f.rotation.y
)*.018;
f.position.z+=
Math.sin(
f.rotation.y
)*.018;
let wave=
Math.sin(
tm*10+ph
)*.48;
if(f.userData.tail)
f.userData.tail.rotation.y=
wave;
if(f.userData.dorsal)
f.userData.dorsal.rotation.z=
Math.PI+
Math.sin(
tm*7+ph
)*.08;
if(f.userData.lower)
f.userData.lower.rotation.z=
Math.sin(
tm*7+ph
)*.07;
f.scale.y=
f.userData.baseScaleY*
(
1+
Math.sin(
tm*10+ph
)*.012
);
});
if(fishHeld){
let ht=
performance.now()*.006;
fishHeld.rotation.y=
Math.sin(ht)*.08;
if(fishHeld.userData.tail)
fishHeld.userData.tail.rotation.y=
Math.sin(ht*8)*.3;
}
renderer.render(
scene,
camera
);
}
function cast(){
if(catching)
return;
if(bait<=0){
alert(
'Sem iscas. Vá à loja para comprar.'
);
return;
}
if(!nearBridge()){
alert(
'Chegue perto de uma ponte para pescar.'
);
return;
}
bait--;
sync();
catching=true;
clicks=0;
$('clickCount').textContent=0;
player.userData.fishing=true;
player.userData.fishingStart=
performance.now();
player.rotation.y=0;
setTimeout(()=>{
if(!catching)
return;
$('catchMsg')
.classList.remove('hide');
setTimeout(()=>{
if(catching){
$('catchMsg')
.classList.add('hide');
$('clickCatch')
.classList.remove('hide');
}
},900);
},3000);
}
sync();
</script>
</body>
</html>Game Source: Remix of Spedfish — Auto Fish Play 3D
Creator: MegaFlare51
Libraries: none
Complexity: complex (2759 lines, 42.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: spedfish-auto-fish-play-3d-megaflare51" to link back to the original. Then publish at arcadelab.ai/publish.