Skip to content

Commit 2dfac1b

Browse files
author
web3blind
committed
Add explicit camp rest option
1 parent e5a2438 commit 2dfac1b

9 files changed

Lines changed: 115 additions & 14 deletions

File tree

app/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@
120120
<script src="js/config.js?v=20260712a"></script>
121121

122122
<!-- I18n -->
123-
<script src="js/i18n/ru.js?v=20260713a"></script>
124-
<script src="js/i18n/en.js?v=20260713a"></script>
123+
<script src="js/i18n/ru.js?v=20260713b"></script>
124+
<script src="js/i18n/en.js?v=20260713b"></script>
125125

126126
<!-- Utilities -->
127127
<script src="js/utils/helpers.js?v=20260621l"></script>
@@ -132,7 +132,7 @@
132132
<script src="js/blockchain/connection.js"></script>
133133
<script src="js/blockchain/account.js"></script>
134134
<script src="js/blockchain/history-source.js?v=20260621d"></script>
135-
<script src="js/blockchain/broadcast.js?v=20260712b"></script>
135+
<script src="js/blockchain/broadcast.js?v=20260713a"></script>
136136
<script src="js/blockchain/invite.js"></script>
137137

138138
<!-- Protocols -->
@@ -170,7 +170,7 @@
170170
<script src="js/engine/quest-system.js?v=20260621e"></script>
171171
<script src="js/engine/loci.js"></script>
172172
<script src="js/engine/npc-framework.js"></script>
173-
<script src="js/engine/state-engine.js?v=20260712c"></script>
173+
<script src="js/engine/state-engine.js?v=20260713b"></script>
174174
<script src="js/engine/daily-leaderboard-storage.js"></script>
175175
<script src="js/engine/daily-leaderboard.js"></script>
176176

@@ -188,7 +188,7 @@
188188
<script src="js/ui/screens/onboarding.js"></script>
189189
<script src="js/ui/screens/home.js?v=20260713a"></script>
190190
<script src="js/ui/screens/character.js?v=20260713a"></script>
191-
<script src="js/ui/screens/hunt.js"></script>
191+
<script src="js/ui/screens/hunt.js?v=20260713b"></script>
192192
<script src="js/ui/screens/inventory.js?v=20260621l"></script>
193193
<script src="js/ui/screens/chronicle.js?v=20260712a"></script>
194194
<script src="js/ui/screens/duel.js"></script>

app/js/blockchain/broadcast.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,17 @@ var VizBroadcast = (function() {
253253
}, callback);
254254
}
255255

256+
/**
257+
* Rest at camp: restore HP through a normal VM action.
258+
* @param {Function} callback
259+
*/
260+
function restAction(callback) {
261+
gameAction({
262+
t: cfg.ACTION_TYPES.REST,
263+
d: {}
264+
}, callback);
265+
}
266+
256267
/**
257268
* Update account metadata (Grimoire)
258269
* @param {string} jsonMetadata - JSON string
@@ -343,6 +354,7 @@ var VizBroadcast = (function() {
343354
armageddonAction: armageddonAction,
344355
templeOffering: templeOffering,
345356
questAction: questAction,
357+
restAction: restAction,
346358
updateMetadata: updateMetadata,
347359
chroniclePost: chroniclePost,
348360
delegateShares: delegateShares,

app/js/engine/state-engine.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,18 @@ var StateEngine = (function() {
14071407
return events[0];
14081408
}
14091409

1410+
/**
1411+
* Process rest for live UI — same authoritative mutation path as replay.
1412+
* @param {string} account
1413+
* @param {number} blockNum
1414+
* @returns {Object|null}
1415+
*/
1416+
function processRestResult(account, blockNum) {
1417+
var events = _handleRest(account, blockNum || 0);
1418+
if (!events.length) return null;
1419+
return events[0];
1420+
}
1421+
14101422
/**
14111423
* Process crafting for live UI — same authoritative mutation path as replay.
14121424
* @param {string} account
@@ -1507,6 +1519,7 @@ var StateEngine = (function() {
15071519
getTempleBlessing: getTempleBlessing,
15081520
processHuntResult: processHuntResult,
15091521
processMoveResult: processMoveResult,
1522+
processRestResult: processRestResult,
15101523
processCraftResult: processCraftResult,
15111524
processMarketListResult: processMarketListResult,
15121525
processMarketCancelResult: processMarketCancelResult,

app/js/i18n/en.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ var LangEN = {
167167
stat_for: 'Fortune',
168168

169169
// Hunt
170+
hunt_rest_title: 'Rest at camp',
171+
hunt_rest_desc: 'Safe rest fully restores HP before the next hunt.',
172+
hunt_rest_hp_now: 'Current: {hp} HP.',
173+
hunt_rest_button: 'Rest at camp and restore HP',
174+
hunt_rest_full: 'HP already full',
175+
hunt_rest_pending: 'Recording camp rest in the World...',
176+
hunt_rest_success: 'You rested at camp. HP is fully restored.',
177+
hunt_rest_error: 'Could not record rest. Check the World connection and try again.',
170178
hunt_title: 'Hunt',
171179
hunt_choose_creature: 'Choose your prey',
172180
hunt_no_creatures_here: 'There are currently no huntable creatures in this location.',
@@ -1064,7 +1072,7 @@ var LangEN = {
10641072
help_mana_text: 'Mana is your energy (0–100%). Every spell costs mana. Full regeneration takes ~5 days. Shown as a percentage — 1.00% = 100 basis points on the blockchain.',
10651073

10661074
help_section_hp: 'HP & Recovery',
1067-
help_hp_text: 'HP is your health. Max HP grows from your level, class, and Resilience: base 100 + level × 10 + RES × 3, then your class multiplier is applied. It does not regenerate automatically to full — passive regen slowly restores up to 30% of your max HP (+1 HP every 100 blocks, ~5 min). Full recovery: visit the Hunt screen (camp rest) or use a Health Scroll (craftable or dropped by creatures).',
1075+
help_hp_text: 'HP is your health. Max HP grows from your level, class, and Resilience: base 100 + level × 10 + RES × 3, then your class multiplier is applied. It does not regenerate automatically to full — passive regen slowly restores up to 30% of your max HP (+1 HP every 100 blocks, ~5 min). Full recovery: open the Hunt screen and press “Rest at camp”, or use a Health Scroll (craftable or dropped by creatures).',
10681076

10691077
help_section_quests: 'Quests & Prophecies',
10701078
help_quests_text: 'Regular quests are accepted in the Quests screen. “Traveler” currently means visiting 3 different regions via the world map — open the map, choose another region, and press “Travel”. The Daily Prophecy must also be accepted first, then completed. If it tells you to bless others, do that in the Chronicle with the “Bless” button on other mages\' entries.',

app/js/i18n/ru.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ var LangRU = {
167167
stat_for: 'Удача',
168168

169169
// Hunt
170+
hunt_rest_title: 'Отдых у костра',
171+
hunt_rest_desc: 'Безопасный отдых полностью восстанавливает HP перед следующей охотой.',
172+
hunt_rest_hp_now: 'Сейчас: {hp} HP.',
173+
hunt_rest_button: 'Отдохнуть у костра и восстановить HP',
174+
hunt_rest_full: 'HP уже полное',
175+
hunt_rest_pending: 'Отдых у костра записывается в Мир...',
176+
hunt_rest_success: 'Вы отдохнули у костра. HP восстановлено полностью.',
177+
hunt_rest_error: 'Не удалось записать отдых. Проверьте связь с Миром и попробуйте ещё раз.',
170178
hunt_title: 'Охота',
171179
hunt_choose_creature: 'Выбери добычу',
172180
hunt_no_creatures_here: 'В этой локации сейчас нет доступной добычи для охоты.',
@@ -1064,7 +1072,7 @@ var LangRU = {
10641072
help_mana_text: 'Мана — ваша энергия (0–100%). Каждое заклинание тратит ману. Полное восстановление занимает ~5 дней. Отображается в процентах — 1.00% = 100 базисных пунктов в блокчейне.',
10651073

10661074
help_section_hp: 'HP и Восстановление',
1067-
help_hp_text: 'HP — ваше здоровье. Максимум HP растёт от уровня, класса и Стойкости (Resilience): базово 100 + уровень × 10 + RES × 3, затем применяется модификатор класса. До максимума здоровье само не восстанавливается — пассивная регенерация возвращает до 30% от максимума (+1 HP каждые 100 блоков, ~5 мин). Полное восстановление: зайди на экран Охоты (отдых в лагере) или используй Свиток Здоровья (крафтится или падает с существ).',
1075+
help_hp_text: 'HP — ваше здоровье. Максимум HP растёт от уровня, класса и Стойкости (Resilience): базово 100 + уровень × 10 + RES × 3, затем применяется модификатор класса. До максимума здоровье само не восстанавливается — пассивная регенерация возвращает до 30% от максимума (+1 HP каждые 100 блоков, ~5 мин). Полное восстановление: открой экран Охоты и нажми «Отдых у костра» или используй Свиток Здоровья (крафтится или падает с существ).',
10681076

10691077
help_section_quests: 'Задания и Пророчества',
10701078
help_quests_text: 'Обычные задания берутся во вкладке Задания. «Путешественник» сейчас означает: посетить 3 разных региона через карту мира — откройте карту, выберите другой регион и нажмите «Путешествие». Ежедневное Пророчество тоже нужно сначала принять, а затем выполнить его цель. Если в цели сказано благословлять — это делается в Хронике кнопкой «Благословить» у записей других магов.',

app/js/ui/screens/hunt.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,17 @@ var HuntScreen = (function() {
3131
selectedCreature = null;
3232
selectedSpell = null;
3333

34+
var needsRest = ch && ch.maxHp && ch.hp < ch.maxHp;
35+
var hpText = ch && ch.maxHp ? (Helpers.formatNumber(ch.hp || 0) + ' / ' + Helpers.formatNumber(ch.maxHp)) : '';
3436
var html = '<div class="hunt-screen">' +
3537
'<h1>' + t('hunt_title') + '</h1>' +
38+
'<section class="hunt-rest-section" aria-label="' + t('hunt_rest_title') + '">' +
39+
'<h2>⛺ ' + t('hunt_rest_title') + '</h2>' +
40+
'<p class="quest-desc">' + t('hunt_rest_desc') + (hpText ? ' ' + t('hunt_rest_hp_now', {hp: hpText}) : '') + '</p>' +
41+
'<button class="btn btn-secondary" id="btn-rest-camp" type="button"' + (needsRest ? '' : ' disabled') + '>' +
42+
(needsRest ? t('hunt_rest_button') : t('hunt_rest_full')) +
43+
'</button>' +
44+
'</section>' +
3645
'<h2>' + t('hunt_choose_creature') + '</h2>';
3746

3847
if (!creatures.length) {
@@ -143,6 +152,11 @@ var HuntScreen = (function() {
143152

144153
Helpers.$('btn-attack').addEventListener('click', _doHunt);
145154

155+
var restBtn = Helpers.$('btn-rest-camp');
156+
if (restBtn) {
157+
restBtn.addEventListener('click', _doRest);
158+
}
159+
146160
var returnBtn = Helpers.$('btn-return-commons');
147161
if (returnBtn) {
148162
returnBtn.addEventListener('click', function() {
@@ -177,6 +191,39 @@ var HuntScreen = (function() {
177191
Helpers.$('btn-attack').disabled = !(selectedCreature && selectedSpell);
178192
}
179193

194+
function _doRest() {
195+
var t = Helpers.t;
196+
var resultEl = Helpers.$('hunt-result');
197+
var btn = Helpers.$('btn-rest-camp');
198+
var user = VizAccount.getCurrentUser();
199+
var ch = StateEngine.getCharacter(user);
200+
if (!user || !ch) {
201+
if (resultEl) resultEl.innerHTML = '<p class="error">' + t('error_network') + '</p>';
202+
return;
203+
}
204+
if (!ch.maxHp || ch.hp >= ch.maxHp) return;
205+
if (btn) btn.disabled = true;
206+
if (resultEl) resultEl.innerHTML = '<p class="pending">' + t('hunt_rest_pending') + '</p>';
207+
SoundManager.play('tap');
208+
VizBroadcast.restAction(function(err, broadcastResult) {
209+
if (err) {
210+
if (resultEl) resultEl.innerHTML = '<p class="error">' + t('hunt_rest_error') + '</p>';
211+
if (btn) btn.disabled = false;
212+
return;
213+
}
214+
var blockNum = (broadcastResult && broadcastResult.action && broadcastResult.action.block_num) || (StateEngine.getState().headBlock || 0);
215+
var ev = StateEngine.processRestResult(user, blockNum);
216+
if (!ev) {
217+
if (resultEl) resultEl.innerHTML = '<p class="error">' + t('hunt_rest_error') + '</p>';
218+
if (btn) btn.disabled = false;
219+
return;
220+
}
221+
CheckpointSystem.saveCheckpoint('global', blockNum, StateEngine.getState(), function() {});
222+
if (resultEl) resultEl.innerHTML = '<p class="success">' + t('hunt_rest_success') + '</p>';
223+
render();
224+
});
225+
}
226+
180227
function _doHunt() {
181228
if (!selectedCreature || !selectedSpell) return;
182229
var t = Helpers.t;

app/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"short_name": "VizMagic",
44
"description": "Viz Magic — accessible browser RPG on the VIZ blockchain",
55
"id": "https://vizmagic.web3blind.xyz/",
6-
"start_url": "/?pwa=viz-magic-v48",
6+
"start_url": "/?pwa=viz-magic-v49",
77
"scope": "/",
88
"display": "standalone",
99
"display_override": ["standalone", "minimal-ui"],

app/sw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Viz Magic — Service Worker
2-
var CACHE_NAME = 'viz-magic-v48';
2+
var CACHE_NAME = 'viz-magic-v49';
33
var ASSETS = [
44
'/',
55
'/index.html',

tests/player-bug-regressions.test.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const toastJs = read('app/js/ui/components/toast.js');
4646
const craftingJs = read('app/js/ui/screens/crafting.js');
4747
const inventoryJs = read('app/js/ui/screens/inventory.js');
4848
const huntJs = read('app/js/ui/screens/hunt.js');
49+
const helpJs = read('app/js/ui/screens/help.js');
4950
const mapJs = read('app/js/ui/screens/map.js');
5051
const chronicleJs = read('app/js/ui/screens/chronicle.js');
5152
const mapScreenJs = read('app/js/ui/screens/map.js');
@@ -344,7 +345,7 @@ test('high-traffic UI narration, screen announcements, and inventory stat labels
344345

345346
test('service worker updates quickly and keeps navigations network-first', function () {
346347
const swJs = read('app/sw.js');
347-
assert.ok(/viz-magic-v48/.test(swJs), 'service worker cache version should be bumped');
348+
assert.ok(/viz-magic-v49/.test(swJs), 'service worker cache version should be bumped');
348349
assert.ok(/self\.skipWaiting\(\)/.test(swJs), 'service worker should activate new cache without waiting for all tabs to close');
349350
assert.ok(/self\.clients\.claim\(\)/.test(swJs), 'service worker should claim clients after activation');
350351
assert.ok(/event\.request\.mode === 'navigate'[\s\S]*fetch\(event\.request\)/.test(swJs), 'navigation requests should prefer network to avoid stale cached index');
@@ -393,7 +394,7 @@ test('mobile entry helpers cover keyboard paste, home-screen shortcut, nav parit
393394
assert.ok(/SoundManager\.setVolume\(sfxVolume \/ 100\)/.test(read('app/js/ui/screens/settings.js')), 'settings should apply stored SFX volume on render');
394395
assert.ok(/localStorage\.setItem\(STORAGE_PREFIX \+ 'sfx_volume'/.test(read('app/js/ui/sound.js')), 'sound manager should persist SFX volume');
395396
assert.ok(/var volume = _getStoredNumber\('sfx_volume', 0\.5\)/.test(read('app/js/ui/sound.js')), 'sound manager should restore persisted SFX volume');
396-
assert.ok(/viz-magic-v48/.test(read('app/sw.js')), 'service worker cache should be bumped for UI changes');
397+
assert.ok(/viz-magic-v49/.test(read('app/sw.js')), 'service worker cache should be bumped for UI changes');
397398
});
398399

399400

@@ -455,8 +456,8 @@ test('magical weather is labelled and affects hunts', function () {
455456
assert.ok(/forecast-card-effect/.test(homeJs + mainCss), 'forecast effect column needs its own thematic icon/card');
456457
assert.ok(/function getCurrentFestival/.test(worldEventsJs), 'magical holidays should sometimes appear in the forecast');
457458
assert.ok(/festival_today_prefix/.test(homeJs + ruJs + enJs), 'forecast holidays should have localized copy');
458-
assert.ok(/i18n\/ru.js\?v=20260713a/.test(indexHtml), 'Russian weather copy must be cache-busted');
459-
assert.ok(/i18n\/en.js\?v=20260713a/.test(indexHtml), 'English weather copy must be cache-busted');
459+
assert.ok(/i18n\/ru.js\?v=20260713b/.test(indexHtml), 'Russian weather copy must be cache-busted');
460+
assert.ok(/i18n\/en.js\?v=20260713b/.test(indexHtml), 'English weather copy must be cache-busted');
460461
assert.ok(/home.js\?v=20260713a/.test(indexHtml), 'home forecast layout must be cache-busted');
461462
assert.ok(/quests.js\?v=20260712a/.test(indexHtml), 'quest-limit UX must be cache-busted');
462463
assert.ok(/nav.js\?v=20260712b/.test(indexHtml), 'bottom tray nav must be cache-busted');
@@ -523,7 +524,7 @@ test('reported mobile UX issues have explicit fixes', function () {
523524

524525
test('PWA icon and HP heart use expressive color accents', function () {
525526
assert.ok(/viz-magic-192\.png\?v=20260713a/.test(indexHtml), 'PWA icon link should be cache-busted after plus placement/color update');
526-
assert.ok(/viz-magic-v48/.test(read('app/manifest.json')), 'manifest start URL should change so launchers can refresh icons');
527+
assert.ok(/viz-magic-v49/.test(read('app/manifest.json')), 'manifest start URL should change so launchers can refresh icons');
527528
assert.ok(/label:' HP'/.test(homeJs), 'HP label should use a red heart emoji variant');
528529
});
529530

@@ -538,6 +539,18 @@ test('character screen uses current home-scale vitals and growth explainers', fu
538539
assert.ok(/char_mana_explainer/.test(characterScreenJs + ruJs + enJs), 'character screen should explain mana growth');
539540
});
540541

542+
543+
test('hunt screen exposes explicit camp rest promised by Help', function () {
544+
assert.ok(/hunt.js\?v=20260713b/.test(indexHtml), 'hunt screen should be cache-busted');
545+
assert.ok(/broadcast.js\?v=20260713a/.test(indexHtml), 'broadcast helper should be cache-busted for restAction');
546+
assert.ok(/state-engine.js\?v=20260713b/.test(indexHtml), 'state-engine should be cache-busted for processRestResult');
547+
assert.ok(/function restAction\(callback\)/.test(broadcastJs), 'broadcast helper should expose restAction');
548+
assert.ok(/function processRestResult\(account, blockNum\)/.test(stateEngineJs), 'state engine should expose live rest processing');
549+
assert.ok(/hunt-rest-section/.test(huntJs) && /btn-rest-camp/.test(huntJs), 'Hunt should show a visible rest-at-camp section and button');
550+
assert.ok(/VizBroadcast\.restAction/.test(huntJs), 'rest button should record a normal VM rest action');
551+
assert.ok(/Отдых у костра/.test(ruJs) && /нажми «Отдых у костра»/.test(ruJs), 'Russian Help should name the visible rest button');
552+
});
553+
541554
if (process.exitCode) {
542555
process.exit(process.exitCode);
543556
}

0 commit comments

Comments
 (0)