Экономика: базы снастей ×5 (удочки 1750/4500/10000, наживки 1250/3000/7500), цена апгрейда upgCost() — id 0 фикс (250/750/1800, 200/550/1400), id>0 база×UPG_MULT (0.2/0.6/1.2); Озёра 100000; HUD вываживания: ⛵ → иконки 🐟+⚡ (полоска выносливости сдвинута). Тесты: ассерты цен/сиды монет обновлены, +2 (апгрейд по формуле, пин цен), shots 30000. 117/117, exit 0
This commit is contained in:
@@ -29,13 +29,13 @@
|
||||
|
||||
## Где крутить
|
||||
|
||||
- Баланс (рыба/удилища/приманки): константы `SPECIES`, `RODS`, `LURES` в начале скрипта. Апгрейды снастей (уровни 1..4): `ROD_UPG_COST`/`LURE_UPG_COST`/`ROD_UPG_NEXT`/`LURE_UPG_NEXT` (ДАННЫЕ), уровни `save.rodLv`/`save.lureLv`, эффективные статы — `effRod()`/`effLure()` (глобалы `rod()`/`lure()` отдают эффективные), гейтинг открытия — в `itemRow()`/`shopClick()`.
|
||||
- Баланс (рыба/удилища/приманки): константы `SPECIES`, `RODS`, `LURES` в начале скрипта (базы ×5: удочки 1750/4500/10000, наживки 1250/3000/7500). Апгрейды снастей (уровни 1..4): цена перехода — `upgCost(type,i,lv)`: id 0 — фикс `ROD_UPG_COST`/`LURE_UPG_COST` (250/750/1800 и 200/550/1400), id>0 — база×`UPG_MULT` (0.2/0.6/1.2, округление до 10); подписи уровней `ROD_UPG_NEXT`/`LURE_UPG_NEXT`, уровни `save.rodLv`/`save.lureLv`, эффективные статы — `effRod()`/`effLure()` (глобалы `rod()`/`lure()` отдают эффективные), гейтинг открытия — в `itemRow()`/`shopClick()`.
|
||||
- Ночь (M3): длительность суток `DAY_LENGTH` (сек), ночная таблица рыб `SPECIES_NIGHT`, фонарик — апгрейд 1 любого удилища (`save.rodLv[save.rod]>=2`, проверка `hasLight()` в ветке WAIT, секция «M3: Цикл дня/ночи»).
|
||||
- Босс (M4): тест-форс появления `forceBoss`, фазы боя `stateBoss`/`bossPhase`, босс-ветки в `updateReel()` (обрыв фазы) и `SNAP` в `updateGame()` (авто-переход), награда ×5 в `catchFish()`.
|
||||
- Варианты рыб (M6): таблица `VARIANTS` (порядок ролла: gold → silver → rainbow, иначе normal), `variantChance()` — единственная точка вероятностей (дождь M8 уже здесь — ×2,5, см. строку «Погода (M8)»; gold — живая `SHINY_CHANCE`), тест-форс `forceVariant`, босс всегда normal. Рыбу нельзя улучшать: «Улучшить вариант» (M6) убрано в этапе 21 — карточка улова только бейдж/оформление по варианту.
|
||||
- Погода (M8): таблица `WEATHER` (rain — `variantMult` ×2,5; fog — `biteMult` ×1,5), таймеры в реальном времени — `updateWeather()` в `frame()` (gap `WEATHER_GAP_MIN..MAX` 120–240 с, длительность `WEATHER_DURATION` 120 с, под форсом не гоняются), `weatherActive()` — единственная точка чтения (форс приоритетнее), эффекты только в `variantChance()` (rain) и `biteChance()` (fog), тест-форс `forceWeather` ('rain'/'fog'/null, паттерн `forceBoss`); погода не персистится в сейв. Заметка: туман может дать p>1 → насыщение 100% клёва (осознанно).
|
||||
- Скины (M-skins): таблицы `BOAT_SKINS`/`BOBBER_SKINS` (лодка: Обычная 0 / «Красная лодка» 800 / «Лодка-домик» 2000 🪙; поплавок: Обычный 0 / Красный 500 / Неоновый 1500 🪙), сейв `save.boatSkin(s)`/`save.bobberSkin(s)` + миграция в `loadSave()` (включая гарантию бесплатного скина 0), вкладка «Скины» 🎨 (`#tabSkins`, строка `skinRow(list,i,owned,equipped,group)`, ветки buyskin/select в `shopClick()`: покупка со списанием и авто-экипировкой, выбор — только купленное), отрисовка `drawBoat()`/`drawBobber()` с clamp 0..2 (скин 2: каюта у лодки, неоновое свечение `shadowBlur` в save/restore у поплавка); чистая косметика — без эффектов на геймплей.
|
||||
- Локации (этап 5): таблица `LOCATIONS` (Река 0 — бесплатно / «Озёра» 25000 🪙; `water` — 6 hex [dayTop,dayMid,dayBot,nightTop,nightMid,nightBot] для градиента `drawWater()`, nightMix-логика та же), сейв `save.locations[]`/`save.location` + миграция в `loadSave()` (гарантия бесплатной Реки, паттерн скинов), вкладка «Локации» 🗺️ (`#tabLocations`, строка `locationRow(loc,i,owned,selected)`, ветки buylocation/selectlocation в `shopClick()`: покупка — списание, push, АВТО-ПЕРЕХОД `save.location`, тост «Добро пожаловать на Озёра!»; выбор — только купленное). Таблицы рыб: `RIVER_SPECIES(_NIGHT)` — ТОЛЬКО 8 базовых видов (роллы реки не меняются: pin-тесты §9j(b)/M4/M6 завязаны на веса и порядок — новые виды в реку НЕ добавлять!), `LAKE_SPECIES(_NIGHT)` — все 11 видов (веса `LAKE_DAY_WEIGHTS`/`LAKE_NIGHT_WEIGHTS`); `SPECIES`/`SPECIES_NIGHT` — полный словарь 11 видов (новые: zander/chuch/crayfish, ночные веса 6/10/2.5). `pickSpecies()` выбирает таблицу по `save.location` и dayPhase (логика ролла та же); босс (sturge) есть в обеих локациях; ачивка species_8 — теперь «Поймай все виды» (все 11, id не менять — старые сейвы).
|
||||
- Локации (этап 5): таблица `LOCATIONS` (Река 0 — бесплатно / «Озёра» 100000 🪙; `water` — 6 hex [dayTop,dayMid,dayBot,nightTop,nightMid,nightBot] для градиента `drawWater()`, nightMix-логика та же), сейв `save.locations[]`/`save.location` + миграция в `loadSave()` (гарантия бесплатной Реки, паттерн скинов), вкладка «Локации» 🗺️ (`#tabLocations`, строка `locationRow(loc,i,owned,selected)`, ветки buylocation/selectlocation в `shopClick()`: покупка — списание, push, АВТО-ПЕРЕХОД `save.location`, тост «Добро пожаловать на Озёра!»; выбор — только купленное). Таблицы рыб: `RIVER_SPECIES(_NIGHT)` — ТОЛЬКО 8 базовых видов (роллы реки не меняются: pin-тесты §9j(b)/M4/M6 завязаны на веса и порядок — новые виды в реку НЕ добавлять!), `LAKE_SPECIES(_NIGHT)` — все 11 видов (веса `LAKE_DAY_WEIGHTS`/`LAKE_NIGHT_WEIGHTS`); `SPECIES`/`SPECIES_NIGHT` — полный словарь 11 видов (новые: zander/chuch/crayfish, ночные веса 6/10/2.5). `pickSpecies()` выбирает таблицу по `save.location` и dayPhase (логика ролла та же); босс (sturge) есть в обеих локациях; ачивка species_8 — теперь «Поймай все виды» (все 11, id не менять — старые сейвы).
|
||||
- Сложность вываживания: `updateReel()`.
|
||||
- Физика боя (спринт 4): `fishForce()` (сила рыбы: рывок `(16+34·st)·(0.5+0.9·ag)·(0.7+0.5·ov)`, затишье `5+7·ag`, уставшая ×0.45) и `updateReel()` — выносливость `st` (старт `st0`, флаг `tired` при `st≤0.12`: рывки ×0.45, втягивание быстрее до ×1.6), тормоз катушки (черта `drag` в `RODS`: в рывке выше тормоза леска скользит `over·0.22`), натяжение (`10+over·1.1+22·ag`, ×0.35 в затишье), втягивание `(11+3·reel)·(1+0.6·(1−st))`, свободный бег `F·0.32`, escape-бар растёт только при затишье без удержания (отпускание в рывок безопасно), near-miss `1.45·d0`, `d0=45+7·w`. Пустые клёвы: ролл `biteChance()` в ветке WAIT (тест-форс `forceBite`). Тесты (§10) завязаны на эти константы — при ребалансе гонять Monte-Carlo точными формулами (docs/PHYSICS-RESEARCH.md, §4.7) и править тесты в том же коммите.
|
||||
- Награды (M2): шаблоны квестов `QUEST_TPLS`, награды стрика `STREAK_REWARDS`, достижения `ACHIEVEMENTS` — секция «M2. Награды» после сейва.
|
||||
|
||||
+27
-20
@@ -295,13 +295,13 @@ const RARITY_COLORS=['#9fc3e2','#2ecc71','#4da3ff','#c56cf0','#ffd75e'];
|
||||
const NIGHT_WEIGHTS={carass:40,perch:80,bream:10,carp:8,pike:22,catfish:18,sturge:4,gold:1.2,zander:6,chuch:10,crayfish:2.5};
|
||||
const SPECIES_NIGHT=SPECIES.map(s=>Object.assign({},s,{weight:NIGHT_WEIGHTS[s.id]}));
|
||||
|
||||
/* Этап 5: Локации — Река (родная вода, бесплатно) и Озёра (25000 🪙, новые рыбы).
|
||||
/* Этап 5: Локации — Река (родная вода, бесплатно) и Озёра (100000 🪙, новые рыбы).
|
||||
water: [dayTop, dayMid, dayBot, nightTop, nightMid, nightBot] — базовые пары цветов
|
||||
градиента drawWater(): день ↔ ночь плавно через nightMix (логика не меняется) */
|
||||
const LOCATIONS=[
|
||||
{id:0, name:'Река', cost:0, desc:'родная вода',
|
||||
water:['#2f9bd8','#1f6fb0','#0a2f52','#0d2246','#091c3a','#041226']},
|
||||
{id:1, name:'Озёра', cost:25000, desc:'новые рыбы: судак, хариус, гигантский рак',
|
||||
{id:1, name:'Озёра', cost:100000, desc:'новые рыбы: судак, хариус, гигантский рак',
|
||||
water:['#3d9c8a','#20726a','#0c423c','#0f2b33','#0a2129','#04141a']}
|
||||
];
|
||||
/* Река — только 8 базовых видов: роллы реки остаются прежними (новые виды заходят только в Озёра).
|
||||
@@ -365,22 +365,30 @@ let forceWeather=null; // M8: тест-форс погоды ('rain'/'fog'/null
|
||||
|
||||
const RODS=[
|
||||
{id:0, name:'Дедова удочка', icon:'🧹', cost:0, maxW:3, line:100, drag:34, reel:1.0, size:0.00, desc:'До 3 кг · Нагрузка 100 · Drag 34 · Подсечка ×1.0'},
|
||||
{id:1, name:'Стеклопластик', icon:'🎣', cost:250, maxW:8, line:140, drag:47, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Drag 47 · Подсечка ×1.3'},
|
||||
{id:2, name:'Карбоновая', icon:'🏹', cost:700, maxW:18, line:190, drag:63, reel:1.7, size:0.16, desc:'До 18 кг · Нагрузка 190 · Drag 63 · Подсечка ×1.7'},
|
||||
{id:3, name:'Титан', icon:'💪', cost:1600, maxW:40, line:260, drag:87, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Drag 87 · Подсечка ×2.3'},
|
||||
{id:1, name:'Стеклопластик', icon:'🎣', cost:1750, maxW:8, line:140, drag:47, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Drag 47 · Подсечка ×1.3'},
|
||||
{id:2, name:'Карбоновая', icon:'🏹', cost:4500, maxW:18, line:190, drag:63, reel:1.7, size:0.16, desc:'До 18 кг · Нагрузка 190 · Drag 63 · Подсечка ×1.7'},
|
||||
{id:3, name:'Титан', icon:'💪', cost:10000, maxW:40, line:260, drag:87, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Drag 87 · Подсечка ×2.3'},
|
||||
];
|
||||
const LURES=[
|
||||
{id:0, name:'Простой крючок', icon:'🪝', cost:0, cast:1.00, bite:1.0, rare:0.0, size:0.00, desc:'Надёжно, но рыба не в восторге'},
|
||||
{id:1, name:'Червь', icon:'🪱', cost:150, cast:1.00, bite:1.3, rare:0.2, size:0.12, desc:'Клёв на 30% чаще, шанс крупной выше'},
|
||||
{id:2, name:'Воблер', icon:'🐟', cost:450, cast:1.15, bite:1.2, rare:0.8, size:0.28, desc:'Дальше бросок, приманивает крупных'},
|
||||
{id:3, name:'Золотая блёстка',icon:'✨', cost:1200, cast:1.20, bite:1.4, rare:2.0, size:0.50, desc:'Лучшее, что есть. К ней плывут легенды'},
|
||||
{id:1, name:'Червь', icon:'🪱', cost:1250, cast:1.00, bite:1.3, rare:0.2, size:0.12, desc:'Клёв на 30% чаще, шанс крупной выше'},
|
||||
{id:2, name:'Воблер', icon:'🐟', cost:3000, cast:1.15, bite:1.2, rare:0.8, size:0.28, desc:'Дальше бросок, приманивает крупных'},
|
||||
{id:3, name:'Золотая блёстка',icon:'✨', cost:7500, cast:1.20, bite:1.4, rare:2.0, size:0.50, desc:'Лучшее, что есть. К ней плывут легенды'},
|
||||
];
|
||||
/* M5: апгрейды снастей — уровни 1..4 (макс), стоимость переходов 1→2, 2→3, 3→4 */
|
||||
const ROD_UPG_COST=[400,1200,3000];
|
||||
const LURE_UPG_COST=[300,900,2200];
|
||||
/* M5: апгрейды снастей — уровни 1..4 (макс). id 0 (база 0) — фиксированные цены
|
||||
переходов 1→2, 2→3, 3→4; снасти id>0 — доля UPG_MULT от их базовой стоимости */
|
||||
const ROD_UPG_COST=[250,750,1800];
|
||||
const LURE_UPG_COST=[200,550,1400];
|
||||
const UPG_MULT=[0.2,0.6,1.2]; // доля базы: →2 ур ×0.2, →3 ур ×0.6, →4 ур ×1.2
|
||||
/* Что даёт следующий уровень (индекс = текущий уровень − 1) — подпись в лавке */
|
||||
const ROD_UPG_NEXT=['фонарик (ночь), леска ×1.15','тормоз ×1.15, катушка ×1.10','леска ещё ×1.15, крупные +0.10'];
|
||||
const LURE_UPG_NEXT=['клёв ×1.15','редкие +1.0','крупные +0.15'];
|
||||
/* Цена перехода снасти i на следующий уровень (lv — текущий 1..3): id 0 — фикс, id>0 — база×множитель */
|
||||
function upgCost(type,i,lv){
|
||||
if(i===0) return (type==='rod'?ROD_UPG_COST:LURE_UPG_COST)[lv-1];
|
||||
const base=(type==='rod'?RODS:LURES)[i].cost;
|
||||
return Math.round(base*UPG_MULT[lv-1]/10)*10;
|
||||
}
|
||||
|
||||
/* Этап 4: скины лодки и поплавка — чистая косметика (coin-sink), в лавке вкладка «Скины» */
|
||||
const BOAT_SKINS=[
|
||||
@@ -1672,14 +1680,15 @@ function drawMeters(){
|
||||
ctx.font='20px system-ui'; ctx.textAlign='center';
|
||||
ctx.fillText('🐟',fx,dy-6+Math.sin(time*10)*1.5);
|
||||
ctx.font='18px system-ui';
|
||||
ctx.fillText('⛵',dx+4,dy+dh+18);
|
||||
ctx.fillText('🐟',dx+12,dy+dh+18);
|
||||
ctx.fillText('⚡',dx+32,dy+dh+18);
|
||||
// Спринт 4: выносливость — тонкая полоска под дистанцией (затухает к нулю)
|
||||
// T2b: начинается после эмодзи ⛵ (dx+18), чтобы не перекрывать лодку
|
||||
// T2b: начинается после иконок 🐟⚡ (dx+44), чтобы не перекрывать их
|
||||
const sdh=5, sdy=dy+dh+3;
|
||||
ctx.fillStyle='rgba(0,0,0,0.3)'; rr(dx+18,sdy,dw-18,sdh,2.5); ctx.fill();
|
||||
ctx.fillStyle='rgba(0,0,0,0.3)'; rr(dx+44,sdy,dw-44,sdh,2.5); ctx.fill();
|
||||
if(reel.st>0.01){
|
||||
ctx.fillStyle=reel.tired?'#7d5ba6':'#b07cc6';
|
||||
rr(dx+19.5,sdy+1.5,Math.max(1.5,(dw-21)*reel.st),sdh-3,2); ctx.fill();
|
||||
rr(dx+45.5,sdy+1.5,Math.max(1.5,(dw-47)*reel.st),sdh-3,2); ctx.fill();
|
||||
}
|
||||
if(reel.tired){
|
||||
ctx.font='13px system-ui'; ctx.textAlign='left';
|
||||
@@ -1838,7 +1847,6 @@ function lvPips(lv){ let s=''; for(let k=1;k<=4;k++) s+=k<=lv?'●':'○'; retur
|
||||
function itemRow(item,i,type,owned,cur){
|
||||
const list=type==='rod'?RODS:LURES;
|
||||
const lvMap=type==='rod'?(save.rodLv||{}):(save.lureLv||{});
|
||||
const costMap=type==='rod'?ROD_UPG_COST:LURE_UPG_COST;
|
||||
const nextTxt=type==='rod'?ROD_UPG_NEXT:LURE_UPG_NEXT;
|
||||
const lv=owned?Math.max(1,Math.min(4,lvMap[i]>=1?lvMap[i]:1)):1;
|
||||
// M5: гейтинг — снасть №i открывается, когда снасть №(i−1) того же типа на 4 ур.
|
||||
@@ -1848,7 +1856,7 @@ function itemRow(item,i,type,owned,cur){
|
||||
if(owned){
|
||||
btns+=cur?'<button class="buybtn current" disabled>✓ Выбрано</button>'
|
||||
:'<button class="buybtn owned" data-act="use" data-type="'+type+'" data-i="'+i+'">Выбрать</button>';
|
||||
if(lv<4) btns+='<button class="buybtn upgbt" data-act="upgrade" data-type="'+type+'" data-i="'+i+'">⬆ '+nextTxt[lv-1]+' · '+costMap[lv-1]+' 🪙</button>';
|
||||
if(lv<4) btns+='<button class="buybtn upgbt" data-act="upgrade" data-type="'+type+'" data-i="'+i+'">⬆ '+nextTxt[lv-1]+' · '+upgCost(type,i,lv)+' 🪙</button>';
|
||||
}else if(!locked){
|
||||
btns='<button class="buybtn" data-act="buy" data-type="'+type+'" data-i="'+i+'">'+item.cost+' 🪙</button>';
|
||||
}
|
||||
@@ -1961,7 +1969,7 @@ function renderShop(){
|
||||
BOBBER_SKINS.forEach((s,i)=>{ h+=skinRow(BOBBER_SKINS,i,save.bobberSkins.indexOf(i)>=0,save.bobberSkin===i,'bobber'); });
|
||||
body.innerHTML=h;
|
||||
}else if(shopTab==='locations'){
|
||||
// Этап 5: локации — Река (родная вода) и Озёра (25000 🪙, новые рыбы)
|
||||
// Этап 5: локации — Река (родная вода) и Озёра (100000 🪙, новые рыбы)
|
||||
let h='<div class="section-title">Локации</div>';
|
||||
LOCATIONS.forEach((loc,i)=>{ h+=locationRow(loc,i,save.locations.indexOf(i)>=0,save.location===i); });
|
||||
body.innerHTML=h;
|
||||
@@ -2015,7 +2023,6 @@ function shopClick(e){
|
||||
if(!save.rodLv||typeof save.rodLv!=='object') save.rodLv={};
|
||||
if(!save.lureLv||typeof save.lureLv!=='object') save.lureLv={};
|
||||
const lvMap=type==='rod'?save.rodLv:save.lureLv;
|
||||
const costMap=type==='rod'?ROD_UPG_COST:LURE_UPG_COST;
|
||||
const item=list[i];
|
||||
if(b.dataset.act==='buy'){
|
||||
if(i>0&&lvMap[list[i-1].id]<4){ sfx('no'); toast('Сначала улучши «'+list[i-1].name+'» до 4 ур.'); return; } // M5: гейтинг
|
||||
@@ -2029,7 +2036,7 @@ function shopClick(e){
|
||||
if(owned.indexOf(i)<0){ sfx('no'); return; } // M5: не куплено — апгрейд недоступен (паттерн защиты buy)
|
||||
const lv=(lvMap[i]>=1)?lvMap[i]:1;
|
||||
if(lv>=4) return;
|
||||
const cost=costMap[lv-1];
|
||||
const cost=upgCost(type,i,lv);
|
||||
if(save.coins<cost){ sfx('no'); toast('Не хватает монет 🪙'); return; }
|
||||
save.coins-=cost; lvMap[i]=lv+1; // M5: апгрейд — уровень++
|
||||
persist(); updateHUD(); sfx('coin'); renderShop();
|
||||
|
||||
@@ -326,13 +326,13 @@ async function forceCatch() {
|
||||
await shot('orca-night.png');
|
||||
|
||||
// 9c. M5: фонарик в лавке — апгрейд 1 удилища (не приманка id=4): в строке
|
||||
// удилища кнопка «⬆ 400 🪙» и подпись «фонарик (ночь)». Лавка открыта на
|
||||
// удилища кнопка «⬆ 250 🪙» и подпись «фонарик (ночь)». Лавка открыта на
|
||||
// вкладке, где её закрыли в шаге 7 (🏅) — переключаем на Снасти.
|
||||
await clickByText('🛒');
|
||||
await waitCond('!document.getElementById("shop").classList.contains("hidden") ? "yes" : "shop hidden"', 15000, 'лавка открыта');
|
||||
await clickByID('tabTackle');
|
||||
await waitCond('document.getElementById("shopBody").textContent.indexOf("фонарик") >= 0 ? "yes" : "no flask"', 15000, 'апгрейд-фонарик в снастях');
|
||||
ok('лавка: фонарик — апгрейд 1 удилища (⬆ 400, пометка про ночь)', true);
|
||||
ok('лавка: фонарик — апгрейд 1 удилища (⬆ 250, пометка про ночь)', true);
|
||||
await clickByText('×');
|
||||
await waitCond('document.getElementById("shop").classList.contains("hidden") ? "yes" : "shop open"', 15000, 'лавка закрыта');
|
||||
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ async function snap(page, name) {
|
||||
pickSpecies = window.__pick;
|
||||
});
|
||||
|
||||
await page.evaluate(() => { save.coins = 10000; });
|
||||
await page.evaluate(() => { save.coins = 30000; });
|
||||
await page.click('#btnShop');
|
||||
await waitFor(page, () => !document.getElementById('shop').classList.contains('hidden'));
|
||||
await waitPaint(page);
|
||||
|
||||
+37
-21
@@ -191,11 +191,11 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
ok('у удилища указан предел веса', rodDesc.includes('кг'));
|
||||
await page.click('#shopBody [data-act="buy"][data-type="rod"]');
|
||||
const rodAfter = await page.evaluate(() => ({ rod: save.rod, coins: save.coins, owned: save.rods.length }));
|
||||
ok('куплено удилище', rodAfter.rod === 1 && rodAfter.coins === 9750 && rodAfter.owned === 2, JSON.stringify(rodAfter));
|
||||
ok('куплено удилище', rodAfter.rod === 1 && rodAfter.coins === 8250 && rodAfter.owned === 2, JSON.stringify(rodAfter));
|
||||
|
||||
await page.click('#shopBody [data-act="buy"][data-type="lure"]');
|
||||
const lureAfter = await page.evaluate(() => ({ lure: save.lure, coins: save.coins }));
|
||||
ok('куплена приманка', lureAfter.lure === 1 && lureAfter.coins === 9600, JSON.stringify(lureAfter));
|
||||
ok('куплена приманка', lureAfter.lure === 1 && lureAfter.coins === 7000, JSON.stringify(lureAfter));
|
||||
|
||||
await page.click('#tabJournal');
|
||||
await sleep(100);
|
||||
@@ -210,7 +210,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
await page.reload({ waitUntil: 'load' });
|
||||
await sleep(500);
|
||||
const saved = await page.evaluate(() => ({ coins: save.coins, rod: save.rod, lure: save.lure, casts: save.casts }));
|
||||
ok('сохранение после reload', saved.coins === 9600 && saved.rod === 1 && saved.lure === 1 && saved.casts === 5, JSON.stringify(saved));
|
||||
ok('сохранение после reload', saved.coins === 7000 && saved.rod === 1 && saved.lure === 1 && saved.casts === 5, JSON.stringify(saved));
|
||||
|
||||
// --- 9. Сброс данных (играть сначала нужно зайти в игру) ---
|
||||
await page.click('#btnPlay');
|
||||
@@ -222,7 +222,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
await page.click('#btnResetNo');
|
||||
ok('отмена: диалог закрыт, данные не тронуты',
|
||||
await page.$eval('#resetDlg', el => el.classList.contains('hidden'))
|
||||
&& (await page.evaluate(() => save.coins)) === 9600);
|
||||
&& (await page.evaluate(() => save.coins)) === 7000);
|
||||
await page.click('#btnReset');
|
||||
await sleep(100);
|
||||
await page.click('#btnResetYes');
|
||||
@@ -565,8 +565,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const row=items.find(el=>el.querySelector('[data-act="upgrade"][data-type="rod"]'));
|
||||
return row?{txt:row.textContent}:null;
|
||||
});
|
||||
ok('M5 лавка: фонарик = апгрейд 1 удилища (кнопка ⬆ 400 🪙, подпись «фонарик (ночь)»)',
|
||||
m3shop && m3shop.txt.indexOf('400')>=0 && m3shop.txt.indexOf('фонарик')>=0 && m3shop.txt.indexOf('ноч')>=0,
|
||||
ok('M5 лавка: фонарик = апгрейд 1 удилища (кнопка ⬆ 250 🪙, подпись «фонарик (ночь)»)',
|
||||
m3shop && m3shop.txt.indexOf('250')>=0 && m3shop.txt.indexOf('фонарик')>=0 && m3shop.txt.indexOf('ноч')>=0,
|
||||
m3shop?m3shop.txt:'нет строки апгрейда');
|
||||
await page.click('#btnCloseShop');
|
||||
|
||||
@@ -1038,8 +1038,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
};
|
||||
return { r1: info(byName('Стеклопластик')), l1: info(byName('Червь')) };
|
||||
});
|
||||
ok('M5 гейтинг лавки: после rodLv[0]=4 / lureLv[0]=4 — locked снят, кнопки buy «250 🪙» / «150 🪙»',
|
||||
!m5g2.r1.locked && m5g2.r1.buyTxt === '250 🪙' && !m5g2.l1.locked && m5g2.l1.buyTxt === '150 🪙',
|
||||
ok('M5 гейтинг лавки: после rodLv[0]=4 / lureLv[0]=4 — locked снят, кнопки buy «1750 🪙» / «1250 🪙»',
|
||||
!m5g2.r1.locked && m5g2.r1.buyTxt === '1750 🪙' && !m5g2.l1.locked && m5g2.l1.buyTxt === '1250 🪙',
|
||||
JSON.stringify(m5g2));
|
||||
|
||||
// (8) двойная защита: buy-ветка shopClick при закрытом гейтинге не покупает
|
||||
@@ -1061,7 +1061,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
&& m5d.toast.includes('Сначала улучши'),
|
||||
JSON.stringify(m5d));
|
||||
|
||||
// (9) покупка апгрейда: 1000 🪙 → 600, rodLv[0]=2, фонарик включился
|
||||
// (9) покупка апгрейда: 1000 🪙 → 750, rodLv[0]=2, фонарик включился
|
||||
await page.evaluate(() => {
|
||||
save.rods = [0]; save.lures = [0]; save.rod = 0; save.lure = 0;
|
||||
save.rodLv = { 0: 1 }; save.lureLv = { 0: 1 }; save.coins = 1000;
|
||||
@@ -1072,8 +1072,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
coins: save.coins, lv: save.rodLv[0], light: hasLight(),
|
||||
toast: document.getElementById('toast').textContent
|
||||
}));
|
||||
ok('M5 покупка апгрейда: rod 0 1→2 ур. за 400 🪙 (1000→600), hasLight()=true, тост «Улучшено … → 2 ур»',
|
||||
m5u.coins === 600 && m5u.lv === 2 && m5u.light === true
|
||||
ok('M5 покупка апгрейда: rod 0 1→2 ур. за 250 🪙 (1000→750), hasLight()=true, тост «Улучшено … → 2 ур»',
|
||||
m5u.coins === 750 && m5u.lv === 2 && m5u.light === true
|
||||
&& m5u.toast.includes('Улучшено') && m5u.toast.includes('2 ур'),
|
||||
JSON.stringify(m5u));
|
||||
|
||||
@@ -1093,7 +1093,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
coins: save.coins, lv: save.rodLv[0],
|
||||
toast: document.getElementById('toast').textContent
|
||||
}));
|
||||
ok('M5 не хватает монет: 100 🪙 на апгрейд за 400 — coins=100, уровень не изменился, тост «Не хватает монет»',
|
||||
ok('M5 не хватает монет: 100 🪙 на апгрейд за 250 — coins=100, уровень не изменился, тост «Не хватает монет»',
|
||||
m5p.coins === 100 && m5p.lv === 1 && m5p.toast.includes('Не хватает монет'),
|
||||
JSON.stringify(m5p));
|
||||
await page.click('#btnCloseShop');
|
||||
@@ -1135,7 +1135,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
forceBite = false; state = 'IDLE'; reel = null; biteFish = null;
|
||||
setDayTime(0.3);
|
||||
save.rods = [0]; save.lures = [0]; save.rod = 0; save.lure = 0;
|
||||
save.rodLv = { 0: 4 }; save.lureLv = { 0: 1 }; save.coins = 1000;
|
||||
save.rodLv = { 0: 4 }; save.lureLv = { 0: 1 }; save.coins = 3000;
|
||||
});
|
||||
await page.click('#btnShop');
|
||||
await sleep(150);
|
||||
@@ -1146,11 +1146,27 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
rods: save.rods.join(','), lv: save.rodLv[1],
|
||||
rod: save.rod, coins: save.coins
|
||||
}));
|
||||
ok('M5 покупка разблокированной снасти: rod 1 за 250 🪙 — rods=[0,1], rodLv[1]=1, снасть экипирована, coins=750',
|
||||
m5b.rods === '0,1' && m5b.lv === 1 && m5b.rod === 1 && m5b.coins === 750,
|
||||
ok('M5 покупка разблокированной снасти: rod 1 за 1750 🪙 — rods=[0,1], rodLv[1]=1, снасть экипирована, coins=1250',
|
||||
m5b.rods === '0,1' && m5b.lv === 1 && m5b.rod === 1 && m5b.coins === 1250,
|
||||
JSON.stringify(m5b));
|
||||
|
||||
// (14b) апгрейд по формуле: rod 1 (база 1750) 1→2 ур. = 1750×0.2 = 350
|
||||
await page.click('#shopBody [data-act="upgrade"][data-type="rod"][data-i="1"]');
|
||||
const m5bu = await page.evaluate(() => ({ lv: save.rodLv[1], coins: save.coins }));
|
||||
ok('M5 апгрейд по формуле: rod 1 1→2 ур. за 350 🪙 (база 1750×0.2) — coins=1250→900, rodLv[1]=2',
|
||||
m5bu.lv === 2 && m5bu.coins === 900, JSON.stringify(m5bu));
|
||||
await page.click('#btnCloseShop');
|
||||
|
||||
// (15) пин цен апгрейдов: id 0 — фикс, id>0 — база×(0.2/0.6/1.2)
|
||||
const m5c = await page.evaluate(() => ({
|
||||
r: [0,1,2,3].map(i=>[1,2,3].map(lv=>upgCost('rod',i,lv)).join(',')),
|
||||
l: [0,1,2,3].map(i=>[1,2,3].map(lv=>upgCost('lure',i,lv)).join(','))
|
||||
}));
|
||||
ok('M5 цены апгрейдов: id 0 фикс (250/750/1800, 200/550/1400); id>0 = база×(0.2/0.6/1.2)',
|
||||
m5c.r.join('|')==='250,750,1800|350,1050,2100|900,2700,5400|2000,6000,12000'
|
||||
&& m5c.l.join('|')==='200,550,1400|250,750,1500|600,1800,3600|1500,4500,9000',
|
||||
JSON.stringify(m5c));
|
||||
|
||||
// --- 10c. M6: варианты рыб (VARIANTS, forceVariant, карточка, full_variants) ---
|
||||
// (1) данные: таблица VARIANTS и живой gold-шанс (variantChance → SHINY_CHANCE)
|
||||
const m6v = await page.evaluate(() => {
|
||||
@@ -1638,9 +1654,9 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
l.water.every(h => typeof h === 'string' && /^#[0-9a-fA-F]{6}$/.test(h)))
|
||||
};
|
||||
});
|
||||
ok('локации данные: SPECIES=11 — zander «Судак» r2 40🪙 1.0–6.0, chuch «Хариус» r1 30🪙 0.2–2.0, crayfish «Гигантский рак» r3 200🪙 0.2–1.0; LOCATIONS 2 (Река 0 / Озёра 25000), у каждой 6 hex в water',
|
||||
ok('локации данные: SPECIES=11 — zander «Судак» r2 40🪙 1.0–6.0, chuch «Хариус» r1 30🪙 0.2–2.0, crayfish «Гигантский рак» r3 200🪙 0.2–1.0; LOCATIONS 2 (Река 0 / Озёра 100000), у каждой 6 hex в water',
|
||||
lc1.n === 11 && lc1.z === 'Судак,2,40,1,6' && lc1.c === 'Хариус,1,30,0.2,2' && lc1.k === 'Гигантский рак,3,200,0.2,1'
|
||||
&& lc1.locs === 2 && lc1.costs === '0,25000' && lc1.names === 'Река,Озёра' && lc1.water === true,
|
||||
&& lc1.locs === 2 && lc1.costs === '0,100000' && lc1.names === 'Река,Озёра' && lc1.water === true,
|
||||
JSON.stringify(lc1));
|
||||
|
||||
// (2) таблицы: река — ровно старые 8 (порядок), озёра — все 11, ночные веса — числа
|
||||
@@ -1726,10 +1742,10 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
&& lc5.c.locs.join(',') === '0' && lc5.c.loc === 0,
|
||||
JSON.stringify(lc5));
|
||||
|
||||
// (6) покупка: 26000 монет → «Озёра» (25000) — списание, авто-переход, тост
|
||||
// (6) покупка: 101000 монет → «Озёра» (100000) — списание, авто-переход, тост
|
||||
const lcSt0 = await page.evaluate(() => state);
|
||||
if (lcSt0 === 'MENU') { await page.click('#btnPlay'); await sleep(150); }
|
||||
await page.evaluate(() => { save.coins = 26000; });
|
||||
await page.evaluate(() => { save.coins = 101000; });
|
||||
await page.click('#btnShop');
|
||||
await sleep(150);
|
||||
await page.click('#tabLocations');
|
||||
@@ -1739,7 +1755,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
coins: save.coins, locs: save.locations.slice(), loc: save.location,
|
||||
toast: document.getElementById('toast').textContent
|
||||
}));
|
||||
ok('локации покупка: 26000 → «Озёра» (25000) — coins=1000, locations=[0,1], авто-переход location=1, тост «Добро пожаловать на Озёра!»',
|
||||
ok('локации покупка: 101000 → «Озёра» (100000) — coins=1000, locations=[0,1], авто-переход location=1, тост «Добро пожаловать на Озёра!»',
|
||||
lc6.coins === 1000 && lc6.locs.join(',') === '0,1' && lc6.loc === 1 && lc6.toast.includes('Добро пожаловать на Озёра!'),
|
||||
JSON.stringify(lc6));
|
||||
|
||||
@@ -1765,7 +1781,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
shopClick({ target: b });
|
||||
});
|
||||
const lc7c = await page.evaluate(() => save.location);
|
||||
ok('локации защиты: 100 < 25000 — coins/locations не изменились, тост «Не хватает монет»; buylocation при locations=[0,1] — без дубля; selectlocation не купленной — location не изменился',
|
||||
ok('локации защиты: 100 < 100000 — coins/locations не изменились, тост «Не хватает монет»; buylocation при locations=[0,1] — без дубля; selectlocation не купленной — location не изменился',
|
||||
lc7a.coins === 100 && lc7a.locs === '0' && lc7a.toast.includes('Не хватает монет 🪙')
|
||||
&& lc7b.coins === 100 && lc7b.locs.join(',') === '0,1' && lc7b.locs.length === 2
|
||||
&& lc7c === 0,
|
||||
|
||||
Reference in New Issue
Block a user