Этап 44: береговые силуэты на горизонте (полный вариант различения локаций) + ревизия Реки. scenery/initScenery() по локации: Река — береговая полоса на всю ширину {k:'bank',h} (трава 12–17 px + тёмная кромка у воды) + хвойный лес в 2 ряда, посаженный на её кромку ({k:'pine',layer,by} — by=waterY−bankH, база ствола); Озёра — холмы {k:'hill',rx,ry} (2–3 дуги за деревьями) + лиственные деревья {k:'tree'}; Море — парусник {k:'ship',dir,v,s} (медленно дрейфует по курсу, паруса светлые) + 3 чайки {k:'gull',y,v,ph} (летят, машут крыльями); Океан — ПУСТО (открытый горизонт без берега). drawScenery() — между drawSky() и drawWater() (вода перекрывает базу силуэтов); ночь — dusk()=hexLerp(·,'#0b1220',nm*0.8) гасит силуэты, чайки почти гаснут; дрейф — updateDecor(dt). Река без волн (ревизия): LOCATIONS[0].waves — все нули, drawWater() рисует линию только при amp>0/amp2>0 — спокойная вода, ровный край. Провозировка initScenery(): resize(), обе ветки смены локации shopClick (рядом с initFish), кадры lake/sea/ocean в gen-screenshots.js (без него кадр Моря показал бы лес с Реки). Скриншоты перегенерированы (72 PNG: screenshots-wiki/ + screenshots-wiki-en/). Чисто визуал: роллы/баланс/физика не тронуты. Тесты: 193/193 PASS, exit 0; check.mjs OK; balance.mjs — пины без изменений. Приёмка: headless 1280×720, 4 локации × день/ночь + кроп-контроль горизонта (пиксельная выборка неба — чистый градиент, «точки» в превью — артефакт даунскейла). Доки: DEVLOG (Этап 44), AGENTS.md (строка «Локации» — waves/сценарий silhouette + вызовы initScenery), README (строка локаций — «свой берег»).
@@ -33,7 +33,7 @@
|
||||
- Варианты рыб (особая рыба ≈31%): медная ×1.5, серебряная ×2, кварцевая ×2.5, золотая ×3, изумрудная ×4, алмазная ×4.5, радужная ×5 (топ, самая редкая) — бейдж и оформление карточки улова; снасть усиливает шанс (тип наживки и её уровень — редким вариантам прибавка больше, топ-снасть ≈×5 к шансу радужной)
|
||||
- Погода: дождь (🌧️) — шанс особых рыб ×3; туман (🌫️) — клёв ×1,5; событие идёт 120 с, следующее — раз в 2–4 минуты — <img src="screenshots-wiki/mobp-weather-rain.png" width="200" alt="дождь">
|
||||
- Скины лодки и поплавка 🎨 — вкладка «Скины» в лавке: лодка «Красная лодка» 800 🪙 / «Лодка-домик» 2000 🪙, поплавок «Красный» 500 🪙 / «Неоновый» 1500 🪙 — чистая косметика (без эффектов на геймплей)
|
||||
- Локации: Озёра (65 536 🪙) — судак, хариус, гигантский рак; Море (131 072 🪙) — скумбрия, тунец, рыба-меч; Океан (196 608 🪙 = ×1.5 Моря) — глубоководные гиганты: синий кит, гигантский кальмар, скат-манта, гигантский осьминог; у каждой — свой состав днём/ночью, своя вода (цвет, характер волн, рыбы в толще), чем дальше вода — тем доходнее улов; покупка и переключение во вкладке 🗺️ лавки — <img src="screenshots-wiki/mobp-lake.png" width="200" alt="озёра"> <img src="screenshots-wiki/mobp-sea.png" width="200" alt="море"> <img src="screenshots-wiki/mobp-ocean.png" width="200" alt="океан">
|
||||
- Локации: Озёра (65 536 🪙) — судак, хариус, гигантский рак; Море (131 072 🪙) — скумбрия, тунец, рыба-меч; Океан (196 608 🪙 = ×1.5 Моря) — глубоководные гиганты: синий кит, гигантский кальмар, скат-манта, гигантский осьминог; у каждой — свой состав днём/ночью, своя вода (цвет, волны, рыбы в толще) и свой берег (Река — лесистая полоса, Озёра — холмы, Море — парусник и чайки, Океан — открытый горизонт), чем дальше вода — тем доходнее улов; покупка и переключение во вкладке 🗺️ лавки — <img src="screenshots-wiki/mobp-lake.png" width="200" alt="озёра"> <img src="screenshots-wiki/mobp-sea.png" width="200" alt="море"> <img src="screenshots-wiki/mobp-ocean.png" width="200" alt="океан">
|
||||
- Квесты дня (3 шт, генерация по дню), стрик входов со «щитом», 34 достижения с бонусами
|
||||
- Дневник рыбака — коллекция, максимальный улов по каждому виду, статистика
|
||||
- Магазин с вкладками: Лавка 🛒, Дневник 📔, Награды 🎯, Достижения 🏅, Скины 🎨, Локации 🗺️
|
||||
|
||||
@@ -795,3 +795,18 @@ git add -A && git commit -m "что изменилось" && git push
|
||||
**Не тронуто:** небо (`SKY_PHASES`), облака, звёзды, лодка/поплавок, дорожка света от светила, погода — общие для всех вод (по выбранному варианту); роллы/баланс/физика — `waves` и `AMBIENT_FISH` чисто данные/визуал.
|
||||
|
||||
**Тесты:** новые пины не нужны (визуал не ассертится; формат `water` lc1 — без изменений). Итого **193/193 PASS, exit 0**; `check.mjs` OK; `tools/balance.mjs` — пины без изменений (Река 65.1/125.9 · Озёра 194.1/249.0 · Море 297.3/476.7 · Океан 329.1/552.7; `LOCATIONS` в вырезке скрипта, но это данные — цифры не сдвинулись). Визуальная приёмка по скриншотам desk-вьюпорта: Река (приглушённый циан, мелкие рыбы) / Озёра (teal, гладь) / Море (светлая бирюза, умеренная волна, средние рыбы) / Океан (глубокий тёмно-синий, крупные зыби, гигантские рыбы) — четыре разные воды.
|
||||
|
||||
## Этап 44. Береговые силуэты на горизонте (полный вариант различения локаций)
|
||||
|
||||
**Задача:** «давай сделаем полный вариант» — после лёгкого Этапа 43 (вода/волны/рыбы) добавить то, что делает место узнаваемым: горизонт. Первая ревизия внутри этапа: «на реке береговая полоса на всю ширину, на которой растут деревья, и убрать волны».
|
||||
|
||||
**Решения:**
|
||||
- **`scenery` + `initScenery()`** (секция декораций, после `AMBIENT_FISH`/`initFish()`): массив силуэтов на горизонте по локации (индекс = `save.location`, clamp). Река — **береговая полоса на всю ширину** `{k:'bank',h}` (трава 12–17 px + тёмная влажная кромка у воды) + **хвойный лес в 2 ряда, посаженный НА КРОМКУ** (`{k:'pine',layer,by}`, `by=waterY−bankH` — база ствола; `by` хранится в объекте при генерации, `waterY` к этому моменту известен: `resize()` считает его до `initScenery()`, смена локации — в игре с готовым `waterY`). Озёра — холмы `{k:'hill',rx,ry}` (2–3, дуга эллипса над водой) + лиственные деревья `{k:'tree',h}` (ствол + крона из 3 кругов). Море — парусник `{k:'ship',x,v,s,dir}` (корпус/мачта/два светлых паруса) + 3 чайки `{k:'gull',y,v,ph}` (дуги крыла, взмах `sin(time*7+ph)`). Океан — **пусто**: открытый горизонт без берега (вода и небо — его отличительный знак).
|
||||
- **`drawScenery()`** — между `drawSky()` и `drawWater()` в `draw()`: базу силуэтов (низ холмов, киль, нижние 3 px травы) перекрывает градиент воды — «садятся» ровно на линию воды. Ночь — `dusk(c)=hexLerp(c,'#0b1220',nm*0.8)` гасит силуэты к тёмно-синему; паруса темнеют (`0.9−0.45·nm`), чайки гаснут почти до нуля (alpha `<0.12` — пропуск). Порядок в массиве = порядок рисования: у Реки bank ПЕРВЫЙ (лес на нём), у Озёр холмы перед деревьями.
|
||||
- **Дрейф** — `updateDecor(dt)`: парусник `x += dir·v` (2.5–5 px/s, обход за экраном по курсу), чайки `x += v` (14–26 px/s, вправо, обход).
|
||||
- **Провозировка:** `initScenery()` — `resize()` (рядом с `initClouds/initSparkles/initRain`), ОБЕ ветки смены локации в `shopClick()` (рядом с `initFish()`), старт — через `resize()` (loadSave → resize). Кадры lake/sea/ocean в `gen-screenshots.js` тоже зовут `initScenery()` — без них кадр Моря показал бы лес с Реки (сцена живёт в памяти, а не пересобирается по `save.location`).
|
||||
- **Ревизия — Река без волн:** `LOCATIONS[0].waves` → все нули, `drawWater()` рисует линию только при `amp>0`/`amp2>0` (обе линии). Река — спокойная вода: ровный край градиента + берег с лесом. Остальные воды — как в Этапе 43.
|
||||
|
||||
**Не тронуто:** небо/облака/звёзды/светила — общие; лодка/поплавок/дорожка света; роллы/баланс/физика; `resize()` по-прежнему без `initFish()` (рыбы не пересобираются по resize — поведение Этапа 43 сохранено).
|
||||
|
||||
**Тесты:** новые пины не нужны (визуал не ассертится; canvas-пиксели тестами не проверяются). Итого **193/193 PASS, exit 0**; `check.mjs` OK; `tools/balance.mjs` — пины без изменений. Визуальная приёмка (headless 1280×720, 4 локации × день/ночь, + кроп-контроль горизонта): Река — сплошная травяная полоса на всю ширину, лес с видимыми стволами на её кромке, волновых линий нет (день и ночь: ночью полоса/лес — тёмные силуэты, фонарь лодки светится); Озёра — холмы за деревьями, гладь; Море — парусник (паруса читаются днём и ночью) + чайки; Океан — открытый горизонт. «Светлые точки в небе» в дневном кадре — артефакт даунскейла превью (пиксельная выборка неба: чистый градиент).
|
||||
|
||||
@@ -1136,13 +1136,14 @@ const SPECIES_NIGHT=SPECIES.map(s=>Object.assign({},s,{weight:NIGHT_WEIGHTS[s.id
|
||||
Этап 43: визуальное различение локаций — (1) вода различима по цвету (Река/Озёра не
|
||||
тронуты; Море — светлая бирюза, Океан — глубокий тёмно-синий; тест пинит только ФОРМАТ
|
||||
6 hex на локацию — значения можно двигать); (2) waves — характер волн поверхности в
|
||||
drawWater(): amp/amp2 — амплитуда, freq/freq2 — частота, speed/speed2 — скорость (Река —
|
||||
мелкие гребни, заметно текут в сторону; Озёра — длинная медленная гладь; Море — умеренная
|
||||
волна; Океан — крупные зыби). Чисто визуал, на механику/баланс не влияет. */
|
||||
drawWater(): amp/amp2 — амплитуда, freq/freq2 — частота, speed/speed2 — скорость;
|
||||
amp/amp2=0 — линии НЕ рисуются (Река — без волн, спокойная вода — ревизия Этапа 44);
|
||||
Озёра — длинная медленная гладь; Море — умеренная волна; Океан — крупные зыби.
|
||||
Чисто визуал, на механику/баланс не влияет. */
|
||||
const LOCATIONS=[
|
||||
{id:0, name:'Река', nameEn:'River', cost:0, desc:'родная вода', descEn:'home waters',
|
||||
water:['#2f9bd8','#1f6fb0','#0a2f52','#0d2246','#091c3a','#041226'],
|
||||
waves:{amp:2.2,amp2:2.5,freq:0.06,freq2:0.05,speed:3.2,speed2:2.0}},
|
||||
waves:{amp:0,amp2:0,freq:0,freq2:0,speed:0,speed2:0}}, // Река — без волновых линий (спокойная вода)
|
||||
{id:1, name:'Озёра', nameEn:'Lakes', cost:65536, desc:'озёрные рыбы: судак, хариус, линь, навага, сиг, гигантский рак и другие', descEn:'lake fish: zander, grayling, tench, navaga, cisco, giant crayfish and more',
|
||||
water:['#3d9c8a','#20726a','#0c423c','#0f2b33','#0a2129','#04141a'],
|
||||
waves:{amp:1.2,amp2:1.6,freq:0.03,freq2:0.025,speed:0.8,speed2:0.5}},
|
||||
@@ -1698,7 +1699,7 @@ function resize(){
|
||||
ctx.setTransform(DPR,0,0,DPR,0,0);
|
||||
waterY=Math.round(H*0.26);
|
||||
boatX=Math.round(W*0.20);
|
||||
initClouds(); initSparkles(); initRain();
|
||||
initClouds(); initSparkles(); initRain(); initScenery(); // Этап 44: силуэты по новой ширине
|
||||
}
|
||||
window.addEventListener('resize',resize);
|
||||
|
||||
@@ -1770,6 +1771,7 @@ let weather=null; // 'rain'/'fog'/null — активное погодное с
|
||||
let weatherT=0; // M8: остаток секунд активного события
|
||||
let weatherGap=WEATHER_GAP_MIN+Math.random()*(WEATHER_GAP_MAX-WEATHER_GAP_MIN); // M8: сек до следующего события
|
||||
let ambient=[]; // фоновые рыбы
|
||||
let scenery=[]; // Этап 44: береговые силуэты на горизонте (по локации)
|
||||
let clouds=[], sparkles=[];
|
||||
let rain=[]; // M8: частицы дождя (визуал)
|
||||
let particles=[], ripples=[];
|
||||
@@ -1820,6 +1822,35 @@ function initFish(){
|
||||
});
|
||||
}
|
||||
}
|
||||
/* Этап 44: береговые силуэты на горизонте — визуальное различение локаций:
|
||||
Река — береговая полоса НА ВСЮ ШИРИНУ (трава, 12–17 px, тёмная кромка у воды) + хвойный
|
||||
лес, посаженный НА ЕЁ КРАЮ (дальний ряд светлее, ближний темнее), Озёра — холмы +
|
||||
лиственные деревья, Море — парусник (медленно дрейфует) + чайки (летят, машут крыльями),
|
||||
Океан — открытый горизонт БЕЗ берега (вода и небо). Элементы: {k:'bank',h} /
|
||||
{k:'pine',layer,by?} (by — база ствола: у реки — верх кромки берега) / {k:'hill'} /
|
||||
{k:'tree'} / {k:'ship',dir,v} / {k:'gull',y,v,ph}.
|
||||
Вызовы initScenery(): resize(), старт (в resize), смена локации в shopClick —
|
||||
новую точку смены локации без initScenery() не добавлять. Дрейф парусника/чаек —
|
||||
updateDecor(dt). Отрисовка drawScenery() — между drawSky() и drawWater() (базу
|
||||
силуэтов закрывает вода). Чисто визуал */
|
||||
function initScenery(){
|
||||
const i=Math.max(0,Math.min(LOCATIONS.length-1,save.location|0));
|
||||
scenery=[];
|
||||
if(i===0){ // Река — береговая полоса на всю ширину + лес в два ряда, посаженный на её кромку
|
||||
const bankH=12+((Math.random()*6)|0); // 12–17 px
|
||||
scenery.push({k:'bank',h:bankH});
|
||||
for(let k=0;k<16;k++) scenery.push({k:'pine',layer:0,x:rand(-12,W+12),h:rand(16,34),by:waterY-bankH});
|
||||
for(let k=0;k<12;k++) scenery.push({k:'pine',layer:1,x:rand(-12,W+12),h:rand(12,26),by:waterY-bankH});
|
||||
}else if(i===1){ // Озёра — холмы (за деревьями) + лиственные деревья
|
||||
const nh=2+((Math.random()*2)|0);
|
||||
for(let k=0;k<nh;k++) scenery.push({k:'hill',x:rand(W*0.1,W*0.9),rx:rand(W*0.22,W*0.42),ry:rand(16,34)});
|
||||
for(let k=0;k<10;k++) scenery.push({k:'tree',x:rand(-8,W+8),h:rand(8,18)});
|
||||
}else if(i===2){ // Море — парусник + чайки (берега нет — открытый горизонт)
|
||||
scenery.push({k:'ship',x:rand(W*0.25,W*0.8),v:rand(2.5,5),s:rand(0.8,1.15),dir:Math.random()<0.5?1:-1});
|
||||
for(let k=0;k<3;k++) scenery.push({k:'gull',x:rand(0,W),y:rand(26,72),v:rand(14,26),ph:rand(0,6.28)});
|
||||
}
|
||||
// i===3 — Океан: берега нет вообще (открытая вода)
|
||||
}
|
||||
function initClouds(){
|
||||
clouds=[];
|
||||
for(let i=0;i<4;i++){
|
||||
@@ -2208,6 +2239,11 @@ function updateBiteFishOut(dt){
|
||||
}
|
||||
function updateDecor(dt){
|
||||
for(const c of clouds){ c.x+=c.v*dt; if(c.x>W+80) c.x=-80; }
|
||||
// Этап 44: дрейф силуэтов — парусник медленно (по курсу), чайки быстрее (вправо)
|
||||
for(const o of scenery){
|
||||
if(o.k==='ship'){ o.x+=o.dir*o.v*dt; if(o.dir>0&&o.x>W+40) o.x=-40; if(o.dir<0&&o.x<-40) o.x=W+40; }
|
||||
else if(o.k==='gull'){ o.x+=o.v*dt; if(o.x>W+30) o.x=-30; }
|
||||
}
|
||||
// M8: дождь — полоски летят вниз-влево, ушедшие за нижний край возвращаются сверху
|
||||
if(weatherActive()==='rain'){
|
||||
for(const dr of rain){
|
||||
@@ -2354,6 +2390,77 @@ function drawSky(){
|
||||
ctx.fill(); ctx.restore();
|
||||
}
|
||||
}
|
||||
/* Этап 44: береговые силуэты по локации (см. initScenery). Ночь — силуэты гаснут
|
||||
к тёмно-синему (hexLerp к #0b1220), парусник/чайки темнеют. Рисуется ПЕРЕД водой:
|
||||
то, что проваливается под линию воды (низ холмов/киль), перекрывается градиентом */
|
||||
function drawScenery(){
|
||||
if(!scenery.length) return;
|
||||
const nm=nightMix();
|
||||
const dusk=c=>hexLerp(c,'#0b1220',nm*0.8);
|
||||
const y0=waterY+1; // базовая линия — чуть под кромкой, чтобы «сесть» на воду
|
||||
for(const o of scenery){
|
||||
if(o.k==='bank'){
|
||||
// Река: береговая полоса на всю ширину (трава + тёмная влажная кромка у воды);
|
||||
// рисуется ПЕРВОЙ (в scenery стоит первой) — лес сажается на её верхнюю кромку
|
||||
ctx.fillStyle=dusk('#4e7a4e');
|
||||
ctx.fillRect(0,waterY-o.h,W,o.h+3);
|
||||
ctx.fillStyle=dusk('#39583c');
|
||||
ctx.fillRect(0,waterY-3,W,6);
|
||||
}else if(o.k==='pine'){
|
||||
// сосна: ствол + 3 яруса (дальний ряд — светлее и выше, ближний — темнее);
|
||||
// база ствола — o.by (у реки — верх кромки берега), по умолчанию линия воды
|
||||
const pb=o.by==null?y0:o.by;
|
||||
ctx.fillStyle=o.layer?dusk('#24452f'):dusk('#3a6a4a');
|
||||
ctx.fillRect(o.x-1.2,pb-3,2.4,4);
|
||||
for(let t=0;t<3;t++){
|
||||
const by=pb-3-t*o.h*0.26, hw=o.h*0.3*(1-0.3*t), th=o.h*0.42;
|
||||
ctx.beginPath(); ctx.moveTo(o.x-hw,by); ctx.lineTo(o.x+hw,by); ctx.lineTo(o.x,by-th);
|
||||
ctx.closePath(); ctx.fill();
|
||||
}
|
||||
}else if(o.k==='hill'){
|
||||
// холм: дуга эллипса над линией воды (низ закроет вода)
|
||||
ctx.fillStyle=dusk('#4a7a5e');
|
||||
ctx.beginPath(); ctx.ellipse(o.x,y0+4,o.rx,o.ry,0,Math.PI,0); ctx.fill();
|
||||
}else if(o.k==='tree'){
|
||||
// лиственное дерево: ствол + крона из трёх кругов
|
||||
ctx.fillStyle=dusk('#3c6b4f');
|
||||
ctx.fillRect(o.x-1,y0-o.h*0.5,2,o.h*0.5+1);
|
||||
ctx.beginPath();
|
||||
ctx.arc(o.x,y0-o.h,o.h*0.42,0,7);
|
||||
ctx.arc(o.x-o.h*0.3,y0-o.h*0.78,o.h*0.34,0,7);
|
||||
ctx.arc(o.x+o.h*0.3,y0-o.h*0.78,o.h*0.34,0,7);
|
||||
ctx.fill();
|
||||
}else if(o.k==='ship'){
|
||||
// парусник: киль/мачта/два паруса (парус светлее силуэта — читается на небе)
|
||||
ctx.save(); ctx.translate(o.x,0); ctx.scale(o.dir,1);
|
||||
const s=o.s;
|
||||
ctx.fillStyle=dusk('#3a4f63');
|
||||
ctx.beginPath(); ctx.moveTo(-14*s,y0-4*s); ctx.lineTo(14*s,y0-4*s); ctx.lineTo(9*s,y0+1); ctx.lineTo(-9*s,y0+1);
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.strokeStyle=dusk('#3a4f63'); ctx.lineWidth=1.6;
|
||||
ctx.beginPath(); ctx.moveTo(0,y0-4*s); ctx.lineTo(0,y0-22*s); ctx.stroke();
|
||||
ctx.fillStyle='rgba(235,242,250,'+(0.9-0.45*nm).toFixed(3)+')';
|
||||
ctx.beginPath(); ctx.moveTo(1.5,y0-4.5*s); ctx.lineTo(1.5,y0-20*s); ctx.lineTo(12*s,y0-5.5*s);
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.beginPath(); ctx.moveTo(-1.5,y0-4.5*s); ctx.lineTo(-1.5,y0-16*s); ctx.lineTo(-10*s,y0-5.5*s);
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.restore();
|
||||
}else if(o.k==='gull'){
|
||||
// чайка: две дуги крыла, взмах синусом; ночью почти гаснет
|
||||
const a=0.65-0.4*nm;
|
||||
if(a<0.12) continue;
|
||||
const y=y0-o.y+Math.sin(time*1.3+o.ph)*3;
|
||||
const f=Math.sin(time*7+o.ph)*0.5+0.5;
|
||||
ctx.strokeStyle='rgba(35,45,60,'+a.toFixed(3)+')';
|
||||
ctx.lineWidth=1.4;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(o.x-6,y);
|
||||
ctx.quadraticCurveTo(o.x-3,y-3-3*f,o.x,y);
|
||||
ctx.quadraticCurveTo(o.x+3,y-3-3*f,o.x+6,y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
function drawWater(){
|
||||
const nm=nightMix();
|
||||
// градиент воды: день ↔ ночь, плавно (визуал); Этап 5: базовые цвета по локации (LOCATIONS)
|
||||
@@ -2385,22 +2492,27 @@ function drawWater(){
|
||||
const a=Math.max(0,Math.sin(time*1.5+s.ph))*0.25*(1-nm*0.5);
|
||||
if(a>0.02){ ctx.fillStyle='rgba(255,255,255,'+a.toFixed(3)+')'; ctx.fillRect(s.x,s.y,3,1.5); }
|
||||
}
|
||||
// волна на поверхности + вторая, глубинная (визуал); Этап 43: характер по локации (LOCATIONS.waves)
|
||||
// волна на поверхности + вторая, глубинная (визуал); Этап 43: характер по локации (LOCATIONS.waves);
|
||||
// ревизия Этапа 44: amp/amp2=0 — линия не рисуется (Река — спокойная вода без волн)
|
||||
const wv=loc.waves;
|
||||
ctx.strokeStyle='rgba(255,255,255,'+(0.55-nm*0.25).toFixed(3)+')'; ctx.lineWidth=2;
|
||||
ctx.beginPath();
|
||||
for(let x=0;x<=W;x+=8){
|
||||
const y=waterY+Math.sin(x*wv.freq+time*wv.speed)*wv.amp;
|
||||
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
||||
if(wv.amp>0){
|
||||
ctx.strokeStyle='rgba(255,255,255,'+(0.55-nm*0.25).toFixed(3)+')'; ctx.lineWidth=2;
|
||||
ctx.beginPath();
|
||||
for(let x=0;x<=W;x+=8){
|
||||
const y=waterY+Math.sin(x*wv.freq+time*wv.speed)*wv.amp;
|
||||
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle='rgba(255,255,255,'+(0.2-nm*0.1).toFixed(3)+')'; ctx.lineWidth=1.5;
|
||||
ctx.beginPath();
|
||||
for(let x=0;x<=W;x+=10){
|
||||
const y=waterY+7+Math.sin(x*wv.freq2-time*wv.speed2+1.7)*wv.amp2;
|
||||
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
||||
if(wv.amp2>0){
|
||||
ctx.strokeStyle='rgba(255,255,255,'+(0.2-nm*0.1).toFixed(3)+')'; ctx.lineWidth=1.5;
|
||||
ctx.beginPath();
|
||||
for(let x=0;x<=W;x+=10){
|
||||
const y=waterY+7+Math.sin(x*wv.freq2-time*wv.speed2+1.7)*wv.amp2;
|
||||
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
function drawFishShape(x,y,size,dir,hue,alpha,wob){
|
||||
ctx.save(); ctx.translate(x,y); ctx.scale(dir,1);
|
||||
@@ -2816,6 +2928,7 @@ function draw(){
|
||||
ctx.translate(rand(-1,1)*shake*8,rand(-1,1)*shake*8);
|
||||
}
|
||||
drawSky();
|
||||
drawScenery(); // Этап 44: береговые силуэты (перед водой — базу закрывает градиент)
|
||||
drawWater();
|
||||
drawAmbientFish();
|
||||
if(biteFish&&biteFish.mode!=='out'){
|
||||
@@ -3468,12 +3581,14 @@ function shopClick(e){
|
||||
save.coins-=loc.cost; save.locations.push(i);
|
||||
save.location=i; // авто-переход на купленную локацию
|
||||
initFish(); // Этап 43: фоновые рыбы по новой воде
|
||||
initScenery(); // Этап 44: силуэты новой воды
|
||||
persist(); updateHUD(); sfx('coin'); renderShop();
|
||||
toast(loc.id===1?t('welcome_lakes'):(loc.id===2?t('welcome_sea'):(loc.id===3?t('welcome_ocean'):tt('bought',{name:trName(loc)})))); // Этап 35/36: у каждой локации свой тост
|
||||
}else{
|
||||
if(save.locations.indexOf(i)<0){ sfx('no'); return; } // не куплено — выбрать нельзя (паттерн защиты buy)
|
||||
save.location=i;
|
||||
initFish(); // Этап 43: фоновые рыбы по новой воде
|
||||
initScenery(); // Этап 44: силуэты новой воды
|
||||
persist(); updateHUD(); sfx('click'); renderShop();
|
||||
toast(tt('location_set',{name:trName(loc)}));
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 152 KiB |
@@ -38,7 +38,8 @@
|
||||
*
|
||||
* Зависит от тест-глобалов игры (AGENTS.md, «Подводные камни»):
|
||||
* state, forceBite, forceWeather, biteTimer, reel, catchFish, SHINY_CHANCE,
|
||||
* SPECIES, setDayTime, save, dayTime, dayPhase, forceLang, setLang, initFish (Этап 43)
|
||||
* SPECIES, setDayTime, save, dayTime, dayPhase, forceLang, setLang, initFish (Этап 43),
|
||||
* initScenery (Этап 44 — силуэты берега в кадрах lake/sea/ocean)
|
||||
* и DOM-кнопок: #btnPlay, #btnShop, #tabAch, #tabJournal, #btnCloseShop,
|
||||
* #btnCardOk, #card, #shop, #menu.
|
||||
*/
|
||||
@@ -185,21 +186,22 @@ async function captureSet(browser, vp) {
|
||||
await shoot(page, `${vp.key}-night.png`);
|
||||
await page.evaluate(() => { setDayTime(0.3); });
|
||||
|
||||
// Озёра — save.location=1, день, dayTime 0.3 (Этап 43: initFish() — фоновые рыбы по воде)
|
||||
await page.evaluate(() => { save.locations = [0, 1]; save.location = 1; initFish(); setDayTime(0.3); });
|
||||
// Озёра — save.location=1, день, dayTime 0.3 (Этап 43: initFish() — рыбы по воде;
|
||||
// Этап 44: initScenery() — силуэты новой воды, без них кадр покажет лес с Реки)
|
||||
await page.evaluate(() => { save.locations = [0, 1]; save.location = 1; initFish(); initScenery(); setDayTime(0.3); });
|
||||
await waitPaint(page);
|
||||
await shoot(page, `${vp.key}-lake.png`);
|
||||
|
||||
// Море — save.location=2, день, dayTime 0.3 (Этап 43)
|
||||
await page.evaluate(() => { save.locations = [0, 1, 2]; save.location = 2; initFish(); setDayTime(0.3); });
|
||||
// Море — save.location=2, день, dayTime 0.3 (Этап 43/44)
|
||||
await page.evaluate(() => { save.locations = [0, 1, 2]; save.location = 2; initFish(); initScenery(); setDayTime(0.3); });
|
||||
await waitPaint(page);
|
||||
await shoot(page, `${vp.key}-sea.png`);
|
||||
|
||||
// Океан — save.location=3, день, dayTime 0.3 (Этап 43)
|
||||
await page.evaluate(() => { save.locations = [0, 1, 2, 3]; save.location = 3; initFish(); setDayTime(0.3); });
|
||||
// Океан — save.location=3, день, dayTime 0.3 (Этап 43/44: силуэтов нет — открытый горизонт)
|
||||
await page.evaluate(() => { save.locations = [0, 1, 2, 3]; save.location = 3; initFish(); initScenery(); setDayTime(0.3); });
|
||||
await waitPaint(page);
|
||||
await shoot(page, `${vp.key}-ocean.png`);
|
||||
await page.evaluate(() => { save.location = 0; save.locations = [0]; initFish(); });
|
||||
await page.evaluate(() => { save.location = 0; save.locations = [0]; initFish(); initScenery(); });
|
||||
|
||||
// вываживание: recall → заброс + клёв + подсечка → REEL
|
||||
// biteTimer сбрасывается при завершении CAST, поэтому ставим его после заброса
|
||||
|
||||